PspCidTable进程枚举

-------------------------------------这是分割线---------------------------------------

Windows句柄表格式

句柄是Windows对象管理中引入的一个东西,它的实际意义是对象在句柄表中的索引。Windows2000使用的是固定的三层句柄表,而WindowsXP和Windows2003都是使用的动态可扩展的三层句柄表,这是一种很优秀的结构,易扩展,且查找迅速,值得学习。通常情况下每个进程的句柄表都是一级表,当句柄数超过一级表的容量时,就会扩展为二级表,此时二级表中放的是指向一级表的指针。同样,当二级表也放满时,就会扩展为三级表,里面放指向二级表的指针。但是通常我们看不到这种情况,因为就目前的大多数情况来说,二级表的容量已经够我们用了。
接下来具体地看一下进程的句柄表。每个进程都有一个句柄表,包括System进程(不过System的句柄表稍有点特殊),但是Idle除外,因为它并不是一个事实上真正的进程。在进程对象EPROCESS中,可以直接找到句柄表指针。
来找个进程对象看看:

lkd> dt _EPROCESS 8a92cb98
nt!_EPROCESS
 
  ...
 
  +0x0c4ObjectTable     : 0xe23d3690 _HANDLE_TABLE
 
  ...
句柄表是一个HANDLE_TABLE结构,继续来看:

lkd> dt _HANDLE_TABLE0xe23d3690
nt!_HANDLE_TABLE
 
  +0x000TableCode       : 0xe3202001 //句柄表
 
  +0x004QuotaProcess    : 0x89833da0 _EPROCESS //所属进程的EPROCESS
 
  +0x008 UniqueProcessId :0x00000430 //所属进程的pid
 
  +0x00c HandleTableLock : [4]_EX_PUSH_LOCK //句柄表锁,用于进程某些操作时锁住句柄表
 
  +0x01c HandleTableList :_LIST_ENTRY [ 0xe216735c - 0xe2394fd4 ]//句柄表的双链,所有进程的句柄表链在一起,如果搞隐藏进程的话,这个地方是必须要照顾到的。
 
  +0x024 HandleContentionEvent :_EX_PUSH_LOCK
 
  +0x028DebugInfo       : (null)
 
  +0x02cExtraInfoPages   : 0
 
  +0x030FirstFree       : 0x114c
 
  +0x034LastFree        : 0
 
  +0x038 NextHandleNeedingPool :0x1800 //当前句柄表池的上界
 
  +0x03cHandleCount     : 1152 //当前进程中句柄总数
 
  +0x040Flags           : 0
 
  +0x040StrictFIFO      : 0y0

这里面最重要的,当然就是TableCode了。
TableCode的低两位被用作标志位,用于表示当前句柄表的级数。0,1,2分别表示一级表,二级表,三级表。
比如这里的0xe3202001,掩去高位,低两位的值为1,即为二级表。对于二级表,表中存放的是指向一级表的指针,一级表又被称为基本表,这实际上一个HANDLE_TABLE_ENTRY数组,它里面存放的HANDLE_TABLE_ENTRY中才是真正的对象,其定义如下:

typedef struct _HANDLE_TABLE_ENTRY{

   //
    // Thepointer to the object overloaded with three ob attributes bitsin
    // the lowerorder and the high bit to denote locked or unlocked entries
   //

   union {

       PVOID Object;

       ULONG ObAttributes;

       PHANDLE_TABLE_ENTRY_INFO InfoTable;

       ULONG_PTR Value;
   };

   //
    // Thisfield either contains the granted access mask for the handle oran
    // obvariation that also stores the same information. Or in the caseof
    // a freeentry the field stores the index for the next free entry inthe
    // freelist. This is like a FAT chain, and is used instead ofpointers
    // to maketable duplication easier, because the entries can just be
    // copiedwithout needing to modify pointers.
   //

   union {

       union {

           ACCESS_MASK GrantedAccess;

           struct {

               USHORT GrantedAccessIndex;
               USHORT CreatorBackTraceIndex;
           };
       };

       LONG NextFreeTableEntry;
   };

} HANDLE_TABLE_ENTRY,*PHANDLE_TABLE_ENTRY;

一级表可以放多大的句柄呢?按WRK中的宏展开,有(TABLE_PAGE_SIZE /sizeof(HANDLE_TABLE_ENTRY))*HANDLE_VALUE_INC,即0x1000/8*4=0x800。
计算过程:一级表(基本表)的大小为一页即4K=0x1000Byte,而HANDLE_TABLE_ENRY大小为8字节,则可存放的个数为0x1000/8=0x200=512,而句柄以4为步进,因此最大句柄为0x200*4=0x800.其中可存放的最大句柄不超过0x800(最大为0x800-4),而每个一级表的第一个HANDLE_TABLE_ENTRY的Object总是为0,因为我们都知道0是一个无效的句柄,它不指向一个有效的对象。因此,每个一级表实际存放的句柄数为511个。我们当前查看的句柄表中共有句柄1152个,显然一个表是不够的,1152=511*2+130,这里显然需要三个表,且第三个表未放满。而TableCode值为0xe3202001,其低两位也说明了这是个二级表。掩去低两位之后才是二级表的真正地址。来查看一下:

lkd> dd 0xe3202000
e3202000 e2a7b000 e3203000 e2c1e000 00000000
e3202010 00000000 00000000 00000000 00000000
e3202020 00000000 00000000 00000000 00000000
e3202030 00000000 00000000 00000000 00000000
e3202040 00000000 00000000 00000000 00000000

没错的,二级表中放了三个一级表指针。再来查看一下第一个一级表的内容。

lkd> dd e2a7b000
e2a7b000 00000000 fffffffe e100b4e9 000f0003
e2a7b010 e1a9ef41 00000003 898343b3 00100020
e2a7b020 8982ece9 021f0003 e1a794e9 000f000f
e2a7b030 e23a61b9 021f0001 e2390a59 020f003f

明显看到,第一个HANDLE_TABLE_ENTRY的Object为0,从第二个起才是有效的对象(如果某对象所对应的句柄被关闭,那么该对象就会从相应的句柄表中删除,所以并非句柄表中的每个位置存放的都是有效对象)。
接下来看HANDLE_TABLE_ENTRY,e100b4e9 000f0003这一对数据就是HANDLE_TABLE_ENTRY结构中的Object和GrantedAccess了。但是这里的Object并不是直接指向对象的。因为对象体总是8字节对齐,所以地址的低三位总是0,因此被用于标志位,表明该对象所对应的句柄的一些属性,比如可继承等等。因此,要对这里得到的Object掩去低三位,才会得到一个指向对象头OBJECT_HEADER的指针。
e100b4e9这里掩去之后为e100b4e8

lkd> dt _OBJECT_HEADERe100b4e8
nt!_OBJECT_HEADER
   +0x000PointerCount    : 28
   +0x004HandleCount     : 27
   +0x004NextToFree      : 0x0000001b
   +0x008Type            : 0x8a928e70 _OBJECT_TYPE
   +0x00cNameInfoOffset   : 0x20 ' '
   +0x00d HandleInfoOffset : 0''
   +0x00e QuotaInfoOffset : 0''
   +0x00fFlags           : 0x36 '6'
   +0x010 ObjectCreateInfo :0x00000001 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged :0x00000001
   +0x014 SecurityDescriptor :0xe100b44d
   +0x018Body            : _QUAD

继续来看一下这个OBJECT_TYPE

lkd> dt _OBJECT_TYPE0x8a928e70
nt!_OBJECT_TYPE
   +0x000Mutex           : _ERESOURCE
   +0x038TypeList        : _LIST_ENTRY [ 0xe100b4d8 - 0xe100b4d8 ]
   +0x040Name            : _UNICODE_STRING "KeyedEvent"
   +0x048DefaultObject   : 0x80568b40
   +0x04cIndex           : 0x10
   +0x050 TotalNumberOfObjects :1
   +0x054 TotalNumberOfHandles :0x1b
   +0x058HighWaterNumberOfObjects : 1
   +0x05cHighWaterNumberOfHandles : 0x1b
   +0x060TypeInfo        : _OBJECT_TYPE_INITIALIZER
   +0x0acKey             : 0x6579654b
   +0x0b0ObjectLocks     : [4] _ERESOURCE

