本文主要介绍LoadLibrary的深入案例讲解。本文通过一个简单的案例来说明对这项技术的理解和使用。以下是详细内容,有需要的朋友可以参考一下。
LoadLibrary流程分析
在Windows的开发中,我们都有一个规则:不要在DllMain中处理过于复杂的事情,防止死锁。
那么,为什么DllMain中容易造成死锁呢?我们来分析一下LoadLibrary的整个流程和原理。
1. 使用
让我们来看看LoadLibrary是如何使用的。由于这个函数的底层调用LoadLibraryEx,所以我们来看看LoadLibraryEx的用法。
1.1 声明
HMODULE WINAPI LoadLibraryEx(
_In_ LPCTSTR lpFileName,
_保留_处理hFile,
_In_ DWORD dwFlags
);
这里,使用时需要注意第三个主要参数:
Don _ resolve _ DLL _ references:这个标志用来告诉系统把Dll映射到调用进程的地址空间,但是不调用DllMain,不加载依赖Dll(只映射自身)。
LOAD_LIBRARY_AS_DATAFILE:这个标志类似于DONT_RESOLVE_DLL_REFERENCES标志,因为系统只将DLL映射到进程的地址空间,就像它是一个数据文件一样。系统不需要额外的时间来准备执行文件中的任何代码。
LOAD_LIBRARY_SEARCH_USER_DIRS:搜索路径的使用使用AddDllDirectory和SetDllDirectory设置的路径(保护Dll本身和依赖Dll)。
LOAD_LIBRARY_SEARCH_SYSTEM32:从%windows%\system32加载Dll及其依赖项。
应用程序安装路径搜索Dll及其依赖项。
LOAD_WITH_ALTERED_SEARCH_PATH:在以下目录中搜索:
当前进程目录。
Windows系统目录。
16位Windows的系统目录。
Windows目录。
Path环境变量目录。
默认情况下,LoadLibrary和LoadLibrary根据以下目录进行搜索:
当前进程目录。
SetDllDirectory设置的文件夹路径。
Windows系统目录。
16位Windows的系统目录。
Windows目录。
Path环境变量目录。
1.2 SetDllDirectoryW
该功能的实现如下:
KernelBaseGetGlobalData返回的结果信息如下:
以下信息是:
0:003 dd KERNELBASE!KernelBaseGlobalData
75b 155 a 0 0000000000000000000160014 7f9a 1240
75b 155 b 0 00280026 7f9a 1260 00000000000000
75b 155 c 0 00000000 00e 60000 75b 156 c 0 7 fff 0000
75b 155d 0 00c4 b494 0000011 a 00cc 1914 0000012 c
75b 155 E0 00cb9f 34 00000253 00cc 2658 00cc 5e 20
75b 155 f 0 000000000 00e 84380 000000000 ffffffff
75b 15600 ffffffff 0000000000000000000000000
75b 15610 020007 d0 75950000 00000000000000
0:003 du 7f9a1240
7f9a1240 'C:\Windows '
0:003 du 7f9a1260
7f9a1260 'C:\Windows\system32 '
2. LoadLibrary分析
我们来分析一下这个函数的执行过程:
h module _ _ stdcall loadlibrary w(LPCWSTR lpLibFileName)
{
返回LoadLibraryExW(lpLibFileName,0,0);
}
2.1 LoadLibraryExW
LoadLibraryW的主要流程如下:
h module _ _ stdcall loadlibrary EXW(LPCWSTR lpLibFileName,HANDLE hFile,DWORD dwFlags)
{
//.
search path=BaseGetProcessDllPath(
dwFlags,
(chFlags LOAD _ WITH _ ALTERED _ SEARCH _ PATH)!=0 ?DllName。缓冲区:0,
0,
(int)dw flags);
//.
ntStatus=LdrLoadDll(SearchPath,(PULONG)lpLibFileName,DllName,hFile);
}
通过BaseGetProcessDllPath获得的路径是:
00072 ACC ' C:\ Users \ XXX \ Desktop;' C:\Windows '
00072 b0c’system32;c:\ Windows \ system;C:\Wi '
00072 b4c ' n windows;c:\ Windows \ system32;C:\W '
00072b8c ' indows' c:\ Windows \ System32 \ Wbem;'
00072密件抄送' C:\ Windows \ System32 \ Windows power '
00072c0c '外壳\ 1.0版'
下一步是使用LdrLoadDll值Dll。
2.2 LdrLoadDll
NTSTATUS _ _ stdcall LdrLoadDll(PWSTR search path,PULONG LoadFlags,PUNICODE_STRING DllName,PVOID *BaseAddress)
{
//.
if(搜索路径)
{
result=RtlInitUnicodeStringEx(DestinationString,search path);
如果(结果0)
返回结果;
新闻搜索=目标字符串;
}
其他语句
{
newsearchpath=ldrpdefaultpath
}
//-我.
S7=ldploaddall(dllname,int)NewSearchPath,v6,1,0,(int)dllname;
//-我.
返回V7;
}
这里有一个默认的加载路径为ldrpdefaultpath,路径信息如下:
0,000 ds ldrpdefaultpath
0006560 ' c:\ users \ XXX \ desktop;C:\Windows
000615至“0”system32;c:\ windows \ system;C:\Wi
000615和0 ' ndows .c:\ windows \ system32;C:\W
000620 '印第斯;c:\ windows \ system32 \ WBEM;"
0006660 ' c:\ windows \ system32 \ windows power '
0006至0 '外壳程序\ 1.0版'
2.3 LdrpLoadDll
int _ _ stdcall LDP loadall(pcunicode _ string source,int a2,int a3,char a4,int a5,int a6)
{
//-我.
如果(!ldrpinldrinit)
RTL输入临界函数(ldploadlock);
//-我.
ldpf dormapdll(* pcunicode _ string *)(char *)v 31 1、v29、a3、v27[0]、int)v33、int)v 31;
//-我.
如果(v20x 10000000)
v22=进程导入((请参阅*)v21,v3);
其他语句
v22=ldroprisstatistimports(v 23,v 29);
//-我.
ldprunaminaryutins(0);
//-我.
如果(!ldrpinldrinit)
RTL离开批判功能(ldploadlock);
}
这个函数的整理流程如下:
获取加载锁RTL输入临界函数(ldploadlock);尝试加载dll:ldpf dormapdl .处理导入表信息。运行回调函数ldprunarminiciones。这是什么?释放锁RTL离开批判功能(ldploadlock);
这里值得注意的地方就是ldrpruninitializeroutines(消歧义)是调用dllmain给你的函数,调用堆栈信息如下:
# ChildEBP回退一个参数名到儿童
00 0029d 718 67 f 42 c 22 67 ef 0000 00000000 dllt!dllmain给你的
01 0029 d75 c67 f42 def 67 ef 0000 00000000 dllt!dllman _ dispatch 0 xb2(dllman _ dispatch 0 xb2)
02 0029d 770 b89 D8 67 ef 0000 00000000 dllt!_ dllmaincrtstartp0x 1 f
03 0029d 790 777 C5 c41 67 F3 DCC 5 67 ef 0000 00000001 ntdll!ldr allianti0x 14
04 0029d 884 777 c 052 e 0000000 73 BD 12d 3777 a 7c 9 a ntdll!ldprunarmulaities0x 26 f
05 0029 d9f 0 777 c 232 c 0029来自1c 00000000 ntdll的50 0029!LDP loadl 0 x4 D1
-伊甸园字幕组=-翻译:ldloaddll0x 92
5c 763 D3 C3 c12 00000000 00000001核心基础中的07 0029!LoadLibraryExW0x15a
加载动态链接库的时候,会获取锁ldrploaderlock然而加载的时候又会调用ldrpruninitializeroutines(消歧义)进入dllmain给你的。
2.4 LdrpFindOrMapDll
加载和映射的动态链接库的函数是ldrpfindormapdll,这个函数在加载动态链接库之前,需要判断动态链接库是否被加载过,已经加载的动态链接库文件,通过一个哈希表加载,哈希表如下:
list _ entry ldrashtable[ldr _ hash _ table _ entries];
其实每个ldrphashtable(可译)都是通过一个S7-1200可编程控制器的成员哈希链接(哈希链接)链接起来,结构如下:
0,000 dt ntdll!S7-1200可编程控制器
0x 000内联顺序链接:_ list _ entry
0x 008 im订单链接:_ list _ entry
in itializationorder链接中的0x 010:_ list _ entry
0x018 DllBase : Ptr32 Void
0x01c输入类型:Ptr32无效
0x 020大小图像:uint 4b
0x 024完整的dllname:_ unicode _ string
0x 02c based name:_ unicode _ string
0x034标志:Uint4B
0x038 LoadCount : Uint2B
0x 03 a索引:uint 2b
0x 03 c哈希链接:_ list _ entry//ldphashtable连接的列表结构
0x03c节指针:Ptr32无效
0x040校验和:Uint4B
0x044时间日期戳:Uint4B
0x 044 loaded imports:ptr 32无效
0x 048条目类型activity context:ptr 32 _ activation _ context
0x04c修补程序信息:Ptr32无效
0x050转发链接:_列表_条目
0x058服务链接:_列表_条目
0x060静态变量:_列表_条目
0x068背景信息:Ptr32无效
0x06c原始ase : Uint4B
0x070加载时间:_LARGE_INTEGER
对于每个加载的动态链接库文件,都会有两种形式:
动态链接库名称加载。
动态链接库全路径加载。
在ldrpfindloadeddllbyname也会根据不同的加载来匹配不同的情况:
如果使用动态链接库名称加载,那么比较basedllname使用rtlequalunicodestring .
如果使用动态链接库全路径加载,那么比较fulldllname使用rtlequalunicodestring .
例如:
0,000 dt ntdll!S7-1200可编程控制器
0x 000内联顺序链接:_ list _ entry[0x 6 f 6358-0x 6 F5 EB 8]
0x 008 im订单链接:_ list _ entry[0x 6 f 6360-0x 6 F5 EC 0]
in itializationorder链接中的0x 010:_ list _ entry[0x 6 F6 e38-0x 6 f 6368]
0x018 DllBase :0x75b30000同上
0x01c输入类型:0x75b43273同上
0x020大小调整:0x110000
0x 024完整的dllname:_ unicode _ string ' c:\ windows \ sys wow 64 \ kernel 32。dll '//全路径匹配这个
0x 02c based dllname:_ unicode _ string”内核32。dll“//dll名称匹配这个
0x034标志:0x84004
0x038加载计数:0xffff
0x 03 a索引:0
0x 03 c哈希链接:_ list _ entry[0x 77c 148 A0-0x 77c 148 A0]
0x03c区段指标:0x77c148a0 Void
0x040校验和:0x77c148a0
0x044时间日期戳:0x589c961f
0x 044导入:0x 589 c 961 f void
0x 048条目类型activationcontext:(null)
0x04c修补程序信息:(空)
0x050转发链接:_ list _ entry[0x 7188580-0x 7188580]
0x058服务链接:_LIST_ENTRY [0x6f62c8 -0x6f62c8 ]
0x060静态变量:_LIST_ENTRY [0x6f63f0 -0x6f62f0 ]
0x068上下文信息:0x77b4ef40 Void
0x06c原始ase :0x7dd60000
0x 070加载时间:_ large _ integer0x 01d 4876‐ee 26 eab 0
如果,这里匹配成功了,那么动态链接库就不会再加载了。
3. 总结
3.1 同名DLL
一个进程是否可以加载相同名字的动态链接库呢?按照上面分析有一个加载规则
在ldrpfindloadeddllbyname也会根据不同的加载来匹配不同的情况:
如果使用动态链接库名称加载,那么比较basedllname使用rtlequalunicodestring .
如果使用动态链接库全路径加载,那么比较fulldllname使用rtlequalunicodestring .
那么可以从这个规则来看,是看加载是的方式:
int main(int args、char* argv[])
{
loadlibrary w(c:\ \ dllt ST . dll):
loadlibrary w(" dllt ST . dll ");
系统("暂停");
返回0;
}
这种方式,第二个应该加载库w(dllt ST . dll)不会去匹配加载了,如下:
那么我们按照这种方式加载呢?
int main(int args、char* argv[])
{
loadlibrary w(" dllt ST . dll ");
loadlibrary w(c:\ \ dllt ST . dll):
系统("暂停");
返回0;
}
按照规则来说,应该是可以加载的,如下:
这个中情况在哇64号情况下,有时候会踩到坑,例如,对于哪里64号路径,如果直接使用c:\ windows \ system32 \ kernel 32。动态链接库路径加载,那么ldrpfindloadeddllbyname一定会失败,因为FullDllName为哇64号使用的值:c:\ windows \ sys wow 64 \ kernel 32。dll .
3.2 死锁
我们知道,在调用dllmain给你的的时候,会占用锁RTL进入关键行动阶段,所有凡是在dllmain给你的中调用可能获取RTL输入临界函数的操作,就可能会导致死锁,例如:
直接调用加载库(例如)。
调用共党分子(在共党分子底层会调用LoadLibraryEx).
调用创建线程(因为创建线程(创建线程)会继续调用dllmain给我的,稍有不慎可能就会死锁页:1。
调用getmodulefilename ' getmoduleandle等(底层获取RTL输入临界函数锁页:1。
到此这篇关于加载库(加载库)深入案例详解的文章就介绍到这了,更多相关加载库(加载库)详解内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。