mirror of
https://github.com/volatilityfoundation/volatility
synced 2026-06-08 18:04:46 +00:00
e8b6ece9a5
this bug would cause string translation to fail for strings on PAE and x64 images found in physical offsets greater than 4GB. specifically, it would report them to the in FREE memory rather than the correct process or kernel module.
264 lines
9.9 KiB
Python
264 lines
9.9 KiB
Python
# Volatility
|
|
# Copyright (C) 2007-2013 Volatility Foundation
|
|
# Copyright (C) 2009 Timothy D. Morgan (strings optimization)
|
|
#
|
|
# 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 os
|
|
import volatility.win32 as win32
|
|
import volatility.debug as debug
|
|
import volatility.utils as utils
|
|
import volatility.plugins.common as common
|
|
import volatility.plugins.taskmods as taskmods
|
|
import volatility.plugins.filescan as filescan
|
|
from volatility.renderers import TreeGrid
|
|
from volatility.renderers.basic import Address
|
|
|
|
class Strings(common.AbstractWindowsCommand):
|
|
"""Match physical offsets to virtual addresses (may take a while, VERY verbose)"""
|
|
|
|
def __init__(self, config, *args, **kwargs):
|
|
common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs)
|
|
|
|
config.add_option('STRING-FILE', short_option = 's', default = None,
|
|
help = 'File output in strings format (offset:string)',
|
|
action = 'store', type = 'str')
|
|
config.add_option("SCAN", short_option = 'S', default = False,
|
|
action = 'store_true', help = 'Use PSScan if no offset is provided')
|
|
config.add_option('OFFSET', short_option = 'o', default = None,
|
|
help = 'EPROCESS offset (in hex) in the physical address space',
|
|
action = 'store', type = 'int')
|
|
config.add_option('PID', short_option = 'p', default = None,
|
|
help = 'Operate on these Process IDs (comma-separated)',
|
|
action = 'store', type = 'str')
|
|
|
|
def get_processes(self, addr_space):
|
|
"""Enumerate processes based on user options.
|
|
|
|
:param addr_space | <addrspace.AbstractVirtualAddressSpace>
|
|
|
|
:returns <list>
|
|
"""
|
|
|
|
bounce_back = taskmods.DllList.virtual_process_from_physical_offset
|
|
if self._config.OFFSET != None:
|
|
tasks = [bounce_back(addr_space, self._config.OFFSET)]
|
|
elif self._config.SCAN:
|
|
procs = list(filescan.PSScan(self._config).calculate())
|
|
tasks = []
|
|
for task in procs:
|
|
tasks.append(bounce_back(addr_space, task.obj_offset))
|
|
else:
|
|
tasks = win32.tasks.pslist(addr_space)
|
|
|
|
try:
|
|
if self._config.PID is not None:
|
|
pidlist = [int(p) for p in self._config.PID.split(',')]
|
|
tasks = [t for t in tasks if int(t.UniqueProcessId) in pidlist]
|
|
except (ValueError, TypeError):
|
|
debug.error("Invalid PID {0}".format(self._config.PID))
|
|
|
|
return tasks
|
|
|
|
@classmethod
|
|
def get_modules(cls, addr_space):
|
|
"""Enumerate the kernel modules.
|
|
|
|
:param addr_space | <addrspace.AbstractVirtualAddressSpace>
|
|
|
|
:returns <tuple>
|
|
"""
|
|
|
|
modules = win32.modules.lsmod(addr_space)
|
|
mask = addr_space.address_mask
|
|
mods = dict((mask(mod.DllBase), mod) for mod in modules)
|
|
mod_addrs = sorted(mods.keys())
|
|
|
|
return (mods, mod_addrs)
|
|
|
|
@classmethod
|
|
def find_module(cls, mods, mod_addrs, addr_space, vpage):
|
|
"""Determine which module owns a virtual page.
|
|
|
|
:param mods | <list>
|
|
mod_addrs | <list>
|
|
addr_space | <addrspace.AbstractVirtualAddressSpace>
|
|
vpage | <int>
|
|
|
|
:returns <_LDR_DATA_TABLE_ENTRY> || None
|
|
"""
|
|
|
|
mask = addr_space.address_mask
|
|
return win32.tasks.find_module(mods, mod_addrs, mask(vpage))
|
|
|
|
@classmethod
|
|
def get_module_name(cls, module):
|
|
"""Get the name of a kernel module.
|
|
|
|
:param module | <_LDR_DATA_TABLE_ENTRY>
|
|
|
|
:returns <str>
|
|
"""
|
|
|
|
return str(module.BaseDllName or '')
|
|
|
|
@classmethod
|
|
def get_task_pid(cls, task):
|
|
"""Get the PID of a process.
|
|
|
|
:param task | <_EPROCESS>
|
|
|
|
:returns <int>
|
|
"""
|
|
|
|
return task.UniqueProcessId
|
|
|
|
def calculate(self):
|
|
|
|
if (self._config.STRING_FILE is None or
|
|
not os.path.exists(self._config.STRING_FILE)):
|
|
debug.error("Strings file not found")
|
|
|
|
addr_space = utils.load_as(self._config)
|
|
tasks = self.get_processes(addr_space)
|
|
|
|
stringlist = open(self._config.STRING_FILE, "r")
|
|
reverse_map = self.get_reverse_map(addr_space, tasks)
|
|
|
|
for line in stringlist:
|
|
try:
|
|
(offsetString, string) = self.parse_line(line)
|
|
offset = int(offsetString)
|
|
except ValueError:
|
|
debug.error("String file format invalid.")
|
|
|
|
pids = ["FREE MEMORY:-1"]
|
|
if reverse_map.has_key(offset & 0xFFFFFFFFFFFFF000):
|
|
pids = ["{0}:{1:08x}".format(pid[0], pid[1] | (offset & 0xFFF)) for pid in reverse_map[offset & 0xFFFFFFFFFFFFF000][1:]]
|
|
|
|
yield offset, pids, "{0}".format(string.strip())
|
|
|
|
@classmethod
|
|
def parse_line(cls, line):
|
|
"""Parses a line of strings.
|
|
|
|
:param cls | <Strings>
|
|
line | <str>
|
|
|
|
:returns <tuple>
|
|
"""
|
|
# Remove any leading spaces to handle nasty strings output
|
|
line = line.lstrip()
|
|
maxlen = len(line)
|
|
split_char = ' '
|
|
for char in [' ', ':']:
|
|
charpos = line.find(char)
|
|
if charpos < maxlen and charpos > 0:
|
|
split_char = char
|
|
maxlen = charpos
|
|
return tuple(line.split(split_char, 1))
|
|
|
|
@classmethod
|
|
def get_reverse_map(cls, addr_space, tasks):
|
|
"""Generates a reverse mapping of physical addresses
|
|
to the kernel and/or tasks.
|
|
|
|
:param addr_space | <addrspace.AbstractVirtualAddressSpace>
|
|
tasks | <list>
|
|
|
|
:returns <dict>
|
|
"""
|
|
|
|
# ASSUMPTION: no pages mapped in kernel and userland
|
|
# XXX: Can we eliminate the above assumption? It seems like the only change needed for
|
|
# that would be to store a boolean with each pid/vaddr pair...
|
|
#
|
|
# XXX: The following code still fails to represent information about larger pages in
|
|
# the final output. The output implies that addresses in a large page are
|
|
# really stored in one or more 4k pages. This is no different from the old
|
|
# version of the code, but in this version it could be corrected easily by
|
|
# recording vpage instead of vpage+i in the reverse map. -- TDM
|
|
reverse_map = {}
|
|
|
|
(mods, mod_addrs) = cls.get_modules(addr_space)
|
|
|
|
debug.debug("Calculating kernel mapping...\n")
|
|
available_pages = addr_space.get_available_pages()
|
|
for (vpage, vpage_size) in available_pages:
|
|
kpage = addr_space.vtop(vpage)
|
|
for i in range(0, vpage_size, 0x1000):
|
|
# Since the output will always be mutable, we
|
|
# don't need to reinsert into the list
|
|
pagelist = reverse_map.get(kpage + i, None)
|
|
if pagelist is None:
|
|
pagelist = [True]
|
|
reverse_map[kpage + i] = pagelist
|
|
# Try to lookup the owning kernel module
|
|
module = cls.find_module(mods, mod_addrs, addr_space, vpage + i)
|
|
if module:
|
|
hint = cls.get_module_name(module)
|
|
else:
|
|
hint = 'kernel'
|
|
pagelist.append((hint, vpage + i))
|
|
|
|
debug.debug("Calculating task mappings...\n")
|
|
for task in tasks:
|
|
task_space = task.get_process_address_space()
|
|
debug.debug(" Task {0} ...".format(cls.get_task_pid(task)))
|
|
process_id = cls.get_task_pid(task)
|
|
try:
|
|
available_pages = task_space.get_available_pages()
|
|
for (vpage, vpage_size) in available_pages:
|
|
physpage = task_space.vtop(vpage)
|
|
for i in range(0, vpage_size, 0x1000):
|
|
# Since the output will always be mutable, we
|
|
# don't need to reinsert into the list
|
|
pagelist = reverse_map.get(physpage + i, None)
|
|
if pagelist is None:
|
|
pagelist = [False]
|
|
reverse_map[physpage + i] = pagelist
|
|
if not pagelist[0]:
|
|
pagelist.append((process_id, vpage + i))
|
|
|
|
except (AttributeError, ValueError, TypeError):
|
|
# Handle most errors, but not all of them
|
|
continue
|
|
|
|
return reverse_map
|
|
|
|
|
|
def unified_output(self, data):
|
|
return TreeGrid([("Offset(P)", Address),
|
|
("Attribution", str),
|
|
("Offset(V)", Address),
|
|
("String", str)],
|
|
self.generator(data))
|
|
|
|
def generator(self, data):
|
|
for offset, pids, string in data:
|
|
for p in pids:
|
|
item, addr = p.split(":")
|
|
yield (0, [Address(offset),
|
|
str(item),
|
|
Address(int(addr, 16)),
|
|
str(string)])
|
|
|
|
def render_text(self, outfd, data):
|
|
for offset, pids, string in data:
|
|
outfd.write("{0} [{1}] {2}\n".format(offset, ' '.join(pids), string))
|
|
|