首页
社区
课程
招聘
[原创] 腾讯2025游戏安全PC方向初赛题解
发表于: 2025-3-31 16:30 9045

[原创] 腾讯2025游戏安全PC方向初赛题解

2025-3-31 16:30
9045

小Q是一位热衷于PC客户端安全的技术爱好者,为了不断提升自己的技能,他经常参与各类CTF竞赛。某天,他收到了一封来自神秘人的邮件,内容如下:
“我可以引领你进入游戏安全的殿堂,但在此之前,你需要通过我的考验。打开这扇大门的钥匙就隐藏在附件中,你有能力找到它吗?”
评分标准:满分5分
(1)在64位Windows10+系统上运行exe, 找到正确的flag,作为答案提交(2分)。
(2)文档编写,详细描述解题过程,详述提供的解题程序的演示方法。(满1分)
(3)提供解题演示所用的源代码。编码工整风格优雅(1分)、注释详尽(1分)。

flag{ACE_We1C0me!T0Z0Z5GamESecur1t9*CTf}

先说结论:

赛博厨子如下设置就能正确解密

转到initterm加载的最后一个函数就可以发现其中使用std::thread库开启了一个反调试线程,其线程入口为sub_140001C90


我们可以跟进去看一下

可以发现经典的检测手段。后面那一大坨则是std::this_thread::sleep_forstd::chrono组合起来编写的等待函数,Release下优化导致的。

我们直接在开启反调试线程的地方ret掉就行了。

main函数中初始化了一个ACE_SDK对象调用了一个函数

跟进去看一下原来就是调用其他函数

继续跟

会发现其他的成员函数

一个个来,先看服务安装,标准的通过注册表安装服务的方式安装了驱动,值得注意的是,由于安装的MiniFilter驱动,这里需要注册表 多创建Instances子健, 所以这里还是不太一样的

代码如下:

start的则是正常的开启服务

创建通信则是用的MiniFilter,并把句柄存在类中。

由于没有TlsCallBack我们直接转main前初始化的部分分析。着重分析initterm加载的函数。

我们在倒数第二个函数发现了点不一样的

当然图里是已经分析好的了,我们去分析初始化Base58的表。

我们会发现其就是单纯的字符串xor加密,直接动调拿表即可。

明显长度大于58位,我们直接缩减到58位即可(源于对base58的分析)
所以表为:

为什么是base58呢我们往下看,xref一下就能到base58的函数,这里写了58,很明显了。


不过有一点值得注意,我们往下翻的话会看到一个结果倒叙

这里需要特别注意一下

首先看输入之后,发现这里的长度有一些限制,其应为:0x14+0x10 -> 0x24

输入的长度必须大于36(包含00),也就是35个字符

然后前4位必须是ACE_

继续跟,后面会发现初始化了一个字符串内容为sxx

然后后面把ACE_后面的字符提出来了,做base58

做完之后把其与sxx字符进行异或。

后面就进入了r0进行check

然后后面就是卸载驱动了。

首先既然是通过MiniFilter进行的通信,我们直接找一下其注册函数就行,驱动入口被保护了,但是注册的地方没保护。直接看

直接看消息回调

首先会发现有点混淆,手算一下就能知道是到pop r10的位置

我们直接全nop了,后面也有一大堆,手动去一下就行了

F5一下发现有问题

patch一下

会发现其中会创建缓冲区然后复制,其还测试了传入的内存是否可读保证安全


不过报错也是新思路直接跟过去

其里面依然有混淆,手动去一下,就会发现其实是消息分发函数

这里面呢其实有2个功能

值得注意的是,这里a1是个结构,其结构如下:

第一个功能呢是在r3传入

时返回This is TestHello from r0

第二个则是重点,我们看检查flag的函数。

其中去了混淆长这样

值得注意的是其把明文按一个字符扩展成了unsigned int 所以有42个密文。

很明显key和密文都有了,里面就是个tea,但是呢怎么可能这么简单结束了?

对着tea函数按x会发现一大堆引用,我们把混淆去一下挨个看看。

首先看第一个很明显在写tea函数,在做最后的patch

值得一提的是,代码写的很棒,首先提升了IRQL,然后判断了一下CR4寄存器的CET位,该位在开启时必须开启CR0的WP保护位,反过来说:CR4.CET开启时关闭CR0.WP会蓝屏,(Intel手册卷3 2.5节详细说了)所以其关闭了CR4.CET再去改CR0.WP让内存可写。同时加上了关闭外部中断,保证了其线程会一直占用CPU。恢复寄存器后重新开中断降低IRQL。该强写代码堪称简单又标准!

闲言少叙,我们直接来看到底写了什么。

函数贼长,不过看起来是在写一片内存,同时也在计算些什么。

显然的4048是跳板。

继续看其他的点呢,则是修改了原始tea加入了点新东西,rax,rcx的值则是上面算出来的。

