The challenge this time is an arm binary, which let’s us input two strings (CHECK and FIGHT).
After disassembling the code for the binary might look like this:
Obviously, there’s a format string vulnerability in check() and fight() is prone to a buffer overflow. But there’s a canary check, which prevents us from abusing the overflow (for now).
So, we should first try to leak the canary. The format string vuln will help with that. Though, it uses scanf to read our input, so no null bytes are allowed. Since the canary is stored at 0x00098f8c, we cannot pass it in our payload to put the address onto the stack and then read from it.
We also have only one shot with our format string, so we cannot put it on the stack with the first payload and read it with another one.
This means, we have to reuse the existing addresses on the stack. To make it easier to find appropriate format string parameters, I wrote a quick&dirty scanner (disable ASLR before running it locally)
This will produce an output file, which can then be used, to search the available addresses in an editor and eases the pain to check every parameter. One could also parse the responses to make the output look a little bit nicer, but it should suffice for a ctf challenge.
Setting a breakpoint to main+152 will show us the stack address, at which the return address is stored (if we think in x86 terms)
0x7efff244 holds the return address (lr gets called on next instruction by bx lr). Let’s do a quick search in the output file for 0x7efff244
Ok, the 460th format string parameter points to that address, so we can use it to overwrite the return address and get pc control.
Let’s point it back to the start of main
This will result in an endless loop:
Ok, much more comfortable :)
We might now be able to just put the address on the canary by sending a 4 byte aligned string with the canary address at the end, and then read it with the second check call, but I decided to use two combined format strings for this.
For this, just stop again in the check-function and search the memory, where the format string parameters are located
And there’s a 0x7efff258, which points to another format string param. Searching 0x7efff258 in the output file, reveals that it’s number 525 and it’s pointing to 532. So we’ll be storing the address of the canary with parameter 525 and then read it with 532.
Keep in mind, that the lowest byte of the canary will be 0x0, so we’ll read just one byte higher and then append the 0x0 byte later on.
Now that we’re able to leak the canary, we can continue with the buffer overflow in the fight() function. We’ll be using it to call execve("/bin/sh", 0, 0), so we’d need /bin/sh somewhere in memory, but the binary doesn’t contain any occurence of this.
We can just put it at the beginning of our payload, though we’ll need the address to our payload then. For this, we can just leak a stack address first and then calculate the offset to our payload. Parameter 3 contains the address of the buffer used in check, and the buffer in fight will be 0x400 bytes behind that.
Since the binary is statically linked and doesn’t contain any references to execve or system, we’ll have to use rop to call execve via syscall.
We’ll be in thumb mode, when our payload will be executed, so for calling execve we have to fill our registers: