基于pspCidTable的进程检测技术

来源:互联网 发布:学英语的网站 知乎 编辑:程序博客网 时间:2024/05/16 02:48

基于pspCidTable的进程检测技术

-----------------------------------------------
一.     pspCidTable概念及内核调试
二.     获取pspCidTable的方法
三.     几种进程检测方法的对比
四.     anti-pspCidTable技术及其他



一.     pspCidTable概念及内核调试
-----------------------------------------------------

pspCidTable是内核未导出的HANDLE_TALBE结构,它保存着所有进程和线程对象的指针。
只要能遍历这个PspCidTable句柄表,就可以遍历到系统的所有进程,包括所有隐藏进程,除非你抹掉pspCidTable...

结构如下:
typedef struct _HANDLE_TABLE
{
   ULONG           Flags;
   LONG             HandleCount;
   PHANDLE_TABLE_ENTRY **Table;
   PEPROCESS         QuotaProcess;
   HANDLE           UniqueProcessId;
   LONG             FirstFreeTableEntry;
   LONG             NextIndexNeedingPool;
   ERESOURCE         HandleTableLock;
   LIST_ENTRY         HandleTableList;
   KEVENT           HandleContentionEvent;
} HANDLE_TABLE , *PHANDLE_TABLE ;

我们利用调试器先从内核里找到这张表:

kd> version
Windows XP Kernel Version 2600 (Service Pack 2) UP Free x86 compatible
Built by: 2600.xpsp_sp2_rtm.040803-2158
Kernel base = 0x804d7000 PsLoadedModuleList = 0x8055ab20
//...
dbgeng:   image 6.7.0005.1, built Wed Jun 20 11:50:35 2007
//...

kd> dd pspCidTAble
80560ce0   e10018c8 00000002 00000000 00000000
80560cf0   00000000 00000000 00000000 00000000
80560d00   00000000 00000000 00000000 00000000
80560d10   00000000 00000000 00000000 00000000

kd> dt _HANDLE_TABLE e10018c8
nt!_HANDLE_TABLE
   +0x000 TableCode         : 0xe1003000
   +0x004 QuotaProcess     : (null)
   +0x008 UniqueProcessId   : (null)
   +0x00c HandleTableLock   : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList   : _LIST_ENTRY [ 0xe10018e4 - 0xe10018e4 ]
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo         : (null)
   +0x02c ExtraInfoPages   : 0
   +0x030 FirstFree         : 0x770
   +0x034 LastFree         : 0x764
   +0x038 NextHandleNeedingPool : 0x800
   +0x03c HandleCount       : 266
   +0x040 Flags             : 1
   +0x040 StrictFIFO       : 0y1

注意NextHandleNeedingPool : 0x800,句柄表依靠NextHandleNeedingPool来计数的,
凡是大于0x800(2048)的部分就放入第二张表中.所以当CID(PID)号大于0x800时,
就需要查找第二张表了.例如要定位CID等于0x844的句柄表位置,
0xe1003800 + ( 0x844 - 0x800 ) * 2 这个偏移就是CID为0x844的句柄项了.

kd> dd e1003000
e1003000   00000000 fffffffe 81fc9a01 00000000
e1003010   81fc9789 00000000 81fc9341 00000000
e1003020   81fc8021 00000000 81fc8da9 00000000
e1003030   81fc8b31 00000000 81fc88b9 00000000
e1003040   81fc8641 00000000 81fc83c9 00000000
e1003050   81fc7021 00000000 81fc7da9 00000000
e1003060   81fc7b31 00000000 81fc78b9 00000000
e1003070   81fc7641 00000000 81fc73c9 00000000

kd> !object 81fc9a01
Object: 81fc9a01   Type: (0081fc90)
     ObjectHeader: 81fc99e9 (old version)
     HandleCount: 1073741824   PointerCount: 33554432

kd> !object 81fc9a00
Object: 81fc9a00   Type: (81fc9040) Process
     ObjectHeader: 81fc99e8 (old version)
     HandleCount: 2   PointerCount: 56

81fc9a00的进程是System...以后的都是其线程。

kd> !object 81fc73c9
Object: 81fc73c9   Type: (0081fc9e)
     ObjectHeader: 81fc73b1 (old version)
     HandleCount: 1879048192   PointerCount: 0

kd> !object 81fc73c8
Object: 81fc73c8   Type: (81fc9e70) Thread
     ObjectHeader: 81fc73b0 (old version)
     HandleCount: 0   PointerCount: 1

kd> !thread 81fc73c8
THREAD 81fc73c8   Cid 0004.003c   Teb: 00000000 Win32Thread: 00000000 WAIT: (WrQueue) UserMode Non-Alertable
     80561b7c   Unknown
Not impersonating
DeviceMap                 e10001e8
Owning Process             81fc9a00       Image:         System
Wait Start TickCount       16940           Ticks: 20342 (0:00:03:23.712)
Context Switch Count       418         
UserTime                   00:00:00.000
KernelTime                 00:00:03.124
//....

我们可以根据PID查找进程对象.HANDLE_TABLE的Entry地址+PID*2 。
在此之前,你可以用:
kd>!process 0 0
枚举所有的进程,然后再根据PID来查找验证。
我们以calc.exe PID738为例:

kd> dd e1003000+738*2
e1003e70   81f15da1 00000000 81ef26e1 00000000
e1003e80   81d1f021 00000000 00000000 00000124
e1003e90   00000000 00000744 00000000 00000150
e1003ea0   00000000 0000075c 00000000 00000750
e1003eb0   00000000 00000760 00000000 00000758
e1003ec0   00000000 00000764 00000000 00000768
e1003ed0   00000000 0000076c 00000000 00000774
e1003ee0   00000000 0000077c 00000000 00000770

kd> !object 81f15da1
Object: 81f15da1   Type: (0081fc90)
     ObjectHeader: 81f15d89 (old version)
     HandleCount: 1073741824   PointerCount: 33554432

kd> !object 81f15da0
Object: 81f15da0   Type: (81fc9040) Process
     ObjectHeader: 81f15d88 (old version)
     HandleCount: 2   PointerCount: 15

kd> !process 81f15da0
PROCESS 81f15da0   SessionId: 0   Cid: 0738     Peb: 7ffd5000   ParentCid: 03ec
     DirBase: 0ddf4000   ObjectTable: e1a222f8   HandleCount:   26.
     Image: calc.exe
//...


二.     获取pspCidTable的方法
-----------------------------------------------------
罗嗦几句,在得到pspCidTable后,我们可以有很多方法来实现枚举。
比如:
1.利用未导出的ExEnumHandleTable函数 ;
2.直接自己获取PHANDLE_TABLE_ENTRY等,然后PsLookupProcessByProcessId ;
回归正题:

1.在PsLookupProcessByProcessId函数中搜索特征串定位 PspCidTalbe.
这个方法也是本人用的方法,代码如下:

void GetPspCidTable()
{
     PUCHAR cPtr;
     unsigned char * pOpcode;
     ULONG Length;

     for (cPtr = (PUCHAR)PsLookupProcessByProcessId;
         cPtr < (PUCHAR)PsLookupProcessByProcessId + PAGE_SIZE;
         cPtr += Length)
     {
         Length = SizeOfCode(cPtr, &pOpcode);     //credit to LDasm.c by Ms-Rem

         if (!Length) break;

         if (*(PUSHORT)cPtr == 0x35FF && *(pOpcode + 6) == 0xE8)
         {
             pPspCidTable = **(PVOID **)(pOpcode + 2);
             break;
         }
     }
}

其中,全局变量:
PHANDLE_TABLE pPspCidTable = NULL;

当然,PsLookupProcessThreadByCid里也是一样的。



2.利用KDDEBUGGER_DATA32结构得到pspCidTable.
流程是这样的:     KdEnableDebugger->KdInitSystem->KdDebuggerDataBlock->KDDEBUGGER_DATA32
         ->PspCidTable
当然也是内存字节搜索,比如006A006Ah,05C6006ah,0e801h,4742444bh,42444b68h等等。

typedef struct _KDDEBUGGER_DATA32 {
     DBGKD_DEBUG_DATA_HEADER32 Header;
     ULONG   KernBase;
     ULONG   BreakpointWithStatus;       // address of breakpoint
     ULONG   SavedContext;
     USHORT   ThCallbackStack;             // offset in thread data
     USHORT   NextCallback;               // saved pointer to next callback frame
     USHORT   FramePointer;               // saved frame pointer
     USHORT   PaeEnabled:1;
     ULONG   KiCallUserMode;             // kernel routine
     ULONG   KeUserCallbackDispatcher;   // address in ntdll

     ULONG   PsLoadedModuleList;
     ULONG   PsActiveProcessHead;
     ULONG   PspCidTable;             // <--------- What we need!!
     //...
     ULONG   MmLoadedUserImageList;
} KDDEBUGGER_DATA32, *PKDDEBUGGER_DATA32;


三.     几种进程检测方法的对比
-----------------------------------------------------
1.既然要检测隐藏进程,那就先写个简单的隐藏吧。

