作者:bybye@知道創宇404實驗室
時間:2020年7月24日

漏洞背景

WalletService 服務是 windows 上用來持有錢包客戶端所使用的對象的一個服務,只存在 windows 10 中。

CVE-2020-1362 是 WalletService 在處理 CustomProperty 對象的過程中出現了越界讀寫,此漏洞可以導致攻擊者獲得管理員權限,漏洞評級為高危。

微軟在 2020 年 7 月更新對漏洞發布補丁。

環境搭建

  1. 復現環境:windows 10 專業版 1909 (內部版本號 18363.815)
  2. 設置 WalletService 服務啟動類型為自動

    image-20200724115418829

  3. 調試環境:windbg -psn WalletService 即可。

漏洞原理與分析

漏洞點是設置 CustomProperty 對象的 Group 的 get 方法和 set 方法沒有檢查邊界。

  1. get 方法的 a2 參數沒有檢查邊界導致可以泄露堆上的一些地址。 enter description here

  2. set 方法的 a2 參數沒有檢查邊界,可以覆蓋到對象的虛表指針,從而控制程序流。 enter description here

漏洞利用過程

創建 CustomProperty 對象

WalletService 服務由 WalletService.dll 提供,WalletService.dll 實際上是一個動態鏈接庫形式的 Com 組件,由 svchost.exe 加載。我們可以在自己寫的程序(下面稱為客戶端)中使用 CoCreateInstance() 或者 CoGetClassObject() 等函數來創建對象,通過調用獲得的對象的類方法來使用服務提供的功能。

如何創建出漏洞函數對應的對象呢?最簡單的辦法是下載 msdn 的符號表,然后看函數名。

我們想要創建出 CustomProperty 對象,ida 搜索一下,發現有兩個創建該對象的函數:Wallet::WalletItem::CreateCustomProperty() 和 Wallet::WalletXItem::CreateCustomProperty()。

img

所以我們創建一個 CustomProperty 需要一個 WalletXItem 對象或者 WalletItem 對象,那么使用哪個呢?繼續用 ida 搜索 CreateWalletItem 或者 CreateWalletXItem,會發現只有 CreateWalletItem。

img

那到這里我們需要一個 WalletX 對象,繼續用 ida 搜索會發現找不到 CreateWalletX,但是如果搜索 WalletX,會發現有個 WalletXFactory::CreateInstance(),如果有過 Com 組件開發經驗的同學就會知道,這個是個工廠類創建接口類的函數,上面提到的 CoCreateInstance() 函數會使 WalletService 調用這個函數來創建出接口類返回給客戶端。

img

那么如何調用 WalletXFactory::CreateInstance() 并創建出 WalletX 對象呢?我們需要在客戶端使用 CoCreateInstance() 。

HRESULT CoCreateInstance(
    REFCLSID rclsid, // CLSID,用于找到工廠類
    LPUNKNOWN pUnkOuter, // 設置為 NULL 即可
    DWORD dwClsContext, // 設置為 CLSCTX_LOCAL_SERVER,一個宏
    REFIID riid, // IID, 提供給工程類,用于創建接口類實例
    LPVOID *ppv // 接口類實例指針的地址
);
  1. 首先,我們需要 WalletXFactory 的 CLSID,可以使用 OLEViewDotNet 這個工具查看。

    img

  2. 其次,我們需要一個 WalletX 的 IID,這個可以用 ida 直接看 WalletXFactory::CreateInstance() 這個函數。

    img

有了 WalletXFactory 的 CLSID 和 WalletX 的 IID,然后在客戶端調用 CoCreateInstance(),WalletService 就會調用 CLSID 對應的工廠類 WalletXFactory 的 CreateInstance(), 創建出 IID 對應的 WalletX 對象,并返回對象給客戶端。

然后按照上面的分析,使用 WalletX::CreateWalletItem() 創建出 WalletItem 對象,然后使用 WalletItem::CreateCustomProperty() 創建出 CustomProperty 對象。

對于上面的步驟有疑問的同學可以去學一學 Com 組件開發,尤其是進程外組件開發。

偽造虛表,覆蓋附表指針

由于同一個動態庫,在不同的進程,它的加載基址也是一樣的,我們可以知道所有dll里面的函數的地址,所以可以獲得偽造的虛表里面的函數地址。

那么把虛表放哪里呢?直接想到的是放堆上。

但如果我們繼續分析,會發現,CustomProperty 類里面有一個 string 對象,并且可以使用 CustomProperty::SetLabel() 對 string 類進行修改,所以,我們可以通過修改 string 類里面的 beg 指針 和 end 指針,然后調用 CustomProperty::SetLabel() 做到任意地址寫。

img

有了任意地址寫,我們選擇把虛表放在 WalletService.dll 的 .data 節區,以避免放在堆上可能破壞堆上的數據導致程序崩潰。

控制程序流到 LoadLibrary 函數

使用偽造 vtable 并覆蓋虛表指針的辦法,我們可以通過調用虛函數控制 WalletService 的程序流到任意地址了。

那么怎么提權呢?在 windows 服務提權中,通常的辦法是把程序流控制到可以執行 LoadLibrary() 等函數來加載一個由我們自己編寫的動態鏈接庫,因為在加載 dll 的時候會執行 dll 里面的 DllMain(),這個方法是最強大的也是最實用的。

這里使用漏洞提交者的方法,把虛表的某個地址覆蓋成 dxgi.dll 里面的 ATL::CComObject\::`vector deleting destructor(),因為這個函數調用的 LoadLibraryExW() 會使用一個全局變量作為想要加載的 dll 的路徑。

img

我們可以通過上面的 SetLabel() 進行任意地址寫,修改上圖的全局變量 Src,使其指向我們自己實現的動態鏈接庫的路徑,然后調用對應的虛表函數,使程序流執行到 LoadLibrarExW() 即可。

實現一個動態鏈接庫

在 DllMain() 里面寫上我們希望以高權限執行代碼,然后調用虛表里面對應的函數是 WalletService 的程序流運行到 LoadLibraryEx() 即可。

注意,因為 windows 服務運行在后臺,所以需要在 DllMain() 里面使用命名管道或者 socket 等技術來進行回顯或者交互,其次由于執行的是 LoadLibraryExW(),所以這里的 dll 路徑要使用寬字符。

其它

在控制虛表函數程序流到 LoadLibraryExW() 時,需要繞過下面兩個 check。

第一個是需要設置 this+0x80 這個地址的值,使得下面的 and 操作為 true。

image-20200724111317787

第二個是要調整 qword_C5E88 和 qword_C5E80 是下面的變量 v4 指向具有寫權限的內存。

image-20200724111819875

漏洞利用結果

可以獲得管理員權限

image-20200724121314068

補丁前后對比

可以看到,打了補丁之后,get 方法和 set 方法都對 a2 參數添加了邊界檢測。

img

img

參考鏈接

[1] PoC鏈接
[2] 微軟更新公告
[3] nvd漏洞評級


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1276/