# Volatility # Copyright (C) 2008-2013 Volatility Foundation # # 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 . # """ @author: AAron Walters and Brendan Dolan-Gavitt @license: GNU General Public License 2.0 @contact: awalters@4tphi.net,bdolangavitt@wesleyan.edu @organization: Volatility Foundation """ import struct import sys import volatility.plugins.common as common import volatility.win32 as win32 import volatility.utils as utils import volatility.obj as obj import volatility.plugins.taskmods as taskmods try: import distorm3 #pylint: disable-msg=F0401 except ImportError: pass class volshell(common.AbstractWindowsCommand): """Shell in the memory image""" # Declare meta information associated with this plugin meta_info = {} meta_info['author'] = 'Brendan Dolan-Gavitt' meta_info['copyright'] = 'Copyright (c) 2007,2008 Brendan Dolan-Gavitt' meta_info['contact'] = 'bdolangavitt@wesleyan.edu' meta_info['license'] = 'GNU General Public License 2.0' meta_info['url'] = 'http://moyix.blogspot.com/' meta_info['os'] = 'WIN_32_XP_SP2' meta_info['version'] = '1.3' def __init__(self, config, *args, **kwargs): common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs) config.add_option('OFFSET', short_option = 'o', default = None, help = 'EPROCESS Offset (in hex) in kernel address space', action = 'store', type = 'int') config.add_option('IMNAME', short_option = 'n', default = None, help = 'Operate on this Process name', action = 'store', type = 'str') config.add_option('PID', short_option = 'p', default = None, help = 'Operate on these Process IDs (comma-separated)', action = 'store', type = 'str') self._addrspace = None self._proc = None def getpidlist(self): return win32.tasks.pslist(self._addrspace) def getmodules(self): return win32.modules.lsmod(self._addrspace) def context_display(self): print "Current context: {0} @ {1:#x}, pid={2}, ppid={3} DTB={4:#x}".format(self._proc.ImageFileName, self._proc.obj_offset, self._proc.UniqueProcessId.v(), self._proc.InheritedFromUniqueProcessId.v(), self._proc.Pcb.DirectoryTableBase.v()) def ps(self, procs = None): print "{0:16} {1:6} {2:6} {3:8}".format("Name", "PID", "PPID", "Offset") for eproc in procs or self.getpidlist(): print "{0:16} {1:<6} {2:<6} {3:#08x}".format(eproc.ImageFileName, eproc.UniqueProcessId.v(), eproc.InheritedFromUniqueProcessId.v(), eproc.obj_offset) def modules(self, modules = None): if self._addrspace.profile.metadata.get('memory_model', '32bit') == '32bit': print "{0:10} {1:10} {2}".format("Offset", "Base", "Name") else: print "{0:18} {1:18} {2}".format("Offset", "Base", "Name") for module in modules or self.getmodules(): print "{0:#08x} {1:#08x} {2}".format(module.obj_offset, module.DllBase, module.FullDllName or module.BaseDllName or '') def set_context(self, offset = None, pid = None, name = None, physical = False): if physical and offset != None: offset = taskmods.DllList.virtual_process_from_physical_offset(self._addrspace, offset).obj_offset elif pid is not None: offsets = [] for p in self.getpidlist(): if p.UniqueProcessId.v() == pid: offsets.append(p) if not offsets: print "Unable to find process matching pid {0}".format(pid) return elif len(offsets) > 1: print "Multiple processes match {0}, please specify by offset".format(pid) print "Matching processes:" self.ps(offsets) return else: offset = offsets[0].v() elif name is not None: offsets = [] for p in self.getpidlist(): if p.ImageFileName.find(name) >= 0: offsets.append(p) if not offsets: print "Unable to find process matching name {0}".format(name) return elif len(offsets) > 1: print "Multiple processes match name {0}, please specify by PID or offset".format(name) print "Matching processes:" self.ps(offsets) return else: offset = offsets[0].v() elif offset is None: print "Must provide one of: offset, name, or pid as a argument." return self._proc = obj.Object("_EPROCESS", offset = offset, vm = self._addrspace) self.context_display() def render_text(self, _outfd, _data): self._addrspace = utils.load_as(self._config) if not self._config.OFFSET is None: self.set_context(offset = self._config.OFFSET) self.context_display() elif self._config.PID is not None: # FIXME: volshell is really not intended to switch into multiple # process contexts at once, so it doesn't make sense to use a csv # pid list. However, the linux and mac volshell call the respective # linux_pslist and mac_pslist which require a csv pidlist. After # the 2.3 release we should close this along with issue 375. pidlist = [int(p) for p in self._config.PID.split(',')] for p in pidlist: self.set_context(pid = p) break elif self._config.IMNAME is not None: self.set_context(name = self._config.IMNAME) else: # Just use the first process, whatever it is for p in self.getpidlist(): self.set_context(offset = p.v()) break # Functions inside the shell def cc(offset = None, pid = None, name = None, physical = False): """Change current shell context. This function changes the current shell context to to the process specified. The process specification can be given as a virtual address (option: offset), PID (option: pid), or process name (option: name). If multiple processes match the given PID or name, you will be shown a list of matching processes, and will have to specify by offset. """ self.set_context(offset = offset, pid = pid, name = name, physical = physical) def db(address, length = 0x80, space = None): """Print bytes as canonical hexdump. This function prints bytes at the given virtual address as a canonical hexdump. The address will be translated in the current process context (see help on cc for information on how to change contexts). The length parameter (default: 0x80) specifies how many bytes to print, the width parameter (default: 16) allows you to change how many bytes per line should be displayed, and the space parameter allows you to optionally specify the address space to read the data from. """ if not space: space = self._proc.get_process_address_space() #if length % 4 != 0: # length = (length+4) - (length%4) data = space.read(address, length) if not data: print "Memory unreadable at {0:08x}".format(address) return for offset, hexchars, chars in utils.Hexdump(data): print "{0:#010x} {1:<48} {2}".format(address + offset, hexchars, ''.join(chars)) def dd(address, length = 0x80, space = None): """Print dwords at address. This function prints the data at the given address, interpreted as a series of dwords (unsigned four-byte integers) in hexadecimal. The address will be translated in the current process context (see help on cc for information on how to change contexts). The optional length parameter (default: 0x80) controls how many bytes to display, and space allows you to optionally specify the address space to read the data from. """ if not space: space = self._proc.get_process_address_space() # round up to multiple of 4 if length % 4 != 0: length = (length + 4) - (length % 4) data = space.read(address, length) if not data: print "Memory unreadable at {0:08x}".format(address) return dwords = [] for i in range(0, length, 4): (dw,) = struct.unpack(")'" elif type(cmd) == str: try: doc = pydoc.getdoc(shell_funcs[cmd]) except KeyError: print "No such command: {0}".format(cmd) return print doc else: doc = pydoc.getdoc(cmd) print doc # Break into shell banner = "Welcome to volshell! Current memory image is:\n{0}\n".format(self._config.LOCATION) banner += "To get help, type 'hh()'" try: import IPython try: # New versions of IPython IPython.embed() except AttributeError: # Old versions of IPythom shell = IPython.Shell.IPShellEmbed([], banner = banner) shell() except (AttributeError, ImportError): import code, inspect frame = inspect.currentframe() # Try to enable tab completion try: import rlcompleter, readline #pylint: disable-msg=W0612 readline.parse_and_bind("tab: complete") except ImportError: pass # evaluate commands in current namespace namespace = frame.f_globals.copy() namespace.update(frame.f_locals) code.interact(banner = banner, local = namespace)