└文章标题┐:弱弱的自校验和注册算法分析(手动破解补丁)
└破文作者┐:-=>大菜一号<=-
└破解对象┐:附件中
└下载地址┐:附件中
└对象大小┐:未知
└加壳方式┐:fsg
└保护方式┐:自校验和普通算法
└编写语言┐:vc
└使用工具┐:od
└破解平台┐:d-xp
└破解声明┐:嘿嘿``自校验也不难嘛!
----------------------------------------------------------------------------------
└破解过程┐:
fsg的壳,单步跟一跟或Peid插件脱一脱就掉了,运行后发现出错
1、解决自校验
OD载入,f9运行,出现异常,停在:
0040126D |. E8 00F00000 call 00410272 ; \1551--cr.00410272
00401272 |. 81BD D8FDFFFF>cmp dword ptr [ebp-228], 0FB45 <-[ebp-228]是脱壳后程序的大小
0040127C |. 7E 02 jle short 00401280 <-小于等于0xfb45就跳,不跳就异常
0040127E |. CD 13 int 13 <-停在这
我们把40127c的jle改为jmp就行了
2、注册分析
弱弱的自校验搞定了,运行程序输入个小于10位的注册码,程序友好提示我们注册码要10位^-^,错误信息->"继续加油吧!"
OD载入,下断GetWindowText,断在:
0040F908 |. FF15 E8524100 call dword ptr [<&USER32.GetWindowTex>; \GetWindowTextA <-断在这里
0040F90E |. EB 12 jmp short 0040F922
0040F910 |> FF7424 08 push dword ptr [esp+8]
0040F914 |. 8B10 mov edx, dword ptr [eax]
0040F916 |. 8BC8 mov ecx, eax
0040F918 |. FF7424 08 push dword ptr [esp+8]
0040F91C |. FF92 84000000 call dword ptr [edx+84]
0040F922 \> C2 0800 retn 8
直接看:
004013BE > \68 E9030000 push 3E9 <-返回之后的代码
004013C3 . 8BCD mov ecx, ebp
004013C5 . E8 74E40000 call 0040F83E
004013CA . 85C0 test eax, eax
004013CC . 74 0E je short 004013DC
004013CE . 8D5424 7C lea edx, dword ptr [esp+7C]
004013D2 . 6A 64 push 64
004013D4 . 52 push edx
004013D5 . 8BC8 mov ecx, eax
004013D7 . E8 1AE50000 call 0040F8F6 <-去调用GetWindowText取注册码
004013DC > 8D7C24 7C lea edi, dword ptr [esp+7C]
004013E0 . 83C9 FF or ecx, FFFFFFFF
004013E3 . 33C0 xor eax, eax
004013E5 . F2:AE repne scas byte ptr es:[edi]
004013E7 . F7D1 not ecx
004013E9 . 49 dec ecx
004013EA . 83F9 0A cmp ecx, 0A
004013ED . 73 11 jnb short 00401400 <- 注册码长度和10比较
004013EF . 50 push eax ; /Arg3 => 00000000
004013F0 . 50 push eax ; |Arg2 => 00000000
004013F1 . 68 B8A04100 push 0041A0B8 ; |Arg1 = 0041A0B8
004013F6 . E8 610C0100 call 0041205C ; \1551-cra.0041205C <-注册码不足十位的提示
004013FB . E9 33010000 jmp 00401533
00401400 > 8D7C24 18 lea edi, dword ptr [esp+18] <-大于等于10位跳到这
00401404 . 83C9 FF or ecx, FFFFFFFF \
00401407 . 33C0 xor eax, eax |
00401409 . F2:AE repne scas byte ptr es:[edi] |->取注册名长度
0040140B . F7D1 not ecx |
0040140D . 49 dec ecx /
0040140E . 0F84 1F010000 je 00401533
00401414 . BF ACA04100 mov edi, 0041A0AC ; ASCII "6D*h!dN^!g" <-一串有用的字符
00401419 . 83C9 FF or ecx, FFFFFFFF \
0040141C . F2:AE repne scas byte ptr es:[edi] |
0040141E . F7D1 not ecx |
00401420 . 2BF9 sub edi, ecx |
00401422 . 56 push esi |
00401423 . 8D5424 1C lea edx, dword ptr [esp+1C] |
00401427 . 8BF7 mov esi, edi |
00401429 . 8BFA mov edi, edx |
0040142B . 8BD1 mov edx, ecx |
0040142D . 83C9 FF or ecx, FFFFFFFF |
00401430 . F2:AE repne scas byte ptr es:[edi] | ->有用的字符串接到注册名后面
00401432 . 8BCA mov ecx, edx |
00401434 . 4F dec edi |
00401435 . C1E9 02 shr ecx, 2 |------\
00401438 . F3:A5 rep movs dword ptr es:[edi], dword p> |
0040143A . 8BCA mov ecx, edx |
0040143C . 83E1 03 and ecx, 3 |
0040143F . F3:A4 rep movs byte ptr es:[edi], byte ptr> /
00401441 > 8A4C04 1C mov cl, byte ptr [esp+eax+1C] <-循环开始,每次都取注册名的各个字符
00401445 . C0E1 02 shl cl, 2 <-左移两位
00401448 . 80F1 25 xor cl, 25 <-与25异或
0040144B . 884C04 1C mov byte ptr [esp+eax+1C], cl <-结果传到[esp+eax+1c]
0040144F . 40 inc eax
00401450 . 83F8 0A cmp eax, 0A
00401453 .^ 7C EC jl short 00401441 <-运算十次
00401455 . 33F6 xor esi, esi
00401457 > 0FBE4434 1C movsx eax, byte ptr [esp+esi+1C] <-第二区循环开始,循环每次都取第一区循环结果的字符
0040145C . 8D5424 14 lea edx, dword ptr [esp+14]
00401460 . 6A 0A push 0A ; /Arg3 = 0000000A
00401462 . 52 push edx ; |Arg2
00401463 . 50 push eax ; |Arg1
00401464 . E8 A6600000 call 0040750F ; \1551-cra.0040750F <-每位都格式化成十进制字符串
00401469 . 8A4424 20 mov al, byte ptr [esp+20] <-格式化后的字符串的第一位到
al0040146D . 8A8C34 8C0000>mov cl, byte ptr [esp+esi+8C] <-注册码对应的字符到cl
00401474 . 83C4 0C add esp, 0C
00401477 . 3AC1 cmp al, cl <-两者比较
00401479 . 884434 1C mov byte ptr [esp+esi+1C], al
0040147D . 74 02 je short 00401481 <-不等就跳
0040147F . 32DB xor bl, bl <-相等bl就为0
00401481 > 34 12 xor al, 12 <-然后al(格式化后的字符串的第一位)与0x12异或
00401483 . 884434 1C mov byte ptr [esp+esi+1C], al <-传回去
00401487 . 46 inc esi <-计数器加一
00401488 . 83FE 04 cmp esi, 4 <-循环四次
0040148B .^ 7C CA jl short 00401457 <-没完跳上去
到这里可以得到注册码的前四位,分别是上面每次循环的al的值,不过别被骗了,和注册码比较之后的那些运算主要是掩盖真码的!正确的其实是在cmp al,cl中!且可以知道bl是一个标志
0040148D . 33C9 xor ecx, ecx
0040148F > 0FBE440C 20 movsx eax, byte ptr [esp+ecx+20] <-这里从第一区循环所得到的字符串的第五位开始取字符
前面算了四位了嘛,这里从第五位开始算
00401494 . 83F0 97 xor eax, FFFFFF97 <-与ffffff97异或
00401497 . BE 1A000000 mov esi, 1A
0040149C . 99 cdq
0040149D . F7FE idiv esi <-再与1a取余
0040149F . 8A840C 840000>mov al, byte ptr [esp+ecx+84] <-注册码从第五位开始取字符
004014A6 . 80C2 41 add dl, 41 <-再加上41
004014A9 . 3AD0 cmp dl, al <-与注册码第五位开始比较
004014AB . 88540C 20 mov byte ptr [esp+ecx+20], dl
004014AF . 74 02 je short 004014B3 <-不等就跳
004014B1 . 32DB xor bl, bl <-bl是标志,相等则bl是0
004014B3 > 80F2 76 xor dl, 76 <-所得到的真码与0x76异或
004014B6 . 88540C 20 mov byte ptr [esp+ecx+20], dl <-传回去掩盖真码
004014BA . 41 inc ecx <-计数器加一
004014BB . 8D51 04 lea edx, dword ptr [ecx+4]
004014BE . 83FA 0A cmp edx, 0A
004014C1 .^ 7C CC jl short 0040148F <-循环6次,得到注册码后六位
004014C3 . 84DB test bl, bl
004014C5 . 5E pop esi
004014C6 74 62 je short 0040152A
004014C8 . A1 00A54100 mov eax, dword ptr [41A500]
004014CD . 894424 0C mov dword ptr [esp+C], eax
004014D1 . 68 A8A04100 push 0041A0A8
004014D6 . 8D4C24 10 lea ecx, dword ptr [esp+10]
004014DA . C78424 EC0000>mov dword ptr [esp+EC], 0
004014E5 . E8 6EF10000 call 00410658
004014EA . 68 A4A04100 push 0041A0A4
004014EF . 8D4C24 10 lea ecx, dword ptr [esp+10]
004014F3 . E8 24F20000 call 0041071C
004014F8 . 68 A0A04100 push 0041A0A0
004014FD . 8D4C24 10 lea ecx, dword ptr [esp+10]
00401501 . E8 16F20000 call 0041071C
00401506 . 8B4C24 0C mov ecx, dword ptr [esp+C]
0040150A . 6A 00 push 0 ; /Arg3 = 00000000
0040150C . 6A 00 push 0 ; |Arg2 = 00000000
0040150E . 51 push ecx ; |Arg1
0040150F . E8 480B0100 call 0041205C ; \1551-cra.0041205C <-注册成功信息
嗯``算法总结:
"6D*h!dN^!g"把这串接到注册名后面,主要是补够十位,如果注册名够十位的话这也是没意义的
进行第一区循环,上面连接后的字符串的前十位每位都和0x25异或,得到新的字符串
进行第二区循环,得出注册码前四位,并把后面算的假码传回到原处,掩蔽真码
进行第三区循环,得出注册码后六位,并把后面算的假码传回到原处,掩蔽真码没了..
还有还有``
我们cmp一下:
cmp 注册成功信息,注册失败信息 = cmp "继续加油吧!","加油吧!"
我汗`````````
下面是c++注册机:
#include <iostream.h>
#include <stdio.h>
#include <windows.h>
#include <string.h>
main()
{
char name[100],code[100],temp[5];
int i,len;
memset(name,0,100);
memset(code,0,100);
memset(temp,0,5);
cout<<"Your name:";
cin>>name;
len=strlen(name);
strcat(name,"6D*h!dN^!g");
i=0;
len=0xffffff97;
for(;i<10;i++)
name[i]=(name[i]<<2)^0x25;
for(i=0;i<4;i++)
{
sprintf(temp,"%d",name[i]);
code[i]=temp[0];
memset(temp,0,5);
}
for(i=4;i<10;i++)
{
code[i]=(name[i]^len)%0x1a+0x41;
}
code[10]='\0';
cout<<"Your code:"<<code<<endl;
}
3、手动打破解补丁
其实很简单`因为程序用到了messageBox,我们把参数, 也就是要显示的字符串的地址改为真码的地址让程序给我们显示出来就行了
不过程序在运算时加密了真码,呵呵``我们nop掉!
好`
我们从上面的分析可以知道,
004014C6 74 62 je short 0040152A<-这个跳转跳过注册成功信息(其实都是同一个messagebox),字符串不同而已
0040150A . 6A 00 push 0 ; /Arg3 = 00000000
0040150C . 6A 00 push 0 ; |Arg2 = 00000000
0040150E . 51 push ecx ; |Arg1
0040150F . E8 480B0100 call 0041205C ; \1551-cra.0041205C <-注册成功信息
那个跳转跳过之后就不会来到上面的call的,我们把它nop掉
f8走到push ecx处的时候,我们D一下ecx,可以看到"加油吧!"这个成功信息
从上面的分析,我们也可以知道程序运算后的结果保存到12f740这个内存中了!
还有就是程序算出注册码之后又算出一个假码``传到12f740这个内存地址中去掩蔽真码,那我们就先这样:
00401481 > 34 12 xor al, 12<-这句nop
004014B3 > 80F2 76 xor dl, 76<-这句也nop
把0040150e处的messagebox的字符串参数地址改为0012f740
这显然不行,这样会被当作是把12f740这个数值压栈!
我们可以看到00401506 . 8B4C24 0C mov ecx, dword ptr [esp+C]<-在messagebox前的一条语句,给ecx赋值
我们就改为mov ecx,12f740
这样也不行的,多填充了一个空字节,把messagebox的参数给nop掉了,破坏了堆栈,程序死了!
那我们就不要在原地改动代码了,设一个跳转跳到全0处的空代码吧!
用peid查看一下全0的位置,我找到的是42057b处
先设一个跳转,在00401506处,也就是给ecx赋值的那句,我们改成jmp 42057b跳到我们的代码去执行
ctrl+g到42057b:
打上这样的补丁:
0042057B B9 40F71200 mov ecx, 12F740<-给ecx赋一个指针,指针是真码的地址
00420580 90 nop <-补上一个空字节
00420581 6A 00 push 0 <-messagebox的参数我们在这里压进去,等一下直接跳回那个call就行了
00420583 6A 00 push 0 <-也是messagebox的参数
00420585 51 push ecx <-真码地址,也是messagebox要显示的字符串
0042058D ^\E9 7D0FFEFF jmp 0040150F <-跳回那个call
好,我们保存一下程序,输入十位以上的注册码,注册名"jiangwu55",得到"----UGYYYCD*h!dN^!g"
注册码是十位的,这显然不对!
原因是因为程序把那串补够注册名十位数的字符串连接到后面了,变成一个内存段,每个内存单元都连起来了
所以全部输出了!
在编程时都会给数组多分配一个字节,这是为啥?呵呵``就是为了让系统自动在后面加上一个空中止!
程序在输出内存中的字符串时,遇到0结尾就停止输出,而我们这个字符串的0结尾在'g'的后面
要的只是前十位,那就把第十一位改为0就行了!
命令行下d 12f740看一下内存中:
0012F740 2D 2D 2D 2D 55 47 59 59 59 43 44 2A 68 21 64 4E | ----UGYYYCD*h!dN
0012F750 5E 21 67 00 | ^!g.
-------------------------------
12f740处是2d,我们就在12f74a处改为0
------------------------------
42057b处的补丁变成这样:
0042057B B9 40F71200 mov ecx, 12F740
00420580 90 nop
00420581 6A 00 push 0
00420583 6A 00 push 0
00420585 51 push ecx
00420586 C605 4AF71200 00 mov byte ptr [12F74A], 0 <-多了这句,
0042058D ^ E9 7D0FFEFF jmp 0040150F
-----------------------------
把所有修改保存一下,运行程序,注册名"jiangwu55",我们得到"----UGYYYC"呵呵``十位字符的真码,马上记下来!^-^
全部的所打的补丁如下:
00401481 > 34 12 xor al, 12 <-这句nop
004014B3 > 80F2 76 xor dl, 76 <-这句也nop
004014C6 74 62 je short 0040152A<-这句也nop
00401506 /E9 70F00100 jmp 0042057B <-跳到我们的补丁上执行
0040150B |90 nop <-补空
0040150C |6A 00 push 0 <-这句我们没执行
0040150E |51 push ecx <-这句我们没执行
0040150F |E8 480B0100 call 0041205C <-注册成功的消息框,上面是它的参数,不过我们直接在全0处压栈了,所以直接 跳到这里执行了!
0042057B B9 40F71200 mov ecx, 12F740 <-跳到这里执行
00420580 90 nop <-补空
00420581 6A 00 push 0 <-参数1
00420583 6A 00 push 0 <-参数2
00420585 51 push ecx <-参数3
00420586 C605 4AF71200 00 mov byte ptr [12F74A], 0 <-加个空中止
0042058D ^ E9 7D0FFEFF jmp 0040150F <-跳回去
我们这个程序被当作是注册机来用了,就算注册成功也只显示真码!所以我们如果还要输入十位的注册码的限制的话实在麻烦,那就把4013ed处的jnb改为jmp,^-^
完了```
偶要去玩咯```被被~~~~~~~~~~~~~~~~~
----------------------------------------------------------------------------------
└经验总结┐:
```不知不觉做了两个注册机``哦呵呵呵``
----------------------------------------------------------------------------------
└版权声明┐ 本文原创于看雪软件安全论坛, 转载请注明作者并保持文章的完整, 谢谢!
2007年5月6日 11:14:37
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课