iOS面试 —— Hook Block(2)

前一篇讲了两个简单的Hook Block面试题目,主要了解一下基本的Hook Block的思路和注意的问题。实现都不算很难,属于基操,基本按照流程来就问题不大。

这里,打算对题目二进行一个额外的扩展,将题目改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 2.实现下面的函数,将任意参数 block 的实现修改成打印所有入参,并调用原始实现
//
// 比如
// void(^block)(int a, NSString *b) = ^(int a, NSString *b){
// NSLog(@"block invoke");
// }
// HookBlockToPrintArguments(block);
// block(123,@"aaa");
// 这里输出 "123,aaa" 和 "block invoke"

// void(^block)(int a, double b) = ^(int a, double b){
// NSLog(@"block invoke");
// }
// HookBlockToPrintArguments(block);
// block(123,3.14);
// 这里输出 "123,3.14" 和 "block invoke"

分析题目:首先,题目的本意和原本的题目二一样,就是hook block 的 invoke,然后将其所有的入参打印出来,再调用原实现。区别在于“任意Block”,这个任意block,就让我们无法对用来替换的函数有一个很合适的定义,因为我们定义的时候,根本就不知道我们即将hook的block有几个参数。

基于这个不确定性的参数,我们第一反应肯定是变参函数。如果将其定义为变参函数,是不是就解决了这个问题呢?

经过实践,效果似乎不太理想。主要原因猜测是因为变参函数的实参传入时存储的寄存器和定参函数实参传入的寄存器有所出入。仔细一想其实好像也是这么一回事,就像对象或者结构体一样,即使在运行过程中,可以用类型转换将其转换为任意结构体或者对象,但是它本质是不会变的,如果使用不当,就会产生问题。当然,这点仅仅是猜测,还未深究,总之就是变参函数这条路,我们是走不通的。

既然我们能现有的想到的方法无法如愿,那我们就只能另辟蹊径(百度google一顿搜索)了。网上也有挺多介绍hook block的示例代码,但是他们的思路都基本是用block hook block。总结一下就是“极限一换一”。怎么极限一换一呢?其实就是把目前我们遇到的问题,丢给使用者,就是让使用者实现一个block去hook 使用者想hook的block。当然,这个block是有要求的,就是要和厡block的返回值,参数类型和个数等等都一致,不然就GG。这显然不是我们最理想的解决办法。

抱着我们不可能是第一个遇到这个问题的人的信念,总算找到了我们想要的思路(其实包括实现都有)。鉴于这个代码已经封装成一个能使用的库了,直接使用显然不太符合我们的初衷,所以我们还是舍近求远,摒弃现有工具,直接对着源码理清思路自己操作。

源码里提到了另一个库libffi,这个是重点,我们遇到的问题,就靠它解决。

首先讲一下我查看源码看到的一个大致思路:

1、获取要hook的block的相关信息,例如返回值、参数列表。这些信息都存储在bkock的方法签名里。

2、通过上一步获取到的信息,利用libffi创建一个函数模板(ffi_prep_cif())。

3、创建动态调用函数,并替换block中的Invoke。

4、编写替换函数,并实现调用原函数。

思路基本就是这个样子。那我们现在就来写点代码走一下流程:

首先获取block的相关信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Block_layout *layout = (__bridge struct Block_layout *)block;

if (! (layout->flags & BLOCK_HAS_SIGNATURE)){
NSLog(@"当前block没有签名");
return;
}

uint8_t *desc = (uint8_t *)layout->descriptor;

desc += sizeof(struct Block_descriptor_1);

if (layout->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
struct Block_descriptor_3 *desc_3 = (struct Block_descriptor_3 *)desc;

const char *signature = desc_3->signature;
NSMethodSignature *m_signature = [NSMethodSignature signatureWithObjCTypes:signature];

创建函数模板:

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
ffi_type **args = malloc(sizeof(ffi_type *)*[m_signature numberOfArguments]);

// 返回值类型
ffi_type *return_ffi;
const char *return_type = [m_signature methodReturnType];
if (*return_type == @encode(_Bool)[0]) {
return_ffi = &ffi_type_sint8;
}else if (*return_type == @encode(signed char)[0]){
return_ffi = &ffi_type_sint8;
}else if (*return_type == @encode(unsigned char)[0]){
return_ffi = &ffi_type_uint8;
}else if (*return_type == @encode(short)[0]){
return_ffi = &ffi_type_sint16;
}else if (*return_type == @encode(int)[0]){
return_ffi = &ffi_type_sint32;
}else if (*return_type == @encode(long)[0]){
return_ffi = &ffi_type_sint64;
}else if (*return_type == @encode(long long)[0]){
return_ffi = &ffi_type_sint64;
}else if (*return_type == @encode(id)[0]){
return_ffi = &ffi_type_pointer;
}else if (*return_type == @encode(Class)[0]){
return_ffi = &ffi_type_pointer;
}else if (*return_type == @encode(SEL)[0]){
return_ffi = &ffi_type_pointer;
}else if (*return_type == @encode(void *)[0]){
return_ffi = &ffi_type_pointer;
}else if (*return_type == @encode(char *)[0]){
return_ffi = &ffi_type_pointer;
}else if (*return_type == @encode(float)[0]){
return_ffi = &ffi_type_float;
}else if (*return_type == @encode(double)[0]){
return_ffi = &ffi_type_double;
}else if (*return_type == @encode(void)[0]){
return_ffi = &ffi_type_void;
}else{
NSLog(@"未找到合适的类型");
return;
}
// 初始化参数列表
for (int i=0; i<[m_signature numberOfArguments]; i++) {
const char *type = [m_signature getArgumentTypeAtIndex:i];
if (*type == @encode(_Bool)[0]) {
args[i] = &ffi_type_sint8;
}else if (*type == @encode(signed char)[0]){
args[i] = &ffi_type_sint8;
}else if (*type == @encode(unsigned char)[0]){
args[i] = &ffi_type_uint8;
}else if (*type == @encode(short)[0]){
args[i] = &ffi_type_sint16;
}else if (*type == @encode(int)[0]){
args[i] = &ffi_type_sint32;
}else if (*type == @encode(long)[0]){
args[i] = &ffi_type_sint64;
}else if (*type == @encode(long long)[0]){
args[i] = &ffi_type_sint64;
}else if (*type == @encode(id)[0]){
args[i] = &ffi_type_pointer;
}else if (*type == @encode(Class)[0]){
args[i] = &ffi_type_pointer;
}else if (*type == @encode(SEL)[0]){
args[i] = &ffi_type_pointer;
}else if (*type == @encode(void *)[0]){
args[i] = &ffi_type_pointer;
}else if (*type == @encode(char *)[0]){
args[i] = &ffi_type_pointer;
}else if (*type == @encode(float)[0]){
args[i] = &ffi_type_float;
}else if (*type == @encode(double)[0]){
args[i] = &ffi_type_double;
}else{
NSLog(@"未知类型:注,结构体未处理");
return;
}
}

// _cif 定义的是全局变量 ffi_cif _cif;
ffi_status status = ffi_prep_cif(&_cif, FFI_DEFAULT_ABI, (int)[m_signature numberOfArguments], return_ffi, args);
if (status != FFI_OK) {
NSLog(@"初始化 cif 失败");
return;
}

创建并绑定动态调用的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 	_closure 定义的是全局变量		ffi_closure *_closure;
// _replacementInvoke 定义的是全局变量 void *_replacementInvoke;

_closure = ffi_closure_alloc(sizeof(ffi_closure), &_replacementInvoke);
if (!_closure) {
NSLog(@"hook 失败");
return;
}
ffi_status closure_loc_status = ffi_prep_closure_loc(_closure, &_cif, replace_bloke2_2, (__bridge void *)(NSObject.new), _replacementInvoke);
if (closure_loc_status != FFI_OK) {
NSLog(@"Hook failed! ffi_prep_closure returned %d", (int)status);
return;
}

替换block的invoke:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//    修改内存属性
vm_address_t invoke_addr = (vm_address_t)&layout->invoke;
vm_size_t vmsize = 0;
mach_port_t object = 0;
vm_region_basic_info_data_64_t info;
mach_msg_type_number_t infoCnt = VM_REGION_BASIC_INFO_COUNT_64;
kern_return_t ret = vm_region_64(mach_task_self(), &invoke_addr, &vmsize, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &infoCnt, &object);
if (ret != KERN_SUCCESS) {
NSLog(@"获取失败");
return;
}
vm_prot_t protection = info.protection;
// 判断内存是否可写
if ((protection&VM_PROT_WRITE) == 0) {
// 修改内存属性 ===> 可写
ret = vm_protect(mach_task_self(), invoke_addr, sizeof(invoke_addr), false, protection|VM_PROT_WRITE);
if (ret != KERN_SUCCESS) {
NSLog(@"修改失败");
return;
}
}
// 保存原来的invoke
origin_blockInvoke2_2 = (void *)layout->invoke;
layout->invoke = (uintptr_t)_replacementInvoke;

还有最后一步,也是最关键的一步,实现替换的函数:

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
45
46
47
48
49
50
51
52
53
54
55
56
void replace_bloke2_2(ffi_cif *cif, void *ret, void **args, void *userdata) {
struct Block_layout *layout = (struct Block_layout *)userdata;
uint8_t *desc = (uint8_t *)layout->descriptor;

desc += sizeof(struct Block_descriptor_1);

if (layout->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
struct Block_descriptor_3 *desc_3 = (struct Block_descriptor_3 *)desc;

const char *signature = desc_3->signature;
NSMethodSignature *m_signature = [NSMethodSignature signatureWithObjCTypes:signature];

NSLog(@"回调函数");
NSLog(@"%d",cif->nargs);
// 解析参数
for (int i=0; i<[m_signature numberOfArguments]; i++) {
ffi_type *arg = args[i];
const char *type = [m_signature getArgumentTypeAtIndex:i];
if (*type == @encode(_Bool)[0]) {
NSLog(@"%d",(bool)arg->size);
}else if (*type == @encode(signed char)[0]){
NSLog(@"%d",(char)arg->size);
}else if (*type == @encode(unsigned char)[0]){
NSLog(@"%d",(unsigned char)arg->size);
}else if (*type == @encode(short)[0]){
NSLog(@"%d",(short)arg->size);
}else if (*type == @encode(int)[0]){
NSLog(@"%d",(int)arg->size);
}else if (*type == @encode(long)[0]){
NSLog(@"%ld",(long)arg->size);
}else if (*type == @encode(long long)[0]){
NSLog(@"%lld",(long long)arg->size);
}else if (*type == @encode(id)[0]){
NSLog(@"%@",(__bridge id)((void *)arg->size));
}else if (*type == @encode(Class)[0]){
NSLog(@"%@",(__bridge Class)((void *)arg->size));
}else if (*type == @encode(SEL)[0]){
NSLog(@"%s",((char *)arg->size));
}else if (*type == @encode(void *)[0]){
NSLog(@"0x%llx",((long long)arg->size));
}else if (*type == @encode(char *)[0]){
NSLog(@"%s",((char *)arg->size));
}else if (*type == @encode(float)[0]){
NSLog(@"%f",((float)arg->size));
}else if (*type == @encode(double)[0]){
NSLog(@"%f",((double)arg->size));
}else{
NSLog(@"未知类型:注,结构体未处理");
}
}
// 调用原函数
ffi_call(&_cif,(void *)origin_blockInvoke2_2, ret, args);

}

最后测试代码:

1
2
3
4
5
6
7
8
9
10
11
//        test2
void (^test_block2)(int a, NSString *b)= ^(int a, NSString *b) {
NSLog(@"test2: block invoke");
};
HookBlockToPrintArguments(test_block2);
test_block2((int)123,@"bbb");
void (^test_block2_2)(int a, NSString *b,NSObject *c)= ^(int a, NSString *b,NSObject *c) {
NSLog(@"test2_2: block invoke");
};
HookBlockToPrintArguments(test_block2_2);
test_block2_2((int)123,@"bbb",NSObject.new);

log如下:

1
2
3
4
5
6
7
8
9
10
11
......
2021-06-19 12:06:59.020612+0800 HookBlockDemo[79596:3789053] <__NSGlobalBlock__: 0x102d5c088>
2021-06-19 12:06:59.020637+0800 HookBlockDemo[79596:3789053] 123
2021-06-19 12:06:59.020677+0800 HookBlockDemo[79596:3789053] bbb
2021-06-19 12:06:59.020699+0800 HookBlockDemo[79596:3789053] test2: block invoke
......
2021-06-19 12:06:59.021114+0800 HookBlockDemo[79596:3789053] <__NSGlobalBlock__: 0x102d5c0c8>
2021-06-19 12:06:59.021136+0800 HookBlockDemo[79596:3789053] 123
2021-06-19 12:06:59.021177+0800 HookBlockDemo[79596:3789053] bbb
2021-06-19 12:06:59.021315+0800 HookBlockDemo[79596:3789053] <NSObject: 0x283c800d0>
2021-06-19 12:06:59.021348+0800 HookBlockDemo[79596:3789053] test2_2: block invoke