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, noyau5.4.0-1071-awsObjectif : 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/disket/opt/memont é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
objdumpincorrecte :objdump: option '--disassemble' doesn't allow an argumentLa 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 :
- Elle attend que
/home/local.txtexiste. - Elle efface le buffer global
flagavecmemset. - Elle lit le contenu de
/home/local.txt. - Elle supprime
/home/local.txtavecunlink. - Elle appelle
make_file_name. - Elle crée temporairement un chemin dérivé du contenu du flag.
- 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_lockedouflag_test_and_setprovenaient 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>/memet 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 Intelreadelf -Ws— table des symboles d’un binaire ELF/proc/<PID>/mapset/proc/<PID>/mem— introspection mémoire d’un processus Linuxdebugfs -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