PE文件头

说明

PE结构好文
windows下的各种类型定义
对pe文件头的解析
各种数据结构的定义都在 Winnt.h中

DOS头

typedef struct _IMAGE_DOS_HEADER {
      WORD e_magic;      // MZ头
      WORD e_cblp;
      WORD e_cp;
      WORD e_crlc;
      WORD e_cparhdr;
      WORD e_minalloc;
      WORD e_maxalloc;
      WORD e_ss;
      WORD e_sp;
      WORD e_csum;
      WORD e_ip;
      WORD e_cs;
      WORD e_lfarlc;
      WORD e_ovno;
      WORD e_res[4];
      WORD e_oemid;
      WORD e_oeminfo;
      WORD e_res2[10];
      LONG e_lfanew;      // PE头的开始位置
    } IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;

PE头

 typedef struct _IMAGE_NT_HEADERS64 {
      DWORD Signature;        //PE头
      IMAGE_FILE_HEADER FileHeader;    //文件头
      IMAGE_OPTIONAL_HEADER64 OptionalHeader;   //可选头
    } IMAGE_NT_HEADERS64,*PIMAGE_NT_HEADERS64;

文件头

IMAGE_FILE_HEADER

 typedef struct _IMAGE_FILE_HEADER {
      WORD Machine;               //可执行文件的目标CPU类型
      WORD NumberOfSections;      //PE文件的节区个数
      DWORD TimeDateStamp;        //从1970.1.1到创建该文件时的秒数
      DWORD PointerToSymbolTable;
      DWORD NumberOfSymbols;
      WORD SizeOfOptionalHeader;  //可选头的大小
      WORD Characteristics;       //文件类型(dll...)
    } IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;

选项头

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;                    //PE(dos头,pe头,文件头...等所有头的)头部的大小
      DWORD CheckSum;                         //校验和
      WORD Subsystem;                         //
      WORD DllCharacteristics;
      ULONGLONG SizeOfStackReserve;
      ULONGLONG SizeOfStackCommit;
      ULONGLONG SizeOfHeapReserve;
      ULONGLONG SizeOfHeapCommit;
      DWORD LoaderFlags;
      DWORD NumberOfRvaAndSizes;            //固定值16,也就是下面的 IMAGE_NUMBEROF_DIRECTORY_ENTRIES
      IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
                                            //数据目录表
    } IMAGE_OPTIONAL_HEADER64,*PIMAGE_OPTIONAL_HEADER64;

选项头的最后就是下面这个结构体的数组
这个数组的每一项都描述了一个特定的空间(导入表,导出表...)

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

该数组每一项的意义

#define IMAGE_DIRECTORY_ENTRY_EXPORT 0

#define IMAGE_DIRECTORY_ENTRY_IMPORT 1

#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2

#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3

#define IMAGE_DIRECTORY_ENTRY_SECURITY 4

#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5

#define IMAGE_DIRECTORY_ENTRY_DEBUG 6

#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7

#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7

#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8

#define IMAGE_DIRECTORY_ENTRY_TLS 9

#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10

#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11

#define IMAGE_DIRECTORY_ENTRY_IAT 12

#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13

#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14

15是预留位置

导出表

函数导出的方式有两种,一种是按名字导出,一种是按序号导出。这两种导出方式在导出表中的描述方式也不相同

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;        //保留
    DWORD   TimeDateStamp;          //导出表的时间戳
    WORD    MajorVersion;           
    WORD    MinorVersion; 
    DWORD   Name;                   //模块的名字
    DWORD   Base;                   //如果是按序号导出,序号从base开始
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;          //按名字导出函数的数量
    DWORD   AddressOfFunctions;     // 一个RVA,指向一个DWORD数组,数组中的每一项是一个导出函数的RVA,顺序与导出序号相同
    DWORD   AddressOfNames;         // 一个RVA,依然指向一个DWORD数组,数组中的每一项仍然是一个RVA,指向一个表示函数名字
    DWORD   AddressOfNameOrdinals;  // 一个RVA,还是指向一个WORD数组,数组中的每一项与AddressOfNames中的每一项对应,表示该名字的函数在AddressOfFunctions中的序号
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

比较绕


这里我们来实际操作一下,解析一下kernel32.dll的导出表
先解析可选头,把导出表的开始地址(virtual_address)和长度(size)解析出来
那么IMAGE_EXPORT_DIRECTORY的起始地址就是virtual_address
那么Name的偏移量处就是一个char*数组,这里就是KERNEL32.dll
注意计算出Name的结构体偏移后,要加上映射地址
这里其实比较简单,值得注意的有
1.AddressOfNameOrdinals指向的数组的每一项的需要加上Base,才是真正的序列号(这个base一般都是1)
2.通过函数名,在名字数组中找到对应的项,然后根据该项,在对应的序号数组中,拿到序号,最后通过序号拿到函数的地址
3.计算地址的时候比较绕,容易算错,多check

