手动加载DLL(PE文件)
2017-04-30 21:28
495 查看
以前学重载内核时学到了手动加载一个PE文件,想在Ring3层也实现一遍,不过在GitHub上看到有现成的源码了就不自己写了。本篇文章就分析一下这个mmLoader,看看怎么实现手动加载PE文件
阅读前需要了解PE文件结构,还不了解的自行恶补
这个库为了实现注入ShellCode,自己实现了memset等常用库函数,还自己定义了要用到的库函数表。实际使用时,如果不用注入ShellCode使用常规的库函数就好
虽然这个库是用来加载DLL的,修改一下也可以用来加载EXE
加载PE文件的主要步骤:
将PE头和各个节映射到内存
重定位
修复IAT
调用模块入口点
重载模块的话会有其他问题,比如全局变量在内存中有两份副本。其实也很好解决,重定位时定位到原模块就行了。不过用来绕hook未免小题大做了,针对性地用汇编绕过部分hook就行了
隐藏注入模块
你手动加载的模块,操作系统根本不知道它的存在,模块列表里没有,也不会引起任何回调
获取模块的原始内容,比如内核中的SSDT
阅读前需要了解PE文件结构,还不了解的自行恶补
这个库为了实现注入ShellCode,自己实现了memset等常用库函数,还自己定义了要用到的库函数表。实际使用时,如果不用注入ShellCode使用常规的库函数就好
虽然这个库是用来加载DLL的,修改一下也可以用来加载EXE
加载PE文件的主要步骤:
将PE头和各个节映射到内存
重定位
修复IAT
调用模块入口点
映射文件到内存
这一步就是把PE文件的内容读到内存相应的位置,要注意文件中各节的对齐和内存中的对齐是不一样的/// <summary> /// Maps all the sections. /// </summary> /// <param name="pMemModule">The <see cref="MemModule" /> instance.</param> /// <returns>True if successful.</returns> BOOL MapMemModuleSections(PMEM_MODULE pMemModule, LPVOID lpPeModuleBuffer) { // Validate if (NULL == pMemModule || NULL == pMemModule->pNtFuncptrsTable || NULL == lpPeModuleBuffer) return FALSE; // Function pointer // VirtualAlloc的函数指针,ShellCode用的,实际使用时直接写VirtualAlloc即可,后面类似的不再赘述 Type_VirtualAlloc pfnVirtualAlloc = (Type_VirtualAlloc)(pMemModule->pNtFuncptrsTable->pfnVirtualAlloc); Type_VirtualFree pfnVirtualFree = (Type_VirtualFree)(pMemModule->pNtFuncptrsTable->pfnVirtualFree); // Convert to IMAGE_DOS_HEADER // PE文件头部指针,即DOS头 PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)(lpPeModuleBuffer); // Get the pointer to IMAGE_NT_HEADERS // NT头,MakePointer是个宏,返回某指针+某偏移量后的新指针 PIMAGE_NT_HEADERS pImageNtHeader = MakePointer( PIMAGE_NT_HEADERS, pImageDosHeader, pImageDosHeader->e_lfanew); // Get the section count int nNumberOfSections = pImageNtHeader->FileHeader.NumberOfSections; // Get the section header // 节头 PIMAGE_SECTION_HEADER pImageSectionHeader = MakePointer( PIMAGE_SECTION_HEADER, pImageNtHeader, sizeof(IMAGE_NT_HEADERS)); // Find the last section limit // 计算整个模块尺寸 DWORD dwImageSizeLimit = 0; for (int i = 0; i < nNumberOfSections; ++i) { if (0 != pImageSectionHeader[i].VirtualAddress) { if (dwImageSizeLimit < (pImageSectionHeader[i].VirtualAddress + pImageSectionHeader[i].SizeOfRawData)) dwImageSizeLimit = pImageSectionHeader[i].VirtualAddress + pImageSectionHeader[i].SizeOfRawData; } } // Align the last image size limit to the page size // 按页对齐 dwImageSizeLimit = (dwImageSizeLimit + pMemModule->dwPageSize - 1) & ~(pMemModule->dwPageSize - 1); // Reserve virtual memory // 优先使用ImageBase作为模块基址,分配一块内存 LPVOID lpBase = pfnVirtualAlloc( (LPVOID)(pImageNtHeader->OptionalHeader.ImageBase), dwImageSizeLimit, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); // Failed to reserve space at ImageBase, then it's up to the system // 这个基址不能使用,让系统随机选另一个基址(后面需要重定位) if (NULL == lpBase) { // Reserver memory in arbitrary address lpBase = pfnVirtualAlloc( NULL, dwImageSizeLimit, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); // Failed again, return if (NULL == lpBase) { pMemModule->dwErrorCode = MMEC_ALLOCATED_MEMORY_FAILED; return FALSE; } } // Commit memory for PE header LPVOID pDest = pfnVirtualAlloc(lpBase, pImageNtHeader->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE); if (!pDest) { pMemModule->dwErrorCode = MMEC_ALLOCATED_MEMORY_FAILED; return FALSE; } // Copy the data of PE header to the memory allocated // 复制PE头 mml_memmove(pDest, lpPeModuleBuffer, pImageNtHeader->OptionalHeader.SizeOfHeaders); // Store the base address of this module. pMemModule->lpBase = pDest; pMemModule->dwSizeOfImage = pImageNtHeader->OptionalHeader.SizeOfImage; pMemModule->bLoadOk = TRUE; // Get the DOS header, NT header and Section header from the new PE header buffer pImageDosHeader = (PIMAGE_DOS_HEADER)pDest; pImageNtHeader = MakePointer(PIMAGE_NT_HEADERS, pImageDosHeader, pImageDosHeader->e_lfanew); pImageSectionHeader = MakePointer(PIMAGE_SECTION_HEADER, pImageNtHeader, sizeof(IMAGE_NT_HEADERS)); // Map all section data into the memory // 复制所有的节 LPVOID pSectionBase = NULL; LPVOID pSectionDataSource = NULL; for (int i = 0; i < nNumberOfSections; ++i) { if (0 != pImageSectionHeader[i].VirtualAddress) { // Get the section base pSectionBase = MakePointer(LPVOID, lpBase, pImageSectionHeader[i].VirtualAddress); if (0 == pImageSectionHeader[i].SizeOfRawData) { if (pImageNtHeader->OptionalHeader.SectionAlignment > 0) { // If the size is zero, but the section alignment is not zero then allocate memory with the aligment pDest = pfnVirtualAlloc(pSectionBase, pImageNtHeader->OptionalHeader.SectionAlignment, MEM_COMMIT, PAGE_READWRITE); if (NULL == pDest) { pMemModule->dwErrorCode = MMEC_ALLOCATED_MEMORY_FAILED; return FALSE; } // Always use position from file to support alignments smaller than page size. mml_memset(pSectionBase, 0, pImageNtHeader->OptionalHeader.SectionAlignment); } } else { // Commit this section to target address pDest = pfnVirtualAlloc(pSectionBase, pImageSectionHeader[i].SizeOfRawData, MEM_COMMIT, PAGE_READWRITE); if (NULL == pDest) { pMemModule->dwErrorCode = MMEC_ALLOCATED_MEMORY_FAILED; return FALSE; } // Get the section data source and copy the data to the section buffer pSectionDataSource = MakePointer(LPVOID, lpPeModuleBuffer, pImageSectionHeader[i].PointerToRawData); mml_memmove(pDest, pSectionDataSource, pImageSectionHeader[i].SizeOfRawData); } // Get next section header pImageSectionHeader[i].Misc.PhysicalAddress = (DWORD)(ULONGLONG)pDest; } } return TRUE; }
重定位
代码中那些绝对地址是以模块基址=ImageBase为前提硬编码的,如果模块加载的基址不是ImageBase指定的基址,则需要重定位新地址=旧地址-ImageBase+实际模块基址,只要算出
实际模块基址-ImageBase,重定位时加上偏移量就行了
/// <summary> /// Relocates the module. /// </summary> /// <param name="pMemModule">The <see cref="MemModule" /> instance.</param> /// <returns>True if successful.</returns> BOOL RelocateModuleBase(PMEM_MODULE pMemModule) { // Validate the parameters if (NULL == pMemModule || NULL == pMemModule->pImageDosHeader) return FALSE; PIMAGE_NT_HEADERS pImageNtHeader = MakePointer( PIMAGE_NT_HEADERS, pMemModule->pImageDosHeader, pMemModule->pImageDosHeader->e_lfanew); // Get the delta of the real image base with the predefined // 计算偏移量 LONGLONG lBaseDelta = ((PUINT8)pMemModule->iBase - (PUINT8)pImageNtHeader->OptionalHeader.ImageBase); // This module has been loaded to the ImageBase, no need to do relocation if (0 == lBaseDelta) return TRUE; if (0 == pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress || 0 == pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size) return TRUE; // 重定位表 PIMAGE_BASE_RELOCATION pImageBaseRelocation = MakePointer(PIMAGE_BASE_RELOCATION, pMemModule->lpBase, pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); if (NULL == pImageBaseRelocation) { pMemModule->dwErrorCode = MMEC_INVALID_RELOCATION_BASE; return FALSE; } while (0 != (pImageBaseRelocation->VirtualAddress + pImageBaseRelocation->SizeOfBlock)) { PWORD pRelocationData = MakePointer(PWORD, pImageBaseRelocation, sizeof(IMAGE_BASE_RELOCATION)); int NumberOfRelocationData = (pImageBaseRelocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); for (int i = 0; i < NumberOfRelocationData; i++) { if (IMAGE_REL_BASED_HIGHLOW == (pRelocationData[i] >> 12)) { // 需要重定位的地址 PDWORD pAddress = (PDWORD)(pMemModule->iBase + pImageBaseRelocation->VirtualAddress + (pRelocationData[i] & 0x0FFF)); // 重定位 *pAddress += (DWORD)lBaseDelta; } #ifdef _WIN64 if (IMAGE_REL_BASED_DIR64 == (pRelocationData[i] >> 12)) { PULONGLONG pAddress = (PULONGLONG)(pMemModule->iBase + pImageBaseRelocation->VirtualAddress + (pRelocationData[i] & 0x0FFF)); *pAddress += lBaseDelta; } #endif } pImageBaseRelocation = MakePointer(PIMAGE_BASE_RELOCATION, pImageBaseRelocation, pImageBaseRelocation->SizeOfBlock); } return TRUE; }
修复IAT
这一步载入本模块依赖的模块,并将本模块的IAT定位到依赖模块的相应的函数上/// <summary> /// Resolves the import table. /// </summary> /// <param name="pMemModule">The <see cref="MemModule" /> instance.</param> /// <returns>True if successful.</returns> BOOL ResolveImportTable(PMEM_MODULE pMemModule) { if (NULL == pMemModule || NULL == pMemModule->pNtFuncptrsTable || NULL == pMemModule->pImageDosHeader) return FALSE; Type_GetModuleHandleA pfnGetModuleHandleA = (Type_GetModuleHandleA)(pMemModule->pNtFuncptrsTable->pfnGetModuleHandleA); Type_LoadLibraryA pfnLoadLibraryA = (Type_LoadLibraryA)(pMemModule->pNtFuncptrsTable->pfnLoadLibraryA); Type_GetProcAddress pfnGetProcAddress = (Type_GetProcAddress)(pMemModule->pNtFuncptrsTable->pfnGetProcAddress); PIMAGE_NT_HEADERS pImageNtHeader = MakePointer(PIMAGE_NT_HEADERS, pMemModule->pImageDosHeader, pMemModule->pImageDosHeader->e_lfanew); if (pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0 || pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size == 0) return TRUE; PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor = MakePointer(PIMAGE_IMPORT_DESCRIPTOR, pMemModule->lpBase, pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); // 遍历导入的模块 for (; pImageImportDescriptor->Name; pImageImportDescriptor++) { // Get the dependent module name // 依赖的模块名 PCHAR pDllName = MakePointer(PCHAR, pMemModule->lpBase, pImageImportDescriptor->Name); // Get the dependent module handle // 取依赖的模块句柄,其实直接用LoadLibrary就行了,这里两个函数都用了而且释放模块时也没有FreeLibrary,会造成内存泄漏 HMODULE hMod = pfnGetModuleHandleA(pDllName); // Load the dependent module if (NULL == hMod) hMod = pfnLoadLibraryA(pDllName); // Failed if (NULL == hMod) { pMemModule->dwErrorCode = MMEC_IMPORT_MODULE_FAILED; return FALSE; } // Original thunk PIMAGE_THUNK_DATA pOriginalThunk = NULL; if (pImageImportDescriptor->OriginalFirstThunk) pOriginalThunk = MakePointer(PIMAGE_THUNK_DATA, pMemModule->lpBase, pImageImportDescriptor->OriginalFirstThunk); else pOriginalThunk = MakePointer(PIMAGE_THUNK_DATA, pMemModule->lpBase, pImageImportDescriptor->FirstThunk); // IAT thunk PIMAGE_THUNK_DATA pIATThunk = MakePointer(PIMAGE_THUNK_DATA, pMemModule->lpBase, pImageImportDescriptor->FirstThunk); // 遍历导入的函数 for (; pOriginalThunk->u1.AddressOfData; pOriginalThunk++, pIATThunk++) { // 取函数地址 FARPROC lpFunction = NULL; if (IMAGE_SNAP_BY_ORDINAL(pOriginalThunk->u1.Ordinal)) { lpFunction = pfnGetProcAddress(hMod, (LPCSTR)IMAGE_ORDINAL(pOriginalThunk->u1.Ordinal)); } else { PIMAGE_IMPORT_BY_NAME pImageImportByName = MakePointer( PIMAGE_IMPORT_BY_NAME, pMemModule->lpBase, pOriginalThunk->u1.AddressOfData); lpFunction = pfnGetProcAddress(hMod, (LPCSTR)&(pImageImportByName->Name)); } // Write into IAT // 写到IAT #ifdef _WIN64 pIATThunk->u1.Function = (ULONGLONG)lpFunction; #else pIATThunk->u1.Function = (DWORD)lpFunction; #endif } } return TRUE; }
设置各节内存保护
没有这一步也无所谓,为了安全和严谨就加上这一步吧/// <summary> /// Sets the memory protected stats of all the sections. /// </summary> /// <param name="pMemModule">The <see cref="MemModule" /> instance.</param> /// <returns>True if successful.</returns> BOOL SetMemProtectStatus(PMEM_MODULE pMemModule) { if (NULL == pMemModule || NULL == pMemModule->pNtFuncptrsTable) return FALSE; // 索引含义:可执行、可读、可写,根据这3个属性决定内存保护 int ProtectionMatrix[2][2][2] = { { // not executable { PAGE_NOACCESS, PAGE_WRITECOPY }, { PAGE_READONLY, PAGE_READWRITE }, }, { // executable { PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, { PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, }, }; Type_VirtualProtect pfnVirtualProtect = (Type_VirtualProtect)(pMemModule->pNtFuncptrsTable->pfnVirtualProtect); Type_VirtualFree pfnVirtualFree = (Type_VirtualFree)(pMemModule->pNtFuncptrsTable->pfnVirtualFree); PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)(pMemModule->lpBase); ULONGLONG ulBaseHigh = 0; #ifdef _WIN64 ulBaseHigh = (pMemModule->iBase & 0xffffffff00000000); #endif PIMAGE_NT_HEADERS pImageNtHeader = MakePointer( PIMAGE_NT_HEADERS, pImageDosHeader, pImageDosHeader->e_lfanew); int nNumberOfSections = pImageNtHeader->FileHeader.NumberOfSections; PIMAGE_SECTION_HEADER pImageSectionHeader = MakePointer( PIMAGE_SECTION_HEADER, pImageNtHeader, sizeof(IMAGE_NT_HEADERS)); // 遍历各节 for (int idxSection = 0; idxSection < nNumberOfSections; idxSection++) { DWORD protectFlag = 0; DWORD oldProtect = 0; BOOL isExecutable = FALSE; BOOL isReadable = FALSE; BOOL isWritable = FALSE; BOOL isNotCache = FALSE; ULONGLONG dwSectionBase = (pImageSectionHeader[idxSection].Misc.PhysicalAddress | ulBaseHigh); DWORD dwSecionSize = pImageSectionHeader[idxSection].SizeOfRawData; if (0 == dwSecionSize) continue; // This section is in this page // 接下来根据节属性设置页面内存保护 DWORD dwSectionCharacteristics = pImageSectionHeader[idxSection].Characteristics; // Discardable if (dwSectionCharacteristics & IMAGE_SCN_MEM_DISCARDABLE) { pfnVirtualFree((LPVOID)dwSectionBase, dwSecionSize, MEM_DECOMMIT); continue; } // Executable if (dwSectionCharacteristics & IMAGE_SCN_MEM_EXECUTE) isExecutable = TRUE; // Readable if (dwSectionCharacteristics & IMAGE_SCN_MEM_READ) isReadable = TRUE; // Writable if (dwSectionCharacteristics & IMAGE_SCN_MEM_WRITE) isWritable = TRUE; if (dwSectionCharacteristics & IMAGE_SCN_MEM_NOT_CACHED) isNotCache = TRUE; protectFlag = ProtectionMatrix[isExecutable][isReadable][isWritable]; if (isNotCache) protectFlag |= PAGE_NOCACHE; // 设置内存保护 if (!pfnVirtualProtect((LPVOID)dwSectionBase, dwSecionSize, protectFlag, &oldProtect)) { pMemModule->dwErrorCode = MMEC_PROTECT_SECTION_FAILED; return FALSE; } } return TRUE; }
调用TLS回调和模块入口点
这一步也是可选的,不过如果不做有些全局变量可能未初始化。做了的话有可能失败,因为操作系统不认我们手动加载的模块,调用某些函数有可能失败…/// <summary> /// Executes the TLS callback function. /// </summary> /// <param name="pMemModule">The <see cref="MemModule" /> instance.</param> /// <returns>True if successful.</returns> BOOL ExecuteTLSCallback(PMEM_MODULE pMemModule) { if (NULL == pMemModule || NULL == pMemModule->pImageDosHeader) return FALSE; PIMAGE_NT_HEADERS pImageNtHeader = MakePointer( PIMAGE_NT_HEADERS, pMemModule->pImageDosHeader, pMemModule->pImageDosHeader->e_lfanew); IMAGE_DATA_DIRECTORY imageDirectoryEntryTls = pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]; if (imageDirectoryEntryTls.VirtualAddress == 0) return TRUE; PIMAGE_TLS_DIRECTORY tls = (PIMAGE_TLS_DIRECTORY)(pMemModule->iBase + imageDirectoryEntryTls.VirtualAddress); PIMAGE_TLS_CALLBACK* callback = (PIMAGE_TLS_CALLBACK *)tls->AddressOfCallBacks; if (callback) { while (*callback) { (*callback)((LPVOID)pMemModule->hModule, DLL_PROCESS_ATTACH, NULL); callback++; } } return TRUE; } /// <summary> /// Calls the module entry. /// </summary> /// <param name="pMemModule">The <see cref="MemModule" /> instance.</param> /// <param name="dwReason">The reason of the calling.</param> /// <returns>True if successful.</returns> BOOL CallModuleEntry(PMEM_MODULE pMemModule, DWORD dwReason) { if (NULL == pMemModule || NULL == pMemModule->pImageDosHeader) return FALSE; PIMAGE_NT_HEADERS pImageNtHeader = MakePointer( PIMAGE_NT_HEADERS, pMemModule->pImageDosHeader, pMemModule->pImageDosHeader->e_lfanew); Type_DllMain pfnModuleEntry = NULL; pfnModuleEntry = MakePointer( Type_DllMain, pMemModule->lpBase, pImageNtHeader->OptionalHeader.AddressOfEntryPoint); if (NULL == pfnModuleEntry) { pMemModule->dwErrorCode = MMEC_INVALID_ENTRY_POINT; return FALSE; } return pfnModuleEntry(pMemModule->hModule, dwReason, NULL); }
总流程
这个函数就是把之前分析过的函数调用一遍实现加载PE文件。有些不重要而且看名字就知道干什么的函数就不分析了BOOL __stdcall LoadMemModule(PMEM_MODULE pMemModule, LPVOID lpPeModuleBuffer, BOOL bCallEntry) { if (NULL == pMemModule || NULL == pMemModule->pNtFuncptrsTable || NULL == lpPeModuleBuffer) return FALSE; pMemModule->dwErrorCode = ERROR_SUCCESS; // Verify file format if (FALSE == IsValidPEFormat(pMemModule, lpPeModuleBuffer)) { return FALSE; } // Map PE header and section table into memory if (FALSE == MapMemModuleSections(pMemModule, lpPeModuleBuffer)) return FALSE; // Relocate the module base if (FALSE == RelocateModuleBase(pMemModule)) { UnmapMemModule(pMemModule); return FALSE; } // Resolve the import table if (FALSE == ResolveImportTable(pMemModule)) { UnmapMemModule(pMemModule); return FALSE; } pMemModule->dwCrc = mml_getcrc32( 0, pMemModule->lpBase, pMemModule->dwSizeOfImage); // Correct the protect flag for all section pages if (FALSE == SetMemProtectStatus(pMemModule)) { UnmapMemModule(pMemModule); return FALSE; } if (FALSE == ExecuteTLSCallback(pMemModule)) return FALSE; if (bCallEntry) { if (FALSE == CallModuleEntry(pMemModule, DLL_PROCESS_ATTACH)) { // failed to call entry point, // clean resource, return false UnmapMemModule(pMemModule); return FALSE; } } return TRUE; }
应用
重载某模块,绕过各种hook重载模块的话会有其他问题,比如全局变量在内存中有两份副本。其实也很好解决,重定位时定位到原模块就行了。不过用来绕hook未免小题大做了,针对性地用汇编绕过部分hook就行了
隐藏注入模块
你手动加载的模块,操作系统根本不知道它的存在,模块列表里没有,也不会引起任何回调
获取模块的原始内容,比如内核中的SSDT
相关文章推荐
- PE文件和COFF文件格式分析——导出表的应用——通过导出表隐性加载DLL
- DLL系列------编程实现感染PE文件加载DLL
- [置顶] 【PE】Windows平台下为可执行文件或动态库dll添加版本信息
- 当加载XXXX.dll文件时出错的解决方法
- 【玩转.Net MF – 05】加载文件系统中的Pe文件 推荐
- “未能加载文件或程序集“AForge.Video.FFMPEG.dll”或它的某一个依赖项。找不到指定的模块” 解决方法
- PE文件结构及其加载机制(二)
- PE文件结构及其加载机制(四)
- ATL编写的控件中动态调用加载dll文件,并调用函数
- 未能加载文件或程序集“System.Data.SQLite.DLL”或它的某一个依赖项
- 内存加载SkinH.dll,不释放文件
- PE头的应用---插入代码到EXE或DLL文件中
- 另类DLL加载方法 —— PEAnalyais
- c# 动态加载dll文件,并实现调用其中的方法
- C#中调用DLL时未能加载文件或程序集错误处理方法
- 手动加入PE文件数字签名信息及格式具体解释图之下(历史代码,贴出学习)
- JavaWeb项目中dll文件动态加载方法解析(详细步骤)
- VC++通过动态生成并加载DLL,实现可执行文件的自删除
- 关于java加载dll文件一直报Unable to load library的问题
- 怎样获取exe,dll,ocx等PE文件的版本号