BLOG

内存直接加载运行DLL文件

前言:

将DLL文件作为资源插入到自己程序中的方法,前面已经说过了。附上链接:MFC —— 资源文件释放(为了程序更简洁) 程序需要动态调用DLL文件,内存加载运行技术可以把这些DLL作为资源插入到自己的程序中。此时直接在内存中加载运行即可,不需要再将DLL释放到本地。

实现原理:

将资源加载到内存,然后把DLL文件按照映像对齐大小映射到内存中,切不可直接将DLL文件数据存储到内存中。因为根据PE结构的基础知识可知,PE文件有两个对齐字段,一个是映像对齐大小SectionAlignment,另一个是文件对齐大小FileAlignment。其中,映像对齐大小是PE文件加载到内存中所用的对齐大小,而文件对齐大小是PE文件存储在本地磁盘所用的对齐大小。一般文件对齐大小会比映像对齐大小要小,这样文件会变小,以此节省磁盘空间。然而,成功映射内存数据之后,在DLL程序中会存在硬编码数据,硬编码都是以默认的加载基址作为基址来计算的。由于DLL可以任意加载到其他进程空间中,所以DLL的加载基址并非固定不变。当改变加载基址的时候,硬编码也要随之改变,这样DLL程序才会计算正确。但是,如何才能知道需要修改哪些硬编码呢?换句话说,如何知道硬编码的位置?答案就藏在PE结构的重定位表中,重定位表记录的就是程序中所有需要修改的硬编码的相对偏移位置。根据重定位表修改硬编码数据后,这只是完成了一半的工作。DLL作为一个程序,自然也会调用其他库函数,例如MessageBox。那么DLL如何知道MessageBox函数的地址呢?它只有获取正确的调用函数地址后,方可正确调用函数。PE结构使用导入表来记录PE程序中所有引用的函数及其函数地址。在DLL映射到内存之后,需要根据导入表中的导入模块和函数名称来获取调用函数的地址。若想从导入模块中获取导出函数的地址,最简单的方式是通过GetProcAddress函数来获取(此次采用的方法)。但是为了避免调用敏感的WIN32 API函数而被杀软拦截检测,采用直接遍历PE结构导出表的方式来获取导出函数地址。

实现流程:

(1).将资源形式的dll文件加载到内存

(2).根据PE结构获取其文件映像大小

(3).根据文件映像大小再申请一块可读、可写、可执行的内存

(4).将内存DLL数据按映像对齐大小(SectionAlignment)映射到刚刚申请的内存中

(5).根据PE结构的重定位表,对需要重定位的数据进行修正

(6).根据PE结构的导入表,加载所需的DLL,获取函数地址并写入导入地址表

(7).修改DLL的加载基址为第(3)步申请的空间的首地址

(8).获取Dll的入口地址并构造DllMain函数,然后调用DllMain函数

实现代码:

//************************************

// 函数名: CStartDlg::LoadMyResource

// 返回类型: LPVOID

// 功能: 加载资源到内存

// 参数1: UINT uiResourceName 资源名

// 参数1: char * lpszResourceType 资源类型

//************************************

LPVOID CStartDlg::LoadMyResource(UINT uiResourceName, char* lpszResourceType)

{

//获取指定模块里的资源

HRSRC hRsrc = FindResource(GetModuleHandle(NULL), MAKEINTRESOURCE(uiResourceName), (LPCWSTR)lpszResourceType);

if (NULL == hRsrc)

{

MessageBox(L"获取资源失败!");

return FALSE;

}

//获取资源大小

DWORD dwSize = SizeofResource(NULL, hRsrc);

if (dwSize <= 0)

{

MessageBox(L"获取资源大小失败!");

return FALSE;

}

//将资源加载到内存里

HGLOBAL hGlobal = LoadResource(NULL, hRsrc);

if (NULL == hGlobal)

{

MessageBox(L"资源加载到内存失败!");

return FALSE;

}

//锁定资源

LPVOID lpVoid = LockResource(hGlobal);

if (NULL == lpVoid)

{

MessageBox(L"锁定资源失败!");

return FALSE;

}

return lpVoid;

}

//************************************

// 函数名: CStartDlg::MmLoadLibrary

// 返回类型: LPVOID

// 功能: 模拟LoadLibrary加载内存文件到进程中

// 参数1: LPVOID lpData 文件基址

// 参数2: BOOL IsExe 文件属性标志,TRUE表示exe文件,FALSE表示dll文件

//************************************

LPVOID CStartDlg::MmLoadLibrary(LPVOID lpData,BOOL IsExe)

