[翻译]Exploiting CVE-2015-0057 ——Part 1

前言

​ NCC的关于CVE-2015-0057漏洞分析的文章写的十分详细,但是是英文的,这里再一边翻译一边阅读一下吧。这里是第一部分的内容,大致说了如何利用漏洞触发点来构造一个新的堆溢出漏洞利用,其中还包括一些前置知识。

漏洞点

​ 下面的代码显示了一个相当微妙的bug,下面的代码为win32k!xxxEnableWndSBArrows的汇编:

没打补丁前:

text:FFFFF97FFF1B157D mov r8d, r13d
.text:FFFFF97FFF1B1580 mov rdx, r14
.text:FFFFF97FFF1B1583 call xxxDrawScrollBar 
; xxxDrawScrollBar could've issued usermode callback
.text:FFFFF97FFF1B1588 jmp short 
loc_FFFFF97FFF1B1519
[...]
.text:FFFFF97FFF1B1519 mov eax, [rbx] 
; Dereference tagSBINFO ptr without sanity check
.text:FFFFF97FFF1B151B mov ebp, 0FFFFFFFBh
.text:FFFFF97FFF1B1520 xor eax, esi

在上述代码中,xxxDrawScrollBar函数可以在某种情况下进行一个用户模式回调,使tagSBINFO指针被free,当回调完成回到这段代码时,仍会使用被free过的tagSBINFO,这将会导致一个UAF。

打补丁后:

.text:FFFFF97FFF1D69C3 xor r8d, r8d
.text:FFFFF97FFF1D69C6 mov rdx, rbp
.text:FFFFF97FFF1D69C9 call xxxDrawScrollBar 
; Could've issued usermode callback
.text:FFFFF97FFF1D69CE cmp rbx, [rdi+0B0h] 
; Is tagSBINFO ptr still correct?
.text:FFFFF97FFF1D69D5 jz short 
loc_FFFFF97FFF1D69E4 ; If so, continue
.text:FFFFF97FFF1D69D7
.text:FFFFF97FFF1D69D7 loc_FFFFF97FFF1D69D7: 
.text:FFFFF97FFF1D69D7 mov rcx, rbp 
.text:FFFFF97FFF1D69DA call _ReleaseDC
.text:FFFFF97FFF1D69DF jmp loc_FFFFF97FFF1D6958 
; Jump to exit function block
.text:FFFFF97FFF1D69E4 ; ---------------------------------------------
------------------------------
.text:FFFFF97FFF1D69E4
.text:FFFFF97FFF1D69E4 loc_FFFFF97FFF1D69E4: 
.text:FFFFF97FFF1D69E4 mov eax, [rbx] 
; Safely deref the unchanged tagSBINFO ptr
.text:FFFFF97FFF1D69E6 xor eax, r14d

在打过补丁的版本里,当用户模式回调完成后会进行一个check,修补了漏洞。

基础——阶段1 内存损坏

​ 在开发过程中,我们经历了一系列的破坏过程。因此,我们可以把触发bug看作是第一阶段的破坏。

​ 技术问题的根源是一个桌面堆的use-after-free(稍后将详细介绍这堆)。一开始我很困惑,因为我不熟悉win32k使用的用户模式回调函数。所以我认为这个问题可能是由于锁问题导致的竞态条件,而锁问题又会导致use-after-free。但最终,实际支持锁定的结构的锁定是正确的,并且前面的行为方式是可以预料到的。简而言之,真正的问题是:

  1. win32k!xxxEnableWndSBArrows函数拥有一个指向tagSBINFO结构体的桌面堆指针,该结构体用于描述滚动条,它从关联窗口的tagWND结构体中读取滚动条。
  2. win32k !xxxEnableWndSBArrows函数调用一个可能导致用户模式回调(可以被hook)的函数。
  3. 一旦代码在userland中执行,就可以通过调用其他win32k.sys系统调用来更改桌面堆上的结构,包括从桌面堆中释放tagSBINFO结构。
  4. 在返回内核模式时,win32k!xxxEnableWndSBArrows不重新引用并验证最初从tagWND结构中获得的原始tagSBINFO指针(验证将发现它已被释放),而是继续使用现在已经过时的指针。

就是这样。忽略用户模式回调目前的工作方式,这一部分非常简单。

了解什么是我们可以控制的

​ 但这到底让我们修改了什么?正如Udi的博客文章所提到的,您可以从代码认为的tagSBINFO结构的WSBFlags成员有效地设置或取消设置两个位。这不是一个真正的理想use-after-free场景,但是那篇论文给出一个提示如何利用这一点,我将在下一节中进行描述。但首先,让我们更好地理解如何控制位操作。

