iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >LoadLibrary深入案例详解
  • 614
分享到

LoadLibrary深入案例详解

2024-04-02 19:04:59 614人浏览 独家记忆
摘要

LoadLibrary流程分析 在windows开发中,我们都有过一个规定:在DllMain中不应该处理过于复杂的事情,防止死锁的发生。 那么,到底为什么DllMain中容易导致死锁

LoadLibrary流程分析

windows开发中,我们都有过一个规定:在DllMain中不应该处理过于复杂的事情,防止死的发生。

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

1. 使用

我们来看一下LoadLibrary怎么使用的,由于这个函数底层是调用LoadLibraryEx我们看LoadLibraryEx的使用情况。

1.1 声明


HMODULE WINapi LoadLibraryEx(
  _In_       LPCTSTR lpFileName,
  _Reserved_ HANDLE  hFile,
  _In_       DWord   dwFlags
);

这里主要第三个参数使用起来有需要注意的地方:

  1. DONT_RESOLVE_DLL_REFERENCES : 这个标志用于告诉系统将DLL映射到调用进程的地址空间中,但是不调用DllMain并且不加载依赖Dll(只映射自己本身)。
  2. LOAD_LIBRARY_AS_DATAFILE : 这个标志与DONT_RESOLVE_DLL_REFERENCES标志相类似,因为系统只是将DLL映射到进程的地址空间中,就像它是数据文件一样。系统并不花费额外的时间来准备执行文件中的任何代码。
  3. LOAD_LIBRARY_SEARCH_USER_DIRS : 搜索路径的使用使用ADDDllDirectory和SetDllDirectory设置的路径(保护Dll自己和依赖Dll)。
  4. LOAD_LIBRARY_SEARCH_SYSTEM32 : 从%windows%\system32加载Dll和其依赖项。
  5. LOAD_LIBRARY_SEARCH_APPLICATioN_DIR : 应用程序安装路径搜索Dll和其依赖项。
  6. LOAD_WITH_ALTERED_SEARCH_PATH : 按照如下目录搜索:
    1. 进程当前目录。
    2. Windows的系统目录。
    3. 16 位Windows的系统目录。
    4. Windows目录。
    5. path环境变量目录。

默认情况下,LoadLibrary和LoadLibrary按照如下目录搜索:

  1. 进程当前目录。
  2. SetDllDirectory设置的文件夹路径。
  3. Windows的系统目录。
  4. 16 位Windows的系统目录。
  5. Windows目录。
  6. path环境变量目录。

1.2 SetDllDirectoryW

这个函数的实现如下:

在这里插入图片描述

其中KernelBaseGetGlobalData返回的结果信息如下:

在这里插入图片描述

如下信息为:


0:003> dd KERNELBASE!KernelBaseGlobalData
75b155a0  00000000 00000000 00160014 7f9a1240
75b155b0  00280026 7f9a1260 00000000 00000000
75b155c0  00000000 00e60000 75b156c0 7fff0000
75b155d0  00c4b494 0000011a 00cc1914 0000012c
75b155e0  00cb9f34 00000253 00cc2658 00cc5e20
75b155f0  00000000 00e84380 0000000f ffffffff
75b15600  ffffffff 00000000 00000000 00000000
75b15610  020007d0 75950000 00000000 00000000
0:003> du 7f9a1240
7f9a1240  "C:\Windows"
0:003> 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(
                       &dwFlags,
                       (chFlags & LOAD_WITH_ALTERED_SEARCH_PATH) != 0 ? DllName.Buffer : 0,
                       0,
                       (int)&dwFlags);

    //...
    ntStatus = LdrLoadDll(SearchPath, (PULONG)&lpLibFileName, &DllName, &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 LdrLoadDll


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

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


0:000> dS LdrpDefaultPath
00061560  "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 LdrpLoadDll


int __stdcall LdrpLoadDll(PCUNICODE_STRING Source, int a2, int a3, char a4, int a5, int a6)
{
    //...
    if ( !LdrpInLdrInit )
      RtlEnterCriticalSection(&LdrpLoaderLock);
    
    //...
    LdrpFindORMapDll(*(PCUNICODE_STRING *)((char *)&v31 + 1), v29, a3, v27[0], (int)&v33, (int)&v31);

    //...
    if ( v21 & 0x1000000 )
        v22 = LdrpcorProcessImports((void *)v21, v33);
    else
        v22 = LdrpProcessStaticImports(v33, v29);
    //...

    LdrpRunInitializeRoutines(0);

    //...

    if ( !LdrpInLdrInit )
      RtlLeaveCriticalSection(&LdrpLoaderLock);
}

这个函数的整理流程如下:

获取加载锁RtlEnterCriticalSection(&LdrpLoaderLock);尝试加载dll: LdrpFindOrMapDll。处理导入表信息。运行回调函数LdrpRunInitializeRoutines。释放锁RtlLeaveCriticalSection(&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+0x14
04 0029d884 777c052e 00000000 73bd12d3 777a7c9a ntdll!LdrpRunInitializeRoutines+0x26f
05 0029d9f0 777c232c 0029da50 0029da1c 00000000 ntdll!LdrpLoadDll+0x4d1
06 0029da24 75b688ee 00072acc 0029da64 0029da50 ntdll!LdrLoadDll+0x92
07 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_DATA_TABLE_ENTRY的成员HashLinks链接起来,结构如下:


0:000> 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,都会有两种形式:

  1. dll名称加载。
  2. dll全路径加载。

在LdrpFindLoadedDllByName也会根据不同的加载来匹配不同的情况:

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

例如:


0:000> 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也会根据不同的加载来匹配不同的情况:

  1. 如果使用dll名称加载,那么比较BaseDllName;使用RtlEqualUnicodeString.
  2. 如果使用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;
}

按照规则来说,应该是可以加载的,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aVEtDGkf-1577506406764)(assets/images/2019-12-28-12-05-34.png)]

这个中情况在wow64 情况下,有时候会踩到坑,例如,对于wowo64路径,如果直接使用C:\Windows\system32\kernel32.dll路径加载,那么LdrpFindLoadedDllByName一定会失败,因为FullDllName为wow64使用的值:C:\Windows\syswow64\kernel32.dll.

3.2 死锁

我们知道,在调用Dllmain的时候,会占用锁RtlEnterCriticalSection(&LdrpLoaderLock),所有凡是在DllMain中调用可能获取RtlEnterCriticalSection(&LdrpLoaderLock)的操作,就可能会导致死锁,例如:

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

到此这篇关于LoadLibrary深入案例详解的文章就介绍到这了,更多相关LoadLibrary详解内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: LoadLibrary深入案例详解

本文链接: https://www.lsjlt.com/news/132750.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • LoadLibrary深入案例详解
    LoadLibrary流程分析 在Windows开发中,我们都有过一个规定:在DllMain中不应该处理过于复杂的事情,防止死锁的发生。 那么,到底为什么DllMain中容易导致死锁...
    99+
    2024-04-02
  • C# 关于LoadLibrary的疑问详解
    关于 LoadLibrary 的疑问 Win32 API 中 LoadLibrary 函数的功能是加载某个库文件(通常是 dll 文件),然后返回 HMODULE 句柄,可以使用两个...
    99+
    2024-04-02
  • Python-OpenCV深度学习入门示例详解
    目录0.前言1.计算机视觉中的深度学习简介1.1深度学习的特点1.2深度学习大爆发2.用于图像分类的深度学习简介3.用于目标检测的深度学习简介4.深度学习框架keras介绍与使用4....
    99+
    2024-04-02
  • PHP 性能优化:深入案例分析
    php 性能优化通过分析和优化,可以显著提升网站性能。优化措施包括查询优化、数据缓存、i/o 优化。案例研究表明,这些优化措施可减少页面加载时间,提升数据库查询效率,并增强用户体验。通过...
    99+
    2024-05-10
    php 性能优化 redis
  • PostgreSQLUSAGE和SELECT权限案例深入分析
    目录起因分析总结起因 因为项目需求,需要学习PostgreSQL,本人之前学习过MYSQL,临时快速上手,尽快的去学习项目需要PostgREST, 在学习PostgREST的第一个起...
    99+
    2023-05-15
    PostgreSQL USAGE权限 PostgreSQL SELECT权限
  • Debezium系列之:深入理解Debezium Server Operator和实际应用Debezium Server Operator案例详解
    Debezium系列之:深入理解Debezium Server Operator和实际应用Debezium Server Operator案例详解 一、认识Debezium Server Operator 二、深入理解Debe...
    99+
    2023-08-30
    Debezium系列 Debezium Server Operator 实际应用案例详解
  • 深入了解Java.Util.Date详情
    目录Java.Util.Date有什么问题什么是“瞬间”常见问题如何将Date日期转换为其他时区?如何将Date日期转换为其他格式?前言: 很少有类能像jav...
    99+
    2024-04-02
  • SQL Server批量插入数据案例详解
    在SQL Server 中插入一条数据使用Insert语句,但是如果想要批量插入一堆数据的话,循环使用Insert不仅效率低,而且会导致SQL一系统性能问题。下面介绍SQL Serv...
    99+
    2024-04-02
  • MyBatis框架简介及入门案例详解
    目录前言MyBatis简介快速入门映射文件sql片段与resultMapMyBatis的增删改查1.添加操作2.修改操作3.删除操作前言 传统的JDBC操作数据库都是通过写一个jav...
    99+
    2022-11-13
    MyBatis 简介 MyBatis 案例
  • 深入解析Java类加载的案例与实战教程
    目录一、Tomcat类加载器架构二、动态代理的原理三、Java语法糖的改变本篇文章主要介绍Tomcat类加载器架构,以及基于类加载和字节码相关知识,去分析动态代理的原理。 一、Tom...
    99+
    2024-04-02
  • PostgreSQL USAGE和SELECT权限案例深入分析
    目录起因分析总结起因 因为项目需求,需要学习PostgreSQL,本人之前学习过mysql,临时快速上手,尽快的去学习项目需要PostgREST, 在学习PostgREST的第一个起步案例 教程 0 - 让它运行起来 的...
    99+
    2023-04-12
    PostgreSQL USAGE权限 PostgreSQL SELECT权限
  • Java ResourceBundle案例详解
    JAVA中ResourceBundle使用详解 这个类主要用来解决国际化和本地化问题。国际化和本地化可不是两个概念,两者都是一起出现的。可以说,国际化的目的就是为...
    99+
    2024-04-02
  • JavaScript offsetParent案例详解
    1. offsetParent定义:那么offsetParent就是距离该子元素最近的进行过定位的父元素(position:absolute  relative fixed...
    99+
    2024-04-02
  • Java BigDecimal案例详解
    引言   float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提...
    99+
    2024-04-02
  • Java Assert.assertEquals案例详解
    junit.framework包下的Assert提供了多个断言方法. 主用于比较测试传递进去的两个参数. Assert.assertEquals();及其重载方法: 1. 如果两者一...
    99+
    2024-04-02
  • Java Map.entry案例详解
       Map.entrySet() 这个方法返回的是一个Set<Map.Entry<K,V>>,Map.Entry 是Map中的一个接口,...
    99+
    2024-04-02
  • Java WeakHashMap案例详解
    WeakHashMap 继承于AbstractMap,实现了Map接口。 和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映...
    99+
    2024-04-02
  • C# TreeNode案例详解
    目录添加节点删除修改方法1:方法二:添加节点 private void Form1_Load(object sender, EventArgs e) { tree...
    99+
    2024-04-02
  • Java Spring @Lazy延迟注入源码案例详解
    前言 有时候我们会在属性注入的时候添加@Lazy注解实现延迟注入,今天咱们通过阅读源码来分析下原因 一、一个简单的小例子 代码如下: @Service public class ...
    99+
    2024-04-02
  • Java SpringSecurity入门案例与基本原理详解
    目录1、入门案例1.1、创建SpringBoot项目1.2、勾选对应的maven依赖1.3、编写Controller路由1.4、启动项目2、基本原理2.1、Security的本质2....
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作