typedef struct ServiceDescriptorEntry {
         unsigned int *ServiceTableBase;
         unsigned int *ServiceCounterTableBase; //Used only in checked build
         unsigned int NumberOfServices;
         unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;

__declspec(dllimport)   ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

嗯,ssdt,hook掉ZwQuerySystemInformation。网上代码多如牛毛,就不多浪费时间现眼了。
if(0 == memcmp(SystemProcesses->ProcessName.Buffer, L"services.exe", 24))
把services.exe藏起来吧...



2.枚举进程,用户态下最多的(包括taskmgr)就是ZwQuerySystemInformation了。
真是哪壶不开提哪壶啊,哈哈...

     ZwQuerySystemInformation(SystemProcessesAndThreadsInformation, pBuffer,
                         cbBuffer, NULL);
 
     PSYSTEM_PROCESS_INFORMATION pInfo =
         (PSYSTEM_PROCESS_INFORMATION)pBuffer;
 
     for (;;)
     {
         printf("ProcessID: %d (%ls)/n", pInfo->ProcessId,
             pInfo->ProcessName.Buffer);
     
         if (pInfo->NextEntryDelta == 0)
             break;
 
         pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo)
             + pInfo->NextEntryDelta);
     }

分析结果:
================================================
ProcessID: 0 ((null))
ProcessID: 4 (System)
ProcessID: 312 (smss.exe)
ProcessID: 404 (csrss.exe)
ProcessID: 428 (winlogon.exe)
ProcessID: 492 (lsass.exe)
ProcessID: 636 (svchost.exe)
ProcessID: 704 (svchost.exe)
ProcessID: 740 (svchost.exe)
ProcessID: 780 (svchost.exe)
ProcessID: 1004 (explorer.exe)
ProcessID: 1024 (vmsrvc.exe)
ProcessID: 1152 (vpcmap.exe)
ProcessID: 1320 (vmusrvc.exe)
ProcessID: 1512 (svchost.exe)
ProcessID: 1816 (InstDrv.exe)
ProcessID: 1836 (notepad.exe)
ProcessID: 1848 (calc.exe)
ProcessID: 200 (dv.exe)
ProcessID: 344 (InstDrv.exe)
ProcessID: 356 (enumprocsnt.exe)
==================================================
没有services.exe...



3.该我们的 get PspCidTable to ExEnumHandleTable 出马了。
为了方便,代码做了很多简略处理,EnumHandleCallback也没发挥应有的作用,
凑合着看吧 :-)

ULONG GetFunctionAddr(IN PCWSTR FunctionName)
{
     UNICODE_STRING UniCodeFunctionName;
     RtlInitUnicodeString( &UniCodeFunctionName, FunctionName );
         return (ULONG)MmGetSystemRoutineAddress( &UniCodeFunctionName );
}

BOOLEAN EnumHandleCallback(PHANDLE_TABLE_ENTRY HandleTableEntry,HANDLE Handle,
                           PVOID EnumParameter)

     NTSTATUS ntStatus;
     HANDLE Cid;
     PEPROCESS   Process;
     PETHREAD   Thread;

     if(EnumParameter== HandleTableEntry)
     {
         return TRUE;
     }
     else
     {     //   we ignore the in handle param,use i,j to walk the list
         for(uWalkTableCount=0;uWalkTableCount<0x100;uWalkTableCount++)
         {
             if(HandleTableEntry->Object)
             {
                 Cid=(HANDLE)((1024*uWalkTablePage)+(uWalkTableCount<<2));
                 //you can take Handle,too; 
             
                 if (Cid> (PVOID)4)
                 {//you can walk the list yourself completely...I'm slothful :-)
                     ntStatus = PsLookupProcessByProcessId( Cid, &Process );
                     if(NT_SUCCESS(ntStatus))
                     {
                         DbgPrint("PID:%4d/tNAME:/t%-16s/n",
                             Cid, ((PUCHAR)Process+EPROC_NAME_OFFSET) );
                         ObDereferenceObject( Process );
                     }
                 }
                 else
                 {
                     if (Cid== 0)
                     {
                         DbgPrint("PID:%4d/tNAME:/tIdle/n",0);   //简化
                     }
                     else
                     {
                         DbgPrint("PID:%4d/tNAME:/tSystem/n",4);   //简化,自己EPROCESS吧
                     }
                 }
             }
         }
         uWalkTablePage++;
     
         return FALSE;
     }
}

NTSTATUS
DriverEntry( IN PDRIVER_OBJECT   DriverObject,
     IN PUNICODE_STRING RegistryPath )
{
     HANDLE h; 

     DbgPrint("DriverEntry Loading.../n");
     DriverObject->DriverUnload = DriverUnload; 
 
     ExEnumHandleTable = (EXENUMHANDLETABLE)GetFunctionAddr(L"ExEnumHandleTable");
     if ( ExEnumHandleTable == NULL )
         {
         DbgPrint("Get ExEnumHandleTable Addr Error!!");
         return STATUS_DEVICE_CONFIGURATION_ERROR;
     }
     DbgPrint("Address of ExEnumHandleTable:%x/n",ExEnumHandleTable);

     GetPspCidTable();
     DbgPrint("CidTable:%x/n",pPspCidTable);
 
     if (!ExEnumHandleTable(pPspCidTable, EnumHandleCallback, NULL, &h ))
     {
         DbgPrint( "HandleTable Found: %lx./n", h );
     }
     else
     {
         DbgPrint( "HandleTable not found./n");
     }     
 
     return STATUS_SUCCESS;
}


分析结果:
==================================================
Address of ExEnumHandleTable:805a4274
CidTable:e10018c8
PID:   0 NAME: Idle
PID:   4 NAME: System
PID: 200 NAME: dv.exe       
PID: 312 NAME: smss.exe     
PID: 344 NAME: InstDrv.exe 
PID: 356 NAME: enumprocsnt.exe
PID: 404 NAME: csrss.exe   
PID: 428 NAME: winlogon.exe 
PID: 480 NAME: services.exe 
PID: 492 NAME: lsass.exe   
PID: 636 NAME: svchost.exe 
PID: 704 NAME: svchost.exe 
PID: 740 NAME: svchost.exe 
PID: 780 NAME: svchost.exe 
PID:1004 NAME: explorer.exe 
PID:1024 NAME: vmsrvc.exe   
PID:1152 NAME: vpcmap.exe   
PID:1320 NAME: vmusrvc.exe 
PID:1512 NAME: svchost.exe 
PID:1816 NAME: InstDrv.exe 
PID:1836 NAME: notepad.exe 
PID:1848 NAME: calc.exe     
Handle Found: bb40.
=====================================================
看起来蛮强大,连死进程也能读出来...   :-)



4.ring3下的间接pspCidTable,看代码:

void main()

     for (int i=0;i<=65535;i+=4)     //实际上,取到0x4e1c就够了
     {
         if(OpenProcess(PROCESS_QUERY_INFORMATION,FALSE,i)!= 0)
         {
             printf("ProcessID: %d/n",i);
             CloseHandle(&i);
         }
     } 
 
     getchar(); 
}

很猥琐的代码,哈哈...
想得到更多信息,自己#include "psapi.h",把lib库link上去,然后:
EnumProcessModules,GetModuleFileNameEx,GetProcessImageFileName等等吧...
至于想比较出哪些是隐藏进程或者恶意进程,EnumProcesses或者CreateToolhelp32Snapshot,
得到用户态下的一张表对比就知道了。


分析结果:
=============================================
ProcessID: 4
ProcessID: 200
ProcessID: 312
ProcessID: 344
ProcessID: 356
ProcessID: 428
ProcessID: 480
ProcessID: 492
ProcessID: 636
ProcessID: 740
ProcessID: 852
ProcessID: 1004
ProcessID: 1024
ProcessID: 1152
ProcessID: 1320
ProcessID: 1836
ProcessID: 1848
==============================================
ProcessID: 480,嗯,是services.exe...

其实原理很简单,OpenProcess->NtOpenProcess->PsLookupProcessByProcessId,
反汇编PsLookupProcessByProcessId,有这样一段代码:
mov     eax, large fs:124h
push     [ebp+ProcessId]
mov     esi, eax
dec     dword ptr [esi+0D4h]
push     PspCidTable
call     ExMapHandleToPointer

还原出来是这样的:
PHANDLE_TABLE_ENTRY CidEntry;
CidEntry = ExMapHandleToPointer(PspCidTable, ProcessId);

像PsLookupProcessThreadByCid,PspExitThread,PspCreateThread等也有类似的代码,
但是由于入函数太深,查找不方便。

-----------------------------------
综合起来看,pspCidTable枚举进程还是很成功的,一般的进程隐藏技术都过不了它。 :-)



四.     anti-pspCidTable技术及其他
-----------------------------------------------------

写这篇的原因仅仅是先前有人在maillist上问起,这个技术并不神秘,想anti它很简单,抹掉pspCidTable就行了,
扫描内存得到pspCidTable,然后对应EPROCESS进行分析,找到要隐藏的进程,将相关信息置成0,安放一个进程notify routine。
FUTo的代码一直放着还没看,也不知道是不是其他的思想,有空扫一眼。
文章没提到其他检测技术,比如用activelist,handletable,handletablelisthead获得进程表,
挂钩KiSwapContext,KiService,CreateProcessNotifyRoutine等等...
当然,相应地可以摘除进程控制块中的ActiveProcessLinks里的自己,或者摘除csrss.exe进程句柄表,
再或者挂钩SwapContext,自己实现线程调度...
隐藏进程的方法多种多样,也有很多高深的技术,未公开的技术,谁叫RK和ARK技术总是相生相克的呢...
:-)
原创粉丝点击