首页 > 学院 > 逻辑算法 > 正文

第五节 脱壳高级篇

2019-09-10 09:02:25
字体:
来源:转载
供稿:网友

 
1、认识Import表
著者: [yAtEs] [Jamesluton@hotmail.com]
译者:hying[CCG]
标题:PE输入表说明
例子:下载
有很多介绍PE文件的文章,但是我打算写一篇关于输入表的文章,因为它对于破解很有用。
我想解释它的最好的方法是举一个例子,你可以跟着我逐步深入,一步一步的思考,最后你将完全明白,我选择了一个我刚下载下来的小程序,它是用TASM编译的,有一个比较小的输入表,所以我想它应该是个不错的范例。
好了,让我们开始吧。首先我们得找到输入表,它的地址放在PE文件头偏移80处,所以我们用16进制编辑器打开我们的EXE文件,我们先得找到PE文件头的起始点,这很简单,因为它总是以PE00开始,我们可以在偏移100处找到它。在一般的WIN32程序中文件头偏移被放在文件0X3C处,在那我们通常可看到00 01 00 00,由于数据存储时是低位在前,高位在后的,所以翻转过来实际就是00000100,就象前面我们说的。接下来我们就可以在PE文件中找到我们的输入表,100+80=180在偏移180处我们看到0030 0000,翻转一下,它其实应该是00003000,这说明输入表在内存3000处,我们必须把它转换成文件偏移。
一般来说,输入表总是在某个段的起始处,我们可以用PE编辑器来查看虚拟偏移,寻找3000并由此发现原始偏移。很简单的。打开我们看到:
-CODE 00001000 00001000 00000200 00000600
-DATA 00001000 00002000 00000200 00000800
.idata 00001000 00003000 00000200 00000A00
.reloc 00001000 00004000 00000200 00000C00
找一下,我们就发现.idata段的虚拟偏移是3000,原始偏移是A00,3000-A00=2600,我们要记住2600,以便以后转换其它的偏移。如果你没找到输入表的虚拟偏移,那么就找一下最接近的段。
来到偏移A00处,我们就看到被称为IMAGE_IMPORT_DESCRIPTORs(IID)的东东,它用5个字段表示每一个被调用DLL的信息,最后以Null结束。
**************************************************************************
(IID) IMAGE_IMPORT_DESCRIPTOR的结构包含如下5个字段:
OriginalFirstThunk TimeDateStamp ForwarderChain Name FirstThunk


OriginalFirstThunk
该字段是指向一32位以00结束的RVA偏移地址串,此地址串中每个地址描述一个输入函数,它在输入表中的顺序是不变的。

TimeDateStamp
一个32位的时间标志,有特殊的用处。

ForwarderChain
输入函数列表的32位索引。

Name
DLL文件名(一个以00结束的ASCII字符串)的32位RVA地址。

FirstThunk
该字段是指向一32位以00结束的RVA偏移地址串,此地址串中每个地址描述一个输入函数,它在输入表中的顺序是可变的。
**************************************************************************

好了,你有没有理解?让我们看看我们有多少IID,它们从偏移A00处开始
3C30 0000 / 0000 0000 / 0000 0000 / 8C30 0000 / 6430 0000
{OrignalFirstThunk} {TimeDateStamp} {ForwardChain} {Name} {First Thunk}
5C30 0000 / 0000 0000 / 0000 0000 / 9930 0000 / 8430 0000
{OrignalFirstThunk} {TimeDateStamp} {ForwardChain} {Name} {First Thunk}
0000 0000 / 0000 0000 / 0000 0000 / 0000 0000 / 0000 0000

每三分之一是个分界,我们知道每个IID包含了一个DLL的调用信息,现在我们有2个IID,所以我们估计这个程序调用了2个DLL。甚至我可以打赌,你能推测出我们将能找到什么。
每个IID的第四个字段表示的是名字,通过它我们可以知道被调用的函数名。第一个IID的名字字段是8C30 0000,翻转过来也就是地址0000308C,将它减去2600可以得到原始偏移,308C-2600=A8C,来到文件偏移A8C处,我们看到了什么?啊哈!原来调用的是KERNEL32.dll。
好了,接下来我们就要去找出KERNEL32.dll中被调用的函数。回到第一个IID。
FirstThunk字段包含了被调用的函数名的标志,OriginalFirstThunk仅仅是FirstThunk的备份,甚至有的程序根本没有,所以我们通常看FirstThunk,它在程序运行时被初始化。
KERNEL32.dll的FirstThunk字段值是6430 0000,翻转过来也就是地址00003064,减去2600得A64,在偏移A64处就是我们的IMAGE_THUNK_DATA,它存储的是一串地址,以一串00结束。如下:
A430 0000/B230 0000/C030 0000/CE30 0000/DE30 0000/EA30 0000/F630 0000/0000 0000
通常在一个完整的程序里都将有这些。我们现在有了7个函数调用,让我们来看其中的两个:
DE30 0000翻转后是30DE,减去2600后等于ADE,看在偏移ADE处的字符串是ReadFile,
EA30 0000翻转后是30EA,减去2600后等于AEA,看在偏移AEA处的字符串是WriteFile,
你可能注意到了,在函数名前还有2个这字节的00,它是被作为一个提示。
很简单吧,你可以自己来试一下。回到A00,看第二个DLL的调用
5C30 0000 / 0000 0000 / 0000 0000 / 9930 0000 / 8430 0000
{OrignalFirstThunk} {TimeDateStamp} {ForwardChain} {Name} {First Thunk}
先找它的DLL文件名。9930翻转为3099-2600 =A99,在偏移A99处找到USER32.dll。再看FirstThunk字段值:8430翻转为3084-2600=A84,偏移A84处保存的地址为08310000,翻转后3108-2600=B08,偏移B08处字符串为MessageBoxA。明白了吧,接下来你就可以把这些用在你自己的EXE文件上了。
摘要:
在PE文件头+80偏移处存放着输入表的地址,输入表包含了DLL被调用的每个函数的函数名和FirstThunk,通常还有Forward Chain和TimeStamp。
当运行程序时系统调用GetProcAddress,将函数名作为参数,换取真正的函数入口地址,并在内存中写入输入表。当你对一个程序脱时你可能注意到你有了一个已经初始化的FirstThunk。例如,在我的WIN98上,函数GetProcAddress的入口地址是AE6DF7BF,在98上,所有的KERNEL32.dll函数调用地址看上去地址都象:xxxxF7BF,如果你在输入表中看到这些,你可以利用orignal thunk重建它,或者重建这个PE程序。
好了,我已经告诉你它们是如何工作的,我不是专家,如果你发现什么错误,请告诉我。

2、Import表的重建

标题:重建 PE 文件的输入表

原著:TiTi/BLiZZARD

翻译:Sun Bird [CCG]

日期:2000年5月24日


1. 前言
=======

大家好 :) 我之所以写这篇短文,是由于我在 Dump 时发现,很多加压、加密软件都使
得输入表(Import Table)不可用,所以 Dump 出的可执行文件必须要重建输入表。而在普
通的讲授 Win32 汇编的站点上我没有找到这样的介绍,所以如果你对此感兴趣,那么这篇
短文对你会有些帮助。

例如,为了让从内存中 Dump 出的经 PETite v2.1 压缩过的可执行文件正常运行,必
须重建输入表。(对于 ASPack、PEPack、PESentry……也同样)这就是所有 Dump 软件
具备重建输入表功能的原因(例如 G-RoM/UCF 制作的 Phoenix Engine(ProcDump 内含),
或者由 Virogen/PC 和我制作的 PE Rebuilder)。

