在前面的一篇文章中我写了点关于用PspCidTable枚举进程的学习笔记。不过一种方法并不能满足,所以又在网上找了一点关于暴力搜索内存枚举进程的资料,然后写了一些代码。最后写这篇文章作为自己的学习笔记。
既然是搜索,就要明白以下几个问题:
1.关于进程,我们应该搜索他的什么结构?
2.从那里搜索到哪里结束?
3.如果空间很大,如何加速搜索?
4.如何防止访问无效内存空间造成BSOD?
1.关于搜索进程的什么结构,我在网上找了一下,大概搜索的进程的PEB(PEB(Process Environment Block)
进程环境块)。主要原因是PEB地址的高4位是相同的。
PROCESS 85982da0 SessionId: none Cid: 03f4 Peb: 7ffde000 ParentCid: 0004
DirBase: 0b0d0020 ObjectTable: e1010d50 HandleCount: 20.
Image: smss.exe
PROCESS 85953880 SessionId: none Cid: 0404 Peb: 7ffdc000 ParentCid: 03f4
DirBase: 0b0d0040 ObjectTable: 00000000 HandleCount: 0.
Image: autochk.exe
PROCESS 8597eda0 SessionId: 0 Cid: 043c Peb: 7ffd9000 ParentCid: 03f4
DirBase: 0b0d0080 ObjectTable: e17eac78 HandleCount: 608.
Image: csrss.exe
PROCESS 858cb618 SessionId: 0 Cid: 0458 Peb: 7ffd7000 ParentCid: 03f4
DirBase: 0b0d00a0 ObjectTable: e1a5f9e8 HandleCount: 491.
Image: winlogon.exe
没错,都是7ffdxxxx。
我们知道,在2000系统中PEB的地址是固定的7FFDF000h。可是在Xp SP2的系统后这个值就发生了变化,但是其地址的
高4位却没有变化。具体可以看看这些代码。
PVOID HighestVadAddress;
LARGE_INTEGER CurrentTime;
HighestVadAddress = (PVOID) ((PCHAR)MM_HIGHEST_VAD_ADDRESS + 1);
KeQueryTickCount (&CurrentTime);
CurrentTime.LowPart &= ((X64K >> PAGE_SHIFT) - 1);
if (CurrentTime.LowPart <= 1) {
CurrentTime.LowPart = 2;
}
HighestVadAddress = (PVOID) ((PCHAR)HighestVadAddress - (CurrentTime.LowPart<<PAGE_SHIFT));
可以看出,PEB地址的设置跟时间是有关系的。在WRK中可以发现这样的定义:
#define X64K (ULONG)65536
#define MM_HIGHEST_VAD_ADDRESS ((PVOID)((ULONG_PTR)MM_HIGHEST_USER_ADDRESS - (64 * 1024)))
用WinDbg可以观察到MM_HIGHEST_USER_ADDRESS
lkd> dd MmHighestUserAddress
80558e3c 7ffeffff 00000000 00000001 00000000
那么MM_HIGHEST_VAD_ADDRESS 就是7ffdffff了,那么HighestVadAddress就是7ffe0000。而我们知道
PAGE_SHIFT其实就是12,那么((X64K >> PAGE_SHIFT) - 1)就是0xF。PEB变化的情况就只剩下16种了,接下来的
一个条件判断让0到F十六种情况一下子剩下14种了。最后用CurrentTime.LowPart<<PAGE_SHIFT的范围就是在
0x2000到0xf000中,这样HighestVadAddress 的地址就在7ffd1000到7ffde000之间的14种情况了。
2.从哪里开始搜索,到哪里结束呢?
用WinDbg的!process命令观察发现所有进程的EProcess结构地址,可以发现都
在0x80000000到"system"进程的EPROCESS之间。那么我们的搜索范围就确定了下来。至于Window怎么分配这些空间
的我还没有研究,等有时间再说吧。
3、4.如果空间很大,如何加速搜索?这个问题网上的前辈们也提供的很好的方法。那就是验证PTE和PDE的有效性。这
里就要说到地址的转换问题了。同时这样还可以有效避免BSOD。
虚拟地址->虚拟地址对应的PDE地址PDE_Address=(VirtualAddress>>22)*4+0xC0300000
虚拟地址->虚拟地址对应的PTE地址PTE_Address=(VirtualAddress>>12)*4+0xC0000000
typedef struct _HARDWARE_PTE_X86
{
ULONG Valid:1;
ULONG Write:1;
ULONG Owner:1;
ULONG WriteThrough:1;
ULONG CacheDisable:1;
ULONG Accessed:1;
ULONG Dirty:1;
ULONG LargePage:1;
ULONG Global:1;
ULONG CopyOnWrite:1;
ULONG Prototype: 1;
ULONG reserved: 1;
ULONG PageFrameNumber:20;
} HARDWARE_PTE_X86, *PHARDWARE_PTE_X86;
看看PTE的结构可知我们主要要判断Valid,LargePage。
然后判断其有效性,达到后来加速的效果。
根据前辈的代码,我自己重写了一下判断函数。
NTSTATUS NTAPI MmIsAddressInPageValid( IN PULONG Address ,OUT PULONG pType )
{
#ifndef PAGE_VALID
#define PDE_VALID 0x1
#define PTE_VALID 0x2
#define PDE_INVALID 0x3
#define PTE_INVALID 0x4
#define ADDR_INVALID 0x0
#endif
ULONG ulAddr;
ULONG ulPTE;
ULONG ulPDE;
ulAddr = (ULONG)Address;
ulPDE = 0xc0300000 + ( ulAddr >> 22 ) * 4;
//KdPrint(( "ulPDE%x" ,ulPDE ));
if ( !MmIsAddressValid( (PULONG)ulPDE ) )
{
if ( pType != NULL )
{
*pType = ADDR_INVALID;
}
return STATUS_UNSUCCESSFUL;
}
if ( ( (*(PULONG)ulPDE) & 0x1 ) != 0 )
{
if ( ( (*(PULONG)ulPDE) & 0x80 ) != 0 )
{
if ( pType != NULL )
{
*pType = PDE_VALID;
}
return STATUS_SUCCESS;
}
ulPTE = 0xc0000000 + ( ulAddr >> 12 ) * 4;
//KdPrint(( "ulPTE:%x" ,ulPTE ));
if ( !MmIsAddressValid( (PULONG)ulPTE ) )
{
if ( pType != NULL )
{
*pType = ADDR_INVALID;
}
return STATUS_UNSUCCESSFUL;
}
if ( ( (*(PULONG)ulPTE) & 0x1 ) != 0 )
{
if ( pType != NULL )
{
*pType = PTE_VALID;
}
return STATUS_SUCCESS;
}
else
{
if ( pType != NULL )
{
*pType = PTE_INVALID;
}
return STATUS_UNSUCCESSFUL;
}
}
if ( pType != NULL )
{
*pType = PDE_INVALID;
}
return STATUS_UNSUCCESSFUL;
}
最后要做的事情就是写几个函数,得到自己要的变量。网上这样的代码很多,我只是重写了一下,呵呵。
得到Process Type的函数。
ULONG NTAPI GetProcessType()
{
ULONG ulHeader;
ULONG Type;
ulHeader = (ULONG)PsGetCurrentProcess();
ulHeader = OBJECT_TO_OBJECT_HEADER( ulHeader );
Type = *(PULONG)(ulHeader + OBJECT_TYPE_OFFSET);
return Type;
}
判断是否是一个有效的EPROCESS的地址。
NTSTATUS NTAPI MmIsProcessAddr( IN PULONG Address )
{
ULONG ulProcessType;
ULONG ulAddr;
ULONG ObjectTypeAddr;
ULONG ObjectType;
ulAddr = (ULONG)Address;
if ( !NT_SUCCESS( MmIsAddressInPageValid( Address ,NULL ) ) )
{
KdPrint(( "Address is invalid" ));
return STATUS_UNSUCCESSFUL;
}
ObjectTypeAddr = OBJECT_TO_OBJECT_HEADER( ulAddr ) + OBJECT_TYPE_OFFSET;
if ( !NT_SUCCESS( MmIsAddressInPageValid( (PULONG)ObjectTypeAddr ,NULL ) ) )
{
KdPrint(( "ObjectTypeAddr is invalid" ));
return STATUS_UNSUCCESSFUL;
}
ObjectType = *(PULONG)ObjectTypeAddr;
if ( ObjectType == GetProcessType() )
{
return STATUS_SUCCESS;
}
return STATUS_UNSUCCESSFUL;
}
显示进程的函数。
void NTAPI ShowProcessInformation( IN PULONG Address )
{
PLARGE_INTEGER pExitTime;
ULONG PID;
PUCHAR pFileName;
pExitTime = (PLARGE_INTEGER)( (ULONG)Address + EXIT_TIME_OFFSET );
if ( pExitTime->QuadPart != 0 )
{
return;
}
PID = *(PULONG)( (ULONG)Address + PROCESS_ID_OFFSET );
pFileName = (PUCHAR)( (ULONG)Address + FILE_NAME_OFFSET );
KdPrint(( "ID:%d\t\tName:%s\n" ,PID ,pFileName ));
}
得到PEB地址的高4位,让然这里不能用System的PEB。因为System的PEB总是0。
PROCESS 867bc2c0 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 01038000 ObjectTable: e1000b80 HandleCount: 506.
Image: System
ULONG NTAPI GetPebAddressType()
{
ULONG PebAddr;
ULONG ulProcessAddr;
PULONG pSystem;
pSystem = (PULONG)PsGetCurrentProcess();
ulProcessAddr = (ULONG)(((PLIST_ENTRY)( (ULONG)pSystem + PROCESS_LINK_OFFSET ))-
>Flink)\
- PROCESS_LINK_OFFSET;
PebAddr = *(PULONG)( ulProcessAddr + PEB_OFFSET );
return ( PebAddr & 0xffff0000 );
}
最后的这个就是搜索函数。
void NTAPI DetectProcessBySearchMemory()
{
PULONG pSystem;
ULONG ulPoint;
ULONG ulPebAddr;
ULONG ulPageSign;
pSystem = (PULONG)PsGetCurrentProcess();
for ( ulPoint = 0x80000000 ;ulPoint < (ULONG)pSystem ;ulPoint += 4 )
{
if ( NT_SUCCESS( MmIsAddressInPageValid( (PLONG)ulPoint ,&ulPageSign ) ) )
{
ulPebAddr = *(PULONG)ulPoint;
if ( ( ulPebAddr & 0xffff0000 ) == GetPebAddressType() )
{
if ( NT_SUCCESS( MmIsProcessAddr( (PULONG)(ulPoint -
PEB_OFFSET) ) ) )
{
ShowProcessInformation( (PULONG)( ulPoint -
PEB_OFFSET ) );
}
}
}
else if ( ulPageSign == PTE_INVALID )
{
if ( ulPoint != 0x80000000 )
{
ulPoint -= 4;
}
ulPoint += 0x1000;
}
else if ( ulPageSign == PDE_INVALID )
{
if ( ulPoint != 0x80000000 )
{
ulPoint -= 4;
}
ulPoint += 0x400000;
}
else
{
KdPrint(( "Error!" ));
}
}
}
参考文档:
加密与解密(第三版)
JIURL玩玩Win2k内存篇 分页机制
ring0检测隐藏进程
WRK1.2
- 标 题:暴力搜索内存枚举进程的学习笔记
- 作 者:nightxie
- 时 间:2008-10-30 14:41:40
- 链 接:http://bbs.pediy.com/showthread.php?t=75681