Scan Process Memory
In addition to scanning static files with Yara rules, directly scanning memory is another effective strategy for detecting malicious behavior. To scan a process's memory, we can use the VirtualQuery function. VirtualQuery is a Windows API function that retrieves information about a specified memory region. This function is very useful for memory analysis and malware detection, particularly when analyzing the memory distribution of a process.
VirtualQuery
Parameters of VirtualQuery
(see: https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualquery):
SIZE_T VirtualQuery(
[in, optional] LPCVOID lpAddress, // A pointer to the base address of the region of pages
[out] PMEMORY_BASIC_INFORMATION lpBuffer, // A pointer to a MEMORY_BASIC_INFORMATION structure
[in] SIZE_T dwLength // The size of the buffer pointed to by the lpBuffer
);
MEMORY_BASIC_INFORMATION
The output of VirtualQuery
is stored in MEMORY_BASIC_INFORMATION
, which describes a range of pages in a process's virtual address space (base address, size, state, protection, type). See: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-memory_basic_information
typedef struct _MEMORY_BASIC_INFORMATION {
PVOID BaseAddress; // The base address of the memory region
PVOID AllocationBase; // The base address of the allocated memory region
DWORD AllocationProtect; // The protection attributes of the allocated memory region
WORD PartitionId; // The partition ID, a member added in Windows 10 version 2004
SIZE_T RegionSize; // The size of the memory region
DWORD State; // The state of the memory region(MEM_COMMIT,MEM_FREE,MEM_RESERVE)
DWORD Protect; // The protection attributes of the current memory region
DWORD Type; // The type of the memory region(MEM_IMAGE,MEM_MAPPED,MEM_PRIVATE)
} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;
Example 1: VirtualQuery basics
Query and print information about a specific memory region:
#include <windows.h>
#include <stdio.h>
void PrintMemoryInfo(LPCVOID address) {
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery(address, &mbi, sizeof(mbi))) {
printf("Base Address: %p\n", mbi.BaseAddress);
printf("Allocation Base: %p\n", mbi.AllocationBase);
printf("Region Size: %zu bytes\n", mbi.RegionSize);
printf("State: 0x%lx\n", mbi.State);
printf("Type: 0x%lx\n", mbi.Type);
} else {
printf("VirtualQuery failed with error code %lu\n", GetLastError());
}
}
int main() {
PrintMemoryInfo((LPCVOID)0x00400000);
return 0;
}
The return values:
Base Address: 0x00400000
Allocation Base: 0x00400000
Region Size: 4096 bytes
State: 0x1000 // MEM_COMMIT
Type: 0x20000 // MEM_PRIVATE
Example 2: Allocate, query, and free
Allocate a 1024-byte virtual memory block with VirtualAlloc, query it using VirtualQuery, then release it with VirtualFree. Ensure lpBuffer
is large enough or you may get ERROR_INSUFFICIENT_BUFFER
.
#include <windows.h>
#include <iostream>
#include <windows.h>
#include <iostream>
int main() {
// Allocate a virtual memory block of size 1024 bytes
LPVOID p = VirtualAlloc(NULL, 1024, MEM_COMMIT, PAGE_READWRITE);
if (p == NULL) {
// Memory allocation failed
return -1;
}
// Define structure to store virtual memory information
MEMORY_BASIC_INFORMATION mbi;
// Query virtual memory information
SIZE_T size = VirtualQuery(p, &mbi, sizeof(mbi));
if (size == 0) {
// Query failed
return -1;
}
// Output virtual memory information
std::cout << "Base address of the virtual memory block: " << mbi.BaseAddress << std::endl;
std::cout << "Size of the virtual memory block: " << mbi.RegionSize << std::endl;
std::cout << "State of the virtual memory block: " << mbi.State << std::endl;
std::cout << "Protection level of the virtual memory block: " << mbi.Protect << std::endl;
std::cout << "Type of access to the virtual memory block: " << mbi.Type << std::endl;
// Free virtual memory
VirtualFree(p, 0, MEM_RELEASE);
return 0;
}