首先让我们看看tagSBINFO结构(32/64位一致):

kd> dt -b !tagSBINFO
win32k!tagSBINFO
 +0x000 WSBflags : Int4B
 +0x004 Horz : tagSBDATA
 	+0x000 posMin : Int4B
 	+0x004 posMax : Int4B
 	+0x008 page : Int4B
	+0x00c pos : Int4B
 +0x014 Vert : tagSBDATA
 	+0x000 posMin : Int4B
	+0x004 posMax : Int4B
 	+0x008 page : Int4B
 	+0x00c pos : Int4B

​ UAF bug存在于win32k!xxxEnableWndSBArrows()中,它负责启用和禁用与滚动条控件关联的水平和垂直滚动条箭头中的一个或两个。滚动条控件实际上是一个用于操作滚动条的特殊窗口。可以在CreateWindow()中使用内置的“SCROLLBAR”系统类创建它。win32k!xxxEnableWndSBArrows()的函数原型是:

BOOL xxxEnableWndSBArrows(PWND wnd, UINT WSBflags, UINT wArrows);

​ WSBFlags参数对应于WinUser.h中的滚动条用户空间常量,指定实际操作哪个滚动条:

#define SB_HORZ 0	
#define SB_VERT 1	
#define SB_CTL 2	
#define SB_BOTH 3	

​ wArrows参数实际上指定了箭头的状态,箭头是启用还是禁用的。位的设置意味着箭头被禁用,位的未设置意味着箭头被启用。

​ 下面的代码,来自win32k!xxxEnableWndSBArrows(),显示了设置或取消设置水平箭头位,取决于SB_HORZ或SB_BOTH标志被设置:
在这里插入图片描述

bug实际上在设置水平和垂直滚动条标志之间显示。更新水平滚动条之后,只要与滚动条关联的窗口当前在桌面上是可见的,win32k!xxxEnableWndSBArrows()函数就会调用win32k!xxxDrawScrollBar(),这样也就可能会将其引入用户模式回调。

​ 在讨论usermode回调之前,让我们首先继续讨论在调用win32k!xxxDrawScrollBar()之后会发生什么。如果我们选择禁用垂直滚动条,并假设我们触发了use-after-free,这将在现在分配给tagSBINFO块的任何地方写入两个位。假设初始值是0x2,现在是0xe。如下图所示。

在这里插入图片描述

这种位翻转足以最终执行代码。我没有研究通过还原位来实现利用的方法,但它可能是可行的。
关于上面的一件重要的事情是,为了实际操作水平栏和垂直栏,必须以一种表明两者都有的方式创建滚动条。这涉及在调用CreateWindow()时设置WS_HSCROLL和WS_VSCROLL标志。例子如下:

g_hSBCtl = CreateWindowEx(
0, // No extended style
"SCROLLBAR", // class
NULL, // name
SBS_HORZ | WS_HSCROLL | WS_VSCROLL, // need both SB 
types
10, // x 
10, // y 
100, // width
100, // height
g_hSpray[UAFWND], // a non control 
parent window is required
(HMENU)NULL,
NULL, // window owner
NULL // extra params
);

同时还需要确保它是可见的(它应该是默认的,但是以防万一)

result = ShowWindow(g_hSBCtl, SW_SHOW);

默认情况下,滚动条是启用的,一旦我们准备好点击上面显示的脆弱代码,我们可以禁用它们,最终设置我们想要破坏的位:

result = EnableScrollBar(g_hSBCtl, SB_CTL | SB_BOTH, 
ESB_DISABLE_BOTH);

触发这个Bug

​ 所以上面我解释了什么是缺陷,以及如何触发一些相关的代码,但是我们仍然错过了最重要的一步:拦截由win32kxxxDrawScrollBar()触发的用户模式回调,只有这样我们才能在win32k!xxxEnableWndSBArrows()继续运行之前更改堆的内容。我们需要实际触发这个bug,如果你对win32k.sys一无所知的话,会比较困难。

​ 最初的文章包含一个良好的调用堆栈图,显示在win32k!xxxDrawScrollBar()调用所触发的功能深处,将调用ClientLoadLibrary()函数,并通过KeUserModeCallback()函数进行分配。我们需要弄清楚KeUserModeCallback()究竟调用了什么,这样我们就可以尝试将它挂接到我们的进程中。我找到了一些关于usermode回调工作原理的好文章:

  • https://media.blackhat.com/bh-us-11/Mandt/BH_US_11_Mandt_win32k_WP.pdf (Tarjei’s white paper)
  • http://azimuthsecurity.com/resources/recon2012_mandt.pptx (Tarjei’s slides with extra info)
  • http://www.nynaeve.net/?p=204
  • http://www.cprogramdevelop.com/3825874/
  • http://www.zer0mem.sk/?p=410
  • https://www.reactos.org/wiki/Techwiki:RegisterUserApiHook
  • http://pasotech.altervista.org/windows_internals/Win32KSYS.pdf
  • http://j00ru.vexillium.org/?p=614
  • http://uninformed.org/index.cgi?v=10&a=2#SECTION00042000000000000000

​ 基本上,每个进程维护一个用户模式回调函数指针表,PEB->KernelCallBackTable成员指向这个表。当内核想要调用一个用户模式函数时,它会将一个函数索引传递给KeUserModeCallBack()。在上面的例子中,索引对应于用户模式中的_ClientLoadLibrary()函数。

KeUserModeCallBack()将最终调用用户态中的KiUserModeCallbackDispatch()函数,该函数反过来在PEB->KernelCallBackTable中查找索引并执行它。

​ 为了hook一个给定的条目,您可以查找PEB->KernelCallBackTable表,并直接修改_ClientLoadLibrary()函数对应的索引。应该注意的是,这些索引对于每个OS版本都是不同的,但是在不同的体系结构中是一致的。
​ 如果我们想要研究PEB->KernelCallBackTable表以查看其中的内容并计算索引,我们使用WinDbg查找表的地址。请注意,我在32位和64位之间交替使用,这两个例子应该不会有太大的区别:

kd> dt !_PEB @$peb
ntdll!_PEB
 +0x000 InheritedAddressSpace : 0 ''
 +0x001 ReadImageFileExecOptions : 0 ''
 +0x002 BeingDebugged : 0 ''
 +0x003 BitField : 0x8 ''
 +0x003 ImageUsesLargePages : 0y0
[...]
 +0x02c KernelCallbackTable : 0x76daf620 Void
kd> dds 0x76daf620
76daf620 76d96443 user32!__fnCOPYDATA
76daf624 76ddf0e4 user32!__fnCOPYGLOBALDATA
76daf628 76da736b user32!__fnDWORD
76daf62c 76d9d603 user32!__fnNCDESTROY
76daf630 76dc50f9 user32!__fnDWORDOPTINLPMSG
76daf634 76ddf1be user32!__fnINOUTDRAG
76daf638 76dc6cd0 user32!__fnGETTEXTLENGTHS
76daf63c 76ddf412 user32!__fnINCNTOUTSTRING
76daf640 76d9ce49 user32!__fnINCNTOUTSTRINGNULL
[...]
76daf724 76da3962 user32!__ClientLoadLibrary
kd> ?? (0x76daf724-0x76daf620)/4
int 0n65

​ 在上面的例子中,我们知道__ClientLoadLibrary函数是索引65,所以这是我们要hook的条目。在hook之后,我注意到与win32k相关的代码经常调用_ClientLoadLibrary函数!我需要做的第一件事是在真正触发我们的目标调用函数之前向我的钩子表明,这样我们就可以确切地知道我们hook了哪个调用。因此,hook代码检查一个全局标志,并只尝试做一些有趣的事情,如果它被设置。然后还有两个障碍:

​ 1)如果我让原始的_ClientLoadLibrary函数正常运行,并且我实际触发win32k中的漏洞点调用时。我发现它最终并没有真正进入用户领域。我并没有深入研究这个问题,但是我认为这可能是因为它为这个调用加载的任何库都已经加载了,所以它决定不需要再次调用这个函数。为了解决这个问题,我让我的钩子操纵了对_ClientLoadLibrary()的每次调用并且不返回任何结果,这迫使它不断地重试加载库。通过逆向user32.dll中的__ClientLoadLibrary(),我们计算出在结构参数中传递回空值就足够了。

​ 2)对EnableScrollBar()的调用最后会触发__ClientLoadLibrary调用这个是由win32k!xxxDrawScrollBar()触发的,我们想要滥用它,所以我必须在我感兴趣的调用之前计算出调用的数量,并使用计数器,这样我就知道在钩子的正确调用上触发bug。幸运的是,这个计数在架构和操作系统版本中都是稳定的。
所以这个hook看起来是这样的:

void
ClientLoadLibraryHook(void * p)
{
	CHAR Buf[PGSZ];
	memset(Buf, 0, sizeof(Buf));
	if (g_PwnFlag) {
		dprintf("[+] __ClientLoadLibrary hook called\n");
		if (++g_HookCount == 2) {
        	g_PwnFlag = 0; // Only fire once..
			ReplaceScrollBarChunk(NULL);
		} 
    }
	fpClientLoadLibrary(&Buf); // call original
}

​ 一旦我们确定我们已经被明确地从win32k!xxxDrawScrollBar()调用调用,我们可以尝试触发错误。现在,因为我们只担心触发,我们可以调用DestroyWindow(g_hSBCtl)。这将足以从窗口释放tagSBINFO结构,窗口结构本身还不会被释放,因为仍然有一个引用计数,因为原始调用仍然在使用它,但是tagSBINFO没有这样的引用计数机制,所以在进程中被释放。

​ 现在我们已经触发了这个bug。即使我们不重新分配持有tagSBINFO的now-free块,我们也会将这两个禁用位写入释放的堆位置中。下一步是将释放的数据块替换为我们想要放在那里的数据块,这样我们可以做一些更有趣的事情,而不仅仅是翻转几个位。为了做到这一点,我们需要一点桌面堆的背景知识

桌面堆

​ win32ksys使用桌面堆来存储与给定桌面相关联的GUI对象。其中包括窗口对象及其相关结构,如属性列表、窗口文本和滚动条等。Tarjei的文章提到了这一点,但最重要的是,它实际上只是使用RtlAllocateHeap()和RtlHeapFree()操作的用户态后端分配器的一个简化版本。堆由_HEAP结构跟踪,它没有前端分配器,所以没有低碎片堆,没有lookaside列表。

​ 每次创建桌面时,都会创建一个堆来为其提供服务。这意味着我们实际上可以分配一个新的桌面,以获得一个更“新鲜”的堆,我们可以更可预测地操作它。值得注意的是,在低完整性环境中运行的进程实际上不允许创建新的桌面

​ 目前主要关注的(我们将在稍后讨论关于元数据等的更多细节)是跟踪分配。

观察桌面堆的分配

为了监控桌面堆的释放和分配,我使用以下WinDbg脚本:

64位

ba e 1 nt!RtlFreeHeap ".printf\"RtlFreeHeap(%p, 0x%x, %p)\", @rcx, 
@edx, @r8; .echo ; gc";
ba e 1 nt!RtlAllocateHeap "r @$t2 = @r8; r @$t3 = @rcx; gu; .printf 
\"RtlAllocateHeap(%p, 0x%x):\", @$t3, @$t2; r @rax; gc";

32位

ba e 1 nt!RtlAllocateHeap "r @$t2 = poi(@esp+c); r @$t3 = poi(@esp+4); 
gu; .printf \"RtlAllocateHeap(%p, 0x%x):\", @$t3, @$t2; r @eax; gc";
ba e 1 nt!RtlFreeHeap ".printf\"RtlFreeHeap(%p, 0x%x, %p)\", 
poi(@esp+4), poi(@esp+8), poi(@esp+c); .echo ; gc"

除了这些断点脚本之外,因为桌面堆实际上只是用户态后端分配器的一种简化形式,所以我们实际上可以利用WinDBG本身中的!heap命令

填充堆孔

​ 为了利用这个漏洞,我们需要替换这个最近释放的tagSBINFO块。由于知道这个漏洞通常是如何被利用的,因此我们最终会破坏一些相邻的数据。于是有了一个基本的要求,即可预测地分配块。为了预测块的分配位置,我们必须控制整个堆布局(或尽可能多地控制)。合乎逻辑的方法是尽可能多地填充空闲块,这样所有新的分配都是相邻的,如果我们需要空闲空间,我们可以在可预测的位置创建它们(通过释放关联的块)。

​ 这部分是简单地理解副作用分配,上面的WinDbg脚本可以帮助解决这个问题。Tarjei在他的win32k幻灯片中提到了在这个堆上分配的大多数主要对象,我发现这与我所看到的非常一致。他的列表:

  • Window

  • Menu

  • Hook

  • CallProcData

  • Input Context

