Taking a short glance at the decompiled source of the challenge, I quickly decided that I don’t want to reverse it and just went on with a dynamic approach, which worked out pretty well.
Let’s just see, what crashes the binary.
Seems it’s calling a dtor on objectManager and tries to dereference a part of our payload, probably some kind of exit funcs.
Let’s give it a proper address, so that dereferencing it won’t crash it.
Now, we can see, that the binary will compare the value at the given address with a predefined set of functions and if it’s in that list, it will execute it.
So, by giving references to pointers to those functions, we can chain multiple calls of them (which will have our payload as input object).
This narrows the interesting code to look at a bit down.
This looks already interesting, since this will point to our payload, we would control rdi and rdx argument for memcpy, but arg would always be 0xb leading to a crash, when trying to execute memcpy.
Though we’re not really interested in calling addTag, addTwiceTag has the side-effect of setting rsi to this+1, giving us control over rsi itself.
We can now chain addTwiceTag to set rsi and then call set to execute a memcpy with all arguments controlled. But for now, we can only copy existing stuff from a known address to another known address.
Let’s turn this into a more useful primitive, by overwriting memcpy.got itself with this to read.got (for which we then would also control all arguments the same way)
After this, memcpy.got will point to read
We can now do the same again, but this time, it will call read(rdi,rsi,rdx) instead, giving us the possibility to write arbitrary data somewhere.
Let’s take a look at fire to get an idea, how we could turn free into a leak.
fire would call ~objectString, which then will call free on the second argument in our payload. Since we still have no leak, I used the memcpy-read to overwrite free.got with puts, so we can leak some data by freeing it.
That turned out to get a bit tricky and in hindsight it might have been better to search for some other controllable got than free, since after the dtor of objectString it will also call delete on the object, which will ultimately call free, so we have to make sure, that we serve it a valid chunk, that can be freed.
The payload will first set rsi accordingly and then call read(0, e.got["free"], 0x20) via the set function. We’ll send a second payload then, which will for one overwrite e.got["free] with e.plt["puts] and put a 0x20 fake chunk behind it, with its content pointing to e.got["setvbuf].
When we now trigger Fire, this will call ~objectString(e.got["free]+0x10), which will then call free([e.got["free"]+0x10+0x8]), which now boils down to puts(e.got["setvbuf"]) giving us our libc leak finally.
After the leak it will try to delete the chunk, which will be done nicely, since we also put a fake 0x21 size in that fake object allowing us to continue our call chain.
Looking good and with the leak not crashing in free we got the hardest part done.
We can now use our read primitive again, to overwrite free again, but this time with system and pass it another chunk, which will point to /bin/sh. Calling Fire again should then result in a system("/bin/sh") call.
With everything prepared, all that’s left is to trigger fire one more time.