记一次 .NET 某工控视觉软件 非托管泄漏分析

网站建设3年前发布
17 0 0

最近分享了好几篇关于 非托管内存泄漏​ 的文章,有时候就是这么神奇,来求助的都是这类型的dump,一饮一啄,莫非前定。让我被迫加深对 NT堆​, 页堆 的理解,这一篇就给大家再带来一篇内存泄漏。,前段时间有位朋友找到我,说他的程序出现了非托管泄漏,某一块的操作会导致非托管内存上涨的很快,让我帮忙逆向看下是哪里的操作没有释放资源?既然找到我,那就上 WinDbg 分析吧。,看内存泄漏还是老规矩,使用 !address -summary 命令就可以了。,从卦中看,当前进程有 13.6 G​ 的提交内存,NtHeap 占用了 13G​,很明显这是非托管内存泄漏,既然是非托管泄漏,那就需要二番战,也就是让朋友开启 ust​,或者启用应用程序验证器 (Application Verifier)​ 开启页堆,目的就是记录分配这块内存的源头,这里就让朋友用 gflags 开启下 ust,具体怎么开,这里就不介绍了,大家可以网上搜一下。,有了 ust 的加持,接下来就可以继续分析,使用 !heap -s 观察下 nt 堆的布局。,从卦中看,commit 最大的也就是 67408k = 67M​, 这和 13G​ 差的不是一星半点,如果你了解 NtHeap 的布局,应该知道当 分配内存 > 512k​ 的时候,会进入到 HEAP 的 VirtualAllocdBlocks​ 双向链表中,言外之意就是当你觉得内存对不上的时候,就要观察下这个链表了,即上图中的 Virt blocks​ 列,可以看到 handle=0000000029fb0000​ 的 Virt blocks=189​,接下来继续下钻 handle=0000000029fb0000 这个堆。,我去,卦中出现了不愿看到的 Unable to read nt!_HEAP_VIRTUAL_ALLOC_ENTRY structure at 0000000043500000​,也就是说显示不出 _HEAP_VIRTUAL_ALLOC_ENTRY 结构,可以用 dt 验证一下。,为什么在他的机器上没记录到,可能和它生产服务器的 Windows 系统有关,这里就不细究原因,接下来的问题是:!heap​ 命令失效,该怎么把 VirtualAllocdBlocks 给挖出来呢?只能纯人肉了...,要想人肉挖,需要一些底层知识,比如下面三点。,VirtualAllocdBlocks 是一个记录大块内存的双向链表结构,可以用 dt nt!_HEAP 0000000029fb0000 命令从 HEAP 中找出来。,从卦中可以看到, VirtualAllocdBlocks​ 是一个拥有 Flink​ 和 Blink 的双向链表结构。,我们都知道 heap 的 block <512k​ 是 _HEAP_ENTRY​ 结构,那 block >512k​ 的块就是 _HEAP_VIRTUAL_ALLOC_ENTRY 结构,不信的话可以用 dt 导出来。,从卦中可以看到,除了真正的分配 BusyBlock​ 之外还有一些附属信息,比如 CommitSize​ , ReserveSize​ 等等,接下来就可以抽取 第一个节点地址 加上 +0x30​ 来找到这个真正的内存分配块,即 0x0000000043500000 + 0x30​, 然后使用 !heap -p -a 就可以看到这个分配块的源头在哪里了。,可以看到第一块 size= 0x1000040 byte = 16M​ 的内存是 HalconDotNet 分配的,接下来我们多抽几个,或者用脚本来归纳一下,发现有大量的 88M 内存占用,大体上归为两类:,2023030601083411cc507021b89a6216b096490526a4a5108b96985,20230306010854a56971629558b7bbac96909752cbd2714dc738814,最后就是把这个结果给了朋友,让朋友看下用 !ip2md 显示出来的托管方法,为什么没有释放,是不是漏了。,这个dump可以看出是因为对 halcon​  做了一套 DotNet 版的封装上出现了一些瑕疵,这个 dump 的难点在于当 !heap 扩展命令失效的情况下,如何通过纯手工的方式把 NTHeap 剥离的明明白白。

© 版权声明

相关文章