逆枫゛

Qt学习群:1149411109 群文件提供博客源码,定期答疑、更新学习资料。

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
今天我就以为个人的能力去研究一下,一个重要的进程svchost,本人水平极其有限,而svchost又是微软的一个非常重要的进程,很强大,所以题目就叫svchost进程的浅析,如果

有高手,可以后面跟贴补充~~~不甚感激!

科普一下:
写过一些病毒和木马的人,也许一定不会对svchost进程感到陌生,中过木马的人也许也看到过这个进程,没错,svchost进程,曾几何时一度风靡病毒界,很多病毒木马制作者一

定会用到它,由于杀毒软件的发展,一些杀毒软件已经严密监控住了这个进程(只是部分),所以svchost进程也一时受到冷默,大部分人转向了rootkit技术,可是现在rootkit技

术也成为各大杀软的头号目标,什么高启发式,云查杀,主动防御等技术都会对一般的rootkit绝不留情的,呵呵~~不过,道高一尺,魔高一丈,聪明的病毒和木马制作者总是会想

出一些更高操作的技术........

svchost.exe是NT核心系统的一个重要的进程,通过ctrl+alt+del打开任务管理器,就会在进程列表中看到进程中会出现几个svchost,这些svchost提供很多系统服务,如:rpcss服

务,dmserver服务,dhcp服务等,要想知道svchost进程,提供了哪些服务,很简单,(xp系统)在cmd中输入tasklist /svc即可,我们就会看到一大堆我们熟悉的服务:
RpcSs,Dhcp,Netman,srservice,swcsvc,winmgmt......

下面借用网上一段话来详细讲解一下svchost进程!
  Windows系统进程分为独立进程和共享进程两种,“Svchost.exe”文件存在于“%systemroot% system32”目录下,它属于共享进程。随着Windows系统服务不断增多,为

了节省系统资源,微软把很多服务做成共享方式,交由 Svchost.exe进程来启动。
    但Svchost进程只作为服务宿主,并不能实现任何服务功能,即它只能提供条件让其他服务在这里被启动,而它自己却不能给用户提供任何服务。那这些服务是如何实现的

呢?
    原来这些系统服务是以动态链接库(dll)形式实现的,它们把可执行程序指向 Svchost,由Svchost调用相应服务的动态链接库来启动服务。那Svchost又怎么知道某个系统

服务该调用哪个动态链接库呢?这是通过系统服务在注册表中设置的参数来实现。
  svchost.exe是一个属于微软Windows操作系统的系统程序,微软官方对它的解释是:Svchost.exe 是从动态链接库 (DLL) 中运行的服务的通用主机进程名称。这个程序对

系统的正常运行是非常重要,而且是不能被结束的。
  当系统启动时,Svchost将检查注册表中的服务部分,以构建需要加载的服务列表。Svchost的多个实例可以同时运行。每个Svchost会话可以包含一组服务,以便根据

Svchost的启动方式和位置的不同运行不同的服务,这样可以更好地进行控制且更加便于调试。
    Svchost组是由注册表[HKEY_LOCAL_MACHINE\ Software\Microsoft\Windows NT\CurrentVersion\Svchost]项来识别的。在这个注册表项下的每个值都代表单独的Svchost

组,并在我们查看活动进程时作为单独的实例显示。这里的键值均为REG_MULTI_SZ类型的值,并且包含该Svchost组里运行的服务名称

总结一点:服务是靠Svchost来启动的(并不是所有的)

举例说明:
  以windows xp为例,点击“开始”/“运行”,输入“services.msc”命令,弹出服务对话框,然后打开“remote procedure call”属性对话框,可以看到rpcss服务的可

执行文件的路径为“c:\windows\system32\svchost -k rpcss”,这说明rpcss服务是依靠svchost调用“rpcss”参数来实现的,而参数的内容则是存放在系统注册表中的。
    在运行对话框中输入“regedit.exe”后回车,打开注册表编辑器,找到[hkey_local_machine systemcurrentcontrolsetservicesrpcss]项,找到类型为“reg_expand_sz

”的键“magepath”,其键值为“%systemroot%system32svchost -k rpcss”(这就是在服务窗口中看到的服务启动命令),另外在“parameters”子项中有个名为“servicedll

”的键,其值为“% systemroot%system32rpcss.dll”,其中“rpcss.dll”就是rpcss服务要使用的动态链接库文件。这样 svchost进程通过读取“rpcss”服务注册表信息,就能

启动该服务了。

上面介绍了svchost的基本原理,下面我通过分析svchost主要代码来说明svchost,代码参考开源操作系统ReactOS~~