是个KeyedEvent对象。对象头再加上它本身的大小0x18之后就到了对象体OJBECT_BODY了,这个因不同对象而异。直接来看一下:

lkd> !object e100b4e8+0x18
Object: e100b500 Type: (8a928e70) KeyedEvent
 
  ObjectHeader: e100b4e8 (old version)
 
   HandleCount:27 PointerCount: 28
 
   DirectoryObject: e10000a8 Name: CritSecOutOfMemoryEvent

跟前面看到的对比一下,完全是一致的。这样就了解了如何从进程的句柄表中得到实际的对象。

下面以该进程中的一个句柄0x1078为例说明句柄如何索引到对象。
0x1078/0x800=2
0x1078%0x800=0x78
前面已经知道,该进程的句柄表为二级表,而上面的计算结果表明,该句柄大小已经超出了两个句柄表的范围,在第三个句柄表中,且偏移为0x78*2.(因为句柄按4递增,而HANDLE_TABLE_ENTRY结构大小为8,所以正确公式应该为0x78除以4得到索引值,再乘以8得到以字节计算的偏移值,结果即0x78*2)
而第三张表基址为e2c1e000 。来看一下:

lkd> dd e2c1e000+0x78*2
e2c1e0f0 896b7019 001f03ff 8984e659 00100003
e2c1e100 8984e611 00100003 e2d183d9 00020019
e2c1e110 898003f1 001f0003 896c0701 0012019f
e2c1e120 8984e5c9 00100003 89834691 00100003

可以得到Object为896b7019,掩去低两位,则对象头指针为896b7018,对象体指针为896b7018+0x18

lkd> !object 896b7018+0x18
Object: 896b7030 Type: (8a92c040) Thread
 
  ObjectHeader: 896b7018 (old version)
 
   HandleCount:3 PointerCount: 5

结果显示这是一个线程对象,与ProcessExplorer中显示的结果完全一致。以上过程很容易转化为程序实现并且不依赖任何系统函数,具体实现过程可参考WRK中的ExpLookupHandleTableEntry函数,其原型为:

PHANDLE_TABLE_ENTRY
ExpLookupHandleTableEntr
y (
 
   INPHANDLE_TABLE HandleTable,
 
   IN EXHANDLEtHandle
 
   );
下一篇准备再写写PspCidTable~

ps:大部分进程中句柄数并不多,因此通常为一级表。那么如何找到一个有二级表的进程进行观察呢?首先,你可以自己人为地“造”一个出来。或者,找服务数最多的那个svchost.exe进程(用ProcessExplorer可以很容易找出来),它是20多个服务的宿主,句柄不多就怪了~~

 

-------------------------------------这是分割线---------------------------------------

PspCidTable概述

 

PspCidTable也是一个句柄表,其格式与普通的句柄表是完全一样的.但它与每个进程私有的句柄表有以下不同:
1.PspCidTable中存放的对象是系统中所有的进线程对象,其索引就是PID和TID。
2.PspCidTable中存放的直接是对象体(EPROCESS和ETHREAD),而每个进程私有的句柄表则存放的是对象头(OBJECT_HEADER)。
3.PspCidTable是一个独立的句柄表,而每个进程私有的句柄表以一个双链连接起来。

注意访问对象时要掩掉低三位,每个进程私有的句柄表是双链连接起来的,实际上ZwQuerySystemInformation
枚举系统句柄时就是走的这条双链,隐藏进程的话,这条链也是要断掉的~~

PspCidTable相关的调用:
1.系统初始化时调用PspInitPhase0()初始化进程管理子系统,此时创建PspCidTable。
  1. PspCidTable = ExCreateHandleTable(NULL);  //创建!
  2.     if(PspCidTable == NULL) {
  3.         returnFALSE;
  4.     }
  5.     //
  6.     //Set PID and TID reuse to strict FIFO. This isn't absolutely neededbut
  7.     //it makes tracking audits easier.
  8.     //
  9.     ExSetHandleTableStrictFIFO (PspCidTable);
  10.     ExRemoveHandleTable(PspCidTable);  //使得PspCidTable独立于其它句柄表

