导读:在移动安全中,客户端层的代码安全非常重要,因为移动应用程序通常在客户端设备上执行,并处理用户敏感数据。通过加固应用程序,可以有效抵御逆向工程和恶意攻击,保护应用程序的机密信息和知识产权。加固通过代码混淆、加密保护和反调试等手段,为应用提供了强大的安全防护层,增加了攻击者破解和修改应用程序的难度。
一、ios 加固方案
目前主流的加固方案分为三种:
源码加固:直接基于源码工程进行混淆,该模式一般只能在开发者环境下部署加固工具,需要额外做一些环境配置。
bitcode 加固:因为 bitcode 本质上是 ipa 编译过程的中间代码,其加固原理和源码并无太大区别,主要区别在对接方式,通过上传带 bitcode 的包体,加固流程可以在加固厂商进行,减少对接和环境部署的成本。
无源码加固:基于 ipa 包的加固,因其作用于二进制文件,功能上控制力度没有源码灵活,但接入成本低。
在新的 xcode15 发布后,官方已经移除了 bitcode 的生成开关。
https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes
这使 ios 客户端加固可能最后只剩下源码加固和无源码两种方案。因源码加固方案原理上是开源且有参考依据的,这里不做过多讲述,下面简单介绍无源码加固的一些原理。
二、ios 无源码加固
任何平台的无源码加固都无法脱壳文件格式,ios也不例外。同android下elf文件, ios 的可执行文件是 macho 格式文件的一种。从程序加载运行角度, macho 和 elf 有以下相同点和不同点。
相同点:
○ 都是用 segment 描述内存加载范围和权限,并使用 section 对代码和数据进行更详细的划分。
○ 都包含符号表和字符串表记录函数,变量信息。
○ 都有重定位概念。
不同点:
○ ios section 有更明确的意义,尤其是 objc 以及 swift 相关的代码信息。
○ ios 支持懒加载。
○ ios 对符号有更严谨的分类和排序。
○ ios 中 load command 更类似于 elf 中 dynamic 和 phdr 的组合,是对程序结构和依赖的描述。
要理解一个平台程序的加固方式,首先需要理解程序加载和执行流程。
加载
加载流程包括内存映射,内存修复和初始化过程。
macho 从文件内容看,包含三大块数据结构。
○ macho header :包含了 mach-o 文件的基本信息,如文件类型、cpu 类型、load command 数量等。
○ load command :每个加载命令都包含一个标头和数据。标头包含了加载命令的类型、大小和其他相关信息。
○ segment:用于描述 mach-o 文件中的代码段、数据段和加载器需要的数据信息。
load command 所有关联的数据都可以通过解析 command 结构读取,且数据必定在以上数据范围。整个 macho 文件大小等于以上三块数据大小总和。
程序在编译过程中存在一些约定,部分自实现的变量或函数的调用,使用的是一个相对地址,可以简单理解成相对于程序加载的首地址的偏移 ,而不是真实地址。但程序加载到内存,首地址一般是随机的,运行前需要对这些地址进行修复,这个过程称之为 rebase。
程序开发过程中,如用到动态库中的函数或变量时,生成的二进制产物中,会标识这些函数或变量是需要导入的,会在一块地址区域(got 表)预留该函数地址。动态链接的过程就是要修复这些位置的函数地址或者变量值,而这个过程被叫做 bind。
程序加载过程简单理解可以分成以下三个流程。
○ 按照 segment 设定的地址,分配数据内存,并分配权限。
○ 按照 rebase 中的规则,对编译生成的相对地址进行修复,转成真实地址。
○ 按照 bind 中的规则,对编译依赖的其他函数和变量地址进行修复,保证程序能正常调用。
因 ios 版本迭代,rebase 和 bind 的描述以及数据存储方式在不同版本之间是有差异的。
ios14 以下可以通过解析 lc_dyld_info 或者 lc_dyld_info_only 来进行 rebase 和 bind 操作。
rebase 和 bind 操作都遵循 ios 自定义一套 opcode 进行解析,可以参考 dyld 源码去理解 opcode 的处理过程。
rebase 通过一系列操作码进行特定运算,结果是为了保证 app 经过加载,内存基址随机化的情况下,程序关联的内部地址可以得到修复。
bind 使用的是另一套编码,bind 过程用于绑定符号,比如程序使用其他库中的变量函数等情况,需要将地址信息写入程序内存,保证正常访问和使用这些函数和变量。
ios14 以上虽兼容 14 以下格式,但提供新的格式(fixup chains)来完成动态链接。当编译 app 时选择仅支持 ios14 以上的情况下,macho 文件中,不再有 lc_dyld_info 或 lc_dyld_info_only,取而代之的是 dyld_chained_fixups和dyld_exports_trie。
○ dyld_chained_fixups 对应旧版本的 bind 和 rebase,其中删除了 lazy bind 机制,但仍有 bind 和 weak bind 之分。
○ dyld_exports_trie对应旧版本的导出符号。
dyld_chained_fixups的数据关系图如下:
以上 ios 重新定义了一套重定位数据的方法,fixup chains 的数据关键功能有两个:
○ 初始化 import table,导入表是一张链表或数组,其中包含符号信息和lib信息,指向符号的来源,其数组索引被bind数据关联。
○ 引导 dyld 找到 dyld_chained_starts_in_segment 数据,计算出代码数据中真正需要进行修复的地址,地址一般在 got 表或一些引用的本地变量。
而在数据部分,采用不同于之前版本的初始化方法。
○ 旧版格式在 linkedit 中直接指定好了哪些地址需要进行 rebase,哪些地址需要 bind。真正要修复的地址中,默认值可能是 0 或者是一个相对地址。
○ 新版格式 fixupchains 只是获取数据的首地址,而数据需要以链表的形式,通过 chainedfixuppointerondisk 的结构进行解析,根据结构类型做对应的修复操作。
新版本 macho 在格式上添加了复杂度,但在存在大量重定位的程序中,可以很少地减少数据占用的体积。
执行
执行流程涉及程序内部默认执行的一些接口。这里我们更关注 main 函数启动之前的行为。
○ 加载并注册类(class)和类扩展(category)中的方法。
○ 调用 objc 的 load 函数。
○ 执行__mod_init_func 中的函数其中包含但不限于以下规则中声明的函数。
○ 声明为__attribute__((constructor)) 的 c 函数。
○ 全局变量中已经初始化的对象。
○ 执行 main 函数。
关注启动流程,是为了在做加固时,在合适的时机对加固相关的功能进行初始化,比如在 load 中进行初始化,或者是在插入 init 执行初始化。需要保证初始化时机在加固功能生效之前。
方案和效果
在确认程序的加载和执行流程后,我们可以对程序进一步做类似数据加密、混淆变换、功能对抗等操作,以达到需要的保护效果。二进制的加密混淆几乎都是基于汇编和文件去处理的,相对于源码有一定难度,但处理得当,其分析难度并不比源码加固差,相反,会因为汇编和文件处理的灵活性,更容易对特征进行修改。
以下是其中一种保护效果:
从效果看,不同于普通的混淆,表面上存在一层抽取,并对真实代码二次进行了混淆处理,不仅无法分析函数体,甚至对于参数类型也无法正常解析。
三、结语
在本文中,我们详细介绍了部分 ios 应用程序加固的原理。希望能从中帮助一些需要了解加固朋友了解一些原理上的知识。在当前数字化时代,应用程序安全至关重要。为了确保用户数据和业务的安全,我们不断完善 ios 加固产品,旨在为开发者提供全面的应用程序保护至尊全讯大全官网的解决方案。