一次失败的svchost hacking
事缘前几天需要做HOOK LPC通信拦截服务的创建等相关服务操作。而后好奇svchost share类型的服务是如何动态启动的,这里强调动态,只要是相对静态来说,大家都知道svchost.exe是在启动的时候读取注册表
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SvcHost,取得各个组和相关的服务名,接着用服务名到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
获取相关的服务配置信息,最后把这些服务加载启动起来。
为了了解svchost.exe如何动态加载share类型的服务,调试并查找了相关资料后,发现是service.exe通过pipe通信触发svchost动态加载的。所以总的来说,从我们的程序到svchost加载我们的dll服务过程的通信是这样的:myapp.exe->service.exe是rpc base lpc通信的,而service.exe->svchost是通过pipe通信的,在这里只详解下后者。
myapp.exe通信触发service.exe的ScStartService()函数,ScStartService函数则取得服务名testsrv,和服务的imagepath文件路径,ScStartService函数中判断testsrv这个服务的类型是不是share,如果是,继续调用函数ScGetImageFileName(unsigned __int16 *ServiceName, unsigned __int16 **ImageNamePtr)判断这个imagepath是不是曾经启动过(service.exe会有一个全局链表slist1保存启动过的imagepath),这里分2种情况:
1。为了下面述说方便,我先说如果木有启动过的情况,当service.exe在那个slist1中找不到的话,service.exe就会ScLogonAndStartImage()函数使用相应的身份如local system调用createprocess来启动imagepath这个服务,一般是"%SystemRoot%\system32\svchost.exe -k xxxx" 。回过头来,在ScLogonAndStartImage函数里面会首先调用ScCreateControlInstance创建一管道
#define CONTROL_PIPE_NAME L"\\\\.\\pipe\\net\\NtControlPipe" status = ScWriteCurrentServiceValue((unsigned int *)&dwServiceID); if ( status ) goto ExitAccountError; v16 = ScCreateControlInstance(&pipeHandle, dwServiceID, ServiceSid); status = v16;
这个管道的名字是CONTROL_PIPE_NAME拼上dwServiceID,如\\\\.\\pipe\\net\\NtControlPipe1,\\\\.\\pipe\\net\\NtControlPipe2。。。。
后面的数据是记录在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ServiceCurrent中,每用完一次,则会加1。
创建这个管道后,就得到了一个pipe handle ,service.exe会把imagepath和这个pipe handle对应的保存在那个slist中。
回到上面说的,createprocess启动svchost.exe后,svchost会默认到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\ServiceCurrent中也读取这个值,也拼出service.exe的那个 pipename。
最后打开,并连接上去。
2。这是第二种service.exe在slist中找到imagepath的情况,动态加载的话,一般是这种情况,既然进入第二种情况的话,那第一种情况必须已经发生过了,所以这时候svchost.exe已经连接上那个pipe并且在监听等待service.exe传过去的指令。所以在这种情况下,service.exe只需要根据imagepath搜索slist找出对应的pipe handle。
无论哪种情况,service.exe最后就得到了对应的pipe并且对应的svchost.exe已经在waitfor这个pipe信息的到来,接着service.exe就发送加载服务的命令即可。下面是发送的协议内容
PWCHAR pTestServices = L"thisistest"; PCTRL_MSG_HEADER pSendBuffer; DWORD dwSendSize=210; pSendBuffer = (PCTRL_MSG_HEADER)new char[dwSendSize]; ZeroMemory(pSendBuffer, dwSendSize); pSendBuffer->Count = sizeof(CTRL_MSG_HEADER)+4+sizeof(WCHAR)+wcslen(pTestServices)*2; pSendBuffer->NumCmdArgs = 0; pSendBuffer->ArgvOffset = 0; pSendBuffer->ServiceNameOffset = sizeof(CTRL_MSG_HEADER); pSendBuffer->OpCode = SERVICE_CONTROL_START_OWN; wcscpy((WCHAR*)pSendBuffer->szdata, pTestServices); / char szPipe[256]; HANDLE hfile =gethandle(); if (hfile==0) { return 1; } printf("write handle %x\n", hfile); DWORD dwSended=0; BOOL bRet = WriteFile(hfile, pSendBuffer, dwSendSize, &dwSended, NULL); printf("dwSended,%d, handle %x, bret=%d,error:%d\n", dwSended, hfile, bRet,GetLastError());
但最后还是失败了,因为在svchost接收到这个指令,得到服务名后,还会用openservice()尝试打开这个服务,当然,我这个服务是伪造的,不存在,所以就失败了。
PS:那个pipe在获取的时候还是有点曲折的,想直接调用createfile打开的话,会返回错误“拒绝打开“,权限不够,最后我是通过DuplicateHandle才获取成功。
==================================
//难道service.exe判断是否使用一个新的svchost.exe,是根据 -k 后面的服务组名判断的??
//简单做了一个测试,在一个已经建立好的服务里面修改type字段值为SERVICE_WIN32_OWN_PROCESS,然后再启动服务的时候
//发现服务启动失败,提示找不到模块。但是对应的服务的dll已经被加载了。
//如果-k后面是一个新组,即使指定了SERVICE_WIN32_SHARE_PROCESS,也是会启动一个新svchost进程的
//结论, 只有指定了SERVICE_WIN32_SHARE_PROCESS而且ImagePath已经运行过才会用老的svchost.exe来加载
//因为services.exe的一个链表中保存着这些运行过的imagepath信息,同时里面对应着那个imagepath的pipe句柄
//所以,如果是用老的svchost的话,直接取出这个pipe句柄即可通信
//这个pipe是在最开始的时候,services.exe和svchost.exe同步去注册表currentservice中取出的一个数字拼上NtControlFile
//得到的一个namedpipe,然后调用createfile打开的句柄
//注意:如果启动的服务组 (-k 指定 )已经运行过了,但服务创建的时候指定了SERVICE_WIN32_OWN_PROCESS的话,
//还是会创建一个新进程svchost.exe,所以在2个情况下,一定会启动新进程,1。是指定SERVICE_WIN32_OWN_PROCESS,
//2。指定的imagepath未运行过
//
// Is the image file for that service already running?
// If not, call StartImage.
//
// If the Image Record was NOT found in the database, then start the
// image file.
/********************************
结合上面分析,如果想直接通过svchost来加载一个dll达到注入绕过主防的话,
1。先找到那个pipe句柄
2。通信协议
3.刚发现还有一个检验,就是当通过pipe把消息发送到svchost,svchost收到服务名srv1后,它会检验srv1是否在注册表服务组里面的
键值链表里面,如果是的话,则才会去读取注册表servicedll获取路径,然后加载
后期搜索到的不错的paper