标 题:
【原创】PspCidTable杂谈
作 者: sudami 时 间: 2008-02-15,21:31 链 接: http://bbs.pediy.com/showthread.php?t=59680 今天再次看了 gz1X牛的文章,回忆了许多知识. 哈哈. 玩了很长时间游戏,写点儿文字算是对自责的一种安慰 ![]() 要说R0枚举隐藏进程, 只是对没有抹掉 PspCidTable而言的. gz1X牛提示了2种方法: ![]() " 第8个男人" 的code里面就做的很好,找到 PspCidTable后自己搜索所有的进程对象就行了: //---------------------------------------------------------------------- VOID IsValidProcess () /*++ Author : 第8个男人 Leaner : sudami [xiao_rui_119@163.com] Time : 08/02/15 参数 : NULL 返回 : NULL 功能 : 给出PspCidTable的地址,结合_EXHANDLE、HANDLE_TABLE、HANDLE_TABLE_ENTRY 搜索到每个进程对象,纪录之. 前提条件: 假设暂且PspCidTable没有被抹掉 --*/ { ULONG PspCidTable; ULONG TableCode; ULONG table1,table2; ULONG object,objectheader; ULONG NextFreeTableEntry; ULONG processtype,type; ULONG flags; ULONG i; PspCidTable = GetCidAddr(); // 搜索PsLookupProcessByProcessId函中的特征串即可 processtype = GetProcessType(); if (PspCidTable == 0) { return ; } else { //TableCode的低2位决定句柄表的级数 TableCode = *( PULONG) ( *( PULONG) PspCidTable ); if ( (TableCode & 3) == 0 ) { // 0级 table1 = TableCode; table2 = 0; } else if ( (TableCode & 3 ) == 1 ) { // 1级 TableCode = TableCode & 0xfffffffc; table1 = *( PULONG)TableCode; table2 = *( PULONG)( TableCode + 4 ); } // 对cid从0x0到0x4e1c进行遍历 for (i = 0; i < 0x4e1c; i++) { if (i <= 0x800) { // 第一张表中 if (MmIsAddressValid( ( PULONG)(table1 + i*2) )) { // HANDLE_TABLE_ENTRY地址 + PID * 2 ---> 对象的地址 object = *( PULONG)( table1 + i*2 ); if (MmIsAddressValid( ( PULONG)(table1 + i*2 + NEXTFREETABLEENTRY) )) { // 验证HANDLE_TABLE_ENTRY的合法性,这在ExEnumHandleTable函数的代码中也有 // 正常的_HANDLE_TABLE_ENTRY中NextFreeTableEntry应该为0 NextFreeTableEntry = *( PULONG)(table1 + i*2 + NEXTFREETABLEENTRY); if (NextFreeTableEntry == 0) { // 去掉低3位掩码标志 object = ((object | 0x80000000) & 0xfffffff8); // 转换为对象(体)指针 objectheader = ( ULONG) / OBJECT_TO_OBJECT_HEADER(object); // 获取对象(头)指针 if (MmIsAddressValid( ( PULONG)(objectheader + TYPE) )) { type = *( PULONG)(objectheader + TYPE); if (type == processtype) { // 是否为进程对象 flags = *( PULONG)( ( ULONG)object + / GetPlantformDependentInfo(OFFSET_EPROCESS_FLAGS) ); if ((flags&0xc) != 0xc) RecordInfo( object ); //flags显示进程没有退出 } } } } } } else { // 第2张表 if (table2 != 0) { // 步骤同上,只是object的获取为: HANDLE_TABLE_ENTRY地址 + (PID- 0x800)*2 if (MmIsAddressValid( ( PULONG)(table2 + (i - 0x800)*2) )) { object = *( PULONG)(table2 + (i - 0x800)*2); if (MmIsAddressValid(( PULONG)((table2+(i-0x800)*2)+NEXTFREETABLEENTRY))) { NextFreeTableEntry=*( PULONG)((table2+(i-0x800)*2)+NEXTFREETABLEENTRY); if (NextFreeTableEntry==0x0) { object = ((object | 0x80000000) & 0xfffffff8); objectheader = ( ULONG) OBJECT_TO_OBJECT_HEADER(object); if (MmIsAddressValid( ( PULONG)(objectheader + TYPE) )) { type = *( PULONG)(objectheader + TYPE); if(type == processtype) { flags = *( PULONG) (( ULONG)object + / GetPlantformDependentInfo(OFFSET_EPROCESS_FLAGS)); if ((flags&0xc) != 0xc) RecordInfo(object); } } } } } } } } } } 当然,用 ExEnumHandleTable也是不错的. Windbg把它的实现看了一遍, 和WRK上的无任何出入[ XP SP2], 实现起来也很方便 ![]() R3下就更简单了.一阵 OpenProcess就把部分隐藏进程揪出来了: #include <iostream.h> #include <stdio.h> #include <windows.h> void main() { int a = 0; for ( int i=0;i<=65535;i+=4) { if( OpenProcess(PROCESS_QUERY_INFORMATION, FALSE,i)!= 0) { a++; cout << "ProcessID: " << i << endl; CloseHandle(&i); } } cout << "Total Counts = " << a << endl; } 不过俺看了下,好像有些进程没有显示出来,比如SVCHOST.exe、SMSS.EXE... 嘿嘿,结合下面这些就更好搞了 [V大语录]: ![]() 目前通过 job杀进程MS很流行,内存清零也很厉害(就怕attach不上),插APC,杀线程之流的....杀杀IS等不在话下. 不过一般搜索到未导出的 PspTerminateProcessByPointer,再深入点儿把这个函数,甚至 PspExitThread 都自己实现一遍(用Windbg看了遍PspTerminateThreadByPointer,发现和WRK上的一样.哈哈,没有任何出入 [XP SP2]), Kill掉大部分进程是木有问题的,不过像KV2008这样猥亵的家伙在投递APC上下了手脚,就要先恢复了inline hook再杀了. 炉子牛的R0那个干掉 KV2008的好像就是这样搞的.哈哈, 丢个驱动在这里,供有兴趣的IDA look之(应该无壳). R3下搞KV 炉子出了个录象,很神秘的样子,不过好像是成功了.哈哈 对了.炉子牛(又是炉子...)放了个simple task, R3下的简单管理器 [ VB],--- 检测隐藏进程的思路很科普: 一个 EnumProcess or ToolHelp32来做对照, 一个 ZwQuerySsytemInformation来走过场,一个从0 到65535的遍历 OpenProcess来达到PspCidTable的效果. 3个途径来扫盲式的检测,方法是科普点儿. 结束进程部分很扫盲(原作者: EST的 willy123牛): ZwCreateJobObject-->ZwAssignProcessToJobObject-->ZwTerminateJobObject 对付IS这样inlie hook 了 NtOpenProcess等的就用了一种很和谐的方法,FlowerCode提供滴.不过是VB写的, 俺无聊把其转化为了C描述,方便些: //-------------------------------------------------------------- // btw: 头文件的那些申明自己写咯~ // 调用前要有SE_DEBUG权限 HANDLE SDM_OpenProcess ( DWORD dwDesiredAccess, BOOL bInhert, DWORD ProcessId, BOOL bOpenIt, LPDWORD aryPids ) /*++ 原作者 : FlowerCode | 炉子 [VB] 转换者 : sudami [xiao_rui_119@163.com] [C] Time : 08/02/12 参数 : dwDesiredAccess - 希望以怎样的方式打开进程 bInhert - 是否有继承权限 ProcessId - PID bOpenIt - 是要打开进程,还是要保存所有进程ID paryPids - 保存PID的数组 返回 : 成功 - 指定的进程句柄 失败 - 0 功能 : 1. 复制来复制去,总之是要获得指定进程的句柄 2. 用普通方法得到所有进程ID --*/ { ULONG cbBuffer = 0x1000; //先设定一个较小的缓冲空间 ULONG uRetSzie; ULONG NumOfHandle = 0; PCHAR pBuffer = NULL; PVOID pOneEprocess = 0; CLIENT_ID cid; NTSTATUS st; OBJECT_ATTRIBUTES oa; PROCESS_BASIC_INFORMATION pbi; PSYSTEM_HANDLE_TABLE_ENTRY_INFO h_info; HANDLE hProcessToDup, hProcessCur, hProcessToRet; oa.Length = sizeof (OBJECT_ATTRIBUTES); oa.RootDirectory = 0; oa.ObjectName = 0; oa.Attributes = 0; oa.SecurityDescriptor = 0; oa.SecurityQualityOfService = 0; if (bInhert) { oa.Attributes |= OBJ_INHERIT; } if (bOpenIt) { // 看能否直接得到句柄 cid.UniqueProcess = ( HANDLE) (ProcessId + 1); cid.UniqueThread = 0; st = NtOpenProcess (&hProcessToRet, dwDesiredAccess, &oa, &cid); if (NT_SUCCESS ( st)) { return hProcessToRet; } :: MessageBox ( NULL, "NtOpenProcess 失败", "=。=!", MB_OK); } // 传递16号获得所有句柄,可能被hook过;不管了,走过场~ do { st = ZwQuerySystemInformation( /*SystemHandleInformation*/16, / pBuffer, cbBuffer, &uRetSzie ); if ( st == STATUS_INFO_LENGTH_MISMATCH) { free( pBuffer); cbBuffer *= 2; pBuffer = ( PCHAR) malloc (cbBuffer); } else if ( !NT_SUCCESS ( st) ) { free( pBuffer); :: MessageBox ( NULL, "ZwQuerySystemInformation失败", ".", MB_OK); return 0; } } while ( st == STATUS_INFO_LENGTH_MISMATCH); NumOfHandle = ( ULONG) pBuffer; h_info = (PSYSTEM_HANDLE_TABLE_ENTRY_INFO) ( ( ULONG)pBuffer + 4 ); /* // 获得进程对象类型 pOneEprocess = h_info[0].Object; g_pObjectTypeProcess = *(PULONG) ( (ULONG)pOneEprocess - OBJECT_HEADER_SIZE / + OBJECT_TYPE_OFFSET ); */ for ( ULONG i = 0; i < NumOfHandle; i++) { aryPids[i] = h_info[i].UniqueProcessId; if (bOpenIt) { :: MessageBox ( NULL, "333", "=。=!", MB_OK); if ( 5 == h_info[i].ObjectTypeIndex) { // 是进程的句柄,打开它 cid.UniqueProcess = ( HANDLE)h_info[i].UniqueProcessId; st = NtOpenProcess (&hProcessToDup, PROCESS_DUP_HANDLE, &oa, &cid); if (NT_SUCCESS ( st)) { // 复制该句柄后赋予全部权限,以便调用ZwQuery*时顺利进行 st = ZwDuplicateObject (hProcessToDup, ( PHANDLE)h_info[i].HandleValue, ( HANDLE)-1,/ &hProcessCur, PROCESS_ALL_ACCESS, 0, DUPLICATE_SAME_ATTRIBUTES); if (NT_SUCCESS ( st)) { // 查看复制的句柄是否为我们想要的 st = ZwQueryInformationProcess (hProcessCur, ProcessBasicInformation,/ &pbi, sizeof(pbi), 0); if (NT_SUCCESS ( st)) { if (ProcessId == pbi.UniqueProcessId ) { // 若是想要的PID,就把该句柄按照希望的访问权限复制,然后返回 st = ZwDuplicateObject (hProcessToDup, ( PHANDLE)h_info [i].HandleValue,/ ( HANDLE)-1, &hProcessToRet, dwDesiredAccess,/ OBJ_INHERIT, DUPLICATE_SAME_ATTRIBUTES); ZwClose (hProcessCur); ZwClose (hProcessToDup); return hProcessToRet; } } } ZwClose (hProcessCur); } ZwClose (hProcessToDup); } } } return 0; } 顺便问下: 要抹掉PspCidTable中指定的进程容易,抹 CSRSS.exe的也容易,什么挂SwapContext的,自己实现线程调度rootkit.com上的 PHIDE2 engine也实现了(颇为复杂,引擎写的很漂亮),还有啥改ETHREAD,EPROCESS, flag等DKOM类的...好多好多.但是一个内存暴力搜索不就都出来了.这可怎么隐藏啊 ![]() ------------------------------------------------------------------------------------------------------- 参考资料: (1) 基于pspCidTable的进程检测技术 (2) 句柄啊,3层表啊,ExpLookupHandleTableEntry啊 (3) PsLookupProcessByProcessId执行流程 (4) PspTerminateThreadByPointer (5) WRK,ReactOS |