Описание формата DMP

Дата публикации 10 окт 2007

Описание формата DMP — Архив WASM.RU

I. Структура крешдампа

Не так давно я зарелизил свой аналог livekd под названием gr8lkd, который является драйвером-фильтром файловой системы, аттачащийся к диску C:\ и создающий на нем виртуальный креш дамп, перехватывая и обрабатывая все запросы к нему.

Теперь у меня возникло желание описать формат крешдампов в Windows - .DMP. В интернете можно найти всего 1 страницу, и то на английском, с каким-то не очень подробным и довольно кривым описанием)

Я постараюсь описать этот формат наиболее полно и со всех сторон. Все нижеизложенное основывается только лишь на собственных исследованиях дизассемблерного листинга ядра, а именно функции IoWriteCrashDump() и связанных с ней.

Приступим...

Во-первых, немного о крешдампах и о том, как они генерируются. Когда происходит что-то плохое в ядре, например, освобождение уже освобожденной памяти, и когда ExFreePool (функция освобождения пула) это обнаруживает, продолжать работу может быть небезопасно, поэтому систему решается аварийно завершить. ExFreePool вызывает KeBugCheckEx( KeBugCheck2, KeBugCheck3, в зависимости от версии Windows, в первом случае это экспортируемая документированная функция, показывающая синий экран, во втором и третьем - внутренние, делающие тоже самое), которая показывает синий экран BAD_POOL_CALLER и вызывает IoWriteCrashDump для записи крешдампа. Крешдамп записывается аккурат в сектора диска, занимаемые файлом подкачки (для этого заблаговременно вызывается FSCTL_GET_RETRIEVAL_POINTERS для получения карты размещения файла подкачки), потому что на данном этапе вызывать драйвер файловой системы небезопасно - вдруг он и есть причина сбоя. Поэтому используется специальный драйвер, который сбрасывает дамп в файл подкачки. При следующей загрузке он достается оттуда и сохраняется по пути, прописанному в реестре.

Как известно, в Windows существует три типа креш-дампов:

  1. Minidump или, как он называется в ядре, triage dump. Размер такого дампа мал (обычно 64к) и они содержат минимум информации о системе в момент краха. Такой тип дампов стоит по умолчанию, но, к сожалению, он не подходит для обычных разработок в области ядра операционной системы, поэтому мы двинемся дальше и посмотрим на следующий тип:
  2. Kernel Memory Dump или, как он называется в ядре, summary dump. В этот дамп включаются только страницы памяти ядра, причем не все, а только те, что необходимы для анализа краха. Этот тип наиболее подходит для обычного анализа причины краха системы, т.к. в дамп включаются код и данные ядра, всех загруженных драйверов и системных структур. Файла подкачки должно быть достаточно для размещения всех этих данных. Примерный размер его трудно предсказать, у меня он обычно составляет 1/8 от размера физической памяти.
  3. Full Memory Dump. В такой дамп последовательно включаются все страницы физической памяти. Файла подкачки должно быть достаточно, чтобы вместить все страницы памяти.

Рассмотрим сперва структуру, общую для всех дампов - dump header page, это первая страница файла дампа, первые 0x1000 (4096) байт. Структура этой страницы такова - сперва вся страница заполняется заполнителем "PAGE" (байты 'EGAP'), а потом по нужным смещениям записываются данные. Сперва идет структура, идентифицирующая сам крешдамп:

Код (Text):
  1.  
  2. typedef struct _DUMP_HEADER {
  3. /* 00 */    ULONG Signature;
  4. /* 04 */    ULONG ValidDump;
  5. /* 08 */    ULONG MajorVersion;
  6. /* 0c */    ULONG MinorVersion;
  7. /* 10 */    ULONG DirectoryTableBase;
  8. /* 14 */    PULONG PfnDataBase;
  9. /* 18 */    PLIST_ENTRY PsLoadedModuleList;
  10. /* 1c */    PLIST_ENTRY PsActiveProcessHead;
  11. /* 20 */    ULONG MachineImageType;
  12. /* 24 */    ULONG NumberProcessors;
  13. /* 28 */    ULONG BugCheckCode;
  14. /* 2c */    ULONG BugCheckParameter1;
  15. /* 30 */    ULONG BugCheckParameter2;
  16. /* 34 */    ULONG BugCheckParameter3;
  17. /* 38 */    ULONG BugCheckParameter4;
  18. /* 3c */    CHAR  VersionUser[32];
  19. /* 5c */    BYTE  PaeEnabled;
  20.             BYTE  NotUsed[3];
  21. /* 60 */    PVOID KdDebuggerDataBlock;
  22. } DUMP_HEADER, *PDUMP_HEADER;
  • Signature - Это поле не заполняется и в нем оставляются лежать байты 'EGAP'
  • ValidDump Сигнатура дампа - "DUMP" ('PMUD')
  • MajorVersion - 0x0F если Free build 0x0C если Checked build
  • MinorVersion - Build number системы
  • DirectoryTableBase - Значение CR3 в момент краха системы - физический адрес каталога страниц
  • PfnDatabase - Виртуальный адрес MmPfnDatabase - база данных фреймов страниц (PFN)
  • PsLoadedModuleList - Виртуальный адрес PsLoadedModuleList - список загруженных модулей
  • PsActiveProcessHead - Виртуальный адрес PsActiveProcessHead - список активных процессов
  • MachineImageType - на x86 это 0x14C, остальные константы можно посмотреть в winnt.h
  • NumberProcessors - Число процессоров системы, берется из KeKeNumberProcessors
  • BugCheckCode - Стоп-код ошибки
  • BugCheckParameter1, BugCheckParameter2, BugCheckParameter3, BugCheckParameter4 - Параметры ошибки
  • VersionUser - Версия чего-то, так и не выяснил чего. Первый байт обычно записан в 0, остальное не заполнено
  • PaeEnabled - =1 если включена поддержка Physical Address Extensions (PAE), =0 если выключена
  • KdDebuggerDataBlock - Виртуальный адрес очень важной структуры KdDebuggerDataBlock, описание которой я дам позже.

Вот так нехитро устроено начало дампа. Дальше по определенным смещениям запихнуты еще несколько блоков данных, для начала определим некоторые константы:

Код (Text):
  1.  
  2. // Data Blocks
  3. #define DH_PHYSICAL_MEMORY_BLOCK        25
  4. #define DH_CONTEXT_RECORD               200
  5. #define DH_EXCEPTION_RECORD             500
  6. #define DH_DUMP_TYPE                    994
  7. #define DH_CALLBACKS_STATUS             996   // (поле необязательно)
  8. #define DH_PRODUCT_TYPE                 997   // (поле необязательно, значение берется из KUSER_SHARED_DATA)
  9. #define DH_SUITE_MASK                   998   // (поле необязательно, значение берется из KUSER_SHARED_DATA)
  10. #define DH_REQUIRED_DUMP_SPACE          1000
  11. #define DH_INTERRUPT_TIME               1006  // (поле необязательно, значение берется из KUSER_SHARED_DATA)
  12. #define DH_SYSTEM_TIME                  1008  // (поле необязательно, значение берется из KUSER_SHARED_DATA)

Первая страница рассматривается как массив DWORD-ов и эти константы определяют индексы в этом массиве, где содержатся данные. То есть, другими словами, на си:

Код (Text):
  1. ULONG* blocks = (ULONG*) HeaderPage;
  2. //&blocks[DH_xxx] - адрес соответствующего блока

Рассмотрим теперь эти блоки по отдельности:

DH_PHYSICAL_MEMORY_BLOCK Тут содержится описатель физической памяти, структура PHYSICAL_MEMORY_DESCRIPTOR:

Код (Text):
  1. typedef unsigned long PFN_NUMBER;
  2.  
  3. typedef struct _PHYSICAL_MEMORY_RUN {
  4.     PFN_NUMBER BasePage;
  5.     PFN_NUMBER PageCount;
  6. } PHYSICAL_MEMORY_RUN, *PPHYSICAL_MEMORY_RUN;
  7.  
  8. typedef struct _PHYSICAL_MEMORY_DESCRIPTOR {
  9.     ULONG NumberOfRuns;
  10.     PFN_NUMBER NumberOfPages;
  11.     PHYSICAL_MEMORY_RUN Run[1];
  12. } PHYSICAL_MEMORY_DESCRIPTOR, *PPHYSICAL_MEMORY_DESCRIPTOR;

Она, вместе с массивами Run, описывает доступную физическую память. Обычно в системе есть три-четыре Run'а - сплошных набора физических страниц. Например на моей системе (512МБ физической памяти) они выглядят так:

Код (Text):
  1. PPHYSICAL_MEMORY_DESCRIPTOR:
  2. NumberOfRuns = 0x00000003  NumberOfPages = 0x0001ff8d
  3. PPHYSICAL_MEMORY_RUN[0]: BasePage = 0x00000001   PageCount = 0x0000009e
  4. PPHYSICAL_MEMORY_RUN[1]: BasePage = 0x00000100   PageCount = 0x00000eff
  5. PPHYSICAL_MEMORY_RUN[2]: BasePage = 0x00001000   PageCount = 0x0001eff0

То есть у меня доступны физические страницы с pfn'ами (номерами, другими словами) от 1 до 0x9e, от 0x100 до 0xeff и от 0x1000 до 0x1eff0.

Размер данного блока равен sizeof(PHYSICAL_MEMORY_DESCRIPTOR) - sizeof(PHYSICAL_MEMORY_RUN) + sizeof(PHYSICAL_MEMORY_RUN) * NumberOfRuns

  • DH_CONTEXT_RECORD Тут хранится структура CONTEXT, хранящая в себе контекст потока, вызвавшего ошибку. Дополнительных комментариев, думаю, не нужно.
  • DH_EXCEPTION_RECORD Аналогично предыдущему, здесь хранится структура EXCEPTION_RECORD с информацией об исключении, если она была доступна.
  • DH_DUMP_TYPE Здесь хранится дворд, определяющий непосредственно тип данного дампа - triage, summary или full dump. Соответствующие значения этого поля:

    Код (Text):
    1. // Dump types
    2. #define DUMP_TYPE_TRIAGE                4
    3. #define DUMP_TYPE_SUMMARY               2
    4. #define DUMP_TYPE_FULL                  1
  • DH_CALLBACKS_STATUS - Тут сохраняется NTSTATUS от вызова BugcheckCallbacks
  • DH_PRODUCT_TYPE - Тут сохраняется тип системы, описываемый перечислением

    Код (Text):
    1.  
    2. enum _NT_PRODUCT_TYPE {
    3.   NtProductWinNt = 0x1,
    4.   NtProductLanManNt = 0x2,
    5.   NtProductServer = 0x3,
    6. };
  • DH_SUITE_MASK - Здесь сохраняется поле KUSER_SHARED_DATA->SuiteMask, и, честно признаться, я не знаю, что оно означает.
  • DH_REQUIRED_DUMP_SPACE - Здесь хранится LARGE_INTEGER, содержащий в себе полный размер дампа в байтах.
  • DH_INTERRUPT_TIME - Назначение этого поля мне неизвестно, берется из KUSER_SHARED_DATA->InterruptTime
  • DH_SYSTEM_TIME - Это поле берется из KUSER_SHARED_DATA->SystemTime и содержит, насколько мне известно, текущий аптайм.

Вот, собственно, и всё с начальной страницей дампа. Дальнейшая структура дампа сильно зависит от его типа:

  1. Minidump (triage dump) - данный тип дампа не особо интересен, поэтому просто приведу частичную структуру TRIAGE_DUMP_HEADER без особых комментариев (анализ ее я еще не закончил), отметив лишь, что она содержит оффсеты некоторых структур, которые следуют дальше в сыром виде:

    Код (Text):
    1.  
    2. // Triage dump header
    3. typedef struct _TRIAGE_DUMP_HEADER {
    4.     ULONG   ServicePackBuild;   // 00
    5.     ULONG   SizeOfDump;         // 04
    6.     ULONG   ValidOffset;        // 08
    7.     ULONG   ContextOffset;      // 0c
    8.     ULONG   ExceptionOffset;    // 10
    9.     ULONG   MmOffset;           // 14
    10.     ULONG   UnloadedDriversOffset; // 18
    11.     ULONG   PrcbOffset;         // 1c
    12.     ULONG   ProcessOffset;      // 20
    13.     ULONG   ThreadOffset;       // 24
    14.     ULONG   Unknown1;           // 28
    15.     ULONG   Unknown2;           // 2c
    16.     ULONG   DriverListOffset;   // 30
    17.     ULONG   DriverCount;        // 34
    18.    
    19.     ...
    20.  
    21.     ULONG   TriageOptions;      // 44
    22.  
    23. } TRIAGE_DUMP_HEADER, *PTRIAGE_DUMP_HEADER;
  2. Kernel memory dump (Summary dump) - вторая страница дампа начинается со структуры

    Код (Text):
    1.  
    2. // Kernel summary dump header
    3. typedef struct _SUMMARY_DUMP_HEADER {
    4.     ULONG   Signature1;         // 00
    5.     ULONG   ValidDump;          // 04
    6.     ULONG   Signature2;         // 08
    7.     ULONG   HeaderSize;         // 0c
    8.     ULONG   BitmapSize;         // 10
    9.     ULONG   Pages;              // 14
    10.     ULONG   Unknown3;           // 18
    11.     ULONG   Unknown4;           // 1c
    12.  
    13. } SUMMARY_DUMP_HEADER, *PSUMMARY_DUMP_HEADER;
    • Signature1, Signature2 - Эти поля содержат сигнатуру "SDMP", точнее они просто не заполняются, а вся структура заполняется этими двордами перед заполнением.
    • ValidDump - Это поле содержит сигнатуру "DUMP"
    • HeaderSize - Полный размер заголовка дампа
    • BitmapSize - Размер битовой карты, следующей непосредственно за этой структурой - о ней позже.
    • Pages Количество страниц памяти ядра, включенных в дамп (совпадает с числом всех установленных битов битовой карты).

    Непосредственно за этой структурой следует битовая карта, число бит в ней равно числу физических страниц в системе, а установленные соответсвующие биты сообщают, включена ли конкретная страница в дамп, или нет. Для работы с такой битовой картой предназначены структура:

    Код (Text):
    1. typedef struct _RTL_BITMAP {
    2.     ULONG SizeOfBitMap;                     // Number of bits in bit map
    3.     PULONG Buffer;                          // Pointer to the bit map itself
    4. } RTL_BITMAP;
    5. typedef RTL_BITMAP *PRTL_BITMAP;
    и функции RtlInitializeBitMap, RtlClearAllBits, RtlSetAllBits, RtlFindClearBits, RtlFindSetBits, RtlCheckBit и другие.

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

  3. Full dump - после заглавной страницы дампы данного типа содержат подряд все физические страницы из всех memory runs, начиная с 0 и заканчивая NumberOfRuns-1.

II. Написание анализатора дампа

Теперь у нас есть достаточно знаний, чтобы написать простейший анализатор крешдампа. Загруженный дамп в нашем анализаторе будет представлен структурой:

Код (Text):
  1. // Mapped crash dump descriptor
  2. struct MappedCrashDump {
  3.     HANDLE hFile;
  4.     HANDLE hMapping;
  5.     union {
  6.         PBYTE lpMapping;
  7.         PDUMP_HEADER DumpHeader;
  8.         ULONG* DataBlocks;
  9.     };
  10.     ULONG *DumpType;
  11.     ULONG *DumpFlags;
  12.     PLARGE_INTEGER DumpSize;
  13.     PPHYSICAL_MEMORY_DESCRIPTOR PhysicalMemoryDescriptor;
  14.     PCONTEXT Context;
  15.     union {
  16.         // Summary dump type
  17.         struct {
  18.             PSUMMARY_DUMP_HEADER pSummaryDumpHeader;
  19.             RTL_BITMAP KernelMemoryMap;
  20.         };
  21.  
  22.         // Triage dump type
  23.         PTRIAGE_DUMP_HEADER pTriageDumpHeader;
  24.     };
  25. };

План действий будет таков - мы открываем файл, создаем объект проекции и проецируем первые 10 страниц файла дампа, остальное будет проецироваться по мере необходимости. Для реализации этого напишем функцию MapDumpPages, которая будет проецировать вид файла, если он еще не спроецирован:

Код (Text):
  1. //
  2. // This routine maps dump page at given file offset (DesiredAddress is the sum of BaseMapping and desired offset)
  3. //
  4.  
  5. PVOID MapDumpPage( HANDLE hMapping, PBYTE BaseMapping, PBYTE DesiredAddress )
  6. {
  7.     ULONG FileOffset = (ULONG) ( ( (ULONG_PTR)DesiredAddress-(ULONG_PTR)BaseMapping ) & 0xFFFF0000 );
  8.     PVOID Base = (PVOID)(ULONG_PTR) ( ((ULONG)(ULONG_PTR)DesiredAddress)&0xFFFF0000 );
  9.     MEMORY_BASIC_INFORMATION mbi = {0};
  10.     PVOID Ret = 0;
  11.  
  12.     VirtualQuery( DesiredAddress, &mbi, sizeof(mbi) );
  13.  
  14.     if( mbi.State != MEM_COMMIT ) {
  15.         Ret = MapViewOfFileEx(  hMapping,
  16.                                 FILE_MAP_READ,
  17.                                 0,
  18.                                 FileOffset,
  19.                                 0x10000,
  20.                                 Base
  21.                                 );
  22.         if( Ret == Base ) {
  23.             return DesiredAddress;
  24.         }
  25.  
  26.         // Loaded at different address. Fail
  27.         UnmapViewOfFile( Ret );
  28.         return NULL;
  29.     }
  30.  
  31.     return DesiredAddress;
  32. }

Дальше мы реализуем функцию, которая получит страницу из summary-дампа по её физическому адресу в момент краха:

Код (Text):
  1. PBYTE GetSummaryDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )
  2. {
  3.     PBYTE StartingAddress = (PBYTE)(ULONG_PTR) ( (ULONG)(ULONG_PTR)CrashDump->pSummaryDumpHeader
  4.                             + CrashDump->pSummaryDumpHeader->HeaderSize - 0x1000 );
  5.     ULONG NumberOfPage = (ULONG) (AddressAtCrashTime / 0x1000);
  6.     ULONG OffsetInPage = (ULONG) (AddressAtCrashTime & 0xFFF);
  7.  
  8.     if( NumberOfPage >= CrashDump->KernelMemoryMap.SizeOfBitMap )
  9.         return NULL;
  10.  
  11.     if( RtlCheckBit( &CrashDump->KernelMemoryMap, NumberOfPage ) == 0 )
  12.         return NULL; // not included in dump
  13.  
  14.     // Calculate page number in dump
  15.     ULONG PageNumber = 0;
  16.  
  17.     for( ULONG i=0; i<CrashDump->KernelMemoryMap.SizeOfBitMap; i++ ) {
  18.         if( i == NumberOfPage ) {
  19.             PBYTE Result = StartingAddress + PageNumber*0x1000 + OffsetInPage;
  20.  
  21.             return (PBYTE) MapDumpPage( CrashDump->hMapping, CrashDump->lpMapping, Result );
  22.         }
  23.  
  24.         if( RtlCheckBit( &CrashDump->KernelMemoryMap, i ) ) {
  25.             PageNumber++;
  26.         }
  27.     }
  28.  
  29.     return NULL;
  30. }

Тут реализована работа с битовой картой, чтобы узнать, присутствует ли страница в дампе, и чтобы узнать ее номер на диске. После этого она проецируется с диска в оперативную память, если еще не спроецирована.

Дальше нужно определить аналогичную функцию для полного дампа. Для начала нужно научиться конвертировать PFN страницы в памяти в PFN страницы на диске, и наоборот. С этим справятся следующие две функции:

Код (Text):
  1. PFN_NUMBER GetPhysicalPFN( PPHYSICAL_MEMORY_DESCRIPTOR ppmd, PFN_NUMBER DumpPFN )
  2. {
  3.     PFN_NUMBER iBuffPage, iPage = iBuffPage = DumpPFN;
  4.  
  5.     if( iBuffPage >= ppmd->NumberOfPages )
  6.         return -1;
  7.  
  8.     // Calculate page in memory
  9.     ULONG NumberOfRunsRequired = 0;
  10.     PFN_NUMBER TotalPageCount = 0;
  11.  
  12.     for( ; NumberOfRunsRequired<ppmd->NumberOfRuns; NumberOfRunsRequired++ )
  13.     {
  14.         PPHYSICAL_MEMORY_RUN Runs = ppmd->Run;
  15.  
  16.         if( iBuffPage >= TotalPageCount &&
  17.             iBuffPage < TotalPageCount + Runs[NumberOfRunsRequired].PageCount )
  18.             break;
  19.  
  20.         TotalPageCount += (Runs[NumberOfRunsRequired].PageCount);
  21.     }
  22.  
  23.     PFN_NUMBER PreviousEnd = 0;
  24.     NumberOfRunsRequired ++;
  25.  
  26.     for( ULONG i=0; i<NumberOfRunsRequired; i++ )
  27.     {
  28.         PPHYSICAL_MEMORY_RUN Runs = ppmd->Run;
  29.  
  30.         iPage += (Runs[i].BasePage - PreviousEnd);
  31.         PreviousEnd = Runs[i].BasePage + Runs[i].PageCount;
  32.     }
  33.  
  34.     return iPage;
  35. }
  36.  
  37. PFN_NUMBER GetDumpPFN( PPHYSICAL_MEMORY_DESCRIPTOR ppmd, PFN_NUMBER PhysicalPFN )
  38. {
  39.     for( int run=0; run<ppmd->NumberOfRuns; run++ )
  40.     {
  41.         PPHYSICAL_MEMORY_RUN Runs = ppmd->Run;
  42.  
  43.         if( PhysicalPFN >= Runs[run].BasePage &&
  44.             PhysicalPFN < (Runs[run].BasePage + Runs[run].PageCount) )
  45.             break;
  46.     }
  47.  
  48.     if( run == ppmd->NumberOfRuns )
  49.         return -1;
  50.  
  51.     PFN_NUMBER iDumpPFN = 0;
  52.  
  53.     for( int i=0; i<run; i++ )
  54.     {
  55.         iDumpPFN += ppmd->Run[i].PageCount;
  56.     }
  57.  
  58.     iDumpPFN += PhysicalPFN - ppmd->Run[i].BasePage;
  59.  
  60.     return iDumpPFN;
  61. }

Дальше можно описать функцию, получающую страницу из дампа по ее физическому адресу на момент краха и общую функцию, получающую тип дампа и вызывающую нужную из двух функций получения адреса:

Код (Text):
  1. PBYTE GetCompleteDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )
  2. {
  3.     PBYTE StartingAddress = (PBYTE)(ULONG_PTR)( (ULONG)(ULONG_PTR)CrashDump->lpMapping + 0x1000 );
  4.     PFN_NUMBER PFN = (ULONG) (AddressAtCrashTime >> 12);
  5.     ULONG OffsetInPage = (ULONG) (AddressAtCrashTime & 0xFFF);
  6.  
  7.     // Calculate page number in dump
  8.     for( ULONG i=0; i<CrashDump->PhysicalMemoryDescriptor->NumberOfRuns; i++ ) {    
  9.         PPHYSICAL_MEMORY_RUN Runs = (PPHYSICAL_MEMORY_RUN) &CrashDump->PhysicalMemoryDescriptor->Run;
  10.  
  11.         if( PFN >= Runs[i].BasePage && PFN < (Runs[i].BasePage + Runs[i].PageCount) ) {
  12.        
  13.             PFN_NUMBER DumpPFN = GetDumpPFN( CrashDump->PhysicalMemoryDescriptor, PFN );
  14.             PBYTE Result = StartingAddress + DumpPFN*0x1000 + OffsetInPage;
  15.  
  16.             return (PBYTE) MapDumpPage( CrashDump->hMapping, CrashDump->lpMapping, Result );
  17.         }
  18.     }
  19.     return NULL;
  20. }
  21.  
  22. PBYTE GetDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )
  23. {
  24.     if( *CrashDump->DumpType == DUMP_TYPE_SUMMARY ) {
  25.  
  26.         return GetSummaryDumpPagesByPhysicalAddress( CrashDump, AddressAtCrashTime );
  27.  
  28.     } else if( *CrashDump->DumpType == DUMP_TYPE_COMPLETE ) {
  29.  
  30.         return GetCompleteDumpPagesByPhysicalAddress( CrashDump, AddressAtCrashTime );
  31.  
  32.     } else {
  33.  
  34.         return NULL;
  35.     }
  36. }

