一:背景
1. 講故事
前幾天有位朋友找到我,說他的機器記憶體在不斷的上漲,但在工作管理員中查不出是哪個程式吃的記憶體,特別奇怪,截圖如下:
在我的分析旅程中都是使用者態模式的記憶體洩漏,像上圖中的異常徵兆已經明確告訴你了,不是使用者態程式吃的記憶體,那就是核心態程式吃的,比如:
- 某些驅動程式
- 作業系統
從機率上來說一般都是某些第三方程式記憶體洩露導致的,這一篇我們就來聊一聊這種問題該如何解決。
二:核心模式堆洩露分析
1. 驅動程式是如何分配記憶體的
相信有很多朋友都知道,使用者態的程式是直接或者間接的呼叫 VirtualAlloc
方法來向作業系統要記憶體,包括 C# 的 GC 堆也是一樣,它的方法簽名如下:
LPVOID VirtualAlloc(
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
那核心中的驅動程式是如何向作業系統要記憶體的呢?一般都是呼叫 ExAllocatePool2
方法來要記憶體的,簽名如下:
DECLSPEC_RESTRICT PVOID ExAllocatePool2(
POOL_FLAGS Flags,
SIZE_T NumberOfBytes,
ULONG Tag
);
上面有兩個引數要詳細解釋一下:
- Flags 引數
一般用的多的就是 POOL_FLAG_NON_PAGED
和 POOL_FLAG_PAGED
兩種,前者表示分配的記憶體是需要永久駐留記憶體,不可以交換到硬碟的。後者分配的記憶體是可以交換到硬碟的。
- Tag 引數
這個引數的本意就是方便日後洞察記憶體洩露的,它強行讓一塊記憶體和這個 Tag(4byte的ascii 字串)
做了強繫結,到時候透過這個 tag 就知道是誰分配的記憶體。
2. 製造核心模式堆洩露
為了能夠讓驅動程式洩露,可以使用微軟提供的 NotMyFault
工具,這個工具利用 myfault.sys
驅動不斷的向作業系統分配記憶體。官方網址為:https://learn.microsoft.com/zh-cn/sysinternals/downloads/notmyfault
開啟 myfault 工具然後輸入 40M/s 的洩露,並分配在非換頁池
中,同時配置下核心態轉儲dump
, 程式碼和截圖參考如下:
ExAllocatePool2(POOL_FLAG_NON_PAGED,40*1024*1024,"Leak");
在洩露的過程中,透過 Process Explorer 很明顯的發現提交了 6.7G 的記憶體,其中有 4.9G 是在 NonPaged 中,即透過上圖中的 POOL_FLAG_NON_PAGED 標記分配的,截圖如下:
接下來在 MyFault 上切換到 Crash 選項卡,強行讓作業系統藍色畫面來生成 dump 檔案。
3. dump 分析
拿到dump後,先透過 !vm 觀察下作業系統級的虛擬記憶體的分佈情況。
3: kd> !vm
...
Physical Memory: 2069421 ( 8277684 Kb)
Available Pages: 445015 ( 1780060 Kb)
ResAvail Pages: 707292 ( 2829168 Kb)
Locked IO Pages: 0 ( 0 Kb)
Free System PTEs: 4295052431 (17180209724 Kb)
...
Modified Pages: 11479 ( 45916 Kb)
Modified PF Pages: 11479 ( 45916 Kb)
Modified No Write Pages: 0 ( 0 Kb)
NonPagedPool Usage: 1219892 ( 4879568 Kb)
NonPagedPoolNx Usage: 24512 ( 98048 Kb)
NonPagedPool Max: 4294967296 (17179869184 Kb)
PagedPool Usage: 32907 ( 131628 Kb)
PagedPool Maximum: 4294967296 (17179869184 Kb)
...
NonPagedPool Commit: 1246469 ( 4985876 Kb)
...
Sum System Commit: 1409562 ( 5638248 Kb)
Total Private: 279673 ( 1118692 Kb)
********** Sum of individual system commit + Process commit exceeds overall commit by 1952 Kb ? ********
Committed pages: 1688747 ( 6754988 Kb)
Commit limit: 4166573 ( 16666292 Kb)
從卦中的 NonPagedPool Usage
指標可以看到,當前的 非換頁池
佔用了 4.8G 記憶體,總計 121w 的記憶體頁。
接下來就是要深挖下 非換頁池
,看看到底都是什麼 Tag 分配的,可以使用 !poolused 2
命令。
3: kd> !poolused 2
....
Sorting by NonPaged Pool Consumed
NonPaged Paged
Tag Allocs Used Allocs Used
Leak 119 4991221760 0 0 UNKNOWN pooltag 'Leak', please update pooltag.txt
ConT 238 14499840 0 0 UNKNOWN pooltag 'ConT', please update pooltag.txt
KETR 16410 8117664 0 0 UNKNOWN pooltag 'KETR', please update pooltag.txt
EtwB 196 7565568 2 131072 Etw Buffer , Binary: nt!etw
2872 6 5660864 0 0 UNKNOWN pooltag '2872', please update pooltag.txt
287R 1026 4183040 0 0 UNKNOWN pooltag '287R', please update pooltag.txt
File 9734 3877408 0 0 File objects
Thre 1257 3217920 0 0 Thread objects , Binary: nt!ps
EtwR 12141 2672640 0 0 Etw KM RegEntry , Binary: nt!etw
...
從卦中資料看,有一個神秘的 Tag=Leak
的記憶體分配,它分配了 119 次,總大小 4.99G。 哈哈,其實就是剛才透過 MyFault 做的 40M/s 的記憶體分配。
接下來的問題是:這個 Leak 是哪一個驅動程式所為呢?最簡單的辦法就是在各個驅動的記憶體空間中做記憶體搜尋,看看誰裡面有 Leak
的asc硬編碼,對吧,有了這個思路,先用 lm 看看裡面都有哪些 sys 。
3: kd> lm
start end module name
ffffc25c`891b0000 ffffc25c`89480000 win32kbase (deferred)
ffffc25c`8a190000 ffffc25c`8a545000 win32kfull (deferred)
...
fffff807`22600000 fffff807`23646000 nt (pdb symbols)
fffff807`23c00000 fffff807`23d16000 clipsp (deferred)
fffff807`47f30000 fffff807`47f4b000 monitor (deferred)
fffff807`47f50000 fffff807`47f59000 myfault (deferred)
...
Unloaded modules:
fffff807`3c6e0000 fffff807`3c6ec000 360Sensor64.sys
fffff807`31550000 fffff807`31560000 dump_storport.sys
fffff807`315a0000 fffff807`315d3000 dump_storahci.sys
fffff807`31000000 fffff807`3101e000 dump_dumpfve.sys
fffff807`26b80000 fffff807`26bac000 luafv.sys
fffff807`26b20000 fffff807`26b30000 dump_storport.sys
fffff807`26b70000 fffff807`26ba3000 dump_storahci.sys
fffff807`26bd0000 fffff807`26bee000 dump_dumpfve.sys
fffff807`28130000 fffff807`2814c000 dam.sys
fffff807`24200000 fffff807`2420a000 360elam64.sys
fffff807`25230000 fffff807`25241000 hwpolicy.sys
接下來就是寫指令碼在每個 sys 的 start ~ end
區間做 s 搜尋,這個指令碼我就不放了,非常簡單,最終就在 myfault.sys 中成功找到了 Leak
硬編碼,參考如下:
3: kd> lmvm myfault
Browse full module list
start end module name
fffff807`47f50000 fffff807`47f59000 myfault (deferred)
Image path: \??\C:\Windows\system32\drivers\myfault.sys
Image name: myfault.sys
Browse all global symbols functions data
Timestamp: Fri Sep 30 00:17:31 2022 (6335C51B)
CheckSum: 00010CED
ImageSize: 00009000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
Information from resource tables:
3: kd> ? fffff807`47f59000 - fffff807`47f50000
Evaluate expression: 36864 = 00000000`00009000
3: kd> s -a fffff807`47f50000 L?0x9000 "Leak"
fffff807`47f51559 4c 65 61 6b 0f 42 c1 41-8d 49 fd 8b d0 ff 15 0c Leak.B.A.I......
fffff807`47f515c7 4c 65 61 6b 0f 42 c1 33-c9 8b d0 ff 15 a0 1a 00 Leak.B.3........
三: 總結
在過往的dump分析中都是使用者態程式的洩露,核心態模式堆的的洩露還是第一次分析,不是朋友提供的這次機會,真的就沒緣分啦!在這次dump分析過程中,也讓大家看到了 windbg 是多麼的強大!