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