吾爱2025-Windows逆向高级题-5
CTF逆向小白解题全流程,过程中可能有些点有问题,请大佬们多多指教。
考点:异步消息执行,变种tea展开、变种MD5、时间戳、Flag分段检查
解题过程
这一段是获取两个编辑框的内容,即uid和flag,然后flag要符合异或的那一系列条件,实际格式是flag{...}。
跟到这边发现有一系列函数,main_program里面的执行验证按钮后主流程,execute是main_program里面通过不同消息来执行不同命令的函数。(都是自命名的函数,仅代表个人想法)
execute函数
其他消息:将flag括号内数据进行unhex(如1122字符串直接转成0x11,0x22数据)
0x35消息:获取当前半小时整点时间戳数据。
0x55消息:通过利用变种MD5+Salt将解密完数据的前十六字节计算得到4字节数值。
0x25消息:unhex后数据进行解密(Decrypt函数)。
main_program函数
第一部分
获取flag括号内数据通过消息分发执行execute的unhex消息,然后再执行execute的Decrypt函数,解密unhex后的数据,将解密完的数据长度赋值给v12。
第二部分
将解密完数据的前16字节进行custom_MD5,得到4字节数据,然后判断解密后数据第17个字节开始四个字节是否和计算得到的4字节数据相等。
如果相等就再次判断v12,即解密后数据长度,判断是否等于20。
再调用execute的时间戳获取消息,得到8字节时间戳数据。
最后再检查解密后数据前8字节是否等于时间戳数据,以及第九个字节往后8字节是否等于编辑框输入的uid。
结论
输入的flag得是被和Decrypt相对于的加密函数进行加密后的数据,加密前格式:{半时整点时间戳(8字节),uid(8字节),Custom_MD5(前面十六字节)(4字节),0x04填充(四个字节)}
最后一部分填充会在下面Decrypt函数里面说明来由。
Decrypt函数(sub_7FF7FAC92C40)
要求unhex后数据长度要是8的倍数,且利用一系列计算得到v12这个数据,参与内部解密的Key生成,最后还要求解密完的数据符合一系列条件验证。
解密后数据条件验证
从这部分逻辑代码可以分析,他是将最后v8指向最后一个数据,然后v9赋值最后一个数据,然后v8循环递减,直到当前v8指向v8开始往前的第v9个指针结束,然后最后解密后数据长度=当前长度-v9。
已知解密后前面已经占用了20字节(时间戳+uid+md5),在main_program也已知解密后数据长度要等于20,所以可以知道这边v9必须等于4,所以v8等于4,最后这边一共占用4个字节,即{4,4,4,4},这样经过这边的验证最后的size才会等于20。
dec函数
将unhex后数据按8字节分块进行tea的解密,tea加密的Key由上一层传入的v12通过RC4得到,且每次解密Key都会变化(固定变化),直接动调就可以拿到几次解密用到的Key值。
下面一系列解密就是tea的解密,不过是展开,可以数出一共是12轮,且Delta直接可以通过两次sum的值相减得到(由于tea解密这边应该是加上sum,ida伪代码展示是减,但是实际计算后数值一样),B979379E就是tea解密用到的Delta。
所以就可以通过动调得到的几次Key和Delta写出tea的加密代码。
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字节)。
然后将uid转为8字节字节数据拼接到时间戳字节后面。
MD5值暂时填充4个0x00,将MD5值和4个0x04字节拼接上。
将完整数据进行tea_encrypt,再用flag{}包裹填入编辑框进行验证。
在MD5生成代码处,断点在箭头处,即可得到MD5四字节数据。
最终再重复上面步骤即可得到flag。
完整代码
#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;
}
int main()
{
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]{};
uint8_t uid[]{ 0x50, 0x04, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00 };
uint8_t md5_and_pad[]{ 0xD2, 0x63, 0xE4, 0xE6, 0x04, 0x04, 0x04, 0x04 };
FILETIME time{};
DWORD64 timestamp{};
GetSystemTimeAsFileTime(&time);
memcpy((void*)(×tamp), (void*)(&time), 8);
timestamp = 1800 * ((timestamp / 0x989680 - 0x2B6109100LL) / 0x708);
memcpy((void*)(timestamp_bytes), (void*)(×tamp), 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伪代码里面的变量值和实际值一些情况下是不一样的,之前写题没在意那么多,这次很多地方都发现有这种问题,卡了我分析好久。所以关键代码段最好用汇编逐步分析,看实际数据的变化。