首页
论坛
课程
招聘
[原创]看雪.京东 2018CTF-第二题分析
2018-6-19 16:26 1146

[原创]看雪.京东 2018CTF-第二题分析

2018-6-19 16:26
1146
IDA载入程序后:
一、man函数分析
代码如下:
int __cdecl main(int argc, const char **argv, const char **envp)
{
	int result; // eax
	char key[32]; // [esp+10h] [ebp-44h]
	char* successBuf; // [esp+30h] [ebp-24h]
	int v6; // [esp+34h] [ebp-20h]
	int v7; // [esp+38h] [ebp-1Ch]
	int v8; // [esp+3Ch] [ebp-18h]
	char* wrongBuf; // [esp+40h] [ebp-14h]
	int v10; // [esp+44h] [ebp-10h]
	int v11; // [esp+48h] [ebp-Ch]
	int v12; // [esp+4Ch] [ebp-8h]

    //得到输入用于验证的key
	GetInputKey("%s", key, 32);

	//初始化key正确字符串
	SetSuccessString(&successBuf);

    //初始化key错误字符串
	SetWrongString(&wrongBuf);

	//判断输入的key的字符是否是大小写字母和数字
	if ( ifCharOrDigit(key, (int)&wrongBuf) )
	{
	    //输入的key所有字符都是大小写字母或数字,判断输入key长度是否是22,
		if ( &key[strlen(key) + 1] - &key[1] == 22 )
		{
		    //输入的key长度是22,则调用KeyCmp函数进一步判断
			KeyCmp(key, (char *)&successBuf, (char *)&wrongBuf);
		}
		else
	  	{
			//输入的key长度不是22,直接输入错误
			outputString(&wrongBuf);
		}
		wrongBuf = 0;
		v10 = 0;
		v11 = 0;
		v12 = 0;
		successBuf = 0;
		v6 = 0;
		v7 = 0;
		v8 = 0;
		system("pause");
		result = 0;
	}
	else
	{
	    //输入的key存在不是大小写字母和数字的情况,非法,直接返回
		successBuf = 0;
		v6 = 0;
		v7 = 0;
		v8 = 0;
		wrongBuf = 0;
		v10 = 0;
		v11 = 0;
		v12 = 0;
		result = 0;
	}
	return result;
}
1、调用GetInputKey函数获得用户输入的key,并保存到key字符串变量中。
2、调用SetSuccessString函数 使得 successBuf = “Answer correct!”。
3、调用SetWrongString函数,使得wrongBuf = “Answer is Wrong”。
4、调用ifCharOrDigit函数检测输入key字符是否非法:必须为"a-z, A-Z, 0-9",如果不是程序直接终止运行,无任何输出。
5、输入的key合法,则判断长度是否是22,如果不是,则调用outputString输出 “Answer is Wrong”。
6、如果输入的key是22,则调用KeyCmp函数进行进一步key校验。
二、 KeyCmp函数分析
int __cdecl KeyCmp(char *inputKey, char *successBuf, char *wrongBuf)
	{
	char key_19_20_21Buf[128]; // [esp+4h] [ebp-47Ch]
	char key_16_17_18Buf[128]; // [esp+88h] [ebp-3F8h]
	char key_7_8Buf[128]; // [esp+10Ch] [ebp-374h]
	char key_2_3buf[128]; // [esp+190h] [ebp-2F0h]
	char key_4_5_6Buf[128]; // [esp+214h] [ebp-26Ch]
	char key_9_10_11_12Buf[128]; // [esp+298h] [ebp-1E8h]
	char key_0_1Buf[128]; // [esp+31Ch] [ebp-164h]
	char key13_14_15Buf[128]; // [esp+3A0h] [ebp-E0h]
	int v12; // [esp+424h] [ebp-5Ch]
	int v13; // [esp+428h] [ebp-58h]
	int v14; // [esp+42Ch] [ebp-54h]
	int v15; // [esp+430h] [ebp-50h]
	int v16; // [esp+434h] [ebp-4Ch]
	int v17; // [esp+438h] [ebp-48h]
	int const_3; // [esp+43Ch] [ebp-44h]
	int const_2; // [esp+440h] [ebp-40h]
	char key_19char; // [esp+444h] [ebp-3Ch]
	char key_20char; // [esp+445h] [ebp-3Bh]
	char key_21char; // [esp+446h] [ebp-3Ah]
	char v23; // [esp+447h] [ebp-39h]
	char key_4char; // [esp+448h] [ebp-38h]
	char key_5char; // [esp+449h] [ebp-37h]
	char key_6char; // [esp+44Ah] [ebp-36h]
	char v27; // [esp+44Bh] [ebp-35h]
	char key_2char; // [esp+44Ch] [ebp-34h]
	char key_3char; // [esp+44Dh] [ebp-33h]
	char v30; // [esp+44Eh] [ebp-32h]
	char key_13char; // [esp+450h] [ebp-30h]
	char key_14char; // [esp+451h] [ebp-2Fh]
	char key_15char; // [esp+452h] [ebp-2Eh]
	char v34; // [esp+453h] [ebp-2Dh]
	char key_16char; // [esp+454h] [ebp-2Ch]
	char key_17char; // [esp+455h] [ebp-2Bh]
	char key_18char; // [esp+456h] [ebp-2Ah]
	char v38; // [esp+457h] [ebp-29h]
	int v39; // [esp+458h] [ebp-28h]
	int v40; // [esp+45Ch] [ebp-24h]
	char key_0char; // [esp+460h] [ebp-20h]
	char key_1char; // [esp+461h] [ebp-1Fh]
	char v43; // [esp+462h] [ebp-1Eh]
	char key_7char; // [esp+464h] [ebp-1Ch]
	char key_8char; // [esp+465h] [ebp-1Bh]
	char v46; // [esp+466h] [ebp-1Ah]
	char key_9char; // [esp+468h] [ebp-18h]
	char key_10char; // [esp+469h] [ebp-17h]
	char key_11char; // [esp+46Ah] [ebp-16h]
	char key_12char; // [esp+46Bh] [ebp-15h]
	char v51; // [esp+46Ch] [ebp-14h]
	int v52; // [esp+47Ch] [ebp-4h]

    //将输入的key拆分成8个字符串分别是:
    //char key_19_20_21Buf
	//char key_16_17_18Buf
	//char key_7_8Buf[128]
	//char key_2_3buf[128]
	//char key_4_5_6Buf[128]
	//char key_9_10_11_12Buf
	//char key_0_1Buf[2][128]
	//char key13_14_15Buf[3]
	const_2 = 2;
	v43 = 0;
	key_2char = inputKey[2];
	const_3 = 3;
	v27 = 0;
	key_1char = inputKey[1];
	key_12char = inputKey[12];
	key_16char = inputKey[16];
	key_9char = inputKey[9];
	key_7char = inputKey[7];
	key_10char = inputKey[10];
	key_8char = inputKey[8];
	key_20char = inputKey[20];
	v17 = 4;
	v51 = 0;
	key_15char = inputKey[15];
	v16 = 2;
	v30 = 0;
	v15 = 2;
	v46 = 0;
	key_6char = inputKey[6];
	key_14char = inputKey[14];
	key_4char = inputKey[4];
	key_3char = inputKey[3];
	key_13char[4] = inputKey[13];
	v14 = 3;
	v38 = 0;
	key_5char = inputKey[5];
	key_17char = inputKey[17];
	key_0char = *inputKey;
	key_21char = inputKey[21];
	v13 = 3;
	v34 = 0;
	key_19char = inputKey[19];
	key_11char = inputKey[11];
	key_18char = inputKey[18];
	v12 = 3;
	v23 = 0;
	ptreeHead = 0;
	v40 = 0;

	//创建一个空的字典树ptreeHead
	std::_Ref_count_obj<__ExceptionPtr>::_Ref_count_obj<__ExceptionPtr>(&ptreeHead);
	v52 = 0;

	//将输入的第13、14、15组成的字符串插入到字典树中
	copyBuf(key13_14_15Buf, &key_13char);
	TreeInsert(&ptreeHead, (int)key13_14_15Buf);

	//将输入的第0,1组成的字符串插入到字典树中
	copyBuf(key_0_1Buf, &key_0char);
	TreeInsert(&ptreeHead, (int)key_0_1Buf);

	//将输入的第9,、10、11、12组成的字符串插入到字典树中
	copyBuf(key_9_10_11_12Buf, &key_9char);
	TreeInsert(&ptreeHead, (int)key_9_10_11_12Buf);

	//将输入的第4、5、6组成的字符串插入到字典树中
	copyBuf(key_4_5_6Buf, &key_4char);
	TreeInsert(&ptreeHead, (int)key_4_5_6Buf);

	//将输入的第2、3组成的字符串插入到字典树中
	copyBuf(key_2_3buf, &key_2char);
	TreeInsert(&ptreeHead, (int)key_2_3buf);

	//将输入的第7、8组成的字符串插入到字典树中
	copyBuf(key_7_8Buf, &key_7char);
	TreeInsert(&ptreeHead, (int)key_7_8Buf);

	//将输入的第16、17、18组成的字符串插入到字典树中
	copyBuf(key_16_17_18Buf, &key_16char);
	TreeInsert(&ptreeHead, (int)key_16_17_18Buf);

	//将输入的第19、20、21组成的字符串插入到字典树中
	copyBuf(key_19_20_21Buf, &key_19char);
	TreeInsert(&ptreeHead, (int)key_19_20_21Buf);

	//将输入key组成的字典树与原始dword_977E48的字典树进行比较
	if ( callTreeCmp(&ptreeHead, (int)&dword_977E48) )
	{
	    //两个字典树相等,lastVerity进行最后的判断
		lastVerity(&key_0char, &key_7char, (int)&key_13char, (int)&key_16char, successBuf, wrongBuf);
	}
	else
	{
		//两个字典树不相等,输入错误
		outputString(wrongBuf);
	}
	v52 = -1;
	return sub_972AC0();
}
1、KeyCmp函数首先将输入的key拆分成8个字符串,如下:
  key_19_20_21Buf
  key_16_17_18Buf
  key_7_8Buf
  key_2_3buf
  key_4_5_6Buf
  key_9_10_11_12Buf
  key_0_1Buf
  key13_14_15Buf
