Hook的原理就是改变程序的执行流程,采用面向切面(AOP)编程思想。
Runtime–Method Swizzling
得益于Objective-C这门语言的动态性,我们可以让程序在运行时做出一些改变,进而调用我们自己定义的方法。使用Runtime交换方法的核心就是Method Swizzling。
需要调用的方法为:method_exchangeImplementations
,它实际上将两个方法的实现进行交换:
1
2
3
4
IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);
在iOS中,每一个选择子(Selector, SEL类型),实际上是以C的字符串(const char *)来标识的,可以参考Swift,它主要用作方法的名字。而每一个Selector会对应一个实现,否则编译器会有警告,如果执行未实现的调用,怎会发生崩溃,而实现了的方法则用IMP(Implementation)来标识,即方法的实现。选择子(SEL)与实现(IMP)组成一个方法(Method)。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static inline void swizzleMethod(Class cls, SEL originSelector, SEL swizzledSelector) {
Method originMethod = class_getInstanceMethod(cls, originSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
// 添加原Selector是为了做一层保护,如果这个类没有实现原始方法"originalSel" ,但其父类实现了,那么class_getInstanceMethod会返回父类的方法,使用 method_exchangeImplementations实际上替换的是父类的方法。之所以这里的SEL是orginSelector,是因为将交换后方法的实现映射到原方法调用中
BOOL result = class_addMethod(cls, originSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (result) {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
} else {
method_exchangeImplementations(originMethod, swizzledMethod);
}
};
+ (void)load {
//
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originSel = @selector(viewWillAppear:);
SEL swizzledSel = @selector(lj_viewWillAppear:);
swizzleMethod(self, originSel, swizzledSel);
});
}
- (void)lj_viewWillAppear:(BOOL)animated {
[self lj_viewWillAppear:animated];
NSLog(@"%s", __func__);
}
RAC
本质还是通过Method Swizzle实现,只不过是通过关联方式返回了一个RACSubject的信号类。
示例:
1
2
3
4
5
6
7
8
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
ViewController *viewController = [super allocWithZone:zone];
[[viewController rac_signalForSelector:@selector(viewDidLoad)] subscribeNext:^(id x) {
NSLog(@"调用ViewDidLoad");
}];
return viewController;
}
fishhook
fishhook是FaceBook开源的可以动态修改Mach-O符号表的工具,仓库地址:https://github.com/facebook/fishhook
借助它,我们可以实现C函数的Hook,fishhook的实现依赖于PIC技术:
- 先在Mach-O文件_DATA段的符号表中为每一个被引用的系统C函数建立一个指针(8字节的数据,放的全是0),用于动态绑定时重定位到共享库中的函数实现。
- 在运行时当系统C函数被第一次调用时会动态绑定一次,然后将Mach-O文件_DATA段符号表中对应的指针,指向外部函数(其在共享库中的实际内存地址)。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static int (*orig_close)(int);
static int (*orig_open)(const char *, int, ...);
int my_close(int fd) {
printf("Calling real close(%d)\n", fd);
// 调用原函数
return orig_close(fd);
}
int my_open(const char *path, int oflag, ...) {
va_list ap = {0};
mode_t mode = 0;
// O_CREAT 如果不存在则创建
if ((oflag & O_CREAT) != 0) {
// mode 只适用于 O_CREAT
va_start(ap, oflag);
mode = va_arg(ap, int);
va_end(ap);
printf("Calling real open(%s, %d, %d)\n", path, oflag, mode);
return orig_open(path, oflag, mode);
} else {
printf("Calling real open(%s, %d)\n", path, oflag);
return orig_open(path, oflag, mode);
}
}
- (void)test {
// hook C函数,第一个参数是一个rebinding类型结构体数组,第二个参数是第一个参数结构体数组的长度
struct rebinding rebindings[2] = {
{"close", my_close, (void *)&orig_close},
{"open", my_open, (void *)&orig_open}
};
rebind_symbols(rebindings, 2);
// 获取二进制文件进行读取
NSString *binaryPath = [[NSBundle mainBundle] pathForResource:@"Hooks" ofType:@""];
const char *path = [binaryPath cStringUsingEncoding:NSUTF8StringEncoding];
int fd = open(path, O_CREAT);
uint32_t magic_number = 0;
// 读取4个字节
read(fd, &magic_number, 4);
printf("Mach-O Magic Number: %x \n", magic_number);
close(fd);
}
libffi
通过约定好参数的传递顺序、传递方式,栈维护的方式,名字修饰,动态地调用C函数
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int rectangleArea(int length, int width) {
printf("Rectangle length is %d, width is %d\n", length, width);
return length * width;
}
void *testArea() {
// dlsym 返回rectangleAred函数指针
void *dlsymFuncPtr = dlsym(RTLD_DEFAULT, "rectangleArea");
return dlsymFuncPtr;
}
void testFFICallCFunc() {
// libffi调用c函数
ffi_cif cif;
//参数类型指针数组, 根据被调用的函数入参的类型来制定
ffi_type *argumentTypes[] = {
&ffi_type_pointer,
&ffi_type_pointer,
&ffi_type_sint32,
&ffi_type_sint32
};
//用过ffi_prep_cif 内ffi_prep_cif_core 来设置ffi_cif结构体所需要的数据, 包括ABI
// 参数个数,参数类型等
ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 4, &ffi_type_pointer, argumentTypes);
// 函数参数的设置
int bar = 123;
int baz = 456;
void *args[] = {&bar, &baz};
// 返回值声明
int returnValue;
ffi_call(&cif, testArea(), &returnValue, args);
NSLog(@"ffi_call: %d", returnValue);
}
Cydia Substrate
主要用作逆向开发,制作Tweak插件。
使用 theos Tweak示例:
1
2
3
4
5
6
7
8
9
10
11
%hook ViewController
- (void)clickButton {
UIAlertController *alerView = [UIAlertController alertControllerWithTitle:@"测试" message:@"Hook成功,鼓励一下吧" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction =[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];
[alerView addAction:cancelAction];
[self presentViewController:alerView animated:YES completion:nil];
%orig;
}
%end
格式为:
1
2
3
4
5
6
7
8
9
10
%hook 后对应需要 Hook 的 Class
- (void)needHookMethod {
// 是否有返回值取决于该函数
// 调用原函数
%orig;
}
%end