深入分析一個Pwn2Own的優質Webkit漏洞

  • 2019 年 12 月 11 日
  • 筆記

今年的Pwn2Own比賽剛剛結束,在Pwn2Own溫哥華站的比賽中,Fluoroacetate團隊所使用的一個WebKit漏洞成功吸引了我的注意。這個漏洞是一個價值五萬五千美金的漏洞利用鏈的一部分,在這篇文章中,我將會對這個漏洞進行深入分析,並對漏洞進行驗證和研究。

當然了,在開始深入分析之前,我們先把該漏洞的概念驗證PoC提供給大家:

首先,我們需要對受該漏洞影響的WebKit版本進行編譯,即Safari v12.0.3版本,根據蘋果的版本發布資訊,該版本對應的是修訂版v240322。

svn checkout -r 240322 https://svn.webkit.org/repository/webkit/trunk webkit_ga_asan

我們可以使用AddressSanitizer(ASAN)來完成編譯操作,它可以允許我們在發生記憶體崩潰的時候第一時間檢測到錯誤資訊。

ZDIs-Mac:webkit_ga_asan zdi$ Tools/Scripts/set-webkit-configuration --asan      ZDIs-Mac:webkit_ga_asan zdi$ Tools/Scripts/build-webkit # --jsc-only can be used here which should be enough

我們將使用11db來進行調試,因為macOS本身就自帶了這個工具。由於PoC中沒有包含任何的呈現程式碼,因此我們需要在11db中使用JavaScriptCore(JSC)來執行它。為了在11db中執行jsc,我們需要調用它的二進位程式碼文件,而不是之前的腳本run-jsc。這個文件可以從 WebKitBuild/Release/jsc路徑獲取,並且需要正確設置環境變數。

env DYLD_FRAMEWORK_PATH=/Users/zdi/webkit_ga_asan/WebKitBuild/Release

我們可以在11db中運行這條命令,或者把它放在一個文本文件中,然後傳遞到11db -s中。

ZDIs-Mac:webkit_ga_asan zdi$ cat lldb_cmds.txt    env     DYLD_FRAMEWORK_PATH=/Users/zdi/webkit_ga_asan/WebKitBuild/Release    r

好的,接下來,我們可以開始調試了:

我們可以看到,程式碼在0x6400042d1d29處發生了崩潰:mov qword ptr [rcx + 8*rsi], r8,經分析後我們確認為越界寫入所導致的記憶體崩潰。棧追蹤分析顯示,它發生在虛擬機環境中,也就是編譯過程或者JITed程式碼出了問題。我們還注意到的rsi索引,它包含了0x20000040,這個數字我們在PoC中是有見過的。

這個數字是bigarr! 的大小,即NUM_SPREAD_ARGS * sizeof(a)。為了查看JITed程式碼,我們可以設置JSC_dumpDFGDisassembly環境變數,這樣jsc就可以跳轉到DFG和FTL的編譯程式碼了。

ZDIs-Mac:webkit_ga_asan zdi$ JSC_dumpDFGDisassembly=true lldb -s lldb_cmds.txt WebKitBuild/Release/jsc ~/poc3.js

這將丟棄掉大量無關的程式碼集,那我們應該如何確定相關程式碼呢?

我們知道崩潰事件發生在0x6400042d1d29處:mov qword ptr [rcx + 8*rsi], r8。那我們為何不嘗試搜索這個地址呢?

沒錯,我們在DFG中找到了:

程式碼在使用DFG JIT的分布操作符來創建一個新數組時,調用了NewArrayWithSpread方法,整個行為發生在gen_func生成的一個函數f中,調用行為發生在一個循環中。

在對源程式碼進行分析後,我們發現了Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp文件中的SpeculativeJIT::compileNewArrayWithSpread函數。這是DFG程式碼的起始位置,啟動程式碼意味著將JIT生成的機器程式碼寫入記憶體以供以後執行。

我們可以通過查看compileNewArrayWithSpread方法來理解其中的機器程式碼。我們看到compileAllocateNewArrayWithSize()負責分配具有特定大小的新數組,它的第三個參數sizeGPR將作為第二個參數傳遞給emitAllocateButterfly(),這意味著它將為數組分配一個新的butterfly(包含JS對象值的記憶體空間)。如果您不熟悉JSObject的butterfly,可以點擊https://liveoverflow.com/the-butterfly-of-jsobject-browser-0x02/了解更多資訊。

跳轉到EnITalListAtButoFuffE(),我們看到大小參數siZeGPR向左移動3位(乘以8),然後添加到常數sieof(IndexingHeader)。

方便起見,我們需要將實際的機器程式碼與我們在這個函數中的C++程式碼相匹配。m_jit欄位的類型是JITCompiler。

JITCompiler負責根據數據流圖生成JIT程式碼。它通過委託給jit來實現,後者生成一個宏彙編程式(JITCompiler通過繼承關係擁有該程式)。JITCompiler保存編譯期間所需資訊的引用,並記錄鏈接中使用的資訊(例如,要鏈接的所有調用的列表)。

這意味著我們看到的調用,如m_jit.move()、m_jit.add32()等,是發出程式集的函數。通過跟蹤每一個函數,我們將能夠將其與C++對應的組件匹配。除了跟蹤記憶體分配的malloc調試功能外,我們還根據自己對Intel程式集的偏好配置11db。

     ZDIs-Mac:~ zdi$ cat ~/.lldbinit          settings set target.x86-disassembly-flavor intel          type format add --format hex long          type format add --format hex "unsigned long"          command script import lldb.macosx.heap          settings set target.env-vars          DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib          settings set target.env-vars MallocStackLogging=1          settings set target.env-vars MallocScribble=1

由於在啟用Guard Malloc的情況下正在分配大容量,我們需要設置另一個允許此類分配的環境變數。

ZDIs-Mac:webkit_ga_asan zdi$ cat lldb_cmds.txt          env DYLD_FRAMEWORK_PATH=/Users/zdi/webkit_ga_asan/WebKitBuild/Release env MALLOC_PERMIT_INSANE_REQUESTS=1          r

JSC_dumpDFGDisassembly將以AT&T格式轉儲程式集,因此我們運行deassembly-s 0x6400042d1c22-c 70可以獲得英特爾風格的程式集,結果如下:

讓我們嘗試匹配emitAllocateButterfly()中的一些程式碼。查看程式集列表,我們可以匹配以下內容:

接下來分析機器程式碼,此時需要設置斷點。為此,我們在編譯之前向jsc.cpp添加了一個dbg()函數。這將有助於在我們需要的時候進入JS程式碼。編譯器報錯顯示未使用EncodedJSValue JSC_HOST_CALL functionDbg(ExecState*exec)函數中的exec,因此失敗。為了解決這個問題,我們只添加了exec->argumentCount();這不會影響執行。

讓我們在這裡添加dbg(),因為實際的NewArrayWithSpread函數將在創建bigarr期間執行。

再次JSC_dumpDFGDisassembly=true lldb -s lldb_cmds.txt WebKitBuild/Release/jsc ~/poc3.js運行將會導出編譯程式碼:

在bigarr創建之前中斷,您可以看到NewArrayWithSpread的機器程式碼。讓我們在函數的開始處放置一個斷點並繼續執行。

斷點生效:

接下來,我們需要仔細分析斷點資訊:

那麼這裡到底發生了什麼?還記得PoC中的下面這部分資訊嗎?

mk_arr函數創建一個數組,第一個參數作為大小,第二個參數作為元素。大小為(0x20000000+0x40)/8=0x4000008,這將創建一個大小為0x4000008、元素值為0x41414141410000的數組。i2f函數用於將整數轉換為浮點值,以便最終在記憶體中得到預期值。

我們現在知道rcx指向對象a的butterfly-0x10,因為它的大小是rcx+8,這使得butterfly rcx+0x10。在這段程式碼的其餘部分中,我們看到r8、r10、rdi、r9、rbx、r12和r13都指向對象a的一個副本-具體來說是八個副本,edx不斷地添加每個副本的大小。

此時,edx的值變成了0x20000040:

那麼這八個a拷貝到了哪裡呢?值0x20000040代表的又是什麼呢?

重新看看PoC:

這意味f變成了:

f通過擴展NUM_SPREAD_ARGS(8)第一個參數的副本和第二個參數的單個副本來創建數組。用對象a(8*0x04000008)和c(長度1)調用f。當NewArrayWithSpread被調用時,它為8個a和1個c騰出了空間。

最後一步到顯示對象c的長度,這使得最終的edx值為0x20000041。

下一步應該是長度的分配,它發生在emitAllocateButterfly()中。

我們注意到shl r8d,0x3的溢出,其中0x20000041被封裝到了0x208。當分配大小傳遞給emitAllocatevariableSize()時,它變為了0x210。

我們看到的越界讀取訪問衝突發生在mov qword ptr[rcx+8*rsi],r8的以下程式碼片段中。這個程式碼片段的問題是用錯誤的大小0x20000041反向迭代新創建的butterfly,而溢出後的實際大小是0x210。然後,它將每個元素歸零,但由於記憶體中的實際大小遠小於0x20000041,因此在ASAN構建中發生了了越界訪問衝突。

下面給出的是整個越界訪問行為的流程圖:

總結

在這篇文章中,我們對WebKit版本v240322中的一個越界訪問漏洞進行了深入分析,這個漏洞是一個價值五萬五千美金的漏洞利用鏈中的一部分。Pwn2Own上出現的漏洞往往都是行業內較為優質的漏洞,而本文所分析的這個漏洞也不例外。在日常的漏洞研究過程中,我也希望大家能夠學會使用11db,如果大家有更多關於該漏洞的想法,可以直接在我的推特上艾特我(@ziadrb)。希望本文能夠給大家提供幫助!

*參考來源:thezdi,FB小編Alpha_h4ck編譯,轉載請註明來自FreeBuf.COM