]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/chgue/otw19.md
chgue add more writeups
[pub/jan/ctf-seminar.git] / writeups / chgue / otw19.md
1 # General retrospective
2 In general, the CTF was very fun. The web challenges were a bit guess heavy but the other challenges were fine. Apart from the 4 challenges in this writeup I also looked at some other challenges, mostly Battle of the Galaxies, Summer Adventure Game and Lost in Maze. 
3
4 **Time spent:** 24 hours (incl. other challenges not part of the writeup)
5
6 # EasterEgg 3
7 ## Retrospective
8 Not too interesting but I learned about AZTEC QR codes (which are apparently often used by railway companies, etc.).
9 **Time spent:** 1 hour
10
11 ## Overview
12 The organizers posted this image on twitter with no additional information
13 ![mattermost login required](https://mattermost.w0y.at/api/v4/files/oksmhc5kcfdxmgzu4csnchqowc "challenge image")
14
15 ## Solution
16 Clearly, the first instinct is to use QR code scanner. This results in `137:64:137:154:171:146:63:175` which can be interpreted as ASCII in octal notation (the `:` being separators between the letters). This results in the string `_4_lyf3}`. Because of the `}` this part is presumably the end of the flag. However, some other information must be hidden somewhere.
17
18 I cut out the QR code and used `zxing` and to try out different formats. `zxing --barcode_format=AZTEC qr.png` (Note the Aztec looking letters in the image!) resulted in `414f54577b6234726330643373`. Interpreting this as ASCII in hex notation results in `AOTW{b4rc0d3s`.
19
20 The flag is then given by `AOTW{b4rc0d3s_4_lyf3}`.
21
22 # christmaSSE keygen (day 10)
23 ## Retrospective
24 The challenge was fun and a learning experience since I never heard of SIMD-instructions or used numpy before. An especially memorable lesson was to never forget to think about whether `\\` or `\` is appropriate... This error cost me like 2 hours. Funnily enough, @georg was working on the challenge in parallel. However, he also had one tiny error somewhere in his decoding (endianess was wrong iirc). Before fixing my error I was able to get the flag by combining the output of his matrix exponentiation and my decyption. Teamwork makes the dreamwork! ^^
25
26 **Time spent:** 6 hours
27
28 ## Overview
29 A program which uses SIMD-instructions is given. It takes an input string, does some calculations, transforms the input with the result of the calculations and then prints the transformed string (i.e. the flag). The catch is that the calculations contains loops, an outer and an inner loop. The outer loop has `1234567890123456789` iterations and the inner loop has `1000` iterations. Clearly, this does not terminate for a long time so it has to be optimized.
30
31 ## Solution
32 @HaH translated the assembly instructions to pseudocode. From this it is clear that the SIMD-instructions are used to emulate some sort of operations on matrices and vectors. To be more precise, the instructions can be interpreted as follows:
33
34 The initial matrix is the identity matrix of size 4, i.e. 
35 ```math
36 A_0 =
37 \begin{pmatrix}
38 1 & 0 & 0 & 0\\
39 0 & 1 & 0 & 0\\
40 0 & 0 & 1 & 0\\
41 0 & 0 & 0 & 1
42 \end{pmatrix}
43 ```
44
45 One iteration $`i`$ of the outer loop takes the matrix $`A_{i-1}`$ and computes the following matrix product:
46 ```math
47 A_i' = A_{i-1} \cdot
48 \begin{pmatrix}
49 1 & 2 & 3 & 4\\
50 5 & 6 & 7 & 8\\
51 9 & 10 & 11 & 12\\
52 13 & 14 & 15 & 16
53 \end{pmatrix}
54 ```
55
56 The inner loop implements a modulo operation on $`A_i'`$. Essentially, using some SIMD-instructions the prime modulus $`p`$ is subtracted repeatedly from every value in the matrix $`x`$ long as the $`x - n  \geq 0`$. The inner loop runs a thousand times which is enough since the values are only 32 bit integers. Thus, after the inner loop the resulting matrix is given by 
57 ```math
58 A_i = A_i'  \mod p
59 ```
60
61 Therefore, the nested loops are just applying modular exponentiation on a matrix and the result after all outer loop iterations is given by 
62 ```math
63 \begin{pmatrix}
64 1 & 2 & 3 & 4\\
65 5 & 6 & 7 & 8\\
66 9 & 10 & 11 & 12\\
67 13 & 14 & 15 & 16
68 \end{pmatrix} ^ {1234567890123456789} \mod p
69 ```
70
71 This can easily be calculated in e.g. Python. The result is then used to transform the input in some manner to yield the flag. However, this can be translated straight from the decompiled assembly into Python code without thinking to much about what it is doing. Below is a script that correctly computes the flag using numpy and a rather fast matrix modular exponentiation algorithm taken from [Wikipedia](https://en.wikipedia.org/wiki/Modular_exponentiation#Generalizations). However, @georg implemented a simpler algorithm which was fast enough as well.
72
73 ```python
74 import numpy as np
75 import struct
76 m = np.matrix("1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16")
77 exponent = 1234567890123456789
78 prime = 0x96433d
79 flag_pt1 = [0xfc, 0x14, 0xeb, 0x09, 0xbc, 0xae, 0xe7, 0x47, 0x4f, 0xe3, 0x7c, 0xc1, 0x52, 0xa5, 0x02, 0x8e]
80 flag_pt2 = [0x89, 0x71, 0xc8, 0x8d, 0x96, 0x23, 0x01, 0x6d, 0x71, 0x40, 0x5a, 0xea, 0xfd, 0x46, 0x1d, 0x23]
81 flag = flag_pt1 + flag_pt2
82
83 # Matrix modular exponentiation algorithm taken from wikipedia
84 def mmulmod(a, b, c):
85     return np.mod(np.matmul(a, b), c)
86
87 def mexpm(a, b, c):
88     if b == 0:
89         return np.matrix("1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1")
90     if b % 2 == 1:
91         return mmulmod(a, mexpm(a, b-1, c), c)
92     d = mexpm(a, b//2, c)
93     return mmulmod(d, d, c)
94
95 # Matrix exponentiation and turn into list for transformation
96 pos = mexpm(m, exponent, prime).tolist()
97
98 # Translate the assembly code to python
99 pos_added = [i for s in pos for i in s]
100 key = [i for s in [list(struct.pack("<I", x)) for x in pos_added] for i in s]
101 rax = 0
102 while rax != 32:
103     flag[rax] = key[rax*2] ^ flag[rax]
104     flag[rax+1] = key[rax*2+1] ^ flag[rax+1]
105     rax = rax + 2
106
107 print("AOTW{" + "".join([chr(x) for x in flag]) + "}")
108 ```
109
110 The resulting flag is `AOTW{M4tr1x_3xp0n3nti4t1on_5728391723}`. 
111
112 # naughty list (day 12)
113 ## Retrospective
114 Rather guess-y web challenge and really strange setup overall. Therefore, it wasn't such a nice challenge but rather naughty ;) However, it was a nice team effort solving this annoying challenge.
115
116 **Time spent:** 6 hours
117
118 ## Overview
119 Given is a website with login/register capabilities. Upon registering and logging in one is greeted by some token counter that is set to `0/5000`. Thus, one needs to acquire `5000` tokens to get the flag. It's possible to send other people tokens by inputting some sort of encrypted string. However, only addresses (they change every refresh) for the user `santa` is visible and the own address is not. Furthermore, the page has some weird state management via URL parameters e.g. `http://3.93.128.89:1212/?page=404&from_page=kxcX1b-P5IhR0olPNWc9PbPAUr1tGB01Tig2sP6MkC8`.
120
121 ## Solution
122 ### Race condition
123 @superwayne found out that the token transfer has a race condition. Essentially, sending a token transfer simultaneously results in more tokens being sent than are available. However, without a way to send money to a chosen account this race condition is not that useful.
124
125 ### Getting the address for a user
126 As mentioned above, the page has some weird state behaviour. When one tries to open the page `http://3.93.128.89:1212/?page=wtf` it e.g. redirects to `http://3.93.128.89:1212/?page=404&from_page=kxcX1b-P5IhR0olPNWc9PbPAUr1tGB01Tig2sP6MkC8`. The `from_page` attribute is the previous page, i.e. `wtf`, indeterministically encrypted. If one enters a valid page, e.g. `http://3.93.128.89:1212/?page=contact` and then changes the `from_page` parameter to `page` the `contact` page is opened again. Thus, this constitutes and encryption oracle for arbitrary plaintext.
127
128 After lots of trial and error a human fuzzer (aka @lavish) encrypted the payload `lavish:ciaociao`. Trying to send tokens to the address resulted in `That user does not exist in our system.` After playing around with it a little bit the workings of the site became obvious. Any payload of the form `<random stuff>:<existing username>` generates an address for the specified username since the code probably splits at `:` and checks the second value. 
129
130 ### Putting it together
131 @lavish created a script to combine the two exploits. This resulted in an account getting `13` tokens from one account (note that 5000 are needed). However, registering new users was rate limited. The obvious idea is then to juggle tokens between two accounts using the race condition. I.e. the account with 13 tokens then uses the race condition to transfer 13 tokens as often as possible to the previous account and so forth. This generates enough tokens extremely quickly and resulted in the flag `AOTW{S4n7A_c4nT_hAv3_3lF-cOnTroL_wi7H0uT_eLf-d1sCipl1N3}`.
132
133 # snowflake idle (day 17)
134 ## Retrospective
135 Another web challenge where the guessing part was the most difficult. To be fair, the guessing part was way more obvious than in the previous challenge but we were too stupid to notice. Otherwise, the challenge was rather straightforward.
136
137 **Time spent:** 5 hours
138
139 ## Overview
140 On navigating to the page one is asked to chose a username. After choosing a name one is presented with a website which essentially is some cookie clicker like thing but instead of cookies one can generate snowflakes. After having collected a certain amount, one can upgrade the collection speed by 1  (similar to cookie clicker games). The amount of snowflakes over time is visualized as a graph and there exists some button to buy the flag after having reached a large amount of snowflakes. 
141
142 The immediately visible endpoints are `/control` for registering a user, `/client` for actions related to the game (e.g. getting snowflakes) and `/history/client` which is used by the history graph and returns the history as a json. The history is composed of every action that was `POST`ed to `/client`.
143
144 ## Solution
145 The snowflake gathering cannot be scripted efficiently as it is checked server-side and the amount of snowflakes cannot be underflowed as negative snowflake values are possible. Many people in MM and myself tried to find something for quite a long time until @TwentyOneCool used dirbuster (I believe) to find the endpoint `/history/control` (this was the guessing part which is obvious in retrospect. Similar to `/history/client` this endpoint returns a history of the `/control` endpoint. Therefore, we saw that the endpoint `/control` offered the capabilities to `load` and `save` a client state, i.e. the amount of snowflakes! Furthermore, the client state was displayed for the `save` command. By registering a really long username consisting of `a...a` we noticed that the encrypted pattern repeats. Therefore, the client data was encrypted using some sort of poly-alphabetic shift cipher. Using a script it is easy to extract the key:
146
147 ```python
148 import base64
149  
150 testblock = "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"
151
152 testblock2 = "gjzphWCyCG41GYdKlgi8awbm6FmdPL7KPPtRbmFY2wGEHrxrFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5YtvthAtbljXBcdF/SgU9+xdmH/li2+2EC1uWNcFx0X9KBT37F2Yf+WLb7YQLW5Y1wXHRf0oFPfsXZh/5Yssqg=="
153
154 def xor(b1, b2):
155     return [a ^ b for a, b in zip(b1, b2)]
156
157 def get_key(s):
158     s = list(base64.b64decode(s))
159     key = xor([ord("a")]*60, s[36:96]) # repetition starts at index 36
160     return key[-16:] + key[:-16] # the last 16 bytes are actually the start of the key
161
162 def main():
163     k = get_key(testblock)
164     print(k)
165     print("".join([chr(x) for x in xor(list(base64.b64decode(testblock))[:60], k)]))
166     print("".join([chr(x) for x in xor(list(base64.b64decode(testblock2))[:60], k)]))
167
168 if __name__ == "__main__":
169     main()
170
171 ```
172
173 Running the script outputs the following:
174 ```
175 [249, 30, 132, 234, 14, 215, 113, 76, 15, 57, 182, 100, 166, 36, 156, 73, 117, 150, 141, 60, 249, 30, 132, 234, 14, 215, 113, 76, 15, 57, 182, 100, 166, 36, 156, 73, 117, 150, 141, 60, 249, 30, 132, 234, 14, 215, 113, 76, 15, 57, 182, 100, 166, 36, 156, 73, 117, 150, 141, 60]
176 {"money": 5.0, "speed": 1, "name": "aaaaaaaaaaaaaaaaaaaaaaaa
177 {"money": 1.0, "speed": 2, "name": "aaaaaaaaaaaaaaaaaaaaaaaa
178 ```
179 The first array is the key in decimal notation, the second is the beginning of the state which the key was derived from and the third line is the beginning of an earlier game state. Clearly, the decryption key stays the same and thus it is very easy to encrypt a custom json object with the required amount of money. Sadly, I can't try it anymore since the service is down but teammates solved the challenge without having the full key (which is a bit more cumbersome).
180
181 After getting enough money one can buy the flag `AOTW{leaKinG_3ndp0int5}`.