Flare-On 8 - known

Flare-On 8 - known
We need your help with a ransomware infection that tied up some of our critical files. Good luck.

With the second challenge, it's a bit step up in the difficulty. We are given an EXE with some files (different types; images images and text) that has been encrypted.

Opening file in Ghidra, we can see less than 10 methods. From entry we can identify main and check what it's doing.

local_c = FindFirstFileA(s_*.encrypted_0040372c,(LPWIN32_FIND_DATAA)&local_194);
if (local_c == (HANDLE)0xffffffff) {
while( true ) {
  do {
    local_14 = FUN_00401030((int)local_54,(int)local_194.cFileName);
    local_194.cAlternateFileName[local_14 + 6] = '\0';
    local_8 += 1;
    BVar1 = FindNextFileA(local_c,(LPWIN32_FIND_DATAA)&local_194);
  } while (BVar1 != 0);
  DVar2 = GetLastError();
  if (DVar2 == 0x12) break;

The core part is the following:

  • find files with .encrypted extension
  • prepares a new name, without .encrypted extension
  • call FUN_00401220 with the new name, original name a argument passed to the binary

Cleaning up the above code, we can get the following, much readable version

File = FindFirstFileA(s_*.encrypted_0040372c,(LPWIN32_FIND_DATAA)&local_194);
if (hFile == (HANDLE)0xffffffff) {
while( true ) {
  do {
    fileNameLen = copy(fileName,local_194.cFileName);
    local_194.cAlternateFileName[fileNameLen + 6] = '\0';
    cnt += 1;
    res = FindNextFileA(hFile,(LPWIN32_FIND_DATAA)&local_194);
  } while (res != 0);
  DVar1 = GetLastError();
  if (DVar1 == 0x12) break;

decrypt_files is the function we want to focus and check the code (again, after a clean up) we can see that the main routine is simple decrypt function written as follow

void __cdecl decrypt(char *dst,char *src)

  byte j;
  uint i;
  i = 0;
  while (j = (byte)i, (char)j < 8) {
    dst[i] = ((dst[i] ^ src[i]) << (j & 7) | (byte)(dst[i] ^ src[i]) >> 8 - (j & 7)) - j;
    i = (uint)(byte)(j + 1);

The function works in chunks of 8 bytes and transforms them according the the above formula. This actually might be a bit confusing with the shift-s, and or-s, but if we would look at the assembly (which is sometimes better - see https://allthingsreversed.io/dont-trust-the-decompilers/) it's clear that the routine is build from two simple opcodes: xor, rol and sub.

Simplifying, each character is processed in the following way:

dst[j] = rol(inp[j] ^ key[j], j) - j # j goes from 0 to 7

Rol (and ror) are not available in python but we can write then using simple lambda:

ror = lambda val, r_bits, max_bits: \
    ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
    (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

rol = lambda val, r_bits, max_bits: \
    (val << r_bits%max_bits) & (2**max_bits-1) | \
    ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

To use them, we need to provide the additional information as 3rd parameter - size of the data - in this case - 8 bits).

Knowing the routine, there's still one problem - password for the decryption. It's being passed as an argument to this code and we don't know it, yet.

Let's focus on the encrypted files. We have, cicero.txt.ecnrypted, commandovm.gif.encrypted, critical_data.txt.encrypted, flarevm.jpg.encrypted, lating_alphabeth.txt.encrypted and capa.png.encrypted.

Some of those images, are known FlareVM images that we could find and extract password for the encryption. But we have a simpler sample. latin_alphabeth.txt.encrypted.

If we consider the file name to be telling the truth, we should have all the Latin letters inside this file. Let's see what we will get if we can reverse the routine and get the password (knowing the output and the input):

def extract_key(dst, inp):
    key = [None]*len(inp)
    for j in range(8):
        key[j] = ror(inp[j] + j,j) ^ dst[j] 
    return key

If we pass our encrypted latin_alphabeth.txt.encrypted and potential input "ABCDEFGHIJKLMNOPQRSTUVWXYZ" we can retrieve the key: No1Trust. Having that, we can decode the rest of the files and obtain the flag: (>0_0)> You_Have_Awakened_Me_Too_Soon_EXE@flare-on.com <(0_0<). Solved.