Lab Recov : récupération de données supprimées et analyse mémoire sous Linux

Cible : 176.16.132.37 (NixV2 for Recov) Système : Ubuntu 18.04.3 LTS, noyau 5.4.0-1071-aws Objectif : récupérer deux flags via récupération disque et analyse mémoire, sans exécuter les binaires suspects Résultat : ✅ 2 flags récupérés par analyse statique + forensique disque et mémoire

1. Objet du rapport

Ce rapport documente la résolution du lab Recov, consacré à la réponse à incident, à l’analyse de fichiers, à la récupération de données supprimées sous Linux et à l’analyse mémoire. L’objectif était d’identifier deux flags en partant d’une machine compromise fournie par le lab.

⚠️ Périmètre de l’analyse : les manipulations décrites ont été réalisées uniquement sur la machine d’entraînement 176.16.132.37, avec le compte fourni par le lab. Les exécutables /opt/disk et /opt/mem ont été analysés comme des artefacts suspects. Ils n’ont pas été lancés manuellement pendant l’analyse.

2. Informations initiales et accès

Élément Valeur
Cible 176.16.132.37
Utilisateur infected
Mot de passe infected
Système observé Ubuntu 18.04.3 LTS, noyau Linux 5.4.0-1071-aws
Partition racine /dev/nvme0n1p1
Système de fichiers ext4

Connexion SSH :

ssh infected@176.16.132.37

La première tentative a échoué avec Permission denied, puis la seconde a réussi. Après connexion, l’invite indiquait :

infected@ip-176-16-132-37:~$

Le message d’accueil confirmait également l’adresse IP 176.16.132.37 sur l’interface réseau ens5.

3. Vérification du système et des privilèges

uname -a
id
sudo -n true 2>/dev/null && echo SUDO_OK || echo SUDO_PASSWORD_REQUIRED

Sortie importante :

Linux ip-176-16-132-37 5.4.0-1071-aws #76~18.04.1-Ubuntu SMP Mon Mar 28 17:49:57 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
uid=1001(infected) gid=1001(infected) groups=1001(infected)
SUDO_PASSWORD_REQUIRED

Pourquoi cette étape est-elle nécessaire ?

uname -a établit le contexte système et peut révéler une architecture 64 bits, une version de noyau et un environnement cloud. id indique l’utilisateur courant et ses groupes. Le test sudo -n vérifie uniquement si sudo peut être utilisé sans interaction ; il n’essaie pas de deviner un mot de passe. Dans ce cas, sudo exigeait le mot de passe fourni par le lab.

💡 Cette distinction est importante en DFIR : la capacité à lire /proc/<PID>/mem, une partition brute ou des journaux protégés dépend souvent des privilèges disponibles.

4. Recherche initiale des fichiers récents

find /home /tmp /var/tmp /opt /root -xdev -type f \
  -printf '%TY-%Tm-%Td %TH:%TM %s %p\n' 2>/dev/null \
  | sort -r | head -100
Date Taille Chemin Interprétation
2026-09-01 15:31 17 296 octets /opt/disk Exécutable récent lié à la récupération disque
2026-09-01 15:31 17 000 octets /opt/mem Exécutable récent lié à l’analyse mémoire
2020-01-08 10:26 35 octets /home/infected/.bash_history Historique utilisateur ancien

Pourquoi cette étape est-elle nécessaire ?

Les dates de modification et de création permettent de distinguer les fichiers système habituels des artefacts introduits pour le scénario. Les deux fichiers placés dans /opt le jour du démarrage du lab étaient anormalement pertinents : leurs noms correspondaient directement aux thèmes disk et mem.

L’option -xdev évite de traverser d’autres systèmes de fichiers montés. Cela réduit le bruit et limite la collecte à la partition contenant le répertoire examiné.

5. Recherche de dumps et d’images mémoire

find / -xdev -type f \
  \( -iname '*.raw' -o -iname '*.mem' -o -iname '*.lime' \
     -o -iname '*.dmp' -o -iname '*.vmem' -o -iname '*dump*' \) \
  -printf '%s %p\n' 2>/dev/null | sort -n

Cette recherche n’a pas révélé de dump mémoire autonome appartenant au scénario. Les résultats concernaient principalement des fichiers système, des pages de manuel et des fichiers de documentation.

Pourquoi cette étape est-elle nécessaire ?

Dans une investigation réelle, un dump peut être présent sous une extension connue, mais il peut aussi être caché, renommé ou uniquement maintenu en mémoire par un processus actif. L’absence de fichier .raw, .mem ou .dmp ne signifie donc pas qu’il n’existe aucune preuve mémoire. Il faut ensuite examiner les processus actifs et leurs mappings mémoire.

