記一次 .NET某工控 宇宙射線 導致程式崩潰分析

一線碼農發表於2023-12-25

一:背景

1. 講故事

為什麼要提 宇宙射線, 太陽耀斑 導致的程式崩潰呢?主要是昨天在知乎上看了這篇文章:莫非我遇到了傳說中的bug? ,由於 rip 中的0x41變成了0x61出現了bit位翻轉導致程式崩潰,截圖如下:


下面的評論大多是說由於 宇宙射線,這個太玄乎了,說實話看到這個 傳說bug 的提法,我還是挺興奮的,畢竟在我的分析旅程中,我也是真的遇到過,這篇就拿出來給大家分享吧,當時百思不得其解,真的是無語死了。

這位朋友找到我的時候,說程式會出現偶發性崩潰,自己在網上也發了很多帖子來尋找答案,最後都不了了之,問題確實太玄乎了,這一篇我們就開始這個奇妙之旅吧。

二:Windbg 分析

1. 為什麼會崩潰

找崩潰點比較簡單,使用windbg 自帶的 !analyze -v 命令去挖那個 EXCEPTION_POINTERS 結構體即可。


0:083> !analyze -v

CONTEXT:  (.ecxr)
rax=0000024f82c77341 rbx=000000f275dfe7f0 rcx=00007ffb05e55658
rdx=7ffb083d8c582d89 rsi=0000000000000000 rdi=000000f275dfe300
rip=00007ffb64be082f rsp=000000f275dfeaa0 rbp=000000007ffb05ee
 r8=0000024ff9bc0810  r9=deb6f5c6f59b3377 r10=1441a86c71655650
r11=ebbed78e94800000 r12=00007ffb05e55640 r13=0000000000000020
r14=0000024b26a3d9e0 r15=0000024f82c77340
iopl=0         ov up ei ng nz na po cy
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010a85
clr!WKS::gc_heap::background_mark_simple1+0x516:
00007ffb`64be082f 4c8b02          mov     r8,qword ptr [rdx] ds:7ffb083d`8c582d89=????????????????
Resetting default scope

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 00007ffb64be082f (clr!WKS::gc_heap::background_mark_simple1+0x0000000000000516)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000001
NumberParameters: 2
   Parameter[0]: 0000000000000000
   Parameter[1]: ffffffffffffffff
Attempt to read from address ffffffffffffffff

STACK_TEXT:  
000000f2`75dfeaa0 00007ffb`64be03a0     :  clr!WKS::gc_heap::background_mark_simple1+0x516
000000f2`75dfeb00 00007ffb`64be074e     :  clr!WKS::gc_heap::background_mark_simple+0x6d
000000f2`75dfeb30 00007ffb`64a45fc7     :  clr!WKS::gc_heap::background_promote+0x98
...

從卦中資料看,當前觸發了後臺GC,並且處於標記階段,在標記託管堆上的物件時發現了有壞物件,無奈只能觸發 CLR執行引擎異常,這也說明當前的託管堆是處於損壞狀態,可以用 !verifyheap 命令驗證一下。


0:083> !verifyheap
object 0000024f82c76b18: bad member 0000024F82C77F40 at 0000024F82C76B70
Last good object: 0000024F82C76AA0.
object 0000024f82c76ca8: bad member 0000024F82C77340 at 0000024F82C76CB0
Last good object: 0000024F82C76C58.
object 0000024f82c76fa8: bad member 0000024F82C77050 at 0000024F82C76FD0
Last good object: 0000024F82C76F88.
Could not request method table data for object 0000024F82C77050 (MethodTable: 00007FFB3C032138).
Last good object: 0000024F82C76FA8.

果然卦中的資料也驗證了這一點,託管堆上有三個壞物件,接下來抽一個用 !do 命令來驗證下。


0:083> !do 0000024f82c76b18
Name:        System.Windows.Forms.TreeNode
MethodTable: 00007ffb3c431af8
EEClass:     00007ffb3c488500
Size:        168(0xa8) bytes
File:        C:\xxxx\System.Windows.Forms.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
...
00007ffb3c431ed8  400263f       58 ....Forms.TreeNode[]  0 instance 0000024f82c77f40 children
...

0:083> !do 0000024f82c77f40
<Note: this object has an invalid CLASS field>
Invalid object

從錯誤資訊以及剛才卦中的資料表明 TreeNode.children 記憶體佈局被破壞了,這種情況大多是因為 MethodTable 不對了導致CLR識別不出這塊記憶體的物件,可以用 dp 驗證下。


0:083> dp 0000024f82c77f40 L4
0000024f`82c77f40  00007ffb`3c411ed8 00000000`00400008
0000024f`82c77f50  0000024f`82c56fa8 0000024f`82c57378
0:083> !dumpmt 00007ffb`3c411ed8
00007ffb3c411ed8 is not a MethodTable

從卦中的 00007ffb3c411ed8 is not a MethodTable 可以看到這個地址是錯誤的,那正確地址是什麼呢?如果有心細的朋友會看到 !do 的時候已經顯示了正確的方法表,即 00007ffb3c431ed8

接下來仔細觀察 00007ffb3c411ed800007ffb3c431ed8 這兩個地址,會發現一個是 3c41 一個是 3c43,真的是無語了,截圖如下:

一般來說,這種單bit位的翻轉也不像是用 PInvoke 的方式讓 C++ 破壞了 C# 的託管堆,也不像是什麼 hook 注入導致的,反正很神奇,為了拿更多證據可以在抽一個 壞物件 觀察下。


0:083> !do 0000024f82c76fa8
Name:        System.Windows.Forms.TreeNode
MethodTable: 00007ffb3c431af8
EEClass:     00007ffb3c488500
Size:        168(0xa8) bytes
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
...
00007ffb3c432138  4002636       28 ...eNodeImageIndexer  0 instance 0000024f82c77050 imageIndexer
...
0:083> !do 00007ffb`3c032138
<Note: this object has an invalid CLASS field>
Invalid object

0:083> dp 0000024f82c77050 L1
0000024f`82c77050  00007ffb`3c032138

從卦中資料看:方法表 00007ffb3c03213800007ffb3c432138 也是差了一個bit位,即 3c033c43 的差別。

2. 為什麼會翻轉

有些朋友可能說,你這資料是不是網路資料,比如有什麼 糾錯碼海明碼 之類的,其實 mt 的資料是嵌入到 image 中的,這塊資料一般在初始化的時候由 clr 構建好,後期不會有人去改寫的,可以用 !address 看下。


0:083> !address 00007ffb3c432138

Usage:                  Image
Base Address:           00007ffb`3c431000
End Address:            00007ffb`3c434000
Region Size:            00000000`00003000 (  12.000 kB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE
Type:                   01000000          MEM_IMAGE
Allocation Base:        00007ffb`3c400000
Allocation Protect:     00000080          PAGE_EXECUTE_WRITECOPY
Image Path:             C:\Windows\assembly\NativeImages_v4.0.30319_64\System.Windows.Forms\1534a59650e0fd08da0ed8931d9f6d5f\System.Windows.Forms.ni.dll
Module Name:            System_Windows_Forms_ni
Loaded Image Name:      
Mapped Image Name:      
More info:              lmv m System_Windows_Forms_ni
More info:              !lmi System_Windows_Forms_ni
More info:              ln 0x7ffb3c432138
More info:              !dh 0x7ffb3c400000


Content source: 1 (target), length: 1ec8

後來計劃讓朋友開啟 MDA 託管除錯助手去驗證,結果朋友給我反饋說開啟後,程式執行特別慢,這個很好理解,如果你的程式 PInvoke 過多,確實容易引發過高的 GC,所以能不能適應到各位的程式,還需要實際測試。

遺憾的這條路朋友沒有走通,所以尋找答案就遙遙無期了,最後也就不了了之,因為那時候我認為所有的使用者態異常都是軟體造成的。。。

三:總結

直到昨天看了這篇 莫非我遇到了傳說中的bug? 我現在有想法了,在下面可能的七種選項中:

  • 宇宙射線
  • 太陽耀斑
  • 地磁暴
  • 電離輻射
  • 硬體故障
  • 防毒軟體
  • 記憶體超頻

我覺得 記憶體超頻 引發的程式不穩定機率是最大的,不知道大家可有不同的看法?

圖片名稱

相關文章