聊一聊 .NET高级调试 内核模式堆泄露
一:背景
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 是多么的强大!

聊一聊 .NET高级调试 内核模式堆泄露的更多相关文章
- 使用WinDbg调试入门(内核模式)
windbg是一个内核模式和用户模式调试器,包含在Windows调试工具中.这里我们提供了一些实践练习,可以帮助您开始使用windbg作为内核模式调试器. 设置内核模式调试 内核模式调试环境通常有两台 ...
- .NET高级调试系列-Windbg调试入门篇
Windbg是.NET高级调试领域中不可或缺的一个工具和利器,也是日常我们分析解决问题的必备.准备近期写2篇精华文章,集中给大家分享一下如果通过Windbg进行.NET高级调试. 今天我们来一篇入门的 ...
- ------- 软件调试——挫败 QQ.exe 的内核模式保护机制 -------
------------------------------------------------------------------------ QQ 是一款热门的即时通信(IM)类工具,在安装时刻会 ...
- 通过USB 2.0电缆手动设置内核模式调试
Windows的调试工具支持通过USB 2.0电缆进行内核调试.本文介绍如何手动设置USB 2.0调试.通过USB 2.0电缆进行调试需要以下硬件: USB 2.0调试电缆.此电缆不是标准USB 2. ...
- Windows内核开发-5-(2)-内核模式调试
Windows内核开发-5-(2)-内核模式调试 普通用户模式的调试,采取的是给进程添加一个线程来挂起断点,作为一个调试器的线程在进程中使用.照这样来类推,对操作系统调试相当于添加一个进程来限制操作系 ...
- Linux高级调试与优化——gdb调试命令
番外 2019年7月26日至27日,公司邀请<软件调试>和<格蠹汇编——软件调试案例集锦>两本书的作者张银奎老师进行<Linux高级调试与优化>培训,有幸聆听张老师 ...
- windows程序员进阶系列:《软件调试》之堆 (一)
windows程序员进阶系列:<软件调试>之堆 (一) 堆是软件在运行时动态申请内存空间的主要途径.从堆上申请来的空间需要程序员自己申请和释放,且申请和释放操作必须绝对匹配.忘记释放或者多 ...
- 使用Windbg调试内核
Windbg是微软开发的免费源码级调试工具.Windbg可以用于Kernel模式调试和用户模式调试,还可以调试Dump文件. 1.从http://www.microsoft.com/whdc/devt ...
- [Android Studio 权威教程]断点调试和高级调试
好了开始写一个简单的调试程序,我们先来一个for循环 ? 1 2 3 4 5 6 7 8 <code class="language-java hljs ">for ( ...
- core--线程同步(内核模式)
什么是内核?windows操作系统为了更好的管理进程,线程,创建了很多数据结构,这些数据结构运行在windows的底层,并不开放给开发人员:所以开发人员称这些结构为内核,但是为了开发人员能够使用,wi ...
随机推荐
- langchain中的LLM模型使用介绍
简介 构建在大语言模型基础上的应用通常有两种,第一种叫做text completion,也就是一问一答的模式,输入是text,输出也是text.这种模型下应用并不会记忆之前的问题内容,每一个问题都是最 ...
- 为不断增长的Go生态系统扩展gopls
原文在这里. 由 Robert Findley and Alan Donovan 发布于 2023年9月8日 今年夏天初,Go团队发布了gopls的v0.12版本,这是Go语言的语言服务器,它进行了核 ...
- KRPano中文教程文档PDF版本下载
KRPano中文教程文档PDF版本下载 下载地址:https://pan.baidu.com/s/1qXIZ2os 感谢KRPano技术解密群小伙伴:斌仔分享 中文文档目录: 概述文件说明krpano ...
- Jenkins 忘记密码|密码重置
I. 当前环境 OS Version : AlmaLinux release 8.8 Jenkins Version : 2.414.1 II. 操作步骤 2.1 修改配置文件 1. SSH 登录服务 ...
- OCI云主机环境如何上传下载文件
OCI云主机的连接是使用密钥而非用户密码连接. 之前使用的非主流的一个SSH工具,正常连接主机都没问题,但需要手工输入用户. 可是在选择SFTP时,始终找不到用户名的设置,导致密钥连接的SFTP始终失 ...
- Springboot项目使用Undertow替换内置Tomcat服务器,实现RESTFUL接口web应用
Maven实例:pom.xml文件中添加更换依赖 <dependency> <groupId>org.springframework.boot</groupId> ...
- C++ typedef用法详解
typedef的语法描述 在现实生活中,信息的概念可能是长度,数量和面积等.在C语言中,信息被抽象为int.float和 double等基本数据类型.从基本数据类型名称上,不能够看出其所代表的物理 ...
- 【算法】国庆加班,火锅与Linq.AddRange的奇妙螺旋
在国庆假期的一个傍晚,小悦正在家中享受火锅美食.她嘴里咀嚼着鲜嫩的牛肉,脸上洋溢着满足的微笑.突然,手机铃声响起,打破了这温馨的氛围.她拿起手机一看,是公司打来的电话. "小悦,有个紧急的项 ...
- mac应用已损坏无法打开
sudo xattr -r -d com.apple.quarantine /User/name/yourapp # '/User/name/yourapp' 替换成你自己要安装的 mac 应用地址 ...
- 一个Node.js的小爬虫
爬虫其实就是对网页内特定id.class.标签内容的提取,多是循环出来的,对我们爬取非常便利. 1.安装node node官网下载安装包安装,后在命令行工具中输入node -v查看node安装的版本. ...