[pwn] Hack The Box — Ropme Write-up

Gabriel Pirjolescu
6 min readApr 2, 2020

Ropme is a hard pwn challenge on Hack The Box. In this article, I will explain the concepts and techniques needed to solve it. As usual, the first step is to decompile the binary to take a look at the source code and also run it to have a good idea of its functionalities. For the decompilation, I have used IDA, but there are plenty of other alternatives available. We can immediately spot a buffer overflow since char s only stores one character and fgets reads and stores 500 characters in it.

The main function decompiled in IDA

Now let’s see how long our input needs to be before we overflow the return address located on the top of the stack. First, we need to create and store a pattern. Then we place a breakpoint on the ret instruction in main and run the program with the pattern as an input. When execution reaches the breakpoint, we look for the values on the top of the stack in the pattern to get the offset. For this stage, I will use GDB with the Python Exploit Development Assistance.

Create a pattern, set the breakpoint and run the program
Find the pattern offset

Buffer overflow protections

To know how to exploit the buffer overflow vulnerability, we also need to look at the protections that are in place for this executable.

The existing protections

NX stands from Non-Executable and it means that when the application is running and is loaded in memory, the segments are not allowed to be both writable and executable. We can check this by looking at the virtual memory mapping when the application is running in GDB. Therefore, we can’t inject shellcode and execute it directly.

The virtual mapping of the memory sections with the associated permissions

RELRO is the acronym of Relocation Read-Only. It means that the Global Offset Table (GOT) which is used to allow the dynamic linker to load and link symbols are marked as read-only. We will talk more about the GOT later.

We can safely assume that ASLR is enabled as well. It randomly positions the base address of the executable and the position of libraries, heap and stack in a process’ address space. This prevents us from knowing at what position in memory the functions or gadgets are located.

PLT and GOT

The Global Offset Table (GOT) is made up of the .got and .got.plt sections. The .got segment holds the addresses of the functions in libc. The addresses in GOT are populated dynamically by the dynamic linker while the program is running. The .got.plt holds a reference to .got for each symbol.

The Procedure Linkage Table (PLT) contains only one section, .plt. For each function in an outside library referenced in our program, we have an entry in .plt which is actually a jump to the address in .got.plt for that function. Here I have found a very good explanation of how shared functions are linked. LiveOverflow also has a nice video where he explains the process.

The first time a shared function is called, the GOT contains a pointer back to the PLT, where the dynamic linker is called to find the actual location of the function in question. The location found is then written to the GOT. The second time a function is called, the GOT contains the known location of the function. This is called “lazy binding.” This is because it is unlikely that the location of the shared function has changed and it saves some CPU cycles as well.

Leaking addresses

We have to keep in mind that we can’t place shellcode on the stack because it is Non-Executable. To overcome this we will use a technique called Return Oriented Programming (ROP). This means that we will overflow the return address with the address of functions and gadgets (instructions) already in memory and jump from one to another in order to exploit the vulnerability.

The easiest way is to use the address of the system function in libc with an argument of /bin/sh which can also be found in libc. However, because of ASLR, their addresses are randomised. To bypass the randomisation we need to leak the address of a function (in our case puts) in .got and then compute the address of system and /bin/sh by using the difference (although the base address of libc is randomised the position and offsets of symbols in it are the same).

We will attempt to leak the address of puts in libc. Hence, we need the address of puts in PLT, which will be called to leak the address of puts in GOT. In addition, we need a pop rdi gadget so that we can pass puts in GOT as an argument. The reason is that the calling convention in x64 is that the parameters are stored in registers and the first argument is store in the rdi register.

Finding the required addresses
Script used to leak the address of puts in libc (ignore plt_main for now)
Leaking the address of libc on the server

Now we need to find out the version of libc used on the server. We will use a tool called libc-database. To find the base of libc we look at the offset of puts and subtract it from the leaked address. In addition, we look for the offset of the system function and /bin/sh.

Finding libc and the addresses of puts, system and /bin/sh inside it

Exploitation

At this point, we have all the pieces we need for exploitation. The flow of the exploit will look like this. First, we leak the address of puts and call main again. Then we compute the address of system and /bin/sh by adding their offsets to the base address of libc. Finally, we create the exploit by calling system with a parameter of /bin/sh called by using pop rdi.

The script used for exploitation
The remote exploit

Contact

If you would like to get in touch you can contact me on one of the following platforms:

Sources:

--

--

Gabriel Pirjolescu

MSc Cyber Security student at the University of Southampton