什么叫rootkit?
它是由有用的小型程序组成的工具包,使得攻击者能够保持访问计算机上具有最高权限的用户“root”.rootkit是能够持久或可靠地、无法检测地存在于计算机上的一组程序和代码.
rootkit主要分为下列大类:
1 进程隐藏
2 文件隐藏
3 端口隐藏
4 注册表隐藏
5 驱动服务隐藏
Part I: 进程隐藏
一:序言
下列情况不在讨论之中(没进程)
1 通过CreateRemoteThread Inject代码到另一个进程(有种病毒就用这种方法,实现内存感染的;其实还有更多应用)
2 通过CreateRemoteThread LoadLibray一dll到另一个进程(屏蔽Ctrl+Alt+Del,就是通过这种方法和SetWindowLog实现)
二:进程隐藏
1 Hook/InlineHook Api NtQuerySystemInformation(taskmgr.exe就是用这个函数得到Process list)
2 Hook/InlineHook Api Process32Next
3 把要隐藏的进程的EPROCESS从LIST_ENTRY中摘除
a)ring0下驱动实现,注意:Nt/2000/xp/2003中PID和FLINK在EPROCESS中的offset不尽相同
b)ring3下利用call gate结合/Device/PhysicalMemory内核对象实现
二:检测进程隐藏
我们重要讨论一下杀毒软件Kaspersky和rootkit检测工具Icesword的两种方法:
1 kaspersky的方法:kaspersky从6.0中加入了主动防御功能,它detour了SwapContext.
lkd> u KiSwapThread L20
nt!KiSwapThread:
804dd66e 8bff mov edi,edi
804dd670 56 push esi
804dd671 57 push edi
804dd672 3ea120f0dfff mov eax,ds:[ffdff020]
804dd678 8bf0 mov esi,eax
804dd67a 8b4608 mov eax,[esi+0x8]
804dd67d 85c0 test eax,eax
804dd67f 8b7e04 mov edi,[esi+0x4]
804dd682 0f8557ba0000 jne nt!KiSwapThread+0x16 (804e90df)
804dd688 53 push ebx
804dd689 0fbe5e10 movsx ebx,byte ptr [esi+0x10]
804dd68d 33d2 xor edx,edx
804dd68f 8bcb mov ecx,ebx
804dd691 e86bffffff call nt!KiFindReadyThread (804dd601)
804dd696 85c0 test eax,eax
804dd698 0f843e990000 je nt!KiSwapThread+0x2e (804e6fdc)
804dd69e 5b pop ebx
804dd69f 8bc8 mov ecx,eax
804dd6a1 e80cf7ffff call nt!KiSwapContext (804dcdb2)
804dd6a6 84c0 test al,al
804dd6a8 8a4f58 mov cl,[edi+0x58]
804dd6ab 8b7f54 mov edi,[edi+0x54]
804dd6ae 8b3570864d80 mov esi,[nt!_imp_KfLowerIrql (804d8670)]
804dd6b4 0f85d10a0100 jne nt!KiSwapThread+0x56 (804ee18b)
804dd6ba ffd6 call esi
804dd6bc 8bc7 mov eax,edi
804dd6be 5f pop edi
804dd6bf 5e pop esi
804dd6c0 c3 ret
lkd> u KiSwapContext L20
nt!KiSwapContext:
804dcdb2 83ec10 sub esp,0x10
804dcdb5 895c240c mov [esp+0xc],ebx
804dcdb9 89742408 mov [esp+0x8],esi
804dcdbd 897c2404 mov [esp+0x4],edi
804dcdc1 892c24 mov [esp],ebp
804dcdc4 8b1d1cf0dfff mov ebx,[ffdff01c]
804dcdca 8bf1 mov esi,ecx
804dcdcc 8bbb24010000 mov edi,[ebx+0x124]
804dcdd2 89b324010000 mov [ebx+0x124],esi
804dcdd8 8a4f58 mov cl,[edi+0x58]
804dcddb e8d9000000 call nt!SwapContext (804dceb9)
804dcde0 8b2c24 mov ebp,[esp]
804dcde3 8b7c2404 mov edi,[esp+0x4]
804dcde7 8b742408 mov esi,[esp+0x8]
804dcdeb 8b5c240c mov ebx,[esp+0xc]
804dcdef 83c410 add esp,0x10
804dcdf2 c3 ret
lkd> u SwapContext L10
nt!SwapContext:
804dceb9 0ac9 or cl,cl
804dcebb 26c6462d02 mov byte ptr es:[esi+0x2d],0x2
804dcec0 9c pushfd
804dcec1 8b0b mov ecx,[ebx]
804dcec3 e948cfa077 jmp f7ee9e10(注意:这个地址不在NTOSKRNL.EXE范围中,落在klif.sys范围中,<它用了相对转跳,这样可以节约两个字节,cs:08>)
804dcec8 90 nop
804dcec9 90 nop
804dceca 51 push ecx
804dcecb 0f8534010000 jne nt!SwapContext+0x14d (804dd005)
804dced1 833d8c29568000 cmp dword ptr [nt!PPerfGlobalGroupMask (8056298c)],0x0
804dced8 0f85fe000000 jne nt!SwapContext+0x124 (804dcfdc)
804dcede 0f20c5 mov ebp,cr0
804dcee1 8bd5 mov edx,ebp
804dcee3 8a4e2c mov cl,[esi+0x2c]
804dcee6 884b50 mov [ebx+0x50],cl
804dcee9 fa cli
考虑到机器的效率,SwapContext是用汇编代码实现的,看看它具体功能(实现自己看代码吧:)):
;++
;
; Routine Description:
;
; This routine is called to swap context from one thread to the next.
; It swaps context, flushes the data, instruction, and translation
; buffer caches, restores nonvolatile integer registers, and returns
; to its caller.
;
; N.B. It is assumed that the caller (only caller's are within this
; module) saved the nonvolatile registers, ebx, esi, edi, and
; ebp. This enables the caller to have more registers available.
;
; Arguments:
;
; cl - APC interrupt bypass disable (zero enable, nonzero disable).
; edi - Address of previous thread.
; esi - Address of next thread.
; ebx - Address of PCR.
;
; Return value:
;
; al - Kernel APC pending.
; ebx - Address of PCR.
; esi - Address of current thread object.
;
;--
(虽然悬挂或等待的线程,不会获得cpu时间,但在SwapContext的时候仍然要检测,知道Thread了,得到对应Process也就容易了)
注:这个方法最初是J. Butler提出的,参见:http://ieeexplore.ieee.org/xpl/freeabs_all.jsp?arnumber=1232409
2 IceSword的方法:以前的方法是检测EPRCOESS,后来改成了PspCidTable
a)全局变量PspCidTable是一个HANDLE_TABLE的指针,这个变量并没有被NTOSKRNL导出,这个HANDLE_TABLE的表保存着所有进程和线程对象的指针.
b)PID(进程ID)和 ThreadID(线程ID)就是在这个句柄表中的索引,这个HANDLE_TABLE不属于任何进程,也没有链在HANDLE_TABLE链上.
c)PspCidTable在PsLookupProcessByProcessId中被用到,所以可以在此函数中搜索PspCidTalbe变量以定位其地址.
d)得到PspCidTable这个句柄表地址后,IceSword调用ExEnumHandleTable.
这个函数的函数原形是:
BOOLEAN ExEnumHandleTable(
IN PHANDLE_TABLE HandleTable,
IN EX_ENUMERATE_HANDLE_ROUTINE EnumHandleProcedure,
IN PVOID EnumParameter,
OUT PHANDLE Handle OPTIONAL)
参数说明:
HandleTable : 句柄表,可以用PspCidTable做参数.
EnumHandleProcedure: 类型为BOOLEAN (*EX_ENUMERATE_HANDLE_ROUTINE)(HANDLE_TALBE_ENTRY*,DWORD PID,PVOID Param)函数指针.
EnumParameter : 传送给EnumHandleProcedure函数的参数.
Handle : 此函数返回True时此参数才有效,为停止枚举前所枚举的句柄(可选).
功能说明:
调用ExEnumHandleTable函数的时,在每次枚举到表中的一个句柄时都会调用一次回调函数;
回调函数返回值为FALSE时继续枚举句柄表,返回TRUE时则停止枚举.
我们来看看他的具体实现吧!
BOOLEAN
ExEnumHandleTable(
IN PHANDLE_TABLE HandleTable,
IN EX_ENUMERATE_HANDLE_ROUTINE EnumHandleProcedure,
IN PVOID EnumParameter,
OUT PHANDLE Handle OPTIONAL
)
/*++
Routine Description:
This function enumerates all the valid handles in a handle table.
For each valid handle in the handle table, the specified eumeration
function is called. If the enumeration function returns TRUE, then
the enumeration is stopped, the current handle is returned to the
caller via the optional Handle parameter, and this function returns
TRUE to indicated that the enumeration stopped at a specific handle.
Arguments:
HandleTable - Supplies a pointer to a handle table.
EnumHandleProcedure - Supplies a pointer to a fucntion to call for
each valid handle in the enumerated handle table.
EnumParameter - Supplies an uninterpreted 32-bit value that is passed
to the EnumHandleProcedure each time it is called.
Handle - Supplies an optional pointer a variable that receives the
Handle value that the enumeration stopped at. Contents of the
variable only valid if this function returns TRUE.
Return Value:
If the enumeration stopped at a specific handle, then a value of TRUE
is returned. Otherwise, a value of FALSE is returned.
--*/
{
PHANDLE_ENTRY HandleEntry;
BOOLEAN ResultValue;
PHANDLE_ENTRY TableEntries;
PHANDLE_ENTRY TableBound;
ULONG TableIndex;
PAGED_CODE();
ASSERT(HandleTable != NULL);
//
// Lock the handle table exclusive and enumerate the handle entries.
//
ResultValue = FALSE;
ExLockHandleTableShared(HandleTable);
TableBound = HandleTable->TableBound;
TableEntries = HandleTable->TableEntries;
HandleEntry = &TableEntries[1];
while (HandleEntry < TableBound) {
if (ExIsEntryUsed(TableEntries, TableBound, HandleEntry)) {
TableIndex = HandleEntry - TableEntries;
if ((*EnumHandleProcedure)(HandleEntry,
INDEX_TO_HANDLE(TableIndex),
EnumParameter)) {
if (ARGUMENT_PRESENT(Handle)) {
*Handle = INDEX_TO_HANDLE(TableIndex);
}
ResultValue = TRUE;
break;
}
}
HandleEntry += 1;
}
ExUnlockHandleTableShared(HandleTable);
return ResultValue;
}
(PS:IceSword的检测方法部分参见匿名用户的文章,非常感谢!)
原文: http://blog.vckbase.com/windowssky/archive/2007/07/16/27457.aspx