It’s been quite a while since my last writeup. I wanted to do a writeup of a simple challenge from Hack.lu CTF 2018. Hack.lu CTF this year unfortunately happened during the workweek so I didn’t get to play much of it. I jumped on in the final hours before the game ended. It was late at night so I wanted to work on an easier challenge. The full downloadable .zip
files for Baby Reverse and Baby Exploit are available:
With that out of the way, here’s the writeup for Baby Exploit.
The challenge for Baby Exploit was a continuation of the challenge Baby Reverse where we were given a binary chall. Essentially, chall was a simple crackme that compared user input against the flag for Baby Reverse. All of the logic for the decryption was contained within a single function and turned out to be a simple xor cipher.
Solving Baby Reverse gave the flag flag{Yay_if_th1s_is_yer_f1rst_gnisrever_flag!}
. This flag for Baby Reverse is the password for the zip for Baby Exploit.
Now, Baby Exploit is a little bit more interesting than Baby Reverse. We’re given several files and a connection to a remote service:
1 | nc arcade.fluxfingers.net 1807 |
The remote server is running the following code in babyexploit.py:
1 | #!/usr/bin/env python3 |
We’re also given a Makefile and asm.template to help us out in building our shellcode. I ended up not needing to use this as you’ll see later on.
Makefile
1 | .PHONY: all |
asm.template
1 | BITS 64 |
The important script is babyexploit.py
. So basically, the server lets us flip one single bit in the chall binary and then runs it. Generally, we want to flip a bit in a jump command so that it jumps to somewhere in the region of memory where we have write access (since the binary prompts for input). The question is: which bit to flip? Let’s take a look again at our disassembly for all the jumps we have available:
From the above linear disassembly, these are the candidate jumps that we can bitflip:
1 | 0x4000A6 75 F0 jnz short loc_400098 |
Before choosing which jump that we should consider for a bit flip, we should probably figure out what region of memory we have control over. It’s easy enough to use gdb
to figure out what region and how much data we have control over writing:
1 | $ gdb chall |
From above, we can see that read()
is called and 0x2e
bytes of data is written to 0x4000d7
. So we want to try to jump somewhere within this region to execute code. From our options, it looks like the only bit to overwrite would be at 0x4000BC
. We want to overwrite bit 3 so the instruction goes from 75 49
to 75 41
. This would jump us to 0x4000FE
. This was easily verified by flipping the single bit in a local copy of the binary and running in gdb
to see where the jump would go.
Now that we know that we want to flip the bit position 3 at byte offset 0xbc
, we need to figure out what shellcode we want to write to those memory positions. Since we can only jump to 0x4000FE
and the writable buffer ends at 0x400105
(0x4000D7 + 0x2E = 0x400105
), let’s write a short jump at 0x4000FE
to jump to the beginning of our writable buffer so we have more memory space to play with.
The jump opcodes we want are EB DD
, which will jump to 0x4000D7
, where the beginning of our buffer is. Since we wrote our jump instruction at 0x4000FE
, we have 0x27
(0x4000FE - 0x4000D7 = 0x27
) bytes worth of space for our shellcode to execute a shell. I grabbed this shellcode from shellstorm after trying several other shellcode implementations that didn’t work:
1 | \x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05 |
Now all that’s left is to put it all together! Of course, when we write our shellcode, we need to remember that the binary does some xor’ing of the bytes. Essentially, each byte is xor’ed with the next byte from the original byte sequence. It was easy enough to write a function which does the opposite of this so that after the binary does the xor cipher, the buffer in memory is restored to our desired shellcode.
The following script connects to the remote server, tells it to flip the bit at byte offset 0xbc
and bit position 3, and then writes the enciphered shellcode to get our shell:
exploit.py
1 | from pwn import * |
Running this script spawns us a remote shell where we can get the flag:
1 | $ python exploit.py |
This was a fun little challenge which didn’t require all too much stress or thinking. It was pretty straightforward and simple but I enjoyed it! Now to go work on HITCON CTF…