Win10和Win11内存区域划分及动态随机的本质

在win10内存中,很多win7中的结构体都被微软废除了,取而代之的使用了一个全局的变量来存放内存相关信息。看了很多中文的资料,发现对win10内存的资料介绍甚少,仅有的几篇也是我豆总在看雪发表的,不知道是大哥们对这个不屑一顾,还是不屑一顾,win10版本马上都停止维护了,但是在我看来比较有用的知识点还是没有人点出,(英文的资料还是有的),windows虽然日薄西山把,但也不至于这么日薄西山吧。这篇文章就来说下win10 内存的一些知识点。


这篇文章算是纯逆向所得,如果出现有明显错误的观点,欢迎指教,并请直接说出你认为的正确观点及证据。

0.背景知识介绍


MiState :这么说吧,你拿到他,win10内存基本就不用看了。一切你认为的没有符号,都源自于你没注意到他。

MI_SYSTEM_INFORMATION :MiState的类型就是它。具体这样:

Win10和Win11内存区域划分及动态随机的本质

MI_SYSTEM_VA_TYPE 这个是微软提供的内存类型。

Win10和Win11内存区域划分及动态随机的本质


引子:win10内存区域如何获取


nt内核中有一个函数为 MiGetSystemRegionType(ULONG64 va), 传入一个虚拟地址进去, 返回的是一个MI_SYSTEM_VA_TYPE枚举类型的值,该值代表了虚拟地址属于那种类型。

Win10和Win11内存区域划分及动态随机的本质

很明显, byte_140c6A018是一个数组,在没有对MiState的类型重新定义的时候,就是这样的,如果修改了MiState类型为MI_SYSTEM_INFORMATION。

Win10和Win11内存区域划分及动态随机的本质

