作者:zoemurmure
原文鏈接:https://www.zoemurmure.top/posts/cve_2023_21752_1/

0. 前言

CVE-2023-21752 是 2023 年開年微軟第一個有 exploit 的漏洞,原本以為有利用代碼會很好分析,但是結果花費了很長時間,難點主要了兩個:漏洞點定位和漏洞利用代碼分析,因此在本文中花費了更多的篇幅介紹了這兩部分內容,歡迎指正。

1. 漏洞簡介

根據官方信息,該漏洞是 Windows Backup Service 中的權限提升漏洞,經過身份認證的攻擊者可利用此漏洞提升至 SYSTEM 權限。成功利用此漏洞需要攻擊者贏得競爭條件。

EXP 代碼位于 Github,提供了兩個版本,版本 1 可以實現任意文件刪除,可穩定復現;版本 2 嘗試利用任意刪除實現本地提權,但是復現不穩定。

2. 漏洞點定位的曲折之路

這部分內容是一些失敗的過程記錄,以及我自己的碎碎念,防止自己之后犯同樣的錯誤,只對漏洞分析感興趣的可以略過 2.1 和 2.3 小節。

2.1 失敗的過程

首先嘗試復現,提權版本的利用程序在虛擬機上沒有復現成功,任意文件刪除版本的利用程序由于我沒有使用完整路徑,也沒有復現成功(但是此時我還不知道原因)。

之后我嘗試進行補丁對比,但是想要進行補丁對比首先要確定漏洞位于哪個文件中,根據漏洞利用程序的文件命名 SDRsvcEop,找到了文件 sdrsvc.dll,但是補丁對比后并沒有發現差異。

這期間我還搜索了關于這個漏洞的信息,但是除了漏洞通告和 GitHub 的 exp 代碼外,沒有找到其他內容。這里吐槽一下某數字站的漏洞通告,竟然說這個漏洞是 Windows Server Backup (WSB) 上的……

這個時候我已經開始對漏洞利用代碼進行分析了,一方面通過微軟的文檔,了解代碼中一些函數和參數的使用,一方面開始在 Windbg 上進行調試,并由此找到了 rpcrt4.dll、combase.dll 這些和漏洞無關的文件。

在調試過程中,我花費了很多時間在 DeviceIoControl 這個函數上,因為之前看的很多漏洞最終定位的文件都是 sys 驅動文件,因此雖然我在心里仍舊為 dll 文件留了一些位置,但是在方法選擇上,我仍舊趨向去尋找某個 sys 文件。所以我想要在利用程序執行到 DeviceIoControl 的時候,在用戶態轉內核態的入口位置設置一個斷點,然后監控系統究竟執行到了哪里。當然,這個方法失敗了,并且搞得我心煩意亂。

我在這個時候才想起來要把利用程序參數的相對路徑改成絕對路徑,并且成功復現了任意文件刪除。雖然但是,這之后我又走了彎路……

之前學習病毒分析的時候,有一個算是標準的流程,就是要先執行病毒,看一下它的動態特征,以此方便后面的動態分析。之前看的很多漏洞分析文章,也都是要執行一下 poc 或者 exp,進行進程監控,但是我完全忘記了,或者說我雖然想到了這個方法,但是并沒有十分重視。

我在繼續分析漏洞利用代碼,在此期間看了一些關于 DCOM 的資料,確定 sdrsvc.dll 是依賴 rpcss.dll 文件功能的(明明可以通過 process hacker 直接確定的……),通過補丁對比發現函數 CServerSet::RemoveObject 被修改,我嘗試在 Windbg 中在這個函數設置斷點,但是利用程序沒有執行到這里,所以漏洞點不在這個文件。

其中這個時候我的潛意識已經告訴我我的路走錯了,因為微軟的漏洞說明上明明白白的寫著 Windows backup service,所以漏洞文件肯定是和這個功能直接相關的文件,要不然它就直接寫 RPC 漏洞了。

2.2 轉入正軌

此時我仍舊沒有使用 procmon 對利用程序進行監控,我選擇在安裝補丁前后的系統上執行利用程序,并檢查輸出(輸出內容做了一些修改),得到以下結果(因為只是測試功能,并沒有選擇對高權限文件進行刪除):

補丁修復前

