13、fishhook原理Dobby

news2024/9/29 3:31:52

一、fishhook原理

1.1 fishhook代码分析

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;
}
static void _rebind_symbols_for_image(const struct mach_header *header,
                                      intptr_t slide) {
    rebind_symbols_for_image(_rebindings_head, header, slide);
}
  • 1、Fishhook采用链表的方式来存储每一次调用rebind_symbols传入的参数,每次调用,就会在链表的头部插入一个节点,链表的头部是:_rebindings_head,  prepend_rebindings的函数会将整个 rebindings 数组添加到 _rebindings_head 这个链表的头部
  • 2、根据上面的prepend_rebinding来做判断,如果小于0的话,直接返回一个错误码回去
  • 3、根据_rebindings_head->next是否为空判断是不是第一次调用。
    • 3.1 第一次调用的话,调用_dyld_register_func_for_add_image注册监听方法.
    • 3.2 已经被dyld加载的image会立刻进入回调。
    • 3.3 之后的image会在dyld装载的时候触发回调。
    • 3.4 不是第一次调用,则 遍历已经加载的image,进行的hook
//回调的最终就是这个函数! 三个参数:要交换的数组  、 image的头 、 ASLR的偏移
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
                                     const struct mach_header *header,
                                     intptr_t slide) {
    
    /*dladdr() 可确定指定的address 是否位于构成进程的进址空间的其中一个加载模块(可执行库或共享库)内,如果某个地址位于在其上面映射加载模块的基址和为该加载模块映射的最高虚拟地址之间(包括两端),则认为该地址在加载模块的范围内。如果某个加载模块符合这个条件,则会搜索其动态符号表,以查找与指定的address 最接近的符号。最接近的符号是指其值等于,或最为接近但小于指定的address 的符号。
     */
    /*
     如果指定的address 不在其中一个加载模块的范围内,则返回0 ;且不修改Dl_info 结构的内容。否则,将返回一个非零值,同时设置Dl_info 结构的字段。
     如果在包含address 的加载模块内,找不到其值小于或等于address 的符号,则dli_sname 、dli_saddr 和dli_size字段将设置为0 ; dli_bind 字段设置为STB_LOCAL , dli_type 字段设置为STT_NOTYPE 。
     */
    //这个dladdr函数就是在程序里面找header
  Dl_info info;
  if (dladdr(header, &info) == 0) {
    return;
  }
    //下面就是定义好几个变量,准备从MachO里面去找!
  segment_command_t *cur_seg_cmd;
  segment_command_t *linkedit_segment = NULL;
  struct symtab_command* symtab_cmd = NULL;
  struct dysymtab_command* dysymtab_cmd = NULL;
  //跳过header的大小,找loadCommand
  uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
  for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
    cur_seg_cmd = (segment_command_t *)cur;
    if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
      if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
        linkedit_segment = cur_seg_cmd;
      }
    } else if (cur_seg_cmd->cmd == LC_SYMTAB) {
      symtab_cmd = (struct symtab_command*)cur_seg_cmd;
    } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
      dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
    }
  }
   //如果刚才获取的,有一项为空就直接返回
  if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || !dysymtab_cmd->nindirectsyms) {
    return;
  }

// Find base symbol/string table addresses
//链接时程序的基址 = __LINKEDIT.VM_Address -__LINKEDIT.File_Offset + silde的改变值
  uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
//    printf("地址:%p\n",linkedit_base);
    //符号表的地址 = 基址 + 符号表偏移量
  nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
     //字符串表的地址 = 基址 + 字符串表偏移量
  char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);

  // Get indirect symbol table (array of uint32_t indices into symbol table)
    //动态符号表地址 = 基址 + 动态符号表偏移量
  uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);

  cur = (uintptr_t)header + sizeof(mach_header_t);
  for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
    cur_seg_cmd = (segment_command_t *)cur;
    if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
        //寻找到data段
      if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
          strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
        continue;
      }
        
      for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
        section_t *sect =
          (section_t *)(cur + sizeof(segment_command_t)) + j;
          //找懒加载表
        if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
        }
          //非懒加载表
        if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
        }
      }
    }
  }
}

1.2 Fishhook源码汇总分析

  • rebind_symbols
    • rebindings数组添加到链表
    • 根据链表判断是否第一次调用.这么做的目的时保证注册方法只会调用一次.两种情况都是为了回调_rebind_symbols_for_image
    • 第一次:利用 _dyld_register_func_for_add_image注册监听方法
    • 如果不是第一次,循环遍历已经加装的image. 进行 _rebind_symbols_for_image回调
  • rebind_symbols_for_image
    • 第一步,拿到那三张表再内存中的地址
      • 符号表的地址: symtab
      • 字符串表的地址: strtab
      • 动态(间接)符号表的地址: indirect_symtab
    • 第二步、找懒加载和非懒加载表
    • 第三步、调用perform_rebinding_with_section: 要交换的数组、懒加载和非懒加载表、三个表地址;
  • perform_rebinding_with_section
    • 1、得到indirect_symbol_bindings
    • 2、遍历间接符号表最终找到符号的过程
    • 3、判断是否是需要HOOK的函数
    • 4、保存函数指针,然后替换懒加载符号表里面的函数地址,完成HOOK

二、Dobby框架

  • 内联钩子,所谓InlineHook就是直接修改目标函数的头部代码.让它跳转到我们自定义的函数里面执行我们的代码,从而达到Hook的目的.这种Hook技术一般用在静态语言的HOOK上面.

2.1 编译Dobby

  • 首先将代码clone下来
    • depth用于指定克隆深度,为1表示只克隆最近一次commit
$ git clone https://github.com/jmpews/Dobby.git --depth=1
  • 注意它是跨平台的,所以项目并不是一个Xcode工程,我们要使用cmake将这个工程编译为Xcode工程.进入Dobby目录,创建一个文件夹,然后cmake编译工程
  • 最新的源码中没有ios.toolchain.cmake文件、所以从老项目里边拖了一个过来
cd Dobby && mkdir build_for_ios_arm64 && cd build_for_ios_arm64
cmake .. -G Xcode \
-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \
-DPLATFORM=OS64 -DARCHS="arm64" -DCMAKE_SYSTEM_PROCESSOR=arm64 \
-DENABLE_BITCODE=0 -DENABLE_ARC=0 -DENABLE_VISIBILITY=1 -DDEPLOYMENT_TARGET=9.3 \
-DDynamicBinaryInstrument=ON -DNearBranch=ON -DPlugin.SymbolResolver=ON -DPlugin.Darwin.HideLibrary=ON -DPlugin.Darwin.ObjectiveC=ON
    • 该文件存放在Dobby/cmake文件夹下

  • 也可以采用官方建议的构建方法、因为本质上也就是获取动静态库.
  • 为iphoneos构建

python3 scripts/platform_builder.py --platform=iphoneos --arch=all

  • 得到构建产物:动态库和静态库

2.2 Dobby应用

  • 将DobbyX.framework添加进一个新的iOS项目中、依次添加拷贝进项目

  • DobbyHOOK函数
// function inline hook
// arg1: 需要HOOK的函数地址
// arg2: 新函数地址
// arg3: 原始函数指针的地址
int DobbyHook(void *address, dobby_dummy_func_t replace_func, dobby_dummy_func_t *origin_func);
  • 编写测试代码
- (void)viewDidLoad {
    [super viewDidLoad];
    DobbyHook(sum, mySum, (void *)&sum_p);
}
int sum(int a, int b) {
    return a+b;
}
//函数指针用于保留原来的执行
static int (*sum_p)(int a,int b);
//新函数地址
int mySum(int a,int b) {
    NSLog(@"HOOK success,原来的结果是 %d",sum_p(a,b));
    return a-b;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"output result %d",sum(10, 20));
}
  • 运行成功,点击屏幕的输出结果为
2023-04-10 20:06:45.004089+0800 InlineHookDemo[17503:615561] HOOK success,原来的结果是 30
2023-04-10 20:06:45.004274+0800 InlineHookDemo[17503:615561] output result -10
  • 由此可见,已成功HOOK

  • 通过汇编真机调试可以看到
    • 前三句代码被替换
    • 拉伸栈空间的代码没有了
  • 因此
    • 静态函数的HOOK,并没有在原始函数中增加代码,而是将拉伸栈空间的三句代码进行了替换
    • 当调用原始函数,才会拉伸栈平衡.然后在原始函数的代码中,恢复栈平衡

2.3 HOOK函数地址

  • 上面的案例存在着问题、当实际逆向开发中,三方会剥离符号表,我们无法获得符号名称,所以HOOK的一定是地址、
  • 应用每次启动时,ASLR偏移地址都不同,所以不能直接HOOK地址
  • 正确的做法: 先找到函数在MachO中的偏移地址,加上PAGEZERO的0x100000000,再加上本次启动的ASLR偏移地址
  • 运行程序,显示汇编,点击屏幕,触发断点、

  • 此时的 0x10114a360 地址即是要跳转的函数地址A
  • 通过image list 在控制台获取 ASLR地址 0x0000000101144000 : B
  • A - B = 0x6360 偏移量, 查看再MachO中代码段的内容为

  • 对应的正是sum函数的汇编
static uintptr_t sumP = 0x100006328;
- (void)viewDidLoad {
    [super viewDidLoad];
    //获取ASLR,相当于rebase
    uintptr_t aslr = _dyld_get_image_vmaddr_slide(0);
    sumP += aslr;
    NSLog(@"sump: %p\n sum:%p",(void *)sumP,sum);
    DobbyHook((void *)sumP, mySum, (void *)&sum_p);
}
  • 然而这里我们对代码进行了修改,那么sum地址会发生改变,这个时候查看6360已经不是刚才的代码了

  • 很明显,变成了6328, 结合输出打印的地址 0x100d8a328、减去ASLR、得到0x6328
  • 修改测试案例、得到HOOK成功的日志输出

  • 因此: 在代码不修改的情况下,地址不会发生改变.所以在逆向开发中,分析第三方应用,不会出现上述出现的这种情况

2.4 Dobby总结

  • Dobby原理: 运行时对目标函数的汇编代码替换,修改的是内存中MachO的代码段
  • Dobby替换汇编代码时,对原始函数的调用,会影响栈的拉伸和平衡
  • 在真实HOOK场景中,我们拿不到符号名称,只能对地址进行HOOK
  • HOOK地址时,需要加上PAGEZERO和ASLR

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/438508.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java动态代理(JDK/CGLIB)静态代理

Java Guide动态代理阅读笔记。 一、代理模式 代理模式是一种比较好的理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real obiect)的访问&#xff0c;这样就可以在不修改原目标对象的前提下&#xff0c;提供额外的功能操作&#xff0c;扩展目标对象的功能。 …

vue2+vue3——36+

vue2vue3——36 尚硅谷vue2vue2 Vue监测数据的原理_数组【18:56】数组vue 查看 没有 set() get()对象 有 get() set() 监测不到 不更新测试 : 数据改了 &#xff0c; 单页面没变 修改数组的 7方法 &#xff1a; filter 不改变原数组&#xff0c; 返回新的 数组 可以替换掉 原数…

逍遥自在学C语言 | 位运算符~的高级用法

前言 在上一篇文章中&#xff0c;我们介绍了^运算符的高级用法&#xff0c;本篇文章&#xff0c;我们将介绍~ 运算符的一些高级用法。 一、人物简介 第一位闪亮登场&#xff0c;有请今后会一直教我们C语言的老师 —— 自在。 第二位上场的是和我们一起学习的小白程序猿 ——…

linux 安装git(centos7)-yum

文章目录 在 Linux 系统上安装 Git步骤一&#xff1a;使用自带软件管理器安装步骤二&#xff1a;检查 Git步骤三&#xff1a;设置git环境变量 在 Linux 系统上安装 Git Git 是一种分布式版本控制系统&#xff0c;适用于处理从小型到非常大型的项目。在 Linux 系统上&#xff0…

c++11 标准模板(STL)(std::queue)(四)

定义于头文件 <queue> template< class T, class Container std::deque<T> > class queue; std::queue 类是容器适配器&#xff0c;它给予程序员队列的功能——尤其是 FIFO &#xff08;先进先出&#xff09;数据结构。 类模板表现为底层容器的包…

flac格式如何转mp3,3招帮你搞定

flac格式如何转mp3&#xff0c;3招帮你搞定的方法来啦。当你的音频是flac格式是不是很头疼&#xff0c;又不知道怎么转mp3 。然后网上搜索出很多方法又不知道从哪个下手&#xff0c;是不是很疑惑&#xff1f;那今天就来看看小编推荐的方法吧&#xff0c;一定让你眼前一亮&#…

petalinux-2021.1在zynq7020的flash上启动linux

一、 前言 在电脑上安装虚拟机或者找一个电脑安装linux&#xff0c;用于编译petalinux工程&#xff1b;安装与vivado对应的petalinux-tool&#xff1b;版本信息&#xff1a; 1&#xff09;linux&#xff1a;ubuntu16.04.06&#xff1b; 2&#xff09;vivado&#xff1a;v2021.…

row_number 和 cte 使用实例:考场监考安排

row_number 和 cte 使用实例&#xff1a;考场监考安排 考场监考安排使用 cte 模拟两个表的原始数据使用 master..spt_values 进行数据填充优先安排时长较长的考试使用 cte 安排第一个需要安排的科目统计老师已有的监考时长尝试使用 cte 递归&#xff0c;进行下一场考试安排&…

网咯通信基础 - 数据交换方式

文章目录 1 概述2 分类2.1 电路交换2.2 报文交换2.3 分组交换 1 概述 2 分类 2.1 电路交换 图示&#xff1a;发送方和接收方用一系列链路直接连通数据传输划分&#xff1a;电路建立、数据传输 和 电路拆除 3 个过程原理&#xff1a;当交换机收到一个呼叫后就会在网络中寻找一…

机器学习之SVM分类器介绍——核函数、SVM分类器的使用

系类文章目录 机器学习算法——KD树算法介绍以及案例介绍 机器学习的一些常见算法介绍【线性回归&#xff0c;岭回归&#xff0c;套索回归&#xff0c;弹性网络】 文章目录 一、SVM支持向量机介绍 1.1、SVM介绍 1.2、几种核函数简介 a、sigmoid核函数 b、非线性SVM与核函…

运行时内存数据区之堆(一)

堆&#xff08;Heap&#xff09; 堆的核心概述 一个JVM实例只存在一个堆内存&#xff0c;堆也是Java内存管理的核心区域。Java堆区在JVM启动的时候即被创建&#xff0c;其空间大小也就确定了。是JVM管理的最大一块内存空间。堆内存的大小是可以调节的。《Java虚拟机规范》规定…

“行泊舱”+出海全面发力,这家ADAS厂商跑出规模化“新速度”

进入2023年&#xff0c;智能汽车市场已经由纯技术驱动迈入了市场驱动的新周期&#xff0c;接下来的市场竞争比拼的重点将是真正的规模化普及。 《高工智能汽车研究院》认为&#xff0c;中国乘用车市场已经来到了L2普及、L2冲刺发力以及L3/L4小规模落地的并行发展周期。对于智能…

AI工具应该成为开发者的帮手

前言 作为一名大前端开发者来说&#xff0c;最看重的就是web3.0的进一步发展以及推广速度。说到web3.0就不得不说元宇宙&#xff0c;就拿今年的支付宝集五福活动&#xff0c;就用到了元宇宙的功能&#xff1a;福气乐园&#xff0c;这也是为什么元宇宙越来越成为触手可及的&…

Ubuntu 自带截图工具快捷键盘

PrtSc – 获取整个屏幕的截图并保存到 Pictures 目录。 Shift PrtSc – 获取屏幕的某个区域截图并保存到 Pictures 目录。 Alt PrtSc –获取当前窗口的截图并保存到 Pictures 目录。 Ctrl PrtSc – 获取整个屏幕的截图并存放到剪贴板。 Shift Ctrl PrtSc – 获取屏幕的某个…

嚣张|微软“光明正大”要数据,Access用户怎么办?WPS笑了

微软“光明正大”要数据 继微软“数据门”事件之后&#xff0c;微软又开始出“幺蛾子”了。 最近&#xff0c;电脑是windows11会提示&#xff1a;你的数据将在所在国家或地区之外进行处理。 最让用户感到霸道的是&#xff0c;竟然没有“跳过”按钮。只能点击继续&#xff0c;…

MyBatis学习总结(五)逆向工程

MyBatis学习总结&#xff08;五&#xff09;逆向工程 一、MyBatis的逆向工程 正向工程&#xff1a;先创建Java实体类&#xff0c;由框架负责根据实体类生成数据库表。 Hibernate是支持正向工 程的。逆向工程&#xff1a;先创建数据库表&#xff0c;由框架负责根据数据库表&am…

操作系统(四)——文件管理

文章目录 第四章 文件管理[4.1.1] 初识文件管理&#xff08;一&#xff09;文件的属性&#xff08;二&#xff09;文件内部的数据应该怎样组织起来&#xff08;三&#xff09;文件之间应该怎样组织起来&#xff08;四&#xff09;操作系统应该向上提供哪些功能&#xff08;五&a…

geoserver发布矢量切片服务

以前切片服务只支持栅格切片&#xff0c;后来技术更新发展&#xff0c;也支持矢量切片了&#xff0c;好处是不失真&#xff0c;而且很快&#xff0c;geoserver本身也不支持这种服务&#xff0c;但是他提供了一个插件&#xff0c;去官网下载下来&#xff0c;放到lib文件夹里&…

2023/4/16周报

目录 摘要 论文阅读 1、标题和现存问题 2、模型构建 3、方法实现 4、实验结果 5、扩展实验 深度学习 1、GNN特点 2、原理 3、GNN数据处理 总结 摘要 本周在论文阅读上&#xff0c;阅读了一篇基于图神经网络的技术识别链接预测方研究论文。通过融合了时间特征的专利…

镭速Raysync v6.6.8.0版本发布

最近镭速发布了v6.6.8.0版本&#xff0c;已经发布上线了。主要更新内容有服务器下发任务支持指定客户端&#xff0c;客户端增加日志清理和日志压缩&#xff0c;自动删除源文件保持源目录结构&#xff0c;支持将文件投递给其他成员等功能&#xff0c;详细的更新内容如下&#xf…