Показать сообщение отдельно

  #2  
Старый 07.09.2007, 19:39
_Great_
Флудер
Регистрация: 27.12.2005
Сообщений: 2,372
Провел на форуме:
5339610

Репутация: 4360


Отправить сообщение для _Great_ с помощью ICQ
По умолчанию

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 ) );
}
Всё готово. Напишем небольшой демонстрационный код для анализа крешдампа: