Friday, August 27, 2010

Understanding Buffer Overruns

Understanding Buffer Overruns
-----------------------------
by James Turner

The goal of every darkside hacker is to get the target system to do something you don't want it to do.

A primary example is to reveal sensitive files such as /etc/passwd and /etc/shadow (which store the user names and encrypted passwords of your users.) Once these are in his or her hands, it is possible to use a "dictionary" attack on the passwords.

Alternatively, they can have your system FTP over an infected file and run it, which can be as bad or worse. In order to do this, they need to get a "trusted" program to execute commands they specify. Usually, this is done by a "buffer overrun" attack. Typically, buffer overrun attacks are aimed at programs that implement network functions, such as Web servers and mail servers.

A famous attack was aimed at the Sendmail Simple Mail Transfer Protocol (SMTP) mail server. These programs listen for incoming network connections and process the requests. Some, like SMTP, use plain-english conversations that look like:

220 secure.hostname.com ESMTP Sendmail 8.11.2/8.11.2; Thu, 4 Sep 2003 13:18:30
HELLO mail.otherhost.com
250 secure.hostname.com Hello skinny [127.0.0.1], pleased to meet you

So, essentially, the program is reading lines of text, interpretting them, and doing actions based on them. A convenient feature that these "network daemons" take advantage of is that they can communicate with the client computer using "standard input" and "standard output", which for a normal program would be the keyboard and display of the person running the program. But in this case it is reading and writing to another program (such as Microsoft Outlook) running on another computer.

To understand how a buffer overrun works, you need to look at the very small C program below:

#include
main() {
char *name;
char *dangerous_system_command;
name = (char *) malloc(10);
dangerous_system_command = (char *) malloc(128);
printf("Address of name is %d\n", name);
printf("Address of command is %d\n", dangerous_system_command);
sprintf(dangerous_system_command, "echo %s", "Hello world!");
printf("What's your name?");
gets(name);
system(dangerous_system_command);
}
If you're not a C coder, don't worry, I'll walk you through it. This program is designed to be run by a user on a console, but it illustrated the trouble that a poorly written network daemon can cause.

The first thing the program does is to declare two string variables, and assign memory to them. The "name" variable is given 10 bytes of memory (which will allow it to hold a 10 character string. The "dangerous_system_command" variable is given 128 bytes. The thing you have to understand is that in C, the memory chunks given to these variable will be located directly next to each other in the virtual memory space given to the program. If you run the program with a short name, you can see how things are supposed to work:

[jturner@secure jturner]$ ./overrun
Address of name is 134518696
Address of command is 134518712
What's your name?James
Hello world!
[jturner@secure jturner]$
As you can see, the address given to the "dangerous_system_command" variable is 16 bytes from the start of the "name" variable. The extra 6 bytes are overhead used by the "malloc" system call to allow the memory to be returned to general usage when it is freed.

After allocating the memory and printing the memory locations of the two variables, the program generates a command which will later be sent to the "system" call, which causes it to be excuted as if it had been typed at a keyboard. In this case, all it does is print "Hello world!". Then, we prompt the user for their name and read it using the "gets" system call. In a real network daemon, this might be printing a prompt and awaiting a command from the client program such as a web site address or mail address.

The important thing to know is that "gets", which reads a string from standard input to the specified memory location, DOES NOT have a "length" specification. This means it will read as many characters as it takes to get to the end of the line, even if it overruns the end of the memory allocated. Knowing this, a wiley hacker can overrun the "name" memory into the "dangerous_system_command" memory, and run whatever command they wish. For example:

[jturner@secure jturner]$ ./overrun
Address of name is 134518696
Address of command is 134518712
What's your name?0123456789123456cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:
daemon:x:2:2:daemon:/sbin:
adm:x:3:4:adm:/var/adm:
lp:x:4:7:lp:/var/spool/lpd:
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail

By padding out the response to the name query to 16 character and then adding a system command, the system command overwrites "echo Hello World!" with "cat /etc/passwd". As you can see, this causes that command to be run instead of the appropriate one.

So what can be done to prevent this? For one, using the fgets system call, which specifies a maximum length, will eliminate the possibility altogether. By changing the "gets" call to:

fgets(name, 10, stdin);

The problem is solved:

[jturner@secure jturner]$ ./overrun
Address of name is 134518768
Address of command is 134518784
What's your name?01234567890123456cat /etc/passwd
Hello world!
[jturner@secure jturner]$

But, since many sites run software that they don't have source code to (commericial databases, for example), you can't protect yourself from all buffer overruns. The other important step you need to take is to turn off any network services you don't use, and only run the ones you do use at a permission level that meets the needs of the program. For example, don't run a database as root, give it its own user and group. That way, if it is exploited, it can't be used to take over the system.

Buffer overruns are one of those things that every first-year programming student should be taught to avoid. That it still is used with such frequency by hackers is an indication of how far we have to go in the quest for truly reliable and secure software.

No comments:

Post a Comment