-
Notifications
You must be signed in to change notification settings - Fork 390
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
Feat: Migrate windows idt plug-in to volatility 3 #976
base: develop
Are you sure you want to change the base?
Changes from all commits
d6d94d6
85c918e
d530d2f
1b64953
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,295 @@ | ||
from typing import List | ||
|
||
from volatility3.framework import interfaces | ||
from volatility3.framework.renderers import TreeGrid, format_hints | ||
from volatility3.framework.interfaces import plugins | ||
from volatility3.framework.configuration import requirements | ||
from volatility3.plugins.windows import modules | ||
|
||
GDT_DESCRIPTORS = dict( | ||
enumerate( | ||
[ | ||
"Data RO", | ||
"Data RO Ac", | ||
"Data RW", | ||
"Data RW Ac", | ||
"Data RO E", | ||
"Data RO EA", | ||
"Data RW E", | ||
"Data RW EA", | ||
"Code EO", | ||
"Code EO Ac", | ||
"Code RE", | ||
"Code RE Ac", | ||
"Code EO C", | ||
"Code EO CA", | ||
"Code RE C", | ||
"Code RE CA", | ||
"<Reserved>", | ||
"TSS16 Avl", | ||
"LDT", | ||
"TSS16 Busy", | ||
"CallGate16", | ||
"TaskGate", | ||
"Int Gate16", | ||
"TrapGate16", | ||
"<Reserved>", | ||
"TSS32 Avl", | ||
"<Reserved>", | ||
"TSS32 Busy", | ||
"CallGate32", | ||
"<Reserved>", | ||
"Int Gate32", | ||
"TrapGate32", | ||
] | ||
) | ||
) | ||
|
||
|
||
class _KIDT: | ||
def __init__(self, idt_struct): | ||
self.idt = idt_struct | ||
self.Offset = idt_struct.Offset | ||
self.Selector = idt_struct.Selector | ||
self.Access = idt_struct.Access | ||
self.ExtendedOffset = idt_struct.ExtendedOffset | ||
|
||
@property | ||
def Address(self): | ||
if self.ExtendedOffset: | ||
return self.ExtendedOffset << 16 | self.Offset | ||
|
||
return 0 | ||
|
||
|
||
class _KGDT: | ||
def __init__(self, gdt_struct): | ||
self.gdt = gdt_struct | ||
self.LimitLow = gdt_struct.LimitLow | ||
self.BaseLow = gdt_struct.BaseLow | ||
self.HighWord = gdt_struct.HighWord | ||
|
||
@property | ||
def Type(self): | ||
"""Get a string name of the descriptor type""" | ||
flag = self.HighWord.Bits.Type & 1 << 4 | ||
typeval = self.HighWord.Bits.Type & ~(1 << 4) | ||
|
||
if flag == 0: | ||
typeval += 16 | ||
|
||
return GDT_DESCRIPTORS.get(typeval, "UNKNOWN") | ||
|
||
@property | ||
def Base(self): | ||
"""Get the base (start) of memory for this GDT""" | ||
return self.BaseLow + ( | ||
(self.HighWord.Bits.BaseMid + (self.HighWord.Bits.BaseHi << 8)) << 16 | ||
) | ||
|
||
|
||
class _KPCR: | ||
def __init__(self, kpcr_obj, ntkrnlmp, layer_name, symbol_table): | ||
self.kpcr = kpcr_obj | ||
self.ntkrnlmp = ntkrnlmp | ||
self.layer_name = layer_name | ||
self.symbol_table = symbol_table | ||
|
||
def idt_entries(self): | ||
base_idt = self.kpcr.IDT | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this may only work for x86. On x64, the |
||
idt_index = 0 | ||
for idt_index in range(256): | ||
idt_offset = base_idt + 8 * idt_index | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A good shortcut here would be to use something similar to the code below (from the Windows
You could just create an array of 256 |
||
idt_struct = self.ntkrnlmp.object( | ||
object_type="_KIDTENTRY", | ||
layer_name=self.layer_name, | ||
offset=idt_offset, | ||
absolute=True, | ||
) | ||
try: | ||
yield idt_index, _KIDT(idt_struct) | ||
except: | ||
Check notice Code scanning / CodeQL Empty except Note
'except' clause does nothing but pass and there is no explanatory comment.
Check notice Code scanning / CodeQL Except block handles 'BaseException' Note
Except block directly handles BaseException.
|
||
pass | ||
|
||
def gdt_entries(self): | ||
base_gdt = self.kpcr.GDT | ||
|
||
# Since the real GDT size is read from a register, we'll just assume | ||
# that there are 128 entries (which is normal for most OS) | ||
for gdt_index in range(128): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as above regarding the use of |
||
gdt_offset = base_gdt + 8 * gdt_index | ||
gdt_struct = self.ntkrnlmp.object( | ||
object_type="_KGDTENTRY", | ||
layer_name=self.layer_name, | ||
offset=gdt_offset, | ||
absolute=True, | ||
) | ||
|
||
try: | ||
yield gdt_index, _KGDT(gdt_struct) | ||
except: | ||
Check notice Code scanning / CodeQL Empty except Note
'except' clause does nothing but pass and there is no explanatory comment.
Check notice Code scanning / CodeQL Except block handles 'BaseException' Note
Except block directly handles BaseException.
|
||
pass | ||
|
||
|
||
class IDT(plugins.PluginInterface): | ||
"""Lists the Interrupt Descriptor Table (IDT)""" | ||
|
||
_required_framework_version = (2, 0, 0) | ||
_version = (1, 0, 0) | ||
|
||
@classmethod | ||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: | ||
return [ | ||
requirements.ModuleRequirement( | ||
name="kernel", | ||
description="Windows kernel", | ||
architectures=["Intel32", "Intel64"], | ||
), | ||
requirements.PluginRequirement( | ||
name="modules", plugin=modules.Modules, version=(1, 0, 0) | ||
), | ||
] | ||
|
||
def get_module( | ||
self, | ||
context: interfaces.context.ContextInterface, | ||
layer_name: str, | ||
symbol_table: str, | ||
offset: int, | ||
): | ||
try: | ||
mods = modules.Modules.list_modules(context, layer_name, symbol_table) | ||
|
||
for mod in mods: | ||
if mod.DllBase + mod.SizeOfImage >= offset and mod.DllBase <= offset: | ||
return mod | ||
except: | ||
Check notice Code scanning / CodeQL Empty except Note
'except' clause does nothing but pass and there is no explanatory comment.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please figure out exactly which type of exception you're intending to handle, and catch only that one. This allows for other errors that might not have been envisaged to bubble up through the code and potentially be handled more appropriately by something higher up. This goes for all try/except blocks in this code. Check notice Code scanning / CodeQL Except block handles 'BaseException' Note
Except block directly handles BaseException.
|
||
pass | ||
|
||
return None | ||
|
||
@staticmethod | ||
def get_section_name(ntkrnlmp, layer_name, mod, addr): | ||
"""Get the name of the PE section containing | ||
the specified address. | ||
|
||
@param ntkrnlmp: ntkrnlmp module object | ||
@param layer_name: kernel layer name | ||
@param mod: an _LDR_DATA_TABLE_ENTRY | ||
@param addr: virtual address to lookup | ||
|
||
@returns string PE section name | ||
""" | ||
|
||
def name_array_to_str(name_array): | ||
name = "" | ||
for char in name_array: | ||
if char <= 0: | ||
break | ||
name += chr(char) | ||
return name | ||
|
||
try: | ||
dos_header = ntkrnlmp.object( | ||
object_type="_IMAGE_DOS_HEADER", | ||
layer_name=layer_name, | ||
offset=mod.DllBase, | ||
absolute=True, | ||
) | ||
nt_header = dos_header.get_nt_header() | ||
except ValueError: | ||
return '' | ||
|
||
for sec in nt_header.get_sections(): | ||
if ( | ||
addr > mod.DllBase + sec.VirtualAddress | ||
and addr < sec.Misc.VirtualSize + (mod.DllBase + sec.VirtualAddress) | ||
): | ||
return name_array_to_str(sec.Name) or "" | ||
|
||
return '' | ||
|
||
def get_pcrs(self, ntkrnlmp, layer_name, symbol_table): | ||
# Get the number of processors | ||
cpu_count_offset = ntkrnlmp.get_symbol("KeNumberProcessors").address | ||
cpu_count = ntkrnlmp.object( | ||
object_type="unsigned int", layer_name=layer_name, offset=cpu_count_offset | ||
) | ||
|
||
for cpu_index in range(cpu_count): | ||
# Calculate the address of KiProcessorBlock | ||
KiProcessorBlock_addr = ( | ||
ntkrnlmp.get_symbol("KiProcessorBlock").address + cpu_index * 4 | ||
) | ||
KiProcessorBlock = ntkrnlmp.object( | ||
object_type="pointer", | ||
layer_name=layer_name, | ||
offset=KiProcessorBlock_addr, | ||
) | ||
|
||
# Get kpcr object | ||
kpcr_offset = ntkrnlmp.get_type("_KPCR").relative_child_offset("PrcbData") | ||
kpcr = ntkrnlmp.object( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like you're trying to wrap the KPCR data into a custom object, rather than using a volatility object? Can I please strongly recommend that you consider defining a JSON file with the appropriate types for a _KPCR if the standard ones aren't suitable? You can even define an override class if there are specific methods or calculations you require (such as I don't recall whether it was the KPCR or the KD_DEBUGGER_DATA that started getting encrypted by Microsoft, but if that is the case for the KPCR, we may want a more generic/general means to handle it, so all the code can live in a central location in the core. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was KD_DEBUGGER_DATA that gets encrypted now. AFAIK the |
||
object_type="_KPCR", | ||
layer_name=layer_name, | ||
offset=KiProcessorBlock - kpcr_offset, | ||
absolute=True, | ||
) | ||
|
||
yield cpu_index, _KPCR(kpcr, ntkrnlmp, layer_name, symbol_table) | ||
|
||
def _generator(self): | ||
# Initialize the ntkrnlmp object and etc. | ||
kernel = self.context.modules[self.config["kernel"]] | ||
layer_name = kernel.layer_name | ||
symbol_table = kernel.symbol_table_name | ||
kvo = self.context.layers[layer_name].config["kernel_virtual_offset"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should also be accessible as |
||
ntkrnlmp = self.context.module(symbol_table, layer_name=layer_name, offset=kvo) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be the same as kernel? |
||
|
||
for cpu_index, kpcr in self.get_pcrs(ntkrnlmp, layer_name, symbol_table): | ||
gdt = dict((i * 8, sd) for i, sd in kpcr.gdt_entries()) | ||
for idt_index, idt in kpcr.idt_entries(): | ||
addr = idt.Address | ||
gdt_entry = gdt.get(idt.Selector, None) | ||
|
||
if gdt_entry is not None and "Code" in gdt_entry.Type: | ||
addr += gdt_entry.Base | ||
|
||
module = self.get_module(self.context, layer_name, symbol_table, addr) | ||
|
||
if addr == 0: | ||
module_name = "NOT USED" | ||
sect_name = '' | ||
elif module: | ||
module_name = module.BaseDllName.get_string() | ||
sect_name = self.get_section_name( | ||
ntkrnlmp, layer_name, module, addr | ||
) | ||
else: | ||
module_name = "UNKNOWN" | ||
sect_name = '' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use a class derived from |
||
|
||
yield ( | ||
0, | ||
( | ||
cpu_index, | ||
hex(idt_index).replace("0x", "").upper(), | ||
format_hints.Hex(idt.Selector), | ||
format_hints.Hex(idt.Address), | ||
module_name, | ||
sect_name, | ||
), | ||
) | ||
|
||
def run(self): | ||
return TreeGrid( | ||
[ | ||
('CPU', int), | ||
('Index', str), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it isn't clear why Index is a |
||
('Selector', format_hints.Hex), | ||
('Value', format_hints.Hex), | ||
('Module', str), | ||
('Section', str), | ||
], | ||
self._generator(), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ma1icious I'd recommend using "extensions" instead of this style of class representing
_KIDT
,_KGDT
, and_KPCR
. There are examples here:https://github.com/volatilityfoundation/volatility3/tree/develop/volatility3/framework/symbols/windows/extensions
The advantage is the classes will then be accessible from all plugins, not just the IDT plugin. Also, it will eliminate all the lines like
self.Offset = idt_struct.Offset
as those will just be set naturally. The properties and methods can mostly stay the same, as they work on extensions as well.