Du 24 octobre au 11 novembre 2020, la DGSE et ESIEE Paris organisaient le challenge/CTF Brigitte Friang.

Cette année, “Brigitte Friang”, résistante et membre du BCRA, les services de renseignement de la France Libre de la Seconde Guerre mondiale est mise à l’honneur.


En résumé, le challenge était composé de trois parties :

Une première consistant à décoder un message depuis la première page du challenge: challengecybersec.fr. Une fois cette étape passée, nous arrivions sur un “chat” composé de plusieurs agents nous donnant des missions de plusieurs disciplines (algo, web, cryptographie, forensic). Nous devions résoudre une de ces catégories pour enfin accéder à la troisième partie. Qui était finalement une plateforme de CTF classique en équipe.

Nous allons voir dans cet article la manière dont j’ai résolu la track forensic de la deuxième partie du challenge. Qui est, je trouve, assez intéressante pour être détaillée et partagée.

Elle constitue une très bonne introduction au domaine du Forensic Avec un cas plutôt réaliste, un “vrai” malware et un peu de reverse engineering!

C’est parti!

Nous commençons donc directement à la deuxième partie du challenge, le chat avec les différentes catégories.

screenshot-chat

Acceptons la mission du cher monsieur Bertillon !

screenshot-chat

Logs Nginx

Faisons une simple recherche avec grep sur le nom “Evil” de “EvilCorp”


$ grep "Evil" access.log
179.97.58.61 - - [Nov 05 2020 16:22:20] "POST /login HTTP/1.1" 200 476 "-" "Evil Browser"
screenshot-chat

Bon…

Steganographie

Passons aux choses sérieuses. Nous obtenons une image.

screenshot-chat

Très jolie… Mais un peu lourde pour sa taille en regardant de plus près avec file et ls


$ file evil_country_landscape.jpg && ls -lh evil_country_landscape.jpg
evil_country_landscape.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, progressive, precision 8, 640x436, components 3
-rw-r--r-- 1 nirae nirae 303M 26 oct.  15:37 evil_country_landscape.jpg

Une image de 640x436 pesant 303 Mo. Elle contient surement des fichiers cachés à l’intérieur.

Nous pouvons le vérifier avec l’outil binwalk


$ binwalk evil_country_landscape.jpg

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
79798         0x137B6         Zip archive data, at least v2.0 to extract, uncompressed size: 173015040, name: part2.img
4775856       0x48DFB0        Zlib compressed data, best compression
^[[C34737670      0x2120E06       MySQL MISAM compressed data file Version 1
56164092      0x358FEFC       IMG0 (VxWorks) header, size: 257308484
128298187     0x7A5ACCB       Cisco IOS microcode, for "w%"
158637081     0x9749C19       Zip archive data, at least v2.0 to extract, uncompressed size: 173015040, name: part3.img
239002530     0xE3EE3A2       Zlib compressed data, best compression
317286906     0x12E969FA      End of Zip archive, footer length: 22

Effectivement elle contient bien d’autres fichiers, dont des archives ZIP. Binwalk nous permets de les extraire.


$ binwalk -eM evil_country_landscape.jpg

Scan Time:     2020-10-26 11:07:46
Target File:   /home/nirae/Lab/brigitte-friang/evil_country_landscape.jpg
MD5 Checksum:  8c9bb1dddadd9f04df78cbe8d44be4fe
Signatures:    391

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
79798         0x137B6         Zip archive data, at least v2.0 to extract, uncompressed size: 173015040, name: part2.img
4775856       0x48DFB0        Zlib compressed data, best compression
34737670      0x2120E06       MySQL MISAM compressed data file Version 1
56164092      0x358FEFC       IMG0 (VxWorks) header, size: 257308484
128298187     0x7A5ACCB       Cisco IOS microcode, for "w%"
158637081     0x9749C19       Zip archive data, at least v2.0 to extract, uncompressed size: 173015040, name: part3.img
239002530     0xE3EE3A2       Zlib compressed data, best compression
317286906     0x12E969FA      End of Zip archive, footer length: 22


Scan Time:     2020-10-26 11:08:51
Target File:   /home/nirae/Lab/brigitte-friang/_evil_country_landscape.jpg.extracted/part2.img
MD5 Checksum:  0eb69c5169184ab37d478dce45e4ed55
Signatures:    391

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
3694592       0x386000        Linux EXT filesystem, blocks count: 83456, image size: 85458944, rev 1.0, ext3 filesystem data, UUID=bcb580b1-a10c-4c29-9cab-833f13f213f2
142617411     0x8802B43       Cisco IOS microcode, for "w%"
159703094     0x984E036       MySQL MISAM compressed data file Version 8


Scan Time:     2020-10-26 11:09:10
Target File:   /home/nirae/Lab/brigitte-friang/_evil_country_landscape.jpg.extracted/part3.img
MD5 Checksum:  00f1ff95393315894f605c1ff94b8ee6
Signatures:    391

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------

RAID

Nous avons donc obtenus deux fichiers intéressants : “part2.img” et “part3.img”. Observons les.


$ file part2.img && file part3.img 
part2.img: Linux Software RAID version 1.2 (1) UUID=dfaa645a:19afec72:60f1fa33:30d841da name=user-XPS-15-9570:6 level=5 disks=3
part3.img: Linux Software RAID version 1.2 (1) UUID=dfaa645a:19afec72:60f1fa33:30d841da name=user-XPS-15-9570:6 level=5 disks=3

Ce sont en fait deux images de disques RAID Linux. On apprend aussi qu’il s’agit de RAID 5, avec 3 disques. Et nous en avons visiblement 2.

Voyons ce que Wikipédia a à nous dire:

Le RAID est un ensemble de techniques de virtualisation du stockage permettant de répartir des données sur plusieurs disques durs afin d’améliorer soit les performances, soit la sécurité ou la tolérance aux pannes de l’ensemble du ou des systèmes.

Le principe du RAID5 est de fonctionner avec au moins 3 disques et justement pouvoir récupérer les données si un des disques tombe en panne ou manque. C’est notre cas ici.

Nous allons donc reconstruire le RAID pour accéder aux données qui y sont stockées. Je vais faire ça dans une machine virtuelle Debian, avec Vagrant.

On va d’abord monter les fichiers images sur des fichiers loop avec losetup pour les passer à mdadm


$ sudo losetup /dev/loop0 part2.img
$ sudo losetup /dev/loop1 part3.img

Maintenant, grâce à mdadm, nous allons pouvoir recréer le RAID logiciel tel qu’il était avant la perte d’un des disques.


$ sudo mdadm --assemble /dev/md0 /dev/loop0 /dev/loop1

Les données sont maintenant ré-assemblées dans le volume RAID /dev/md0, il ne reste plus qu’a le monter.


$ sudo mkdir /mnt/recover
$ sudo mount /dev/md0 /mnt/recover
$ ls /mnt/recover
dump.zip  lost+found

Et voilà ! Nous avons pu récupérer le fichier dump.zip.

Forensic

Après avoir décompressé le fichier trouvé, nous obtenons un fichier dump.vmem. Regardons s’il s’agit d’un dump mémoire avec Volatility.

J’utilise personnellement une version Docker de Volatility mais c’est évidemment la même chose avec la version classique.

Pour savoir si c’est un dump mémoire, nous pouvons lancer un imageinfo afin d’en connaitre le type si c’est le cas


$ docker run --rm -v $(pwd):/data -ti cincan/volatility imageinfo -f /data/dump.vmem                   !
Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_24000, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_24000, Win7SP1x64_23418
                     AS Layer1 : WindowsAMD64PagedMemory (Kernel AS)
                     AS Layer2 : FileAddressSpace (/data/dump.vmem)
                      PAE type : No PAE
                           DTB : 0x187000L
                          KDBG : 0xf80002c4c0a0L
          Number of Processors : 1
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0xfffff80002c4dd00L
             KUSER_SHARED_DATA : 0xfffff78000000000L
           Image date and time : 2020-10-05 11:17:37 UTC+0000
     Image local date and time : 2020-10-05 13:17:37 +0200

Volatility nous indique que c’est effectivement un dump, probablement un Windows 7 (Win7SP1x64)

On regarde les fichiers présents et au bout de quelques recherches simples avec grep, on tombe sur des fichiers avec l’extension .evil


$ volatility -f data/dump.vmem --profile=Win7SP1x64 filescan | grep evil
Volatility Foundation Volatility Framework 2.6
0x000000001715ed50     16      0 R--r-- \Device\HarddiskVolume1\Users\user\Documents\informations_attaque.txt.evil
0x000000003fa3ebc0      2      0 RW-r-- \Device\HarddiskVolume1\ProgramData\Microsoft\RAC\PublishedData\RacWmiDatabase.sdf.evil
0x000000003fac8d10     32      0 RW-r-- \Device\HarddiskVolume1\ProgramData\Microsoft\Windows\WER\ReportQueue\NonCritical_Firefox_installe_d514681bfc376345742b2157ace1e72c17fd991_cab_0938b7ba\appcompat.txt.evil
0x000000003fad8620     16      0 RW-r-- \Device\HarddiskVolume1\Users\user\AppData\Local\Microsoft\Windows\Caches\{AFBF9F1A-8EE8-4C77-AF34-C647E37CA0D9}.1.ver0x0000000000000002.db.evil

On peut donc supposer que la machine a été infectée par un virus, qui a chiffré certains fichiers. Partons à sa recherche en listant l’arbre des processus en cours


$ volatility -f /data/dump.vmem --profile=Win7SP1x64 pstree
Volatility Foundation Volatility Framework 2.6.1
Name                                                  Pid   PPid   Thds   Hnds Time
-------------------------------------------------- ------ ------ ------ ------ ----
 0xfffffa8002816060:wininit.exe                       404    336      3     74 2020-10-05 11:13:42 UTC+0000
. 0xfffffa8002a007c0:lsass.exe                        512    404      7    565 2020-10-05 11:13:42 UTC+0000
. 0xfffffa8002a0db30:lsm.exe                          520    404     10    145 2020-10-05 11:13:42 UTC+0000
. 0xfffffa80029f76d0:services.exe                     504    404      8    220 2020-10-05 11:13:42 UTC+0000
.. 0xfffffa8002b6e430:VGAuthService.                 1560    504      4     84 2020-10-05 11:13:46 UTC+0000
.. 0xfffffa8002d2ab30:svchost.exe                    1304    504     20    325 2020-10-05 11:13:45 UTC+0000
.. 0xfffffa8002b8ab30:svchost.exe                     920    504     48    979 2020-10-05 11:13:43 UTC+0000
.. 0xfffffa8002a0fb30:svchost.exe                     644    504     10    353 2020-10-05 11:13:43 UTC+0000
... 0xfffffa8001ca9060:WmiPrvSE.exe                  3032    644     15    330 2020-10-05 11:14:07 UTC+0000
... 0xfffffa8002f1f060:WmiPrvSE.exe                  1892    644     10    193 2020-10-05 11:13:47 UTC+0000
.. 0xfffffa8000e78920:taskhost.exe                   2464    504      6     88 2020-10-05 11:17:08 UTC+0000
.. 0xfffffa8002bf8060:svchost.exe                     304    504     20    391 2020-10-05 11:13:44 UTC+0000
.. 0xfffffa8002cc6310:spoolsv.exe                    1156    504     15    270 2020-10-05 11:13:44 UTC+0000
.. 0xfffffa80031d5790:sppsvc.exe                     2844    504      6    156 2020-10-05 11:16:54 UTC+0000
.. 0xfffffa8001e6ab30:dllhost.exe                    1832    504     20    186 2020-10-05 11:13:47 UTC+0000
.. 0xfffffa8003021690:VSSVC.exe                      2076    504      6    109 2020-10-05 11:13:49 UTC+0000
.. 0xfffffa8002d11a60:taskhost.exe                   1216    504     10    204 2020-10-05 11:13:44 UTC+0000
.. 0xfffffa8002affb30:svchost.exe                     708    504      6    276 2020-10-05 11:13:43 UTC+0000
.. 0xfffffa80030d4770:svchost.exe                    2372    504     23    258 2020-10-05 11:13:51 UTC+0000
.. 0xfffffa8002b6f2c0:vmtoolsd.exe                   1584    504     12    292 2020-10-05 11:13:46 UTC+0000
... 0xfffffa800117db30:cmd.exe                       1744   1584      0 ------ 2020-10-05 11:17:37 UTC+0000
.... 0xfffffa8001116060:ipconfig.exe                 2832   1744      0 ------ 2020-10-05 11:17:37 UTC+0000
.. 0xfffffa801bbf6b30:svchost.exe                    2276    504     12    327 2020-10-05 11:16:54 UTC+0000
.. 0xfffffa8002fd4b30:wmpnetwk.exe                   2296    504     11    213 2020-10-05 11:13:50 UTC+0000
.. 0xfffffa8002ef2b30:dllhost.exe                    2008    504     17    195 2020-10-05 11:13:48 UTC+0000
.. 0xfffffa8002fc9560:msdtc.exe                       872    504     15    155 2020-10-05 11:13:49 UTC+0000
.. 0xfffffa800107c6a0:WmiApSrv.exe                   2632    504      7    119 2020-10-05 11:17:18 UTC+0000
.. 0xfffffa8002c4bb30:SearchIndexer.                 2156    504     12    557 2020-10-05 11:13:50 UTC+0000
... 0xfffffa80030e4750:SearchProtocol                2400   2156      7    273 2020-10-05 11:13:51 UTC+0000
... 0xfffffa80030e7b30:SearchFilterHo                2420   2156      4     86 2020-10-05 11:13:51 UTC+0000
.. 0xfffffa8002b28b30:svchost.exe                     880    504     16    320 2020-10-05 11:13:43 UTC+0000
... 0xfffffa8002c6b500:dwm.exe                       1072    880      6    124 2020-10-05 11:13:44 UTC+0000
.. 0xfffffa8002bd4060:svchost.exe                     244    504     26    720 2020-10-05 11:13:44 UTC+0000
.. 0xfffffa8002b1f4a0:svchost.exe                     760    504     23    514 2020-10-05 11:13:43 UTC+0000
... 0xfffffa8002bb4b30:audiodg.exe                    980    760      7    128 2020-10-05 11:13:44 UTC+0000
 0xfffffa8002810060:csrss.exe                         352    336      8    517 2020-10-05 11:13:42 UTC+0000
. 0xfffffa8002161630:conhost.exe                     2928    352      0 ------ 2020-10-05 11:17:37 UTC+0000
 0xfffffa8000e91b30:drpbx.exe                        2304   2916      8    149 2020-10-05 11:17:01 UTC+0000
 0xfffffa8000cc5b30:System                              4      0     87    393 2020-10-05 11:13:41 UTC+0000
. 0xfffffa800bad5480:smss.exe                         264      4      2     29 2020-10-05 11:13:41 UTC+0000
 0xfffffa8002c6fb30:explorer.exe                     1084   1064     32    828 2020-10-05 11:13:44 UTC+0000
. 0xfffffa8001072060:notepad.exe                     1880   1084      1     62 2020-10-05 11:17:36 UTC+0000
. 0xfffffa8002d27b30:vmtoolsd.exe                    1248   1084      8    166 2020-10-05 11:13:44 UTC+0000
. 0xfffffa8002d234f0:vm3dservice.ex                  1240   1084      3     39 2020-10-05 11:13:44 UTC+0000
 0xfffffa80111d32e0:csrss.exe                         412    396      9    209 2020-10-05 11:13:42 UTC+0000
 0xfffffa80029c2910:winlogon.exe                      460    396      4    110 2020-10-05 11:13:42 UTC+0000

Après un peu de temps à analyser les processus, il y en a un qui parait intéressant, drpbx.exe. Un processus qui tenterait de se faire passer pour Dropbox ?

Notre moteur de recherche préféré (ou pas forcément) nous indiquera qu’il s’agit bien d’un virus connu, Jigsaw.

Jigsaw est un ransomware (rançongiciel) très connu car très actif à partir de 2016. Il est écrit en .NET et il existe plusieurs versions différentes dans la nature. Mais qui fonctionne de la même manière, il va se faire passer pour Firefox et/ou Dropbox, chiffrer les fichiers utilisateurs (fichiers textes, images, zip, etc) en les renommant avec une extension différente suivant les versions. L’utilisateur se retrouve donc sans fichiers et avec une interface demandant de payer une certaine somme en Bitcoin pour les déchiffrer.

Au vu de l’extension .evil, on sait qu’il s’agit d’une version modifiée du malware car elle ne fait pas partie des extensions des versions connues. On va donc devoir l’analyser nous-même.

Pour l’extraire:


$ volatility -f /samples/dump.vmem --profile=Win7SP1x64 procdump --dump-dir /samples/out -p 2304
Volatility Foundation Volatility Framework 2.6.1
Process(V)         ImageBase          Name                 Result
------------------ ------------------ -------------------- ------
0xfffffa8000e91b30 0x0000000000870000 drpbx.exe            OK: executable.2304.exe

Update 02-12-2020:

Le virus n’a pas mis longtemps avant de tomber entre les mains de quelques chercheurs qui l’ont lancé dans des sandboxes. Laissant apparaitre son interface.

screenshot-sawseeson

On reconnait bien la french touch grâce au nom “Saw See Son”!


Reverse Engineering

Nous allons maintenant dé-compiler le malware pour voir son code. Nous savons qu’il est écrit en .NET, donc nous allons utiliser ILSpy. Pour ma part, je vais (encore) utiliser cette version Docker de ILSpy.


$ docker run -v $(pwd):/samples cincan/ilspy /samples/executable.2304.exe

Je vous passe le code entier, pour des raisons évidentes, mais voyons la partie qui nous intéresse, les fonctions et variables de chiffrement/déchiffrement.


....
internal const string EncryptionPassword = "RXZpbERlZmF1bHRQYXNzIQ==";
....
private static void DecryptFile(string path, string encryptionExtension)
{
        try
        {
                if (!path.EndsWith(encryptionExtension))
                {
                        return;
                }
                string outputFile = path.Remove(path.Length - 4);
                using (AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider())
                {
                        aesCryptoServiceProvider.Key = Convert.FromBase64String("RXZpbERlZmF1bHRQYXNzIQ==");
                        aesCryptoServiceProvider.IV = new byte[16]
                        {
                                0,
                                1,
                                0,
                                3,
                                5,
                                3,
                                0,
                                1,
                                0,
                                0,
                                2,
                                0,
                                6,
                                7,
                                6,
                                0
                        };
                        DecryptFile(aesCryptoServiceProvider, path, outputFile);
                }
        }
        catch
        {
                return;
        }
        try
        {
                File.Delete(path);
        }
        catch (Exception)
        {
        }
}
....
private static void EncryptFile(SymmetricAlgorithm alg, string inputFile, string outputFile)
{
        byte[] array = new byte[65536];
        using (FileStream fileStream = new FileStream(inputFile, FileMode.Open))
        {
                using (FileStream stream = new FileStream(outputFile, FileMode.Create))
                {
                        using (CryptoStream cryptoStream = new CryptoStream(stream, alg.CreateEncryptor(), CryptoStreamMode.Write))
                        {
                                int num;
                                do
                                {
                                        num = fileStream.Read(array, 0, array.Length);
                                        if (num != 0)
                                        {
                                                cryptoStream.Write(array, 0, num);
                                        }
                                }
                                while (num != 0);
                        }
                }
        }
}
....

On peut donc voir que c’est un chiffrement AES. Nous avons la clé dans le code ainsi que le vecteur d’initialisation. Le programme utilise la classe AesCryptoServiceProvider, avec le mode CBC par défaut

Nous avons donc toutes les informations pour recoder la fonction de déchiffrement en python, sous la forme d’un script.


#! /usr/bin/env python3
# usage: decrypt.py <filename>

from Crypto.Cipher import AES
import sys
import base64

key = b'RXZpbERlZmF1bHRQYXNzIQ=='
iv = bytearray([0, 1, 0, 3, 5, 3, 0, 1, 0, 0, 2, 0, 6, 7, 6, 0])

def main(filename):
    with open(filename, 'rb') as file:
        content = file.read()
        file.close()

    aes = AES.new(base64.b64decode(key), AES.MODE_CBC, iv)
    aes.block_size = 128
    text = aes.decrypt(content)

    result_filename = filename[:-5]
    with open(result_filename, 'wb') as file:
        file.write(text)

    print("Good! %s was decrypted to %s" % (filename, result_filename))

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("usage: decrypt.py <filename>")
    else:
        main(sys.argv[1])

Notre script va ouvrir le fichier passé en paramètre, le déchiffrer et restaurer le fichier d’origine sans l’extension .evil.

Précedemment, nous avions trouvé un fichier qui avait l’air très intéressant, informations_attaque.txt.evil. Extractons le et utilisons notre script dessus!


$ docker run --rm -v $(pwd):/samples -ti cincan/volatility -f /samples/dump.vmem --profile=Win7SP1x64 dumpfiles -D /samples/out -Q '0x000000001715ed50'

$ mv file.None.0xfffffa800e9fec60.dat informations_attaque.txt.evil

$ ./decrypt.py informations_attaque.txt.evil
Good! informations_attaque.txt.evil was decrypted to informations_attaque.txt

Ce message est destiné à toute force alliée en mesure de nous venir en aide, nous la résistance d’Evil Country. Hier, nous sommes parvenus à mettre la main sur un dossier confidentiel émanant des services secrets d’Evil Gouv.

Ces documents font mention d’une opération d’anéantissement de la résistance à l’aide d’un puissant agent innervant, le VX. L’attaque est prévue dans 4 jours à 4h40 au sein du fief de la résistance.

Nous savons de source sure qu’un convoi permettant la synthèse du VX partira de l’entrepot Stockos de Bad Country vers le fief de la résistance d’ici quelques jours. Il faut intercepter ce convoi !

Contactez-nous à cette adresse : http://ctf.challengecybersec.fr/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Nous avons déchiffré le fichier ! 🏁

Merci de votre lecture, et vive Brigitte !