Aletheia is a macOS proof-of-concept that pairs an encrypted Mach-O executable with a companion watcher dylib.
The watcher enforces on-demand page decryption: encrypted code pages trigger a SIGSEGV, the dylib decrypts just-in-time, lets execution proceed, and then re-encrypts the page when it goes idle. The project is aimed at reverse-engineering and software-protection research demos.
- Self-decrypting payload –
parent/main.ckeeps its__TEXT,__textsection XOR-encrypted on disk and decrypts only the pages that are actively executing. - Mach exception choreography –
dylib/watcher.cinstalls aSIGSEGV/SIGBUShandler, flips page protections withmach_vm_protect, and usespthread_jit_write_protect_npon Apple Silicon to satisfy hardened runtime rules. - Black-box deploy –
scripts/encrypt_binary.pyscans Mach-O load commands to locate the text section, encrypts it, updatesmaxprot, and emits a runnable encrypted binary without manual offsets. - Auto-build workflow –
scripts/build.shcompiles every component, performs segment encryption, applies ad-hoc signatures with JIT entitlements, and drops artifacts inbuild/.
- Compile & encrypt – The build script compiles
parent_binary, then XORs its text section usingencrypt_binary.py, yieldingparent_binary_encrypted. The encryption key defaults to0x55. - Arm page protections – When the encrypted binary launches with
libwatcher.dylibinjected, the dylib identifies the executable image and marks its text pagesPROT_NONE. - Intercept faults – Any attempt to execute the encrypted code faults. The handler temporarily grants read/write, XOR-decrypts the page, clears the instruction cache, and restores RX permissions.
- Re-hide on idle – A monitor thread timestamps decrypted pages and re-encrypts any page that has been idle for ~500 ms, restoring
PROT_NONE.
- macOS 13 or newer (tested on Apple Silicon; x86_64 should work but is not exercised regularly).
- Xcode Command Line Tools providing
clang,codesign, and Mach headers (xcode-select --install). - Python 3.9+ for the encryption utility (uses only the standard library).
- Ability to run ad-hoc signed binaries (System Integrity Protection must remain enabled; no kernel modifications are required).
Run all commands from the repository root.
./scripts/build.sh
DYLD_INSERT_LIBRARIES=build/libwatcher.dylib ./build/parent_binary_encryptedExpected runtime log (truncated):
[watcher] Watcher dylib loaded.
[watcher] Protected pages armed: 0x8 (8)
SIGSEGV at address: 0x104038000
Decrypting page at 0x104038000
Page decrypted and executable.
Parent process started. Preparing payload...
[driver] Iteration 1 -> stage 2
...
To observe the failure mode without the watcher:
./build/parent_binary_encrypted # terminates with SIGILL/SIGTRAP because pages stay encryptedparent/main.c– long-running payload driver that fabricates data, exercises ten transformation stages, and periodically reports checksums.dylib/watcher.c– Mach-O introspection, fault handling, page accounting, and background re-encryption logic.scripts/build.sh– orchestrated build & sign pipeline.scripts/encrypt_binary.py– Mach-O parser and XOR encryptor; supports custom keys with--key.scripts/entitlements.plist– minimal entitlements granting JIT permissions for hardened macOS processes.build/– populated after running the build script (ignored by version control).
- Encryption key – change with
python3 scripts/encrypt_binary.py --key 0xAA(remember to rebuild or re-run the script if you skipbuild.sh). - Architecture –
build.shcompiles foruname -m. Override by exportingARCHbefore running the script, e.g.ARCH=x86_64 ./scripts/build.sh. - Output paths –
encrypt_binary.pytakes--input/--outputso you can encrypt other Mach-O binaries produced by the build.
otool -l build/parent_binary_encrypted | grep -A5 __TEXTto confirm theVMSIZEandmaxprotadjustments.otool -tV build/parent_binaryvs.build/parent_binary_encryptedto compare clear vs. XOR’d instructions.vmmap <pid>while the demo runs to inspect page protection flips in real time.
- Architecture mismatch – Ensure the dylib and binary share the same slice (
file build/*to inspect). Re-run the build script after forcingARCH. mach_vm_protectfailures – Usually indicates SIP-restricted permissions. Rerun the binary from an ad-hoc signed location (the build script handles signing).- Silent exits – Check
Console.appfor crash logs; executing the encrypted payload without the watcher should crash immediately withEXC_BAD_INSTRUCTION. - Python errors – Verify that Python can run from
/usr/bin/python3orpython3on PATH; no external packages are needed.
- The encryption routine uses XOR for simplicity; swap in a stronger cipher for realistic protection research.
- The demo touches low-level system APIs—run it only on systems you control and understand.
- All code is intended for educational use in exploring runtime code encryption, not for hiding malicious behaviour.