鉴于这个问题十分特殊,而且比较复杂,所以我假定你已经了解了 PE 文件结构。(你
需要阅读有关 PE 文件的文档)

2. 预备知识
===========

首先是一些关于输入表和 RVA/VA 的简介。

输入表的相对虚拟地址(RVA)储存在 PE 文件头部的相应目录入口(它的偏移量为
[ PE 文件头偏移量+80h ])。由于是虚拟偏移量,所以它和文件输入表中的偏移量(VA)
是不匹配的(除非文件纯粹是刚刚从内存中 Dump 出来的)。于是我们首先要做的事情是,
找到 PE 文件的输入表,将 RVA 转换为相应的 VA。为此,我们可以采用不同的办法:你可
以自行编制软件来分析块(Sections)目录并计算 VA,但最简单的办法是使用专门为此设
计的应用程序接口(API)。这个 API 包括在 IMAGEHLP.DLL(Win9X 和 NT 系统都使用的
一个库)中,名为 ImageRvaToVa。下面是对它的描述(完整的内容详见 MSDN 库):

# LPVOID ImageRvaToVa(
# IN PIMAGE_NT_HEADERS NtHeaders
# IN LPVOID Base
# IN DWORD Rva
# IN OUT PIMAGE_SECTION_HEADER *LastRvaSection
#);
#
# 参数:
#
# NtHeaders
#
# 指示一个 IMAGE_NT_HEADERS 结构。通过调用 ImageNtHeader 函数可以获得这个结构。
#
# Base
#
# 指定通过调用 MapViewOfFile 函数映射入内存的一个映象的基址(Base Address)。
#
# Rva
#
# 指定相对虚拟地址的位置。
#
# LastRvaSection
#
# 指向一个指定的最终 RVA 块的 IMAGE_SECTION_HEADER 结构。这是一个可选参数。当被
#指定时,它指向一个变量,该变量包含指定映象的最后块值,以便将 RVA 转换为 VA。

就这么简单。你只需要将 PE 文件映射入内存,然后调用这个函数就能够得到输入表的正
确 VA。

注意,下面我会忽略所有的 RVA/VA 注释,但是,当你对重建的 PE 文件进行读出或写入
RVAs 操作时,不要忘记它们之间的转换。

3. 完整说明
===========

这里是一个完整改变输入表的例子(这个 PE 文件的输入表已经被 PETite v2.1 压缩过,
并且是直接从内存中 Dump 出来的):

我们用“`”表示 00,用“-”表示非字符串

0000C1E8h : 00 00 00 00 00 00 00 00 00 00 00 00 BA C2 00 00 ````````````----
0000C1F8h : 38 C2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ----````````````
0000C208h : C5 C2 00 00 44 C2 00 00 00 00 00 00 00 00 00 00 --------````````
0000C218h : 00 00 00 00 D2 C2 00 00 54 C2 00 00 00 00 00 00 ````--------````
0000C228h : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ````````````````
0000C238h : 7F 89 E7 77 4C BC E8 77 00 00 00 00 E6 9F F1 77 --------````----
0000C248h : 1A 38 F1 77 10 40 F1 77 00 00 00 00 4F 1E D8 77 --------````----
0000C258h : 00 00 00 00 00 00 4D 65 73 73 61 67 65 42 6F 78 ``````MessageBox
0000C268h : 41 00 00 00 77 73 70 72 69 6E 74 66 41 00 00 00 A```wsprintfA```
0000C278h : 45 78 69 74 50 72 6F 63 65 73 73 00 00 00 4C 6F ExitProcess```Lo
0000C288h : 61 64 4C 69 62 72 61 72 79 41 00 00 00 00 47 65 adLibraryA````Ge
0000C298h : 74 50 72 6F 63 41 64 64 72 65 73 73 00 00 00 00 tProcAddress````
0000C2A8h : 47 65 74 4F 70 65 6E 46 69 6C 65 4E 61 6D 65 41 GetOpenFileNameA
0000C2B8h : 00 00 55 53 45 52 33 32 2E 64 6C 6C 00 4B 45 52 ``USER32.dll`KER
0000C2C8h : 4E 45 4C 33 32 2E 64 6C 6C 00 63 6F 6D 64 6C 67 NEL32.dll`comdlg
0000C2D8h : 33 32 2E 64 6C 6C 00 00 00 00 00 00 00 00 00 00 32.dll``````````

正如你看到的,这个输入表被分成三个主要部分:

- C1E8h - C237h:IMAGE_IMPORT_DESCRIPTOR 结构部分,对应着每一个需要输入的动态
链接库(DLL)。这部分以关键字 00 结束。

IMAGE_IMPORT_DESCRIPTOR struct
OriginalFirstThunk dd 0 ;原拆分 IAT 的 RVA
TimeDateStamp dd 0 ;没有使用
ForwarderChain dd 0 ;没有使用
Name dd 0 ;DLL 名字符串的 RVA
FirstThunk dd 0 ;IAT 部分的 RVA
IMAGE_IMPORT_DESCRIPTOR ends

- C238h - C25Bh:这部分双字(DWord) 称作“IAT”,由 IMAGE_IMPORT_DESCRIPTOR
结构中的 FirstThunk 部分指明。这部分每一个 DWord 对应一个输入函数。

- C25Ch - C2DDh : 这里是输入函数和 DLL 文件的名称。问题是,这些是没有规定顺序
的:有时候 DLL 文件在函数前面,有时候正好相反,另外一些时候它们混在一起。

输入表的简介
------------

OriginalFirstThunk 是 IAT 的一部分,它是 PE 文件引导时首先要搜索的。如果存在,PE
文件的引导部分将使用它来纠正在 FirstThunk IAT 部分的问题。当调入内存后,FirstThunk
的每一个 Dword (包含有函数名字符串的 RVA),将被 RVA 替换为函数的真实地址(当调用这
些函数时,它们调入内存的位置将被执行)。所以,只要 OriginalFirstThunk 没有被改变,基
本上这里不存在输入表的问题。 下面来看我们的问题
------------------

好了,经过简单描述后,下面来看我们的问题。如果你试图运行包含上面显示的输入表的可
执行文件,它不会被调入,Windows 会显示一个错误信息。为什么?很简单,因为
OriginalFirstThunk 被删除了。事实上,你应该注意到,在这个输入表的每一个IMAGE_IMPORT_DESCRIPTOR 结构,OriginalFirstThunk 的内容都是 00000000h。嗯,所以我们
可以推测出,当我们运行这个可执行程序时,PE 文件的引导部分试图从 FirstThunk 部分获得
输入函数的名字。但是,正象你注意到的,这部分根本没有包含函数名字符串的 RVA,但是函数
地址的 RVA 在内存中。

我们需要怎么做
--------------

现在,为了让这个可执行文件运行,我们需要重建 FirstThunk 部分的内容,让它们指向我
们在输入表第三部分看到的函数名字符串。这不是一项很困难的任务,但是,我们需要知道哪个
IAT 对应哪个函数,而函数字符串和 FirstThunk 内容并不采用同样的存储方法。所以,对于每
一个 IAT,我们需要验证它对应的是哪个函数名(事实上,根据 IMAGE_IMPORT_
DESCRIPTOR.Name DWord 我们已经有了 DLL 名称,这些并没有被改变)。

如何验证每一个函数
------------------

正向我们上面所见到的,在内存中,每一个被破坏的 IAT 都有一个函数地址的 RVA。这些
地址并没有被破坏,所以,我们只要重新找回指向错误 IAT 的函数地址,把它们指向函数名字
符串。
为此,在 Kernel32.dll 中有一个非常有用的 API:GetProcAddress。它允许你得到给定函
数的地址。这里是它的描述:

