kvstore looked like your usual heap challenge on the first glance, providing a linked list of Items
But when skimming through the code, no obvious bug in the Item implementation was seen. It created, freed the notes in a safe way. The only thing that stand out, was the item_lookup
Since it uses memcmp and we control key and keylen, this can be used to leak values behind the key by bruteforcing them byte by byte. So, let’s start with that to get leaks out of our way.
Here, we’re just preparing the key chunks in such a way, that some libc main arena pointers will be available behind a key.
Now, we can just brute force the libc address byte per byte by trying to access the corresponding item and check, if it’s accessable or not.
Having a libc leak is a good start, but still we need to find a way to either get an arbitrary write or corrupt memory at all.
But there was still this weird save functionality
How should writing notes to /dev/null help at all and what was it meant for? But since the Item functionality seemed to be quite safe, some iofile exploitation might be needed, why else would this file pointer be included.
Taking a closer look at the exit functionality made it clearer
Trying to exit, will fclose the fp structure, before asking, if you really want to exit. Thus trying to exit but then going back into the application will result in an uaf, since the fp structure is freed.
While this felt like the right direction, another issue occured, as the fp structure has a 0x1e0 size, but since the challenge used getline to read the key, you could only create either a 0x100 or 0x1f0 chunk for new keys. Thus tcache would never serve us the freed fp chunk to manipulate it.
We would need to get the fp chunk out of tcache first, but for that we would need to free a chunk with size 0x1e0 at least 7 times. But since we cannot create such a chunk for ourself, the only possibility would be to free the fp chunk itself multiple times.
Experimenting in that direction brought up some strange behaviour in fclose.
Calling save and thus fprintf on the freed fp seemed to avoid the double free validation when freeing it again.
By doing this repeatedly, we’ll free the fp struct over and over again pushing it out of tcache into a normal freed bin.
So, now it’s possible to split the freed fp struct freed chunk by just adding a key with a length smaller than 0x100, which will then be put into the freed chunk enabling us to overwrite the fp struct.
We can use that to overwrite _IO_buf_base and _IO_buf_end, which will be used in the next save to determine the buffer, which the fp structs uses to buffer the data to write. By aligning those addresses around __free_hook, we can use that to overwrite it.
Looks good, so all there’s left, is to put system in our key at a position, so that it will land in __free_hook (instead of those Xs) and free a key containing /bin/sh.