HITCON CTF start pwnable
This pwnable task with the description “Have you tried pwntools-ruby?” was a challenge that was served on: 184.108.40.206 31337
The task contained two files, the first was a ruby script called
and another binary called
start which reveals to be a x64 binary statically linked.
So the task is already pretty clear. it seems as we have to connect to the server and then give our exploit in ruby code and then exploit the local binary serving at
As we can see that they are forcing us to use pwntools in ruby we simply write our exploit in python and then port it later on and debug it on their machines so we don’t have to install it ;)
The first thing we do is check the security enabled on the binary. We do that by opening the binary in gdb-peda and run checksec
Stack canary and NX bit enabled. With that in mind we will now look at the binary.
The binary is rather simple. We can see that it sets an alarm with the timer 0xA(10 seconds) alarm img
Then a little further down we see the call to read with 3 arguments.
read(int fd, void *buf, size_t count);
The first argument is placed in RDI and is the filedescripter 0 which is STDIN
The second argument is the buffer and placed in RSI and is a pointer to the buffer
The third is the buffer size and is placed in RDX and is a buf size of 0xD9 as also commented in the screenshot.
Then the binary calls strncmp and looks for the string
exit\n in the first 5 bytes of the buffer we put data into. If it does contain
exit\n we jump down to the stack cookie check and then if the stack is not smashed we exit cleanly. If
exit\n is not in buffer we simply print the buffer to STDOUT and run the loop again.
With this in mind we now want to see what the stack layout looks like to see if the buffer is the proper size.
as we can see above the stack is only 24 bytes long. We are allowed to write 0xD9(217) bytes to the stack and the buffer overflow is pretty clear. Our problem is here that if we overwrite our buffer we will smash our stack cookie and the binary will crash.
In order to fix this we will need to leak our stack cookie. This is done by using the fact that our buffer is above our stack cookie. If we write 24 bytes in our buffer we can let the program print our buffer. But since the first part of the stack cookie is a null byte, puts will simply stop here. But if we instead send 25 bytes we overwrite 1 byte of the stack cookie(the null byte) and when puts is ran again we will now print our whole buffer + 7 bytes of the stack cookie minus the null byte. This enables us to simply add the null byte afterwards and we now have the stack cookie leaked. We can now overwrite down to the return address(called r) as long as we remember to write the stack_cookie over with the old stack cookie value.
So the payload would look like this after the cookie is leaked:
"A"*24 + LEAKED_STACKCOOKIE + "B"*8 + NEW_RETURNADDR
We now have EIP control over the binary. Onwards to a working exploit!
So as the binary is statically linked we looked for usefull functions in the binary. We ended up taking the long road and ran
mprotect on the stack in order to make it executable again and then simply just jmp to our shellcode with a
jmp rsp gadget.
As mprotect takes 3 arguments
mprotect(void *addr, size_t len, int prot); we needed to set the 3 registers RDI, RSI and RDX
To do this we made a ropchain that looked like this:
In order to do this however we needed to know what to put in as the RDI register(*addr) as this should be a stack address which is page aligned.
To do this we needed to go back and use the trick we did to leak the stack cookie. The thing is that further down the stack there is a stack address, which we can leak(64 bytes down the stack). This address points to a higher address on the stack and we therefore substact 0x300 to get below our buffer and page align it
After we had the leaked stack address, leaked stack cookie and everything else we needed we simply had to put all the bits together and have a working exploit.
This made us end up with the final exploit:
from here we simply had to port our exploit to ruby(which made it quite ugly). So to sum up what this does. We have the ruby exploit in a string. It has to be under 1024 bytes. We send this to the server and they run it and it then exploits the local binary and runs a command that cats the flag.