本篇文章较与依赖前一篇 Mach-O文件 的先导知识 , 建议先阅读后再探究 .git
因为逆向过程当中代码注入每每会使用
hook
这种方式 , 并且在安全防御与监测方面常用 .github另外只知道
runtime
交换imp
的方式对于中高级开发人员 ( 想偷懒又想装* ) 显然是不太够的 .数组那么咱们今天就来好好探讨一下
Hook
与fishHook
原理 .缓存
HOOK,中文译为 “挂钩“
或 “钩子”
。在 iOS 逆向中是指改变程序运行流程的一种技术。经过 hook
可让别人的程序执行本身所写的代码。在逆向中常用这种技术。因此在学习过程当中,咱们重点要了解其原理,这样可以对恶意代码进行有效的防御。安全
大名鼎鼎的 Hook 已经不知道被多少人玩出了花 ❀ , 其用途之多咱们就不说了 . bash
利用 OC 的 Runtime
特性,动态改变 SEL
(方法编号)和 IMP
(方法实现)的对应关系,达到 OC 方法调用流程改变的目的。主要用于 OC 方法。ide
经常使用的有函数
method_exchangeImplementations
交换函数 impclass_replaceMethod
替换方法method_getImplementation
与 method_setImplementation
直接 get
/ set
imp关于这些
Runtime
方法的基本使用以及原理 这一点在 重签应用调试与代码修改 这篇文章最后有详细的解释和demo
, 感兴趣的能够去阅读一下 .工具
它是 Facebook
提供的一个动态修改连接 Mach-O
文件的工具。利用 MachO
文件加载原理,经过修改懒加载和非懒加载两个表的指针达到 C 函数 Hook
的目的。post
Cydia Substrate
原名为 Mobile Substrate
,它的主要做用是针对 OC 方法、C 函数以及函数地址进行 Hook
操做。固然它并非仅仅针对 iOS 而设计的,安卓同样能够用。官方地址:www.cydiasubstrate.com/
它使用的是
logos
语法 , 关于这个工具的使用 , 后续文章我会详细讲述 .
git - 地址 : fishhook - git
有须要的能够下载这个有中文注释的版本 link 提取码:f4f8 .
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//rebinding结构体
struct rebinding nslog;
//须要HOOK的函数名称,C字符串
nslog.name = "NSLog";
//新函数的地址
nslog.replacement = myNslog;
//原始函数地址的指针!
nslog.replaced = (void *)&sys_nslog;
//rebinding结构体数组
struct rebinding rebs[1] = {nslog};
/** * 参数1 : 存放rebinding结构体的数组 * 参数2 : 数组的长度 */
rebind_symbols(rebs, 1);
}
//---------------------------------更改NSLog-----------
//函数指针
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(@"点击了屏幕!!");
}
@end
复制代码
点击屏幕 , 打印结果 :
001--fishHookDemo[15776:645816] 点击了屏幕!!勾上了!
复制代码
rebind_symbols
, 源码以下 :
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
//prepend_rebindings的函数会将整个 rebindings 数组添加到 _rebindings_head 这个链表的头部
//Fishhook采用链表的方式来存储每一次调用rebind_symbols传入的参数,每次调用,就会在链表的头部插入一个节点,链表的头部是:_rebindings_head
int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
//根据上面的prepend_rebinding来作判断,若是小于0的话,直接返回一个错误码回去
if (retval < 0) {
return retval;
}
//根据_rebindings_head->next是否为空判断是否是第一次调用。
if (!_rebindings_head->next) {
//第一次调用的话,调用_dyld_register_func_for_add_image注册监听方法.
//已经被dyld加载的image会马上进入回调。
//以后的image会在dyld装载的时候触发回调。
_dyld_register_func_for_add_image(_rebind_symbols_for_image);
} else {
//遍历已经加载的image,进行hook
uint32_t c = _dyld_image_count();
for (uint32_t i = 0; i < c; i++) {
_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
}
}
return retval;
}
复制代码
fishhook
的基础使用很是简单.
fishhook
能够帮咱们保存原系统函数的实现地址 , 另外将须要替换的函数名称
和自定义函数地址
写成结构体调用 rebind_symbols
就能够了 ,hook
.基础 OC
函数的 hook
原理咱们不在多赘述了 , 其实简单来讲就是替换掉方法实现的 imp
, 这个基于 OC
语言的动态运行时机制是很好理解的 .
可是 C
呢 ?
咱们知道
C
函数是静态的,也就是说在编译的时候,编译器就知道了它的实现地址,这也是为何C
函数只写函数声明调用时会报错。那么为何fishhook
还可以改变C
函数的调用呢?难道函数也有动态的特性存在?咱们一块儿来探究它的原理
fishhook
是能够 hook
系统的函数 , 并不是全部的 C
函数 , 也就是说 fishhook
也只能对带有符号表的系统函数进行重绑定 , 而对本身实现的 C 函数一样是没有办法的.
咱们大能够本身写一个 C
函数实验一下 .
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// hook 自定义C函数
struct rebinding Cfunction;
Cfunction.name = "func";
Cfunction.replacement = newfunc;
Cfunction.replaced = (void *)&funcOri;
struct rebinding resbs[1] = {Cfunction};
rebind_symbols(resbs, 1);
}
// 要hook 的c函数
void func(const char * str){
NSLog(@"%s",str);
}
//原始的函数指针记录
static void(*funcOri)(const char *);
void newfunc(const char * str){
NSLog(@"勾住了!");
funcOri(str);
}
@end
复制代码
运行 , 打印结果
2019-11-12 14:54:19.001680+0800 fishhookDemo[35238:1563336] 点击了屏幕
2019-11-12 14:54:19.706819+0800 fishhookDemo[35238:1563336] 点击了屏幕
2019-11-12 14:54:19.861428+0800 fishhookDemo[35238:1563336] 点击了屏幕
复制代码
没法 hook
自定义 C
函数 , 接下来咱们经过 fishhook
原理来解析一下缘由 .
首先 :
C | OC |
---|---|
静态 | 动态 |
编译时肯定函数地址 | 运行时肯定函数地址 |
而系统的 C
函数有动态的部分 , 就是咱们常常提到的符号表 , 用到的技术叫作 Position Independent Code ( 位置代码独立 ) , fishhook
也就是在此处作了文章 .
fishhook
原文讲述 :因为 iOS
系统中 UIKit
/ Foundation
库每一个应用都会经过 dyld
加载到内存中 , 所以 , 为了节约空间 , 苹果将这些系统库放在了一个地方 : 动态库共享缓存区 (dyld shared cache) . ( Mac OS 同样有 ) .
所以 , 相似 NSLog
的函数实现地址 , 并不会也不可能会在咱们本身的工程的 Mach-O
中 , 那么咱们的工程想要调用 NSLog
方法 , 如何能找到其真实的实现地址呢 ?
其流程以下 :
在工程编译时 , 所产生的 Mach-O
可执行文件中会预留出一段空间 , 这个空间其实就是符号表 , 存放在 _DATA
数据段中 ( 由于 _DATA
段在运行时是可读可写的 )
编译时 : 工程中全部引用了共享缓存区中的系统库方法 , 其指向的地址设置成符号地址 , ( 例如工程中有一个 NSLog
, 那么编译时就会在 Mach-O
中建立一个 NSLog
的符号 , 工程中的 NSLog
就指向这个符号 )
运行时 : 当 dyld
将应用加载到内存中时 , 根据 load commands
中列出的须要加载哪些库文件 , 去作绑定的操做 ( 以 NSLog
为例 , dyld
就会去找到 Foundation
中 NSLog
的真实地址写到 _DATA
段的符号表中 NSLog
的符号上面 )
这个过程被称为 PIC
技术 . ( Position Independent Code : 位置代码独立 )
那么了解了系统函数的整个加载过程 , 咱们再来看 fishhook
的函数名称 :
rebind_symbols :: 重绑定符号
也就简单明了了.
其原理就是 :
将编译后系统库函数所指向的符号 , 在运行时重绑定到用户指定的函数地址 , 而后将原系统函数的真实地址赋值到用户指定的指针上.
那么再回头看自定义的C函数为何 hook
不了 ?
那答案就很简单了 :
C
函数实际地址就在本身的 Mach-O
内 , 也并无符号和绑定的过程 .利用 MachOView
直接查看.
有同窗说了 , 说是这么说 , 怎么验证呢 ?
既然讲到了这儿 , 那咱们就顺道提一点符号表的知识 , 毕竟一些 bug 收集工具也常常用到符号表还原 , 顺便咱们来实际操做一下 , 一边验证理论 , 一边加深记忆 .
从 MachOView
中咱们看到 , 符号表分为两种
Lazy Symbol Pointers
Non-Lazy Symbol Pointers
就是字面意思 , 懒加载和非懒加载 .
所以咱们在使用 fishhook
的时候 , 最好是调用一下原函数 , 以防止可能会出现没使用并无绑定的问题 .
那么接下来 , 咱们来玩一下 ?
废话很少说 , 来到咱们刚刚写的 hook
了 NSLog
的 demo
, 在 viewdidload
中先加一句 NSLog(@"123")
;
开始玩
cmd + r
运行运行工程来到断点 , 找到 Mach-O
, 使用 MachOView
查看.
首先咱们看到 这个符号基于 Mach-O
文件的首地址偏移量是 3028
, ( 每一个人的都不同 , 你用你本身的 ) .
那么 Mach-O
的地址在哪呢 ?
来到工程 LLDB
输入指令 : image list
第一个就是咱们工程的 Mach-O
实际内存地址
打开计算器 cmd + 3
, 选择十六进制 , cmd + v
把 Mach-O
实际内存地址粘贴进去 , 加上 MachOView
的符号偏移地址 3028
.
cmd
+ c
拷贝计算结果.
x
+ 0x1042C8028
( 你的计算结果 )
( memory read
也能够 , x
就是 memory read
的简写 )
注意 : iOS
小端模式 , 从右往左读 .
那么我上图中对应的实际地址就是 0x01042c69c0
.
查看汇编 : dis -s 0x01042c69c0 ( 你本身的地址 )
那么咱们看到里面并无什么内容 , 也就是说在此时这个断点这里 , 符号并无被绑定内容 .
过掉断点 , 来到第二处断点 .
( 有对于汇编不熟悉的同窗不用着急 , 对比一下第二个断点的结果来看 . 另外后续笔者会考虑继续更汇编部份内容 )
x
+ 0x1042C8028
( 你的计算结果 )
能够看到明显内容已经改变了 .
再次查看汇编 : dis -s 0x01042c69c0 ( 你本身的地址 )
大功告成 , 再回想一下咱们以前的原理探究部分. 彻底验证 !
别着急 , 这只是验证了 iOS
的 PIC
部分 , 那么咱们 fishhook
呢 ?
touchesBegan
加个断点 ( 不必定非要在 touchesBegan
加 , 我这里只是 fishhook
的 rebind_symbols
后面就没有代码能够过断点了. )rebind_symbols
) , 点击屏幕 来到下一个断点.结果以下 :
fishhook
重绑定后符号指向了咱们自定义的函数地址 . 彻底验证以前假设 .
fishhook
在逆向中很是重要 , 不少工具也内置了 fishhook
, 所以但愿你们能认真理解并掌握原理 .
感谢关注 , 咱们下期见 .