101

砸壳这件破事

 3 years ago
source link: https://mp.weixin.qq.com/s/xFHA2tlc6HCLti_ihlrsZA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

砸壳这件破事

Original 0xcc 非尝咸鱼贩 5/15

iOS 以其封闭生态闻名,所有的来自应用市场的 App 都会默认受到 DRM 加密保护。在设备越狱之后有了调试进程和部分访问内核功能的机会,也就可以从设备上通过复用系统解密逻辑拿到二进制可执行文件。这个过程就叫“砸壳”,所有逆向教程都会从砸壳这一步开始。

代码加密的原理和分析资料太多了,本文不做赘述,而是想讨论一下现在几个流行的工具和他们的特点,以及尝试解决一些边角问题。


砸壳工具的鼻祖应该是“树人” Stefan Esser 的 dumpdecrypted(GitHub:

stefanesser / dumpdecrypted)。

加密(压缩)壳都有一个特点,为了能正常执行程序,对应的代码页需要以明文加载到内存中。iOS 的 DRM 保护没有太多的花花肠子,在加载可执行文件(打开 App,或者 dlopen 一个库)时便会全部解密。

MachO 格式的加密信息保存在 LC_ENCRYPTION_INFO(LC_ENCRYPTION_INFO_64)里,包括文件是否被加密、具体的起始地址偏移和加密内容的大小。

dumpdecrypted 正是等 dyld 和 XNU 将可执行文件初始化结束后,解析 LC_ENCRYPTION_INFO 的信息找到具体的内存地址,然后直接覆盖写入回原始文件的副本,抹掉 cryptid 标记。

这个办法理论上到今天都是能用的,之后很长一段时间的各种工具的核心也沿袭了这种实现——无论是基于 task_for_pid 直接调试目标进程,基于 lldb 脚本的实现,还是基于 frida 的 js 移植版。这些工具包括 Clutch、bfinject、frida-ios-dump,甚至笔者自己也写了一个 bagbak。

bfinject 属于换汤不换药,本质上只是把 DYLD_INSERT_LIBRARIES 的换成了动态注入,没有解决任何问题。dumpdecrypted 最大的局限是程序本身需要作为一个 dylib 动态注入到目标进程中运行,使用起来比较繁琐。其他基于相同原理的工具在体验上提升了不少,例如可以根据 bundle id 定位程序路径、自动打包 zip(ipa)。

在实际操作中仍然会遇到各种情况:

  • App 有越狱检测,运行之后直接花式退出

  • App 启动后不会一次性加载所有的 frameworks,有遗漏

  • App Extension 进程不能直接运行,需要走 launchd

Clutch 启动进程用了 posix_spawn 的 POSIX_SPAWN_START_SUSPENDED 参数,可以避免执行 App 主进程的业务逻辑,即不会触发越狱检测:

640?wx_fmt=png
640?wx_fmt=png

然而在解密 framework 时仍然需要针对每一个框架单独创建一个进程,透传参数之后用 dlopen 加载 framework:

640?wx_fmt=png
640?wx_fmt=png

我们都知道可以用 __attribute__((constructor)) 给动态库添加初始化函数。假如在这里实现了越狱检测,理论上还是会被执行到。

之前有人提出 framework 加密的问题。但在最近的 iOS 版本上,我为了调试测试了不少 App,都没遇到 framework 被加了 cryptid 的样本。

Apple 在 iOS 13 之后使用了 App 瘦身技术来减少 AppStore 下载体积。App 加密似乎很不上心了,cryptsize 只有 4k,一个内存页都不到。毕竟 DRM 解密在非越狱设备上读不到文件,而越狱之后又形同虚设。

笔者大胆猜测现在 framework 没遇到加密样本,且主程序只加密一小部分的原因是加密算法会影响输出数据的熵,导致压缩效率下降,AppStore 干脆大幅减少加密的范围。


另一个坑是 App Extensions 进程。iOS 目前的 Today Widget、File Provider 等功能都是第三方应用在独立的 XPC 服务进程中实现的。直接运行此类 XPC 服务会在 xpc_main 里抛一个异常。

