Dylib Hijacking
An app's Mach-O binary resolves a dynamic library by a weak or relative path; placing a malicious dylib where the loader looks first runs attacker code with the host app's privileges.
Dylib hijacking is the macOS analogue of Windows DLL search-order hijacking. A
Mach-O binary records the dynamic libraries it needs as load commands, and some
of those paths are weak (LC_LOAD_WEAK_DYLIB, tolerated if missing) or use
@rpath/@executable_path tokens that are resolved relative to the running
binary's location at load time rather than to a single fixed system path. If an
attacker can write a dylib to a directory the loader consults before the
legitimate one — or supply a dylib for a weak/optional dependency that was
never actually shipped — dyld loads and runs it inside the host app's
process, inheriting its entitlements and any elevated trust that app carries.
How it works
otool -L on the target reveals the recorded library paths:
$ otool -L VulnerableApp.app/Contents/MacOS/VulnerableApp
@rpath/Sparkle.framework/Versions/A/Sparkle (compatibility version 1.0.0)
@executable_path/../Frameworks/Helper.dylib (compatibility version 1.0.0, weak)An @rpath entry is resolved against every LC_RPATH the binary defines, in
order; an @executable_path entry is resolved relative to the binary's own
directory. If either search turns up a writable location before the real
library, or the weak dependency was never bundled at all, dropping a
same-named dylib there is enough — no exploit, just an attacker-controlled file
landing first:
cp evil.dylib "VulnerableApp.app/Contents/Frameworks/Helper.dylib"The dropped dylib typically proxies every real export to the genuine library
(loaded manually with dlopen from its true path) so the host app keeps
working normally while the attacker's __attribute__((constructor)) function
runs once at load.
Detection & analysis
Static analysis:
- Run
otool -L(ordyld_info -dependents) against an app's main binary and every embedded helper, and separately confirm each recorded path actually resolves to a real, code-signed file. Aweakor@rpath-relative dependency your check cannot resolve to a shipped file is the primary tell. - Check the code-signing status of everything under
Contents/FrameworksandContents/PlugInswithcodesign -dv --verbose=4; an ad-hoc-signed or unsigned dylib sitting next to Apple- or vendor-signed ones stands out. - Compare a suspect dylib's exported symbol table (
nm -D,otool -Iv) against the genuine library's — a hijacking dylib re-exports the same symbols so the host app doesn't crash from a missing function.
Dynamic analysis:
- Run the target app under Endpoint Security (
es_message_type_tfile events) orfs_usageand watch for the app writing into its ownFrameworks/PlugInsdirectories, or for a dylib load (dyldimage-load notifications) resolving to a path outside the app bundle's expected signed contents. - Hardened Runtime with library validation enabled blocks loading a dylib not
signed by the same team ID as the main executable — testing whether that
entitlement is actually set (
codesign -d --entitlements :-) tells you whether this app is even exploitable this way.
Detection rule hint:
Alert when a process's dyld image list includes a library under the app's
own bundle whose code-signing team ID differs from the main executable's, or
whose signature is missing/ad-hoc — especially for a dependency recorded as
weak or @rpath-relative in the binary's own load commands.