bp GetWindowTextA下断,运行后随便输入name和key点击“确定”,中断下来后ALT+F9返回到用户领空:
00403C22 call <jmp.&MFC42.#3874_CWnd::GetWindowTextA>
00403C27 mov ecx, dword ptr [ebp-4]
向下不远处:
00403C84 mov edx, dword ptr [ebp-8]
00403C87 push edx
00403C88 mov eax, dword ptr [ebp-C]
00403C8B push eax
00403C8C call 00401113
跟进就是处理name和key的。
由代码:
00403335 cmp eax, 0C ; 用户名长度必须是12
00403338 je short 0040334E
知道用户名长度必须是12;从后面的分析还知道name串中不能有重复的字符。
向下跟到:
00403598 cmp eax, 35 ; key长度要大于35H
0040359B jnb short 004035B1
知道key长度要大于等于53,从后面的分析还知道key串中字符只可能取‘0’,‘1’,‘2’,‘3’中的一个。向下跟到:
00403615 push ecx
00403616 lea edx, dword ptr [ebp-100]
0040361C push edx
0040361D lea eax, dword ptr [ebp-2B0]
00403623 push eax ; key每个字符减30H后按DWORD存放
00403624 call 00401087
……
00401087 jmp 004022A0
……
便到了主要部分了,sub_004022A0函数是用来验证key的,其复杂程度相当大!!!好多人在此放弃了,sub_004022A0函数共有三个参数,第一个dwKey是输入的key串逐个字符转换成整型后存储的内存地址;第二个参数buffer是存储生成的字符串的缓冲区,大小为24字节;第三个参数a3为:{0,1,2,3,4,5,6,7,8,9,0x0A,0x0B};现将整理后的C代码贴出来分析:
可以看到最终生成的buffer串字符是由字符‘a’到‘l’组成的,而且buffer最终长度是24,因此最先想到构造的buffer串是:“abcdefghijklabcdefghijkl”。当然要先确定这个串是否合理,具体方法是在sub_004022A0函数的出口“00402F27 retn”处F4运行至,然后观察buffer的内容,二进制编辑成“abcdefghijklabcdefghijkl”,F9运行发现弹出对话框“ok!”,说明构造的这个buffer是合理的。
我“悟出”的算法思想是这样的:
外层循环2次,内层循环12次,也就是说尽量在第一次大循环时就构造出“abcdefghijkl”;dwKey[0]不为空,我这里设置为3;将12分割为3块,每块元素有四个(12 div 3)。

每次循环元素40的位置下移一位,上图中把V12的12个关键元素分割为3块,分别标示为0块,1块,2块。如果设置dwKey[1]=0,dwKey[2]=1,则v13和v14是分别求0块和1块4个元素之和的。
第一次循环中,40在0块的第0个元素,因此v13 > v14,在C代码里找到条件“if ( v13 > v14 )”进去分析。
“
v12[4 * dwKey[1] + dwKey[11]] = v12[4 * dwKey[12]+ dwKey[13]];
v12[4 * dwKey[1] + dwKey[14]] = v12[4 * dwKey[15]+ dwKey[16]];
v12[4 * dwKey[1] + dwKey[17]] = v12[4 * dwKey[18]+ dwKey[19]];
v12[4 * dwKey[2] + dwKey[20]] = v12[4 * dwKey[21]+ dwKey[22]];
v12[4 * dwKey[2] + dwKey[23]] = v12[4 * dwKey[24]+ dwKey[25]];
v12[4 * dwKey[2] + dwKey[26]] = v12[4 * dwKey[27]+ dwKey[28]];
”
这段代码是对v12表元素换位用的,暂不分析,向下看v15和v16也是分别求0块和1块4个元素之和的。如果不对v12表的元素进行换位操作,则第一次v15 > v16,满足条件“if ( v15 > v16 )”进去分析。
“
if ( v12[4 * dwKey[29] + dwKey[30]] == v12[4 * dwKey[31]+ dwKey[32]] )
buffer[v5++] = 'e';
else
buffer[v5++] = 'a';
”
欲得到‘a’,则使4 * dwKey[29] + dwKey[30]不等于4 * dwKey[31]+ dwKey[32],则令
4 * dwKey[29] + dwKey[30]=0(此时v12[0]=40),令4 * dwKey[31]+ dwKey[32]=1(此时v12[1]=30),故dwKey[29]= dwKey[30]=0,dwKey[31]=0,dwKey[32]=1.
如果按上述分析结果重新设置dwKey,则可以得到第一个字符‘a’。由于后面几轮循环仍是v13 > v14,而又想得到字符‘b’,‘c’,‘d’,则要满足v15 == v16,因此就要考虑上面提及的v12表元素换位代码。
经过分析换位形式为:
0块换用1块元素,换用元素索引为1,2,3
1块换用2块元素,换用元素索引为1,2,3
只有这样后面连续的3次循环v15 才能等于v16,故有:
4 * dwKey[1] + dwKey[11]=0/1
4 * dwKey[1] + dwKey[14]=0/2
4 * dwKey[1] + dwKey[17]=0/3
4 * dwKey[12]+ dwKey[13]=0/1
4 * dwKey[15]+ dwKey[16]=0/2
4 * dwKey[18]+ dwKey[19]=0/3
4 * dwKey[2] + dwKey[20]=0/1
4 * dwKey[2] + dwKey[23]=0/2
4 * dwKey[2] + dwKey[26]=0/3
4 * dwKey[21]+ dwKey[22]=0/1
4 * dwKey[24]+ dwKey[25]=0/2
4 * dwKey[27]+ dwKey[28]=0/3
已知dwKey[1]=0,dwKey[2]=1,进行简单求解得:
dwKey[12]=dwKey[15]=dwKey[18]=1;
dwKey[21]=dwKey[24]=dwKey[27]=2;
简单求解得:
dwKey[11]=1 dwKey[13]=1 dwKey[20] =1 dwKey[22] =1
dwKey[14]=2 dwKey[16]=2 dwKey[23] =2 dwKey[25] =2
dwKey[17]=3 dwKey[19]=3 dwKey[26] =3 dwKey[28] =3
按照这个规则重写dwKey,这样第2,3,4次循环便满足条件“if ( v15 == v16 )”,
if ( v12[4 * dwKey[37] + dwKey[38]] > v12[4 * dwKey[39]+ dwKey[40]] )
buffer[v5++] = 'b';
if ( v12[4 * dwKey[37] + dwKey[38]] < v12[4 * dwKey[39]+ dwKey[40]] )
buffer[v5++] = 'c';
if ( v12[4 * dwKey[37] + dwKey[38]] ==v12[4 * dwKey[39]+ dwKey[40]] )
buffer[v5++] = 'd';
第2次循环v12[1]=40,第3次循环v12[2]=40,第4次循环v12[3]=40,想要依次得到‘b’,‘c’,‘d’,则令4 * dwKey[37] + dwKey[38]=1;4 * dwKey[39]+ dwKey[40]=2即可。
简单求解得:
dwKey[37]=0;dwKey[38]=1;dwKey[39]=0;dwKey[40]=2;
第5次循环开始40元素便在v12表的第1块了,因此“v13 < v14”,下面要得到字符‘e’,就要使“v12[4 * dwKey[45] + dwKey[46]] == v12[4 * dwKey[47]+ dwKey[48]]”成立(v17 < v18已经成立,因为代码的条件是对称,分析当如v13与v14)。因为v12表中的12个元素11个是30,只有一个是40,因此随便填写dwKey[45],dwKey[46],dwKey[47],dwKey[48]满足上式成立即可。
再下面是字符‘f’,‘g’,‘h’,这里要注意一点是虽然第6次循环本来v12[5]=40,但是经过换位处理后v12[1]=40.所以有:
4 * dwKey[41] + dwKey[42]=1;4 * dwKey[43]+ dwKey[44]=2;
简单求解得:
dwKey[41]=0,dwKey[42]=1,dwKey[43]=0,dwKey[44]=2.
接下来是字符‘i’,‘j’,‘k’,‘l’,这个有点技巧。如果要按照字符大小顺序来计算的话,条件形式为:
不等/不等;不等/相等;相等/不等;相等/相等。
第9次循环: v12[8]=40,v12[9]=30,v12[10]=30,v12[11]=30。
第10次循环:v12[8]=30,v12[9]=40,v12[10]=30,v12[11]=30。
第11次循环:v12[8]=30,v12[9]=30,v12[10]=40,v12[11]=30。
第12次循环:v12[8]=30,v12[9]=30,v12[10]=30,v12[11]=40。
我选择的比较方式是v12[9]和v12[8]比较,v12[10]和v12[8]比较,这样就能满足上述条件形式,便可以顺序得到字符‘i’,‘j’,‘k’,‘l’。所以有:
4 * dwKey[3] + dwKey[4]=9; 4 * dwKey[5] + dwKey[6]=8;
4 * dwKey[7] + dwKey[8]=10;4 * dwKey[9]+ dwKey[10]=8;
得:dwKey[3]=2; dwKey[4]=1; dwKey[5]=2; dwKey[6]=0;
dwKey[7]=2; dwKey[8]=2; dwKey[9]=2; dwKey[10]=0;
按照上述规则重写dwKey,在第一次12轮循环之后便可以得到字符串“abcdefghijkl”。但是在第二轮的循环中还需要注意几点(关键元素40变为了20),不过也很简单,主要是因为算法是对称的。
在确定第二轮的‘b’,‘c’,‘d’字符时,令4 * dwKey[49] + dwKey[50]=1;4 * dwKey[51]+ dwKey[52]=2;
得:dwKey[49]=0,dwKey[50]=1,dwKey[51]=0,dwKey[52]=2。
在确定第二轮的‘f’,‘g’,‘h’字符时,令4 * dwKey[33] + dwKey[34]=1;4 * dwKey[35]+ dwKey[36]=2;
得:dwKey[33]=0,dwKey[34]=1,dwKey[35]=0,dwKey[36]=2。
重写dwKey,如:
{3,0,1,2,1,2,0,2,2,2,0,1,1,1,2,1,2,3,1,3,1,2,1,2,2,2,3,2,3,0,0,0,1,0,1,0,2,0,1,0,2,0,1,0,2,0,1,0,0,0,1,0,2};
输出的buffer串便是“abcdefghijklabcdefghijkl”.
现在用cm.exe程序测试,输入序列:
abcdefghijkl
30121202220111212313121222323000101020102010201000102
点击“确定”后弹出“ok!”:

本人对上述算法不懂,仅仅是靠找规律玩填字游戏而已,因此未能提出通用的解法。
2008-10-7