-
-
[原创]看雪.京东 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
2、创建一个空的字典树 :ptreeHeadkey13_14_15Buf
函数__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]=?
再分析下lastVerity函数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 函数
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位为字符cinputkey[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位为字符cinputkey[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”