作者本人的文章:[原创]强网杯2023 dotdot 题解及设计思路-CTF对抗-看雪-安全社区|安全招聘|kanxue.com
白盒AES的文章:[原创]一种还原白盒AES秘钥的方法-Android安全-看雪-安全社区|安全招聘|kanxue.com
白盒的文章:白盒加密 – To1in's blog
先说一下这道题主要涉及的考点:白盒AES、C#反序列化漏洞(不知道具体是啥子.......)、RC4文件加密、经典的tea加密。
我会把复现这道题的整个历程尽可能地用文字展现出来。
首先一开始的时候我先把文件托入了die分析一下
是NET应用,再用exeinfopen看的话可以发现是C#语言,所以我选择了DnSpy用来反编译它。这里要注意选择32位的。
它有一个很明显的main函数,初步看也应该是真的main函数,所以我们依据main函数分析即可。
点进去BBB观察,
发现就是判断我们的输入是否16字节,然后copy给了v31,我们回去接着看。
进入AAA函数接着分析,看见了9和4的循环数,大致就猜到这是个AES加密,再仔细看又更进一步确认是白盒AES(其实这里我也还不太懂白盒AES,大致是查表防曝光密钥),但是跟典型白盒AES对比还是很好看出来的。白盒AES可以看下这篇AES白盒加密解读与实现(Chow方案)-CSDN博客
之后就是对白盒AES的逆向了,因为我们只有密文,所以我们要想办法把密钥搞出来,这里要介绍一下强大的python库phoenixAES,同时可以看下这篇文章找回消失的密钥 --- DFA分析白盒AES算法 - 奋飞安全
这个库可以从这里下载https://github.com/SideChannelMarvels/JeanGrey/tree/master/phoenixAES
可能也可以在pip下载,但是我记不太清我当时怎么下的了,不过这两种坑定至少有一种是可行去下载这个库的。
然后如果是上面链接下载可以搜下把这个库要安到python环境的哪个具体位置。
我们继续求解这个白盒AES的密钥。简单说就是在第九轮时改密文的一个数据,来影响最终加密出的密文数据,如果修改时机正确,那么将改变四个字节的值。以同样的明文输入来修改十六次,得出十六组错误数据再加上一组正确加密数据,基本就可以通过这个库来将第十轮的密钥吐出来。
import phoenixAES
inp = "qwertyuiopqwerty"
dump = """3ECD176E1D95235F07DD0DB18F354B2C0ECD176E1D95233F07DDECB18FE04B2C9ACD176E1D95238C07DD1DB18FF34B2CDCCD176E1D95234307DD0FB18FF04B2C4BCD176E1D9523FF07DD49B18FCF4B2CBFCD176E1D95230607DDAFB18FB94B2C30CD176E1D95238D07DD5BB18F184B2C3DCD176E1D95238807DD7DB18F514B2CF9CD176E1D95239807DD17B18F494B2C12CD176E1D95232B07DD6BB18F634B2CC0CD176E1D95237807DD33B18FDC4B2C42CD176E1D95233107DD1FB18F594B2C97CD176E1D9523E407DDBEB18F0C4B2CF5CD176E1D9523B207DD5EB18FDD4B2C5DCD176E1D95231E07DD68B18F104B2CEFCD176E1D95236107DDD1B18F384B2CC5CD176E1D95231B07DD51B18F714B2C"""
with open(".\dump", "wb") as f:f.write(dump.encode())
phoenixAES.crack_file('.\dump', verbose=0)# Last round key #N found:# EA9F6BE2DF5C358495648BEAB9FCFF81
然后就是根据第十轮密钥得出初始密钥,方法我记得有python库也能做到来着,但是我这里找了其他工具,
GitHub - SideChannelMarvels/Stark: Repository of small utilities related to key recovery 同样在上面的链接中有提到。
把文件下载到LInux上,然后在该文件目录下打开终端输入make指令,就能生成想要的程序文件,然后用于解密出第一轮密钥。
灰常好用。
拿到AES密钥后就是正常的AES解密了,这里可以自行在网上搜集脚本解。
解出我们的输入即AES密钥:WelcomeToQWB2023
下一步我们在回到main函数看。
这一步AAA发现两个参数和我的输入都没啥联系,就没管了。
之后发现确实没啥用好像。
这里有一点是动调可以看v4这个对比字符串来作为AES密文,也可以用更好点的dotpeek来静态看(dotpeek是真的挺卡慢的)。CCC就是个比较函数。
再下面分析就是调用了License.dat这个文件,看了下EEE函数,是rc4加密,综合来看其实是对License这个文件进行了rc4解密,rc4是对称加密,加密即解密。似乎是不需要管,但是如果我们这里再往下调试,就会触发反序列化报错,结合捕捉异常发现是在License文件的294字节处。而为了看这个位置的东西,我们就必须先把这个文件解密一遍才能看到真实数据。
我给一个rc4脚本加了点东西,以便对文件加解密。如下
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include <sys/stat.h>
#include<Windows.h>
/*
RC4初始化函数
*/
void rc4_init(unsigned char* s, unsigned char* key, unsigned long Len_k)
{
int i = 0, j = 0;
char k[256] = { 0 };
unsigned char tmp = 0;
for (i = 0; i < 256; i++) {
s[i] = i;
k[i] = key[i % Len_k];
}
for (i = 0; i < 256; i++) {
j = (j + s[i] + k[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
/*
RC4加解密函数
unsigned char* Data 加解密的数据
unsigned long Len_D 加解密数据的长度
unsigned char* key 密钥
unsigned long Len_k 密钥长度
*/
void rc4_crypt(unsigned char* Data, unsigned long Len_D, unsigned char* key, unsigned long Len_k) //加解密
{
unsigned char s[256];
rc4_init(s, key, Len_k);
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for (k = 0; k < Len_D; k++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
t = (s[i] + s[j]) % 256;
Data[k] = Data[k] ^ s[t];
}
}
int main()
{
// //加解密字符串用
//
// unsigned char key[] = "zstuctf";
// unsigned long key_len = sizeof(key) - 1;
// //数组密钥
// //unsigned char key[] = {};
// //unsigned long key_len = sizeof(key);
//
// //加解密数据
// unsigned char data[] = { 0x7E, 0x6D, 0x55, 0xC0, 0x0C, 0xF0, 0xB4, 0xC7, 0xDC, 0x45,
// 0xCE, 0x15, 0xD1, 0xB5, 0x1E, 0x11, 0x14, 0xDF, 0x6E, 0x95,
// 0x77, 0x9A, 0x12, 0x99 };
// //加解密
// rc4_crypt(data, sizeof(data), key, key_len);
// for (int i = 0; i < sizeof(data); i++)
// {
// printf("%c", data[i]);
// }
// printf("\n");
// return;
//
//**************************************************
//加解密文件
unsigned char key[] = "WelcomeToQWB2023";//密钥
unsigned int key_len =16;
FILE* fp = fopen("License.dat", "rb");//读取文件内容,文件要放到目录下
if (fp != NULL)
printf("读取成功");
fseek(fp, 0, SEEK_END);
int last = ftell(fp);
int size = last;
fseek(fp, 0, SEEK_SET);
PBYTE pBuf = (PBYTE)malloc(size);
fread(pBuf,size,1, fp);
rc4_crypt(pBuf, size, key, key_len);//文件解密或加密,对称性,加密即解密
fp = fopen("LIC.dat", "wb");
if (fp == NULL)
printf("创建失败");
fwrite(pBuf,size,1, fp);//重新写回去
fclose(fp);
fclose(fp);
free(pBuf);
}
//之前不知道为什仫一直有问题,但是现在好了,可能是大小之前搞错了。
完成后,我们把解密后的文件拖入010editor查看294位置会发现是一堆0,然后这个点我觉得我是想不到的,但是作者确实是这样出的,如果我们再观察这个exe文件,会发现FFF函数一直没用到,然后它是个tea,再看一下License文件的内容,发现似乎对FFF进行了调用,结合具体代码可以发现FFF是个验证功能,它的参数,一个是密钥,一个是明文,结合之前解出的十六字节的WelcomeToQWB2023,不难猜测它是这个tea的密钥,然后再根据密文求得明文即可。这里建议用dotpeek查看比较字符串,即密文。
#include <stdio.h>
#include <stdint.h>
#include<string.h>
//加密函数
void encrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = -709370400, i; /* set up */
uint32_t delta = 0x9e3779b9; /* a key schedule constant */
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
} /* end cycle */
v[0] = v0; v[1] = v1;
}
//解密函数
void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], i; /* set up */
uint32_t delta = 3735928559; /* a key schedule constant */
unsigned int sum = delta * 32;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum )^ ((v1 >> 5) + k1);
sum -= delta;
} /* end cycle */
v[0] = v0; v[1] = v1;
}
int main()
{
int i,len,j;
//密文或明文
char eninput[100]={69,182,171,33,121,107,254,150,92,29,4,178,138,166,184,106,53,241,42,191,23,211,3,107};
len = strlen(eninput);
eninput[len] = 0;
for (i = 0; i < len / 8; i++)
{
int* v = (int*)eninput+i*2, k[] = { 0x636c6557,0x54656d6f,0x4257516f,0x33323032 };
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
/* encrypt(v, k);
printf("加密后的数据:%u %u\n", v[0], v[1]);*/
decrypt(v, k);
}
printf("%s",eninput);
return 0;//dotN3t_Is_1nt3r3sting
//WelcomeToQWB2023
}
然后,最后我虽然知道报错点在294,然后手里也拿着两段明文,结合题目修复License文件,把这个两段明文作为参数填入到解密后的License文件中,
再rc4加密后,启动dot文件,输入第一段明文,得出flag
用时三天完成了这道题的复现,但还是有很多细节地方不明不白。但是大体就是这样了。