对EditPlus3.6的注册算法分析及注册机写法(C语言)
本帖最后由 henshan2002 于 2013-12-29 16:59 编辑对EditPlus3.6的注册算法分析及注册机源码【逆向思路】
1、分析程序验证流程,找出关键判断点。
2、分析关键算法。
3、写出注册机。
【第一步:分析验证大概流程】
这个软件的界面非常友好。安装后,会出现对话框 如图1
file:///C:/Users/ljb/AppData/Local/Temp/Wiz/7ce61343-1029-4669-b6cc-f6ecae7a9477_128_files/15097948.png
当输入用户名和一个regcode后,出现一个提示错误信息的对话框 如图2
file:///C:/Users/ljb/AppData/Local/Temp/Wiz/7ce61343-1029-4669-b6cc-f6ecae7a9477_128_files/15380481.png
好吧,我们就在这里下断吧。bp MessageBoxExW 断到了系统里面。接着ALT+F9返回到用户领空来到这里 如图3
file:///C:/Users/ljb/AppData/Local/Temp/Wiz/7ce61343-1029-4669-b6cc-f6ecae7a9477_128_files/15788237.png
这里并不是注册分析的关键点,经过几次CTR+F9返回到这里
004CB77F .E8 4F0D0400 call editplus.0050C4D3 ;错误4
观察到这段代码的上面有个字符处理函数,因此我们估计得出,回到了分析代码里面了。为了证明这一点,实际操作过程中还要重新来过,进入到这里观察寄存器和堆栈里的数据
这里具体的过程,我就不给图了。
程序验证时大概流程如下:
头部是
004CB6A0 .81EC 34020000 sub esp,0x234 ;这里是检测注册码头部
关键算法是这里
004CB769 .E8 02F9FFFF call editplus.004CB070 ;关键算法运算
运算的结果由 eax带回,1就是正确,0当然是失败了。如下图:(修改那个jnz为jmp就 不会有错误提示了,但重启软件会再验证,所以修改这里一处还不能爆破成功)如图4
file:///C:/Users/ljb/AppData/Local/Temp/Wiz/7ce61343-1029-4669-b6cc-f6ecae7a9477_128_files/17590501.png
【第二步:算法分析】
A、初始化代码处的分析
004CB6A0 .81EC 34020000 sub esp,0x234 ;这里是检测注册码头部
004CB6A6 .A1 5C7B5B00 mov eax,dword ptr ds:
004CB6AB .33C4 xor eax,esp
004CB6AD .898424 300200>mov dword ptr ss:+0x230],eax
004CB6B4 .53 push ebx
004CB6B5 .55 push ebp
004CB6B6 .8D4424 08 lea eax,dword ptr ss:+0x8]
004CB6BA .50 push eax
004CB6BB .8BE9 mov ebp,ecx
004CB6BD .68 F4010000 push 0x1F4
004CB6C2 .8D4C24 4C lea ecx,dword ptr ss:+0x4C]
004CB6C6 .51 push ecx
004CB6C7 .8D95 C8000000 lea edx,dword ptr ss:+0xC8]
004CB6CD .52 push edx
004CB6CE .E8 9DFEFFFF call editplus.004CB570 ;得到用户名和长度
004CB6D3 .83C4 10 add esp,0x10
004CB6D6 .837C24 08 00cmp dword ptr ss:+0x8],0x0 ;比较用户名长度
004CB6DB .8BD8 mov ebx,eax
004CB6DD .895C24 0C mov dword ptr ss:+0xC],ebx
004CB6E1 .0F84 20010000 je editplus.004CB807 ;用户名长度=0就跳
004CB6E7 .57 push edi
004CB6E8 .8D4424 0C lea eax,dword ptr ss:+0xC]
004CB6EC .50 push eax
004CB6ED .6A 32 push 0x32
004CB6EF .8D4C24 1C lea ecx,dword ptr ss:+0x1C]
004CB6F3 .51 push ecx
004CB6F4 .8D55 74 lea edx,dword ptr ss:+0x74]
004CB6F7 .52 push edx
004CB6F8 .E8 73FEFFFF call editplus.004CB570 ;得到密码和长度
004CB6FD .8BF8 mov edi,eax
004CB6FF .8B4424 1C mov eax,dword ptr ss:+0x1C] ;eax=密码长度
004CB703 .83C4 10 add esp,0x10
004CB706 .85C0 test eax,eax
004CB708 .0F84 F8000000 je editplus.004CB806 ;密码长度要>0
004CB70E .56 push esi
004CB70F .33F6 xor esi,esi ;i=0
004CB711 .85C0 test eax,eax
004CB713 .7E 52 jle Xeditplus.004CB767
004CB715 .EB 09 jmp Xeditplus.004CB720
004CB717 .8DA424 000000>lea esp,dword ptr ss:
004CB71E .8BFF mov edi,edi
004CB720 >66:0FB6043E movzx ax,byte ptr ds:+edi] ;eax=密码
004CB725 .0FB7D8 movzx ebx,ax
004CB728 .B9 00010000 mov ecx,0x100
004CB72D .66:3BD9 cmp bx,cx
004CB730 .73 0D jnb Xeditplus.004CB73F ;密码大于0x100则跳
004CB732 .0FB6D3 movzx edx,bl ;edx=密码
004CB735 .0FB70455 C8CC>movzx eax,word ptr ds:*2+0x5ECCC8] ;eax=字符阵列[密码*2]
004CB73D .EB 1A jmp Xeditplus.004CB759
004CB73F >0FB7C3 movzx eax,bx
004CB742 .50 push eax ; /StringOrChar
004CB743 .FF15 50E75600 call dword ptr ds:[<&USER32.CharUpperW>] ; \CharUpperW
004CB749 .0FB7C0 movzx eax,ax
004CB74C .66:85C0 test ax,ax
004CB74F .75 05 jnz Xeditplus.004CB756
004CB751 .0FB7C3 movzx eax,bx
004CB754 .EB 03 jmp Xeditplus.004CB759
004CB756 >0FB7C0 movzx eax,ax
004CB759 >88043E mov byte ptr ds:+edi],al ;--------------
004CB75C .46 inc esi ;i++
004CB75D .3B7424 10 cmp esi,dword ptr ss:+0x10] ;i<7
004CB761 .^ 7C BD jl Xeditplus.004CB720 ;上面的循环把密码小写部分变成大写
004CB763 .8B5C24 14 mov ebx,dword ptr ss:+0x14] ;ebx=用户名
上面的代码比较简单,总结来说,做了三件事:
1、得到输入的用户名和长度;
2、得到输入的注册码和长度;
3、把注册码转成大写;
用c语言实现如下:(注,为了使流程与源码一致,没有做任何优化,可能看起来比较臃肿)
int iGetLeng(char* p)
{//得到长度
for (int i=0;p;i++);
return i;
}
void vHitoLow( char* p)
{//小写转大写
for (int i=0;p;i++)
{
p=(p>='a'&&p<='z')?(p-32):p;
}
}
int main(int argc, char* argv[])
{
long eax;
int name_len,key_len;
char name[20], key[15];
printf("请输入你的注册名\n");
scanf("%s",name);
name_len=iGetLeng(name);
if (!name_len) return;
printf("请输入你的注册码\n");
scanf("%s",key);
key_len=iGetLeng(key);
if (!key_len) return;
vHitoLow(key);//小写转大写
}
B、核心算法分析
原代码如下
004CB070/$83EC 10 sub esp,0x10 ;关键运算程序
004CB073|.A1 5C7B5B00 mov eax,dword ptr ds:
004CB078|.33C4 xor eax,esp
004CB07A|.894424 0C mov dword ptr ss:+0xC],eax
004CB07E|.53 push ebx
004CB07F|.55 push ebp
004CB080|.8B6C24 1C mov ebp,dword ptr ss:+0x1C] ;ebp=用户名
004CB084|.56 push esi
004CB085|.8B7424 24 mov esi,dword ptr ss:+0x24] ;esi=注册码
004CB089|.8BC5 mov eax,ebp ;eax=用户名
004CB08B|.57 push edi
004CB08C|.8D50 01 lea edx,dword ptr ds:+0x1]
004CB08F|.90 nop
004CB090|>8A08 /mov cl,byte ptr ds: ;cl=用户名[0]
004CB092|.40 |inc eax
004CB093|.84C9 |test cl,cl
004CB095|.^ 75 F9 \jnz Xeditplus.004CB090
004CB097|.2BC2 sub eax,edx
004CB099|.8BD8 mov ebx,eax ;ebx=用户名长度
004CB09B|.8BC6 mov eax,esi ;eax=注册码
004CB09D|.8D50 01 lea edx,dword ptr ds:+0x1]
004CB0A0|>8A08 /mov cl,byte ptr ds:
004CB0A2|.40 |inc eax
004CB0A3|.84C9 |test cl,cl
004CB0A5|.^ 75 F9 \jnz Xeditplus.004CB0A0
004CB0A7|.2BC2 sub eax,edx
004CB0A9|.8BF8 mov edi,eax ;edi=注册码长度-------整个上面代码就是得到用户名长度和注册码长度
004CB0AB|.E8 F0F6FFFF call editplus.004CA7A0 ;【这里面运算得到byte[512]因子】--重要1
004CB0B0|.53 push ebx
004CB0B1|.55 push ebp
004CB0B2|.6A 00 push 0x0
004CB0B4|.E8 47F7FFFF call editplus.004CA800 ;对name进行运算-返回一个eax-重要2
004CB0B9|.0FB7C0 movzx eax,ax
004CB0BC|.50 push eax ;Arg3
004CB0BD|.8D4C24 20 lea ecx,dword ptr ss:+0x20]
004CB0C1|.68 404A5800 push editplus.00584A40 ;ASCII "%02X"
004CB0C6|.51 push ecx ;Arg1
004CB0C7 >|.E8 54F9FFFF call editplus.004CAA20 ;sprintf(ecx,"%02X", eax)
004CB0CC|.0FB656 02 movzx edx,byte ptr ds:+0x2] ;edx=key[2]
004CB0D0|.0FBE4C24 28 movsx ecx,byte ptr ss:+0x28] ;ecx=name2[0]
004CB0D5|.8D46 02 lea eax,dword ptr ds:+0x2]
004CB0D8|.83C4 18 add esp,0x18
004CB0DB|.3BD1 cmp edx,ecx
004CB0DD|.74 15 je Xeditplus.004CB0F4 ;不跳就说明失败了
004CB0DF|>5F pop edi
004CB0E0|.5E pop esi
004CB0E1|.5D pop ebp
004CB0E2|.33C0 xor eax,eax
004CB0E4|.5B pop ebx
004CB0E5|.8B4C24 0C mov ecx,dword ptr ss:+0xC]
004CB0E9|.33CC xor ecx,esp
004CB0EB|.E8 4F460700 call editplus.0053F73F
004CB0F0|.83C4 10 add esp,0x10
004CB0F3|.C3 retn
004CB0F4|>0FB656 03 movzx edx,byte ptr ds:+0x3]
004CB0F8|.0FBE4C24 11 movsx ecx,byte ptr ss:+0x11] ;name2[3]
004CB0FD|.3BD1 cmp edx,ecx
004CB0FF|.^ 75 DE jnz Xeditplus.004CB0DF ;跳走则失败了
004CB101|.83C7 FE add edi,-0x2
004CB104|.57 push edi
004CB105|.50 push eax
004CB106|.6A 00 push 0x0
004CB108|.E8 F3F6FFFF call editplus.004CA800 ; 对key2进行运算-返回一个eax-重要2
004CB10D|.0FB7D0 movzx edx,ax ;eax=2627
004CB110|.52 push edx
004CB111|.8D4424 20 lea eax,dword ptr ss:+0x20]
004CB115|.68 404A5800 push editplus.00584A40 ;ASCII "%02X"
004CB11A|.50 push eax
004CB11B|.E8 00F9FFFF call editplus.004CAA20
004CB120|.0FB60E movzx ecx,byte ptr ds: ;ecx=key[0]
004CB123|.0FBE5424 28 movsx edx,byte ptr ss:+0x28] ;edx=name3[0]
004CB128|.83C4 18 add esp,0x18
004CB12B|.3BCA cmp ecx,edx
004CB12D|.^ 75 B0 jnz Xeditplus.004CB0DF
004CB12F|.0FB646 01 movzx eax,byte ptr ds:+0x1] ;eax=key[1]
004CB133|.0FBE4C24 11 movsx ecx,byte ptr ss:+0x11] ;ecx=name3[1]
004CB138|.33D2 xor edx,edx
004CB13A|.3BC1 cmp eax,ecx
004CB13C|.8B4C24 1C mov ecx,dword ptr ss:+0x1C]
004CB140|.5F pop edi
004CB141|.0F94C2 sete dl
004CB144|.5E pop esi
004CB145|.5D pop ebp
004CB146|.5B pop ebx
004CB147|.33CC xor ecx,esp
004CB149|.8BC2 mov eax,edx
004CB14B|.E8 EF450700 call editplus.0053F73F
004CB150|.83C4 10 add esp,0x10
004CB153\.C3 retn
其中
004CB0AB|.E8 F0F6FFFF call editplus.004CA7A0
内容如下
004CA7A0 $68 00020000 push 0x200 ;这里也是非常重要的计算
004CA7A5 .6A 00 push 0x0
004CA7A7 .68 B0DC5E00 push editplus.005EDCB0
004CA7AC .E8 CF570700 call editplus.0053FF80 ;BYTE dizhi[512]={0};
004CA7B1 .83C4 0C add esp,0xC
004CA7B4 .33D2 xor edx,edx
004CA7B6 .EB 08 jmp Xeditplus.004CA7C0
004CA7B8 .8DA424 000000>lea esp,dword ptr ss:
004CA7BF .90 nop
004CA7C0 >B8 C1C00000 mov eax,0xC0C1 ;eax=c0c1---这是个固定的因子
004CA7C5 .B9 01000000 mov ecx,0x1 ;ecx=1
004CA7CA .8D9B 00000000 lea ebx,dword ptr ds: ;ebx=用户名长度
004CA7D0 >85CA test edx,ecx
004CA7D2 .74 08 je Xeditplus.004CA7DC
004CA7D4 .66:310455 B0D>xor word ptr ds:*2+0x5EDCB0],ax ;这是一个16位的数
004CA7DC >03C0 add eax,eax
004CA7DE .35 03400000 xor eax,0x4003
004CA7E3 .03C9 add ecx,ecx ;ecx=ecx*2
004CA7E5 .81F9 00010000 cmp ecx,0x100
004CA7EB .0FB7C0 movzx eax,ax
004CA7EE .^ 7C E0 jl Xeditplus.004CA7D0 ;ecx<100
004CA7F0 .42 inc edx ;i++;
004CA7F1 .81FA 00010000 cmp edx,0x100 ;i<100
004CA7F7 .^ 7C C7 jl Xeditplus.004CA7C0
004CA7F9 .C3 retn
上面这段代码产生了一个512byte大小的数据区域,且它的内容与输入的用户名与注册码无关,C语言实现如下
void Call004CA7A0()
{
long eax,i2;
for (int i1=0;i1<256;i1++)
{
dizhi=0;
}
for (int i=0;i<256;i++)
{
eax=0xc0c1;
i2=0x1;
while(i2<256)
{
if(!((i&i2)==0)) dizhi=dizhi^(unsigned short) eax;
eax=(eax*2)^0x4003;
i2=i2+i2;
eax=unsigned short (eax);
}
}
}
另外一个重要的地方是004CB0B4|.E8 47F7FFFF call editplus.004CA800
004CA800/$8B4C24 08 mov ecx,dword ptr ss:+0x8] ;ecx=用户名
004CA804|.8B4424 0C mov eax,dword ptr ss:+0xC] ;eax=用户长度
004CA808|.8D1401 lea edx,dword ptr ds:+eax] ;edx=用户名地址+4位
004CA80B|.3BCA cmp ecx,edx
004CA80D|.73 25 jnb Xeditplus.004CA834
004CA80F|.8B4424 04 mov eax,dword ptr ss:+0x4] ;eax=arg1=0
004CA813|.56 push esi
004CA814|.57 push edi
004CA815|>0FB639 /movzx edi,byte ptr ds: ;edi=name
004CA818|.0FB6F0 |movzx esi,al ;esi=0
004CA81B|.66:C1E8 08 |shr ax,0x8
004CA81F|.33F7 |xor esi,edi
004CA821|.66:330475 B0D>|xor ax,word ptr ds:*2+0x5EDCB0] //这里就是前面那个因子数据的地方
004CA829|.41 |inc ecx ;i++
004CA82A|.0FB7C0 |movzx eax,ax
004CA82D|.3BCA |cmp ecx,edx
004CA82F|.^ 72 E4 \jb Xeditplus.004CA815
004CA831|.5F pop edi
004CA832|.5E pop esi
004CA833|.C3 retn
004CA834|>66:8B4424 04mov ax,word ptr ss:+0x4]
004CA839\.C3 retn
分析得出,这里把参数1与那个512byte大小的数据块内容进行一系列运算后得到一个16位数返回。C语言实现如下
long Call004CA800( char *name)
{
long eax=0;
long esi;
for (int i=0;name;i++)
{
esi=BYTE (eax);
eax=eax>>8;
esi=esi^((BYTE)(name&0xff));
eax= unsigned short (eax)^ dizhi;
eax=unsigned short (eax);
}
return eax;
}
C、总结算法流程
1)004CB0AB call editplus.004CA7A0 ;运算得到byte大小的因子数据,后面的运算要用到这里面的数据
2)004CB0B4 call editplus.004CA800 ;利用上面那个数据块对用户名进行运算,因为这里push的是用户名
3)004CB0C7 > call editplus.004CAA20 ;sprintf(ecx,"%02X", eax),这里是把上面运算的结果,以字符形式输出到ecx中,
;比方说,eax=0x123;运算之后,ecx="123";
4)
004CB0CC movzx edx,byte ptr ds: ;edx=key
004CB0D0 movsx ecx,byte ptr ss: ;ecx=name2
004CB0D5 lea eax,dword ptr ds:
004CB0D8 add esp,0x18
004CB0DB| cmp edx,ecx
004CB0DDje Xeditplus.004CB0F4 ;不跳就说明失败了
//上面的代码很简单,就是说判断输入的注册码的第3位要与第三步算出的内容的第1位要一样。否则验证就失败。
5)
004CB0F4|> \0FB656 03 movzx edx,byte ptr ds:
004CB0F8|.0FBE4C24 11 movsx ecx,byte ptr ss: ;name2
004CB0FD|.3BD1 cmp edx,ecx
004CB0FF|.^ 75 DE jnz Xeditplus.004CB0DF ;跳走则失败了
//接下来是比较注册码的第4位要与第三步算出的内容的第2位要一样。否则验证就失败。
6)004CB108|.E8 F3F6FFFF call editplus.004CA800这里再一次进行运算,就是上面第2步那个函数,但这一次push的内容不是用户名了,而是注册码从第3位开始的内容
比如输入的注册码是“52PoJie”那这时push的是"POJIE"注意是大写。
7)004CB11B|.E8 00F9FFFF call editplus.004CAA20和上面那个一样,就是把0xh的内容,转成字符
8)
004CB120|.0FB60E movzx ecx,byte ptr ds: ;ecx=key
004CB123|.0FBE5424 28 movsx edx,byte ptr ss: ;edx=name3
004CB128|.83C4 18 add esp,0x18
004CB12B|.3BCA cmp ecx,edx
004CB12D|.^ 75 B0 jnz Xeditplus.004CB0DF
004CB12F|.0FB646 01 movzx eax,byte ptr ds: ;eax=key
004CB133|.0FBE4C24 11 movsx ecx,byte ptr ss: ;ecx=name3
//上面就是说,注册码的第1位要与第7步算出来的值的第1位一样,否则,直接失败
//注册码的第2位要与第7步算出来的值的第2位一样,否则,直接失败
仔细看,程序只验证了输入的注册码的前四位。
【第三步:注册机写法】
// crEditPlus.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdio.h>
#define BYTE unsigned char
unsigned short dizhi[256]={0};
void Call004CA7A0()
{
long eax,i2;
for (int i1=0;i1<256;i1++)
{
dizhi=0;
}
for (int i=0;i<256;i++)
{
eax=0xc0c1;
i2=0x1;
while(i2<256)
{
if(!((i&i2)==0)) dizhi=dizhi^(unsigned short) eax;
eax=(eax*2)^0x4003;
i2=i2+i2;
eax=unsigned short (eax);
}
}
}
long Call004CA800( char *name)
{
long eax=0;
long esi;
for (int i=0;name;i++)
{
esi=BYTE (eax);
eax=eax>>8;
esi=esi^((BYTE)(name&0xff));
eax= unsigned short (eax)^ dizhi;
eax=unsigned short (eax);
}
return eax;
}
void vHitoLow( char* p)
{//小写转大写
for (int i=0;p;i++)
{
p=(p>='a'&&p<='z')?(p-32):p;
}
}
int iGetLeng(char* p)
{//得到长度
for (int i=0;p;i++);
return i;
}
int main(int argc, char* argv[])
{
long eax;
int name_len,key_len;
char name[20];
printf("请输入你的注册名\n");
scanf("%s",name);
name_len=iGetLeng(name);
if (!name_len) return;
char key[15];
printf("请输入你的注册码\n");
scanf("%s",key);
key_len=iGetLeng(key);
if (!key_len) return;
vHitoLow(key);
Call004CA7A0();
eax=Call004CA800(name);
eax=unsigned short (eax);
char name2[15];
char key2[15]={0x0};
sprintf(name2,"%02x",eax);
key[2]=name2[0];
key[3]=name2[1];
for (int i=0;key+2];i++)
{
key2=key+2];
}
/*char key2="99DIY";*/
vHitoLow(key2);
eax=Call004CA800(key2);
char name3[15];
sprintf(name3,"%02x",eax);
key[0]=name3[0];
key[1]=name3[1];
printf("the key is:%s\n",key);
}
让我们来看看是否正确: 如图5 6
file:///C:/Users/ljb/AppData/Local/Temp/Wiz/1269364.png
file:///C:/Users/ljb/AppData/Local/Temp/Wiz/1338722.png
_Fezz 发表于 2014-11-26 16:10
前辈还在吗?过了一年,EditPlus也仅仅是从3.60升到了3.70,我看了下3.70的算法,和3.60几乎一模一样,但这 ...
它的重启验证我没有仔细分析。我上面破文确实没有完全破解,这个注册机有大概20%的机率算不出来,因为目前没有再研究,所以不能给你更多的信息了,请你自己去研究吧,我想不难。 前辈还在吗?过了一年,EditPlus也仅仅是从3.60升到了3.70,我看了下3.70的算法,和3.60几乎一模一样,但这样写的注册机算出来的注册码是无法绕过3.70的重启验证的,难道之前3.60能绕过,还是说前辈有什么别的解决方案。 自己先沙发, 看看怎么用 c太深奥啦支持一下 图文并貌好 新手表示看不懂。但是很厉害的样子! 膜拜一下~ 写的很仔细,感激不尽 看起来很厉害的样子! 好厉害的样子,太长了