7110 - OverTheWire Advent 2019 CTF

30 Dec 2019

Tags: ctf writeup python

This one was one of the easier ones. We are given some .txt files, .csv files, and a single .h C header file. We see that the .csv files contain keylogging data, where the first column is a timestamp and the second column is the key that was pressed. From these .csv files, we are supposed to figure out what Santa typed.

The .csv files correspond to the .txt files (e.g. sms1.csv corresponds to sms1.txt), and we are supposed to decode sms4.csv. Though we are not completely sure what the function keys correspond to (i.e. MENU_LEFT, MENU_RIGHT, MENU_UP, MENU_DOWN), we can still try to decode the file. And so we write the following Python code:

import sys

KS = {
    0: " 0",
    1: ".,'?!\"1-()@/:",
    2: "abc2",
    3: "def3",
    4: "ghi4",
    5: "jkl5",
    6: "mno6",
    7: "pqrs7",
    8: "tuv8",
    9: "wxyz9",
    10: "@/:_;+&%*[]{}",
    11: ['T9', 'T9_CAPS', 'ABC', 'ABC_CAPS'],
    100: ['LEFT' * i for i in range(1, 20)],
    101: ['RIGHT' * i for i in range(1, 20)],
    102: ['UP' * i for i in range(1, 20)],
    103: ['DOWN' * i for i in range(1, 20)],
    104: ['CALL_ACCEPT' * i for i in range(1, 20)],
    105: ['CALL_REJECT' * i for i in range(1, 20)]
}

strokes = []

with open(sys.argv[1], 'r') as f:
    strokes = list(map(lambda s: (int(s.split(',')[0]), int(s.split(',')[1])),
                       f.readlines()))

def process(key, times):
    global KS
    return KS[key][(times - 1) % len(KS[key])]

last_ts, last_key = strokes[0]
repeat = 1
for ts, key in strokes[1:]:
    if last_key != key:
        # The keys changed; output
        print(process(last_key, repeat), end='')
        repeat = 1
        last_ts = ts
        last_key = key
    elif last_key == key:
        # The same key
        repeat += 1
        last_ts = ts
    else:
        # W
        print(f'\nWATT (ts={ts}, key={key})\n')

In essence, we look up a character based on the number of times we see it repeated. Let’s try running it:

$ python analyze.py sms1.csv
LEFTLEFTLEFTLEFTT9_CAPSrudolf where are you bsLEFTLEFT0m, .l ,p

$ cat sms1.txt
date: 1999-11-23 03:01:10
to: 00611015550117
text: rudolf where are you brrr

If you recall, we were also given the timestamps of each keystroke. Remember that if you pressed a key and waited long enough, the key would be printed and you could press another key (even if it is the same key). So we add this delay into play:

# ...
TS_DELTA = 900
# ...
elif last_key == key and ts - last_ts <= TS_DELTA:
	# ...
elif last_key == key and ts - last_ts >= TS_DELTA:
	# The same key but more time has passed
	print(process(last_key, repeat), end='')
	repeat = 1
	last_ts = ts
# ...

We set a constant TS_DELTA as a threshold, equal to 900 ms. We run the program again and see the results:

$ python analyze.py sms1.csv
LEFTLEFTLEFTLEFTT9_CAPSrudolf where are you brrrLEFTLEFT0m, .l ,p

Let’s see if we can do the same with sms4.csv:

$ # Line-breaks for your sanity
$ python analyze.py sms4.csv
LEFTLEFTLEFTLEFTT9_CAPSalright pal hersUPeDOWN ye flag good lucj enterUPUPUPUPUPUPUPDOWNRIGHTk
DOWNDOWNDOWNDOWNDOWNDOWNing it with those hooves lol its
aotw{l3ts_dr1nk_s0m3_eggnogRIGHTRIGHT0g_y0u_cr4zy_d33r}LEFTLEFT0m.. .l ,p

We seem to be very close to the answer. We see that the flag is split up by two RIGHTs. The characters at the left and the right of the delimiter are similar to each other. From this, we deduce that the MENU_RIGHT button acts as a backspace. We make these alterations to the code:

# ...
KS = {
	# ...
	101: ['\b' * i for i in range(1, 20)],
	# ...
}
# ...

\b is an escape sequence that brings the pointer backwards by 1 character.

$ # Line breaks for your sanity
$ python analyze.py sms4.csv
LEFTLEFTLEFTLEFTT9_CAPSalright pal hersUPeDOWN ye flag good lucj
enterUPUPUPUPUPUPUPDOWkDOWNDOWNDOWNDOWNDOWNDOWNing it with those hooves lol its
aotw{l3ts_dr1nk_s0m3_eggn0g_y0u_cr4zy_d33r}LEFTLEFT0m.. .l ,p

Also you can see that the MENU_UP and MENU_DOWN both correspond to moving the pointer to the left and to the right, but since it doesn’t concern the flag, we don’t need to worry about them. It is left as an exercise to the reader to modify the above code so that it takes MENU_UP and MENU_DOWN into account.