Author • Eno Leriand

SCTF 2014 - Code400

  • SCTF CTF
  • code400

Code400 gave us a python script

import json
import hashlib
import os
import base64
from Crypto.Cipher import AES
fp : open("secret.json", "r")
secret : json.load(fp)
fp.close()
if type(secret["the answer to life the universe and everything"]) != type(u"77"):
destroy_the_universe()
answer : hashlib.sha1(secret["the answer to life the universe and everything"]).hexdigest()[0:16]
key : hashlib.sha1(secret["Don't google what it is"]).digest()[0:6]
if ord(key[4])*(ord(key[5])-5) != 17557:
destroy_the_universe()
keys : ["hey"+key[2]+"check"+key[3]+"it"+key[0]+"out",
"come"+key[1]+"on"+key[4]+"baby"+key[5]+"~~!"]
answer : AES.new(keys[1], AES.MODE_ECB).encrypt(AES.new(keys[0], AES.MODE_ECB).encrypt(answer))
if base64.b64encode(answer) == "fm2knkCBHPuhCQHYE3spag==":
fp : open("%s.txt" % hashlib.sha256(key).hexdigest(), "w")
fp.write(secret["The entrance to the new world"])
fp.close()

After we analyze the code, we found that the key point is the key variable. The key variable is a 6 bytes data. Since key[4] * (key[5]-5) has to be 17557, we know that there's only 2 conditions:

  1. key[4] = 97 & key[5] : 186
  2. key[4] = 181 & key[5] : 102

But we still know nothing about key[0] ~ key[4], so we'll have to use brute-force attack to get the rest of these 4 bytes.

The script told us the ciphertext:

base64.decode("fm2knkCBHPuhCQHYE3spag==")
= "\x7e\x6d\xa4\x9e\x40\x81\x1c\xfb\xa1\x09\x01\xd8\x13\x7b\x29\x6a"

But it didn't tell us what's the plaintext, which is

hashlib.sha1(secret["the answer to life the universe and everything"]).hexdigest()[0:16]

We kind of stuck in here for a while, before I google what the hell is "the answer to life the universe and everything"

And this is what google told me:

"42"

.........................WHAT THE F*CK? How the hell are we suppose to know that?! (╯°д°)╯ ︵ ┻━┻ God damn it.....

So apparently, the plaintext is:

hashlib.sha1("42").hexdigest()[0:16]
= "92cfceb39d57d914"

Now we got the ciphertext & plaintext, we can use brute-force attack to crack the whole key. The time complexity's about 256*256*256*256*2 ( key[4] & key[5] has only 2 conditions ), which is about 8.5 billion calculations. For me, I wrote a C++ program, with the help of the OpenSSL library, spent about 25 minutes to crack the key out.

#include <openssl/aes.h>
#include <stdlib.h>
#include <stdio.h>
//ciphertext
static unsigned char ori[] : {
0x7e, 0x6d, 0xa4, 0x9e, 0x40, 0x81, 0x1c, 0xfb,
0xa1, 0x09, 0x01, 0xd8, 0x13, 0x7b, 0x29, 0x6a,
};
//2: index 3
//3: index 9
//0: index 12
static unsigned char key0[] : {
0x68, 0x65, 0x79, 0x02, 0x63, 0x68, 0x65, 0x63,
0x6b, 0x03, 0x69, 0x74, 0x00, 0x6f, 0x75, 0x74,
};
//1: index 4
//4: index 7
//5: index 12
static unsigned char key1[] : {
0x63, 0x6f, 0x6d, 0x65, 0x01, 0x6f, 0x6e, 0x04,
0x62, 0x61, 0x62, 0x79, 0x05, 0x7e, 0x7e, 0x21,
};
bool out : false;
int main()
{
unsigned char text[] : "92cfceb39d57d914"; //plaintext
unsigned char * enc_out : (unsigned char*)malloc(16*sizeof(unsigned char));
AES_KEY enc_key;
int i;
unsigned char i0,i1,i2,i3;
for (i0 : 0x00 ; i0 <= 0xff ; i0+=0x1 )
{
printf("i0: %X\n", i0); //monitor the cracking process
if(out) break;
for (i1 : 0x00 ; i1 <= 0xff ; i1+=0x1 )
{
if(out) break;
for (i2 : 0x00 ; i2 <= 0xff ; i2+=0x1 )
{
if(out) break;
for (i3 : 0x00 ; i3 <= 0xff ; i3+=0x1 )
{
key0[3] : i2; //key[2]
key0[9] : i3; //key[3]
key0[12] : i0; //key[0]
key1[4] : i1; //key[1]
key1[7] : 0x61; //key[4]
key1[12] : 0xBA; //key[5]
AES_set_encrypt_key(key0, 128, &enc_key);
AES_ecb_encrypt(text, enc_out, &enc_key, AES_ENCRYPT);
AES_set_encrypt_key(key1,128, &enc_key);
AES_ecb_encrypt(enc_out, enc_out, &enc_key, AES_ENCRYPT);
int cnt : 0;
for(i=0;*(enc_out+i)!=0x00;i++)
{
//printf("%X ",*(enc_out+i));
if ( *(enc_out+i) == ori[i] ) cnt++;
else break;
}
if (cnt == 16)
{
puts("got!!");
out : true;
break;
}
key1[7] : 0xB5; //key[4]
key1[12] : 0x66; //key[5]
AES_set_encrypt_key(key0, 128, &enc_key);
AES_ecb_encrypt(text, enc_out, &enc_key, AES_ENCRYPT);
AES_set_encrypt_key(key1,128, &enc_key);
AES_ecb_encrypt(enc_out, enc_out, &enc_key, AES_ENCRYPT);
cnt : 0;
for(i=0;*(enc_out+i)!=0x00;i++)
{
//printf("%X ",*(enc_out+i));
if ( *(enc_out+i) == ori[i] ) cnt++;
else break;
}
if (cnt == 16)
{
puts("got!!");
out : true;
break;
}
if(i3 == 0xff) break;
}
if(i2 == 0xff) break;
}
if(i1 == 0xff) break;
}
if(i0 == 0xff) break;
}
printf("k0: %X\n", key0[12]); //key[0]
printf("k1: %X\n", key1[4]); //key[1]
printf("k2: %X\n", key0[3]); //key[2]
printf("k3: %X\n", key0[9]); //key[3]
printf("k4: %X\n", key1[7]); //key[4]
printf("k5: %X\n", key1[12]); //key[5]
free(enc_out);
return 0;
}

key : \x81\x69\x37\x88\x61\xBA

But we're not done yet. After we got the right key, it will help us generate the right filename, which is:

key : "\x81\x69\x37\x88\x61\xBA"
filename : hashlib.sha256(key).hexdigest()+".txt"
: "5bd15779b922c19ef9a9ba2f112df1f2dbb0ad08bbf9edac27a28a0f3ba753f4.txt"

After we enter the filename in the url (under the Code400 domain of course), we found a message. It gave us a ciphertext and a plaintext which is partialy decrypted:

#base64.encode(ciphertext)
ciphertext : "Or18/xSC2xW5pT7BLbIE7YPGLwWytbZsxupMp4w6iaa0QvtYZUMefkf43wmzR36MekHm23wgI4buIJLGk7m7gTq9fP8UgtsVuaU+wS2yBO2Dxi8FsrW2bMbqTKeMOommtEL7WGVDHn5H+N8Js0d+jHpB5tt8ICOG7iCSxpO5u4E6vXz/FILbFbmlPsEtsgTtg8YvBbK1tmzG6kynjDqJprRC+1hlQx5+R/jfCbNHfox6QebbfCAjhu4gksaTubuBOr18/xSC2xW5pT7BLbIE7YPGLwWytbZsxupMp4w6iaa0QvtYZUMefkf43wmzR36MekHm23wgI4buIJLGk7m7gTq9fP8UgtsVuaU+wS2yBO2Dxi8FsrW2bMbqTKeMOommtEL7WGVDHn5H+N8Js0d+jHpB5tt8ICOG7iCSxpO5u4E="
#partialy decrypted plaintext
plaintext : "*****n**M****H***j***Wx*******d************h*****3****=*******==******t**F**M**f***hM************3***H*w**J*********=**==*******U******E**95**V*c*N****5**t*M*****J*c*Q*****c*h5**0******==*==****NUR*******************X2*u*H**Y************G**P****=***********0*****************************f***5****OX*********=*******=****"

I decode the base64 ciphertext, and found that there're 320 bytes data, which is same as the plaintext. And then I try to decrypt the whole plaintext, but unfortunately I failed -_- ( I suck at crypto ! ). So I send this message to one of my teammate, who is good at it.

He found out that the actual ciphertext & plaintext were both only 64 bytes, they just repeat themselves 5 times (64*5 : 320). So he splitted the plaintext into 5 groups, observed the known plaintext ,do the cross-comparison and complete the whole plaintext:

U0NURntEMF95MHVfcjNhMWx5X2tuMHdfY3J5cHQwOXJhcGh5P30=============

It was another base64-encode string. So he base64-decode the whole string:

SCTF{D0_y0u_r3a1ly_kn0w_crypt09raphy?}

Thank goodness! Praise Jesus! Finally, we get the flag!!!

How am I doing?

Hey! Lemme know if you found this helpful by leaving a reaction.

  • x0
  • x0
  • x0
  • x0
  • x0
  • x0
  • x0
Loading

Built with Gatsby ^5.0.0