MSDN系列(41)--调试Windows服务
2021-12-03 03:14:34 Author: mp.weixin.qq.com(查看原文) 阅读量:24 收藏

创建: 2021-11-29 11:59
更新: 2021-12-02 12:16
http://scz.617.cn:8/windows/202111291159.txt

目录

☆ 调试Windows服务知识点汇总

若服务源码是自己开发的,可在被调试代码逻辑中主动调用DebugBreak(),以此呼叫"Just-In-Time Debugging";也可通过命令行参数让被调试代码逻辑以普通控制台进程方式运行,这种没法调试SCM相关的代码。

一般调试Windows服务,并非服务源码可控的情形。通常分两种情况,一种是被调试代码逻辑可以在Attach之后触发,一种是被调试代码逻辑只在服务启动时触发。第一种情况和普通调试一样,第二种情况相对复杂些,要做些特别设置。以前没有过第二种需求,只知道大概思路,未实践过。最近碰上,实践一番,有不少琐碎的知识点,在此记录一二。

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

(全文见TXT,内容太多,公众号不好排版)

2) Debugging Server模式

为了调试服务启动阶段,需要用到"Debugging Server"

2.2) Debugging Server

在Guest中

cdb.exe -server tcp:port=8766,password=8766 -noinh -snul -hd -o -G notepad.exe

在Host中

cdb.exe -remote tcp:server=192.168.65.136,port=8766,password=8766 -noinh -snul -hd

这种组合是"Debugging Server"。

一个调试服务端可以对应多个调试客户端,比如在Host中开第二个cdb接入调试服务端。假设现在有一个调试服务端、两个调试客户端,这三端都可以输入调试命令,调试命令产生的输出会同时出现在三端。"Debugging Server"这种搞法允许多人同时协作调试同一个目标进程,有点意思,这与dbgsrv模式完全不同。

2.3) ServerTransport/ClientTransport

关于ServerTransport/ClientTransport的语法,参看windbg帮助。

"npipe:pipe"要求LanmanServer服务启动中,涉及SMB认证,无谓地引入复杂性。像我,LanmanServer服务常年禁用中。若调试服务端、调试客户端都在本机,且LanmanServer服务启动中,用"npipe:pipe"也行

C/S不在同一主机时,并不推荐"npipe:pipe"。

2.4) -noio

调试服务端指定-noio后,调试服务端本身不接受调试命令的输入,也不同步显示调试命令产生的输出,无法Ctrl-C终止调试服务端。调试Windows服务时,建议始终指定。

2.5) -noshell

调试服务端指定-noshell后,一上来就关闭调试服务端对.shell的支持。调试Windows服务时,建议始终指定。

2.6) IcfEnable (PFW放行)

Win10有PFW,cdb首次侦听8766/TCP时会弹框提示是否允许入连接,必须选"允许",让相应规则进入wf.msc。

若通过IFEO间接启动cdb,并且是cdb首次侦听8766/TCP,情况就有些微妙了。假设wf.msc中无相应放行规则,按理要弹框选择的。但若IFEO的原始进程在Session 0中启动,cdb导致的弹框也在Session 0中,你看不到,没法选,8766/TCP被阻断中。

为解决上述问题,可提前增设PFW规则,避免弹框提示。在Session 1中用cdb触发弹框,选"允许",这是一种办法。另一种办法是在管理员级cmd中执行

cdb.exe -server tcp:port=8766,password=8766,icfenable -noinh -snul -hd -o -G notepad.exe

-server中指定了IcfEnable,大小写不敏感。这条命令不会触发弹框,直接在wf.msc中增设名为"Debugger RPC Port Mapping"的8766/TCP放行规则。

IcfEnable添加到wf.msc的放行规则不会因调试终止而自动删除,始终存在,只能手工删除。只有在管理员级或SYSTEM级别时,指定IcfEnable才有效,否则即使指定IcfEnable,仍将弹框提示。

2.7.1) 勿在初始化断点处设置硬件断点

在ibp用ba设置硬件断点,一般会失败报错

> ba e1 /1 @$exentry "kpn"
        ^ Unable to set breakpoint error
The system resets thread contexts after the process
breakpoint so hardware breakpoints cannot be set.
Go to the executable's entry point and set it then.

尽量避免在ibp对任何地址设置硬件断点,因为后面会过ZwContinue,该函数会切换CONTEXT,必然导致DR*寄存器被修改。理论上在ibp处不是不能设硬件断点,而是设了之后,很快就会被破坏。为了避免将来这种破坏引起误会,cdb干脆禁止在ibp处使用ba设置硬件断点,如果尝试ba命令,会提示到了exentry之后才可以设硬件断点。其实在@$exentry之前很多地方都可以正常使用ba设置硬件断点,比如"sxe cpr"、"sxe ld:ntdll"命中时、流程到达ntdll!RtlUserThreadStart时,这几处都可以ba。

反调试手段之一就是拦截ZwContinue,修改DR*、TF等。

假设前面那个测试环境是A环境,现在换到另一个B环境。在B环境中,在ibp用ba设置硬件断点,没有失败报错,bl可以看到硬件断点,当然,后来还是被ZwContinue破坏而失效。B环境发生的事很不友好,我在B环境中被坑了一把,当时忘了ibp处设置硬件断点的坑,发现ba断不下来,还奇怪呢,云海提醒之后才重新想起缘由。

A、B环境都是Win10企业版2016 LTSB 1607。A打过2021.10补丁,B打过2021.11补丁,ntoskrnl.exe不同,但具体到notepad.exe、ntdll.dll这两个模块,并无差别。A环境有kd接入,B环境未进入"Test Mode"。A、B所用cdb是同一版本。

云海在更早期的其他环境中测试,重现B环境出现的现象,看上去与最近这些补丁无关。

未深究造成A、B差异的原因,最靠谱的办法当然是调试cdb本身,回头有时间看看。

3) ServicesPipeTimeout

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

reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control" /v "ServicesPipeTimeout" /t REG_DWORD /d 0x15752a00 /f

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

3.1) 热Patch让ServicesPipeTimeout生效

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

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

Win10无法用cdb调试services.exe,涉及PPL保护,回头单写一下此事。

4) IFEO (Image File Execution Options)

reg.exe add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\spoolsv.exe" /v "Debugger" /t REG_SZ /d "C:\temp\cdb.exe -server tcp:port=8766,password=8766,icfenable -noshell -noio -noinh -snul -hd -o -G -y \"srv*\\vmware-host\Shared Folders\sym*http://msdl.microsoft.com/download/symbols\"" /f

有几点微妙之处,后面逐一讨论。

4.2) NT AUTHORITY\SYSTEM

spoolsv.exe是以"NT AUTHORITY\SYSTEM"身份启动的,通过IFEO启动cdb(或ntsd),cdb也是以SYSTEM身份执行,将涉及几个小问题。

4.2.1) -noshell

若IFEO中未指定-noshell,客户端cdb远程接入后可以用这类命令

.shell whoami
.shell echo %_NT_SYMBOL_PATH%

这是SYSTEM级别的shell。

4.2.2) _NT_SYMBOL_PATH环境变量

若_NT_SYMBOL_PATH不在系统级环境变量中,只在用户级环境变量中,SYSTEM账户就没有_NT_SYMBOL_PATH环境变量,可用.shell或!envvar检查之。

4.4) 用gflags图形界面设置IFEO

前面直接操作注册表设置IFEO,也可以用windbg自带的gflags设置IFEO

a) 执行gflags
b) 切到"Image File"页
c) 在Image中输入spoolsv.exe,按TAB键
d) 勾中Debugger,输入

C:\temp\cdb.exe -server tcp:port=8766,password=8766,icfenable -noshell -noio -noinh -snul -hd -o -G -y "srv*\\vmware-host\Shared Folders\sym*http://msdl.microsoft.com/download/symbols"

5) 远程调试

5.1) 手工启动待调试服务

在Guest中以管理员身份运行

sc config spooler start= demand
sc stop spooler
sc start spooler

sc启动spooler服务时,由于IFEO,实际执行的是

C:\temp\cdb.exe -server tcp:port=8766,password=8766,icfenable -noshell -noio -noinh -snul -hd -o -G -y "srv*\\vmware-host\Shared Folders\sym*http://msdl.microsoft.com/download/symbols" spoolsv.exe

这将进入Debugging Server模式,cdb位于Session 0,不可见,等待调试客户端接入。由于未指定-g,cdb将停在ibp。

5.2) 调试客户端接入

cdb.exe -remote tcp:server=192.168.65.136,port=8766,password=8766 -noinh -snul -hd

.prompt_allow +reg +ea +dis
bp /1 @$exentry "kpn"
bp sechost!StartServiceCtrlDispatcherW "kpn"

勿在ibp处ba设断,就用普通的bp设断

5.3) 无法完美退出远程调试

推荐流程

.endsrv 0   // 调试服务端不再侦听8766/TCP
.detach     // 让被调试进程脱离调试后运行
Ctrl-B 回车 // 离开调试客户端操作界面

这样干之后,被调试进程正常跑,但服务端cdb不会结束,也在那儿,只是不能再调试目标进程。

6) Isolating the Service

打印服务本来就在单独的spoolsv.exe进程,但很多其他服务共用一个svchost.exe,比如

$ tasklist /svc /fi "services eq dnscache"

Image Name                     PID Services
========================= ======== ============================================
svchost.exe                    964 CryptSvc, Dnscache, LanmanWorkstation,
                                   NlaSvc, TermService

$
 sc qc dnscache

SERVICE_NAME: dnscache
        TYPE               : 20  WIN32_SHARE_PROCESS
        START_TYPE         : 2   AUTO_START
        ERROR_CONTROL      : 1   NORMAL
        BINARY_PATH_NAME   : C:\Windows\system32\svchost.exe -k NetworkService
        LOAD_ORDER_GROUP   : TDI
        TAG                : 0
        DISPLAY_NAME       : DNS Client
        DEPENDENCIES       : Tdx
                           : nsi
        SERVICE_START_NAME : NT AUTHORITY\NetworkService

将Dnscache服务隔离出来,单独使用一个svchost.exe,不与其他服务共用,是比较稳妥的选择;好处在于,调试目标服务时不影响其他服务。官方文档提出三种隔离方案。

6.2) Changing the Service Type

可用如下命令让Dnscache服务独占一个svchost.exe

sc config dnscache type= own

外在效果同TempGrp方案。但官方文档里说这样干会改变服务行为,并不推荐。个人觉得不妨一试,毕竟最简捷。type可选值有

恢复操作

sc config dnscache type= share

8) 获取服务进程PID

一般用Process Explorer找,命令行多用这几种

tasklist /svc /fi "services eq dnscache"
sc queryex dnscache | findstr PID

都是系统自带工具。


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