一 HOOK之Method Swizzle
利用OC的Runtime特性,動(dòng)態(tài)改變SEL(方法編號(hào))和IMP(方法實(shí)現(xiàn))的對(duì)應(yīng)關(guān)系,達(dá)到OC方法調(diào)用流程改變的目的甲葬。主要用于OC方法甜刻。
#import <objc/message.h>
+(void)hookClass:(Class )class Withmethod:(SEL)oldMethod newMethod:(SEL)newMethod
{
Method old = class_getInstanceMethod(class, oldMethod);
Method news = class_getInstanceMethod(class, newMethod);
method_exchangeImplementations(old, news);
}
//交換方法
[hookManager hookClass:self.class Withmethod:@selector(old) newMethod:@selector(news)];
//類方法
[hookManager hookClass:object_getClass(self.class) Withmethod:@selector(classOld) newMethod:@selector(classNew)];
二 HOOK之 fishhook
Facebook提供的一個(gè)動(dòng)態(tài)修改鏈接mach-O文件的工具。利用MachO文件加載原理眨八,原理是通過修改懶加載和非懶加載兩個(gè)表的指針達(dá)到C函數(shù)HOOK的目的,所以不能修改自己定義的函數(shù),只能修改系統(tǒng)庫函數(shù)左电。
- 簡單使用
#import "fishhook.h"
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"123");
//定義rebinding結(jié)構(gòu)體
/*
struct rebinding {
const char *name;//需要HOOK的函數(shù)名稱,字符串
void *replacement;//替換到那個(gè)新的函數(shù)上(函數(shù)指針,也就是函數(shù)的名稱)
void **replaced;//保存原始函數(shù)指針變量的指針(它是一個(gè)二級(jí)指針!)
};
*/
struct rebinding nslogBind;
//函數(shù)的名稱
nslogBind.name = "NSLog";
//新的函數(shù)地址
nslogBind.replacement = myNSLog;
//保存原始函數(shù)地址的變量的指針
nslogBind.replaced = (void *)&old_nslog;
//定義數(shù)組
struct rebinding rebs[] = {nslogBind};
/*
arg1 : 存放rebinding結(jié)構(gòu)體的數(shù)組
arg2 : 數(shù)組的長度
*/
rebind_symbols(rebs, 1);
}
//函數(shù)指針,用保存原始的函數(shù)的地址
static void (*old_nslog)(NSString *format, ...);
//新的NSLog
void myNSLog(NSString *format, ...){
format = [format stringByAppendingString:@"\n勾上了!"];
//再調(diào)用原來的
old_nslog(format);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"點(diǎn)擊了屏幕!!");
}
- 原理
- APP運(yùn)行時(shí)踪古,MachO被dyld加載進(jìn)內(nèi)存
- ASLR讓MachO被加載時(shí)內(nèi)存地址隨機(jī)分配
- 蘋果的PIC位置與代碼獨(dú)立技術(shù),讓MachO調(diào)用系統(tǒng)庫函數(shù)時(shí)券腔,先在MachO表中的_DATA段建立一個(gè)指針指向外部庫函數(shù)伏穆,dyld加載MachO時(shí)知道外部庫函數(shù)的調(diào)用地址,會(huì)動(dòng)態(tài)的把_DATA段的指針指向外部庫函數(shù)
- fishhook通過nslogBind.name = "NSLog" 就能替換庫函數(shù)纷纫,是因?yàn)镸achO的符號(hào)表里有NSLog等枕扫,可以通過符號(hào)表找到NSLog字符串
- 原理求證
-
在 rebind_symbols(rebs, 1);處打個(gè)斷點(diǎn),進(jìn)入包內(nèi)容辱魁,找到MachO可執(zhí)行文件烟瞧,用MachOView打開找到懶加載和非懶加載兩個(gè)表诗鸭,如下:
得到NSLog的偏移量為0x8018
- 斷點(diǎn)斷住后,用lldb輸入image list得到如下:
(lldb) image list
[ 0] 48743862-C387-35A9-AC9E-973B65CCE0F4 0x0000000100688000 /Users/mac/Library/Developer/Xcode/DerivedData/001--fishHookDemo-gwgiujnhvjhvlsfgctoolqctiwnc/Build/Products/Debug-iphoneos/001--fishHookDemo.app/001--fishHookDemo
即MachO文件的起始地址是0x0000000100688000参滴,那么NSLog的地址是0x8018+0x0000000100688000
- lldb調(diào)試讀取NSLog地址的內(nèi)存
(lldb) x 0x0000000100688000+0x8018 //memory read 0x0000000100688000+0x8018
0x100690018: 80 a6 4d 85 01 00 00 00 a8 61 4d 85 01 00 00 00 ..M......aM.....
0x100690028: a0 56 9f 8e 01 00 00 00 64 eb 68 00 01 00 00 00 .V......d.h.....
- 得到內(nèi)容為0x01854da680,然后再dis -s 0x01854da680 輸出如下:
Foundation`NSLog:
0x1854da680 <+0>: sub sp, sp, #0x20 ; =0x20
0x1854da684 <+4>: stp x29, x30, [sp, #0x10]
0x1854da688 <+8>: add x29, sp, #0x10 ; =0x10
0x1854da68c <+12>: add x8, x29, #0x10 ; =0x10
0x1854da690 <+16>: str x8, [sp, #0x8]
0x1854da694 <+20>: add x1, x29, #0x10 ; =0x10
0x1854da698 <+24>: mov x2, x30
0x1854da69c <+28>: bl 0x18557bc68 ; _NSLogv
- 過了斷點(diǎn)后再x 0x0000000100688000+0x8018 得到如下:
0x100690018: c8 dd 68 00 01 00 00 00 a8 61 4d 85 01 00 00 00 ..h......aM.....
0x100690028: a0 56 9f 8e 01 00 00 00 d8 7d 55 84 01 00 00 00 .V.......}U.....
得到地址0x010068ddc8,然后dis -s 0x010068ddc8 得到:
001--fishHookDemo`myNSLog:
0x10068ddc8 <+0>: sub sp, sp, #0x30 ; =0x30
0x10068ddcc <+4>: stp x29, x30, [sp, #0x20]
0x10068ddd0 <+8>: add x29, sp, #0x20 ; =0x20
0x10068ddd4 <+12>: mov x8, #0x0
0x10068ddd8 <+16>: stur x8, [x29, #-0x8]
0x10068dddc <+20>: sub x9, x29, #0x8 ; =0x8
0x10068dde0 <+24>: str x0, [sp, #0x10]
0x10068dde4 <+28>: mov x0, x9
即NSLog被myNSLog替換了
- 通過符號(hào)表强岸,查找字符串
-
MachO的Dynamic Symbol Table下的Indirect Symbols表和 Lazy Symbol Pointers表一一對(duì)應(yīng)
-
_NSLog的Data 0x7A 是Symbols表的下標(biāo),0x7A的十進(jìn)制是122砾赔,那么找到Symbols表的#122位置處:
-
得到_NSLog在字符串表的偏移量是0x9B蝌箍,然后字符串表,表的起始地址是0xCEFC,加上偏移量0x9B得到0xcf97,在表中找到0xcf97果然找到NSLlog
總過程如圖
三 基本防護(hù)
因?yàn)槲募虞d的順序,防護(hù)代碼寫在framework中的load函數(shù)中暴心,因?yàn)閒ramework中的load函數(shù)會(huì)被先調(diào)用妓盲,從而起到防護(hù)作用
文件加載順序:framework中的load -> ViewController的load ->AppDelegate的load
基本防護(hù)代碼:
#import "fishhook.h"
#import <objc/message.h>
@implementation hookMgr
//專門HOOK
+(void)load
{
//內(nèi)部用到的交換代碼 寫在這里
Method old = class_getInstanceMethod(objc_getClass("ViewController"), @selector(btnClick1:));
Method new = class_getInstanceMethod(self, @selector(click1Hook:));
method_exchangeImplementations(old, new);
//基本防護(hù) 會(huì)檢測(cè)到外部的hook
struct rebinding bd;
bd.name = "method_exchangeImplementations";
bd.replacement = myExchang;
bd.replaced = (void *)&exchangeP;
struct rebinding bd2;
bd2.name = "method_setImplementation";
bd2.replacement = myExchang;
bd2.replaced = (void *)&setIMP;
// method_setImplementation
struct rebinding rebindings[] = {bd,bd2};
rebind_symbols(rebindings, 2);
}
//保留原來的交換函數(shù)
IMP _Nonnull (*setIMP)(Method _Nonnull m, IMP _Nonnull imp);
void (*exchangeP)(Method _Nonnull m1, Method _Nonnull m2);
//新的函數(shù)
void myExchang(Method _Nonnull m1, Method _Nonnull m2){
NSLog(@"檢測(cè)到了HOOK!!!");
//強(qiáng)制退出!
exit(1);
}
-(void)click1Hook:(id)sendr{
NSLog(@"原來APP的HOOK保留!!");
}