内存扫描一直都是比较根本的威胁检测手法,对于顽固型木马外挂而言,依赖于操作系统API相关的传统检测方式,模块扫描,窗口扫描,进程扫描以及文件扫描等都显得较为吃力,应用层而言如此,到内核层亦是如此。对于x86内核而言,0x80000000~0xFFFFFFFF 2GB内存扫描检测不是什么难事,因为整个区域就这么大。但是对于x64内核空间而言,就没有这么简单,尽管64位地址高16位一直都是保留字节0xffff,48位地址虚拟内存总量也大的有些离谱。或许有人会说物理内存就这么大,现在见的比较大的是16GB、32GB内存条,直接扫描物理内存不就好了。起初我也这么想,但是最主要的是要访问物理内存本质上需要通过虚拟内存经过页表转换到MMU单元才能完成对物理页的访问,而且抛开这个层面,尽管可以直接访问物理内存最终我们也是需要转为虚拟内存地址。那么本篇就来探索下从Win7开始到Win10 x64内核的虚拟地址空间布局以及经历了哪些更新变化。
黑暗之门
WIN7相关资料也比较多,WIN8总体上与WIN7很像,就放在一起介绍了。推荐下面两篇文章
原文版本Kernel Virtual Address Layout
翻译版本内核虚拟地址空间布局
引用下这张图

那我们看看怎么获取MmNonPagedPoolStart与MmNonpagedPoolEnd吧:
固定思路先从MiInitializeNonPagedPool函数找到未导出变量MmNonPagedPoolStart,发现他在MiCreatePfnDatabase里初始化

自己算不太现实,有几个因子也不好确定。查看MmNonpagedPool的引用发现数据段上有个引用:

最上面有个KdDebuggerDataBlock好像跟调试有关,发现导出函数KeCapturePersistentThreadState有用到这个,记得Blackbone有用到这个函数,可以获取一堆未导出变量地址
dumpHeader->KdDebuggerDataBlock就是KdDebuggerDataBlock地址,另外在0x2080偏移处还有另外一份Copy
那么拿到内存区域,就可以扫描一些自己想要的东西了,我最想扫的就是PG代码了...... 也可以扫隐藏驱动,隐藏进程
这里实际的应用我就不再做阐述了,简单的做个验证吧,找了几种可以在内核申请内存的方式:

可以得出
MmMapViewInSystemSpace是直接映射到System Ptes区域,与驱动映像属于同一块区域;
MDL申请出来的虚拟内存也是在System Ptes区域;
MmAllocateContiguousMemory申请的连续非分页物理内存 对应的虚拟内存空间 也是在SystemPtes区域。
说起这个阶段,印象最为深刻的就是这两个函数了
MiInitializeDynamicRegion 与 MiInitializeDynamicBitmap
MiInitializeDynamicRegion看着就以为好多区域都动态了,不再静态了,实际上呢?



就拿这三个区域说事,唬人还是巨硬强。
不过 引入的这两个结构管理内存区域,算是很值得称赞的,内存块全部用Bitmap管理,优化了性能(_MI_DYNAMIC_BITMAP Win8.1没有符号)
其实比较难找的还是NonPagePool区域,因为从Win8.1开始,KdDebuggerDataBlock就不再导出MmNonPagedPoolStart内核变量了,再来Win8.1开始就直接去掉了MmNonPagedPoolStart变量...真令人发指!
跟进去MiInitializeNonpagedPool函数看看,由于Win8.1结构体没符号,拿10240作为样例分析

这个qword_140340930原名叫做MiNodeInformation,Win10把这个变量符号拿掉了。
这个函数其实是对 0x100000000000 大小的NonpagedPool内存进行等分(份数就是KeNumberNodes的值,这个值是干什么的呢?目前我还没搞懂)
那么NonpagedPool基地址是多少呢? 是 ((0x8000000 / KeNumberNodes) << 9 * Index - 0x2000000000) << 12 (Release版本优化才会这么难看)
其实地址就是 Index 为0的时候,正好是 0xFFFFE000`00000000
整理之后,得到
看看Win8.1 测试结果

结果有些不一样,MmMapViewInSystemSpace映射的地址空间在DriverImage,而MDL与连续的物理页面映射的虚拟地址都在SystemPtes
其实从Win8.1开始,DriverImage不再纳入System Ptes空间,而是归入Initial Loader Mapping区域,统一改名为DriverImage,MapViewOfSection这种方式就会在DriverImage中申请虚拟内存空间
要是说Win8.1与早期的Win10版本没有真正意义上的内存布局动态化的话,那么Win10RS1以后的就是真正的动态化了,这个动态化从调试来看不是nt内部做的初始化,是更上一层,推测应该是Hal或者是Hyper-V进行的初始化布局。
之前研究过这篇文章 Win10 1909 反向计算windows内核内存布局及代码实现 知道了内核有个0x100大小的Mark标记数组,可以通过数组计算每个区域的具体位置,可惜的是到Win10 2004就已经找不到Mark的踪迹了。
观前顾后,我找到了MiQuerySystembase函数用到的一个未导出变量

我是怎么找到他的呢?很简单,还是看的MiInitializeNonpagedPool,发现用了这个数组的0号成员,很明显这个数组就是存放各种区域的地址范围的,并且Nt初始化时这个数组也早已被初始化了

通过一些猜测与符号的查找,发现这个数组结构体以及宏定义如下
在NT内核初始化时,会将Assignment数组里的地址范围全部应用到系统中。
那么找这个数组就用MiQuerySytemBase的特征码就行了。
不过...
最让人头疼的事情 是...
尽管8号之前没什么变化,但是后面序号一直在变,也不知道微软程序员抽了什么风...
最后看下Win10的测试结果吧,由于序号变得快,调试信息有不准的地方我也没做修改了...

结论还是与Win8.1一致,MmMapViewInSystemSpace映射在DriverImage区域,MDL与连续物理页的虚拟页映射在SystemPtes区域。
五点了,睡觉睡觉!
VOID InitializeDebuggerBlock()
{
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_FULL;
RtlCaptureContext(&context);
PDUMP_HEADER dumpHeader = (PDUMP_HEADER)ExAllocatePoolWithTag(NonPagedPool, DUMP_BLOCK_SIZE, MMS_POOL_TAG);
if (dumpHeader)
{
KeCapturePersistentThreadState(&context, NULL, 0, 0, 0, 0, 0, dumpHeader);
g_KdDebuggerDataBlock = dumpHeader->KdDebuggerDataBlock;
RtlCopyMemory(&g_KdBlock, (PUCHAR)dumpHeader + KDDEBUGGER_DATA_OFFSET, sizeof(g_KdBlock));
ExFreePool(dumpHeader);
}
}
VOID InitializeDebuggerBlock()
{
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_FULL;
RtlCaptureContext(&context);
PDUMP_HEADER dumpHeader = (PDUMP_HEADER)ExAllocatePoolWithTag(NonPagedPool, DUMP_BLOCK_SIZE, MMS_POOL_TAG);
if (dumpHeader)
{
KeCapturePersistentThreadState(&context, NULL, 0, 0, 0, 0, 0, dumpHeader);
g_KdDebuggerDataBlock = dumpHeader->KdDebuggerDataBlock;
RtlCopyMemory(&g_KdBlock, (PUCHAR)dumpHeader + KDDEBUGGER_DATA_OFFSET, sizeof(g_KdBlock));
ExFreePool(dumpHeader);
}
}
VOID MmsTestAllocateMemory()
{
MmsTestAllocatePagedPoolMemory();
MmsTestAllocateNonPagedPoolMemory();
MmsTestMapViewInSystemSpace();
MmsTestAllocateMDLMemory();
MmsTestAllocateContiguousMemory();
}
VOID MmsTestAllocateMemory()
{
MmsTestAllocatePagedPoolMemory();
MmsTestAllocateNonPagedPoolMemory();
MmsTestMapViewInSystemSpace();
MmsTestAllocateMDLMemory();
MmsTestAllocateContiguousMemory();
}
//0x68 bytes (sizeof)
struct _MI_SYSTEM_PTE_TYPE
{
struct _RTL_BITMAP_EX Bitmap; //0x0
struct _MMPTE* BasePte; //0x10
ULONG Flags; //0x18
enum _MI_SYSTEM_VA_TYPE VaType; //0x1c
ULONG* FailureCount; //0x20
ULONG PteFailures; //0x28
union
{
ULONGLONG SpinLock; //0x30
struct _FAST_MUTEX* GlobalMutex; //0x30
};
struct _MMSUPPORT* Vm; //0x38
volatile ULONGLONG TotalSystemPtes; //0x40
ULONGLONG Hint; //0x48
struct _MI_CACHED_PTE* CachedPtes; //0x50
volatile ULONGLONG TotalFreeSystemPtes; //0x58
volatile LONG CachedPteCount; //0x60
};
//0x50 bytes (sizeof)
struct _MI_DYNAMIC_BITMAP
{
struct _RTL_BITMAP_EX Bitmap; //0x0
ULONGLONG MaximumSize; //0x10
ULONGLONG Hint; //0x18
VOID* BaseVa; //0x20
ULONGLONG SizeTopDown; //0x28
ULONGLONG HintTopDown; //0x30
VOID* BaseVaTopDown; //0x38
ULONGLONG SpinLock; //0x40
struct _MMSUPPORT* Vm; //0x48
};
//0x68 bytes (sizeof)
struct _MI_SYSTEM_PTE_TYPE
{
struct _RTL_BITMAP_EX Bitmap; //0x0
struct _MMPTE* BasePte; //0x10
ULONG Flags; //0x18
enum _MI_SYSTEM_VA_TYPE VaType; //0x1c
ULONG* FailureCount; //0x20
ULONG PteFailures; //0x28
union
{
ULONGLONG SpinLock; //0x30
struct _FAST_MUTEX* GlobalMutex; //0x30
};
struct _MMSUPPORT* Vm; //0x38
volatile ULONGLONG TotalSystemPtes; //0x40
ULONGLONG Hint; //0x48
struct _MI_CACHED_PTE* CachedPtes; //0x50
volatile ULONGLONG TotalFreeSystemPtes; //0x58
volatile LONG CachedPteCount; //0x60
};
//0x50 bytes (sizeof)
struct _MI_DYNAMIC_BITMAP
{
struct _RTL_BITMAP_EX Bitmap; //0x0
ULONGLONG MaximumSize; //0x10
ULONGLONG Hint; //0x18
VOID* BaseVa; //0x20
ULONGLONG SizeTopDown; //0x28
ULONGLONG HintTopDown; //0x30
VOID* BaseVaTopDown; //0x38
ULONGLONG SpinLock; //0x40
struct _MMSUPPORT* Vm; //0x48
};
NTSTATUS MmsInitMemoryLayoutForWin8_1ToWin10TH2(IN OUT PDYNAMIC_DATA pData)
{
if (!pData)
{
return STATUS_INVALID_ADDRESS;
}
pData->MmPteSpaceStart = (PVOID)0xFFFFF68000000000;
pData->MmPteSpacecEnd = (PVOID)0xFFFFF6FFFFFFFFFF;
pData->MmHyperSpaceStart = (PVOID)0xFFFFF70000000000;
pData->MmHyperSpaceEnd = (PVOID)0xFFFFF77FFFFFFFFF;
pData->MmSharedSystemPageStart = (PVOID)0xFFFFF78000000000;
pData->MmSharedSystemPageEnd = (PVOID)0xFFFFF78000000FFF;
pData->MmSystemCacheStart = (PVOID)0xFFFFB00000000000;
pData->MmSystemCacheEnd = (PVOID)0xFFFFBFFFFFFFFFFF;
pData->MmPagedPoolStart = (PVOID)0xFFFFC00000000000;
pData->MmPagedPoolEnd = (PVOID)0xFFFFCF7FFFFFFFFF;
pData->MmSpecialPoolStart = (PVOID)0xFFFFCF8000000000;
pData->MmSpecialPoolEnd = (PVOID)0xFFFFCFFFFFFFFFFF;
pData->MmSystemPtesStart = (PVOID)0xFFFFD00000000000;
pData->MmSystemPtesEnd = (PVOID)0xFFFFDFFFFFFFFFFF;
pData->MmNonpagedPoolStart = (PVOID)0xFFFFE00000000000;
pData->MmNonpagedPoolEnd = (PVOID)0xFFFFF00000000000;//等分成KeNumberNodes块
pData->MmDriverImageStart = (PVOID)0xFFFFF80000000000;
pData->MmDriverImageEnd = (PVOID)0xFFFFF87FFFFFFFFF;
pData->MmSessionSpaceStart = (PVOID)0xFFFFF90000000000;
pData->MmSessionSpaceEnd = (PVOID)0xFFFFF97FFFFFFFFF;
pData->MmDynamicVASpaceStart = (PVOID)0xFFFFF98000000000;
pData->MmDynamicVASpaceEnd = (PVOID)0xFFFFFA70FFFFFFFF;
pData->MmPfnDatabaseStart = (PVOID)0xFFFFFA8000000000;
pData->MmPfnDatabaseEnd = (PVOID)((ULONG_PTR)pData->MmNonpagedPoolStart - 1);
return STATUS_SUCCESS;
}
NTSTATUS MmsInitMemoryLayoutForWin8_1ToWin10TH2(IN OUT PDYNAMIC_DATA pData)
{
if (!pData)
{
return STATUS_INVALID_ADDRESS;
}
pData->MmPteSpaceStart = (PVOID)0xFFFFF68000000000;
pData->MmPteSpacecEnd = (PVOID)0xFFFFF6FFFFFFFFFF;
pData->MmHyperSpaceStart = (PVOID)0xFFFFF70000000000;
pData->MmHyperSpaceEnd = (PVOID)0xFFFFF77FFFFFFFFF;
pData->MmSharedSystemPageStart = (PVOID)0xFFFFF78000000000;
pData->MmSharedSystemPageEnd = (PVOID)0xFFFFF78000000FFF;
pData->MmSystemCacheStart = (PVOID)0xFFFFB00000000000;
pData->MmSystemCacheEnd = (PVOID)0xFFFFBFFFFFFFFFFF;
pData->MmPagedPoolStart = (PVOID)0xFFFFC00000000000;
pData->MmPagedPoolEnd = (PVOID)0xFFFFCF7FFFFFFFFF;
pData->MmSpecialPoolStart = (PVOID)0xFFFFCF8000000000;
pData->MmSpecialPoolEnd = (PVOID)0xFFFFCFFFFFFFFFFF;
pData->MmSystemPtesStart = (PVOID)0xFFFFD00000000000;
pData->MmSystemPtesEnd = (PVOID)0xFFFFDFFFFFFFFFFF;
pData->MmNonpagedPoolStart = (PVOID)0xFFFFE00000000000;
pData->MmNonpagedPoolEnd = (PVOID)0xFFFFF00000000000;//等分成KeNumberNodes块
pData->MmDriverImageStart = (PVOID)0xFFFFF80000000000;
pData->MmDriverImageEnd = (PVOID)0xFFFFF87FFFFFFFFF;
pData->MmSessionSpaceStart = (PVOID)0xFFFFF90000000000;
pData->MmSessionSpaceEnd = (PVOID)0xFFFFF97FFFFFFFFF;
pData->MmDynamicVASpaceStart = (PVOID)0xFFFFF98000000000;
pData->MmDynamicVASpaceEnd = (PVOID)0xFFFFFA70FFFFFFFF;
pData->MmPfnDatabaseStart = (PVOID)0xFFFFFA8000000000;
pData->MmPfnDatabaseEnd = (PVOID)((ULONG_PTR)pData->MmNonpagedPoolStart - 1);
return STATUS_SUCCESS;
}
enum _MI_ASSIGNED_REGION_TYPES
{
AssignedRegionNonPagedPool = 0,
AssignedRegionPagedPool = 1,
AssignedRegionSystemCache = 2,
AssignedRegionSystemPtes = 3,
AssignedRegionUltraZero = 4,
AssignedRegionPfnDatabase = 5,
AssignedRegionCfg = 6,
AssignedRegionHyperSpace = 7,
AssignedRegionKernelStacks = 8,
AssignedRegionPageTables = 9,
AssignedRegionSession = 10,
AssignedRegionSecureNonPagedPool = 11,
AssignedRegionSystemImages = 12,
AssignedRegionMaximum = 13
};
typedef struct _MI_SYSTEM_VA_ASSIGNMENT
{
VOID* BaseAddress; //0x0
ULONGLONG NumberOfBytes; //0x8
} MI_SYSTEM_VA_ASSIGNMENT, * PMI_SYSTEM_VA_ASSIGNMENT;
enum _MI_ASSIGNED_REGION_TYPES
{
AssignedRegionNonPagedPool = 0,
AssignedRegionPagedPool = 1,
AssignedRegionSystemCache = 2,
AssignedRegionSystemPtes = 3,
AssignedRegionUltraZero = 4,
AssignedRegionPfnDatabase = 5,
AssignedRegionCfg = 6,
AssignedRegionHyperSpace = 7,
AssignedRegionKernelStacks = 8,
AssignedRegionPageTables = 9,
AssignedRegionSession = 10,
AssignedRegionSecureNonPagedPool = 11,
AssignedRegionSystemImages = 12,
AssignedRegionMaximum = 13
};
typedef struct _MI_SYSTEM_VA_ASSIGNMENT
{
VOID* BaseAddress; //0x0
ULONGLONG NumberOfBytes; //0x8
} MI_SYSTEM_VA_ASSIGNMENT, * PMI_SYSTEM_VA_ASSIGNMENT;
[培训]科锐软件逆向54期预科班、正式班开始火爆招生报名啦!!!
最后于 2020-10-25 10:54
被FaEry编辑
,原因: