import json import os import sys import logging import bottle import importlib import pyhindsight import pyhindsight.plugins from pyhindsight.analysis import AnalysisSession from pyhindsight.utils import banner, MyEncoder # This will be the main pyhindsight.AnalysisSession object that all the work will be done on analysis_session = None STATIC_PATH = 'static' def get_plugins_info(): plugin_descriptions = [] 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 description = {'file_name': plugin, 'friendly_name': None, 'version': None, 'error': None, 'error_msg': None, 'parent_path': None} try: module = importlib.import_module("pyhindsight.plugins.{}".format(plugin)) description['friendly_name'] = module.friendlyName description['version'] = module.version try: module.plugin() except ImportError as e: description['error'] = 'import' description['error_msg'] = e continue except Exception as e: description['error'] = 'other' description['error_msg'] = e continue finally: plugin_descriptions.append(description) completed_plugins.append(plugin) # 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])) if real_path not in sys.path: sys.path.insert(0, real_path) # Loop through all paths, to pick up all potential locations for plugins 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 plugin in plugin_listing: if plugin[-3:] == ".py" and plugin[0] != '_': description = {'file_name': plugin, 'friendly_name': None, 'version': None, 'error': None, 'error_msg': None, 'parent_path': potential_plugin_path} plugin = plugin.replace(".py", "") # Check to see if we've already run this plugin (likely from a different path) if plugin in completed_plugins: continue try: module = __import__(plugin) description['friendly_name'] = module.friendlyName description['version'] = module.version try: module.plugin() except ImportError as e: description['error'] = 'import' description['error_msg'] = e continue except Exception as e: description['error'] = 'other' description['error_msg'] = e continue finally: plugin_descriptions.append(description) completed_plugins.append(plugin) 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) return plugin_descriptions # Static Routes @bottle.get('/static/<filename:re:.*\.(png|css|ico|svg|json|eot|svg|ttf|woff|woff2)>') def images(filename): return bottle.static_file(filename, root=STATIC_PATH) @bottle.route('/') def main_screen(): global analysis_session analysis_session = AnalysisSession() bottle_args = analysis_session.__dict__ analysis_session.plugin_descriptions = get_plugins_info() bottle_args['plugins_info'] = analysis_session.plugin_descriptions return bottle.template(os.path.join('templates', 'run.tpl'), bottle_args) @bottle.route('/run', method='POST') def do_run(): # Get user selections from the UI ui_selected_decrypts = bottle.request.forms.getall('selected_decrypts') analysis_session.selected_plugins = bottle.request.forms.getall('selected_plugins') analysis_session.input_path = bottle.request.forms.get('profile_path') # TODO: refactor bottle name analysis_session.cache_path = bottle.request.forms.get('cache_path') analysis_session.browser_type = bottle.request.forms.get('browser_type') analysis_session.timezone = bottle.request.forms.get('timezone') analysis_session.log_path = bottle.request.forms.get('log_path') # 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) if 'windows' in ui_selected_decrypts: analysis_session.available_decrypts['windows'] = 1 else: analysis_session.available_decrypts['windows'] = 0 if 'mac' in ui_selected_decrypts: analysis_session.available_decrypts['mac'] = 1 else: analysis_session.available_decrypts['mac'] = 0 if 'linux' in ui_selected_decrypts: analysis_session.available_decrypts['linux'] = 1 else: analysis_session.available_decrypts['linux'] = 0 analysis_session.run() analysis_session.run_plugins() return bottle.redirect('/results') @bottle.route('/results') def display_results(): return bottle.template('templates/results.tpl', analysis_session.__dict__) @bottle.route('/sqlite') def generate_sqlite(): temp_output = '.tempdb' try: os.remove(temp_output) except: # temp file deletion failed pass import io str_io = io.BytesIO() analysis_session.generate_sqlite(temp_output) with open(temp_output, 'rb') as f: str_io.write(f.read()) try: os.remove(temp_output) except: # temp file deletion failed pass bottle.response.headers['Content-Type'] = 'application/x-sqlite3' bottle.response.headers['Content-Disposition'] = 'attachment; filename={}.sqlite'.format(analysis_session.output_name) str_io.seek(0) return str_io.read() @bottle.route('/xlsx') def generate_xlsx(): import io string_buffer = io.BytesIO() analysis_session.generate_excel(string_buffer) string_buffer.seek(0) bottle.response.headers['Content-Type'] = \ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8' bottle.response.headers['Content-Disposition'] = f'attachment; filename="{analysis_session.output_name}.xlsx"' return string_buffer @bottle.route('/jsonl') def generate_jsonl(): # TODO: there has to be a way to do this without making a temp file... temp_output = '.tempjsonl' try: os.remove(temp_output) except: # temp file deletion failed pass analysis_session.generate_jsonl(temp_output) import io string_buffer = io.BytesIO() with open(temp_output, 'rb') as f: string_buffer.write(f.read()) try: os.remove(temp_output) except: # temp file deletion failed pass bottle.response.headers['Content-Type'] = 'application/json; charset=UTF-8' bottle.response.headers['Content-Disposition'] = f'attachment; filename={analysis_session.output_name}.jsonl' string_buffer.seek(0) return string_buffer def main(): print(banner) global STATIC_PATH # Get the hindsight module's path on disk to add to sys.path, so we can find templates and static files module_path = os.path.dirname(pyhindsight.__file__) sys.path.insert(0, module_path) # Loop through all paths in system path, to pick up all potential locations for templates and static files. # Paths can get weird when the program is run from a different directory, or when the packaged exe is unpacked. for potential_path in sys.path: potential_template_path = potential_path if os.path.isdir(potential_template_path): # Insert the current plugin location to the system path, so bottle can find the templates bottle.TEMPLATE_PATH.insert(0, potential_template_path) potential_static_path = os.path.join(potential_path, 'static') if os.path.isdir(potential_static_path): STATIC_PATH = potential_static_path # webbrowser.open("http://localhost:8080") bottle.run(host='localhost', port=8080, debug=True) if __name__ == "__main__": main()