0x00491a08指令引用的0x00000000内存。该内存不能为written?

    • 5.1、以文本为中心的设计
    • 5.2、增加复杂性层次结构
    • 5.3、原来的DOM没有经过封装
    • 6.3、漏洞产生的根本原因分析

Explorer进程并导航到恶意网页来实现攻击。此攻击中使用的漏洞被分配为CVE-,并于2021年6月由Microsoft修复。

0day漏洞在野利用的攻击事件,例如:CVE-、CVE-,所以研究Internet Explorer漏洞,还是存在一定的意义。

4发布,之后不断的加入新的技术并随着新版本的Internet Explorer发布。在Trident7.0(Internet Explorer 11使用)中,微软对Trident排版引擎做了重大的变动,除了加入新的技术之外,并增加了对网页标准的支持。EdgeHTML是由微软开发并用于Microsoft Edge的专有排版引擎。该排版引擎是Trident的一个分支,但EdgeHTML移除所有旧版Internet Explorer遗留下来的代码,并重写主要的代码以和其他现代浏览器的设计精神互通有无。

在Google威胁分析团队发布了上面所说的那篇文章之后,又在Google Project Zero的博客上公布了这些漏洞的细节。本文章就是对Internet Explorer中的CVE-漏洞的分析过程的一个记录。我之前分析过老版本的Internet Explorer的漏洞,这是第一次比较正式的分析新版本Internet Explorer的漏洞,如有错误和不足之处,还望见谅。

innerHTML属性对内部html元素设置内容(包含文本字符串)时触发的。通过innerHTML属性修改标签之间的内容时,会造成IE生成的DOM树/DOM流的结构发生改变,IE会调用CSpliceTreeEngine类的相关函数对IE的DOM树/DOM流的结构进行调整。当调用CSpliceTreeEngine::RemoveSplice()去删除一些DOM树/DOM流结构时,恰好这些结构中包含文本字符串时,就有可能会造成堆越界写。

设置”。注意:设置界面拥有两个选项卡,“系统设置”和“程序设置”。我们先看“系统设置”,与ASLR有关系的是“强制映像随机化(强制性ASLR)”、“随机化内存分配(自下而上ASLR)”、“高熵ASLR”,我们都将其设为关闭状态。先关闭“高熵ASLR”,然后再关闭其他两项。

“强制映像随机化(强制性ASLR)”,不管编译时是否使用“/DYNAMICBASE”编译选项进行编译,开启了“强制性ASLR”后,会对所有软件模块的加载基址进行随机化,包括未使用“/DYNAMICBASE”编译选项编译的软件模块。关于编译时是否使用了“/DYNAMICBASE”编译选项进行编译,可以使用“Detect It

“随机化内存分配(自下而上ASLR)”,开启了该选项后,当我们使用malloc()或HeapAlloc()在堆上申请内存时,得到的堆块地址将在一定程度上进行随机化。

“高熵ASLR”,这个选项需要配合“随机化内存分配(自下而上ASLR)”选项使用,开启了该选项后,会在“随机化内存分配(自下而上ASLR)”基础上,更大程度的随机化堆块的分配地址。

接下来,我们来看“程序设置”。由于Windows10可以对单独的应用程序设置缓解措施的开启或关闭,并且替换“系统设置”中的设置,造成关闭了“系统设置”中所有与ASLR相关的缓解措施后,dll模块的加载基址还是在变化。切换到“程序设置”选项卡后,找到iexplore.exe,点击编辑,将所有与ASLR有关的设置的“替代系统设置”的勾去掉。

设置完成后,重启一下操作系统。

由于原始PoC过于精简,无法观察到执行效果,对我理解程序的执行流程造成了一定的障碍。所以我尝试了以下几种经过修改的PoC,用于观察执行效果。

我们可以得出以下结论:PoC通过HTML DOM方法document.createElement(),创建了一个“html”结点(同时创建“head”和“body”结点),并把新创建的“html”结点添加到原有的“body”结点中。然后,创建了一个Array数组并进行了初始化。最后将该数组转化为字符串,通过HTML DOM的innerHTML属性,添加到新创建的“html”结点中的“body”结点中。

原始PoC中,并未将创建的Array数组初始化,我们通过Chrome的开发者工具查看未初始化的Array数组转化为字符串后,得到的是什么。这有助于我们后面在调试PoC时,观察字符串所对应的内存数据。

可以看到,初始化后的Array数组转化成字符串后,每个元素是使用“,”分隔的。未初始化的Array数组转化成字符串后,只有一连串的“,”。其个数为Array数组元素个数减1。

好了,我们现在开始通过调试复现此漏洞。这里使用的是原始的PoC。首先打开Internet Explorer,拖入PoC,会弹出一个提示框“Internet Explorer已限制此网页运行脚本或ActiveX控件”,表示现在html中的javascript代码还没有得到执行。这时,我们打开WinDbg,附加到iexplore.exe上,输入g命令运行,然后在Internet Explorer界面点击提示框中的“允许阻止的内容”(可能需要刷新一下)。然后Internet Explorer会执行异常,WinDbg会捕获到异常并中断下来。以下是Crash的现场情况:

通过观察WinDbg的输出信息,可以发现PoC造成了异常代码为0xc0000005的内存访问违例异常。0x63a46809处的异常代码向一个内存访问权限为PAGE_NOACCESS(不可访问)的地址写入一个值,从而造成Crash。通过k命令打印栈回溯,可以知道发生异常的代码位于MSHTML!CSpliceTreeEngine::RemoveSplice()函数中。

当如今的Web开发者想到DOM树时,他们通常会想到这样的一个树:

这样的树看起来非常的简单,然而,现实是Internet Explorer的DOM树的实现是相当复杂的。

简单地说,Internet Explorer的DOM树是为了20世纪90年代的网页设计的。当时设计原始的数据结构时,网页主要是作为一个文档查看器(顶多包含几个动态的GIF图片和其他的静态图片)。因此,算法和数据结构更类似于为Microsoft Word等文档查看器提供支持的算法和数据结构。回想一下网页发展的早期,JavaScript还没有出现,并不能通过编写脚本操作网页内容,因此我们所了解的DOM树并不存在。文本是组成网页的主要内容,DOM树的内部结构是围绕快速、高效的文本存储和操作而设计的。内容编辑(WYSIWYG:What You See Is What You Get)和以编辑光标为中心用于字符插入和有限的格式化的操作范式是当时网页开发的特点。

由于其以文本为中心的设计,DOM的原始结构是为了文本后备存储,这是一个复杂的文本数组系统,可以在最少或没有内存分配的情况下有效地拆分和连接文本。后备存储将文本(Text)和标签(Tag)表示为线性结构,可通过全局索引或字符位置(CP:Character Position)进行寻址。在给定的CP处插入文本非常高效,复制/粘贴一系列的文本由高效的“splice(拼接)”操作集中处理。下图直观地说明了如何将包含“hello world”的简单标记加载到文本后备存储中,以及如何为每个字符和标签分配CP。

文本后备存储为非文本实体(例如:标签和插入点)提供特殊的占位符。

为了存储非文本数据(例如:格式化和分组信息),另一组对象与后备存储分开进行维护:表示树位置的双向链表(TreePos对象)。TreePos对象在语义上等同于HTML源代码标记中的标签——每个逻辑元素都由一个开始和结束的TreePos表示。这种线性结构使得在深度优先前序遍历(几乎每个DOM搜索API和CSS/Layout算法都需要)DOM树时,可以很快的遍历整个DOM树。后来,微软扩展了TreePos对象以包括另外两种“位置”:TreeDataPos(用于指示文本的占位符)和PointerPos(用于指示诸如脱字符(“^大写字符”:用于表示不可打印的控制字符)、范围边界点之类的东西,并最终用于新特性,如:生成的内容结点)。

每个TreePos对象还包括一个CP对象,它充当标签的全局序数索引(对于遗留的document.all API之类的东西很有用)。从TreePos进入文本后备存储时需要用到CP,它可以使结点顺序的比较变得容易,甚至可以通过减去CP索引来得到文本的长度。

为了将它们联系在一起,一个TreeNode将成对的TreePos绑定在一起,并建立了JavaScript DOM所期望的“树”层次结构,如下图所示:

CP的设计造成了原有的DOM非常复杂。为了使整个系统正常工作,CP必须是最新的。因此,每次DOM操作(例如:输入文本、复制/粘贴、DOM API操作,甚至点击页面——这会在DOM中设置插入点)后都会更新CP。最初,DOM操作主要由HTML解析器或用户操作驱动,所以CP始终保持最新的模型是完全合理的。但是随着JavaScript和DHTML的兴起,这些操作变得越来越普遍和频繁。

为了保持原来的更新速度,DOM添加了新的结构以提高更新的效率,并且伸展树(SplayTree)也随之产生,伸展树是在TreePos对象上添加了一系列重叠的树连接。起初,增加的复杂性提高了DOM的性能,可以用O(log n)速度实现全局CP更新。然而,伸展树实际上仅针对重复的局部搜索进行了优化(例如:针对以DOM树中某个位置为中心的更改),并没有证明对JavaScript及其更多的随机访问模式具有同样的效果。

另一个设计现象是,前面提到的处理复制/粘贴的“Splice(拼接)”操作被扩展到处理所有的树突变。核心“Splice Engine(拼接引擎)”分三步工作,如下图所示:

在步骤1中,引擎将通过从操作开始到结束遍历树的位置(TreePos)来“记录”拼接信息。然后创建一个拼接记录,其中包含此操作的命令指令(在浏览器的还原栈(Undo Stack)中重用的结构)。

在步骤2中,从树中删除与操作关联的所有结点(即TreeNode和TreePos对象)。请注意,在IE DOM树中,TreeNode/TreePos对象与脚本引用的Element对象不同,TreeNode/TreePos对象可以使标签重叠更容易,所以删除它们并不是一个功能性问题。

最后,在步骤3中,拼接记录用于在目标位置“Replay(重现)”(重新创建)新对象。例如,为了完成appendChild DOM操作,拼接引擎(Splice Engine)在结点周围创建了一个范围(从TreeNode的起始TreePos到其结束TreePos),将此范围“拼接”到旧位置之外,并创建新结点来表示新位置处的结点及其子结点。可以想象,除了算法效率低下之外,这还造成了大量内存分配混乱。

原来的DOM没有经过封装

这些只是Internet Explorer DOM复杂性的几个示例。更糟糕的是,原来的DOM没有经过封装,因此从Parser一直到Display系统的代码都对CP/TreePos具有依赖性,这需要许多年的开发时间来解决。

复杂性很容易带来错误,DOM代码库的复杂性对于软件的可靠性是一种负担。根据内部调查,从IE7到IE11,大约28%的IE可靠性错误源自核心DOM组件中的代码。而且这种复杂性也直接削弱了IE的灵活性,每个新的HTML5功能的实现成本都变得更高,因为将新理念实现到现有架构中变得更加困难。

逆向主要是通过微软提供的pdb文件,以及先前泄露的IE5.5源码完成的。

以下是IE源代码中的关于此类功能的一些注释:

1、此SpliceTree的行为是移除指定范围内的所有文本(Text),以及完全落入该范围内的所有元素(Element)。

2、语义是这样的,如果一个元素不完全在一个范围内,它的结束标签(End-Tags)将不会相对于其他元素进行移动。但是,可能需要减少该元素的结点数。发生这种情况时,结点将从右边界(Right Edge)移除。

3、范围内的不具有cling的指针(CTreeDataPos)最终会出现在开始标签(Begin-Tags)和结束标签(End-Tags)之间的空间中(可以说,它们应该放在开始标签和结束标签之间)。带有cling的指针会被删除。

1、复制指定范围内的所有文本(Text),以及完全落在该范围内的元素(Element)。

2、与左侧范围重叠的元素被复制;开始边界(Begin-Edges)隐含在范围的最开始处,其顺序与开始边界在源中出现的顺序相同。

3、与右侧范围重叠的元素被复制;结束边界(End-Edges)隐含在范围的最末端,其顺序与结束边界在源中出现的顺序相同。

1、指定范围内的所有文本(Text),以及完全落入该范围内的元素(Element),都被移动(移除并插入到新位置,而不是复制)。

2、使用与移除(Remove)相同的规则修改与右侧或左侧重叠的元素,然后使用与复制(Copy)相同的规则将其复制到新位置。

1、这种对SpliceTree的操作只能从还原代码(Undo Code)中调用。本质上,它是由先前移除(Remove)中保存的数据驱动的移动(Move)。更复杂的是,我们必须将保存的数据编织到已经存在的树中。

下面是我经过逆向得出的IE11中CSpliceTreeEngine类对象的大部分成员。

html代码中,每一对标签在IE中都会对应一个CTreeNode对象,每个CTreeNode对象的tpBegin和tpEnd成员分别用来标识对应标签的起始标签和结束标签。IE11中CTreeNode对象的第三个DWORD的低12位为标签的类型,通过IE5.5源代码中的enum ELEMENT_TAG枚举变量和pdb文件中全局g_atagdesc表,可以得出当前版本mshtml.dll渲染引擎中大部分标签对应的枚举值。

下面是我经过逆向得出的IE11中CTreeNode类对象的部分成员。

每个标签的开始标签和结束标签都有一个对应的CTreePos对象,其包含在CTreeNode对象中。通过CTreePos对象可以找到任何一个标签在DOM流中的位置,以及在DOM树中的位置。IE通过CTreePos对象的pFirstChild和pNext成员构成了实际的DOM树,通过pLeft和pRight成员构成了DOM流(双链表)。

下面枚举变量EType是CTreePos对象所对应的元素的类型。

下面枚举变量是某一个CTreePos对象在DOM树中与相连的CTreePos对象的关系,以及CTreePos对象的类型。

下面是我经过逆向得出的IE11中CTreePos类对象的完整成员。

CTreePos::GetCch()函数用于获取当前CTreePos对象对应的元素所占用的字符数量。起始标签和结束标签对应的字符数量为1,文本字符串为实际拥有的字符数,指针数据字符数的获取在CTreePos::GetContentCch()中(为0或1)。前面介绍DOM流结构时,在“以文本为中心的设计”中有提到过。

CTreeDataPos继承于CTreePos。CTreeDataPos类为CTreePos类的扩展,用于表示文本数据和指针数据。此漏洞所涉及到的关键类,就是该类。

下面是我经过逆向得出的IE11中CTreeDataPos类对象的完整成员。

IE11的CTreeDataPos拥有一个新的成员_pTextData,IE8及以前是没有的。以前文本数据是存在CTxtArray类中的,并通过CTxtPtr类对其进行访问。在IE11中并没有废除以前的方式,而是添加了一种新的用于存储文本数据的方式,即Tree::TextData类。

下面是我经过逆向得出的IE11中Tree::TextData类对象的完整成员。

下面函数是上面函数的重载。能够添加额外的字符串。

指示CTxtArray中某一元素的索引

指示CTxtArray中某一元素的内容中的字符索引

漏洞PoC所对应的DOM树

重新调试,附加IE进程,在初始断点断下后,设置以下两个断点。

以下内容是WinDbg调试输出的结果:

以下是ROOT标签的CTreeNode、起始标签和结束标签对应的CTreePos的对象内存数据:

以下是html标签的CTreeNode、起始标签和结束标签对应的CTreePos的对象内存数据:

以下是head标签的CTreeNode、起始标签和结束标签对应的CTreePos的对象内存数据:

以下是body标签的CTreeNode、起始标签和结束标签对应的CTreePos的对象内存数据:

我根据CTreePos中的pLeft和pRight成员,可以还原出此漏洞PoC所对应的DOM流结构如下图所示:

漏洞产生的根本原因分析

以下是动态调试过程中,关键部分的WinDbg输出内容:

存储实际获得的文本长度的局部变量

返回值为文本字符串的指针,Tree::TextData对象偏移8字节处

edi,源文本字符串长度,未截断文本长度

eax,源文本字符串内存地址

edx,目的内存大小,截断文本长度

    // 去除边界上带有cling的指针。这样做是为了让_ptpSourceL/R可以在非指针位置上重新定位。我们这样做是为了让元素能够在退出树通知中进行选择。

是文本数据则执行,不必须满足,CTreePos

+= TextLen; //下面会使用未截断的文本长度进行索引

造成堆越界写的根本原因是,用于标识文本字符串在DOM树/DOM流中的位置的CTreeDataPos类对象中有两个结构用于记录文本字符串的长度,一个是结构体DATAPOSTEXT的cch成员(25bit),一个是Tree::TextData对象中的cch成员(32bit)。由于它们的大小不同,当文本字符串的长度超过25bit能够表示的长度后,在向结构体DATAPOSTEXT的cch成员赋值时,会造成其存储的是截断后的长度。之后调用CSpliceTreeEngine::RemoveSplice()函数删除文本字符串在DOM树/DOM流的结构时,会使用CTreePos::GetCp()函数获得要删除的DOM树/DOM流结构所占用的字符数(包含截断的文本字符串长度),并用其申请一段内存。然后,调用Tree::TextData::GetText()函数获得Tree::TextData对象中的cch成员中存储的未截断文本字符串长度,并用其作为索引,对前面申请的内存进行赋值操作,从而造成了堆越界写漏洞。

分析此漏洞时,使用的环境是Windows 10 1809 Pro x64。在此漏洞的,可以找到当前环境该漏洞的补丁号为。在补丁详情页面,我们可以知道此补丁只适用于LTSC版本。当前环境,此补丁无法安装成功。所以我使用Windows 10 Enterprise LTSC 2019环境来进行补丁安装并进行补丁分析。我用的是2019年03月发布的Windows 10 Enterprise LTSC 2019,成功安装此漏洞补丁需要先安装2021年5月11日之后发布的服务堆栈更新(SSU),这里安装的是KB5003711,安装完之后再安装此漏洞的补丁KB5003646,就可以成功安装。

由于KB5003646补丁是2021年6月8日发布的一个累计更新,如果补丁分析时所用的两个漏洞模块文件是两个更新时间相差较大的环境提取出来的,会造成不好定位补丁位置。所以我们需要知道2021年5月发布的累计更新补丁编号。这可以通过KB5003646在的信息得到。

接下来我们将这两个补丁环境的mshtml.dll提取出来,使用IDA打开并生成IDB文件,再使用BinDiff进行补丁比较。不同的IDA版本和不同的BinDiff版本可能会出现不兼容的情况,我这里使用的是IDA Pro7.5+BinDiff6。分析完成后,得到如下结果:

0×2000000,就会触发断言失败。普通断言(assert())只有在debug版本的文件中会得到执行,而在release版本的文件中不会得到执行。这里使用的是一种由C++提供的,可以添加到release版本的文件中的断言函数Release_Assert()。断言失败后,通过SetUnhandledExceptionFilter()函数设置异常处理函数,并会抛出一个断点异常。之后会一直在异常处理流程中,并不会造成IE执行堆越界写的代码。

首先被提示,这是没有pdb文件 的

这个就很详细了,尝试向0地址写入,它其实就是调试中用到的结构体:可以直接通过MSDN查到它的定义

表示线程试图写入不可访问的地址,很明显,这里表示第二种,具体参考MSDN

