Ofensive 100 - CTF Trend Micro Online Qualifier 2016

Find the flag.
https://www.mediafire.com/?3x3m94598518054

openssl enc -d -aes-256-cbc -k x0nSTZ9NrDgvCnqKhL9y -in files1.enc -out files1.zip
unzip files1.zip

After unzipping the file I found strange it was a hta file extension which I never heard off, but after analyzing the code it was just some html and javascript.

Some of the JavaScript code was minimized so I just used http://jsbeautifier.org/ to make it readable for humans.

The first thing you can see in the code are these three interesting hashes:

1
2
3
4
5
6
7

window.host=FnBJT9OVUieRCjeTgMPMBe4U.hs;
var m = window.host;
var nl = [0,2,1,12,7,15,5,4,8,16,17,3,9,10,14,11,13,6,0];
var ko="c33367701511b4f6020ec61ded352059";
var ka="61636f697b57b5b7d389db0edb801fc3";
var kq="d2172edf24129e06f3913376a12919a4";

The first one is actually very easy to crack I used john the ripper but you could find it even easier by searching it on google you can immediately find it’s a md5 hash by analysing the FnBJT9OVUieRCjeTgMPMBe4U.hs which is a known js code for md5 encryption.

1
2
3
4
5
6

kinyabitch@Debian ~/h/p/j/run> ./john --wordlist=password.lst  pass.txt  --format=raw-md5
Loaded 1 password hash (Raw MD5 [128/128 SSE2 intrinsics 12x])
654321           (?)
guesses: 1  time: 0:00:00:00 DONE (Sun Jul 31 13:09:53 2016)  c/s: 960  trying: 1q2w3e - blazer
Use the "--show" option to display all of the cracked passwords reliably

After cracking it with john you find the hash c33367701511b4f6020ec61ded352059 was actually ‘654321’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

function checkPW(pass) {
if (pass != null && pass == "close") {
window.close();
};
if (pass == null || pass.length != 24) {
alert("Wrong password");
return;
};
if (pass.substring(0, 6) != "TMCTF{" || pass.substr(pass.length - 1) != "}") {
alert("Wrong password");
return;
};
var pwbody = (" " + pass.substring(6, pass.length - 1)).split("");
var h1 = "",
h2 = "",
h3 = "";
for (var i = 0; i < pwbody.length;) {
h1 += pwbody[nl[++i]];
h2 += pwbody[nl[++i]];
h3 += pwbody[nl[++i]];
};
if (co(m(h1.replace(/(^\s+)|(\s+$)/g, ""))) && ca(m(h3.replace(/(^\s+)|(\s+$)/g, ""))) && cq(m(h2.replace(/(^\s+)|(\s+$)/g, "")))) {
alert("ok!");
window.close();
} else {
alert("Wrong password");
return;
};
}

function co(o) {
return (o === ko);
}

function ca(a) {
return (a === ka.replace(/6/g, '2').replace(/b/g, 'a').replace(/d/g, '4'));
}

function cq(q) {
return (q === kq.replace(/1/g, '5').replace(/2/g, '8').replace(/3/g, 'b').replace(/9/g, 'c'));
}

Analyzing this function was the key to this challenge with this code you can find out the flag has 18 characters always having this format “TMCTF{pass}” otherwise will be always wrong.

1
2
3
4
5
6
7

if (pass.substring(0, 6) != "TMCTF{" || pass.substr(pass.length - 1) != "}") {
alert("Wrong password");
return;
};

var pwbody = (" " + pass.substring(6, pass.length - 1)).split("");

After passing this first length checks you will notice that this for loop is actually mixing from different indexes splited into 3 strings (h1, h2, h3).

1
2
3
4
5
6

for (var i = 0; i < pwbody.length;) {
h1 += pwbody[nl[++i]];
h2 += pwbody[nl[++i]];
h3 += pwbody[nl[++i]];
};

Then you will see somethig that is a little confusing 3 boolean conditions (co, ca and cq),the first regex expression replaces characters like spaces you will find the function in “m” that is actually the hash function into md5:

1
2

if (co(m(h1.replace(/(^\s+)|(\s+$)/g, ""))) && ca(m(h3.replace(/(^\s+)|(\s+$)/g, ""))) && cq(m(h2.replace(/(^\s+)|(\s+$)/g, "")))) {...}

And now after looking to this 3 functions you will realize why you couldn’t crack the other two, because some of its characters are being replaced by the right ones making the hash much easier to crack (you can copy the entire js code (browser JS console) and just run the replace code in ka and kq string).

1
2
3
4
5
6
7
8
9
10
11
12

function co(o) {
return (o === ko);
}

function ca(a) {
return (a === ka.replace(/6/g, '2').replace(/b/g, 'a').replace(/d/g, '4'));
}

function cq(q) {
return (q === kq.replace(/1/g, '5').replace(/2/g, '8').replace(/3/g, 'b').replace(/9/g, 'c'));
}
1
2
3
4
5
6
7
8

> "61636f697b57b5b7d389db0edb801fc3".replace(/6/g, '2').replace(/b/g, 'a').replace(/d/g, '4');

21232f297a57a5a743894a0e4a801fc3

> "d2172edf24129e06f3913376a12919a4".replace(/1/g, '5').replace(/2/g, '8').replace(/3/g, 'b').replace(/9/g, 'c');

d8578edf8458ce06fbc5bb76a58c5ca4

A now cracking it on john you find out they ‘qwerty’ and ‘admin’.

1
2
3
4
5
6

kinyabitch@Debian ~/h/p/j/run> ./john --wordlist=password.lst  pass2.txt  --format=raw-md5
Loaded 3 password hashes with no different salts (Raw MD5 [128/128 SSE2 intrinsics 12x])
Remaining 2 password hashes with no different salts
qwerty           (?)
admin            (?)

After finding this 3 hashes you can finally realize you can reconstruct the password basing on that for loop, I wrote a python script to make it easier to change (doing manually would take a lot of time and I could make mistakes):

http://pastebin.com/embed_iframe/phYNZi4A
After running it you will find the flag:

1
2
3

kinyabitch@Debian ~/h/c/ofensive> python crack.py
TMCTF{q6r4dy5ei2na1twm3}