iOS 逆向开发16:HOOK原理上(HOOK 系统C函数)

iOS 逆向开发 文章汇总

目录

  • 一、 HOOK概述
  • 二、fishHook的简单使用
  • 三、fishHook原理探究
  • 四、NSLog间接符号绑定的流程
  • 总结
  • 参考


一、 HOOK概述

HOOK,中文译为“挂钩”或“钩子”。在iOS逆向中是指改变程序运行流程的一种技术。通过hook可以让别人的程序执行自己所写的代码。在逆向中经常使用这种技术。所以在学习过程中,我们重点要了解其原理,这样能够对恶意代码进行有效的防护。

HOOK示意图

iOS中HOOK技术的几种方式

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

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

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

Method Swizzle
Cydia Substrate-MobileHooker
Cydia Substrate-MobileLoader


二、fishHook的简单使用

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

获取代码:git clone https:github.com/facebook/fishhook.git

关键函数:

// 用来重 新绑定符号表的函数。使用它来交换
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
参数一  存放rebinding结构体的数组(可以同时交换多个函数)
参数二  rebindings数组的长度
struct rebinding {
  const char *name;  // 需要HOOK的函数名称,字符串
  void *replacement; // 替换到哪个新的函数上(函数指针,也就是函数名称)
  void **replaced;   // 保存原始函数指针变量的指针(它是一个二级指针)
};

2.1 HOOK NSLog

#import "ViewController.h"
#import "fishhook.h"

- (void)viewDidLoad {
    [super viewDidLoad];
//------------HOOK NSLog------------

    //创建rebinding 结构体
    struct rebinding nslog;
    nslog.name = "NSLog";
    nslog.replacement = my_NSLog;
    //保存NSLog系统函数地址的指针!
    nslog.replaced = (void *)&sys_nslog;
    
    //需求:HOOK NSLog
    struct rebinding bds[] = {nslog};
    
    rebind_symbols(bds, 1);
}

//函数指针!
static void (*sys_nslog)(NSString *format, ...);

//新函数!
void my_NSLog(NSString *format, ...) {
    format = [format stringByAppendingString:@"\n我HOOK到了!"];
    //走到系统的NSLog里面去!
    sys_nslog(format);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"hello");
}

点击屏幕输出:hello
          我HOOK到了!

2.2 HOOK Func

#import "ViewController.h"
#import "fishhook.h"

void func(const char * str) {
    NSLog(@"%s",str);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //----------HOOK  Func -------
    //创建rebinding 结构体
    struct rebinding func;
    func.name = "func";
    func.replacement = my_func;
    //保存NSLog系统函数地址的指针!
    func.replaced = (void *)&func_p;
    
    //需求:HOOK NSLog
    struct rebinding bds[] = {func};
    
    rebind_symbols(bds, 1);
}

static void (*func_p)(const char * str);

void my_func(const char * str) {
    NSLog(@"HOOK了!!");
    func_p(str);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    func("hello");
}

点击屏幕输出:hello

从2.1和2.2可以看到fishHook只能HOOK系统的函数不能HOOK自定义的函数

因为NSLog不是本Mach-O文件中的,func是本Mach-O文件中的。编译的时候,并不知道NSLog 的真实地址!因此可以HOOK NSLog函数。


三、fishHook原理探究

How it works

