Last time I blogged about the solution to my challenge
motd_v.01. I mentioned that the solutions to the second and third binaries
would follow shortly. As life has it, I got caught up in other things and did not get a chance to sit down to write these blogs, but now I finally do, so here it goes.
This post will cover
motd_v0.2 and the intended solution. If you have another solution, I’d be happy to hear about it!
The code, binaries and full solutions can be found here for people who would like to follow along.
Without any further ado, let’s get started.
Like the previous challenge, this starts off in a similar way: Figure out what kind of binary we’re looking at.
The first difference from the previous challenge is that this time around, the binary is now dynamically linked. What this effectively means is that the system’s GLIBC will be loaded at runtime instead of being included directly in the binary, greatly reducing the code size and the amount of available gadgets to build a ROP chain.
readelf, it’s possible to identify which imports are required by the binary. These imports are located in a structure called the Procedure Linkage Table, which, thanks to partia RELRO will not move around in the binary, allowing our exploit code to jump directly to PLT entries.
Unfortunately, the address of the functions are all zeroes. This normal and it is due to the fact that the function will be resolved at runtime when the function is called for the first time. This is known as lazy loading and is a commonly used technique in dynamically linked binaries.
Enough peeking around statically, though, it’s time to find out what’s new in v0.2! Running the program, we are greeted by the following:
It looks like the program now supports multiple messages of the day and even allows to rate the message. The newly added functionality is as follows:
- Index memory by selecting a message of the day.
- Read/Write a number at the indexed memory.
Quickly checking the
motd update function reveals that the call to
gets has been patched and that the buffer size looks to be properly validated, making it impossible to smash the stack.
Because this is a CTF challenge, it is easy to conjecture (accurately) that the only write primitive that’s left (rating) must somehow be the key to the kingdom. With that in mind, taking a peek at how rating works turns out to be partially revealing:
In the function,
var_18h holds the first argument of
rate_motd, which appears to be a pointer to some structure. The first red box with steps
(2) shows the function
get_motd being called, which can be seen in the next screenshot. Suffice to say that this function returns a pointer to the select
motd, which is stored on the stack in a local I aptly named
The second red box shows steps
(4) which respectively consist of retrieving the pointer stored in
selected_motd and storing the retrieved rating at offset
0x8 inside the structure pointed
In other words, offset
+8 in the
motd structure is the rating field.
get_motd function also receives a pointer to the
motd structure, and reveals even further information about its layout. Indeed, what this function does is prompt the user to pick a message of the day index at
(1) and then validates it (incorrectly) to be lesser or equal to
(2)). If the validation check passes, block
0x40127e [of] shows that the index is used on
var_18h) to compute
the index in what is now obviously an array of
motd entries. This incorrect bound check allows for negative indexing into the array.
With a bit more digging, the array is coming from the
main function and appears to be located on the stack.
To summarize what we know so far:
- It is possible to provide a negative index into the
- It is possible to write at
*motd+8, an arbitrary 8 byte value
- The motd message array is stored on the stack
A little bit more reversing shows that the motd struct size is 16 bytes, and that because of the stack layout,
*motd+8 maps directly on top of the stored stack base pointer and return address:
STACK LAYOUT < 0xfffffffffff > | . . . | |== main =======| | return_addr | | old rbp | |---------------| | motd->rate | | motd->text | | motd->rate | | motd->text | | motd->rate | (+8) | motd->text | (+0) |== get_motd ===| | return_addr | <-- motd[-1]->rate | old rbp | <-- motd[-1]->text |---------------|` | . . . | < 0x00000000000 >
Objective: Use the
ret2libc technique to execute one of the
motd text. This time, however, there’s one obstacle:
$rdi does not contain a pointer to a text buffer.
The first step to successful exploitation is to find a way to populate
$rdi with the address of a
motd[i]->text. This requires a gadget that will pop from the stack and into
$rdi… finding it is simply
a matter of using ropgadget on the file and filtering for
pop rdi instructions. It’s important to note that had
PIE been enabled, address space layout
randomization would have made it more difficult than just hardcoding the gadget address.
This challenge’s purpose was to further solidify the concept of
ret2libc and introduce gadgets without too much added work. A simple, straight forward gadget without any preventive measures allowed for a smooth introduction of the
technique and tools required for modern ROP exploitation.