Теперь нам нужно научиться транслировать виртуальные адреса в физические, и тогда мы полностью сможем вынуть данные по любому виртуальному адресу из дампа. Для начала определим структуры каталогов PDPE, PDE и PTE, которые нам могут понадобиться:

Код (Text):
  1. //
  2. // Page Directory Entry
  3. //
  4.  
  5. struct PDE {
  6.     DWORD  Present:1;
  7.     DWORD  ReadWrite:1;
  8.     DWORD  UserSupervisor:1;
  9.     DWORD  WriteThrough:1;
  10.     DWORD  CacheDisabled:1;
  11.     DWORD  Accessed:1;
  12.     DWORD  Reserved:1;      // Dirty, ignored
  13.     DWORD  PageSize:1;
  14.     DWORD  GlobalPage:1;    // Ignored
  15.     DWORD  Available:3;
  16.     DWORD  Pte:19;
  17. };
  18.  
  19.  
  20. //
  21. // Page Table Entry
  22. //
  23.  
  24. struct PTE {
  25.     DWORD  Present:1;
  26.     DWORD  ReadWrite:1;
  27.     DWORD  UserSupervisor:1;
  28.     DWORD  WriteThrough:1;
  29.     DWORD  CacheDisabled:1;
  30.     DWORD  Accessed:1;
  31.     DWORD  Dirty:1;
  32.     DWORD  PageTableAttributeIndex:1;
  33.     DWORD  GlobalPage:1;
  34.     DWORD  Available:3;
  35.     DWORD  PageFrameNumber:19;
  36. };
  37.  
  38. // Virtual address
  39. struct VIRTUAL_ADDRESS {
  40.     DWORD  Offset:12;
  41.     DWORD  Table:10;
  42.     DWORD  Directory:10;
  43. };
  44.  
  45.  
  46. //
  47. // Page Directory Entry in PAE mode
  48. //
  49.  
  50. #define QWORD ULONGLONG
  51.  
  52. struct LongPDE {
  53.     QWORD  Present:1;
  54.     QWORD  ReadWrite:1;
  55.     QWORD  UserSupervisor:1;
  56.     QWORD  WriteThrough:1;
  57.     QWORD  CacheDisabled:1;
  58.     QWORD  Accessed:1;
  59.     QWORD  Reserved:1;      // Dirty, ignored
  60.     QWORD  PageSize:1;
  61.     QWORD  GlobalPage:1;    // Ignored
  62.     QWORD  Available:3;
  63.     QWORD  Pte:24;
  64.     QWORD  ReservedHigh:28;
  65. };
  66.  
  67.  
  68. //
  69. // Page Table Entry in PAE mode
  70. //
  71.  
  72. struct LongPTE {
  73.     QWORD  Present:1;
  74.     QWORD  ReadWrite:1;
  75.     QWORD  UserSupervisor:1;
  76.     QWORD  WriteThrough:1;
  77.     QWORD  CacheDisabled:1;
  78.     QWORD  Accessed:1;
  79.     QWORD  Dirty:1;
  80.     QWORD  PageTableAttributeIndex:1;
  81.     QWORD  GlobalPage:1;
  82.     QWORD  Available:3;
  83.     QWORD  PageFrameNumber:24;
  84.     QWORD  ReservedHigh:28;
  85. };
  86.  
  87. //
  88. // Page Directory Pointer Table (PAE mode only)
  89. //
  90.  
  91. struct PDPE {
  92.     QWORD  Present:1;
  93.     QWORD  Reserved1:2;
  94.     QWORD  WriteThough:1;
  95.     QWORD  CacheDisabled:1;
  96.     QWORD  Reserved2:4;
  97.     QWORD  Available:3;
  98.     QWORD  Pdt:24;
  99.     QWORD  ReservedHigh:28;
  100. };
  101.  
  102. // Virtual address (PAE)
  103. struct PAE_VIRTUAL_ADDRESS {
  104.     DWORD  Offset:12;
  105.     DWORD  Table:9;
  106.     DWORD  Directory:9;
  107.     DWORD  DirectoryPointer:2;
  108. };

Тут определены две серии структур - без включенного PAE и с включенным PAE. Напомню, что в режиме PAE (Physical Address Extensions) адресация стала трехуровневой. Регистр CR3 больше не указывает на PDE, теперь он указывает на массив Page Directory Pointer Entries - PDPE, которых должно быть 4 штуки. Каждый из PDPE указывает на массив PDE, а уже PDE указывают на PTE. Структура виртуального адреса немного изменилась, у PDE и PTE "откусили" по одному биту в пользу двухбитового поля номера PDPE. С учетом всего этого, функция трансляции адресов:

Код (Text):
  1. //
  2. // Translates virtual address to physical address
  3. //
  4.  
  5. ULONGLONG VirtualToPhysical( MappedCrashDump *CrashDump, ULONG VirtualAddress )
  6. {
  7.     ULONG CR3 = CrashDump->DumpHeader->DirectoryTableBase;
  8.  
  9.     CR3 &= 0xFFFFFFF0;   // clear flags in cr3
  10.  
  11.     if( CrashDump->DumpHeader->PaeEnabled )
  12.     {
  13.         // PAE enabled. Use 3-level addressing system: PDPE->PDE->PTE->Page
  14.         PAE_VIRTUAL_ADDRESS va;
  15.  
  16.         *(ULONG*)&va = VirtualAddress;
  17.  
  18.         PDPE* dirptr = (PDPE*)GetDumpPagesByPhysicalAddress( CrashDump, CR3 );
  19.  
  20.         if( dirptr != NULL && dirptr[va.DirectoryPointer].Present )
  21.         {
  22.             LongPDE *dir = (LongPDE*)GetDumpPagesByPhysicalAddress( CrashDump,
  23.                             dirptr[va.DirectoryPointer].Pdt << 12 );
  24.  
  25.             if( dir != NULL && dir[va.Directory].Present )
  26.             {
  27.                 LongPTE* tbl = (LongPTE*)GetDumpPagesByPhysicalAddress( CrashDump,
  28.                                 dir[va.Directory].Pte << 12 );
  29.  
  30.                 if( tbl != NULL && tbl[va.Table].Present )
  31.                 {
  32.                     return ( tbl[va.Table].PageFrameNumber << 12 ) | va.Offset;
  33.                 }
  34.             }
  35.         }
  36.     }
  37.     else
  38.     {
  39.         // PAE disabled. Use 2-level addressing system: PDE->PTE->Page
  40.         VIRTUAL_ADDRESS va;
  41.         *(ULONG*)&va = VirtualAddress;
  42.  
  43.         PDE *dir = (PDE*)GetDumpPagesByPhysicalAddress( CrashDump, CR3 );
  44.        
  45.         if( dir != NULL && dir[va.Directory].Present )
  46.         {
  47.             PTE* tbl = (PTE*)GetDumpPagesByPhysicalAddress( CrashDump, dir[va.Directory].Pte << 12 );
  48.  
  49.             if( tbl != NULL && tbl[va.Table].Present )
  50.             {
  51.                 return ( tbl[va.Table].PageFrameNumber << 12 ) | va.Offset;
  52.             }
  53.         }
  54.     }
  55.  
  56.     return NULL;
  57. }
  58.  
  59. //
  60. // Translates virtual address to physical address and loads that physical pages
  61. //
  62.  
  63. PVOID RetrieveDumpData( MappedCrashDump* CrashDump, PVOID VirtualAddress )
  64. {
  65.     return GetDumpPagesByPhysicalAddress( CrashDump, VirtualToPhysical( CrashDump,
  66.                                         (ULONG)(ULONG_PTR)VirtualAddress ) );
  67. }

Всё готово. Напишем небольшой демонстрационный код для анализа крешдампа:

Код (Text):
  1. struct CONST_DESCRIPTION {
  2.     ULONG   Value;
  3.     LPSTR   Desc;
  4. #define DEFINE_STRING(x) { x, #x }
  5. #define TABLE_END { 0, 0 }
  6. };
  7.  
  8. CONST_DESCRIPTION MachineTypes[] = {
  9.     DEFINE_STRING( IMAGE_FILE_MACHINE_I386 ),
  10.     DEFINE_STRING( IMAGE_FILE_MACHINE_IA64 ),
  11.     TABLE_END
  12. };
  13.  
  14. CONST_DESCRIPTION DumpTypes[] = {
  15.     DEFINE_STRING( DUMP_TYPE_TRIAGE ),
  16.     DEFINE_STRING( DUMP_TYPE_SUMMARY ),
  17.     DEFINE_STRING( DUMP_TYPE_FULL ),
  18.     TABLE_END
  19. };
  20.  
  21. // Bugcheck descriptions
  22. typedef ULONG NTSTATUS;
  23. #include "D:\Progs\driverdev\bcdesc.h"    // багчек коды бсодов, я не прилагаю
  24.                                           // этот файл, скажу лишь что там лежит
  25.                                           // функция BugCheckDescf, получающая
  26.                                           // имя бсода по его номеру.
  27.  
  28. // Получение имени константы по значению
  29. LPSTR LookupConstDesc( CONST_DESCRIPTION* Table, ULONG Value )
  30. {
  31.     while( Table->Desc ) {
  32.         if( Table->Value == Value ) {
  33.             return Table->Desc;
  34.         }
  35.         Table ++;
  36.     }
  37.     return "(unknown)";
  38. }
  39.  
  40. void ExtractDumpHeader( MappedCrashDump* CrashDump, char* saveto )
  41. {
  42.     HANDLE hFile = CreateFile( saveto, GENERIC_WRITE, 0, 0, OPEN_ALWAYS, 0, 0 );
  43.     DWORD wr;
  44.     WriteFile( hFile, CrashDump->lpMapping, 0x1000, &wr, 0 );
  45.     SetEndOfFile( hFile );
  46.     CloseHandle( hFile );
  47. }
  48.  
  49.  
  50. int AnalyseDump( char* filename )
  51. {
  52.     MappedCrashDump CrashDump = {0};
  53.     CrashDump.hFile = INVALID_HANDLE_VALUE;
  54.  
  55.     __try
  56.     {
  57.         CrashDump.hFile = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0 );
  58.         if( CrashDump.hFile == INVALID_HANDLE_VALUE )
  59.             return 0;
  60.  
  61.         CrashDump.hMapping = CreateFileMapping( CrashDump.hFile, 0, PAGE_READONLY, 0, 0, NULL );
  62.         if( CrashDump.hMapping == NULL )
  63.             return 0;
  64.  
  65.         CrashDump.lpMapping = (PBYTE) MapViewOfFile( CrashDump.hMapping, FILE_MAP_READ, 0, 0, 0x10000 ); //0x12000 );
  66.         if( CrashDump.lpMapping == NULL )
  67.             return 0;
  68.  
  69.         PDUMP_HEADER hdr = CrashDump.DumpHeader;
  70.  
  71.         if( hdr->ValidDump != 'PMUD' || hdr->Signature != 'EGAP' ) {
  72.             printf("Invalid dump header\n");
  73.             return 0;
  74.         }
  75.  
  76.         printf("Crash dump '%s' analysing started. Version: %d.%d\n\n", filename,
  77.                 hdr->MajorVersion, hdr->MinorVersion);
  78.  
  79.         // Header
  80.         printf("CR3 = 0x%08x                   PfnDatabase = 0x%08x\n"
  81.                "PsLoadedModuleList = 0x%08x    PsActiveProcessHead = 0x%08x\n",
  82.                 hdr->DirectoryTableBase, (ULONG_PTR)hdr->PfnDataBase,
  83.                (ULONG_PTR)hdr->PsLoadedModuleList, (ULONG_PTR)hdr->PsActiveProcessHead );
  84.         printf("Machine type: %s,  NumberProcessors: %d\n",
  85.                 LookupConstDesc( MachineTypes, hdr->MachineImageType ), hdr->NumberProcessors);
  86.         printf("PaeEnabled = %d, KdDebuggerDataBlock = 0x%08x\n", hdr->PaeEnabled,
  87.                 (ULONG_PTR)hdr->KdDebuggerDataBlock);
  88.         printf("\n");
  89.  
  90.         // Bug check
  91.         printf("Bugcheck code %s (0x%08x)\n", BugCheckDescf(hdr->BugCheckCode), hdr->BugCheckCode);
  92.         printf("Arguments[0] = 0x%08x\n", hdr->BugCheckParameter1);
  93.         printf("Arguments[1] = 0x%08x\n", hdr->BugCheckParameter2);
  94.         printf("Arguments[2] = 0x%08x\n", hdr->BugCheckParameter3);
  95.         printf("Arguments[3] = 0x%08x\n", hdr->BugCheckParameter4);
  96.         printf("\n");
  97.  
  98.         // Data blocks
  99.         ULONG* block = CrashDump.DataBlocks;
  100.  
  101.         // Dump type & size
  102.         CrashDump.DumpType  = &block[ DH_DUMP_TYPE     ];
  103.         CrashDump.DumpFlags = &block[ DH_DUMP_TYPE + 1 ];
  104.         CrashDump.DumpSize = (PLARGE_INTEGER) &block[ DH_REQUIRED_DUMP_SPACE ];
  105.         printf( "Dump type: %s, DumpSize: %d bytes (0x%08x)\n",
  106.                 LookupConstDesc( DumpTypes, *CrashDump.DumpType ),
  107.                 CrashDump.DumpSize->LowPart,
  108.                 CrashDump.DumpSize->LowPart );
  109.         printf("\n");
  110.  
  111.         // Physical memory descriptor
  112.         CrashDump.PhysicalMemoryDescriptor = (PPHYSICAL_MEMORY_DESCRIPTOR)( &block[DH_PHYSICAL_MEMORY_BLOCK] );
  113.         if( CrashDump.PhysicalMemoryDescriptor->NumberOfRuns == 'EGAP' )
  114.         {
  115.             printf("PPHYSICAL_MEMORY_DESCRIPTOR: Invalid\n");
  116.         }
  117.         else
  118.         {
  119.             printf( "PPHYSICAL_MEMORY_DESCRIPTOR:\nNumberOfRuns = 0x%08x  NumberOfPages = 0x%08x\n",
  120.                     CrashDump.PhysicalMemoryDescriptor->NumberOfRuns,
  121.                     CrashDump.PhysicalMemoryDescriptor->NumberOfPages );
  122.             for( ULONG i=0;i<CrashDump.PhysicalMemoryDescriptor->NumberOfRuns;i++ )
  123.             {
  124.                 printf( "PPHYSICAL_MEMORY_RUN[%d]: BasePage = 0x%08x   PageCount = 0x%08x\n",
  125.                         i,
  126.                         CrashDump.PhysicalMemoryDescriptor->Run[i].BasePage,
  127.                         CrashDump.PhysicalMemoryDescriptor->Run[i].PageCount );
  128.             }
  129.         }
  130.         printf("\n");
  131.  
  132.         // Context record:
  133.         CrashDump.Context = (PCONTEXT)( &block[DH_CONTEXT_RECORD] );
  134.         printf( "Context record:\nEip = 0x%08x   ESP = 0x%08x   EBP = 0x%08x\n",
  135.                 CrashDump.Context->Eip,
  136.                 CrashDump.Context->Esp,
  137.                 CrashDump.Context->Ebp );
  138.         printf("EAX=%08x EBX=%08x ECX=%08x EDX=%08x ESI=%08x EDI=%08x\n",
  139.             CrashDump.Context->Eax,
  140.             CrashDump.Context->Ebx,
  141.             CrashDump.Context->Ecx,
  142.             CrashDump.Context->Edx,
  143.             CrashDump.Context->Esi,
  144.             CrashDump.Context->Edi,
  145.             CrashDump.Context->Eax);
  146.         printf("\n");
  147.  
  148.         // Analyse dump
  149.         if( *CrashDump.DumpType == DUMP_TYPE_TRIAGE )
  150.         {
  151.             //
  152.             // Minidump
  153.             //
  154.  
  155.             CrashDump.pTriageDumpHeader = (PTRIAGE_DUMP_HEADER)( CrashDump.lpMapping + 0x1000 );
  156.  
  157.             printf("Analysing triage dump header\n");
  158.  
  159.             printf("[-] Not implemented\n");
  160.  
  161.             __asm nop;
  162.         }
  163.         else if( *CrashDump.DumpType == DUMP_TYPE_SUMMARY )
  164.         {
  165.             //
  166.             // Kernel summary dump - only kernel address space available
  167.             //
  168.  
  169.             CrashDump.pSummaryDumpHeader = (PSUMMARY_DUMP_HEADER) ( CrashDump.lpMapping + 0x1000 );
  170.             CrashDump.KernelMemoryMap.SizeOfBitMap = CrashDump.pSummaryDumpHeader->BitmapSize;
  171.             CrashDump.KernelMemoryMap.Buffer = (PULONG) ( CrashDump.pSummaryDumpHeader + 1 );
  172.  
  173.             if( CrashDump.pSummaryDumpHeader->ValidDump != 'PMUD' ) {
  174.                 printf("Invalid summary dump header\n");
  175.                 return 0;
  176.             }
  177.  
  178.             printf("Analyzing summary dump header\n");
  179.             printf( "HeaderSize = 0x%08x   BitmapSize = 0x%08x\n",
  180.                     CrashDump.pSummaryDumpHeader->HeaderSize,
  181.                     CrashDump.pSummaryDumpHeader->BitmapSize );
  182.             printf( "Number of kernel pages in dump: 0x%08x\n\n",
  183.                     CrashDump.pSummaryDumpHeader->Pages );
  184.  
  185.  
  186.             ULONG Virtual = CrashDump.Context->Eip;
  187.             ULONGLONG Physical;
  188.             Physical = VirtualToPhysical( &CrashDump, Virtual  );
  189.  
  190.             printf("Translating virtual address [0x%08x]: 0x%08x\n", Virtual, Physical);
  191.  
  192.             PBYTE MappedEIP = (PBYTE)RetrieveDumpData( &CrashDump, (void*)(ULONG_PTR)CrashDump.Context->Eip );
  193.  
  194.             if( MappedEIP )
  195.             {
  196.                 printf("Bytes at [EIP=%08x]: %02x %02x %02x %02x  %02x %02x %02x %02x\n",
  197.                             CrashDump.Context->Eip,
  198.                             MappedEIP[0], MappedEIP[1], MappedEIP[2], MappedEIP[3],
  199.                             MappedEIP[4], MappedEIP[5], MappedEIP[6], MappedEIP[7] );
  200.             }
  201.             else printf("Memory pointed by EIP is not present\n");
  202.  
  203.             __asm nop;
  204.         }
  205.         else if( *CrashDump.DumpType == DUMP_TYPE_COMPLETE )
  206.         {
  207.             //
  208.             // Complete memory dump - full address space available
  209.             //
  210.  
  211.             ULONG Virtual = CrashDump.Context->Eip;
  212.             ULONGLONG Physical;
  213.             Physical = VirtualToPhysical( &CrashDump, Virtual  );
  214.  
  215.             printf("Translating virtual address [0x%08x]: 0x%08x\n", Virtual, Physical);
  216.  
  217.             PBYTE MappedEIP = (PBYTE)RetrieveDumpData( &CrashDump, (void*)(ULONG_PTR)CrashDump.Context->Eip );
  218.  
  219.             printf("Bytes at [EIP=%08x]: %02x %02x %02x %02x  %02x %02x %02x %02x\n", CrashDump.Context->Eip,
  220.                         MappedEIP[0], MappedEIP[1], MappedEIP[2], MappedEIP[3],
  221.                         MappedEIP[4], MappedEIP[5], MappedEIP[6], MappedEIP[7] );
  222.  
  223.             ExtractDumpHeader( &CrashDump, "crashdump.hdr" );
  224.  
  225.             __asm nop;
  226.  
  227.         }
  228.  
  229.  
  230.         printf("\nDump analysis finished\n");
  231.     }
  232.     __finally
  233.     {
  234.         if( CrashDump.lpMapping )
  235.             UnmapViewOfFile( CrashDump.lpMapping );
  236.  
  237.         if( CrashDump.hMapping )
  238.             CloseHandle( CrashDump.hMapping );
  239.  
  240.         if( CrashDump.hFile != INVALID_HANDLE_VALUE )
  241.             CloseHandle( CrashDump.hFile );
  242.  
  243.         Sleep(INFINITE);
  244.     }
  245.  
  246.     return 0;
  247. }
  248.  
  249.  
  250. int main()
  251. {
  252.     AnalyseDump( "C:\\gr8lkd.dmp" );
  253.     //AnalyseDump( "D:\\memory.dmp" );
  254.  
  255.     return 0;
  256. }

В нем уже забит путь к моему крешдампу c:\gr8lkd.dmp от моей утилиты gr8lkd.

Вывод примерно следующий:

Код (Text):
  1. Crash dump 'C:\gr8lkd.dmp' analysing started. Version: 15.2600
  2.  
  3. CR3 = 0x00373000                   PfnDatabase = 0x80557b48
  4. PsLoadedModuleList = 0x805531a0    PsActiveProcessHead = 0x80559258
  5. Machine type: IMAGE_FILE_MACHINE_I386,  NumberProcessors: 1
  6. PaeEnabled = 1, KdDebuggerDataBlock = 0x80544ce0
  7.  
  8. Bugcheck code KMODE_EXCEPTION_NOT_HANDLED (0x0000001e)
  9. Arguments[0] = 0x80000004
  10. Arguments[1] = 0xf3b21315
  11. Arguments[2] = 0x00000000
  12. Arguments[3] = 0x00000000
  13.  
  14. Dump type: DUMP_TYPE_FULL, DumpSize: 536403968 bytes (0x1ff8e000)
  15.  
  16. PPHYSICAL_MEMORY_DESCRIPTOR:
  17. NumberOfRuns = 0x00000003  NumberOfPages = 0x0001ff8d
  18. PPHYSICAL_MEMORY_RUN[0]: BasePage = 0x00000001   PageCount = 0x0000009e
  19. PPHYSICAL_MEMORY_RUN[1]: BasePage = 0x00000100   PageCount = 0x00000eff
  20. PPHYSICAL_MEMORY_RUN[2]: BasePage = 0x00001000   PageCount = 0x0001eff0
  21.  
  22. Context record:
  23. Eip = 0xf3b21315   ESP = 0xf8ab4928   EBP = 0xf8ab4c1c
  24. EAX=00000000 EBX=816b7000 ECX=00000000 EDX=00004e24 ESI=00000000 EDI=f8ab4c00
  25.  
  26. Translating virtual address [0xf3b21315]: 0x1d9ac315
  27. Bytes at [EIP=f3b21315]: 58 89 85 d0  fd ff ff 9c
  28.  
  29. Dump analysis finished

III. Создание собственного дампа.

Мы подошли к самому интересному моменту, а именно к созданию собственного файда дампа.

Сперва стоит описать недокументированную структуру KdDebuggerDataBlock, поскольку она нам очень поможет при создании дампа. Итак, по адресу KdDebuggerDataBlock лежит следующее:

