上次写了两篇后,总想也做个DELPHI的示范。无奈DELPHI不象VB、C之类的有独立运行库,它的库函数N多,也没有相关的函数库可供调用。所以极不推荐提取DELPHI程序的反 汇编代码做注册机,对于一些库函数可能用逆向的方法来的更快。但是既然曾经说过要弄篇终结篇,还是找了个DELPHI程序来个“霸王硬上弓”。
    这次找了个XX速查手册做为目标(因为正好用到这东西)。
    依旧是先用OD动态分析算法,算法非常简单,取机器码经过几次的MD5得到注册码。这么简单的算法极力推荐自己写,采用提取代码的办法反而复杂化了。所以我这里是在画蛇 添——多次一举:
005C22D6  |.  8B55 F0       MOV EDX,DWORD PTR SS:[EBP-10]            ;  机器码
005C22D9  |.  8BC7          MOV EAX,EDI
005C22DB  |.  E8 34FAFFFF   CALL <DUMPED.sub_5C1D14>                 ;  关键算法
005C22E0  |.  8B95 A0FCFFFF MOV EDX,DWORD PTR SS:[EBP-360]           ;  得到注册码

    每当碰到这样结构的,就是压入机器码,经过一系列的算法得到注册码的,我就觉得用提取代码做注册机的方法最简单,因为我们无需去分析CALL 005C1D14的内容,不管里面 是什么,只要把里面的代码全部拿出来放到注册机中就可以得到注册码。 但是有时候里面的代码特别多,我们还是要进去稍微分析下,以便能做到能少拷就少拷。为了少走冤枉路 ,还是进去看看,果然,很多代码可以不要,跟进CALL 005C1D14:
...
005C1D3F  |.  BB 05000000   MOV EBX,5                                ;  5
005C1D44 >|>  8D4D F8       /LEA ECX,DWORD PTR SS:[EBP-8]
005C1D47  |.  8B55 FC       |MOV EDX,DWORD PTR SS:[EBP-4]            ;  SS:[EBP-4]的初识值是机器码
005C1D4A  |.  8BC6          |MOV EAX,ESI
005C1D4C  |.  E8 1BFEFFFF   |CALL <DUMPED.sub_5C1B6C>                ;  里面含有MD5
005C1D51  |.  8B55 F8       |MOV EDX,DWORD PTR SS:[EBP-8]            ;  MD5算法后的结果
005C1D54  |.  8D45 FC       |LEA EAX,DWORD PTR SS:[EBP-4]
005C1D57  |.  E8 3031E4FF   |CALL <DUMPED.@@LStrLAsg>
005C1D5C  |.  4B            |DEC EBX
005C1D5D  |.^ 75 E5         \JNZ SHORT <DUMPED.loc_5C1D44>           ;  循环5次
005C1D5F  |.  8BC7          MOV EAX,EDI
005C1D61  |.  8B55 FC       MOV EDX,DWORD PTR SS:[EBP-4]             ;  最终的MD5值

...
    这样我们只要提取这几行代码包括CALL 005C1B6C内的内容就行。行动...
    IDA反汇编之,使用DELPHI签名文件识别出DELPHI的库函数。用脚本提取上面部分代码包括CALL 5C1B6C的内容放到注册机模版中,在RADASM中调试,我喜欢用RADASM,它能把 错误列的一清二楚,使用也很方便。
    RADASM列出了错误,  很可怕,很多错误:

D:\masm32\Bin\ML.EXE /c /coff /Cp /nologo /I"D:\masm32\Include" "keygen.asm"
 Assembling: keygen.asm
error A4910: cannot open file: D:\masm32\Bin\ML.err
keygen.asm(97) : error A2006:  : @System@@LStrAddRef$qqrpv
keygen.asm(101) : error A2006:  : sub_5C1D84
keygen.asm(102) : error A2108: 
keygen.asm(103) : error A2108: 
keygen.asm(115) : error A2006:  : @System@@LStrLAsg$qqrpvpxv
keygen.asm(122) : error A2006:  : unknown_libname_46
keygen.asm(128) : error A2108: 
keygen.asm(129) : error A2006:  : loc_5C1D8B
keygen.asm(135) : error A2006:  : @System@@LStrArrayClr$qqrpvi
keygen.asm(173) : error A2006:  : @System@@LStrAddRef$qqrpv
keygen.asm(177) : error A2006:  : s_S4FIi_LxN@
keygen.asm(178) : error A2108: 
keygen.asm(179) : error A2108: 
keygen.asm(190) : error A2006:  : @System@@LStrToPChar$qqrx17System@AnsiString
keygen.asm(209) : error A2006:  : unknown_libname_46
keygen.asm(215) : error A2108: 
keygen.asm(219) : error A2006:  : @System@@LStrArrayClr$qqrpvi
keygen.asm(433) : error A2006:  : unk_63103C
keygen.asm(495) : error A2006:  : s_SFIi_LxN@
keygen.asm(496) : error A2108: 
keygen.asm(497) : error A2108: 
keygen.asm(499) : error A2006:  : @System@@LStrClr$qqrpv
keygen.asm(512) : error A2006:  : byte_63107C
keygen.asm(520) : error A2006:  : byte_63107C
keygen.asm(526) : error A2006:  : @System@@LStrCatN$qqrv
keygen.asm(536) : error A2108: 
keygen.asm(540) : error A2006:  : @System@@LStrArrayClr$qqrpvi
keygen.asm(569) : error A2006:  : @System@@FillChar$qqrpvic
keygen.asm(586) : error A2006:  : unknown_libname_4
keygen.asm(1447) : error A2006:  : unknown_libname_48
keygen.asm(1478) : error A2006:  : sub_4028EC
keygen.asm(1751) : error A2006:  : j_@System@@HandleFinally$qqrv
keygen.asm(1752) : error A2108: 
keygen.asm(1753) : error A2108: 
keygen.asm(1757) : error A2006:  : byte_63204D
keygen.asm(1765) : error A2006:  : dword_6325EC
keygen.asm(1768) : error A2006:  : unk_6325FC
keygen.asm(1771) : error A2006:  : dword_632628
keygen.asm(1778) : error A2006:  : hMem
keygen.asm(1779) : error A2006:  : hMem
keygen.asm(1786) : error A2006:  : hMem
keygen.asm(1793) : error A2006:  : unk_63260C
keygen.asm(1796) : error A2006:  : dword_632618
keygen.asm(1797) : error A2006:  : byte_6325C4
keygen.asm(1805) : error A2108: 
keygen.asm(1806) : error A2006:  : loc_401BD9
keygen.asm(1810) : error A2006:  : byte_63204D
keygen.asm(1973) : error A2006:  : off_62D04C
keygen.asm(1993) : error A2006:  : off_62D048
keygen.asm(2015) : error A2006:  : off_62D044
keygen.asm(2080) : error A2006:  : dword_632618
keygen.asm(2081) : error A2006:  : dword_63261C
keygen.asm(2085) : error A2006:  : dword_632610
keygen.asm(2117) : error A2006:  : SearchSmallBlocks
keygen.asm(2126) : error A2006:  : NewCommit
keygen.asm(2150) : error A2006:  : dword_632620
keygen.asm(2151) : error A2006:  : dword_632620
keygen.asm(2156) : error A2006:  : dword_6325B4
keygen.asm(2158) : error A2006:  : dword_6325B8
keygen.asm(2167) : error A2006:  : DeleteFree
keygen.asm(2178) : error A2006:  : InsertFree
keygen.asm(2205) : error A2006:  : dword_6325B4
keygen.asm(2207) : error A2006:  : dword_6325B8
keygen.asm(2241) : error A2006:  : byte_6325C4
keygen.asm(2260) : error A2108: 
keygen.asm(2261) : error A2108: 
keygen.asm(2262) : error A2006:  : byte_63204D
keygen.asm(2284) : error A2006:  : @System@SysGetMem$qqri
keygen.asm(2305) : error A2006:  : unknown_libname_4
keygen.asm(2308) : error A2006:  : @System@SysFreeMem$qqrpv
keygen.asm(2320) : error A2108: 
keygen.asm(2325) : error A2006:  : byte_63204D
keygen.asm(2338) : error A2006:  : @System@@HandleFinally$qqrv
keygen.asm(2420) : error A2006:  : DeleteBlock
keygen.asm(2452) : error A2006:  : AddBlockAfter
keygen.asm(2549) : error A2006:  : dword_632620
keygen.asm(2553) : error A2006:  : dword_632620
keygen.asm(2555) : error A2006:  : dword_63261C
keygen.asm(2556) : error A2006:  : dword_63261C
keygen.asm(2560) : error A2006:  : dword_632620
keygen.asm(2562) : error A2006:  : dword_63261C
keygen.asm(2576) : error A2006:  : DeleteFree
keygen.asm(2590) : error A2006:  : InternalFreeMem
keygen.asm(2607) : error A2006:  : dword_632620
keygen.asm(2610) : error A2006:  : dword_63261C
keygen.asm(2615) : error A2006:  : dword_63261C
keygen.asm(2617) : error A2006:  : dword_632620
keygen.asm(2618) : error A2006:  : dword_63261C
keygen.asm(2621) : error A2006:  : dword_63261C
keygen.asm(2622) : error A2006:  : dword_632620
keygen.asm(2623) : error A2006:  : dword_63261C
keygen.asm(2625) : error A2006:  : dword_63261C
keygen.asm(2631) : error A2006:  : dword_6325B8
keygen.asm(2642) : error A2006:  : FreeCurAlloc
keygen.asm(2669) : error A2006:  : DeleteFree
keygen.asm(2679) : error A2006:  : InsertFree
keygen.asm(2705) : error A2006:  : NewCommitAt
keygen.asm(2729) : error A2006:  : dword_6325B8
keygen.asm(2793) : error A2006:  : DeleteBlock
keygen.asm(2809) : error A2006:  : DeleteBlock
keygen.asm(2823) : fatal error A1012: 

