您的位置:首页 > 其它

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

2006-11-12 21:50 381 查看
[align=left]本教程中我们将学习如何检测给定文件是一有效PE文件。[/align]
[align=left] [/align]
[align=left] [/align]
[align=left]理论:[/align]
[align=left]如何才能校验指定文件是否为一有效PE文件呢? 这个问题很难回答,完全取决于想要的精准程度。您可以检验PE文件格式里的各个数据结构,或者仅校验一些关键数据结构。大多数情况下,没有必要校验文件里的每一个数据结构,只要一些关键数据结构有效,我们就认为是有效的PE文件了。下面我们就来实现前面的假设。[/align]
[align=left] [/align]
[align=left]我们要验证的重要数据结构就是 PE header。从编程角度看,PE header 实际就是一个 IMAGE_NT_HEADERS 结构。定义如下:[/align]
[align=left] [/align]
[align=left]IMAGE_NT_HEADERS STRUCT [/align]
[align=left]Signature dd ? [/align]
[align=left]FileHeader IMAGE_FILE_HEADER <> [/align]
[align=left]OptionalHeader IMAGE_OPTIONAL_HEADER32 <> [/align]
[align=left]IMAGE_NT_HEADERS ENDS [/align]
[align=left] [/align]
[align=left]Signature 一dword类型,值为50h, 45h, 00h, 00h(PE/0/0)。 本域为PE标记,我们可以此识别给定文件是否为有效PE文件。[/align]
[align=left]FileHeader 该结构域包含了关于PE文件物理分布的信息, 比如节数目、文件执行机器等。[/align]
[align=left]OptionalHeader 该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。[/align]
[align=left] [/align]
[align=left]我们目的很明确。如果IMAGE_NT_HEADERS的signature域值等于"PE/0/0",那么就是有效的PE文件。实际上,为了比较方便,Microsoft已定义了常量IMAGE_NT_SIGNATURE供我们使用。[/align]
[align=left] [/align]
[align=left]IMAGE_DOS_SIGNATURE equ 5A4Dh [/align]
[align=left]IMAGE_OS2_SIGNATURE equ 454Eh [/align]
[align=left]IMAGE_OS2_SIGNATURE_LE equ 454Ch [/align]
[align=left]IMAGE_VXD_SIGNATURE equ 454Ch [/align]
[align=left]IMAGE_NT_SIGNATURE equ 4550h [/align]
[align=left] [/align]
[align=left]接下来的问题是: 如何定位 PE header? 答案很简单: DOS MZ header 已经包含了指向 PE header 的文件偏移量。DOS MZ header 又定义成结构 IMAGE_DOS_HEADER 。查询windows.inc,我们知道 IMAGE_DOS_HEADER 结构的e_lfanew成员就是指向 PE header 的文件偏移量。 [/align]
[align=left] [/align]
[align=left]现在将所有步骤总结如下: [/align]
[align=left] [/align]
[align=left]首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE,是则 DOS MZ header 有效。 [/align]
[align=left]一旦证明文件的 DOS header 有效后,就可用e_lfanew来定位 PE header 了。 [/align]
[align=left]比较 PE header 的第一个字的值是否等于 IMAGE_NT_HEADER。如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。 [/align]
[align=left]Example:[/align]
[align=left].386 [/align]
[align=left].model flat,stdcall [/align]
[align=left]option casemap:none [/align]
[align=left]include /masm32/include/windows.inc [/align]
[align=left]include /masm32/include/kernel32.inc [/align]
[align=left]include /masm32/include/comdlg32.inc [/align]
[align=left]include /masm32/include/user32.inc [/align]
[align=left]includelib /masm32/lib/user32.lib [/align]
[align=left]includelib /masm32/lib/kernel32.lib [/align]
[align=left]includelib /masm32/lib/comdlg32.lib [/align]
[align=left] [/align]
[align=left]SEH struct [/align]
[align=left]PrevLink dd ? ; the address of the previous seh structure [/align]
[align=left]CurrentHandler dd ? ; the address of the exception handler [/align]
[align=left]SafeOffset dd ? ; The offset where it's safe to continue execution [/align]
[align=left]PrevEsp dd ? ; the old value in esp [/align]
[align=left]PrevEbp dd ? ; The old value in ebp [/align]
[align=left]SEH ends[/align]
[align=left] [/align]
[align=left].data [/align]
[align=left]AppName db "PE tutorial no.2",0 [/align]
[align=left]ofn OPENFILENAME <> [/align]
[align=left]FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0 [/align]
[align=left]db "All Files",0,"*.*",0,0 [/align]
[align=left]FileOpenError db "Cannot open the file for reading",0 [/align]
[align=left]FileOpenMappingError db "Cannot open the file for memory mapping",0 [/align]
[align=left]FileMappingError db "Cannot map the file into memory",0 [/align]
[align=left]FileValidPE db "This file is a valid PE",0 [/align]
[align=left]FileInValidPE db "This file is not a valid PE",0 [/align]
[align=left] [/align]
[align=left].data? [/align]
[align=left]buffer db 512 dup(?) [/align]
[align=left]hFile dd ? [/align]
[align=left]hMapping dd ? [/align]
[align=left]pMapping dd ? [/align]
[align=left]ValidPE dd ? [/align]
[align=left] [/align]
[align=left].code [/align]
[align=left]start proc [/align]
[align=left]LOCAL seh:SEH [/align]
[align=left]mov ofn.lStructSize,SIZEOF ofn [/align]
[align=left]mov ofn.lpstrFilter, OFFSET FilterString [/align]
[align=left]mov ofn.lpstrFile, OFFSET buffer [/align]
[align=left]mov ofn.nMaxFile,512 [/align]
[align=left]mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY [/align]
[align=left]invoke GetOpenFileName, ADDR ofn [/align]
[align=left].if eax==TRUE [/align]
[align=left]invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL [/align]
[align=left].if eax!=INVALID_HANDLE_VALUE [/align]
[align=left]mov hFile, eax [/align]
[align=left]invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0 [/align]
[align=left].if eax!=NULL [/align]
[align=left]mov hMapping, eax [/align]
[align=left]invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0 [/align]
[align=left].if eax!=NULL [/align]
[align=left]mov pMapping,eax [/align]
[align=left]assume fs:nothing [/align]
[align=left]push fs:[0] [/align]
[align=left]pop seh.PrevLink [/align]
[align=left]mov seh.CurrentHandler,offset SEHHandler [/align]
[align=left]mov seh.SafeOffset,offset FinalExit [/align]
[align=left]lea eax,seh [/align]
[align=left]mov fs:[0], eax [/align]
[align=left]mov seh.PrevEsp,esp [/align]
[align=left]mov seh.PrevEbp,ebp [/align]
[align=left]mov edi, pMapping [/align]
[align=left]assume edi:ptr IMAGE_DOS_HEADER [/align]
[align=left].if [edi].e_magic==IMAGE_DOS_SIGNATURE [/align]
[align=left]add edi, [edi].e_lfanew [/align]
[align=left]assume edi:ptr IMAGE_NT_HEADERS [/align]
[align=left].if [edi].Signature==IMAGE_NT_SIGNATURE [/align]
[align=left]mov ValidPE, TRUE [/align]
[align=left].else [/align]
[align=left]mov ValidPE, FALSE [/align]
[align=left].endif [/align]
[align=left].else [/align]
[align=left]mov ValidPE,FALSE [/align]
[align=left].endif [/align]
[align=left]FinalExit: [/align]
[align=left].if ValidPE==TRUE [/align]
[align=left]invoke MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION [/align]
[align=left].else [/align]
[align=left]invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION [/align]
[align=left].endif [/align]
[align=left]push seh.PrevLink [/align]
[align=left]pop fs:[0] [/align]
[align=left]invoke UnmapViewOfFile, pMapping [/align]
[align=left].else [/align]
[align=left]invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR [/align]
[align=left].endif [/align]
[align=left]invoke CloseHandle,hMapping [/align]
[align=left].else [/align]
[align=left]invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR [/align]
[align=left].endif [/align]
[align=left]invoke CloseHandle, hFile [/align]
[align=left].else [/align]
[align=left]invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR [/align]
[align=left].endif [/align]
[align=left].endif [/align]
[align=left]invoke ExitProcess, 0 [/align]
[align=left]start endp [/align]
[align=left] [/align]
[align=left]SEHHandler proc uses edx pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD [/align]
[align=left]mov edx,pFrame [/align]
[align=left]assume edx:ptr SEH [/align]
[align=left]mov eax,pContext [/align]
[align=left]assume eax:ptr CONTEXT [/align]
[align=left]push [edx].SafeOffset [/align]
[align=left]pop [eax].regEip [/align]
[align=left]push [edx].PrevEsp [/align]
[align=left]pop [eax].regEsp [/align]
[align=left]push [edx].PrevEbp [/align]
[align=left]pop [eax].regEbp [/align]
[align=left]mov ValidPE, FALSE [/align]
[align=left]mov eax,ExceptionContinueExecution [/align]
[align=left]ret [/align]
[align=left]SEHHandler endp [/align]
[align=left]end start [/align]
[align=left] [/align]
[align=left]分析:[/align]
[align=left]本例程打开一文件,先检验DOS header是否有效,有效就接着检验PE header的有效性,ok就认为是有效的PE文件了。这里,我们还运用了结构异常处理(SEH),这样就不必检查每个可能的错误: 如果有错误出现,就认为PE检测失效所致,于是给出我们的报错信息。其实Windows内部普遍使用SEH来检验参数传递的有效性。若对SEH感兴趣的话,可阅读Jeremy Gordon的 文章。[/align]
[align=left] [/align]
[align=left]程序调用打开文件通用对话框,用户选定执行文件后,程序便打开文件并映射到内存。并在有效性检验前建立一 SEH:[/align]
[align=left] [/align]
[align=left]assume fs:nothing [/align]
[align=left]push fs:[0] [/align]
[align=left]pop seh.PrevLink [/align]
[align=left]mov seh.CurrentHandler,offset SEHHandler [/align]
[align=left]mov seh.SafeOffset,offset FinalExit [/align]
[align=left]lea eax,seh [/align]
[align=left]mov fs:[0], eax [/align]
[align=left]mov seh.PrevEsp,esp [/align]
[align=left]mov seh.PrevEbp,ebp [/align]
[align=left] [/align]
[align=left]一开始就假设寄存器 fs为空(assume fs:nothing)。 记住这一步不能省却,因为MASM假设fs寄存器为ERROR。接下来保存 Windows使用的旧SEH处理函数地址到我们自己定义的结构中,同时保存我们的SEH处理函数地址和异常处理时的执行恢复地址,这样一旦错误发生就能由异常处理函数安全地恢复执行了。同时还保存当前esp及ebp的值,以便我们的SEH处理函数将堆栈恢复到正常状态。[/align]
[align=left] [/align]
[align=left]mov edi, pMapping [/align]
[align=left]assume edi:ptr IMAGE_DOS_HEADER [/align]
[align=left].if [edi].e_magic==IMAGE_DOS_SIGNATURE [/align]
[align=left] [/align]
[align=left]成功建立SEH后继续校验工作。置目标文件的首字节地址给edi,使其指向DOS header的首字节。为便于比较,我们告诉编译器可以假定edi正指向IMAGE_DOS_HEADER结构(事实亦是如此)。然后比较DOS header的首字是否等于字符串"MZ",这里利用了windows.inc中定义的IMAGE_DOS_SIGNATURE常量。若比较成功,继续转到PE header,否则设ValidPE 值为FALSE,意味着文件不是有效PE文件。[/align]
[align=left] [/align]
[align=left]add edi, [edi].e_lfanew [/align]
[align=left]assume edi:ptr IMAGE_NT_HEADERS [/align]
[align=left].if [edi].Signature==IMAGE_NT_SIGNATURE [/align]
[align=left]mov ValidPE, TRUE [/align]
[align=left].else [/align]
[align=left]mov ValidPE, FALSE [/align]
[align=left].endif [/align]
[align=left] [/align]
[align=left]要定位到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文件。[/align]
[align=left]如果e_lfanew的值不正确导致异常,我们的SEH处理函数就得到执行控制权,简单恢复堆栈指针和基栈指针后,就根据safeoffset的值恢复执行到FinalExit标签处。[/align]
[align=left] [/align]
[align=left]FinalExit: [/align]
[align=left].if ValidPE==TRUE [/align]
[align=left]invoke MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION [/align]
[align=left].else [/align]
[align=left]invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION [/align]
[align=left].endif [/align]
[align=left] [/align]
[align=left]上述代码简单明确,根据ValidPE的值显示相应信息。[/align]
[align=left] [/align]
[align=left]push seh.PrevLink [/align]
[align=left]pop fs:[0] [/align]
[align=left] [/align]
[align=left]一旦SEH不再使用,必须从SEH链上断开。[/align]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: