During the last two weeks, I participated in the IceCTF competition. During this event, I came across an interesting challenge called ROPi. I made an attempt at this challenge, which I will now record for future reference.
Note that this is not a writeup: the (a!) correct solution is far simpler (re-use the “ezy” function to create a new exploitable condition, after each rop chain), this is simply documentation regarding an exploitation technique which I found amusing.
This challenge was presented as a Linux binary, download here: ropi
Upon initial investigation, it is clear there is a stack overflow vulnerability in the “ezy” function, as illustrated below:
Some further cursory information gathering also reveals that:
- the stack is marked as not executable, making a trickier return-based exploit necessary.
- there are three other functions: ‘ret’, ‘ori’ and ‘pro’, which open the flag file, read the flag and print it respectively – simply put, we should be able to treat them as giant rop gadgets.
Some initial testing reveals that a rop chain is a sensible solution, but we quickly run into problems: the space we have to overflow isn’t very long, and we’re not able to string together three functions to form a rop chain.
A bit more time in the disassembly reveals that each of these functions ends with ‘leave’ / ‘ret’.
According to traditional wisdom, it is easiest to avoid ROP gadgets ending with ‘leave’ / ‘ret’, as ‘leave’ wrecks your stack – but it is possible to leverage this to “migrate” our rop chain, rewriting ESP to point to a location we control (preferrably with more space), and then executing ‘ret’ from there.
We can leverage this in this case as follows:
- Initial (“stage 1”) ROP against “read”, reading an arbitrary number of bytes into a static offset in the “flag” buffer.
- Controlling EBP to point to an appropriate location in “flag” buffer, we use a ‘leave / ret’ gadget to migrate ESP to our controlled EBP.
- The ‘ret’ instruction pops an address off our stack, which is in ‘flag’, executing the new code we supplied to the previous ‘read’ call (“stage 2” – without the arbitrary length limit).
A screenshot of this being exploited is below:
As you can see, ESP is now at an unusual-but-controllable address, with the contents of the stack within our control.
Note that this does’t seem to work – a second function call to ‘open’ to get the flag file seems to break the application deep down the call chain; I’m not currently sure why.
At this point, I stopped investigating this challenge, as another team member successfully solved it. You can find the source code for my abortive foray here. It has some extra hardcoded ROP gadget addresses to play with, if you care to explore the technique.