构建时发生错误.
总共编译时间 4031 毫秒

 
    不要害怕,  这里有很多是重复的,我们来了解一下这些错误分别是什么。在这之前,先了解一点点PE知识:一些伪指令和预定义段。
.386
这是一个汇编语言伪指令,他告诉编译器我们的程序是使用80386指令集编写的。您还可以使用 .486、.586, 但最安全的还是使用.386。对于每一种CPU有两套几乎功能相同伪指 令: .386/.386P、 486/.486P、 586/.586P。 带P的指令标明您的程序中可以用特权级指令。特权级指令是保留给操作系统的,如虚拟设备驱动程序。在大多数时间,您的程序都 无须运行在RING0层,故用不带后缀P的伪指令已足够了。
.MODEL FLAT,STDCALL 
.MODEL 是用来指定内存模式的伪指令,在Win32下,只有一种内存模型,那就是FLAT。 STDCALL 告诉编译器参数的传递约定。参数的传递约定是指参数传达时的顺序(从左到右或 从右到左)和由谁恢复堆栈指针(调用者或被调用者)。在Win16下有两种约定:C 和 PASCAL。C 约定规定参数传递顺序是从右到左,即最右边的参数最先压栈,由调用者恢复堆栈指 针。
.DATA 其中包括已初始化的数据。
.DATA? 其中包括未初始化的数据。比如有时您仅想预先分配一些内存但并不想指定初始值。使用未初始化的数据的优点是它不占据可执行文件的大小,如:若您要在 .DATA? 段中 分配10,000字节的空间,您的可执行文件的大小无须增加10,000字节,而仅仅是要告诉编译器在装载可执行文件时分配所需字节。
.CONST 其中包括常量定义。这些常量在程序运行过程中是不能更改的。 应用程序并不需要以上所有的三个"分段", 可以根据需要进行定义。 
.CODE 这是代码"分段"
<label> 
end <label> 
是用来唯一标识您的代码范围的标签, 两个标签必须相同,应用程序的所有可执行代码必修在两个标签之间。

预定义段 

   一个Windows NT的应用程序典型地拥有9个预定义段,它们是.text、.bss、.rdata、.data、.rsrc、.edata、.idata、.pdata和.debug。一些应用程序不需要所有的这些段, 同样还有一些应用程序为了自己特殊的需要而定义了更多的段。这种做法与MS-DOS和Windows 3.1中的代码段和数据段相似。事实上,应用程序定义一个独特的段的方法是使用标准 编译器来指示对代码段和数据段的命名,或者使用名称段编译器选项-NT——就和Windows 3.1中应用程序定义独特的代码段和数据段一样。 
   以下是一个关于Windows NT PE文件之中一些有趣的公共段的讨论。 

.text   可执行代码段
  Windows 3.1和Windows NT之间的一个区别就是Windows NT默认的做法是将所有的代码段(正如它们在Windows 3.1中所提到的那样)组成了一个单独的段,名为“.text”。既 然Windows NT使用了基于页面的虚拟内存管理系统,那么将分开的代码放入不同的段之中的做法就不太明智了。因此,拥有一个大的代码段对于操作系统和应用程序开发者来说, 都是十分方便的。
  .text段也包含了早先提到过的入口点。IAT亦存在于.text段之中的模块入口点之前。(IAT在.text段之中的存在非常有意义,因为这个表事实上是一系列的跳转指令,并且它 们的跳转目标位置是已固定的地址。)当Windows NT的可执行映像装载入进程的地址空间时,IAT就和每一个导入函数的物理地址一同确定了。要在.text段之中查找IAT,装载器只 用将模块的入口点定位,而IAT恰恰出现于入口点之前。既然每个入口拥有相同的尺寸,那么向后退查找这个表的起始位置就很容易了。

.bss、.rdata、.data  数据段
  .bss段表示应用程序的未初始化数据,包括所有函数或源模块中声明为static的变量。
  .rdata段表示只读的数据,比如字符串文字量、常量和调试目录信息。
   所有其它变量(除了出现在栈上的自动变量)存储在.data段之中。基本上,这些是应用程序或模块的全局变量。

.rsrc  资源段
  .rsrc段包含了模块的资源信息。它起始于一个资源目录结构,这个结构就像其它大多数结构一样,但是它的数据被更进一步地组织在了一棵资源树之中。

.edata 导出数据段
   .edata段包含了应用程序或DLL的导出数据。在这个段出现的时候,它会包含一个到达导出信息的导出目录。 

.idata 导入数据段
   .idata段是导入数据,包括导入库和导入地址名称表。

.debug 调试信息段
   调试信息位于.debug段之中,同时PE文件格式也支持单独的调试文件(通常由.DBG扩展名标识)作为一种将调试信息集中的方法。调试段包含了调试信息,但是调试目录却位 于早先提到的.rdata段之中。

    另外我们需要了解下IDA反汇编PE文件后所加的那些伪指令分别代表些什么:
byte_XXXXXX         单字节型常量或变量
word_XXXXXX         2字符型常量或变量
dword_XXXXXX        4字节型常量或变量  
QWORD_XXXXXX        8字节型常量或变量 
TWORD_XXXXXX        10字节型常量或变量 
NEAR                标号作段内转移指令的目标地址,NEAR必须借助PTR才能指示无条件转移指令目标地址的属性
FAR                 标号作段间转移或调用指令的目标地址
SHORT可以不必借助PTR直接指示无条件转移指令目标地址属性为段内短转移
off_XXXXXX          是数据标号的偏移地址
unk_XXXXXX          变量地址
s_XXXXXX            字符串
sub_XXXXXX          子程序
loc_XXXXXX          子程序内部标号

    对于fs:,MASM在.model flat模式自动做了下面的定义:
    ASSUME CS:flat,       DS:flat,       ES:flat,       SS:flat,       FS:ERROR,GS:ERROR
    flat是内存平坦模式,意思是段寻址4G空间。因此,CS,DS,ES,SS可以在程序中平坦使用。使用FS和GS则会报错。所以需要做如下定义:
    assume  fs:nothing  


    这样我们就知道那些错误分别是什么,大部分是指定的常量或变量未初始化,有的是DELPHI的库函数不能识别,有的是找不到子程序,子程序还需要提取等...
    一般的做法都是在IDA中找相关的错误源,在IDA中按快捷键G,输入XXXXXX查看相关内容,是数据的,拷贝数据到MASM的数据区,是子程序的,用脚本提取代码放在MASM的代码 段。先解决数据错误,再解决代码错误,你所要做的就是手快眼快,不停的拷贝,不停的在RADASM中编译测试。
    IDA不是万能的,也有出错的时候,因为是静态反汇编,所以有时候将连续的跳转之类的反汇编成字符串,就向本例中的:
0040EF3A  |.  68 4FEF4000   PUSH 40EF4F
0040EF3F >|>  8D45 F4       LEA EAX,DWORD PTR SS:[EBP-C]             ;  loc_40EF3F
0040EF42  |.  E8 AD5EFFFF   CALL <DUMPED.@@LStrClr>
0040EF47  \.  C3            RETN
0040EF48 > .^ E9 EB57FFFF   JMP <DUMPED.@@HandleFinally>             ;  loc_40EF48
0040EF4D   .^ EB F0         JMP SHORT <DUMPED.loc_40EF3F>
0040EF4F > .  8BC3          MOV EAX,EBX                              
0040EF51   .  5B            POP EBX
0040EF52   .  8BE5          MOV ESP,EBP
0040EF54   .  5D            POP EBP
0040EF55   .  C3            RETN

在IDA中变成这样:
CODE:0040EF3A                 push    offset aLLxLUlQsvwlGWb ; "?[?]?勒?QSVW?\b?熳∧'c"
CODE:0040EF3F
CODE:0040EF3F loc_40EF3F:                             ; CODE XREF: CODE:0040EF4Dj
CODE:0040EF3F                 lea     eax, [ebp+var_C]
CODE:0040EF42                 call    @@LStrClr
CODE:0040EF47                 retn
CODE:0040EF48 ; ---------------------------------------------------------------------------
CODE:0040EF48
CODE:0040EF48 loc_40EF48:                             ; DATA XREF: sub_40EEE4+Fo
CODE:0040EF48                 jmp     @@HandleFinally
CODE:0040EF48 sub_40EEE4      endp
CODE:0040EF48
CODE:0040EF4D ; ---------------------------------------------------------------------------
CODE:0040EF4D                 jmp     short loc_40EF3F
CODE:0040EF4D ; ---------------------------------------------------------------------------
CODE:0040EF4F aLLxLUlQsvwlGWb db '?[?]?勒?QSVW?',8,'?熳∧',27h,'c',0
CODE:0040EF4F                                         ; CODE XREF: sub_40EFD0+93p
CODE:0040EF4F                                         ; sub_40EFD0+145p
CODE:0040EF4F                                         ; DATA XREF: ...
  
    直接将0040EF48-0040EF55反汇编成字符串“?[?]?勒?QSVW?',8,'?熳∧',27h,'c'”,该例中这样的代码不止一处,所以这样的结构都被弄成了字符串。
    恢复的话,切换到OD拷贝相关代码就行。要注意标号和名称。

    ...所有拷贝过程省略...

    等代码完全没有错误后进入调试。
    代码在MASM中通过,并不代表程序没有问题,在动态执行中还涉及到对内存的读写,往往会出现内存不能读写的错误。这就需要在RADASM中用OD对程序进行进一步调试,找到 错误源。
    非常遗憾,我没有调试成功。因为在核心代码的前面部分有几处对内存写入的操作,想提取那些代码,但是扩散开来却是很多的一部分代码,我也懒的去做,教程在遗憾中结 束。同时我们看到了这种方法做注册机的局限性,庞大的垃圾代码就是最大的“杀点”,但是对于从未学过高级语言,或者说想写注册机却一直不会的,这种方法未尝可以尝试。

    该软件算法很简单,所以看教程就当“走马看花”,千万不要笑出声来。  附简单的注册机代码:
Generate proc hWnd:HWND
  invoke GetDlgItemText,hWnd,IDC_NAME,addr NameBuffer,40 
  
  mov  ebx, 5
loc_5C1D44:        ; CODE XREF: sub_5C1D14+49j
  invoke MD5hash,offset NameBuffer,eax,offset hash    
  invoke lstrcpynA,offset hash,offset hash,33
  invoke RtlZeroMemory,addr NameBuffer,40
  invoke lstrcpy,offset NameBuffer,offset hash
  invoke lstrlen,offset NameBuffer
  lea     edx, hash
  dec  ebx
  jnz  short loc_5C1D44  
  mov     esi, edx
        mov     edi, offset SerialBuffer

@@:
  lodsw
        test    ax, ax
        jz      @f
        stosw
        jmp @b
        
@@:
  invoke SetDlgItemText,hWnd,IDC_SERIAL,addr SerialBuffer
  xor eax,eax
        ret
      
Generate endp

    另外,也附上提取的代码,供“好事者”看看。
    最后说一下这个软件,网上下载的都是被作者抹掉部分功能的限制版,所以注册不起作用。正式版注册是这样的,软件第一次启动后在安装目录创建一个以C盘序列号第2位开 始的3位作为文件名的DLL程序,取主硬盘序列号作为机器码,循环5次MD5值为注册码。所以下断GetVolumeInformationA很容易找到关键处。 
 

--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2007年1月10日