PE详解.docx

上传人:b****9 文档编号:26196013 上传时间:2023-06-17 格式:DOCX 页数:54 大小:47.41KB
下载 相关 举报
PE详解.docx_第1页
第1页 / 共54页
PE详解.docx_第2页
第2页 / 共54页
PE详解.docx_第3页
第3页 / 共54页
PE详解.docx_第4页
第4页 / 共54页
PE详解.docx_第5页
第5页 / 共54页
点击查看更多>>
下载资源
资源描述

PE详解.docx

《PE详解.docx》由会员分享,可在线阅读,更多相关《PE详解.docx(54页珍藏版)》请在冰豆网上搜索。

PE详解.docx

PE详解

PE文件详解(教程1-7)

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

PE教程1:

PE文件格式一览

PE的意思就是PortableExecutable(可移植的执行体)。

它是Win32环境自身所带的执行体文件格式。

它的一些特性继承自Unix的Coff(commonobjectfileformat)文件格式。

"portableexecutable"(可移植的执行体)意味着此文件格式是跨win32平台的:

即使Windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。

当然,移植到不同的CPU上PE执行体必然得有一些改变。

所有win32执行体(除了VxD和16位的Dll)都使用PE文件格式,包括NT的内核模式驱动程序(kernelmodedrivers)。

因而研究PE文件格式给了我们洞悉Windows结构的良机。

本教程就让我们浏览一下PE文件格式的概要。

DOSMZheader

DOSstub

PEheader

Sectiontable

Section1

Section2

Section...

Sectionn

上图是PE文件结构的总体层次分布。

所有PE文件(甚至32位的DLLs)必须以一个简单的DOSMZheader开始。

我们通常对此结构没有太大兴趣。

有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随MZheader之后的DOSstub。

DOSstub实际上是个有效的EXE,在不支持PE文件格式的操作系统中,它将简单显示一个错误提示,类似于字符串"ThisprogramrequiresWindows"或者程序员可根据自己的意图实现完整的DOS代码。

通常我们也不对DOSstub太感兴趣:

因为大多数情况下它是由汇编器/编译器自动生成。

通常,它简单调用中断21h服务9来显示字符串"ThisprogramcannotruninDOSmode"。

紧接着DOSstub的是 PEheader。

PEheader是PE相关结构 IMAGE_NT_HEADERS 的简称,其中包含了许多PE装载器用到的重要域。

当我们更加深入研究PE文件格式后,将对这些重要域耳目能详。

执行体在支持PE文件结构的操作系统中执行时,PE装载器将从DOSMZheader中找到PEheader的起始偏移量。

因而跳过了DOSstub直接定位到真正的文件头PEheader。

PE文件的真正内容划分成块,称之为sections(节)。

每节是一块拥有共同属性的数据,比如代码/数据、读/写等。

我们可以把PE文件想象成一逻辑磁盘,PEheader是磁盘的boot扇区,而sections就是各种文件,每种文件自然就有不同属性如只读、系统、隐藏、文档等等。

 值得我们注意的是----节的划分是基于各组数据的共同属性:

而不是逻辑概念。

重要的不是数据/代码是如何使用的,如果PE文件中的数据/代码拥有相同属性,它们就能被归入同一节中。

不必关心节中类似于"data","code"或其他的逻辑概念:

如果数据和代码拥有相同属性,它们就可以被归入同一个节中。

(译者注:

节名称仅仅是个区别不同节的符号而已,类似"data","code"的命名只为了便于识别,惟有节的属性设置决定了节的特性和功能)如果某块数据想付为只读属性,就可以将该块数据放入置为只读的节中,当PE装载器映射节内容时,它会检查相关节属性并置对应内存块为指定属性。

如果我们将PE文件格式视为一逻辑磁盘,PEheader是boot扇区而sections是各种文件,但我们仍缺乏足够信息来定位磁盘上的不同文件,譬如,什么是PE文件格式中等价于目录的东东?

别急,那就是PEheader接下来的数组结构sectiontable(节表)。

 每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。

如果PE文件里有5个节,那么此结构数组内就有5个成员。

因此,我们便可以把节表视为逻辑磁盘中的根目录,每个数组成员等价于根目录中目录项。

以上就是PE文件格式的物理分布,下面将总结一下装载一PE文件的主要步骤:

1.当PE文件被执行,PE装载器检查DOSMZheader里的PEheader偏移量。

如果找到,则跳转到PEheader。

2.PE装载器检查PEheader的有效性。

如果有效,就跳转到PEheader的尾部。

3.紧跟PEheader的是节表。

PE装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时付上节表里指定的节属性。

4.PE文件映射入内存后,PE装载器将处理PE文件中类似importtable(引入表)逻辑部分。

上述步骤是基于本人观察后的简述,显然还有一些不够精确的地方,但基本明晰了执行体被处理的过程。

你应该下载 LUEVELSMEYER的《PE文件格式》。

 该文的描述相当详细,可用作案头的参考手册。

PE教程2:

检验PE文件的有效性

本教程中我们将学习如何检测给定文件是一有效PE文件。

下载 范例

理论:

如何才能校验指定文件是否为一有效PE文件呢?

这个问题很难回答,完全取决于想要的精准程度。

您可以检验PE文件格式里的各个数据结构,或者仅校验一些关键数据结构。

大多数情况下,没有必要校验文件里的每一个数据结构,只要一些关键数据结构有效,我们就认为是有效的PE文件了。

下面我们就来实现前面的假设。

我们要验证的重要数据结构就是PEheader。

从编程角度看,PEheader实际就是一个IMAGE_NT_HEADERS结构。

定义如下:

IMAGE_NT_HEADERSSTRUCT 

Signaturedd?

 

FileHeaderIMAGE_FILE_HEADER<> 

OptionalHeaderIMAGE_OPTIONAL_HEADER32<> 

IMAGE_NT_HEADERSENDS

Signature 一dword类型,值为50h,45h,00h,00h(PE\0\0)。

本域为PE标记,我们可以此识别给定文件是否为有效PE文件。

FileHeader 该结构域包含了关于PE文件物理分布的信息,比如节数目、文件执行机器等。

OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。

我们目的很明确。

如果IMAGE_NT_HEADERS的signature域值等于"PE\0\0",那么就是有效的PE文件。

实际上,为了比较方便,Microsoft已定义了常量IMAGE_NT_SIGNATURE供我们使用。

IMAGE_DOS_SIGNATUREequ5A4Dh 

IMAGE_OS2_SIGNATUREequ454Eh 

IMAGE_OS2_SIGNATURE_LEequ454Ch 

IMAGE_VXD_SIGNATUREequ454Ch 

IMAGE_NT_SIGNATUREequ4550h

接下来的问题是:

如何定位PEheader?

答案很简单:

DOSMZheader已经包含了指向PEheader的文件偏移量。

DOSMZheader又定义成结构 IMAGE_DOS_HEADER 。

查询windows.inc,我们知道IMAGE_DOS_HEADER 结构的e_lfanew成员就是指向PEheader的文件偏移量。

现在将所有步骤总结如下:

1.首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE,是则DOSMZheader有效。

2.一旦证明文件的DOSheader有效后,就可用e_lfanew来定位PEheader了。

3.比较PEheader的第一个字的值是否等于 IMAGE_NT_HEADER。

如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。

Example:

.386 

.modelflat,stdcall 

optioncasemap:

none 

include\masm32\include\windows.inc 

include\masm32\include\kernel32.inc 

include\masm32\include\comdlg32.inc 

include\masm32\include\user32.inc 

includelib\masm32\lib\user32.lib 

includelib\masm32\lib\kernel32.lib 

includelib\masm32\lib\comdlg32.lib 

SEHstruct 

PrevLinkdd?

;theaddressoftheprevioussehstructure 

CurrentHandlerdd?

;theaddressoftheexceptionhandler 

SafeOffsetdd?

;Theoffsetwhereit'ssafetocontinueexecution 

PrevEspdd?

;theoldvalueinesp 

PrevEbpdd?

;Theoldvalueinebp 

SEHends

.data 

AppNamedb"PEtutorialno.2",0 

ofnOPENFILENAME<> 

FilterStringdb"ExecutableFiles(*.exe,*.dll)",0,"*.exe;*.dll",0 

db"AllFiles",0,"*.*",0,0 

FileOpenErrordb"Cannotopenthefileforreading",0 

FileOpenMappingErrordb"Cannotopenthefileformemorymapping",0 

FileMappingErrordb"Cannotmapthefileintomemory",0 

FileValidPEdb"ThisfileisavalidPE",0 

FileInValidPEdb"ThisfileisnotavalidPE",0 

.data?

 

bufferdb512dup(?

) 

hFiledd?

 

hMappingdd?

 

pMappingdd?

 

ValidPEdd?

 

.code 

startproc 

LOCALseh:

SEH 

movofn.lStructSize,SIZEOFofn 

movofn.lpstrFilter,OFFSETFilterString 

movofn.lpstrFile,OFFSETbuffer 

movofn.nMaxFile,512 