{

LPVOID lpBaseAddress = NULL;

// 获取映像大小

DWORD dwSizeOfImage = GetSizeOfImage(lpData);

// 在进程中申请一个可读、可写、可执行的内存块

lpBaseAddress = VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

if (NULL == lpBaseAddress)

{

MessageBox(L"申请空间失败!");

return NULL;

}

// 将申请的空间的数据全部置0

RtlZeroMemory(lpBaseAddress, dwSizeOfImage);

// 将内存DLL数据按映像对齐大小(SectionAlignment)映射到刚刚申请的内存中

if (FALSE == MmMapFile(lpData, lpBaseAddress))

{

MessageBox(L"区段映射到内存失败!");

return NULL;

}

// 修改PE文件的重定位表信息

if (FALSE == DoRelocationTable(lpBaseAddress))

{

MessageBox(L"修复重定位失败!");

return NULL;

}

// 填写PE文件的导入表信息

if (FALSE == DoImportTable(lpBaseAddress))

{

MessageBox(L"导入表填写失败!");

return NULL;

}

// 修改PE文件的加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase

if (FALSE == SetImageBase(lpBaseAddress))

{

MessageBox(L"修改加载机制失败!");

return NULL;

}

// 调用DLL的入口函数DllMain,函数地址即为PE文件的入口点AddressOfEntryPoint

if (FALSE == CallDllMain(lpBaseAddress,IsExe))

{

MessageBox(L"调用入口函数失败!");

return NULL;

}

return lpBaseAddress;

}

//************************************

// 函数名: CStartDlg::GetSizeOfImage

// 返回类型: DWORD

// 功能: 获取文件映像大小

// 参数1: LPVOID lpData 文件基址

//************************************

DWORD CStartDlg::GetSizeOfImage(LPVOID lpData)

{

//获取Dos头

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpData;

//判断是否是有效的PE文件 0x5A4D

if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)

{

MessageBox(L"这不是一个PE文件!");

return 0;

}

//获取NT头

PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader+pDosHeader->e_lfanew);

//判断是否是有效的PE文件 0x00004550

if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)

{

MessageBox(L"这不是一个PE文件!");

return 0;

}

//获取文件映像大小

return pNtHeader->OptionalHeader.SizeOfImage;

}

//************************************

// 函数名: CStartDlg::MmMapFile

// 返回类型: BOOL

// 功能: 将内存DLL数据按映像对齐大小(SectionAlignment)映射到刚刚申请的内存中

// 参数1: LPVOID lpData 文件基址

// 参数2: LPVOID lpBaseAddress 申请的内存的首地址

//************************************

BOOL CStartDlg::MmMapFile(LPVOID lpData, LPVOID lpBaseAddress)

{

//获取Dos头

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpData;

//获取NT头

PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);

//获取所有头部+区段表的大小

DWORD dwSizeOfHeaders = pNtHeader->OptionalHeader.SizeOfHeaders;

//获取区段数量

WORD wNumberOfSections = pNtHeader->FileHeader.NumberOfSections;

//获取区段表数组的首元素

PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);

//将头部(包括区段表)拷贝到内存

RtlCopyMemory(lpBaseAddress, lpData, dwSizeOfHeaders);

LPVOID lpSrcMem = NULL;

LPVOID lpDestMem = NULL;

DWORD dwSizeOfRawData = 0;

//循环加载所有区段

for (WORD i = 0; i < wNumberOfSections; i++)

{

//过滤掉无效区段

if (0 == pSectionHeader->VirtualAddress || 0 == pSectionHeader->SizeOfRawData)

{

pSectionHeader++;

continue;

}

//获取区段在文件中的位置

lpSrcMem = (LPVOID)((DWORD)lpData + pSectionHeader->PointerToRawData);

//获取区段映射到内存中的位置

lpDestMem = (LPVOID)((DWORD)lpBaseAddress + pSectionHeader->VirtualAddress);

//获取区段在文件中的大小

dwSizeOfRawData = pSectionHeader->SizeOfRawData;

//将区段数据拷贝到内存中

RtlCopyMemory(lpDestMem, lpSrcMem, dwSizeOfRawData);

//获取下一个区段头(属性)

pSectionHeader++;

}

return TRUE;

}

//************************************

// 函数名: CStartDlg::DoRelocationTable

// 返回类型: BOOL

// 功能: 修改PE文件的重定位表信息

// 参数1: LPVOID lpBaseAddress 映像对齐后的文件基址

//************************************

BOOL CStartDlg::DoRelocationTable(LPVOID lpBaseAddress)

{

//获取Dos头

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;

//获取NT头

PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);

//获取重定位表的地址

PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pDosHeader +

pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

//注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址

//判断是否有重定位表

if ((PVOID)pReloc == (PVOID)pDosHeader)

{

//没有重定位表

return TRUE;

}

int nNumberOfReloc = 0;

WORD* pRelocData = NULL;

DWORD* pAddress = NULL;

//开始修复重定位

while (pReloc->VirtualAddress != 0 && pReloc->SizeOfBlock != 0)

