HackTheBox: Space — Write-up
Twenty-odd years ago, when I first came to the hacking scene, developing exploits was a lot easier. This was my first lesson when tackling this Pwn challenge on HackTheBox.
I spent far too long recursively falling down rabbit holes about which offsets to use, how best to tackle the shellcode size constraints, etc. I’m embarrassed to admit that this took me several evenings to solve and grab the flag. But, when the penny dropped as to where I was going wrong, it was a very quick solve. Maybe 30 minutes or so.
TL;DR — An old dog learnt a few new tricks about working around randomising stack addresses, popped a shell and got a flag. Shellcode is on my github — scroll to bottom.
The challenge requires you to download a zip file containing an ELF binary called ‘space.’ Inspecting it shows there’s nothing special about it. Should be fairly straightforward.
The binary calls the vuln() function which reads input from stdin. Then that is copied into a stack buffer using strcpy().
After passing the binary with a sequenced string inside GDB I found we had 32 bytes to work with; EIP overwrite happened after 18 bytes:
18 bytes free + EIP overwrite + 10 to end of buffer
That didn’t leave much space at all to spawn a shell. I tried several different versions of shellcode but couldn’t get anything to pop. Frustrated, I attempted to use ROP gadgets to get the shellcode but that was another failure for me. ROP was not the solution r4j, the challenge author, had intended to be used either; I was determined to get this done the right way.
Eventually I concluded splitting the shellcode was the only plausible solution. At this point I knew that after the RET from vuln(), EAX pointed to the start of the buffer and ESP pointed to the last 10 bytes. So to exploit that I would need to put the first stage of code where ESP pointed and do a short JMP backwards to the start of the buffer.
Using the new shellcode I could get a shell to pop in GDB. “Success!” I thought foolishly. After trying the same shellcode outside of a debugger I had nothing but failures. At first I thought I may be able to brute force the offset and slapped together a Python script to do that; it failed! By this point I was pretty disheartened.
After sleeping on things for maybe the third or fourth time I had an epiphany: the address I’ve been setting breakpoints on has been constant! This was true no matter how GDB was invoked — between attaching or running from CLI stacks will move. The code addresses don’t move, though.
So what’s the next step? Well, we know ESP points to my shellcodes entry point so let’s look for a JMP or CALL to ESP that we can use.
Evan’s Debugger has this great little plug-in to hunt for opcodes. That allowed me to hunt out this JMP ESP. That meant we had an address we could put into EIP to cause the execution of our stage 1 shellcode.
After making a quick update to the assembler source I had a shellcode that worked on my local machine. I jumped straight on to HTB, started a remote instance of Space, and…
Remote shell popped. Flag captured. Self-esteem starting to return.
The challenge was pretty fun even with the self-inflicted stress!
When I was last popping shells on machines randomised stacks were not a common thing. If I had been a little more observant I could have probably saved myself some headaches and worked around this. Therefore I’m not really counting it within the difficulty level of the challenge.
The most trying part was getting a shellcode pieced together that avoided overwriting itself. I originally solved that by doing the majority of the set up code beyond the EIP overwrite, then jumped backwards to push values to the stack. Then I realised I could just load EAX into ESP and that completely avoided any overwriting of our own code.
All in all a great challenge. Taught me a few good lessons about how exploit development has changed since I was doing it in the ‘00s.
I’ve added my shellcode to my github.