Example #1
0
    def run_plugins(self):
        log.info("Selected plugins: " + str(self.selected_plugins))
        completed_plugins = []

        for plugin in self.selected_plugins:

            # First check built-in plugins that ship with Hindsight
            # log.info(" Built-in Plugins:")
            for standard_plugin in pyhindsight.plugins.__all__:
                # Check if the standard plugin is the selected_plugin we're looking for
                if standard_plugin == plugin:
                    # Check to see if we've already run this plugin (likely from a different path)
                    if plugin in completed_plugins:
                        log.info(" - Skipping '{}'; a plugin with that name has run already".format(plugin))
                        continue

                    log.info(" - Loading '{}' [standard plugin]".format(plugin))
                    try:
                        module = importlib.import_module("pyhindsight.plugins.{}".format(plugin))
                    except ImportError, e:
                        log.error(" - Error: {}".format(e))
                        print format_plugin_output(plugin, "-unknown", 'import failed (see log)')
                        continue
                    try:
                        log.info(" - Running '{}' plugin".format(module.friendlyName))
                        parsed_items = module.plugin(self)
                        print format_plugin_output(module.friendlyName, module.version, parsed_items)
                        self.plugin_results[plugin] = [module.friendlyName, module.version, parsed_items]
                        log.info(" - Completed; {}".format(parsed_items))
                        completed_plugins.append(plugin)
                        break
                    except Exception, e:
                        print format_plugin_output(module.friendlyName, module.version, 'failed')
                        self.plugin_results[plugin] = [module.friendlyName, module.version, 'failed']
                        log.info(" - Failed; {}".format(e))
Example #2
0
    def run_plugins(self):
        log.info("Selected plugins: " + str(self.selected_plugins))
        completed_plugins = []

        for plugin in self.selected_plugins:

            # First check built-in plugins that ship with Hindsight
            # log.info(" Built-in Plugins:")
            for standard_plugin in pyhindsight.plugins.__all__:
                # Check if the standard plugin is the selected_plugin we're looking for
                if standard_plugin == plugin:
                    # Check to see if we've already run this plugin (likely from a different path)
                    if plugin in completed_plugins:
                        log.info(" - Skipping '{}'; a plugin with that name has run already".format(plugin))
                        continue

                    log.info(" - Loading '{}' [standard plugin]".format(plugin))
                    try:
                        module = importlib.import_module("pyhindsight.plugins.{}".format(plugin))
                    except ImportError, e:
                        log.error(" - Error: {}".format(e))
                        print format_plugin_output(plugin, "-unknown", 'import failed (see log)')
                        continue
                    try:
                        log.info(" - Running '{}' plugin".format(module.friendlyName))
                        parsed_items = module.plugin(self)
                        print format_plugin_output(module.friendlyName, module.version, parsed_items)
                        self.plugin_results[plugin] = [module.friendlyName, module.version, parsed_items]
                        log.info(" - Completed; {}".format(parsed_items))
                        completed_plugins.append(plugin)
                        break
                    except Exception, e:
                        print format_plugin_output(module.friendlyName, module.version, 'failed')
                        self.plugin_results[plugin] = [module.friendlyName, module.version, 'failed']
                        log.info(" - Failed; {}".format(e))
Example #3
0
def main():
    def write_excel(analysis_session):
        import StringIO

        # Set up a StringIO object to save the XLSX content to before saving to disk
        string_buffer = StringIO.StringIO()

        # Generate the XLSX content using the function in the AnalysisSession and save it to the StringIO object
        analysis_session.generate_excel(string_buffer)

        # Go back to the beginning (be kind, rewind)
        string_buffer.seek(0)

        # Write the StringIO object to a file on disk named what the user specified
        with open(
                "{}.{}".format(
                    os.path.join(real_path, analysis_session.output_name),
                    analysis_session.selected_output_format),
                'wb') as file_output:
            shutil.copyfileobj(string_buffer, file_output)

    def write_sqlite(analysis_session):
        output_file = analysis_session.output_name + '.sqlite'

        if not os.path.exists(output_file):
            analysis_session.generate_sqlite(output_file)
        else:
            print(
                "\n Database file \"{}\" already exists. Please choose a different output location.\n"
                .format(output_file))

    print(banner)

    # Useful when Hindsight is run from a different directory than where the file is located
    real_path = os.path.dirname(os.path.realpath(sys.argv[0]))

    # Set up the AnalysisSession object, and transfer the relevant input arguments to it
    analysis_session = AnalysisSession()

    # parse_arguments needs the analysis_session as an input to set things like available decrypts
    args = parse_arguments(analysis_session)

    if args.output:
        analysis_session.output_name = args.output

    if args.cache:
        analysis_session.cache_path = args.cache

    analysis_session.selected_output_format = args.format
    analysis_session.browser_type = args.browser_type
    analysis_session.timezone = args.timezone

    if args.log == 'hindsight.log':
        args.log = os.path.join(real_path, args.log)
    analysis_session.log_path = args.log

    # Set up logging
    logging.basicConfig(
        filename=analysis_session.log_path,
        level=logging.DEBUG,
        format='%(asctime)s.%(msecs).03d | %(levelname).01s | %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S')
    log = logging.getLogger(__name__)

    # Hindsight version info
    log.info(
        '\n' + '#' * 80 +
        '\n###    Hindsight v{} (https://github.com/obsidianforensics/hindsight)    ###\n'
        .format(pyhindsight.__version__) + '#' * 80)

    # Analysis start time
    print(format_meta_output("Start time", str(datetime.datetime.now())[:-3]))

    # Read the input directory
    analysis_session.input_path = args.input
    print(format_meta_output("Input directory", args.input))
    log.info("Reading files from %s" % args.input)
    input_listing = os.listdir(args.input)
    log.debug("Input directory contents: " + str(input_listing))

    # Search input directory for browser profiles to analyze
    input_profiles = analysis_session.find_browser_profiles(args.input)
    log.info(" - Found {} browser profile(s): {}".format(
        len(input_profiles), input_profiles))
    analysis_session.profile_paths = input_profiles

    print(
        format_meta_output(
            "Output name",
            "{}.{}".format(analysis_session.output_name,
                           analysis_session.selected_output_format)))

    # Run the AnalysisSession
    print("\n Processing:")
    analysis_session.run()

    print("\n Running plugins:")
    log.info("Plugins:")
    completed_plugins = []

    # First run built-in plugins that ship with Hindsight
    log.info(" Built-in Plugins:")
    for plugin in pyhindsight.plugins.__all__:
        # Check to see if we've already run this plugin (likely from a different path)
        if plugin in completed_plugins:
            continue

        log.debug(" - Loading '{}'".format(plugin))
        try:
            module = importlib.import_module(
                "pyhindsight.plugins.{}".format(plugin))
        except ImportError, e:
            log.error(" - Error: {}".format(e))
            print(
                format_plugin_output(plugin, "-unknown",
                                     'import failed (see log)'))
            continue
        try:
            log.info(" - Running '{}' plugin".format(module.friendlyName))
            parsed_items = module.plugin(analysis_session)
            print(
                format_plugin_output(module.friendlyName, module.version,
                                     parsed_items))
            log.info(" - Completed; {}".format(parsed_items))
            completed_plugins.append(plugin)
        except Exception, e:
            print(
                format_plugin_output(module.friendlyName, module.version,
                                     'failed'))
            log.info(" - Failed; {}".format(e))
