blog.dornea.nu

Hack, code and drink some țuică. Personal blog of Victor Dorneanu.

ringzer0 CTF - JavaScript challenges

These challenges were quite tricky since they didn't focus only on the JavaScript language itself but also on
all kind of stuff you can do with JavaScript: Crypto, obfuscation etc. I think they were a good opportunity
to learn more about the language itself and get some ideas how JavaScript obfuscation techniques work.

All I had to do is to search for secret string whose hash (SHA1) is b89356ff6151527e89c4f3e3d30c8e6586c63962.
Here you go: http://sha1.gromweb.com/?hash=b89356ff6151527e89c4f3e3d30c8e6586c63962. And the value is: adminz.
And finally the flag: FLAG-bXNsYg9tLCaIX6h1UiQMmMYB.

You can try to understand the callback function what it really does in JavaScript or you reverse it and build it Python.
Before doing that let's build genFunc in Python:

defgenFunc(part):# Assume that length of part is 4c=''# Check if first character is a digitifpart[0].isdigit():c=part[0]else:c='"'+part[0]+'"'# Create functions deff_part(p,d):""" p: parameter d: dictionary """# This will concatenate the first character of part with p# e.g. if part = 'abcd' and p='k' then a new dictionary will# be created: # dict['d'] = 'bc' + 'k'# d[part[3]]=''.join(p)+''.join(part[0])returndreturn{'func':f_part}

Calling genFunc for a list of paremeters would then give you a list of functions:

defmagic_function(a):# Do a "reverse" loop: Starting from the length of input array go down to 0.# e.g. if len(a) = 5 then i will have following values: 4,3,2,1,0foriinrange(len(a)-1,-1,-1):j=len(a)-1-i""" Basically the algo looks like this: d = {} d[a[i][3]] = a[j][1:3] + a[i][0] """# Pickup 2nd and 3rd character from the part where a[i] is pointing tob=''.join(a[i][1:3])# Pickup the functiony=funcs[i]['func']# Pickup 2nd and 3rd character from the part where a[len(a) - 1 -i] is pointing top=''.join(a[j][1:3])# Call the function x=y(p,y(b,{}))print("i:%d\tj:%d\ta[j][1:3]:%s\ta[i][0]:%s\tdict:%s"%(i,j,p,a[i][0],x))

So it's an AES encryption in CBC mode with an IV. Let's transcript that code to some node JS code:

!/usr/bin/env nodevarCryptoJS=require("crypto-js");// Simple function to convert a hex string to asciifunctionhex_to_ascii(str1){varhex=str1.toString();varstr='';for(varn=0;n<hex.length;n+=2){str+=String.fromCharCode(parseInt(hex.substr(n,2),16));}returnstr;}// This is what we havevaruser="\x68\x34\x78\x30\x72";vark=CryptoJS.SHA256("\x93\x39\x02\x49\x83\x02\x82\xf3\x23\xf8\xd3\x13\x37");varkey=CryptoJS.enc.Hex.parse(k.toString().substring(0,32));variv=CryptoJS.enc.Hex.parse(k.toString().substring(32,64));varencrypted="ob1xQz5ms9hRkPTx+ZHbVg==";// Do the decryptionp=CryptoJS.AES.decrypt(encrypted,key,{iv:iv})// Convert word array to (hex) stringk=''+p;console.log(user,hex_to_ascii(k));

I think the main difficulty was to convert the word array to sth useful. Anyway: Using h4x0r PassW0RD!289%!*
as username/password will reveal the flag: FLAG-gYtLBa66178DG7l28Uu5lW45CR.

As we can see in line 5 the key has to have a length of 9. Futhermore we can see that the key (k) is being XOR'ed
with characters of func. In lines 9-11 we can see that j is resetted to 0 if it exceeds the key's length. That means
characters of k are used multiple times in the XOR operation. So let's stop for a minute and think about what we've
got:

XOR operation

small key length

the key is being used multiple times to perform the decryption

Polyalphabetic ciphers

