1.方法hook
先上代碼
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(test);
SEL swizzledSelector = @selector(swizzle_test);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)test {
NSLog(@"test");
}
- (void)swizzle_test {
[self swizzle_test];
}
一個方法對應一個sel和imp,方法A的imp指針和方法B的imp指針進行了替換,并且在要替換的方法中調用了一下原來的方法實現。這個也就就是開發中最常用到的方法混淆。
2.函數hook
推薦使用https://github.com/facebook/fishhook 這個庫來做函數hook,使用起來簡便。內部實現復雜,很強大,源碼基本上看不懂。大致原理還是要弄清楚的。例如有2個函數,函數A,函數B,想要將函數A的實現替換為函數B的實現。fishhook的實現大致分為2步,第一步找到目標函數的函數地址。第二步找到函數名,進行比對。匹配成功,替換目標函數地址為自己的函數地址。
struct rebinding {
const char *name; // 目標函數名
void *replacement; // 替換函數
void **replaced; // 二級目標函數指針
};
/*
struct rebinding rebindings[]:要重新綁定的結構體數組
rebindings_nel:要重新綁定的函數個數
*/
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
int rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel);
上代碼:
示例1:hook Foundation庫的NSSetUncaughtExceptionHandler()函數(生效)
static NSUncaughtExceptionHandler *g_vaildUncaughtExceptionHandler;
static void (*ori_NSSetUncaughtExceptionHandler)( NSUncaughtExceptionHandler *_Nullable);
void swizzle_NSSetUncaughtExceptionHandler( NSUncaughtExceptionHandler * handler) {
g_vaildUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
if (g_vaildUncaughtExceptionHandler != NULL) {
NSLog(@"UncaughtExceptionHandler=%p",g_vaildUncaughtExceptionHandler);
}
ori_NSSetUncaughtExceptionHandler(handler);
g_vaildUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
}
static void xr_uncaught_exception_handler (NSException *exception) {
NSLog(@"XX");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
rebind_symbols((struct rebinding [1]){"NSSetUncaughtExceptionHandler",swizzle_NSSetUncaughtExceptionHandler,(void *)&ori_NSSetUncaughtExceptionHandler}, 1);
NSSetUncaughtExceptionHandler(&xr_uncaught_exception_handler);
[@[] objectAtIndex:1];
}
return 0;
}
示例2:hook自己的函數(不生效)
void test(void) {
NSLog(@"test");
}
void swizzle_test(void) {
NSLog(@"swizzle_test");
}
static void (*funcptr)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 初始化一個 rebinding 結構體
struct rebinding test_rebinding = { "test", swizzle_test, (void *)&funcptr };
// 將結構體包裝成數組,并傳入數組的大小,對原符號 open 進行重綁定
rebind_symbols((struct rebinding[1]){test_rebinding}, 1);
// 調用原函數
test();
}
return 0;
}
函數的hook大致原理和方法hook類似。方法調用其實在運行時也就是轉成一個個函數調用。根據方法名(SEL)去查找對應的方法實現的函數地址(IMP),然后執行該函數。只不過函數和方法的存儲空間不一樣,查找函數地址的實現方法也就不一樣。但是原理是差不多的。
示例2中,發現hook不生效。而示例1中可以。很奇怪。原來fishhook只能hook其他鏡像中函數,不可以hook當前鏡像(庫或可執行文件)中運行的函數。在main函數執行之前,dylb會通過load_images加載項目里所有的鏡像。而Foundation庫和項目本身分別是兩個鏡像。所以示例1中hook是Foundation庫鏡像的函數。而示例2hook的則是本身鏡像中的函數。所以也就會不生效。
Snip20170425_14.png