
Caricatore in-memory di dylib Mach-O per Python macOS standard; decifra, mappa ed esegue payload senza dlopen o scrittura su disco, con incorporamento opzionale di un file di copertura cifrato.
dyen carica una dylib Mach-O in memoria usando la Python stock di macOS e chiama un entry point esportato senza dlopen su quella dylib. L'obiettivo è che l'immagine non sia mai un file standalone sul target e non venga mai registrata con dyld.
di cenobyte [email protected] 2026
https://github.com/cenobyte-vincit/dyen
dyen è un loader Mach-O in memoria basato solo sulla libreria standard (loader.py) per /usr/bin/python3 stock su macOS. Legge una dylib semplice, un blob DYEN v1 standalone (l'involucro cifrato) o un file copertina con quel blob aggiunto all'EOF, mappa la slice dell'host con mmap anonimo, applica i classici fixup rebase/bind e chiama dyld_main tramite un puntatore a funzione ctypes.
encrypt_dyld.py è lo strumento di staging: avvolge una dylib come blob DYEN su stdout, oppure copia un file copertina in dyld_<basename> con il blob aggiunto. Il payload dyld/dyld.c incluso stampa solo hello world. loader.py è solo macOS; l'encryptor gira su Linux o macOS.
Il loader incluso nell'albero legge un percorso locale o stdin. Un dropper o uno stage 1 recupererebbe il file copertina (o i byte grezzi) dalla rete e li passerebbe a read_dyld_bytes(), così la dylib cifrata non arriva mai come file autonomo sul target.
.dyen o file copertina.hello world). I diagnostici vanno su stderr./usr/bin/python3 stock (solo libreria standard)loader.py è solo macOS. Compilare la dylib PoC richiede clang Apple su un Mac. encrypt_dyld.py gira su Linux o macOS su una dylib precompilata.
clang e make (per compilare dyld/dyld.c)encrypt_dyld.pyruff, pylint, cppcheck (brew install ruff pylint cppcheck)make
Questo compila build/libdyld.dylib (universale x86_64 + arm64), scrive build/libdyld.dyen e incorpora la stessa dylib cifrata in pdf/dyld_c4611_sample_explain.pdf. Il PDF generato non è versionato; make lo scrive e make clean lo elimina. Il file copertina sorgente è pdf/c4611_sample_explain.pdf.
La dylib semplice è un artefatto intermedio. A runtime il loader decifra un blob DYEN prima del mapping, da un file .dyen standalone o da un file copertina con la dyld cifrata aggiunta alla fine.
# 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 gira su Linux o macOS. loader.py gira solo su macOS.
Il CLI del loader accetta un percorso file oppure legge da stdin. Non ha un flag per la password. La decifratura prova prima il serial hardware di questo Mac, poi DEFAULT_DYLD_KEY (CHANGEMEASAP). Se il serial non può essere letto, viene provata solo la chiave hardcoded. Il CLI carica sempre l'input e chiama dyld_main. Copia loader.py sul target.
/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 file copertina senza blob aggiunto fallisce:
/usr/bin/python3 loader.py pdf/c4611_sample_explain.pdf
loader.py --serial stampa il serial hardware di questo Mac (solo macOS):
python3 loader.py --serial
Imposta LOADER_DEBUG a qualsiasi valore per tracce verbose su stderr:
LOADER_DEBUG=1 /usr/bin/python3 loader.py build/libdyld.dyen
loader.py e encrypt_dyld.py:
| Code | Significato |
|---|---|
| 0 | Successo |
| 1 | Errore di utilizzo, decifratura fallita, serial mancante o errore I/O |
LOADER_DEBUG (qualsiasi valore) stampa tracce verbose su stderr: sorgente di input, controlli header/magic, rilevamento blob DYEN, decifratura, map/bind, risoluzione simboli. make passa LOADER_DEBUG=1 a encrypt_dyld.py di default; make LOADER_DEBUG= silenzia le tracce dello step di cifratura.
DEFAULT_DYLD_KEY in loader.py è CHANGEMEASAP. La cifratura richiede una password esplicita o un argomento serial Mac a encrypt_dyld.py.
Il numero di iterazioni (PBKDF2_ITERATIONS in loader.py) segue la raccomandazione attuale dell'OWASP Password Storage Cheat Sheet per PBKDF2-HMAC-SHA256 (600.000 iterazioni). PKCS #5 / RFC 8018 definisce PBKDF2 ma lascia il numero di iterazioni al deployer; NIST SP 800-63B consente PBKDF2 senza imporre un numero specifico. OWASP pubblica obiettivi pratici che aumentano nel tempo man mano che l'hardware diventa più veloce, quindi questo valore dovrebbe essere rivisto periodicamente. È una linea guida di password stretching applicata qui alla chiave di test, non una costante di protocollo permanente.
Host di build:
make test
make lint # cppcheck (C) + ruff + pylint (Python)
make check # cppcheck only
make lint-python # ruff + pylint
make test esegue i linter, compila la dylib PoC, la cifra (.dyen standalone e incorporamento PDF) e lancia i test di integrazione con /usr/bin/python3. Ogni test di caricamento verifica che stdout contenga hello world. Questi test girano su un Mac colocalizzato. Non sono una prova di runtime pulito.
CrowdStrike Falcon macOS rileva questa tecnica. Gli altri EDR non sono testati.
Il loader implementa solo il rebase/bind classico di LC_DYLD_INFO. Non gestisce chained fixups, Objective-C o Swift. dlopen è usato solo per dipendenze di sistema firmate Apple (ad esempio /usr/lib/libSystem.B.dylib). Il payload non viene mai registrato con dyld.
L'occultamento tramite append all'EOF è visibile in un dump esadecimale.
Weaponizzazione: cambia DYENCRYPT prima dell'uso operativo. La magic PoC in DYLD_BLOB_MAGIC (loader.py) è una firma statica. Le regole YARA, gli scanner di file e le semplici euristiche rfind possono basarsi su di essa. Sostituiscila con un valore unico a tua scelta (mantieni encrypt e loader sincronizzati; la lunghezza non è fissa, ma più è lunga meglio è per i file copertina incorporati).
La dylib inclusa nell'albero stampa solo hello world.