我用这些工具,假使你也使用它们。
查壳工具PEID。
动态调戏器OD。
今日从某个网站载来一些书,可是呢?现在的作者忒抠门些,说:“你前面的看好了,不收你钱。”言下之意就是后面的就要RMB了,或者干一些比付钱更加痛苦的事情也可以得到一个阅读码。可我是穷人啊!再说我是cracker啊!自己动手丰衣足食!
越来越习惯于OD的强大,所以,一发现它没有壳,我就用OD开始调戏它了!
本来,一个delphi的程序应该用dede来分析。可是作者尽是用了一些不可视化的表达unit1,unit2等等,所以DD不好分析。我就顺便用DD输出了一个map文件供OD用(输出->ida/softice的map/sym文件,在输出文件下填好路径―>输出文件),OD的loadmap插件载入它,其实这点干与不干一个样。没用!但这是一个好习惯,而且看起软件来更加清楚。这里没用不表示所有地方都没用。你记下了吗?
一载入,就是查找字符串。
/*4891A9*/  MOV EDX,vb经典编.004892DC  
……代表若干行代码
/*48C3E0*/  MOV EDX,vb经典编.0048C494  //  机器码: 
/*48C3E5*/  CALL vb经典编.00403DC4  //->system.@LStrCat3;这是输入map文件后多出来的注释
/*48C3EA*/  LEA EDX,DWORD PTR SS:[EBP-10]
……
/*48C3FC*/  LEA EAX,DWORD PTR SS:[EBP-14]
/*48C3FF*/  MOV ECX,vb经典编.0048C4A8  //-阅读密码
……
/*495017*/  MOV ECX,vb经典编.004950F4  //提示
/*49501C*/  MOV EDX,vb经典编.0049517C  //现在可阅读全部目录了!
/*495021*/  MOV EAX,DWORD PTR DS:[4A0A80]
……
/*49502F*/  PUSH 0
/*495031*/  MOV ECX,vb经典编.004950F4  //提示
/*495036*/  MOV EDX,vb经典编.00495194  //阅读文书密码错误!
还有一些“文件已被非法改动,不能打开!”“异常一”等也告诉我们,可能有自校验。首先当然是爆破试试,因为简单。根据上面的,感觉随便找一点理论上都可以实现爆破。当然在失败之后,我放弃了,你也还是快快放弃吧!毕竟校验不是盖的。我对自己说,还是序列号更现实。
OD载入它,有异常不管,直接Shift+F9,记得吗?很多次之后我们来到的是kernel32的领空。因为是DELPHI写的,那么先请你看如下的文字:
Q:DELPHI、C++ Builder编译的软件用GetWindowText等断点拦不住?
A:这是因为DELPHI通过向Edit发送WM_GETTEXT(直接调用WNDProc,而没有
使用消息函数)消息来获得Text的内容的,整个过程没有调用过任何Win32 API函数。所以常用的GetDlgItemTextA、GetWindowTextA等断点失效是当然的。那么如何才能将用户输入的字符串拷贝到软件的缓冲区中时使OD中断呢?办法有多种,例如用DeDe反编译得到该事件的地址,对此地址设断拦截。
懒人啦,我是啦。
好了,点击最下面那一章。出现一个提示注册的框,如图1

http://bbs.pediy.com/upload/2006/4/image/image001.png_907.png 
图1

输入123789。
好吧!既然我们心爱的GetWindowTextA是不能断了的,那么MessageBoxA还是可以的。真的下断MessageBoxA吗?当然我不会选择MessageBoxA,更好的是用apibreak插件下断万能断点。MessageBoxA要从后面找前面,而万能断点是从前面顺着调试一直走下来!这里要说明一点,有些人看到万能断点出来后有一个API叫CallWindowProcA,就认为可以达到同样的效果,那么在你试过之后就会发现不可以。因为每次选择这个软件它就会被调用,所以根本来不及看到软件的面目就又被OD断下了。还有假如你要断CallWindowProcA,如果你现在直接按ctrl+n的话那么你是不会看见CallWindowProcA的,因为我们现在在的是kernel32的领空,而CallWindowProcA很不好意思的是user32的函数。你可以用命令框,输入bp  CallWindowProcA。或者ctrl+g(转到――>表达式),输入49E12C(这是它的入口点,你可以用stud_pe看看)。现在ctrl+n才可以看见这个函数。
用插件apibreak下万能断点。点击确定,断下后取消万能断点。
/*494FC2*/  CALL vb经典编.0042F9B8
/*494FC7*/  CMP BYTE PTR DS:[EBX+28215],0C7;<――慢慢出来之后在这里,因为刚才的都只是在平衡堆栈而已。这里F2下一个断点。防止待会儿万一程序跑飞又要重新下万能断点。这都是好习惯。
/*494FCE*/  JBE SHORT vb经典编.00494FF2
在这之后气氛就渐渐浓起来了。我们慢慢的F8跟踪。眼睛看着代码窗口,寄存器窗口,数据窗口,信息窗口……
当信息窗口出现如图2所示时,表示接下来有事情要发生了。还不明白,这个一长串不就是图1中所示的机器码吗?看来不远处就会有结果了。

http://bbs.pediy.com/upload/2006/4/image/image003.png_940.png 
图2

往下看
/*495006*/  CALL vb经典编.00403E88
/*49500B*/  JNZ SHORT vb经典编.0049502F
……省略一些无用代码
/*495028*/  CALL vb经典编.0044DEF0
/*49502D*/  JMP SHORT vb经典编.00495047
看到这个不知道你有没有感触上面一个jnz和下面的jmp是相克的,上面的跳走就不会有下面的跳,上面的不跳下面的就跳了。虽然说还有很多的call,但是只有这两个这么有趣的。我们直接运行到49500B。OD的跳转变红显示它要跳,我假设这里是关键跳转。因为我输入的伪码肯定是错的,所以我不能让它从这里跳走。所以我们修改Z标志位(看到Z没有,就在寄存器窗口中)。在Z标志位上双击,代码窗口中本来红色的跳转变成了灰色,说明它不会跳走了,现在我们就直接F9(运行)看看会有什么结果。用图画更加有说服力。如图3

http://bbs.pediy.com/upload/2006/4/image/image005.png_966.png 
图3

