pe内容解析程序学习
学习一个未知的东西需要一个强烈且明确的目的推动!学习逆向以来,一直使用的现有工具去解析pe结构,在一次偶然的机会,遇到了iat重建问题,手工重建多次未果后决定系统的了解一下pe结构。光从书面去了解我觉得应该是不够的,决定手动去写一个程序来解析pe头,也能对pe各字段的作用有更深的了解。
查阅了大量的资料,大多是文字表述,篇幅很长,内容雷同(天下文章一大抄,唉...),很难让人有耐心读下去。我也是抽了很多碎片时间逛了多次某sdn一点一点的读完了(真的很煎熬,虽然很拉跨,但不能否认前人对知识的分享),通过梳理文字,将pe头的结构通过图的形式展示出来(一下子就直观清晰了),图如下:
通过该图,尝试写出了一个程序,解析pe头部内容,后续计划通过头部内容解析出IAT表的内容,打算后面写个界面出来,所以目前只打印了dos头和节表看一下效果,实际数据已经读取了。程序如下(很拉跨):
#include <iostream>
#include <fstream>
#include <cstdint>
#include <iomanip>
#include <sstream>
#include <vector>
using namespace std;
// DOS 头数据结构 (IMAGE_DOS_HEADER)
struct DosHeader {
char e_magic; // 魔术数字 (MZ)
uint16_t e_cblp; // 文件最后页字节数
uint16_t e_cp; // 文件页数
uint16_t e_crlc; // 重定义元素个数
uint16_t e_cparhdr;// 头部尺寸,以段落为单位
uint16_t e_minalloc; // 所需最小附加段
uint16_t e_maxalloc; // 所需最大附加段
uint16_t e_ss; // 初始ss值
uint16_t e_sp; // 初始sp值
uint16_t e_csum; // 校验和
uint16_t e_ip; // 初始ip值
uint16_t e_cs; // 初始cs值(相对偏移量)
uint16_t e_lfarlc; // 重分配表文件地址
uint16_t e_ovno; // 覆盖号
uint16_t e_res; // 保留字
uint16_t e_oemid; // OEM 标识符
uint16_t e_oeminfo;// OEM 信息
uint16_t e_res2; // 保留字
int32_t e_lfanew; // pe头偏移量
};
//file_header
struct FileHeader {
uint16_t Machine;//运行平台
uint16_t NumberOfSections;//区块表的个数
uint32_t TimeDataStamp;//文件创建时间,是从1970年至今的秒数
uint32_t PointerToSymbolicTable;//指向符号表的指针
uint32_t NumberOfSymbols;//符号表的数目
uint16_t SizeOfOptionalHeader;//IMAGE_NT_HEADERS结构中OptionHeader成员的大小,对于win32平台这个值通常是0x00e0
uint16_t Characteristics;//文件的属性值
};
// 数据目录条目结构 (IMAGE_DATA_DIRECTORY)
struct DataDirectoryEntry {
uint32_t virtualAddress; // 虚拟地址,指向数据目录的位置
uint32_t size; // 数据目录的大小
};
struct directoryDataStruct {
DataDirectoryEntry exportDirectory; //导出表
DataDirectoryEntry importDirectory; //导入表
DataDirectoryEntry resourceDirectory; //资源表
DataDirectoryEntry exceptionDirectory; //异常表
DataDirectoryEntry secirityDirectory; //安全性表
DataDirectoryEntry relocationDirectory; //基址重定位表
DataDirectoryEntry debugDirectory; //调试信息表
DataDirectoryEntry architectureDirectory; //体系结构相关表
DataDirectoryEntry reservedDirectory; //全局指针寄存器表
DataDirectoryEntry TLSDirectory; //tls表
DataDirectoryEntry configurationDirectory; //加载配置表
DataDirectoryEntry boundImportDirectory; //绑定导入表
DataDirectoryEntry IATDirectory;//IAT表
DataDirectoryEntry delayDirectory;//延迟导入表
DataDirectoryEntry runTimeDirectory; //运行时描述符表
};
//optionalHeader(可选头)
struct optionalHeader {
uint16_t Magic;// 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
unsigned char MajorLinkerVersion;// 链接程序的主版本号
unsigned char MinorLinkerVersion;// 链接程序的次版本号
uint32_t SizeOfCode;// 所有含代码的节的总大小
uint32_t SizeOfInitializedData;// 所有含已初始化数据的节的总大小
uint32_t SizeOfUninitializedData;// 所有含未初始化数据的节的大小
uint32_t AddressOfEntryPoint;// 程序执行入口RVA
uint32_t BaseOfCode;// 代码的区块的起始RVA
uint32_t BaseOfData;// 数据的区块的起始RVA
// NT additional fields. 以下是属于NT结构增加的领域。
uint32_t ImageBase;// 程序的首选装载地址
uint32_t SectionAlignment;// 内存中的区块的对齐大小
uint32_t FileAlignment;// 文件中的区块的对齐大小
uint16_t MajorOperatingSystemVersion;// 要求操作系统最低版本号的主版本号
uint16_t MinorOperatingSystemVersion;// 要求操作系统最低版本号的副版本号
uint16_t MajorImageVersion;// 可运行于操作系统的主版本号
uint16_t MinorImageVersion;// 可运行于操作系统的次版本号
uint16_t MajorSubsystemVersion;// 要求最低子系统版本的主版本号
uint16_t MinorSubsystemVersion;// 要求最低子系统版本的次版本号
uint32_t Win32VersionValue;// 莫须有字段,不被病毒利用的话一般为0
uint32_t SizeOfImage;// 映像装入内存后的总尺寸
uint32_t SizeOfHeaders;// 所有头 + 区块表的尺寸大小
uint32_t CheckSum;// 映像的校检和
uint16_t Subsystem;// 可执行文件期望的子系统
uint16_t DllCharacteristics;// DllMain()函数何时被调用,默认为 0
uint32_t SizeOfStackReserve;// 初始化时的栈大小
uint32_t SizeOfStackCommit;// 初始化时实际提交的栈大小
uint32_t SizeOfHeapReserve;// 初始化时保留的堆大小
uint32_t SizeOfHeapCommit;// 初始化时实际提交的堆大小
uint32_t LoaderFlags;// 与调试有关,默认为 0
uint32_t NumberOfRvaAndSizes;// 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
directoryDataStruct Data_Directory; // 数据目录表
};
// PE 结构 (IMAGE_NT_HEADERS)
struct PeSignature {
char signature; // PE Signature (PE\0\0)
FileHeader fileheader;
optionalHeader optionalheader;
};
// 节表
typedef struct SectionTable {
unsigned char Name;
uint32_t VirtualSize; //指出实际的、被使用的区块的大小(也就是区块的数据没有对齐处理的实际大小)16H个
uint32_t VirtualAddress; //该块装载到内存中的RVA
uint32_t SizeOfRawData;//该块在磁盘文件中所占的大小
uint32_t PointerToRawData;//该块在磁盘文件中的偏移
uint32_t PointerToRelocations;//在EXE文件中无意义
uint32_t PointerToLinenumbers;
uint16_t NumberOfRelocations;//由pointerToRelocations指向的重定位的数目
uint16_t NumberOfLinenumbers;
uint32_t Characteristics; //块属性
};
int main() {
// 打开pe文件-二进制格式
ifstream file("C:/Users/小鱼/Desktop/字符.exe", ios::binary);
if (!file) {
cerr << "文件打开失败!" << endl;
return 1;
}
// 读取文件内容并保存到fileData容器中(保留数据,可能有其他功能开发需要)
vector<unsigned char> fileData;
unsigned char byte;
while (file.get(reinterpret_cast<char&>(byte))) {
fileData.push_back(byte);
}
//清除文件错误,否则seekg不生效
file.clear();
file.seekg(0, ios::beg); // 设置文件指针回到文件开头
//cout << file.tellg() << endl;
// 十六进制显示数据
//十六进制格式写入流
int count = 0;
stringstream hexStream;
for (const auto& byte : fileData) {
hexStream << hex << setw(2) << setfill('0') << static_cast<int>(byte) << " ";
count++;
if (count % 16 == 0) {
hexStream << endl;
}
}
//以字符形式将流输出到dos窗口,便于对照文件内容
//cout << hexStream.str() << endl;
// 读取DOS头数据
DosHeader dosHeader;
file.read(reinterpret_cast<char*>(&dosHeader), sizeof(DosHeader));
//检查是否存在MZ
if (dosHeader.e_magic != 'M' || dosHeader.e_magic != 'Z') {
cerr << "非法的DOS头格式!" << endl;
file.close();
return 1;
}
// 移动文件指针到pe头
file.seekg(dosHeader.e_lfanew, ios::beg);
// 读取pe结构
PeSignature peStructs;
file.read(reinterpret_cast<char*>(&peStructs), sizeof(PeSignature));
// 检查PE魔术字符是否存在
if (peStructs.signature != 'P' || peStructs.signature != 'E' ||
peStructs.signature != '\0' || peStructs.signature != '\0') {
cerr << "非法的PE格式!." << endl;
file.close();
return 1;
}
//显示文件当前指针
//cout << file.tellg() << endl;
//读取节表
file.seekg(8, ios::cur);//文件指针后移8个字节,绕过8个字符到节表处
int sectionNum = (int)peStructs.fileheader.NumberOfSections;
//cout << sectionNum << endl; // 显示节数
// 使用 vector 动态创建 SectionTable 结构体数组
vector<SectionTable> sectionTableArray(sectionNum);
// 循环读取节表数据,写入vector结构体数组
for (int i = 0; i < sectionNum; i++) {
file.read(reinterpret_cast<char*>(§ionTableArray), sizeof(SectionTable));
}
//输出DOS头信息
cout << "DOS头信息:" << endl;
cout << left << setw(15) << "头部:"<< dosHeader.e_magic << endl;
cout << left<<setw(15)<<"e_cblp:" <<left<<setw(15)<<hex<<dosHeader.e_cblp<< endl;
cout << left<<setw(15) << "e_cp:" << left << setw(15) << hex<<dosHeader.e_cp<< endl;
cout << left << setw(15) << "e_crlc:" << left << setw(15) << hex<<dosHeader.e_crlc<<endl;
cout << left << setw(15) << "e_cparhdr:" << left << setw(15) << hex<<dosHeader.e_cparhdr<< endl;
cout << left << setw(15) << "e_minalloc:" << left << setw(15) << hex<<dosHeader.e_minalloc<<endl;
cout << left << setw(15) << "e_maxalloc:" << left << setw(15) << hex<<dosHeader.e_maxalloc<<endl;
cout << left << setw(15) << "e_ss:" << left << setw(15) << hex<<dosHeader.e_ss<< endl;
cout << left << setw(15) << "e_sp:" << left << setw(15) << hex<<dosHeader.e_sp<< endl;
cout << left << setw(15) << "e_csum:" << left << setw(15) << hex<<dosHeader.e_csum<< endl;
cout << left << setw(15) << "e_ip:" << left << setw(15) << hex<<dosHeader.e_ip<< endl;
cout << left << setw(15) << "e_cs:" << left << setw(15) << hex<<dosHeader.e_cs<<endl;
cout << left << setw(15) << "e_lfarlc:" << left << setw(15) << hex<<dosHeader.e_lfarlc<< endl;
cout << left << setw(15) << "e_ovno:" << left << setw(15) << hex<<dosHeader.e_ovno<<endl;
cout << left << setw(15) << "e_res:" << left << setw(15) << hex<<dosHeader.e_res<< endl;
cout << left << setw(15) << "e_oemid:" << left << setw(15) << hex<<dosHeader.e_oemid<<endl;
cout << left << setw(15) << "e_oeminfo:" << left << setw(15) << hex<<dosHeader.e_oeminfo<<endl;
cout << left << setw(15) << "e_res2:" << left << setw(15) << hex<<dosHeader.e_res2<< endl;
cout << left << setw(15) << "e_lfanew:" << left << setw(15) << hex<<dosHeader.e_lfanew<<endl;
cout << endl;
// 输出节表数据
cout << "节表数据:" << endl;
for (const auto& section : sectionTableArray) {
// 以结构体形式输出 SectionTable 数据
cout << "Name: ";
for (int i = 0; i < 8; i++) {
cout << section.Name;
}
cout << endl;
cout << "VirtualSize: " << section.VirtualSize << endl;
cout << "VirtualAddress: 0x" << hex << section.VirtualAddress << endl;
cout << "SizeOfRawData: " << dec << section.SizeOfRawData << " bytes" << endl;
cout << "PointerToRawData: " << hex << section.PointerToRawData << endl;
cout << "PointerToRelocations: " << hex << section.PointerToRelocations << endl;
cout << "PointerToLinenumbers: " << hex << section.PointerToLinenumbers << endl;
cout << "NumberOfRelocations: " << dec << section.NumberOfRelocations << endl;
cout << "NumberOfLinenumbers: " << dec << section.NumberOfLinenumbers << endl;
cout << "Characteristics: " << hex << section.Characteristics << endl;
cout << endl;
}
// 在不再使用sectionTable数组时,释放内存
file.close();
return 0;
}
运行效果如下:
页:
[1]