最近发现部分 App 以字符串拼接的方法调用私有 API,在提交 AppStore 审核后被发现打回修改的案例。
对于开发者提交的二进制文件,Apple 是如何检查出私有 API 的调用 ?本文尝试探讨可能的检测机制,以及每种机制的漏洞。
[self _privateMethod];
借助 Objective-C 语言的动态特性,在运行时用 performSelector
执行拼接的 selector 方法:
NSArray *parts = @[@"_priva", @"teMethod"];
NSString *selectorString = [parts componentsJoinedByString:@""];
[self performSelector:NSSelectorFromString(selectorString) withObject:nil];
用 nm
、otool
等工具导出二进制包的函数符号表,以检查私有 API 的调用。
开源项目 iOS-private-api-checker 以这种方式实现了对私有 API 调用的检查。
然而这种方法的缺点是,无法检测字符串拼接方法的私有 API 调用。
在审核人员运行 App 的同时,用 runtime 工具检测是否调用了私有 API。具体原理待补充。
这种方法的漏洞,当审核人员无法进入调用了私有 API 的功能时(通过后台下发配置文件控制功能入口),会有漏测的情况。
为检测字符串拼接法调用私有 API,受论文 [1] 启发,可以在对二进制文件反汇编结果的基础上,进行静态分析:
performSelector:
,以及调用对象的类以私有 API 调用方法2 的代码为例,用 Hopper 对其反汇编,得到伪代码:
void -[ViewController viewDidLoad](void * self, void * _cmd) {
r31 = r31 - 0x60;
*(r31 + 0x30) = r22;
*(0x40 + r31) = r21;
*(r31 + 0x40) = r20;
*(0x50 + r31) = r19;
*(r31 + 0x50) = r29;
*(0x60 + r31) = r30;
*(r31 + 0x28) = ___stack_chk_guard;
*(r31 + 0x8) = self;
*(r31 + 0x10) = 0x100008d38;
[[r31 + 0x8 super] viewDidLoad];
*(r31 + 0x18) = @"_private";
*(0x28 + r31) = @"Method";
r0 = [0x100008d28 arrayWithObjects:r31 + 0x18 count:0x2];
r0 = [r0 retain];
r20 = r0;
r0 = [r0 componentsJoinedByString:@""];
r0 = [r0 retain];
[self performSelector:NSSelectorFromString(r0) withObject:zero_extend_64(0x0)];
[r0 release];
[r20 release];
if (___stack_chk_guard != *(r31 + 0x28)) {
__stack_chk_fail();
}
return;
}
可以看出,伪代码有着充分的信息,可以进行静态分析推导,判断代码片段是否调用了私有 API。
然而这种方法也有漏洞,拼接的字符串由服务器下发,可以避开检查。
欢迎大家补充,可以留言在 https://github.com/liuslevis/hexo/blob/master/source/_posts/ios-private-api-detection.md
[1] Bergeron, Jean, et al. “Static analysis of binary code to isolate malicious behaviors.” Enabling Technologies: Infrastructure for Collaborative Enterprises, 1999.(WET ICE’99) Proceedings. IEEE 8th International Workshops on. IEEE, 1999. PDF
[2] 反汇编的利器 IDA 和 Hopper 的基本使用 Niyao https://niyaoyao.github.io/2017/01/18/Learning-Reverse-From-Today-D3/