原文链接:https://www.x86matthew.com/view_post?id=import_dll_injection
这篇文章展示了我最近提出的一个相当简单的 DLL 注入想法。我在网上快速浏览了一下,看看有没有其他人使用过这种方法,但找不到任何结果。(更新:hasherezade 提到微软在 MSDetours 中名为 DetourCreateProcessWithDlls 的函数中使用了非常相似的方法)
此方法不需要创建临时线程、修改可执行代码或更改线程上下文。
简而言之,我们将修改新创建进程内存中的 PE 标头,以将我们自己的 DLL 添加到导入描述符列表中。
我的想法如下:
1 2 3 4 5 6 7 8 9 |
1. 使用带有CREATE_SUSPENDED的CreateProcess 创建目标 EXE 进程的挂起实例旗帜。 2. 使用NtQueryInformationProcess 计算目标 EXE 的基地址,读取目标进程的 PEB 地址,使用ReadProcessMemory读取ImageBaseAddress字段值。 3. 使用ReadProcessMemory从目标进程 读取主 PE 头 ( IMAGE_NT_HEADERS ) 。 4. 为以后存储未修改的IMAGE_NT_HEADERS结构 的副本。 5. 在 PE 头中的导入描述符列表中添加一个额外的 DLL 条目(ImageNtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT])。还需要在目标进程中分配模块路径、导入查找表和导入地址表。导入表将包括一个条目:ordinal #1。 6. 使用WriteProcessMemory将更新的 PE 头写回目标进程。 7. 使用ResumeThread恢复目标进程的执行。 8.循环使用ReadProcessMemory 读取注入DLL 的新导入地址表。等到目标进程更新了单个导入(序数 #1)的地址。 9. DLL 现在已经被注入到目标进程中。释放任何临时内存并从第 4 步恢复原始 PE 标头。 |
这是有效的,因为新创建的挂起进程只有ntdll.dll最初加载 – 这意味着我们可以在加载剩余的 DLL 之前操作内存中的导入表。这种方法可靠,在我的测试中效果很好。
这种方法的主要缺点是它只能用于将 DLL 注入新进程。无法使用此方法注入已运行的进程,因为仅在进程首次启动时才处理导入描述符列表。
此方法的另一个小缺点是目标 DLL 需要导出至少一个函数。Windows 不会加载具有空白导入查找表(至少在我的系统上)的导入 DLL,因此我在导入序号 #1 的查找表中添加了一个条目。此导出条目必须存在于目标 DLL 中,否则目标进程将无法启动。我将在本文末尾包含示例 DLL 的代码。
我们不能保证有足够的空间在现有的 PE 头数据中添加额外的模块条目。为了解决这个问题,我将现有的导入描述符列表复制到我在内存中分配的更大的缓冲区中。然后将新的 DLL 附加到此列表中,并在VirtualAddress和Size字段中IMAGE_DIRECTORY_ENTRY_IMPORT更新为指向新列表。
成功注入后恢复原始 PE 标头可消除任何篡改迹象。但是,目标 DLL 不会以任何方式隐藏 – 它将像任何其他合法加载的 DLL 一样在内存中公开可见。
完整代码如下:
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 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
#include <stdio.h> #include <windows.h> DWORD (WINAPI *NtQueryInformationProcess)(HANDLE ProcessHandle, DWORD ProcessInformationClass, PVOID ProcessInformation, DWORD ProcessInformationLength, DWORD *ReturnLength); struct PROCESS_BASIC_INFORMATION { DWORD ExitStatus; void *PebBaseAddress; DWORD AffinityMask; DWORD BasePriority; DWORD UniqueProcessId; DWORD InheritedFromUniqueProcessId; }; DWORD LaunchTargetProcess(char *pExePath, HANDLE *phProcess, HANDLE *phProcessMainThread) { PROCESS_INFORMATION ProcessInfo; STARTUPINFO StartupInfo; // initialise startup data memset((void*)&StartupInfo, 0, sizeof(StartupInfo)); StartupInfo.cb = sizeof(StartupInfo); printf("Launching target process...\n"); // create target process (suspended) memset((void*)&ProcessInfo, 0, sizeof(ProcessInfo)); if(CreateProcess(NULL, pExePath, NULL, NULL, 0, CREATE_SUSPENDED, NULL, NULL, &StartupInfo, &ProcessInfo) == 0) { return 1; } // store handles *phProcess = ProcessInfo.hProcess; *phProcessMainThread = ProcessInfo.hThread; return 0; } DWORD InjectDll(HANDLE hProcess, HANDLE hProcessMainThread, char *pDllPath) { IMAGE_DOS_HEADER ImageDosHeader; IMAGE_NT_HEADERS ImageNtHeader; IMAGE_NT_HEADERS ImageNtHeader_Original; PROCESS_BASIC_INFORMATION ProcessBasicInfo; DWORD dwExeBaseAddrPebPtr = 0; DWORD dwExeBaseAddr = 0; DWORD dwNtHeaderAddr = 0; DWORD dwDllPathLength = 0; void *pRemoteAlloc_DllPath = NULL; void *pRemoteAlloc_ImportLookupTable = NULL; void *pRemoteAlloc_ImportAddressTable = NULL; void *pRemoteAlloc_NewImportDescriptorList = NULL; DWORD dwImportLookupTable[2]; DWORD dwExistingImportDescriptorEntryCount = 0; DWORD dwNewImportDescriptorEntryCount = 0; BYTE *pNewImportDescriptorList = NULL; IMAGE_IMPORT_DESCRIPTOR NewDllImportDescriptors[2]; DWORD dwExistingImportDescriptorAddr = 0; BYTE *pCopyImportDescriptorDataPtr = NULL; DWORD dwNewImportDescriptorListDataLength = 0; DWORD dwOriginalProtection = 0; DWORD dwOriginalProtection2 = 0; DWORD dwCurrentImportAddressTable[2]; printf("Reading image base address from PEB...\n"); // get process info memset(&ProcessBasicInfo, 0, sizeof(ProcessBasicInfo)); if(NtQueryInformationProcess(hProcess, 0, &ProcessBasicInfo, sizeof(ProcessBasicInfo), NULL) != 0) { return 1; } // get target exe base address from PEB dwExeBaseAddrPebPtr = (DWORD)ProcessBasicInfo.PebBaseAddress + 8; if(ReadProcessMemory(hProcess, (void*)dwExeBaseAddrPebPtr, (void*)&dwExeBaseAddr, sizeof(DWORD), NULL) == 0) { return 1; } printf("Reading PE headers...\n"); // read PE header from target process memset((void*)&ImageDosHeader, 0, sizeof(ImageDosHeader)); if(ReadProcessMemory(hProcess, (void*)dwExeBaseAddr, (void*)&ImageDosHeader, sizeof(ImageDosHeader), NULL) == 0) { return 1; } // read NT header from target process dwNtHeaderAddr = dwExeBaseAddr + ImageDosHeader.e_lfanew; memset((void*)&ImageNtHeader, 0, sizeof(ImageNtHeader)); if(ReadProcessMemory(hProcess, (void*)dwNtHeaderAddr, (void*)&ImageNtHeader, sizeof(ImageNtHeader), NULL) == 0) { return 1; } // save a copy of the original NT header memcpy((void*)&ImageNtHeader_Original, (void*)&ImageNtHeader, sizeof(ImageNtHeader_Original)); // calculate dll path length dwDllPathLength = strlen(pDllPath) + 1; // allocate buffer for the dll path in the remote process pRemoteAlloc_DllPath = VirtualAllocEx(hProcess, NULL, dwDllPathLength, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if(pRemoteAlloc_DllPath == NULL) { return 1; } // write dll path to remote process buffer if(WriteProcessMemory(hProcess, (void*)pRemoteAlloc_DllPath, pDllPath, dwDllPathLength, NULL) == 0) { return 1; } // set import lookup table values (import ordinal #1) dwImportLookupTable[0] = 0x80000001; dwImportLookupTable[1] = 0; // allocate buffer for the new import lookup table in the remote process pRemoteAlloc_ImportLookupTable = VirtualAllocEx(hProcess, NULL, sizeof(dwImportLookupTable), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if(pRemoteAlloc_ImportLookupTable == NULL) { return 1; } // write import lookup table to remote process buffer if(WriteProcessMemory(hProcess, (void*)pRemoteAlloc_ImportLookupTable, (void*)dwImportLookupTable, sizeof(dwImportLookupTable), NULL) == 0) { return 1; } // allocate buffer for the new import address table in the remote process pRemoteAlloc_ImportAddressTable = VirtualAllocEx(hProcess, NULL, sizeof(dwImportLookupTable), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if(pRemoteAlloc_ImportAddressTable == NULL) { return 1; } // write import address table to remote process buffer (initially the same values as the import lookup table) if(WriteProcessMemory(hProcess, (void*)pRemoteAlloc_ImportAddressTable, (void*)dwImportLookupTable, sizeof(dwImportLookupTable), NULL) == 0) { return 1; } // set import descriptor values for injected dll NewDllImportDescriptors[0].OriginalFirstThunk = (DWORD)pRemoteAlloc_ImportLookupTable - dwExeBaseAddr; NewDllImportDescriptors[0].TimeDateStamp = 0; NewDllImportDescriptors[0].ForwarderChain = 0; NewDllImportDescriptors[0].Name = (DWORD)pRemoteAlloc_DllPath - dwExeBaseAddr; NewDllImportDescriptors[0].FirstThunk = (DWORD)pRemoteAlloc_ImportAddressTable - dwExeBaseAddr; // end of import descriptor chain NewDllImportDescriptors[1].OriginalFirstThunk = 0; NewDllImportDescriptors[1].TimeDateStamp = 0; NewDllImportDescriptors[1].ForwarderChain = 0; NewDllImportDescriptors[1].Name = 0; NewDllImportDescriptors[1].FirstThunk = 0; // calculate existing number of imported dll modules dwExistingImportDescriptorEntryCount = ImageNtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size / sizeof(IMAGE_IMPORT_DESCRIPTOR); if(dwExistingImportDescriptorEntryCount == 0) { // the target process doesn't have any imported dll entries - this is highly unusual but not impossible dwNewImportDescriptorEntryCount = 2; } else { // add one extra dll entry dwNewImportDescriptorEntryCount = dwExistingImportDescriptorEntryCount + 1; } // allocate new import description list (local) dwNewImportDescriptorListDataLength = dwNewImportDescriptorEntryCount * sizeof(IMAGE_IMPORT_DESCRIPTOR); pNewImportDescriptorList = (BYTE*)malloc(dwNewImportDescriptorListDataLength); if(pNewImportDescriptorList == NULL) { return 1; } if(dwExistingImportDescriptorEntryCount != 0) { // read existing import descriptor entries dwExistingImportDescriptorAddr = dwExeBaseAddr + ImageNtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; if(ReadProcessMemory(hProcess, (void*)dwExistingImportDescriptorAddr, pNewImportDescriptorList, dwExistingImportDescriptorEntryCount * sizeof(IMAGE_IMPORT_DESCRIPTOR), NULL) == 0) { free(pNewImportDescriptorList); return 1; } } // copy the new dll import (and terminator entry) to the end of the list pCopyImportDescriptorDataPtr = pNewImportDescriptorList + dwNewImportDescriptorListDataLength - sizeof(NewDllImportDescriptors); memcpy(pCopyImportDescriptorDataPtr, (void*)NewDllImportDescriptors, sizeof(NewDllImportDescriptors)); // allocate buffer for the new import descriptor list in the remote process pRemoteAlloc_NewImportDescriptorList = VirtualAllocEx(hProcess, NULL, dwNewImportDescriptorListDataLength, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if(pRemoteAlloc_NewImportDescriptorList == NULL) { free(pNewImportDescriptorList); return 1; } // write new import descriptor list to remote process buffer if(WriteProcessMemory(hProcess, (void*)pRemoteAlloc_NewImportDescriptorList, pNewImportDescriptorList, dwNewImportDescriptorListDataLength, NULL) == 0) { free(pNewImportDescriptorList); return 1; } // free local import descriptor list buffer free(pNewImportDescriptorList); printf("Updating PE headers...\n"); // change the import descriptor address in the remote NT header to point to the new list ImageNtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = (DWORD)pRemoteAlloc_NewImportDescriptorList - dwExeBaseAddr; ImageNtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = dwNewImportDescriptorListDataLength; // make NT header writable if(VirtualProtectEx(hProcess, (LPVOID)dwNtHeaderAddr, sizeof(ImageNtHeader), PAGE_EXECUTE_READWRITE, &dwOriginalProtection) == 0) { return 1; } // write updated NT header to remote process if(WriteProcessMemory(hProcess, (void*)dwNtHeaderAddr, (void*)&ImageNtHeader, sizeof(ImageNtHeader), NULL) == 0) { return 1; } printf("Resuming process...\n"); // resume target process execution ResumeThread(hProcessMainThread); printf("Waiting for target DLL...\n"); // wait for the target process to load the DLL for(;;) { // read the IAT table for the injected DLL memset((void*)dwCurrentImportAddressTable, 0, sizeof(dwCurrentImportAddressTable)); if(ReadProcessMemory(hProcess, (void*)pRemoteAlloc_ImportAddressTable, (void*)dwCurrentImportAddressTable, sizeof(dwCurrentImportAddressTable), NULL) == 0) { return 1; } // check if the IAT table has been processed if(dwCurrentImportAddressTable[0] == dwImportLookupTable[0]) { // IAT table for injected DLL not yet processed - try again in 100ms Sleep(100); continue; } // DLL has been loaded by target process break; } printf("Restoring original PE headers...\n"); // restore original NT headers in target process if(WriteProcessMemory(hProcess, (void*)dwNtHeaderAddr, (void*)&ImageNtHeader_Original, sizeof(ImageNtHeader), NULL) == 0) { return 1; } // restore original protection value for remote NT headers if(VirtualProtectEx(hProcess, (LPVOID)dwNtHeaderAddr, sizeof(ImageNtHeader), dwOriginalProtection, &dwOriginalProtection2) == 0) { return 1; } // free temporary memory in remote process VirtualFreeEx(hProcess, pRemoteAlloc_DllPath, 0, MEM_RELEASE); VirtualFreeEx(hProcess, pRemoteAlloc_ImportLookupTable, 0, MEM_RELEASE); VirtualFreeEx(hProcess, pRemoteAlloc_ImportAddressTable, 0, MEM_RELEASE); VirtualFreeEx(hProcess, pRemoteAlloc_NewImportDescriptorList, 0, MEM_RELEASE); return 0; } int main(int argc, char *argv[]) { HANDLE hProcess = NULL; HANDLE hProcessMainThread = NULL; char *pExePath = NULL; char *pInjectDllPath = NULL; char szInjectDllFullPath[512]; printf("ImportDLLInjection - www.x86matthew.com\n\n"); if(argc != 3) { printf("Usage: %s [exe_path] [inject_dll_path]\n\n", argv[0]); return 1; } // get params pExePath = argv[1]; pInjectDllPath = argv[2]; // get full path from dll filename memset(szInjectDllFullPath, 0, sizeof(szInjectDllFullPath)); if(GetFullPathName(pInjectDllPath, sizeof(szInjectDllFullPath) - 1, szInjectDllFullPath, NULL) == 0) { printf("Invalid DLL path\n"); return 1; } // get NtQueryInformationProcess function ptr NtQueryInformationProcess = (unsigned long (__stdcall *)(void *,unsigned long,void *,unsigned long,unsigned long *))GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationProcess"); if(NtQueryInformationProcess == NULL) { printf("Failed to find NtQueryInformationProcess function\n"); return 1; } // launch target process if(LaunchTargetProcess(pExePath, &hProcess, &hProcessMainThread) != 0) { printf("Failed to launch target process\n"); return 1; } // inject DLL into target process if(InjectDll(hProcess, hProcessMainThread, szInjectDllFullPath) != 0) { printf("Failed to inject DLL\n"); // error TerminateProcess(hProcess, 0); CloseHandle(hProcessMainThread); CloseHandle(hProcess); return 1; } printf("Finished\n"); // close handles CloseHandle(hProcessMainThread); CloseHandle(hProcess); return 0; } |
Sample DLL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <stdio.h> #include <windows.h> // a blank exported function - this will be ordinal #1 __declspec(dllexport) void __cdecl ExportedFunction(void) { } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { if(fdwReason == DLL_PROCESS_ATTACH) { // display a message box MessageBox(NULL, "MessageBox from injected DLL", "www.x86matthew.com", MB_OK); } return 1; } |