在WIN64系统上运行的原生64位应用程序,其PE格式称为PE32+,当然是和32位程序有所不同的。但是,相同的部分是占大多数的,不同的地方,基本只是在PE头部分而已(IMAGE_NT_HEADERS64)。所以,本文只讲述PE32和PE32+不同的地方,相同的地方就略过不讲了。本文所有的资料均来源于微软官方的电子书籍《Microsoft可移植可执行文件和通用目标文件格式文件规范》和WDK7自带的ntimage.h,所以在数据的准确性方面应该是没有问题的。
首先对比一下IMAGE_NT_HEADERS32和IMAGE_NT_HEADERS64:
typedefstruct_IMAGE_NT_HEADERS{ ULONGSignature; typedefstruct_IMAGE_NT_HEADERS64{ ULONGSignature; IMAGE_FILE_HEADERFileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; IMAGE_FILE_HEADERFileHeader; IMAGE_OPTIONAL_HEADER64 OptionalHeader; } IMAGE_NT_HEADERS32,} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS32; *PIMAGE_NT_HEADERS64;
可见只是“可选头”部分不同。再对比IMAGE_OPTIONAL_HEADER32和IMAGE_OPTIONAL_HEADER64的异同:
typedefstruct_IMAGE_OPTIONAL_HEADER{typedef struct USHORTMagic; _IMAGE_OPTIONAL_HEADER64{ UCHARMajorLinkerVersion; UCHARMinorLinkerVersion; ULONGSizeOfCode; USHORT UCHAR UCHAR ULONG ULONG Magic; MajorLinkerVersion; MinorLinkerVersion; SizeOfCode; ULONGSizeOfInitializedData; ULONGSizeOfUninitializedData; ULONGAddressOfEntryPoint; ULONGBaseOfCode; SizeOfInitializedData; ULONG ULONGBaseOfData; SizeOfUninitializedData; //以上是标准域,以下是特定域 ULONGImageBase; ULONG ULONG AddressOfEntryPoint; BaseOfCode; ULONGSectionAlignment; ULONGFileAlignment; USHORT //以上是标准域,以下是特定域 ULONGLONGImageBase; ULONG SectionAlignment; MajorOperatingSystemVersion; USHORT ULONG FileAlignment; USHORT MinorOperatingSystemVersion; USHORTMajorImageVersion; USHORTMinorImageVersion; USHORTMajorSubsystemVersion; USHORTMinorSubsystemVersion; ULONGWin32VersionValue; ULONGSizeOfImage; MajorOperatingSystemVersion; USHORT MinorOperatingSystemVersion; USHORT USHORT USHORT MajorImageVersion; MinorImageVersion; MajorSubsystemVersion; USHORT ULONGSizeOfHeaders; ULONGCheckSum; MinorSubsystemVersion; USHORTSubsystem; ULONG ULONG ULONG ULONG USHORT USHORT Win32VersionValue; USHORTDllCharacteristics; ULONGSizeOfStackReserve; ULONGSizeOfStackCommit; ULONGSizeOfHeapReserve; ULONGSizeOfHeapCommit; ULONGLoaderFlags; SizeOfImage; SizeOfHeaders; CheckSum; Subsystem; DllCharacteristics; ULONGLONGSizeOfStackReserve; ULONGLONGSizeOfStackCommit; ULONGLONGSizeOfHeapReserve; ULONGLONGSizeOfHeapCommit; ULONGNumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ ENTRIES]; ULONG ULONG LoaderFlags; } IMAGE_OPTIONAL_HEADER32, NumberOfRvaAndSizes; *PIMAGE_OPTIONAL_HEADER32; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTO RY_ENTRIES]; } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
可见两者基本上是相同的,具体各部分含义如下(以下内容来自《Microsoft可移植可执行文件和通用目标文件格式文件规范》)。
首先是标准域部分:
特定域部分:
一些常数:
在整个可选头结构体里,最引人关注的恐怕就是DataDirectory(数据目录)了。
DataDirectory其实是一个结构体数组,它有16个元素:
typedefstruct_IMAGE_DATA_DIRECTORY{ ULONGVirtualAddress; ULONGSize; }IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY; #defineIMAGE_NUMBEROF_DIRECTORY_ENTRIES #defineIMAGE_DIRECTORY_ENTRY_EXPORT #defineIMAGE_DIRECTORY_ENTRY_IMPORT #defineIMAGE_DIRECTORY_ENTRY_RESOURCE #defineIMAGE_DIRECTORY_ENTRY_EXCEPTION #defineIMAGE_DIRECTORY_ENTRY_SECURITY #defineIMAGE_DIRECTORY_ENTRY_BASERELOC #defineIMAGE_DIRECTORY_ENTRY_DEBUG 0//ExportDirectory 1//ImportDirectory 2//ResourceDirectory 3//ExceptionDirectory 4//SecurityDirectory 5//BaseRelocationTable 6//DebugDirectory 7//(X86usage) // IMAGE_DIRECTORY_ENTRY_COPYRIGHT #defineIMAGE_DIRECTORY_ENTRY_ARCHITECTURE Data 7 //ArchitectureSpecific #defineIMAGE_DIRECTORY_ENTRY_GLOBALPTR 8//RVAofGP #defineIMAGE_DIRECTORY_ENTRY_TLS #defineIMAGE_DIRECTORY_ENTRY_LOAD_CONFIG Directory 9//TLSDirectory 10//LoadConfiguration #defineIMAGE_DIRECTORY_ENTRY_BOUND_IMPORT11//BoundImportDirectoryin headers #defineIMAGE_DIRECTORY_ENTRY_IAT #defineIMAGE_DIRECTORY_ENTRY_DELAY_IMPORT Descriptors 12//ImportAddressTable 13//DelayLoadImport #defineIMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR14//COMRuntimedescriptor
有人可能觉得奇怪,说为什么定义了16个元素,可是只有15个宏呢?这是因为还有一个IMAGE_DIRECTORY_ENTRY尚未使用,处于Reserve状态。在这已有的15个元素里,每个元素的VirtualAddress都指向一个结构体,Size都指出了这个结构体的大小。其中大家最为关注的输入表、导出表、重定位表、资源的结构体跟PE32一样,没有发生任何变化。但是也不是全部没有发生变化,比如TLS就发生了变化,不过这个似乎关注的人不多。
关于PE32+相对于PE32的变化,该说的我基本都说完了,不过如果这样子就结束文章,估计有些朋友会不太高兴。所以,我“做了一个艰难的决定”,一是把我亲自修改而成的PE32+文件结构超高清大图(3056*1910)奉献给大家,二是把我之前做的一个程序《SimplePE64Viewer》开源,以飨读者。接下来详细讲述一下我的PE查看器源码。类似于制作ARK,制作PE信息查看器也是从“底层新人”晋升为“底层高手”的必经之路。目前,支持PE32+文件的PE信息查看器不多,LordPE算是一个。我就模仿LordPE,把一个PE文件的基本信息,以及输入表和导出表的信息显示出来。在展示源码之前,先说思路,这个思路很简单,就是把PE文件载入内存,此时ReadFile返回的buffer指针就是IMAGE_DOS_HEADER结构体的首地址。当确认文件是PE32文件且是PE32+文件时,根据DOS头结构体e_lfanew成员的值获得NT头的偏移,然后展示一系列文件头和可选头的信息之后,显示导出表的信息(如果导出表不存在的话,就跳到后面显示输入表的信息)。由于用语言
描述获得导出表和输入表的信息十分麻烦,所以用列表的方式描述。
获得导出表信息(假设导出表存在):
1.把OptionalHeader.DataDirectory[0].VirtualAddress的值(RVA,相对虚拟地址)转化为VA(虚拟地址),获得IMAGE_EXPORT_DIRECTORY结构体相对于buffer的地址;
2.再使用三次ImageRvaToVa,把IMAGE_EXPORT_DIRECTORY结构体里AddressOfNames、AddressOfFunctions、AddressOfNameOrdinals这三个成员的值(RVA)转化为VA,获得导出函数的名字,RVA和序号,然后使用printf把它们打印出来。获得输入表的信息(假设输入表存在):
1.把OptionalHeader.DataDirectory[1].VirtualAddress的值(RVA)转化为VA,获得IMAGE_EXPORT_DIRECTORY结构体相对于buffer的地址;
2.获得被输入的DLL的名字;
3.获得第一个Thunk的值(假定名为dwThunk),并把这个值(RVA)转化为VA,得到IMAGE_THUNK_DATA相对于buffer的地址(假定名为_pThunk);
4.把pThunk-u1.AddressOfData-Name的值(RVA)转化为VA,获得函数名和Hint,然后用printf把Hint(序号)、dwThunk(RVA)以及函数名打印出来;
5.重复步骤3,直到_pThunk-u1.AddressOfData为NULL;
6.重复步骤2,直到“输入的DLL的名字”为NULL。
详细代码如下:
#includestdio.h #includeWindows.h #includeIMAGEHLP.H #pragmacomment(lib,ImageHlp.lib) voidMyCls(HANDLEhConsole) { COORDcoordScreen={0,0};//设置清屏后光标返回的屏幕左上角坐标BOOLbSuccess; DWORDcCharsWritten; CONSOLE_SCREEN_BUFFER_INFOcsbi;//保存缓冲区信息 DWORDdwConSize;//当前缓冲区可容纳的字符数 bSuccess=GetConsoleScreenBufferInfo(hConsole,csbi);//获得缓冲区信息 dwConSize=csbi.dwSize.X*csbi.dwSize.Y;//缓冲区容纳字符数目 bSuccess=FillConsoleOutputCharacter(hConsole,(TCHAR) ,dwConSize,coordScreen,cCharsWritten); bSuccess=GetConsoleScreenBufferInfo(hConsole,csbi);//获得缓冲区信息 bSuccess=FillConsoleOutputAttribute(hConsole,csbi.wAttributes,dwConSize,coor dScreen,cCharsWritten); bSuccess=SetConsoleCursorPosition(hConsole,coordScreen); return; } voidclrscr(void) { HANDLEhStdOut=GetStdHandle(STD_OUTPUT_HANDLE); MyCls(hStdOut); return; } DWORDFileLen(char*filename) { WIN32_FIND_DATAAfileInfo={0}; DWORDfileSize=0; HANDLEhFind; hFind=FindFirstFileA(filename,fileInfo); if(hFind!=INVALID_HANDLE_VALUE) { fileSize=fileInfo.nFileSizeLow; FindClose(hFind); } returnfileSize; } CHAR*LoadFile(char*filename) { DWORDdwReadWrite,LenOfFile=FileLen(filename); HANDLEhFile=CreateFileA(filename,GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0); if(hFile!=INVALID_HANDLE_VALUE) { PCHARbuffer=(PCHAR)malloc(LenOfFile); SetFilePointer(hFile,0,0,FILE_BEGIN); ReadFile(hFile,buffer,LenOfFile,dwReadWrite,0); CloseHandle(hFile); returnbuffer; } returnNULL; } VOIDShowPE64Info(char*filename) { PIMAGE_NT_HEADERS64pinths64; PIMAGE_DOS_HEADERpdih; char*filedata; filedata=LoadFile(filename); pdih=(PIMAGE_DOS_HEADER)filedata; pinths64=(PIMAGE_NT_HEADERS64)(filedata+pdih-e_lfanew); if(pinths64-Signature!=0x00004550) { printf(无效的PE文件!\n); return; } if(pinths64-OptionalHeader.Magic!=0x20b) { printf(不是PE32+格式的文件!\n); return; } printf(\n); printf(入口点:%llx\n,pinths64-OptionalHeader.AddressOfEntryPoint); printf(镜像基址: printf(镜像大小: printf(代码基址: printf( %llx\n,pinths64-OptionalHeader.ImageBase); %llx\n,pinths64-OptionalHeader.SizeOfImage); %llx\n,pinths64-OptionalHeader.BaseOfCode); 块对齐: %llx\n,pinths64-OptionalHeader.SectionAlignment); printf(文件块对齐:%llx\n,pinths64-OptionalHeader.FileAlignment); printf(子系统:%llx\n,pinths64-OptionalHeader.Subsystem); printf(区段数目: printf(时间日期标志: %llx\n,pinths64-FileHeader.NumberOfSections); %llx\n,pinths64-FileHeader.TimeDateStamp); printf( 首部大小: %llx\n,pinths64-OptionalHeader.SizeOfHeaders); printf(特征值: printf(校验和: printf( %llx\n,pinths64-FileHeader.Characteristics); %llx\n,pinths64-OptionalHeader.CheckSum); 可选头部大小: %llx\n,pinths64-FileHeader.SizeOfOptionalHeader); printf(RVA 数及大小: %llx\n,pinths64-OptionalHeader.NumberOfRvaAndSizes); getchar(); printf(\n); printf(输出表:\n); printf(Ordinal\tRVA\t\tName\n); PIMAGE_EXPORT_DIRECTORYpied; if(pinths64,pdih,pinths64-OptionalHeader.DataDirectory[0].VirtualAddres s==0)gotoimp; pied=(PIMAGE_EXPORT_DIRECTORY)ImageRvaToVa((PIMAGE_NT_HEADERS)pinths64,p dih,pinths64-OptionalHeader.DataDirectory[0].VirtualAddress,NULL); DWORDi=0; DWORDNumberOfNames=pied-NumberOfNames; ULONGLONG**ppdwNames=(ULONGLONG**)pied-AddressOfNames; ppdwNames = (PULONGLONG*)ImageRvaToVa((PIMAGE_NT_HEADERS)pinths64,pdih,(ULONG)ppdwNames, NULL); ULONGLONG**ppdwAddr=(ULONGLONG**)pied-AddressOfFunctions; ppdwAddr = (PULONGLONG*)ImageRvaToVa((PIMAGE_NT_HEADERS)pinths64,pdih,(DWORD)ppdwAddr,N ULL); ULONGLONG *ppdwOrdin=(ULONGLONG*)ImageRvaToVa((PIMAGE_NT_HEADERS)pinths64,pdih,(DWORD) pied-AddressOfNameOrdinals,NULL); char* szFun=(PSTR)ImageRvaToVa((PIMAGE_NT_HEADERS)pinths64,pdih,(ULONG)*ppdwNames, NULL); for(i=0;iNumberOfNames;i++) { printf(%0.4x\t%0.8x\t%s\n,i+1,*ppdwAddr,szFun); szFun=szFun+strlen(szFun)+1; ppdwAddr++; if(i%200==0i/200=1) { printf({Press[ENTER]tocontinue...}); getchar(); } } imp: printf(\n\n输入表:\n); printf(\tHint\tThunkRVA\tName\n); PIMAGE_IMPORT_DESCRIPTORpiid; PIMAGE_THUNK_DATA_pThunk=NULL; DWORDdwThunk=NULL; USHORTHint; if(pinths64-OptionalHeader.DataDirectory[1].VirtualAddress==0)return; piid=(PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa((PIMAGE_NT_HEADERS)pinths64, pdih,pinths64-OptionalHeader.DataDirectory[1].VirtualAddress,NULL); for(;piid-Name!=NULL;) { char*szName=(PSTR)ImageRvaToVa((PIMAGE_NT_HEADERS)pinths64,pdih, (ULONG)piid-Name,0); printf(%s\n,szName); if(piid-OriginalFirstThunk!=0) { dwThunk=piid-OriginalFirstThunk; _pThunk=(PIMAGE_THUNK_DATA)ImageRvaToVa((PIMAGE_NT_HEADERS)pinths64,pdih ,(ULONG)piid-OriginalFirstThunk,NULL); } else { dwThunk=piid-FirstThunk; _pThunk=(PIMAGE_THUNK_DATA)ImageRvaToVa((PIMAGE_NT_HEADERS)pinths64,pdih ,(ULONG)piid-FirstThunk,NULL); } for(;_pThunk-u1.AddressOfData!=NULL;) { char *szFun=(PSTR)ImageRvaToVa((PIMAGE_NT_HEADERS)pinths64,pdih,(ULONG)(((PIMAGE_ IMPORT_BY_NAME)_pThunk-u1.AddressOfData)-Name),0); if(szFun!=NULL) memcpy(Hint,szFun-2,2); else Hint=-1; printf(\t%0.4x\t%0.8x\t%s\n,Hint,dwThunk,szFun); dwThunk+=8; _pThunk++; } piid++; printf({Press[ENTER]tocontinue...}); getchar(); } } intmain() { charfilename[MAX_PATH]={0}; SetConsoleTitleA(SimplePE64Viewer); bgn: printf(Simple PE64 Viewer\n====================\nAuthor: Tesla.Angela\nVersion:0.01\nSupport:PE32+file\n\n\n); printf(输入文件名(支持文件拖拽,直接按回车则默认打开ntoskrnl.exe,输入 exit退出):); gets(filename); if(FileLen(filename)==0) { if(stricmp(filename,exit)) { CopyFileA(c:\\windows\\system32\\ntoskrnl.exe,c:\\ntoskrnl.exe,0); strcpy(filename,c:\\ntoskrnl.exe); printf(c:\\ntoskrnl.exe\n); } else gotoend; } ShowPE64Info(filename); clrscr(); gotobgn; end: DeleteFileA(c:\\ntoskrnl.exe); return0; }
运行效果:
本文到此结束,如果错误之处,还请各位读者不吝赐教。本文所说的图片和源码均包含在了附件里。(编辑提醒,本文涉及的代码直接在PDF附件下载即可)