一:背景
1. 講故事
在dump分析的旅程中也會碰到一些讓我無法解釋的靈異現象,追過這個系列的朋友應該知道,上一篇我聊過 宇宙射線
導致的程式崩潰,後來我又發現了一例,而這一例恰恰是高鐵的 列控連鎖一體化
程式,所以更加讓我確定這是由於 電離輻射
干擾了計算機的 數字訊號
導致程式的bit翻轉,而這一篇也是一個我認為的 靈異現象
,拿出來給朋友們分享一下。
前段時間有位朋友找到我,說他的程式會偶發性崩潰,一直找不到原因很糾結,看我在這一塊非常有經驗讓我幫忙看一下怎麼回事,既然是有備而來自然dump也準備好了,接下來開始分析之旅吧。
二:WinDbg 分析
1. 為什麼會崩潰
要想分析崩潰的原因還得windbg自帶的自動化分析命令 !analyze -v
,輸出如下:
0:117> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
CONTEXT: (.ecxr)
rax=0000000000000001 rbx=0000000000000000 rcx=0000000000000002
rdx=000000000005001b rsi=000000000000000e rdi=00000161b1b8c718
rip=00007ffdd0961abd rsp=000000341547b370 rbp=000000341547b250
r8=0000000000000005 r9=000000000000003d r10=0000000000000000
r11=7007f0b8d350316a r12=0000000000000000 r13=0000000000000003
r14=000000341547b5c0 r15=0000000000000001
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
clr!_report_gsfailure+0x1d:
00007ffd`d0961abd cd29 int 29h
Resetting default scope
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 00007ffdd0961abd (clr!_report_gsfailure+0x000000000000001d)
ExceptionCode: c0000409 (Security check failure or stack buffer overrun)
ExceptionFlags: 00000001
NumberParameters: 1
Parameter[0]: 0000000000000002
Subcode: 0x2 FAST_FAIL_STACK_COOKIE_CHECK_FAILURE
SYMBOL_NAME: clr!_report_gsfailure+1d
...
卦中有一句話叫 Security check failure or stack buffer overrun
,淺層意思就是: 安全檢查失敗或緩衝區溢位
,行話就是:棧上的cookie遭到了破壞。
可能有些朋友對 cookie 不是很瞭解,這個cookie非web的cookie,而是在方法棧上藏的一個隨時值,在方法的退出前會檢查這個值有沒有被破壞,目的就是防止有人無意或者惡意攻擊執行緒棧,如果遭到破壞,會觸發 int 29
即 nt!KiRaiseSecurityCheckFailure
函式讓程式快速硬性崩潰。
如果有些朋友不明白,畫個圖如下:
2. cookie 被破壞了嗎
既然說 cookie 被破壞了,說明有棧溢位的情況,那到底溢位了什麼東西呢?這需要分析崩潰處附近的彙編程式碼才能知道,接下來使用 .ecxr ; k 3
切到崩潰前的上下文。
0:117> .ecxr ; k 3
rax=0000000000000001 rbx=0000000000000000 rcx=0000000000000002
rdx=000000000005001b rsi=000000000000000e rdi=00000161b1b8c718
rip=00007ffdd0961abd rsp=000000341547b370 rbp=000000341547b250
r8=0000000000000005 r9=000000000000003d r10=0000000000000000
r11=7007f0b8d350316a r12=0000000000000000 r13=0000000000000003
r14=000000341547b5c0 r15=0000000000000001
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
clr!_report_gsfailure+0x1d:
00007ffd`d0961abd cd29 int 29h
# Child-SP RetAddr Call Site
00 00000034`1547b370 00007ffd`d0977900 clr!_report_gsfailure+0x1d
01 00000034`1547b3b0 00007ffd`d097816d clr!RtlAllocateLUnicodeString+0xe0
02 00000034`1547b420 00007ffd`d09e1d06 clr!RtlDuplicateLUnicodeString+0x8d
...
卦中的資訊很豐富,說 clr 在 RtlAllocateLUnicodeString
函式退出階段時檢查 cookie 被破壞了,繼而程式快速崩潰,接下來需要反編譯 RtlAllocateLUnicodeString
函式,簡化後如下:
0:117> uf clr!RtlAllocateLUnicodeString
clr!RtlAllocateLUnicodeString:
00007ffd`d0977820 48895c2418 mov qword ptr [rsp+18h],rbx
00007ffd`d0977825 55 push rbp
00007ffd`d0977826 56 push rsi
00007ffd`d0977827 57 push rdi
00007ffd`d0977828 488bec mov rbp,rsp
00007ffd`d097782b 4883ec50 sub rsp,50h
00007ffd`d097782f 488b05d2777600 mov rax,qword ptr [clr!_security_cookie (00007ffd`d10df008)]
00007ffd`d0977836 4833c4 xor rax,rsp
00007ffd`d0977839 488945f8 mov qword ptr [rbp-8],rax
00007ffd`d097783d 488bfa mov rdi,rdx
00007ffd`d0977840 488bf1 mov rsi,rcx
00007ffd`d0977843 c745f0e50000c0 mov dword ptr [rbp-10h],0C00000E5h
00007ffd`d097784a 33db xor ebx,ebx
00007ffd`d097784c 4885d2 test rdx,rdx
00007ffd`d097784f 745f je clr!RtlAllocateLUnicodeString+0x90 (00007ffd`d09778b0) Branch
...
00007ffd`d09778f2 8bc3 mov eax,ebx
00007ffd`d09778f4 488b4df8 mov rcx,qword ptr [rbp-8]
00007ffd`d09778f8 4833cc xor rcx,rsp
00007ffd`d09778fb e820a1feff call clr!_security_check_cookie (00007ffd`d0961a20)
00007ffd`d0977900 488b9c2480000000 mov rbx,qword ptr [rsp+80h]
00007ffd`d0977908 4883c450 add rsp,50h
00007ffd`d097790c 5f pop rdi
00007ffd`d097790d 5e pop rsi
00007ffd`d097790e 5d pop rbp
00007ffd`d097790f c3 ret
卦中的資訊量還是非常大的,我們通讀下彙編程式碼理解下 安全檢查
中的一些基本元素以及邏輯是什麼? 步驟大概如下:
- _security_cookie
這個是 cookie 種子,可以用 dp 給撈出來,即下面的 0000d9998c879750
。
0:117> dp clr!_security_cookie L1
00007ffd`d10df008 0000d999`8c879750
- xor rax,rsp
將 cookie 種子和當前方法的棧頂指標rsp異或一下,目的就是做一個和棧幀相關的隨機值,當前的rsp即k上的000000341547b3b0
,用 windbg 計算之後為:
0:117> ? 00000034`1547b3b0 ^ 0000d999`8c879750
Evaluate expression: 239339632076000 = 0000d9ad`99c024e0
- qword ptr [rbp-8],rax
將異或後的 安全值
塞到 rbp-8
的棧位置,這裡的 rbp 由上面的彙編語句 mov rbp,rsp
賦值的,因為上面有三個push加一個call,所以rbp應該退掉4個0x8,最後計算的結果為棧位置000000341547b3f8 存的就是安全值,下面的輸出也可以確認。
0:117> ? 00000034`1547b420-0x8-0x8-0x8-0x8
Evaluate expression: 223695320064 = 00000034`1547b400
0:117> dp 00000034`1547b400-8 L1
00000034`1547b3f8 0000d9ad`99c024e0
- clr!_security_check_cookie
在方法退出時需要透過 _security_check_cookie
方法來檢查cookie是否損壞,核心程式碼為:
clr!RtlAllocateLUnicodeString+0xd2:
00007ffd`d09778f4 488b4df8 mov rcx,qword ptr [rbp-8]
00007ffd`d09778f8 4833cc xor rcx,rsp
00007ffd`d09778fb e820a1feff call clr!_security_check_cookie (00007ffd`d0961a20)
經過 windbg 計算 rcx=0000d9998c879750
,即 _security_cookie
值。
0:117> dp 00000034`1547b400-8 L1
00000034`1547b3f8 0000d9ad`99c024e0
0:117> ? 0000d9ad`99c024e0 ^ 00000034`1547b3b0
Evaluate expression: 239253510920016 = 0000d999`8c879750
接下來拿著 rcx= 0000d9998c879750
去反彙編下 _security_check_cookie 函式,簡化後如下:
0:117> uf clr!_security_check_cookie
00007ffd`d0961a20 483b0de1d57700 cmp rcx,qword ptr [clr!_security_cookie (00007ffd`d10df008)]
00007ffd`d0961a27 7510 jne clr!_security_check_cookie+0x19 (00007ffd`d0961a39)
00007ffd`d0961a29 48c1c110 rol rcx,10h
00007ffd`d0961a2d 66f7c1ffff test cx,0FFFFh
00007ffd`d0961a32 7501 jne clr!_security_check_cookie+0x15 (00007ffd`d0961a35)
00007ffd`d0961a34 c3 ret
00007ffd`d0961a35 48c1c910 ror rcx,10h
00007ffd`d0961a39 e962000000 jmp clr!_report_gsfailure (00007ffd`d0961aa0)
00007ffd`d0961aa0 48894c2408 mov qword ptr [rsp+8],rcx
00007ffd`d0961aa5 4883ec38 sub rsp,38h
00007ffd`d0961aa9 b917000000 mov ecx,17h
00007ffd`d0961aae ff15e4fa5a00 call qword ptr [clr!_imp_IsProcessorFeaturePresent (00007ffd`d0f11598)]
00007ffd`d0961ab4 85c0 test eax,eax
00007ffd`d0961ab6 7407 je clr!_report_gsfailure+0x1f (00007ffd`d0961abf)
00007ffd`d0961ab8 b902000000 mov ecx,2
00007ffd`d0961abd cd29 int 29h
程式碼邏輯非常簡單,還原成 C 大概如下:
void __fastcall _security_check_cookie(uintptr_t stackcookie)
{
if ((stackcookie == __security_cookie) && (stackcookie高四位 == "0000")) {
return;
}
else {
_report_gsfailure()
}
}
從C的邏輯看我們的 stackcookie=0000d9998c879750
完全滿足 if 條件,但不知道為什麼會走到這個 else 裡面去,無法想象。。。所以定性為 靈異事件!!!
4. 故事後續
把所有的值都推算完了之後,在不可能走到 else 的情況下還是走到了 else,這個真的很讓人無語+費解,過了幾天找朋友確認的時候,朋友又反饋了一個資訊,說電腦上的其他程式也會遇到這種情況,讓客戶重灌作業系統,目前還沒遇到問題。
所以我覺得這個問題可能是 作業系統層面
的問題,或者是 硬體層面
的問題,而且程式的異常是在 clr 層面,使用者程式碼是無法干涉的,程式中也沒有做 Pinvoke。
三:總結
一個是輻射導致的bit位翻轉,一個是不可能走到else的地方走了else,各個奇奇怪怪的事情,讓我的高階除錯之旅豐富多彩,大家覺得這個崩潰還有其他的可能性嗎?期待大家的留言。