2、创建一个空的字典树 :ptreeHead
      函数__thiscall std::_Ref_count_obj<__ExceptionPtr>::_Ref_count_obj<__ExceptionPtr>如下:
_DWORD *__thiscall std::_Ref_count_obj<__ExceptionPtr>::_Ref_count_obj<__ExceptionPtr>(_DWORD *this)
{
  _DWORD *v1; // ST14_4
  _DWORD *Memory; // ST0C_4

  v1 = this;
  sub_973070(this);
  *v1 = &TrieTree::`vftable';
  Memory = operator new(0x110u);
  v1[1] = sub_973100(Memory);
  return v1;
}
通过搜索“TrieTree”可知是字典树的创建,链接如下字典树(Trie tree) - Rollen Holt - 博客园,关于字典树可以看这个链接的介绍。
3、调用TreeInsert开始将上面的8个字符串插入到 ptreeHead字典中。
4、调用callTreeCmp函数将 ptreeHead字典数与原始的dword_977E48字典树进行比较
5、如果2个字典树不等,则调用outputString直接输入错误
6、如果2个字典树相等,则调用lastVerity函数进行最后的校验。
三、  dword_977E48 字典树初始化
1、在程序开始运行时,在执行main之前,调用initterm_e(&unk_975154, &unk_975160) 进行初始化,树的结构如下:


2、treeNode 数据结构如下:
//treeNode
//offset		type		len
///+4			char*		0x80		该节点的字符串,每个节点字符长度最大0x80
//+0x84			int			4			该节点字符串长度
//+0x88			int*		0x80		存储每个字节点的地址,最多有0x20个子节点
//+0x108		int			4			子节点个数
//+0x10C		int			4			该节点字符串个数 = 0 表示没有对应字符串

typedef struct treeNode
{
	int  TrieTreeFlag;				 //标识??TrieTree
	char string[0x80];					//该节点的字符串
	int  len;							 //字符串长度
	treeNode*  childNod[0x20];		 //子节点数组
	int childNum;						//子节点个数
	int stringNum;						//该节点字符串个数 = 0 表示没有对应字符串
}treeNode;
对于treeNode结构,比较关键的是成员 stringNum:
stringNum = 0 表示该节点并没有字符串
stringNum = 1 表示该节点只有一个字符串
stringNum = 2 表示该节点存在2个相同的字符串
    
3、从上面的原始节点可知,存在如下字符串:
kx    c7    c7M(2个)    c7Mk    ct    ct9    ctf,恰好是8个字符串,与将输入key拆分成8个字符串正好对应。因此输入key被拆分成的8个字符串一定是:
“kx    c7    c7M(2个)    c7Mk    ct    ct9    ctf ” 这8个字符串。
还是继续分析下treeCmp函数。

四、treeCmp 字典树比较
char __thiscall treeCmp(struct treeNode *inputTree, struct treeNode *orgKeyTree)
{
	const char *v3; // eax
	char v4; // [esp+0h] [ebp-A4h]
	int *childNodeEndAddr; // [esp+84h] [ebp-20h]
	int num2; // [esp+88h] [ebp-1Ch]
	int num1; // [esp+8Ch] [ebp-18h]
	int *v8; // [esp+90h] [ebp-14h]
	int v9; // [esp+94h] [ebp-10h]
	struct treeNode **inputChildNode; // [esp+98h] [ebp-Ch]
	int *childNodeStartAddr; // [esp+9Ch] [ebp-8h]
	struct treeNode *inputTree_1; // [esp+A0h] [ebp-4h]

	inputTree_1 = inputTree;

	//输入的2个字典树根节点字符串比较,如果不等,则返回错误
	if ( callTreeStringCmp(inputTree->string, orgKeyTree->string) )
		return 0;

	//获取输入key的树的子节点个数
	num1 = inputTree_1->childNum;

	//获取原始树的子节点个数
	num2 = orgKeyTree->childNum;

	//如果树的子节点个数不等,则返回失败
	if ( num1 != num2 )
		return 0;

	//如果2个节点的字符串个数不同
	if ( inputTree_1->stringNum != orgKeyTree->stringNum)
		return 0;

	//此时说明2个节点是相同的,开始比较子节点是否相同
	inputChildNode = inputTree_1->childNode;

	//获取inputTree的子节点数组的起始地址
	childNodeStartAddr = (int *)inputTree_1->childNode;

	//获取inputTree的子节点数组的结束地址
	childNodeEndAddr = (int *)&inputTree_1->childNode[inputTree_1->childNum];

	//递归遍历inputTree的子节点
	while ( childNodeStartAddr != childNodeEndAddr )
	{
		//获取inputTree对应子节点的字符串
		v9 = *childNodeStartAddr;
		v3 = (const char *)stringCpy(v9, &v4);

		//从原始的字典树中查找对应字符串的节点
		v8 = (int *)findStringFromTree(&orgKeyTree->constAddr, v3);

		//如果没找到,则返回0
		if ( !v8 )
			return 0;

		//如果找到了则继续进行递归比较
		if ( calltreeCmp(v8, v9) )
			return 0;

		//下一个子节点
		++childNodeStartAddr;
	}
	return 1;
}
1、判断2个字典树的子节点个数是否相同,不同则返回失败。
2、判断2个字典数根节点的字符串个数是否相同,不同则返回失败。
3、对2个树的根节点字符串进行比较,如果不同,则返回失败。
4、开始遍历树的子节点。
5、获取第一个树子节点的字符串,从第2个树中查找对应的字符串,如果没找到,则返回失败。
6 、然后继续将子节点和findStringFromTree找到的节点树作为参数,调用calltreeCmp进行递归比较。
7、重复1-6的过程,直至整个树比较完成。

五、里程碑
1、从上面分析可知输入的key被拆分成8个字符串,然后将这8个字符串创建一个字典树,为inputKeyTree,与原始的字典树进行比较,原始的字典树见上面“三”的树图。
2、因此可以确定输入的字典树必须为上面的"三"对应树图。
3、输入的key拆分的8个子字符串为:

  key_19_20_21Buf

  key_16_17_18Buf

  key_7_8Buf

  key_2_3buf

  key_4_5_6Buf

  key_9_10_11_12Buf

  key_0_1Buf

  key13_14_15Buf

  4、从上面分析可知,只有一个字符串的长度为4个字符( key_9_10_11_12Buf ),而从字典树中,只有一种可能是4个字符,即为:c7Mk,因此
 inputkey[0]=?
 inputkey[1]=?
 inputkey[2]=?
 inputkey[3]=?
 inputkey[4]=?
 inputkey[5]=?
 inputkey[6]=?
 inputkey[7]=?
 inputkey[8]=?
 inputkey[9]='c' 
 inputkey[10]='7' 
 inputkey[11]='M'
 inputkey[12]='k'
 inputkey[13]=?
 inputkey[14]=?
 inputkey[15]=?
 inputkey[16]=?
 inputkey[17]=?
 inputkey[18]=?
 inputkey[19=?
 inputkey[20]=?
 inputkey[21]=? 
再分析下lastVerity函数
六、lastVerity 函数
int __cdecl lastVerity(char *key_0_1Buf, char *key_7_8Buf, char *key13_14_15Buf, char *key_16_17_18Buf, char *successBuf, char *eorBuf)
{
  int result; // eax

  if ( (key_0_1Buf[1] ^ *key_0_1Buf) != 0x54
    || (key_7_8Buf[1] ^ *key_7_8Buf) != 0x13
    || (key13_14_15Buf[1] ^ key13_14_15Buf[2]) != 0x12
    || (key_16_17_18Buf[2] ^ key_16_17_18Buf[1]) != 0x4D )
  {
    result = outputString(eorBuf);
  }
  else
  {
    result = outputString(successBuf);
  }
  return result;
}

1、从上面可知,inputKey的第一个字符和第2个字符 异或 后为0x54,从原始树中可知, 字符 c与字符7 异或为0x54,因此可知如下:

 inputkey[0]=‘c’

 inputkey[1]='7’

 inputkey[2]=?

 inputkey[3]=?

 inputkey[4]=?

 inputkey[5]=?

 inputkey[6]=?

 inputkey[7]=?

 inputkey[8]=?

 inputkey[9]='c' 

 inputkey[10]='7' 

 inputkey[11]='M'

 inputkey[12]='k'

 inputkey[13]=?

 inputkey[14]=?

 inputkey[15]=?

 inputkey[16]=?

 inputkey[17]=?

 inputkey[18]=?

 inputkey[19=?

 inputkey[20]=?

 inputkey[21]=? 

2、输入key的第7和第8个字符进行 异或 为0x13,从原始树的字符可知,kx 异或为0x13,因此可知

 inputkey[0]=‘c’

 inputkey[1]='7'

 inputkey[2]=?

 inputkey[3]=?

 inputkey[4]=?

 inputkey[5]=?

 inputkey[6]=?

 inputkey[7]=‘k’

 inputkey[8]='x'

 inputkey[9]='c' 

 inputkey[10]='7' 

 inputkey[11]='M'

 inputkey[12]='k'

 inputkey[13]=?

 inputkey[14]=?

 inputkey[15]=?

 inputkey[16]=?

 inputkey[17]=?

 inputkey[18]=?

 inputkey[19=?

 inputkey[20]=?

 inputkey[21]=?

3、输入的key14,15 异或 为0x12 从原始树中可知,tf 异或 为0x12,而 key 13、14、15为一个字符串,结合原始树,可知:13位为字符c

 inputkey[0]=‘c’

 inputkey[1]='7'

 inputkey[2]=?

 inputkey[3]=?

 inputkey[4]=?

 inputkey[5]=?

 inputkey[6]=?

 inputkey[7]=‘k’

 inputkey[8]='x'

 inputkey[9]='c' 

 inputkey[10]='7' 

 inputkey[11]='M'

 inputkey[12]='k'

 inputkey[13]=‘c’

 inputkey[14]='t'

 inputkey[15]='f'

 inputkey[16]=?

 inputkey[17]=?

 inputkey[18]=?

 inputkey[19=?

 inputkey[20]=?

 inputkey[21]=?

4、输入的key17 18异或为0x4d 从原始树中可知,t9 异或 为0x4d,而 key 16、17、18为一个字符串,结合原始树,可知:16位为字符c

 inputkey[0]=‘c’

 inputkey[1]='7'

 inputkey[2]=?

 inputkey[3]=?

 inputkey[4]=?

 inputkey[5]=?

 inputkey[6]=?

 inputkey[7]=‘k’

 inputkey[8]='x'

 inputkey[9]='c' 

 inputkey[10]='7' 

 inputkey[11]='M'

 inputkey[12]='k'

 inputkey[13]=‘c’

 inputkey[14]='t'

 inputkey[15]='f'

 inputkey[16]=‘c’

 inputkey[17]='t'

 inputkey[18]='9'

 inputkey[19=?

 inputkey[20]=?

 inputkey[21]=?

5、目前还剩下面3个字符没有获取

  key_2_3buf

  key_4_5_6Buf

  key_19_20_21Buf

  而原始树中还差 ct 以及2个c7M没有分配,因此可知:

key_2_3buf  必须是"ct"

key_4_5_6Buf 与 key_19_20_21Buf 相同,是“c7M” 因此key如下:

inputkey[0]=‘c’

 inputkey[1]='7'

 inputkey[2]='c'

 inputkey[3]='t'

 inputkey[4]='c'

 inputkey[5]='7'

 inputkey[6]='M'

 inputkey[7]=‘k’

 inputkey[8]='x'

 inputkey[9]='c' 

 inputkey[10]='7' 

 inputkey[11]='M'

 inputkey[12]='k'

 inputkey[13]=‘c’

 inputkey[14]='t'

 inputkey[15]='f'

 inputkey[16]=‘c’

 inputkey[17]='t'

 inputkey[18]='9'

 inputkey[19='c'

 inputkey[20]='7'

 inputkey[21]='M'

六、输入的key为:“c7ctc7Mkxc7Mkctfct9c7M”



 






看雪侠者千人榜,看看你上榜了吗?

最后于 2018-6-19 21:04 被oooAooo编辑 ,原因: C
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回