热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Windbg实战系列之升级半成品引发的血案

《Windbg实战系列》围绕着Windbg解决实际开发中遇到的各种各样的问题,其间会涉及到:1)OS的异常分发机制:a、从CPU到内核到用户态,一个异常是如何被分发的;b、VEH、SEH、UEF、VC

《Windbg实战系列》围绕着Windbg解决实际开发中遇到的各种各样的问题,其间会涉及到:

1)OS的异常分发机制:

a、从CPU到内核到用户态,一个异常是如何被分发的;

b、VEH、SEH、UEF、VCH等在异常分发中所扮演的角色;

c、关键的异常分发函数的逆向分析;

d、与异常分发相关的注册表配置项;

继续补充

2)Windbg的dmp分析:

a、dmp分析的各类命令与背后的实现原理;

b、各类dmp的分析实战;

c、dmp的文件格式解析;

d、常用的抓取dmp的方法;

e、dmp分析中栈回溯的各种方法;

继续补充

3)Windbg的动态调试排错:

a、动态调试及涉及到的断点命令与各类断点技巧;

b、gflags.exe与调试相关的配置;

c、堆对调试的支持——各种堆结构分析;

d、栈对调试的支持——GS选项;

e、通过调试解决各类问题:死锁、资源泄漏、内存泄漏、性能分析、镜像校验等等等;

继续补充

4)Windbg的内核学习:

a、在调试器中突破各种OS内核安全防护机制;

b、在调试器中分析OS的内核对象管理目录结构;

c、在调试器中分析OS的注册表结构实现注册表穿透;

d、在调试器中分析OS全局句柄表及进程局部句柄表实现提权;

e、在调试器中遍历全局ATOM表;

f、 在调试器中遍历全局HOOK链/局部HOOK链;

g、 在调试器中手动解析PDE/PTE页表,给0x00000000手动挂物理页;

继续补充

0、为了避免具体公司及相关项目信息的泄漏,本文中隐去了所有名称信息,代之以A、B之类的标识符;

 

1、背景

这两天client灰度了新版本,根据后台上报的crash来看,总体情况还算稳定,但列表中出现一类新的crash,之前没这么集中的出现过,这个新版本连着出现了多次,肯定是不正常的;下载一个下来分析了下,挺有代表性,记录下来;

 

2、分析过程:


2.1、step1:Windbg打开,.exr -1看了下异常记录里所呈现的信息,如下:

0:000> .exr -1
ExceptionAddress: 00822d00
ExceptionCode: c0000005 (Access violation)
ExceptionFlags: 00000000
NumberParameters: 2
Parameter[0]: 00000008
Parameter[1]: 00822d00
Attempt to execute non-executable address 00822d00

上边标红的两行,第一行的意思是说“访问异常”,基本就是访问了不该访问的内存,比如处于Free状态的内存;或者是该内存是只读的,却往里边写数据了;第二行的意思是说,代码执行了不可执行的部分,换句话说就是EIP跑飞了;当然这个第二行的提示文本是Windbg善解人意的自动推导出来辅助你分析的,可能对也可能不对;且看下边继续分析;

2.2、step2:看一下00822d00附近的代码:

0:000> u 00822d00
00822d00 0000 add byte ptr [eax],al
00822d02 0000 add byte ptr [eax],al
00822d04 0000 add byte ptr [eax],al
00822d06 0000 add byte ptr [eax],al
00822d08 0000 add byte ptr [eax],al
00822d0a 0000 add byte ptr [eax],al
00822d0c 70e1 jo 00822cef
00822d0e 8400 test byte ptr [eax],al

确实不正常,从反汇编出来的代码看,这段数据不应该是可执行代码,EIP确实跑飞了;为了确保万无一失,再看下这块内存的属性,如下:

0:000> !address 00822d00
Usage: Image
Base Address: 007fb000
End Address: 0084b000
Region Size: 00050000 ( 320.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000002 PAGE_READONLY
Type: 01000000 MEM_IMAGE
Allocation Base: 00690000
Allocation Protect: 00000080 PAGE_EXECUTE_WRITECOPY
Image Path: G:aaaabbbbccccdddd.dll
Module Name: dddd
Loaded Image Name: dddd.dll
Mapped Image Name: c:usersaaaadesktoppdbdddd.dll

入上边标红的属性所示,这块内存确实不可执行,也正如Windbg提示的那样。此外,我们还可以得到的信息是,这块内存确实是cgclient.dll模块中的,而且这块内存也是MEM_COMMIT的,换句话说它确实是挂了物理页的;那么EXCEPTION_RECORD中记录的Access violation基本就是往只读内存块写入数据了,这个也被PAGE_READONLY所佐证;

2.3、step3:下面就是将上下文恢复到出现问题[异常]时的那会了;执行.ecxr即可;

0:000> .ecxr
eax=0080e41c ebx=0058ea28 ecx=0059e0b4 edx=0554ce08 esi=07b600d0 edi=0000001d
eip=00822d00 esp=0027d690 ebp=0027d6e0 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210202
00822d00 0000 add byte ptr [eax],al ds:002b:0080e41c=d0

简单分析下上边所给出的信息;触发异常的显然就是这句汇编代码了,触发的原因也自然是eax这块内存有问题,啥问题?不可写或者根本就是Free的呗;到底是哪一种情况呢?看一下这块内存的属性吧,如上图所示,这块内存确实是MEM_COMMIT的,就排除了Free的可能性,那自然就是只读的这种情况了,结合PAGE_READONLY便铁证如山了;好,到现在已有的结论整理如下:
EIP跑飞了,去执行了一块不可执行内存的数据且往一块只读内存里写数据;这边留一个疑问,既然这块内存不可执行,那为何触发的不是不可执行异常呢?

2.4、step4:下一个问题就是哪里导致的程序EIP跑飞了?

大家可以先停一下,思考一下这个问题;从汇编角度来说,肯定是执行了修改EIP寄存器的指令了;常规的修改EIP之类的方法主要有两类,如下:

1)jcc类;
2)call类;

当然除了这两类,还有很多其他的变通方法,如ret、ret n、iret、syscall、sysexit、int n、调用门、中断门、陷阱门、任务门、任务段等等方式。但考虑到常规的开发中都是用高级语言编写的,不太会涉及到底层的汇编语言,而C/CPP这些语言遇到最多的便是if-else switch-case这些分支语句,他们编译后对应的都是jcc类,虚函数调用编译后则对应的是call类;jcc导致的EIP跑飞的可能性不大,因为正常情况下,jcc跳转都是段跳转,基本都是在一个函数内跳转,同一个函数内的内存属性是一致的,所以jcc基本排除;但这不是100%的,笔者曾经遇到过一起案例,就是jcc导致的EIP跑飞,这种场景在安全对抗中常见;那剩下的就是call这一类了;可能大家会问,为什么非要区分个所以然出来呢?嗯,好问题。既然要找到是哪里触发的EIP跑飞,那自然是想要知道上一条执行在哪里执行的了;

2.5、step5:直捣黄龙,揪出bug;且看下边的执行命令;

0:000> dd 0027d690
0027d690 013a2220 c3167915 0939bd50 017941f8
0027d6a0 0939be60 092b510c 092b5100 09331218
0027d6b0 00000000 07b600d0 30303100 00003330
0027d6c0 00000008 0939bd50 00000000 0000000f
0027d6d0 c3167915 0027d75c 0165f6c2 ffffffff
0027d6e0 0027d768 01610c54 0939bd60 0939bd74
0027d6f0 0027d708 00000001 c316789d 0939bd50
0027d700 07b38548 092b2958 0052e9f0 00540000
0:000> !address 013a2220
Usage: Image
Base Address: 01371000
End Address: 0168a000
Region Size: 00319000 ( 3.098 MB)
State: 00001000 MEM_COMMIT
Protect: 00000020 PAGE_EXECUTE_READ
Type: 01000000 MEM_IMAGE
Allocation Base: 01370000
Allocation Protect: 00000080 PAGE_EXECUTE_WRITECOPY
Image Path: G:aaaabbbbcccc..kkkkmmmm.exe
Module Name: mmmm
Loaded Image Name: mmmm.exe
0:000> ub 013a2220
013a21f5 ff1590b66801 call dword ptr [0168b690]
013a21fb c70694447901 mov dword ptr [esi],01794494
013a2201 c7460898467901 mov dword ptr [esi+8],01794698
013a2208 c745fcffffffff mov dword ptr [ebp-4],0FFFFFFFFh
013a220f 8b8bec010000 mov ecx,dword ptr [ebx+1ECh]
013a2215 89b3d8010000 mov dword ptr [ebx+1D8h],esi
013a221b 8b01 mov eax,dword ptr [ecx]
013a221d ff5054 call dword ptr [eax+54h]

找出来了,罪魁祸首就是上边标黄色的这行;这行为什么会有问题呢?简单分析下,上边比较关键的几行汇编指令截取如下:

013a221b 8b01 mov eax,dword ptr [ecx]
013a221d ff5054 call dword ptr [eax+54h]

对CPP底层了解的同学应该知道,CPP的方法调用约定默认是 _thiscall,该调用约定的传参方式是第一个参数通过ECX寄存器传递,其他参数通过栈传递,那么对于CPP来说,通常第一个参数都是编译器自动安排的对象地址,那么ECX就是指向该对象地址了;带有虚函数的类,其对象的第一个元素便是虚表指针,指向该类所提供的虚函数;那么013a221b处汇编指令就是取出虚表指针了;我们可以通过查看下eax的数据来看下上边这个结论是否正确;如下:

由step3知道eax=0080e41
0:000> dps 0080e41c
0080e41c 006cd8d0 dddd!nn::hhhh::uuuu::`scalar deleting destructor'
0080e420 006cda10 dddd!nn::hhhh::uuuu::loadAAAA
0080e424 006cda20 dddd!nn::hhhh::uuuu::setBBBB
0080e428 006cda40 dddd!nn::hhhh::uuuu::getBBBB
0080e42c 006cda50 dddd!nn::hhhh::uuuu::setCCCC
0080e430 006cda70 dddd!nn::hhhh::uuuu::getCCCC
0080e434 006cda90 dddd!nn::hhhh::uuuu::setDDDD
0080e438 006cdab0 dddd!nn::hhhh::uuuu::getDDDD
0080e43c 006cdaf0 dddd!nn::hhhh::uuuu::setEEEE
0080e440 006cdb10 dddd!nn::hhhh::uuuu::getEEEE
0080e444 006cdb30 dddd!nn::hhhh::uuuu::setFFFF
0080e448 006cdb50 dddd!nn::hhhh::uuuu::getFFFF
0080e44c 006cdb70 dddd!nn::hhhh::uuuu::getMMMM
0080e450 006cdbb0 dddd!nn::hhhh::uuuu::getMMMM
0080e454 006cdbc0 dddd!nn::hhhh::uuuu::updateJJJJ
0080e458 006cdc10 dddd!nn::hhhh::uuuu::getTTTT
0080e45c 006cdc30 dddd!nn::hhhh::uuuu::getRRRR
0080e460 006cdc70 dddd!nn::hhhh::uuuu::getEEEE
0080e464 006cdc80 dddd!nn::hhhh::uuuu::getWWWW
0080e468 006cdd10 dddd!nn::hhhh::uuuu::getQQQQ
0080e46c 006cddb0 dddd!nn::hhhh::uuuu::applyYYYY
0080e470 00822d00 dddd!nn::hhhh::uuuu::`RTTI Complete Object Locator'
0080e474 006cd6e0 dddd!nn::hhhh::uuuu::`scalar deleting destructor'
0080e478 007c2b61 dddd!purecall
0080e47c 007c2b61 dddd!purecall

基本判断无误,好了接下来013a221d处的汇编代码就很好理解了,便是调用虚函数,该虚函数的索引号如下:

0:000> ?54/4
Evaluate expression: 21 = 00000015

细心数一下就会发现,21号虚函数对应的恰恰是上边标蓝的这行,再看下这个所谓的方法的地址是00822d00,这个地址与前边EXCEPTION_RECORD中记录的是一致的,相互佐证;

2.6、step6:为什么会call到这个地方来?编译器问题?玄学?

我去看了下git上该分支下这个类的虚函数定义,发现本次的更新包中,该类新增了两个虚函数方法,索引号21便是其中新增的一个虚函数,但这里却实实在在的告诉我——该类就是没有你新增的这个接口,赤裸裸的挑衅;啥原因呢?看下版本号吧,如下:

0:000> lmvm mmmm
Browse full module list
start end module name
01370000 0187d000 mmmm
Loaded symbol image file: mmmm.exe
Mapped memory image file: c:usersaaaadesktoppdbmmmm.exe
Image path: G:aaaabbbbcccc..kkkkmmmm.exe
Image name: mmmm.exe
Browse all global symbols functions data
Timestamp: Mon Jun 1 11:08:39 2020 (5ED47137)
CheckSum: 0050FCE2
ImageSize: 0050D000
File version: 10.30.2000.1085
Product version: 10.30.2000.1085
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 1.0 App
File date: 00000000.00000000
Translations: 0804.04b0
Information from resource tables:
CompanyName: CCCC
ProductName: BBBB
InternalName: mmmm.exe
OriginalFilename: mmmm.exe
ProductVersion: 10.30.2000.1085
FileVersion: 10.30.2000.1085
FileDescription: CCCC
LegalCopyright: Copyright (C) 2018
0:000> lmvm dddd
Browse full module list
start end module name
00690000 0086b000 dddd
Loaded symbol image file: dddd.dll
Mapped memory image file: c:usersaaaadesktoppdbdddd.dll
Image path: G:aaaabbbbccccdddd.dll
Image name: dddd.dll
Browse all global symbols functions data
Timestamp: Wed May 20 16:58:22 2020 (5EC4F12E)
CheckSum: 001E8204
ImageSize: 001DB000
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
Information from resource tables:

嗯,很明显,下边这个dll没有文件版本号这个信息,那该怎么办?退而求其次,比对下其他信息;

mmmm.exe的Timestamp为 Mon Jun 1 11:08:39 2020 (5ED47137) -------->2020.06.01 11:08:39
dddd.dll的Timestamp为 Wed May 20 16:58:22 2020 (5EC4F12E) -------->2020.05.20 16:58:22

很显然了,升级的时候,mmmm.exe升级成功了而dddd.dll升级失败了,下次用于再次启动mmmm.exe时,调用打了一个旧的dddd.dll中没有的方法,然后EIP就跑飞了,再然后程序就Crash了;

 

3、这便是整个分析过程了;

 

4、小彩蛋——探寻命令背后的实现原理

Q: .exr与.ecxr他们在干什么?不用这两个命令是否能够进行dmp的分析?
A: 当然可以,并且很多情况下,这两个命令压根没啥用,下篇将专门讲解.exr与.ecxr背后的实现原理,看看如何手工分析数据以替换这两个命令的工作;这里简单提下:

.exr ----> _EXCEPTION_REOCRD;
.ecxr/.excr ----> _CONTEXT


推荐阅读
  • 原文地址http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/最开始时 ... [详细]
  • 本文介绍了使用数据库管理员用户执行onstat -l命令来监控GBase8s数据库的物理日志和逻辑日志的使用情况,并强调了对已使用的逻辑日志是否及时备份的重要性。同时提供了监控方法和注意事项。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 在Oracle11g以前版本中的的DataGuard物理备用数据库,可以以只读的方式打开数据库,但此时MediaRecovery利用日志进行数据同步的过 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • Python中sys模块的功能及用法详解
    本文详细介绍了Python中sys模块的功能及用法,包括对解释器参数和功能的访问、命令行参数列表、字节顺序指示符、编译模块名称等。同时还介绍了sys模块中的新功能和call_tracing函数的用法。推荐学习《Python教程》以深入了解。 ... [详细]
  • 流数据流和IO流的使用及应用
    本文介绍了流数据流和IO流的基本概念和用法,包括输入流、输出流、字节流、字符流、缓冲区等。同时还介绍了异常处理和常用的流类,如FileReader、FileWriter、FileInputStream、FileOutputStream、OutputStreamWriter、InputStreamReader、BufferedReader、BufferedWriter等。此外,还介绍了系统流和标准流的使用。 ... [详细]
  • PatchODAX8: ... [详细]
  • OCR:用字符识别方法将形状翻译成计算机文字的过程Matlab:商业数学软件;CUDA:CUDA™是一种由NVIDIA推 ... [详细]
author-avatar
强毛
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有