检测隐藏进程(4)
这个文件了。这个方法可以用在对一些正在被读/写的文件的访问上(比如,正在被其他进程锁定的文件)。应该回到我们的问题上来了 - 通
过对PspCidTable的分析得到进程句柄列表。
要分析它我们得了解句柄表的格式,这样才能遍历这个列表。在这个地方Windows 2000和Windows XP有着巨大的不同。由于句柄表格式不尽相
同,所以我们应该把操作系统分类进行分析。
因为Windows 2000的句柄表相对简单一些,所以我们先分析它。先来看看ExMapHandleToPointer函数:
Code:
PAGE:00493285 ExMapHandleToPointer proc near
PAGE:00493285
PAGE:00493285
PAGE:00493285 HandleTable = dword ptr 8
PAGE:00493285 Handle = dword ptr 0Ch
PAGE:00493285
PAGE:00493285 push esi
PAGE:00493286 push [esp+Handle]
PAGE:0049328A push [esp+4+HandleTable]
PAGE:0049328E call ExpLookupHandleTableEntry
PAGE:00493293 mov esi, eax
PAGE:00493295 test esi, esi
PAGE:00493297 jz short loc_4932A9
PAGE:00493299 push esi
PAGE:0049329A push [esp+4+HandleTable]
PAGE:0049329E call ExLockHandleTableEntry
PAGE:004932A3 neg al
PAGE:004932A5 sbb eax, eax
PAGE:004932A7 and eax, esi
PAGE:004932A9 loc_4932A9:
PAGE:004932A9 pop esi
PAGE:004932AA retn 8
PAGE:004932AA ExMapHandleToPointer endp
这里我们调用搜索HANDLE_TABLE的函数ExMapHandleToPointer以及设置Lock Bit的ExLockHandleTableEntry函数。要了解句柄表的内部结构我
们必须反汇编这些函数。先从ExpLookupHandleTableEntry函数开始:
Code:
PAGE:00493545 ExpLookupHandleTableEntry proc near
PAGE:00493545
PAGE:00493545
PAGE:00493545 HandleTable = dword ptr 0Ch
PAGE:00493545 Handle = dword ptr 10h
PAGE:00493545
PAGE:00493545 push esi
PAGE:00493546 push edi
PAGE:00493547 mov edi, [esp+Handle]
PAGE:0049354B mov eax, 0FFh
PAGE:00493550 mov ecx, edi
PAGE:00493552 mov edx, edi
PAGE:00493554 mov esi, edi
PAGE:00493556 shr ecx, 12h
PAGE:00493559 shr edx, 0Ah
PAGE:0049355C shr esi, 2
PAGE:0049355F and ecx, eax
PAGE:00493561 and edx, eax
PAGE:00493563 and esi, eax
PAGE:00493565 test edi, 0FC000000h
PAGE:0049356B jnz short loc_49358A
PAGE:0049356D mov eax, [esp+HandleTable]
PAGE:00493571 mov eax, [eax+8]
PAGE:00493574 mov ecx, [eax+ecx*4]
PAGE:00493577 test ecx, ecx
PAGE:00493579 jz short loc_49358A
PAGE:0049357B mov ecx, [ecx+edx*4]
PAGE:0049357E test ecx, ecx
PAGE:00493580 jz short loc_49358A
PAGE:00493582 lea eax, [ecx+esi*8]
PAGE:00493585 loc_493585:
PAGE:00493585 pop edi
PAGE:00493586 pop esi
PAGE:00493587 retn 8
PAGE:0049358A loc_49358A:
PAGE:0049358A xor eax, eax
PAGE:0049358C jmp short loc_493585
PAGE:0049358C ExpLookupHandleTableEntry endp
除此之外,我们来看看从ntoskrnl.pdb中得到的HANDLE_TABLE结构:
Code:
struct _HANDLE_TABLE {
// static data ------------------------------------
// non-static data --------------------------------
/*<thisrel this+0x0>*/ /*|0x4|*/ unsigned long Flags;
/*<thisrel this+0x4>*/ /*|0x4|*/ long HandleCount;
/*<thisrel this+0x8>*/ /*|0x4|*/ struct _HANDLE_TABLE_ENTRY*** Table;
/*<thisrel this+0xc>*/ /*|0x4|*/ struct _EPROCESS* QuotaProcess;
/*<thisrel this+0x10>*/ /*|0x4|*/ void* UniqueProcessId;
/*<thisrel this+0x14>*/ /*|0x4|*/ long FirstFreeTableEntry;
/*<thisrel this+0x18>*/ /*|0x4|*/ long NextIndexNeedingPool;
/*<thisrel this+0x1c>*/ /*|0x38|*/ struct _ERESOURCE HandleTableLock;
/*<thisrel this+0x54>*/ /*|0x8|*/ struct _LIST_ENTRY HandleTableList;
/*<thisrel this+0x5C>*/ /*|0x10|*/ struct _KEVENT HandleContentionEvent;
}; // <size 0x6c>
根据这些数据我们用C语言还原这个结构:
Code:
typedef struct _WIN2K_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;
} WIN2K_HANDLE_TABLE , *PWIN2K_HANDLE_TABLE ;
显而易见,句柄表由对象表的三个层次的索引组成。现在我们再来看看ExLookhandleTableEntry函数:
Code:
PAGE:00492E2B ExLockHandleTableEntry proc near
PAGE:00492E2B
PAGE:00492E2B
PAGE:00492E2B var_8 = dword ptr -8
PAGE:00492E2B var_4 = dword ptr -4
PAGE:00492E2B HandleTable = dword ptr 8
PAGE:00492E2B Entry = dword ptr 0Ch
PAGE:00492E2B
PAGE:00492E2B push ebp
PAGE:00492E2C mov ebp, esp
PAGE:00492E2E push ecx
PAGE:00492E2F push ecx
PAGE:00492E30 push ebx
PAGE:00492E31 push esi
PAGE:00492E32 xor ebx, ebx
PAGE:00492E34 loc_492E34:
PAGE:00492E34 mov eax, [ebp+Entry]
PAGE:00492E37 mov esi, [eax]
PAGE:00492E39 test esi, esi
PAGE:00492E3B mov [ebp+var_8], esi
PAGE:00492E3E jz short loc_492E89
PAGE:00492E40 jle short loc_492E64
PAGE:00492E42 mov eax, esi
PAGE:00492E44 or eax, 80000000h // set WIN2K_TABLE_ENTRY_LOCK_BIT
PAGE:00492E49 mov [ebp+var_4], eax
PAGE:00492E4C mov eax, [ebp+var_8]
PAGE:00492E4F mov ecx, [ebp+Entry]
PAGE:00492E52 mov edx, [ebp+var_4]
PAGE:00492E55 cmpxchg [ecx], edx
PAGE:00492E58 cmp eax, esi
PAGE:00492E5A jnz short loc_492E64
PAGE:00492E5C mov al, 1
PAGE:00492E5E loc_492E5E:
PAGE:00492E5E pop esi
PAGE:00492E5F pop ebx
PAGE:00492E60 leave
PAGE:00492E61 retn 8
PAGE:00492E64 loc_492E64:
PAGE:00492E64 mov eax, ebx
PAGE:00492E66 inc ebx
PAGE:00492E67 cmp eax, 1
PAGE:00492E6A jb loc_4BC234
PAGE:00492E70 mov eax, [ebp+HandleTable]
PAGE:00492E73 push offset unk_46D240 ; Timeout
PAGE:00492E78 push 0 ; Alertable
PAGE:00492E7A push 0 ; WaitMode
PAGE:00492E7C add eax, 5Ch
PAGE:00492E7F push 0 ; WaitReason
PAGE:00492E81 push eax ; Object
PAGE:00492E82 call KeWaitForSingleObject
PAGE:00492E87 jmp short loc_492E34
PAGE:00492E89 loc_492E89:
PAGE:00492E89 xor al, al
PAGE:00492E8B jmp short loc_492E5E
PAGE:00492E8B ExLockHandleTableEntry endp
这段代码检查了HANDLE_TABLE_ENTRY结构的Object成员的第31位,设置该位,如果该位被设置,意味着等待HANDLE_TABLE的
HandleContentionEvent。对我们来说设置TABLE_ENTRY_LOCK_BIT才是最重要的,因为它是目标地址的一部分,如果标志位没有设置,我们就会
得到无效句柄。现在我们明白了句柄表的格式,可以写代码来遍历这个表了:
Code:
void ScanWin2KHandleTable(PWIN2K_HANDLE_TABLE HandleTable)
{
int i, j, k;
PHANDLE_TABLE_ENTRY Entry;
for (i = 0; i < 0x100; i++)
{
if (HandleTable->Table)
{
for (j = 0; j < 0x100; j++)
{
if (HandleTable->Table[j])
{
for (k = 0; k < 0x100; k++)
{
Entry = &HandleTable->Table[j][k];
if (Entry->Object)
ProcessObject((PVOID)((ULONG)Entry->Object | WIN2K_TABLE_ENTRY_LOCK_BIT));
}
}
}
}
}
}
这段代码处理了所有表中的成员,并且为每一个成员调用了ProcessObject函数。ProcessObject函数检测成员类型并且恰当地处理了它们。这
个函数代码如下:
Code:
void ProcessObject(PVOID Object)
{
POBJECT_HEADER ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);
if (ObjectHeader->Type == *PsProcessType) CollectProcess(Object);
if (ObjectHeader->Type == *PsThreadType) ThreadCollect(Object);
}
我们已经了解了Windows 2000下的句柄表结构,现在开始分析Windows XP的表结构。从反汇编ExpLookupHandleTableEntry函数开始:
Code:
PAGE:0048D3C1 ExpLookupHandleTableEntry proc near
PAGE:0048D3C1
PAGE:0048D3C1
PAGE:0048D3C1 HandleTable = dword ptr 8
PAGE:0048D3C1 Handle = dword ptr 0Ch
PAGE:0048D3C1
PAGE:0048D3C1 mov edi, edi
PAGE:0048D3C3 push ebp
PAGE:0048D3C4 mov ebp, esp
PAGE:0048D3C6 and [ebp+Handle], 0FFFFFFFCh
PAGE:0048D3CA mov eax, [ebp+Handle]
PAGE:0048D3CD mov ecx, [ebp+HandleTable]
PAGE:0048D3D0 mov edx, [ebp+Handle]
PAGE:0048D3D3 shr eax, 2
PAGE:0048D3D6 cmp edx, [ecx+38h]
PAGE:0048D3D9 jnb loc_4958D6
PAGE:0048D3DF push esi
PAGE:0048D3E0 mov esi, [ecx]
PAGE:0048D3E2 mov ecx, esi
PAGE:0048D3E4 and ecx, 3 // ecx - table level
PAGE:0048D3E7 and esi, not 3 // esi - pointer to first table
PAGE:0048D3EA sub ecx, 0
PAGE:0048D3ED jnz loc_48DEA4
PAGE:0048D3F3 lea eax, [esi+eax*8]
PAGE:0048D3F6 loc_48D3F6:
PAGE:0048D3F6 pop esi
PAGE:0048D3F7 loc_48D3F7:
PAGE:0048D3F7 pop ebp
PAGE:0048D3F8 retn 8
PAGE:0048DEA4 loc_48DEA4:
PAGE:0048DEA4 dec ecx
PAGE:0048DEA5 mov ecx, eax
PAGE:0048DEA7 jnz loc_52F57A
PAGE:0048DEAD shr ecx, 9
PAGE:0048DEB0 mov ecx, [esi+ecx*4]
PAGE:0048DEB3 loc_48DEB3:
PAGE:0048DEB3 and eax, 1FFh
PAGE:0048DEB8 lea eax, [ecx+eax*8]
PAGE:0048DEBB jmp loc_48D3F6
PAGE:0052F57A loc_52F57A:
PAGE:0052F57A shr ecx, 13h
PAGE:0052F57D mov edx, ecx
PAGE:0052F57F mov ecx, [esi+ecx*4]
PAGE:0052F582 shl edx, 13h
PAGE:0052F585 sub eax, edx
PAGE:0052F587 mov edx, eax
PAGE:0052F589 shr edx, 9
PAGE:0052F58C mov ecx, [ecx+edx*4]
PAGE:0052F58F jmp loc_48DEB3
再来看看ntoskrnl.pdb中的HANDLE_TABLE:
Code:
struct _HANDLE_TABLE {
// static data ------------------------------------
// non-static data --------------------------------
/*<thisrel this+0x0>*/ /*|0x4|*/ unsigned long TableCode;
/*<thisrel this+0x4>*/ /*|0x4|*/ struct _EPROCESS* QuotaProcess;
/*<thisrel this+0x8>*/ /*|0x4|*/ void* UniqueProcessId;
/*<thisrel this+0xc>*/ /*|0x10|*/ struct _EX_PUSH_LOCK HandleTableLock[4];
/*<thisrel this+0x1c>*/ /*|0x8|*/ struct _LIST_ENTRY HandleTableList;
/*<thisrel this+0x24>*/ /*|0x4|*/ struct _EX_PUSH_LOCK HandleContentionEvent;
/*<thisrel this+0x28>*/ /*|0x4|*/ struct _HANDLE_TRACE_DEBUG_INFO* DebugInfo;
/*<thisrel this+0x2c>*/ /*|0x4|*/ long ExtraInfoPages;
/*<thisrel this+0x30>*/ /*|0x4|*/ unsigned long FirstFree;
/*<thisrel this+0x34>*/ /*|0x4|*/ unsigned long LastFree;
/*<thisrel this+0x38>*/ /*|0x4|*/ unsigned long NextHandleNeedingPool;
/*<thisrel this+0x3c>*/ /*|0x4|*/ long HandleCount;
/*<thisrel this+0x40>*/ /*|0x4|*/ unsigned long Flags;
/*<bitfield this+0x40>*/ /*|0x1|*/ unsigned char StrictFIF0:1;
}; // <size 0x44>
利用以上信息还原该结构:
Code:
typedef struct _XP_HANDLE_TABLE
{
ULONG TableCode;
PEPROCESS QuotaProcess;
PVOID UniqueProcessId;
EX_PUSH_LOCK HandleTableLock[4];
LIST_ENTRY HandleTableList;
EX_PUSH_LOCK HandleContentionEvent;
PHANDLE_TRACE_DEBUG_INFO DebugInfo;
LONG ExtraInfoPages;
ULONG FirstFree;
ULONG LastFree;
ULONG NextHandleNeedingPool;
LONG HandleCount;
LONG Flags;
UCHAR StrictFIFO;
} XP_HANDLE_TABLE, *PXP_HANDLE_TABLE;
从上面的表中可以很明显看出ExpLookupHandleTableEntry函数从HANDLE_TABLE结构中得到TableCode的值,并基于其低2位的内容计算出表的层
次数。其余的位指向第1层表。因此Windows XP下的HANDLE_TABLE可以拥有1到3个层次,每一个层次的表的大小为1FFh。当表中记录的数量增加
时,系统会自动增加层数。很明显,当表中记录的数量超过0x200的时候表就会拥有两层,当大于0x40000时增加到第3层。不知道当销毁对象时
系统会不会减少表的层数,我没有注意到这件事。
Windows XP下没有ExLockHandleTableEntry函数,因此表中相应的模块被定位在ExMapHandleToPointer函数中。反汇编这个函数看看它做了什
么?
Code:
PAGE:0048F61E ExMapHandleToPointer proc near
PAGE:0048F61E
PAGE:0048F61E
PAGE:0048F61E var_8 = dword ptr -8
PAGE:0048F61E var_4 = dword ptr -4
PAGE:0048F61E HandleTable = dword ptr 8
PAGE:0048F61E Handle = dword ptr 0Ch
PAGE:0048F61E
PAGE:0048F61E mov edi, edi
PAGE:0048F620 push ebp
PAGE:0048F621 mov ebp, esp
PAGE:0048F623 push ecx
PAGE:0048F624 push ecx
PAGE:0048F625 push edi
PAGE:0048F626 mov edi, [ebp+Handle]
PAGE:0048F629 test di, 7FCh
PAGE:0048F62E jz loc_4A2A36
PAGE:0048F634 push ebx
PAGE:0048F635 push esi
PAGE:0048F636 push edi
PAGE:0048F637 push [ebp+HandleTable]
PAGE:0048F63A call ExpLookupHandleTableEntry
PAGE:0048F63F mov esi, eax
PAGE:0048F641 test esi, esi
PAGE:0048F643 jz loc_4A2711
PAGE:0048F649 mov [ebp+var_4], esi
PAGE:0048F64C loc_48F64C:
PAGE:0048F64C mov ebx, [esi]
PAGE:0048F64E test bl, 1
PAGE:0048F651 mov [ebp+var_8], ebx
PAGE:0048F654 jz loc_508844
PAGE:0048F65A lea eax, [ebx-1]
PAGE:0048F65D mov [ebp+Handle], eax
PAGE:0048F660 mov eax, [ebp+var_8]
PAGE:0048F663 mov ecx, [ebp+var_4]
PAGE:0048F666 mov edx, [ebp+Handle]
PAGE:0048F669 cmpxchg [ecx], edx
PAGE:0048F66C cmp eax, ebx
PAGE:0048F66E jnz loc_50884C
PAGE:0048F674 mov eax, esi
PAGE:0048F676 loc_48F676:
PAGE:0048F676 pop esi
PAGE:0048F677 pop ebx
PAGE:0048F678 loc_48F678:
PAGE:0048F678 pop edi
PAGE:0048F679 leave
PAGE:0048F67A retn 8
PAGE:0048F67A ExMapHandleToPointer endp
ExpLookuphandleTableEntry函数返回指向HANDLE_TABLE_ENTRY的指针后,我们要检查Object域的低字节,如果是被设置了的,说明是被清除了
的(俄文翻译者kao注:希望我翻译的对...),如果该位没有被设置,我们要等到它被设置了为止。因此当得到对象地址的时候我们不应该设
置高位(Windows 2000平台),而是要清除低位。综上所述,扫描表的代码如下:
Code:
void ScanXpHandleTable(PXP_HANDLE_TABLE HandleTable)
{
int i, j, k;
PHANDLE_TABLE_ENTRY Entry;
ULONG TableCode = HandleTable->TableCode & ~TABLE_LEVEL_MASK;
switch (HandleTable->TableCode & TABLE_LEVEL_MASK)
{
case 0 :
for (i = 0; i < 0x200; i++)
{
Entry = &((PHANDLE_TABLE_ENTRY)TableCode);
if (Entry->Object) ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT));
}
break;
case 1 :
for (i = 0; i < 0x200; i++)
{
if (((PVOID *)TableCode))
{
for (j = 0; j < 0x200; j++)
{
Entry = &((PHANDLE_TABLE_ENTRY *)TableCode)[j];
if (Entry->Object) ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT));
}
}
}
break;
case 2 :
for (i = 0; i < 0x200; i++)
{
if (((PVOID *)TableCode))
{
for (j = 0; j < 0x200; j++)
{
if (((PVOID **)TableCode)[j])
{
for (k = 0; k < 0x200; k++)
{
Entry = &((PHANDLE_TABLE_ENTRY **)TableCode)[j][k];
if (Entry->Object)
ProcessObject((PVOID)((ULONG)Entry->Object & ~XP_TABLE_ENTRY_LOCK_BIT));
}
}
}
}
}
break;
}
}
我们已经明白对象表格式了。现在想要枚举进程我们还需要得到PspCidTable的地址。也许你已经猜到了,我们要在
PsLookupProcessByProcessId函数中搜索,这个函数中的第1个函数调用包含着PspCidTable的地址。代码如下:
顶(0)
踩(0)
- 最新评论