一次失败的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

SVChost hacking a-z

在W2K中提升权限的几个攻击实例之成败心得

posted @ 2012-06-07 13:19  kkindof  阅读(1268)  评论(0编辑  收藏  举报