A Simple ROP Exploit | poor_canary writeup

HXP 2018 has a “baby” challenge called poor_canary which was my first actual ROP exploit. If you want to follow along you’ll need to download and install the hxp 2018 vm image and install it locally. If you’re on a mac, you might need to port forward in order to get the image working properly. I have the following in my .zshrc:

That way I can just open up a new screen with my vm running and run hxp to have my vm serve the hxp site to localhost:8888 and if I want to forward one of the services I can run something like hxp 18113 and then nc localhost 18113 will work as expected. I’d like to give a huge shout out to the ENOFLAG team who’s writeup helped me comprehend this challenge. Now that that’s settled, let’s jump in.

Pwntools

I’d never used pwntools before, but it’s something that is completely necessary for this challenge. I set it up in a virtual environment and just source that anytime I want to use it.

Solution

Looking at the c code provided shows us that this is a buffer overflow exploit, but the title leads us to believe that the stack has a canary in it. Looking at the binary in binary ninja confirms this hunch.

__stack_chk_guard check the canary

We can also see that the function pointer to systemis saved after main so we can guess that this address should be somewhere in the binary.

Further analysis of the binary shows us that __libc_system is located at address 0x0016d90.

Now that we know this information, we need to start messing with payloads to send to the binary. Running the command pwn template --host 127.0.0.1 --port 18113 ./canary will generate code to connect to a remote host and send payloads to it. This is part of pwntools. Once we connect to the remote, we can send some code like this:

What does this actually do? Well looking at the c code we can see that we are allocated a 40 character buffer, but we can read in 0x60 characters. It’s also important to note that many canary’s start with 0x00 (why? because this makes it harder to get the canary because this acts like a null terminator). But, in our binary, if we just send 41 characters this will overwrite the null byte and send the remainder of the canary! Then we add back 0x00to get our canary and we’re ready to move forward.

Next comes finding the string /bin/sh, since this is what we’d ideally like to pass to system to spawn a shell. So let’s use ropper to see if there is anything in our binary.

Well that was easy; ropper rocks!

Now, where do we put this string in order to have it be run by system? Using godbold to help us we can look at the ARM assembly for system("/bin/sh");.

Great, so we push our command into register r0 and then system will use that as its argument. Now let’s find some rop gadgets that put things into r0. Keep in mind that ARM is a little weird and uses the concept of regliststo push and pop multiple registers at the same time.

pop {r0, r4, pc}looks ideal. This will take the next three words off the stack and save them into r0, r4, and pc respectively. And with this we can construct our payload! We have 40 bytes of garbage to fill up the buffer, the canary, 12 bytes of garbage (this I just took from the other writeup, if someone could explain exactly how to get this 12 byte offset for the return address that would be really helpful), our ROP gadget, the address of /bin/sh, 4 bytes of garbage (we don’t care what gets saved into r4), and finally the address of system.

To fully lay it out, this will overwrite the return address to be our ROP gadget, which will then pop the address of /bin/sh into r0 and change the PC to the system function. This is essentially calling system("/bin/sh");. Our full exploit script looks like so:

And this gives us access! When we run: