Description:

We are given files snip.php, readme.txt, and readme.txt.fsociety. The goal is to decript the message in readme.txt.fsociety knowing that it uses AES with a randomly generated key.

flag format: flag{...}

Solution:

(by L0rdC0mm4nd3r)

The given snip.php file is fairly small and simple to analyse.

function destroy_file($fp,$len) {
  $random = "";
  for ($i = 0;$i < $len;$i++)
    $random.= chr(mt_rand(0, 255));
  fseek($fp, 0);
  fwrite($fp, substr($random, 0, $len));
  fclose($fp);
}


function gen_aes_key() {
 $key = "";
 for ($i = 0;$i < 16;$i++)
   $key.= chr(mt_rand(0, 255));
 return $key;

}

$crypted = fopen($file . ".fsociety", "w");
$fp = fopen($file, "r+");
$clear = fread($fp, 2048);
// destroy original file
destroy_file($fp,strlen($clear));


// generate unique key
$key = gen_aes_key();
$aes = new Crypt_AES(CRYPT_AES_MODE_ECB);
$aes->setKeyLength(128);
$aes->setKey($key);

// create encrypted file
$clear = $aes->encrypt($clear);
fwrite($crypted,$clear,strlen($clear));
  1. The program opens file readme.txt to read and readme.txt.fsociety to write.

  2. It erases the readme.txt file that contains the original message using function destroy_file that basically writes a random char over each position of the original message.

  3. It generates a 16-byte AES key.

  4. It ciphers the original message with the generated key, and writes the output in readme.txt.fsociety.

So there is no padding, no oracle, AES is impossible to brute-force… What should we do? The question that immediately came to my mind was why do we have the readme.txt file? Is there any known vulnerability on mt_rand()?

Search for mt_rand()

Immediately got

Caution This function does not generate cryptographically secure values, and should not be used for cryptographic purposes.

I immediately knew that I was on the right track. After a bit of searching I knew that it is possible to find out the used seed and a few tools for that can be found in the internet mt_rand attacks.

After looking at a few of them I found php_mt_seed that receives a sequence of random generated numbers with mt_rand() and returns all possible seeds. Longer inputs narrow down the number of outcomes.

The attack was the following

  1. Read readme.txt to get the first n generated random numbers.
input_to_derandomizer = ''
with open('readme.txt', 'r') as f:
	x = f.readline()
	for char in x:
		input_to_derandomizer += str(ord(char)) + ' ' + str(ord(char)) + ' ' + '0 255 '
  1. Give these numbers to php_mt_seed to get all possible seeds. There was just one – 844114388
./php_mt_seed 123 123 0 255 54 54 0 255 14 14 0 255 233 233 0 255 249 249 0 255 185 185 0 255 28 28 0 255 254 254 0 255 208 208 0 255 187 187 0 255 208 208 0 255 230 230 0 255 19 19 0 255 17 17 0 255 88 88 0 255 40 40 0 255 252 252 0 255 254 254 0 255 132 132 0 255 166 166 0 255 116 116 0 255 83 83 0 255 3 3 0 255 246 246 0 255 133 133 0 255 182 182 0 255 226 226 0 255 112 112 0 255 118 118 0 255 195 195 0 255 65 65 0 255 248 248 0 255 174 174 0 255 196 196 0 255 156 156 0 255 165 165 0 255
  1. Fix the seed on destroy_file function with mt_srand(844114388) and generate a new file readme.txt.rand. If readme.txt.rand and readme.txt are equal than we are on the right path.

  2. Generate 16 more random bytes using gen_aes_key() to get the used AES key.

  3. Use this key to decrypt readme.txt.fsociety.

BLOCK_SIZE=16

def encrypt(message, passphrase):
    #IV = Random.new().read(BLOCK_SIZE)
    aes = AES.new(passphrase, AES.MODE_ECB)
    return aes.encrypt(message)


def decrypt(encrypted, passphrase):
    #IV = Random.new().read(BLOCK_SIZE)
    aes = AES.new(passphrase, AES.MODE_ECB)
    return aes.decrypt(encrypted)


message=''
with open('readme.txt.fsociety', 'rb') as f:
	message = f.read(1000)

with open('readme.txt.key', 'rb') as f:
	key = f.readline().strip()

print len(message), repr(message)

print repr(decrypt(message, key))
  1. Done. 'flag{the_darkarmy_is_now_on_to_you}\n\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c'

Since we were in a CTF everything that works is suitable to solve the challenge so I used a mix of python and php and whatever worked out.