转:通过ReverseMe学习PE文件结构-输出表

来源:互联网 发布:远程wifi控制软件 编辑:程序博客网 时间:2024/05/16 01:31

转:http://lwglucky.blog.51cto.com/1228348/302172

任务: 
给程序Reversed.exe添加一个输出表,将调用MessageBoxA的那个过程改造成一个输出函数,然后在ReverseMe.exe中装载修改后的Reversed.exe文件,调用该函数,让ReverseMe.exe和Reversed.exe有相同的功能。 
工具:U-Edit、OllyDBG 
需要知识:PE头结构、输入表结构、少许汇编知识 
导出表的起始位置有且只有一个IMAGE_EXPORT_DIRECTORY结构(《Window环境下32位汇编语言程序设计(第一版)》P700,我发现这本书中的PE文件结构很好懂): 
IMAGE_EXPORT_DIRECTORY STRUCT 
  Characteristics           DWORD      ?;未使用,总是为0 
  TimeDateStamp             DWORD      ?;文件产生的时刻 
  MajorVersion              WORD       ?;未使用,总是为0 
  MinorVersion              WORD       ?;未使用,总是为0 
  nName                     DWORD      ?;指向文件名的RVA 
  nBase                     DWORD      ?;导出函数的起始序号 
  NumberOfFunctions         DWORD      ?;导出函数的总数 
  NumberOfNames             DWORD      ?;以名称导出的函数总数 
  AddressOfFunctions        DWORD      ?;指向导出函数地址表的RVA 
  AddressOfNames            DWORD      ?;指向函数名地址表的RVA 
  AddressOfNameOrdinals     DWORD      ?;指向函数名序号表RVA 
IMAGE_EXPORT_DIRECTORY ENDS 
部分字段的说明: 
  nName 
这个文件名存的不是文件当前的名字,而是他被编译的时候的名字。 
  nBase 
将AddressOfFunctions字段指向的入口地址表的索引号加上这个起始值就是对应函数的导出序号,举例来说,若nBase为x,则入口表指定的第一个导出函数的序号是x,第二个导出函数的序号是x+1,总之,一个导出函数的导出序号等于nBase字段的值加上其在入口地址表中的位置索引值。 
  NumberOfNames 
既可以通过序号导出又可以通过名称导出的函数的数目,NumberOfFunctions-NumberOfNames是只能通过序号导出的函数的数目。不一定所有的函数都是通过函数名导出的。 
  AddressOfFunctions 
这是一个地址数组,每一项都是一个函数的入口地址,项的数目为NumberOfFunctions 
  AddressOfNames和AddressOfNameOrdinals 
