# qwn2own 記錄 qwn2own 是 2016 年 bkp 的題目(好像),是一道 C++ 做成的 js extension 跑在 webkit browser 上, exploit 方式也滿有趣的,賽方要求參賽者給一個跑有 exploit js 的網址,賽方直播用 browser 打開網頁,如果有彈 calc 就算成功 exploit 範圍可以分為: exploit C++ 和 jit 兩個步驟, 217 的 seanwu 有[教學影片](https://www.youtube.com/watch?v=PhMcUJ9pWs0&t=1749s)可供參考,但 exploit 的穩定度好像不是很高,有些方法也是參照[這篇 blog](https://blog.frizn.fr/bkpctf-2016/qwn2own-bkpctf16)的,所以可以直接看這篇 blog ,不過 sean 後面還有提到 jit spray 的部分,算是額外補充。 > 影片中雖然說環境好像差不是很多,但我用 18.04 ubuntu 弄不出來,換 15.10 就可以 (比賽環境) > ## 分析 因為整個 js extension 的重點基本上都在操作 vector ,所以找了 qvector.h 看一下 source code: 在 BKPStore::remove() 的地方, function 根據 idx 來 erase 但並未檢查其範圍,導致 Vector header 的部分可以被 erase 掉(已經被 patch 掉了 QQ) ## exploit Vector 的 erase 會把後面的資料前移,若 erase(-1) 則原本 index 為 0 的資料會蓋到 -1 的位置,這個位置為 Vector 的 offset , offset 作用=是當程式要從 vector 取資料時會從開頭 + offset 的地方拿,能覆蓋掉 offset 為 0 則可以修改掉 vector header , vector header 內的 size field 改大後基本上有了一個不太任意的地址寫入和讀取(透過 insert, get()) 這種讀取寫入的限制太多,但若是修改掉 offset 則能讀寫的範圍就更大了,特別注意的是修改掉自身的 offset 基本上很難做第二次的讀寫,所以最好的做法就是要了兩塊 vector A, B ,A 先用不太任意的讀寫改掉 B 的 offset ,再由 B 去讀寫,這樣基本上就有了任意記憶體位置讀寫 :exclamation: 由於 browser 是個 multi thread 的 process ,很多 thread 會同時使用 libc heap ,加上 garbage collection 的關係, heap 上的 layout 很難預測,當要了 A, B 兩個 vector 時,很可能 A 在 B 之後,這種情況可以在 exploit 前面大量塞滿相同大小的 chunk ,讓 A, B 相對順序高機率處於 A 前 B 後的情況 [參考](https://pdfs.semanticscholar.org/8c7f/27fabc8661de3483e305cd2d08ee8fad0e34.pdf) 沒有(或者說近乎沒有) size 限制後能做的操作有: 1. 任意記憶體讀取: 把 offset 改為 addr - B_vec_addr ,透過 B.get(0) 可以直接讀取 2. 任意記憶體寫入: 透過任意讀取設定 B.offset ,直接用 insert 就可以任意記憶體位置寫入 讀取和寫入的重點就是必須知道 B vector address ,這點可以在 libc heap 上利用任意地址讀取找 B 的 store object (內有保存 B vector object 地址) 有了讀取和寫入,剩下就是想辦法轉成任意執行,blog 提供 3 個思路: 1. 寫掉 vtable 後控制 stack ,配合 mprotect 打開執行權限再放入 shellcode: 要另外找到 stack address 2. 找到 jit segment ,利用 jit page 為可寫可執行的特性放上 shellcode 後再改掉 vtable entry ,讓其跳轉時跳入 shellcode 內 3. 弄一個 function object ,找到其地址後寫掉 jit pointer 或 machine code 最後一種算是最簡單,因為我的目標就是拿到 shell 而已,不太需要管怎麼回復程式,所以直接用第三種方法, blog 提供了另外一種透過尋找 symbol 開啟 mprotect 後寫 shellcode 彈計算機回復狀態的做法,不過現在太累,之後有機會再看 第三點難在於 js heap 和 libc heap 是分開的,必須從 libc heap 找到存放 js heap 結構相關資料的 structure 再爬到 js heap ,再從 js heap 爬到 jit page ,這部分對於沒碰過的人會沒有頭緒 QQ 找尋 js heap 的方法: 1. 往回搜尋 heap: 因為前面塞了一堆穩定狀態的 object ,此時 A, B object 應該會在 heap 後端的位置 2. 找到 heap 內部存有地址且對齊 page size 3. 利用其他欄位判斷是否為 js heap 上面這個思路是 blog 作者提供的,他說找的方法很多種,透過觀察可以發現 heap 內部存有對齊 page size 的值,所以用上面的步驟來判定 我有試著觀察一下 heap 內容,但 libc heap 其實滿大的,手動找有點累,故我寫了一個 gcc script 來找,找法是看值是否對齊 page size 和 js heap 大小 0x10000 當作判斷依據: ``` ``` 因為資料還是有點多,可以透過 gdb 的 `set logging on` 輸出到外部檔案,最後在慢慢看哪個地址上 0x10000 和對齊 page size 的值很接近,基本上可以斷定是 js heap ,不過在 18.04 下好像不太管用... 找到 js heap 後接下來要想怎麼找到 jit page 上對應的 jit code ,做法很簡單,先弄一堆 element 的 array 出來,裡面放入特定的 pattern 和 function object ,搜尋整個 js heap 時比對 pattern 就能找到想找的 function object 不過在這之前要先確保 function 有被 jit ,只要在 function 內寫個很大很沒意義的迴圈: ```javascript= targeted = function(a) { var x = 0; for(i = 0;i < 10000;i++) { x = 0xaabbccdd ^ a; } return x; } ``` function 就會指到 jit code 上了 有了 function 位置後再往該地方放上 shellcode ,最後呼叫該 function 就能順利跳到 shellcode 上 shellcode 的部份 sean 最後做了一個 reverse shell ,我也依樣畫葫蘆弄一個 shellcode 出來: 總結步驟: 1. 利用漏洞修改 offset 2. 修改掉 Vector header 的 size 3. 找到另一個 Object 地址和偏移,根據地址扣掉偏移得到自身 Vector 地址 4. 根據修改 Vector header 的 offset 達到任意地址讀寫 5. 弄出一個會被放到 jit page 的 function object 6. 將 function obj 和特定 pattern 的標記放入 Array 中 7. 掃描記憶體看有沒有找到 8. 根據 function obj 爬到 jit page 中 9. 修改 machine code 為 shellcode 10. 呼叫 function object ## 問題 因為該題目的 js exploit 基本上只能用 alert() 大法 debug ,所以寫起來很痛苦...,以下是我發現的幾個不同: 1. ~~reload 採用 document.location = window.location.href~~ 沒差 2. ~~output function 採用的 document.getElementById('dd').innerHTML 用 div ,而我用 pre 會出錯~~ 3. 不能用 let 4. B vector 一定要超過 4 個 element