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
  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 retracting 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 to automate SIMH to automate here documents. After all, is the standard editor.

Cloning the repository

First, the repo and into it.

Building 4.3BSD

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

Running 4.3BSD

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

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 in , we see a classical stack-based buffer overflow: A 512-byte buffer can be overflowed by , 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.

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:

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 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 for testing

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

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

Next, comment out the conditional. If you're using , you'll have to save with , since the file is read-only. Compile with (debugging symbols) once you're done.

Transferring files the old-school way

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

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

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 to download our exploit buffer. We named it with earlier.

Debugging and disassembling with

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

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

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.

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

Dump 200 longwords from SP to inspect our buffer.

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.

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

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

Download the new exploit buffer with estimated return address.

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

If you hit a 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 payload from Metasploit, since writing the shellcode out by hand is rather tedious.

and should be familiar. is much like . is probably the instruction that needs explanation. You can think of it like the instruction in x86. There's a limit, index (register), and displacement (jump). We're using it to our file descriptors without duplicating code. It's basically a 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 , connect back to the attacker with , the socket descriptor across standard input, output, and error, and finally execute with .

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

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 instruction to a so we can skip ahead to our shellcode. Download the new exploit buffer.

Set up to catch the shell.

And directly execute with the exploit buffer this time.

Check your tab or window.

We've got a shell! It might be helpful to do something like to make your new shell more usable. You can also use to spawn a PTY.

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

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 and a little knowledge of the SMTP protocol. is used to clean the mail message.

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

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

Emacs 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 tree with Emacs source.

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

Preparing a SUID-root

Exploiting via

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 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.'

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

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.

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

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

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

Attachments

  • Original document
  • Permalink

Disclaimer

Rapid7 Inc. published this content on 02 January 2019 and is solely responsible for the information contained herein. Distributed by Public, unedited and unaltered, on 02 January 2019 14:13:01 UTC