dyld binds lazy and non-lazy symbols by updating pointers in particular sections of the __DATAsegment of a Mach-O binary. fishhook re-binds these symbols by determining the locations to update for each of the symbol names passed to rebind_symbols and then writing out the corresponding replacements.
For a given image, the __DATA segment may contain two sections that are relevant for dynamic symbol bindings: __nl_symbol_ptr and __la_symbol_ptr. __nl_symbol_ptr is an array of pointers to non-lazily bound data (these are bound at the time a library is loaded) and __la_symbol_ptr is an array of pointers to imported functions that is generally filled by a routine called dyld_stub_binder during the first call to that symbol (it's also possible to tell dyld to bind these at launch). In order to find the name of the symbol that corresponds to a particular location in one of these sections, we have to jump through several layers of indirection. For the two relevant sections, the section headers (struct sections from ) provide an offset (in the reserved1 field) into what is known as the indirect symbol table. The indirect symbol table, which is located in the __LINKEDIT segment of the binary, is just an array of indexes into the symbol table (also in __LINKEDIT) whose order is identical to that of the pointers in the non-lazy and lazy symbol sections. So, given struct section nl_symbol_ptr, the corresponding index in the symbol table of the first address in that section is indirect_symbol_table[nl_symbol_ptr->reserved1]. The symbol table itself is an array of struct nlists (see ), and each nlist contains an index into the string table in __LINKEDIT which where the actual symbol names are stored. So, for each pointer __nl_symbol_ptrand __la_symbol_ptr, we are able to find the corresponding symbol and then the corresponding string to compare against the requested symbol names, and if there is a match, we replace the pointer in the section with the replacement.

3.1设置并进入第一个断点:

编译查看Mach-O文件

拿到ASLR的值:

ASLR + MachO中的 NSLog Data中的值就是当前NSLog的实际地址:
0x0000000100de0000+0x22a4 = 0x100de22a4

通过ASLR + MachO中的 NSLog Offset中的值也能看到NSLog的实际地址:

由于还未进行NSLog的符号绑定,因此此时NSLog的实际地址并不是NSLog真实函数的地址:

3.2进入第二个断点

由于是懒加载符号,所有在调用NSLog时才进行了符号绑定。

3.3进入第三个断点

经过fishhook将NSLog符号重新绑定了

C语言是静态语言,但不是所有的C函数调用都是静态的。外部的C函数调用是动态的。


四、NSLog间接符号绑定的流程

什么是符号表❓
对于函数名、变量名、方法名等编译完都会生成一张符号表,符号分为内部符号外部符号。外部符号(本Mach-O以外的符号)也称为间接符号。

符号也分为本地符号全局符号
本地符号:自己内部使用的
全局符号:外部也可以使用

// 全局符号 -- 暴露给外界使用
void test() {
}

// 本地符号 -- 作用域为本地符号
static void test1() {
    NSLog(@"test1");
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    test1();
}

编译进入可执行文件目录:
objdump --macho -t 文件名

可以看到test1是本地符号,test和main是全局符号

间接符号表


4.1 NSLog调用前进行符号绑定

添加如下代码:

运行查看汇编代码:

0x1047562d0-ASLR(0x0000000104754000)=0x22D0(NSLog的桩

因此调用NSLog时是bl到NSLog的桩(一块代码)。
桩的值为:1F2003D570E9025800021FD6 -- 即如下代码:

因此NSLog的执行流程为:bl-->桩代码(去符号表里面的绑定地址执行)-->符号表



0x0000000104756384(x16)-0x0000000104754000(ALSR)=0x2384

这里可以看到桩执行符号表中初始的地址(此时NSLog的真实地址还未绑定)

汇编继续执行236c的代码(重新运行了一次,ASLR和上面的不一样了):

经过执行dyld_stub_binder,就将NSLog的符号绑定了。

在非懒加载符号表中可以看到dyld_stub_binder的符号位置,非懒加载符号刚开始运行就绑定了(上篇文章可以看到非懒加载符号绑定的时机)。


4.2 NSLog符号绑定后的调用流程

0x0000000102890000(ALSR)+0x8000(offset) = 0x0102898000

已绑定符号

现在桩执行的就是0x0102898000指向的NSLog的真实地址了。

间接符号表的绑定流程:


NSLog间接符号表的绑定流程


总结

  • 符号绑定的过程
    • 外部函数调用是执行桩里面的代码! TEXT, stubs
      • 通过懒加载符号表里面的地址去执行!
    • 懒加载符号表里面默认保存的是寻找binder的代码
      • binder函数在非 懒加载符号表里面(程序运行就绑定好了! )
  • HOOK: 改变程序原有执行流程
    • iOS中的HOOK技术
      • OC方法: MethodSwizzle
      • 系统函数: fishhook
  • fishhook:
    • 重新绑定符号达到HOOK的目的
      • 外部符号,会在懒加载和非懒加载表中保存函数地址
      • fishhook就是找到这两张表,并且修改里面的地址做到HOOK!


参考

符号表Symbol Table
iOS-开发进阶02:链接与Symbol(上)
iOS-开发进阶03:链接与Symbol(下)

你可能感兴趣的:(iOS 逆向开发16:HOOK原理上(HOOK 系统C函数))