We covered another case of a binary vulnerable to buffer overflow but has some protections enabled such as NX and PIE. To get around these protections, we leaked a binary address and subtracted the address from a specific offset found by subtracting a start of the user input in memory from the start of the stack. Then we build the ROP chain consisting of GOT, PLT, setvbuf, system and /bin/sh offsets so that these gadgets will execute in the memory stack and return shell.. This was part of HackTheBox Pwnshop Intro to Binary Exploitation track.

Initial Analysis

I start by downloading the challenge files and turning on the instance to interact with the binary. I use the file command to determine the nature of the pawnshop file. It’s a 64-bit executable and is stripped (meaning debugging symbols are removed). I check the security protections. NX (Non-eXecutable stack) is enabled, meaning the stack cannot contain executable code. PIE (Position Independent Executable) is also enabled, meaning the binary and its dependencies are loaded into random memory locations each time, making exploitation harder.

Understanding the Binary with Ghidra

I open the binary in Ghidra to analyze its functions. The buy function has a buffer overflow vulnerability. It allocates 72 bytes for a variable but reads about 80-82 bytes from the user, allowing for an overflow. I note the offset of the buy function (132a). The sell function has a potential memory leak. It uses the read function, which doesn’t add a null byte to the end of the string. If used with %s (to read a string), this can lead to leaking memory addresses. When I interact with the sell function and provide input, I observe unrecognizable strings, which are likely leaked binary addresses.

Exploitation – Leaking Addresses and Bypassing PIE

My exploit code first interacts with the sell function to trigger the memory leak. I capture the output after the prompt and encode it to hex to reveal the leaked address. To bypass PIE, I need to calculate the base address of the binary. I use GDB (with the Jeff plugin) for this. In GDB, I find the memory mapping and identify an offset by subtracting the start of the program’s memory from an address where my input (“test”) is stored. The base address is then calculated by subtracting this offset from the leaked address obtained earlier.

Crafting the ROP Chain

With PIE bypassed, I start building a ROP (Return-Oriented Programming) chain. ROP chains use “gadgets” (small sequences of instructions ending in ret) to execute code. I use ropgadget to find useful gadgets in the pawnshop binary. I look for a gadget that subtracts from RSP (Stack Pointer) to ensure enough space for other gadgets (e.g., sub rsp, 0x28; ret). I note its offset (1219). I find the offset for pop rdi; ret (13c3), which is crucial for ROP chains to control function arguments. I also need offsets for PLT (Procedure Linkage Table) entries like puts@plt and GOT (Global Offset Table) entries. I use objdump to find the offset for setvbuf@plt (1030) and puts@plt. The offset of the vulnerable buy function (132a) is also included.

Finding Libc Offsets

To achieve ret2libc, I need the addresses of functions like system and strings like /bin/sh within libc. Initially, I find these offsets locally on my machine using commands like readelf and strings on my system’s libc. However, these local offsets won’t work on the remote challenge server. I use an online libc database (like libc.blukat.me) to find the correct offsets for the target architecture by providing a known libc function (e.g., setvbuf) and its leaked address. I find the offsets for system, setvbuf, and /bin/sh from this database.

Final Payload and Execution

The libc base address is calculated by subtracting the offset of a known libc function (e.g., setvbuf) from its leaked runtime address. I construct the final payload by combining:

  • Padding to overflow the buffer.
  • The addresses of the ROP gadgets (base address + gadget offset).
  • The address of puts@plt to leak a libc address.
  • The address of the buy function to return to for another overflow.
  • Then, in the second stage of the overflow:
    • The address of pop rdi; ret.
    • The address of /bin/sh in libc.
    • The address of system in libc.

I define the target IP and port and execute the Python script. The exploit successfully gives me a shell on the remote server, and I can then read the flag.

Technical Commands

  • file pawnshop
  • gdb pawnshop
  • vmmap (GDB command, often used with Jeff plugin)
  • grep test (GDB command, often used with Jeff plugin)
  • p/x <address1> - <address2> (GDB command)
  • ropgadget --binary pawnshop | grep rsp
  • ropgadget --binary pawnshop
  • ropgadget --binary pawnshop | grep "pop rdi"
  • objdump -M intel -d pawnshop | grep setvbuf
  • objdump -M intel -d pawnshop | grep "puts@plt"
  • readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system
  • strings -a -t x /lib/x86_64-linux-gnu/libc.so.6 | grep "/bin/sh"
  • readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep setvbuf
  • python exploit.py
  • ls
  • cat flag.txt

Flag

HTB{th1s_is_wh@t_I_c@ll_a_g00d_d3a1!}

Video Walkthrough

About the Author

Mastermind Study Notes is a group of talented authors and writers who are experienced and well-versed across different fields. The group is led by, Motasem Hamdan, who is a Cybersecurity content creator and YouTuber.

View Articles