前言
- 原先调试一个windows进程,习惯在内核模式的windbg中切换到目标进程上下文,加载调试符号和下断点。但是这样只能调试已经启动的、运行中的进程。最近要调试某个svchost服务的入口(下文以TermService服务为例),也就是需要调试该服务的
ServiceMain函数,需要能在这个函数或者在该函数执行之前通过调试器中断下来。 - 经过一番网上冲浪,得到如下两种方法:
- 利用
Image File Execution Options键,使用调试器启动服务,这个算比较正经的途径。在《使用Windbg&OllyDbg从头调试windows服务》这篇文章中提到结合桌面交互检测服务(Interactive Services Detection)进行本地调试,然而在Win10的较新版本中这个服务已经被移除了,因此本文采用的调试方式为远程调试。 - 将动态库的入口函数的开始部分字节替换成一条自循环的指令,之后启动服务时,就会先在这里卡住,这时可附加调试器,再将之替换为原指令即可。这样做不需要修改注册表,但要先把原来的dll文件替换掉。
- 利用
- 下文以TermService服务为例,实验验证上述两种方法。
实验环境
- 物理机:Windows 10 x64,已安装Windbg Preview。
- 虚拟机:Windows 10 x64,将在该系统上运行要调试的TermService服务
实验一:结合Image File Execution Options键,使用调试器启动服务进程
搭建调试环境(windbg + ntsd/cdb)
- 电脑安装wdk后,其中的
Debuggers目录下有全部跟调试有关的库和程序等(注入windbg,cdb,ntsd,dbgsrv)。我的虚拟机是x64的系统,故将Debuggers\x64目录拷贝到虚拟机中。注意,必须拷贝整个目录,不能只拷贝单个程序,否则会因缺少库而导致各种问题。

- 关闭防火墙,以便使用tcp端口作为远程调试用的端口。
- 找到
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options项。我要调试termservice服务,其通过svchost.exe启动,故找到svchost.exe键,新建字符串类型值debugger,填入调试器路径及启动参数:C:\Users\cmtest\Desktop\x64\ntsd.exe -server tcp:port=1234 -noio -y srv*C:\win_symbols*http://msdl.microsoft.com/download/symbols。-y参数指定符号路径。使用ntsd或cdb开启调试服务后,远程调试时调试器是在虚拟机本地搜索调试符号文件的。

- 在
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control键下,新建一个dword值ServicesPipeTimeout,该值表示服务启动的超时时间(单位:毫秒)。为了保证有足够时间调试,设置值为86400000,表示24小时。注意,需重启电脑才能使这一设置生效。

- 打开服务管理器,找到要调试的服务,在
登录项里选中允许服务与桌面交互。如果前面设置了debugger值而不设置这一项,则启动服务会提示无权限。

- 虚拟机中启动要调试的服务。
- 在宿主机中打开windbg preview,选择
Connect to remote debugger,在Connection strings中填写tcp:server=192.168.29.128,port=1234,并点击OK。

- 成功连接后,宿主机的windbg和虚拟机的情况分别如下二图。在下面第二张图中,可看到ntsd进程的使用的TCP 1234端口已建立了连接。


问题一:所有svchost服务启动时都会通过ntsd使用同一端口
- 这样一来,系统有时启动一些svchost服务,会出现很多新的ntsd进程,调试上比较麻烦(类似于后面的问题二所述)。
- 解决方法如下:
- 在system32目录,拷贝svchost文件,新文件命名为
svchost2.exe。 - 修改
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\TermService键的ImagePath值,改用svchost2的文件路径。 - 注册表
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options下新建一个svchost2.exe项,参照前面的填法填入调试器路径及参数。

- 在system32目录,拷贝svchost文件,新文件命名为
- 之后启动服务,可看到使用的是
svchost2。并且不会再出现其他ntsd进程。

问题二:无法退出调试的服务
- 当服务退出时,windbg会再次中断,停留在
ntdll!NtTerminateProcess函数的结束处(这也表明被调试的进程已经结束了)。但是退出调试器再连接此端口时,仍会中断于此。


