运维开发网

LoadLibrary深入案例介绍

运维开发网 https://www.qedev.com 2022-06-02 16:13 出处:网络
这篇文章主要介绍了LoadLibrary深入案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下 LoadLibrary流程分析

这篇文章主要介绍了LoadLibrary深入案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下 LoadLibrary流程分析

在Windows的开发中,我们都有一个规则:不要在DllMain中处理过于复杂的事情,防止死锁。

那么,为什么DllMain中容易造成死锁呢?我们来分析一下LoadLibrary的整个流程和原理。

1. 使用

让我们来看看LoadLibrary是如何使用的。由于这个函数的底层调用LoadLibraryEx,所以我们来看看LoadLibraryEx的用法。

1.1 声明HMODULE WINAPI LoadLibraryEx( _In_ LPCTSTR lpFileName, _Reserved_ HANDLE hFile, _In_ DWORD dwFlags);

这里,使用时需要注意第三个主要参数:

DONT_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和其依赖项。 LOAD_LIBRARY_SEARCH_APPLICATION_DIR : 应用程序安装路径搜索Dll和其依赖项。 LOAD_WITH_ALTERED_SEARCH_PATH : 按照如下目录搜索: 进程当前目录。 Windows的系统目录。 16 位Windows的系统目录。 Windows目录。 path环境变量目录。

默认情况下,LoadLibrary和LoadLibrary根据以下目录进行搜索:

进程当前目录。 SetDllDirectory设置的文件夹路径。 Windows的系统目录。 16 位Windows的系统目录。 Windows目录。 path环境变量目录。1.2 SetDllDirectoryW

该功能的实现如下:


KernelBaseGetGlobalData返回的结果信息如下:


以下信息是:

0:003gt; dd KERNELBASE!KernelBaseGlobalData75b155a0 00000000 00000000 00160014 7f9a124075b155b0 00280026 7f9a1260 00000000 0000000075b155c0 00000000 00e60000 75b156c0 7fff000075b155d0 00c4b494 0000011a 00cc1914 0000012c75b155e0 00cb9f34 00000253 00cc2658 00cc5e2075b155f0 00000000 00e84380 0000000f ffffffff75b15600 ffffffff 00000000 00000000 0000000075b15610 020007d0 75950000 00000000 000000000:003gt; du 7f9a12407f9a1240 "C:\Windows"0:003gt; du 7f9a1260 7f9a1260 "C:\Windows\system32"2. LoadLibrary分析

我们来分析一下这个函数的执行过程:

HMODULE __stdcall LoadLibraryW(LPCWSTR lpLibFileName){ return LoadLibraryExW(lpLibFileName, 0, 0);}2.1 LoadLibraryExW

LoadLibraryW的主要流程如下:

HMODULE __stdcall LoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags){ //... SearchPath = BaseGetProcessDllPath( amp;dwFlags, (chFlags amp; LOAD_WITH_ALTERED_SEARCH_PATH) != 0 #63; DllName.Buffer : 0, 0, (int)amp;dwFlags); //... ntStatus = LdrLoadDll(SearchPath, (PULONG)amp;lpLibFileName, amp;DllName, amp;hFile);}

通过BaseGetProcessDllPath获得的路径是:

00072acc "C:\Users\xxx\Desktop;C:\Windows\"00072b0c "system32;C:\Windows\system;C:\Wi"00072b4c "ndows;.;C:\Windows\system32;C:\W"00072b8c "indows;C:\Windows\System32\Wbem;"00072bcc "C:\Windows\System32\WindowsPower"00072c0c "Shell\v1.0\"

下一步是使用LdrLoadDll值Dll。

2.2 LdrLoadDllNTSTATUS __stdcall LdrLoadDll(PWSTR SearchPath, PULONG LoadFlags, PUNICODE_STRING DllName, PVOID *BaseAddress){ //... if ( SearchPath ) { result = RtlInitUnicodeStringEx(amp;DestinationString, SearchPath); if ( result lt; 0 ) return result; NewSearchPath = amp;DestinationString; } else { NewSearchPath = amp;LdrpDefaultPath; } //... v7 = LdrpLoadDll(DllName, (int)NewSearchPath, v6, 1, 0, (int)amp;DllName); //... return v7;}

有一个名为LdrpDefaultPath的默认加载路径,路径信息如下:

0:000gt; dS LdrpDefaultPath00061560 "C:\Users\xxx\Desktop;C:\Windows\"000615a0 "system32;C:\Windows\system;C:\Wi"000615e0 "ndows;.;C:\Windows\system32;C:\W"00061620 "indows;C:\Windows\System32\Wbem;"00061660 "C:\Windows\System32\WindowsPower"000616a0 "Shell\v1.0\"2.3 LdrpLoadDllint __stdcall LdrpLoadDll(PCUNICODE_STRING Source, int a2, int a3, char a4, int a5, int a6){ //... if ( !LdrpInLdrInit ) RtlEnterCriticalSection(amp;LdrpLoaderLock); //... LdrpFindOrMapDll(*(PCUNICODE_STRING *)((char *)amp;v31 + 1), v29, a3, v27[0], (int)amp;v33, (int)amp;v31); //... if ( v21 amp; 0x1000000 ) v22 = LdrpCorProcessImports((void *)v21, v33); else v22 = LdrpProcessStaticImports(v33, v29); //... LdrpRunInitializeRoutines(0); //... if ( !LdrpInLdrInit ) RtlLeaveCriticalSection(amp;LdrpLoaderLock);}

该功能的排序过程如下:

获取加载锁定RtlEnterCriticalSection(amp;LdrpLoaderLock);尝试加载dll: LdrpFindOrMapDll。正在处理导入表信息。运行回调函数LdrpRunInitializeRoutines。释放锁RtlLeaveCriticalSection(amp;LdrpLoaderLock);。

这里值得注意的是,LdrpRunInitializeRoutines调用DllMain函数,调用栈信息如下:

# ChildEBP RetAddr Args to Child 00 0029d718 67f42c22 67ef0000 00000001 00000000 DllTest!DllMain 01 0029d75c 67f42def 67ef0000 00000001 00000000 DllTest!dllmain_dispatch+0xb2 02 0029d770 777b89d8 67ef0000 00000001 00000000 DllTest!_DllMainCRTStartup+0x1f 03 0029d790 777c5c41 67f3dcc5 67ef0000 00000001 ntdll!LdrpCallInitRoutine+0x1404 0029d884 777c052e 00000000 73bd12d3 777a7c9a ntdll!LdrpRunInitializeRoutines+0x26f05 0029d9f0 777c232c 0029da50 0029da1c 00000000 ntdll!LdrpLoadDll+0x4d106 0029da24 75b688ee 00072acc 0029da64 0029da50 ntdll!LdrLoadDll+0x9207 0029da5c 763d3c12 00000000 00000000 00000001 KERNELBASE!LoadLibraryExW+0x15a

加载dll时,将获取锁LdrpLoaderLock但是加载时会调用LdrpRunInitializeRoutines进入DllMain。

2.4 LdrpFindOrMapDll

加载映射dll的函数是LdrpFindOrMapDll。在加载Dll之前,这个函数需要确定Dll是否已经被加载。加载的dll是通过哈希表加载的,哈希表如下:

LIST_ENTRY LdrpHashTable[LDR_HASH_TABLE_ENTRIES];

实际上,每个LdrpHashTable都是由_ LDR _数据_表_条目的成员HashLinks链接的。其结构如下:

0:000gt; dt ntdll!_LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks : _LIST_ENTRY +0x008 InMemoryOrderLinks : _LIST_ENTRY +0x010 InInitializationOrderLinks : _LIST_ENTRY +0x018 DllBase : Ptr32 Void +0x01c EntryPoint : Ptr32 Void +0x020 SizeOfImage : Uint4B +0x024 FullDllName : _UNICODE_STRING +0x02c BaseDllName : _UNICODE_STRING +0x034 Flags : Uint4B +0x038 LoadCount : Uint2B +0x03a TlsIndex : Uint2B +0x03c HashLinks : _LIST_ENTRY //LdrpHashTable连接的列表结构 +0x03c SectionPointer : Ptr32 Void +0x040 CheckSum : Uint4B +0x044 TimeDateStamp : Uint4B +0x044 LoadedImports : Ptr32 Void +0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT +0x04c PatchInformation : Ptr32 Void +0x050 ForwarderLinks : _LIST_ENTRY +0x058 ServiceTagLinks : _LIST_ENTRY +0x060 StaticLinks : _LIST_ENTRY +0x068 ContextInformation : Ptr32 Void +0x06c OriginalBase : Uint4B +0x070 LoadTime : _LARGE_INTEGER

对于每个加载的DLL,将有两种形式:

dll名称加载。 dll全路径加载。

在LdrpFindLoadedDllByName中,会根据不同的负载匹配不同的情况:

如果使用dll名称加载,那么比较BaseDllName;使用RtlEqualUnicodeString. 如果使用dll全路径加载,那么比较FullDllName;使用RtlEqualUnicodeString.

例如:

0:000gt; dt ntdll!_LDR_DATA_TABLE_ENTRY 006f6270 +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x6f6358 - 0x6f5eb8 ] +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x6f6360 - 0x6f5ec0 ] +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x6f6e38 - 0x6f6368 ] +0x018 DllBase : 0x75b30000 Void +0x01c EntryPoint : 0x75b43273 Void +0x020 SizeOfImage : 0x110000 +0x024 FullDllName : _UNICODE_STRING "C:\Windows\syswow64\kernel32.dll" //全路径匹配这个 +0x02c BaseDllName : _UNICODE_STRING "kernel32.dll" //Dll名称匹配这个 +0x034 Flags : 0x84004 +0x038 LoadCount : 0xffff +0x03a TlsIndex : 0 +0x03c HashLinks : _LIST_ENTRY [ 0x77c148a0 - 0x77c148a0 ] +0x03c SectionPointer : 0x77c148a0 Void +0x040 CheckSum : 0x77c148a0 +0x044 TimeDateStamp : 0x589c961f +0x044 LoadedImports : 0x589c961f Void +0x048 EntryPointActivationContext : (null) +0x04c PatchInformation : (null) +0x050 ForwarderLinks : _LIST_ENTRY [ 0x718580 - 0x718580 ] +0x058 ServiceTagLinks : _LIST_ENTRY [ 0x6f62c8 - 0x6f62c8 ] +0x060 StaticLinks : _LIST_ENTRY [ 0x6f63f0 - 0x6f62f0 ] +0x068 ContextInformation : 0x77b4ef40 Void +0x06c OriginalBase : 0x7dd60000 +0x070 LoadTime : _LARGE_INTEGER 0x01d48576`ee26eab0

如果此处匹配成功,则不会再次加载Dll。

3. 总结3.1 同名DLL

进程可以加载同名的Dll吗?根据以上分析,有一个加载规则。

在LdrpFindLoadedDllByName中,会根据不同的负载匹配不同的情况:

如果使用dll名称加载,那么比较BaseDllName;使用RtlEqualUnicodeString. 如果使用dll全路径加载,那么比较FullDllName;使用RtlEqualUnicodeString.

那么,从这个规律来看,就是装载的方式:

int main(int args, char* argv[]){LoadLibraryW(L"C:\\DllTest.dll");LoadLibraryW(L"DllTest.dll");system("pause");return 0;}

这样,第二个应该LoadLibraryW(L"DllTest.dll ")不会被加载以匹配,如下所示:


那么我们用这种方式装载什么呢?

int main(int args, char* argv[]){LoadLibraryW(L"DllTest.dll");LoadLibraryW(L"C:\\DllTest.dll");system("pause");return 0;}

根据规则,它应该是可加载的,如下所示:


这样的话,在wow64的情况下,有时候会踩坑。例如,对于wow64路径,如果使用C:\ windows \ system32 \ kernel32.dll路径直接加载,那么LdrpFindLoadedDllByName将失败,因为FullDllName是WOW64使用的值:C: \ windows \ syswow64 \ kernel32。

3.2 死锁

我们知道,当调用Dllmain时,锁RtlEnterCriticalSection(amp;LdrpLoaderLock),所有调用DllMain的人都可能获得RtlEnterCriticalSection(amp;LdrpLoaderLock)的操作可能会导致死锁,例如:

直接调用LoadLibrary(Ex)。 调用CoInitializeEx(在CoInitializeEx底层会调用LoadLibraryEx)。 调用CreateThread(因为CreateThread会继续调用DllMain,稍有不慎可能就会死锁)。 调用GetModuleFileName、GetModuleHandle 等(底层获取RtlEnterCriticalSection(amp;LdrpLoaderLock)锁)。

关于LoadLibrary的深度案例详解,本文到此结束

0

精彩评论

暂无评论...
验证码 换一张
取 消