符号文件对于调试程序是相当重要的,通常符号文件中包含以下内容

源文件路径以及每个符号的行号

变量,结构等的类型信息

ln 命令显示给定地址处的或者最近的符号。

ln表示list near,ln命令将尽可能地给出与特定地址相关的符号,如果没有符号能够精确地与这个地址匹配,那么调试器将通过指针算法对靠近这地址的符号进行运逄,

我们发现,第一个显示为Exact matches:表示精确匹配了一个地址,如果不是精确匹配,我们要小心,是否模块进行了优化,在优化后,一个函数,可能被拆分为多个部分

分别位于不同的地址,经过优化的映像可以通过lm查看:会有perf标识

当你在查看某部分数据,却不知道这部分数据所表示的内容时,这个命名能带来极大的帮助

伪寄存器都是通过r来操作的

对于那些偶尔使用调试器的用户是很难记得所有平台的指令指针寄存器名字(或其他的名字),为了克服这个问题,调试器的开发团队引入了各种伪寄存器,由调试器把这些伪寄存器对应到不同的硬件架构上,形式为$name,与标准的寄存器一样,如果要在表达式中使用伪寄存器,那么必须使用转义字符@

 一般可以直接在这下断点,

注意到下面显示的分别是$ip,eip,虽然它们在X86下是同一个东东.

其实也是对应当前线程,如果要看所有线程的当前函数的返回地址:

主要的值寄存器,在函数调用返回后,函数的结果将放在这个寄存器中,根据处理器架构的不同,$retreg的值分别为

当前的栈指针,根据处理器架构的不同,$csp的值分别为

当前进程的标识(PID)

当前线程的标识(TID0

$ea 最后一条被执行指令的有效地址(effective address)。如果这条指令没有一个有效地址,将显示"Bad register error"。如果这条指令有两个有效地址,则显示第一个地址。

$ra 当前堆栈的返回地址。

这个在执行命令中特别有用。例如,g @$ra 将一直执行到返回地址处(虽然,对于“步出(stepping out)”当前函数gu (Go Up)是一个更加准备有效的方法)。

$ip 指令指针寄存器:

Itanium 处理器:涉及 iip(请看表后的注解)

x64处理器:和rip相同

$previp   前一个事件发生时的指令指针。(中断进入调试器算做一个事件。)

$thread 当前线程的地址(换句话说,就是 ETHREAD 块的地址)。

$ptrsize   指针大小。在内核模式下,指目标计算机上的指针大小。

有二十个自定义伪寄存器:$t0, $t1, ..., $t19。它们是可以通过调试器读写的变量。能用来保存任意整数值。做为循环变量时非常有用

除非 r 命令使用了 ? 开关选项,否则一个伪寄存器总是具有整数类型。如果用到了 ? 选项,则伪寄存器可以获得赋给它的任意类型,例如,下面的命令把 UNICODE_STRING** 类型和 0x0012FFBC 值赋给了 $t15(译注:这里好像是 UNICODE_STRING 类型吧!另外,如果 UNICODE_STRING 解析不了,可以用 _UNICODE_STRING)。

可以看到$t0保存的是真实的test1!g_char的指针地址,而$t1只是保存了它的对象地址

dv 命令显示当前作用域的所有局部变量的名字和值。

我们可以看到,的确显示的四个局部变量,当然,出现的顺序可能并不一致,

/i 使得输出中显示变量的类型:局部、全局、参数、函数或未知:

数据结构和陌生的数据类型不会完整显示,而只显示他们的类型名。要显示整个结构或结构中的特定成员,使用dt (Display Type)命令。

显示不了local,这是因为dv命令是显示当前函数的局部变量,而当前函数是系统的函数(比如ntdll!DbgBreakPoint),那么因为没有系统模块的私有符号,所以会出错。

建议你切换到0号线程(~0s),然后使用单步或者gu命令执行到CSimple1Demo模块中的函数,然后再用dv命令

我们可以看到,第一个就是指向虚表的指针:__VFN_table

补充点小细节,用dt /f <addr>可以用它来查看任何地方的任何代码处有些什么参数和局部变量。它会关闭对值得显示并隐含/V。/f 必须是最后一个标志

也可以使用通配符,注意用双引号

对于后者,WinDBG 会自动找到MyApp!SomeFunction 对应的地址并设置断点。 但是使用bp的问题在于:

1)当代码修改之后,函数地址改变,该断点仍然保持在相同位置,不一定继续有效;

上例说明三种用法作用是一样的,都是bp Address(windbg内部会换成符号文件对应的地址,或伪寄存器的地址)

bp /1 Address表示该断点为一次性断点,有点类似于F4作用于OD,一旦激活就自动删除了:

默认情况下,断点在第一次执行断点位置的代码时被激活。这种默认情况和把Passes 设置为1是一样的。要使得断点在程序至少执行该代码一次之后才激活,可以将这个值设置为2或更大。例如,值为2时,使得断点在第二次执行到该代码时被激活。该参数创建一个在每次执行断点处的代码时被减少1的计数器。要查看Passes 计数器的初始值和当前值,使用bl (Breakpoint List)。Passes 仅当程序响应g (Go)命令并执行通过断点时才减少。单步或跟踪(tracing)通过它是不会减少的。当Passes 到达1时,可以通过清除并重设断点来重置它。

我们来试试,用bc把以前断点都删除,再设置在第三次运行LoadLibraryW时激活该处断点

我们注意到这个断点显示的是) F5运行:

我们注意到这个断点现在显示的是0001 (0003),表示前面忽略了两次,

bu 命令是针对某个符号下断点。 比如 bu MyApp!SomeFunction 。 在代码被修改之后, 该断点可以随着函数地址改变而自动更新到最新位置。  而且bu 断点会保存在WinDbg工作空间中, 下次启动 Windbg 的时候该断点会自动设置上去。另外,在模块没有被加载的时候,bp 断点会失败(因为函数地址不存在),而bu 断点则可以成功。 新版的WinDBG中 bp失败后会自动被转成bu

这个函数比较有用,比如我想对Draw开头的函数都下断点:

对于每个断点,该命令显示以下信息:

    断点ID。该ID是一个可以在其他命令中引用这个断点的十进制数字。

    如果出现字母"u",说明断点是未定的。即,该断点中的符号引用还没有和任何当前已加载的模块匹配。

    断点位置的虚拟地址或符号表达式。如果启用了源码行号加载,bl 命令显示文件和行号信息而不是地址偏移。如果该断点未定,则它的地址会被省略并出现在列表末尾。

    (仅数据断点) 数据断点的类型和大小信息会显示出来。类型可以是e (执行)、 r (读/写)、w (写)或 i (输入/输出)。类型后面是以字节为单位的大小。关于这种类型断点的更多信息,查看ba (Break on Access)。

    关联的进程和线程。如果线程是用三个星号("***")表示的,说明这不是一个指定线程的断点。

    符合断点地址的模块和函数以及偏移。如果是未定断点,这里会用括号括起来的断点地址替代。如果断点设置在合法地址,但是没有符号信息,这个域为空。

    该断点触发时要自动执行的命令。这个命令以引号括起来。

使用星号(*)来指定所有断点

ba 命令就是针对数据下断点的命令, 该断点在指定内存被访问时触发。 命令格式为

Size 是监控访问的位置的大小,以字节为单位。 值为 1、2或4,还可以是 8(64位机)。

有时我们只想让程序断在某个线程上:

前面~1 表示只有当指定的线程ID为1执行到达断点的地址上时,调试器才会停止.

 在X86下dr0-dr3记录了断点地址值,dr6是断点的状态寄存器,dr7是断点的控制寄存器。

另外,在初始断点命中时,尚不能设置硬件断点,如果设置,会得到如下错误:

初始断点后系统会重设线程上下文,因此不能设置硬件断点,建议执行到程序的入口后再设置

!gle 扩展显示当前线程的最后一个错误码。

-all 显示目标系统中每个用户模式线程的最终错误。如果在用户模式下省略该参数,调试器显示当前线程的最终错误。如果内核模式下省略该参数,调试器显示当前的寄存器上下文指定的线程的最终错误。

g(go)命令开始指定进程或线程的执行。这种执行将会在程序结束、遇到BreakAddress 或者其他造成调试器停止的事件发生时停止。

这个我们太经常用到了,

1.如果直接用g不带参数,表示无条件恢复调试目标的执行

2.g Address,相当于设了一个一次性断点,然后将调试目标执行到断点

3.gu 用于使调试目标执行完当前函数并且返回到调用者,由于这个命令知道当前的栈指针,因此它可以从递归函数调用中返回

4.运行到光标处,可以使用Ctrl+F10

5.gc 命令使用和遇到断点时一样的方式(单步、跟踪或自由执行)来从一个条件断点恢复执行。

6.gn和gN 命令继续给定线程的执行,但是不将异常标记为已处理。这样使得应用程序的异常处理器可以处理该异常

7.gh命令将给定线程的异常标识为已处理,并且允许该线程从产生异常的指令继续执行。

sx* 命令用来控制被调试的程序发生某个异常或特定事件时,调试器要采取的动作

sx 命令显示当前进程的异常列表和所有非异常的事件列表,并且显示调试器遇到每个异常和事件时的行为。

sxr 命令将所有异常和事件过滤器的状态重设为默认值。命令被清除、中断和继续选项被重设为默认值,等等。

sx这个命令的输出信息可以分为三个部分:

第一部分是事件处理与相应处理模式的交互,第二部分是标准的异常交互和处理行为,最后一部分是用户自定义的异常交互和处理行为

以下面为例,我们先输入sxr再输入sx看下默认的处理行为都是怎么样的:

(Disabled)        发生该类异常时,调试器不会在第一次处理机会时中断(虽然会显示信息)。如果其他错误处理器没有处理掉该异常,执行会停止下来并中断到调试器。这种处理类型称为第二次处理机会。

(Notify)   当该异常发生时,目标程序不中断到调试器中。但是,会通过一条消息提示发生了异常。

果然断下来了,断在加载LPK.dll模块时,那么如果我们只想让它在加载SkinHgy.dll时断下来怎么办呢?需要介绍下ld了

对于每个异常Windows系统会最多给予两轮处理机会,对于每一轮机会Windows都会试图先分发给调试器,然后再寻找异常处理器(VEH、SEH等)。这样看来,对于每个异常,调试器最多可能收到两次处理机会,每次处理后调试器都应该向系统返回一个结果,说明它是否处理了这个异常。

对于第一轮异常处理机会,调试器通常是返回没有处理异常,然后让系统继续分发,交给程序中的异常处理器来处理。对于第二轮机会,如果调试器不处理,那么系统便会采取终极措施:如果异常发生在应用程序中,那么系统会启动应用程序错误报告过程并终止应用程序;如果发生在内核代码中,那么便启用蓝屏机制停止整个系统。所以对于第二轮处理机会,调试器通常是返回已经处理,让系统恢复程序执行,这通常会再次导致异常,又重新分发异常,如此循环。值得说明的是,对于断点异常和调试异常,调试器是在第一轮就返回已经处理的。

ld(load symbols)命令加载指定模块的符号并刷新所有模块信息。

可以包含通配符,这是个好消息

提示已加载,看来还是.reload好用啊。

那么接着1,我们来设置只在ld skinhgy时断下来:

我们已经restart了,但ld的状态还没有变,所以WinDBG会自动把一些东西记录到工作空间(Workspace)里,因为工作空间是隐式管理的,所以容易让初用WinDBG的人摸不着头脑,像MJ说的那样操作一下,并且保存到工作空间中(结束调试时,WinDBG询问要不要保存工作空间时选YES),或者干脆删除工作空间就可以了,当然也可以写成

这样所有的Skin*模块都会触发断点

指定一个当异常或事件发生时要执行的命令。该命令在异常的第一次处理机会时执行(也就是第一轮异常),不管该异常是否会中断到调试器。Cmd1 字符串必须包含在引号中。该字符串可以包含多条用分号分隔的命令。-c和括起来的命令字符串之间的空格是可选的。

指定当异常或事件发生并且没有在第一次处理机会被处理时执行的命令。该命令在异常的第二次处理机会时执行,(也就是第二轮异常),不管它是否会中断到调试器。Cmd2 字符串必须包含在引号中。该字符串可以包含多条用分号分隔的命令。-c2 和括起来的命令字符串之间的空格是可选的。

改变指定事件的处理状态而不是中断状态。如果Event 是cc、hc、bpec或ssec,-h 选项不是一定需要。

 比如我要在第一次加载SkinHgy.dll时断下来并打印MSG:

这里介绍种GUI使用的方法:

我们看到我们先前加载的SkinHgy.dll都在,事件(-c和-c2)对应Commands按钮来修改,中断状态可以通过"Execution"来修改,还可以通过Add和Remove来增加或删除异常码.

