macOS Dyld Process
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Basic Information
The real entrypoint of a Mach-o binary is the dynamic linked, defined in LC_LOAD_DYLINKER
usually is /usr/lib/dyld
.
This linker will need to locate all the executables libraries, map them in memory and link all the non-lazy libraries. Only after this process, the entry-point of the binary will be executed.
Of course, dyld
doesn't have any dependencies (it uses syscalls and libSystem excerpts).
If this linker contains any vulnerability, as it's being executed before executing any binary (even highly privileged ones), it would be possible to escalate privileges.
Flow
Dyld will be loaded by dyldboostrap::start
, which will also load things such as the stack canary. This is because this function will receive in its apple
argument vector this and other sensitive values.
dyls::_main()
is the entry point of dyld and it's first task is to run configureProcessRestrictions()
, which usually restricts DYLD_*
environment variables explained in:
Then, it maps the dyld shared cache which prelinks all the important system libraries and then it maps the libraries the binary depends on and continues recursively until all the needed libraries are loaded. Therefore:
it start loading inserted libraries with
DYLD_INSERT_LIBRARIES
(if allowed)Then the shared cached ones
Then the imported ones
Then continue importing libraries recursively
Once all are loaded the initialisers of these libraries are run. These are coded using __attribute__((constructor))
defined in the LC_ROUTINES[_64]
(now deprecated) or by pointer in a section flagged with S_MOD_INIT_FUNC_POINTERS
(usually: __DATA.__MOD_INIT_FUNC
).
Terminators are coded with __attribute__((destructor))
and are located in a section flagged with S_MOD_TERM_FUNC_POINTERS
(__DATA.__mod_term_func
).
Stubs
All binaries sin macOS are dynamically linked. Therefore, they contain some stubs sections that helps the binary to jump to the correct code in different machines and context. It's dyld when the binary is executed the brain that needs to resolve these addresses (at least the non-lazy ones).
Som stub sections in the binary:
__TEXT.__[auth_]stubs
: Pointers from__DATA
sections__TEXT.__stub_helper
: Small code invoking dynamic linking with info on the function to call__DATA.__[auth_]got
: Global Offset Table (addresses to imported functions, when resolved, (bound during load time as it's marked with flagS_NON_LAZY_SYMBOL_POINTERS
)__DATA.__nl_symbol_ptr
: Non-lazy symbol pointers (bound during load time as it's marked with flagS_NON_LAZY_SYMBOL_POINTERS
)__DATA.__la_symbol_ptr
: Lazy symbols pointers (bound on first access)
Note that the pointers with the prefix "auth_" are using one in-process encryption key to protect it (PAC). Moreover, It's possible to use the arm64 instruction BLRA[A/B]
to verify the pointer before following it. And the RETA[A/B] can be used instead of a RET address.
Actually, the code in __TEXT.__auth_stubs
will use braa
instead of bl
to call the requested function to authenticate the pointer.
Also note that current dyld versions load everything as non-lazy.
Finding lazy symbols
Interesting disassembly part:
It's possible to see that the jump to call printf is going to __TEXT.__stubs
:
In the disassemble of the __stubs
section:
you can see that we are jumping to the address of the GOT, which in this case is resolved non-lazy and will contain the address of the printf function.
In other situations instead of directly jumping to the GOT, it could jump to __DATA.__la_symbol_ptr
which will load a value that represents the function that it's trying to load, then jump to __TEXT.__stub_helper
which jumps the __DATA.__nl_symbol_ptr
which contains the address of dyld_stub_binder
which takes as parameters the number of the function and an address.
This last function, after finding the address of the searched function writes it in the corresponding location in __TEXT.__stub_helper
to avoid doing lookups in the future.
However notice taht current dyld versions load everything as non-lazy.
Dyld opcodes
Finally, dyld_stub_binder
needs to find the indicated function and write it in the proper address to not search for it again. To do so it uses opcodes (a finite state machine) within dyld.
apple[] argument vector
In macOS the main function receives actually 4 arguments instead of 3. The fourth is called apple and each entry is in the form key=value
. For example:
Result:
By the time these values reaches the main function, sensitive information has already been removed from them or it would have been a data leak.
it's possible to see all these interesting values debugging before getting into main with:
dyld_all_image_infos
This is a structure exported by dyld with information about the dyld state which can be found in the source code with information like the version, pointer to dyld_image_info array, to dyld_image_notifier, if proc is detached from shared cache, if libSystem initializer was called, pointer to dyls's own Mach header, pointer to dyld version string...
dyld env variables
debug dyld
Interesting env variables that helps to understand what is dyld doing:
DYLD_PRINT_LIBRARIES
Check each library that is loaded:
DYLD_PRINT_SEGMENTS
Check how is each library loaded:
DYLD_PRINT_INITIALIZERS
Print when each library initializer is running:
Others
DYLD_BIND_AT_LAUNCH
: Lazy bindings are resolved with non lazy onesDYLD_DISABLE_PREFETCH
: DIsable pre-fetching of __DATA and __LINKEDIT contentDYLD_FORCE_FLAT_NAMESPACE
: Single-level bindingsDYLD_[FRAMEWORK/LIBRARY]_PATH | DYLD_FALLBACK_[FRAMEWORK/LIBRARY]_PATH | DYLD_VERSIONED_[FRAMEWORK/LIBRARY]_PATH
: Resolution pathsDYLD_INSERT_LIBRARIES
: Load an specifc libraryDYLD_PRINT_TO_FILE
: Write dyld debug in a fileDYLD_PRINT_APIS
: Print libdyld API callsDYLD_PRINT_APIS_APP
: Print libdyld API calls made by mainDYLD_PRINT_BINDINGS
: Print symbols when boundDYLD_WEAK_BINDINGS
: Only print weak symbols when boundDYLD_PRINT_CODE_SIGNATURES
: Print code signature registration operationsDYLD_PRINT_DOFS
: Print D-Trace object format sections as loadedDYLD_PRINT_ENV
: Print env seen by dyldDYLD_PRINT_INTERPOSTING
: Print interposting operationsDYLD_PRINT_LIBRARIES
: Print librearies loadedDYLD_PRINT_OPTS
: Print load optionsDYLD_REBASING
: Print symbol rebasing operationsDYLD_RPATHS
: Print expansions of @rpathDYLD_PRINT_SEGMENTS
: Print mappings of Mach-O segmentsDYLD_PRINT_STATISTICS
: Print timing statisticsDYLD_PRINT_STATISTICS_DETAILS
: Print detailed timing statisticsDYLD_PRINT_WARNINGS
: Print warning messagesDYLD_SHARED_CACHE_DIR
: Path to use for shared library cacheDYLD_SHARED_REGION
: "use", "private", "avoid"DYLD_USE_CLOSURES
: Enable closures
It's possible to find more with someting like:
Or downloading the dyld project from https://opensource.apple.com/tarballs/dyld/dyld-852.2.tar.gz and running inside the folder:
References
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Last updated