基础学习记录——内存及内存操作

内存

这里主要关注虚拟内存,物理内存我们不太需要考虑,都有系统来自动分配。

物理内存和虚拟内存

物理内存就是我们使用的内存条中的实际内存,内存条的容量也就是系统中物理内存的容量。比如一条8GB的内存条就是8GB的内存容量。

虚拟内存是内存管理的一种技术,为每个进程分配一块连续的虚拟内存,实际上这些虚拟内存并不是一定存在于物理内存中,系统会匀出一部分硬盘空间来充当内存使用,你可以找到你的硬盘空间中一个名为PageFile.Sys的文件,这个就是系统将硬盘的一部分充当为虚拟内存的硬盘空间。

进程的内存

Windows为每个32位进程分配了4GB的虚拟地址空间。每个进程都拥有4GB的内存空间, 4GB的大小是因为:32位 CPU可以取地址的空间为2的32次方,就是4GB大小。而64位进程, CPU可以取地址的空间为2的64次方,所以64位进程的虚拟地址空间大小为16EB。

每个进程都有自己专用的内存空间,与其他进程所拥有的内存之间是相互隔离开的,每个进程所创建的线程都只能访问它所在进程的内存。

当一个进程被创建时,系统会为该进程分配4GB的虚拟内存,那么地址空间就是0x00000000~0xFFFFFFFF,其中2GB的内存是该进程独有的,用于存放程序的代码、数据、堆栈等,另外2GB用于共享系统的使用。

当进程操作内存时,系统会从物理内存中对进程所申请的内存进行映射,将进程的虚拟内存映射为物理内存,再进行内存空间的使用。

每个进程有2GB的虚拟内存,但是当进程没有使用这些内存空间时,这2GB的内存通常被分割成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,只有需要用到这部分内存的时候,才会装载到物理内存中进行数据的交换。

实地址和虚地址

进程所使用的内存空间,例如打印指针所显示的对应的内存地址,其实是虚地址,也被称为逻辑地址,其对用的存储空间称为虚存空间或逻辑地址空间;而物理内存的地址则成为实地址或物理地址,其对应的存储空间成为物理存储空间或主存空间。程序进行虚地址到实地址转换的过程称为程序的再定位。

虚拟内存访问过程

虚拟内存按照进程的虚地址存放在辅存种,当程序运行时,系统会根据创建进程时给进程分配的实地址将程序的一部分调入物理内存。进程每次访问数据时,系统首先会判断该虚拟地址是否在物理内存中有实际映射的地址,如果有,则进行相应的地址转换并使用实地址访问主存,如果没有,则将辅存中的相应部分程序映射到物理地址,再使用实地址访问主存。

内存的属性

Windows操作系统的内存有三种属性,分别为:可读、可写、可执行。

内存操作

C语言内存管理函数

C语言中提供了一些操作内存的函数,如下:

void *calloc(int num, int size);
在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。

void free(void *address);
该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。

void *malloc(int num);
在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。

void *realloc(void *address, int newsize);
该函数重新分配内存,把内存扩展到 newsize

以上函数都比较容易理解,并且申请内存或者指定内存的函数返回值都是指针形式,所以我们接收返回值时也需要使用指针变量去接受,例如如下示例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char *p;
    p=(char *)calloc(5,20);
    printf("%p",p);
}

上述代码使用calloc函数申请了5块连续的长度为20字节的内存空间,并且每个字节的值都为0。

但是并没有平常在shellcodeloader中看到的申请内存的函数,例如VirtualAlloc、CopyMemory等,其实这些函数并不是C语言中的函数,而是Windows API函数,接着看一下WindowsAPI函数的用法。

内存相关的WindowsAPI

WindowsAPI

WindowsAPI是Windows操作系统提供的系统编程接口,开发者在编程时可以通过直接调用这些WindowsAPI函数来操作Windows系统中的内存、资源等。

内存相关API

内存保护属性和存取权限
属性常量 意义
PAGE_EXECUTE 可执行
PAGE_EXECUTE_READ 可读,可执行
PAGE_EXECUTE_READWRITE 可读,可写,可执行
PAGE_EXECUTE_WRITECOPY 可读,可写,可执行,以Read-on-write和copy-on-write方式共享
PAGE_NOACCESS 不可访问
PAGE_READONLY 只读
PAGE_READWRITE 可读,可写
PAGE_WRITECOPY copy-on-write保护机制
PAGE_GUARD 保护,如果访问则异常(不能单独使用)
PAGE_NOCACHE 不进行CPU缓存(不能单独使用)
PAGE_WRITECOMBINE write-combined优化(不能单独使用)

堆管理API
HeapCreate 为进程创建一个堆,返回一个堆句柄
GetProcessHeap 获取当前进程中的一个堆
HeapAlloc 从指定堆上分配内存块
HeapReAlloc 重新分配内存,改变已经分配好的堆内存块大小
GetSystemInfo 获取系统信息
HeapSize 获取指定堆大小,以字节为单位返回堆大小信息
HeapFree 释放HeapAlloc和HeapReAlloc所分配的内存
HeapDestroy 销毁由HeapCreate创建的堆
LocalAlloc 从堆中分配指定大小的字节数

全局和局部内存管理API函数
GlobalAlloc 在默认堆上分配指定属性的大小的内存
GlobalFree 释放由GlobalAlloc分配的内存
GlobalReAlloc 重新分配内存,改变已经分配好的内存块大小
GlobalLock 将GlobalAlloc及GlobalReAlloc分配属性为GMEM_MOVEABLE内存块设为固定
GlobalHandle 与GlobalLock相对
GlobalSize 获取内存大小

虚拟内存管理API函数
VirtualAlloc "保留"或"提交"内存页面,将"空闲的"内存页面变为"保留的"或"已经提交的",保留的改为已提交的,简单来说就是申请一块内存
VirtualAllocEx 类似VirtualAlloc功能,不过可以为其他进程分配内存
VirtualFree 将内存状态从"已经提交的"变为"保留的"
VirtualFreeEx 释放VirtualAllocEx分配的内存,功能,使用方法和VirtualFree类似
VirtualProtect 改变指定虚拟内存分页的保护属性

内存操作与内存信息管理API函数
CopyMemory 复制内存,第一个参数为目的地址,第二个参数为源地址,第三个参数为复制数据大小
FillMemory 填充内存,将一段内存填充为同一个值
MoveMemory 功能和CopyMemory类似,不同的是源地址和目的地址可以相同
ZeroMemory 将指定内存区域清零
GlobalMemoryStatusEx 用于获取系统当前内存使用情况
IsBadCodePtr 判断调用进程是否拥有对指定地址内存的读操作权限
IsBadReadPtr 判断调用进程是否有对指定地址段内存的读操作权限
ISBadStringPtr 判断调用进程是否拥有对字符串指针的读取权限
IsBadWritePtr 判断调用进程是否拥有对指定地址段内存的写操作权限
RtlCopyMemory 同CopyMemory一样
RtlMoveMemory 从指定内存中复制内存至另一内存里,和CopyMemory一样
RtlCopyBytes 复制给定的字节数从一个位置到另一个地方,和RtlCopyMemory一样

其他

ReadProcessMemory //一个内存操作函数, 其作用为根据进程句柄读入该进程的某个内存空间
WriteProcessMemory //此函数能写入某一进程的内存区域(直接写入会出Access Violation错误),故需此函数入口区必须可以访问,否则操作将失败
CreateFileMapping //创建一个内存文件映射对象
MapViewOfFile 将内存映射文件映射到进程的虚拟地址中
OpenFileMapping 打开一个命名的文件映射内核对象