Tooling · macOS XPC research

Poppy: dynamic XPC observability for the PAC era

An open-source macOS toolkit pairing Frida instrumentation, DTrace probes and a small set of custom injectors to produce a unified JSONL trace of XPC daemon behaviour. Designed for the arm64e platform, where Pointer Authentication Codes have made static call graphs unreliable and live observation is no longer optional.

24 May 2026 · Toolset reference · github.com/jetnoir/poppy
Status: early-stage · Platform: macOS arm64e · Licence: MIT

The platform problem

The standard recipe for understanding a macOS daemon used to be straightforward: extract the binary from the dyld shared cache, run it through a disassembler, walk the call graph, identify the XPC handler vtable, follow the dispatch logic. On x86_64 that recipe was sound. On arm64e — the silicon Apple has shipped on consumer hardware since 2020 — it is no longer sufficient.

Pointer Authentication Codes embed a cryptographic signature in the high bits of indirect-branch targets. Signed pointers in live memory do not resemble the unsigned pointers a disassembler shows. Dynamic dispatch through PAC-signed vtables becomes invisible to static analysis — the disassembler sees that a virtual call is happening, but not where it lands. This is a deliberate hardening property, working as designed. It is also a hard problem for static-only research methodology.

Poppy is the practice’s answer to that problem: observe the daemon at runtime, capture what it actually calls, and reconstruct the call graph from runtime evidence.

Capabilities

A typical workflow

# 1. Trace the daemon under ordinary use
sudo python3 poppy.py run --daemon tipsd --duration 60

# 2. Apply the standard fault-injection corpus
sudo python3 poppy.py inject --daemon tipsd --variants all

# 3. Identify behaviour that deviates from baseline
python3 analysers/anomaly.py runs/poppy_tipsd_*.jsonl

# 4. Produce the effective entitlement map
python3 analysers/entitlement_map.py runs/poppy_*.jsonl --md > entitlements.md

Each step writes timestamped JSONL into runs/. Steps three and four operate over any earlier trace and can be re-run as analysis posture evolves.

Design choices worth flagging

JSONL as the lingua franca. Every component — Frida agent, DTrace probe, Python harness — emits one JSON object per line. Downstream consumption is streaming. The format is deliberately boring: a one-hour trace produces a file that grep handles sensibly, and the analyser pipeline does not need to be Python-aware to participate.

Frida and DTrace, paired. Frida supplies the high-level dispatch view — it can hook the Objective-C runtime, inspect message arguments, modify state. DTrace supplies the system-call view — what the daemon actually asks the kernel for. Neither alone is sufficient; the two streams are merged on timestamp at analysis time.

Root and SIP posture. The toolchain requires root for Frida injection into system daemons. DTrace probes against Apple-signed binaries require SIP to be disabled, or the equivalent nvram boot-args. This is a research-box posture and the documentation is unambiguous about it; Poppy is not designed to run on a workstation.

Optional GUI. A PySide6 dashboard for live trace viewing is provided as an optional dependency. The command line is canonical; the GUI is convenience.

Where Poppy fits in the practice’s methodology. Where Metis triages compiled binaries against a library of defect templates, Poppy supplies the behavioural evidence that converts a static hypothesis into a runtime observation. The two tools are intended to be used together on macOS targets — Metis flags candidate handler functions, Poppy watches whether they fire and under what conditions.

Honest scope

Poppy is early-stage. The repository is small, the test coverage is limited, and the public release lags the practice’s working tree by a non-trivial margin. The core workflow — trace, inject, analyse — is solid in operational use; the rough edges are around configuration, error reporting and the GUI dashboard.

It is published openly because the underlying technique — pairing Frida and DTrace, normalising on JSONL, building a methodology around live observation of XPC behaviour — is worth being part of the public conversation about macOS security research in the PAC era. The exact code is secondary to that methodology.

What it does not do

Engagement

Patches, issues and methodology discussion are welcome through the GitHub project. Commercial support is not offered.

Repository & documentation

Source: github.com/jetnoir/poppy

The repository ships a User Guide, an Operations Manual, and a Troubleshooting reference. Licence is MIT; the exact text is in the repository.

Dependencies and credit

Poppy is built on Frida and uses macOS-native DTrace. Optional components depend on PySide6 (Qt for Python) and PyObjC. All are the work of their respective authors, used under their own licences. Their inclusion as dependencies should not be read as endorsement of Poppy by their maintainers.

Legal note

Poppy is published under the MIT licence. No warranty is given as to fitness for any particular purpose. Users are responsible for ensuring that their use of the tool complies with applicable law, including the Computer Misuse Act 1990 (England and Wales) and the relevant Apple developer agreements. The tool is intended for use against systems owned by the user.