After having played the EKOParty pre-CTF a few week backs, I decided to take a quick look at what the conference CTF finals looked like. Unlike the pre-CTF, the finals CTF only ran for a couple of days rather than a whole week. I thought I’d warm up a little on the Baby pwn 50pt pwnable challenge, but as you’ll see it gave me a little more trouble than it probably should have, that or I’m just more of a newb than I thought.

Opening the challenge binary up in IDA Pro reveals the three main functions that perform most of the logic in the program. The first function prints the welcome banner, reads in a 32bit unsigned integer from the socket, and ensures that it is less than 0x3ff.

The second function allocates a buffer of the user supplied length and reads that many bytes into the newly allocated buffer from the socket. It then calls the third function.

The last function performs a number of trivial checks on several of the leading bytes. If these checks pass, the function then copies 0x50 bytes from the input buffer onto the stack. It then proceeds to iterate through a loop that XORs each byte of the destination buffer with 0x158 and prints it. The original constraint for the loop is a variable that is set from the input buffer that must be less than the initial size set.

Looking closer at the 3rd function, it appears the memcpy from the input buffer to the stack will always cause a memory corruption so we merely have to construct the input packet to overwrite EIP at the right offset. Since we control the loop constraint, there is also a memory disclosure of up to 0x3ff bytes. After setting up a POC to pass the input checks, we get the following output from the binary. We make sure to XOR each output byte with 0x158 so we get the original value in memory. 3c3d3b373c3d3c6252414141414141200000004242424242424242424242424242424242421c000000

4242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242

420000000000000000583bbebf69880408800000004c3bbebf0c3cbebf583bbebf25e262b7903576b78b8804088000

00008088040800000000d83bbebf165e61b701000000043cbebf0c3cbebf583875b7219876b7fffffffff41f77b78c

83040801000000c03bbebf162c76b7c02a77b7483b75b7f49f74b70000000000000000d83bbebf80a366f791d5ab49

000000000000000000000000010000008085040800000000c08976b73b5d61b7f41f77b70100000080850408000000

00a18504081088040801000000043cbebf8088040870880408903576b7fc3bbebf082977b7010000007f4ebebf0000

0000864ebebf904ebebf984ebebfa84ebebfba4ebebfc74ebebfd84ebebfe34ebebf254fbebf344fbebf474fbebf57

4fbebf674fbebf7d4fbebf9a4fbebfaf4fbebfcc4fbebfe14fbebf0000000020000000004475b721000000004075b7

10000000fffb8b17060000000010000011000000640000000300000034800408040000002000000005000000080000

0007000000005075b7080000000000000009000000808504080b000000e80300000c000000e80300000d000000e803

00000e000000e8030000170000000000000019000000fb3cbebf1f000000f14fbebf0f0000000b3dbebf0000000000

0000000000001eb176483188c57f7f8f519a858f5b396936383600000

Running it a few more times, we see that the stack and library addresses change with each run. It appears the binary is running with ASLR enabled. We run our checksec script on our local copy to see what other protections may be compiled in. Luckily it appears ASLR is the only thing we have to bypass.

One important thing to note from our memory disclosure output, was we noticed that the value we supplied for the length of the memory disclosure appeared to be ignored. The program was leaking memory until we hit the page boundary, causing the program to crash before we return to our overwritten EIP. Taking a closer look, we realized this was happening because the loop constraint variable was also being overwritten by the memory corruption memcpy. Once we updated our POC, we were able to overwrite EIP and gain code execution.

At this point, I assumed that since this challenge was only 50 pts, all that remained was plugging in a simple /bin/sh shellcode after returning to some set of ROP gadgets that could jump to ESP. Unfortunately, I couldn’t track down a single ROP gadget I could use to get the current stack address. This was necessary since the target OS had ASLR enabled. I also had very little space to work with since the total memcpy was 0x50 bytes and some of that was for passing the input checks.