还是先按调试的顺序,先从函数入口看起吧,哈哈~~~
代码如下:
int _tmain (int argc, LPTSTR argv [])
{
  DWORD NrOfServices;
  LPSERVICE_TABLE_ENTRY ServiceTable;

  if (argc < 3)
  {
    //当输入参数少于3个时,可以做一些你想要的事,这里直接返回
    return 0;
  }

  if (_tcscmp(argv[1], _T("-k")) != 0)  //检查第两个参数是不是-k
  {
    //如果不是也直接返回
    return 0;
  }

  NrOfServices = LoadServiceCategory(argv[2]);  //如果参数正确,则检查注册表中的服务部分,以构建需要加载的服务列表,并在其中加载所有服务

  DPRINT1("NrOfServices: %lu\n", NrOfServices);
  if (0 == NrOfServices)
    return 0;

  ServiceTable =(LPSERVICE_TABLE_ENTRY)HeapAlloc(GetProcessHeap(), 0, sizeof(SERVICE_TABLE_ENTRY) * (NrOfServices + 1));   //分配存放服务列表的堆栈,构

建服务列表

  if (NULL != ServiceTable)
  {
    DWORD i;
    PSERVICE Service = FirstService;

    //填充服务列表,使用了数据结构中的链表进行操作
    for (i = 0; i < NrOfServices; ++i)        
    {
      DPRINT1("Loading service: %s\n", Service->Name);
      ServiceTable[i].lpServiceName = Service->Name;
      ServiceTable[i].lpServiceProc = Service->ServiceMainFunc;
      Service = Service->Next;
    }

    //用一个空的服务,做为服务列表的结束
    ServiceTable[i].lpServiceName = NULL;
    ServiceTable[i].lpServiceProc = NULL;

    if (FALSE == StartServiceCtrlDispatcher(ServiceTable))      //启动服务的分发函数
      printf("Failed to start service control dispatcher, ErrorCode: %lu\n", GetLastError());

    HeapFree(GetProcessHeap(), 0, ServiceTable);        //释放分配的服务列表堆栈
  }
  else
  {
    DPRINT1("Not enough memory for the service table, trying to allocate %u bytes\n", sizeof(SERVICE_TABLE_ENTRY) * (NrOfServices + 1));
  }

  DPRINT1("Freeing services...\n");
  FreeServices();      //做最后的清理工作

    return 0;
}


代码很简单,主要用到了StartServiceCtrlDispatcher启动服务,其实这就是编写svchost启动的服务和编写一般服务的区别,一般的服务会写成一个EXE的文件,然后自己编写一

个安装启动服务的程序,但是用svchost,其实svchost就帮我们做了这些工作,我们只需要在某个注册表下写入svchost的值,svchost会在像上面操作的一样,在系统启动时,通

过svchost的参数,来检查注册表,然后建立一个服务列表,再通过遍历服务列表,一项一项的加启它们就OK了!

看了上面的一段话,我想大家都已经很清楚了svchost进程,下面我在进一步讲解一下~~上面用到了一个自定义函数LoadServiceCategory来检查注册表,我们来看看它做了哪些工

作吧~~
DWORD LoadServiceCategory(LPCTSTR ServiceCategory)
{
  HKEY hServicesKey;
  DWORD KeyType;
  DWORD BufferSize = REG_MAX_DATA_SIZE;
  TCHAR Buffer[REG_MAX_DATA_SIZE];
  LPCTSTR ServiceName;
  DWORD BufferIndex = 0;
  DWORD NrOfServices = 0;

  //得到所有的服务
  //static LPCTSTR SVCHOST_REG_KEY = _T("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SvcHost")
  if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, SVCHOST_REG_KEY, 0, KEY_READ, &hServicesKey))
  {
    DPRINT1("Could not open service category: %s\n", ServiceCategory);
    return 0;
  }

  if (ERROR_SUCCESS != RegQueryValueEx(hServicesKey, ServiceCategory, NULL, &KeyType, (LPBYTE)Buffer, &BufferSize))
  {
    DPRINT1("Could not open service category (2): %s\n", ServiceCategory);
    RegCloseKey(hServicesKey);
    return 0;
  }

  //对应上面的RegOpenKeyEx
  RegCloseKey(hServicesKey);

  //加载所有的服务
  ServiceName = Buffer;
  while (_T('\0') != ServiceName[0])
  {
    size_t Length;
    
    Length = _tcslen(ServiceName);
    if (0 == Length)
      break;

    if (TRUE == PrepareService(ServiceName))     //写入到注册表的相应位置,加载服务
      ++NrOfServices;

    BufferIndex += (Length + 1) * sizeof(TCHAR);

    ServiceName = &Buffer[BufferIndex];
  }

  return NrOfServices;
}

通过上面的这个函数,将SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SvcHost键下的所有服务名,写入到注册表相应位置,并加载服务,这个函数很简单只进行了注册

表的查寻,然后将得到的键值存入一个数组中,在从数组中取出相应值进行操作,逐个操作,并调用了一个自定义函数PrepareService~~再来分析一下PrepareService这个函数在

为我们做哪些工作~~~
BOOL PrepareService(LPCTSTR ServiceName)
{
  HKEY hServiceKey;
  TCHAR ServiceKeyBuffer[MAX_PATH + 1];
  DWORD LeftOfBuffer = sizeof(ServiceKeyBuffer) / sizeof(ServiceKeyBuffer[0]);
  DWORD KeyType;
  PTSTR Buffer = NULL;
  DWORD BufferSize = MAX_PATH + 1;
  LONG RetVal;
  HINSTANCE hServiceDll;
  TCHAR DllPath[MAX_PATH + 2]; /* See MSDN on ExpandEnvironmentStrings() for ANSI strings for more details on + 2 */
  LPSERVICE_MAIN_FUNCTION ServiceMainFunc;
  PSERVICE Service;

  //构建服务在注册表中的一些参数
  //static LPCTSTR SERVICE_KEY = _T("SYSTEM\\CurrentControlSet\\Services\\")
  //static LPCTSTR PARAMETERS_KEY  = _T("\\Parameters");
  _tcsncpy(ServiceKeyBuffer, SERVICE_KEY, LeftOfBuffer);
  LeftOfBuffer -= _tcslen(SERVICE_KEY);
  _tcsncat(ServiceKeyBuffer, ServiceName, LeftOfBuffer);
  LeftOfBuffer -= _tcslen(ServiceName);
  _tcsncat(ServiceKeyBuffer, PARAMETERS_KEY, LeftOfBuffer);
  LeftOfBuffer -= _tcslen(PARAMETERS_KEY);

  if (LeftOfBuffer < 0)
  {
    DPRINT1("Buffer overflow for service name: '%s'\n", ServiceName);
    return FALSE;
  }

  //打开相应注册表项,查找要加载的DLL名
  if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, ServiceKeyBuffer, 0, KEY_READ, &hServiceKey))
  {
    DPRINT1("Could not open service key (%s)\n", ServiceKeyBuffer);
    return FALSE;
  }

  do
  {
    if (Buffer)
      HeapFree(GetProcessHeap(), 0, Buffer);

    Buffer = (PTSTR)HeapAlloc(GetProcessHeap(), 0, BufferSize);
    if (NULL == Buffer)
    {
      DPRINT1("Not enough memory for service: %s\n", ServiceName);
      return FALSE;
    }

    RetVal = RegQueryValueEx(hServiceKey, _T("ServiceDll"), NULL, &KeyType, (LPBYTE)Buffer, &BufferSize);

  } while (ERROR_MORE_DATA == RetVal);


  RegCloseKey(hServiceKey);

  if (ERROR_SUCCESS != RetVal || 0 == BufferSize)
  {
    DPRINT1("Could not read 'ServiceDll' value from service: %s, ErrorCode: 0x%x\n", ServiceName, RetVal);
    HeapFree(GetProcessHeap(), 0, Buffer);
    return FALSE;
  }

  //传换成系统中的DLL的目录
  BufferSize = ExpandEnvironmentStrings(Buffer, DllPath, sizeof(DllPath));
  if (0 == BufferSize)
  {
    DPRINT1("Invalid ServiceDll path: %s\n", Buffer);
    HeapFree(GetProcessHeap(), 0, Buffer);
    return FALSE;
  }

  HeapFree(GetProcessHeap(), 0, Buffer);

  
  DPRINT1("Trying to load dll\n");
  hServiceDll = LoadLibrary(DllPath);  //加载服务的DLL

  if (NULL == hServiceDll)
  {
    DPRINT1("Unable to load ServiceDll: %s, ErrorCode: %u\n", DllPath, GetLastError());
    return FALSE;
  }

  ServiceMainFunc = (LPSERVICE_MAIN_FUNCTION)GetProcAddress(hServiceDll, "ServiceMain");  //查找DLL中存在的ServiceMain函数,现在知道为什么每个DLL服务中

必须包括一个ServiceMain函数了吧

  //分配一个服务列表中的一个服务
  Service =(PSERVICE)HeapAlloc(GetProcessHeap(), 0, sizeof(SERVICE));
  if (NULL == Service)
  {
    DPRINT1("Not enough memory for service: %s\n", ServiceName);
    return FALSE;
  }

  memset(Service, 0, sizeof(SERVICE));
  Service->Name =(PTSTR)HeapAlloc(GetProcessHeap(), 0, _tcslen(ServiceName) + sizeof(TCHAR));
  if (NULL == Service->Name)
  {
    DPRINT1("Not enough memory for service: %s\n", ServiceName);
    HeapFree(GetProcessHeap(), 0, Service);
    return FALSE;
  }
  
  //填充服务列表中的服务
  //服务列表的结构如下:
  /*
  typedef struct _SERVICE {
        PTSTR    Name;  //服务名
    HINSTANCE  hServiceDll;  //此服务所使用的DLL
        LPSERVICE_MAIN_FUNCTION    ServiceMainFunc;  //DLL中的ServiceMain函数
    struct _SERVICE  *Next;  //指向下一个服务
  } SERVICE, *PSERVICE;
  */
  _tcscpy(Service->Name, ServiceName);
  Service->hServiceDll = hServiceDll;
  Service->ServiceMainFunc = ServiceMainFunc;

  Service->Next = FirstService;
  FirstService = Service;

  return TRUE;
}

