WIN32汇编语言教程:第17章 PE文件 · 17.1 PE文件的结构(8)
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 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
源代码中没有任何新的概念:程序首先创建一个对话框,并在初始化的时候将RichEdit控件的字体设置为“宋体”。当用户选择“打开”文件菜单后,则显示一个打开文件通用对话框让用户选择一个文件。
最后,程序用内存映射文件的方法将PE文件映射到内存中以供处理,处理使用的代码就是根据具体的情况编写的_ProcessPeFile.asm文件。
在处理文件之前,程序使用第14章中介绍的SEH来设置一个异常处理回调函数,一旦发生异常的话,则将程序转移到_ErrFormat标号处执行并认为文件的格式存在异常。由于PE文件的分析中涉及到很多指针操作,对任何一个指针都进行检测并判断它们是否已经越出了内存映射文件的范围是很麻烦的,使用SEH可以让这方面的工作开销最少。
当一切准备就绪后,程序要简单地判断一下打开的文件是否是一个合法的PE文件,详见下面的一段代码:
assume esi:ptr IMAGE_DOS_HEADER .if [esi].e_magic != IMAGE_DOS_SIGNATURE jmp _ErrFormat .endif add esi,[esi].e_lfanew assume esi:ptr IMAGE_NT_HEADERS .if [esi].Signature != IMAGE_NT_SIGNATURE jmp _ErrFormat .endif invoke _ProcessPeFile,@lpMemory,esi,@dwFileSize
esi一开始被指向文件的头部,程序首先判断DOS文件头中的标识符是否和“MZ”(也就是IMAGE_DOS_SIGNATURE)符合,如果符合的话,那么从003ch处(也就是e_lfanew字段)取出PE文件头的偏移,并比较PE文件头的标识是否为IMAGE_NT_SIGNATURE,这两个步骤都通过的话,那么几乎可以认定这是一个合法的PE文件了,程序就真正开始分析工作——调用_ProcessPeFile.asm 文件中的_ProcessPeFile子程序。
_ProcessPeFile.asm文件的内容如下所示:
.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,0ah,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 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 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 .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 .if ! al mov al,' ' .endif stosb 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 ;####################################################################
程序首先显示了PE文件头中一些重要字段的数值,如Machine,NumberOfSections和Characteristics等字段,然后根据NumberOfSections的数值构造一个循环,并在循环中显示每个节的信息。
在子程序开始的地方,edi寄存器被赋值为PE文件头指针,当在循环开始前将edi加上IMAGE_NT_HEADERS结构的长度后,edi指向的就是节表的起始地址了,使用edi作指针就可以将每个节的名称、尺寸、RVA地址、在文件中的偏移以及大小显示出来。
读者可以用这个PEInfo程序打开不同的文件来验证一下本节中叙述的一些内容,并思考下面的问题:
(1)比较EXE文件和DLL文件的Characteristics字段的差异。
(2)EXE文件往往没有重定位节(一般名称为.reloc),而DLL文件中总是有这个节。
(3)看看代码节和数据节的属性有什么不同,再查看第11章中KeyHook例子中的DLL文件,看看包含共享数据的节的属性又有什么不同。
(4)编写一个有.data?段却没有.data段的程序并用PEInfo去查看,可以发现数据节在磁盘文件中的长度为0,但是被映射到内存中以后却不为0。