Windows服务原理&调试方法

版权声明: https://blog.csdn.net/m0_37552052/article/details/82390354

Windows服务原理&调试方法

教程参考自《逆向工程核心原理》


1.概述

服务程序比较难调试,有时,即使是逆向分析经验丰富的人调试起来也并非易事。本文通过服务的原理来简单介绍下服务的调试方法。


2.原理

Service(服务程序)由SCM(Service Control Manager)管理,运行Service时,需要由sc(控制器)执行启动命令。sc向SCM提出服务控制请求,SCM向Service传递控制命令,并接收其返回的值。PS:sc无法直接向Service下达命令,必须通过SCM传达。
1

SCM可以理解为是一个抽象化的服务接口,一般通过函数OpenSCManager调用。
2

而sc就是我们常用的服务控制器,可以通过:services.msc打开或直接使用命令行运行sc.exe进行操作。
3

所有Service都是由sc调用StartService() API启动的,若Service为自启动服务,则由SCM调用StartService()启动。
4

Service启动过程:

[1]sc调用StartService()
sc调用StartService()的同时,SCM会创建相应的Service进程,然后执行Service进程的EP代码。

[2]Service进程调用StartServiceCtrlDispatcher()
为了以服务形式运行,必须在服务进程内部调用StartServiceCtrlDispatcher() API来注册服务主函数SvcMain()的地址d调用StartServiceCtrlDispatcher()时,返回sc的StartService()函数。SCM调用Service进程的服务主函数SvcMain()。

[3]服务进程调用SetServiceStatus()
虽然已经创建了Service进程,但尚未以服务的形式运行。当前状态仍为SERVICE_START_PENDING。在服务主函数SvcMain()内部调用SetServiceStatus(SERVICE_RUNNING) API后,才正式以Service进程形式运行。


3.代码演示

根据有无运行参数,本例程序可分别以服务模式(无参数)或常规模式(有参数)运行。以服务模式运行时会调用StartServiceCtrlDispatcher() API,启动服务主函数(SrvMain());以常规模式运行时,根据所给参数的种类,分别调用InstallService()/UninstallService()函数,它们分别用来安装或卸载服务。

主函数
5

6

InstallServie()函数
7

UninstallService()函数
8

8

服务主函数SvcMain()
9

服务控制函数SvcCtrlHandler()
10


4.程序演示

在命令行下运行程序DebugMe1.exe,加上参数install。
11

从services.msc中可以看到服务SvcTest已被创建。
12

SvcTest服务的进程(DebugMe1.exe)是以services.exe进程的子进程形式运行的,起始所有服务进程都以该形式运行。Services.exe进程就是SCM。
13

对于EXE文件形态的Windows服务程序而言,必须在其EP代码内部调用StartServiceCtrlDispatcher() API,将服务主函数(SvcMain())的地址通知给SCM。对于DLL文件形式为Windows服务而言,服务主函数(默认为ServiceMain)为导出函数,SCM会调用运行导出函数,所以不需要另外调用StartServiceCtrlDispatcher() API。


5.调试服务

使用IDA可以快速找到ServiceMain(),如下图,对应的地址为0x1000C8C0。
14

然后使用OD加载程序,直接在ServiceMain(0x1000C8C0)处右键->此处为EIP。
15

大家可能有个疑问,那么直接?不会造成什么寄存器、栈的访问错误吧。答案是一般不会,ServiceMain()是一个较独立的函数(名字都叫Main函数了),很少接收外界传来的参数,所以可以将EIP指过去直接运行。不过有时出现下面这种情况需要注意,ServiceMain需要传来的ServiceName参数,这时我们若不构建一个ServieName,将会出现内存访问错误。
16

a2是一个双重指针,在栈中0x12F870的位置,我们在其写入一个伪造的地址,这里地址为0x1001A424,然后再在0x1001A424中写入地址0x1001A434(ServiceName存放的地址),最后,再在0x1001A434下填入服务名就行了,这样就实现了参数填充,不会再出现内存访问错误了。PS:0x1001A424和0x1001A434不是特定的,自己找块空闲的内存写就行。
17

猜你喜欢

转载自blog.csdn.net/m0_37552052/article/details/82390354