探索黑客技术攻防,实战研究与安全创新

导航菜单

初步探索PE32+格式文件

在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可移植可执行文件和通用目标文件格式文件规范》)。

首先是标准域部分:

图片1.png

特定域部分:

图片2.png

图片3.png

一些常数:

图片4.png

在整个可选头结构体里,最引人关注的恐怕就是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;
}


运行效果:

图片5.png

图片6.png

本文到此结束,如果错误之处,还请各位读者不吝赐教。本文所说的图片和源码均包含在了附件里。(编辑提醒,本文涉及的代码直接在PDF附件下载即可)