@@ -52,6 +52,370 @@ OMG_it_took_like_LITerally_s0oO00_long_2_MAK3_md5_werrk_you_have_no_id34
52
52
Putting that into the website's prompt gives a popup with the flag:
53
53
` easyctf{OUR_3nCRYpti0n_is_N0T_br0k3n_Ur_brok3n_6c5a390d} `
54
54
55
+ ### Alternate Solution
56
+ ###### Writeup by rofl0r
57
+ Here's the site:
58
+ ![ Screenshot 1] ( https://raw.githubusercontent.com/ValarDragon/CTF-Writeups/master/2017/EasyCTF/Genius/Site-1.png )
59
+ ![ Screenshot 2] ( https://raw.githubusercontent.com/ValarDragon/CTF-Writeups/master/2017/EasyCTF/Genius/Site-2.png )
60
+
61
+ the hash function was available as embedded javascript snippet in the page.
62
+ unrolled version:
63
+
64
+ ``` js
65
+ var super_secret_hash = function (s ){
66
+ function L (k ,d ){return (k<< d)| (k>>> (32 - d))}
67
+ function K (G ,k ){
68
+ var I ,d,F ,H ,x;
69
+ F = (G & 2147483648 );
70
+ H = (k& 2147483648 );
71
+ I = (G & 1073741824 );
72
+ d= (k& 1073741824 );
73
+ x= (G & 1073741823 )+ (k& 1073741823 );
74
+ if (I & d){
75
+ return (x^ 2147483648 ^ F ^ H )
76
+ }
77
+ if (I | d){
78
+ if (x& 1073741824 ){
79
+ return (x^ 3221225472 ^ F ^ H )
80
+ } else {
81
+ return (x^ 1073741824 ^ F ^ H )
82
+ }
83
+ } else {
84
+ return (x^ F ^ H )
85
+ }
86
+ }
87
+ function r (d ,F ,k ){
88
+ return (d& F )| ((~ d)& k)
89
+ }
90
+ function q (d ,F ,k ){
91
+ return (d& k)| (F & (~ k))
92
+ }
93
+ function p (d ,F ,k ){
94
+ return (d^ F ^ k)
95
+ }
96
+ function n (d ,F ,k ){
97
+ return (F ^ (d| (~ k)))
98
+ }
99
+ function u (G ,F ,aa ,Z ,k ,H ,I ){
100
+ G = K (G ,K (K (r (F ,aa,Z ),k),I ));
101
+ return K (L (G ,H ),F )
102
+ }
103
+ function f (G ,F ,aa ,Z ,k ,H ,I ){
104
+ G = K (G ,K (K (q (F ,aa,Z ),k),I ));
105
+ return K (L (G ,H ),F )
106
+ }
107
+ function D (G ,F ,aa ,Z ,k ,H ,I ){
108
+ G = K (G ,K (K (p (F ,aa,Z ),k),I ));
109
+ return K (L (G ,H ),F )
110
+ }
111
+ function t (G ,F ,aa ,Z ,k ,H ,I ){
112
+ G = K (G ,K (K (n (F ,aa,Z ),k),I ));
113
+ return K (L (G ,H ),F )
114
+ }
115
+ function e (G ){
116
+ var Z ;
117
+ var F = G .length ;
118
+ var x= F + 8 ;
119
+ var k= (x- (x% 64 ))/ 64 ;
120
+ var I = (k+ 1 )* 16 ;
121
+ var aa= Array (I - 1 );
122
+ var d= 0 ;
123
+ var H = 0 ;
124
+ while (H < F ){
125
+ Z = (H - (H % 4 ))/ 4 ;
126
+ d= (H % 4 )* 8 ;
127
+ aa[Z ]= (aa[Z ]| (G .charCodeAt (H )<< d));
128
+ H ++
129
+ }
130
+ Z = (H - (H % 4 ))/ 4 ;
131
+ d= (H % 4 )* 8 ;
132
+ aa[Z ]= aa[Z ]| (128 << d);
133
+ aa[I - 2 ]= F << 3 ;
134
+ aa[I - 1 ]= F >>> 29 ;
135
+ return aa
136
+ }
137
+ function B (x ){
138
+ var k= " " ,F = " " ,G ,d;
139
+ for (d= 0 ;d<= 3 ;d++ ){
140
+ G = (x>>> (d* 8 ))& 255 ;
141
+ F = " 0" + G .toString (16 );
142
+ k= k+ F .substr (F .length - 2 ,2 )
143
+ }
144
+ return k
145
+ }
146
+ function J (k ){
147
+ k= k .replace (/ rn/ g ," n" );
148
+ var d= " " ;
149
+ for (var F = 0 ;F < k .length ;F ++ ){
150
+ var x= k .charCodeAt (F );
151
+ if (x< 128 ){
152
+ d+= String .fromCharCode (x)
153
+ } else {
154
+ if ((x> 127 )&& (x< 2048 )){
155
+ d+= String .fromCharCode ((x>> 6 )| 192 );
156
+ d+= String .fromCharCode ((x& 63 )| 128 )
157
+ } else {
158
+ d+= String .fromCharCode ((x>> 12 )| 224 );
159
+ d+= String .fromCharCode (((x>> 6 )& 63 )| 128 );
160
+ d+= String .fromCharCode ((x& 63 )| 128 )
161
+ }
162
+ }
163
+ }
164
+ return d
165
+ }
166
+ var C = Array ();
167
+ var P ,h,E ,v,g,Y ,X ,W ,V ;
168
+ var S = 7 ,Q = 12 ,N = 17 ,M = 22 ;
169
+ var A = 5 ,z= 9 ,y= 14 ,w= 20 ;
170
+ var o= 4 ,m= 11 ,l= 16 ,j= 23 ;
171
+ var U = 6 ,T = 10 ,R = 15 ,O = 21 ;
172
+ s= J (s);
173
+ C = e (s);
174
+ Y = 1732584193 ;
175
+ X = 4023233417 ;
176
+ W = 2562383102 ;
177
+ V = 271733878 ;
178
+ for (P = 0 ;P < C .length ;P += 16 ){
179
+ h= Y ;
180
+ E = X ;
181
+ v= W ;
182
+ g= V ;
183
+ Y = u (Y ,X ,W ,V ,C [P + 0 ],S ,3614090360 );
184
+ V = u (V ,Y ,X ,W ,C [P + 1 ],Q ,3905402710 );
185
+ W = u (W ,V ,Y ,X ,C [P + 2 ],N ,606105819 );
186
+ X = u (X ,W ,V ,Y ,C [P + 3 ],M ,3250441966 );
187
+ Y = u (Y ,X ,W ,V ,C [P + 4 ],S ,4118548399 );
188
+ V = u (V ,Y ,X ,W ,C [P + 5 ],Q ,1200080426 );
189
+ W = u (W ,V ,Y ,X ,C [P + 6 ],N ,2821735955 );
190
+ X = u (X ,W ,V ,Y ,C [P + 7 ],M ,4249261313 );
191
+ Y = u (Y ,X ,W ,V ,C [P + 8 ],S ,1770035416 );
192
+ V = u (V ,Y ,X ,W ,C [P + 9 ],Q ,2336552879 );
193
+ W = u (W ,V ,Y ,X ,C [P + 10 ],N ,4294925233 );
194
+ X = u (X ,W ,V ,Y ,C [P + 11 ],M ,2304563134 );
195
+ Y = u (Y ,X ,W ,V ,C [P + 12 ],S ,1804603682 );
196
+ V = u (V ,Y ,X ,W ,C [P + 13 ],Q ,4254626195 );
197
+ W = u (W ,V ,Y ,X ,C [P + 14 ],N ,2792965006 );
198
+ X = u (X ,W ,V ,Y ,C [P + 15 ],M ,1236535329 );
199
+ Y = f (Y ,X ,W ,V ,C [P + 1 ],A ,4129170786 );
200
+ V = f (V ,Y ,X ,W ,C [P + 6 ],z,3225465664 );
201
+ W = f (W ,V ,Y ,X ,C [P + 11 ],y,643717713 );
202
+ X = f (X ,W ,V ,Y ,C [P + 0 ],w,3921069994 );
203
+ Y = f (Y ,X ,W ,V ,C [P + 5 ],A ,3593408605 );
204
+ V = f (V ,Y ,X ,W ,C [P + 10 ],z,38016083 );
205
+ W = f (W ,V ,Y ,X ,C [P + 15 ],y,3634488961 );
206
+ X = f (X ,W ,V ,Y ,C [P + 4 ],w,3889429448 );
207
+ Y = f (Y ,X ,W ,V ,C [P + 9 ],A ,568446438 );
208
+ V = f (V ,Y ,X ,W ,C [P + 14 ],z,3275163606 );
209
+ W = f (W ,V ,Y ,X ,C [P + 3 ],y,4107603335 );
210
+ X = f (X ,W ,V ,Y ,C [P + 8 ],w,1163531501 );
211
+ Y = f (Y ,X ,W ,V ,C [P + 13 ],A ,2850285829 );
212
+ V = f (V ,Y ,X ,W ,C [P + 2 ],z,4243563512 );
213
+ W = f (W ,V ,Y ,X ,C [P + 7 ],y,1735328473 );
214
+ X = f (X ,W ,V ,Y ,C [P + 12 ],w,2368359562 );
215
+ Y = D (Y ,X ,W ,V ,C [P + 5 ],o,4294588738 );
216
+ V = D (V ,Y ,X ,W ,C [P + 8 ],m,2272392833 );
217
+ W = D (W ,V ,Y ,X ,C [P + 11 ],l,1839030562 );
218
+ X = D (X ,W ,V ,Y ,C [P + 14 ],j,4259657740 );
219
+ Y = D (Y ,X ,W ,V ,C [P + 1 ],o,2763975236 );
220
+ V = D (V ,Y ,X ,W ,C [P + 4 ],m,1272893353 );
221
+ W = D (W ,V ,Y ,X ,C [P + 7 ],l,4139469664 );
222
+ X = D (X ,W ,V ,Y ,C [P + 10 ],j,3200236656 );
223
+ Y = D (Y ,X ,W ,V ,C [P + 13 ],o,681279174 );
224
+ V = D (V ,Y ,X ,W ,C [P + 0 ],m,3936430074 );
225
+ W = D (W ,V ,Y ,X ,C [P + 3 ],l,3572445317 );
226
+ X = D (X ,W ,V ,Y ,C [P + 6 ],j,76029189 );
227
+ Y = D (Y ,X ,W ,V ,C [P + 9 ],o,3654602809 );
228
+ V = D (V ,Y ,X ,W ,C [P + 12 ],m,3873151461 );
229
+ W = D (W ,V ,Y ,X ,C [P + 15 ],l,530742520 );
230
+ X = D (X ,W ,V ,Y ,C [P + 2 ],j,3299628645 );
231
+ Y = t (Y ,X ,W ,V ,C [P + 0 ],U ,4096336452 );
232
+ V = t (V ,Y ,X ,W ,C [P + 7 ],T ,1126891415 );
233
+ W = t (W ,V ,Y ,X ,C [P + 14 ],R ,2878612391 );
234
+ X = t (X ,W ,V ,Y ,C [P + 5 ],O ,4237533241 );
235
+ Y = t (Y ,X ,W ,V ,C [P + 12 ],U ,1700485571 );
236
+ V = t (V ,Y ,X ,W ,C [P + 3 ],T ,2399980690 );
237
+ W = t (W ,V ,Y ,X ,C [P + 10 ],R ,4293915773 );
238
+ X = t (X ,W ,V ,Y ,C [P + 1 ],O ,2240044497 );
239
+ Y = t (Y ,X ,W ,V ,C [P + 8 ],U ,1873313359 );
240
+ V = t (V ,Y ,X ,W ,C [P + 15 ],T ,4264355552 );
241
+ W = t (W ,V ,Y ,X ,C [P + 6 ],R ,2734768916 );
242
+ X = t (X ,W ,V ,Y ,C [P + 13 ],O ,1309151649 );
243
+ Y = t (Y ,X ,W ,V ,C [P + 4 ],U ,4149444226 );
244
+ V = t (V ,Y ,X ,W ,C [P + 11 ],T ,3174756917 );
245
+ W = t (W ,V ,Y ,X ,C [P + 2 ],R ,718787259 );
246
+ X = t (X ,W ,V ,Y ,C [P + 9 ],O ,3951481745 );
247
+ Y = K (Y ,h);
248
+ X = K (X ,E );
249
+ W = K (W ,v);
250
+ V = K (V ,g)
251
+ }
252
+ var i= B (Y )+ B (X )+ B (W )+ B (V );
253
+ return i .toLowerCase ()
254
+ }
255
+ ;
256
+
257
+ function hash (word ) {
258
+ var out = " " ;
259
+ for (var i = 0 ; i < word .length ; i += 4 ) {
260
+ out += super_secret_hash (word .substring (i, i + 4 ));
261
+ }
262
+ return out;
263
+ }
264
+
265
+ ```
266
+ what immediately hit my eye is this statement:
267
+ ``` js
268
+ for (var i = 0 ; i < word .length ; i += 4 ) {
269
+ out += super_secret_hash (word .substring (i, i + 4 ));
270
+ ` ` `
271
+ that makes it clear that the final hash is just a concatenation of hashes from the input string in chunks of 4 bytes.
272
+ so instead of analyzing the hash function, i just immediately started looking if it is feasible to bruteforce the hash.
273
+
274
+ for testing, i added the following code to generate a hash from a word passed in as a commandline argument:
275
+ ` ` ` js
276
+ word = process .argv [2 ];
277
+ console .log (hash (word));
278
+ ` ` `
279
+ and created the hash of the word "flag".
280
+ ` ` `
281
+ $ node genius- sum .js flag
282
+ 327a6c4304ad5938eaf0efb6cc3e53dc
283
+ ` ` `
284
+ to generate the candidates for hashing, i used my battle-proofed bruteforce password generator (i call it brutus):
285
+ ` ` ` c
286
+ #include < stdio .h >
287
+ #include < string .h >
288
+ #include < stdlib .h >
289
+
290
+ struct bruteforcer {
291
+ unsigned long long step;
292
+ char* out;
293
+ size_t out_len;
294
+ const char *alpha ;
295
+ size_t alpha_len;
296
+ unsigned * pows;
297
+ };
298
+
299
+ static unsigned long upow (unsigned base , unsigned exp ) {
300
+ unsigned long res = 1 , i= 0 ;
301
+ while (i++ < exp) res = res * base;
302
+ return res;
303
+ }
304
+
305
+ static unsigned calc_pow (unsigned position , size_t maxlen , size_t alpha_len ) {
306
+ unsigned r = (maxlen - position) - 1 ;
307
+ return upow (alpha_len, r);
308
+ }
309
+
310
+ /* init bruteforcer state. out_len needs to be *not the buffersize*, but the size of characters in out!
311
+ that means that you need to put the zero-terminator manually before initialising the state */
312
+ static void bruteforcer_init (struct bruteforcer * bf , char * out , size_t out_len , const char * alpha , size_t alpha_len ) {
313
+ *bf = (struct bruteforcer) {.step = - 1ULL , .out = out, .out_len = out_len, .alpha = alpha, .alpha_len = alpha_len};
314
+ bf - > pows = malloc (sizeof * (bf- > pows) * out_len);
315
+ unsigned i ;
316
+ for (i= 0 ; i< out_len; i++ ) bf - > pows [i ] = calc_pow (i, out_len, alpha_len);
317
+ }
318
+
319
+ static unsigned powstep (unsigned long long step , unsigned long long pwr , size_t alpha_len , int * termflag ) {
320
+ unsigned long long d = step / pwr;
321
+ if (d < alpha_len) return d;
322
+ else * termflag = 1 ;
323
+ return 0 ;
324
+ }
325
+
326
+ static int bruteforcer_step (struct bruteforcer * b ) {
327
+ b- > step++ ;
328
+ unsigned long long mystep = b- > step;
329
+ size_t i;
330
+ int termflag = 0 ;
331
+ for (i= 0 ;i < b- > out_len; i++ ) {
332
+ unsigned pwr = b- > pows[i];
333
+ unsigned x = powstep (mystep, pwr, b- > alpha_len, & termflag);
334
+ if (i == 0 && termflag) return 0 ;
335
+ if (x) mystep -= (pwr * x);
336
+ b- > out[i] = b- > alpha[ x ];
337
+ }
338
+ return 1 ;
339
+ }
340
+
341
+ static int usage (char * argv0 ) {
342
+ printf (
343
+ " brutus 1.0 by rofl0r\n "
344
+ " usage: %s alphabet length\n "
345
+ " for example:\n "
346
+ " %s abcdef 4\n " , argv0, argv0);
347
+ return 1 ;
348
+ }
349
+
350
+ int main (int argc , char ** argv ) {
351
+ if (argc != 3 ) return usage (argv[0 ]);
352
+ const char *alpha = argv[1 ];
353
+ struct bruteforcer b;
354
+ int candidatelen = atoi (argv[2 ]);
355
+ char* candidate = malloc (candidatelen+ 1 );
356
+ candidate[candidatelen] = 0 ;
357
+ bruteforcer_init (& b, candidate, candidatelen, alpha, strlen (alpha));
358
+ while (bruteforcer_step (& b)) puts (candidate);
359
+ return 0 ;
360
+ }
361
+ ` ` `
362
+ Then i created a new js file with the hash function and the following snippet:
363
+ the idea is to pass the hash as command line argument (` want ` ), and the candidates to hash and compare via stdin.
364
+ ` ` ` js
365
+ want = process .argv [2 ];
366
+
367
+ // that's a generic codeblock to be able to read from stdin.
368
+ var readline = require (' readline' );
369
+ var rl = readline .createInterface ({
370
+ input: process .stdin ,
371
+ output: process .stdout ,
372
+ terminal: false
373
+ });
374
+
375
+ rl .on (' line' , function (line ){
376
+ if (hash (line) === want) {
377
+ console .log (line);
378
+ process .exit (0 );
379
+ }
380
+ })
381
+ ` ` `
382
+
383
+ For the first try i used just a lowercase alphabet against the hash of "flag" to see if the attack is feasible (i.e. fast enough):
384
+
385
+ ` ` `
386
+ time ./ brutus abcdefghijklmnopqrstuvwxyz 4 | node genius .js 327a6c4304ad5938eaf0efb6cc3e53dc
387
+ flag
388
+
389
+ real 0m1 .087s
390
+ user 0m1 .095s
391
+ sys 0m0 .008s
392
+ ` ` `
393
+ very promising!
394
+
395
+ in order to use all 8 available cores, i resorted to my tool [jobflow](https://github.com/rofl0r/jobflow).
396
+ it's similar to GNU parallel, but it's much slimmer and faster since it's written in C.
397
+ and it uses much less memory. it's also by default installed on my distro [sabotage linux](http://github.com/sabotage-linux/sabotage).
398
+ the concept is to evenly distribute the input from brutus to 8 node.js processes running the "password cracker".
399
+ i then went to the guts and started cracking the actual hashes of the competition (in 32 byte chunks).
400
+
401
+ ` ` `
402
+ time ./ brutus _ - 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 4 | jobflow - j = 8 - exec node genius .js 8a7fca9234d2f19c8abfcd812971a26c
403
+ OMG_
404
+
405
+ real 0m24 .328s
406
+ user 0m1 .145s
407
+ sys 0m1 .535s
408
+
409
+ ` ` `
410
+ success! the first part of the input string is ` OMG_ ` .
411
+ since the lowercase alphabet didn't cut it, i expanded the alphabet for brutus to include uppercase, lowercase, plus some special chars.
412
+
413
+ i repeated the process with the other hash chunks, and got the final "password":
414
+ ` OMG_it_took_like_LITerally_s0oO00_long_2_MAK3_md5_werrk_you_have_no_id34 `
415
+
416
+ Putting that into the website's prompt gives a popup with the flag:
417
+ ` easyctf {OUR_3nCRYpti0n_is_N0T_br0k3n_Ur_brok3n_6c5a390d}`
418
+
55
419
### External Writeups
56
420
57
421
* [https://github.com/HackThisCode/CTF-Writeups/blob/master/2017/EasyCTF/Genius/README.md](https://github.com/HackThisCode/CTF-Writeups/blob/master/2017/EasyCTF/Genius/README.md)
0 commit comments