首页
社区
课程
招聘
[原创]2026 年腾讯游戏安全初赛 Android方向
发表于: 3小时前 56

[原创]2026 年腾讯游戏安全初赛 Android方向

3小时前
56

注:本人草台班子出身,属于那种“能把效果整出来,但文章过程写得一坨”的类型。这篇题解也是靠 AI 帮忙润色过的。看了论坛里各位大佬的文章后,才发现自己还有不少明显的丢分点,只能说下次继续努力了。
   另外,本文仅代表个人解法,最终答案请以官方题解为准。


一、题目理解

这题表面上是一个 Godot 小游戏,场景里有黄色方块和绿色方块。黄色方块更像是演示流程,车直接开过去就能触发;真正和计分相关的是绿色方块。

这题要求也不只是拿到绿色方块对应的 flag,而是还要:

  1. 找出 flag 的生成算法;
  2. 给出对应的逆算法;
  3. 并且要求使用 C/C++ 实现。

所以这题本质上不是单纯“跑出一个结果”,而是要把整条链路都分析清楚。

二、整体思路

我的做法分成两步:

  1. 先把 Godot 脚本侧的逻辑摸清楚,确认绿色分支到底调用了什么;
  2. 再去跟 native 的 GDExtension,直接把生成 flag 的 Process 方法调用起来。

最后可以确认,绿色 flag 的计算实际上被拆成了两层:

  • 第一层在 GDScript 中,先对 token 做一轮 xor_enc
  • 第二层在 native 中,再和一个固定的 8 字节 key 做异或

右上角显示的那串内容,本质上就是最终 8 字节结果转换成的大写十六进制字符串

三、先解决 token 从哪里来

题目左上角会显示一个随机 token。我这里注意到,token 对应的脚本会直接把内容打印到 Godot 日志里,所以最稳的方法其实是直接读 logcat:

04-11 04:32:52.144  6682  6726 I godot   : Token: 37b8c7e2

所以当前这一局对应的 token 是:

37b8c7e2

后面的动态验证,我都以这个 token 作为主样本。

四、绿色分支并不是单纯的 GDScript

静态分析后可以确认,绿色方块的触发逻辑并不是在脚本里直接把 flag 算完,而是走到了一个原生扩展对象。

整个关键关系可以概括为:

flag1 = obj.Process(xor_enc(token))

最后界面显示的是:

flag{sec2026_PART1_<flag1>}

这里有两个关键点:

  1. xor_enc 是脚本层做的预处理;
  2. 真正决定 flag 的核心逻辑在 native 的 Process 方法里。

所以分析重点自然就变成了:Process 到底是谁、怎么注册、具体做了什么。

五、定位原生类和方法

这题是 Godot + GDExtension 结构。native 逻辑不是直接暴露在 Java 层,而是通过 Godot 的扩展接口在运行时注册进去的。

我的处理方式是用 Frida 去 hook 它的注册流程。做法比较简单粗暴:

  1. hook extension_init
  2. hook module_init
  3. hook classdb_register_extension_class5
  4. hook classdb_register_extension_class_method
  5. 把类名、方法名、对象地址、方法调用入口都记下来

最终抓到的 native 类名是:

GameEx

父类是:

Node

真正需要的 native 方法是:

Process

动态抓到的关键参数如下:

get_proc         = 0x7542e187bc
object           = 0xb4000076b7c077d0
method_userdata  = 0xb400007627d07050
call_func        = 0x7535e6fde4
ptrcall_func     = 0x7535e6fe3c

这些值本身不是答案,但它们证明了一件事:Process 不是伪线索,它确实是运行时注册出来并被正常调用的 native 方法。

到这里,其实就已经可以跳过“碰到绿色方块”这件事,直接模拟绿色分支的调用过程了。

六、脚本层算法:xor_enc

Process 的输入并不是原始 token,而是 xor_enc(token) 的结果。

这个预处理不复杂。输入是 token 的 8 个 ASCII 字节,处理规则如下:

out = bytes(token)
for i = 0..6:
    out[i] = out[i] ^ out[i + 1]
out[7] = out[7] ^ out[0]

如果把 token 记作 t0..t7,输出记作 x0..x7,那么它们之间的关系就是:

x0 = t0 ^ t1
x1 = t1 ^ t2
x2 = t2 ^ t3
x3 = t3 ^ t4
x4 = t4 ^ t5
x5 = t5 ^ t6
x6 = t6 ^ t7
x7 = t7 ^ x0

拿当前 token 37b8c7e2 去算,得到:

xor_enc("37b8c7e2") = 04 55 5A 5B 54 52 57 36

这里我也做了动态验证:在调用 Process 之前,把传进去的 8 字节打印出来,结果和脚本推导完全一致

七、直接调用 Process

有了 GameEx 的实例地址和 Process 的调用入口之后,就可以直接在 Frida 里构造 Godot 参数对象,然后原地调用 Process

对 token 37b8c7e2 的调用结果如下:

token   = 37b8c7e2
xor_enc = [04,55,5A,5B,54,52,57,36]
result  = 7E4A09122764744A

于是当前样本对应的绿色 flag 就直接出来了:

flag{sec2026_PART1_7E4A09122764744A}

八、算法还原

接下来我又喂了几组不同的 token 给同一个 native Process,拿到了下面这些样本:

00000000 -> 7A1F53497336234C
ffffffff -> 7A1F53497336231A
12345678 -> 791E544870372C47
37b8c7e2 -> 7E4A09122764744A
37b8c7e3 -> 7E4A09122764754B
deadbeef -> 7B1B564F7436201B

这些样本放在一起看,规律其实很明显:native 并没有做什么复杂加密,而是对 xor_enc(token) 的每个字节又做了一次固定异或

把 key 反推出以后,得到:

K = [7A, 1F, 53, 49, 73, 36, 23, 7C]

所以 Process 的真实逻辑其实非常简单:

y[i] = x[i] ^ K[i]
flag_suffix = HEX_UPPER(y[0..7])

也就是说,Part1 的正向算法就是:

flag_suffix = HEX_UPPER(xor_enc(token) XOR K)

完整 flag 格式为:

flag{sec2026_PART1_<flag_suffix>}

九、逆算法

既然正向算法已经拆成了两层,那么逆向时按相反顺序还原即可。

1. 先去掉 native 的固定异或

先把 flag 后缀的 16 个十六进制字符解成 8 个字节 y0..y7,然后计算:

x[i] = y[i] ^ K[i]

这样就恢复出了 xor_enc(token) 的结果。

2. 再反解 xor_enc

前面已经知道:

x0 = t0 ^ t1
x1 = t1 ^ t2
x2 = t2 ^ t3
x3 = t3 ^ t4
x4 = t4 ^ t5
x5 = t5 ^ t6
x6 = t6 ^ t7
x7 = t7 ^ x0

把这组关系整理一下,可以得到一组比较干净的恢复公式:

t0 = x7 ^ x1 ^ x2 ^ x3 ^ x4 ^ x5 ^ x6
t[i] = t[i - 1] ^ x[i - 1], i = 1..7

恢复出 8 个字节后,再检查它们是否都是合法的十六进制字符。如果是,就得到了原始 token。

拿当前样本验证:

7E4A09122764744A -> 37b8c7e2

结果与日志中的 token 完全吻合。

十、C++ 实现说明

我把正向和逆向都写在了同一个 C++ 文件里:

tx.cpp

主要支持两种用法:

tx.exe --flag 37b8c7e2
tx.exe --token 7E4A09122764744A

其中:

  • --flag:根据 token 计算 flag 后缀
  • --token:根据 flag 后缀反推 token

这样题目要求的正向算法逆算法,都能用 C/C++ 方式完整跑通。

十一、最终结果

当前样本下,Part1 绿色方块对应的 flag 为:

flag{sec2026_PART1_7E4A09122764744A}

对应的 token 为:

37b8c7e2

十二、总结

这题我最后的感觉是:表面上看是个小游戏,实际上核心在于把 Godot 脚本层和 native GDExtension 串起来看

真正有用的信息链路是:

  1. 从日志里拿到 token;
  2. 在脚本层确认 xor_enc
  3. 在运行时定位并调用 native 的 Process
  4. 通过多组样本反推出固定 key;
  5. 最后补出正向和逆向的 C++ 实现。

整体上不算特别重型,但如果一开始没意识到绿色分支是走 native,那确实容易绕很久。


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 3小时前 被清野编辑 ,原因:
上传的附件:
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回