对于内存管理的方式有很多,内存也分为很多种,例如:
对于内存的分配函数也分为很多种,例如:new,malloc,Alloc, HeapAlloc;对于这些内存管理函数,其实都是封装了底层内存管理,然后对上层提供了相关的内存接口,最原始的内存管理函数应该是分为如下几个:
//分配内存
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess,
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
//释放内存
BOOL WINAPI VirtualFreeEx(
_In_ HANDLE hProcess,
_In_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD dwFreeType
);
//修改内存的属性
BOOL WINAPI VirtualProtect(
_In_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flNewProtect,
_Out_ PDWORD lpflOldProtect
);
//查询内存属性信息
SIZE_T WINAPI VirtualQueryEx(
_In_ HANDLE hProcess,
_In_opt_ LPCVOID lpAddress,
_Out_ PMEMORY_BASIC_INFORMATION lpBuffer,
_In_ SIZE_T dwLength
);
所有的上层内存函数,底层都是通过这些函数分配一个大块内存,然后在底层中,通过管理向上层提供小块内存操作接口。
在windows中,虚拟内存是针对进程的,每个进程的虚拟内存是不同的,因此在进程结构中存在管理虚拟内存的变量VadRoot
,如下:
0: kd> dt nt!_EPROCESS ffff9c84`80b133c0 -n VadRoot
nt!_EPROCESS
+0x658 VadRoot : _RTL_AVL_TREE
对于VadRoot的结构,每个系统的版本都存在不同,WRK中的结构信息如下:
typedef struct _MMVAD {
union {
LONG_PTR Balance : 2;
struct _MMVAD *Parent;
} u1;
struct _MMVAD *LeftChild;
struct _MMVAD *RightChild;
ULONG_PTR StartingVpn;
ULONG_PTR EndingVpn;
union {
ULONG_PTR LongFlags;
MMVAD_FLAGS VadFlags;
} u;
PCONTROL_AREA ControlArea;
PMMPTE FirstPrototypePte;
PMMPTE LastContiguousPte;
union {
ULONG LongFlags2;
MMVAD_FLAGS2 VadFlags2;
} u2;
} MMVAD, *PMMVAD;
这个是一个RVL树结构:
typedef struct _MMVAD_FLAGS {
ULONG_PTR CommitCharge : COMMIT_SIZE; // limits system to 4k pages or bigger!
ULONG_PTR NoChange : 1;
ULONG_PTR VadType : 3;
ULONG_PTR MemCommit: 1;
ULONG_PTR Protection : 5;
ULONG_PTR Spare : 2;
ULONG_PTR PrivateMemory : 1; // used to tell VAD from VAD_SHORT
} MMVAD_FLAGS;
typedef struct _CONTROL_AREA {
PSEGMENT Segment;
LIST_ENTRY DereferenceList;
ULONG NumberOfSectionReferences; // All section refs & image flushes
ULONG NumberOfPfnReferences; // valid + transition prototype PTEs
ULONG NumberOfMappedViews; // total # mapped views, including
// system cache & system space views
ULONG NumberOfSystemCacheViews; // system cache views only
ULONG NumberOfUserReferences; // user section & view references
union {
ULONG LongFlags;
MMSECTION_FLAGS Flags;
} u;
PFILE_OBJECT FilePointer; //文件对象,例如dll,sys等等
PEVENT_COUNTER WaitingForDeletion;
USHORT ModifiedWriteCount;
USHORT FlushInProgressCount;
ULONG WritableUserReferences;
#if !defined (_WIN64)
ULONG QuadwordPad;
#endif
} CONTROL_AREA, *PCONTROL_AREA;
我们知道,对于一个进程,所有的模块都会被映射到内存空间中,所以遍历VadRoot
中的CONTROL_AREA
文件对象就可以遍历到所有加载的模块信息了。
如果我们希望别人看不到我们的内存,那么我们隐藏VadRoot
中的结构就可以到达目的。
在windbg中,提供了!vad
命令来查看vad内存信息,查看结构如下:
0: kd> dt nt!_EPROCESS ffff9c84`af3980c0 -n VadRoot
+0x658 VadRoot : _RTL_AVL_TREE
0: kd> dx -id 0,0,ffff9c8480b133c0 -r1 (*((ntkrnlmp!_RTL_AVL_TREE *)0xffff9c84af398718))
(*((ntkrnlmp!_RTL_AVL_TREE *)0xffff9c84af398718)) [Type: _RTL_AVL_TREE]
[+0x000] Root : 0xffff9c84b16d61c0 [Type: _RTL_BALANCED_NODE *]
0: kd> !vad 0xffff9c84af398718
VAD Level Start End Commit
ffff9c84b1ac5770 7 7ffe0 7ffe0 1 Private READONLY
ffff9c84b1ac58b0 6 7ffe6 7ffe6 1 Private READONLY
ffff9c84b1ac5220 8 72ae870 72ae8ef 11 Private READWRITE
ffff9c84aaf468e0 7 72ae8f0 72ae96f 11 Private READWRITE
ffff9c84b1ac5a90 5 72aea00 72aebff 5 Private READWRITE
ffff9c84a8a5d0c0 7 1eeb0400 1eeb040f 0 Mapped READWRITE Pagefile section, shared commit 0
ffff9c84a8a5d980 8 1eeb0410 1eeb0417 0 Mapped READWRITE Pagefile section, shared commit 0
ffff9c84ac05e4f0 6 1eeb0420 1eeb043a 0 Mapped READONLY Pagefile section, shared commit 0
ffff9c849b3440d0 7 1eeb0440 1eeb0443 0 Mapped READONLY Pagefile section, shared commit 0
ffff9c84b1ac5360 4 1eeb0450 1eeb0451 2 Private READWRITE
ffff9c84af58e410 8 1eeb0460 1eeb0491 4 Private READWRITE
ffff9c84af591b60 7 1eeb04a0 1eeb04d1 1 Private READWRITE
ffff9c84a8a5d700 8 1eeb04e0 1eeb04e0 0 Mapped READONLY Pagefile section, shared commit 0
ffff9c84a8a5d7a0 6 1eeb04f0 1eeb04f0 0 Mapped READONLY Pagefile section, shared commit 0
ffff9c84a8a5d840 8 1eeb0500 1eeb050d 0 Mapped READONLY \Windows\servicing\CbsMsg.dll
ffff9c84af58a0e0 7 1eeb0510 1eeb060f 255 Private READWRITE
ffff9c84a8a5cc60 5 1eeb0610 1eeb06d6 0 Mapped READONLY \Windows\System32\locale.nls
ffff9c84a8a5ea60 9 1eeb06e0 1eeb06e7 0 Mapped READONLY Pagefile section, shared commit 0
ffff9c84a8a5ec40 8 1eeb06f0 1eeb07b0 0 Mapped READONLY Pagefile section, shared commit 0
ffff9c84af597ba0 7 1eeb07c0 1eeb07cf 8 Private READWRITE
ffff9c84b16d7b10 9 1eeb07d0 1eeb07df 9 Private READWRITE
//...
ffff9c84a8a5e2e0 9 7fff30380 7fff3039e 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\profapi.dll
ffff9c84a8a5dde0 6 7fff303a0 7fff303b0 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\kernel.appcore.dll
ffff9c84a8a5eba0 9 7fff303c0 7fff30409 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\powrprof.dll
ffff9c84a8a5fbe0 8 7fff30410 7fff305a3 8 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\gdi32full.dll
ffff9c84a8a5ce40 7 7fff305b0 7fff3064d 7 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\msvcp_win.dll
ffff9c84a8a5c9e0 8 7fff30650 7fff30749 4 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ucrtbase.dll
ffff9c84a8a5d200 4 7fff30750 7fff309f2 9 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\KernelBase.dll
ffff9c84a8a5dac0 8 7fff30a00 7fff30b48 10 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\crypt32.dll
ffff9c84a8a5e9c0 9 7fff30b50 7fff30b70 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\win32u.dll
ffff9c84a8a5e240 7 7fff30b80 7fff30bdb 4 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\wintrust.dll
ffff9c84a8a5e560 8 7fff30be0 7fff30c29 5 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\cfgmgr32.dll
ffff9c84a8a5cd00 6 7fff30c30 7fff30caf 2 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\bcryptprimitives.dll
ffff9c84a8a5e060 8 7fff30d60 7fff30d76 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\cryptsp.dll
ffff9c84a8a5db60 9 7fff30d80 7fff30da5 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\bcrypt.dll
ffff9c84a8a5d160 7 7fff31590 7fff31626 6 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\sechost.dll
ffff9c84a8a5d3e0 9 7fff31630 7fff316cd 10 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\msvcrt.dll
ffff9c84a8a5f6e0 8 7fff316d0 7fff31863 6 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\user32.dll
ffff9c84a8a5ca80 9 7fff31900 7fff31a1f 5 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\rpcrt4.dll
ffff9c84a8a5c8a0 5 7fff31bd0 7fff31c81 5 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\kernel32.dll
ffff9c84a8a5c300 7 7fff31c90 7fff31fc5 9 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\combase.dll
ffff9c84a8a5e7e0 8 7fff31fd0 7fff32072 8 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\advapi32.dll
ffff9c84a8a5d2a0 6 7fff32110 7fff321d3 6 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\oleaut32.dll
ffff9c84a8a5e600 8 7fff32550 7fff326a5 6 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ole32.dll
ffff9c84a8a5e6a0 9 7fff32da0 7fff32dc5 4 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\gdi32.dll
ffff9c84a8a5e740 7 7fff332c0 7fff33361 9 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\clbcatq.dll
ffff9c84a8a5fdc0 9 7fff33370 7fff333de 4 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ws2_32.dll
ffff9c84ac05d5f0 8 7fff33480 7fff3366f 17 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ntdll.dll
我们调用VirtualAlloc
分配内存的时候,底层就会通过NtAllocateVirtualMemory
分配虚拟内存,这个函数的基本流程如下:
NTSTATUS
NtAllocateVirtualMemory(
__in HANDLE ProcessHandle,
__inout PVOID *BaseAddress,
__in ULONG_PTR ZeroBits,
__inout PSIZE_T RegionSize,
__in ULONG AllocationType,
__in ULONG Protect
)
{
//...
PMMVAD Vad;
Vad = ExAllocatePoolWithTag (NonPagedPool, sizeof(MMVAD_SHORT), 'SdaV');
Status = MiFindEmptyAddressRange (CapturedRegionSize,
Alignment,
(ULONG)ZeroBits,
&StartingAddress);
Status = MiInsertVadCharges (Vad, Process);
}
这里创建一个内存管理节点,然后保存内存信息,并插入到进程的用户内存空间中。
上面的VirtualAlloc
分配了一个内存区,插入到了进程的用户内存表中,但是,我们知道可以使用VirtualProtect
来改变其中某些页面的属性。
因此对于一个MMVAD
还会形成一系列的Region,每个Region的属性是不同的,所有的Region组成了MMVAD
,修改属性的函数为MiProtectVirtualMemory
,在Reactos中,这个实现过程如下:
至于MmSplitRegion
这个函数,是修改其中某块的属性,然后针对属性进行修改,如果修改过后的属性和其他相邻的Region相同,则形成合并(这里是Reactos的代码,但是原理上面来说,应该都差不多)。
从上面的分析,我们知道可以直接使用如下函数枚举进程的模块
NTSTATUS ZwQueryVirtualMemory(
_In_ HANDLE ProcessHandle,
_In_opt_ PVOID BaseAddress,
_In_ MEMORY_INFORMATION_CLASS MemoryInformationClass,
_Out_ PVOID MemoryInformation,
_In_ SIZE_T MemoryInformationLength,
_Out_opt_ PSIZE_T ReturnLength
);
typedef enum _MEMORY_INFORMATION_CLASS {
MemoryBasicInformation
#if DEVL
,MemoryWorkingSetInformation
#endif
,MemoryMappedFilenameInformation
,MemoryRegionInformation
,MemoryWorkingSetExInformation
} MEMORY_INFORMATION_CLASS;
更加底层的,我们可以直接枚举进程空间中的内存获取模块信息。
既然我们可以通过VadRoot获取进程模块,那么通过同样的方法,我们也可以通过这里进行隐藏,具体实现可以参考网上的方法,但是这种方法不是特别稳定应当慎重使用。
很容易,如果我们将MMVDA中的内存长度进行修改,就会导致内存隐藏,具体实现参考网上的一些实现方法,但是这种方法也不是稳定的方案,应当慎重使用。