Tuesday, April 26, 2011

pCTF 2011 #22 Hashcalc1

Challenge #22 "Hashcalc 1" was binary exploitation over the network.

A good write-up is already available on sleepya's blog. He made an exploit bypassing any ASLR/NX using ROP.

However, NX was not enabled on the wargame machine... Organizers thought they did, but it was not effective :( Good for us it means only ASLR, and the binary was not even PIE. One could exploit it quickly by writing a shellcode in the GOT, let's see that.

Context

22 - hashcalc1 - 300 pts

Description
Category: pwnables

Download

nc a9.amalgamated.biz 30001
download file

Analysis

Quickly reverse it and see that it's a network server using regular sockets.


decompilation by IDA Pro with Hex-Rays

In the main function that handles clients connections, we see that the server opens a log file, and writes the user buffer into it using fprintf(fp, buf), which is an obvious format string vulnerability.


Using the format string vulnerability which gives us an arbitrary write, we would like to control the program flow to our shellcode. We can do that by overwriting the pointer of the next dynamic function being called: strlen. This pointer is located in the Global Offset Table (GOT).


strlen's GOT entry is at 0x0804A41C.

Using the format string with two write2 (%hn format specifier) we can overwrite the value at this address to point to our shellcode. You do not want to use a single write4 (%n) because it would require as many characters to be written to the log file.

However, our code is received in a buffer on the stack, which has ASLR, so we cannot easily return to it. No problem, we can just use a bunch of write2 to copy a shellcode in the GOT, for instance right after strlen.

Choosing a shellcode, socket reuse

Since it is a remote exploitation, we can either use a classic connect-back shellcode or reuse the socket and spawn a shell from there. Since the first solution can fail if there is an outbound firewall (but there wasn't), I chose the second one.

In order to do a socket reuse, we need to get the socket file descriptor. Easy, it's incremental: 0 (stdin), 1 (stdout), 2 (stderr), 3 (log file), 4 (server socket) and 5 our client accept socket. You can also simply guess it by trying numbers incrementally.

Then, we need a shellcode to close 0/1/2 and reopen them as duplicates of the client socket file descriptor. Syscall dup2 does that, and a public shellcode doing it for fd 0 already exists. We just adapt it to get the following 17 bytes:
$ echo -ne '\x31\xc9\x31\xdb\xb3\x05\x6a\x3f\x58\xcd\x80\x41\x80\xf9\x03\x75\xf5' |ndisasm -u -                                                                                                                
00000000  31C9              xor ecx,ecx     # ecx (new fd) => 0
00000002  31DB              xor ebx,ebx     # ebx (old fd) => 0
00000004  B305              mov bl,0x5      # ebx = 5, our socket fd
00000006  6A3F              push byte +0x3f
00000008  58                pop eax         # eax (syscall number) = sys_dup2
00000009  CD80              int 0x80        # syscall! dup2(5,ecx)
0000000B  41                inc ecx         # increment new fd so
0000000C  80F903            cmp cl,0x3       # we do this
0000000F  75F5              jnz 0x6         # for fd 0/1/2
Finally, just append a regular /bin/sh shellcode (23 bytes).

Exploit the format string

In order to have an arbitrary write, we need the offset in the stack of our buffer. And since the format string happens in the log file, we don't get this information remotely. Just run it locally and view the log. Good thing here is that this offset will not move (fixed stack, recv into the buffer) and we don't need any padding unlike shell exploitation of format strings, where your arguments influence the stack size and thus the offset and padding.

Then we just have to build our format string with 2 write2 to update strlen's GOT entry and multiple write2 to copy the shellcode in the GOT, right after strlen's GOT entry.
Full exploit here: colored syntax or plain .py

Just run it with nc for the network part, and cat to keep stdin opened:
$ { python exploit.py; cat; } | nc a9.amalgamated.biz 30001
** Welcome to the online hash calculator **
$ id
uid=1009(hashcalc1) gid=1010(hashcalc1) groups=1010(hashcalc1)
Quick & reliable, but remember that this exploit would not have worked if NX had been present, unlike sleepya's and surely others.

No comments:

Post a Comment