注:本人草台班子出身,属于那种“能把效果整出来,但文章过程写得一坨”的类型。这篇题解也是靠 AI 帮忙润色过的。看了论坛里各位大佬的文章后,才发现自己还有不少明显的丢分点,只能说下次继续努力了。
另外,本文仅代表个人解法,最终答案请以官方题解为准。
这题表面上是一个 Godot 小游戏,场景里有黄色方块和绿色方块。黄色方块更像是演示流程,车直接开过去就能触发;真正和计分相关的是绿色方块。
这题要求也不只是拿到绿色方块对应的 flag,而是还要:
所以这题本质上不是单纯“跑出一个结果”,而是要把整条链路都分析清楚。

我的做法分成两步:
最后可以确认,绿色 flag 的计算实际上被拆成了两层:
右上角显示的那串内容,本质上就是最终 8 字节结果转换成的大写十六进制字符串。
题目左上角会显示一个随机 token。我这里注意到,token 对应的脚本会直接把内容打印到 Godot 日志里,所以最稳的方法其实是直接读 logcat:
所以当前这一局对应的 token 是:
后面的动态验证,我都以这个 token 作为主样本。
静态分析后可以确认,绿色方块的触发逻辑并不是在脚本里直接把 flag 算完,而是走到了一个原生扩展对象。
整个关键关系可以概括为:
最后界面显示的是:
这里有两个关键点:
所以分析重点自然就变成了:Process 到底是谁、怎么注册、具体做了什么。
这题是 Godot + GDExtension 结构。native 逻辑不是直接暴露在 Java 层,而是通过 Godot 的扩展接口在运行时注册进去的。
我的处理方式是用 Frida 去 hook 它的注册流程。做法比较简单粗暴:
最终抓到的 native 类名是:
父类是:
真正需要的 native 方法是:
动态抓到的关键参数如下:
这些值本身不是答案,但它们证明了一件事:Process 不是伪线索,它确实是运行时注册出来并被正常调用的 native 方法。
到这里,其实就已经可以跳过“碰到绿色方块”这件事,直接模拟绿色分支的调用过程了。
Process 的输入并不是原始 token,而是 xor_enc(token) 的结果。
这个预处理不复杂。输入是 token 的 8 个 ASCII 字节,处理规则如下:
如果把 token 记作 t0..t7,输出记作 x0..x7,那么它们之间的关系就是:
拿当前 token 37b8c7e2 去算,得到:
这里我也做了动态验证:在调用 Process 之前,把传进去的 8 字节打印出来,结果和脚本推导完全一致。
有了 GameEx 的实例地址和 Process 的调用入口之后,就可以直接在 Frida 里构造 Godot 参数对象,然后原地调用 Process。
对 token 37b8c7e2 的调用结果如下:
于是当前样本对应的绿色 flag 就直接出来了:
接下来我又喂了几组不同的 token 给同一个 native Process,拿到了下面这些样本:
这些样本放在一起看,规律其实很明显:native 并没有做什么复杂加密,而是对 xor_enc(token) 的每个字节又做了一次固定异或。
把 key 反推出以后,得到:
所以 Process 的真实逻辑其实非常简单:
也就是说,Part1 的正向算法就是:
完整 flag 格式为:
既然正向算法已经拆成了两层,那么逆向时按相反顺序还原即可。
先把 flag 后缀的 16 个十六进制字符解成 8 个字节 y0..y7,然后计算:
这样就恢复出了 xor_enc(token) 的结果。
前面已经知道:
把这组关系整理一下,可以得到一组比较干净的恢复公式:
恢复出 8 个字节后,再检查它们是否都是合法的十六进制字符。如果是,就得到了原始 token。
拿当前样本验证:
结果与日志中的 token 完全吻合。
我把正向和逆向都写在了同一个 C++ 文件里:
主要支持两种用法:
其中:
这样题目要求的正向算法和逆算法,都能用 C/C++ 方式完整跑通。
当前样本下,Part1 绿色方块对应的 flag 为:
对应的 token 为:
这题我最后的感觉是:表面上看是个小游戏,实际上核心在于把 Godot 脚本层和 native GDExtension 串起来看。
真正有用的信息链路是:
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 4天前
被清野编辑
,原因: