文件格式
.png 、.mp4、.gif、.dll等等,这些文件都具有不同格式 不能随意修改这些文件,否则将无法打开 PE文件(可执行文件)
学习PE文件目标
掌握PE文件就掌握winodws运行秘密 掌握PE文件对逆向,病毒分析,调试,漏洞.....不可替代作
PE文件常见术语
字段:结构体中某个成员
头: 说明书本的目录或者前言
区段:书本中的章节
偏移:用于文件偏移
镜像:磁盘上的文件
映像:将这个镜像加载到内存
DOS头
其中有用的就两个字段e_magic ,e_lfanew
e_magic必须实时IMAGE_DOS_SINGATURE 0x5A4D e_lfanew指向NT头
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
NT头
结构
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;
第一个字段 signature是一个 PE00标志 这个标志始终都是0x00004550, 宏 IMAGE_NT_SIGNATURE表示。 同它可以知道这个文件是否是PE文件
bool ispe_file(char * pbuff) {
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pbuff;
if(pDos.e_magic != IMAGE_DOS_SIGNATRUE )
{
return false;
}
PMIAGE_NT_HEADER pNt = (PMIAGE_NT_HEADER)(pDos.e_lfanew + (DWORD)pbuff);
if(pNt.signature != IMAGE_NT_SIGNATURE)
{
return false;
}
return true;
}
第二个字段是一个文件头结构体
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //[0x04] (1)运行平台
WORD NumberOfSections; //[0x06] (2)区段的数量*
DWORD TimeDateStamp; //[0x08] (3)文件创建时间
DWORD PointerToSymbolTable; //[0x0C] (4)符号表指针
DWORD NumberOfSymbols; //[0x10] (5)符号的数量
WORD SizeOfOptionalHeader; //[0x14] (6)扩展头大小* 32大小E0,64位F0
WORD Characteristics; //[0x16] (7)文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
运行平台有很多宏,常见的两个i386(x86) 值0x14c, AMD64(x64) 值0x8666;
基本术语
加载基址:内存中起始位置
虚拟地址:在进程中的空间某个位置
相对虚拟地址: 相对于加载基址 公式 虚拟地址(VA) = 加载基址(imagebase) + 相对虚拟地址(RVA)
对齐 文件对齐:区段在文件中对齐,一般以0x200
内存对齐: 区段在内存中对齐,一般以0x1000(刚好一页4k)
扩展头
扩展头位于NT头的后一个字段 IMAGE_OPTIONAL_HEADER
typedef struct _IMAGE_OPTIONAL_HEADER { // 标准域
WORD Magic; //[0x18] (1) 标志位
BYTE MajorLinkerVersion; //[0x1A] (2) 连接器主版本号
BYTE MinorLinkerVersion; //[0x1B] (3) 连接器子版本号
DWORD SizeOfCode; //[0x1C] (4) 所有代码段 的总大小
DWORD SizeOfInitializedData; //[0x20] (5) 所有初始化段总大小
DWORD SizeOfUninitializedData; //[0x24] (6) 所有未初始化段总大小
DWORD AddressOfEntryPoint; //[0x28] (7) 程序执行入口RVA*
DWORD BaseOfCode; //[0x2C] (8) 代码段起始RVA
DWORD BaseOfData; //[0x30] (9) 数据段起始RVA // NT 附加域
DWORD ImageBase; //[0x34] (10) 程序默认载入基地址*
DWORD SectionAlignment; //[0x38] (11) 内存中的段对齐值
DWORD FileAlignment; //[0x3C] (12) 文件中的段对齐值
WORD MajorOperatingSystemVersion; //[0x40] (13) 系统主版本号
WORD MinorOperatingSystemVersion; //[0x42] (14) 系统子版本号
WORD MajorImageVersion; //[0x44] (15) 自定义的主版本号
WORD MinorImageVersion; //[0x46] (16) 自定义的子版本号
WORD MajorSubsystemVersion; //[0x48] (17) 所需子系统主版本号
WORD MinorSubsystemVersion; //[0x4A] (18) 所需子系统子版本号
DWORD Win32VersionValue;//[0x4C] (19) 保留,通常为0x00
DWORD SizeOfImage; //[0x50] (20) 内存中映像总尺寸*
DWORD SizeOfHeaders; //[0x54] (21) 各个文件头的总尺寸*
DWORD CheckSum; //[0x58] (22) 映像文件校验和
WORD Subsystem; //[0x5C] (23) 文件子系统
WORD DllCharacteristics; //[0x5E] (24) DLL标志位
DWORD SizeOfStackReserve; //[0x60] (25) 初始化栈大小
DWORD SizeOfStackCommit; //[0x64] (26) 初始化实际提交栈大小
DWORD SizeOfHeapReserve; //[0x68] (27) 初始化保留栈大小
DWORD SizeOfHeapCommit; //[0x6C] (28) 初始化实际保留栈大小
DWORD LoaderFlags; //[0x70] (29) 调试相关,默认0x00
DWORD NumberOfRvaAndSizes; //[0x74] (30) 数据目录表的数量*
IMAGE_DATA_DIRECTORY DataDirectory[0x10]; //[0x78] (31) 数据目录表*
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
扩展头后一个字段是一个数据目录表,默认都是16项 每一项都指向IMAGE_DATA_DIRECTORY 结构体
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 数据块的起始RVA地址*
DWORD Size; // 数据块的长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
区段头
扩展头后就是区段头,通过IMAGE_FIRST_SECTION32(NtHeader) 可以获取区段头
typedef struct _IMAGE_SECTION_HEADER
{
BYTE Name[0x8]; // (1) 区段名
union {
DWORD PhysicalAddress;
DWORD VirtualSize; // (2) *区段大小
} Misc;
DWORD VirtualAddress; // (3)区段的RVA地址*
DWORD SizeOfRawData; // (4) 文件中的区段对齐大小*
DWORD PointerToRawData; // (5) 区段在文件中的偏移*
DWORD PointerToRelocations; // (6) 重定位的偏移(OBJ)
DWORD PointerToLinenumbers; // (7) 行号表的偏移(调试)
WORD NumberOfRelocations; // (8) 重定位项数量(OBJ)
WORD NumberOfLinenumbers; // (9) 行号表项数量
DWORD Characteristics; // (10) 区段的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
使用LOADPE查看区段信息
使用010edit查看区段
代码解析 NT头->区段头
//显示区段信息
void Show_SectionInfo(char * pbuff)
{ //1.获取DOS
//2.获取NT
//3.获取区段头
//4.遍历区段显示信息
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pbuff;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pbuff);
// 区段头位置= pNT+4+sizeof(IMAGE_FILE_HEADERS)+sizeof(IMAGE_OPTIONAL_HEADERS)
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
//输出区段信息
char name[9];
strncpy_s(name, (char*)pSection[i].Name, 8);
printf("区段名字:%s
", name);
printf("区段RVA: %08x
", pSection[i].VirtualAddress);
}
}
RVA TO FOA
逆向的时候在OD找到了内存位置,那么通过这个位置找到文件对应的位置。
//RVA 转 FOA
DWORD RvaToOffset(char * pbuff, DWORD RVA)
{
//1. 遍历判断RVA落在哪个区段
//2. 计算FOA = RVA - VOffset + ROffset;
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pbuff;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pbuff);
// 区段头位置= pNT+4+sizeof(IMAGE_FILE_HEADERS)+sizeof(IMAGE_OPTIONAL_HEADERS)
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
//判断落在什么区段
if (RVA >= pSection[i].VirtualAddress &&
RVA < (pSection[i].VirtualAddress + pSection[i].SizeOfRawData))
{
//返回计算后的FOA
return RVA - pSection[i].VirtualAddress + pSection[i].PointerToRawData;
}
}
return -1;
}
010Edit模板 安装EXE模板
例子
main.cpp
#include "pch.h" #include <iostream> #include "PE.h" int main() { PE pe; pe.ReadPe(L"..\Debug\test-PE.exe"); DWORD RVA = pe.GetNtHeader()->OptionalHeader.AddressOfEntryPoint; DWORD FOA = pe.RvaToFoa(RVA); printf("RVA = %08X FOA = %08X ",RVA,FOA); pe.ShowNTInfo(); pe.ShowSectionInfo(); }
PE.h
#pragma once #include<windows.h> class PE { public: PE(); ~PE(); //读取PE文件 char * ReadPe(const TCHAR * szPath); //获取DOS头 PIMAGE_DOS_HEADER GetDosHeader(); //获取NT头 PIMAGE_NT_HEADERS GetNtHeader(); //显示NT头信息 void ShowNTInfo(); //获取区段头 PIMAGE_SECTION_HEADER GetSectionHeader(); //显示区段信息 void ShowSectionInfo(); //Rva To FoA DWORD RvaToFoa(DWORD dwRva); //PE文件指针 char *m_pBuff; };
PE.cpp
#include "pch.h" #include "PE.h" #include <cstdio> PE::PE() { } PE::~PE() { } char * PE::ReadPe(const TCHAR * szPath) { //1.打开一个文件 HANDLE hFile = CreateFile( szPath, //打开的文件名 GENERIC_READ, //读方式打开 FILE_SHARE_READ,//共享方式 NULL, //安全属性 OPEN_EXISTING, //创建标志 FILE_ATTRIBUTE_NORMAL, //属性 NULL); //2. 读取到内存中 //2.1获取文件大小 DWORD dwSize; dwSize = GetFileSize(hFile, 0); //2.2 开辟空间 m_pBuff= new char[dwSize]; //2.3 读取文件 DWORD dwReadSize; ReadFile(hFile, m_pBuff, dwSize, &dwReadSize, 0); //3.关闭文件 CloseHandle(hFile); return m_pBuff; return nullptr; } PIMAGE_DOS_HEADER PE::GetDosHeader() { return (PIMAGE_DOS_HEADER)(m_pBuff); } PIMAGE_NT_HEADERS PE::GetNtHeader() { //计算 nt头 = DOS.e_lfnew + m_pbuff //获取dos头 PIMAGE_DOS_HEADER pDos = GetDosHeader(); //获取NT头 return (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)m_pBuff); } void PE::ShowNTInfo() { //获取NT头 PIMAGE_NT_HEADERS pNt = GetNtHeader(); // pNt->Signature PE00,0x00004550 printf("NT标志:%08X ", pNt->Signature); //文件头 区段数量 运行平台 扩展头大小 //运行平台 0x014c i386 ; 0x8664 amd64 //#define IMAGE_FILE_MACHINE_I386 0x014c //#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8) printf("区段个数:%08X ", pNt->FileHeader.NumberOfSections); printf("运行平台:%08X ", pNt->FileHeader.Machine); printf("扩展头大小:%08X ", pNt->FileHeader.SizeOfOptionalHeader); //输出扩展头信息 printf("程序入口点:%08X ", pNt->OptionalHeader.AddressOfEntryPoint); //对齐方式 //文件中:默认对齐0x200 //内存中:默认对齐0x1000 printf("文件对齐:%08X ", pNt->OptionalHeader.FileAlignment); printf("内存对齐:%08X ", pNt->OptionalHeader.SectionAlignment); //默认exe加载基址 0x00400000 //默认dll加载基址 0x10000000 printf("加载基址:%08X ", pNt->OptionalHeader.ImageBase); //数据目录表 for (int i = 0; i < 16; i++) { printf(" 数据目录表 %d RVA:%08X Size:%08X ", i, pNt->OptionalHeader.DataDirectory[i].VirtualAddress, pNt->OptionalHeader.DataDirectory[i].Size ); } } PIMAGE_SECTION_HEADER PE::GetSectionHeader() { //区段头在 NT头后面 //1. 获取NT头 PIMAGE_NT_HEADERS pNt = GetNtHeader(); //2.计算NT头后的区段头 return IMAGE_FIRST_SECTION(pNt); } void PE::ShowSectionInfo() { //1.获取区段个数 文件头中保存区段个数 DWORD dwCount = GetNtHeader()->FileHeader.NumberOfSections; //2. 输出区段信息 PIMAGE_SECTION_HEADER pSection = GetSectionHeader(); for (DWORD i = 0; i < dwCount; i++) { char szName[9] = {0}; strncpy_s(szName, (char*)pSection[i].Name, 8); printf("区段名: %s ", szName); printf(" VOffset: %08X ", pSection[i].VirtualAddress); printf(" VSize: %08X ", pSection[i].Misc.VirtualSize); printf(" ROffset: %08X ", pSection[i].PointerToRawData); //文件中的大小 0x200对齐后的大小 printf(" RSize: %08X ", pSection[i].SizeOfRawData); } } DWORD PE::RvaToFoa(DWORD dwRva) { //1. 获取区段头表 PIMAGE_SECTION_HEADER pSection = GetSectionHeader(); //2. 遍历区段 DWORD dwCount = GetNtHeader()->FileHeader.NumberOfSections; for (DWORD i = 0; i < dwCount; i++) { //3. 判断这个RVA落在什么区段上 if (dwRva >= pSection[i].VirtualAddress && dwRva < (pSection[i].VirtualAddress + pSection[i].SizeOfRawData) ) { //3.1 FOA = RVA - 内存中区段位置 + 文件中区段位置 return dwRva - pSection[i].VirtualAddress + pSection[i].PointerToRawData; } } return 0; }