在`Windows`操作系統中,每個進程的虛擬地址空間都被劃分為若幹記憶體塊,每個記憶體塊都具有一些屬性,如記憶體大小、保護模式、類型等。這些屬性可以通過`VirtualQueryEx`函數查詢得到。該函數可用於查詢進程虛擬地址空間中的記憶體信息的函數。它的作用類似於`Windows`操作系統中的`Task... ...
在Windows
操作系統中,每個進程的虛擬地址空間都被劃分為若幹記憶體塊,每個記憶體塊都具有一些屬性,如記憶體大小、保護模式、類型等。這些屬性可以通過VirtualQueryEx
函數查詢得到。
該函數可用於查詢進程虛擬地址空間中的記憶體信息的函數。它的作用類似於Windows
操作系統中的Task Manager
中的進程選項卡,可以顯示出一個進程的記憶體使用情況、模塊列表等信息。使用VirtualQueryEx
函數,可以枚舉一個進程的所有記憶體塊。該函數需要傳入要查詢的進程的句柄、基地址和一個MEMORY_BASIC_INFORMATION
結構體指針。它會返回當前記憶體塊的基地址、大小、狀態(free/commit/reserve
)、保護模式等信息。
在實現對記憶體塊的枚舉之前,我們先通過ReadProcessMemory
函數實現一個記憶體遠程記憶體讀取功能,如下代碼所示,首先,通過OpenProcess
函數打開進程句柄,獲得當前進程的操作許可權。然後,調用EnumMemory
函數,傳入進程句柄以及起始地址和終止地址參數,依次讀取每一頁記憶體,通過迴圈列印其記憶體數據。
#include <iostream>
#include <windows.h>
// 枚舉記憶體實現
void EnumMemory(HANDLE Process, DWORD BeginAddr, DWORD EndAddr)
{
// 每次讀入長度
const DWORD pageSize = 1024;
BYTE page[pageSize];
DWORD tmpAddr = BeginAddr;
while (tmpAddr <= EndAddr)
{
ReadProcessMemory(Process, (LPCVOID)tmpAddr, &page, pageSize, 0);
for (int x = 0; x < pageSize; x++)
{
if (x % 15 == 0)
{
printf("| 0x%08X \n", tmpAddr + x);
}
printf("0x%02X ", page[x]);
}
tmpAddr += pageSize;
}
}
int main(int argc, char* argv[])
{
HANDLE process;
// 打開當前進程
process = OpenProcess(PROCESS_ALL_ACCESS, false, GetCurrentProcessId());
// 枚舉記憶體 從0x401000 - 0x7FFFFFFF
EnumMemory(process, 0x00401000, 0x7FFFFFFF);
system("pause");
return 0;
}
上述代碼簡單明瞭,易於理解,但並沒有實現過濾特定記憶體屬性的功能。如果需要對特定類型的記憶體進行分析,需要結合VirtualQueryEx
函數實現記憶體屬性的查詢和過濾。
接著我們進入本章的重點,實現枚舉進程記憶體塊,要實現該功能首先讀者必須要瞭解一個結構體_SYSTEM_INFO
該結構體是系統信息結構,可用於存儲系統硬體和系統配置信息,而我們所需要的記憶體塊數據同樣可以使用該結構進行存儲。
根據具體需求,可以通過調用GetSystemInfo
函數來獲得_SYSTEM_INFO
結構體的信息。GetSystemInfo
函數可以返回系統的硬體信息,包括有多少個處理器,每個處理器有多少個核心,系統頁大小等信息,該結構體的定義如下所示;
typedef struct _SYSTEM_INFO {
union {
DWORD dwOemId; // 相容性保留
struct {
WORD wProcessorArchitecture; // 操作系統處理器體繫結構
WORD wReserved; // 保留
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
DWORD dwPageSize; // 頁面大小和頁面保護和承諾的粒度
LPVOID lpMinimumApplicationAddress; // 指嚮應用程式和dll可訪問的最低記憶體地址的指針
LPVOID lpMaximumApplicationAddress; // 指嚮應用程式和dll可訪問的最高記憶體地址的指針
DWORD_PTR dwActiveProcessorMask; // 處理器掩碼
DWORD dwNumberOfProcessors; // 當前組中邏輯處理器的數量
DWORD dwProcessorType; // 處理器類型,相容性保留
DWORD dwAllocationGranularity; // 虛擬記憶體的起始地址的粒度
WORD wProcessorLevel; // 處理器級別
WORD wProcessorRevision; // 處理器修訂
} SYSTEM_INFO, *LPSYSTEM_INFO;
接著就是要查詢記憶體塊的狀態了,我們可通過VirtualQueryEx
函數實現查詢進程虛擬地址空間中的記憶體信息,其原型定義如下:
SIZE_T VirtualQueryEx(
HANDLE hProcess,
LPCVOID lpAddress,
PMEMORY_BASIC_INFORMATION lpBuffer,
SIZE_T dwLength
);
參數說明:
- hProcess:進程句柄。需要查詢的進程的句柄
- lpAddress:基地址。需要查詢的記憶體塊的基地址
- lpBuffer:記憶體信息緩衝區。 PMEMORY_BASIC_INFORMATION 結構指針,用於存儲查詢結果。它包含了取得的記憶體塊信息,如基地址、保護屬性、狀態、大小等
- dwLength:緩衝區大小。緩衝區的大小,以位元組為單位。如果緩衝區太小,則函數將返回指定的記憶體塊信息長度存放到此處,不會寫入完整的信息
該函數返回實際填充到緩衝區中的位元組數。如果函數失敗,則返回0。當我們需要瞭解特定進程的記憶體使用情況時,可以使用VirtualQueryEx()
函數枚舉進程記憶體中的所有記憶體塊,並按需查詢其中的屬性值。
#include <iostream>
#include <windows.h>
#include <Psapi.h>
#pragma comment(lib,"psapi.lib")
// 枚舉特定進程記憶體塊信息
VOID ScanProcessMemory(HANDLE hProc)
{
SIZE_T stSize = 0;
PBYTE pAddress = (PBYTE)0;
SYSTEM_INFO sysinfo;
MEMORY_BASIC_INFORMATION mbi = { 0 };
//獲取頁的大小
ZeroMemory(&sysinfo, sizeof(SYSTEM_INFO));
GetSystemInfo(&sysinfo);
// 得到的鏡像基地址
pAddress = (PBYTE)sysinfo.lpMinimumApplicationAddress;
printf("------------------------------------------------------------------------ \n");
printf("開始地址 \t 結束地址 \t 大小 \t 狀態 \t 記憶體類型 \t 頁面屬性 \n");
printf("------------------------------------------------------------------------ \n");
// 判斷只要當前地址小於最大地址就迴圈
while (pAddress < (PBYTE)sysinfo.lpMaximumApplicationAddress)
{
// 對結構體進行初始化
ZeroMemory(&mbi, sizeof(MEMORY_BASIC_INFORMATION));
// 查詢記憶體屬性
stSize = VirtualQueryEx(hProc, pAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
if (stSize == 0)
{
pAddress += sysinfo.dwPageSize;
continue;
}
// 輸出查詢結果
printf("0x%08X \t 0x%08X \t %8d K \t ", mbi.BaseAddress, ((DWORD)mbi.BaseAddress + (DWORD)mbi.RegionSize), mbi.RegionSize >> 10);
// 輸出狀態
switch (mbi.State)
{
case MEM_FREE: printf("空閑 \t"); break;
case MEM_RESERVE: printf("保留 \t"); break;
case MEM_COMMIT: printf("提交 \t"); break;
default: printf("未知 \t"); break;
}
// 輸出類型
switch (mbi.Type)
{
case MEM_PRIVATE: printf("私有 \t"); break;
case MEM_MAPPED: printf("映射 \t"); break;
case MEM_IMAGE: printf("鏡像 \t"); break;
default: printf("未知 \t"); break;
}
if (mbi.Protect == 0)
{
printf("---");
}
else if (mbi.Protect & PAGE_EXECUTE)
{
printf("E--");
}
else if (mbi.Protect & PAGE_EXECUTE_READ)
{
printf("ER-");
}
else if (mbi.Protect & PAGE_EXECUTE_READWRITE)
{
printf("ERW");
}
else if (mbi.Protect & PAGE_READONLY)
{
printf("-R-");
}
else if (mbi.Protect & PAGE_READWRITE)
{
printf("-RW");
}
else if (mbi.Protect & PAGE_WRITECOPY)
{
printf("WCOPY");
}
else if (mbi.Protect & PAGE_EXECUTE_WRITECOPY)
{
printf("EWCOPY");
}
printf("\n");
// 每次迴圈累加記憶體塊的位置
pAddress = (PBYTE)mbi.BaseAddress + mbi.RegionSize;
}
}
int main(int argc, char* argv[])
{
// 打開進程
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
// 開始枚舉
ScanProcessMemory(hProc);
CloseHandle(hProc);
system("pause");
return 0;
}
運行上述代碼片段則首先通過GetCurrentProcessId()
得到自身進程的PID
號,接著通過調用ScanProcessMemory
函數實現對自身進程記憶體塊的枚舉功能,最終輸出如下圖所示的效果;
當然了雖然上述代碼可以實現對記憶體塊的枚舉功能,但是在實際的開發場景中我們還是需要將枚舉結果存儲起來以便後期調用,此時我們可以考慮在全局定義vector
容器,容器的屬性為每一個記憶體塊的MEMORY_BASIC_INFORMATION
屬性,當需要查詢時只需要枚舉這個容器並迴圈輸出該容器內的數據即可,改進後的代碼如下所示;
#include <Windows.h>
#include <vector>
#include <iostream>
#include <assert.h>
using namespace std;
// 枚舉指定進程所有記憶體塊
static bool ScanProcessMemory(HANDLE hProcess, OUT vector<MEMORY_BASIC_INFORMATION>& memories)
{
// 如果 hProcess 為空則結束運行
assert(hProcess != nullptr);
// 初始化容器並設置容量
memories.clear();
memories.reserve(200);
// 獲取 PageSize 和地址粒度
SYSTEM_INFO sysInfo = { 0 };
GetSystemInfo(&sysInfo);
// 定義基本的記憶體結構
const char* p = (const char*)sysInfo.lpMinimumApplicationAddress;
MEMORY_BASIC_INFORMATION memInfo = { 0 };
// 開始遍歷記憶體
while (p < sysInfo.lpMaximumApplicationAddress)
{
// 獲取進程虛擬記憶體塊緩衝區位元組數
size_t size = VirtualQueryEx(
hProcess, // 進程句柄
p, // 要查詢記憶體塊的基地址指針
&memInfo, // 接收記憶體塊信息的 MEMORY_BASIC_INFORMATION 對象
sizeof(MEMORY_BASIC_INFORMATION32) // 緩衝區大小
);
if (size != sizeof(MEMORY_BASIC_INFORMATION32))
{
break;
}
// 將記憶體塊信息追加到容器內
memories.push_back(memInfo);
// 移動指針
p += memInfo.RegionSize;
}
// 容器大於0則返回
return memories.size() > 0;
}
int main(int argc, char* argv[])
{
// 存放進程記憶體塊的數組
vector<MEMORY_BASIC_INFORMATION> vec;
// 打開自身進程
HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
// 遍歷該進程的記憶體
if (ScanProcessMemory(handle, vec))
{
printf("------------------------------------------------------------------------ \n");
printf("開始地址 \t 結束地址 \t 大小 \t 狀態 \t 記憶體類型 \t 頁面屬性 \n");
printf("------------------------------------------------------------------------ \n");
// 此處迴圈遍歷結構
for (int i = 0; i < vec.size(); i++)
{
printf("0x%08X \t 0x%08X \t %8d K \t ", vec[i].BaseAddress, ((DWORD)vec[i].BaseAddress + (DWORD)vec[i].RegionSize), vec[i].RegionSize >> 10);
switch (vec[i].State)
{
case MEM_FREE: printf("空閑 \t"); break;
case MEM_RESERVE: printf("保留 \t"); break;
case MEM_COMMIT: printf("提交 \t"); break;
default: printf("未知 \t"); break;
}
switch (vec[i].Type)
{
case MEM_PRIVATE: printf("私有 \t"); break;
case MEM_MAPPED: printf("映射 \t"); break;
case MEM_IMAGE: printf("鏡像 \t"); break;
default: printf("未知 \t"); break;
}
if (vec[i].Protect == 0)
{
printf("---");
}
else if (vec[i].Protect & PAGE_EXECUTE)
{
printf("E--");
}
else if (vec[i].Protect & PAGE_EXECUTE_READ)
{
printf("ER-");
}
else if (vec[i].Protect & PAGE_EXECUTE_READWRITE)
{
printf("ERW");
}
else if (vec[i].Protect & PAGE_READONLY)
{
printf("-R-");
}
else if (vec[i].Protect & PAGE_READWRITE)
{
printf("-RW");
}
else if (vec[i].Protect & PAGE_WRITECOPY)
{
printf("WCOPY");
}
else if (vec[i].Protect & PAGE_EXECUTE_WRITECOPY)
{
printf("EWCOPY");
}
printf("\n");
}
}
system("pause");
return 0;
}
讀者可編譯並自行運行上述代碼,觀察輸出效果其與第一個案例中的效果保持一致,此處僅僅只是通過容器中轉了參數傳遞,輸出效果圖如下所示;
對於記憶體塊中的範圍區間同樣可實現繼續查詢,例如在開始地址0x5DF00000-0x5DF01000
這個記憶體區間內,可能灰灰劃分為更多的子塊,當Basicinfo.State
記憶體屬性中的子塊屬性為MEM_COMMIT
時,我們還可以繼續調用VirtualQuery
函數對這個大記憶體塊內的子記憶體塊進行更加細緻的解析效果,這段代碼如下所示;
#include <iostream>
#include <Windows.h>
int main(int argc, char* argv[])
{
DWORD Addres = 0, Size = 0;
MEMORY_BASIC_INFORMATION Basicinfo = {};
// 遍歷進程所有分頁, 輸出內容
while (VirtualQuery((LPCVOID)Addres, &Basicinfo, sizeof(MEMORY_BASIC_INFORMATION)))
{
Size = Basicinfo.RegionSize;
printf("[+] 開始地址: 0x%08X \t 結束地址: 0x%08X \t 大小: %7d K \t 類型: ",
Basicinfo.BaseAddress, ((DWORD)Basicinfo.BaseAddress + (DWORD)Basicinfo.RegionSize), Basicinfo.RegionSize >> 10);
switch (Basicinfo.Type)
{
case MEM_PRIVATE: printf("私有 \t"); break;
case MEM_MAPPED: printf("映射 \t"); break;
case MEM_IMAGE: printf("鏡像 \t"); break;
default: printf("未知 \t"); break;
}
printf(" \t 狀態: ");
switch (Basicinfo.State)
{
case MEM_FREE: printf("空閑 \n"); break;
case MEM_RESERVE: printf("保留 \n"); break;
case MEM_COMMIT: printf("提交 \n"); break;
default: printf("未知 \n"); break;
}
// 如果是提交狀態的記憶體區域,那麼遍歷所有塊中的信息
if (Basicinfo.State == MEM_COMMIT)
{
// 遍歷所有基址是 Address
LPVOID BaseBlockAddress = (LPVOID)Addres;
DWORD BlockAddress = Addres;
DWORD dwBlockSize = 0;
// 遍歷大記憶體塊中的小記憶體塊
while (VirtualQuery((LPVOID)BlockAddress, &Basicinfo, sizeof(Basicinfo)))
{
if (BaseBlockAddress != Basicinfo.AllocationBase)
{
break;
}
printf("[*] ---> 塊地址: 0x%08X \t ", BlockAddress);
// 查看記憶體狀態,映射方式
switch (Basicinfo.Type)
{
case MEM_PRIVATE: printf("私有 \t "); break;
case MEM_MAPPED: printf("映射 \t "); break;
case MEM_IMAGE: printf("鏡像 \t "); break;
default: printf("未知 \t "); break;
}
if (Basicinfo.Protect == 0)
printf("---");
else if (Basicinfo.Protect & PAGE_EXECUTE)
printf("E--");
else if (Basicinfo.Protect & PAGE_EXECUTE_READ)
printf("ER-");
else if (Basicinfo.Protect & PAGE_EXECUTE_READWRITE)
printf("ERW");
else if (Basicinfo.Protect & PAGE_READONLY)
printf("-R-");
else if (Basicinfo.Protect & PAGE_READWRITE)
printf("-RW");
else if (Basicinfo.Protect & PAGE_WRITECOPY)
printf("WCOPY");
else if (Basicinfo.Protect & PAGE_EXECUTE_WRITECOPY)
printf("EWCOPY");
printf("\n");
// 計算所有相同塊大小
dwBlockSize += Basicinfo.RegionSize;
// 累加記憶體塊的位置
BlockAddress += Basicinfo.RegionSize;
}
// 有可能大小為空
Size = dwBlockSize ? dwBlockSize : Basicinfo.RegionSize;
}
// 下一個區域記憶體信息
Addres += Size;
}
system("pause");
return 0;
}
當上述代碼運行後,我們就可以獲取到當前記憶體中有多少個記憶體塊,以及每一個記憶體塊的屬性信息,如下圖所示;
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/c09766a2.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
文章出處:https://www.cnblogs.com/LyShark/p/17721481.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!