上次写了两篇后,总想也做个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日
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)