58同城iOS客户端安全加固原理与实践

导语

本文结合 iOS 反向破解中常用的技术手段阐述了代码混淆、反调试、签名信息验证、异常数据检测和处理等安全保护原理和实践,使您的 App 安全性更高。

背景

iOS 系统一直被称为安全。为了保证系统的安全,苹果使用 App 签名、沙箱机制、地址随机化的安全防护措施很多。iOS 系统虽然安全,但并非坚不可破。iOS 系统的安全让很多开发者忽略了自己 App 的安全。

在越狱设备中可以访问 App 沙箱数据,Keychain 存储的数据;使用 class-dump 工具可以导出 Objective-C 头文件; 的帮助IDA、Hooper 可以静态分析代码逻辑,如反汇编和反编译工具Cycript、debugserver lldb 可态调试程序;攻击者还可以编写 tweak 注入到 App 中,篡改应用逻辑,实现二次包装App 多开。

市场上 BAT、 ** 、网易等一线大厂 App 做了一定的安全防护,但大多数 App 没有基本的安全防护。App 如果不进行安全加固,在越狱环境中就像裸奔,严重者被黑产品利用,给公司造成经济损失。

本文主要介绍代码混淆、反调试保护、签名信息验证、异常数据检测和处理等 iOS 常用的安全防护技术,以及 58 同城 iOS 客户端的实际应用。

iOS安全加固的原理和实践

1. 核心代码混淆

1.1 代码混淆概述

在 iOS 在逆向过程中,攻击者通常以类名、函数名等符号为突破口,分析破解应用。class-dump 工具,可恢复 Objective-C 中的头文件信息包括类名、属性、方法和协议。Hopper 、IDA 在反汇编和反编译工具中,搜索类名和方法名可以快速定位到相应的函数,静态分析代码逻辑。因此, App 混淆敏感业务中的类别和方法,用晦涩的符号代替,可以增加攻击者的逆向难度。

虽然代码混淆是一种简单有效的安全防护手段,但作者研究了微信、支付宝、美团、抖音等 App,发现符号混淆并没有被广泛使用。在项目中,我们基本上使用 Objective -C 的 Runtime 特性,基于字符串,动态创建类别,呼叫方法。符号混淆存在潜在的风险。考虑到各种复杂的情况,稍有不慎就会导致程序异常,难以跟踪调试。此外,苹果不允许大量使用混淆,也有被拒绝审计的风险。虽然在项目中大规模使用代码混淆是不现实的,但只混淆核心业务代码并不是一种有效的方法。

在 Objective-C 使用核心函数及其参数和返回值block 使用 实现敏感业务代码C 或 C 实现还可以增加逆向分析的难度。

1.2 代码混淆实践

混淆方法名和返回值后,使用 class-dump 导出头文件,混淆后检查效果如下:

代码混淆效果

2. 反调试防护

2.1 反调试防护原理

动态分析对应于静态分析,也是攻击者常用的破解手段。 用于越狱设备debugserver、lldb 工具可动态调试分析 App 操作逻辑,设置断点,查看函数调用堆栈,打印输出变量值。支付宝、美团、抖音等。App 都实现了反调试保护,防止攻击者动态调试分析 App,有必要在程序中增加反调制机制。

从逻辑上讲,反调试可以分为两种,一种是防止调试器挂载,如 ptrace;另一种是获取过程信息,检测当前过程是否被调试,如 sysctl。

a) 使用 ptrace 防止调试器挂载

ptrace 是一种系统调用,主要用于跟踪调试应用程序,其函数原型如下:

int ptrace(int request,pid_t pid,caddr_t addr,int data);

第一个参数 request 指定要执行的操作,当参数设置为 PT_DENY_ATTACH 值时,应用程序会通知系统不允许跟踪和调试。试图跟踪调试应用程序将被拒绝,应用程序将收到一个部分错误。

   一段简单的 iOS ptrace 反调试代码示例如下:

#import <dlfcn.h>#import <sys/types.h>typedef int (*ptrace_ptr_t)(int _request,pid_t _pid,caddr_t _addr,int _data);#if !defined(PT_DENY_ATTACH)#define PT_DENY_ATTACH 31#endifstatic __attribute__((always_inline)) void antidebug_ptrace() { void* handle = dlopen(0,RTLD_GLOBAL | RTLD_NOW); ptrace_ptr_t ptrace_ptr = dlsym(handle,"ptrace"); ptrace_ptr(PT_DENY_ATTACH,0,0,0); dlclose(handle);}

注:以上代码参考:https:// ** .theiphonewiki.com/wiki/Bugging_Debuggers

另外,ptrace 也可通过系统调用实现,ptrace 相应的系统调用号为 26,PT_DENY_ATTACH 值为 31,示例代码如下:

static __attribute__((always_inline)) void antidebug_syscall_ptrace() syscall(26、31、0、0)

b) 通过 sysctl 检测过程信息

与 ptrace 通过防止调试器不同的过程,sysctl 检测当前过程是否通过获取过程信息进行调试。

sysctl 函数定义如下:

int sysctl(int *name,u_int namelen,void *oldp,size_t *oldlenp,void *newp,size_t newlen);

第一个参数 name 是一个描述要求信息的整形数组;第二个参数 namelen 指针数组 name 长度;第三个参数用于存储输出数据,当函数返回时,请求的信息被赋值到变量;第四个参数是相应的输出数据大小。

输出数据的类型是 kinfo_proc 结构体包括另一个 extern_proc 型结构体(kp_proc),kp_proc 中间的字段 p_flag 标记当前过程是否被调试。

sysctl 反调试代码示例如下:

#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <sys/sysctl.h> #include <stdlib.h> static __attribute__((always_inline)) static int antidebug_sysctl(void) { int name[4]struct kinfo_proc info; size_t info_size = sizeof(info); info.kp_proc.p_flag = 0; name[0] = CTL_KERN; name[1] = KERN_PROC; name[2] = KERN_PROC_PID; name[3] = getpid();;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;if (sysctl(name,4,&info,&info_size,NULL,0) == -1) exit(-1); return ((info.kp_proc.p_flag & P_TRACED) !=

注:以上代码参考:https:// ** .coredump.gr/articles/ios-anti-debugging-protections-part-2/

c) 基于汇编实现 ptrace 系统调用

直接使用 ptrace 和 sysctl 函数符号的反调试容易被攻击者反调试,绕过防护。fishhook hook 上述 C 函数,修改代码逻辑,会导致保护失败。与显示函数符号的反调试相比,安全系数更高,不容易 hook 绕过。

用汇编实现 ptrace 系统调用代码如下:

static __attribute__((always_inline)) void antidebug_a ** _ptrace() {#ifdef __arm ** __ a ** volatile "mov x0,#26n" "mov x1,#31n" "mov x2,#0n" "mov x3,#0n" "mov x16,#0n" "svc #0x80n" ;#endif}

代码参考如下:https://github.com/vtky/ios-antidebugging

上述代码等于 syscall(26,31,0,0)ARM 在汇编过程中,内核入口通过 svc 指令完成,使用 svc 系统中断调用需要明确 3 点:中断号、系统调用号和参数。

2.2 反调试防护实践

在 App 中实现 ptrace 反调试后,在越狱环境中使用 debugserver 调试加固后的 App,会防止调试器挂载,产生段错误,效果如下:

ptrace 反调试

3. 签名信息验证

反向破解应用后,攻击者通常会重新包装并分发应用程序。App 签名信息是否完整,是一种必要的安全防护手段。常见的签名信息校验有以下几种方式:

a) 判断 App Bundle ID 是否被篡改

通过系统 API 读取 App Bundle ID,判断 Bundle ID 是否被篡改是最容易想到的方法。不幸的是,直接使用系统 API 容易被攻击者 hook 绕过检测;另外,使用通配符 App ID 可以重新签名 Bundle ID 不变,因此该方法不完全有效。

b) 读取 App 主目录下是否存在 embedded.mobileprovision 文件和文件中的签名信息

App 重新签名后,通常存在于主目录下embedded.mobileprovision 文件,解析 mobileprovision即使 文件,读取签名信息App Bundle ID 不变,也可以通过验证其他信息来判断 App 是否被篡改。但总有例外,一些工具重新签名 App 之后,主目录下没有 embedded.mobileprovision 文件,所以这种方法善。

c) 从 Mach-O 在可执行文件中读取 App 验证签名信息的完整性

与上述两种方法相比,从 Mach-O 在执行文件中读取签名信息是一种相对安全且不易破解的方法。Mach-O 在文件中读取签名信息的主要步骤如下:

i. 阅读应用程序可执行文件,获取文件头信息,包括程序加载指令集的数量和大小。

ii. 目前可指定文件的起始地址,加上文件头的大小,即 load com ** nd指令集的位置。根据第一步,从第一个文件信息中获得的指令集数量遍历载。每个指令集都有一个字段来表示当前指令集的类型,搜索类型是 LC_CODE_SIGNATURE 指令集,即代码签名指令集。

LC_CODE_SIGNATURE 类型的 Load Com ** nd

iii. 代码签名指令集中存储着签名数据的偏移量和数据大小,据此可以计算并定位代码签名数据段在文件中的位置。

代码签名信息

iv. 签名数据段起始部分为 CS_SuperBlob结构体,其中 ** gic 字段指明了Blob 数据类型,length 字段指明了 SuperBlob 大小,count字段指明了接下来会有多少个子条目。据此解析签名数据,即可获取应用标识符。

签名信息解析结果示例

简单使用上述一种方法检测 App 签名信息很容易被攻击者绕过破解,签名信息校验可以在程序的不同地方,组合使用上述校验方法,更大程序度上保证签名信息校验有效性。单纯使用签名信息实现 App 多开检测也不能保证完全有效,应尽可能使用多种检测手段,如SignerIdentity检测,使检测更加精确有效。

4. 异常数据检测及处理

除了代码混淆、反调试防护、签名信息校验等防护手段外,还可以通过检测 App 异常数据,上报至服务器端,作为业务风控参考指标。当 App 遭受注入、调试、钩子、篡改等攻击行为时,对用户发出警告,严重者封号,甚至直接终止 App 运行。

4.1 越狱设备检测

通常 App 逆向破解都是从越狱设备着手,因此检测设备是否越狱十分必要。越狱检测可以从以下方面实现:

a) 检测是否存在越狱相关的文件、路径,如:

/Library/MobileSubstrate/MobileSubstrate.dylib/Applications/Cydia.app、/usr/bin/cycript等。

MobileSubstrate.dylib越狱文件

b) 私有文件写入权限校验

越狱手机拥有 root 权限,可以往 App 沙盒外的目录写入文件。例如尝试在 /private 目录写入文件,如果文件创建成功,则说明设备已越狱,示例代码如下:

NSError *error;NSString *stringToBeWritten = @"This is a test.";[stringToBeWritten writeToFile:@"/private/jailbreak.txt" atomically:YESencoding:NSUTF8StringEncoding error:&error];if(error==nil){//Device is jailbrokenreturn YES;} else {//Device is not jailbroken[[NSFileManager defaultManager] removeItemAtPath:@"/private/jailbreak.txt" error:nil];}

注:代码引自:https://mobile-security.gitbook.io/mobile-security-testing-guide/

c) 越狱设备通常会安装 cydia 等工具,可以尝试判断能否打开 cydia scheme URL,检测设备是否越狱

4.2 App 加密检测

在苹果官方渠道下载的 App 都是加密的,而开发者重签名,二次打包后的 App 都未加密。解析 Mach-O 可执行文件中 LC_ENCRYPTION_INFO 类型的 Load Com ** nd,判断 App 是否加密,检测 App 是否来源是否合法。

otool 查看Mach-O 文件是否加密

4.3 动态库注入检测在越狱设备中,攻击者通常通过注入动态库,篡改应用逻辑。可以使用 getenv读取 DYLD_INSERT_LIBRARIES 环境变量是否有值,检测是否有注入动态库,如下图:

getenv 查看动态库注入

动态库注入检测还可以通过增加动态库白名单,使用 dyld_i ** ge_count 获取程序链接动态库数据个数,遍历数据读取映象名称(dyld_get_i ** ge_name),与白名单比对,检测是否有链接 MobileSubstrate.dylib、RevealServer等异常动态库。

4.4 异常处理

在检测到 App 异常行为后,可以上报至服务器端,结合业务应用场景,警告用户,禁用某些功能,甚至封号。

发现异常后,直接终止 App 运行,是一种简单粗暴有效的异常处理方式。终止 App 运行,可以通过抛出异常或调用 exit 函数实现。显示调用 exit 函数,容易被攻击者反向定位到程序退出位置,通过系统调用或内联汇编实现程序终止,是一种更高明的方式。

总结

本文结合 iOS 逆向破解中常用的技术手段,阐述了代码混淆、反调试、签名信息校验、异常数据检测及处理等安全防护原理与实践。iOS 系统并非想象的那么安全,开发者必须提高自己的代码安全意识,不能仅依赖 iOS 系统的安全措施。使用上述防护加固方法,在一定程度上可以增强 App 的安全性,防止攻击者轻易破解应用。但没有绝对的安全,混淆、加密、反调试等等加固手段只是安全防护的一道道锁,用来增加攻击者的破解难度。

单纯依靠客户端的加固,要实现 App 的安全防护也远远不够,安全更要从整个系统来考虑,前、后端协同努力,才能更大程度上保证系统安全。App 的安全加固更非一朝一夕就能实现,是在攻击与防护长期对抗中,发现问题,分析问题,解决问题,持续升级完善的,攻防相长,此之谓也。

参考文献

1. iOS 安全攻防:https://blog.csdn.net/yiyaaixuexi/article/category/1302847

2. 对 iOS App 进行安全加固:https://zhuanlan.zhihu.com/p/33109826

3. iOS App的加固保护原理:https://zhuanlan.zhihu.com/p/33109826

4. 浅谈关于iOS加固的几种方法:http:// ** .blogfshare.com/ios-protect.html

5. Mach-O文件格式:https://feicong.github.io/2017/01/13/ ** cho/

6. iOS 多开检测,反多开检测,反反多开检测:http://iosre.com/t/ios/11611

7. 关于反调试&反反调试那些事:http://iosre.com/t/topic/8179

8. 反调试与绕过的奇淫技巧:http://bbs.iosre.com/t/topic/9351

9. iOS Anti-Debugging Protections #1:https:// ** .coredump.gr/articles/ios-anti-debugging-protections-part-1/

10. iOS Anti-Debugging Protections #2:https:// ** .coredump.gr/articles/ios-anti-debugging-protections-part-2/

11. Bugging Debuggers:https:// ** .theiphonewiki.com/wiki/Bugging_Debuggers

12. Mobile Security Testing Guide: https://mobile-security.gitbook.io/mobile-security-testing-guide/

作者简介

贾学文,58 同城用户价值增长部高级研发工程师,主要负责 App 安全加固及暗黑模式适配研发工作。

扫码免费用

源码支持二开

申请免费使用

在线咨询