论坛首页 逆向工程技术区 阅读主题

[原创]Frida企业级加固对抗实战: 通过 mmap/mprotect 钓出壳隐藏的真实 SO

368 浏览 22 回复
#1 楼主 2026-06-01 21:09:09
今天在对某企业级 Android 加固方案进行逆向分析时,我通过 Frida 注入开展动态调试,过程中发现了一个明显违背 ELF 标准的反常现象:通过 dlopen 遍历已加载 SO 时,打印某 SO 的基地址与 JNI_OnLoad 函数地址后,我发现该 JNI_OnLoad 地址完全不属于该 SO 的内存区间,甚至出现了函数地址 < 模块基址的反常现象,严重违背 ELF/Linker 标准的内存布局规则。深入排查后最终定位到根因:该加固壳劫持了系统 linker64 链接器、Hook 了 dlopen 系列函数,通过伪造虚假的 SO 信息(向 Frida 等调试工具暴露一个看似正常的 “壳 SO”),将真正的业务 SO 隐藏在内存中、绕开系统 linker 私自加载,以此实现反调试、反逆向的防护效果。本文将详细拆解这类企业级加固的对抗思路,从底层原理、脚本编写、动态调试到脱壳还原,手把手教大家如何通过 Frida 从内存中精准 “钓” 出被壳隐藏的真实业务 SO,完成脱壳还原。全程实操为主、原理为辅,文中所有脚本均已脱敏处理,可直接复用。水平有限,文中若有疏漏或错误,恳请各位大佬斧正。

本文用到的工具和前置知识:1.mmap的分配原理和mmproct函数的权限修正:     东方玻璃大佬的底层知识讲解一直十分之详细,大家可以参考[原创]Android从ELF-Loader到自定义Linker的实现及原理-Android安全-看雪安全社区|专业技术交流与安全研究论坛
2.Frida stalker :Frida 是本次动态调试、Hook 系统函数的核心工具,需要大家熟悉 Frida 的基本注入命令、Interceptor 钩子用法、Module 相关 API;Frida Stalker 是 Frida 内置的高性能指令级追踪引擎,也是本次验证脱壳正确性的关键工具,需要了解其基本的追踪配置、指令捕获逻辑。
3.ELF 文件结构基础:需要了解 SO 文件的基本结构,包括导出表、代码段(text)、数据段(data)、soinfo 结构体的基本作用,明白符号表查询的基本流程,这样才能理解“导出表异常“地址倒挂”等反常现象的本质。
哈哈   发现本文用到的知识点上面东方玻璃的大佬刚刚发的的帖子都有讲到,大家可以看看获取一款企业级加固安卓 APP 后,我通过 Frida 对进程进行注入,Hook 系统dlopen函数枚举加载的 SO 模块,成功定位到目标加固 SO。该 SO 采用典型的企业级加固方案,也是很经典的,加载完一个so就被杀进程了

进程崩溃后,首要任务是定位崩溃的核心位置。熟悉 Android SO 加载流程的朋友都知道,JNI_OnLoad 是 Android SO 库被系统加载时自动执行的第一个核心入口函数,也是企业级加固放置反调试、完整性校验、进程强杀等防护逻辑的关键位置,属于逆向分析中必查的核心节点。因此,我优先针对 JNI_OnLoad 函数的执行流程进行排查,核心思路是:通过 Frida 挂钩 JNI_OnLoad 的 onEnter(函数进入)和 onLeave(函数退出)两个关键时机,并打印日志,以此验证进程能否完整执行完 JNI_OnLoad 函数、顺利走到 onLeave 阶段,从而快速判定崩溃是否发生在 JNI_OnLoad 函数内部。
排查jni函数脚本:
注入结果:

将上述脚本通过 Frida 注入 APP 进程(注入命令:frida -U -f 包名 -l 脚本名.js --no-pause),操作 APP 触发目标 SO 加载,观察日志输出:从日志中可以清晰看到:脚本成功检测到目标 SO 加载,成功找到 JNI_OnLoad 函数地址,并打印“成功开始”,但始终没有打印“成功结束”。这个现象直接印证了我的预判:进程并未成功执行完 JNI_OnLoad 函数并正常退出,崩溃发生在 JNI_OnLoad 函数内部。因此,下一步的排查思路已十分清晰:提取 JNI_OnLoad 函数地址,dump 出完整的加固 SO 文件,通过 IDA 分析 SO 结构,再结合 Frida Stalker 追踪 JNI_OnLoad 的指令执行流,逐行分析执行流程,最终精准定位进程崩溃的“死亡点”(即加固的反调试触发点)。

