当前位置: 首页 > 逆向 > 正文

一个简单的CrackMe的算法分析

CrackMe可以到这里去下载:点我下载

首先CrackMe是没有加壳的.程序使用的是MFC编写,从图标和反汇编后的导入函数可以看出,

PEDI查看也显示:

Microsoft Visual C++ 6.0

首先OD载入程序,来到了入口点:

004017C0 >/$  55            push    ebp
004017C1  |.  8BEC          mov     ebp, esp
004017C3  |.  6A FF         push    -1
004017C5  |.  68 00244000   push    00402400
004017CA  |.  68 46194000   push    <jmp.&MSVCRT._except_handler3>   ;  SE 处理程序安装
004017CF  |.  64:A1 0000000>mov     eax, dword ptr fs:[0]
004017D5  |.  50            push    eax
004017D6  |.  64:8925 00000>mov     dword ptr fs:[0], esp

然后,直接运行程序:

程序主界面

从界面中可以看到,有两个编辑框,那么对获取编辑框文本的API进行下断,这样方便我们来到程序的关机部位:

因为获取文本一般来讲,有两个API:

1.GetDlgItemText

2.GetWindowText

对API断点下端就可到达程序获取文本的地方,如果不是到程序使用了哪一个API,可以对两个API都下断.只要断下来就行

这里对GetWindowText下断:

BP GetWindowTextA

中断后,按alt+F9返回到程序领空,单步几次后,来到下面的代码 :

004012A2   .  E8 9D040000   call    <jmp.&MFC42.#2370>
004012A7   .  83C6 64       add     esi, 64
004012AA   .  56            push    esi
004012AB   .  68 E9030000   push    3E9
004012B0   .  57            push    edi
004012B1   .  E8 8E040000   call    <jmp.&MFC42.#2370>
004012B6   .  5F            pop     edi
004012B7   .  5E            pop     esi
004012B8   .  C2 0400       retn    4

这里的两个call就是获取文本的内容

单步跟踪,知道再次来到程序领空:

004014C0   .  81EC 00040000 sub     esp, 400
004014C6   .  56            push    esi
004014C7   .  57            push    edi
004014C8   .  8BF1          mov     esi, ecx
004014CA   .  6A 01         push    1
004014CC   .  E8 9D020000   call    <jmp.&MFC42.#6334>
004014D1   .  8B46 60       mov     eax, dword ptr [esi+60]
004014D4   .  8D7E 60       lea     edi, dword ptr [esi+60]
004014D7   .  8178 F8 00010>cmp     dword ptr [eax-8], 100
004014DE   .  7C 10         jl      short 004014F0
004014E0   .  6A 00         push    0
004014E2   .  6A 00         push    0
004014E4   .  68 28304000   push    00403028                         ;  ASCII "Wrong"
004014E9   .  8BCE          mov     ecx, esi

我们可以看到下面有一个Wrong,其实这就是错误提示,

那么我们从004014DE开始分析:

004014D7   .  8178 F8 00010>cmp     dword ptr [eax-8], 100           ;  判断长度
004014DE   .  7C 10         jl      short 004014F0
004014E0   .  6A 00         push    0
004014E2   .  6A 00         push    0
004014E4   .  68 28304000   push    00403028                         ;  ASCII "Wrong"
004014E9   .  8BCE          mov     ecx, esi
004014EB   .  E8 78020000   call    <jmp.&MFC42.#4224>
004014F0   >  8D4C24 08     lea     ecx, dword ptr [esp+8]
004014F4   .  51            push    ecx
004014F5   .  68 00010000   push    100
004014FA   .  8BCF          mov     ecx, edi
004014FC   .  E8 61020000   call    <jmp.&MFC42.#2915>
00401501   .  50            push    eax
00401502   .  E8 29FFFFFF   call    00401430                         ;  算法的关键函数
00401507   .  8B46 64       mov     eax, dword ptr [esi+64]
0040150A   .  8D5424 10     lea     edx, dword ptr [esp+10]          ;  此时。堆栈地址中存放了真序列号的位置
0040150E   .  52            push    edx                              ; /s2
0040150F   .  50            push    eax                              ; |s1
00401510   .  FF15 AC214000 call    dword ptr [<&MSVCRT._mbscmp>]    ; \比较真序列号和假序列号
00401516   .  83C4 10       add     esp, 10
00401519   .  85C0          test    eax, eax
0040151B   .  6A 00         push    0
0040151D   .  6A 00         push    0
0040151F   .  75 15         jnz     short 00401536                   ;  不等于0就跳转,跳向失败
00401521   .  68 20304000   push    00403020                         ;  ASCII "Great"
00401526   .  8BCE          mov     ecx, esi                         ;  不跳就成功
00401528   .  E8 3B020000   call    <jmp.&MFC42.#4224>
0040152D   .  5F            pop     edi
0040152E   .  5E            pop     esi
0040152F   .  81C4 00040000 add     esp, 400
00401535   .  C3            retn
00401536   >  68 28304000   push    00403028                         ;  ASCII "Wrong"

如果是暴力破解的话。直接nop掉jnz short 00401536即可,但是我们是算法分析,那么我们重新输入新的序列号和用户名,此时我们跟进call 00401430

来到了如下的代码:

00401442    57              push edi
00401443    8A55 00         mov dl,byte ptr ss:[ebp]                                       ; 依次获取用户名的每一位
00401446    84D2            test dl,dl
00401448    74 59           je short Crackme2.004014A3                                     ; 判断是不是已经取完了
0040144A    8D7C24 10       lea edi,dword ptr ss:[esp+10]
0040144E    8AC2            mov al,dl                                                      ; al保存用户名
00401450    8BF5            mov esi,ebp
00401452    2BFD            sub edi,ebp
00401454    8AD8            mov bl,al
00401456    02D9            add bl,cl                                                      ; tmp[i] = i+AScii 用户名的ascii值加上对应的位数
00401458    32D8            xor bl,al                                                      ; tmp[i]=tmp[i] xor i
0040145A    D0E0            shl al,1                                                       ; tmp2[i]=ascii*2
0040145C    0AD8            or bl,al                                                       ; tmp3[i]=tmp2[i] or tmp[i]
0040145E    8A46 01         mov al,byte ptr ds:[esi+1]
00401461    881C37          mov byte ptr ds:[edi+esi],bl
00401464    41              inc ecx
00401465    46              inc esi
00401466    84C0            test al,al
00401468  ^ 75 EA           jnz short Crackme2.00401454
0040146A    84D2            test dl,dl
0040146C    74 35           je short Crackme2.004014A3
0040146E    8BBC24 18040000 mov edi,dword ptr ss:[esp+418]
00401475    8D5C24 10       lea ebx,dword ptr ss:[esp+10]
00401479    8BF5            mov esi,ebp
0040147B    2BDD            sub ebx,ebp
0040147D    8A041E          mov al,byte ptr ds:[esi+ebx]
00401480    57              push edi
00401481    50              push eax
00401482    E8 69FFFFFF     call Crackme2.004013F0   ;F7跟进,算法的另外一部分在里面
00401487    8A46 01         mov al,byte ptr ds:[esi+1]
0040148A    83C4 08         add esp,8
0040148D    83C7 02         add edi,2
00401490    46              inc esi
00401491    84C0            test al,al
00401493  ^ 75 E8           jnz short Crackme2.0040147D
00401495    C607 00         mov byte ptr ds:[edi],0
00401498    5F              pop edi
00401499    5E              pop esi
0040149A    5D              pop ebp
0040149B    5B              pop ebx
0040149C    81C4 00040000   add esp,400
004014A2    C3              retn

然后我们跟进004013F0,分析后的代码如下:

004013F0    0FBE4C24 04     movsx ecx,byte ptr ss:[esp+4]                                  ; tmp4[i]=对tmp3[i]进行符号拓展
004013F5    8BC1            mov eax,ecx                                                    ; tmp5[i] == tmp4[i]
004013F7    83E1 0F         and ecx,0F                                                     ; tmp4[i]=tmp4[i] and 15
004013FA    C1F8 04         sar eax,4                                                      ; tmp5[i] = tmp5[i] *16
004013FD    83E0 0F         and eax,0F                                                     ; tmp5[i]=tmp5[i] and 15
00401400    83F8 0A         cmp eax,0A                                                     ; tmp5[i]>10 ?
00401403    7D 04           jge short Crackme2.00401409
00401405    04 30           add al,30                                                      ; tmp5[i]<10 tmp5[i] = tmp5[i]+48
00401407    EB 02           jmp short Crackme2.0040140B
00401409    04 42           add al,42
0040140B    8B5424 08       mov edx,dword ptr ss:[esp+8]
0040140F    83F9 0A         cmp ecx,0A
00401412    8802            mov byte ptr ds:[edx],al
00401414    7D 07           jge short Crackme2.0040141D
00401416    80C1 61         add cl,61
00401419    884A 01         mov byte ptr ds:[edx+1],cl
0040141C    C3              retn
0040141D    80C1 45         add cl,45
00401420    884A 01         mov byte ptr ds:[edx+1],cl
00401423    C3              retn

根据上面的注释我们就可以写出keygen了,我写的keygen的代码如下:

#include <iostream>
#include <string.h>
using namespace std;
int main()
{
	char username[255];
	//cout<<"用户名:";
	cin>>username;
	int length= strlen(username);

	signed char * tmp = new signed char[length];
	signed char * Result = new signed char[2*length];

	for(int i=0;i<length;i++){
		int tmp2 ;
		*(tmp+i) = (int)username[i];
		tmp2 = (int)(*(tmp+i));
		tmp2=tmp2+i;
		tmp2 = tmp2 ^ (int)(*(tmp+i));
		*(tmp+i) = (int)(*(tmp+i))*2;
		*(tmp+i) = (int)(*(tmp+i)) | tmp2;
	}
	for(int j=0;j<length;j++){
		int tmp3 ;
		int tmp4;
		tmp4 = *(tmp+j);  //movsx
		/*if(tmp4>=128){
			*(tmp+j) = 0-(255-(int)(*(tmp+j))+1);
		}*/
		tmp3 = tmp4;
		tmp4 = tmp4 & 15;
		tmp3 = tmp3 >>4; //有问题
		tmp3=tmp3 &15;
		if(tmp3>=10){
			tmp3=tmp3+66;
		}
		else{
			tmp3=tmp3+48;
		}
		if(tmp4>=10){
			tmp4 = tmp4 + 69;
		}
		else{
			tmp4 = tmp4 + 97;
		}
		*(Result+j*2) = tmp3;
		*(Result+2*j+1) = tmp4;
	}
//	cout<<"序列号是:";
	for(int n=0;n<2*length;n++){
		cout<<*(Result+n);
	}
	cout<<endl;
}


本文固定链接: http://kuaile.in/archives/864 | 蒲公英的博客

该日志由 蒲公英 于2012年04月20日发表在 逆向 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: 一个简单的CrackMe的算法分析 | 蒲公英的博客
关键字:

一个简单的CrackMe的算法分析:等您坐沙发呢!

发表评论


You must enable javascript to see captcha here!

快捷键:Ctrl+Enter