halleb3rry was quite an interesting heap challenge, which seemed to have no easy way for leaks.
After asking us for a username (which will get stored in bss), we have the option to create, edit and delete byte arrays. Though we are only allowed to free the array 5 times.
When reading the username, 0x80 bytes will be read and the last byte (or newline) will be replaced with a null byte.
The username will be stored on the stack, and aligned to it, the menu input will be stored.
If it would be possible to enlarge the username by one byte, it might have been possible to use the print_name function to leak the following stack address (by giving an option like 0*127+4), but it didn’t work out for me (might just have been a red herring), so had to find another way to get a proper leak.
So, let’s take a look at create/edit/delete
Nothing special here, reads a size, allocates an appropriate chunk and stores it in global ptr.
So, this will just free ptr without clearing it, allowing a double free.
The edit function doesn’t do the boundary check correctly, so we can write way behind our allocated chunk.
But we still have no leak, and we’d need at least a libc address to do something useful.
I still have the feeling, I overlooked something important (like using the double free at all), since solving this was going to be quite a ride.
Since at that point, my mind was set up, that we cannot use print_name for anything useful, the only other option seemed to be leaking via overwriting stdout buffers.
For getting a leak, we’d thus have to
Create a libc address on the heap
Overwrite _IO_write_ptr of stdout, so it will try to flush the output from the heap printing our prepared libc address
Since we’re using tcache, freeing chunks will only put heap addresses in the FD pointers.
But if you free a chunk, big enough to not be handled by tcache, main_arena will be used instead to handle this chunk. So, we just have to forge a chunk on the heap with a size of 0x500 for example and free that one. This will put pointers to main_arena on the heap.
After this, the heap will look like this
tcache arena will be cleared, after we freed the big chunk, so we have to resetup some chunks to do something useful.
And still, we have to find a way to print the content of our heap (for which no functionality in the binary exists).
Since the binary is using printf, a chunk on the heap is allocated for output buffering
If we are able to overwrite _IO_write_ptr, libc will think, there’s still data to print, and spill out the data between _IO_write_base and _IO_write_ptr.
So, how to get to overwrite anything in stdout without having a libc address yet?
We’ll be using the fact, that bss contains a pointer to it…
Since allocating a chunk into stdout itself, might crash the application, when following allocations write a FD pointer into it, we’ll use stderr instead, which happens to be just above stdout.
To pull this off, we’ll now prepare two freed chunks again (which will be handled by tcache again) and then overwrite the FD of a freed chunk with the address of stderr (on bss).
tcache will now contain the address of stdout on bss.
Now, when we allocate the next chunk, malloc will serve us the chunk at 0x602040
and put the "FD" from this chunk (which happens to be the address of stderr) into the bin list.
Thus allocating another chunk will now give us a chunk inside stderr.
stderr will be broken from now on, so be cautious to not trigger an error message, or it will crash.
We now have ptr pointing to stderr (which is above stdout) and can use this to overwrite _IO_write_ptr. Overwriting the LSB will get us nowhere, since it will only print stuff inside the stdout buffer, but overwriting the second byte should give us a huge leak.
So, finally we have our libc leak… But as well, we’re out of free, since we freed already 5 times to get here.
Might have been a bad idea, that I totally ignored the possible double free in my exploit. Maybe this would have helped to get this done with a less amount of frees, but well, there I was, a libc leak at hand and only armed with a stderr chunk and nothing to free anymore :(
Well, if the binary won’t help me triggering free anymore, we have to force it.
We still can allocate as many chunks as we like, so it’s time for house of orange.
This will result in freeing the top chunk and putting it into tcache arena.
But still not enough to do something useful with it, we need another chunk behind it, that is also freed.
Let’s just do it again :)
Two freed chunks in tcache arena again, this might finally come to an end :)
All that’s left now, is to allocate the first freed chunk, overwrite the FD of the second freed chunk with __malloc_hook and overwrite it with a one_gadget.
One last thing to note: When writing the exploit, I ran the binary directly from the script, which resulted in the final exploit not working remotely, since the size of the buffer chunk will vary. You can get around this by running the binary in socat and connect to it locally, like you would do remote, which will help in fixing the offsets of the chunks.
Still feels, like I overcomplicated this one, so I’m curious to see other writeups :)