From 6c4f1cd1d782d7c6916d4671fe0b9bac7f95bebe Mon Sep 17 00:00:00 2001 From: mli Date: Mon, 20 Jan 2020 03:29:25 +0100 Subject: [PATCH] add otw-bonanza writeup --- writeups/mli/otw-advent-bonanza-19.md | 331 ++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 writeups/mli/otw-advent-bonanza-19.md diff --git a/writeups/mli/otw-advent-bonanza-19.md b/writeups/mli/otw-advent-bonanza-19.md new file mode 100644 index 0000000..d24e7e9 --- /dev/null +++ b/writeups/mli/otw-advent-bonanza-19.md @@ -0,0 +1,331 @@ +# OTW Advent Bonanza 2019 + +## Time spent + +In total I spent 2+4+3+1+1 for the challenges plus 1 hour for this writeup and also at least half a day to look through the challenges' solutions and writeups after the bonanza was over. __Total 16 hours__ + +## day-01: `7110` (keylogger programming pwned!) + +Description: + +Santa is stranded on the Christmas Islands and is desperately trying to reach his trusty companion via cellphone. We've bugged the device with a primitive keylogger and have been able to decode some of the SMS, but couldn't make much sense of the last one. Can you give us a hand? + +Download: f01d32e3f32957cf42f9672e78fcb415c6deac398fdacbd69531a322b08a39c8-7110.tar.gz + +containing the files + +* keys.h +* sms1.csv +* sms1.txt +* sms2.csv +* sms2.txt +* sms3.csv +* sms3.txt +* sms4.csv +* sms4.txt + +### Time spent + +It took me 2 hours to write the script parsing the sms messages. + +### Overview + +Basically we can use the logged keys incl. timestamps to reconstruct text (but we need to know which number how often resolves to which key - what we have in keys.h - and the timelimit/interval which we decide on change actual character/write next character - which we need to guess, I used a value of 900ms). + +### Solution/Exploit + +Here is my script: + +```python +#!/usr/bin/env python3 + +# mapping from keys.h +N7110_KEYPAD_ZERO = 0, +N7110_KEYPAD_ONE = 1, +N7110_KEYPAD_TWO = 2, +N7110_KEYPAD_THREE = 3, +N7110_KEYPAD_FOUR = 4, +N7110_KEYPAD_FIVE = 5, +N7110_KEYPAD_SIX = 6, +N7110_KEYPAD_SEVEN = 7, +N7110_KEYPAD_EIGHT = 8, +N7110_KEYPAD_NINE = 9, +N7110_KEYPAD_STAR = 10, +N7110_KEYPAD_HASH = 11, +N7110_KEYPAD_MENU_LEFT = 100, +N7110_KEYPAD_MENU_RIGHT = 101, +N7110_KEYPAD_MENU_UP = 102, +N7110_KEYPAD_MENU_DOWN = 103, +N7110_KEYPAD_CALL_ACCEPT = 104, +N7110_KEYPAD_CALL_REJECT = 105 + +N7110_IME_T9 = 0, +N7110_IME_T9_CAPS = 1, +N7110_IME_ABC = 2, +N7110_IME_ABC_CAPS = 3 + +N7110_KEYPAD_ZERO_ABC_CHARS = " 0" +N7110_KEYPAD_ONE_ABC_CHARS = ".,'?!\"1-()@/:" +N7110_KEYPAD_TWO_ABC_CHARS = "abc2" +N7110_KEYPAD_THREE_ABC_CHARS = "def3" +N7110_KEYPAD_FOUR_ABC_CHARS = "ghi4" +N7110_KEYPAD_FIVE_ABC_CHARS = "jkl5" +N7110_KEYPAD_SIX_ABC_CHARS = "mno6" +N7110_KEYPAD_SEVEN_ABC_CHARS = "pqrs7" +N7110_KEYPAD_EIGHT_ABC_CHARS = "tuv8" +N7110_KEYPAD_NINE_ABC_CHARS = "wxyz9" +N7110_KEYPAD_STAR_ABC_CHARS = "@/:_;+&%*[]{}" + +mapping = { + '0': " 0", + '1': ".,'?!\"1-()@/:", + '2': "abc2", + '3': "def3", + '4': "ghi4", + '5': "jkl5", + '6': "mno6", + '7': "pqrs7", + '8': "tuv8", + '9': "wxyz9", + '10': "@/:_;+&%*[]{}" +} + +def read_file(name): + import csv + # https://stackoverflow.com/a/38370569/1380748 + reader = csv.reader(open(name, 'r')) + l = [] + for row in reader: + time, key = row + l.append((time, key)) + return l + +def time(t): + return t[0] +def keyx(t): + return t[1] + +def key(l): + timeout=900 + out=[] + for i in range(len(l)): + j = i+1 + if j < len(l) and keyx(l[j])==keyx(l[i]) and int(time(l[j]))-int(time(l[i])) < timeout: + continue # next char may be the real + key = keyx(l[i]) + keycount = 1 + counter = i - 1 + while counter > 0: + if keyx(l[counter+1]) == keyx(l[counter]) and int(time(l[counter+1]))-int(time(l[counter])) < timeout: + keycount += 1 + counter = counter - 1 + else: + counter = 0 + out.append((key, keycount)) + return out + +def mapit(l): + result="" + for char, cnt in l: + if int(char) > 10: + if int(char) == 101: # N7110_KEYPAD_MENU_RIGHT looks like the deletion key + result = result[:-cnt] + else: + result += repr((char,cnt)) + else: + result += mapping[char][(cnt-1) % len(mapping[char])] + return result + + +if __name__ == "__main__": + files = ["sms1.csv", "sms2.csv", "sms3.csv", "sms4.csv"] + for f in files: + print(mapit(key(read_file(f)))) +``` + +script above prints following output: + +```output +('100', 3)('11', 2)rudolf where are you brrr('100', 1)('100', 1)0m, .l ,p('100', 1) +('100', 3)('11', 2)its too damn cold here and im out of eggnog lul('100', 2)0m.. .l ,p('100', 1) +('100', 3)('11', 2)sorri bout my last 2msg but i could realy need your help bud('102', 20)l('102', 36y('103', 56) :*('100', 2)0m, .l ,p('100', 1) +('100', 3)('11', 2)alright pal hers('102', 1)e('103', 1) ye flag good lucj enter('102', 7)('103', 1k('103', 6)ing it with those hooves lol its aotw{l3ts_dr1nk_s0m3_eggn0g_y0u_cr4zy_d33r}('100', 2)0m.. .l ,p('100', 1) +``` + +including the flag `aotw{l3ts_dr1nk_s0m3_eggn0g_y0u_cr4zy_d33r}` :D + +## day-02: `Summer ADVENTure` (crypto rev network misc pwned!) + +Description: + +An elf is tired of snow and wanted to visit summer... in an online RPG at least. Could you help him beat the game? + +Service: + +Server setup description: + +### Time spent + +I spent about 4 hours on this challenge. + +### Overview + +I started by playing the game for some time. Navigation is by WASD, you can buy stuff and have to fight monsters (for which you get money). You can level up and have experience and Health points. + +There is a scroll of secrets to buy which costs 10.000.000 money. Phew. Is the flag in there? + +I notices the following things: When running a fake server, the only information I can receive (from the fake client on their server) is always something like 0x84f91202e7062ec4d1ca8e50a6b0a1aa - after I send "success" and some newlines in plaintext and after I log in in the web browser. This response varies depending on what I type as username/password in the browser. For example, for user:pw 0000:0000 I receive b'\x84\xf9\x12\x02\xe7\x06.\xc4\xd1\xca\x8eP\xa6\xb0\xa1\xaa', for 1111:1111 I get b'\x84\xf9\x12\x02\xe7\x06/\xc5\xd0\xcb\x8eP\xa7\xb1\xa0\xab'. I could not spot a real pattern here - the username/pw is not directly in this response. But indirectly it is, because the last 4 bytes XORed with the bits 10010110 10000000 10010001 10011010 are the password (I don't know what this pattern should mean, I extracted it from trying different passwords) and changing user/pw on only 1 character changes only one byte. Changing length of user/pw changes more bits, so maybe there is length encoded. + +I cannot trigger any more messages, and I cannot manage the browser to come further than "loading the game" (it only displays "Loading..." in the right bottom corner after I send success). + +We have to reverse-engineer the protocol server-client (they posted this as a hint: ) but have nothing to start. I did not manage to guess anything noteworthy than "success", where you get the encoded (?) login data back. I did not try any fuzzer. + +I also wanted to look at the websocket traffic to the browser, but the challenge makers write in a hint that this does not reveal any information we need (because only blazor protocol/HTML elements, important transmission is between server-client and server-server) so I did not try further. + +### Solution/Exploit + +I did not manage to get anywhere near a solution here because I only spent a few hours on this. + +Fellow w0yers managed to get the flag the weeks after the challenge started. The communication was encrypted using some cipher. +The flag was splitted into client/server-side flag. +Writeup: + +## day-03: `northpole airwaves` + +Description: + +During our latest expedition to the north pole, one of our scientists tuned her radio to 88Mhz and captured a slice of 1MHz (samplerate = 1M/s) to a GNURadio file sink. Surprisingly, there was more than just silence. We need your help to figure out what's going on. + +Download: d6d0f728ac3ff4848f849b8cf222fb38a14aee870184cc22f2efe01336b2ec17-northpole-airwaves.bin + +### Time spent + +I spent (wasted) 3 hours for this challenge, most of the time downloading, converting and playing around/learning with GNU-Radio. + +### Overview + +I downloaded the challenge file and found out that it was around 740MB in size. I always wanted to play around with GNURadio so I downloaded it and tried to find need information in gnuradio tutorials online. Although I spent a few hours with this because the conversions took so long and I did not really know what I am doing, I found out absolutely nothing. + +### Solution/Exploit + +I did not manage to get ANY information for this challenge. Apparently a third of the flag was in the RDS data. + +Writeup: + +## day-04: `mooo` (web pwned!) + +Description: + +'Moo may represent an idea, but only the cow knows.' - Mason Cooley + +Service: + +### Time spent + +1 hour + +### Overview + +We are provided with a web interface where we can type a message and choose a Cow type. +I tried various special characters in the message field but there was no sign of some injection happening. When selecting different Cow types, the ascii output looked different. I tried to change the value of the combobox to some non-provided values but this also not worked. + +By using the 'custom' Cow type, I got redirected to another page called cow designer (`/cow_designer`), where you could design your own cow including eyes and tongue. Okay, another input field for injection possibility. I played around but could not really do anything. Then I googled for some of the Cow type names and found out that those are from the `cowsay` program, which exists to print ASCII cows. :D + +I looked at the sourcecode on Github (as and ), found out this was Perl code and the cow paintings always ended with `EOC`. + +I typed this EOC into the custom cow designer and a basic perl print command. It worked, so command injection was here. I googled for perl system functions and found system() which worked out of the box. + +### Solution/Exploit + +See above. Flag: `AOTW{th3_p3rl_c0w_s4ys_M0oO0o0O}` + +Writeup with more technical details: + +## day-05: `Sudo Sudoku` (misc sudoku pwned) + +Description: + +Santa's little helpers are notoriously good at solving Sudoku puzzles, so they made a special variant... + +Download: 2a3fad4ea8987eb63b5abdea1c8bdc75d4f2e6b087388c5e33cec99136b4257a-sudosudoku.tar.xz + +Content: + +* challenge.txt + +which includes a Sudoku board as below as well as the additional rules: + +```rules +In addition to the standard Sudoku puzzle above, +the following equations must also hold: + +B9 + B8 + C1 + H4 + H4 = 23 +A5 + D7 + I5 + G8 + B3 + A5 = 19 +I2 + I3 + F2 + E9 = 15 +I7 + H8 + C2 + D9 = 26 +I6 + A5 + I3 + B8 + C3 = 20 +I7 + D9 + B6 + A8 + A3 + C4 = 27 +C7 + H9 + I7 + B2 + H8 + G3 = 31 +D3 + I8 + A4 + I6 = 27 +F5 + B8 + F8 + I7 + F1 = 33 +A2 + A8 + D7 + E4 = 21 +C1 + I4 + C2 + I1 + A4 = 20 +F8 + C1 + F6 + D3 + B6 = 25 +``` + +```sudoku + 1 2 3 4 5 6 7 8 9 + +-------+-------+-------+ +A | . . . | . . . | . . 1 | +B | . 1 2 | . . . | . . . | +C | . . . | . . . | 2 . . | + +-------+-------+-------+ +D | . . . | . . . | . . 2 | +E | . 2 . | . . . | . . . | +F | . . . | . . . | . . . | + +-------+-------+-------+ +G | . . . | . . . | 1 2 . | +H | 1 . . | . . 2 | . . . | +I | . . . | 1 . . | . . . | + +-------+-------+-------+ +``` + +### Time spent + +I spent 1 hour for this challenge + +### Overview + +We have to solve the sudoku. Best way would be an SMT/SAT solver. + +### Solution/Exploit + +I tried customizing the Python Sudoku solver script at by extending the eliminate function with very naive if statements + +```python +def eliminate(values, s, d): + # ... + if len(values['B9']) == 1 and len(values['B8']) == 1 and len(values['C1']) == 1 and len(values['H4']) == 1: # len 1 means only 1 value possible for those fields + if int(values['B9'])+int(values['B8'])+int(values['C1'])+int(values['H4'])*2 != 23: + return False # reject solution if B9 + B8 + C1 + H4 + H4 != 23 + if len(values['A5']) == 1 and len(values['D7']) == 1 and len(values['I5']) == 1 and len(values['G8']) == 1 and len(values['B3']) == 1: + if int(values['D7'])+int(values['I5'])+int(values['G8'])+int(values['B3'])+int(values['A5'])*2 != 19: + return False + if len(values['I2']) == 1 and len(values['I3']) == 1 and len(values['F2']) == 1 and len(values['E9']) == 1: + if int(values['I2'])+int(values['I3'])+int(values['F2'])+int(values['E9']) != 15: + return False + if len(values['I7']) == 1 and len(values['H8']) == 1 and len(values['C2']) == 1 and len(values['D9']) == 1: + if int(values['I7'])+int(values['H8'])+int(values['C2'])+int(values['D9']) != 26: + return False + if len(values['I6']) == 1 and len(values['A5']) == 1 and len(values['I3']) == 1 and len(values['B8']) and len(values['C3']) == 1: + if int(values['I6'])+int(values['A5'])+int(values['I3'])+int(values['B8'])+int(values['C3']) != 20: + return False + # ... + the other rules + return values +``` + +But this was highly inefficient even with only the first if, although it works. + +I then saw in Mattermost that we already solved this challege using Z3 and stopped trying improving this python script. + +Writeup: -- 2.43.0