sxe Handled 执行返回时,事件被标识为已处理。

windbg帮助上写得很清楚了.

这两个很有用,今天在调程序时发现内存访问一直被断,用sxi av就行了,也就是忽视av这种状态

    • 5.1、以文本为中心的设计
    • 5.2、增加复杂性层次结构
    • 5.3、原来的DOM没有经过封装
    • 6.3、漏洞产生的根本原因分析

Explorer进程并导航到恶意网页来实现攻击。此攻击中使用的漏洞被分配为CVE-,并于2021年6月由Microsoft修复。

0day漏洞在野利用的攻击事件,例如:CVE-、CVE-,所以研究Internet Explorer漏洞,还是存在一定的意义。

4发布,之后不断的加入新的技术并随着新版本的Internet Explorer发布。在Trident7.0(Internet Explorer 11使用)中,微软对Trident排版引擎做了重大的变动,除了加入新的技术之外,并增加了对网页标准的支持。EdgeHTML是由微软开发并用于Microsoft Edge的专有排版引擎。该排版引擎是Trident的一个分支,但EdgeHTML移除所有旧版Internet Explorer遗留下来的代码,并重写主要的代码以和其他现代浏览器的设计精神互通有无。

在Google威胁分析团队发布了上面所说的那篇文章之后,又在Google Project Zero的博客上公布了这些漏洞的细节。本文章就是对Internet Explorer中的CVE-漏洞的分析过程的一个记录。我之前分析过老版本的Internet Explorer的漏洞,这是第一次比较正式的分析新版本Internet Explorer的漏洞,如有错误和不足之处,还望见谅。

innerHTML属性对内部html元素设置内容(包含文本字符串)时触发的。通过innerHTML属性修改标签之间的内容时,会造成IE生成的DOM树/DOM流的结构发生改变,IE会调用CSpliceTreeEngine类的相关函数对IE的DOM树/DOM流的结构进行调整。当调用CSpliceTreeEngine::RemoveSplice()去删除一些DOM树/DOM流结构时,恰好这些结构中包含文本字符串时,就有可能会造成堆越界写。

设置”。注意:设置界面拥有两个选项卡,“系统设置”和“程序设置”。我们先看“系统设置”,与ASLR有关系的是“强制映像随机化(强制性ASLR)”、“随机化内存分配(自下而上ASLR)”、“高熵ASLR”,我们都将其设为关闭状态。先关闭“高熵ASLR”,然后再关闭其他两项。

“强制映像随机化(强制性ASLR)”,不管编译时是否使用“/DYNAMICBASE”编译选项进行编译,开启了“强制性ASLR”后,会对所有软件模块的加载基址进行随机化,包括未使用“/DYNAMICBASE”编译选项编译的软件模块。关于编译时是否使用了“/DYNAMICBASE”编译选项进行编译,可以使用“Detect It

“随机化内存分配(自下而上ASLR)”,开启了该选项后,当我们使用malloc()或HeapAlloc()在堆上申请内存时,得到的堆块地址将在一定程度上进行随机化。

“高熵ASLR”,这个选项需要配合“随机化内存分配(自下而上ASLR)”选项使用,开启了该选项后,会在“随机化内存分配(自下而上ASLR)”基础上,更大程度的随机化堆块的分配地址。

接下来,我们来看“程序设置”。由于Windows10可以对单独的应用程序设置缓解措施的开启或关闭,并且替换“系统设置”中的设置,造成关闭了“系统设置”中所有与ASLR相关的缓解措施后,dll模块的加载基址还是在变化。切换到“程序设置”选项卡后,找到iexplore.exe,点击编辑,将所有与ASLR有关的设置的“替代系统设置”的勾去掉。

设置完成后,重启一下操作系统。

由于原始PoC过于精简,无法观察到执行效果,对我理解程序的执行流程造成了一定的障碍。所以我尝试了以下几种经过修改的PoC,用于观察执行效果。

我们可以得出以下结论:PoC通过HTML DOM方法document.createElement(),创建了一个“html”结点(同时创建“head”和“body”结点),并把新创建的“html”结点添加到原有的“body”结点中。然后,创建了一个Array数组并进行了初始化。最后将该数组转化为字符串,通过HTML DOM的innerHTML属性,添加到新创建的“html”结点中的“body”结点中。

原始PoC中,并未将创建的Array数组初始化,我们通过Chrome的开发者工具查看未初始化的Array数组转化为字符串后,得到的是什么。这有助于我们后面在调试PoC时,观察字符串所对应的内存数据。

可以看到,初始化后的Array数组转化成字符串后,每个元素是使用“,”分隔的。未初始化的Array数组转化成字符串后,只有一连串的“,”。其个数为Array数组元素个数减1。

好了,我们现在开始通过调试复现此漏洞。这里使用的是原始的PoC。首先打开Internet Explorer,拖入PoC,会弹出一个提示框“Internet Explorer已限制此网页运行脚本或ActiveX控件”,表示现在html中的javascript代码还没有得到执行。这时,我们打开WinDbg,附加到iexplore.exe上,输入g命令运行,然后在Internet Explorer界面点击提示框中的“允许阻止的内容”(可能需要刷新一下)。然后Internet Explorer会执行异常,WinDbg会捕获到异常并中断下来。以下是Crash的现场情况:

通过观察WinDbg的输出信息,可以发现PoC造成了异常代码为0xc0000005的内存访问违例异常。0x63a46809处的异常代码向一个内存访问权限为PAGE_NOACCESS(不可访问)的地址写入一个值,从而造成Crash。通过k命令打印栈回溯,可以知道发生异常的代码位于MSHTML!CSpliceTreeEngine::RemoveSplice()函数中。

当如今的Web开发者想到DOM树时,他们通常会想到这样的一个树:

这样的树看起来非常的简单,然而,现实是Internet Explorer的DOM树的实现是相当复杂的。

简单地说,Internet Explorer的DOM树是为了20世纪90年代的网页设计的。当时设计原始的数据结构时,网页主要是作为一个文档查看器(顶多包含几个动态的GIF图片和其他的静态图片)。因此,算法和数据结构更类似于为Microsoft Word等文档查看器提供支持的算法和数据结构。回想一下网页发展的早期,JavaScript还没有出现,并不能通过编写脚本操作网页内容,因此我们所了解的DOM树并不存在。文本是组成网页的主要内容,DOM树的内部结构是围绕快速、高效的文本存储和操作而设计的。内容编辑(WYSIWYG:What You See Is What You Get)和以编辑光标为中心用于字符插入和有限的格式化的操作范式是当时网页开发的特点。

