Skip to content

Commit b063979

Browse files
committed
add alternative solution for genius
1 parent fbfb7e5 commit b063979

File tree

1 file changed

+369
-0
lines changed

1 file changed

+369
-0
lines changed
Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
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+
![Screenshot 1](https://raw.githubusercontent.com/ValarDragon/CTF-Writeups/master/2017/EasyCTF/Genius/Site-1.png)
9+
![Screenshot 2](https://raw.githubusercontent.com/ValarDragon/CTF-Writeups/master/2017/EasyCTF/Genius/Site-2.png)
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

Comments
 (0)