记一次 .NET某防伪验证系统 崩溃分析

news/2024/4/28 20:03:50/文章来源:https://blog.csdn.net/huangxinchen520/article/details/137108035

一:背景

1. 讲故事

昨晚给训练营里面的一位朋友分析了一个程序崩溃的故障,因为看小伙子昨天在群里问了一天也没搞定,干脆自己亲自上阵吧,抓取的dump也是我极力推荐的用 procdump 注册 AEDebug 的方式,省去了很多沟通成本。

二:WinDbg分析

1. 为什么会崩溃

windbg有一个非常强大的点就是当你双击打开后,会自动帮你切换到崩溃的线程以及崩溃处的汇编代码,省去了 !analyze -v 命令的龟速输出,参考信息如下:


................................................................
...................................................
This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(10f4.f58): Access violation - code c0000005 (first/second chance not available)
For analysis of this file, run !analyze -v
eax=00000000 ebx=00000000 ecx=00000040 edx=00000000 esi=004c1b98 edi=07a8ed4c
eip=7008508f esp=07a8ec74 ebp=07a8ec80 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
clr!Thread::GetSafelyRedirectableThreadContext+0x7c:
7008508f 8038eb          cmp     byte ptr [eax],0EBh        ds:002b:00000000=??
...

从卦中可以看到,当前崩溃是因为 eax=0 导致的,那为什么 eax 等于 0 呢?要想寻找这个答案,需要观察崩溃前的线程栈上下文,可以使用命令 .ecxr;k 9 即可。


0:009> .ecxr;k 9
eax=00000000 ebx=00000000 ecx=00000040 edx=00000000 esi=004c1b98 edi=07a8ed4c
eip=7008508f esp=07a8ec74 ebp=07a8ec80 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
clr!Thread::GetSafelyRedirectableThreadContext+0x7c:
7008508f 8038eb          cmp     byte ptr [eax],0EBh        ds:002b:00000000=??# ChildEBP RetAddr      
00 07a8ec80 6fe7f6cd     clr!Thread::GetSafelyRedirectableThreadContext+0x7c
01 07a8f030 6fe7f2f3     clr!Thread::HandledJITCase+0x31
02 07a8f0a4 6fee23da     clr!Thread::SuspendRuntime+0x260
03 07a8f184 6fedf72d     clr!WKS::GCHeap::SuspendEE+0x1fe
04 07a8f1b0 6fe309ca     clr!WKS::GCHeap::GarbageCollectGeneration+0x168
05 07a8f1c0 6fe30a2e     clr!WKS::GCHeap::GarbageCollectTry+0x56
06 07a8f1e4 6fe30a90     clr!WKS::GCHeap::GarbageCollect+0xa5
07 07a8f230 6f058b01     clr!GCInterface::Collect+0x5d
08 07a8f26c 055fa4b1     mscorlib_ni+0x3b8b01

从卦中信息看,尼玛,真无语了 GCInterface::Collect 说明有人用 GC.Collect() 手工触发GC,不知道为什么要这么做来污染GC内部的统计信息,不管怎么说这个肯定不是崩溃的原因。

2. GC正在干什么

我们继续观察线程栈,可以看到它的逻辑大概是这样的,通过 SuspendRuntime 把所有的托管线程进行逻辑上暂停,在暂停其中的一个线程时抛出了异常。

稍微提醒一下,这个 HandledJITCase 方法是用 ip 劫持技术将代码引入到 coreclr 中进行 GC完成等待,这种神操作有些杀毒软件会认为是病毒!!!

有些朋友肯定会说,有没有代码支撑。。。这里我就找一下 coreclr 的源码贴一下吧。


void ThreadSuspend::SuspendRuntime(ThreadSuspend::SUSPEND_REASON reason)
{while ((thread = ThreadStore::GetThreadList(thread)) != NULL){...if (workingOnThreadContext.Acquired() && thread->HandledJITCase()){...}...}
}

结合源码分析思路就非常清晰了,这里的 thread->HandledJITCase() 中的 thread 到底是哪一个线程? 可以观察 kb 输出然后用 !t 去做比对。

从卦中看,当前 GC 正在 Suspend 主线程,并且还看到了主线程有一个 System.AccessViolationException 异常,无语了。。。

3. 主线程到底怎么了

主线程进入到视野之后,那就重点关注一下它,可以用 k 看一下输出。


0:009> ~0s
eax=00000000 ebx=0029ea50 ecx=0029ea90 edx=00000000 esi=7efdb800 edi=000d0000
eip=00000000 esp=0029ea4c ebp=75146381 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00210202
00000000 ??              ???
0:000> k
00 75146381 7efdb800     0x0
01 75146381 7517fa04     0x7efdb800
02 0029ea80 7736013a     user32!__fnHkINLPKBDLLHOOKSTRUCT+0x28
03 0029eae4 7514908d     ntdll!KiUserCallbackDispatcher+0x2e
04 0029eae4 076e3912     user32!CallNextHookEx+0x84
05 0029eb28 076e3064     0x76e3912
06 0029eb5c 0011d48f     xxx!xxx.ScanerHook.KeyboardHookProc+0xe4
07 0029eb8c 75146381     0x11d48f
08 0029eba8 7517fa04     user32!DispatchHookW+0x38
09 0029ebd8 7736013a     user32!__fnHkINLPKBDLLHOOKSTRUCT+0x28
0a 0029ec3c 751406eb     ntdll!KiUserCallbackDispatcher+0x2e
0b 0029ec3c 75140751     user32!_PeekMessage+0x88
0c 0029ec68 6d8af3bf     user32!PeekMessageW+0x108
...

从卦象看,这卦非常奇怪,有如下两点信息:

  • eip=00000000,这个很无语,线程已经疯了
  • KeyboardHookProc ,居然有键盘钩子

熟悉 eip 的朋友应该知道,它相当于一辆车的方向盘,一辆高速行驶的车突然没了方向盘,真的太可怕了,最后必然车毁人亡。

4. 是 eip=0 导致的崩溃吗

在汇编中是因为eax=0导致,而这里eip恰好也等于0,仿佛冥冥之中自有牵连,带着强烈的好奇心我们来反汇编下 GetSafelyRedirectableThreadContext 方法逻辑,简化后如下:


0:000> uf 7008508f
clr!Thread::GetSafelyRedirectableThreadContext:
6fe7f60e 55              push    ebp
6fe7f60f 8bec            mov     ebp,esp
6fe7f611 53              push    ebx
6fe7f612 56              push    esi
6fe7f613 57              push    edi
6fe7f614 8bf1            mov     esi,ecx
...
7008506d ffe9            jmp     rcx
7008506f fd              std
70085070 c1daff          rcr     edx,0FFh
70085073 f6450801        test    byte ptr [ebp+8],1
70085077 0f84efa5dfff    je      clr!Thread::GetSafelyRedirectableThreadContext+0xcc (6fe7f66c)
7008507d 8b8604010000    mov     eax,dword ptr [esi+104h]
70085083 3987b8000000    cmp     dword ptr [edi+0B8h],eax
70085089 0f85dda5dfff    jne     clr!Thread::GetSafelyRedirectableThreadContext+0xcc (6fe7f66c)
7008508f 8038eb          cmp     byte ptr [eax],0EBh  

从上面的汇编代码看eax的取值链条是: eax <- esi+104h <- ecx ,很显然这里的 ecx 是 thiscall 协议中的 Thread=004c1b98 参数,可以用 dp 验证下。


0:000> dp 004c1b98+0x104 L1
004c1c9c  00000000

从卦中看果然是 0,有些朋友好奇这个 104 偏移到底是个什么东西,参考 coreclr 源码其实就是 m_LastRedirectIP 字段,参考如下:


BOOL Thread::GetSafelyRedirectableThreadContext(DWORD dwOptions, CONTEXT* pCtx, REGDISPLAY* pRD)
{if (!EEGetThreadContext(this, pCtx)){return FALSE;}... if (GetIP(pCtx) == m_LastRedirectIP){const BYTE short_jmp = 0xeb;const BYTE self = 0xfe;BYTE* ip = (BYTE*)m_LastRedirectIP;if (ip[0] == short_jmp && ip[1] == self)m_LastRedirectIP = 0;return FALSE;}
}

结合汇编代码其实我们崩溃在 ip[0] == short_jmp 这一句上,仔细分析上面的C++代码会发现一个很奇怪的信息,那就是为什么 GetIP(pCtx)= 0,接下来用 dt 观察下寄存器上下文。


