记一次 .NET 某企业采购平台 崩溃分析
一:背景
1. 讲故事
前段时间有个朋友找到我,说他们的程序有偶发崩溃的情况,让我帮忙看下怎么回事,针对这种 crash 的程序,用 AEDebug 的方式抓取一个便知,有了 dump 之后接下来就可以分析了。
二:Windbg 分析
1. 为什么会崩溃
既然是程序的崩溃,我们可以像看蓝屏一下看dump文件,使用 !analyze -v
命令即可。
0:000> !analyze -v
*******************************************************************************
* *
* Exception Analysis *
* *
*******************************************************************************
CONTEXT: (.ecxr)
rax=0000000000000000 rbx=0000000000f7ccb0 rcx=00007ffe23af7ab0
rdx=00000000013e3b10 rsi=0000000000f7ccb0 rdi=0000000000f7c7c0
rip=00007ffe538e7044 rsp=0000000000f7cf60 rbp=0000000000f7d770
r8=0000000000000001 r9=000000f7000006bd r10=0000000000f7d640
r11=0000000000f7cf50 r12=0000000000000001 r13=0000000000000001
r14=000000005c520126 r15=00000000013e3b10
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010204
clr!ComPreStubWorker+0xf2e54:
00007ffe`538e7044 f6403820 test byte ptr [rax+38h],20h ds:00000000`00000038=??
Resetting default scope
EXCEPTION_RECORD: (.exr -1)
ExceptionAddress: 00007ffe538e7044 (clr!ComPreStubWorker+0x00000000000f2e54)
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000001
NumberParameters: 2
Parameter[0]: 0000000000000000
Parameter[1]: 0000000000000038
Attempt to read from address 0000000000000038
PROCESS_NAME: xxx.exe
READ_ADDRESS: 0000000000000038
ERROR_CODE: (NTSTATUS) 0xc0000005 - 0x%p 0x%p %s
EXCEPTION_CODE_STR: c0000005
EXCEPTION_PARAMETER1: 0000000000000000
EXCEPTION_PARAMETER2: 0000000000000038
STACK_TEXT:
00000000`00f7cf60 00007ffe`538e7044 clr!ComPreStubWorker+0xf2e54
00000000`00f7d590 00007ffe`53712d62 clr!ComCallPreStub+0x62
00000000`00f7d660 00007ffe`1de3ba83 wwkrn64+0xba83
00000000`00f7d740 00007ffe`638ebc70 ole32!CPrivDragDrop::PrivDragDrop+0x2b0
00000000`00f7d790 00007ffe`638eb98c ole32!PrivDragDrop+0x198
00000000`00f7d830 00007ffe`638a9c1e ole32!CDragOperation::GetDropTarget+0xee
00000000`00f7d8b0 00007ffe`638ac239 ole32!CDragOperation::UpdateTarget+0x4cd
....
从上面的信息看,这个程序是一个经典的 访问违例
异常,违例是因为 rax=0
导致读取了不该读取的地方,接下来我们切到异常上下文看下为什么会是 0 ?
2. eax 为什么会是 0
要想切到异常上下文,先使用 .ecxr
命令,再使用 ub 反汇编。
0:000> .ecxr
rax=0000000000000000 rbx=0000000000f7ccb0 rcx=00007ffe23af7ab0
rdx=00000000013e3b10 rsi=0000000000f7ccb0 rdi=0000000000f7c7c0
rip=00007ffe538e7044 rsp=0000000000f7cf60 rbp=0000000000f7d770
r8=0000000000000001 r9=000000f7000006bd r10=0000000000f7d640
r11=0000000000f7cf50 r12=0000000000000001 r13=0000000000000001
r14=000000005c520126 r15=00000000013e3b10
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010204
clr!ComPreStubWorker+0xf2e54:
00007ffe`538e7044 f6403820 test byte ptr [rax+38h],20h ds:00000000`00000038=??
0:000> ub 00007ffe`538e7044
clr!ComPreStubWorker+0xf2e20:
00007ffe`538e7010 0f8591d3f0ff jne clr!ComPreStubWorker+0x1b4 (00007ffe`537f43a7)
00007ffe`538e7016 488b0e mov rcx,qword ptr [rsi]
00007ffe`538e7019 488d81b8ffffff lea rax,[rcx-48h]
00007ffe`538e7020 48898424c0050000 mov qword ptr [rsp+5C0h],rax
00007ffe`538e7028 48898424b8050000 mov qword ptr [rsp+5B8h],rax
00007ffe`538e7030 488b89c0ffffff mov rcx,qword ptr [rcx-40h]
00007ffe`538e7037 e8fcfcebff call clr!MethodTable::GetComCallWrapperTemplate (00007ffe`537a6d38)
00007ffe`538e703c 48898424b0050000 mov qword ptr [rsp+5B0h],rax
从汇编代码看,rax 是 clr!MethodTable::GetComCallWrapperTemplate
方法的返回值,从方法名字看是一个经典的 COM 和 .NET 互操作,接下来继续用 uf
反汇编看下这个方法,精简后的代码如下:
0:000> uf clr!MethodTable::GetComCallWrapperTemplate
00007ffe`537a6d38 48895c2408 mov qword ptr [rsp+8],rbx
00007ffe`537a6d3d 57 push rdi
...
00007ffe`537a6d57 488bcb mov rcx,rbx
00007ffe`537a6d5a e8c943f7ff call clr!MethodTable::GetClass (00007ffe`5371b128)
00007ffe`537a6d5f 488b4030 mov rax,qword ptr [rax+30h]
...
再结合 coreclr 源码:
inline ComCallWrapperTemplate *MethodTable::GetComCallWrapperTemplate()
{
LIMITED_METHOD_CONTRACT;
return GetClass()->GetComCallWrapperTemplate();
}
class EEClass // DO NOT CREATE A NEW EEClass USING NEW!
{
ComCallWrapperTemplate* m_pccwTemplate; // points to interop data structures used when this type is exposed to COM
inline ComCallWrapperTemplate *GetComCallWrapperTemplate()
{
LIMITED_METHOD_CONTRACT;
return m_pccwTemplate;
}
}
到这里大概能推测到是因为 EEClass.m_pccwTemplate
字段为 null 所致,从注释看,他是 CLR 用来暴露给 COM 使用的数据结构,那为什么暴露给 COM 使用的数据结构为 NULL 呢? 这个分析起来就复杂了。
但有一点可以确定,像这种逻辑必然是 坚如磐石
,受过日月精华,经历过500年的风吹雨打,不可能无缘无故的出篓子。
3. 出路在哪里
要寻找突破口还得从调用栈入手,我们用 k
命令洞察一下。
0:000> k
*** Stack trace for last set context - .thread/.cxr resets it
# Child-SP RetAddr Call Site
00 00000000`00f7cf60 00007ffe`53712d62 clr!ComPreStubWorker+0xf2e54
01 00000000`00f7d590 00007ffe`1de3ba83 clr!ComCallPreStub+0x62
02 00000000`00f7d660 00007ffe`638ebc70 wwkrn64+0xba83
03 00000000`00f7d740 00007ffe`638eb98c ole32!CPrivDragDrop::PrivDragDrop+0x2b0 [com\ole32\com\rot\getif.cxx @ 659]
04 00000000`00f7d790 00007ffe`638a9c1e ole32!PrivDragDrop+0x198 [com\ole32\com\rot\getif.cxx @ 920]
05 00000000`00f7d830 00007ffe`638ac239 ole32!CDragOperation::GetDropTarget+0xee [com\ole32\ole232\drag\drag.cpp @ 1128]
06 00000000`00f7d8b0 00007ffe`638ac91c ole32!CDragOperation::UpdateTarget+0x4cd [com\ole32\ole232\drag\drag.cpp @ 2026]
07 00000000`00f7d9a0 00007ffe`2443f664 ole32!DoDragDrop+0x10c [com\ole32\ole232\drag\drag.cpp @ 3007]
08 00000000`00f7dc80 00007ffe`244ccd8d System_Windows_Forms_ni+0x9cf664
...
仔细观察线程栈信息,不难发现用户是在用 DoDragDrop
方法实现控件的拖拽,不过在执行流中有一个陌生的动态链接库 wwkrn64
,它到底是何方神圣呢?我们用 lmvm 观察下。
0:000> lmvm wwkrn64
Browse full module list
start end module name
00007ffe`1de30000 00007ffe`1df1e000 wwkrn64 C (export symbols) wwkrn64.dll
Loaded symbol image file: wwkrn64.dll
Image path: D:\xxx\wwall\wwkrn64.dll
Image name: wwkrn64.dll
Browse all global symbols functions data
Timestamp: Wed Apr 26 10:18:26 2023 (644889F2)
CheckSum: 00000000
ImageSize: 000EE000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
Information from resource tables:
从输出信息看,果然是一个外来物种,经过网上一顿搜索,发现是一款 信息安全软件
,哪家公司就模糊了哈,截图如下:
到这里就真相大白了,让朋友把这款软件卸载掉再试试看,问题就解决了。
4. 安全软件为什么要介入
我这里只能简单推测一下,ComCallPreStub
和 ComPreStubWorker
方法是 JIT 在编译某一个方法时的前缀路径,也是很多 加壳软件
以及 永恒之蓝
这样的蠕虫病毒重点关注的方法,所以这些高危方法自然也是 安全软件
重点监视的,如果 安全软件
没处理好,自然就会误杀。。。
当然真正的原因只能问 系铃人
。
可能有些朋友要说了,怎么验证这两个方法就是 JIT 编译的前缀,这里我们用 普通方法+windbg 的方法简单验证下吧,参考代码如下:
internal class Program
{
static void Main(string[] args)
{
Debugger.Break();
Test();
Console.ReadLine();
}
static void Test()
{
Console.WriteLine("Test1");
}
}
接下来我们重点观察下 Test
方法的编译过程,看过程之前先上一张架构图:
从架构图看 Test() 方法的编译最终是由 clrjit!jitNativeCode
来处理的,要想验证很简单用 bp clrjit!jitNativeCode
下一个断点即可。
0:000> bp clrjit!jitNativeCode
0:000> g
Breakpoint 0 hit
clrjit!jitNativeCode:
00007ffb`590cc040 4c894c2420 mov qword ptr [rsp+20h],r9 ss:0000009c`5efed218=0000000000000000
0:000> k
# Child-SP RetAddr Call Site
00 0000009c`5efed1f8 00007ffb`5917d683 clrjit!jitNativeCode [D:\a\_work\1\s\src\coreclr\jit\compiler.cpp @ 6941]
01 0000009c`5efed200 00007ffb`594d3091 clrjit!CILJit::compileMethod+0x83 [D:\a\_work\1\s\src\coreclr\jit\ee_il_dll.cpp @ 279]
02 (Inline Function) --------`-------- coreclr!invokeCompileMethodHelper+0x86 [D:\a\_work\1\s\src\coreclr\vm\jitinterface.cpp @ 12774]
03 (Inline Function) --------`-------- coreclr!invokeCompileMethod+0xc5 [D:\a\_work\1\s\src\coreclr\vm\jitinterface.cpp @ 12839]
04 0000009c`5efed270 00007ffb`594d274d coreclr!UnsafeJitFunction+0x7f1 [D:\a\_work\1\s\src\coreclr\vm\jitinterface.cpp @ 13355]
05 0000009c`5efed760 00007ffb`594d22ce coreclr!MethodDesc::JitCompileCodeLocked+0x1f1 [D:\a\_work\1\s\src\coreclr\vm\prestub.cpp @ 1051]
06 0000009c`5efed930 00007ffb`59472009 coreclr!MethodDesc::JitCompileCodeLockedEventWrapper+0x466 [D:\a\_work\1\s\src\coreclr\vm\prestub.cpp @ 920]
07 0000009c`5efeda90 00007ffb`59473f58 coreclr!MethodDesc::JitCompileCode+0x2a9 [D:\a\_work\1\s\src\coreclr\vm\prestub.cpp @ 860]
08 (Inline Function) --------`-------- coreclr!MethodDesc::PrepareILBasedCode+0x5ae [D:\a\_work\1\s\src\coreclr\vm\prestub.cpp @ 439]
09 (Inline Function) --------`-------- coreclr!MethodDesc::PrepareCode+0x5ae [D:\a\_work\1\s\src\coreclr\vm\prestub.cpp @ 332]
0a 0000009c`5efedb40 00007ffb`5947340c coreclr!CodeVersionManager::PublishVersionableCodeIfNecessary+0x7f8 [D:\a\_work\1\s\src\coreclr\vm\codeversion.cpp @ 1701]
0b 0000009c`5efee070 00007ffb`5947316b coreclr!MethodDesc::DoPrestub+0x16c [D:\a\_work\1\s\src\coreclr\vm\prestub.cpp @ 2215]
0c 0000009c`5efee190 00007ffb`595abec5 coreclr!PreStubWorker+0x21b [D:\a\_work\1\s\src\coreclr\vm\prestub.cpp @ 2039]
0d 0000009c`5efee320 00007ffa`f9a0296e coreclr!ThePreStub+0x55
0e 0000009c`5efee3d0 00007ffb`595aae93 Example_19_1_1!Example_19_1_1.Program.Main+0x2e [D:\skyfly\19.20230624\src\Example\Example_19_1_1\Program.cs @ 10]
...
如果想在 jitNativeCode
方法中把 md
提取出来的话,可以取 r9 参数。
0:000> !dumpmd poi(r9)
Method Name: Example_19_1_1.Program.Test()
Class: 00007ffaf9abd520
MethodTable: 00007ffaf9ac8880
mdToken: 0000000006000006
Module: 00007ffaf9ac6908
IsJitted: no
Current CodeAddr: ffffffffffffffff
Version History:
ILCodeVersion: 0000000000000000
ReJIT ID: 0
IL Addr: 000001f865be20a7
CodeAddr: 0000000000000000 (MinOptJitted)
NativeCodeVersion: 0000000000000000
三:总结
这次崩溃事故的直接原因是由于第三方安全软件的介入导致的,因 ComPreStubWorker
是加壳程序和蠕虫病毒注入的突破口,不管怎样还是希望安全软件对高危函数 ComPreStubWorker
的照护逻辑再优化下吧,减少误杀的发生。
记一次 .NET 某企业采购平台 崩溃分析的更多相关文章
- 记一次 Windows10 内存压缩模块 崩溃分析
一:背景 1. 讲故事 在给各位朋友免费分析 .NET程序 各种故障的同时,往往也会收到各种其他类型的dump,比如:Windows 崩溃,C++ 崩溃,Mono 崩溃,真的是啥都有,由于基础知识的相 ...
- 记一次 .NET 某自动化集采软件 崩溃分析
一:背景 1.讲故事 前段时间有位朋友找到我,说他的程序在客户的机器上跑着跑着会出现偶发卡死,然后就崩掉了,但在本地怎么也没复现,dump也抓到了,让我帮忙看下到底怎么回事,其实崩溃类的dump也有简 ...
- iOS 崩溃分析
崩溃统计分析,在APP中是非常常见一种优化APP,发现APP的BUG的方式. 1.异常处理 可通过try catch 方式处理,如果发生异常,会走catch ,最终走fianlly.对一些我们不想他崩 ...
- windowsclient崩溃分析和调试
本文介绍windows上崩溃分析的一些手段,顺便提多进程调试.死锁等. 1.崩溃分析过程 1.1 确认错误码 不管是用windbg还是用vs.首先应该注意的是错误码,而90%以上的崩溃都是非法訪问. ...
- 记一次程序从x86_64linux平台移植到armv7平台
前言 最近接了个任务,需要把代码移植到armv7平台,搜寻相关方法,了解到可以利用交叉编译工具如:gcc-linaro-arm-linux-gnueabihf.把自己依赖的第三方库代码和自己代码分别编 ...
- Unity3D跨平台动态库编译---记kcp基于CMake的各平台构建实践
一 为什么需要动态库 1)提供原生代码(native code)的支持,也叫原生插件,但是我实践的是c/cpp跨平台动态库,这里不具体涉及安卓平台java库和ios平台的objectc库构建. 2)某 ...
- 记一次 .NET 某教育系统API 异常崩溃分析
一:背景 1. 讲故事 这篇文章起源于 搬砖队大佬 的精彩文章 WinDBg定位asp.net mvc项目异常崩溃源码位置 ,写的非常好,不过美中不足的是通览全文之后,总觉得有那么一点不过瘾,就是没有 ...
- 记一次 .NET 某医疗器械 程序崩溃分析
一:背景 1.讲故事 前段时间有位朋友在微信上找到我,说他的程序偶发性崩溃,让我帮忙看下怎么回事,上面给的压力比较大,对于这种偶发性崩溃,比较好的办法就是利用 AEDebug 在程序崩溃的时候自动抽一 ...
- 记一次 .NET 某工控MES程序 崩溃分析
一:背景 1.讲故事 前几天有位朋友找到我,说他的程序出现了偶发性崩溃,已经抓到了dump文件,Windows事件日志显示的崩溃点在 clr.dll 中,让我帮忙看下是怎么回事,那到底怎么回事呢? 上 ...
- 记一次 .NET 某医疗住院系统 崩溃分析
一:背景 1. 讲故事 最近收到了两起程序崩溃的dump,查了下都是经典的 double free 造成的,蛮有意思,这里就抽一篇出来分享一下经验供后面的学习者避坑吧. 二:WinDbg 分析 1. ...
随机推荐
- 淘宝/天猫获得淘宝商品评论 API 返回值说明
item_review-获得淘宝商品评论 taobao.item_review 公共参数 API测试工具 名称 类型 必须 描述 key String 是 调用key(必须以GET方式拼接在URL中) ...
- flask基本项目结构
|-- myproject |-- config.py |-- flask-env.yml |-- manage.py |-- app | |-- models.py | |-- __init__.p ...
- django模糊查询排序
class Book(models.Model): """ 列名 """ class Meta: db_table = 'book' nam ...
- C++重载的奥义之运算符重载
0.引言 重载,顾名思义从字面上理解就是重复装载,打一个不恰当的比方,你可以用一个篮子装蔬菜,也可以装水果或者其它,使用的是同一个篮子,但是可以用篮子重复装载的东西不一样. 正如在之前的文章<重 ...
- 如何在 .NET Core WebApi 中处理 MultipartFormDataContent
最近在对某个后端服务做 .NET Core 升级时,里面使用了多处处理 MultipartFormDataContent 相关内容的代码.这些地方从 .NET Framework 迁移到 .NET C ...
- cesium源码编译调试及调用全过程
完整记录一次cesium源码从下载.打包.调用.调试的全过程. 本文使用软件或API版本: VSCode Node:12.18.3 cesium版本:1.94 总体步骤: 下载源码 执行npm ins ...
- JS 打开本地应用软件
我们有遇到可以直接打开QQ的跳转链接,也有遇到过直接启动office打开文档. 具体是如何操作的呢? 添加注册表项 首先需要在注册表中添加应用软件的启动地址,操作方式如下 在HKEY_CLASSES_ ...
- 沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟
目录 沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置 沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟 CH32V 存储容量命 ...
- 云原生时代崛起的编程语言Go并发编程实战
@ 目录 概述 基础理论 并发原语 协程-Goroutine 通道-Channel 多路复用-Select 通道使用 超时-Timeout 非阻塞通道操作 关闭通道 通道迭代 定时器-TimerAnd ...
- Pwn系列之Protostar靶场 Stack1题解
(gdb) disasse main Dump of assembler code for function main: 0x08048464 <main+0>: push ebp 0x0 ...