首页
社区
课程
招聘
[原创]2019看雪CTF_第五题 青梅竹马
2019-3-24 12:15 4071

[原创]2019看雪CTF_第五题 青梅竹马

DCO 活跃值
1
2019-3-24 12:15
4071

验证条件分析

第一部分验证

直接bp GetDlgItemTextA,定位到关键点后,进入IDA分析

下面这段代码,主要是验证输入的key必须满足一定的条件才能进行下一步的验证


.text:004026D7                 call    esi ; GetDlgItemTextA ; 获取输入key
.text:004026D9                 mov     [ebp+nLen], eax
.text:004026DC                 lea     eax, [ebp+buf_2]
.text:004026E2                 push    eax             ; void *
.text:004026E3                 push    ebx             ; int
.text:004026E4                 call    GetStr
.text:004026E9                 cmp     [ebp+nLen], 12
.text:004026ED                 pop     ecx
.text:004026EE                 pop     ecx
.text:004026EF                 mov     [ebp+nIndex], ebx
.text:004026F2                 mov     [ebp+nFlag], ebx
.text:004026F5                 jge     short loc_4026FE ; 判断输入key_len,大于12就置位nFlag
.text:004026F7                 mov     [ebp+nFlag], 1
.text:004026FE
.text:004026FE loc_4026FE:
.text:004026FE                 cmp     [ebp+key], 'A'
.text:00402705                 jnz     short loc_402713
.text:00402707                 cmp     [ebp+key+1], 'A'
.text:0040270E                 jnz     short loc_402713 ; if(key[0] == 'A' || key[1] == 'A')
.text:0040270E                                              ; 置位nFlag
.text:00402710                 inc     [ebp+nFlag]
.text:00402713
.text:00402713 loc_402713:
.text:00402713                 cmp     [ebp+key+5], 'V'
.text:0040271A                 jnz     short loc_402725
.text:0040271C                 cmp     [ebp+key+0Bh], 'V'
.text:00402723                 jz      short loc_402728 ; if(key[5] != 'V' || key[11] != 'V')
.text:00402723                                              ; 置位nFlag
.text:00402725
.text:00402725 loc_402725:
.text:00402725                 inc     [ebp+nFlag]
.text:00402728
.text:00402728 loc_402728:

通过后面的代码可知nFlag 必须等于0,否则验证失败。也就是说

key_len >= 12

key[0] != 'A'  && key[1] != 'A'

key[5] == 'V' && key[11] == 'V'

第二部分验证

第二部分验证是在这里

.text:0040283E                 call    _memcmp

有一个全局的数组g_word_buf

判断g_word_buf[key[i]] & 0x107 结果,决定是否置位nFlag。

这里是验证输入的key的每个字符的合法性

.text:0040272A                 cmp     [ebp+nLen], ebx
.text:0040272D                 jle     short loc_40278E
.text:0040272F                 mov     edi, 107h
.text:00402734
.text:00402734 loc_402734:
.text:00402734                 cmp     esi, 5
.text:00402737                 jz      short loc_402782
.text:00402739                 cmp     esi, 0Bh
.text:0040273C                 jz      short loc_402782 ; 剔除 key[5] 和 key[11],也就是那两个等于 'V' 的
.text:0040273E                 cmp     g_flag, 1
.text:00402745                 mov     cl, [ebp+esi+key]
.text:0040274C                 mov     [ebp+var_5], cl
.text:0040274F                 jle     short loc_402762
.text:00402751                 movsx   eax, cl
.text:00402754                 push    edi             ; int
.text:00402755                 push    eax             ; int
.text:00402756                 call    __isctype
.text:0040275B                 pop     ecx
.text:0040275C                 pop     ecx
.text:0040275D                 mov     cl, [ebp+var_5]
.text:00402760                 jmp     short loc_402771
.text:00402762 ; ---------------------------------------------------------------------------
.text:00402762
.text:00402762 loc_402762:
.text:00402762                 mov     edx, dword ptr g_word_buf
.text:00402768                 movsx   eax, cl
.text:0040276B                 mov     ax, [edx+eax*2]
.text:0040276F                 and     eax, edi
.text:00402771
.text:00402771 loc_402771:
.text:00402771                 cmp     eax, ebx
.text:00402773                 jz      short loc_40278A
.text:00402775                 mov     eax, [ebp+nIndex]
.text:00402778                 inc     [ebp+nIndex]
.text:0040277B                 mov     [ebp+eax+key_], cl
.text:00402782
.text:00402782 loc_402782:
.text:00402782
.text:00402782                 inc     esi
.text:00402783                 cmp     esi, [ebp+nLen]
.text:00402786                 jl      short loc_402734
.text:00402788                 jmp     short loc_40278E
.text:0040278A ; ---------------------------------------------------------------------------
.text:0040278A
.text:0040278A loc_40278A:
.text:0040278A                 add     [ebp+nFlag], 3  ; 置位nFlag,验证失败

算法部分

来到下面的部分,看来还是F5清晰

其中W_402297()是一个查表类的加密,后来验证是RSA,算法不及格,也不知道该怎么描述这部分,先放在这里,后面逆推key时还会用到

W_40231E()这个又把加密后的结果给还原回来了,并且还在后面调用memcpy()进行比较

if (!nFlag)
{
	buf_3[0] = 0;
	memset(&buf_3[1], 0, 0xFCu);
	*(_WORD *)&buf_3[253] = 0;
	buf_3[255] = 0;
	buf_4[0] = 0;
	memset(&buf_4[1], 0, 0xFCu);
	*(_WORD *)&buf_4[253] = 0;
	buf_4[255] = 0;
	buf_5[0] = 0;
	memset(&buf_5[1], 0, 0xFCu);
	*(_WORD *)&buf_5[253] = 0;
	buf_5[255] = 0;
	v5 = String[0];
	xor_402889(80, buf_5, 0x40u);
	W_402270(buf_5);
	len = W_402297((unsigned __int8 *)key_, buf_3);// 这里算是RSA的加密,加密结果保存在buf_3
	W_40231E((unsigned __int8 *)buf_3, buf_4, len);// 根据buf_3生成buf_4,buf_4必须与key_相同,下面会有验证
	if (buf_3[0])
	{
		if (!memcmp(buf_4, key_, nIndex))
		{
			v7 = sub_4024E1(100, v5, (int)buf_3, len);
			GetStr(v7, buf_2);                      // v7 == 2  才会注册成功
		}
	}
}
MessageBoxA(hDlg, buf_2, buf_2, 0);

通过几次验证发现,只要是sub_4024E1()返回2就是验证成功

那么重点就看看这个函数的内部实现

大数计算部分

进入sub_4024E1()

其实一开始我根本也不知道它是啥玩意,是含泪一步一步肛的,看F5是在是看不懂,太乱了,就大致的还原了一下C逻辑

搞了几个后发现是大数据的计算

我就开始尝试着猜了……,慢慢的就都猜出来了

先说一下对象结构,大致是下面这个东西,我也不知道是不是作者使用的大数库,还是他自己写的

struct OBJ 
{
	DWORD unknow;
	DWORD len;
	BYTE* pbuf;
};
 魔改后的代码
