FCSC 2023 Forensics : Ranso mémoire

Résolution d'un challenge d'analyse mémoire et reverse engineering d'un malware.

Dans ma quête d’apprentissage du forensique, je me suis frotté au challenge annuel de cybersécurité français organisé par l’ANSSI. C’était la première fois que je participais à celui-ci, j’y suis donc allé tranquille en résolvant les épreuves qui me feraient apprendre le plus de choses.

Etant totalement nouveau dans le domaine du forensique, j’ai appris sur le tas grâce à ce challenge, et je me suis dit qu’il était bon de partager le challenge que j’ai pris le plus de plaisir à résoudre.

Objectif du challenge

Vous étiez en train de consulter vos belles photos de chats quand patatra, votre fichier super secret sur votre Bureau change d’extension et devient illisible…

Vous faites une capture mémoire pour comprendre ce qu’il s’est passé, dans le but de récupérer ce précieux fichier.

Selon l’intitulé, nous avons visiblement affaire à un ransomware qui aurait chiffré un fichier précis sur le bureau.

Grace à la capture mémoire fournie, nous allons remonter cette piste et trouver un moyen de récupérer ce fichier.

Trouver le malware

La première étape consiste à trouver le malware dans la capture mémoire. Nous allons analyser celle-ci grâce à l’outil d’analyse mémoire volatility3.

J’ai commencé par la partie compliquée. J’ai analysé les handles vers le bureau avec l’option windows.handles. pour faire simple, en programmation Windows, un handle est un objet retourné par une fonction permettant d’interagir avec des objets Windows (programmes, fichiers, clés de registre..), quand un processus agit sur un de ces objets, il obtient d’abord un handle sur celui-ci.

Comme nous savons que le processus agit sur le bureau, il est naturel de penser qu’un handle vers le bureau doit être associé à un processus particulier :

1
2
3
4
5
> python vol.py -f ../fcsc.dmp windows.handles | grep File | grep Desktop

1748	CompatTelRunne	0x818688458e50	0xd90	File	0x120089	\Device\HarddiskVolume2\Windows\System32\CatRoot\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\Microsoft-Windows-Client-Desktop-Required-Package01~31bf3856ad364e35~amd64~~10.0.19041.2006.cat
1748	CompatTelRunne	0x818689b0fca0	0xfb0	File	0x120089	\Device\HarddiskVolume2\Windows\System32\CatRoot\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\Microsoft-Windows-Client-Desktop-Required-Package051021~31bf3856ad364e35~amd64~~10.0.19041.2006.cat
5540	svchost.exe	0x818689b8f590	0x218	File	0x100001	\Device\HarddiskVolume2\Users\Admin\Desktop

Le seul processus “non système” ayant un handle sur le bureau est svchost.exe avec un PID de 5540.

Maintenant, la méthode plus simple :

1
2
3
4
5
6
7
> python vol.py -f ../fcsc.dmp windows.pstree --pid 5540

624	548	winlogon.exe	0x818684cd7080	5	-	1	False	2023-04-16 21:46:21.000000 	N/A
* 3892	624	userinit.exe	0x8186813f5340	0	-	1	False	2023-04-16 21:47:17.000000 	2023-04-16 21:47:42.000000
** 3928	3892	explorer.exe	0x818684aa0340	66	-	1	False	2023-04-16 21:47:17.000000 	N/A
*** 6424	3928	VBoxTray.exe	0x81868852e080	13	-	1	False	2023-04-16 21:47:34.000000 	N/A
**** 5540	6424	svchost.exe	0x818687754080	1	-	1	False	2023-04-17 17:21:18.000000 	N/A

On peut remarquer que svchost.exe a été créé depuis un processus utilisateur (VboxTray) descendant d’explorer.exe, ce qui est suspicieux.

Analyse du malware

Une fois ce processus identifié, il convient de l’extraire de la mémoire pour l’analyser. En effet, la plupart des processus en cours d’exécution lors d’une capture mémoire conservent leur fichier d’exécution à l’intérieur de celle-ci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
_BOOL8 __fastcall write_random_bytes_files(PUCHAR pbBuffer, ULONG cbBuffer)
{
  HANDLE FileW; // rax
  void *v6; // r13
  DWORD NumberOfBytesWritten; // [rsp+4Ch] [rbp-22Ch] BYREF
  WCHAR FileName[276]; // [rsp+50h] [rbp-228h] BYREF

  wsprintfW(FileName, L"C:\\Windows\\Temp\\MsCmdRun%d.log");
  if ( BCryptGenRandom(0i64, pbBuffer, cbBuffer, 2u) )
    return 0i64;
  FileW = CreateFileW(FileName, 0x40000000u, 0, 0i64, 2u, 0x80u, 0i64);
  v6 = FileW;
  if ( FileW == -1i64 )
    return 0i64;
  if ( WriteFile(FileW, pbBuffer, cbBuffer, &NumberOfBytesWritten, 0i64) )
  {
    CloseHandle(v6);
    return NumberOfBytesWritten == cbBuffer;
  }
  else
  {
    CloseHandle(v6);
    return 0i64;
  }
}