- 最后只能通过process hacker将ntsd进程杀掉。暂无其他方法。
问题三:.reload命令没能下载符号
- 在宿主机的windbg调试器中执行
.reload命令时提示The system cannot find the file specified。看了下符号路径,也没发现问题。无解。


- 目前的解决方法:先在虚拟机中启动termservice服务(需先把前面对
Image File Execution Options项设置的debugger去掉),然后ntsd -p <pid>附加到此服务进程,设置sympath,再通过.reload /f下载符号文件。

分析svchost.exe,以确定作为服务载体的dll库被加载的时机
- 刚开始连上调试服务时,windbg停在启动进程时用的加载器函数
LdrInitializeThunk这里。这时svchost.exe已加载(即可使用调试符号),termsrv.dll未加载。需要在termsrv.dll被加载完后,才能到这个库的入口处下断点。

- 思路是在
svchost.exe中找到对LoadLibrary函数的调用,以确定加载目标dll的那段代码。用ida打开svchost.exe,查看LoadLibrary函数的交叉引用。

- 可以看到主要是
GetServiceMainFunctions函数在调用LoadLibrary函数,判断加载动态库的主要操作在此中,所以进GetServiceMainFunctions函数中简单分析一下。 - 首先看到了对注册表项
System\\CurrentControlSet\\Services\\Parameters下的ServiceDll和ServiceManifest等值的读取,明白这里要获取目标dll的文件路径。


- 获取的路径字符串存在了
a1参数指向的某结构体中(a1 + 8,即a1处往后的第8个qword处)

- 之后便是调用
LoadLibrary函数加载动态库。

- 之后又分别获取
SvchostPushServiceGlobals,SvchostPushServiceGlobalsEx以及dll主函数地址,分别存放到a2,a3,a4参数中。



- 通过分析,确定
GetServiceMainFunctions函数会完成dll的加载,并且不会将执行权转交给dll。因此,找到调用GetServiceMainFunctions函数的地方,运行完该函数,即可完成termsrv.dll的加载。 - ida中看到调用
GetServiceMainFunctions函数的地方只有一处,因此在此处调用后面下断点,直接F5运行至此,完成termsrv.dll库的加载。


- 既然
termsrv.dll已被加载到内存中,那就能加载其调试符号,然后找到主函数并下断点了。

实验二:修改程序入口,迟滞进程的启动
- 前面提到,上面的方法有一个问题,就是不能通过在宿主机的windbg中执行
.reload /f命令将所有相关的符号文件下载到本地。于是有了下面的测试。
实验2.1:将termsrv!DllMainCRTStartup函数开头替换为自跳转指令
- 在ida中加载
termsrv.dll文件,将DllMainCRTStartup函数的开头两个字节替换成EB FE,跳转到此短跳转指令自身,让程序在开始运行时陷入死循环。


- 导出打过补丁的
termsrv.dll,替换虚拟机中的该文件,然后启动TermService服务。这时,即使没有通过调试器启动svchost,服务启动时也会卡着。

- 这时就可在本地(也就是虚拟机中)通过windbg附加到服务进程。在反汇编窗口可看到
DllMainCRTStartup函数修改后的样子。这时候,可以执行.reload /f将包括termsrv.pdb在内的相关的符号文件下载到本地。然后就可以愉快地找函数下断点了。

- 之后再windbg中打开内存窗口,将前面的补丁字节改回来。

- 继续运行,服务便完成启动了。

实验2.1:将termsrv!DllMainCRTStartup函数开头替换成int 3
- 现在再测一下将
termsrv!DllMainCRTStartup函数第一个字节替换成CC,以触发int 3中断。

- 在虚拟机已配置好内核调试的情况下,服务一启动系统就会卡死。

- 这时在物理机挂上内核模式的windbg,可看到系统卡在svchost进程这,也就是上面设置的
int 3中断处。这时可以通过.reload命令将符号文件下载到物理机的文件系统中。之后同前文所述,下断点、将开头字节改回原字节,即可继续运行并调试了。

6890

被折叠的 条评论
为什么被折叠?



