While solving this challenge during the ctf, most of the time went into parsing that ncurses screen. After the ctf ended, I saw a neat solution from Chovid99 though, using pyte to do the parsing in a much cleaner way.
So, let’s first go through the original solution. But for not having not to suffer that pain with ncurses in the future anymore, I’ll also add a cleaned up exploit, combining Chovid99’s approach for parsing with my exploit.
Original solution
The game has an obvious buffer overflow in show_scoreboard
This will read input from the user, converts it into a nibble and writes it to the buffer on the stack. Thus, we can directly write bytes on the stack and also overflow the buffer (since it will continue until we press Enter).
Though it has a canary, that can be easily circumvented by just entering characters not in the valid range [0-9, a-f]. The function will then not touch the current nibble and just step forward to the next.
This payload would leave the canary and rbp intact and segfault into 0xbbbbbbbbbbbbbbbb. Easy enough.
The main problem for this challenge is to get proper leaks, since PIE and ASLR is active. As soon as we have a libc leak, this should be easily finished.
The binary contains a pointer to system, though system isn’t called anywhere, so let’s check, what it is used for then.
Everytime, a new tile is popped from the tile queue, the binary will call get_bleak, to initialize the B_leak array, which is used to draw the numbers on the different tiles.
p_system will point to the stack frame and depending on rand_value, we’ll get the address at the corresponding offset to it:
So, if we know the initial rng seed, we can parse the tiles in the game screen, reverse the xor on it, and by knowing rand_value recalculate single bytes from the original address.
Since every tile will take different bytes from the B_leak array, we’d just have to play the game long enough to reverse all the addresses on the stack frame (we only need system address though).
RNG is initialized with srand(time(0)), so guessing the random values in the binary is easy.
At start, the binary pushes three tiles to the queue and pops the first tile, so let’s simulate this
With this, we’ll know the first 3 tiles in the queue and also the first rand_val for get_bleak.
Parsing the map was the most painful thing in this challenge, so yeah, ncurses made me curse a lot…
The loop will now always receive the game screen (parse it with the regular expressions to get rid of the ANSI codes) until it finds a new block (consisting of 4 bytes), then parse the block, recalculating the leaks, send a keyboard sequence to drop the block somewhere on the bottom and simulate the pop for a new block (which will calculate the next random values for shape and bleak).
It will continue doing this until it was able to completely reverse the address of system. We can then calculate libc base and switch to interactive, so we can manually play the game.
To get into the scoreboard, we’ll have to get a score > 0, so yeah, let’s just play it manually. To make this a bit easier, I piped the complete logging into a file (so we can tail it in a second terminal) and forwarded the received bytes from the service to the terminal, so it will draw the game board the same way as when directly connecting to it.
Since we now know, when a new block is dropped and know the random values, we can recalculate the leaks from it
When we’re lucky enough and the leak for system gets finished in time, all there’s left to do, is to send the ropchain into reward to get a shell popping up.
Running it in two terminals will look like this:
Since the parsing via the regular expressions was a bit rough, the exploit is not super stable and will have to be run multiple times to get lucky with the blocks and the leaks, but at some point it will do the job ;)
Doing it cleaner with pyte
As mentioned before, after the ctf, I saw the solution from Chovid99, that used pyte to parse the ncurses screen. So in aftermath, I combined that with my approach to get a clean solution and serving as a lookup for future ncurses challenges for me.
In the cleaned up exploits, I’ll create multiple threads, one for parsing the game map and one for calculating the leaks when a new block is discovered. In the main thread, we can then read user input and forward it to the service, so the leaks will be the solved “while we play”.
When leaks are finished, we’ll just have to score and play into a game over.
When we get to the reward screen, press q to finish the input thread, so the exploit can send the ropchain to it (check the attached exploit for details).
This was a lot more stable than the previous exploit :)