CVE-2012-0003

1. 辅助调试 :gflags开启堆页

gflags开启堆页

与普通堆相比页堆有很大的不同,每个堆块至少占用两个内存页。在存放用户数据的第一个内存页后面,堆管理器会额外多分配一个内存页。这个内存页是用来检测溢出的,被称为栅栏页。

栅栏页的属性为PAEG_NOACCESS,因此一旦用户数据发生溢出触及到栅栏页便会引发异常,使调试人员第一时间发现问题,从而可以迅速定位到导致溢出的代码。

有人也许会有疑问,当堆块非常小时,难道也是占用两个内存页么?答案是肯定的。为了及时检测溢出,堆块被放到第一个内存页的末尾紧邻栅栏页,因此第一个内存页前面的大半部分有可能都是没有被使用的。由于分配粒度为8byte,堆块和栅栏页之间可能会有填充字段。对于很小的堆块也需要占用两个内存页,这是很耗费空间的。

2. MIDI文件格式

闲暇时间是个不错的选择 ( ╯ ‵ □ ′ ) ╯ ︵ ┻━┻

音轨事件结构
事件类型 格式 描述
关闭音符(Note Off) 0x8n note velocity n通道号,note音高,velocity按键速度
打开音符(Note On) 0x9n note velocity n通道号,note音高,velocity按键速度
触后音符(Note Aftertouch) 0xAn note amount n通道号,note音高,amount按压力度

3.基于导图推算的漏洞分析方法

下载pdb文件:

symchk /r E:\dll /s SRV*E:\pdb*http://msdl.microsoft.com/download/symbols

漏洞在微软的多媒体库winmm.dll中。记得先把system32下的winmm.dll替换成有漏洞的版本,以及留个备份。

windows media player的版本可以不用太强求,wmplayer10就行。然后IE6。

immunity debugger附加到ie上,然后打开poc.html,因为开启了页堆的原因,当发生堆访问越界时程序立刻断下来了:


ida看一下漏洞所在的函数,是一个叫做midiOutPlayNextPolyEvent的函数(可以加载微软官方符号表来识别函数名)


实际上是访问*v25[esi]的时候发生了堆访问越界。v25的值主要受v24v20的影响。其中:

而v1的值来自函数的参数wParam,所以v20导致崩溃的可能性很低,所以重点看下v24的来源。

接下来需要分析一下v24的变量传播的路径。

这里我想到借助ida python自动化完成这里的变量传播静态分析,但是遇到了一些问题:idapython:How can I get xref to a stack variable in the form of pseudocode

手动+眼力分析,最后得出v24的值与wParam,v2,v9,v11,v13,v21有关,对这几个变量在immunity debugger中shift+F4条件记录断点,运行后得到log:

v2wParam是不变的,v9是递增的计数器,v11v13相等,v12v11的最后一个字节,根据漏洞报告中触发漏洞的是0x9x或0x8x的音轨事件,再看下v11v21,很容易猜到v11就是包含参数的音轨事件,而v21就是音轨事件类型。

触发漏洞的音轨块

在读取音轨事件处shift+F2下条件断点,当[ebx+eax]==0x007db29f时断下:

读取音轨事件

继续往后跟:

实际上可以看到,漏洞成因是由Note On事件计算地址偏移导致的访问越界,其中v24是一个过大的偏移值(大于堆块大小),v20是访问基址,实际调试v20的值0x045A2C00
看一下0x45a2c00的内容

对windows xp sp3的堆结构还没有什么了解,但从0x45a2c00往前看好像并没有找到堆块的头部。

另一种思路只能确认下这个地址的来源:

  1. 确认v20的来源:

  2. v1又来自wParam即漏洞函数sub_76B2D038的参数。

  3. 交叉引用查看对sub_76B2D038的调用:

  4. 漏洞函数的参数v6来自全局变量gpEmuList

  5. gpEmuList的交叉引用:

ESI = v20
       = *(gpEmuList+0x84);
       = *(v7+0x84);
       = v8
       = winmmAlloc(0x400u);
  1. winmmAlloc()函数内容:

就是一个HeapAlloc啊,堆块头部嘞?此处留坑

到这里漏洞的成因也已经很明显了,使用HeapAlloc申请了一块0x400大小的堆块VulBuffer,但是之后访问了这个堆块的偏移0x419,造成了堆的越界访问

4. 漏洞利用

首先把gflags关了(N次失败之后,每次都是执行到漏洞点结果直接抛出异常了?是gflags没关掉还是啥原因哟> <,然后换成ie8并且恢复到一个远古时期的虚拟机镜像后终于成功了)

4.1 重新回顾一下漏洞:

  • 是一个计算偏移值错误导致的堆越界访问,堆块VulBuffer原本大小为0x400,实际访问偏移0x419位置的内存。
  • 越界访问后会将偏移0x419处的一字节+1


    off-by-one

4.2 漏洞利用原理:

1.先创建select元素selob,并为其设置64个属性,其中只有w1为String类型,其余均为object类型。可以计算出selob的大小为64*0x10=0x400字节(构造UAF的条件)

var selob = document.createElement("select")
selob.w0 = alert
selob.w1 = unescape("%0c0c %u0c0c")
selob.w2 = alert
...
...
selob.w62=alert
selob.w63=alert

selob在内存中的内容:


其中09代表object类型,08代表string类型,03代表int

2.然后创建一个大小为1000的数组clones,利用selob元素的cloneNode方法循环复制数据到数组clones;最后再间隔地释放clones数组中的元素;

var clones=new Array(1000);
function feng_shui(){
    var i=0;
    while(i<1000){
        clones[i] = selob.cloneNode(true)
        i=i+1;
    }

var j = 0;
while(j<1000){
    delete clones[j];
    CollectGarbage();
    j = j+2;
    }
}

溢出后将w1的类型标识+1变为09,之后会将w1视作一个object类型。调用该属性对象的函数会去索引虚表(每个对象的头4字节即为虚表指针)。
从而可以通过selob.w1的string内容来控制虚表指针,导致执行任意代码。

索引虚表

string内容

ecx

可以看到此时的ecx的值正是我们声明string的值。

4.3 又是堆喷射:

上堆喷射!

此时ecx=0C0C1BDC
[ecx+4]的内容刚好是我们用来填充的\xc0\xc0\xc0\xc0
跟进call [ecx+4]会发现成功执行到0xC0C0C0C0的slide中

slide

一路滑向shellcode(系统关闭DEP):

关于如何利用ROP绕过DEP并执行shellcode有待进一步探究。

你可能感兴趣的:(CVE-2012-0003)