fishhook 原理

fishhook原理及关键问题解析

1.准备测试环境,使用 hook_nslog 替换 系统NSLog

//
//  ViewController.m
//  fishhook
//
//  Created by LEE on 4/14/22.
//

#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()

@end

@implementation ViewController
//原函数指针变量
static void (*sys_nslog)(NSString *format, ...);
//hook新函数
void hook_nslog(NSString *format, ...) {
    format = [format stringByAppendingString:@"❤️ ( ⚫︎ー⚫︎ ) balalala~"];
    sys_nslog(format);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //提出需求->分解需求->完成需求->测试验收
    //【需求】hook NSLog
    //1. 点击屏幕输出日志;
    //2.fishhook hook NSLog让日志内容发生点变化!!!
    
      //定义hook的函数的结构体变量
    struct rebinding nslog_reb;
    nslog_reb.name = "NSLog";
    nslog_reb.replacement = hook_nslog;
    nslog_reb.replaced = (void *)&sys_nslog;
    //定义需要hook的函数的结构体数组变量
    struct rebinding rebs[] = {nslog_reb};
      //很简单,传递结构体数组地址及其成员变量数目
    rebind_symbols(rebs, 1);
}

//点击屏幕输出日志
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"click!!!");
}




@end


2.构建替换的结构体

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x00000001000c0274 fishhook`prepend_rebindings(rebindings_head=0x00000001000c5560, rebindings=0x000000016fd411c0, nel=1) at fishhook.c:80:22
    frame #1: 0x00000001000c0654 fishhook`rebind_symbols(rebindings=0x000000016fd411c0, rebindings_nel=1) at fishhook.c:249:16
    frame #2: 0x00000001000c0104 fishhook`-[ViewController viewDidLoad](self=0x0000000109e09460, _cmd="viewDidLoad") at ViewController.m:38:5

在这里插入图片描述

3.执行符号地址替换

请添加图片描述

4.几个关键问题

4.1.运行时的可执行文件不是不可以修改吗,为什么这里 使用 vm_protect 可以修改 引用的动态符号?

为什么可以修改动态符号引用?
你的理解有一个误区。可执行文件的代码段确实不能修改,但是 fishhook 修改的不是代码段,而是数据段中的符号指针表。
1. 动态符号绑定的工作原理
在 Mach-O 文件中,动态符号的绑定是通过以下机制实现的:
代码段:包含实际的机器代码,通常是只读的
数据段:包含符号指针表(__la_symbol_ptr 和 __nl_symbol_ptr),这些指针指向实际的函数地址
2. fishhook 修改的是什么?
从代码中可以看到,fishhook 修改的是:


          err = vm_protect (mach_task_self (), (uintptr_t)indirect_symbol_bindings, section->size, 0, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);

这里的 indirect_symbol_bindings 指向的是符号指针表,位于数据段中,而不是代码段。
3. vm_protect 的作用
vm_protect 是 macOS/iOS 的虚拟内存管理 API,它的作用是:
修改内存页面的保护属性:将原本只读的内存页面改为可写
参数说明:
mach_task_self():当前进程
(uintptr_t)indirect_symbol_bindings:要修改的内存地址
section->size:要修改的内存大小
0:设置为新的保护属性
VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY:新的保护属性(读、写、写时复制)
4. 为什么需要 VM_PROT_COPY?
VM_PROT_COPY 是关键,它启用了写时复制(Copy-on-Write)机制:
当进程尝试写入共享的只读内存页面时,系统会创建一个该页面的私有副本
这样既保持了原始页面的完整性,又允许进程修改自己的副本

4.2.VM_PROT_COPY 是关键,它启用了写时复制(Copy-on-Write)机制: 系统会创建一个该页面的私有副本,那么需要动态找到外部符号的时候,是如何知道要去新页面去找的喃?

写时复制机制的工作原理
1. 虚拟内存映射机制
在 macOS/iOS 中,每个进程都有自己的虚拟地址空间,但多个进程可能映射同一个物理页面。当使用 VM_PROT_COPY 时:

进程A的虚拟地址 0x1000 → 物理页面P (只读)
进程B的虚拟地址 0x1000 → 物理页面P (只读)

2. 写时复制的触发
当进程尝试写入共享的只读页面时:

// 这行代码会触发写时复制
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;

系统会:
检测到写操作:发现进程试图写入只读页面
创建私有副本:分配新的物理页面,复制原始内容
更新页表:将进程的虚拟地址重新映射到新的物理页面
设置写权限:新页面具有读写权限

修改前:
进程A的虚拟地址 0x1000 → 物理页面P (只读)

修改后:
进程A的虚拟地址 0x1000 → 物理页面P' (读写,私有副本)
进程B的虚拟地址 0x1000 → 物理页面P (只读,保持不变)

3. 符号查找的连续性
关键在于:虚拟地址保持不变!


// 这行代码获取符号指针表的地址
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);

section->addr 是符号指针表在虚拟地址空间中的固定偏移
slide 是 ASLR (Address Space Layout Randomization) 的随机偏移
最终的虚拟地址 indirect_symbol_bindings 在写时复制前后完全相同

4. 动态符号解析过程
当程序需要调用外部函数时:

; 假设这是调用 printf 的代码
call [rip + offset_to_symbol_ptr]  ; 间接调用

CPU 执行间接调用:从符号指针表读取函数地址
符号指针表地址:indirect_symbol_bindings[i] 指向的虚拟地址
读取函数地址:从该地址读取实际的函数指针
跳转到函数:执行函数调用
5. 写时复制后的效果
写时复制后,符号指针表的内容发生了变化:


// 修改前:指向原始的 printf 函数
indirect_symbol_bindings[i] = 0x7fff12345678;  // 原始 printf 地址

// 修改后:指向我们的 hook 函数
indirect_symbol_bindings[i] = 0x1000123456;    // 我们的 hook 函数地址

6. 为什么不需要"知道新页面"
程序不需要知道页面是否被复制,因为:
虚拟地址不变:indirect_symbol_bindings[i] 的地址始终相同
内存访问透明:CPU 和内存管理单元 (MMU) 自动处理物理地址转换
页表自动更新:操作系统自动维护虚拟地址到物理地址的映射
7. 具体的内存访问流程

程序执行:call [0x1000]  (间接调用)

1. CPU 访问虚拟地址 0x1000
2. MMU 查询页表,发现 0x1000 → 物理页面P'
3. 从物理页面P' 读取数据:0x1000123456 (hook函数地址)
4. CPU 跳转到 0x1000123456 执行

总结
写时复制机制的美妙之处在于:
对程序透明:程序代码不需要任何修改
地址不变:符号指针表的虚拟地址保持不变
内容改变:符号指针表的内容被替换为 hook 函数地址
自动处理:操作系统自动处理物理页面的复制和映射
这就是为什么 fishhook 能够在不修改代码段的情况下,成功 hook 动态符号调用的原理!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值