NtQueryVirtualMemory是windows的一个未公开API(导出但未形成文档),他的作用主要是查询指定进程的某个虚拟地址控件所在的内存对象的一些信息。
原型(prototype):
NTSTATUS NTAPI NtQueryVirtualMemory(
IN HANDLE ProcessHandle, //目标进程句柄
IN PVOID BaseAddress, //目标内存地址
IN MEMORY_INFORMATION_CLASS MemoryInformationClass, //查询内存信息的类别
OUT PVOID Buffer, //用于存储获取到的内存信息的结构地址
IN ULONG Length, //Buffer的最大长度
OUT PULONG ResultLength OPTIONAL); //存储该函数处理返回的信息的长度的ULONG的地址
第一个参数是目标进程的句柄,第二个参数是要查询的内存地址,第五个和第六个参数为Buffer长度,和函数处理结果返回的长度。
第三个参数类型MEMORY_INFORMATION_CLASS是一个枚举类型其定义如下:
//MEMORY_INFORMATION_CLASS定义
typedef enum _MEMORY_INFORMATION_CLASS
{
MemoryBasicInformation, //内存基本信息
MemoryWorkingSetInformation, //工作集信息
MemoryMappedFilenameInformation //内存映射文件名信息
} MEMORY_INFORMATION_CLASS;
第四个参数是根据第三个参数选用不同的结构去接收内存信息的地址。
其对应关系如下:
0x00:使用MemoryBasicInformation时,Buffer应当指向的结构为MEMORY_BASIC_INFORMATION,其定义如下:
typedef struct _MEMORY_BASIC_INFORMATION {
PVOID BaseAddress;
PVOID AllocationBase;
DWORD AllocationProtect;
SIZE_T RegionSize;
DWORD State;
DWORD Protect;
DWORD Type;
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
0x01:使用MemoryWorkingSetInformation时,Buffer应当指向的结构为MEMORY_WORKING_SET_INFORMATION,其定义如下:
typedef struct _MEMORY_WORKING_SET_INFORMATION {
ULONG SizeOfWorkingSet;
DWORD WsEntries[ANYSIZE_ARRAY];
} MEMORY_WORKING_SET_INFORMATION, *PMEMORY_WORKING_SET_INFORMATION;
0x02:当使用MemoryMappedFilenameInformation 时,Buffer应当指向结构为MEMORY_MAPPED_FILE_NAME_INFORMATION,其定义如下:
#define _MAX_OBJECT_NAME 1024/sizeof(WCHAR)
typedef struct _MEMORY_MAPPED_FILE_NAME_INFORMATION {
UNICODE_STRING Name;
WCHAR Buffer[_MAX_OBJECT_NAME];
} MEMORY_MAPPED_FILE_NAME_INFORMATION, *PMEMORY_MAPPED_FILE_NAME_INFORMATION;
第一种和第二种没有太多需要解释的地方,至于第三种的MEMORY_MAPPED_FILE_NAME_INFORMATION的定义形式需要说明一下,第三种使用方法目前资料很少,我看多资料都是直接直接传了个数组进去,然后很多人就发表评论说为什么要传那个长度呢?为什么不可以传这个长度呢?无人回答……那好我们自己来找找标准用法吧。
翻阅了一下NT2k的源码,看看这个函数的实现:
NTSTATUS
NtQueryVirtualMemory (
IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
OUT PVOID MemoryInformation,
IN ULONG MemoryInformationLength,
OUT PULONG ReturnLength OPTIONAL
)
/*++
Routine Description:
This function provides the capability to determine the state,
protection, and type of a region of pages within the virtual address
space of the subject process.
The state of the first page within the region is determined and then
subsequent entries in the process address map are scanned from the
base address upward until either the entire range of pages has been
scanned or until a page with a nonmatching set of attributes is
encountered. The region attributes, the length of the region of pages
with matching attributes, and an appropriate status value are
returned.
If the entire region of pages does not have a matching set of
attributes, then the returned length parameter value can be used to
calculate the address and length of the region of pages that was not
scanned.
Arguments:
ProcessHandle - An open handle to a process object.
BaseAddress - The base address of the region of pages to be
queried. This value is rounded down to the next host-page-
address boundary.
MemoryInformationClass - The memory information class about which
to retrieve information.
MemoryInformation - A pointer to a buffer that receives the
specified information. The format and content of the buffer
depend on the specified information class.
MemoryBasicInformation - Data type is PMEMORY_BASIC_INFORMATION.
MEMORY_BASIC_INFORMATION Structure
ULONG RegionSize - The size of the region in bytes
beginning at the base address in which all pages have
identical attributes.
ULONG State - The state of the pages within the region.
State Values State Values
MEM_COMMIT - The state of the pages within the region
is committed.
MEM_FREE - The state of the pages within the region
is free.
MEM_RESERVE - The state of the pages within the
region is reserved.
ULONG Protect - The protection of the pages within the
region.
Protect Values Protect Values
PAGE_NOACCESS - No access to the region of pages is
allowed. An attempt to read, write, or execute
within the region results in an access violation
(i.e., a GP fault).
PAGE_EXECUTE - Execute access to the region of pages
is allowed. An attempt to read or write within
the region results in an access violation.
PAGE_READONLY - Read-only and execute access to the
region of pages is allowed. An attempt to write
within the region results in an access violation.
PAGE_READWRITE - Read, write, and execute access to
the region of pages is allowed. If write access
to the underlying section is allowed, then a
single copy of the pages are shared. Otherwise,
the pages are shared read-only/copy-on-write.
PAGE_GUARD - Read, write, and execute access to the
region of pages is allowed; however, access to
the region causes a "guard region entered"
condition to be raised in the subject process.
PAGE_NOCACHE - Disable the placement of committed
pages into the data cache.
ULONG Type - The type of pages within the region.
Type Values
MEM_PRIVATE - The pages within the region are
private.
MEM_MAPPED - The pages within the region are mapped
into the view of a section.
MEM_IMAGE - The pages within the region are mapped
into the view of an image section.
MemoryInformationLength - Specifies the length in bytes of
the memory information buffer.
ReturnLength - An optional pointer which, if specified,
receives the number of bytes placed in the process
information buffer.
Return Value:
Returns the status
TBS
Environment:
Kernel mode.
--*/
{
KPROCESSOR_MODE PreviousMode;
PEPROCESS TargetProcess;
NTSTATUS Status;
PMMVAD Vad;
BOOLEAN PteIsZero;
PVOID Va;
BOOLEAN Found;
SIZE_T TheRegionSize;
ULONG NewProtect;
ULONG NewState;
PVOID FilePointer;
ULONG_PTR BaseVpn;
MEMORY_BASIC_INFORMATION Info;
LOGICAL Attached;
Found = FALSE;
PteIsZero = FALSE;
//
// Make sure the user's buffer is large enough for the requested operation.
//
//
// Check argument validity.
//
switch (MemoryInformationClass) {
case MemoryBasicInformation:
if (MemoryInformationLength < sizeof(MEMORY_BASIC_INFORMATION)) {
return STATUS_INFO_LENGTH_MISMATCH;
}
break;
case MemoryWorkingSetInformation:
if (MemoryInformationLength < sizeof(ULONG)) {
return STATUS_INFO_LENGTH_MISMATCH;
}
break;
case MemoryMappedFilenameInformation:
FilePointer = NULL;
break;
default:
return STATUS_INVALID_INFO_CLASS;
}
PreviousMode = KeGetPreviousMode();
if (PreviousMode != KernelMode) {
//
// Check arguments.
//
try {
ProbeForWrite(MemoryInformation,
MemoryInformationLength,
sizeof(ULONG_PTR));
if (ARGUMENT_PRESENT(ReturnLength)) {
ProbeForWriteUlong(ReturnLength);
}
} except (EXCEPTION_EXECUTE_HANDLER) {
//
// If an exception occurs during the probe or capture
// of the initial values, then handle the exception and
// return the exception code as the status value.
//
return GetExceptionCode();
}
}
if (BaseAddress > MM_HIGHEST_USER_ADDRESS) {
return STATUS_INVALID_PARAMETER;
}
if ((BaseAddress >= MM_HIGHEST_VAD_ADDRESS)
#if defined(MM_SHARED_USER_DATA_VA)
||
(PAGE_ALIGN(BaseAddress) == (PVOID)MM_SHARED_USER_DATA_VA)
#endif
) {
//
// Indicate a reserved area from this point on.
//
if ( MemoryInformationClass == MemoryBasicInformation ) {
try {
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->AllocationBase =
(PCHAR) MM_HIGHEST_VAD_ADDRESS + 1;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->AllocationProtect =
PAGE_READONLY;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->BaseAddress =
PAGE_ALIGN(BaseAddress);
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->RegionSize =
((PCHAR)MM_HIGHEST_USER_ADDRESS + 1) -
(PCHAR)PAGE_ALIGN(BaseAddress);
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->State = MEM_RESERVE;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->Protect = PAGE_NOACCESS;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->Type = MEM_PRIVATE;
if (ARGUMENT_PRESENT(ReturnLength)) {
*ReturnLength = sizeof(MEMORY_BASIC_INFORMATION);
}
#if defined(MM_SHARED_USER_DATA_VA)
if (PAGE_ALIGN(BaseAddress) == (PVOID)MM_SHARED_USER_DATA_VA) {
//
// This is the page that is double mapped between
// user mode and kernel mode.
//
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->AllocationBase =
(PVOID)MM_SHARED_USER_DATA_VA;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->Protect =
PAGE_READONLY;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->RegionSize =
PAGE_SIZE;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->State =
MEM_COMMIT;
}
#endif
} except (EXCEPTION_EXECUTE_HANDLER) {
//
// Just return success.
//
}
return STATUS_SUCCESS;
} else {
return STATUS_INVALID_ADDRESS;
}
}
if ( ProcessHandle == NtCurrentProcess() ) {
TargetProcess = PsGetCurrentProcess();
} else {
Status = ObReferenceObjectByHandle ( ProcessHandle,
PROCESS_QUERY_INFORMATION,
PsProcessType,
PreviousMode,
(PVOID *)&TargetProcess,
NULL );
if (!NT_SUCCESS(Status)) {
return Status;
}
}
if (MemoryInformationClass == MemoryWorkingSetInformation) {
MmLockPagableSectionByHandle(ExPageLockHandle);
Status = MiGetWorkingSetInfo (MemoryInformation,
MemoryInformationLength,
TargetProcess);
MmUnlockPagableImageSection(ExPageLockHandle);
if ( ProcessHandle != NtCurrentProcess() ) {
ObDereferenceObject (TargetProcess);
}
try {
if (ARGUMENT_PRESENT(ReturnLength)) {
*ReturnLength = ((((PMEMORY_WORKING_SET_INFORMATION)
MemoryInformation)->NumberOfEntries - 1) *
sizeof(ULONG)) +
sizeof(MEMORY_WORKING_SET_INFORMATION);
}
} except (EXCEPTION_EXECUTE_HANDLER) {
}
return STATUS_SUCCESS;
}
//
// If the specified process is not the current process, attach
// to the specified process.
//
if ( ProcessHandle != NtCurrentProcess() ) {
KeAttachProcess (&TargetProcess->Pcb);
Attached = TRUE;
}
else {
Attached = FALSE;
}
//
// Get working set mutex and block APCs.
//
LOCK_WS_AND_ADDRESS_SPACE (TargetProcess);
//
// Make sure the address space was not deleted, if so, return an error.
//
if (TargetProcess->AddressSpaceDeleted != 0) {
UNLOCK_WS_AND_ADDRESS_SPACE (TargetProcess);
if (Attached == TRUE) {
KeDetachProcess();
ObDereferenceObject (TargetProcess);
}
return STATUS_PROCESS_IS_TERMINATING;
}
//
// Locate the VAD that contains the base address or the VAD
// which follows the base address.
//
Vad = TargetProcess->VadRoot;
BaseVpn = MI_VA_TO_VPN (BaseAddress);
for (;;) {
if (Vad == (PMMVAD)NULL) {
break;
}
if ((BaseVpn >= Vad->StartingVpn) &&
(BaseVpn <= Vad->EndingVpn)) {
Found = TRUE;
break;
}
if (BaseVpn < Vad->StartingVpn) {
if (Vad->LeftChild == (PMMVAD)NULL) {
break;
}
Vad = Vad->LeftChild;
} else {
if (BaseVpn < Vad->EndingVpn) {
break;
}
if (Vad->RightChild == (PMMVAD)NULL) {
break;
}
Vad = Vad->RightChild;
}
}
if (!Found) {
//
// There is no virtual address allocated at the base
// address. Return the size of the hole starting at
// the base address.
//
if (Vad == NULL) {
TheRegionSize = ((PCHAR)MM_HIGHEST_VAD_ADDRESS + 1) -
(PCHAR)PAGE_ALIGN(BaseAddress);
} else {
if (Vad->StartingVpn < BaseVpn) {
//
// We are looking at the Vad which occupies the range
// just before the desired range. Get the next Vad.
//
Vad = MiGetNextVad (Vad);
if (Vad == NULL) {
TheRegionSize = ((PCHAR)MM_HIGHEST_VAD_ADDRESS + 1) -
(PCHAR)PAGE_ALIGN(BaseAddress);
} else {
TheRegionSize = (PCHAR)MI_VPN_TO_VA (Vad->StartingVpn) -
(PCHAR)PAGE_ALIGN(BaseAddress);
}
} else {
TheRegionSize = (PCHAR)MI_VPN_TO_VA (Vad->StartingVpn) -
(PCHAR)PAGE_ALIGN(BaseAddress);
}
}
UNLOCK_WS_AND_ADDRESS_SPACE (TargetProcess);
if (Attached == TRUE) {
KeDetachProcess();
ObDereferenceObject (TargetProcess);
}
//
// Establish an exception handler and write the information and
// returned length.
//
if ( MemoryInformationClass == MemoryBasicInformation ) {
try {
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->AllocationBase =
NULL;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->AllocationProtect =
0;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->BaseAddress =
PAGE_ALIGN(BaseAddress);
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->RegionSize =
TheRegionSize;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->State = MEM_FREE;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->Protect = PAGE_NOACCESS;
((PMEMORY_BASIC_INFORMATION)MemoryInformation)->Type = 0;
if (ARGUMENT_PRESENT(ReturnLength)) {
*ReturnLength = sizeof(MEMORY_BASIC_INFORMATION);
}
} except (EXCEPTION_EXECUTE_HANDLER) {
//
// Just return success.
//
}
return STATUS_SUCCESS;
}
return STATUS_INVALID_ADDRESS;
}
//
// Found a VAD.
//
Va = PAGE_ALIGN(BaseAddress);
Info.BaseAddress = Va;
//
// There is a page mapped at the base address.
//
if (Vad->u.VadFlags.PrivateMemory) {
Info.Type = MEM_PRIVATE;
} else if (Vad->u.VadFlags.ImageMap == 0) {
Info.Type = MEM_MAPPED;
if ( MemoryInformationClass == MemoryMappedFilenameInformation ) {
if (Vad->ControlArea) {
FilePointer = Vad->ControlArea->FilePointer;
}
if ( !FilePointer ) {
FilePointer = (PVOID)1;
} else {
ObReferenceObject(FilePointer);
}
}
} else {
Info.Type = MEM_IMAGE;
}
Info.State = MiQueryAddressState (Va, Vad, TargetProcess, &Info.Protect);
Va = (PVOID)((PCHAR)Va + PAGE_SIZE);
while (MI_VA_TO_VPN (Va) <= Vad->EndingVpn) {
NewState = MiQueryAddressState (Va,
Vad,
TargetProcess,
&NewProtect);
if ((NewState != Info.State) || (NewProtect != Info.Protect)) {
//
// The state for this address does not match, calculate
// size and return.
//
break;
}
Va = (PVOID)((PCHAR)Va + PAGE_SIZE);
} // end while
Info.RegionSize = ((PCHAR)Va - (PCHAR)Info.BaseAddress);
Info.AllocationBase = MI_VPN_TO_VA (Vad->StartingVpn);
Info.AllocationProtect = MI_CONVERT_FROM_PTE_PROTECTION (
Vad->u.VadFlags.Protection);
//
// A range has been found, release the mutexes, deattach from the
// target process and return the information.
//
#if !(defined(_MIALT4K_))
UNLOCK_WS_AND_ADDRESS_SPACE (TargetProcess);
#else
UNLOCK_WS_UNSAFE (TargetProcess);
if (TargetProcess->Wow64Process != NULL) {
Info.BaseAddress = PAGE_4K_ALIGN(BaseAddress);
MiQueryRegionFor4kPage(Info.BaseAddress,
MI_VPN_TO_VA_ENDING(Vad->EndingVpn),
&Info.RegionSize,
&Info.State,
&Info.Protect,
TargetProcess);
}
UNLOCK_ADDRESS_SPACE (TargetProcess);
#endif
if (Attached == TRUE) {
KeDetachProcess();
ObDereferenceObject (TargetProcess);
}
#if DBG
if (MmDebug & MM_DBG_SHOW_NT_CALLS) {
if ( !MmWatchProcess ) {
DbgPrint("queryvm base %lx allocbase %lx protect %lx size %lx\n",
Info.BaseAddress, Info.AllocationBase, Info.AllocationProtect,
Info.RegionSize);
DbgPrint(" state %lx protect %lx type %lx\n",
Info.State, Info.Protect, Info.Type);
}
}
#endif //DBG
if ( MemoryInformationClass == MemoryBasicInformation ) {
try {
*(PMEMORY_BASIC_INFORMATION)MemoryInformation = Info;
if (ARGUMENT_PRESENT(ReturnLength)) {
*ReturnLength = sizeof(MEMORY_BASIC_INFORMATION);
}
} except (EXCEPTION_EXECUTE_HANDLER) {
}
return STATUS_SUCCESS;
}
//
// Try to return the name of the file that is mapped.
//
if ( !FilePointer ) {
return STATUS_INVALID_ADDRESS;
} else if ( FilePointer == (PVOID)1 ) {
return STATUS_FILE_INVALID;
}
//
// We have a referenced pointer to the file. Call ObQueryNameString
// and get the file name
//
//获取文件名///
Status = ObQueryNameString(
FilePointer,
MemoryInformation,
MemoryInformationLength,
ReturnLength
);
//获取文件名/ ObDereferenceObject(FilePointer);
return Status;
}
只需要看一下函数最后的处理方式,获取文件名的时候使用了ObQueryNameString这个函数,而且是直接把Buffer和buffer的长度作为参数使用,若要继续探究下去,我们还要去看一下ObQueryNameString这个函数的实现方法,可以看源码,但是我们有更快捷的途径,查看MSDN吧,MSDN形成的文档毕竟比源码直观。
ObQueryNameStringThe ObQueryNameString routine supplies the name, if any, of a given object to which the caller has a pointer. NTSTATUS ParametersObject Pointer to the object for which the name is requested. This parameter is required and cannot be NULL.ObjectNameInfo Pointer to a caller-allocated buffer that receives the object name information, formatted as follows. typedef struct _OBJECT_NAME_INFORMATION { Return ValueObQueryNameString returns STATUS_SUCCESS or an NTSTATUS value such as the following:
CommentsIf the given object is named and the type-specific query-name method succeeds, the returned string is the full path to the given object. In this case,ObQueryNameString setsName.Buffer to the address immediately afterObjectNameInfo. If the given object is unnamed, or if the creator of the object type does not supply a query-name method,ObQueryNameString setsName.Buffer to NULL and setsName.Length andName.MaximumLength to zero. The storage for ObjectNameInfo can be allocated from paged or nonpaged pool. Callers of ObQueryNameString must be running at IRQL < DISPATCH_LEVEL. |
我们要找的MEMORY_MAPPED_FILE_NAME_INFORMATION的定义原因就是上述用画线加大字体所描述的:
如果给定的对象是命名的,并且指定类型的查询名称的方法成功,则返回的字符串为指定对象的全路径名称。在这种情况下,ObQueryNameString将会设置Name的buffer字段指向紧接着ObjectNameInfo结构后面的地址。
简单来说就是这个函数如果执行成功,并且查询的对象确实有名称,那么他就把名称存放放在传入的参数ObjectNameInfo + sizeof(UNICODE_STRING)的地方,所以这个ObjectNameInfo的定义就应该是:
#define _MAX_OBJECT_NAME 1024/sizeof(WCHAR)
typedef struct _MEMORY_MAPPED_FILE_NAME_INFORMATION {
UNICODE_STRING Name;
WCHAR Buffer[_MAX_OBJECT_NAME];
} MEMORY_MAPPED_FILE_NAME_INFORMATION, *PMEMORY_MAPPED_FILE_NAME_INFORMATION;
至于Buffer的大小为什么是1024Bytes 前面参数介绍也有说。
目前流行的会涉及到这个函数的用法是用于枚举进程所有模块,通常用Ring3的现成API CreateToolhelp32Snapshot 这种方法来枚举,只从进程的PEB的三条模块LINK中枚举,但是有很多系统加载的,或者特意在PEB的模块链表中抹去了模块就无法被枚举出来,这时候NtQueryVirtualMemory就登场了,NtQueryVirtualMemory用于枚举进程模块的时候有点类似“暴力内存搜索”,即指定一个片内存区域,然后对于这段内存区域内所有的可以用于对齐模块的地址使用NtQueryVirtualMemory获取内存信息,就可以没有任何遗漏的检测出所有模块。
下面给出一个用来检测某一地址所处的模块全路径名的方法的代码片段:
MEMORY_BASIC_INFORMATION MemBasciInfo;
ULONG ulRetLen = 0;
NTSTATUS status = NtQueryVirtualMemory(
GetCurrentProcess(),
pTargetLinearAddress,
MemoryBasicInformation,
&MemBasciInfo,
sizeof(MEMORY_BASIC_INFORMATION),
&ulRetLen);
if (!NT_SUCCESS(status))
{
return FALSE;
}
if (MEM_IMAGE != MemBasciInfo.Type)
{
return FALSE;
}
MEMORY_MAPPED_FILE_NAME_INFORMATION MappedFileName;
ZeroMemory(&MappedFileName, sizeof(MappedFileName));
MappedFileName.Name.Buffer = MappedFileName.Buffer;
MappedFileName.Name.Length = 0;
MappedFileName.Name.MaximumLength = sizeof(MappedFileName.Buffer);
status = NtQueryVirtualMemory(
GetCurrentProcess(),
MemBasciInfo.AllocationBase,
MemoryMappedFilenameInformation,
&MappedFileName,
sizeof(MappedFileName),
&ulRetLen);
if (!NT_SUCCESS(status))
{
return FALSE;
}
if (0 == MappedFileName.Name.Length || NULL == MappedFileName.Name.Buffer )
{
return FALSE;
}