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

[原创]2026腾讯游戏安全竞赛决赛安卓客户端安全分析

427 浏览 0 回复
#1 楼主 2026-06-01 21:08:54
1. 分析过程2. 算法逻辑分析3. 安全机制解析4. 附录文件第一,Godot 安卓题不能先押单线。初赛已经证明,只要一上来就认定“脚本一定是主线”或者“native 一定才是真核心”,后面就很容易在错误方向上投入过多时间。更稳的做法,是先把资源层、对象层、native 层都保留成候选入口,再让证据去决定谁先走。第二,资源层可见不等于资源层可直接利用。初赛里已经反复遇到过这种情况:APK 里能看见 .gdc、场景文件、pack 文件,但它们并不等于可直接阅读的源码,也不等于真实执行时看到的那一份逻辑。这个经验直接影响了决赛的开局判断,也就是不能因为看到了 project.binary、assets.sparsepck、token.gdc 和 Trigger/*.gdc,就立刻把主战场押在资源恢复上。第三,真实样本优先于完整理解。初赛里真正帮助主线快速收束的,从来不是先把所有文件都解释清楚,而是尽快拿到一组真实 token -> flag 或真实对象触发样本。因为只有样本到手之后,后面的脚本分析、native 还原、逆算法验证才有硬约束。这个经验到了决赛里依然成立,所以后面的路线才会不断强调“先撞到真块、先看到真输出、先抓到真样本”。第四,动态窗口必须优先稳定。初赛已经说明,Godot 和 native 混合题如果动态窗口不稳定,后面的对象枚举、脚本实例化、运行时调用、VM trace 全都无从谈起。所以决赛一开始并不是先去深挖某个算法函数,而是先判断启动期和注册期的探针应该落在哪里,才能保证进程真的活着进入主场景。也正因为有了这四条经验,决赛开局的判断顺序才会显得比较“克制”:先判断哪一层最有机会快速形成闭环,而不是看到哪一层信息多就一股脑扎进去。从 APK 结构上看,题目同时给出了三类非常明显的入口信号:因此最初并没有理由武断地认为“题目一定主要靠资源层”或者“题目一定主要靠 VM”。更合理的做法,是先把三层都保留成候选主线,再用证据决定谁先走。决赛里最终形成的总体分工如下:本题后续所有实机样本获取、对象层验证、脚本运行时调用和 VM 取证,都建立在启动窗口可稳定利用这一前提之上。因此在进入三个 Part 的具体逆向之前,需要先交代启动期反调试链是如何被压缩到可工作的分析窗口中的。只有这一窗口稳定下来,后面的截图、日志和运行时证据才具备可重复性。最初的教训是:不要太早把 hook 压到库内直钩上。实际对照现象非常明显:随后又通过线程级隔离实验确认:这组实验把策略从“整条守卫线程静音”修正成了“只旁路纯反调试线程,保留兼带初始化职责的线程,只改它看到的危险输入”。这不是风格选择,而是被崩溃现象强制出来的工程结论。后续之所以采用:本质上就是因为前面的试错已经证明:如果想继续在真机上拿样本、截屏、dump 脚本对象、trace VM,就必须优先保证这条动态基线足够稳定。本题的总体推进顺序可以概括为下图。因重要程度,反调试/反注入和详细的入口线程、关键字符串、kill gate 具体会在第三章说明,本章节只写逆向分析过程。稳定动态基线的关键是两步:对应的关键运行日志如下,已经足够说明启动期模块白名单检查被压制,而进程不会立刻在 %resume 后死亡:而在继续给 libc 的 exit / abort / __assert2 加观测后,又能看到进程终止前并不会命中这些高层终止接口:这组对照意味着:后续所有算法分析都不是在“裸奔环境”里进行,而是在已经穿过启动期检测窗口之后进行的。也正因为如此,后续三条主线才能分别闭环,而不是反复卡死在 attach/spawn 阶段。Part1 的关键并不是“先把 native VM 完全逆干净”,而是先确认绿色块到底走哪条真实生成链。这个判断在推进过程中经历过一次明显的路线修正。最早的直觉是:题目既然显式带有 libsec2026.so 和 VMEntry,那绿色块很可能也会落进 native VM 主线。因此最初一段时间的工作重点,确实放在了:但随着样本积累,这个判断被证据推翻了。后续主线改到脚本层,直接原因就是 trigger2.gd 的运行时调用结果与真实样本一致。对象层先给出了第一批硬证据。我在外侧挂三类窄目标 hook:android_dlopen_ext 用来确认 libsec2026.so 何时加载,pthread_create 用来识别三条守卫线程并仅旁路 thread_ptrace_fork_watch,strstr 用来把 /memfd:frida-agent-64.so (deleted) 这类危险模块名伪装成正常系统模块路径。主线程 %resume 之后,再进入场景树枚举对象。用于稳定这个窗口的关键 Frida 代码如下:在这个窗口稳定之后,主场景里可以直接枚举到四个关键 Area3D:结合题面和后续实测,这四个对象分别对应:这里还有一个很容易被忽略但实际非常重要的观察:绿色 Part1 方块的 y 值明显高于其他几个方块,达到 11.673913...。这和后续实机截图中“绿色方块在屋顶附近”的画面是互相吻合的。也就是说,绿色块不是“代码上能出分但玩家一定碰不到”的伪目标,而是一个可以直接通过对象层传送验证的真实得分点。Part1 这条线的第一步动作不是先读脚本,而是先把车移动到绿色块,先确认题面的绿色目标在实机里到底会不会真正出分。场景树里只有一个可控车体,运行时对象为 VehicleBody3D id=38470157839,初始位置约为 (7.119446, 3.489174, -16.001696);绿色块 Area3D id=43251664672 的位置约为 (-14.960197, 11.673913, -3.083051)。因此对象层的第一步,就是把这辆车直接传送到绿色块上方一点,再轮询左上角 Label 和右上角 Label2。frida代码如下:也就是先通过 set(global_position) 把唯一车体放到绿色块附近,再立即回读位置,确认对象层传送已经生效。完成这一步后,轮询左上角 Label 和右上角 Label2,就能直接拿到第一组绿色块真样本:这一步先把对象层的三个关键事实固定了下来:实机截图也对应了同一条对象层链路。下图是绿色块 Part1 命中截图,可以看到左上角是实时 Token: 4a23ab75,右上角已经显示 flag{sec2026_PART1_203703fc},说明绿色块现场输出和后续恢复出来的脚本算法是一致的。在对象层拿到绿色块真样本之后,再回到资源层补脚本归属。trigger2.gd 在运行时可以被 ResourceLoader 直接加载、实例化,并且方法列表中能稳定看到以下辅助函数:这一组命名已经非常关键,因为它同时覆盖了:这说明 trigger2.gd 并不是简单触发器,而是已经带有完整的脚本级算法管线。与之形成对照的是 token.gd。运行时日志显示,token.gd 的方法表只有 _mk 和 _ready 两个核心方法,并且直接调用 _mk(8) 会返回形如 52303553 这类 8 位 token。它说明 token.gd 负责的是“左上角 token 从哪里来

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-290981.htm

暂无回复,快来抢沙发吧!

请登录后参与讨论

立即登录 注册账号