2.3 PEComp的实现

PEComp是PE文件比较器。功能是按照PE文件格式的数据结构按字段对两个指定的PE文件进行比对,以获取两个PE文件结构中的不相同的信息。在实际应用中,我们可以通过对比病毒感染前后PE文件在相关字段上发生的变化来判断病毒的感染方式,从而确定清理病毒的方法。

2.3.1 编程思路

PEComp的功能是在通用框架pe.asm的基础上,实现两个PE文件的对比,并通过图形界面将不同之处形象地表示出来。编码的大致思路如下:

步骤1 打开要比较的两个文件,分别进行文件的内存映射,获取内存起始地址。

步骤2 线性搜索,根据文件头部内容确定该文件是否为PE文件,不是则退出。

步骤3 将esi指向要操作的第一个文件的相关字段处,将edi指向第二个要操作的文件的相同字段处,同时获取该位置的指定个数的字节到内存,比较并显示,如果不同,则显示时使用红色背景以示区别。

下面开始具体的设计过程,首先根据构思的最终显示效果定义资源文件。

2.3.2 定义资源文件

将2.1.2节中的资源文件pe.rc复制到pecomp.rc,并在pecomp.rc文件中增加一个对话框,该对话框中包括用户选择的参与对比的两个PE文件文本框ID_TEXT1和ID_TEXT2、两个浏览按钮、一个显示结果用的表格IDC_MODULETABLE和一个执行按钮,增加的对话框脚本定义如下所示:

  RESULT_MODULE DIALOG 76,10,630,480
  STYLE DS_MODALFRAME | WS_POPUP |WS_VISIBLE | WS_CAPTION |WS_SYSMENU
  CAPTION "PE文件对比结果"
  FONT 9,"宋体"
  BEGIN
    LTEXT "您选定的第一个文件为:",ID_STATIC,10,13,200,15
    EDITTEXT ID_TEXT1,130,13,440,15
    PUSHBUTTON "浏览...",IDC_BROWSE1,570,13,50,14
    LTEXT "您选定的第二个文件为:",ID_STATIC1,10,35,200,15
    EDITTEXT ID_TEXT2,130,35,440,15
    PUSHBUTTON "浏览...",IDC_BROWSE2,570,35,50,14
    CONTROL  "",  IDC_MODULETABLE,  "SysListView32",13  |  WS_CHILD  |  WS_VISIBLE  |
                                      WS_BORDER | WS_TABSTOP, 10,60,610,390
    AUTOCHECKBOX "只显示不同的值" IDC_THESAME,10,460,100,14
    PUSHBUTTON "执行...(&R)",IDC_OK,570,460,50,14
  END

2.3.3 PEComp编码

复制pe.asm到PEComp.asm,并从代码中的窗口回调函数的菜单项响应部分开始编码,增加内容有以下4个部分。

1.菜单项响应代码

在主窗口的回调函数中,定义对鼠标点击菜单项“文件”|“打开”所引发的消息处理程序,添加如下代码:

.elseif eax==IDM_OPEN    ;打开PE文件对比对话框
          invoke DialogBoxParam,hInstance,RESULT_MODULE,hWnd,\
                offset _resultProcMain,0
          invoke InvalidateRect,hWnd,NULL,TRUE
          invoke UpdateWindow,hWnd

当用户选择了“打开”菜单选项时,会弹出资源文件里定义的对话框RESULT_MODULE。通过定义该对话框的回调函数,可以处理对话框控件发出的消息。该对话框的回调函数是_resultProcMain,其代码如代码清单2-4所示。

代码清单2-4 PE文件比较器的窗口回调函数_resultProcMain实现(chapter2\pecomp.asm)

   ;-----------------------
   ; 弹出PE对比窗口回调函数
   ;-----------------------
_resultProcMain    proc  uses ebx edi esi
        hProcessModuleDlg:HWND,wMsg,wParam,lParam 5 mov eax,wMsg
        .if eax==WM_CLOSE
            invoke EndDialog,hProcessModuleDlg,NULL
        .elseif eax==WM_INITDIALOG
            invoke GetDlgItem,hProcessModuleDlg,IDC_MODULETABLE
            mov hProcessModuleTable,eax
            invoke GetDlgItem,hProcessModuleDlg,ID_TEXT1
            mov hText1,eax
            invoke GetDlgItem,hProcessModuleDlg,ID_TEXT2
            mov hText2,eax
            ;定义表格外观
            invoke SendMessage,hProcessModuleTable,LVM_SETEXTENDEDLISTVIEWSTYLE,\
                     0,LVS_EX_GRIDLINES or LVS_EX_FULLROWSELECT
            invoke ShowWindow,hProcessModuleTable,SW_SHOW
            ;清空表格内容
            invoke _clearResultView
            .elseif eax==WM_NOTIFY
               mov eax,lParam
               mov ebx,lParam
               ;更改各控件状态
               mov eax,[eax+NMHDR.hwndFrom]
               .if eax==hProcessModuleTable
                    mov ebx,lParam
                    .if [ebx+NMHDR.code]==NM_CUSTOMDRAW   ;绘画时
                      mov ebx,lParam
                      assume ebx:ptr NMLVCUSTOMDRAW
                      .if [ebx].nmcd.dwDrawStage==CDDS_PREPAINT
                          invoke SetWindowLong,hProcessModuleDlg,DWL_MSGRESULT,\
                                                    CDRF_NOTIFYITEMDRAW
                          mov eax,TRUE
                      .elseif [ebx].nmcd.dwDrawStage==CDDS_ITEMPREPAINT
                         ;当每一单元格内容被重新绘制前,判断
                          ;两列的值是否一致
                          invoke _GetListViewItem,hProcessModuleTable,\
                                          [ebx].nmcd.dwItemSpec,1,addr bufTemp1
                          invoke _GetListViewItem,hProcessModuleTable,\
                                          [ebx].nmcd.dwItemSpec,2,addr bufTemp2
                          invoke lstrlen,addr bufTemp1
                          invoke _MemCmp,addr bufTemp1,addr bufTemp2,eax
                          ;如果一致,则将文本的背景色设置为浅红色,否则为黑色
                          .if eax==1
                              mov [ebx].clrTextBk,0a0a0ffh
                          .else
                              mov [ebx].clrTextBk,0ffffffh
                          .endif
                          invoke SetWindowLong,hProcessModuleDlg,DWL_MSGRESULT,\
                                                             CDRF_DODEFAULT
                          mov eax,TRUE
                        .endif
                    .elseif [ebx+NMHDR.code]==NM_CLICK
                         assume ebx:ptr NMLISTVIEW
                    .endif
               .endif
            .elseif eax==WM_COMMAND
                mov eax,wParam
                .if ax==IDC_OK   ;执行对比
                    invoke _openFile
                .elseif ax==IDC_BROWSE1
                    invoke _OpenFile1     ;用户选择第一个文件
                .elseif ax==IDC_BROWSE2
                    invoke _OpenFile2     ;用户选择第二个文件
                .endif
           .else
                mov eax,FALSE
                ret
           .endif
           mov eax,TRUE
           ret
    _resultProcMain     endp

PE文件比较器窗口回调函数中最主要的代码集中在第31~58行。由窗口回调函数注册的监听器一旦发现由表格控件引发了绘画消息,则判断该绘画是否为表格项重画(第38行)。如果是,则获取要重画的项目当前所在行的第1列和第2列的值。这两个值来自于两个不同PE文件的同一个字段,通过判断二者是否相等来决定重画时使用的背景色。如果不相等,则将重画时的背景色设置为0a0a0ffh(浅红色),否则设置为0ffffffh(黑色)。

如上所示,当用户选择了两个要参与对比的PE文件以后,点击执行对比按钮,系统会调用函数_openFile。

2. _openFile函数

_openFile函数的功能是把两个PE文件数据结构中的相关字段的值取出,分别放到表格的第1列和第2列,判断两个值是否相等的代码在回调函数_resultProcMain中已经给出。

与前面的思路一样,程序还是使用了内存映射函数来操作参与对比的两个PE文件,所不同的是这里需要定义两个指针。一个指针指向第一个文件的内存映射函数的起始位置,另一个指针指向第二个文件的内存映射函数的起始位置。假设该工作已经完成,接下来就是把两个PE文件按照第3章里描述的所有字段的值取出来显示到表格中,完成该功能的代码如代码清单2-5所示。

 ;到此为止,两个内存文件的指针已经获取到了
 ;@lpMemory和@lpMemory1分别指向两个文件头
 ;下面是从这个文件头开始,先找出各数据结构的字段值,然后进行比较
 ;调整esi,edi指向DOS头
 mov esi,@lpMemory
 assume esi:ptr IMAGE_DOS_HEADER
 mov edi,@lpMemory1
 assume edi:ptr IMAGE_DOS_HEADER
  invoke _Header1
  ;调整esi,dei指针指向PE文件头
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  add edi,[edi].e_lfanew
  assume edi:ptr IMAGE_NT_HEADERS
  invoke _Header2
  movzx ecx,word ptr [esi+6]
  movzx eax,word ptr [edi+6]
  .if eax>ecx
      mov ecx,eax
  .endif
  ;调整esi,edi指针指向节表
  add esi,sizeof IMAGE_NT_HEADERS
  add edi,sizeof IMAGE_NT_HEADERS
  mov eax,1
  .repeat
     invoke _Header3
     dec ecx
     inc eax
     .break .if ecx==0
     add esi,sizeof IMAGE_SECTION_HEADER
     add edi,sizeof IMAGE_SECTION_HEADER
  .until FALSE

代码清单2-5 _openFile函数实现的部分代码

由于编码比较长,这里只以结构IMAGE_DOS_HEADER中的字段e_lpanew为例介绍程序设计的流程:将esi和edi分别赋值到两个PE文件的IMAGE_DOS_HEADER后,调用函数_Header1,处理数据结构DOS头的相关字段(第5~10行)。

3._Header1函数

_Header1函数完成了DOS头部分的字段比较,此部分的详细代码如代码清单2-6所示。

代码清单2-6 DOS头部分的字段比较函数_Header1(chapter2\pecomp.asm)

;--------------------------------------------
; IMAGE_DOS_HEADER头信息
;-------------------------------------------
_Header1 proc
    pushad
    invoke _addLine,addr szRec1,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec2,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec3,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec4,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec5,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec6,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec7,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec8,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec9,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec10,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec11,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec12,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec13,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec14,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec15,esi,edi,8
    add esi,8
    add edi,8
    invoke _addLine,addr szRec16,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec17,esi,edi,2
    add esi,2
    add edi,2
    invoke _addLine,addr szRec18,esi,edi,20
    add esi,20
    add edi,20
    invoke _addLine,addr szRec19,esi,edi,4
    popad
    ret
_Header1 endp

DOS头结构中字段e_lpanew的处理在第59~61行。首先,将esi和edi指向该字段所在内存位置;然后,调用_addLine函数将两个PE文件对应字段的值加入到表格中。

invoke _addLine,para1,para2,para3,para4

_addLine的参数描述如下:

para1:字段名称字符串所在地址。

para2:PE文件1该字段值所在内存地址。

para3:PE文件2该字段值所在内存地址。

para4:该字段的长度(即字节数)。

4. _addLine函数

该函数完成了在表格中增加一行的操作,具体定义如代码清单2-7所示。

代码清单2-7 在表格中增加一行的函数_addLine( chapter2\pecomp.asm)

;--------------------------------------------
; 在表格中增加一行
; _lpSZ为第一行要显示的字段名
; _lpSP1为第一个文件该字段的位置
; _lpSP2为第二个文件该字段的位置
; _Size为该字段的字节长度
;--------------------------------------------
_addLine proc _lpSZ,_lpSP1,_lpSP2,_Size
    pushad
    invoke _ListViewSetItem,hProcessModuleTable,dwCount,-1,\
                   _lpSZ                ;在表格中新增加一行
    mov dwCount,eax
    xor ebx,ebx
    invoke _ListViewSetItem,hProcessModuleTable,dwCount,ebx,\
           _lpSZ                        ;显示字段名
    invoke RtlZeroMemory,addr szBuffer,50
    invoke MemCopy,_lpSP1,addr bufTemp2,_Size
    invoke _Byte2Hex,_Size
    ;将指定字段按照十六进制显示,格式:一个字节+一个空格
    invoke lstrcat,addr szBuffer,addr bufTemp1
    inc ebx
    invoke _ListViewSetItem,hProcessModuleTable,dwCount,ebx,\
                        addr szBuffer ;第一个文件中的值
    invoke RtlZeroMemory,addr szBuffer,50
    invoke MemCopy,_lpSP2,addr bufTemp2,_Size
    invoke _Byte2Hex,_Size
    invoke lstrcat,addr szBuffer,addr bufTemp1
    inc ebx
    invoke _ListViewSetItem,hProcessModuleTable,dwCount,ebx,\
                        addr szBuffer ;第二个文件中的值
    popad
    ret
