Win10主控台登录认证流程

一、背景介绍

搜某些关键字时命中[1],作者在看雪论坛分享过Win7主控台登录认证流程中的部分环节。正好当时有深究Win10 RPC调试的动机,这篇也涉及一些,实验环境唾手可得,就在Win10上实践一番。

我对登录认证流程没有研究过,那个可能是黑产党的刚需。本文不涉及黑产党视角,就是简单看看[1]之作者所述内容在Win10上的变化,以调试视角呈现内容。阅读本文前,应该先看[1]。

本文在Win10企业版2016 LTSB 1607(OS Build 14393.4704)上测试。

二、Win10交互式登录大框架

Win10主控台登录动态创建进程树

winlogon.exe
LogonUI.exe

假设在主控台登录成功,则上述进程树被销毁;此时若锁屏,会创建新的进程树。每种交互式登录都有这样的进程树被创建,RDP登录时也会创建这样的进程树。

下面这个框架对应几件事

a) 出现密码输入界面
b) 输入密码点击确定后的RPC调用
c) 报告登录验证结果


winlogon!StateMachineWorkerCallback

ntdll!LdrpDispatchUserCallTarget
winlogon!WLGeneric_Request_Logon_Credz_Execute
winlogon!RequestCredentials
winlogon!WluiRequestCredentials // 要求用户输入密码,出现新的密码输入界面
logoncontroller!WluirRequestCredentials // 从winlogon进入LogonUI
winlogon!SignalManagerSetSignal // 错误密码触发

ntdll!LdrpDispatchUserCallTarget
winlogon!WLGeneric_Logon_ReportFailedResult_Execute
winlogon!ReportResult
winlogon!WluiReportResult // 报告登录验证结果
logoncontroller!WluirReportResult
winlogon!WluiDisplayRequestCredentialsError // 提示”密码不正确”
logoncontroller!WluirDisplayRequestCredentialsError
winlogon!WlStateMachineSetSignal
winlogon!SignalManagerSetSignal // 提示”密码不正确”,点击确定,触发

winlogon!WLGeneric_Authenticating_Execute
winlogon!WlDisplayStatus
winlogon!WluiDisplayStatus
logoncontroller!WluirDisplayStatus
winlogon!AuthenticateUser // 输入密码,提交,触发
usermgrcli!UMgrLogonUser
usermgrcli!DoRpcCall< >
usermgrcli!::operator()
RPCRT4!NdrClientCall3
RPCRT4!NdrpClientCall3
usermgr!svcUMLogonUser // 从winlogon进入UserManager服务
usermgr!UserMgrCli::UMLogonUser
sspicli!LsaLogonUser // Win10在UserManager服务中调用此函数
sspicli!SspipLogonUser
RPCRT4!NdrClientCall3
SspiSrv!SspirLogonUser // 从UserManager服务进入lsass

winlogon!SignalManagerSetSignal // 错误密码触发

Win10在winlogon与lsass之间多了一个UserManager服务

winlogon->UserManager服务->lsass

过去可能由winlogon直接发起RPC请求sspicli!SspipLogonUser,Win10有变化,由UserManager服务发起RPC请求。

三、winlogon!Wlui*

winlogon!WluiRequestCredentials负责展现新的密码输入界面,函数名中的”ui”暗示了这是用户界面相关的函数。winlogn中这类函数还有

x /1 winlogon!Wlui*[a-z]
winlogon!WluiiReleaseLessPrivilegedToken
winlogon!WluiiStartupImpl
winlogon!WluiiWaitForServer
winlogon!WluiiShutdownImpl
winlogon!WluiDisplayStatus
winlogon!WluiDisplayRequestCredentialsError
winlogon!WluiDisplayTSDisconnectOptionsList
winlogon!WluiPromptForCredentials
winlogon!WluiDisplayLocked
winlogon!WluiAbort
winlogon!WluiSecureDelayLocked
winlogon!WluiNotifyIsReadyForDesktopSwitch
winlogon!WluiFinishOperation
winlogon!WluiDisplayMessage
winlogon!WluiGetLockScreenIdleTimeout
winlogon!WluiDelayLocked
winlogon!WluiWaitForLockScreenDismiss
winlogon!WluiGetShutdownResolverInfo
winlogon!WluiShutdown
winlogon!WluiAbortUIThread
winlogon!WluiDisplayTSDisconnectUI
winlogon!WluiDisplayTSReconnectUI
winlogon!WluipCopyToUnicodeString
winlogon!WluiReportResult
winlogon!WluiiDestroySharedEvents
winlogon!WluiInformLogonUI
winlogon!WluiDisplayTSDisconnectOptionsMessage
winlogon!WluiDisplaySecurityOptions
winlogon!WluiDisplayWelcome
winlogon!WluiSecureDisplayLocked
winlogon!WluiNotifyUserIsLoggedOn
winlogon!WluiReleaseUI
winlogon!WluiRequestCredentials
winlogon!WluiDisplayTSReconnectOptions
winlogon!WluiReleaseContext
winlogon!WluiClearUIState

直接”x /1 winlogon!Wlui*”会显示很多内部函数,比如

winlogon!WluiDisplayStatus$filt$0

参看windbg帮助中的”String Wildcard Syntax”小节。”x /1 winlogon!Wlui*[a-z]”大小写不敏感。

四、logoncontroller!Wluir*

从框架图中看到winlogon!WluiRequestCredentials通过RPC调用LogonUI进程中的logoncontroller!WluirRequestCredentials,函数名多了一个字母”r”,表示RPC。
这一对函数分别对应RPC Client、RPC Server。类似的还有

winlogon!WluiReportResult
logoncontroller!WluirReportResult

winlogon!WluiDisplayRequestCredentialsError
logoncontroller!WluirDisplayRequestCredentialsError

winlogon!WluiDisplayStatus
logoncontroller!WluirDisplayStatus

调试LogonUI.exe,查看

.shell -ci “x /1 logoncontroller!Wluir*” findstr Wluir
logoncontroller!WluirDisplayLocked
logoncontroller!WluirSecureDisplayLocked
logoncontroller!WluirDisplayTSReconnectUI
logoncontroller!WluirSignalShutdown
logoncontroller!WluirDelayLocked
logoncontroller!WluirInformLogonUI
logoncontroller!WluirDisplayStatus
logoncontroller!WluirDisplayMessage
logoncontroller!WluirRequestCredentials
logoncontroller!WluirPromptForCredentials
logoncontroller!WluirNotifyUserIsLoggedOn
logoncontroller!WluirDisplaySecurityOptions
logoncontroller!WluirGetShutdownResolverInfo
logoncontroller!WluirSecureDelayLocked
logoncontroller!WluirWaitForLockScreenDismiss
logoncontroller!WluirNotifyIsReadyForDesktopSwitch
logoncontroller!WluirAbort
logoncontroller!WluirReleaseContext
logoncontroller!WluirDisplayRequestCredentialsError
logoncontroller!WluirDisplayTSDisconnectOptions
logoncontroller!WluirDisplayWelcome
logoncontroller!WluirClearUIState
logoncontroller!WluirReportResult
logoncontroller!WluirDisplayTSDisconnectUI
logoncontroller!WluirFinishOperation

由于x命令后面的模板大小写不敏感,直接”x /1 logoncontroller!Wluir*”会命中这种

logoncontroller!WluiRequestReasonToLogonReason

不是我们想要的,所以用了”.shell -ci”技巧。上述25个函数都有相应的winlogon版本。

更精准的办法是,用IDA的findrpc插件看LogonController.dll的”f3f09ffd-fbcf-4291-944d-70ad6e0e73bb”接口,该接口提供的远程过程正是上述25个函数。关于findrpc,参看

《利用findrpc寻找RPC接口信息》
https://blog.nsfocus.net/findrpc-rpc/

五、SspiSrv!SspirLogonUser

主控台登录时流程会过lsass进程空间的SspiSrv!SspirLogonUser。对之设断,断下来时查看IID/ProcNum/RPC Client PID

bp SspiSrv!SspirLogonUser
r $t0=poi(@rsp+0xc8+0xe0);dt ntdll!_GUID poi(@$t0+0x28)+4;? dwo(@$t0+0x1c)
r $t1=poi(@$t0+0x68);? qwo(@$t1+8)

在SspiSrv!SspirLogonUser入口处获取RPC Client PID的Hacking方案不可用于产品。其他IID/ProcNum目标函数入口处未必如此,需要逆向确认,不可直接套用。假设获取信息如下

IID 4f32adc8-6052-4a04-8701-293ccf2096f0
ProcNum SspiSrv!SspirLogonUser (12)
RPC Client PID 872

$ tasklist /svc /fi “pid eq 872”

Image Name PID Services
========================= ========
svchost.exe 872 Appinfo, AppMgmt, BITS, CertPropSvc,
DsmSvc, gpsvc, IKEEXT, iphlpsvc, lfsvc,
ProfSvc, RasMan, Schedule, seclogon, SENS,
SessionEnv, Themes, UserManager, UsoSvc,
Winmgmt, WpnService

RPC客户端是svchost(872),不是winlogon.exe,具体说,是UserManager服务。

六、sspicli!SspipLogonUser

调试svchost(872),调试UserManager服务

bp RPCRT4!NdrpClientCall3 “r $t0=poi(poi(@rdx));.if(@$t0){.if(qwo(@$t0+4)==0x4a0460524f32adc8 and qwo(@$t0+0xc)==0xf09620cf3c290187 and @r8==0n12){kpn}.else{gc}}.else{gc}”
dt -io ntdll!_GUID @$t0+4;? @r8

IID/ProcNum匹配时断下

# Call Site
00 RPCRT4!NdrpClientCall3
01 RPCRT4!NdrClientCall3+0xf2
02 sspicli!SspipLogonUser+0x394
03 sspicli!LsaLogonUser+0x83
04 usermgr!UserMgrCli::UMLogonUser+0x2c6
05 usermgr!svcUMLogonUser+0xd7
06 RPCRT4!Invoke+0x73
07 RPCRT4!Ndr64StubWorker+0xbb1
08 RPCRT4!NdrServerCallAll+0x3c
09 RPCRT4!DispatchToStubInCNoAvrf+0x24
0a RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1bd
0b RPCRT4!RPC_INTERFACE::DispatchToStubWithObject+0x15e
0c RPCRT4!LRPC_SCALL::DispatchRequest+0x177
0d RPCRT4!LRPC_SCALL::HandleRequest+0x2bc
0e RPCRT4!LRPC_ADDRESS::HandleRequest+0x36c
0f RPCRT4!LRPC_ADDRESS::ProcessIO+0x91b
10 RPCRT4!LrpcIoComplete+0xaa
11 ntdll!TppAlpcpExecuteCallback+0x25e
12 ntdll!TppWorkerThread+0x8d9
13 KERNEL32!BaseThreadInitThunk+0x14
14 ntdll!RtlUserThreadStart+0x21

UserManager服务调用sspicli!SspipLogonUser,发起RPC,调用lsass的
SspiSrv!SspirLogonUser。

七、sspicli!LsaLogonUser

sspicli!SspipLogonUser的父函数是sspicli!LsaLogonUser,有个同名函数文档化过,参[2]


NTSTATUS LsaLogonUser
(
[in] HANDLE LsaHandle, // rcx
/*
* dt UxTheme!LSA_STRING @rdx
*
* “Winlogon”
/ [in] PLSA_STRING OriginName, // rdx /
* dt UxTheme!_SECURITY_LOGON_TYPE
*
* Interactive(2)
/ [in] SECURITY_LOGON_TYPE LogonType, // r8 [in] ULONG AuthenticationPackage, // r9 /
* A pointer to an input buffer that contains authentication information,
* such as user name and password. The format and content of this buffer
* are determined by the authentication package.
*
* db poi(@rsp+0x28) l dwo(@rsp+0x30)
*/
[in] PVOID AuthenticationInformation, // poi(@rsp+0x28)
[in] ULONG AuthenticationInformationLength, // dwo(@rsp+0x30)
[in] PTOKEN_GROUPS LocalGroups, // poi(@rsp+0x38)
[in] PTOKEN_SOURCE SourceContext, // poi(@rsp+0x40)
[out] PVOID *ProfileBuffer,
[out] PULONG ProfileBufferLength,
[out] PLUID LogonId,
[out] PHANDLE Token,
[out] PQUOTA_LIMITS Quotas,
[out] PNTSTATUS SubStatus

)

我以为AuthenticationInformation指向下述结构

dt UxTheme!_MSV1_0_INTERACTIVE_LOGON
+0x000 MessageType : _MSV1_0_LOGON_SUBMIT_TYPE
+0x008 LogonDomainName : _UNICODE_STRING
+0x018 UserName : _UNICODE_STRING
+0x028 Password : _UNICODE_STRING

bp sspicli!LsaLogonUser “db poi(@rsp+0x28) l dwo(@rsp+0x30)”

user:scz
pass:xxx

断在sspicli!LsaLogonUser时查看AuthenticationInformation

00000205a41b4c40 01 80 00 00 06 00 00 00-ba 00 00 00 00 00 00 00 ……………. 00000205a41b4c50 40 42 0a 9c 05 02 00 00-02 00 00 00 ff 7f 00 00 @B…………..
00000205a41b4c60 18 00 18 00 ff 7f 00 00-40 00 00 00 00 00 00 00 ……..@……. 00000205a41b4c70 06 00 06 00 00 00 00 00-58 00 00 00 00 00 00 00 ……..X…….
00000205a41b4c80 5c 00 5c 00 00 00 00 00-5e 00 00 00 00 00 00 00 \.\…..^……. 00000205a41b4c90 00 00 00 00 00 00 00 00-44 00 45 00 53 00 4b 00 ……..D.E.S.K.
00000205a41b4ca0 54 00 4f 00 50 00 2d 00-54 00 45 00 53 00 54 00 T.O.P.-.T.E.S.T. 00000205a41b4cb0 73 00 63 00 7a 00 40 00-40 00 44 00 07 00 08 00 s.c.z.@.@.D…..
00000205a41b4cc0 0c 00 0a 00 0d 00 59 00-41 00 41 00 41 00 41 00 ……Y.A.A.A.A. 00000205a41b4cd0 41 00 6e 00 50 00 41 00-41 00 41 00 41 00 41 00 A.n.P.A.A.A.A.A.
00000205a41b4ce0 41 00 41 00 41 00 41 00-53 00 48 00 58 00 7a 00 A.A.A.A.S.H.X.z. 00000205a41b4cf0 64 00 63 00 5a 00 65 00-5a 00 51 00 71 00 47 00 d.c.Z.e.Z.Q.q.G.
00000205a41b4d00 6f 00 76 00 37 00 47 00-57 00 61 00 54 00 6d 00 o.v.7.G.W.a.T.m. 00000205a41b4d10 5a 00 Z.

可能与winlogon通过RPC到UserManager服务有关,AuthenticationInformation不再直接对应MSV1_0_INTERACTIVE_LOGON结构。后来跟踪到usermgr!UserMgrCli::WrapWlAuthInfo中,AuthenticationInformation指向”struct UserMgrCli::_WL_AUTH_INFO”。手工解码如下


01 80 00 00 // +0x0 在usermgr!UserMgrCli::WrapWlAuthInfo+0x53处被设置
06 00 00 00 // +0x4
/*

buf/len均来自UserMgrCli::UMLogonUser的形参a7,其类型是”struct _AUTH_INFO_RPC *”
*/
ba 00 00 00 // +0x8 len=dwo()=0xba
00 00 00 00 // +0xc
40 42 0a 9c 05 02 00 00 // +0x10 buf=0x2059c0a4240
// db 0x2059c0a4240 l 0xba

// 跟后面的数据区相同

02 00 00 00 // +0x0 MessageType=dwo()=0x2

ff 7f 00 00 // +0x4 填充对齐

18 00 // +0x8 LogonDomainNameLen=wo()=0x18
18 00 // +0xa
ff 7f 00 00 // +0xc 填充对齐

40 00 00 00 00 00 00 00 // +0x10 LogonDomainNameOff=0x40

06 00 // +0x18 UserNameLen=wo()=0x6
06 00 // +0x1a
00 00 00 00 // +0x1c 填充对齐

58 00 00 00 00 00 00 00 // +0x20 UserNameOff=0x58

5c 00 // +0x28 PasswordLen=0x5c
5c 00 // +0x2a
00 00 00 00 // +0x2c 填充对齐

5e 00 00 00 00 00 00 00 // +0x30 PasswordOff=0x5e

00 00 00 00 00 00 00 00 // +0x38 填充对齐

