Last time I blogged about the solution to my challenge
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
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
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
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 to.
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
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
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
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
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.