Windows x64 进程隐藏: 断开进程活动链,抹除全局句柄表自己的内容,修改EPROCESS的PID貌似也行……(未完待续)
通过进程活动链 偏移是从内核.exe查出来的,多少有点拙劣,不是很想用API,怕Hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 VOID Traversal_Process () { PEPROCESS currentProcess = PsGetCurrentProcess (); PLIST_ENTRY currentEntry = (PLIST_ENTRY)((ULONG_PTR)currentProcess + 0x188 ); PLIST_ENTRY Temp_PList_Entry = currentEntry->Flink; CHAR* FileName = *(CHAR**)((ULONG64)currentProcess + 0x2e0 ); KdPrint (("进程:%s\n" , FileName)); while (Temp_PList_Entry!=currentEntry) { FileName = (CHAR*)((ULONG64)Temp_PList_Entry - 0x188 + 0x2e0 ); KdPrint (("PID:0x%x 进程:%s\n" , *(ULONG64*)((ULONG64)Temp_PList_Entry - 0x8 ), FileName)); Temp_PList_Entry = Temp_PList_Entry->Flink; } }
效果如下:
优点:
简单直接 :该方法比较标准,通过遍历 ActiveProcessLinks
链表可以很方便地枚举所有进程。
效率高 :双向链表结构比较紧凑,遍历开销较小。
进程管理相关性强 :Windows 内核使用这个链表来维护进程,所有正常运行的进程都会在链表中。
缺点:
容易被Rootkit隐藏 :一些恶意软件或Rootkit可以通过从 ActiveProcessLinks
链表中移除特定进程的节点,来实现进程隐藏。这种情况下,隐藏的进程无法被这种方法检测到。
遍历PspCidTable PspCidTable 是一个内核句柄表,存放进程和线程的内核对象(EPROCESS 和 ETHREAD),并通过 PID 和 TID 进行索引(所以进程ID和线程ID不可能相同),ID 号以 4 递增。
然后我查阅资料发现 PsLookupProcessByProcessId
不是通过 EPROCESS
的双向链表(ActiveProcessLinks
)去获取进程,而是使用全局的进程/线程句柄表( PspCidTable
),在内核态有很多优点,特别是在性能、并发安全性和系统一致性方面。
所以首先去WDK看看这个函数在低版本的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 NTSTATUS PsLookupProcessByProcessId ( __in HANDLE ProcessId, __deref_out PEPROCESS *Process ) { PHANDLE_TABLE_ENTRY CidEntry; PEPROCESS lProcess; PETHREAD CurrentThread; NTSTATUS Status; PAGED_CODE (); Status = STATUS_INVALID_PARAMETER; CurrentThread = PsGetCurrentThread (); KeEnterCriticalRegionThread (&CurrentThread->Tcb); CidEntry = ExMapHandleToPointer (PspCidTable, ProcessId); if (CidEntry != NULL ) { lProcess = (PEPROCESS)CidEntry->Object; if (lProcess->Pcb.Header.Type == ProcessObject && lProcess->GrantedAccess != 0 ) { if (ObReferenceObjectSafe (lProcess)) { *Process = lProcess; Status = STATUS_SUCCESS; } } ExUnlockHandleTableEntry (PspCidTable, CidEntry); } KeLeaveCriticalRegionThread (&CurrentThread->Tcb); return Status; }
发现是通过 ExMapHandleToPointer 这个函数实现的
然后又在 ExMapHandleToPointer 函数发现是在 ExpLookupHandleTableEntry 中获取EPROCESS
于是我们打开win7的 ntoskrnl.exe,分析下win7 x64内核是如何实现PsLookupProcessByProcessId拿到进程信息的
我们发现几个有用的点:
PID必然是4的倍数
看PspCidTable的最后两个字节,一共有三种处理方式,如图展示
于是我们便可以编写代码,去把这个进程遍历出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 VOID Traversal_Process_ByCid () { ULONG64 Handle_Table = *(ULONG64*)0xfffff8a000004890 ; ULONG64 Temp = Handle_Table; for (ULONG64 i = 0 ; i < 65535 ; i =i+ 4 ) { Handle_Table = Temp; ULONG64 PEprocess = 0 ; if ((Handle_Table & 3 ) == 0 ) { PEprocess = *(ULONG64*)(Handle_Table + i * 4 ); } else if ((Handle_Table & 3 ) == 1 ) { Handle_Table = Handle_Table - 1 ; PEprocess = *(ULONG64*)((((i & 0xFFFFFFFFFFFFFFFC ) - (i & 0x3FC )) >> 7 ) + Handle_Table) + 4 * (i & 0x3FC ); } else if ((Handle_Table & 3 ) == 2 ) { Handle_Table = Handle_Table - 2 ; ULONG64 v6 = i & 0xFFFFFFFFFFFFFFFC ; PEprocess = *(ULONG64*)(*(ULONG64*)(((((v6 - (v6 & 0x3FF )) >> 7 ) - (((v6 - (v6 & 0x3FF )) >> 7 ) & 0xFFF )) >> 9 ) + Handle_Table) + (((v6 - (v6 & 0x3FF )) >> 7 ) & 0xFFF )) + 4 * (v6 & 0x3FF ); } ULONG64 EPROCESS; if (MmIsAddressValid ((PVOID)PEprocess)&& PEprocess!=NULL ) { EPROCESS = *(ULONG64*)PEprocess & 0xFFFFFFFFFFFFFFFE ; if (MmIsAddressValid ((PVOID)EPROCESS) && EPROCESS != NULL ) { if (*(unsigned char *)EPROCESS == 3 ) { CHAR* FileName = (CHAR*)(EPROCESS + 0x2e0 ); KdPrint (("进程:%s\n" , FileName)); } } } } }
效果:
暴力内存搜索 我们可以观察发现,EPROCESS所处的内存空间貌似都是集中的,那么我们就可以去扫这一块内存
那么这就需要我们找特征:
例如EPROCESS的第一个字节,必然是3,代表应用程序
还有其他的,可以去用MmIsAddressValid检测是否是合法地址辅助判断
总之这种方法,进程是几乎不可能隐藏的,可以说是无解,但是效率较低