作者:HuanGMz@知道創宇404實驗室
時間:2022年6月7日
English version: http://www.bjnorthway.com/1914/
分析一下最近Microsoft Office 相關的 MSDT 漏洞。
1. WTP 框架
Windows Troubleshooting Platform (WTP) provides ISVs, OEMs, and administrators the ability to write troubleshooting packs that are used to discover and resolve issues found on the computer
WTP 框架提供了一種自動化 檢測/修復 故障的方式。
WTP 結構:
上圖展示了 WTP 的底層結構:
- WTP由兩個進程組成,Process1 是帶UI 的 Troubleshooting Run-time Engine,Process2 用于提供 Windows PowerShell Runtime 環境。
- Process2 提供的 PowerShell 運行時環境提供了4條特殊的 PowerShell 命令:Get-DiagInput, Update-DiagReport, Update-DiagRootCause, Write-DiagProgress。
- Troubleshooting Pack 運行在Process1 和 Process2 所構建的平臺上。
故障排除包 是用戶可編程部分,其本質上是一組 針對特定故障的 檢測/修復腳本。Process1 的Troubleshooting Run-time Engine 從故障排除包中獲取 檢測腳本,并交給Process2 運行。Process2 中特殊的 PowerShell 運行時環境提供了4條專用的命令給故障排除包里的腳本使用。
故障排除包的設計基于三個步驟:檢測問題(troubleshooting)、解決問題(resolution)和驗證解決方案(verification),對應 TS_、RS_、VF_ 三種腳本。
實際上 Process1 就是 msdt.exe ,Process2 則是 sdiagnhost.exe。sdiagnhost.exe 為了給msdt.exe 提供運行腳本的能力,注冊了IScriptedDiagnosticHost com接口,相應的com方法就是:RunScript()。
WTP 還提供了一系列的默認故障排除包,可以在 ms-msdt 協議里通過 -id 參數指定。本次漏洞中所使用的 PCWDiagnostic 就是其中之一,用于程序兼容性的故障排除。
2. 漏洞復現與調試方法
漏洞復現:
該漏洞可通過 doc 或 rtf 文檔的形式觸發,但為了調試方便,我們直接使用 msdt.exe 命令觸發:
C:\Windows\system32\msdt.exe ms-msdt:/id PCWDiagnostic /skip force /param "IT_RebrowseForFile=cal?c IT_SelectProgram=NotListed IT_BrowseForFile=fff$(IEX('mspaint.exe'))i/../../../../../../../../../../../../../../Windows/System32/mpsigstub.exe "
在 cmd 中使用上面的命令觸發漏洞(不要直接用powershell)。
漏洞調試:
mspaint.exe 進程創建在 sdiagnhost.exe 下,且 PowerShell Runtime 由c# 實現。盡管 sdiagnhost.exe 本身是一個非托管程序,我們仍然可以使用 dnspy 來進行 .net 調試。
設置好 dnspy 調試所需要的環境變量:
COMPlus_ZapDisable=1
COMPlus_ReadyToRun=0
在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ 注冊表路徑下創建 sdiagnhost.exe 項,并在該項下創建 Debugger 字符串,值為dnspy 路徑。
使用前面的命令觸發漏洞,然后會看到 dnspy 被調用,但處于未開始調試的狀態。此時需要我們手動點擊上面的"啟動" 來開啟調試。
在 Microsoft.Windows.Diagnosis.SDHost.dll 里的 Microsoft.Windows.Diagnosis.ManagedHost.RunScript() 方法下斷點。該方法實現了 IScriptedDiagnosticHost com接口里的 RunScript() 方法,用于給 msdt.exe 提供執行檢測腳本所需的 PowerShell 運行時環境。然后重新觸發漏洞,便可在此中斷。

RunScript() 方法一共被觸發了兩次,第一次用于調用 TS 腳本,第二次用于調用 RS 腳本,且第二次有參數。
3. 漏洞原因與觸發條件
本質上這是一個 PowerShell 代碼注入漏洞。
ManagedHost.RunScript() 使用 PowerShell.AddScript() 方法來添加要執行的命令,并且text 中的部分內容可控(參數部分)。這是典型的 PowerShell 代碼注入漏洞,使用AddScript() 會導致在調用時對 text 里的 $ 字符進行語法解析(優先將其解析為子表達式運算符)。
類似于下面這樣:
PowerShell powerShellCommand = PowerShell.Create();
powerShellCommand.AddScript("ls -test $(iex('mspaint.exe'))");
var result = powerShellCommand.Invoke();
實際上漏洞觸發于第二次調用 RunScript(),調用 RS 腳本時,相應的 text 為:
@"& ""C:\Users\MRF897~1.WAN\AppData\Local\Temp\SDIAG_d89d16cb-49d3-48ef-bea4-daebc1919abb\RS_ProgramCompatibilityWizard.ps1"" -TargetPath ""h$(IEX('mspaint.exe'))i/../../../../../../../../../../../../../../Windows/System32/mpsigstub.exe"" -AppName ""mpsigstub"""
可以看到傳給 AddScript() 的字符串沒有對 $ 符號進行過濾,導致了代碼注入。
觸發條件:
要想成功觸發對 RS_ProgramCompatibilityWizard.ps1 的調用,要先通過 TS_ProgramCompatibilityWizard.ps1 腳本的檢測。
觀察TS_ProgramCompatibilityWizard.ps1 腳本的代碼:

Get-DiagInput 命令就是我們前面提到的WTP PowerShell Runtime 提供的4條特殊命令之一,該命令用于從用戶獲取輸入信息。這里獲取我們傳入的 IT_BrowseForFile 參數 并賦值給了 $selectedProgram 變量。
而后調用 Test-Selection方法來對 $selectedProgram 進行檢測:
該函數首先使用 test-path 命令來對路徑進行檢測,以保證路徑存在。然后要求路徑的擴展名為 exe 或 msi。
但是 test-path 對于使用 /../ 返回到根路徑之外的路徑會返回True,比如下面的:

這里以 \ 開頭,表示當前盤符的根目錄,\..\ 存在,所以 \..\..\ 便超出了范圍,返回為true。也可以像c:\..\..\hello.exe 這樣。如果像原始payload 那樣以一個普通字符開頭,考慮到 TS_ProgramCompatibilityWizard.ps1 腳本所在的臨時目錄,以及 該普通字符所占一級,至少需要9個 \..\ 才可以。

然后從 $selectedProgram 里提取文件名,并過濾$符號,以防代碼注入。但這行代碼其實是錯的,正確的寫法如下:
$appName = [System.IO.Path]::GetFileNameWithoutExtension($selectedProgram).Replace("`$", "``$")
由于原來的腳本中直接使用 "$",該 "$" 實際會在傳給Replace之前被PowerShell 引擎解析,根本無法匹配到 $ 字符。

TS 腳本的最后,使用了Update-DiagRootCause 命令,該命令也是4條特殊命令之一,用于報告root cause 的狀態。注釋中寫道該命令會觸發調用 RS_ 腳本,-parameter 指定的字典會被作為參數傳給腳本。導致第二次調用 RunScript() 方法,并且參數中的 -TargetPath 可控,進而觸發了漏洞。
參考資料:
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1913/
暫無評論