1. Vulnerability Description
1.1 The Issue
崩溃发生在CAttrArray::PrivateFindInl函数中。 在函数中rcx(this)指针应该指向一个CAttrArray,但它实际上指向一个CAttribute。
CAttrArray::PrivateFindInl只会执行读取操作,其返回值将被调用函数(CAttrArray::SetParsed)抛弃。
1.2 Affect version
Windows 10 Enterprise 64-bit (OS version 1607, OS build 14393.1198)
Microsoft Edge 38.14393.1066.0, Microsoft EdgeHTML 14.14393.
1.3 Timeline
01/12/2016 Advisory disclosed
01/12/2016 +0 days Countermeasure disclosed
01/12/2016 +0 days SecurityTracker entry created
01/12/2016 +0 days VulnerabilityCenter entry assigned
01/13/2016 +1 days VulnerabilityCenter entry created
01/14/2016 +1 days VulDB entry created
01/17/2016 +3 days VulnerabilityCenter entry updated
01/19/2016 +2 days VulDB last update
2. Technical description and PoC
2.1 Crash
从Google Project Zero的报告中获取的PoC如下
WinDBG attach到Edge上,运行PoC,发现Crash(注:这里用了一款Edge专用的辅助Debug的工具,可以比较方便的在命令行直接attach到进程上。)
edgehtml!CAttrArray::PrivateFindInl+0xd6:
00007ffa`3b9e04b6 41f644d00380 test byte ptr [r8+rdx*8+3],80h ds:00000003`0005ffbe=??
可以看出,这里是引用了一个无效的指针,此时的调用栈如下
1:048> k
# Child-SP RetAddr Call Site
00 00000013`84bfad60 00007ffa`3bbaccc9 edgehtml!CAttrArray::PrivateFindInl+0xd6
01 00000013`84bfad90 00007ffa`3bb1a68b edgehtml!CAttrArray::SetParsed+0x49
02 00000013`84bfae00 00007ffa`3bb1c40c edgehtml!CssParser::RecordProperty+0x24b
03 00000013`84bfae70 00007ffa`3bb1b10c edgehtml!CssParser::HandleSingleDeclaration+0x21c
04 00000013`84bfaef0 00007ffa`3bae026b edgehtml!CssParser::HandleDeclaration+0x9c
05 00000013`84bfaf20 00007ffa`3badedaa edgehtml!CssParser::Write+0x3b
06 00000013`84bfaf60 00007ffa`3b93165c edgehtml!ProcessCSSText+0x112
07 00000013`84bfafe0 00007ffa`3b94aae3 edgehtml!CStyle::SetCssText+0xbc
08 00000013`84bfb020 00007ffa`3bc2ed85 edgehtml!CFastDOM::CCSSStyleDeclaration::Trampoline_Set_cssText+0x77
09 00000013`84bfb070 00007ffa`3af6c35b edgehtml!CFastDOM::CCSSStyleDeclaration::Profiler_Set_cssText+0x25
0a 00000013`84bfb0a0 00007ffa`3af34460 chakra!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x16b
0b 00000013`84bfb180 00007ffa`3aed6d09 chakra!Js::LeaveScriptObject<1,1,0>::LeaveScriptObject<1,1,0>+0x180
0c 00000013`84bfb1d0 00007ffa`3aed44ae chakra!Js::JavascriptOperators::CallSetter+0xa9
0d 00000013`84bfb270 00007ffa`3aed4be2 chakra!Js::JavascriptOperators::SetProperty_Internal<0>+0x4de
0e 00000013`84bfb330 00007ffa`3aed4b1f chakra!Js::JavascriptOperators::OP_SetProperty+0xa2
0f 00000013`84bfb380 00007ffa`3af8c1fb chakra!Js::JavascriptOperators::PatchPutValueWithThisPtrNoFastPath+0x9f
10 00000013`84bfb400 00007ffa`3aec1ca0 chakra!Js::ProfilingHelpers::ProfiledStFld<0>+0x1cb
11 00000013`84bfb4d0 00007ffa`3aec6a50 chakra!Js::InterpreterStackFrame::OP_ProfiledSetProperty > const >+0x70
12 00000013`84bfb520 00007ffa`3aec4aa2 chakra!Js::InterpreterStackFrame::ProcessProfiled+0x340
13 00000013`84bfb5b0 00007ffa`3aec8b5e chakra!Js::InterpreterStackFrame::Process+0x142
14 00000013`84bfb610 00007ffa`3aeca265 chakra!Js::InterpreterStackFrame::InterpreterHelper+0x48e
15 00000013`84bfb950 00000176`dcdc0fb2 chakra!Js::InterpreterStackFrame::InterpreterThunk+0x55
16 00000013`84bfb9a0 00007ffa`3aff1393 0x00000176`dcdc0fb2
17 00000013`84bfb9d0 00007ffa`3aebd873 chakra!amd64_CallFunction+0x93
18 00000013`84bfba20 00007ffa`3aec0490 chakra!Js::JavascriptFunction::CallFunction<1>+0x83
19 00000013`84bfba80 00007ffa`3aec4f4d chakra!Js::InterpreterStackFrame::OP_CallI > > >+0x110
1a 00000013`84bfbad0 00007ffa`3aec4b07 chakra!Js::InterpreterStackFrame::ProcessUnprofiled+0x32d
1b 00000013`84bfbb60 00007ffa`3aec8b5e chakra!Js::InterpreterStackFrame::Process+0x1a7
1c 00000013`84bfbbc0 00007ffa`3aeca265 chakra!Js::InterpreterStackFrame::InterpreterHelper+0x48e
1d 00000013`84bfbf00 00000176`dcdc0fba chakra!Js::InterpreterStackFrame::InterpreterThunk+0x55
1e 00000013`84bfbf50 00007ffa`3aff1393 0x00000176`dcdc0fba
1f 00000013`84bfbf80 00007ffa`3aebd873 chakra!amd64_CallFunction+0x93
20 00000013`84bfbfd0 00007ffa`3af2c2ec chakra!Js::JavascriptFunction::CallFunction<1>+0x83
21 00000013`84bfc030 00007ffa`3af2b8b6 chakra!Js::JavascriptFunction::CallRootFunctionInternal+0x104
22 00000013`84bfc120 00007ffa`3afd6259 chakra!Js::JavascriptFunction::CallRootFunction+0x4a
23 00000013`84bfc190 00007ffa`3af31d41 chakra!ScriptSite::CallRootFunction+0xb5
24 00000013`84bfc230 00007ffa`3af2d8fc chakra!ScriptSite::Execute+0x131
25 00000013`84bfc2c0 00007ffa`3bb3278d chakra!ScriptEngineBase::Execute+0xcc
26 00000013`84bfc360 00007ffa`3bb326d8 edgehtml!CJScript9Holder::ExecuteCallbackDirect+0x3d
27 00000013`84bfc3b0 00007ffa`3bb431f7 edgehtml!CJScript9Holder::ExecuteCallback+0x18
28 00000013`84bfc3f0 00007ffa`3bb42fe7 edgehtml!CListenerDispatch::InvokeVar+0x1fb
29 00000013`84bfc570 00007ffa`3bb310da edgehtml!CListenerDispatch::Invoke+0xdb
2a 00000013`84bfc5f0 00007ffa`3bbc1602 edgehtml!CEventMgr::_InvokeListeners+0x2ca
2b 00000013`84bfc750 00007ffa`3ba9a495 edgehtml!CEventMgr::_InvokeListenersOnWindow+0x66
2c 00000013`84bfc780 00007ffa`3ba99f23 edgehtml!CEventMgr::Dispatch+0x405
2d 00000013`84bfca50 00007ffa`3bad00c2 edgehtml!CEventMgr::DispatchEvent+0x73
2e 00000013`84bfcaa0 00007ffa`3bb0296a edgehtml!COmWindowProxy::Fire_onload+0x14e
2f 00000013`84bfcbb0 00007ffa`3bb01596 edgehtml!CMarkup::OnLoadStatusDone+0x376
30 00000013`84bfcc70 00007ffa`3bb46d7f edgehtml!CMarkup::OnLoadStatus+0x112
31 00000013`84bfcca0 00007ffa`3bb2859d edgehtml!CProgSink::DoUpdate+0x3af
32 00000013`84bfd130 00007ffa`3bb29d70 edgehtml!GlobalWndOnMethodCall+0x24d
33 00000013`84bfd230 00007ffa`593e1c24 edgehtml!GlobalWndProc+0x130
34 00000013`84bfd2f0 00007ffa`593e156c user32!UserCallWinProcCheckWow+0x274
35 00000013`84bfd450 00007ffa`380ec781 user32!DispatchMessageWorker+0x1ac
36 00000013`84bfd4d0 00007ffa`380eec41 EdgeContent!CBrowserTab::_TabWindowThreadProc+0x4a1
37 00000013`84bff720 00007ffa`4f7b9266 EdgeContent!LCIETab_ThreadProc+0x2c1
38 00000013`84bff840 00007ffa`59d98364 iertutil!SettingStore::CSettingsBroker::SetValue+0x246
39 00000013`84bff870 00007ffa`59f55e91 KERNEL32!BaseThreadInitThunk+0x14
3a 00000013`84bff8a0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
寄存器的值如下
1:048> r
rax=0000000000003ffd rbx=0000000000000002 rcx=00000176d9804cf0
rdx=000000000000bff7 rsi=0000000000003ffd rdi=0000000000000000
rip=00007ffa3b9e04b6 rsp=0000001384bfad60 rbp=0000000000000002
r8=0000000300000003 r9=00000000800114a4 r10=0000000000000000
r11=0000000000007ffa r12=00000176d9a9c680 r13=00000176d9734b01
r14=00000176d9804c88 r15=0000000000000000
iopl=0 nv up ei pl nz na pe nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
在ida中查看相关的代码:
int __fastcall CAttrArray::PrivateFindInl(__int64 this, int a2, signed int CAttrValue__AATYPE)
{
signed int v3; // edi@1
signed int v4; // er10@1
int v5; // er9@1
__int64 this_add_4; // r11@4
__int64 this_add_8; // r8@4
signed int v8; // ebx@4
unsigned __int64 v9; // rax@7
signed __int64 v10; // rdx@7
unsigned __int64 v11; // r11@8
int v12; // ecx@10
signed int v13; // ecx@16
__int64 v14; // rsi@22
int v15; // ecx@23
signed int v16; // ecx@34
signed int v17; // esi@38
signed __int64 v18; // rdx@38
int v19; // ecx@40
int v21; // [sp+38h] [bp+10h]@20
v3 = 0;
v4 = CAttrValue__AATYPE;
v5 = a2;
if ( CAttrValue__AATYPE == 6 )
v4 = 0;
if ( a2 == -1 )
{
v21 = -1;
LODWORD(v9) = CAttrArray::PrivateFindLinear(this, (unsigned int)v4, &v21, 0xFFFFFFFFi64);
}
else
{
this_add_4 = *(_DWORD *)(this + 4);
this_add_8 = *(_QWORD *)(this + 8);
v8 = 2;
if ( v4 > 2 )
v8 = v4;
if ( (signed int)this_add_4 < 11 )
{
v9 = *(_QWORD *)(this + 8);
v10 = 3 * this_add_4;
goto LABEL_8;
}
if ( (signed int)this_add_4 > 0 )
{
while ( 1 )
{
v14 = ((signed int)this_add_4 + v3) / 2;
v9 = this_add_8 + 24 * v14;
if ( *(_BYTE *)(this_add_8 + 24 * v14 + 3) & 0x80 )// vuln
v15 = *(_DWORD *)(v9 + 8);
else
v15 = *(_DWORD *)(*(_QWORD *)(v9 + 8) + 48i64);
if ( a2 < v15 )
{
LODWORD(this_add_4) = ((signed int)this_add_4 + v3) / 2;
goto LABEL_26;
}
if ( a2 > v15 )
goto LABEL_30;
v16 = *(_BYTE *)v9;
if ( v4 == v16 )
return v9;
if ( v8 >= v16 )
break;
LODWORD(this_add_4) = ((signed int)this_add_4 + v3) / 2;
LABEL_26:
if ( (signed int)this_add_4 - v3 < 10 )
{
v9 = this_add_8 + 24i64 * v3;
goto LABEL_28;
}
if ( v3 >= (signed int)this_add_4 )
goto LABEL_19;
}
if ( v8 == 2i64 )
{
v17 = v14 - 1;
v18 = v9 - 24;
if ( v17 >= v3 )
{
while ( 1 )
{
v19 = *(_BYTE *)(v18 + 3) & 0x80 ? *(_DWORD *)(v18 + 8) : *(_DWORD *)(*(_QWORD *)(v18 + 8) + 48i64);
if ( v5 != v19 )
break;
if ( v4 == *(_BYTE *)v18 )
{
LODWORD(v9) = v18;
return v9;
}
if ( v17 != v3 )
{
v18 -= 24i64;
if ( --v17 >= v3 )
continue;
}
break;
}
}
LABEL_28:
v10 = 3i64 * (signed int)this_add_4;
LABEL_8:
v11 = this_add_8 + 8 * v10;
if ( v9 < v11 )
{
while ( 1 )
{
if ( *(_BYTE *)(v9 + 3) & 0x80 )
v12 = *(_DWORD *)(v9 + 8);
else
v12 = *(_DWORD *)(*(_QWORD *)(v9 + 8) + 48i64);
if ( v12 >= v5 )
{
if ( v12 != v5 )
goto LABEL_19;
v13 = *(_BYTE *)v9;
if ( v13 == v4 )
return v9;
if ( v13 > v8 )
goto LABEL_19;
}
v9 += 24i64;
if ( v9 >= v11 )
goto LABEL_19;
}
}
goto LABEL_19;
}
LABEL_30:
v3 = v14 + 1;
goto LABEL_26;
}
LABEL_19:
LODWORD(v9) = 0;
}
return v9;
}
其中引发问题的指令是
if ( *(_BYTE *)(this_add_8 + 24 * v14 + 3) & 0x80 )
其中this_add_8变量对应的是 rcx+8
那么先查看下rcx的值
1:048> dps rcx
00000176`d9804cf0 00007ffa`3c562d38 edgehtml!CAttribute::`vftable'
00000176`d9804cf8 00000003`00000003
00000176`d9804d00 00000000`00000008
00000176`d9804d08 00000000`00000000
00000176`d9804d10 00000000`00000000
00000176`d9804d18 00000176`dcc18150
00000176`d9804d20 00007ffa`3c55fae0 edgehtml!s_propdescCElementstyle_Str
00000176`d9804d28 00000000`800103eb
00000176`d9804d30 00000176`d9a742f4
00000176`d9804d38 00000000`00000000
00000176`d9804d40 00000000`00000000
00000176`d9804d48 00000000`00000000
00000176`d9804d50 00000176`d9828500
00000176`d9804d58 00000176`d98a8580
00000176`d9804d60 00000176`d9850c00
00000176`d9804d68 00000176`d983c330
这里rcx是this指针,在CAttrArray::PrivateFindInl这个类的函数中应该是一个CAttrArray对象的this指针,可是这里rcx存了一个CAttribute的虚表对象,也就是说,在之前有一个错误的调用。
那么根据调用栈继续上溯,我们可以看到CAttrArray::SetParsed函数,调用PrivateFindInl的代码如下
if ( v6 || (v8 = *a1) == 0i64 || (LODWORD(v9) = CAttrArray::PrivateFindInl((__int64)v8, a2, 0), !v9) )
{
v11 = v4;
v10 = 31;
CAttrArray::Set(v7, (unsigned int)v5, &v10, 0i64);
}
也就是说这里如果该函数返回错误的值,那么CAttrArray::Set将不会执行。
继续向上回溯,查看CssParser::RecordProperty函数
void __fastcall CssParser::RecordProperty(CssParser *this, struct CssTokenizer *a2, __int32 a3, char a4, bool a5)
{
char v5; // si@1
__int32 v6; // edi@1
struct CssTokenizer *v7; // r14@1
CssParser *v8; // rbp@1
CBase *v9; // rcx@3
char v10; // bl@4
bool v11; // r15@4
bool v12; // al@6
char v13; // r13@9
unsigned __int8 v14; // bl@10
MemoryProtection *v15; // rsi@11
const unsigned __int16 *v16; // r12@11
__int32 v17; // er8@13
__int64 v18; // rcx@18
const unsigned __int16 *v19; // rdi@18
unsigned __int64 v20; // rsi@18
__int64 v21; // rax@18
__int16 v22; // ax@24
unsigned __int64 v23; // r14@30
signed __int64 v24; // r14@31
const struct PROPERTYDESC *v25; // rax@37
int v26; // er10@37
CDoc *v27; // rax@44
const unsigned __int16 *v28; // rdx@51
unsigned __int16 *v29[2]; // [sp+20h] [bp-48h]@4
__int64 v30; // [sp+30h] [bp-38h]@4
__int32 v31; // [sp+80h] [bp+18h]@1
v31 = a3;
v5 = a4;
v6 = a3;
v7 = a2;
v8 = this;
if ( *((_UNKNOWN **)this + 22) == &CCSSStyleDeclaration::s_apHdlDescs && *((_QWORD *)this + 2) )
{
v9 = (CBase *)*((_QWORD *)this + 23);
if ( !v9 )
{
LABEL_4:
v10 = 0;
v30 = 0i64;
_mm_storeu_si128((__m128i *)v29, 0i64);
v11 = v6 == -1;
v12 = v5 || v11;
if ( a5 || v12 )
{
v13 = 1;
if ( v12 )
v10 = -128;
}
else
{
v13 = 0;
}
v14 = v10 | 8;
if ( !v13 )
{
LABEL_11:
v15 = (MemoryProtection *)v29[0];
v16 = &g_szEmpty;
if ( v11 )
{
if ( v29[0] )
v28 = v29[0];
else
v28 = &g_szEmpty;
if ( CssParser::GetExpandoDispID(v8, v28, &v31) )
{
LABEL_15:
if ( v15 )
MemoryProtection::HeapFree(v15, (void *)a2);
return;
}
v6 = v31;
}
if ( v6 != -1 )
{
v17 = *((_DWORD *)v8 + 7) + 1;
if ( (unsigned int)v17 > *((_DWORD *)v8 + 6) >> 2 )
{
if ( v17 < 0 )
{
Abandonment::InvalidArguments();
JUMPOUT(*(_QWORD *)&byte_18042A78B);
}
CImplAry::EnsureSizeWorker((CssParser *)((char *)v8 + 24), 4ui64, v17);
}
*(_DWORD *)(*((_QWORD *)v8 + 4) + 4i64 * (*((_DWORD *)v8 + 7))++) = v6;
if ( v13 )
{
if ( v11 )
{
CAttrArray::SetParsed(*((struct CAttrArray ***)v8 + 2), v31, &pwzURI, v14);
}
else
{
v25 = GetStandardPropDescFromAliasDISPID(v31);
if ( v25 )
v26 = *((_DWORD *)v25 + 12);
if ( v15 )
v16 = (const unsigned __int16 *)v15;
CAttrArray::SetParsed(*((struct CAttrArray ***)v8 + 2), v26, v16, v14);
}
}
}
goto LABEL_15;
}
v18 = *((_QWORD *)v7 + 1);
v19 = (const unsigned __int16 *)*((_QWORD *)v7 + 5);
v20 = v18 + 2i64 * *((_DWORD *)v7 + 5);
v21 = *((_DWORD *)v7 + 4);
if ( v20 > v18 + 2 * v21 )
v20 = v18 + 2 * v21;
if ( !v11 )
{
if ( (unsigned __int64)v19 >= v20 )
{
LABEL_31:
v24 = (signed __int64)(v20 - (_QWORD)v19) >> 1;
if ( !v11 && (unsigned int)v24 >= 0xA && !StrCmpNICW(v20 - 20, L"!important", 10i64) )
{
LODWORD(v24) = v24 - 10;
v14 |= 2u;
}
CBuffer::Append((CBuffer *)v29, v19, v24);
CBuffer::TrimTrailingWhitespace((CBuffer *)v29);
v6 = v31;
goto LABEL_11;
}
if ( 58 == *v19 )
++v19;
v22 = *(_WORD *)(v20 - 2);
if ( v22 == 59 || !v22 || v22 == 125 )
v20 -= 2i64;
}
if ( (unsigned __int64)v19 < v20 )
{
while ( IsCharSpaceW(*v19) )
{
++v19;
if ( (unsigned __int64)v19 >= v20 )
goto LABEL_31;
}
if ( (unsigned __int64)v19 < v20 )
{
do
{
v23 = v20 - 2;
if ( !IsCharSpaceW(*(_WORD *)(v20 - 2)) )
break;
v20 -= 2i64;
}
while ( (unsigned __int64)v19 < v23 );
}
}
goto LABEL_31;
}
v27 = CBase::GetCDoc(v9);
if ( !v27 || CDoc::CheckCSSDiagnosticsAvailability(v27) )
{
v6 = v31;
goto LABEL_4;
}
}
}
CAttrArray::SetParsed的调用在下面两句:
CAttrArray::SetParsed(*((struct CAttrArray ***)v8 + 2), v31, &pwzURI, v14);
CAttrArray::SetParsed(*((struct CAttrArray ***)v8 + 2), v26, v16, v14);
这里v8是this指针,也就是说在这里发生了一次错误的调用,本来应该传入的是CAttrArray,但是传入了一个CAttribute,造成了Type confusion
3. References
1. project-zero
2. nvd
3. cve
4. seebug