腾讯游戏安全竞赛2026-安卓赛道决赛-反注入分析
前言
结果非常意外,我竟然获得了今年安卓的优秀奖,好听点是腾讯游戏安全竞赛2026安卓赛道决赛第5名!
比较遗憾的就是对反调试反注入这一块没有分析好,再加上很多分析过程都依赖AI,导致最后还是排名偏低,不过也是非常满意了。
对于FLAG生成的部分,网上已经有很多优秀的WP了,包括第一名Matriy师傅的,让我受益匪浅,因此在这里只对我比赛最为困惑的且学习颇多的地方,注入检测进行复盘

多线程检测
在平常的APP分析中,当Frida注入程序崩溃后,我第一件想到的事情就是去Hook pthread_create或者clone这两个线程创建相关的函数,这里就不再赘述了,相信问一下AI都能给出结果
对libsec2026.so分析,定位到0x99094处,存在着三处检测

按照以往的经验,尝试把线程回调函数设置为一个空函数,直接暴力不执行各种检测就好了。
1 | function nop_64(addr) { |
但程序在一段时间后依然崩溃,事情肯定没有这么简单。
这时候我想起过之前的一种思路,在线程函数里塞入和互斥锁相关的操作,如果直接不执行,就会导致死锁,而且在函数中我也看到有很多处调用了pthread_mutex_lock,但后来发现其实似乎并没有太大关系
父子进程检测
0x9C654可以看到fork,waitpid等操作,这里非常明显能看出来就是程序fork出一个子进程并进行附加,这样调试器就没有办法再attach上去调试了

fd与线程检测
0x9CDC4在进行间接跳转混淆的处理后,可以看到经过一个CFF魔改,CFG已经不成样了,但正如AI时代的发展,人类的阈值也在不断被提高,甚至控制流平坦化都眉清目秀的,这里就不再处理了,能看就行

通过字符串解密,这里是读取了/proc/self/task/%s/status,检查是否有gum-js-loop和gmain,典型的特征线程检测

在0x99418,读取了/proc/self/fd/,通过readlink获取每个fd指向的真实目标

检查是否出现了特有文件的描述符linjector的字样

页权限修改
通过mprotect把exit函数的内存页权限修改为不可写,因为此处在多线程中,直接去hook,也可能出现条件竞争的情况,可能Frida刚设置完可写,这边又设置回去了

Maps检测
这也是个非常经典的套路了,通过读取/proc/self/maps

然后检查加载的库里有没有frida的特征,通常Frida注入后会读到像这样的痕迹/memfd:frida-agent-64.so (deleted)

godot运行库crc校验
sub_96A00这里开始有很多互斥锁的操作,看的我心慌,先是解密出了libgodot_android.so,盲猜要做校验

通过sub_9AF98进行了一波CRC校验

心跳检验
在sub_9AF98中,除了CRC校验,还有一个非常重要的点
通过clock_gettime的系统调用,获取了当前时间,并通过ts = v23.tv_nsec / 1000 + 1000000 * v23.tv_sec;转化

在GDExtension的onTick函数中,也在实时监测着心跳,如果发现差值大于10000000,也就是10s,程序直接走向崩溃
因此我们要手动在Frida中,用setInterva不断向这块内存注入心跳,不然程序肯定崩溃

process_vm_readv系统调用校验
就算绕过了前面所有的监测点,只要hook了PROCESS_IMPL函数0x97704,程序一段时间后被杀,严重怀疑是因为存在校验
如果真的有地方对这段内存进行了校验,用stackplz下个读取断点尝试一下
1 | ./stackplz -p 10997 --brk 0x97704:r --brk-lib libsec2026.so --brk-len 1 --stack |
但非常可惜的是,命中结果为0

在定位到libgodot_android.so的0x10B9E4C后,可以惊讶地发现,这里从libsec2026.so的0x95C70开始读取0xCA210到buffer,居然还会对进行一波校验!
但为什么stackplz抓不到这个读的操作呢,问题就出在process_vm_readv

来看看process_vm_readv这个系统调用的功能
process_vm_readv 是 Linux 3.2+ 引入的系统调用,用于直接在两个进程的用户空间之间传输数据,无需经过内核缓冲区,从而减少一次数据拷贝,提升进程间通信性能。它的反向操作是 process_vm_writev,用于写入远程进程内存。
核心特性
- 零拷贝优化:数据直接在进程地址空间间传输,避免内核中转。
- 基于 iovec:支持一次调用传输多个不连续的内存区域。
- 权限控制:需要相同有效 UID 或 CAP_SYS_PTRACE 权限,并且目标进程必须运行中。
- 非原子性:多段传输可能部分成功,需要检查返回值。
woc,也就是说,它的拷贝发生在内核页,因此不会产生目标进程用户态 watchpoint/BRK 事件,而常规的硬件断点只能抓像LDRB W14, [X12],LDR X8, [X9]这样的内存读写,因此根本抓不到,个检测思路我也是第一次见,也算是又学到妙妙技巧了。
当最后发现校验不通过时,直接就进入销毁的函数了,0x10BA0A4对应源码中的void Main::cleanup(bool p_force)
所以去hook exit根本没有用,整个核心进程的生命周期已经结束了

而且这里的调用还不是通过BL这样的正常函数调用,而是通过BR直接跳转,导致我根本抓不到这里的调用栈!

而非常可惜的是,我的ebpf tracepoint诊断脚本,虽然hook了process_vm_readv,但根本没有将参数解析

这就是现在大概的逻辑
1 | struct user_iovec64 { |
现在这条日志,就能非常清楚地记录了
1 | [ 9139.465] EXIT pid=10360 tid=10412 comm=VkThread process_vm_readv(270) ret=827920 pid=10360 lvec=0x7c0ea30e58 liovcnt=1 rvec=0x7c0ea30e48 riovcnt=1 flags=0x0 local_iov_parsed=1/1 local_iov[0]={base=0xb400007c10a68df0 len=0xca210} -> "00:00 0 [anon:System property context nodes]"+0xb3fffffc8155edf0..0xb3fffffc81629000 map=0x7f8f519000-0xffffffffffffffff off=0xf000 prot=0x0 remote_iov_parsed=1/1 remote_iov[0]={base=0x7c11ec3c70 len=0xca210} -> "/data/app/~~HBS2UqCzNaLwfLuACuXyfQ==/com.tencent.ACE.gamesec2026.final-Ei05wgsojgSTR69gZPEWbw==/lib/arm64/libsec2026.so"+0x95c70..0x15fe80 map=0x7c11e2e000-0x7c11f8e310 off=0x0 prot=0x5 |
- 标题: 腾讯游戏安全竞赛2026-安卓赛道决赛-反注入分析
- 作者: 两只羊
- 创建于 : 2026-05-05 21:50:56
- 更新于 : 2026-05-06 11:28:09
- 链接: https://twogoat.github.io/2026/05/05/腾讯游戏安全竞赛2026安卓赛道-反注入分析/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。