6. Énumération des processus actifs

ps auxwwf

Un processus s’est immédiatement distingué :

root      1532  99.9  0.0   4388   804 ?  R  15:31  9:48 /opt/mem

Dans une collecte ultérieure, le même processus apparaissait ainsi :

root      1532     1  14:24 R  100  0.0 /opt/mem

Interprétation : le processus /opt/mem était lancé par root, avec le PID 1532, et consommait environ 100 % d’un cœur CPU. Son nom et son comportement correspondaient directement à la partie « memory analysis » du lab.

La consommation CPU élevée suggérait une boucle active. Le désassemblage a ensuite confirmé que le programme attendait l’apparition d’un fichier, le lisait, puis entrait dans une boucle infinie.

Pourquoi cette étape est-elle nécessaire ?

Un processus actif peut conserver en mémoire des données qui ne sont plus présentes sur le disque. Cette situation est classique lorsqu’un programme lit un fichier puis le supprime. L’analyse des processus permet donc de préserver une preuve volatile avant qu’elle ne disparaisse.

7. Analyse statique de /opt/mem et /opt/disk

Avant toute exécution manuelle, les deux fichiers ont été examinés statiquement :

ls -lah /opt/mem /opt/disk
stat /opt/mem /opt/disk
sha256sum /opt/mem /opt/disk
strings -a -n 6 /opt/mem
strings -a -n 5 /opt/disk
readelf -Ws /opt/mem
readelf -Ws /opt/disk
Fichier SHA-256
/opt/mem 6e4e41f88614fba0ef7324b5cf4be2e877ec2cb64fc058968abf4a8898cb38e9
/opt/disk 0863ad53308465976c156d8728ee44341e8c9a31bc9b7ebb0d88311ef85e9478

Chaînes intéressantes de /opt/mem :

memory.c
check_for_flags
read_flag

Symboles globaux confirmant les fonctions :

check_for_flags
read_flag
main

Pour /opt/disk, chaînes et symboles importants :

/home/local.txt
check_for_flags
read_flag
make_file_name

Pourquoi l’analyse statique est-elle préférable au lancement manuel ?

L’exécution d’un binaire suspect peut modifier des fichiers, effacer des traces ou changer l’état du système. strings, readelf et objdump permettent de comprendre une partie de la logique sans provoquer ces effets.

La présence de symboles nommés check_for_flags et read_flag est particulièrement instructive : elle montre que les binaires ont été compilés en conservant des informations utiles à l’analyse, ce qui est fréquent dans un exercice pédagogique.

8. Compréhension de la logique de /opt/disk

objdump -d -M intel /opt/disk \
  | sed -n '/<make_file_name>/,/^$/p; \
           /<read_flag>/,/^$/p; \
           /<check_for_flags>/,/^$/p; \
           /<main>/,/^$/p'

⚠️ Une première tentative avait utilisé une option objdump incorrecte :

objdump: option '--disassemble' doesn't allow an argument

La commande a été corrigée en désassemblant tout le fichier puis en filtrant les fonctions avec sed.

8.1 Fonction check_for_flags

Le code de /opt/disk montrait que check_for_flags vérifiait l’existence de la chaîne située à 0x2004. La section .rodata révélait cette chaîne :

/home/local.txt

La fonction retourne 1 si ce chemin existe et 0 dans le cas contraire.

8.2 Fonction read_flag

La fonction read_flag ouvre /home/local.txt, puis lit 0xfa octets, soit 250 octets, dans un buffer global nommé flag situé à l’adresse symbolique 0x40c0 :

mov edx,0xfa
lea rsi,[rip+0x2e6f]        # 40c0 <flag>
call read@plt

Cela signifie que le contenu du fichier est copié dans la variable globale flag.

8.3 Fonction main

La fonction principale effectue les opérations suivantes :

  1. Elle attend que /home/local.txt existe.
  2. Elle efface le buffer global flag avec memset.
  3. Elle lit le contenu de /home/local.txt.
  4. Elle supprime /home/local.txt avec unlink.
  5. Elle appelle make_file_name.
  6. Elle crée temporairement un chemin dérivé du contenu du flag.
  7. Elle attend deux secondes, supprime ce chemin et libère la mémoire.

8.4 Fonction make_file_name

Cette fonction alloue un buffer, écrit /home/ au début, puis ajoute le contenu de flag à partir de l’offset 6. Le résultat est donc un nom de fichier de la forme :

/home/<contenu_du_flag>

Le binaire supprime ensuite ce fichier. Le flag n’était donc pas nécessairement présent comme un fichier permanent ; il pouvait exister dans le nom d’un fichier temporaire ou dans un bloc de la partition après suppression.

9. Compréhension de la logique de /opt/mem

objdump -d -M intel /opt/mem \
  | sed -n '/<check_for_flags>/,/^$/p; \
           /<read_flag>/,/^$/p; \
           /<main>/,/^$/p'

La logique était analogue, mais le comportement final était différent.

La fonction read_flag lit 250 octets dans un buffer global situé à 0x40a0 :

mov edx,0xfa
lea rsi,[rip+0x2e9f]        # 40a0 <flag>
call read@plt

Après la lecture, main copie les 256 octets du buffer global vers une zone située sur la pile, puis boucle sur l’instruction :

jmp 12b5 <main+0x99>

Le processus conservait donc le contenu du flag en mémoire, même après la suppression du fichier source.

10. Tentative de récupération par debugfs

La partition racine a d’abord été identifiée avec :

findmnt -no SOURCE,FSTYPE,OPTIONS /
/dev/nvme0n1p1 ext4 rw,relatime,discard

Recherche des inodes supprimés :

echo infected | sudo -S debugfs -R 'lsdel' /dev/nvme0n1p1
Inode  Owner  Mode    Size      Blocks   Time deleted
0 deleted inodes found.

Interprétation : la commande lsdel n’a trouvé aucun inode supprimé exploitable. La partition était montée avec l’option discard, ce qui peut entraîner la libération rapide des blocs supprimés. Dans ce contexte, la récupération classique par inode supprimé pouvait échouer, même si des fragments du contenu existaient encore ailleurs sur la partition.

Pourquoi ne pas s’arrêter à cet échec ?

En DFIR, l’échec d’une méthode ne prouve pas l’absence de la donnée. Il faut changer de source de preuve. Ici, deux possibilités restaient ouvertes :

  • le contenu pouvait subsister dans les blocs bruts de la partition ;
  • le contenu pouvait être présent dans la mémoire du processus /opt/mem.

11. Récupération du flag mémoire

Le processus /opt/mem étant un exécutable ELF de type PIE, l’adresse symbolique 0x40a0 n’était pas directement utilisable comme adresse virtuelle finale. Il fallait d’abord obtenir la base de chargement ASLR depuis /proc/1532/maps.

PID=$(pgrep -f '^/opt/mem$' | head -1)
echo infected | sudo -S cat /proc/$PID/maps | grep '/opt/mem'
564a20cf9000-564a20cfa000 r--p 00000000 ... /opt/mem
564a20cfa000-564a20cfb000 r-xp 00001000 ... /opt/mem
564a20cfb000-564a20cfc000 r--p 00002000 ... /opt/mem
564a20cfc000-564a20cfd000 r--p 00002000 ... /opt/mem
564a20cfd000-564a20cfe000 rw-p 00003000 ... /opt/mem

La base de chargement était donc 0x564a20cf9000. L’adresse virtuelle du buffer global a été calculée ainsi :

BASE=0x564a20cf9000
ADDR=$((BASE + 0x40a0))
printf '0x%x\n' "$ADDR"
0x564a20cfd0a0

Le contenu mémoire a ensuite été lu avec des privilèges sudo :

echo infected | sudo -S dd \
  if=/proc/1532/mem \
  bs=1 skip=$ADDR count=250 status=none 2>/dev/null \
  | tee /tmp/mem_flag.bin \
  | strings -a -n 4
flag_73d685cc_b5a6_4cc5_a4ac_feda1b6d0fec

Pourquoi cette méthode fonctionne-t-elle ?

/proc/<PID>/maps décrit les régions virtuelles associées à un processus. Comme /opt/mem était chargé à une base variable à cause de l’ASLR, l’adresse du symbole devait être recalculée en ajoutant l’offset du symbole à la base du mapping.

/proc/1532/mem représente l’espace mémoire virtuel du processus. La lecture à l’adresse calculée a permis d’extraire directement le buffer global flag rempli par read_flag.

12. Récupération du flag disque par recherche brute

Après l’échec de debugfs -R 'lsdel', une recherche en lecture seule a été effectuée directement sur la partition :

echo infected | sudo -S grep -aobE \
  'flag_[A-Za-z0-9_{}-]{10,}' \
  /dev/nvme0n1p1 2>/dev/null | head -30

Résultats significatifs :

67993664:flag_5b4b7866_1099_46c0_8108_99d0edae115a
135376896:flag_73d685cc_b5a6_4cc5_a4ac_feda1b6d0fec

