破解完全入门篇(转帖)

时间:2022-08-19 20:03:49

作者: emailwht


第一章--前言 
好多哥们儿说看教程跟老大的书都看不太明白,所以,我尽量把话说到最容易理解的份上,本文写给那些刚入门和尚未入门的朋友们... 
目录 
no.1------------------前言(说明一下) 
no.2------------------汇编语言 
no.3------------------Windows程序 
no.4------------------调试器及相关工具入门 
no.5------------------破解原理 
no.6------------------初级破解实践,强暴一个软件 
no.7------------------中级破解实践,找到注册码及写内存注册机 
no.8------------------高级破解实践,分析软件算法,编写注册机 
由于现在网吧,临时写来,所以,今天只写个前言吧,呵呵... 
本章只作一些说明,现在也说了这么多了,没别的了,卖个广告,推荐几本书吧。 
首先,力荐看雪老大《加密与解密--软件保护技术及完全解决方案》,绝对物超所值,要的抢先了...(汗~~俺都没看过,看来要落伍了)。当然,还有看雪精华一、二、三、四以及将要出来的五,足够带你上路,还有风飘雪大虾的《风飘雪破解教程》等等等等(其它一些,没说到的就请自行搜集吧),还有就是常到论坛来转转 ;=》 
另外,我觉的你真的很有必要学一门编程语言以及掌握一些Win32程序的知识... 

课后FAQ 
Q:哪些人可以学习破解? 
A:任何会启动电脑并运行软件同时又想学习破解的人。我说的全是实话,如果你既不会启动电脑又不会运行软件,那么我教你一个更高深的吧--破解电脑,呵呵,很简单,到大街上随便抡个板砖什么的,回去慢慢破解吧 (记得关电源) 
Q:有没有什么办法可以使我快速入门并成为高手? 
A:有。但你得是个MM(P不PL无所谓),然后找个离你家最近的破解达人,什么也不用做,眨个眼放个电之类的会吧(现在连初中的小女生都会这个),然后就成了,呵呵,想破什么的话,让高手帮忙吧,到时说成是自己破的就成了 MM问为什么?因为那些高手大都奇丑无比,呵呵,有了头脑就没了长相,男的也是这样,而且越是高手,长的就是越丑。据说一次市里到CCG考察奶牛们的出乳情况,看到大哥Sun某的时候,说了句“这奶牛个儿这么小啊,中午大家吃涮锅” (众大哥:大家准备好家伙,我们一会儿要去械斗)。呵呵,玩笑开到这里,其实我说这么多,只是想告诉你,学习破解跟其它技术一样,请你不要试图投机取巧,要想学,就脚踏实地,多看教程多动手实践积累经验,不要经常POSE那种弱智问题“我不懂XX,请问我能学破解吗?”,答案是不能,你问的同时,不也正在学吗?想知道重要吗?那我告诉你好了,凡是看雪教程上要求掌握的,你全要掌握,这还不算,要想成为高手就必须精通,如果你不想一直只停留在入门阶段的话。不要想偷机取巧,谁一开始也不是什么都会的,但你只要花一些时间和一小部分精力,那么没有什么你学不会的,知识是要积累的,你知道自己不会却不去学,而在那儿问重不重要,人家会觉的你这个人并不想认真学破解,而是报有侥幸心理在浪费时间,请不要做浪费时间的人。不要刚开始学就想马上成为高手,没有高手,你没必要立下超越的目标,只把学知识放在首位就够了,欲速则不达,请不要做急于求成的人。 
Q:学破解对我来说有什么好处? 
A:这个问题应该你自己来回答,呵呵,你为什么要学?“我想免费使用共享软件”倒...那多少也算是个目的,但我希望你不要只报这种目的(目前国内共享软件业还有待发展)。我只是想说给那些只是因为一时冲动才学习破解的人,请将你们当初的冲动继续维持下去,你需要明白,学习破解的目的不只在于破解软件这个词,也许后来你会变为软件分析,随着学习时间的增加,对你的编程水平,相信会有相当大的提高。学习别人好的思想,并化为已用 就我个人来说,学习破解可以把我的汇编的基础给打好,呵呵,俺对操作系统这玩意儿感兴趣,到时候还想写出来个玩玩儿呢,所以汇编这关必须要过.... 
Q:我很笨,那些大虾的教程我大都看不明白,我能学会吗? 
A:永远不要说你笨,你只是学的比人家晚而已,太高深的看不懂,那你就捡能看懂的看,别人能入门,你也能,不得要领只是暂时,大虾与你,也许差的就是一两年时间的问题。 
—————————— 

第二章--汇编语言 (修订版) 
稍微有点儿计算机知识的朋友一定知道,计算机是只识别0和1的,最初那会儿,要写程序,就要用0和1来写,呵呵,Cool吧!所以曾经有过的对程序员的崇拜,可能就源自那个时候吧 后来,人们发现用0和1来写程序,太不爽了,不但写起来不上手,而且回过头来看的话,应该很难再看明白了,总之出于这些原因,就有了汇编语言。 
汇编语言用一些助记符来代替0和1的多种组合,也就是各个指令,这样的话,从一定程度上来说,方便了许多(一头老牛:方便太多了)(一只菜鸟:一点儿也不方便,完全看不懂)。但是,汇编也同样不方便,同样写起来不爽,而且后期维护同样不方便,再加上人们慢慢地需要写一些更大的程序,在这样的情况下,高级语言就被人发明了出来,就是我们今天用的Basic、pascal、C、C++等等等等,这些语言的出现,一下了使程序的开发难度大大减低了(一头老牛:减低太多了,我膝盖就能写程序了)(一只菜鸟:还不是一样难),以前用汇编要很长时间才能开发出来的程序,现在只需要很短的时间且很轻松的就可以搞定了,特别是最近几年,可视化编程的大肆普及,使程序员的神秘感一下子摔了下来,Coder这样的词现在都满天飞了。最惨的就是汇编,一夜之间变成了低级语言、下流的语言、吃完大蒜不刷牙的民工、开车加完油不给钱的地痞、在公共汽车上吐口水的冰岛人等等等等 
(汇编:呜呜呜…我不活了)。 
但是汇编还是有它先天的优势的,因为其与CPU内部的指令一一对应,所以在一些特殊的场合,必须由汇编来实现,比如访问硬件的端口、写病毒…. 
而且生成的可执行文件效率巨高,且生成的可执行文件贼小,写小程序是很爽的,呵呵,而且用汇编写注册机,是件很轻松的事,你不用再为怎样还原为你所熟悉的语言而为难。说了这么多,还是切入主题吧(昏倒观众若干): 
既然计算机只识别0和1,那么,所有存储在计算机上的文件,也都是以二进制的形式存放的,当然也包括可执行文件了。 
所以,你只要找一个十六进制编辑器比如Ultra Edit什么的,就可直接打开并查看可执行文件了,呵呵,如果你能看懂的话 你会发现,此时看到的,全是些十六进制数值(每4位二进制数可转换为一位十六进制数),这就是可执行文件的具体内容,当然,其中就包括可执行文件的代码了。(一头老牛:好亲切啊)(一只菜鸟:笨牛,你给我闭嘴,我眼都花了)。 
呵呵,此时,你是不是觉得看这些东西,有些那个? 
这些东西看起来就像有字天书,没人能靠这玩意儿来进行分析,于是乎。就有了相应的软件,可以将这些十六进制数值转换为相应的汇编代码,这样的话,我们就可以对别人的软件进行分析了。这就是所谓的逆向分析了。 
呵呵,聪明的你现在一定在想,如果找到软件计算注册码的部分,并对其进行分析,弄懂它的计算方法,那么你不就不用通过¥的方式来进行软件注册了吗?当然,你也可以将此计算过程还原为任意一个你所熟悉的编程语言,那么,编译后的这个程序,就叫做注册机,它的功能就是计算某一特定软件的注册码。(呵呵,是不是经常在软件中看到此类说明?"禁止制作和提供该软件的注册机及破解程序;禁止对本软件进行反向工程,如反汇编、反编译等") 
作者这样做,心情我们是可以理解的,毕竟人家花了那么多心思在自己的软件上,所以,我不希望你仅仅是因为交不起注册费的原因来学习破解。 
总的说来,上边儿的介绍有点儿太理想化了,上面提到的分析方法,就是所谓的静态分析,此类分析常用的工具有W32DASM、IDA和HIEW等。静态分析,顾名思义,就是只通过查看软件的反汇编代码来对软件进行分析。一般如果只是想暴破软件,只进行静态分析就够了。但要想真正的弄清注册算法,一般还是要进行动态分析的,即能过调试器来一边执行程序一边进行分析。具体内容,我会在《破解原理》和《调试器入门》中详细说明,呵呵,毕竟现在都以经有点儿跑题了。 
我废话说了这么多,其实就是想告诉你汇编的重要性,我不要求你精通,但最少你也得能看懂吧,要不,还谈什么分析?虽然有哥们儿一点儿汇编都不懂就上路了,甚至还破掉了几个软件,但是,这样是不是惨了点儿?难不成你想暴破软件暴破一辈子? 
其实你完全不用惧怕汇编的,看上去怪吓人的,其实跟你平时背那些控件的属性方法差不多,MFC那么多你都搞的定,汇编命令才有多少?而且,汇编不光只是在Crack软件时有用,在好多地方也都有用,且用处巨大,所以我觉得,把汇编拿下,是件义不容辞的事: 
你只要相信它并不难就好了。 
(以下为第二次修改时加入) 
先给你讲一下CPU的组成吧: 
CPU的任务就是执行存放在存储器里的指令序列。为此,除要完成算术逻辑操作外,还需要担负CPU和存储器以及I/O之间的数据传送任务。早期的CPU芯片只包括运算器和控制器两大部分。到了近几年,为了使存储器速度能更好地与运算器的速度相匹配,又在芯片中引入了高速缓冲存储器(知道为什么P4比P4赛扬贵那么多吗?)。(当!一个硬物飞了过来,话外音:你讲这些做什么,我们又不要设计CPU) 
你急什么嘛,由于汇编比较“低级” ;;所以它是直接操作硬件的,你以为这是用VB呢,想什么时候用变量随手就可以拿来用,你不掌握好CPU内部的一些工作分配情况,到时怎么来看汇编代码啊。(当!又一声,重要还不快点儿说) 
除了高速缓冲存储器之外的组成,大体上可以分为3个部分: 
1.算术逻辑部件ALU(arithmetic logic unit)用来进行算术和逻辑运算。这部分与我们的关系不太大,我们没必要管它。 
2.控制逻辑。同样与我们的关系不大。 
3.这个才是最最重要的。工作寄存器,它在计算机中起着重要的作用,每一个寄存器相当于运算器中的一个存储单元,但它的存取速度却贼快贼快,比存储器要快很多了。它用来存放计算过程中所需要的或所得到的各种信息,包括操作数地址、操作数及运算的中间结果等。下面我们专门的介绍这些寄存器。 
在介绍之前,有必要说点儿基础性的知识。知道什么是32位吧,就是说寄存器是32位的,晕~~等于没说。在CPU中,一个二进制位被看作是一位,八位就是一个字节,在内存中,就是以字节为单位来在存储信息的,每一个字节单元给以一唯一的存储器地址,称为物理地址,到时候访问相应的内存,就是通过这个地址。八个二进制位都能表达些什么呢?可以表达所有的ASCII码,也就是说一个内存单元可以存储一个英文字符或数字什么的,而中文要用Unicode码来表示,也就是说两个内存单元,才能装一个汉字。十六位就是两个字节这不难理解吧,当然啦,那有了十六位,就肯定有三十二位六十四位什么的,三十二位叫做双字,六十四位就叫做四字。今天我们所使的CPU,相信全是32位的了,除非你用的是286或更早的话。自然而然,CPU中的寄存器,也就是32位的了,也就是说一个寄存器,可以装下32个0或1(这其中不包括段寄存器)。 
大体上来说,你需要掌握的寄存器,有十六个,我一个一个给介绍给你: 
首先,介绍小翠儿(当!,我自己打我自己一下得了,最近看周星驰看多了),重说,首先,介绍通用寄存器。 
一共八个,分别是EAX、EBX、ECX、EDX、ESP、EBP、EDI、ESI。 
其中,EAX—EDX这四个寄存器又可称为数据寄存器,你除了直接访问外,还可分别对其高十六位和低十六位(还计的我说它们是32位的吗?)进行访问。它们的低十六位就是把它们前边儿的E去掉,即EAX的低十六位就是AX。而且它们的低十六位又可以分别进行八位访问,也就是说,AX还可以再进行分解,即AX还可分为AH(高八位)AL(低八位)。其它三个寄存器请自行推断。这样的话,你就可以应付各种情况,如果你想操作的是一个八位数据,那么可以用 MOV AL (八位数据)或MOV AH (八位数据),如果你要操作的是一个十六位数据,可以用MOV AX (十六位数据)三十二位的话,就用MOV EAX (三十二位数据)也许我这样说,你还是会不明白,没关系,慢慢来,我给你大概画张图吧,虽然不怎么漂亮: 
─────────────────────── 
│ │ │ │ 
│ │ │ │ 
│ 高十六位 EAX AH AX AL │ 
│ │ │ │ 
│ │ │ │ 
─────────────────────── 
(我倒啊...这个图为啥老是不能正常显示?我都重画三遍了) 
明白了吗?不明白没有关系,你就按你自己的理解能力,能理解多少,就理解多少。 
这四个寄存器,主要就是用来暂时存放计算过程中所用的操作数、结果或其它信息。 
而ESP、EBP、EDI、ESI这四个呢,就只能用字来访问,它们的主要用途就是在存储器寻址时,提供偏移地址。因此,它们可以称为指针或变址寄存器。话说回来,从386以后,所有的寄存器都可以用来存储内存地址。(这里给你讲一个小知识,你在破解的时候是不是看到过[EBX]这样的形式呢?这就是说此时EBX中装的是一个内存地址,而真正要访问的,就是那那个内存单元中所存储的值)。 
在这几个寄存器中,ESP称为堆栈指针寄存。堆栈是一个很重要的概念,它是以“后进先出”方式工作的一个存储区,它必须存在于堆栈段中,因而其段地址存放于SS寄存器中。它只有一个出入口,所以只有一个堆栈指针寄存器。ESP的内容在任何时候都指向当前的栈顶。我这样说你可能会觉的还是不明白,那我举个例子吧,知道民工盖房吧,假设有两个民工,一个民工(以下简称民工A)要向地上铺砖,另一个民工(以下简称民工B)给民工A递砖,民工A趴在地上,手边是民工B从远处搬来的板砖,他拿起来就用,民工B从远处搬来后,就还放在那一堆砖上,这样,民工A拿着用后,民工B随既就又补了上去,这就是后进先出。你在脑子里想象一下这个这程。有没有想明白,民工A永远是从最上边开始拿砖。堆栈就是这样,它的基址开始于一个高地址,然后每当有数据入栈,它就向低地址的方向进行存储。相应的入栈指令是PUSH。每当有数据入栈,ESP就跟着改变,总之,它永远指向最后一个压入栈的数据。之后,如果要用压入堆栈的数据,就用出栈指令将其取出。相应的指令是POP,POP指令执行后,ESP会加上相应的数据位数。 
特别是现在到了Win32系统下面,堆栈的作用更是不可忽视,API所用的数据,均是靠堆栈来传送的,即先将要传送的数据压入堆栈,然后CALL至API函数,API函数会在函数体内用出栈指令将相应的数据出栈。然后进行操作。以后你就会知道这点的重要性了。许多明码比较的软件,一般都是在关键CALL前,将真假两个注册码压入栈。然后在CALL内出栈后进行比较。所以,只要找到个关键CALL,就能在压栈指令处,下d命令来查看真正的注册码。具体内容会在后面详细介绍,本章暂不予讨论。 
另外还有EBP,它称为基址指针寄存器,它们都可以与堆栈段寄存器SS联用来确定堆栈中的某一存储单元的地址,ESP用来指示段顶的偏移地址,而EBP可作为堆栈区中的一个基地址以便访问堆栈中的信息。ESI(源变址寄存器)和EDI(目的变址寄存器)一般与数据段寄存器DS联用,用来确定数据段中某一存储单元的地址。这两个变址寄存器有自动增量和自动减量的功能,可以很方便地用于变址。在串处理指令中,ESI和EDI作为隐含的源变址和目的变址寄存器时,ESI和DS联用,EDI和附加段ES联用,分别达到在数据段和附加段中寻址的目的。目前暂时不明白不要紧。 
接下来,再介绍如花(当当当,我再打自己三下算了)接下来,介绍一下专用寄存器,呵呵,有没有被这个名字吓倒?看起来怪专业的。 
所谓的专用寄存器,有两个,一个是EIP,一个是FLAGS。 
我们先来说这个EIP,可以说,EIP算是所有寄存器中最重要的一个了。它的意思就是指令指针寄存器,它用来存放代码段中的偏移地址。在程序运行的过程中,它始终指向下一条指令的首地址。它与段寄存器CS联用确定下一条指令的物理地址。当这一地址送到存储器后,控制器可以取得下一条要执行的指令,而控制器一旦取得这条指令就马上修改EIP的内容,使它始终指向下一条指令的首地址。可见,计算机就是用EIP寄存器来控制指令序列的执行流程的。 
那些跳转指令,就是通过修改EIP的值来达到相应的目的的。 
再接着我们说一下这个FLAGS,标志寄存器,又称PSW(program status word),即程序状态寄存器。这一个是存放条件标志码、控制标志和系统标志的寄存器。 
其实我们根本不需要太多的去了解它,你目前只需知道它的工作原理就成了,我举个例子吧: 
Cmp EAX,EBX ;用EAX与EBX相减 
JNZ 00470395 ;不相等的话,就跳到这里; 
这两条指令很简单,就是用EAX寄存器装的数减去EBX寄存器中装的数。来比较这两个数是不是相等,当Cmp指令执行过后,就会在FLAGS的ZF(zero flag)零标志位上置相应值,如果结果为0,也就是他们两个相等的话,ZF置1,否则置0。其它还有OF(溢出标志)SF(符号标志)CF(进位标志)AF(辅助进位标志)PF(奇偶标志)等。 
这些你目前没必要了解那么清楚,会用相应的转移指令就行了。 
最后要介绍的就是段寄存器了(刚才是谁说的樱红?反正不是我) 
这部分寄存器一共六个,分别是CS代码段,DS数据段,ES附加段,SS堆栈段,FS以及GS这两个还是附加段。 
其实现在到了Win32环境下,段寄存器以经不如DOS时代那样重要了。 
所以,我们知道就行了。 
啰嗦了这么多,相信你对CPU以经有了个大概的了解了吧。什么?还是什么也不明白?呵呵,那也不要灰心,请相信这是我的错,是我没有讲清楚而已,你可以去参考一些书籍。我始终觉的,你案头有一本讲汇编的书是非常非常有必要的,我这边儿是清华版的《80x86汇编语言程序设计》沈美明主编,46元。 
我们接下来就再讲一讲一些常用的汇编指令吧。(由于考虑到目前以经有了相应的帖子,所以,我只是从汇编指令中,挑出一些最常用,需要掌握的,更多内容,还请参见书本。) 
CMP A,B 比较A与B其中A与B可以是寄存器或内存地址,也可同时是两个寄存器,但不能同都是内存地址。这个指令太长见了,许多明码比较的软件,就用这个指令。 
MOV A,B 把B的值送给A其中,A与B可是寄存器或内存地址,也可同时是两个寄存器,但不能同都是内存地址。 
Xor a,a异或操作,主要是用来将a清空 
LEA装入地址,例如LEA DX,string 将字符的地址装入DX寄存器 
PUSH 压栈 
POP 出栈 
ADD 加法指令 格式:ADD DST,SRC 执行的操作:(DST)<-(SRC)+(DST) 
SUB 减法指令 格式UB DST,SRC 执行的操作:(DST)<-(DST)-(SRC) 
MUL 无符号乘法指令 格式: MUL SRC 执行的操作:字节操作(AX)<-(AL)*(SRC);字操作(DX,AX)<-(AX)*(SRC);双字操作:(EDX,EAX)<-(EAX)*(SRC) 
DIV 无符号除法指令 格式IV SRC 执行的操作:字节操作:16们被除数在AX中,8位除数为源操作数,结果的8位商在AL中,8位余数在AH中。表示为: 
(AL)<-(AX)/(SRC)的商,(AH)<-(AX)/(SRC)的余数。字操作:32位被除数在DX,AX中。其中DX为高位字,16位除数为源操作数,结果的16位商在AX中,16位余数在DX中。表示为:(AX)<-(DX,AX)/(SRC)的商,(DX)<-(DX,AX)/(SRC)的余数。 
双字操作:64位的被除数在EDX,EAX中。其中EDX为高位双字;32位除数为源操作数,结果的32位商在EAX中,32位余数在EDX中。表示为: 
(EAX)<-(EDX,EAX)/(SRC)的商,(EDX)<-(EDX,EAX)/(SRC)的余数。 
NOP 无作用,可以用来抹去相应的语句,这样的话,嘿嘿嘿… 
CALL调用子程序,你可以把它当作高级语言中的过程来理解。 
控制转移指令: 
JE 或JZ 若相等则跳 
JNE或JNZ 若不相等则跳 
JMP 无条件跳 
JB 若小于则跳 
JA 若大于则跳 
JG 若大于则跳 
JGE 若大于等于则跳 
JL 若小于则跳 
JLE 若小于等于则跳 
总的来说,以上几个,都是比较常见的,需要掌握,但需要掌握的绝不止这几个,其它的指令希望你能在私下里再了解一下,可以找相应的教程来看。 
刚才忘了,现在再把数制转换也给贴上: 
首先说二进制转换为十进制的问题: 
各位二进制数码乘以与其对应的权之和即为该二进制相对应的十进制数。例如: 
10100=2的4次方+2的2次方,也就是十进制数20。 
11000=2的4次方+2的3次方,也就是十进制数24。 
接着说一下十进制数转换为二进制数的方法: 
这样的方法到底有多少,我也不清楚,我只讲最简单的一个-除法: 
把要转换的十进制数的整数部分不断除以2,并记下余数,直到商为0为止。 
例:N=34D(说明一下,你可能在某些数字的后边看到过加有一个字母,这个字母便是用来表示数制的,十进制数用D,二进制数用B,八进制数用O,十六进制数用H) 
34/2=17 (a0=0) 
17/2=8 (a1=1) 
8/2=4 (a2=0) 
4/2 例:N=34D(说明一下,你可能在某些数字的后边看到过加有一个字母,这个字母便是用来表示数制的,十进制数用D,二进制数用B,八进制数用O,十六进制数用H) 
34/2=17 (a0=0) 
17/2=8 (a1=1) 
8/2=4 (a2=0) 
4/2=2 (a3=0) 
2/2=1 (a4=0) 
1/2=0 (a5=1) 
所以N=34D=100010B。 
对于被转换的十进制数的小数部分则应不断乘以2,并记下其整数部分,直到结果的小数部分为0为止。 
十六进制数与二进制数、十进制数之间的转换: 
总的来说,十六进制数与二进数之间的转换,应该算是很简单的了,你只需把与之相对应的数值进行转换就成了。 
十六进制数的基数是16,共有16个数码,它们是0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F。其中A表示十进制中的10,其余类推。它们与二进制和十进制数的关系如下: 
0H=0D=0000B,1H=1D=0001B,2H=2D=0010B,3H=3D=0011B,4H=4D=0100B,5H=5D=0101B,6H=6D=0110B,7H=7D=0111B,8H=8D=1000B,9H=9D=1001B,AH=10D=1010B,BH=11D=1011B,CH=12D=1100B,DH=13D=1101B,EH=14D=1110B,FH=15D=1111B 
所以,二进制与十六进制之间要进行转换的话,只要把它们由低到高每四位组成一级,直接用十六进制来表示就可以了: 
例: 1000 1010 0011 0101 
8 A 3 5 
十六进制转二进制则用只需将每一位用四位二进制数来表示就成了: 
例: A B 1 0 
1010 1011 0001 0000 
最后是十六进制数与十进制数之间的互相转换 
十六进制数转十进制数 
各位十六进制数与其对应权值的乘积之和即为与此十六进制数相对应的十进制数。 
例:N=BF3CH 
=11*16的3次方+15*16的2次方+3*16的1次方+12*16的0次方 
=11*4096+15*256+3*16+12*1 
=48956D 
十进制转十六进制 
我还是只讲最简单的除法: 
把要转换的十进制数的整数值部分不断除以16,并记下余数,直到商为0为止。 
例N=48956D 
48956/16=3059 (a0=12) 
3059/16=191 (a1=3) 
191/16=11 (a2=15) 
11/16=0 (a3=11) 
所以N=48956D=BF3CH。 
通过以上的介绍,我不知道你到底看懂没有,如果有的话,请你去看一下书本,把我没讲到的地方和讲过了的地方都仔细地看几遍。如果你根本就没有看懂,那么你就更需要去看书了,不要因为这就丧失掉学习的信心。你认真地把前边儿的CPU介绍看完,弄清楚寄存器的概念,再把后边汇编指令拿下,就可以上路了。你认真学,认真背的话,会发现其实并没你想像中的那么难。一星期的时间,就可大概掌握了,但只是掌握而已,最起码可以看懂汇编代码了。要真想学好的话,就连后边儿的也一同看了吧,再写一些小程序来练练手。当然想精通汇编,那可不是一天两天一月两月的事,但你只要有恒心,有什么搞不定的?CPU也是人做的,指令只是其中的一部分而已,人家能做出CPU,你还怕连使用都学不会? 

课后FAQ 
Q:我以前学过8086/8088,并且也在DOS下写过程序,能行吗? 
A:绝对能行,相对8086/8088,现在的CPU在基本指令方面,也没有添加多少新的指令。你只需了解一下各寄存器的变化以及补充一下Windows程序的知识就成了。而且,既然你用汇编在DOS下写过程序,那么对Debug等调试器,肯定已经很上手了,所以你有先天的优势。 
Q:汇编对我来说不成问题,可我为什么总是不上手呢? 
A:呵呵,这样的老鸟倒还有不少,他们把汇编用的相当熟练,但是,只是因为经验的原因,所以才觉的不上手的,许多人当初不也都这样吗?最起码我就是,见了CALL就跟进,呵呵,倒跟了不少API,所以对于这部分高手,你只需多练练手以及掌握一些分析的技巧就成了。 
Q:我没学过编程,能学汇编吗? 
A:总的来说,也行。不过希望汇编的学习,不会使你丢掉学习其它高级语言的信心。 

答网友问 
Q:寄存器可以随便用么,有没有什么限制?写个程序的时候那些变量什么的可以放在任意的寄存器么? 
A:呵呵,我现在就来回答楼上朋友的问题。 
寄存器有它的使用机制,及各个寄存器都有着明确的分工。 
如小翠儿 如数据寄存器(EAX-EDX),它们都是通用寄存器,及在软件中,任何数据都可存放于此。但是除此之外,它们又可以都可以用于各自的专用目的。 
例如: 
EAX可以作为累加器来使用,所以它是算术运算的主要寄存器。在乘除法等指令中指定用来存放操作数。比如在乘法中,你可以用AL或AX或EAX来装被乘数,而AX或DX:AX或EAX或EDX:EAX则用来装最后的积。 
EBX一般在计算存储器地址时,它经常用作基址寄存器。 
ECX则常用来保存计数值,如在移位指令它用来装位移量、循环和串处理指令中作隐含的计数器。 
最后就剩下四大天王中的黎明了,近一段时间来,他总是比较低调...(你别打我了,我去撞墙好了)最后就剩下EDX了,一般在作双字长运算时把DX和AX组在一起存放一个双字长数(你还记的什么是双字长吧,举个例子,比如说有一个数二进制数据01101000110101000100100111010001,你要把它寄存起来,就可以把0110100011010100(即高十六位)放在DX中,把0100100111010001(即低十六位)放在AX中,这个数表示为DX:AX)当然完全可以用一个EDX就把这个数给装下。所以,还可以用EDX:EAX来装一个64位数据,这个你会推断出来吧。 
而ESP、EBP、EDI、ESI,我上边儿以经大概介绍的差不多了,所以这里不说它们了。 
当然还有其它的一些限制,因为我们只是要看程序的汇编代码(人家写好了的,肯定不会犯错误吧),而不是要去写,所以可以不必掌握。有性趣的话,去看相关书籍。 
另外再说一下你的最后一个问题“写个程序的时候那些变量什么的可以放在任意的寄存器么? ”这句话我不明白你要问的是什么。我想你可能是把一些关点给搞错了,变量这词通常都是出现在高级语言中的,而你用高级语言写程序的话,完全不用理解那些寄存器什么的,这些都跟高级语言没什么关系。但是最终,高级语言也还是把你写的程序转换为对寄存器、内部存储器的操作。 
—————————— 

第三章—Windows程序 
这一章我都不知道该如何写了,呵呵~~ 
毕竟,Win32是一个非常深奥的系统,目前还容不得我这种小辈在这儿说三道四,不过,我既然是要写给那些入门阶段的朋友们看的,又不是写给那些搞程序设计老鸟看的,所以,我也犯不着怕被人背后指着骂 本章的名字就叫《Windows程序》而不是《Windows程序设计》所以,我只是讲一些关于Windows程序运作的原理: 
Windows为什么叫Windows,相信所有用过的朋友都可以明白,那桌面上一个一个的窗口,就是它名字的由来。也就是这一个又一个窗口的出现,使计算机的使用一下子简单了巨多。几年前接触过电脑的朋友一定知道DOS吧,不知道的话,去问加解密工具下载版的版主老哥,让他跟你解释 你还记的DOS下那黑乎乎的窗口吧,没见过的哥们儿可以在开始菜单中找出来看看。DOS通过一系列的命令来进行相应的操作,如进入一个目录,删除一个目录等等等等。那种工作方式就叫做命令提示符方式,也即命令行。 
现在国内不懂电脑的人还老爱说要想学电脑,必须要英语过关。(就是这个,吓跑了多少仅仅是想学习一些基本操作的朋友)可能也就是源自DOS的原因吧。 
后来,随着硬件的支持以及技术上的提高,当然还有为了使电脑更方便的服务与人,慢慢的就有了所谓的视图操作系统,从此,你不用再记忆那些大堆的指令了,而且操作上,也有了相大的提高,可以说操作系统发展到今天的份儿上,操作已经够简单了,去看看那些在网吧里一把鼻涕的小孩子们吧… 
当然,就像当年DOS之于命令提示行一样,今天的Windows仍和当年一样,占据着大部分的用户群。 
(场外:一观众扔来一烂柿饼,你是唐僧啊,这么多废话) 
马上转入正题,Windows之所以好用,除了不用背N多的命令外,一个原因就是因为它本身提供了大量的标准Windows GUI函数。所以对于用户,面对的是同一套标准的窗口,对这些窗口的操作都是一样的,所以使用不同的应用程序时无须重新学习操作。不用像当年在DOS下面那样一安装新程序,就要马上看帮助,看说明。 
而Windows GUI函数,只不过是微软提供给程序开发人员的API(Application Programming Interface 应用编程接口)中的一小部分而以。Windows API是一大组功能强大的函数,它们本身驻扎在 Windows *人们随时调用。这些函数的大部分被包含在几个动态链接库(DLL)中,譬如:kernel32.dll、 user32.dll 和 gdi32.dll。 Kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作等等。 
你可能多多少少听说过API函数,如果你不太清楚到底是怎么一回事的话,我尽量给你解释的清楚一点。 
不知道你有没有想过,Windows中的那一个又一个窗口是怎么画出来的呢?呵呵,你可能用VB、Delphi编过程序,你有没有想过你写的程序中的那些窗口是怎么形成的?是控件变成的。倒...呵呵,相信你当初学VB或Delphi的时候,所看的书上一定对可视化编程环境大肆赞扬了一番吧,是不是也提到过比VC++怎么怎么方便?怎么怎么不用再为生成程序的界面而花费大量无用时间了等等。 
(台下上来一东北民工:小子,你找抽啊,还讲不讲了) 
马上开说,其实我只是想告诉你,所有你用的Windows下的程序,都是通过调用一个又一个的Windows API来执行相应任务的,没有API,你的程序什么也做不了。用VB、Delphi以及MFC的朋友也许会说我根本没有调用什么API啊!其实这些API都是由你所用的开发环境自动进行相应的转换的。比如说你用Delphi新建一程序,什么也不用动就直接按F9来运行它,是不是出现一个空白的窗体?这就是个标准的Windows程序,它有Windows程序所具有的一切特征,如最大化按钮、最小化按钮、关闭按钮…你可以通过鼠标来移动它。 
但是如果你想用VC++或MASM32来写这样一个程序,那么你有两种方法,在VC++中,你可以用MFC或直接调用API,而在MASM32中,你就只有直接调用API这一种方法。所谓直接调用API,就是指所有的操作都通过最原始的API来完成。通过直接调用API来生成这样一个程序,你必须要先注册窗口类(除非您使用 Windows 预定义的窗口类,如 MessageBox 或 dialog box);然后产生窗口;然后在桌面显示窗口(除非您不想立即显示它); 然后刷新窗口客户区; 
麻烦吧,如果你想真正的让这个程序能正常地运行下来,还要再加入以下步骤: 
1.你要得到您应用程序的句柄。2.窗体显示后就进入无限的获取窗口消息的循环。3. 如果有消息到达,由负责该窗口的窗口回调函数处理。4. 如果用户关闭窗口,进行退出处理。 
上面这此步骤,都需要调用相应的API来完成。比如说得到程序的句柄用GetModuleHandle注册窗口类用RegisterClass或RegisterClassEx;注册后,还要用CreateWindowEx函数来生成相应窗口,而后用ShowWindow来显示它,之后还会用UpdateWindow 来更新客户区等等等等。这些还都不算呢,如果你真通过直接调用API去写一个稍大一点儿的程序的话,你会发现那是一个多么不令人愉快的事情。 
上面说的这些,只不过是API中的一小小小小小小小小小小….部分,这才几个,真正的API有成百上千个,包括对系统各个方面进行的操作。没有API,你的程序什么也干不了。比如说你的程序中有一个Edit控件,VB中应该叫做Text控件吧,你想将用户输入到里面的信息放到一个变量中去,那么Delphi中可以用Str:=Edit1.text来实现。VB中应该是Str=Text1.Text;但是如果你用API,想要得到Edit输入框里的文本内容,就要调用GetDlgItemInt(Edit中输入的值当做数值来用)GetDlgItemText、GetDlgItemTextA(Edit中输入的值当做字符串来用)。而上面我说的VB、Delphi得到编辑框中输入的内容的方法,最终在编译成可执行文件的时候,也会由编译器自动对其进行相应的转换。你只要明白一件事就好了,那就是你所用的程序,无时无刻都在调用着系统中的各种各样的API函数。 
其实Windows中的API,就相当于当年DOS系统中的系统功能调用,及中断21。只不过在数量上和功能上,都是DOS系统功能调用所不及的。 
如果你还是看不明白,那我不怪你,可能是我讲的不清楚,所以,还是给你推荐老牛写的书吧。力推《Windows程序设计》,看过之后你会内力大增的,那时候你所知道的知识就不止是API而以了。 
其实话说回来,我这篇文章不是教你编程的,所以关于Windows程序的原理,没有必要说那么多,我之所以跟你讲API,是想让你知道Windows程序的运行机制。免的到时候用调试器下断点的时候问什么是API。(众人(十分愤怒地)冲上台来:“拉下去PK!把我们当什么了!”) 
(我再次来到台上,镜头切向脸的一侧,来个特写。只见上面有若干处大小不同的伤口)可能还有些重点的地方我没有提到,欢迎指正。如果你有什么不明白的地方,欢迎跟贴提问。只要别太那个,比如说“你能把所有的API给我列出来让我回去背背好吗?” 
附上几个常用的API函数吧。相信你此时因该以经对API有个大概的了解了。 
MessageBox 显示一信息对话框 
MessageBoxEx 显示一信息对话框 
MessageBoxIndirect 显示一定制信息对话框 
(以上这三个,可以用来中断那些错误提示,比如说你注册码输入错误了,程序就可能通过这几个函数中的一个,来提示你错误) 
GetDlgItemInt 得指定输入框整数值 
GetDlgItemText 得指定输入框输入字符串 
GetDlgItemTextA 得指定输入框输入字符串 
(软件可以用这三个来得到用户输入的注册码) 
GetLocalTime 得当前本地时间 
GetSystemTime 得当前系统时间 
(软件可以用这两个来判断软件是否过期) 
RegQueryvalueA 获取一个项的设置值 
RegQueryvalueExA 获取一个项的设置值 
RegSetvalueA 设置指定项或子项的值 
RegSetvalueExA 设置指定项的值 
(如果软件用注册表存储注册信息的话,那么这几个也许会有用) 
上面讲的,只是几个平时比较常见的,更多请参见看雪以前的教程或Windows开发人员手册。 
最后,我们还要隆重介绍一个重量级函数,你可能不知道API是什么,但你只要用过调试器,就一定知道它的名字。你可以不知道美国现任的总统是谁,但是你一定要知道这个函数。我虽然知道现任美国总统是鲍威尔 但我同时也知道这个函数是谁。 
它就是----吴孟达!(导演:NG)重新说。它就是hmemcpy。 
这个函数是干什么的? 
它是一个非常简单的函数。只完成一项非常非常基本的任务,就是把数据从一个地方复制到另一个地方。应用程序本身并不调用它,理由很简单,它很低级(汇编:谁敢说跟我一样?)。但是大部分API函数却非常频繁地调用它。所以,它也叫万能函数。平时你可能都不知道有这么个东西,但是断起程序来却非常管用。但目前到了2K跟Xp下,却没有这个函数了,与之相应的是一个叫memcpy的函数,虽然功能与其相同,但是基本上已经是个废人了 总知,你用memcpy根本就断不下什么来。所以,这么一个好使的函数只能在98下使用了。这就像美国的总统一样,再好使也只能使八年,不好使的就别说了。说不定明年就把他踢飞 
别的我也不多说什么了,这章你就知道API是什么就成了。 
如果你觉的有什么不妥的地方或有什么问题,并且想文明一点地表代出来的话,就请在回复。如果想野蛮一点的话,就拿鸡蛋往你显示器上丢吧 
—————————— 
第四章--调试器及相关工具入门 
在写这章之前,我看了一下看雪以往的教程。本来想参考一下,可忽然发现,写这样的一章,是一件非常愚蠢的事情,因为我觉的关于这些工具的使用教程。看雪教程中已经写的够详细的了,我并不认为你会看不懂。所以我不想做浪费时间的人,本章就此搁浅。 
推荐看《Crack Tutorial 2001》,推荐看《看雪论坛精华一、二、三、四》,推荐看《加密与解密--软件保护技术及完全解决方案》,推荐看一切与之有关的教程。 
本章补遗: 
要想上路,你最少应该熟练掌握以下工具: 
SoftICE:目前公认最好的跟踪调试工具。(由于我使用的分辩率的关系,从没有用过它) 
Trw2000: 国人骄傲,其中有我最喜欢的pmodule命令。(河南老乡,殷墟旧人) 
W32Dasm8.93或其它任意版本:反汇编的*工具。 
Hiew 或者Ultra Edit或者其它:十六进制工具。爆破时使用,DOS下使用Hiew,Windows下使用Ultra Edit、WinHex、Hex Workshop等,我个人喜欢用Ultra Edit。 
侦测文件类型工具:比如TYP、gtw或FileInfo等。这是一个能侦测你的软件是被哪一种「壳」给加密了。 
PROCDUMP与其它N多的脱壳软件。 
EXESCOPE:拥有执行文件(EXE, DLL等)的解析与显示功能;提取资源到外部文件 ;资源的重新写入;记录文件的记录及其再编辑(成批编辑)等功能。是汉化软件的常用工具,当然破解软件时也很有用。 
其它许多......(等你入了门后再学也不迟) 
(作者注:以上工具的使用方法,大都可在看雪以有的教程中找到,故不愿复之)第五章--破解原理 
从本章开始,我们来一步一步学习Crack软件(80%读者昏死过去,且不省人世...另有20%在寻找附近可以用来打人的东西) 
不可不说一下学习破解的三个阶段: 
初级,修改程序,用ultraedit等工具修改exe文件,称暴力破解,简称爆破 
中级,追出软件的注册码 
高级,写出注册机 
先说这爆破。所谓爆破,就是指通过修改可执行文件的源文件,来达到相应的目的。你不明白?呵呵,举个例子好了,比如说某共享软件,它比较用户输入的注册码,如果用户输入的,跟它通过用户名(或其它)算出来的注册码相等的话(也就是说用户输入的注册码正确了),那么它就会跳到注册成功的地方去,否则就跳到出错的地方去。 
明白过来了吧,我们只要找到这个跳转指令,把它修改为我们需要的“造型”,这样,我们是不是就可以为所欲为了?(某软件双手放在胸口,你要干嘛?) 
常见的修改方法有两种,我给你举例说明: 
no.1 
在某软件中,这样来进行注册: 
00451239 CALL 00405E02 (关键CALL,用来判断用户输入的注册码是否正确) 
0045123D JZ 004572E6 (!!!<--此为关键跳转,如果用户输入的注册码正确,就跳向成功处,即004572E6处) 
0045XXXX YYYYYYYYYY 
XXXXXXXX YYYYYYYYYY 
XXXXXXXX YYYYYYYYYY 
XXXXXXXX 执行到此处,就提示用户注册失败 
...提示用户注册码不正确等相关信息 
... 
004572E6 ... <--(注册成功处!!!) 
...提示用户注册成功等相关信息 
呵呵,看明白了吗?没有的话,我来给你讲一下。在软件执行到00451239处的时候,CALL置0045E02处来进行注册码判断。接着回来后就来一个跳转语句,即如果用户输入的注册码正确就跳到004572E6处,跳到此处,就算是注册成功了。如果用户输入的注册码不正确的话,那么就不会在0045123D处进行跳转,而一直执行下去。在下面等它的,是注册失败部分。 
想明白了吗?嘿嘿...没错,我们只要把那个关键跳转JZ给改为JNZ(如果用户输入的注册码错误,就注册成功,输入正确则注册失败)。当然你也可以将JNZ修改为Jmp,这样的话,你输入的注册码无论正确与否。都可以注册成功。 
no.2 
我们再来讲一下另外的一种情况: 
00451239 CALL 00405E02 (关键CALL,用来判断用户输入的注册码是否正确) 
0045123D JNZ 004572E6 (!!!<--此为关键跳转,如果用户输入的注册码不正确,就跳向失败处,即004572E6处) 
0045XXXX YYYYYYYYYY 
XXXXXXXX YYYYYYYYYY 
XXXXXXXX YYYYYYYYYY 
XXXXXXXX 执行到此处,就提示用户注册成功 
...提示用户注册成功等相关信息 
... 
004572E6 ... <--(注册失败处!!!) 
...提示用户注册码不正确等相关信息 
这次我相信,并且深信不疑。你一定明白了。我还是不明白...倒... 
你一定看出跟第一种情况不同的地方了吧。没错!它与第一种不同的,就是第一种情况是如果注册码正确,就跳到注册成功处,如果没有跳走,就会执行到失败处。而这一种情况则是如果注册码不正确,就跳到注册失败处,否则将执行到注册成功处。 
这种情况的修改,除了把JNZ改为JZ外,还可以将其改为Nop,Nop这个指令没有任何意义,将该条指令修改为Nop后,便可随意输入注册码来进行注册了。 
原理以经给你讲了,下面我们再来讲一下具体的修改办法吧。(我假设你以经明白了我所说的工具的使用方法) 
先说一下虚拟地址和偏移量转换的问题,在SoftICE和W32Dasm下显示的地址值是所谓的内存地址(memory offset),或称之为虚拟地址(Virual Address,VA)。而十六进制工具里,如:Hiew、Hex Workshop等显示的地址就是文件地址,称之为偏移量(File offset) 或物理地址(RAW offset)。 
所以当我们要通过那些十六进制工具来对可执行文件中的相应指令进行修改的话,先要找到它的File offset。我们没有必要去使用那些专门的转换工具,在W32Dasm中就有这个功能,比如说你W32Dasm中来到0045123D处,在W32Dasm界面下方的状态栏中? 
004572E6 ... <--(注册失败处!!!) 
...提示用户注册码不正确等相关信息 
这次我相信,并且深信不疑。你一定明白了。我还是不明白...倒... 
你一定看出跟第一种情况不同的地方了吧。没错!它与第一种不同的,就是第一种情况是如果注册码正确,就跳到注册成功处,如果没有跳走,就会执行到失败处。而这一种情况则是如果注册码不正确,就跳到注册失败处,否则将执行到注册成功处。 
这种情况的修改,除了把JNZ改为JZ外,还可以将其改为Nop,Nop这个指令没有任何意义,将该条指令修改为Nop后,便可随意输入注册码来进行注册了。 
原理以经给你讲了,下面我们再来讲一下具体的修改办法吧。(我假设你以经明白了我所说的工具的使用方法) 
先说一下虚拟地址和偏移量转换的问题,在SoftICE和W32Dasm下显示的地址值是所谓的内存地址(memory offset),或称之为虚拟地址(Virual Address,VA)。而十六进制工具里,如:Hiew、Hex Workshop等显示的地址就是文件地址,称之为偏移量(File offset) 或物理地址(RAW offset)。 
所以当我们要通过那些十六进制工具来对可执行文件中的相应指令进行修改的话,先要找到它的File offset。我们没有必要去使用那些专门的转换工具,在W32Dasm中就有这个功能,比如说你W32Dasm中来到0045123D处,在W32Dasm界面下方的状态栏中就会出现该条指令的虚拟地址和偏移地址,即@:0045123D @offset 0005063Dh 后面的这个0005063Dh就是相应的偏移地址。我们得到该地址后,便可用UltraEdit等十六进制工具来对可执行文件进行修改了。比如使用UltraEdit,你先用UltraEdit打开该可执行文件,然后按Ctrl+G,接着输入你得到的偏移地址,就可以来到其相应的机器码处。 
再给你讲一下机器码,所谓的机器码。就是你看到的那些个十六进制数据了。还记的它们与汇编指令是一一对应的吗? 
以下这几个是爆破时要用到的,其它的如果感兴趣,可自行查看相关资料: 
JZ=74;JNZ=75;JMP=EB;Nop=90 
爆破的时候,只要对以上机器码进行相应的修改就行了,比如第一种情况的时候,可以将74修改为EB,即将JZ修改为JMP。而第二种情况,责需将75修改为90,即将JNZ修改为Nop。 
由于本章只讲原理,具体一点的。如怎样找到关键跳转等,我们在下一章中再讲。(一个砖头飞了上来!嘿嘿,这次被俺接到了) 
上边讲了爆破的原理,你需要明白的是。爆破只是你学习Crack的开始,是很简单的手段。刚入门的时候可以玩玩儿,但希望你不要就此不前! 
(嘿嘿,再说了。人家的软件中不是都说了嘛,不准对其进行逆向修改。你动了人家的身子,怎么能不买帐呢? ) 
偶就不喜欢爆破,做不出注册机也要找出注册码。否则我就不会去注册这个软件,既然想不掏钱,就要靠你自己的本事。(等以后我有钱了,会考虑去注册那些优秀的共享软件的 )。所以,从某种意义上来说,我是一个正人君子 
其实要找到注册码并不是一件多么难的事,我是指你所针对的软件不太那个的时候 不过你无需惧怕。 
刚才我们说爆破的时候不提到过关键CALL吗?一般情况下,这个关键CALL就是对两个注册码(一个是软件自身通过你的注册名或机器什么的计算出来的正确的注册码,令一个就是你输入的错误的注册码)进行比较。我前边提到过,CALL之前一般会把所用到的数据先放到一个地方,CALL过去的时候再从这些地方把先前放入的数据取出来,进行相应的处理。这个关键CALL也是这样,在CALL之前,一般会把那两个注册码放到堆栈或某个寄存器中。嘿嘿,我们只要在调试器中,单步执行到该CALL,在未进去之前通过CALL之前的指令判断其将正确的和不正确的注册码放到哪里了。然后再用相应指令进行查看就成了,我说过不难的。 
下面列出两个最常见的情况(可参考相关教程): 
no.1 
mov eax [ ] 这里可以是地址,也可以是其它寄存器 
mov edx [ ] 同上,该条指令也可以是pop edx 
call 00?????? 关键call 
test eax eax 
jz(jnz)或jne(je) 关键跳转 
看明白了吧,在关键CALL之前,软件会把两个注册码分别放入eax和edx中,你只要在CALL处下d eax或d edx就能看到正确的注册码了。 
no.2 
mov eax [ ] 这里可以是地址,也可以是其它寄存器 
mov edx [ ] 同上,该条指令也可以是pop edx 
call 00?????? 关键call 
jne(je) 关键跳转 
以上两种情况最为常见,而那些个不太常见的情况,我们这里就不再提了。到下下一章的时候,我会给你讲相关方法的... 
关于查找软件注册码的部分,就到这里。具体内容,下下一章咱们再说。(不是说了吗?我以经可以接到你的砖头了,干嘛还要丢呢? ) 
最后,再来说最后的所谓的高级阶段,如果你相信自己。并且热爱Crack,那么你一定会熬到这个阶段的,只是时间因人而异。 
其实分析软件的算法,是有好多技巧在里面的。呵呵,最起码我刚开始的时候就摸不着头脑,那么多CALL,每个看起来,都很重要,都追一遍?结果连好多API都被追了进去。等你自己真正用心分析了一个软件的算法,并写出了注册机后。你就会明白其中的道理了,我们下下下一章再说。(大哥,你不是吧,连你家太阳能都丢过来了 ) 
—————————— 

第六章--爆破软件 
爆破其实很简单,最起码比你能一下把你家的牙膏给全挤出来要容易多了。你只要先到大街上买几根雷管,然后放到你的显示器上再点着就OK了(不难吧,记的点着后跑远点儿) 
爆破的原理我也说过了,相信你很容易就能理解了。我们今天就具体讲一下如何找到那个关键跳转以及如何才能买到即便宜又好用的雷管... 
爆破一个软件一般只需要很少的几个步骤,首先先看一下其有无加壳,有的话是用何工具加的壳,知道了以后用相应的工具将其脱掉或进行手工脱壳,参考以有教程。接着我们就可以对脱过壳之后的软件来开刀了。你有两种选择,用W32Dasm或调试器,一般如果你遇上的是那种很菜的软件的话,用W32Dasm就可以搞定了。如果遇上的不是那种比较菜的,就买股票吧,因为股票是你如胶似漆的妻子!当!快醒醒啊...哦,一般如果你遇上的不是那种很菜的软件的话,就用调试器吧。先来说W32Dasm:我们首先用W32Dasm来进行反汇编(废话!)之后在串式参考中找到错误提示信息或可能是正确的提示信息双击鼠标左键来到相应的地址处。在W32Dasm的主窗口中分析相应汇编代码,找出关键跳转和关键call。绿色光条停在关键跳转,在W32Dasm主窗口底部找到关键跳转的偏移地址(实际修改地址)。用ultraedit找到偏移地址(实际修改地址)修改机器码(或放上一根雷管),保存(点火)!而用调试器也同样简单,等会儿会详细说明。 
道理废话了那么多,来实例动手说明吧: 
首先讲解用W32Dasm来进行爆破: 
【软件名称】中华压缩(*p) 
【软件版本】7.0 
【文件大小】1041KB 
【适用平台】Win9x/Me/NT/2000 
【软件简介】*p(中华压缩)是一款压缩、解压各种压缩文档的工具软件,它支持包括ZIP格式文件在内的各种常见压缩格式如:ARJ、CAB、GZIP、JAR、LHA、TAR、ZOO、ARC、LZH、Pak等等。 
软件的出处是电脑报2001年的合订本配套光盘,7.0时的保护做的很那个,目前最新版应该好多了... 
好的,我们开始吧,首先第一步是你得把它装上(引来野狼N头),之后先随便找个字符串填上去注册一下,会看到一个错误对话框,提示"注册码不正确,无法注册"。接着我们用FI来看一下它用的是什么壳。ASPack 2.001,caspr出场。脱过壳后我们用W32Dasm花上半分钟或半小时的时间来对它进行反汇编。我们以经反汇编完毕。之后在串式参考中(字符串数据参考)中找刚才你看到的那个错误提示,找到之后双击几次,发现其只有一处调用。我们会来到004F0E64处,我把具体代码给贴上(请你从代码的最下边开始看): 
:004F4DD1 E84EE1F3FF call 00432F24 
:004F4DD6 8B55F0 mov edx, dword ptr [ebp-10] 
:004F4DD9 8D4DF4 lea ecx, dword ptr [ebp-0C] 
:004F4DDC 8BC3 mov eax, ebx 
:004F4DDE E8C9010000 call 004F4FAC 
:004F4DE3 8B55F4 mov edx, dword ptr [ebp-0C] 
:004F4DE6 58 pop eax 
:004F4DE7 E830F3F0FF call 0040411C 
:004F4DEC 7576 jne 004F4E64 <--这个就是传说中的男人,Stop!这个就是传说中的关键跳转 
:004F4DEE B201 mov dl, 01 
:004F4DF0 A158254500 mov eax, dword ptr [00452558] 

* Referenced by a (U)nconditional or (C)onditional Jump at Address: 
|:004F4D86(C) 

:004F4DF5 E85ED8F5FF call 00452658 
:004F4DFA 8945FC mov dword ptr [ebp-04], eax 
:004F4DFD 33C0 xor eax, eax 
:004F4DFF 55 push ebp 
:004F4E00 685D4E4F00 push 004F4E5D 
:004F4E05 64FF30 push dword ptr fseax] 
:004F4E08 648920 mov dword ptr fseax], esp 
:004F4E0B B101 mov cl, 01 

* Possible StringData Ref from Code Obj ->"Software/XDZHAN/*p" 

:004F4E0D BAA84E4F00 mov edx, 004F4EA8 
:004F4E12 8B45FC mov eax, dword ptr [ebp-04] 
:004F4E15 E822DAF5FF call 0045283C 

* Possible StringData Ref from Code Obj ->"Real Programmers Use Pascal!" 

:004F4E1A B9CC4E4F00 mov ecx, 004F4ECC 

* Possible StringData Ref from Code Obj ->"Key" 

:004F4E1F BAF44E4F00 mov edx, 004F4EF4 
:004F4E24 8B45FC mov eax, dword ptr [ebp-04] 
:004F4E27 E854DEF5FF call 00452C80 

* Possible StringData Ref from Code Obj ->"软件注册成功,谢谢您的支持!" <--我们向上看会在这里发现注册成功后的正确信息。正确信息处向上找第一个跳转就是我们要找的关键跳转。 

:004F4E2C B8004F4F00 mov eax, 004F4F00 
:004F4E31 E8563DF6FF call 00458B8C 
:004F4E36 A16C305000 mov eax, dword ptr [0050306C] 
:004F4E3B 8B00 mov eax, dword ptr [eax] 

* Possible StringData Ref from Code Obj ->"中华压缩(*p)-注册版" 

:004F4E3D BA244F4F00 mov edx, 004F4F24 
:004F4E42 E80DE1F3FF call 00432F54 
:004F4E47 33C0 xor eax, eax 
:004F4E49 5A pop edx 
:004F4E4A 59 pop ecx 
:004F4E4B 59 pop ecx 
:004F4E4C 648910 mov dword ptr fseax], edx 
:004F4E4F 686E4E4F00 push 004F4E6E 

* Referenced by a (U)nconditional or (C)onditional Jump at Address: 
|:004F4E62(U) 

:004F4E54 8B45FC mov eax, dword ptr [ebp-04] 
:004F4E57 E868E2F0FF call 004030C4 
:004F4E5C C3 ret 


:004F4E5D E9C2E9F0FF jmp 00403824 
:004F4E62 EBF0 jmp 004F4E54 

* Referenced by a (U)nconditional or (C)onditional Jump at Address: 
|:004F4DEC(C) 

* Possible StringData Ref from Code Obj ->"注册码不正确,无法注册!" <--这个就是出错的信息了,那正确信息也就在附近,上下看看。 

:004F4E64 B8484F4F00 mov eax, 004F4F48 <--双击来到这里 
:004F4E69 E81E3DF6FF call 00458B8C 
:004F4E6E 33C0 xor eax, eax 
:004F4E70 5A pop edx 
:004F4E71 59 pop ecx 
:004F4E72 59 pop ecx 
:004F4E73 648910 mov dword ptr fseax], edx 
:004F4E76 689B4E4F00 push 004F4E9B 
你可能有点不明白,为什么我说它就是关键跳转呢?还记的在破解原理中我举的例子吗? 
我再给你讲一遍好了,通常我们会遇到两种关键跳转,我分别举例说明: 
(1) 
je (jne,jz,jnz) 19870219 
........ XXXXXXXXXX 
........ XXXXXXXXXX 
........ 软件注册正确的相关信息 
... 
... 
19870219 软件的出错信息 
....... 
....... 
也就是说这第一种情况是先判断注册码是否正确,如果不正确就跳到19870219处,正确的话就不跳转,一直执行下去,直至注册正确处。 
对于这种情况,我们要找的关键跳转,就是正确信息上面的第一个跳转。我们可能对其作相应修改或将其给nop掉就万事OK了。 
(2) 
je (jne,jz,jnz) 19870219 
........ XXXXXXXXXX 
........ XXXXXXXXXX 
........ 软件的出错信息 
... 
... 
19870219 软件注册正确的相关信息 
....... 
....... 
而这第二种情况就是先判断注册码正确与否,如果正确就跳到19870219处,不正确的话就不跳转,一直执行下去,直至出错处。 
对于这种情况,我们要找的关键跳转就是出错信息上面的第一个跳转。将其做相应修改或改为jmp后我们就可以为所欲为了 
呵呵,道理也都给你讲明白了,我们来改一下试试吧。我们在W32Dasm中选中关键跳转,在右下角的状态栏中看到相应的偏移地址为000F41EC。好的,我们用UltraEdit来打开它。Ctrl+G,接着输入0xF41EC,回车后便会跳到相应的位置。相应的机器码是75(jne),我们将其改为74(jz)后存盘退出。 
好了,运行一下看看,我们来随便输入一个注册码注册一下试试。呵呵,注册成功! 
用W32Dasm我们就讲到这里,呵呵,很简单的,你下去之后自己找些保护简单的软件上上手吧。 
我们接着来讲用调试器来进行爆破。 
如果你真的试图用W32Dasm去爆破几个软件的话,用不了多少时间你就会发现一些问题。比如说有的软件你用W32Dasm反汇编后串式参考根本就不能用。或者串式参考中没有出错或正确的信息。还有就是有的软件就算你通过串式参考来到了相应的地方,刚想去找关键跳转你就会发现眼前的东西比你想像中的要乱的多...虽然你有可能通过认真仔细地找,仍会找到,但我不认为那是一件聪明的事情。毕竟,有一些动静是只有在程序执行期间才能看出来的。好的,如果你用W32Dasm遇到了找不到关键跳转的软件,就去用调试器吧!(你用调试器前可先用W32Dasm打开一遍看个先,如果很容易就让你找到了。那就没必要了) 
在开始之前我们有必要讲一下用调试器来爆破的步骤(我知道你一定会用调试器的 ):首先,我们当然还是要把你要Crack的软件给装上(我挡我挡我挡,不要乱丢东西嘛!)然后来到输入注册码的地方,仍旧随便输入一个,接着不要按确定,待我们把调试器叫出来先。还记的我前面跟你讲的API的事情吗?软件要得到你输入的注册码,就一定会调用某个API函数来达到目的。我们就在调试器中用相应的API来做断点,这样的话,只要一有程序调用这个API,就会被调试器给拦截下来。 
GetDlgItemInt、GetDlgItemText、GetDlgItemTextA这三个函数可能会有用。但是如果你用的是98,那为什么不用hmemcpy呢?那真的是一个不错的主意。当我们下完断点后就返回到你要注册的那个软件中,点确定这类的按钮。如果被调试器给断了下来,就说明你刚才下的断点有用,如果没有被断下来,就换个断点试试。接下来我们在调试器中来取消刚才你下的那个断点,我们以TRW2000为例(SoftICE与其操作大体相同)取消断点用bc *指令。然后我们就输入pmodule指令来返回到程序的领空(而在SoftICE中由于没有相应指令,呵呵,狂按F12吧)。现在我们把话题岔开一下,什么是领空呢?举个例子吧,你的程序要得到你输入的那个注册码,就会去调用相应的函数。比如调用GetDlgItemTextA,而GetDlgItemTextA本身又会去调用Hmemcpy这个函数,而这些函数都是存在于系统中的某个DLL文件中的。那么当这个程序调用相应的API函数的话,程序的领空就会转到这个相应的DLL文件中去执行这个API函数。(你就这样理解就行了)我前边也说过了,Hmemcpy这个函数应用程序本身并不直接调用,而是由其它的API函数来调用。那么,你就可以理解为你的程序调用了一个API函数,调用的同时程序的领空会转到这个API所在的DLL文件里,而这个API又调用了Hmemcpy函数,那么此时领空就会又转到了Hmemcpy所在的DLL文件中,之后当Hmemcpy执行完毕,就会返回到调用它的API的领空中去,而当这个API执行完毕的后就会返回到调用它的应用程序的领空中去。比如说我们用Hmemcpy这个函数来当断点,当我们输入完注册码按确定后,程序就会去调用某个API来得到你输入的那些数据,而这“某个API”又会去调用Hmemcpy,所以程序就被断到了。当然此时程序的领空也就不会在应用程序中了,但是当我们输入过pmodule指令之后我们就可以反回到应用程序本身的领空中去了。这样的话你看到的就是应用程序自身的代码了,而不是API的!好了,我接着刚才的说(到哪儿了来着?)当我们返回到程序自身的领空中去后就一直狂按F12吧,F12的作用是一直执行程序,直到遇上ret等指令。也就是一大坨一大坨地来执行程序^_^你一直按F12,直到程序出现注册错误对话框。然后记下刚才你按的次数,接着从头做起,这一次按F12的次数是你刚才按的次数-1,也就是说比上一次要少按一次。而后按键由F12换至F10(怎么没有F4?),还是一路狂按,直到软件提示出错,这次记下你按F10的次数。好的,再从头来一遍,我们再次按F10的时候,要一步一步慢慢来,一般你按F10的次数离你上次按的次数相差五六步的时候,一般就会看见一个CALL,接着是一个跳转指令。你一步一步地来,看过了这个跳转指令之后会不会跳走,如果跳走了,那一般你不会再过两三步就应该出错了。当然也有可能是你没有跳走,而过了两三步就出错了。这个应该不难理解,因为基本上它和我前边跟你介绍过的是一个道理。然而另外一种情况是你一路按F10下来,到了最后会发现根本没什么跳转指令,呵呵,别害怕,这个很常见的。遇上这种情况,我们只要把F10的次数变换为上次按F10的次数-1,这样的话你一般就会停在一个CALL处,而这个CALL,就是程序中的关键CALL,我们之后要吃点儿苦,要按F8追进去分析它,程序注册的成功与失败,就在这个CALL中,也就是说我们要修改的关键跳转,也在这个CALL中。呵呵,其实也很好理解的,就是把我上边说的那些个判断什么地放到了一个CALL里面。我们按F8追进去之后便仍旧按F10来一步一步执行,过不了多长时间你就会发现关键跳转了,找关键跳转的方法跟我前边说的一样,即按F10的次数跟上一次差五六步的时候慢下来,就会看到了。 
你应该明白,程序是很灵活的东西,并没有那么多公式化的东西在里边,大概的分析方法就是这个样子,一切都要靠你自己去掌握,别人跟你讲,也只是讲一个分析的方法而以,我相信随着你以后经验的提高,你慢慢地就能应付各种情况了。 
现在,我们再用调试器来对*P这个软件进行分析,希望你能够掌握这个并不难的方法。 
首先,你要把刚才爆破过了的再改回来,或直接重装一遍。之后我们打开它,任意输入注册码,接着按Ctrl+N呼出TRW,下断点hmemcpy。下过后按F5退出(它就是不用F4,我也没办法^_^)然后我们点击确定。好的,程序被断了下来: 
KERNEL?HMEMCPY 
0147:9e62 push bp 
0147:9e63 mov bp,sp 
0147:9e65 push ds 
0147:9e66 push edi 
0147:9e68 push esi 
0147:9e6a cld 
0147:9e6b mov ecx,[bp+06] 
0147:9e6f jcxz 9ee9 
……以下N多代码省略…… 
我们输入bc *来取消断点,然后用pmodule来返回到程序的领空: 
0167:00436d13 mov [ebx+0c],eax 
0167:00436d16 mov eax,[ebx] 
0167:00436d18 cmp eax,byte +0c 
0167:00436d1b jnz 00436d38 
0167:00436d1d mov edx,[ebx+08] 
0167:00436d20 push edx 
0167:00436d21 mov ecx,[ebx+04] 
0167:00436d24 mov edx,eax 
0167:00436d26 mov eax,esi 
0167:00436d28 call 00432b24 
……N多代码仍旧省略…… 
按7下F12另加1下F10来到0167:004f4dc4处,我们接着一下一下来按F10,大概按了10多下,就可以看到004f4dec处有一个跳转,我们执行到004f4dec处后果然跳走了。会跳到004f4e64处,我们跳过去之后按不了三下,程序就提示出错了。呵呵,明白过来了吧,004f4dec处的那个跳转jnz 004f4e64就是关键跳转,嘿嘿,找到了之后不用我说了吧 
0167:004f4dc4 mov eax,[ebp-08] 
0167:004f4dc7 push eax 
0167:004f4dc8 lea edx,[ebp-10] 
0167:004f4dcb mov eax,[ebx+02e0] 
0167:004f4dd1 call 00432f24 
0167:004f4dd6 mov edx,[ebp-10] 
0167:004f4dd9 lea ecx,[ebp-0c] 
0167:004f4ddc mov eax,ebx 
0167:004f4dde call 004f4fac 
0167:004f4de3 mov edx,[ebp-0c] 
0167:004f4de6 pop eax 
0167:004f4de7 call 0040411c 
0167:004f4dec jnz 004f4e64 <--关键跳转!! 
0167:004f4dee mov dl,01 
0167:004f4df0 mov eax,[00452558] 
0167:004f4df5 call 00452658 
0167:004f4dfa mov [ebp-04],eax 
0167:004f4dfd xor eax,eax 
0167:004f4dff push ebp 
0167:004f4e00 push dword 004f4e5d 
0167:004f4e05 push dword [fs:eax] 
0167:004f4e08 mov [fs:eax],esp 
0167:004f4e0b mov cl,01 
0167:004f4e0d mov edx,004f4ea8 

再来给你举另一个例子: 
【软件名称】天网防火墙 
【软件版本】2.46 Beta 
【文件大小】1289KB 
【适用平台】Win9x/Me/NT/2000 
【软件简介】天网防火墙个人版是一套给个人电脑使用的网络安全程序,它可以帮你抵挡网络入侵和攻击,防止信息泄露,并可与我们的网站相配合,根据可疑的攻击信息,来找到攻击者。同时天网防火墙个人版把网络分为本地网和互联网,可以针对来自不同网络的信息,来设置不同的安全方案,它适合于在拨号上网的用户,也适合通过网络共享软件上网的用户。 
该软件仍旧是我从电脑报2001年合订本的配套光盘中找的,软件的注册码可以到其网站免费获得... 
我们还是要先把它装上(某民工:你小子敢再说一句废话试试!^_^)之后我们用FI看一下它有没有加壳,呵呵,BC++编译,没有加壳,爽!运行它,在注册对话框中随便输入点什么,比如说这星期又出了几部新电影,都叫什么名字等等... 
好的,我们接下来请TRW2000出场。先胡乱输入两个字符串,比如第一个输入“英雄的导演是?”第二个输入“可能是赵本山” 
接下来就按Ctrl+N把TRW2K叫出来,下bpx hmemcpy,之后按F5退出。 
接着可以按确定就成了,程序会被TRW2K断掉,我们紧接着输入bc *以及pmodule。 
下面可以开始按F12了,一共按8下程序就会报错,我们第二次就按7下然后开始按F10,按70下F10程序就又报错了(呵呵,一定要有耐心哦)。 
好的,我把反汇编后的代码给你贴出来: 
0167:0041c617 lea edx,[ebp-04] <--7下F12后按一下F10来到这里 
0167:0041c61a mov ecx,[0052ae7c] 
0167:0041c620 mov eax,[ecx] 
0167:0041c622 mov eax,[eax+0318] 
0167:0041c628 add eax,byte +2c 
0167:0041c62b call 00517740 
0167:0041c630 dec dword [ebp-20] 
0167:0041c633 lea eax,[ebp-04] 
0167:0041c636 mov edx,02 
0167:0041c63b call 00517710 
0167:0041c640 mov word [ebp-2c],14 
0167:0041c646 lea eax,[ebp-08] 
0167:0041c649 call 00401d60 
0167:0041c64e mov edx,eax 
0167:0041c650 inc dword [ebp-20] 
0167:0041c653 mov ecx,[ebp-40] 
0167:0041c656 mov eax,[ecx+02e0] 
0167:0041c65c call 004b9f14 
0167:0041c661 lea edx,[ebp-08] 
0167:0041c664 mov ecx,[0052ae7c] 
0167:0041c66a mov eax,[ecx] 
0167:0041c66c mov eax,[eax+0318] 
0167:0041c672 add eax,byte +30 
0167:0041c675 call 00517740 
0167:0041c67a dec dword [ebp-20] 
0167:0041c67d lea eax,[ebp-08] 
0167:0041c680 mov edx,02 
0167:0041c685 call 00517710 
0167:0041c68a lea eax,[ebp-10] 
0167:0041c68d call 00401d60 
0167:0041c692 mov edx,eax 
0167:0041c694 inc dword [ebp-20] 
0167:0041c697 mov ecx,[ebp-40] 
0167:0041c69a mov eax,[ecx+02e0] 
0167:0041c6a0 call 004b9f14 
0167:0041c6a5 lea edx,[ebp-10] 
0167:0041c6a8 push dword [edx] 
0167:0041c6aa mov word [ebp-2c],20 
0167:0041c6b0 lea eax,[ebp-0c] 
0167:0041c6b3 call 00401d60 
0167:0041c6b8 mov edx,eax 
0167:0041c6ba inc dword [ebp-20] 
0167:0041c6bd mov ecx,[ebp-40] 
0167:0041c6c0 mov eax,[ecx+02d4] 
0167:0041c6c6 call 004b9f14 
0167:0041c6cb lea edx,[ebp-0c] 
0167:0041c6ce mov edx,[edx] 
0167:0041c6d0 mov eax,[0052ae7c] 
0167:0041c6d5 mov eax,[eax] 
0167:0041c6d7 pop ecx 
0167:0041c6d8 call 0040525c 
0167:0041c6dd mov [ebp-45],al 
0167:0041c6e0 dec dword [ebp-20] 
0167:0041c6e3 lea eax,[ebp-10] 
0167:0041c6e6 mov edx,02 
0167:0041c6eb call 00517710 
0167:0041c6f0 dec dword [ebp-20] 
0167:0041c6f3 lea eax,[ebp-0c] 
0167:0041c6f6 mov edx,02 
0167:0041c6fb call 00517710 
0167:0041c700 cmp byte [ebp-45],00 
0167:0041c704 jz 0041c750 <--按了60多下F10后会在这里发现一个跳转,嘿嘿,就是它了!!! 
0167:0041c706 mov ecx,[0052ae7c] 
0167:0041c70c mov eax,[ecx] 
0167:0041c70e mov eax,[eax+0318] 
0167:0041c714 call 00411fd0 
0167:0041c719 mov word [ebp-2c],2c 
0167:0041c71f mov edx,00521b50 
0167:0041c724 lea eax,[ebp-14] 
0167:0041c727 call 005175b0 
0167:0041c72c inc dword [ebp-20] 
0167:0041c72f mov eax,[eax] 
0167:0041c731 call 004b41b0 
0167:0041c736 dec dword [ebp-20] 
0167:0041c739 lea eax,[ebp-14] 
0167:0041c73c mov edx,02 
0167:0041c741 call 00517710 
0167:0041c746 mov eax,[ebp-40] 
0167:0041c749 call 004a81d0 
0167:0041c74e jmp short 0041c77d 
0167:0041c750 mov word [ebp-2c],38 
0167:0041c756 mov edx,00521b6b 
0167:0041c75b lea eax,[ebp-18] 
0167:0041c75e call 005175b0 
0167:0041c763 inc dword [ebp-20] 
找到了关键跳转之后就别闲着了,呵呵,放雷管吧!(你可以用W32Dasm打开这个文件,然后按Shift+F12,之后输入0041c704,这样就可以在右下角看到相应的偏移地址了) 
小技巧:在TRW中,如果你觉的某处可能是关键跳转的话,可以用r fl z这个指令来进行测试,该指令可使以成立的条件取反,比如说本来JZ XXXXXXXX成立,可以跳走了,用r fl z指令后该条指令就不成立了,即就不会跳走了。以上也是,你可以在0041c704处输入r fl z,呵呵,再执行几步看看,是不是成功了?还有就是如果你只是想达到注册软件的目的,且该软件只在注册的时候验证一次的话,用这个方法就可以代替雷管了! 
呵呵,最后还是要说一句,爆破只是一些雕虫小技。刚入门时玩几次就够了,切莫就此不前... 
后话:你可能慢慢就会发现,有一些软件其实并没有你想象中那么简单,你甚至连找到它的关键跳转都找不到。这很正常,你要做的便是多动手多练习,慢慢你就会明白过来的。我今天之所以给你举这两个例子,就是因为它们两个都比较简单,且能说明重点,给你讲那些比较那个的软件的爆破,反而会让你看的一头雾水... 

第七章-寻找软件的注册码 
我们来寻找软件真正的注册码! 
寻找软件的注册码就像你小时玩的躲猫猫一样,简单又有趣,虽然后来你会不这样觉的 
好的,我们开始。 
我不知道你有没有明白我前面在原理中讲的那些东西,如果没明白,我就再说一遍 
软件通过你输入的用户名或者机器码什么的生成一个正确的注册码来与你输入的注册码进行比较,如果两个相同,也就是说你输入的注册码是正确的话,那软件就会完成注册。如果你输入的不正确,嘿嘿,当然就不会注册成功。 
好的,现在你已经知道软件会有一个比较两个注册码的过程,这就是关键所在。一般如果你遇到的是那种明码比较的软件,这会是一件非常另人愉快的事情的 
软件会先计算出正确的注册码然后再与你输入的那个进行比较,也就是说正确的注册码会被软件自己算出来!嘿嘿,搜身你会吗?虽然法律以及道德不允许我们去搜身,但… 
我接着说,虽然现在的软件已经比以前要厉害上许多,但,那种用明码比较的,还是大有人在的。所谓明码比较,就是说软件先算出正确的注册码,然后放到内存或你家的沙发下面,之后再得到你输入的那个注册码,接着就比较了。呵呵,好理解吧,我们只要找到那个比较的地方,看一下软件把注册码放到内存的哪里了,再到相应的内存处瞧一瞧,就万事OK了! 
还记的对你说过的那些常见的(也是最菜的)比较吗?我捡其中最简单的一个来给你再解释一下: 
mov eax [ ] 这里可以是地址,也可以是其它寄存器 该条指令也可以是mov eax [ ] 
mov edx [ ] 同上 通常这两个地址就储存着重要信息 该指令也可以是 pop edx 
call 00?????? 关键call 
jz(jnz)或 jne(je) 关键跳转 
第一条mov eax [ ]指令是将一个内存地址或另外一个寄存器(该寄存器中装的是内存地址)装入eax中。第二条指令与其相同,是将一个内存地址或另外一个寄存器中的内存地址装入edx中。而这两条指令是干什么的呢?嘿嘿嘿嘿… 
这两条指令就是用来存放真假两个注册码的地址的,也就是说eax和edx这两个寄存器中此时一个装的是正确的注册码的内存地址,一个是你输入的那个错误的注册码的内存地址。软件在比较注册码前将两个注册码的内存地址分别装入到两个寄存器中,然后就是关键Call出场。在这个关键Call中对注册码进行比较时,软件会从相应的寄存器中取出两个注册码来比较,接着出来就是一个关键跳转,通过上面Call中的比较结果来做相应的跳转… 
你应该已经想到什么了吧!没错,我们只要找到软件的关键Call,然后在关键Call处来查看相应的内存地址就可以找到正确的注册码了 而这一切,都可以通过调试器来完成。从某种意义上来说,如果你能自己一个人把你家的微波炉修好,那你就绝对会用调试器 我们在调试器中,只要一步一步执行到关键Call处,然后用d eax和d edx就可以查看两个地址中放的两个注册码,如果你发现其中的一个是你自己刚才输入的,那么另一条就是正确的 
而所谓的内存注册机呢?我这里就不再多说了,它的原理就是自动在软件注册的时候中断到相应的地方,并显示相应内存处的值,当然它是需要配置的... 此类软件有CRACKCODE2000和注册机编写器keymake,具体用法你可以参考软件的联机帮助^_^ 
我们剩下的问题就是如何来找个这关键Call了,基本上来说你就用前边给你讲爆破时的那种方法就可以了,很简单的 
但是就像你家后门的玻璃可能永远擦不干净一样,我们家后门的玻璃也从来没擦干净过 导演:NG!重说,就像所有事情都有例外一样,有些软件的关键Call会比较难找一点,但如果你掌握了适当的方法,同样也会很好找的... 
我们就来玩玩吧: 
首先,我们还来用*P这个软件上上手^_^ 
它已经是我们的老朋友了,所以就不用再介绍它了吧 
好的,我们先装上它(嘿嘿,偶就是喜欢说废话,你打偶偶也要说^_^)接着我们点帮助-注册,输入Nameuunb[CCG],Code:19870219 
然后请出我们的老伙计TRW2000,下bpx hmemcpy 按F5点确定被拦: 
KERNEL?HMEMCPY 
0147:9e62 push bp 
0147:9e63 mov bp,sp 
0147:9e65 push ds 
0147:9e66 push edi 
0147:9e68 push esi 
0147:9e6a cld 
0147:9e6b mov ecx,[bp+06] 
0147:9e6f jcxz 9ee9 
...省略N多代码... 
输入bc *,删除断点。pmodule ,直接跳到程序领空: 
0167:00436d13 mov [ebx+0c],eax 
0167:00436d16 mov eax,[ebx] 
0167:00436d18 cmp eax,byte +0c 
0167:00436d1b jnz 00436d38 
0167:00436d1d mov edx,[ebx+08] 
0167:00436d20 push edx 
0167:00436d21 mov ecx,[ebx+04] 
0167:00436d24 mov edx,eax 
0167:00436d26 mov eax,esi 
0167:00436d28 call 00432b24 
...省略N多代码... 
按8下F12就会提示出错,我们第二次就按7次 接着我们再来按F10,按16下就会报错,好的,我们再来:这一次我们按F10的时候,就按我前边说过的方法,到与上次按的次数相差五六次的时候就慢下来。好的,我们按十来下的时候就慢下来仔细瞅瞅,呵呵,一下子就看到004f4dec处的那个跳转以及它上面的关键CALL了 我们按F10单步执行到004f4de7处(即关键CALL处)后下指令d edx就可看到真正的注册码,而d eax则可以看到我刚才输入的19870219 代码给你: 
0167:004f4dc4 mov eax,[ebp-08] <---7下F12,1下F10就来到这里(此时ebp-08处放的是刚才输入的注册码19870219) 
0167:004f4dc7 push eax <---将EAX压栈; 
0167:004f4dc8 lea edx,[ebp-10] 
0167:004f4dcb mov eax,[ebx+02e0] 
0167:004f4dd1 call 00432f24 <---该CALL用来得到用户输入的用户名,其实就是某个API函数,嘿嘿,好奇的话可以追进去看看 
0167:004f4dd6 mov edx,[ebp-10] <---将得到的用户名放入EDX; 
0167:004f4dd9 lea ecx,[ebp-0c] 
0167:004f4ddc mov eax,ebx 
0167:004f4dde call 004f4fac <---该CALL用来计算出真正的注册码; 
0167:004f4de3 mov edx,[ebp-0c] <---将计算出的真.注册码放入EDX,在下条指令时可用D EDX查看; 
0167:004f4de6 pop eax <---先前压入的注册码出栈; 
0167:004f4de7 call 0040411c <---该CALL用来比较两个注册码,罪魁祸首啊!; 
0167:004f4dec jnz 004f4e64 <---不相等则跳,跳必死,暴破将75改为74或EB,当然90也行; 
0167:004f4dee mov dl,01 
0167:004f4df0 mov eax,[00452558] 
0167:004f4df5 call 00452658 
0167:004f4dfa mov [ebp-04],eax 
0167:004f4dfd xor eax,eax 
0167:004f4dff push ebp 
0167:004f4e00 push dword 004f4e5d 
0167:004f4e05 push dword [fs:eax] 
0167:004f4e08 mov [fs:eax],esp 
0167:004f4e0b mov cl,01 
0167:004f4e0d mov edx,004f4ea8 
0167:004f4e12 mov eax,[ebp-04] 
0167:004f4e15 call 0045283c 
0167:004f4e1a mov ecx,004f4ecc 
0167:004f4e1f mov edx,004f4ef4 
0167:004f4e24 mov eax,[ebp-04] 
0167:004f4e27 call 00452c80 
0167:004f4e2c mov eax,004f4f00 
0167:004f4e31 call 00458b8c 
0167:004f4e36 mov eax,[0050306c] 
0167:004f4e3b mov eax,[eax] 
0167:004f4e3d mov edx,004f4f24 
0167:004f4e42 call 00432f54 
0167:004f4e47 xor eax,eax 
0167:004f4e49 pop edx 
0167:004f4e4a pop ecx 
0167:004f4e4b pop ecx 
0167:004f4e4c mov [fs:eax],edx 
0167:004f4e4f push dword 004f4e6e 
0167:004f4e54 mov eax,[ebp-04] 
0167:004f4e57 call 004030c4 
0167:004f4e5c ret 
0167:004f4e5d jmp 00403824 
0167:004f4e62 jmp short 004f4e54 
0167:004f4e64 mov eax,004f4f48 <---由上面的0167:004f4dec处跳来,挂!; 
0167:004f4e69 call 00458b8c 
0167:004f4e6e xor eax,eax 
整理: 
Nameuunb[CCG] 
CodeCCG5296 
可以真接在TRW2000中下断点bpx 004f4de6,中断后用D EDX来查看真.注册码。 
另附:CRACKCODE2000的CRACKCODE.INI 
[Options] 
CommandLine=*P.exe 
Mode=2 
First_Break_Address=4f4de7 
First_Break_Address_Code=E8 
First_Break_Address_Code_Lenth=5 
Second_Break_Address=404123 
Second_Break_Address_Code_Lenth=2 
Save_Code_Address=EDX 
呵呵,是不是很简单?我说过了嘛,其实并不难的 
我不知道你有没有发现,其实上面的软件的关键CALL还是很好找的,相信你用W32Dasm就中以找出来,那为什么不用呢?对于那些比较简单的软件,何必非请出调试器呢? 
给你贴个用W32Dasm找关键CALL的: 
【软件名称】e族百变桌面 
【软件版本】4.0 
【文件大小】1316KB 
【适用平台】Win9x/Me/NT/2000 
【软件简介】提供25种变换桌面的方式,让你的桌面焕然一新。操作简单,无需费力学习。支持多种Internet流行图片格式。将壁纸文件打包,方便存储、转发。将壁纸包展开,还原图片文件。 
嘿嘿,我也懒的去折腾我的小猫了,咱们就还用电脑报2001年合订本配套光盘上的软件吧 (2002年的偶没有买) 
首先装上它(嘿嘿,你习惯了?为什么不丢东西了? ^_^)运行一下该软件先,该软件自动生成了相应的机器码,并停留在注册项上,输入注册码19870219,点确定,挂! 
用fi检查,该软件为Delphi编译,没加壳。 
用W32DASM打开该执行文件,参考-串式参考,在最下边,见到了刚才弹出的"注册码不正确,请联系作者"。 
用鼠标双击,发现只有一处调用,在00488E97处,接着在串式参考对话框中在"注册码不正确,请联系作者"处向上找,找到"感谢您支持国产软件,祝您好运"(说的我都不好意思了) 
用鼠标双击,仍旧只有一处调用,在00488DF7处: 
* Referenced by a (U)nconditional or (C)onditional Jump at Address: 
|:00488DCD(U) 

:00488DD9 8B45FC mov eax, dword ptr [ebp-04] 
:00488DDC 8B8020040000 mov eax, dword ptr [eax+00000420] 
:00488DE2 35280BB61E xor eax, 1EB60B28 
:00488DE7 3B45F8 cmp eax, dword ptr [ebp-08] <---关键比较,? EAX来查看软件正确的注册码; 
:00488DEA 0F85A0000000 jne 00488E90 <---关键跳转,不相等就跳,跳必挂! 
:00488DF0 6A40 push 00000040 

* Possible StringData Ref from Code Obj ->"注册成功" 

:00488DF2 68D48E4800 push 00488ED4 

* Possible StringData Ref from Code Obj ->"感谢您支持国产软件,祝您好运!" 

:00488DF7 68E08E4800 push 00488EE0 <---双击串式参考便跳到此行,我们向上找第一个跳转处就是关键跳转,关键跳转上面就是关键比较; 
:00488DFC 8B45FC mov eax, dword ptr [ebp-04] 
:00488DFF E81CD2FBFF call 00446020 
:00488E04 50 push eax 
...省略代码若干... 
向上看,00488DEA处有一跳转,不相等便跳到00488E90处,跳必挂!还记的00488E97处的出错对话框吧! 罪魁祸首啊! 
在向上一行,看00488DE7处:cmp eax, dword ptr [ebp-08],此为关键比较。可用? EAX查看软件正确的注册码。 
整理: 
打开该软件,在注册码处输入19870219,打开TRW2000,下断点bpx 00488DE7,点注册被拦。输入? EAX得到软件正确的注册码。 
机器码:533226313 
注册码:25061473 
用注册机编写器keymake编写该软件的注册机: 
点其它-另类注册机(F8),软件名称输入ePaper.exe,注册码选寄存器方式 EAX 十进制。 
添加断点,中断地址:00488DE7,中断次数:1,第一字节:3B,指令长度:3。 
生成注册机后完工,万事OK! 
—————————— 


嘿嘿,现在是不是觉的找软件的注册码越来越像小时候玩的躲猫猫了? 可惜偶小时候没有青梅竹马那种类型的伙伴... 
好的,我们这次讲个有点儿名气的软件,WinZIP8.1,这个软件相信大家都用过吧,反正偶是喜欢用RAR,不过也多少用过几天这玩意儿... 
如果你没听说过,那看介绍好了 
【软件名称】WinZIP 
【软件版本】8.1 Beta 2 
【文件大小】1757KB 
【适用平台】Win9x/Me/NT/2000 
【软件简介】一个强大并且易用的压缩实用程序,支持ZIP、CAB、TAR、GZIP、MIME,以及更多格式的压缩文件。其特点是紧密地与Windows资源管理器拖放集成,不用离开资源管理器而进行压缩、解压缩。 
不用我说了吧,出处仍旧是电脑报2001年合订本的配套光盘 
我之所以先择它,是因为觉得它的关键CALL没有前边那两个那样好找(其实也就那样了^_^)极具代表性,而且通过它可以让你感受一下Ollydbg这个魅力比你家的荼几还大的调试器 
这里之所以提到Ollydbg,是觉的它真是一个非常非常棒的调试器...强烈建议你多玩几次...(MP3好听吗? ^_^) 
我们来吧,首先当然还是要装上它(左闪术,右闪术),然后用Ollydbg来载入,此时界面会被分成四个部分,左上方是软件反汇编后的代码,右上方是寄存器开会的地方,左下方是内存区,右下方显示的则是堆栈的信息。 
我们来下断点,按Alt+F4,之后选USER32,然后再鼠标右键-->搜索-->当前模块中的名称,然后在那一大堆函数中找到GetDlgItemTextA,按F2来下断点,它会提示你错误,并说无法设置中断点,是不是很过瘾?(呜呜呜...大哥,我错了,再也不敢了...) 
呵呵,这个我也不知道什么原因,明明是用了这个函数嘛,就是不让断,其实我对Ollydbg也不是太那个(关键是讨厌它的下断方式)看来还是用我们的万能断点吧,输入注册名Suunb[CCG],输入注册码19870219,然后用TRW2000下断bpx hmemcpy,断到之后,pmodule返回领空后一次F12就会出错,看来所有的东东就在这里了... 
我们用TRW2000再断一下,返回领空之后记着第一条指令的地址0040bd5f,呜呜呜...上条指令明明是调用GetDlgItemTextA,为什么在Ollydbg中不让下呢? 
没关系,我们记下这个地址后仍旧用Ollydbg来加载程序,之后在反汇编窗口中找到0040bd5f处,然后按下F2来下断(会变为红色),下断之后便按F9来运行程序,接着输入注册名Suunb[CCG],注册码19870219后按确定,程序会被Ollydbg给断到: 
0040BD5F |. 57 PUSH EDI 
0040BD60 |. E8 F34A0500 CALL WINZIP32.00460858 
0040BD65 |. 57 PUSH EDI ; /Arg1 


第八章--注册码是怎样炼成的 
你应该明白的是,并不是所有的软件作者都像你想象并希望的那笨 没有人愿意自己的软件被别人在调试器中用一条d指令就能找到正确的注册码...要是那样的话还出来搞什么? 
前边儿我们讲的查找软件注册码的方法是有针对性的,必须保证的?br>
 
 
【发表回复】【查看论坛原帖】【添加到收藏夹】【关闭】 

--------------------------------------------------------------------------------
 emailwht 回复于:2003-07-26 20:04:51
是该软件使用的是明码比较,这样的话,我们只需找对地方,一个d指令就成了。那既然有明码比较这个词,就不难猜出还有相应的非明码比较...非明码比较也比较容易理解,就是软件比较两个注册码的方法不同而以,并不是计算出正确的注册码后就与用户输入的进行比较,它可能会采用每计算出一位就与注码中的相应位比较一次,一但发现与用户输入的不同,就提示出错等等等等... 
遇到这样的软件,我们其实也可以找到其相应的注册码,但有点儿惨,要一位一位的计下来...但是如果人家不给你面子,一但计算出某位不正确就跳走的话,那你怎么办?所以,国民想致富,种树是根本...NG!所以遇到这种软件,我们就只有对其算法进行分析,并做出注册机才是唯一的方法(如果你想写注册机的话)... 
你要明白,就算我们能找到那些采用明码比较的软件的注册码,原因也仅仅是因为其采用的是明码比较,所以我们没有什么值的高兴的地方,我们真正要做的,并不是找到一个注册码而以...当然如果你刚入门,那对你的提高还是很有帮助的。我们Crack一个软件的最终目的,是对其进行相应的分析,搞懂它的注册算法并写出注册机,这样才算是成功的Crack了一个软件,成功后的心情是难以表达的!就像你便秘了多天后一下子排了出来一样 ^_^,呵呵这个比喻虽然粗俗,但是你可以想象一下,对一个软件进行仔细的分析,最后一下把它的算法给搞明白了,那种感觉...我深信不疑的认为有一天你也能体会的到,偶等你 
相信你以前看过那些高人大虾的关于软件注册算法分析的文章,同时也相信你有过试图跟踪分析某软件的举动,虽然后来的结果另人不太满意 
其实分析一个软件的注册算法,这其中包括了一些技巧性方面的东西以及必要的经验,很难想象一个连调试器的使用都还没掌握的人试图去分析一个软件会是怎样一个场面...嘿嘿,偶是见过的 使用调试器并不难,但那并不意味着你就能去分析一个软件了,见CALL就追这样的举动可不是偶一个人有过的经历,本章我尽量给你说明适当的分析方法。 
相信大家都有不在父母陪同下独自使用调试器的能力以及看懂大部分汇编指令的能力了吧,那就够了!我们开始... 
正式开始今天的正题,我来举两个例子,相信这两个例子都有足够的表达能力,最起码比我们家楼下那个卖油条的表达能力要强多了... 
好的,首先,我们还是请出我们的那位老朋友吧 嘿嘿,在此,偶向*P(中华压缩)v7.0的作者表示我内心最真诚的歉意!相信我用这个老版本的中华压缩不会给您带来经济上的麻烦... 
通过前边儿两章的讲解,我们已经把这个软件大体上给搞明白了,并且也追出了其相应的注册码。而我们今天的目的是对其注册算法进行分析,并写出注册机!这个软件的注册算法其实也比较简(并且存在Bug)用它来当例子,很能说明情况... 
好的,我们开始,前边儿追注册码的时候我们就已经知道了其用于计算正确注册码的关键CALL的所在位置为004f4dde,我们用TRW2000来对其进行分析!(鉴于目前大部分教程中仍以TRW2000为主,而且这个是大多数菜鸟都会用的调试器,偶就用这个调试器来做具体讲解) 
先启动*P,帮助--注册(所以我才说这个软件非常适合写教程用嘛,注册后仍然中以再次注册)输入注册名Suunb[CCG],注册码19870219。之看按Ctrl+N呼出TRW2000,下断点bpx 004f4dde,F5返回。 
接着就按确定吧,呵呵,被TRW2000拦到了。通过前边两章的分析,我们以经知道了004f4dde处的这个CALL用于计算正确的注册码,所以我们直接按F8跟进吧!注册码的算法,就包涵在这个CALL中,把它给分析透了,我们也就能弄明白软件的注册码是怎样生成的了。但是要怎么分析呢?这是一个比较严肃的问题,面对那一堆堆的指令,我不知道你是怎么想的,反正我第一次时是觉的找不着北,我怎么哪些重要哪些不重要呢?再说了,里面又包涵了那么多CALL,我还要一个一个地追进去看看? 
呵呵,这就是我说的技巧所在了。其实也没什么可怕的,只要你汇编不是问题,就行了。我们首先可以先把这个计算注册码的CALL从头到尾执行一遍,搞明白其中大概的跳转以及其中某些CALL的作用,hehe~~你可以执行过一个CALL后就看一下各个寄存器的变化情况(如果寄存器中的值改变了,颜色就会变)如果某寄存器的值在CALL过之后改变了,我们就可以看一下其包含的值是何类型,如是内存地址就用d指令看一下,如是数值就看一下是不是得到你输入注册名或注册码的位数等等,这样的话就可以淘汰下来一大部分的CALL,因为有许多CALL的作用只是把注册名或注册码装入到内存中的某个地址或者得到注册名(注册码)的位数或注册码某一位的ASCII码,对与这些,我们不必深究。还是推荐你用Ollydbg,执行过一条指令后很多信息都可以看到 好的,我接着说,按F8追入CALL之后先大概走一遍...我给出追入后的反汇编代码,并给出注释,相应的分析看后面... 
0167:004f4fac push ebp <--F8跟入后的第一条指令 
0167:004f4fad mov ebp,esp 
0167:004f4faf push byte +00 
0167:004f4fb1 push byte +00 
0167:004f4fb3 push byte +00 
0167:004f4fb5 push byte +00 
0167:004f4fb7 push byte +00 
0167:004f4fb9 push byte +00 
0167:004f4fbb push byte +00 
0167:004f4fbd push ebx 
0167:004f4fbe push esi 
0167:004f4fbf push edi 
0167:004f4fc0 mov [ebp-08],ecx 
0167:004f4fc3 mov [ebp-04],edx 
0167:004f4fc6 mov eax,[ebp-04] 
0167:004f4fc9 call 004041c0 
0167:004f4fce xor eax,eax 
0167:004f4fd0 push ebp 
0167:004f4fd1 push dword 004f5097 
0167:004f4fd6 push dword [fs:eax] 
0167:004f4fd9 mov [fs:eax],esp 
0167:004f4fdc xor esi,esi 
0167:004f4fde lea eax,[ebp-0c] 
0167:004f4fe1 mov edx,[ebp-04] 
0167:004f4fe4 call 00403e24 <--此CALL过后用于得到用户输入的注册名 
0167:004f4fe9 mov eax,[ebp-0c] <--将得到的注册名的地址装用eax寄存器 
0167:004f4fec call 0040400c <--此CALL用于得到用户输入的注册名的位数,并将其放入eax中 
0167:004f4ff1 mov edi,eax <--将注册名的位数装入edi中 
0167:004f4ff3 test edi,edi <--对edi进行测试 
0167:004f4ff5 jng 004f5051 <--如果edi中的值为0就会跳走 
0167:004f4ff7 mov ebx,01 <--ebx置1,用于后面的运算 
0167:004f4ffc mov eax,[ebp-0c] <--ebp-0c中装的是注册名的内存地址,此时将其付于eax 
0167:004f4fff mov al,[eax+ebx-01] <--eax中此时装的是注册名的内存地址,加上ebx中的值再减去01,用于得到注册码中的相应位的字符,比如说我们第一次执行到这里的时候ebx中装入的是01,再减去01后得到的值其实还是eax本身,这样就能得到注册名中的第一个字符了,而执行到后边再跳回来时ebx会加上1,所以就能得到下一个字符了... 
0167:004f5003 call 004f4f60 <--这个CALL很重要,后面会说明我们是怎样知道它很重要的 
0167:004f5008 test al,al <--在这里我们会发现一个测试运算,对象是al,而al在前边CALL之前刚装入了注册名中的某一个字符,所以我们可以断定上面的那个CALL会对得到的字符做上一些手脚,待会儿我们再跟入... 
0167:004f500a jz 004f5031 <--如果al中装的是0就跳到004f5031处,而al中的值会被004f5003处的那个CALL所改变 
0167:004f500c lea eax,[ebp-18] 
0167:004f500f mov edx,[ebp-0c] <--ebp-0c中装的是注册名的内存地址,此时装入edx中 
0167:004f5012 mov dl,[edx+ebx-01] <--跟前边儿004f4fff处的指令道理相同,得到注册码中的当前参加运算的字符 
0167:004f5016 call 00403f34 <--不重要!! 
0167:004f501b mov eax,[ebp-18] 
0167:004f501e lea edx,[ebp-14] 
0167:004f5021 call 004088ac <--不重要!! 
0167:004f5026 mov edx,[ebp-14] 
0167:004f5029 lea eax,[ebp-10] 
0167:004f502c call 00404014 <--该CALL同样比较重要,其作用是这样的,如果当前参加运算的字符在前边004f5003的CALL里进行运算之后符合了要求(符合要求后al会被置非0值)那么在004f500a处的跳转将会失去作用,而执行到这里后该CALL会将当前的这个符合要求的字符保存到00D3B3C4处(内存)!!后边儿会再详细说明 
0167:004f5031 cmp ebx,byte +01 <--用此时ebx中装的值减去1 
0167:004f5034 jz 004f5040 <--如果为零,也就是说此时计算的是注册名中的第一个字符的话就跳到004f5040处 
0167:004f5036 mov eax,[ebp-0c] <--ebp-0c中装的是注册名的内存地址,该指令将注册名的内存地址装入eax中 
0167:004f5039 movzx eax,byte [eax+ebx-02] <--用于得到上一个参加运算的字符 
0167:004f503e jmp short 004f5046 <--无条件跳转到004f5046处 
0167:004f5040 mov eax,[ebp-0c] <--ebp-0c中装的是注册名的内存地址 
0167:004f5043 movzx eax,byte [eax] <--得到注册名的第一个字符 
0167:004f5046 lea esi,[esi+eax*4+a8] <--!!!这一条指令就是关键所在,后面会说明的!!!此指令先得到本轮参加运算的字符的ASCII码,然后乘以6,之后再加上a8(即十进制数168,呵呵,可以理解)同时再将这个字符计算得到的值与前面已经运算过的字符的值的和相加! 
0167:004f504d inc ebx <--ebx加1,用于得到注册码的下一个字符 
0167:004f504e dec edi <--edi减1,edi中装的是注册码的位数 
0167:004f504f jnz 004f4ffc <--不为零就跳到004f4ffc处开始对下一个字符进行运算...也就是说每计算完一个字符就将edi减去1,直到其为0也就是所有的字符全参加过运算为止。 
0167:004f5051 lea edx,[ebp-1c] <--把装注册码后半部分的地址装入edx,传给下面的CALL 
0167:004f5054 mov eax,esi <--将前面计算的注册码的后半部分的值装入eax中 
0167:004f5056 call 00408c70 <--将前面计算得到的注册码后半部分的值转换为十进制,并装入ebp-1c中 
0167:004f505b mov ecx,[ebp-1c] <--epb-1c中装的是注册码的后半部分 
0167:004f505e lea eax,[ebp-0c] 
0167:004f5061 mov edx,[ebp-10] <--ebp-10中装的是注册码的前半部分 
0167:004f5064 call 00404058 <--该CALL用于将前后两部分注册码合并置一起,合并后的注册码会存放置ebp-0c处 
0167:004f5069 mov eax,[ebp-08] 
0167:004f506c mov edx,[ebp-0c] 
0167:004f506f call 00403de0 
0167:004f5074 xor eax,eax 
0167:004f5076 pop edx 
0167:004f5077 pop ecx 
0167:004f5078 pop ecx 
0167:004f5079 mov [fs:eax],edx 
0167:004f507c push dword 004f509e 
0167:004f5081 lea eax,[ebp-1c] 
0167:004f5084 mov edx,05 
0167:004f5089 call 00403db0 
0167:004f508e lea eax,[ebp-04] 
0167:004f5091 call 00403d8c —————————— 

呵呵,看了我加了注释后的代码是不是好理解多了?你也许会问,你怎么知道那些CALL是做什么的?我前边儿不是说过方法了吗?我们先大概地过上一遍,看一下各个跳转,然后再大大概的看一下各个CALL的作用...你以为上面这些注释是我过一遍之后就能写出来的?你多过几遍,心中就会有了个大概... 
你现在在想些什么?众人:站着说话不腰痛...我晕~~ 
呵呵,我尽量说的仔细一些: 
其实很好理解的,我们追了进来,之后大概的看一下那些个CALL,其中一些稍有经验的一看就知道是用来得到注册名或长度什么的...之后我们再从头跟一遍...跟到004f4ff3处,会发现其会对注册名的位数进行一个比较,看用户是否输入了注册名...(也就是说如果edi中装的注册名的位数不大于0,即没输入就跳走,一会儿我会在后面说一下关于这点儿的Bug)而后在004f4ffc处我们会发现软件会得到注册名的内存地址,接下来的一条指令一看就知道是用来得到注册名中的各个字符的,嘿嘿,见到这类指令,马上就向下看吧,找一下下边儿哪条指令会再跳回到004f4ffc处...呵呵,我们会在004f504f处发现目标,好了,现在我们就知道了从004f4ffc到004f504f之间的那些个指令会对注册名中的每一个字符进行计算... 
呵呵,也就是说软件从004f4ffc处开始先是得到注册名中的第N位字符,然后进行一系列的运算,之后执行到了004f504e处时把先前先到的注册名的位数减去1然后看其是否为0,不为0就再跳到004f4ffc处,然后得以注册名的N+1位再来进行计算。此举的目的就是为了看注册名的各位是否都被计算过了,如果不为0就说明还没有计算完,呵呵,很简单的道理嘛,edi中装的是注册名的位数,第计算过一位后就将其减1,减完了,注册名的各位也就都参加了运算... 
好的,我们再来看具体的算法部分: 
在004f4ff5的跳转,如果你输入了注册名,其就不会跳走...偶输入的是Suunb[CCG],好的,此时会继续执行到004f4ff7处,该指令对ebx进行初始化...给它付1,然后在004f4ffc处时会将ebp-0c中装的注册名的内存地址装入eax中,接着的004f4fff处用于得到注册名的第一个字符,并将其装入al。想象一下,eax中装的是注册名的内存地址,从该地址开始连续10个内存单元是我们输入的注册名S u u n b [ C C G ] 呵呵,明白了吗?eax中装的内存地址就是注册名在内存中的首地址,第一次执行到这里时ebx中装的是1,eax+ebx-01后得到的还是注册名的首地址,也就是S。而等到后面004f504f处的跳转指令跳转回来之前,会在004f504d处有一条inc指令会给ebx加1,这样的话再执行到这里时就会得到注册名中的第2个字符u了,嘿嘿,第三次来之前会再给ebx加上1,明白了吗?总知你可以把ebx中的值理解为当前参加运算的字符在注册名中的位数,即ebx是1就是得到注册名的第一位(S),如果ebx是2就是得到注册名的第2位(u). 
而后紧接着在004f5003处会有一个CALL等着我们,呵呵,这个CALL比较关键,注册码的一部份由它来决定,要发现它的重要性并不难,因为在004f5003处下面会有一个跳转,跳转之前会对al进行测试,嘿嘿,而al在CALL之前装入的是当前参与运算的字符...并且你用调试器过一下这个CALL就会发现其对al进行了修改,呵呵,这个CALL会对al做一些处理,而处理的结果直接影响了后面部分的流程,所以,对于它,我们一定要跟进...最好能派出两个人在边路对其进行防守,并找专门的后位对其盯梢... 
我们待会儿再跟进它,现在还是要先搞明白软件大体上的算法。好的,我接着说,在004f5008处对al进行了测试之后会有一个跳转,即如果al中此时装的值为0就跳到004f5031处去...你可以理解为这个CALL会对字符进行一些运算,如果符合了要求,al就会被置0或1什么的,出来后的测试用来判断当前字符是否符合要求,如果符合就跳或不符合就跳... 
继续,由于我输入的注册名的第一个字符是S,而S刚好能通过004f5003处的那个CALL的计算 所以就没有跳走,我继续按F10进行单步执行...接下来的004f500c、004f500f、004f5012这三条指令跟前边儿的得到注册码第N位字符的指令道理是一样的,你看注释好了...而后面从004f5016到004f5029处的这几条指令也没什么好讲的,对中间的两个CALL好奇的话可以进去大概看一下。得不到什么实质性的东西...而004f502c处的这个CALL嘛,就很重要了,呵呵,它的作用是什么呢?还记的我刚才说过的004f5003处的那个CALL吧,它执行过后会使al发生变化,它下面的跳转指令会根据al的值做相应跳转,即如果al为0,就跳到004f5031处,刚好就跳过了004f502c处的这个CALL...而我输入的第一个字符是S,刚好符合了004f5003处那个CALL的要求,所以没有跳走,于是就执行到了这里,你可以追进去看一下,里面并不复杂,只是将当前参加运算的字符装入内存的00D3B3C4处(如果当前参加运算的字符在004f5003处没有通过,就不会执行到这里,呵呵,明白过来了吧,这个CALL用于收集注册名中所有符合004f5003处那个CALL要求的字符) 
HOHOHO~~(请模仿周星星式的笑声...)现在我们已经明白了一半了...好的,我们继续... 
不管你是从004f500a处跳到004f5031处的,还是一步步执行到这里的,总知,不管你输入的注册名中参加当前运算的那一个字符符不符合004f5003处的那个CALL的要求,总知都会执行到这里...这条指令用来干什么呢?还记的ebx中装的是参加运算的字符在注册名中的相应的位数吗?cmp ebx,byte +01 就是用ebx减去1,该条指令的用途也就是看一下当前参加运算的字符是不是注册名中的第一个字符,如果是就跳到 004f5040处,否则继续... 我们先看004f5040处,当执行到此处时,ebp-0c中装的其实是注册名的内存地址(前边就已经说过了)在这里将其装入eax中,而后面004f5043处的指令的用途就是得到注册名的第一个字符...好了,我们再拐回来看004f5036处,如果当前参加运算的字符不是注册名中的第一个字符,就不会跳走,而执行到这里时同样将ebp-0c中装的注册名的内存地址放入eax中,而004f5039处的eax,byte [eax+ebx-02]嘛,呵呵,很好理解,eax+ebx-01得到的是当前参加运算的字符的内存地址,而这里的eax+ebx-02得到的就是当前参加运算的字符的前面的那个字符,了解? 
我们接着看004f5046处的那条指令吧,这个同样非常重要,它的作用是计算注册码的后半部分! 
我相信你很容易就能理解它的意思了,当执行到这里时,eax中装的或者是注册码中的第一个字符,或者是当前参加运算的字符的前一个字符(注:字符在内存或寄存器中是以ASCII码来表示的,如S在eax中会显示为00000053,而S的ASCII码便是53,十进制为83)...我们第一次执行到这里时,esi中的值为0(即00000000)eax*4+a8的意思就是用当前参加运算的字符的ASCII码乘以4,再用积加上a8(也就是十进制数168,一路发?)再用这个和与esi相加,我已经说过了,第一次执行到这里时esi中的值为0...而当第二次执行到这里时,esi中装的便是注册名的第一个字符的ASCII码乘以4再加一路发的和... 
你会问你为什么知道它是计算注册码的后半部分的?猜的!!呵呵,当然不是,我们可以看到,在004f5054处,程序会将前面计算的结果装用eax中,后边儿紧接着就是一个CALL,嘿嘿,光天化日之下,这也太明显了吧,我们追进去大概看一下就知道它的作用是将十六进制的数转换为十进制的...并将转换后的结果装入edx中装的内存地址处,在CALL之前我们会看到edx中的值以由004f5051处装入,即ebp-1c,呵呵,CALL过之后你用d ebp-1c看一下,就会看到你注册码的后半部分了... 
而后程序会在004f505b将注册码后半部分装入ecx中,在004f505e处时会将一个内存地址ebp-0c装入eax处(它的作用就是起一个传递参数的作用,在待会儿的CALL中会用eax中装入的值来存放结果)之后的004f5061处会将ebp-10装入edx中,ebp-10处装的是什么呢?我们用d ebp-10指令看一下就会知道它的地址为00D3B3C4,嘿嘿,你的嗅觉敏感吗?不敏感的话我就再说一遍,还记的004f502c处的那个CALL吗?它的作用就是收集符合004f5003处的那个CALL的要求的字符... 
嘿嘿,你明白过来了吗? 
这个软件的注册算法是这样的:首先得到注册码的位数,看其是否大于0,不大于0就跳到004f5051处...好的,我们输入了Suunb[CCG]这个注册名,此时的注册码位数就是10,所以不会跳走,之后我们会来到004f4fff处,第一次执行到这里时会将注册名的第一个字符S装入al中,第二次来时会将注册名中的第二个字符(即u)装入al中,它的作用就是将当前参加运算的字符装入al中,之后紧接着就是一个CALL,这个CALL会对当前参加运算的字符进行计算...接着出来会有一个跳转,看al中装的是不是0,如果是就跳到004f5031处,如果不是非0值就说明当前这个字符符合了要求,那么就会执行到004f502c处,这里的CALL会将其存放置内存的00D3B3C4处...而后到了004f5031处会有一个比较,作用是看当前参加运算的字符是不是注册名中的第一个字符,是的话就跳到004f5040处,在此将注册名的第一个字符装入eax,用来参加004f5046处的计算。如果当前参加运算的不是注册名的第一个字符,那么就会在执行到004f5039处时得到当前参加运算的字符前面的那个字符,将其装入eax后就无条件跳到004f5046处来参加运算。了解?也就是说你输入的注册名的第一个字符会参加两次计算,而最后一个字符不会参加计算(想想看,如果当前参加运算的字符是注册名中的第一个字符,它会参加计算,如果是第二个,就取前边的一个,即第一个又会参加一次计算,到了第三个的时候取第二个,到了第四个的时候取第三个...而当最后一个字符 
跳到0040446F处时,再得到的就是你输入的注册码的第3位了 
00404488 |. 83C6 02 |ADD ESI,2 <--同上,ESI加上2后再跳到0040446F处重新再来一遍时就会得到正确的注册码的第3位 
0040448B |. 3ACB |CMP CL,BL <--再次比较CL是否为空 
0040448D |.^75 E0 /JNZ SHORT LIAOCACH.0040446F <--不为空就再跳到0040446F处,来继续比较前4位中的1、3两位 
0040448F |> 33C0 XOR EAX,EAX <--1-4位全部比较完后会跳到这里 
00404491 |. EB 05 JMP SHORT LIAOCACH.00404498 
00404493 |> 1BC0 SBB EAX,EAX 
00404495 |. 83D8 FF SBB EAX,-1 
00404498 |> 3BC3 CMP EAX,EBX 
0040449A |. 0F85 AB000000 JNZ LIAOCACH.0040454B 
004044A0 |. 8D7424 38 LEA ESI,DWORD PTR SSESP+38] <--与上面的大体相同嘛,将正确注册码的5-8位的内存地址装入ESI中 
004044A4 |. 8D4424 18 LEA EAX,DWORD PTR SSESP+18] <--你输入的注册码的5-8位的内存地址装入EAX中 
004044A8 |> 8A10 /MOV DL,BYTE PTR DSEAX] <--得到你输入的注册码的第5 or 7位(道理我相信你一定已经明白了) 
004044AA |. 8ACA |MOV CL,DL <--再装入CL中 
004044AC |. 3A16 |CMP DL,BYTE PTR DSESI] <--与正确的注册码的第5 or 7位比较 
004044AE |. 75 1C |JNZ SHORT LIAOCACH.004044CC <--不相等就跳走 
004044B0 |. 3ACB |CMP CL,BL <--与BL中的00000000比较,看5-8位是否已经全部比较完毕 
004044B2 |. 74 14 |JE SHORT LIAOCACH.004044C8 <--是的话就跳走 
004044B4 |. 8A50 01 |MOV DL,BYTE PTR DSEAX+1] <--得到你输入的注册码的第6 or 8位 
004044B7 |. 8ACA |MOV CL,DL <--装入CL 
004044B9 |. 3A56 01 |CMP DL,BYTE PTR DSESI+1] <--与正确的注册码的第6 or 8位比较 
004044BC |. 75 0E |JNZ SHORT LIAOCACH.004044CC <--不正确就跳走 
004044BE |. 83C0 02 |ADD EAX,2 <--EAX加2,这样做的目的相信你已经知道了吧 
004044C1 |. 83C6 02 |ADD ESI,2 <--ESI也加上2 
004044C4 |. 3ACB |CMP CL,BL <--比较CL是否为空 
004044C6 |.^75 E0 /JNZ SHORT LIAOCACH.004044A8 <--不是就跳回去再来一遍 
004044C8 |> 33C0 XOR EAX,EAX <--5-8位全部比较完后全跳到这里 
004044CA |. EB 05 JMP SHORT LIAOCACH.004044D1 
004044CC |> 1BC0 SBB EAX,EAX 
004044CE |. 83D8 FF SBB EAX,-1 
004044D1 |> 3BC3 CMP EAX,EBX 
004044D3 |. 75 76 JNZ SHORT LIAOCACH.0040454B 
004044D5 |. 8D7424 40 LEA ESI,DWORD PTR SSESP+40] <--将正确的注册码的9-12位的内存地址装入ESI 
004044D9 |. 8D4424 20 LEA EAX,DWORD PTR SSESP+20] <--你输入的 
004044DD |> 8A10 /MOV DL,BYTE PTR DSEAX] <--得到你输入的注册码的第9 or 11位 
004044DF |. 8ACA |MOV CL,DL <--装入CL 
004044E1 |. 3A16 |CMP DL,BYTE PTR DSESI] <--与正确的注册码的第9 or 11位比较 
004044E3 |. 75 1C |JNZ SHORT LIAOCACH.00404501 <--不对便跳走 
004044E5 |. 3ACB |CMP CL,BL <--看CL是否为空,即看9-12位是否全部比较完毕 
004044E7 |. 74 14 |JE SHORT LIAOCACH.004044FD <--是的话跳走 
004044E9 |. 8A50 01 |MOV DL,BYTE PTR DSEAX+1] <--得到你输入的注册码的第10 or 12位 
004044EC |. 8ACA |MOV CL,DL <--装入CL 
004044EE |. 3A56 01 |CMP DL,BYTE PTR DSESI+1] <--与正确的注册码的第10 or 12位比较 
004044F1 |. 75 0E |JNZ SHORT LIAOCACH.00404501 <--不相等就跳走 
004044F3 |. 83C0 02 |ADD EAX,2 <--EAX加2 
004044F6 |. 83C6 02 |ADD ESI,2 <--ESI加2 
004044F9 |. 3ACB |CMP CL,BL <--看是否全部比较完毕 
004044FB |.^75 E0 /JNZ SHORT LIAOCACH.004044DD <--没有就跳回去再来一遍 
004044FD |> 33C0 XOR EAX,EAX <--9-12位全部比较完毕后会跳到这里 
004044FF |. EB 05 JMP SHORT LIAOCACH.00404506 
00404501 |> 1BC0 SBB EAX,EAX 
00404503 |. 83D8 FF SBB EAX,-1 
00404506 |> 3BC3 CMP EAX,EBX 
00404508 |. 75 41 JNZ SHORT LIAOCACH.0040454B 
0040450A |. 8D7424 48 LEA ESI,DWORD PTR SSESP+48] <--嘿嘿,都讲了三遍了,你一定明白了吧,所以我把这最后一段留给你自己来看吧,从这里到00404530处我相信你猜也猜的出来 (众人:鄙视你!) 
0040450E |. 8D4424 28 LEA EAX,DWORD PTR SSESP+28] 
00404512 |> 8A10 /MOV DL,BYTE PTR DSEAX] 
00404514 |. 8ACA |MOV CL,DL 
00404516 |. 3A16 |CMP DL,BYTE PTR DSESI] 
00404518 |. 75 1C |JNZ SHORT LIAOCACH.00404536 
0040451A |. 3ACB |CMP CL,BL 
0040451C |. 74 14 |JE SHORT LIAOCACH.00404532 
0040451E |. 8A50 01 |MOV DL,BYTE PTR DSEAX+1] 
00404521 |. 8ACA |MOV CL,DL 
00404523 |. 3A56 01 |CMP DL,BYTE PTR DSESI+1] 
00404526 |. 75 0E |JNZ SHORT LIAOCACH.00404536 
00404528 |. 83C0 02 |ADD EAX,2 
0040452B |. 83C6 02 |ADD ESI,2 
0040452E |. 3ACB |CMP CL,BL 
00404530 |.^75 E0 /JNZ SHORT LIAOCACH.00404512 
00404532 |> 33C0 XOR EAX,EAX <--全部通过后来到这里 
00404534 |. EB 05 JMP SHORT LIAOCACH.0040453B 
00404536 |> 1BC0 SBB EAX,EAX 
00404538 |. 83D8 FF SBB EAX,-1 
0040453B |> 3BC3 CMP EAX,EBX 
0040453D |. 75 0C JNZ SHORT LIAOCACH.0040454B 
0040453F |. 8BC7 MOV EAX,EDI 
00404541 |. 5F POP EDI 
00404542 |. 5E POP ESI 
00404543 |. 5D POP EBP 
00404544 |. 5B POP EBX 
00404545 |. 83C4 54 ADD ESP,54 
00404548 |. C2 0400 RETN 4 

我的注释写的还算清楚吧 ^_^,我再大概给你讲解一下: 
软件的注册码是这样计算出来的,机器码中的各个字符的ASCII码加上1500后除以62的余数在密码表中对应的字符,就是相应的注册码。 
比如说我这里的机器码为xn2urkeUMwpNv5xZ,x的ASCII码为78(十进制120) 78+5DC的值为654(即1620) 接着用1620除以3E(62)得商26余8,好的,我们从“密码表”0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ的开始处向后数8下,就会到了8这个字符,嘿嘿,这就是x对应的注册码。 
好的,我给出Delphi的注册机(我仍将其写为函数的形式): 
function KeyGen(Name: String): String; 
var 
String[16]; 
Ptring; 
Keytring; 
i,N,Z:integer; 
begin 
P:='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 
if Length(Name)<16 then 
Result:='机器码必须为16位...' 
else 
begin 
S:=Name; 
for i:=1 to 16 do 
begin 
N:=Ord(S[i]); 
N:=N+1500; 
Z:= N mod 62; 
Z:=Z+1; 
Key:=Key+P[Z]; 
end; 
Result:=Key; 
end; 
end;