六 Hock原理分析

2020-06-16  本文已影响0人  蚂蚁也疯狂
nx_001.jpeg

上篇文章MachO文件解析已经详细介绍了MachO,并且由MachO引出了dyld,再由dyld讲述了App的启动流程,而在App的启动流程中又说到了一些关键的名称如:LC_LOAD_DYLINKER、LC_LOAD_DYLIB以及objc的回调函数_dyld_objc_notify_register等等。

这篇文章我们来了解一下,符号表、fishhook相关的内容。

用到的工具:fishhook

接下来本文会从以下几点进行阐述:

1.HOOK概述

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

1.1 Hook 流程图

六 Hock流程图.png

如上图,这就是我们HOOK代码大概流程。

1.2 HOOK的方式

1.****Method Swizzld
利用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/

1.3 Cydia Substrate简介

Cydia Substrate的原名为MobileHooker,顾名思义用于HOOK。它定义一系列的宏和函数,底层调用objcruntimefishhook来替换系统或者目标应用的函数.

其中有两个函数:
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 就是对此函数做了一层封装。

1.4 Cydia Substrate特点

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

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

2.fishHook的简单使用

fishHook代码地址:fishhook

struct rebinding {
  const char *name;//需要HOOK的函数名称,C字符串
  void *replacement;//新函数的地址
  void **replaced;//原始函数地址的指针!
};

关键函数:

FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], 
                   size_t rebindings_nel);
参数一:存放rebinding结构体的数组(可以同时交换多个函数)
参数二:rebindings数组的长度     

FISHHOOK_VISIBILITY
int rebind_symbols_image(void *header,
                         intptr_t slide,
                         struct rebinding rebindings[],
                         size_t rebindings_nel);

hook系统的NSLog函数fishhook简单使用代码
运行代码,点击屏幕,会发现我们的代码执行了,

hook C 函数fishhook 系统C函数
运行代码,我们会发现,我们的hook方法失效了,这是为什么么呢?下面我们来一起探究下吧。。。。

3.fishHook原理探究

在上篇文章已经提到了在dyld启动app的第二个步骤就是加载共享缓存库,共享缓存库包括Foundation框架,NSLog是被包含在Foundation框架的。有了这个贡献缓存库,就可以解决多个app共同调用一个库等问题了。

六 Hock流程图.png

3.1 fishHook 加载流程

首先,我们想一下OC为什么可以hook呢?C为什么不能hook?

OC为什么可以hook? 因为OC底层使用的Runtime技术,通过运行时去找到方法的实现,所以OC可以hook。

C为什么不能hook?
C语言函数通常是静态的,编译之后,从汇编代码变成了内存地址。它都是通过内存地址去找的,在编译的时候就绑定了的,所以hook不了。

那么问题来了,为什么问题来了,为什么fishhook可以hook到C方法呢?
原因是:iOS系统实现了一个动态缓存库技术,一些公共的系统库放进内存中的某个地方,当某个iOS项目启动后,machO文件会在Data段创建一个指针,dyld动态将machO中Data段中这个指针指向外部函数,这里的指针指向内部函数的调用,指向外部函数的地址,而这个指针也就是我们通常说的符号;这也是为什么fishhook中函数名为rebind_symbols(重新绑定符号),实际上是修改这个指针指向外部函数的地址,这也就是为什么修改不了内部函数和自定义函数,只能修改machO外部函数(在符号表中能找到的函数)。由于苹果实现了ASLR技术(不了解ASLR,看这篇逆向学习笔记8——ASLR),所以这些动态缓存库函数在APP项目的内存地址不确定,每次启动APP的时候都会有相应的变化,这是C语言的动态表现。

通过MacOView分析,我们知道了iOS应用启动时的启动流程:

  1. 启动APP会执行dyld,加载程序
  2. 进入dyld:main函数
  3. 配置一些环境
  4. 加载共享缓存库
  5. 实例化主程序
  6. 加载动态库
  7. 链接主程序

通过以上分析,我们可以利用dyld加载的时候对C函数进行修改。

简单总结下就是:

注意:
fishhook 修改的是内存的地址

下面,我们使用MachOView来看一下吧
这里我们代码写的少,所以就直接用 2fishhook hook系统C函数这个案例来讲解了。

首先,我们来看看NSLog的地址在什么时候被加载的,也就是NSLog到底在哪里。

1.使用MachOView查看,并使用验证

// 在viewDidLoad中添加以下两句代码,断点断到第一句代码
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"测试1");
    NSLog(@"测试1");
}

1.在第一个NSLog使用断点断住,在Project中,得到当前APP的 MachO文件。使用MachOView进行分析。我们可以找到如下内容。