_addLine   endp

显示字段值的同时,会在消息处理函数中调用字节比对函数_MemCmp以确定值是否相同,如果不相同则将表格行的背景色设置为红色以示区别。其他字段的处理方式与字段IMAGE_DOS_HEADER. e_lpanew的处理方式类似,不再一一陈述。

2.3.4 运行PEComp

编译资源文件PEComp.rc,编译链接PEComp.asm生成最终的PEComp.exe程序;将随书文件中目录chapter2下的两个测试用文件peinfoNor.bin和peinfoVir.bin重命名,其扩展名都改为exe,然后运行PEComp.exe程序。

依次选择菜单“文件”→“打开”,弹出PE对比对话框窗口,在对话框中选择并打开刚才重命名的两个EXE文件,然后进行对比,运行效果如图2-5所示。

                                                                图2-5 PEComp运行效果

注意 请不要运行以上两个文件,因为peinfoVir.exe是个病毒文件。测试完以后请将扩展名改回原来的“bin”。

通过对比可以看出,在两个PE文件的文件头部结构中,字段不相同的部分来自最后一个节的描述。可以初步断定,该病毒程序是通过修改正常程序的最后一个节的相关字段的值来实现病毒代码携带的。


笔记:

peComp.rc文件:

#include 
#define ICO_MAIN  1000
#define DLG_MAIN  1000
#define IDC_INFO  1001
#define IDM_MAIN  2000
#define IDM_OPEN  2001
#define IDM_EXIT  2002
#define IDM_1    4000
#define IDM_2    4001
#define IDM_3    4002
#define IDM_4    4003
#define RESULT_MODULE 5000
#define ID_TEXT1  5001
#define ID_TEXT2  5002
#define IDC_MODULETABLE 5003
#define IDC_OK 5004
#define ID_STATIC 5005
#define ID_STATIC1 5006
#define IDC_BROWSE1 5007
#define IDC_BROWSE2 5008
#define IDC_THESAME 5009
ICO_MAIN  ICON  "main.ico"
DLG_MAIN DIALOG 50,50,544,399
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "PEComp by Scott"
MENU IDM_MAIN
FONT 9,"宋体"
BEGIN
   CONTROL "",IDC_INFO,"RichEdit20A",196 | ES_WANTRETURN | WS_CHILD | ES_READONLY
               | WS_VISIBLE |WS_BORDER | WS_VSCROLL | WS_TABSTOP,0,0,540,396
END
RESULT_MODULE DIALOG 76,10,630,480
STYLE DS_MODALFRAME | WS_POPUP |WS_VISIBLE | WS_CAPTION |WS_SYSMENU
CAPTION "PE文件对比结果"
FONT 9,"宋体"
BEGIN
  LTEXT "您选定的第一个文件为:",ID_STATIC,10,13,200,15
  EDITTEXT ID_TEXT1,130,13,440,15
  PUSHBUTTON "浏览...",IDC_BROWSE1,570,13,50,14
  LTEXT "您选定的第二个文件为:",ID_STATIC1,10,35,200,15
  EDITTEXT ID_TEXT2,130,35,440,15
  PUSHBUTTON "浏览...",IDC_BROWSE2,570,35,50,14
  CONTROL "", IDC_MODULETABLE, "SysListView32",13 | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 10,60,610,390
  AUTOCHECKBOX "只显示不同的值" IDC_THESAME,10,460,100,14
  PUSHBUTTON "执行...(&R)",IDC_OK,570,460,50,14
END
IDM_MAIN menu discardable
BEGIN
  POPUP "文件(&F)"
  BEGIN
    menuitem "打开PE对比对话框",IDM_OPEN
    menuitem "---",IDM_1
    menuitem "---",IDM_2
    menuitem "---",IDM_3 CHECKED
    menuitem separator
    menuitem "退出(&x)",IDM_EXIT
  END
  POPUP "编辑(&E)"
  BEGIN
    menuitem separator
  END
  POPUP "格式(&O)"
  BEGIN
    menuitem separator
  END
  POPUP "查看(&V)"
  BEGIN
    menuitem "源文件",IDM_1
    menuitem "窗口透明度",IDM_2
    menuitem separator
    menuitem "大小",IDM_3
    menuitem "宽度",IDM_4
  END
  POPUP "帮助(&H)"
  BEGIN
    menuitem separator
  END
END

peComp.asm文件:

;peComp.asm   通用程序框架
;使用 nmake 或下列命令进行编译和链接:
;ml -c -coff peComp.asm
;rc -r peComp.rc
;link -subsystem:windows peComp.obj peComp.res
.386
.model flat, stdcall
option casemap:none
include		c:/masm32/include/windows.inc
include 	c:/masm32/include/user32.inc
include 	c:/masm32/include/kernel32.inc
include 	c:/masm32/include/comdlg32.inc
include 	c:/masm32/include/gdi32.inc
include 	c:/masm32/include/comctl32.inc
include 	c:/masm32/include/comdlg32.inc
include 	c:/masm32/include/advapi32.inc
include 	c:/masm32/include/shell32.inc
include 	c:/masm32/include/masm32.inc
include 	c:/masm32/include/netapi32.inc
include 	c:/masm32/include/winmm.inc
include 	c:/masm32/include/ws2_32.inc
include 	c:/masm32/include/psapi.inc
include 	c:/masm32/include/mpr.inc 			;WNetCancelConnection2
include 	c:/masm32/include/iphlpapi.inc 		;SendARP
includelib 	c:/masm32/lib/user32.lib
includelib 	c:/masm32/lib/kernel32.lib
includelib 	c:/masm32/lib/comdlg32.lib
includelib 	c:/masm32/lib/comdlg32.lib
includelib 	c:/masm32/lib/gdi32.lib
includelib 	c:/masm32/lib/comctl32.lib
includelib 	c:/masm32/lib/comdlg32.lib
includelib 	c:/masm32/lib/advapi32.lib
includelib 	c:/masm32/lib/shell32.lib
includelib 	c:/masm32/lib/masm32.lib
includelib 	c:/masm32/lib/netapi32.lib
includelib 	c:/masm32/lib/winmm.lib
includelib 	c:/masm32/lib/ws2_32.lib
includelib 	c:/masm32/lib/psapi.lib
includelib 	c:/masm32/lib/mpr.lib
includelib 	c:/masm32/lib/iphlpapi.lib
ICO_MAIN	equ 1000
DLG_MAIN 	equ 1000
IDC_INFO	equ 1001
IDM_MAIN 	equ 2000
IDM_OPEN 	equ 2001
IDM_EXIT 	equ 2002
IDM_1		equ 4000
IDM_2 		equ 4001
IDM_3 		equ 4002
RESULT_MODULE   equ 5000
ID_TEXT1        equ 5001
ID_TEXT2        equ 5002
IDC_MODULETABLE equ 5003
IDC_OK          equ 5004
ID_STATIC       equ 5005
ID_STATIC1      equ 5006
IDC_BROWSE1     equ 5007
IDC_BROWSE2     equ 5008
IDC_THESAME     equ 5009
.data
hInstance 	dword ?
hRichEdit 	dword ?
hWinMain	dword ?
hWinEdit	dword ?
dwCount 	dword ?
dwColorRed 	dword ?
hText1 		dword ?
hText2 		dword ?
hFile 		dword ?
hProcessModuleTable dword ?
szFileName 			byte MAX_PATH dup(?)
szFileNameOpen1		byte MAX_PATH dup(0)
szFileNameOpen2 	byte MAX_PATH dup(0)
szResultColName1	byte 'PE数据结构相关字段',0
szResultColName2 	byte '文件1的值(H)',0
szResultColName3 	byte '文件2的值(H)',0
szBuffer 			byte 256 dup(0), 0
bufTemp1 			byte 200 dup(0), 0
bufTemp2 			byte 200 dup(0), 0
szFilter1 			byte 'Excutable Files',0,'*.exe;*.com',0
					byte 0
.const
szDllEdit 	byte 'RichEd20.dll', 0
szClassEdit byte 'RichEdit20A', 0
szFont 		byte '宋体', 0
szExtPe 	byte 'PE File',0,'*.exe;*.dll;*.scr;*.fon;*.drv',0
			byte 'All Files(*.*)',0,'*.*',0,0
