
Mach-O-dylib-Loader im Speicher für unmodifiziertes macOS-Python; entschlüsselt, mappt und führt Payloads aus, ohne dlopen zu verwenden oder auf die Festplatte zu schreiben, mit optionaler Einbettung in eine verschlüsselte Tarn-Datei.
dyen lädt ein Mach-O-dylib mit dem Standard-macOS-Python in den Speicher und ruft einen exportierten Einstiegspunkt auf, ohne dlopen auf dieser dylib zu verwenden. Ziel ist, dass das Image auf dem Zielsystem nie als eigenständige Datei existiert und nie bei dyld registriert wird.
von cenobyte [email protected] 2026
https://github.com/cenobyte-vincit/dyen
dyen ist ein reiner Stdlib-In-Memory-Mach-O-Loader (loader.py) für das vorinstallierte /usr/bin/python3 unter macOS. Er liest eine unverschlüsselte dylib, einen eigenständigen DYEN-v1-Blob (den verschlüsselten Umschlag) oder eine Cover-Datei, an deren EOF dieser Blob angehängt ist, bildet die Host-Slice mit anonymem mmap ab, wendet klassische Rebase/Bind-Fixups an und ruft dyld_main über einen ctypes-Funktionszeiger auf.
encrypt_dyld.py ist das Staging-Werkzeug: Es verpackt eine dylib als DYEN-Blob auf stdout oder kopiert eine Cover-Datei nach dyld_<basename> mit angehängtem Blob. Das gebündelte Payload dyld/dyld.c gibt lediglich hello world aus. loader.py funktioniert nur unter macOS; der Encryptor läuft unter Linux oder macOS.
Der im Repository enthaltene Loader liest einen lokalen Pfad oder stdin. Ein Dropper oder Stage 1 würde die Cover-Datei (oder die Rohbytes) über das Netzwerk holen und an read_dyld_bytes() übergeben, sodass die verschlüsselte dylib auf dem Zielsystem nie als eigene Datei landet.
.dyen oder Cover-Datei.hello world). Diagnosemeldungen gehen an stderr./usr/bin/python3 (nur Stdlib)loader.py funktioniert nur unter macOS. Zum Kompilieren der PoC-dylib wird Apple-clang auf einem Mac benötigt. encrypt_dyld.py läuft unter Linux oder macOS und arbeitet mit einer zuvor gebauten dylib.
clang und make (zum Kompilieren von dyld/dyld.c)encrypt_dyld.pyruff, pylint, cppcheck (brew install ruff pylint cppcheck)make
Dies kompiliert build/libdyld.dylib (universal x86_64 + arm64), schreibt build/libdyld.dyen und bettet dieselbe verschlüsselte dylib in pdf/dyld_c4611_sample_explain.pdf ein. Das erzeugte PDF ist nicht eingecheckt; make schreibt es und make clean löscht es. Die Quell-Cover-Datei ist pdf/c4611_sample_explain.pdf.
Die unverschlüsselte dylib ist ein Zwischenartefakt. Zur Laufzeit entschlüsselt der Loader einen DYEN-Blob vor dem Mapping, entweder aus einer eigenständigen .dyen-Datei oder aus einer Cover-Datei mit der am Ende angehängten verschlüsselten dyld.
# 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 läuft unter Linux oder macOS. loader.py läuft nur unter macOS.
Die Loader-CLI akzeptiert einen Dateipfad oder liest von stdin. Sie hat kein Passwort-Flag. Die Entschlüsselung versucht zuerst die Hardware-Seriennummer dieses Macs, dann DEFAULT_DYLD_KEY (CHANGEMEASAP). Wenn die Seriennummer nicht gelesen werden kann, wird nur der hartkodierte Schlüssel versucht. Die CLI lädt die Eingabe immer und ruft dyld_main auf. Kopiere loader.py auf das Zielsystem.
/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
Eine Cover-Datei ohne angehängten Blob schlägt fehl:
/usr/bin/python3 loader.py pdf/c4611_sample_explain.pdf
loader.py --serial gibt die Hardware-Seriennummer dieses Macs aus (nur macOS):
python3 loader.py --serial
Setze LOADER_DEBUG auf einen beliebigen Wert für ausführliche stderr-Traces:
LOADER_DEBUG=1 /usr/bin/python3 loader.py build/libdyld.dyen
loader.py und encrypt_dyld.py:
| Code | Bedeutung |
|---|---|
| 0 | Erfolg |
| 1 | Nutzungsfehler, Entschlüsselungsfehler, fehlende Seriennummer oder I/O-Fehler |
LOADER_DEBUG (beliebiger Wert) gibt ausführliche Traces auf stderr aus: Eingabequelle, Header-/Magic-Prüfungen, DYEN-Blob-Erkennung, Entschlüsselung, Map/Bind, Symbolauflösung. make übergibt standardmäßig LOADER_DEBUG=1 an encrypt_dyld.py; make LOADER_DEBUG= unterdrückt die Traces des Verschlüsselungsschritts.
DEFAULT_DYLD_KEY in loader.py ist CHANGEMEASAP. Zum Verschlüsseln ist ein explizites Passwort- oder Mac-Seriennummernargument für encrypt_dyld.py erforderlich.
Die Iterationsanzahl (PBKDF2_ITERATIONS in loader.py) folgt der aktuellen Empfehlung des OWASP Password Storage Cheat Sheet für PBKDF2-HMAC-SHA256 (600.000 Iterationen). PKCS #5 / RFC 8018 definiert PBKDF2, überlässt die Iterationsanzahl jedoch dem Einsatzverantwortlichen; NIST SP 800-63B erlaubt PBKDF2, ohne eine bestimmte Zahl vorzuschreiben. OWASP veröffentlicht praktische Zielwerte, die im Laufe der Zeit steigen, da die Hardware schneller wird; dieser Wert sollte daher regelmäßig überprüft werden. Es handelt sich um eine Passwort-Stretching-Empfehlung, die hier auf den Testschlüssel angewendet wird, nicht um eine dauerhafte Protokollkonstante.
Build-Host:
make test
make lint # cppcheck (C) + ruff + pylint (Python)
make check # cppcheck only
make lint-python # ruff + pylint
make test führt die Linters aus, baut die PoC-dylib, verschlüsselt sie (eigenständige .dyen und PDF-Einbettung) und führt die Integrationstests mit /usr/bin/python3 aus. Jeder Ladetest prüft, dass die Standardausgabe hello world enthält. Diese Tests laufen auf einem am selben Ort befindlichen Mac. Sie sind kein Beweis für eine saubere Laufzeit.
CrowdStrike Falcon für macOS erkennt diese Technik. Andere EDRs sind ungetestet.
Der Loader implementiert nur klassisches LC_DYLD_INFO-Rebase/Bind. Er unterstützt keine Chained Fixups, kein Objective-C und kein Swift. dlopen wird nur für von Apple signierte Systemabhängigkeiten verwendet (zum Beispiel /usr/lib/libSystem.B.dylib). Das Payload wird nie bei dyld registriert.
Die EOF-Anhängung zur Tarnung ist in einem Hex-Dump sichtbar.
Bewaffnung: Ändere DYENCRYPT vor dem operativen Einsatz. Die PoC-Magic in DYLD_BLOB_MAGIC (loader.py) ist eine statische Signatur. YARA-Regeln, Dateiscanner und einfache rfind-Heuristiken können darauf anspringen. Ersetze sie durch einen eindeutigen Wert deiner Wahl (halte Encrypt und Loader synchron; die Länge ist nicht festgelegt, aber länger ist besser für eingebettete Cover-Dateien).
Die im Repository enthaltene dylib gibt nur hello world aus.