本例主要讲一下,用VC6如何生成DLL,用工具WinHex取得DLL全部内容,VC2015项目加载内存中的DLL函数,并调用函数的示例。
本例中的示例代码下载,点击可以下载
1.新建项目,这里选择Win32 Dynamic-link Library,如下图:
2.选择“A simple DLL project”,然后点击完成,如下:
3.生成示例项目后,如下图:
4.添加自己的示例函数,这里以简单的求和函数为例:
/*
程序功能:DLL生成项目,生成测试的DLL文件,
作者:依星
QQ:34596561,312337667
日期:2023/8/15
*/
#include "stdafx.h"
extern "C" __declspec(dllexport) int sum(int a,int b);
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
extern "C" __declspec(dllexport) int sum(int a,int b)
{
return a+b;
}
5.设置项目为release,开始编译构建DLL文件,按“F7”。
这里使用WinHex来取得DLL的全部数据,具体使用流程如下:
1.打开WinHex,然后将DLL文件拖入到主界面中,如下图:
2.选择“编辑”---“复制所有”---“C源码”,这里实际是复制了所有的数据,如下图:
3.新建一个记事本,命名为“dll.h” ,将上面复制的数据粘贴到记事本中,然后将此文件复制到VC2015项目中。
1. 这里先用VC2015建立一个标准的MFC工程项目,新建---项目,如下:
2. 选择MFC--MFC应用程序,然后选择保存的目录,并命名项目名称,如下图:
3.下一步,然后再选择“基于对话框”,点击“在静态库中使用MFC”,如下图:
4.至此没有特别要设置的了,点击下一步,直到完成。
5.项目创建后,默认为Unicode字符集,这里改为多字节;(PS:其实改不改都行,主要是项目中都是使用的多字节,习惯了)如下图:
6.将上一步生成的Dll.h文件添加到解决方案的头文件中,然后再新建一个头文件和源文件,用于把网上的CMemLoadDll类源码复制过来,源码将在后面贴出来,咱们先说流程。添加好之后,如下图工程目录:
7.在主对话框中,加一个按钮,用于调用示例:
8.双击按钮,显示点击方法(MemRunDllDlg.cpp):
/*
调用CMemLoadDll,
加载内存中的DLL,
并运行DLL中的函数
//
dllData:为生成的测试DLL数据文件dll.h;(注:此处也可以把这个DLL文件加到资源中加载,或者以文件形式读取到内存中,都是可以的)
为安全起见,可以把DLL的数据加密存储到DLL.H中,本例不再展示。
*/
void CMemRunDllDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CMemLoadDll *pMemLoadDll = new CMemLoadDll();
if (!pMemLoadDll->MemLoadLibrary(&dllData, sizeof(dllData))) //加载dll到当前进程的地址空间
{
AfxMessageBox("Load DLL error!");
return ;
}
addNumberProc addNumber = (addNumberProc)pMemLoadDll->MemGetProcAddress("sum");
if (addNumber == NULL)
{
AfxMessageBox("Find Add function failed!");
return ;
}
int c = addNumber(1, 2);
char itc[10];
sprintf(itc, "%d", c);
AfxMessageBox(itc);
}
9 .在此文件头部(MemRunDllDlg.cpp),加入文件引用及DLL函数声明,如下:
#include "dll.h"
#include "MemLoadDll.h"
typedef int(*addNumberProc)(int, int);
10.生成EXE并运行,正常运行,如下:
1.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;
};
2.MemLoadDll.cpp源文件
#include "stdafx.h"
#include
#include
#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)NULL, 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里面就是实际的函数地址)
int i;
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的名字
TCHAR buf[256]; //dll name;
BYTE *pName = (BYTE *)((unsigned long)pImageBase + pID->Name);
for (i = 0; i < 256; i++)
{
if (pName[i] == 0)
{
break;
}
buf[i] = pName[i];
}
if (i >= 256)
{
return FALSE; // bad dll name
}
else
{
buf[i] = 0;
}
HMODULE hDll = GetModuleHandle(buf);
if (hDll == NULL)
{
hDll = LoadLibrary(buf);
}
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 (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;//(PDWORD) 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;
}