某金融APP防护检测分析
文章目录
- 1. 写在前面
- 2. 反编译防护
- 3. 反调|伪造检测
- 4. Frida检测防护
- 5. 流程加固
- 6. 文件完整性与抓包防护
- 7. Root三要素检测
【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章
作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!
1. 写在前面
随着移动应用安全威胁日益复杂,APP加固技术已成为抵御逆向分析、恶意篡改、数据窃取的核心手段。本文聚焦某安全厂商APP加固方案,从源码层深入拆解其核心防护体系,涵盖SSL
抓包拦截、Root
环境识别、文件完整性校验、Frida
调试检测、反编译障碍设置等关键防护点,通过对各防护机制的实现逻辑、检测策略及防御强度分析
2. 反编译防护
记录一下某安全厂商下APP加固各项检测防护点。从源码层面分析大致包括了:ssl抓包检测
、Root检测
、文件完整性校验
、frida检测
、反编译防护...
,发现它这个bitcode
通过跳表计算出寄存器
的地址,如下所示:
ida_bytes.get_qword(0x100059A10 + 0x7 * 8) + 0xFFFFFFFFAC826A8F & 0xFFFFFFFFFFFFFFFF
接下来从下面的代码片段来看,属于字符串加密(可能是宏定义实现
)通过调用函数str_decrypt9
来实现的运行时字符串加解密
,传入了加密后的字符串asc_1000610B0、unk_100048D2C
和长度参数4、7u
__int64 __fastcall str_decrypt9(__int64 result, int a2, __int64 a3, unsigned int a4)
{int v4; // w8,用于临时存储 i 的高 32 位__int64 i; // 循环变量,64 位,低 32 位/高 32 位会被拆分使用for ( i = 0LL; ; LODWORD(i) = (v4 + 1) % a4 ){v4 = HIDWORD(i); // 取 i 的高 32 位 → 作为 result 的偏移索引if ( HIDWORD(i) == a2 ) // 高 32 位等于 a2 时终止循环break;++HIDWORD(i); // 高 32 位自增(类似索引 +1)// 核心异或:result[v4] ^= a3[(int)i]*(BYTE *)(result + v4) ^= *(BYTE *)(a3 + (int)i);}
}
自定义的一个xor
加密函数,通过复用64
位变量i
的高地位,实现的明文到密文按a2
长度处理、密钥按a4
长度循环的xor
加解密
3. 反调|伪造检测
接下来看位置防伪造的一个地方,load
方法是一个加载阶段的自执行函数
,优先级特别高(一般在程序启动、main
函数之前触发)一般像方法的hook
、初始化各种黑科技逻辑常用,代码如下:
void __cdecl +[CLLocationManager load](id a1, SEL a2) {_QWORD block[5]; InitFunc_0(a1, a2);// 构造一个栈 block(iOS 中 block 是闭包,这里手动构造内存布局)block[0] = _NSConcreteStackBlock; block[1] = 3254779904LL; block[2] = hook_CLLocationManager_delegate_block; // 关键:hook 的 block 逻辑block[3] = &unk_10004C758; block[4] = a1; // dispatch_once 保证逻辑只执行一次(典型单例式 hook 注入)if ( qword_100062A88 != -1 ) dispatch_once(&qword_100062A88, block);
}
CLLocationManager
是iOS
中取设备位置(GPS
)的核心类,这段代码在+load
里通过dispatch_once
注入了一个block
,拦截CLLocationManagerDelegate
的回调,篡改真实位置或检测位置是否被伪造(如: locationManager:didUpdateLocations:
)
再接着看下面第二段代码,GNBrandDirectory
的+load
,如下:
void __cdecl +[GNBrandDirectory load](id a1, SEL a2) {id v2; void *v3; InitFunc_12(a1, a2);// 初始化一个可变字典(NSMutableDictionary)v2 = objc_msgSend(objc_alloc((Class)&OBJC_CLASS__NSMutableDictionary), "init"); v3 = (void *)qword_100062E80; qword_100062E80 = (_int64)v2; // 把字典存到全局变量objc_release(v3);
}
在+load
里初始化了一个NSMutableDictionary
并存在全局变量,类似预存配置 / 标记位
,应该是用来存储是否启用了位置的校验开关
或缓存合法位置的特征(用于后续对比伪造数据
)
往下看防伪造的第三段关联代码,GNProductRecommendation
的+load
,如下所示:
void __cdecl +[GNProductRecommendation load](id a1, SEL a2) {dispatch_time_t v2; id v3; dispatch_time_t v4; // 延迟 0.8 秒(dispatch_time 单位是纳秒,8e8 纳秒=0.8 秒)v2 = dispatch_time(0LL, 800000000LL); v3 = objc_autoreleasePoolPush(); // 延迟执行一个空函数(可能是占位,或者隐藏真实逻辑)dispatch_after(v2, (dispatch_queue_t)&dispatch_main_q, &stru_10004CC78); v4 = dispatch_time(0LL, 1000000000LL); // 延迟 1 秒// 延迟执行 sub_1000184A0(关键逻辑!)dispatch_after(v4, (dispatch_queue_t)&dispatch_main_q, &stru_10004CCBB);
}int64 sub_1000184A0() {NSProcessInfo *v0; NSDictionary *v1; id v3; _int64 v4; // 获取进程信息(NSProcessInfo),常用来检测环境(是否越狱、是否被调试)v0 = objc_retainAutoreleasedReturnValue(objc_msgSend(&OBJC_CLASS__NSProcessInfo, "processInfo")); // 获取进程环境变量(可能检测是否有调试器、hook 工具注入)v1 = objc_retainAutoreleasedReturnValue(objc_msgSend(v0, "environment")); objc_release(v0); if ( !v1 ) goto LABEL_7; if ( !atomic_load((unsigned int *)&dword_100062B04) ) {pthread_mutex_lock(&stru_10005AB48); if ( !dword_100062B04 ) {atomic_store(1u, (unsigned int *)&dword_100062B04); // 这里调用了 sub_1000187F4,可能是“校验逻辑”(比如位置合法性)sub_1000187F4((__int64)aF, 34, (__int64)&unk_1000480A6, 6u); }pthread_mutex_unlock(&stru_10005AB48); }// 从字典里取关键值(可能是位置相关标记)v3 = objc_retainAutoreleasedReturnValue(objc_msgSend(v1, "objectForKeyedSubscript:", CFSTR("(exA9\x10\xc8\x73.vA2\x0e\xd6\x61\"j\xA9\x05\xd3\x72.e\x82\x0e\xCF\x72*j\xA5\x10\xD8\x74'\x88\x02)"))); objc_release(v1); if ( !v3 ) {v4 = 0xFFFFFFFFLL; } else {// ... 后续逻辑(根据取值判断是否伪造)objc_release(v3); v4 = 0LL; }
LABEL_7:objc_release(v1); return v4;
}
+load
里用dispatch_after
延迟执行逻辑,再看sub_1000184A0
这里通过NSProcessInfo
获取进程环境(检测是否被调试、越狱
,因为伪造位置工具常伴随越狱环境),用atomic_load/pthread_mutex
做了一个线程安全的标记位
校验,最后从字典中取key
(特征值做比对是否篡改)
整个过程就是先检测运行环境是否安全(被越狱
、注入hook
)再校验位置数据的合法性,发现即阻断
(典型的IOS
逆向对抗场景)
4. Frida检测防护
这里我们再来看看分析一下这个APP针对Frida
的检测防护逻辑,整个核心围绕拦截Frida特征线程名+启动期监控
来阻止动态调试。核心对抗逻辑hook_pthread_setname_np
函数,如下所示:
代码里硬编码了frida-gadget-tcp-、frida-gadget、pool-frida
等线程名的特征(通过CFSTR
存二进制字符串)Frida
注入后,会创建带这些关键字的线程,一旦检测到线程名匹配,直接调用call_exit()
终止进程,让Frida
无法附着调试
void __cdecl +[GNTaskScheduler load](id a1, SEL a2) {...// 异步执行其他二次校验逻辑dispatch_async(dispatch_get_global_queue(0LL, 0LL), &stru_10004CAE8);
}
启动期注入GNTaskScheduler + load
方法,hook pthread_setname_np
刚设置就会被拦截,监听UIApplicationDidFinishLaunchingNotification
,确保APP启动后继续监控线程(防止Frida
后期注入)完成启动后的监控
Frida
端口检测,通过连接本地特定端口发送auth
特征字符串数据,判断是否存在调试环境。如下所示:
Frida
内存扫描检测,通过dlsym
查找系统函数、结合原子操作标记。实现对Frida
相关内存区域的扫描,如下所示:
vm_region_recurse*
属于XNU
内核的内存区域查询函数,Frida
会通过这些函数遍历内存,查找可注入的代码段
getsectbynamefromheader*
用于解析Mach-O
二进制的段信息,注入时会修改这些段
bool sub_10003BC4C() {return NSClassFromString(&CFSTR("DobbyHooked")) != 0LL;
}
如上,还检测了是否存在Dobby
框架的Hook
的行为,NSClassFromString
是Objective-C
中的一个函数,通过判断NSClassFromString(&CFSTR("DobbyHooked"))
的返回值是否为0LL
(即nil)
完整的防护链路:Frida注入特征
-Hook拦截
-特征匹配
-启动后监控
5. 流程加固
__int64 __fastcall start(int a1, char **a2) {// 1. 自动释放池入栈(OC 内存管理基础)void *v4 = objc_autoreleasePoolPush(); // 2. 插入的核心防护逻辑!在 main 函数前执行call_from_main(); // 3. 常规启动流程:找 AppDelegate + 启动 UIApplicationobjc_class *v5 = objc_opt_class(&OBJC_CLASS__AppDelegate); NSString *v6 = NSStringFromClass(v5); __int64 v7 = UIApplicationMain(a1, a2, 0LL, v6); // 4. 释放资源objc_release(v6); objc_autoreleasePoolPop(v4); return v7;
}
IOS程序的启动流程main->UIApplicationMain
,这里直接在start
比客户main
更早的入口采用前置注入call_from_main()
(启动逻辑前强制执行防护代码
)
void call_from_main() {// 1. 注册通知回调(QingChangSDKCallback)add_notification_qiangchangcallback(); // 2. 界面加载校验(ViewController 的 viewDidLoad CRC 校验)add_check_ViewController_viewDidLoad_crc(); ...
}
通知中心埋雷(add_notification_qiangchangcallback
)
// 伪代码还原逻辑:
void add_notification_qiangchangcallback() {NSNotificationCenter *center = [NSNotificationCenter defaultCenter];// 注册一个“清场”的通知,触发时执行 block[center addObserverForName:@"QingChangSDKCallback" object:nil queue:nil usingBlock:^(NSNotification *note) {// 关键点:block 里判断条件,不符合则调用 exit!if (...) { call_exit(); }}];
}
如上整个串起来就是一个自定义引爆点
,APP内部的某些敏感操作都会通知,如果hook
了某些关键函数(篡改相关
)触发通知block
校验失败直接就会exit
界面 CRC 校验(add_check_ViewController_viewDidLoad_crc
)
// 伪代码还原逻辑:
void add_check_ViewController_viewDidLoad_crc() {// 1. 获取所有 ViewController 类NSArray *allVCs = ...; // 遍历 runtime 获取所有 VC 类for (Class vcClass in allVCs) {// 2. 获取 viewDidLoad 方法的实现地址IMP imp = class_getMethodImplementation(vcClass, @selector(viewDidLoad)); // 3. 计算该方法的 CRC 值(或哈希)uint32_t crc = crc32(0, imp, method_size); // 4. 对比预先存储的合法 CRCif (crc != precomputed_crcs[vcClass]) { call_exit(); // 校验失败,退出}}
}
这里对所有ViewController
的viewDidLoad
方法做CRC
校验,确保方法实现没被篡改,一旦发现hook
直接杀进程
固定哈希可能是AppDelegate.application:didFinishLaunchingWithOptions
的特征值,哈希比对检测是否被hook
以及参数是否合法、设备是否安全有无风险。一旦校验失败,后续的GNTaskScheduler
还会做投屏检测(airplay
)跟界面劫持检测(validateInputFields_GNSensorManager
)形成多层全面的防护层层嵌套(绕过一层还有一层...
)
继续看下一段代码,IOS
应用加固中动态库
&内存校验
的核心逻辑。通过构建校验函数数组
在运行时批量检测逆向痕迹
if (!qword_1000630D8) {v13 = calloc(1ULL, 0x90uLL); // 分配内存存校验函数指针// 构建校验函数数组(每个元素是一个检测逻辑)v13[0] = sub_10003ABB0; // 校验应用名称v13[1] = get_result_from_statusCode_; v13[2] = check_frida_crypt_MobileSubstrate; // 检测 Frida/MobileSubstratev13[3] = check_ImageNames_MobileSubstrate; // 检测动态库注入v13[4] = check_objc_copyImageNames; // 检测 Objective-C 类篡改v13[5] = frida_crypt_check; // Frida 加密特征检测v13[6] = get_sysctl_tracepid; // 获取进程跟踪信息v13[7] = check_dlsym_sysctl_syscall_issafe; // 检测 dlsym/hook 风险v13[15] = check_netproxy_getifaddrs; // 网络代理/抓包检测v13[8] = check_netproxy_; // 网络代理校验v13[9] = compare_mainBundle_exec_text_crc_; // 主二进制 CRC 校验// ... 其他校验函数(包括ssl ping抓包一系列相关的)qword_1000630D8 = (__int64)v13; // 全局变量存储,确保只初始化一次
}
如上E函数指针数组驱动的批量检测
,把各类防护逻辑封装成函数,集中初始化并执行。一旦有任意一个校验失败,后续极可能触发exit
终止进程
check_frida_crypt_MobileSubstrate
跟frida_crypt_check
作用检测Frida
注入,会在内存中留下特定代码段、线程名,这些函数会扫描内存特征、动态库列表,匹配则判定为攻击
MobileSubstrate
动态库(Hook框架
)检测其加载的动态库(libsubstrate.dylib
)
compare_mainBundle_exec_text_crc_
主二进制文件的CRC
校验,计算mainBundle
可执行文件的CRC或者哈希
与预存的合法值对比。若被篡改(二次打包、植入代码
)校验失败
check_netproxy_
跟check_netproxy_getifaddrs
检查设备是否配置代理(防止中间人攻击、抓包分析
)检测是否有异常网络配置(VPN、虚拟网卡
)
总结下来这一套防护就是:反注入、反篡改、反调试
组合拳。要逆向绕过的话需要重点patch
这些校验函数的逻辑!而且它这个防护函数分散在多个二进制段且存在加密校验(难度不低
)
6. 文件完整性与抓包防护
CFSTR("...")
是HTTPSPort
的二进制编码用来检测代理端口,启用的条件如下所示:
// v3(HTTP 代理开关)为 true
v7 = v3 && objc_msgSend(v4, "length")
针对ssl
抓包的检测,验证ssl
证书的合法性,检测证书是否被中间人工具替换,如下所示:
解析CodeResources
并校验文件哈希完成文件完整性校验来防止文件被篡改,代码片段如下所示:
// 简化后的逻辑流程
void sub_1000386D8() {// 1. 获取主 Bundle 的资源路径NSBundle *mainBundle = [NSBundle mainBundle];NSString *resourcePath = [mainBundle resourcePath];// 2. 拼接 CodeResources 路径(_CodeSignature/CodeResources)NSString *codeResourcesPath = [resourcePath stringByAppendingPathComponent:@"_CodeSignature/CodeResources"];// 3. 加载 CodeResources 文件(plist 格式,存储资源文件的哈希值)NSMutableDictionary *codeResources = [NSMutableDictionary dictionaryWithContentsOfFile:codeResourcesPath];// 4. 遍历需要校验的文件列表(files2 或 _CodeSignature/CodeResources 中的文件)NSArray *filesToCheck = sub_1000386D8(/* 传入文件列表参数 */);for (NSString *filePath in filesToCheck) {// 5. 获取文件的实际哈希值(通过某种哈希算法计算)NSString *actualHash = calculateFileHash(filePath);// 6. 获取 CodeResources 中预存的哈希值NSString *expectedHash = codeResources[filePath];// 7. 对比哈希值,不一致则判定文件被篡改if (![actualHash isEqualToString:expectedHash]) {// 触发防护逻辑(如退出应用、上报异常)exit(0);}}
}
7. Root三要素检测
通过枚举越狱特征文件、库、函数构建行为特征库
,精准识别设备是否越狱
// 返回的文件列表是经典越狱标识
return NSArray ("/Applications/Cydia.app","/Applications/Sileo.app","/var/binpack","/Library/MobileSubstrate/DynamicLibraries","/Library/PreferenceBundles/LibertyPref.bundle","/Library/PreferenceBundles/ShadowPreferences.bundle","/Library/PreferenceBundles/ABypassPrefs.bundle","/Library/PreferenceBundles/FlyJBPrefs.bundle","/usr/lib/libhooker.dylib","/usr/lib/libsubstitute.dylib","/usr/lib/substrate","/usr/lib/TweakInject"
)
// 检测的库名是越狱框架核心组件
return NSArray (MobileSubstrate,TweakInject,libhooker,substrate,SubstrateLoader,SubstrateInserter,SubstrateBootstrap,ABypass,FlyJB,substitute,Cephei,rocketbootstrap,Electra
)
// 检测的函数是 hook 框架的核心 API
return NSArray (MSHookFunction, // MobileSubstrate 的函数 hook APIMSHookMessageEx, // MobileSubstrate 的 Objective-C 方法 hook APIMSFindSymbol, // MobileSubstrate 的符号查找 APIMSGetImageByName, // MobileSubstrate 的动态库查找 APIZzBuildHook, // 早期越狱工具的 hook 函数DobbyHook, // Dobby 框架的 hook API(跨平台)LHHookFunctions // 其他 hook 框架的 API
)