About
This post is part of a series in which I’ll post writeups for all challenges of the OverTheWire Advent Bonanza 2019 CTF. To see the intro, click here
Overview
So apparently Santa got stuck on an island and is sending SMSes to Rudolph, and we have got key logger logs from his cellphone. We have a few known SMSes and an unknown one, probably containing the flag. When we download the .tar.gz file, we get a few files: a C header file, and a few known SMSes in CSV and text format, and an unknown SMS CSV file. The header reviles the following mapping of the keys of Santa’s cellphone:
- [0] Key 0 -> ” 0″
- [1] Key 1 -> “.,’?!\”1-()@/:”
- [2] Key 2 -> “abc2”
- [3] Key 3 -> “def3”
- [4] Key 4 -> “ghi4”
- [5] Key 5 -> “jkl5”
- [6] Key 6 -> “mno6”
- [7] Key 7 -> “pqrs7”
- [8] Key 8 -> “tuv8”
- [9] Key 9 -> “wxyz9”
- [10] Key * -> “@/:_;+&%*[]{}”
- [11] Key # -> T9, T9_CAPS, ABC, ABC_CAPS
- [100] Key LEFT -> LEFT
- [101] Key RIGHT -> RIGHT
- [102] Key UP -> UP
- [103] Key DOWN -> DOWN
- [104] Key CALL_ACCEPT -> CALL_ACCEPT
- [105] Key CALL_REJECT -> CALL_REJECT
So let us begin decoding our SMSes
SMS 1
So, let’s start by looking at the resulting SMS:
date: 1999-11-23 03:01:10
to: 00611015550117
text: rudolf where are you brrr
And we also look at the CSV:
945918410125,100
945918410538,100
945918411013,100
945918411409,100
...
Now the first column seems to be some sort of timestamp, and the second one seems to be the actual key pressed. So lets just ignore the timestamp for now, and just print out every key pressed:
$ python parse.py sms1.csv LEFT LEFT LEFT LEFT # # 7 7 7 8 8 3 6 6 6 5 5 5 3 3 3 0 9 4 4 3 3 7 7 7 3 3 0 2 7 7 7 3 3 0 9 9 9 6 6 6 8 8 0 2 2 7 7 7 7 7 7 7 7 7 LEFT LEFT 0 0 6 1 1 0 1 5 5 5 0 1 1 7 LEFT
So looking at that, we may recognize that our target phone number is in this CSV, namely 00611015550117, surrounded by two LEFTs each. In between there’s a huge chunk of key presses, so lets look into those a bit more.
We can see that some keys are repeated more than once. When we think back to old cellphones, we could assume that that cycles through the characters mapped to that specific key. So let’s try that on the huge chunk of key presses:
$ python parse.py sms1.csv rudolf where are you bTraceback (most recent call last): ... IndexError: string index out of range
Well, that didn’t quite work out. Let’s see why: The message ends in three ‘r’s. Because our code doesn’t care about the timestamp, it interpreted this as 9 consecutive presses of the 7 Key. But there’s no interpretation of 9x 7 Key, so we crash. The solution is that for a key press to be counted as a consecutive key press, it needs to happen in about 0.75 seconds after the previous one. With this implemented, we get a perfect reconstruction of the SMS:
$ python parse.py sms1.csv rudolf where are you brrr
SMS 2
The real SMS is:
date: 1999-11-23 03:04:11
to: 00611015550117
text: its too damn cold here and im out of eggnog lul
Let’s try our parse script from before:
$ python parse.py sms2.csv its fuckingTraceback (most recent call last): ... IndexError: list index out of range
So, it seems our parser doesn’t like bad words and crashed. Jokes aside, how did that ‘f***ing’ get in there? Let’s implement an failsafe when an invalid key press is encountered:
$ python parse.py sms2.csv its fuckin Encountered invalid keypress 101! Encountered invalid keypress 101! Encountered invalid keypress 101! Encountered invalid keypress 101! Encountered invalid keypress 101! Encountered invalid keypress 101! Encountered invalid keypress 101! 4too damn cold here and im out of eggnog lul
We see that we encounter a few RIGHT presses. Could it be that RIGHT is functioning as a backspace? Let’s implement that:
$ python parse.py sms2.csv
its too damn cold here and im out of eggnog lul
And we reconstructed the SMS perfectly!
SMS 3
This is the last known SMS:
date: 1999-11-23 03:06:39
to: 00611015550117
text: sorry bout my last 2msg but i could really need your help bud :*
Let’s try our old script:
$ python parse.py sms3.csv Encountered invalid keypress 102! Encountered invalid keypress 102! Encountered invalid keypress 102! Encountered invalid keypress 102! Encountered invalid keypress 102! Encountered invalid keypress 102! ... Encountered invalid keypress 103! sorri bout my last 2msg but i could realy need your help buy
OK, not quite and there’re a lot of UPs and DOWNs. So, let’s think a little: On old cellphones you could usually cycle between menus while writing an SMS. Maybe UP and DOWN cycle through the menus? So lets have an integer, which increments when pressing UP and decrements when pressing DOWN, and only care about key presses when that integer is 0:
$ python parse.py sms3.csv sorri bout my last 2msg but i could realy need your help bud :*
And again a perfect reconstruction! (The differences are intended, nothing to worry about)
EDIT: Thanks to Thuf1r from the CTF Discord, I realized I made a mistake. The UP and DOWN keys don’t actually cycle menus, but actually move the cursor back and forth. Because that doesn’t affect the flag, I wont fix the error.
SMS 4 (The unknown)
So let’s try our script on the unknown SMS:
$ python parse.py sms4.csv alright pal hers ye flag good lucj entering it with those hooves lol its aotw{l3ts_dr1nk_s0m3_eggn60g_y0u_cr4zy_d33r}
And we’ve got the flag!
aotw{l3ts_dr1nk_s0m3_eggn60g_y0u_cr4zy_d33r}
And here’s my (uggly) script:
#!/usr/bin/python import csv import sys mappings = {0: " 0", 1: ".,'?!\"1-()@/:", 2: "abc2", 3: "def3", 4: "ghi4", 5: "jkl5", 6: "mno6", 7: "pqrs7", 8: "tuv8", 9: "wxyz9", 10: "@/:_;+&%*[]{}"} with open(sys.argv[1], "r") as f: c = csv.reader(f, delimiter=',') cnt = 0 lk = -1 lt = 0 m = 0 s = "" for r in c: cnt += 1 if lk == -1 and cnt < 7: continue t = int(r[0]) k = int(r[1]) if k == 100: break if m == 0: if (k != lk or t - lt > 750) and lk < 100: if lk >= 0: s +=mappings[lk][(cnt - 2) % len(mappings[lk])] cnt = 1 lk = k lt = t if m == 0 and k == 101: s = s[:-1] cnt = 0 lk = -2 lt = 0 continue elif k == 102: m += 1 cnt = 0 lk = -2 lt = 0 continue elif k == 103: m -= 1 cnt = 0 lk = -2 lt = 0 continue elif m == 0 and k > 100: print("\nEncountered invalid keypress %d!" % k) cnt = 0 continue if lk < 100: s +=mappings[lk][(cnt - 2) % len(mappings[lk])] print(s)