当前位置: 首页 > ops >正文

windows内核研究(内存管理-线性地址的管理)

内存管理


线性地址的管理

进程空间的地址划分

分区x86 32位Windows
空指针赋值区0x00000000 - 0x0000FFFF
用户模式区0x00010000 - 0x7FFEFFFF
64KB禁入区0x7FFF0000 - 0x7FFFFFFF
内核0x80000000 - 0xFFFFFFFF

线性地址有4GB,但是并不是所有的地方都能访问(这里的不能访问只是默认情况下,一但给这些区域挂上物理页还是可以访问的),所以需要记录哪些地方分配了

在内核空间是通过一个链表把所有未分配的空间链在一起
但是在用户空间,这样管理的效率太低,而是通过收索二叉树来管理

在_EPROCESS结构体当中有一个成员VadRoot,这个成员就是这个二叉树的入口点

在这里插入图片描述
由于我是用64位windbg分析32位的系统,版本等原因导致VadRoot的地址未被正常解析出来,所以我们直接加上偏移来解析这个地址

dt _RTL_AVL_TREE (ac110040 + 310)  // 进程地址ac110040 + 偏移310 VadRoot处

在这里插入图片描述
得到地址:0xbb925678

VadRoot通常每一个节点都是_MMVAD结构,但是现在的windows对VadRoot进行了优化,并不直接指向_MMVAD,而是通过AVL 树/红黑树的结构来进行优化访问和存储可以使用以下命令直接遍历VadRoot

!vad 地址 // 遍历vad

在这里插入图片描述

字段示例值含义
VAD 节点地址bb923260该 VAD 节点在内核中的内存地址(_MMVAD 结构地址)
Level8该节点在 VAD 树中的深度(层级)
Start580内存区域的起始页号(需转换为虚拟地址:Start << PAGE_SHIFT,32位系统 PAGE_SHIFT=12,即 0x580000
End5a7内存区域的结束页号(0x5A7000
Commit5已提交的物理页数量(单位:页,每页通常 4KB)
TypeMapped内存类型:
Private(私有内存,如堆/栈)
Mapped(映射文件或共享内存)
SubtypeExe子类型(仅适用于 Mapped 类型):
Exe(可执行文件映射)
Image(镜像文件)
• 其他(如 Pagefile
ProtectionEXECUTE_WRITECOPY内存保护标志:
READONLY/READWRITE
EXECUTE/EXECUTE_WRITECOPY
PAGE_GUARD(保护页)
File/Desc\Users\...\x32dbg.exe如果是文件映射,显示文件路径;如果是共享内存,显示描述信息(如 Pagefile section

Private Memory

申请内存的两种方式:

  1. 通过VirtualAlloc/VirtualAllocEx申请的:Private Memory(当前的进程独享内存)
  2. 通过CreateFileMapping映射的:Mapped Memory

我们来通过代码来看一下VirtualAlloc在没有分配和分配后的线性地址

#include<iostream>
#include<windows.h>LPVOID lpAddr;int main() {printf("当前内存还未申请!");getchar();lpAddr = VirtualAlloc(NULL, 0x1000 * 2, MEM_COMMIT, PAGE_READWRITE);printf("申请的内存地址:0x%x", lpAddr);system("pasue");return 0;
}

在这里插入图片描述
此时内存还未申请,我们用windbg查看一下当前进程的线程地址

在这里插入图片描述
回到程序让程序申请内存后我们再来看下
在这里插入图片描述

在这里插入图片描述
可以看到在我们没有分配内存时,0xbc0位置是没有分配的,可以看上面对应的属性和我们申请时填写的一致

堆与栈

那这个VirtualAlloc和我们在写c/c++程序时,用到的molloc/new关键字有什么区别呢,c/c++使用的申请是在当中申请的它们的低层实现是HeapAlloc,它是由操作系统提前通过VirtualAlloc申请好的一块内存空间,当使用molloc/new时,就会把申请好的地址给挂过去

代码测试

#include<iostream>
#include<Windows.h>int num = 0x789;int main() {printf("申请内存之前!");getchar();// 在栈上分配内存int stack = 0x123;// 在堆上分配内存int* heap = new int(0x456);printf("栈空间的地址:0x%x\n",&stack);printf("堆空间的地址:0x%x\n",heap);printf("全局变量的地址:0x%x\n", &num);system("pause");return 0;
}

在这里插入图片描述

在这里插入图片描述

可以发现在我们程序中无论是全局变量,还是堆空间,栈空间中的内存在程序运行时就已经存在了

可以发现全局变量是在我们的程序中的一个位置写死的


Mapped Memory

上面讲到Private Memory是推私有的,而Mapped Memory是共享的,可以是文件共享或者是物理页共享

在这里插入图片描述
在上图中,Mapped后面有对应文件路径的就是文件共享,反之就是物理页共享

代码测试

#include<iostream>
#include<windows.h>int main(){// 第一个参数如果提供一个文件的句柄,那么创建出来的就是文件映射,否则就是内存映射。HANDLE g_hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,BUFSIZ,L"共享内存");// 将物理页与线性地址进行关联LPTSTR g_lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile,FILE_MAP_ALL_ACCESS,0,0,BUFSIZ);*(PDWORD)g_lpBuff = 0x12345678;printf("A进程写入地址内容:%p - %x",g_lpBuff,*(PDWORD)g_lpBuff);system("pause");return 0;
}

在这里插入图片描述
我们再到windbg中遍历一下

在这里插入图片描述
可以看到B30的位置已经分配好了物理页,然后我们就可以在其他进程获取到这个创建好的内存空间

代码测试

#include <iostream>
#include <windows.h>int main() {HANDLE g_hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"共享内存");// 将物理页与线性地址进行映射LPTSTR g_lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);printf("B进程读取%x", *(PDWORD)g_lpBuff);system("pause");return 0;
}

在这里插入图片描述
可以看到我们成功的读取到了内容

共享文件

#include<iostream>
#include<windows.h>int main(){HANDLE g_hFile = CreateFile(L"newMemory.exe",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS, FILE_ATTRIBUTE_READONLY, NULL);HANDLE g_hMapFile = CreateFileMapping(g_hFile,NULL,PAGE_READWRITE,0,BUFSIZ,NULL);LPTSTR g_lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);printf("地址:0x%x",g_lpBuff);system("pause");return 0;
}

在这里插入图片描述

windbg中查看

在这里插入图片描述

可以看到已经成功的映射到了我们的文件上


写拷贝

可以看到这里的有一部分它的类型是EXECUTE_WRITECOPY,Mapped的后面还有一个Exe,这又是什么呢?
在这里插入图片描述

代码测试

#include<iostream>
#include<windows.h>int main(){LoadLibrary(L"C:\\Users\\win10x32\\Desktop\\gxnc.exe");system("pause");return 0;
}

在这里插入图片描述

可以看到当我们以LoadLibrary载入一个PE文件时,它的属性会被设置为EXECUTE_WRITECOPOY,所以我们看到的kernel32.dll,KernelBase.dll,其实都是操作系统用LoadLibrary一个个加载的,本质上没有任何区别,设置为EXECUTE_WRITECOPOY是因为当前系统环境有很多进程都在使用,也都可以对该文件进行修改,那这样以来,一但某一个进程修改了系统dll,那其他使用这个dll的进程就会出问题

http://www.xdnf.cn/news/17173.html

相关文章:

  • 前端百分比展示导致后端 BigDecimal 转换异常的排查与解决
  • 【数据库】如何从本地电脑连接服务器上的MySQL数据库?
  • 第二集 测试概念
  • 3a服务器的基本功能1之身份认证
  • 【ee类保研面试】数学类---概率论
  • 嵌入式硬件学习(十一)—— platform驱动框架
  • 基于 HT 引擎实现 3D 智慧物流转运中心一体化管控系统
  • 基于开源链动2+1模式AI智能名片S2B2C商城小程序的用户留存策略研究
  • 计算机基础·linux系统
  • 解决Git提交人信息默认全局化问题:让提交人自动关联当前用户
  • 阿里云部署若依后,浏览器能正常访问,但是apifox和小程序访问后报错链接被重置
  • 【保姆级喂饭教程】python基于mysql-connector-python的数据库操作通用封装类(连接池版)
  • 动态代理常用的两种方式?
  • 大疆无人机使用eport连接Jetson主板实现目标检测
  • 异构系统数据集成之数据源管理:打通企业数据孤岛的关键一步
  • TDengine IDMP 背后的技术三问:目录、标准与情景
  • ​ubuntu22.04系统入门 (四)linux入门命令 权限管理、ACL权限、管道与重定向
  • 思途AOP学习笔记 0806
  • day20|学习前端
  • 比特币量化模型高级因子筛选与信号生成报告
  • 数据大集网:以数据为纽带,重构企业贷获客生态的助贷平台实践
  • 重生之我在暑假学习微服务第十一天《配置篇》+网关篇错误订正
  • 【图像处理基石】什么是数字高程模型?如何使用数字高程模型?
  • HarmonyOS应用开发环境搭建以及快速入门介绍
  • Diamond基础1:认识Lattice器件
  • 【LeetCode 热题 100】347. 前 K 个高频元素——(解法三)桶排序
  • 接口——串口uart(485)
  • 常用排序方法
  • LeetCode 面试经典 150_数组/字符串_O(1)时间插入、删除和获取随机元素(12_380_C++_中等)(哈希表)
  • Java throw exception时需要重点关注的事情!