France - L4bR4t - 375 pts

Challenge

RU: В секретной лаборатории-X были сделаны фотографии неизвестного эксперимента для внутреннего архива. После этого на устройстве, с которого производилась съемка, сразу же сработал крипто-триггер, функция которого - обеспечить конфиденциальность графических данных (ведь именно так и положено поступать в супер-секретных-лабораториях?). Известно, что из-за плавающей ошибки в коде, триггер сработал не для всех сделанных фотографий. Нам удалось заполучить бинарник, который имеет отношение к крипто-триггеру. И теперь у нас есть все шансы узнать, какие секреты таит лаборатория-X...

EN: There was some photos of unknown experiment taken in a secret lab-X for they internal archive. After that the device from which the shot was made, immediately load crypto-trigger, whose function - to ensure the confidentiality of image data (this is exactly how it should be in a super-secret laboratories?). It is known that, due to some floating code errors, the trigger has not completed his work and not all the photos was encrypted. We managed to get a binary, which has something to do with that crypto-trigger software. And now we have a good chance to find out what secrets hides laboratory-X... We found an ftp service, I'm sure there's some way to log on to it.

Files given:

  • SecretLabXData_a63542e08f967070abb670be41703264.zip
  • SecretLabXLib_f70b84b224826b8362872b40a2448096.so

Solution

The zip files contains a bunch of encrypted and plain jpeg files. The idea here is to find a way to decrypt the encrypted jpeg in order to retrieve the flag. To achieve that, we also have a dynamic library named "SecretLabXLib.so".

At first, we had a look into this "secret" library. Using GDB, nm or objdump, we got interesting information looking at external symbols.

0000000000002b02 T MixColumns(unsigned char (*) [4])
0000000000001d0f T AddRoundKey(unsigned char (*) [4], unsigned int const*)
0000000000002419 T InvSubBytes(unsigned char (*) [4])
00000000000029c5 T InvShiftRows(unsigned char (*) [4])
00000000000035c5 T InvMixColumns(unsigned char (*) [4])
000000000000186e T ccm_format_assoc_data(unsigned char*, int*, unsigned char const*, int)
0000000000001950 T ccm_format_payload_data(unsigned char*, int*, unsigned char const*, int)
000000000000174c T ccm_prepare_first_ctr_blk(unsigned char*, unsigned char const*, int, int)
00000000000017a9 T ccm_prepare_first_format_blk(unsigned char*, int, int, int, int, unsigned char const*, int)
00000000000019fb T SubWord(unsigned int)
00000000000014fd T xor_buf(unsigned char const*, unsigned char*, unsigned long)
0000000000001faa T SubBytes(unsigned char (*) [4])
0000000000002888 T ShiftRows(unsigned char (*) [4])
0000000000004fe0 r sbox
00000000000051e0 r gf_mul
00000000000050e0 r invsbox
0000000000001650 T SuperSecretLabCryptor2000::decrypt_cbc(unsigned char const*, unsigned long, unsigned char*, unsigned int const*, int, unsigned char const*)
0000000000001554 T SuperSecretLabCryptor2000::encrypt_cbc(unsigned char const*, unsigned long, unsigned char*, unsigned int const*, int, unsigned char const*)
0000000000004a48 T SuperSecretLabCryptor2000::decrypt(unsigned char const*, unsigned char*, unsigned int const*, int)
0000000000004498 T SuperSecretLabCryptor2000::encrypt(unsigned char const*, unsigned char*, unsigned int const*, int)
0000000000001284 T SuperSecretLabCryptor2000::init_iv()
0000000000001222 T SuperSecretLabCryptor2000::init_key()
00000000000012d0 T SuperSecretLabCryptor2000::CryptFile(char*)
0000000000001ad2 T SuperSecretLabCryptor2000::key_setup(unsigned char const*, unsigned int*, int)
00000000000011b0 T SuperSecretLabCryptor2000::SuperSecretLabCryptor2000()
00000000000011b0 T SuperSecretLabCryptor2000::SuperSecretLabCryptor2000()

SupserSecretLabCryptor2000 class contains everything to manage the encryption.

Common names like decrypt_cbc, sbox, MixColumns, etc make it obvious that the encryption is about AES in CBC mode.

Going deeper, we found other interesting things:

