标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2024-2974] 作者: 大猪 发表于: [2022-02-11]
本文共 [558] 位读者顶过
一、调试Windows服务知识点汇总若服务源码是自己开发的,可在被调试代码逻辑中主动调用DebugBreak(),以此呼叫”Just-In-Time Debugging”;也可通过命令行参数让被调试代码逻辑以普通控制台进程方式运行,这种没法调试SCM相关的代码。 一般调试Windows服务,并非服务源码可控的情形。通常分两种情况,一种是被调试代码逻辑可以在Attach之后触发,一种是被调试代码逻辑只在服务启动时触发。第一种情况和普通调试一样,第二种情况相对复杂些,要做些特别设置。以前没有过第二种需求,只知道大概思路,未实践过。最近碰上,实践一番,有不少琐碎的知识点,在此记录一二。 在Win10企业版2016 LTSB 1607(OS Build 14393.4704)上测试。 1) 官方文档 windbg帮助(debugger.chm)
Debugging Techniques[出自:jiwo.org]
Debugger Operation 官方文档是最好的,看过后就差不多了,不过我意识到这点有些晚。 2) Debugging Server模式 为了调试服务启动阶段,需要用到”Debugging Server” 2.1) Process Servers (User Mode) 在Guest中以管理员身份运行 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 lsass.exe 这种组合是”Process Servers (User Mode)”,不是”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”。 调试客户端用的是-remote,不是-premote。 调试服务端-server、调试客户端-remote必须是各自命令的第一个参数。调试客户端不能指定-p、-pn,完全由调试服务端决定调试目标。 调试客户端指定”-noinh -snul -hd”可能并无意义?反正调试客户端指定不了”-o”。 一个调试服务端可以对应多个调试客户端,比如在Host中开第二个cdb接入调试服务端。假设现在有一个调试服务端、两个调试客户端,这三端都可以输入调试命令,调试命令产生的输出会同时出现在三端。”Debugging Server”这种搞法允许多人同时协作调试同一个目标进程,有点意思,这与dbgsrv模式完全不同。 .servers 显示接入的调试服务端 .clients 显示接入的所有调试客户端 .endsrv 0 让调试服务端不再侦听8766/TCP。这个有延迟,在调试服务端用Process Explorer查看cdb,发现8766/TCP仍在LISTEN状态,实际上调试服务端不再接受新的入连接。新开调试客户端尝试接入,失败,再去看调试服务端,不再侦8766/TCP。 不影响已接入的所有调试客户端,已有TCP连接均保持。 .shell whoami 无论在哪端使用.shell,真正执行的都是调试服务端的shell,一般是cmd .noshell 关闭调试服务端对.shell的支持。没有反命令,一旦生效,将保持到调试服务端终止。 !envvar _NT_SYMBOL_PATH 可用!envvar查看被调试进程环境变量。但该命令依赖ntdll!_PEB,要求ntdll.pdb就位。若.sympath有误,找不到ntdll.pdb,!envvar报错。 2.3) ServerTransport/ClientTransport
-server ServerTransport 关于ServerTransport/ClientTransport的语法,参看windbg帮助。 “npipe:pipe”要求LanmanServer服务启动中,涉及SMB认证,无谓地引入复杂性。像我,LanmanServer服务常年禁用中。若调试服务端、调试客户端都在本机,且LanmanServer服务启动中,用”npipe:pipe”也行
cdb.exe -server npipe:pipe=anyname,password=8766 -noinh -snul -hd -o -G notepad.exe 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放行规则。 $ netsh advfirewall firewall show rule name=”Debugger RPC Port Mapping” Rule Name: Debugger RPC Port Mapping
Enabled: Yes IcfEnable添加到wf.msc的放行规则不会因调试终止而自动删除,始终存在,只能手工删除。只有在管理员级或SYSTEM级别时,指定IcfEnable才有效,否则即使指定IcfEnable,仍将弹框提示。 windbg帮助里说,IcfEnable用于”npipe:pipe”时会放行139、445/TCP,未实测确认。 2.7) 调试示例 在Guest中以管理员身份运行 cdb.exe -server tcp:port=8766,password=8766,icfenable -noshell -noio -noinh -snul -hd -o -G notepad.exe 在Host中 cdb.exe -remote tcp:server=192.168.65.136,port=8766,password=8766 -noinh -snul -hd 调试客户端接入后,发现调试服务端断在ibp(初始化断点)
# Child-SP RetAddr Call Site
.prompt_allow +reg +ea +dis 2.7.1) 勿在初始化断点处设置硬件断点 在ibp用ba设置硬件断点,一般会失败报错
ba e1 /1 @$exentry “kpn” 尽量避免在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本身,回头有时间看看。 2.7.2) Detach 推荐流程
.endsrv 0 // 调试服务端不再侦听8766/TCP
这样干之后,调试服务端cdb终止,notepad保持运行。就notepad调试示例而言,直 3) ServicesPipeTimeout 参[2] 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 0x15752a00是360000000,单位是毫秒,换算过来就是100小时。需要重启OS使之生效。该值缺省30000,即30秒。 3.1) 热Patch让ServicesPipeTimeout生效 碰上一个场景,Guest接有kd,但未提前设置过ServicesPipeTimeout,因故不方便重启OS,想找个热Patch方案让ServicesPipeTimeout生效。此时确实有热Patch方案,简介如下
kd> !process 0 0 services.exe
.process /i ffffda884002c480;g 回到Guest,在管理员级cmd中执行 sc start spooler 前述内核态断点命中 Patch
ed services!g_dwScControlMessageTimeout 0n360000000 UnPatch
ed services!g_dwScControlMessageTimeout 0n30000 禁用断点,继续执行
bd * Win10无法用cdb调试services.exe,涉及PPL保护,回头单写一下此事。 4) IFEO (Image File Execution Options) Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\spoolsv.exe] “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\symhttp://msdl.microsoft.com/download/symbols\””
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\symhttp://msdl.microsoft.com/download/symbols\”” /f 有几点微妙之处,后面逐一讨论。 4.1) cdb vs ntsd windbg帮助里有
The only difference between NTSD and CDB is that NTSD spawns a new console
start cdb [parameters] 用ntsd的话,会额外产生一个conhost.exe进程。调试Windows服务时还是用cdb好了。 4.2) NT AUTHORITY\SYSTEM spoolsv.exe是以”NT AUTHORITY\SYSTEM”身份启动的,通过IFEO启动cdb(或ntsd),cdb也是以SYSTEM身份执行,将涉及几个小问题。 4.2.1) -noshell 若IFEO中未指定-noshell,客户端cdb远程接入后可以用这类命令
.shell whoami 这是SYSTEM级别的shell。 4.2.2) _NT_SYMBOL_PATH环境变量 若_NT_SYMBOL_PATH不在系统级环境变量中,只在用户级环境变量中,SYSTEM账户就没有_NT_SYMBOL_PATH环境变量,可用.shell或!envvar检查之。 4.2.3) \vmware-host\Shared Folders SYSTEM账户可以访问”\vmware-host\Shared Folders\sym”,但不能访问[net use Z: “\vmware-host\Shared Folders”]映射的Z盘,SYSTEM账户并未映射过盘符。可能SYSTEM账户干脆无法”net use”? 4.3) .sympath/-y 为使用符号,保险起见,可在IFEO中用-y指定符号路径。即使未在IFEO中使用-y,将来总是可以用.sympath设置符号路径 .sympath srv\vmware-host\Shared Folders\symhttp://msdl.microsoft.com/download/symbols 用.sympath时不要用双引号,用-y时要用双引号,注意转义。 4.4) 用gflags图形界面设置IFEO 前面直接操作注册表设置IFEO,也可以用windbg自带的gflags设置IFEO,参[3]、[6]、[7]。
a) 执行gflags C:\temp\cdb.exe -server tcp:port=8766,password=8766,icfenable -noshell -noio -noinh -snul -hd -o -G -y “srv\vmware-host\Shared Folders\symhttp://msdl.microsoft.com/download/symbols” 4.4.1) 用gflags命令行设置IFEO (不推荐) 参[8],有人琢磨出用gflags命令行设置IFEO的办法 gflags.exe /p /enable spoolsv.exe /debug “C:\temp\cdb.exe -server tcp:port=8766,password=8766,icfenable -noshell -noio -noinh -snul -hd -o -G -y \”srv\vmware-host\Shared Folders\symhttp://msdl.microsoft.com/download/symbols\”” 但上述gflags命令会额外设置几个注册表项,比如GlobalFlag、PageHeapFlags Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\spoolsv.exe] “PageHeapFlags”=”0x2” reg.exe query “HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\spoolsv.exe” /v “Debugger” 下列gflags命令会同时删除上述三个注册表项 gflags.exe /p /disable spoolsv.exe 我们并不想设置GlobalFlag、PageHeapFlags,未找到”干净”的gflags命令行参数。只能用gflags的图形界面进行”干净”的IFEO设置。 5) 远程调试 5.1) 手工启动待调试服务 在Guest中以管理员身份运行
sc qc 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\symhttp://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
# Child-SP RetAddr Call Site
.prompt_allow +reg +ea +dis 勿在ibp处ba设断,就用普通的bp设断
# Child-SP RetAddr Call Site 5.3) 无法完美退出远程调试 推荐流程
.endsrv 0 // 调试服务端不再侦听8766/TCP 这样干之后,被调试进程正常跑,但服务端cdb不会结束,也在那儿,只是不能再调试目标进程。 与前面notepad调试示例不同,用IFEO调试spoolsv.exe时,如下操作序列会出幺蛾子
.endsrv 0 这样干之后,服务端cdb与spoolsv.exe都彻底终止。给服务端cdb指定-pd,现象依旧;当然,-pd实际就是.detach。是我哪里用得不对? 不知造成这种差异的原因是啥,与spoolsv.exe是服务进程相关,还是与IFEO相关? 6) Isolating the Service 打印服务本来就在单独的spoolsv.exe进程,但很多其他服务共用一个svchost.exe,比如 $ tasklist /svc /fi “services eq dnscache”
Image Name PID Services $ sc qc dnscache
SERVICE_NAME: dnscache 将Dnscache服务隔离出来,单独使用一个svchost.exe,不与其他服务共用,是比较稳妥的选择;好处在于,调试目标服务时不影响其他服务。官方文档提出三种隔离方案。 6.1) Moving the Service to its Own Group (推荐) 关注”svchost.exe -k”的参数,这是原始注册表设置 Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SvcHost]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SvcHost\NetworkService] “DefaultRpcStackSize”=dword:0000001c SvcHost下有名为NetworkService的键值,类型是REG_MULTI_SZ,内容是
CryptSvc 这与前面tasklist的输出相符。SvcHost下还有名为NetworkService的子键。这是改动后的注册表设置。 Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SvcHost]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SvcHost\NetworkService]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SvcHost\TempGrp] “DefaultRpcStackSize”=dword:0000001c 根据NetworkService组的设置仿造一个新组TempGrp,从前者删除DNSCache,在后者添加DNSCache,其余设置要保持二者间完全一样。 SvcHost下有名为TempGrp的键值,类型是REG_MULTI_SZ,内容是 DNSCache SvcHost下还有名为TempGrp的子键。 改完注册表后,执行
sc config dnscache binpath= “C:\Windows\system32\svchost.exe -k TempGrp” 重启OS使之生效。若充分理解上下文及各种边际效应,某些情况下可以只重启服务使之生效,Dnscache服务正是如此。
sc stop dnscache && sc start dnscache Dnscache服务已经独占一个svchost.exe。 还原时,在NetworkService组中恢复DNSCache,删除TempGrp键值、子键,最后执行
sc config dnscache binpath= “C:\Windows\system32\svchost.exe -k NetworkService” 这是官方推荐套路。 6.2) Changing the Service Type 可用如下命令让Dnscache服务独占一个svchost.exe
sc config dnscache type= own 外在效果同TempGrp方案。但官方文档里说这样干会改变服务行为,并不推荐。个人觉得不妨一试,毕竟最简捷。type可选值有 type= 恢复操作 sc config dnscache type= share 6.3) Duplicating the SvcHost Binary
sc stop dnscache 这种方案太直白了,无需解释。但官方文档里说这样干会改变服务行为,并不推荐。 恢复操作
sc stop dnscache 6.4) 杂议 前述三种服务隔离方案任选其一即可,官方推荐第一种,我觉得第二种挺好的。[5]的作者同时用到官方并不推荐的后两种服务隔离方案,没必要同时用。 7) Windows 2000比较特殊 前面说的是Win10,Windows 2000比较特殊,参[4]。
When we connect through Terminal Services, we are in a different Window [4]演示了一些特别技巧处理此问题,包括remote.exe的使用。 8) 获取服务进程PID 一般用Process Explorer找,命令行多用这几种
tasklist /svc /fi “services eq dnscache” 都是系统自带工具。 参考资源
[1] Debugging a Service – [2021-01-08]
DebugBreak function (debugapi.h)
Debugging a Service Application – [2020-12-08]
[2] A slow service does not start due to time-out error in Windows – [2021-09-24]
[3] Running a Program in a Debugger – [2021-06-17]
[4] How to debug Windows services with Windbg – Alex (Alejandro Campos Magencio) [2008-08-19]
[5] HOWTO Debug a Windows Service Using windbg – sam [2010-08-03]
[6] How to debug a Windows service – Julien Crozon [2010-10-14]
How to debug a process as soon as it starts with WinDbg or Visual Studio 2010 – Julien Crozon [2011-03-26]
[7] Debugging Windows services – Vetle kland [2019-01-25]
[8] Debugging a Windows Service – Marc Durdin [2020-09-17] |