Réseau
Le réseau du projet sépare deux besoins:
- un réseau privé stable entre attaquant et victime pour le scénario
- un accès NAT QEMU pour télécharger paquets et scripts depuis le host.
Topologie
Interfaces
| Machine | Connexion | Adresse | Usage |
|---|---|---|---|
| Host | br-wlkom | 192.168.100.254/24 | bridge privé |
| Attaquant | wlkom-lab | 192.168.100.1/24 | dashboard, payloads, listener |
| Victime | wlkom-lab | 192.168.100.2/24 | HTTP vers page victime, TCP reverse |
| VMs | wlkom-wan | DHCP QEMU | apt, accès 10.0.2.2:8000 |
Les scripts NetworkManager ciblent les interfaces par MAC QEMU. Cela évite de dépendre des noms ens3, enp0s3, etc.
Le projet choisit des IPs fixes configurées dans les VMs (192.168.100.1 pour l'attaquant, 192.168.100.2 pour la victime) plutôt qu'une IP DHCP à recopier à chaque lancement. C'est le scénario le plus proche d'un contrôleur déjà maîtrisé: le module connaît l'adresse du serveur C2 dès son chargement via attacker_ip.
Le piège à éviter: 10.0.2.2 n'est pas l'attaquant. C'est l'adresse spéciale du host vue depuis le NAT QEMU. Le module, lui, revient vers 192.168.100.1:4444.
Ports
| Port | Machine | Service |
|---|---|---|
8000/tcp | host | serveur de fichiers host served/ |
8080/tcp | attaquant | Flask, dashboard, page victime, payloads |
4444/tcp | attaquant | listener C2 du module |
Le port 8000 est vu depuis les VMs via:
http://10.0.2.2:8000
Le port 8080 est vu depuis la victime via:
http://192.168.100.1:8080
Le canal C2 est:
wlkom.ko victime -> 192.168.100.1:4444 attaquant
Flux HTTP
La page victime et les payloads sont publics sur le réseau privé. Le dashboard et l'API /api/rootkit/* sont restreints à l'attaquant et localhost.
Flux C2
Le backend découpe une réponse avec le marqueur __KOZACI_RESPONSE_END_8F8F6A0E6D4A41C2__, puis consomme le prompt suivant. Il ne s'appuie pas seulement sur wlkom>, car une commande pourrait afficher ce texte elle-même.
Pourquoi une connexion reverse?
La victime ne garde pas de port ouvert. Le module connaît seulement:
attacker_ip=192.168.100.1
command_port=4444
Il tente donc de joindre l'attaquant. Ce choix simplifie le fonctionnement:
- le dashboard peut rester en attente
- le module retente si le panel n'est pas encore lancé
- une déconnexion ferme la socket et déclenche une nouvelle boucle
- l'état UI reste lisible: absent, détecté, besoin de mot de passe, connecté.
Techniquement, le module utilise sock_create_kern, kernel_connect, kernel_sendmsg, kernel_recvmsg, kernel_sock_shutdown, kvec et msghdr. Le backend utilise une socket Python classique. Les deux côtés appliquent le même chiffrement XOR dérivé du mot de passe, avec offsets émission et réception séparés.
Pour comprendre ces fonctions kernel, voir Sockets kernel et kvec, msghdr et envoi partiel.
Retry et état
Côté kernel:
| Situation | Comportement |
|---|---|
kernel_connect échoue | pause 10 secondes, retry |
| mot de passe faux | message Bad password., fermeture socket, pause 5 secondes |
| session terminée | fermeture socket, pause 5 secondes |
| module stoppé | kernel_sock_shutdown(..., SHUT_RDWR) réveille kernel_recvmsg(), puis sortie via kthread_should_stop() |
Côté backend, status expose:
listener_bound
detected
needs_password
connected
active_peer
pending_peer
connection_attempts
auth_failures
last_seen_at
last_error
crypto.tx_offset
crypto.rx_offset
Contrôles rapides
Host:
ip addr show br-wlkom
ip link show tap-attacker
ip link show tap-victim
Attaquant:
ip -brief addr
ss -lntp | grep -E ':8080|:4444'
curl -fsS http://192.168.100.1:8080/api/rootkit/status
Victime:
ip -brief addr
curl -I http://192.168.100.1:8080/freecloude
curl -I http://192.168.100.1:8080/payload/freecloude.py
curl -I http://192.168.100.1:8080/payload/freecloude.sh
Si ces commandes passent mais que le C2 ne se connecte pas, le problème est probablement côté listener 4444, mot de passe ou chargement du module. Voir Troubleshooting.