您的位置:首页 > 其它

手动加载DLL(PE文件)

2017-04-30 21:28 495 查看
以前学重载内核时学到了手动加载一个PE文件,想在Ring3层也实现一遍,不过在GitHub上看到有现成的源码了就不自己写了。本篇文章就分析一下这个mmLoader,看看怎么实现手动加载PE文件

阅读前需要了解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文件 加载DLL