44 00 45 00 53 00 4b 00 // +0x40 LogonDomainName=”DESKTOP-TEST”
54 00 4f 00 50 00 2d 00

54 00 45 00 53 00 54 00

73 00 63 00 7a 00 // +0x58 UserName=”scz”

40 00 40 00 44 00 07 00 // +0x5e Password=(混淆过)
08 00 0c 00 0a 00 0d 00
59 00 41 00 41 00 41 00 // +0x6e “YAAAAAnPAAAAAAAAASHXzdcZeZQqGov7GWaTmZ”
41 00 41 00 6e 00 50 00
41 00 41 00 41 00 41 00
41 00 41 00 41 00 41 00
41 00 53 00 48 00 58 00
7a 00 64 00 63 00 5a 00
65 00 5a 00 51 00 71 00
47 00 6f 00 76 00 37 00
47 00 57 00 61 00 54 00

6d 00 5a 00

☆ usermgrcli!UMgrLogonUser

看大框架图,winlogon会调用usermgrcli!UMgrLogonUser


usermgrcli!UMgrLogonUser
(
a1, // rcx
a2, // rdx
AuthenticationPackage, // r8
/*
* db @r9 l dwo(@rsp+0x28)
*/
AuthenticationInformation, // r9
AuthenticationInformationLength, // dwo(@rsp+0x28)

)

bp usermgrcli!UMgrLogonUser “db @r9 l dwo(@rsp+0x28)”

# Call Site
00 usermgrcli!UMgrLogonUser
01 winlogon!AuthenticateUser+0x556
02 winlogon!WLGeneric_Authenticating_Execute+0x45e
03 winlogon!StateMachineWorkerCallback+0xc8
04 ntdll!TppWorkpExecuteCallback+0x35e
05 ntdll!TppWorkerThread+0x474
06 KERNEL32!BaseThreadInitThunk+0x14
07 ntdll!RtlUserThreadStart+0x21

000001e3b4d70000 02 00 00 00 ff 7f 00 00-18 00 18 00 ff 7f 00 00 ……………. 000001e3b4d70010 40 00 00 00 00 00 00 00-06 00 06 00 00 00 00 00 @……………
000001e3b4d70020 58 00 00 00 00 00 00 00-5c 00 5c 00 00 00 00 00 X…….\.\….. 000001e3b4d70030 5e 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ^……………
000001e3b4d70040 44 00 45 00 53 00 4b 00-54 00 4f 00 50 00 2d 00 D.E.S.K.T.O.P.-. 000001e3b4d70050 54 00 45 00 53 00 54 00-73 00 63 00 7a 00 40 00 T.E.S.T.s.c.z.@.
000001e3b4d70060 40 00 44 00 07 00 08 00-0c 00 0a 00 0d 00 59 00 @.D………..Y. 000001e3b4d70070 41 00 41 00 41 00 41 00-41 00 6e 00 50 00 41 00 A.A.A.A.A.n.P.A.
000001e3b4d70080 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A. 000001e3b4d70090 53 00 48 00 58 00 7a 00-64 00 63 00 5a 00 65 00 S.H.X.z.d.c.Z.e.
000001e3b4d700a0 5a 00 51 00 71 00 47 00-6f 00 76 00 37 00 47 00 Z.Q.q.G.o.v.7.G. 000001e3b4d700b0 57 00 61 00 54 00 6d 00-5a 00 W.a.T.m.Z.

对混淆过的Password设置数据断点,逐步溯源至

# Call Site
00 RPCRT4!Ndr64ComplexStructUnmarshall+0x503
01 RPCRT4!Ndr64pClientUnMarshal+0x28f
02 RPCRT4!NdrpClientCall3+0x1010
03 RPCRT4!NdrClientCall3+0xf2
04 winlogon!WluiRequestCredentials+0xa4
05 winlogon!RequestCredentials+0x9f
06 winlogon!WLGeneric_Request_Logon_Credz_Execute+0x190
07 winlogon!StateMachineWorkerCallback+0x7f
08 ntdll!TppWorkpExecuteCallback+0x35e
09 ntdll!TppWorkerThread+0x474
0a KERNEL32!BaseThreadInitThunk+0x14
0b ntdll!RtlUserThreadStart+0x21

长度为0xba的数据来自winlogon!WluiRequestCredentials,调用栈表明正在返回RPC结果,其服务端是logoncontroller!WluirRequestCredentials。这意味着对明文口令”xxx”的混淆工作很可能发生在LogonUI.exe进程空间。

八、logoncontroller!WluirRequestCredentials


winlogon!WluiRequestCredentials
(
a1,
a2,
a3,
/*
* out型形参,返回时会设置poi(@r9+0x10)、dwo(@r9+8)
*
* 对应logoncontroller!WluirRequestCredentials
*/
sth, // r9
a5

)

logoncontroller!WluirRequestCredentials
(
a1,
a2,
a3,
a4,
/*
* 对应winlogon!WluiRequestCredentials的sth
*/
sth, // poi(@rsp+0x28)
a6

)

sth就是长度为0xba的数据。对sth中混淆过的Password设置数据断点,异常艰难地逐步溯源,中途几欲放弃,这个时候特别怀念VMware 7之前的Record/Replay功能。后来溯源至credprovs!KerbInteractiveUnlockLogonPack。

☆ credprovs!KerbInteractiveUnlockLogonPack


credprovs!KerbInteractiveUnlockLogonPack
(
/*
* dt -r uxtheme!_KERB_INTERACTIVE_UNLOCK_LOGON @rcx
*/
a1,

)

该函数第一形参指向下述结构

dt -r uxtheme!_KERB_INTERACTIVE_UNLOCK_LOGON @rcx
+0x000 Logon : _KERB_INTERACTIVE_LOGON
+0x000 MessageType : 2 ( KerbInteractiveLogon )
+0x008 LogonDomainName : _UNICODE_STRING “DESKTOP-TEST”
+0x000 Length : 0x18
+0x002 MaximumLength : 0x1a
+0x008 Buffer : 0x000001cf0b0d5b60 “DESKTOP-TEST” +0x018 UserName : _UNICODE_STRING “scz” +0x000 Length : 6 +0x002 MaximumLength : 8 +0x008 Buffer : 0x000001cf0679b020 “scz”
+0x028 Password : _UNICODE_STRING “@@D???”
+0x000 Length : 0x5c
+0x002 MaximumLength : 0x5e
+0x008 Buffer : 0x000001cf`0a86f980 “@@D???”
+0x038 LogonId : _LUID
+0x000 LowPart : 0
+0x004 HighPart : 0n0

如下断点可以查看混淆过的Password

bp credprovs!KerbInteractiveUnlockLogonPack “r $t0=@rcx+0x28;db poi(@$t0+8) l wo(@$t0)”

☆ sechost!CredProtectW

从credprovs!KerbInteractiveUnlockLogonPack开始,对混淆过的Password用数据断点溯源,这个时候就比较容易了。后来溯源至

# Call Site
00 ntdll!memcpy+0xb9
01 sechost!CredProtectW+0xa6
02 credprovs!CPasswordCredential::_EncryptAndMarshal+0xfe
03 credprovs!CPasswordCredential::_KerbInteractiveUnlockLogonFill+0xf7
04 credprovs!CPasswordCredential::_GetSerializationUnlockLogon+0x7f
05 credprovs!CPasswordCredential::GetSerialization+0x1f4
06 credprovhost!CGetSerializationJob::Do+0x224
07 credprovhost!CJobQueue::DoJobInternal+0xec
08 credprovhost!CCredentialProviderThread::_vThreadProc+0x355
09 credprovhost!CCredentialProviderThread::_sThreadProc+0xc7
0a KERNEL32!BaseThreadInitThunk+0x14
0b ntdll!RtlUserThreadStart+0x21

sechost!CredProtectW是文档化的导出函数,参[3],大致流程如下


BOOL CredProtectW
(
[in] BOOL fAsSelf,
/*
* 明文口令
/ [in] LPWSTR pszCredentials, // rdx /
* 明文口令长度,是字符长度,不是字节长度
/ [in] DWORD cchCredentials, // r8 /
* 返回混淆后的Password
/ [out] LPWSTR pszProtectedCredentials, // r9 /
* 返回混淆后的Password长度,是字符长度,不是字节长度
*/
[in, out] DWORD *pcchMaxChars, // poi(@rsp+0x28)
[out] CRED_PROTECTION_TYPE *ProtectionType

)

sechost!CredProtectW
sechost!CredpEncryptAndMarshalBinaryBlobEx // db @r8 l @r9
sechost!CredpEncodeSecretEx // 对明文口令做混淆,混淆结果是16字节字节流
// db @rdx l @r8
CRYPTBASE!SystemFunction040 // db @rcx l @rdx
// 该函数就地混淆(加密)
ntdll!NtDeviceIoControlFile // db poi(@rsp+0x38) l dwo(@rsp+0x40)
// 访问”\Device\KsecDD”
// ? qwo(CRYPTBASE!g_hKsecDD)

sechost!CredMarshalCredentialW // 对混淆结果序列化

在LogonUI进程空间拦截sechost!CredProtectW,查看交互式登录的明文口令

bp sechost!CredProtectW “db @rdx l @r8*2”

有两次命中,第一次先计算结果长度,第二次才生成结果,非常熟悉的Win32 API套路。此外,下列断点都可以查看交互式登录的明文口令

bp sechost!CredpEncryptAndMarshalBinaryBlobEx “db @r8 l @r9”
bp sechost!CredpEncodeSecretEx “db @rdx l @r8”
bp CRYPTBASE!SystemFunction040 “db @rcx l @rdx”
bp ntdll!NtDeviceIoControlFile “db poi(@rsp+0x38) l dwo(@rsp+0x40)”

☆ sechost!CredUnprotectW

sechost!CredUnprotectW是逆函数,也是文档化的导出函数,参[3]


BOOL CredUnprotectW
(
[in] BOOL fAsSelf,
/*
* 混淆过的Password
/ [in] LPWSTR pszProtectedCredentials, // rdx /
* 混淆过的Password长度
/ [in] DWORD cchProtectedCredentials, // r8 /
* 返回明文口令
/ [out] LPWSTR pszCredentials, // r9 /
* 返回明文口令长度
*/
[in, out] DWORD *pcchMaxChars // poi(@rsp+0x28)

)

调试lsass进程

bp sechost!CredUnprotectW “db @rdx l @r8*2”

000002518519b110 40 00 40 00 44 00 07 00-08 00 0c 00 0a 00 0d 00 @.@.D........... 000002518519b120 59 00 41 00 41 00 41 00-41 00 41 00 6e 00 50 00 Y.A.A.A.A.A.n.P.
000002518519b130 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A. 000002518519b140 41 00 53 00 48 00 58 00-7a 00 64 00 63 00 5a 00 A.S.H.X.z.d.c.Z.
000002518519b150 65 00 5a 00 51 00 71 00-47 00 6f 00 76 00 37 00 e.Z.Q.q.G.o.v.7. 000002518519b160 47 00 57 00 61 00 54 00-6d 00 5a 00 00 00 G.W.a.T.m.Z…

# Call Site
00 sechost!CredUnprotectW
01 lsasrv!LsapDecodeSecret+0xe9
02 lsasrv!LsapConvertLogonAuthInfo+0x253
03 lsasrv!SspiExLogonUser+0x610
04 SspiSrv!SspirLogonUser+0x247
05 RPCRT4!Invoke+0x73
06 RPCRT4!Ndr64StubWorker+0xbb1
07 RPCRT4!NdrServerCallAll+0x3c
08 RPCRT4!DispatchToStubInCNoAvrf+0x24
09 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1bd
0a RPCRT4!RPC_INTERFACE::DispatchToStub+0xcb
0b RPCRT4!LRPC_SCALL::DispatchRequest+0x34c
0c RPCRT4!LRPC_SCALL::HandleRequest+0x2bc
0d RPCRT4!LRPC_ADDRESS::HandleRequest+0x36c
0e RPCRT4!LRPC_ADDRESS::ProcessIO+0x91b
0f RPCRT4!LrpcIoComplete+0xaa
10 ntdll!TppAlpcpExecuteCallback+0x25e
11 ntdll!TppWorkerThread+0x8d9
12 KERNEL32!BaseThreadInitThunk+0x14
13 ntdll!RtlUserThreadStart+0x21

看到前面介绍过的SspiSrv!SspirLogonUser,它会间接调用sechost!CredUnprotectW获取明文口令。

sechost!CredUnprotectW大致流程如下


sechost!CredUnprotectW
sechost!CredpUnmarshalandDecodeStringEx // db @r8
// du @r8
sechost!CredUnmarshalCredentialW // 反序列化
// db @rcx
// du @rcx
sechost!CredpUnmarshalandDecodeStringEx+0xbc // 查看明文口令
// p;db poi(@rbp+0x60) l dwo(@rbp+38)
sechost!CredpDecodeSecretEx // 反混淆,r8此时等于0x10
// 查看密文口令
// db @rdx l @r8
sechost!CredpDecodeSecretEx+0x132
CRYPTBASE!SystemFunction041 // db @rcx l @rdx
// 该函数就地反混淆(解密)
CRYPTBASE!SystemFunction041+0x55
ntdll!NtDeviceIoControlFile // db poi(@rsp+0x38) l dwo(@rsp+0x40)
// 访问”\Device\KsecDD”

// ? qwo(CRYPTBASE!g_hKsecDD)

在lsass进程空间查看交互式登录的明文口令

bp sechost!CredpUnmarshalandDecodeStringEx+0xc1 “db poi(@rbp+0x60) l dwo(@rbp+0x38)”
bp sechost!CredUnprotectW+0x6a “db poi(@rsp+0x48) l dwo(@rsp+0x40)”

☆ winlogon的状态与信号

因为[1]提到了winlogon的状态与信号,这里也说一下Win10的情况。Win10没有Win7的StateMachineSetSignal,只有SignalManagerSetSignal

winlogon!WlStateMachineSetSignal
winlogon!SignalManagerSetSignal

Win10的winlogon共有100个状态,41个信号

dps winlogon!g_rpWLGeneric_States l 0n100
winlogon!g_xWLGeneric_Start_State
winlogon!g_xWLGeneric_NotifyCreateSession_State
winlogon!g_xWLGeneric_Welcome_State
….
winlogon!g_xWLGeneric_PseudoLogging_Off3_Unlock_State
winlogon!g_xWLGeneric_NotifyTerminateSession_State

dqs winlogon!g_rpWLGeneric_Signals l 0n41
winlogon!g_xAction_Succeeded_Signal
winlogon!g_xAction_Failed_Signal
winlogon!g_xLogoff_NtUserCompleted_Signal

winlogon!g_xWLGeneric_ChangePassword_Signal
winlogon!g_xWLGeneric_ChangeIsAlreadyDone_Signal

九、后记

本文只研究了[1]在Win10中的变化,没有进一步拓展。有兴趣继续深入研究这个方向的朋友,可以参照框架图设断、动态调试。

十、参考资源

[1] Windows7口令认证流程调试 – boywhp [2013-02-14]
https://bbs.pediy.com/thread-162632-1.htm

[2] LsaLogonUser function (ntsecapi.h)
https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsalogonuser

SECURITY_LOGON_TYPE enumeration (ntsecapi.h)
https://docs.microsoft.com/en-us/windows/desktop/api/ntsecapi/ne-ntsecapi-security_logon_type

MSV1_0_INTERACTIVE_LOGON structure (ntsecapi.h)
https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapi-msv1_0_interactive_logon

[3] CredProtectW function (wincred.h)
https://docs.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credprotectw

CredUnprotectW function (wincred.h)
https://docs.microsoft.com/en-us/windows/win32/api/wincred/nf-wincred-credunprotectw

CRED_PROTECTION_TYPE enumeration (wincred.h)
https://docs.microsoft.com/en-us/windows/win32/api/wincred/ne-wincred-cred_protection_type

版权声明

本站“技术博客”所有内容的版权持有者为绿盟科技集团股份有限公司(“绿盟科技”)。作为分享技术资讯的平台,绿盟科技期待与广大用户互动交流,并欢迎在标明出处(绿盟科技-技术博客)及网址的情形下,全文转发。
上述情形之外的任何使用形式,均需提前向绿盟科技(010-68438880-5462)申请版权授权。如擅自使用,绿盟科技保留追责权利。同时,如因擅自使用博客内容引发法律纠纷,由使用者自行承担全部法律责任,与绿盟科技无关。

Spread the word. Share this post!

Meet The Author

C/ASM程序员

Leave Comment