调试services.exe进程

一、ServicesPipeTimeout

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

Windows的SCM(Service Control Manager)启动某个服务时缺省等待30秒,超时则认为启动失败,会有其他动作,比如杀掉目标进程重启服务。假设需要调试服务启动阶段代码,断点命中后的交互式调试很容易导致服务启动阶段超时被杀,可能上一步还在kpn,下一步发现目标进程不在了。这很影响调试,幸好有注册表设置这个超时。

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control]

“ServicesPipeTimeout”=dword:15752a00

reg.exe add “HKLM\SYSTEM\CurrentControlSet\Control” /v “ServicesPipeTimeout” /t REG_DWORD /d 0x15752a00 /f
reg.exe query “HKLM\SYSTEM\CurrentControlSet\Control” /v “ServicesPipeTimeout”
reg.exe delete “HKLM\SYSTEM\CurrentControlSet\Control” /v “ServicesPipeTimeout” /f

0x15752a00是360000000,单位是毫秒,换算过来就是100小时。需要重启OS使之生效。该值缺省30000,即30秒。

碰上一个场景,Guest接有kd,但未提前设置过ServicesPipeTimeout,因故不方便重启OS,想找个热Patch方案让ServicesPipeTimeout生效。后来确实找到此场景下的热Patch方案,简介如下:

用”.process /i;g”切到services.exe进程空间,对nt!NtCreateUserProcess设断。
用”sc start spooler”触发内核态断点,然后

Patch

ed services!g_dwScControlMessageTimeout 0n360000000
ed services!g_dwHandlerTimeout 0n360000000
eb services!g_fDefaultControlMessageTimeout 0

UnPatch

ed services!g_dwScControlMessageTimeout 0n30000
ed services!g_dwHandlerTimeout 0n30000
eb services!g_fDefaultControlMessageTimeout 1

二、ServicesPipeTimeout热Patch方案的寻找过程

本节介绍一下热Patch方案寻找过程,讲讲为什么这样就可以了,这个可能更有意义。

不是常年浸淫Windows逆向工程之人,事起突然,只能从大的逻辑层面开始思考。”sc start”时既然有30s超时,那去看看创建服务进程时的代码,在附近找找哪些调用涉及超时设置。

services.exe就是SCM的核心,它创建服务进程时可能过nt!NtCreateUserProcess。