导入表

在PE文件加载时,会根据这个表里的内容加载依赖的DLL,并填充所需函数的地址

typedef struct _IMAGE_IMPORT_DESCRIPTOR{
    union{
        DWORD Characteristics;
        DWORD OriginalFirstThunk;//导入名称表(INT)
    };
    DWORD TimeDateStamp;        //时间戳
    DWORD ForwarderChain;       //在老版的绑定中,它引用API的第一个forwarder chain(传递器链表)。它可被设置为0xFFFFFFFF以代表没有forwarder。
    DWORD Name;                 //dll名称
    DWORD FirstThunk;           //导入地址表(IAT)
}IMAGE_IMPORT_DESCRIPTOR;

在磁盘上时,INT和IAT的值一样(也有不一样的情况,此时IAT指向的内容已经被填充好),加载到内存时,IAT就会指向,由加载器填充好的真实的地址
这一块网上好像没有特别全的信息,可以在《解密黑客编程》IAT hook中寻找
先拿到导入表的起始地址和大小
然后在导入表起始位置有一个IMAGE_IMPORT_DESCRIPTOR数组,每个数组元素对应一个dll,以一个全0的结构体结束

INT和IAT都会指向一个IMAGE_THUNK_DATA32结构体数组,该数组以全0结构体结尾,

//OriginalFirstThunk和FirstThunk都指向的是_IMAGE_THUNK_DATA32结构体 
//导入名称表最高位是0,就是名称导入,此时该值指向一个IMAGE_IMPORT_BY_NAME结构体,保存了名字
//最高位是1,就是序号导入
typedef struct _IMAGE_THUNK_DATA32 {
    union {
        DWORD ForwarderString;      // PBYTE
        DWORD Function;             // PDWORD,导入函数的地址,在加载到内存后,这里才起作用
        DWORD Ordinal;              // 假如是序号导入的,会用到这里
        DWORD AddressOfData;         //PIMAGE_IMPORT_BY_NAME,假如是函数名导入的,用到这                   里 ,它指向另外一个结 构体:PIMGE_IMPORT_BY_NAME
        } u1;
} IMAGE_THUNK_DATA32;
 
typedef struct _IMAGE_IMPORT_BY_NAME{
    WORD Hint;      //序号
    BYTE NAME[1];   //函数名,越界访问即可
}IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;

节表

节的个数由文件头的NumberOfHeader给出

    typedef struct _IMAGE_SECTION_HEADER 
  {
      BYTE Name[IMAGE_SIZEOF_SHORT_NAME];   // 8个字节
      union {
	DWORD PhysicalAddress;
	DWORD VirtualSize;          //节数据没有对齐后的大小
      } Misc;
      DWORD VirtualAddress;
      DWORD SizeOfRawData;          //该节表在磁盘上的大小(通常是对齐后的值)
      DWORD PointerToRawData;       //该节表项在磁盘文件上的偏移
      DWORD PointerToRelocations;
      DWORD PointerToLinenumbers;
      WORD NumberOfRelocations;
      WORD NumberOfLinenumbers;
      DWORD Characteristics;
    } IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;

对应节表的作用

地址转换

VA虚拟地址,也就是程序装载进内存的位置
RVA相对虚拟地址,地址 - 程序装载地址
FileOffset在文件中的地址
在磁盘上时,对齐 = 选项头.FileAlignment
在内存中时,对齐 = 选项头.SectionAlignment
装载入内存的起始地址 = 可选头.ImageBase
RVA到FileOffset的转换思路 : 查地址到当前节的偏移,然后通过查节头的地址,即可转换

实例分析

简单的c++代码,弹出一个MessageBox("hello")

DOS大小64字,末尾四个字就是PE头的开始位置:0x80 00 00 00
然后是264字的PE头:4字的PE标记符 + 20字的文件头 + 240字的可选头

把文件头提取出来

number of sections :0x 11 00 也就是17个
size of optional header: 0x f0 00 也就是240,选项头的大小要从节表读取
characteristics : 0x 27 00 ,也就是 0b00100111,标记该文件的类型(exe,dll,...)
然后就是选项头(一般的文件的选项头的结尾就在.text的前面)

这里要用64位的选项头分析
size of code 所有包含可执行属性的节的大小: 0x 00 1e 00 00
address of entry point 入口点 :0x 00 15 00 00
base of code 代码段的起始位置 : 0x 00 10 00 00
base of data 数据段的起始位置 : 0x 00 00 40 00
image base 文件被装入内存后的首选建议装载地址 :0x 00 00 00 00
section alignment 内存对齐 :0x 00 10 00 00
file alignment 磁盘对齐 : 0x 00 20 00 00
size of image 装入内存后的总大小 :
size of headers 整个PE(DOS头 + PE头 + 节表)头的大小 :
number og rva and sizes 数据目录项的个数 :
最后(4(DWORD 虚拟地址) + 4(DWROD size)) * 16字,也就是数据目录表 :IMAGE_DATA_DIRECTORY DataDirectory[16]

data directory 数据目录表 :0x 10 00 00 00 可以看到这里就是16个,似乎该值可以改动,如下
为什么这里是2

然后选项头后面就是节表,从前面可以知道这里有16个节表

地址计算

RVA 相对虚拟地址
VA 虚拟地址
offset 文件偏移
RVA = VA - image base
offset = 该数据所在的节偏移 + (RVA(该数据) - RVA(该数据所在的节))

PE查看器

解析的时候注意结构体的版本,在64位下和32位下有一些区间
前置知识
涉及各种指针操作,直接用宏封装,可以直接提取对应的成员,自动化寻址

/*
---- From XDU's mzb
*/
#include <bits/stdc++.h>
#include <Windows.h>
#include <Winnt.h>
using namespace std;
using ll = long long int;

#define offset(data,member) (size_t)(&(((typeof(data)*)0)->member))
#define point_shift(p,dis) reinterpret_cast<decltype(p)>(reinterpret_cast<BYTE*>(p) + dis)
#define get_addr(p,type,member) reinterpret_cast<decltype(type::member)*>(point_shift(p,offset(type,member)))
#define get_value(p,type,member) *(get_addr(p,type,member)) 
template<typename T> 
void show_bytes(T* p)
  {
  	auto show_16 = [](ll n) -> char
	  {
	  	if (n <= 9)
	  	  return n + '0';
	  	else
	  	  return n - 10 + 'A';
	  };
  	BYTE* base = reinterpret_cast<BYTE*>(p);
  	for (ll i = 0;i < ll(sizeof(T));i++)
  	  {
  	  	ll val = ll(base[i]);
  	  	cout << setw(4) << show_16(val / 16) 
			            << show_16(val % 16);
		if (i % 16 == 15 or i + 1 == sizeof(T))
		  cout << "\n";
		}
  }
  
struct PE
  {
  	HANDLE h_file;
  	HANDLE h_map;
  	LPVOID base;
  	BOOL open_file(string file_name)
	  {
	  	h_file = CreateFile(file_name.c_str(),
		                    GENERIC_READ | GENERIC_WRITE,
							FILE_SHARE_READ,
							NULL,
							OPEN_EXISTING,
							FILE_ATTRIBUTE_NORMAL,
							NULL);
		if (h_file == INVALID_HANDLE_VALUE)
		  {
		  	return FALSE;
		  }
		h_map = CreateFileMapping(h_file,
		                          NULL,
								  PAGE_READWRITE | SEC_IMAGE,
								  0,0,0);
		if (h_map == NULL)
		  {
		  	CloseHandle(h_file);
		  	return FALSE;
		  }
		base = MapViewOfFile(h_map,
		                          FILE_MAP_READ | FILE_SHARE_WRITE,
								  0,0,0);
		if (base == NULL)
		  {
		  	CloseHandle(h_map);
		  	CloseHandle(h_file);
		  	return FALSE;
		   } 
		return TRUE;
	  }
	PE()
	  {
	  	h_file = NULL;
	  	h_map = NULL;
	  	base = NULL;
	  }
	~PE()
	  {
	  	CloseHandle(h_map);
	  	CloseHandle(h_file);
	  }
	IMAGE_DOS_HEADER* get_dos_header();
	IMAGE_NT_HEADERS64* get_pe_header();
	IMAGE_FILE_HEADER* get_file_header();
	IMAGE_OPTIONAL_HEADER64* get_optional_header();
	vector<IMAGE_SECTION_HEADER> enum_section_header();
	vector<tuple<WORD,string>> enum_export(); // 序号,函数名,函数地址 
  };
void show_hex(PE const& pe,ll n)
  {
  	auto show_16 = [](ll n) -> char
	  {
	  	if (n <= 9)
	  	  return n + '0';
	  	else
	  	  return n - 10 + 'A';
	  };
  	BYTE* p = static_cast<BYTE*>(pe.base);
	for (ll i = 0;i < 20;i++)
	  {
	  	cout << setw(5) << i << " ";
		for (ll k = 0;k < 16;k++)
	  	  {
	  	  	ll val = ll(p[i * 16 + k]);
	  	  	cout << setw(4) << show_16(val / 16) 
				            << show_16(val % 16);
			}
	  	cout << "\n";
	  }
  }
IMAGE_DOS_HEADER* PE::get_dos_header()
  {
  	IMAGE_DOS_HEADER* addr = static_cast<IMAGE_DOS_HEADER*>(base);
  	if (get_value(addr,IMAGE_DOS_HEADER,e_magic) != IMAGE_DOS_SIGNATURE)
  	  {
  	  	return NULL;
		}
	else
	  {
	  	return addr;
	  }
   }
IMAGE_NT_HEADERS64* PE::get_pe_header()
  {
  	auto dos_header = get_dos_header();
  	if (dos_header == NULL)
  	  {
  	  	return nullptr;
		} 
	IMAGE_NT_HEADERS64* ret = point_shift(reinterpret_cast<IMAGE_NT_HEADERS64*>(base),get_value(dos_header,IMAGE_DOS_HEADER,e_lfanew));
    if (get_value(ret,IMAGE_NT_HEADERS64,Signature) != IMAGE_NT_SIGNATURE)
      {
      	return nullptr;
	  }
	else
	  {
	  	return ret;
	  }
  }

IMAGE_FILE_HEADER* PE::get_file_header()
  {
  	auto pe_header = get_pe_header();
  	if (pe_header == nullptr)
  	  {
  	  	return NULL;
		}
	else
	  {
	  	return get_addr(pe_header,IMAGE_NT_HEADERS64,FileHeader);
	  }
  }
IMAGE_OPTIONAL_HEADER64* PE::get_optional_header()
  {
  	auto pe_header = get_pe_header();
  	if (pe_header == nullptr)
  	  {
  	  	return NULL;
		}
	else 
	  {
	  	return get_addr(pe_header,IMAGE_NT_HEADERS64,OptionalHeader);
	  }
  }
vector<IMAGE_SECTION_HEADER> PE::enum_section_header()
  {
  	if (get_file_header() == nullptr)
  	  {
  	  	return vector<IMAGE_SECTION_HEADER>();
		}
  	auto num_of_sections = get_value(get_file_header(),IMAGE_FILE_HEADER,NumberOfSections);
  	auto size_of_optional = get_value(get_file_header(),IMAGE_FILE_HEADER,SizeOfOptionalHeader);
	cout << "num of sections = " << num_of_sections << "\n";
	cout << "size of optional = " << size_of_optional << "\n";
  	auto base = point_shift(reinterpret_cast<IMAGE_SECTION_HEADER*>(get_optional_header()),size_of_optional);	
  	return vector<IMAGE_SECTION_HEADER>(base,base + num_of_sections);
  }
vector<tuple<WORD,string>> PE::enum_export()
  {
  	auto optional_header = get_optional_header();
  	if (optional_header == nullptr)
  	  {
  	  	return vector<tuple<WORD,string>>();
		}
	auto data_directory = get_addr(optional_header,IMAGE_OPTIONAL_HEADER64,DataDirectory);
	auto import = reinterpret_cast<IMAGE_DATA_DIRECTORY*>(data_directory);
	auto virtual_address = get_value(import,IMAGE_DATA_DIRECTORY,VirtualAddress);
	auto size = get_value(import,IMAGE_DATA_DIRECTORY,Size);
	auto export_header = reinterpret_cast<IMAGE_EXPORT_DIRECTORY*>(point_shift(base,virtual_address));	
	auto name = get_value(export_header,IMAGE_EXPORT_DIRECTORY,Name); 
	auto number_of_names = get_value(export_header,IMAGE_EXPORT_DIRECTORY,NumberOfNames);
	auto number_of_functions = get_value(export_header,IMAGE_EXPORT_DIRECTORY,AddressOfFunctions);
	auto address_of_names = get_value(export_header,IMAGE_EXPORT_DIRECTORY,AddressOfNames);
	auto address_of_name_ordinals = get_value(export_header,IMAGE_EXPORT_DIRECTORY,AddressOfNameOrdinals);
	auto p_name = reinterpret_cast<DWORD*>(point_shift(base,address_of_names));
	auto p_id = reinterpret_cast<DWORD*>(point_shift(base,address_of_name_ordinals)); 
	vector<tuple<WORD,string>> ret;
	for (ll i = 0;i < number_of_names;i++)
	  {
	  	ret.emplace_back(get_value(export_header,IMAGE_EXPORT_DIRECTORY,Base) + 
		                 *(reinterpret_cast<WORD*>(point_shift(base,address_of_name_ordinals)) + i),
		                 reinterpret_cast<char*>(point_shift(base,p_name[i])));
	  }
	return ret;
	}	  
int main()
  {
  	PE pe;
  	string dll_name = "kernel32.dll";
  	if (pe.open_file(dll_name.c_str()) == 0)
  	  {
  	  	return -1;
		}
	auto v = pe.enum_export(); 
	for (auto it : v)
	  {
	  	cout << "id = " << setw(10) << left << get<0>(it) << " "
		     << "function " << setw(20) << left << get<1>(it) << "\n";
	  }
	return 0;
}


posted @ 2021-10-16 21:23  XDU18清欢  阅读(137)  评论(0)    收藏  举报