HOOK原理及介绍

注入小结

通过之前的学习,我们知道了利用动态库注入的两种方式:

- Framework
- Dylib
  • 注入 App 后,使得 项目和动态库产生关联关系。
  • 修改 Mach-o 文件的 Load Commands 段。

然后开始在注入的动态库中,开发你想实现的逻辑功能!

一、HOOK概述

HOOK(钩子) 其实就是改变程序执行流程的一种技术的统称!

15245516017168.jpg

iOS中HOOK技术的几种方式

1、Method Swizzle

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

2、fishhook

它是Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理,通过修改懒加载和非懒加载两个表的指针达到C函数HOOK的目的。

OC 是动态语言,C 则是静态语言,一经编译,所有的都可以获取到,那 Fishhook 是怎么做到的呢????

3、Cydia Substrate

Cydia Substrate 原名为 Mobile Substrate ,它的主要作用是针对OC方法、C函数以及函数地址进行HOOK操作。当然它并不是仅仅针对iOS而设计的,安卓一样可以用。官方地址:http://www.cydiasubstrate.com/

Cydia Substrate主要由3部分组成:

  • MobileHooker

    MobileHooker顾名思义用于HOOK。它定义一系列的宏和函数,底层调用objc的runtimefishhook来替换系统或者目标应用的函数.
    其中有两个函数:

    • MSHookMessageEx 主要作用于Objective-C方法
     void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result)
    
    
    • MSHookFunction 主要作用于C和C++函数
     void MSHookFunction(voidfunction,void* replacement,void** p_original)
    
    

    Logos语法的%hook 就是对此函数做了一层封装

  • MobileLoader

    MobileLoader用于加载第三方dylib在运行的应用程序中。启动时MobileLoader会根据规则把指定目录的第三方的动态库加载进去,第三方的动态库也就是我们写的破解程序.

  • safe mode

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

fishhook 的简单使用

Github 有相关的介绍,使用方法等;

https://github.com/facebook/fishhook

  • fishhook Demo 测试 1:系统方法交换

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // rebinding 结构体的定义
    //    struct rebinding {
    //        const char *name; // 需要 HOOK 的函数名称,字符串
    //        void *replacement; // 替换的新函数(函数指针,也就是函数名称)
    //        void **replaced; //  保存原始函数指针变量/地址的指针(它是一个二级指针!)
    //    };
    // C 语言传参是值/址传递的,把它的值/址穿过去,就可以在函数内部修改函数指针变量的值

    // 定义 rebinding 结构体
    struct rebinding nslogRebind;
    // 函数名称
    nslogRebind.name = "NSLog";
    // 新的函数指针
    nslogRebind.replacement = myLog;
    // 保存原始函数地址的变量的指针
    nslogRebind.replaced = (void *)&old_Nslog;// fishhook 将 old_Nslog 这个指针指向了 NSLog 这个函数,怎么做的呢???

    // 定义数组
    struct rebinding rebs[] = {nslogRebind};
    
    /*
     1. rebindings : 存放 rebinding 结构体的数组,可以交换 N 种方法
     2. size_t rebindings_nel : 数组的长度???
     */
    rebind_symbols(rebs, 1);
}

// 函数指针,用来保存原始的函数地址 (C 语言语法,函数指针类型变量)
static void (*old_Nslog)(NSString *format, ...);

// 自定义的 Log
void myLog (NSString *format, ...){
    format = [format stringByAppendingString:@"——> 勾上了!!!💖💖💖💖💖💖💖💖"];
    old_Nslog(format);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"点击了屏幕");
    
}

点击屏幕,可以看到控制台日志:

image.png
  • fishhook Demo 测试2:自定义方法交换 :
- (void)viewDidLoad {
    [super viewDidLoad];
    // 交换简写!
    // (struct rebinding [1]) 数组类型
    // {{"func",newFunc,(void *)&funcP}} 值为结构体,三个参数
    rebind_symbols((struct rebinding [1]){{"func",newFunc,(void *)&funcP}}, 1);
}

// 原始函数指针
static void (*funcP)(const char *);
// 新函数
void newFunc(const char *str){
    NSLog(@"———> 勾住了!!!💖💖💖💖💖💖");
    funcP(str);
}

// 原始函数
void func(const char * str){
    NSLog(@"%s",str);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    func("Hello world!!!");
}
image.png