Код (Text):
  1. template <class T>
  2. struct PFUNC {
  3.     T  VirtualAddress;
  4.     ULONG  ZeroField;
  5. };
  6.  
  7. typedef struct _KD_DEBUGGER_DATA_BLOCK {
  8.     ULONG  Unknown1[4];
  9.     ULONG  ValidBlock; // 'GBDK'
  10.     ULONG  Size; // 0x290
  11.     PFUNC<PVOID>  _imp__VidInitialize;
  12.     PFUNC<PVOID>  RtlpBreakWithStatusInstruction;
  13.     ULONG  Unknown2[4];
  14.     PFUNC<PVOID>  KiCallUserMode;
  15.     ULONG  Unknown3[2];
  16.     PFUNC<PVOID>  PsLoadedModuleList;
  17.     PFUNC<PVOID>  PsActiveProcessHead;
  18.     PFUNC<PVOID>  PspCidTable;
  19.     PFUNC<PVOID>  ExpSystemResourcesList;
  20.     PFUNC<PVOID>  ExpPagedPoolDescriptor;
  21.     PFUNC<PVOID>  ExpNumberOfPagedPools;
  22.     PFUNC<PVOID>  KeTimeIncrement;
  23.     PFUNC<PVOID>  KeBugCheckCallbackListHead;
  24.     PFUNC<PVOID>  KiBugCheckData;
  25.     PFUNC<PVOID>  IopErrorLogListHead;
  26.     PFUNC<PVOID>  ObpRootDirectoryObject;
  27.     PFUNC<PVOID>  ObpTypeObjectType;
  28.     PFUNC<PVOID>  MmSystemCacheStart;
  29.     PFUNC<PVOID>  MmSystemCacheEnd;
  30.     PFUNC<PVOID>  MmSystemCacheWs;
  31.     PFUNC<PVOID>  MmPfnDatabase;
  32.     PFUNC<PVOID>  MmSystemPtesStart;
  33.     PFUNC<PVOID>  MmSystemPtesEnd;
  34.     PFUNC<PVOID>  MmSubsectionBase;
  35.     PFUNC<PVOID>  MmNumberOfPagingFiles;
  36.     PFUNC<PVOID>  MmLowestPhysicalPage;
  37.     PFUNC<PVOID>  MmHighestPhysicalPage;
  38.     PFUNC<PVOID>  MmNumberOfPhysicalPages;
  39.     PFUNC<PVOID>  MmMaximumNonPagedPoolInBytes;
  40.     PFUNC<PVOID>  MmNonPagedSystemStart;
  41.     PFUNC<PVOID>  MmNonPagedPoolStart;
  42.     PFUNC<PVOID>  MmNonPagedPoolEnd;
  43.     PFUNC<PVOID>  MmPagedPoolStart;
  44.     PFUNC<PVOID>  MmPagedPoolEnd;
  45.     PFUNC<PVOID>  MmPagedPoolInfo;
  46.     PFUNC<PVOID>  Unknown4;
  47.     PFUNC<PVOID>  MmSizeOfPagedPoolInBytes;
  48.     PFUNC<PVOID>  MmTotalCommitLimit;
  49.     PFUNC<PVOID>  MmTotalCommittedPages;
  50.     PFUNC<PVOID>  MmSharedCommit;
  51.     PFUNC<PVOID>  MmDriverCommit;
  52.     PFUNC<PVOID>  MmProcessCommit;
  53.     PFUNC<PVOID>  MmPagedPoolCommit;
  54.     PFUNC<PVOID>  Unknown5;
  55.     PFUNC<PVOID>  MmZeroedPageListHead;
  56.     PFUNC<PVOID>  MmFreePageListHead;
  57.     PFUNC<PVOID>  MmStandbyPageListHead;
  58.     PFUNC<PVOID>  MmModifiedPageListHead;
  59.     PFUNC<PVOID>  MmModifiedNoWritePageListHead;
  60.     PFUNC<PVOID>  MmAvailablePages;
  61.     PFUNC<PVOID>  MmResidentAvailablePages;
  62.     PFUNC<PVOID>  PoolTrackTable;
  63.     PFUNC<PVOID>  NonPagedPoolDescriptor;
  64.     PFUNC<PVOID>  MmHighestUserAddress;
  65.     PFUNC<PVOID>  MmSystemRangeStart;
  66.     PFUNC<PVOID>  MmUserProbeAddress;
  67.     PFUNC<PVOID>  KdPrintCircularBuffer;
  68.     PFUNC<PVOID>  KdPrintWritePointer;
  69.     PFUNC<PVOID>  KdPrintWritePointer2;
  70.     PFUNC<PVOID>  KdPrintRolloverCount;
  71.     PFUNC<PVOID>  MmLoadedUserImageList;
  72.     PFUNC<PVOID>  NtBuildLab;
  73.     PFUNC<PVOID>  Unknown6;
  74.     PFUNC<PVOID>  KiProcessorBlock;
  75.     PFUNC<PVOID>  MmUnloadedDrivers;
  76.     PFUNC<PVOID>  MmLastUnloadedDriver;
  77.     PFUNC<PVOID>  MmTriageActionTaken;
  78.     PFUNC<PVOID>  MmSpecialPoolTag;
  79.     PFUNC<PVOID>  KernelVerifier;
  80.     PFUNC<PVOID>  MmVerifierData;
  81.     PFUNC<PVOID>  MmAllocateNonPagedPool;
  82.     PFUNC<PVOID>  MmPeakCommitment;
  83.     PFUNC<PVOID>  MmTotalCommitLimitMaximum;
  84.     PFUNC<PVOID>  CmNtCSDVersion;
  85.     PFUNC<PPHYSICAL_MEMORY_DESCRIPTOR*>  MmPhysicalMemoryBlock;
  86.     PFUNC<PVOID>  MmSessionBase;
  87.     PFUNC<PVOID>  MmSessionSize;
  88.     PFUNC<PVOID>  Unknown7;
  89.  
  90. } KD_DEBUGGER_DATA_BLOCK, *PKD_DEBUGGER_DATA_BLOCK;

Это же очень удобно! Достаточно получить лишь один неэкспортируемый адрес KdDebuggerDataBlock, как все остальные нужные адреса как на ладони. Поле ValidBlock должно содержать сигнатуру 'GBDK', а Size должно точно равняться sizeof(KD_DEBUGGER_DATA_BLOCK).

Получить адрес KdDebuggerDataBlock можно через экспортируемый символ KeCapturePersistentThreadState, а именно (для версии xp 2600): KdDebuggerDataBlock = *(PVOID*)((ULONG)KeCapturePersistentThreadState + *(ULONG*)((ULONG)KeCapturePersistentThreadState + 0xC )+ 0x11);

Не очень симпотично, зато что нам это дает! Множество неэкспортируемых адресов внутренних структур. Оно нам потребуется для заполнения хидера дампа.

Ну а теперь, поскольку мы всё знаем, приведу отрывочный код заполнения заглавной страницы дампа:

