Solution de hashp4 pour 3615 Incident 2/3

forensics memory linux

7 décembre 2023

Introduction đź“Ś

Le contexte reste le même que lors de la première partie. Une victime de plus est tombée sous le coup d’un rançongiciel. Le paiement de la rançon n’est pas envisagée vu le montant demandé. Nous sommes donc appelés pour essayer de restaurer les fichiers chiffrés.

Cette fois-ci, l’objectif est de retrouver la clĂ© de chiffrement de ce rançongiciel !

Note : RĂ©ponse attendue au format ECSC{hey.hex()}.

(La rĂ©solution de l’Ă©preuve s’effectue toujours Ă  l’aide du fichier mem.dmp.tar.xz. Pour rappel, il s’agit d’une image mĂ©moire de l’ordinateur de la victime. Concrètement, celle-ci correspond au contenu de la mĂ©moire volatile (RAM) au moment de l’acquisition et nous allons voir que cela se rĂ©vèle ĂŞtre une excellente source d’informations pour de l’investigation numĂ©rique.

1. Analyse du code source du rançongiciel 🔬

Suite Ă  notre analyse lors de la première partie, nous avons la chance de disposer du code source de ce rançongiciel. Pour rappel, il se trouve ici : https://github.com/mauri870/ransomware. Et heureusement, car nous n’avions pas tellement envie de reverse du Go, n’est-ce pas ? :D

Un zoom sur ransomware.go

Commençons par trouver la manière dont la clĂ© est gĂ©nĂ©rĂ©e. La fonction encryptFile() dans le fichier cmd/ransomware/ransomware.go, Ă  la ligne 112 nous permet d’en apprendre un peu plus.

func encryptFiles() {
	keys := make(map[string]string)
	[...]
		// Generate the id and encryption key
		keys["id"], _ = utils.GenerateRandomANString(32)
		keys["enckey"], _ = utils.GenerateRandomANString(32)

		// Persist the key pair on server
		res, err := Client.AddNewKeyPair(keys["id"], keys["enckey"])
	[...]

Nous constatons 2 choses :

  • La gĂ©nĂ©ration d’un id Ă  l’aide de la fonction GenerateRandomANString() situĂ©e dans le fichier utils et ayant pour paramètre 32.
  • La gĂ©nĂ©ration de enckey Ă  l’aide de la fonction GenerateRandomANString() situĂ©e dans le fichier utils et ayant pour paramètre 32.

Ensuite, ce couple id et enckey est envoyĂ© sur un serveur Ă  l’aide de la fonction AddNewKeyPair() situĂ©e dans le fichier client.

Nous pouvons supposer que l’id et la clĂ© de chiffrement enckey (qui est sĂ»rement le diminutif de encrypted key) sont tout deux une chaĂ®ne de 32 caractères gĂ©nĂ©rĂ©s alĂ©atoirement. Pour en ĂŞtre sĂ»r, nous pouvons Ă©tudier la fonction correspondante.

Un zoom sur utils.go

Ici, ce qui nous intĂ©resse c’est la fonction GenerateRandomANString(), Ă  la ligne 13 de utils/utils.go.

// Generate a random alphanumeric string with the given size
func GenerateRandomANString(size int) (string, error) {
	key := make([]byte, size)
	_, err := rand.Read(key)
	if err != nil {
		return "", err
	}

	return hex.EncodeToString(key)[:size], nil
}

La fonction est plutĂ´t simple. Elle prend un entier en paramètre et gĂ©nère une chaĂ®ne de caractère alĂ©atoire de la mĂŞme taille. Celle-ci est ensuite encodĂ©e en hexadĂ©cimal Ă  l’aide de la fonction hex.EncodeToString() avant d’ĂŞtre retournĂ©e.

Notre hypothèse est donc confirmĂ©e. Nous sommes bien Ă  la recherche d’une chaĂ®ne de 32 caractères et nous savons qu’ils sont hexadĂ©cimaux. Mais nous n’avons encore aucune piste sur le moyen de rĂ©cupĂ©rer cette fameuse clĂ©…

Un zoom sur client.go

Regardons de plus près la fonction AddNewKeyPair() dans client/client.go, ligne 90.

// AddNewKeyPair persist a new keypair on server
func (c *Client) AddNewKeyPair(id, encKey string) (*http.Response, error) {
	payload := fmt.Sprintf(`{"id": "%s", "enckey": "%s"}`, id, encKey)
	return c.SendEncryptedPayload("/api/keys/add", payload, map[string]string{})
}

Cette fonction prends bien en paramètre un id et une clé de chiffrement enckey. Elle va ensuite mettre en forme la charge utile (payload en anglais) sous la forme :

{
    "id": "l'id associé",
    "enckey": "la clé de chiffrement"
}

Puis, il sera chiffrĂ© et envoyĂ© au serveur de l’attaquant par le biais d’une API Ă  travers l’URI /api/key/add via la fonction SendEncryptedPayload() (je vous laisse le soin d’aller regarder cette fonction plus en dĂ©tail si cela vous intĂ©resse). Il est donc intĂ©ressant de se concentrer sur l’envoi de cette charge utile pour retrouver la clĂ©. Il reste peut ĂŞtre encore des traces dans le dump mĂ©moire !

NOTE : Vous vous demandez peut-ĂŞtre Ă  quoi sert la prĂ©sence d’id ? En règle gĂ©nĂ©rale, les opĂ©rateurs de rançongiciels n’ont pas qu’une seule victime. Chacune d’entre-elles possède une clĂ© de chiffrement unique et il faut ĂŞtre en mesure des les identifier pour ĂŞtre en mesure de dĂ©chiffrer les donnĂ©es en cas de paiement de la rançon. De ce fait, un identifiant unique est attribuĂ© Ă  chaque victime et est associĂ© Ă  la bonne clĂ© de chiffrement. Attention, cela s’applique uniquement s’il y a une volontĂ© de l’attaquant Ă  dĂ©chiffrer les donnĂ©es. Il n’y a absolument aucune garantie qu’il respecte sa part du marchĂ©…

En rĂ©sumĂ©…

Bien, il est grand temps de faire le résumé de notre analyse. Ce que nous savons à présent :

  • L’identifiant unique associĂ© Ă  la clĂ© de chiffrement est une suite alĂ©atoire de 32 caractères hexadĂ©cimaux.
  • La clĂ© de chiffrement est Ă©galement une suite alĂ©atoire de 32 caractères hexadĂ©cimaux.
  • Ceux-ci sont envoyĂ©s au serveur de l’attaquant Ă  travers les paramètres respectifs id et enckey et mis en forme avec le modèle suivant : {"id": "l'id", "enckey": "la clĂ©"}.

Grâce Ă  ces informations, nous pouvons passer Ă  l’extraction de cette fameuse clĂ© de chiffrement.

2. Extraction de la clé de chiffrement 🗝

Nous devons trouver au sein du dump mĂ©moire la clĂ© de chiffrement. Pour ce faire, nous pouvons nous appuyer sur le modèle (ou pattern en anglais) d’envoi de la charge utile contenant id et enckey au serveur de l’attaquant. Quoi de mieux que grep pour retrouver un pattern dans un fichier ? :)

grep for the win

grep est un outil très intĂ©ressant quand il s’agit de rĂ©pĂ©rer des pattern au sein d’un fichier. Ici, nous allons lui ajouter plusieurs options. La commande finale est la suivante : grep -B 3 -A 3 -wE '{"id": "\w{32}", "enckey":'. DĂ©taillons un peu plus ces options :

  • L’option -B 3 permet d’afficher 3 lignes avant la correspondance.
  • L’option -A 3 permet d’afficher 3 lignes après la correspondance.
  • L’option -wE permet d’une part de n’afficher que les rĂ©sultats correspondant exactement au pattern spĂ©cifiĂ© et d’autre part d’activer les expressions rĂ©gulières Ă©tendues..

DĂ©tail de l’expression rĂ©gulière Ă©tendue {"id": "\w{32}", "enckey": :

  • \w{32} : permet de faire correspondre une chaĂ®ne de 32 caractères alphanumĂ©riques (avec le caractère _ en supplĂ©ment). C’est l’Ă©quivalent de [a-zA-Z0-9_]

Maintenant que notre grep est prĂŞt, nous pouvons l’utiliser sur le dump mĂ©moire :

┌──(hashp4㉿kali)-[~/Bureau]
└─$ strings mem.dmp | grep -B 3 -A 3 -wE '{"id": "\w{32}", "enckey":'
"C:\Users\TNKLSAI3TGT7O9\Downloads\assistance.exe" 
C:\Users\TNKLSAI3TGT7O9\Downloads\assistance.exe
S-1-5-21-2377780471-3200203716-3353778491-1000
{"id": "cd18c00bb476764220d05121867d62de", "enckey": "
cd18c00bb476764220d05121867d62de64e0821c53c7d161099be2188b6cac24cd18c00bb476764220d05121867d62de64e0821c53c7d161099be2188b6cac2495511870061fb3a2899aa6b2dc9838aa422d81e7e1c2aa46aa51405c13fed15b95511870061fb3a2899aa6b2dc9838aa422d81e7e1c2aa46aa51405c13fed15b
Encrypting C:\Users\Administrateur\Contacts\desktop.ini...
C:\Users\TNKLSA~1\AppData\Local\Temp\desktop.ini

Il se trouve que nous avons belle et bien une correspondance !

{
    "id": "cd18c00bb476764220d05121867d62de", 
    "enckey": "cd18c00bb476764220d05121867d62de64e0821c53c7d161099be2188b6cac24cd18c00bb476764220d05121867d62de64e0821c53c7d161099be2188b6cac2495511870061fb3a2899aa6b2dc9838aa422d81e7e1c2aa46aa51405c13fed15b95511870061fb3a2899aa6b2dc9838aa422d81e7e1c2aa46aa51405c13fed15b

Ici, l’id a donc pour valeur cd18c00bb476764220d05121867d62de. Cependant, la clĂ© de chiffrement est bien plus grande que prĂ©vu. Nous devons donc dĂ©couper celle-ci en paquet de 32 caractères. Pour ce faire, nous enregistrons manuellement la clĂ© dans un fichier key.txt. Puis, nous pouvons utiliser l’utilitaire fold Ă  l’aide de l’option -b32 pour dĂ©couper notre chaĂ®ne de caractère :

┌──(hashp4㉿kali)-[~/Bureau]
└─$ fold -b32 key.txt | sort | uniq
422d81e7e1c2aa46aa51405c13fed15b
64e0821c53c7d161099be2188b6cac24
95511870061fb3a2899aa6b2dc9838aa
cd18c00bb476764220d05121867d62de

(L’utilisation de sort et uniq permet simplement d’enlever les paquets de 32 identiques).

Nous nous retrouvons avec 4 candidats potentiels. Cela dit, nous remarquons que la dernière clĂ© est en fait identique Ă  l’id ! Il ne nous reste que 3 clĂ©s Ă  tester. ;)

Test des clés

Dans des conditions rĂ©elles, nous devrions analyser le code du rançongiciel, prendre connaissance de l’algorithme de chiffrement utilisĂ©, dump un fichier chiffrĂ© depuis l’image mĂ©moire et tenter de le dĂ©chiffrer avec chacune des clĂ©s potentielles. Cependant, dans le cadre de ce writeup, cela reviendrait Ă  donner la solution pour la dernière Ă©tape de cette Ă©preuve…

De ce fait, et compte-tenu de la faible proportion de clĂ© Ă  Ă©valuer, il nous suffit de les tester une par une en tant que flag jusqu’Ă  que celui-ci sois valide.

  • ❌ ECSC{422d81e7e1c2aa46aa51405c13fed15b}
  • ❌ ECSC{64e0821c53c7d161099be2188b6cac24}
  • âś… ECSC{95511870061fb3a2899aa6b2dc9838aa}

3. Flag đźš©

Grâce à notre analyse (et un trèèèèès léger bruteforce), nous obtenons le flag suivant : ECSC{95511870061fb3a2899aa6b2dc9838aa}

Cela marque la conclusion de cette seconde partie du challenge 3615 Incident. Dans un premier temps, nous avons pu voir comment Ă©tait gĂ©nĂ©rĂ© la clĂ© de chiffrement et par quel moyen celle-ci Ă©tait transmise Ă  l’attaquant en Ă©tudiant le code source du rançongiciel. Dans un second temps, nous avons retrouvĂ© celle associĂ©e Ă  notre victime Ă  l’aide d’une recherche de pattern dans l’image mĂ©moire. Il ne vous reste plus que quelques efforts supplĂ©mentaires pour rĂ©ussir la dernière Ă©tape de ce challenge. ;)