这只是成功的第一步。虽然就像它显示的一样现在我们可阅读全部目录了,但是每次都这个操作一次简直就是锻炼我们的耐性。我们再接再厉。
现在我们ctrl+F2从新来过。知道了495006这个是比较关键的call。前面一共还有四个call,其中一个在我们F8调戏的时候明确可以知道这个call是取得伪码,这点很重要。顺便说一下delphi的特征,delphi中经常拿寄存器来传值了,不像vc那样还老是用堆栈。我们分别从下往上来看。其实这个家伙是明码比较的。
/*494FFB*/  CALL vb经典编.0042F9B8;这里出来之后才第一次在信息窗口中看到伪码
/*495000*/  MOV EAX,DWORD PTR SS:[EBP-4C]
/*495003*/  MOV EDX,DWORD PTR SS:[EBP-4]
/*495006*/  CALL vb经典编.00403E88
/*49500B*/  JNZ SHORT vb经典编.0049502F
根据494FFB处这个call的特殊性,我们基本上可以确定了,495006这是一个关键call因为想想也知道要是没有伪码出现就一切都处理好了,那么要注册码何用?如何来判断是否注册呢?所以上面代码中赋给EAX,EDX的值就非常关键了。F8走过之后发现EAX指向的内存地址的值是伪码,而EDX不用说了吧,就是指向注册码的地址了,我这边的值是“,1?_H$h30”引号不包括在内。不难猜想,前面有一些代码是在根据机器码生成重要数据,而下面的一个call呢,则是在进行比较。此刻我们来看看它们是怎么比较的。我们分三块来看这个貌似不起眼的比较。我觉得这里的比较很有趣,所以想谈谈。而且我本来期待着可以找出更多的可用注册码?在仔细分析了这些代码之后我就知道不可能了。
(一)这里我们要关注的是EDX的值的产生。
/*403E9F*/  MOV EAX,DWORD PTR DS:[ESI-4];这是伪码长度赋给EAX
/*403EA2*/  MOV EDX,DWORD PTR DS:[EDI-4];这是真码长度赋给EDX,也就是9。
/*403EA5*/  SUB EAX,EDX;这一句话其实有点致命
/*403EA7*/  JA SHORT vb经典编.00403EAB;JA的意思是高于时跳转,条件是ZF=0且CF=0。注册框最多只允许你输入9个数字。
/*403EA9*/  ADD EDX,EAX;假如上面没有跳,和403EA5这句联合在一起看,达到的效果就是EDX=EAX,而EAX的值在这里也被注定位EAX=EAX-EDX 
/*403EAB*/  PUSH EDX;暂时保存一下待会儿要用。
/*403EAC*/  SHR EDX,2;相当于EDX=EDX  div  4。
/*403EAF*/  JE SHORT vb经典编.00403ED7
(二)
这是一个循环,若跳到循环外面就是死。通常明码比较的方法很多,而下面的这种我还是觉得很好玩。假如判断只有这么一些的话,那么我用“,1?_H$h3”也可以注册成功但是……下面(三)还有一重判断。
/*403EB1*/  MOV ECX,DWORD PTR DS:[ESI];ESI是伪码地址
/*403EB3*/  MOV EBX,DWORD PTR DS:[EDI];EDI是真码地址
/*403EB5*/  CMP ECX,EBX;判断前四位是否相同
/*403EB7*/  JNZ SHORT vb经典编.00403F11
/*403EB9*/  DEC EDX
/*403EBA*/  JE SHORT vb经典编.00403ED1
/*403EBC*/  MOV ECX,DWORD PTR DS:[ESI+4]
/*403EBF*/  MOV EBX,DWORD PTR DS:[EDI+4]
/*403EC2*/  CMP ECX,EBX;判断后四位是否相同
/*403EC4*/  JNZ SHORT vb经典编.00403F11
/*403EC6*/  ADD ESI,8;指针移动
/*403EC9*/  ADD EDI,8;同上,指向了最后一个字符
/*403ECC*/  DEC EDX
/*403ECD*/  JNZ SHORT vb经典编.00403EB1
(三)
/*403ECF*/  JMP SHORT vb经典编.00403ED7
/*403ED1*/  ADD ESI,4
/*403ED4*/  ADD EDI,4
/*403ED7*/  POP EDX;上面有保存的
/*403ED8*/  AND EDX,3
/*403EDB*/  JE SHORT vb经典编.00403EFF;看到这一行我本来还高兴了一下,以为可以构造一个新的注册码,谁知这条语句跳向的是AND EAX,EAX。希望破灭。
/*403EDD*/  MOV ECX,DWORD PTR DS:[ESI]
/*403EDF*/  MOV EBX,DWORD PTR DS:[EDI]
/*403EE1*/  CMP CL,BL;这里是最后的第九位判断是否相同,这里其实有一点技巧了
我们在命令框中输入d esi,看到的是这样的:00E0265C  33 00 00 00由于在内存中数据存储是逆向的,所以其实在处理中这里的值就是00 00 00 30。在d edi,00E02644  30 00 BC C7同上面的道理这里的值就是C7 BC 00 30。故 ECX= 00000030,EBX=C7BC0030。真正是我们输入的伪码只是ECX的低位即CL。所以比较时要两个低位比较。                               
/*403EE3*/  JNZ SHORT vb经典编.00403F26;是否成功就在此一举了
到这里我们也已经把它分析的差不多了。因为注册码是从一开始就躺在那里的,对于这种东西我们直接winhex找内存就可以发现序列号了。如图4

http://bbs.pediy.com/upload/2006/4/image/image007.png_001.png 

图4
这个东西的致命缺点就是明码比较,但要赞叹的一点是防爆破做得很好。