PE基础练习
#include <windows.h> #include <stdio.h> struct TypeOffset { WORD Offset : 12; WORD Type : 4; }; // 保存文件大小 DWORD FileSize = 0; // 保存文件的基址 DWORD FileBase; // 保存DOS头 PIMAGE_DOS_HEADER DosHeader = nullptr; PIMAGE_NT_HEADERS NtHeader = nullptr; DWORD RVAtoFOA(DWORD rva) { // 1. 获取区段表 auto SectionTables = IMAGE_FIRST_SECTION(NtHeader); // 2. 获取区段数量 WORD Count = NtHeader->FileHeader.NumberOfSections; // 3. 遍历区段 for (int i = 0; i < Count; ++i) { // 4. 判断是否存在于区段中 if (rva >= SectionTables[i].VirtualAddress && rva < (SectionTables[i].VirtualAddress + SectionTables[i].SizeOfRawData)) { // 5. 找到之后计算位置并返回值 return rva - SectionTables[i].VirtualAddress + SectionTables[i].PointerToRawData; } } // 6. 没有找到返回 -1 return -1; } VOID OpenPeFile(LPCSTR FileName) { // 1. 打开文件 HANDLE Handle = CreateFileA(FileName, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); // 2. 获取文件大小 FileSize = GetFileSize(Handle, NULL); // 3. 读取文件数据 DWORD OperSize = 0; FileBase = (DWORD)new BYTE[FileSize]; ReadFile(Handle, (LPVOID)FileBase, FileSize, &OperSize, NULL); // 4. 判断是不是一个有效的 PE 文件 // 4.1 获取DOS头并判断是不是一个有效的DOS文件 DosHeader = (PIMAGE_DOS_HEADER)FileBase; if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE) { MessageBox(NULL, "不是一个有效的DOS文件", "提示", MB_OK); system("pause"); exit(0); } // 4.2 获取 NT 头并判断是不是一个有效的PE文件 NtHeader = (PIMAGE_NT_HEADERS)(FileBase + DosHeader->e_lfanew); if (NtHeader->Signature != IMAGE_NT_SIGNATURE) { MessageBox(NULL, "不是一个有效的PE文件", "提示", MB_OK); system("pause"); exit(0); } // 4.3 判断是不是一个32位文件 if (NtHeader->OptionalHeader.Magic != 0x010B) { MessageBox(NULL, "不是一个有效的32位文件", "提示", MB_OK); system("pause"); exit(0); } CloseHandle(Handle); } // 遍历重定位表 VOID FixReloc() { DWORD base = NtHeader->OptionalHeader.ImageBase; // 1. 获取重定位表的 rva DWORD RelocRVA = NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress; // 2. 获取重定位表 auto Reloc = (PIMAGE_BASE_RELOCATION)(FileBase + RVAtoFOA(RelocRVA)); // 3. 遍历重定位表中的重定位块,以0结尾 while (Reloc->SizeOfBlock != 0) { // 3.1 输出分页基址 printf("PAGE_BASE: %08X ", Reloc->VirtualAddress); // 3.2 找到重定位项 auto Offset = (TypeOffset*)(Reloc + 1); // 3.3 计算重定位项的个数 // Reloc->SizeOfBlock 保存的是整个重定位块的大小 结构体 + 重定位项数组 // Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) 得到数组大小 // 上面的结果 2 = 重定位项的个数,原因是重定位项的大小为 两个字节 DWORD Size = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; // 3.4 遍历所有的重定位项 for (int i = 0; i < Size; ++i) { // 获取重定位类型,只关心为3的类型 DWORD Type = Offset[i].Type; // 获取重定位的偏移值 DWORD pianyi = Offset[i].Offset; // 获取要重定位的地址所在的RVA: offset+virtualaddress DWORD rva = pianyi + Reloc->VirtualAddress; // 获取要重定位的地址所在的FOA DWORD foa = RVAtoFOA(rva); // 获取要重定位的地址所在的fa DWORD fa = foa + FileBase; // 获取要重定位的地址 DWORD addr = *(DWORD*)fa; // 计算重定位后的数据: addr - oldbase + newbase DWORD new_addr = addr - base + 0x1500000; // 将重定位后的数据写回缓冲区(文件) if (Offset[i].Type == 3) *(DWORD*)fa = new_addr; // printf(" T(%d)%08X->%08X: %08X[%08X] ", Type, rva, foa, addr, new_addr); } // 找到下一个重定位块 Reloc = (PIMAGE_BASE_RELOCATION) ((DWORD)Reloc + Reloc->SizeOfBlock); } NtHeader->OptionalHeader.ImageBase = 0x1500000; // 1. 打开文件 HANDLE Handle = CreateFileA("newfile.exe", GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); // 3. 写入文件数据 DWORD OperSize = 0; WriteFile(Handle, (LPVOID)FileBase, FileSize, &OperSize, NULL); CloseHandle(Handle); } VOID Import() { // 1. 从数据目录表的下标为 1的项找到rva DWORD rav = NtHeader->OptionalHeader.DataDirectory[1].VirtualAddress; // 2. 找到导入表结构体 auto ImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(RVAtoFOA(rav) + FileBase); // 3. 遍历导入表数组,数组以全 0 结尾 while (ImportTable->Name) { // 4. 输出对应DLL的名字 CHAR* DllName = (CHAR*)(RVAtoFOA(ImportTable->Name) + FileBase); printf("DLLName: %s ", DllName); // 5. 找到 iat auto Iat = (PIMAGE_THUNK_DATA)(RVAtoFOA(ImportTable->FirstThunk) + FileBase); // 6. 遍历 iat ,全 0 结尾 while (Iat->u1.Ordinal != 0) { // 7. 判断是否有名字 if (Iat->u1.AddressOfData & 0x80000000) { // 序号导入,直接输出 printf(" [%hd]: None ", LOWORD(Iat->u1.AddressOfData)); } else { // 找到名字结构体 auto Name = (PIMAGE_IMPORT_BY_NAME)(RVAtoFOA(Iat->u1.AddressOfData) + FileBase); printf(" [%hd]: %s ", Name->Hint, Name->Name); } ++Iat; } // 指向下一个结构 ImportTable++; } } VOID Export() { // 1. 从数据目录表的下标为 0 的项找到rva DWORD rav = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress; // 2. 找到导入表结构体 auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(rav) + FileBase); // 3. 获取有名字的个数和函数总个数 DWORD NameCount = ExportTable->NumberOfNames; DWORD FunctionCount = ExportTable->NumberOfFunctions; // 4. 获取三张表,序号表是WORD DWORD* 地址表 = (DWORD*)(RVAtoFOA(ExportTable->AddressOfFunctions) + FileBase); DWORD* 名称表 = (DWORD*)(RVAtoFOA(ExportTable->AddressOfNames) + FileBase); WORD* 序号表 = (WORD*)(RVAtoFOA(ExportTable->AddressOfNameOrdinals) + FileBase); // 5. 遍历地址表 for (int i = 0; i < FunctionCount; ++i) { bool HaveName = FALSE; // 6. 判断是否有名字,有名字的话,下标会存在序号表中 for (int j = 0; j < NameCount; ++j) { // 有名字 if (i == 序号表[j]) { HaveName = TRUE; // 对应序号表下标的名称表内保存的是名字 CHAR* Name = (CHAR*)(RVAtoFOA(名称表[j]) + FileBase); printf("[%hd]: %p %s ", i + ExportTable->Base, 地址表[i], Name); break; } } // 如果全部找完还没有名字 if (HaveName == FALSE) { printf("[%hd]: %p None ", i + ExportTable->Base, 地址表[i]); } } } int main() { OpenPeFile("test.dll"); // FixReloc(); // Import(); // Export(); return 0; }