2.进程创建时,PspCreateProcess()在PspCidTable中以进程对象创建句柄,是为PID。
//

    //Create the process ID

  1.     //
  2.     CidEntry.Object = Process;
  3.     CidEntry.GrantedAccess = 0;
  4.     Process->UniqueProcessId = ExCreateHandle(PspCidTable,&CidEntry);  //进程的PID是这么来的
  5.     if(Process->UniqueProcessId == NULL) {
  6.         Status = STATUS_INSUFFICIENT_RESOURCES;
  7.         gotoexit_and_deref;
  8.     }

3.线程创建时,PspCreateThread()在PspCidTable中以线程对象创建句柄,是为TID。

  1.     Thread->ThreadsProcess = Process;
  2.     Thread->Cid.UniqueProcess =Process->UniqueProcessId;
  3.     CidEntry.Object = Thread;
  4.     CidEntry.GrantedAccess = 0;
  5.     Thread->Cid.UniqueThread = ExCreateHandle(PspCidTable, &CidEntry); //线程的TID
  6.     if(Thread->Cid.UniqueThread == NULL) {
  7.         ObDereferenceObject (Thread);
  8.         return(STATUS_INSUFFICIENT_RESOURCES);
  9.     }


这儿可以清楚地知道:PID和TID分别是EPROCESS和ETHREAD对象在PspCidTable这个句柄表中的索引。

4.进程和线程的查询,主要是以下三个函数,按照给定的PID或TID从PspCidTable从查找相应的进线程对象。
PsLookupProcessThreadByC
id()
PsLookupProcessByProcess
Id()
PsLookupThreadByThreadId
()
其中有如下调用:
CidEntry = ExMapHandleToPointer(PspCidTable, ProcessId);
CidEntry = ExMapHandleToPointer(PspCidTable, ThreadId);

ExMapHandleToPointer内部仍然是调用ExpLookupHandleTableEntr y()根据指定的句柄查找相应的HANDLE_TABEL_ENTRY,从而获取Object。

5.线程退出时,PspThreadDelete()在PspCidTable中销毁句柄。
  1. if (Thread->Cid.UniqueThread != NULL) {
  2.         if(!ExDestroyHandle (PspCidTable,Thread->Cid.UniqueThread, NULL)) {
  3.             KeBugCheck(CID_HANDLE_DELETION);
  4.         }
  5.     }


6.进程退出时,PspProcessDelete()在PspCidTable中销毁句柄。

  1. if (Process->UniqueProcessId) {
  2.         if(!(ExDestroyHandle (PspCidTable,Process->UniqueProcessId, NULL))) {
  3.             KeBugCheck (CID_HANDLE_DELETION);
  4.         }
  5.     }


这里要注意,如果进线程退出时,销毁句柄却发现句柄不存在造成ExDestroyHandle返回失败,可是要蓝屏滴~
所以抹了PspCidTable来隐藏的进程,在退出时必须把进线程对象再放回去,欲知后事如何,请看下回分解~~

 

-------------------------------------这是分割线---------------------------------------

PspCidTable攻与防

 

PspCidTable的攻与防,其实就是进程隐藏与检测所涉及到的一部分工作~~
不管基于PspCidTable的进线程检测,还是抹PspCidTable进行进程对象的隐藏,都涉及到对PspCidTable的遍历。
所以如何安全正确地遍历PspCidTable才是该技术的关键。

一、获取PspCidTable的地址
常用的方法是从前面提到的三个查询PspCidTable的函数中特征搜索。
PsLookupProcessThreadByC
id()
PsLookupProcessByProcess
Id()
PsLookupThreadByThreadId
()
比如PsLookupProcessByProcess
Id()中:
  1. lkd> u
  2. nt!PsLookupProcessByProcessId+0x12:
  3. 8057ce2fff8ed4000000    dec     dword ptr [esi+0D4h]
  4. 8057ce35ff35e0955680    push    dword ptr [nt!PspCidTable (805695e0)] //就是这儿了
  5. 8057ce3be89b1dffff      call    nt!ExMapHandleToPointer (8056ebdb)
  6. 8057ce408bd8            mov     ebx,eax

这个方法没什么好说的,匹配就是了~

另一种方法是从KPCR中取,我比较喜欢这种方法。
lkd> dt _KPCR ffdff000
nt!_KPCR
 
  +0x000NtTib           : _NT_TIB
 
  ...
 
  +0x034KdVersionBlock   :0x80555038
lkd> dd 0x80555038
80555038 0a28000f 00020006 030c014c 0000002d
80555048 804e0000 ffffffff 80563420 ffffffff//这里分别是KernelBase和PsLoadedModuleList
...
805550a8 80563420 00000000 805694d8 00000000//这里是PsLoadedModuleList和PsActiveProcessHead
805550b8 805695e0 00000000 8056ba08 00000000//这里是PspCidTable和ExpSystemResourcesList
代码如下:

  1. PHANDLE_TABLE PspCidTable;
  2. _asm
  3. {
  4.     moveax,fs:[0x34]
  5.     moveax,[eax+0x80]
  6.     moveax,[eax]
  7.     movPspCidTable,eax
  8. }
  9. DbgPrint("PspCidTable=0xX\n",PspCidTable);

二、如何遍历PspCidTable
第一种方法是使用导出的ExEnumHandleTable,优点是该函数导出了,用起来安全快捷。
可能的缺点也是因为被导出了,所以比较容易被XX再XXX,不是足够可靠~
函数原型如下:
NTKERNELAPI
BOOLEAN
ExEnumHandleTable (
PHANDLE_TABLE HandleTable,
EX_ENUMERATE_HANDLE_ROUTINE EnumHandleProcedure,
PVOID EnumParameter,
PHANDLE Handle
);
typedef BOOLEAN (*EX_ENUMERATE_HANDLE_ROUTINE)(
IN PHANDLE_TABLE_ENTRY HandleTableEntry,
IN HANDLE Handle,
IN PVOID EnumParameter
 
   );
我们只要自己实现EnumHandleProcedure就可以了,传递给我们的参数有HANDLE_TABEL_ENTRY的指针和对应的句柄。
HandleTableEntry->Object就拿到对象了,接下来嘛,该干啥干啥~

  1. BOOLEAN MyEnumerateHandleRoutine(
  2.     IN PHANDLE_TABLE_ENTRY HandleTableEntry,
  3.     IN HANDLE Handle,
  4.     IN PVOID EnumParameter
  5.     )
  6. {
  7.     BOOLEANResult=FALSE;
  8.     ULONGProcessObject;
  9.     ULONGObjectType;
  10.     ProcessObject=(HandleTableEntry->Value)&~7;//掩去低三位
  11.     
  12.     ObjectType=*(ULONG*)(ProcessObject-0x10);//取对象类型
  13.     if(ObjectType==(ULONG)PsProcessType)//判断是否为Process
  14.     {
  15.         (*(ULONG*)EnumParameter)++;
  16.         //注意PID其实就是Handle,而不是从EPROCESS中取,可以对付伪pid
  17.         DbgPrint("PID=M\t EPROCESS=0xX%s\n",Handle,ProcessObject,PsGetProcessImageFileName((PEPROCESS)ProcessObject));
  18.     }
  19.     returnResult;//返回FALSE继续
  20. }

然后这样调用:
ExEnumHandleTable(PspCidTable,MyEnumerateHandleRoutine
,NULL,&hLastHandle);
好了,打开DebugView看结果吧,还不错~~

第二种方法就是自己遍历PspCidTable了,结构嘛前面已经清楚了,和普通句柄表结构一样,不难下手。
自己实现一个山寨的MyEnumHandleTable了,接口和ExEnumHandleTable一样~~

  1. #define   MAX_ENTRY_COUNT(0x1000/8)  //一级表中的HANDLE_TABLE_ENTRY个数
  2. #define  MAX_ADDR_COUNT   (0x1000/4)//二级表和三级表中的地址个数
  3. BOOLEAN
  4. MyEnumHandleTable (
  5. PHANDLE_TABLE HandleTable,
  6. MY_ENUMERATE_HANDLE_ROUTINE EnumHandleProcedure,
  7. PVOID EnumParameter,
  8. PHANDLE Handle
  9.     )
  10. {
  11. ULONG i,j,k;
  12. ULONG_PTR CapturedTable;
  13.     ULONGTableLevel;
  14. PHANDLE_TABLE_ENTRYTableLevel1,*TableLevel2,**TableLevel3;
  15. BOOLEAN CallBackRetned=FALSE;
  16. BOOLEAN ResultValue=FALSE;
  17. ULONG MaxHandle;
  18. //判断几个参数是否有效
  19. if (!HandleTable
  20.   && !EnumHandleProcedure
  21.   && !MmIsAddressValid(Handle))
  22. {
  23.   return ResultValue;
  24. }
  25. //取表基址和表的级数
  26. CapturedTable=(HandleTable->TableCode)&~3;
  27. TableLevel=(HandleTable->TableCode)&3;
  28. MaxHandle=HandleTable->NextHandleNeedingPool;
  29. DbgPrint("句柄上限值为0x%X\n",MaxHandle);
  30. //判断表的等级
  31. switch(TableLevel)
  32. {
  33. case 0:
  34.    {
  35.    //一级表
  36.    TableLevel1=(PHANDLE_TABLE_ENTRY)CapturedTable;
  37.    DbgPrint("解析一级表0xx...\n",TableLevel1);
  38.    for(i=0;i<MAX_ENTRY_COUNT;i++)
  39.     {
  40.     *Handle=(HANDLE)(i*4);
  41.     if(TableLevel1[i].Object &&MmIsAddressValid(TableLevel1[i].Object))
  42.     {
  43.      //对象有效时,再调用回调函数
  44.      CallBackRetned=EnumHandleProcedure(&TableLevel1[i],*Handle,EnumParameter);
  45.      if(CallBackRetned)  break;
  46.     }
  47.     }//endof for i
  48.    ResultValue=TRUE;
  49.    
  50.    }
  51.   break;
  52. case 1:
  53.    {
  54.    //二级表
  55.    TableLevel2=(PHANDLE_TABLE_ENTRY*)CapturedTable;
  56.    DbgPrint("解析二级表0xx...\n",TableLevel2);
  57.    DbgPrint("二级表的个数:%d\n",MaxHandle/(MAX_ENTRY_COUNT*4));
  58.    for(j=0;j<MaxHandle/(MAX_ENTRY_COUNT*4);j++)
  59.     {
  60.     TableLevel1=TableLevel2[j];
  61.     if(!TableLevel1)
  62.      break;//为零则跳出
  63.     for(i=0;i<MAX_ENTRY_COUNT;i++)
  64.     {
  65.      *Handle=(HANDLE)(j*MAX_ENTRY_COUNT*4+i*4);
  66.      if(TableLevel1[i].Object &&MmIsAddressValid(TableLevel1[i].Object))
  67.      {
  68.       //对象有效时,再调用回调函数
  69.       CallBackRetned=EnumHandleProcedure(&TableLevel1[i],*Handle,EnumParameter);
  70.       if(CallBackRetned)  break;
  71.       //DbgPrint("Handle=%d\tObject=0xX\n",Handle,(TableLevel1[i].Value)&~3);
  72.      }
  73.     }//end of for i
  74.     }//endof for j
  75.    ResultValue=TRUE;
  76.    }
  77.   break;
  78. case 2:
  79.    {
  80.    //三级表
  81.    TableLevel3=(PHANDLE_TABLE_ENTRY**)CapturedTable;
  82.    DbgPrint("解析三级表0xx...\n",TableLevel3);
  83.    DbgPrint("三级表的个数:%d\n",MaxHandle/(MAX_ENTRY_COUNT*4*MAX_ADDR_COUNT));
  84.    for(k=0;k<MaxHandle/(MAX_ENTRY_COUNT*4*MAX_ADDR_COUNT);k++)
  85.     {
  86.     TableLevel2=TableLevel3[k];
  87.     if(!TableLevel2)
  88.      break;//为零则跳出
  89.     for(j=0;j<MaxHandle/(MAX_ENTRY_COUNT*4);j++)
  90.     {
  91.      TableLevel1=TableLevel2[j];
  92.      if(!TableLevel1)
  93.       break;//为零则跳出
  94.      for(i=0;i<MAX_ENTRY_COUNT;i++)
  95.      {
  96.       *Handle=(HANDLE)(k*MAX_ENTRY_COUNT*MAX_ADDR_COUNT+j*MAX_ENTRY_COUNT+i*4);
  97.       if(TableLevel1[i].Object &&MmIsAddressValid(TableLevel1[i].Object))
  98.       {
  99.        //对象有效时,再调用回调函数
  100.        CallBackRetned=EnumHandleProcedure(&TableLevel1[i],*Handle,EnumParameter);
  101.        if(CallBackRetned)  break;
  102.        //DbgPrint("Handle=%d\tObject=0xX\n",Handle,(TableLevel1[i].Value)&~3);
  103.       }
  104.      }//end of for i
  105.     }//end of for j
  106.     }//endof for k
  107.    ResultValue=TRUE;
  108.    }
  109.      break;
  110. default:
  111.    {
  112.    DbgPrint("Shoud NOT get here!\n");
  113.    }
  114.      break;
  115. }//end of switch
  116. return ResultValue;
  117. }

