strground was an interesting challenge with some twists. The attachment provided a libc-2.30.so and the binary contained an obvious double free bug with leaks.
So normally this should be super easy, just poisoning tcache and be good to go.
But the libc seemed to be patched to disable tcache, which leads to a rather weird situation:
We’ll not be able to use any tcache attacks, but at the same time have to get around the new security restrictions of 2.30 libc, which also makes normal heap attacks harder (top size checks, etc.).
Since there were no other solves to this challenge, I decided to do a writeup on it, though I’d love to see, if there’s an easier solution than this :)
Let’s take a look at the implementation
The binary reads a command with size 0x5f and then checks, which command we want to execute. For CREATE it will take the rest of the string and
calculate the length of a string. The way it does this will restrain the maximum size, we can allocate to 0x5f-7.
This is another constraint, the author seemed to put in to avoid that we do a simple misaligned allocation into __malloc_hook. To be able to do this, we’d need to be able to allocate 0x70 fastbins, which we cannot do because of this. So another simple attack primitive is cancelled out.
The ENCODE command just takes the string buffer and adds 0x3 to every byte and then prints it out. We can use this function to leak data from the stack, we just have to keep in mind to reverse this to get the proper output.
DELETE just frees the chunk, but doesn’t clear it from the table, by which we get a UAF with which we can easily double free and leak from chunks.
Before getting into the exploitation itself, let’s get some proper leaks. Since the buffer for the input command isn’t initialized, we might get some out of it.
So we have a stack, libc and elf address inside the input buffer, which we can leak by creating aligned commands, so that strncpy will also copy those addresses into our string on the heap. We can then retrieve them via encode.
Getting a heap leak is pretty simple due to the fact that we can do double frees.
With this we should have all the leaks we’d possibly need to tackle this.
Like already mentioned, we can only create chunks with a max size of 0x58, so we’ll not be able to create 0x70 fastbins, so misaligned allocation into __malloc_hook won’t work. We also don’t have tcache arena, so all of the usual tcache stuff won’t work also.
At that point, I created some fake unsorted bin chunks on the heap and did backwards consolidation into the string table itself, which then gives us a nice primitive, since we then control the complete string table, but that didn’t seem to help us much, since we then still can only show or delete valid chunks (which was already possible without this).
Since this didn’t really add any value, I went back to the start and checked what else we can do. Then I remembered a little trick uafio and me used in some older challenges, when tcache wasn’t a thing.
Though misaligned allocation into __malloc_hook doesn’t work, since we would need a 0x70 chunk for it, we can create and free 0x50 chunks. Since freeing a fastbin on the heap will put the address into main_arena, we can then do a misaligned allocation with that address.
We have already a freed 0x60 fastbin in main_arena, let’s take a look at it.
So, this looks almost like a possible target for allocating a 0x50 fastbin into, though a size of 0x55 is not a valid size for a fastbin. But when we’ll re-enable ASLR there’s a 50/50 chance, that the heap address will not start with 0x55, but with 0x56, which IS a valid fastbin size.
This enables us to to do a double free on 0x50 chunks, overwrite the freed FD and allocate into this misaligned chunk, by which we can then overwrite stuff in main_arena itself (like for example the top pointer).
Again, the allocation will fail, if the heap address starts with 0x55 and only with ASLR enabled, there’s a chance that it starts with 0x56.
For having an easier time debugging this, I’ll keep ASLR disabled for now and manually overwrite the chunk size within gdb to simulate a successful ASLR run.
This way, we can keep it easy with breakpoints but have the same behaviour as with ASLR.
After this, we should have our fake FD in main arena fastbin list, so the next allocation of a 0x50 chunk would write into main_arena.
With everything prepared, we can now overwrite the top pointer with the next allocation, so now we should be game, just putting some address into top, easily putting a ropchain to stack or somewhere else…
But again… We have a 2.30 libc, which has some additional checks for the top chunk, like for example, that it need to have a valid top size (< 0x21000 and more), so we can not just point it anywhere :-/
Finding a good target for a fake top chunk took ages… First I tried to find a good top size on the stack, so that we could put a ropchain into the return of the memcpy function, but after some time I gave that up, since there didn’t seemed to be anything on the stack we could use (or it would have been overwritten after the allocation).
So, I went into debugging the exit function, with which we soon landed in ld land, which was a bit of a pain to debug, since we don’t have an ld with debug symbols at hand. But after a lot of time digging through it with gdb, we’ll land here
As it turns out, 0xa60is a good top chunk size. So we can overwrite top with 0x7ffff7ffde48 and then the next allocations will go there, increasing our fake top chunk until we can overwrite the function pointer, that gets called in the ld code.
Let’s try it out
With this, we’ll have overwritten top to point to our fake top.
With some additional allocations, which go to our fake top chunk, we should now have overwritten the function pointer, that get’s called from exit handler.
Calling EXIT after this results in
Finally, rip control… But we have only one shot, and one_gadget constraints of the provided libc all won’t work at this point (r12 and r13 should be 0x0 for it.).
Let’s just put a ret there for now, to see where we’ll be going.
So, we hit our next 0xdeadbee2, but this time r12 and r13 are 0x0, which fulfills the constraints for the one_gadget, so let’s just put it there to finally end this :)
Like already mentioned, for the exploit to work with ASLR, we might need to run it multiple times to get a heap address, that starts with 0x56.
But after some runs against the remote challenge server, we should come to something like this
That was an interesting challenge, having to deal with 2.30 heap hardening restrictions, without having access to tcache (avoiding all the usual cookie cutter solutions).