French CyberSecurity Challenge 2020


Le French CyberSecurity Challenge est une compétition en ligne organisée par l’Agence National de la Sécurité des Systèmes d’Informations (ANSSI). Ce CTF a pour but de préselectionner 30 joueurs: 15 juniors (entre 15 et 20 ans) et 15 seniors (entre 21 et 25 ans). Les catégories étaient extrêmement variées: du forensic au web en passant par du hardware, reverse, etc…


Find me

Vous avez accès à un fichier find_me qui semble renfermer un secret bien gardé, qui n’existe peut-être même plus. Retrouvez son contenu !

TL;DR

Cette épreuve consiste à analyser une partition EXT4. Dans cette partition, on trouve une autre partition chiffrée (LUKS). La clé de chiffrement de ce volume a été supprimée, mais à l’aide de testdisk, il est possible de la récupérer. Ainsi, déchiffrer le volume, le monter et accéder au flag.

État des lieux

On commence cette épreuve avec un volume EXT4, système de fichier classique. Pour ce challenge, il existe au moins deux méthodes pour retrouver la clé du volume chiffré.

➜  file find_me 
find_me: Linux rev 1.0 ext4 filesystem data, UUID=9c0d2dc5-184c-496a-ba8e-477309e521d9, volume name "find_me" (needs journal recovery) (extents) (64bit) (large files) (huge files)

Solution 1 - Testdisk

Le premier réflexe en tant que CTFer est de le passer dans testdisk, d’autant plus que le volume est plutôt léger.

➜  sudo testdisk find_me
# Entrer -> None -> Advanced -> List

On extrait tous les fichiers et un simple cat de tous les fichiers part** montre une chaine de caractère rigolote:

➜  sudo cat part*
TWYtOVkyb01OWm5IWEtzak04cThuUlRUOHgzVWRZ
➜  sudo cat part* | base64 -d
Mf-9Y2oMNZnHXKsjM8q8nRTT8x3UdY

Il semblerait que la clé soit:

Mf-9Y2oMNZnHXKsjM8q8nRTT8x3UdY

Solution 2 - The Sleuth Kit

Cette solution n’utilise pas l’outil testdisk, mais la suite The Sleuth Kit (TSK). Cette suite d’outils est très utilisée dans les investigations numériques. Dans les faits, les outils les plus utilisés de cette suite doivent être:

  • mmls : Permets de lister les partitions d’un volume;
  • fls : Liste les fichiers et répertoires d’un volume;
  • istat : Affiche les méta données d’une structure, comme un inode;
  • icat : Affiche le contenu d’un inode.

Avec la commande fls il est possible de lister aussi les fichiers supprimés:

➜  mkdir a
➜  fls -a -r -m a find_me 
0|a/.|2|d/drwxr-xr-x|0|0|1024|1585770980|1585770852|1585770852|0
0|a/..|2|d/drwxr-xr-x|0|0|1024|1585770980|1585770852|1585770852|0
0|a/lost+found|11|d/drwx------|0|0|12288|1585770852|1585770844|1585770844|0
0|a/lost+found/.|11|d/drwx------|0|0|12288|1585770852|1585770844|1585770844|0
0|a/lost+found/..|2|d/drwxr-xr-x|0|0|1024|1585770980|1585770852|1585770852|0
0|a/unlock_me|12|r/rrw-r--r--|0|0|26214400|1585771103|1585771236|1585771236|0
0|a/pass.b64|13|r/rrw-r--r--|0|0|32|1585770998|1585770852|1585770852|0
0|a/part00 (deleted)|14|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part01 (deleted)|15|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part02 (deleted)|16|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part03 (deleted)|17|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part04 (deleted)|18|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part05 (deleted)|19|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part06 (deleted)|20|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part07 (deleted)|21|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part08 (deleted)|22|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part09 (deleted)|23|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part0a (deleted)|24|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part0b (deleted)|25|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part0c (deleted)|26|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part0d (deleted)|27|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part0e (deleted)|28|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part0f (deleted)|29|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part10 (deleted)|30|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part11 (deleted)|31|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part12 (deleted)|32|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part13 (deleted)|33|r/rr--------|0|0|2|1585770852|1585770852|1585770852|0
0|a/part14 (deleted)|34|r/rr--------|0|0|1|1585770852|1585770852|1585770852|0
0|a/$OrphanFiles|7681|V/V---------|0|0|0|0|0|0|0
0|a/$OrphanFiles/OrphanFile-1921 (deleted)|1921|-/drwxr-xr-x|0|0|0|1585770852|1585770852|1585770852|0

On peut voir qu’il y a plusieurs fichiers supprimés de l’inode 14 à 34.

Récupérer la clé

Avec icat, il est possible d’afficher directement le contenu et décoder la clé:

➜  for i in $(seq 14 34); do icat find_me $i; done | base64 -d
Mf-9Y2oMNZnHXKsjM8q8nRTT8x3UdY

Mf-9Y2oMNZnHXKsjM8q8nRTT8x3UdY

Déchiffrement et montage du volume LUKS

L’utilitaire cryptsetup permet de déchiffrer un volume:

➜  mkdir a
➜  sudo cryptsetup luksOpen unlock_me a 
Saisissez la phrase secrète pour unlock_me : Mf-9Y2oMNZnHXKsjM8q8nRTT8x3UdY
➜  lsblk
NAME                    MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
loop29                    7:29   0    25M  0 loop  
└─a                     253:3    0    23M  0 crypt 

Maintenant que le volume déchiffré a été associé à un mapper, il ne reste qu’à le monter:

➜  sudo mount /dev/mapper/a b 
➜  ls -la b 
total 4
drwxr-xr-x 2 root root   92 avril  1 21:54 .
drwxrwxr-x 4 maki maki 4096 avril 24 16:41 ..
-r-------- 1 root root   70 avril  1 21:54 .you_found_me

Le flag se trouve dans le fichier .you_found_me

Flag

FCSC{750322d61518672328c856ff72fac0a80220835b9864f60451c771ce6f9aeca1}


Petite frappe 2

Lors de l’investigation d’un poste GNU/Linux, vous analysez un nouveau fichier qui semble être généré par un programme d’enregistrement de frappes de clavier. Retrouvez ce qui a bien pu être écrit par l’utilisateur de ce poste à l’aide de ce fichier !

Format du flag : FCSC{xxx}, où xxx est la chaîne que vous aurez trouvée.

TL;DR

Fichier contenant des events “xinput”. Il suffit de décoder les entrées du clavier avec la bonne table de correspondance.

État des lieux

On commence cette épreuve avec un fichier texte contenant ceci :

➜  wc -l petite_frappe_2.txt 
213 petite_frappe_2.txt
➜  cat petite_frappe_2.txt 
key press   46 
key release 46 
key press   24 
key press   65 
key release 24 
key release 65 
key press   39 
key release 39 
key press   32 
key release 32 

Avec deux recherches Google, on tombe sur ce blog : https://blog.rom1v.com/2011/11/keylogger-sous-gnulinux-enregistrer-les-touches-tapees-au-clavier/ L’auteur a fait un script qui récupère la table de son Linux et décode le tout. Il suffit de modifier un peu ce script pour qu’il prenne un fichier en entrée et qu’il décode.

Décodage

Donc, voici le script modifié:

#!/usr/bin/python3

# https://blog.rom1v.com/2011/11/keylogger-sous-gnulinux-enregistrer-les-touches-tapees-au-clavier/
# python3 ./decode2.py | tr -d '\n' | sed 's/space/ /g' | sed 's/underscore/_/g'

import re, sys
from subprocess import *

def get_keymap():
    keymap = {}
    table = Popen(['xmodmap', '-pke'], stdout=PIPE).stdout
    for line in table:
        m = re.match('keycode +(\d+) = (.+)', line.decode())
        if m and m.groups()[1]:
            keymap[m.groups()[0]] = m.groups()[1].split()[0]
    return keymap

if __name__ == '__main__':
    keymap = get_keymap()
    dump = open('petite_frappe_2.txt','rb').read().split(b"\n")
    for i in dump:
        m = re.match('key press +(\d+)',i.decode())
        if m:
            keycode = m.groups()[0]
            if keycode in keymap:
                print(keymap[keycode])
            else:
                print('?' + keycode)  

L’output de ce script est presque satisfaisant:

➜  python ./decode2.py 
l
a
space
s
o
[...]

Par souci de flemme, j’ai utilisé bash pour formater l’output. Finalement:

➜  python3 ./decode2.py | tr -d '\n' | sed 's/space/ /g' | sed 's/underscore/_/g'
la solution avec xinput ne semble pas super pratique a decoderShift_Rsemicolon le flag est un_clavier_azerty_en_vaut_deux

Flag

FCSC{un_clavier_azerty_en_vaut_deux}


Cryptolocker

Un de nos admins nous a appelé en urgence suite à un CryptoLocker qui s’est lancé sur un serveur ultra-sensible, juste après avoir appliqué une mise à jour fournie par notre prestataire informatique.

Ce malware vise spécifiquement un fichier pouvant faire perdre des millions d’euros à notre entreprise : il est très important de le retrouver !

L’administrateur nous a dit que pour éviter que le logiciel ne se propage, il a mis en pause le serveur virtualisé et a récupéré sa mémoire vive dès qu’il a détecté l’attaque. Vous êtes notre seul espoir.

SHA256(memory1.dmp.gz) = 39e31ca61067e51f9566f0b669ac1ddbd09924a6f7720dcb08c9ac46cee186f5.

TL;DR

Un dump mémoire Windows 7 où un ransomware a été exécuté. Le processus “updater” est le ransomware. Il est possible de récupérer les fichiers de flag et de clé dans les dossiers de l’utilisateur courant. Un peu de rétro-ingénierie sur le binaire montre que c’est un xor un peu modifié.

État des lieux

On commence cette épreuve avec un dump de la mémoire d’un Windows visiblement:

➜  file memory.dmp 
memory.dmp: MS Windows 32bit crash dump, PAE, full dump, 262030 pages

On sort notre plus beau volatility et on cherche le bon profil:

➜  python volatility/vol.py -f /opt/usr_land/memory.dmp imageinfo
Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86_24000, Win7SP1x86 (Instantiated with WinXPSP2x86)
                     AS Layer1 : IA32PagedMemoryPae (Kernel AS)
                     AS Layer2 : WindowsCrashDumpSpace32 (Unnamed AS)
                     AS Layer3 : FileAddressSpace (/opt/usr_land/memory.dmp)
                      PAE type : PAE
                           DTB : 0x185000L
             KUSER_SHARED_DATA : 0xffdf0000L
           Image date and time : 2020-04-13 18:39:35 UTC+0000
     Image local date and time : 2020-04-13 11:39:35 -0700

Le profile à utiliser est:

Win7SP1x86_23418

Avant de partir comme un débile dans le dump, il faut se poser et se demander ce qu’on sait et ce qu’on cherche. Ce qu’on sait:

  • Déclenchement juste après l’exécution d’une mise à jour ;
  • Le malware vise un fichier (flag ?) ;
  • Dump mémoire Windows 7.

Ce qu’on cherche:

  • Le malware ;
  • Le fichier chiffré ;
  • Le mécanisme de chiffrement.

Aller, yapluka.

Localiser le malware

Comme le malware a été exécuté après une mise à jour, voyons ce que les processus donnent:

➜  python volatility/vol.py -f /opt/usr_land/memory.dmp --profile=Win7SP1x86_23418 pstree
Volatility Foundation Volatility Framework 2.6.1
Name                                                  Pid   PPid   Thds   Hnds Time
-------------------------------------------------- ------ ------ ------ ------ ----
 0x85093c90:explorer.exe                             1432   1320     28    756 2020-04-13 18:37:17 UTC+0000
[...]
. 0x83de43a8:update_v0.5.ex                          3388   1432      2     61 2020-04-13 18:38:00 UTC+0000
[...]

Je ne suis pas un expert Windows, mais de mémoire le processus de mise à jour, ce n’est pas du tout update_v0.5.exe. On peut essayer de l’extraire et regarder rapidement ce que c’est:

➜  python volatility/vol.py -f /opt/usr_land/memory.dmp --profile=Win7SP1x86_23418 procdump -p 3388 -D /opt/usr_land/Wu/
Volatility Foundation Volatility Framework 2.6.1
Process(V) ImageBase  Name                 Result
---------- ---------- -------------------- ------
0x83de43a8 0x00400000 update_v0.5.ex       OK: executable.3388.exe

Alors déjà, qu’est ce que c’est que ce fichier:

➜  file executable.3388.exe 
executable.3388.exe: PE32 executable (console) Intel 80386, for MS Windows
➜  strings executable.3388.exe
[...]
[info] entering the folder : %s
flag.txt
[info] file encryptable found : %s
	ENCRYPTOR v0.5
key.txt
[error] can't read the key-file :s
****Chiffrement termin
e ! Envoyez l'argent !
[...]

Déjà, pas de bol, ce n’est pas du .net. Ensuite, le binaire n’a pas l’air super friendly. Il parle de “flag.txt” et “key.txt”.

Récupération du flag chiffré et de la clé

On va voir si ces fichiers sont présents en mémoire:

