The Story Of The "Morris Worm" & How Rust Could Have Prevented It
Ashish Mradul Bamania
?? I Help You To Level Up With AI | Tech & AI Writer With 1M+ views | Software Engineer | Emergency Physician
The White House’s Office of the National Cyber Director recently published a 19-page-long document that urged developers to use Rust when building critical software.
The document points out obvious memory safety issues with C/ C++ and asks developers to cut their use across critical systems.
Have a look yourself.
You might be wondering how ‘dumping’ C/C++ might be a bit of an overkill, but hold on.
Let me tell you an interesting story first.
The Worm That Shook The?Internet
A lot of core critical software used today has been written in C/ C++.
This includes the Unix/ Linux operating system and tools.
The story begins in 1988, when a graduate student at Cornell University, called Robert Morris decided to run a seemingly harmless experiment to measure the size of the Internet at the time (at least according to him).
He wrote a sophisticated C program for this purpose and executed it at 8:30 pm on November 2, 1988, from MIT (to hide the fact that he was based at Cornell).
This was the moment that would become one the worst possible cyberattack in history.
His C program was a computer worm (a malware program that can replicate itself and spread using network connections or an infected device’s email lists), and it spread like wildfire.
Within 15 hours, almost 3000 machines were infected with it.
During its lifetime of a few days, it had taken down 6,000 machines that were running the UNIX operating system.
This was almost 10% of all computers connected to the internet back then.
How Did The Worm?Work?
Morris Worm worked primarily by exploiting the fingerd daemon on Unix systems.
This is a network service that provides information about users on the computer system.
Unfortunately, this service at that time had an unknown vulnerability.
When processing a client request, it would copy the contents of the request into a fixed-size buffer without properly checking and limiting the input length.
This lack of input validation and bounds checking caused Buffer overflow when an input longer than the allocated buffer was passed in the request.
This overflow allowed one to write past the buffer’s end, overwriting other parts in memory.
Robert Morris, exploited this vulnerability to craft a request that would inject executable code/ shell code into the memory of the target machine, past the buffer?—?a classic example of a Buffer Overflow Attack.
The worm exploited other vulnerabilities in the Unix sendmail program and the remote shell (rsh) and remote execution (rexec) services on Unix systems, to propagate itself while simultaneously trying to crack encrypted passwords on the infected computer.
领英推荐
An Example Of Buffer Overflow in?C
Here’s a simple example of a C program that copies the user input into a 16-bit buffer and prints its value.
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
char buffer[16];
if (argc > 1) {
strcpy(buffer, argv[1]); // Code vulnerability
printf("Input: %s\n", buffer);
}
return 0;
}
In this example, the strcpy function can copy more data to the buffer that it can hold, which can lead to a buffer overflow.
A user can input some malicious shellcode here and overwrite the return address on the stack, redirecting the program’s control flow to the injected shellcode.
As shown below, I use some shell code that spawns a shell on the host.
However the operating system detects that a buffer overflow has occurred, and the program is terminated with the stack smashing detected error message.
Lucky us, in this case, but unfortunately there are mechanisms through which malicious players can easily make this happen (not discussed in detail on a public platform but here’s a little link for further reading for the curious).
How Rust Saves Us From Such?Attacks?
Unlike languages like C, which would allow access to arrays beyond their bounds, Rust takes a completely different approach.
int array[5];
for (int i = 0; i <=5; i++){ //Buffer overflow prone loop which works in C
array[i] = i;
}
let mut array = [0;5];
for i in 0..=5 {
array[i] = i;
}
The above code snippet will be checked at compile time and the following error will be thrown.
Exited with status 101
thread 'main' panicked at src/main.rs:5:7:
index out of bounds: the len is 5 but the index is 5
Along with the above strict bound-checking system in place, Rust also has robust type-checking, matching and error-handling mechanisms that make sure that no cases go unhandled.
Take a look below where we use these to write safe and elegant code.
Using these, Rust could have prevented several other vulnerabilities that the Morris Worm exploited.
enum Command { // Ensuring type safety with Enum
Run,
Terminate,
}
fn parse(input: &str) -> Result<Command, &'static str>{ //Type checking
match input.trim(){ // Matching for error handling
"run" => Ok(Command::Run),
"terminate" => Ok(Command::Terminate),
_ => Err("Command unknown. Please try again."),
}
}
fn execute(command: Command){
match command {
Command::Run => println!("Running..."),
Command::Terminate => println!("Stopping..."),
}
}
fn main(){
let input = "exit";
match parse(input){
Ok(command) => execute(command),
Err(error) => println!("Error: {}", error),
}
}
// Error: Command unknown. Please try again.
Yes, Rust wasn’t a thing when the Morris Worm was written, but it is now.
Rust needs more adoption so that exploitation can be avoided in the major systems that the world uses every day.
Further Reading
(The information shared here is obviously for educational purposes only and not intended to encourage their use for malicious purposes.)