-
-
[原创][邀请码已发]对简单CrackMe的分析
-
发表于: 2011-4-11 09:43 4071
-
【文章标题】: 对CrackMe的简单分析
【文章作者】: Xsoda
【软件大小】: 8kb
【下载地址】: CreakMe.7z
【加壳方式】: 未加壳
【编写语言】: Win32Asm
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
1. 找关键Call,OD载入看入口点没加壳,F9 运行没有任何异常,说明没有反调试。胡乱注册了一下,有提示”wrong serial! Keep trying,you’ll get it!”.是个MessageBox函数提示。接下来方法就多了:
1).可以用菜单”调试”---->”暂停”来暂停程序运行(不点击对话框的”确定”按钮,对应快捷键F12),然后Alt + K (查看调用堆栈)查看调用堆栈,如图:
图中00401263就是调用的MessageBox地址。双击“调用来自”栏的 CRACLME4.00401263就可以到达该地址的反汇编窗口,向上就可以找到爆破点。
2).下断点”bpx MessageBox”,在所有调用该函数的地址下断点也可以找到(载入程序后,在命令栏输入bpx MessageBox ,然后回车,打开”查看””断点”检查是否已经成功下了断点,运行程序,注册信息随意输入,在点击CrackMe的“check”就可以断下来了)。
3).最简单的超级字符串查找: 右键菜单”Ultra String Reference””Find ASCII”,可以找到刚刚发现的提示信息。最后通过3种方法找到的关键CALL如下图:
2. 爆破:我们找到的关键CALL为:00401236 |. E8 BE000000 call 004012F9,下面根着cmp指令和je指令,0040123E |. 74 15 je short 00401255这个就是关键跳了,爆破方法很多:
1).常规的将je指令该为2个nop
2).je改为jne
3).由于je指令只看ZF标志位,我们也可以改标志位:我也不知道可不可以直接修改ZF标志位,我只知道可以修改DF(方向)和CF(进位)标志位。但是我们可以通过变通的方法来修改:通过这里的代码cmp eax,0和je 00401255可以判断出如果注册失败,eax是等于0的(也就是函数004012F9的返回值)。我们找一个永远不会等于0的寄存器来代替eax,这样无论函数004012F9返回什么,都可以注册成功。这样的寄存器就只有2个了,eip和esp,eip不可以直接控制,我们选择esp。我们将cmp eax,0修改成cmp esp,0仍旧可以注册成功:
其他的爆破方法在00401236 |. E8 BE000000 call 004012F9里面,我们跟进去看看。
3. 注册算法分析:
用IDA分析进入关键call:
.text:004012F9 sub_4012F9 proc near ; CODE XREF: sub_401123+113p
.text:004012F9 push ebp
.text:004012FA mov ebp, esp
.text:004012FC push esi
.text:004012FD push edi
.text:004012FE lea esi, szName ; szName 保存注册名
.text:00401304 lea edi, szSerial_1 ; szSerial_1保存真正的注册码
.text:0040130A xor eax, eax ; eax 清 0
.text:0040130C xor ecx, ecx ; ecx 清 0 作为下面的循环变量
.text:0040130E mov bl, 1Ah ; 是一个常量
.text:00401310
.text:00401310 loc_401310: ; CODE XREF: sub_4012F9+2Fj
.text:00401310 cmp byte ptr [esi], 0 ; 从这里开始循环处理注册名的每一位,0就是'\0'
.text:00401313 jz short endLoop
.text:00401315 mov al, [esi] ; 注册名的每一位放入al
.text:00401317 add al, cl ; cl是一个循环变量,初始为0,而后每处理一位都加 1
.text:00401319 xor al, cl
.text:0040131B div bl ; 除法,商在 eax 里面,余数在 edx
.text:0040131D shr ax, 8 ; ax 右移 8 位实际是把 ah 赋值给 al,然后 ah 清0
.text:00401321 add al, 'A'
.text:00401323 mov [edi], al ; edi 就是指向计算的真注册码了
.text:00401325 inc edi ; edi 加 1,以便存放下一位真正的注册码
.text:00401326 inc esi ; esi 加 1,处理szName的下一位
.text:00401327 inc ecx ; 循环变量加 1
.text:00401328 jmp short loc_401310
.text:0040132A ; ---------------------------------------------------------------------------
.text:0040132A
.text:0040132A endLoop: ; CODE XREF: sub_4012F9+1Aj
.text:0040132A mov byte ptr [edi], 0 ; edi 指向的是真正的注册码,这里就是构建一个以 ‘\0’结尾的字符串
.text:0040132D xor eax, eax
.text:0040132F cmp ecx, 0 ; ecx 只有在注册码为空时等于 0
.text:00401332 jz short Fail ; 注册码位空直接跳到失败
.text:00401334 push offset szSerial_1 ; lpString2
.text:00401339 push offset szSerial ; lpString1 szSerial是我们输入的注册码
.text:0040133E call lstrcmpA
.text:00401343 cmp eax, 0
.text:00401346 jz short Success ; 两个字符串相等
.text:00401348 xor eax, eax
.text:0040134A jmp short Fail ; 字符串不相等,失败
.text:0040134C ; ---------------------------------------------------------------------------
.text:0040134C
.text:0040134C Success: ; CODE XREF: sub_4012F9+4Dj
.text:0040134C mov eax, ecx
.text:0040134E
.text:0040134E Fail: ; CODE XREF: sub_4012F9+39j
.text:0040134E ; sub_4012F9+51j
.text:0040134E pop edi
.text:0040134F pop esi
.text:00401350 leave
.text:00401351 retn 4
.text:00401351 sub_4012F9 endp
.text:00401351
到此注册算法已经很明了了,下面给出C语言代码:
#include <stdio.h> //以下代码在VC++6.0下编译通过。
#include <stdlib.h>
char szName[20];
char szKey[20];
void keygen(void)
{
int i;
int temp;
for(i = 0; szName != '\0';i ++)
{
temp = szName + i;
temp ^= i; // C语言学的不好,我也想不到C语言中有64位的类型,就用了内嵌汇编
__asm{
mov edx,0x140608
mov eax,temp
mov bl,0x1A
div bl
mov temp,eax
}
temp >>= 8;
temp += 'A';
szKey = temp;
}
szKey = '\0';
return;
}
void main(void)
{
printf("%s","please enter your name:");
scanf("%s",szName);
keygen();
printf("the key is:%s\n",szKey);
system("pause");
return;
}
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课