今天我们将继续讲解逆向开发工程另一个重要内容 --Hook 原理讲解. Hook, 可以中文译为 "挂钩" 或者 "钩子", 逆向开发中改变程序运行的一种技术. 按照如下过程进行讲解
Hook 概述
Hook 技术方式
fishhook 原理及实例
符号表查看函数名称
总结
一, Hook 概述
在逆向开发中是指改变程序运行流程的技术, 通过 Hook 可以让自己的代码运行在别人的程序中. 需要了解其 Hook 原理, 这样就能够对恶意代码攻击进行有效的防护.
二, Hook 技术方式
2.1 Method Swizzle 方式
Method Swizzle 上次已经讲到, 是利用 OC 的 Runtime 的特性, 去动态改变 SEL(方法编号) 与 IMP(方法实现) 的对应关系, 达到 OC 方法调用流程更改的目的. 也是主要用于 OC 方法.
2.2 Cydia Substrate 方式
Cydia Substrate 原名叫做 Mobile SubStrate, 主要作用为针对 C 函数, OC 函数以及函数的地址进行 Hook 操作. 并且有个很大的优势, Cydia Substrate 并不是仅仅是针对 iOS 设计, Andriod 一样也可以使用.
2.2.1
Cydia Substrate 定义了一系列的函数和宏, 底层调用了 objc 的 runtime 和 fishHook 来替代目标函数或者系统方法.
其中有两个函数
MSHookMessageEx 主要用于 OC 方法
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result)
MSHookFunction 主要用于 C++ 和 C 函数
- void MSHookFunction(voidfunction,void* replacement,void** p_original)
- 2.2.2 MobileLoader
MobileLoader 主要用于加载第三方 dylib 运行的应用程序中. 启动时 MobileLoader 会根据指定的第三方动态库加载进去, 第三方动态库也是我们写的破解程序.
2.2.3 safe mode
破解程序的本质在于 dylib, 寄生于别人程序进程中. 但是系统进程一旦出现错误, 可能会导致整个进程崩溃, 也可能会导致 iOS 程序崩溃. 在 Cydia Substrate 中引入了安全模式, 如果一旦错误, 三方的 dylib 会被禁用, 便于查错和修复.
2.3 fishHook
fishHook 是 Facebook 提供一种动态修改链接 Mach-O 文件的工具. 此利用 Mach-O 文件加载原理, 通过修改非懒加载和懒加载两个表的指针达到 C 函数的 Hook 的目的.
今天我们主要讲解第三种方式 fishHook 达到更改程序的目的.
三, fishhook 原理及实例
3.1 概述
fishhook 的源码地址为 https://github.com/facebook/fishhook
fishhook 的主要方法有两个还有一个结构体
查看代码结构为, 将红色圈起来部分移入到代码中, 即可使用 fishhook 来 hook 代码.
3.2 实例
3.2.1 Demo1 实例 1
- // rebinding 结构体的定义
- // struct rebinding {
- // const char *name; // 需要 HOOK 的函数名称, 字符串
- // void *replacement; // 替换的新函数 (函数指针, 也就是函数名称)
- // void **replaced; // 保存原始函数指针变量 / 地址的指针 (它是一个二级指针!)
- // };
- // C 语言传参是值 / 址传递的, 把它的值 / 址穿过去, 就可以在函数内部修改函数指针变量的值
- - (void)viewDidLoad {
- [super viewDidLoad];
- NSLog(@"123");
- //rebinding 结构体
- struct rebinding nslog;
- nslog.name = "NSLog";// 函数名称
- nslog.replacement = myNslog; // 新的函数指针
- nslog.replaced = (void *)&sys_nslog;// 保存原始函数地址的变量的指针
- //rebinding 结构体数组
- struct rebinding rebs[1] = {nslog};
- /**
- * 存放 rebinding 结构体的数组
- * 数组的长度
- */
- rebind_symbols(rebs, 1);
- }
- //--------------------------------- 更改 NSLog-----------
- // 函数指针, 用来保存原始的函数地址 (C 语言语法, 函数指针类型变量)
- static void(*sys_nslog)(NSString * format,...);
- // 定义一个新的函数
- void myNslog(NSString * format,...){
- format = [format stringByAppendingString:@"勾上了!\n"];
- // 调用原始的
- sys_nslog(format);
- }
- -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- NSLog(@"点击了屏幕!!");
- }
上面的代码运行结果如下:
3.2.2 Demo2 实例 2
- void func(const char * str){
- NSLog(@"%s",str);
- }
- - (void)viewDidLoad {
- [super viewDidLoad];
- //rebinding 结构体
- struct rebinding nslog;
- nslog.name = "func";
- nslog.replacement = new_func;
- nslog.replaced = (void *)&old_func;
- //rebinding 结构体数组
- struct rebinding rebs[1] = {nslog};
- /**
- * 存放 rebinding 结构体的数组
- * 数组的长度
- */
- rebind_symbols(rebs, 1);
- }
- //--------------------------------- 更改 NSLog-----------
- // 函数指针
- static void(*old_func)(const char * str);
- // 定义一个新的函数
- void new_func(const char * str){
- NSLog(@"%s + 1",str);
- }
- -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- {
- func("哈哈");
- }
运行结果如下:
从上面可以看出自定义的交换方法为什么交换不了呢? 首先可以肯定的是代码是 OK 的, 下面我们讲解原理, 为什么自定义的方法不行呢?
3.3 原理探究
Mach-O 文件是如何加载的?
Dyld 工具动态加载, 加载 MachO 文件完成后, 开始加载依赖的动态库, 也就是通过上篇博客的 image List 可看到相关的类库.
PIC(Promrammable Interrupt Controller) 位置代码独立, 由外设发出中断请求需要中断控制器来处理.
Mach-O 文件内部调用系统函数时:
Mach-O _data 段建立了一个指针 (也就是符号, 实现指向内部的函数调用, 指向了外部的函数地址), 指向了外部函数 (dyld), 可读可写, 当 Mach-O 被加载进去, 就会指向所指的函数.
Dyld 会动态的绑定, 将 Mach-O 中的 data 段中指针指向了外部的函数, 也是 Dyld 为什么叫做动态绑定的原因.
这也回答了上面的问题, 为什么内部 / 自定义的函数不能修改, 只能修改 Mach-O 文件的外部函数, 如果是另外一个动态库或者需要动态符号绑定的就可以 (符号表中能找到才可以实现)
下面我们是真实查看内容, 通过实例
利用第一个 Demo 来测试, 运行起来, 然后查看可执行文件, 通过 MachoView 工具
从图 2 看出 offset 偏移地址为 3028, 也就是 NSLog 函数文件的偏移地址, 懒加载此表时在 Mach-O 文件偏移地址 + 函数偏移的地址.
下面以 Demo1 查看, 在 Demo1 打断点, 查看 Mach-O 函数偏移地址, 通过指令 image list 第一个就是 Mach-O 内容和地址 (本人上篇博客地址即可)
Mach-O 在内存的偏移地址也就是 Mach-O 的真实地址, 发现为 0x000000010a9c5000
通过上面红色加重算法, 计算 Mach-O 文件 Data 段的函数指针
发现执行完只有就会被绑定. NSLog 函数文件就会被绑定.
下面再看一下, 对于屏幕点击的, hook 如下
前提是我们去除 ViewDidLoad 方法里面的 NSLog(@"123") 这句代码, 运行代码, 最后将断点断在 touchesBegan 里面, 此时开始看地址和内容
截图的前两次打印是程序运行时, 但是未曾点击 touchesBegan, 后两次是点击屏幕时断点进入到了里面, 再看内容, 打印的对象是 NSLog 还是 myNslog, 通过上面发现是 myNslog, 说明 Hook 成功.
通过上面可看出, fishhook 能够 Hook c 函数, 是因为 Mach-O 文件特点, PIC 位置代码独立造就了静态语言 C 也有动态的部分, 之后通过 Dyld 进行动态绑定的时机, 在这其中我们就可以做手脚, 替换自定义的方法.
fishhook 是根据方法字符串的名字 "NSLog", 它是怎么找到的呢? 下面将讲解利用符号表查看函数名称字符串.
四, 符号表查看函数名称
再次查看 Mach-O 文件, 查看懒加载表中的 NSLog 函数
懒加载表是和动态符号表是一一对应关系, 通过上面发现 NSLog 函数时第一个, 而对应的 Dynamic Symbol table 也是第一个, 打开 Dynamic Symbol table
查看 Dynamic Symbol Table 第一个也是 NSLog, 查看 Data 值为 7A, 对应的十进制为 122, 然后到 Symbols Table 里面查看 122, 如下:
查看 Symbols Table 的 data 值为 0000009B, 然后在 String Table Index 去看函数偏移值为 0000009B 的内容, 如下:
为什么选择 00004F94 查看 NSLog 呢, 我们从上面得知 Symbols Table 的 data 值为 0000009B, 然后加上 String Table 的函数第一个地址为 00004F04, 然后将 0000009B + 00004F04 = 0X4F9F, 最后看 00004F94 里面包含了 0X4F9F, 蓝色内容看出是 NSLog 内容, 也就是找到啦. 完美!!!
以上过程可以在 fishhook 中 GitHub 上有说明图:
上面的说明图也就是通过符号表查看函数名称以及反过来也可以逆查的过程. 配上说明图, 方便大家熟悉流程.
五, 总结
上面讲述了 Hook 的几种技术方式以及 fishhook 的原理探究, 以及如何让别人的 App 实现自己的代码. 下面我们对此总结一下, 写了一个本篇博客的整个过程便于大家整理, 希望对大家有所帮助加深理解.
来源: https://www.cnblogs.com/guohai-stronger/p/11921916.html