EditPlus 2.01b 注册机的制作 (22千字)
EditPlus 2.01b 注册机的制作E-mail:luoyi.ly@yeah.net
写作
修改
版本:2.01 Beta
软件背景资料
运行平台: Win9X
文件名称: Ep2setup20b.exe
程序类型: Text Edit
下载地点: http://www.editplus.com
文件大小: 910KB
使用的工具
Trw2000 V1.23--Win9X Debugger
W32Dasm V8.93--Win9X Dissembler
Masm32 V5.00--KeyGen Compiler
难易程度
Easy( ) Medium(X) Hard( ) Pro( )
------------------=====Begin=====------------------
关于EditPlus的注册码问题,在《论坛精华2》中,dr0大客曾经说过:“好象从1.21版本就
开始了。它判断注册码不是集中在一个地方判断,程序刚启动的时候判断几位,退出的时候再判断另
外几位,你使用它的preferences菜单的时候再判断一位。
以上是旧版本的情况。2.01a版本与此有所不同。由于不知道它会在哪里判断注册码,所以不
能清除BPR断点,即要一直保留针对输入的假注册码所设的BPR断点;或者用bpx RegDeleteValueA设断
点,试用一下其各种功能,看它是否会删除注册表中的假注册码,就可以找到判断注册码的地方。另外,
这个软件的注册码的前5位是由后面的计算出来的,而后面有多少位并无明显的限制,所以各人作出来的
注册码长短不一。另,据说注册码似乎有随机性。 ”
由这段话,我们可以认识到:程序判断注册码的地点不集中,有很多操作可以触发判断注册码
正确与否的例程。但是,由实际分析结果来看,其所说的用bpr断点找到各个判断例程的方法在对付这个
程序时并不适用。原因很简单:该程序判断注册码正确与否的子例程总共有8处之多。而触发这8个判断
代码的条件我们并不清楚。就我跟踪的结果来看,一次是在输入注册码时(这也是最明显的一处,
《论坛精华2》中,xiA Qin的爆破就是在这段子例程中修改代码的);一次是在你关闭程序时;一次是
在你按照程序提示,重新进入程序时;前面这三处还很容易想到,因为大部分的软件都是在这些时候判
断注册码的正确性。而剩下来的其他4次,其触发事件就很独特了。dr0大客说使用preferences菜单时会
判断一位,就我跟踪来看,并不正确。虽然在使用preferences菜单的过程中,会调用判断注册码的例程,
但此时该例程的作用并不是判断注册码正确与否,而仅仅是程序运行的正常需要。倒是在按下工具条上的
“open”快捷纽时,会触发一处注册码判断。至于其他几处,我没有去细找。因为此时,我已经发现了程
序判断注册码的核心子例程了——也就是说,程序每个判断注册码的地方,都要调用该字例程。找到核心
子例程之后,要找判断点就容易多了。该子例程的作用其实很简单——把一个16进制数转成字符串存放在
指定的地点同时作灭零处理(这也就是后面计算注册码步骤中的第7步的原因)。下面就是该程序判断注册
码的核心子例程:
* Referenced by a CALL at Addresses:
|:00419A28 , :004218EA , :004575A5 , :0045EAFF , :0045EB2F
|:0045EB5F , :0045EB8F , :0045F6F6 , :004622C1 , :0046264E
|:004626A4 , :0047B952 , :0049CD24 ;总共13处调用
|
:0048FBE0 55 push ebp
:0048FBE1 8BEC mov ebp, esp
:0048FBE3 83EC20 sub esp, 00000020
:0048FBE6 8B4508 mov eax, dword ptr [ebp+08]
:0048FBE9 56 push esi
:0048FBEA 8945E8 mov dword ptr [ebp-18], eax
:0048FBED 8945E0 mov dword ptr [ebp-20], eax
:0048FBF0 8D4510 lea eax, dword ptr [ebp+10]
:0048FBF3 C745EC42000000 mov [ebp-14], 00000042
:0048FBFA 50 push eax
:0048FBFB 8D45E0 lea eax, dword ptr [ebp-20]
:0048FBFE FF750C push [ebp+0C]
:0048FC01 C745E4FFFFFF7F mov [ebp-1C], 7FFFFFFF
:0048FC08 50 push eax
:0048FC09 E897470000 call 004943A5
:0048FC0E 83C40C add esp, 0000000C
:0048FC11 FF4DE4 dec [ebp-1C]
:0048FC14 8BF0 mov esi, eax
:0048FC16 7808 js 0048FC20
:0048FC18 8B45E0 mov eax, dword ptr [ebp-20]
:0048FC1B 802000 and byte ptr [eax], 00
:0048FC1E EB0D jmp 0048FC2D
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048FC16(C)
|
:0048FC20 8D45E0 lea eax, dword ptr [ebp-20]
:0048FC23 50 push eax
:0048FC24 6A00 push 00000000
:0048FC26 E862460000 call 0049428D
:0048FC2B 59 pop ecx
:0048FC2C 59 pop ecx
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0048FC1E(U)
|
:0048FC2D 8BC6 mov eax, esi
:0048FC2F 5E pop esi
:0048FC30 C9 leave
:0048FC31 C3 ret
在这里,很清楚地看到,主程序中总共有13处调用了该核心子例程,但并不是每一次调用都是用
来判断注册码的。我们如何区分?很简单,分别到这13处调用处看一下就知道了。下面举一例说明:
:004199F4 83C117 add ecx, 00000017 <====此处,ecx是根据你名字算出来的一个值
:004199F7 B8ABAAAA2A mov eax, 2AAAAAAB
:004199FC F7E9 imul ecx
:004199FE 8BC2 mov eax, edx
:00419A00 C1E81F shr eax, 1F
:00419A03 8D440203 lea eax, dword ptr [edx+eax+03]
:00419A07 8D0CC500000000 lea ecx, dword ptr [8*eax+00000000]
:00419A0E 2BC8 sub ecx, eax
:00419A10 81E10F000080 and ecx, 8000000F
:00419A16 7905 jns 00419A1D
:00419A18 49 dec ecx
:00419A19 83C9F0 or ecx, FFFFFFF0
:00419A1C 41 inc ecx
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00419A16(C)
|
:00419A1D 51 push ecx
:00419A1E 8D542418 lea edx, dword ptr [esp+18]
* Possible StringData Ref from Data Obj ->"%1X"
|
:00419A22 6880534F00 push 004F5380
:00419A27 52 push edx
:00419A28 E8B3610700 call 0048FBE0 <====调用核心子例程
:00419A2D 8B86F0020000 mov eax, dword ptr [esi+000002F0]
:00419A33 83C40C add esp, 0000000C
:00419A36 8B00 mov eax, dword ptr [eax] <====eax指向注册码
:00419A38 8378F807 cmp dword ptr [eax-08], 00000007 <====长度大于7?
:00419A3C 7C0B jl 00419A49 <====not great,bad guy!
:00419A3E 8A4006 mov al, byte ptr [eax+06] <====注册码第7位
:00419A41 8A4C2414 mov cl, byte ptr [esp+14] <====由名字计算得出的校验值
:00419A45 3AC1 cmp al, cl <====比较
:00419A47 740C je 00419A55 <====equal,good guy!
这就很容易看出,419A28处,一定是一个判断点(但是它的判断触发事件,我们并不知道!)。
再来看一处不是判断点的代码,区别就很明显了:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0045EAD5(C)
|
:0045EAE7 D9442420 fld dword ptr [esp+20]
:0045EAEB DC742414 fdiv qword ptr [esp+14]
:0045EAEF 83EC08 sub esp, 00000008
:0045EAF2 8D442430 lea eax, dword ptr [esp+30]
:0045EAF6 DD1C24 fstp qword ptr [esp]
* Possible StringData Ref from Data Obj ->"%.2f"
|
:0045EAF9 68C0614F00 push 004F61C0
:0045EAFE 50 push eax
:0045EAFF E8DC100300 call 0048FBE0
:0045EB04 83C410 add esp, 00000010
:0045EB07 8D4C2428 lea ecx, dword ptr [esp+28]
:0045EB0B 51 push ecx
:0045EB0C 8D8E00030000 lea ecx, dword ptr [esi+00000300]
:0045EB12 E88D450400 call 004A30A4
:0045EB17 D9442424 fld dword ptr [esp+24]
:0045EB1B DC742414 fdiv qword ptr [esp+14]
:0045EB1F 83EC08 sub esp, 00000008
:0045EB22 8D542430 lea edx, dword ptr [esp+30]
:0045EB26 DD1C24 fstp qword ptr [esp]
整段程序没有一个比较语句。而且,在跟踪时我们可以通过“d”和“?”发现,该段程序和我们
输入的注册码以及名字没有任何联系,所以我们可以断定:此处不是在比较注册码。
下面,我把这13处调用及其判断对象整理如下供大家
(L=注册码长度;Str[]=注册码字符串)
序号 地址 是否判断点 判断对象
01 00419A28 是 L>7 AND Str[6]
02 004218EA 是 L>A AND Str[9]
03 004575A5 是 L>8 AND Str[7]
04 0045EAFF 否
05 0045EB2F 否
06 0045EB5F 否
07 0045EB8F 否
08 0045F6F6 否
09 004622C1 是 L>B AND Str[A]
10 0046264E 是 L>5 AND Str[4]
11 004626A4 是 Str[2] AND Str[3]
12 0047B952 是 Str[0] AND Str[1]
13 0049CD24 是 L>9 AND Str[8]
由该表我们可以发现,注册码至少应为11位长,程序只判断前11位中的10位(除开第6位,也就是
Str[5])。由于未发现其他判断处,所以,程序的注册码的长度应该只要大于11就可以。其中,第1,2位是
通过后面的号码推算出来的。而其他的(从第3位往后,除开第6位,到第11位为止)号码则由名字推算出来。
具体算法可以表示如下:
(1) 由名字得到一个Magic Number:
SUM=1;
for(i=0;i<strlen(name);i++) SUM+=name[i];
得到的SUM就是我们所需要的Magic Number。
(2) 由Magic Number算出注册码的第5和7--11位。(具体算法见注册机源码,此处从略)
(3) 由名字算出注册码的第3,4位:
(num[]是一张256个Word的数据表,详见注册机源码)
ax=0;dx=0;bx=0;
for (i=0;i<strlen(name);i++)
{
dx=ax;
dx&=0xff;
dx^=name[i];
bx=ax>>8;
ax=num[dx];
ax^=bx;
}
得出的ax中的ah,就是注册码的第3,4位。
(4) 注册码的第6位任意(在我的注册机里,假设它为0)。
(5) 由注册码的第3--11算出注册码的第1,2位。
(num[]和第三步的含义相同;newstr[]指向注册码第2位后面的字符串)
ax=0;dx=0;bx=0;
for (i=0;i<strlen(newstr);i++)
{
dx=ax;
dx&=0xff;
dx^=newstr[i];
bx=ax>>8;
ax=num[dx];
ax^=bx;
}
(6) 得出的ax中的ah,就是注册码的第1,2位
(7) 如果注册码的第1位为0,则需要延长注册码的长度,以使第一位不为0。(在我的注册机里,没有
使注册码延长的代码。我是通过让用户改变用户名实现的。如果哪位对它有兴趣的话,可以自己
添加该功能)。
------------------===== Last Words=====------------------
该程序的破解,可以说是动态跟踪+静态分析的典范:为了找全程序的注册监测点,我们需要在多
次动态跟踪,熟悉程序注册流程的基础上对静态反汇编代码仔细分析;分析出来检测点以后,又要由动态
跟踪确定各检测点调用核心子例程的各自参数的含义 。两者结合,才能彻底搞清楚程序的注册算法,从而
写出注册机。
常看到有人问注册机里面的那张很大的表格是怎么的来的。呵呵……当然不是抄下来的拉。你可以
在Trw2000里“d memeory address l length >filename”把数表所在的内存数据保存下来。再用EditPlus
(也就是在这篇文章里挨揍的那个程序)的“列块选择”功能去掉一些不必要的空格和地址数据以及ASCII
码数据,再用Tc2.0编一个小程序就可以转成你想要的格式的表格了。这个程序编起来不是很复杂,有兴趣
的朋友就当作是学习C语言文件操作的一个入门吧!
注册机的编译器选择也很重要。在32位程序大行其道的今天,Tc2.0实在是有点力不从心-----虽然
它是最经典的编译器之一。相形之下,Vc++ 6.0则方便得多。而我手头现在没有Vc++,所以只好将就着用
M32asmV5了。我是临时报佛脚地学了一下Win32Asm^_^,所以代码写得比较乱。有条件的朋友建议用Vc++6.0
编注册机,肯定好看多了!:)
