第七届强网杯RE dotdot复现

作者本人的文章:[原创]强网杯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

用时三天完成了这道题的复现,但还是有很多细节地方不明不白。但是大体就是这样了。

  • 39
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值