Morris Worm Code Walkthrough
- YT :: https://www.youtube.com/watch?v=xNcrfveKlDU
- Original title :: The Worm that changed everything
A code-level tour of the 1988 Morris worm — the program that infected roughly 10% of all connected computers in 13 hours and produced the first conviction under the Computer Fraud and Abuse Act. Prime reads the actual C source: the main loop, the "battle royale" population-control protocol between worm instances, the decoy phone-home to an innocent Berkeley host, the password cracker, the three spread vectors (rsh, fingerd, sendmail debug mode), and the two-stage bootstrap where a tiny grappling-hook program turns itself into a shell wired to a TCP socket. His verdict: simple, readable code that still does something out of science fiction.
The event and the aftermath
Released 2 November 1988 around 18:00 PST from an MIT machine, the worm reached Pittsburgh within an hour, Stanford and Minnesota within three, Berkeley/Princeton/UNC/Maryland within four, and Harvard, Chicago, Colorado State, Purdue and Georgia Tech by the 13-hour mark — about 10% of the connected internet, which scaled to today would be on the order of a billion devices. Researchers from Harvard, Stanford, MIT and NASA decompiled it collectively, which led to Robert Tappan Morris's arrest six months later and the first CFAA conviction. Morris got probation, went on to found startups with Paul Graham, and co-founded Y Combinator. Not everyone was impressed at the time: Prime quotes the contemporary analysis calling the code unfinished, "clever, but not particularly gifted" — a critique he finds hilarious given the worm took down a tenth of the network.
Why "worm"
The term comes from John Brunner's 1975 novel The Shockwave Rider, where "tapeworm" programs replicate across the net so that killing one copy is useless — you have to kill them all. That is exactly the Morris design.
Structure: a game loop
The worm is a small program with a loop that repeats a fixed sequence: try to spread to other hosts, run population control, phone home (fake), crack local passwords, fork and exit, sleep, and check whether it should quit.
Spreading is attempted first, so that if the worm is caught quickly it has already had a chance to jump elsewhere. All the cryptically named h* functions (hg, hl, ha) are spread attempts.
Population control and the battle royale
One in seven worms rolls immortal: it never checks again and only dies if a human kills the process. The rest try to connect to a local server on port 23357 (a hex constant Prime jokes you should be converting in your head). If no connection, the worm just carries on. If it connects, the two instances play a duel: each sends a random number, both sum the pair, odd means the server dies, even means the client dies. The survivor then tries to become the server itself.
Bug number one: if any step of that exchange fails, the function simply returns without ever deciding to die — an accidental immortal worm. Bug number two: re-binding the server socket within five seconds often fails because the port is still busy, producing more accidental immortals. This is a major reason the worm spread far faster than Morris intended, since the whole one-in-seven-plus-duel scheme exists precisely to keep the population from exploding as each infection spawns another connecting instance.
The Ernie decoy
With a 1-in-15 chance the worm "reports" a break-in by sending a single-byte datagram to ernie.berkeley.edu — a host with nothing to do with the attack. It is deliberate misdirection to send anyone decompiling the binary hunting for a nonexistent operator, and it does not even work: the code opens a TCP connection and then sends a UDP packet. Prime's favourite piece of the source, and a moment of sympathy for Ernie.
Password cracking
The worm carries a table of roughly 432 common passwords (banks, bananas, aztec, and friends) and grinds them against local accounts. Because a single crypt attempt could cost up to five seconds of CPU, it deliberately cracks only a few at a time and then sleeps, so it does not show up in top. Cracked username/password pairs are stored because they often work on remote machines too, making the next hop easier. Prime digresses into his favourite pull request of all time: someone asking for their password to be removed from a public wordlist so hackers cannot hack them ("dolphins").
Hiding
After each pass the worm forks and the parent exits, so the PID keeps changing every few minutes and any retrospective look at CPU usage points at a process that no longer exists. It also sleeps for two minutes at a time to stay quiet. It only actually quits if it lost the population-control duel — and even then, not until it has cracked at least ten passwords, so infected machines kept worms alive longer than the design implied.
Three ways in
Spreading relied almost entirely on trust relationships of the era. First choice was rsh — trusted lab-to-lab remote shell, like an SSH key shared across a whole department. Failing that, fingerd: sending 24 extra bytes plus a newline overflowed it into a shell. Failing that, sendmail compiled with debug mode on (the common default at the time), whose debug commands could be used to spawn a shell directly.
The grappling hook: the part that blew his mind
Once a remote shell exists, the server worm sends a command that compiles a tiny C bootstrap program on the victim, runs it with three arguments (the server's address, port, and a magic number), removes the evidence, and echoes "done". The server just waits for that "done" string.
The bootstrap itself is the clever bit. It unlinks its own executable immediately. It forks; the parent exits so that the shell command completes and "done" is emitted while the child keeps running. (If the fork fails it exits with a good status code — Prime's note: at least it tells you the worm failed to worm.) The child zeroes out its own argv so ps reveals nothing, connects back to the server, replaces stdin and stdout with the TCP socket, writes the magic string to authenticate, reads the real worm's C source off the connection, and then calls execl on a shell. The process becomes a shell whose input and output are already the socket — so the server now has full remote shell access, and simply tells it to compile and run the real worm. Back to the main loop, and back to bothering Ernie.
Closing
Source is available in the morris-worm-malware repository on GitHub, and most of the analysis comes from Don Seely's paper "A Tour of the Worm" (linked in the description). Prime's takeaway: the code is genuinely easy to read, and the execl trick — wiping the file descriptors, hooking them to TCP, and transmuting yourself into a shell — is the single most impressive line in it.