Hmmm... That smells like polyalphabetic ciphers which are
substitution ciphers! (No shit! That was indeed my first idea :).
Although the number of possible keys is very large, these ciphers are not very strong due to following reasons: By
having a reasonable length of an (encrypted) message, one could do a frequency analysis. That means, by having a
look at the frequency distribution of symbols (characters inside the encrypted text), one could deduce the meaning of
the most common symbol. In the English language for example the most common (3 letter) word would be "the". Let's say
you have an encrypted text and you know that the text was originally written in English. Then you could look for a
sequence of letters (let's say CHZ) in your ciphertext. If this sequence has the most distribution in the
ciphertext, then you have probably found the encrypted text for "the". Afterwards you can find the corresponding
alphabet which was used to encrypt the message, just by "shifting around". Let's have a look what that means.

Some example

Let's say our message is sth like THIS IS A VERY LONG MESSAGE. As an alphabet let's take the English one (A-Z
which are 26 letters). The encryption process will look like this:

Dn(x) = (x - n) mod 26
where:
n: how many letters has the alphabet been shifted
x: position of letter to be decrypted in the alphabet

For the example we had above let's do the decryption:

Dn("W") = (22 - 3) mod 26
= 19
= "T"

Kasiski analysis

For the further cryptanalysis it is important to determine the key length. The
(Kasiski) examination involves looking for sequence of letters that
are repeated in the ciphertext. Then if the distances between consecutive sequences of letters are likely to be
multiples of the length of the key. Doing this for different letter sequences narrows down the possible key lengths,
since we can take the GCD (greatest common divisor) of all distances.

Using some small python utility, I've scanned the ciphertext for occurences of the same letter. Addtionally I've
incremented the offset between the letters and counted the occurences:

As you can see the most occurences have taken place when offset = 9. We can verify this finding by having a look at the
JavaScript code above:

5if(k.length==9){

So the key length has to be 9. Okay, let's move on to the next step.

Frequency analysis

The big weakness of substitution ciphers is that they don't hide information about statistical characteristics of the
originating plaintext. We can use this to conduct a frequency analysis
which will help us finally decrypt the text.

In the next step I'll calculate the frequency distribution of the letters in the encrypted text. So basically I'll count
how many times each letter appears. Afterwards I'll compare this distribution to the letter frequency distribution of some
JavaScript library.

Letter frequency for D3 (JavaScript)

I'll use JavaScript code (in my case D3) in order to have a good comparison (I suppose
the ciphertext is also some JavaScript code as well):

importcollectionsimportosdefcountletters(myfile):""" Returns a dictionary containing a occurence frequency of each found character"""d=collections.defaultdict(int)myfile=open(myfile)forlineinmyfile:line=line.rstrip('\n')forcinline:d[c]+=1returnddefget_letters_count(myfile):""" Gets amount of characters in myfile """withopen(myfile)asf:c=f.read()returnlen(c)filename='/tmp/d3.v4.min.js'freqs=countletters(filename)file_size=get_letters_count(filename)percent_freqs={}fork,vinfreqs.iteritems():# Save ASCII code of letter and its occurence frequency percent_freqs[ord(k)]="{0:.8f}".format(v/float(file_size))# For all other unoccured letters, store occurence = 0foriinxrange(0,256):ifnotiinpercent_freqs:percent_freqs[i]="{0:.8f}".format(0)

And the results (frequency (occurences / length of text) of every ASCII code (0-255)):

Then using the Chi squared distribution we'll try to guess the right key.

Chi squared

In order to compare the letter distribution in our ciphertext against the one based on the D3 JavaScript code, I'll use
the Chi-squared test. The lower the value of the test, the higher the
probability that the decryption was successful.

Well that's almost perfect! As you can see, although the key is not the right one, the decrypted message almost reveals the original
text (as I've mentioned above, polyalphabetic ciphers are not secure since even pieces of information about the key could reveal the message).
However, from the code you can deduce that the key is XorIsCoolButNotUnbreakable. And that leads to the flag: FLAG-rhwMJNczASAJ4WgwfIA7fcJD.

Level 8: WTF Lol!

Yeah, this challenge was a little bit like "WTF?LOL?!". But first let's have a look at the code:

We remember that the function valueof() of Array was overridden by f2(). f2() will

pop some value from the variable stack (line 2)

increment some index by 3 (line 22)

the popped value from the stack will be then used as an index for tmp (line 3-10)

the return value of the operation will be: tmp[index]

Example: The first popped value will be m. The value of tmp['m'] is 12 (line 7). So the return value of the first
tmp[ [] ] operation will be 18.

tmp[ [] ] * tmp[ [] ] * 1337

Here we have another Array operation, so the return value will be: tmp[<popped character from stack>]. Afterwards the two
previous values are multiplicated and the result is again multiplicated by 1337. In this case we have:

return value of first Array operation: 12
return value of 2nd Array operation: 18 (the popped value from stack is 'm')
The result will be: 12 * 18 * 1337

tmp[ "" + { "wtf": password[1] } ]

Here we have:

an Object: { "wtf": password[1] }

the concatenation of an empty string "" with an Object

In this case the function toString() of the Object will be called, which is f3() (see line 35). Now let's have a look at f3():

If we have an Object like this { "wtf": value }, f3() will then increase the iterator (i) by the index of value in stack.
Afterwards value will be then pushed into stack. Example: If we had sth like { "wtf": "w" } the iterator will increase by 1
since w is at index 1 in stack. Afterwards w will be pushed into stack. So far so good.

However, line 30 looks very strange. If you look closely, the valueof() function of type Number is being called. This however
will call f1():

21functionf1(){returnstack[i++%stack.length].charCodeAt(0);}

So you basically have values that

get pushed into the stack

depending on their index in the stack and some iterator return some values from tmp

After the calculations of a, b, c etc. the normal behaviour is being restored:

The === operator will return true if the two operands have the same values and also the same type. As we can see a and b must be from the
same type and have the same value. The same applies to b and c, c and d and so on. At the end the script will check if every single
character in the supplied password/key is different from the other ones:

Bruteforce

Now we need to find all possible permutations (of 2 characters) between array1 and array2:

aa

ab

ac

..

da

db

..

zz

Bruteforce a and b

In order to find out for which combination a === b, we'll have to generate all permutations between array1 and array2:

functionallPossibleCases(arr){if(arr.length==1){returnarr[0];}else{varresult=[];varallCasesOfRest=allPossibleCases(arr.slice(1));// recur with the rest of arrayfor(vari=0;i<allCasesOfRest.length;i++){for(varj=0;j<arr[0].length;j++){result.push(arr[0][j]+allCasesOfRest[i]);}}returnresult;}}vararray1='abcdefghijklmnopqrstuvwxyz'.split('');vararray2='abcdefghijklmnopqrstuvwxyz'.split('');vartmp_array=[array1,array2];varperms=allPossibleCases(tmp_array);

Next we can call check_password with every permutation. However, you'll have to make sure that only a === b is being checked:

Bruteforce e

For the previous values a, b, c and d were calculated using password[0], password[1], password[2] and password[3].
However, e is calculated using password[7] and notpassword[4] as we would expect. Therefore the javascript has to be
slightly adapated:

As you can see the first 4 characters are already known: dklj. The next 3 ones (password[4], password[5], password[6]) are
still unknown. So we need to find the 7th one (password[7]). And the result is: dkljXXXj. So password[7] has to be j.