Код (Text):
  1. BOOLEAN
  2. InitializeDumpHeader(
  3.     IN  PBYTE HeaderPage
  4.     )
  5. {
  6.     PDUMP_HEADER hdr;
  7.     ULONG* blocks;
  8.     PKD_DEBUGGER_DATA_BLOCK KdDebuggerDataBlock;
  9.     EXCEPTION_RECORD exception;
  10.     PVOID KeCapturePersistentThreadState;
  11.     UNICODE_STRING uKeCapturePersistentThreadState;
  12.     PPHYSICAL_MEMORY_DESCRIPTOR MmPhysicalMemoryBlock;
  13.     CONTEXT ctx = {CONTEXT_FULL};
  14.     PEXCEPTION_POINTERS pei;
  15.    
  16.     // Get context
  17.     __asm
  18.     {
  19.         // Common registers
  20.         mov [ctx.Eax], eax
  21.         mov [ctx.Ebx], ebx
  22.         mov [ctx.Ecx], ecx
  23.         mov [ctx.Edx], edx
  24.         mov [ctx.Esi], esi
  25.         mov [ctx.Edi], edi
  26.  
  27.         // Control registers
  28.         mov [ctx.Esp], esp
  29.         mov [ctx.Ebp], ebp
  30.  
  31.         call _1
  32.         // This address will appear in kd as crash address:
  33. _1:     pop eax
  34.         mov [ctx.Eip], eax
  35.  
  36.         pushfd
  37.         pop eax
  38.         mov [ctx.EFlags], eax
  39.  
  40.         // Debug registers
  41.         __emit 0x0F
  42.         __emit 0x21
  43.         __emit 0xC0 ; mov eax, dr0
  44.         mov [ctx.Dr0], eax
  45.         __emit 0x0F
  46.         __emit 0x21
  47.         __emit 0xC8 ; mov eax, dr1
  48.         mov [ctx.Dr1], eax
  49.         __emit 0x0F
  50.         __emit 0x21
  51.         __emit 0xD0 ; mov eax, dr2
  52.         mov [ctx.Dr2], eax
  53.         __emit 0x0F
  54.         __emit 0x21
  55.         __emit 0xD8 ; mov eax, dr3
  56.         mov [ctx.Dr3], eax
  57.         __emit 0x0F
  58.         __emit 0x21
  59.         __emit 0xF0 ; mov eax, dr6
  60.         mov [ctx.Dr6], eax
  61.         __emit 0x0F
  62.         __emit 0x21
  63.         __emit 0xF8 ; mov eax, dr7
  64.         mov [ctx.Dr7], eax
  65.  
  66.         // Segment registers
  67.         push cs
  68.         pop eax
  69.         mov [ctx.SegCs], eax
  70.         xor eax,eax
  71.         mov ax, ss
  72.         mov [ctx.SegSs], eax
  73.         mov ax, ds
  74.         mov [ctx.SegDs], eax
  75.         mov ax, es
  76.         mov [ctx.SegEs], eax
  77.         mov ax, fs
  78.         mov [ctx.SegFs], eax
  79.         mov ax, gs
  80.         mov [ctx.SegGs], eax
  81.     }
  82.  
  83.     // Get KeCapturePersistentThreadState address
  84.     RtlInitUnicodeString( &uKeCapturePersistentThreadState, L"KeCapturePersistentThreadState" );
  85.     KeCapturePersistentThreadState = MmGetSystemRoutineAddress( &uKeCapturePersistentThreadState );
  86.  
  87.     // Initialize dump header
  88.     hdr = (PDUMP_HEADER) HeaderPage;
  89.     hdr->ValidDump = 'PMUD';
  90.     hdr->MinorVersion = (USHORT) *NtBuildNumber;
  91.     hdr->MajorVersion = (USHORT) 0xF; // checked/free, в идеале нужно брать
  92.                                          // инфу из реестра; если посмотреть код
  93.                                          // в приложении к статье - там так и сделано
  94.     hdr->DirectoryTableBase = CR3();
  95.  
  96.     //
  97.     // Capture KdDebuggerDataBlock
  98.     //
  99.    
  100.     __try {
  101.         hdr->KdDebuggerDataBlock = *(PVOID*)((ULONG)KeCapturePersistentThreadState +
  102.                 *(ULONG*)((ULONG)KeCapturePersistentThreadState + 0xC )+ 0x11);
  103.  
  104.     } __except( (pei=GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER ) {
  105.         ULONG i;
  106.  
  107.         DbgPrint("An exception occurred while trying to get KdDebuggerDataBlock address:\n");
  108.         DbgPrint("Exception code: 0x%08x\n", pei->ExceptionRecord->ExceptionCode);
  109.         DbgPrint("Number of arguments: 0x%08x\n", pei->ExceptionRecord->NumberParameters);
  110.  
  111.         for( i = 0; i < pei->ExceptionRecord->NumberParameters; i++ ) {
  112.             DbgPrint("Argument[%d]: 0x%08x\n", i, pei->ExceptionRecord->ExceptionInformation[i]);
  113.         }
  114.  
  115.         return FALSE;
  116.     }
  117.     hdr->MachineImageType = 0x14c;
  118.     hdr->NumberProcessors = 1;
  119.     hdr->BugCheckCode = KMODE_EXCEPTION_NOT_HANDLED;
  120.     hdr->BugCheckParameter1 = STATUS_BREAKPOINT;
  121.     hdr->BugCheckParameter2 = ctx.Eip;
  122.     hdr->BugCheckParameter3 = 0;
  123.     hdr->BugCheckParameter4 = 0;
  124.     hdr->PaeEnabled = (CR4() & PAE_ENABLED) ? TRUE : FALSE;
  125.  
  126.     KdDebuggerDataBlock = (PKD_DEBUGGER_DATA_BLOCK) hdr->KdDebuggerDataBlock;
  127.  
  128.     // Check KdDebuggerDataBlock
  129.     if( KdDebuggerDataBlock->ValidBlock != 'GBDK' || KdDebuggerDataBlock->Size != sizeof(*KdDebuggerDataBlock) )
  130.     {
  131.         // Invalid debugger data block
  132.         DbgPrint(   "KdDebuggerDataBlock is not valid.\nSignature = 0x%08x (should be 0x%08x)\nSize = 0x%08x
  133.                     (should be 0x%08x)\n",
  134.                     KdDebuggerDataBlock->ValidBlock, 'GBDK',
  135.                     KdDebuggerDataBlock->Size, sizeof(*KdDebuggerDataBlock) );
  136.         return FALSE;
  137.     }
  138.  
  139.     DbgPrint("Got valid KdDebuggerDataBlock=0x%08x\n", KdDebuggerDataBlock);
  140.    
  141.     hdr->PfnDataBase = (PULONG) KdDebuggerDataBlock->MmPfnDatabase.VirtualAddress;
  142.     hdr->PsLoadedModuleList = (PLIST_ENTRY) KdDebuggerDataBlock->PsLoadedModuleList.VirtualAddress;
  143.     hdr->PsActiveProcessHead = (PLIST_ENTRY) KdDebuggerDataBlock->PsActiveProcessHead.VirtualAddress;
  144.  
  145.     DbgPrint("PfnDataBase = 0x%08x\n", hdr->PfnDataBase);
  146.     DbgPrint("PsLoadedModuleList = 0x%08x\n", hdr->PsLoadedModuleList);
  147.     DbgPrint("PsActiveProcessHead = 0x%08x\n", hdr->PsActiveProcessHead);
  148.  
  149.     blocks = (ULONG*)(ULONG_PTR)HeaderPage;
  150.  
  151.     //
  152.     // Get physical memory descriptor
  153.     //
  154.  
  155.     MmPhysicalMemoryBlock = *(KdDebuggerDataBlock->MmPhysicalMemoryBlock.VirtualAddress);
  156.  
  157.     DbgPrint("MmPhysicalMemoryBlock = 0x%08x\n", MmPhysicalMemoryBlock);
  158.  
  159.     if( MmPhysicalMemoryBlock->NumberOfRuns == 'EGAP' ) {
  160.         RtlCopyMemory(  &blocks[ DH_PHYSICAL_MEMORY_BLOCK ],
  161.                         MmPhysicalMemoryBlock,
  162.                         sizeof(PHYSICAL_MEMORY_DESCRIPTOR)
  163.                         );
  164.     } else {
  165.         RtlCopyMemory(  &blocks[ DH_PHYSICAL_MEMORY_BLOCK ],
  166.                         MmPhysicalMemoryBlock,
  167.                         sizeof(PHYSICAL_MEMORY_DESCRIPTOR) - sizeof(PHYSICAL_MEMORY_RUN) +
  168.                         sizeof(PHYSICAL_MEMORY_RUN)*MmPhysicalMemoryBlock->NumberOfRuns
  169.                         );
  170.     }
  171.  
  172.     //
  173.     // Save context record
  174.     //
  175.  
  176.     RtlCopyMemory(  &blocks[ DH_CONTEXT_RECORD ],
  177.                     &ctx,
  178.                     sizeof(CONTEXT)
  179.                     );
  180.     DbgPrint("Context record saved.\n");
  181.     DbgPrint("EAX=%08x EBX=%08x ECX=%08x EDX=%08x\n", ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx);
  182.     DbgPrint("ESI=%08x EDI=%08x EBP=%08x ESP=%08x\n", ctx.Esi, ctx.Edi, ctx.Ebp, ctx.Esp);
  183.     DbgPrint("EIP=%08x CS=%08x FS=%08x DS=%08x\n", ctx.Eip, ctx.SegCs, ctx.SegFs, ctx.SegDs);
  184.  
  185.     //
  186.     // Create & store exception record
  187.     //
  188.  
  189.     exception.ExceptionCode = STATUS_BREAKPOINT;
  190.     exception.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
  191.     exception.ExceptionRecord = NULL;
  192.     exception.ExceptionAddress = (PVOID) ctx.Eip;
  193.     exception.NumberParameters = 0;
  194.  
  195.     RtlCopyMemory(  &blocks[ DH_EXCEPTION_RECORD ],
  196.                     &exception,
  197.                     sizeof(EXCEPTION_RECORD)
  198.                     );
  199.  
  200.     //
  201.     // Initialize dump type & size
  202.     //
  203.  
  204.     blocks[ DH_DUMP_TYPE ] = DUMP_TYPE_COMPLETE;
  205.  
  206.     ((LARGE_INTEGER*)&blocks[DH_REQUIRED_DUMP_SPACE])->QuadPart = ( MmPhysicalMemoryBlock->NumberOfPages << 12 ) + 0x1000;
  207.  
  208.     DbgPrint("Header page initialized OK\n");
  209.  
  210.     return TRUE;
  211. }

На этом всё. В приложении к статье можно найти более новую версию gr8lkd, которую я еще никуда не выкладывал, исходники AnalyseCrashDump и исходники gendump драйвера.

Файлы к статье:

© Great

0 1.864
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532