tangptr@126.com 发表于 2015-7-11 19:12:43

TP的学习笔记:用读取内核内存的方式枚举进程

本帖最后由 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结构体:

可见在+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的位置,如图所示:

那么只需要对枚举活动链的代码稍作修改,就变成了枚举会话进程链表的代码,代码如下:
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不同的情况出现,如图所示:

于是呵呵哒,可见遍历会话进程链表在NT5是可靠的,但NT6就不一样了,那么就选择一条新的路:枚举句柄表
这个句柄表不是PspCidTable,那张表有点麻烦,还有一级表二级表啥啥的,还要去定位。。。这里呢指的是对象表。我们再看看EPROCESS的结构,可见在+0xC4的位置上就是进程对应的句柄表,如图所示:

再看看这里的_HANDLE_TABLE结构:

可以发现在+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的反汇编结果:

可以发现第五行的汇编代码是mov eax,dword ptr,也就是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:}

Tesla.Angela 发表于 2015-7-11 22:07:05

文章很不错!只不过在7、8年前才有价值!!!唉。。。

upring 发表于 2015-7-12 18:54:50

现在有价值的好文章越来越少了

xtfpg 发表于 2016-4-28 12:03:41

妹妹啊,怎么全是VB代码

tangptr@126.com 发表于 2016-4-28 12:34:19

xtfpg 发表于 2016-4-28 12:03
妹妹啊,怎么全是VB代码

习惯了在Ring3用VB写程序了,我几乎没有用除VB之外的语言写EXE了

xtfpg 发表于 2016-4-28 13:20:55

tangptr@126.com 发表于 2016-4-28 12:34
习惯了在Ring3用VB写程序了,我几乎没有用除VB之外的语言写EXE了

算你狠,你有驱动枚举进程的么?支持全系统的

tangptr@126.com 发表于 2016-4-28 22:11:36

xtfpg 发表于 2016-4-28 13:20
算你狠,你有驱动枚举进程的么?支持全系统的

用ZwQuerySystemInformation的要不要23333333333333兼容性爆表

xtfpg 发表于 2016-5-4 11:56:39

tangptr@126.com 发表于 2016-4-28 22:11
用ZwQuerySystemInformation的要不要23333333333333兼容性爆表

哈哈,我已经找到群主的相关代码了,很简单,好用的爆表
页: [1]
查看完整版本: TP的学习笔记:用读取内核内存的方式枚举进程