mirror of
https://github.com/volatilityfoundation/volatility
synced 2026-06-08 18:04:46 +00:00
401f6e8f8e
Note: binary data is left out for now in VERBOSE mode
422 lines
14 KiB
Python
422 lines
14 KiB
Python
# Volatility
|
|
# Copyright (C) 2007-2013 Volatility Foundation
|
|
# Copyright (c) 2010, 2011, 2012 Michael Ligh <michael.ligh@mnin.org>
|
|
#
|
|
# This file is part of Volatility.
|
|
#
|
|
# Volatility is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Volatility is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Volatility. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
import volatility.utils as utils
|
|
import volatility.obj as obj
|
|
import volatility.plugins.common as common
|
|
import volatility.win32.modules as modules
|
|
import volatility.win32.tasks as tasks
|
|
import volatility.debug as debug
|
|
import volatility.plugins.malware.malfind as malfind
|
|
import volatility.exceptions as exceptions
|
|
from volatility.renderers import TreeGrid
|
|
from volatility.renderers.basic import Address, Hex
|
|
|
|
#--------------------------------------------------------------------------------
|
|
# constants
|
|
#--------------------------------------------------------------------------------
|
|
|
|
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",
|
|
]))
|
|
|
|
#--------------------------------------------------------------------------------
|
|
# object classes
|
|
#--------------------------------------------------------------------------------
|
|
|
|
class _KIDTENTRY(obj.CType):
|
|
"""Class for interrupt descriptors"""
|
|
|
|
@property
|
|
def Address(self):
|
|
"""Return the address of the IDT entry handler"""
|
|
|
|
if self.ExtendedOffset == 0:
|
|
return 0
|
|
|
|
return (self.ExtendedOffset.v() << 16 | self.Offset.v())
|
|
|
|
class _KGDTENTRY(obj.CType):
|
|
"""A class for GDT entries"""
|
|
|
|
@property
|
|
def Type(self):
|
|
"""Get a string name of the descriptor type"""
|
|
|
|
flag = self.HighWord.Bits.Type.v() & 1 << 4
|
|
typeval = self.HighWord.Bits.Type.v() & ~(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))
|
|
|
|
@property
|
|
def Limit(self):
|
|
"""Get the limit (end) of memory for this GDT"""
|
|
limit = (self.HighWord.Bits.LimitHi.v() << 16) | self.LimitLow.v()
|
|
|
|
if self.HighWord.Bits.Granularity == 1:
|
|
limit = (limit + 1) * 0x1000
|
|
limit -= 1
|
|
|
|
return limit
|
|
|
|
@property
|
|
def CallGate(self):
|
|
"""Get the call gate address"""
|
|
return self.HighWord.v() & 0xffff0000 | self.LimitLow.v()
|
|
|
|
@property
|
|
def Present(self):
|
|
"""Returns True if the entry is present"""
|
|
return self.HighWord.Bits.Pres == 1
|
|
|
|
@property
|
|
def Granularity(self):
|
|
"""Returns True if page granularity is used. Otherwise
|
|
returns False indicating byte granularity is used."""
|
|
return self.HighWord.Bits.Granularity == 1
|
|
|
|
@property
|
|
def Dpl(self):
|
|
"""Returns the descriptor privilege level"""
|
|
return self.HighWord.Bits.Dpl
|
|
|
|
#--------------------------------------------------------------------------------
|
|
# profile modifications
|
|
#--------------------------------------------------------------------------------
|
|
|
|
class MalwareIDTGDTx86(obj.ProfileModification):
|
|
before = ['WindowsObjectClasses', 'WindowsOverlay']
|
|
conditions = {'os': lambda x: x == 'windows',
|
|
'memory_model': lambda x: x == '32bit'}
|
|
def modification(self, profile):
|
|
profile.object_classes.update({
|
|
'_KIDTENTRY': _KIDTENTRY,
|
|
'_KGDTENTRY': _KGDTENTRY,
|
|
})
|
|
profile.merge_overlay({"_KPCR" : [None,
|
|
{'IDT': [None,
|
|
["pointer", ["array", 256, ['_KIDTENTRY']]]],
|
|
}]})
|
|
# 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)
|
|
profile.merge_overlay({"_KPCR" : [None,
|
|
{'GDT': [None,
|
|
["pointer", ["array", 128, ['_KGDTENTRY']]]],
|
|
}]})
|
|
|
|
#--------------------------------------------------------------------------------
|
|
# GDT plugin
|
|
#--------------------------------------------------------------------------------
|
|
|
|
class GDT(common.AbstractWindowsCommand):
|
|
"Display Global Descriptor Table"
|
|
|
|
@staticmethod
|
|
def is_valid_profile(profile):
|
|
return (profile.metadata.get('os', 'unknown') == 'windows' and
|
|
profile.metadata.get('memory_model', '32bit') == '32bit')
|
|
|
|
def calculate(self):
|
|
addr_space = utils.load_as(self._config)
|
|
|
|
# Currently we only support x86. The x64 does still have a GDT
|
|
# but hooking is prohibited and results in bugcheck.
|
|
if not self.is_valid_profile(addr_space.profile):
|
|
debug.error("This command does not support the selected profile.")
|
|
|
|
for kpcr in tasks.get_kdbg(addr_space).kpcrs():
|
|
for i, entry in kpcr.gdt_entries():
|
|
yield i, entry
|
|
|
|
|
|
def unified_output(self, data):
|
|
# Note: binary data is left out for now in VERBOSE mode
|
|
return TreeGrid([("CPU", int),
|
|
("Sel", Address),
|
|
("Base", Address),
|
|
("Limit", Address),
|
|
("Type", str),
|
|
("DPL", int),
|
|
("Gr", str),
|
|
("Pr", str)],
|
|
self.generator(data))
|
|
|
|
def generator(self, data):
|
|
for n, entry in data:
|
|
selector = n * 8
|
|
|
|
# Is the entry present? This applies to all types of GDT entries
|
|
if entry.Present:
|
|
present = "P"
|
|
else:
|
|
present = "Np"
|
|
|
|
# The base, limit, and granularity is calculated differently
|
|
# for 32bit call gates than they are for all other types.
|
|
if entry.Type == 'CallGate32':
|
|
base = entry.CallGate
|
|
limit = 0
|
|
granularity = '-'
|
|
else:
|
|
base = entry.Base
|
|
limit = entry.Limit
|
|
if entry.Granularity:
|
|
granularity = "Pg"
|
|
else:
|
|
granularity = "By"
|
|
|
|
# The parent is GDT. The grand-parent is _KPCR
|
|
cpu_number = entry.obj_parent.obj_parent.ProcessorBlock.Number
|
|
|
|
yield (0, [int(cpu_number),
|
|
Address(selector),
|
|
Address(base), Address(limit),
|
|
str(entry.Type),
|
|
int(entry.Dpl),
|
|
str(granularity),
|
|
str(present)])
|
|
|
|
|
|
def render_text(self, outfd, data):
|
|
|
|
self.table_header(outfd, [('CPU', '>6'),
|
|
('Sel', '[addr]'),
|
|
('Base', '[addrpad]'),
|
|
('Limit', '[addrpad]'),
|
|
('Type', '<14'),
|
|
('DPL', '>6'),
|
|
('Gr', '<4'),
|
|
('Pr', '<4')
|
|
])
|
|
|
|
for n, entry in data:
|
|
|
|
selector = n * 8
|
|
|
|
# Is the entry present? This applies to all types of GDT entries
|
|
if entry.Present:
|
|
present = "P"
|
|
else:
|
|
present = "Np"
|
|
|
|
# The base, limit, and granularity is calculated differently
|
|
# for 32bit call gates than they are for all other types.
|
|
if entry.Type == 'CallGate32':
|
|
base = entry.CallGate
|
|
limit = 0
|
|
granularity = '-'
|
|
else:
|
|
base = entry.Base
|
|
limit = entry.Limit
|
|
if entry.Granularity:
|
|
granularity = "Pg"
|
|
else:
|
|
granularity = "By"
|
|
|
|
# The parent is GDT. The grand-parent is _KPCR
|
|
cpu_number = entry.obj_parent.obj_parent.ProcessorBlock.Number
|
|
|
|
self.table_row(outfd,
|
|
cpu_number,
|
|
selector,
|
|
base, limit,
|
|
entry.Type,
|
|
entry.Dpl,
|
|
granularity,
|
|
present)
|
|
|
|
#--------------------------------------------------------------------------------
|
|
# IDT plugin
|
|
#--------------------------------------------------------------------------------
|
|
|
|
class IDT(common.AbstractWindowsCommand):
|
|
"Display Interrupt Descriptor Table"
|
|
|
|
@staticmethod
|
|
def is_valid_profile(profile):
|
|
return (profile.metadata.get('os', 'unknown') == 'windows' and
|
|
profile.metadata.get('memory_model', '32bit') == '32bit')
|
|
|
|
@staticmethod
|
|
def get_section_name(mod, addr):
|
|
"""Get the name of the PE section containing
|
|
the specified address.
|
|
|
|
@param mod: an _LDR_DATA_TABLE_ENTRY
|
|
@param addr: virtual address to lookup
|
|
|
|
@returns string PE section name
|
|
"""
|
|
|
|
try:
|
|
dos_header = obj.Object("_IMAGE_DOS_HEADER",
|
|
offset = mod.DllBase, vm = mod.obj_vm)
|
|
nt_header = dos_header.get_nt_header()
|
|
except (ValueError, exceptions.SanityCheckException):
|
|
return ''
|
|
|
|
for sec in nt_header.get_sections():
|
|
if (addr > mod.DllBase + sec.VirtualAddress and
|
|
addr < sec.Misc.VirtualSize + (mod.DllBase + sec.VirtualAddress)):
|
|
return str(sec.Name or '')
|
|
|
|
return ''
|
|
|
|
def calculate(self):
|
|
addr_space = utils.load_as(self._config)
|
|
|
|
# Currently we only support x86. The x64 does still have a IDT
|
|
# but hooking is prohibited and results in bugcheck.
|
|
if not self.is_valid_profile(addr_space.profile):
|
|
debug.error("This command does not support the selected profile.")
|
|
|
|
mods = dict((addr_space.address_mask(mod.DllBase), mod) for mod in modules.lsmod(addr_space))
|
|
mod_addrs = sorted(mods.keys())
|
|
|
|
for kpcr in tasks.get_kdbg(addr_space).kpcrs():
|
|
# Get the GDT for access to selector bases
|
|
gdt = dict((i * 8, sd) for i, sd in kpcr.gdt_entries())
|
|
for i, entry in kpcr.idt_entries():
|
|
# Where the IDT entry points.
|
|
addr = entry.Address
|
|
# Per MITRE, add the GDT selector base if available.
|
|
# This allows us to detect sneaky attempts to hook IDT
|
|
# entries by changing the entry's GDT selector.
|
|
gdt_entry = gdt.get(entry.Selector.v())
|
|
if gdt_entry != None and "Code" in gdt_entry.Type:
|
|
addr += gdt_entry.Base
|
|
|
|
# Lookup the function's owner
|
|
module = tasks.find_module(mods, mod_addrs, addr_space.address_mask(addr))
|
|
|
|
yield i, entry, addr, module
|
|
|
|
def unified_output(self, data):
|
|
# Note: binary data is left out for now in VERBOSE mode
|
|
return TreeGrid([("CPU", Hex),
|
|
("Index", Hex),
|
|
("Selector", Address),
|
|
("Value", Address),
|
|
("Module", str),
|
|
("Section", str)],
|
|
self.generator(data))
|
|
|
|
def generator(self, data):
|
|
for n, entry, addr, module in data:
|
|
if addr == 0:
|
|
module_name = "NOT USED"
|
|
sect_name = ""
|
|
elif module:
|
|
module_name = str(module.BaseDllName or "")
|
|
sect_name = self.get_section_name(module, addr)
|
|
else:
|
|
module_name = "UNKNOWN"
|
|
sect_name = ""
|
|
|
|
# The parent is IDT. The grand-parent is _KPCR.
|
|
cpu_number = entry.obj_parent.obj_parent.ProcessorBlock.Number
|
|
|
|
yield (0, [Hex(cpu_number), Hex(n),
|
|
Address(entry.Selector),
|
|
Address(addr),
|
|
str(module_name),
|
|
str(sect_name)])
|
|
|
|
|
|
def render_text(self, outfd, data):
|
|
|
|
self.table_header(outfd, [('CPU', '>6X'),
|
|
('Index', '>6X'),
|
|
('Selector', '[addr]'),
|
|
('Value', '[addrpad]'),
|
|
('Module', '20'),
|
|
('Section', '12'),
|
|
])
|
|
|
|
for n, entry, addr, module in data:
|
|
|
|
if addr == 0:
|
|
module_name = "NOT USED"
|
|
sect_name = ''
|
|
elif module:
|
|
module_name = str(module.BaseDllName or '')
|
|
sect_name = self.get_section_name(module, addr)
|
|
else:
|
|
module_name = "UNKNOWN"
|
|
sect_name = ''
|
|
|
|
# The parent is IDT. The grand-parent is _KPCR.
|
|
cpu_number = entry.obj_parent.obj_parent.ProcessorBlock.Number
|
|
|
|
self.table_row(outfd,
|
|
cpu_number, n,
|
|
entry.Selector,
|
|
addr,
|
|
module_name,
|
|
sect_name)
|
|
|
|
if self._config.verbose:
|
|
data = entry.obj_vm.zread(addr, 32)
|
|
outfd.write("\n".join(
|
|
["{0:#x} {1:<16} {2}".format(o, h, i)
|
|
for o, i, h in malfind.Disassemble(data = data, start = addr, stoponret = True)
|
|
]))
|
|
outfd.write("\n")
|