type
status
date
slug
summary
tags
category
icon
password

越狱hook的原理

越狱hook用的是cydia mobilesubstrate框架,通常直接用MSHookFunction进行内联hook。我个人的感觉和Windows常用的minhook是差不多的。修改代码段、添加了一个管道函数。用起来非常方便。
除此之外,常用的是一个叫kittymemory的框架,提供了包括特征扫描、修改代码段等等一系列功能,也会自动找到模块基地址进而去拿虚拟地址,不需要自己去算,提供了一系列这样的api,非常方便。

非越狱为什么用不了

在jailed环境下用,首先是没有substrate框架,这个可以一起注入进ipa来解决。问题是内联hook、kittymemory的patch都会改代码段,而iOS有代码完整性检查。这个非常厉害,只要代码段被改了,和签名不匹配就会马上崩溃。所以其实越狱的注入substrate后,唯一的问题就是不能动代码段了。如果只做数据读写其实是没问题的。比如下面这个崩溃日志,在codesigning崩溃了。
notion image

一种朴素的思路

很自然的我们就能想到,能不能把要hook和要patch的地方重定向到我们自己的代码,根据用户的设置,选择跳转到哪里继续执行。这里的跳转分别就是原位置继续执行和执行hook函数。
想法非常简单而且肯定可行,只是自己做起来有点难度。在我的进一步调研之下,发现了有一种外挂叫做H5GG,它就是采用这种方式进行了hook。接着我在GitHub找到了别人用C语言实现的H5GG的hook方式,只不过这个仓库不太完善,只给了一些函数。而且代码写的比较乱。接下来我梳理一下其中的逻辑,整合了越狱、非越狱hook的框架,通过宏定义操作,就可以快速实现两个版本的编译。

macho基本知识

load_command

在Mach-O文件格式中,load_command是文件头的一部分,用于描述与文件相关的各种信息。每个load_command都包含一个固定的结构体,用来描述某种加载任务或数据的类型。每个命令的内容由两个部分组成:
  1. 命令类型(cmd):这个字段是load_command的类型标识符,表示该命令的类型(例如,是否是代码段、库、符号表等)。
  1. 命令大小(cmdsize):这个字段表示命令结构体的总大小(包括命令本身的大小),它用于遍历命令列表时确定每个命令的大小。
load_command本质上定义了可执行文件的各个部分,系统加载时需要了解这些信息。常见的命令类型有:
  • LC_SEGMENT:表示一个内存段,包含程序的代码或数据。
  • LC_SYMTAB:符号表命令,用于描述符号表的位置。
  • LC_LOAD_DYLIB:加载动态库(dylib)的命令。
  • LC_LOAD_WEAK_DYLIB:加载弱引用动态库的命令,意味着该动态库是可选的。
  • LC_UUID:UUID命令,描述文件的唯一标识符。
  • LC_MAIN:指示程序的入口点。
每个load_command都紧跟在Mach-O文件头部之后,并且多个命令会按顺序排列在文件中,供加载器(loader)在加载过程中解析。
具体来说,load_command是Mach-O格式用来告知操作系统如何加载可执行文件或共享库的重要机制。
其中LC_SEGMENT给人的感觉和Windows下PE头的Section Header很像。LC相当于把所有的导入命令集合于一体了,包括自身信息和加载其他动态库的信息。LC_LOAD_DYLIB又很类似于导入表。但二者和Windows的实现方式都不一样。
所以如果需要修改macho,这个头地方肯定是需要修改的

框架原理

主要有这些函数。
notion image
前两个函数就不说了,是虚拟地址转相对再转文件中的地址。load_macho_data是加载当前应用的可执行文件。这个是可以设置的,比如说对于Unity的游戏,我们需要操作的是UnityFramework这个framework,那就用它的路径即可。它在加载之后还检测了魔数,判断一下是不是支持的架构。
notion image
主要是在staticInlineHookPatch函数中进行了寻找/使用的操作。
大概逻辑是,首先我们遍历当前的可执行文件,是不是有__HOOK_TEXT段和__HOOK_DATA段。如果有,说明这是修补过的。
首先是查看load_command,查看这个可执行文件都有哪些段。如果有__HOOK_TEXT段和__HOOK_DATA,那说明这是我们已经修改过的可执行文件了,不必再进行操作;如果没找到的话,就调用add_hook_section,给macho手动再添加上这两个段。添加上之后,再循环一次就能找到并break了。
notion image

add_hook_section的流程如下:

1. 读取 Mach-O 文件头
  • struct mach_header_64* header = (struct mach_header_64*)macho.mutableBytes;
  • 通过 macho.mutableBytes 获取 Mach-O 文件的内存映像,然后将其解析为 mach_header_64 结构,获取 Mach-O 文件的基本信息。
2. 遍历 Load Commands(加载命令)
  • 通过遍历 Mach-O 文件中的加载命令来查找各个 segment(段)和 section(节),并获取相关的内存地址和文件偏移信息。
  • struct load_command* lc = (struct load_command*)((UInt64)header + sizeof(*header)); 这一部分获取到所有的加载命令,并逐个解析。
  • 如果是 LC_SEGMENT_64 类型的命令,代码会继续查看它是否是 __LINKEDIT 段,并计算出内存中的最大地址(vm_end)和最小的 section 偏移量(min_section_offset)。
3. 提取 __LINKEDIT
  • NSRange linkedit_range = NSMakeRange(linkedit_seg->fileoff, linkedit_seg->filesize);
  • 通过提取 __LINKEDIT 段的数据,暂时删除它([macho replaceBytesInRange:linkedit_range withBytes:nil length:0];),因为接下来会修改它。
4. 构建新的段和节
  • 代码接着创建了两个新的段:
    • __HOOK_TEXT:这个段将存放 hook 的执行代码。
    • __HOOK_DATA:这个段将存放与 hook 相关的数据。
  • 创建这两个段时,代码设置了它们的虚拟地址、文件偏移、大小、权限等信息。
    • notion image
5. 修改 Load Command
  • 在新段和节被添加之后,代码需要更新 Mach-O 文件中的 load_command。它通过计算 linkedit_cmd_offset 来定位到 __LINKEDIT 段的位置,并替换为新的段(text_segdata_seg)。
  • 然后,更新了加载命令中的各种字段,如 LC_SYMTABLC_DYSYMTAB 中涉及符号表和动态符号表的偏移量。
6. 修改其他相关信息
  • 之后,代码还会调整 dyld_info_commandsymtab_command 等结构体中的偏移量,确保新的段和节被正确地映射到它们的文件和虚拟内存地址中。
7. 向 Mach-O 文件末尾添加代码和数据
  • 通过 macho appendBytes 将实际的 hook 代码(全是 0xFF 填充)和数据(全是 0)附加到 Mach-O 文件的末尾。
  • 注意,text_seg.vmsizedata_seg.vmsize 决定了附加的代码和数据段的大小。
8. 返回修改后的 Mach-O 文件
  • 最终,修改后的 Mach-O 文件作为 NSMutableData 返回。

添加hook和patch

因为添加的段的大小都是一个页的大小。这里遍历一个页,找到还是空着的地方。
notion image
把整个数据段当成一个StaticInlineHookBlock数组,找到还是空着的,就是没插入过的hook。下面是这个结构体的定义。
notion image
找到第一个空着的该结构体,作为新添加hook的位置。在这里用dobby框架进行hook。如果是patch的话还要写入patch的大小和哈希。这里写哈希的作用是,后续启动patch的时候一样需要传入需要改为的指令。如果该指令哈希与我们修改可执行文件时候写入的哈希不一样,认为代码有错误,那么不会进行patch。
notion image
在全部hook/patch添加完成之后,那么应该保存一次macho。以此作为在非越狱手机上使用的文件。

启动hook

notion image
这个函数,以vaddr作为标识找到对应的hookblock,修改其中的target_replace,就可以把执行流重定向到我们的函数。返回的是原函数地址。

启动patch

notion image
启动patch的做法仍然是按照对应的地址找到对应的block,修改target_replace到patched_vaddr,重定向执行流。只不过会添加一个哈希作为校验,不一样的话不会执行,因为patch是写死在可执行文件里的,跟dylib没关系,防止造成误解。

关闭patch

notion image
找到对应的block、确定哈希正确,把target_replace设置为null即可。
关于dobby是怎么建立管道的,看这个函数即可。我们直接包含编译完成的.a文件就行了。

🤗 打包和使用

生成免越狱版可执行文件、签名、安装

对于指定的可执行文件,我们在越狱手机上注入一次刚编译的针对免越狱的dylib,他会读取整个.text段,把对应的位置修改后保存下来。
在应用的document目录会产生新的可执行文件,替换到dump下来的未加密app中
notion image
重新压缩payload文件夹,重命名为.ipa
notion image
然后,使用ipatch注入dylib,注意勾选inject substrate。这是在非越狱手机上使用的必要框架
notion image
patch完成之后需要对ipa进行签名。其中所有的dylib也都需要代码签名。问题是很多签名工具都不会自动签其中的dylib。签不好的话打开就闪退。最好用的是爱思助手,可以直接签好整个ipa。淘宝买个证书12块钱能用半年到一年。
notion image
这样拿到了签名后的ipa。可以直接用xcode安装。windows也可以用爱思助手安装。
notion image
再配合上一个imgui的菜单,就可以在非越狱的手机上随时随地开挂了。其实是添加了一个MTKView用来处理Metal渲染管线。MTKView 的 draw(in:) 方法会调用 ImGui 的渲染函数,将 UI 渲染数据通过 Metal 命令提交到 GPU。框架写完之后,和在电脑上写挂没什么区别。
notion image
 
Relate Posts
写一个Android Hook小框架
Lazy loaded image
一加11 内核、ROM爆改+脱壳机 LineageOS 22.2 Android 15
Lazy loaded image
小红书shield Chomper模拟
Lazy loaded image
iOS网易新闻登录算法逆向
Lazy loaded image
iOS典型反调反越狱app分析
Lazy loaded image
iOS逆向——某跑步软件的文件上传
Lazy loaded image
一些macos常用软件破解记录iOS逆向——某跑步软件的文件上传
Loading...
Lynnette177
Lynnette177
建议开着梯子访问站点。图片是直接从Notion获取的,不开梯子容易看不见图片。
Latest posts
写一个Android Hook小框架
2025-6-23
一些macos常用软件破解记录
2025-6-22
iOS典型反调反越狱app分析
2025-6-22
iOS网易新闻登录算法逆向
2025-6-22
小红书shield Chomper模拟
2025-6-22
一加11 内核、ROM爆改+脱壳机 LineageOS 22.2 Android 15
2025-6-22
Announcement
🎉2024.6.9 上线🎉