movofn.Flags,OFN_FILEMUSTEXISTorOFN_PATHMUSTEXISTorOFN_LONGNAMESorOFN_EXPLORERorOFN_HIDEREADONLY 

invokeGetOpenFileName,ADDRofn 

.ifeax==TRUE 

invokeCreateFile,addrbuffer,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL 

.ifeax!

=INVALID_HANDLE_VALUE 

movhFile,eax 

invokeCreateFileMapping,hFile,NULL,PAGE_READONLY,0,0,0 

.ifeax!

=NULL 

movhMapping,eax 

invokeMapViewOfFile,hMapping,FILE_MAP_READ,0,0,0 

.ifeax!

=NULL 

movpMapping,eax 

assumefs:

nothing 

pushfs:

[0] 

popseh.PrevLink 

movseh.CurrentHandler,offsetSEHHandler 

movseh.SafeOffset,offsetFinalExit 

leaeax,seh 

movfs:

[0],eax 

movseh.PrevEsp,esp 

movseh.PrevEbp,ebp 

movedi,pMapping 

assumeedi:

ptrIMAGE_DOS_HEADER 

.if[edi].e_magic==IMAGE_DOS_SIGNATURE 

addedi,[edi].e_lfanew 

assumeedi:

ptrIMAGE_NT_HEADERS 

.if[edi].Signature==IMAGE_NT_SIGNATURE 

movValidPE,TRUE 

.else 

movValidPE,FALSE 

.endif 

.else 

movValidPE,FALSE 

.endif 

FinalExit:

 

.ifValidPE==TRUE 

invokeMessageBox,0,addrFileValidPE,addrAppName,MB_OK+MB_ICONINFORMATION 

.else 

invokeMessageBox,0,addrFileInValidPE,addrAppName,MB_OK+MB_ICONINFORMATION 

.endif 

pushseh.PrevLink 

popfs:

[0] 

invokeUnmapViewOfFile,pMapping 

.else 

invokeMessageBox,0,addrFileMappingError,addrAppName,MB_OK+MB_ICONERROR 

.endif 

invokeCloseHandle,hMapping 

.else 

invokeMessageBox,0,addrFileOpenMappingError,addrAppName,MB_OK+MB_ICONERROR 

.endif 

invokeCloseHandle,hFile 

.else 

invokeMessageBox,0,addrFileOpenError,addrAppName,MB_OK+MB_ICONERROR 

.endif 

.endif 

invokeExitProcess,0 

startendp 

SEHHandlerprocusesedxpExcept:

DWORD,pFrame:

DWORD,pContext:

DWORD,pDispatch:

DWORD 

movedx,pFrame 

assumeedx:

ptrSEH 

moveax,pContext 

assumeeax:

ptrCONTEXT 

push[edx].SafeOffset 

pop[eax].regEip 

push[edx].PrevEsp 

pop[eax].regEsp 

push[edx].PrevEbp 

pop[eax].regEbp 

movValidPE,FALSE 

moveax,ExceptionContinueExecution 

ret 

SEHHandlerendp 

endstart

分析:

本例程打开一文件,先检验DOSheader是否有效,有效就接着检验PEheader的有效性,ok就认为是有效的PE文件了。

这里,我们还运用了结构异常处理(SEH),这样就不必检查每个可能的错误:

如果有错误出现,就认为PE检测失效所致,于是给出我们的报错信息。

其实Windows内部普遍使用SEH来检验参数传递的有效性。

若对SEH感兴趣的话,可阅读JeremyGordon的 文章。

程序调用打开文件通用对话框,用户选定执行文件后,程序便打开文件并映射到内存。

并在有效性检验前建立一SEH:

assumefs:

nothing 

pushfs:

[0] 

popseh.PrevLink 

movseh.CurrentHandler,offsetSEHHandler 

movseh.SafeOffset,offsetFinalExit 

leaeax,seh 

movfs:

[0],eax 

movseh.PrevEsp,esp 

movseh.PrevEbp,ebp

一开始就假设寄存器fs为空(assumefs:

nothing)。

记住这一步不能省却,因为MASM假设fs寄存器为ERROR。

接下来保存Windows使用的旧SEH处理函数地址到我们自己定义的结构中,同时保存我们的SEH处理函数地址和异常处理时的执行恢复地址,这样一旦错误发生就能由异常处理函数安全地恢复执行了。

同时还保存当前esp及ebp的值,以便我们的SEH处理函数将堆栈恢复到正常状态。

movedi,pMapping 

assumeedi:

ptrIMAGE_DOS_HEADER 

.if[edi].e_magic==IMAGE_DOS_SIGNATURE

成功建立SEH后继续校验工作。

