social



Trend Micro CTF 2017 Quals - Misc 400 writeup

The night

The night air is cool and pleasant against the skin of my old hands. I put the decanter aside, wipe my lips gently, stroke my beard once and turn my mind to the matter at hand again. A Wireshark is waiting on the screen of the machine I borrowed from an unwitting western barbarian, playful and eager to chew on whatever I throw her, already getting used to her new master. I smile and hand her a scroll titled 192_168_64_134.pcapng, acquired as well in my last foray to lands of the mortals; it seems to be a puzzle, and I like puzzles. She quickly works over the scroll and shows me what she has found.

I knit my brow, scrolling the window up and down. Some lines the color of the summer sky catch my eye. Something's not right. Those don't look like DNS queries they claim to be.

Blue DNS queries

Malformed DNS packet

They all go to 192.168.100.100, so I filter everything else out; then, I examine some. I don't recognize the pattern right away, but some parts seem to be constant. I cast a discerning spell upon them.

Discerning spell

SNMP, then! I've heard about it; the barbarians use it on occasion to command their various devices. But doesn't my Wireshark already know how to figure it out? It must need some guidance. I summon a context menu over one of the packets and order the trusty demon to decode everything on the UDP port 53 as SNMP.

SNMP traffic

This helps, to a degree. They are all quite alike, and save the second one they do have the same exact structure. One field, designated 1.3.6.1.4.1.3055.1, appears to signify time, increasing with every packet; of the rest, three change, one pulsating between 44 and 55 and the two others staying relatively close to their previous values. I look them over some more and notice that those two tend to change in every other packet, but sometimes - rarely, though - can break this rule and do it faster.

SNMP variables

A pair of values, changing over time. This speaks to me; could they perchance be Descartes coordinates? For what purpose could they be send to a distant machine, I wonder? I glance at the Jade Rabbit, but he seems to be busy with his mortar right now. Oh well.

I flex and stretch, then relax and set to weave a spell to refine and portray them, so my weary eyes would be able to help me better:

from scapy.all import *
import struct
from PIL import Image, ImageDraw

packets = rdpcap('192_168_64_134.pcapng')

extracted = []
for p in packets.filter(lambda r: r[IP].dst == "192.168.100.100"):
    values = (p.load.split("\x6f\x01\x02\x04")[-1][:4],
              p.load.split("\x6f\x05\x02\x02")[-1][:2],
              p.load.split("\x6f\x06\x02\x02")[-1][:2])
    extracted += [values]

extracted = [(struct.unpack(">I", _[0])[0], 
              struct.unpack(">H", _[1])[0], 
              struct.unpack(">H", _[2])[0]) for _ in extracted]

img = Image.new("RGB", (2000,2000))
draw = ImageDraw.Draw(img)

for p in extracted:
    (x, y) = (p[1], p[2])
    draw.ellipse((x-10, y-10, x+10, y+10), fill="green", outline="green")

img.save("out.png")

out.png

I look down my fingertips and smile to myself. Keypresses, by my fish drum!

Now, everything acquires meaning gradually. The alterating bytes of 44 and 55 are ASCII values of the letters U and D, of course; those must be commands to lift a finger up and bring it down! I have the sequence, and I have the locations; but those are not exact, so I must band them together. I recall a treatise I've read recently that would help me do it in a simple way; I'm going to need a total number of the keys though.

I count them; there are 34 in use, including the odd couple at the far left. The one I recognize as the spacebar, I reckon, should be counted twice, for the humble algorithm would not be able to accomodate groups of different size; 35, then.

First, I store the figures in a corner, to save on the loading times in future. I also make room for the U/D byte there:

from scapy.all import *
import struct

packets = rdpcap('192_168_64_134.pcapng')

extracted = []
for p in packets.filter(lambda r: r[IP].dst == "192.168.100.100"):
    values = (p.load.split("\x6f\x01\x02\x04")[-1][:4],
              p.load.split("\x6f\x04\x04\x01")[-1][:1],
              p.load.split("\x6f\x05\x02\x02")[-1][:2],
              p.load.split("\x6f\x06\x02\x02")[-1][:2])
    extracted += [values]

extracted = [(struct.unpack(">I", _[0])[0], 
              True if _[1][0] == "D" else False, 
              struct.unpack(">H", _[2])[0], 
              struct.unpack(">H", _[3])[0]) for _ in extracted]

print extracted
(ctf) oldzg@zhongtiao:~/work/trendmicro2017quals/misc400$ python store.py > data.txt

What now? I recall the recipe, then adapt it for my purposes: I shall pick out every keypress I haven't sorted out yet and put any others within the right distance in the same basket.

I try the tolerance of 1 first:

data = eval(open("data.txt").read())

clusters = {}
tolerance = 1
cur = 0

for i, p in enumerate(data):
    if i not in clusters:
        clusters[i] = cur
        for j, p2 in enumerate(data):
            distance = sum([(p[_] - p2[_]) ** 2 for _ in [2, 3]])

            if distance < tolerance:
                clusters[j] = cur
        cur += 1

print "Total no of distinct keys: %s" % cur
(ctf) oldzg@zhongtiao:~/work/trendmicro2017quals/misc400$ python count.py
Total no of distinct keys: 282

Hmm. 42, then:

...
tolerance = 42
...
(ctf) oldzg@zhongtiao:~/work/trendmicro2017quals/misc400$ python count.py
Total no of distinct keys: 106

That's better. What if...

...
tolerance = 1337
...
(ctf) oldzg@zhongtiao:~/work/trendmicro2017quals/misc400$ python count.py
Total no of distinct keys: 38

This will do nicely! A couple of extra keys aren't going to present much trouble. And now, I need to see where everything ended up.

...

from PIL import Image, ImageDraw

for i in range(cur):
    img = Image.new("RGB", (2000,2000))
    draw = ImageDraw.Draw(img)

    for j, p in enumerate(data):
        (x, y) = (p[2], p[3])
        if p[1]: # the key is pressed
            if clusters[j] == i:
                draw.ellipse((x-10, y-10, x+10, y+10), 
                    fill="green", outline="green")
            else:
                draw.ellipse((x-10, y-10, x+10, y+10), 
                    fill="red", outline="red")

    img.save("key%d.png" % i)

key0.png key8.png

For a minute, I take in the fruits of my work. Then, I take my brush and a sheet of paper and chart what I see in the images I wrought.

Charted keyboard

And now, I am finally ready to bring it together.

...
keyboard = {
        10: "a",
        14: "s",
        22: "d",
        33: "f",
        19: "g",
        6: "h",
        30: "k",
        27: "l",
        25: ";",
        12: "'",
        21: "<enter>",
        31: "<enter>",
        4: "<shift>",
        34: "c",
        26: "v",
        18: "n",
        23: "m",
        20: ",",
        29: "w",
        7: "e",
        11: "r",
        5: "t",
        9: "y",
        36: "u",
        17: "i",
        24: "o",
        16: "p",
        35: "[",
        37: "]",
        13: "<backspace>",
        32: "`",
        15: "<space>",
        8: "<space>",
        0: "?",
        1: "?",
        2: "?",
        3: "?",
        28: "-",
        }

text = ""
for i, p in enumerate(data):
    if p[1]:
        text += keyboard[clusters[i]]

print text
(ctf) oldzg@zhongtiao:~/work/trendmicro2017quals/misc400$ python typeout.py
??<shift>the<space>year<shift>'<backspace>'s<space>at<space>the<space>spring,<enter><shift>and<space>day's<space>at<space>the<space>morn;<enter><shift>morning's<space>at<space>seven;<enter><shift>the<space>hill-side's<space>dew-pearl'd;<enter><shift>the<space>lark's<space>on<space>the<space>thorn<backspace><backspace><backspace><backspace><backspace>wing;<enter><shift>the<space>snail's<space>on<space>the<space>thorn;<enter><shift>god's<space>in<space><shift>his<space>heaven--<enter><shift>all<space>righ<backspace><backspace><backspace><backspace><backspace>'s<space>right<space>with<space>th<space>world<shift>`<enter><shift>and<space>flag<space>is<space><shift>t<shift>m<shift>c<shift>t<shift>f<shift>[<shift>paracelsus<shift>]<enter>

So, TMCTF{Paracelsus} is the ultimate answer! I lift my eyes again; the rabbit is looking at me quizzically. I raise my hand in a gesture of victory, but he just shakes his head and returns to his mortar.

Sometimes I feel underappreciated.

For more oldzg@zhongtiao writeups, proceed here!