Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
xspawn — Lance des programmes macOS via l'interface XPC privée de launchd sans passer par exec, ce qui amène l'EDR à enregistrer launchd comme parent. Prend en charge les jobs one-shot, KeepAlive et basés sur des plist. | Kitploit
Outils/GitHubGitHub/cenobyte-vincit/xspawn
Mécanismes de PersistanceÉvasion IDS/IPSPost-ExploitationRed TeamingAttaque Adversariale
GitHubcenobyte-vincit/xspawn

xspawn

Lance des programmes macOS via l'interface XPC privée de launchd sans passer par exec, ce qui amène l'EDR à enregistrer launchd comme parent. Prend en charge les jobs one-shot, KeepAlive et basés sur des plist.

Voir le dépôt
21il y a 22 joursPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

xspawn

xspawn démarre un programme sur macOS via launchd et n'exécute jamais ce programme lui-même. L'objectif est qu'un EDR enregistre launchd comme parent, et non cet outil ou le shell appelant.

par cenobyte [email protected] 2026

https://github.com/cenobyte-vincit/xspawn

Résumé

xspawn ouvre xpc_pipe_create_from_port(bootstrap_port) et amorce l'exécution d'un programme via _xpc_pipe_interface_routine, le même pipe XPC privé qu'utilise launchctl, et n'exécute jamais le programme lui-même.

  • Root est refusé. Il n'existe pas de mode autre-UID.
  • Une invocation soumet ou supprime un seul job.
  • Le client n'exécute jamais /bin/launchctl.

Prérequis

Hôte d'exécution

  • macOS (Darwin) avec une session gui/<uid>

Hôte de compilation

  • macOS (Darwin) avec Xcode Command Line Tools ou Xcode
  • Compilateur C17 (cc)
  • make
  • cppcheck pour le développement (brew install cppcheck)

Compilation

root@kitploit:~
make

Utilisation

root@kitploit:~
xspawn oneshot -l <label> [-o <stdout>] [-e <stderr>] [--] <program> [args...]
xspawn submit  -l <label> [-o <stdout>] [-e <stderr>] [--] <program> [args...]
xspawn remove  -l <label>
xspawn load    -p <plist>

One-shot (RunAtLoad + LaunchOnlyOnce ; 0 signifie aucune pause) :

root@kitploit:~
./xspawn oneshot -l com.example.once -- /tmp/helloworld 0

Les arguments après -- sont les ProgramArguments. Cela inclut le code en ligne (python3 -c, perl -e). CrowdStrike Falcon pour macOS enregistre la CommandLine complète ; utilisez donc le code en ligne avec des interpréteurs avec parcimonie.

root@kitploit:~
./xspawn oneshot -l com.example.py -o /tmp/py.out -- \
	/usr/bin/python3 -c "print('hello world')"

Un sleep de 60 pour que CrowdStrike Falcon pour macOS et launchctl print voient toujours le processus :

root@kitploit:~
./xspawn submit -l com.example.svc \
	-o /tmp/out.log -e /tmp/err.log -- /tmp/helloworld 60

Inspectez avec launchctl print (oracle uniquement ; ce client ne l'appelle pas) :

root@kitploit:~
launchctl print gui/$(id -u)/com.example.svc

En cas de succès, on voit type = LaunchAgent (et non Submitted), program avec le chemin absolu, et state = running ou brièvement xpcproxy. Submitted signifie que le job n'a pas emprunté le chemin de bootstrap.

Nettoyez un job de test :

root@kitploit:~
./xspawn remove -l com.example.svc

Chargez une plist détenue par l'appelant (non supprimée après la réponse) :

root@kitploit:~
./xspawn load -p /tmp/job.plist

<program> doit être un chemin absolu. launchd ne recherche pas dans $PATH.

load -p exige un chemin absolu se terminant par .plist.

-o / -e peuvent être relatifs. Ils sont résolus par rapport au répertoire de travail courant avant d'être écrits dans la plist. Les -o et -e omis valent /dev/null.

oneshot et submit vérifient la disponibilité du label dans gui et user (descripteur 708) avant d'écrire la plist temporaire. Un label déjà pris se termine avec label already loaded et sans aucune sortie sur stdout. Cette vérification existe pour qu'un 800 voué à l'échec n'écrive pas $TMPDIR/XXXXXX/XXXXXX.plist (un artefact DFIR ; CrowdStrike Falcon conserve le chemin dans ASEPFilePath) ni n'imprime la copie XML du dictionnaire du job. Un label libre affiche le chemin temporaire, puis ce XML, puis envoie 800. load -p effectue la même vérification d'occupation sur le Label du fichier, puis affiche le chemin de l'appelant et le XML. Le répertoire temporaire est supprimé à chaque sortie. remove se fait par label.

Codes de sortie

CodeSignification
0Bootstrap ou bootout XPC réussi
1Erreur d'utilisation, label invalide, root, ou rejet de launchd/XPC

Vérification

Hôte de compilation (make et l'arborescence de test ; souvent colocalisé avec une session gui). Ces vérifications ne constituent pas une preuve d'exécution propre :

root@kitploit:~
make
make test
make test-unit
make test-functional
root@kitploit:~
./xspawn oneshot -l com.example.once -- /tmp/helloworld 0

Limites

  • Uniquement gui/<uid> du même utilisateur. Root est refusé. Pas de ciblage d'un autre UID.
  • Éphémère : ne persiste pas après un redémarrage ou une déconnexion.
  • Les constantes privées XPC de load et bootout sont épinglées à macOS 26.6.1 build 25G76. Réépinglez si sw_vers -buildVersion change (voir ARCHITECTURE.md).
  • La plist temporaire est $TMPDIR/XXXXXX/XXXXXX.plist ($TMPDIR doit être absolu, sinon /tmp). Le répertoire est supprimé à chaque sortie. Un label déjà pris ne crée jamais ce fichier.
  • CrowdStrike Falcon pour macOS enregistre le chemin de la plist temporaire dans le champ Auto-Start Extensibility Point (ASEPFilePath) de l'événement ProcessRollup2. Le parent du processus reste launchd.
  • Une exécution autonome de xspawn est visible en tant que ce client : historique du shell et événement de processus EDR pour ce binaire. CrowdStrike Falcon pour macOS enregistre la CommandLine complète, qui inclut le chemin du programme et ses arguments. Compilez le client dans d'autres outils lorsque cette image et cet argv seraient distinctifs. L'intégration ne supprime ni ni la ligne de bootstrap de launchd.log (voir ARCHITECTURE.md, Parentage).

Protocole XPC privé

launchd est un serveur bootstrap Mach. Ce client n'utilise pas le XPC public (xpc_connection_create). Il ouvre un pipe libxpc privé sur le bootstrap_port hérité avec xpc_pipe_create_from_port(bootstrap_port, 4), puis envoie _xpc_pipe_interface_routine. Ces symboles se trouvent dans libxpc et ne sont pas dans les en-têtes du SDK.

L'ID de routine est l'argument descripteur, et non une clé du dictionnaire de requête. Sur macOS 26.6.1 build 25G76, load est le descripteur 800 et bootout est 801. Les indicateurs d'interface sont 6. Une session gui/<uid> est requise : le port hérité n'est le domaine launchd gui qu'à l'intérieur d'une session de connexion Aqua, et ce client n'envoie que type 8 avec handle = uid.

Load (800) est un dictionnaire XPC. La définition du job n'est pas dans le corps du message.

root@kitploit:~
handle   uid (uint64)
type     8 (gui)
paths    [absolute .plist]
by-cli   true

launchd exécute stat sur le chemin, analyse la plist, puis exécute posix_spawn sur xpcproxy. xpcproxy exécute le programme dans le même PID. Le succès se traduit par un retour de pipe à 0, pas de xpc-fault, error 0, bootstrap-error 0.

Bootout (801) se fait par label : handle, type 8, name, no-einprogress, wait. Pas de plist.

Le canal relève de l'art antérieur. Jonathan Levin (launjctl, 2015 ; Mac OS X and iOS Internals Vol. 1) a montré que launchctl communique avec launchd via un pipe XPC privé et a documenté xpc_pipe_create_from_port / xpc_pipe_routine avec les clés de dictionnaire type, handle, subsystem, routine et name. Patrick Wardle (The Art of Mac Malware Vol. 2) a documenté _xpc_pipe_interface_routine comme point d'entrée d'envoi ultérieur. Csaba Fitzl et Brandon Dalton (OBTS) ont cartographié la même famille de dictionnaires et les codes de type de domaine (gui est 8). Des extraits publics utilisaient déjà xpc_pipe_create_from_port(bootstrap_port, 4).

Ces articles décrivent la classe de protocole. Ils ne fournissent pas les constantes de load réelles de 25G76. La capture de Levin de 2015 plaçait subsystem et routine dans le dictionnaire et utilisait xpc_pipe_routine. Sur 25G76, ces clés sont absentes. launchctl bootstrap passe par _xpc_pipe_interface_routine avec l'ID de routine comme argument descripteur. L'analyse statique arm64e de launchctl ressemble toujours à l'ancien chemin de dictionnaire et suggère 703 comme ID de load. L'exécution réelle de lldb en x86_64 et celle d'un client arm64 utilisent toutes deux 800 / 801 avec ces clés absentes. Ce client embarque cette forme unique sur les deux slices.

Les dumps de registres, la recette de re-pin lldb et les notes sur les champs de parenté se trouvent dans ARCHITECTURE.md.

Voir aussi

  • ARCHITECTURE.md : chemin de spawn, parenté, live wire, re-pin
  • AGENTS.md : hôte de compilation par rapport à la cible
Télécharger l’outil
Sous-commandeCycle de vie
oneshotOne-shot (RunAtLoad + LaunchOnlyOnce)
submitKeepAlive
loadplist détenue par l'appelant, telle qu'écrite
removeDéchargement par label
ASEPFilePath
  • Launchd enregistre également le spawn dans /private/var/log/com.apple.xpc.launchd/launchd.log (voir ARCHITECTURE.md, Parentage).
  • Ne mettez pas de clé XPCService dans une plist écrite à la main : xpcproxy effectue alors un fork et CrowdStrike Falcon pour macOS enregistre le parent xpcproxy.