Apprentice's Return

Apprentice’s Return was the first pwn challenge of SwampCTF 2018. A single binary, return, is provided, an x86 32-bit unstripped ELF.

Opening the file in a dissasembler, we see three functions we’re interested in:

  • main()
  • doBattle()
  • slayTheBeast()

The first, main(), simply calls doBattle().

The function slayTheBeast() outputs the flag, so this is our target function.

doBattle() prints out some story, then performs a read() of 50 bytes from stdin to a buffer. Looking at the stack, we see the input buffer resides at ebp-0x26, meaning we can overflow it by 12 bytes. The return address resides 8 bytes away from the end of the buffer, so it can be overwritten, as well as another 4 bytes after.

Before the function returns, however, the return address is checked. If the value is greater than or equal to 0x8048595, the program exits, otherwise it outputs a line and returns, giving the attacker control over eip.

The first choice here would be to overwrite eip with the address of the slayTheBeast() function, however that does not work as the address 0x80485db is larger than the permitted value. If we try the largest permitted value, we see it lands at the nop instruction at the very end of the doBattle() function.

This is where the additional extra four bytes we can overflow come into play. We have exactly enough room for another address. If we choose the maximum value for our first address, it would appear that you could loop back and return to the second address provided. This is the basis of return-oriented programming (ROP) attacks. The challenge and binary name are clues to this. However, the leave instruction before the ret is a hinderance, as it messes up the stack frame, meaning our second address is not used.

Instead, we look for a different gadget. Using ROPgadget, we can even specify a range of addresses, so that we don’t get any gadgets with an address greater than the allowed maximum:

ROPgadget --binary return --range 0-0x8048594

This gives us 59 unique gadgets. Of these, the simple ret gadget will work. Our first return will jump from 0x8048596 to this address, 0x0804835a, popping the value from the stack. The next ret from the gadget will be executed, popping our second value from the stack. This has no restrictions, and so we can use the address of the slayTheBeast() function. This function is returned to, and we get our flag.


# arg1 = first return addr - ret gadget (0x0804835A)
# arg2 = target addr - slayTheBeast() (0x80485db)

echo -n $(perl -e 'print "A" x 42, "\x5a\x83\x04\x08", "\xdb\x85\x04\x08"') \
	| nc 1802 | grep flag

This gives us the flag: flag{f34r_n0t_th3_4nc13n7_R0pn1qu3}