GetProcAddress(

HMODULE hModule // DLL 模块的句柄
LPCSTR lpProcName // 函数名
);

所以,对于每一个被破坏的 IAT,在 GetProcAddress 返回我们寻找的函数地址之前,只需
要分析包含在输入表第三部分的所有函数名。

- hModule 参数是 DLL 模块的句柄(也就是说,模块映象在内存中的基址),我们可以通
过 GetModuleHandleA API 得到:

HMODULE GetModuleHandle(
LPCTSTR lpModuleName // 返回模块名地址句柄
);

(lpModuleName 只需要指向我们从 IMAGE_IMPORT_DESCRIPTOR.Name 部分得到的 DLL 文件
名字符串)

- lpProcName 仅指向函数名字符串。

注意,有时候函数是按序号输入的。这些序号是在每个 [ 函数名偏移量-2 ] 处的单字(WORDS)。
所以,你在分析程序时需要检查函数是按名称还是按序号输入的。

使用上面输入表的实例
--------------------

针对上面输入表的例子,我将说明如何修复第一个输入 DLL 的第一个输入函数。

1. 我们来看第一个 IMAGE_IMPORT_DESCRIPTOR 结构部分(C1E8h),.Name 部分(C1E4h,指向
C1BAh)指出了 DLL 名。我们看到,那是 USER32.dll。

2. 我们来看 .FirstThunk 部分,它们指向 IAT 部分;每个对应一个这个 DLL(user32.dll)的
输入函数。在这里是 C1F8h,指向 C238h。所以,在 C238h,我们可以修复被破坏的 IATs。(你
会注意到,这个 IAT 部分包含二个 DWords,所以,这个 DLL 有二个函数输入)

3. 我们得到了第一个被破坏的 IAT。它的值是 77E7897Fh。这是函数在内存中的地址。

4. 对每一个输入表第三部分中的函数,我们调用 GetProcAddress API。当该 API 返回 7E7897Fh
时就意味着,我们到达了正确的函数。所以我们让被破坏的 IAT 指向正确函数名(在本例中为 'wsprintfA')。

5. 现在我们只需要将 IAT 指向:偏移量(函数名字符串)-2。为什么是 -2 ?因为有时候使用了
函数序列。
所以在本例中,我们改变地址 C238h,让它指向 C26Ah(以代替 77E7897Fh)。

6. 就这样,这个函数被修复了,下面你只需要对所有的 IATs 重复这个过程就可以了。

后记
----

我描述的是一般的操作过程。当然只有在 DLLs 被正常调入内存后才能够这样做。对于其他情
况,你需要将它们调入,或者你需要仔细研究它们的输出表才能找到正确的函数地址。

3、IceDump和NticeDump使用

  IceDump和NticeDump是一款配合SoftICE扩展其内存操作的工具,IceDump支持Windows 9x、Windows Millennium系统,NticeDump支持Windows NT/2000。它们的出现,使SoftICE如虎添翼,TRW2000的许多特色功能在SoftICE里也可实现了。

1.Icedump操作简介
  运行IceDump前,首先要确定SoftICE版本号,按Ctrl+D切换到SoftICE下命令:VER,查看版本号。然后在相应SoftICE版本号目录下运行icedump.exe文件,它会调用自身的VXD文件,装载成功出现图7.16所示画面。如果发现SoftICE没运行或版本不符,就拒绝运行。 如果想从内存中卸载它,可以在DOS下键入"icedump u"。

1)/DUMP <起始地址> [<长度> <文件名>]
  抓取内存中的数据到文件里,类似TRW2000中的W命令。<文件名>参数可以指定盘符和路径,当在Ring-0下还原时最好清除还原区域内的全部断点,否则会给SoftICE带来不必要麻烦。(这一点在所有的IceDump命令里都应该值得注意)
在Win32系统下读者可能会想到用/BHRAMA或/PEDUMP从内存内中重建一个可用的PE镜像。请看下面关于/OPTION命令的说明。

注意: IceDump 6.015以前类似的命令是PAGEIN D <address> [<length> <200456124836.htm>]

2)/LOAD <地址> <长度> <文件名>
  把<文件>指定长度的字节内容调入到内存中的<地址>处。与/DUMP的作用相反,同样需要注意的是不要设置断点。

3)/BHRAMA <Bhrama dumper server 窗口名>
  用Procdump的Bhrama(由G-Rom出品的著名脱工具)来初始化dumping。用户必须提供窗口的名称,可以从标题条找到它。为了使工作简单化,可以在winice.dat里设置F3键:
F3="/BHRAMA ProcDump32 - Dumper Server;"

4) /TRACEX <low EIP> [<high EIP>]
  控制跟踪器并退出SoftICE。注意该命令只能用于跟踪当前线程,如果要跟踪其它线程,请使用/TRACE命令。


  /TRACEX <low EIP>: 跟踪当前线程。注意,如果跟踪当前线程弹出SoftICE窗口后想继续跟踪,必须使用/TRACEX命令,否则跟踪器会失去对当前线程的控制。
   当线程的EIP到达<low EIP>时,跟踪停止并弹出SoftICE窗口。这也要求EIP真正可以到达,否则SoftICE不会弹出。


  /TRACEX <low EIP> <high EIP> 跟踪当前线程,注意事项同上。
  当线程的EIP到达<low EIP>与<high EIP>之间的区域内时停止并弹出SoftICE窗口。注意这里没有作<low EIP>和<high EIP>的边界检查,所以错误的参数地址会使SoftICE不能中断。

5) /SCREENDUMP [<文件名>]
  把SoftICE屏幕内容保存到一个文件中。注意该功能只支持通用显示驱动模式。这个命令的用法类似于/DUMP,如果没有指定<文件名>,IceDump将在模式0、1、2、3和4中切换。
  模式1:默认模式,将以ASCII格式输出。
  模式0:字节属性也将被抓取。
  模式2:可以把屏幕内容保存成一个HTML文件。
  模式3:会把屏幕内容保存成LaTeX格式的文件。
  模式4 :把屏幕内容保存为EPS (encapsulated Postscript)格式。

2.NticeDump操作简介
  Nticedump远不如IceDump功能强大,并且Nticedump装载方式不同于IceDump,它是通过给SoftICE打补丁来实现0特权级控制权的,这是因为在Windows 2000上,要切换到0特权级不象Windows9x那么容易了。
  要打补丁的文件是/WINNT/SYSTEM32/DRIVERS/Ntice.sys,在Nticedump目录里有一补丁工具ntid.exe,把安装目录下相应SoftICE版本的Icedump文件与ntid.exe一同复制到/WINNT/SYSTEM32/DRIVERS/目录下,然后运行ntid.exe程序就能正确补丁Ntice.sys。这样Nticedump和SoftICE就完全结合了。

1) 抓取内存数据:PAGEIN D 基地址 长度 文件名
例: PAGEIN D 400000 512 /??/C:/memory.dmp


注意: 在NT输入输出管理系统中,象"C:/memory.dmp"不是合法路径。"/??/C:/200456124836.htm.dmp"是在C盘根目录下创建"200456124836.htm.dmp"文件。

2) 抓取进程: PAGEIN B <Bhrama窗口名>
例: PAGEIN B ProcDump32 - Dumper Server

3) 导入文件: PAGEIN L 基地址 长度 文件名
Example: PAGEIN L 400000 512 /??/C:/memory.dmp


4) 帮助: PAGEIN 例: PAGEIN

 4、Import REConstructor使用

   Import REConstructor可以从杂乱的IAT中重建一个新的Import表(例如加软件等),它可以重建Import表的描述符、IAT和所有的ASCII函数名。用它配合手动脱,可以脱UPX、CDilla1、PECompact、PKLite32、Shrinker、ASPack ASProtect等。该工具位于:光盘/tools/PE tools/Rebuilders/Import REConstructor。
在运行Import REConstructor之前,必须满足如下条件:
1) 目标文件己完全被Dump到另一文件;
2) 目标文件必须正在运行中;
3) 事先要找到真正的入口点(OEP);
4) 最好加载IceDump,这样建立的输入表较少存在跨平台的问题。

步骤如下:

(1)找被脱的入口点(OEP);
(2)完全Dump目标文件;
(3)运行Import REConstructor和需要脱的应用程序;
(4)在Import REConstructor下拉列表框中选择应用程序进程;
(5)在左下角填上应用程序的真正入口点偏移(OEP);
(6)按"IAT AutoSearch"按钮,让其自动检测IAT位置, 出现"Found address which may be in the Original IAT.Try 'Get Import'"对话框,这表示输入的OEP发挥作用了。
(7)按"Get Import"按钮,让其分析IAT结构得到基本信息;
(8)如发现某个DLL显示"valid :NO" ,按"Show Invalids"按钮将分析所有的无效信息,在Imported Function Found栏中点击鼠标右键,选择"Trace Level1 (Disasm)",再按"Show Invalids"按钮。如果成功,可以看到所有的DLL都为"valid:YES"字样;
(9)再次刷新"Show Invalids"按钮查看结果,如仍有无效的地址,继续手动用右键的Level 2或3修复;
(10)如还是出错,可以利用"Invalidate function(s)"、"Delete thunk(s)"、编辑Import表(双击函数)等功能手动修复。
(11)开始修复已脱的程序。选择Add new section (缺省是选上的) 来为Dump出来的文件加一个Section(虽然文件比较大,但避免了许多不必要的麻烦) 。
(12)按"Fix Dump"按钮,并选择刚在(2)步Dump出来的文件,在此不必要备份。如修复的文件名是"Dump.exe",它将创建一个"Dump_.exe",此外OEP也被修正。
(13)生成的文件可以跨平台运行。

1、ASProtect v0.95保护

教程写作: 看雪
技术指导:D.boy 和RuFeng
写作日期:2000年5月30日
目标程序:ShowDep 4.0 beta 1
程序大小:Showdep.exe 为177K
程序下载:ShowDep 4.0
使用工具:Softice 4.05; ProcDump 1.6.2 Final; FrogsICE v0.43;Icedump 6.016

  首先忠心感谢D.boy 和RuFeng的热心帮助,是在他们的帮助下,我才对PE文件有一定程度的了解。我把从他们那里吸取的经验总结了一篇文章,希望对大家有所帮助。为了使初学者能更好理解这文章,本人尽量写的仔细点。


一、Import表的一些理论知识

在你看这篇文章之前你应对PE文件的结构有一定了解否则请先看看PE介绍.

1、现在不少软件后Import表被损坏或地址发生改变,需要我们手动找到正确的Import表来替换被破坏的Import表,并修正Import表的地址,程序才能正确执行。

程序装载时需要装载很多函数和DLL文件这时程序需要判定目标函数的地址并将该函数插补到该执行文件的映像中,所需要的信息都是放在PE文件的Import表,PE文件中的每一个输入函数都明确的列于该表中。 一般来说Import表是存放在程序的.idata块,它一般包含其他外来DLL的函数及数据信息。但也有不少程序的Import表不存在idata块中,给我们判断Import表的地址造成困难,但不要着急,只要了解Import表的结构你就能迅速定位Import表的地址。

2、Import表以一个IMAGE_IMPORT_DESCRIPTOR数组开始。每一个被PE文件隐式连结进来的DLL都有一个IMAGE_IMPORT_DESCRIPTOR。在这个数组中,没有字段指出该结构数组的项数,但它的最后一个单元是NULL,可以由此计数算出该数组的项数。IMAGE_IMPORT_DESCRIPTOR的格式如下:

image_import_descriptors结构:

IMAGE_IMPORT_DESCRIPTOR struct
 riginalFirstThunk dd 0 该字段是一个指针数组的RVA偏移。其中每一个指针都指向一IMAGE_IMPORT_BY_NAME结构
 TimeDateStamp dd 0 时间及日期标志,在这可以忽略
 ForwarderChain dd 0 正向链结索引,在这可以忽略
 Name dd 0 以NULL结尾的ASCII字符的RVA地址,该字符串包含输入的DLL名,比如"Kernel32.dll" 或"USER32.DLL" (关键!,我们定位Import表的依据)
 FirstThunk dd 0 该字段是在Image_thunk_data联合结构中的RVA偏移。大多数情况下,Image_thunk_data是指IMAGE_IMPORT_BY_NAME结构的指针。如果不是一个指针的话,那它就是该功能在DLL中的序号。
IMAGE_IMPORT_DESCRIPTOR ends

3、为了使大家更好理解,我们以ShowDep 4.0 beta的Import表为例,ShowDep 4.0用 ASProtect 加,用prodump 分析,发现没有idata段。 脱大师D.boy很成功分析出该软件的Import表:import rav 0042D104,size 00001470 ;Image Base(基址)=00400000。

软件的Import表的image_import_descriptors结构如下:


-----SHOWDEP!.rdata+1104--------------------------dword-------------PROT---(0)--
0030:0042D104 ①0002D23C ②00000000 ③00000000 ④0002DA8A <............... ^
0030:0042D114 ⑤0002C070 ⑥0002D43C ⑦00000000 ⑦00000000 p...<........... v

(图一)
为了解释方便,我在每个数据前加了序号。上图就是一个典型的Import表开始处的image_import_descriptors结构,Import表就是以这个数组开始的一段连续内存空间,在这里大小是1470的连续空间。各项数据含义如下:

IMAGE_IMPORT_DESCRIPTOR struct
 riginalFirstThunk dd 0 ①0002D23C
 TimeDateStamp dd 0 ②00000000
 ForwarderChain dd 0 ③00000000
 Name dd 0 ④0002DA8A(关键!,我们定位Import表的依据)
 FirstThunk dd 0 ⑤0002C070
IMAGE_IMPORT_DESCRIPTOR ends

现在我们将image_import_descriptors结构中每项的地址均显示分析一下:


① Name选项(我们定位Import表地址就是以此为依据的)

在这例Name项值为:④0002DA8A

下命令DD 42DA8A (显示内存地址42DA8A的数据,其中42DA8A=④0002DA8A+Image Base(基址))

-----SHOWDEP!.rdata+1A8A--------------------------dword-------------PROT---(0)--
0030:0042DA8A 4E52454B 32334C45 4C4C442E 019A0000 KERNEL32.DLL.... ^
0030:0042DA9A 64616F4C 73727543 0041726F 6F4C01AB LoadCursorA...Lo v

(图二)
想必大家己睁大眼睛了,发现什么有价值的东西?对,就是KERNEL32.DLL!就是我们的突破口。

Import表装载的基本原理是:根据Import表的指示找到外部模块的文件名,再使用Win32 API函数GetModuleHandleA获得该模块在内存中的句柄。如果没在内存中就使用LoadLibraryA API调用装入该模块。随后使用获得的模块句柄调用Win32 API函数GetProcAddress 获得该模块中Import表指定功能的实际地址,加上装入基址,并且填入Import表的FirstThunk所指的IMAGE_IMPORT_BY_NAME结构指针数组中,完成该模块的一个功能的人工装入填写。循环调用函数GetProcAddress以获得其他功能调用的地址,加上装入基址,并填入之,以完成一个外部模块的装入。再循环上述过程对其他模块进行装入。