Dump of assembler code for function _ZN25SuperSecretLabCryptor2000C2Ev:
... 
   0x00000000000011f4 <+68>:    mov    edi,0x0
   0x00000000000011f9 <+73>:    call   0x1090 <time@plt>
   0x00000000000011fe <+78>:    mov    edx,eax
   0x0000000000001200 <+80>:    mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000001204 <+84>:    mov    DWORD PTR [rax],edx
   0x0000000000001206 <+86>:    mov    rax,QWORD PTR [rbp-0x8]
   0x000000000000120a <+90>:    mov    rdi,rax
   0x000000000000120d <+93>:    call   0xf80 <_ZN25SuperSecretLabCryptor20008init_keyEv@plt>
   0x0000000000001212 <+98>:    mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000001216 <+102>:   mov    rdi,rax
   0x0000000000001219 <+105>:   call   0x1070 <_ZN25SuperSecretLabCryptor20007init_ivEv@plt>
...

Okay, the constructor initialize the key and the IV based on the time returned by time(0).

Looking at SuperSecretLabCryptor2000::CryptFile(), we could easily see how the file are encrypted. Here's a rapid pseudocode:

SuperSecretLabCryptor2000::CryptFile(char *pathToFile)
{

  FILE *f = fopen(pathToFile, "rb");

  fseek(f, 0, SEEK_END);

  ftell(f);   //used to get size of file

  fseek(f, 0, 0);  //get back to the beginning

  //loop based on file size
  for()
  {
    fread(void *ptr = buff_plain, size_t size = 1, size_t nmemb = 0x100, f);
  }

  fclose(f);

  this->key_setup(this->key, derivedKey, 256);

  this->encrypt_cbc(buff_plain, file_size, buff_enc, derived_key, 256, this->IV);


  //Then simply write encrypted result to file
  ...
}

To resolve this challenge, we need to find the time value that was used for the picture encryption. The modification date of the encrypted picture is "2016-07-31 20:42:32.000000000 +0200".

From now, there's different possibilities to resolve this challenge:

  • Reverse the init_key() and init_iv() function and use a simple python-or-whatever script to do the AES-CBC.
  • Use the library directly (dlopen, dlsym) to call init_key/init_iv and get the key/IV.
  • Patch the library to use decrypt_cbc() function and directly decrypt the file.

As the library offers a decrypt_cbc() method with the exact same signature as encrypt_cbc, we choose the third idea.

The function encrypt_cbc() has a dynamic symbol with a value of 0x1554. When the encrypt_cbc() is called, the control flow goes to a stub function (0xf50). This is where the dynamic linker do its job using the symbol value of encrypt_cbc() from the symbol table in order to retrieve the real function.

So, we patched the symbol value of encrypt_cbc() symbol by the one of decrypt_cbc() (0x1650) at offset 0x560. From now, calling CryptFile() will result in a decryption instead of encryption ;-)

Instead of using dlopen/dlsym, we recreated the header file of the library. The function are easily retrieved using symbol table and a demangler. For the attributes, it is more complicated. The easiest solution is to declare a big char array in order to avoid memory collision. But, if you debug the library, you will see what exactly are the attributes in memory.

//libaes.h

class SuperSecretLabCryptor2000 {
public:

    SuperSecretLabCryptor2000();
    void CryptFile(char*);

private:
    int32_t time;
    char key[32];
    char IV[16];
};

Using this header file, we can simply decrypt a file with this simple main function:

//main.c

#include "libaes.h"

int main()
{
    SuperSecretLabCryptor2000 *calculator = new SuperSecretLabCryptor2000();
    calculator->CryptFile("test.jpg");
    return 0;
}

Last thing, we need to overwrite the time() function.

//libhack.c

uint32_t time(uint32_t *t)
{
 return 1466136077;
}

Finally, compiling everything.

$ gcc libhack.c -o libhack.so -shared
$ g++ main.c -o main -Ldecrypt -laes
$ cp dat.0.jpg test.jpg
$ LD_PRELOAD=$(pwd)/libhack.so LD_LIBRARY_PATH=$(pwd)/decrypt ./main
$ xdg-open test.jpg

Note that the secret library must be named libaes.so. We put our patched library into the ./decrypt/ directory.

h4ck1t{CrYp70_3xxP3R1m3N75_VV0n7_4LVV4Y5_3ND_VV3II}