轉自:http://hi.baidu.com/_achillis/blog/item/2bb59619d8019cbc4bedbc56.html
不管基於PspCidTable的進線程檢測,還是抹PspCidTable進行進程對象的隱藏,都涉及到對PspCidTable的遍歷 .
所以如何安全正確地遍歷PspCidTable纔是該技術的關鍵
一、獲取PspCidTable的地址
常用的方法是從前面提到的三個查詢PspCidTable的函數中特徵搜索.
PsLookupProcessThreadByCid()
PsLookupProcessByProcessId()
PsLookupThreadByThreadId()
比如PsLookupProcessByProcessId()中:
- lkd> u
- nt!PsLookupProcessByProcessId+0x12:
- 8057ce2f ff8ed4000000 dec dword ptr [esi+0D4h]
- 8057ce35 ff35e0955680 push dword ptr [nt!PspCidTable (805695e0)] //就是這兒了
- 8057ce3b e89b1dffff call nt!ExMapHandleToPointer (8056ebdb)
- 8057ce40 8bd8 mov ebx,eax
這個方法沒什麼好說的,匹配就是了~
另一種方法是從KPCR中取,我比較喜歡這種方法:
lkd> dt _KPCR ffdff000
nt!_KPCR
+0x000 NtTib : _NT_TIB
...
+0x034 KdVersionBlock : 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
代碼如下:
- PHANDLE_TABLE PspCidTable;
- _asm
- {
- mov eax,fs:[0x34]
- mov eax,[eax+0x80]
- mov eax,[eax]
- mov PspCidTable,eax
- }
- DbgPrint("PspCidTable=0x%08X/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就拿到對象了,接下來嘛,該幹啥幹啥~
- BOOLEAN MyEnumerateHandleRoutine(
- IN PHANDLE_TABLE_ENTRY HandleTableEntry,
- IN HANDLE Handle,
- IN PVOID EnumParameter
- )
- {
- BOOLEAN Result=FALSE;
- ULONG ProcessObject;
- ULONG ObjectType;
- ProcessObject=(HandleTableEntry->Value)&~7; //掩去低三位
- ObjectType=*(ULONG *)(ProcessObject-0x10);//取對象類型
- if (ObjectType==(ULONG )PsProcessType)//判斷是否爲Process
- {
- (*(ULONG *)EnumParameter)++;
- //注意PID其實就是Handle,而不是從EPROCESS中取,可以對付僞pid
- DbgPrint("PID=%4d/t EPROCESS=0x%08X %s/n" ,Handle,ProcessObject,PsGetProcessImageFileName((PEPROCESS)ProcessObject));
- }
- return Result;//返回FALSE繼續
- }
然後這樣調用:
ExEnumHandleTable(PspCidTable,MyEnumerateHandleRoutine,NULL,&hLastHandle);
好了,打開DebugView看結果吧,還不錯~~
第二種方法就是自己遍歷PspCidTable了,結構嘛前面已經清楚了,和普通句柄表結構一樣,不難下手.
自己實現一個山寨的MyEnumHandleTable了,接口和ExEnumHandleTable一樣~~
- #define MAX_ENTRY_COUNT (0x1000/8) //一級表中的HANDLE_TABLE_ENTRY個數
- #define MAX_ADDR_COUNT (0x1000/4) //二級表和三級表中的地址個數
- BOOLEAN
- MyEnumHandleTable (
- PHANDLE_TABLE HandleTable,
- MY_ENUMERATE_HANDLE_ROUTINE EnumHandleProcedure,
- PVOID EnumParameter,
- PHANDLE Handle
- )
- {
- ULONG i,j,k;
- ULONG_PTR CapturedTable;
- ULONG TableLevel;
- PHANDLE_TABLE_ENTRY TableLevel1,*TableLevel2,**TableLevel3;
- BOOLEAN CallBackRetned=FALSE;
- BOOLEAN ResultValue=FALSE;
- ULONG MaxHandle;
- //判斷幾個參數是否有效
- if (!HandleTable
- && !EnumHandleProcedure
- && !MmIsAddressValid(Handle))
- {
- return ResultValue;
- }
- //取表基址和表的級數
- CapturedTable=(HandleTable->TableCode)&~3;
- TableLevel=(HandleTable->TableCode)&3;
- MaxHandle=HandleTable->NextHandleNeedingPool;
- DbgPrint("句柄上限值爲0x%X/n" ,MaxHandle);
- //判斷表的等級
- switch(TableLevel)
- {
- case 0:
- {
- //一級表
- TableLevel1=(PHANDLE_TABLE_ENTRY)CapturedTable;
- DbgPrint("解析一級表0x%08x.../n" ,TableLevel1);
- for (i=0;i<MAX_ENTRY_COUNT;i++)
- {
- *Handle=(HANDLE )(i*4);
- if (TableLevel1[i].Object && MmIsAddressValid(TableLevel1[i].Object))
- {
- //對象有效時,再調用回調函數
- CallBackRetned=EnumHandleProcedure(&TableLevel1[i],*Handle,EnumParameter);
- if (CallBackRetned) break ;
- }
- }//end of for i
- ResultValue=TRUE;
- }
- break ;
- case 1:
- {
- //二級表
- TableLevel2=(PHANDLE_TABLE_ENTRY*)CapturedTable;
- DbgPrint("解析二級表0x%08x.../n" ,TableLevel2);
- DbgPrint("二級表的個數:%d/n" ,MaxHandle/(MAX_ENTRY_COUNT*4));
- for (j=0;j<MaxHandle/(MAX_ENTRY_COUNT*4);j++)
- {
- TableLevel1=TableLevel2[j];
- if (!TableLevel1)
- break ; //爲零則跳出
- for (i=0;i<MAX_ENTRY_COUNT;i++)
- {
- *Handle=(HANDLE )(j*MAX_ENTRY_COUNT*4+i*4);
- if (TableLevel1[i].Object && MmIsAddressValid(TableLevel1[i].Object))
- {
- //對象有效時,再調用回調函數
- CallBackRetned=EnumHandleProcedure(&TableLevel1[i],*Handle,EnumParameter);
- if (CallBackRetned) break ;
- //DbgPrint("Handle=%d/tObject=0x%08X/n",Handle,(TableLevel1[i].Value)&~3);
- }
- }//end of for i
- }//end of for j
- ResultValue=TRUE;
- }
- break ;
- case 2:
- {
- //三級表
- TableLevel3=(PHANDLE_TABLE_ENTRY**)CapturedTable;
- DbgPrint("解析三級表0x%08x.../n" ,TableLevel3);
- DbgPrint("三級表的個數:%d/n" ,MaxHandle/(MAX_ENTRY_COUNT*4*MAX_ADDR_COUNT));
- for (k=0;k<MaxHandle/(MAX_ENTRY_COUNT*4*MAX_ADDR_COUNT);k++)
- {
- TableLevel2=TableLevel3[k];
- if (!TableLevel2)
- break ; //爲零則跳出
- for (j=0;j<MaxHandle/(MAX_ENTRY_COUNT*4);j++)
- {
- TableLevel1=TableLevel2[j];
- if (!TableLevel1)
- break ; //爲零則跳出
- for (i=0;i<MAX_ENTRY_COUNT;i++)
- {
- *Handle=(HANDLE )(k*MAX_ENTRY_COUNT*MAX_ADDR_COUNT+j*MAX_ENTRY_COUNT+i*4);
- if (TableLevel1[i].Object && MmIsAddressValid(TableLevel1[i].Object))
- {
- //對象有效時,再調用回調函數
- CallBackRetned=EnumHandleProcedure(&TableLevel1[i],*Handle,EnumParameter);
- if (CallBackRetned) break ;
- //DbgPrint("Handle=%d/tObject=0x%08X/n",Handle,(TableLevel1[i].Value)&~3);
- }
- }//end of for i
- }//end of for j
- }//end of for k
- ResultValue=TRUE;
- }
- break ;
- default :
- {
- DbgPrint("Shoud NOT get here!/n" );
- }
- break ;
- }//end of switch
- return ResultValue;
- }
在回調函數中,我們可以根據情況作出具體處理.若是檢測進程,就像我寫的那樣,檢查一下對象類型是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()會調用
PspCreateThreadNotifyRoutine,因此在這個回調中把線程對象放回去是合適的.同法,進程在退出時調用的
PspExitProcess()也會執行PspCreateProcessNotifyRoutine,此時再把進程對象放回去.這裏我想如不設置
FirstFree爲我們抹掉的句柄項,這個被我們抹掉的HANDLE_TABLE_ETNRY項會被保留嗎?
另外也可以來個ObjectHook,Fuck掉PsProcessType->TypeInfo->DeleteProcedure和
PsThreadType->TypeInfo->DeleteProcedure然後是隱藏掉的進程就照顧一下~~~
下面是PsProcessType的部分內容:
+0x02c DumpProcedure : (null)
+0x030 OpenProcedure : (null)
+0x034 CloseProcedure : (null)
+0x038 DeleteProcedure : 0x805d2cdc void nt!PspProcessDelete+0
+0x03c ParseProcedure : (null)
+0x040 SecurityProcedure : 0x805f9150 long nt!SeDefaultObjectMethod+0
+0x044 QueryNameProcedure : (null)
+0x048 OkayToCloseProcedure : (null)
總之,只要在PspCidTable中找到空位把目標進程的EPROCESS和ETHREAD再放回去,並且其索引與
EPROCESS->UniqueProcessId和ETHREAD->Cid.UniqueThread保持一致就可以了.抽點時間寫個
隱藏進程的Demo練習下~~