ITPub博客

首页 > 大数据 > Hadoop > (软件加密解密技术内幕)PE文件结构

(软件加密解密技术内幕)PE文件结构

Hadoop 作者:caocao1012 时间:2013-11-25 16:59:00 0 删除 编辑

PE教程2: 检验PE文件的有效性

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

理论:

如何才能校验指定文件是否为一有效PE文件呢?这个问题很难回答,完全取决于想要的精准程度。您可以检验PE文件格式里的各个数据结构,或者仅校验一些关键数据结构。大多数情况下,没有必要校验文件里的每一个数据结构,只要一些关键数据结构有效,我们就认为是有效的PE文件了。下面我们就来实现前面的假设。

我们要验证的重要数据结构就是PE
header。从编程角度看,PE
header实际就是一个IMAGE_NT_HEADERS结构。定义如下:

IMAGE_NT_HEADERS STRUCT   Signature
dd ?   FileHeader IMAGE_FILE_HEADER <>   OptionalHeader
IMAGE_OPTIONAL_HEADER32 <>IMAGE_NT_HEADERS ENDS

Signature一dword类型,值为50h, 45h, 00h, 00h(PE)。本域为PE标记,我们可以此识别给定文件是否为有效PE文件。FileHeader该结构域包含了关于PE文件物理分布的信息,比如节数目、文件执行机器等。OptionalHeader该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。

我们目的很明确。如果IMAGE_NT_HEADERS的signature域值等于"PE",那么就是有效的PE文件。实际上,为了比较方便,Microsoft已定义了常量IMAGE_NT_SIGNATURE供我们使用。

IMAGE_DOS_SIGNATURE equ
5A4DhIMAGE_OS2_SIGNATURE
equ 454EhIMAGE_OS2_SIGNATURE_LE equ 454ChIMAGE_VXD_SIGNATURE equ 454ChIMAGE_NT_SIGNATURE equ 4550h

接下来的问题是:如何定位PE header?答案很简单: DOS MZ header已经包含了指向PE header的文件偏移量。DOS MZ
header又定义成结构IMAGE_DOS_HEADER。查询windows.inc,我们知道IMAGE_DOS_HEADER结构的e_lfanew成员就是指向PE header的文件偏移量。

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

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

一旦证明文件的DOS header有效后,就可用e_lfanew来定位PE header了。

比较PE header的第一个字的值是否等于IMAGE_NT_HEADER。如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。

Example:

.386.model flat,stdcalloption casemap:noneinclude masm32includewindows.incinclude masm32includekernel32.incinclude masm32includecomdlg32.incinclude masm32includeuser32.incincludelib masm32libuser32.libincludelib masm32libkernel32.libincludelib masm32libcomdlg32.libSEH structPrevLink dd ?
   ; the address of the previous seh structureCurrentHandler dd ?    ; the
address of the exception handlerSafeOffset dd ?    ; The offset where it's
safe to continue executionPrevEsp dd ?      ; the old value in espPrevEbp dd ?     ; The old value in ebpSEH ends.dataAppName db "PE tutorial no.2",0ofn OPENFILENAME <>FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0                 db "All Files",0,"*.*",0,0FileOpenError db "Cannot
open the file for reading",0FileOpenMappingError db "Cannot open the file
for memory mapping",0FileMappingError db "Cannot map the file into
memory",0FileValidPE db "This file is a valid PE",0FileInValidPE db
"This file is not a valid PE",0.data?buffer db 512 dup(?)hFile dd ?hMapping dd ?pMapping dd ?ValidPE dd ?.codestart procLOCAL seh:SEHmov ofn.lStructSize,SIZEOF
ofnmov ofn.lpstrFilter, OFFSET FilterStringmov ofn.lpstrFile, OFFSET
buffermov ofn.nMaxFile,512mov ofn.Flags, OFN_FILEMUSTEXIST or
OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLYinvoke GetOpenFileName, ADDR ofn.if eax==TRUE    invoke
CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL    .if eax!=INVALID_HANDLE_VALUE       mov
hFile, eax       invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0       .if eax!=NULL          mov hMapping, eax          invoke
MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0          .if eax!=NULL             mov pMapping,eax             assume fs:nothing             push fs:[0]             pop seh.PrevLink            
mov seh.CurrentHandler,offset SEHHandler             mov
seh.SafeOffset,offset FinalExit             lea eax,seh            
mov fs:[0], eax             mov seh.PrevEsp,esp             mov
seh.PrevEbp,ebp             mov edi, pMapping             assume
edi:ptr IMAGE_DOS_HEADER             .if [edi].e_magic==IMAGE_DOS_SIGNATURE                add edi, [edi].e_lfanew                assume edi:ptr
IMAGE_NT_HEADERS                .if [edi].Signature==IMAGE_NT_SIGNATURE                   mov ValidPE, TRUE                .else                   mov ValidPE, FALSE                .endif             .else                 mov ValidPE,FALSE            
.endifFinalExit:             .if ValidPE==TRUE                
invoke MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION             .else                invoke MessageBox, 0, addr
FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION             .endif             push seh.PrevLink             pop fs:[0]            
invoke UnmapViewOfFile, pMapping          .else             invoke
MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR          .endif          invoke CloseHandle,hMapping       .else          invoke MessageBox, 0, addr FileOpenMappingError, addr AppName,
MB_OK+MB_ICONERROR       .endif       invoke CloseHandle, hFile    .else       invoke MessageBox, 0, addr FileOpenError, addr AppName,
MB_OK+MB_ICONERROR    .endif.endifinvoke ExitProcess, 0start
endpSEHHandler proc uses edx pExcept:DWORD, pFrame:DWORD,
pContext:DWORD, pDispatch:DWORD    mov edx,pFrame    assume edx:ptr
SEH    mov eax,pContext    assume eax:ptr CONTEXT    push
[edx].SafeOffset    pop [eax].regEip    push [edx].PrevEsp    pop
[eax].regEsp    push [edx].PrevEbp    pop [eax].regEbp    mov
ValidPE, FALSE    mov eax,ExceptionContinueExecution    retSEHHandler endpend start