PS C:\Users\exp\Desktop> C:\Users\exp\Desktop\SDRsvcEop.exe C:\Users\exp\Desktop\test.txt
[wmain] Directory: C:\users\exp\appdata\local\temp\23980418-9164-497e-8ce7-930949d1af55
[Trigger] Path: \\127.0.0.1\c$\Users\exp\AppData\Local\Temp\23980418-9164-497e-8ce7-930949d1af55
[FindFile] Catch FILE_ACTION_ADDED of C:\users\exp\appdata\local\temp\23980418-9164-497e-8ce7-930949d1af55\SDT2C35.tmp
[FindFile] Start to CreateLock...
[cb] Oplock!
[CreateJunction] Junction \\?\C:\Users\exp\AppData\Local\Temp\23980418-9164-497e-8ce7-930949d1af55 -> \RPC Control created!
[DosDeviceSymLink] Symlink Global\GLOBALROOT\RPC Control\SDT2C35.tmp -> \??\C:\Users\exp\Desktop\test.txt created!
[Trigger] Finish sdc->proc7
[wmain] Exploit successful!
[DeleteJunction] Junction \\?\C:\Users\exp\AppData\Local\Temp\23980418-9164-497e-8ce7-930949d1af55 deleted!
[DelDosDeviceSymLink] Symlink Global\GLOBALROOT\RPC Control\SDT2C35.tmp -> \??\C:\Users\exp\Desktop\test.txt deleted!

補丁修復后

PS C:\Users\exp\Desktop> C:\Users\exp\Desktop\SDRsvcEop.exe C:\Users\exp\Desktop\test.txt
[wmain] Directory: C:\users\exp\appdata\local\temp\183c772e-f444-4aec-a489-7d9f734ee719
[Trigger] Path: \\127.0.0.1\c$\Users\exp\AppData\Local\Temp\183c772e-f444-4aec-a489-7d9f734ee719
[FindFile] Catch FILE_ACTION_ADDED of C:\users\exp\appdata\local\temp\183c772e-f444-4aec-a489-7d9f734ee719\SDT1F8A.tmp
[Trigger] Finish sdc->proc7
_

由此可知修復后利用程序無法再獲取一個 tmp 文件的句柄,我猜測應該是補丁修復之前,漏洞文件創建了這個 tmp 文件,并且創建的權限有問題(這個猜測不一定準確),但是這個猜測目前沒什么用,還是沒辦法定位漏洞文件。

然后,幾乎走投無路的我終于想起來要用 procmon 了,謝天謝地。

根據上面利用程序輸出結果的對比,確定漏洞修復的位置和創建的 tmp 文件有關,因此格外注意 procmon 中該文件的創建操作:

并在 Stack 選項卡中,定位到 sdrsvc.dll 調用的功能位于 sdengin2.dll 中:

根據 SdCheck + 0x490c2,在 IDA 中定位到函數 CSdCommonImpl::QueryStorageDevice,該地址為這個函數調用 QueryStorageDevice 的位置。

經過補丁對比,發現了函數 IsWritable,這個函數進行了修改,并且被 QueryStorageDevice 所調用。

2.3 反思

這次漏洞分析遇到了幾個障礙:

  1. 無法通過漏洞名稱直接確認漏洞文件,導致無法使用常用的補丁對比的分析方法;
  2. exp 一開始未成功復現,這種情況對我來說很常見,但是由于不清楚原因,我以為是對備份服務的功能以及 exp 代碼不熟悉導致;
  3. 由于不熟悉利用代碼:
  4. 花費很多時間查找相關資料;
  5. 需要對輔助功能代碼和直接漏洞利用代碼進行區分。

除此之外,我之前極少分析帶有 exp 且 exp 可以正常復現的漏洞,習慣從靜態分析入手,再使用 windbg 動態輔助分析。一般遇到可以使用的 poc,我也是直接觸發崩潰,然后使用 windbg 從崩潰開始進行調試分析,從沒有使用過 procmon 進行動態監控,并且這樣的方法也都成功對漏洞進行了分析,因此輕視了 procmon 動態監控方法的有效性。

不過 procmon 也不是萬能的,目前看來,這個方法在漏洞點定位上十分有效,但是如果通過其他信息已經能夠對漏洞點進行定位,那么 procmon 提供的幫助就不那么顯著了,而且通過其他方法也能夠完成漏洞分析。

3. 漏洞原理

3.1 補丁對比

漏洞修復前:

__int64 __fastcall IsWritable(unsigned __int16 *a1, int a2, int *a3)
{
  ...
  v7 = -1;
  if ( a2 == 7 )
  {
    if ( !GetTempFileNameW(a1, L"SDT", 0, TempFileName) )// 如果獲取 temp 文件名失敗,進入 if 語句
    {
      rtnValue = v17;
LABEL_28:
      *a3 = v6;
toend2:
      if ( v7 != -1 )
      {
        CloseHandle(v7);
        rtnValue = v17;
      }
      goto end;
    }
    rtnValue = SxDeleteFile(TempFileName);      // 刪除之前可能存在的 tmp 文件
    v17 = rtnValue;
    v8 = 0x148;
    if ( rtnValue >= 0 )                        // 刪除成功
    {
      v18 = 0x148;
LABEL_27:
      v6 = 1;
      goto LABEL_28;
    }
toend:
    v19 = v8;
    goto end;
  }
  ...
}

上述代碼中的 a2 和傳入的路徑類型有關,由于利用程序傳入的是 UNC 路徑,因此最終程序執行流程到達此處。

根據 GetTempFileNameW 函數的文檔說明,當其第三個參數為數值 0 時,該函數會嘗試使用系統時間生成一個唯一數字文件名,如果該文件已存在,數字遞增直至文件名唯一,在這種情況下,該函數會創建一個該文件名的空文件并釋放其句柄。因此在漏洞修復之前,系統通過 GetTempFileNameW 創建臨時文件是否成功的方式檢查傳入的 unc 路徑是否可以寫入,如果可以寫入,再刪除創建的這個臨時文件。

漏洞修復后:

__int64 __fastcall IsWritable(unsigned __int16 *a1, int a2, int *a3)
{
  ...
  v7 = -1;
  if ( a2 == 7 )
  {
    if ( CheckDevicePathIsWritable(a1) < 0 )
    {
LABEL_10:
      rtnValue = v16;
      *a3 = v6;
toend2:
      if ( v7 != -1 )
      {
        CloseHandle(v7);
        rtnValue = v16;
      }
      goto end;
    }
LABEL_9:
    v6 = 1;
    goto LABEL_10;
  }

漏洞修復后,原本 GetTempFileNameW 函數的位置變成了 CheckDevicePathIsWritableGetTempFileNameW 函數的實現位于 kernelbase.dll 文件中,如果你仔細對比,會發現這兩個函數中的大部分代碼相同,只有一處差異點需要注意,就是在創建臨時文件的時候,兩者的代碼如下:

// GetTempFileNameW
v24 = CreateFileW(lpTempFileName, GENERIC_READ, 0, 0i64, 1u, 0x80u, 0i64);

// CheckDevicePathIsWritable
v22 = CreateFileW(FileName, GENERIC_READ, 0, 0i64, 1u, 0x4000080u, 0i64);

可以看到在 CheckDevicePathIsWritable 函數中,CreateFileW 函數的第六個參數 dwFlagsAndAttributes 數值由 0x80 變成了 0x4000080,即從 FILE_ATTRIBUTE_NORMAL 變成了 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE

根據文檔說明,FILE_FLAG_DELETE_ON_CLOSE 表示文件會在所有句柄關閉時直接刪除,并且之后打開該文件的請求必須包含 FILE_SHARE_DELETE 共享模式,否則會失敗。

簡單來說,修復后的代碼將臨時文件的創建和刪除操作整合成為了一個元操作。

3.2 漏洞原理分析

上面補丁對比的結果可以確定這是一個條件競爭漏洞,由于臨時文件的創建操作和刪除操作接次發生,并且在兩個操作之間沒有對文件進行限制,這就導致攻擊者可以創建另一線程,在臨時文件創建之后,刪除之前,獲取文件句柄并創建機會鎖阻止其他線程操作,同時將文件刪除,并設置原文件路徑指向其他文件,當機會鎖釋放后,指向的其他文件就會被刪除。

4. 漏洞利用

4.1 文件刪除漏洞利用代碼流程總結

  1. 在臨時文件夾下,使用 FULL_SHARING 模式創建目錄 dir ,作為上述臨時文件的保存位置;
  2. 創建線程 FindFile,監控 dir 目錄下的文件創建操作:
  3. 獲取創建文件句柄并創建機會鎖;
  4. 將創建的文件移動到其他目錄下;
  5. 創建符號鏈接,將原文件路徑指向要刪除的目標文件;
  6. 釋放機會鎖;
  7. 主線程將目錄 dir 的路徑轉換為 unc 格式,并通過 CoCreateInstance 的方式調用 sdrsvc 服務的 CSdCommonImpl::QueryStorageDevice 接口;
  8. sdrsvc 服務在 unc 格式目錄下創建臨時文件,之后刪除文件。

如下圖所示:

4.2 提權漏洞利用

4.2.1 原理分析

這部分內容基本上看 ZDI 的文章就可以,這里做一下介紹。

簡單來說,從 任意文件刪除 到本地提權,需要與 MSI installer 文件運行過程進行條件競爭。

Windows Installer 服務負責應用程序的安裝,而 msi 文件定義了安裝過程中會發生的變化,例如創建了哪些文件夾、復制了哪些文件、修改了哪些注冊表等等。因為程序安裝過程中會對系統進行修改,為了避免安裝出錯導致系統無法恢復,msi 會在運行時創建文件夾 C:/Config.msi,將安裝過程中做的所有更改記錄到 .rbs 后綴的文件中,同時將被替換的系統文件存儲為 .rbf 格式放入該文件夾。所以如果可以替換其中的 rbf 文件,就能將系統文件替換為任意惡意文件。正是因為有文件替換的風險,所以 C:/Config.msi 及其中的文件默認具有強 DACL。

但是如果攻擊者能做到 任意目錄刪除,就可以將 C:/Config.msi 刪除,重新創建一個弱 DACL 的 C:/Config.msi 目錄,并在 msi 程序創建完 rbs 和 rbf 文件之后,對其進行替換,使用惡意 rbf 文件實現提權。

具體來看,msi 在運行時經歷了 創建->刪除->再創建 的過程,之后才會開始創建 rbs 文件,因此 任意目錄刪除 需要在 再創建 之后,rbs 文件創建之前刪除 C:/Config.msi 目錄,并監控 rbs 文件的產生,對文件進行替換,條件競爭就發生在這里。

上面提到的漏洞是 任意目錄刪除,如果發現的是 任意文件刪除,可以刪除 C:/Config.msi::$INDEX_ALLOCATION 數據流,同樣可以實現目錄的刪除。

利用任意文件刪除漏洞實現提權的流程如下圖所示:

4.2.2 失敗原因分析

上面介紹的流程把 Config.msi 的刪除 當作一個元操作,但在 CVE-2023-21752 這個漏洞中,文件刪除同樣需要條件競爭才能實現,具有相對繁瑣的步驟。也就是說想要利用這個漏洞實現本地提權,需要同時實現贏得兩個條件競爭,這也是我一開始復現總是失敗的原因。

為了方便了解利用代碼的執行流程,我對代碼中的注釋進行了添加和修改,得到了如下的執行結果:

PS C:\Users\exp\Desktop> C:\Users\exp\Desktop\SDRsvcEop.exe
[wmain] Config.msi directory created!
[wmain] Directory: C:\users\exp\appdata\local\temp\3bbbd2cf-7baf-42b7-98ea-242f703b08f8
[wmain] Got handle of uuid directory
[wmain] Finish create oplock for config.msi

[Trigger] Path: \\127.0.0.1\c$\Users\exp\AppData\Local\Temp\3bbbd2cf-7baf-42b7-98ea-242f703b08f8
[FindFile] Found added file C:\users\exp\appdata\local\temp\3bbbd2cf-7baf-42b7-98ea-242f703b08f8\SDT73B.tmp
[FindFile] Got handle of C:\users\exp\appdata\local\temp\3bbbd2cf-7baf-42b7-98ea-242f703b08f8\SDT73B.tmp
[cb] Oplock!
[Move] Finish moving to \??\C:\windows\temp\c5b82788-8133-4971-b351-38f58233ced1
[CreateJunction] Junction \\?\C:\Users\exp\AppData\Local\Temp\3bbbd2cf-7baf-42b7-98ea-242f703b08f8 -> \RPC Control created!
[DosDeviceSymLink] Symlink Global\GLOBALROOT\RPC Control\SDT73B.tmp -> \??\C:\Config.msi::$INDEX_ALLOCATION created!
[FindFile] End

[Move] Finish moving to \??\C:\windows\temp\0f1161f2-a8c5-4798-a71d-f32ebba87125
[install] MSI file: C:\windows\temp\MSI72F.tmp
[install] Start ACTION=INSTALL
[cb1] Detect first create
[cb1] Detect first delete
[install] Start REMOVE=ALL
[install] Start delete msi file

[Fail] Race condtion failed!
[DeleteJunction] Junction \\?\C:\Users\exp\AppData\Local\Temp\3bbbd2cf-7baf-42b7-98ea-242f703b08f8 deleted!
[DelDosDeviceSymLink] Symlink Global\GLOBALROOT\RPC Control\SDT73B.tmp -> \??\C:\Config.msi::$INDEX_ALLOCATION deleted!

我在不同流程的結果之間添加了回車,方便觀察,可以看到在 刪除文件 流程中,程序監控到了臨時文件的生成,也創建了符號鏈接,但是由于符號鏈接將臨時文件鏈接到了 C:/Config.msi::$INDEX_ALLOCATION 上,而 C:/Config.msi 的機會鎖又掌握在 msi 線程上,因此刪除操作停滯了。

于此同時 msi 線程上只監測到了 C:\\Config.msi 的第一次創建和刪除,并沒有監測到第二次創建,因為這里使用了循環對創建行為進行檢測,因此該線程也陷入了無限循環。

與 msi 的條件競爭失敗,導致兩個線程發生死鎖,漏洞利用失敗。

4.2.3 問題解決

首先,我將利用代碼的整個流程畫成了如下的流程圖:

紅框部分就是條件競爭失敗的地點。

我嘗試增加虛擬機的 CPU 數量,對監控 Config.msi 創建的代碼進行優化,但是都沒有成功。同時我也單獨使用 procmon 監控了 msi 文件的運行過程,確定 Config.msi 目錄確實發生了二次創建。

所以結論只有一個,Config.msi 目錄的二次創建發生的太快了。但是既然 Config.msi 目錄的二次創建是確實發生的,同時利用代碼已經監測到了第一次刪除的行為,那么如果這個時候就釋放機會鎖 2,又會如何呢?

如果不對 Config.msi 目錄的二次創建進行監控,直接釋放機會鎖 2,因為 Config.msi 目錄的二次創建時間間隔非常短,等待良久的 sdrsvc 就有機會成功刪除 Config.msi。此時漏洞利用流程可以繼續進行下去,并成功實現漏洞利用!

3.2 符號鏈接的問題

之前我對利用代碼中如何鏈接向待刪除文件存在疑問,實際上這種利用手法來自 James Forshaw,參考鏈接 5 和 6 對其進行了介紹。

重分析點/Junction 是一種 NTFS 文件系統中文件夾的屬性,NTFS 驅動在打開文件夾的時候會對它進行讀取。可以使用它對目錄之間進行鏈接,假設要建立目錄 A 向目錄 B 的重分析點,只要普通用戶對目錄 A 具有可寫權限就能夠進行,而對目錄 B 的權限沒有任何要求。

在 Windows 系統中,我們常提到的 C 盤目錄并不是一個真的文件夾,它實際上是一個指向設備物理地址的符號鏈接對象,你可以使用 WinObj 在 \GLOBAL?? 中看到 C: 項是一個 SymbolicLink。當我們訪問 C 盤中的某個文件時,系統會對訪問路徑進行轉換,轉換成真正的設備物理地址。普通用戶也可以在對象管理器中添加或刪除符號鏈接,但是該行為只能在有限的目錄下進行,例如 \RPC Control.

在上面利用代碼執行結果中,有下面兩行輸出:

[CreateJunction] Junction \\?\C:\Users\exp\AppData\Local\Temp\3bbbd2cf-7baf-42b7-98ea-242f703b08f8 -> \RPC Control created!
[DosDeviceSymLink] Symlink Global\GLOBALROOT\RPC Control\SDT73B.tmp -> \??\C:\Config.msi::$INDEX_ALLOCATION created!

首先創建了攻擊者可控的目錄 3bbbd2cf-7baf-42b7-98ea-242f703b08f8 指向 \RPC Control 的重分析點,這樣訪問 \RPC Control 就相當于訪問這個可控目錄;之后在 \RPC Control 下面創建了一個由 SDT73B.tmp 指向待刪除文件的符號鏈接,這一步就相當于將可控目錄下的 SDT73B.tmp 指向了待刪除文件,刪除 SDT73B.tmp 就相當于刪除了目標文件。

6. 參考資料

  1. https://mp.weixin.qq.com/s/dhKtb8EeBwJnBEjepHXPpg
  2. Windows Exploitation Tricks: Exploiting Arbitrary Object Directory Creation for Local Elevation of Privilege
  3. CVE-2023-21752 exp
  4. ABUSING ARBITRARY FILE DELETES TO ESCALATE PRIVILEGE AND OTHER GREAT TRICKS
  5. Follow the Link: Exploiting Symbolic Links with Ease
  6. Understanding and Exploiting Symbolic links in Windows - Symlink Attack EOP

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