首页
社区
课程
招聘
[原创]Terminate导致DLL产生阻塞的原因,深扒!
发表于: 2024-2-23 22:24 3754

[原创]Terminate导致DLL产生阻塞的原因,深扒!

2024-2-23 22:24
3754

死磕的续章

接着上篇文章最后的问题,苦思几天之后,终于找到了答案。原本是想重新复盘一下大佬[原创]调试TerminateThread导致的死锁的文章,但是遇到了新的最后描述的问题。


复现问题的源码

分析死锁而模拟的源代码可见https://gitee.com/sunrise_wsp/debug-demo-set/tree/master/DeadLocks/TerminateThread

关键testTerminateThread.cpp源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
while (1)
{
    OutputDebugString(L"looper start...\n");
 
    HMODULE hModule = LoadLibrary(_T("testDll.dll"));
    if (NULL == hModule)
    {
        OutputDebugString(L"Failed to load testDll.dll\n");
        return 0;
    }
 
    pfnGenerateThread pfn = (pfnGenerateThread)GetProcAddress(hModule, "GenerateThread");
    if (NULL == pfn)
    {
        OutputDebugString(L"Failed to GetProcAddress.\n");
        return 0;
    }
 
    HANDLE hThread = pfn();
 
    // give thread time to start up
    Sleep(1000);
 
    // terminate thread.
    BOOL bOk = TerminateThread(hThread, 0);
 
    // dead lock in this function...
    RunProcess(argv[0], NULL);
 
    FreeLibrary(hModule);
 
    OutputDebugString(L"looper end...\n");
}

DllMain.cpp关键源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
 
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        OutputDebugString(L"====> DLL_PROCESS_ATTACH called.\n");
        break;
    case DLL_THREAD_ATTACH:
        OutputDebugString(L"----> DLL_THREAD_ATTACH called.\n");
        break;
    case DLL_THREAD_DETACH:
        OutputDebugString(L"<---- DLL_THREAD_DETACH called.\n");
        // with LdrpLoaderLock held! sleep 5 seconds.
        Sleep(5000);
        break;
    case DLL_PROCESS_DETACH:
        OutputDebugString(L"<==== DLL_PROCESS_DETACH called.\n");
        break;
    }
    return TRUE;
}

问题重现

使用WinDbg启动调试TestDeadLocks.7z的TestExe.exe程序,发现是这样子的
图片描述

WinDbg上可以看到打印信息:
图片描述
然后程序一直在这里卡死了。


正式分析

祭WinDbg,因为上篇提到,使用!cs -l和!locks没有任何有效输出,因为之前大佬的文章的原因,先入为主,直接认为是交叉互锁的问题。但是,卡死不一定只是互锁。所以,直接~*kvn看看对各线程的堆栈情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
0:002> ~*kvn
 
   0  Id: 3b54.291c Suspend: 1 Teb: 007ea000 Unfrozen
 # ChildEBP RetAddr      Args to Child             