在回调函数中,我们可以根据情况作出具体处理。若是检测进程,就像我写的那样,检查一下对象类型是Process的就记录之~
若是抹PspCidTable,则将相应的Object清零,并设置到FirstFree,达到这个Handle就像真的被Destroy了一样的效果~
Futo_enhanced的驱动中是这样写的(稍稍改动了一下,假设是在抹掉一个EPROCESS):
p_tableEntry[a].Object = 0;
p_tableEntry[a].GrantedAccess =PspCidTable->FirstFree;
PspCidTable->FirstFree = pid ;
这里涉及到句柄的分配算法了,这样,基于PspCidTable的进程检测就歇菜了。但是上一篇的分析提到了,进线程退出时会调用ExDestroyHandle()销毁句柄,若找不到就会蓝屏。因此,必须在被保护的目标进程及其线程退出前的某个时候把抹掉的进线程对象再放回去。结束线程其实就是给线程插APC执行PspExitThread(),而PspExitThread()会调用PspCreateThreadNotifyRou
tine,因此在这个回调中把线程对象放回去是合适的。同法,进程在退出时调用的PspExitProcess()也会执行PspCreateProcessNotifyRoutine,此时再把进程对象放回去。这里我想如不设置FirstFree为我们抹掉的句柄项,这个被我们抹掉的HANDLE_TABLE_ETNRY项会被保留吗?
另外也可以来个ObjectHook,Fuck掉PsProcessType->TypeInfo->DeleteProcedure和PsThreadType->TypeInfo->DeleteProcedure然后是隐藏掉的进程就照顾一下~~~
下面是PsProcessType的部分内容:
   +0x02cDumpProcedure   : (null)
   +0x030OpenProcedure   : (null)
   +0x034CloseProcedure   : (null)
   +0x038 DeleteProcedure :0x805d2cdc    void nt!PspProcessDelete+0
   +0x03cParseProcedure   : (null)
   +0x040 SecurityProcedure :0x805f9150    long nt!SeDefaultObjectMethod+0
   +0x044 QueryNameProcedure :(null)
   +0x048 OkayToCloseProcedure :(null)

总之,只要在PspCidTable中找到空位把目标进程的EPROCESS和ETHREAD再放回去,并且其索引与EPROCESS->UniqueProcessId和ETHREAD->Cid.UniqueThread保持一致就可以了。抽点时间写个隐藏进程的Demo练习下~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值