Add more detailed analysis from mullender
These come from the book 'Obfuscated C and Other Mysteries' but I don't believe this violates any copyright since the only copied text (and code, some of which I had to typo fix but certainly it might be that I made some typos as well) is from the authors of the entry not the book (well one of the authors).
This commit is contained in:
1 parent
55798ccbe6
commit
5af91ec8b9
1 file changed
+199
-1
+199
-1
@@ -99,7 +99,14 @@ BTW: this remains my (Landon Curt Noll's) all time favorite entries!
|
|||||||
These remarks, found at
|
These remarks, found at
|
||||||
[https://lainsystems.com/posts/exploring-mullender-dot-c/](https://lainsystems.com/posts/exploring-mullender-dot-c/),
|
[https://lainsystems.com/posts/exploring-mullender-dot-c/](https://lainsystems.com/posts/exploring-mullender-dot-c/),
|
||||||
were provided by Sjoerd Mullender years later. We thank the author of the
|
were provided by Sjoerd Mullender years later. We thank the author of the
|
||||||
article for the quote!
|
article for the quote! For a more detailed analysis, taken from the Obfuscated C
|
||||||
|
and Other Mysteries, see below. We hope that this is okay with the author of the
|
||||||
|
book. Considering that the analysis is entirely the authors' comments we don't
|
||||||
|
think this will be a problem. Unfortunately the excerpt was PDF and it did not
|
||||||
|
copy paste well. [Cody Boone Ferguson](/winners.html#Cody_Boone_Ferguson) had to
|
||||||
|
go back and forth to type so it's possible he made a typo though he also fixed
|
||||||
|
some typos found in the extract. As for the other comments:
|
||||||
|
|
||||||
|
|
||||||
I have never known a lot about the VAX assembly, so we used the C compiler to
|
I have never known a lot about the VAX assembly, so we used the C compiler to
|
||||||
create the VAX code. We didn't write it ourselves from scratch as we did with
|
create the VAX code. We didn't write it ourselves from scratch as we did with
|
||||||
@@ -141,6 +148,197 @@ with that, but we didn't need that for this program. In fact, we made it as
|
|||||||
beautifier which would re-indent your program to make the layout look better. Our
|
beautifier which would re-indent your program to make the layout look better. Our
|
||||||
program is idempotent under `cb`.
|
program is idempotent under `cb`.
|
||||||
|
|
||||||
|
### More detailed analysis
|
||||||
|
|
||||||
|
When this program is compiled, the compiler places the array somewhere in
|
||||||
|
memory, just like it places any compiled code somewhere in memory. Usually, the
|
||||||
|
C startup code (`crt0.o`) calls a routine named `main`. The loader fills in the address in
|
||||||
|
the startup code, but, at least on the old systems where this program
|
||||||
|
ran, it doesn't know that the `main` in this program isn't code but data!
|
||||||
|
|
||||||
|
When the program is run, the C startup code transfers control to the location
|
||||||
|
`main`. The contents of the array just happen to be machine instructions for
|
||||||
|
both a PDP-11 and a VAX.
|
||||||
|
|
||||||
|
On the VAX, the routine main is called with the `calls` instruction. This
|
||||||
|
instruction uses the first (2-byte) word of the called routine as a mask of
|
||||||
|
registers that are to be saved on the stack. In other words, on the VAX the
|
||||||
|
first word can be anything. On the PDP, the first word is a branch instruction
|
||||||
|
that branches over the VAX code. The PDP and VAX codes are thus completely
|
||||||
|
separate.
|
||||||
|
|
||||||
|
The PDP and VAX codes implement the same algorithm:
|
||||||
|
|
||||||
|
```c
|
||||||
|
for (;;) {
|
||||||
|
write(1, " :-)\b\b\b\b", 9);
|
||||||
|
delay();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The result is that the symbols `:-)` move over the screen. `delay` is
|
||||||
|
implemented differently on the PDP, where we used a nonexistent system call
|
||||||
|
(`sys 55`), and on the VAX where we used a delay loop.
|
||||||
|
|
||||||
|
My co-author, Robbert, and I had earlier written a similar program for an
|
||||||
|
assignment on the PDP-11. Along came the first Obfuscated C Code Contest, and
|
||||||
|
we decided that we should write a program like this, but make it run on two
|
||||||
|
different architectures.
|
||||||
|
|
||||||
|
We didn't think long about what the program should do, so it does something very simple.
|
||||||
|
|
||||||
|
We started with writing the PDP code in assembler. We both knew PDP-11
|
||||||
|
assembler, so that was no problem. The assembler code we came up with is as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
pdp:
|
||||||
|
mov pc,r4
|
||||||
|
tst -(r4)
|
||||||
|
sub $9, r4
|
||||||
|
mov r4,0f
|
||||||
|
mov $1, r0
|
||||||
|
sys 4; 0:0; 9
|
||||||
|
mov $1000, r2
|
||||||
|
1:
|
||||||
|
sys 55
|
||||||
|
sob r2,1b
|
||||||
|
br pdp
|
||||||
|
```
|
||||||
|
|
||||||
|
This is not the code we originally wrote, but it is the code that we ultimately
|
||||||
|
used in the program. The string to be printed is shared by the VAX and the PDP
|
||||||
|
code and is located between the two sections.
|
||||||
|
|
||||||
|
First, the program deals with figuring out the address of the string. Then the
|
||||||
|
program counter is saved in a scratch register. Since the program counter points
|
||||||
|
at the second instruction, we subtract 2 from the scratch register in the second
|
||||||
|
instruction. Then we subtract the length of the string and store the result in
|
||||||
|
the location with label 0. This has to do with the calling sequence of system
|
||||||
|
calls on the PDP. Following the `sys` instruction is the system call number (4
|
||||||
|
for `write`), the address of the buffer (pointed to by label 0), and the length
|
||||||
|
of the buffer (9). The file descriptor is in register `r0`. The rest of the code
|
||||||
|
implements a delay loop. In each iteration, a non-existing system call (55)
|
||||||
|
slows things down.
|
||||||
|
|
||||||
|
We assembled this program and extracted the machine code from the resulting
|
||||||
|
object file. We used this code in the VAX part. Since neither of us was fluent
|
||||||
|
in VAX assembly, we wrote the VAX code in C and massaged the compiler output.
|
||||||
|
The VAX assembly program that we came up with is as follows:
|
||||||
|
|
||||||
|
|
||||||
|
```asm
|
||||||
|
vax: .word 0400 + (pdp - vax) / 2 - 1
|
||||||
|
1:
|
||||||
|
pushl $9
|
||||||
|
pushal str
|
||||||
|
pushl $1
|
||||||
|
calls $3, write
|
||||||
|
cvtwl $32767, r2
|
||||||
|
|
||||||
|
2:
|
||||||
|
decl r2
|
||||||
|
jneq 2b
|
||||||
|
jbr 1b
|
||||||
|
|
||||||
|
write: .word 0
|
||||||
|
chmk $4
|
||||||
|
ret
|
||||||
|
|
||||||
|
str: .ascii " :-)\b\b\b\b"
|
||||||
|
|
||||||
|
pdp: .word 4548, 3044, 58820, 9, 4407, 6, 5568, 1, 35076, 0, 9, 5570, 512, 35117, 32386, 496
|
||||||
|
```
|
||||||
|
|
||||||
|
The first word (after the label `vax`) is the PDP branch instruction. PDP branch
|
||||||
|
instructions are octal `400+` the distance divided by 2. The string that both
|
||||||
|
the PDP and VAX programs use is after the `str` label, and the PDP code is after
|
||||||
|
the pap label.
|
||||||
|
|
||||||
|
On the VAX, the program pushes 9 (the length of the string), the address of the
|
||||||
|
string and 1 (the file descriptor) on the stack and calls `write`. Since we
|
||||||
|
didn't know the exact calling sequence for system calls, we just copied the
|
||||||
|
source for the `write` system call stub into our program. After `write`
|
||||||
|
finishes, the program executes a delay loop, after which it jumps back to the
|
||||||
|
start of the program.
|
||||||
|
|
||||||
|
We assembled this program, and extracted the machine code from the object file.
|
||||||
|
After this we only had to convert the machine code to ASCII and write a little
|
||||||
|
bit of C to glue everything together. We wanted to use different formats for
|
||||||
|
each constant in the resulting array, and we wanted to choose the format
|
||||||
|
randomly. So we wrote a program to choose an appropriate format at random. The
|
||||||
|
program we wrote for that follows. This program actually also extracted the
|
||||||
|
machine code from the object file.
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <a.out.h>
|
||||||
|
|
||||||
|
main(argc, argv) char **argv;
|
||||||
|
{
|
||||||
|
register FILE *fp;
|
||||||
|
register short pos = 0, c, n;
|
||||||
|
register char *fmt;
|
||||||
|
if (argc != 2) {
|
||||||
|
fprintf (stderr, "Usage: %s file\n", argv[0]);
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((fp = fopen(argv[1], "r")) == NULL) {
|
||||||
|
fprintf(stderr, "%s: can't open %s\n", argv[0], argv[1]);
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek (fp, (long) sizeof (struct exec), 0);
|
||||||
|
printf("/* portable between VAX and PDP11 */\n\n");
|
||||||
|
printf ("short main[] = {\n");
|
||||||
|
for (;;) {
|
||||||
|
if (pos == 0)
|
||||||
|
printf("\t");
|
||||||
|
|
||||||
|
c=getc(fp) & 0377;
|
||||||
|
if (feof(fp)) break;
|
||||||
|
n = getc(fp) << 8|c;
|
||||||
|
|
||||||
|
switch (rand() % 5) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
fmt = "%d"; break;
|
||||||
|
case 2:
|
||||||
|
fmt = "%u"; break;
|
||||||
|
case 3:
|
||||||
|
fmt = "0%o"; break;
|
||||||
|
case 4:
|
||||||
|
fmt = "0x%x"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (32 <= n && n < 127 && (rand() % 4)) fmt = "'%c'";
|
||||||
|
printf(n < 8 ? "%d" : fmt, n);
|
||||||
|
printf(",");
|
||||||
|
if (pos++ == 8) {
|
||||||
|
printf("\n");
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
else printf(" ");
|
||||||
|
|
||||||
|
printf("};\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
As can be seen, there is a slight preference for decimal, and also a character
|
||||||
|
format is sometimes used, but only if the data is a printable ASCII character.
|
||||||
|
|
||||||
|
When we ran this program, we were almost completely satisfied with the result.
|
||||||
|
The only problem we had was that the program had chosen an octal representation
|
||||||
|
for the first word. Since everybody knows what a PDP-11 branch instruction looks
|
||||||
|
like (everyone knows that the traditional magic word for an executable, `0407`,
|
||||||
|
is a PDP-11 branch), we changed that to decimal. After checking the size of the
|
||||||
|
resulting program we saw that it was one byte too long. The limit was 512 bytes,
|
||||||
|
and our program was 513 bytes. So we changed the word `and` in the comment to
|
||||||
|
`&&`.
|
||||||
|
|
||||||
## Copyright and CC BY-SA 4.0 License:
|
## Copyright and CC BY-SA 4.0 License:
|
||||||
|
|
||||||
This file is Copyright (c) 2023 by Landon Curt Noll. All Rights Reserved.
|
This file is Copyright (c) 2023 by Landon Curt Noll. All Rights Reserved.
|
||||||
|
|||||||
Reference in new issue
Block a user