导致DllMain中死锁的关键隐藏因子

article/2025/7/18 19:19:26

原文地址:https://blog.csdn.net/hczhiyue/article/details/18505087

        有了前面两节的基础,我们现在切入正题:研究下DllMain为什么会因为不当操作导致死锁的问题。首先我们看一段比较经典的“DllMain中死锁”代码。

//主线程中  
HMODULE h = LoadLibraryA(strDllName.c_str());  
// DLL中代码  
static DWORD WINAPI ThreadCreateInDllMain(LPVOID) {  return 0;  
}  BOOL APIENTRY DllMain( HMODULE hModule,  DWORD  ul_reason_for_call,  LPVOID lpReserved  )  
{  DWORD tid = GetCurrentThreadId();  switch (ul_reason_for_call)     {  case DLL_PROCESS_ATTACH: {  printf("DLL DllWithoutDisableThreadLibraryCalls_A:\tProcess attach (tid = %d)\n", tid);  HANDLE hThread = CreateThread(NULL, 0, ThreadCreateInDllMain, NULL, 0, NULL);  WaitForSingleObject(hThread, INFINITE);  CloseHandle(hThread);  }break;  case DLL_PROCESS_DETACH:  case DLL_THREAD_ATTACH:  case DLL_THREAD_DETACH:  break;  }  return TRUE;  
}  

        简要说下DLL中逻辑:设计该段代码的同学希望在DLL第一次被映射到进程内存空间时,创建一个工作线程,该工作线程内容可能很简单。为了尽可能简单,我们让这个工作线程直接返回0。这样从逻辑和效率上看,都不会因为我们的工作线程写的有问题而导致死锁。然后我们在DllMain中等待这个线程结束才从返回。

        粗略看这个问题,我们很难看出这个逻辑会导致死锁。但是事实就是这样发生了。我们跑一下程序,发现程序输出一下结果 后就停住了,光标在闪动,貌似还是在等待我们输入:

可是我们怎么敲击键盘都没有用:它死锁了。 我是在VS2005中调试该程序,于是我们可以Debug->Break All来冻结所有线程。

我们先查看主线程(3096)的堆栈 堆栈不长,

我全部列出来

17 	ntdll.dll!_KiFastSystemCallRet@0()
16 	ntdll.dll!_NtWaitForSingleObject@12()
15 	kernel32.dll!_WaitForSingleObjectEx@12()
14 	kernel32.dll!_WaitForSingleObject@8()
13 	DllWithoutDisableThreadLibraryCalls_A.dll!DllMain(HINSTANCE__ * hModule=0x10000000, unsigned long ul_reason_for_call=1, void * lpReserved=0x00000000)
12 	DllWithoutDisableThreadLibraryCalls_A.dll!__DllMainCRTStartup(void * hDllHandle=0x10000000, unsigned long dwReason=1, void * lpreserved=0x00000000)
11 	DllWithoutDisableThreadLibraryCalls_A.dll!_DllMainCRTStartup(void * hDllHandle=0x10000000, unsigned long dwReason=1, void * lpreserved=0x00000000)
10 	ntdll.dll!_LdrpCallInitRoutine@16()
9 	ntdll.dll!_LdrpRunInitializeRoutines@4()
8 	ntdll.dll!_LdrpLoadDll@24()
7 	ntdll.dll!_LdrLoadDll@16()
6 	kernel32.dll!_LoadLibraryExW@12()
5 	kernel32.dll!_LoadLibraryExA@12()
4 	kernel32.dll!_LoadLibraryA@4()
3 	DllMainSerial.exe!wmain(int argc=3, wchar_t * * argv=0x003b7000)
2 	DllMainSerial.exe!__tmainCRTStartup()
1 	DllMainSerial.exe!wmainCRTStartup()
0 	kernel32.dll!_BaseProcessStart@4()

我们看下这个堆栈。大致我们可以将我们程序分为4段:

0 启动启动我们程序 1~6 我们加载Dll4。

7~10 系统为我们准备DLL的加载。

11~17 DLL内部代码执行。

        我们关注一下14~17这段对WaitForSingleObject的调用逻辑。15、16步这个过程显示了Kernel32中的WaitForSingleObjectEx在底层是调用了NtDll中的NtWaitForSingleObject。在NtWaitForSingleObject内部,即17步,我们看到的“_KiFastSystemCallRet@0”。这儿要说明下,这个并不是意味着我们程序执行到这个函数。我们看下这个函数的代码

        KiFastSystemCallRet函数是内核态(Ring0层)逻辑回到用户态(Ring3层)的着陆点。与之相对应的KiFastSystemCall函数是用户态进入内核态必要的调用方法。因为内核态代码我们是无法查看的,所以动态断点只能设置到KiFastSystemCallRet开始处。所以实际死锁是因为NtWaitForSingleObject在底层调用了KiFastSystemCall进入内核,在内核态中死锁的。 

 我们在《DllMain中不当操作导致死锁问题的分析--死锁介绍》中介绍过,死锁存在的条件是相互等待。主线程中,我们发现其等待的是工作线程结束。那么工作线程在等待主线程什么呢?我们看下工作线程的调用堆栈

我们对这个堆栈进行编号 

6 	ntdll.dll!_KiFastSystemCallRet@0()
5 	ntdll.dll!_NtWaitForSingleObject@12()  + 0xc bytes
4 	ntdll.dll!_RtlpWaitForCriticalSection@4()  + 0x8c bytes
3 	ntdll.dll!_RtlEnterCriticalSection@4()  + 0x46 bytes
2 	ntdll.dll!__LdrpInitialize@12()  + 0xb4bf bytes
1 	ntdll.dll!_KiUserApcDispatcher@20()  + 0x7 bytes
0 	ntdll.dll!_RtlAllocateHeap@12()  + 0x9b48 bytes

        我们看到倒数两步(5、6)和主线程中最后两步(16、17)是相同的,即工作线程也是在进入内核态后死锁的。我们知道主线程在等工作线程结束,那么工作线程在等什么呢?我们追溯栈,请关注“ntdll.dll!__LdrpInitialize@12() + 0xb4bf bytes”处的代码

       我们看到,是因为_RtlEnterCriticalSection在底层调用了NtWaitForSingleObject。那么我们关注下_RtlEnterCriticalSection的参数_LdrpLoaderLock,它是什么?我们借助下IDA查看下LdrpInitialize反编译代码

……  
v4 = *(_DWORD *)(*MK_FP(__FS__, 0x18) + 0x30);  
v3 = *MK_FP(__FS__,0x18);  ……  *(_DWORD *)(v4 + 0xa0) = &LdrpLoaderLock;  if ( !(unsigned __int8)RtlTryEnterCriticalSection(&LdrpLoaderLock) )  {  ……  RtlEnterCriticalSection(&LdrpLoaderLock);  }  ……  if ( *(_DWORD *)(v4 + 0xc) )  {  ……  LdrpInitializeThread(a1);  }  else  {  
……  v17 = LdrpInitializeProcess(a1, a2, &v11, v14, v15);  
……  }  
……  

   由RtlTryEnterCriticalSection 可知LdrpLoaderLock是_RTL_CRITICAL_SECTION类型。在尝试进入临界区之前,LdrpLoaderLock将被保存到某个结构体变量v4的某个字段(偏移0xA0)中。那么v4是什么类型呢?这儿可能要科普下windows x86操作系统的一些知识:

        在windows系统中每个用户态线程都有一个记录其执行环境的结构体TEB(Thread Environment Block)。TEB结构体中第一个字段是一个TIB(ThreadInformation Block)结构体,该结构体中保存着异常登记链表等信息。在x86系统中,段寄存器FS总是指向TEB结构。于是FS:[0]指向TEB起始字段,也就是指向TIB结构体。我们用Windbg查看下TEB的结构体,该结构体很大,我只列出我们目前关心的字段

lkd> dt _TEB  
nt!_TEB  +0x000 NtTib            : _NT_TIB  +0x01c EnvironmentPointer : Ptr32 Void  +0x020 ClientId         : _CLIENT_ID  
……  

NtTib就是TIB结构体对象名。 我们再看下TIB结构体B

lkd> dt _NT_TIB  
nt!_NT_TIB  +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD  +0x004 StackBase        : Ptr32 Void  +0x008 StackLimit       : Ptr32 Void  +0x00c SubSystemTib     : Ptr32 Void  +0x010 FiberData        : Ptr32 Void  +0x010 Version          : Uint4B  +0x014 ArbitraryUserPointer : Ptr32 Void  +0x018 Self             : Ptr32 _NT_TIB  

该结构体其他字段不解释,我们只看最后一个字段(FS:[18])指向_NT_TIB结构体的指针Self。正如其名,该字段指向的是TIB结构体在进程空间中的虚拟地址。为什么要指向自己?那我们是否可以直接使用FS:[0]地址?不可以。举个例子:我用windbg挂载到我电脑上一个运行中的calc(计算器)。我们查看fs:[0]指向空间保存的值,7ffdb000是TIB的Self字段。

我们查看TIB结构体去匹配该地址指向的空间的。

可以看到7ffdb000所指向的空间的各字段的值和FS:[0]指向的空间的值一致。但是如果我们这样输入就会失败

介绍完这些后,我们再回到IDA反汇编的代码中。v4 = *(_DWORD*)(*MK_FP(__FS__, 0x18) + 0x30);这段中MK_FP不是一个函数,是一个宏。它的作用是在基址上加上偏移得出一个地址。于是MK_FP(__FS__, 0x18)就是FS:[0x18],即TIB的Self字段。在该地址再加上0x30得到的地址已经超过了TIB空间,于是我们继续查看TEB结构体

发现0x30偏移的是PEB(Process Environment Block)。

lkd> dt _PEB  
nt!_PEB  +0x000 InheritedAddressSpace : UChar  +0x001 ReadImageFileExecOptions : UChar  
……  
+0x09c GdiDCAttributeList : Uint4B  +0x0a0 LoaderLock       : Ptr32 Void  +0x0a4 OSMajorVersion   : Uint4B  

可以发现该结构体偏移0xa0处是一个名字为LoaderLock的变量。 《windows核心编程》中有关于DllMain序列化执行的讲解,大致意思是:线程在调用DllMain之前,要先获取锁,等DllMain执行完再解开这个锁。这样不同线程加载DLL就可以实现序列化操作。而在微软官方文档《Best Practices for Creating DLLs》中也有对这个说法的佐证

The DllMain entry-point function. This function is called by the loader when it loads or unloads a DLL. 
The loader serializes calls to DllMain so that only a single DllMain function is run at a time .  

其中还有段关于这个锁的介绍

The loader lock. This is a process-wide synchronization primitive that the loader uses to ensure serialized loading of DLLs. 
Any function that must read or modify the per-process library-loader data structures must acquire this lock 
before performing such an operation. 
The loader lock is recursive, which means that it can be acquired again by the same thread. 

在该文中多处对这个锁的说明值暗示这个锁是PEB中的LoaderLock。

        那么刚才为什么要*(_DWORD *)(v4 + 0xa0) = &LdrpLoaderLock;?因为该LdrpLoaderLock是进程内共享的变量。这样每个线程在执行初期,会先进入该 临界区,从而实现在进程内DllMain的执行是序列化的。于是我们得出以下结论: 进程内所有线程共用了同一个临界区来序列化DllMain的执行。

        结合《DllMain中不当操作导致死锁问题的分析--进程对DllMain函数的调用规律的研究和分析》中介绍的规律 二 线程创建后会调用已经加载了的DLL的DllMain,且调用原因是DLL_THREAD_ATTACH。 我们发现

HANDLE hThread = CreateThread(NULL, 0, ThreadCreateInDllMain, NULL, 0, NULL);  
WaitForSingleObject(hThread, INFINITE);  

主线程进入临界区去调用DllMain时进入了临界区,而工作线程也要进入临界区去执行DllMain。但是此时临界区被主线程占用,工作线程便进入等待状态。而主线程却等待工作线程退出才退出临界区。于是这就是死锁产生的原因。


http://chatgpt.dhexx.cn/article/wllunDlK.shtml

相关文章

DllMain详解

DllMain详解 源码下载: DLL – 动态链接库 DllMainTest – 测试DLL的DllMain 1 DLL的进入/退出函数 1.1 DllMain简介 跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,就是DllMain。以“DllMain”为关键字,来看看MSD…

Flash 加密和破解

关于Flash(swf),我们需要明确一点: ***Flash字节码的意义都是公开的 所以如果cracker真的有足够的耐心他最终还是可以破解掉你的Flash。我们能做的只是尽量提高Flash被破解的门槛让cracker破解它需要消耗的时间大于自己更新版本的…

flashfxp是ftp软件吗,你知道flashfxp是ftp软件吗

flashfxp是ftp软件吗?当然。ftp是一种文件传输协议,用于互联网双向传输,控制文件下载空间在服务器复制文件从本地计算机或本地上传文件复制到服务器上。而FlashFXP是一款功能强大的FXP/FTP软件,集成了其它优秀的FTP软件的优点&…

pfx证书解析公钥私钥

从pfx中获取CA证书 openssl pkcs12 -in test.pfx -nodes -out server.pem 输入证书密码 提取公钥 X.509格式 openssl x509 -in server.pem -out public.cer RSA格式 openssl rsa -in server.pem -outform PEM -pubout -out public.pem 提取私钥 openssl rsa -in server…

提权学习:第三方软件提权(FlashFXP 替换文件漏洞提权)

当你的才华 还撑不起你的野心时 那你就应该静下心来学习 目录 FlashFXP 替换文件漏洞提权 0x01 前言 介绍 用途 特点 0x02 环境配置 0x03 FlashFXP 提权思维导图 0x04 操作演示 FlashFXP 替换文件漏洞提权 0x01 前言 介绍 FlashFXP是一个功能强大的 FXP/FTP 软件&…

用FlashFXP上传文件到SSH服务器,附FlashFXP显示中文乱码的解决方法

用SSH Secure Shell能上传文件,又能输入命令,而且上传文件的时候不容易无故断开连接。但是它缺少续传的功能,不太适合上传比较大的文件;而且它在与linux系统SSH登陆时不方便支持中文,所以这里我向大家推荐FlashFXP来实…

flashfxp链接Linux

使用FLASHFXP链接我们的Linux操作系统,可以方便实现文件的上传下载到Linux中。在我们的Linux能成功链接网络后,利用ifconfig命令设置ip、掩码、网关。 设置ip与掩码:ifconfig eth0 192.168.1.114 netmask 255.255.255.0,设置网关…

FlashFXP使用阿里云ECS私钥登录

前言 最近购买了一个阿里云服务器主机,需要使用到FTP上传文件到服务器,因为我一直习惯使用FlashFXP上传文件,使用的是SFTP,所以没有在额外的安装FTP的服务,但是发现阿里云的ECS在SFTP模式下直接使用密码和用户名登录会报错。下面是…

NET主流ORM框架分析

接上文我们测试了各个ORM框架的性能,大家可以很直观的看到各个ORM框架与原生的ADO.NET在境删改查的性能差异。这里和大家分享下我对ORM框架的理解及一些使用经验。 ORM框架工作原理 所有的ORM框架的工作原理都离不开下面这张图,只是每个框架的实现程度不…

Linux USB总线驱动框架分析

1、USB驱动引入 USB(全称 Universal Serial Bus,通用串行总线),已经成为PC及嵌入式设备中最常用、最便捷的通信接口。Linux USB子系统较为庞大,本文主要对Linux系统下的USB总线驱动框架进行概述,重点的细节…

Django框架详解

一、MVC框架 1、MVC框架核心思想: 分工和解耦 2、web MVC举例: 以通过浏览器注册用户信息为例: M:Model,模型, 和数据库进行交互。V:View,视图, 产生html页面。C:Controller,控制器, 接收请求,进行处…

几种数据可视化框架分析

根据下面各个框架本身的特性,以及各自的优缺点,推荐使用D3或者ECharts作为数据可视化的首选框架,某些小特性可以辅用其他小框架实现。具体每个框架的说明和分析如下: D3.js D3是指数据驱动文档(Data-Driven Documents)。D3.js是一…

常见威胁建模框架分析与比较

1 概述 威胁建模的方法最初是为了帮助开发更多的安全的操作系统,但已经开发的大量威胁建模方法,有些只关注软件发展,有些仅涵盖业务或组织的风险和威胁,另有一些可能是技术性的,不同的威胁模型都在基于不同的目的而开…

某商城框架分析

开篇 最近做了一个商城系统,从系统的需求,到系统的选型以及框架和各种技术的研究和使用,我都参与其中,所以,分享一下这个系统. 框架 框架如下: 说明: 表现层 用户分为两种,PC端用户和移动端用户; 其中PC端包括 商城首页,商品类目,购物车,订单,商品展示,会员中心等…

Cgroup框架分析

cgroups概念扫盲 关于cgroups本文主要分析的是cgroups整体框架,不涉及对各个子系统的介绍和cgroupsfs的介绍,在分析cgroup整体框架的时候,首先我们需要对cgroups中的一些概念有所深刻认识,只有在这个基础上,才能明白为…

音视频常见播放器框架分析

音视频面试必问:如何设计一个播放器 1.常见播放器框架分析 2.如何解决播放端延迟的问题 3.音视频如何做同步 4.快进、快退、逐帧播放如何实现 音视频开发技术精讲《常见播放器框架分析》 播放器框架 SDL2播放音频 使用SDL播放解码后的音频数据,SDL播放…

商业分析经典框架

商业分析经典框架 文章目录 商业分析经典框架1.行业怎么盈利现状分析PEST分析产业链分析 2.行业里谁在挣钱竞争分析标杆公司分析竞争格局分析细分机会分析 3.企业还能盈利么发展分析商业化模式演变新技术/产品新用户新市场 总结 1.行业怎么盈利 现状分析 PEST分析 这个行业在…

Linux内核4.14版本——alsa框架分析(1)—alsa简介

目录 一,ALSA声音编程介绍 二,ALSA历史 三,数字音频基础 四,ALSA基础 五,ALSA体系结构 六,设备命名 七,声音缓存和数据传输 八,Over and Under Run 九,一个典型…

sFuzz源代码框架分析

sFuzz是发表在软件工程顶会ICSE2020年论文(sFuzz:An Efficient Adaptive Fuzzer for Solidity Smart Contracts)中实现的工具。sFuzz通过基于距离的启发式策略的模糊测试技术实现对合约漏洞检测。 sFuzz基于C实现,集成了以太坊和模…

Linux V4L2 框架分析

背景 Read the fucking source code! --By 鲁迅A picture is worth a thousand words. --By 高尔基 说明: Kernel版本:4.14ARM64处理器,Contex-A53,双核使用工具:Source Insight 3.5, Visio 1. 概述 V…