Ikoct的饮冰室

你愿意和我学一辈子二进制吗?

0%

2025腾讯游戏安全竞赛 PC客户端安全 初赛题解

和前几年的相比还是太CTF了 第一天就出了flag 感觉有很多人都能做出来 不知道要达到什么要求才能拿满分数进决赛

R3层程序分析

flag前4位静态分析时就能看出是ACE_

全局构造函数中发现反调试函数 编写IDApython脚本跳过这个函数以便调试:

image-20250328125411862

image-20250329102933746

发现调试的时候在与驱动建立mini通讯连接时会直接卡死 现在只关注R3层程序对输入的处理 故也编写IDApython跳过这个函数先看看应用层对flag做了什么处理:

QQ_1743215477403

调试至中间发现一个像某种表的字符串:

image-20250328142601349

紧接着就把这个表和输入的flag[4:]传入了一个函数进行编码

首先按照base58的第一步进行了分割:

image-20250328143050626

但是看下面实际上编码用的是base64的表:

image-20250328150446947

比较一下结果就能发现实际上就是换表base58用@填充 + 反转:

image-20250328145503202

下面是一个循环异或:

image-20250328150707324

根据结果验证一下这一步的加密流程:

1
2
3
4
5
6
7
8
9
10
11
12
enc = [
0x33, 0x1C, 0x2C, 0x21, 0x00, 0x0A, 0x0A, 0x0F, 0x4E, 0x3D,
0x2D, 0x02, 0x07, 0x12, 0x1C, 0x38, 0x35, 0x1E, 0x20, 0x21,
0x0B, 0x29, 0x4C, 0x01, 0x34, 0x1A, 0x4D, 0x03, 0x4D, 0x01,
0x04, 0x21, 0x00, 0x16
]

key = b'sxx'
c = 0
plain = [chr(x ^ key[c % 3]) for c, x in enumerate(enc)]
print(''.join(plain))
# @dTRxryw6NUztjdKMfSYsZ4yGb5p5ywYxe

成功验证

R0层程序分析

接下来就进入到了驱动的校验函数 但是在驱动处理应用层发送的信息的回调函数里基本上都是花指令没法看 使用WinDBG进行辅助分析 关于WinDBG的远程调试配置参考我之前的博客 但是不推荐用IDA来调试 虽然IDA用的也是WinDBG作为调试器 但是查看内存还是没有WinDBG方便

先用DriverView辅助计算下断点的虚拟内存 断点就下在ProbeForRead 处 因为这个函数是驱动获取用户内存的API:

image-20250328172317640

得到这两个函数都用于实现数据从用户层迁移到驱动层:

image-20250328173322530

随后在读到驱动层的数据上下硬件断点跟踪数据流向 发现来到了如下的函数 参数就是用户层完成加密后的前两个字节以及b’ACE6’:

QQ_1743154801365

image-20250328174056789

在IDA中找到对应函数进行静态分析:

image-20250328174134180

发现就是一个XTEA 本来打算直接运行来验证其是否每次传入的都是相邻两个字节的用户层加密结果 但是断点没有被击中第二次 猜测应该是在加密完后直接就与正确密文进行了对比直接退出 这一次调试尝试使用步过来查看结果:

img

image-20250328175408664

通过RSI计算是从哪里取出的与TEA结果对比的 计算偏移为0x4060:

image-20250328175524796

也就是说这一块应该存放的就是最终的密文

手动修改zf寄存器值多观察几轮 r @zf=1 随后复制IDA伪代码验证是否就是TEA加密 发现结果和内核调试过程中得到的不一样 对TEA交叉引用发现有函数对其内容进行了修改:

image-20250328180550821

在WinDBG中找到TEA函数对应的位置拷贝其内存到IDA中进行patch看看结果

QQ_1743156768453

image-20250328181253336

发现TEA函数被改为了跳到某个地址 在这个地址下断看看做了什么:

QQ_1743156984996

大致可以看出来也是类似TEA的中间操作 指令条数实际上并不是很多 可以在IDA中找一块内存将其patch成TEA跳转到的这块指令然后再修改TEA+0x56处的逻辑让其跳到自己手写的指令上最后应该就能让IDA反汇编出实际执行的指令了(为了方便写入指令 将程序基址rebase成了0):

QQ_1743158119488

QQ_1743158161787

QQ_1743158182514

经过测试成功运算出了调试时得到的结果:

QQ_1743158606986

编写解密脚本

接下来要做的就是编写脚本解密了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

/* r0层加密使用的key: */
int key[4] = {0x41, 0x43, 0x45, 0x36};
/* r3层加密使用的key: */
char key2[] = "sxx\x00";
/* r0层得到的密文: */
unsigned int to_decrypt[42] = {
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
};

/* XTEA解密函数 */
void XTEA_decrypt(unsigned int * to_decrypt){
unsigned int v1 = to_decrypt[0], v2 = to_decrypt[1], v3 = key[0], v5 = key[1], delta = 0x61C88647;
unsigned int sum = delta * (-32);
for(int _ = 0; _ < 32; _++){
unsigned int tmp = sum + key[(sum >> 11) & 3];
v2 -= tmp ^ (v1 + ((16 * v1) ^ (v1 >> 5)));
v1 -= (sum + v2) ^ (v3 + 16 * v2) ^ (v5 + (v2 >> 5));
sum += delta;
}
to_decrypt[0] = v1;
to_decrypt[1] = v2;
}

int main(){
for(int i = 0; i < 42; i += 2){
XTEA_decrypt(to_decrypt + i);
}
for(int i = 0; i < 42; i++){
printf("%#08x, ", to_decrypt[i]);
}
puts("");
for(int i = 0; i < 42; i++){
to_decrypt[i] ^= key2[i % 3];
printf("%c", to_decrypt[i]);
}
return 0;
}

将得到的字符串反转并b58decode后拼接ACE_:

1
2
3
4
5
6
7
8
9
10
11
12
def b58decode(cipher_input : bytes):
alphabet = b'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789'
if cipher_input[-1] == b'@'[0]:
cipher_input = cipher_input[:-1]
base = len(alphabet)
num = 0
for char in cipher_input:
num = num * base + alphabet.index(char)
return num.to_bytes((num.bit_length() + 7) // 8, 'big')

to_decode = b'@PksUn39kYj763ggA1HLBUCaWSZv4vs4CwSevAnQEs'[::-1]
print('ACE_' + b58decode(to_decode).decode('utf-8'))

img

得到flag: ACE_We1C0me!T0Z0Z5GamESecur1t9*CTf

验证flag:

QQ_1743167529693