因此我们可以从函数LoadLibraryA入手,该函数会装入外部模块,我们视这函数的入口参数是否为KERNEL32.DLL,以此来确定Import表的状况。即确定 image_import_descriptors结构中的name选项。

函数LoadLibrary:

HINSTANCE LoadLibrary(
 LPCTSTR lpLibFileName // 执行模块的文件名和地址
);

只要函数LoadLibrary参数的模块名为KERNEL32.DLL,就会出现图二的情况,这时KERNEL32.DLL地址为0042DA8A。因此image_import_descriptors结构中name为0042DA8A―400000=2DA8A,这时利用S命令在内存中查找字条串002DA8A,就可确定import表的地址。


(到这里我们就能确定Import表的地址了,下面几项可帮助我们大家更好理解Import表)

②riginalFirstThunk项

在这里riginalFirstThunk项值为:①0002D23C

下命令DD 42D23C (显示内存地址42D23C的数据,其中42DA8A=①0002D23C+Image Base(基址))

-----SHOWDEP!.rdata+123C--------------------------dword-------------PROT---(0)--
0030:0042D23C 0002D798 0002D7A8 0002D7BE 0002D782 ................ ^
0030:0042D24C 0002D7CC 0002D7E0 0002D7F6 0002D80A ................ ^
0030:0042D25C 0002D81C 0002D830 0002D840 0002D854 ....0...@...T... v
0030:0042D26C 0002D868 0002D87C 0002D890 0002D8A0 h...|........... v

(图三)
图三是一个指针数组,其中每一个指针都指向一IMAGE_IMPORT_BY_NAME结构。

我们以第一个指针0002D798为例,显示它所指的IMAGE_IMPORT_BY_NAME结构.
下命令:dd 42d798(注意:0002D798+Image Base(基址))

-----SHOWDEP!.rdata+1798--------------------------dword-------------PROT---(0)--
0030:0042D798 6547011B 636F4C74 69546C61 0000656D ..GetLocalTime.. ^
0030:0042D7A8 6F430025 6E69746E 65446575 45677562 %.ContinueDebugE ^
0030:0042D7B8 746E6576 022B0000 65736552 65764574 vent..+.ResetEve v
0030:0042D7C8 0000746E 615702CB 6F467469 62654472 nt....WaitForDeb v

(图四)
③FirstThunk项

在这例,FirstThunk项的值为:⑤0002C070


下命令dd 42c070 (注意:0002c070+Image Base(基址)):

-----SHOWDEP!.rdata+0070--------------------------dword-------------PROT---(0)--
0030:0042C070 0002D798 0002D7A8 0002D7BE 0002D782 ................ ^
0030:0042C080 0002D7CC 0002D7E0 0002D7F6 0002D80A ................ ^
0030:0042C090 0002D81C 0002D830 0002D840 0002D854 ....0...@...T... v
0030:0042C0A0 0002D868 0002D87C 0002D890 0002D8A0 h...|........... v

(图五)
图五是一个指针数组(Image_thunk_data联合结构),大多数情况下,Image_thunk_data是指IMAGE_IMPORT_BY_NAME结构的指针。如果不是一个指针的话,那它就是该功能在DLL中的序号。不知你发现没有,图五的数据和图三完全相同,原因就是这个。

二、确定Import表的地址和大小并修正Import表

通过上面的讲解,想必大家对Import表己比较熟悉了吧,现在以脱ShowDep 4.0 beta 1的为例,讲解定位Import表的位置和大小的几种方法。

方法一:通过idata来确定Import表的位置

大部分加程序的块表中都有.idata这一项,.idata包含其他外来DLL的函数及数据信息,也就是说Import表的起始地址就是.idata的地址。如是这种情况,脱就简单多了。

先dump取正确的Import表(假设取名为idata.bin),然后在解压入口点full dump取整个程序(假设取名为dump.exe)用 Procdump打开dump.exe文件, 察看.idata Section. 记下Raw Size和 Raw Offset的值。用HexWorkshop打开dump.exe 将idata.bin拷贝/粘贴到 dump.exe(粘贴位置为Raw Offset 大小为Raw Size). 再修正Entry Point和Import表的地址(此值就是.idata的RVA)

具体操作参考后面几节的脱教学实例。

方法二:没.idata一项,利用bpx loadlibrarya来判断Import表的位置

由于ShowDep 4.0 beta 1没.idata一项,因此要确定Import表的位置和大小较困难,步骤如下:

①分析ShowDep 4.0 beta 1的文件PE头

运行 Procdump,点击pe-editor按钮,选中ShowDep.exe文件:

Size of image : 000A0000 ; 这个PE文件执行时分配的内存空间。
Image Base : 00400000 ; 基址

②确定Import表的地址

a.先装载Icedump
在这用Icedump 6.016版本,其命令操作形式完全和以前的版本不同。先在Icedump目录里运行相应SOFTICE版本的icedump.exe(我用的SOFTICE是4.05版,因此在win9x/405目录下运行icedump.exe),如Icedump装载成功,Icedump会返回如下信息:

icedump v6.0.1.6 for winice v4.05 loader
icedump unloaded
icedump loaded ←出现这句话表示Icedump装载成功

C:>

(图六)
b.再装载FrogsICE
由于ShowDep能检测到SOFTICE的存在,因此装载Frogsice就可躲过。在我机子里:FrogsICE 1.00 Final和Icedump不能很好兼容工作,因此我将FrogsICE换成版本v0.43,hehe..它们配合的很好。双击FPloader.exe文件即可装载成功。
OK到此你的SOFTICE的功能己大大加强里。
这时试试运行ShowDep,这时屏幕将蓝屏给你一菜单选项,告知ShowDep发现了SOFTICE,是否欺骗它,这时你按ESC按钮,程序即可正常运行。(注:我的SOFTICE用icepath打过补丁)


c、拦截函数loadlibararya


