此篇文章简单记录一下PE文件结构,作为笔记,方便平时查阅!
部分数据目录未总结,后面学习用到时会补充!
文章目录
PE文件结构图:


DOS头
Dos头一共64(0x40)
个字节
typedef struct _IMAGE_DOS_HEADER
{
WORD e_magic; // DOS头的标识 "MZ" 4Dh 5Ah(2个字节) #define IMAGE_DOS_SIGNATURE 0x5A4D
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]; // 保留字
// DosHeader + 0x3C 正好定位到e_lfanew
LONG e_lfanew; // NT头相对于文件起始地址的偏移, 4字节, 指示NT头的位置
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
DOS头下方紧跟着的并不是NT头,而是一段Stub,它随着链接器的不同而不同,大小不确定。
因此不能通过IMAGE_DOS_HEADER + 0x40
来定位NT头,而应该是IMAGE_DOS_HEADER + IMAGE_DOS_HEADER->e_lfanew
。
NT头
NT头在32位和64位下大小不同,主要在于可选头的不同。
32位:4 + 20(0x14) + 224(0xE0)= 248(0xF8)字节
64位:4 + 20(0x14) + 240(0xF0)= 264(0x108)字节
typedef struct _IMAGE_NT_HEADERS
{
DWORD Signature; //标志位:"PE\0\0" 50 45 00 00(4字节) #define IMAGE_NT_SIGNATURE 0x00004550
IMAGE_FILE_HEADER FileHeader; //PE文件头(PE标准头)
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //PE可选头(PE扩展头)
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_NT_HEADERS64
{
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
文件头
文件头一共20(0x14)
个字节
typedef struct _IMAGE_FILE_HEADER
{
WORD Machine; //PE文件运行的平台,值为IMAGE_FILE_MACHINE_I386(0x14c)表示是x86处理器,
//IMAGE_FILE_MACHINE_AMD64(0x8664)或IMAGE_FILE_MACHINE_IA64(0x200)表示是x64处理器。
WORD NumberOfSections; //文件中存在的节的个数,如果想在PE文件中增加或删除节,必须变更此处的值
DWORD TimeDateStamp; //创建此文件时的时间戳
DWORD PointerToSymbolTable; //COFF符号表的文件偏移,对于映像文件来说,此值为0
DWORD NumberOfSymbols; //符号表中元素的数目,对于映像文件来说,此值为0
WORD SizeOfOptionalHeader; //可选头的大小,32位下默认为00E0h,64位下默认为00F0h
WORD Characteristics; //文件属性标志,exe一般是010fh,dll一般是210eh
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
判断一个文件是不是PE文件
ImageDosHeader = (PIMAGE_DOS_HEADER)FileContent;
if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
return FALSE;
}
ImageNtHeaders = (PIMAGE_NT_HEADERS)(FileContent + ImageDosHeader->e_lfanew);
if (ImageNtHeaders->Signature != IMAGE_NT_SIGNATURE)
{
return FALSE;
}
return TRUE;
判断一个PE文件是不是dll
DWORD Flag = IMAGE_FILE_DLL;
if ((Flag & ImageNtHeaders->FileHeader.Characteristics) == Flag)
{
return TRUE;
}
判断一个PE文件是不是exe
DWORD Flag = IMAGE_FILE_EXECUTABLE_IMAGE;
if (!IsDll() &&
!IsSys() &&
(Flag & ImageNtHeaders->FileHeader.Characteristics) == Flag)
{
return TRUE;
}
判断一个PE文件是32位还是64位(方法1,2)
方法一:
if (ImageNtHeaders->FileHeader.SizeOfOptionalHeader == 0xF0)
{
Is64PeFile = TRUE;
}
else
{
Is64PeFile = FALSE;
}
方法二:
if (ImageNtHeaders->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
{
Is64PeFile = TRUE;
}
if (ImageNtHeaders->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 || ImageNtHeaders->FileHeader.Machine == IMAGE_FILE_MACHINE_IA64)
{
Is64PeFile = FALSE;
}
可选头
32位下: 96(0x60)+ 128(0x80)= 224字节(0xE0)
64位下:112(0x70)+ 128(0x80)= 240字节(0xF0)
64位和32位的区别:
- 64位没有成员
BaseOfData
; - 64位的
ImageBase
、SizeOfStackReserve
、SizeOfStackCommit
、SizeOfHeapReserve
、SizeOfHeapCommit
是ULONGLONG
类型,为了适应增大的进程虚拟内存。
typedef struct _IMAGE_OPTIONAL_HEADER
{
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b
WORD Magic; //文件的类型。0x10b 表示文件为PE32,0x107表示文件为ROM映像,0x20b 表示PE64
BYTE MajorLinkerVersion; //链接器的主版本号
BYTE MinorLinkerVersion; //链接器的次版本号
DWORD SizeOfCode; //所有代码节长度的总和,该大小是基于文件对齐的。
DWORD SizeOfInitializedData; //所有包含初始化数据的节的总长度
DWORD SizeOfUninitializedData; //所有包含未初始化数据的节的总长度。这些数据在文件中不占用空间,但在被加载到内存以后,PE加载程序应该为这些数据分配适当大小的虚拟地址空间
DWORD AddressOfEntryPoint; //程序执行入口的RVA,对于exe这个地址可以理解为WinMain的RVA
DWORD BaseOfCode; //代码节起始地址的RVA,表示映像被加载进内存时代码节的开头相对于映像基址的偏移地址。一般情况下,代码节紧跟在PE头部后面
DWORD BaseOfData; //数据节起始地址的RVA。一般情况下,数据节位于文件末尾,64位没有这个成员
DWORD ImageBase; //文件被系统装入内存后的默认基地址(建议程序装载地址)。链接器默认exe的装入地址是0x400000,dll的是0x10000000。如果一个进程用到了多个Dll,其装入地址可能会发生冲突,PE加载器会调整其中的地址,使所有的dll文件都能被正确装入。
DWORD SectionAlignment; //内存中节的对齐粒度,32位下为0x1000
DWORD FileAlignment; //文件中节的对齐粒度(对齐是为了提高文件从磁盘加载的效率),32位下为0x200,SectionAlignment必须大于或等于FileAlignment
WORD MajorOperatingSystemVersion; //操作系统的版本号
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion; //映象的版本号
WORD MinorImageVersion;
WORD MajorSubsystemVersion; //所需子系统版本号
WORD MinorSubsystemVersion;
DWORD Win32VersionValue; //保留,必须为0
DWORD SizeOfImage; //内存中整个PE文件的映射大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小
DWORD SizeOfHeaders; //所有头+节表按照文件对齐粒度对齐后的大小
DWORD CheckSum; //校验和
WORD Subsystem; //运行该PE文件所需的子系统,指示是一个控制台程序(IMAGE_SUBSYSTEM_WINDOWS_CUI)还是图形界面程序(IMAGE_SUBSYSTEM_WINDOWS_GUI)
WORD DllCharacteristics; //DLL文件属性
DWORD SizeOfStackReserve; //初始化时保留的栈大小,默认为0x100000(1MB)
DWORD SizeOfStackCommit; //初始化时实际提交的栈大小
DWORD SizeOfHeapReserve; //初始化时保留的堆大小,默认为0x100000(1MB)
DWORD SizeOfHeapCommit; //初始化时实际提交的堆大小,默认为1页(0x1000)
DWORD LoaderFlags; //保留,必须为0
DWORD NumberOfRvaAndSizes; //数据目录结构的数量,即下面这个数组的项数,一般为16个
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_OPTIONAL_HEADER64
{
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG 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;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
判断一个PE文件是32位还是64位(方法3)
if (ImageNtHeaders->OptionalHeader.magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
{
Is64PeFile = TRUE;
}
else
{
Is64PeFile = FALSE;
}
数据目录
IMAGE_OPTIONAL_HEADER->DataDirectory
是一个结构体数组,共有16个成员,由16个IMAGE_DATA_DIRECTORY排列而成。
typedef struct _IMAGE_DATA_DIRECTORY
{
DWORD VirtualAddress; //数据块的起始RVA
DWORD Size; //数据块的长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // 导出表(.edata),指向一个IMAGE_EXPORT_DIRECTORY结构
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // 导入表(.idata),指向一个IMAGE_IMPORT_DESCRIPTOR结构数组
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // 资源表(.rsrc),指向一个IMAGE_RESOURCE_DIRECTORY结构
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // 异常表(.pdata),指向一个IMAGE_RUNTIME_FUNCTION_ENTRY结构数组,CPU特定的并且基于表的异常处理,用于除x86之外的其它CPU上
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // 安全表,指向一个WIN_CERTIFICATE结构的列表,它定义在WinTrust.h中,不会被映射到内存中。因此VirtualAddress域是一个文件偏移,而不是一个RVA
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // 重定位表(.reloc),指向基址重定位信息
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // 调试信息表(.debug),指向一个IMAGE_DEBUG_DIRECTORY结构数组,其中每个结构描述了映像的一些调试信息
// #define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // 版权信息表,指向一个IMAGE_ARCHITECTURE_HEADER结构数组
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // 全局指针表
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS 线程局部存储(.tls),指向线程局部存储初始化节
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // 配置加载表,指向一个IMAGE_LOAD_CONFIG_DIRECTORY结构
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // 绑定导入表,指向一个IMAGE_BOUND_IMPORT_DESCRIPTOR结构数组,每一个结构对应一个DLL。数组元素中的时间戳允许加载器快速判断绑定是否是新的,如果不是则加载器忽略绑定信息并且按正常方式解决导入API
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // 导入函数地址表,指向第一个导入地址表(IAT)的起始位置,对应于每个被导入DLL的IAT都连续地排列在内存中,Size域指出了所有IAT的总大小。
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // 延迟加载导入表
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
导出表
导出表描述的是该PE文件向其他程序提供的可供调用函数的情况。
只有一个IMAGE_EXPORT_DESCRIPTOR
。
typedef struct _IMAGE_EXPORT_DIRECTORY
{
DWORD Characteristics;
DWORD TimeDateStamp; //导出表创建的时间
WORD MajorVersion; //导出表的版本号
WORD MinorVersion;
DWORD Name; //指向该导出表的文件名称字符串
DWORD Base; //导出函数地址表中第一个地址所对应函数的编号。
DWORD NumberOfFunctions; //所有导出函数的个数
DWORD NumberOfNames; //通过名称导出的函数的个数
DWORD AddressOfFunctions; //指向导出函数地址表,存放所有导出函数所在地址的RVA值,大小为NumberOfFunctions
DWORD AddressOfNames; //指向函数名称地址表,存放导出函数名称字符串所在地址的RVA值,大小为NumberOfNames
DWORD AddressOfNameOrdinals; //指向函数名称索引表的RVA,存放名称导出函数的导出索引,它的值是对应函数在AddressOfFunctions中的数组下标,大小为NumberOfNames
}IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
三个数组
根据函数名称获得函数地址
FunctionAddress = DllBase + AddressOfFunctions[AddressOfNameOrdinals[i]]; //i为遍历AddressOfNames数组得到的数组下标
导入表
导入表描述的是该PE文件中的程序调用了其他动态链接库函数的情况。
是一个IMAGE_IMPORT_DESCRIPTOR
数组。
操作系统在加载PE文件时,会将其导入表里的所有DLL一起加载,并根据DLL导出表中对导入函数的描述修正IAT的值。
typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; //桥1,指向导入名称表(INT),INT是一个IMAGE_THUNK_DATA结构的数组,每个IMAGE_THUNK_DATA结构指向IMAGE_IMPORT_BY_NAME结构,最后一个IMAGE_THUNK_DATA的内容为0。
// OriginalFirstThunk:双字最高位为0表示导入符号是一个数值,该数值是一个RVA;双字最高位为1,表示导入符号是一个名称
};
DWORD TimeDateStamp; //时间戳,一般为0;如果该导入表项被绑定,那么绑定后的这个时间戳就是被设置为对应DLL文件的时间戳。操作系统在加载时,可以通过这个时间戳来判断绑定的信息是否过时。
DWORD ForwarderChain; //链表的前一个结构
DWORD Name; //指向该结构对应的DLL文件的名称,以“\0”结尾的Anisi字符串
DWORD FirstThunk; //桥2,指向导入函数地址表(IAT)的RVA,IAT是一个IMAGE_THUNK_DATA结构的数组,它定义了针对Name这个动态链接库导入的所有导入函数。
}IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
双桥结构
桥1和桥2本来是通向一个目的地,导入函数的“索引-名称”(Hint/Name)描述部分。
typedef struct _IMAGE_IMPORT_BY_NAME
{
USHORT Hint;
UCHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
当PE文件被加载到内存后,IAT的内容会被修改成函数的VA,这个修改会导致桥2发生断裂。
在内存中,
桥1可以让你找到调用函数的名称和索引;
桥2可以让你找到调用函数在内存空间的地址。
判断一个PE文件是不是sys
看它的导入表中有没有ntoskrnl.exe
、hal.dll
、ndis.sys
、bootvid.dll
、kdcom.dll
。
while (ImageImportDescriptor->Characteristics != 0)
{
PCHAR Name = (char*)((ULONG_PTR)FileImageBase + RvaToFoa(ImageNtHeaders, ImageImportDescriptor->Name));
PCHAR ModuleName[5] = { "ntoskrnl.exe", "hal.dll", "ndis.sys", "bootvid.dll", "kdcom.dll" };
for (UINT32 i = 0; i < 5; i++)
{
if (strcmp(Name, ModuleName[i]) == 0)
{
return TRUE;
}
}
ImageImportDescriptor++;
}
资源表
程序中常用的有六类资源:位图(BITMAP)、光标(CURSOR)、图标(ICON)、菜单(MENU)、对话框(DIALOG)、自定义资源。
资源表描述了资源数据在PE中的分布情况。PE文件的资源组织方式类似于文件管理。从根目录开始,下有一级子目录(按资源类型分类)、二级子目录(按资源ID分类)、三级子目录(按资源的代码页分类),三级子目录下的才是真正的资源数据项。
这么说可能比较抽象,直接查看一个实例的资源表:
根目录资源目录
根据资源类型分成了10个资源目录项;资源目录项1,它又根据资源ID分成了17个资源目录项。
这个新的资源目录项1按照资源代码页分类,只得到一个资源目录项1,它下面的才是真正的资源数据项。
所以,PE文件的资源表共涉及到3个数据结构:资源目录头、资源目录项、资源数据项。
// 资源目录头
typedef struct _IMAGE_RESOURCE_DIRECTORY_
{
DWORD Characteristics; //资源的属性,保留为将来使用,必须为0
DWORD TimeDataStamp; //资源的创建时间
WORD MajorVersion; //资源的主版本号
WORD MinorVersion; //资源的次版本号
WORD NumberOfNamedEntries; //以名称命名的目录项个数
WORD NumberOfIdEntries; //以ID命名的目录项个数
}IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
// 资源目录项
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY_
{
DWORD Name; //目录项的名称或ID,最高位(31位)为1时表示低地址部分为一个指向Unicode字符串的指针;为0时表示该字段是一个编号。
DWORD OffsetToData; //一个指针,最高位(31位)为1时表示低位数据指向下一级目录块的起始地址;为0时表示指针指向的是描述资源数据块的指针,通常出现再第三级目录中
}IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
// 资源数据项
typedef struct _IMAGE_RESOURCE_DATA_ENTRY_
{
DWORD OffsetToData; //指向资源数据块的指针,是一个RVA值,再文件中访问时需要注意转换成文件偏移
DWORD Size; //资源数据的大小
DWORD CodePage; //代码页,未用,大多数情况下为0
DWORD Reserved; //保留字段,总是为0
}IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
安全表
如果一个PE文件有数字签名,则其安全表不为NULL。
typedef struct _WIN_CERTIFICATE
{
DWORD dwLength;
WORD wRevision; //证书版本,WIN_CERT_REVISION_xxx
WORD wCertificateType; //证书类型,WIN_CERT_TYPE_xxx
BYTE bCertificate[ANYSIZE_ARRAY]; //包含一个或多个证书,一般来说这个证书的内容一直到安全表的末尾
} WIN_CERTIFICATE, *PWIN_CERTIFICATE;
重定位表
PE文件被装入内存时,其基地址是由OptionalHeader->ImageBase决定的。但是如果装入时该位置已被占用,则操作系统会重新选择一个基地址,此时就需要对所有的重定位信息进行修正,修正的依据就是重定位表。
它与导入表类似,是一个结构体数组。
typedef struct _IMAGE_BASE_RELOCATION_
{
DWORD VirtualAddress; //重定位内存页的起始RVA
DWORD SizeOfBlock; //重定位块的大小
}IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;
调试信息表
typedef struct _IMAGE_DEBUG_DIRECTORY
{
DWORD Characteristics;
DWORD TimeDataStamp; //Debug信息的时间
WORD MajorVersion; //Debug的主版本
WORD MinorVersion; //Debug的次版本
DWORD Type; //Debug信息的类型
DWORD SizeOfData; //Debug数据的大小
DWORD AddressOfRawData; //当被映射到内存时Debug数据的大小
DWORD PointerToRawData; //Debug数据的文件偏移
}IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
TLS
typedef struct _IMAGE_TLS_DIRECTORY32
{
ULONG StartAddressOfRawData; // TLS模板的起始地址
ULONG EndAddressOfRawData; // TLS模板的结束地址,最后一个字节的地址,不包括用于填充的0
ULONG AddressOfIndex; // TLS索引的位置,索引的具体值由加载器确定。这个位置在.data中。
ULONG AddressOfCallBacks; // 指向TLS回调函数的指针数组,数组以NULL结尾。若没有回调函数,则指向的位置是4个字节的0
ULONG SizeOfZeroFill; // 用0填充的字节数
ULONG Characteristics; // 保留
} IMAGE_TLS_DIRECTORY32, *PIMAGE_TLS_DIRECTORY32;
typedef struct _IMAGE_TLS_DIRECTORY64
{
ULONGLONG StartAddressOfRawData;
ULONGLONG EndAddressOfRawData;
ULONGLONG AddressOfIndex;
ULONGLONG AddressOfCallBacks;
ULONG SizeOfZeroFill;
ULONG Characteristics;
} IMAGE_TLS_DIRECTORY64, *PIMAGE_TLS_DIRECTORY64;
节表
节表是PE文件中所有节的目录。
节表的大小 = 节的数量(IMAGE_FILE_HEADER->NumberOfSections
) * 40字节
typedef struct _IMAGE_SECTION_HEADER
{
BYTE Name[8]; // 节名称,8字节,一般情况是一个以“\0”结尾的ASCII码字符串,#define IMAGE_SIZEOF_SHORT_NAME 8
union
{
DWORD PhysicalAddress; // 物理地址
DWORD VirtualSize; // 实际的节区大小,就是节的数据在没有对齐前的真实尺寸
} Misc;
DWORD VirtualAddress; // 节区起始数据的RVA,一般为0x1000(我自己认为的,并不是书上的)
DWORD SizeOfRawData; // 节在文件中对齐后的大小
DWORD PointerToRawData; // 节区起始数据在文件中的偏移(即FOA)
DWORD PointerToRelocations; // 指向重定位表的指针(在“.obj”文件中使用)
DWORD PointerToLinenumbers; // 行号表的偏移(供调试使用)
WORD NumberOfRelocations; // 重定位表的个数(在“.obj”文件中使用)
WORD NumberOfLinenumbers; // 行号表中行号的数目
DWORD Characteristics; // 节区的属性,如可读、可写、可执行等
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
当PE文件被加载到内存中时,其占用的地址空间要比在磁盘中大一点,因为各个节在内存中是按页对齐的。
通过RVA获得FOA
ULONG
RvaToFoa(
IN PIMAGE_NT_HEADERS NtHeaders,
IN ULONG Rva
)
{
PIMAGE_SECTION_HEADER SecHeader = IMAGE_FIRST_SECTION(NtHeaders);
for (UINT32 i = 0; i < NtHeaders->FileHeader.NumberOfSections; i++, SecHeader++)
{
if (Offset >= SecHeader->VirtualAddress &&
Offset <= SecHeader->VirtualAddress + SecHeader->Misc.VirtualSize)
{
return SecHeader->PointerToRawData + (Rva - SecHeader->VirtualAddress);
}
}
return 0xFFFFFFFF;
}
常见的节
- .text:包含CPU执行指令,是唯一可以执行的节。
- .rdata:包含只读数据,包括导入与导出函数信息。有的文件还会包含.idata和.edata节,来存储导入导出信息。
- .data:包含全局数据。
- .rsrc:包含由可执行文件所使用的资源,这些内容并不是可执行的,如图标、图片、菜单项和字符串等。