Skip to content

task_for_pid : le Mach trap le plus dangereux de macOS

Comment task_for_pid fonctionne, pourquoi Apple le verrouille, et ce que son modèle d'entitlements implique pour les outils de sécurité macOS.

Publié le 5 min de lecture

S'il fallait résumer le modèle de sécurité de macOS en un seul syscall, ce serait task_for_pid. C'est la primitive clé qui, en trois appels, donne à un processus la capacité de lire, écrire et exécuter du code dans n'importe quel autre processus du système. Chaque toolkit d'injection de code, chaque débogueur macOS, chaque EDR à scan mémoire — tous commencent ici.

Apple le sait, c'est pourquoi task_for_pid est l'un des syscalls les plus verrouillés du noyau. Ce post explique comment le trap fonctionne réellement, pourquoi les barrières existent, et ce qui change la donne pour les outils offensifs comme défensifs.

Le trap, mécaniquement

Le prototype est petit :

kern_return_t task_for_pid(
    mach_port_name_t  target_tport,   // mach_task_self()
    int               pid,            // processus cible
    mach_port_name_t *t               // out : send right sur le task port cible
);

Vous donnez au noyau votre propre task port et un PID cible. En cas de succès, vous récupérez un send right Mach vers le task port de la cible. Avec ce send right, vous pouvez appeler :

  • mach_vm_read — lire de la mémoire arbitraire dans la cible.
  • mach_vm_write — écrire de la mémoire arbitraire dans la cible.
  • mach_vm_allocate + mach_vm_protect — créer de la nouvelle mémoire RX.
  • thread_create_running — démarrer un nouveau thread à n'importe quel PC.

C'est l'intégralité du toolkit d'injection. Tout ce qui se présente comme une technique d'injection de code sur macOS — injection osascript, injection de dylib, patch de dyld-cache à distance — utilise une variation de ces quatre appels après que task_for_pid ait réussi.

Comment la barrière fonctionne

Dans XNU, task_for_pid() finit dans task_for_pid_posix_check() (dans bsd/vm/vm_unix.c). Les vérifications, dans leur ordre :

  1. Soi-même ? Si pid == getpid(), l'appel réussit toujours.
  2. Root + même session d'audit ? Un appelant root dans la même session d'audit que la cible réussit inconditionnellement.
  3. Hook de politique AMFI. AppleMobileFileIntegrity vote. Si l'appelant a l'entitlement com.apple.system-task-ports (réservé Apple), succès.
  4. Entitlement débogueur ? Si l'appelant a com.apple.security.cs.debugger et que la cible a get-task-allow, succès. C'est le chemin Xcode pour le debug.
  5. Politique MACF. Le framework MACF (où branchent TCC et Endpoint Security) a le veto final.
  6. Sinon : KERN_FAILURE. Loggé dans /var/log/system.log sous « Security policy would not allow process ».

Conclusion : un appelant non-root sans entitlement ne peut obtenir un task port que pour son propre processus, ou pour un enfant debug-allowed qu'il a lui-même lancé. Tout le reste échoue par conception.

Le raccourci Developer Tools

Réglages Système → Confidentialité et sécurité → Outils de développement est le chemin utilisateur pour relâcher la barrière. Quand un utilisateur y ajoute un processus, TCC enregistre une exception par-processus qui satisfait MACF à l'étape 5. C'est ainsi que Charles Proxy, Wireshark et la plupart des outils de profilage convainquent le noyau de leur remettre des task ports.

Pour l'automatisation, l'équivalent est le plist taskgated-helper et le droit d'autorisation system.privilege.taskport. Terrain connu pour les pipelines de build.

Pourquoi task_name_for_pid est le cousin « sûr »

Il existe un syscall sœur, task_name_for_pid, au nom trompeusement proche et à la capacité bien plus faible. Il retourne un port en nom seulement — vous pouvez lire certaines infos comptables (PID, jeton d'audit, état basique) mais pas la mémoire, ni écrire, ni changer l'état des threads. Apple l'a introduit précisément pour que les outils de monitoring puissent énumérer les processus sans déclencher la lourde barrière task_for_pid.

Deux cousins introduits dans macOS 11+ vont plus loin :

  • task_read_for_pid — port en lecture seule. Peut mach_vm_read mais pas mach_vm_write. Utilisé par sample, vmmap, et le Time Profiler.
  • task_inspect_for_pid — encore plus étroit ; ne lit que les statistiques.

Si vous écrivez un outil qui doit introspecter un autre processus sans écrire, préférez ces variantes. Elles survivent mieux au resserrement du sandbox et déclenchent moins d'alertes.

Ce que font réellement les attaquants

Les rapports publics 2024–2026 montrent deux schémas courants :

  1. task_for_pid en direct contre des PIDs non liés. Le classique. DazzleSpy, JokerSpy, ChromeLoader-mac, NotLockBit-mac — tous chaînent task_for_pid + mach_vm_write + thread_create_running. Ça marche quand le malware tourne en root (souvent via un LaunchDaemon malicieux) ou quand l'utilisateur a été socialement piégé pour ajouter le helper aux Outils de développement.

  2. Abus d'entitlement via binaires signés livrés. Trouver un binaire Apple-signé avec com.apple.system-task-ports et le shipper comme « helper ». Les travaux 2023 sur Find Any File et plus anciens sur le helper suid de Tunnelblick en sont des exemples publics. Apple révoque les signatures à mesure qu'ils sont repérés.

  3. debug_control_port_for_pid (plus récent). Introduit dans macOS 12, ce Mach trap retourne un task_control_port aux débogueurs entitlés, distinct du legacy task_for_pid. C'est désormais le chemin de Xcode sur Apple Silicon. Tout malware qui veut se faire passer pour un débogueur viendra ici s'il peut gagner l'entitlement.

Ce que les défenseurs doivent surveiller

Endpoint Security expose les événements clés :

  • ES_EVENT_TYPE_AUTH_GET_TASK — se déclenche avant la décision ; vous pouvez refuser.
  • ES_EVENT_TYPE_NOTIFY_GET_TASK — se déclenche après le succès ; vous pouvez enregistrer sans bloquer.
  • ES_EVENT_TYPE_AUTH_GET_TASK_NAME et _NOTIFY_GET_TASK_NAME — pour les cousins légers.

Règle de détection pratique : tout NOTIFY_GET_TASK dont l'appelant n'est pas un débogueur système (lldb-rpc-server, Xcode.app/Contents/MacOS/Xcode, Activity Monitor, sample, vmmap) et dont la cible est WindowServer, launchd, loginwindow, ou un processus navigateur. Le taux de faux positifs est étonnamment bas.

Si vous construisez de l'outillage là-dessus, le post suivant, Détecter l'abus de syscalls avec macOS Endpoint Security en 2026, parcourt le côté ES de bout en bout.

Pour aller plus loin

Articles associés

Correspondance pratique entre les syscalls macOS et les événements Endpoint Security pour la détection en EDR moderne.
XNU mélange un micro-noyau Mach 3 et une personnalité BSD : à quoi sert chaque famille de syscalls et comment elles diffèrent en pratique.