As this task is one category higher than the last one (very easy -> easy), I wrote a separate blog post about std::string, as some knowledge about it would be required for this task and I assume you’ve already read it.
After cd-ing to this task’s distrib directory we’ll see a binary and cpp source code, from which we know, that this binary already provides spawn_shell function, though does not call it anywhere, and consists of 2 other functions – play() and main():
As play function contains much boilerplate, I’ll omit the noise and post only the most important code from which we’ll deduce a vector of attack:
Before I tell you how to exploit a vulnerability that is visible on this picture, let’s first run the binary to show what it prints:
As you see, it’s a simple game of making two strings equal by gradually replacing/swapping characters.
Now, going back to the code from the image before – you may have already noticed, that the “replace” command does not provide any sanitization of returned index! If you input a character that does not exist in the string the program searches, it’s going to return std::string::npos, which is 8 bytes, 0xFFFFFFFFFFFFFFFF.
That means, that if we try to access
[string’s data pointer address] + [0xFFFFFFFFFFFFFFFF]
we’re going to end up with…
[string’s data pointer address] – minus one byte!
If you don’t get it, I explained pointer overflow in the post linked above.
As a consequence, we’re going to end up writing in string’s length field’s last byte. That will make string’s length to hold ridiculously high value, and allow us to print more information with “print” command:
You may wonder – what use do I have from this seemingly random data?
Vector of attack – return pointer overflow
This data provides us 2 important facts:
- position of return pointer from play function;
we’ll inject address of spawn_shell there.
- position and value of a set of bytes that we could use with “swap” command;
we’ll use them to swap bytes of play’s return pointer
Of course, we won’t do this manually as it’s too cumbersome. We’ll write a python script for that, but before, we’ll need 2 another facts that gdb’s going to provide:
- spawn_shell address
- address of instruction that comes just after call to play function in main function; it’s going to be the value of return pointer in play function – from that fact we’ll know which bytes to swap to make it return to spawn_shell instead
First one is a simple matter of:
spawn_shell’s address is 0x00000000004011a7 (left padded to 16 bytes since it’s x86_64 platform I’m working on).
In the second one, we’ve got to disassemble main and find the call to the play function:
As you see, return pointer value’s going to be 0x000000000040246d.
Now we have all the information we need; it’s time for the python script.
I won’t comment about it much since it’s self-commenting, but the algorithm is:
- Connect to program (it’s running on a Docker image on port 22224)
- Wait untill it sends the command prompt text (and do it every sending of some command)
- Send ‘replace X a’ – X since there are no upper case characters in those strings so it’s always safe to pick one of those – ‘a’ since the program seemingly doesn’t crush that much with replacing with a’s – but it’s only arbitrary taken character, any other will do.
- Send ‘print’ request to get info of what is after string’s local data pointer
- Find index of return pointer (0x40246d) in what ‘print’ returned – I found it manually by printing hex of returned data to console before writing the rest of the script and just hardcoded it, but one can easily code searching for.
- Find indexes of 0x40, 0x11, 0xa7 bytes. There’s a chance they will be somewhere in program’s memory, there’s a chance they won’t. If not, just restart the exploit, since the stringmaster1 binary is re-spawned every connection.
- Replace return pointer bytes with bytes from indexes we’ve found in the last step
- Print ‘exit’ to make it return to spawn_shell
- Send ‘ls’
- Send ‘cat flag.txt’
Docker was convenient, but I’ll try to implement hacking the binary without docker; hooking python script to stdout, without sockets, just for further experiments.
Also, my python skills need some improvement.