由于其以文本为中心的设计,DOM的原始结构是为了文本后备存储,这是一个复杂的文本数组系统,可以在最少或没有内存分配的情况下有效地拆分和连接文本。后备存储将文本(Text)和标签(Tag)表示为线性结构,可通过全局索引或字符位置(CP:Character Position)进行寻址。在给定的CP处插入文本非常高效,复制/粘贴一系列的文本由高效的“splice(拼接)”操作集中处理。下图直观地说明了如何将包含“hello world”的简单标记加载到文本后备存储中,以及如何为每个字符和标签分配CP。

文本后备存储为非文本实体(例如:标签和插入点)提供特殊的占位符。

为了存储非文本数据(例如:格式化和分组信息),另一组对象与后备存储分开进行维护:表示树位置的双向链表(TreePos对象)。TreePos对象在语义上等同于HTML源代码标记中的标签——每个逻辑元素都由一个开始和结束的TreePos表示。这种线性结构使得在深度优先前序遍历(几乎每个DOM搜索API和CSS/Layout算法都需要)DOM树时,可以很快的遍历整个DOM树。后来,微软扩展了TreePos对象以包括另外两种“位置”:TreeDataPos(用于指示文本的占位符)和PointerPos(用于指示诸如脱字符(“^大写字符”:用于表示不可打印的控制字符)、范围边界点之类的东西,并最终用于新特性,如:生成的内容结点)。

每个TreePos对象还包括一个CP对象,它充当标签的全局序数索引(对于遗留的document.all API之类的东西很有用)。从TreePos进入文本后备存储时需要用到CP,它可以使结点顺序的比较变得容易,甚至可以通过减去CP索引来得到文本的长度。

为了将它们联系在一起,一个TreeNode将成对的TreePos绑定在一起,并建立了JavaScript DOM所期望的“树”层次结构,如下图所示:

CP的设计造成了原有的DOM非常复杂。为了使整个系统正常工作,CP必须是最新的。因此,每次DOM操作(例如:输入文本、复制/粘贴、DOM API操作,甚至点击页面——这会在DOM中设置插入点)后都会更新CP。最初,DOM操作主要由HTML解析器或用户操作驱动,所以CP始终保持最新的模型是完全合理的。但是随着JavaScript和DHTML的兴起,这些操作变得越来越普遍和频繁。

为了保持原来的更新速度,DOM添加了新的结构以提高更新的效率,并且伸展树(SplayTree)也随之产生,伸展树是在TreePos对象上添加了一系列重叠的树连接。起初,增加的复杂性提高了DOM的性能,可以用O(log n)速度实现全局CP更新。然而,伸展树实际上仅针对重复的局部搜索进行了优化(例如:针对以DOM树中某个位置为中心的更改),并没有证明对JavaScript及其更多的随机访问模式具有同样的效果。

另一个设计现象是,前面提到的处理复制/粘贴的“Splice(拼接)”操作被扩展到处理所有的树突变。核心“Splice Engine(拼接引擎)”分三步工作,如下图所示:

在步骤1中,引擎将通过从操作开始到结束遍历树的位置(TreePos)来“记录”拼接信息。然后创建一个拼接记录,其中包含此操作的命令指令(在浏览器的还原栈(Undo Stack)中重用的结构)。

在步骤2中,从树中删除与操作关联的所有结点(即TreeNode和TreePos对象)。请注意,在IE DOM树中,TreeNode/TreePos对象与脚本引用的Element对象不同,TreeNode/TreePos对象可以使标签重叠更容易,所以删除它们并不是一个功能性问题。

最后,在步骤3中,拼接记录用于在目标位置“Replay(重现)”(重新创建)新对象。例如,为了完成appendChild DOM操作,拼接引擎(Splice Engine)在结点周围创建了一个范围(从TreeNode的起始TreePos到其结束TreePos),将此范围“拼接”到旧位置之外,并创建新结点来表示新位置处的结点及其子结点。可以想象,除了算法效率低下之外,这还造成了大量内存分配混乱。

原来的DOM没有经过封装

这些只是Internet Explorer DOM复杂性的几个示例。更糟糕的是,原来的DOM没有经过封装,因此从Parser一直到Display系统的代码都对CP/TreePos具有依赖性,这需要许多年的开发时间来解决。

复杂性很容易带来错误,DOM代码库的复杂性对于软件的可靠性是一种负担。根据内部调查,从IE7到IE11,大约28%的IE可靠性错误源自核心DOM组件中的代码。而且这种复杂性也直接削弱了IE的灵活性,每个新的HTML5功能的实现成本都变得更高,因为将新理念实现到现有架构中变得更加困难。

逆向主要是通过微软提供的pdb文件,以及先前泄露的IE5.5源码完成的。

以下是IE源代码中的关于此类功能的一些注释:

1、此SpliceTree的行为是移除指定范围内的所有文本(Text),以及完全落入该范围内的所有元素(Element)。

2、语义是这样的,如果一个元素不完全在一个范围内,它的结束标签(End-Tags)将不会相对于其他元素进行移动。但是,可能需要减少该元素的结点数。发生这种情况时,结点将从右边界(Right Edge)移除。

3、范围内的不具有cling的指针(CTreeDataPos)最终会出现在开始标签(Begin-Tags)和结束标签(End-Tags)之间的空间中(可以说,它们应该放在开始标签和结束标签之间)。带有cling的指针会被删除。

1、复制指定范围内的所有文本(Text),以及完全落在该范围内的元素(Element)。

2、与左侧范围重叠的元素被复制;开始边界(Begin-Edges)隐含在范围的最开始处,其顺序与开始边界在源中出现的顺序相同。

3、与右侧范围重叠的元素被复制;结束边界(End-Edges)隐含在范围的最末端,其顺序与结束边界在源中出现的顺序相同。

1、指定范围内的所有文本(Text),以及完全落入该范围内的元素(Element),都被移动(移除并插入到新位置,而不是复制)。

2、使用与移除(Remove)相同的规则修改与右侧或左侧重叠的元素,然后使用与复制(Copy)相同的规则将其复制到新位置。

1、这种对SpliceTree的操作只能从还原代码(Undo Code)中调用。本质上,它是由先前移除(Remove)中保存的数据驱动的移动(Move)。更复杂的是,我们必须将保存的数据编织到已经存在的树中。

下面是我经过逆向得出的IE11中CSpliceTreeEngine类对象的大部分成员。

