大部分代碼取自網絡,重新修改、編輯後發佈
//MemLoadDll.h
#pragma once
typedef BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD, LPVOID );
class CMemLoadDll
{
public:
CMemLoadDll();
~CMemLoadDll();
BOOL MemLoadLibrary( void* lpFileData , int DataLength); // Dll file data buffer
FARPROC MemGetProcAddress(LPCSTR lpProcName);
private:
BOOL isLoadOk;
BOOL CheckDataValide(void* lpFileData, int DataLength);
int CalcTotalImageSize();
void CopyDllDatas(void* pDest, void* pSrc);
BOOL FillRavAddress(void* pBase);
void DoRelocation(void* pNewBase);
int GetAlignedSize(int Origin, int Alignment);
private:
ProcDllMain pDllMain;
private:
DWORD pImageBase;
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNTHeader;
PIMAGE_SECTION_HEADER pSectionHeader;
};
//MemLoadDll.cpp
#include "stdafx.h"
#include "MemLoadDll.h"
CMemLoadDll::CMemLoadDll()
{
isLoadOk = FALSE;
pImageBase = NULL;
pDllMain = NULL;
}
CMemLoadDll::~CMemLoadDll()
{
if(isLoadOk) {
ASSERT(pImageBase != NULL);
ASSERT(pDllMain != NULL);
//脫鉤,準備卸載dll
pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);
VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);
}
}
//MemLoadLibrary函數從內存緩衝區數據中加載一個dll到當前進程的地址空間,缺省位置0x10000000
//返回值: 成功返回TRUE , 失敗返回FALSE
//lpFileData: 存放dll文件數據的緩衝區
//DataLength: 緩衝區中數據的總長度
BOOL CMemLoadDll::MemLoadLibrary(void* lpFileData, int DataLength)
{
if(pImageBase != NULL) {
return FALSE; //已經加載一個dll,還沒有釋放,不能加載新的dll
}
//檢查數據有效性,並初始化
if(!CheckDataValide(lpFileData, DataLength))return FALSE;
//計算所需的加載空間
int ImageSize = CalcTotalImageSize();
if(ImageSize == 0) return FALSE;
// 分配虛擬內存
void *pMemoryAddress = VirtualAlloc((LPVOID)0x10000000, ImageSize,
MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if(pMemoryAddress == NULL) return FALSE;
else {
CopyDllDatas(pMemoryAddress, lpFileData); //複製dll數據,並對齊每個段
//重定位信息
if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress >0
&& pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size>0) {
DoRelocation(pMemoryAddress);
}
//填充引入地址表
if(!FillRavAddress(pMemoryAddress)) { //修正引入地址表失敗
VirtualFree(pMemoryAddress,0,MEM_RELEASE);
return FALSE;
}
//修改頁屬性。應該根據每個頁的屬性單獨設置其對應內存頁的屬性。這裏簡化一下。
//統一設置成一個屬性PAGE_EXECUTE_READWRITE
unsigned long old;
VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE,&old);
}
//修正基地址
pNTHeader->OptionalHeader.ImageBase = (DWORD)pMemoryAddress;
//接下來要調用一下dll的入口函數,做初始化工作。
pDllMain = (ProcDllMain)(pNTHeader->OptionalHeader.AddressOfEntryPoint +(DWORD) pMemoryAddress);
BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH,0);
if(!InitResult) { //初始化失敗
pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0);
VirtualFree(pMemoryAddress,0,MEM_RELEASE);
pDllMain = NULL;
return FALSE;
}
isLoadOk = TRUE;
pImageBase = (DWORD)pMemoryAddress;
return TRUE;
}
//MemGetProcAddress函數從dll中獲取指定函數的地址
//返回值: 成功返回函數地址 , 失敗返回NULL
//lpProcName: 要查找函數的名字或者序號
FARPROC CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName)
{
if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||
pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)
return NULL;
if(!isLoadOk) return NULL;
DWORD OffsetStart = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
DWORD Size = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pImageBase + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
int iBase = pExport->Base;
int iNumberOfFunctions = pExport->NumberOfFunctions;
int iNumberOfNames = pExport->NumberOfNames; //<= iNumberOfFunctions
LPDWORD pAddressOfFunctions = (LPDWORD)(pExport->AddressOfFunctions + pImageBase);
LPWORD pAddressOfOrdinals = (LPWORD)(pExport->AddressOfNameOrdinals + pImageBase);
LPDWORD pAddressOfNames = (LPDWORD)(pExport->AddressOfNames + pImageBase);
int iOrdinal = -1;
if(((DWORD)lpProcName & 0xFFFF0000) == 0) { //IT IS A ORDINAL!
iOrdinal = (DWORD)lpProcName & 0x0000FFFF - iBase;
} else { //use name
int iFound = -1;
for(int i=0; i<iNumberOfNames; i++) {
char* pName= (char* )(pAddressOfNames[i] + pImageBase);
if(strcmp(pName, lpProcName) == 0) {
iFound = i;
break;
}
}
if(iFound >= 0) {
iOrdinal = (int)(pAddressOfOrdinals[iFound]);
}
}
if(iOrdinal < 0 || iOrdinal >= iNumberOfFunctions ) return NULL;
else {
DWORD pFunctionOffset = pAddressOfFunctions[iOrdinal];
if(pFunctionOffset > OffsetStart && pFunctionOffset < (OffsetStart+Size))//maybe Export Forwarding
return NULL;
else return (FARPROC)(pFunctionOffset + pImageBase);
}
}
// 重定向PE用到的地址
void CMemLoadDll::DoRelocation( void *NewBase)
{
/* 重定位表的結構:
// DWORD sectionAddress, DWORD size (包括本節需要重定位的數據)
// 例如 1000節需要修正5個重定位數據的話,重定位表的數據是
// 00 10 00 00 14 00 00 00 xxxx xxxx xxxx xxxx xxxx 0000
// ----------- ----------- ----
// 給出節的偏移 總尺寸=8+6*2 需要修正的地址 用於對齊4字節
// 重定位表是若干個相連,如果address 和 size都是0 表示結束
// 需要修正的地址是12位的,高4位是形態字,intel cpu下是3
*/
//假設NewBase是0x600000,而文件中設置的缺省ImageBase是0x400000,則修正偏移量就是0x200000
DWORD Delta = (DWORD)NewBase - pNTHeader->OptionalHeader.ImageBase;
//注意重定位表的位置可能和硬盤文件中的偏移地址不同,應該使用加載後的地址
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)NewBase
+ pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) { //開始掃描重定位表
WORD *pLocData = (WORD *)((int)pLoc + sizeof(IMAGE_BASE_RELOCATION));
//計算本節需要修正的重定位項(地址)的數目
int NumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/sizeof(WORD);
for( int i=0 ; i < NumberOfReloc; i++) {
if( (DWORD)(pLocData[i] & 0xF000) == 0x00003000) { //這是一個需要修正的地址
// 舉例:
// pLoc->VirtualAddress = 0x1000;
// pLocData[i] = 0x313E; 表示本節偏移地址0x13E處需要修正
// 因此 pAddress = 基地址 + 0x113E
// 裏面的內容是 A1 ( 0c d4 02 10) 彙編代碼是: mov eax , [1002d40c]
// 需要修正1002d40c這個地址
DWORD * pAddress = (DWORD *)((unsigned long)NewBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
*pAddress += Delta;
}
}
//轉移到下一個節進行處理
pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc->SizeOfBlock);
}
}
//填充引入地址表
BOOL CMemLoadDll::FillRavAddress(void *pImageBase)
{
// 引入表實際上是一個 IMAGE_IMPORT_DESCRIPTOR 結構數組,全部是0表示結束
// 數組定義如下:
//
// DWORD OriginalFirstThunk; // 0表示結束,否則指向未綁定的IAT結構數組
// DWORD TimeDateStamp;
// DWORD ForwarderChain; // -1 if no forwarders
// DWORD Name; // 給出dll的名字
// DWORD FirstThunk; // 指向IAT結構數組的地址(綁定後,這些IAT裏面就是實際的函數地址)
unsigned long Offset = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ;
if(Offset == 0) return TRUE; //No Import Table
PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned long) pImageBase + Offset);
while(pID->Characteristics != 0 ) {
PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->FirstThunk);
PIMAGE_THUNK_DATA pOriginalIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->OriginalFirstThunk);
//獲取dll的名字
char buf[256]; //dll name;
BYTE* pName = (BYTE*)((unsigned long)pImageBase + pID->Name);
int i = 0;
for(; i<256; i++) {
if(pName[i] == 0)break;
buf[i] = pName[i];
}
if(i>=256) return FALSE; // bad dll name
else buf[i] = 0;
_bstr_t bstrTmp(buf);
LPCWSTR strTmp = (LPTSTR)bstrTmp;
HMODULE hDll = GetModuleHandle(strTmp);
if(hDll == NULL)return FALSE; //NOT FOUND DLL
//獲取DLL中每個導出函數的地址,填入IAT
//每個IAT結構是 :
// union { PBYTE ForwarderString;
// PDWORD Function;
// DWORD Ordinal;
// PIMAGE_IMPORT_BY_NAME AddressOfData;
// } u1;
// 長度是一個DWORD ,正好容納一個地址。
for(int i=0; ; i++) {
if(pOriginalIAT[i].u1.Function == 0)break;
FARPROC lpFunction = NULL;
if(pOriginalIAT[i].u1.Ordinal & IMAGE_ORDINAL_FLAG) { //這裏的值給出的是導出序號
lpFunction = GetProcAddress(hDll, (LPCSTR)(pOriginalIAT[i].u1.Ordinal & 0x0000FFFF));
} else { //按照名字導入
//獲取此IAT項所描述的函數名稱
PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)
((DWORD)pImageBase + (DWORD)(pOriginalIAT[i].u1.AddressOfData));
// if(pByName->Hint !=0)
// lpFunction = GetProcAddress(hDll, (LPCSTR)pByName->Hint);
// else
lpFunction = GetProcAddress(hDll, (char *)pByName->Name);
}
if(lpFunction != NULL) { //找到了!
pRealIAT[i].u1.Function = (DWORD) lpFunction;
} else return FALSE;
}
//move to next
pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR));
}
return TRUE;
}
//CheckDataValide函數用於檢查緩衝區中的數據是否有效的dll文件
//返回值: 是一個可執行的dll則返回TRUE,否則返回FALSE。
//lpFileData: 存放dll數據的內存緩衝區
//DataLength: dll文件的長度
BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength)
{
//檢查長度
if(DataLength < sizeof(IMAGE_DOS_HEADER)) return FALSE;
pDosHeader = (PIMAGE_DOS_HEADER)lpFileData; // DOS頭
//檢查dos頭的標記
if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) return FALSE; //0x5A4D : MZ
//檢查長度
if((DWORD)DataLength < (pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)) ) return FALSE;
//取得pe頭
pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader->e_lfanew); // PE頭
//檢查pe頭的合法性
if(pNTHeader->Signature != IMAGE_NT_SIGNATURE) return FALSE; //0x00004550 : PE00
if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) //0x2000 : File is a DLL
return FALSE;
if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0x0002 : 指出文件可以運行
return FALSE;
if(pNTHeader->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) return FALSE;
//取得節表(段表)
pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));
//驗證每個節表的空間
for(int i=0; i< pNTHeader->FileHeader.NumberOfSections; i++) {
if((pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) > (DWORD)DataLength)return FALSE;
}
return TRUE;
}
//計算對齊邊界
int CMemLoadDll::GetAlignedSize(int Origin, int Alignment)
{
return (Origin + Alignment - 1) / Alignment * Alignment;
}
//計算整個dll映像文件的尺寸
int CMemLoadDll::CalcTotalImageSize()
{
int Size;
if(pNTHeader == NULL)return 0;
int nAlign = pNTHeader->OptionalHeader.SectionAlignment; //段對齊字節數
// 計算所有頭的尺寸。包括dos, coff, pe頭 和 段表的大小
Size = GetAlignedSize(pNTHeader->OptionalHeader.SizeOfHeaders, nAlign);
// 計算所有節的大小
for(int i=0; i < pNTHeader->FileHeader.NumberOfSections; ++i) {
//得到該節的大小
int CodeSize = pSectionHeader[i].Misc.VirtualSize ;
int LoadSize = pSectionHeader[i].SizeOfRawData;
int MaxSize = (LoadSize > CodeSize)?(LoadSize):(CodeSize);
int SectionSize = GetAlignedSize(pSectionHeader[i].VirtualAddress + MaxSize, nAlign);
if(Size < SectionSize)
Size = SectionSize; //Use the Max;
}
return Size;
}
//CopyDllDatas函數將dll數據複製到指定內存區域,並對齊所有節
//pSrc: 存放dll數據的原始緩衝區
//pDest:目標內存地址
void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc)
{
// 計算需要複製的PE頭+段表字節數
int HeaderSize = pNTHeader->OptionalHeader.SizeOfHeaders;
int SectionSize = pNTHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
int MoveSize = HeaderSize + SectionSize;
//複製頭和段信息
memmove(pDest, pSrc, MoveSize);
//複製每個節
for(int i=0; i < pNTHeader->FileHeader.NumberOfSections; ++i) {
if(pSectionHeader[i].VirtualAddress == 0 || pSectionHeader[i].SizeOfRawData == 0)continue;
// 定位該節在內存中的位置
void *pSectionAddress = (void *)((unsigned long)pDest + pSectionHeader[i].VirtualAddress);
// 複製段數據到虛擬內存
memmove((void *)pSectionAddress,
(void *)((DWORD)pSrc + pSectionHeader[i].PointerToRawData),
pSectionHeader[i].SizeOfRawData);
}
//修正指針,指向新分配的內存
//新的dos頭
pDosHeader = (PIMAGE_DOS_HEADER)pDest;
//新的pe頭地址
pNTHeader = (PIMAGE_NT_HEADERS)((int)pDest + (pDosHeader->e_lfanew));
//新的節表地址
pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));
return ;
}
以下是調用方法
在工程的頭文件中
public:
bool LoadDllFromMemAndCall(void);
DWORD dwSize;
LPVOID pBuffer;
HRSRC hr;
HGLOBAL hg;
在工程的原文件中
#include "MemLoadDll.h"
typedef int (WINAPI *PFN_POPMSGBOX)(void);
void CtestmemoydllDlg::OnBnClickedButton1()
{
LoadDllFromMemAndCall();
}
bool CtestmemoydllDlg::LoadDllFromMemAndCall(void)
{
HINSTANCE hinst=AfxGetInstanceHandle();
/*HRSRC hr=NULL;
HGLOBAL hg=NULL;*/
//對資源名稱字符串進行簡單的異或操作,達到不能通過外部字符串參考下斷點
hr=FindResource(hinst,MAKEINTRESOURCE(IDR_MYDLL1),TEXT("MYDLL"));
if (NULL == hr) return FALSE;
//獲取資源的大小
dwSize = SizeofResource(hinst, hr);
if (0 == dwSize) return FALSE;
hg=LoadResource(hinst,hr);
if (NULL == hg) return FALSE;
//鎖定資源
pBuffer =(LPSTR)LockResource(hg);
if (NULL == pBuffer) return FALSE;
}
void CtestmemoydllDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知處理程序代碼
CMemLoadDll* pMemLoadDll;
pMemLoadDll=new CMemLoadDll();
PFN_POPMSGBOX pfn;
if(pMemLoadDll->MemLoadLibrary(pBuffer, dwSize)) //加載dll到當前進程的地址空間
{
pfn = pMemLoadDll->MemGetProcAddress("PopCurrentProcPath");
}
pfn();
FreeResource(hg);
delete pMemLoadDll;
pMemLoadDll = NULL;
}
工程下載