kd> .shell -ci “!dml_proc” findstr services.exe
ffffda88`4002c480 30c services.exe

kd> !process 0 0 services.exe
PROCESS ffffda884002c480
SessionId: 0 Cid: 030c Peb: 13e8b6c000 ParentCid: 02ac
DirBase: 2198e3000 ObjectTable: ffffa08d0c9a0e80 HandleCount:
Image: services.exe

.process /i ffffda884002c480;g
.reload /f /user
ba e1 /1 /p @$proc nt!NtCreateUserProcess

回到Guest,在管理员级cmd中执行

sc start spooler

前述内核态断点命中,调用栈回溯如下

# Child-SP RetAddr Call Site
00 ffffb1016bfa0a88 fffff802b7d75103 nt!NtCreateUserProcess
01 ffffb1016bfa0a90 00007fff88d97414 nt!KiSystemServiceCopyEnd+0x13
02 00000013ae2fd788 00007fff8609b860 ntdll!NtCreateUserProcess+0x14
03 00000013ae2fd790 00007fff860df453 KERNELBASE!CreateProcessInternalW+0x1610
04 00000013ae2fe260 00007fff885cd37f KERNELBASE!CreateProcessAsUserW+0x63
05 00000013ae2fe2d0 00007ff6f110307d KERNEL32!CreateProcessAsUserWStub+0x5f
06 00000013ae2fe340 00007ff6f1106334 services!ScLogonAndStartImage+0x41d
07 00000013ae2fe5c0 00007ff6f110dc24 services!ScStartService+0x4d4
08 00000013ae2fe740 00007ff6f110d3ee services!ScStartMarkedServicesInServiceSet+0x1a4
09 00000013ae2fe7c0 00007ff6f1104d0c services!ScStartServiceAndDependencies+0x3de
0a 00000013ae2fe890 00007fff8817a583 services!RStartServiceW+0xfc
0b 00000013ae2fe900 00007fff88122b4b RPCRT4!Invoke+0x73
0c 00000013ae2fe960 00007fff881653ea RPCRT4!NdrStubCall2+0x46b
0d 00000013ae2feff0 00007fff8814a284 RPCRT4!NdrServerCall2+0x1a
0e 00000013ae2ff020 00007fff8814919d RPCRT4!DispatchToStubInCNoAvrf+0x24
0f 00000013ae2ff070 00007fff88149a4b RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1bd
10 00000013ae2ff140 00007fff881310ac RPCRT4!RPC_INTERFACE::DispatchToStub+0xcb
11 00000013ae2ff1a0 00007fff8813152c RPCRT4!LRPC_SCALL::DispatchRequest+0x34c
12 00000013ae2ff280 00007fff8811ae1c RPCRT4!LRPC_SCALL::HandleRequest+0x2bc
13 00000013ae2ff3a0 00007fff8811c67b RPCRT4!LRPC_ADDRESS::HandleRequest+0x36c
14 00000013ae2ff450 00007fff88143a2a RPCRT4!LRPC_ADDRESS::ProcessIO+0x91b
15 00000013ae2ff590 00007fff88d0d35e RPCRT4!LrpcIoComplete+0xaa
16 00000013ae2ff630 00007fff88d0ecc9 ntdll!TppAlpcpExecuteCallback+0x25e
17 00000013ae2ff6e0 00007fff885b84d4 ntdll!TppWorkerThread+0x8d9
18 00000013ae2ffae0 00007fff88d41791 KERNEL32!BaseThreadInitThunk+0x14
19 00000013ae2ffb10 0000000000000000 ntdll!RtlUserThreadStart+0x21

sc.exe扮演”RPC Client”,services.exe扮演”RPC Server”,后者负责启动服务进程。

利用调用栈上的数据获取RPC请求的更多信息

IID 367abb81-9844-35f1-ad32-98f038001003
ProcNum 19 (services!RStartServiceW)

之前想得美,以为在services!ScStartService附近能找到超时相关的代码,没找到,
或者说不显眼?后来反应过来,我把简单问题复杂化了,直接IDA分析services.exe,
看”ServicesPipeTimeout”的交叉引用即可。一步定位,注意到下列代码片段:

if
(
RegQueryValueExW( hKey, “ServicesPipeTimeout”, 0, &Type, &g_dwScControlMessageTimeout, &cbData )
||
Type != 4
)
{
/*
* 注册表中无ServicesPipeTimeout,使用缺省值30s
/ g_dwScControlMessageTimeout = 30000; } else { /
* 注册表中有ServicesPipeTimeout时将这个全局变量清零,其初值为1
/ g_fDefaultControlMessageTimeout = 0; } if ( RegQueryValueExW( hKey, “HandlerTimeout”, 0, &Type, &g_dwHandlerTimeout, &cbData ) || Type != 4 ) { /
* 注册表中无HandlerTimeout,用ServicesPipeTimeout初始化HandlerTimeout,
* 于是一般情况下二者相等
/ g_dwHandlerTimeout = g_dwScControlMessageTimeout & 0x7fffffff; } else if ( g_dwHandlerTimeout && ( g_dwHandlerTimeout & 0x7fffffff ) < g_dwScControlMessageTimeout ) { /
* 注册表中有HandlerTimeout,一般情况下这个数学运算相当于给HandlerTimeout
* 赋值ServicesPipeTimeout
*/
g_dwHandlerTimeout ^= ( g_dwScControlMessageTimeout ^ g_dwHandlerTimeout) & 0x7fffffff;

}

在一台注册表中未设置ServicesPipeTimeout的Guest中用livekd检查这三个全局变量

livekd.exe -k kd.exe

kd> !process 0 0 services.exe
PROCESS ffff9f8a2d70d800

kd> .process /p /r ffff9f8a2d70d800

kd> ? dwo(services!g_dwScControlMessageTimeout)
Evaluate expression: 30000 = 0000000000007530 kd> ? dwo(services!g_dwHandlerTimeout) Evaluate expression: 30000 = 0000000000007530
kd> ? by(services!g_fDefaultControlMessageTimeout)
Evaluate expression: 1 = 00000000`00000001

用”.detach”退出livekd。

