先载入TraceMe.exe,Ctrl+G,输入GetDlgItemTextA,确定来到这边:
76086900 > 8BFF MOV EDI,EDI
F2,下断点,运行程序输入用户名 pediy,密码123456,确定,程序被断下:
76086900 > 8BFF MOV EDI,EDI ; USER32.GetDlgItemTextA
这里断在了系统领空,我们先取消断点,然后按ALT+F9返回程序领空(程序停在004011B6):
004011AE . 6A 51 PUSH 51 ; /Count = 51 (81.)
004011B0 . 50 PUSH EAX ; |Buffer
004011B1 . 6A 6E PUSH 6E ; |ControlID = 6E (110.)
004011B3 . 56 PUSH ESI ; |hWnd
004011B4 . FFD7 CALL EDI ; \GetDlgItemTextA
004011B6 . 8D8C24 9C00000>LEA ECX,DWORD PTR SS:[ESP+9C]
004011BD . 6A 65 PUSH 65 ; /Count = 65 (101.)
004011BF . 51 PUSH ECX ; |Buffer
004011C0 . 68 E8030000 PUSH 3E8 ; |ControlID = 3E8 (1000.)
004011C5 . 56 PUSH ESI ; |hWnd
004011C6 . 8BD8 MOV EBX,EAX ; |
004011C8 . FFD7 CALL EDI ; \GetDlgItemTextA
我们发现,正好上下一共调用了两次GetDlgItemTextA函数,我们猜测这里是读取文本框的用户名和密码。
在第一次调用GetDlgItemTextA的时候下一个断点,这里是004011AE处,Ctrl+F2重新载入程序,运行,输入用户名(pediy)和密码(123456),程序被断下:
004011AE . 6A 51 PUSH 51 ; /Count = 51 (81.)
004011B0 . 50 PUSH EAX ; |Buffer
EAX=0x0019F758 是Buffer的地址
004011B1 . 6A 6E PUSH 6E ; |ControlID = 6E (110.)
004011B3 . 56 PUSH ESI ; |hWnd
004011B4 . FFD7 CALL EDI ; \GetDlgItemTextA
此时我们在内存窗口Ctrl+G,输入0x0019F758,可以看到内容是我们的用户名
004011B6 . 8D8C24 9C00000>LEA ECX,DWORD PTR SS:[ESP+9C]
004011BD . 6A 65 PUSH 65 ; /Count = 65 (101.)
004011BF . 51 PUSH ECX ; |Buffer
ECX=0x0019F7A8 是Buffer的地址
004011C0 . 68 E8030000 PUSH 3E8 ; |ControlID = 3E8 (1000.)
004011C5 . 56 PUSH ESI ; |hWnd
004011C6 . 8BD8 MOV EBX,EAX ; |
EAX是上一个函数的返回值,即用户名长度
004011C8 . FFD7 CALL EDI ; \GetDlgItemTextA
这里,我们查看0x0019F7A8地址,内容正好是我们的密码,这里也就验证了我们的猜想,两个GetDlgItemTextA函数分别获取了用户名和密码
继续F8单步往下走
004011CA . 8A4424 4C MOV AL,BYTE PTR SS:[ESP+4C];
获取用户名的第一个字节
004011CE . 84C0 TEST AL,AL
004011D0 . 74 76 JE SHORT TraceMe.00401248 ;
通过TEST命令,如果第一个字节是0,就跳转,即失败
004011D2 . 83FB 05 CMP EBX,5 ;在004011C6 处,
EBX存放了用户名长度
004011D5 . 7C 71 JL SHORT TraceMe.00401248;
用户名长度小于5则失败
004011D7 . 8D5424 4C LEA EDX,DWORD PTR SS:[ESP+4C];
EDX是用户名字符串的指针
004011DB . 53 PUSH EBX
004011DC . 8D8424 A000000>LEA EAX,DWORD PTR SS:[ESP+A0];
EAX是密码字符串的指针
004011E3 . 52 PUSH EDX
004011E4 . 50 PUSH EAX
004011E5 . E8 56010000 CALL TraceMe.00401340;
004011EA . 8B3D BC404000 MOV EDI,DWORD PTR DS:[<&USER32.GetDlgIte>; USER32.GetDlgItem
004011F0 . 83C4 0C ADD ESP,0C
004011F3 . 85C0 TEST EAX,EAX
004011F5 . 74 37 JE SHORT TraceMe.0040122E
004011E5 的CALL很重要,因为上面将用户名和密码分别入栈,然后调用这个CALL,所以很可能是判断密码是否正确的,我们先看最后一行(004011F5),这里对EAX进行判断,走下去发现,如果EAX=0,就跳转到失败的地方,EAX正好是这个CALL的返回值,所以这里基本可以判断,这个CALL返回0则密码错误,非0则密码正确,那么我们把004011F5这一行NOP就可以实现简单的爆破了。
现在我们在这个CALL F2下断点,Ctrl+F2重新载入,输入用户名(pediy)和密码(123456)确定,F7 进入这个CALL
00401340 /$ 55 PUSH EBP
00401341 |. 8B6C24 0C MOV EBP,DWORD PTR SS:[ESP+C];
EBP是用户名字符串指针
00401345 |. 56 PUSH ESI
00401346 |. 57 PUSH EDI
00401347 |. 8B7C24 18 MOV EDI,DWORD PTR SS:[ESP+18];
EDI是用户名长度
0040134B |. B9 03000000 MOV ECX,3;
ECX=3,是个常量
00401350 |. 33F6 XOR ESI,ESI
00401352 |. 33C0 XOR EAX,EAX
00401354 |. 3BF9 CMP EDI,ECX;
将用户名长度(EDI)和3比较
00401356 |. 7E 21 JLE SHORT TraceMe.00401379;
用户名长度小于等于3则跳转,即失败
00401358 |. 53 PUSH EBX
00401359 |> 83F8 07 /CMP EAX,7
0040135C |. 7E 02 |JLE SHORT TraceMe.00401360;
这边把EAX和7对比,小于等于7就跳到下一行,否则用XOR EAX,EAX将EAX置0
0040135E |. 33C0 |XOR EAX,EAX
00401360 |> 33D2 |XOR EDX,EDX
00401362 |. 33DB |XOR EBX,EBX
00401364 |. 8A1429 |MOV DL,BYTE PTR DS:[ECX+EBP];
DL=用户名[ECX],即取用户名的第ECX+1个字符
00401367 |. 8A98 30504000 |MOV BL,BYTE PTR DS:[EAX+405030];
这里0x405030应该是个数组指针,根据上面EAX不能大于7,所以推测,数组长度为8,我们在内存窗口,Ctrl+G,输入0x405030,果然是一个数组,前八个字节分别为0x0C, 0x0A, 0x13, 0x09, 0x0C, 0x0B, 0x0A, 0x08
0040136D |. 0FAFD3 |IMUL EDX,EBX
00401370 |. 03F2 |ADD ESI,EDX ;
将上面的两个值相乘,结果放入ESI
00401372 |. 41 |INC ECX ;
ECX递增
00401373 |. 40 |INC EAX;
EAX递增
00401374 |. 3BCF |CMP ECX,EDI;
00401354 处在EDI存放了用户名长度,所以这边是将ECX和用户名长度相比
00401376 |.^7C E1 \JL SHORT TraceMe.00401359;
ECX小于用户名长度则跳转,也就是重复上述步骤(do...while循环),这里我们发现,算法是从用户名的第4个字节开始,数组从第一个字节开始,两个字节相乘,结果加到ESI,如果数组超过第8个字节,则再从从第一个字节开始
00401378 |. 5B POP EBX
00401379 |> 56 PUSH ESI ; /<%ld>
0040137A |. 68 78504000 PUSH TraceMe.00405078 ; |Format = "%ld"
0040137F |. 55 PUSH EBP ; |s
00401380 |. FF15 9C404000 CALL DWORD PTR DS:[<&USER32.wsprintfA>] ; \wsprintfA
调用wsprintf函数,将ESI从整型转换成字符串,*****这是关键,这个字符串就是密码*****
00401386 |. 8B4424 1C MOV EAX,DWORD PTR SS:[ESP+1C]
0040138A |. 83C4 0C ADD ESP,0C
0040138D |. 55 PUSH EBP ; /String2
0040138E |. 50 PUSH EAX ; |String1
0040138F |. FF15 04404000 CALL DWORD PTR DS:[<&KERNEL32.lstrcmpA>] ; \lstrcmpA
通过lstrcmpA将算出来的密码和用户输入的进行对比,判断密码是否正确
算法分析到此结束,下面给出C语言的注册机算法:
#include <stdio.h>
#include <stdlib.h>
int CalcKey(const char *name,char *key);
int main()
{
char name[100];
char key[100];
while (1)
{
printf("Input the name:");
scanf("%s", name);
if (0==strcmp(name,"exit"))
{
break;
}
CalcKey(name, key);
printf("The key is:%s\n", key);
}
return 0;
}
int CalcKey(const char *name,char *key)
{
unsigned char keyArr[8] = { 0x0C, 0x0A, 0x13, 0x09, 0x0C, 0x0B, 0x0A, 0x08 };
int nameLen = strlen(name);
long sum = 0;
int i = 0, j = 3;
if (nameLen <= 3)
{
return -1;
}
do
{
if (i>7)
{
i = 0;
}
sum += name[j] * keyArr[i];
++i;
++j;
} while (j < nameLen);
sprintf(key, "%ld", sum);
return 0;
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
上传的附件: