At long last, the final post in my series of write-ups for the Montrehack workshop on Return Oriented Programming. I held out on this writeup for a little bit, both because I didn’t actually have time to write, but also because I was hoping to give people more time to try it. This challenge was orders of magnitude harder than its predecessors and was mostly intended as a wall for people who got through the first two challenges too quickly. Thankfully nobody made it to this challenge during the workshop, which means the level of difficulty was good enough.
As usual, the first step is to check the executable properties:
This is identical to motd_v0.2:
- Dynamic linking,
- No ASLR
- NX enabled
So we’ll most likely need to do a ret2libc with system… let’s get the address right away:
system is not in the PLT? Opening the binary in radare to confirm it
is fairly obvious that this is the exact same code as challenge 2, but with the
system() entirely stripped. Combing through the imported symbols,
there are no gadgets that would let us evaluate a command directly.
What this means is that we will need a way to execute raw assembly code.
The vulnerability is the same as
motd_v0.2, recall that the available actions
for the user are:
- Read user-controlled data and display it to the screen (Option 1)
- Write data to a buffer somewhere (Option 2)
However, this time we cannot simply put a command in a buffer. We first need to allocate an executable buffer somewhere in memory to store our shellcode. Once we have the buffer’s address, we need to somehow upload or write our shellcode into that buffer. Lastly, we need to transfer execution to our shellcode. All of that with the stack being marked as No-eXecute. More concretely, we need to:
- Take control of the execution pointer and launch a ROP chain
- Allocate a buffer (
- Make the buffer executable (
- Generate a shellcode that will read the flag (
stdinfrom the GOT to use in the next step
- Upload the shellcode into the buffer (
- Jump into the buffer (
Whew, that’s a long chain!
mallocsounds fine until you try to run the exploit and realize that
mprotectis failing. The reason for this is that
mallocwill prefix some metadata in the buffer, making the actual user-controlled portion of it not be page aligned. However,
mprotectexpects a page-aligned address. The solution is to use
Thankfully, all the necessary functions seem to be imported in the PLT. How convenient!
Let’s break down the gadgets per function call and explain what they are used
for. Recall the Linux x64 calling convention:
rdi, rsi, rdx, rcx, r8, r9.
We will need, at the very least, gadgets for
rdx. We will
also need some additional gadgets to jump into the buffer, swap some registers
around, and retrieve
stdin. There is more than one possible solution here, but
I have settled for the following gadgets after some trial and error. Your
solution might vary.
stdin required a way to dereference a register. When looking for
gadgets, it’s a good idea to pick the ones with the least possible side effects
and in this case it turned out to be an
[rbp - 4], so this required an
pop rbp gadget. This is fine in this case because it’s okay to
crash the process, but would make recovery in a real exploit a lot more
RSP gadget will make sense in a few paragraphs.
Next, we need the address of the PLT entries in order to build the ROP chain.
This can be done manually in any reversing tool or with readelf, but here is a
r2 one-liner for brevity:
Alright, we have everything we need. Let’s build the ROP chain.
Recall also that the result of function calls goes into
rax, so we’ll need a
way to move
rdi to retrieve the allocated buffer address, hence the
RAX gadget. Everything else is just shuffling the registers to get the
arguments from the stack into the right place.
The most interesting part of this chain is the addition to the
in order to counteract the
FD gadget. After this part of the chain,
contains the file descriptor for
Thanks to the previous gadgets in the chain,
rdi already contains the buffer
rdx already contains the
stdin file descriptor.
While debugging, it looks like
fgets is clobbering
rdi, but thankfully it
returns the buffer into
rax so it’s possible to just move it to
mprotect call. The other important thing to notice is that there is
no gadget to pop into
rdx directly, so instead a
mov is used, hence
being set twice.
All that’s left is to move the address of the buffer from
last time and use the
jmp rax gadget to finally transfer execution to the
If you’ve read through the write-up for the second challenge, you might remember that we only controlled one out of every two QWORDs through the rating function. Our gadget chain is much longer than that though… so we need a way to fit it somehwere in memory and pivot to it. Luckily, it is possible to write the chain into a motd buffer and then write a pivot into the return address.
The pivot gadget that I selected also pops
r13-r15, so this requires a bit of
padding in the chain. The final exploit code looks like this:
This challenge was a lot more difficult that the other two challenges. It demonstrated a complex, multi-stage ROP chain with a stack pivot to execute an arbitrary shellcode. The goal was to give something challenging to participants experienced in return oriented programming and highlight how complex real world exploits can get.