想要通过 IDA 分析 SO,首先需要将内存中的 SO 文件 dump 到本地。这里的关键是选择合适的 Dump 时机——时机选择不当,可能会导致 dump 出的 SO 不完整、未解密,无法正常分析。本次采用的 Dump 方案是:在 dlopen 的 onLeave 回调时机,筛选内存中加载的目标 SO 并直接 dump。选择这个时机的核心原因有 3 点:dump脚本:执行结果:

修复后用 IDA 打开,发现该 SO 导出表仅有一个 start 函数,完全没有 JNI_OnLoad。这与我之前 Frida 能正常 Hook 到 JNI_OnLoad 的现象完全矛盾,也让我意识到:我拿到的地址根本不属于这个 SO。此时我立刻产生了一个猜想:难道是壳在执行完 JNI_OnLoad 函数后,对加固的 SO 进行了动态篡改,删除了导出表中的 JNI_OnLoad 信息,导致我 dump 出的 SO 没有该函数?为了验证这个猜想,我立刻调整了 Dump 时机,将 Dump 操作放在 JNI_OnLoad 的 onEnter 阶段——也就是函数刚准备执行、还未进行任何操作时,就对 SO 进行内存 Dump。修改后的脚本核心逻辑的是:在 Hook JNI_OnLoad 的 onEnter 回调中,调用 dumpSo 函数,确保在函数执行前完成 Dump。重新执行脚本、导出 SO 文件,再次用 IDA 打开后,结果和之前完全一致:导出表依旧只有一个 start 函数,没有 JNI_OnLoad。这就排除了“壳动态篡改导出表”的猜想,也让我更加确定:问题出在 Frida 获取函数地址的底层流程上,壳一定篡改了符号查找的逻辑,导致 Frida 拿到了虚假的函数地址。

为了弄清楚壳到底是如何篡改符号查找逻辑的,这里给大家详细拆解 Frida 获取函数地址的底层细节,也为后面分析壳的篡改手法做足铺垫——我刚才脚本中获取 JNI_OnLoad 地址的方式,看似只有两行代码,背后却涉及系统 linker、soinfo 结构体、dlsym 函数的完整交互流程,具体拆解如下:我脚本中核心的两行代码,负责获取 JNI_OnLoad 的内存地址,也是逆向分析中最常用的符号查找方式:这两行代码的底层执行流程,并非 Frida 自身实现,而是完全依赖 Linux/Android 系统的动态链接机制,具体可以分为 4 个步骤,每一步都至关重要,也是壳篡改的核心靶点:这行代码的核心作用,是遍历当前进程内核态维护的“已加载模块链表”(该链表由系统 linker 维护,记录了所有已加载到进程内存中的动态库信息),根据传入的 SO 文件名(so_name),匹配到目标 SO 模块,并返回一个包含该模块所有核心信息的对象。返回的 module 对象中,最关键的两个信息是:             m

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-290647.htm
#2 2026-06-01 21:09:09
mb_cfjwplfo

大佬我发现你文章那个jnionload算错了,偏移应该是函数减基地址,你算的是基址减偏移,函数地址是大于基址的
并没有算错 就是因为函数小于这个地址,才有了这篇文章 因为他把这个重定向到了另外一块地址
#3 2026-06-01 21:09:09
rbq
#4 2026-06-01 21:09:09
wx_妳还是不懂

大佬,遇到防止hook的壳怎么办,会直接卡第一屏,检测很厉害
我有一篇帖子专门讲了过检测的思路,还在脱敏 应该过一阵会放出来。这篇帖子就是过检测的前置
#5 2026-06-01 21:09:09
大佬,遇到防止hook的壳怎么办,会直接卡第一屏,检测很厉害

最后于 2026-4-4 21:58
被wx_妳还是不懂编辑

