網頁掛馬常見漏洞分析與檢測
一、cve-2018-8373漏洞的初步分析
2018年8月15日,趨勢科技披露了他們發現的一起瀏覽器漏洞攻擊事件。在檢測到的攻擊流量中,攻擊者使用了cve-2018-8373這個漏洞來攻擊IE瀏覽器,訪問存在該漏洞的頁面可能導致遠端程式碼執行。有經驗的研究員應該瞭解,在大部分的EK工具包中,針對IE瀏覽器的漏洞經常被用來做網頁掛馬,國外通常稱為“Drive-By-Download”攻擊。加上該漏洞使用的混淆方式和之前披露的cve-2018-8174類似,漏洞利用程式碼估計是同一批人所編寫的。而趨勢科技在報告中提到他們通過啟發式分析檢測到了該流量,目前來說主流的瀏覽器漏洞檢測方式分為靜態和動態兩類,靜態檢測是匹配其觸發漏洞以及漏洞利用的關鍵程式碼特徵,動態檢測則是檢測瀏覽器的敏感行為,當然也有利用機器學習對惡意網頁進行分類、檢測的技術,不再詳述。接下來先簡單分析下cve-2018-8373。
由於沒有拿到攻擊樣本,只能依據簡單的poc來進行分析、復現。poc的程式碼如下:
接下來我們有兩種除錯的方式,第一種是利用windbg載入IE瀏覽器,並通過開啟頁堆來跟蹤漏洞的觸發點。第二種是將其中的vbscript程式碼提取出來,寫入到vbs指令碼檔案中,利用wscript.exe程式載入vbs指令碼,再進行分析。由於該demo並沒有使用到document相關的api,加上使用wscript.exe除錯起來相對簡單點,所以這裡使用第二種方法進行除錯,接下來通過除錯來看下poc中對應的二進位制程式的程式碼實現。在win環境下,由vbscript.dll對vbs指令碼進行解釋執行,通過ida開啟vbscript.dll並且載入符號,可以看到一些意義性較強的函式,大致能根據這些函式名稱來定位具體的vbs指令碼中的程式碼。
在poc中,將MyClass通過new進行建立並賦值給指定變數cls,該操作首先會觸發類的建立以及初始化,建立類的函式由vbscript!VBScriptClass::Create函式完成,在初始化類的各項屬性後,將該class封裝為一個variant變數,class的建立如下:
最後會將該class封裝成一個variant型別的變數
在微軟的官網以及windows kits的標頭檔案中均可以查到variant types的定義,部分如下:
針對不同型別的variant變數解釋如下:
建立類成功後則會呼叫vbscript!VBScriptClass::InitializeClass函式對class的內容進行初始化 :
在vbscript!VAR::IsFunction函式中獲取class指標
隨後呼叫class的虛擬函式vbscript!CScriptEntryPoint::Call 進行初始化,最終的呼叫棧如下:
除錯過該vbs漏洞的研究人員應該瞭解,vbscript!CScriptRunTime::RunNoEH負責對編譯後的vbs程式碼進行解釋執行。這裡執行類的初始化操作,主要包含了array陣列的定義以及Class_Initialize函式的執行。vbscript中建立陣列的函式為vbscript!MakeArray,如下:
其中引數a1代表傳入的陣列的維度個數,由於poc中定義的陣列維度為空,因此a1為0,因此直接返回0,array陣列並沒有構造成功。
隨後在函式Initialize_Class中,利用Redim函式將array陣列重新定義為一個數組長度為3的一維陣列。但由於上次建立陣列失敗,其實這裡並沒有如同趨勢科技中的報告所說,呼叫vbscript!RedimPreserveArray函式來對陣列進行重新分配,而是再次呼叫了vbscript!MakeArray這個函式建立了一個數組。在除錯過程中分別對vbscript!MakeArray以及vbscript!VBScriptClass::InitializeClass下斷點,會發現先命中MakeArray後命中InitializeClass,緊接著再次命中MakeArray。
vbscript中的陣列型別為tagSafeArray,結構如下:
記憶體中的結構如下:
看接下來的poc:
該段程式碼執行時會先後呼叫vbscript!AccessArray、vbscript!AssignVar函式來獲取cls.array(2)的具體地址。在AccessArray函式會對cls陣列進行檢測、訪問並獲取cls第二個陣列的地址,隨後賦值給第二個引數,並在vbscript!AssignVar函式作為引數傳入,在AssignVar函式中會將該地址賦值給esi,並在函式退出前將資料複製到該地址的16個位元組。漏洞的觸發點就在這裡,函式在開始就確認了需要進行賦值操作的資料地址,但是在獲取源運算元(這裡指cls類)時,發生了對被賦值地址的釋放操作,並且也沒有對該地址進行檢查,最終導致了釋放後重用。在獲取源運算元時,會將cls類的variant變數傳入並進行檢測,當檢測到源運算元型別為0x9時,會呼叫vbscript!VAR::InvokeByDispID函式來獲取cls的對應值。在一般情況下,是無法將一個類的地址資訊傳遞給一個數組的,vbscript並不支援這麼做,會報如下錯誤:
但是由於MyClass定義了Public Default Property Get P,因此實際上會將P的值作為cls的返回值返回給目標,類似的程式碼在cve-2018-8174中同樣被使用,因此在考慮檢測漏洞觸發程式碼時可以將它作為關鍵的檢測因素。
在vbscript!assignvar函式中對cls訪問時會呼叫到如下函式,並將P的值返回給棧上的第4個引數。
接下來會呼叫下面的程式碼:
該段程式碼會觸發vbscript!RedimPreserveArray函式來對array陣列進行重新賦值,程式碼如下:
主要的實現在oleaut32!SafeArrayRedim函式中。當申請的陣列的長度超過原有的陣列長度,會申請新的記憶體來覆蓋原有的safearray中的pvdata指標,並修改safearray的rgsabound區。整個呼叫堆疊如下:
隨後通過vbscript!AssignVar函式獲取P的資料,並返回給指定引數,可以看出其呼叫堆疊是一致的。
當從InvokeByDispID返回到AssignVar時,會將P的值拷貝到原來的cls.array(2)地址處。
但此時該處的地址已經被釋放,
這就造成了一個uaf漏洞。該漏洞的利用方式是可以向被釋放的記憶體寫入指定資料,即對應陣列pvdata+陣列偏移+0x8的位置,剛好該釋放的堆塊的大小為0x30,而一個二維的tagSafeArray格式的陣列的大小同樣為0x30,趨勢科技的分析報告中的樣本通過構造大量的二維陣列以佔用被釋放的記憶體,而本身二維陣列的長度是由兩個維度長度的乘積決定的,而該通過該漏洞則可以偽造二維陣列中第一維度的長度為任意值。這樣有了一個偽造的超長二維陣列,由於堆塊頭部的存在,導致後續申請的二維陣列存在8位元組的錯位,導致可以修改偽造的二維陣列的頭4個位元組,這樣相當於可以控制一個variant變數的型別以及具體資料,隨後將偽造的長度為0x7ffffff的一維tagSafeArray陣列通過字串的形式寫入,並更改其變數型別為VT_ARRAY|VT_VARIANT,可以偽造一個首地址為0,長度為0x7ffffff的一維陣列,通過該陣列實現全域性讀寫,詳細的利用方式可以參考看雪論壇上的兩篇文章。
二、漏洞觸發流程的思考以及漏洞檢測
思考本次漏洞觸發的核心流程,關鍵就在於vbscript中array陣列的建立、賦值、重構,本次的漏洞觸發中所利用到的關鍵函式如下圖:
這裡不禁一個疑問,為何需要呼叫兩次makearray來建立陣列,直接在定義陣列時建立一個定長陣列不就省下了一部分漏洞觸發的程式碼嗎?帶著這樣的疑問,重新修改了poc,並刪除了Initialize_Class函式的內容,如下:
結果返回一個執行時錯誤:
為了瞭解該錯誤的原因,對array陣列的建立進行了跟蹤除錯,在poc中,陣列的名稱為array,在除錯過程中發現通過vbscript!VbscriptClass::CreateVar函式建立了名為array的變數:
隨後呼叫vbscript!MakeArray建立了array的tagsafearray結構以及對應的陣列空間,並將結構的地址寫入到array變數+0xc的偏移處,程式碼如下:
最終的結果如下圖:
在呼叫vbscript!RedimPreserveArray過程中,會對傳入的陣列型別進行檢測,具體程式碼如下:
當檢測到array陣列的feature屬性&0x10的結果為1時,反回0x8002000D錯誤。可以看到通過Dim array(2)定義的tagsafearray結構的feature屬性值為0x892,檢視微軟對feature的解釋如下:
0x892的值為0x880|0x12得來,具體程式碼如下:
當陣列成功建立後,會將其feature屬性值同0x12進行邏輯或,結果就會建立一個靜態陣列,而靜態陣列是不可以被重新修改其陣列大小的,該型別的陣列也就不存在釋放後重用漏洞。而通過Redim array()建立的是一個動態的tagSafeArray結構體陣列,初始化的結果就是array變數+0xc處的資料為0,第一次呼叫Redim函式的時候,會先從vbscript!vbscriptclass::createvar建立的array變數+8的地方獲取到array陣列地址,經過判斷後array陣列的地址為0,最終呼叫了vbscript!MakeArray函式來建立一個新的陣列,具體的程式碼邏輯如下:
這裡同樣思考另外一個問題,在漏洞觸發過程的第二步僅僅是對array陣列的重新建立,是否必須要利用到Initialize_Class該函式來執行,答案當然是非必需的,這裡僅需要讓array在被vbscript!RedimPreserveArray函式呼叫前具有長度為3的陣列即可,因此可以構造如下的利用demo,也可以實現相同的效果。
這樣在考慮對該漏洞進行靜態檢測時,在不考慮樣本被混淆、加密的情況下,首先應該是對Public Default Property Get以及Redim、Redim Preserve等關鍵字進行檢測,至於Initialize_Class關鍵字則不是必要檢測欄位,如果做動態檢測,則可以對如vbscript!MakeArray、vbscript!RedimPreserveArray、vbscript!AssignVar等函式hook,對陣列的建立、重新分配、賦值等操作做檢測。當然這裡僅僅是對漏洞觸發的部分程式碼進行檢測,其實針對漏洞利用的程式碼如heap spary、rop、RW物件等進行檢測會更加有效。