看网上的代码好累,摸索整理了一下,顺便巩固下PE
首先介绍下PE头,分为2个部分
1、DOS头 (IMAGE_DOS_HEADER)
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // ASCII字符MZ
WORD e_cblp; // 文件最后页的字节数
WORD e_cp; // 文件页数
WORD e_crlc; // 重定位元素个数
WORD e_cparhdr; // 以段落为单位的头部大小
WORD e_minalloc; // 所需的最小附加段
WORD e_maxalloc; // 所需的最大附加段
WORD e_ss; // 初始的堆栈段(SS)相对偏移量值
WORD e_sp; // 初始的堆栈指针(SP)值
WORD e_csum; // 校验和
WORD e_ip; // 初始的指令指针(IP)值
WORD e_cs; // 初始的代码段(CS)相对偏移量值
WORD e_lfarlc; // 重定位表在文件中的偏移地址
WORD e_ovno; // 覆盖号
WORD e_res[4]; // 保留字(一般都是为确保对齐而预留)
WORD e_oemid; // OEM 标识符(相对于 e_oeminfo)
WORD e_oeminfo; // OEM 信息,即 e_oemid 的细节
WORD e_res2[10]; // 保留字(一般都是为确保对齐而预留)
LONG e_lfanew; // NT头在文件中的偏移地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
2、NT头 (IMAGE_NT_HEADERS)
NT头还区分为32位和64位,64位就不做介绍了,重点说下32位
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //签名(文件类型标志)
IMAGE_FILE_HEADER FileHeader; //PE 文件头结构(占用20个字节)
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //可选头结构(占用224个字节)
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
- Signature的值为:
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_OS2_SIGNATURE 0x454E // NE
#define IMAGE_OS2_SIGNATURE_LE 0x454C // LE
#define IMAGE_VXD_SIGNATURE 0x454C // LE
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
-FileHeader的结构
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //该文件运行所要求的CPU
WORD NumberOfSections; //文件的节数目
DWORD TimeDateStamp; //文件创建日期和时间
DWORD PointerToSymbolTable; //COFF 符号表格的偏移位置
DWORD NumberOfSymbols; //COFF 符号表格中的符号个数
WORD SizeOfOptionalHeader; //Optional Header(IMAGE_OPTIONAL_HEADER)结构大小
WORD Characteristics; //0x0001 文件中没有重定位(relocation)、0x0002 文件是一个可执行程序exe(也就是說不是OBJ 或LIB)、0x2000 文件是dll
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
-Optional Header 包含了PE文件的逻辑分布信息
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //用来定义 image 的状态:0x0107 一个 ROM image,0x010B 一个正常的(一般的)EXE
BYTE MajorLinkerVersion; //产生此PE文件的链接器的版本
BYTE MinorLinkerVersion; //产生此PE文件的链接器的版本
DWORD SizeOfCode; //所有code section 的总和大小
DWORD SizeOfInitializedData; //所有包含初始化内容的 sections(但不包括 code section)的总和大小
DWORD SizeOfUninitializedData; //所有需要PE装载器将内存地址空间赋予它但是却不占用硬盘空间的所有 sections 的大小总和
DWORD AddressOfEntryPoint; //这是PE文件开始执行的位置
DWORD BaseOfCode; //一个RVA,表示程序中的 code section 从何开始
DWORD BaseOfData; //一个RVA,表示程序中的 data section 从何开始
DWORD ImageBase; //PE文件的优先装载地址(Base Address)
DWORD SectionAlignment; //内存中节对齐的粒度
DWORD FileAlignment; //文件中节对齐的粒度
WORD MajorOperatingSystemVersion; //使用此可执行程序的操作系统的最小版本
WORD MinorOperatingSystemVersion; //使用此可执行程序的操作系统的最小版本
WORD MajorImageVersion; //WIN32子系统版本
WORD MinorImageVersion; //WIN32子系统版本
WORD MajorSubsystemVersion; //使用者自定义的域,允许你拥有不同版本的exe或dll
WORD MinorSubsystemVersion; //使用者自定义的域,允许你拥有不同版本的exe或dll
DWORD Win32VersionValue; //似乎总是0
DWORD SizeOfImage; //内存中整个PE映像体的尺寸
DWORD SizeOfHeaders; //所有头 + 节表的大小,也就等于文件尺寸减去文件中所有节的尺寸
DWORD CheckSum; //此程序的一个CRC 校验和
WORD Subsystem; //用来识别PE文件属于哪个子系统
WORD DllCharacteristics; //一组标志位,用来指出dll的初始化函数(例如 DllMain)在什么环境下被调用
DWORD SizeOfStackReserve; //线程初始堆栈的保留大小
DWORD SizeOfStackCommit; //一开始就被指定给执行线程初始堆栈的内存数量
DWORD SizeOfHeapReserve; //保留给最初的进程堆(process heap)的虚拟内存数量
DWORD SizeOfHeapCommit; //一开始就被指定给进程堆(process heap)的内存数量
DWORD LoaderFlags; //Debug用
DWORD NumberOfRvaAndSizes; //在DataDirectory(下一个域)数组的成员结构个数
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //一个IMAGE_DATA_DIRECTORY 结构数组。每个结构给出一个重要数据结构的RVA。
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
IMAGE_DATA_DIRECTORY的结构如下:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //虚拟地址
DWORD Size; //长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
注释:
RAV 代表相对虚拟地址。RVA是虚拟空间中到参考点的一段距离。RVA就是类似文件偏移量的东西。当然它是相对虚拟空间里的一个地址,而不是文件头部
重定位表在DataDirectory数组的第5个表内(IMAGE_DIRECTORY_ENTRY_BASERELOC)
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //表起始偏移值
DWORD Size; //表大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
即:模块基地址 + 表起始偏移值 = 重定位表数据
重定位表有好几块,每个块的头8个字节为2个DWORD,存放偏移值 和 当前块的大小,因此可以计算出当前块中需要修正的偏移(Word类型)的数量。
即:(当前块的大小 - 8) / 2 = 修正数量
修正偏移 = 原值 & 0xFFF
最后修正直接 将当前载入的基地址 + 修正偏移 就得到的修正数据的地址,直接将地址内的数据加上修正值。
修正值 = 当前载入的基地址 - imagebase
接下来直接给出代码,内存中加载DLL,并且修复重定位(RAV)
byte *MemModule = Null; //用于存放加载的模块
//参数为模块路径,加载成功返回内存地址,失败返回-1
int LoadModuleInMem(char *ModuleName)
{
int iRet = -1;
HANDLE hFile = CreateFile(ModuleName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL); //读取文件
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD dwFileSilze= GetFileSize(hFile,NULL); //获取个文件长度
MemModule = (BYTE*)malloc(dwFileSize); //申请内存
if (ReadFile(hFile, OrgCode, dwFileSize, &dwRead, NULL))
{
IMAGE_DOS_HEADER* DosHeader = (IMAGE_DOS_HEADER*)MemModule;
IMAGE_NT_HEADERS* PEHeader = (IMAGE_NT_HEADERS*)((DWORD)DosHeader + DosHeader->e_lfanew);
PIMAGE_DATA_DIRECTORY pRelocDir = NULL;
PIMAGE_BASE_RELOCATION pBaseReloc = NULL;
UINT nRelocSize = NULL;
DWORD ImageBase = PEHeader->OptionalHeader.ImageBase; //相对文件代码段偏移
pRelocDir = &PEHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; //获取重定位表结构
//判断是否存在重定位
if (pRelocDir->VirtualAddress != 0)
{
int Delta = 0;
if ((DWORD)OrgCode != ImageBase)
{
//修正值
Delta =(DWORD)MemModule - ImageBase ;
}
//获取重定位表数据地址
DWORD pRelocBase = pRelocDir->VirtualAddress+(DWORD)hSrcModule;
//取出重定位表的第一块 偏移 和 长度
pBaseReloc =(PIMAGE_BASE_RELOCATION)(pRelocBase);
//判断是否存在偏移
if (Delta != 0)
{
//修正重定位
while (pBaseReloc->VirtualAddress + pBaseReloc->SizeOfBlock != 0)
{
//0x1000 块内的 重定位偏移数量
int NumberOfReloc = (pBaseReloc->SizeOfBlock - 8 ) / 2;
for( int i=0 ; i < NumberOfReloc; i++)
{
WORD pLocData = *(WORD*)(pRelocBase + 8 + i * 2) & 0xFFF;
//内存中代码的地址 = 内存基地址 + 重定位RVA偏移 + 偏移;
//获取源值
WORD OldCode = *(WORD*)(MemModule + pBaseReloc->VirtualAddress + pLocData);
//将修正值写回
*(WORD*)(MemModule + pBaseReloc->VirtualAddress + pLocData) = OldCode + Delta;
}
//移动指针,指向下个块
pRelocBase = pRelocBase + pBaseReloc->SizeOfBlock;
//获取下个块的结构
pBaseReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pBaseReloc +pBaseReloc->SizeOfBlock);
}
iRet = MemModule;
}
}
}
}
return iRet;
}