html代码中,每一对标签在IE中都会对应一个CTreeNode对象,每个CTreeNode对象的tpBegin和tpEnd成员分别用来标识对应标签的起始标签和结束标签。IE11中CTreeNode对象的第三个DWORD的低12位为标签的类型,通过IE5.5源代码中的enum ELEMENT_TAG枚举变量和pdb文件中全局g_atagdesc表,可以得出当前版本mshtml.dll渲染引擎中大部分标签对应的枚举值。

下面是我经过逆向得出的IE11中CTreeNode类对象的部分成员。

每个标签的开始标签和结束标签都有一个对应的CTreePos对象,其包含在CTreeNode对象中。通过CTreePos对象可以找到任何一个标签在DOM流中的位置,以及在DOM树中的位置。IE通过CTreePos对象的pFirstChild和pNext成员构成了实际的DOM树,通过pLeft和pRight成员构成了DOM流(双链表)。

下面枚举变量EType是CTreePos对象所对应的元素的类型。

下面枚举变量是某一个CTreePos对象在DOM树中与相连的CTreePos对象的关系,以及CTreePos对象的类型。

下面是我经过逆向得出的IE11中CTreePos类对象的完整成员。

CTreePos::GetCch()函数用于获取当前CTreePos对象对应的元素所占用的字符数量。起始标签和结束标签对应的字符数量为1,文本字符串为实际拥有的字符数,指针数据字符数的获取在CTreePos::GetContentCch()中(为0或1)。前面介绍DOM流结构时,在“以文本为中心的设计”中有提到过。

CTreeDataPos继承于CTreePos。CTreeDataPos类为CTreePos类的扩展,用于表示文本数据和指针数据。此漏洞所涉及到的关键类,就是该类。

下面是我经过逆向得出的IE11中CTreeDataPos类对象的完整成员。

IE11的CTreeDataPos拥有一个新的成员_pTextData,IE8及以前是没有的。以前文本数据是存在CTxtArray类中的,并通过CTxtPtr类对其进行访问。在IE11中并没有废除以前的方式,而是添加了一种新的用于存储文本数据的方式,即Tree::TextData类。

下面是我经过逆向得出的IE11中Tree::TextData类对象的完整成员。

下面函数是上面函数的重载。能够添加额外的字符串。

指示CTxtArray中某一元素的索引

指示CTxtArray中某一元素的内容中的字符索引

漏洞PoC所对应的DOM树

重新调试,附加IE进程,在初始断点断下后,设置以下两个断点。

以下内容是WinDbg调试输出的结果:

以下是ROOT标签的CTreeNode、起始标签和结束标签对应的CTreePos的对象内存数据:

以下是html标签的CTreeNode、起始标签和结束标签对应的CTreePos的对象内存数据:

以下是head标签的CTreeNode、起始标签和结束标签对应的CTreePos的对象内存数据:

以下是body标签的CTreeNode、起始标签和结束标签对应的CTreePos的对象内存数据:

我根据CTreePos中的pLeft和pRight成员,可以还原出此漏洞PoC所对应的DOM流结构如下图所示:

漏洞产生的根本原因分析

以下是动态调试过程中,关键部分的WinDbg输出内容:

存储实际获得的文本长度的局部变量

返回值为文本字符串的指针,Tree::TextData对象偏移8字节处

edi,源文本字符串长度,未截断文本长度

eax,源文本字符串内存地址

edx,目的内存大小,截断文本长度

    // 去除边界上带有cling的指针。这样做是为了让_ptpSourceL/R可以在非指针位置上重新定位。我们这样做是为了让元素能够在退出树通知中进行选择。

是文本数据则执行,不必须满足,CTreePos

+= TextLen; //下面会使用未截断的文本长度进行索引

造成堆越界写的根本原因是,用于标识文本字符串在DOM树/DOM流中的位置的CTreeDataPos类对象中有两个结构用于记录文本字符串的长度,一个是结构体DATAPOSTEXT的cch成员(25bit),一个是Tree::TextData对象中的cch成员(32bit)。由于它们的大小不同,当文本字符串的长度超过25bit能够表示的长度后,在向结构体DATAPOSTEXT的cch成员赋值时,会造成其存储的是截断后的长度。之后调用CSpliceTreeEngine::RemoveSplice()函数删除文本字符串在DOM树/DOM流的结构时,会使用CTreePos::GetCp()函数获得要删除的DOM树/DOM流结构所占用的字符数(包含截断的文本字符串长度),并用其申请一段内存。然后,调用Tree::TextData::GetText()函数获得Tree::TextData对象中的cch成员中存储的未截断文本字符串长度,并用其作为索引,对前面申请的内存进行赋值操作,从而造成了堆越界写漏洞。

分析此漏洞时,使用的环境是Windows 10 1809 Pro x64。在此漏洞的,可以找到当前环境该漏洞的补丁号为。在补丁详情页面,我们可以知道此补丁只适用于LTSC版本。当前环境,此补丁无法安装成功。所以我使用Windows 10 Enterprise LTSC 2019环境来进行补丁安装并进行补丁分析。我用的是2019年03月发布的Windows 10 Enterprise LTSC 2019,成功安装此漏洞补丁需要先安装2021年5月11日之后发布的服务堆栈更新(SSU),这里安装的是KB5003711,安装完之后再安装此漏洞的补丁KB5003646,就可以成功安装。

由于KB5003646补丁是2021年6月8日发布的一个累计更新,如果补丁分析时所用的两个漏洞模块文件是两个更新时间相差较大的环境提取出来的,会造成不好定位补丁位置。所以我们需要知道2021年5月发布的累计更新补丁编号。这可以通过KB5003646在的信息得到。

接下来我们将这两个补丁环境的mshtml.dll提取出来,使用IDA打开并生成IDB文件,再使用BinDiff进行补丁比较。不同的IDA版本和不同的BinDiff版本可能会出现不兼容的情况,我这里使用的是IDA Pro7.5+BinDiff6。分析完成后,得到如下结果:

0×2000000,就会触发断言失败。普通断言(assert())只有在debug版本的文件中会得到执行,而在release版本的文件中不会得到执行。这里使用的是一种由C++提供的,可以添加到release版本的文件中的断言函数Release_Assert()。断言失败后,通过SetUnhandledExceptionFilter()函数设置异常处理函数,并会抛出一个断点异常。之后会一直在异常处理流程中,并不会造成IE执行堆越界写的代码。

我要回帖

更多关于 指令引用内存不能为written 的文章

 

随机推荐