MSDN系列(45)--调试services.exe进程
2021-12-29 06:52:53 Author: mp.weixin.qq.com(查看原文) 阅读量:10 收藏



☆ 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: <Data Not Accessible>
    Image: services.exe

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

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

sc start spooler

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

 # Call Site
00 nt!NtCreateUserProcess
01 nt!KiSystemServiceCopyEnd+0x13
02 ntdll!NtCreateUserProcess+0x14
03 KERNELBASE!CreateProcessInternalW+0x1610
04 KERNELBASE!CreateProcessAsUserW+0x63
05 KERNEL32!CreateProcessAsUserWStub+0x5f
06 services!ScLogonAndStartImage+0x41d
07 services!ScStartService+0x4d4
08 services!ScStartMarkedServicesInServiceSet+0x1a4
09 services!ScStartServiceAndDependencies+0x3de
0a services!RStartServiceW+0xfc
0b RPCRT4!Invoke+0x73
0c RPCRT4!NdrStubCall2+0x46b
0d RPCRT4!NdrServerCall2+0x1a
0e RPCRT4!DispatchToStubInCNoAvrf+0x24
0f RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1bd
10 RPCRT4!RPC_INTERFACE::DispatchToStub+0xcb
11 RPCRT4!LRPC_SCALL::DispatchRequest+0x34c
12 RPCRT4!LRPC_SCALL::HandleRequest+0x2bc
13 RPCRT4!LRPC_ADDRESS::HandleRequest+0x36c
14 RPCRT4!LRPC_ADDRESS::ProcessIO+0x91b
15 RPCRT4!LrpcIoComplete+0xaa
16 ntdll!TppAlpcpExecuteCallback+0x25e
17 ntdll!TppWorkerThread+0x8d9
18 KERNEL32!BaseThreadInitThunk+0x14
19 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: <Data Not Accessible>
    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: <Data Not Accessible>
    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: <Data Not Accessible>
    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    id30c attach  name: C:\Windows\system32\services.exe
.  1    id58c 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
 # Call Site
00 ntdll!LdrpDoDebuggerBreak+0x30
01 ntdll!LdrpInitializeProcess+0x1be4
02 ntdll!LdrpInitialize+0x141
03 ntdll!LdrInitializeThunk+0xe

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

《MSDN系列(41)--调试Windows服务》
http://scz.617.cn:8/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

[3Local 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)


文章来源: http://mp.weixin.qq.com/s?__biz=MzUzMjQyMDE3Ng==&mid=2247485123&idx=1&sn=16a756ce2b236c1e2a930a380c7b6ba2&chksm=fab2c5fccdc54cea30c44e889881d6c4abc3fefb889160e00db0f62b2caf5da493221d5cf193#rd
如有侵权请联系:admin#unsafe.sh