吾爱2025-Windows逆向高级题-5

考点:异步消息执行,变种tea展开、变种MD5、时间戳、Flag分段检查

解题过程

这一段是获取两个编辑框的内容,即uid和flag,然后flag要符合异或的那一系列条件,实际格式是flag{…}。

alt text

跟到这边发现有一系列函数,main_program里面的执行验证按钮后主流程,execute是main_program里面通过不同消息来执行不同命令的函数。(都是自命名的函数,仅代表个人想法)

alt text

execute函数

其他消息:将flag括号内数据进行unhex(如1122字符串直接转成0x11,0x22数据)

0x35消息:获取当前半小时整点时间戳数据。

alt text

0x55消息:通过利用变种MD5+Salt将解密完数据的前十六字节计算得到4字节数值。

alt text

0x25消息:unhex后数据进行解密(Decrypt函数)。

alt text

main_program函数

第一部分

获取flag括号内数据通过消息分发执行execute的unhex消息,然后再执行execute的Decrypt函数,解密unhex后的数据,将解密完的数据长度赋值给v12。

alt text

第二部分

将解密完数据的前16字节进行custom_MD5,得到4字节数据,然后判断解密后数据第17个字节开始四个字节是否和计算得到的4字节数据相等。

如果相等就再次判断v12,即解密后数据长度,判断是否等于20。

再调用execute的时间戳获取消息,得到8字节时间戳数据。

最后再检查解密后数据前8字节是否等于时间戳数据,以及第九个字节往后8字节是否等于编辑框输入的uid。

结论

输入的flag得是被和Decrypt相对于的加密函数进行加密后的数据,加密前格式:{半时整点时间戳(8字节),uid(8字节),Custom_MD5(前面十六字节)(4字节),0x04填充(四个字节)}

最后一部分填充会在下面Decrypt函数里面说明来由。

alt text

Decrypt函数(sub_7FF7FAC92C40)

要求unhex后数据长度要是8的倍数,且利用一系列计算得到v12这个数据,参与内部解密的Key生成,最后还要求解密完的数据符合一系列条件验证。

alt text

解密后数据条件验证

从这部分逻辑代码可以分析,他是将最后v8指向最后一个数据,然后v9赋值最后一个数据,然后v8循环递减,直到当前v8指向v8开始往前的第v9个指针结束,然后最后解密后数据长度=当前长度-v9。

已知解密后前面已经占用了20字节(时间戳+uid+md5),在main_program也已知解密后数据长度要等于20,所以可以知道这边v9必须等于4,所以v8等于4,最后这边一共占用4个字节,即{4,4,4,4},这样经过这边的验证最后的size才会等于20。

alt text

dec函数

将unhex后数据按8字节分块进行tea的解密,tea加密的Key由上一层传入的v12通过RC4得到,且每次解密Key都会变化(固定变化),直接动调就可以拿到几次解密用到的Key值。

alt text

下面一系列解密就是tea的解密,不过是展开,可以数出一共是12轮,且Delta直接可以通过两次sum的值相减得到(由于tea解密这边应该是加上sum,ida伪代码展示是减,但是实际计算后数值一样),B979379E就是tea解密用到的Delta。

alt text

所以就可以通过动调得到的几次Key和Delta写出tea的加密代码。

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
uint32_t key1[] =
{
0xD7851B65,
0x473457C1,
0x1231F787,
0x9ACD6D9A
};
uint32_t key2[] =
{
0xB728E994,
0x1746382E,
0xC52D865C,
0x10778A6E
};
uint32_t key3[] =
{
0x7459F437,
0x90D1E5D,
0x779375B2,
0xEFCB8541
};

void tea_encrypt(uint32_t v[2], const uint32_t k[4])
{
uint32_t v0 = v[0], v1 = v[1], sum = 0;

uint32_t delta = 0xB979379E;

for (uint32_t i = 0; i < 12; i++)
{
sum += delta;
v1 += ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
v0 += ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
}

v[0] = v0;
v[1] = v1;
}

主解题流程

通过用c++实现这部分代码,获取时间戳数据(8字节)。

alt text

然后将uid转为8字节字节数据拼接到时间戳字节后面。

MD5值暂时填充4个0x00,将MD5值和4个0x04字节拼接上。

将完整数据进行tea_encrypt,再用flag{}包裹填入编辑框进行验证。

在MD5生成代码处,断点在箭头处,即可得到MD5四字节数据。

alt text

最终再重复上面步骤即可得到flag。

完整代码

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <iostream>
#include <Windows.h>

void tea_encrypt(uint32_t v[2], const uint32_t k[4])
{
uint32_t v0 = v[0], v1 = v[1], sum = 0;

uint32_t delta = 0xB979379E;

for (uint32_t i = 0; i < 12; i++)
{
sum += delta;
v1 += ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
v0 += ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
}

v[0] = v0;
v[1] = v1;
}

/*
输入格式:
flag
{
tea_enc
(
timestamp 8字节
uid 8字节
md5 4字节
0x04*4 4字节填充
)
}
*/

int main()
{
// 动调得到的三个Key
uint32_t key1[] =
{
0xD7851B65,
0x473457C1,
0x1231F787,
0x9ACD6D9A
};
uint32_t key2[] =
{
0xB728E994,
0x1746382E,
0xC52D865C,
0x10778A6E
};
uint32_t key3[] =
{
0x7459F437,
0x90D1E5D,
0x779375B2,
0xEFCB8541
};

uint8_t timestamp_bytes[8]{};
// 8字节下的UID
uint8_t uid[]{ 0x50, 0x04, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00 };
// 动调得到MD5四个字节,加上最后4个0x04填充
uint8_t md5_and_pad[]{ 0xD2, 0x63, 0xE4, 0xE6, 0x04, 0x04, 0x04, 0x04 };

// 半时整点时间戳计算
FILETIME time{};
DWORD64 timestamp{};

GetSystemTimeAsFileTime(&time);

memcpy((void*)(&timestamp), (void*)(&time), 8);
timestamp = 1800 * ((timestamp / 0x989680 - 0x2B6109100LL) / 0x708);
memcpy((void*)(timestamp_bytes), (void*)(&timestamp), 8);

// 加密数据
tea_encrypt((uint32_t*)timestamp_bytes, (uint32_t*)key1);
tea_encrypt((uint32_t*)uid, (uint32_t*)key2);
tea_encrypt((uint32_t*)md5_and_pad, (uint32_t*)key3);

printf("flag{");
for (int i = 0; i < 8; i++)
{
printf("%02X", timestamp_bytes[i]);
}
for (int i = 0; i < 8; i++)
{
printf("%02X", uid[i]);
}
for (int i = 0; i < 8; i++)
{
printf("%02X", md5_and_pad[i]);
}
printf("}");
return 0;
}

心得

动调调试分析程序主体流程很重要,要先了解大概执行框架才能逐步往下层分析,且上层一些代码条件有助于下层的分析。

然后踩了一个严重的坑就是IDA伪代码里面的变量值和实际值一些情况下是不一样的,之前写题没在意那么多,这次很多地方都发现有这种问题,卡了我分析好久。所以关键代码段最好用汇编逐步分析,看实际数据的变化。