一:背景

1. 讲故事

前些天有位朋友在微信上找到我,说他的系统有偶发崩溃,自己也没找到原因,让我帮忙看下怎么回事,我分析dump一直都是免费的,毕竟对这些东西挺感兴趣,有问题可以直接call我,好了,接下来我们就来分析dump吧。

二:程序为什么会崩

1. 观察崩溃上下文

windbg有一个厉害之处在于双击dump之后会自动定位到崩溃的线程,然后通过 .ecxr; k10 命令就可以看到崩溃点了,输出如下:


0:083> .ecxr; k10
rax=000000004bdefa50 rbx=00000c45ea960c80 rcx=000000ffffffffff
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=00000000773da365 rsp=0000000046d0fb50 rbp=000000004bde28d0
r8=000000004bde28c0 r9=0000000000000001 r10=0000000000000000
r11=0000000000000206 r12=00000000006b0000 r13=000000004bde2950
r14=0000000000000000 r15=0000000000000001
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
ntdll!RtlFreeHeap+0x1a5:
00000000`773da365 488b7b08 mov rdi,qword ptr [rbx+8] ds:00000c45`ea960c88=????????????????
# Child-SP RetAddr Call Site
00 00000000`46d0fb50 000007fe`feda10c8 ntdll!RtlFreeHeap+0x1a5
01 00000000`46d0fbd0 000007fe`ee66f126 msvcrt!free+0x1c
02 00000000`46d0fc00 000007fe`ee6556ae ksproxy!CStandardInterfaceHandler::KsCompleteIo+0x4e6
03 00000000`46d0fce0 000007fe`ee66bf5c ksproxy!CKsOutputPin::OutputPinBufferHandler+0x1e
04 00000000`46d0fd10 00000000`771a556d ksproxy!CAsyncItemHandler::AsyncItemProc+0x20c
05 00000000`46d0fd70 00000000`7740372d kernel32!BaseThreadInitThunk+0xd
06 00000000`46d0fda0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d

从卦中可以看到,程序崩溃在 RtlFreeHeap 函数中,熟悉这玩意的朋友应该知道它是用来释放 堆块 的,签名如下:


NTSYSAPI LOGICAL RtlFreeHeap(
[in] PVOID HeapHandle,
[in, optional] ULONG Flags,
_Frees_ptr_opt_ PVOID BaseAddress
);

接下来就是寻找该堆块的首地址 BaseAddress,即 r8 寄存器值,使用 uf ntdll!RtlFreeHeap 即可,输出如下:


0:083> uf ntdll!RtlFreeHeap
ntdll!RtlFreeHeap:
00000000`773da1c0 4053 push rbx
00000000`773da1c2 55 push rbp
00000000`773da1c3 56 push rsi
00000000`773da1c4 57 push rdi
00000000`773da1c5 4154 push r12
00000000`773da1c7 4883ec50 sub rsp,50h
00000000`773da1cb 33f6 xor esi,esi
00000000`773da1cd 498be8 mov rbp,r8
00000000`773da1d0 8bfa mov edi,edx
00000000`773da1d2 4c8be1 mov r12,rcx
00000000`773da1d5 488bde mov rbx,rsi
00000000`773da1d8 4d85c0 test r8,r8
...
ntdll!RtlFreeHeap+0x179:
00000000`773da339 498b4008 mov rax,qword ptr [r8+8]
00000000`773da33d 498bd8 mov rbx,r8
00000000`773da340 48b9ffffffffff000000 mov rcx,0FFFFFFFFFFh
00000000`773da34a 4933dc xor rbx,r12
00000000`773da34d 4823c1 and rax,rcx
00000000`773da350 48c1eb04 shr rbx,4
00000000`773da354 4833d8 xor rbx,rax
00000000`773da357 48331d3a321000 xor rbx,qword ptr [ntdll!RtlpLFHKey (00000000`774dd598)]
00000000`773da35e 48c1e304 shl rbx,4
00000000`773da362 0f0d0b prefetchw [rbx]
00000000`773da365 488b7b08 mov rdi,qword ptr [rbx+8]

根据卦中的汇编代码 mov rbp,r8 ,看样子是 rbp 保存了 BaseAddress 地址,接下来使用 !heap -x 000000004bde28d0 看看到底啥情况,输出如下:


0:083> !heap -x 000000004bde28d0
SEGMENT HEAP ERROR: failed to initialize the extention
List corrupted: (Blink->Flink = 0000000000000000) != (Block = 000000004bde28c0)
HEAP 00000000006b0000 (Seg 000000004bde0000) At 000000004bde28b0 Error: block list entry corrupted List corrupted: (Flink->Blink = 900000004bdefa50) != (Block = 000000004bdefa50)
HEAP 00000000006b0000 (Seg 000000004bde0000) At 000000004bdefa40 Error: block list entry corrupted Entry User Heap Segment Size PrevSize Unused Flags
-------------------------------------------------------------------------------------------------------------
000000004bde28b0 000000004bde28c0 00000000006b0000 000000004bde0000 3740 800 0 free

从卦中可以看到,当前的 000000004bde28c0 是一个 free 堆块,同时也抛了一个 堆块列表 的损坏错误,这就有点意思了。。。

2. doublefree 导致的吗

熟悉 win32 的朋友应该知道,在已经 free 的块上再次调用 RtlFreeHeap 释放会是一个经典的 doublefree,貌似这个问题已经定位了。。。但如果你修车经验丰富的话,你应该知道 堆管理器 检测到 doublefree 的时候会是这样的调用栈记一次 .NET 某医疗住院系统 崩溃分析, 参考如下:


0:090> !heap -s
Details: Heap address: 000001c14fd20000
Error address: 000001ce25531c50
Error type: HEAP_FAILURE_BLOCK_NOT_BUSY
Details: The caller performed an operation (such as a free
or a size check) that is illegal on a free block.
Follow-up: Check the error's stack trace to find the culprit. Stack trace:
Stack trace at 0x00007ffed7b82848
00007ffed7abe109: ntdll!RtlpLogHeapFailure+0x45
00007ffed7acbb0e: ntdll!RtlFreeHeap+0x9d3ce
00007ffeb093276f: OraOps12!ssmem_free+0xf
00007ffeb0943077: OraOps12!OpsMetFreeValCtx+0xd7
00007ffeb093cdd8: OraOps12!OpsDacDispose+0x2b8
00007ffe655e4374: +0x655e4374
...

而我们这个dump是访问违例,虽然这个dump必崩无疑,但这段逻辑此时还没执行到,也就是在这块逻辑之前就崩掉了,那为什么会崩掉呢?到底经历了何样的惊魂时刻。。。

3. 突破口在哪里

要想寻找突破口,就得理解下面的这两句了,再次输出一下吧。


List corrupted: (Blink->Flink = 0000000000000000) != (Block = 000000004bde28c0)
HEAP 00000000006b0000 (Seg 000000004bde0000) At 000000004bde28b0 Error: block list entry corrupted List corrupted: (Flink->Blink = 900000004bdefa50) != (Block = 000000004bdefa50)
HEAP 00000000006b0000 (Seg 000000004bde0000) At 000000004bdefa40 Error: block list entry corrupted

要理解这个,需要你对 _HEAP 结构有一个深度的理解,这个在我的 .NET高级调试训练营 里有一个系统的解读。

大家要知道 堆管理器 对 Free 的管理采用的是 双向链表 的方式,其中 Flink 表示下一个(Forward)节点,BLink 表示前一个(Backward)结点,有朋友搞不清的话,我画个简图。

有了简图之后,接下来逐个解读下:

  • (Blink->Flink = 0000000000000000) != (Block = 000000004bde28c0)

这是经典的 一去一回 ,结果发现不再是自己了。。。即 0000000000000000 != 000000004bde28c0,我们用 ntdll!_LIST_ENTRY 来具象化一下,截图如下:

从卦中可以看到 Blink = 0x900000004bdefa50 时就报错了,由于报错windbg 就将结果显示为 0000000000000000,所以这一来一回居然不等于自己,所以堆管理器就觉得很奇葩。。。

  • (Flink->Blink = 900000004bdefa50) != (Block = 000000004bdefa50)

这是经典的 一回一去,结果发现 Blink 不对。。。本来应该是 0x000000004bdefa50 结果是 0x900000004bdefa50 截图如下:

到这里我相信很多人会有一个疑问,我也没看到 0x000000004bdefa50 地址呀,凭什么说0x900000004bdefa50 的前身是 0x000000004bdefa50

要想找到这个答案,又是考你的 堆管理器 知识,所有的 free 都挂在 FreeLists 字段里,同样也是采用 双向链表 的方式,输出如下:


0:083> dt nt!_HEAP 00000000006b0000 -y FreeLists
ntdll!_HEAP
+0x158 FreeLists : _LIST_ENTRY [ 0x00000000`1f1d7f90 - 0x00000000`1f23dbf0 ]

接下来写一段简单的脚本把这个list给遍历下,看看 0x000000004bde28c0 的BLink结点是不是 0x000000004bdefa50 即可,脚本如下:


$$ $$>a< D:\debugging\18.20250506\src\Example\scripts\while2.txt
r @$t0 = 0x00000000006b0000 ; r @$t1 = poi(@$t0+0x158) ; $$ Flink
r @$t2 = poi(@$t0+0x158+8); $$ Blink .if (@$t1 == @$t2)
{
.echo "@$t0+0x158 list is empty"
}
.else
{
.echo "Walking @$t0+0x128 list..." r @$t3 = @$t0+0x158
r @$t4 = @$t1 .while (@$t4 != @$t3)
{
.printf "Entry at %p\n", @$t4
r @$t4 = poi(@$t4)
} .echo "End of list reached"
}

哈哈,睁大眼睛看下卦哦,真的是 000000004bdefa50,而不是 0x900000004bdefa50,到这里基本就搞清楚了,由于地址的变化 000000004bdefa50 -> 0x900000004bdefa50 导致 双向链表 断裂,当 RtlFreeHeap 在解码这个错误的内存地址时,导致程序的崩溃,同样你了解底层的话,你会发现用户态怎么可能会有 0x900000004bdefa50 这样的内存地址呢???

4. 为什么地址变化了

这个问题真的问到我了,因为我也不知道为什么地址 突变了, 原因是我在 000000004bde28c0 周围也没发现有越界写入的情况,为啥高3128位就硬生生的由0变1了,输出如下:


0:083> dp 000000004bde28b0
00000000`4bde28b0 00000000`00000000 0001a08c`44153f2e
00000000`4bde28c0 00000000`4bdf79e0 90000000`4bdefa50
00000000`4bde28d0 00000000`00000080 00000000`00000000 0:083> .formats 90000000`4bdefa50
Binary: 10010000 00000000 00000000 00000000 01001011 11011110 11111010 01010000

最后就是这种问题该如何解决呢?只能开启 页堆,可以用 Application Verifier 工具,截图如下:

能不能找到就看个人造化了,如何真的找不到,就当是神奇的 bit位翻转 吧,建议换机器尝试,或上 ECC 纠错的内存条。。。

三:总结

这次生产事故非常考察你对 Windows 堆管理器 的深度理解,这块在我的训练营里有系统而深入的讲解,dump分析就是这样的有趣,各种迷惑和幻境,全靠扎实的底层功力和丰富的经验来冲出迷雾!

记一次 .NET 某发证机系统 崩溃分析的更多相关文章

  1. 记一次 .NET 某医疗住院系统 崩溃分析

    一:背景 1. 讲故事 最近收到了两起程序崩溃的dump,查了下都是经典的 double free 造成的,蛮有意思,这里就抽一篇出来分享一下经验供后面的学习者避坑吧. 二:WinDbg 分析 1. ...

  2. 记一次 .NET 某企业 ERP网站系统 崩溃分析

    一:背景 1. 讲故事 前段时间收到了一个朋友的求助,说他的ERP网站系统会出现偶发性崩溃,找了好久也没找到是什么原因,让我帮忙看下,其实崩溃好说,用 procdump 自动抓一个就好,拿到 dump ...

  3. 记一次 .NET 某医疗器械 程序崩溃分析

    一:背景 1.讲故事 前段时间有位朋友在微信上找到我,说他的程序偶发性崩溃,让我帮忙看下怎么回事,上面给的压力比较大,对于这种偶发性崩溃,比较好的办法就是利用 AEDebug 在程序崩溃的时候自动抽一 ...

  4. 爱默生UPS并机系统:进入与退出操作方法

    UPS并机系统的进入与退出: 进入:.合UPS的出线及进线开关:开启第一台UPS,等待整流指示灯常亮.然后Invert On,正常开机,此时UPS进入逆变状态 .合另外一台UPS的出线及进线开关,等待 ...

  5. Atitit. 常用街机系统and 模拟器总结 snk neo geo cps mame sfc smc

    Atitit. 常用街机系统and 模拟器总结 snk neo geo cps mame sfc smc 1. #-------常用 游戏类型 1 2. 街机的历史 2 3. #=========== ...

  6. python 之路,Day27 - 主机管理+堡垒机系统开发

    python 之路,Day27 - 主机管理+堡垒机系统开发   本节内容 需求讨论 构架设计 表结构设计 程序开发 1.需求讨论 实现对用户的权限管理,能访问哪些机器,在被访问的机器上有哪些权限 实 ...

  7. 简单易用的堡垒机系统—Teleport

    简单易用的堡垒机系统-Teleport 官方文档:http://teleport.eomsoft.net/doc#!1  一.Teleport介绍 Teleport是触维软件推出的一款简单易用的堡垒机 ...

  8. 宿主机系统 Deepin 15.4,解决 Virtualbox 5.1 中 XP虚拟机无法使用 USB设备(如:U盘、罗技优联接收器等)的问题

    软件环境 宿主机系统:Deepin 15.4.1, 虚拟机软件:VirtualBox 5.1 虚拟机系统:XP 操作步骤如下: (1)  安装 Virtualbox Extension Pack 进入 ...

  9. 居于mtk芯片安卓车机系统具体流程

    一:车机系统框架  MCU 功能  电源控制  Radio 控制(RDS)  按键检测(Panel/Remote/SW)  常见信号检查(倒车/大灯/刹车)  CAN 模块通讯  ARM- ...

  10. kvm 客户机系统的代码是如何运行的

    一个普通的 Linux 内核有两种执行模式:内核模式(Kenerl)和用户模式 (User).为了支持带有虚拟化功能的 CPU,KVM 向 Linux 内核增加了第三种模式即客户机模式(Guest), ...

随机推荐

  1. chrome播放webRTC的H265视频方法

    需求描述 最近有需求实现浏览器直接播放摄像头视频 鉴于Camera本身支持了rtsp流,本想web直接播放rtsp,但是还不行,搜了一下webRTC实现的效果和延迟会好一些.于是就使用了mediaMT ...

  2. Delphi Inputbox 输入时显示‘*’号

    unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

  3. Shell语言编程(炼气)

    1. Shell脚本执行方式 执行方式 应用及场景 通过sh或bash 书写脚本后,最常用的方式,在其他非红帽系统中,建议使用bash运行脚本 通过.点或source 加载/生效配置文件(环境变量,别 ...

  4. BUUCTF---佛说:只能四天

    题目 尊即寂修我劫修如婆愍闍嚤婆莊愍耨羅嚴是喼婆斯吶眾喼修迦慧迦嚩喼斯願嚤摩隸所迦摩吽即塞願修咒莊波斯訶喃壽祗僧若即亦嘇蜜迦須色喼羅囉咒諦若陀喃慧愍夷羅波若劫蜜斯哆咒塞隸蜜波哆咤慧聞亦吽念彌諸嘚嚴諦咒 ...

  5. CSS那些事读书笔记-2

    背景 作为一个后端开发,曾经尝试过学习前端,但是总觉不得要领,照猫画虎,而公司里又有专业的前端开发,工作中几乎接触不到实际的前端任务,所以前端的技能田野一直是一片荒芜.但是笔者深知前端的技能对找工作和 ...

  6. 【Ubuntu】Ubuntu 24.04 配置镜像源

    [Ubuntu]Ubuntu 24.04 配置镜像源 零.起因 最近在虚拟机中安装了个ubuntu-24.04-desktop-amd64,默认是国外的软件源,很慢,故替换到国内. 壹.替换 源地址( ...

  7. 【EasyPR】Linux安装使用EasyPR开源车牌识别系统

    [EasyPR]Linux安装使用EasyPR开源车牌识别系统 零.安装OpenCV - 3.2.0 我使用的是Kali系统,基于Debian的一个Linux发行版本. 1.配置系统的软件源(配置正确 ...

  8. DEF4Delphi-master的安装

    保姆教程 d2007的TWEBbrowser因为用的太老的IE内核.现在的浏览器上的功能呈现就卡住了. 那么DEF4Delphi效果非常好. 如何安装后成功运行.很简单.直接去:https://git ...

  9. 5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明

    5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明 @ 目录 5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明 1. Exchanges 交换机的 ...

  10. 2024 蓝桥杯模拟赛3(div1+div2)

    2024 蓝桥杯模拟赛3(div1+div2) P8834 [传智杯 #3 决赛] 序列 简单的模拟,数据范围很小,暴力即可 点击查看代码 #include <bits/stdc++.h> ...