Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugins: Initial prototype crashdump writer #472

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions volatility3/framework/plugins/windows/writecrashdump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import math
from typing import List, Type

from volatility3.framework import interfaces, renderers, constants, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins, configuration
from volatility3.framework.layers import physical, intel
from volatility3.framework.symbols import intermed
from volatility3.plugins.windows import info


class WriteCrashDump(plugins.PluginInterface):
"""Runs the automagics and writes the output to a crashdump format file"""
default_block_size = 0x500000

_required_framework_version = (1, 0, 0)
_version = (2, 0, 0)

@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.TranslationLayerRequirement(name = 'primary', description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "nt_symbols", description = "Windows kernel symbols"),
requirements.IntRequirement(name = 'block_size',
description = "Size of blocks to copy over",
default = cls.default_block_size,
optional = True),
requirements.VersionRequirement("info", component = info.Info, version = (1, 0, 0))
]

@classmethod
def write_crashdump(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str,
open_method: Type[interfaces.plugins.FileHandlerInterface]):
primary = context.layers[layer_name]
is_pae = isinstance(primary, intel.IntelPAE)
is_64_bit = isinstance(primary, intel.Intel32e)

if is_64_bit:
crashdump_json = 'crash64'
dump_header_name = '_DUMP_HEADER64'
valid_dump_suffix = [ord('6'), ord('4')]
else:
crashdump_json = 'crash'
dump_header_name = '_DUMP_HEADER'
valid_dump_suffix = [ord('M'), ord('P')]

config_path = 'whatever'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad past @ikelos , very, very bad. This sets a really bad example, and this needs changing before this gets committed.

crash_table_name = intermed.IntermediateSymbolTable.create(context,
configuration.path_join(config_path, 'symbols'),
'',
crashdump_json)

dump_header_type = context.symbol_space[crash_table_name].get_type(dump_header_name)
header_layer_name = context.layers.free_layer_name('header_layer')
header_layer = physical.BufferDataLayer(context,
configuration.path_join(config_path, 'layer'),
name = header_layer_name,
buffer = b"PAGE" * (dump_header_type.size // 4))
context.layers.add_layer(header_layer)
dump_header = context.object(dump_header_type, header_layer.name, 0)

kdbg = info.Info.get_kdbg_structure(context, configuration.path_join(config_path, 'info'), layer_name,
symbol_table)
kuser = info.Info.get_kuser_structure(context, layer_name,
symbol_table)

dump_header.ValidDump.write([ord('D'), ord('U')] + valid_dump_suffix)
dump_header.KdDebuggerDataBlock.write(kdbg.vol.offset | (0 if not is_64_bit else 0xFFFF000000000000))
dump_header.MajorVersion.write(0)
dump_header.MinorVersion.write(0)
dump_header.DirectoryTableBase.write(primary.config['page_map_offset'])
dump_header.PaeEnabled.write(int(is_pae))
dump_header.PfnDataBase.write(kdbg.MmPfnDatabase)
dump_header.PsLoadedModuleList.write(kdbg.PsLoadedModuleList)
dump_header.PsActiveProcessHead.write(kdbg.PsActiveProcessHead)
dump_header.DumpType.write(1)

dump_header.SystemTime.write(kuser.SystemTime.cast('unsigned long long'))

dump_header.BugCheckCode.write(0)
dump_header.BugCheckCodeParameter.write([0, 0, 0, 0])

blank_len = dump_header.Exception.vol.offset - dump_header.ContextRecord.vol.offset
header_layer.write(dump_header.ContextRecord.vol.offset, b"\x00" * blank_len)

dump_header.Comment.write(
bytes("Volatility 3 {} generated crashdump file\x00".format(constants.PACKAGE_VERSION),
'latin-1'))

# Write the actual data
virtual_layer = context.layers[layer_name]
physical_layer_names = set([layer for _, _, _, _, layer in virtual_layer.mapping(virtual_layer.minimum_address,
virtual_layer.maximum_address - virtual_layer.minimum_address,
ignore_errors = True)])
if len(physical_layer_names) != 1:
raise exceptions.LayerException("Unable to write virtual layer with multiple physical sources")
physical_layer = context.layers[physical_layer_names.pop()]

page_count = math.ceil((physical_layer.maximum_address - physical_layer.minimum_address) / 1024)
dump_header.PhysicalMemoryBlockBuffer.NumberOfRuns.write(1)
dump_header.PhysicalMemoryBlockBuffer.NumberOfPages.write(page_count)
run0 = dump_header.PhysicalMemoryBlockBuffer.Run[0]
run0.BasePage.write(physical_layer.minimum_address)
run0.PageCount.write(page_count)

# We don't try any form of compression, but just write the data as one large run
with open_method('crash.dmp') as f:
# We want to include the maxmium address
header_data = header_layer.read(0, header_layer.maximum_address + 1)
f.write(header_data)
for offset in range(physical_layer.minimum_address, physical_layer.maximum_address, 0x1000):

f.write(physical_layer.read(offset, 0x1000, pad = True))

def _generator(self):
self.write_crashdump(self.context, self.config['primary'], self.config['nt_symbols'], self._file_handler)
yield 0, ('Done',)

def run(self):
return renderers.TreeGrid([("Status", str)], self._generator())