Description of DMP Format — Архив WASM.RU
I. Structure of crash dump
Not so long ago I've released my counterpart of livekd, named gr8lkd, which is filter driver for file system. It attaches to disk C: and creates here virtual crash dump to intercept and process all requests to it.
Now I've decided to describe the format of Windows crash dumps - .DMP. On the internet you can find only one page in English (original article is in Russian) with incomplete and buggy description.
I will try to describe this format as complete as possible. Everything underwritten is based on my own research of core's dissassembler listing - if to be more precise, function IoWriteCrashDump and those ones associated with it.
Let's begin...
First, let's talk about crash dumps and how they are generated. When something bad happens in the core, for example freeing of already freed memory, and ExFreePool (function for pool freeing) founds it, it is insecure to continue to work so it is decided to make emergency system shudown. ExFreePool calls KeBuCheckEx (KeBugCheck2, KeBugCheck3 - depending on Windows version, in first case it is exported documented function which shows blue screen, in second and third ones - inner function which do the same) which shows blue screen BAD_POOL_CALLER and calls IoWriteCrashDump to write a crash dump. The crash dump is writed in the disk sectors which are occupied by swap file (before this FSCTL_GET_RETRIEVAL_POINTERS is called to get swap file map (карта размещения)) because at this stage calling of file system driver is insecure - maybe it is itseft the reason of fail, so special driver is used for writing dump to swap file. At the next boot dump will be get from swap file and written to the path specified in the registry.
As is known, there are three types of Windows crash dumps:
- "Minidump", or "triage dump" as it is called in the core. Usually such dumps are small (about 64k) and they contain least of information about system when it crashed. This type of dumps is set by default but unfortunately it is not fit for usual system core developments, so we move ahead and look at the next type:
- "Kernel Memory Dump", or "summary dump" as it called in the core. Such dumps include only core's memory pages, and not all but only those which are neccessary for crash ananalysis. This dump type is fit for usual analysis of system crash's reasons because it includes code and data of the core, all loaded drivers and system structures. Swap file should be big enough to contain all of this. It is hard to predict it's size, at my maching it is usually about 1/8 of physical memory.
- "Full Memory Dump"
. This dump includes all pages of physical memory. Swap file should be big enough to contain it.First we will examine the structure which is common for all dumps - dump header page. It is the first page of dump file, first 0x1000 (4096) bytes. The structure of this page is - first, all page is filled with "PAGE" (bytes 'EGAP') and then data is written to required offsets.
The structure which identify the crash dump is first:
Код (Text):
typedef struct _DUMP_HEADER { /* 00 */ ULONG Signature; /* 04 */ ULONG ValidDump; /* 08 */ ULONG MajorVersion; /* 0c */ ULONG MinorVersion; /* 10 */ ULONG DirectoryTableBase; /* 14 */ PULONG PfnDataBase; /* 18 */ PLIST_ENTRY PsLoadedModuleList; /* 1c */ PLIST_ENTRY PsActiveProcessHead; /* 20 */ ULONG MachineImageType; /* 24 */ ULONG NumberProcessors; /* 28 */ ULONG BugCheckCode; /* 2c */ ULONG BugCheckParameter1; /* 30 */ ULONG BugCheckParameter2; /* 34 */ ULONG BugCheckParameter3; /* 38 */ ULONG BugCheckParameter4; /* 3c */ CHAR VersionUser[32]; /* 5c */ BYTE PaeEnabled; BYTE NotUsed[3]; /* 60 */ PVOID KdDebuggerDataBlock; } DUMP_HEADER, *PDUMP_HEADER;
- Signature - This field is not filled and leaved with 'EGAP' bytes.
- ValidDump The dump signature - "DUMP" ('PMUD')
- MajorVersion - 0x0F if Free build 0x0C if Checked build
- MinorVersion - Build number системы System's build number
- DirectoryTableBase - CR3 value at the moment of system's crash - physical address of page directory
- PfnDatabase - MmPfnDatabase virtual address - page frame (number? - not sure here [translator]) database (PFN)
- PsLoadedModuleList - PsLoadedModuleList virtual address - list of loaded modules
- PsActiveProcessHead - PsActiveProcessHead virtual address - list of active processes
- MachineImageType - on x86 it is equal to 0x14C, you can check other constants in the winnt.h.
- NumberProcessors - number of processor (from KeNumberProcessors)
- BugCheckCode - Stop code of error
- BugCheckParameter1, BugCheckParameter2, BugCheckParameter3, BugCheckParameter4 - Error's parameters
- VersionUser - Version of something, I did not find out what exactly. As a rule, first byte written as 0, others are not filled.
- PaeEnabled - =1 if support of Physical Address Extensions (PAE) is turned on, =0 if turned off
- KdDebuggerDataBlock - Virtual address of very important structure KdDebuggerDataBlock, which will be described later.
Well, this is beginning of the dump. Some data blocks are written to other offsets, so first let's define several constants:
Код (Text):
// Data Blocks #define DH_PHYSICAL_MEMORY_BLOCK 25 #define DH_CONTEXT_RECORD 200 #define DH_EXCEPTION_RECORD 500 #define DH_DUMP_TYPE 994 #define DH_CALLBACKS_STATUS 996 // (field is not required) #define DH_PRODUCT_TYPE 997 // (field is not required, value is taken from KUSER_SHARED_DATA) #define DH_SUITE_MASK 998 // (field is not required, value is taken from KUSER_SHARED_DATA) #define DH_REQUIRED_DUMP_SPACE 1000 #define DH_INTERRUPT_TIME 1006 // (field is not required, value is taken from KUSER_SHARED_DATA) #define DH_SYSTEM_TIME 1008 // (field is not required, value is taken from KUSER_SHARED_DATA)First page is treated as DWORD array, and this constants define indexes of this array where data is contained. So, in other words using C:
Код (Text):
ULONG* blocks = (ULONG*) HeaderPage; //&blocks[DH_xxx] - address of corresponding blockLet's examine this block one-by-one:
DH_PHYSICAL_MEMORY_BLOCK - Here is the physical memory descriptor, the structure PHYSICAL_MEMORY_DESCRIPTOR:
Код (Text):
typedef unsigned long PFN_NUMBER; typedef struct _PHYSICAL_MEMORY_RUN { PFN_NUMBER BasePage; PFN_NUMBER PageCount; } PHYSICAL_MEMORY_RUN, *PPHYSICAL_MEMORY_RUN; typedef struct _PHYSICAL_MEMORY_DESCRIPTOR { ULONG NumberOfRuns; PFN_NUMBER NumberOfPages; PHYSICAL_MEMORY_RUN Run[1]; } PHYSICAL_MEMORY_DESCRIPTOR, *PPHYSICAL_MEMORY_DESCRIPTOR;It, as well as Run arrays, describes available physical memory. Usually system has 3-4 Runs - solid sets of physical pages. For example, on my system (512Mb of physical memory) they are showed as:
Код (Text):
PPHYSICAL_MEMORY_DESCRIPTOR: NumberOfRuns = 0x00000003 NumberOfPages = 0x0001ff8d PPHYSICAL_MEMORY_RUN[0]: BasePage = 0x00000001 PageCount = 0x0000009e PPHYSICAL_MEMORY_RUN[1]: BasePage = 0x00000100 PageCount = 0x00000eff PPHYSICAL_MEMORY_RUN[2]: BasePage = 0x00001000 PageCount = 0x0001eff0I.e. physical pages with PFNs (numbers in other words) from 1 to 0x9e, from 0x100 to 0xeff and from 0x1000 to 0x1eff0.
Size of this block is equal to sizeof(PHYSICAL_MEMORY_DESCRIPTOR) - sizeof(PHYSICAL_MEMORY_RUN) + sizeof(PHYSICAL_MEMORY_RUN) * NumberOfRuns
- DH_CONTEXT_RECORD The structure CONTEXT is stored here which contains context of thread which triggered error. I think no additional explanations are needed.
- DH_EXCEPTION_RECORD As in previous, the structure EXCEPTION_RECORD is stored here which contains information (if available) about exception.
- DH_DUMP_TYPE Here is stored the dword which indicates the dump type - triage, summary of full dump. The values of this field are:
Код (Text):
// Dump types #define DUMP_TYPE_TRIAGE 4 #define DUMP_TYPE_SUMMARY 2 #define DUMP_TYPE_FULL 1- DH_CALLBACKS_STATUS - Here is stored NTSTATUS returned by BugcheckCallbacks.
- DH_PRODUCT_TYPE - Here is stored the system type described by enumeration:
Код (Text):
enum _NT_PRODUCT_TYPE { NtProductWinNt = 0x1, NtProductLanManNt = 0x2, NtProductServer = 0x3, };- DH_SUITE_MASK - Here is stored the field KUSER_SHARED_DATA->SuiteMask and if honestly I don't know what it means.
- DH_REQUIRED_DUMP_SPACE - Here is stored LARGE_INTEGER which contains full size of dump in bytes.
- DH_INTERRUPT_TIME - I don't know it's purpose but it is taken from KUSER_SHARED_DATA->InterruptTime
- DH_SYSTEM_TIME - This field is taken from KUSER_SHARED_DATA->SystemTime and, as far as I know, contains current uptime
Well, that's all about first dump page. Following structure of dump is heavily depends on it's type:
- Minidump (triage dump) - this dump type is not very interesting, so I will only provide partial structure TRIAGE_DUMP_HEADER without many comments (I have not finished it's analysis). Just note that it constains offsets of some structures which are followed then in row format.
Код (Text):
// Triage dump header typedef struct _TRIAGE_DUMP_HEADER { ULONG ServicePackBuild; // 00 ULONG SizeOfDump; // 04 ULONG ValidOffset; // 08 ULONG ContextOffset; // 0c ULONG ExceptionOffset; // 10 ULONG MmOffset; // 14 ULONG UnloadedDriversOffset; // 18 ULONG PrcbOffset; // 1c ULONG ProcessOffset; // 20 ULONG ThreadOffset; // 24 ULONG Unknown1; // 28 ULONG Unknown2; // 2c ULONG DriverListOffset; // 30 ULONG DriverCount; // 34 ... ULONG TriageOptions; // 44 } TRIAGE_DUMP_HEADER, *PTRIAGE_DUMP_HEADER;- Kernel memory dump (Summary dump) - second page of dump is beginning with the next structure:
Код (Text):
// Kernel summary dump header typedef struct _SUMMARY_DUMP_HEADER { ULONG Signature1; // 00 ULONG ValidDump; // 04 ULONG Signature2; // 08 ULONG HeaderSize; // 0c ULONG BitmapSize; // 10 ULONG Pages; // 14 ULONG Unknown3; // 18 ULONG Unknown4; // 1c } SUMMARY_DUMP_HEADER, *PSUMMARY_DUMP_HEADER;
- Signature1, Signature2 - This field contains "SDMP" signature, if to be more precise, the structure is filled with those at the beginning and then this field is leave as is.
- ValidDump - This field contains "DUMP" signature.
- HeaderSize - Full size of dump header.
- BitmapSize - Size of the bitmap which follows right after this structure - we will talk about it later.
- Pages Number of core's memory pages included in the dump (matches to the number of all set bits in bitmap).
The bitmap follows right after this structure, the number of bits in it equal to number of physical pages in the system, and setting of bit indicates if corresponding page included in the dump or not. Next structure is designed for working with such bitmap:
и функции RtlInitializeBitMap, RtlClearAllBits, RtlSetAllBits, RtlFindClearBits, RtlFindSetBits, RtlCheckBit и другие.Код (Text):
typedef struct _RTL_BITMAP { ULONG SizeOfBitMap; // Number of bits in bit map PULONG Buffer; // Pointer to the bit map itself } RTL_BITMAP; typedef RTL_BITMAP *PRTL_BITMAP;In the second part of this article I will show example of simple crash dump analyzer and how to work with this map.
- Full dump - After first page, dumps of this type contain every physical from all memory runs beginning with 0 and ending with NumberOfRuns-1.
II. Writing of dump analyzer
Now we have enough information to write simple crash dump analyzer.
In our analyzer loaded dump will be shown with the next structure:
Код (Text):
// 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; }; };The plan is: we open a file, create map object and map first 10 pages of dump file. Other will be mapped on demand. To implement this, we will write function MapDumpPage which will map part of file if it is not mapped: already.
Код (Text):
// // 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; }Next we implement function which will get page from summary dump using it's physical address at the time of crash:
Код (Text):
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; }To find out if a page is present in the dump and to get it's number, special function was implemented. After we have this information, page is mapped from disk to memory (if not mapped already).
Next we have to define counterpart function for full dump. First, we need to learn how to convert PFN of memory page to PFN of disk page and vice versa. Two following functions will do the job:
Код (Text):
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; }Next we can define a function to get a page from the dump using it's physical address at the time of the crash, and a general function to get the dump type, and call one of the two functions to get it's address (which is more appropriate):
Код (Text):
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; } }Now we have to learn how to translate virtual adresses to physical ones. Then we will be able to get data from any virtual address of the dump. First, let's define directory structures PDPE, PDE and PTE. We will need them later.
Код (Text):
// // 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; };Here is two series of structures defined: with PAE turned off and PAE turned on. I want to remind you, addressing became three-level in PAE mode. CR3 register no more points to PDE, now it points to array of "Page Directory Pointer Entries" or PDPE. There shoud be 4 of them. Every PDPE points to PDE array, and PDE points to PTE. Composition of virtual address also changed a little. One bit of PDE and PTE was cut in favour of two-bit field of PDPE number.
Taking this into account, the function to translate adresses is:
Код (Text):
// // 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 ) ); }Everything is ready. Now we will code a small example for crash dump analyzing.
Код (Text):
struct CONST_DESCRIPTION { ULONG Value; LPSTR Desc; #define DEFINE_STRING(x) { x, #x } #define TABLE_END { 0, 0 } }; CONST_DESCRIPTION MachineTypes[] = { DEFINE_STRING( IMAGE_FILE_MACHINE_I386 ), DEFINE_STRING( IMAGE_FILE_MACHINE_IA64 ), TABLE_END }; CONST_DESCRIPTION DumpTypes[] = { DEFINE_STRING( DUMP_TYPE_TRIAGE ), DEFINE_STRING( DUMP_TYPE_SUMMARY ), DEFINE_STRING( DUMP_TYPE_FULL ), TABLE_END }; // Bugcheck descriptions typedef ULONG NTSTATUS; #include "D:\Progs\driverdev\bcdesc.h" // codes of bsods, I do not provide // this file, there is BugCheckDescf // function which gets bsod name by // it's number // Get constant's name by it's value LPSTR LookupConstDesc( CONST_DESCRIPTION* Table, ULONG Value ) { while( Table->Desc ) { if( Table->Value == Value ) { return Table->Desc; } Table ++; } return "(unknown)"; } void ExtractDumpHeader( MappedCrashDump* CrashDump, char* saveto ) { HANDLE hFile = CreateFile( saveto, GENERIC_WRITE, 0, 0, OPEN_ALWAYS, 0, 0 ); DWORD wr; WriteFile( hFile, CrashDump->lpMapping, 0x1000, &wr, 0 ); SetEndOfFile( hFile ); CloseHandle( hFile ); } int AnalyseDump( char* filename ) { MappedCrashDump CrashDump = {0}; CrashDump.hFile = INVALID_HANDLE_VALUE; __try { CrashDump.hFile = CreateFile( filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0 ); if( CrashDump.hFile == INVALID_HANDLE_VALUE ) return 0; CrashDump.hMapping = CreateFileMapping( CrashDump.hFile, 0, PAGE_READONLY, 0, 0, NULL ); if( CrashDump.hMapping == NULL ) return 0; CrashDump.lpMapping = (PBYTE) MapViewOfFile( CrashDump.hMapping, FILE_MAP_READ, 0, 0, 0x10000 ); //0x12000 ); if( CrashDump.lpMapping == NULL ) return 0; PDUMP_HEADER hdr = CrashDump.DumpHeader; if( hdr->ValidDump != 'PMUD' || hdr->Signature != 'EGAP' ) { printf("Invalid dump header\n"); return 0; } printf("Crash dump '%s' analysing started. Version: %d.%d\n\n", filename, hdr->MajorVersion, hdr->MinorVersion); // Header printf("CR3 = 0x%08x PfnDatabase = 0x%08x\n" "PsLoadedModuleList = 0x%08x PsActiveProcessHead = 0x%08x\n", hdr->DirectoryTableBase, (ULONG_PTR)hdr->PfnDataBase, (ULONG_PTR)hdr->PsLoadedModuleList, (ULONG_PTR)hdr->PsActiveProcessHead ); printf("Machine type: %s, NumberProcessors: %d\n", LookupConstDesc( MachineTypes, hdr->MachineImageType ), hdr->NumberProcessors); printf("PaeEnabled = %d, KdDebuggerDataBlock = 0x%08x\n", hdr->PaeEnabled, (ULONG_PTR)hdr->KdDebuggerDataBlock); printf("\n"); // Bug check printf("Bugcheck code %s (0x%08x)\n", BugCheckDescf(hdr->BugCheckCode), hdr->BugCheckCode); printf("Arguments[0] = 0x%08x\n", hdr->BugCheckParameter1); printf("Arguments[1] = 0x%08x\n", hdr->BugCheckParameter2); printf("Arguments[2] = 0x%08x\n", hdr->BugCheckParameter3); printf("Arguments[3] = 0x%08x\n", hdr->BugCheckParameter4); printf("\n"); // Data blocks ULONG* block = CrashDump.DataBlocks; // Dump type & size CrashDump.DumpType = &block[ DH_DUMP_TYPE ]; CrashDump.DumpFlags = &block[ DH_DUMP_TYPE + 1 ]; CrashDump.DumpSize = (PLARGE_INTEGER) &block[ DH_REQUIRED_DUMP_SPACE ]; printf( "Dump type: %s, DumpSize: %d bytes (0x%08x)\n", LookupConstDesc( DumpTypes, *CrashDump.DumpType ), CrashDump.DumpSize->LowPart, CrashDump.DumpSize->LowPart ); printf("\n"); // Physical memory descriptor CrashDump.PhysicalMemoryDescriptor = (PPHYSICAL_MEMORY_DESCRIPTOR)( &block[DH_PHYSICAL_MEMORY_BLOCK] ); if( CrashDump.PhysicalMemoryDescriptor->NumberOfRuns == 'EGAP' ) { printf("PPHYSICAL_MEMORY_DESCRIPTOR: Invalid\n"); } else { printf( "PPHYSICAL_MEMORY_DESCRIPTOR:\nNumberOfRuns = 0x%08x NumberOfPages = 0x%08x\n", CrashDump.PhysicalMemoryDescriptor->NumberOfRuns, CrashDump.PhysicalMemoryDescriptor->NumberOfPages ); for( ULONG i=0;i<CrashDump.PhysicalMemoryDescriptor->NumberOfRuns;i++ ) { printf( "PPHYSICAL_MEMORY_RUN[%d]: BasePage = 0x%08x PageCount = 0x%08x\n", i, CrashDump.PhysicalMemoryDescriptor->Run[i].BasePage, CrashDump.PhysicalMemoryDescriptor->Run[i].PageCount ); } } printf("\n"); // Context record: CrashDump.Context = (PCONTEXT)( &block[DH_CONTEXT_RECORD] ); printf( "Context record:\nEip = 0x%08x ESP = 0x%08x EBP = 0x%08x\n", CrashDump.Context->Eip, CrashDump.Context->Esp, CrashDump.Context->Ebp ); printf("EAX=%08x EBX=%08x ECX=%08x EDX=%08x ESI=%08x EDI=%08x\n", CrashDump.Context->Eax, CrashDump.Context->Ebx, CrashDump.Context->Ecx, CrashDump.Context->Edx, CrashDump.Context->Esi, CrashDump.Context->Edi, CrashDump.Context->Eax); printf("\n"); // Analyse dump if( *CrashDump.DumpType == DUMP_TYPE_TRIAGE ) { // // Minidump // CrashDump.pTriageDumpHeader = (PTRIAGE_DUMP_HEADER)( CrashDump.lpMapping + 0x1000 ); printf("Analysing triage dump header\n"); printf("[-] Not implemented\n"); __asm nop; } else if( *CrashDump.DumpType == DUMP_TYPE_SUMMARY ) { // // Kernel summary dump - only kernel address space available // CrashDump.pSummaryDumpHeader = (PSUMMARY_DUMP_HEADER) ( CrashDump.lpMapping + 0x1000 ); CrashDump.KernelMemoryMap.SizeOfBitMap = CrashDump.pSummaryDumpHeader->BitmapSize; CrashDump.KernelMemoryMap.Buffer = (PULONG) ( CrashDump.pSummaryDumpHeader + 1 ); if( CrashDump.pSummaryDumpHeader->ValidDump != 'PMUD' ) { printf("Invalid summary dump header\n"); return 0; } printf("Analyzing summary dump header\n"); printf( "HeaderSize = 0x%08x BitmapSize = 0x%08x\n", CrashDump.pSummaryDumpHeader->HeaderSize, CrashDump.pSummaryDumpHeader->BitmapSize ); printf( "Number of kernel pages in dump: 0x%08x\n\n", CrashDump.pSummaryDumpHeader->Pages ); ULONG Virtual = CrashDump.Context->Eip; ULONGLONG Physical; Physical = VirtualToPhysical( &CrashDump, Virtual ); printf("Translating virtual address [0x%08x]: 0x%08x\n", Virtual, Physical); PBYTE MappedEIP = (PBYTE)RetrieveDumpData( &CrashDump, (void*)(ULONG_PTR)CrashDump.Context->Eip ); if( MappedEIP ) { printf("Bytes at [EIP=%08x]: %02x %02x %02x %02x %02x %02x %02x %02x\n", CrashDump.Context->Eip, MappedEIP[0], MappedEIP[1], MappedEIP[2], MappedEIP[3], MappedEIP[4], MappedEIP[5], MappedEIP[6], MappedEIP[7] ); } else printf("Memory pointed by EIP is not present\n"); __asm nop; } else if( *CrashDump.DumpType == DUMP_TYPE_COMPLETE ) { // // Complete memory dump - full address space available // ULONG Virtual = CrashDump.Context->Eip; ULONGLONG Physical; Physical = VirtualToPhysical( &CrashDump, Virtual ); printf("Translating virtual address [0x%08x]: 0x%08x\n", Virtual, Physical); PBYTE MappedEIP = (PBYTE)RetrieveDumpData( &CrashDump, (void*)(ULONG_PTR)CrashDump.Context->Eip ); printf("Bytes at [EIP=%08x]: %02x %02x %02x %02x %02x %02x %02x %02x\n", CrashDump.Context->Eip, MappedEIP[0], MappedEIP[1], MappedEIP[2], MappedEIP[3], MappedEIP[4], MappedEIP[5], MappedEIP[6], MappedEIP[7] ); ExtractDumpHeader( &CrashDump, "crashdump.hdr" ); __asm nop; } printf("\nDump analysis finished\n"); } __finally { if( CrashDump.lpMapping ) UnmapViewOfFile( CrashDump.lpMapping ); if( CrashDump.hMapping ) CloseHandle( CrashDump.hMapping ); if( CrashDump.hFile != INVALID_HANDLE_VALUE ) CloseHandle( CrashDump.hFile ); Sleep(INFINITE); } return 0; } int main() { AnalyseDump( "C:\\gr8lkd.dmp" ); //AnalyseDump( "D:\\memory.dmp" ); return 0; }Path to my crash dump used by my gr9lkd utility is already defined.
Output will be something like following:
Код (Text):
Crash dump 'C:\gr8lkd.dmp' analysing started. Version: 15.2600 CR3 = 0x00373000 PfnDatabase = 0x80557b48 PsLoadedModuleList = 0x805531a0 PsActiveProcessHead = 0x80559258 Machine type: IMAGE_FILE_MACHINE_I386, NumberProcessors: 1 PaeEnabled = 1, KdDebuggerDataBlock = 0x80544ce0 Bugcheck code KMODE_EXCEPTION_NOT_HANDLED (0x0000001e) Arguments[0] = 0x80000004 Arguments[1] = 0xf3b21315 Arguments[2] = 0x00000000 Arguments[3] = 0x00000000 Dump type: DUMP_TYPE_FULL, DumpSize: 536403968 bytes (0x1ff8e000) PPHYSICAL_MEMORY_DESCRIPTOR: NumberOfRuns = 0x00000003 NumberOfPages = 0x0001ff8d PPHYSICAL_MEMORY_RUN[0]: BasePage = 0x00000001 PageCount = 0x0000009e PPHYSICAL_MEMORY_RUN[1]: BasePage = 0x00000100 PageCount = 0x00000eff PPHYSICAL_MEMORY_RUN[2]: BasePage = 0x00001000 PageCount = 0x0001eff0 Context record: Eip = 0xf3b21315 ESP = 0xf8ab4928 EBP = 0xf8ab4c1c EAX=00000000 EBX=816b7000 ECX=00000000 EDX=00004e24 ESI=00000000 EDI=f8ab4c00 Translating virtual address [0xf3b21315]: 0x1d9ac315 Bytes at [EIP=f3b21315]: 58 89 85 d0 fd ff ff 9c Dump analysis finishedIII. Creating of own dump
Here is the most interesting thing - creating of own dump file.
First, we will need the undocumented structure KdDebuggerDataBlock, because it will be very helpful for this task. So, it contains following items:
Код (Text):
template <class T> struct PFUNC { T VirtualAddress; ULONG ZeroField; }; typedef struct _KD_DEBUGGER_DATA_BLOCK { ULONG Unknown1[4]; ULONG ValidBlock; // 'GBDK' ULONG Size; // 0x290 PFUNC<PVOID> _imp__VidInitialize; PFUNC<PVOID> RtlpBreakWithStatusInstruction; ULONG Unknown2[4]; PFUNC<PVOID> KiCallUserMode; ULONG Unknown3[2]; PFUNC<PVOID> PsLoadedModuleList; PFUNC<PVOID> PsActiveProcessHead; PFUNC<PVOID> PspCidTable; PFUNC<PVOID> ExpSystemResourcesList; PFUNC<PVOID> ExpPagedPoolDescriptor; PFUNC<PVOID> ExpNumberOfPagedPools; PFUNC<PVOID> KeTimeIncrement; PFUNC<PVOID> KeBugCheckCallbackListHead; PFUNC<PVOID> KiBugCheckData; PFUNC<PVOID> IopErrorLogListHead; PFUNC<PVOID> ObpRootDirectoryObject; PFUNC<PVOID> ObpTypeObjectType; PFUNC<PVOID> MmSystemCacheStart; PFUNC<PVOID> MmSystemCacheEnd; PFUNC<PVOID> MmSystemCacheWs; PFUNC<PVOID> MmPfnDatabase; PFUNC<PVOID> MmSystemPtesStart; PFUNC<PVOID> MmSystemPtesEnd; PFUNC<PVOID> MmSubsectionBase; PFUNC<PVOID> MmNumberOfPagingFiles; PFUNC<PVOID> MmLowestPhysicalPage; PFUNC<PVOID> MmHighestPhysicalPage; PFUNC<PVOID> MmNumberOfPhysicalPages; PFUNC<PVOID> MmMaximumNonPagedPoolInBytes; PFUNC<PVOID> MmNonPagedSystemStart; PFUNC<PVOID> MmNonPagedPoolStart; PFUNC<PVOID> MmNonPagedPoolEnd; PFUNC<PVOID> MmPagedPoolStart; PFUNC<PVOID> MmPagedPoolEnd; PFUNC<PVOID> MmPagedPoolInfo; PFUNC<PVOID> Unknown4; PFUNC<PVOID> MmSizeOfPagedPoolInBytes; PFUNC<PVOID> MmTotalCommitLimit; PFUNC<PVOID> MmTotalCommittedPages; PFUNC<PVOID> MmSharedCommit; PFUNC<PVOID> MmDriverCommit; PFUNC<PVOID> MmProcessCommit; PFUNC<PVOID> MmPagedPoolCommit; PFUNC<PVOID> Unknown5; PFUNC<PVOID> MmZeroedPageListHead; PFUNC<PVOID> MmFreePageListHead; PFUNC<PVOID> MmStandbyPageListHead; PFUNC<PVOID> MmModifiedPageListHead; PFUNC<PVOID> MmModifiedNoWritePageListHead; PFUNC<PVOID> MmAvailablePages; PFUNC<PVOID> MmResidentAvailablePages; PFUNC<PVOID> PoolTrackTable; PFUNC<PVOID> NonPagedPoolDescriptor; PFUNC<PVOID> MmHighestUserAddress; PFUNC<PVOID> MmSystemRangeStart; PFUNC<PVOID> MmUserProbeAddress; PFUNC<PVOID> KdPrintCircularBuffer; PFUNC<PVOID> KdPrintWritePointer; PFUNC<PVOID> KdPrintWritePointer2; PFUNC<PVOID> KdPrintRolloverCount; PFUNC<PVOID> MmLoadedUserImageList; PFUNC<PVOID> NtBuildLab; PFUNC<PVOID> Unknown6; PFUNC<PVOID> KiProcessorBlock; PFUNC<PVOID> MmUnloadedDrivers; PFUNC<PVOID> MmLastUnloadedDriver; PFUNC<PVOID> MmTriageActionTaken; PFUNC<PVOID> MmSpecialPoolTag; PFUNC<PVOID> KernelVerifier; PFUNC<PVOID> MmVerifierData; PFUNC<PVOID> MmAllocateNonPagedPool; PFUNC<PVOID> MmPeakCommitment; PFUNC<PVOID> MmTotalCommitLimitMaximum; PFUNC<PVOID> CmNtCSDVersion; PFUNC<PPHYSICAL_MEMORY_DESCRIPTOR*> MmPhysicalMemoryBlock; PFUNC<PVOID> MmSessionBase; PFUNC<PVOID> MmSessionSize; PFUNC<PVOID> Unknown7; } KD_DEBUGGER_DATA_BLOCK, *PKD_DEBUGGER_DATA_BLOCK;It is very convenient! We need only to get one non-exported address of KdDebuggerDataBlock, and every other required adresses are in our reach. Field "ValidBlock" should contain 'GBDK' signature, and Size should be equal to sizeof(KD_DEBUGGER_DATA_BLOCK).
Getting of KdDebuggerDataBlock address is possible via exported symbol KdDebuggerDataBlock = *(PVOID*)((ULONG)KeCapturePersistentThreadState + *(ULONG*)((ULONG)KeCapturePersistentThreadState + 0xC )+ 0x11);
Not very nice, but powerful: we get a lot of non-exported addresses of innet structures. We will need it to fill a dump header.
Now, as we know everything we need, I will show part of code for filling the first dump page.
Код (Text):
BOOLEAN InitializeDumpHeader( IN PBYTE HeaderPage ) { PDUMP_HEADER hdr; ULONG* blocks; PKD_DEBUGGER_DATA_BLOCK KdDebuggerDataBlock; EXCEPTION_RECORD exception; PVOID KeCapturePersistentThreadState; UNICODE_STRING uKeCapturePersistentThreadState; PPHYSICAL_MEMORY_DESCRIPTOR MmPhysicalMemoryBlock; CONTEXT ctx = {CONTEXT_FULL}; PEXCEPTION_POINTERS pei; // Get context __asm { // Common registers mov [ctx.Eax], eax mov [ctx.Ebx], ebx mov [ctx.Ecx], ecx mov [ctx.Edx], edx mov [ctx.Esi], esi mov [ctx.Edi], edi // Control registers mov [ctx.Esp], esp mov [ctx.Ebp], ebp call _1 // This address will appear in kd as crash address: _1: pop eax mov [ctx.Eip], eax pushfd pop eax mov [ctx.EFlags], eax // Debug registers __emit 0x0F __emit 0x21 __emit 0xC0 ; mov eax, dr0 mov [ctx.Dr0], eax __emit 0x0F __emit 0x21 __emit 0xC8 ; mov eax, dr1 mov [ctx.Dr1], eax __emit 0x0F __emit 0x21 __emit 0xD0 ; mov eax, dr2 mov [ctx.Dr2], eax __emit 0x0F __emit 0x21 __emit 0xD8 ; mov eax, dr3 mov [ctx.Dr3], eax __emit 0x0F __emit 0x21 __emit 0xF0 ; mov eax, dr6 mov [ctx.Dr6], eax __emit 0x0F __emit 0x21 __emit 0xF8 ; mov eax, dr7 mov [ctx.Dr7], eax // Segment registers push cs pop eax mov [ctx.SegCs], eax xor eax,eax mov ax, ss mov [ctx.SegSs], eax mov ax, ds mov [ctx.SegDs], eax mov ax, es mov [ctx.SegEs], eax mov ax, fs mov [ctx.SegFs], eax mov ax, gs mov [ctx.SegGs], eax } // Get KeCapturePersistentThreadState address RtlInitUnicodeString( &uKeCapturePersistentThreadState, L"KeCapturePersistentThreadState" ); KeCapturePersistentThreadState = MmGetSystemRoutineAddress( &uKeCapturePersistentThreadState ); // Initialize dump header hdr = (PDUMP_HEADER) HeaderPage; hdr->ValidDump = 'PMUD'; hdr->MinorVersion = (USHORT) *NtBuildNumber; hdr->MajorVersion = (USHORT) 0xF; // checked/free, ideally we should get // info from registry; if you look at // at code in the article attachment - // it is how it was done there hdr->DirectoryTableBase = CR3(); // // Capture KdDebuggerDataBlock // __try { hdr->KdDebuggerDataBlock = *(PVOID*)((ULONG)KeCapturePersistentThreadState + *(ULONG*)((ULONG)KeCapturePersistentThreadState + 0xC )+ 0x11); } __except( (pei=GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER ) { ULONG i; DbgPrint("An exception occurred while trying to get KdDebuggerDataBlock address:\n"); DbgPrint("Exception code: 0x%08x\n", pei->ExceptionRecord->ExceptionCode); DbgPrint("Number of arguments: 0x%08x\n", pei->ExceptionRecord->NumberParameters); for( i = 0; i < pei->ExceptionRecord->NumberParameters; i++ ) { DbgPrint("Argument[%d]: 0x%08x\n", i, pei->ExceptionRecord->ExceptionInformation[i]); } return FALSE; } hdr->MachineImageType = 0x14c; hdr->NumberProcessors = 1; hdr->BugCheckCode = KMODE_EXCEPTION_NOT_HANDLED; hdr->BugCheckParameter1 = STATUS_BREAKPOINT; hdr->BugCheckParameter2 = ctx.Eip; hdr->BugCheckParameter3 = 0; hdr->BugCheckParameter4 = 0; hdr->PaeEnabled = (CR4() & PAE_ENABLED) ? TRUE : FALSE; KdDebuggerDataBlock = (PKD_DEBUGGER_DATA_BLOCK) hdr->KdDebuggerDataBlock; // Check KdDebuggerDataBlock if( KdDebuggerDataBlock->ValidBlock != 'GBDK' || KdDebuggerDataBlock->Size != sizeof(*KdDebuggerDataBlock) ) { // Invalid debugger data block DbgPrint( "KdDebuggerDataBlock is not valid.\nSignature = 0x%08x (should be 0x%08x)\nSize = 0x%08x (should be 0x%08x)\n", KdDebuggerDataBlock->ValidBlock, 'GBDK', KdDebuggerDataBlock->Size, sizeof(*KdDebuggerDataBlock) ); return FALSE; } DbgPrint("Got valid KdDebuggerDataBlock=0x%08x\n", KdDebuggerDataBlock); hdr->PfnDataBase = (PULONG) KdDebuggerDataBlock->MmPfnDatabase.VirtualAddress; hdr->PsLoadedModuleList = (PLIST_ENTRY) KdDebuggerDataBlock->PsLoadedModuleList.VirtualAddress; hdr->PsActiveProcessHead = (PLIST_ENTRY) KdDebuggerDataBlock->PsActiveProcessHead.VirtualAddress; DbgPrint("PfnDataBase = 0x%08x\n", hdr->PfnDataBase); DbgPrint("PsLoadedModuleList = 0x%08x\n", hdr->PsLoadedModuleList); DbgPrint("PsActiveProcessHead = 0x%08x\n", hdr->PsActiveProcessHead); blocks = (ULONG*)(ULONG_PTR)HeaderPage; // // Get physical memory descriptor // MmPhysicalMemoryBlock = *(KdDebuggerDataBlock->MmPhysicalMemoryBlock.VirtualAddress); DbgPrint("MmPhysicalMemoryBlock = 0x%08x\n", MmPhysicalMemoryBlock); if( MmPhysicalMemoryBlock->NumberOfRuns == 'EGAP' ) { RtlCopyMemory( &blocks[ DH_PHYSICAL_MEMORY_BLOCK ], MmPhysicalMemoryBlock, sizeof(PHYSICAL_MEMORY_DESCRIPTOR) ); } else { RtlCopyMemory( &blocks[ DH_PHYSICAL_MEMORY_BLOCK ], MmPhysicalMemoryBlock, sizeof(PHYSICAL_MEMORY_DESCRIPTOR) - sizeof(PHYSICAL_MEMORY_RUN) + sizeof(PHYSICAL_MEMORY_RUN)*MmPhysicalMemoryBlock->NumberOfRuns ); } // // Save context record // RtlCopyMemory( &blocks[ DH_CONTEXT_RECORD ], &ctx, sizeof(CONTEXT) ); DbgPrint("Context record saved.\n"); DbgPrint("EAX=%08x EBX=%08x ECX=%08x EDX=%08x\n", ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx); DbgPrint("ESI=%08x EDI=%08x EBP=%08x ESP=%08x\n", ctx.Esi, ctx.Edi, ctx.Ebp, ctx.Esp); DbgPrint("EIP=%08x CS=%08x FS=%08x DS=%08x\n", ctx.Eip, ctx.SegCs, ctx.SegFs, ctx.SegDs); // // Create & store exception record // exception.ExceptionCode = STATUS_BREAKPOINT; exception.ExceptionFlags = EXCEPTION_NONCONTINUABLE; exception.ExceptionRecord = NULL; exception.ExceptionAddress = (PVOID) ctx.Eip; exception.NumberParameters = 0; RtlCopyMemory( &blocks[ DH_EXCEPTION_RECORD ], &exception, sizeof(EXCEPTION_RECORD) ); // // Initialize dump type & size // blocks[ DH_DUMP_TYPE ] = DUMP_TYPE_COMPLETE; ((LARGE_INTEGER*)&blocks[DH_REQUIRED_DUMP_SPACE])->QuadPart = ( MmPhysicalMemoryBlock->NumberOfPages << 12 ) + 0x1000; DbgPrint("Header page initialized OK\n"); return TRUE; }That's all. In the article's attachments you can find more fresh version of gr8lkd and sources of AnalyseCrashDump and gendump driver.
Attachments:
© Great, пер. Aquila
- gr8lkd2.rar (81.1 Kb)
- AnalyseCrashDump.rar (11.6 Kb)
- gendump.rar (10.1 Kb)
Description of DMP Format
Дата публикации 8 июн 2008