XPC 服务可以使用 launchd 的私有 API 启动,或者用 POSIX_SPAWN_START_SUSPENDED 让其暂停在入口点。只要别执行 xpc_main 就不会进程终止。

我自己写的 bagbak 则是使用了 PluginKit 框架的 NSExtension API,加上对 pkd 进程做动态补丁实现运行 Extension 服务进程。这个办法仍然具有一定的失败概率。

以下代码来自 Multi-Process iOS App Using NSExtension,通过 bundle id 获取 NSExtension 实例:

NSError *error;NSExtension *extension = [NSExtension     extensionWithIdentifier:@"com.example.App.AppExtension"    error:&error];

创建 NSExtension 实例之后,再调用 [NSExtension beginExtensionRequestWithInputItems:completion:] 即可激活扩展进程。在 PluginKit 的底层代码中还是走的 launch_msg 调用 launchd 相关功能,我比较懒就没有去逆向具体的消息格式。

NSExtensionItem *item = [[NSExtensionItem alloc] init];  [item setAttachments:@[@"input string"]];  [extensio beginExtensionRequestWithInputItems:@[item] completion:^void (NSUUID *requestIdentifier) {      int pid = [self.extension pidForRequestIdentifier:requestIdentifier];      NSLog(@"Started extension request: %@. Extension PID is: %i", requestIdentifier, pid);  }];

在 App Extensions 的 Info.plist 里有一个 NSExtensionActivationRule,定义了扩展进程的激活时机。由于不同的 App 这个字段也不尽相同,我对系统服务 pkd 也注入了一段 hook 来迫使 -[PKDPlugIn allowForClient:] 每次都返回允许,才能让扩展正常启动。在 iOS 14 之后这个方法被改名为 -[PKDPlugin allowForClient:discoveryInstanceUUID:]。

此外 App Extensions 进程和主进程使用独立 container 保存数据,而且沙箱限制更为严格,特别是 Jetsam 给的内存配额更低。还需要在更高特权的进程中额外调用一次 memorystatus_control() 增加插件可用的内存配额,否则 frida 根本无法注入运行。


这里提到了 frida 的方案,核心原理还是基于 dumpdecrypted 的内存转储思路。

最早是有人在 frida codeshare 上分享了将 dumpdecrypted 移植到 Javascript(frida)的代码,之后各种基于 frida 的方案基本都是从这份脚本演变而来,例如 frida-ios-dump。

我觉得 frida-ios-dump 需要的依赖项太多,所以自己又写了一个 bagbak。frida-ios-dump 的文件传输依赖 SSH(paramiko),而 bagbak 则完全基于 frida 的功能实现了无外部依赖的文件下载。frida-ios-dump 也有无法处理 App Extensions 的问题。

bagbak 代码完全基于 node.js,而前者则是用的 Python。从开发体验上来看 Python 胜于 nodejs,在于其核心库的功能足够丰富(例如 SQLite 数据库,和 iOS 上几乎无处不在的 plist 格式解析)。不过 frida 的 Python 绑定至今没有支持 asyncio,如果不想依赖外部协议实现大量数据传输,会有性能损失。

之后我会写一篇完全使用 frida 实现 iproxy(将 iOS 上的端口转发掉电脑的工具)的文章来对比 Python 和 nodejs 做开发的优劣。


到这里看上去是给 bagbak 写的软文。实际上在处理完很多 issue 之后我更希望能找到一种类似静态脱壳的办法替代现有的实现,最好不要把具体的代码执行起来。这样可以避免很多兼容性问题和没完没了的环境对抗。

终于有人写了一个接近静态脱壳的工具 FlexDecrypt。

在支持 DRM 的平台上,XNU 提供了一个 mremap_encrypted 的 syscall 用来给 dyld 解密代码页:

#if CONFIG_CODE_DECRYPTION489  AUE_MPROTECT  ALL  { int mremap_encrypted(caddr_t addr, size_t len, uint32_t cryptid, uint32_t cputype, uint32_t cpusubtype); }

FlexDecrypt 尝试模拟 dyld 的行为,将 MachO 映射到内存之后直接调用让内核解密相关的页。

640?wx_fmt=png

Flex 其实有两个后端,MachOFile+Decrypt.swift 是这里提到的 mremap_encrypted,另一个 MachOFile+SpawnDecrypt.swift 则是经典的 posix_spawn 然后 task_for_pid 直接转储进程内存的方案。

但是作者非常迷惑地使用了 swift 编写这个项目,做了过度封装,极大降低代码的可读性而增加提交补丁的难度。FlexDecrypt 就是一个命令行工具,既没有图形界面也没有用到 swift 的特性,核心代码还不得不用 C,却莫名其妙需要带上运行库才能使用。

FlexDecrypt 大多数情况能正常工作,然而 misty 发现少数 App 可能导致 FlexDecrypt 失败,因为其重建 MachO 格式的实现存在 bug。NowSecure 的 @meme 发布了另一个工具 foulplay,实现非常简洁,不过面向的平台是 M1 的 mac,而不是 iOS。misty 尝试把 foulplay 迁移到 iOS,却在 mremap_encrypted 之后程序停止在 memcpy 一步。

根据 NyaMisty 的分析,在 iOS 14 之后 dyld 本身不再在用户态调用 mremap_encrypted 解密,而是把更多工作交给内核:

https://github.com/NyaMisty/fouldecrypt/tree/master/analysis

mremap_encrypted 仍然可以用,但是这个函数要求起始地址按页对齐(目前多数设备 PAGE_SIZE 是 16k)。

mremap_encrypted 实际上不会立刻解密内存,而是将对应的页标记为需要通过 APPLE PROTECT MEMORY PAGER 处理。当访问到具体的页时,才会触发 fairplay 的解密。这就是为什么之前在参数有错误的情况下,remap 执行完毕,但在 memcpy 一步程序直接挂起。

Misty 在 foulplay 的基础上重新实现了一个 fouldecrypt,直接修改内核当中对应的 struct proc_regioninfo 结构信息来让地址满足按页对齐。

https://github.com/NyaMisty/fouldecrypt/blob/435ffed/main.cpp#L187

这个项目安装包发布在

https://repo.misty.moe/apt

通过 Cydia 添加软件源之后安装 fouldecrypt 即可。命令行格式:

flexdecrypt2 [path-to-executable]

将把解密好的文件保存到 /tmp 下。

我也在考虑把 VSCode 插件里的快捷方式改为 fouldecrypt。


除了最核心的解密逻辑之外,很多人喜欢一键的自动化的体验——输入一个 bundle,自动处理所有的文件并打包成 zip(ipa)。不过通过 bundle id 找到对应的 App 路径确实恶心,因为 app 安装路径有随机 uuid 处理。这只是交互的问题,没有涉及技术。clutch 除了年久失修之外,其实这点处理还不错的。

打包 zip 又有一个细节。zip 压缩可以在 iOS 上做,也可以等文件全部拖到 PC 端做。iOS 算力比不上 PC,可能耗时更长;但假如电脑端是 Windows,由于文件系统的差异,生成的 zip 会没有可执行文件等属性。另外这时候又要黑一下 nodejs,Python 核心库直接可以读写 zip,而 node 则要安装依赖包处理。

总之写一个好用的砸壳工具还是很恶心的,也不是套个界面这么简单。

参考资料

[1]. dumpdecrypted https://github.com/stefanesser/dumpdecrypted

[2]. Clutch https://github.com/KJCracks/Clutch

[3]. frida-ios-dump https://github.com/AloneMonkey/frida-ios-dump

[4]. https://codeshare.frida.re/@lichao890427/dump-ios/

[5]. Multi-Process iOS App Using NSExtension https://ianmcdowell.net/blog/nsextension/

[6]. https://www.linkedin.com/pulse/decrypting-apps-ios-john-coates

[7]. https://github.com/JohnCoates/flexdecrypt

[8]. https://github.com/meme/apple-tools/tree/master/foulplay

[9]. https://github.com/NyaMisty/fouldecrypt/tree/master/analysis


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK