499 lines
16 KiB
Python
Executable File
499 lines
16 KiB
Python
Executable File
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright 2013 The Plaso Project Authors.
|
|
# Please see the AUTHORS file for details on individual authors.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""This file contains a console, the CLI friendly front-end to plaso."""
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import random
|
|
import sys
|
|
import tempfile
|
|
|
|
from dfvfs.lib import definitions
|
|
from dfvfs.path import factory as path_spec_factory
|
|
from dfvfs.resolver import resolver as path_spec_resolver
|
|
|
|
try:
|
|
# Support version 1.X of IPython.
|
|
# pylint: disable=no-name-in-module
|
|
from IPython.terminal.embed import InteractiveShellEmbed
|
|
except ImportError:
|
|
# Support version older than 1.X of IPython.
|
|
# pylint: disable=no-name-in-module
|
|
from IPython.frontend.terminal.embed import InteractiveShellEmbed
|
|
|
|
from IPython.config.loader import Config
|
|
|
|
# pylint: disable=unused-import
|
|
from plaso import analysis
|
|
from plaso import filters
|
|
from plaso import formatters
|
|
from plaso import output
|
|
from plaso import parsers
|
|
from plaso import preprocessors
|
|
|
|
from plaso.classifier import scanner
|
|
|
|
from plaso.engine import collector
|
|
from plaso.engine import engine
|
|
from plaso.engine import queue
|
|
from plaso.engine import single_process
|
|
from plaso.engine import utils as engine_utils
|
|
|
|
from plaso.frontend import frontend
|
|
from plaso.frontend import utils as frontend_utils
|
|
|
|
from plaso.lib import binary
|
|
from plaso.lib import bufferlib
|
|
from plaso.lib import errors
|
|
from plaso.lib import event
|
|
from plaso.lib import eventdata
|
|
from plaso.lib import filter_interface
|
|
from plaso.lib import lexer
|
|
from plaso.lib import objectfilter
|
|
from plaso.lib import output as output_lib
|
|
from plaso.lib import pfilter
|
|
from plaso.lib import proxy
|
|
from plaso.lib import putils
|
|
from plaso.lib import registry as class_registry
|
|
from plaso.lib import storage
|
|
from plaso.lib import timelib
|
|
from plaso.lib import utils
|
|
|
|
from plaso.multi_processing import foreman
|
|
from plaso.multi_processing import rpc_proxy
|
|
from plaso.multi_processing import process_info
|
|
|
|
from plaso.output import helper as output_helper
|
|
|
|
from plaso.parsers import manager as parsers_manager
|
|
from plaso.parsers import plugins
|
|
from plaso.parsers import text_parser
|
|
from plaso.proto import plaso_storage_pb2
|
|
|
|
from plaso.serializer import interface as serializer_interface
|
|
from plaso.serializer import json_serializer
|
|
from plaso.serializer import protobuf_serializer
|
|
|
|
from plaso.unix import bsmtoken
|
|
|
|
from plaso.winnt import environ_expand
|
|
from plaso.winnt import known_folder_ids
|
|
|
|
from plaso.winreg import cache as win_registry_cache
|
|
from plaso.winreg import interface as win_registry_interface
|
|
from plaso.winreg import path_expander
|
|
from plaso.winreg import utils as win_registry_utils
|
|
from plaso.winreg import winpyregf
|
|
from plaso.winreg import winregistry
|
|
|
|
|
|
class PshellFrontend(frontend.ExtractionFrontend):
|
|
"""Class that implements the pshell front-end."""
|
|
|
|
_BYTES_IN_A_MIB = 1024 * 1024
|
|
|
|
def __init__(self):
|
|
"""Initializes the front-end object."""
|
|
input_reader = frontend.StdinFrontendInputReader()
|
|
output_writer = frontend.StdoutFrontendOutputWriter()
|
|
|
|
super(PshellFrontend, self).__init__(input_reader, output_writer)
|
|
|
|
|
|
def FindAllOutputs():
|
|
"""FindAllOutputs() - All available outputs."""
|
|
return putils.FindAllOutputs()
|
|
|
|
|
|
def GetEventData(event_proto, before=0):
|
|
"""Prints a hexdump of the event data."""
|
|
return frontend_utils.OutputWriter.GetEventDataHexDump(event_proto, before)
|
|
|
|
|
|
def GetFileEntryFromEventObject(event_object):
|
|
"""Return a file entry object from a pathspec object.
|
|
|
|
Args:
|
|
event_object: An event object (an instance of EventObject).
|
|
|
|
Returns:
|
|
A file entry object (instance of vfs.file_entry.FileEntry) or
|
|
None if the event object doesn't have a defined path spec.
|
|
"""
|
|
path_spec = getattr(event_object, 'pathspec', None)
|
|
|
|
if not path_spec:
|
|
return
|
|
|
|
return path_spec_resolver.Resolver.OpenFileEntry(path_spec)
|
|
|
|
|
|
def GetParserNames(parser_filter_string=None):
|
|
"""Retrieves the parser names.
|
|
|
|
Args:
|
|
parser_filter_string: Optional parser filter string. The default is None.
|
|
|
|
Returns:
|
|
A list of parser names.
|
|
"""
|
|
return parsers_manager.ParsersManager.GetParserNames(
|
|
parser_filter_string=parser_filter_string)
|
|
|
|
|
|
def GetParserObjects(parser_filter_string=None):
|
|
"""Retrieves the parser objects.
|
|
|
|
Args:
|
|
parser_filter_string: Optional parser filter string. The default is None.
|
|
|
|
Returns:
|
|
A list of parser objects (instances of BaseParser).
|
|
"""
|
|
return parsers_manager.ParsersManager.GetParserObjects(
|
|
parser_filter_string=parser_filter_string)
|
|
|
|
|
|
def OpenOSFile(path):
|
|
"""Opens a file entry from the OS."""
|
|
if not os.path.isfile(path):
|
|
logging.error(u'File: {0:s} does not exist.'.format(path))
|
|
return
|
|
|
|
path_spec = path_spec_factory.Factory.NewPathSpec(
|
|
definitions.TYPE_INDICATOR_OS, location=path)
|
|
return path_spec_resolver.Resolver.OpenFileEntry(path_spec)
|
|
|
|
|
|
def OpenStorageFile(storage_path):
|
|
"""Opens a storage file and returns the storage file object."""
|
|
if not os.path.isfile(storage_path):
|
|
return
|
|
|
|
try:
|
|
store = storage.StorageFile(storage_path, read_only=True)
|
|
except IOError:
|
|
print 'Unable to load storage file, not a storage file?'
|
|
|
|
return store
|
|
|
|
|
|
def OpenTskFile(image_path, image_offset, path=None, inode=None):
|
|
"""Opens a file entry of a file inside an image file."""
|
|
path_spec = path_spec_factory.Factory.NewPathSpec(
|
|
definitions.TYPE_INDICATOR_OS, location=image_path)
|
|
|
|
if image_offset > 0:
|
|
volume_path_spec = path_spec_factory.Factory.NewPathSpec(
|
|
definitions.TYPE_INDICATOR_TSK_PARTITION, start_offset=image_offset,
|
|
parent=path_spec)
|
|
else:
|
|
volume_path_spec = path_spec
|
|
|
|
if inode is not None:
|
|
if path is None:
|
|
path = u''
|
|
path_spec = path_spec_factory.Factory.NewPathSpec(
|
|
definitions.TYPE_INDICATOR_TSK, inode=inode, location=path,
|
|
parent=volume_path_spec)
|
|
else:
|
|
path_spec = path_spec_factory.Factory.NewPathSpec(
|
|
definitions.TYPE_INDICATOR_TSK, location=path, parent=volume_path_spec)
|
|
|
|
return path_spec_resolver.Resolver.OpenFileEntry(path_spec)
|
|
|
|
|
|
def OpenVssFile(path, image_path, store_number, image_offset):
|
|
"""Opens a file entry inside a VSS inside an image file."""
|
|
path_spec = path_spec_factory.Factory.NewPathSpec(
|
|
definitions.TYPE_INDICATOR_OS, location=image_path)
|
|
|
|
if image_offset > 0:
|
|
volume_path_spec = path_spec_factory.Factory.NewPathSpec(
|
|
definitions.TYPE_INDICATOR_TSK_PARTITION, start_offset=image_offset,
|
|
parent=path_spec)
|
|
else:
|
|
volume_path_spec = path_spec
|
|
|
|
store_number -= 1
|
|
|
|
path_spec = path_spec_factory.Factory.NewPathSpec(
|
|
definitions.TYPE_INDICATOR_VSHADOW, store_index=store_number,
|
|
parent=volume_path_spec)
|
|
path_spec = path_spec_factory.Factory.NewPathSpec(
|
|
definitions.TYPE_INDICATOR_TSK, location=path, parent=path_spec)
|
|
|
|
return path_spec_resolver.Resolver.OpenFileEntry(path_spec)
|
|
|
|
|
|
def ParseFile(file_entry):
|
|
"""Parse a file given a file entry or path and return a list of results.
|
|
|
|
Args:
|
|
file_entry: Either a file entry object (instance of dfvfs.FileEntry)
|
|
or a string containing a path (absolute or relative) to a
|
|
local file.
|
|
|
|
Returns:
|
|
A list of event object (instance of EventObject) that were extracted from
|
|
the file (or an empty list if no events were extracted).
|
|
"""
|
|
if not file_entry:
|
|
return
|
|
|
|
if isinstance(file_entry, basestring):
|
|
file_entry = OpenOSFile(file_entry)
|
|
|
|
# Set up the engine.
|
|
# TODO: refactor and add queue limit.
|
|
collection_queue = single_process.SingleProcessQueue()
|
|
storage_queue = single_process.SingleProcessQueue()
|
|
parse_error_queue = single_process.SingleProcessQueue()
|
|
engine_object = engine.BaseEngine(
|
|
collection_queue, storage_queue, parse_error_queue)
|
|
|
|
# Create a worker.
|
|
worker_object = engine_object.CreateExtractionWorker(0)
|
|
# TODO: add support for parser_filter_string.
|
|
worker_object.InitalizeParserObjects()
|
|
worker_object.ParseFileEntry(file_entry)
|
|
|
|
collection_queue.SignalEndOfInput()
|
|
engine_object.SignalEndOfInputStorageQueue()
|
|
|
|
results = []
|
|
while True:
|
|
try:
|
|
item = storage_queue.PopItem()
|
|
except errors.QueueEmpty:
|
|
break
|
|
|
|
if isinstance(item, queue.QueueEndOfInput):
|
|
break
|
|
|
|
results.append(item)
|
|
return results
|
|
|
|
|
|
def Pfile2File(file_object, path):
|
|
"""Saves a file-like object to the path."""
|
|
return frontend_utils.OutputWriter.WriteFile(file_object, path)
|
|
|
|
|
|
def PrintTimestamp(timestamp):
|
|
"""Prints a human readable timestamp from a timestamp value."""
|
|
return frontend_utils.OutputWriter.GetDateTimeString(timestamp)
|
|
|
|
|
|
def PrintTimestampFromEvent(event_object):
|
|
"""Prints a human readable timestamp from values stored in an event object."""
|
|
return PrintTimestamp(getattr(event_object, 'timestamp', 0))
|
|
|
|
|
|
def Main():
|
|
"""Start the tool."""
|
|
temp_location = tempfile.gettempdir()
|
|
|
|
options = putils.Options()
|
|
|
|
# Set the default options.
|
|
options.buffer_size = 0
|
|
options.debug = False
|
|
options.filename = '.'
|
|
options.file_filter = ''
|
|
options.filter = ''
|
|
options.image = False
|
|
options.image_offset = None
|
|
options.image_offset_bytes = None
|
|
options.old_preprocess = False
|
|
options.open_files = False
|
|
options.output = os.path.join(temp_location, 'wheredidmytimelinego.dump')
|
|
options.output_module = ''
|
|
options.parsers = ''
|
|
options.parse_vss = False
|
|
options.preprocess = False
|
|
options.recursive = False
|
|
options.single_process = False
|
|
options.timezone = 'UTC'
|
|
options.workers = 5
|
|
|
|
format_str = '[%(levelname)s] (%(processName)-10s) %(message)s'
|
|
logging.basicConfig(format=format_str)
|
|
|
|
front_end = PshellFrontend()
|
|
|
|
try:
|
|
front_end.ParseOptions(options, source_option='filename')
|
|
front_end.SetStorageFile(options.output)
|
|
except errors.BadConfigOption as exception:
|
|
logging.error(u'{0:s}'.format(exception))
|
|
|
|
# TODO: move to frontend object.
|
|
if options.image and options.image_offset_bytes is None:
|
|
if options.image_offset is not None:
|
|
bytes_per_sector = getattr(options, 'bytes_per_sector', 512)
|
|
options.image_offset_bytes = options.image_offset * bytes_per_sector
|
|
else:
|
|
options.image_offset_bytes = 0
|
|
|
|
namespace = {}
|
|
|
|
pre_obj = event.PreprocessObject()
|
|
|
|
namespace.update(globals())
|
|
namespace.update({
|
|
'frontend': front_end,
|
|
'pre_obj': pre_obj,
|
|
'options': options,
|
|
'find_all_output': FindAllOutputs,
|
|
'parse_file': ParseFile,
|
|
'timestamp_from_event': PrintTimestampFromEvent,
|
|
'message': formatters.manager.EventFormatterManager.GetMessageStrings})
|
|
|
|
# Include few random phrases that get thrown in once the user exists the
|
|
# shell.
|
|
_my_random_phrases = [
|
|
u'I haven\'t seen timelines like this since yesterday.',
|
|
u'Timelining is super relaxing.',
|
|
u'Why did I not use the shell before?',
|
|
u'I like a do da cha cha',
|
|
u'I AM the Shogun of Harlem!',
|
|
(u'It doesn\'t matter if you win or lose, it\'s what you do with your '
|
|
u'dancin\' shoes'),
|
|
u'I have not had a night like that since the seventies.',
|
|
u'Baker Team. They\'re all dead, sir.',
|
|
(u'I could have killed \'em all, I could\'ve killed you. In town '
|
|
u'you\'re the law, out here it\'s me.'),
|
|
(u'Are you telling me that 200 of our men against your boy is a no-win '
|
|
u'situation for us?'),
|
|
u'Hunting? We ain\'t huntin\' him, he\'s huntin\' us!',
|
|
u'You picked the wrong man to push',
|
|
u'Live for nothing or die for something',
|
|
u'I am the Fred Astaire of karate.',
|
|
(u'God gave me a great body and it\'s my duty to take care of my '
|
|
u'physical temple.'),
|
|
u'This maniac should be wearing a number, not a badge',
|
|
u'Imagination is more important than knowledge.',
|
|
u'Do you hate being dead?',
|
|
u'You\'ve got 5 seconds... and 3 are up.',
|
|
u'He is in a gunfight right now. I\'m gonna have to take a message',
|
|
u'That would be better than losing your teeth',
|
|
u'The less you know, the more you make',
|
|
(u'A SQL query goes into a bar, walks up to two tables and asks, '
|
|
u'"Can I join you?"'),
|
|
u'This is your captor speaking.',
|
|
(u'If I find out you\'re lying, I\'ll come back and kill you in your '
|
|
u'own kitchen.'),
|
|
u'That would be better than losing your teeth',
|
|
(u'He\'s the kind of guy who would drink a gallon of gasoline so '
|
|
u'that he can p*ss into your campfire.'),
|
|
u'I\'m gonna take you to the bank, Senator Trent. To the blood bank!',
|
|
u'I missed! I never miss! They must have been smaller than I thought',
|
|
u'Nah. I\'m just a cook.',
|
|
u'Next thing I know, you\'ll be dating musicians.',
|
|
u'Another cold day in hell',
|
|
u'Yeah, but I bet you she doesn\'t see these boys in the choir.',
|
|
u'You guys think you\'re above the law... well you ain\'t above mine!',
|
|
(u'One thought he was invincible... the other thought he could fly... '
|
|
u'They were both wrong'),
|
|
u'To understand what recursion is, you must first understand recursion']
|
|
|
|
arg_description = (
|
|
u'pshell is the interactive session tool that can be used to'
|
|
u'MISSING')
|
|
|
|
arg_parser = argparse.ArgumentParser(description=arg_description)
|
|
|
|
arg_parser.add_argument(
|
|
'-s', '--storage_file', '--storage-file', dest='storage_file',
|
|
type=unicode, default=u'', help=u'Path to a plaso storage file.',
|
|
action='store', metavar='PATH')
|
|
|
|
configuration = arg_parser.parse_args()
|
|
|
|
if configuration.storage_file:
|
|
store = OpenStorageFile(configuration.storage_file)
|
|
if store:
|
|
namespace.update({'store': store})
|
|
|
|
functions = [
|
|
FindAllOutputs, GetEventData, GetParserNames, GetParserObjects,
|
|
OpenOSFile, OpenStorageFile, OpenTskFile, OpenVssFile,
|
|
ParseFile, Pfile2File,
|
|
PrintTimestamp, PrintTimestampFromEvent]
|
|
|
|
functions_strings = []
|
|
for function in functions:
|
|
docstring, _, _ = function.__doc__.partition(u'\n')
|
|
docstring = u'\t{0:s} - {1:s}'.format(function.__name__, docstring)
|
|
functions_strings.append(docstring)
|
|
functions_strings = u'\n'.join(functions_strings)
|
|
|
|
banner = (
|
|
u'--------------------------------------------------------------\n'
|
|
u' Welcome to Plaso console - home of the Plaso adventure land.\n'
|
|
u'--------------------------------------------------------------\n'
|
|
u'This is the place where everything is allowed, as long as it is '
|
|
u'written in Python.\n\n'
|
|
u'Objects available:\n\toptions - set of options to the frontend.\n'
|
|
u'\tfrontend - A copy of the pshell frontend.\n'
|
|
u'\n'
|
|
u'All libraries have been imported and can be used, see help(frontend) '
|
|
u'or help(parser).\n'
|
|
u'\n'
|
|
u'Base methods:\n'
|
|
u'{0:s}'
|
|
u'\n\tmessage - Print message strings from an event object.'
|
|
u'\n'
|
|
u'\n'
|
|
u'p.s. typing in "pdb" and pressing enter puts the shell in debug'
|
|
u'mode which causes all exceptions being sent to pdb.\n'
|
|
u'Happy command line console fu-ing.\n\n').format(functions_strings)
|
|
|
|
exit_message = u'You are now leaving the winter wonderland.\n\n{}'.format(
|
|
random.choice(_my_random_phrases))
|
|
|
|
shell_config = Config()
|
|
# Make slight adjustments to the iPython prompt.
|
|
shell_config.PromptManager.out_template = (
|
|
r'{color.Normal}[{color.Red}\#{color.Normal}]<<< ')
|
|
shell_config.PromptManager.in_template = (
|
|
r'[{color.LightBlue}\T{color.Normal}] {color.LightPurple}\Y2\n'
|
|
r'{color.Normal}[{color.Red}\#{color.Normal}] \$ ')
|
|
shell_config.PromptManager.in2_template = r'.\D.>>>'
|
|
|
|
ipshell = InteractiveShellEmbed(
|
|
user_ns=namespace, config=shell_config, banner1=banner,
|
|
exit_msg=exit_message)
|
|
ipshell.confirm_exit = False
|
|
# Set autocall to two, making parenthesis not necessary when calling
|
|
# function names (although they can be used and are necessary sometimes,
|
|
# like in variable assignments, etc).
|
|
ipshell.autocall = 2
|
|
ipshell()
|
|
|
|
return True
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if not Main():
|
|
sys.exit(1)
|
|
else:
|
|
sys.exit(0)
|