Aller au contenu

APTNightmare-2

·11 mins· loading · loading · ·
HackTheBox Sherlock Hard Linux
Jaybird1291
Auteur
Jaybird1291
Sommaire

Scénario
#

Ă€ l’issue du processus de rĂ©cupĂ©ration du serveur, l’Ă©quipe IR a mis en Ă©vidence un rĂ©seau complexe de trafic persistant, de communications furtives et de processus tenaces ayant rĂ©sistĂ© Ă  nos tentatives d’arrĂŞt. Il est Ă©vident que la portĂ©e de l’incident dĂ©passe la violation initiale de nos serveurs et de nos clients. En tant qu’expert en forensic, pouvez-vous Ă©clairer les ombres qui cachent ces activitĂ©s clandestines ?

Setup
#

Pour ce Sherlock nous allons utiliser :

  • Volatility2
  • IDA

Nous allons également nous appuyer sur cette cheatsheet :

Profil volatility
#

Premièrement on doit installer python2, volatility2 et ajouter le profil nécessaire.

Un profil Volatility est un fichier contenant des informations structurelles sur le système d’exploitation cible. Pour simplifier, c’est comme une “carte” qui permet Ă  Volatility de comprendre comment les donnĂ©es sont organisĂ©es dans la mĂ©moire d’un système spĂ©cifique.

Ce profil contient principalement deux types d’informations :

  • les dĂ©finitions des structures de donnĂ©es du kernel
  • les symboles du kernel (adresses des fonctions et variables)

Installation :

sudo apt install -y python2 python2-dev build-essential libdistorm3-dev libssl-dev libffi-dev zlib1g-dev

curl -sS https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip.py

sudo python2 get-pip.py

sudo python2 -m pip install --upgrade pip setuptools wheel

sudo python2 -m pip install distorm3 pycrypto openpyxl pillow yara-python

git clone https://github.com/volatilityfoundation/volatility.git

cd volatility

python2 vol.py -h

Profil :

cp Ubuntu_5.3.0-70-generic_profile.zip /home/kali/Documents/volatility/volatility/plugins/overlays/linux/

python2 vol.py --info | grep Linux

Question 1
#

Quels sont les IP et le port utilisĂ©s par l’attaquant pour le reverse shell ?

Pour cela on va utiliser le module linux_netstat de volatility qui permet d’extraire et afficher toutes les connexions rĂ©seaux qui Ă©taient prĂ©sentes lors de la capture mĂ©moire. On va rediriger l’output dans un fichier pour faciliter la recherche via un Ă©diteur de texte / IDE etc.

python2 vol.py -f ~/Downloads/APTNightmare-2/dump.mem --profile=LinuxUbuntu_5_3_0-70-generic_profilex64 linux_netstat > netstat.txt

Sous Linux, le plus probable est de retrouver un reverse shell bash particulièrement basique, et effectivement :

netstat

Réponse : 10.0.2.6:443

Question 2
#

Quel était le PPID de la connexion malveillante du reverse shell ?

