diff --git a/setup.py b/setup.py index 239384c0f..e203221bf 100644 --- a/setup.py +++ b/setup.py @@ -46,8 +46,10 @@ "volatility.plugins.linux", "volatility.plugins.overlays", "volatility.plugins.overlays.linux", + "volatility.plugins.overlays.mac", "volatility.plugins.overlays.windows", "volatility.plugins.windows", + "volatility.plugins.windows.gui", "volatility.plugins.windows.malware", "volatility.plugins.windows.registry", "volatility.ui", diff --git a/vol.py b/vol.py index 1c6b74cb7..20b32a3a3 100755 --- a/vol.py +++ b/vol.py @@ -110,8 +110,11 @@ def UpdateSessionFromArgv(user_session, FLAGS): return result -if __name__ == '__main__': - FLAGS = args.parse_args() + +def main(argv=None): + global FLAGS + + FLAGS = args.parse_args(argv=argv) logging.basicConfig(level=logging.INFO) @@ -138,3 +141,6 @@ def UpdateSessionFromArgv(user_session, FLAGS): (IPython011Support(user_session) or IPython012Support(user_session) or NativePythonSupport(user_session)) + +if __name__ == '__main__': + main() diff --git a/volatility/ipython_support.py b/volatility/ipython_support.py index d2133c60d..3e26a07cf 100644 --- a/volatility/ipython_support.py +++ b/volatility/ipython_support.py @@ -44,6 +44,19 @@ def Shell(user_session): cfg = Config() cfg.InteractiveShellEmbed.autocall = 2 + cfg.PromptManager.in_template = ( + r'{color.LightCyan}' + '{session.profile.__class__.__name__}:{session.base_filename}' + '{color.LightBlue}{color.Green} \T> ') + + cfg.PromptManager.in2_template = ( + r'{color.Green}|{color.LightGreen}\D{color.Green}> ') + + cfg.PromptManager.out_template = r'Out<\#> ' + cfg.InteractiveShell.separate_in = '' + cfg.InteractiveShell.separate_out = '' + cfg.InteractiveShell.separate_out2 = '' + shell = InteractiveShellEmbed(config=cfg, user_ns=user_session._locals, banner2=constants.BANNER) diff --git a/volatility/obj.py b/volatility/obj.py index eac947e5f..1aaba60b9 100644 --- a/volatility/obj.py +++ b/volatility/obj.py @@ -1061,6 +1061,7 @@ def copy(self): result = self.__class__(session=self.session) result.vtypes = copy.deepcopy(self.vtypes) result.overlays = copy.deepcopy(self.overlays) + result.applied_modifications = self.applied_modifications[:] # Object classes are shallow dicts. result.object_classes = self.object_classes.copy() @@ -1485,7 +1486,9 @@ def __new__(cls, profile): except KeyError: # Return a copy of the profile. result = profile.copy() - cls.modify(result) + res = cls.modify(result) + result = res or result + result.applied_modifications.append(cls.__name__) PROFILE_CACHE.Put(key, result) diff --git a/volatility/plugins/core.py b/volatility/plugins/core.py index 1505bef23..159f440c9 100644 --- a/volatility/plugins/core.py +++ b/volatility/plugins/core.py @@ -41,7 +41,6 @@ class Info(plugin.Command): __name = "info" - standard_options = [("output", " Save output to this file."), ("overwrite", " Must be set to overwrite an output " "file. You can also set this in the session as a " @@ -64,6 +63,11 @@ def plugins(self): if name: yield name, cls.name, cls.__doc__.splitlines()[0] + def profiles(self): + for name, cls in obj.Profile.classes.items(): + if name: + yield name, cls.__doc__.splitlines()[0].strip() + def address_spaces(self): for name, cls in addrspaces.BaseAddressSpace.classes.items(): yield dict(name=name, function=cls.name, definition=cls.__module__) @@ -207,14 +211,23 @@ def _clean_up_doc(self, doc, dedent=0): def render_general_info(self, renderer): renderer.write(constants.BANNER) + renderer.section() renderer.table_header([('Plugin', 'function', "20"), ('Provider Class', 'provider', '20'), ('Docs', 'docs', '[wrap:50]'), ]) - for cls, name, doc in self.plugins(): + for cls, name, doc in sorted(self.plugins()): renderer.table_row(name, cls, doc) + renderer.section() + renderer.table_header([('Profile', 'profile', "20"), + ('Docs', 'docs', '[wrap:70]'), + ]) + + for name, doc in sorted(self.profiles()): + renderer.table_row(name, doc) + class LoadAddressSpace(plugin.ProfileCommand): diff --git a/volatility/plugins/overlays/basic.py b/volatility/plugins/overlays/basic.py index 1a296fdfe..a068d4588 100644 --- a/volatility/plugins/overlays/basic.py +++ b/volatility/plugins/overlays/basic.py @@ -139,6 +139,7 @@ def __str__(self): return super(UnicodeString, self).__str__().encode("utf8") def size(self): + return len(self.v()) * 2 # This will only work if the encoding and decoding are equivalent. return len(self.v().encode(self.encoding, 'ignore')) @@ -191,8 +192,8 @@ def __getattr__(self, attr): class Enumeration(obj.NativeType): """Enumeration class for handling multiple possible meanings for a single value""" - def __init__(self, choices = None, target = "unsigned long", value=None, - default = None, **kwargs): + def __init__(self, choices = None, target="unsigned long", value=None, + default=None, target_args={}, **kwargs): super(Enumeration, self).__init__(**kwargs) self.choices = choices or {} self.default = default @@ -203,7 +204,8 @@ def __init__(self, choices = None, target = "unsigned long", value=None, if value is None: self.target = target self.target_obj = self.obj_profile.Object( - target, offset=self.obj_offset, vm=self.obj_vm, context=self.obj_context) + target, offset=self.obj_offset, vm=self.obj_vm, context=self.obj_context, + **target_args) def v(self, vm=None): if self.value is None: @@ -369,6 +371,10 @@ def __str__(self): return "-" + def __repr__(self): + return "%s (%s)" % (super(UnixTimeStamp, self).__repr__(), + str(self)) + def as_datetime(self): try: dt = datetime.datetime.utcfromtimestamp(self.v()) diff --git a/volatility/plugins/overlays/native_types.py b/volatility/plugins/overlays/native_types.py index 9bdd1e833..41f984309 100644 --- a/volatility/plugins/overlays/native_types.py +++ b/volatility/plugins/overlays/native_types.py @@ -16,6 +16,7 @@ 'unsigned short int' : obj.Curry(obj.NativeType, theType='unsigned short int', format_string=' 100: - import pdb; pdb.set_trace() + raise RuntimeError("Vad tree too deep - something went wrong!") if visited == None: visited = set() diff --git a/volatility/plugins/windows/gui/__init__.py b/volatility/plugins/windows/gui/__init__.py index 0c1e8cef3..0eee6aaeb 100644 --- a/volatility/plugins/windows/gui/__init__.py +++ b/volatility/plugins/windows/gui/__init__.py @@ -1,3 +1,4 @@ from volatility.plugins.windows.gui import atoms +from volatility.plugins.windows.gui import clipboard from volatility.plugins.windows.gui import windowstations from volatility.plugins.windows.gui import sessions diff --git a/volatility/plugins/windows/gui/clipboard.py b/volatility/plugins/windows/gui/clipboard.py index 7851749e6..d5b8791f2 100644 --- a/volatility/plugins/windows/gui/clipboard.py +++ b/volatility/plugins/windows/gui/clipboard.py @@ -10,108 +10,129 @@ # This program 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. +# General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # +import logging -import volatility.obj as obj -import volatility.debug as debug -import volatility.utils as utils -import volatility.commands as commands -import volatility.plugins.gui.sessions as sessions -import volatility.plugins.gui.windowstations as windowstations -import volatility.plugins.gui.constants as consts +from volatility import obj +from volatility.plugins.windows import common +from volatility.plugins.windows.gui import sessions +from volatility.plugins.windows.gui import windowstations +from volatility.plugins.windows.gui import win32k_core +from volatility.plugins.windows.gui import constants -class Clipboard(commands.Command, sessions.SessionsMixin): + + +class Clipboard(common.WinProcessFilter): """Extract the contents of the windows clipboard""" + __name = "clipboard" + + @classmethod + def args(cls, parser): + parser.add_argument("-v", "--verbose", default=False, + action="store_true", + help="Dump more information") + + def __init__(self, verbose=False, **kwargs): + super(Clipboard, self).__init__(**kwargs) + self.verbose = verbose + self.profile = win32k_core.Win32GUIProfile(self.profile) + def calculate(self): - kernel_space = utils.load_as(self._config) + session_plugin = self.session.plugins.sessions() # Dictionary of MM_SESSION_SPACEs by ID - sesses = dict((int(session.SessionId), session) - for session in self.session_spaces(kernel_space) - ) + sessions = dict((int(session.SessionId), session) + for session in session_plugin.session_spaces()) # Dictionary of session USER objects by handle session_handles = {} - # If various objects cannot be found or associated, + # If various objects cannot be found or associated, # we'll return none objects e0 = obj.NoneObject("Unknown tagCLIPDATA") e1 = obj.NoneObject("Unknown tagWINDOWSTATION") e2 = obj.NoneObject("Unknown tagCLIP") - # Handle type filter + # Handle type filter filters = [lambda x : str(x.bType) == "TYPE_CLIPDATA"] - # Load tagCLIPDATA handles from all sessions - for sid, session in sesses.items(): + # Load tagCLIPDATA handles from all sessions + import pdb; pdb.set_trace() + for sid, session in sessions.items(): handles = {} shared_info = session.find_shared_info() if not shared_info: - debug.debug("No shared info for session {0}".format(sid)) + logging.debug("No shared info for session {0}".format(sid)) + continue for handle in shared_info.handles(filters): handles[int(handle.phead.h)] = handle + session_handles[sid] = handles - # Each WindowStation - for wndsta in windowstations.WndScan(self._config).calculate(): - session = sesses.get(int(wndsta.dwSessionId), None) - # The session is unknown + # Scan for Each WindowStation + windowstations_plugin = self.session.plugins.wndscan() + + for wndsta, station_as in windowstations_plugin.generate_hits(): + session = sessions.get(int(wndsta.dwSessionId), None) + # The session is unknown if not session: continue + handles = session_handles.get(int(session.SessionId), None) - # No handles in the session + # No handles in the session if not handles: continue - clip_array = wndsta.pClipBase.dereference() - # The tagCLIP array is empty or the pointer is invalid + + clip_array = wndsta.pClipBase.dereference(vm=station_as) + # The tagCLIP array is empty or the pointer is invalid if not clip_array: continue - # Resolve tagCLIPDATA from tagCLIP.hData + + # Resolve tagCLIPDATA from tagCLIP.hData for clip in clip_array: handle = handles.get(int(clip.hData), e0) - # Remove this handle from the list + # Remove this handle from the list if handle: handles.pop(int(clip.hData)) + yield session, wndsta, clip, handle # Any remaining tagCLIPDATA not matched. This allows us # to still find clipboard data if a window station is not # found or if pClipData or cNumClipFormats were corrupt - for sid in sesses.keys(): + for sid in sessions.keys(): handles = session_handles.get(sid, None) - # No handles in the session + # No handles in the session if not handles: continue - for handle in handles.values(): - yield sesses[sid], e1, e2, handle - - def render_text(self, outfd, data): - - self.table_header(outfd, - [("Session", "10"), - ("WindowStation", "12"), - ("Format", "18"), - ("Handle", "[addr]"), - ("Object", "[addrpad]"), - ("Data", "50"), - ]) - - for session, wndsta, clip, handle in data: + for handle in handles.values(): + yield sessions[sid], e1, e2, handle + + def render(self, renderer): + renderer.table_header([("Session", "session", "10"), + ("WindowStation", "window_station", "12"), + ("Format", "format", "18"), + ("Handle", "handle", "[addr]"), + ("Object", "object", "[addrpad]"), + ("Data", "data", "50"), + ]) + + for session, wndsta, clip, handle in self.calculate(): # If no tagCLIP is provided, we do not know the format if not clip: fmt = obj.NoneObject("Format unknown") else: - # Try to get the format name, but failing that, print - # the format number in hex instead. - if clip.fmt.v() in consts.CLIPBOARD_FORMAT_ENUM: + # Try to get the format name, but failing that, print + # the format number in hex instead. + if clip.fmt.v() in constants.CLIPBOARD_FORMAT_ENUM: fmt = str(clip.fmt) else: fmt = hex(clip.fmt.v()) @@ -119,24 +140,20 @@ def render_text(self, outfd, data): # Try to get the handle from tagCLIP first, but # fall back to using _HANDLEENTRY.phead. Note: this can # be a value like DUMMY_TEXT_HANDLE (1) etc. - if clip: - handle_value = clip.hData - else: - handle_value = handle.phead.h + handle_value = clip.hData or handle.phead.h clip_data = "" if handle and "TEXT" in fmt: clip_data = handle.reference_object().as_string(fmt) - self.table_row(outfd, - session.SessionId, - wndsta.Name, - fmt, - handle_value, - handle.phead.v(), - clip_data) + renderer.table_row(session.SessionId, + wndsta.Name, + fmt, + handle_value, + handle.phead.v(), + clip_data) # Print an additional hexdump if --verbose is specified - if self._config.VERBOSE and handle: + if self.verbose and handle: hex_dump = handle.reference_object().as_hex() outfd.write("{0}".format(hex_dump)) diff --git a/volatility/plugins/windows/gui/sessions.py b/volatility/plugins/windows/gui/sessions.py index 6cd2a8106..e64f90098 100644 --- a/volatility/plugins/windows/gui/sessions.py +++ b/volatility/plugins/windows/gui/sessions.py @@ -32,8 +32,10 @@ def __init__(self, **kwargs): self.profile = win32k_core.Win32GUIProfile(self.profile) def session_spaces(self): - """ Generators unique _MM_SESSION_SPACE objects - referenced by active processes. + """Generates unique _MM_SESSION_SPACE objects. + + Generates unique _MM_SESSION_SPACE objects referenced by active + processes. Yields: _MM_SESSION_SPACE instantiated from the session space's address space. diff --git a/volatility/plugins/windows/gui/vtypes/win7.py b/volatility/plugins/windows/gui/vtypes/win7.py index d1e6686c8..b70fe6ff8 100644 --- a/volatility/plugins/windows/gui/vtypes/win7.py +++ b/volatility/plugins/windows/gui/vtypes/win7.py @@ -40,18 +40,19 @@ def find_shared_info(self): The HeEntrySize member didn't exist before Windows 7 thus the need for separate methods.""" + handle_table_size = self.obj_profile.get_obj_size("_HANDLEENTRY") - handle_table_size = self.profile.get_obj_size("_HANDLEENTRY") - - handle_entry_offset = self.profile.get_obj_offset( + handle_entry_offset = self.obj_profile.get_obj_offset( "tagSHAREDINFO", "HeEntrySize") + import pdb; pdb.set_trace() + for chunk in self._section_chunks(".data"): if chunk != handle_table_size: continue - shared_info = self.profile.tagSHAREDINFO( + shared_info = self.obj_profile.tagSHAREDINFO( offset = chunk.obj_offset - handle_entry_offset, vm = self.obj_vm) diff --git a/volatility/plugins/windows/gui/win32k_core.py b/volatility/plugins/windows/gui/win32k_core.py index e9c462103..f9a3b3050 100644 --- a/volatility/plugins/windows/gui/win32k_core.py +++ b/volatility/plugins/windows/gui/win32k_core.py @@ -19,6 +19,7 @@ from volatility import obj from volatility.plugins.windows.gui import constants +from volatility.plugins.overlays.windows import pe_vtypes from volatility.plugins.overlays.windows import windows @@ -68,47 +69,26 @@ def _section_chunks(self, sec_name): @returns all chunks on a 4-byte boundary. """ - - dos_header = obj.Object("_IMAGE_DOS_HEADER", - offset = self.Win32KBase, vm = self.obj_vm) - - if dos_header: - try: - nt_header = dos_header.get_nt_header() - - sections = [ - sec for sec in nt_header.get_sections(False) - if str(sec.Name) == sec_name - ] - - # There should be exactly one section - if sections: - desired_section = sections[0] - return obj.Object("Array", targetType = "unsigned long", - offset = desired_section.VirtualAddress + dos_header.obj_offset, - count = desired_section.Misc.VirtualSize / 4, - vm = self.obj_vm) - except ValueError: - ## This catches PE header parsing exceptions - pass - ## In the rare case when win32k.sys PE header is paged or corrupted ## thus preventing us from parsing the sections, use the fallback ## mechanism of just reading 5 MB (max size of win32k.sys) from the ## base of the kernel module. - data = self.obj_vm.zread(self.Win32KBase, 0x500000) + section_base = self.Win32KBase + section_size = 0x500000 - ## Fill a Buffer AS with the zread data and set its base to win32k.sys - ## so we can still instantiate an Array and have each chunk at the - ## correct offset in virtual memory. - buffer_as = addrspace.BufferAddressSpace(conf.ConfObject(), - data = data, - base_offset = self.Win32KBase) + dos_header = self.obj_profile._IMAGE_DOS_HEADER( + offset=self.Win32KBase, vm=self.obj_vm) - return obj.Object("Array", targetType = "unsigned long", - offset = self.Win32KBase, - count = len(data) / 4, - vm = buffer_as) + for section in dos_header.NTHeader.Sections: + if section.Name == sec_name: + section_base = section.VirtualAddress + section_size = section.Misc.VirtualSize / 4 + break + + + return self.obj_profile.Array(targetType="unsigned long", + offset=section_base, + count=section_size, vm=self.obj_vm) def find_gahti(self): """Find this session's gahti. @@ -147,7 +127,7 @@ def find_shared_info(self): iterate over each DWORD-aligned possibility and treat it as a tagSHAREDINFO until the sanity checks are met. """ - + import pdb; pdb.set_trace() for chunk in self._section_chunks(".data"): # If the base of the value is paged if not chunk.is_valid(): @@ -667,6 +647,8 @@ class Win32GUIProfile(obj.ProfileModification): @classmethod def modify(cls, profile): + profile = pe_vtypes.PEFileImplementation(profile) + # Some constants - These will probably change in win8 which does not # allow non ascii tags. profile.add_constants(PoolTag_WindowStation="Win\xe4", diff --git a/volatility/plugins/windows/malware/svcscan.py b/volatility/plugins/windows/malware/svcscan.py index 9f7f98af0..e1f4235f2 100644 --- a/volatility/plugins/windows/malware/svcscan.py +++ b/volatility/plugins/windows/malware/svcscan.py @@ -176,13 +176,17 @@ def is_valid(self): _SERVICE_RECORD_VISTA_X86 = { '_SERVICE_RECORD': [None, { 'PrevEntry': [ 0x0, ['pointer', ['_SERVICE_RECORD']]], - 'ServiceName': [ 0x4, ['pointer', ['String', dict(encoding = 'utf16', length = 512)]]], - 'DisplayName': [ 0x8, ['pointer', ['String', dict(encoding = 'utf16', length = 512)]]], + 'ServiceName': [ 0x4, ['pointer', ['UnicodeString', dict( + encoding = 'utf16', length = 512)]]], + 'DisplayName': [ 0x8, ['pointer', ['UnicodeString', dict( + encoding = 'utf16', length = 512)]]], 'Order': [ 0xC, ['unsigned int']], 'ServiceProcess': [ 0x1C, ['pointer', ['_SERVICE_PROCESS']]], - 'DriverName': [ 0x1C, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]], + 'DriverName': [ 0x1C, ['pointer', ['UnicodeString', dict( + encoding = 'utf16', length = 256)]]], 'Type' : [ 0x20, ['Flags', {'bitmap': SERVICE_TYPE_FLAGS}]], - 'State': [ 0x24, ['Enumeration', dict(target = 'long', choices = SERVICE_STATE_ENUM)]], + 'State': [ 0x24, ['Enumeration', dict( + target = 'long', choices = SERVICE_STATE_ENUM)]], }], } @@ -190,11 +194,14 @@ def is_valid(self): _SERVICE_RECORD_VISTA_X64 = { '_SERVICE_RECORD': [None, { 'PrevEntry': [ 0x0, ['pointer', ['_SERVICE_RECORD']]], - 'ServiceName': [ 0x8, ['pointer', ['String', dict(encoding = 'utf16', length = 512)]]], - 'DisplayName': [ 0x10, ['pointer', ['String', dict(encoding = 'utf16', length = 512)]]], + 'ServiceName': [ 0x8, ['pointer', ['UnicodeString', dict( + encoding = 'utf16', length = 512)]]], + 'DisplayName': [ 0x10, ['pointer', ['UnicodeString', dict( + encoding = 'utf16', length = 512)]]], 'Order': [ 0x18, ['unsigned int']], 'ServiceProcess': [ 0x28, ['pointer', ['_SERVICE_PROCESS']]], - 'DriverName': [ 0x28, ['pointer', ['String', dict(encoding = 'utf16', length = 256)]]], + 'DriverName': [ 0x28, ['pointer', ['UnicodeString', dict( + encoding = 'utf16', length = 256)]]], 'Type' : [ 0x30, ['Flags', {'bitmap': SERVICE_TYPE_FLAGS}]], 'State': [ 0x34, ['Enumeration', dict(target = 'long', choices = SERVICE_STATE_ENUM)]], }], diff --git a/volatility/plugins/windows/registry/__init__.py b/volatility/plugins/windows/registry/__init__.py index 977332a0f..6dacbaffd 100644 --- a/volatility/plugins/windows/registry/__init__.py +++ b/volatility/plugins/windows/registry/__init__.py @@ -1,5 +1,7 @@ # Module for memory analysis of the windows registry. +from volatility.plugins.windows.registry import evtlogs from volatility.plugins.windows.registry import getsids +from volatility.plugins.windows.registry import getservicesids from volatility.plugins.windows.registry import hivescan try: diff --git a/volatility/plugins/windows/registry/evtlogs.py b/volatility/plugins/windows/registry/evtlogs.py new file mode 100644 index 000000000..2e523badd --- /dev/null +++ b/volatility/plugins/windows/registry/evtlogs.py @@ -0,0 +1,292 @@ +# Volatility +# Copyright (C) 2008-2011 Volatile Systems +# Copyright (C) 2011 Jamie Levy (Gleeda) +# Additional Authors: +# Michael Cohen +# +# This program 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. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +@author: Jamie Levy (gleeda) +@license: GNU General Public License 2.0 or later +@contact: jamie.levy@gmail.com +@organization: Volatile Systems +""" +from volatility import obj +from volatility import scan +from volatility import utils +from volatility.plugins import core +from volatility.plugins.windows.registry import registry +from volatility.plugins.windows.registry import getsids + +import os, datetime, ntpath + + +# for more information on Event Log structures see WFA 2E pg 260-263 by Harlan +# Carvey +evt_log_types = { + 'EVTLogHeader' : [ 0x30, { + 'HeaderSize' : [ 0x0, ['unsigned int']], + 'Magic' : [ 0x4, ['String', dict(length=4)]], # LfLe + + # Offset of oldest record. + 'OffsetOldest' : [ 0x10, ['int']], + + # Offset of next record to be written. + 'OffsetNextToWrite' : [ 0x14, ['int']], + 'NextID' : [ 0x18, ['int']], # Next event record ID. + 'OldestID' : [ 0x1c, ['int']], # Oldest event record ID. + + # Maximum size of event record (from registry). + 'MaxSize' : [ 0x20, ['int']], + + # Retention time of records (from registry). + 'RetentionTime' : [ 0x28, ['int']], + + # Size of the record (repeat of DWORD at offset 0). + 'RecordSize' : [ 0x2c, ['int']], + } ], + + 'EVTRecordStruct' : [ 0x38, { + 'RecordLength' : [ 0x0, ['int']], + 'Magic' : [ 0x4, ['String', dict(length=4)]], # LfLe + 'RecordNumber' : [ 0x8, ['int']], + + 'TimeGenerated' : [ 0xc, ['UnixTimeStamp']], + 'TimeWritten' : [ 0x10, ['UnixTimeStamp']], + + # Specific to event source and uniquely identifies the event. + 'EventID' : [ 0x14, ['unsigned short']], + 'EventType' : [ 0x18, ['Enumeration', dict( + target = 'unsigned short', + choices = {0x01: "Error", + 0x02: "Warning", + 0x04: "Info", + 0x08: "Success", + 0x10: "Failure"})]], + + # Number of description strings in event message. + 'NumStrings' : [ 0x1a, ['unsigned short']], + 'EventCategory' : [ 0x1c, ['unsigned short']], + 'ReservedFlags' : [ 0x1e, ['unsigned short']], + 'ClosingRecordNum' : [ 0x20, ['int']], + + # Offset w/in record of description strings. + 'StringOffset' : [ 0x24, ['int']], + + # Length of SID: if 0 no SID is present. + 'SidLength' : [ 0x28, ['int']], + + # Offset w/in record to start of SID (if present). + 'SidOffset' : [ 0x2c, ['int']], + + # Length of binary data of record. + 'DataLength' : [ 0x30, ['int']], + + # Offset of data w/in record. + 'DataOffset' : [ 0x34, ['int']], + + 'Source': [0x38, ['UnicodeString', dict( + length=lambda x: x.RecordLength)]], + + # The computer name is right after the Source. + 'Computer': [lambda x: x.Source.obj_offset + x.Source.size(), + ['UnicodeString', dict( + length=lambda x: x.RecordLength)]], + + 'Sid': [lambda x: x.obj_offset + x.SidOffset, ['_SID']], + + 'Data':[lambda x: x.obj_offset + x.StringOffset, [ + "ListArray", dict( + target="UnicodeString", + target_args=dict(encoding="utf16"), + maximum_size=lambda x: x.RecordLength, + count=lambda x: x.NumStrings)]], + } ], + + "_SID": [None, { + "IdentifierAuthority": [None, ["Enumeration", dict( + choices={ + "\x00\x00\x00\x00\x00\x00": "Null Authority", + "\x00\x00\x00\x00\x00\x01": "World Authority", + "\x00\x00\x00\x00\x00\x02": "Local Authority", + "\x00\x00\x00\x00\x00\x03": "Creator Authority", + "\x00\x00\x00\x00\x00\x04": "NonUnique Authority", + "\x00\x00\x00\x00\x00\x05": "NT Authority", + }, + target="String", + target_args=dict(length=6, term=None) + )]], + "NumericIdentifier": [0x4, ["unsigned be int"]], + "SubAuthority": [None, ["Array", dict( + target="unsigned long", + count=lambda x: x.SubAuthorityCount)]], + }], + } + + + +class _SID(obj.CType): + """A Pretty printing implementation of sids. + + Reference: http://www.sekchek.com/downloads/white-papers/windows-about-sids.pdf + """ + def __str__(self): + """Format the Sid using SDDL Notation.""" + components = [self.Revision, self.NumericIdentifier] + components.extend(self.SubAuthority) + + result = "S-" + "-".join([str(x) for x in components]) + + # Try to resolve a friendly name from the cache in the context. + friendly_name = self.obj_context.get("sid_cache", {}).get(result) + if friendly_name: + result = "%s (%s)" % (result, friendly_name) + + return result + + +class EVTObjectTypes(obj.ProfileModification): + """An implementation for parsing event logs.""" + + @classmethod + def modify(cls, profile): + profile.add_overlay(evt_log_types) + profile.add_classes(dict(_SID=_SID)) + + +class EVTScanner(scan.BaseScanner): + checks = [('MultiStringFinderCheck', dict(needles=["LfLe"]))] + + def scan(self, offset, maxlen=None, context=None): + for hit in super(EVTScanner, self).scan(offset, maxlen=maxlen): + event_offset = hit - self.profile.get_obj_offset( + "EVTRecordStruct", "Magic") + + event = self.profile.EVTRecordStruct( + offset=event_offset, vm=self.address_space, context=context) + + # Eliminate crazy events (between 2001 and 2017): + if (1500000000 > event.TimeGenerated > 1000000000 and + 1500000000 > event.TimeWritten > 1000000000): + yield event + + +class EvtLogs(registry.RegistryPlugin): + """Extract Windows Event Logs (XP/2003 only)""" + + __name = "evtlogs" + + @classmethod + def args(cls, parser): + super(EvtLogs, cls).args(parser) + parser.add_argument("-v", "--verbose", default=False, action="store_true", + help='Resolve sids to users, services etc.') + + @classmethod + def is_active(cls, config): + """Only active for windows XP.""" + return (super(EvtLogs, cls).is_active(config) and + config.profile.metadata("major") == 5) + + def __init__(self, save_evt=False, verbose=False, **kwargs): + self.save_evt = save_evt + self.verbose = verbose + super(EvtLogs, self).__init__(**kwargs) + self.profile = EVTObjectTypes(self.profile) + self.context = dict(sid_cache={}) + + def FindEVTFiles(self): + """Search for event log files in memory. + + We search for processes called 'services.exe' with a vad to and open + file ending with '.evt'. + """ + ps_plugin = self.get_plugin("pslist", proc_regex="services.exe") + + for task in ps_plugin.filter_processes(): + for vad in task.RealVadRoot.traverse(): + try: + filename = vad.ControlArea.FilePointer.FileName + if utils.SmartUnicode(filename).lower().endswith(".evt"): + yield task, vad + except AttributeError: + pass + + def ScanEvents(self, vad, address_space): + scanner = EVTScanner(profile=self.profile, address_space=address_space, + session=self.session) + for event in scanner.scan(offset=vad.Start, maxlen=vad.Length, + context=self.context): + yield event + + def PrecacheSids(self): + """Search for known sids that we can cache.""" + sid_cache = self.context["sid_cache"] + sid_cache.update(getsids.well_known_sids) + + # Search for all known user sids. + for hive_offset in self.hive_offsets: + hive_address_space = registry.HiveAddressSpace( + base=self.kernel_address_space, + hive_addr=hive_offset, profile=self.profile) + + reg = registry.Registry( + profile=self.profile, address_space=hive_address_space) + + # We get the user names according to the name of the diretory where + # their profile is. This is not very accurate - should we check the + # SAM instead? + profiles = reg.open_key('Microsoft\\Windows NT\\CurrentVersion\\ProfileList') + for profile in profiles.subkeys(): + path = profile.open_value("ProfileImagePath").DecodedData + if path: + sid_cache[utils.SmartUnicode(profile.Name)] = utils.SmartUnicode( + ntpath.basename(path)) + + # Search for all service sids. + getservicesids = self.get_plugin("getservicesids") + for sid, service_name in getservicesids.get_service_sids(): + sid_cache[sid] = "(Service: %s)" % service_name + + import pdb; pdb.set_trace() + + def render(self, renderer): + if self.verbose: + self.PrecacheSids() + + renderer.table_header([("TimeWritten", "timestamp", ""), + ("Filename", "filename", ""), + ("Computer", "computer", ""), + ("Sid", "sid", ""), + ("Source", "source", ""), + ("Event Id", "event_id", ""), + ("Event Type", "event_type", ""), + ("Message", "message", "")]) + + for task, vad in self.FindEVTFiles(): + filename = ntpath.basename( + utils.SmartUnicode(vad.ControlArea.FilePointer.FileName)) + + for event in self.ScanEvents(vad, task.get_process_address_space()): + renderer.table_row(event.TimeWritten, + filename, + event.Computer, + event.Sid, + event.Source, + event.EventID, + event.EventType, + ";".join(repr(utils.SmartStr(x)) for x in event.Data)) diff --git a/volatility/plugins/windows/registry/getservicesids.py b/volatility/plugins/windows/registry/getservicesids.py new file mode 100644 index 000000000..4a5e283db --- /dev/null +++ b/volatility/plugins/windows/registry/getservicesids.py @@ -0,0 +1,76 @@ +# Volatility +# Copyright (C) 2011 Volatile Systems +# Copyright (C) 2011 Jamie Levy (Gleeda) +# +# This program 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. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +@author: Jamie Levy (Gleeda) +@license: GNU General Public License 2.0 or later +@contact: jamie.levy@gmail.com +@organization: Volatile Systems +""" + +from volatility import utils +from volatility.plugins.windows.registry import registry + +import hashlib +import struct + + +class GetServiceSids(registry.RegistryPlugin): + """Get the names of services in the Registry and return Calculated SID""" + + __name = "getservicesids" + + def createservicesid(self, service_name): + """Calculate the Service SID.""" + + # We depend on service name to be a unicode string here. + service_name = utils.SmartUnicode(service_name) + + sha = hashlib.sha1(service_name.encode("utf-16-le").upper()).digest() + return 'S-1-5-80-' + '-'.join( + [str(n) for n in struct.unpack("= 50: + if self.pager and len(ui_renderer.data) >= self.paging_limit: pager = renderer.Pager(self) - pager.write(ui_renderer.data) + for data in ui_renderer.data: + pager.write(data) # Now wait for the user to exit the pager. pager.flush() @@ -225,6 +233,10 @@ def _set_profile(self, profile): else: raise RuntimeError("A profile must be a string.") + def _set_filename(self, filename): + self.__dict__['filename'] = filename + self.__dict__['base_filename'] = os.path.basename(filename) + def __unicode__(self): return u"Session" @@ -260,6 +272,7 @@ def __init__(self, env=None, **kwargs): self.overwrite = False super(InteractiveSession, self).__init__() + self.paging_limit = 50 self._ready = True diff --git a/volatility/ui/renderer.py b/volatility/ui/renderer.py index 08b8bca2b..3c19cba8a 100644 --- a/volatility/ui/renderer.py +++ b/volatility/ui/renderer.py @@ -182,7 +182,7 @@ def format_type_s(self, value, fields): # from __unicode__ (e.g. NoneObject). result = value.__unicode__() except AttributeError: - result = unicode(value) + result = utils.SmartUnicode(value) # None objects get a -. if result is None or isinstance(result, obj.NoneObject): @@ -514,13 +514,15 @@ class TextRenderer(RendererBaseClass): last_message_len = 0 isatty = False - def __init__(self, tablesep=" ", elide=False, max_data=1024*1024, **kwargs): + def __init__(self, tablesep=" ", elide=False, max_data=1024*1024, + paging_limit=None, **kwargs): super(TextRenderer, self).__init__(**kwargs) self.tablesep = tablesep self.elide = elide + self.paging_limit = paging_limit # We keep the data that we produce in memory for while. - self.data = '' + self.data = [] self.max_data = max_data # Make sure that our output is unicode safe. @@ -545,10 +547,23 @@ def end(self): self.session.progress = None def write(self, data): - self.fd.write(data) - self.fd.flush() + self.data.append(data) + + if self.paging_limit is None: + self.fd.write(data) + return + + elif len(self.data) < self.paging_limit: + self.fd.write(data) + self.fd.flush() + elif len(self.data) == self.paging_limit: + self.fd.write( + self.color("Please wait while the rest is paged...", + foreground="YELLOW") + "\r\n") + self.fd.flush() def flush(self): + self.data = [] self.fd.flush() def table_header(self, columns = None, suppress_headers=False,