Running strings we see this is a x8664 binary _statically compiled, which should make things a lot easier for us.
Running the application, with IDA open on the side to quickly get past any restriction, I see that this program simply takes some number of calculations and then for each it allows you to choose wish operation to perform and the two operands. Both operands must be greater than 39, and the input and result are 4 byte ints. There are 12 global variables, three for each operation, and one for each operand and the result, plus four bytes padding between each set to maintain alignment.
When you first choose how many calculation to make, an appropriately size buffer is
malloced and the result of each is saved. If you decide to quit out early, then the buffer is
memcpyd to a buffer on the stack, which gives us our stack smashing opportunity – Just select more operations than the stack buffer can fit the results for. This will, however, overwrite the pointer to the alloc’d buffer, causing the
free call to crash, but we can easily get around that:
free(0) short circuits and returns, so all we need to do is overwrite the stack will nulls, which we can do by subtracting some number from itself.
Now that I have RIP, I need to exploit it. Stack is NX, and I can’t find “/bin/*sh” in a static part of the binary, nor is there a
system. This rules out ret2libc, and we will have to do a ROP.
This was my first non-trivial ROP (and some would argue it’s still trivial), so I will go into how I did it.
First I needed to figure out how to make the syscall. I found some simple shellcode here, and figured out what it was doing:
- Set AL to 0x3B
- Set RDX to NULL
- Set RDI to point to “/bin/sh\0”
- Set RSI to point to a pointer to “/bin/sh\0” followed by NULL.
Since we are on x86_64, this requires two ‘operations’ so that we write eight bytes. This also gives us another problem: The stack location is unpredictable, where do we put this arg array and string? Lucky for us, we have easily controllable global vars. Once we set up the ROP chain on the stack, we can re-run dummy operations to overwrite the global ‘cache’ of operands and results.
To do this I simply ‘divided’ “/bin” by “/sh\0”, which puts “/bin/sh\0” at the
divv symbol. For the array, it was a bit trickier: I needed something with 12 NULL bytes after (4 to cover the second half of the pointer, and 8 more for the second elements). Since
sub was at the end, I could do this. I put the address of
divv as both operands of a sub operation, which would result in a NULL result, plus the four bytes following would be NULL (since the section is .bss). Unfortunately I hit another problem: In practice, a symbol soon after the sub result,
_dl_tls_static_used was set to 0x60. To get around this, I would need an extra ROP gadget to write over this value with 0.
In the end, I ended up with what I think is a pretty elegant solution: