iOS逆向深入解析Hook的原理方法和安全防护

网友投稿 1276 2022-05-30

Hook 简介

Hook就是一种改变程序执行流程的一种技术的统称;

一段程序的执行流程是 A --> B --> C,现在我们在 A 和 B 之间插入一段代码或者直接改变 B ,这样程序原有的执行流程就发生了改变。如下图所示:

Hook的方式:Method Swizzle,fishhook,Cydia Substrate;

Hook 原理

利用OC的Runtime特性,动态去改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的主要用于OC方法。

Hook中主要用到的方法(参数: Class、SEL、IMP、Method):

// 1、方法交换 OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // 2、替换方法 OBJC_EXPORT IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); // 3、setIMP & getIMP OBJC_EXPORT IMP _Nonnull method_setImplementation(Method _Nonnull m, IMP _Nonnull imp) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0); OBJC_EXPORT IMP _Nullable class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

Class 一个 objc_class 类型的结构体指针,用于说明对象是哪个类;

Method 一个 objc_method 类型的结构体指针,用于定义一个方法,在objc源码中定义如下:

struct objc_method { SEL _Nonnull method_name OBJC2_UNAVAILABLE; char * _Nullable method_types OBJC2_UNAVAILABLE; IMP _Nonnull method_imp OBJC2_UNAVAILABLE; } OBJC2_UNAVAILABLE;

1

2

3

4

5

SEL 可以发现在 Method 的定义中,也能看见SEL。在苹果官方文档中定义这种类型:

typedef struct objc_selector *SEL;

1

IMP 同样在 Method 的定义中也能看到,在苹果官方文档的定义如下:

id (IMP *)(id, SEL, ...)

1

SEL是一个C String,用于表示一个方法的名称,IMP是一个方法实现首地址,默认有两个参数 self 和 _cmd。其实SEL和IMP的关系可以类比一本书的目录,SEL就是目录中的内容标题,IMP是后面的页码。一个方法调用时,通过SEL找到对应的IMP,进而找到方法的实现。如下图:

在Hook一个OC方法时,只需要改变其SEL所指向的IMP时,就可以实现方法的交换的目的,或者使用class_replaceMethod 和 method_setImplementation 改变一个类原有方法的实现,原理如下图:

逆向中,Hook一个OC方法其实就是改变其SEL所指向的IMP,从而找到另一个实现地址,执行另一个方法实现。但这种方法的局限在于,其只能针对OC方法进行Hook,对于C函数则无法Hook。

fishhook主要利用了共享缓存功能和PIC技术来实现hook功能。

fishHook是由faceBook开发的,是一个动态修改MachO文件的工具,主要是通过修改懒加载和非懒加载表里的指针的指向来达到hook的目的,因此一般用它来hook系统的C函数。

DATA区有两个section和动态符号链接相关:__nl_symbol_ptr 、__la_symbol_ptr;__nl_symbol_ptr为一个指针数组,直接对应non-lazy绑定数据。__la_symbol_ptr也是一个指针数组,通过dyld_stub_binder辅助链接。

fishhook实现:根据符号(字符)获取系统函数地址;替换符号指向的地址为用户声明的函数地址(符号绑定);对外部声明的指针进行系统函数地址赋值。

示例说明

static void (* old_log)(NSString* str); void newLog(NSString * str){ str = [str stringByAppendingString:@"\n 勾住了"]; old_log(str); } // hook rebind_symbols((struct rebinding [1]){{"NSLog",newLog,(void *)&old_log}}, 1);

1

2

3

4

5

6

7

rebind_symbols的简单逻辑

假设:

A -> 原方法 ,B -> 新方法 ,Temp -> 中间变量

Temp = A;

A = B;

hook后需要调用原方法就调用Temp

具体如下:

1⃣️ 首先,MachO文件是被dyld(dynamic load)动态加载的,也就是说,MachO文件被加载到内存的时候是不固定的,它有一个ASLR随机值;

2⃣️ 同样,依赖的系统库的加载也是随机的,因此,当MachO文件加载到内存之前根本不知道系统库在哪;

3⃣️ 因此 ,苹果采用了PIC技术(位置代码独立,位置和代码无关);

假设要调用NSLog函数:首先会在映射表中增加一个间接指针,指向外部的NSLog函数;然后dyld会动态的去绑定,将指针指向外部的函数地址。

machO符号表中有懒加载表(_la_symbol_ptr)和非懒加载表(_nl_symbol_ptr)的_data段,在表中存放着与外部绑定的函数指针,在懒加载端有offset地址,如下:

右边红色方框中的都是我们熟悉的名称,这些函数的使用是需要进行懒加载绑定的;

左边的offset提供了相对于MachO起始地址的偏移量offset,实际地址即是系统函数所在的内存地址;

我们观察NSLog函数,记住对应的offset=0x8018这个偏移量;

使用fishHook的rebind_symbols库函数交换系统NSLog函数:

#pragma mark - 交换NSLog - (void)exchangeLog { // 加载出来NSLog函数,生成内存地址(因为NSLog是Lazy Symbol Pointers) NSLog(@"我来了"); // hook NSLog函数 struct rebinding imp; imp.name = "NSLog"; imp.replacement = my_NSLog; imp.replaced = &sys_nslog; // 存放rebinding结构体数组,一次可以交换多个函数 struct rebinding rebs[1] = {imp}; rebind_symbols(rebs, 1); NSLog(@"点击了屏幕"); } // 实现一个函数来替换原有函数-函数名称即是函数的指针 void my_NSLog(NSString *format, ...) { printf("拦截打印\n"); sys_nslog(format); } // 定义指针来接收原始函数的指针 static void (*sys_nslog)(NSString *format,...);

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

然后运行输出,点击屏幕,查看控制台打印输出,可以看到方法已被拦截,说明NSLog函数已被替换;

所有的函数地址在编译后都是确定,在OC中能够交换方法是因为有sel和imp的连接过程,函数与用户之间有一个中间者,那么fishhook应该也是如此,修改了中间者的imp指向,否则直接调用函数,就没有交换的可能,在MachO中这个中间者叫符号。

在rebind_symbols(rebs, 1);调用前打断点,在控制台执行image list获取模块列表(image就是一个个模块,一个个MachO文件),如下:

上面的红色标志就是应用程序加载的起始地址(在程序运行期间是固定,重新启动该地址则会随机变化),然后根据这个地址去找NSLog函数的地址(偏移offset=0x8018),即在控制台继续输入:x 0x000000010453c000+0x8018

上图中的74 39 59 84 01 00 00 00即为NSLog函数所对应的值,由于CPU的小端模式,因此需要从右往左取值: 0x0184593074就是NSLog的内存地址;然后dis -s 0x0184593074:

继续让断点往下多执行一步,重复上面的操作,就可以看到fishhook对系统NSLog函数的替换:

fishHook官方说明示意图

回到MachO文件,来看看Lazy Symbol、Dynamic Symbol Table、Symbol Table、String Table表关系:

① Lazy Symbol和Dynamic Symbol Tabel一一对应(在数组的下标一致)这两个表包含了所有与动态库相关的符号;

② Dynamic Symbol Tabel和Symbol Table关联,Dynamic Symbol Table中的Data字段是Symbol Table数组的下标;

③ Symbol Table中的data字段地址 + String Table表的起始地址,就是目标函数对应字符的位置。

首先在Lazy Symbol Pointers中找到NSLog函数,它处于表中第一个元素;

找到indirect Symbols表中的NSLog项,它也处于表中第一个元素;

在上图中找到NSLog的Data地址0x94,等于十进制148,即为Symbol Table表中NSLog对应的下标,如下:

通过下标找到了对应的符号,主要上面的标注0xBA为String Table中的偏移量,表的起始地址加上偏移量即是函数名所在的位置;

通过首地址获取最终函数名,0xBA + 0x6180 = 0x623A,找到0x623A地址处,如下:

最终找到系统的函数NSLog。

Cydia Substrate 原名为 Mobile Substrate ,它的主要作用是针对OC方法、C函数以及函数地址进行HOOK操作。

跟method Swizzle类似,也是用的OC的动态性,知识Method Swizzle是用的方法交换,Cydia Substrate 是用的method_getImplementation和 method_setImplementation这两个方法,获取方法实现和设置方法实现。

Cydia Substrate主要由3部分组成:

MobileHooker

它定义一系列的宏和函数,底层调用objc的runtime和fishhook来替换系统或者目标应用的函数。主要有两个函数:MSHookMessageEx 主要作用于Objective-C方法,MSHookFunction 主要作用于C和C++函数;

MSHookMessageEx函数的作用对象是Objective-C函数,其原理是调用Objective-C中高等级的运行时函数API:class_getInstanceMethod、method_setImplementation、method_exchangeImplementations等来替换原函数的逻辑,其实MobileHooker就是对fishhook和runtime做了封装,就是和AFNetworking和NSURLSession的关系是一样的;

// MSHookMessageEx void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result) // MSHookFunction void MSHookFunction(voidfunction,void* replacement,void** p_original)

1

2

3

4

5

6

MobileLoader

MobileLoader的作用就是去加载第三方dylib,在ios启动的时候,会由launchd将MobileLoader载入内存,然后MobileLoader会根据同名的plist文件指定的作用范围,有选择地在不同的进程当中去通过dlopen函数打开目录/Library/MobileSubstrate/DynamicLibraries/下的所有的dylib。

safe mode

因为APP程序质量参差不齐崩溃再所难免,破解程序本质是dylib,寄生在别人进程里。 一旦出错,就可能导致整个进程崩溃,而一旦崩溃的是SpringBoard等系统进程,崩溃后就会造成iOS瘫痪。所以CydiaSubstrate引入了安全模式,在安全模式下所有基于CydiaSubstratede 的三方dylib都会被禁用,便于查错与修复。

Hook 流程(以Objective-C为例)

在 Objective-C 中,所有的 [receiver message] 都会转换为 objc_msgSend(receiver, @selector(message))。

在当前 class 的方法缓存里寻找(cache methodLists)

找到了跳到对应的方法实现,没找到继续往下执行;

从当前 class 的 方法列表里查找(methodLists),找到了添加到缓存列表里,然后跳转到对应的方法实现;没找到继续往下执行;

从 superClass 的缓存列表和方法列表里查找,直到找到基类为止;

以上步骤还找不到 IMP,则用_objc_msgForward函数指针代替 IMP ,最后执行这个 IMP;

// objc-runtime-new.mm 文件里与 _objc_msgForward 有关的三个函数使用伪代码展示,这也是 obj_msgSend 的实现过程 id objc_msgSend(id self, SEL op, ...) { if (!self) return nil; IMP imp = class_getMethodImplementation(self->isa, SEL op); // 调用这个函数,伪代码... imp(self, op, ...); } //查找IMP IMP class_getMethodImplementation(Class cls, SEL sel) { if (!cls || !sel) return nil; IMP imp = lookUpImpOrNil(cls, sel); // _objc_msgForward 用于消息转发 if (!imp) return _objc_msgForward; return imp; } IMP lookUpImpOrNil(Class cls, SEL sel) { if (!cls->initialize()) { _class_initialize(cls); } Class curClass = cls; IMP imp = nil; // 先查缓存,缓存没有时重建,仍旧没有则向父类查询 do { if (!curClass) break; if (!curClass->cache) fill_cache(cls, curClass); imp = cache_getImp(curClass, sel); if (imp) break; } while (curClass = curClass->superclass); return imp; }

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

第一个阶段的具体方法是+(BOOL)resolveInstanceMethod:(SEL)sel 和+(BOOL)resolveClassMethod:(SEL)sel,当方法是实例方法时调用前者,当方法为类方法时,调用后者。这个方法是为了给类利用 class_addMethod 添加方法的机会;

第二个阶段是备援接收者阶段,对象的具体方法是-(id)forwardingTargetForSelector:(SEL)aSelector ,此时,运行时询问能否把消息转给其他接收者处理,也就是此时系统给了个将这个 SEL 转给其他对象的机会;

第三个阶段是完整消息转发阶段,对应方法-(void)forwardInvocation:(NSInvocation *)anInvocation,这是消息转发流程的最后一个环节(hook方案的核心)。

直接替换原方法的实现为_objc_msgForward。当原来的函数被调用时,就不会在类方法,父类方法列表里查找实现了,直接表示找不到,进入转发流程。用_objc_msgForward来代替原来的函数,代码如下:

class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));

1

替换forwardInvocation:的实现,当进入转发流程时,阶段一和阶段二都不接手,在阶段三forwardInvocation:里会接手;这里会替换forwardInvocation:的实现,用newForwardInvocation代替,这样就可以hook,完成自己的逻辑后,还要调用被hook的函数原来的逻辑。

id newForwardInvocation = ^(id self, NSInvocation *invocation) { // hook时要添加的代码 if (originalForwardInvocation == NULL) { [self doesNotRecognizeSelector:invocation.selector]; } else { originalForwardInvocation(self, forwardInvocationSEL, invocation); } }; class_replaceMethod(class, @selector(forwardInvocation:), imp_implementationWithBlock(newForwardInvocation), "v@:@");

1

2

3

4

5

6

7

8

9

10

11

简单防护

我们知道Method Swizzle原理是方法交换,那么可以使用fishHook 修改method_exchangeImplementations函数的指向,这个修改指向要在我们的方法交换之后进行(保证自己能改,别人不能改)。

我们的方法交换要在别人hook之前执行,这个地方就需要将我们的方法封装到静态库中,静态库中的load方法会先加载;

它的原理是修改imp的set和get方法,因此我们也可以通过fishHook修改method_getImplementation和method_setImplementation方法。

示例

+ (void)load { // 先交换,防护之前要将所有的交换都写完 Method oldOne = class_getInstanceMethod(objc_getClass("ViewController"), @selector(btnClickOne:)); Method newOne = class_getInstanceMethod(self, @selector(ClickOneHook:)); method_exchangeImplementations(oldOne, newOne); // 基本防护 Method Swizzle struct rebinding bd ; // 原函数名(字符串) A函数 bd.name = "method_exchangeImplementations"; // 交换后的函数 B函数 bd.replacement = myExchange; // 暂存原函数的地址 中间变量temp函数 bd.replaced = (void *)&old_exchage; // 升级防护,用于防护logos(cydia substrate) // method_setImplementation struct rebinding bd1 ; bd1.name = "method_getImplementation"; bd1.replacement = myExchange; bd1.replaced = (void *)&getImp; struct rebinding bd2 ; bd2.name = "method_setImplementation"; bd2.replacement = myExchange; bd2.replaced = (void *)&setImp; // fishhook方法交换 struct rebinding rebind[] = {bd,bd1,bd2}; /* arg1:数组,元素必须是rebinding这个结构体 arg2:数组个数 */ rebind_symbols(rebind, 3); } // 用于存放method_exchangeImplementations涵数 也就是Temp函数 void (* old_exchage)(Class _Nullable cls, SEL _Nonnull name); // 用于存放method_getImplementation涵数 IMP _Nonnull(*getImp)(Method _Nonnull m) ; // 用于存放method_setImplementation涵数 IMP _Nonnull(*setImp)(Method _Nonnull m, IMP _Nonnull imp) ; // 新的交换函数 B函数 void myExchange(Class _Nullable cls, SEL _Nonnull name){ NSLog(@"检测到hook"); exit(1); }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

iOS逆向之深入解析Hook的原理方法和安全防护

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

iOS 数据结构

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Web安全和浏览器跨域访问
下一篇:深度解读设备的“万能语言”鸿蒙的分布式软总线能力
相关文章