标题: MSDN系列(45)--调试services.exe进程 创建: 2021-12-22 13:49 更新: 2021-12-23 17:53 链接: https://scz.617.cn/windows/202112221349.txt -------------------------------------------------------------------------- 目录: ☆ ServicesPipeTimeout ☆ ServicesPipeTimeout热Patch方案的寻找过程 ☆ cdb无法调试services.exe ☆ Win10的lsass.exe缺省不是PPL进程 ☆ 以调试services.exe子进程的方式调试spoolsv.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 ffffb101`6bfa0a88 fffff802`b7d75103 nt!NtCreateUserProcess 01 ffffb101`6bfa0a90 00007fff`88d97414 nt!KiSystemServiceCopyEnd+0x13 02 00000013`ae2fd788 00007fff`8609b860 ntdll!NtCreateUserProcess+0x14 03 00000013`ae2fd790 00007fff`860df453 KERNELBASE!CreateProcessInternalW+0x1610 04 00000013`ae2fe260 00007fff`885cd37f KERNELBASE!CreateProcessAsUserW+0x63 05 00000013`ae2fe2d0 00007ff6`f110307d KERNEL32!CreateProcessAsUserWStub+0x5f 06 00000013`ae2fe340 00007ff6`f1106334 services!ScLogonAndStartImage+0x41d 07 00000013`ae2fe5c0 00007ff6`f110dc24 services!ScStartService+0x4d4 08 00000013`ae2fe740 00007ff6`f110d3ee services!ScStartMarkedServicesInServiceSet+0x1a4 09 00000013`ae2fe7c0 00007ff6`f1104d0c services!ScStartServiceAndDependencies+0x3de 0a 00000013`ae2fe890 00007fff`8817a583 services!RStartServiceW+0xfc 0b 00000013`ae2fe900 00007fff`88122b4b RPCRT4!Invoke+0x73 0c 00000013`ae2fe960 00007fff`881653ea RPCRT4!NdrStubCall2+0x46b 0d 00000013`ae2feff0 00007fff`8814a284 RPCRT4!NdrServerCall2+0x1a 0e 00000013`ae2ff020 00007fff`8814919d RPCRT4!DispatchToStubInCNoAvrf+0x24 0f 00000013`ae2ff070 00007fff`88149a4b RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1bd 10 00000013`ae2ff140 00007fff`881310ac RPCRT4!RPC_INTERFACE::DispatchToStub+0xcb 11 00000013`ae2ff1a0 00007fff`8813152c RPCRT4!LRPC_SCALL::DispatchRequest+0x34c 12 00000013`ae2ff280 00007fff`8811ae1c RPCRT4!LRPC_SCALL::HandleRequest+0x2bc 13 00000013`ae2ff3a0 00007fff`8811c67b RPCRT4!LRPC_ADDRESS::HandleRequest+0x36c 14 00000013`ae2ff450 00007fff`88143a2a RPCRT4!LRPC_ADDRESS::ProcessIO+0x91b 15 00000013`ae2ff590 00007fff`88d0d35e RPCRT4!LrpcIoComplete+0xaa 16 00000013`ae2ff630 00007fff`88d0ecc9 ntdll!TppAlpcpExecuteCallback+0x25e 17 00000013`ae2ff6e0 00007fff`885b84d4 ntdll!TppWorkerThread+0x8d9 18 00000013`ae2ffae0 00007fff`88d41791 KERNEL32!BaseThreadInitThunk+0x14 19 00000013`ae2ffb10 00000000`00000000 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 = 00000000`00007530 kd> ? dwo(services!g_dwHandlerTimeout) Evaluate expression: 30000 = 00000000`00007530 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 .prompt_allow +reg +ea +dis 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 00000000`0079ee80 00007fff`88d83268 ntdll!LdrpDoDebuggerBreak+0x30 01 00000000`0079eec0 00007fff`88d68145 ntdll!LdrpInitializeProcess+0x1be4 02 00000000`0079f2d0 00007fff`88d67fae ntdll!LdrpInitialize+0x141 03 00000000`0079f350 00000000`00000000 ntdll!LdrInitializeThunk+0xe 借助kd之后可以这样调试特定服务的启动阶段,算是下文的补充 《MSDN系列(41)--调试Windows服务》 https://scz.617.cn/windows/202111291159.txt 过去services.exe不是PPL进程,无需借助kd就可以这样干。注意,可在用户态Patch ServicesPipeTimeout。 ☆ 参考资源 [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)