]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/icecrax/asis2019.md
Add personal writeup for SECCON.
[pub/jan/ctf-seminar.git] / writeups / icecrax / asis2019.md
1 # ASIS 2019
2 ## web: protected-area (unsolved)
3 A web page is given which printed the content of a file from a directory to the html of the page.
4
5 First I looked up the source file of the webpage. It was importing the following JS script:
6 ``` javascript   
7     var file_check = function(file){
8         $.ajax({
9             url: '/check_perm/readable/',
10             data: {'file': file}
11         }).done(function(data){
12             if (data == "True") {
13                 file_read(file)
14             }else{
15                 console.log('fail')
16             }
17         })
18     }
19
20     var file_read = function(file){
21         $.ajax({
22             url: '/read_file/',
23             data: {'file': file}
24         }).done(function(data){
25             update_page(data)
26         })
27         return
28     }
29
30     var update_page = function(text){
31         $("#t").append(text)
32     }
33
34     $(document).ready(function() {
35         console.log("ready!");
36         file_check('public.txt');
37     });
38 ```
39 The first command `file_check` executed like this `http://66.172.33.148:8008/check_perm/readable/?file=public.txt` gives either `True` or `0`, depending if the file exists. If the file is not found it prints in the console `fail`. If it exists its being passed to the command `file_read`.
40
41 The second command `file_read` executed like this `http://66.172.33.148:8008/read_file/?file=public.txt` opens the file. It then passes itas an argument to `update_page`. If the file is not found `security` or `500` is being printed.
42
43 The trird command `update_page` takes the passed text and appends to the DOM.
44
45 I tried path triversal by encoding the ../ in UTF-8, by replacing ../ with ..././ or ..;/, but it seems the dots and slash are still being replace, bacause `http://66.172.33.148:8008/read_file/?file=../../../public.txt` is being interpreted the same as `http://66.172.33.148:8008/read_file/?file=public.txt`
46
47 There was also `private.txt` file in the same directory as `public.txt` with the content `lol haha`.
48
49 I tried to mess up with the console:
50 `update_page("<script>alert()</script>")` executes the script and the pop up shows, hints towards an XSS attack.
51
52 I tried looking for a way to list the directory via javascript
53 ``` javascript
54 const testFolder = './public.txt';
55 const fs = require('fs');
56
57 fs.readdir(testFolder, (err, files) => {
58   files.forEach(file => {
59     console.log(file);
60   });
61 });
62 ```
63
64 ## web: cursed-app (unsolved)
65
66 An `cursed_app.elf` file is given.
67 The description of the challenge stated `If we want to say 100, should we start at 1 and count until 100?` and `sha256(flag) = dd791346264fc02ddcd2466b9e8b2b2bba8057d61fbbc824d25c7957c127250d` was given.
68
69 First I tried to brute force the hash using variuos websites. No luck there.
70 Hexdumping the file reveals some readable strings: `hexdump -C cursed_app.elf`. 
71 After that I found a more useful command `strings`, which filters only the readable strings.
72 Intressting segment was 
73 ```
74 []A\A]A^A_
75 please locate license file, run ./app license_key
76 Congratz! You got the correct flag!!
77 Bummer! You got the wrong flag!!
78 ;*3$"
79 ```
80 So I tried executing file. If it's being run without arguments the first message is printed: `please locate license file, run ./app license_key`, if a file is specified it prited `Bummer! You got the wrong flag!!`.
81
82 So clearly the program was reading the flag file and computing and comparing the string.
83
84 I tried `strace` and `ltrace` to understand better what functions and libraries are being called. 
85
86 Using `objdump -d cursed_app.elf` showed me that there was no main function.
87
88 I then installed `Hopper`, which is a reverse engineering tool that lets me disassemble, decompile and debug the applications. With it I tried to undestand the execution pattern and to find out how and when `Congratz!` is being printed. I figured out that after opening the flag/key file 59 characters are being read and most likely compared (although no cmp function was found). Those 59 chars could be brute forced and encrypted with `sha256` and then compared with the given solution in the description. A brute force of 59 chars seems impossible.
89
90 ## web: Golden_Delicios (unsolved)
91
92  A `.sage` file is given, containing the following python scrypt. And at the bottom of it, there were the results of the print function commented.
93
94  ``` python
95  #!/usr/local/bin/sage
96
97 from sage.all import *
98 from secret import *
99
100 print '(A_1, B_1) =', (A_1, B_1)
101 print '(A_2, B_2) =', (A_2, B_2)
102
103 E_1 = EllipticCurve(Zmod(n_1), [A_1, B_1])
104 E_2 = EllipticCurve(Zmod(n_2), [A_2, B_2])
105
106 d_1 = int(flag[:len(flag)/2].encode('hex'), 16)
107 d_2 = int(flag[len(flag)/2:].encode('hex'), 16)
108
109 assert d_1 < n_1 and d_2 < n_2 and n_1 * n_2 % 2 == 1
110
111 print 'P_1 =', P_1
112 print 'P_2 =', P_2
113
114 Q_1 = d_1 * P_1
115 Q_2 = d_2 * P_2
116
117 print 'Q_1 =', Q_1
118 print 'Q_2 =', Q_2
119  ```
120
121 The EllipticCurve function was interesting. After googling it I found out that there exists a very popular algorithm in cryptography for encrypting public keys.
122 The formula for an elliptic curve is as follows: `y^2=x^3+ax+b` and the parameters corresponded to those used in the script, so I guessed I was on the right track.
123 After that I spent time reading wikipedia articles and watching youtube youtibe videos explaining it. But after some time trying to understand it, I gave up... I decided to concetrate more on the other challenges.
124
125 ## web: Bit_game (unsolved)
126 The following script was given, together with a text file containing the resulting H and enc variables:
127 ```python
128 import random
129 from Crypto.Util.number import *
130 #from flag import flag
131
132 def gen_rand(nbit, l):
133     R = []
134     while True:
135         r = random.randint(0, nbit-1)
136         if r not in R:
137             R.append(r)
138             if len(R) == l:
139                 break
140     R.sort()
141     rbit = '1'
142     for i in range(l-1):
143         rbit += (R[i+1] - R[i] - 1) * '0' + '1'
144     rbit += (nbit - R[-1] - 1) * '0'
145     return int(rbit, 2)
146
147 def genkey(p, l):
148     n = len(bin(p)[2:])
149     f, skey = gen_rand(n, l), gen_rand(n, l)
150     pkey = f * inverse(skey, p) % p 
151     return (p, pkey), skey
152
153 def encrypt(msg, pkey):
154     p, g = pkey
155     msg, enc, n = bytes_to_long(msg), [], len(bin(p)[2:])
156     print(n)
157     for b in bin(msg)[2:]:
158         s, t = gen_rand(n, l), gen_rand(n, l)
159         c = (s * g + t) % p
160         if b == '0':
161             enc.append((c, t))
162         else:
163             enc.append((p-c, t))
164     return enc
165     
166 p = 862718293348820473429344482784628181556388621521298319395315527974911
167 l = 5
168
169 pkey, skey = genkey(p, l)
170 enc = encrypt(flag, pkey)
171 H = pkey[1] ** 2 % p
172 print('H =', H)
173 print('enc =', enc)
174 ```
175
176 I think I was on the right track on this one. Sadly this challenge was released on the last day and I didn't get the opportunity to solve it during the CTF. The CTF was over and the challenge was left unsolved. I had an Idea how to begin, but applying what I found didn't produce meaningful output. I continued working on it afterwards, since I spent the most time on it and I wanted to finish it.
177 Nevertheless this is what I managed to find out during the CTF:
178 Note: A lot of time the was spent reading articles, researching and understanding the underlying theorems and math applied.  
179
180     1) Numbers produced by the 'gen_rand()' function are not completely random. Their binary representation always have five '1's.
181     
182     2) Equation of the form x^2 ≡ q (mod n) are called "Quadratic residues".  
183
184     3) Reversing H = pkey[1] ** 2 % p is a hard problem if 'p' was a prime, but since it is not, we could split it into factors and try to find solutions for all the individual factors pi: pkey^2 ≡ H (mod pi).
185
186     4) Legendre symbol or Euler’s Criterion can be used to check if a number is a quadratic residue.
187
188     5) The solutions from the individual factors can be combined via the "Chinese remainder theorem" to find a solution to the original problem, meaning we will find the public key.
189
190     6) For finding solutions to the individual problems there are two approaches:
191         - If `'a'` is a quadratic residue (mod p) and `p ≡ 3 (mod 4)` then `a^((p+1)/4)` is a solution to `x^2 ≡ a (mod p)`. 
192         - If `a ≡ 1 (mod 4)` there is no analogous formula. In that case, one may use the "Tonelli-Shanks algorithm".
193
194 PS: I posted a detailed solution for this challenge as well.