Cette première fonction génère 64 bytes aléatoires et les inscrit dans un fichier à chaque exécution. le fichier contenant ces bytes se trouvera dans “C:\Windows\Temp\MsCmdRun%d.log” où %d sera remplacé par le nombre correspondant à l’exécution.

if ( nbytes ) { op_counter = 0i64; do { *(Heap + op_counter) ^= execution_counter ^ *(bytes + op_counter); ++op_counter; } while ( nbytes != op_counter ); v12 = NumberOfBytesRead[0]; } if ( SetFilePointer(FileW, -v12, 0i64, 1u) == -1 || !WriteFile(FileW, Heap, NumberOfBytesRead[0], &NumberOfBytesWritten, 0i64) || NumberOfBytesRead[0] != NumberOfBytesWritten ) { goto LABEL_12; }

Cet extrait de fonction est responsable du chiffrement du fichier. C’est ici que nous comprenons la logique de chiffrement de ce malware. Elle est simple, les bytes générés aléatoirement servent à XORer les bytes du fichier initial, en plus du numéro d’éxecution.

Le programme scanne le bureau en recherche de tout fichier possédant l’exécution .fcsc. Si il n’en trouve pas, il incrémente un compteur et genère de nouveaux bytes aléatoires.

Nous savons que le XOR est une opération réversible. Ainsi, en ayant le compteur d’exécution, le fichier des bytes aléatoire et le fichier chiffré, nous pouvons retrouver le fichier originel.

Reste encore à récupérer ces fichiers..

Récupération des fichiers

Volatility3 contient une commande permettant de lister les fichiers présents en mémoire :

1
2
3
4
> python vol.py -f ../fcsc.dmp windows.filescan | grep MsCmdRun

0x818687921450.0\Windows\Temp\MsCmdRun20.log	216
0x818689b862b0	\Windows\Temp\MsCmdRun19.log	216

Voici les fichiers de bytes aléatoires présents en mémoire. Ce n’est pas suffisant car nous ne savons pas lequel a été utilisé pour chiffrer notre fichier. De plus, la mémoire ne contient pas le fichier chiffré présent sur le bureau.

C’est là qu’entre en jeu la MFT (Master File Table), pour faire simple, dans un système de fichiers NTFS, il existe un fichier recensant les informations de chaque fichier présent sur le disque (taille, dernière modification, adresse dans le disque), permettant ainsi à Windows de le trouver plus facilement. En revanche, cette table stocke également le contenu des fichiers de petite taille, et une bonne partie est disponible en mémoire.

Volatitility3 ne propose pas encore une analyse poussée de la table MFT, il va ainsi falloir se rabattre sur volatility2 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
> ./volatility_2.6_lin64_standalone -f ../fcsc.dmp mftparser>mft.txt

Creation                       Modified                       MFT Altered                    Access Date                    Name/Path
------------------------------ ------------------------------ ------------------------------ ------------------------------ ---------
2023-04-17 17:23:45 UTC+0000 2023-04-17 17:23:50 UTC+0000   2023-04-17 17:23:50 UTC+0000   2023-04-17 17:23:50 UTC+0000   Users\Admin\Desktop\flag.fcsc.enc

$DATA
0000000000: 3b 65 17 19 64 03 71 9f dd 1a 30 ec 37 ba 83 c9   ;e..d.q...0.7...
0000000010: 1b b0 44 c9 8d 05 45 88 ff 41 40 d6 32 e5 61 09   ..D...E..A@.2.a.
0000000020: 5f f2 32 07 44 6a 8d 05 c7 fe 82 2f 22 76 9a 08   _.2.Dj...../"v..
0000000030: 32 28 7a ad ff 90 c8 4d 96 ca 99 54 1c 2c 58 f7   2(z....M...T.,X.
0000000040: 7a 8b e5 c5 5d 51 5a                              z...]QZ

Je trouve dans la table MFT le fichier chiffré. On sait qu’un fichier de bytes aléatoires du nom de MsCmdRun est créé avant d’altérer le fichier flag. Ainsi, je cherche dans cette table un fichier MsCmdRun qui aurait la date de création/modification la plus proche de celle du flag :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$FILE_NAME
Creation                       Modified                       MFT Altered                    Access Date                    Name/Path
------------------------------ ------------------------------ ------------------------------ ------------------------------ ---------
2023-04-17 17:23:50 UTC+0000 2023-04-17 17:23:50 UTC+0000   2023-04-17 17:23:50 UTC+0000   2023-04-17 17:23:50 UTC+0000   Windows\Temp\MsCmdRun14.log

