原文链接:https://www.x86matthew.com/view_post?id=add_exe_import
这个项目松散地基于我几个月前发布的另一个项目(ImportDLLInjection)。ImportDLLInjection 将在挂起状态下启动 EXE,修改内存中的 IAT 表以包含额外的 DLL,然后继续执行。这将导致注入我们的目标 DLL 文件。
在此项目中,我们将使用类似的概念来修改磁盘上的 EXE 文件,以添加额外的 DLL 导入条目。这比修改内存中的 PE 标头稍微复杂一些,因为我们需要在向标头添加额外条目的同时维护 EXE 文件的原始结构。
此方法可能对持久性有用 – 例如,如果系统已经在启动时运行特定进程(abc.exe),我们可以修改此EXE以在后台自动加载DLL。
如上一篇文章所述,IAT 表中的 DLL 条目需要导入至少一个函数。我们将再次使用序数 #1。IAT 表也将重新定位到文件末尾,因为现有标头中不太可能有足够的空间来添加额外的 DLL。
我还首次为此项目添加了 64 位支持。此工具可以编译为 32/64 位,默认情况下,它将同时适用于这两种体系结构。
此工具的工作原理如下:
1.将原始EXE文件加载到内存中。
2. 检查 EXE 是 32 位还是 64 位 – 这很重要,因为 NT 标头的长度不同。
3. 从IMAGE_DIRECTORY_ENTRY_IMPORT数据目录中查找原始导入表位置。
4. 将导入表位置从虚拟地址转换为文件指针 – 我创建了一个函数来自动执行此操作,称为 VirtualAddressToFilePtr。
5. 获取原始导入表的副本并将其存储在内存中。
6. 创建此导入表的克隆版本,并为我们的新 DLL 文件添加一个额外的条目。
7. 创建一个空白的 EXE 输出文件。
8. 将原始 EXE 文件的内容写入新的输出文件(直到最后一节的末尾)。
9. 将新的扩展 IAT 表附加到新的 EXE 文件(紧跟在最后一节之后)。
10. 更新IMAGE_DIRECTORY_ENTRY_IMPORT目录以指向新的 IAT 表。
11. 增加上一节的 SizeOfRawData 和 VirtualSize 字段,以确保新的 IAT 表正确加载到内存中。磁盘上的分区数据必须与 pImageNtHeader->OptionalHeader.FileAlignment 的值对齐,因此此处可能需要额外的填充。
12. 检查原始 EXE 中在最后一个标头之后是否存在任何剩余数据。调试符号和嵌入文件通常附加到EXE文件的末尾 – 这些不是EXE格式的一部分,因此默认情况下它们不会加载到内存中。如有必要,pImageNtHeader->FileHeader.PointerToSymbolTable 值将更新为指向正确的位置,尽管这些值不太可能存在于生产软件中。
完整代码如下:
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 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
#include <stdio.h> #include <windows.h> DWORD LoadFileIntoMemory(char *pPath, BYTE **pFileData, DWORD *pdwFileSize) { HANDLE hFile = NULL; DWORD dwFileSize = 0; BYTE *pFileDataBuffer = NULL; DWORD dwBytesRead = 0; // open file hFile = CreateFile(pPath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) { return 1; } // calculate file size dwFileSize = GetFileSize(hFile, NULL); // allocate buffer pFileDataBuffer = (BYTE*)malloc(dwFileSize); if(pFileDataBuffer == NULL) { return 1; } // read file contents if(ReadFile(hFile, pFileDataBuffer, dwFileSize, &dwBytesRead, NULL) == 0) { return 1; } // verify byte count if(dwBytesRead != dwFileSize) { return 1; } // close file handle CloseHandle(hFile); // store values *pFileData = pFileDataBuffer; *pdwFileSize = dwFileSize; return 0; } DWORD WriteToFile(char *pPath, BYTE *pFileData, DWORD dwOrigFileSize, DWORD dwNewDataFilePosition, BYTE *pNewImportDirectory, DWORD dwNewImportDirectorySize, char *pDllName, BYTE *pImportLookupTable, DWORD dwImportLookupTableSize, DWORD dwPaddingBytes) { HANDLE hFile = NULL; DWORD dwBytesWritten = 0; BYTE bPaddingByte = 0; // create file hFile = CreateFile(pPath, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(hFile == INVALID_HANDLE_VALUE) { return 1; } // write original EXE data if(WriteFile(hFile, (void *)pFileData, dwNewDataFilePosition, &dwBytesWritten, NULL) == 0) { return 1; } // write new import directory if(WriteFile(hFile, (void *)pNewImportDirectory, dwNewImportDirectorySize, &dwBytesWritten, NULL) == 0) { return 1; } // write DLL name if(WriteFile(hFile, (void *)pDllName, (DWORD)(strlen(pDllName) + 1), &dwBytesWritten, NULL) == 0) { return 1; } // write import lookup table if(WriteFile(hFile, (void *)pImportLookupTable, dwImportLookupTableSize, &dwBytesWritten, NULL) == 0) { return 1; } // write import lookup table if(WriteFile(hFile, (void*)pImportLookupTable, dwImportLookupTableSize, &dwBytesWritten, NULL) == 0) { return 1; } // write section padding for(DWORD i = 0; i < dwPaddingBytes; i++) { if(WriteFile(hFile, (void*)&bPaddingByte, 1, &dwBytesWritten, NULL) == 0) { return 1; } } // write original appended data (debug symbols, installation data, etc) if(WriteFile(hFile, (void *)(pFileData + dwNewDataFilePosition), dwOrigFileSize - dwNewDataFilePosition, &dwBytesWritten, NULL) == 0) { return 1; } // close file handle CloseHandle(hFile); return 0; } BYTE *VirtualAddressToFilePtr(BYTE *pFileData, IMAGE_NT_HEADERS32 *pImageNtHeader, DWORD dwVirtualAddress) { IMAGE_SECTION_HEADER *pCurrSectionHeader = NULL; BYTE *pFilePtr = NULL; DWORD dwSectionDataLength = 0; // loop through all sections for(DWORD i = 0; i < pImageNtHeader->FileHeader.NumberOfSections; i++) { // get current section header pCurrSectionHeader = (IMAGE_SECTION_HEADER *)((BYTE*)&pImageNtHeader->OptionalHeader + pImageNtHeader->FileHeader.SizeOfOptionalHeader + (i * sizeof(IMAGE_SECTION_HEADER))); if(pCurrSectionHeader->SizeOfRawData != 0) { // calculate section data length (on disk) dwSectionDataLength = pCurrSectionHeader->SizeOfRawData; if(dwVirtualAddress >= pCurrSectionHeader->VirtualAddress && dwVirtualAddress < (pCurrSectionHeader->VirtualAddress + dwSectionDataLength)) { pFilePtr = pFileData; pFilePtr += pCurrSectionHeader->PointerToRawData; pFilePtr += (dwVirtualAddress - pCurrSectionHeader->VirtualAddress); return pFilePtr; } } } return NULL; } int main(int argc, char *argv[]) { DWORD dwFileSize = 0; BYTE *pFileData = NULL; IMAGE_DOS_HEADER *pImageDosHeader = NULL; IMAGE_NT_HEADERS32 *pImageNtHeader = NULL; IMAGE_NT_HEADERS64 *pImageNtHeader64 = NULL; IMAGE_DATA_DIRECTORY *pImageDataDirectory = NULL; char *pInputFilePath = NULL; char *pDllName = NULL; char szOutputFilePath[512]; IMAGE_THUNK_DATA32 ImportLookupTable32[2]; IMAGE_THUNK_DATA64 ImportLookupTable64[2]; DWORD dwTotalAddedSize = 0; IMAGE_IMPORT_DESCRIPTOR *pImageImportDescriptor = NULL; BYTE *pImportBaseAddr = NULL; DWORD dwCurrImportBlockOffset = 0; IMAGE_SECTION_HEADER *pCurrSectionHeader = NULL; IMAGE_SECTION_HEADER *pLastSectionHeader = NULL; DWORD dwNewDataVirtualAddress = 0; DWORD dwModuleCount = 0; DWORD dwNewDataFilePosition = 0; IMAGE_IMPORT_DESCRIPTOR NewDllImportDescriptors[2]; DWORD dwOrigImportSize = 0; DWORD dwNewImportDirectorySize = 0; BYTE *pNewImportDirectory = NULL; BYTE *pCopyImportPtr = NULL; DWORD dwFileAlignment = 0; DWORD dwPaddingBytes = 0; BYTE *pImportLookupTable = NULL; DWORD dwImportLookupTableSize = 0; printf("AddExeImport - www.x86matthew.com\n\n"); if(argc != 3) { printf("Usage: %s [input_exe_path] [add_dll_name]\n\n", argv[0]); return 1; } // get cmd param pInputFilePath = argv[1]; pDllName = argv[2]; printf("Opening EXE: '%s'...\n", pInputFilePath); // load dll into memory if(LoadFileIntoMemory(pInputFilePath, &pFileData, &dwFileSize) != 0) { printf("Error: Failed to load EXE into memory\n"); return 1; } // get dos header pImageDosHeader = (IMAGE_DOS_HEADER *)pFileData; if(pImageDosHeader->e_magic != 0x5A4D) { printf("Error: Invalid EXE\n"); free(pFileData); return 1; } // get nt header pImageNtHeader = (IMAGE_NT_HEADERS32 *)(pFileData + pImageDosHeader->e_lfanew); if(pImageNtHeader->Signature != IMAGE_NT_SIGNATURE) { printf("Error: Invalid EXE\n"); free(pFileData); return 1; } // check exe type if(pImageNtHeader->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) { // 64-bit printf("64-bit EXE detected\n"); pImageNtHeader64 = (IMAGE_NT_HEADERS64 *)pImageNtHeader; pImageDataDirectory = pImageNtHeader64->OptionalHeader.DataDirectory; } else if(pImageNtHeader->FileHeader.Machine == IMAGE_FILE_MACHINE_I386) { // 32-bit printf("32-bit EXE detected\n"); pImageNtHeader64 = NULL; pImageDataDirectory = pImageNtHeader->OptionalHeader.DataDirectory; } else { printf("Error: Invalid EXE\n"); free(pFileData); return 1; } // find import table pImportBaseAddr = VirtualAddressToFilePtr(pFileData, pImageNtHeader, pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); if(pImportBaseAddr == NULL) { printf("Error: Invalid EXE\n"); free(pFileData); return 1; } // find last section in file (this should be the last entry in the list but this is not necessarily the case) for(DWORD i = 0; i < pImageNtHeader->FileHeader.NumberOfSections; i++) { // get current section header pCurrSectionHeader = (IMAGE_SECTION_HEADER*)((BYTE*)&pImageNtHeader->OptionalHeader + pImageNtHeader->FileHeader.SizeOfOptionalHeader + (i * sizeof(IMAGE_SECTION_HEADER))); if(pLastSectionHeader == NULL) { // set initial value pLastSectionHeader = pCurrSectionHeader; } else { // check if this section is the last entry so far if(pCurrSectionHeader->PointerToRawData > pLastSectionHeader->PointerToRawData) { // store current value pLastSectionHeader = pCurrSectionHeader; } } } // ensure the last section was found if(pLastSectionHeader == NULL) { printf("Error: Invalid EXE\n"); free(pFileData); return 1; } // store positions of the end of the current EXE contents (virtual address + file position) dwNewDataVirtualAddress = pLastSectionHeader->VirtualAddress + pLastSectionHeader->SizeOfRawData; dwNewDataFilePosition = pLastSectionHeader->PointerToRawData + pLastSectionHeader->SizeOfRawData; // check if the exe already contains imports if(pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size != 0) { // calculate number of existing imported modules for(;;) { pImageImportDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)(pImportBaseAddr + dwCurrImportBlockOffset); if(pImageImportDescriptor->Name == 0) { // finished break; } // increase counter dwModuleCount++; // update import block offset dwCurrImportBlockOffset += sizeof(IMAGE_IMPORT_DESCRIPTOR); } } printf("Adding '%s' to import table...\n", pDllName); // allocate memory for new (enlarged) import table dwOrigImportSize = dwModuleCount * sizeof(IMAGE_IMPORT_DESCRIPTOR); dwNewImportDirectorySize = dwOrigImportSize + sizeof(NewDllImportDescriptors); pNewImportDirectory = (BYTE*)malloc(dwNewImportDirectorySize); if(pNewImportDirectory == NULL) { printf("Error: Failed to allocate memory\n"); free(pFileData); return 1; } // set import descriptor values for new dll NewDllImportDescriptors[0].Name = dwNewDataVirtualAddress + dwNewImportDirectorySize; NewDllImportDescriptors[0].OriginalFirstThunk = NewDllImportDescriptors[0].Name + (DWORD)strlen(pDllName) + 1; NewDllImportDescriptors[0].FirstThunk = NewDllImportDescriptors[0].OriginalFirstThunk; if(pImageNtHeader64 == NULL) { // 32-bit NewDllImportDescriptors[0].FirstThunk += sizeof(ImportLookupTable32); } else { // 64-bit NewDllImportDescriptors[0].FirstThunk += sizeof(ImportLookupTable64); } NewDllImportDescriptors[0].TimeDateStamp = 0; NewDllImportDescriptors[0].ForwarderChain = 0; // 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; // copy original imports to the buffer pCopyImportPtr = pNewImportDirectory; if(dwModuleCount != 0) { memcpy(pNewImportDirectory, pImportBaseAddr, dwOrigImportSize); pCopyImportPtr += dwOrigImportSize; } // append the new imported module to the end of the list memcpy((void*)pCopyImportPtr, (void*)&NewDllImportDescriptors, sizeof(NewDllImportDescriptors)); // initialise import lookup table for the new DLL (1 import - ordinal #1) - 32-bit ImportLookupTable32[0].u1.Ordinal = 0x80000001; ImportLookupTable32[1].u1.Ordinal = 0; // initialise import lookup table for the new DLL (1 import - ordinal #1) - 64-bit ImportLookupTable64[0].u1.Ordinal = 0x8000000000000001; ImportLookupTable64[1].u1.Ordinal = 0; // update IAT directory position pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = dwNewDataVirtualAddress; pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size = dwNewImportDirectorySize; // calculate total length of additional data to append dwTotalAddedSize = dwNewImportDirectorySize; dwTotalAddedSize += (DWORD)strlen(pDllName) + 1; if(pImageNtHeader64 == NULL) { // 32-bit dwTotalAddedSize += (sizeof(ImportLookupTable32) * 2); } else { // 64-bit dwTotalAddedSize += (sizeof(ImportLookupTable64) * 2); } // get file alignment value if(pImageNtHeader64 == NULL) { // 32-bit dwFileAlignment = pImageNtHeader->OptionalHeader.FileAlignment; } else { // 64-bit dwFileAlignment = pImageNtHeader64->OptionalHeader.FileAlignment; } // calculate number of bytes to pad (section data in file must be aligned) dwPaddingBytes = dwFileAlignment - (dwTotalAddedSize % dwFileAlignment); if(dwPaddingBytes == dwFileAlignment) { dwPaddingBytes = 0; } dwTotalAddedSize += dwPaddingBytes; // the last section must have read/write permissions at minimum to allow the loader to store the resolved IAT value pLastSectionHeader->Characteristics |= IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE; pLastSectionHeader->SizeOfRawData += dwTotalAddedSize; pLastSectionHeader->Misc.VirtualSize += dwTotalAddedSize; if(pImageNtHeader64 == NULL) { // 32-bit pImageNtHeader->OptionalHeader.SizeOfImage += dwTotalAddedSize; } else { // 64-bit pImageNtHeader64->OptionalHeader.SizeOfImage += dwTotalAddedSize; } // check if debug symbols are currently stored at the end of the exe if(pImageNtHeader->FileHeader.PointerToSymbolTable == dwNewDataFilePosition) { // adjust debug symbol ptr pImageNtHeader->FileHeader.PointerToSymbolTable += dwTotalAddedSize; } // get import lookup table values if(pImageNtHeader64 == NULL) { // 32-bit pImportLookupTable = (BYTE*)&ImportLookupTable32[0]; dwImportLookupTableSize = sizeof(ImportLookupTable32); } else { // 64-bit pImportLookupTable = (BYTE*)&ImportLookupTable64[0]; dwImportLookupTableSize = sizeof(ImportLookupTable64); } // write new exe to file memset(szOutputFilePath, 0, sizeof(szOutputFilePath)); _snprintf_s(szOutputFilePath, sizeof(szOutputFilePath) - 1, "%s_modified.exe", pInputFilePath); printf("Writing new file to '%s'...\n", szOutputFilePath); if(WriteToFile(szOutputFilePath, pFileData, dwFileSize, dwNewDataFilePosition, pNewImportDirectory, dwNewImportDirectorySize, pDllName, pImportLookupTable, dwImportLookupTableSize, dwPaddingBytes) != 0) { printf("Error: Failed to write new EXE\n"); free(pNewImportDirectory); free(pFileData); return 1; } printf("Finished\n"); // free memory free(pNewImportDirectory); free(pFileData); return 0; } |