Skip to content
Code Injectionintermediate

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:

text
$ 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:

text
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 (or dyld_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. A weak or @rpath-relative dependency your check cannot resolve to a shipped file is the primary tell.
  • Check the code-signing status of everything under Contents/Frameworks and Contents/PlugIns with codesign -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_t file events) or fs_usage and watch for the app writing into its own Frameworks/ PlugIns directories, or for a dylib load (dyld image-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.

Votes

Comments(0)