理清逻辑之后就可以直接动调了,断加密前面给加密的代码抠出来就行了。

关于如何下断:这里动调偷懒断FltRegisterFilter就省的写回调下断点了。

输入内容后段下来,可以看到确实被改了

转过去把字节抠出来方便ida分析

存到本地,ida修一下就可以f5直接看到了

这个就是加密算法了,写一下解密算法这个题就完事了。
exp:

abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789
abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789
struct Message
{
    unsigned int messageID;
    unsigned int messageLen;
    char messageBuf[1];     //变长数组,以len为长度
}
struct Message
{
    unsigned int messageID;
    unsigned int messageLen;
    char messageBuf[1];     //变长数组,以len为长度
}
#include <stdio.h>
typedef signed char        int8_t;
typedef short              int16_t;
typedef int                int32_t;
typedef long long          int64_t;
typedef unsigned char      uint8_t;
typedef unsigned short     uint16_t;
typedef unsigned int       uint32_t;
typedef unsigned long long uint64_t;
 
void decrypt(uint32_t* v, uint32_t* key)
{
    uint32_t v0 = v[0], v1 = v[1];
    uint32_t sum = 0x9E3779B9 * 32;
    const uint32_t delta = 0x9E3779B9;
    for (int i = 0; i < 32; i++)
    {
        v1 -= (sum + key[(sum >> 11) & 3]) ^ ((v0 << 4 ^ v0 >> 5) + v0);
        v0 -= ((v1 << 4) + key[0]) ^ (v1 + sum) ^ ((v1 >> 5) + key[1]);
        sum -= delta;
    }
    v[0] = v0;
    v[1] = v1;
}
 
int main() {
    uint32_t key[] = { 'A','C','E','6' };
    uint32_t enc[] = {
    0x0EC367B8, 0xC9DA9044, 0xDA6C2DEB, 0x88DDC9C3, 0x32A01575, 0x231DD0B4, 0x4B9E8A74, 0xD75D3E74,
    0xEAAB8712, 0xE704E888, 0xE01A31AC, 0xECAE205C, 0xA7BE7467, 0x0C6252A3, 0x1AEFEC4E, 0xC40DED44,
    0xC3C842CC, 0xDE4A0C0E, 0x7C24F3FC, 0x8FB8D001, 0x11153E6E, 0x530ED15C, 0xF4214811, 0xBEB517E0,
    0x63F91634, 0x4D96F8A5, 0xFE23EAC8, 0x2C607ADF, 0xCC43D85C, 0xFF186C5B, 0x8763E1A5, 0x9187BD58,
    0x87D1069B, 0xD7878D7B, 0x836E6B68, 0x55A0C63F, 0xD979FDB3, 0x3E524DEE, 0x7AB35C82, 0xA2F4DA8D,
    0x1708BA4C, 0x710653E6,
    };
    for (size_t i = 0; i < 42; i += 2)
    {
        decrypt(&enc[i], key);
        printf("0x%hhx 0x%hhx ", enc[i], enc[i + 1]);
    }
 
}
#include <stdio.h>
typedef signed char        int8_t;
typedef short              int16_t;
typedef int                int32_t;
typedef long long          int64_t;
typedef unsigned char      uint8_t;
typedef unsigned short     uint16_t;
typedef unsigned int       uint32_t;
typedef unsigned long long uint64_t;
 
void decrypt(uint32_t* v, uint32_t* key)

[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2025-3-31 16:49 被moshuiD编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (8)
雪    币: 14329
活跃值: (7392)
能力值: ( LV15,RANK:673 )
在线值:
发帖
回帖
粉丝
2
向黑客致敬.jpg
2025-3-31 16:49
0
雪    币: 119
活跃值: (282)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
厉害????有没有移动端的分析大佬!
2025-3-31 20:40
0
雪    币: 2334
活跃值: (5624)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4
changetea没有实际使用价值,修改cet的操作是无用的,开了cet微软会让你强制开hvci,此时会保护cr4.cet标志,修改cr4直接蓝屏
2025-4-7 09:21
0
雪    币: 120
活跃值: (59)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
安全公司招EDR相关人才
2025-4-7 22:17
0
雪    币: 803
活跃值: (2789)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
Oxygen1a1 changetea没有实际使用价值,修改cet的操作是无用的,开了cet微软会让你强制开hvci,此时会保护cr4.cet标志,修改cr4直接蓝屏
并不会
2025-4-14 10:20
0
雪    币: 257
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7

学习

最后于 2025-4-14 12:32 被Samber编辑 ,原因:
2025-4-14 12:18
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
のばら 并不会
最后于 2025-4-17 08:14 被mb_bsejbqbn编辑 ,原因:
2025-4-16 08:18
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
请问.sys文件如何加载?放在System32/drivers/目录下,仍然报错Driver loading failed
2025-4-17 20:25
0
游客
登录 | 注册 方可回帖
返回