The challenge is about creating robot objects, which will then just call a robot_self_destruct function after the specified time.
When specifying the number of turrets, it will calculate the needed size for the buffer for storing the robot objects with round_up and allocate a buffer for it. Then it will loop through the robots, read the data for it and start a timer thread.
robot_self_destruct itself will print the name of the robot and a random death message.
So far, so good… If we’re nice and only entering valid sizes for the robot array…
robot_assembly_line calculates the size for the buffer object with round_up, but uses the entered count for the loop.
This screams for integer overflow. If we provide a count big enough, that val + size - 1 will overflow and then be smaller than size * size, result would be 0 and round_up will return the minimum size 0x30, but the following loop uses our huge count for reading the robot objects.
This will lead to an out of bounds write via the robot objects, with which we can overwrite the timer structs for previous robots.
Let’s set this up
This will setup the buffer object with malloc(0x30), which creates a 0x40 chunk, which has enough room for 2 robots, but it will then create 4 robots. Thus, robots 2 and 3 will overwrite the timer struct for robots 0 and 1. If robots 0 or 1 would try to execute it’s death function now, it would segfault, since they’re overwritten with junk. Because of that, we gave them a huge execution time to avoid them to be executed at all.
The reasoning behind creating 4 robots in the start is to move down the heap, so that the pointer in the timer struct points to a heap area, where we can make it point to some useful data with just a one byte overwrite (we could have also tried to do a nibble bruteforce, but let’s keep it clean, so that no bruteforce is needed).
Let’s check the heap, while the objects are created, to get a better understanding what’s happening.
After creating the first two robots, the heap will look like this
The timer object for every bot will contain a pointer to the bot itself, a pointer to the self_destruct function and the time, after which it should be executed.
Since the buffer was allocated too small, the current input function for bot 2 will read data to 0x55555555a420, which is also the timer object for bot 0. But since we have no leak by now, and there’s nothing interesting in the heap from 0x000055555555a300 to 0x000055555555a3ff, we just create more robots, so that the robot pointer for a timer object will point to 0x000055555555a4XX.
Then we can do a one byte overwrite and let it point somewhere else, which contains more useful data.
After creating bots 2 and 3
Now, the robot pointer of the timer object for bot 2 points to 0x000055555555a420 and there are a lot more useful addresses in the range, that we can now reach with overwriting only the LSB byte. Let’s point it to 0x55555555a490.
Remember, bot 2 is already running and we’re currently entering the name for bot 4. So we can just send one byte (but no newline), which will overwrite the name pointer, and then we just wait for bot 2 trying to self-destruct.
When bot 2 now self-destructs, it will print its name, which points to 0x55555555a490 and thus prints a heap-address, which we can read.
Since we now have a complete heap address, we can overwrite more than only the least significant byte of an address.
We’ll finish entering the data for bot 4 (the input method is still waiting for us to finish the input), so that we can start entering data for bot 5, which is also the timer for bot 3 (which hasn’t terminated yet).
This will now again overwrite the robot pointer in the next timer object and lets it point to the robot_self_destruct function pointer in the timer object. With this we can leak and calculate the base address of the binary.
We could also have leaked a libc address from the pthread object in the heap this way and call system("/bin/sh"), but the binary also contains a wat function, which we can use to solve it completely without relying on libc at all.
Though calling wat itself, would call rand and then just return, since the result will probably never be 0. Also jumping anywhere before 0x1E42 would fill the parameters with junk making execve fail.
Since the robot_self_destruct function will be called with its first argument pointing to our robot object itself, we luckily don’t need anything else than the execve call, so we can just overwrite robot_self_destruct with elfbase + 0x1e42, which will then call execve(botname, 0, 0).
Just put a /bin/sh into the name of a bot and overwrite the robot pointer in the next timer object with it and overwrite robot_self_destruct with elfbase + 0x1e42 and then just… wait…