The link for unexploitable leads us to a website for guessing keys.
So, the source code for the page is also available via Get Source
Let’s take this apart
reads key1 from tmp/key1
reads key2 from tmp/key2
for getting to level 1 we need to provide the content of key1 (upto)
for getting to level 2 we need to provide the content of key2 (upto)
if we’re level2 we’re allowed to use the exploit service, which will b64decode our input and pass it to the unexploitable binary (via nc)
Ok, so first let’s get levelled up. For getting the source, the website navigated to http://206.189.83.108/source?name=app.py.
We can also use this to retrieve other files:
http://206.189.83.108/source?name=key1
http://206.189.83.108/source?name=key2
This rewards us with both key files, so we can jump to level2 by entering the content and select Up Level. When reaching level 2, we get a new input field and an Exploit button. This would be way easier, if we would have access to the binary itself.
http://206.189.83.108/source?name=unexploitable
takes care of this :)
So, thanks to DutChen18 for getting rid of that initial web stuff ;)
The binary is statically linked
and contains an obvious buffer overflow, when passing an input longer than 24 characters. So, this should lead to an easy ropchain challenge :)
We just have to consider, that it is run via the website, so we have no stdin to add input later on or reuse any leaks, so our ropchain has to be fire & forget and just do everything without additional input.
One could have guessed that the flag would be located at /home/unexploitable/flag but I wasted the most time trying getting some ls / ls /home / ls /home/unexploitable ready just to realize the obvious, so I spare you the pain until there, and just get on with the open/read/write approach I used in the end ;)
The main problem that remains is getting the needed strings to some known address to use it for an execve or open/read/write ropchain. Since ASLR is active, we cannot reference strings directly from our payload. We should place them somewhere more convenient, like the bss.
While enumerating the directories on the server to find the flag location, I switched from different methods of putting the strings to bss, since I always reached the input limit of 256 chars for our payload.
Starting from just popping the values into a register, pop the destination address into another and then use a mov [rxx], rxx to write the value to the bss. But for doing a ls /home/unexploitable this ultimately reached the input limit, so I switched to some other tricks for getting the strings moved to bss.
Before executing this gadget, we’ll pop the destination address (0x6c9198) into rdi and the string "eexploit" into rdx.
When the ropchain starts, rsi will point to the start of our payload. So it will store the first 8 bytes of our payload into rcx (for this we’ll put “/home/un”” at the start of our payload).
mov [rdi+0x7], rdx] will then write the value of rdx ("eexploit") to 0x6c9198+7
After that mov [rdi], rcx will write the content of rcx ("/home/un") to 0x6c9198 (overwriting the superfluous e from "eexploit").
Thus, we copied the first 15 chars while only wasting 5 gadgets (instead of 10 when using pop/pop/mov).
For the next 15 bytes, we cannot reuse rsi anymore, since it’s still pointing to the start of our payload, but we’ll reuse that gadget in a shorter form
This time, we have to pop rcx for ourself and cannot rely on the first opcode filling it up, but still we only needed 7 gadgets to write the rest of the string to bss (where now /home/unexploitable/flag is stored).
We could now continue with an ordinary open/read/write ropchain
But well, this will lead to a 288 bytes payload again :(
So, we’ll have to optimize this a little bit further. We can get rid of
int the open ropchain, by just executing our bss write ropchain in the opposite order:
Thus, rdi will already point to 0x6c9198 from writing the first 15 characters to bss so we don’t have to set it again.
In shellcode, we’ll nearly always set rax to 0 by using xor rax, rax. We can do the same here, since there’s is a xor rax, rax gadget in the binary, so we’ll change it to
Still 264 bytes, one more gadget to get rid of. This last gadget was quite frustrating to find (though in hindsight, it might even have been easier to switch to an execve ropchain…)
Though, this might corrupt rax, it will definitely set rdx to a nice value for read, so let’s just execute this gadget before open (which will fixup the corrupted rax), and get rid of
resulting in the final payload:
Exactly 256 bytes and on the local test environment it outputs the flag
So, let’s copy that base64 output and feed the website with it :)