iOS 逆向 - Hook / fishHook 原理与符号表

前言 :

本篇文章较与依赖前一篇 Mach-O文件 的先导知识 , 建议先阅读后再探究 .git

  • 因为逆向过程当中代码注入每每会使用 hook 这种方式 , 并且在安全防御与监测方面常用 .github

  • 另外只知道 runtime 交换 imp 的方式对于中高级开发人员 ( 想偷懒又想装* ) 显然是不太够的 .数组

那么咱们今天就来好好探讨一下 HookfishHook 原理 .缓存

Hook 概述

HOOK,中文译为 “挂钩““钩子” 。在 iOS 逆向中是指改变程序运行流程的一种技术。经过 hook 可让别人的程序执行本身所写的代码。在逆向中常用这种技术。因此在学习过程当中,咱们重点要了解其原理,这样可以对恶意代码进行有效的防御。安全

大名鼎鼎的 Hook 已经不知道被多少人玩出了花 ❀ , 其用途之多咱们就不说了 . bash

iOS 中几种常见的 Hook

1 . Method Swizzle

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

经常使用的有函数

  • method_exchangeImplementations 交换函数 imp
  • class_replaceMethod 替换方法
  • method_getImplementationmethod_setImplementation 直接 get / set imp

关于这些 Runtime 方法的基本使用以及原理 这一点在 重签应用调试与代码修改 这篇文章最后有详细的解释和 demo , 感兴趣的能够去阅读一下 .工具

2 . fishhook

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

3. Cydia Substrate

Cydia Substrate 原名为 Mobile Substrate ,它的主要做用是针对 OC 方法、C 函数以及函数地址进行 Hook 操做。固然它并非仅仅针对 iOS 而设计的,安卓同样能够用。官方地址:www.cydiasubstrate.com/

它使用的是 logos 语法 , 关于这个工具的使用 , 后续文章我会详细讲述 .

fishhook 基本使用

下载

git - 地址 : fishhook - git

有须要的能够下载这个有中文注释的版本 link 提取码:f4f8 .

demo

#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.

fishhook 分析

基础 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 原理来解析一下缘由 .

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 就会去找到 FoundationNSLog 的真实地址写到 _DATA 段的符号表中 NSLog 的符号上面 )

这个过程被称为 PIC 技术 . ( Position Independent Code : 位置代码独立 )

那么了解了系统函数的整个加载过程 , 咱们再来看 fishhook 的函数名称 :

rebind_symbols :: 重绑定符号 也就简单明了了.

其原理就是 :

将编译后系统库函数所指向的符号 , 在运行时重绑定到用户指定的函数地址 , 而后将原系统函数的真实地址赋值到用户指定的指针上.

那么再回头看自定义的C函数为何 hook 不了 ?

那答案就很简单了 :

  • 自定义 C 函数实际地址就在本身的 Mach-O 内 , 也并无符号和绑定的过程 .
  • 编译时就已经肯定了 , 并无办法操做 .

Mach-O 中查看符号表

利用 MachOView 直接查看.

有同窗说了 , 说是这么说 , 怎么验证呢 ?

既然讲到了这儿 , 那咱们就顺道提一点符号表的知识 , 毕竟一些 bug 收集工具也常常用到符号表还原 , 顺便咱们来实际操做一下 , 一边验证理论 , 一边加深记忆 .

符号表与实际操做验证理论

MachOView 中咱们看到 , 符号表分为两种

  • Lazy Symbol Pointers
  • Non-Lazy Symbol Pointers

就是字面意思 , 懒加载和非懒加载 .

所以咱们在使用 fishhook 的时候 , 最好是调用一下原函数 , 以防止可能会出现没使用并无绑定的问题 .

那么接下来 , 咱们来玩一下 ?

废话很少说 , 来到咱们刚刚写的 hookNSLogdemo , 在 viewdidload 中先加一句 NSLog(@"123");

开始玩

1 准备好代码和断点

2 MachOView 查看

cmd + r 运行运行工程来到断点 , 找到 Mach-O , 使用 MachOView 查看.

3 计算符号地址

  • 首先咱们看到 这个符号基于 Mach-O 文件的首地址偏移量是 3028 , ( 每一个人的都不同 , 你用你本身的 ) .

    那么 Mach-O 的地址在哪呢 ?

    来到工程 LLDB 输入指令 : image list

    第一个就是咱们工程的 Mach-O 实际内存地址

  • 打开计算器 cmd + 3 , 选择十六进制 , cmd + vMach-O 实际内存地址粘贴进去 , 加上 MachOView 的符号偏移地址 3028.

    cmd + c 拷贝计算结果.

4 lldb 查看内存与汇编代码

x + 0x1042C8028 ( 你的计算结果 )

( memory read 也能够 , x 就是 memory read 的简写 )

5 查看前八个字节的内容

注意 : iOS 小端模式 , 从右往左读 .

那么我上图中对应的实际地址就是 0x01042c69c0 .

查看汇编 : dis -s 0x01042c69c0 ( 你本身的地址 )

那么咱们看到里面并无什么内容 , 也就是说在此时这个断点这里 , 符号并无被绑定内容 .

过掉断点 , 来到第二处断点 .

( 有对于汇编不熟悉的同窗不用着急 , 对比一下第二个断点的结果来看 . 另外后续笔者会考虑继续更汇编部份内容 )

从新查看符号

x + 0x1042C8028 ( 你的计算结果 )

能够看到明显内容已经改变了 .

再次查看汇编 : dis -s 0x01042c69c0 ( 你本身的地址 )

大功告成 , 再回想一下咱们以前的原理探究部分. 彻底验证 !

别着急 , 这只是验证了 iOSPIC 部分 , 那么咱们 fishhook 呢 ?

  • touchesBegan 加个断点 ( 不必定非要在 touchesBegan 加 , 我这里只是 fishhookrebind_symbols 后面就没有代码能够过断点了. )
  • 过掉当前断点 ( rebind_symbols ) , 点击屏幕 来到下一个断点.
  • 再次查看内存和汇编

结果以下 :

fishhook 重绑定后符号指向了咱们自定义的函数地址 . 彻底验证以前假设 .

最后

fishhook 在逆向中很是重要 , 不少工具也内置了 fishhook , 所以但愿你们能认真理解并掌握原理 .

感谢关注 , 咱们下期见 .

相关文章
相关标签/搜索