ℹ️ Les autres résultats tels que flag_get_by_name, flag_write_locked ou flag_test_and_set provenaient de chaînes appartenant aux logiciels installés sur le système. Ils ne respectaient pas le format caractéristique des flags du lab et n’étaient donc pas retenus.

Le motif trouvé à l’offset 67993664 avait la forme exacte attendue :

flag_5b4b7866_1099_46c0_8108_99d0edae115a

Pourquoi la recherche brute fonctionne-t-elle malgré lsdel ?

lsdel cherche des métadonnées d’inodes supprimés encore accessibles. Une recherche brute, elle, parcourt les octets de la partition et peut retrouver des fragments qui ne sont plus référencés par un inode actif. Les deux méthodes ne recherchent donc pas exactement la même chose.

La présence de discard rend toutefois la récupération dépendante de l’état des blocs. Dans ce cas précis, la chaîne du flag était encore présente à l’offset 67993664.

13. Résultats finaux

# Catégorie Flag Preuve principale
1 Récupération disque flag_5b4b7866_1099_46c0_8108_99d0edae115a Chaîne trouvée dans /dev/nvme0n1p1 à l’offset 67993664
2 Analyse mémoire flag_73d685cc_b5a6_4cc5_a4ac_feda1b6d0fec Buffer global lu dans /proc/1532/mem à l’adresse 0x564a20cfd0a0

14. Chaîne méthodologique complète

Accès SSH
   │
   ▼
Identification du système et des privilèges
   │
   ▼
Recherche des fichiers récents
   │
   ▼
Identification de /opt/disk et /opt/mem
   │
   ▼
Énumération des processus
   │
   ▼
Découverte du processus root /opt/mem, PID 1532
   │
   ▼
Analyse statique des deux ELF
   │
   ▼
Confirmation de /home/local.txt et des buffers flag
   │
   ▼
Tentative debugfs sur les inodes supprimés
   │
   ▼
Échec lié à l'absence d'inodes récupérables et à discard
   │
   ▼
Lecture du buffer en mémoire du processus /opt/mem
   │
   ▼
Recherche brute dans /dev/nvme0n1p1
   │
   ▼
Validation des deux flags

15. Bonnes pratiques retenues

  • Ne pas exécuter manuellement les binaires suspects. L’analyse statique a fourni suffisamment d’informations pour comprendre leur logique sans provoquer de nouvelles suppressions.
  • Rechercher les preuves volatiles tôt dans l’investigation. Le processus /opt/mem était actif et contenait directement le flag ; son espace mémoire constituait donc une source de preuve prioritaire.
  • Comparer plusieurs sources : système de fichiers, liste des processus, symboles ELF, mappings mémoire et partition brute. Une seule technique, comme debugfs lsdel, n’aurait pas suffi.
  • Associer chaque flag à une preuve technique indépendante : une position dans l’image brute pour le flag disque et une adresse mémoire calculée pour le flag mémoire.

16. Conclusion

Le lab illustre deux scénarios complémentaires. Le premier flag a été conservé dans les blocs de la partition après la suppression de fichiers et a été retrouvé par recherche brute. Le second a été conservé dans un processus actif après lecture d’un fichier supprimé et a été récupéré par analyse de la mémoire du processus.

La combinaison de l’énumération Linux, de l’analyse statique ELF, de l’examen de /proc, de la compréhension de l’ASLR et de la recherche forensique dans une partition brute a permis de retrouver les deux flags sans exécuter les binaires manuellement.

Flags finaux :

flag_5b4b7866_1099_46c0_8108_99d0edae115a
flag_73d685cc_b5a6_4cc5_a4ac_feda1b6d0fec

⚠️ Note de reproductibilité : les commandes de collecte et d’analyse doivent être exécutées uniquement sur une machine de laboratoire ou un système pour lequel une autorisation explicite existe. Les lectures de /proc/<PID>/mem et d’un périphérique bloc brut nécessitent généralement des privilèges élevés et peuvent produire une sortie volumineuse ; elles doivent être redirigées vers un fichier de preuve lorsque la conservation formelle est requise.

Références

  • objdump -d -M intel — désassemblage AT&T → syntaxe Intel
  • readelf -Ws — table des symboles d’un binaire ELF
  • /proc/<PID>/maps et /proc/<PID>/mem — introspection mémoire d’un processus Linux
  • debugfs -R 'lsdel' — recherche d’inodes supprimés sur ext4
  • Option de montage discard (TRIM) — impact sur la récupération de données supprimées