分析:

本例程打开一文件,先检验DOS
header是否有效,有效就接着检验PE header的有效性,ok就认为是有效的PE文件了。这里,我们还运用了结构异常处理(SEH),这样就不必检查每个可能的错误:如果有错误出现,就认为PE检测失效所致,于是给出我们的报错信息。其实Windows内部普遍使用SEH来检验参数传递的有效性。若对SEH感兴趣的话,可阅读Jeremy Gordon的文章。

程序调用打开文件通用对话框,用户选定执行文件后,程序便打开文件并映射到内存。并在有效性检验前建立一SEH:

   assume fs:nothing   push fs:[0]   pop
seh.PrevLink   mov seh.CurrentHandler,offset SEHHandler   mov
seh.SafeOffset,offset FinalExit   lea eax,seh   mov fs:[0], eax   mov seh.PrevEsp,esp   mov seh.PrevEbp,ebp

一开始就假设寄存器fs为空(assume
fs:nothing)。记住这一步不能省却,因为MASM假设fs寄存器为ERROR。接下来保存Windows使用的旧SEH处理函数地址到我们自己定义的结构中,同时保存我们的SEH处理函数地址和异常处理时的执行恢复地址,这样一旦错误发生就能由异常处理函数安全地恢复执行了。同时还保存当前esp及ebp的值,以便我们的SEH处理函数将堆栈恢复到正常状态。

   mov edi, pMapping   assume edi:ptr
IMAGE_DOS_HEADER   .if [edi].e_magic==IMAGE_DOS_SIGNATURE

成功建立SEH后继续校验工作。置目标文件的首字节地址给edi,使其指向DOS header的首字节。为便于比较,我们告诉编译器可以假定edi正指向IMAGE_DOS_HEADER结构(事实亦是如此)。然后比较DOS header的首字是否等于字符串"MZ",这里利用了windows.inc中定义的IMAGE_DOS_SIGNATURE常量。若比较成功,继续转到PE
header,否则设ValidPE值为FALSE,意味着文件不是有效PE文件。

      add edi, [edi].e_lfanew      assume edi:ptr
IMAGE_NT_HEADERS      .if [edi].Signature==IMAGE_NT_SIGNATURE         mov ValidPE, TRUE      .else         mov ValidPE, FALSE      .endif

要定位到PE
header,需要读取DOS
header中的e_lfanew域值。该域含有PE header在文件中相对文件首部的偏移量。edi加上该值正好定位到PE header的首字节。这儿可能会出错,如果文件不是PE文件,e_lfanew值就不正确,加上该值作为指针就可能导致异常。若不用SEH,我们必须校验e_lfanew值是否超出文件尺寸,这不是一个好办法。如果一切OK,我们就比较PE header的首字是否是字符串"PE"。这里在此用到了常量IMAGE_NT_SIGNATURE,相等则认为是有效的PE文件。如果e_lfanew的值不正确导致异常,我们的SEH处理函数就得到执行控制权,简单恢复堆栈指针和基栈指针后,就根据safeoffset的值恢复执行到FinalExit标签处。

FinalExit:   .if ValidPE==TRUE      invoke
MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION   .else      invoke MessageBox, 0, addr FileInValidPE, addr AppName,
MB_OK+MB_ICONINFORMATION   .endif

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

   push seh.PrevLink   pop fs:[0]

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

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/7514496/viewspace-1111957/,如需转载,请注明出处,否则将追究法律责任。

上一篇: 没有了~
下一篇: 没有了~
请登录后发表评论 登录
全部评论