在IDA中查看这三个全局变量的交叉引用,会涉及WaitForMultipleObjectsEx之类的调用。理论上注册表中有两个设置

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control]
“ServicesPipeTimeout”=dword:15752a00

“HandlerTimeout”=dword:15752a00

reg.exe add “HKLM\SYSTEM\CurrentControlSet\Control” /v “ServicesPipeTimeout” /t REG_DWORD /d 0x15752a00 /f
reg.exe query “HKLM\SYSTEM\CurrentControlSet\Control” /v “ServicesPipeTimeout”
reg.exe delete “HKLM\SYSTEM\CurrentControlSet\Control” /v “ServicesPipeTimeout” /f

reg.exe add “HKLM\SYSTEM\CurrentControlSet\Control” /v “HandlerTimeout” /t REG_DWORD /d 0x15752a00 /f
reg.exe query “HKLM\SYSTEM\CurrentControlSet\Control” /v “HandlerTimeout”
reg.exe delete “HKLM\SYSTEM\CurrentControlSet\Control” /v “HandlerTimeout” /f

HandlerTimeout在网上搜不到有效信息。现在回头看热Patch方案就很好理解了

Patch

ed services!g_dwScControlMessageTimeout 0n360000000
ed services!g_dwHandlerTimeout 0n360000000
eb services!g_fDefaultControlMessageTimeout 0

UnPatch

ed services!g_dwScControlMessageTimeout 0n30000
ed services!g_dwHandlerTimeout 0n30000
eb services!g_fDefaultControlMessageTimeout 1

三、cdb无法调试services.exe

静态想出热Patch方案后,最初想用cdb去改三个全局变量,发现无法Attach到services.exe,只好在kd里改。后来确认,services.exe是PPL进程,OS对之有额外的保护。

参[1],Alex Ionescu写了几篇精彩的PPL技术blog,看过就大致明白PPL是个啥。

两个缩写

PPL (Protected Process Light)
TCB (Trusted Computing Base)

几个结构、枚举型

kd> dt nt!_EPROCESS Protection
+0x6ca Protection : _PS_PROTECTION

普通进程Protection为0,PPL进程该值不为0。

kd> dt nt!_PS_PROTECTION
+0x000 Level : UChar
+0x000 Type : Pos 0, 3 Bits
+0x000 Audit : Pos 3, 1 Bit
+0x000 Signer : Pos 4, 4 Bits

kd> dt nt!_PS_PROTECTED_TYPE
PsProtectedTypeNone = 0n0
PsProtectedTypeProtectedLight = 0n1
PsProtectedTypeProtected = 0n2
PsProtectedTypeMax = 0n3

kd> dt nt!_PS_PROTECTED_SIGNER
PsProtectedSignerNone = 0n0
PsProtectedSignerAuthenticode = 0n1
PsProtectedSignerCodeGen = 0n2
PsProtectedSignerAntimalware = 0n3
PsProtectedSignerLsa = 0n4
PsProtectedSignerWindows = 0n5
PsProtectedSignerWinTcb = 0n6
PsProtectedSignerWinSystem = 0n7
PsProtectedSignerMax = 0n8

kd> dt nt!_EPROCESS SeAuditProcessCreationInfo.ImageFileName->Name
+0x468 SeAuditProcessCreationInfo :
+0x000 ImageFileName :
+0x000 Name : _UNICODE_STRING

kd> dt nt!_EPROCESS ImageFileName
+0x450 ImageFileName : [15] UChar

列出系统中所有Protection不为0的进程

kd> !for_each_process “r? $t0=(nt!_EPROCESS*) @#Process;.if(@@(@$t0->Protection.Level)){.printf \”%-5d [%-53msu] %#x\n\”,@@(@$t0->UniqueProcessId),@@(&@$t0->SeAuditProcessCreationInfo.ImageFileName->Name),@@(@$t0->Protection.Level)}”
4 [ ] 0x72
500 [\Device\HarddiskVolume4\Windows\System32\smss.exe ] 0x61
588 [\Device\HarddiskVolume4\Windows\System32\csrss.exe ] 0x61
652 [\Device\HarddiskVolume4\Windows\System32\smss.exe ] 0x61
660 [\Device\HarddiskVolume4\Windows\System32\csrss.exe ] 0x61
684 [\Device\HarddiskVolume4\Windows\System32\wininit.exe ] 0x61
780 [\Device\HarddiskVolume4\Windows\System32\services.exe] 0x61
1864 [ ] 0x72
6160 [\Device\HarddiskVolume4\ProgramData\Microsoft\Windows Defender\platform\4.18.2111.5-0\MsMpEng.exe] 0x31
3484 [\Device\HarddiskVolume4\ProgramData\Microsoft\Windows Defender\platform\4.18.2111.5-0\NisSrv.exe] 0x31

注意”(nt!_EPROCESS*) @#Process”中间有个空格,否则语法解析时就报错。具体拆解这几个Protection

0x72 PsProtectedSignerWinSystem | PsProtectedTypeProtected
0x61 PsProtectedSignerWinTcb | PsProtectedTypeProtectedLight
0x31 PsProtectedSignerAntimalware | PsProtectedTypeProtectedLight

PPL进程Protection一般是0x?1,比如0x61、0x31。PID(1864)这个进程有点意思,应该没有文件与之对应

kd> !process 0n1864 0
Searching for Process with Cid == 748
PROCESS ffffda883fc23040
SessionId: none Cid: 0748 Peb: 00000000 ParentCid: 0004
DirBase: 19b11000 ObjectTable: ffffa08d18c34040 HandleCount:
Image: MemCompression

kd> dt nt!_EPROCESS ImageFileName ffffda883fc23040
+0x450 ImageFileName : [15] “MemCompression”

单独查看services.exe的Protection

kd> !process 0 0 services.exe
PROCESS ffffda884002c480
SessionId: 0 Cid: 030c Peb: 13e8b6c000 ParentCid: 02ac
DirBase: 2243e3000 ObjectTable: ffffa08d0c9a0e80 HandleCount:
Image: services.exe

kd> dx ((nt!_EPROCESS)0xffffda884002c480)->Protection->Type : 0x1 [Type: unsigned char] kd> dx ((nt!_EPROCESS)0xffffda884002c480)->Protection->Signer
: 0x6 [Type: unsigned char]

PsProtectedSignerWinTcb | PsProtectedTypeProtectedLight

用Process Explorer查看services.exe的Security页,显示的是PsProtectedSignerWinTcb-Light

若A进程想打开B进程对之调试,A进程的Protection必须不小于B进程的Protection。正常情况下即便在管理员级cmd中启动cdb,其Protection仍为0,小于services.exe的0x61,前者无法调试后者。从Win8开始有这种保护机制。

kd在场的情况下,有多种办法应对,最不靠谱的办法是将services.exe降为0,调试环境无所谓吧。

dx ((nt!_EPROCESS)0xffffda884002c480)->Protection->Level dx ((nt!_EPROCESS)0xffffda884002c480)->Protection->Level=0

在Guest中以管理员身份运行

C:\temp\dbgsrv.exe -t tcp:port=8765,password=8765

在Host中

cdb.exe -noinh -snul -hd -o -premote tcp:server=192.168.65.136,port=8765,password=8765 -pn services.exe

不再提示”拒绝访问”,成功Attach。

也可将dbgsrv.exe提升到0x72或0x61,不动services.exe的0x61,之后即可调试。

kd> !process 0 0 dbgsrv.exe
PROCESS ffffda8843895080
SessionId: 1 Cid: 0b1c Peb: e6efd4d000 ParentCid: 1110
DirBase: 1fea00000 ObjectTable: ffffa08d1c0810c0 HandleCount:
Image: dbgsrv.exe

kd> dx ((nt!_EPROCESS*)0xffffda8843895080)->Protection->Level=0x72

推荐这种办法,从此dbgsrv可以调试任意进程。既然必须先有kd,为何还要用dbgsrv?用kd直接调试用户态代码毕竟不方便,这就是理由。

四、Win10的lsass.exe缺省不是PPL进程

编者对Windows系统研究很少,PPL这种从Win8就有的技术我是今年10月才知道的。确切地说,同事在我提RpcView之后推荐findrpc.py,看findrpc.py的说明时我知道了PPL。当时误以为Win10的lsass.exe是PPL进程,但实际上缺省不是的。有个注册表项可以让lsass.exe成为PPL进程

reg.exe query “HKLM\SYSTEM\CurrentControlSet\Control\Lsa” /v “RunAsPPL”

不建议盲目启用之。这个一旦启用,再想恢复到缺省状态将非常麻烦,微软甚至专门发布了一款工具用于回滚lsass.exe,参[3]。其根本原因在于,一旦从注册表启用,OS会将启用状态保存到”UEFI firmware database”,之后就不用注册表的设置了。即便用WinPE删除RunAsPPL,也不能恢复到缺省状态,必须用微软的重置工具。由于测试太过麻烦,小编在此略过。

lsass.exe成为PPL进程会有效阻止Mimikatz这类工具,但需要case by case地启用,
以防其他不兼容的情况出现。

五、以调试services.exe子进程的方式调试spoolsv.exe

假设已经调整过Protection、ServicesPipeTimeout,在此前提下,以调试services.exe子进程的方式调试spoolsv.exe完全可行。

在Guest中以管理员身份运行

C:\temp\dbgsrv.exe -t tcp:port=8765,password=8765

在Host中

cdb.exe -noinh -snul -hd -o -premote tcp:server=192.168.65.136,port=8765,password=8765 -pn services.exe

0:006> .childdbg
Processes created by the current process will not be debugged

命令行上的-o没生效,可能Attach方式时就是不生效。用”.childdbg 1″显式允许调试子进程

0:006> .childdbg 1
Processes created by the current process will be debugged
0:006> |
. 0 id: 30c attach name: C:\Windows\system32\services.exe
0:006> g

当前只有父进程services.exe,现在去”sc start spooler”,cdb立刻断下。用”|”查看被调试的所有进程

1:006> |
0 id: 30c attach name: C:\Windows\system32\services.exe
. 1 id: 58c child name: spoolsv.exe

我们只想从头调试spoolsv.exe,已经不需要调试services.exe。

1:006> |0s;.detach;|
Detached
. 1 id: 58c child name: spoolsv.exe

现在cdb只调试spoolsv.exe,位于初始化断点(ibp)处

1:006> kpn
# Child-SP RetAddr Call Site
00 000000000079ee80 00007fff88d83268 ntdll!LdrpDoDebuggerBreak+0x30
01 000000000079eec0 00007fff88d68145 ntdll!LdrpInitializeProcess+0x1be4
02 000000000079f2d0 00007fff88d67fae ntdll!LdrpInitialize+0x141
03 000000000079f350 0000000000000000 ntdll!LdrInitializeThunk+0xe

借助kd之后可以这样调试特定服务的启动阶段,算是下文的补充

《MSDN系列(41)–调试Windows服务》
http://scz.617.cn:8/windows/202111291159.txt

过去services.exe不是PPL进程,无需借助kd就可以这样干。注意,可在用户态PatchServicesPipeTimeout。

参考资源

[1] The Evolution of Protected Processes Part 1: Pass-the-Hash Mitigations in Windows 8.1 – Alex Ionescu [2013-11-05]
http://www.alex-ionescu.com/?p=97

The Evolution of Protected Processes Part 2: Exploit/Jailbreak Mitigations, Unkillable Processes and Protected Services – Alex Ionescu [2013-12-10]
https://www.alex-ionescu.com/?p=116
(PspProcessOpen/PspThreadOpen)

Protected Processes Part 3 : Windows PKI Internals (Signing Levels, Scenarios, Root Keys, EKUs & Runtime Signers) – Alex Ionescu [2013-12-28]
https://www.alex-ionescu.com/?p=146

Unreal mode: Breaking Protected Processes – Alex Ionescu [2014]
https://www.nosuchcon.org/talks/2014/D3_05_Alex_ionescu_Breaking_protected_processes.pdf
(讲了回滚lsass.exe)

[2] UpdateProcThreadAttribute function (processthreadsapi.h)
https://docs.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute

[3] Local Security Authority (LSA) Protected Process Opt-out
https://www.microsoft.com/en-us/download/details.aspx?id=40897
(An efi tool to disable LSA’s protected process setting on machines with secure boot)

[4] PPLKiller
https://github.com/Mattiwatti/PPLKiller
(a kernel mode driver that disables Protected Process Light protection on all running processes)
(从Windows 10.0.18362.0开始,会触发PatchGuard)

版权声明

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

Spread the word. Share this post!

Meet The Author

C/ASM程序员

Leave Comment