一、简介
有个Windows认证的逆向需求出现,涉及调试lsass。这项技术本身没有什么难度,属
于Windows安全入门技能,值得初学者掌握。科普一下。
二、环境
测试环境Win10企业版2016 LTSB,winver显示1607(OS Build 14393.4704)
$ ver
Microsoft Windows [Version 10.0.14393]
NtlmShared.dll
size 38904
ver 10.0.14393.3269 (rs1_release.190929-1234)
SHA256 a95e5823b68182c4e32cb783ad23bc4ff60690001c70e6b5e920c12740c4c37c
msv1_0.dll
size 401152
ver 10.0.14393.3866 (rs1_release.200805-1327)
SHA25 46ad1ac8c7db7d21e8f41efc734b855cee566cb58f8fb825775490dc5de89c94
lsasrv.dll
size 1501184
ver 10.0.14393.4704 (rs1_release.211004-1917)
SHA25 726a3441e46f2b2a109a3fce3002b2108c168d708e43ca23a3819f458964c4cf
SspiSrv.dll
size 28672
ver 10.0.14393.4704 (rs1_release.211004-1917)
SHA25 89365a7ceade64504f15978c93e2bf61dab299327ea2002886bbc5269cad158e
环境不同无所谓,只是为了陈述的严谨性,重在实验过程,理解原理后自行适配。
上面说的是VMware Guest,至于VMware Host那完全无所谓。
三、调试lsass
1) 用kd调试lsass
假设Guest停留在登录界面,尚未登录,kd接入Guest
kd.exe -b -s -k com:pipe,port=\.\pipe\com_136,resets=0
一点准备工作
.prompt_allow +reg +ea +dis
查看当前进程,此刻一般是System
kd> !process -1 0
PROCESS ffffda883e4a36c0
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 001ab000 ObjectTable: ffffa08d0bc01280 HandleCount:
Image: System
寻找lsass的EPROCESS
kd> !process 0 0 lsass.exe
PROCESS ffffda884002a800
SessionId: 0 Cid: 0314 Peb: e6548a7000 ParentCid: 02ac
DirBase: 214800000 ObjectTable: ffffa08d0c9362c0 HandleCount:
Image: lsass.exe
切换进程空间到lsass
.process /i ;g
.process /i ffffda884002a800;g
尽量不在这步用”.process /p /r”,而是用”.process /i;g”。后面要在lsass进程空
间直接用bp设用户态断点,本例断点位于系统dll中,用”.process /p /r”切换也成,
但最靠谱的还是用”.process /i;g”切换。
再次检查当前进程(第二种方式)
kd> !thread -p -1 0
PROCESS ffffda884002a800
SessionId: 0 Cid: 0314 Peb: e6548a7000 ParentCid: 02ac
DirBase: 214800000 ObjectTable: ffffa08d0c9362c0 HandleCount:
Image: lsass.exe
THREAD ffffda883ebbb040 Cid 0004.0148 Teb: 0000000000000000 Win32Thread: 0000000000000000 RUNNING on processor 0
刷新kd维护的用户态加载模块列表使之匹配当前进程,这一步有些耗时,耐心等待
.reload /f /user
在kd查看用户态进程(本例是lsass)加载的dll
lmuf
确保相关模块符号就位,顺便获取模块基址
lmu m msv1_0
lmu m NtlmShared
还可以这样看基址
!lmi NtlmShared // 假设是0x7fff84a10000
!lmi msv1_0 // 假设是0x7fff84a20000
!lmi lsasrv // 假设是0x7fff84e60000
!lmi SspiSrv // 假设是0x7fff84520000
设置断点,g起来
kd> dt nt!_EPROCESS UniqueProcessId ImageFileName @$proc
+0x2e8 UniqueProcessId : 0x00000000`00000314 Void
+0x450 ImageFileName : [15] “lsass.exe”
kd> bp /p @$proc NtlmShared!MsvpPasswordValidate
kd> g
在kd中设置位于系统dll的用户态断点,尽量指定”/p @$proc”以减少干挠,系统dll
可能出现在多个不同进程空间,且加载基址一样。
去Guest的登录界面随便输入啥口令,不需要输正确口令,断点命中。再设另一个一
次性断点,g起来,会立即命中,查看调用栈回溯
kd> bp /1 /p @$proc ntdll!RtlCompareMemory
kd> g
Breakpoint 1 hit
kd> kpn
# Child-SP RetAddr Call Site
00 000000e65557bff8 00007fff84a136f5 ntdll!RtlCompareMemory
01 000000e65557c000 00007fff84a4ff40 NtlmShared!MsvpPasswordValidate+0x5c5
02 000000e65557c100 00007fff84a4ec3f msv1_0!MsvpValidateLogonWithUserPassword+0x1f0
03 000000e65557c210 00007fff84a4d52d msv1_0!MsvpSamValidate+0xab3
04 000000e65557c900 00007fff84a519d9 msv1_0!MsvSamValidate+0x1ad
05 000000e65557cb40 00007fff84a3ae07 msv1_0!MsvpSamValidateAtLogon+0xd9
06 000000e65557cbf0 00007fff84e844be msv1_0!LsaApLogonUserEx2+0x2067
07 000000e65557dde0 00007fff84e839e6 lsasrv!NegLogonUserEx2Worker+0x7f6
08 000000e65557df70 00007fff84e835b5 lsasrv!NegLogonUserEx2+0x2b6
09 000000e65557e250 00007fff84e7b241 lsasrv!LsapCallAuthPackageForLogon+0x101
0a 000000e65557e300 00007fff84e87f0b lsasrv!LsapAuApiDispatchLogonUser+0x391
0b 000000e65557e6d0 00007fff
84521467 lsasrv!SspiExLogonUser+0x79b
0c 000000e65557eac0 00007fff8817a583 SspiSrv!SspirLogonUser+0x247
0d 000000e65557ec40 00007fff881d9f41 RPCRT4!Invoke+0x73
0e 000000e65557ed00 00007fff88162d3c RPCRT4!Ndr64StubWorker+0xbb1
0f 000000e65557f3d0 00007fff8814a284 RPCRT4!NdrServerCallAll+0x3c
10 000000e65557f420 00007fff8814919d RPCRT4!DispatchToStubInCNoAvrf+0x24
11 000000e65557f470 00007fff88149a4b RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1bd
12 000000e65557f540 00007fff881310ac RPCRT4!RPC_INTERFACE::DispatchToStub+0xcb
13 000000e65557f5a0 00007fff8813152c RPCRT4!LRPC_SCALL::DispatchRequest+0x34c
14 000000e65557f680 00007fff8811ae1c RPCRT4!LRPC_SCALL::HandleRequest+0x2bc
15 000000e65557f7a0 00007fff8811c67b RPCRT4!LRPC_ADDRESS::HandleRequest+0x36c
16 000000e65557f850 00007fff88143a2a RPCRT4!LRPC_ADDRESS::ProcessIO+0x91b
17 000000e65557f990 00007fff88d0d35e RPCRT4!LrpcIoComplete+0xaa
18 000000e65557fa30 00007fff88d0ecc9 ntdll!TppAlpcpExecuteCallback+0x25e
19 000000e65557fae0 00007fff885b84d4 ntdll!TppWorkerThread+0x8d9
1a 000000e65557fee0 00007fff88d41791 KERNEL32!BaseThreadInitThunk+0x14
1b 000000e65557ff10 0000000000000000 ntdll!RtlUserThreadStart+0x21
简单整理一下上述调用栈回溯,换个形式展现
SspiSrv!SspirLogonUser
SspiSrv!SspirLogonUser+0x241 // call qword ptr [SspiSrv!_guard_dispatch_icall_fptr]
ntdll!LdrpDispatchUserCallTarget // “u @rax l 1″检查目标函数
// “jmp rax”进行流程转移
lsasrv!SspiExLogonUser
lsasrv!SspiExLogonUser+0x796
lsasrv!LsapAuApiDispatchLogonUser
lsasrv!LsapAuApiDispatchLogonUser+0x38c
lsasrv!LsapCallAuthPackageForLogon
lsasrv!LsapCallAuthPackageForLogon+0xfc
lsasrv!NegLogonUserEx2 // IDA中符号带形参,C++符号
// 0x7fff84e83730
lsasrv!NegLogonUserEx2+0x2b1 // 0x7fff84e83730+0x2b1=0x7fff84e839e1
lsasrv!NegLogonUserEx2Worker // IDA中符号带形参,C++符号
lsasrv!NegLogonUserEx2Worker+0x7f0 // 0x7fff84e844b8
// call qword ptr [lsasrv!_guard_dispatch_icall_fptr]
ntdll!LdrpDispatchUserCallTarget // “jmp rax”进行流程转移
msv1_0!LsaApLogonUserEx2
msv1_0!LsaApLogonUserEx2+0x2062
msv1_0!MsvpSamValidateAtLogon // IDA中符号带形参,C++符号
msv1_0!MsvpSamValidateAtLogon+0xd4 // 0x7fff84a519d4
msv1_0!MsvSamValidate
msv1_0!MsvSamValidate+0x1a8
msv1_0!MsvpSamValidate
msv1_0!MsvpSamValidate+0xaae
msv1_0!MsvpValidateLogonWithUserPassword
msv1_0!MsvpValidateLogonWithUserPassword+0x1ea
msv1_0!_imp_MsvpPasswordValidate
NtlmShared!MsvpPasswordValidate
NtlmShared!MsvpPasswordValidate+0x5bf
NtlmShared!_imp_RtlCompareMemory
ntdll!RtlCompareMemory
NtlmShared!MsvpPasswordValidate+0x5c5 // cmp rax, r14
绝大多数函数名是自解释的,函数内相对偏移版本强相关,不用太在意。过去
MsvpPasswordValidate由msv1_0直接提供,Win10将该函数移入NtlmShared。
kd> u NtlmShared!MsvpPasswordValidate+0x5bf l 3
00007fff84a136ef ff15c31b0000 call qword ptr [NtlmShared!_imp_RtlCompareMemory (00007fff
84a152b8)]
00007fff84a136f5 493bc6 cmp rax,r14 00007fff
84a136f8 0f8518fbffff jne NtlmShared!MsvpPasswordValidate+0xe6 (00007fff`84a13216)
就该版本NtlmShared而言,设置如下Patch型断点,g起来
kd> bc *
kd> bp /p @$proc NtlmShared!MsvpPasswordValidate+0x5c5 “r @rax=@r14;gc”
kd> g
就是让NtlmShared!MsvpPasswordValidate+0x5bf处的内存比较始终认为相等,可在
IDA中F5查看更多细节。
你会发现无论之前登录界面输了啥错误口令,都登录成功。这个实验对控制台登录、
锁屏登录有效,不适用于SMB、RDP登录。
为了加快可能出现的其他实验进度,小结一下kd操作
.prompt_allow +reg +ea +dis
!process -1 0
!process 0 0 lsass.exe
.process /i ;g
!thread -p -1 0
.reload /f /user
lmuf
lmu m msv1_0
lmu m NtlmShared
u NtlmShared!MsvpPasswordValidate
2) 用dbgsrv调试lsass
在Guest中
net use Z: “\vmware-host\Shared Folders”
set _NT_SYMBOL_PATH=srvz:\symhttp://msdl.microsoft.com/download/symbols
dir “Z:\Green\Windows Kits\10\x64\Debuggers\x64\”
DbgModel.dll
dbgeng.dll
dbghelp.dll
dbgsrv.exe
复制上述几个文件到Guest中,这种远程用户态调试只要求Guest中有这些文件。
dbgsrv.exe -t tcp:port=8765,password=8765
并不需要提前查找lsass的PID
tasklist | find “lsass”
tasklist | findstr lsass
在Host中
cdb.exe -noinh -snul -hd -o -premote tcp:server=192.168.65.136,port=8765,password=8765 -pn lsass.exe
全用户态操作
.prompt_allow +reg +ea +dis
u NtlmShared!MsvpPasswordValidate+0x5bf l 3
bc *
bp NtlmShared!MsvpPasswordValidate+0x5c5 “r @rax=@r14;gc”
g
用runas触发Patch型断点
runas /noprofile /user:DESKTOP-TEST\test cmd
输入任意口令都行。runas也是交互式登录的一种。
Administrator、Guest有其他缺省安全限制,只是Patch口令认证,仍然runas失败。
3) 用”ntsd+kd”调试lsass
参看windbg帮助
Debugger Operation
Remote Debugging
Controlling the User-Mode Debugger from the Kernel Debugger
Starting the Debugging Session
Switching Modes
When to Use This Technique
Debugging CSRSS
Debugging WinLogon
这种技术将用户态ntsd的I/O重定向到内核态kd,通过kd操作ntsd。
在Guest中
net use Z: “\vmware-host\Shared Folders”
set _NT_SYMBOL_PATH=srvz:\symhttp://msdl.microsoft.com/download/symbols
dir “Z:\Green\Windows Kits\10\x64\Debuggers\x64\”
DbgModel.dll
dbgeng.dll
dbghelp.dll
dbgsrv.exe
symsrv.dll
cdb.exe
ntsd.exe
复制上述几个文件到Guest中。从Vista开始,系统缺省不带ntsd。
普通测试
ntsd.exe -noinh -snul -hd -o -g -G “C:\Windows\System32\notepad.exe”
bu notepad!SaveFile
“ntsd+kd”联动测试,主要是指定”-d”参数,让ntsd的I/O重定向到kd
ntsd.exe -d -noinh -snul -hd -o -g -G “C:\Windows\System32\notepad.exe”
本例刻意演示一种复杂情况,未直接断在ntsd中,notepad跑起来了,想让”ntsd+kd”
重获控制。
此时需要提前查找notepad的PID
tasklist | find “notepad”
tasklist | findstr notepad
在kd中
!bpid
指定PID时注意进制,tasklist输出的是10进制,kd中最好加上0n前缀
kd> !bpid 0n4944
Finding wininit.exe (1)…
Finding winlogon.exe (1)…
Waiting for winlogon.exe to break. This can take a couple of minutes…
…
ntdll!DbgBreakPoint:
00007fff`88d994f0 cc int 3
0:001>
!bpid向目标进程注入一个线程,通过该线程中断在ntsd中,这可能会耗较长时间。
若因系统资源不足或其他原因使得创建、注入线程失败,!bpid就会失败。!bpid成功
后可以看出内核态kd提示符已经变成用户态ntsd提示符。
获知被调试进程映像文件绝对路径,以防调错了目标进程
0:001> ?? @$peb->ProcessParameters->ImagePathName
struct _UNICODE_STRING
“C:\Windows\System32\notepad.exe”
+0x000 Length : 0x3e
+0x002 MaximumLength : 0x40
+0x008 Buffer : 0x000001f4`e8402168 “C:\Windows\System32\notepad.exe”
通过”ntsd+kd”设置用户态断点
0:001> .prompt_allow +reg +ea +dis
0:001> bu notepad!SaveFile
0:001> g
操作notepad触发断点,不再调试时可以正常detach
0:000> .detach
NoTarget> q
从ntsd detach不影响kd的存在。
有些介绍”ntsd+kd”的文章,上来就让你搞Image File Execution Options(IFEO)。
“ntsd+kd”与IFEO没有必然联系,动用IFEO只是想尽早attach目标进程,调试其早期
启动阶段。
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe]
“Debugger”=”C:\temp\ntsd.exe -d -noinh -snul -hd -o -g -G”
reg.exe add “HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe” /v “Debugger” /t REG_SZ /d “C:\temp\ntsd.exe -d -noinh -snul -hd -o -g -G” /f
reg.exe query “HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe” /v “Debugger”
reg.exe delete “HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe” /f
前面演示目标是notepad.exe,理解原理后将目标换成lsass.exe没毛病。
tasklist | findstr lsass
ntsd.exe -d -noinh -snul -hd -o -g -G -p
kd> !bpid 0n788
Finding wininit.exe (0)…
Waiting for wininit.exe to break. This can take a couple of minutes…
…
ntdll!DbgBreakPoint:
00007fff88d994f0 cc int 3 0:007> ?? @$peb->ProcessParameters->ImagePathName struct _UNICODE_STRING “C:\Windows\system32\lsass.exe” +0x000 Length : 0x3a +0x002 MaximumLength : 0x3c +0x008 Buffer : 0x0000025184403598 “C:\Windows\system32\lsass.exe”
在”ntsd+kd”中
.prompt_allow +reg +ea +dis
u NtlmShared!MsvpPasswordValidate+0x5bf l 3
bc *
bp NtlmShared!MsvpPasswordValidate+0x5c5 “r @rax=@r14;gc”
g
用runas触发Patch型断点
runas /noprofile /user:DESKTOP-TEST\test cmd
不想调试lsass时,在kd中Ctrl-C,这将断在kd中,而不是ntsd中,必须再次!bpid
kd> !bpid 0n788
0:007> bl
0 e 00007fff`84a136f5 0001 (0001) 0:**** NtlmShared!MsvpPasswordValidate+0x5c5 “r @rax=@r14;gc”
0:007> .detach
NoTarget> q
若需尽早调试lsass,动用IFEO比较省事,但不是非此不可,直接用kd完全可以尽早
调试lsass,不在此讨论。
4) 简单对比几种方案
前述几种调试方案各有利弊,视需求不同而定。若只是关心登录认证过程,用dbgsrv
调试lsass最佳。
调试环境是VMware时,符号目录不是问题,Guest、Host通过”Shared Folders”共用
符号目录即可。只有”ntsd+kd”方案面临此问题。
四、结语
本文只是演示调试lsass的入门技能,断点、Patch都选用最简单的那种,示例与我的
原始需求无关。
版权声明
本站“技术博客”所有内容的版权持有者为绿盟科技集团股份有限公司(“绿盟科技”)。作为分享技术资讯的平台,绿盟科技期待与广大用户互动交流,并欢迎在标明出处(绿盟科技-技术博客)及网址的情形下,全文转发。
上述情形之外的任何使用形式,均需提前向绿盟科技(010-68438880-5462)申请版权授权。如擅自使用,绿盟科技保留追责权利。同时,如因擅自使用博客内容引发法律纠纷,由使用者自行承担全部法律责任,与绿盟科技无关。