]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/mli/otw-advent-bonanza-19.md
add otw-bonanza writeup
[pub/jan/ctf-seminar.git] / writeups / mli / otw-advent-bonanza-19.md
1 # OTW Advent Bonanza 2019
2
3 ## Time spent
4
5 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__
6
7 ## day-01: `7110` (keylogger programming pwned!)
8
9 Description:
10
11 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?
12
13 Download: f01d32e3f32957cf42f9672e78fcb415c6deac398fdacbd69531a322b08a39c8-7110.tar.gz
14
15 containing the files
16
17 * keys.h
18 * sms1.csv
19 * sms1.txt
20 * sms2.csv
21 * sms2.txt
22 * sms3.csv
23 * sms3.txt
24 * sms4.csv
25 * sms4.txt
26
27 ### Time spent
28
29 It took me 2 hours to write the script parsing the sms messages.
30
31 ### Overview
32
33 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).
34
35 ### Solution/Exploit
36
37 Here is my script:
38
39 ```python
40 #!/usr/bin/env python3
41
42 # mapping from keys.h
43 N7110_KEYPAD_ZERO = 0,
44 N7110_KEYPAD_ONE = 1,
45 N7110_KEYPAD_TWO = 2,
46 N7110_KEYPAD_THREE = 3,
47 N7110_KEYPAD_FOUR = 4,
48 N7110_KEYPAD_FIVE = 5,
49 N7110_KEYPAD_SIX = 6,
50 N7110_KEYPAD_SEVEN = 7,
51 N7110_KEYPAD_EIGHT = 8,
52 N7110_KEYPAD_NINE = 9,
53 N7110_KEYPAD_STAR = 10,
54 N7110_KEYPAD_HASH = 11,
55 N7110_KEYPAD_MENU_LEFT = 100,
56 N7110_KEYPAD_MENU_RIGHT = 101,
57 N7110_KEYPAD_MENU_UP = 102,
58 N7110_KEYPAD_MENU_DOWN = 103,
59 N7110_KEYPAD_CALL_ACCEPT = 104,
60 N7110_KEYPAD_CALL_REJECT = 105
61
62 N7110_IME_T9 = 0,
63 N7110_IME_T9_CAPS = 1,
64 N7110_IME_ABC = 2,
65 N7110_IME_ABC_CAPS = 3
66
67 N7110_KEYPAD_ZERO_ABC_CHARS = " 0"
68 N7110_KEYPAD_ONE_ABC_CHARS =  ".,'?!\"1-()@/:"
69 N7110_KEYPAD_TWO_ABC_CHARS =  "abc2"
70 N7110_KEYPAD_THREE_ABC_CHARS = "def3"
71 N7110_KEYPAD_FOUR_ABC_CHARS = "ghi4"
72 N7110_KEYPAD_FIVE_ABC_CHARS = "jkl5"
73 N7110_KEYPAD_SIX_ABC_CHARS  = "mno6"
74 N7110_KEYPAD_SEVEN_ABC_CHARS = "pqrs7"
75 N7110_KEYPAD_EIGHT_ABC_CHARS = "tuv8"
76 N7110_KEYPAD_NINE_ABC_CHARS = "wxyz9"
77 N7110_KEYPAD_STAR_ABC_CHARS = "@/:_;+&%*[]{}"
78
79 mapping = {
80     '0': " 0",
81     '1': ".,'?!\"1-()@/:",
82     '2': "abc2",
83     '3': "def3",
84     '4': "ghi4",
85     '5': "jkl5",
86     '6': "mno6",
87     '7': "pqrs7",
88     '8': "tuv8",
89     '9': "wxyz9",
90     '10': "@/:_;+&%*[]{}"
91 }
92
93 def read_file(name):
94     import csv
95     # https://stackoverflow.com/a/38370569/1380748
96     reader = csv.reader(open(name, 'r'))
97     l = []
98     for row in reader:
99         time, key = row
100         l.append((time, key))
101     return l
102
103 def time(t):
104     return t[0]
105 def keyx(t):
106     return t[1]
107
108 def key(l):
109     timeout=900
110     out=[]
111     for i in range(len(l)):
112         j = i+1
113         if j < len(l) and keyx(l[j])==keyx(l[i]) and int(time(l[j]))-int(time(l[i])) < timeout:
114             continue # next char may be the real
115         key = keyx(l[i])
116         keycount = 1
117         counter = i - 1
118         while counter > 0:
119             if keyx(l[counter+1]) == keyx(l[counter]) and int(time(l[counter+1]))-int(time(l[counter])) < timeout:
120                 keycount += 1
121                 counter = counter - 1
122             else:
123                 counter = 0
124         out.append((key, keycount))
125     return out
126
127 def mapit(l):
128     result=""
129     for char, cnt in l:
130         if int(char) > 10:
131             if int(char) == 101: # N7110_KEYPAD_MENU_RIGHT looks like the deletion key
132                 result = result[:-cnt]
133             else:
134                 result += repr((char,cnt))
135         else:
136             result += mapping[char][(cnt-1) % len(mapping[char])]
137     return result
138
139
140 if __name__ == "__main__":
141     files = ["sms1.csv", "sms2.csv", "sms3.csv", "sms4.csv"]
142     for f in files:
143         print(mapit(key(read_file(f))))
144 ```
145
146 script above prints following output:
147
148 ```output
149 ('100', 3)('11', 2)rudolf where are you brrr('100', 1)('100', 1)0m, .l ,p('100', 1)
150 ('100', 3)('11', 2)its too damn cold here and im out of eggnog lul('100', 2)0m.. .l ,p('100', 1)
151 ('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)
152 ('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)
153 ```
154
155 including the flag `aotw{l3ts_dr1nk_s0m3_eggn0g_y0u_cr4zy_d33r}` :D
156
157 ## day-02: `Summer ADVENTure` (crypto rev network misc pwned!)
158
159 Description:
160
161 An elf is tired of snow and wanted to visit summer... in an online RPG at least. Could you help him beat the game?
162
163 Service: <http://3.93.128.89:12020>
164
165 Server setup description: <https://docs.google.com/document/d/1wYlM2ideh5R5I7KDTLFTu_NLQmAJAmV-hVjNlmAOIEY/edit>
166
167 ### Time spent
168
169 I spent about 4 hours on this challenge.
170
171 ### Overview
172
173 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.
174
175 There is a scroll of secrets to buy which costs 10.000.000 money. Phew. Is the flag in there?
176
177 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.
178
179 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).
180
181 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.
182
183 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.
184
185 ### Solution/Exploit
186
187 I did not manage to get anywhere near a solution here because I only spent a few hours on this.
188
189 Fellow w0yers managed to get the flag the weeks after the challenge started. The communication was encrypted using some cipher.
190 The flag was splitted into client/server-side flag.
191 Writeup: <https://github.com/nononovak/otwadvent2019-ctfwriteup/blob/master/day2_summer_adventure.md>
192
193 ## day-03: `northpole airwaves`
194
195 Description:
196
197 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.
198
199 Download: d6d0f728ac3ff4848f849b8cf222fb38a14aee870184cc22f2efe01336b2ec17-northpole-airwaves.bin
200
201 ### Time spent
202
203 I spent (wasted) 3 hours for this challenge, most of the time downloading, converting and playing around/learning with GNU-Radio.
204
205 ### Overview
206
207 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.
208
209 ### Solution/Exploit
210
211 I did not manage to get ANY information for this challenge. Apparently a third of the flag was in the RDS data.
212
213 Writeup: <https://github.com/nononovak/otwadvent2019-ctfwriteup/blob/master/day3_northpole_airwaves.md>
214
215 ## day-04: `mooo` (web pwned!)
216
217 Description:
218
219 'Moo may represent an idea, but only the cow knows.' - Mason Cooley
220
221 Service: <http://3.93.128.89:1204>
222
223 ### Time spent
224
225 1 hour
226
227 ### Overview
228
229 We are provided with a web interface where we can type a message and choose a Cow type.
230 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.
231
232 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
233
234 I looked at the sourcecode on Github (as <https://github.com/tnalpgge/rank-amateur-cowsay/blob/master/cows/TuxStab.pm> and <https://github.com/tnalpgge/rank-amateur-cowsay/blob/master/cows/stegosaurus.cow>), found out this was Perl code and the cow paintings always ended with `EOC`.
235
236 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.
237
238 ### Solution/Exploit
239
240 See above. Flag: `AOTW{th3_p3rl_c0w_s4ys_M0oO0o0O}`
241
242 Writeup with more technical details: <https://github.com/nononovak/otwadvent2019-ctfwriteup/blob/master/day4_mooo.md>
243
244 ## day-05: `Sudo Sudoku` (misc sudoku pwned)
245
246 Description:
247
248 Santa's little helpers are notoriously good at solving Sudoku puzzles, so they made a special variant...
249
250 Download: 2a3fad4ea8987eb63b5abdea1c8bdc75d4f2e6b087388c5e33cec99136b4257a-sudosudoku.tar.xz
251
252 Content:
253
254 * challenge.txt
255
256 which includes a Sudoku board as below as well as the additional rules:
257
258 ```rules
259 In addition to the standard Sudoku puzzle above,
260 the following equations must also hold:
261
262 B9 + B8 + C1 + H4 + H4 = 23
263 A5 + D7 + I5 + G8 + B3 + A5 = 19
264 I2 + I3 + F2 + E9 = 15
265 I7 + H8 + C2 + D9 = 26
266 I6 + A5 + I3 + B8 + C3 = 20
267 I7 + D9 + B6 + A8 + A3 + C4 = 27
268 C7 + H9 + I7 + B2 + H8 + G3 = 31
269 D3 + I8 + A4 + I6 = 27
270 F5 + B8 + F8 + I7 + F1 = 33
271 A2 + A8 + D7 + E4 = 21
272 C1 + I4 + C2 + I1 + A4 = 20
273 F8 + C1 + F6 + D3 + B6 = 25
274 ```
275
276 ```sudoku
277     1 2 3   4 5 6   7 8 9
278   +-------+-------+-------+
279 A | . . . | . . . | . . 1 |
280 B | . 1 2 | . . . | . . . |
281 C | . . . | . . . | 2 . . |
282   +-------+-------+-------+
283 D | . . . | . . . | . . 2 |
284 E | . 2 . | . . . | . . . |
285 F | . . . | . . . | . . . |
286   +-------+-------+-------+
287 G | . . . | . . . | 1 2 . |
288 H | 1 . . | . . 2 | . . . |
289 I | . . . | 1 . . | . . . |
290   +-------+-------+-------+
291 ```
292
293 ### Time spent
294
295 I spent 1 hour for this challenge
296
297 ### Overview
298
299 We have to solve the sudoku. Best way would be an SMT/SAT solver.
300
301 ### Solution/Exploit
302
303 I tried customizing the Python Sudoku solver script at <http://norvig.com/sudoku.html> by extending the eliminate function with very naive if statements
304
305 ```python
306 def eliminate(values, s, d):
307     # ...
308     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
309         if int(values['B9'])+int(values['B8'])+int(values['C1'])+int(values['H4'])*2 != 23:
310             return False # reject solution if B9 + B8 + C1 + H4 + H4 != 23
311     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:
312         if int(values['D7'])+int(values['I5'])+int(values['G8'])+int(values['B3'])+int(values['A5'])*2 != 19:
313             return False
314     if len(values['I2']) == 1 and len(values['I3']) == 1 and len(values['F2']) == 1 and len(values['E9']) == 1:
315         if int(values['I2'])+int(values['I3'])+int(values['F2'])+int(values['E9']) != 15:
316             return False
317     if len(values['I7']) == 1 and len(values['H8']) == 1 and len(values['C2']) == 1 and len(values['D9']) == 1:
318         if int(values['I7'])+int(values['H8'])+int(values['C2'])+int(values['D9']) != 26:
319             return False
320     if len(values['I6']) == 1 and len(values['A5']) == 1 and len(values['I3']) == 1 and len(values['B8']) and len(values['C3']) == 1:
321         if int(values['I6'])+int(values['A5'])+int(values['I3'])+int(values['B8'])+int(values['C3']) != 20:
322             return False
323     # ... + the other rules
324     return values
325 ```
326
327 But this was highly inefficient even with only the first if, although it works.
328
329 I then saw in Mattermost that we already solved this challege using Z3 and stopped trying improving this python script.
330
331 Writeup: <https://github.com/nononovak/otwadvent2019-ctfwriteup/blob/master/day5_sudo_sudoku.md>