In this challenge, we’re able to play a maze or card game. Though I used the card game merely to enlarge the heap when needed or triggering an allocation, the interesting part is in the maze game.
The maze is stored on the heap (right below our user info object)
In most ctf maze games the target is to somehow leave the maze to access memory around it, so the objective was clear. Wasted some hours to leave it “in the wrong direction” and totally overcomplicated it (trying to resize card objects to free them multiple times, and so on…).
But after a break and a fresh reset, I set my mind that it might be the best idea to keep the heap clean and simple and just leave the maze to the top, “wandering” into our user object. This turned out to be a lot easier.
So, how to leave the maze without winning? Well, we can create a new maze with an empty space on top and place a portal inside the maze, which will warp us into this free space. When we then leave the portal to the north, the game will not recognize that we left the maze and lets us walk around outside of it.
With this, we’ll create a 4*16 maze, which will fit nicely into a 0x50 chunk. Thus it will reuse (free/alloc) the existing maze. We also placed an open space at the middle of the top line and put a portal on 1/1, which will warp into 0/8 (which is the open space).
We’ll now walk through the portal and leave the maze and then walk into the size of userinfo.
The player will be marked in the maze as 0x40 and since we “walked” onto the second byte of size, size will now have changed from 0x18 to 0x4018.
When we move around, we leave 0x20 bytes on our way (since the game wants to replace our previous position with a whitespace to draw the maze cleanly). So, by now, we would already be able to write a huge amount of data behind our name buffer, but it would be even nicer, if we could overwrite the userinfo object itself and point the name buffer anywhere.
So currently it points to 0x4062d0. If we’d just sneak around into the LSB of this, that would turn it into 0x406240, pointing before our userinfo object.
Since lowest 3 nibbles of an address will always be the same, this will also work just fine, when we re-enable ASLR.
With this setup, we can now change our name to align it with the name buffer address and then leak that with Userinfo.
Now, with a heap leak at hand, we can do even more with this primitive.
Let’s point our buffer to tcache arena, so we can control allocation of chunks easily.
This will basically just clear out tcache arena. Thus all new allocations would come from top. We use this to enlarge the heap a bit, so that we can put a big fake chunk in it without much hassle.
Playing cards will allocate and free some 0x21 chunks. We do this multiple times, to push top down the heap.
After this we’ll put a 0x480 fake chunk on the heap and let tcache 0x20 point to it. We’ll then allocate and free it via another card game, which will then pull a main_arena pointer onto the heap.
Before playing cards, heap will look like this
Playing cards and quit will allocate the fake chunk and free it.
In the previous payload, I also directly overwrote the name buffer pointer with the address of the fake chunk, so that we can now directly read the libc address from it.
Now that we know libc, we can bring this to an end. Since User Info will print our buffer with puts, strlen.abs.got is a very good target for this.
When puts gets called, it will check the length of the string with strlen(username), so if we can overwrite it with system first, it would execute system(username).
For this, we’ll overwrite the buffer address in our userinfo with the address of strlen.abs.got-8.
Then we’ll just change the username again to /bin/sh\x00 + system.
This way our username will be /bin/sh, and we’ll also overwrite strlen.abs.got with system.
Now, we’ll just need to show userinfo again to trigger the shell.