
Cargador de dylibs Mach-O en memoria para Python estándar de macOS; descifra, mapea y ejecuta payloads sin usar dlopen ni escribir en disco, con incrustación opcional de archivo de cobertura cifrado.
dyen carga un dylib Mach-O en memoria usando el Python de serie de macOS y llama a un punto de entrada exportado sin usar dlopen sobre ese dylib. El objetivo es que la imagen nunca exista como archivo independiente en el objetivo y que nunca se registre con dyld.
por cenobyte [email protected] 2026
https://github.com/cenobyte-vincit/dyen
dyen es un cargador Mach-O en memoria exclusivo de la biblioteca estándar (loader.py) para el /usr/bin/python3 de serie en macOS. Lee un dylib simple, un blob DYEN v1 independiente (el sobre cifrado) o un archivo de cobertura con ese blob añadido al final (EOF), mapea la porción anfitriona con mmap anónimo, aplica las correcciones clásicas de rebase/bind y llama a dyld_main mediante un puntero a función de ctypes.
encrypt_dyld.py es la herramienta de preparación: envuelve un dylib como blob DYEN en la salida estándar, o copia un archivo de cobertura a con el blob añadido. El payload incluido solo imprime . es solo para macOS; el cifrador se ejecuta en Linux o macOS.
dyld_<basename>dyld/dyld.chello worldloader.pyEl cargador incluido lee una ruta local o la entrada estándar. Un dropper o una etapa 1 descargaría el archivo de cobertura (o los bytes crudos) a través de la red y los pasaría a read_dyld_bytes(), de modo que el dylib cifrado nunca aterrice como archivo propio en el objetivo.
.dyen o archivo de cobertura.hello world). Los diagnósticos van a stderr./usr/bin/python3 de serie (solo biblioteca estándar)loader.py es solo para macOS. Compilar el dylib PoC requiere clang de Apple en un Mac. encrypt_dyld.py se ejecuta en Linux o macOS contra un dylib ya compilado.
clang y make (para compilar dyld/dyld.c)encrypt_dyld.pyruff, pylint, cppcheck (brew install ruff pylint cppcheck)make
Esto compila build/libdyld.dylib (universal x86_64 + arm64), escribe build/libdyld.dyen e incrusta el mismo dylib cifrado en pdf/dyld_c4611_sample_explain.pdf. El PDF generado no está versionado; make lo escribe y make clean lo elimina. El archivo de cobertura fuente es pdf/c4611_sample_explain.pdf.
El dylib sin cifrar es un artefacto intermedio. En tiempo de ejecución el cargador descifra un blob DYEN antes de mapearlo, ya sea desde un archivo .dyen independiente o desde un archivo de cobertura con el dyld cifrado añadido al final.
# Standalone DYEN blob to stdout (Makefile redirects to build/libdyld.dyen)
python3 encrypt_dyld.py build/libdyld.dylib CHANGEMEASAP > build/libdyld.dyen
# Hide encrypted dyld inside a cover file -> pdf/dyld_c4611_sample_explain.pdf
python3 encrypt_dyld.py build/libdyld.dylib CHANGEMEASAP pdf/c4611_sample_explain.pdf
# Use this Mac's serial number as the encryption key instead of a password (local testing)
python3 encrypt_dyld.py build/libdyld.dylib "$(python3 loader.py --serial)" > build/libdyld.dyen
# Use the target Mac's serial number as the encryption key (fictitious serial)
python3 encrypt_dyld.py build/libdyld.dylib C07XH4V2MD6T > build/libdyld.dyen
encrypt_dyld.py se ejecuta en Linux o macOS. loader.py solo se ejecuta en macOS.
La CLI del cargador toma una ruta de archivo o lee de la entrada estándar. No tiene opción de contraseña. El descifrado prueba primero el número de serie del hardware de este Mac y luego DEFAULT_DYLD_KEY (CHANGEMEASAP). Si no se puede leer el número de serie, solo se prueba la clave fija. La CLI siempre carga la entrada y llama a dyld_main. Copia loader.py al objetivo.
/usr/bin/python3 loader.py build/libdyld.dylib
/usr/bin/python3 loader.py pdf/dyld_c4611_sample_explain.pdf
/usr/bin/python3 loader.py build/libdyld.dyen
cat build/libdyld.dylib | /usr/bin/python3 loader.py
cat pdf/dyld_c4611_sample_explain.pdf | /usr/bin/python3 loader.py
cat build/libdyld.dyen | /usr/bin/python3 loader.py
Un archivo de cobertura sin blob añadido falla:
/usr/bin/python3 loader.py pdf/c4611_sample_explain.pdf
loader.py --serial imprime el número de serie del hardware de este Mac (solo macOS):
python3 loader.py --serial
Establece LOADER_DEBUG a cualquier valor para ver trazas detalladas en stderr:
LOADER_DEBUG=1 /usr/bin/python3 loader.py build/libdyld.dyen
loader.py y encrypt_dyld.py:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Usage error, decrypt failure, missing serial, or I/O error |
LOADER_DEBUG (cualquier valor) imprime trazas detalladas en stderr: fuente de entrada, comprobaciones de cabecera/magic, detección de blobs DYEN, descifrado, map/bind y resolución de símbolos. make pasa LOADER_DEBUG=1 a encrypt_dyld.py por defecto; make LOADER_DEBUG= silencia las trazas del paso de cifrado.
DEFAULT_DYLD_KEY en loader.py es CHANGEMEASAP. El cifrado requiere una contraseña explícita o un argumento de número de serie de Mac para encrypt_dyld.py.
El número de iteraciones (PBKDF2_ITERATIONS en loader.py) sigue la recomendación actual de la OWASP Password Storage Cheat Sheet para PBKDF2-HMAC-SHA256 (600.000 iteraciones). PKCS #5 / RFC 8018 define PBKDF2 pero deja el número de iteraciones a criterio de quien lo despliega; NIST SP 800-63B permite PBKDF2 sin exigir un número concreto. OWASP publica objetivos prácticos que aumentan con el tiempo a medida que el hardware se vuelve más rápido, por lo que este valor debería revisarse periódicamente. Es una orientación de estiramiento de contraseñas aplicada aquí a la clave de prueba, no una constante de protocolo permanente.
Host de compilación:
make test
make lint # cppcheck (C) + ruff + pylint (Python)
make check # cppcheck only
make lint-python # ruff + pylint
make test ejecuta los linters, compila el dylib PoC, lo cifra (.dyen independiente e incrustado en PDF) y ejecuta las pruebas de integración con /usr/bin/python3. Cada prueba de carga verifica que la salida estándar contenga hello world. Estas pruebas se ejecutan en un Mac ubicado en el mismo sitio. No son una prueba de entorno limpio.
CrowdStrike Falcon para macOS detecta esta técnica. Otros EDR no han sido probados.
El cargador solo implementa el rebase/bind clásico de LC_DYLD_INFO. No gestiona chained fixups, Objective-C ni Swift. dlopen se usa únicamente para dependencias del sistema firmadas por Apple (por ejemplo /usr/lib/libSystem.B.dylib). El payload nunca se registra con dyld.
El ocultamiento por anexado al final del archivo (EOF) es visible en un volcado hexadecimal.
Weaponización: cambia DYENCRYPT antes de un uso operativo. El magic PoC en DYLD_BLOB_MAGIC (loader.py) es una firma estática. Las reglas YARA, los escáneres de archivos y las heurísticas simples con rfind pueden detectarla. Sustitúyela por un valor único de tu elección (mantén sincronizados el cifrador y el cargador; la longitud no es fija, pero cuanto más larga, mejor para archivos de cobertura incrustados).
El dylib incluido solo imprime hello world.