目录
0x1 概述
0x2 MS-DOS头
0x2.2 PE文件头
0x2.3 区段表
0x2.4 导出表
0x2.5 导入表
0x2.6 资源表
0x2.7 重定位表
0x3 PE文件头信息编辑
0x4 16进制查看功能
0x5 总结
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
前言
为可以更好的理解pe文件结构,尝试写的一款简易的PE-Loader。
目前编辑功能仅实现了个别dos和nt头中基本信息的修改,editor功能有待加强。
感谢15pb老师们的指导。
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
正文
[SIZE="3"][B]0x1 概述
PE,即Portable Executable的首字母简写,它是Win32平台可执行文件的标准格式。
编写一个简单的,类似PELoader的PE文件分析程序。程序大致实现功能及界面如下:
附件 84786
(注:这个强制功能,没什么亮点,比如加载PE文件了,其他选项才可以使用,判断不是pe则btn置灰,在使用过程中,修改了e_magic发现工具分析不了了,所以为一些修改过的PE方便使用(修改回来),所以加了这么个按钮:-D)
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2 MS-DOS头
简要描述:
PE文件头部首先第一部分是MS-DOS头,MS-DOS头部有两个部分构成,第一部分是MZ文件头,紧跟MZ文件头后面的是一个DOS可执行文件。其结构体定义如下:
由这个结构体,我们至少可以知道:
1) 通过判断e_magic的值,可知道文件是不是ms-dos下的可执行文件
2) 最后一个e_lfanew字段可以引导我们找到新的exe文件头.
3) 偏移为0x18的e_lfarlc字段指向的重定位表头部其实也是DOS Stub的起始偏移地址,由此向前推4个字节便是指向PE文件头e_lfanew的地址。
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.2 PE文件头
简要描述:PE文件头位于dos stub之后。是一个一PE/0/0为起始标记,由MS-DOS头中e_lfanew字段指向的结构。PE文件头是Windows NT内核下判断可执行文件的唯一有效结构,由3个字段组成:
一、 IMAGE_NT_HEADERS32
二、 IMAGE_FILE_HEADER
三、 IMAGE_OPTIONAL_HEADER
从这三个结构体中可以获得的信息许多,如NT_HEADERS32中字段Signature,这是PE文件头的标识,其值始终是0x50450000。Win32SDK使用#define IMAGE_NT_SIGNATURE定义了这个值:
#define IMAGE_NT_SIGNATURE 0x50450000 // PE00
再如IAMGE_FILE_HEADER结构体中的字段SizeOfOptionalHeader,这个字段标识了扩展头的大小。字段NumberOfSections标识了区段的数目,等等。
在IMAGE_OPTIOAN_HEADER32中,字段
AddressOfEntryPoint // [0x28] 程序执行入口
ImageBase // [0x34] 程序默认载入基地址
NumberOfRvaAndSizes // [0x74] 数据目录表的数量
IMAGE_DATA_DIRECTORY DataDirectory[0x10] // [0x78] 数据目录表的结构体数组
...
IMAGE_DATA_DIRECTORY的结构如下:
在Win32SDK中也使用了一组宏来表示不同成员的信息。
相关功能展示:
附件 84787
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.3 区段表
简要描述:
PE文件头的数据目录表后是区段表,区段表用来描述位于其后各个区段的各种属性。区段表是由数个首尾相连的IMAGE_SECTION_HEADER结构体数组构成,可以使用IMAGE_FIRST_SECTION32(NtHeader)宏找到第一个区段表所在的位置。示例:
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION32(NtHeader);
可以使用如下代码循环获取其他节:
IMAGE_SECTION_HEADER结构如下:
这个结构里包含了可以详细描述该区段属性的字段信息,如区段名、长度、属性等内容。
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.4 导出表
简要描述:
导出表是PE文件中非常重要的一个数据结构,是PE文件为其它应用程序提供API的一种函数实例导出方式。
Windows下存在导出表的可执行文件以指定自身的一些变量、函数及类,并将其导出,以便提供第三方程序使用。
IMAGE_EXPORT_DIRECTORY结构如下:
上述结构说明:
1) Characteristics : 保留,恒为0x00000000。
2) TimeDateStamp : 导出表创建的时间
3) MajorVersion : 导出表的主版本号
4) MinorVersion : 导出表的主版本号
5) Name : 指向此导出表所在的模块名称的Ascii码的RVA
6) Base : 导出表用于输出API函数索引值的基数,一般情况下此值为1
7) NumberOfFunctions : 函数个数
8) NumberOfNames : 名称表中名称数目
其后三个字段分别标识导出函数、导出函数名、导出序号的相对虚拟地址。
导出表的大致结构图如下:
附件 84776
获取导出表信息的相关代码:
导出表功能测试截图如下(以32位user32.dll为例):
附件 84777
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.5 导入表
简要描述:
导入表机制是PE文件从其他第三方程序中导入API,供本程序调用的机制。
比如导入表在反病毒的领域里,技术人员在某些情况下仅根据导入表就能猜测出此程序的大致行为。由此导入表的重要性可见一斑。
导入表(IMAGE_IMPORT_DESCRIPTOR)结构定义如下:
上述结构说明:
在一般情况下,对于导入表只需要关注OriginalFirstThunk与FirstThunk,两个字段分别指向了保存导出名称与导出地址的IMAGE_THUNK_DATA的结构数组,且这个数组是以一个空的IMAGE_THUNK_DATA结构结尾,其结构的详细内容如下:
上述结构说明如下:
下面了解一下IMAGE_IMPORT_BY_NAME结构:
上述结构说明如下:
Hint : 保存着本映像导入表需导入的函数序号
Name : 保存着本映像导入表需导入的函数名称
IMAGE_IMPORT_BY_NAME结构以一个结构数组的形式存在,并以一个空的IMAGE_IMPORT_BY_NAME结构结束。
导入表的大致结构图如下:
附件 84780
获取导入表相关信息的代码如下:
导入表功能测试截图如下(以32位user32.dll为例):
附件 84781
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.6 资源表
简要描述:
Windows下程序中的各种界面组成部分称为资源,比如菜单、图标、快捷键、版本信息以及其他未格式化的二进制资源。
数据目录表中的IMAGE_DIRECTORY_ENTRY_RESOURCE项指向此结构
资源结构:
资源在PE文件中以目录结构的形式存在,一般情况下分为3层,从根目录开始分别为资源类型、目录资源ID与资源代码页。
此3层目录结构都是由一个IMAGE_RESOURCE_DIRECTORY结构为头部,并在后面跟着一个IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组。
IMAGE_RESOURCE_DIRECTORY结构如下:
这个结构负责指出将紧随其后的IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组的成员个数.IMAGE_RESOURCE_DIRECTORY_ENTRY结构如下:
获取资源表相关信息的代码如下:
资源表相关功能截图:
附件 84782
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.7 重定位表
简要描述:
由于Windows系统中DLL文件并不是每次都能加载到预设的基址上,所以基址重定位主要应用于DLL文件中。
一般情况下,重定位表位于一个名为.reloc的区块内,PE对于重定位的定义非常简单,无需参考外部信息或模块中的其他节,只需简单将文件中所有需要重定位的地址放在一个数组里即可。如果此映像未能在预设的基址上载入,那么加载器就会简单的将数组中的重定位信息逐一修改。
重定位表相关结构:
资源表相关功能截图:
附件 84783
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x3 PE文件头信息编辑
简要描述:
可选择相关字段进行编辑。保存与当前文件,或者另存新文件。目前仅可对指定字段进行编辑。
开始设计的失败,扩展头好多信息使用静态文本框显示内容,不方便文件内容的修改。
所以这里仅仅提供了下拉列表框中的信息修改。修改测试成功的:-D
功能截图如下:
附件 84784
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x4 16进制查看功能
简要描述:
这个16进制的编辑功能是在网上找到的16edit库来加的功能。
功能截图如下:
附件 84785
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x5 总结
目前程序的功能如上所述。
简易的一款PE Editor。
编写的过程中,加深了对PE文件结构的认识,也找到些编程的中的种种问题。
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
全文后记:
感谢学习过程中老师们和同学们的帮助:-D
参考书籍:《黑客免杀攻防》
0x1 概述
0x2 MS-DOS头
0x2.2 PE文件头
0x2.3 区段表
0x2.4 导出表
0x2.5 导入表
0x2.6 资源表
0x2.7 重定位表
0x3 PE文件头信息编辑
0x4 16进制查看功能
0x5 总结
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
前言
为可以更好的理解pe文件结构,尝试写的一款简易的PE-Loader。
目前编辑功能仅实现了个别dos和nt头中基本信息的修改,editor功能有待加强。
感谢15pb老师们的指导。
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
正文
[SIZE="3"][B]0x1 概述
PE,即Portable Executable的首字母简写,它是Win32平台可执行文件的标准格式。
编写一个简单的,类似PELoader的PE文件分析程序。程序大致实现功能及界面如下:
附件 84786
(注:这个强制功能,没什么亮点,比如加载PE文件了,其他选项才可以使用,判断不是pe则btn置灰,在使用过程中,修改了e_magic发现工具分析不了了,所以为一些修改过的PE方便使用(修改回来),所以加了这么个按钮:-D)
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2 MS-DOS头
简要描述:
PE文件头部首先第一部分是MS-DOS头,MS-DOS头部有两个部分构成,第一部分是MZ文件头,紧跟MZ文件头后面的是一个DOS可执行文件。其结构体定义如下:
- Typedef struct _IMAGE_DOS_HEADER{
- WORD e_magic; // 魔术数字
- WORD e_cblp; // 文件最后页的字节数
- WORD e_cp; // 文件页数
- WORD e_crlc; // 重定义元素个数
- WORD e_cparhdr; // 头部尺寸,以段落为单位
- WORD e_minalloc; // 所需的最小附加段
- WORD e_maxalloc; // 所需的最大附加段
- WORD e_ss; // 初始的SS值(相对偏移量)
- WORD e_sp; // 初始的SP值
- WORD e_csum; // 校验和
- WORD e_ip; // 初始的IP值
- WORD e_cs; // 初始的CS值(相对偏移量)
- WORD e_lfarlc; // 重分配表文件地址
- WORD e_ovno; // 覆盖号
- WORD e_res[4]; // 保留字
- WORD e_oemid; // OEM标识符(相对e_oeminfo)
- WORD e_oeminfo; // OEM信息
- WORD e_res2[10]; // 保留字
- LONG e_lfanew; // 新exe头部的文件地址
- } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
由这个结构体,我们至少可以知道:
1) 通过判断e_magic的值,可知道文件是不是ms-dos下的可执行文件
2) 最后一个e_lfanew字段可以引导我们找到新的exe文件头.
3) 偏移为0x18的e_lfarlc字段指向的重定位表头部其实也是DOS Stub的起始偏移地址,由此向前推4个字节便是指向PE文件头e_lfanew的地址。
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.2 PE文件头
简要描述:PE文件头位于dos stub之后。是一个一PE/0/0为起始标记,由MS-DOS头中e_lfanew字段指向的结构。PE文件头是Windows NT内核下判断可执行文件的唯一有效结构,由3个字段组成:
一、 IMAGE_NT_HEADERS32
- typedef struct _IMAGE_NT_HEADERS {
- DWORD Signature; //[0x00] PE文件标识
- IMAGE_FILE_HEADER FileHeader; //[0x04]文件头
- IMAGE_OPTIONAL_HEADER32 OptionalHeader; //[0x18]扩展头
- } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
二、 IMAGE_FILE_HEADER
- typedef struct _IMAGE_FILE_HEADER {
- WORD Machine;
- WORD NumberOfSections;
- DWORD TimeDateStamp;
- DWORD PointerToSymbolTable;
- DWORD NumberOfSymbols;
- WORD SizeOfOptionalHeader;
- WORD Characteristics;
- } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
三、 IMAGE_OPTIONAL_HEADER
- typedef struct _IMAGE_OPTIONAL_HEADER {
- //
- // Standard fields.
- //
- WORD Magic;
- BYTE MajorLinkerVersion;
- BYTE MinorLinkerVersion;
- DWORD SizeOfCode;
- DWORD SizeOfInitializedData;
- DWORD SizeOfUninitializedData;
- DWORD AddressOfEntryPoint;
- DWORD BaseOfCode;
- DWORD BaseOfData;
- //
- // NT additional fields.
- //
- DWORD ImageBase;
- DWORD SectionAlignment;
- DWORD FileAlignment;
- WORD MajorOperatingSystemVersion;
- WORD MinorOperatingSystemVersion;
- WORD MajorImageVersion;
- WORD MinorImageVersion;
- WORD MajorSubsystemVersion;
- WORD MinorSubsystemVersion;
- DWORD Win32VersionValue;
- DWORD SizeOfImage;
- DWORD SizeOfHeaders;
- DWORD CheckSum;
- WORD Subsystem;
- WORD DllCharacteristics;
- DWORD SizeOfStackReserve;
- DWORD SizeOfStackCommit;
- DWORD SizeOfHeapReserve;
- DWORD SizeOfHeapCommit;
- DWORD LoaderFlags;
- DWORD NumberOfRvaAndSizes;
- IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
- } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
从这三个结构体中可以获得的信息许多,如NT_HEADERS32中字段Signature,这是PE文件头的标识,其值始终是0x50450000。Win32SDK使用#define IMAGE_NT_SIGNATURE定义了这个值:
#define IMAGE_NT_SIGNATURE 0x50450000 // PE00
再如IAMGE_FILE_HEADER结构体中的字段SizeOfOptionalHeader,这个字段标识了扩展头的大小。字段NumberOfSections标识了区段的数目,等等。
在IMAGE_OPTIOAN_HEADER32中,字段
AddressOfEntryPoint // [0x28] 程序执行入口
ImageBase // [0x34] 程序默认载入基地址
NumberOfRvaAndSizes // [0x74] 数据目录表的数量
IMAGE_DATA_DIRECTORY DataDirectory[0x10] // [0x78] 数据目录表的结构体数组
...
IMAGE_DATA_DIRECTORY的结构如下:
- typedef struct _IAMGE_DATA_DIRECTORY
- {
- DWORD VirtualAddress; // 数据块的起始地址
- DWORD Size; // 数据块的长度
- }IAMGE_DATA_DIRECTORY, PIAMGE_DATA_DIRECTORY;
在Win32SDK中也使用了一组宏来表示不同成员的信息。
相关功能展示:
附件 84787
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.3 区段表
简要描述:
PE文件头的数据目录表后是区段表,区段表用来描述位于其后各个区段的各种属性。区段表是由数个首尾相连的IMAGE_SECTION_HEADER结构体数组构成,可以使用IMAGE_FIRST_SECTION32(NtHeader)宏找到第一个区段表所在的位置。示例:
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION32(NtHeader);
可以使用如下代码循环获取其他节:
- PIMAGE_SECTION_HEADER pSection1 = IMAGE_FIRST_SECTION(pNT32);
- for( DWORD i=0; i<pImageHeader->NumberOfSections; i++ )
- {
- pSection.push_back(pSection1[i]);
- }
IMAGE_SECTION_HEADER结构如下:
- typedef struct _IMAGE_SECTION_HEADER {
- BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
- union {
- DWORD PhysicalAddress;
- DWORD VirtualSize;
- } Misc;
- DWORD VirtualAddress;
- DWORD SizeOfRawData;
- DWORD PointerToRawData;
- DWORD PointerToRelocations;
- DWORD PointerToLinenumbers;
- WORD NumberOfRelocations;
- WORD NumberOfLinenumbers;
- DWORD Characteristics;
- } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
这个结构里包含了可以详细描述该区段属性的字段信息,如区段名、长度、属性等内容。
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.4 导出表
简要描述:
导出表是PE文件中非常重要的一个数据结构,是PE文件为其它应用程序提供API的一种函数实例导出方式。
Windows下存在导出表的可执行文件以指定自身的一些变量、函数及类,并将其导出,以便提供第三方程序使用。
IMAGE_EXPORT_DIRECTORY结构如下:
- typedef struct _IMAGE_EXPORT_DIRECTORY {
- DWORD Characteristics;
- DWORD TimeDateStamp;
- WORD MajorVersion;
- WORD MinorVersion;
- DWORD Name;
- DWORD Base;
- DWORD NumberOfFunctions;
- DWORD NumberOfNames;
- DWORD AddressOfFunctions; // RVA from base of image
- DWORD AddressOfNames; // RVA from base of image
- DWORD AddressOfNameOrdinals; // RVA from base of image
- } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
上述结构说明:
1) Characteristics : 保留,恒为0x00000000。
2) TimeDateStamp : 导出表创建的时间
3) MajorVersion : 导出表的主版本号
4) MinorVersion : 导出表的主版本号
5) Name : 指向此导出表所在的模块名称的Ascii码的RVA
6) Base : 导出表用于输出API函数索引值的基数,一般情况下此值为1
7) NumberOfFunctions : 函数个数
8) NumberOfNames : 名称表中名称数目
其后三个字段分别标识导出函数、导出函数名、导出序号的相对虚拟地址。
导出表的大致结构图如下:
附件 84776
获取导出表信息的相关代码:
代码:
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpImage;
PIMAGE_NT_HEADERS32 pNT32 = (PIMAGE_NT_HEADERS32)((LONG)lpImage+pDos->e_lfanew);
PIMAGE_OPTIONAL_HEADER32 pOptional = &(pNT32->OptionalHeader);
PIMAGE_DATA_DIRECTORY pDirec = (PIMAGE_DATA_DIRECTORY)pOptional->DataDirectory;
// 导出表的FOA
PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)((LONG)lpImage+RVA2FOA(lpImage, pDirec->VirtualAddress));
DWORD dwNum = RVA2FOA(lpImage,pExp->AddressOfNameOrdinals);
PWORD pNum = (PWORD)(dwNum+(DWORD)lpImage);
DWORD pfn = RVA2FOA(lpImage,pExp->AddressOfFunctions);
PDWORD pGet = PDWORD(pfn+(DWORD)lpImage);
DWORD dwPNames = RVA2FOA(lpImage,pExp->AddressOfNames);
PDWORD pdwNames = PDWORD(dwPNames+(DWORD)lpImage);
DWORD dwNames = RVA2FOA(lpImage,(DWORD)(*pdwNames));
dwNames = (dwNames)+(DWORD)lpImage;
vecDllNum.clear();
vecNames.clear();
vecAdd.clear();
for( DWORD i=0; i<pExp->NumberOfFunctions; i++ )
{
if( !(*pGet) )
{
pGet++;
continue;
}
int count=0; BOOL bflag = FALSE;
for( ; count<pExp->NumberOfNames; count++ )
{
if( i==pNum[count] )
{
bflag = TRUE;
break;
}
}
CString csTempNum;
csTempNum.Format(_T("0x%p"),i+pExp->Base);
vecDllNum.push_back(csTempNum);
csTempNum.Format(_T("0x%p"),*pGet);
vecAdd.push_back(csTempNum);
CHAR Temp[100];
if( !bflag )
{
csTempNum = _T("--");
}
else
{
sprintf_s(Temp,"%s",dwNames);
csTempNum = Temp;
pdwNames++;
dwNames = RVA2FOA(lpImage,(DWORD)(*pdwNames));
dwNames = (dwNames)+(DWORD)lpImage;
}
vecNames.push_back(csTempNum);
pGet++;
}
附件 84777
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.5 导入表
简要描述:
导入表机制是PE文件从其他第三方程序中导入API,供本程序调用的机制。
比如导入表在反病毒的领域里,技术人员在某些情况下仅根据导入表就能猜测出此程序的大致行为。由此导入表的重要性可见一斑。
导入表(IMAGE_IMPORT_DESCRIPTOR)结构定义如下:
代码:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
- OriginalFirstThunk : 包含指向INT的RVA,INT是一个IMAGE_THUNK_DATA结构的数组,大多情况下,数组中的每个IMAGE_THUNK_DATA结构会再指向IMAGE_IMPORT_BY_NAME结构,结尾处以全0x00的IMAGE_THUNK_DATA结构结束.
- TimeDateStamp : 一个32位的时间标识,与下一个转发链字段合并工作,否则可以为空。
- ForwarderChain : 转发链,如果不转发则此值为0
- Name : 指向导入文件的名字
- FirstThunk : 指向导入地址表的RVA
在一般情况下,对于导入表只需要关注OriginalFirstThunk与FirstThunk,两个字段分别指向了保存导出名称与导出地址的IMAGE_THUNK_DATA的结构数组,且这个数组是以一个空的IMAGE_THUNK_DATA结构结尾,其结构的详细内容如下:
代码:
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal; // 被导入函数的序号
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
- ForwarderString : 负责与导入转发表forwarders协同工作的一个字段。当导入表的ForwarderChain不为0时,此值有效,并指向包含有转发函数与导出这个函数的映像文件名的字符串RVA。
- Function : 导入表导入函数的实际内存地址,此字段仅在此映像被加载,且此结构为IAT的前提下有效。
- Ordinal : 被导入函数的序号
- AddressOfData : 指向IMAGE_IMPORT_BY_NAME结构,当以上3个值都未生效时,此值有效。
下面了解一下IMAGE_IMPORT_BY_NAME结构:
- typedef struct _IMAGE_IMPORT_BY_NAME
- {
- WORD Hint; // 需导入的函数序号
- BYTE Name[1]; // 需导入的函数名称
- }IMAGE_IMPORT_BY_NAME, PIMAGE_IMPORT_BY_NAME;
上述结构说明如下:
Hint : 保存着本映像导入表需导入的函数序号
Name : 保存着本映像导入表需导入的函数名称
IMAGE_IMPORT_BY_NAME结构以一个结构数组的形式存在,并以一个空的IMAGE_IMPORT_BY_NAME结构结束。
导入表的大致结构图如下:
附件 84780
获取导入表相关信息的代码如下:
代码:
// 2. 循环遍历导入表
while ( pImport->Name )
{
IMPORT_DLL_INFO stcImportInfo;
// 2.1 获取导入项DLL名称
PCHAR pszDllName = (PCHAR)((DWORD)lpImage+RVA2FOA(lpImage,pImport->Name));
// 2.2 获取INT
PIMAGE_THUNK_DATA32 pINT = (PIMAGE_THUNK_DATA32)((DWORD)lpImage+RVA2FOA(lpImage,(DWORD)pImport->OriginalFirstThunk));
// 2.3 导入项头部信息
dwThunkValue = *pdwThunkValue;
stcImportInfo.ThunkOffset.push_back(dwTempThunkOffset);
stcImportInfo.ThunkRVA.push_back(dwTempThunkRVA);
stcImportInfo.ThunkValue.push_back(dwThunkValue);
lvItem.iItem = dwIndex++;
CString csDllName; csDllName = pszDllName;
lvItem.pszText = (LPWSTR)(LPCWSTR)csDllName;
ListView_InsertItem(hListWnd, &lvItem);
stcImportInfo.DllName = csDllName;
csDllName.Format(_T("0x%p"),pImport->OriginalFirstThunk);
ListView_SetItemText(hListWnd,lvItem.iItem,1,(LPWSTR)(LPCWSTR)csDllName);
csDllName.Format(_T("0x%p"),pImport->TimeDateStamp);
ListView_SetItemText(hListWnd,lvItem.iItem,2,(LPWSTR)(LPCWSTR)csDllName);
csDllName.Format(_T("0x%p"),pImport->ForwarderChain);
ListView_SetItemText(hListWnd,lvItem.iItem,3,(LPWSTR)(LPCWSTR)csDllName);
csDllName.Format(_T("0x%p"),pImport->Name);
ListView_SetItemText(hListWnd,lvItem.iItem,4,(LPWSTR)(LPCWSTR)csDllName);
csDllName.Format(_T("0x%p"),pImport->FirstThunk);
ListView_SetItemText(hListWnd,lvItem.iItem,5,(LPWSTR)(LPCWSTR)csDllName);
// 2.4 循环INT的内容
while ( pINT->u1.Ordinal )
{
// 2.4.1 判断最高位是否不为1,是的话则打印其AddressOfData的内容
if ( !IMAGE_SNAP_BY_ORDINAL32(pINT->u1.Ordinal) )
{
PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)lpImage+RVA2FOA(lpImage,pINT->u1.AddressOfData));
//printf( "%04X %s\r\n", pByName->Hint, pByName->Name );
CString csTemp;
csTemp.Format(_T("%04x"),pByName->Hint);
stcImportInfo.vecHint.push_back(csTemp);
csTemp = pByName->Name;
stcImportInfo.vecName.push_back(csTemp);
dwTempThunkOffset += 4;
dwTempThunkRVA += 4;
pdwThunkValue++;
dwThunkValue = *pdwThunkValue;
stcImportInfo.ThunkOffset.push_back(dwTempThunkOffset);
stcImportInfo.ThunkRVA.push_back(dwTempThunkRVA);
stcImportInfo.ThunkValue.push_back(dwThunkValue);
pINT++;
continue;
}
// 2.4.2 如果最高位为1,则直接打印Ordinal部分
CString csTemp;
csTemp.Format(_T("--"));
stcImportInfo.vecHint.push_back(csTemp);
csTemp.Format(_T("%d"),pINT->u1.Ordinal&0x7FFF);
stcImportInfo.vecName.push_back(csTemp);
dwTempThunkOffset += 4;
dwTempThunkRVA += 4;
pdwThunkValue++;
dwThunkValue = *pdwThunkValue;
stcImportInfo.ThunkOffset.push_back(dwTempThunkOffset);
stcImportInfo.ThunkRVA.push_back(dwTempThunkRVA);
stcImportInfo.ThunkValue.push_back(dwThunkValue);
pINT++;
continue;
// printf( "%04X %s\r\n", pINT->u1.Ordinal&0x0000FFFF, "(Null)" );
// pINT++;
}
// // 2.5 打印导入项的尾部信息,并将指针加1,使其指向下一项导入表结构
// printf( "====================================\r\n\r\n" );
vecDllInfo.push_back(stcImportInfo);
dwTempThunkOffset += 4;
dwTempThunkRVA += 4;
pdwThunkValue++;
dwThunkValue = *pdwThunkValue;
pImport++;
}
附件 84781
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.6 资源表
简要描述:
Windows下程序中的各种界面组成部分称为资源,比如菜单、图标、快捷键、版本信息以及其他未格式化的二进制资源。
数据目录表中的IMAGE_DIRECTORY_ENTRY_RESOURCE项指向此结构
资源结构:
资源在PE文件中以目录结构的形式存在,一般情况下分为3层,从根目录开始分别为资源类型、目录资源ID与资源代码页。
此3层目录结构都是由一个IMAGE_RESOURCE_DIRECTORY结构为头部,并在后面跟着一个IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组。
IMAGE_RESOURCE_DIRECTORY结构如下:
代码:
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries;
WORD NumberOfIdEntries;
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
代码:
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset:31;
DWORD NameIsString:1;
} DUMMYSTRUCTNAME;
DWORD Name;
WORD Id;
} DUMMYUNIONNAME;
union {
DWORD OffsetToData;
struct {
DWORD OffsetToDirectory:31;
DWORD DataIsDirectory:1;
} DUMMYSTRUCTNAME2;
} DUMMYUNIONNAME2;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
代码:
// 2. 循环遍历资源表
// 2.1 第一层目录遍历
DWORD dwCount1 = pResource1->NumberOfIdEntries+pResource1->NumberOfNamedEntries;
for ( DWORD i=0; i<dwCount1; i++ )
{
SRC_1ST stc1st;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pDirEntry1 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pResource1+sizeof(IMAGE_RESOURCE_DIRECTORY));
if ( pDirEntry1[i].NameIsString )
{
PIMAGE_RESOURCE_DIR_STRING_U pString = (PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pResource1+pDirEntry1[i].NameOffset);
stc1st.csType = pString->NameString;
//printf( "Type: %ls", pString->NameString );
//printf("\r\n");
}
else
{
if ( pDirEntry1[i].Name>0x10 )
stc1st.csType.Format(_T("%d"),pDirEntry1[i].Name);
//printf( "Type:%04X\r\n", pDirEntry1[i].Name );
else
stc1st.csType = szResourceType[pDirEntry1[i].Name];
//printf( "Type:%s\r\n", szResourceType[pDirEntry1[i].Name] );
}
if ( pDirEntry1[i].DataIsDirectory )
{
SRC_2ND stc2nd;
// 2.2 第二层目录的遍历
PIMAGE_RESOURCE_DIRECTORY pResource2 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pResource1+pDirEntry1[i].OffsetToDirectory);
DWORD dwCount2 = pResource2->NumberOfIdEntries+pResource2->NumberOfNamedEntries;
for ( DWORD j=0; j<dwCount2; j++ )
{
PIMAGE_RESOURCE_DIRECTORY_ENTRY pDirEntry2 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pResource2+sizeof(IMAGE_RESOURCE_DIRECTORY));
if ( pDirEntry2[j].NameIsString )
{
PIMAGE_RESOURCE_DIR_STRING_U pString = (PIMAGE_RESOURCE_DIR_STRING_U)((DWORD)pResource2+pDirEntry2[j].NameOffset);
stc2nd.csID = pString->NameString;
//printf( "\tID: %ls", pString->NameString );
//printf("\r\n");
}
else
{
stc2nd.csID.Format(_T("%d"),pDirEntry2[j].Name);
//printf( "\tID:%04X\r\n", pDirEntry2[j].Name );
}
if ( pDirEntry2[j].DataIsDirectory )
{
// 2.3 第三层目录的遍历
PIMAGE_RESOURCE_DIRECTORY pResource3 = (PIMAGE_RESOURCE_DIRECTORY)((DWORD)pResource1+pDirEntry2[j].OffsetToDirectory);
stc2nd.vec3rd.push_back(pResource3);
DWORD dwCount3 = pResource3->NumberOfIdEntries+pResource3->NumberOfNamedEntries;
for ( DWORD k=0; k< dwCount3; k++ )
{
PIMAGE_RESOURCE_DIRECTORY_ENTRY pDirEntry3 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)pResource3+sizeof(IMAGE_RESOURCE_DIRECTORY));
//printf( "\t\tLng:%04X\r\n", pDirEntry3[k].Name );
PIMAGE_RESOURCE_DATA_ENTRY pData = (PIMAGE_RESOURCE_DATA_ENTRY)((DWORD)pResource1+pDirEntry3[k].OffsetToData);
printf( "\t\tRVA:0x%p Size:%04d Page:0x%p\r\n", pData->OffsetToData, pData->Size, pData->CodePage );
}
}
else
{
// ....
}
stc1st.vec2nd.push_back(stc2nd);
}
}
附件 84782
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x2.7 重定位表
简要描述:
由于Windows系统中DLL文件并不是每次都能加载到预设的基址上,所以基址重定位主要应用于DLL文件中。
一般情况下,重定位表位于一个名为.reloc的区块内,PE对于重定位的定义非常简单,无需参考外部信息或模块中的其他节,只需简单将文件中所有需要重定位的地址放在一个数组里即可。如果此映像未能在预设的基址上载入,那么加载器就会简单的将数组中的重定位信息逐一修改。
重定位表相关结构:
代码:
// 2. 遍历并执行虚拟重定位操作,并打印结果
while ( pReloc->VirtualAddress )
{
dwIndex++;
stcReloc.dwIndex = dwIndex;
DWORD dwImageBase = pNT32->OptionalHeader.ImageBase;
DWORD dwRelocBlockBase = pReloc->VirtualAddress;
DWORD dwCount = (pReloc->SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))/sizeof(WORD);
PTYPE_OFFSET pTypeOffset = (PTYPE_OFFSET)((DWORD)pReloc+sizeof(IMAGE_BASE_RELOCATION));
stcReloc.dwRVA = dwRelocBlockBase;
stcReloc.dwCount = dwCount;
for ( DWORD i=0; i<pNT32->FileHeader.NumberOfSections; i++ )
{
if ( dwRelocBlockBase>=pSection[i].VirtualAddress && dwRelocBlockBase<pSection[i].VirtualAddress+pSection[i].Misc.VirtualSize )
{
stcReloc.csSection = pSection[i].Name;
break;
}
}
vecReloc.push_back(stcReloc);
stcRelocDetail.dwFlag = dwIndex;
for ( DWORD i=0; i<dwCount; i++ )
{
stcRelocDetail.stcTypeOffset.Type = pTypeOffset->Type;
stcRelocDetail.stcTypeOffset.Offset = ((pTypeOffset+i)->Offset + dwRelocBlockBase);
vecRelocDetail.push_back(stcRelocDetail);
}
// 指向下一个重定位表的起始地址
pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc+pReloc->SizeOfBlock);
}
附件 84783
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x3 PE文件头信息编辑
简要描述:
可选择相关字段进行编辑。保存与当前文件,或者另存新文件。目前仅可对指定字段进行编辑。
开始设计的失败,扩展头好多信息使用静态文本框显示内容,不方便文件内容的修改。
所以这里仅仅提供了下拉列表框中的信息修改。修改测试成功的:-D
功能截图如下:
附件 84784
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x4 16进制查看功能
简要描述:
这个16进制的编辑功能是在网上找到的16edit库来加的功能。
功能截图如下:
附件 84785
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
0x5 总结
目前程序的功能如上所述。
简易的一款PE Editor。
编写的过程中,加深了对PE文件结构的认识,也找到些编程的中的种种问题。
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
全文后记:
感谢学习过程中老师们和同学们的帮助:-D
参考书籍:《黑客免杀攻防》