|
本帖最后由 tangptr@126.com 于 2015-7-11 21:46 编辑
隐藏进程的Rootkit总是很恶心的东西,有的Rootkit甚至还会阻止ARK启动,仅仅是用任务管理器是肯定看不到的。那么我们在这里,用VB写一个检测进程的工具。
为了演示方便,我直接用ZwSystemDebugControl来读内核内存,故只支持Windows XP。要支持其他的操作系统,需要编写一个读写内核内存的驱动
首先要实现读取进程内存,代码如下:
- Public Sub ReadKernelMemory(ByVal dest As Long, ByVal src As Long, ByVal cch As Long)
- Dim mc As MEMORY_CHUNKS
- Dim st As Long, ret As Long
- With mc
- .Address = src
- .pData = dest
- .nSize = cch
- End With
- st = ZwSystemDebugControl(8, VarPtr(mc), Len(mc), 0, 0, ret)
- End Sub
复制代码
其中src是要读取的地址,dest是复制到的位置,cch是长度。
让我们先回顾到最早期的隐藏进程的方式,它是通过Hook SSDT的NtQueryInformationProcess实现的枚举进程,对抗这种方式,我们可以通过枚举进程活动链实现枚举。先看看Windows XP下的EPROCESS结构体:
ActiveLinks
可见在+0x88的位置就是活动进程链的偏移量。那么我们可以通过遍历双向链表的方式实现枚举,先讲原理:
双向链表是以手拉手的方式将多个结构体链接起来的,比如ABCDE这五个进程,它们的排列方式就是:
<-A-><-B-><-C-><-D-><-E->
有了原理,就可以写代码了,代码如下:
- Public Sub EnumProcess(ByVal LvItem As ListView)
- Dim txps As Long
- Dim PID As Long
- Dim ListEntry As LIST_ENTRY
- Dim NodePoint As Long
- Dim LvX As ListItem
- Dim LvC As Long: LvC = 0
- txps = PsGetCurrentProcess
- ReadKernelMemory VarPtr(ListEntry), txps + &H88, 8
- NodePoint = ListEntry.BLink - &H88
- Do
- LvC = LvC + 1
- ReadKernelMemory VarPtr(PID), txps + &H84, 4
- Set LvX = LvItem.ListItems.Add(LvC, , PID)
- LvX.ListSubItems.Add 1, , "0x" & Hex(txps)
- ReadKernelMemory VarPtr(ListEntry), txps + &H88, 8
- txps = ListEntry.FLink - &H88
- Loop Until txps = NodePoint
- MsgBox LvC & "Processes Detected!", vbInformation
- End Sub
复制代码
其中,有个函数叫做PsGetCurrentProcess,这是我自己写的函数,用于返回自己进程的EPROCESS,实现方法是枚举系统全部句柄,然后比较*(PULONG)(pObject+0x84)的值是不是自身进程的PID,如果是,就返回pObject的值。代码如下:
- Public Function PsGetCurrentProcess() As Long
- Dim st As Long
- Dim PID As Long
- Dim NumOfHandle As Long
- Dim i As Long
- st = 0
- Dim bytBuf() As Byte
- Dim arySize As Long: arySize = 1
- Do
- ReDim bytBuf(arySize)
- st = ZwQuerySystemInformation(SystemHandleInformation, VarPtr(bytBuf(0)), arySize, 0&)
- If (Not NT_SUCCESS(st)) Then
- If (st <> STATUS_INFO_LENGTH_MISMATCH) Then
- Erase bytBuf
- Exit Function
- End If
- Else
- Exit Do
- End If
- arySize = arySize * 2
- ReDim bytBuf(arySize)
- Loop
- NumOfHandle = 0
- Call CopyMemory(VarPtr(NumOfHandle), VarPtr(bytBuf(0)), Len(NumOfHandle))
- Dim h_info() As SYSTEM_HANDLE_TABLE_ENTRY_INFO
- ReDim h_info(NumOfHandle)
- Call CopyMemory(VarPtr(h_info(0)), VarPtr(bytBuf(0)) + Len(NumOfHandle), Len(h_info(0)) * NumOfHandle)
- For i = LBound(h_info) To UBound(h_info)
- With h_info(i)
- ReadKernelMemory VarPtr(PID), .pObject + &H84, 4
- If PID = GetCurrentProcessId Then
- PsGetCurrentProcess = .pObject
- End If
- End With
- Next
- End Function
复制代码
然而枚举活动进程链这招并不好使,因为摘除活动进程链是相当老的技术了,某个驱动开发教程甚至把摘除活动链隐藏进程放了进去。。。以至于人人都会摘除活动进程链隐藏进程。。。前些时间我还在VBGOOD论坛上混的时候,曾发过摘链隐藏进程的帖子,地址:摘除ActiveProcessLinks实现隐藏进程
既然枚举活动进程链不好使,就必须另辟蹊径,我们发现在EPROCESS的下面还有个叫做SessionProcessLinks的对象,翻译过来就是会话进程链表,它位于+0xB4的位置,如图所示:
SessionLinks
那么只需要对枚举活动链的代码稍作修改,就变成了枚举会话进程链表的代码,代码如下:
- Public Sub EnumProcess(ByVal LvItem As ListView)
- Dim txps As Long
- Dim PID As Long
- Dim ListEntry As LIST_ENTRY
- Dim NodePoint As Long
- Dim LvX As ListItem
- Dim LvC As Long: LvC = 0
- txps = PsGetCurrentProcess
- ReadKernelMemory VarPtr(ListEntry), txps + &HB4, 8
- NodePoint = ListEntry.BLink - &HB4
- Do
- LvC = LvC + 1
- ReadKernelMemory VarPtr(PID), txps + &H84, 4
- Set LvX = LvItem.ListItems.Add(LvC, , PID)
- LvX.ListSubItems.Add 1, , "0x" & Hex(txps)
- ReadKernelMemory VarPtr(ListEntry), txps + &HB4, 8
- txps = ListEntry.FLink - &HB4
- Loop Until txps = NodePoint
- MsgBox LvC & "Processes Detected!", vbInformation
- End Sub
复制代码
有个缺陷就是只能枚举会话ID相同的进程,若进程的会话ID不同,则枚举不到会话不同的进程。好在Windows XP下的所有进程的会话ID都是0,那么枚举进程也就不存在会话ID的问题。但是在NT6下,会有会话ID不同的情况出现,如图所示:
Session Id
于是呵呵哒,可见遍历会话进程链表在NT5是可靠的,但NT6就不一样了,那么就选择一条新的路:枚举句柄表
这个句柄表不是PspCidTable,那张表有点麻烦,还有一级表二级表啥啥的,还要去定位。。。这里呢指的是对象表。我们再看看EPROCESS的结构,可见在+0xC4的位置上就是进程对应的句柄表,如图所示:
HandleTable1
再看看这里的_HANDLE_TABLE结构:
HandleTable2
可以发现在+0x4的位置是对应进程的EPROCESS,在0x1C的位置则是个双向链表,看到双向链表,就可以枚举了,但绝对不是上面的代码中的偏移量就行了,因为这里我们其实遍历得到的是_HANDLE_TABLE。代码如下:
- Public Sub EnumProcess(ByVal LvItem As ListView)
- Dim txps As Long
- Dim PID As Long
- Dim ListEntry As LIST_ENTRY
- Dim HandleTable As Long
- Dim NodePoint As Long
- Dim LvX As ListItem
- Dim LvC As Long: LvC = 0
- txps = PsGetCurrentProcess
- ReadKernelMemory VarPtr(HandleTable), txps + &HC4, 4
- ReadKernelMemory VarPtr(ListEntry), HandleTable + &H1C, 8
- NodePoint = ListEntry.BLink - &H1C
- Do
- LvC = LvC + 1
- ReadKernelMemory VarPtr(PID), HandleTable + &H8, 4
- ReadKernelMemory VarPtr(txps), HandleTable + &H4, 4
- Set LvX = LvItem.ListItems.Add(LvC, , PID)
- LvX.ListSubItems.Add 1, , "0x" & Hex(txps)
- ReadKernelMemory VarPtr(ListEntry), HandleTable + &H1C, 8
- HandleTable = ListEntry.FLink - &H1C
- Loop Until HandleTable = NodePoint
- MsgBox LvC & "Processes Detected!", vbInformation
- End Sub
复制代码
上述三个方法,遍历进程的效果都不错,如果我们编写个读取内核内存的驱动,那么只需注意硬编码的问题就好。这里给出动态获取活动进程链表的解决方案:首先观察一下EPROCESS的结构,可以发现ActiveProcessLinks这个成员总是UniqueProcessId成员的下一个,我们再观察一下PsGetProcessId的反汇编结果:
PsGetProcessId
可以发现第五行的汇编代码是mov eax,dword ptr[eax+84h],也就是8B8084000000,因此UniqueProcessId的偏移量是在+0xA的位置上,读取这个值后进而算出ActiveProcessLinks的偏移量。
代码如下:
- ULONG DynamicGetOffsetOfActiveProcessLinks()
- {
- PVOID Function;
- UNICODE_STRING uniFuncName;
- ULONG offset;
- RtlInitUnicodeString(&uniFuncName,L"PsGetProcessId");
- Function=MmGetSystemRoutineAddress(&uniFuncName);
- offset=*(PULONG)((ULONG)Function+10);
- offset=offset+4;
- return offset;
- }
复制代码
还有一种算法:取得当前进程的EPROCESS,并用PsGetProcessId取得当前进程的PID,随后查找结构中值等于PID的位置,这个位置的内存地址相对于起始地址就是UniqueProcessId的偏移量。代码如下:
- ULONG DynamicGetOffsetOfActiveProcessLinks()
- {
- ULONG PID;
- PEPROCESS txps;
- ULONG i;
- ULONG offset;
- PVOID pAddr;
- txps=PsGetCurrentProcess();
- PID=PsGetProcessId(txps);
- for(i=(ULONG)txps;(ULONG)txps+0x1000;i=i+4)
- {
- pAddr=(PVOID)i;
- if(MmIsAddressValid(pAddr))
- {
- if(PID==*(PULONG)pAddr)
- {
- offset=(ULONG)pAddr-(ULONG)txps+4;
- return offset;
- }
- }
- }
- }
复制代码
此外就是程序还打包了结束进程的代码,方法就是把多个进程放置到同一个作业里然后结束作业。由于和本文无关,不贴代码了。
p.s:附件的描述摘自于Hovi.Delphic的帖子。由于本文是安利给大家的,所以既不设置水晶币,也不设置阅读权限{:soso_e113:} {:soso_e113:} {:soso_e113:} |
评分
-
查看全部评分
|