美文网首页
六 Hock原理分析

六 Hock原理分析

作者: 蚂蚁也疯狂 | 来源:发表于2020-06-16 11:53 被阅读0次
    nx_001.jpeg

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

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

    用到的工具:fishhook

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

    • HOOK概述
    • fishHook的简单使用
    • 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函数进行修改。

    简单总结下就是:

    • 在MachO文件中有个PIC(贡献缓存库),它在Data段创建了一个指针,这个指针指向外部函数。
    • 当我们调用C方法、函数的时候,这个指针就会起加载(绑定)这个外部的C函数。
    • 而我们的fishhook正是利用了这一步骤进行hook的。

    注意:
    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

    相关文章

      网友评论

          本文标题:六 Hock原理分析

          本文链接:https://www.haomeiwen.com/subject/jgdlxktx.html