HeapsOfPrint (24 solves)
Having studied the works of Professor Flux Horst and becoming more and more adept in different survial techniques, the Sky seems to be closer than ever. In his most recent excercise, Samuel’s objective seems farther away than usual. Can you help him close this gap?
nc flatearth.fluxfingers.net 1747
Attachment: HeapsOfPrint libc.so.6 xpl.py
The binary represents pretty much your default format string vuln challenge, though it has some strict settings
and it only allows one call to printf
, which might make this a little bit harder than just overwriting some got entries ;)
For some quick reversing
Sooo, the format string will be stored on the heap, thus we cannot use our format string for forging addresses on the stack. The do_this
function also leaks the lowest byte from char ch
, which might be used to calculate the offsets on the stack, though I didn’t use this correct. Nevertheless the exploit worked in 2 of 3 cases, so…
One call to printf
will definitely be not enough to pwn this binary, so we should find a way to return to main, so we can do additional printf
s.
Since the binary uses PIE
we aren’t able to know the address of main
or any other address in the binary, but with the 6th format string parameter we are able to overwrite RBP.
Though we don’t know any addresses by now, there will be the address of _start
on the stack. Thus we only have to pivot RBP to the address before that one, and after the leave; ret
at the end of main, it will return to that address, effectively jumping back to main, which enables us to do another printf
:)
In the first step, we should thus do a partial overwrite on RBP, so it will point RSP to the address of _start
and use this initial format string also to leak some other useful addresses from the stack, which we might be able to use in the following stages:
At this point, the binary asks us for another format string, whilst we now have leaks on STACK
, PIE
and LIBC
.
I first tried to get a leak for the heap, so we could put a ropchain there and then just stack pivot to the heap, but didn’t get it to work properly, so I opted for another approach.
When jumping to _start
we’ll be creating new stack frames (going upwards in the stack).
We can leverage this to write values in one stack frame and accessing them from another. We’ll only have to keep in mind, that the offsets for our format string parameters will differ on every stage (+68 from empirical analysis ;-)).
Since we cannot directly write addresses onto the stack, we’ll have to reuse the addresses already there and try to use those, to write arbitrary address onto the stack, which we can then use to write our final values.
In the second stackframe (after we had leaked the addresses), the 13th parameter will contain the value 0x7fffffffee68
(ASLR disabled), so we can use that one, to write to the stack address 0x7fffffffee68
(which is the content of the 47th parameter). But while we’re writing to that address, we’ll also have to overwrite RBP again, so it will jump back to main again (otherwise the binary would stop after that write).
Since the jump back to main will create a new stack frame (Stackframe 3 now), the offsets for our format string parameters will change, so we cannot access 0x7fffffffee68
with the 47th parameter anymore. Like I said, the offsets will also grow by 68 in every stage. This means the value can now be accessed with the 115th parameter.
Let’s use that to write the address of an onegadget onto the stack. To do that, I first searched for an existing libc address on the stack (the higher dword will always be the same, so we’ll only have to overwrite the lower dword).
After some failures due to the stack movement, I found an usable libc address at STACKLEAK - 0x8c8
.
To be able to overwrite that address, we’ll first need an address on the stack, pointing to the address containing this one. Since at 0x7fffffffee68
also a stack address is stored, we can use parameter 13 to overwrite the lower word at 0x7fffffffee68
with the lower word of the address of our target libc address:
The first line of our payload overwrites RBP to jump back to main, the second line overwrites the lower dword at 0x7fffffffee68
with the calculated address, where the libc address is stored.
After this, another stack frame is created, and we jump back into main. We can now use parameter 115 to use our forged address to write to that address.
We’ll be using it to write the lower word of the onegadget address there:
Thus we have successfully overwritten the lower word at that address with the lower word of onegadget.
For overwriting the next word we’ll need another pointer, pointing to that address, so we use the 13th parameter again to overwrite the address at 0x7fffffffee68
again, but this time pointing to LIBCADDR+2.
Again, the format string parameter pointing to that address is increased by 68, so it will now be the 183th parameter, by which we can access this new stack pointer.
Thus, we’ll use it to overwrite the next word in our libc address.
Finally we now have the complete address to our onegadget on the stack, so it might be time to stack pivot to it and get a shell.
But at this time, I didn’t find any onegadget, whose constraints I could fulfill, since the stack was always full of garbage, preventing the onegadget to execute (garbage in argv or envp).
So I decided to clean up the stack with some additional format strings.
Ok, so we’d just need to clear up the value, which will be at rsp+0x30
in our final stackframe.
Again, we’ll first do a partial overwrite to the existing stack address in format string parameter 13, let it point to the value on the stack (which will be rsp+0x30
) and then do another format string to overwrite it with a null
value.
So, now all preconditions for our onegadget should be met, and we’re ready to stack pivot there, by doing a final overwrite of RBP
.
resulting in