{

//计算本区域(每一个描述了4KB大小的区域的重定位信息)需要修正的重定位项的数量

nNumberOfReloc = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

for (int i = 0; i < nNumberOfReloc; i++)

{

//获取IMAGE_BASE_RELOCATION结构后面的数据的地址

pRelocData = (WORD*)((DWORD)pReloc + sizeof(IMAGE_BASE_RELOCATION));

//每个WORD由两部分组成,高4位指出了重定位的类型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定义了重定位类型的取值。

//大部分重定位属性值都是0x3

//低12位是相对于IMAGE_BASE_RELOCATION中第一个元素VirtualAddress描述位置的偏移

//找出需要修正的地址

if ((WORD)(pRelocData[i] & 0xF000) == 0x3000)

{

//获取需要修正数据的地址, 按位与计算优先级比加减乘除低

pAddress = (DWORD*)((DWORD)pDosHeader + pReloc->VirtualAddress + (pRelocData[i] & 0x0FFF));

//进行修改

*pAddress += (DWORD)pDosHeader - pNtHeader->OptionalHeader.ImageBase;

}

}

//下一个重定位块

pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock);

}

return TRUE;

}

//************************************

// 函数名: CStartDlg::DoImportTable

// 返回类型: BOOL

// 功能: 填写PE文件的导入表信息

// 参数1: LPVOID lpBaseAddress 映像对齐后的文件基址

//************************************

BOOL CStartDlg::DoImportTable(LPVOID lpBaseAddress)

{

//获取Dos头

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;

//获取NT头

PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);

//获取导入表地址

PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader +

pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

char* pDllName = nullptr;

HMODULE hDll = NULL;

PIMAGE_THUNK_DATA pIat = NULL;

FARPROC pFuncAddress = NULL;

PIMAGE_IMPORT_BY_NAME pImportByName = NULL;

//循环遍历导入表

while (pImport->Name)

{

//获取导入表中的Dll名称

pDllName = (char*)((DWORD)pDosHeader + pImport->Name);

//检索Dll模块获取模块句柄

hDll = GetModuleHandleA(pDllName);

//获取失败

if (NULL == hDll)

{

//加载Dll模块获取模块句柄

hDll = LoadLibraryA(pDllName);

//加载失败

if (NULL == hDll)

{

pImport++;

continue;

}

}

//获取IAT

pIat = (PIMAGE_THUNK_DATA)((DWORD)pDosHeader + pImport->FirstThunk);

//遍历IAT中函数

while (pIat->u1.Ordinal)

{

//判断导入的函数是名称导入还是序号导入

//判断最高位是否为1,如果是1那么是序号导入

if (pIat->u1.Ordinal & 0x80000000)

{

//获取函数地址

pFuncAddress = GetProcAddress(hDll, (LPCSTR)(pIat->u1.Ordinal & 0x7FFFFFFF));

}

//名称导入

else

{

//获取IMAGE_IMPORT_BY_NAME结构

pImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pDosHeader + pIat->u1.AddressOfData);

//获取函数地址

pFuncAddress = GetProcAddress(hDll, pImportByName->Name);

}

//将函数地址填入到IAT中

pIat->u1.Function = (DWORD)pFuncAddress;

pIat++;

}

pImport++;

}

return TRUE;

}

//************************************

// 函数名: CStartDlg::SetImageBase

// 返回类型: BOOL

// 功能: 修改PE文件的加载基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase

// 参数1: LPVOID lpBaseAddress 映像对齐后的文件基址

//************************************

BOOL CStartDlg::SetImageBase(LPVOID lpBaseAddress)

{

//获取Dos头

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;

//获取NT头

PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);

//修改默认加载基址

pNtHeader->OptionalHeader.ImageBase = (DWORD)lpBaseAddress;

return TRUE;

}

//************************************

// 函数名: CStartDlg::CallDllMain

// 返回类型: BOOL

// 功能: 调用PE文件的入口函数

// 参数1: LPVOID lpBaseAddress 映像对齐后的文件基址

// 参数2: BOOL IsExe 文件属性标志,TRUE表示exe文件,FALSE表示dll文件

//************************************

BOOL CStartDlg::CallDllMain(LPVOID lpBaseAddress,BOOL IsExe)

{

//定义函数指针变量

typedef_DllMain DllMain = NULL;

typedef_wWinMain MyWinMain = NULL;

//获取Dos头

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;

//获取NT头

PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);

BOOL bRet = TRUE;

//如果是exe文件

if (IsExe)

{

MessageBox(_T("有问题,待解决"));

//MyWinMain = (typedef_wWinMain)((DWORD)pDosHeader + pNtHeader->OptionalHeader.AddressOfEntryPoint+0xF8D);

//bRet = MyWinMain((HINSTANCE)lpBaseAddress, NULL, NULL, SW_SHOWNORMAL);

}

//dll 文件

else {

DllMain = (typedef_DllMain)((DWORD)pDosHeader + pNtHeader->OptionalHeader.AddressOfEntryPoint);

//调用入口函数,附加进程DLL_PROCESS_ATTACH

bRet = DllMain((HINSTANCE)lpBaseAddress, DLL_PROCESS_ATTACH, NULL);

}

return bRet;

}