经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
聊一聊 .NET高级调试 内核模式堆泄露
来源:cnblogs  作者:一线码农  时间:2023/12/15 8:47:57  对本文有异议

一:背景

1. 讲故事

前几天有位朋友找到我,说他的机器内存在不断的上涨,但在任务管理器中查不出是哪个进程吃的内存,特别奇怪,截图如下:

在我的分析旅程中都是用户态模式的内存泄漏,像上图中的异常征兆已经明确告诉你了,不是用户态程序吃的内存,那就是内核态程序吃的,比如:

  • 某些驱动程序
  • 操作系统

从概率上来说一般都是某些第三方程序内存泄露导致的,这一篇我们就来聊一聊这种问题该如何解决。

二:内核模式堆泄露分析

1. 驱动程序是如何分配内存的

相信有很多朋友都知道,用户态的程序是直接或者间接的调用 VirtualAlloc 方法来向操作系统要内存,包括 C# 的 GC 堆也是一样,它的方法签名如下:

  1. LPVOID VirtualAlloc(
  2. [in, optional] LPVOID lpAddress,
  3. [in] SIZE_T dwSize,
  4. [in] DWORD flAllocationType,
  5. [in] DWORD flProtect
  6. );

那内核中的驱动程序是如何向操作系统要内存的呢?一般都是调用 ExAllocatePool2 方法来要内存的,签名如下:

  1. DECLSPEC_RESTRICT PVOID ExAllocatePool2(
  2. POOL_FLAGS Flags,
  3. SIZE_T NumberOfBytes,
  4. ULONG Tag
  5. );

上面有两个参数要详细解释一下:

  • Flags 参数

一般用的多的就是 POOL_FLAG_NON_PAGEDPOOL_FLAG_PAGED 两种,前者表示分配的内存是需要永久驻留内存,不可以交换到硬盘的。后者分配的内存是可以交换到硬盘的。

  • Tag 参数

这个参数的本意就是方便日后洞察内存泄露的,它强行让一块内存和这个 Tag(4byte的ascii 字符串) 做了强绑定,到时候通过这个 tag 就知道是谁分配的内存。

2. 制造内核模式堆泄露

为了能够让驱动程序泄露,可以使用微软提供的 NotMyFault 工具,这个工具利用 myfault.sys 驱动不断的向操作系统分配内存。官方网址为:https://learn.microsoft.com/zh-cn/sysinternals/downloads/notmyfault

打开 myfault 工具然后输入 40M/s 的泄露,并分配在非换页池中,同时配置下内核态转储dump, 代码和截图参考如下:

  1. 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 观察下操作系统级的虚拟内存的分布情况。

  1. 3: kd> !vm
  2. ...
  3. Physical Memory: 2069421 ( 8277684 Kb)
  4. Available Pages: 445015 ( 1780060 Kb)
  5. ResAvail Pages: 707292 ( 2829168 Kb)
  6. Locked IO Pages: 0 ( 0 Kb)
  7. Free System PTEs: 4295052431 (17180209724 Kb)
  8. ...
  9. Modified Pages: 11479 ( 45916 Kb)
  10. Modified PF Pages: 11479 ( 45916 Kb)
  11. Modified No Write Pages: 0 ( 0 Kb)
  12. NonPagedPool Usage: 1219892 ( 4879568 Kb)
  13. NonPagedPoolNx Usage: 24512 ( 98048 Kb)
  14. NonPagedPool Max: 4294967296 (17179869184 Kb)
  15. PagedPool Usage: 32907 ( 131628 Kb)
  16. PagedPool Maximum: 4294967296 (17179869184 Kb)
  17. ...
  18. NonPagedPool Commit: 1246469 ( 4985876 Kb)
  19. ...
  20. Sum System Commit: 1409562 ( 5638248 Kb)
  21. Total Private: 279673 ( 1118692 Kb)
  22. ********** Sum of individual system commit + Process commit exceeds overall commit by 1952 Kb ? ********
  23. Committed pages: 1688747 ( 6754988 Kb)
  24. Commit limit: 4166573 ( 16666292 Kb)

从卦中的 NonPagedPool Usage 指标可以看到,当前的 非换页池 占用了 4.8G 内存,总计 121w 的内存页。