$DATA
0000000000: 73 28 4a 54 11 3a 48 a7 b5 26 0b 86 01 85 bc a5   s(JT.:H..&......
0000000010: 73 87 2b a4 b1 3d 79 b7 c5 7c 2a e8 5a da 09 66   s.+..=y..|*.Z..f
0000000020: 68 cb 5f 3a 72 56 e5 3c ab c5 b5 16 1e 49 a6 37   h._:rV.<.....I.7
0000000030: 5e 15 43 c7 c1 ad f0 72 fb f3 f3 6d 73 46 67 9b   ^.C....r...msFg.
0000000040: 46 b2 db fc 6a 22 5e 89 8e 58 7d 0b 5c e5 4a d8   F...j"^..X}.\.J.
0000000050: 62 58 72 87 ee 36 f5 44 49 55 0f bd c0 00 e1 58   bXr..6.DIU.....X
0000000060: 60 5f 1e 0f                                       `_..

Avec ces deux fichiers, il ne me reste plus qu’à créer un script permettant de déchiffrer le flag.

Au départ, je pensais que le compteur d’exécution s’incrémentait à chaque fichier chiffré. Etant le seul fichier chiffré, je me suis mis en tête que le compteur correspondait à 0. Un XOR par 0 n’ayant pas d’intérêt, j’ai omis cette opération :

1
HM]Mu998h<;j6??lh7om<8<?:=j>h?ho79m=6<h9l;79<?<?l=9j>=8?m9j9oj?l<9>97s

Le flag étant encore chiffré, j’ai cependant remarqué que le début de celui-ci m’était semblable, en effet, les flags de cette compétition commençaient par FCSC, on remarque la présence de 2 M correspondant aux deux C de l’entête du flag. Il me restait donc à trouver la valeur qui me permettrait d’obtenir C en XORant M par plusieurs valeurs. C’est la faiblesse des algorithmes XOR lorsqu’un morceau de la chaîne initiale est connue, il est possible de bruteforcer celle-ci afin d’obtenir la chaîne finale.

La valeur 14 permettait d’afficher le flag. C’est ainsi que j’ai remarqué l’importance du compteur d’exécution, et que celui-ci était inscrit dans le fichier de bytes aléatoires (MsCmdRun14)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ms14 = [0x73, 0x28, 0x4a, 0x54, 0x11, 0x3a, 0x48, 0xa7, 0xb5, 0x26, 0x0b, 0x86, 0x01, 0x85, 0xbc, 0xa5,
0x73, 0x87, 0x2b, 0xa4, 0xb1, 0x3d, 0x79, 0xb7, 0xc5, 0x7c, 0x2a, 0xe8, 0x5a, 0xda, 0x09, 0x66,
0x68, 0xcb, 0x5f, 0x3a, 0x72, 0x56, 0xe5, 0x3c, 0xab, 0xc5, 0xb5, 0x16, 0x1e, 0x49, 0xa6, 0x37,
0x5e, 0x15, 0x43, 0xc7, 0xc1, 0xad, 0xf0, 0x72, 0xfb, 0xf3, 0xf3, 0x6d, 0x73, 0x46, 0x67, 0x9b,
0x46, 0xb2, 0xdb, 0xfc, 0x6a, 0x22, 0x5e, 0x89, 0x8e, 0x58, 0x7d, 0x0b, 0x5c, 0xe5, 0x4a, 0xd8,
0x62, 0x58, 0x72, 0x87, 0xee, 0x36, 0xf5, 0x44, 0x49, 0x55, 0x0f, 0xbd, 0xc0, 0x00, 0xe1, 0x58,
0x60, 0x5f, 0x1e, 0x0f, 0x73, 0x28, 0x4a]

fcsc = [0x3b, 0x65, 0x17, 0x19, 0x64, 0x03, 0x71, 0x9f, 0xdd, 0x1a, 0x30, 0xec, 0x37, 0xba, 0x83, 0xc9, 
0x1b, 0xb0, 0x44, 0xc9, 0x8d, 0x05, 0x45, 0x88, 0xff, 0x41, 0x40, 0xd6, 0x32, 0xe5, 0x61, 0x09, 
0x5f, 0xf2, 0x32, 0x07, 0x44, 0x6a, 0x8d, 0x05, 0xc7, 0xfe, 0x82, 0x2f, 0x22, 0x76, 0x9a, 0x08, 
0x32, 0x28, 0x7a, 0xad, 0xff, 0x90, 0xc8, 0x4d, 0x96, 0xca, 0x99, 0x54, 0x1c, 0x2c, 0x58, 0xf7, 
0x7a, 0x8b, 0xe5, 0xc5, 0x5d, 0x51, 0x5a]


flaglist = []
i = 0


while i < len(fcsc):
    flaglist.append((fcsc[i] ^ ms14[i]) ^ 14)
    i += 1
    
print(''.join(chr(i) for i in flaglist))

FCSC{776f25d811bf9ac262143d0f1fa97c382f7b5972121b37d0361c7d7ad1b27079}

Conclusion

Ainsi s’achève cette épreuve de forensics. Elle était très intéressante et m’en a fait apprendre énormément sur l’analyse mémoire et d’autres internals de Windows. Mes compétences en reverse engineering m’ont permis de passer aisément l’épreuve d’analyse du malware, là où d’autres ont sûrement échoué dû à ce manque de compétences.

comments powered by Disqus
Généré avec Hugo
Thème Stack conçu par Jimmy