能力值:
(RANK:280 )
|
-
-
2 楼
标 题: 【看雪论坛.腾讯公司2008软件安全竞赛】第一阶段◇第二题分析及注册机
作 者: mstwugui
时 间: 2008-10-06 12:00
点击确定按钮后程序执行到403C00,读取name和key之后跳入401113
首先是调用403897校验403271至40389F是否有0xCC INT 3断点,如果有直接终止当前进程
接下来继续校验从403271开始的0x76E个字节,如果有代码改动则终止当前进程
接着开始name格式校验,长度为12的无重复小写字符串(不包含'z'),校验失败则显示"username?"对话框并回到返回函数
下一步是排序name, 将name的每个字节转变为他在原始name字符串从小到大排列后的位置
然后校验key的格式,长度至少为53并且每个字符都在0x30-0x33之间,校验失败则显示"key?"对话框并回到返回函数
接着调用401087计算根据key和排序过的name重新计算name,计算出来的结果为长度为24的字符串
如果上一步返回的字符串第n个字节不等于排序过的name的第n个字节(0<=n<12),或不等于第12+n个字节,则显示"wrong!"对话框并回到返回函数
否则显示"ok!"
|
能力值:
(RANK:280 )
|
-
-
3 楼
关于401087也就是4022A0的算法粗看一下有些长,我也没有什么好办法,为了让自己省事一些,先把整个函数用C描述出来
CString CalcRealName(char name[], char key[]) { CString sRet = ""; int n = 12/key[0]; int i, j, offset; int temp[12], bak[12]; int value1, value2;
for (j=0;j<2;j++) { for (offset=0;offset<12;offset++) { for (i=0;i<12;i++) temp = 0x1E; temp[name[offset]] = (j==0)?0x28:0x14; memcpy(bak, temp, sizeof(int)*12); value1 = 0; value2 = 0; for (i=0;i<4;i++) { value1 += temp[(key[1]*4)+i]; value2 += temp[(key[2]*4)+i]; } if (value1 == value2) { if (temp[key[3]*4+key[4]] == temp[key[5]*4+key[6]]) { if (temp[key[7]*4+key[8]] == temp[key[9]*4+key[10]]) sRet = sRet + 'l'; else sRet = sRet + 'k'; } else { if (temp[key[7]*4+key[8]] == temp[key[9]*4+key[10]]) sRet = sRet + 'j'; else sRet = sRet + 'i'; } } else if (value1 > value2) { temp[key[1]*4 + key[11]] = temp[key[12]*4+key[13]]; temp[key[1]*4 + key[14]] = temp[key[15]*4+key[16]]; temp[key[1]*4 + key[17]] = temp[key[18]*4+key[19]]; temp[key[2]*4 + key[20]] = temp[key[21]*4+key[22]]; temp[key[2]*4 + key[23]] = temp[key[24]*4+key[25]]; temp[key[2]*4 + key[26]] = temp[key[27]*4+key[28]];
value1 = 0; value2 = 0; for (i=0;i<4;i++) { value1 += temp[(key[1]*4)+i]; value2 += temp[(key[2]*4)+i]; } if (value1 > value2) { if (temp[key[29]*4+key[30]] == temp[key[31]*4+key[32]]) sRet = sRet + 'e'; else sRet = sRet + 'a'; } else if (value1 < value2) { if (temp[key[33]*4+key[34]] > temp[key[35]*4+key[36]]) sRet = sRet + 'g'; else if (temp[key[33]*4+key[34]] < temp[key[35]*4+key[36]]) sRet = sRet + 'f'; else if (temp[key[33]*4+key[34]] == temp[key[35]*4+key[36]]) sRet = sRet + 'h'; } else if (value1 == value2) { memcpy(temp, bak, sizeof(int)*12); if (temp[key[37]*4+key[38]] > temp[key[39]*4+key[40]]) sRet = sRet + 'b'; else if (temp[key[37]*4+key[38]] < temp[key[39]*4+key[40]]) sRet = sRet + 'c'; else if (temp[key[37]*4+key[38]] == temp[key[39]*4+key[40]]) sRet = sRet + 'd'; } } else if (value1 < value2) { temp[key[1]*4 + key[11]] = temp[key[12]*4+key[13]]; temp[key[1]*4 + key[14]] = temp[key[15]*4+key[16]]; temp[key[1]*4 + key[17]] = temp[key[18]*4+key[19]]; temp[key[2]*4 + key[20]] = temp[key[21]*4+key[22]]; temp[key[2]*4 + key[23]] = temp[key[24]*4+key[25]]; temp[key[2]*4 + key[26]] = temp[key[27]*4+key[28]];
value1 = 0; value2 = 0; for (i=0;i<4;i++) { value1 += temp[(key[1]*4)+i]; value2 += temp[(key[2]*4)+i]; } if (value1 > value2) { if (temp[key[41]*4+key[42]] > temp[key[43]*4+key[44]]) sRet = sRet + 'f'; else if (temp[key[41]*4+key[42]] < temp[key[43]*4+key[44]]) sRet = sRet + 'g'; if (temp[key[41]*4+key[42]] < temp[key[43]*4+key[44]]) sRet = sRet + 'h'; } else if (value1 < value2) { if (temp[key[45]*4+key[46]] == temp[key[47]*4+key[48]]) sRet = sRet + 'e'; else sRet = sRet + 'a'; } else if (value1 == value2) { memcpy(temp, bak, sizeof(int)*12); if (temp[key[49]*4+key[50]] > temp[key[51]*4+key[52]]) sRet = sRet + 'c'; else if (temp[key[49]*4+key[50]] < temp[key[51]*4+key[52]]) sRet = sRet + 'b'; else if (temp[key[49]*4+key[50]] == temp[key[51]*4+key[52]]) sRet = sRet + 'd'; } } } } return sRet; }
现在我们可以开始分析key的格式,首先是注意到有一个长度为12的局部数组,而且代码中有很多a*4+b的定位操作,因此很显然为了覆盖到每一个字节并且不重叠,key的第一个字节应该是3
由于字符"ijkl"的触发条件都是value1==value2, value1和value2分别是从key[1]*4和key[2]*4在临时数组中的连续4个值的和,因此很显然key[1]和key[2]只能是0或1,并且这两个字节必须不同,考虑到后面的
temp[key[1]*4 + key[11]] = temp[key[12]*4+key[13]]; temp[key[1]*4 + key[14]] = temp[key[15]*4+key[16]]; temp[key[1]*4 + key[17]] = temp[key[18]*4+key[19]]; temp[key[2]*4 + key[20]] = temp[key[21]*4+key[22]]; temp[key[2]*4 + key[23]] = temp[key[24]*4+key[25]]; temp[key[2]*4 + key[26]] = temp[key[27]*4+key[28]];
如果key[1]是1,而key[0]是0话,那么在4<=offset<8的情况下,temp[0]->temp[4]的原始值为0x1E, temp[4]->temp[7]当中的4个字节有一个不为0x1E, 此时我们无法确保将value1的值转变为小于value2, 这是因为我们先对value1需要用到的内存空间操作,随后才对value2需要用到的内存空间操作
因此key[1]应该是0,key[2]应该是[1]
接下来根据"ijkl"字符的触发条件,得到
key[3] = 0x02; key[4] = rand()%2; key[5] = 0x02; key[6] = (key[4]+1)%2; key[7] = 0x02; key[8] = 2*(rand()%2); key[9] = 0x02; key[10] = (0x02+key[8])%4;
回过头来我们继续看一下在value1不等于value2时对临时数组所做的调整
temp[key[1]*4 + key[11]] = temp[key[12]*4+key[13]]; temp[key[1]*4 + key[14]] = temp[key[15]*4+key[16]]; temp[key[1]*4 + key[17]] = temp[key[18]*4+key[19]]; temp[key[2]*4 + key[20]] = temp[key[21]*4+key[22]]; temp[key[2]*4 + key[23]] = temp[key[24]*4+key[25]]; temp[key[2]*4 + key[26]] = temp[key[27]*4+key[28]];
考虑到'a'和'e'的触发条件是修改后的value1==value2,因此此处修改应该只对偏移地址1,2,3起作用,所以得到
key[11] = rand()%3; key[14] = (key[11] + 1 + rand()%2)%3; key[17] = (6 - key[11] - key[14])%3; key[11] ++; key[14] ++; key[17] ++;
key[20] = rand()%3; key[23] = (key[20] + 1 + rand()%2)%3; key[26] = (6 - key[20] - key[23])%3; key[20] ++; key[23] ++; key[26] ++;
这里再进一步考虑key[1]和key[2]的值已经确定,因此得到
key[12] = 0x01; key[15] = 0x01; key[18] = 0x01;
key[13] = key[11]; key[16] = key[14]; key[19] = key[17];
key[21] = 0x02; key[24] = 0x02; key[27] = 0x02;
key[22] = key[20]; key[25] = key[23]; key[28] = key[26];
接下来根据a和e的触发条件得出
if (rand()%2) { key[29] = 0; key[30] = 0; key[32] = rand()%4; if (key[32] == 0) key[31] = 2; else key[31] = rand()%3; } else { key[31] = 0; key[32] = 0; key[30] = rand()%4; if (key[30] == 0) key[29] = 2; else key[29] = rand()%3; }
以及
if (rand()%2) { key[45] = 0; key[46] = 0; key[48] = rand()%4; if (key[48] == 0) key[47] = 2; else key[47] = rand()%3; } else { key[47] = 0; key[48] = 0; key[46] = rand()%4; if (key[46] == 0) key[45] = 2; else key[45] = rand()%3; }
再根据"bcd"的触发条件得出
key[37] = 0; key[38] = 1; key[39] = 0; key[40] = 2;
key[49] = 0; key[50] = 1; key[51] = 0; key[52] = 2;
最后根据"fgh"的触发条件得出
key[33] = 0; key[34] = 1; key[35] = 0; key[36] = 2;
key[41] = 0; key[42] = 1; key[43] = 0; key[44] = 2;
呵呵,都要结束了还没有谈到netwind大侠的h圈套,这个h确实看起来很像是个bug,但其实是有解的,只要我们把name中的第8大的字节放到第7大的字节之后这个问题也就迎刃而解了,仔细看一下源代码
.text:00402C59 mov edx, [eax+edx*4] .text:00402C5C cmp edx, [ecx+esi*4] .text:00402C5F jle short loc_402C73 .text:00402C61 .text:00402C61 char_f_0: .text:00402C61 mov eax, [ebp+arg_realname] ...... .text:00402CAB mov eax, [ecx+eax*4] .text:00402CAE cmp eax, [edx+esi*4] .text:00402CB1 jge short loc_402CC5 .text:00402CB3 .text:00402CB3 char_g_0: .text:00402CB3 mov ecx, [ebp+arg_realname] ...... .text:00402CFD mov ecx, [edx+ecx*4] .text:00402D00 cmp ecx, [eax+esi*4] .text:00402D03 jge short loc_402D17 .text:00402D05 .text:00402D05 char_h_0: .text:00402D05 mov edx, [ebp+arg_realname]
这里其实有两次输出操作,if...else之后还有一个单独的if,因此这个h放在g后面就一帆风顺的通过了
从整个分析可以看出,name和key其实是独立的,并没有互相依赖,也就是说可以随意选取各一个合法的name和key都能得到ok!
|
能力值:
(RANK:520 )
|
-
-
4 楼
结果提交时间 21 小时 52 分钟
结果提交时间长度 = 1312 分钟
结果提交次数 = 1
结果提交作为注册码
得分 = [(2880 - 1312)/2880]^1/8 x 100 - (1 -1 ) x 5 = 92.68
|
|
|