➜  python volatility/vol.py -f /opt/usr_land/memory.dmp --profile=Win7SP1x86_23418 filescan > /opt/usr_land/Wu/filescan
Volatility Foundation Volatility Framework 2.6.1
➜  cat filescan| grep -E "flag\.txt|key\.txt"
0x000000003e6fa100      8      0 RW-rw- \Device\HarddiskVolume1\Users\IEUser\Desktop\key.txt
0x000000003ed13898      2      1 R--rw- \Device\HarddiskVolume1\Users\IEUser\Desktop\key.txt
0x000000003ed139f0      2      0 RW-rw- \Device\HarddiskVolume1\Users\IEUser\Desktop\flag.txt.enc

Il semblerait que ces fichiers soient présents. Par acquit de conscience, on peut regarder ce qu’il y a d’autre sur le bureau de l’utilisateur:

➜  cat filescan| fgrep '\IEUser\Desktop\'
0x000000003e6fa100      8      0 RW-rw- \Device\HarddiskVolume1\Users\IEUser\Desktop\key.txt
0x000000003e90b1e8      3      0 R--r-d \Device\HarddiskVolume1\Users\IEUser\Desktop\DumpIt.exe
0x000000003eb40430      8      0 R--rwd \Device\HarddiskVolume1\Users\IEUser\Desktop\desktop.ini
0x000000003ed13898      2      1 R--rw- \Device\HarddiskVolume1\Users\IEUser\Desktop\key.txt
0x000000003ed139f0      2      0 RW-rw- \Device\HarddiskVolume1\Users\IEUser\Desktop\flag.txt.enc
0x000000003ed66b60      6      0 R--r-d \Device\HarddiskVolume1\Users\IEUser\Desktop\update_v0.5.exe
0x000000003fdec360      1      1 RW-rw- \Device\HarddiskVolume1\Users\IEUser\Desktop\IEWIN7-20200413-183930.dmp

Bon, bah le malware est là aussi, ce n’était pas obligatoire de l’extraire des process. Il est temps de récupérer les deux fichiers qui nous intéressent.

➜  python volatility/vol.py -f /opt/usr_land/memory.dmp --profile=Win7SP1x86_23418 dumpfiles -Q 0x000000003ed13898 -D /opt/usr_land/Wu/
Volatility Foundation Volatility Framework 2.6.1
DataSectionObject 0x3ed13898   None   \Device\HarddiskVolume1\Users\IEUser\Desktop\key.txt
SharedCacheMap 0x3ed13898   None   \Device\HarddiskVolume1\Users\IEUser\Desktop\key.txt

➜  python volatility/vol.py -f /opt/usr_land/memory.dmp --profile=Win7SP1x86_23418 dumpfiles -Q 0x000000003ed139f0 -D /opt/usr_land/Wu/
Volatility Foundation Volatility Framework 2.6.1
DataSectionObject 0x3ed139f0   None   \Device\HarddiskVolume1\Users\IEUser\Desktop\flag.txt.enc

Une analyse rapide des fichiers:

➜  cat file.None.0x854fbc98.dat 
0ba883a22afb84506c8d8fd9e42a5ce4e8eb1cc87c315a28dd

➜  cat file.None.0x855651e0.dat
'{kp�U]
       SUU	]Y^\TQU^UWR[W\QTPQ
                                  ^UQUVYZWQRWZP%                                                                                                                   
                                  
➜  cat file.None.0x855651e0.dat | wc -c
4096

C’est étrange d’avoir une taille de 4096, mais pas beaucoup de bytes à l’affichage. Un rapide hexdump montre qu’il y a plein de null bytes (merci volatility, je suppose qu’une page mémoire c’est 4Kb, et qu’il pad avec du \x00).

➜  hexdump -C file.None.0x855651e0.dat 
00000000  27 7b 6b 70 1a 01 00 55  05 07 5d 0c 53 55 05 55  |'{kp...U..].SU.U|
00000010  09 5d 59 5e 06 5c 04 02  06 54 07 51 00 55 01 5e  |.]Y^.\...T.Q.U.^|
00000020  55 57 52 5b 57 5c 51 54  50 07 51 07 0b 5e 55 51  |UWR[W\QTP.Q..^UQ|
00000030  55 56 02 59 5a 07 05 02  57 51 52 01 0f 03 57 02  |UV.YZ...WQR...W.|
00000040  06 01 5a 50 0f 1b 6e 00  00 00 00 00 00 00 00 00  |..ZP..n.........|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00001000

On récupère les bytes qui nous intéressent et on en fait un fichier sans null bytes. Et juste pour le fun on sort un sexy one liner:

➜  hexdump -C file.None.0x855651e0.dat | cut -c11-60 | tr -d '\n ' | sed 's/0000//g' | sed 's/..$//g' | xxd -r -p | wc -c
71

On sait que ce n’est pas de l’AES, vu que ce n’est pas un multiple de 16. Par intuition, je ne pense pas que ce soit du RSA, EC ou autre chiffrement asymétrique. Donc on va se laisser penser que c’est un xor. Mais il est temps de reverse le malware.

Reverse du malware

Notre petit pote il ouvre bien le fichier “key.txt” et le stock dans un buffer:

La fonction que j’ai renommée encrypt_n_xor prend le chemin du flag en paramètre:

En fin de compte, l’hypopthèse de départ était bonne, c’est bien un xor:

C’est donc un xor où la clé est parcourue avec un décalage de +2 octets.

Si on récapitule ce qu’on cherchait au départ:

  • Le malware -> Le binaire “update_v0.5.exe” ;
  • Le fichier chiffré -> Le fichier “flag.txt.enc” ;
  • Le mécanisme de chiffrement -> Un xor un peu revisité avec le fichier “key.txt”.

Déchiffrer le flag

Il ne reste qu’à déchiffrer:

from ENO import *
key = b"0ba883a22afb84506c8d8fd9e42a5ce4e8eb1cc87c315a28dd"
enc_dat = htos("277b6b701a01005505075d0c53550555095d595e065c0402065407510055015e5557525b575c5154500751070b5e5551555602595a070502575152010f03570206015a500f1b6e")
flag = ""

for i in range(len(enc_dat)):
    flag += chr(enc_dat[i] ^ key[(i+2)%len(key)])

print(flag)

Flag

FCSC{324cee8fe3619a8bea64522eadf05c84df7c6df9f15e4cab4d0e04c77b20bb47}

Bonus

Il est possible d’utiliser le plugin “mft_parser” de volatility pour récupérer le contenu de “key.txt” et “flag.txt.enc”. J’utilise toujours ce plugin au début d’un challenge de mem Windows, des fois que le flag soit dans les fichiers résidents :D


Chapardeur de mots de passe

Un ami vous demande de l’aide pour déterminer si l’email qu’il vient d’ouvrir au sujet du Covid-19 était malveillant et si c’était le cas, ce qu’il risque. Il prétend avoir essayé d’ouvrir le fichier joint à cet mail sans y parvenir. Peu de temps après, une fenêtre liée à l’anti-virus a indiqué, entre autre, le mot KPOT v2.0 mais rien d’apparent n’est arrivé en dehors de cela.

Après une analyse préliminaire, votre ami vous informe qu’il est probable que ce malware ait été légèrement modifié, étant donné que le contenu potentiellement exfiltré (des parties du format de texte et de fichier avant chiffrement) ne semble plus prédictible. Il vous recommande donc de chercher d’autres éléments pour parvenir à l’aider.

Vous disposez d’une capture réseau de son trafic pour l’aider à déterminer si des données ont bien été volées et lui dire s’il doit rapidement changer ses mots de passe ! SHA256(pws.pcap) = 98e3b5f1fa4105ecdad4880cab6a7216c5bb3275d7367d1309ab0a0d7411475d - 463MB

TL;DR

Un challenge de PCAP assez lourd (~500 Mo). Il faut trouver la connexion du malware au panel de C2, récupérer les bytes chiffrés. En se documentant on sait qu’il est possible de bruteforce la clé assez rapidement et de récupérer le flag.

État de l’art

On commence donc cette épreuve avec un gros PCAP. Le souci des gros PCAP est qu’il est facile de se perdre dedans en suivant de fausses pistes. Pour éviter ça, il faut savoir ce que l’on cherche avant de plonger tête baissée dedans.

Ce qu’on sait:

  • Le malware est un KPOT v2.0 modifié.

Ce qu’on cherche:

  • Des indices de compromission d’un KPOT v2.0.

Analyse du malware

Dans ma méthodologie, il est possible (et c’est souvent le cas), que le “ce qu’on cherche” demande des connaissances pas encore acquises. En l’occurrence, on connait le malware utilisé et il existe des analyses détaillées:

Proofpoint propose une analyse complète du malware. On peut maintenant affiner le “ce qu’on cherche”:

  • Le RTF ou document de macro utilisé pour drop le malware ;
  • Des connexions vers “past.ee”;
  • Des connexions vers “gate.php”;
  • Trouver la clé de chiffrement du XOR;
  • Trouver le C2 avec une requête GET puis une requête POST.

Cependant, il ne faut pas oublier que l’énoncé nous dit:

il est probable que ce malware ait été légèrement modifié, étant donné que le contenu potentiellement exfiltré (des parties du format de texte et de fichier avant chiffrement) ne semble plus prédictible

Trouver la connexion suspecte

Le fait de chercher “gate.php” est une bonne piste:

Il y a quelque chose de rigolo: toutes les connexions sont uniques sauf la dernière. Toutes les connexions sont de simple GET avec de la data chiffrées et encodées en b64, mais pas la dernière:

C’est une requête POST avec de la donnée chiffrée brute, serait-ce notre flag tant attendu? On verra ça plus tard.

L’article de Proofpoint nous dit que la clé XOR est hardcodée dans le binaire. Mais il n’y a pas de binaire apparent dans ce PCAP (pour vérifier, un binwalk et “Fichiers -> Exporter objet -> HTTP”).

Donc il faut trouver un autre moyen. On sait que c’est du XOR et on connait le format de la configuration envoyée à “gate.php”. Toujours dans l’exemple de proofpoint:

  • Configuration
1111111111111100__DELIMM__A.B.C.D__DELIMM__appdata__GRABBER__*.log,*.txt,__GRABBER__%appdata%__GRABBER__0__GRABBER__1024__DELIMM__desktop_txt__GRABBER__*.txt,__GRABBER__%userprofile%\Desktop__GRABBER__0__GRABBER__150__DELIMM____DELIMM____DELIMM__
  • Clé du XOR

4p81GSwBwRrAhCYK

La clé fait 16 bytes, l’espèce de binaire du début de la configuration fait 16 bytes. De plus, la clé a un charset alphanumérique, soit: [A-Za-z0-9]. Il ne reste qu’à bruteforce tout ça pour trouver la clé qui nous intéresse.

Bruteforce de la clé

Le fichier enc_dat est le base64 décodé de la connexion GET au C2.

#!/usr/bin/python3

from ENO import *
import itertools

enc_dat = open('enc_dat','rb').read()

# Génération de toutes les possibilités du binaire
lst_bin = []
for i in itertools.product(range(2),repeat=16):
    lst_bin.append("".join(map(str,i)))

# On filtre sur les clés potentielles (le filtrage aurait pu être mieux)
pot_key = []
for l in lst_bin:
    tmp = cribDrag(enc_dat,l.encode())
    for j in tmp:
        if j.decode().isprintable():
            pot_key.append(j)

# On bruteforce jusqu'à trouver la configuration déchiffrée
for k in pot_key:
    decrypt_dat = strxor(enc_dat,k)
    if decrypt_dat.decode()[:16].isprintable():
        if "__DELIMM____DELIMM__" in decrypt_dat.decode():
            print(f"Key : {k}\nData : {decrypt_dat}\n\n")

Après environ 1 petite minute, on trouve:

➜  chapardeyr python3 ./bf.py 
Key : b'tDlsdL5dv25c1Rhv'
Data : b'0110101110111110__DELIMM__218.108.149.373__DELIMM__appdata__GRABBER__*.log,*.txt,__GRABBER__%appdata%__GRABBER__0__GRABBER__1024__DELIMM__desktop_txt__GRABBER__*.txt,__GRABBER__%userprofile%\\Desktop__GRABBER__0__GRABBER__0__DELIMM____DELIMM____DELIMM__'

Déchiffrement du message

Yapluka :)

from ENO import *

key = b'tDlsdL5dv25c1Rhv'
enc = htos("2b003e3234097431296249164260181301363d06017e78501a13154363661b0501365f09491a5a10045718225c63453300691a1c552f04321946470655205c061125192c220f66277c49015508375047427c5b425c750c5213510d50506b5a1717205a15017a575d1502060007310d1246255f125329020544020d5a53675b4216250d165d7b54530b386a2763133833351133")

print(strxor(enc,key))

b’_DRAPEAU_P|us2peurQue2M4l! R4ssur3z-Votre-Am1-Et-vo1c1Votredr4peau_FCSC\n{469e8168718996ec83a92acd6fe6b9c03c6ced2a3a7e7a2089b534baae97a7}\nDRAPEAU

En l’occurrence, comme l’énoncé le disait, le code du malware a été modifié, ainsi la structure de la connexion POST change et ne propose plus “_FFFILEE” ou “_SYSINFORMATION”. Il faut lire avait de râler :D

Flag

FCSC{469e8168718996ec83a92acd6fe6b9c03c6ced2a3a7e7a2089b534baae97a7}


Académie - C’est la rentrée

Bienvenue à l’académie de l’investigation numérique ! Votre mission, valider un maximum d’étapes de cette série afin de démontrer votre dextérité en analyse mémoire GNU/Linux.

Première étape : retrouvez le HOSTNAME, le nom de l’utilisateur authentifié lors du dump et la version de Linux sur lequel le dump a été fait.

Format du flag : FCSC{hostname:user:x.x.x-x-amdxx}

TL;DR

Cette suite de challenge se base sur le même dump mémoire. Le premier consiste à se rendre compte que c’est un dump Linux.

État des lieux

La première chose que je fais sur un dump mémoire pour un challenge, c’est de faire deux strings:

➜  strings dmp.mem > str_dmp.txt 
➜  strings -e l dmp.mem > str_el_dmp.txt

Pour ce challenge, on n’aura pas besoin de plus.

Hostname

➜  cat str_* | grep "HOSTNAME="
XAUTHLOCALHOSTNAME=
_HOSTNAME=challenge.fcsc

challenge.fcsc

User

➜  cat str_* | grep -Eo "^/home/.*/$"
/home/Lesage/.mozilla/firefox/peyjyk3f.default-esr/extensions/
/home/Lesage/.local/share/mime/
/home/Lesage/.mozilla/firefox/peyjyk3f.default-esr/extensions/
/home/Lesage/.mozilla/firefox/peyjyk3f.default-esr/extensions/
/home/Lesage/.local/share/mime/
/home/Lesage/.local/share/mime/
/home/Lesage/Documents/temp/

➜  cat str_* | grep "USER=" | sort | uniq
USER=Lesage
[...]

Lesage

Version kernel

➜  cat str_* | grep 'Linux version' | uniq      
Linux version 5.4.0-4-amd64 (debian-kernel@lists.debian.org) (gcc version 9.2.1 20200203 (Debian 9.2.1-28)) #1 SMP Debian 5.4.19-1 (2020-02-13)

➜  cat str_* | grep "BOOT_IMAGE=" 
BOOT_IMAGE=/boot/vmlinuz-5.4.0-4-amd64 root=UUID=536c82dd-f1c5-43ce-b65d-c94e5c4a5031 ro quiet
[...]

5.4.0-4-amd64

Flag

FCSC{challenge.fcsc:Lesage:5.4.0-4-amd64}

Académie - Administration

Ce poste administre un serveur distant avec le protocole SSH à l’aide d’une authentification par clé (clé protégée par mot de passe). La clé publique a été utilisée pour chiffrer le message ci-joint (flag.txt.enc).

Retrouvez et reconstituez la clé en mémoire qui permettra de déchiffrer ce message.

TL;DR

Pour ce challenge il n’y a pas non plus besoin de générer un profil volatility. En effet, le fichier chiffré a une taille de 2048 bits et l’énoncé parle de SSH. Donc on cherche une clé privée RSA, pour cela il existe rsakeyfind qui nous donne “d” et “n”. Ces informations permettent de déchiffrer le flag.

État de l’art

On commence le challenge avec un fichier chiffré “SSH”. Intuitivement, on peut penser à du RSA, mais il existe plusieurs algorithmes disponibles. Pour s’en assurer, il suffit de vérifier la taille du fichier chiffré:

➜  cat flag.txt.enc| wc -c
256 # 256*8 = 2048

On peut se dire qu’on cherche une clé privée RSA.

Récupérer la clé privée

L’outil rsakeyfind : https://github.com/congwang/rsakeyfind

➜  ~/tools/forensic/rsakeyfind/rsakeyfind dmp.mem 
FOUND PRIVATE KEY AT c64ac50
version = 
00 
modulus = 
00 d7 1e 77 82 8c 92 31 e7 69 02 a2 d5 5c 78 de 
a2 0c 8f fe 28 59 31 df 40 9c 60 61 06 b9 2f 62 
40 80 76 cb 67 4a b5 59 56 69 17 07 fa f9 4c bd 
6c 37 7a 46 7d 70 a7 67 22 b3 4d 7a 94 c3 ba 4b 
7c 4b a9 32 7c b7 38 95 45 64 a4 05 a8 9f 12 7c 
4e c6 c8 2d 40 06 30 f4 60 a6 91 bb 9b ca 04 79 
11 13 75 f0 ae d3 51 89 c5 74 b9 aa 3f b6 83 e4 
78 6b cd f9 5c 4c 85 ea 52 3b 51 93 fc 14 6b 33 
5d 30 70 fa 50 1b 1b 38 81 13 8d f7 a5 0c c0 8e 
f9 63 52 18 4e a9 f9 f8 5c 5d cd 7a 0d d4 8e 7b 
ee 91 7b ad 7d b4 92 d5 ab 16 3b 0a 8a ce 8e de 
47 1a 17 01 86 7b ab 99 f1 4b 0c 3a 0d 82 47 c1 
91 8c bb 2e 22 9e 49 63 6e 02 c1 c9 3a 9b a5 22 
1b 07 95 d6 10 02 50 fd fd d1 9b be ab c2 c0 74 
d7 ec 00 fb 11 71 cb 7a dc 81 79 9f 86 68 46 63 
82 4d b7 f1 e6 16 6f 42 63 f4 94 a0 ca 33 cc 75 
13 
publicExponent = 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 01 
privateExponent = 
62 b5 60 31 4f 3f 66 16 c1 60 ac 47 2a ff 6b 69 
00 4a b2 5c e1 50 b9 18 74 a8 e4 dc a8 ec cd 30 
bb c1 c6 e3 c6 ac 20 2a 3e 5e 8b 12 e6 82 08 09 
38 0b ab 7c b3 cc 9c ce 97 67 dd ef 95 40 4e 92 
e2 44 e9 1d c1 14 fd a9 b1 dc 71 9c 46 21 bd 58 
88 6e 22 15 56 c1 ef e0 c9 8d e5 80 3e da 7e 93 
0f 52 f6 f5 c1 91 90 9e 42 49 4f 8d 9c ba 38 83 
e9 33 c2 50 4f ec c2 f0 a8 b7 6e 28 25 56 6b 62 
67 fe 08 f1 56 e5 6f 0e 99 f1 e5 95 7b ef eb 0a 
2c 92 97 57 23 33 36 07 dd fb ae f1 b1 d8 33 b7 
96 71 42 36 c5 a4 a9 19 4b 1b 52 4c 50 69 91 f0 
0e fa 80 37 4b b5 d0 2f b7 44 0d d4 f8 39 8d ab 
71 67 59 05 88 3d eb 48 48 33 88 4e fe f8 27 1b 
d6 55 60 5e 48 b7 6d 9a a8 37 f9 7a de 1b cd 5d 
1a 30 d4 e9 9e 5b 3c 15 f8 9c 1f da d1 86 48 55 
ce 83 ee 8e 51 c7 de 32 12 47 7d 46 b8 35 df 41 
prime1 = 
00 
prime2 = 
00 
exponent1 = 
00 
exponent2 = 
00 
coefficient = 
00 

Déchiffrer le fichier

Il suffit d’utiliser ’d’, ‘n’ et le fichier “flag.txt.enc” pour récupérer le flag:

➜  cat flag.txt.enc | xxd -p | tr -d '\n'
6da249ada8a979ed289b249d9ed4af472bda2ebbd1d9ea8b63f927f9bf91ba1bb76c378e6e62f07b7717f1e1def03dd65a8c6ee3ce40447d12f7b07ad2d18256a3997bd5786cccfaa678b1f60b069e6b8ccc70eb6948c3624eaf7832727bd034c9964c7dd863830d3b07bc4b242c4fe0ead36ae7a81ff1419b658b86dd30d78acc797f5b7a2b5c08d3fc807fbcd9e61e7f46071cdc3906882b9084e9af9231c4b343fbac2349232d368b0d343f6e9e2c7ff32242e1885590fdfe762ce8658c684c04ea19b8bf7b5153c4a4cdfbd5847da3b53fc285469b7761a5da6f1778b8972da6006d279a8042bbfaa7045ec72bf778f24d6da5fe1fde422667bc9c53f77d
d = int(0xd71e77828c9231e76902a2d55c78dea20c8ffe285931df409c606106b92f62408076cb674ab55956691707faf94cbd6c377a467d70a76722b34d7a94c3ba4b7c4ba9327cb738954564a405a89f127c4ec6c82d400630f460a691bb9bca0479111375f0aed35189c574b9aa3fb683e4786bcdf95c4c85ea523b5193fc146b335d3070fa501b1b3881138df7a50cc08ef96352184ea9f9f85c5dcd7a0dd48e7bee917bad7db492d5ab163b0a8ace8ede471a1701867bab99f14b0c3a0d8247c1918cbb2e229e49636e02c1c93a9ba5221b0795d6100250fdfdd19bbeabc2c074d7ec00fb1171cb7adc81799f86684663824db7f1e6166f4263f494a0ca33cc7513)

n = int(0x62b560314f3f6616c160ac472aff6b69004ab25ce150b91874a8e4dca8eccd30bbc1c6e3c6ac202a3e5e8b12e6820809380bab7cb3cc9cce9767ddef95404e92e244e91dc114fda9b1dc719c4621bd58886e221556c1efe0c98de5803eda7e930f52f6f5c191909e42494f8d9cba3883e933c2504fecc2f0a8b76e2825566b6267fe08f156e56f0e99f1e5957befeb0a2c92975723333607ddfbaef1b1d833b796714236c5a4a9194b1b524c506991f00efa80374bb5d02fb7440dd4f8398dab71675905883deb484833884efef8271bd655605e48b76d9aa837f97ade1bcd5d1a30d4e99e5b3c15f89c1fdad1864855ce83ee8e51c7de3212477d46b835df41)

c = int(0x6da249ada8a979ed289b249d9ed4af472bda2ebbd1d9ea8b63f927f9bf91ba1bb76c378e6e62f07b7717f1e1def03dd65a8c6ee3ce40447d12f7b07ad2d18256a3997bd5786cccfaa678b1f60b069e6b8ccc70eb6948c3624eaf7832727bd034c9964c7dd863830d3b07bc4b242c4fe0ead36ae7a81ff1419b658b86dd30d78acc797f5b7a2b5c08d3fc807fbcd9e61e7f46071cdc3906882b9084e9af9231c4b343fbac2349232d368b0d343f6e9e2c7ff32242e1885590fdfe762ce8658c684c04ea19b8bf7b5153c4a4cdfbd5847da3b53fc285469b7761a5da6f1778b8972da6006d279a8042bbfaa7045ec72bf778f24d6da5fe1fde422667bc9c53f77d)

t = pow(c,d,n)
print(hex(t))

0x22681c036dd635f46c2853e08b7359b196e5db09f3fa40a801704b36f7408c8ed931f29ff4319a7ba0be6e107624a43471d49cc14bb0e71488c76230de112bb05f7821bc81f67db4c80fd038938bfc7fed1e963b3e9adb27bd127948b1d9bb3874b1bc320e90628a991bc066dc4406830133627cb148370d14d9538ff238d12f002bc89233daa0beda1ca2c5bc714087ef808624e5b4ed1e9b62bb6f6a34c88907381f1606de98951ba3733bf8d426e310417b61e394300464353437b616335636164363631313464343836366134623535653433636238383936636334393437383535323431623561663864326638613132336333363038336439387d0a

➜  echo -n '022681c036dd635f46c2853e08b7359b196e5db09f3fa40a801704b36f7408c8ed931f29ff4319a7ba0be6e107624a43471d49cc14bb0e71488c76230de112bb05f7821bc81f67db4c80fd038938bfc7fed1e963b3e9adb27bd127948b1d9bb3874b1bc320e90628a991bc066dc4406830133627cb148370d14d9538ff238d12f002bc89233daa0beda1ca2c5bc714087ef808624e5b4ed1e9b62bb6f6a34c88907381f1606de98951ba3733bf8d426e310417b61e394300464353437b616335636164363631313464343836366134623535653433636238383936636334393437383535323431623561663864326638613132336333363038336439387d0a' | xxd -r -p | strings -n 10
FCSC{ac5cad66114d4866a4b55e43cb8896cc4947855241b5af8d2f8a123c36083d98}

Flag

FCSC{ac5cad66114d4866a4b55e43cb8896cc4947855241b5af8d2f8a123c36083d98}


Académie - Premiers artéfacts

Pour avancer dans l’analyse, vous devez retrouver :

  • Le nom de processus ayant le PID 1254.
  • La commande exacte qui a été exécutée le 2020-03-26 23:29:19 UTC.
  • Le nombre d’IP-DST unique en communications TCP établies (état ESTABLISHED) lors du dump.

Format du flag : FCSC{nom_du_processus:une_commande:n}

TL;DR

Ce challenge va nécessiter le profil Linux associé. Pour cela on se sert des snapshot Debian de Sid à la bonne date pour récupérer le bon kernel. Une fois le profil généré, il suffit d’utiliser les plugins linux_psscan, linux_bash et linux_netscan.

État de l’art

Ce challenge annonce le début des ennuis. En effet, il va falloir créer un profil volatility pour pouvoir récupérer ces informations. En temps normal, créer un profil sur un Debian, c’est plutôt très simple (cf. Un poc en bash d’automatisation que j’avais fait : https://github.com/Zeecka/Auto_vol).

Le souci principal va être de trouver la bonne version de Kernel. Grâce au challenge précédent, on sait que le kernel est de version : 5.4.0-4-amd64. Qui dit très récent, va dire très casse-pied.

Génération du profil

La version est:

➜  cat str_* | grep 'Linux version' | uniq      
Linux version 5.4.0-4-amd64 (debian-kernel@lists.debian.org) (gcc version 9.2.1 20200203 (Debian 9.2.1-28)) #1 SMP Debian 5.4.19-1 (2020-02-13)

Deux informations intéressantes:

  • Kernel : 5.4.0-4-amd64
  • Date : 2020-02-13

Avec cette date, il est possible de trouver un snapshot précis des dépôts Debian sur : https://snapshot.debian.org/

La première étape est de créer une VM Debian: https://cdimage.debian.org/debian-cd/current/amd64/bt-cd/debian-10.3.0-amd64-netinst.iso.torrent

Lorsque la machine est installée, un simple uname -a montre qu’on a pas du tout la bonne version de kernel. C’est là que les snapshots Debian seront utiles. Avec un peu de recherche, on trouve ces dépôts:

deb [check-valid-until=no] https://snapshot.debian.org/archive/debian/20200213T231921Z/ sid main contrib non-free
deb-src [check-valid-until=no] https://snapshot.debian.org/archive/debian/20200213T231921Z/ sid main contrib non-free

Donc en l’ajoutant dans notre /etc/apt/sources.list, on peut faire une recherche, et là:

user@debian:~$ apt search linux-headers
En train de trier... Fait
Recherche en texte intégral... Fait
[...]
linux-headers-5.4.0-4-amd64/unstable,now 5.4.19-1 amd64  
  Header files for Linux 5.4.0-4-amd64
[...]

user@debian:~$ apt search linux-image
En train de trier... Fait
Recherche en texte intégral... Fait
[...]
linux-image-5.4.0-4-amd64-unsigned/unstable,now 5.4.19-1 amd64  
  Linux 5.4 for 64-bit PCs
[...]

Oook boy, il est temps d’installer ce qu’il nous faut:

root@debian:/home/user# apt install linux-headers-5.4.0-4-amd64 linux-image-5.4.0-4-amd64-unsigned volatility-tools

Une fois le tout installé, il faut redémarrer la machine et boot sur le bon kernel (grub est plutôt intuitif pour ça):

user@debian:~$ uname -a
Linux debian 5.4.0-4-amd64 #1 SMP Debian 5.4.19-1 (2020-02-13) x86_64 GNU/Linux

Ca a une bonne tête. Il est temps de créer le profil (cf. https://www.maki.bzh/walkthrough/santhacklaus2018/#mission-impossible-1).

root@debian:/home/user# cd /usr/src/volatility-tools/linux/

root@debian:/usr/src/volatility-tools/linux# make
[...BLABLABLA MAKE BLABLALBA]
  
root@debian:/usr/src/volatility-tools/linux# zip ecsc_deb540.zip /usr/src/volatility-tools/linux/module.dwarf /boot/System.map-5.4.0-4-amd64 
  adding: usr/src/volatility-tools/linux/module.dwarf (deflated 91%)
  adding: boot/System.map-5.4.0-4-amd64 (deflated 79%)

Aller maintenant on le met dans un dossier accessible par volatility (j’utilise volatility en docker, pour être sur que les dépendances pètent pas, genre yara: https://github.com/makim0n/infosec-docker/tree/master/forensic/volatility).

Donc j’ai mis le profil ecsc_deb540.zip dans le dossier de /opt/plug_vol de mon docker:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --info | grep ecsc
Linuxecsc_deb540x64                      - A Profile for Linux ecsc_deb540 x64

Linuxecsc_deb540x64

Aller, c’est le moment le plus stressant: faut tester que ça marche.

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_banner
Linux version 5.4.0-4-amd64 (debian-kernel@lists.debian.org) (gcc version 9.2.1 20200203 (Debian 9.2.1-28)) #1 SMP Debian 5.4.19-1 (2020-02-13)

Nom du processus 1254

Maintenant que le profil est créé, en temps normal je fais un linux_pstree, mais ici ça ne semble pas aider beaucoup:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_pstree > /opt/usr_land/linux_pstree 

➜  cat linux_pstree| grep 1254
# Pas de retour, c'est fâcheux.

Donc là, pas le choix, on sort la doc: https://github.com/volatilityfoundation/volatility/wiki/Linux-Command-Reference

On voit le plugin linux_psslit:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_pslist > /opt/usr_land/pslist 

➜  cat pslist| grep 1254
# Pas de retour encore une fois, c'est fâcheux.

Je me rappelle ce qu’un ancien collègue et sensei du forensic (@AZobec <3) m’avais dit:

Volatility c’est bien, mais la doc c’est le code, donc creuse quand tu trouves pas

Donc on part pour le github de volatility dans le dossier des plugins: https://github.com/volatilityfoundation/volatility/tree/master/volatility/plugins/linux

Ce plugin n’est pas du tout présent dans la command reference.

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_psscan > /opt/usr_land/psscan 

➜  cat psscan| grep 1254
0x000000003fdccd80 pool-xfconfd         1254            -               -1              -1     0x0fd08ee88ee08ec0 -

Enfin:

pool-xfconfd

La commande exécutée

Les commandes exécutées par l’utilisateur sont plus simples à trouver:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_bash > /opt/usr_land/linux_bash

➜  cat linux_bash 
Pid      Name                 Command Time                   Command
-------- -------------------- ------------------------------ -------
[...]
1523 bash                 2020-03-26 23:29:19 UTC+0000   nmap -sS -sV 10.42.42.0/24
[...]

nmap -sS -sV 10.42.42.0/24

D’ailleurs, quitte à rester dans “la doc c’est le code”, il existe des arguments pour les plugins volatility, qui ne sont pas forcément documentés:

https://github.com/volatilityfoundation/volatility/blob/master/volatility/plugins/linux/bash.py

Ici, ça ne sert à rien, mais c’est bon à savoir.

Nombre de connexion

Pour le nombre de connexions, on sait que ce sont les connexions “établies”. Avec mon super niveau en anglais, “Peer connections” j’étais pas sur de ce que c’était, mais la commande ss de mon système m’a aidée:

➜  ss | head -n 2
Netid            State                  Recv-Q              Send-Q                                                                                Local Address:Port                                         Peer Address:Port                  
u_str            ESTAB                  0                   0                                                                                                 * 178269                                                  * 180308                

Ce sont donc les adresses de destinations. Il existe un plugin pour lister les connexions lors du dump: linux_netscan.

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_netscan > /opt/usr_land/netscan

➜  cat netscan | grep ESTA
9d72830a8000 TCP      10.42.42.131    :58772 185.199.111.154 :  443 ESTABLISHED    
9d72830a88c0 TCP      10.42.42.131    :45652 35.190.72.21    :  443 ESTABLISHED    
9d72830a9a40 TCP      10.42.42.131    :53190 104.124.192.89  :  443 ESTABLISHED    
9d72830abd40 TCP      10.42.42.131    :55226 151.101.121.140 :  443 ESTABLISHED    
9d72830ad780 TCP      10.42.42.131    :50612 104.93.255.199  :  443 ESTABLISHED    
9d72830af1c0 TCP      10.42.42.131    :38184 216.58.213.142  :  443 ESTABLISHED    
9d7284eba300 TCP      10.42.42.131    :37252 163.172.182.147 :  443 ESTABLISHED    
9d7284fe9180 TCP      127.0.0.1       :38498 127.0.0.1       :34243 ESTABLISHED    
9d7284fe9a40 TCP      10.42.42.131    :57000 10.42.42.134    :   22 ESTABLISHED    
9d7284feb480 TCP      10.42.42.131    :51858 10.42.42.128    :  445 ESTABLISHED    
9d7284fef1c0 TCP      10.42.42.131    :55224 151.101.121.140 :  443 ESTABLISHED    
9d7293778000 TCP      10.42.42.131    :47100 216.58.206.226  :  443 ESTABLISHED    
9d729377cec0 TCP      10.42.42.131    :47106 216.58.206.226  :  443 ESTABLISHED    
9d72c0acb480 TCP      10.42.42.131    :36970 116.203.52.118  :  443 ESTABLISHED    
9d72c1503d40 TCP      127.0.0.1       :34243 127.0.0.1       :38498 ESTABLISHED    
9d72c1bc1280 TCP      fd:6663:7363:1000:c10b:6374:25f:dc37:36280 fd:6663:7363:1000:55cf:b9c6:f41d:cc24:58014 ESTABLISHED    
9d72c23fcec0 TCP      10.42.42.131    :38186 216.58.213.142  :  443 ESTABLISHED    
9d72c23fe040 TCP      10.42.42.131    :47104 216.58.206.226  :  443 ESTABLISHED    
9d72c23fe900 TCP      10.42.42.131    :47102 216.58.206.226  :  443 ESTABLISHED    

Avec un peu de formating pour garder uniquement les IPs sortantes, on tombe sur:

➜  echo -n '185.199.111.154     
35.190.72.21        
104.124.192.89      
151.101.121.140     
104.93.255.199      
216.58.213.142      
163.172.182.147     
127.0.0.1           
10.42.42.134        
10.42.42.128        
151.101.121.140     
216.58.206.226      
216.58.206.226      
116.203.52.118      
127.0.0.1           
216.58.213.142      
216.58.206.226      
216.58.206.226      
fd:6663:7363:1000:55cf:b9c6:f41d:cc24:58014    
' | tr -d ' ' | sort | uniq | wc -l
13

13

Flag

Challenge enfin terminé.

FCSC{pool-xfconfd:nmap -sS -sV 10.42.42.0/24:13}


Académie - Porte dérobée

Un poste distant est connecté au poste en cours d’analyse via une porte dérobée avec la capacité d’exécuter des commandes.

  • Quel est le numéro de port à l’écoute de cette connexion ?
  • Quelle est l’adresse IP distante connectée au moment du dump ?
  • Quel est l’horodatage de la création du processus en UTC de cette porte dérobée ?

Format du flag : FCSC{port:IP:YYYY-MM-DD HH:MM:SS}

TL;DR

Ce challenge est dans la continuité du précédent sur l’analyse du dump. Grâce à linux_netscan, il est possible de voir des processus suspects. Ensuite, linux_pstree permet de s’en assurer. Enfin, l’horodatage est trouvable grâce à linux_pslist.

État des lieux

Ce challenge est aussi de l’analyse plutôt basique d’un dump mémoire. Dans le challenge précédant, on commence par utiliser le plugin linux_netstat, par défaut il va aussi afficher les sockets unix, on n’y reprendra pas, allons voir le code:

https://github.com/volatilityfoundation/volatility/blob/master/volatility/plugins/linux/netstat.py

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_netstat -U > /opt/usr_land/netstat_wo_socketunix

➜  cat netstat_wo_socketunix
➜  cestlarentree cat netstat_wo_socketunix | grep ESTA
[...]
TCP      fd:6663:7363:1000:c10b:6374:25f:dc37:36280 fd:6663:7363:1000:55cf:b9c6:f41d:cc24:58014 ESTABLISHED                  ncat/1515 
[...]
TCP      fd:6663:7363:1000:c10b:6374:25f:dc37:36280 fd:6663:7363:1000:55cf:b9c6:f41d:cc24:58014 ESTABLISHED                    sh/119511
[...]

En vrai, je suis pas un expert, mais un ncat et un sh… Mon petit passif de pentester ma dit que ça sent pas bon.

Port en écoute

En remontant cette piste, il est simple de trouver les informations que l’on cherche:

36280

IP distante connectée

L’IP distante aussi est donnée:

fd:6663:7363:1000:55cf:b9c6:f41d:cc24

Horodatage

Pour l’horodatage, on a le PID du ncat et du sh. Avec linux_pstree il est possible de voir le processus parent facilement:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_pstree > /opt/usr_land/linux_pstree

➜  cat linux_pstree 
Name                 Pid             Uid            
systemd              1                              
[...]
.x-terminal-emul     1503            1001           
..bash               1513            1001           
...ncat              1515            1001           
....sh               119511          1001           

En remontant sur le processus parent bash, la date est donc:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_pslist -p 1513
Offset             Name                 Pid             PPid            Uid             Gid    DTB                Start Time
------------------ -------------------- --------------- --------------- --------------- ------ ------------------ ----------
0xffff9d72c014dd00 bash                 1513            1503            1001            1001   0x000000004012c000 2020-03-26 23:24:20 UTC+0000

2020-03-26 23:24:20

Flag

FCSC{36280:fd:6663:7363:1000:55cf:b9c6:f41d:cc24:2020-03-26 23:24:20}


Académie - Rédaction

Le document note.docx vient d’être créé avec LibreOffice et enregistré avant le dump de la mémoire. Retrouvez son contenu !

Format du flag : FCSC{xxx}, où xxx est la chaîne qui vous sera indiquée à la lecture du contenu du document.

TL;DR

Ce challenge consiste à récupérer le contenu d’un fichier “docx”. En sachant que c’est un docx, il est possible d’en créer un local, le décompresser et chercher la structure de “document.xml” en mémoire et ainsi trouver le flag.

État des lieux

Pour ce challenge, on sait qu’on cherche un fichier docx et que l’utilisateur a utilisé LibreOffice. La grosse erreur est que je n’ai pas respecté ma méthodologie et je suis parti bille en tête “lol je ve trouvé 1 zip”. Après avoir vu que ça ne servait à rien d’y aller comme un débile, il est temps de se poser et de réfléchir.

Pour mes tests, j’ai installé LibreOffice sur ma machine et lancé le “writer” (le Word du pauvre):

➜  ~ ps aux | grep 'office'
maki     29586  0.0  0.0 176844  5912 ?        Sl   15:59   0:00 /usr/lib/libreoffice/program/oosplash --writer
maki     29601  7.0  1.3 7689752 219172 ?      Sl   15:59   0:03 /usr/lib/libreoffice/program/soffice.bin --writer --splash-pipe=5

Visiblement ces processus sont présents aussi dans le dump:

➜  cat linux_pstree| grep -E "soffice|oosplash"
.oosplash            119599          1001           
soffice.bin          119615          1001           

En continuant à piger le fonctionnement de libreOffice, j’ai remarqué qu’un dossier est apparu dans mon /tmp:

➜  /tmp ls -la | grep lu
drwx------  2 maki maki  4096 mai    3 15:59 lu29601r4fv4s.tmp

➜  /tmp ls -la lu29601r4fv4s.tmp 
total 44
drwx------  2 maki maki  4096 mai    3 15:59 .
drwxrwxrwt 26 root root 40960 mai    3 15:59 ..
-rw-------  1 maki maki     0 mai    3 15:59 lu29601r4fv4t.tmp

Un fichier vide, ce dossier est surement utilisé pour la sauvegarde automatique des documents ou ce genre de choses. En théorie, si le fichier est sauvegardé, une copie est présente ici.

Voyons si ce dossier est présent dans le dump:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_find_file -L > /opt/usr_land/linuxff.txt

➜  cat linuxff.txt| grep "/tmp/lu"
          923875 0xffff9d72891fe628 /tmp/lu1196159e8v1r.tmp
          923893 0xffff9d72c2cc3bf8 /tmp/lu1196159e8v1r.tmp/lu1196159e8v23.tmp
          923884 0xffff9d72c2cc0d90 /tmp/lu1196159e8v1r.tmp/lu1196159e8v22.tmp
          923876 0xffff9d72891ce628 /tmp/lu1196159e8v1r.tmp/lu1196159e8v1t.tmp

Mécanisme de sauvegarde de LibreOffice

Après avoir créé et sauvegardé un fichier ODT, le dossier à bien bougé:

➜  ~ ls -la /tmp/lu29601r4fv4s.tmp 
total 56
drwx------  2 maki maki  4096 mai    3 16:06 .
drwxrwxrwt 27 root root 40960 mai    3 16:06 ..
-rw-------  1 maki maki     0 mai    3 16:06 lu29601r4fv4u.tmp
-rw-r--r--  1 maki maki  8504 mai    3 16:06 lu29601r4fv4y.tmp

Il s’agit bien de notre fichier ODT, c’est le même hash:

➜  ~ file /tmp/lu29601r4fv4s.tmp/lu29601r4fv4y.tmp
/tmp/lu29601r4fv4s.tmp/lu29601r4fv4y.tmp: OpenDocument Text
➜  ~ md5sum /tmp/lu29601r4fv4s.tmp/lu29601r4fv4y.tmp ~/Documents/ctf/ecsc2020/cestlarentree/Wu/test_odt.odt 
aba6138e65c1dc360920d63772e5c9c0  /tmp/lu29601r4fv4s.tmp/lu29601r4fv4y.tmp
aba6138e65c1dc360920d63772e5c9c0  /home/maki/Documents/ctf/ecsc2020/cestlarentree/Wu/test_odt.odt

Bon, par contre, on cherche un DOCX et pas un ODT. On va retenter l’expérience avec un DOCX. D’ailleurs, les dossiers temporaires se suppriment lorsque que le processus soffibe.bin se termine.

Après la création et la sauvegarde d’un fichier DOCX, un phénomène intéressant apparait sur les fichiers temporaires… Ou plutot n’apparait pas:

➜  ~ ls -la /tmp/lu30353r4wuxv.tmp/
total 44
drwx------  2 maki maki  4096 mai    3 16:13 .
drwxrwxrwt 26 root root 40960 mai    3 16:13 ..
-rw-------  1 maki maki     0 mai    3 16:12 lu30353r4wuxw.tmp
-rw-------  1 maki maki     0 mai    3 16:13 lu30353r4wuxx.tmp

Les fichiers temporaires sont vides, c’est pour cela qu’extraire les fichiers temporaires du dump mémoire ne donnent rien, ce n’est pas le profile volatility ou encore volatility lui même qui ne fonctionne pas correctement, c’est juste que les fichiers existent, mais ne contiennent rien.

La structure d’un Docx

Si ces fichiers n’existent pas dans les fichiers temporaires, alors la structure du DOCX doit exister en mémoire. Mais avant, comment ça fonctionne un DOCX. On vient de créer le fichier suivant: test_docx.docx

Si on prend les premiers octets:

➜  Wu file test_docx.docx 
test_docx.docx: Microsoft Word 2007+
➜  Wu hexdump -C test_docx.docx| head -n 5
00000000  50 4b 03 04 14 00 08 08  08 00 b3 71 a3 50 00 00  |PK.........q.P..|
00000010  00 00 00 00 00 00 00 00  00 00 0b 00 00 00 5f 72  |.............._r|
00000020  65 6c 73 2f 2e 72 65 6c  73 ad 92 4d 4b 03 41 0c  |els/.rels..MK.A.|
00000030  86 ef fd 15 43 ee dd 6c  2b 88 c8 ce f6 22 42 6f  |....C..l+...."Bo|
00000040  22 f5 07 84 99 ec ee d0  ce 07 33 69 ad ff de 41  |".........3i...À|

https://en.wikipedia.org/wiki/List_of_file_signatures

En théorie, il est possible de décompresser un fichier DOCX:

➜  unzip test_docx.docx 

➜  tree .
.
├── [Content_Types].xml
├── docProps
│   ├── app.xml
│   └── core.xml
├── _rels
├── test_docx.docx
└── word
    ├── document.xml
    ├── fontTable.xml
    ├── _rels
    │   └── document.xml.rels
    ├── settings.xml
    └── styles.xml

4 directories, 9 files

Il y a donc tout un tas de fichiers XML. Avec notre DOCX, on sait que le contenu est “TEST D’UN FICHIER DOCX”. Voyons où il apparait:

Good, maintenant on sait qu’on cherche le fichier “document.xml” probablement ouvert en mémoire de soffice.bin. Pour s’en assurer et éviter de faire un grep comme un débile sur tout le dump, on peut faire une yararule grâce à volatility, en cherchant à partir de <w:document:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_yarascan -Y "<w:document"

Task: soffice.bin pid 119615 rule r1 addr 0x5636239689e0
0x5636239689e0  3c 77 3a 64 6f 63 75 6d 65 6e 74 20 78 6d 6c 6e   <w:document.xmln
0x5636239689f0  73 3a 6f 3d 22 75 72 6e 3a 73 63 68 65 6d 61 73   s:o="urn:schemas
0x563623968a00  2d 6d 69 63 72 6f 73 6f 66 74 2d 63 6f 6d 3a 6f   -microsoft-com:o
0x563623968a10  66 66 69 63 65 3a 6f 66 66 69 63 65 22 20 78 6d   ffice:office".xm
0x563623968a20  6c 6e 73 3a 72 3d 22 68 74 74 70 3a 2f 2f 73 63   lns:r="http://sc
0x563623968a30  68 65 6d 61 73 2e 6f 70 65 6e 78 6d 6c 66 6f 72   hemas.openxmlfor
0x563623968a40  6d 61 74 73 2e 6f 72 67 2f 6f 66 66 69 63 65 44   mats.org/officeD
0x563623968a50  6f 63 75 6d 65 6e 74 2f 32 30 30 36 2f 72 65 6c   ocument/2006/rel
0x563623968a60  61 74 69 6f 6e 73 68 69 70 73 22 20 78 6d 6c 6e   ationships".xmln
0x563623968a70  73 3a 76 3d 22 75 72 6e 3a 73 63 68 65 6d 61 73   s:v="urn:schemas
0x563623968a80  2d 6d 69 63 72 6f 73 6f 66 74 2d 63 6f 6d 3a 76   -microsoft-com:v
0x563623968a90  6d 6c 22 20 78 6d 6c 6e 73 3a 77 3d 22 68 74 74   ml".xmlns:w="htt
0x563623968aa0  70 3a 2f 2f 73 63 68 65 6d 61 73 2e 6f 70 65 6e   p://schemas.open
0x563623968ab0  78 6d 6c 66 6f 72 6d 61 74 73 2e 6f 72 67 2f 77   xmlformats.org/w
0x563623968ac0  6f 72 64 70 72 6f 63 65 73 73 69 6e 67 6d 6c 2f   ordprocessingml/
0x563623968ad0  32 30 30 36 2f 6d 61 69 6e 22 20 78 6d 6c 6e 73   2006/main".xmlns

Bon et bien on dirait que le document.xml a été retrouvé à l’adresse 0x5636239689e0.

Extraction de la zone mémoire de soffice.bin

Il ne reste plus qu’à extraire cette portion de la mémoire. D’abord localiser notre adresse trouvée précédemment:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_proc_maps -p 119615
Offset             Pid      Name                 Start              End                Flags               Pgoff Major  Minor  Inode      File Path
------------------ -------- -------------------- ------------------ ------------------ ------ ------------------ ------ ------ ---------- ---------
[...]
0xffff9d72848edd00   119615 soffice.bin          0x000056361c0cb000 0x0000563623f25000 rw-                   0x0      0      0          0 [heap]
[...]

On dirait bien que notre adresse se trouve dans la heap du processus soffice.bin. Il ne reste qu’à l’extraire:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_dump_map -p 119615 -s 0x000056361c0cb000 -D /opt/usr_land/wu/
Task       VM Start           VM End                         Length Path
---------- ------------------ ------------------ ------------------ ----
    119615 0x000056361c0cb000 0x0000563623f25000          0x7e5a000 /opt/usr_land/Wu/task.119615.0x56361c0cb000.vma

Chercher la structure du XML

Maintenant, l’outil indispensable:

Flag

FCSC{PQHJRTSFYH-3467024-LSHRFLDFGA}


Académie - Dans les nuages

Le poste en cours d’analyse est connecté à un serveur web à l’adresse 10.42.42.132. Le serveur web est protégé par une authentification.

Retrouvez le nom d’utilisateur et le mot de passe de cette connexion. Format du flag :

FCSC{utilisateur:mot_de_passe}. Ce flag est sensible à la casse.

TL;DR

Pour ce challenge, il faut recréer l’infrastructure de l’authentification afin de voir où se trouvent en mémoire les identifiants. Au final, on se retrouve dans une section mémoire de chromium avec les identifiants à la suite encodés en utf16.

État des lieux

Alors pour ce dernier challenge d’une série très intéressante, c’est vraiment le boss final. Si on devait faire l’analogie de ce challenge avec autre chose, on pourrait dire que c’est comme chercher une aiguille, dans un tas d’autres aiguilles et le tout dans un gros tas de foin.

Donc là, on va essayer de ne pas partir comme un débile, parce que déjà que dans les challenges de forensic c’est facile de se perdre, mais alors ici, c’est presque sûr.

Ce qu’on sait:

  • Adresse IP du serveur web : 10.42.42.132;
  • Authentification par login / mot de passe (basic auth ? form html ?).

Ce qu’on cherche:

  • Des identifiants (user/password).

La première chose qui peut être utile est de faire un yarascan sur l’adresse IP pour essayer d’avoir des pistes de recherches:

➜  vol.py --plugins=/opt/plug_vol/ -f usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_yarascan -Y "10.42.42.132" | tee /opt/usr_land/yarascan_10.42.42.132.txt

➜  yarascan_10.42.42.132.txt | grep -B 2 Task
[.. L'output est suuuuper verbeuse, donc j'vais trier pour le WU :) ..]
Task: chromium pid 119148 rule r1 addr 0x55f4346630eb
0x55f4346630eb  31 30 2e 34 32 2e 34 32 2e 31 33 32 2f 70 61 6e   10.42.42.132/pan
0x55f4346630fb  65 6c 2f 00 00 00 00 00 00 00 00 00 00 04 00 00   el/.............
[...]
Task: chromium pid 119187 rule r1 addr 0x7fb1cc18dfa7
0x7fb1cc18dfa7  31 30 2e 34 32 2e 34 32 2e 31 33 32 2f 66 61 76   10.42.42.132/fav
0x7fb1cc18dfb7  69 63 6f 6e 2e 69 63 6f 00 10 00 00 00 00 00 00   icon.ico........
[...]
Task: chromium pid 119218 rule r1 addr 0x7efedc023373
0x7efedc023373  31 30 2e 34 32 2e 34 32 2e 31 33 32 2f 70 61 6e   10.42.42.132/pan
0x7efedc023383  65 6c 2f 00 00 00 00 00 00 00 00 00 00 03 00 00   el/.............
--
Task: chromium pid 119233 rule r1 addr 0x55f4febd60eb
0x55f4febd60eb  31 30 2e 34 32 2e 34 32 2e 31 33 32 2f 70 61 6e   10.42.42.132/pan
0x55f4febd60fb  65 6c 2f 00 00 00 00 00 00 00 00 00 00 04 00 00   el/.............
[...]
Task: chromium pid 119274 rule r1 addr 0x55f504bf68b9
0x55f504bf68b9  31 30 2e 34 32 2e 34 32 2e 31 33 32 3a 38 30 2c   10.42.42.132:80,
0x55f504bf68c9  2a 22 3a 7b 22 6c 61 73 74 5f 6d 6f 64 69 66 69   *":{"last_modifi
--
Task: chromium pid 119302 rule r1 addr 0x55f4febd60eb
0x55f4febd60eb  31 30 2e 34 32 2e 34 32 2e 31 33 32 2f 70 61 6e   10.42.42.132/pan
0x55f4febd60fb  65 6c 2f 00 00 00 00 00 00 00 00 00 00 04 00 00   el/.............
[...]
Task: chromium pid 119364 rule r1 addr 0x16a55ae239d3
0x16a55ae239d3  31 30 2e 34 32 2e 34 32 2e 31 33 32 65 02 00 00   10.42.42.132e...
0x16a55ae239e3  00 0e 00 00 00 00 00 00 4a 41 63 63 65 73 73 20   ........JAccess.
--
Task: soffice.bin pid 119615 rule r1 addr 0x7fee90d25b1b
0x7fee90d25b1b  31 30 2e 34 32 2e 34 32 2e 31 33 32 44 43 46 37   10.42.42.132DCF7
0x7fee90d25b2b  41 35 38 39 35 42 46 37 41 37 45 43 31 37 41 39   A5895BF7A7EC17A9
--
Task: chromium pid 119757 rule r1 addr 0x55f4febd60eb
0x55f4febd60eb  31 30 2e 34 32 2e 34 32 2e 31 33 32 2f 70 61 6e   10.42.42.132/pan
0x55f4febd60fb  65 6c 2f 00 00 00 00 00 00 00 00 00 00 04 00 00   el/.............

Bon, cet énorme output nous apprend plusieurs choses:

  • Probablement que Chromium a été utilisé pour la connexion ;
  • Il existe un dossier /panel/.

Il existe d’autres outils que volatility pour analyser la mémoire, par exemple bulk_extractor permet notamment de créer un pcap avec les traces laissées dans le dump:

➜  bulk_extractor -o bulk dmp.mem

Le pcap généré nous donne quelques informations supplémentaires:

On répond donc à une question du début sur le type d’authentification, c’est une Basic authentication, le realm est Panel \o/ et le serveur web utilisé est un nginx 1.14.2. Ce qui est un marqueur supplémentaire pour notre recherche.

Avant d’aller plus loin, une connexion via Basic authentication, se fait avec une pop qui demande les identifiants. Ces identifiants sont placés dans un header HTTP Authorization: Basic base64(username:password).

Après quelques recherches non concluantes autour de ça, il est temps de revoir la méthode de travail. Le but va être de recréer une connexion entre un client Chromium et un serveur web.

Proof of concept

Mise en place de l’infrastructure

Pour la partie client, on a déjà la VM de test: celle qui nous a permis de faire le profil volatility dans les challenges précédents. Il ne reste qu’à installer une interface graphique (comme xfce4) et chromium.

Pour le serveur nginx, un bon vieux Docker Debian fera l’affaire en suivant un bon p’tit tuto des familles:

https://ahmet.im/blog/docker-http-basic-auth/

➜  ~ docker run --name basic_authent -p4242:4242 -ti debian:latest /bin/bash
➜  (docker) apt-get install nginx apache2-utils
➜  (docker) htpasswd -c /etc/nginx/.htpasswd maki
Password: JeSuisUnGrosZgueg
➜  (docker) sed -i 's/user .*;/user root;/' /etc/nginx/nginx.conf
➜  (docker) tee /etc/nginx/sites-enabled/docker <<EOF 
upstream docker {
  server unix:/var/run/docker.sock;
}

server {
  listen 4242 default_server;
  location / {
    proxy_pass http://docker;
    auth_basic_user_file /etc/nginx/.htpasswd;
    auth_basic "SecretPanelMofo";
  }
}
EOF
➜  (docker) service nginx restart

L’infrastructure a l’air d’être prête, on redémarre la machine cliente pour vider la mémoire et on initialise une connexion. Le prompt apparait et on entre 1 fois les mauvais identifiants et 1 fois les bons, pour avoir un statut 401 comme dans bulk_extractor et un 200, parce que c’est quand même les bons identifiants qu’on cherche à la base.

Les mauvais identifiants:

USERPASBON_MARQUEUR : USERPASBON_MARQUEURPASS

Les bons identifiants:

maki : JeSuisUnGrosZgueg

Maintenant on met la machine en pause et on récupère la mémoire. Utilisant VMWare, il est possible de récupérer la mémoire avec le fichier vmem.

Analyse du PoC

Comme on a utilisé la même machine virtuelle que pour la création du profil, on s’épargne une étape fastidieuse:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/localtest/test\ ecsc\ deb10-f35aa608.vmem --profile=Linuxecsc_deb540x64 linux_banner
Linux version 5.4.0-4-amd64 (debian-kernel@lists.debian.org) (gcc version 9.2.1 20200203 (Debian 9.2.1-28)) #1 SMP Debian 5.4.19-1 (2020-02-13)

Il est temps de partir à la recherche de nos marqueurs, pas classe, mais efficace. Avant d’attendre 5 minutes qu’un Yarascan se termine, on va vérifier que les identifiants apparaissent bien en clair dans la mémoire:

➜  localtest strings test\ ecsc\ deb10-f35aa608.vmem| grep -E '^maki$|JeSuisUnGrosZgueg' | sort | uniq
maki

➜  strings -e l test\ ecsc\ deb10-f35aa608.vmem| grep -E 'maki|JeSuisUnGrosZgueg' | sort | uniq
mikmaki
[...]
maki
[...]
JeSuisUnGrosZgueg

Par contre, les identifiants en mémoire sont encodés sur 16 bytes. Il faut donc adapter la rule en conséquence:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/localtest/test\ ecsc\ deb10-f35aa608.vmem --profile=Linuxecsc_deb540x64 linux_yarascan -Y "{4a ?? 65 ?? 53 ?? 75 ?? 69 ?? 73 ?? 55 ?? 6e ?? 47}"

Task: chromium pid 861 rule r1 addr 0x7f30980e8ea0
0x7f30980e8ea0  4a 00 65 00 53 00 75 00 69 00 73 00 55 00 6e 00   J.e.S.u.i.s.U.n.
0x7f30980e8eb0  47 00 72 00 6f 00 73 00 5a 00 67 00 75 00 65 00   G.r.o.s.Z.g.u.e.
0x7f30980e8ec0  67 00 00 00 00 00 00 00 95 00 00 00 00 00 00 00   g...............
0x7f30980e8ed0  10 af 00 8c 30 7f 00 00 d0 08 00 98 30 7f 00 00   ....0.......0...
0x7f30980e8ee0  a0 dd 06 98 30 7f 00 00 00 00 00 00 00 00 00 00   ....0...........
0x7f30980e8ef0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0x7f30980e8f00  00 00 00 00 00 00 00 00 00 01 09 98 30 7f 00 00   ............0...
0x7f30980e8f10  40 3a 02 98 30 7f 00 00 48 00 00 00 00 00 00 00   @:..0...H.......
0x7f30980e8f20  88 3a 02 98 30 7f 00 00 50 02 00 00 00 00 00 00   .:..0...P.......
0x7f30980e8f30  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0x7f30980e8f40  00 00 00 00 00 00 00 00 e8 49 8a 9d ad c4 e5 bb   .........I......
0x7f30980e8f50  be 3d 6c 31 df 92 20 98 25 00 00 00 00 00 00 00   .=l1....%.......
0x7f30980e8f60  01 00 00 00 00 7f 00 00 00 00 00 00 00 00 00 00   ................
0x7f30980e8f70  69 64 61 74 6f 72 00 00 a5 00 00 00 00 00 00 00   idator..........
0x7f30980e8f80  02 00 01 bb d8 3a ce e3 00 00 00 00 00 00 00 00   .....:..........
0x7f30980e8f90  77 77 77 2e 67 73 74 61 74 69 63 2e 63 6f 6d 00   www.gstatic.com.

Sans trop de surprises, le mot de passe est présent dans la mémoire de chromium. Mais où exactement:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/localtest/test\ ecsc\ deb10-f35aa608.vmem --profile=Linuxecsc_deb540x64 linux_proc_maps -p 861

Offset             Pid      Name                 Start              End                Flags               Pgoff Major  Minor  Inode      File Path
------------------ -------- -------------------- ------------------ ------------------ ------ ------------------ ------ ------ ---------- ---------
[...]
0xffff99baf6a3cd80      861 chromium             0x000055943fbb1000 0x000055943fc9f000 rw-                   0x0      0      0          0 [heap]
0xffff99baf6a3cd80      861 chromium             0x00007f3084000000 0x00007f308416c000 rw-                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f308416c000 0x00007f3088000000 ---                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f3088000000 0x00007f3088021000 rw-                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f3088021000 0x00007f308c000000 ---                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f308c000000 0x00007f308c062000 rw-                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f308c062000 0x00007f3090000000 ---                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f3090000000 0x00007f3090021000 rw-                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f3090021000 0x00007f3094000000 ---                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f3094000000 0x00007f30941ba000 rw-                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f30941ba000 0x00007f3098000000 ---                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f3098000000 0x00007f3098118000 rw-                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f3098118000 0x00007f309c000000 ---                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f309c000000 0x00007f309c021000 rw-                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f309c021000 0x00007f30a0000000 ---                   0x0      0      0          0 
0xffff99baf6a3cd80      861 chromium             0x00007f30a16d5000 0x00007f30a16de000 r--                   0x0      8      1    1063603 /
[...]

Bon, on se retrouve dans une zone mémoire perdue au milieu de tout ce bazar, entre la heap et la mémoire allouée par des fichiers. Il suffit de l’extraire pour trouver ce qu’il y a dedans:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/localtest/test\ ecsc\ deb10-f35aa608.vmem --profile=Linuxecsc_deb540x64 linux_dump_map -s 0x00007f3098000000 -p 861 -D /opt/usr_land/wu/

Task       VM Start           VM End                         Length Path
---------- ------------------ ------------------ ------------------ ----
       861 0x00007f3098000000 0x00007f3098118000           0x118000 /opt/usr_land/Wu/task.861.0x7f3098000000.vma

Et dedans on retrouve bien nos identifiants:

➜  strings -e l task.861.0x7f3098000000.vma
maki
maki
f us
kN6U
_MAR
JeSuisUnGrosZgueg
4N6U
	dgknorst}
46]_cfilv{
').01238[hjmuy

A première vue, il n’y a pas de marqueurs efficaces pour localiser les identifiants dans le dump du challenge. Par contre, il peut être intéressant de regarder tous ça d’un point de vu hexa:

000e8e80  e0 c1 0d 98 30 7f 00 00  60 86 0e 98 30 7f 00 00  |....0...`...0...|
000e8e90  70 6e 0e 98 30 7f 00 00  35 00 00 00 00 00 00 00  |pn..0...5.......|
000e8ea0  4a 00 65 00 53 00 75 00  69 00 73 00 55 00 6e 00  |J.e.S.u.i.s.U.n.|
000e8eb0  47 00 72 00 6f 00 73 00  5a 00 67 00 75 00 65 00  |G.r.o.s.Z.g.u.e.|
000e8ec0  67 00 00 00 00 00 00 00  95 00 00 00 00 00 00 00  |g...............|
000e8ed0  10 af 00 8c 30 7f 00 00  d0 08 00 98 30 7f 00 00  |....0.......0...|
000e8ee0  a0 dd 06 98 30 7f 00 00  00 00 00 00 00 00 00 00  |....0...........|
000e8ef0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000e8f00  00 00 00 00 00 00 00 00  00 01 09 98 30 7f 00 00  |............0...|
000e8f10  40 3a 02 98 30 7f 00 00  48 00 00 00 00 00 00 00  |@:..0...H.......|

Quelque chose qui pourrait être intéressant est de chercher l’adresse de la chaine de caractère: 0x7f30980e8ea0. Et là, un alignement de planète:

  • Vert: Adresse de la chaine pour le “realm”
  • Le second pointeur après la chaine du realm (rouge): Adresse pour le nom d’utilisateur
  • Le troisième pointeur après la chaine du realm (jaune): Adresse pour le mot de passe

C ce n’est pas si étonnant, dans les sources de Chromium il y a ceci:

https://chromium.googlesource.com/chromium/src/net/+/master/http/http_auth_controller.cc

On commence à y voir plus clair dans cette histoire, mais faut-il encore trouver dans quel processus chercher:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/localtest/test\ ecsc\ deb10-f35aa608.vmem --profile=Linuxecsc_deb540x64 linux_pstree | grep chromium

.....chromium        821             1000           
.......chromium      836             1000           
........chromium     838             1000           
.........chromium    981             1000           
.........chromium    993             1000           
chromium             859             1000           
.chromium            940             1000           
chromium             861             1000           

Pour savoir correctement d’où vient notre processus, même s’il n’a pas de parent, il a été lancé par quelque chose. Les plugins pslist et psscan vont répondre à ces questions:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/test\ ecsc\ deb10-f35aa608.vmem --profile=Linuxecsc_deb540x64 linux_pslist | grep 861

Offset             Name                 Pid             PPid            Uid             Gid    DTB                Start Time
------------------ -------------------- --------------- --------------- --------------- ------ ------------------ ----------
0xffff99baf6a3cd80 chromium             861             852             1000            1000   0x000000007692e000 2020-04-28 19:19:35 UTC+0000

Le processus 852 n’est autre que ThreadPoolSingl:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/test\ ecsc\ deb10-f35aa608.vmem --profile=Linuxecsc_deb540x64 linux_psscan | grep 852

Offset             Name                 Pid             PPid            Uid             Gid    DTB                Start Time
------------------ -------------------- --------------- --------------- --------------- ------ ------------------ ----------
0x00000000768e8000 ThreadPoolSingl      852             -               -               -      ------------------ -

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/test\ ecsc\ deb10-f35aa608.vmem --profile=Linuxecsc_deb540x64 linux_pslist | grep 852

Offset             Name                 Pid             PPid            Uid             Gid    DTB                Start Time
------------------ -------------------- --------------- --------------- --------------- ------ ------------------ ----------
0xffff99baf6930000 chromium             859             852             1000            1000   0x0000000076af0000 2020-04-28 19:19:35 UTC+0000
0xffff99baf6a3cd80 chromium             861             852             1000            1000   0x000000007692e000 2020-04-28 19:19:35 UTC+0000

Le processus que l’on cherche est donc le second lancé par ThreadPoolSingl.

À la recherche des identifiants perdus

Le plugin pstree de volatility a déjà été exécuté dans les challenges précédents:

➜  cat linux_pstree | grep chromium
.....chromium        119148          1001           
.......chromium      119163          1001           
........chromium     119165          1001           
.........chromium    119218          1001           
.........chromium    119233          1001           
.........chromium    119274          1001           
.........chromium    119302          1001           
.........chromium    119364          1001           
.........chromium    119380          1001           
.........chromium    119394          1001           
.........chromium    119735          1001           
.........chromium    119757          1001           
chromium             119184          1001           
chromium             119187          1001           

En suivant la logique précédente, le processus qui nous intéresse est le 119187:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_pslist | grep chromium
Volatility Foundation Volatility Framework 2.6.1
0xffff9d72c1848f80 chromium             119148          1378            1001            1001   0x0000000013768000 2020-03-26 23:27:39 UTC+0000
0xffff9d72940e8000 chromium             119163          119162          1001            1001   0x0000000001f0c000 2020-03-26 23:27:39 UTC+0000
0xffff9d72940eae80 chromium             119165          119163          1001            1001   0x000000004213c000 2020-03-26 23:27:39 UTC+0000
0xffff9d72bd57be00 chromium             119184          119180          1001            1001   0x0000000011360000 2020-03-26 23:27:39 UTC+0000
0xffff9d72bd57cd80 chromium             119187          119180          1001            1001   0x0000000011296000 2020-03-26 23:27:39 UTC+0000
0xffff9d729ce6dd00 chromium             119218          119165          1001            1001   0x00000000107da000 2020-03-26 23:27:40 UTC+0000
0xffff9d7283045d00 chromium             119233          119165          1001            1001   0x0000000003058000 2020-03-26 23:27:40 UTC+0000
0xffff9d72824e3e00 chromium             119274          119165          1001            1001   0x00000000025dc000 2020-03-26 23:27:46 UTC+0000
0xffff9d7282481f00 chromium             119302          119165          1001            1001   0x0000000004ece000 2020-03-26 23:27:46 UTC+0000
0xffff9d7284f86c80 chromium             119364          119165          1001            1001   0x00000000052fe000 2020-03-26 23:27:50 UTC+0000
0xffff9d7284f82e80 chromium             119380          119165          1001            1001   0x000000001a206000 2020-03-26 23:28:02 UTC+0000
0xffff9d729a269f00 chromium             119394          119165          1001            1001   0x000000001a29e000 2020-03-26 23:28:02 UTC+0000
0xffff9d72940e4d80 chromium             119735          119165          1001            1001   0x0000000011234000 2020-03-26 23:39:05 UTC+0000
0xffff9d72c1848000 chromium             119757          119165          1001            1001   0x0000000002e62000 2020-03-26 23:39:08 UTC+0000

On peut exclure tous les processus dont le père est 119165, car il s’agit du broker de Chromium, mais comme vu précédemment le processus que l’on cherche ne dépend pas du broker.

Le broker de Chromium est le processus principal, en l’occurrence: 119165. Ensuite, chaque processus fils seront des sites, des iframes, etc… Ces processus seront sandboxés par Chromium:

https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md

Les processus 119218, 119233, 119274, 119302, 119364, 119380, 119394, 119735, 119757, ne nous intéressent donc pas dans ce cas.

Pour coller au PoC fait dans le chapitre précédent, il faut trouver deux processus chromium avec le même parent. Le processus 119180 doit donc être ThreadPoolSingl.

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_pslist | grep 119180

Offset             Name                 Pid             PPid            Uid             Gid    DTB                Start Time
------------------ -------------------- --------------- --------------- --------------- ------ ------------------ ----------
0xffff9d72bd57be00 chromium             119184          119180          1001            1001   0x0000000011360000 2020-03-26 23:27:39 UTC+0000
0xffff9d72bd57cd80 chromium             119187          119180          1001            1001   0x0000000011296000 2020-03-26 23:27:39 UTC+0000

A partir de ce point, il existe au moins deux méthodes pour trouver les identifiants. On sait qu’il faut creuser dans le processus 119187, car comme dans le PoC, il s’agit du second processus exécuté.

Solution 1 - Trouver la structure en mémoire

En connaissant la structure en mémoire, il est possible de commencer à chercher en partant de la chaine du “realm”:

On peut commencer par chercher le realm Panel-\o/:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_yarascan -Y "Panel-" -p 119187

[...]
Task: chromium pid 119187 rule r1 addr 0x7fb1cc163630
0x7fb1cc163630  50 61 6e 65 6c 2d 6f 2f 00 64 6a 37 33 61 65 77   Panel-o/.dj73aew
0x7fb1cc163640  00 00 00 00 33 69 64 74 70 0a 0e cc b1 7f 00 00   ....3idtp.......
0x7fb1cc163650  17 00 00 00 00 00 00 00 1e 00 00 00 00 00 00 00   ................
0x7fb1cc163660  6a 4d bb 5f 7a 34 0a 9f e0 a0 0e cc b1 7f 00 00   jM._z4..........
0x7fb1cc163670  09 00 00 00 00 00 00 00 0e 00 00 00 00 00 00 00   ................
0x7fb1cc163680  34 19 dd 3c f7 a7 5d b8 90 5f 17 cc b1 7f 00 00   4..<..].._......
0x7fb1cc163690  08 00 00 00 00 00 00 00 0e 00 00 00 00 00 00 00   ................
0x7fb1cc1636a0  88 bb 94 35 52 22 36 60 03 00 00 00 d1 d0 c5 85   ...5R"6`........
0x7fb1cc1636b0  90 73 15 cc b1 7f 00 00 90 73 15 cc b1 7f 00 00   .s.......s......
0x7fb1cc1636c0  01 00 00 00 00 00 00 00 ac 61 8d 0e 00 00 00 00   .........a......
0x7fb1cc1636d0  0d ff 2b 37 00 00 00 00 52 7b 0c 9d 60 00 2f 00   ..+7....R{..`./.
0x7fb1cc1636e0  a0 03 00 00 00 00 00 00 25 02 00 00 00 00 00 00   ........%.......
0x7fb1cc1636f0  10 93 18 cc b1 7f 00 00 e0 93 1d cc b1 7f 00 00   ................
0x7fb1cc163700  90 8d 15 cc b1 7f 00 00 18 00 00 00 00 00 00 00   ................
0x7fb1cc163710  18 00 00 00 00 00 00 00 4d 31 6a 9e 31 0a a1 0a   ........M1j.1...
0x7fb1cc163720  bb 01 7f a4 fc a1 dc f1 00 00 0b 74 24 db 8d cc   ...........t$...

Cette yararule ne ressort que 4 résultats, il est donc aisé de trouver le bloc qui nous intéresse. En suivant la logique définie précédemment:

  • Second pointeur (rouge), adresse de username: 0x7fb1cc0ea0e0
  • Troisième pointeur (vert) adresse de password: 0x7fb1cc175f90

Il n’y a plus qu’à extraire la Virtual Memory Area (VMA) et regarder ce qu’il y à ces adresses:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_proc_maps -p 119187

[...]
0xffff9d72bd57cd80   119187 chromium             0x00007fb1cc000000 0x00007fb1cc4e2000 rw-                   0x0      0      0          0 
[...]

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_dump_map -s 0x00007fb1cc000000 -p 119187 -D /opt/usr_land/wu/119187/

Le nom d’utilisateur:

➜  hexdump -C -s 0x0ea0e0 ../187/Wu/task.119187.0x7fb1cc000000.vma | head -n 4
000ea0e0  41 00 64 00 6d 00 69 00  6e 00 33 00 4b 00 7a 00  |A.d.m.i.n.3.K.z.|
000ea0f0  37 00 00 00 00 00 00 00  a0 26 18 cc b1 7f 00 00  |7........&......|
000ea100  00 00 00 00 00 00 00 00  25 00 00 00 00 00 00 00  |........%.......|
000ea110  60 ab 13 cc b1 7f 00 00  20 06 1e cc b1 7f 00 00  |`....... .......|

Admin3Kz7

Le mot de passe:

➜  hexdump -C -s 0x175f90 ../187/Wu/task.119187.0x7fb1cc000000.vma | head -n 4
00175f90  35 00 73 00 64 00 74 00  59 00 68 00 36 00 38 00  |5.s.d.t.Y.h.6.8.|
00175fa0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00175fb0  11 00 00 00 00 00 00 00  75 00 00 00 00 00 00 00  |........u.......|
00175fc0  02 00 00 00 b1 7f 00 00  c0 93 02 cc b1 7f 00 00  |................|

5sdtYh68

Solution 2 - La débrousailleuse

Cette solution a le mérite d’etre plus rapide que la première. La première chose à faire est d’extraires tous les VMA du processus:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_dump_map -p 119187 -D /opt/usr_land/119187/all_vma/

➜  strings --print-file-name 119187/all_vma/* | grep 'http://10.42.42.132'        
task.119187.0x7fb1cc000000.vma: http://10.42.42.132/
task.119187.0x7fb1cc000000.vma: http://10.42.42.132/
task.119187.0x7fb1cc000000.vma: http://10.42.42.132/favicon.ico
task.119187.0x7fb1cc000000.vma: http://10.42.42.132/
task.119187.0x7fb1cc000000.vma: http://10.42.42.132/panel/

Au final, une seule portion ressort, il suffit de faire un strings UTF-16 dessus pour voir la magie opérer:

➜  strings -e l -n 6 task.119187.0x7fb1cc000000.vma
Admin3Kz7
5sdtYh68
 !&(/01=CEIORS_bcfghmvwxy}
-78;?ABDFGLMNPTUVW[]jkz{|
+234569HJKQXYZq~

Flag

FCSC{Admin3Kz7:5sdtYh68}

Bonus 1 - Keepass

En inspectant les processus en cours d’exécution, on remarque un Keepass:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_psaux

Pid    Uid    Gid    Arguments
119514 1001   1001   /usr/bin/cli /usr/lib/keepass2/KeePass.exe

Ne connaissant pas ce processus cli, on demande à Google:

$ which cli
/usr/bin/cli
$ which mono
/usr/bin/mono
$ ls -l /usr/bin/mono
-rwxr-xr-x 1 root root 3054984 Jul 24  2012 /usr/bin/mono
$ ls -l /usr/bin/cli
lrwxrwxrwx 1 root root 21 Apr 17 16:16 /usr/bin/cli -> /etc/alternatives/cli
$ ls -l /etc/alternatives/cli
lrwxrwxrwx 1 root root 13 Apr 17 16:16 /etc/alternatives/cli -> /usr/bin/mono

https://stackoverflow.com/questions/16426041/what-is-difference-between-cli-and-mono-in-unix https://fr.wikipedia.org/wiki/Common_Language_Infrastructure

État des lieux

Donc on peut dire que cli == mono. Mono est un utilitaire permettant d’exécuter le framework .NET de Microsoft sur Linux. Le fait d’avoir un “Keepass.exe” prend son sens.

On sait que les extensions des containers Keepass ont l’extension: .kdbx

➜  cestlarentree cat str_* | grep -i kdbx | sort | uniq
/home/Lesage/Documents/pass/mdp.kdbx
kdbx
KDBX_DetailsFull_HTML.xsl
KDBX_DetailsLight_HTML.xsl
KDBX_Tabular_HTML.xsl
mdp.kdbx
mdp.kdbx - KeePass
					<Path>../../../home/Lesage/Documents/pass/mdp.kdbx</Path>
			<Path>../../../home/Lesage/Documents/pass/mdp.kdbx</Path>

/home/Lesage/Documents/pass/mdp.kdbx

Les base de données Keepass semblent avoir une signature:

➜  file ./test.kdbx 
test.kdbx: Keepass password database 2.x KDBX

➜  hexdump -C ./test.kdbx | head -n 2 
00000000  03 d9 a2 9a 67 fb 4b b5  01 00 03 00 02 10 00 31  |....g.K........1|
00000010  c1 f2 e6 bf 71 43 50 be  58 05 21 6a fc 5a ff 03  |....qCP.X.!j.Z..|

https://keepass.info/help/base/repair.html

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_yarascan -Y "{03 D9 A2 9A 67 FB 4B B5}"
# Y a wallou

Le yarascan n’est pas très concluant. Faisons un test en local pour voir si le contenu du container se trouve dans la mémoire.

Test en local

Une fois n’est pas coutume, on va procéder de la même manière que dans les challenges précédents en installant un Keepass2 sur notre VM de test.

$ apt install keepass2

  • Master key : JeSuisUnSuperPassword
  • Nom de la bdd : TestFCSC_Database
  • Entrée utilisateur : TESTENTREEKEEPASS_maki
  • Entrée mot de passe : TESTENTREEKEEPASS_PASSWORD

La première chose à faire est donc d’essayer de retrouver la Master key:

➜  strings test\ ecsc\ deb10-f35aa608.vmem > str
➜  strings -e l test\ ecsc\ deb10-f35aa608.vmem > str_el
➜  cat str* | grep "JeSuisUnSuper"

Ca ne donne rien. Avant de se décourager, on peut essayer pour les entrées:

➜  cat str* | grep -E "TESTENTREEKEEPASS_maki|TESTENTREEKEEPASS_PASSWORD" | uniq | sort
[...]
TESTENTREEKEEPASS_maki
TESTENTREEKEEPASS_maki
TESTENTREEKEEPASS_PASSWORD
TESTENTREEKEEPASS_PASSWORD
 Title: TESTENTREEKEEPASS_maki, Password: ********, Creation Time: 05/05/2020 17:21:55, Last Modification Time: 
[...]

Bon, pas besoin de la master key s’il est possible de récupérer les informations. Alors, maintenant il faut réussir à localiser précisément où sont les entrées:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/test\ ecsc\ deb10-f35aa608.vmem --profile=Linuxecsc_deb540x64 linux_dump_map -p 814 -D /opt/usr_land/vma/

➜  strings --print-file-name -e l * | grep -E "TESTENTREEKEEPASS_maki|TESTENTREEKEEPASS_PASSWORD" | cut -d':' -f1  | uniq
task.814.0x7f3c83800000.vma

➜  strings -e l task.814.0x7f3c83800000.vma | grep -E "TESTENTREEKEEPASS_maki|TESTENTREEKEEPASS_PASSWORD" | sort | uniq
[...]
MeGYxEBVnEsTrGroup:HPRIZOIDrzIXZ Database, MeGYxEBVnEsTrTitle:HPRIZOIDrzIXZ TESTENTREEKEEPASS_maki, MeGYxEBVnEsTrPassword:HPRIZOIDrzIXZ ********, MeGYxEBVnEsTrCreation Time:HPRIZOIDrzIXZ 05/05/2020 17:21:55, MeGYxEBVnEsTrLast Modification Time:HPRIZOIDrzIXZ 05/05/2020 17:22:56
RIZOIDrzIXZ TESTENTREEKEEPASS_maki, MeGYxEBVnEsTrPassword:HPRIZO
s16 MeGYxEBVnEsTrGroup:HPRIZOIDrzIXZ Database, MeGYxEBVnEsTrTitle:HPRIZOIDrzIXZ TESTENTREEKEEPASS_maki, MeGYxEBVnEsTrPassword:HP
TESTENTREEKEEPASS_maki
TESTENTREEKEEPASS_PASSWORD
 Title: TESTENTREEKEEPASS_maki, Password: ********, Creation Time: 05/05/2020 17:21:55, Last Modification Time: 

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/test\ ecsc\ deb10-f35aa608.vmem --profile=Linuxecsc_deb540x64 linux_proc_maps -p 814
Volatility Foundation Volatility Framework 2.6.1
Offset             Pid      Name                 Start              End                Flags               Pgoff Major  Minor  Inode      File Path
------------------ -------- -------------------- ------------------ ------------------ ------ ------------------ ------ ------ ---------- ---------
[...]
0xffff98a5b9708f80      814 cli                  0x00007f3c83800000 0x00007f3c84401000 rw-                   0x0      0      0          0 
[...]

Il n’y a qu’une zone mémoire contenant les informations que l’on cherche. Maintenant, on sait que le nom d’utilisateur est relativement simple à trouver, car il faut partir d’une structure. En théorie, il y a juste à retrouver cette structure. Quant au mot de passe, c’est un peu différent, il faut réussir à voir ce qu’il y a autour et peut-être déterminer des marqueurs.

➜  strings -e l --print-file-name * | grep -C 2 "TESTENTREEKEEPASS_PASSWORD"
[...]
--
task.814.0x7f3c83800000.vma: SWFClass0.System.Windows.Forms.CheckBox
task.814.0x7f3c83800000.vma: TESTENTREEKEEPAS
task.814.0x7f3c83800000.vma: TESTENTREEKEEPASS_PASSWORD
task.814.0x7f3c83800000.vma: S_PASSWORD
task.814.0x7f3c83800000.vma: TESTENTREEKEEPASS_PASSWORD
task.814.0x7f3c83800000.vma: KeePass.UI.SecureTextBoxEx
task.814.0x7f3c83800000.vma: SWFClass0.KeePass.UI.SecureTextBoxEx
task.814.0x7f3c83800000.vma: TESTENTREEKEEPASS_PASSWORD
task.814.0x7f3c83800000.vma: KeePass.UI.SecureTextBoxEx
task.814.0x7f3c83800000.vma: SWFClass0.KeePass.UI.SecureTextBoxEx
--
task.814.0x7f3c83800000.vma: SWFClass0.System.Windows.Forms.Button
task.814.0x7f3c83800000.vma: TESTENTREEKEEPAS
task.814.0x7f3c83800000.vma: TESTENTREEKEEPASS_PASSWORD
task.814.0x7f3c83800000.vma: S_PASSWORD
task.814.0x7f3c83800000.vma: TESTENTREEKEEPASS_PASSWORD
task.814.0x7f3c83800000.vma: KeePass.UI.SecureTextBoxEx
task.814.0x7f3c83800000.vma: SWFClass0.KeePass.UI.SecureTextBoxEx
task.814.0x7f3c83800000.vma: TESTENTREEKEEPASS_PASSWORD
task.814.0x7f3c83800000.vma: System.Windows.Forms.TabControl

On peut voir que le mot de passe apparait plusieurs fois autour d’une méthode ou d’une classe: SWFClass0.KeePass.UI.SecureTextBoxEx. Ce qui est un nom plutôt évocateur.

Trouver le contenu du keepass

Maintenant qu’on a trouvé un certain nombre d’informations, il est temps de tester sur le Keepass du challenge. Donc même chose que pour le test en local, dump des zones mémoires du keepass et la fameuse technique de “la débroussailleuse”:

➜  python volatility/vol.py --plugins=/opt/plug_vol/ -f /opt/usr_land/dmp.mem --profile=Linuxecsc_deb540x64 linux_dump_map -p 119514 -D /opt/usr_land/keepass

➜  strings --print-file-name -e l * | grep "Title:"  
task.119514.0x7f3f46338000.vma: IyfTEvOFrjanWGroup:IdmZfHKZcQfnm 453, IyfTEvOFrjanWTitle:IdmZfHKZcQfnm 453, IyfTEvOFrjanWUser Name:IdmZfHKZcQfnm LePasSage, IyfTEvOFrjanWPassword:IdmZfHKZcQfnm ********, IyfTEvOFrjanWCreation Time:IdmZfHKZcQfnm 3/26/2020 2:36:34 PM, IyfTEvOFrjanWLast Modification Time:IdmZfHKZcQfnm 3/26/2020 2:39:59 PM
task.119514.0x7f3f46338000.vma: IyfTEvOFrjanWGroup:IdmZfHKZcQfnm 453, IyfTEvOFrjanWTitle:IdmZfHKZcQfnm 453, IyfTEvOFrjanWUser Name:IdmZfHKZcQfnm LePasSage, IyfTEvOFrjanWPassword:IdmZfHKZcQfnm ********, IyfTEvOFrjanWCreation Time:IdmZfHKZcQfnm 3/26/2020 2:36:34 PM, IyfTEvOFrjanWLast Modification Time:IdmZfHKZcQfnm 3/26/2020 2:39:59 PM
task.119514.0x7f3f46338000.vma: IyfTEvOFrjanWGroup:IdmZfHKZcQfnm 453, IyfTEvOFrjanWTitle:IdmZfHKZcQfnm 453, IyfTEvOFrjanWUser Name:IdmZfHKZcQfnm LePasSage, IyfTEvOFrjanWPassword:IdmZfHKZcQfnm ********, IyfTEvOFrjanWCreation Time:IdmZfHKZcQfnm 3/26/2020 2:36:34 PM, IyfTEvOFrjanWLast Modification Time:IdmZfHKZcQfnm 3/26/2020 2:39:59 PM
task.119514.0x7f3f51000000.vma: {\*\generator Mono RichTextBox;}\pard\f0\fs22 \ul Group:IdmZfHKZcQfnm 453, \ul Title:IdmZfHKZcQfnm 453, \ul User Name:IdmZfHKZcQfnm LePasSage, \ul Password:IdmZfHKZcQfnm ********, \ul Creation Time:IdmZfHKZcQfnm 3/26/2020 2:36:34 PM, \ul Last Modification Time:IdmZfHKZcQfnm 3/26/2020 2:39:59 PM\par

Si on nettoie un peu la sortie, on peut récupérer un nom d’utilisateur:

Group: 453, Title: 453, User Name: LePasSage, Password: ********, Creation Time: 3/26/2020 2:36:34 PM, Last Modification Time: 3/26/2020 2:39:59 PM

LePasSage

➜  keepass strings --print-file-name -e l * | grep "LePasSage"
task.119514.0x7f3f46338000.vma: IyfTEvOFrjanWGroup:IdmZfHKZcQfnm 453, IyfTEvOFrjanWTitle:IdmZfHKZcQfnm 453, IyfTEvOFrjanWUser Name:IdmZfHKZcQfnm LePasSage, IyfTEvOFrjanWPassword:IdmZfHKZcQfnm ********, IyfTEvOFrjanWCreation Time:IdmZfHKZcQfnm 3/26/2020 2:36:34 PM, IyfTEvOFrjanWLast Modification Time:IdmZfHKZcQfnm 3/26/2020 2:39:59 PM
task.119514.0x7f3f46338000.vma: IyfTEvOFrjanWGroup:IdmZfHKZcQfnm 453, IyfTEvOFrjanWTitle:IdmZfHKZcQfnm 453, IyfTEvOFrjanWUser Name:IdmZfHKZcQfnm LePasSage, IyfTEvOFrjanWPassword:IdmZfHKZcQfnm ********, IyfTEvOFrjanWCreation Time:IdmZfHKZcQfnm 3/26/2020 2:36:34 PM, IyfTEvOFrjanWLast Modification Time:IdmZfHKZcQfnm 3/26/2020 2:39:59 PM
task.119514.0x7f3f46338000.vma: IyfTEvOFrjanWGroup:IdmZfHKZcQfnm 453, IyfTEvOFrjanWTitle:IdmZfHKZcQfnm 453, IyfTEvOFrjanWUser Name:IdmZfHKZcQfnm LePasSage, IyfTEvOFrjanWPassword:IdmZfHKZcQfnm ********, IyfTEvOFrjanWCreation Time:IdmZfHKZcQfnm 3/26/2020 2:36:34 PM, IyfTEvOFrjanWLast Modification Time:IdmZfHKZcQfnm 3/26/2020 2:39:59 PM
task.119514.0x7f3f47244000.vma: LePasSage
task.119514.0x7f3f51000000.vma: {\*\generator Mono RichTextBox;}\pard\f0\fs22 \ul Group:IdmZfHKZcQfnm 453, \ul Title:IdmZfHKZcQfnm 453, \ul User Name:IdmZfHKZcQfnm LePasSage, \ul Password:IdmZfHKZcQfnm ********, \ul Creation Time:IdmZfHKZcQfnm 3/26/2020 2:36:34 PM, \ul Last Modification Time:IdmZfHKZcQfnm 3/26/2020 2:39:59 PM\par

Dans le cadre du challenge, il n’y a pas qu’un seul espace mémoire contenant le nom d’utilisateur, il y a en 3: task.119514.0x7f3f46338000.vma, task.119514.0x7f3f47244000.vma, task.119514.0x7f3f51000000.vma

Il ne reste qu’à chercher notre marqueur parmi ces trois zones mémoires:

➜  strings -e l  task.119514.0x7f3f46338000.vma task.119514.0x7f3f47244000.vma task.119514.0x7f3f51000000.vma | grep -C 1 "SWFClass0.KeePass.UI.SecureTextBoxEx"
[...]
--
KeePass.UI.SecureTextBoxEx
SWFClass0.KeePass.UI.SecureTextBoxEx
_Rx9PjLIO8Z
--
KeePass.UI.SecureTextBoxEx
SWFClass0.KeePass.UI.SecureTextBoxEx
_Rx9PjLIO8Z
--
KeePass.UI.SecureTextBoxEx
SWFClass0.KeePass.UI.SecureTextBoxEx
_Rx9PjLIO8Z

Je pense donc le mot de passe est:

_Rx9PjLIO8Z

Cependant les identifiants LePasSage:_Rx9PjLIO8Z ne sont pas ceux recherchés pour le challenge, mais peut être qu’un jour ça pourra servir.