
Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009
.pdf
Глава 14. Исследование виртуальной памяти.docx 479
Табл. 14-4. (окончание)
Элемент |
Описание |
dwBlkStorage |
Идентифицирует содержимое блока, включающего адрес, указанный в |
|
параметре pwAddress. Принимает одно из значений: MEM_FREE, |
|
MEM_RESERVE, MEM_IMAGE, MEM_MAPPED или MEM_PRIVATE. |
Чтобы получить нею эту информацию, VMQuerry, естественно, приходится выполнять гораздо больше операции (в том числе многократно вызывать VirtualQueryEx), а потому она работает значительно медленнее VirtualQueryEx. Так что вы должны все тщательно взвесить, прежде чем остановить свой выбор на одной на этих функции. Если вам не нужна дополнительная информация, возвращаемая
VMQuery, используйте VirtualQuery или VirtualQueryEx.
Листинг файла VMQuery.cpp (рис. 14-3) показывает, как я получаю и обрабатываю данные, необходимые для инициализации элементов структуры
VMQUERY. (Файлы VMQuery.cpp и VMQuery.h содержатся в каталоге 14VMMap на компакт-диске, прилагаемом к книге.) Чтобы не объяснять подробности обработки данных «на пальцах», я снабдил тексты программ массой комментарием, вольно разбросанных по всему коду.
VMOuery.cpp
/****************************************************************************** Module: VMQuery.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#include "..\CommonFiles\CmnHdr.h" /* см. приложение A. */ #include <windowsx.h>
#include "VMQuery.h"
///////////////////////////////////////////////////////////////////////////////
// вспомогательная структура typedef struct {
SIZE_T |
RgnSize; |
|
DWORD |
dwRgnStorage; |
// MEM_*: Free, Image, Mapped, Private |
DWORD |
dwRgnBlocks; |
|
DWORD |
dwRgnGuardBlks; |
// если > 0, в регионе содержится стек потока |
BOOL |
bRgnIsAStack; |
// TRUE, если в регионе содержится стек потока |
} VMQUERY_HELP;
//глобальная статическая переменная, содержащая значение -
//гранулярность выделения

480Часть III. Управление памятью
//памяти на данном типе процессора; инициализируется при первом
//вызове VWQuery
static DWORD gs_dwAllocGran = 0;
///////////////////////////////////////////////////////////////////////////////
// эта функция проходит по всем блокам в регионе, возвращая результаты в виде
VMQUERY_HELP
static BOOL VMQueryHelp(HANDLE hProcess, LPCVOID pvAddress, VMQUERY_HELP *pVMQHelp) {
ZeroMemory(pVMQHelp, sizeof(*pVMQHelp));
// получаем базовый адрес региона, включающего переданный адрес памяти
MEMORY_BASIC_INFORMATION mbi;
BOOL bOk = (VirtualQueryEx(hProcess, pvAddress, &mbi, sizeof(mbi)) == sizeof(mbi));
if (!bOk)
return(bOk); // неверный адрес памяти, сообщаем об ошибке
//проходим по региону, начиная с его базового адреса
//(который никогда не изменяется)
PVOID pvRgnBaseAddress = mbi.AllocationBase;
//начинаем с первого блока в регионе
//(соответствующая переменная будет изменяться в цикле)
PVOID pvAddressBlk = pvRgnBaseAddress;
//запоминаем тип физической памяти, переданной данному блоку pVMQHelp->dwRgnStorage = mbi.Type;
for (;;) {
// получаем информацию о текущем блоке
bOk = (VirtualQueryEx(hProcess, pvAddressBlk, &mbi, sizeof(mbi)) == sizeof(mbi));
if (!bOk) |
|
break; |
// не удалось получить информацию; прекращаем цикл |
// проверяем, принадлежит ли текущий блок запрошенному региону if (mbi.AllocationBase != pvRgnBaseAddress)
break; // блок принадлежит следующему региону; прекращаем цикл
// блок принадлежит запрошенному региону
pVMQHelp->dwRgnBlocks++; // добавляем к региону еще один блок

Глава 14. Исследование виртуальной памяти.docx 481
pVMQHelp->RgnSize += mbi.RegionSize; // увеличиваем счетчик блоков
// в этом регионе на 1
//если блок имеет флаг PAGE_GUARD, добавляем 1 к счетчику блоков
//с этим флагом
if ((mbi.Protect & PAGE_GUARD) == PAGE_GUARD) pVMQHelp->dwRgnGuardBlks++;
//делаем наиболее вероятное предположение о типе физической памяти,
//переданной данному блоку. Стопроцентной гарантии дать нельзя,
//потому что некоторые блоки могли быть преобразованы MEM_IMAGE
//в MEM_PRIVATE или из MEM_MAPPED в MEM_PRIVATE; MEM_PRIVATE в любой
//момент может быть замещен наMEM_IMAGE или MEM_MAPPED.
if (pVMQHelp->dwRgnStorage == MEM_PRIVATE) pVMQHelp->dwRgnStorage = mbi.Type;
// получаем адрес следующего блока
pvAddressBlk = (PVOID) ((PBYTE) pvAddressBlk + mbi.RegionSize);
}
//обследовать регион, думаем: не стек ли это?
//Windows Vista: да – если в регионе содержится хотя бы 1 блок
//с флагом PAGE_GUARD
pVMQHelp->bRgnIsAStack = (pVMQHelp->dwRgnGuardBlks > 0);
return(TRUE);
}
///////////////////////////////////////////////////////////////////////////////
BOOL VMQuery(HANDLE hProcess, LPCVOID pvAddress, PVMQUERY pVMQ) {
if (gs_dwAllocGran == 0) {
//если это первый вызов, надо выяснить гранулярность
//выделения памяти в данной системе
SYSTEM_INFO sinf; GetSystemInfo(&sinf);
gs_dwAllocGran = sinf.dwAllocationGranularity;
}
ZeroMemory(pVMQ, sizeof(*pVMQ));
// получаем MEMORY_BASIC_INFORMATION для переданного адреса
MEMORY_BASIC_INFORMATION mbi;
BOOL bOk = (VirtualQueryEx(hProcess, pvAddress, &mbi, sizeof(mbi)) == sizeof(mbi));
if (!bOk)

482 Часть III. Управление памятью
return(bOk); // неверный адрес памяти, сообщаем об ошибке
//структура MEMORY_BASIC_INFORMATION содержит действительную
//информацию – пора заполнить элементы нашей структуры VMQUERY
//во-первых, заполним элементы, описывающие состояния блока;
//данные по региону получим позже
switch (mbi.State) { |
|
case MEM_FREE: |
// свободный блок (незарезервирован- |
ный) |
|
pVMQ->pvBlkBaseAddress = NULL; |
|
pVMQ->BlkSize = 0; |
|
pVMQ->dwBlkProtection = 0; |
|
pVMQ->dwBlkStorage = MEM_FREE; |
|
break; |
|
|
|
case MEM_RESERVE: // зарезервированный блок, которому // не передана физическая память
pVMQ->pvBlkBaseAddress = mbi.BaseAddress; pVMQ->BlkSize = mbi.RegionSize;
//для блока, которому не передана физическая память,
//элемент mbi.Protect недействителен. Поэтому мы покажем,
//что зарезервированный блок унаследовал атрибут защиты
//того региона, в котором он содержатся
pVMQ->dwBlkProtection = mbi.AllocationProtect; pVMQ->dwBlkStorage = MEM_RESERVE;
break;
case MEM_COMMIT: |
// |
зарезервированный блок, которому |
|
// |
передана физическая память |
pVMQ->pvBlkBaseAddress = mbi.BaseAddress; pVMQ->BlkSize = mbi.RegionSize; pVMQ->dwBlkProtection = mbi.Protect; pVMQ->dwBlkStorage = mbi.Type;
break;
default:
DebugBreak();
break;
}
// теперь заполняем элементы, относящиеся к региону
VMQUERY_HELP VMQHelp; switch (mbi.State) {
case MEM_FREE: // свободный блок (незарезервированный) pVMQ->pvRgnBaseAddress = mbi.BaseAddress;

|
Глава 14. Исследование виртуальной памяти.docx 483 |
|
|
|
|
pVMQ->dwRgnProtection |
= mbi.AllocationProtect; |
|
pVMQ->RgnSize |
= mbi.RegionSize; |
|
pVMQ->dwRgnStorage |
= MEM_FREE; |
|
pVMQ->dwRgnBlocks |
= 0; |
|
pVMQ->dwRgnGuardBlks |
= 0; |
|
pVMQ->bRgnIsAStack |
= FALSE; |
|
|
|
|
break; |
|
|
case MEM_RESERVE: |
// зарезервированный блок, которому |
|
|
// не передана физическая память |
|
pVMQ->pvRgnBaseAddress = mbi.AllocationBase; |
|
|
pVMQ->dwRgnProtection |
= mbi.AllocationProtect; |
|
//чтобы получить полную информацию по региону, нам придется
//пройти по всем его блокам
VMQueryHelp(hProcess, pvAddress, &VMQHelp);
pVMQ->RgnSize |
= VMQHelp.RgnSize; |
pVMQ->dwRgnStorage |
= VMQHelp.dwRgnStorage; |
pVMQ->dwRgnBlocks |
= VMQHelp.dwRgnBlocks; |
pVMQ->dwRgnGuardBlks |
= VMQHelp.dwRgnGuardBlks; |
pVMQ->bRgnIsAStack |
= VMQHelp.bRgnIsAStack; |
break; |
|
case MEM_COMMIT: |
// зарезервированный блок, которому |
|
// передана физическая память |
pVMQ->pvRgnBaseAddress = mbi.AllocationBase; |
|
pVMQ->dwRgnProtection |
= mbi.AllocationProtect; |
//чтобы получить полную информацию по региону, нам придется
//пройти по всем его блокам
VMQueryHelp(hProcess, pvAddress, &VMQHelp);
pVMQ->RgnSize |
= VMQHelp.RgnSize; |
pVMQ->dwRgnStorage |
= VMQHelp.dwRgnStorage; |
pVMQ->dwRgnBlocks |
= VMQHelp.dwRgnBlocks; |
pVMQ->dwRgnGuardBlks |
= VMQHelp.dwRgnGuardBlks; |
pVMQ->bRgnIsAStack |
= VMQHelp.bRgnIsAStack; |
break; |
|
default: |
|
DebugBreak(); |
|
break; |
|
|
|
} |
|
return(bOk); |
|

