Skip to content

Tracer les syscalls macOS avec dtrace sur Apple Silicon

Guide pratique pour tracer syscalls BSD et Mach traps avec DTrace sur macOS moderne — prérequis SIP, scripts qui marchent, et que faire quand les probes Apple disparaissent.

Publié le 4 min de lecture

DTrace reste l'outil le plus puissant pour tracer les syscalls sur macOS en 2026 — mais c'est aussi un outil qui se défend de plus en plus. SIP verrouille la plupart des probes utiles ; Apple Silicon a cassé des scripts qui supposaient des registres Intel ; et la séparation BSD/Mach impose deux providers différents pour couvrir l'ensemble. Ce guide montre ce qui marche encore et comment l'utiliser.

Prérequis

DTrace livre dans /usr/sbin/dtrace sur tout Mac, mais les probes nécessaires sont verrouillés par System Integrity Protection (SIP) :

  • Sans modifier SIP : vous pouvez tracer vos propres processus en lançant dtrace en root, et utiliser le provider syscall sur tout processus que vous lancez vous-même.
  • Avec dtrace_unrestrict (recommandé pour la recherche) : démarrez en recovery, csrutil enable --without dtrace, redémarrez. Vous pouvez désormais tracer n'importe quel processus — y compris les binaires Apple signés.

Sur un Mac de labo où réduire SIP est acceptable, c'est le second mode qu'il vous faut. Sinon, le provider syscall fonctionne encore sur les processus lancés en root.

Tracer tous les syscalls BSD d'un processus

Le script utile le plus simple : chaque syscall BSD, trié par fréquence, pour un PID donné.

#pragma D option quiet

syscall:::entry
/pid == $target/
{
    @[probefunc] = count();
}

END {
    printa("%-30s %@d\n", @);
}

Sauvegardez en syscall-count.d puis :

sudo dtrace -s syscall-count.d -c "/usr/bin/curl -s https://example.com -o /dev/null"

Vous verrez quelque chose comme :

mmap                              23
read                              31
stat64                            47
write                             12
close                             19
...

C'est le chemin BSD. Le provider syscall est un fin enrobage de unix_syscall64() dans le noyau — chaque entrée correspond 1:1 à une entrée du catalogue BSD.

Tracer les Mach traps

Les Mach traps ne passent pas par le provider syscall. Pour les voir, il faut le provider mach_trap.

#pragma D option quiet

mach_trap:::entry
/pid == $target/
{
    @[probefunc] = count();
}

END {
    printa("%-30s %@d\n", @);
}

Lancement identique. Vous verrez typiquement :

mach_msg2_trap                  1432
mach_reply_port                    7
task_self_trap                     3
thread_self_trap                  18
mk_timer_arm_leeway_trap          24

C'est le côté Mach — chaque entrée mappe vers une page de référence Mach trap. mach_msg2_trap domine le compte car presque toute forme de communication inter-processus sur macOS — XPC, distributed objects, IOKit, même la livraison d'événements AppKit — finit dans un message Mach.

Tracer un syscall précis avec ses arguments

C'est là que DTrace mérite sa place face à strace/fs_usage. Les probes ont des arguments typés inspectables inline :

#pragma D option quiet

syscall::open*:entry
{
    printf("[%d] %s\n", pid, copyinstr(arg0));
}
sudo dtrace -s open-trace.d

Chaque open() et openat() system-wide, avec le chemin résolu au moment du probe. Utile pour trouver des chemins en dur dans des binaires opaques, voir les chargements de configuration, ou construire une règle EDR sensible au contenu.

Les registres arg0arg7 sont les arguments du syscall dans l'ordre du prototype C. Pour open c'est (path, oflag, mode), donc arg0 est le pointeur sur le chemin.

Surveiller les appels à task_for_pid

Pour le travail de sécurité, le Mach trap le plus intéressant est task_for_pid. DTrace montre chaque appel réussi et raté system-wide :

#pragma D option quiet

mach_trap::task_for_pid:entry
{
    self->target_pid = arg1;
    self->caller = execname;
}

mach_trap::task_for_pid:return
/self->target_pid/
{
    printf("[%-15s pid=%-5d] task_for_pid(%d) -> %d\n",
           self->caller, pid, self->target_pid, arg1);
    self->target_pid = 0;
    self->caller = 0;
}

Vous verrez des usages légitimes (débogueur Xcode, Activity Monitor) et tout ce qui sort du normal (un processus utilisateur qui essaie de chopper launchd ou WindowServer). Sur un système propre, ce script est très silencieux.

Pourquoi les probes Apple disparaissent parfois

Trois pièges récurrents :

  1. SIP bloque les probes sur les binaires Apple signés. dtrace -p $(pgrep Safari) ne retournera rien — Safari est signé et AMFI refuse l'attache. Le contournement csrutil enable --without dtrace est la seule issue pour les binaires à entitlements restreints.

  2. syscall::entry ne voit pas les retours nosys. Quand un slot syscall a été retiré (ou qu'un numéro non implémenté est appelé), le provider syscall enregistre l'entrée mais arg0 du retour est ENOSYS et l'appel est dispatché vers un no-op. Vérifiez la valeur de retour si un compte semble étrangement haut.

  3. fbt (Function Boundary Tracing) est désactivé sur Apple Silicon. Sur macOS Intel, fbt:::entry pouvait sonder n'importe quelle fonction noyau. Sur arm64 c'est désactivé. Vous êtes limité aux providers bien connus (syscall, mach_trap, proc, sched, io).

Alternatives quand DTrace ne suffit pas

Quand DTrace refuse — binaires restreints, événements de plus haut niveau — Endpoint Security (ES) est le chemin moderne. ES n'est pas un traceur de syscalls, mais il expose ~80 événements curatés : ES_EVENT_TYPE_NOTIFY_OPEN, ES_EVENT_TYPE_NOTIFY_EXEC, ES_EVENT_TYPE_NOTIFY_GET_TASK, etc. La plupart correspondent à un syscall précis.

Le post suivant de cette série, Détecter l'abus de syscalls avec macOS Endpoint Security en 2026, parcourt la cartographie pratique entre syscalls et événements ES pour le travail de sécurité.

Pour aller plus loin

Articles associés

Ce qui se passe vraiment quand un programme arm64 sur Mac émet une instruction SVC — du stub utilisateur jusqu'au dispatcher BSD, et retour.
Correspondance pratique entre les syscalls macOS et les événements Endpoint Security pour la détection en EDR moderne.