从内存中加载DLL.docx
- 文档编号:7871078
- 上传时间:2023-05-12
- 格式:DOCX
- 页数:16
- 大小:21.73KB
从内存中加载DLL.docx
《从内存中加载DLL.docx》由会员分享,可在线阅读,更多相关《从内存中加载DLL.docx(16页珍藏版)》请在冰点文库上搜索。
从内存中加载DLL
从内存中加载DLL
[转]从内存中加载DLL
减小字体
增大字体
[转]从内存中加载DLL转一篇文章,原文出处没有找到,已经转了好多了,感觉技术不错,好东东收藏也不错.——————————————————程序使用动态库DLL一般分为隐式加载和显式加载两种,分别对应两种链接情况。
本文主要讨论显式加载的技术问题。
我们知道,要显式加载一个DLL,并取得其中导出的函数地址一般是通过如下步骤:
(1)用LoadLibrary加载dll文件,获得该dll的模块句柄;
(2)定义一个函数指针类型,并声明一个变量;
(3)用GetProcAddress取得该dll中目标函数的地址,赋值给函数指针变量;
(4)调用函数指针变量。
这
个方法要求dll文件位于硬盘上面。
现在假设我们的dll已经位于内存中,比如通过脱壳、解密或者解压缩得到,能不能不把它写入硬盘文件,而直接从内存加
载呢?
答案是肯定的。
经过多天的研究,非法操作了N次,修改了M个BUG,死亡了若干脑细胞后,终于有了初步的结果,下面做个总结与大家共享。
一、加载的步骤由
于没有相关的资料说明,只能凭借感觉来写。
首先LoadLibrary是把dll的代码映射到exe进程的虚拟地址空间中,我们要实现的也是这个。
所以先
要弄清楚dll的文件结构。
好在这个比较简单,它和exe一样也是PE文件结构,关于PE文件的资料很多,阅读一番后,基本上知道了必须做的几个工作:
(1)判断内存数据是否是一个有效的DLL。
这个功能通过函数CheckDataValide完成。
原型是:
BOOLCMemLoadDll:
:
CheckDataValide(void*lpFileData,intDataLength);
(2)计算加载该DLL所需的虚拟内存大小。
这个功能通过函数CalcTotalImageSize完成。
原型是:
intCMemLoadDll:
:
CalcTotalImageSize();
(3)将DLL数据复制到所分配的虚拟内存块中。
该功能通过函数CopyDllDatas完成。
要注意段对齐。
voidCMemLoadDll:
:
CopyDllDatas(void*pDest,void*pSrc);
(4)修正基地重定位数据。
这个功能通过函数DoRelocation完成。
原型是:
voidCMemLoadDll:
:
DoRelocation(void*NewBase);
(5)填充该DLL的引入地址表。
这个功能由函数FillRavAddress完成。
原型是:
BOOLCMemLoadDll:
:
FillRavAddress(void*pImageBase);
(6)根据DLL每个节的属性设置其对应内存页的读写属性。
我这里做了简化,所有内存区域都设置成一样的读写属性。
(7)调用入口函数DllMain,完成初始化工作。
这一步我一开始忽略了,所以总是发现自己加载的dll和LoadLibrary加载的dll有些不同(我把整块内存区域保存到两个文件中进行比较,够晕的)。
只是最近猜想到还需要这一步。
(8)保存dll的基地址(即分配的内存块起始地址),用于查找dll的导出函数。
从现在开始这个dll已经完全映射到了进程的虚拟地址空间,可以使用它了。
(9)不需要dll的时候,释放所分配的虚拟内存。
二、要说明的几个问题
(1)目前CMemLoadDll仅仅针对win32动态库,没有考虑mfc常规和扩展dll。
(2)
只考虑使用dll中的函数,对于导出类的dll,由于通常都是隐式链接,所以也没有考虑。
导出变量的dll虽然也是隐式链接,但是通过查找函数的方法也可
以找到该变量,不过在取值的时候一定要符合dll中对变量的定义,比如dll中导出的是一个int变量,则得到该变量在dll中的地址后,需要强制转换成
int*指针,然后取值。
(3)查找函数的功能通过函数
FARPROCCMemLoadDll:
:
MemGetProcAddress(LPCSTRlpProcName);
实现,参数是dll导出的函数(或者变量)的名字。
这里必须注意函数名修饰,通常不加extern”C”的函数,编译以后在dll中导出的都是修饰名,比如:
在dll头文件中:
extern__declspec(dllexport)intnTestDll;
在.dll中的导出符号变成?
nTestDll@@3HA
所以,为了能够找到我们需要的函数,必须在.h中添加extern“C”修饰。
最好是给dll加一个def文件,里面明确给出每个函数的导出名字。
(4)PE中的内容比较多,有些细节没有考虑。
比如CheckDataValide函数中没有考虑dll对操作系统版本的要求。
(5)PE文件中的节有很多种。
可以从节表(或者叫做区块表)中一一找到。
而且每个节的属性都不同。
例如:
.text,.data,.rsrc,.crt等等。
由于这个代码基于手头已有的pe文件资料,对于不熟悉的节,在映射dll数据的时候没有考虑是否需要处理。
(6)
一开始把dll映射到进程的地址空间以后,我试图直接使用GetProcAddress查找函数。
最初我认为LoadLibrary返回的
HINSTANCE值是0×10000000,把它传递给GetProcAddress可以找到目标函数,而我也把dll映射到0×10000000这个
地址,但是当我把这个值传递给GetProcAddress的时候,发现无法找到函数,用GetLastError得到错误码一看是无效句柄的错误,这才
明白原来LoadLibrary在加载dll的时候,同时创建了一个句柄放入进程的句柄表,而我们要做这个工作是比较麻烦的,所以只能自己写一个查找函
数。
(7)释放dll所占据的虚拟内存,原来我使用
VirtualFree((LPVOID)pImageBase,0,MEM_FREE);
后来发现有问题,应该使用VirtualFree((LPVOID)pImageBase,0,MEM_RELEASE);
(8)MemGetProcAddress不仅支持通过函数名查找,还支持通过导出序号查找函数。
例如下面的用法:
DLLFUNCTIONfDll=(DLLFUNCTION)a.MemGetProcAddress((LPCTSTR)1);三、创建测试用的DLL,工程的名字取”TestDll”用VC向导创建一个WIN32DLL工程,里面选择“导出一些符号”,为了测试需要,对源代码进行如下修改:
(1)头文件
//ThisclassisexportedfromtheTestDll.dll
classTESTDLL_APICTestDll{
public:
CTestDll(void);
};
externTESTDLL_APIintnTestDll;
//要修改的地方,添加了extern“C”和char*参数:
extern“C”TESTDLL_APIintfnTestDll(char*);
(2)cpp文件
a.添加#include“stdlib.h”
b.DllMain中
caseDLL_PROCESS_DETACH:
nTestDll=12345;
break;
c.初始化变量
TESTDLL_APIintnTestDll=654321;
d.修改函数
TESTDLL_APIintfnTestDll(char*p)
{
if(p==NULL)
returnnTestDll;
else
returnatoi(p);
}四、创建测试工程。
使用一个dlg工程,测试代码如下:
假设DllNameBuffer里面保存有dll文件的路径
CFilef;
if(f.Open(DllNameBuffer,CFile:
:
modeRead))
{
intFileLength=f.GetLength();
void*lpBuf=newchar[FileLength];
f.Read(lpBuf,FileLength);
f.Close();CMemLoadDlla;
if(a.MemLoadLibrary(lpBuf,FileLength))//加载dll到当前进程的地址空间
{
typedefint(*DLLFUNCTION)(char*);
DLLFUNCTIONfDll=(DLLFUNCTION)a.MemGetProcAddress(”fnTestDll”);
if(fDll!
=NULL)
{
MessageBox(”找到函数!
!
”);
CStringstr;
str.Format(”Resultis:
%d&%d”,fDll(NULL),fDll(”100″));
MessageBox(str);
}
else
{
DWORDerr=GetLastError();
CStringstr;
str.Format(”Error:
%d”,err);
MessageBox(str);
}
}delete[]lpBuf;
}五、加载类源代码。
typedefBOOL(__stdcall*ProcDllMain)(HINSTANCE,DWORD,LPVOID);classCMemLoadDll
{
public:
CMemLoadDll();
~CMemLoadDll();
BOOLMemLoadLibrary(void*lpFileData,intDataLength);//Dllfiledatabuffer
FARPROCMemGetProcAddress(LPCSTRlpProcName);
private:
BOOLisLoadOk;
BOOLCheckDataValide(void*lpFileData,intDataLength);
intCalcTotalImageSize();
voidCopyDllDatas(void*pDest,void*pSrc);
BOOLFillRavAddress(void*pBase);
voidDoRelocation(void*pNewBase);
intGetAlignedSize(intOrigin,intAlignment);
private:
ProcDllMainpDllMain;private:
DWORDpImageBase;
PIMAGE_DOS_HEADERpDosHeader;
PIMAGE_NT_HEADERSpNTHeader;
PIMAGE_SECTION_HEADERpSectionHeader;
};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到当前进程的地址空间,缺省位置0×10000000
//返回值:
成功返回TRUE,失败返回FALSE
//lpFileData:
存放dll文件数据的缓冲区
//DataLength:
缓冲区中数据的总长度
BOOLCMemLoadDll:
:
MemLoadLibrary(void*lpFileData,intDataLength)
{
if(pImageBase!
=NULL)
{
returnFALSE;//已经加载一个dll,还没有释放,不能加载新的dll
}
//检查数据有效性,并初始化
if(!
CheckDataValide(lpFileData,DataLength))returnFALSE;
//计算所需的加载空间
intImageSize=CalcTotalImageSize();
if(ImageSize==0)returnFALSE;//分配虚拟内存
void*pMemoryAddress=VirtualAlloc((LPVOID)0×10000000,ImageSize,
MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
if(pMemoryAddress==NULL)returnFALSE;
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);
returnFALSE;
}
//修改页属性。
应该根据每个页的属性单独设置其对应内存页的属性。
这里简化一下。
//统一设置成一个属性PAGE_EXECUTE_READWRITE
unsignedlongold;
VirtualProtect(pMemoryAddress,ImageSize,PAGE_EXECUTE_READWRITE,&old);
}
//修正基地址
pNTHeader->OptionalHeader.ImageBase=(DWORD)pMemoryAddress;//接下来要调用一下dll的入口函数,做初始化工作。
pDllMain=(ProcDllMain)(pNTHeader->OptionalHeader.AddressOfEntryPoint+(DWORD)pMemoryAddress);
BOOLInitResult=pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH,0);
if(!
InitResult)//初始化失败
{
pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0);
VirtualFree(pMemoryAddress,0,MEM_RELEASE);
pDllMain=NULL;
returnFALSE;
}isLoadOk=TRUE;
pImageBase=(DWORD)pMemoryAddress;
returnTRUE;
}//MemGetProcAddress函数从dll中获取指定函数的地址
//返回值:
成功返回函数地址,失败返回NULL
//lpProcName:
要查找函数的名字或者序号
FARPROCCMemLoadDll:
:
MemGetProcAddress(LPCSTRlpProcName)
{
if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress==0||
pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size==0)
returnNULL;
if(!
isLoadOk)returnNULL;DWORDOffsetStart=pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
DWORDSize=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);
intiBase=pExport->Base;
intiNumberOfFunctions=pExport->NumberOfFunctions;
intiNumberOfNames=pExport->NumberOfNames;//AddressOfNameOrdinals+pImageBase);
LPDWORDpAddressOfNames=(LPDWORD)(pExport->AddressOfNames+pImageBase);intiOrdinal=-1;if(((DWORD)lpProcName&0xFFFF0000)==0)//ITISAORDINAL!
{
iOrdinal=(DWORD)lpProcName&0×0000FFFF-iBase;
}
else//usename
{
intiFound=-1;for(inti=0;i{
char*pName=(char*)(pAddressOfNames[i]+pImageBase);
if(strcmp(pName,lpProcName)==0)
{
iFound=i;break;
}
}
if(iFound>=0)
{
iOrdinal=(int)(pAddressOfOrdinals[iFound]);
}
}if(iOrdinal=iNumberOfFunctions)returnNULL;
else
{
DWORDpFunctionOffset=pAddressOfFunctions[iOrdinal];
if(pFunctionOffset>OffsetStart&&pFunctionOffset<(OffsetStart+Size))//maybeExportForwarding
returnNULL;
elsereturn(FARPROC)(pFunctionOffset+pImageBase);
}}//重定向PE用到的地址
voidCMemLoadDll:
:
DoRelocation(void*NewBase)
{
/*重定位表的结构:
//DWORDsectionAddress,DWORDsize(包括本节需要重定位的数据)
//例如1000节需要修正5个重定位数据的话,重定位表的数据是
//0010000014000000xxxxxxxxxxxxxxxxxxxx0000
//———–———–—-
//给出节的偏移总尺寸=8+6*2需要修正的地址用于对齐4字节
//重定位表是若干个相连,如果address和size都是0表示结束
//需要修正的地址是12位的,高4位是形态字,intelcpu下是3
*/
//假设NewBase是0×600000,而文件中设置的缺省ImageBase是0×400000,则修正偏移量就是0×200000
DWORDDelta=(DWORD)NewBase-pNTHeader->OptionalHeader.ImageBase;//注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址
PIMAGE_BASE_RELOCATIONpLoc=(PIMAGE_BASE_RELOCATION)((unsignedlong)NewBase
+pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while((pLoc->VirtualAddress+pLoc->SizeOfBlock)!
=0)//开始扫描重定位表
{
WORD*pLocData=(WORD*)((int)pLoc+sizeof(IMAGE_BASE_RELOCATION));
//计算本节需要修正的重定位项(地址)的数目
intNumberOfReloc=(pLoc->SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))/sizeof(WORD);
for(inti=0;iVirtualAddress=0×1000;
//pLocData[i]=0×313E;表示本节偏移地址0×13E处需要修正
//因此pAddress=基地址+0×113E
//里面的内容是A1(0cd40210)汇编代码是:
moveax,[1002d40c]
//需要修正1002d40c这个地址
DWORD*pAddress=(DWORD*)((unsignedlong)NewBase+pLoc->VirtualAddress+(pLocData[i]&0×0FFF));
*pAddress+=Delta;
}
}
//转移到下一个节进行处理
pLoc=(PIMAGE_BASE_RELOCATION)((DWORD)pLoc+pLoc->SizeOfBlock);
}
}//填充引入地址表
BOOLCMemLoadDll:
:
FillRavAddress(void*pImageBase)
{
//引入表实际上是一个IMAGE_IMPORT_DESCRIPTOR结构数组,全部是0表示结束
//数组定义如下:
//
//DWORDOriginalFirstThunk;//0表示结束,否则指向未绑定的IAT结构数组
//DWORDTimeDateStamp;
//DWORDForwarderChain;//-1ifnoforwarders
//DWORDName;//给出dll的名字
//DWORDFirstThunk
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 内存 加载 DLL