szErr 		byte '文件格式错误!',0
szErrFormat byte '这个文件不是PE格式的文件!',0
szSuccess 	byte '恭喜你,程序执行到这里是成功的。',0
szNotFound 	byte '无法查找',0
szRec1      byte 'IMAGE_DOS_HEADER.e_magic',0
szRec2      byte 'IMAGE_DOS_HEADER.e_cblp',0
szRec3      byte 'IMAGE_DOS_HEADER.e_cp',0
szRec4      byte 'IMAGE_DOS_HEADER.e_crlc',0
szRec5      byte 'IMAGE_DOS_HEADER.e_cparhdr',0
szRec6      byte 'IMAGE_DOS_HEADER.e_minalloc',0
szRec7      byte 'IMAGE_DOS_HEADER.e_maxalloc',0
szRec8      byte 'IMAGE_DOS_HEADER.e_ss',0
szRec9      byte 'IMAGE_DOS_HEADER.e_sp',0
szRec10     byte 'IMAGE_DOS_HEADER.e_csum',0
szRec11     byte 'IMAGE_DOS_HEADER.e_ip',0
szRec12     byte 'IMAGE_DOS_HEADER.e_cs',0
szRec13     byte 'IMAGE_DOS_HEADER.e_lfarlc',0
szRec14     byte 'IMAGE_DOS_HEADER.e_ovno',0
szRec15     byte 'IMAGE_DOS_HEADER.e_res',0
szRec16     byte 'IMAGE_DOS_HEADER.e_oemid',0
szRec17     byte 'IMAGE_DOS_HEADER.e_oeminfo',0
szRec18     byte 'IMAGE_DOS_HEADER.e_res2',0
szRec19     byte 'IMAGE_DOS_HEADER.e_lfanew',0
szRec20     byte 'IMAGE_NT_HEADERS.Signature',0
szRec21     byte 'IMAGE_FILE_HEADER.Machine',0
szRec22     byte 'IMAGE_FILE_HEADER.NumberOfSections',0
szRec23     byte 'IMAGE_FILE_HEADER.TimeDateStamp',0
szRec24     byte 'IMAGE_FILE_HEADER.PointerToSymbolTable',0
szRec25     byte 'IMAGE_FILE_HEADER.NumberOfSymbols',0
szRec26     byte 'IMAGE_FILE_HEADER.SizeOfOptionalHeader',0
szRec27     byte 'IMAGE_FILE_HEADER.Characteristics',0
szRec28     byte 'IMAGE_OPTIONAL_HEADER32.Magic',0
szRec29     byte 'IMAGE_OPTIONAL_HEADER32.MajorLinkerVersion',0
szRec30     byte 'IMAGE_OPTIONAL_HEADER32.MinorLinkerVersion',0
szRec31     byte 'IMAGE_OPTIONAL_HEADER32.SizeOfCode',0
szRec32     byte 'IMAGE_OPTIONAL_HEADER32.SizeOfInitializedData',0
szRec33     byte 'IMAGE_OPTIONAL_HEADER32.SizeOfUninitializedData',0
szRec34     byte 'IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint',0
szRec35     byte 'IMAGE_OPTIONAL_HEADER32.BaseOfCode',0
szRec36     byte 'IMAGE_OPTIONAL_HEADER32.BaseOfData',0
szRec37     byte 'IMAGE_OPTIONAL_HEADER32.ImageBase',0
szRec38     byte 'IMAGE_OPTIONAL_HEADER32.SectionAlignment',0
szRec39     byte 'IMAGE_OPTIONAL_HEADER32.FileAlignment',0
szRec40     byte 'IMAGE_OPTIONAL_HEADER32.MajorOperatingSystemVersion',0
szRec41     byte 'IMAGE_OPTIONAL_HEADER32.MinorOperatingSystemVersion',0
szRec42     byte 'IMAGE_OPTIONAL_HEADER32.MajorImageVersion',0
szRec43     byte 'IMAGE_OPTIONAL_HEADER32.MinorImageVersion',0
szRec44     byte 'IMAGE_OPTIONAL_HEADER32.MajorSubsystemVersion',0
szRec45     byte 'IMAGE_OPTIONAL_HEADER32.MinorSubsystemVersion',0
szRec46     byte 'IMAGE_OPTIONAL_HEADER32.Win32VersionValue',0
szRec47     byte 'IMAGE_OPTIONAL_HEADER32.SizeOfImage',0
szRec48     byte 'IMAGE_OPTIONAL_HEADER32.SizeOfHeaders',0
szRec49     byte 'IMAGE_OPTIONAL_HEADER32.CheckSum',0
szRec50     byte 'IMAGE_OPTIONAL_HEADER32.Subsystem',0
szRec51     byte 'IMAGE_OPTIONAL_HEADER32.DllCharacteristics',0
szRec52     byte 'IMAGE_OPTIONAL_HEADER32.SizeOfStackReserve',0
szRec53     byte 'IMAGE_OPTIONAL_HEADER32.SizeOfStackCommit',0
szRec54     byte 'IMAGE_OPTIONAL_HEADER32.SizeOfHeapReserve',0
szRec55     byte 'IMAGE_OPTIONAL_HEADER32.SizeOfHeapCommit',0
szRec56     byte 'IMAGE_OPTIONAL_HEADER32.LoaderFlags',0
szRec57     byte 'IMAGE_OPTIONAL_HEADER32.NumberOfRvaAndSizes',0
szRec58     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Export)',0
szRec59     byte 'IMAGE_DATA_DIRECTORY.isize(Export)',0
szRec60     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Import)',0
szRec61     byte 'IMAGE_DATA_DIRECTORY.isize(Import)',0
szRec62     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Resource)',0
szRec63     byte 'IMAGE_DATA_DIRECTORY.isize(Resource)',0
szRec64     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Exception)',0
szRec65     byte 'IMAGE_DATA_DIRECTORY.isize(Exception)',0
szRec66     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Security)',0
szRec67     byte 'IMAGE_DATA_DIRECTORY.isize(Security)',0
szRec68     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(BaseReloc)',0
szRec69     byte 'IMAGE_DATA_DIRECTORY.isize(BaseReloc)',0
szRec70     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Debug)',0
szRec71     byte 'IMAGE_DATA_DIRECTORY.isize(Debug)',0
szRec72     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Architecture)',0
szRec73     byte 'IMAGE_DATA_DIRECTORY.isize(Architecture)',0
szRec74     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(GlobalPTR)',0
szRec75     byte 'IMAGE_DATA_DIRECTORY.isize(GlobalPTR)',0
szRec76     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(TLS)',0
szRec77     byte 'IMAGE_DATA_DIRECTORY.isize(TLS)',0
szRec78     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Load_Config)',0
szRec79     byte 'IMAGE_DATA_DIRECTORY.isize(Load_Config)',0
szRec80     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Bound_Import)',0
szRec81     byte 'IMAGE_DATA_DIRECTORY.isize(Bound_Import)',0
szRec82     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(IAT)',0
szRec83     byte 'IMAGE_DATA_DIRECTORY.isize(IAT)',0
szRec84     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Delay_Import)',0
szRec85     byte 'IMAGE_DATA_DIRECTORY.isize(Delay_Import)',0
szRec86     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Com_Descriptor)',0
szRec87     byte 'IMAGE_DATA_DIRECTORY.isize(Com_Descriptor)',0
szRec88     byte 'IMAGE_DATA_DIRECTORY.VirtualAddress(Reserved)',0
szRec89     byte 'IMAGE_DATA_DIRECTORY.isize(Reserved)',0
szRec90     byte 'IMAGE_SECTION_HEADER%d.Name1',0
szRec91     byte 'IMAGE_SECTION_HEADER%d.VirtualSize',0
szRec92     byte 'IMAGE_SECTION_HEADER%d.VirtualAddress',0
szRec93     byte 'IMAGE_SECTION_HEADER%d.SizeOfRawData',0
szRec94     byte 'IMAGE_SECTION_HEADER%d.PointerToRawData',0
szRec95     byte 'IMAGE_SECTION_HEADER%d.PointerToRelocations',0
szRec96     byte 'IMAGE_SECTION_HEADER%d.PointerToLinenumbers',0
szRec97     byte 'IMAGE_SECTION_HEADER%d.NumberOfRelocations',0
szRec98     byte 'IMAGE_SECTION_HEADER%d.NumberOfLinenumbers',0
szRec99     byte 'IMAGE_SECTION_HEADER%d.Characteristics',0
szOut1      byte '%02x',0
szOut2      byte '%04x',0
lpszHexArr  byte '0123456789ABCDEF',0
.data?
stLVC 	LV_COLUMN
stLVI 	LV_ITEM
.code
;初始化窗口程序
_init proc
	local @stCf:CHARFORMAT
	invoke GetDlgItem, hWinMain, IDC_INFO
	mov hWinEdit, eax
	;为窗口设置图标
	invoke LoadIcon, hInstance, ICO_MAIN
	invoke SendMessage, hWinMain, WM_SETICON, ICON_BIG, eax
	;设置编辑控件
	invoke SendMessage, hWinEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0
	invoke RtlZeroMemory, addr @stCf, sizeof @stCf 					;初始化
	mov @stCf.cbSize, sizeof @stCf
	mov @stCf.yHeight, 9*20
	mov @stCf.dwMask, CFM_FACE or CFM_SIZE or CFM_BOLD
	invoke lstrcpy, addr @stCf.szFaceName, addr szFont
	invoke SendMessage, hWinEdit, EM_SETCHARFORMAT, 0, addr @stCf
	invoke SendMessage, hWinEdit, EM_EXLIMITTEXT, 0, -1
	ret
_init endp
;-------------------------------
;错误Handler
;--------------------------------
_Handler proc _lpExceptionRecord, _lpSEH, \
			_lpContext, _lpDispathcerContext
	pushad
	mov esi, _lpExceptionRecord
	mov edi, _lpContext
	assume esi:ptr EXCEPTION_RECORD, edi:ptr CONTEXT
	mov eax, _lpSEH
	push [eax+0ch]
	pop [edi].regEbp
	push [eax+8]
	pop [edi].regEip
	push eax
	pop [edi].regEsp
	assume esi:nothing, edi:nothing
	popad
	mov eax, ExceptionContinueExecution
	ret
_Handler endp
;-------------------------------
; 将内存偏移量RVA转换为文件偏移
;-------------------------------
_RVAToOffset proc _lpFileHead, _dwRVA
	local @dwReturn
	pushad
	mov esi, _lpFileHead
	assume esi:ptr IMAGE_DOS_HEADER
	add esi, [esi].e_lfanew
	assume esi:ptr IMAGE_NT_HEADERS
	mov edi, _dwRVA
	mov edx, esi
	add edx, sizeof IMAGE_NT_HEADERS
	assume edx:ptr IMAGE_SECTION_HEADER
	movzx ecx, [esi].FileHeader.NumberOfSections
	;遍历节表
	.repeat
		mov eax, [edx].VirtualAddress
		add eax, [edx].SizeOfRawData		;计算该节结束RVA    相对虚拟地址
		.if (edi >= [edx].VirtualAddress) && (edi < eax)
			mov eax, [edx].VirtualAddress
			sub edi, eax 					;计算RVA在节中的偏移
			mov eax, [edx].PointerToRawData
			add eax, edi 					;加上节在文件中的的起始位置
			jmp @F
		.endif
		add edx, sizeof IMAGE_SECTION_HEADER
	.untilcxz
	assume edx:nothing
	assume esi:nothing
	mov eax, -1
@@:
	mov @dwReturn, eax
	popad
	mov eax, @dwReturn
	ret
_RVAToOffset endp
;------------------------
; 获取RVA所在节的名称
;------------------------
_getRVASectionName proc _lpFileHead, _dwRVA
	local @dwReturn
	pushad
	mov esi, _lpFileHead
	assume esi:ptr IMAGE_DOS_HEADER
	add esi, [esi].e_lfanew
	assume esi:ptr IMAGE_NT_HEADERS
	mov edi, _dwRVA
	mov edx, esi
	add edx, sizeof IMAGE_NT_HEADERS
	assume edx:ptr IMAGE_SECTION_HEADER
	movzx ecx, [esi].FileHeader.NumberOfSections
	;遍历节表
	.repeat
		mov eax, [edx].VirtualAddress
		add eax,[edx].SizeOfRawData  ;计算该节结束RVA
		.if (edi>=[edx].VirtualAddress) && (edi < eax)
			mov eax, edx
			jmp @F
		.endif
		add edx, sizeof IMAGE_SECTION_HEADER
	.untilcxz
	assume edx:nothing
	assume esi:nothing
	mov eax, offset szNotFound
@@:
	mov @dwReturn, eax
	popad
	mov eax, @dwReturn
	ret
_getRVASectionName endp
;---------------------------------
;在ListView中增加一个列
;输入:_dwColumn = 增加的列编号
;	  _dwWidth = 列的宽度
;	  _lpszHead = 列的标题字符串
;---------------------------------
_ListViewAddColumn proc uses ebx ecx _hWinView, _dwColumn, _dwWidth, _lpszHead
	local @stLVC:LV_COLUMN
	invoke RtlZeroMemory, addr @stLVC, sizeof LV_COLUMN
	mov @stLVC.imask, LVCF_TEXT or LVCF_WIDTH or LVCF_FMT
	mov @stLVC.fmt, LVCFMT_LEFT
	push _lpszHead
	pop @stLVC.pszText
	push _dwWidth
	pop @stLVC.lx
	push _dwColumn
	pop @stLVC.iSubItem
	invoke SendMessage, _hWinView, LVM_INSERTCOLUMN, _dwColumn, addr @stLVC
	ret
_ListViewAddColumn endp
;----------------------------------------------------------------------
; 在ListView中新增一行,或修改一行中某个字段的内容
; 输入:_dwItem = 要修改的行的编号
;	   _dwSubItem = 要修改的字段的编号,-1表示插入新的行,>=1表示字段的编号
;-----------------------------------------------------------------------
_ListViewSetItem proc uses ebx ecx _hWinView, _dwItem, _dwSubItem, _lpszText
	invoke RtlZeroMemory, addr stLVI, sizeof LV_ITEM
	invoke lstrlen, _lpszText
	mov stLVI.cchTextMax, eax
	mov stLVI.imask, LVIF_TEXT
	push _lpszText
	pop stLVI.pszText
	push _dwItem
	pop stLVI.iItem
	push _dwSubItem
	pop stLVI.iSubItem
	.if _dwSubItem == -1
		mov stLVI.iSubItem, 0
		invoke SendMessage, _hWinView, LVM_INSERTITEM, NULL, addr stLVI
	.else
		invoke SendMessage, _hWinView, LVM_SETITEM, NULL, addr stLVI
	.endif
	ret
_ListViewSetItem endp
;----------------------
; 清除ListView中的内容
; 删除所有的行和所有的列
;----------------------
_ListViewClear proc uses ebx ecx _hWinView
	invoke SendMessage, _hWinView, LVM_DELETEALLITEMS, 0, 0
	.while TRUE
		invoke SendMessage, _hWinView, LVM_DELETECOLUMN, 0, 0
		.break .if !eax
	.endw
	ret
_ListViewClear endp
;---------------------
; 返回指定行列的值
; 结果在szBuffer中
;---------------------
_GetListViewItem proc _hWinView:DWORD, _dwLine:DWORD, _dwCol:DWORD, _lpszText
	local @stLVI:LV_ITEM
	invoke RtlZeroMemory, addr @stLVI, sizeof LV_ITEM
	invoke RtlZeroMemory, _lpszText, 512
	mov @stLVI.cchTextMax, 512
	mov @stLVI.imask, LVIF_TEXT
	push _lpszText
	pop @stLVI.pszText
	push _dwCol
	pop @stLVI.iSubItem
	invoke SendMessage, _hWinView, LVM_GETITEMTEXT, _dwLine, addr @stLVI
	ret
_GetListViewItem endp
;---------------------
; 初始化结果表格
;---------------------
_clearResultView proc uses ebx ecx
	invoke _ListViewClear, hProcessModuleTable
	;添加表头
	mov ebx, 1
	mov eax, 200
	lea ecx, szResultColName1
	invoke _ListViewAddColumn, hProcessModuleTable, ebx, eax, ecx
	mov ebx, 2
	mov eax, 400
	lea ecx, szResultColName2
	invoke _ListViewAddColumn, hProcessModuleTable, ebx, eax, ecx
	mov ebx, 3
	mov eax, 400
	lea ecx, szResultColName3
	invoke _ListViewAddColumn, hProcessModuleTable, ebx, eax, ecx
	mov dwCount, 0
	ret
_clearResultView endp
;------------------------------------------
; 打开输入文件
;------------------------------------------
_OpenFile1 proc
	local @stOF:OPENFILENAME 				;openfilename
	local @stES:EDITSTREAM 					;editstream
	;如果打开之前还有文件句柄存在,则先关闭再赋值
	.if hFile
		invoke CloseHandle, hFile
		mov hFile, 0
	.endif
	;显示“打开文件”对话框
	invoke RtlZeroMemory, addr @stOF, sizeof @stOF
	mov @stOF.lStructSize, sizeof @stOF
	push hWinMain
	pop @stOF.hwndOwner
	push hInstance
	pop @stOF.hInstance
	mov @stOF.lpstrFilter, offset szFilter1
	mov @stOF.lpstrFile, offset szFileNameOpen1
	mov @stOF.nMaxFile, MAX_PATH
	mov @stOF.Flags, OFN_FILEMUSTEXIST or \
						OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
	invoke GetOpenFileName, addr @stOF
	.if eax
		invoke SetWindowText, hText1, addr szFileNameOpen1
	.endif
	ret
_OpenFile1 endp
;------------------------------------------
; 打开输入文件
;------------------------------------------
_OpenFile2 proc
	local @stOF:OPENFILENAME
	local @stES:EDITSTREAM
	;如果打开之前还有文件句柄存在,则先关闭再赋值
	.if hFile
		invoke CloseHandle, hFile
		mov hFile, 0
	.endif
	;显示“打开文件”对话框
	invoke RtlZeroMemory, addr @stOF, sizeof @stOF
	mov @stOF.lStructSize, sizeof @stOF
	push hWinMain
	pop @stOF.hwndOwner
	push hInstance
	pop @stOF.hInstance
	mov @stOF.lpstrFilter, offset szFilter1
	mov @stOF.lpstrFile, offset szFileNameOpen2
	mov @stOF.nMaxFile, MAX_PATH
	mov @stOF.Flags, OFN_FILEMUSTEXIST or \
						OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
	invoke GetOpenFileName, addr @stOF
	.if eax
		invoke SetWindowText, hText2, addr szFileNameOpen2
	.endif
	ret
_OpenFile2 endp
;-------------------------------------------------
; 将_lpPoint位置处_dwSize个字节转换为16进制的字符串
; bufTemp1处为转换后的字符串
;-------------------------------------------------
_Byte2Hex proc _dwSize
	local @dwSize:dword
	pushad
	mov esi, offset bufTemp2
	mov edi, offset bufTemp1
	mov @dwSize, 0
	.repeat
		mov al, byte ptr [esi]
		mov bl, al
		xor edx, edx
		xor eax, eax
		mov al, bl
		mov cx, 16
		div cx 				;结果高位在al中,余数在dl中
		xor bx, bx
		mov bl, al
		movzx edi, bx
		mov bl, byte ptr lpszHexArr[edi]
		mov eax, @dwSize
		mov byte ptr bufTemp1[eax], bl
		inc @dwSize
		xor bx, bx
		mov bl, dl
		movzx edi, bx
		;invoke wsprintf,addr szBuffer,addr szOut2,edx
		;invoke MessageBox,NULL,addr szBuffer,NULL,MB_OK
		mov bl, byte ptr lpszHexArr[edi]
		mov eax, @dwSize
		mov byte ptr bufTemp1[eax], bl
		inc @dwSize
		mov bl, 20h
		mov eax, @dwSize
		mov byte ptr bufTemp1[eax], bl
		inc @dwSize
		inc esi
		dec _dwSize
		.break .if _dwSize == 0
	.until FALSE
	mov bl, 0
	mov eax, @dwSize
	mov byte ptr bufTemp1[eax],bl
	popad
	ret
_Byte2Hex endp
_MemCmp proc _lp1, _lp2, _size
	local @dwResult:dword
	pushad
	mov esi, _lp1
	mov edi, _lp2
	mov ecx, _size
	.repeat
		mov al, byte ptr [esi]
		mov bl, byte ptr [edi]
		.break .if al != bl
		inc esi
		inc edi
		dec ecx
		.break .if ecx == 0
	.until FALSE
	.if ecx != 0
		mov @dwResult, 1
	.else
		mov @dwResult, 0
	.endif
	popad
	mov eax, @dwResult
	ret
_MemCmp endp
;--------------------------------------------
; 在表格中增加一行
; _lpSZ为第一行要显示的字段名
; _lpSP1为第一个文件该字段的位置
; _lpSP2为第二个文件该字段的位置
; _Size为该字段的字节长度
;--------------------------------------------
_addLine proc _lpSZ, _lpSP1, _lpSP2, _Size
	pushad
	invoke _ListViewSetItem, hProcessModuleTable, dwCount, -1, \
				_lpSZ 				;在表格中新增加一行
	mov dwCount, eax
	xor ebx, ebx
	invoke _ListViewSetItem, hProcessModuleTable, dwCount, ebx, \
				_lpSZ 				;显示字段名
	invoke RtlZeroMemory, addr szBuffer, 50
	invoke MemCopy, _lpSP1, addr bufTemp2, _Size
	invoke _Byte2Hex, _Size
	;将指定字段按照十六进制显示,格式:一个字节+一个空格
	invoke lstrcat, addr szBuffer, addr bufTemp1
	inc ebx
	invoke _ListViewSetItem, hProcessModuleTable, dwCount, ebx, \
				addr szBuffer 			;第一个文件中的值
	invoke RtlZeroMemory, addr szBuffer, 50
	invoke MemCopy, _lpSP2, addr bufTemp2, _Size
	invoke _Byte2Hex, _Size
	invoke lstrcat, addr szBuffer, addr bufTemp1
	inc ebx
	invoke _ListViewSetItem, hProcessModuleTable, dwCount, ebx, \
				addr szBuffer			;第二个文件中的值
	popad
	ret
_addLine endp
;-----------------------
; IMAGE_DOS_HEADER头信息
;-----------------------
_Header1 proc
	pushad
	invoke _addLine, addr szRec1, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec2, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec3, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec4, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec5, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec6, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec7, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec8, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec9, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec10, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec11, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec12, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec13, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec14, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec15, esi, edi, 8
	add esi, 8
	add edi, 8
	invoke _addLine, addr szRec16, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec17, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec18, esi, edi, 20
	add esi,20
	add edi,20
	invoke _addLine,addr szRec19,esi,edi,4
	popad
	ret
_Header1 endp
;-----------------------
; IMAGE_DOS_HEADER头信息
;-----------------------
_Header2 proc
	pushad
	invoke _addLine, addr szRec20, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec21, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec22, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec23, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec24, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec25, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec26, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec27, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec28, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec29, esi, edi, 1
	add esi, 1
	add edi, 1
	invoke _addLine, addr szRec30, esi, edi, 1
	add esi, 1
	add edi, 1
	invoke _addLine, addr szRec31, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec32, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec33, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec34, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec35, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec36, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec37, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec38, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec39, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec40, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec41, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec42, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec43, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec44, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec45, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec46, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec47, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec48, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec49, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec50, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec51, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke _addLine, addr szRec52, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec53, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec54, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec55, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec56, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec57, esi, edi, 4
	;IMAGE_DATA_DIRECTORY
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec58, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec59, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec60, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec61, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec62, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec63, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec64, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec65, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec66, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec67, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec68, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec69, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec70, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec71, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec72, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec73, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec74, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec75, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec76, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec77, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec78, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec79, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec80, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec81, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec82, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec83, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec84, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec85, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec86, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec87, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec88, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke _addLine, addr szRec89, esi, edi, 4
	popad
	ret
_Header2 endp
;---------------------------------------
; 节表
; eax=节序号
;---------------------------------------
_Header3 proc
	local _dwValue:dword
	pushad
	mov _dwValue, eax
	invoke wsprintf, addr szBuffer, addr szRec90, _dwValue
	invoke _addLine, addr szBuffer, esi, edi, 8
	add esi, 8
	add edi, 8
	invoke wsprintf, addr szBuffer, addr szRec91, _dwValue
	invoke _addLine, addr szBuffer, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke wsprintf, addr szBuffer, addr szRec92, _dwValue
	invoke _addLine, addr szBuffer, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke wsprintf, addr szBuffer, addr szRec93, _dwValue
	invoke _addLine, addr szBuffer, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke wsprintf, addr szBuffer, addr szRec94, _dwValue
	invoke _addLine, addr szBuffer, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke wsprintf, addr szBuffer, addr szRec95, _dwValue
	invoke _addLine, addr szBuffer, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke wsprintf, addr szBuffer, addr szRec96, _dwValue
	invoke _addLine, addr szBuffer, esi, edi, 4
	add esi, 4
	add edi, 4
	invoke wsprintf, addr szBuffer, addr szRec97, _dwValue
	invoke _addLine, addr szBuffer, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke wsprintf, addr szBuffer, addr szRec98, _dwValue
	invoke _addLine, addr szBuffer, esi, edi, 2
	add esi, 2
	add edi, 2
	invoke wsprintf, addr szBuffer, addr szRec99, _dwValue
	invoke _addLine, addr szBuffer, esi, edi, 4
	popad
	ret
