7110 - OverTheWire Advent 2019 CTF
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.