Windows 10以上版本系统svchost服务入口函数调试方法

前言

  • 原先调试一个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项,参照前面的填法填入调试器路径及参数。
      在这里插入图片描述 在这里插入图片描述
  • 之后启动服务,可看到使用的是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下的ServiceDllServiceManifest等值的读取,明白这里要获取目标dll的文件路径。
    在这里插入图片描述
    在这里插入图片描述
  • 获取的路径字符串存在了a1参数指向的某结构体中(a1 + 8,即a1处往后的第8个qword处)
    在这里插入图片描述
  • 之后便是调用LoadLibrary函数加载动态库。
    在这里插入图片描述
  • 之后又分别获取SvchostPushServiceGlobalsSvchostPushServiceGlobalsEx以及dll主函数地址,分别存放到a2a3a4参数中。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 通过分析,确定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命令将符号文件下载到物理机的文件系统中。之后同前文所述,下断点、将开头字节改回原字节,即可继续运行并调试了。
    在这里插入图片描述

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值