Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
dyen — In-memory Mach-O dylib loader for stock macOS Python; decrypts, maps, and runs payloads without dlopen or writing to disk, with optional encrypted cover-file embedding. | Kitploit
Tools/GitHubGitHub/cenobyte-vincit/dyen
Payload GenerationIDS/IPS EvasionPost-ExploitationCryptographyBinary AnalysisRed TeamingPayload Development
GitHubcenobyte-vincit/dyen

dyen

In-memory Mach-O dylib loader for stock macOS Python; decrypts, maps, and runs payloads without dlopen or writing to disk, with optional encrypted cover-file embedding.

View Repository
2022 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

dyen

dyen loads a Mach-O dylib in memory using stock macOS Python and calls an exported entry point without dlopen on that dylib. The aim is that the image is never a standalone file on the target and is never registered with dyld.

by cenobyte [email protected] 2026

https://github.com/cenobyte-vincit/dyen

Summary

dyen is a stdlib-only in-memory Mach-O loader (loader.py) for stock /usr/bin/python3 on macOS. It reads a plain dylib, a standalone DYEN v1 blob (the encrypted envelope), or a cover file with that blob appended at EOF, maps the host slice with anonymous mmap, applies classic rebase/bind fixups, and calls dyld_main through a ctypes function pointer.

encrypt_dyld.py is the staging tool: it wraps a dylib as a DYEN blob on stdout, or copies a cover file to with the blob appended. The bundled payload only prints . is macOS-only; the encryptor runs on Linux or macOS.

dyld_<basename>
dyld/dyld.c
hello world
loader.py

The in-tree loader reads a local path or stdin. A dropper or stage 1 would fetch the cover file (or the raw bytes) over the network and pass them to read_dyld_bytes(), so the encrypted dylib never lands as its own file on the target.

  • File path or stdin: plain dylib, .dyen, or cover file.
  • Success writes the payload's stdout (PoC: hello world). Diagnostics go to stderr.
  • The process holds an executable anonymous mapping. Decrypted Mach-O is not written to disk.

Requirements

Runtime host

  • macOS (Darwin)
  • stock /usr/bin/python3 (stdlib only)

Build host

loader.py is macOS-only. Compiling the PoC dylib needs Apple clang on a Mac. encrypt_dyld.py runs on Linux or macOS against a pre-built dylib.

  • macOS with clang and make (to compile dyld/dyld.c)
  • Python 3 (stdlib only) for encrypt_dyld.py
  • ruff, pylint, cppcheck (brew install ruff pylint cppcheck)

Build

root@kitploit:~
make

This compiles build/libdyld.dylib (universal x86_64 + arm64), writes build/libdyld.dyen, and embeds the same encrypted dylib into pdf/dyld_c4611_sample_explain.pdf. The generated PDF is not committed; make writes it and make clean deletes it. The source cover is pdf/c4611_sample_explain.pdf.

The plain dylib is an intermediate artefact. At runtime the loader decrypts a DYEN blob before mapping, either from a standalone .dyen file or from a cover file with the encrypted dyld appended at the end.

root@kitploit:~
# 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 runs on Linux or macOS. loader.py runs on macOS only.

Usage

The loader CLI takes one file path, or reads stdin. It has no password flag. Decrypt tries this Mac's hardware serial first, then DEFAULT_DYLD_KEY (CHANGEMEASAP). If the serial cannot be read, only the hardcoded key is tried. The CLI always loads the input and calls dyld_main. Copy loader.py onto the target.

root@kitploit:~
/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

A cover file with no appended blob fails:

root@kitploit:~
/usr/bin/python3 loader.py pdf/c4611_sample_explain.pdf

loader.py --serial prints this Mac's hardware serial (macOS only):

root@kitploit:~
python3 loader.py --serial

Set LOADER_DEBUG to any value for verbose stderr traces:

root@kitploit:~
LOADER_DEBUG=1 /usr/bin/python3 loader.py build/libdyld.dyen

Exit codes

loader.py and encrypt_dyld.py:

CodeMeaning
0Success
1Usage error, decrypt failure, missing serial, or I/O error

Configuration

LOADER_DEBUG (any value) prints verbose traces to stderr: input source, header/magic checks, DYEN blob detection, decrypt, map/bind, symbol resolution. make passes LOADER_DEBUG=1 to encrypt_dyld.py by default; make LOADER_DEBUG= silences encrypt-step traces.

DEFAULT_DYLD_KEY in loader.py is CHANGEMEASAP. Encrypt requires an explicit password or Mac serial argument to encrypt_dyld.py.

The iteration count (PBKDF2_ITERATIONS in loader.py) follows the current OWASP Password Storage Cheat Sheet recommendation for PBKDF2-HMAC-SHA256 (600,000 iterations). PKCS #5 / RFC 8018 defines PBKDF2 but leaves the iteration count to the deployer; NIST SP 800-63B permits PBKDF2 without mandating a specific number. OWASP publishes practical targets that rise over time as hardware gets faster, so this value should be revisited periodically. It is password-stretching guidance applied here to the test key, not a permanent protocol constant.

Verify

Build host:

root@kitploit:~
make test
make lint        # cppcheck (C) + ruff + pylint (Python)
make check       # cppcheck only
make lint-python # ruff + pylint

make test runs the linters, builds the PoC dylib, encrypts it (standalone .dyen and PDF embed), and runs the integration tests under /usr/bin/python3. Each load test asserts stdout contains hello world. These tests run on a colocated Mac. They are not a clean-runtime proof.

Limitations

CrowdStrike Falcon macOS detects this technique. Other EDRs are untested.

The loader implements classic LC_DYLD_INFO rebase/bind only. It does not handle chained fixups, Objective-C, or Swift. dlopen is used only for Apple-signed system dependencies (for example /usr/lib/libSystem.B.dylib). The payload is never registered with dyld.

Append-at-EOF concealment is visible in a hex dump.

Weaponisation: change DYENCRYPT before operational use. The PoC magic in DYLD_BLOB_MAGIC (loader.py) is a static signature. YARA rules, file scanners, and simple rfind heuristics can key off it. Replace it with a unique value of your choice (keep encrypt and loader in sync; length is not fixed, but longer is better for embedded cover files).

The in-tree dylib only prints hello world.

See also

  • ARCHITECTURE.md
  • https://github.com/cenobyte-vincit/dyen
Download Tool