Example #4
0
                            # Check to see if we've already run this plugin (likely from a different path)
                            if plugin in completed_plugins:
                                log.debug(
                                    " - Skipping '{}'; a plugin with that name has run already"
                                    .format(plugin))
                                continue

                            log.debug(" - Loading '{}'".format(plugin))
                            try:
                                module = __import__(plugin)
                            except ImportError, e:
                                log.error(" - Error: {}".format(e))
                                print(
                                    format_plugin_output(
                                        plugin, "-unknown",
                                        'import failed (see log)'))
                                continue
                            try:
                                log.info(" - Running '{}' plugin".format(
                                    module.friendlyName))
                                parsed_items = module.plugin(analysis_session)
                                print(
                                    format_plugin_output(
                                        module.friendlyName, module.version,
                                        parsed_items))
                                log.info(
                                    " - Completed; {}".format(parsed_items))
                                completed_plugins.append(plugin)
                            except Exception, e:
                                print(
Example #5
0
def main():

    def write_excel(analysis_session):
        import io

        # Set up a StringIO object to save the XLSX content to before saving to disk
        string_buffer = io.BytesIO()

        # Generate the XLSX content using the function in the AnalysisSession and save it to the StringIO object
        analysis_session.generate_excel(string_buffer)

        # Go back to the beginning (be kind, rewind)
        string_buffer.seek(0)

        # Write the StringIO object to a file on disk named what the user specified
        with open(f'{os.path.join(real_path, analysis_session.output_name)}.{analysis_session.selected_output_format}', 'wb') as file_output:
            shutil.copyfileobj(string_buffer, file_output)

    def write_sqlite(analysis_session):
        output_file = analysis_session.output_name + '.sqlite'

        if os.path.exists(output_file):
            if os.path.getsize(output_file) > 0:
                print(('\nDatabase file "{}" already exists.\n'.format(output_file)))
                user_input = input('Would you like to (O)verwrite it, (R)ename output file, or (E)xit? ')
                over_re = re.compile(r'(^o$|overwrite)', re.IGNORECASE)
                rename_re = re.compile(r'(^r$|rename)', re.IGNORECASE)
                exit_re = re.compile(r'(^e$|exit)', re.IGNORECASE)
                if re.search(exit_re, user_input):
                    print("Exiting... ")
                    sys.exit()
                elif re.search(over_re, user_input):
                    os.remove(output_file)
                    print(("Deleted old \"%s\"" % output_file))
                elif re.search(rename_re, user_input):
                    output_file = "{}_1.sqlite".format(output_file[:-7])
                    print(("Renaming new output to {}".format(output_file)))
                else:
                    print("Did not understand response.  Exiting... ")
                    sys.exit()

        analysis_session.generate_sqlite(output_file)

    def write_jsonl(analysis_session):
        output_file = analysis_session.output_name + '.jsonl'
        analysis_session.generate_jsonl(output_file)

    print(banner)

    # Useful when Hindsight is run from a different directory than where the file is located
    real_path = os.path.dirname(os.path.realpath(sys.argv[0]))

    # Set up the AnalysisSession object, and transfer the relevant input arguments to it
    analysis_session = AnalysisSession()

    # parse_arguments needs the analysis_session as an input to set things like available decrypts
    args = parse_arguments(analysis_session)

    if args.output:
        analysis_session.output_name = args.output

    if args.cache:
        analysis_session.cache_path = args.cache

    analysis_session.selected_output_format = args.format
    analysis_session.browser_type = args.browser_type
    analysis_session.timezone = args.timezone

    if args.log == 'hindsight.log':
        args.log = os.path.join(real_path, args.log)
    analysis_session.log_path = args.log

    # Set up logging
    logging.basicConfig(filename=analysis_session.log_path, level=logging.DEBUG,
                        format='%(asctime)s.%(msecs).03d | %(levelname).01s | %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S')
    log = logging.getLogger(__name__)

    # Hindsight version info
    log.info(
        '\n' + '#' * 80 + '\n###    Hindsight v{} (https://github.com/obsidianforensics/hindsight)    ###\n'
        .format(pyhindsight.__version__) + '#' * 80)

    # Analysis start time
    print((format_meta_output("Start time", str(datetime.datetime.now())[:-3])))

    # Read the input directory
    analysis_session.input_path = args.input
    print((format_meta_output('Input directory', args.input)))
    log.info(f'Reading files from {args.input}')
    input_listing = os.listdir(args.input)
    log.debug("Input directory contents: " + str(input_listing))

    # Search input directory for browser profiles to analyze
    input_profiles = analysis_session.find_browser_profiles(args.input)
    log.info(f' - Found {len(input_profiles)} browser profile(s): {input_profiles}')
    analysis_session.profile_paths = input_profiles

    print((format_meta_output(
        'Output name', f'{analysis_session.output_name}.{analysis_session.selected_output_format}')))

    # Run the AnalysisSession
    print("\n Processing:")
    analysis_session.run()

    print("\n Running plugins:")
    log.info("Plugins:")
    completed_plugins = []

    # First run built-in plugins that ship with Hindsight
    log.info(" Built-in Plugins:")
    for plugin in pyhindsight.plugins.__all__:
        # Check to see if we've already run this plugin (likely from a different path)
        if plugin in completed_plugins:
            continue

        log.debug(" - Loading '{}'".format(plugin))
        try:
            module = importlib.import_module("pyhindsight.plugins.{}".format(plugin))
        except ImportError as e:
            log.error(" - Error: {}".format(e))
            print((format_plugin_output(plugin, "-unknown", 'import failed (see log)')))
            continue
        try:
            log.info(" - Running '{}' plugin".format(module.friendlyName))
            parsed_items = module.plugin(analysis_session)
            print((format_plugin_output(module.friendlyName, module.version, parsed_items)))
            log.info(" - Completed; {}".format(parsed_items))
            completed_plugins.append(plugin)
        except Exception as e:
            print((format_plugin_output(module.friendlyName, module.version, 'failed')))
            log.info(" - Failed; {}".format(e))

    # Then look for any custom user-provided plugins in a 'plugins' directory
    log.info(" Custom Plugins:")

    if real_path not in sys.path:
        sys.path.insert(0, real_path)

    # Loop through all paths, to pick up all potential locations for custom plugins
    for potential_path in sys.path:
        # If a subdirectory exists called 'plugins' or 'pyhindsight/plugins' at the current path, continue on
        for potential_plugin_path in [os.path.join(potential_path, 'plugins'),
                                      os.path.join(potential_path, 'pyhindsight', 'plugins')]:
            if os.path.isdir(potential_plugin_path):
                log.info(" Found custom plugin directory {}:".format(potential_plugin_path))
                try:
                    # Insert the current plugin location to the system path, so we can import plugin modules by name
                    sys.path.insert(0, potential_plugin_path)

                    # Get list of available plugins and run them
                    plugin_listing = os.listdir(potential_plugin_path)

                    log.debug(" - Contents of plugin folder: " + str(plugin_listing))
                    for plugin in plugin_listing:
                        if plugin[-3:] == ".py" and plugin[0] != '_':
                            plugin = plugin.replace(".py", "")

                            # Check to see if we've already run this plugin (likely from a different path)
                            if plugin in completed_plugins:
                                log.debug(" - Skipping '{}'; a plugin with that name has run already".format(plugin))
                                continue

                            log.debug(" - Loading '{}'".format(plugin))
                            try:
                                module = __import__(plugin)
                            except ImportError as e:
                                log.error(" - Error: {}".format(e))
                                print((format_plugin_output(plugin, "-unknown", 'import failed (see log)')))
                                continue
                            try:
                                log.info(" - Running '{}' plugin".format(module.friendlyName))
                                parsed_items = module.plugin(analysis_session)
                                print((format_plugin_output(module.friendlyName, module.version, parsed_items)))
                                log.info(" - Completed; {}".format(parsed_items))
                                completed_plugins.append(plugin)
                            except Exception as e:
                                print((format_plugin_output(module.friendlyName, module.version, 'failed')))
                                log.info(" - Failed; {}".format(e))
                except Exception as e:
                    log.debug(' - Error loading plugins ({})'.format(e))
                    print('  - Error loading plugins')
                finally:
                    # Remove the current plugin location from the system path, so we don't loop over it again
                    sys.path.remove(potential_plugin_path)

    # Check if output directory exists; attempt to create if it doesn't
    if os.path.dirname(analysis_session.output_name) != "" \
            and not os.path.exists(os.path.dirname(analysis_session.output_name)):
        os.makedirs(os.path.dirname(analysis_session.output_name))

    # Get desired output type form args.format and call the correct output creation function
    if analysis_session.selected_output_format == 'xlsx':
        log.info("Writing output; XLSX format selected")
        try:
            print(("\n Writing {}.xlsx".format(analysis_session.output_name)))
            write_excel(analysis_session)
        except IOError:
            type, value, traceback = sys.exc_info()
            print((value, "- is the file open?  If so, please close it and try again."))
            log.error("Error writing XLSX file; type: {}, value: {}, traceback: {}".format(type, value, traceback))

    elif args.format == 'jsonl':
        log.info("Writing output; JSONL format selected")
        print(("\n Writing {}.jsonl".format(analysis_session.output_name)))
        write_jsonl(analysis_session)

    elif args.format == 'sqlite':
        log.info("Writing output; SQLite format selected")
        print(("\n Writing {}.sqlite".format(analysis_session.output_name)))
        write_sqlite(analysis_session)

    # Display and log finish time
    print(f'\n Finish time: {str(datetime.datetime.now())[:-3]}')
    log.info(f'Finish time: {str(datetime.datetime.now())[:-3]}\n\n')
Example #6
0
class AnalysisSession(object):
    def __init__(self,
                 profile_path=None,
                 cache_path=None,
                 browser_type=None,
                 available_input_types=None,
                 version=None,
                 display_version=None,
                 output_name=None,
                 log_path=None,
                 timezone=None,
                 available_output_formats=None,
                 selected_output_format=None,
                 available_decrypts=None,
                 selected_decrypts=None,
                 parsed_artifacts=None,
                 artifacts_display=None,
                 artifacts_counts=None,
                 plugin_descriptions=None,
                 selected_plugins=None,
                 plugin_results=None,
                 hindsight_version=None):
        self.profile_path = profile_path
        self.cache_path = cache_path
        self.browser_type = browser_type
        self.available_input_types = available_input_types
        self.version = version
        self.display_version = display_version
        self.output_name = output_name
        self.log_path = log_path
        self.timezone = timezone
        self.available_output_formats = available_output_formats
        self.selected_output_format = selected_output_format
        self.available_decrypts = available_decrypts
        self.selected_decrypts = selected_decrypts
        self.parsed_artifacts = parsed_artifacts
        self.artifacts_display = artifacts_display
        self.artifacts_counts = artifacts_counts
        self.plugin_descriptions = plugin_descriptions
        self.selected_plugins = selected_plugins
        self.plugin_results = plugin_results
        self.hindsight_version = hindsight_version

        if self.available_input_types is None:
            self.available_input_types = ['Chrome', 'Brave']

        if self.parsed_artifacts is None:
            self.parsed_artifacts = []

        if self.artifacts_counts is None:
            self.artifacts_counts = {}

        if self.available_output_formats is None:
            self.available_output_formats = ['sqlite']

        if self.available_decrypts is None:
            self.available_decrypts = {'windows': 0, 'mac': 0, 'linux': 0}

        if self.plugin_results is None:
            self.plugin_results = {}

        if __version__:
            self.hindsight_version = __version__

        # Try to import modules for different output formats, adding to self.available_output_format array if successful
        try:
            import xlsxwriter
            self.available_output_formats.append('xlsx')
        except ImportError:
            logging.warning(
                "Couldn't import module 'xlsxwriter'; XLSX output disabled.")

        # Set output name to default if not set by user
        if self.output_name is None:
            self.output_name = "Hindsight Report ({})".format(
                time.strftime('%Y-%m-%dT%H-%M-%S'))

        # Try to import modules for cookie decryption on different OSes.
        # Windows
        try:
            import win32crypt
            self.available_decrypts['windows'] = 1
        except ImportError:
            self.available_decrypts['windows'] = 0
            logging.warning(
                "Couldn't import module 'win32crypt'; cookie decryption on Windows disabled."
            )

        # Mac OS
        try:
            import keyring
            self.available_decrypts['mac'] = 1
        except ImportError:
            self.available_decrypts['mac'] = 0
            logging.warning(
                "Couldn't import module 'keyring'; cookie decryption on Mac OS disabled."
            )

        # Linux / Mac OS
        try:
            import Cryptodome.Cipher.AES
            import Cryptodome.Protocol.KDF
            self.available_decrypts['linux'] = 1
        except ImportError:
            self.available_decrypts['linux'] = 0
            self.available_decrypts['mac'] = 0
            logging.warning(
                "Couldn't import module 'Cryptodome'; cookie decryption on Linux/Mac OS disabled."
            )

    def run(self):
        if self.selected_output_format is None:
            self.selected_output_format = self.available_output_formats[-1]

        if 'pytz' in sys.modules:
            # If the timezone exists, and is a string, we need to convert it to a tzinfo object
            if self.timezone is not None and isinstance(self.timezone, str):
                try:
                    self.timezone = pytz.timezone(self.timezone)
                except pytz.exceptions.UnknownTimeZoneError:
                    print("Couldn't understand timezone; using UTC.")
                    self.timezone = pytz.timezone('UTC')

            elif self.timezone is None:
                self.timezone = pytz.timezone('UTC')
        else:
            self.timezone = None

        logging.debug("Options: " + str(self.__dict__))

        # Analysis start time
        logging.info("Starting analysis")

        if self.browser_type == "Chrome":
            browser_analysis = Chrome(
                self.profile_path,
                available_decrypts=self.available_decrypts,
                cache_path=self.cache_path,
                timezone=self.timezone)
            browser_analysis.process()
            self.parsed_artifacts = browser_analysis.parsed_artifacts
            self.artifacts_counts = browser_analysis.artifacts_counts
            self.artifacts_display = browser_analysis.artifacts_display
            self.version = browser_analysis.version
            self.display_version = browser_analysis.display_version

            for item in browser_analysis.__dict__:
                try:
                    # If the browser_analysis attribute has 'presentation' and 'data' subkeys, promote from
                    # browser_analysis object to analysis_session object
                    if browser_analysis.__dict__[item][
                            'presentation'] and browser_analysis.__dict__[
                                item]['data']:
                        setattr(self, item, browser_analysis.__dict__[item])
                except:
                    pass

        elif self.browser_type == "Brave":
            browser_analysis = Brave(self.profile_path, timezone=self.timezone)
            browser_analysis.process()
            self.parsed_artifacts = browser_analysis.parsed_artifacts
            self.artifacts_counts = browser_analysis.artifacts_counts
            self.artifacts_display = browser_analysis.artifacts_display
            self.version = browser_analysis.version
            self.display_version = browser_analysis.display_version

            for item in browser_analysis.__dict__:
                try:
                    # If the browser_analysis attribute has 'presentation' and 'data' subkeys, promote from
                    # browser_analysis object to analysis_session object
                    if browser_analysis.__dict__[item][
                            'presentation'] and browser_analysis.__dict__[
                                item]['data']:
                        setattr(self, item, browser_analysis.__dict__[item])
                except:
                    pass

    def run_plugins(self):
        logging.info("Selected plugins: " + str(self.selected_plugins))
        completed_plugins = []

        for plugin in self.selected_plugins:

            # First check built-in plugins that ship with Hindsight
            # logging.info(" Built-in Plugins:")
            for standard_plugin in pyhindsight.plugins.__all__:
                # Check if the standard plugin is the selected_plugin we're looking for
                if standard_plugin == plugin:
                    # Check to see if we've already run this plugin (likely from a different path)
                    if plugin in completed_plugins:
                        logging.info(
                            " - Skipping '{}'; a plugin with that name has run already"
                            .format(plugin))
                        continue

                    logging.info(
                        " - Loading '{}' [standard plugin]".format(plugin))
                    try:
                        module = importlib.import_module(
                            "pyhindsight.plugins.{}".format(plugin))
                    except ImportError, e:
                        logging.error(" - Error: {}".format(e))
                        print format_plugin_output(plugin, "-unknown",
                                                   'import failed (see log)')
                        continue
                    try:
                        logging.info(" - Running '{}' plugin".format(
                            module.friendlyName))
                        parsed_items = module.plugin(self)
                        print format_plugin_output(module.friendlyName,
                                                   module.version,
                                                   parsed_items)
                        self.plugin_results[plugin] = [
                            module.friendlyName, module.version, parsed_items
                        ]
                        logging.info(" - Completed; {}".format(parsed_items))
                        completed_plugins.append(plugin)
                        break
                    except Exception, e:
                        print format_plugin_output(module.friendlyName,
                                                   module.version, 'failed')
                        self.plugin_results[plugin] = [
                            module.friendlyName, module.version, 'failed'
                        ]
                        logging.info(" - Failed; {}".format(e))

            for potential_path in sys.path:
                # If a subdirectory exists called 'plugins' at the current path, continue on
                potential_plugin_path = os.path.join(potential_path, 'plugins')
                if os.path.isdir(potential_plugin_path):
                    try:
                        # Insert the current plugin location to the system path, so we can import plugin modules by name
                        sys.path.insert(0, potential_plugin_path)

                        # Get list of available plugins and run them
                        plugin_listing = os.listdir(potential_plugin_path)

                        for custom_plugin in plugin_listing:
                            if custom_plugin[
                                    -3:] == ".py" and custom_plugin[0] != '_':
                                custom_plugin = custom_plugin.replace(
                                    ".py", "")

                                if custom_plugin == plugin:
                                    # Check to see if we've already run this plugin (likely from a different path)
                                    if plugin in completed_plugins:
                                        logging.info(
                                            " - Skipping '{}'; a plugin with that name has run already"
                                            .format(plugin))
                                        continue

                                    logging.debug(
                                        " - Loading '{}' [custom plugin]".
                                        format(plugin))
                                    try:
                                        module = __import__(plugin)
                                    except ImportError, e:
                                        logging.error(" - Error: {}".format(e))
                                        print format_plugin_output(
                                            plugin, "-unknown",
                                            'import failed (see log)')
                                        continue
                                    try:
                                        logging.info(
                                            " - Running '{}' plugin".format(
                                                module.friendlyName))
                                        parsed_items = module.plugin(self)
                                        print format_plugin_output(
                                            module.friendlyName,
                                            module.version, parsed_items)
                                        self.plugin_results[plugin] = [
                                            module.friendlyName,
                                            module.version, parsed_items
                                        ]
                                        logging.info(" - Completed; {}".format(
                                            parsed_items))
                                        completed_plugins.append(plugin)
                                    except Exception, e:
                                        print format_plugin_output(
                                            module.friendlyName,
                                            module.version, 'failed')
                                        self.plugin_results[plugin] = [
                                            module.friendlyName,
                                            module.version, 'failed'
                                        ]
                                        logging.info(" - Failed; {}".format(e))
                    except Exception as e:
                        logging.debug(
                            ' - Error loading plugins ({})'.format(e))
                        print '  - Error loading plugins'
Example #7
0
def main():

    def write_excel(analysis_session):
        import StringIO

        # Set up a StringIO object to save the XLSX content to before saving to disk
        string_buffer = StringIO.StringIO()

        # Generate the XLSX content using the function in the AnalysisSession and save it to the StringIO object
        analysis_session.generate_excel(string_buffer)

        # Go back to the beginning (be kind, rewind)
        string_buffer.seek(0)

        # Write the StringIO object to a file on disk named what the user specified
        with open("{}.{}".format(os.path.join(real_path, analysis_session.output_name), analysis_session.selected_output_format), 'wb') as file_output:
            shutil.copyfileobj(string_buffer, file_output)

    def write_sqlite(analysis_session):
        output_file = analysis_session.output_name + '.sqlite'

        if os.path.exists(output_file):
            if os.path.getsize(output_file) > 0:
                print('\nDatabase file "{}" already exists.\n'.format(output_file))
                user_input = raw_input('Would you like to (O)verwrite it, (R)ename output file, or (E)xit? ')
                over_re = re.compile(r'(^o$|overwrite)', re.IGNORECASE)
                rename_re = re.compile(r'(^r$|rename)', re.IGNORECASE)
                exit_re = re.compile(r'(^e$|exit)', re.IGNORECASE)
                if re.search(exit_re, user_input):
                    print("Exiting... ")
                    sys.exit()
                elif re.search(over_re, user_input):
                    os.remove(output_file)
                    print("Deleted old \"%s\"" % output_file)
                elif re.search(rename_re, user_input):
                    output_file = "{}_1.sqlite".format(output_file[:-7])
                    print("Renaming new output to {}".format(output_file))
                else:
                    print("Did not understand response.  Exiting... ")
                    sys.exit()

        analysis_session.generate_sqlite(output_file)

    def write_jsonl(analysis_session):
        output_file = analysis_session.output_name + '.jsonl'
        analysis_session.generate_jsonl(output_file)

    print(banner)

    # Useful when Hindsight is run from a different directory than where the file is located
    real_path = os.path.dirname(os.path.realpath(sys.argv[0]))

    # Set up the AnalysisSession object, and transfer the relevant input arguments to it
    analysis_session = AnalysisSession()

    # parse_arguments needs the analysis_session as an input to set things like available decrypts
    args = parse_arguments(analysis_session)

    if args.output:
        analysis_session.output_name = args.output

    if args.cache:
        analysis_session.cache_path = args.cache

    analysis_session.selected_output_format = args.format
    analysis_session.browser_type = args.browser_type
    analysis_session.timezone = args.timezone

    if args.log == 'hindsight.log':
        args.log = os.path.join(real_path, args.log)
    analysis_session.log_path = args.log

    # Set up logging
    logging.basicConfig(filename=analysis_session.log_path, level=logging.DEBUG,
                        format='%(asctime)s.%(msecs).03d | %(levelname).01s | %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S')
    log = logging.getLogger(__name__)

    # Hindsight version info
    log.info(
        '\n' + '#' * 80 + '\n###    Hindsight v{} (https://github.com/obsidianforensics/hindsight)    ###\n'
        .format(pyhindsight.__version__) + '#' * 80)

    # Analysis start time
    print(format_meta_output("Start time", str(datetime.datetime.now())[:-3]))

    # Read the input directory
    analysis_session.input_path = args.input
    print(format_meta_output("Input directory", args.input))
    log.info("Reading files from %s" % args.input)
    input_listing = os.listdir(args.input)
    log.debug("Input directory contents: " + str(input_listing))

    # Search input directory for browser profiles to analyze
    input_profiles = analysis_session.find_browser_profiles(args.input)
    log.info(" - Found {} browser profile(s): {}".format(len(input_profiles), input_profiles))
    analysis_session.profile_paths = input_profiles

    print(format_meta_output("Output name", "{}.{}".format(analysis_session.output_name, analysis_session.selected_output_format)))

    # Run the AnalysisSession
    print("\n Processing:")
    analysis_session.run()

    print("\n Running plugins:")
    log.info("Plugins:")
    completed_plugins = []

    # First run built-in plugins that ship with Hindsight
    log.info(" Built-in Plugins:")
    for plugin in pyhindsight.plugins.__all__:
        # Check to see if we've already run this plugin (likely from a different path)
        if plugin in completed_plugins:
            continue

        log.debug(" - Loading '{}'".format(plugin))
        try:
            module = importlib.import_module("pyhindsight.plugins.{}".format(plugin))
        except ImportError, e:
            log.error(" - Error: {}".format(e))
            print(format_plugin_output(plugin, "-unknown", 'import failed (see log)'))
            continue
        try:
            log.info(" - Running '{}' plugin".format(module.friendlyName))
            parsed_items = module.plugin(analysis_session)
            print(format_plugin_output(module.friendlyName, module.version, parsed_items))
            log.info(" - Completed; {}".format(parsed_items))
            completed_plugins.append(plugin)
        except Exception, e:
            print(format_plugin_output(module.friendlyName, module.version, 'failed'))
            log.info(" - Failed; {}".format(e))
Example #8
0
                    log.debug(" - Contents of plugin folder: " + str(plugin_listing))
                    for plugin in plugin_listing:
                        if plugin[-3:] == ".py" and plugin[0] != '_':
                            plugin = plugin.replace(".py", "")

                            # Check to see if we've already run this plugin (likely from a different path)
                            if plugin in completed_plugins:
                                log.debug(" - Skipping '{}'; a plugin with that name has run already".format(plugin))
                                continue

                            log.debug(" - Loading '{}'".format(plugin))
                            try:
                                module = __import__(plugin)
                            except ImportError, e:
                                log.error(" - Error: {}".format(e))
                                print(format_plugin_output(plugin, "-unknown", 'import failed (see log)'))
                                continue
                            try:
                                log.info(" - Running '{}' plugin".format(module.friendlyName))
                                parsed_items = module.plugin(analysis_session)
                                print(format_plugin_output(module.friendlyName, module.version, parsed_items))
                                log.info(" - Completed; {}".format(parsed_items))
                                completed_plugins.append(plugin)
                            except Exception, e:
                                print(format_plugin_output(module.friendlyName, module.version, 'failed'))
                                log.info(" - Failed; {}".format(e))
                except Exception as e:
                    log.debug(' - Error loading plugins ({})'.format(e))
                    print('  - Error loading plugins')
                finally:
                    # Remove the current plugin location from the system path, so we don't loop over it again
Example #9
0
class AnalysisSession(object):
    def __init__(self, input_path=None, profile_paths=None, cache_path=None, browser_type=None, available_input_types=None,
                 version=None, display_version=None, output_name=None, log_path=None, timezone=None,
                 available_output_formats=None, selected_output_format=None, available_decrypts=None,
                 selected_decrypts=None, parsed_artifacts=None, artifacts_display=None, artifacts_counts=None,
                 plugin_descriptions=None, selected_plugins=None, plugin_results=None, hindsight_version=None, preferences=None):
        self.input_path = input_path
        self.profile_paths = profile_paths
        self.cache_path = cache_path
        self.browser_type = browser_type
        self.available_input_types = available_input_types
        self.version = version
        self.display_version = display_version
        self.output_name = output_name
        self.log_path = log_path
        self.timezone = timezone
        self.available_output_formats = available_output_formats
        self.selected_output_format = selected_output_format
        self.available_decrypts = available_decrypts
        self.selected_decrypts = selected_decrypts
        self.parsed_artifacts = parsed_artifacts
        self.artifacts_display = artifacts_display
        self.artifacts_counts = artifacts_counts
        self.plugin_descriptions = plugin_descriptions
        self.selected_plugins = selected_plugins
        self.plugin_results = plugin_results
        self.hindsight_version = hindsight_version
        self.preferences = preferences

        if self.version is None:
            self.version = []

        if self.available_input_types is None:
            self.available_input_types = ['Chrome', 'Brave']

        if self.parsed_artifacts is None:
            self.parsed_artifacts = []

        if self.artifacts_counts is None:
            self.artifacts_counts = {}

        if self.available_output_formats is None:
            self.available_output_formats = ['sqlite']

        if self.available_decrypts is None:
            self.available_decrypts = {'windows': 0, 'mac': 0, 'linux': 0}

        if self.plugin_results is None:
            self.plugin_results = {}

        if self.preferences is None:
            self.preferences = []

        if __version__:
            self.hindsight_version = __version__

        # Try to import modules for different output formats, adding to self.available_output_format array if successful
        try:
            import xlsxwriter
            self.available_output_formats.append('xlsx')
        except ImportError:
            log.warning("Couldn't import module 'xlsxwriter'; XLSX output disabled.")

        # Set output name to default if not set by user
        if self.output_name is None:
            self.output_name = "Hindsight Report ({})".format(time.strftime('%Y-%m-%dT%H-%M-%S'))

        # Try to import modules for cookie decryption on different OSes.
        # Windows
        try:
            import win32crypt
            self.available_decrypts['windows'] = 1
        except ImportError:
            self.available_decrypts['windows'] = 0

        # Mac OS
        try:
            import keyring
            self.available_decrypts['mac'] = 1
        except ImportError:
            self.available_decrypts['mac'] = 0

        # Linux / Mac OS
        try:
            import Cryptodome.Cipher.AES
            import Cryptodome.Protocol.KDF
            self.available_decrypts['linux'] = 1
        except ImportError:
            self.available_decrypts['linux'] = 0
            self.available_decrypts['mac'] = 0

    @staticmethod
    def sum_dict_counts(dict1, dict2):
        """Combine two dicts by summing the values of shared keys"""
        for key, value in dict2.items():
            # Case 1: dict2's value for key is a string (aka: it failed)
            if isinstance(value, str):
                #  The value should only be non-int if it's a Failed message
                if not value.startswith('Fail'):
                    raise ValueError('Unexpected status value')

                dict1[key] = dict1.setdefault(key, 0)

            # Case 2: dict1's value of key is a string (aka: it failed)
            elif isinstance(dict1.get(key), str):
                #  The value should only be non-int if it's a Failed message
                if not dict1.get(key).startswith('Fail'):
                    raise ValueError('Unexpected status value')

                dict1[key] = value

            # Case 3: dict2's value for key is an int, or doesn't exist.
            else:
                dict1[key] = dict1.setdefault(key, 0) + value
        return dict1

    def promote_object_to_analysis_session(self, item_name, item_value):
        if self.__dict__.get(item_name):
            self.__dict__[item_name]['data'].extend(item_value['data'])
            # TODO: add some checks around value of presentation. It *shouldn't* differ...
            self.__dict__[item_name]['presentation'] = item_value['presentation']

        else:
            setattr(self, item_name, item_value)

    @staticmethod
    def is_profile(base_path, existing_files, warn=False):
        """
        Log a warning message if any file in `required_files` is missing from
        `existing_files`. Return True if all required files are present.
        """
        is_profile = True
        for required_file in ['History', 'Cookies']:
            # This approach (checking the file names) is naive but should work.
            if required_file not in existing_files:
                if warn:
                    log.warning("The profile directory {} does not contain the file {}. Analysis may not be very useful.")
                is_profile = False
        return is_profile

    def search_subdirs(self, base_path):
        """Recursively search a path for browser profiles"""
        found_profile_paths = []
        base_dir_listing = os.listdir(base_path)
        if self.is_profile(base_path, base_dir_listing):
            found_profile_paths.append(base_path)
        for item in base_dir_listing:
            item_path = os.path.join(base_path, item)
            if os.path.isdir(item_path):
                profile_found_in_subdir = self.search_subdirs(item_path)
                if profile_found_in_subdir:
                    found_profile_paths.extend(profile_found_in_subdir)
        return found_profile_paths

    def find_browser_profiles(self, base_path):
        """Search a path for browser profiles (only Chromium-based at the moment)."""
        found_profile_paths = [base_path]
        base_dir_listing = os.listdir(base_path)

        # The 'History' and 'Cookies' SQLite files are kind of the minimum required for most
        # Chrome analysis. Warn if they are not present.
        if not self.is_profile(base_path, base_dir_listing, warn=True):
            # Only search sub dirs if the current dir is not a Profile (Profiles are not nested).
            found_profile_paths.extend(self.search_subdirs(base_path))
        log.debug("Profile paths: " + str(found_profile_paths))
        return found_profile_paths

    def generate_display_version(self):
        self.version = sorted(self.version)
        if self.version[0] != self.version[-1]:
            self.display_version = "%s-%s" % (self.version[0], self.version[-1])
        else:
            self.display_version = self.version[0]

    def run(self):
        if self.selected_output_format is None:
            self.selected_output_format = self.available_output_formats[-1]

        if 'pytz' in sys.modules:
            # If the timezone exists, and is a string, we need to convert it to a tzinfo object
            if self.timezone is not None and isinstance(self.timezone, str):
                try:
                    self.timezone = pytz.timezone(self.timezone)
                except pytz.exceptions.UnknownTimeZoneError:
                    print("Couldn't understand timezone; using UTC.")
                    self.timezone = pytz.timezone('UTC')

            elif self.timezone is None:
                self.timezone = pytz.timezone('UTC')
        else:
            self.timezone = None

        log.debug("Options: " + str(self.__dict__))

        # Analysis start time
        log.info("Starting analysis")

        # Search input directory for browser profiles to analyze
        input_profiles = self.find_browser_profiles(self.input_path)
        log.info(" - Found {} browser profile(s): {}".format(len(input_profiles), input_profiles))
        self.profile_paths = input_profiles

        # Make sure the input is what we're expecting
        assert isinstance(self.profile_paths, list)
        assert len(self.profile_paths) >= 1

        for found_profile_path in self.profile_paths:

            if self.browser_type == "Chrome":
                browser_analysis = Chrome(found_profile_path, available_decrypts=self.available_decrypts,
                                          cache_path=self.cache_path, timezone=self.timezone)
                browser_analysis.process()
                self.parsed_artifacts.extend(browser_analysis.parsed_artifacts)
                self.artifacts_counts = self.sum_dict_counts(self.artifacts_counts, browser_analysis.artifacts_counts)
                self.artifacts_display = browser_analysis.artifacts_display
                self.version.extend(browser_analysis.version)
                self.display_version = browser_analysis.display_version
                self.preferences.extend(browser_analysis.preferences)

                for item in browser_analysis.__dict__:
                    if isinstance(browser_analysis.__dict__[item], dict):
                        try:
                            # If the browser_analysis attribute has 'presentation' and 'data' subkeys, promote from
                            if browser_analysis.__dict__[item].get('presentation') and browser_analysis.__dict__[item].get('data'):
                                self.promote_object_to_analysis_session(item, browser_analysis.__dict__[item])
                        except Exception as e:
                            log.info("Exception occurred while analyzing {} for analysis session promotion: {}".format(item, e))

            elif self.browser_type == "Brave":
                browser_analysis = Brave(found_profile_path, timezone=self.timezone)
                browser_analysis.process()
                self.parsed_artifacts = browser_analysis.parsed_artifacts
                self.artifacts_counts = browser_analysis.artifacts_counts
                self.artifacts_display = browser_analysis.artifacts_display
                self.version = browser_analysis.version
                self.display_version = browser_analysis.display_version

                for item in browser_analysis.__dict__:
                    if isinstance(browser_analysis.__dict__[item], dict):
                        try:
                            # If the browser_analysis attribute has 'presentation' and 'data' subkeys, promote from
                            if browser_analysis.__dict__[item].get('presentation') and browser_analysis.__dict__[item].get('data'):
                                self.promote_object_to_analysis_session(item, browser_analysis.__dict__[item])
                        except Exception as e:
                            log.info("Exception occurred while analyzing {} for analysis session promotion: {}".format(item, e))

        self.generate_display_version()

    def run_plugins(self):
        log.info("Selected plugins: " + str(self.selected_plugins))
        completed_plugins = []

        for plugin in self.selected_plugins:

            # First check built-in plugins that ship with Hindsight
            # log.info(" Built-in Plugins:")
            for standard_plugin in pyhindsight.plugins.__all__:
                # Check if the standard plugin is the selected_plugin we're looking for
                if standard_plugin == plugin:
                    # Check to see if we've already run this plugin (likely from a different path)
                    if plugin in completed_plugins:
                        log.info(" - Skipping '{}'; a plugin with that name has run already".format(plugin))
                        continue

                    log.info(" - Loading '{}' [standard plugin]".format(plugin))
                    try:
                        module = importlib.import_module("pyhindsight.plugins.{}".format(plugin))
                    except ImportError, e:
                        log.error(" - Error: {}".format(e))
                        print format_plugin_output(plugin, "-unknown", 'import failed (see log)')
                        continue
                    try:
                        log.info(" - Running '{}' plugin".format(module.friendlyName))
                        parsed_items = module.plugin(self)
                        print format_plugin_output(module.friendlyName, module.version, parsed_items)
                        self.plugin_results[plugin] = [module.friendlyName, module.version, parsed_items]
                        log.info(" - Completed; {}".format(parsed_items))
                        completed_plugins.append(plugin)
                        break
                    except Exception, e:
                        print format_plugin_output(module.friendlyName, module.version, 'failed')
                        self.plugin_results[plugin] = [module.friendlyName, module.version, 'failed']
                        log.info(" - Failed; {}".format(e))

            for potential_path in sys.path:
                # If a subdirectory exists called 'plugins' at the current path, continue on
                potential_plugin_path = os.path.join(potential_path, 'plugins')
                if os.path.isdir(potential_plugin_path):
                    try:
                        # Insert the current plugin location to the system path, so we can import plugin modules by name
                        sys.path.insert(0, potential_plugin_path)

                        # Get list of available plugins and run them
                        plugin_listing = os.listdir(potential_plugin_path)

                        for custom_plugin in plugin_listing:
                            if custom_plugin[-3:] == ".py" and custom_plugin[0] != '_':
                                custom_plugin = custom_plugin.replace(".py", "")

                                if custom_plugin == plugin:
                                    # Check to see if we've already run this plugin (likely from a different path)
                                    if plugin in completed_plugins:
                                        log.info(" - Skipping '{}'; a plugin with that name has run already".format(plugin))
                                        continue

                                    log.debug(" - Loading '{}' [custom plugin]".format(plugin))
                                    try:
                                        module = __import__(plugin)
                                    except ImportError, e:
                                        log.error(" - Error: {}".format(e))
                                        print format_plugin_output(plugin, "-unknown", 'import failed (see log)')
                                        continue
                                    try:
                                        log.info(" - Running '{}' plugin".format(module.friendlyName))
                                        parsed_items = module.plugin(self)
                                        print format_plugin_output(module.friendlyName, module.version, parsed_items)
                                        self.plugin_results[plugin] = [module.friendlyName, module.version, parsed_items]
                                        log.info(" - Completed; {}".format(parsed_items))
                                        completed_plugins.append(plugin)
                                    except Exception, e:
                                        print format_plugin_output(module.friendlyName, module.version, 'failed')
                                        self.plugin_results[plugin] = [module.friendlyName, module.version, 'failed']
                                        log.info(" - Failed; {}".format(e))
                    except Exception as e:
                        log.debug(' - Error loading plugins ({})'.format(e))
                        print '  - Error loading plugins'