00 008ff114 772d1b6e     00000088 00000000 00000000 ntdll!NtWaitForSingleObject+0xc (FPO: [3,0,0])
01 008ff138 772ce1fd     f9de7a02 008ff1d4 009efa88 ntdll!LdrpDrainWorkQueue+0x14f (FPO: [Non-Fpo])
02 008ff180 772ceb3e     00000020 00000003 009efa88 ntdll!LdrpLoadDllInternal+0x89 (FPO: [SEH])
03 008ff418 772cf949     009efa88 009efa88 00000003 ntdll!LdrpLoadForwardedDll+0x10e (FPO: [SEH])
04 008ff4b4 772cfa35     008ff524 00000000 761020d4 ntdll!LdrpGetDelayloadExportDll+0x80 (FPO: [Non-Fpo])
05 008ff760 772ccc1b     00000000 75422870 761020d4 ntdll!LdrpHandleProtectedDelayload+0x95 (FPO: [Non-Fpo])
06 008ff7b8 7608ef20     76050000 760f7ecc 00000000 ntdll!LdrResolveDelayLoadedAPI+0x17b (FPO: [SEH])
07 008ff7d8 760917e2     760f7ecc 761020d4 00000000 RPCRT4!__delayLoadHelper2+0x28 (FPO: [Non-Fpo])
08 008ff800 769c5a69     008ff818 00000090 008ff85c RPCRT4!_tailMerge_bcryptprimitives_dll+0xd
09 008ff82c 769c5a10     009eef00 009eef8c 008ff85c SHELL32!TraceLoggingCorrelationVector::TraceLoggingCorrelationVector+0x31 (FPO: [Non-Fpo])
0a 008ff840 769c6cae     e325808c 009eef00 008ffa84 SHELL32!TelemetryCorrelationVectorServiceProvider::MakeSeed+0x27 (FPO: [Non-Fpo])
0b 008ff944 76a14b32     008ffa84 e3258040 77430000 SHELL32!CShellExecute::ExecuteNormal+0x31 (FPO: [Non-Fpo])
0c 008ff988 76a145f7     77430000 00000138 75734110 SHELL32!ShellExecuteNormal+0xc1 (FPO: [Non-Fpo])
0d 008ffa6c 009b10a8     008ffa84 009e6100 009e5860 SHELL32!ShellExecuteExW+0x97 (FPO: [1,51,4])
0e (Inline) --------     -------- -------- -------- TestExe!RunProcess+0x4c (Inline Function @ 009b10a8) (CONV: cdecl) [E:\debug-demo-set\DeadLocks\TerminateThread\TestExe\testTerminateThread.cpp @ 18]
0f 008ffac0 009b12b1     00000001 009e5860 009e6100 TestExe!wmain+0xa8 (FPO: [Non-Fpo]) (CONV: cdecl) [E:\debug-demo-set\DeadLocks\TerminateThread\TestExe\testTerminateThread.cpp @ 57]
10 (Inline) --------     -------- -------- -------- TestExe!invoke_main+0x1c (Inline Function @ 009b12b1) (CONV: cdecl) [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 90]
11 008ffb08 7571fcc9     007e7000 7571fcb0 008ffb74 TestExe!__scrt_common_main_seh+0xfa (FPO: [Non-Fpo]) (CONV: cdecl) [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
12 008ffb18 772e7c5e     007e7000 f9de70f6 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
13 008ffb74 772e7c2e     ffffffff 77308bf0 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
14 008ffb84 00000000     009b1339 007e7000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])
 
   1  Id: 3b54.394c Suspend: 1 Teb: 007f6000 Unfrozen
 # ChildEBP RetAddr      Args to Child             
00 00cafa5c 772d1b6e     00000088 00000000 00000000 ntdll!NtWaitForSingleObject+0xc (FPO: [3,0,0])
01 00cafa84 772cdb11     f99b709a 007e7000 00000000 ntdll!LdrpDrainWorkQueue+0x14f (FPO: [Non-Fpo])
02 00cafb18 772e65bb     f99b70f2 00000000 00000000 ntdll!LdrpInitializeThread+0x78 (FPO: [SEH])
03 00cafb70 772e64e1     00000000 00000000 00000000 ntdll!_LdrpInitialize+0x84 (FPO: [Non-Fpo])
04 00cafb7c 00000000     00cafb90 77280000 00000000 ntdll!LdrInitializeThunk+0x11 (FPO: [Non-Fpo])
 
#  2  Id: 3b54.1bcc Suspend: 1 Teb: 007f9000 Unfrozen
 # ChildEBP RetAddr      Args to Child             
00 00dafd8c 7732dba9     f98b763e 7732db70 7732db70 ntdll!DbgBreakPoint (FPO: [0,0,0])
01 00dafdbc 7571fcc9     00000000 7571fcb0 00dafe28 ntdll!DbgUiRemoteBreakin+0x39 (FPO: [Non-Fpo])
02 00dafdcc 772e7c5e     00000000 f98b75aa 00000000 KERNEL32!BaseThreadInitThunk+0x19 (FPO: [Non-Fpo])
03 00dafe28 772e7c2e     ffffffff 77308bf0 00000000 ntdll!__RtlUserThreadStart+0x2f (FPO: [SEH])
04 00dafe38 00000000     7732db70 00000000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

基本是已经可以看到是因为主线程发生了阻塞,且阻塞最底层的

00 008ff114 772d1b6e 00000088 00000000 00000000 ntdll!NtWaitForSingleObject+0xc (FPO: [3,0,0])

从这里可以看到NtWaitForSingleObject在等待一个88的句柄。之前一直卡在这里,不知道只是一个孤零零的句柄,该如何下手呢?终于,一天早上,上班之前,灵光一现,按照正向开发的思路,只要看看这个句柄是来自那个对象并在哪里产生的,问题就会有线索了。毕竟资源一般都有约定俗成的使用原则,谁申请,谁释放。这样相关联的嫌疑对象不久扒拉出来了嘛。而且等待的这个句柄是个事件句柄,应该是谁使用了它,但是因为某种原因又没有重置激活它,所以导致现在的局面。
好了,思路有了,开撸。通过定位772d1b6e处的反汇编代码:
图片描述
知道事件句柄保存在[esp+14h]中,这个明显是在栈上的变量,要么是来自参数,要么来自局部变量,简单看看上层栈帧的入参情况:
图片描述
显然了,没有DWORD的参数,那么这里动态反汇编调试就可以断定,该事件句柄就在ntdll!LdrpDrainWorkQueue函数的局部变量中。因为不想去计算ebp和esp的关系,所以直接上IDA吧,毕竟这个才是静态分析反汇编的神器。根据偏移找到对应IDA反汇编位置
图片描述
找到对应的变量后,同函数向上扒拉,果然有惊喜的发现:
图片描述
hEvent来自一个全局变量 _LdrpLoadCompleteEvent,这个是什么鬼,直接g.cn了一下,发现
图片描述
基本上,结合上面的打印语句,一切真相就了然了,DLL在使用TerminateThread结束之后,并没有走最后的DLL_PROCESS_DETACH,所以_LdrpLoadCompleteEvent(重点是全局的)就没有被正确重置激活,导致下次加载DLL时一直阻塞。


在ntdll.dll中_LdrpLoadCompleteEvent相关引用仅有三处:
图片描述
图片描述
图片描述
这三处也是和上面提到的IPC卡死流程是一致的。


总结:

  1. 曾经每次分析堆栈涉及到系统对象或函数就退避三舍,相信以后再也不会了
  2. 分析越深入,见到高质量文章和高手就越多,很多大佬还是很友好的
  3. 技术是需要坚持思考的,继续加油!
  4. 遥想当年,死不承认TerminateThread的危害,和同事各种诡辩,汗颜万分,今天算是了了心结了

参考:
DLL加载过程初探
调试 -- 危险的 Windows DllMain 死锁问题


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2024-2-24 08:31 被_THINCT编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (7)
雪    币: 1545
活跃值: (4598)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
不是exe调用TerminateThread结束在dll里产生的线程?
2024-2-23 23:52
0
雪    币: 1545
活跃值: (4598)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
freelibrary不会重置这个_LdrpLoadCompleteEvent?,TerminateThread结束线程后不是走DLL_PROCESS_DETACH吧,咋判定结束这个线程后dll就要走DLL_PROCESS_DETACH
2024-2-23 23:58
0
雪    币: 4315
活跃值: (8675)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
逆向爱好者 freelibrary不会重置这个_LdrpLoadCompleteEvent?,TerminateThread结束线程后不是走DLL_PROCESS_DETACH吧,咋判定结束这个线程后dll就要走 ...
TerminateThread间接导致了不走DLL_PROCESS_DETACH,你可以试试跳过Terminate试试看。
2024-2-24 08:30
1
雪    币: 1545
活跃值: (4598)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
_THINCT TerminateThread间接导致了不走DLL_PROCESS_DETACH,你可以试试跳过Terminate试试看。
用terminatethread结束在dll产生的线程会导致后面那个freeloadlibrary会失败dll没法释放,主线程再次加载dll会一直等待这个是这个意思么
2024-2-24 18:15
0
雪    币: 1545
活跃值: (4598)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
_THINCT TerminateThread间接导致了不走DLL_PROCESS_DETACH,你可以试试跳过Terminate试试看。
实际上没必要释放,我用这个freelibrary就没成功过hh
2024-2-24 18:18
0
雪    币: 3114
活跃值: (30886)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
感谢分享
2024-2-27 10:48
1
雪    币: 10248
活跃值: (4421)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
https://learn.microsoft.com/zh-cn/windows/win32/api/shellapi/nf-shellapi-shellexecuteexw
“请务必在持有加载程序锁时不要调用 ShellExecuteEx”
2024-3-22 13:47
0
游客
登录 | 注册 方可回帖
返回
//