AddressOfNames是一个字符串数组,每一项都是一个函数名,项的数目为NumberOfNames。 
前面说过AddressOfNames和NumberOfFunctions不一定相等,就是说函数名和函数地址不一定是一一对应的关系。那么如何知道这些函数名究竟对应地址表中的哪个函数呢?AddressOfNameOrdinals字段就派上用场了。 
AddressOfNameOrdinals是一个word类型的数组,实际上是一个索引数组,每一项存一个函数的索引,项的数目为NumberOfNames。 
这样函数名数组AddressOfNames就通过索引数组AddressOfNameOrdinals与地址数组AddressOfFunctions对应起来了。 
举例来说,假如函数名称字符串地址表的第n项指向一个字符串“MyFunction”,那么可以去查找AddressOfNameOrdinals字段指向的数组的第n项,若该项的值为a,则AddressOfFunctions的第a项函数入口地址就是名为“MyFunction”的函数的入口地址。 
可见,AddressOfNameOrdinals指向的数组起了一个桥梁的作用。 
============ 
一.给程序Reversed.exe建立输出表 
1.将调用MessageBoxA的那个过程改造成一个函数 
将00404080处的代码改成这个样子: 
00404080 >  68 00104000     push    00401000 
00404085    55              push    ebp 
00404086    8BEC            mov     ebp, esp 
00404088    6A 00           push    0 
0040408A    6A 00           push    0 
0040408C    68 A0404000     push    004040A0                         ; ASCII "I am a LaMe rEvErSeR!:p"
00404091    6A 00           push    0 
00404093    FF15 44404000   call    dword ptr [<&USER32.MessageBoxA>&gt;; USER32.MessageBoxA 
00404099    5D              pop     ebp 
0040409A    C3              retn 
当然,函数是从00404085处开始的。 
2.建立输出表并将新函数加入输出表 
新建一个块: 
Name:      .edata 
VirtualSize:    0x0100 
VirtualAddress:    0x5000 
SizeOfRawData:    0x0200 
PointerToRawData:    0x0C00 
PointerToRelocations:  0 
PointerToLinenumbers:  0 
NumberOfRelocations:  0 
NumberOfLinenumbers:  0 
Characteristics:    0xC0000040 
我们要建立的输出表只包含一个名为"MyMessageBox"的输出函数,文件名为"Reversed_.exe"。 
根据上面的信息建立一个输出表: 
  Characteristics           0x00 
  TimeDateStamp             0x00 
  MajorVersion              0x00 
  MinorVersion              0x00 
  nName                     0x05040 "Reversed_.exe" 
  nBase                     0x01 
  NumberOfFunctions         0x01 
  NumberOfNames             0x01 
  AddressOfFunctions        0x05050 
  AddressOfNames            0x05030“MyMessageBox” 
  AddressOfNameOrdinals     0x05060;数组只有一个元素0 
AddressOfFunctions数组中的值:004085 
然后修改PE头中输出表的指针指向新块。 
============ 
2.将Reversed_.exe中的新函数MyMessageBox添加到ReverseMe.exe的输入表中。 
============ 
3.在程序中添加调用新函数的代码: 
00401010      FF15 1D404000 call    dword ptr [<&Reversed_.MyMessage>;  Reversed.MyMessageBox 
00401016    ^ EB E8         jmp     short <模块入口点> 
============ 
4.调整入口点到401010处 
============ 
到这里似乎所有的工作都做完了,把两个文件放到一起运行一下试试,得到了个错误。 
怎么产生这个错误的?我们漏掉了什么?请看二楼=.=

 

 

 

===================

为什么会产生这个错误?跟踪一下ReverseMe_.exe的运行情况就会发现:对MyMessageBox的调用没错,程序正确的找到了这个函数的入口地址,该函数在我这里表现是这个样子的: 
00374085 &gt;  55              push    ebp 
00374086    8BEC            mov     ebp, esp 
00374088    6A 00           push    0 
0037408A    6A 00           push    0 
0037408C    68 A0404000     push    4040A0《一 
00374091    6A 00           push    0 
00374093    FF15 44404000   call    dword ptr [404044]               ; ReverseM.00400000《二 
为了便于理解,这里使Reversed_.exe中的同一段代码: 
00404085 &gt;  55              push    ebp 
00404086    8BEC            mov     ebp, esp 
00404088    6A 00           push    0 
0040408A    6A 00           push    0 
0040408C    68 A0404000     push    004040A0                         ; ASCII "I am a LaMe rEvErSeR!:p"《一 
00404091    6A 00           push    0 
00404093    FF15 44404000   call    dword ptr [<&USER32.MessageBoxA>&gt;; USER32.MessageBoxA《二 
应该注意的到各个命令除了地址不同,其他都是一样的(真是废话,他们就是同一段代码)。 
对照一下就能发现造成错误的原因是地址:代码的地址被移动到了另一个位置,而代码中使用的数据地址、函数地址却没有做调整。 
怎么能让程序自己对地址做调整?这就是重定位表的作用了。 
观察一下出问题的两条语句(标记一、二)会发现命令中用到的地址与正确的地址相差的距离是一样的,如果能把这个距离求出来,让程序自己把地址调整一下,命令不就都是对的了么?这个距离是模块实际装入地址(00370000 )与模块建议装入地址(00400000)之差。可见,重定位需要3个数据:模块实际装入地址、模块建议装入地址、需要修正的机器码地址。其中,前两个数据分别是由PE头和WINDOWS装载器确定的,所以需要我们提供的数据仅仅是需要修改的机器码的地址一个。 
每个重定位块以一个IMAGE_BASE_RELOCATION结构开头,后面跟着本页面使用的所有重定位项,每个重定位项占用16位的地址(WORD): 
IMAGE_BASE_RELOCATION STRUCT 
VirtualAddress dd  ?;重定位内存页的起始RVA 
SizeOfBlock dd  ?;重定位块的长度 
IMAGE_BASE_RELOCATION ENDS 
一般来说,重定位项里放的应该是地址,而存放一个地址需要32位(DWORD),为何重定位表中的重定位项是16位的? 
每个需要重定位的块都需要单独的重定位块(我猜是因为不同的块被装入的地址不一样,重定位信息不一样,自然不同的块的重定位信息要分开放)。一个块中的地址的高位总是相同的而且在一个页面中寻址需要的指针位数是12,如果把高位地址统一表示,就可以省略一部分空间。 
每个重定位项的低12位就是要重定位的数据在页面中的地址,高4位用来描述当前重定位项的类型,能见到的只有两种: 
0 这个重定位项无意义,仅仅用来作为对齐用 
3 重定位地址指向的双字的32位都需要被修正 
最后所有的重定位块以一个VirtualAddress字段为0的IMAGE_BASE_RELOCATION结构作为结束。 
另外,重定位块中的重定位项数=(SizeOfBlock-8)/2 
============ 
一.给程序Reversed_.exe建立重定位表 
建立重定位表 
新建一个块: 
Name:      .reloc 
VirtualSize:    0x0100 
VirtualAddress:    0x6000 
SizeOfRawData:    0x0200 
PointerToRawData:    0x0E00 
PointerToRelocations:  0 
PointerToLinenumbers:  0 
NumberOfRelocations:  0 
NumberOfLinenumbers:  0 
Characteristics:    0x42000040 
我们需要重定位的有两处: 
0040408C    68 A0404000     push    004040A0                         ; ASCII "I am a LaMe rEvErSeR!:p"《一 
00404093    FF15 44404000   call    dword ptr [<&USER32.MessageBoxA>&gt;; USER32.MessageBoxA《二 
所以需要修改的机器码的地址为408D和4094(记得吧?命令中用到的地址),建立这样一个重定位块: 
VirtualAddress dd  0x4000 
SizeOfBlock dd  0xC(重定位块的长度,而不是需要修改的机器码的地址的个数) 
0x308D(加上类型说明) 
0x3095 
VirtualAddress dd  0x0000(表示重定位块结束) 
SizeOfBlock dd  0x0 
============ 
2.将Reversed_.exe的重定位表指向新块。 
============ 
3.运行一下,发现还是不行。跟踪程序的运行情况会发现重定位的两处地址并没有改变。 
重新看一遍PE结构,发现IMAGE_FILE_HEADER结构里有个Characteristics项,这一项决定了文件的装入方式。 
我把第0位置0(说明文件中存在重定位信息)时情况依然。 
我更干脆的把文件类型改成DLL文件(第13位置1)时两个程序都运行不了了。 
看来我选错对手了,这条路我走不下去了 =.= 
============ 
4.不用重定位的解决方法。 
之所以会有重定位问题,是因为文件被装载到的位置不是我们预期的位置,会造成这种情况的原因是建议装入地址已经有别的文件占用了,所以我的解决方法就是改变Reversed_.exe的基址和一些代码的地址参数: 
因为这个方法并不好,没有通用性,就不细说了,有兴趣的自己看看程序 
============ 
问题: 
1.重定位表是DLL文件专用的?就没办法将EXE当DLL一样用么? 
2.我把Reversed_.exe的第13位置1的时候,运行ReverseMe_.exe会遇到一个初始化错误。这是怎么回事?

原创粉丝点击