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:
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.
This gives us the flag: flag{f34r_n0t_th3_4nc13n7_R0pn1qu3}