接下来就是要深挖下 非换页池 ,看看到底都是什么 Tag 分配的,可以使用 !poolused 2 命令。

  1. 3: kd> !poolused 2
  2. ....
  3. Sorting by NonPaged Pool Consumed
  4. NonPaged Paged
  5. Tag Allocs Used Allocs Used
  6. Leak 119 4991221760 0 0 UNKNOWN pooltag 'Leak', please update pooltag.txt
  7. ConT 238 14499840 0 0 UNKNOWN pooltag 'ConT', please update pooltag.txt
  8. KETR 16410 8117664 0 0 UNKNOWN pooltag 'KETR', please update pooltag.txt
  9. EtwB 196 7565568 2 131072 Etw Buffer , Binary: nt!etw
  10. 2872 6 5660864 0 0 UNKNOWN pooltag '2872', please update pooltag.txt
  11. 287R 1026 4183040 0 0 UNKNOWN pooltag '287R', please update pooltag.txt
  12. File 9734 3877408 0 0 File objects
  13. Thre 1257 3217920 0 0 Thread objects , Binary: nt!ps
  14. EtwR 12141 2672640 0 0 Etw KM RegEntry , Binary: nt!etw
  15. ...

从卦中数据看,有一个神秘的 Tag=Leak 的内存分配,它分配了 119 次,总大小 4.99G。 哈哈,其实就是刚才通过 MyFault 做的 40M/s 的内存分配。

接下来的问题是:这个 Leak 是哪一个驱动程序所为呢?最简单的办法就是在各个驱动的内存空间中做内存搜索,看看谁里面有 Leak 的asc硬编码,对吧,有了这个思路,先用 lm 看看里面都有哪些 sys 。

  1. 3: kd> lm
  2. start end module name
  3. ffffc25c`891b0000 ffffc25c`89480000 win32kbase (deferred)
  4. ffffc25c`8a190000 ffffc25c`8a545000 win32kfull (deferred)
  5. ...
  6. fffff807`22600000 fffff807`23646000 nt (pdb symbols)
  7. fffff807`23c00000 fffff807`23d16000 clipsp (deferred)
  8. fffff807`47f30000 fffff807`47f4b000 monitor (deferred)
  9. fffff807`47f50000 fffff807`47f59000 myfault (deferred)
  10. ...
  11. Unloaded modules:
  12. fffff807`3c6e0000 fffff807`3c6ec000 360Sensor64.sys
  13. fffff807`31550000 fffff807`31560000 dump_storport.sys
  14. fffff807`315a0000 fffff807`315d3000 dump_storahci.sys
  15. fffff807`31000000 fffff807`3101e000 dump_dumpfve.sys
  16. fffff807`26b80000 fffff807`26bac000 luafv.sys
  17. fffff807`26b20000 fffff807`26b30000 dump_storport.sys
  18. fffff807`26b70000 fffff807`26ba3000 dump_storahci.sys
  19. fffff807`26bd0000 fffff807`26bee000 dump_dumpfve.sys
  20. fffff807`28130000 fffff807`2814c000 dam.sys
  21. fffff807`24200000 fffff807`2420a000 360elam64.sys
  22. fffff807`25230000 fffff807`25241000 hwpolicy.sys

接下来就是写脚本在每个 sys 的 start ~ end 区间做 s 搜索,这个脚本我就不放了,非常简单,最终就在 myfault.sys 中成功找到了 Leak 硬编码,参考如下:

  1. 3: kd> lmvm myfault
  2. Browse full module list
  3. start end module name
  4. fffff807`47f50000 fffff807`47f59000 myfault (deferred)
  5. Image path: \??\C:\Windows\system32\drivers\myfault.sys
  6. Image name: myfault.sys
  7. Browse all global symbols functions data
  8. Timestamp: Fri Sep 30 00:17:31 2022 (6335C51B)
  9. CheckSum: 00010CED
  10. ImageSize: 00009000
  11. Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
  12. Information from resource tables:
  13. 3: kd> ? fffff807`47f59000 - fffff807`47f50000
  14. Evaluate expression: 36864 = 00000000`00009000
  15. 3: kd> s -a fffff807`47f50000 L?0x9000 "Leak"
  16. fffff807`47f51559 4c 65 61 6b 0f 42 c1 41-8d 49 fd 8b d0 ff 15 0c Leak.B.A.I......
  17. fffff807`47f515c7 4c 65 61 6b 0f 42 c1 33-c9 8b d0 ff 15 a0 1a 00 Leak.B.3........

三: 总结

在过往的dump分析中都是用户态程序的泄露,内核态模式堆的的泄露还是第一次分析,不是朋友提供的这次机会,真的就没缘分啦!在这次dump分析过程中,也让大家看到了 windbg 是多么的强大!

图片名称

原文链接:https://www.cnblogs.com/huangxincheng/p/17900826.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号