PE头IMAGE_NT_HEADERS后紧跟着节表(也可以理解为IMAGE_OPTIONAL_HEADER后为节表).它由许多个节表项(IMAGE_SECTION_HEADER)组成,每个节表项记录了PE中与某个特定的节有关的信息,如节的属性、节的大小、节在文件和内存中的起始位置等.节表中节的数量由IMAGE_FILE_HEADER.NumberOfSection来定义.
IMAGE_SECTION_HEADER
结构及成员含义如下:
#define IMAGE_SIZEOF_SECTION_HEADER 40
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //节表名称.大小8字节.一般情况下是以"\0"结尾的ASCII码字符串.内容可自定义.
union {
DWORD PhysicalAddress; //联合体.
DWORD VirtualSize; //节在内存中按照内存对齐前的大小(该值可以不准确).
} Misc;
DWORD VirtualAddress; //(RVA)节在内存中的偏移地址.加上ImageBase才是在内存中的地址.
DWORD SizeOfRawData; //节在文件中按照文件对齐后的大小.
DWORD PointerToRawData; //(FOA)节在文件中的偏移地址.
DWORD PointerToRelocations; //.OBJ文件中使用,指向重定位表的指针.
DWORD PointerToLinenumbers; //行号表的偏移,提供调试.
WORD NumberOfRelocations; //.OBJ文件中使用,重定位项数目.
WORD NumberOfLinenumbers; //行号表中行号的数量.
DWORD Characteristics; //节的属性.
} IMAGE_SECTION_HEADER, * PIMAGE_SECTION_HEADER;
通过WinHex定位第一个节表(文件中状态,第一个节表位于扩展PE头后):
Name: 节表名称.大小8字节.一般情况下是以"\0"结尾的ASCII码字符串.如果不是以"\0"结尾,系统会截取8个字节的长度进行处理.内容可自定义.
常见名及含义:
.text | 指令代码 |
.data | 初始化的数据 |
.idata | 导入表 |
.rsrc | 资源数据 |
.reloc | 基地址重定位表 |
.edata | 导出表 |
.tls | thread local storage,线程局部存储器 |
.rdata | 存放调试目录和说明字符串 |
Misc.VirtualSize: 节在内存中实际的大小(单位为字节).默认为编译器填充,可以修改不影响程序正常使用.该值可能大于SizeOfRawData.(如果在文件状态下增加shellcode需要注意此值,当它大于文件中节区大小时将会出错).
VirtualAddress: 该值为RVA(Relative Virtual Address)相对虚拟地址,必须是SectionAlignment的整数倍.节在内存中的偏移地址.加上ImageBase才是在内存中的地址.
SizeOfRawData: 节在文件中按照文件对齐后的大小(单位为字节).必须是FileAlignment的整数倍.
PointerToRawData: 该值为FOA(File Offset Address)文件偏移地址,必须是FileAlignment的整数倍.节在文件中的偏移地址.
该节磁盘中与内存中对应地址以及数据如下:
按照对应节表解析,文件中与内存中分布如下:
Characteristics: 节的属性,每一位都有不同含义,如下表所示:
数据位 | 常量符号 | 常量值 | 为1时含义 |
---|---|---|---|
5 | IMAGE_SCN_CNT_CODE | 0x00000020 | 节中包含代码 |
6 | IMAGE_SCN_CNT_INITIALIZED_DATA | 0x00000040 | 节中包含已初始化数据 |
7 | IMAGE_SCN_CNT_UNINITIALIZED_DATA | 0x00000080 | 节中包含未初始化数据 |
8 | IMAGE_SCN_LNK_OTHER | 0x00000100 | 保留供将来使用 |
25 | IMAGE_SCN_MEM_DISCARDABLE | 0x02000000 | 节中的数据在进程开始以后会被丢弃,如.reloc |
26 | IMAGE_SCN_MEM_NOT_CACHED | 0x04000000 | 节中的数据不会经过缓存 |
27 | IMAGE_SCN_MEM_NOT_PAGED | 0x08000000 | 节中的数据不会被交换到磁盘 |
28 | IMAGE_SCN_MEM_SHARED | 0x10000000 | 表示节中的数据将被不同的进程所共享 |
29 | IMAGE_SCN_MEM_EXECUTE | 0x20000000 | 映射到内存后的页面包含了可执行属性 |
30 | IMAGE_SCN_MEM_READ | 0x40000000 | 映射到内存后的页面包含了可读属性 |
31 | IMAGE_SCN_MEM_WRITE | 0x80000000 | 映射到内存后的页面包含了可写属性 |
将0x60000020转换为二进制后:0110 0000 0000 0000 0000 0000 0010 0000. 下标5,29,30,为1代表该节属性为IMAGE_SCN_CNT_CODE,IMAGE_SCN_MEM_EXECUTE,IMAGE_SCN_MEM_READ.
代码节的属性一般为60000020h,也就是可执行、可读和节中包含代码;
数据节的属性一般为C0000040h,也就是可读、可写和包含已初始化数据;
常量节的属性一般为40000040h,也就是可读和包含已初始化数据;
代码定位IMAGE_SECTION_HEADER如下:
读取文件代码:
PVOID FileToMem(IN PCHAR szFilePath, OUT LPDWORD dwFileSize)
{
//打开文件
FILE* pFile = fopen(szFilePath, "rb");
if (!pFile)
{
printf("FileToMem fopen Fail \r\n");
return NULL;
}
//获取文件长度
fseek(pFile, 0, SEEK_END); //SEEK_END文件结尾
DWORD Size = ftell(pFile);
fseek(pFile, 0, SEEK_SET); //SEEK_SET文件开头
//申请存储文件数据缓冲区
PCHAR pFileBuffer = (PCHAR)malloc(Size);
if (!pFileBuffer)
{
printf("FileToMem malloc Fail \r\n");
fclose(pFile);
return NULL;
}
//读取文件数据
fread(pFileBuffer, Size, 1, pFile);
//判断是否为可执行文件
if (*(PSHORT)pFileBuffer != IMAGE_DOS_SIGNATURE)
{
printf("Error: MZ \r\n");
fclose(pFile);
free(pFileBuffer);
return NULL;
}
if (*(PDWORD)(pFileBuffer + *(PDWORD)(pFileBuffer + 0x3C)) != IMAGE_NT_SIGNATURE)
{
printf("Error: PE \r\n");
fclose(pFile);
free(pFileBuffer);
return NULL;
}
if (dwFileSize)
{
*dwFileSize = Size;
}
fclose(pFile);
return pFileBuffer;
}
读取节表数据代码:
VOID PrintSecHeader()
{
//读取文件二进制数据
DWORD dwFileSize = 0;
PCHAR pFileBuffer = FileToMem(FILE_PATH_IN, &dwFileSize);
if (!pFileBuffer)
{
return;
}
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_NT_HEADERS pNth = (PIMAGE_NT_HEADERS)(pFileBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
PIMAGE_OPTIONAL_HEADER pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((PUCHAR)pOpo + pFil->SizeOfOptionalHeader);
CHAR szSecName[9] = { 0 };
for (size_t i = 0; i < pFil->NumberOfSections; i++)
{
memset(szSecName, 0, 9);
memcpy(szSecName, pSec[i].Name, IMAGE_SIZEOF_SHORT_NAME);
printf("IMAGE_SECTION_HEADER[%d] ->Name [%s] \r\n", i, szSecName);
printf("IMAGE_SECTION_HEADER[%d] ->VirtualSize [0x%08x] \r\n", i, pSec[i].Misc.VirtualSize);
printf("IMAGE_SECTION_HEADER[%d] ->VirtualAddress [0x%08x] \r\n", i, pSec[i].VirtualAddress);
printf("IMAGE_SECTION_HEADER[%d] ->SizeOfRawData [0x%08x] \r\n", i, pSec[i].SizeOfRawData);
printf("IMAGE_SECTION_HEADER[%d] ->PointerToRawData [0x%08x] \r\n", i, pSec[i].PointerToRawData);
printf("IMAGE_SECTION_HEADER[%d] ->PointerToRelocations [0x%08x] \r\n", i, pSec[i].PointerToRelocations);
printf("IMAGE_SECTION_HEADER[%d] ->PointerToLinenumbers [0x%08x] \r\n", i, pSec[i].PointerToLinenumbers);
printf("IMAGE_SECTION_HEADER[%d] ->NumberOfRelocations [0x%04x] \r\n", i, pSec[i].NumberOfRelocations);
printf("IMAGE_SECTION_HEADER[%d] ->NumberOfLinenumbers [0x%04x] \r\n", i, pSec[i].NumberOfLinenumbers);
printf("IMAGE_SECTION_HEADER[%d] ->Characteristics [0x%08x] \r\n", i, pSec[i].Characteristics);
}
}