484 Часть III. Управление памятью
}
//////////////////////////////// End of File //////////////////////////////////
Программа-пример VMMap
Эта программа, «14-VMMap.exe», просматривает свое адресное пространство и показывает содержащиеся в нем регионы и блоки, присутствующие в регионах. Файлы исходного кода и ресурсов этой программы находятся в каталоге 14VMMap внутри архива, доступного на сайте поддержке этой книги. После запуска VMMap на экране появляется следующее окно.
Карты виртуальной памяти, представленные в главе 13 в таблицах 13-2 и 13-3, созданы с помощью именно этой программы. Каждый элемент в списке — результат вызова моей функции VMQuery. Основной цикл программы VMMap (в функции Refresh) выглядит так:
BOOL bOk = TRUE;
PV0ID pvAddress = NULL;
…
while (bOk) { VMQUERY vmq;
bOk = VMQuery(hProcess, pvAddress, &vmq); if (bOk) {
//формируем строку для вывода на экран
//и добавляем ее в окно списка
Глава 14. Исследование виртуальной памяти.docx 485
TCHAR szLine[1024];
ConstructRgnInfoLine(hProcess, &vroq, szLlne, slzeof(szLine));
ListBox_AddSt ring(hWndLB. szLine);
if (bExpandRegions) {
for (DWORD dwBlock = 0; bOk && (dwBlock < vmq.dwRgnBlocks); dwBlock++) {
ConstructBlkInfoLine(&vmq. szLlne, sizeof(szLine));
ListBox_AddString(hWndLB, szLine);
// получаем адрес следующего региона
pvAddress = ((PBYTE) pvAddress + vroq.BlkSize); if (dwBlock < vmq.dwRgnBlocks - 1) {
//нельзя запрашивать информацию о памяти за последним блоком bOk = VMQuery(hProcess, pvAddress, &vroq);
}
}
}
// получаем адрес следующего региона
pvAddress = ((PBYTE) vroq.pvRgnBaseAddress + vmq.RgnSize);
}
}
Этот цикл начинает работу с виртуального адреса NULL и заканчивается, когда VMQuery возвращает FALSE, что указывает на невозможность дальнейшего просмотра адресного пространства процесса. На каждой итерации цикла вызывается функция ConstructRgnInfoLine; она заполняет символьный буфер информацией о регионе. Потом эти данные вносятся в список.
В основной цикл вложен еще один цикл — он позволяет получать информацию о каждом блоке текущего региона. На каждой итерации из данного цикла вызывается функция ConstructBlkInfoLine, заполняющая символьный буфер информацией о блоках региона. Эти данные тоже добавляются к списку. В общем, с помощью функции VMQpery просматривать адресное пространство процесса очень легко.
Если запустить VMMap на компьютере с Windows Vista после перезагрузки (или сравнить результаты программы на разных компьютерах с Vista), можно заметить, что разные DLL-библиотеки каждый раз загружаются по разным адресам. Так работает новая функция Windows под названием Address Space Layout Randomization (ASLR). Цель случайного выбора базовых адресов — затруднить хакерам поиск известных DLL в памяти, чтобы они не смогли использовать их в своих целях.
Например, хакеры часто используют переполнение буфера или стека, чтобы вызвать стандартную функцию из системной DLL В системе с ASLR у хакера только один шанс из 256 (а то и меньше) найти нужную ему фун-
486 Часть III. Управление памятью
кцию по стандартному адресу. В результате хакерам будет намного сложнее воспользоваться ошибками из-за переполнения для обхода защиты.
При загрузке DLL ядро изменяет и выравнивает ее базовый адрес, после чего всем процессам передается уже измененный базовый адрес этой библиотеки. Это повышает эффективность использования памяти, поскольку освобождает от необходимости выравнивания адресов для каждого процесса по отдельности.
Примечание. В Visual Studio 2005 SP1 и выше вы можете использовать ASLR для своих DLL- и ЕХЕ-файлов, включив ключ /dynamicbase при компоновке. Я также рекомендую использовать этот ключ, если ваш модуль загружается по адресу, отличному от базового, — так процессы получат уже выровненный адрес вашей библиотеки, что повысит эффективность использования памяти.
Оглавление |
|
Г Л А В А 1 5 Использование виртуальной памяти в приложениях ............................ |
487 |
Резервирование региона в адресном пространстве......................................................... |
487 |
Передача памяти зарезервированному региону................................................................ |
490 |
Резервирование региона с одновременной передачей физической памяти .491 |
|
В какой момент региону передают физическую память ....................................... |
492 |
Возврат физической памяти и освобождение региона .................................................... |
495 |
В какой момент физическую память возвращают системе ................................. |
496 |
Изменение атрибутов защиты ....................................................................................... |
505 |
Сброс содержимого физической памяти ................................................................... |
506 |
Механизм Address Windowing Extensions............................................................................ |
510 |