一:背景 1. 講故事 這一期程式故障除了做原理分析,還順帶吐槽一下,熟悉我的朋友都知道我分析dump是免費的,但免費不代表可以濫用我的寶貴時間,我不知道有些人故意惡搞卡死是想幹嘛,不得而知,希望後面類似的事情越來越少吧!廢話不多說,我們來看看是如何被惡搞的。 二:WinDbg 分析 1. 程式是如 ...
一:背景
1. 講故事
這一期程式故障
除了做原理分析,還順帶吐槽一下,熟悉我的朋友都知道我分析dump是免費的,但免費不代表可以濫用我的寶貴時間,我不知道有些人故意惡搞卡死是想幹嘛,不得而知,希望後面類似的事情越來越少吧!廢話不多說,我們來看看是如何被惡搞的。
二:WinDbg 分析
1. 程式是如何卡死的
既然是窗體程式自然就是看主線程,我們使用 k 命令即可。
0:000> k
# Child-SP RetAddr Call Site
00 00000036`e1f7da18 00007ffe`70bf30ce ntdll!NtWaitForSingleObject+0x14
01 00000036`e1f7da20 00007ffd`eff74910 KERNELBASE!WaitForSingleObjectEx+0x8e
...
05 (Inline Function) --------`-------- coreclr!CLREventBase::Wait+0x12 [D:\a\_work\1\s\src\coreclr\vm\synch.cpp @ 412]
06 00000036`e1f7db20 00007ffd`f00c9afa coreclr!Thread::WaitSuspendEventsHelper+0xc8 [D:\a\_work\1\s\src\coreclr\vm\threadsuspend.cpp @ 4626]
07 (Inline Function) --------`-------- coreclr!Thread::WaitSuspendEvents+0x8 [D:\a\_work\1\s\src\coreclr\vm\threadsuspend.cpp @ 4663]
08 00000036`e1f7dbe0 00007ffd`f0009c80 coreclr!Thread::RareEnablePreemptiveGC+0x7d99a [D:\a\_work\1\s\src\coreclr\vm\threadsuspend.cpp @ 2414]
09 (Inline Function) --------`-------- coreclr!Thread::EnablePreemptiveGC+0x16 [D:\a\_work\1\s\src\coreclr\vm\threads.h @ 2044]
0a 00000036`e1f7dc20 00007ffd`f0075d10 coreclr!Thread::RareDisablePreemptiveGC+0xc8 [D:\a\_work\1\s\src\coreclr\vm\threadsuspend.cpp @ 2156]
0b 00000036`e1f7dca0 00007ffd`f0096a5f coreclr!JIT_ReversePInvokeEnterRare2+0x18 [D:\a\_work\1\s\src\coreclr\vm\jithelpers.cpp @ 5462]
0c 00000036`e1f7dcd0 00007ffd`907afe09 coreclr!JIT_ReversePInvokeEnterTrackTransitions+0xaf [D:\a\_work\1\s\src\coreclr\vm\jithelpers.cpp @ 5509]
0d 00000036`e1f7dd00 00007ffe`72e0e858 0x00007ffd`907afe09
0e 00000036`e1f7dd80 00007ffe`72e0e3dc user32!UserCallWinProcCheckWow+0x2f8
0f 00000036`e1f7df10 00007ffe`72e20c93 user32!DispatchClientMessage+0x9c
10 00000036`e1f7df70 00007ffe`730f0e64 user32!_fnDWORD+0x33
11 00000036`e1f7dfd0 00007ffe`70b31104 ntdll!KiUserCallbackDispatcherContinue
12 00000036`e1f7e058 00007ffe`72e21c0e win32u!NtUserGetMessage+0x14
13 00000036`e1f7e060 00007ffe`711e28f2 user32!GetMessageW+0x2e
...
1d 00000036`e1f7e460 00007ffd`f009ba53 xxx!xxx.App.Main+0x5e
...
2a 00000036`e1f7f140 00007ffe`4cd4e1e6 hostfxr!execute_app+0x2fa [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 145]
...
34 00000036`e1f7f890 00000000`00000000 ntdll!RtlUserThreadStart+0x21
從卦中的調用棧可以看到,主線程通過 GetMessageW 從消息隊列中拿到了數據,在處理時被 coreclr 押解到 WaitSuspendEventsHelper 處等待一個event事件,接下來看下這個方法的源碼到底是怎麼寫的?簡化後如下:
BOOL Thread::WaitSuspendEventsHelper(void)
{
if (m_State & TS_DebugSuspendPending)
{
ThreadState oldState = m_State;
while (oldState & TS_DebugSuspendPending)
{
ThreadState newState = (ThreadState)(oldState | TS_SyncSuspended);
if (InterlockedCompareExchange((LONG*)&m_State, newState, oldState) == (LONG)oldState)
{
result = m_DebugSuspendEvent.Wait(INFINITE, FALSE);
break;
}
oldState = m_State;
}
}
return result != WAIT_OBJECT_0;
}
從上面的代碼可以明顯的看到當前Thread 處於 TS_DebugSuspendPending 狀態,從名字上看貌似和調試有關,接下來查下這個枚舉的註解。
enum ThreadState
{
TS_DebugSuspendPending = 0x00000008, // Is the debugger suspending threads?
}
從註解看是因為調試器阻塞了這個線程,我去,哪裡來的調試器呢?這引起了我的巨大興趣。。。
2. 哪裡來的調試器
我剛開始是抱有巨大的善意,我以為他的程式被人惡意註入導致卡死,但分析下來我還是太單純了,繼續往下看吧,既然有調試器,一般來說PEB.BeingDebugged=Yes
狀態,命令的輸出結果也得到了證實。
0:000> !peb
PEB at 00000036e1c11000
...
BeingDebugged: Yes
...
Base TimeStamp Module
7ff7f5230000 65310000 Oct 19 18:08:00 2023 G:\xxx\C#\Projects\NugetApplications\Applications\xxx\bin\Debug\net6.0-windows\xxx.exe
...
我以為到這裡就可以告訴朋友答案,你的程式可能被人惡意註入了,但眼尖的我發現了一點異常,他的 xxx.exe
啟動目錄怎麼會有 \bin\Debug\net6.0-windows\
呢? 很明顯這是用 VS 直接跑起來的呀。。。。
3. 真的是 VS 跑起來的嗎
首先大家要知道 Visual Studio 是托管調試器,托管調試器在調試程式的時候會在 coreclr 層面設置一個全局變數 g_CORDebuggerControlFlags=0x0200
,這也是 Debugger.IsAttached
的底層判斷依據,不相信的朋友可以看下這個方法的底層實現:
FCIMPL0(FC_BOOL_RET, DebugDebugger::IsDebuggerAttached)
{
CORDebuggerAttached();
}
inline bool CORDebuggerAttached()
{
return (g_CORDebuggerControlFlags & DBCF_ATTACHED);
}
enum DebuggerControlFlag
{
DBCF_ATTACHED = 0x0200,
};
有了這些基礎知識之後,接下來就可以用 x 命令去驗證下。
看到上面的答案基本就可以實錘了,有些朋友可能還是不大相信,我們可以這樣看,VisualStudio 是 WPF 寫的,如果用 VS 來調試,那麼線程棧裡面應該會有很多類似的 VisualStudio 字元串。
從卦中看,你說正常發佈的程式怎麼可能會有 9 個 Microsoft.VisualStudio.DesignTools 字元串呢?
4. 這個dump是如何生成的
這個問題我相信可能有一些朋友會比較疑惑,其實沒什麼疑惑的,Visual Studio 有生成Dump的功能,操作步驟為: Debug -> Save Dump As...
, 接下來通過一個小例子觀察一下。
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine($"托管調試器:{Debugger.IsAttached}");
Console.WriteLine($"非托管調試器:{IsDebuggerPresent()}");
Console.ReadLine();
}
[DllImport("kernel32.dll")]
static extern bool IsDebuggerPresent();
}
調試起來後,截圖如下:
最後打開生成好的 Dump 文件,使用 kc 觀察主線程的輸出:
0:000> kc
# Call Site
00 ntdll!NtWaitForSingleObject
01 KERNELBASE!WaitForSingleObjectEx
02 coreclr!CLREventWaitHelper2
03 coreclr!CLREventWaitHelper
04 coreclr!CLREventBase::WaitEx
05 coreclr!CLREventBase::Wait
06 coreclr!Thread::WaitSuspendEventsHelper
07 coreclr!Thread::WaitSuspendEvents
08 coreclr!Thread::RareEnablePreemptiveGC
09 coreclr!Thread::EnablePreemptiveGC
0a coreclr!Thread::RareDisablePreemptiveGC
0b coreclr!Thread::DisablePreemptiveGC
...
19 ConsoleApp4!ConsoleApp4.Program.Main
...
30 ntdll!RtlUserThreadStart
從卦中看,正是停留在 WaitSuspendEventsHelper 函數里,一摸一樣,徹底被360°無死角的證實。。。。
三:總結
這是一次人為的故意惡搞,大概率就是想看下我到底是騾子是馬。。。我分析的結果對他和他所在的公司來說沒有任何價值,只能白白的浪費我的時間,真是分析多了,什麼樣的人都能遇到,謹以此文告知後來者,免費但請不要濫用!