​ 桌面堆非常有趣,因为大多数分配直接绑定到窗口对象,由tagWND结构管理,这意味着如果我们想分配任意大小的块(比如一个小块来填充一个小洞),那么我们首先需要一个分配的窗口来与之交互。基本上可以将窗口结构看作堆的分配接口。另一个有趣的地方是,您可以通过一个窗口创建的许多分配的堆随后不能在不破坏窗口本身的情况下被销毁,这显然会产生堆副作用。最后,让我们假设一个窗口允许您分配大小为N的块。假设,我们需要20个大小为N的分配。让我们分配任意大小的窗口中的实际内容不会存储在列表中。每个窗口允许你做一个大小为N的受控分配,所以如果你需要做20个大小为N的分配,你必须先创建20个窗口,并使用每个窗口来完成分配。还有另外三种重要的数据类型,也分配在这个堆上,我们可以通过窗口对象间接地使用它们来控制堆上的数据。我会广泛地利用这些来开发和风水。这些都是:

  1. tagPROPLIST结构:这些作为一个足够小的分配,它们将填补我们所担心的任何小漏洞。包含单个tagPROPLIST条目的窗口将在32位上分配0x10字节,在64位上分配0x18字节。
  2. Windows text:这是桌面堆上任意大小的UNICODE字符串分配,它存储在嵌入到tagWND结构中的_LARGE_UNICODE_STRING结构中。请注意,strName成员是一个结构,而不是指针,但是该结构将包含一个指向关联窗口文本分配的指针。
  3. tagSBINFO结构:该漏洞的来源,但也包含四个部分或完全控制的成员。

下图展示了这些数据类型之间的关系:

在这里插入图片描述

​ 为了完成初始的堆填充,我创建了大量的tagWND结构(通过创建新窗口)。这样做的效果是在堆上填满了很多大洞,并且还为我们提供了接口,以便根据需要进行其他分配。在Windows 8和Windows 8.1中,分配一个新窗口会自动分配一个tagPROPLIST结构(在开发过程中可以使用上面的WinDbg脚本观察到这一点)。在Windows 7和更早的版本中,我们自己分配新的tagPROPLIST条目,这些条目可以填补任何小漏洞。

​ 此时,我们喷洒的每个窗口都没有对应的窗口文本字符串值,因此我们仍然可以使用这些值进行任意大小的分配和释放。在不破坏相应窗口的情况下,实际上无法删除现有的属性列表,但是可以强制重新分配列表以容纳新的条目,这可以用于在以前的位置创建一个孔。要做到这一点,只需要设置一个新属性,该属性的标识符(atomKey)在列表中不存在。

验证风水布局

​ 有趣的是,桌面堆被映射为只读的用户空间。这意味着,我们可以验证我们试图创建的风水布局,并在事情没有成功时退出。首先,我们需要找出桌面堆的用户地图在哪里。Tarjei在他的win32k论文中再次描述了这一点。PEB包含一个未文档化的名为Win32ClientInfo的结构,大致定义如下:

typedef struct _CLIENTINFO
{
ULONG_PTR CI_flags;
ULONG_PTR cSpins;
DWORD dwExpWinVer;
DWORD dwCompatFlags;
DWORD dwCompatFlags2;
DWORD dwTIFlags;
PDESKTOPINFO pDeskInfo;
ULONG_PTR ulClientDelta;
// incomplete. see reactos
} CLIENTINFO, *PCLIENTINFO;

首先,PDESKTOPINFO结构包含以下内容:

typedef struct _DESKTOPINFO { 
    PVOID pvDesktopBase;
    PVOID pvDesktopLimit;
// incomplete. see reactos 
} DESKTOPINFO, *PDESKTOPINFO;
pvDesktopBase包含我们记录的桌面堆的内核地址。接下来,来自Win32ClientInfo结构的ulClientDelta值包含一个delta,它是用户空间映射和内核映射之间的偏移量,它告诉我们信息。

​ pvDesktopBase包含我们记录的桌面堆的内核地址。接下来,来自Win32ClientInfo结构的ulClientDelta值包含一个delta,它是用户空间映射和内核映射之间的偏移量,它告诉我们信息。但是,如果不需要的话,我们不希望自己解析堆,所以理想情况下,我们还希望能够获取一个给定的user32句柄,比如HWND值,并将其转换为用户态映射中的地址,这样我们就可以实际确定它与其他分配的关系。为了处理查找,我们需要找到一个名为gShared的结构,它通常存储在user32.dll中。在Windows 7和以后的版本中,这个地址会被导出,所以很容易找到。

大多数系统的结构定义如下:

kd> dt !tagSHAREDINFO
win32k!tagSHAREDINFO
 +0x000 psi : Ptr32 tagSERVERINFO
 +0x004 aheList : Ptr32 _HANDLEENTRY
 +0x008 HeEntrySize : Uint4B
 +0x00c pDispInfo : Ptr32 tagDISPLAYINFO
 +0x010 ulSharedDelta : Uint4B
 +0x014 awmControl : [31] _WNDMSG
 +0x10c DefWindowMsgs : _WNDMSG
 +0x114 DefWindowSpecMsgs : _WNDMSG

​ 在上面的结构中,aheList是一个指向句柄数组的指针,每个_HANDLEENTRY都包含一个指向句柄的实际内核地址的指针。然后我们可以从中减去我们已知的用户空间偏移,并得到一个可用的地址进行调查。不幸的是,在Windows 7之前的系统上查找gSharedInfo数据并不容易,因为这个符号没有导出。Tarjei的论文指出,未文档化的CsrClientConnectToServer函数可以用来获取gSharedInfo的一个副本,但是我找不到可用的示例。实现的一个恼人障碍是,函数所需的结构大小在64位Vista、32位Vista、64位和32位Windows XP之间变化,因此根据我的经验,我们不能完全相信ReactOS中的内容。一旦我们确定了对象的映射位置,我们就可以构建函数来精确地告诉我们窗口对象在桌面堆中的位置。然后,如果我们想知道在哪里分配了相应的属性列表或文本块,我们可以只解析用户态中该位置的结构。

用tagPROPLIST替换tagSBINFO

​ 现在我们终于接近开发这个了。我们有一种方法来调整堆,一种方法来验证我们的块在正确的位置,我们可以触发错误,所以现在我们可以最终确保释放的tagSBINFO块被替换为我们选择的tagPROPLIST属性列表。注意,tagPROPLIST只是一个更大的列表的头部。关于为什么我们能够将列表的大小与滚动条信息块匹配,稍后我们将进行描述。它基本上是一个标记道具条目数组,但被称为属性列表;所以我将交替使用数组和列表这两个术语。在64位上,tagPROPLIST的结构如下:

kd> dt -b !tagPROPLIST
win32k!tagPROPLIST
 +0x000 cEntries : Uint4B
 +0x004 iFirstFree : Uint4B
 +0x008 aprop : tagPROP
     +0x000 hData : Ptr64 
     +0x008 atomKey : Uint2B
     +0x00a fs : Uint2B

​ 正如前面提到的,属性列表与窗口相关联。属性列表是使用SetProp()函数创建的。它的工作方式是使用匹配的atomKey搜索现有属性,如果没有找到匹配的atomKey,则在属性列表中创建一个新的属性条目。如果根本没有找到属性列表,则分配一个并将其链接到tagWND结构中。

​ 因此,假设我们已经喷洒了一个tagWND结构的bug,并为每个tagPROPLIST创建了相关的条目,这将得到如下所示的布局:

在这里插入图片描述

​ 一旦设置好,我们就可以分配我们想要利用的滚动条控件。这将导致类似的结果如下:

在这里插入图片描述

然后我们与滚动条控件进行交互,从而触发我们的用户模式回调函数,该函数允许我们通过试图销毁窗口来释放tagSBINFO结构。这导致布局类似于以下:

在这里插入图片描述

在64位上,tagSBINFO结构是0x28字节,单个条目tagPROPLIST数组是0x18字节,其中0x10字节是默认的tagPROP条目。因此,具有两个条目的属性列表将是0x28字节(0x8 + 0x10 + 0x10),这是一个完美的匹配。假设我们已经喷洒了内存,所以所有的漏洞都被填满了。我们只需要使用一个带有预先存在的属性列表的窗口,并计划在释放tagSBINFO之后立即在其列表中添加一个新条目(在前面的图中已经说明了)。这做的是释放与原始tagPROPLIST结构相关联的0x18块,由于堆喷涂,它不会与空闲块相邻,因此不会合并成任何大到足以容纳我们希望的后续0x28字节分配的块。相反,将使用最近释放的tagSBINFO位置。这个场景如下图所示:

在这里插入图片描述

我们从hook的回调函数中返回,UAF将触发,位将被写入tagPROPLIST,特别是cEntries成员。最初cEntries是0x2,对应于我们创建的两个属性列表条目。重写之后,它变成0xe,对应于正在设置的第3位和第4位。至此,我们创建了一个新的内存溢出利用点。我们第一部分的利用就完成了。

明日计划

29号交论文二稿,28号改一天论文没问题吧。今晚会尝试一下对第一部分进行调试。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章