Tout d’abord, testons avec ``linux_pstree` :

python2 vol.py -f ~/Downloads/APTNightmare-2/dump.mem --profile=LinuxUbuntu_5_3_0-70-generic_profilex64 linux_pstree | grep -C 5 3633

pstree

Pas de PPID. Pourquoi ? Le plugin linux_pstree reconstruit l’arborescence des processus en se basant principalement sur une seule source d’information : la liste des tâches actives du système (task_struct).

kernel map
https://makelinux.github.io/kernel/map/

On va donc plutôt utiliser le plugin linux_psxview qui est conçu spécifiquement pour détecter les processus cachés. Il utilise plusieurs sources pour idenfifier les processus :

  • task_struct list : la mĂŞme liste de tâches utilisĂ©e par linux_pstree
  • pid hash table : une structure de hachage utilisĂ©e par le kernel pour rechercher rapidement les processus par PID
  • pslist : liste des processus extraite d’autres sources mĂ©moire
  • kmem_cache : cache du kernel qui peut contenir des rĂ©fĂ©rences aux processus
  • d_path : informations sur les processus tirĂ©es du système de fichiers procfs
  • thread_info : informations des threads qui peuvent rĂ©vĂ©ler des processus cachĂ©s

Il compare ensuite les rĂ©sultats de ces diffĂ©rentes sources et signale les incohĂ©rences, par exemple lorsqu’un processus apparaĂ®t dans une source mais pas dans une autre.

python2 vol.py -f ~/Downloads/APTNightmare-2/dump.mem --profile=LinuxUbuntu_5_3_0-70-generic_profilex64 linux_psxview > psxview.txt

psxview

Et logiquement, on se doute bien que le parent est le PID juste avant.

Mais pourquoi le cacher ? Au vu du scĂ©nario, on sait que l’on a Ă  faire Ă  un rootkit. Ce qui s’est probablement passĂ© c’est que le rootkit a modifiĂ© la liste des tâches (task_struct list) en “dĂ©connectant” son processus de reverse shell de cette liste chaĂ®nĂ©e. Concrètement, il a manipulĂ© les pointeurs next et prev de cette liste pour que son processus soit ignorĂ© lors du parcours de la liste.

Cependant, le rootkit n’a pas rĂ©ussi Ă  effacer toutes les traces de son existence. Il a omis de modifier une ou plusieurs des autres structures surveillĂ©es par linux_psxview.

Le rĂ©sultat est que linux_pstree, qui ne se fie qu’Ă  la liste des tâches, ne voit pas le processus malveillant, tandis que linux_psxview, qui vĂ©rifie plusieurs sources, le dĂ©tecte via les structures que le rootkit a nĂ©gligĂ© de modifier.

Réponse : 3632

Question 3
#

Indiquer le nom du module malveillant du kernel.

Pour cela on va utiliser le plugin linux_check_modules. Mais avant, remettons en contexte qu’est-ce qu’un module kernel et quel est le lien avec un rootkit.

Un module kernel c’est un morceau de code qui peut ĂŞtre chargĂ© et dĂ©chargĂ© dynamiquement dans le kernel d’un système d’exploitation en cours d’exĂ©cution. Cela permet d’Ă©tendre ses fonctionnalitĂ©s (comme la prise en charge de nouveaux pĂ©riphĂ©riques ou systèmes de fichiers) sans devoir redĂ©marrer ou recompiler complètement le kernel.

Les rootkits opèrent au niveau du kernel Linux en insérant leurs propres modules kernel (LKM - Loadable Kernel Modules). Ces modules malveillants peuvent:

  • intercepter les appels système pour dissimuler des fichiers, processus ou connexions
  • Ă©tablir des backdoors persistantes dans le système
  • dĂ©sactiver certaines fonctionnalitĂ©s de sĂ©curitĂ© du kernel
  • masquer leur propre prĂ©sence aux outils standard du système etc.

Concernant le plugin volatility linux_check_modules. Il est conçu pour dĂ©tecter les LKM cachĂ© en comparant encore une fois diffĂ©rente sources d’information du kernel.

1. Analyse de la liste officielle des modules

Tout d’abord, le plugin examine la liste des modules officiellement chargĂ©s (modules.list). Cette liste circulairement chaĂ®nĂ©e est maintenue par le kernel et contient tous les modules lĂ©gitimement chargĂ©s. Elle est accessible via la commande lsmod.

2. Analyse des symboles du kernel

Ensuite, il parcourt la table des symboles du kernel (accessible via /proc/kallsyms). Cette table contient les adresses de toutes les fonctions et variables du kernel, y compris celles introduites par des modules chargés.

3. Analyse de la section modulaire .ko

Le plugin examine Ă©galement les sections mĂ©moire oĂą les modules kernel (.ko) sont typiquement chargĂ©s, recherchant les signatures caractĂ©ristiques des modules mĂŞme s’ils ne sont pas rĂ©fĂ©rencĂ©s ailleurs.

4. Techniques de détection des modules cachés

  • la technique principale consiste Ă  comparer les modules trouvĂ©s dans la liste officielle avec ceux dĂ©tectĂ©s par l’analyse des symboles ou des sections mĂ©moire. Un module prĂ©sent dans une source mais absent de la liste officielle est probablement cachĂ© intentionnellement.
  • le plugin examine Ă©galement la table des appels système (syscall table) pour dĂ©tecter si des fonctions originales ont Ă©tĂ© remplacĂ©es par des versions modifiĂ©es - une technique courante des rootkits pour intercepter les interactions avec le kernel.
  • il vĂ©rifie si les adresses des fonctions de modules pointent vers des rĂ©gions mĂ©moire suspectes ou non standard, ce qui pourrait indiquer du code injectĂ©.
  • les attributs des modules sont analysĂ©s (comme l’horodatage, le nom, l’auteur) pour dĂ©tecter des informations incohĂ©rentes ou inhabituelles.

Ok c’est cool mais comment les rootkit se cache au fait ?

Il y a beaucoup de technique différente mais on retrouve généralement :

  • DKOM (Direct Kernel Object Manipulation) : Ils modifient les structures de donnĂ©es du kernel en mĂ©moire pour retirer leur module de la liste modules.list, tout en laissant le module fonctionnel.

  • Hooks de syscall : Ils remplacent les fonctions lĂ©gitimes du kernel par leurs propres versions qui filtrent les rĂ©sultats (par exemple, une version modifiĂ©e de read qui ne montre jamais certains fichiers).

  • Module sans nom : Certains modules malveillants utilisent des chaĂ®nes vides ou des caractères spĂ©ciaux comme nom pour compliquer leur dĂ©tection.

Enfin bref, revenons Ă  la question.

python2 vol.py -f ~/Downloads/APTNightmare-2/dump.mem --profile=LinuxUbuntu_5_3_0-70-generic_profilex64 linux_check_modules 

linux_check_modules

Le nom “nfentlink” est une tentative de camouflage d’un module malveillant en se faisant passer pour “nfnetlink”, qui est un module kernel lĂ©gitime de Linux utilisĂ© pour la communication entre l’espace kernel et l’espace utilisateur pour le firewall et le rĂ©seau.

Réponse : nfentlink

Question 4
#

Quand est-ce que le module a été chargé ?

Au dĂ©part, j’Ă©tais parti sur une mauvaise piste. Ma pensĂ©e Ă©tait :

  • prendre le timestamp du chargement du module dans dmesg via linux_dmesg
  • prendre le timestamp du boot dans linux_pslist
  • calculer et hop

Cela aurait fonctionnĂ© si c’Ă©tait la première fois que le module Ă©tait chargĂ©. NĂ©anmoins, il a dĂ©jĂ  Ă©tĂ© chargĂ© dans le passĂ©. Cette mĂ©thode est intrinsèquement mauvaise et peut induire en erreur dans un contexte de rĂ©ponse Ă  incident.

Au final j’ai remis tout Ă  plat et je me suis dit “oĂą puis-je trouver des timestamp liĂ© Ă  des actions passĂ©es après de multiple boot ?”.

Les logs systèmes évidemment. Tout particulièrement /var/log/kern.log ou /var/log/syslog.log.

Pour récupérer ces fichiers on va premièrement énumérer les fichiers dans la capture mémoire :

python2 vol.py -f ~/Downloads/APTNightmare-2/dump.mem --profile=LinuxUbuntu_5_3_0-70-generic_profilex64 linux_enumerate_files > files.txt

Et en effet on retrouve bien :

enumerate

Ensuite, pour extraire /var/log/kern.log on va :

python2 vol.py -f ~/Downloads/APTNightmare-2/dump.mem --profile=LinuxUbuntu_5_3_0-70-generic_profilex64 linux_find_file -i 0xffff98ea5a732fa8 -O kern.log

kernel.log

Réponse : 2024-05-01 20:42:57

Question 5
#

Quel est le chemin d’accès complet et le nom du fichier du module du kernel malveillant ?

De mĂŞme, vĂ©rifions parmi les fichiers Ă©numĂ©rĂ©s. Premièrement on cherche le module qu’on a identifiĂ© “nfentlink”.

cat files.txt |grep nfentlink

ça ne donne rien d’intĂ©ressant.

On va donc chercher le module qui a le vrai nom pour voir :

On va revenir sur le deuxième fichier plus tard.

Réponse : /lib/modules/5.3.0-70-generic/kernel/drivers/net/nfnetlink.ko

Question 6
#

Quel est le hash MD5 du fichier du module malveillant ?

Il suffit d’extraire le fichier et calculer son hash :

python2 vol.py -f ~/Downloads/APTNightmare-2/dump.mem --profile=LinuxUbuntu_5_3_0-70-generic_profilex64 linux_find_file -i 0xffff98ea266b5a68 -O nfnetlink.ko

md5sum nfnetlink.ko

Réponse : 35bd8e64b021b862a0e650b13e0a57f7

Question 7
#

Quel est le chemin d’accès complet et le nom du fichier du module du kernel lĂ©gitime ?

Revenons au screen de la question 5.

Réponse : /lib/modules/5.3.0-70-generic/kernel/net/netfilter/nfnetlink.ko

Question 8
#

Quelle est la diffĂ©rence d’un seul caractère dans la valeur de l’auteur entre le module lĂ©gitime et le module malveillant ?

Premièrement on va checker le lĂ©gitime via modinfo qui permet d’afficher des informations dĂ©taillĂ©s sur un module kernel spĂ©cifique.

modinfo /lib/modules/6.11.2-amd64/kernel/net/netfilter/nfnetlink.ko.xz

Ensuite, on check le module kernel qu’on a rĂ©cupĂ©rĂ© dans la capture :

modinfo malicious-nfnetlink.ko

On voit donc bien qu’il manque un “i”.

Réponse : i

Question 9
#

Quel est le nom de la fonction d’initialisation du module du kernel malveillant ?

Pour rĂ©pondre Ă  cette question je vais utiliser IDA. C’est vraiment overkill, on peut se limiter Ă  gdb (gef>gdb), radare2 etc.

On va donc regarder les fonctions :

On voit bien que la fonction d’initialisation est nfnetlink_init mais aussi init_module. C’est encore plus visible avec gef :

Gef affiche les deux fonctions à la même adresse mémoire. On observe donc une technique délibérée de camouflage utilisée par des rootkits au niveau kernel.

Le module malveillant utilise la fonction standard init_module (qui est l’entrĂ©e obligatoire pour tout module kernel Linux) mais a intentionnellement renommĂ© cette fonction en nfnetlink_init pour ressembler au module lĂ©gitime du kernel.

Les symboles d’exportation comme init_module sont essentiels pour que le kernel Linux puisse charger le module, mais l’attaquant a utilisĂ© des astuces de compilation pour que la mĂŞme fonction porte deux noms diffĂ©rents, l’un pour le chargement par le kernel et l’autre pour le camouflage visuel.

Réponse : nfnetlink_init

Question 10
#

Il existe une fonction pour hooker les syscall. Quel est le dernier syscall du tableau ?

Dans la fonction nfnetlink_init on voit bien _sys_call_table = kallsyms_lookup_name("sys_call_table"); :

_sys_call_table = kallsyms_lookup_name("sys_call_table");

Cette ligne utilise la fonction kallsym_lookup_name pour obtenir l’adresse de la table des syscall sys_call_table dans la mĂ©moire du kernel.

sys_call_table est un tableau contenant les pointeurs vers les fonctions des syscall utilisĂ©s par le kernel. En modifiant cette table, l’attaquant redirige les appels système vers ses propres routines malveillantes.

On va donc aller voir le tableau de données dans la section .rodata (section contenant des chaînes de caractères et des données en lecture seule).

Ce tableau contient des références à des symboles qui sont utilisées pour diverses manipulations dans le module malicieux.

aX64SysGetdents       db '_x64_sys_getdents64',0
aX64SysGetdents       db '_x64_sys_getdents',0
aX64SysKill           db '_x64_sys_kill',0

Ces chaînes sont des références aux symboles des fonctions système que le module va utiliser ou modifier.

Ces fonctions font partie de l’API des syscall du kernel Linux, et dans ce cas, elles sont hookĂ©es ou utilisĂ©es pour rediriger des appels.

Réponse : __x64_sys_kill

Question 11
#

Quel numĂ©ro de signal est utilisĂ© pour masquer le PID d’un processus en cours d’exĂ©cution lors de l’envoi ?

On va donc aller voir la fonction hook_kill :

hook_kill

Ce qui retient immédiatement notre attention est :

cmp     dword ptr [rdi+68h], 64

ainsi que le hide_pid.

Allons voir le pseudocode généré par IDA :

hook_kill

if ( (*(DWORD *)(a1 + 104)) != 64 )
    return ((__int64 (*) (void))orig_kill());
  • a1 + 104 : cela accède au signal envoyĂ© avec l’appel kill(). Le champ Ă  l’adresse a1 + 104 correspond donc au signal.

  • (*(DWORD *)(a1 + 104)) != 64 : cette condition vĂ©rifie si le signal n’est pas Ă©gal Ă  64.

Si le signal n’est pas Ă©gal Ă  64, la fonction exĂ©cute la fonction orig_kill (l’originale, avant le hook) pour continuer l’exĂ©cution normale du kernel.

Sinon il fait appel Ă  hide_pid :

sprintf(hide_pid, "%d", *((QWORD *)(a1 + 112)));
  • sprintf(hide_pid, "%d", ...) : la fonction sprintf est utilisĂ©e ici pour formater et passer le PID dans la fonction hide_pid. On comprend donc que ce PID est utilisĂ© par la fonction hide_pid afin de dissimuler le processus au niveau du système, par exemple en supprimant ses entrĂ©es dans /proc ou dans d’autres structures internes du kernel.

  • hide_pid : est la fonction pour cacher un processus, empĂŞchant ainsi sa visibilitĂ©.

  • %d : C’est un format pour afficher l’entier (le PID).

Réponse : 64


Lab terminé !