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
RIGHT
s. 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.