文章总结: 本文剖析某iOSAPP的反调试机制,展示从C函数Hook到SVC系统调用层的绕过过程。作者利用IDA定位检测frida-server路径的系统调用并Hook,同时处理UIAlertView限制及XOR字符串混淆。文章总结了多层防御的对抗经验,提供了实用的逆向分析方法。 综合评分: 90 文章分类: 移动安全,逆向分析,免杀
某APP样本安全检测绕过:小试SVC系统调用HOOK
原创
CFC4N
榫卯江湖
2026年1月7日 08:08 北京
#
目标样本: 某 iOS APP 对抗难度: ⭐⭐ 关键技术: Frida检测 | 系统调用Hook | 字符串混淆破解
🗺️ 技术路径导航
本文展示了一个完整的iOS应用反调试绕过流程,从应用层到系统调用层的深入分析:
📍 分析路径
├─ 0x00 砸壳 (工具切换策略)
├─ 0x01 常规检测绕过 (C函数Hook)
├─ 0x02 深入系统调用层 (SVC指令分析) ⭐ 核心技术点
├─ 0x03 弹窗拦路虎 (UI层绕过)
├─ 0x04 隐藏的彩蛋 (字符串混淆解密)
└─ 0x05 总结与思考 (攻防技术栈对比)
🔄 整体攻防流程图
0x00 砸壳
拿到样本第一件事当然是砸壳,老规矩掏出frida-ios-dump[1],结果……APP 直接给我来了个闪退暴击。
症状:
- 不执行 Frida:APP 正常启动 ✅ (弹窗提示越狱)
- 执行 Frida:闪退 ❌
果然,有Frida反调试检测。但没关系,咱还有Plan B。换用appdecrypt[2]项目,基于调试器原理的静态砸壳工具,成功拿到脱壳二进制文件 🎉
于是我们就可以基于frida检测绕过做一次简单的分析。
0x01 常规检测绕过
首先通过一些常规检测手法作为锚点,定位一些反调试检测的代码片段,前面不执行frida时候,会弹窗提示越狱。所以把样本扔进 IDA ,将常规的越狱特征字符串一顿搜索,果然发现了一些代码:
🏗️ 检测层级架构
应用采用了多层次的检测策略,从上层C库函数到底层系统调用:
💡 分层防御策略:当C库函数被Hook后,syscall层仍可继续检测
发现的检测点
代码片段里大量使用access()和stat()函数检测这些路径:
/Applications/Cydia.app
/Applications/Sileo.app
/Applications/blackra1n.app
/Library/MobileSubstrate/MobileSubstrate.dylib
/usr/sbin/sshd
/bin/bash
/private/var/tmp/cydia.log
...
初步绕过尝试
写了个 Frida 脚本 Hook 这些文件操作函数,打印其参数,设置白名单过滤正常路径,关键代码如下:
function iswhite(path) {
if (path == null) returntrue;
if (path.startsWith('/var/mobile/Containers')) returntrue;
if (path.startsWith('/System')) returntrue;
if (path.startsWith('/usr')) returntrue;
// ... 更多白名单规则
returnfalse;
}
Interceptor.attach(Module.findExportByName(null, "stat"), {
onEnter: function(args) {
if (args[0].isNull()) return;
var path = args[0].readUtf8String();
if (iswhite(path)) return;
console.log("stat " + path);
this.replace = 1; // 标记需要替换返回值
},
onLeave: function(retval) {
if(this.replace) {
retval.replace(-1); // 返回文件不存在
}
}
});
// 同样 Hook access、lstat 等函数...
运行效果
成功拦截到大量文件操作函数的调用:
stat /Applications/Cydia.app
stat /Applications/Sileo.app
stat /Library/MobileSubstrate/MobileSubstrate.dylib
stat /bin/bash
lstat /Applications
...
但是!APP 依然闪退 😅
分析: 显然对方不只是用高层 C 库函数,肯定还有更底层的检测手段。
0x02 深入系统调用层
🔍 SVC指令分析流程
定位SVC指令
由于app用了一些上层的文件操作函数进行特征检测,怀疑其也会使用系统调用的方式进行检测,那就从汇编层面入手。写了个 IDAPython 脚本扫描所有SVC指令(ARM64 的系统调用指令):
import ida_bytes, ida_ida, ida_segment
import idaapi, idautils, idc
def getAddrRange():
# 限定在 __text 段
for seg in idautils.Segments():
seg = idaapi.getseg(seg)
segName = ida_segment.get_segm_name(seg)
if"text"in segName:
return seg.start_ea, seg.size()
start, size = getAddrRange()
for addr in idautils.Heads(start, start+size):
mnem = idc.print_insn_mnem(addr)
if mnem == "SVC":
print(hex(addr), idc.GetDisasm(addr))
扫描结果
发现了100+ 处SVC 指令!继续输出它们的系统调用号(从X16寄存器获取):
addr_list = [0x10009a330, 0x10009a500, 0x10009a534, ...] # 100+ 地址
for i in addr_list:
print(hex(i-8), idc.GetDisasm(i-4)) # 查看 MOV X16, #xxx 指令
解析系统调用类型
看到了这些系统调用号:
0x21→access()调用0x159→statfs64()调用0xC6→ptrace()调用- …
具体调用关系可以转成10进制后查看,apple提供了相关文档。📚 参考文档:Apple XNU syscalls.master[3]
逐个Hook验证
对每个 SVC 地址进行 Hook,打印它们的入参:
// 💡 关键技术点:
// • base.add() - 计算实际内存地址(基址+偏移)
// • this.context.x0 - 获取第一个参数(通常为路径字符串指针)
// • readCString() - 读取C风格字符串
// • 通过修改返回值或参数值使检测失败
...
Interceptor.attach(base.add(0x1648acc), {
onEnter: function (args) {
console.log("syscall access 0x1648acc", this.context.x0.readCString());
}
})
Interceptor.attach(base.add(0x16495a0), {
onEnter: function (args) {
console.log("syscall access 0x16495a0", this.context.x0.readCString());
}
})
Interceptor.attach(base.add(0x1649b80), {
onEnter: function (args) {
console.log("syscall access 0x1649b80", this.context.x0.readCString());
}
})
Interceptor.attach(base.add(0x164a33c), {
onEnter: function (args) {
console.log("syscall access 0x164a33c", this.context.x0.readCString());
}
})
Interceptor.attach(base.add(0x164adb4), {
onEnter: function (args) {
console.log("syscall access 0x164adb4", this.context.x0.readCString());
}
})
Interceptor.attach(base.add(0x164b314), {
onEnter: function (args) {
console.log("syscall access 0x164b314", this.context.x0.readCString());
}
})
Interceptor.attach(base.add(0x164c090), {
onEnter: function (args) {
console.log("syscall access 0x164c090", this.context.x0.readCString());
}
})
Interceptor.attach(base.add(0x164db20), {
onEnter: function (args) {
console.log("syscall access 0x164db20", this.context.x0.readCString());
}
})
Interceptor.attach(base.add(0x164e400), {
onEnter: function (args) {
console.log("syscall access 0x164e400", this.context.x0.readCString());
}
})
Interceptor.attach(base.add(0x24081c0), {
onEnter: function (args) {
console.log("syscall access 0x24081c0", this.context.x0.readCString());
}
})
Interceptor.attach(base.add(0x2408c2c), {
onEnter: function (args) {
console.log("syscall access 0x2408c2c", this.context.x0.readCString());
}
})
....
🎯 致命发现
在地址0x15f5328和0x2408c2c的 syscall 调用中,捕获到了这个入参:
/usr/sbin/frida-server
真相大白!原来是在 syscall 层直接检测 frida-server 进程路径。所以我们直接这个地址进行hook,传入一个虚假的path后,再次打开应用,应用不在闪退,证明成功饶过。
0x03 弹窗拦路虎
虽然不闪退了,但 APP 弹出”检测到越狱环境”的提示框,依然无法进入主界面。这说明还有其他的越狱检测点我没有绕过,但是由于应用不点击确认不会闪退,因此我们可以不去分析其他越狱特征,仅尝试饶过这个弹窗限制
Hook UIAlertView
var UIAlertView = ObjC.classes.UIAlertView['- show'];
Interceptor.attach(UIAlertView.implementation, {
onEnter: function(args) {
console.log('UIAlertView triggered!');
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n'));
}
});
追溯调用栈
通过堆栈回溯,定位到弹窗触发的业务逻辑函数:
代码逻辑很清晰,就是一个越狱状态判断 → 弹窗的流程。我们patch掉这个弹窗,成功在执行Frida的同时进入APP!
0x04 隐藏的彩蛋
在分析过程中发现一个困惑的点:在 IDA 的字符串列表里搜索frida-server,什么都没找到!但是在上面syscall 调用中却有这个入参。
定位检测地址,查看参数:
入参伪代码如下 ,原来是个简单的 XOR 混淆:
if ((byte_103C6B5BB[0] & 1) != 0) {
for (i = 0; i != 23; ++i)
byte_104528523[i] = byte_103C6B5BB[i + 1] ^ (i + 100);
byte_103C6B5BB[0] = '4';
}
解密逻辑
encrypted = [0x66, 0x72, 0x69, 0x64, 0x61, ...] # 从二进制中提取
decrypted = ""
for i in range(23):
decrypted += chr(encrypted[i] ^ (i + 100))
print(decrypted) # 输出:/usr/sbin/frida-server
🔐 XOR混淆可视化流程
原始加密数据 (byte数组):
┌─────┬─────┬─────┬─────┬─────┬─────────┐
│ 0x66│ 0x72│ 0x69│ 0x64│ 0x61│ ... │
└──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴─────────┘
│ XOR │ XOR │ XOR │ XOR │
│ ↓ │ ↓ │ ↓ │ ↓ │ ↓
│ 100 │ 101 │ 102 │ 103 │ 104 (密钥: i+100)
└──┬──┴──┬──┴──┬──┴──┬──┴──┬──
↓ ↓ ↓ ↓ ↓
'/' 'u' 's' 'r' '/' ...
解密结果: /usr/sbin/frida-server
设计目的: 避免静态分析时被字符串搜索直接定位。
0x05 总结与思考
攻防技术栈对比
| 🛡️ 防护手段 | ⚔️ 绕过方案 | 🎯 难度 | | — | — | — | | Apple Store应用加密 | appdecrypt[4] 砸壳 | ⭐ | | 文件操作函数检测越狱等基础风险特征 | Hook C函数返回假值 | ⭐⭐ | | 系统调用检测Frida特征 | Hook SVC指令地址 | ⭐⭐⭐ | | 越狱检测弹窗 | Hook弹窗代码逻辑,绕过即可 | ⭐⭐ | | 静态分析字符串加密 | 分析解密函数/动态调试 | ⭐⭐ |
防御者视角建议
- 多层检测叠加: 仅靠单一层次的检测容易被针对性绕过
- 关键逻辑加固: syscall 层检测应配合代码混淆、完整性校验
- 行为分析为主: 静态特征检测终将被绕过,转向运行时行为监控
攻击者视角经验
- 工具链冗余: Frida 被杀?试试其他调试工具!
- 分层Hook策略: 从上到下逐层下探(ObjC → C → syscall → 内联汇编)
- 动态调试优先: 字符串混淆、控制流混淆等静态难题,动态调试一览无余
- 耐心是美德: 现代 APP 保护往往是10+ 种对抗手段的组合,慢慢攻克
0x06 后续研究方向 🔬
- 进一步分析其他反调试检测逻辑
- 研究其网络层是否有额外的设备指纹检测
免责声明: 本文仅供安全研究和技术交流,请勿用于非法用途。
0x07 工作机会
公司部门介绍
美团信息安全部,城市可选北京、上海。 公众号留言,即可直接投递简历。
岗位名称
反爬蓝军对抗专家
岗位职责
- 参与日常红蓝对抗演练活动,分析防守方薄弱点,以攻促防。
- 研究反爬领域的对抗技术,从攻防视角设计方案,持续提高反爬水位。
- 参与体系化对抗系统建设、自动化武器设计与对内部赋能。
- 业界爬虫前沿对抗思路研究、探索、设计、落地。
岗位基本要求
- 本科及以上学历,网络安全,计算机相关专业,熟悉android、iOS开发和调试。
- 精通爬虫客户端对抗思路,包括不限于APP、浏览器、小程序等多客户端,了解客户端的指纹实现,会话认证机制,点击触摸模拟、人机识别(图形、语音)。
- 精通反爬系统风控策略,从协议、行为模拟、真人化、好人化等多角度识别定位绕过防御系统。
- 熟悉Android Hook原理,熟悉常见Xposed、LSPosed、Magisk、Frida等HOOK工具。
- 掌握常见的动态静态分析技巧,熟练使用IDA、Ghidra、Jeb和Jadx等常用工具对程序进行分析。
- 熟悉iOS客户端对抗知识,掌握软件静态分析、动态调试、协议抓包、HOOK技术原理、HOOK框架应用。
参考资料
[1]
frida-ios-dump: https://github.com/AloneMonkey/frida-ios-dump
[2]
appdecrypt: https://github.com/paradiseduo/appdecrypt
[3]
Apple XNU syscalls.master: https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master
[4]
appdecrypt: https://github.com/paradiseduo/appdecrypt
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:榫卯江湖 CFC4N《某APP样本安全检测绕过:小试SVC系统调用HOOK》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论