首页
社区
课程
招聘
Windows 10以上版本系统svchost服务入口函数调试方法
2023-11-26 22:41 5470

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

2023-11-26 22:41
5470

前言

  • 原先调试一个windows进程,习惯在内核模式的windbg中切换到目标进程上下文,加载调试符号和下断点。但是这样只能调试已经启动的、运行中的进程。最近要调试某个svchost服务的入口,也就是需要调试该服务的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已被加载到内存中,那就能加载其调试符号,然后找到主函数并下断点了。

实验二:修改程序入口,迟滞进程的启动

  • 前面提到,上面的方法有一个问题,就是不能通过在远程调试器中执行.reload /f命令将所有相关的符号文件下载到本地。于是有了下面的测试。

实验2.1:将termsrv!DllMainCRTStartup函数开头替换为自跳转指令

  • 在ida中加载termsrv.dll文件,将DllMainCRTStartup函数的开头两个字节替换成EB FE,即一条跳转到自身的短跳转指令,让程序在开始运行时陷入死循环。

  • 导出打过补丁的termsrv.dll,替换虚拟机中的该文件,然后启动TermService服务。这时,即使没有通过调试器启动svchost,服务启动时也会卡着。
  • 这时就可在本地(也就是虚拟机中)通过windbg附加到服务进程。在反汇编窗口可看到DllMainCRTStartup函数修改后的样子。执行.reload /f命令将包括termsrv.pdb在内的相关的符号文件下载到本地,然后就可以愉快地找函数下断点了。
  • 之后在windbg中打开内存窗口,将前面的补丁字节改回来。
  • 继续运行,服务便完成启动了。

实验2.2:将termsrv!DllMainCRTStartup函数开头替换成int 3

  • 现在再测一下将termsrv!DllMainCRTStartup函数第一个字节替换成CC,以触发int 3中断。
  • 在虚拟机已配置好内核调试的情况下,服务一启动系统就会卡死。
  • 在物理机用内核模式的windbg连上虚拟机,可看到系统卡在svchost进程这,也就是上面设置的int 3中断处。这时通过执行.reload命令即可将符号文件下载到物理机的文件系统中。之后同前文所述,下断点、将开头字节改回原字节,即可继续运行并调试了。

参考


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2024-2-1 09:30 被wx_Niatruc编辑 ,原因:
收藏
点赞6
打赏
分享
最新回复 (4)
雪    币: 19461
活跃值: (29125)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-11-26 23:19
2
2
感谢分享
雪    币: 108
活跃值: (873)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yangya 2023-11-27 10:10
3
0
直接改二进制文件不需要绕过签名吗?
雪    币: 853
活跃值: (1397)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
wx_Niatruc 2023-11-27 11:06
4
0
yangya 直接改二进制文件不需要绕过签名吗?
不用,我测的时候是直接替换原始dll文件的
雪    币: 2011
活跃值: (2291)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
evilbeast 1 2023-11-28 01:20
5
0
收藏了 好文章
游客
登录 | 注册 方可回帖
返回