將w32 dll文件以資源的形式加載,並且在內存中調用

大部分代碼取自網絡,重新修改、編輯後發佈


//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;
}

工程下載


發佈了27 篇原創文章 · 獲贊 12 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章