首先代码是没有问题的,为什么呢?自定义方法交换不了呢???

fishhook 原理探究

  1. Mach-O 文件是怎么加载的?
    DYLD 工具动态加载,加载完 Mach-O 文件后,加载依赖的动态库,通过 image list 可以看到加载的相关类库。

  2. ASLR 地址空间随机布局,Mach-O 文件加载的时候是随机地址的。
    系统类库,每次启动,所有地址也是随机布局。

  3. PIC 位置代码独立

???

控制器(Programmable Interrupt Controller)”的简称。

???

可编程中断控制器(Programmable Interrupt Controller),也简称为PIC,是微处器与外设之间的中断处理的桥梁,由外设发出的中断请求需要中断控制器来进行处理。

如果 Mach-o 文件内部需要调用系统的函数时:

  • 现在 Mach-o _data 段 建立一个指针(就是符号,实现指向内部的函数调用,指向外部的函数地址),指向外部函数,(dyld 加载)
    可读可写,当 Mach-o 被加载进去的时候,它就会指向所指的函数。

  • DYLD会进行动态的绑定,将 Mach-o 文件Data 段中的指针指向外部的函数!!!所以 DYLD 叫动态加载。
    故 fishhook 中 的绑定方法叫做:rebind_symbols 重新绑定符号,就是绑定程序内部的函数。

这也就是为什么内部/自定义的函数修改不了,只能修改 Mach-O 文件外部的函数,如果是另一个动态库的或者是需要动态符号绑定的就可以,在符号表中能找到的才可以。

接下来,验证一下:

还是用 fishhook Demo 测试 1 来测试,运行,查看 Mach-o 符号表:

image.png
NSLog 函数

offset :8018, NSLog文件偏移地址,也就是说懒加载这个表也就在 Mach-O 文件偏移的地址 + 函数偏移地址

动态调试:

断点位置
Mach-O 在内存中的偏移地址

Mach-O 在内存中的偏移地址也就是 Mach-O 的真实地址。

Mach-o 文件Data 段中的函数指针

计算符号表的地址

通过符号表找方法的地址,通过 dis -s 反汇编命令查看函数详情。

接下来,过段点继续查看:

前后比较

通过上面可以看出,fishhook 之所以能够 Hook C 函数,是因为根据 Mach-O 文件 特点,PIC 位置代码独立 也就造就了所谓的静态语言 C 也有动态的部分,通过 DYLD 进行动态绑定的时候,我们做了手脚,替换为我们自定义的方法。

备注: 值得注意的是,调用之前先调用一下 NSLog 函数,(符号表中应该没有 NSLog 函数的地址,但其实是有,但是可能是不正确的/固定的地址??)否则则调用 rebind_symbols 方法前不能正确的获取 函数的符号表地址(fishHook 内部会调用吧??)

  • 懒加载表中 NSLog 函数什么时候给他赋值呢??

现在是存放在 Mach-o 文件偏移 8018 的位置 ,而它存放的一个 8 个字节的指针,保存的就是 动态缓存区 中保存 NSLog 函数的真实的地址,那这个地址是什么时候保存进去的呢???
并不是 Mach-O 文件加载的时候保存的,而是第一次调用 该函数时保存进去的,绑定一下,由 DYLD 绑定 NSLog 这个符号指向真实的 NSLog 的地址。

未调用方法的符号表
image.png

那 fishhook 根据方法字符串名字 "NSLog" 也就是 struct rebinding const char *name 是怎么找到的呢 ???

通过符号表查看函数名称字符串

再次查看 Mach-o 文件,上面查看的懒加载表中的 NSLog 函数:

懒加载表和动态符号表

懒加载表和动态符号表是一一对应 的关系,如下:

动态符号表

动态符号表中的 Data 其实就是 方法的 Symbol Table 中的下标,转换为 10 进制 (0x88 = 136),到 Symbol Table 中查看:

Symbol Table

_ 和 .
_ 是函数的开始,. 是分隔符,\0 的转义字符。

这里还不是最终的 NSLog 字符串,可以看到 _NSLog 在 String Table 中的 Index 偏移为 0x000000AC。接下来继续查看 String Table

NSLog 字符串存放的位置

String Table 首地址0xD0A4 + 偏移地址 0xAc = 0xD150

以上过程也就是 GitHub fishhook 的说明图,如下:

查找过程

小结:

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容