六 加载流程1.png

懒加载表 非懒加载表

2.获得当前NSLog的偏移量。

3.在LLDB中使用 image list列出当前加载的镜像。第一个地址为当前MachO文件在内存中的首地址

4.通过首地址加上当前的偏移量,我们可以获得如下NSLog在内存中的位置。


六 NSLog共享缓存1.png

5.由于iOS是小端模式,我们的地址从右往左读,如上图第二个lldb的内容,最后我们会看到libdyld.dylib`dyld_stub_binder:。上图中有体现。

6.我们的断点在往下走一步,会发现我们获取MachO文件的首地址内存发生了变化。上图中有体现。

7.这时我们查看一下当前的地址,看看有什么? 六 NSLog共享缓存2.png

,如图,我们的NSLog被打印出来了。

到这里,说明了我们的NSLog在我们的系统共享缓存区里,第一次没使用的时候需要绑定,第二次直接去找地址,不用绑定。

2.使用汇编的方式查看
1.同上面的第1,2,3步,通过Debug -> Debug WorkFlow -> Always Show Disassembly 调试,如下图:

六 汇编分析NSLog.png

我们同样可以得到 libdyld.dylib`dyld_stub_binder:函数

4.fishHook源码分析

4.1、fishhook的总体思路

Facebook的开源库fishhook就可以完美的实现这个任务。
先上一张官网原理图:

fishhook流程.png

总体来说,步骤是这样的:

  1. 先找到四张表Lazy Symbol Pointer Table、Indirect Symbol Table、Symbol Table、String Table。
  2. MachO有个规律:Lazy Symbol Pointer Table中第index行代表的函数和Indirect Symbol Table中第index行代表的函数是一样的。
  3. Indirect Symbol Table中value值表示Symbol Table的index。
  4. 找到Symbol Table的中对应index的对象,其data代表String Table的偏移值。
  5. 用String Table的基值,也就是第一行的pFile值,加上Symbol Table的中取到的偏移值,就能得到Indirect Symbol Table中value(这个value代表函数的偏移值)代表的函数名了。

4.2、源码分析

fishhook的源码总共只有250行左右,所以结合MachO慢慢看,其实一点也不费劲,接下来,我们简单的分析一下吧。

1.fishhook为维护一个链表,用来储存需要hook的所有函数。

// 给需要rebinding的方法结构体开辟出对应的空间
// 生成对应的链表结构(rebindings_entry),并将新的entry插入头部
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
                              struct rebinding rebindings[],
                              size_t nel)

2.根据linkedit的基值,找到对应的三张表:symbol_table、string_table和indirect_symtab。

// 找到linkedit的头地址
// linkedit_base其实就是MachO的头地址!!!可以通过查看linkedit_base值和image list命令查看验证!!!(文末附有验证图)
/**********************************************************
 Linkedit虚拟地址 = PAGEZERO(64位下1G) + FileOffset
 MachO地址 = PAGEZERO + ASLR
 上面两个公式是已知的 得到下面这个公式
 MachO文件地址 = Linkedit虚拟地址 - FileOffset + ASLR(slide)
**********************************************************/
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
// 获取symbol_table的真实地址
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
// 获取string_table的真实地址
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
// Get indirect symbol table (array of uint32_t indices into symbol table)
// 获取indirect_symtab的真实地址
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);

3.最核心的一个步骤,查找并且替换目标函数。

// 在四张表(section,symtab,strtab,indirect_symtab)中循环查找
// 直到找到对应的rebindings->name,将原先的函数复制给新的地址,将新的函数地址赋值给原先的函数
static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
                                           section_t *section,
                                           intptr_t slide,
                                           nlist_t *symtab,
                                           char *strtab,
                                           uint32_t *indirect_symtab)

在了解了fishhook的简单使用之后,我们就可以做一些基本的防护的内容了,参见代码

关于几个名词的解释。

几个名词(pFile 、offset 、File Offset)的解释

  1. 首先,这三个都是表示相对于MachO的内存偏移,只不过其含义被细分了。
  2. pFile 和 offset含义相近,不过offset更详细,能够对应上具体某一个符号(DATA? TEXT?)。比如文件里面有许多类,类里面有许多的属性,pFile就代表各个类的偏移值,offset代表各个属性的偏移值。
  3. File Offset 这个存在于Segment的字段中。用于从Segment快速找到其代表的「表」真正的偏移值。

参考文章:
作者:一缕清风扬万里
原文地址:https://www.jianshu.com/p/95896fb96a03

上一篇下一篇

猜你喜欢

热点阅读