置目标文件的首字节地址给edi,使其指向DOSheader的首字节。

为便于比较,我们告诉编译器可以假定edi正指向IMAGE_DOS_HEADER结构(事实亦是如此)。

然后比较DOSheader的首字是否等于字符串"MZ",这里利用了windows.inc中定义的IMAGE_DOS_SIGNATURE常量。

若比较成功,继续转到PEheader,否则设ValidPE 值为FALSE,意味着文件不是有效PE文件。

addedi,[edi].e_lfanew 

assumeedi:

ptrIMAGE_NT_HEADERS 

.if[edi].Signature==IMAGE_NT_SIGNATURE 

movValidPE,TRUE 

.else 

movValidPE,FALSE 

.endif

要定位到PEheader,需要读取DOSheader中的e_lfanew域值。

该域含有PEheader在文件中相对文件首部的偏移量。

edi加上该值正好定位到PEheader的首字节。

这儿可能会出错,如果文件不是PE文件,e_lfanew值就不正确,加上该值作为指针就可能导致异常。

若不用SEH,我们必须校验e_lfanew值是否超出文件尺寸,这不是一个好办法。

如果一切OK,我们就比较PEheader的首字是否是字符串"PE"。

这里在此用到了常量IMAGE_NT_SIGNATURE,相等则认为是有效的PE文件。

如果e_lfanew的值不正确导致异常,我们的SEH处理函数就得到执行控制权,简单恢复堆栈指针和基栈指针后,就根据safeoffset的值恢复执行到FinalExit标签处。

FinalExit:

 

.ifValidPE==TRUE 

invokeMessageBox,0,addrFileValidPE,addrAppName,MB_OK+MB_ICONINFORMATION 

.else 

invokeMessageBox,0,addrFileInValidPE,addrAppName,MB_OK+MB_ICONINFORMATION 

.endif

上述代码简单明确,根据ValidPE的值显示相应信息。

pushseh.PrevLink 

popfs:

[0]

一旦SEH不再使用,必须从SEH链上断开。

PE教程3:

FileHeader(文件头)

本课我们将要研究PEheader的fileheader(文件头)部分。

至此,我们已经学到了哪些东东,先简要回顾一下:

∙DOSMZheader又命名为 IMAGE_DOS_HEADER.。

其中只有两个域比较重要:

 e_magic 包含字符串"MZ",e_lfanew 包含PEheader在文件中的偏移量。

∙比较e_magic 是否为IMAGE_DOS_SIGNATURE以验证是否是有效的DOSheader。

比对符合则认为文件拥有一个有效的DOSheader。

∙为了定位PEheader,移动文件指针到e_lfanew所指向的偏移。

∙PEheader的第一个双字包含字符串"PE\0\0"。

该双字与IMAGE_NT_SIGNATURE比对,符合则认为PEheader有效。

本课我们继续探讨关于PEheader的知识。

PEheader的正式命名是 IMAGE_NT_HEADERS。

再来回忆一下这个结构。

IMAGE_NT_HEADERSSTRUCT 

Signaturedd?

 

FileHeaderIMAGE_FILE_HEADER<> 

OptionalHeaderIMAGE_OPTIONAL_HEADER32<> 

IMAGE_NT_HEADERSENDS

Signature PE标记,值为50h,45h,00h,00h(PE\0\0)。

 

FileHeader 该结构域包含了关于PE文件物理分布的一般信息。

OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息。

最有趣的东东在 OptionalHeader 里。

不过,FileHeader 里的一些域也很重要。

本课我们将学习FileHeader,下一课研究OptionalHeader。

IMAGE_FILE_HEADERSTRUCT 

MachineWORD?

 

NumberOfSectionsWORD?

 

TimeDateStampdd?

 

PointerToSymbolTabledd?

 

NumberOfSymbolsdd?

 

SizeOfOptionalHeaderWORD?

 

CharacteristicsWORD?

 

IMAGE_FILE_HEADERENDS

Fieldname

Meanings

Machine

该文件运行所要求的CPU。

对于Intel平台,该值是IMAGE_FILE_MACHINE_I386(14Ch)。

我们尝试了LUEVELSMEYER的pe.txt声明的14Dh和14Eh,但Windows不能正确执行。

看起来,除了禁止程序执行之外,本域对我们来说用处不大。

NumberOfSections

文件的节数目。

如果我们要在文件中增加或删除一个节,就需要修改这个值。

TimeDateStamp

文件创建日期和时间。

我们不感兴趣。

PointerToSymbolTable

用于调试。

NumberO

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 幼儿教育 > 幼儿读物

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1