avatar

Catalog
Private Memory

前言

对于内存空间有两种描述方式,一种是物理内存的角度,所有地址只分为两类,挂了物理页的地址与没有挂物理页的地址,其属性由PDE/PTE决定。另一种是线性地址的角度,分为私有内存与映射内存。前者,在介绍段、页时已有了解,近来这几篇主要是从线性地址的角度,来学习内存空间。本篇就来学习其中的一种类型,私有内存。

私有内存与映射内存

在前一篇用指令打印Vad树时,可以看到,同一个进程中,就存在私有内存(Private)与映射内存(Mapped)两种类型的内存空间。

这两类内存的区别主要有2点不同:

  • 申请内存的方式不同:
    • 私有内存:通过VirtualAlloc/VirtualAllocEx申请的。
    • 映射内存:通过CreateFileMapping映射的。
  • 使用方式不同:
    • 私有内存:独享物理页。
    • 映射内存:可能要与其它进程共享物理页。

VirtualAlloc

函数原型

本篇主要介绍私有内存,就先从它的申请内存函数开始。首先是VirtualAlloc的函数原型:

c
1
2
3
4
5
6
LPVOID VirtualAlloc{
LPVOID lpAddress, // 要分配的内存区域的地址
DWORD dwSize, // 分配的大小
DWORD flAllocationType, // 分配的类型
DWORD flProtect // 该内存的初始保护属性
};

这4个参数都挺重要的,除注释外,再额外介绍一下:

  • lpAddress:申请内存地址,自然要有起点,终点,这个参数就是指申请地址的起点。结合第二个参数dwSize,例如申请起始地址0x123000,大小0x1000,那么就会给你分配0x123000~0x123fff这个物理页。但是如果这块内存已经被占用了,自然就无法申请了,由于需要查找Vad树才能知道这块地址是否被占用,过程较为麻烦,因此,通常来说,这个值填NULL,让系统自动去分配一块没被占用的内存地址。
  • dwSize:就是想要分配的大小,如果提供了lpAddress,那么lpAddress就是地址的起点,lpAddress+dwSize-1就是地址的终点。这个值必须是0x1000(4KB,即一个物理页)的整数倍。
  • flAllocationType:分配的类型,有两种:
    • MEM_COMMIT:创建节点并分配物理页。
    • MEM_RESERVE:只创建节点,不分配物理页。
  • flProtect:内存的初始保护属性,例如READWRITE,READONLY这样的。

与VirtualAllocEx的差异

还有一个类似的函数VirtualAllocEx,参数什么的都和VirtualAlloc一样,唯一不同的VirtualAllocEx可以跨进程申请内存,VirtualAlloc只能在当前进程中申请内存。

VirtualAlloc实验

下面用一个实验来了解VirtualAlloc申请内存的过程:

  1. 编译运行如下代码(环境:Windows XP,编译器:VC++6.0)

    c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include "stdafx.h"
    #include

    int main(int argc, char* argv[])
    {
    printf("申请内存前\n");
    getchar();

    LPVOID address = VirtualAlloc(NULL, 0x3000, MEM_COMMIT, PAGE_READWRITE);

    printf("内存地址:%X\n", address);
    getchar();
    return 0;
    }
  2. 第一次运行时,会在第一个getchar()处停下,此时进入Windbg查看当前进程的Vad树

  3. 在用VirtualAlloc函数申请完内存后,再来观察Vad树,情况就有些不一样了

    可以看到,在执行完VirtualAlloc后,0x3a0000~0x3a2fff处新分配了一个大小为0x3000的内存空间,这在执行前是没有的,同时Vad树的总结点树也增加了1。同时新分配的内存空间,属性也是对应了VirtualAlloc的各个参数。

    这样,使用VirtualAlloc函数申请内存的过程就好理解了,它会在低2G中,还没有使用的内存空间中,分配一个指定大小的私有内存空间,然后将其对应的_MMVAD结构添加到创建它进程的Vad树中

