Skip to content

Commit b7eee04

Browse files
committed
Combine into one file.
1 parent 56afd52 commit b7eee04

File tree

2 files changed

+364
-369
lines changed

2 files changed

+364
-369
lines changed

cryptography/genius-230-points.md

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,370 @@ OMG_it_took_like_LITerally_s0oO00_long_2_MAK3_md5_werrk_you_have_no_id34
5252
Putting that into the website's prompt gives a popup with the flag:
5353
`easyctf{OUR_3nCRYpti0n_is_N0T_br0k3n_Ur_brok3n_6c5a390d}`
5454

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+
55419
### External Writeups
56420
57421
* [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

Comments
 (0)