本帖最后由 JackLSQ 于 2024-1-14 23:03 编辑
最近在学习Windows pe文件结构,一点笔记,简单的介绍一下。
在pe结构中最主要的是有三个 Dos头,Nt头,节头。这三个头记录了所有的信息。
Dos头
关于dos头中没有太多需要讲的东西,这个头部是为了兼容老系统留下来的。在这个头部只存在两个有意义的字段。e_magic 它的值是MZ, 二进制值是5A4D。还有一个字段是e_lfanew 这个字段是指向nt头开始的位置。
获取dos头数据的代码
PIMAGE_DOS_HEADER pDosHead = NULL;
pDosHead = (PIMAGE_DOS_HEADER)imageBase;
printf("DOS头: 0x%.8x\n", pDosHead->e_magic);
printf("文件地址: 0x%.8x\n", pDosHead->e_lfarlc);
printf("PE结构偏移: 0x%.8x\n", pDosHead->e_lfanew);
nt头
在nt头中又分为Signature字段,文件头,可选头。
Signature 默认是0x00004550 程序位数不同这个数值其实是不一样的,x86的下是0x10B x64下是 0x20B 。
可以用这里位置判断exe程序是32位的还是64位的。
文件头
在文件头中有以下几个字段
字段名 |
字段值 |
字段代表的意义 |
Machine |
由编译决定也可指定 |
运行平台 |
NumberOfSections |
编译器填充也可自己填充 |
文件的节数目 |
TimeDateStamp |
编译器填充也可自己填充 |
文件创建日期和时间 |
PointerToSymbolTable |
编译器填充也可自己填充 |
指向符号表(用于调试) |
NumberOfSymbols |
编译器填充也可自己填充 |
符号表中的符号数量 |
SizeOfOptionalHeader |
编译器填充也可自己填充t |
可选头的长度 |
Characteristics |
编译器填充也可自己填充 |
文件的属性 exe=0x010h dll=0x210h |
在该字段中比较重要的字段主要有 NumberOfSections 、Characteristics。前者是用来遍历节表数的依据,后者可以用来判断当前文件是一个exe程序还是一个dll库。
获取文件头数据的代码
PIMAGE_NT_HEADERS pNtHead = NULL;
PIMAGE_FILE_HEADER pFileHead = NULL;
pNtHead = GetNtHead(imageBase);
pFileHead = &pNtHead->FileHeader;
printf("运行平台:\t\t0x%.8x\n", pFileHead->Machine);
printf("节区数目:\t\t0x%.8x\n", pFileHead->NumberOfSections);
printf("时间标记:\t\t%.8d\n", pFileHead->TimeDateStamp);
printf("可选头大小:\t\t0x%.8x\n", pFileHead->SizeOfOptionalHeader);
printf("文件特性:\t\t0x%.8x\n", pFileHead->Characteristics);
可选头
在可选头中的数据成员
字段名 |
字段值 |
字段意义 |
Magic |
0x10B 或0x20B |
MajorLinkerVersion |
连接器版本 |
MinorLinkerVersion |
SizeOfCode |
所有包含代码节的总大小 |
SizeOfInitializedData |
所有已初始化数据的节总大小 |
SizeOfUninitializedData |
所有未初始化数据的节总大小 |
AddressOfEntryPoint |
程序执行入口RVA |
BaseOfCode |
代码节的起始RVA |
BaseOfData |
数据节的起始RVA |
ImageBase |
程序镜像基地址 |
SectionAlignment |
内存中节的对其粒度 |
FileAlignment |
文件中节的对其粒度 |
MajorOperatingSystemVersion |
操作系统主版本号 |
MinorOperatingSystemVersion |
操作系统副版本号 |
MajorImageVersion |
可运行于操作系统的最小版本号 |
MinorImageVersion |
MajorSubsystemVersion |
可运行于操作系统的最小子版本号 |
MinorSubsystemVersion |
Win32VersionValue |
SizeOfImage |
内存中整个PE映像尺寸 |
SizeOfHeaders |
所有头加节表的大小 |
CheckSum |
Subsystem |
DllCharacteristics |
SizeOfStackReserve |
初始化时堆栈大小 |
SizeOfStackCommit |
SizeOfHeapReserve |
SizeOfHeapCommit |
LoaderFlags |
NumberOfRvaAndSizes |
数据目录的结构数量 |
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] |
|
|
在这个部分中重要的部分已经用中标识说明。
还有一个更重要的部分是DataDirectory 这个后面再说。
获取可选头数据的代码
PIMAGE_NT_HEADERS pNtHead = NULL;
pNtHead = GetNtHead(ImageBase);
printf("入口点:\t\t\t0x%.8x\n", pNtHead->OptionalHeader.AddressOfEntryPoint);
printf("镜像基址:\t\t0x%.8x\n", pNtHead->OptionalHeader.ImageBase);
printf("镜像大小:\t\t0x%.8x\n", pNtHead->OptionalHeader.SizeOfImage);
printf("代码基址:\t\t0x%.8x\n", pNtHead->OptionalHeader.BaseOfCode);
printf("区块对齐:\t\t0x%.8x\n", pNtHead->OptionalHeader.SectionAlignment);
printf("文件块对齐:\t\t0x%.8x\n", pNtHead->OptionalHeader.FileAlignment);
printf("子系统:\t\t\t0x%.8x\n", pNtHead->OptionalHeader.Subsystem);
printf("区段数目:\t\t0x%.8x\n", pNtHead->FileHeader.NumberOfSections);
printf("时间日期标志:\t\t0x%.8x\n", pNtHead->FileHeader.TimeDateStamp);
printf("首部大小:\t\t0x%.8x\n", pNtHead->OptionalHeader.SizeOfHeaders);
printf("特征值:\t\t\t0x%.8x\n", pNtHead->FileHeader.Characteristics);
printf("校验和:\t\t\t0x%.8x\n", pNtHead->OptionalHeader.CheckSum);
printf("可选头部大小:\t\t0x%.8x\n", pNtHead->FileHeader.SizeOfOptionalHeader);
printf("RVA 数及大小:\t\t0x%.8x\n", pNtHead->OptionalHeader.NumberOfRvaAndSizes);
printf("是否启用随机地址:\t0x%.8x\n", pNtHead->OptionalHeader.DllCharacteristics);
节头
在节表头中的数据字段
字段名 |
字段值 |
字段意义 |
Name[IMAGE_SIZEOF_SHORT_NAME] |
节区名称 |
Misc |
节区尺寸 |
VirtualAddress |
节区RVA |
SizeOfRawData |
在文件中对齐后的尺寸 |
PointerToRawData |
在文件中的偏移 |
PointerToRelocations |
|
PointerToLinenumbers |
NumberOfRelocations |
NumberOfLinenumbers |
Characteristics |
节区属性字段 |
在读取该段的数据的代码
PIMAGE_NT_HEADERS pNtHead = NULL;
PIMAGE_FILE_HEADER pFileHead = NULL;
PIMAGE_SECTION_HEADER pSection = NULL;
DWORD NumberOfSectinsCount = 0;
pNtHead = GetNtHead(ImageBase);
pSection = IMAGE_FIRST_SECTION(pNtHead);
pFileHead = &pNtHead->FileHeader;
NumberOfSectinsCount = pFileHead->NumberOfSections; // 获得区段数量
DWORD* difA = NULL; // 虚拟地址开头
DWORD* difS = NULL; // 相对偏移(用于遍历)
difA = (DWORD*)malloc(NumberOfSectinsCount * sizeof(DWORD));
difS = (DWORD*)malloc(NumberOfSectinsCount * sizeof(DWORD));
printf("节区名称 相对偏移\t虚拟大小\t实际偏移\t实际大小\t节区属性\n");
for (int temp = 0; temp < NumberOfSectinsCount; temp++, pSection++)
{
printf("%s\t 0x%.8X \t 0x%.8X \t 0x%.8X \t 0x%.8X \t 0x%.8X\n",
pSection->Name, pSection->VirtualAddress, pSection->Misc.VirtualSize,
pSection->PointerToRawData, pSection->SizeOfRawData, pSection->Characteristics);
difA[temp] = pSection->VirtualAddress;
difS[temp] = pSection->VirtualAddress - pSection->PointerToRawData;
}
free(difA);
free(difS);
difA = NULL;
difS = NULL;
在pe中除了以上这些,还有一些重要的东西,比如导入表,导出表(存在dll文件中,exe中也有不过该位置数据为空),RVA到FOA(FOA到RVA),IAT表(IAThook,以及脱壳中IAT修复)。
后面这些下回再发
pe输出信息
最终完整代码
[C] 纯文本查看 复制代码
#include<stdio.h>
#include<Windows.h>
#include<ImageHlp.h>
#pragma comment(lib,"ImageHlp.lib")
HANDLE OpenPeByName(LPTSTR fileName) {
LPTSTR peFile = fileName;
HANDLE hFile, hMapFile, lpMapAddress = NULL;
DWORD dwFileSize = 0;
hFile = CreateFile(peFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] 文件打开失败\n");
exit(0);
}
dwFileSize = GetFileSize(hFile, NULL);
if (dwFileSize != 0) {
printf("[+] 文件已读入!\n");
}
hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, dwFileSize, NULL);
if (hMapFile == NULL) {
printf("[-]创建文件映射对象失败!!\n");
exit(0);
}
lpMapAddress = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwFileSize);
if (lpMapAddress != NULL) {
return lpMapAddress;
}
}
void displayDosHeadInfo(HANDLE imageBase)
{
PIMAGE_DOS_HEADER pDosHead = NULL;
pDosHead = (PIMAGE_DOS_HEADER)imageBase;
printf("DOS头: 0x%.8x\n", pDosHead->e_magic);
printf("文件地址: 0x%.8x\n", pDosHead->e_lfarlc);
printf("PE结构偏移: 0x%.8x\n", pDosHead->e_lfanew);
}
PIMAGE_NT_HEADERS GetNtHead(HANDLE imageBase)
{
PIMAGE_DOS_HEADER pDosHead = NULL;
PIMAGE_NT_HEADERS pNtHead = NULL;
pDosHead = (PIMAGE_DOS_HEADER)imageBase;
//如果该程序是64位的 pNtHead = (PIMAGE_NT_HEADERS)((DWORD_PTR)pDosHead + pDosHead->e_lfanew);
pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);
return pNtHead;
}
void displayFileHeadInfo(HANDLE imageBase) {
PIMAGE_NT_HEADERS pNtHead = NULL;
PIMAGE_FILE_HEADER pFileHead = NULL;
pNtHead = GetNtHead(imageBase);
pFileHead = &pNtHead->FileHeader;
printf("运行平台:\t\t0x%.8x\n", pFileHead->Machine);
printf("节区数目:\t\t0x%.8x\n", pFileHead->NumberOfSections);
printf("时间标记:\t\t%.8d\n", pFileHead->TimeDateStamp);
printf("可选头大小:\t\t0x%.8x\n", pFileHead->SizeOfOptionalHeader);
printf("文件特性:\t\t0x%.8x\n", pFileHead->Characteristics);
}
void DisplayOptionalHeaderInfo(HANDLE ImageBase)
{
PIMAGE_NT_HEADERS pNtHead = NULL;
pNtHead = GetNtHead(ImageBase);
printf("入口点:\t\t\t0x%.8x\n", pNtHead->OptionalHeader.AddressOfEntryPoint);
printf("镜像基址:\t\t0x%.8x\n", pNtHead->OptionalHeader.ImageBase);
printf("镜像大小:\t\t0x%.8x\n", pNtHead->OptionalHeader.SizeOfImage);
printf("代码基址:\t\t0x%.8x\n", pNtHead->OptionalHeader.BaseOfCode);
printf("区块对齐:\t\t0x%.8x\n", pNtHead->OptionalHeader.SectionAlignment);
printf("文件块对齐:\t\t0x%.8x\n", pNtHead->OptionalHeader.FileAlignment);
printf("子系统:\t\t\t0x%.8x\n", pNtHead->OptionalHeader.Subsystem);
printf("区段数目:\t\t0x%.8x\n", pNtHead->FileHeader.NumberOfSections);
printf("时间日期标志:\t\t0x%.8x\n", pNtHead->FileHeader.TimeDateStamp);
printf("首部大小:\t\t0x%.8x\n", pNtHead->OptionalHeader.SizeOfHeaders);
printf("特征值:\t\t\t0x%.8x\n", pNtHead->FileHeader.Characteristics);
printf("校验和:\t\t\t0x%.8x\n", pNtHead->OptionalHeader.CheckSum);
printf("可选头部大小:\t\t0x%.8x\n", pNtHead->FileHeader.SizeOfOptionalHeader);
printf("RVA 数及大小:\t\t0x%.8x\n", pNtHead->OptionalHeader.NumberOfRvaAndSizes);
printf("是否启用随机地址:\t0x%.8x\n", pNtHead->OptionalHeader.DllCharacteristics);
}
void DisplaySectionHeaderInfo(HANDLE ImageBase)
{
PIMAGE_NT_HEADERS pNtHead = NULL;
PIMAGE_FILE_HEADER pFileHead = NULL;
PIMAGE_SECTION_HEADER pSection = NULL;
DWORD NumberOfSectinsCount = 0;
pNtHead = GetNtHead(ImageBase);
pSection = IMAGE_FIRST_SECTION(pNtHead);
pFileHead = &pNtHead->FileHeader;
NumberOfSectinsCount = pFileHead->NumberOfSections; // 获得区段数量
DWORD* difA = NULL; // 虚拟地址开头
DWORD* difS = NULL; // 相对偏移(用于遍历)
difA = (DWORD*)malloc(NumberOfSectinsCount * sizeof(DWORD));
difS = (DWORD*)malloc(NumberOfSectinsCount * sizeof(DWORD));
printf("节区名称 相对偏移\t虚拟大小\t实际偏移\t实际大小\t节区属性\n");
for (int temp = 0; temp < NumberOfSectinsCount; temp++, pSection++)
{
printf("%s\t 0x%.8X \t 0x%.8X \t 0x%.8X \t 0x%.8X \t 0x%.8X\n",
pSection->Name, pSection->VirtualAddress, pSection->Misc.VirtualSize,
pSection->PointerToRawData, pSection->SizeOfRawData, pSection->Characteristics);
difA[temp] = pSection->VirtualAddress;
difS[temp] = pSection->VirtualAddress - pSection->PointerToRawData;
}
free(difA);
free(difS);
difA = NULL;
difS = NULL;
}
void showPeInfoFunction() {
HANDLE lpMapAddress = NULL;
wchar_t exePath[] = L"./x32dbg.exe";
lpMapAddress = OpenPeByName(exePath);
printf("================================================\n");
printf("Dos head info:\n");
displayDosHeadInfo(lpMapAddress);
printf("================================================\n");
printf("Nt File head info:\n");
displayFileHeadInfo(lpMapAddress);
printf("================================================\n");
printf("Nt Optional head info:\n");
DisplayOptionalHeaderInfo(lpMapAddress);
printf("================================================================================================\n");
printf("Section head info:\n");
DisplaySectionHeaderInfo(lpMapAddress);
} |