We covered another scenario of exploiting a binary vulnerable to buffer overflow. This scenario presented a binary that takes user input and compares it to three predetermined strings based on which the binary will either store byte input into a defined memory address, allow the user to store 48 bytes into a variable whose size is 16 byte and lastly execute a system call to return the date.
We exploited the BOF by creating a ROP chain that consists of first the offset, next the gadget address, third a memory address that we can control and store /bin/sh and lastly the memory address of the system call. This was part of HackTheBox HTB-Console Intro to binary exploitation track.. This was part of HackTheBox HTB-Console Intro to Binary Exploitation track.
Initial Analysis
I started by examining the provided binary file. It was a 64-bit executable and a “stripped binary,” meaning debugging information was removed, making it harder to find function addresses like main
. I checked the security protections on the binary and found that NX (Non-eXecutable stack) was enabled. This prevents direct code execution on the stack.
Reverse Engineering with Ghidra
I opened the binary in Ghidra to understand its functionality. In the main
function, I observed a variable Local 18
declared with 16 bytes. The program takes user input using fgets
and stores it in Local 18
. A subsequent function call takes Local 18
as an argument.
Inside this function, there were several if-else
conditions checking the user input:
- If the input was “flag”, the program prompted for another input, this time allowing up to 48 bytes to be stored in
Local 18
. This is where the buffer overflow vulnerability lies, asLocal 18
was initially allocated only 16 bytes. - If the input was “Hof” (Hall of Fame), the program allowed the user to enter 16 bytes, which were stored at a specific memory address. This address became controllable.
- Other inputs like “LS” printed predefined strings, and there was a system call that returned the date.
Triggering the Overflow and Finding the Offset
I launched the binary in GDB (GNU Debugger). To trigger the overflow, I first entered “flag”. Then, I provided an input of 50 bytes (though 48 bytes would have been exact) to cause a segmentation fault. By examining the stack pointer and base pointer, and using a pattern search tool, I determined the offset to be 24 bytes. This is the padding needed before overwriting the return address.
Building the ROP Chain
Since NX was enabled, I couldn’t directly execute shellcode on the stack. Instead, my goal was to redirect the execution flow to the winner
function. I needed the memory address of the winner
function, which I obtained earlier using Radare2 (afl
).
I built the ROP chain with the following components:
- Padding: The 24-byte offset found earlier.
- Gadget Address: I used the
ropgadget
tool to find a “pop RDI; ret” gadget. This gadget would pop a value from the stack into the RDI register and then return. The RDI register is typically used to pass the first argument to a function. /bin/sh
Address: I needed a memory address that I could control to store the string “/bin/sh”. The “Hof” functionality provided this. By sending “Hof” and then “/bin/sh”, I stored “/bin/sh” at a known, controllable memory address.- System Call Address: The program already contained a
system()
call. I found its address in Ghidra.
Crafting and Sending the Exploit
I wrote an exploit script. The script first connected to the remote target. It sent “Hof” followed by “/bin/sh” to store the command string in the controllable memory location. Then, it sent “flag” to trigger the vulnerable input path.
The final payload was constructed as follows:
- 24 bytes of padding.
- The address of the “pop RDI; ret” gadget.
- The memory address where “/bin/sh” was stored (this would be popped into RDI).
- The address of the
system()
call (this would be the return address, sosystem("/bin/sh")
would be executed).
The script sent this payload and then entered interactive mode, giving me a shell on the target. This approach allowed me to bypass the NX protection and gain control of the program’s execution flow to spawn a shell.
Technical Commands Used on the Terminal
gdb ./binary_name
pattern create 50
r
pattern search <value_from_stack_pointer_or_base_pointer>
ropgadget --binary ./binary_name
python exploit_script.py