NcN 2013 CTF Quals – Level 1

NcN 2013 Qualification - Level 1 - Task Description

To get the key for “Access Level 1” we need to pass an authentication form. When trying to submit a key, we get the following reply:

NcN 2013 Quals - Level 1 - Invalid Password

So we need to have a look into the source code of the password validation functions. At first we are going to see the HTML lines (index.php). The interesting ones are these:

<script type="text/javascript" src="crypto.js"></script>

<form action="login.php" method="POST" onsubmit="return encrypt(this);">
  <input id="key" type="hidden" name="key" value="" />
  <input id="verification" type="hidden" name="verification" value="yes" />
</form>

As we can see the page uses an external JavaScript file to calculate the validation of the password. Next step will be to examine this script file (crypto.js). It looks like this:

var _0x52ae=["\x66\x20\x6F\x28\x38\x29\x7B\x63\x20\x69...
...,"\x67"];eval(function (_0x7038x1,_0x7038x2,_0x7038x3..
...toString(36));};if(!_0x52ae[4][_0x52ae[6]](/^/,String)..
...[0],46,46,_0x52ae[3][_0x52ae[2]](_0x52ae[1]),0,{}));

The JavaScript itself uses many “eval” (evaluation) functions, confusing variable names and is incredible obfuscated at all. It cannot be read this way! So we need make it readable again – at best evaluate the “eval” function to get the real plain source code. It very good way to do so is using raw SpiderMonkey – the Mozilla JavaScript engine:

rup0rt@lambda:~/NcN2013/level1$ js
js> load("crypto.js")
js> print(encrypt)

function encrypt(form) { 
  var res;
  res=numerical_value(form.password.value);
  res=res*(3+1+3+3+7);
  res=res>>>6;
  res=res/4;
  res=res^4153;

  if (res!=0) {
    alert('Invalid password!')
  } else {
    alert('Correct password :)')
  }

  form.key.value=numerical_value(form.password.value);
  form.verification.value="yes"+simpleHash(form.password.value);
  return true
}

There it is! The plain human-readable encrypt()-function that is used the validate our password. Obviously there are other self-written functions used (numerical_value(), simple_hash(), …) that we need to extract as well using the same technique:

function numerical_value (str) {
  var i,a=0,b;
  for (i=0;i<str.length;++i) {
    b=ascii_one(str.charAt(i));
    a+=b*(i+1)
  }
  return a
}

function simpleHash (str) {
  var i,hash=0;
  for (i=0;i<str.length;i++) {
    hash+=(str[i].charCodeAt()*(i+1))
  }
  return Math.abs(hash)%31337
}

function ascii_one (foo) {
  foo=foo.charAt(0);
  var i;
  for (i=0;i<256;++i) {
    var hex_i=i.toString(16);
    if (hex_i.length==1) hex_i="0"+hex_i;
    hex_i="%"+hex_i;
    hex_i=unescape(hex_i);
    if (hex_i==foo) break
  }
  return i
}

To get the final key we need to do all calculations the other way around. So we take these lines of code:

var res;
res=numerical_value(form.password.value);
res=res*(3+1+3+3+7);
res=res>>>6;
res=res/4;
res=res^4153;

if (res!=0) {

… and reverse them …

var back;
back = 4153;
back = back * 4;
back = back * 2 * 2 * 2 * 2* 2 * 2; // <<<6 does not work
back = back / (3+1+3+3+7)
alert(back);

When executing the reversed part of the script, alert() shows us the following information:

NcN 2013 Qualls - Level 1 - Reversed password

This value has to be (approximate) the result of “numerical_value(form.password.value)”!

As you can see in the source code above, numerical_value() just sums up the ASCII-Codes of the characters multiplied with their position in the string. For example:

a = 97 (1 * 97)
ab = 293 ( 1 * 97 + 2 * 98)
abc = 591 (1 * 97 + 2 * 98 + 3 * 99)

To get the reverse of numerical_value() we might calculate it in a proper way … or just brute force it ;-). A small Perl-Script does this job for us:

#!/usr/bin/perl -w

$target = 62539.294117647056;
$sum = 0;
$string = "";

while ($sum != $target) {
  if ($sum < $target) {
    $string = "z" . $string;
  } else { last; }

  $sum = 0;
  for ($i=0; $i<length($string); $i++) {
    $sum += ($i+1) * ord(substr($string, $i, 1));
  }
  print "$sum\t= $string\n";
}

$i = 1;
while ($sum > $target) {
  $todo = $sum - $target;
  print "need to reduce $todo\n";

  $last = ord(substr($string, $i-1, 1));
  $diff = $todo / $i;
  print "len: $i\n";
  print "last: $last\n";
  print "diff: $diff\n";
  if ($last-$diff < 33) {
    substr($string, $i-1, 1) = "a";
  } else {
    substr($string, $i-1, 1) = chr($last-int($diff+1));
  }

  $sum = 0;
  for ($w=0; $w<length($string); $w++) {
    $sum += ($w+1) * ord(substr($string, $w, 1));
  }
  print "$sum\t= $string\n";

  $i++;

}

This (very dirty) script just fills up a string with letter “z” until it reaches the target value (62539). Afterwards it reduces the string until the numerical_value is below the target value again. It is just an approximation so we will have to adjust the result to fit our needs. Executing the script gives this output:

rup0rt@lambda:~/NcN2013/level1$ ./level1.pl 
122     = z
366     = zz
732     = zzz
[...]
need to reduce 751.705882352944
len: 10
last: 122
diff: 75.1705882352944
62531   = aaaaaaaaa.zzzzzzzzzzzzzzzzzzzzzz

This result string is an just approximation. We first need to fill it up to the target value of 62539, so we need to add 7 to the numerical_value() by increasing the first letter to “i”:

iaaaaaaaa.zzzzzzzzzzzzzzzzzzzzzz

Finally we need to counterbalance the impreciseness of JavaScript reverse calculation. This can be done by trial and error or add an additional alert() field to debug the JavaScript code. The final numerical_value() is 62544 so the final string is:

naaaaaaaa.zzzzzzzzzzzzzzzzzzzzzz

Submitting this string as password, results in the following window …

NcN 2013 Quals - Level 1 - correct password

… and finally redirects us to the flag of this challenge:

NcN 2013 Quals - Level 1 - Solution

The flag is 23f8d1cea8d60c5816700892284809a94bd00fe7347645b96a99559749c7b7b8.

Leave a Reply

Your email address will not be published. Required fields are marked *