搜索高端内存枚举进程

之前写了pspCidTable枚举进程,有朋友mail提到了直接搜索内存高地址来枚举进程。记得好像以前uty提到过这种方法,顺带也自己试试。

第一部分:分页处理
————————————————————————————-

0x80000000-0xFFFFFFFF 是系统地址空间。一般kd debug的时候很容易看到EPROCESS都集中在0x80000000-0x90000000。挂驱动进去搜吧。 🙂
嗯,不过既然是XP环境下,分页是必然了(要验证的话,系统初始化的时候break下来,CR0里31位是1)。分页了,必要的检查是少不了了。因为虚拟地址是被映射过的,一旦搜索到的页是交换到硬盘上的,那就蓝的海天一色了…

检查也不复杂,如果我们访问的虚拟地址所在页在物理内存里,虚拟地址所在页相应的 PDE,PTE 就都是有效的,那么其他的就扔给CPU转换成物理地址然后访问就完了。如果页不在物理内存中(最不希望的就是在硬盘上的交换文件里),那对应的PDE,PTE 都是无效的,一访问必然是Page-Fault。

看下有效页表项PTE的结构:
kd> dt _HARDWARE_PTE
nt!_HARDWARE_PTE
+0x000 Valid      : Pos 0, 1 Bit
+0x000 Write      : Pos 1, 1 Bit
+0x000 Owner      : Pos 2, 1 Bit
+0x000 WriteThrough   : Pos 3, 1 Bit
+0x000 CacheDisable   : Pos 4, 1 Bit
+0x000 Accessed     : Pos 5, 1 Bit
+0x000 Dirty      : Pos 6, 1 Bit
+0x000 LargePage    : Pos 7, 1 Bit
+0x000 Global      : Pos 8, 1 Bit
+0x000 CopyOnWrite   : Pos 9, 1 Bit
+0x000 Prototype    : Pos 10, 1 Bit
+0x000 reserved     : Pos 11, 1 Bit
+0x000 PageFrameNumber : Pos 12, 20 Bits

OK,这些多的信息已经足够了…其实我们只关心里面的两个位,0位和7位。当0位为1时,说明PTE有效,我们遍历的页在物理内存中,一切安全…而7位的LargePage表示当前虚拟地址大于等于0x80000000 并且小于0xa0000000(减去0x80000000就得到了物理地址),很和谐,这部分也是安全的….
完事了,转到页目录项PDE,对我们有用的信息不多,0位是1时为Valid,其他的不关心了。 🙂

大概情况是这样:
4G地址空间的1024个页表按顺序被映射到了0xC0000000~0xC03FFFFF的4M地址空间。第一个4M地址空间的页表对应0xC0000000开始的4K,以此类推。而页目录被映射到了0xC0300000开始处的4K地址空间。
也就是说,PTE是按0x1000(4KB)步进,而PDE是按0x400000(4mb)递增。

代码如下:
ULONG ValidatePage(ULONG Addr)
{
ULONG pte;
ULONG pde;

pde = 0xc0300000 + (Addr>>22)*4;
if((*(PULONG)pde & 0x1) != 0)      //判断0位是否为1
{
if((*(PULONG)pde & 0x80) != 0)   //判断7位是否为1
{
return VALID;
}

pte = 0xc0000000 + (Addr>>12)*4;
if((*(PULONG)pte & 0x1) != 0)
{
return VALID;
}
else
{
return PTE_INVALID;
}
}

return PDE_INVALID;
}

几个全局变量如PDE_INVALID随便定义个值,值得关注的是怎么获取虚拟地址对应的PTE和PDE。
从WINDOWS内核勾出来的,如下:
#define MiGetPteAddress(va) ((PMMPTE)(((((ULONG)(va)) >> 12) << 2) + PTE_BASE))
#define MiGetPdeAddress(va) ((PMMPTE)(((((ULONG)(va)) >> 22) << 2) + PDE_BASE))
PTE_BASE为0xc0000000,PDE_BASE为0xc0300000。

第二部分:进程搜索
———————————————————————————-
参考原来uty的文章,我们重写之:

VOID SearchProcess(void)
{
ULONG i;
ULONG result;
ULONG Address;

for (i = 0x80000000 ;i<0x90000000;i+=4)
{
result = ValidatePage(i);
if (result == VALID)
{
Address = *(PULONG)i;
if ((Address & 0xffff0000) == 0x7ffd0000)
{
if(ValidateProcess(i))
{
DbgPrint(“EPROCESS: 0x%x “,i-PEB_OFFSET);
DbgPrint(“PID:%4d ProcessName: %s
“,
*(PULONG)(i-PEB_OFFSET+PROCESS_ID_OFFSET),
(PCHAR)(i-PEB_OFFSET+EPROCESS_NAME_OFFSET));

i += EPROCESS_SIZE;
}
}
}
else
if(result == PTE_INVALID)
{
i -=4;
i += 0x1000;
}
else
{
i-=4;
i+= 0x400000;
}
}

DbgPrint(“Done.”);
}

有几个有意思的地方:

1. if ((Address & 0xffff0000) == 0x7ffd0000)
我没花时间去想怎么在内核里得到PEB,比如EPROCESS遍历链或者ZwQueryInformationProcess,而是直接硬编码,因为获取PEB地址的方法简单的过分,交给ring3部分来处理,如下:

#include <stdio.h>

__inline __declspec(naked) unsigned int GetPEB()
{
__asm
{
xor esi, esi
mov esi, fs:[esi + 30H]
mov eax, esi
ret
}
}

void main(void)
{
printf(“located at: 0x%0.8X
“,GetPEB());
getchar();
}

参考我写的《WIN下获取kernel基址的shellcode探讨》。
得到的值是0x7ffd9000,对不同的进程,PEB高位是一样的,我们屏蔽低位就可以了。

2. 和XP平台相关的一些值:
#define EPROCESS_SIZE 0x25C
#define PEB_OFFSET 0x1b0
#define PROCESS_ID_OFFSET 0x084
#define OBJECT_HEADER_SIZE 0x18
#define OBJECT_TYPE_OFFSET 0x8
#define EPROCESS_NAME_OFFSET 0x174
这些都可以用KD自己看到,不废话了。 🙂

3. 判断是不是进程,这个我没做什么更改,把uty牛牛的代码直接搬过来的 *。*

4. 关于改进:
你可以考虑下程序的效率,对循环做点优化,因为毕竟不可能到0x90000000;另外一个就是system进程,枚举它还是有点问题的,自己PsGetCurrentProcess吧。
其他的,暂时想不到了…刚吃饱饭,容易得胃病…

完整的代码:
=========================================================================
#include <ntddk.h>

#define PDE_INVALID 2
#define PTE_INVALID 1
#define VALID 0

#define EPROCESS_SIZE 0x25C
#define PEB_OFFSET 0x1b0
#define PROCESS_ID_OFFSET 0x084
#define OBJECT_HEADER_SIZE 0x18
#define OBJECT_TYPE_OFFSET 0x8
#define EPROCESS_NAME_OFFSET 0x174

VOID WorkThread(IN PVOID pContext);
VOID DriverUnloAd(IN PDRIVER_OBJECT Driver_object);
VOID SearchProcess(VOID);
VOID ShowProcess(ULONG Addr);
ULONG ValidatePage(ULONG Addr);
BOOLEAN ValidateProcess(ULONG i);

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
{
NTSTATUS dwStAtus;
HANDLE hThreAd;

DriverObject->DriverUnload = DriverUnloAd;

dwStAtus = PsCreateSystemThread(
&hThreAd,
(ACCESS_MASK)0,
NULL,
(HANDLE)0,
NULL,
WorkThread,
NULL
);

return STATUS_SUCCESS;
}
//——————————————————————–
VOID DriverUnloAd(IN PDRIVER_OBJECT Driver_object)
{
}
//——————————————————————–
VOID WorkThread(IN PVOID pContext)
{
DbgPrint(“WorkThread is ready.”);
SearchProcess();

PsTerminateSystemThread(STATUS_SUCCESS);
}
//——————————————————————–
VOID SearchProcess(void)
{
ULONG i;
ULONG result;
ULONG Address;

DbgPrint(“WorkThread is working.”);
for (i = 0x80000000 ;i<0x90000000;i+=4)
{
result = ValidatePage(i);
if (result == VALID)
{
Address = *(PULONG)i;
if ((Address & 0xffff0000) == 0x7ffd0000)
{
if(ValidateProcess(i))
{
//DbgPrint(“EPROCESS: 0x%x “,i-PEB_OFFSET);
ShowProcess(i);
i += EPROCESS_SIZE;
}
}
}
else
if(result == PTE_INVALID)
{
i -=4;
i += 0x1000;//4k
}
else
{
i-=4;
i+= 0x400000;//4mb
}
}

for (i = 0xf0000000 ;i<0xffbe0000;i+=4)
{
result = ValidatePage(i);
if (result == VALID)
{
Address = *(PULONG)i;
if ((Address & 0xffff0000) == 0x7ffd0000)
{
if(ValidateProcess(i))
{
//DbgPrint(“EPROCESS: 0x%x “,i-PEB_OFFSET);
ShowProcess(i);
i += EPROCESS_SIZE;
}
}
}
else
if(result == PTE_INVALID)
{
i -=4;
i += 0x1000;//4k
}
else
{
i-=4;
i+= 0x400000;//4mb
}
}

DbgPrint(“Searching is finished.”);
}
//——————————————————————–
VOID ShowProcess(ULONG i)
{
DbgPrint(“PID:%4d ProcessName: %s
“,
*(PULONG)(i-PEB_OFFSET+PROCESS_ID_OFFSET),(PCHAR)(i-PEB_OFFSET+EPROCESS_NAME_OFFSET));
}
//——————————————————————–
ULONG ValidatePage(ULONG Addr)
{
ULONG pte;
ULONG pde;

pde = 0xc0300000 + (Addr>>22)*4;
if((*(PULONG)pde & 0x1) != 0)
{
if((*(PULONG)pde & 0x80) != 0)
{
return VALID;
}

pte = 0xc0000000 + (Addr>>12)*4;
if((*(PULONG)pte & 0x1) != 0)
{
return VALID;
}
else
{
return PTE_INVALID;
}
}

return PDE_INVALID;
}
//——————————————————————–
BOOLEAN ValidateProcess(ULONG i)
{
NTSTATUS stAtus;
PUNICODE_STRING pUnicode;
UNICODE_STRING Process;
ULONG pObjectType;
ULONG pObjectTypeProcess;

pObjectTypeProcess = *(PULONG)((ULONG)PsGetCurrentProcess()
-OBJECT_HEADER_SIZE +OBJECT_TYPE_OFFSET);

if (ValidatePage(i-PEB_OFFSET) != VALID)
{
return FALSE;
}

if (ValidatePage(i-PEB_OFFSET – OBJECT_HEADER_SIZE + OBJECT_TYPE_OFFSET)
== VALID)
{
pObjectType = *(PULONG)(i-PEB_OFFSET – OBJECT_HEADER_SIZE + OBJECT_TYPE_OFFSET);
}
else
{
return FALSE;
}

if(pObjectTypeProcess == pObjectType)
{
return TRUE;
}

return FALSE;
}
//——————————————————————–

评论关闭。