恶意软件开发——内存相关API
一、前言
Windows操作系统的内存有三种属性,分别为:可读、可写、可执行,并且操作系统将每个进程的内存都隔离开来,当进程运行时,创建一个虚拟的内存空间,系统的内存管理器将虚拟内存空间映射到物理内存上,所以每个进程的内存都是等大的。
操作系统给予每个进程申请内存的权利,使用不同的API,申请的内存具有不同的涵义。
在进程申请时,需要声明这块内存的基本信息:申请内存大小、申请内存起始内存基址、申请内存属性、申请内存对外的权限等。
二、相关API介绍
1.VirtualAlloc
该函数的功能是在调用进程的虚地址空间,预定或者提交一部分页,如果用于内存分配的话,并且分配类型未指定MEM_RESET,则系统将自动设置为0;其函数原型:
LPVOID VirtualAlloc{
LPVOID lpAddress, // 要分配的内存区域的地址
DWORD dwSize, // 分配的大小
DWORD flAllocationType, // 分配的类型
DWORD flProtect // 该内存的初始保护属性
};
参数说明:
1.LPVOID lpAddress, 分配内存区域的地址。当你使用VirtualAlloc来提交一块以前保留的内存块的时候,lpAddress参数可以用来识别以前保留的内存块。如果这个参数是NULL,系统将会决定分配内存区域的位置,并且按64-KB向上取整(roundup)。
2.SIZE_T dwSize, 要分配或者保留的区域的大小。这个参数以字节为单位,而不是页,系统会根据这个大小一直分配到下页的边界DWORD
3.flAllocationType, 分配类型 ,你可以指定或者合并以下标志:MEM_COMMIT,MEM_RESERVE和MEM_TOP_DOWN。
4.DWORD flProtect 指定了被分配区域的访问保护方式:PAGE_EXECUTE_READ,PAGE_EXECUTE_READWRITE
小结:
VirtualAlloc可以通过并行多次调用提交一个区域的部分或全部来保留一个大的内存区域。多重调用提交同一块区域不会引起失败。这使得一个应用程 序保留内存后可以随意提交将被写的页。当这种方式不在有效的时候,它会释放应用程序通过检测被保留页的状态看它是否在提交调用之前已经被提交。
VirtualAlloc对应的释放函数为VirtualFree。
2.HeapAlloc
该函数是从堆上分配一块内存,且分配的内存是不可移动的(即如果没有连续的空间能满足分配的大小,程序不能将其他零散的空间利用起来,从而导致分配失败)。该分配方法是从一指定地址开始分配,而不像GloabalAlloc是从全局堆上分配,这个有可能是全局,也有可能是局部,其函数原型:
LPVOID HeapAlloc(
HANDLE hHeap, //要分配堆的句柄
DWORD dwFlags, //堆分配时的可选参数
SIZE_T dwBytes, //要分配堆的字节数
);
参数说明:
1.HANDLE hHeap,要分配堆的句柄,可以通过HeapCreate()函数或GetProcessHeap()函数获得。
2.DWORD dwFlags,堆分配时的可选参数,其值可以为以下的一种或多种:HEAP_GENERATE_EXCEPTIONS(如果分配错误将会抛出异常,而不是返回NULL。异常值可能是STATUS_NO_MEMORY, 表示获得的内存容量不足,或是STATUS_ACCESS_VIOLATION,表示存取不合法),HEAP_NO_SERIALIZE(不使用连续存取),HEAP_ZERO_MEMORY(将分配的内存全部清零)。
3.SIZE_T dwBytes,要分配堆的字节数。
小结:
hHeap是进程堆内存开始位置,dwFlags是分配堆内存的标志。包括HEAP_ZERO_MEMORY,即使分配的空间清零,dwBytes是分配堆内存的大小,其对应的释放空间函数为HeapFree。
3.GlobalAlloc
该函数用于从全局堆中分配出内存供程序使用,函数原型为:
HGLOBALGlobalAlloc(
UINTuFlags, // 分配属性(方式)
DWORDdwBytes, // 分配的字节数
);
参数说明:
1.UINTuFlags,指定如何分配内存,若指定为0,则是默认的GMEM_FIXED.这个值可以是下面其中一个或几个位标识(那些指明不兼容的组合除外),标识:GHND(为GMEM_MOVEABLE 和 GMEM_ZEROINIT的组合),GMEM_FIXED(分配固定的内存,返回值是一个指针),GMEM_MOVEABLE(分配可移动的内存,在Win32中内存块在物理内存中是不可移动的,但在缺省堆中可以. 返回值是该内存对象的句柄,可使用函数 GlobalLock 将该句柄转换为一个指针,这个标识不能与 GMEM_FIXED 组合使用),GMEM_ZEROINIT(将所申请内存初始化为0),GPTR(为GMEM_FIXED和GMEM_ZEROINIT组合)。
2.DWORDdwBytes,指定要申请的字节数.若该参数为 0 且参数 uFlags 指定为 GMEM_MOVEABLE 则该函数返回一个内存对象的句柄,该内存对象被标识为discarded(可抛弃的)
小结:
使用此函数分配内存可以保证8字节的边界.所有的内存均在执行访问时创建;不需要特别的函数来动态执行所产生的代码,若函数调用成功,将至少分配所需内存.若实际分配量超过所需,则内存仍然能够充分利用之.可用函数 GlobalSize 来确定实际所分配的字节数,可使用 GlobalFree 来释放内存。
4.LocalAlloc
该函数用于从局部堆中分配内存供程序使用,函数原型为:
HLOCAL LocalAlloc(
UINT uFlags, //指定怎样去分配内存
UINT uBytes, //指定要分配的字节数
);
参数说明:
1.uFlags,指定怎样去分配内存。如果zero被指定,默认的是LMEM_FIXED标志。此参数有三种标志:LMEM_FIXED(分配固定内存,返回值是指向一个内存对象的指针),LMEM_ZEROINIT(初始化内存内容为zero),LPTR(结合了LMEM_FIXED和LMEM_ZEROINIT这两种标志),LMEM_MOVEABLE(分配可移动内存),LMEM_DISCARDABLE(分配可删除的内存)。
2.uBytes,指定要分配的字节数。
小结:
在16位Windows中是有区别的,因为在16位windows用一个全局堆和局部堆来管理内存,每一个应用程序或dll装入内存时,代码段被装入全局 堆,而系统又为每个实例从全局堆中分配了一个64kb的数据段作为该实例的局部堆,用来存放应用程序的堆栈和所有全局或静态变量。而 LocalAlloc/GlobalAlloc就是分别用于在局部堆或全局堆中分配内存。
由于每个进程的局部堆很小,所以在局部堆中分配内存会受到空间的限制。但这个堆是每个进程私有的,相对而言分配数据较安全,数据访问出错不至于影响到整个系统。
而在全局堆中分配的内存是为各个进程共享的,每个进程只要拥有这个内存块的句柄都可以访问这块内存,但是每个全局内存空间需要额外的内存开销,造成分配浪费。而且一旦发生严重错误,可能会影响到整个系统的稳定。
不过在Win32中,每个进程都只拥有一个省缺的私有堆,它只能被当前进程访问。应用程序也不可能直接访问系统内存。所以在Win32中全局堆和局部堆都 指向进程的省缺堆。用LocalAlloc/GlobalAlloc分配内存没有任何区别。甚至LocalAlloc分配的内存可以被 GlobalFree释放掉。所以在Win32下编程,无需注意Local和Global的区别,一般的内存分配都等效于 HeapAlloc(GetProcessHeap(),…)。
LocalAlloc对应的释放函数为LockFree。
5.Malloc
malloc与free是C++/C语言的标准库函数,可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用 malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是 库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
6.New
是C++的运算符,主要用来新建类,与C++的构造函数和异常机制有关,与上述其它函数的使用环境大相庭径。一般编译器中的new都是用malloc来分配内存的。
New对应的释放函数为delete。
三、拷贝/复制内存函数
1.memcpy
函数原型:void *memcpy(void *destin, void *source, unsigned n);
参数:
- destin– 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
- source– 指向要复制的数据源,类型强制转换为 void* 指针。
- n– 要被复制的字节数。
2.CopyMemory
函数原型:
VOID CopyMemory(
PVOID Destination,
CONST VOID *Source,
SIZE_T Length
);
参数:
- Destination– 要复制内存块的目的地址。
- Source– 要复制内存块的源地址。
- Length– 指定要复制内存块的大小,单位为字节
3.RtlCopyMemory
同CopyMemory一样
函数原型:
void RtlCopyMemory(
void* Destination,
const void* Source,
size_t Length
);
参数:
- Destination– 要复制内存块的目的地址。
- Source– 要复制内存块的源地址。
- Length– 指定要复制内存块的大小,单位为字节
4.RtlMoveMemory
函数原型:
VOID RtlMoveMemory(
VOID UNALIGNED *Destination,
const VOID UNALIGNED *Source,
SIZE_T Length
);
参数:
Destination– 指向移动目的地址的指针。
Source– 指向要复制的内存地址的指针。
Length– 指定要复制的字节数。