以前学重载内核时学到了手动加载一个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
// 复制所有的节