def logo(): print(""" _ (_) _ _ _ ____ _____ ____ | | | | | _ \| ___ |/ ___) \ V /| | |_| | ____| | \_/ |_| __/|_____)_| v{} |_| """.format(__version__)) db = Database() count = db.get_sample_count() try: db.find('all') except Exception: print_error("You need to update your Viper database. Run 'python update.py -d'") sys.exit() if __project__.name: name = __project__.name else: name = 'default' print(magenta("You have " + bold(count)) + magenta(" files in your " + bold(name)) + magenta(" repository"))
def run(self, *args): try: args = self.parser.parse_args(args) except SystemExit: return if __sessions__.is_set(): if not __sessions__.current.file.id: self.log( 'error', "The opened file does not have an ID, have you stored it yet?" ) return self.log( 'info', "Current name is: {}".format( bold(__sessions__.current.file.name))) new_name = input("New name: ") if not new_name: self.log('error', "File name can't be empty!") return Database().rename(__sessions__.current.file.id, new_name) self.log('info', "Refreshing session to update attributes...") __sessions__.new(__sessions__.current.file.path) else: self.log( 'error', "No open session. This command expects a file to be open.")
def cmd_help(self, *args): print(bold("Commands:")) rows = [] for command_name, command_item in self.commands.items(): rows.append([command_name, command_item['description']]) print(table(['Command', 'Description'], rows)) print("") print(bold("Modules:")) rows = [] for module_name, module_item in __modules__.items(): rows.append([module_name, module_item['description']]) print(table(['Command', 'Description'], rows))
def cmd_projects(self, *args): parser = argparse.ArgumentParser( prog='projects', description="Open a file", epilog="List or switch existing projects") group = parser.add_mutually_exclusive_group() group.add_argument('-l', '--list', action='store_true', help="List all existing projects") group.add_argument('-s', '--switch', metavar='PROJECT NAME', help="Switch to the specified project") try: args = parser.parse_args(args) except: return projects_path = os.path.join(expanduser("~"), '.viper', 'projects') if not os.path.exists(projects_path): self.log('info', "The projects directory does not exist yet") return if args.list: self.log('info', "Projects Available:") rows = [] for project in os.listdir(projects_path): project_path = os.path.join(projects_path, project) if os.path.isdir(project_path): current = '' if __project__.name and project == __project__.name: current = 'Yes' rows.append([ project, time.ctime(os.path.getctime(project_path)), current ]) self.log( 'table', dict(header=['Project Name', 'Creation Time', 'Current'], rows=rows)) elif args.switch: if __sessions__.is_set(): __sessions__.close() self.log('info', "Closed opened session") __project__.open(args.switch) self.log('info', "Switched to project {0}".format(bold(args.switch))) # Need to re-initialize the Database to open the new SQLite file. self.db = Database() else: self.log('info', parser.print_usage())
def run(self, *args): try: args = self.parser.parse_args(args) except SystemExit: return if not __sessions__.is_set(): self.log( 'error', "No open session. This command expects a file to be open.") return db = Database() # check if the file is already stores, otherwise exit malware = db.find(key='sha256', value=__sessions__.current.file.sha256) if not malware: self.log( 'error', "The opened file doesn't appear to be in the database, have you stored it yet?" ) return if args.list: # Retrieve all analysis for the currently opened file. analysis_list = malware[0].analysis if not analysis_list: self.log('info', "No analysis available for this file yet") return # Populate table rows. rows = [[analysis.id, analysis.cmd_line, analysis.stored_at] for analysis in analysis_list] # Display list of existing results. self.log( 'table', dict(header=['ID', 'Cmd Line', 'Saved On (UTC)'], rows=rows)) elif args.view: # Retrieve analysis wth the specified ID and print it. result = db.get_analysis(args.view) if result: self.log('info', bold('Cmd Line: ') + result.cmd_line) for line in json.loads(result.results): self.log(line['type'], line['data']) else: self.log('info', "There is no analysis with ID {0}".format(args.view)) elif args.delete: # Delete the analysis with the specified ID. db.delete_analysis(args.delete) else: self.parser.print_usage()
def cmd_new(self, *args): title = input("Enter a title for the new file: ") # Create a new temporary file. tmp = tempfile.NamedTemporaryFile(delete=False) # Open the temporary file with the default editor, or with nano. os.system('"${EDITOR:-nano}" ' + tmp.name) __sessions__.new(tmp.name) __sessions__.current.file.name = title self.log( 'info', "New file with title \"{0}\" added to the current session".format( bold(title)))
def logo(): print(""" _ (_) _ _ _ ____ _____ ____ | | | | | _ \\| ___ |/ ___) \\ V /| | |_| | ____| | \\_/ |_| __/|_____)_| v{} |_| """.format(__version__)) db = Database() count = db.get_sample_count() try: db.find("all") except Exception: sys.exit() if __project__.name: name = __project__.name else: name = "default" print( magenta("You have " + bold(count)) + magenta(" files in your " + bold(name)) + magenta(" repository.")) modules_count = len(__modules__) if modules_count == 0: print("") print(red(bold("You do not have any modules installed!"))) print( red("If you wish to download community modules from GitHub run:")) print(red(bold(" update-modules"))) else: print( magenta("You have " + bold(modules_count)) + magenta(" modules installed."))
def cmd_projects(self, *args): parser = argparse.ArgumentParser(prog='projects', description="Open a file", epilog="List or switch existing projects") group = parser.add_mutually_exclusive_group() group.add_argument('-l', '--list', action='store_true', help="List all existing projects") group.add_argument('-s', '--switch', metavar='PROJECT NAME', help="Switch to the specified project") try: args = parser.parse_args(args) except: return cfg = Config() if cfg.paths.storage_path: base_path = cfg.paths.storage_path else: base_path = os.path.join(expanduser("~"), '.viper') projects_path = os.path.join(base_path, 'projects') if not os.path.exists(projects_path): self.log('info', "The projects directory does not exist yet") return if args.list: self.log('info', "Projects Available:") rows = [] for project in os.listdir(projects_path): project_path = os.path.join(projects_path, project) if os.path.isdir(project_path): current = '' if __project__.name and project == __project__.name: current = 'Yes' rows.append([project, time.ctime(os.path.getctime(project_path)), current]) self.log('table', dict(header=['Project Name', 'Creation Time', 'Current'], rows=rows)) elif args.switch: if __sessions__.is_set(): __sessions__.close() self.log('info', "Closed opened session") __project__.open(args.switch) self.log('info', "Switched to project {0}".format(bold(args.switch))) # Need to re-initialize the Database to open the new SQLite file. self.db = Database() else: self.log('info', parser.print_usage())
def cmd_analysis(self, *args): parser = argparse.ArgumentParser(prog="analysis", description="Show stored module results") group = parser.add_mutually_exclusive_group() group.add_argument('-l', '--list', action='store_true', help="List all module results available for the current file") group.add_argument('-v', '--view', metavar='ANALYSIS ID', type=int, help="View the specified analysis") group.add_argument('-d', '--delete', metavar='ANALYSIS ID', type=int, help="Delete an existing analysis") try: args = parser.parse_args(args) except: return if not __sessions__.is_set(): self.log('error', "No open session") return # check if the file is already stores, otherwise exit malware = Database().find(key='sha256', value=__sessions__.current.file.sha256) if not malware: self.log('error', "The opened file doesn't appear to be in the database, have you stored it yet?") return if args.list: # Retrieve all analysis for the currently opened file. analysis_list = malware[0].analysis if not analysis_list: self.log('info', "No analysis available for this file yet") return # Populate table rows. rows = [[analysis.id, analysis.cmd_line, analysis.stored_at] for analysis in analysis_list] # Display list of existing results. self.log('table', dict(header=['ID', 'Cmd Line', 'Saved On'], rows=rows)) elif args.view: # Retrieve analysis wth the specified ID and print it. result = Database().get_analysis(args.view) if result: self.log('info', bold('Cmd Line: ') + result.cmd_line) for line in json.loads(result.results): self.log(line['type'], line['data']) else: self.log('info', "There is no analysis with ID {0}".format(args.view))
def cmd_rename(self, *args): if __sessions__.is_set(): if not __sessions__.current.file.id: self.log('error', "The opened file does not have an ID, have you stored it yet?") return self.log('info', "Current name is: {}".format(bold(__sessions__.current.file.name))) new_name = input("New name: ") if not new_name: self.log('error', "File name can't be empty!") return self.db.rename(__sessions__.current.file.id, new_name) self.log('info', "Refreshing session to update attributes...") __sessions__.new(__sessions__.current.file.path) else: self.log('error', "No open session")
def run(self, *args): try: args = self.parser.parse_args(args) except SystemExit: return if cfg.get('paths').storage_path: base_path = cfg.get('paths').storage_path else: base_path = os.path.join(expanduser("~"), '.viper') projects_path = os.path.join(base_path, 'projects') if args.list: if not os.path.exists(projects_path): self.log('info', "The projects directory does not exist yet") return self.log('info', "Projects Available:") rows = [] for project in os.listdir(projects_path): project_path = os.path.join(projects_path, project) if os.path.isdir(project_path): current = '' if __project__.name and project == __project__.name: current = 'Yes' rows.append([project, time.ctime(os.path.getctime(project_path)), current]) self.log('table', dict(header=['Project Name', 'Creation Time', 'Current'], rows=rows)) elif args.switch: if __sessions__.is_set(): __sessions__.close() self.log('info', "Closed opened session") __project__.open(args.switch) self.log('info', "Switched to project {0}".format(bold(args.switch))) # Need to re-initialize the Database to open the new SQLite file. db.__init__() else: self.log('info', self.parser.print_usage())
def run(self, *args): try: args = self.parser.parse_args(args) except SystemExit: return if not __sessions__.is_set(): self.log('error', "No open session. This command expects a file to be open.") return db = Database() # check if the file is already stores, otherwise exit malware = db.find(key='sha256', value=__sessions__.current.file.sha256) if not malware: self.log('error', "The opened file doesn't appear to be in the database, have you stored it yet?") return if args.list: # Retrieve all analysis for the currently opened file. analysis_list = malware[0].analysis if not analysis_list: self.log('info', "No analysis available for this file yet") return # Populate table rows. rows = [[analysis.id, analysis.cmd_line, analysis.stored_at] for analysis in analysis_list] # Display list of existing results. self.log('table', dict(header=['ID', 'Cmd Line', 'Saved On (UTC)'], rows=rows)) elif args.view: # Retrieve analysis wth the specified ID and print it. result = db.get_analysis(args.view) if result: self.log('info', bold('Cmd Line: ') + result.cmd_line) for line in json.loads(result.results): self.log(line['type'], line['data']) else: self.log('info', "There is no analysis with ID {0}".format(args.view))
def print_info(message): print(bold(cyan("[*]")) + " {0}".format(message))
def run(self, *args): try: args = self.parser.parse_args(args) except SystemExit: return if not __sessions__.is_set(): self.log('error', "No open session") return # check if the file is already stores, otherwise exit as no notes command will work if the file is not stored in the database malware = db.find(key='sha256', value=__sessions__.current.file.sha256) if not malware: self.log('error', "The opened file doesn't appear to be in the database, have you stored it yet?") return if args.list: # Retrieve all notes for the currently opened file. notes = malware[0].note if not notes: self.log('info', "No notes available for this file yet") return # Populate table rows. rows = [[note.id, note.title] for note in notes] # Display list of existing notes. self.log('table', dict(header=['ID', 'Title'], rows=rows)) elif args.add: title = input("Enter a title for the new note: ") # Create a new temporary file. with tempfile.NamedTemporaryFile(mode='w+') as tmp: # Open the temporary file with the default editor, or with nano. os.system('"${EDITOR:-nano}" ' + tmp.name) # Once the user is done editing, we need to read the content and # store it in the database. body = tmp.read() db.add_note(__sessions__.current.file.sha256, title, body) self.log('info', 'New note with title "{0}" added to the current file'.format(bold(title))) elif args.view: # Retrieve note wth the specified ID and print it. note = db.get_note(args.view) if note: self.log('info', bold('Title: ') + note.title) if isinstance(note.body, bytes): # OLD: Old style, the content is stored as bytes # This is fixed when the user edits the old note. body = note.body.decode() else: body = note.body self.log('info', '{}\n{}'.format(bold('Body:'), body)) else: self.log('info', "There is no note with ID {0}".format(args.view)) elif args.edit: # Retrieve note with the specified ID. note = db.get_note(args.edit) if note: # Create a new temporary file. with tempfile.NamedTemporaryFile(mode='w+') as tmp: # Write the old body to the temporary file. if isinstance(note.body, bytes): # OLD: Old style, the content is stored as bytes body = note.body.decode() else: body = note.body tmp.write(body) tmp.flush() tmp.seek(0) # Open the old body with the text editor. os.system('"${EDITOR:-nano}" ' + tmp.name) # Read the new body from the temporary file. body = tmp.read() # Update the note entry with the new body. db.edit_note(args.edit, body) self.log('info', "Updated note with ID {0}".format(args.edit)) elif args.delete: # Delete the note with the specified ID. db.delete_note(args.delete) else: self.parser.print_usage()
def run(self, *args): try: args = self.parser.parse_args(args) except SystemExit: return title = input("Enter a title for the new file: ") # Create a new temporary file. tmp = tempfile.NamedTemporaryFile(delete=False) # Open the temporary file with the default editor, or with nano. os.system('"${EDITOR:-nano}" ' + tmp.name) __sessions__.new(tmp.name) __sessions__.current.file.name = title self.log('info', "New file with title \"{0}\" added to the current session".format(bold(title)))
def start(self): # Logo. logo() # Setup shell auto-complete. def complete(text, state): # Try to autocomplete commands. cmds = [i for i in self.cmd.commands if i.startswith(text)] if state < len(cmds): return cmds[state] # Try to autocomplete modules. mods = [i for i in __modules__ if i.startswith(text)] if state < len(mods): return mods[state] # Then autocomplete paths. if text.startswith("~"): text = "{0}{1}".format(expanduser("~"), text[1:]) return (glob.glob(text+'*')+[None])[state] # Auto-complete on tabs. readline.set_completer_delims(' \t\n;') readline.parse_and_bind('tab: complete') readline.set_completer(complete) # Save commands in history file. def save_history(path): readline.write_history_file(path) # If there is an history file, read from it and load the history # so that they can be loaded in the shell. # Now we are storing the history file in the local project folder history_path = os.path.join(__project__.path, 'history') if os.path.exists(history_path): readline.read_history_file(history_path) # Register the save history at program's exit. atexit.register(save_history, path=history_path) # Main loop. while self.active: # If there is an open session, we include the path to the opened # file in the shell prompt. # TODO: perhaps this block should be moved into the session so that # the generation of the prompt is done only when the session's # status changes. prefix = '' if __project__.name: prefix = bold(cyan(__project__.name)) + ' ' if __sessions__.is_set(): stored = '' filename = '' if __sessions__.current.file: filename = __sessions__.current.file.name if not Database().find(key='sha256', value=__sessions__.current.file.sha256): stored = magenta(' [not stored]', True) misp = '' if __sessions__.current.misp_event: misp = '[MISP' if __sessions__.current.misp_event.event.id: misp += ' {}'.format(__sessions__.current.misp_event.event.id) else: misp += ' New Event' if __sessions__.current.misp_event.off: misp += ' (Offline)' misp += ']' prompt = (prefix + cyan('viper ', True) + white(filename, True) + blue(misp, True) + stored + cyan(' > ', True)) # Otherwise display the basic prompt. else: prompt = prefix + cyan('viper > ', True) # Wait for input from the user. try: data = input(prompt).strip() except KeyboardInterrupt: print("") # Terminate on EOF. except EOFError: self.stop() print("") continue # Parse the input if the user provided any. else: # If there are recognized keywords, we replace them with # their respective value. data = self.keywords(data) # Skip if the input is empty. if not data: continue # Check for output redirection # If there is a > in the string, we assume the user wants to output to file. if '>' in data: data, console_output['filename'] = data.split('>') print("Writing output to {0}".format(console_output['filename'].strip())) # If the input starts with an exclamation mark, we treat the # input as a bash command and execute it. # At this point the keywords should be replaced. if data.startswith('!'): os.system(data[1:]) continue # Try to split commands by ; so that you can sequence multiple # commands at once. # For example: # viper > find name *.pdf; open --last 1; pdf id # This will automatically search for all PDF files, open the first entry # and run the pdf module against it. split_commands = data.split(';') for split_command in split_commands: split_command = split_command.strip() if not split_command: continue # If it's an internal command, we parse the input and split it # between root command and arguments. root, args = self.parse(split_command) # Check if the command instructs to terminate. if root in ('exit', 'quit'): self.stop() continue try: # If the root command is part of the embedded commands list we # execute it. if root in self.cmd.commands: self.cmd.commands[root]['obj'](*args) del(self.cmd.output[:]) # If the root command is part of loaded modules, we initialize # the module and execute it. elif root in __modules__: module = __modules__[root]['obj']() module.set_commandline(args) module.run() if cfg.modules.store_output and __sessions__.is_set(): try: Database().add_analysis(__sessions__.current.file.sha256, split_command, module.output) except: pass del(module.output[:]) else: print("Command not recognized.") except KeyboardInterrupt: pass except Exception: print_error("The command {0} raised an exception:".format(bold(root))) traceback.print_exc() console_output['filename'] = None # reset output to stdout
def start(self): # Setup shell auto-complete. def complete(text, state): return (glob.glob(text+'*')+[None])[state] # Auto-complete on tabs. readline.set_completer_delims(' \t\n;') readline.parse_and_bind('tab: complete') readline.set_completer(complete) # Save commands in history file. def save_history(path): readline.write_history_file(path) # If there is an history file, read from it and load the history # so that they can be loaded in the shell. history_path = os.path.expanduser('~/.viperhistory') if os.path.exists(history_path): readline.read_history_file(history_path) # Register the save history at program's exit. atexit.register(save_history, path=history_path) # Main loop. while self.active: # If there is an open session, we include the path to the opened # file in the shell prompt. # TODO: perhaps this block should be moved into the session so that # the generation of the prompt is done only when the session's # status changes. if __session__.is_set(): prompt = cyan('shell ') + white(__session__.file.name) + cyan(' > ') # Otherwise display the basic prompt. else: prompt = cyan('shell > ') # Wait for input from the user. try: data = raw_input(prompt).strip() except KeyboardInterrupt: print("") # Terminate on EOF. except EOFError: self.stop() print("") continue # Parse the input if the user provided any. else: # If there are recognized keywords, we replace them with # their respective value. data = self.keywords(data) # Skip if the input is empty. if not data: continue # If the input starts with an exclamation mark, we treat the # input as a bash command and execute it. # At this point the keywords should be replaced. if data.startswith('!'): os.system(data[1:]) continue # If it's an internal command, we parse the input and split it # between root command and arguments. root, args = self.parse(data) # Check if the command instructs to terminate. if root in ('exit', 'quit'): self.stop() continue try: # If the root command is part of the embedded commands list we # execute it. if root in self.cmd.commands: self.cmd.commands[root]['obj'](*args) # If the root command is part of loaded modules, we initialize # the module and execute it. elif root in __modules__: module = __modules__[root]['obj']() module.set_args(args) module.run() else: print("Command not recognized.") except KeyboardInterrupt: pass except Exception as e: print_error("The command {0} raised an exception:".format(bold(root))) traceback.print_exc()
def start(self): # log start log.info('Starting viper-cli') # Logo. logo() # Setup shell auto-complete. def complete(text, state): # Try to autocomplete both commands and modules completions = list() completions += [i for i in self.cmd.commands if i.startswith(text)] completions += [i for i in __modules__ if i.startswith(text)] if state < len(completions): return completions[state] # Then autocomplete paths. if text.startswith("~"): text = "{0}{1}".format(expanduser("~"), text[1:]) return (glob.glob(text + '*') + [None])[state] # Auto-complete on tabs. readline.set_completer_delims(' \t\n;') readline.parse_and_bind('tab: complete') readline.set_completer(complete) # Save commands in history file. def save_history(path): readline.write_history_file(path) # If there is an history file, read from it and load the history # so that they can be loaded in the shell. # Now we are storing the history file in the local project folder history_path = os.path.join(__project__.path, 'history') if os.path.exists(history_path): readline.read_history_file(history_path) # Register the save history at program's exit. atexit.register(save_history, path=history_path) # Main loop. while self.active: # If there is an open session, we include the path to the opened # file in the shell prompt. # TODO: perhaps this block should be moved into the session so that # the generation of the prompt is done only when the session's # status changes. prefix = '' if __project__.name: prefix = bold(cyan(__project__.name)) + ' ' if __sessions__.is_set(): stored = '' filename = '' if __sessions__.current.file: filename = __sessions__.current.file.name if not Database().find( key='sha256', value=__sessions__.current.file.sha256): stored = magenta(' [not stored]', True) misp = '' if __sessions__.current.misp_event: misp = '[MISP' if __sessions__.current.misp_event.event.id: misp += ' {}'.format( __sessions__.current.misp_event.event.id) else: misp += ' New Event' if __sessions__.current.misp_event.off: misp += ' (Offline)' misp += ']' prompt = (prefix + cyan('viper ', True) + white(filename, True) + blue(misp, True) + stored + cyan(' > ', True)) # Otherwise display the basic prompt. else: prompt = prefix + cyan('viper > ', True) # Wait for input from the user. try: data = input(prompt).strip() except KeyboardInterrupt: print("") # Terminate on EOF. except EOFError: self.stop() print("") continue # Parse the input if the user provided any. else: # If there are recognized keywords, we replace them with # their respective value. data = self.keywords(data) # Skip if the input is empty. if not data: continue # Check for output redirection # If there is a > in the string, we assume the user wants to output to file. if '>' in data: data, console_output['filename'] = data.split('>') print("Writing output to {0}".format( console_output['filename'].strip())) # If the input starts with an exclamation mark, we treat the # input as a bash command and execute it. # At this point the keywords should be replaced. if data.startswith('!'): os.system(data[1:]) continue # Try to split commands by ; so that you can sequence multiple # commands at once. # For example: # viper > find name *.pdf; open --last 1; pdf id # This will automatically search for all PDF files, open the first entry # and run the pdf module against it. split_commands = data.split(';') for split_command in split_commands: split_command = split_command.strip() if not split_command: continue # If it's an internal command, we parse the input and split it # between root command and arguments. root, args = self.parse(split_command) # Check if the command instructs to terminate. if root in ('exit', 'quit'): self.stop() continue try: # If the root command is part of the embedded commands list we # execute it. if root in self.cmd.commands: self.cmd.commands[root]['obj'](*args) del (self.cmd.output[:]) # If the root command is part of loaded modules, we initialize # the module and execute it. elif root in __modules__: module = __modules__[root]['obj']() module.set_commandline(args) module.run() if cfg.modules.store_output and __sessions__.is_set( ): try: Database().add_analysis( __sessions__.current.file.sha256, split_command, module.output) except: pass del (module.output[:]) else: print("Command not recognized.") except KeyboardInterrupt: pass except Exception: print_error( "The command {0} raised an exception:".format( bold(root))) traceback.print_exc() console_output['filename'] = None # reset output to stdout
def run(self, *args): try: args = self.parser.parse_args(args) except SystemExit: return if not __sessions__.is_set(): self.log( 'error', "No open session. This command expects a file to be open.") return db = Database() # check if the file is already stores, otherwise exit as no notes command will work if the file is not stored in the database malware = db.find(key='sha256', value=__sessions__.current.file.sha256) if not malware: self.log( 'error', "The opened file doesn't appear to be in the database, have you stored it yet?" ) return if args.list: # Retrieve all notes for the currently opened file. notes = malware[0].note if not notes: self.log('info', "No notes available for this file yet") return # Populate table rows. rows = [[note.id, note.title] for note in notes] # Display list of existing notes. self.log('table', dict(header=['ID', 'Title'], rows=rows)) elif args.add: title = input("Enter a title for the new note: ") # Create a new temporary file. with tempfile.NamedTemporaryFile(mode='w+') as tmp: # Open the temporary file with the default editor, or with nano. os.system('"${EDITOR:-nano}" ' + tmp.name) # Once the user is done editing, we need to read the content and # store it in the database. body = tmp.read() db.add_note(__sessions__.current.file.sha256, title, body) self.log( 'info', 'New note with title "{0}" added to the current file'.format( bold(title))) elif args.view: # Retrieve note wth the specified ID and print it. note = db.get_note(args.view) if note: self.log('info', bold('Title: ') + note.title) if isinstance(note.body, bytes): # OLD: Old style, the content is stored as bytes # This is fixed when the user edits the old note. body = note.body.decode() else: body = note.body self.log('info', '{}\n{}'.format(bold('Body:'), body)) else: self.log('info', "There is no note with ID {0}".format(args.view)) elif args.edit: # Retrieve note with the specified ID. note = db.get_note(args.edit) if note: # Create a new temporary file. with tempfile.NamedTemporaryFile(mode='w+') as tmp: # Write the old body to the temporary file. if isinstance(note.body, bytes): # OLD: Old style, the content is stored as bytes body = note.body.decode() else: body = note.body tmp.write(body) tmp.flush() tmp.seek(0) # Open the old body with the text editor. os.system('"${EDITOR:-nano}" ' + tmp.name) # Read the new body from the temporary file. body = tmp.read() # Update the note entry with the new body. db.edit_note(args.edit, body) self.log('info', "Updated note with ID {0}".format(args.edit)) elif args.delete: # Delete the note with the specified ID. db.delete_note(args.delete) else: self.parser.print_usage()
def print_success(message): print(bold(green("[+]")) + " {0}".format(message))
def print_error(message): print(bold(red("[!]")) + " {0}".format(message))
def cmd_notes(self, *args): parser = argparse.ArgumentParser( prog="notes", description="Show information on the opened file") group = parser.add_mutually_exclusive_group() group.add_argument( '-l', '--list', action='store_true', help="List all notes available for the current file") group.add_argument('-a', '--add', action='store_true', help="Add a new note to the current file") group.add_argument('-v', '--view', metavar='NOTE ID', type=int, help="View the specified note") group.add_argument('-e', '--edit', metavar='NOTE ID', type=int, help="Edit an existing note") group.add_argument('-d', '--delete', metavar='NOTE ID', type=int, help="Delete an existing note") try: args = parser.parse_args(args) except: return if not __sessions__.is_set(): self.log('error', "No open session") return # check if the file is already stores, otherwise exit as no notes command will work if the file is not stored in the database malware = Database().find(key='sha256', value=__sessions__.current.file.sha256) if not malware: self.log( 'error', "The opened file doesn't appear to be in the database, have you stored it yet?" ) return if args.list: # Retrieve all notes for the currently opened file. notes = malware[0].note if not notes: self.log('info', "No notes available for this file yet") return # Populate table rows. rows = [[note.id, note.title] for note in notes] # Display list of existing notes. self.log('table', dict(header=['ID', 'Title'], rows=rows)) elif args.add: title = input("Enter a title for the new note: ") # Create a new temporary file. tmp = tempfile.NamedTemporaryFile(delete=False) # Open the temporary file with the default editor, or with nano. os.system('"${EDITOR:-nano}" ' + tmp.name) # Once the user is done editing, we need to read the content and # store it in the database. body = tmp.read() Database().add_note(__sessions__.current.file.sha256, title, body) # Finally, remove the temporary file. os.remove(tmp.name) self.log( 'info', "New note with title \"{0}\" added to the current file".format( bold(title))) elif args.view: # Retrieve note wth the specified ID and print it. note = Database().get_note(args.view) if note: self.log('info', bold('Title: ') + note.title) self.log('info', bold('Body:') + '\n' + note.body) else: self.log('info', "There is no note with ID {0}".format(args.view)) elif args.edit: # Retrieve note with the specified ID. note = Database().get_note(args.edit) if note: # Create a new temporary file. tmp = tempfile.NamedTemporaryFile(delete=False) # Write the old body to the temporary file. tmp.write(note.body) tmp.close() # Open the old body with the text editor. os.system('"${EDITOR:-nano}" ' + tmp.name) # Read the new body from the temporary file. body = open(tmp.name, 'r').read() # Update the note entry with the new body. Database().edit_note(args.edit, body) # Remove the temporary file. os.remove(tmp.name) self.log('info', "Updated note with ID {0}".format(args.edit)) elif args.delete: # Delete the note with the specified ID. Database().delete_note(args.delete) else: parser.print_usage()
def start(self): # log start log.info('Starting viper-cli') # Logo. logo() # Setup shell auto-complete. def complete(text, state): # filesystem path completion only makes sense for a few commands/modules fs_path_completion = False # clean up user input so far (no leading/trailing/duplicate spaces) line = " ".join(readline.get_line_buffer().split()) words = line.split(" ") # split words; e.g. store -f /tmp -> ['store', '-f', '/tmp'] if words[0] in [i for i in self.cmd.commands]: # handle completion for commands # enable filesystem path completion for certain commands (e.g. export, store) if words[0] in [x for x in self.cmd.commands if self.cmd.commands[x]["fs_path_completion"]]: fs_path_completion = True options = [key for key in self.cmd.commands[words[0]]["parser_args"]] # enable tab completion for projects --switch if words[0] == "projects": if "--switch" in words or "-s" in words: options += get_project_list() # enable tab completion for copy (list projects) if words[0] == "copy": options += get_project_list() completions = [i for i in options if i.startswith(text) and i not in words] elif words[0] in [i for i in __modules__]: # handle completion for modules if len(words) == 1: # only the module name is give so far - present all args and the subparsers (if any) options = [key for key in __modules__[words[0]]["parser_args"]] options += [key for key in __modules__[words[0]]["subparser_args"]] elif len(words) == 2: # 1 complete word and one either complete or incomplete that specifies the subparser or an arg if words[1] in list(__modules__[words[0]]["parser_args"]): # full arg for a module is given options = [key for key in __modules__[words[0]]["parser_args"]] elif words[1] in list(__modules__[words[0]]["subparser_args"]): # subparser is specified - get all subparser args options = [key for key in __modules__[words[0]]["subparser_args"][words[1]]] else: options = [key for key in __modules__[words[0]]["parser_args"]] options += [key for key in __modules__[words[0]]["subparser_args"]] else: # more that 2 words if words[1] in list(__modules__[words[0]]["subparser_args"]): # subparser is specified - get all subparser args options = [key for key in __modules__[words[0]]["subparser_args"][words[1]]] else: options = [key for key in __modules__[words[0]]["parser_args"]] completions = [i for i in options if i.startswith(text) and i not in words] else: # initial completion for both commands and modules completions = [i for i in self.cmd.commands if i.startswith(text)] completions += [i for i in __modules__ if i.startswith(text)] if state < len(completions): return completions[state] if fs_path_completion: # completion for paths only if it makes sense if text.startswith("~"): text = "{0}{1}".format(expanduser("~"), text[1:]) return (glob.glob(text + '*') + [None])[state] return # Auto-complete on tabs. readline.set_completer_delims(' \t\n;') readline.parse_and_bind('tab: complete') readline.set_completer(complete) # Save commands in history file. def save_history(path): readline.write_history_file(path) # If there is an history file, read from it and load the history # so that they can be loaded in the shell. # Now we are storing the history file in the local project folder history_path = os.path.join(__project__.path, 'history') if os.path.exists(history_path): readline.read_history_file(history_path) readline.set_history_length(10000) # Register the save history at program's exit. atexit.register(save_history, path=history_path) # Main loop. while self.active: # If there is an open session, we include the path to the opened # file in the shell prompt. # TODO: perhaps this block should be moved into the session so that # the generation of the prompt is done only when the session's # status changes. prefix = '' if __project__.name: prefix = bold(cyan(__project__.name)) + ' ' if __sessions__.is_set(): stored = '' filename = '' if __sessions__.current.file: filename = __sessions__.current.file.name if not Database().find(key='sha256', value=__sessions__.current.file.sha256): stored = magenta(' [not stored]', True) misp = '' if __sessions__.current.misp_event: misp = ' [MISP' if __sessions__.current.misp_event.event.id: misp += ' {}'.format(__sessions__.current.misp_event.event.id) else: misp += ' New Event' if __sessions__.current.misp_event.off: misp += ' (Offline)' misp += ']' prompt = (prefix + cyan('viper ', True) + white(filename, True) + blue(misp, True) + stored + cyan(' > ', True)) # Otherwise display the basic prompt. else: prompt = prefix + cyan('viper > ', True) # force str (Py3) / unicode (Py2) for prompt if sys.version_info <= (3, 0): prompt = prompt.encode('utf-8') else: prompt = str(prompt) # Wait for input from the user. try: data = input(prompt).strip() except KeyboardInterrupt: print("") # Terminate on EOF. except EOFError: self.stop() print("") continue # Parse the input if the user provided any. else: # If there are recognized keywords, we replace them with # their respective value. data = self.keywords(data) # Skip if the input is empty. if not data: continue # Check for output redirection # If there is a > in the string, we assume the user wants to output to file. if '>' in data: data, console_output['filename'] = data.split('>', 1) if ';' in console_output['filename']: console_output['filename'], more_commands = console_output['filename'].split(';', 1) data = '{};{}'.format(data, more_commands) print("Writing output to {0}".format(console_output['filename'].strip())) # If the input starts with an exclamation mark, we treat the # input as a bash command and execute it. # At this point the keywords should be replaced. if data.startswith('!'): os.system(data[1:]) continue # Try to split commands by ; so that you can sequence multiple # commands at once. # For example: # viper > find name *.pdf; open --last 1; pdf id # This will automatically search for all PDF files, open the first entry # and run the pdf module against it. split_commands = data.split(';') for split_command in split_commands: split_command = split_command.strip() if not split_command: continue # If it's an internal command, we parse the input and split it # between root command and arguments. root, args = self.parse(split_command) # Check if the command instructs to terminate. if root in ('exit', 'quit'): self.stop() continue try: # If the root command is part of the embedded commands list we # execute it. if root in self.cmd.commands: self.cmd.commands[root]['obj'](*args) del(self.cmd.output[:]) # If the root command is part of loaded modules, we initialize # the module and execute it. elif root in __modules__: module = __modules__[root]['obj']() module.set_commandline(args) module.run() if cfg.modules.store_output and __sessions__.is_set(): try: Database().add_analysis(__sessions__.current.file.sha256, split_command, module.output) except Exception: pass del(module.output[:]) else: print("Command not recognized.") except KeyboardInterrupt: pass except Exception: print_error("The command {0} raised an exception:".format(bold(root))) traceback.print_exc() console_output['filename'] = None # reset output to stdout
def run(self, *args): try: args = self.parser.parse_args(args) except SystemExit: return if __config__.get('paths').storage_path: base_path = __config__.get('paths').storage_path else: base_path = os.path.join(expanduser("~"), '.viper') projects_path = os.path.join(base_path, 'projects') if args.list: if not os.path.exists(projects_path): self.log('info', "The projects directory does not exist yet") return self.log('info', "Projects Available:") rows = [] for project in os.listdir(projects_path): project_path = os.path.join(projects_path, project) if os.path.isdir(project_path): current = '' if __project__.name and project == __project__.name: current = 'Yes' rows.append([project, time.ctime(os.path.getctime(project_path)), current]) self.log('table', dict(header=['Project Name', 'Creation Time', 'Current'], rows=rows)) elif args.switch: if __sessions__.is_set(): __sessions__.close() self.log('info', "Closed opened session") __project__.open(args.switch) self.log('info', "Switched to project {0}".format(bold(args.switch))) # Need to re-initialize the Database to open the new SQLite file. Database().__init__() elif args.close: if __project__.name != "default": if __sessions__.is_set(): __sessions__.close() __project__.close() elif args.delete: project_to_delete = args.delete if project_to_delete == "default": self.log('error', "You can't delete the \"default\" project") return # If it's the currently opened project, we close it. if project_to_delete == __project__.name: # We close any opened session. if __sessions__.is_set(): __sessions__.close() __project__.close() project_path = os.path.join(projects_path, project_to_delete) if not os.path.exists(project_path): self.log('error', "The folder for project \"{}\" does not seem to exist".format(project_to_delete)) return self.log('info', "You asked to delete project with name \"{}\" located at \"{}\"".format(project_to_delete, project_path)) confirm = input("Are you sure you want to delete the project? You will permanently delete all associated files! [y/N] ") if confirm.lower() != 'y': return try: shutil.rmtree(project_path) except Exception as e: self.log('error', "Something failed while trying to delete folder: {}".format(e)) return self.log('info', "Project \"{}\" was delete successfully".format(project_to_delete)) else: self.log('info', self.parser.print_usage())
def run(self, *args): try: args = self.parser.parse_args(args) except SystemExit: return if __config__.get("paths").storage_path: base_path = __config__.get("paths").storage_path else: base_path = os.path.join(expanduser("~"), ".viper") projects_path = os.path.join(base_path, "projects") if args.list: if not os.path.exists(projects_path): self.log("info", "No projects have been created yet") return self.log("info", "Projects Available:") rows = [] for project in os.listdir(projects_path): project_path = os.path.join(projects_path, project) if os.path.isdir(project_path): current = "" if __project__.name and project == __project__.name: current = "Yes" rows.append([ project, time.ctime(os.path.getctime(project_path)), current ]) self.log( "table", dict(header=["Project Name", "Creation Time", "Current"], rows=rows)) elif args.switch: if __sessions__.is_set(): __sessions__.close() self.log("info", "Closed opened session") __project__.open(args.switch) self.log("info", "Switched to project {0}".format(bold(args.switch))) # Need to re-initialize the Database to open the new SQLite file. Database().__init__() elif args.close: if __project__.name != "default": if __sessions__.is_set(): __sessions__.close() __project__.close() elif args.delete: project_to_delete = args.delete if project_to_delete == "default": self.log("error", "You can't delete the \"default\" project") return # If it"s the currently opened project, we close it. if project_to_delete == __project__.name: # We close any opened session. if __sessions__.is_set(): __sessions__.close() __project__.close() project_path = os.path.join(projects_path, project_to_delete) if not os.path.exists(project_path): self.log( "error", "The folder for project \"{}\" does not seem to exist". format(project_to_delete)) return self.log( "info", "You asked to delete project with name \"{}\" located at \"{}\"" .format(project_to_delete, project_path)) confirm = input( "Are you sure you want to delete the project? You will permanently delete all associated files! [y/N] " ) if confirm.lower() != "y": return try: shutil.rmtree(project_path) except Exception as e: self.log( "error", "Something failed while trying to delete folder: {}". format(e)) return self.log( "info", "Project \"{}\" was delete successfully".format( project_to_delete)) else: self.log("info", self.parser.print_usage())
def start(self, sha256): # Logo. logo() # Setup shell auto-complete. def complete(text, state): # Try to autocomplete commands. cmds = [i for i in self.cmd.commands if i.startswith(text)] if state < len(cmds): return cmds[state] # Try to autocomplete modules. mods = [i for i in __modules__ if i.startswith(text)] if state < len(mods): return mods[state] # Then autocomplete paths. if text.startswith("~"): text = "{0}{1}".format(os.getenv("HOME"), text[1:]) return (glob.glob(text+'*')+[None])[state] # Auto-complete on tabs. readline.set_completer_delims(' \t\n;') readline.parse_and_bind('tab: complete') readline.set_completer(complete) # Save commands in history file. def save_history(path): readline.write_history_file(path) # If there is an history file, read from it and load the history # so that they can be loaded in the shell. # Now we are storing the history file in the local project folder history_path = os.path.join(os.getenv("HOME"), '.viperHistory') if os.path.exists(history_path): readline.read_history_file(history_path) # Register the save history at program's exit. atexit.register(save_history, path=history_path) # Main loop. while self.active: # It's a prompt. There it is. prompt = cyan('viper > ', True) # Wait for input from the user. try: data = input(prompt).strip() except KeyboardInterrupt: print("") # Terminate on EOF. except EOFError: self.stop() print("") continue # Parse the input if the user provided any. else: # Skip if the input is empty. if not data: continue # Check for output redirection # If there is a > in the string, we assume the user wants to output to file. filename = False if '>' in data: data, filename = data.split('>') # If the input starts with an exclamation mark, we treat the # input as a bash command and execute it. # At this point the keywords should be replaced. if data.startswith('!'): os.system(data[1:]) continue # !!!!THIS IS THE LAZY COMMAND # If the input starts with a period it's an API call # so we shell out(this is a client, who gives a f**k) # to curl and jq in this format: viper > .find tag=elf # Also the trigger to switch hashes: .use sha256hashhere if data.startswith('.'): if data[1:].find('find') != -1: tag = data.split(' ') os.system('curl -F ' + str(tag[1]) + ' http://' + host + ':' + port + '/file/find | jq .') if data[1:].find('use') != -1: sha256 = data.split(' ')[1] continue # Try to split commands by ; so that you can sequence multiple # commands at once. # For example: # viper > find name *.pdf; open --last 1; pdf id # This will automatically search for all PDF files, open the first entry # and run the pdf module against it. split_commands = data.split(';') for split_command in split_commands: split_command = split_command.strip() if not split_command: continue # Check if the command instructs to terminate. if split_command in ('exit', 'quit'): self.stop() continue try: # call the Viper API POST method for cmdline viperCommand(sha256=sha256, command=split_command) except KeyboardInterrupt: pass except Exception: print_error("The command {0} raised an exception:".format(bold(root))) traceback.print_exc()
def run(self): if not __session__.is_set(): print_error("No session opened") return if not HAVE_PYDEEP: print_error("Missing dependency, install pydeep (`pip install pydeep`)") return if not __session__.file.ssdeep: print_error("No ssdeep hash available for opened file") return def usage(): print("usage: fuzzy [-v]") def help(): usage() print("") print("Options:") print("\t--help (-h)\tShow this help message") print("\t--verbose (-v)\tPrints verbose logging") print("") arg_verbose = False try: opts, argv = getopt.getopt(self.args[0:], 'hv', ['help', 'verbose']) except getopt.GetoptError as e: print(e) return for opt, value in opts: if opt in ('-h', '--help'): help() return elif opt in ('-v', '--verbose'): arg_verbose = True db = Database() samples = db.find(key='all') matches = [] for sample in samples: if sample.sha256 == __session__.file.sha256: continue if not sample.ssdeep: continue score = pydeep.compare(__session__.file.ssdeep, sample.ssdeep) if score > 40: matches.append(['{0}%'.format(score), sample.name, sample.sha256]) if arg_verbose: print("Match {0}%: {2} [{1}]".format(score, sample.name, sample.sha256)) print_info("{0} relevant matches found".format(bold(len(matches)))) if len(matches) > 0: print(table(header=['Score', 'Name', 'SHA256'], rows=matches))
def cmd_notes(self, *args): parser = argparse.ArgumentParser(prog="notes", description="Show information on the opened file") group = parser.add_mutually_exclusive_group() group.add_argument('-l', '--list', action='store_true', help="List all notes available for the current file") group.add_argument('-a', '--add', action='store_true', help="Add a new note to the current file") group.add_argument('-v', '--view', metavar='NOTE ID', type=int, help="View the specified note") group.add_argument('-e', '--edit', metavar='NOTE ID', type=int, help="Edit an existing note") group.add_argument('-d', '--delete', metavar='NOTE ID', type=int, help="Delete an existing note") try: args = parser.parse_args(args) except: return if not __sessions__.is_set(): self.log('error', "No open session") return # check if the file is already stores, otherwise exit as no notes command will work if the file is not stored in the database malware = Database().find(key='sha256', value=__sessions__.current.file.sha256) if not malware: self.log('error', "The opened file doesn't appear to be in the database, have you stored it yet?") return if args.list: # Retrieve all notes for the currently opened file. notes = malware[0].note if not notes: self.log('info', "No notes available for this file yet") return # Populate table rows. rows = [[note.id, note.title] for note in notes] # Display list of existing notes. self.log('table', dict(header=['ID', 'Title'], rows=rows)) elif args.add: title = input("Enter a title for the new note: ") # Create a new temporary file. tmp = tempfile.NamedTemporaryFile(delete=False) # Open the temporary file with the default editor, or with nano. os.system('"${EDITOR:-nano}" ' + tmp.name) # Once the user is done editing, we need to read the content and # store it in the database. body = tmp.read() Database().add_note(__sessions__.current.file.sha256, title, body) # Finally, remove the temporary file. os.remove(tmp.name) self.log('info', "New note with title \"{0}\" added to the current file".format(bold(title))) elif args.view: # Retrieve note wth the specified ID and print it. note = Database().get_note(args.view) if note: self.log('info', bold('Title: ') + note.title) self.log('info', bold('Body:') + '\n' + note.body) else: self.log('info', "There is no note with ID {0}".format(args.view)) elif args.edit: # Retrieve note with the specified ID. note = Database().get_note(args.edit) if note: # Create a new temporary file. tmp = tempfile.NamedTemporaryFile(delete=False) # Write the old body to the temporary file. tmp.write(note.body) tmp.close() # Open the old body with the text editor. os.system('"${EDITOR:-nano}" ' + tmp.name) # Read the new body from the temporary file. body = open(tmp.name, 'r').read() # Update the note entry with the new body. Database().edit_note(args.edit, body) # Remove the temporary file. os.remove(tmp.name) self.log('info', "Updated note with ID {0}".format(args.edit)) elif args.delete: # Delete the note with the specified ID. Database().delete_note(args.delete) else: parser.print_usage()
def print_warning(message): print(bold(yellow("[!]")) + " {0}".format(message))