_Header3 endp
;_goHere
;--------------------
; 打开PE文件并处理
;--------------------
_openFile proc
	local @stOF:OPENFILENAME
	local @hFile, @dwFileSize, @hMapFile, @lpMemory
	local @hFile1, @dwFileSize1, @hMapFile1, @lpMemory1
	local @bufTemp1[10]:byte
	local @dwTemp:dword
	invoke CreateFile, addr szFileNameOpen1, GENERIC_READ, \
			FILE_SHARE_READ or FILE_SHARE_WRITE, NULL, \
			OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL
	.if eax != INVALID_HANDLE_VALUE
		mov @hFile, eax
		invoke GetFileSize, eax, NULL
		mov @dwFileSize, eax
		.if eax
			invoke CreateFileMapping, @hFile, \			;内存映射文件
					NULL, PAGE_READONLY, 0, 0, NULL
			.if eax
				mov @hMapFile, eax
				invoke MapViewOfFile, eax, \
						FILE_MAP_READ, 0, 0, 0
				.if eax
					mov @lpMemory, eax 				;获得文件在内存的映象起始位置
					assume fs:nothing
					push ebp
					push offset _ErrFormat
					push offset _Handler
					push fs:[0]
					mov fs:[0], esp
					;检测PE文件是否有效
					mov esi, @lpMemory
					assume esi:ptr IMAGE_DOS_HEADER
					.if [esi].e_magic != IMAGE_DOS_SIGNATURE 	;判断是否有MZ字样
						jmp _ErrFormat
					.endif
					add esi, [esi].e_lfanew 					;调整ESI指针指向PE文件头
					assume esi:ptr IMAGE_NT_HEADERS
					.if [esi].Signature != IMAGE_NT_SIGNATURE	;判断是否有PE字样
						jmp _ErrFormat
					.endif
				.endif
			.endif
		.endif
	.endif
	invoke CreateFile, addr szFileNameOpen2, GENERIC_READ, \
			FILE_SHARE_READ or FILE_SHARE_WRITE, NULL, \
			OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL
	.if eax != INVALID_HANDLE_VALUE
		mov @hFile1, eax
		invoke GetFileSize, eax, NULL
		mov @dwFileSize1, eax
		.if eax
			invoke CreateFileMapping, @hFile1, \				;内存映射文件
					NULL, PAGE_READONLY, 0, 0, NULL
			.if eax
				mov @hMapFile1, eax
				invoke MapViewOfFile, eax, \
						FILE_MAP_READ, 0, 0, 0
				.if eax
					mov @lpMemory1, eax 						;获得文件在内存的映象起始位置
					assume fs:nothing
					push ebp
					push offset _ErrFormat1
					push offset _Handler
					push fs:[0]
					mov fs:[0], esp
					;检测PE文件是否有效
					mov esi, @lpMemory1
					assume esi:ptr IMAGE_DOS_HEADER
					.if [esi].e_magic != IMAGE_DOS_SIGNATURE 	;判断是否有MZ字样
						jmp _ErrFormat1
					.endif
					add esi, [esi].e_lfanew 					;调整ESI指针指向PE文件头
					assume esi:ptr IMAGE_NT_HEADERS
					.if [esi].Signature != IMAGE_NT_SIGNATURE 	;判断是否有PE字样
						jmp _ErrFormat1
					.endif
				.endif
			.endif
		.endif
	.endif
	;到此为止,两个内存文件的指针已经获取到了。
	;@lpMemory和@lpMemory1分别指向两个文件头
	;下面是从这个文件头开始,找出各数据结构的字段值,进行比较。
	;调整ESI,EDI指向DOS头
	mov esi, @lpMemory
	assume esi:ptr IMAGE_DOS_HEADER
	mov edi, @lpMemory1
	assume edi:ptr IMAGE_DOS_HEADER
	invoke _Header1
	;调整ESI,EDI指针指向PE文件头
	add esi, [esi].e_lfanew
	assume esi:ptr IMAGE_NT_HEADERS
	add edi, [edi].e_lfanew
	assume edi:ptr IMAGE_NT_HEADERS
	invoke _Header2
	movzx ecx, word ptr [esi+6]
	movzx eax, word ptr [edi+6]
	.if eax > ecx
		mov ecx, eax
	.endif
	;调整ESI,EDI指针指向节表
	add esi, sizeof IMAGE_NT_HEADERS
	add edi, sizeof IMAGE_NT_HEADERS
	mov eax, 1
	.repeat
		invoke _Header3
		dec ecx
		inc eax
		.break .if ecx == 0
		add esi, sizeof IMAGE_SECTION_HEADER
		add edi, sizeof IMAGE_SECTION_HEADER
	.until FALSE
	jmp _ErrorExit 			;正常退出
_ErrFormat:
	invoke MessageBox, hWinMain, offset szErrFormat, NULL, MB_OK
_ErrorExit:
	pop fs:[0]
	add esp, 0ch
	invoke UnmapViewOfFile, @lpMemory
	invoke CloseHandle, @hMapFile
	invoke CloseHandle, @hFile
	jmp @F
_ErrFormat1:
	invoke MessageBox, hWinMain, offset szErrFormat, NULL, MB_OK
_ErrorExit1:
	pop fs:[0]
	add esp, 0ch
	invoke UnmapViewOfFile, @lpMemory1
	invoke CloseHandle, @hMapFile1
	invoke CloseHandle, @hFile1
@@:
	ret
_openFile endp
;-----------------------
; 弹出PE对比窗口回调函数
;----------------------
_resultProcMain proc uses ebx edi esi hProcessModuleDlg:HWND, wMsg, wParam, lParam
	mov eax, wMsg
	.if eax == WM_CLOSE
		invoke EndDialog, hProcessModuleDlg, NULL
	.elseif eax == WM_INITDIALOG
		invoke GetDlgItem, hProcessModuleDlg, IDC_MODULETABLE
		mov hProcessModuleTable, eax
		invoke GetDlgItem, hProcessModuleDlg, ID_TEXT1
		mov hText1, eax
		invoke GetDlgItem, hProcessModuleDlg, ID_TEXT2
		mov hText2, eax
		;定义表格外观
		invoke SendMessage, hProcessModuleTable, LVM_SETEXTENDEDLISTVIEWSTYLE, \
				0, LVS_EX_GRIDLINES or LVS_EX_FULLROWSELECT
		invoke ShowWindow, hProcessModuleTable, SW_SHOW
		;清空表格内容
		invoke _clearResultView
	.elseif eax == WM_NOTIFY
		mov eax, lParam
		mov ebx, lParam
		;更改各控件状态
		mov eax, [eax+NMHDR.hwndFrom]
		.if eax == hProcessModuleTable
			mov ebx, lParam
			.if [ebx+NMHDR.code] == NM_CUSTOMDRAW 		;绘画时
				mov ebx, lParam
				assume ebx:ptr NMLVCUSTOMDRAW
				.if [ebx].nmcd.dwDrawStage == CDDS_PREPAINT
					invoke SetWindowLong, hProcessModuleDlg, DWL_MSGRESULT, \
								CDRF_NOTIFYITEMDRAW
					mov eax, TRUE
				.elseif [ebx].nmcd.dwDrawStage == CDDS_ITEMPREPAINT
					;当每一单元格内容预画时,判断
                    ;两列的值是否一致
					invoke _GetListViewItem, hProcessModuleTable, \
							[ebx].nmcd.dwItemSpec, 1, addr bufTemp1
					invoke _GetListViewItem, hProcessModuleTable, \
							[ebx].nmcd.dwItemSpec, 2, addr bufTemp2
					invoke lstrlen, addr bufTemp1
					invoke _MemCmp, addr bufTemp1, addr bufTemp2, eax
					;如果一致,则将文本的背景色设置为浅红色,否则黑色
					.if eax == 1
						mov [ebx].clrTextBk, 0a0a0ffh
					.else
						mov [ebx].clrTextBk, 0ffffffh
					.endif
					invoke SetWindowLong, hProcessModuleDlg, DWL_MSGRESULT, \
							CDRF_DODEFAULT
					mov eax, TRUE
				.endif
			.elseif [ebx+NMHDR.code] == NM_CLICK
				assume ebx:ptr NMLISTVIEW
			.endif
		.endif
	.elseif eax == WM_COMMAND
		mov eax, wParam
		.if ax == IDC_OK 				;刷新
			invoke _openFile
		.elseif ax == IDC_BROWSE1
			invoke _OpenFile1 			;用户选择第一个文件
		.elseif ax == IDC_BROWSE2
			invoke _OpenFile2			;用户选择第二个文件
		.endif
	.else
		mov eax, FALSE
		ret
	.endif
	mov eax, TRUE
	ret
_resultProcMain endp
;-------------------------------
;窗口程序
;--------------------------------
_ProcDlgMain proc uses ebx edi esi hWnd, wMsg, wParam, lParam
	mov eax, wMsg
	.if eax == WM_CLOSE
		invoke EndDialog, hWnd, NULL
	.elseif eax == WM_INITDIALOG				;初始化
		push hWnd
		pop hWinMain
		call _init
	.elseif eax == WM_COMMAND 					;菜单
		mov eax, wParam
		.if eax == IDM_EXIT						;退出
			invoke EndDialog, hWnd, NULL
		.elseif eax == IDM_OPEN 				;打开PE对比对话框
			invoke DialogBoxParam, hInstance, RESULT_MODULE, hWnd, \
					offset _resultProcMain, 0
			invoke InvalidateRect, hWnd, NULL, TRUE
			invoke UpdateWindow, hWnd
		.elseif eax == IDM_1
		.elseif eax == IDM_2
		.elseif eax == IDM_3
		.endif
	.else
		mov eax, FALSE
		ret
	.endif
	mov eax, TRUE
	ret
_ProcDlgMain endp
start:
	invoke InitCommonControls
	invoke LoadLibrary, offset szDllEdit
	mov hRichEdit, eax
	invoke GetModuleHandle, NULL
	mov hInstance, eax
	invoke DialogBoxParam, hInstance, \
			DLG_MAIN, NULL, offset _ProcDlgMain, NULL
	invoke FreeLibrary, hRichEdit
	invoke ExitProcess, NULL
end start

Makefile文件:

NAME = pecomp
OBJS = $(NAME).obj
RES  = $(NAME).res
LINK_FLAG = /subsystem:windows
ML_FLAG = /c /coff
$(NAME).exe: $(OBJS) $(RES)
	Link $(LINK_FLAG) $(OBJS) $(RES)
.asm.obj:
	ml $(ML_FLAG) $<
.rc.res:
	rc $<
clean:
	del *.obj
	del *.res

编译:

运行:

打开文件:


2.4 PEInfo的实现

PEInfo是PE文件结构查看器,它将PE中的字节码以形象的描述语言显示出来,塑造一个整体的PE形象。通过编写PEInfo,可以锻炼我们使用数据结构定位特定PE信息的能力。

2.4.1 编程思路

这个小工具开发起来也不难,只是过程复杂了一些而已,其编程思路如下:

步骤1 打开文件,判断是否为PE文件。

判断方法非常简单,首先查看IMAGE_DOS_HEADER. e_magic字段,然后查看IMAGE_NT_HEADER.Signature字段;如果符合PE文件定义,则视为合法PE文件。事实上,操作系统在装载PE文件时,对PE文件的检测远比此方法复杂得多。

步骤2 将指针定位到相关数据结构,获取字段内容并以更人性化的方式显示相关内容。

提示 由于我们还没有正式开始学习PE文件格式,而PEInfo编程中涉及PE头部的大量数据结构,所以该部分代码的阅读最好等学习完第3章以后再进行。

2.4.2 PEInfo编码

编写PEInfo不需要额外的资源文件,复制一份pe.rc到PEInfo.rc即可,源代码依然来自pe.asm。与pe.asm不同的是,我们需要在PEInfo.asm的窗口回调函数中,为菜单项“文件”|“打开”的消息响应代码加入调用_openFile函数的代码。如下所示:

.elseif eax==IDM_OPEN    ;打开文件
  call _openFile

下面来看_openFile函数和_getMainInfo函数。

1. _openFile函数

_openFile函数完成了显示PE结构的所有功能,该部分代码如代码清单2-8所示。

代码清单2-8 _openFile函数实现(chapter2\peinfo.asm)

   ;------------------------------
   ; 打开PE文件并处理
   ;------------------------------
   _openFile proc
     local @stOF:OPENFILENAME
     local @hFile,@dwFileSize,@hMapFile,@lpMemory
     invoke RtlZeroMemory,addr @stOF,sizeof @stOF
     mov @stOF.lStructSize,sizeof @stOF
      push hWinMain
      pop @stOF.hwndOwner
      mov @stOF.lpstrFilter,offset szExtPe
      mov @stOF.lpstrFile,offset szFileName
      mov @stOF.nMaxFile,MAX_PATH
      mov @stOF.Flags,OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST
      invoke GetOpenFileName,addr @stOF   ;让用户选择打开的文件
      .if !eax
         jmp @F
      .endif
      invoke CreateFile,addr szFileName,GENERIC_READ,\
               FILE_SHARE_READ or FILE_SHARE_WRITE,NULL,\
               OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL
      .if eax!=INVALID_HANDLE_VALUE
         mov @hFile,eax
         invoke GetFileSize,eax,NULL
         mov @dwFileSize,eax
         .if eax
           invoke CreateFileMapping,@hFile,\   ;内存映射文件
                    NULL,PAGE_READONLY,0,0,NULL
           .if eax
              mov @hMapFile,eax
              invoke MapViewOfFile,eax,\
                       FILE_MAP_READ,0,0,0
              .if eax
                ;获得文件在内存中的映像起始位置
                mov @lpMemory,eax
                assume fs:nothing
                push ebp
                push offset _ErrFormat
                push offset _Handler
                push fs:[0]
                mov fs:[0],esp
                ;检测PE文件是否有效
                mov esi,@lpMemory
                assume esi:ptr IMAGE_DOS_HEADER
                ;判断是否有MZ字样
                .if [esi].e_magic!=IMAGE_DOS_SIGNATURE
                   jmp _ErrFormat
                .endif
                ;调整esi指针指向PE文件头
                add esi,[esi].e_lfanew
                assume esi:ptr IMAGE_NT_HEADERS
                ;判断是否有PE字样
                .if [esi].Signature!=IMAGE_NT_SIGNATURE
                   jmp _ErrFormat
                .endif
                ;到此为止,该文件的验证已经完成。是PE结构文件
                ;接下来分析文件映射到内存中的数据,并显示相关信息
                invoke _getMainInfo,@lpMemory,esi,@dwFileSize
                ;显示导入表
                invoke _getImportInfo,@lpMemory,esi,@dwFileSize
                ;显示导出表
                invoke _getExportInfo,@lpMemory,esi,@dwFileSize
                ;显示重定位信息
                invoke _getRelocInfo,@lpMemory,esi,@dwFileSize
                ;显示其他信息
                jmp _ErrorExit
    _ErrFormat:
                invoke MessageBox,hWinMain,offset szErrFormat,\
                                                                 NULL,MB_OK
    _ErrorExit:
                pop fs:[0]
                add esp,0ch
                invoke UnmapViewOfFile,@lpMemory
              .endif
              invoke CloseHandle,@hMapFile
           .endif
           invoke CloseHandle,@hFile
         .endif
      .endif
    @@:
      ret
    _openFile endp

第44~59行代码的功能是检测打开的文件是否符合PE标准。第61~69行的代码的功能是调用不同的函数显示PE的相关信息。例如,PE的主要信息的显示调用了函数_getMainInfo:

invoke _getMainInfo,@lpMemory,esi,@dwFileSize

2._getMainInfo函数

该函数接收三个参数:

        ❑ _lpFile (内存映射文件的起始地址)

        ❑ _lpPeHead (数据结构IMAGE_NT_HEADERS在内存中的起始位置)

        ❑ _dwSize (PE文件大小)

该函数获取PE文件的头部信息并显示, 由于实现很简单,就不再详细分析了,如代码清单2-9所示。

代码清单2-9 获取PE文件主要信息的函数_getMainInfo(chapter2\peinfo.asm)

;----------------------------------------------
; 从内存中获取PE文件的主要信息
;----------------------------------------------
_getMainInfo   proc _lpFile,_lpPeHead,_dwSize
 local @szBuffer[1024]:byte
 local @szSecName[16]:byte
 pushad
 mov edi,_lpPeHead
  assume edi:ptr IMAGE_NT_HEADERS
  movzx ecx,[edi].FileHeader.Machine             ;运行平台
  movzx edx,[edi].FileHeader.NumberOfSections ;节的数量
  movzx ebx,[edi].FileHeader.Characteristics   ;节的属性
  invoke wsprintf,addr @szBuffer,addr szMsg,\
           addr szFileName,ecx,edx,ebx,\
           [edi].OptionalHeader.ImageBase,\      ;包含建议装入的地址
           [edi].OptionalHeader.AddressOfEntryPoint
  invoke SetWindowText,hWinEdit,addr @szBuffer;添加到编辑框中
  ;显示每个节的主要信息
  invoke _appendInfo,addr szMsgSec
  movzx ecx,[edi].FileHeader.NumberOfSections
  add edi,sizeof IMAGE_NT_HEADERS
  assume edi:ptr IMAGE_SECTION_HEADER
  .repeat
     push ecx
    ;获取节的名称,注意:长度为8的名称,并且不以0结尾
     invoke RtlZeroMemory,addr @szSecName,sizeof @szSecName
     push esi
     push edi
     mov ecx,8
     mov esi,edi
     lea edi,@szSecName
     cld
     @@:
     lodsb
    .if !al  ;如果名称为0,则显示为空格
       mov al,' '
     .endif
     stosb
     loop @B
     pop edi
     pop esi
     ;获取节的主要信息
     invoke wsprintf,addr @szBuffer,addr szFmtSec,\
              addr @szSecName,[edi].Misc.VirtualSize,\
              [edi].VirtualAddress,[edi].SizeOfRawData,\
              [edi].PointerToRawData,[edi].Characteristics
     invoke _appendInfo,addr @szBuffer
     add edi,sizeof IMAGE_SECTION_HEADER
     pop ecx
  .untilcxz
  assume edi:nothing
  popad
  ret
_getMainInfo endp

其他信息(如导入表、导出表、资源表等)的显示代码将在后续的章节中详细介绍。

2.4.3 运行PEInfo

编译链接生成PEInfo.exe,然后运行。用该程序打开第1章生成的HelloWorld.exe程序,运行效果见图2-6。

                                                                      图2-6 PEInfo运行效果

至此,三个小工具的开发工作就完成了,在后续的章节中,会陆续使用这些小工具完成对PE格式的分析和学习。


笔记:

peinfo.rc文件

#include 
#define ICO_MAIN  1000
#define DLG_MAIN  1000
#define IDC_INFO  1001
#define IDM_MAIN  2000
#define IDM_OPEN  2001
#define IDM_EXIT  2002
#define IDM_1    4000
#define IDM_2    4001
#define IDM_3    4002
#define IDM_4    4003
ICO_MAIN  ICON  "main.ico"
DLG_MAIN DIALOG 50,50,544,399
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "PEInfo by Scott"
MENU IDM_MAIN
FONT 9,"宋体"
BEGIN
   CONTROL "",IDC_INFO,"RichEdit20A",196 | ES_WANTRETURN | WS_CHILD | ES_READONLY
               | WS_VISIBLE |WS_BORDER | WS_VSCROLL | WS_TABSTOP,0,0,540,396
END
IDM_MAIN menu discardable
BEGIN
  POPUP "文件(&F)"
  BEGIN
    menuitem "打开文件(&O)...",IDM_OPEN
    menuitem separator
    menuitem "退出(&x)",IDM_EXIT
  END
  POPUP "编辑(&E)"
  BEGIN
    menuitem separator
  END
  POPUP "格式(&O)"
  BEGIN
    menuitem separator
  END
  POPUP "查看(&V)"
  BEGIN
    menuitem "源文件",IDM_1
    menuitem "窗口透明度",IDM_2
    menuitem separator
    menuitem "大小",IDM_3
    menuitem "宽度",IDM_4
  END
  POPUP "帮助(&H)"
  BEGIN
    menuitem separator
  END
END

peinfo.asm文件

;peinfo.asm   通用程序框架
;使用 nmake 或下列命令进行编译和链接:
;ml -c -coff peinfo.asm
;rc -r peinfo.rc
;link -subsystem:windows peinfo.obj peinfo.res
.386
.model flat, stdcall
option casemap:none
include		c:/masm32/include/windows.inc
include 	c:/masm32/include/user32.inc
includelib 	c:/masm32/lib/user32.lib
include 	c:/masm32/include/kernel32.inc
includelib 	c:/masm32/lib/kernel32.lib
include 	c:/masm32/include/comdlg32.inc
includelib 	c:/masm32/lib/comdlg32.lib
ICO_MAIN	equ 1000
DLG_MAIN 	equ 1000
IDC_INFO	equ 1001
IDM_MAIN 	equ 2000
IDM_OPEN 	equ 2001
IDM_EXIT 	equ 2002
IDM_1		equ 4000
IDM_2 		equ 4001
IDM_3 		equ 4002
.data
hInstance 	dword ?
hRichEdit 	dword ?
hWinMain	dword ?
hWinEdit	dword ?
szFileName 	byte MAX_PATH dup(?)
.const
szDllEdit 	byte 'RichEd20.dll', 0
szClassEdit byte 'RichEdit20A', 0
szFont 		byte '宋体', 0
szExtPe 	byte 'PE File',0,'*.exe;*.dll;*.scr;*.fon;*.drv',0
			byte 'All Files(*.*)',0,'*.*',0,0
szErr 		byte '文件格式错误!',0
szErrFormat byte '这个文件不是PE格式的文件!',0
szSuccess	byte '恭喜你,程序执行到这里是成功的。',0
szNotFound	byte '无法查找',0
szMsg 		byte '文件名:%s',0dh,0ah
			byte '-----------------------------------------',0dh,0ah,0dh,0ah,0dh,0ah
			byte '运行平台:      0x%04x  (014c:Intel 386   014dh:Intel 486  014eh:Intel 586)',0dh,0ah
			byte '节的数量:      %d',0dh,0ah
			byte '文件属性:      0x%04x  (大尾-禁止多处理器-DLL-系统文件-禁止网络运行-禁止优盘运行-无调试-32位-小尾-X-X-X-无符号-无行-可执行-无重定位)',0dh,0ah
			byte '建议装入基地址:  0x%08x',0dh,0ah
			byte '文件执行入口(RVA地址):  0x%04x',0dh,0ah,0dh,0ah,0
szMsgSec 	byte '---------------------------------------------------------------------------------',0dh,0ah
			byte '节的属性参考:',0dh,0ah
			byte '  00000020h  包含代码',0dh,0ah
			byte '  00000040h  包含已经初始化的数据,如.const',0dh,0ah
			byte '  00000080h  包含未初始化数据,如 .data?',0dh,0ah
			byte '  02000000h  数据在进程开始以后被丢弃,如.reloc',0dh,0ah
			byte '  04000000h  节中数据不经过缓存',0dh,0ah
			byte '  08000000h  节中数据不会被交换到磁盘',0dh,0ah
			byte '  10000000h  数据将被不同进程共享',0dh,0ah
			byte '  20000000h  可执行',0dh,0ah
			byte '  40000000h  可读',0dh,0ah
			byte '  80000000h  可写',0dh,0ah
			byte '常见的代码节一般为:60000020h,数据节一般为:c0000040h,常量节一般为:40000040h',0dh,0ah
			byte '---------------------------------------------------------------------------------',0dh,0ah,0dh,0ah,0dh,0ah
			byte '节的名称  未对齐前真实长度  内存中的偏移(对齐后的) 文件中对齐后的长度 文件中的偏移  节的属性',0dh,0ah
			byte '---------------------------------------------------------------------------------------------',0dh,0ah,0
szFmtSec	byte '%s     %08x         %08x              %08x           %08x     %08x',0dh,0ah,0dh,0ah,0dh,0ah,0
szMsg1 		byte 0dh,0ah,0dh,0ah,0dh,0ah
			byte '---------------------------------------------------------------------------------------------',0dh,0ah
			byte '导入表所处的节:%s',0dh,0ah
			byte '---------------------------------------------------------------------------------------------',0dh,0ah,0
szMsgImport byte 0dh,0ah,0dh,0ah
			byte '导入库:%s',0dh,0ah
			byte '-----------------------------',0dh,0ah,0dh,0ah
			byte 'OriginalFirstThunk  %08x',0dh,0ah
			byte 'TimeDateStamp       %08x',0dh,0ah
			byte 'ForwarderChain      %08x',0dh,0ah
			byte 'FirstThunk          %08x',0dh,0ah
			byte '-----------------------------',0dh,0ah,0dh,0ah,0
szMsg2 		byte '%08u         %s',0dh,0ah,0
szMsg3		byte '%08u(无函数名,按序号导入)',0dh,0ah,0
szErrNoImport	byte  0dh,0ah,0dh,0ah
				byte  '未发现该文件有导入函数',0dh,0ah,0dh,0ah,0
szMsgExport byte 0dh,0ah,0dh,0ah,0dh,0ah
            byte '---------------------------------------------------------------------------------------------',0dh,0ah
            byte '导出表所处的节:%s',0dh,0ah
            byte '---------------------------------------------------------------------------------------------',0dh,0ah
            byte '原始文件名:%s',0dh,0ah
            byte 'nBase               %08x',0dh,0ah
            byte 'NumberOfFunctions   %08x',0dh,0ah
            byte 'NuberOfNames        %08x',0dh,0ah
            byte 'AddressOfFunctions  %08x',0dh,0ah
            byte 'AddressOfNames      %08x',0dh,0ah
            byte 'AddressOfNameOrd    %08x',0dh,0ah
            byte '-------------------------------------',0dh,0ah,0dh,0ah
            byte '导出序号    虚拟地址    导出函数名称',0dh,0ah
            byte '-------------------------------------',0dh,0ah,0
szMsg4      byte '%08x      %08x      %s',0dh,0ah,0
szExportByOrd 	byte  '(按照序号导出)',0
szErrNoExport 	byte 0dh,0ah,0dh,0ah
				byte  '未发现该文件有导出函数',0dh,0ah,0dh,0ah,0
szMsgReloc1 byte 0dh,0ah,'重定位表所处的节:%s',0dh,0ah,0
szMsgReloc2 byte 0dh,0ah
            byte '--------------------------------------------------------------------------------------------',0dh,0ah
            byte '重定位基地址: %08x',0dh,0ah
            byte '重定位项数量: %d',0dh,0ah
            byte '--------------------------------------------------------------------------------------------',0dh,0ah
            byte '需要重定位的地址列表(ffffffff表示对齐用,不需要重定位)',0dh,0ah
            byte '--------------------------------------------------------------------------------------------',0dh,0ah,0
szMsgReloc3 byte '%08x  ',0
szCrLf      byte 0dh,0ah,0
szMsgReloc4 byte 0dh,0ah,'未发现该文件有重定位信息.',0dh,0ah,0
.code
;初始化窗口程序
_init proc
	local @stCf:CHARFORMAT
	invoke GetDlgItem, hWinMain, IDC_INFO
	mov hWinEdit, eax
	;为窗口设置图标
	invoke LoadIcon, hInstance, ICO_MAIN
	invoke SendMessage, hWinMain, WM_SETICON, ICON_BIG, eax
	;设置编辑控件
	invoke SendMessage, hWinEdit, EM_SETTEXTMODE, TM_PLAINTEXT, 0
	invoke RtlZeroMemory, addr @stCf, sizeof @stCf 					;初始化
	mov @stCf.cbSize, sizeof @stCf
	mov @stCf.yHeight, 9*20
	mov @stCf.dwMask, CFM_FACE or CFM_SIZE or CFM_BOLD
	invoke lstrcpy, addr @stCf.szFaceName, addr szFont
	invoke SendMessage, hWinEdit, EM_SETCHARFORMAT, 0, addr @stCf
	invoke SendMessage, hWinEdit, EM_EXLIMITTEXT, 0, -1
	ret
_init endp
;------------------
; 错误Handler
;------------------
_Handler proc _lpExceptionRecord, _lpSEH, \
			  _lpContext, _lpDispathcerContext
	pushad
	mov esi, _lpExceptionRecord
	mov edi, _lpContext
	assume esi:ptr EXCEPTION_RECORD, edi:ptr CONTEXT
	mov eax, _lpSEH
	push [eax+0ch]
	pop [edi].regEbp
	push [eax+8]
	pop [edi].regEip
	push eax
	pop [edi].regEsp
	assume esi:nothing, edi:nothing
	popad
	mov eax, ExceptionContinueExecution
	ret
_Handler endp
;---------------------------------
; 将内存偏移量RVA转换为文件偏移
; lp_FileHead为文件头的起始地址
; _dwRVA为给定的RVA地址
;---------------------------------
_RVAToOffset proc _lpFileHead, _dwRVA
	local @dwReturn
	pushad
	mov esi, _lpFileHead
	assume esi:ptr IMAGE_DOS_HEADER
	add esi, [esi].e_lfanew
	assume esi:ptr IMAGE_NT_HEADERS
	mov edi, _dwRVA
	mov edx, esi
	add edx, sizeof IMAGE_NT_HEADERS
	assume edx:ptr IMAGE_SECTION_HEADER
	movzx ecx, [esi].FileHeader.NumberOfSections
	;遍历节表
	.repeat
		mov eax, [edx].VirtualAddress
		;计算该节结束RVA,不用Misc的主要原因是有些段的Misc值是错误的!
		add eax, [edx].SizeOfRawData
		.if (edi >= [edx].VirtualAddress) && (edi < eax)
			mov eax, [edx].VirtualAddress
			;计算RVA在节中的偏移
			sub edi, eax
			mov eax, [edx].PointerToRawData
			;加上节在文件中的的起始位置
			add eax, edi
			jmp @F
		.endif
		add edx, sizeof IMAGE_SECTION_HEADER
	.untilcxz
	assume edx:nothing
	assume esi:nothing
	mov eax, -1
@@:
	mov @dwReturn, eax
	popad
	mov eax, @dwReturn
	ret
_RVAToOffset endp
;-------------------------------------------
; 将距离文件头的文件偏移转换为内存偏移量RVA
; lp_FileHead为文件头的起始地址
; _dwOffset为给定的文件偏移地址
;-------------------------------------------
_OffsetToRVA proc _lpFileHead, _dwOffset
	local @dwReturn
	pushad
	mov esi, _lpFileHead
	assume esi:ptr IMAGE_DOS_HEADER
	add esi, [esi].e_lfanew
	assume esi:ptr IMAGE_NT_HEADERS
	mov edi, _dwOffset
	mov edx, esi
	add edx, sizeof IMAGE_NT_HEADERS
	assume edx:ptr IMAGE_SECTION_HEADER
	movzx ecx, [esi].FileHeader.NumberOfSections
	;遍历节表
	.repeat
		mov eax, [edx].PointerToRawData
		;计算该节结束RVA,不用Misc的主要原因是有些段的Misc值是错误的!
		add eax, [edx].SizeOfRawData
		.if (edi >= [edx].PointerToRawData) && (edi < eax)
			mov eax, [edx].PointerToRawData
			;计算RVA在节中的偏移
			sub edi, eax
			mov eax, [edx].VirtualAddress
			;加上节在文件中的的起始位置
			add eax, edi
			jmp @F
		.endif
		add edx, sizeof IMAGE_SECTION_HEADER
	.untilcxz
	assume edx:nothing
	assume esi:nothing
	mov eax, -1
@@:
	mov @dwReturn, eax
	popad
	mov eax, @dwReturn
	ret
_OffsetToRVA endp
;------------------------
; 获取RVA所在节的名称
;------------------------
_getRVASectionName proc _lpFileHead, _dwRVA
	local @dwReturn
	pushad
	mov esi, _lpFileHead
	assume esi:ptr IMAGE_DOS_HEADER
	add esi, [esi].e_lfanew
	assume esi:ptr IMAGE_NT_HEADERS
	mov edi, _dwRVA
	mov edx, esi
	add edx, sizeof IMAGE_NT_HEADERS
	assume edx:ptr IMAGE_SECTION_HEADER
	movzx ecx, [esi].FileHeader.NumberOfSections
	;遍历节表
	.repeat
		mov eax, [edx].VirtualAddress
		add eax, [edx].SizeOfRawData 		;计算该节结束RVA
		.if (edi >= [edx].VirtualAddress) && (edi < eax)
			mov eax, edx
			jmp @F
		.endif
		add edx, sizeof IMAGE_SECTION_HEADER
	.untilcxz
	assume edx:nothing
	assume esi:nothing
	mov eax, offset szNotFound
@@:
	mov @dwReturn,eax
	popad
	mov eax, @dwReturn
	ret
_getRVASectionName endp
;-------------------------------
; 获取指定字符串的API函数的调用地址
; 入口参数:_hModule为动态链接库的基址,_lpApi为API函数名的首址
; 出口参数:eax为函数在虚拟地址空间中的真实地址
;-------------------------------
_getApi proc _hModule, _lpApi
	local @ret
	local @dwLen
	pushad
	mov @ret, 0
	;计算API字符串的长度,含最后的零
	mov edi, _lpApi
	mov ecx, -1
	xor al, al
	cld
	repnz scasb
	mov ecx, edi
	sub ecx, _lpApi
	mov @dwLen, ecx
	;从pe文件头的数据目录获取导出表地址
	mov esi, _hModule
	add esi, [esi+3ch]
	assume esi:ptr IMAGE_NT_HEADERS
	mov esi, [esi].OptionalHeader.DataDirectory.VirtualAddress
	add esi, _hModule
	assume esi:ptr IMAGE_EXPORT_DIRECTORY
	;查找符合名称的导出函数名
	mov ebx, [esi].AddressOfNames
	add ebx, _hModule
	xor edx, edx
	.repeat
		push esi
		mov edi, [ebx]
		add edi, _hModule
		mov esi, _lpApi
		mov ecx, @dwLen
		repz cmpsb
		.if ZERO?
			pop esi
			jmp @F
		.endif
		pop esi
		add ebx, 4
		inc edx
	.until edx >= [esi].NumberOfNames
	jmp _ret
@@:
	;通过API名称索引获取序号索引再获取地址索引
	sub ebx, [esi].AddressOfNames
	sub ebx, _hModule
	shr ebx, 1
	add ebx, [esi].AddressOfNameOrdinals
	add ebx, _hModule
	movzx eax, word ptr [ebx]
	shl eax, 2
	add eax, [esi].AddressOfFunctions
	add eax, _hModule
	;从地址表得到导出函数的地址
	mov eax, [eax]
	add eax, _hModule
	mov @ret, eax
_ret:
	assume esi:nothing
	popad
	mov eax, @ret
	ret
_getApi endp
;---------------------
; 往文本框中追加文本
;---------------------
_appendInfo proc _lpsz
	local @stCR:CHARRANGE
	pushad
	invoke GetWindowTextLength, hWinEdit
	mov @stCR.cpMin, eax 		;将插入点移动到最后
	mov @stCR.cpMax, eax
	invoke SendMessage, hWinEdit, EM_EXSETSEL, 0, addr @stCR
	invoke SendMessage, hWinEdit, EM_REPLACESEL, FALSE, _lpsz
	popad
	ret
_appendInfo endp
;--------------------
; 从内存中获取PE文件的主要信息
;--------------------
_getMainInfo proc _lpFile, _lpPeHead, _dwSize
	local @szBuffer[1024]:byte
	local @szSecName[16]:byte
	pushad
	mov edi, _lpPeHead
	assume edi:ptr IMAGE_NT_HEADERS
	movzx ecx, [edi].FileHeader.Machine 				;运行平台
	movzx edx, [edi].FileHeader.NumberOfSections 		;节的数量
	movzx ebx, [edi].FileHeader.Characteristics		;节的属性
	invoke wsprintf, addr @szBuffer, addr szMsg, \
			addr szFileName, ecx, edx, ebx, \
			[edi].OptionalHeader.ImageBase, \			;含建议装入的地址
			[edi].OptionalHeader.AddressOfEntryPoint
	invoke SetWindowText, hWinEdit, addr @szBuffer		;添加到编辑框中
	;显示每个节的主要信息
	invoke _appendInfo, addr szMsgSec
	movzx ecx, [edi].FileHeader.NumberOfSections
	add edi, sizeof IMAGE_NT_HEADERS
	assume edi:ptr IMAGE_SECTION_HEADER
	.repeat
		push ecx
		;获取节的名称,注意长度为8的名称并不以0结尾
		invoke RtlZeroMemory, addr @szSecName, sizeof @szSecName
		push esi
		push edi
		mov ecx, 8
		mov esi, edi
		lea edi, @szSecName
		cld
@@:
		lodsb 			;从内存中加载一个字节到 AL 寄存器的指令,同时它会自动更新源指针(通常是 ESI 寄存器)。
		.if !al 		;如果名称为0,则显示为空格
			mov al, ' '
		.endif
		stosb
		loop @B
		pop edi
		pop esi
		;获取节的主要信息
		invoke wsprintf, addr @szBuffer, addr szFmtSec,  \
				addr @szSecName, [edi].Misc.VirtualSize, \
				[edi].VirtualAddress, [edi].SizeOfRawData, \
				[edi].PointerToRawData, [edi].Characteristics
		invoke _appendInfo, addr @szBuffer
		add edi, sizeof IMAGE_SECTION_HEADER
		pop ecx
	.untilcxz
	assume edi:nothing
	popad
	ret
_getMainInfo endp
;--------------------
; 获取PE文件的导入表
;--------------------
_getImportInfo proc _lpFile, _lpPeHead, _dwSize
	local @szBuffer[1024]:byte
	local @szSectionName[16]:byte
	pushad
	mov edi, _lpPeHead
	assume edi:ptr IMAGE_NT_HEADERS
	mov eax, [edi].OptionalHeader.DataDirectory[8].VirtualAddress
	.if !eax
		invoke _appendInfo, addr szErrNoImport
		jmp _Ret
	.endif
	invoke _RVAToOffset, _lpFile, eax
	add eax, _lpFile
	mov edi, eax 				;计算引入表所在文件偏移位置
	assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
	invoke _getRVASectionName, _lpFile, [edi].OriginalFirstThunk
	invoke wsprintf, addr @szBuffer, addr szMsg1, eax 	;显示节名
	invoke _appendInfo, addr @szBuffer
	.while [edi].OriginalFirstThunk || [edi].TimeDateStamp || \
				[edi].ForwarderChain || [edi].Name1 || \
				[edi].FirstThunk
		invoke _RVAToOffset, _lpFile, [edi].Name1
		add eax, _lpFile
		invoke wsprintf, addr @szBuffer, addr szMsgImport, eax, \
				[edi].OriginalFirstThunk, [edi].TimeDateStamp, \
				[edi].ForwarderChain, [edi].FirstThunk
		invoke _appendInfo, addr @szBuffer
		;获取IMAGE_THUNK_DATA列表到EBX
		.if [edi].OriginalFirstThunk
			mov eax, [edi].OriginalFirstThunk
		.else
			mov eax, [edi].FirstThunk
		.endif
		invoke _RVAToOffset, _lpFile, eax
		add eax, _lpFile
		mov ebx, eax
		.while dword ptr [ebx]
			;按序号导入
			.if dword ptr [ebx] & IMAGE_ORDINAL_FLAG32
				mov eax, dword ptr [ebx]
				and eax, 0ffffh
				invoke wsprintf, addr @szBuffer, addr szMsg3, eax
			.else 		;按名称导入
				invoke _RVAToOffset, _lpFile, dword ptr [ebx]
				add eax, _lpFile
				assume eax:ptr IMAGE_IMPORT_BY_NAME
				movzx ecx, [eax].Hint
				invoke wsprintf, addr @szBuffer, \
						addr szMsg2, ecx, addr [eax].Name1
				assume eax:nothing
			.endif
			invoke _appendInfo, addr @szBuffer
			add ebx, 4
		.endw
		add edi, sizeof IMAGE_IMPORT_DESCRIPTOR
	.endw
_Ret:
	assume edi:nothing
	popad
	ret
_getImportInfo endp
;--------------------
; 获取PE文件的导出表
;--------------------
_getExportInfo proc _lpFile, _lpPeHead, _dwSize
	local @szBuffer[1024]:byte
	local @szSectionName[16]:byte
	local @lpAddressOfNames, @dwIndex, @lpAddressOfNameOrdinals
	pushad
	mov esi, _lpPeHead
	assume esi:ptr IMAGE_NT_HEADERS
	mov eax, [esi].OptionalHeader.DataDirectory[0].VirtualAddress
	.if !eax
		invoke _appendInfo, addr szErrNoExport
		jmp _Ret
	.endif
	invoke _RVAToOffset, _lpFile, eax
	add eax, _lpFile
	mov edi, eax 			;计算导出表所在文件偏移位置
	assume edi:ptr IMAGE_EXPORT_DIRECTORY
	invoke _RVAToOffset, _lpFile, [edi].nName
	add eax, _lpFile
	mov ecx, eax
	invoke _getRVASectionName, _lpFile, [edi].nName
	invoke wsprintf, addr @szBuffer, addr szMsgExport, \
			eax, ecx, [edi].nBase, [edi].NumberOfFunctions, \
			[edi].NumberOfNames, [edi].AddressOfFunctions, \
			[edi].AddressOfNames, [edi].AddressOfNameOrdinals
	invoke _appendInfo, addr @szBuffer
	invoke _RVAToOffset, _lpFile, [edi].AddressOfNames
	add eax, _lpFile
	mov @lpAddressOfNames, eax
	invoke _RVAToOffset, _lpFile, [edi].AddressOfNameOrdinals
	add eax, _lpFile
	mov @lpAddressOfNameOrdinals, eax
	invoke _RVAToOffset, _lpFile, [edi].AddressOfFunctions
	add eax, _lpFile
	mov esi, eax 			;函数的地址表
	mov ecx, [edi].NumberOfFunctions
	mov @dwIndex, 0
@@:
	pushad
	mov eax, @dwIndex
	push edi
	mov ecx, [edi].NumberOfNames
	cld
	mov edi, @lpAddressOfNameOrdinals
	repnz scasw
	.if ZERO?		;找到函数名称
		sub edi, @lpAddressOfNameOrdinals
		sub edi, 2
		shl edi, 1
		add edi, @lpAddressOfNames
		invoke _RVAToOffset, _lpFile, dword ptr[edi]
		add eax, _lpFile
	.else
		mov eax, offset szExportByOrd
	.endif
	pop edi
	;序号在ecx中
	mov ecx, @dwIndex
	add ecx, [edi].nBase
	invoke wsprintf, addr @szBuffer, addr szMsg4, \
			ecx, dword ptr [esi], eax
	invoke _appendInfo, addr @szBuffer
	popad
	add esi, 4
	inc @dwIndex
	loop @B
_Ret:
	assume esi:nothing
	assume edi:nothing
	popad
	ret
_getExportInfo endp
;-----------------------
; 获取PE文件的重定位信息
;-----------------------
_getRelocInfo proc _lpFile, _lpPeHead, _dwSize
	local @szBuffer[1024]:byte
	local @szSectionName[16]:byte
	pushad
	mov esi, _lpPeHead
	assume esi:ptr IMAGE_NT_HEADERS
	mov eax, [esi].OptionalHeader.DataDirectory[8*5].VirtualAddress
	.if !eax
		invoke _appendInfo, addr szMsgReloc4
		jmp _ret
	.endif
	push eax
	invoke _RVAToOffset, _lpFile, eax
	add eax, _lpFile
	mov esi, eax
	pop eax
	invoke _getRVASectionName, _lpFile, eax
	invoke wsprintf, addr @szBuffer, addr szMsgReloc1, eax
	invoke _appendInfo, addr @szBuffer
	assume esi:ptr IMAGE_BASE_RELOCATION
	;循环处理每个重定位块
	.while [esi].VirtualAddress
		cld
		lodsd 		;eax=[esi].VirtualAddress
		mov ebx, eax
		lodsd 		;eax=[esi].SizeofBlock
		sub eax, sizeof IMAGE_BASE_RELOCATION 		;块总长度-两个dd
		shr eax, 1									;然后除以2,得到重定位项数量
													;除以2是因为重定位项是word
		push eax
		invoke wsprintf, addr @szBuffer, addr szMsgReloc2, ebx, eax
		invoke _appendInfo, addr @szBuffer
		pop ecx 									;重定位项数量
		xor edi, edi
		.repeat
			push ecx
			lodsw
			mov cx, ax
			and cx, 0f000h 		;得到高四位
			.if cx == 03000h		;重定位地址指向的双字的32位都需要休正
				and ax, 0fffh
				movzx eax, ax
				add eax, ebx 		;得到修正以前的偏移,
									;该偏移加上装入时的基址就是绝对地址
			.else 					;该重定位项无意义,仅用来作为对齐
				mov eax, -1
			.endif
			invoke wsprintf, addr @szBuffer, addr szMsgReloc3, eax
			inc edi
			.if edi == 8 			;每显示8个项目换行
				invoke lstrcat, addr @szBuffer, addr szCrLf
				xor edi, edi
			.endif
			invoke _appendInfo, addr @szBuffer
			pop ecx
		.untilcxz
		.if edi
			invoke _appendInfo, addr szCrLf
		.endif
	.endw
_ret:
	assume esi:nothing
	popad
	ret
_getRelocInfo endp
;--------------------
; 打开PE文件并处理
;--------------------
_openFile proc
	local @stOF:OPENFILENAME
	local @hFile, @dwFileSize, @hMapFile, @lpMemory
	invoke RtlZeroMemory, addr @stOF, sizeof @stOF
	mov @stOF.lStructSize, sizeof @stOF
	push hWinMain
	pop @stOF.hwndOwner
	mov @stOF.lpstrFilter, offset szExtPe
	mov @stOF.lpstrFile, offset szFileName
	mov @stOF.nMaxFile, MAX_PATH
	mov @stOF.Flags, OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST
	invoke GetOpenFileName, addr @stOF 		;让用户选择打开的文件
	.if !eax
		jmp @F
	.endif
	invoke CreateFile, addr szFileName, GENERIC_READ, \
			FILE_SHARE_READ or FILE_SHARE_WRITE, NULL, \
			OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL
	.if eax != INVALID_HANDLE_VALUE
		mov @hFile, eax
		invoke GetFileSize, eax, NULL
		mov @dwFileSize, eax
		.if eax
			invoke CreateFileMapping, @hFile, \			;内存映射文件
					NULL, PAGE_READONLY, 0, 0, NULL
			.if eax
				mov @hMapFile, eax
				invoke MapViewOfFile, eax, \
						FILE_MAP_READ, 0, 0, 0
				.if eax
					;获得文件在内存的映象起始位置
					mov @lpMemory, eax
					assume fs:nothing
					push ebp
					push offset _ErrFormat
					push offset _Handler
					push fs:[0]
					mov fs:[0], esp
					;检测PE文件是否有效
					mov esi, @lpMemory
					assume esi:ptr IMAGE_DOS_HEADER
					;判断是否有MZ字样
					.if [esi].e_magic != IMAGE_DOS_SIGNATURE
						jmp _ErrFormat
					.endif
					;调整ESI指针指向PE文件头
					add esi, [esi].e_lfanew
					assume esi:ptr IMAGE_NT_HEADERS
					;判断是否有PE字样
					.if [esi].Signature != IMAGE_NT_SIGNATURE
						jmp _ErrFormat
					.endif
					;到此为止,该文件的验证已经完成。为PE结构文件
					;接下来分析分件映射到内存中的数据,并显示主要参数
					invoke _getMainInfo, @lpMemory, esi, @dwFileSize
					;显示导入表
					invoke _getImportInfo, @lpMemory, esi, @dwFileSize
					;显示导出表
					invoke _getExportInfo, @lpMemory, esi, @dwFileSize
					;显示重定位信息
					invoke _getRelocInfo, @lpMemory, esi, @dwFileSize
					jmp _ErrorExit
_ErrFormat:
					invoke MessageBox, hWinMain, offset szErrFormat, \
								NULL, MB_OK
_ErrorExit:
					pop fs:[0]
					add esp, 0ch
					invoke UnmapViewOfFile, @lpMemory
				.endif
				invoke CloseHandle, @hMapFile
			.endif
			invoke CloseHandle, @hFile
		.endif
	.endif
@@:
	ret
_openFile endp
;-------------------------------
;窗口程序
;--------------------------------
_ProcDlgMain proc uses ebx edi esi hWnd, wMsg, wParam, lParam
	mov eax, wMsg
	.if eax == WM_CLOSE
		invoke EndDialog, hWnd, NULL
	.elseif eax == WM_INITDIALOG				;初始化
		push hWnd
		pop hWinMain
		call _init
	.elseif eax == WM_COMMAND 					;菜单
		mov eax, wParam
		.if eax == IDM_EXIT						;退出
			invoke EndDialog, hWnd, NULL
		.elseif eax == IDM_OPEN 				;打开文件
			call _openFile
		.elseif eax == IDM_1
			invoke MessageBox, NULL, offset szErrFormat, offset szErr, MB_ICONWARNING
		.elseif eax == IDM_2
			invoke MessageBox, NULL, offset szErrFormat, offset szErr, MB_ICONQUESTION
		.elseif eax == IDM_3
			invoke MessageBox, NULL, offset szErrFormat, offset szErr, MB_YESNOCANCEL
		.endif
	.else
		mov eax, FALSE
		ret
	.endif
	mov eax, TRUE
	ret
_ProcDlgMain endp
start:
	invoke LoadLibrary, offset szDllEdit
	mov hRichEdit, eax
	invoke GetModuleHandle, NULL
	mov hInstance, eax
	invoke DialogBoxParam, hInstance, \
			DLG_MAIN, NULL, offset _ProcDlgMain, NULL
	invoke FreeLibrary, hRichEdit
	invoke ExitProcess, NULL
end start

Makefile文件

NAME = peinfo
OBJS = $(NAME).obj
RES  = $(NAME).res
LINK_FLAG = /subsystem:windows
ML_FLAG = /c /coff
$(NAME).exe: $(OBJS) $(RES)
	Link $(LINK_FLAG) $(OBJS) $(RES)
.asm.obj:
	ml $(ML_FLAG) $<
.rc.res:
	rc $<
clean:
	del *.obj
	del *.res

编译:

运行:


2.5 小结

本章主要学习了如何通过汇编语言来编写基于PE操作的三个小工具,在后面对PE文件的分析中会经常使用这三个小工具。后面会讲到如何利用PEInfo遍历PE文件的导入表和导出表,那时还会用到本章的源代码。大家也可以对这些小工具进行扩展或整合,编写属于自己的PE分析工具。

值得一提的是,随编译器分发的可执行程序中有一个基于命令行的PE文件结构分析工具dumpbin.exe,它是公认的最好的PE分析工具之一,如果你喜欢,也可以用它来代替我们的小程序。

posted on 2025-10-03 16:38  ycfenxi  阅读(7)  评论(0)    收藏  举报