0:009> kb 2# ChildEBP RetAddr      Args to Child              
00 07a8ec80 6fe7f6cd     00000003 07a8ed4c 07a8ecf0 clr!Thread::GetSafelyRedirectableThreadContext+0x7c
01 07a8f030 6fe7f2f3     004c1b98 0b367326 76a016a1 clr!Thread::HandledJITCase+0x310:009> dt _CONTEXT 07a8ed4c
ntdll!_CONTEXT+0x000 ContextFlags     : 0x10007...+0x01c FloatSave        : _FLOATING_SAVE_AREA+0x08c SegGs            : 0x2b+0x090 SegFs            : 0x53+0x094 SegEs            : 0x2b+0x098 SegDs            : 0x2b+0x09c Edi              : 0xd0000+0x0a0 Esi              : 0x7efdb800+0x0a4 Ebx              : 0x29ea50+0x0a8 Edx              : 0+0x0ac Ecx              : 0x29ea90+0x0b0 Eax              : 0+0x0b4 Ebp              : 0x75146381+0x0b8 Eip              : 0+0x0bc SegCs            : 0x23+0x0c0 EFlags           : 0x210202+0x0c4 Esp              : 0x29ea4c...

从卦中看果然 eip=0,这是一个非常错误的信息,还有一点就是 m_LastRedirectIP 字段一般用来处理一些比较诡异的兼容性问题,所以这里两个字段都是 0 导致崩溃的产生。

有了上面的信息,我们就知道了前因后果,原来是主线程车毁人亡(eip=0),导致GC无法暂停它,在内部抛出了代码异常,你可以说是 CLR 的bug,也可以说是主线程的Bug,所以给到的解决方案就是:

  1. 屏蔽掉 键盘钩子 的业务逻辑,肯定是它造成的。
  2. 不去掉的话,要重点观察 键盘盘子 ,是否是代码改动引发的。

三:总结

说实话要想解释这个程序为什么会崩溃,需要分析者对GC的SuspendRuntime运作逻辑有一定的了解,否则真抓瞎了,所以.NET调试训练营中的GC理论知识一定是分析这些 dump 的基石。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_1027375.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[flink] flink macm1pro 快速使用从零到一

文章目录 快速使用 快速使用 打开 https://flink.apache.org/downloads/ 下载 flink 因为书籍介绍的是 1.12版本的&#xff0c;为避免不必要的问题&#xff0c;下载相同版本 解压 tar -xzvf flink-1.11.2-bin-scala_2.11.tgz启动 flink ./bin/start-cluster.sh打开 flink web…

RTOS线程切换的过程和原理

0 前言 RTOS中最重要的一个概念就是线程&#xff0c;线程的按需切换能够满足RTOS的实时性要求&#xff0c;同时能将复杂的需求分解成一个个线程执行减轻我们开发负担。 本文从栈的角度出发&#xff0c;详细介绍RTOS线程切换的过程和原理。 注&#xff1a;本文参考的RTOS是RT-T…

<QT基础(5)>事件监听

事件监听 事件监听&#xff08;Event Handling&#xff09;是在程序中监视和响应发生的事件的一种机制。在Qt中&#xff0c;事件监听是一种常见的用于处理用户输入、系统事件以及其他类型事件的方法。通过事件监听&#xff0c;您可以在发生特定事件时捕获事件并执行相应的操作…

【AI】在本地 Docker 环境中搭建使用 Hugging Face 托管的 Llama 模型

目录 Hugging Face 和 LLMs 简介利用 Docker 进行 ML格式的类型请求 Llama 模型访问创建 Hugging Face 令牌设置 Docker 环境快速演示访问页面入门克隆项目构建镜像运行容器结论推荐超级课程: Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战Hugging Fa…

7、鸿蒙学习-共享包概述

HarmonyOS提供了两种共享包&#xff0c;HAR&#xff08;Harmony Archive&#xff09;静态共享包&#xff0c;和HSP&#xff08;Harmony Shared Package&#xff09;动态共享包。 HAR与HSR都是为了实现代码和资源的共享&#xff0c;都可以包含代码、C库、资源和配置文件&#xf…

iPhone用GPT替代Siri

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen 前一段时间&#xff0c;因为iCloud协议的更新&#xff0c;我的云盘空间无法正常…

RISC-V特权架构 - 中断定义

RISC-V特权架构 - 中断定义 1 中断类型1.1 外部中断1.2 计时器中断1.3 软件中断1.4 调试中断 2 中断屏蔽3 中断等待4 中断优先级与仲裁5 中断嵌套6 异常相关寄存器 本文属于《 RISC-V指令集基础系列教程》之一&#xff0c;欢迎查看其它文章。 1 中断类型 RISC-V 架构定义的中…

helm 部署 Kube-Prometheus + Grafana + 钉钉告警部署 Kube-Prometheus

背景 角色IPK8S 版本容器运行时k8s-master-1172.16.16.108v1.24.1containerd://1.6.8k8s-node-1172.16.16.109v1.24.1containerd://1.6.8k8s-node-2172.16.16.110v1.24.1containerd://1.6.8 安装 kube-prometheus mkdir -p /data/yaml/kube-prometheus/prometheus &&…