,原因:
#6 2026-06-01 21:09:09
mb_cfjwplfo

你用了基址减函数这个偏移一定是负数。
看这里文章的这里,jni是0x70fe,基址是0x70f9
而我们发现的“JNI_OnLoad 地址 < 模块基址”,
感谢指正!刚刚的图片在复现时的情况和第一次不一样,第一次遇到情况时,匿名so块的基本地址小于壳so的基本地址,所以才会出现jni_onload重定位到小于base地址的情况!我写帖子复现时出现了第二种结果,匿名so块的基本地址大于壳so基本地址,所以出现了图片的有悖情况!现已将图片替换成第一种情况方便大家确认偏移的异常!但是本质都是劫持linker,这是文章的重点,再次感谢指正!

最后于 2026-4-4 21:14
被reserve_zhou编辑

,原因:
#7 2026-06-01 21:09:09
感谢分享!!
#8 2026-06-01 21:09:09
reserve_zhou


mb_cfjwplfo

我问了ai,而图片中给出的偏移是 0xfffffffffb5cd114,这实际上是 -0x4a32eec 的补码表示(即基址减函数地址 ...
你用了基址减函数这个偏移一定是负数。
看这里文章的这里,jni是0x70fe,基址是0x70f9
而我们发现的“JNI_OnLoad 地址 < 模块基址”,
#9 2026-06-01 21:09:09
mb_cfjwplfo

我问了ai,而图片中给出的偏移是 0xfffffffffb5cd114,这实际上是 -0x4a32eec 的补码表示(即基址减函数地址的结果)。因此,偏移计算的方向反了,得出的结果不正确。正确的偏移应 ...
你把我的开头喂给AI让AI帮你讲解一下,应该就能得到你想要的答案了
#10 2026-06-01 21:09:09
mb_cfjwplfo

我问了ai,而图片中给出的偏移是 0xfffffffffb5cd114,这实际上是 -0x4a32eec 的补码表示(即基址减函数地址的结果)。因此,偏移计算的方向反了,得出的结果不正确。正确的偏移应 ...
正因为它劫持了 Linker,导致  JNI_OnLoad  不在正常 ELF 段内,才出现了这种反常值。所谓的“正数偏移”在此场景下并不成立,这不是计算错误,是内存布局被篡改的典型现象。

最后于 2026-4-4 20:40
被reserve_zhou编辑

,原因:
#11 2026-06-01 21:09:09
reserve_zhou

然后看一下我文章的开头前言,因为这个可能是对地址做了一下重定位 他把这个linker给劫持了
我问了ai,而图片中给出的偏移是 0xfffffffffb5cd114,这实际上是 -0x4a32eec 的补码表示(即基址减函数地址的结果)。因此,偏移计算的方向反了,得出的结果不正确。正确的偏移应为正数 0x4a32eec
#12 2026-06-01 21:09:09
mb_cfjwplfo

大佬我发现你文章那个jnionload算错了,偏移应该是函数减基地址,你算的是基址减偏移,函数地址是大于基址的
然后看一下我文章的开头前言,因为这个可能是对地址做了一下重定位  他把这个linker给劫持了
#13 2026-06-01 21:09:09
感谢分享
#14 2026-06-01 21:09:09
reserve_zhou

frida官方有一个专门查全局函数的API在文档里面有体现,你先把这个jnionload绝对地址给查出来 和他申请的匿名内存块的绝对地址进行相减,就能得到它的真实偏移了 他把jnionload函数 ...
大佬我发现你文章那个jnionload算错了,偏移应该是函数减基地址,你算的是基址减偏移,函数地址是大于基址的
#15 2026-06-01 21:09:09
mb_cfjwplfo

大佬,如何找到dumpso的JNI_OnLoad的地址啊
hook dlsym
#16 2026-06-01 21:09:09
mb_cfjwplfo

大佬,如何找到dumpso的JNI_OnLoad的地址啊
frida官方有一个专门查全局函数的API在文档里面有体现,你先把这个jnionload绝对地址给查出来  和他申请的匿名内存块的绝对地址进行相减,就能得到它的真实偏移了  他把jnionload函数重定位到隐藏so了
‹ 上一页 1 2 下一页 ›

请登录后参与讨论

立即登录 注册账号