int sub_4024E1(int nNum1, int nNum2, char* pBuf, int nBufLen)
{
	int szBuf[100] = { 0 };

	OBJ obj[4];

	int nNum1 = 0;
	int nNum2 = 0;
	int nRet = 0;

	nNum1 = sub_40243F(nNum1, (int*)szBuf);   //没进入看,不过每次生成的nNum1都是固定值0x53

	sub_40115C(obj[0]);     //应该是构造函数/初始化函数
	sub_40115C(obj[1]);
	sub_40115C(obj[2]);
	sub_40115C(obj[3]);
	ObjRollback(obj[1], pBuf, nBufLen); //将字符串反转,赋值给对象
	nRet = sub_4021A7(obj[3]);

	int nIndex = 1;
	if (nNum1 > 1)
	{
		for (int i = 0; i < nNum1; ++i, ++nIndex)
		{
			if (szBuf[nIndex] == nNum2)
			{
				nNum1 = szBuf[1 + i];
				break;
			}
			if (szBuf[nIndex] == 0)
			{
				continue;
			}
			ObjMul(obj[0], obj[0], szBuf[nIndex]);   //obj_0 *= szBuf[nIndex]
		}
	}
	//比较对象 obj[3] <= obj[1] <= obj[0]
	if (CmpObj(obj[1], obj[3]) < 0 || CmpObj(obj[1], obj[0]) > 0)
	{	
		return nRet;
	}
	sub_4020F1(obj[2], obj[1], nNum1, obj[0]);   //obj[2] = obj_1^nNum % obj_0
	//obj[3] <= obj[2] <= obj[0]
	if (CmpObj(obj[2], obj[3]) < 0 || CmpObj(obj[2], obj[0]) > 0)
	{
		return nRet;
	}
	//只有返回2才会注册成功
	return sub_4021A7(obj[2]);
}

大致流程如上图所示,这里就从后往前看了,先看sub_4021A7(obj[2]);

该函数返回2才会注册成功,还原后的类C代码

该函数的返回值,还会由sub_4011A9(obj)来决定,也就是只有sub_4011A9(obj)返回值小于0x20才可能注册成功

int sub_4021A7(OBJ& obj)
{
	int nRet = sub_4011A9(obj);
	if (nRet < 0x20)
	{
		//只有走到这里才可能成功
		if (obj.nNum1 <= 0)
		{
			return -obj.buf[0];
		}
		else
		{
			return obj.buf[0];
		}		
	}
	if (obj.nNum1 > 0)
	{
		nRet = 0x7FFFFFFF;
	}
	else
	{
		nRet = 0x80000000;
	}
	return nRet;
}

再次进入sub_4011A9(obj);

首先一个循环从后向前验证obj.buf[]中第一个不为0的元素

然后取出该元素进行移位处理

最终可以推算出i = 0, j = 31  ---》 nNum = 2/-2 = obj[0]

也就是obj[0]只能是2或-2

int sub_4011A9(OBJ& obj)
{
	int nNum = 0;
	int i = 0;
	int j = 0;
	for (i = obj.len - 1; i >= 0; --i)
	{
		if (obj.buf[i] != 0)
		{
			break;
		}
	}
	nNum = obj.buf[i];
	for (j = 31; j > 0; --j)
	{
		if ((nNum >> j) == 1)
		{
			break;
		}
	}
	i <<= 5; // i 只能是0
	/*
	i = 0, j = 31  ---》 nNum = 2/-2 = obj[0]
	*/
	return i + j + 1;  // < 32
}

再来看看sub_4020F1(obj[2], obj[1], nNum1, obj[0]);这个函数

该函数实际上是计算:obj[2] = obj_1^nNum % obj_0

明显是一个RSA的运算

经过几次尝试发现nNum和obj_0是固定的,obj_1与我们输入的key相关,最终计算出来的结果obj[2].buf[0]只能是2或-2

逆推key

将上面的结果整理成公式,也就是下面这个样子

X^0x53 % 0x41CD66ACC237B22681A18067 = 0xFFFFFFFE 

X^0x53 % 0x41CD66ACC237B22681A18067 = 0x2


其中X与我们输入有关,0x53就是上面的nNum,0x41CD66ACC237B22681A18067是上面的obj_0

整理成RSA相关参数

c=0xFFFFFFFE / 2

n=0x41CD66ACC237B22681A18067 

e=0x53 

p=? 

q=? 

D=? 

m=?


那么就是分解n的问题了,使用在线大数分解http://factordb.com,注意输入只能是10进制,悲催的是有好多,如何找到那个符合我们条件的?



求解p、q

通过上面的大数分解结果可知:

0x41CD66ACC237B22681A18067=3*5*7*11*13*17*19*23*29*31*37*41*43*47*53*59*61*67*71*73

这是一个组合数的问题…………

也就是之前高中学的20个数,任取几个有多少种组合的问题,算了数学不好,还是上网上A算法牛的代码吧…

代码也记不清是A的谁的了,在这里说声谢谢

自己加上大数库,改一改就出来了

#pragma comment(lib, "miracl/miracl.lib")

miracl *mip = mirsys(400, 16);   //初始化一个400位16进制的大数系统
big m = mirvar(0);  //m明文
big c = mirvar(0);  //c密文
big p = mirvar(1);  //大素数p
big q = mirvar(0);  //大素数q
big n = mirvar(0);  //n模数
big pn = mirvar(0); //欧拉函数值pn = (p - 1)(q - 1)
big d = mirvar(0);  //d私钥
big e = mirvar(0);  //e公钥
big A = mirvar(0);

unsigned int result[20];

void zuhe(unsigned int ary[], int N, int K, int index, int deep)
{
	if (deep >= K)
	{
		for (int i = 0; i < K; i++)
		{
			//printf("%d ", result[i]);
			premult(p, result[i], p);  //p = p * result[i]
		}
		//q = n / p; 执行完n的值会发生变化
		divide(n, p, q);

		decr(p, 1, p);       //p = p - 1
		decr(q, 1, q);       //q = q - 1
		multiply(p, q, pn);  //pn = (p - 1)(q - 1)

		xgcd(e, pn, d, d, d);    //计算d = e^-1 mod n

		//c = m ^ d % n;
		cinstr(n, "41CD66ACC237B22681A18067");
		powmod(m, d, n, c);
		powmod(c, e, n, A);
		if (!mr_compare(A, m))
		{
			//找到就输出结果
			cotnum(p, stdout);
			cotnum(q, stdout);
		}
		//清零
		p = mirvar(1);
		q = mirvar(0);
		pn = mirvar(0);
		c = mirvar(0);

		return;
	}
	for (int i = index; i < N; i++)
	{
		result[deep] = ary[i];
		deep++;
		zuhe(ary, N, K, index + 1, deep);
		deep--;
		index++;
	}
}
int sumzuhe(int N, int K)
{
	if (K == 0)
		return 1;
	if (N == K)
		return 1;
	return sumzuhe(N - 1, K - 1) + sumzuhe(N - 1, K);
}
int main(int argc, char **canttrustthis)
{
	unsigned int ary[] = { 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73 };

	//将大数字符串转换成大数
	mip->IOBASE = 16;
	cinstr(n, "41CD66ACC237B22681A18067");
	convert(2, m);
	convert(0x53, e);
	for (int i = 1; i < 11; ++i)
	{
		zuhe(ary, 20, i, 0, 0);
	}
	system("pause");
	return 0;
}

最终计算出p=0x26AB4AE3、q=1B3A11F1BC8BC97AD

那么其他的参数也就都可以计算出来了

最终计算出X^0x53 % 0x41CD66ACC237B22681A18067 = 0x2

X= F9C083ECCADBB0380FD35515

反转一下是:1555D30F38B0DBCAEC83C0F9

将该串带入到sub_40231E()进行解密,解密后的结果是"PEDIy9102dre",然后手动加上V,也就是”PEDIyV9102dVre”

兴冲冲的一试,居然不对,WOW……………

说明:现在回头看看其实是自己没改长度,才会出现少4位的。。。。

过不去的坎

那就从头再梳理一遍,发现在调用sub_402297()时出了猫腻

输入PEDIyV9102dVre,生成的结果是15 55 D3 0F 38 B0 DB CA EC

结果与预想的竟然少3位,少了83C0F9

欠的总要还的,在仔细研究研究sub_402297()函数把。撸出来是下面这个样子

类是与Base64的一种计算,4个字符生成3个

/*
* 功能:类似于Base64
[j]   = [i]*4    | [i+1]/16
[j+1] = [i+1]*16 | [i+2]/4
[j+2] = [i+2]*64 | [i+3]
*/
int sub_402297(unsigned char* buf1, unsigned char* buf2)
{
	int i = 0;
	int nNum = 0;
	int nTmp = 0;
	unsigned char* pBuf1 = buf1;
	unsigned char* pBuf2 = buf2;
	//if (g_40B506 == 0)
	//{
	//	sub_40224F();
	//}
	while (TRUE)
	{
		nTmp = g_40B404[*pBuf1];
		nNum = nTmp * 4;
		++pBuf1;
		if (nTmp >= 0xC0)
		{
			break;
		}

		nTmp = g_40B404[*pBuf1];
		++pBuf1;
		if (nTmp >= 0xC0)
		{
			break;
		}
		*pBuf2 = (nTmp / 16) | nNum;
		nNum = nTmp * 16;
		++pBuf2;

		nTmp = g_40B404[*pBuf1];
		++pBuf1;
		if (nTmp >= 0xC0)
		{
			break;
		}
		*pBuf2 = (nTmp / 4) | nNum;
		++pBuf2;
		nNum = nTmp * 64;

		nTmp = g_40B404[*pBuf1];
		++pBuf1;
		if (nTmp >= 0xC0)
		{
			break;
		}
		*pBuf2 = nTmp | nNum;
		++pBuf2;
	}
	*pBuf2 = 0;
	return pBuf2 - buf2;
}

也就是说我们还差4个字符,那么爆破吧


unsigned char g_40B404[] =
{
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0x3F,
	0x30, 0x22, 0x36, 0x37, 0x38, 0x2E, 0x3A, 0x3B, 0x1D, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0x00, 0x01, 0x02, 0x17, 0x15, 0x0F, 0x06, 0x07, 0x13, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
	0x05, 0x10, 0x11, 0x12, 0x08, 0x14, 0x04, 0x16, 0x32, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0x20, 0x1B, 0x1C, 0x3C, 0x2C, 0x1F, 0x1A, 0x21, 0x35, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
	0x29, 0x2A, 0x2B, 0x1E, 0x2D, 0x39, 0x2F, 0x34, 0x31, 0x03, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
char szTable[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int _tmain(int argc, _TCHAR* argv[])
{
	unsigned char szBuf[10] = { 0 };
	unsigned char szResult[10] = { 0 };
	for (int i = 0; i < (int)strlen(szTable); ++i)
	{
		szBuf[0] = szTable[i];
		for (int j = 0; j < (int)strlen(szTable); ++j)
		{
			szBuf[1] = szTable[j];
			for (int k = 0; k < (int)strlen(szTable); ++k)
			{
				szBuf[2] = szTable[k];
				for (int l = 0; l < (int)strlen(szTable); ++l)
				{
					szBuf[3] = szTable[l];
					sub_402297(szBuf, szResult);
					if (szResult[0] == 0x83 &&
						szResult[1] == 0xC0 &&
						szResult[2] == 0xF9)
					{
						printf("%s\n", szBuf);
						system("pause");
					}
				}
			}
		}
	}
	return 0;
}

最终爆出来是adyu

最终的key: PEDIyV9102dVreadyu

尴尬写完这个都过了12点了……



[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2019-3-29 14:37 被kanxue编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (2)
雪    币: 368
活跃值: (346)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
meanwhile 2019-3-24 12:42
2
0
顶,没做出来。。。
雪    币: 301
活跃值: (275)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
余生挚爱传奇 1 2019-3-24 13:24
3
0
只做了第一题
游客
登录 | 注册 方可回帖
返回