当前位置: 首页 > news >正文

某金融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); 
}

CLLocationManageriOS中取设备位置(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的行为,NSClassFromStringObjective-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(); // 校验失败,退出}}
}

这里对所有ViewControllerviewDidLoad方法做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_MobileSubstratefrida_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
)
http://www.dtcms.com/a/322632.html

相关文章:

  • PromptPilot打造高效AI提示词
  • 智慧农业-无人机视角庄稼倒伏农作物倒伏检测数据集VOC+YOLO格式541张1类别
  • 计算机视觉CS231n学习(6)
  • 跨境电商系统开发:ZKmall开源商城的技术选型与代码规范实践
  • 3D感知多模态(图像、雷达感知)
  • node.js 零基础入门
  • LangChain-Unstructured 基础使用:PDF 与 Markdown 处理解析
  • SwiftUI 登录页面键盘约束冲突与卡顿优化全攻略
  • 为什么动态导入中Vite无法正确解析别名路径?
  • 如何在 Excel 中快速求和?【图文详解】Excel求和技巧,Excel求和公式大全,多种方式求和
  • 【线性代数】6二次型
  • 【线性代数】目录
  • 【线性代数】线性方程组与矩阵——(2)矩阵与线性方程组的解
  • sqli-labs靶场less51~less65
  • Debian防火墙 ufw
  • DataDex 多样化 JSON 服务——使用教程
  • K8s-pod控制器
  • Web前端之Vue框架
  • Java Stream API 实战:提升集合处理的效率与可读性!
  • 使用 Visual Studio 2022 编译 PortAudio 项目
  • 华为实验NAT
  • spring.config.import 不存在
  • 文生图工具之ComfyUI从原理到实践的全维度剖析
  • 矩阵的条件数 向量的条件数
  • 【面试场景题】通过LinkedHashMap来实现LRU与LFU
  • 时序分解 | MATLAB实现SAO-VMD雪消融算法优化变分模态分解
  • 使用流式函数解决v语言zstd程序解压缩失败问题
  • react 常用组件库
  • 下肢康复机器人机械结构设计cad【6张】三维图+设计说明说书
  • web应用服务器tomcat