最近想学DLL反射注入,但是发现要手动生成内存的DLL文件,所以就先尝试学PE加载器的实现
跟着佬的博客学的,感觉讲的很详细
手工模拟PE加载器 - 我可是会飞的啊
模拟加载PE文件 将DLL或者EXE文件手动加载到内存中,而不是用CreateProcess或者LoadLibrary函数来操作,防止了部分检测手段,同时将文件数据加入到资源节中可以实现不将其落地就可以执行的效果,减少了生成文件的检测手段
原理
将对应PE文件按节区顺序从文件结构映射到映像内存结构放入内存
根据重定位表修改硬编码数据指令
从导入表获取所需函数,修正导入表函数地址(如果是相同进程,可以用本进程的导入表直接修改)
执行/如果是DLL则是主动执行DLLMain
重要信息 映射PE头 1 OptionalHeader.SizeOfHeaders
映射Section 1 2 3 4 IMAGE_FIRST_SECTION(pedata.ntHeader) sectionHeader[i].PointerToRawData sectionHeader[i].VirtualAddress sectionHeader[i].SizeOfRawData
修复RelocTable 重定位表结构类似
虽然标准载入PE(exe)的时候是不需要重定位节区的,但是这里是在加载器空间创建内存,所以依然需要修复重定位表。
重定位表中的每一项都是一个结构体
1 2 3 4 5 typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; } IMAGE_BASE_RELOCATION;
TypeOffset是一个数组,每一个元素是一个偏移值说明这一个VirtualAddress段有哪些需要重定位的地址。SizeOfBlock包含了整个结构的大小和TypeOffset的大小
Block中TypeOffest为一个字,高4位表示类型,3表示x86,A表示x64,低12位表示偏移量 默认内存偏移量为
1 memBase + VirtualAddress + TypeOffest&0xfff
当前内存偏移量为
1 newMemBase + VirtualAddress + TypeOffest&0xfff
修复IAT IAT表每个元素都是一个如下结构体IID,每个结构体对应一个DLL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR;
该结构体中,Name字段标识了DLL名称的RVA
FirstThunk是最后的导入地址表指针,最后需要把每个导入的函数的真实地址填入到这里指向的位置
DUMMYUNIONNAME中,一般类型是OriginalFirstThunk,如果是Characteristics,就表示结束(0) OriginalFirstThunk和导入函数的名称或者序号有关
OriginalFirstThunk和FirstThunk指向了IMAGE_THUNK_DATA结构体的地址
1 2 3 4 5 6 7 8 typedef struct _IMAGE_THUNK_DATA64 { union { ULONGLONG ForwarderString; ULONGLONG Function; ULONGLONG Ordinal; ULONGLONG AddressOfData; } u1; } IMAGE_THUNK_DATA64;
具体结构如上,其中AddressOfData是指向IMAGE_IMPORT_BY_NAME的结构体,具体结构如下
1 2 3 4 typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; CHAR Name[1 ]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
如果指向的是导入名称表
,那么u1是AddressOfData, 如果是导入地址表
,分为两种情况 若是序号导入,u1是Ordinal,首位为1,低4位是导入序号 若是名称导入,u1是Function,目标导入地址
所以我们要同时遍历两个表进行赋值
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 #include <windows.h> #include <stdio.h> #include <stdlib.h> #include <memory> struct PEdata { PIMAGE_DOS_HEADER dosHeader; PIMAGE_NT_HEADERS ntHeader; PIMAGE_FILE_HEADER fileHeader; PIMAGE_OPTIONAL_HEADER optionalHeader; size_t memSize; size_t memHeaderSize; size_t sectionAlignment; DWORD64 imageBase; }pedata; BOOL loadPEstruct (BYTE* rawdata) { pedata.dosHeader = (PIMAGE_DOS_HEADER)rawdata; pedata.ntHeader = (PIMAGE_NT_HEADERS)(rawdata + pedata.dosHeader->e_lfanew); pedata.fileHeader = &(pedata.ntHeader->FileHeader); pedata.optionalHeader = &(pedata.ntHeader->OptionalHeader); pedata.imageBase = pedata.optionalHeader->ImageBase; pedata.memSize = pedata.optionalHeader->SizeOfImage; pedata.memHeaderSize = pedata.optionalHeader->SizeOfHeaders; pedata.sectionAlignment = pedata.optionalHeader->SectionAlignment; return TRUE; } std::unique_ptr<BYTE[]> mapToMemory (const char * filePath) { FILE* file; errno_t err = fopen_s (&file, filePath, "rb" ); if (err != 0 ) return nullptr ; fseek (file, 0 , SEEK_END); size_t fileSize = ftell (file); fseek (file, 0 , SEEK_SET); std::unique_ptr<BYTE[]> fileData (new BYTE[fileSize]) ; if (!fileData) return nullptr ; fread (fileData.get (), 1 , fileSize, file); fclose (file); printf ("成功申请内存: 0x%llX\n" , reinterpret_cast <DWORD64>(fileData.get ())); return fileData; } BOOL makeHeadandSectionMap (LPVOID memBase, const BYTE* fileBase) { printf ("--------开始映射节区--------\n" ); RtlMoveMemory (memBase, fileBase, pedata.memHeaderSize); PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION (pedata.ntHeader); for (int i = 0 ; i < pedata.fileHeader->NumberOfSections; i++) { if (sectionHeader[i].VirtualAddress == 0 && sectionHeader[i].SizeOfRawData == 0 ) { continue ; } LPVOID srcMem = (LPVOID)(fileBase + sectionHeader[i].PointerToRawData); LPVOID dstMem = (BYTE*)memBase + sectionHeader[i].VirtualAddress; size_t sizeOfRawData = sectionHeader[i].SizeOfRawData; RtlCopyMemory (dstMem, srcMem, sizeOfRawData); printf (" - 节区%d: %s, 映射到内存 0x%llX\n" , i, sectionHeader[i].Name, reinterpret_cast <DWORD64>(dstMem)); } printf ("--------映射节区结束--------\n" ); return TRUE; } BOOL repareReloc (LPVOID memBase) { printf ("------开始修复重定位表-------\n" ); PIMAGE_BASE_RELOCATION relTable = (PIMAGE_BASE_RELOCATION)((BYTE*)memBase + pedata.optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); size_t delta = reinterpret_cast <DWORD64>(memBase) - pedata.optionalHeader->ImageBase; if (relTable == nullptr || delta == 0 ) { return TRUE; } int j = 0 ; while (relTable->SizeOfBlock != 0 && relTable->VirtualAddress != 0 ) { WORD* pRdata = (WORD*)((BYTE*)relTable + sizeof (IMAGE_BASE_RELOCATION)); size_t dataNum = (relTable->SizeOfBlock - sizeof (IMAGE_BASE_RELOCATION)) / sizeof (WORD); printf (" - 表%d:\n" , j); for (size_t i = 0 ; i < dataNum; i++) { DWORD type = pRdata[i] >> 12 ; if (type == IMAGE_REL_BASED_DIR64) { DWORD64* targetAddress = (DWORD64*)((BYTE*)memBase + relTable->VirtualAddress + (pRdata[i] & 0xFFF )); printf (" - 对应地址: 0x%llX 数据: 0x%llX\n" , reinterpret_cast <DWORD64>(targetAddress), *targetAddress); *targetAddress += delta; printf (" -> 0x%llX delta: 0x%llX\n" , *targetAddress, delta); } } relTable = (PIMAGE_BASE_RELOCATION)((BYTE*)relTable + relTable->SizeOfBlock); j++; } printf ("------修复重定位表结束-------\n" ); return TRUE; } BOOL repareIAT (LPVOID memBase) { printf ("--------开始修复IAT--------\n" ); PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)((BYTE*)memBase + pedata.optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); while (importDesc->OriginalFirstThunk) { char * Dllname = (char *)((BYTE*)memBase + importDesc->Name); HMODULE hDll = GetModuleHandleA (Dllname); if (!hDll) { printf (" - 加载DLL:%s\n" , Dllname); hDll = LoadLibraryA (Dllname); if (!hDll) { printf (" - 加载DLL失败,导入函数终止\n" ); return FALSE; } } else { printf (" - DLL已存在:%s\n" , Dllname); } PIMAGE_THUNK_DATA INT = (PIMAGE_THUNK_DATA)((BYTE*)memBase + importDesc->OriginalFirstThunk); PIMAGE_THUNK_DATA IAT = (PIMAGE_THUNK_DATA)((BYTE*)memBase + importDesc->FirstThunk); for (int i = 0 ; INT[i].u1.AddressOfData; i++) { DWORD64 pFunc = NULL ; DWORD64 targetFunc = reinterpret_cast <DWORD64>((BYTE*)memBase + INT[i].u1.AddressOfData); if (targetFunc & IMAGE_ORDINAL_FLAG) { printf (" - 以序号:%d 导入函数\n" , IMAGE_ORDINAL (targetFunc)); pFunc = (DWORD64)GetProcAddress (hDll, MAKEINTRESOURCEA (IMAGE_ORDINAL (targetFunc))); } else { printf (" - 以名称:%s 导入函数\n" , ((PIMAGE_IMPORT_BY_NAME)targetFunc)->Name); pFunc = (DWORD64)GetProcAddress (hDll, ((PIMAGE_IMPORT_BY_NAME)targetFunc)->Name); } if (pFunc) { IAT[i].u1.Function = pFunc; printf (" - 函数导入成功\n - 目的地址: 0x%llX\n" , pFunc); } else { printf (" - 函数导入失败\n" ); } } importDesc++; } printf ("--------修复IAT结束--------\n" ); return TRUE; } BOOL setImageBase (LPVOID memBase) { IMAGE_DOS_HEADER* dosHeader = (PIMAGE_DOS_HEADER)memBase; IMAGE_NT_HEADERS* ntHeader = (PIMAGE_NT_HEADERS)((BYTE*)memBase + dosHeader->e_lfanew); ntHeader->OptionalHeader.ImageBase = reinterpret_cast <DWORD64>(memBase); printf ("--------修正镜像基地址成功--------\n" ); return TRUE; } int main () { const char * targetPath = "xxx" ; std::unique_ptr<BYTE[]> fileBase = mapToMemory (targetPath); if (!fileBase) return 0 ; loadPEstruct (fileBase.get ()); LPVOID memBase = VirtualAllocEx (GetCurrentProcess (), nullptr , pedata.memSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!memBase) return 0 ; makeHeadandSectionMap (memBase, fileBase.get ()); repareReloc (memBase); repareIAT (memBase); setImageBase (memBase); DWORD dwOldProtect = 0 ; VirtualProtectEx (GetCurrentProcess (), memBase, pedata.memSize, PAGE_EXECUTE_READWRITE, &dwOldProtect); printf ("开始执行\n" ); auto targetFunc = reinterpret_cast <int (*)(void )>((BYTE*)memBase + ((PIMAGE_NT_HEADERS)((BYTE*)memBase + ((PIMAGE_DOS_HEADER)memBase)->e_lfanew))->OptionalHeader.AddressOfEntryPoint); targetFunc (); VirtualFreeEx (GetCurrentProcess (), memBase, 0 , MEM_RELEASE); return 0 ; }
目前未解决的问题
对DLL的载入,下次学反射DLL注入的时候试试
若文件本身有UPX等壳,修正的所有内容均为壳的内容,壳解压后并不能还原PE本身的映射到偏移地址,导致报错