首页
社区
课程
招聘
[原创][看雪论坛.腾讯公司2008软件安全竞赛]我的第二题分析过程及注册机
2008-10-7 15:53 10240

[原创][看雪论坛.腾讯公司2008软件安全竞赛]我的第二题分析过程及注册机

2008-10-7 15:53
10240
标 题: 看雪论坛.腾讯公司2008软件安全竞赛第一阶段◇第二题分析及注册机
作 者: mstwugui

点击确定按钮后程序执行到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!"

关于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!

阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

上传的附件:
收藏
点赞7
打赏
分享
最新回复 (25)
雪    币: 109
活跃值: (383)
能力值: ( LV12,RANK:220 )
在线值:
发帖
回帖
粉丝
cater 5 2008-10-7 16:04
2
0
乌龟大大 我支持你
雪    币: 479
活跃值: (25)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
dttom 3 2008-10-7 16:10
3
0
牛人,支持一下
雪    币: 107
活跃值: (311)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Fido 2008-10-7 16:15
4
0
膜拜啊................................
雪    币: 212
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
edigar 2008-10-7 16:18
5
0
对于楼主的强大,只能膜拜
固定第一第二位的值了,会不会解得值变少
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
mstwugui 6 2008-10-7 16:22
6
0
key的前三位都是固定的,但这个固定并不是因为偷懒,第一位肯定是3,因为12/key[0]必须等于4
key[1]必须小于key[2],这点从后面的反证可以得出
key[1]和key[2]必须小于2,这是由于"ijkl"的触发条件得出的
因此key[1]只能是0,key[2]是1
雪    币: 268
活跃值: (95)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
chimney 3 2008-10-7 16:50
7
0
放眼看雪,遍地是牛!
雪    币: 383
活跃值: (41)
能力值: ( LV12,RANK:530 )
在线值:
发帖
回帖
粉丝
小娃崽 13 2008-10-7 17:05
8
0

从珠海旅游回来,第2题已经开始了,一时冲动下回来看看,但是看到有个call很长,直接放弃了...嘿嘿,现在看看高人的解答,果然不是我等菜鸟可以弄出来的....服了..
雪    币: 7474
活跃值: (2689)
能力值: (RANK:520 )
在线值:
发帖
回帖
粉丝
netwind 13 2008-10-7 17:25
9
0
代码逆得太象了,学习!
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
mstwugui 6 2008-10-7 17:59
10
0
按照源代码逆的话那些常量4应该写成n
不过因为n肯定是4,所以我也就偷懒写数字比较容易看一些,呵呵
雪    币: 107
活跃值: (1437)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
frozenrain 2008-10-7 18:02
11
0
佩服!楼主是在分析的过程中猜得模型逆的,还是最后逆完了才知道模型的?
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
mstwugui 6 2008-10-7 18:06
12
0
分析的时候没有考虑模型,先把代码写出来,然后就很明显了
雪    币: 2314
活跃值: (129)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
shellwolf 10 2008-10-7 18:16
13
0
学习。。。。
雪    币: 111
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Aleaxander 1 2008-10-7 18:31
14
0
呵呵 ,强悍
我是直接把 ebp - 100看来是一个3*4的数组,我标记为:array_100[3][4];
array_100的内存地址为 12F388,呵, 下面就是我执行第一次(总共2*12次,我的注册名为abcdefghijkl)时的内存印象:
0012F388  28 00 00 00 1E 00 00 00 1E 00 00 00 1E 00 00 00  (............
0012F398  1E 00 00 00 1E 00 00 00 1E 00 00 00 1E 00 00 00  ............
0012F3A8  1E 00 00 00 1E 00 00 00 1E 00 00 00 1E 00 00 00  ............

因此array_100[i][j]在这印象就很好查了,i 代表是第几行,j 就是这行的第几列,如,i = 1, j = 2,则指第二行,第三列。呵,包括那些比较我都是写成这种形式,这样你就可以很直接的观察其变化了。不过,你输入的注册名不同,这个数组的值就会不同,不过不管怎么变,都是由一个28H(第一次外循环时)或14H(第二次外循环时),11个1E,当内循环每执行一次时,28H或14H就会变化位置,比如你输的和我的一样,那每执行一次循环时你的28H或14H将后移一个位置。
雪    币: 101
活跃值: (12)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
jjnet 5 2008-10-8 08:31
15
0
lz坏死了
在题目里面贴的那个OK的图
SN第二位是3..
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
mstwugui 6 2008-10-8 08:36
16
0
汗。。。我不是故意的,也没有说是从头开始的
只是因为之前选取复制到cm测试时刚好滚到到那个位置,抱歉哈
雪    币: 107
活跃值: (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
ykzhujiang 1 2008-10-8 10:00
17
0
好像是2,我当时就觉得这个怎么能注册成功呢,呵呵
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
mstwugui 6 2008-10-8 10:16
18
0
如果因为那张图耽误了哪位朋友的时间这里表示道歉
雪    币: 101
活跃值: (12)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
jjnet 5 2008-10-8 10:44
19
0
开玩笑的, 莫介意
我当时也这么想的不能注册成功,这个master乌龟?故意搞个图,然后PS一把来欺骗人,
但有恐自己理解有限, 万一要是说了不能注册又有没想到的结果能注册了, 那就囧大了
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
mstwugui 6 2008-10-8 10:57
20
0
谢谢理解,如果下载了我的注册机,全选key,光标就自动会滚动到最后去,因为key的CEdit长度不够,所以最左边这时候显示的已经不是第一个字节了,当时我也是刚测试完就直接截图上传,自己都没有留意里面的具体信息
雪    币: 8191
活跃值: (4273)
能力值: ( LV15,RANK:2459 )
在线值:
发帖
回帖
粉丝
ccfer 16 2008-10-8 11:25
21
0
我也是看到那个2以后花了些时间去研究
强烈要求乌龟大师送些猪蹄补偿我们损失的脑细胞
雪    币: 407
活跃值: (125)
能力值: ( LV13,RANK:280 )
在线值:
发帖
回帖
粉丝
EricAzhe 3 2008-10-8 11:31
22
0
哈哈,这么多人被乌龟大大迷惑了
雪    币: 503
活跃值: (80)
能力值: (RANK:280 )
在线值:
发帖
回帖
粉丝
mstwugui 6 2008-10-8 11:51
23
0
哈,那先记在账上,下次回来补猪蹄,
雪    币: 167
活跃值: (1544)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
Nisy 5 2008-10-8 13:01
24
0
过来膜拜下 o(∩_∩)o...哈哈
雪    币: 65
活跃值: (811)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
美丽破船 5 2008-10-9 11:48
25
0
来晚了,还是要膜拜一下的1~~
游客
登录 | 注册 方可回帖
返回