看网上的代码好累,摸索整理了一下,顺便巩固下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
- 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;
- 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;
- }