CDH集群hive初始化元数据库失败

oracle数据库操作&#xff1a; 报错如下&#xff1a;命令 (Validate Hive Metastore schema (237)) 已失败 截图如下&#xff1a; 后台日志部分摘录&#xff1a; WARNING: Use “yarn jar” to launch YARN applications. SLF4J: Class path contains multiple SLF4J binding…

UE RPC 外网联机(1)

技术&#xff1a;RPC TCP通信 设计&#xff1a;大厅服务<---TCP--->房间服务<---RPC--->客户端&#xff08;Creator / Participator&#xff09; 1. PlayerController 用于RPC通信控制 2.GameMode 用于数据同步 3.类图 4. 注意 &#xff08;1&#xff09;RPC&a…

机器学习之决策树现成的模型使用

目录 须知 DecisionTreeClassifier sklearn.tree.plot_tree cost_complexity_pruning_path(X_train, y_train) CART分类树算法 基尼指数 分类树的构建思想 对于离散的数据 对于连续值 剪枝策略 剪枝是什么 剪枝的分类 预剪枝 后剪枝 后剪枝策略体现之威斯康辛州乳…

GIMP - GNU 图像处理程序 - 工具栏窗口 (Toolbox)

GIMP - GNU 图像处理程序 - 工具栏窗口 [Toolbox] 1. GNU Image Manipulation Program2. Windows -> Recently Closed Docks -> ToolboxReferences 1. GNU Image Manipulation Program 2. Windows -> Recently Closed Docks -> Toolbox References [1] Yongqiang …

软件概要设计说明书word原件(实际项目)

一、 引言 &#xff08;一&#xff09; 编写目的 &#xff08;二&#xff09; 范围 &#xff08;三&#xff09; 文档约定 &#xff08;四&#xff09; 术语 二、 项目概要 &#xff08;一&#xff09; 建设背景 &#xff08;二&#xff09; 建设目标 &#xff08;三&a…

Typora字数过多的时候造成卡顿现象如何解决?

Typora字数过多的时候造成卡顿现象如何解决&#xff1f; 点击 、切换、滚动、打字都有点卡顿&#xff0c;下面介绍三种方法&#xff0c;三种方法都可以尝试&#xff0c;建议先尝试方法一&#xff0c;效果不满意就用方法二&#xff0c;实在不行就最后一个取巧的办法。 方法1&a…

图像处理与视觉感知---期末复习重点(5)

文章目录 一、膨胀与腐蚀1.1 膨胀1.2 腐蚀 二、开操作与闭操作 一、膨胀与腐蚀 1.1 膨胀 1. 集合 A A A 被集合 B B B 膨胀&#xff0c;定义式如下。其中集合 B B B 也称为结构元素&#xff1b; ( B ^ ) z (\hat{B})z (B^)z 表示 B B B 的反射平移 z z z 后得到的新集合。…

《Vision mamba》论文笔记

原文出处&#xff1a; [2401.09417] Vision Mamba: Efficient Visual Representation Learning with Bidirectional State Space Model (arxiv.org) 原文笔记&#xff1a; What&#xff1a; Vision Mamba: Efficient Visual Representation Learning with Bidirectional St…

llama-index 结合chatglm3-6B 利用RAG 基于文档智能问答

简介 llamaindex结合chatglm3使用 import os import torch from llama_index.core import VectorStoreIndex, ServiceContext from llama_index.core.callbacks import CallbackManager from llama_index.core.llms.callbacks import llm_completion_callback from llama_ind…

Groovy基础入门

一、Groovy简介 Groovy是运行在JVM中的一种动态语言&#xff0c;可以在Java平台上进行编程&#xff0c;使用方式基本与使用Java代码的方式相同&#xff0c;它的语法与Java语言的语法很相似&#xff0c;与Java相比&#xff0c;Groovy更加灵活、简洁&#xff0c;而且完成同样的功…

C语言例4-6:格式字符d的使用例子

代码如下&#xff1a; //格式字符d的使用例子 #include<stdio.h> int main(void) {int num1123;long num2123456;printf("num1%d,num1%5d,num1%-5d,num1%2d\n",num1,num1,num1,num1);//以四种不同格式&#xff0c;输出int型数据num1的值printf("num2%ld,…

Mybatis别名 动态sql语句 分页查询

给Mybatis的实体类起别名 给Mybatis的xml文件注册mapper映射文件 动态sql语句 1 if 2 choose 3 where 4 foreach 一&#xff09;if 查询指定名称商品信息 语法&#xff1a; SELECT * FROM goods where 11 <if test "gName!null"> and g.g_name like co…