嗯,没错是MiState.Vs.SysTemVaType,SysTemVaType ,这个就是豆总前几年说的那个0x100个标记的,另外指出一个错误。在FaEry作者的[原创]Windows内存篇Ⅱ x64内核内存布局的迁移演变(https://bbs.kanxue.com/thread-262931.htm)文章中有错误的表示,说是从Win10 2004之后这个标记已经找不到了, 我这边拿到的是win11 23h2的内核查看的,这个依然存在。

其实到这里依旧是拾人牙慧,只不过是从符号的角度来讲,说明当初豆总说的那个mark标记在哪里。如果只是单纯的想要判断那些地址是在那个内存区域,到这里基本就不用看了,下面讲解的都是为什么能够从这个成员变量得到内存区域枚举值。

想一下,((a1 >> 0x27) & 0x1FF) – 0x100 这个偏移是如何被设计的,SysTemVaType这个成员变量是如何被设计填充。了解这些设计后面的知识,才能明白windows内存区域划分的关键。

1. SystemVaRegions成员

SystemVaRegions 是 MiState.Vs.SysTemVaType 之后的一个成员数组,这个数组的个数在win10的各个版本都存在变化的情况,我选取的win10 版本有 13个 ,SystemVaRegions 的类型为_MI_SYSTEM_VA_ASSIGNMENT ,具体定义:

_MI_SYSTEM_VA_ASSIGNMENT
{
VOID* BaseAddress; //0x0 ULONGLONG
NumberOfBytes; //0x8
};

看到这里是不是觉得开始有趣, 这个BaseAddress是不是和我们的内存区域有关。是的,很明确的告诉,在上一节提到的 FaEry的[原创]Windows内存篇Ⅱ x64内核内存布局的迁移演变(https://bbs.kanxue.com/thread-262931.htm#msg_header_h1_4)文中 提到的MiQuerySystembase函数用到的一个未导出变量,该变量为 SystemVaRegions,打开ida看一下在符号下的样子。

Win10和Win11内存区域划分及动态随机的本质

嗯。有符号确实好看哈。但是这个结构体成员变量是如何被填充的,它是如何和SysTemVaType数组进行关联的。
这个是问题的关键。

还有一个枚举值:MI_ASSIGNED_REGION_TYPES

Win10和Win11内存区域划分及动态随机的本质

通过该枚举值 我们可以知道 SystemVaRegions 的13个数组成员 分别代表了什么 ,那么MI_ASSIGNED_REGION_TYPES 和 MI_SYSTEM_VA_TYPE 是如何被转换的?这也是我们需要思考的。

2. MmInitSystem 函数

总所周知,windows是会分阶段初始化的, 在调用KiInitializeBootStructures过程中,会在0xff阶段的时候调用MmInitSystem函数 ,其实 MmInitSystem 函数 也是分为多个阶段被调用,现在只说和我们相关的,在0xff阶段的时候会调用 MiInitializeSystemVa。

MiInitializeSystemVa 函数是负责填充 SystemVaRegions 结构体的关键函数,也是 windows内存区域能够动态初始化的关键。

MiInitializeSystemVa 函数被执行后,会调用 MiInitializeTopLevelBitmap 函数,用来初始化 一个bitmap 这个bitmap 指向了 MiState.SystemVa.SystemVaAssignment, SystemVaAssignment是一个int[8]类型的成员, 如果你留意其大小,你就会发现, SystemVaAssignment 的大小是32个字节,256bit, 还有一个 MiState.SystemVa.SystemVaAssignmentHint,请记住,这个变量是动态初始化的关键一步。

在初始化这个bitmap之后, MiInitializeSystemVa 会调用 MiAssignTopLevelRanges ,而 MiAssignTopLevelRanges 是梦最初的地方。

ps: 如果想要通过windbg 调试MmInitSystem 的0xff阶段的话,可能需要进行 额外的设置。

3. MiAssignTopLevelRanges

MiAssignTopLevelRanges 如下图所示, 其中通过逆向,对Base 这个数组的类型进行 了重定义。通过qsort函数可知 每个BASE的结构体大小为 0x18 , 总共有13个, 上文提到过,我选取的win10内核中的SystemVaRegions也是13个。

嗯。没错,这个Base 和 SystemVaRegions 有着千丝万缕的关系。

Win10和Win11内存区域划分及动态随机的本质

其中我定义的Base 的结构体为:

struct MyBase_VIsibleType

{

unsigned __int32 index;

unsigned __int32 randseed;

unsigned __int64 BaseAddress;

unsigned __int64 Length;

};

从上面截图可以看出, Base结构体数组 ,对 结构体的 index ,randseed ,Length 都进行了 赋值。

Win10和Win11内存区域划分及动态随机的本质

通过MiAssignSystemVa 函数我们得到 Base数组元素中BaseAddress的值。

Win10和Win11内存区域划分及动态随机的本质

然后 对MiState.Vs.SystemVaRegions的数组进行初始化填充。

4. MiAssignSystemVa

MiAssignSystemVa是 动态内存区域划分的关键函数,其函数原型为
int64 MiAssignSystemVa(ULONG NumberToFind, unsigned int a2)

Win10和Win11内存区域划分及动态随机的本质

首先解释 MiAssignSystemVa 函数的 第一个参数,也就是我们上一节提到的。

v19 = (*p_Length + 0x7FFFFFFFFFi64) & 0xFFFFFF8000000000ui64;
*p_Length = v19;
vaBaseaddress = MiAssignSystemVa(v19 >> 0x27, a1);

NumberToFind 就是 length >> 0x27得到的。
MiAssignSystemVa 的主要功能是取到已经初始化好的MiState.SystemVa.SystemVaAssignment 和 已经随机化好的MiState.SystemVa.SystemVaAssignmentHint,通过RtlFindClearBitsAndSet 去查找到符合满足条件的位图索引。

其中内存区域的动态随机设置就和这个位图的find过程有关,根据以上截图我们可以看出。只有满足找到的索引值是随机值或至少满足10次循环才会跳出find过程。

最后,得到位图的索引值 ,通过 index – 0x100 ,通过左移0x27,得到baseaddress。

根据上面我们可以得出一个结论:windows内核内存区域的动态随机初始化由 两个变量影响:
1.MiState.SystemVa.SystemVaAssignmentHint
2.SystemVaRegions[i].NumberOfBytes
二者通过入参的形式,影响了位图寻找索引的流程。该索引值最终影响了baseaddress 的计算。

5. MI_ASSIGNED_REGION_TYPES 和 MI_SYSTEM_VA_TYPE之间的转换


在 win10和win11 中, MiInitializeSystemVa 在调用完 MiAssignTopLevelRanges 之后,会调用 MiConvertAssignedRegionToVaType(i) 。

MiConvertAssignedRegionToVaType 的原理就是内建了一张表,通过 输入MI_ASSIGNED_REGION_TYPES类型的枚举值,然后根据表 输出 对应的MI_SYSTEM_VA_TYPE类型的值。

在win11中,MiConvertAssignedRegionToVaType被封装到了MiSetSystemRegionTypes(i)中,各个小版本之间这张表可能也不同。

放win11 23h2 和 win10的 代码。

win10
__int64 __fastcall MiConvertAssignedRegionToVaType(int a1)
{
int v1; // ecx
int v2; // ecx
int v3; // ecx
int v5; // ecx
int v6; // ecx
int v7; // ecx
int v8; // ecx

if ( a1 > 7 )
{
v5 = a1 - 8;
if ( v5 )
{
v6 = v5 - 1;
if ( !v6 )
return 4i64;
v7 = v6 - 1;
if ( v7 )
{
v8 = v7 - 1;
if ( v8 )
{
if ( v8 != 1 )
return 0i64;
return 12i64;
}
else
{
return 15i64;
}
}
else
{
return 1i64;
}
}
else
{
return 14i64;
}
}
else if ( a1 == 7 )
{
return 2i64;
}
else if ( a1 )
{
v1 = a1 - 1;
if ( v1 )
{
v2 = v1 - 1;
if ( v2 )
{
v3 = v2 - 1;
if ( v3 )
{
if ( v3 != 2 )
return 0i64;
return 4i64;
}
return 9i64;
}
else
{
return 8i64;
}
}
else
{
return 6i64;
}
}
else
{
return 5i64;
}
}
win 11

__int64 __fastcall MiConvertAssignedRegionToVaType(int a1)
{
int v1; // ecx
int v2; // ecx
int v3; // ecx
int v4; // ecx
int v6; // ecx
int v7; // ecx
int v8; // ecx
int v9; // ecx
int v10; // ecx

if ( a1 > 8 )
{
v6 = a1 - 9;
if ( !v6 )
return 14i64;
v7 = v6 - 1;
if ( v7 )
{
v8 = v7 - 1;
if ( !v8 )
return 1i64;
v9 = v8 - 1;
if ( !v9 )
return 15i64;
v10 = v9 - 1;
if ( !v10 )
return 16i64;
if ( v10 != 1 )
return 0i64;
return 12i64;
}
return 4i64;
}
if ( a1 == 8 )
return 2i64;
if ( !a1 )
return 4i64;
v1 = a1 - 1;
if ( !v1 )
return 5i64;
v2 = v1 - 1;
if ( !v2 )
return 6i64;
v3 = v2 - 1;
if ( !v3 )
return 8i64;
v4 = v3 - 1;
if ( !v4 )
return 9i64;
if ( v4 != 1 )
return 0i64;
return 17i64;
}


6. SystemVaRegions与SystemVaType的建立关联


在 MiInitializeSystemVa 函数执行完上面所提到的流程后,还会对wsl的内存区域进行一个分配。并不在我们讨论的范围,所以跳过。

随着 BaseAddress被填充到 SystemVaRegions 后, 程序会进入一个循环。一个把 SystemVaRegions 与SystemVaType 连起来的循环。

因为ida本身反汇编识别的问题,所以我们这段是直接通过汇编手动还原成c代码。

asm:

Win10和Win11内存区域划分及动态随机的本质

ida 反汇编:

Win10和Win11内存区域划分及动态随机的本质

手动还原:

for ( i = 0; i < 0xD; i ++ ) // SystemVaRegions与SystemVaType有多少个 数组成员 就循环多少次
{

BaseAddress = systemVaRegions[i].BaseAddress;

Length = systemVaRegions[i].NumberOfBytes; // 和填充长度有关

offset = ((BaseAddress >> 0x27) & 0x1FF) - 0x100; //这个offset 眼熟不,和MiGetSystemRegionType
// 的计算数组的偏移是一样的。
paddinglength = Length>> 0x27

value = MiConvertAssignedRegionToVaType(i); //枚举类型转换

for ( j = Length >> 0x27; j; --j )
{

Systemvatype[offset] = value // 对Systemvatype填充转换后的枚举类型

offset ++ ;

}

}

通过手动还原,我们可以知道, SystemVaRegions 与 SystemVaType 建立映射关系的步骤分为:

1.取出属于SystemVaRegions中数组的基地址和这个区域的长度。
2.根据规则算出偏移。
3.根据MiConvertAssignedRegionToVaType函数把 MI_ASSIGNED_REGION_TYPES 类型转换为 MI_SYSTEM_VA_TYPE 类型。
4.根据 offset 对Systemvatype中的数组进行填充,填充的值就是 MI_SYSTEM_VA_TYPE类型的值,填充的长度NumberOfBytes>>0x27。
5.继续下一次循环。

根据还原后的代码,我们可以看出为什么通过 MiGetSystemRegionType 函数可以获取到 指定虚拟地址的 内存区域类型。因为在 填充SystemVaType 成员的时候,就是根据规则偏移来填充的。填充的值还是SystemVaRegions经过转换后的枚举值。填充的长度也和NumberOfBytes>>0x27。

((a1 >> 0x27) & 0x1FF) – 0x100背后的设计理念:


回想一下 在引子部分提到的 :((a1 >> 0x27) & 0x1FF) – 0x100 ,我们可以进一步思考:

a1 为 一个内核的虚拟地址, a1 >> 0x27 是在取 pml4的值, (pml4 & 0x1ff) 这个是 确保能取到 pml4索引,因为pml4 占位 9bit,最终 pml4的index – 0x100 得出最终的索引。因为虚拟地址是连续的,假设存在 nopagedpool 的内存地址 A, A必定满足 大于等于nopagedpool.baseaddress,小于等于
nopagedpool.baseaddress+nopagedpool.length,如果使用我们的符号表示:
systemVaRegions[0].BaseAddress<= A <= systemVaRegions[0].BaseAddress +systemVaRegions[0].NumberOfBytes , 所以 A地址代表的pml4索引必定Systemvatype中代表nopagedpool的连续为分区内。

所以 通过MiGetSystemRegionType去内存区域的本质是 ,通过pml4索引的值 去取已经填充好的“位图”的值。

到此 ,分析告一段落。

7. 后话


1.本篇文章通过 MiGetSystemRegionType 函数能够获取 某个虚拟地址为 引子,介绍了一下几个知识点:

1.SystemVaType、SystemVaRegions、MI_ASSIGNED_REGION_TYPES 和 MI_SYSTEM_VA_TYPE基本类型。

2.MmInitSystem函数初始化流程。

3.windows内核内存的区域的动态随机划分的本质是什么。

4.MI_ASSIGNED_REGION_TYPES 和 MI_SYSTEM_VA_TYPE之间转换的关系。

5.SystemVaRegions与SystemVaType是如何建立关联的。

6.((a1 >> 0x27) & 0x1FF) – 0x100背后的设计理念,以及为什么可以通过MiGetSystemRegionType得出指定虚拟地址的内存类型。


本文仅用作抛砖引玉,希望广大windows内核爱好者能够基于本文的些许启发,对windows 内核的内存有更深的了解。

用大白话说,使用MiState 变量可以 搞到很多关于内存的知识点, 比如在内存初始化的0阶段, 可以通过逆向MiInitNucleus函数 清晰的看到 各个内存区域是如何被建立的,以及物理页帧的几个链表是如何被初始化的。在逆向nt内核过程中我们其实也可以发现 也有其他的关键变量可以去读取内存的关键信息,比如 MiSystemPartition 全局变量。一切关于内存的逆向,还看各位大佬们出手了。

(ps:使用MiState 有一个小坑, 正式版本的 MI_SYSTEM_INFORMATION 微软提供的有些许问题,需要自己改动,如果自己仔细思考的话,解决这个问题应该不在话下)。



Win10和Win11内存区域划分及动态随机的本质


看雪ID:青丝梦

https://bbs.kanxue.com/user-home-724114.htm

*本文为看雪论坛精华文章,由 青丝梦 原创,转载请注明来自看雪社区

Win10和Win11内存区域划分及动态随机的本质



# 往期推荐

1、记由长城杯初赛Time_Machine掌握父子进程并出题

2、从Clang到Pass加载与执行的流程

3、OLLVM混淆源码解读

4、VMProtect保护壳爆破步骤详解(入门级)

5、Attitude Adjustment — Fast Quaternion Attitude


Win10和Win11内存区域划分及动态随机的本质


Win10和Win11内存区域划分及动态随机的本质

球分享

Win10和Win11内存区域划分及动态随机的本质

球点赞

Win10和Win11内存区域划分及动态随机的本质

球在看



Win10和Win11内存区域划分及动态随机的本质

点击阅读原文查看更多

原文始发于微信公众号(看雪学苑):Win10和Win11内存区域划分及动态随机的本质

版权声明:admin 发表于 2024年7月3日 下午6:03。
转载请注明:Win10和Win11内存区域划分及动态随机的本质 | CTF导航

相关文章