petshop

25.03.2023

The Challenge

"petshop" was a category pwn challenge at the insomnihack 2023. It was a very simple address overwrite to exploit in the end, but involved a Use-After-Free/dangling pointer bug + a hidden malloc call and an off-by-one error in another function to trigger.

Counting from where now?

We were given a binary and an endpoint, the usual. When playing around with it for a second, chances are one will encounter this bug already: I think this speaks for itself a bit, a classic. Cleaned it up a bit in ghidra, it was just one medium-sized main function that did all. The bug: We count from zero, using the last element of the list here results in an out of bounds read/execute. (we'll get to the execute part)

The call and the target

Let's look at the function call of the "special abilities" the animals all have:

The function that's executed there is defined upon creating an animal:

And the corresponding function is stored wherever a click takes me. Interestingly, this also reveals the target function called specialAbility:D It just does the /bin/sh dance.

Freeing animals

Freeing animals in the input description of the challenge already kind of gives something away, let's look at the decompiled code: This argument literally calls just free on all the animals in different ways depending on how many.

It does overwrite the pointers to the respective functions everywhere, but misses one of them. It's always the last element to be free'd, which had to be done manually after iterating, because the developers still didn't learn to count.

We can only use this bug if we create 5 animals (the most possible).

This, combined with the bug above, essentially allowed me to have a dangling pointer available, by not creating any animals after freeing them, and then calling the ability of 0.

Hover all the things!!!

At this point I was struggling to find the last piece, because honestly how important does this look at first glance: It's actually the last piece of binary that wasn't 100% verified, and is called if the input on selection in the menu is >5.

I had to hover it eventually, to see the translation of 0x539 -> 1337. lol. As you can see from the browser, the initial input is one of the only I didn't name initially which is why it all seemed so innocent. Always name your variables!!

Anyway here poc

The above uncovered function allowes us to allocate 128 and write to it.

If we just recently freed the all our animals, this should technically be our freed space from the dangling pointer we kept aside above.

To verify; I used this function in gdb and sent a cyclic, then selected animal 0 again for the ability. Resulting in the crash: Here you can see that it tried to call rdx, which is currently user-controlled. Remember that pointer to the basicAbility that was stored on the allocated chunk belonging to the animal? That's supposed to be rdx.

I hate how unspectacular the exploit was in the end...

But we got the shell! And it ended up working first try on the remote as well.

Closing

Loved all of the challenges and the huge effort they put into this event each year. Thanks!