Cette fois-ci, on va commencer par utiliser MemProcFs
.
On regarde les process qui tournaient, via sys/proc.txt
:
-- update_v0.8.ex 3596 1420 U* IEUser 2020-04-13 19:18:13 UTC ***
En effet, ça semble être de nouveau une mise à jour. On envoie le binaire name/update_v0.8.ex-3596/files/modules/update_v0.8.exe
dans ghidra et après l’analyse automatique, on regarde un peu les fonctions définies. La première qui semble intéressante affiche
le nom de l’outil :
undefined4 FUN_00401d3b(void)
{
int iVar1;
time_t tVar2;
int local_14;
FUN_00401fd0();
puts("\tENCRYPTOR v0.8\n");
tVar2 = time((time_t *)0x0);
srand((uint)tVar2);
for (local_14 = 0; local_14 < 0x32; local_14 = local_14 + 1) {
iVar1 = rand();
(&DAT_004063e0)[local_14] = (char)iVar1 + (char)(iVar1 / 0x19) * -0x19 + 'A';
}
DAT_00406412 = 0;
FUN_00401b80("./");
_DAT_004063e0 = 0x30303030;
_DAT_004063e4 = 0x30303030;
_DAT_004063e8 = 0x30303030;
_DAT_004063ec = 0x30303030;
_DAT_004063f0 = 0x30303030;
_DAT_004063f4 = 0x30303030;
_DAT_004063f8 = 0x30303030;
_DAT_004063fc = 0x30303030;
_DAT_00406400 = 0x30303030;
_DAT_00406404 = 0x30303030;
_DAT_00406408 = 0x30303030;
_DAT_0040640c = 0x30303030;
_DAT_00406410 = 0x30;
puts(&DAT_00405070);
getchar();
return 0;
}
On voit qu’elle bricole des valeurs dans le buffer _DAT_004063e0
et lance une fonction avec en paramètre le répertoire courant. Ouvrons celle-ci :
void __cdecl FUN_00401b80(char *param_1)
{
char cVar1;
int iVar2;
uint uVar3;
char *pcVar4;
undefined2 uStack_459;
char local_358 [260];
char acStack_254 [260];
int local_150;
int local_14c;
char local_124 [260];
int local_20;
printf("[info] entering the folder : %s\n",param_1);
FUN_00401530(local_124,param_1);
do {
if (local_20 == 0) {
FUN_00401681(local_124);
return;
}
FUN_0040179f(local_124,local_358);
if (local_150 == 0) {
LAB_00401c8b:
if (local_14c != 0) {
iVar2 = strcmp(acStack_254,"flag.txt");
if (iVar2 == 0) {
strcpy((char *)((int)&uStack_459 + 1),param_1);
strcat((char *)((int)&uStack_459 + 1),acStack_254);
printf("[info] file encryptable found : %s\n",(int)&uStack_459 + 1);
FUN_004019ab((char *)((int)&uStack_459 + 1));
}
}
}
else {
iVar2 = strcmp(acStack_254,".");
if (iVar2 == 0) goto LAB_00401c8b;
iVar2 = strcmp(acStack_254,"..");
if (iVar2 == 0) goto LAB_00401c8b;
strcpy((char *)((int)&uStack_459 + 1),param_1);
strcat((char *)((int)&uStack_459 + 1),acStack_254);
uVar3 = 0xffffffff;
pcVar4 = (char *)((int)&uStack_459 + 1);
do {
if (uVar3 == 0) break;
uVar3 = uVar3 - 1;
cVar1 = *pcVar4;
pcVar4 = pcVar4 + 1;
} while (cVar1 != '\0');
*(undefined2 *)((int)&uStack_459 + ~uVar3) = 0x2f;
FUN_00401b80((char *)((int)&uStack_459 + 1));
}
FUN_00401721((int)local_124);
} while( true );
}
Toujours en lisant en diagonal, cette dernière semble lancer un traitement en présence d’un fichier flag.txt
. Descendons
encore d’un niveau :
void __cdecl FUN_004019ab(char *param_1)
{
char cVar1;
uint uVar2;
char *pcVar3;
char local_12c [256];
size_t local_2c;
size_t local_28;
void *local_24;
size_t local_20;
FILE *local_1c;
FILE *local_18;
int local_14;
uint local_10;
strcpy(local_12c,param_1);
uVar2 = 0xffffffff;
pcVar3 = local_12c;
do {
if (uVar2 == 0) break;
uVar2 = uVar2 - 1;
cVar1 = *pcVar3;
pcVar3 = pcVar3 + 1;
} while (cVar1 != '\0');
*(undefined4 *)(local_12c + (~uVar2 - 1)) = 0x636e652e;
local_12c[~uVar2 + 3] = '\0';
local_18 = fopen(param_1,"rb");
local_1c = fopen(local_12c,"wb+");
fseek(local_18,0,2);
local_20 = ftell(local_18);
local_24 = malloc(local_20);
fseek(local_18,0,0);
local_28 = fread(local_24,1,local_20,local_18);
_strrev(&DAT_004063e0);
local_2c = strlen(&DAT_004063e0);
for (local_10 = 0; (int)local_10 < (int) ; local_10 = local_10 + 1) {
*(byte *)((int)local_24 + local_10) =
*(byte *)((int)local_24 + local_10) ^ (&DAT_004063e0)[local_10 % local_2c]; /* XOR avec DAT_004063e0 */
}
fseek(local_18,0,0);
for (local_14 = 0; local_14 < (int)local_20; local_14 = local_14 + 1) {
putc((int)*(char *)((int)local_24 + local_14),local_1c);
}
free(local_24);
fclose(local_18);
fclose(local_1c);
remove(param_1);
return;
}
À vue de nez, ça ressemble au chiffrement d’un fichier. On voit des ouvertures de fichiers, dont l’un porte
le nom de celui passé en paramètre, complété par .enc
(0x636e652e). On voit aussi des opérations XOR avec
le buffer DAT_004063e0
, généré dans la première fonction, et donc on a inversé l’ordre (_strrev
).
La clef serait dans DAT_004063e0
, revenons donc à sa génération :
tVar2 = time((time_t *)0x0);
srand((uint)tVar2);
for (local_14 = 0; local_14 < 0x32; local_14 = local_14 + 1) {
iVar1 = rand();
(&DAT_004063e0)[local_14] = (char)iVar1 + (char)(iVar1 / 0x19) * -0x19 + 'A';
}
On voit aussi qu’à la fin de la fonction, la clef est écrasée par des zéros, donc aucune chance de la retrouver dans la capture mémoire.
D’après la doc :
La fonction time retourne le nombre de secondes écoulées depuis minuit (00:00:00), le 1er janvier 1970, temps universel coordonné (UTC), d’après l’horloge système.
Or, on sait que le process a été lancé à la date et l’heure 2020-04-13 19:18:13 UTC
. On a donc la graine (1586805493
) qui permet de regénérer la série.
Par contre, le code décompilé ne semble pas avoir de sens. Le code assembleur est :
LAB_00401d73 XREF[1]: 00401dbd(j)
00401d73 e8 48 16 CALL MSVCRT.DLL::rand int rand(void)
00 00
00401d78 89 c1 MOV ECX,EAX
00401d7a ba 1f 85 MOV EDX,0x51eb851f
eb 51
00401d7f 89 c8 MOV EAX,ECX
00401d81 f7 ea IMUL EDX
00401d83 c1 fa 03 SAR EDX,0x3
00401d86 89 c8 MOV EAX,ECX
00401d88 c1 f8 1f SAR EAX,0x1f
00401d8b 29 c2 SUB EDX,EAX
00401d8d 89 d0 MOV EAX,EDX
00401d8f c1 e0 02 SHL EAX,0x2
00401d92 01 d0 ADD EAX,EDX
00401d94 8d 14 85 LEA EDX,[EAX*0x4 + 0x0]
00 00 00 00
00401d9b 01 d0 ADD EAX,EDX
00401d9d 29 c1 SUB ECX,EAX
00401d9f 89 ca MOV EDX,ECX
00401da1 89 d0 MOV EAX,EDX
00401da3 83 c0 41 ADD EAX,0x41
00401da6 89 c2 MOV EDX,EAX
00401da8 8b 44 24 1c MOV EAX,dword ptr [ESP + local_14]
00401dac 05 e0 63 ADD EAX,DAT_004063e0 = ??
40 00
00401db1 88 10 MOV byte ptr [EAX],DL=>DAT_004063e0 = ??
Ça me semble différent du code décompilé, mais ça reste du chinois pour moi. En revanche, en recherchant
la constante 0x51eb851f
sur Internet, je découvre que c’est un trick de compilateur,
et en fait une optimisation pour calculer un modulo. D’après l’explication, un SAR EDX,0x5
serait un modulo 100. On a un
shift de 3 au lieu de 5, donc je suppose que ce serait un modulo 25. Pour confirmer, je regarde ce que donne la compilation
du programme suivant sur Compiler Explorer :
#include <time.h>
int foo(int a)
{
return a % 25;
}
On obtient alors :
foo:
push ebp
mov ebp, esp
mov ecx, DWORD PTR [ebp+8]
mov edx, 1374389535
mov eax, ecx
imul edx
sar edx, 3
mov eax, ecx
sar eax, 31
sub edx, eax
mov eax, edx
sal eax, 2
add eax, edx
lea edx, [0+eax*4]
add eax, edx
sub ecx, eax
mov edx, ecx
mov eax, edx
pop ebp
ret
Et c’est exactement le code que présente ghidra ! Celui-ci, en plus, ajoute le code
ASCII du caractère A
à chaque octet de la clef. On a maintenant tous les éléments
pour tenter de reconstruire la clef. On se fait un petit programme en C :
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
srand(1586805493);
for(unsigned int i=0; i<0x32; i++) {
int r = rand();
r = r%25;
printf("%d,", r);
}
printf("\n");
}
❯ gcc -Wall rand.c -o rand && ./rand
16,9,18,18,2,18,0,22,14,4,13,0,3,18,13,0,5,16,8,22,18,16,12,19,4,11,6,15,14,4,6,5,13,24,0,18,18,0,17,7,5,5,7,10,24,22,10,6,15,20,
Mais en fait, ça ne marchera pas. En effet, l’implémentation de rand()
est différente entre Windows et Linux. Heureusement,
stackoverflow vient encore à notre secours en fournissant un équivalent de l’implémentation Windows :
#include <stdio.h>
int random_seed = 0;
void srand(int seed) {
random_seed = seed;
}
int rand(void) {
random_seed = (random_seed * 214013 + 2531011) & 0xFFFFFFFF;
return (random_seed >> 16) & 0x7FFF;
}
int main(int argc, char **argv) {
srand(1586805493);
for(unsigned int i=0; i<0x32; i++) {
int r = rand();
r = r%25;
printf("%d,", r);
}
printf("\n");
}
❯ gcc -Wall rand.c -o rand && ./rand
22,14,14,0,8,14,12,0,0,11,18,12,20,13,23,8,10,11,13,12,1,10,17,9,5,21,0,3,16,2,5,4,15,18,5,24,24,9,23,10,15,14,15,21,17,22,20,7,19,9,
On est prêt à déchiffrer le fichier ! Que l’on n’a pas sous la main, encore. Et là, déception,
impossible de le trouver avec MemProcFs
. Du coup, on bascule sur Volatility3 :
$ /opt/volatility3/vol.py -f cryptolocker-v2.dmp windows.filescan.FileScan | grep flag
0x3eaec938 100.0\Users\IEUser\Desktop\flag.txt.enc 128
$ /opt/volatility3/vol.py -f cryptolocker-v2.dmp windows.dumpfiles.DumpFiles --physaddr 0x3eaec938
DataSectionObject 0x3eaec938 flag.txt.enc file.0x3eaec938.0x850eb660.DataSectionObject.flag.txt.enc.dat
On a maintenant tout ce qu’il nous faut. Je décide de déchiffrer le flag en utilisant Python, en n’oubliant pas
que tous les octets de la clef ont été augmenté du code ASCII de A
, qu’elle a été renversée et qu’elle doit
être d’une longueur suffisant pour déchiffrer le flag complètement :
>>> encrypted = open('file.0x3eaec938.0x850eb660.DataSectionObject.flag.txt.enc.dat', 'rb').read()
>>> key = reversed([ 22,14,14,0,8,14,12,0,0,11,18,12,20,13,23,8,10,11,13,12,1,10,17,9,5,21,0,3,16,2,5,4,15,18,5,24,24,9,23,10,15,14,15,21,17,22,20,7,19,9 ])
>>> key = [ x+ord('A') for x in key ]
>>> key = key + key
>>> bytes( [ x^y for x,y in zip(encrypted, key) ] )
b'FCSC{93bcf2f427e455685b0580058ba028a0a6f96b42c7336ea13877be5e648aec42}\nQDAVFJRKBMNLKIXNUMSLAAMOIAOOW'
Bingo !