前言
下文提及的相关数据结构,代码片段均为64位环境,32位环境会加以标注.
PE文件
PE(即PortableExecutable)表示可移植的可执行文件,是Windows平台原生的可执行代码载体(此处要区别于NET的IL,JAVA的字节码等),Windows平台所有可以被加载执行的文件都遵从PE文件格式。
注:在linux平台下的可执行文件都遵从ELF文件格式,PE与ELF文件格式都继承于Unix的COFF文件格式,由此可见,虽然不同平台上的可执行文件不能兼容,但是从设计思想上有很多共通之处。
PE文件格式详解
文件数据结构布局
(网上有很多查看PE文件格式的工具,我常用010editor配合其EXE模版)
注:010editor模版所显示的在Section Data后还有其他内容,这只是模版的显示,相关内容其实还是在某个Section Data内部,但是由于其有特殊用途,故被模版单独展示。
由上图所示,一个PE文件有定长部分和不定长部分,这也体现了PE文件格式良好的可扩展性和兼容性。
定长部分:
DOSheader+DOSStub+NTheader
不定长部分:
Sectionheader[n]+Sectiondata[n]
磁盘布局与内存布局
一个PE文件,在加载运行前后的布局这里我叫做磁盘布局和内存布局,这两种布局由于不同时期的对齐粒度不同导致有所差异:磁盘布局的对齐粒度是0x200,内存布局的对齐粒度是0x1000。
所以这里就有一个偏移地址转换的问题,PE文件中大体上有四类地址:
虚拟内存地址(VA):RVA +加载基地址
相对虚拟地址(RVA):内存偏移
文件偏移地址(FOA):文件偏移
特殊地址:这中地址很少见,在资源节中有使用,其计算方式类似于汇编相对跳转的计算
相关数据结构(依次说明)
typedefstruct_IMAGE_DOS_HEADER { WORDe_magic;//DOS文件签名0x4d5a WORDe_cblp; WORDe_cp; WORDe_crlc; WORDe_cparhdr; WORDe_minalloc; WORDe_maxalloc; WORDe_ss; WORDe_sp; WORDe_csum; WORDe_ip; WORDe_cs; WORDe_lfarlc; WORDe_ovno; WORDe_res[4]; WORDe_oemid; WORDe_oeminfo; WORDe_res2[10]; LONGe_lfanew;//NT头偏移 }IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;//DOSHeader结构
其中最重要的是e_magic和e_lfanew字段,e_magic字段是文件头,固定值0x4D5A,e_lfanew:NT头的文件偏移,通常是0xE8
这是一小段Dos程序,当在Dos系统下运行时,会打印出"ThisprogramcannotberuninDOSmode",现在基本不会用到。//DOSStub
typedefstruct_IMAGE_NT_HEADERS64{ DWORDSignature;//PE签名,0x4550 IMAGE_FILE_HEADERFileHeader;//文件头 IMAGE_OPTIONAL_HEADER64OptionalHeader;//可选头 }IMAGE_NT_HEADERS64,*PIMAGE_NT_HEADERS64; //FileHeader结构 typedefstruct_IMAGE_FILE_HEADER{ WORDMachine;//运行平台 WORDNumberOfSections;//SectionHeader的数目 DWORDTimeDateStamp;//编译时间戳 DWORDPointerToSymbolTable;//COFF符号表指针 DWORDNumberOfSymbols;//符号数目 WORDSizeOfOptionalHeader;//可选头大小,32位与64位大小不同 WORDCharacteristics;//属性,实际上是一个bitmap }IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;//NTheader结构
运行平台:
//IMAGE_FILE_HEADER.Characteristics字段 structFILE_CHARACTERISTICSCharacteristics{ WORDIMAGE_FILE_RELOCS_STRIPPED:1//在可执行文件中没有使用。 WORDIMAGE_FILE_EXECUTABLE_IMAGE:1//置1表示该文件为exe文件。 WORDIMAGE_FILE_LINE_NUMS_STRIPPED:1//在可执行文件中没有使用。 WORDIMAGE_FILE_LOCAL_SYMS_STRIPPED:1//在可执行文件中没有使用。 WORDIMAGE_FILE_AGGRESIVE_WS_TRIM:1//在可执行文件中没有使用。 WORDIMAGE_FILE_LARGE_ADDRESS_AWARE:1//该应用程序可以处理大于2G的地址。 WORDIMAGE_FILE_BYTES_REVERSED_LO:1//在可执行文件中没有使用。 WORDIMAGE_FILE_32BIT_MACHINE:1//置1表示希望是32位平台 WORDIMAGE_FILE_DEBUG_STRIPPED:1//在可执行文件中没有使用。 WORDIMAGE_FILE_REMOVABLE_RUN_FROM_SWAP:1//置1表示该程序不能在可移动介质中运行。 WORDIMAGE_FILE_NET_RUN_FROM_SWAP:1//置1表示该程序不能在网络中运行。 WORDIMAGE_FILE_SYSTEM:1//在可执行文件中没有使用。 WORDIMAGE_FILE_DLL:1//置1表示文件是一个动态链接库。 WORDIMAGE_FILE_UP_SYSTEM_ONLY:1//置1该文件应仅在单处理器计算机上运行。 WORDIMAGE_FILE_BYTES_REVERSED_HI:1//在可执行文件中没有使用。 } //IMAGE_OPTIONAL_HEADER64结构 typedefstruct_IMAGE_OPTIONAL_HEADER64{ WORDMagic;//判断是32还是64位可执行映像 BYTEMajorLinkerVersion;//链接器主版本号 BYTEMinorLinkerVersion;//链接器此版本号 DWORDSizeOfCode;//多个代码段大小 DWORDSizeOfInitializedData;//多个初始化数据段大小 DWORDSizeOfUninitializedData;//多个为初始化数据段大小 DWORDAddressOfEntryPoint;//代码入口点的RVA DWORDBaseOfCode;//代码段基址 ULONGLONGImageBase;//加载基址 DWORDSectionAlignment;//内存对齐粒度,默认为页面大小4k DWORDFileAlignment;//文件对齐粒度 WORDMajorOperatingSystemVersion;//操作系统主版本号 WORDMinorOperatingSystemVersion;//操作系统此次版本号 WORDMajorImageVersion;//可执行文件主版本号 WORDMinorImageVersion;//可执行文件次版本号 WORDMajorSubsystemVersion;//子系统主版本号 WORDMinorSubsystemVersion;//子系统此次版本号 DWORDWin32VersionValue;//保留,必须为0 DWORDSizeOfImage;//占用内存的大小,需对齐 DWORDSizeOfHeaders;//定长部分+SectionHeader数组的大小,需对齐 DWORDCheckSum;//校验和 WORDSubsystem;//所需子系统环境 WORDDllCharacteristics;//映像属性 ULONGLONGSizeOfStackReserve;//栈保留大小 ULONGLONGSizeOfStackCommit;//栈提交大小 ULONGLONGSizeOfHeapReserve;//堆保留大小 ULONGLONGSizeOfHeapCommit;//堆提交大小 DWORDLoaderFlags;//在可执行文件中没有使用。 DWORDNumberOfRvaAndSizes;//DataDirectory表表项的数目 IMAGE_DATA_DIRECTORYDataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//DataDirectory表 }IMAGE_OPTIONAL_HEADER64,*PIMAGE_OPTIONAL_HEADER64;
可选头虽说名字叫可选头,但是确是PE⽂件中最不可缺少的部分,其中DataDirectory数组尤为重要,⾥⾯是系统加映像时,初始化资源的重要数据的索引,数组⼀共有16项,⼀项保留,其余15项分别对应15种资源索引,由于篇幅原因, DataDirectory 会在后⾯的⽂章中单独详细解读。
以上就是PE⽂件中的定⻓结构部分,下⾯是不定⻓的Section Header数组和各个Section Data。
//IMAGE_SECTION_HEADER结构 typedefstruct_IMAGE_SECTION_HEADER{ BYTEName[IMAGE_SIZEOF_SHORT_NAME];//char类型的八字节数组,保存section的名称 union{ DWORDPhysicalAddress; DWORDVirtualSize; }Misc;//section加载到内存的大小 DWORDVirtualAddress;//section起始地址的RVA DWORDSizeOfRawData;//Misc.VirtualSize内存对齐后的大小 DWORDPointerToRawData;//section起始地址的文件偏移 DWORDPointerToRelocations;//指向该部分的重定位条目的文件偏移。为0则没有重定位。 DWORDPointerToLinenumbers;//未使用 WORDNumberOfRelocations;//未使用 WORDNumberOfLinenumbers;//未使用 DWORDCharacteristics;//section属性 }IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;//SectionHeader数组就是由一项一项的IMAGE_SECTION_HEADER结构构成
section常⽤属性:
其他不常用标志见后面 MSDN 链接。
最后说明一下文件偏移和内存相对偏移的计算:FOA = RVA - VirtualAddress + PointerToRawData首先要判断的的RVA地址所落在的section,条件是VirtualAddress <= RVA < VirtualAddress + SizeOfRawData
附上我写的转换代码,⼀个壳的代码⽚段:
参数:RVA:内存相对偏移,pNT:指向NT头的指针 返回:转换后的文件偏移 */ UINT32RvaToOffset(UINT32RVA,PIMAGE_NT_HEADERSpNT){ UINT32NumberOfSections=pNT->FileHeader.NumberOfSections;//获取Section数量 UINT32offset=0; PIMAGE_SECTION_HEADERpSH=(PIMAGE_SECTION_HEADER)((UINT64)pNT+sizeof(IMAGE_NT_HEADERS));//获取Sectionheader数组 for(UINT32i=0;i{pSH->Characteristics|=IMAGE_SCN_MEM_WRITE;//为每个section添加写属性if(RVA>=pSH->VirtualAddress&&RVAVirtualAddress+pSH->SizeOfRawData){offset=RVA-pSH->VirtualAddress+pSH->PointerToRawData;}}returnoffset;}/*
常⻅section
这些都是VS编译器⽣成的,⾥⾯的数据带有特定作⽤.
.bss:存放未初始化数据
.data:存放初始化数据
.edata:存放导出表
.idata:存放导⼊表
.pdata:存放异常信息
.rdata:存放初始化的只读数据
.reloc:存放重定位信息
.rsrc:存放资源数据
.text:存放可执⾏代码
.xdata:存放异常信息
⾃定义section
#pragmadata_seg("syclover") charWhoAmI[]="syclover!"; #pragmadata_seg()//在vs中可以通过以下方式自定一个section,命名为syclover
注:SectionHeadr和SectionData并不是一一对应的关系,例如.bss段,占内存但是并不占文件空间,在文件中可以找到.bss段对应的SectionHeadr条目,但是并没有对应的SectionData存在,
参考
/liuyez123/article/details/51281905
/zh-cn/windows/win32/api/winnt/ns-winnt-image_nt_headers64
/zh-cn/windows/win32/api/winnt/ns-winnt-image_section_header
长
按
关
注
三叶草小组公众号
微信号 : 三叶草小组Syclover
新浪微博:@三叶草小组Syclover
在与你相遇的路上马不停蹄~