II. Написание анализатора дампа
Теперь у нас есть достаточно знаний, чтобы написать простейший анализатор крешдампа.
Загруженный дамп в нашем анализаторе будет представлен структурой:
Код:
// Mapped crash dump descriptor
struct MappedCrashDump {
HANDLE hFile;
HANDLE hMapping;
union {
PBYTE lpMapping;
PDUMP_HEADER DumpHeader;
ULONG* DataBlocks;
};
ULONG *DumpType;
ULONG *DumpFlags;
PLARGE_INTEGER DumpSize;
PPHYSICAL_MEMORY_DESCRIPTOR PhysicalMemoryDescriptor;
PCONTEXT Context;
union {
// Summary dump type
struct {
PSUMMARY_DUMP_HEADER pSummaryDumpHeader;
RTL_BITMAP KernelMemoryMap;
};
// Triage dump type
PTRIAGE_DUMP_HEADER pTriageDumpHeader;
};
};
План действий будет таков - мы открываем файл, создаем объект проекции и проецируем первые 10 страниц файла дампа, остальное будет проецироваться по мере необходимости. Для реализации этого напишем функцию MapDumpPages, которая будет проецировать вид файла, если он еще не спроецирован:
Код:
//
// This routine maps dump page at given file offset (DesiredAddress is the sum of BaseMapping and desired offset)
//
PVOID MapDumpPage( HANDLE hMapping, PBYTE BaseMapping, PBYTE DesiredAddress )
{
ULONG FileOffset = (ULONG) ( ( (ULONG_PTR)DesiredAddress-(ULONG_PTR)BaseMapping ) & 0xFFFF0000 );
PVOID Base = (PVOID)(ULONG_PTR) ( ((ULONG)(ULONG_PTR)DesiredAddress)&0xFFFF0000 );
MEMORY_BASIC_INFORMATION mbi = {0};
PVOID Ret = 0;
VirtualQuery( DesiredAddress, &mbi, sizeof(mbi) );
if( mbi.State != MEM_COMMIT ) {
Ret = MapViewOfFileEx( hMapping,
FILE_MAP_READ,
0,
FileOffset,
0x10000,
Base
);
if( Ret == Base ) {
return DesiredAddress;
}
// Loaded at different address. Fail
UnmapViewOfFile( Ret );
return NULL;
}
return DesiredAddress;
}
Дальше мы реализуем функцию, которая получит страницу из summary-дампа по её физическому адресу в момент краха:
Код:
PBYTE GetSummaryDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )
{
PBYTE StartingAddress = (PBYTE)(ULONG_PTR) ( (ULONG)(ULONG_PTR)CrashDump->pSummaryDumpHeader + CrashDump->pSummaryDumpHeader->HeaderSize - 0x1000 );
ULONG NumberOfPage = (ULONG) (AddressAtCrashTime / 0x1000);
ULONG OffsetInPage = (ULONG) (AddressAtCrashTime & 0xFFF);
if( NumberOfPage >= CrashDump->KernelMemoryMap.SizeOfBitMap )
return NULL;
if( RtlCheckBit( &CrashDump->KernelMemoryMap, NumberOfPage ) == 0 )
return NULL; // not included in dump
// Calculate page number in dump
ULONG PageNumber = 0;
for( ULONG i=0; i<CrashDump->KernelMemoryMap.SizeOfBitMap; i++ ) {
if( i == NumberOfPage ) {
PBYTE Result = StartingAddress + PageNumber*0x1000 + OffsetInPage;
return (PBYTE) MapDumpPage( CrashDump->hMapping, CrashDump->lpMapping, Result );
}
if( RtlCheckBit( &CrashDump->KernelMemoryMap, i ) ) {
PageNumber++;
}
}
return NULL;
}
Тут реализована работа с битовой картой, чтобы узнать, присутствует ли страница в дампе, и чтобы узнать ее номер на диске. После этого она проецируется с диска в оперативную память, если еще не спроецирована.
Дальше нужно определить аналогичную функцию для полного дампа. Для начала нужно научиться конвертировать PFN страницы в памяти в PFN страницы на диске, и наоборот. С этим справятся следующие две функции:
Код:
PFN_NUMBER GetPhysicalPFN( PPHYSICAL_MEMORY_DESCRIPTOR ppmd, PFN_NUMBER DumpPFN )
{
PFN_NUMBER iBuffPage, iPage = iBuffPage = DumpPFN;
if( iBuffPage >= ppmd->NumberOfPages )
return -1;
// Calculate page in memory
ULONG NumberOfRunsRequired = 0;
PFN_NUMBER TotalPageCount = 0;
for( ; NumberOfRunsRequired<ppmd->NumberOfRuns; NumberOfRunsRequired++ )
{
PPHYSICAL_MEMORY_RUN Runs = ppmd->Run;
if( iBuffPage >= TotalPageCount &&
iBuffPage < TotalPageCount + Runs[NumberOfRunsRequired].PageCount )
break;
TotalPageCount += (Runs[NumberOfRunsRequired].PageCount);
}
PFN_NUMBER PreviousEnd = 0;
NumberOfRunsRequired ++;
for( ULONG i=0; i<NumberOfRunsRequired; i++ )
{
PPHYSICAL_MEMORY_RUN Runs = ppmd->Run;
iPage += (Runs[i].BasePage - PreviousEnd);
PreviousEnd = Runs[i].BasePage + Runs[i].PageCount;
}
return iPage;
}
PFN_NUMBER GetDumpPFN( PPHYSICAL_MEMORY_DESCRIPTOR ppmd, PFN_NUMBER PhysicalPFN )
{
for( int run=0; run<ppmd->NumberOfRuns; run++ )
{
PPHYSICAL_MEMORY_RUN Runs = ppmd->Run;
if( PhysicalPFN >= Runs[run].BasePage &&
PhysicalPFN < (Runs[run].BasePage + Runs[run].PageCount) )
break;
}
if( run == ppmd->NumberOfRuns )
return -1;
PFN_NUMBER iDumpPFN = 0;
for( int i=0; i<run; i++ )
{
iDumpPFN += ppmd->Run[i].PageCount;
}
iDumpPFN += PhysicalPFN - ppmd->Run[i].BasePage;
return iDumpPFN;
}
Дальше можно описать функцию, получающую страницу из дампа по ее физическому адресу на момент краха и общую функцию, получающую тип дампа и вызывающую нужную из двух функций получения адреса:
Код:
PBYTE GetCompleteDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )
{
PBYTE StartingAddress = (PBYTE)(ULONG_PTR)( (ULONG)(ULONG_PTR)CrashDump->lpMapping + 0x1000 );
PFN_NUMBER PFN = (ULONG) (AddressAtCrashTime >> 12);
ULONG OffsetInPage = (ULONG) (AddressAtCrashTime & 0xFFF);
// Calculate page number in dump
for( ULONG i=0; i<CrashDump->PhysicalMemoryDescriptor->NumberOfRuns; i++ ) {
PPHYSICAL_MEMORY_RUN Runs = (PPHYSICAL_MEMORY_RUN) &CrashDump->PhysicalMemoryDescriptor->Run;
if( PFN >= Runs[i].BasePage && PFN < (Runs[i].BasePage + Runs[i].PageCount) ) {
PFN_NUMBER DumpPFN = GetDumpPFN( CrashDump->PhysicalMemoryDescriptor, PFN );
PBYTE Result = StartingAddress + DumpPFN*0x1000 + OffsetInPage;
return (PBYTE) MapDumpPage( CrashDump->hMapping, CrashDump->lpMapping, Result );
}
}
return NULL;
}
PBYTE GetDumpPagesByPhysicalAddress( MappedCrashDump *CrashDump, ULONGLONG AddressAtCrashTime )
{
if( *CrashDump->DumpType == DUMP_TYPE_SUMMARY ) {
return GetSummaryDumpPagesByPhysicalAddress( CrashDump, AddressAtCrashTime );
} else if( *CrashDump->DumpType == DUMP_TYPE_COMPLETE ) {
return GetCompleteDumpPagesByPhysicalAddress( CrashDump, AddressAtCrashTime );
} else {
return NULL;
}
}
Теперь нам нужно научиться транслировать виртуальные адреса в физические, и тогда мы полностью сможем вынуть данные по любому виртуальному адресу из дампа. Для начала определим структуры каталогов PDPE, PDE и PTE, которые нам могут понадобиться:
Код:
//
// Page Directory Entry
//
struct PDE {
DWORD Present:1;
DWORD ReadWrite:1;
DWORD UserSupervisor:1;
DWORD WriteThrough:1;
DWORD CacheDisabled:1;
DWORD Accessed:1;
DWORD Reserved:1; // Dirty, ignored
DWORD PageSize:1;
DWORD GlobalPage:1; // Ignored
DWORD Available:3;
DWORD Pte:19;
};
//
// Page Table Entry
//
struct PTE {
DWORD Present:1;
DWORD ReadWrite:1;
DWORD UserSupervisor:1;
DWORD WriteThrough:1;
DWORD CacheDisabled:1;
DWORD Accessed:1;
DWORD Dirty:1;
DWORD PageTableAttributeIndex:1;
DWORD GlobalPage:1;
DWORD Available:3;
DWORD PageFrameNumber:19;
};
// Virtual address
struct VIRTUAL_ADDRESS {
DWORD Offset:12;
DWORD Table:10;
DWORD Directory:10;
};
//
// Page Directory Entry in PAE mode
//
#define QWORD ULONGLONG
struct LongPDE {
QWORD Present:1;
QWORD ReadWrite:1;
QWORD UserSupervisor:1;
QWORD WriteThrough:1;
QWORD CacheDisabled:1;
QWORD Accessed:1;
QWORD Reserved:1; // Dirty, ignored
QWORD PageSize:1;
QWORD GlobalPage:1; // Ignored
QWORD Available:3;
QWORD Pte:24;
QWORD ReservedHigh:28;
};
//
// Page Table Entry in PAE mode
//
struct LongPTE {
QWORD Present:1;
QWORD ReadWrite:1;
QWORD UserSupervisor:1;
QWORD WriteThrough:1;
QWORD CacheDisabled:1;
QWORD Accessed:1;
QWORD Dirty:1;
QWORD PageTableAttributeIndex:1;
QWORD GlobalPage:1;
QWORD Available:3;
QWORD PageFrameNumber:24;
QWORD ReservedHigh:28;
};
//
// Page Directory Pointer Table (PAE mode only)
//
struct PDPE {
QWORD Present:1;
QWORD Reserved1:2;
QWORD WriteThough:1;
QWORD CacheDisabled:1;
QWORD Reserved2:4;
QWORD Available:3;
QWORD Pdt:24;
QWORD ReservedHigh:28;
};
// Virtual address (PAE)
struct PAE_VIRTUAL_ADDRESS {
DWORD Offset:12;
DWORD Table:9;
DWORD Directory:9;
DWORD DirectoryPointer:2;
};
Тут определены две серии структур - без включенного PAE и с включенным PAE.
Напомню, что в режиме PAE (Physical Address Extensions) адресация стала трехуровневой. Регистр CR3 больше не указывает на PDE, теперь он указывает на массив Page Directory Pointer Entries - PDPE, которых должно быть 4 штуки. Каждый из PDPE указывает на массив PDE, а уже PDE указывают на PTE. Структура виртуального адреса немного изменилась, у PDE и PTE "откусили" по одному биту в пользу двухбитового поля номера PDPE.
С учетом всего этого, функция трансляции адресов:
Код:
//
// Translates virtual address to physical address
//
ULONGLONG VirtualToPhysical( MappedCrashDump *CrashDump, ULONG VirtualAddress )
{
ULONG CR3 = CrashDump->DumpHeader->DirectoryTableBase;
CR3 &= 0xFFFFFFF0; // clear flags in cr3
if( CrashDump->DumpHeader->PaeEnabled )
{
// PAE enabled. Use 3-level addressing system: PDPE->PDE->PTE->Page
PAE_VIRTUAL_ADDRESS va;
*(ULONG*)&va = VirtualAddress;
PDPE* dirptr = (PDPE*)GetDumpPagesByPhysicalAddress( CrashDump, CR3 );
if( dirptr != NULL && dirptr[va.DirectoryPointer].Present )
{
LongPDE *dir = (LongPDE*)GetDumpPagesByPhysicalAddress( CrashDump, dirptr[va.DirectoryPointer].Pdt << 12 );
if( dir != NULL && dir[va.Directory].Present )
{
LongPTE* tbl = (LongPTE*)GetDumpPagesByPhysicalAddress( CrashDump, dir[va.Directory].Pte << 12 );
if( tbl != NULL && tbl[va.Table].Present )
{
return ( tbl[va.Table].PageFrameNumber << 12 ) | va.Offset;
}
}
}
}
else
{
// PAE disabled. Use 2-level addressing system: PDE->PTE->Page
VIRTUAL_ADDRESS va;
*(ULONG*)&va = VirtualAddress;
PDE *dir = (PDE*)GetDumpPagesByPhysicalAddress( CrashDump, CR3 );
if( dir != NULL && dir[va.Directory].Present )
{
PTE* tbl = (PTE*)GetDumpPagesByPhysicalAddress( CrashDump, dir[va.Directory].Pte << 12 );
if( tbl != NULL && tbl[va.Table].Present )
{
return ( tbl[va.Table].PageFrameNumber << 12 ) | va.Offset;
}
}
}
return NULL;
}
//
// Translates virtual address to physical address and loads that physical pages
//
PVOID RetrieveDumpData( MappedCrashDump* CrashDump, PVOID VirtualAddress )
{
return GetDumpPagesByPhysicalAddress( CrashDump, VirtualToPhysical( CrashDump, (ULONG)(ULONG_PTR)VirtualAddress ) );
}
Всё готово. Напишем небольшой демонстрационный код для анализа крешдампа: