以 SingPass 应用为例分析 iOS RASP 应用自保护的实现以及绕过方法(下)
2023-4-11 12:2:50 Author: 嘶吼专业版(查看原文) 阅读量:14 收藏

之前的 Frida stalker 检查的替代方法是通过以下调用访问当前线程状态:

然后,由于以下比较,它检查 state->ts_64.__pc 是否在 libsystem_kernel.dylib 中:

换句话说,如果 state->ts_64.__pc 与 &mach_msg 的距离小于 0x4000,则认为它在 libsystem_kernel.dylib 中。

乍一看,对这个 RASP 检查可能不是很熟悉,但由于之前与 EVT_CODE_TRACING 相关的检查旨在检测 Frida Stalker,因此该检查也可能旨在检测 Frida Stalker。

为了证实这个假设,我开发了一个小测试用例,在一个独立的二进制文件中重现了这个检查,我们可以根据它是否通过 Frida stalker 来观察差异:

Stalker 测试用例的输出

没有 Stalker 的测试用例的输出

通过使用函数 gum_stalker_exclude 从跟踪者中排除库 libsystem_kernel.dylib ,从而轻松绕过此检查:

可以看到,state->ts_64.__pc 位于 libsystem_kernel.dylib 中:

排除内存范围的测试用例的输出

RASP 事件 EVT_APP_LOADED_LIBRARIES 旨在检查 Mach-O 依赖项的完整性。换句话说,它检查 Mach-O 导入的库是否被修改。

Assembly ranges: 0x100E4CDF8 – 0x100e4d39c

由于 dladdr 函数,与此检查相关的代码首先访问 Mach-O 标头:

dl_info 包含库的基地址,其中包含第一个参数中提供的地址,因此,一个Mach-O二进制文件会连同它的标头文件Dl_info一起加载。Dli_fbase实际上指向mach_header_64。

然后该函数遍历类似 LC_ID_DYLIB 的命令以访问依赖项的名称:

此名称包含依赖项的路径。例如,我们可以按如下方式访问此列表:

依赖项的名称用于填充哈希表,其中哈希值以 32 位编码:

在后面的代码中,这个计算表将与另一个哈希表(代码中硬编码的)进行比较,如下所示:

如果某些库已被修改为注入,例如 FridaGadget.dylib,那么动态计算的哈希将与代码中硬编码的哈希不匹配。

虽然这种检查的执行是相当“标准”的,但有几点值得一提:

首先,哈希函数似乎是一个派生的MurmurHash。

其次,哈希是32位编码的,但是图4中的代码引用了64位的X11/X12寄存器。这实际上是一个限制内存访问次数的编译器优化。

最后,在每个检查实例中,硬编码的哈希值在二进制文件中重复。在 SingPass 中,此 RASP 检查出现两次,因此我们在以下位置找到这些值:0x100E4CF38、0x100E55678。这种重复可能用于防止易于修复的单点位置( single spot location)。

此检查与事件 EVT_CODE_SYSTEM_LIB 相关联,该事件包括验证内存系统库及其在 dyld 共享缓存(磁盘上)中的内容的完整性。

Assembly ranges: 0x100ED5BF8 – 0x100ED5D6C and 0x100ED5E0C – 0x100ED62D4

此检查通常以以下模式开始:

如果带有给定 check_region_cbk 回调的 iterate_system_region 的结果不为 0,它会触发 EVT_CODE_SYSTEM_LIB 事件:

要理解这个检查背后的逻辑,我们需要了解 iterate_system_region 函数的用途以及它与回调 check_region_cbk 的关系。

该函数旨在调用系统函数 vm_region_recurse_64,然后根据可能触发第一个参数check_region_cbk中给出的回调的条件过滤它的输出。

iterate_system_region首先通过SYS_shared_region_check_np系统调用访问dyld共享缓存的基址。这个地址用于读取和记忆dyld_cache_header结构中的一些属性:

1.共享缓存标头;

2.共享缓存结束地址;

3.与共享缓存相关的其他限制;

计算过程如下:

从逆向工程的角度来看,用于记忆这些信息的堆栈变量与稍后调用的 vm_region_recurse_64 的参数信息别名。我不知道这种混叠是否是故意的,但它使结构的逆向工程变得更加复杂。

在vm_region_recurse_64上有一个循环,它查询vm_region_submap_info_64信息,查找dyld共享缓存范围内的这些地址。由于mach_msg_type_number_t *infoCnt参数被设为19,我们可以确定查询的类型(vm_region_submap_info_64):

此循环在某些条件下中断,并且在其他条件下触发回调。正如稍后解释的那样,回调验证 dyld 共享缓存中存在的库的内存完整性。

这个循环在某些条件下中断,而在其他条件下触发回调。回调会验证dyld共享缓存中存在的库在内存中的完整性。

基本上,如果发生以下情况,就会触发对共享缓存进行深度检测的回调:

当条件满足时,iterate_system_region调用check_region_cbk,第一个参数中带有可疑地址:

在分析 SingPass 时,只有一个回调函数与iterate_system_region一起使用,它的代码并没有特别混淆(字符串除外)。一旦我们知道这些检查与dyld共享缓存有关,我们就可以很容易地弄清楚这个函数中涉及的结构。这个回调位于0x100ed5e0c地址,并重命名为check_region_cbk。

它首先访问有关地址的信息:

此信息用于读取与地址参数关联的__TEXT 段的内容。

  __TEXT 字符串以及共享缓存的不同路径(如 /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e 和标头的魔法值:0x01010b9126:dyld_v1 arm64e 或 0x01010b9116:dyld_v1 arm64)都被编码。

另一方面,该函数打开 dyld_shared_cache 并查找包含与地址参数关联的库的共享缓存部分:

第二次调用mmap()的目的是加载包含库代码的共享缓存部分。然后,该函数检查__TEXT段的内容是否与内存中的内容相匹配。执行此比较的循环位于0x100ED6C58 - 0x100ED6C70。

我们可以从这个RASP检查的描述中观察到,开发者花了很多精力来避免性能问题和内存消耗。另一方面,在我的测试中从来没有调用过回调check_region_cbk(即使我挂钩了系统函数)。我不知道是不是因为我误解了条件,但最后,我必须手动强制条件。

由于保存函数指针的不同 #EVT_* 静态变量,混淆器能够为支持的 RASP 事件提供专用回调。尽管如此,应用程序开发人员定义的函数 init_and_check_rasp 将所有这些指针设置为同一个回调:hook_detect_cbk_user_def。在这样的设计中,所有 RASP 事件最终都在一个函数中,这削弱了不同 RASP 检查的强度。

这意味着我们只需要针对这个函数来禁用或绕过 RASP 检查。

由于这个缺点,我可以防止应用程序一启动就显示错误消息。

它存在另外两个 RASP 检查:EVT_APP_MACHO 和 EVT_APP_SIGNATURE,由于开发人员未启用它们,因此在 SingPass 中不存在。

一方面商业解决方案实现了强大而先进的 RASP 功能,例如,内联系统调用分布在应用程序的不同位置。另一方面,应用程序的开发人员通过为所有事件设置相同的回调来削弱 RASP 功能。此外,该应用程序似乎没有使用商业解决方案提供的本机代码混淆,这使得 RASP 检查不受静态代码分析的保护。无论用户提供什么配置,对这些检查强制执行代码混淆都是值得的。

参考及来源:https://www.romainthomas.fr/post/22-08-singpass-rasp-analysis/

相关阅读:

以 SingPass 应用为例 分析 iOS RASP 应用自保护的实现以及绕过方法(上)


文章来源: http://mp.weixin.qq.com/s?__biz=MzI0MDY1MDU4MQ==&mid=2247559959&idx=3&sn=2b5e3942b908aaa8d937d1293259cb58&chksm=e9143b2dde63b23be5a4f785c6bf094baad68ded65879d639156df396c7d5349b1ee64cd06eb#rd
如有侵权请联系:admin#unsafe.sh