想必有些人听说过RootKit这种东西,它能隐藏自身或者别的进程/文件/注册表项等等。
听起来很可怕不是,比较新的灰鸽子就有这些功能。
想必也有人听说过 IceSword,大名鼎鼎的 Anti-RootKit 工具。灰鸽子隐藏的东西在它的检测下是绝对逃不过的。
那么杀灰鸽子必须用 IceSword 么?答案是否定的。事实上,灰鸽子是一款技术含量极低的 RootKit (它甚至不配叫这个名字)。
它的技术含量低到什么程度呢?用 VB 只需写上几行代码即可干掉灰鸽子。
相信么?不信吧……Let's Go!
用某些技术手段可以看到,灰鸽子将一个或两个 DLL(视版本不同数量有所不同)注入至系统所有进程,这个/些 DLL 用来实现 API Hook。
API Hook 是什么?抱歉,限于篇幅,具体定义请自行 Google。
它 Hook 的 API 之一是未公开的 NtQuerySystemInformation,用于隐藏其进程。
用户模式下枚举进程,无论是 ToolHelpAPI 系列,还是 PSAPI 系列,都会最终调用到这个 API。
所以灰鸽子 Hook 了这个 API,当发现正在枚举进程时,它会检查当前返回的结果是否为自己的进程,如果是的话直接忽略并返回下一个进程。
于是所有普通的进程管理工具(任务管理器、Process Explorer 等)中都不会出现其进程。
那么有什么办法可以检出其隐藏进程呢?IceSword 的方法是直接加载驱动程序进入内核,无视其 Hook(很拽吧?),并通过直接读取未公开内核链表 PspCidTable 枚举出所有线程,进而得到所有进程。
如果你对系统还算熟悉,你会了解到每个进程都对应了一个 PID(Process ID,进程标识符),其实就是一串数字。它是随机产生的,且如果没有特殊情况的话在整个系统里是唯一的。
你还会了解到如果要对其他进程进行操作的话,需要调用一个API:OpenProcess。它由 Kernel32.dll 导出。
The code:
- Declare Function OpenProcess Lib "kernel32.dll" ( _
- ByVal dwDesiredAccess As Long, _
- ByVal bInheritHandle As Long, _
- ByVal dwProcessId As Long) As Long
其中第一个参数为你想获得的访问权限(也就是你得到的句柄的权限)。
第二个参数指定句柄是否可继承,无关紧要。
第三个参数是关键,它指定了你想打开的进程的PID。
实际上,这个 API 将参数略作处理后就直接呼叫了内核 API:NtOpenProcess。
The code:
- NTSTATUS NtOpenProcess (
- OUT PHANDLE ProcessHandle,
- IN ACCESS_MASK DesiredAccess,
- IN POBJECT_ATTRIBUTES ObjectAttributes,
- IN PCLIENT_ID ClientId OPTIONAL);
我们看看这个API究竟做了什么。
The code:
- PAGE:0049D59E ; NTSTATUS __stdcall NtOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess,
- POBJECT_ATTRIBUTES ObjectAttributes,PCLIENT_ID ClientId)
- PAGE:0049D59E public NtOpenProcess
- PAGE:0049D59E NtOpenProcess proc near
- PAGE:0049D59E
- PAGE:0049D59E ProcessHandle = dword ptr 4
- PAGE:0049D59E DesiredAccess = dword ptr 8
- PAGE:0049D59E ObjectAttributes= dword ptr 0Ch
- PAGE:0049D59E ClientId = dword ptr 10h
- PAGE:0049D59E
- PAGE:0049D59E push 0C4h
- PAGE:0049D5A3 push offset dword_413560 ; int
- PAGE:0049D5A8 call sub_40BA92
- PAGE:0049D5AD xor esi, esi
- PAGE:0049D5AF mov [ebp-2Ch], esi
- PAGE:0049D5B2 xor eax, eax
- PAGE:0049D5B4 lea edi, [ebp-28h]
- PAGE:0049D5B7 stosd
- PAGE:0049D5B8 mov eax, large fs:124h
- PAGE:0049D5BE mov al, [eax+140h]
- PAGE:0049D5C4 mov [ebp-34h], al
- PAGE:0049D5C7 test al, al
- PAGE:0049D5C9 jz loc_4BE034
- PAGE:0049D5CF mov [ebp-4], esi
- PAGE:0049D5D2 mov eax, MmUserProbeAddress
- PAGE:0049D5D7 mov ecx, [ebp+8]
- PAGE:0049D5DA cmp ecx, eax
- PAGE:0049D5DC jnb loc_520CDE
- PAGE:0049D5E2 loc_49D5E2:
- PAGE:0049D5E2 mov eax, [ecx]
- PAGE:0049D5E4 mov [ecx], eax
- PAGE:0049D5E6 mov ebx, [ebp+10h]
- PAGE:0049D5E9 test bl, 3
- PAGE:0049D5EC jnz loc_520CE5
- PAGE:0049D5F2 loc_49D5F2:
- PAGE:0049D5F2 mov eax, MmUserProbeAddress
- PAGE:0049D5F7 cmp ebx, eax
- PAGE:0049D5F9 jnb loc_520CEF
- PAGE:0049D5FF loc_49D5FF:
- PAGE:0049D5FF cmp [ebx+8], esi
- PAGE:0049D602 setnz byte ptr [ebp-1Ah]
- PAGE:0049D606 mov ecx, [ebx+0Ch]
- PAGE:0049D609 mov [ebp-38h], ecx
- PAGE:0049D60C mov ecx, [ebp+14h]
- PAGE:0049D60F cmp ecx, esi
- PAGE:0049D611 jz loc_4CCB88
- PAGE:0049D617 test cl, 3
- PAGE:0049D61A jnz loc_520CFB
- PAGE:0049D620 loc_49D620:
- PAGE:0049D620 cmp ecx, eax
- PAGE:0049D622 jnb loc_520D0D
- PAGE:0049D628 loc_49D628:
- PAGE:0049D628 mov eax, [ecx]
- PAGE:0049D62A mov [ebp-2Ch], eax
- PAGE:0049D62D mov eax, [ecx+4]
- PAGE:0049D630 mov [ebp-28h], eax
- PAGE:0049D633 mov byte ptr [ebp-19h], 1
- PAGE:0049D637 loc_49D637:
- PAGE:0049D637 or dword ptr [ebp-4], 0FFFFFFFFh
- PAGE:0049D63B loc_49D63B:
- PAGE:0049D63B
- PAGE:0049D63B cmp byte ptr [ebp-1Ah], 0
- PAGE:0049D63F jnz loc_520D34
- PAGE:0049D645 loc_49D645:
- PAGE:0049D645 mov eax, PsProcessType
- PAGE:0049D64A add eax, 68h
- PAGE:0049D64D push eax
- PAGE:0049D64E push dword ptr [ebp+0Ch]
- PAGE:0049D651 lea eax, [ebp-0D4h]
- PAGE:0049D657 push eax
- PAGE:0049D658 lea eax, [ebp-0B8h]
- PAGE:0049D65E push eax
- PAGE:0049D65F call SeCreateAccessState
- PAGE:0049D664 cmp eax, esi
- PAGE:0049D666 jl loc_49D718
- PAGE:0049D66C push dword ptr [ebp-34h] ; PreviousMode
- PAGE:0049D66F push ds:stru_5B6978.HighPart
- PAGE:0049D675 push ds:stru_5B6978.LowPart ; PrivilegeValue
- PAGE:0049D67B call SeSinglePrivilegeCheck
- PAGE:0049D680 test al, al
- PAGE:0049D682 jnz loc_4AA7DB
- PAGE:0049D688 loc_49D688:
- PAGE:0049D688 cmp byte ptr [ebp-1Ah], 0
- PAGE:0049D68C jnz loc_520D52
- PAGE:0049D692 cmp byte ptr [ebp-19h], 0
- PAGE:0049D696 jz loc_4CCB9A
- PAGE:0049D69C mov [ebp-30h], esi
- PAGE:0049D69F cmp [ebp-28h], esi
- PAGE:0049D6A2 jnz loc_4C1301
- PAGE:0049D6A8 lea eax, [ebp-24h]
- PAGE:0049D6AB push eax
- PAGE:0049D6AC push dword ptr [ebp-2Ch]
- PAGE:0049D6AF call PsLookupProcessByProcessId
- PAGE:0049D6B4 loc_49D6B4:
不要被吓倒,这个NtOpenProcess对参数进行检查(主要是检查进程权限)和处理后就传递给了另一个内核API:PsLookupProcessByProcessId。
那么我们继续追踪下去。
The code:
- PAGE:0049D725 public PsLookupProcessByProcessId
- PAGE:0049D725 PsLookupProcessByProcessId proc near
- PAGE:0049D725
- PAGE:0049D725
- PAGE:0049D725 ProcessId = dword ptr 8
- PAGE:0049D725 Process = dword ptr 0Ch
- PAGE:0049D725
- PAGE:0049D725 mov edi, edi
- PAGE:0049D727 push ebp
- PAGE:0049D728 mov ebp, esp
- PAGE:0049D72A push ebx
- PAGE:0049D72B push esi
- PAGE:0049D72C mov eax, large fs:124h
- PAGE:0049D732 push [ebp+ProcessId]
- PAGE:0049D735 mov esi, eax
- PAGE:0049D737 dec dword ptr [esi+0D4h]
- PAGE:0049D73D push PspCidTable
- PAGE:0049D743 call ExMapHandleToPointer
- PAGE:0049D748 mov ebx, eax
- PAGE:0049D74A test ebx, ebx
- PAGE:0049D74C mov [ebp+ProcessId], STATUS_INVALID_PARAMETER
- PAGE:0049D753 jz short loc_49D787
- PAGE:0049D755 push edi
- PAGE:0049D756 mov edi, [ebx]
- PAGE:0049D758 cmp byte ptr [edi], 3
- PAGE:0049D75B jnz short loc_49D77A
- PAGE:0049D75D cmp dword ptr [edi+1A4h], 0
- PAGE:0049D764 jz short loc_49D77A
- PAGE:0049D766 mov ecx, edi
- PAGE:0049D768 call sub_4134A9
- PAGE:0049D76D test al, al
- PAGE:0049D76F jz short loc_49D77A
- PAGE:0049D771 mov eax, [ebp+Process]
- PAGE:0049D774 and [ebp+ProcessId], 0
- PAGE:0049D778 mov [eax], edi
- PAGE:0049D77A loc_49D77A:
- PAGE:0049D77A push ebx
- PAGE:0049D77B push PspCidTable
- PAGE:0049D781 call ExUnlockHandleTableEntry
- PAGE:0049D786 pop edi
- PAGE:0049D787 loc_49D787:
- PAGE:0049D787 inc dword ptr [esi+0D4h]
- PAGE:0049D78D jnz short loc_49D79A
- PAGE:0049D78F lea eax, [esi+34h]
- PAGE:0049D792 cmp [eax], eax
- PAGE:0049D794 jnz loc_52388A
- PAGE:0049D79A loc_49D79A:
- PAGE:0049D79A mov eax, [ebp+ProcessId]
- PAGE:0049D79D pop esi
- PAGE:0049D79E pop ebx
- PAGE:0049D79F pop ebp
- PAGE:0049D7A0 retn 8
PAGE:0049D73D push PspCidTable
PAGE:0049D743 call ExMapHandleToPointer
这两个最为关键。你看到什么了?没错,PspCidTable。这个 API 用另外几个内核 API 遍历 PspCidTable 以确定一个进程是否存在。还记得 IceSword 是怎么检测隐藏进程的么(不过,最新版不只是检查了这个表)?
那么,回溯到文章开头,OpenProcess。你想到了什么?
The code:
- Option Explicit
- Private Declare Function OpenProcess _
- Lib "kernel32.dll" (ByVal dwDesiredAccess As Long, _
- ByVal bInheritHandle As Long, _
- ByVal dwProcessId As Long) As Long
- Private Declare Function CloseHandle _
- Lib "kernel32.dll" (ByVal hObject As Long) As Long
- Private Const PROCESS_QUERY_INFORMATION As Long = (&H400)
- Private Const PROCESS_TERMINATE As Long = (&H1)
- Sub Main()
- Dim i As Long
- Debug.Print "VB Hidden Process Detector"
- For i = 0 To 65536 Step 4
- If OpenProcess(PROCESS_QUERY_INFORMATION, False, i) <> 0 Then
- Debug.Print "PID=" & i
- CloseHandle i
- End If
- Next
- End Sub
打开“立即”窗口,运行一下看看结果吧。
至 于怎么干掉灰鸽子,很简单,把检测到的隐藏进程的句柄传递给 TerminateProcess 这个 API 即可。不过之前 OpenProcess 的时候访问权限要换成 PROCESS_TERMINATE。一旦其主进程被结束,其隐藏手段也就完全失效,然后就可以可以用普通工具直接看到其文件等。
本文仅用于科普,技术稍好点的 RootKit 可轻松突破此方法。
本文借用了国外部分研究资料,在此向作者表示感谢。
谢谢观赏。
THE END
版权所有(C)FlowerCode[0GiNr]