导语
本文结合 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,会防止调试器挂载,产生段错误,效果如下:
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 指令集,即代码签名指令集。
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等。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 是否来源是否合法。
4.3 动态库注入检测在越狱设备中,攻击者通常通过注入动态库,篡改应用逻辑。可以使用 getenv读取 DYLD_INSERT_LIBRARIES 环境变量是否有值,检测是否有注入动态库,如下图:
动态库注入检测还可以通过增加动态库白名单,使用 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 安全加固及暗黑模式适配研发工作。
Copyright © All rights reserved | Colorlib 沪ICP备2021024381号-16
扫码咨询与免费使用
申请免费使用