Create buffer overflows in snow leopard

1.1k views Asked by At

As part of a course at university in computer security, I'm soon about to learn about buffer overflows and how to use them to as exploits. I'm trying to do some simple buffer overflow with the following code:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char buffer_one[4], buffer_two[16];

    strcpy(buffer_one, "one");
    strcpy(buffer_two, "two");

    strcpy(buffer_one, argv[1]);

    printf("buffer_two is at %p and contains \'%s\'\n", buffer_two, buffer_two);
    printf("buffer_one is at %p and contains \'%s\'\n", buffer_one, buffer_one);
}

I'm able to overwrite the content of buffer_one with the null terminator if i run

$./overflow 1234567890123456
 buffer_two is at 0x7fff5fbff8d0 and contains '1234567890123456'
 buffer_one is at 0x7fff5fbff8e0 and contains ''

But if i send more than 16 characters as argument, the program sends Abort trap. I supposed this is some sort of buffer protection on Snow Leopard (ASLR maybe?). If if make the size of buffer_two < 16, the adresse is still 16 bits apart

I'm running gcc -o overflow overflow.c -fno-stack-protector to remove stack protection

Is there any solution to this problem, other than installing a VM running a linux dist.?

4

There are 4 answers

2
user470379 On

The key to why this is happening is the fact that buffer_one is located after buffer_two in memory. This means that when you overflow buffer_one, you are not overflowing into buffer_two. Instead you are overflowing into stack memory being used to hold other things, such as the saved ebp pointer and most importantly, the return address.

And this is exactly what you want to happen when attempting a buffer overflow exploit! When the program executes strcpy(buffer_one, argv[1]); the first four bytes from argv[1] go into the memory allocated for buffer_one. But then the next 12 start overflowing memory being used for other things, eventually overwriting the return address. Without seeing the machine code, I can't say for sure which bytes exactly are overflowing the return address. But I'm guessing the value of EIP at the time of the SIGABRT is 0x31323334 or something similar (hex representation of '1234'). The key is realizing that by being able to overwrite the return address, you control EIP. And when you control EIP, you control the system. (somewhat overexaggerated, but in most cases not far off) When you control EIP, you control which instructions the processor will execute next (putting aside for the moment the fact that the OS/kernel actually stand in between).

Now if you find exactly which eight bytes overwrite the return address you can replace those bytes with the address of your buffer (0x00007fff5fbff8e0) and instead of returning to the original caller (libc in this case), the program will start executing the instructions you provided (AKA the shellcode). Note that you will have to fill in the implied 0s in the most significant places and provide the address as the actual nonprintable ASCII characters (0x00 0x00 0x7f 0xff 0x5f and so on), not the actual digits/characters 7ff5 etc. If using an x86-64 architecture, you'll also have to take the little-endianness into account and supply it backwards -- 0xe0 0xf8 0xbf etc. Supplying these nonprintable characters is most easily accomplished using backticks and command substitution using a brief Python or Perl script:

./overflow `python -c 'print "AAAAAAAAAAAAAAAA\xe0\xf8\xbf\x5f\xff\x7f"'`

(The A's are padding to overflow the buffer.) Unfortunately, you won't be able to provide the 2 additional \x00 needed for the address. One of these NULLs will be placed there for you by strcpy, but you'll have to get lucky with the last NULL and hope the address you're overwriting already started with 0x00 (which is actually highly likely). Now when you execute this with the right number of A's, you'll probably still get a segmentation fault or even possibly illegal instruction, since you'll now jump to the capital A's and execute them as actual machine instructions (0x41 => inc ecx).

Then finally the last piece is putting in the actual shellcode. Given your limited buffer sizes, it will be very hard to provide anything useful in only 12 bytes or so. Since you are writing the code in this case, the easiest thing will probably be to make your buffer bigger. If this weren't an option, then you could either A) use buffer_two as well for 16 more bytes since it comes before buffer_one or B) provide the shellcode in an environment variable and jump to that instead.

If you wish to write the actual shellcode yourself, you'll have to know how to perform syscalls and what calling conventions are and how to use them, as well as how to avoid NULL bytes in the shellcode. The other alternative is to use a payload generator such as the one included with Metasploit which will make it a lot easier (although you won't learn nearly as much).

These are technically the only pieces you need, especially since you have a good idea of what the address will be. However, many times (especially when the shellcode address is not known) a so-called NOP sled will be placed in front of the shellcode so that you don't have to get the address exactly right. A NOP sled (short for No Operation) is simply hundreds to thousands of NOP instructions (0x90) that you can jump into the middle of and then have no effect until execution continues into the shellcode.

If you trace everything in GDB and execution jumps to the shellcode correctly but you still get access violations, it's likely because the NX bit is set on the stack page, meaning that the processor will refuse to execute data on from the stack as instructions. I'm not sure if execstack is included with OSX or not, but if so, you can use it for testing purposes to disable the NX bit (execstack -s overflow).

I apologize for the wall of text, but I wasn't sure how far you wanted to go studying buffer overflows. There's other guides you can check out as well, such as Aleph One's archetypal guide, "Smashing the Stack for Fun and Profit". The Shellcoder's Handbook is a good book to check out as well, and I'm sure others can add recommendations.

TL;DR: In short, you are overflowing your buffer and overwriting saved pointers and return addresses with garbage.

1
Zan Lynx On

If you are learning about exploits then you'll need to really dig into details.

Go ahead, read the machine code! You might be able to find out how to slip the overflow past whatever check method Snow Leopard is using.

The problem may be simpler than that too. There's no rule that the compiler has to put buffer_one and buffer_two in any particular order on the stack or even put them on the stack at all. Notice that buffer_one would actually fit into a register.

That isn't the case here of course, but I see that buffer_two is placed before buffer_one. That means that writing an overflow into buffer_one will never write into buffer_two. I can't explain why it ends up containing '', but f8d0 is definitely before f8e0 in memory.

0
Dave Rager On

Data on the stack on x86 is 4-byte aligned. There is padding placed between buffer_two and buffer_one if buffer_two length is not a multiple of 4 bytes. change it to 12 or less and they should be 12 bytes apart, etc.

[Update] I overlooked the address size. You are on a 64-bit system, your stack is 8-byte aligned. The address differences won't change until your buffer size changes by at least 8 bytes.

Is this line correct:

strcpy(buffer_one, argv[1]);

The output looks like you are copying argv[1] into buffer_two.

Given that case, how much are you copying when it crashes? 17 bytes? 18? If it is more than 24 you will start clobbering the stack in ways which would lead to the abort.

Note that "1234567890123456" is actually copying 17 bytes which includes the null terminator truncating buffer_one.

0
Christoffer Reijer On

Have you tried to disable FORTIFY_SOURCE when compiling?

-D_FORTIFY_SOURCE=0