下命令:bpx loadlibrarya do "dd esp->4"
(注:在TRW2000下实现同样功能的命令是: bpx loadlibrarya do "dd *(esp+4)"

这个命令就是当拦截loadlibararya函数时,显示其入口参数的在内存的值,如:

0137:00710242 PUSH EAX
0137:00710243 CALL [loadlibarary] ;当调用此函数将中断,并显示push参数的值,在这里即:d eax

ok断点设置好后,运行ShowDep程序,将中断,此时按F5一直到数据窗口显示为:KERNEL32.DLL字符。在我win97系统下,只要按两下F5即可看到如下情况:

-----SHOWDEP!.rdata+1A8A--------------------------dword-------------PROT---(0)--
0030:0042DA8A 4E52454B 32334C45 4C4C442E 019A0000 KERNEL32.DLL.... ^
0030:0042DA9A 64616F4C 73727543 0041726F 6F4C01AB LoadCursorA...Lo v

(图六)
hehe...看看图六和图三是不是一样啊!其中前面的地址0042DA8A就是关键。
0042DA8A就是image_import_descriptors结构中的name项的值,因为该结构如下:

0030:0042D104 0002D23C 00000000 00000000 0002DA8A <............... ^
0030:0042D114 0002C070 0002D43C 00000000 00000000 p...<........... v

因此这时我要在数据窗口向前查找字符串0002DA8A(0002DA8A=0042DA8A-基址):

下命令:S DS:400000 L FFFFFFFF 8A DA 02 00
(注:在TRW2000下实现同样功能的命令是: S 30:0 L FFFFFFFF 8A DA 02 00)(用上面的好象不行但愿新版能改进)
结果如下:

-----SHOWDEP!.rdata+1100--------------------------dword-------------PROT---(0)--
013F:0042D100 0042B385 0002D23C 00000000 00000000 ..B.<........... ^
013F:0042D110 0002DA8A 0002C070 0002D43C 00000000 ....p...<....... v

(图七)
仔细比较图七和图一,发现这就是Import表的IMAGE_IMPORT_DESCRIPTOR数组。
如你看不习惯,可再下命令: D 42D110-C 这样就可显示和图一一样的画面了。

这样就可确定Import表的地址是2D104=0042D104―400000(基址)

③确定Import表的大小

现己将Import表的起始地址确定了:RVA=42D104。只要确定Import表的尾部就可计算出其大小,Import表在内存里是连续存放的一段数据,其一般结尾处一段内存空间都是0在此例,你开始先定位来到Import表的起始处,按ALT+↓向下翻页(或ALT+PageDown),直到看到如下情况:

013F:0042E54A 65530262 766E4574 6E6F7269 746E656D b.SetEnvironment
013F:0042E55A 69726156 656C6261 011D0041 4C746547 VariableA...GetL
013F:0042E56A 6C61636F 666E4965 0000576F 00000000 ocaleInfoW......
013F:0042E57A 00000000 00000000 00000000 00000000 ................ v
013F:0042E58A 00000000 00000000 00000000 00000000 ................ v

(图八)
字符串0000576F就是Import表的最后一项,其后面一位000000的地址为:42e574(其边界就是上面红色的W如你在SOFTICE不能确定可DUMP后在十六进制工具Hexworkshop很方便知边界地址)

因此Import表的大小=42E574-42D104=1470

④、找入口点

在SOFTICE你会来到如下:
0137:00710B35 MOV EDX[EAX] 
0137:00710B37 MOV EAX[EBP+08] 
0137:00710B3A ADD EDX[EAX+18]
0137:00710B3D MOV EAX[EBP+08]
0137:00710B40 MOV EAX[EAX+1C]
0137:00710B43 CALL 007104C8 ←按F8进去
0137:00710B48 POP EDI
0137:00710B49 POP ESI
0137:00710B4A POP EBX
0137:00710B4B POP ECX

按F8进去来到:

0137:007104C6 8BC0 MOV EAXEAX
0137:007104C8 89C4 MOV ESPEAX
0137:007104CA 89D0 MOV EAXEDX
0137:007104CC 8B1D34567100 MOV EBX[00715634]
137:007104D2 89041C MOV [EBX+ESP]EAX
0137:007104D5 61 POPAD
0137:007104D6 50 PUSH EAX ←此处EAX=422c3a即入口点的值
0137:007104D7 C3 RET ←返回到入口点

(图九)
此时程序己完全解压准备运行了。记下程序入口点:00422c3a 在dump前,清除所有的断点:bc *. 因此你可在SOFTICE下用命令:

:/dump 400000 A0000 c:/temp/dump.exe
(如你是用Icedump 6.016以前版本用此命令:pagein d 400000 A0000 c:/temp/dump.exe)

⑤修正PE文件头

用 Procdump打开刚建好的 dump.exe文件,点击pe-editor按钮,然后再点击SECTIONS按钮,在每个section点击右键,选中Edit section,把所有的 section 的PSize == VSize offset == RVA (即让物理地址和大小等于虚拟地址和大小)。如你是用Procdump脱的,可省去这一步。

在改完所有的sections后,按OK存盘后,你在资源管理器中刷新一下,就会发现dumped.exe的图标回来了,但还不能运行,你还要修正入口点和import表。

将入口点(Entry Point)改为:00422c3a(记着:00422c3a-imagebase=22c3a)

再点击Directory按钮,将Import Table改为 RVA (2D104 );而其选项Size只要比0大就可;(这程序DUMP后improt并没被破坏,只要把import rav/size 填上就OK了!) 在这例中,Import表的尺寸没用上,但方法要掌握,如碰到Import表损坏的程序,就要替换Import表程了,这时需要Import表的大小了。

然后点击OK退出Procdump,再运行 dumped.exe ,程序成功运行!


方法三:利用S命令查找字串KERNEL32.DLL来确定Import表的位置

如果软件没.idata项,用方法二bpx loadlibrarya do "dd esp->4"也不能看到KERNEL32.DLL,这种情况就要用S命令来协助了。

先bpx loadlibrarya do "dd esp->4"

中断后,凭经验来判断Import表完全触压时机,一般中断在第一次或第二次(某些情况要几次)Import表就基本解压结束了。这时下命令:S DS:400000 l FFFFFFFF 'KERNEL32.DLL'


会在数据区找到KERNEL32.DLL ,但这不一定是image_import_descriptors结构中对应的KERNEL32.DLL项,这要跟据具体情况来分析了,一般我们要找到的KERNEL32.DLL是在xxx:4xxxxxx的地址形式处。

如不能确定何处是关键的KERNEL32.DLL,只好dump后,用十六进制工具来分析了,这样较直观,打开后查找image_import_descriptors类似结构。

当然Import表确定方法多种,这里将本人常用的几种方法列出供参考!也欢迎你将自己的经验告诉大家,互相提高。

2、ASProtect v0.94b保护

英文原作:r!sc 《Almost Manual Unpacking (using Softice and Icedump)》
原作日期:6th febuary 2000
教程翻译: 看雪
翻译日期:2000年5月26日
声 明: 本文以r!sc的教程为基础,以自己的观点补充调整。

目标程序:aspack.exe . 231424 . v 2.001
程序下载:AsPack
使用工具:Softice 4.05; ProcDump 1.6.2 Final; FrogsICE v0.43;Icedump 6.016


part1 . 理论知识

part2 . 分析原文件的PE头

part3 . 抓取import table

part4 . Dump整个程序并修正文件头


=part1===part1===part1===part1===part1===part1===part1===part1===part1===part1=
理论知识

这种被压缩或加密的PE文件,执行时,在内存中将会完全解压。其中import表在装载中也会被完全解压或解密(―攻击点)。其中抓取import表就很关键了,其具体位置,可用 Procdump分析文件头得到。然后,要跟踪程序完全解压后的跳到程序处的入口点,然后在内存里dump取程序的整个部分。将刚dump取的正确import表用十六进制工具粘贴到完全dump的程序,再 修正文件头,这样程序就可正常运行。


=part2===part2===part2===part2===part2===part2===part2===part2===part2===part2=
分析原文件的PE头

运行 Procdump,点击pe-editor按钮,选中ASPack.exe文件,我们想要得到程序解压后的尺寸,import表的地址和大小...幸运的是这个文件的每个块(section )都存在。

Size of image : 00079000 ; 这个PE文件执行时分配的内存空间。
Image Base : 00400000 ; 基址

.idata ;.idata包含其他外来DLL的函数及数据信息
Virtual Size : 00002000 ; idata在内存的尺寸
Virtual Offset : 00046000 ; idata的地址(+imagebase == 00446000)

.rdata
Virtual Size : 00001000
Virtual Offset : 00049000

import表有可能在idata块或rdata块,到底在哪部分?看看它们的尺寸,我将马压在.idata ...


=part3===part3===part3===part3===part3===part3===part3===part3===part3===part3=
抓取import table

1、先装载Icedump
在这用Icedump 6.016版本,其命令操作形式完全和以前的版本不同。先在Icedump目录里运行相应SOFTICE版本的icedump.exe(我用的SOFTICE是4.05版,因此在win9x/405目录下运行icedump.exe),如Icedump装载成功,Icedump会返回如下信息:

icedump v6.0.1.6 for winice v4.05 loader
icedump unloaded
icedump loaded ←出现这句话表示Icedump装载成功

C:>

2、再装载FrogsICE
由于aspack能检测到SOFTICE的存在,因此装载Frogsice就可躲过。在我机子里:FrogsICE 1.00 Final和Icedump不能很好兼容工作,因此我将FrogsICE换成版本v0.43,hehe..它们配合的很好。双击FPloader.exe文件即可装载成功。
OK到此你的SOFTICE的功能己大大加强里。
这时试试运行aspack,这时屏幕将蓝屏给你一菜单选项,告知aspack发现了SOFTICE,是否欺骗它,这时你按ESC按钮,程序即可正常运行。

3、 记住我们的第一步是抓取import table,它在内存的446000到449000处,因此程序运行时注意这段内存代码的解压情况。
由于SOFTICE不能LOAD aspack.exe我们用:bpx loadlibrarya命令来拦断。

loadlibrarya命令解释:如果import表没在内存中就使用LoadLibraryA API调用装入该模块,因此我们可以拦截此函数来观察import表。


:bpx loadlibrarya 然后运行aspack将中断如下:
Break due to BPX KERNEL32!LoadLibraryA
:dd 446000 l 40 (下此命令观察内存446000到449000处的数据)


.
-------SPACK!.idata--------------------dword----------ROT--?(0)--
0030:00446000 ???????? ???????? ???????? ???????? ................ 
0030:00446010 ???????? ???????? ???????? ???????? ................ 
0030:00446020 ???????? ???????? ???????? ???????? ................ 
0030:00446030 ???????? ???????? ???????? ???????? ................

上图是SOFTICE的数据窗口,显示 ??...说明import表在内存中没解压。
再按F5一下,程序将中断如下:(记着:在这例中只能要按一下F5,否则将不能抓取正确的import表)

0030:00446000 00000000 00000000 00000000 0004669C .............f..
0030:00446010 0004612C 00000000 00000000 00000000 a..............
0030:00446020 000468B6 000461AC 00000000 00000000 .h...a..........
0030:00446030 00000000 000468D0 000461B4 00000000 .....h...a......

这时446000处不是?? ?? ?? ??,意味着import表己被解压了。

.import表以一个IMAGE_IMPORT_DESCRIPTOR数组开始。 image_import_descriptors数据有5组dwords组成。

image_import_descriptors结构:
①dd offset original_first_thunk
②dd timedatestamp    时间及日期标志
③dd forwardchain    正向链结索引
④dd offset library name以NULL结尾的ASCII字符的RVA地址,该字符串包含输入的DLL名,
            比如"Kernel32.dll"或"USER32.DLL"。
⑤dd offset first_thunk 该字段是在Image_thunk_data联合结构中的RVA偏移

其中timedatestamp和forwardchain通常设置为00000000 original first thunk 选项不是必须的.

:dd 446000 l 40
0030:00446000 00000000 00000000 00000000 0004669C .............f..
0030:00446010 0004612C 00000000 00000000 00000000 a..............

地址4669c 指向 LibraryName (RVA 你需要加上基址imagebase+400000)

:db 44669c l 10
0030:0044669C 4B 45 52 4E 45 4C 33 32-2E 44 4C 4C 00 00 00 00 KERNEL32.DLL....

地址612c指向 first_thunk 库。

:dd 44612c l 10
0030:0044612C 000466AA 000466C2 000466DA 000466F2 .f...f...f...f..

这些是以NULL结尾的ASCII字符的RVA地址, . . 466aa 是第一个API函数的地址,466c2是第二个API函数地址...它们以以NULL结尾。


:db 0004466aa l 20
0030:004466AA 00 00 44 65 6C 65 74 65-43 72 69 74 69 63 61 6C ..DeleteCritical
0030:004466BA 53 65 63 74 69 6F 6E 00-00 00 4C 65 61 76 65 43 Section...LeaveC

通过上面的分析可知这就是原始的.import表,快dump it!!(看看上文的image_import_descriptors地址)

:/dump 446000 2000 c:/aspack.idata.bin
(如你是用Icedump 6.016以前版本用此命令:pagein d 446000 2000 c:/aspack.idata.bin)


为了方便大家对比,特将dump正确的import表放在此下载


=part4===part4===part4===part4===part4===part4===part4===part4===part4===part4=
Dump整个程序并修正文件头

1、现在我们要找程序的入口点,下命令:bpx loadlibrarya ,然后按14下F5然后按F10一步一步跟踪来到如下代码

0137:00C1150E 8B4508 MOV EAX[EBP+08]
0137:00C11511 8B10 MOV EDX[EAX] DS:004664FC=00400000
0137:00C11513 8B4508 MOV EAX[EBP+08]
0137:00C11516 035018 ADD EDX[EAX+18]
0137:00C11519 8B4508 MOV EAX[EBP+08]
0137:00C1151C 8B401C MOV EAX[EAX+1C]
0137:00C1151F E874F9FFFF CALL 00C10E98 ←在此按F8进入
0137:00C11524 5F POP EDI
0137:00C11525 5E POP ESI
0137:00C11526 5B POP EBX
0137:00C11527 59 POP ECX
0137:00C11528 59 POP ECX
0137:00C11529 5D POP EBP
0137:00C1152A C20400 RET 0004


F8进入后来到如下:

0137:00C10E96 8BC0 MOV EAXEAX
0137:00C10E98 89C4 MOV ESPEAX
0137:00C10E9A 89D0 MOV EAXEDX
0137:00C10E9C 8B1D6C66C100 MOV EBX[00C1666C]
0137:00C10EA2 89041C MOV [EBX+ESP]EAX
0137:00C10EA5 61 POPAD
0137:00C10EA6 50 PUSH EAX ;push 442b98 即为入口点
0137:00C10EA7 C3 RET ;返回到己完全解压的代码处,即入口点处。
0137:00C10EA8 C3 RET

来到入口点:

0167:00442B98 55 PUSH EBP ←此处为入口点
0167:00442B99 8BEC MOV EBPESP
0167:00442B9B 83C4F4 ADD ESP-0C

在0167:00442B98 处就可dump整个内存数据了,此时程序己完全解压准备运行了。记下程序入口点:00442B98
在dump前,清除所有的断点:bc *.

./dump 400000 79000 c:/aspack.dumped.exe
(如你是用Icedump 6.016以前版本用此命令:pagein d 400000 79000 c:/aspack.dumped.exe)

2、替换正确的import表

用Hexworkshop打开aspack.dumped.exe和aspack.idata.bin. Goto到exe文件的46000偏移处Select Block大小为2000. 拷贝aspack.idata.bin文件的同样大小(2000)的Block粘贴到exe文件中以替换掉不正确的.idata section然后存盘。(注意:以上所有数据都是十六进制)

3、修正PE文件头

用 Procdump打开刚建好的 aspack.dumped.exe文件,点击pe-editor按钮,然后再点击SECTIONS按钮,在每个section点击右键,选中Edit section,把所有的 section 的PSize = VSize offset = RVA 。

如:CODE 的PSize=0001E000; VSize=00042000;offset =00000400;RVA=00001000;
改成:PSize = VSize= 00042000;offset = RVA =00001000;

在改完所有的sections后,按OK存盘后,你在资源管理器中刷新一下,就会发现aspack.dumped.exe的图标回来了,但还不能运行,你还要修正入口点和import表。

将入口点(Entry Point)改为:00042B98(记着:00442B98-imagebase=42B98)

再点击Directory按钮,将Import Table改为 RVA (46000 );而其选项Size只要比0大就可;

然后点击OK退出Procdump,再运行 aspack.dumped.exe ,程序运行的很甜美!

这时你用W32DASM不能反汇编,你可用 Procdump编辑第一个section characteristics:

将其 c0000060 (data writable)改为: 60000040 (code executable)或 e0000060 (code data etc etc)

注:大家抓取屏幕可在Icedump 6.016中,用:/Screendump抓取。
不加参数命令:/Screendump 选取模式,重复执行,会在0、1、2、3、4五种模式下转换。
模式1(默认)是以文本方式存盘,模式2是以HTML文件存盘。其它的请参考其readme.
模式选好后,就可用命令: /SCREENDUMP [路径]文件名 抓取整个SOFTICE的屏幕。

3、ASProtect v0.9x保护


Advanced Zip Password Recovery 3.0的脱

教程写作: ****
作者信箱: break_ice@hotmail.com
写作日期: 2000年3月25日
版权声明: 本文没有版权允许任意转贴和修改. 但如果只引用文中部分内容时请最好注明原文出处以表示对一位Cracker同行的劳动的尊重.

使用工具:
TRW2000 1.03
ProcDump 1.6.2
Hexworkshop 3.02

下载:Advanced Zip Password Recovery 3.0

比起其Beta版来AZPR 3.0正式版的保护更为加强. 1. 对Softice的多处Check用FrogsICE不能完全骗过; 2. CRC的校验; 3. 动态地址(好象是这个名吧?); 4.对Loader的防范这回用Process Patch不行了.

用Softice跟踪它会是一种痛苦 (当然完全可以用Softice只是你要了解程序的Anti-debugger技巧在关键的Check后改变跳转方向). 这次祭出我们中国人的骄傲TRW2000来对付它.

先用Procdump的PE Editor查看一下程序的.idata section记下数值. Virtual size=2000 Virtual offset=21000. 另外记下程序的Image Base: 400000.

1. 运行TRW Load程序. 程序中断在入口处. 但接下来无论是否BPX设断只要G继续运行程序便会出错. 下Faults off G OK程序退出.
2. 试BPM 421000 (.idata的offset) 再Load程序. G 程序可以被正常中断 COOL!
3. G当程序第三次(? 记不清了. 原则是d 421000显示的data区见到XXXX0200 000000字样)中断时BD*F10直到下面的语句

*********************************
注意: 程序每次Load时Offset都不一样你的机器中的XXXX:YYYYYYYY肯定和下面的不同. 下Code on指令这样才好参照下面的代码. 另外下面所摘的几段代码是引用高手tiamath的.因为我不清楚在TRW下如何dump屏幕 (谁能指教一下:);而用ICEDump的pagein n命令来Dump Softice的屏幕我的机器会死机. :(
*********************************

0167:004F34CD 50 PUSH EAX
0167:004F34CE B890274F00 MOV EAX004F2790
0167:004F34D3 50 PUSH EAX
0167:004F34D4 B8A4274F00 MOV EAX004F27A4
0167:004F34D9 50 PUSH EAX
0167:004F34DA B8A0284F00 MOV EAX004F28A0
0167:004F34DF 50 PUSH EAX
0167:004F34E0 B8AC274F00 MOV EAX004F27AC
0167:004F34E5 50 PUSH EAX
0167:004F34E6 B8646C4E00 MOV EAX004E6C64
0167:004F34EB 50 PUSH EAX
0167:004F34EC 8B4508 MOV EAX[EBP+08]
0167:004F34EF 8D4824 LEA ECX[EAX+24]
0167:004F34F2 8B4508 MOV EAX[EBP+08]
0167:004F34F5 8B500C MOV EDX[EAX+0C]
0167:004F34F8 8B4508 MOV EAX[EBP+08]
0167:004F34FB 8B4008 MOV EAX[EAX+08]
0167:004F34FE E899F4FFFF CALL 004F299C
0167:004F3503 33C0 XOR EAXEAX <--在这里Dump .idata

下指令 W 421000 L 2000 azprdata.bin

OK. 再往下直到

0167:004F3637 8D4818 LEA ECX[EAX+18]
0167:004F363A 8B4508 MOV EAX[EBP+08]
0167:004F363D 8B10 MOV EDX[EAX]
0167:004F363F 8B4508 MOV EAX[EBP+08]
0167:004F3642 8B401C MOV EAX[EAX+1C]
0167:004F3645 E8EAF6FFFF CALL 004F2D34 <--F8进入
0167:004F364A 5F POP EDI
0167:004F364B 5E POP ESI

到了

0167:004F2D72 E9148B1DA8 JMP A86CB88B
0167:004F2D77 8E4F00 MOV CS[EDI+00]
0167:004F2D7A EB01 JMP 004F2D7D
0167:004F2D7C EB89 JMP 004F2D07
0167:004F2D7E 041C ADD AL1C
0167:004F2D80 EB02 JMP 004F2D84
0167:004F2D82 EBE8 JMP 004F2D6C
0167:004F2D84 61 POPAD
0167:004F2D85 EB01 JMP 004F2D88
0167:004F2D87 E850EB02E9 CALL E95218DC
0167:004F2D8C 17 POP SS
0167:004F2D8D E802000000 CALL 004F2D94
0167:004F2D92 E91758C35E JMP 5F1285AE
0167:004F2D97 5B POP EBX
0167:004F2D98 59 POP ECX

此时小心地跟踪碰到JMP时按F8而不要按F10

直到显示变成

0167:004F2D72 E9148B1DA8 JMP A86CB88B
0167:004F2D77 8E4F00 MOV CS[EDI+00]
0167:004F2D7A EB01 JMP 004F2D7D
0167:004F2D7C EB89 JMP 004F2D07
0167:004F2D7E 041C ADD AL1C
0167:004F2D80 EB02 JMP 004F2D84
0167:004F2D82 EBE8 JMP 004F2D6C
0167:004F2D84 61 POPAD
0167:004F2D85 EB01 JMP 004F2D88
0167:004F2D87 E850EB02E9 CALL E95218DC
0167:004F2D8C 17 POP SS
0167:004F2D8D E802000000 CALL 004F2D94
0167:004F2D92 E91758C35E JMP 5F1285AE
0167:004F2D94 58 POP EAX <-- 光标位于此行时 EAX=401000
0167:004F2D95 C3 RET <--这里

下Suspend指令. 回到Windows. 用ProcDump来Dump(full)脱的程序得到azprdump.exe. 你也可以用TRW的PEDUMP命令来得到脱程序但我个人的经验很容易死机所以我宁愿用ProcDump来做.

4. 用PE Editor修改程序的Entry Point为1000. 并查看脱后程序的.idata section. 此时Raw size=1670 Raw offset=1FC00. 修改Directory中Import Table的RVA=21000SIZE=1670.
5. 用Hexworkshop打开azprdump.exe和azprdata.bin. Goto到exe文件的1FC00偏移处Select Block大小为1670. 拷贝.bin文件的同样大小(1670)的Block粘贴到exe文件中以替换掉不正确的.idata section.

现在再试着运行程序应该可以正常运行了. 如果程序出错再做一件事: 把 .bss section的raw size值改为00000000 (高手tiamath的建议).

有了脱的程序大家应该会Patch它成为注册版了吧. 只需改一个字节.

结语: 用本文所描述的方法可以对绝大多数Asprotect+Aspack保护的程序进行成功的脱. 大家读完这篇教程不妨找几个程序开刀. 比方说The Bat! 1.39现在不应该再难住大家了.

好了这次就到这儿了下回见.

致谢:

感谢SV Hobgoblin tiamath. 没有他们的帮助我不可能做到这一步


(编辑:天命孤独)

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

图片精选