堆与栈

malloc与new

文章开头提到,对于以线性地址为角度描述内存时,分为私有内存与映射内存。申请私有内存的函数为VirtualAlloc/VirtualAllocEx,申请映射内存的函数为CreateFileMapping。那么问题了,曾经我们在C语言中学习到的malloc函数,与C++中学习的new函数,难道就不能申请内存吗。这部分,就来讨论一下这个问题。

在初级班,海哥曾经说过malloc与new的底层调用过程,具体如下:

Code
1
malloc -> _nh_malloc_dbg -> _heap_alloc_dbg -> _heap_alloc_base -> HeapAlloc
Code
1
new -> _nh_malloc -> _nh_malloc_dbg -> _heap_alloc_dbg -> _heap_alloc_base -> HeapAlloc

可以看到,malloc和new本质都是一样的,底层都调用了HeapAlloc这么一个函数。接下来就来看看这个函数。

HeapAlloc与堆

HeapAlloc作用是在堆中分配内存的一个函数,但是它真正申请内存了吗?其实并没有,HeapAlloc函数甚至没有进入0环,一个没有进入0环的函数,它自然没有权限去申请内存。既然没法申请内存,那么HeapAlloc又是如何在堆中分配内存的呢?

这里就要了解一下堆的概念,什么是堆呢?堆其实就是操作系统通过调用VirtualAlloc函数预先分配好的一大块内存。HeapAlloc的作用就是在这一大块已经预先分配好的内存里面,分一些小份出来用。作个比喻,可以认为VirtualAlloc就是批发市场,一次必须批量从操作系统那里购买内存,必须是4KB的整数倍才可以;而HeapAlloc就是零售商,从VirtualAlloc已经批来的货里面(堆)买一部分走。

前面说到堆是OS调用VirtualAlloc预先分配好的一块内存,那么栈是什么呢?栈其实和堆一样,也是预先分配好的内存,但是栈甚至不需要HeapAlloc分配,就可以直接使用。最常见的,就是局部变量了。参考接下来的实验。

堆栈实验

下面就来做一个实验,观察进程是如何使用堆,栈的空间的。

  1. 编译运行如下代码(环境:Windows XP,编译器:VC++6.0)

    c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include "stdafx.h"
    #include

    int z = 0x6666;

    int main(int argc, char* argv[])
    {
    int x = 0x12345678;

    int* y = (int*)malloc(sizeof(int)*128);

    printf("栈:%x \n",&x);
    printf("堆:%x \n",y);
    printf("全局变量:%x\n", &z);

    getchar();
    return 0;
    }
  2. 运行后,分别打印全局变量的地址,局部变量的地址,以及malloc申请的内存首地址。

  3. 然后分别在局部变量初始化前后,以及malloc调用前后,观察Windbg中查看当前进程的Vad树

    会发现,无论是全局变量,局部变量,或者调用malloc函数,它都没有分配新的内存空间,只不过是使用了当前进程已有的内存空间

    不过还是有几个需要说明一下,比如局部变量的地址是0x12ff7c,而它所在的内存块的范围是0x3000~0x12ffff,主要原因是,栈是从高地址向低地址延申的,因此刚开始使用的地址都是当前内存块的高地址。堆的话,就是直接使用了一块已有的内存,可以回想之前批发商与零售商的例子。全局变量,就比较与众不同了,它映射了当前进程的.exe文件,这部分下篇学习映射内存时会讲到。

总结

  1. VirtualAlloc/VirtualAllocEx是申请私有内存的唯一方式。
  2. new与malloc的内部调用是HeapAlloc,HeapAlloc不会进入0环,也不会申请内存,仅能分配一些已经经过VirtualAlloc/VirtualAllocEx申请好的内存。

参考资料

参考教程:

  • 海哥逆向中级预习班

参考链接:

Author: cataLoc
Link: http://cataloc.gitee.io/blog/2020/08/31/Private-Memory/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