PE文件由文件头部分,节表部分,还有节区部分组成。其中文件头包括PE文件头还有DOS文件头,节表主要是用来说明每个节的RVA地址(RVA地址就是文件被装载到内存后数据相对于文件起始位置的偏移量)还有节的尺寸的,当然还有一些其他的信息。而节区就是将属性相同的文件数据放在了一起,比如可执行的放一起,只读的放一起等等。
这是PE文件头的大概样子:
- DOS文件头
DOS头部分由MZ格式的文件头和可执行代码部分组成。而MZ格式的文件头是由一个IMAGE_DOS_HEADER结构定义的:
IMAE_DOS_HEADER STRUCT{ //DOS .EXE header 位置
e_magic WORD ? //DOS可执行文件标记,为"MZ" 0x00
e_cblp WORD ? //Bytes on last page of file 0x02
e_cp WORD ? //Pages in file 0x04
e_crlc WORD ? //Relocations 0x06
e_cparhdr WORD ? //Size of header in paragraphs 0x08
e_minalloc WORD ? //Minimum extra paragraphs needed 0x0A
e_maxalloc WORD ? //Maximum extra paragraphs needed 0x0C
e_ss WORD ? //DOS代码初始化堆栈段 0x0E
e_sp WORD ? //DOS代码的初始化堆栈指针 0x10
e_csum WORD ? //Checksum 0x12
e_ip WORD ? //DOS代码入口IP 0x14
e_cs WORD ? //DOS代码入口CS 0x16
e_lfarlc WORD ? //File address of relocation table 0x18
e_ovno WORD ? //Overlay number 0x1A
e_res WORD 4 dup(?) //Reserved words 0x1C
e_oemid WORD ? //OEM identifier (for e_oeminfo) 0x24
e_oeminfo WORD ? //OEM information; e_oemid specific 0x26
e_res2 WORD 10 dup(?) //Reserved words 0x28
e_lfanew DWORD ? //指向PE文件头 0x3C
IMAGE_DOS-HEADER ENDS
其中比较重要的部分就是第一个字段的DOS可执行文件标记,它用来说明这是一个DOS文件头。还有就是最后一个字段的e_lfnew,它指向真正的PE文件头
- PE文件头(NT文件头)
由上图可知,NT文件头包括三个部分,分别是PE文件标识:Signature,FileHeader,OPtionalHeader。这三个结构都是在一个IMAGE_NT_HEADERS结构里面。
IMAGE_NT_HEADERS STRUCT ;NT文件头
Signature DWORD ? ;PE文件标识
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS
第一个字段就是用来标识PE文件的,第二个字段的结构IMAGE_FILE_HEADER包含了很多信息:
IMAGE_FILE_HEADER STRUCT
Machine WORD ? ;0004h - 运行平台
NumberOfSections WORD ? ;0006h - 文件的节区数目
TimeDateStamp DWORD ? ;0008h - 文件的创建日期和时间
PointerToSymbolTable DWORD ? ;000ch - 指向符号表(用于调试)
NumberOfSymbols DWORD ? ;0010h - 符号表中的符号数量(用于调试)
SizeOfOptionalHeader WORD ? ;0014h - IMAGE_OPTIONAL_HEADER32的结构长度
Characteristics WORD ? ;0016h - 文件属性
IMAGE_FILE_HEADER ENDS
里面对于我们来说比较重要的就是 NumberOfSections:文件的节区数目,IMAGE_OPTIONAL_HEADER32结构的长度以及Characteristics:文件属性。其中文件属性字段的长度是一个字,每一个位都表示一个不同的信息。其他字段看注释就能明白什么意思了。下面这张图就是Characteristics字段各个位的含义。
然后比较重要的一个结构就是IMAGE_OPTIONAL_HEADER32结构,这个结构虽然从英文的意思上来解读像是一个可选部分,但是其实包含了比IMAGE_FILE_HEADER结构更多的信息,并且也是存在于每个PE文件中。我们可以把这两个结构连在一起看,把他们当成连在一起的 PE文件头结构。
IMAGE_OPTIONAL_HEADER32 STRUCT
Magic WORD ? ;0018h 107h=ROM Image,10Bh=exe Image
MajorLinkerVersion BYTE ? ;链接程序的主版本号
MinorLinkerVersion BYTE ? ;链接程序的次版本号
SizeOfCode DWORD ? ;所有含代码的节的总大小
SizeOfInitializedData DWORD ? ;所有含已初始化数据的节的总大小
SizeOfUninitializedData DWORD ? ;所有含未初始化数据的节的大小
AddressOfEntryPoint DWORD ? ;程序执行入口RVA
BaseOfCode DWORD ? ;代码的区块的起始RVA
BaseOfData DWORD ? ;数据的区块的起始RVA
ImageBase DWORD ? ;程序的首选装载地址
SectionAlignment DWORD ? ;内存中的区块的对齐大小
FileAlignment DWORD ? ;文件中的区块的对齐大小
MajorOperatingSystemVersion WORD ? ;要求操作系统最低版本号的主版本号
MinorOperatingSystemVersion WORD ? ;要求操作系统最低版本号的副版本号
MajorImageVersion WORD ? ;可运行于操作系统的主版本号
MinorImageVersion WORD ? ;可运行于操作系统的次版本号
MajorSubsystemVersion WORD ? ;要求最低子系统版本的主版本号
MinorSubsystemVersion WORD ? ;要求最低子系统版本的次版本号
Win32VersionValue DWORD ? ;未用
SizeOfImage DWORD ? ;映像装入内存后的总尺寸
SizeOfHeaders DWORD ? ;所有头 + 节表的尺寸大小
CheckSum DWORD ? ;映像的校检和
Subsystem DWORD ? ;可执行文件期望的子系统
DllCharacteristics DWORD ? ;DllMain()函数何时被调用,默认为 0
SizeOfStackReserve DWORD ? ;初始化时的栈大小
SizeOfStackCommit DWORD ? ;初始化时实际提交的栈大小
SizeOfHeapReserve DWORD ? ;初始化时保留的堆大小
SizeOfHeapCommit DWORD ? ;初始化时实际提交的堆大小
LoaderFlags DWORD ? ;与调试有关,默认为 0
NumberOfRvaAndSizes DWORD ? ;下边数据目录结构的数量
DataDirectory IMAGE_DATA_DIRECTORY 16 dup(<>)
IMAGE_OPTIONAL_HEADER32 ENDS
[点击并拖拽以移动]
可以看到这个IMAGE_OPTIONAL_HEADER32结构包含的信息非常多,但是其实我们只要了解那几个比较重要的就行了,所以这里挑几个重要的介绍一下。
AddressOfEntryPoint: 这个字段存储程序执行入口的RVA
SectionAlignment:内存中的节的对齐粒度,当PE文件被装载到内存中后节区部分需要对齐,也就是说每个节的装入地址都会是本字段的整数倍,每个节的大小都得是本字段的整数倍。加入对齐粒度是2,但是某个节的的大小是7,那么这个节被装载到内存后要填0扩展到8。(WIndows装载器在装载DOS部分,PE文件头部分和节表部分时不做任何处理,而装入节时要根据节的属性做不同处理)
FileAlignment:文件中节的对齐粒度,意思和上面的一样
Subsystem:文件的子系统。这个字段决定了系统如何为程序建立初始的界面,我们用Link链接器链接时的命令 link /subsystem:xxx 这个xxx选项就是这个字段的值。比如有Windows控制台界面Windows图形界面等
DataDirectory:数据目录。可以说这个字段是这个结构里面最重要的字段了。它由16个IMAGE_DATA_DIRECTORY结构组成。IMAGE_DATA_DIRECTORY结构指出了导出表,导入表,资源,重定位信息等数据的起始RVA和数据块的长度。结构的定义如下:
IMAGE_DATA_DIRECTORY STRUCT
VirtualAddress DWORD ? ;数据起始RVA
isize DWORD ? ;数据块的长度
IMAGE_DATA_DIRECTORY ENDS
每个结构定义一个数据块的信息
索 引 | 索引值在WIndows.inc中的预定义值 | 对应的数据块 |
0 | IMAGE_DIRECTOYR_ENTRY_EXPORT | 导出表 |
1 | IMAGE_DIRECTOYR_ENTRY_IMPORT | 导入表 |
2 | IMAGE_DIRECTOYR_ENTRY_RESOURCE | 资源 |
3 | IMAGE_DIRECTOYR_ENTRY_EXCEPTION | 异常 |
4 | IMAGE_DIRECTOYR_ENTRY_SECURITY | 安全 |
5 | IMAGE_DIRECTOYR_ENTRY_BASERELOC | 重定位表 |
6 | IMAGE_DIRECTOYR_ENTRY_DEBUG | 调试信息 |
7 | IMAGE_DIRECTOYR_ENTRY_ARCHITECTURE | 版权信息 |
8 | IMAGE_DIRECTOYR_ENTRY_GLOBALPTR | |
9 | IMAGE_DIRECTOYR_ENTRY_TLS | Thread Local Storage |
10 | IMAGE_DIRECTOYR_ENTRY_LOAD_CONFIG | |
11 | IMAGE_DIRECTOYR_ENTRY_IMPORT | |
12 | IMAGE_DIRECTOYR_ENTRY_IAT | 导入函数地址表 |
13 | IMAGE_DIRECTOYR_ENTRY_DELAY_IMPORT | |
14 | IMAGE_DIRECTOYR_ENTRY_DESCRIPTOR | |
15 | 未使用 |
总之,如果我们要寻找某个数据的位置,就可以通过这个IMAGE_DATA_DIRECTORY结构来寻找。
- 节表
文件头的最后一部分就是节表了,好像也叫区块表。节表也和数据目录一样是由很多个相同的结构组成的,节表里面的信息主要是指出它所指向的节区的RVA还有节区大小,节区属性,还有一些其他的重要信息。节表的结构如下:
IMAGE_SECTION_HEADER STRUCT
Namel db IMAGE_SIZEOF_SHORT_NAME dup(?) ;8个字节的节区名称
union Misc
PhysicalAddress dd ?
VirtualSize dd ? ;节区的尺寸
ends
VirtualAddress dd ? ;节区的RVA地址
SizeOfRawData dd ? ;在文件中对齐后的尺寸
PointerToRawData dd ? ;在文件中的偏移
PointerToRelocations dd ? ;在OBJ文件中使用
PointerToLinenumbers dd ? ;行号表的位置(调试用的)
NumberOfRelocations dw ? ;在OBJ文件中使用
Characteristics dd ? ;节的属性
IMAGE_SECTION_HEADER ENDS
[点击并拖拽以移动]
Name1:第一个字段 ,注意,这后面是一,不是"l"。这个字段定义了节的名称,字段的长度为8个字节,名称小于8个字节后面就补0,但是如果达到了8个字节后面就没有0了。每个节的名字是唯一的,一个PE文件中不能有两个同名的节。节的名字并不决定节的属性,它只是一个标记。但是一般代码节的名字会被取为“.text”,可读数据的节会被名命为 “.data”等等。
VirtualSize:这个表示节在没有进行对齐时的实际大小
VirtualAddress:指出节被装载到内存后的RVA,这个地址是按照SectionAlignment对齐后的
SizeOfRawData:节区在文件中的尺寸(按照FileAlignment对齐后的)
PointerToRawData:一个从文件头开始算起的偏移,它指出节在磁盘文件中所处的位置
Characteristics:属性,不同的数据位代表了不同的属性。高三位从高到低分别表示映射到内存后包含 可读,可写,可执行属性。
知道了这些大概的信息后我们可以用WIN32汇编写一个显示PE文件头信息的小程序。
首先为了方便操作,用内存映射文件函数将要操作的文件映射到内存中,然后我们可以获得一个文件头起始位置的指针,通过这个指针我们可以定位到NT文件头的位置,然后在IMAGE_FILE_HEADER这里我们可以得到节区的数量,然后再定位到IMAGE_OPTIONAL_HEADER32结构这里,我们可以得到更多的信息,拿你想显示的就行。然后再往后定位可以得到节表的位置,这样就可以获取各个节的名称了。
但是注意:我们需要设置一个异常处理,因为可能我们选择的不是一个PE文件,那么我们将这个文件当成一个PE文件来读取时可能会引发内存读取错误。
(程序例子是琢石成器这本书上的)
整个程序是用一个对话框来显示信息的,下面是对话框资源的源码:
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include <resource.h>
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN 1000
#define DLG_MAIN 1000
#define IDC_INFO 1001
#define IDM_MAIN 2000
#define IDM_OPEN 2001
#define IDM_EXIT 2002
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN ICON "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 50,50,250,140
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "PE文件基本信息"
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,249,140
END
IDM_MAIN menu discardable
BEGIN
popup "文件(&F)"
BEGIN
menuitem "打开文件(&O)...",IDM_OPEN
menuitem separator
menuitem "退出(&X)",IDM_EXIT
END
END
然后是程序的汇编代码,分成两部分来写了,一部分是一个整体的框架,还有一部分是具体功能的实现。下面是大体框架:
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Include文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include comdlg32.inc
includelib comdlg32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Equ等值定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000
DLG_MAIN equ 1000
IDC_INFO equ 1001
IDM_MAIN equ 2000
IDM_OPEN equ 2001
IDM_EXIT equ 2002
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hRichEdit dd ?
hWinMain dd ?
hWinEdit dd ?
szFileName db MAX_PATH dup (?)
.const
szDllEdit db 'RichEd20.dll',0
szClassEdit db 'RichEdit20A',0
szFont db '宋体',0
szExtPe db 'PE Files(*.*)',0,'*.exe;*.dll;*.scr;*.fon;*.drv',0
db 'ALl Files(*.*)',0,'*.*',0,0
szErr db '文件格式错误!',0
szErrFormat db '这个文件不是PE格式的文件!',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
_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 ;在 Microsoft 丰富编辑控件中选择一系列字符或组件对象模型 (COM) 对象。
invoke SendMessage,hWinEdit,EM_REPLACESEL,FALSE,_lpsz ;将编辑控件或富编辑控件中的选定文本替换为指定文本
popad
ret
_AppendInfo endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include _ProcessPeFile.asm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_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 C _lpExceptionRecord,_lpSEH,\
_lpContext,_lpDispatcherContext
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 ;原来的Esp的位置
assume esi:nothing,edi:nothing
popad
mov eax,ExceptionContinueExecution
ret
_Handler endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;打开文件,建立映射,读取文件数据,显示数据信息
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_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.nMaxFile,MAX_PATH
mov @stOF.lpstrFile,offset szFileName ;选择的文件返回到这个缓冲区
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 ;映射成功则返回映射到内存中的文件开始的指针
;*******************************************************************************************************************************
;创建用于错误处理的SHE结构
;*******************************************************************************************************************************
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 ;检测是否是DOS文件头
jmp _ErrFormat
.endif
add esi,[esi].e_lfanew ;到PE文件头的位置
assume esi:ptr IMAGE_NT_HEADERS
.if [esi].Signature != IMAGE_NT_SIGNATURE ;检测是否是PE文件头
jmp _ErrFormat
.endif
invoke _ProcessPeFile,@lpMemory,esi,@dwFileSize
jmp _ErrorExit
_ErrFormat:
invoke MessageBox,hWinMain,addr szErrFormat,offset szErr,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 ax == IDM_OPEN
call _OpenFile
.elseif ax == IDM_EXIT ;菜单退出
invoke EndDialog,hWnd,NULL
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke LoadLibrary,offset szDllEdit ;装载RichEd20.dll
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
下面是功能的具体实现部分:
.const
szMsg db '文件名:%s',0dh,0ah
db '----------------------------------------------------------',0dh,0ah
db '运行平台: 0x%04X',0dh,0ah
db '节区数量: %d',0dh,0ah
db '文件标记: 0x%04X',0dh,0ah
db '建议装入地址: 0x%08X',0dh,0ah
db '程序执行入口RVA: 0x%08X',0dh,0ah,0,0
szMsgSection db '----------------------------------------------------------',0dh,0ah
db '节区名称 节区大小 虚拟地址 Raw_尺寸 Raw_偏移 节区属性',0dh,0ah
db '----------------------------------------------------------',0dh,0ah,0
szFmtSection db '%s %08X %08X %08X %08X %08X',0dh,0ah,0
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcessPeFile proc _lpFile,_lpPeHead,_dwSize ;内存映射文件指针,PE文件头指针,文件大小
local @szBuffer[1024]:byte,@szSectionName[16]:byte
pushad
mov edi,_lpPeHead
assume edi:ptr IMAGE_NT_HEADERS
;************************************************************************************************************
;显示PE文件头中的一些信息
;************************************************************************************************************
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 szMsgSection ;设置字符格式并显示字符在对话框
movzx ecx,[edi].FileHeader.NumberOfSections
add edi,sizeof IMAGE_NT_HEADERS
assume edi:ptr IMAGE_SECTION_HEADER ;现在EDI于节表位置
.repeat
push ecx
;************************************************************************************************************
;获取节的名称,因为节区的名称不一定是以0结尾的,所以要进行一些处理
;************************************************************************************************************
invoke RtlZeroMemory,addr @szSectionName,\
sizeof @szSectionName
push esi
push edi
mov ecx,8
mov esi,edi
lea edi,@szSectionName
cld
@@:
lodsb ;将ESI指向的存储单元读入AL
.if ! al ;把0换成空格
mov al,' '
.endif
stosb ;将AL中的数据读入EDI所指的地址处
loop @B
pop edi
pop esi
invoke wsprintf,addr @szBuffer,addr szFmtSection,\
addr @szSectionName,[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
_ProcessPeFile endp
程序编译链接出来后运行是这个样子的:
文章转载于https://blog.csdn.net/weixin_43575859/article/details/104801073?utm_source=app