在上面的操作中我们为服务列表中的服务分配了堆栈,最后一定要记得释放~~于是在main函数的最后调用了一个自定义的函数FreeServices用来释放前面分配的堆栈空间,代码如

下:
VOID FreeServices(VOID)
{
  while (FirstService)        
  {
    PSERVICE Service = FirstService;
    FirstService = Service->Next;

    FreeLibrary(Service->hServiceDll);  //释放前面加载的服务的DLL

    HeapFree(GetProcessHeap(), 0, Service->Name);  释放堆栈  
    HeapFree(GetProcessHeap(), 0, Service);
  }
}

如果你能看到这里,基本上svchost的进程的原理你已经掌握了,如果你写过用svchost启动的服务,我想你一定知道为什么要那么写了~~~其实每个事物的存在都会有它的原因,我们只要肯花时间去研究,就没有什么不懂的!(至于,怎么样编写基于svchost的服务,我就不说了,大家自己google吧,以前有位很牛的前辈用Win32汇编写过一篇很详细的文章)

上面我们详细的分析了一下开源的操作系统ReactOS的Svchost的源代码,其实Windows的Svchost的实现方式和这是差不多的,只是其中还应用了一些用于处理多线程同步的代码,有人也许会问,windows不开布源代码,为何ReactOS组织的人会写出如此相近的源代码,其实,以我个人的想法,无非就是他们经过反汇编windows的svchost进程,而得到的,因为windows中的所有的DLL,EXE,SYS等等之类的东东,微软并没有加壳保护,所以只要反汇编牛一点的人,我想写出类似的代码也不是问题的!!呵呵,只是我的猜想~~有兴趣的人,可以去反汇编看看Windnows中的svchost进程(可以用IDA反汇编看看,还可以用OD跟踪调试,只是记住带参数,不然就直接返回了,我是直接把那句判断跳过,只想看看他的实现原理就可以),你会发现,原来就是这样的!

好了,就写这么多吧,写多了,大家看着也累,我想svchost的基本原理和方法,大家一定都有了一个大概的了解,本人水平有限,听说老毛的《windows内核情景分析》中最后有一章讲到过svchost进程,我没看过,只看过目录上有,也不知道他老人家是怎么讲的,肯定没他老人家那等功夫,如果还看不懂,可以去看看他老人家的书,就是有点贵~~不过说不定,一狠心我就买了,我喜欢拿着书看,并把它看成黑黑的,然后整理成薄薄的笔记,也许就是别人说的,把一本很厚的书看薄了,你就学会了~~再见,我会用一整年的时间(只是有个大概认识,呵呵,真正在理解可能要等以后工作中慢慢体会才能得到windows和linux的精华所在),好好研究一下windows的内核,因为打算以后就从事系统底层驱动开发这方面的工作了,也许过几年还会去研究linux内核,不过都是后话了,在这期间,如果偶有心得体会,我会及时将自己的一些学习休会与心得发到论坛上,希望能给更多喜欢研究内核的朋友们一点帮助,先把windows这个难缠的家伙搞定,学习windows内核不像linux内核(相对难一点,个人观点),linux内核源码网上有很多,但windows有时要反汇编才能明白其中的原理和精华~~其实我们只有掌握了一种学习能力,在加上坚持不懈的努力,最后都可以变成“牛”,学习能力的培养就在于你平时的日积月累了,呵呵,这个谁都帮不了你的!!(最后补充非常重要的一点:小弟前面的一些言论,可以不完全正确,如果实在看不下去的,可以直接跳过,我不想看到因为我的一些胡言乱语,又引起后面一些口水战之类的,再次重申:我的言论不针对任何个人,任何组织和公司,因为我没时间和你口水战,我想大家有时间,还是多花在时间在自己喜欢的值得为之努力奋斗的事情上),但是对于文中的错误和对此有更深层的理解,欢迎指点,后面跟贴,以免我这小菜误导很多看到我这篇文章的人!

最后,把我在vc6.0下编译通过的svchost源码附加上去,用vs2008同样编译通过!*转载请注明来自看雪论坛@PEdiy.com 


原文链接:http://bbs.pediy.com/showthread.php?t=127798

posted on 2015-07-30 10:32  逆枫゛  阅读(430)  评论(0编辑  收藏  举报