France - L4bR4t - 375 pts


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:



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 "".

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
    fread(void *ptr = buff_plain, size_t size = 1, size_t nmemb = 0x100, 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.


class SuperSecretLabCryptor2000 {

    void CryptFile(char*);

    int32_t time;
    char key[32];
    char IV[16];

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


#include "libaes.h"

int main()
    SuperSecretLabCryptor2000 *calculator = new SuperSecretLabCryptor2000();
    return 0;

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


uint32_t time(uint32_t *t)
 return 1466136077;

Finally, compiling everything.

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

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