This is a presentation I put together and presented for my colleagues at IBM back in 2006. I started on the section featuring heap exploits and never finished it. I want to finish it someday.
1. Software Exploits How the Black Hats do what they do—Stack Overflows (or how a 1337 h4x0r can pwn your system) Kevin C. Smallwood March 2006
2.
3.
4.
5.
6.
7. Typical execve() program stack Low Memory High Memory 4-byte null Bottom of Stack 0xBFFFFFFC: Full pathname of executable—null-terminated env strings—null-terminated TERM=vt100, etc. argv strings—null-terminated argv[0], argv[1], etc. zero-filled padding 0 to 8064 bytes Starting address can easily be calculated: /bin/ls 8-bytes 112-bytes of ELF interpreter information env pointers argv pointers runtime data (from _start, etc.) envp argv argc Top of Stack Parameters to main()
Leet Speak: How an “elite” “hacker” can “own” your system.
Most of us have heard about buffer overflows. We know they are poor programming practices, and we may have heard that they allow the “Black Hats” access to our systems or programs in some way. But how do the Black Hats really exploit a small programming error? Why is a gets(), strcpy(), sprintf(), etc. such a problem? Bottom-line is that the Black Hats use buffer overflows on the stack to overwrite the return address to jump to their supplied code or crash the system. The key is that the Black Hat must control the input of the source buffer in order to overflow the destination buffer. If there is no outside of the program input for the source buffer, and the programmer knows that the source buffer is not larger than the destination buffer, a strcpy() is not a bad thing. You just need to know what to look for!
Knowing the contents of the stack and how functions are called is very important in the understanding of how to exploit a buffer overflow. Details of the stack will be covered in up-coming slides. Sometimes a Black Hat will just cause the exploited software to crash. This can cause a Denial of Service (DoS). If a service (local or remote) is important enough, denial to that service could result in lost revenue or reputation.
Shellcode got its name from the typical functionality of providing a privileged shell on a system. A remote exploit may just provide an entry point for the Black Hat to launch further attacks in order to gain full access to the system. Since the Black Hat can overwrite the return address of a function on the stack, that return address could be changed to the address of a program function that rewards the exploiter by applying credits to an account, zeroing-out a balance, increasing the quantity ordered (but not the total price), etc. A return address could be changed to execute a C-Library call like “execve()” to provide a shell or other privilege. This is one way around non-executable stack protection since the stack is just used to build proper arguments and then return into the library to do the dirty work.
The important items in this example program are in red . The main thing to note is that we are passing argv[2] from outside the program to a function where we do a strcpy() into a set-length buffer of size 400. It is pretty easy to supply more than 400 characters from the command line. Those extra characters will overflow the name[] buffer and over-write items on the stack including the return address.
This diagram is very important to know well. The stack grows from high memory to low memory. The starting address of the environment strings can easily be calculated if you know the full pathname of the command. The area on the stack for environment strings can be used to hold shellcode. The zero-filled padding space on the stack was something introduced in 2.5 for hyper-threading. The runtime data is information first loaded when a program is loaded to execute, but before the main() procedure is called. NOTE: This assumes that Red Hat’s exec-shield is not on.
Assembly code is using AT&T format: CMD <source> <destination>
Here we are saving the caller’s frame pointer along with making room for the local variables--in this case 400 bytes for the “name” character array.
The “leave” instruction restores/pops the save frame pointer to point back at the calling routine. The “ret” instruction pops the return address on the stack into the EIP register where execution will continue at that new address.
Let’s use a real simple code example to demonstrate the exploit.
Assume that the source string (i.e., “my ident”) is being provided from outside the program via the command line, a file, an environment variable, a pipe-line or even a network connection. The point is that the source string comes from outside the program and is under the control of the attacker.
The final result of “normal” and expected (by the software engineer) behavior.
Replacing the ASCII characters with numeric (hex) values.
Again, assume that the source string in the strcpy() is being provided from outside the program by the attacker.
The final result of our strcpy(). We have over-written the saved frame pointer and the return address. Note that with some skill, just over-writing the save frame pointer, we can influence the behavior of the executing program. However, it is much faster and more to the point to over-write the return address to execute our supplied “shell code.”
Here we see what is on the stack in numeric form.
Here is the crux of exploiting a stack (or heap) buffer overflow: executing supplied code not originally in the program.
In some cases, the appearance of a NULL is not critical, but in most cases, shell code should not contain any zero bytes. Our main target for this exploit is to find setuid programs so that we can gain more privileges than we currently have.
Various “tricks” used to avoid NULL bytes (for example, using “xor reg, reg” to zero-out that register).
Notice no NULL bytes.
Here is a simple program to tell us where the value of the environment variable is located on the stack.
Smashing the stack of a setuid root program and executing shell-spawning shell code results in an interactive root shell! Yes!
Five copies of the address copied onto the stack over-writing the return address!
Examples of problems from the Common Vulnerabilities and Exposures list and BugTraq.
Dangerous string handling functions: gets(), strcpy(), strcat(), sprintf(), vsprintf(), sscanf(), vscanf(), fscanf() Many of the insecure string handling functions have more secure functions that use a value to limit the number of characters processed. These include: fgets(), strncpy(), strncat(), snprintf(), vsnprintf() While these are more secure due to one of the parameters limiting the number of characters processed, be careful to fall victim to the off-by-one problem if the function automatically appends a NULL—the destination buffer size is now SIZE-1. Additionally, when concatenating strings in a buffer, the size of that buffer may only be the remaining space in the buffer and not the full size of the original buffer; this is a common programming mistake.
This is only the beginning!
The 24 bytes between the variables was found by experimentation.