Понимание ошибки Pool Coruption часть 1 (переполнение буфера)

Пул – это память ядра, которая используется драйверами и кодом ядра. То есть, приложение пользовательского режима не может выделять память в этих пулах – это память только для ядра и драйверов. Мы можем видеть в диспетчере ядра, на скриншоте ниже показано “Выгружаемый пул” и “Невыгружаемый пул”.

 

В англ. варианте это звучит “paged pool” и “nonpaged pool”. Разница этих пулов только в одном – первый пул может выгружаться в файл подкачки, а второй – нет. Это связано с уровнями IRQL, при которых может выполнятся код в режиме ядра. О размерах пулов, а также о том какие они есть и как их можно настраивать можно почитать в книге Руссиновича и Соломона.

Память ОС Windows разбита на страницы, каждая из которых обычно имеет размер 4КБ = 4096 байтов (это аппаратная особенность). Выделением ядром или драйверами объемов памяти по 4 КБайт считается расточительством. Поэтому ядро выделяет некоторый объем памяти под пулы (определенное количество страниц) заранее (см. Руссиновича и Соломона какое именно и как это можно изменять) и это заранее выделенный объем называется пулом. Каждый раз когда драйвер или ядро выделяет память менеджер памяти выделяет его из этого пула но не по 1024 байта а 32 байта (Windows 2000) и даже 8 байтов (XP). Перед каждый выделенным блоком размещается структура Pool Header. Все эти блоки для разных драйверов следуют друг за другом. Особенность блоков пулов представлена на изображении ниже.

В отладчике можно посмотреть сами поля структуры:

lkd> dt _POOL_HEADER
nt!_POOL_HEADER
   +0x000 PreviousSize     : Pos 0, 8 Bits
   +0x000 PoolIndex        : Pos 8, 8 Bits
   +0x002 BlockSize        : Pos 0, 8 Bits
   +0x002 PoolType         : Pos 8, 8 Bits
   +0x000 Ulong1           : Uint4B
   +0x004 PoolTag          : Uint4B
   +0x008 ProcessBilled    : Ptr64 _EPROCESS
   +0x008 AllocatorBackTraceIndex : Uint2B
   +0x00a PoolTagHash      : Uint2B

Очень интересным полем является PoolTag. Оно было изначально задумано для того, чтобы определять, каким именно драйвером было выполнено выделение памяти. В каталоге riagepooltag.txt есть список стандартных драйверов. Вот пример части его содержимого:

ScC1 — classpnp.sys —  Registry path buffer
ScC2 — classpnp.sys —  PDO relations
ScC6 — classpnp.sys —  START_UNIT completion context
ScC7 — classpnp.sys —  Sense info buffer
ScC8 — classpnp.sys —  Registry value name
ScC9 — classpnp.sys —  Device Control SRB
…..

Часть тегов Microsoft “застолбила” за собой. Другие теги должен придумывать разработчик драйверов. Это очень важный момент, поскольку он на порядок облегчает анализ крахов связанных с перезаписью чужого участка памяти. Здесь нет никакой жесткости и любой разработчик может придумать какой хочет тег из четырех символов. Тег используются в ExAllocatePoolWithTag. Пример какого-то кода из github при выделении памяти с тегом ‘klBV’ на скриншоте ниже

Теперь давайте разберемся, как все это связано с крахами. Здесь ситуаций может быть несколько.

Чтобы продемонстрировать все эти ситуации воспользуемся NotMyFault (Скачать можно здесь – https://technet.microsoft.com/en-us/sysinternals/bb963901.aspx). Это утилита разработана Марком Руссиновичем еще во времена sysinternals.com (и идеи с исходным кодом) и устанавливает бажный драйвер в вашу систему, далее вы выбираете, какой тип краха вам нужен и вы его получаете.

Запускаем notmyfault и выбираем “buffer overflow” и нажимаем “Crash”.

Все не должно произойти мгновенно, может потребоваться некоторое время.

Вот кусок кода, который вызывает перезапись пула чужого драйвера:

#define ALLOCATION_SIZE  2048
VOID
BufferOverflow(
VOID
)
{
PCHAR   buffer;
int     i;
CHAR    overflow[] = "OVERFLOW";

//
// Allocate a buffer and zip past the end of it
//
buffer = ExAllocatePool( NonPagedPool, ALLOCATION_SIZE );
for( i = 0; i < ALLOCATION_SIZE+40; i ++ ) {

strcpy( &buffer[i], overflow );
}

//
// Leak the memory so that if we have to try again we 
// get a fresh block of memory to overrun
//
// ExFreePool( buffer );
}

Через 1 минуту работы я получил

Анализируем дамп.

Use !analyze -v to get detailed debugging information.

BugCheck 100000C5, {4f4f4f53, 2, 1, 81aff010}

Probably caused by : ntkrpamp.exe ( nt!ExAllocatePoolWithTag+4ca )

Followup: MachineOwner
---------

kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

DRIVER_CORRUPTED_EXPOOL (c5)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is
caused by drivers that have corrupted the system pool.  Run the driver
verifier against any new (or suspect) drivers, and if that doesn't turn up
the culprit, then use gflags to enable special pool.
Arguments:
Arg1: 4f4f4f53, memory referenced
Arg2: 00000002, IRQL
Arg3: 00000001, value 0 = read operation, 1 = write operation
Arg4: 81aff010, address which referenced memory

Debugging Details:
------------------


BUGCHECK_STR:  0xC5_2

CURRENT_IRQL:  2

FAULTING_IP:
nt!ExAllocatePoolWithTag+4ca
81aff010 895804          mov     dword ptr [eax+4],ebx

CUSTOMER_CRASH_COUNT:  2

DEFAULT_BUCKET_ID:  VISTA_DRIVER_FAULT

PROCESS_NAME:  System

ANALYSIS_VERSION: 6.3.9600.17298 (debuggers(dbg).141024-1500) amd64fre

LAST_CONTROL_TRANSFER:  from 81ac2bee to 81aff010

STACK_TEXT:
8693ba20 81ac2bee 00000000 000001d8 20707249 nt!ExAllocatePoolWithTag+0x4ca
8693ba48 81ac34b0 831349f0 000001d8 20707249 nt!ExAllocatePoolWithQuotaTag+0x58
8693ba7c 81c2437d 0000000a 00140001 f3d71bca nt!IopAllocateIrpPrivate+0x121
8693bb00 81a5bc7a 84efbbf0 800008e4 00000000 nt!NtWriteFile+0x4ed
8693bb00 81a5ae81 84efbbf0 800008e4 00000000 nt!KiFastCallEntry+0x12a
8693bb9c 81bf2f86 80000038 800008e4 00000000 nt!ZwWriteFile+0x11
8693bc18 81bf3293 80000038 8693bc60 00000001 nt!CmpDoFileWrite+0x15c
8693bc34 81bf3f54 866e0a20 00000000 8693bc60 nt!CmpFileWrite+0x33
8693bcd0 81bf3172 866e0a20 8693bcf0 866e0d48 nt!HvWriteHive+0x342
8693bcf8 81bf4d43 81b10e10 81b46ec8 00000000 nt!HvSyncHive+0xa8
8693bd14 81bf4bb9 81b13100 8693bd36 8693bd3c nt!CmpDoFlushNextHive+0xdc
8693bd44 81ab6e22 00000000 00000000 8317f580 nt!CmpLazyFlushWorker+0x9a
8693bd7c 81be6c42 00000000 f3d71d0a 00000000 nt!ExpWorkerThread+0xfd
8693bdc0 81a4fefe 81ab6d25 00000001 00000000 nt!PspSystemThreadStartup+0x9d
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16


STACK_COMMAND:  kb

FOLLOWUP_IP:
nt!ExAllocatePoolWithTag+4ca
81aff010 895804          mov     dword ptr [eax+4],ebx

SYMBOL_STACK_INDEX:  0

SYMBOL_NAME:  nt!ExAllocatePoolWithTag+4ca

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: nt

IMAGE_NAME:  ntkrpamp.exe

DEBUG_FLR_IMAGE_TIMESTAMP:  49e0199e

IMAGE_VERSION:  6.0.6002.18005

FAILURE_BUCKET_ID:  0xC5_2_nt!ExAllocatePoolWithTag+4ca

BUCKET_ID:  0xC5_2_nt!ExAllocatePoolWithTag+4ca

ANALYSIS_SOURCE:  KM

FAILURE_ID_HASH_STRING:  km:0xc5_2_nt!exallocatepoolwithtag+4ca

FAILURE_ID_HASH:  {6c60d930-c549-dab7-4858-72e7740bbfa0}

Followup: MachineOwner
---------

Механизм анализа предполагает, что виновато ядро :). Анализ показывает, что крах произошел во время выполнения

nt!ExAllocatePoolWithTag+4ca

что является индикатором ошибок связанных с нарушением целостности пула.

Подтверждением этого же является  описание стоп кода

“An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is
caused by drivers that have corrupted the system pool.  Run the driver
verifier against any new (or suspect) drivers, and if that doesn’t turn up
the culprit, then use gflags to enable special pool”

Кроме того, приведена правильная рекомендация использовать Driver Verifier, но мы рассмотрим немного более детально наш крах.

Аргумент 4

Arg4: 81aff010, address which referenced memory
указывает на адрес памяти по которой находится инструкция, выполнение которой завершилось крахом, а именно

81aff010 895804          mov     dword ptr [eax+4],ebx

Аргумент 1

указывает на ошибочный адрес памяти, его значение = 4f4f4f53. Что это за значение?

Команда в отладчике

kd> r eax
eax=4f4f4f4f

показывает, что значение регистра eax равно этом значению. Теперь мы можем попробовать посмотреть как оно туда попало.

kd> ub .
nt!ExAllocatePoolWithTag+0x4b2:
81afeff8 0f84ca010000    je      nt!ExAllocatePoolWithTag+0x682 (81aff1c8)
81afeffe 50              push    eax
81afefff 56              push    esi
81aff000 e812060000      call    nt!ExDeferredFreePool (81aff617)
81aff005 e99afeffff      jmp     nt!ExAllocatePoolWithTag+0x35e (81afeea4)
81aff00a 8b3b            mov     edi,dword ptr [ebx]
81aff00c 8b07            mov     eax,dword ptr [edi]
81aff00e 8903            mov     dword ptr [ebx],eax

kd> r edi
edi=8398a810
kd> dq 8398a810
8398a810  4f4f4f4f`4f4f4f4f 4f4f4f4f`4f4f4f4f
8398a820  4f4f4f4f`4f4f4f4f 4f4f4f4f`4f4f4f4f
8398a830  00574f4c`46524556 859a53f8`85994378
8398a840  00010018`8398a848 859cb9e0`859cb9e0
8398a850  859cb9d8`85994378 00010019`8398a860
8398a860  859cbbe0`859cbbe0 859cbbd8`85994378
8398a870  0001001a`8398a878 85988cb8`85988cb8
8398a880  85988cb0`85994378 0001001b`8398a890

Теперь становится понятным, что в eax эти данные попали через указатель в edi. Но здесь важно понять, что установить по этому дампу “виновника” нельзя. Кроме того, чтобы утверждать что проблема связана с нарушением данных пула, нужно использовать команду !pool. Однако в этом случае ее работа имеет следующий результат:

kd> !pool 8398a810
GetPointerFromAddress: unable to read from 81b48868
Unable to read MiSystemVaType memory at 81b28420

Единственный выход — использовать утилиту DriverVerifier.

Давайте теперь рассмотрим полностью цепочку событий.

1. Наш неправильно работающий драйвер выделяет память в невгружаемом пуле

buffer = ExAllocatePool( NonPagedPool, ALLOCATION_SIZE );

Дальше он перезаписывает своими данными, а это строка OVERFLOW, что в 16-ричном виде будет иметь представление

чужие блоки пула, поскольку, там идет такой код

for( i = 0; i < ALLOCATION_SIZE+40; i ++ ) {
    strcpy( &buffer[i], overflow );
}

то будут перезаписываться 4f (символ O).

2. В системе уже есть серьезные проблемы, но у меня все работало нормально, пока ядро не попыталось выделить память под свои нужды, но в eax уже вместо записанного ранее корректного адреса было 4f4f4f4f, что является ошибочным и система сгенерировала BSOD, при этом следует заметить правильно посоветовала использовать Driver Verifier для проверки новых драйверов. Именно с помощью него можно отловить проблемный драйвер, но мы рассмотрим это уже в следующей статье.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *