def pehash(self): if not HAVE_PEHASH: self.log('error', "PEhash is missing. Please copy PEhash to the modules directory of Viper") return current_pehash = None if __sessions__.is_set(): current_pehash = calculate_pehash(__sessions__.current.file.path) self.log('info', "PEhash: {0}".format(bold(current_pehash))) if self.args.all or self.args.cluster or self.args.scan: db = Database() samples = db.find(key='all') rows = [] for sample in samples: sample_path = get_sample_path(sample.sha256) pe_hash = calculate_pehash(sample_path) if pe_hash: rows.append((sample.name, sample.md5, pe_hash)) if self.args.all: self.log('info', "PEhash for all files:") header = ['Name', 'MD5', 'PEhash'] self.log('table', dict(header=header, rows=rows)) elif self.args.cluster: self.log('info', "Clustering files by PEhash...") cluster = {} for sample_name, sample_md5, pe_hash in rows: cluster.setdefault(pe_hash, []).append([sample_name, sample_md5]) for item in cluster.items(): if len(item[1]) > 1: self.log('info', "PEhash cluster {0}:".format(bold(item[0]))) self.log('table', dict(header=['Name', 'MD5'], rows=item[1])) elif self.args.scan: if __sessions__.is_set() and current_pehash: self.log('info', "Finding matching samples...") matches = [] for row in rows: if row[1] == __sessions__.current.file.md5: continue if row[2] == current_pehash: matches.append([row[0], row[1]]) if matches: self.log('table', dict(header=['Name', 'MD5'], rows=matches)) else: self.log('info', "No matches found")
def cmd_investigations(self, *args): parser = argparse.ArgumentParser(prog='investigations', description="Open a case", epilog="List or switch current investigations") group = parser.add_mutually_exclusive_group() group.add_argument('-l', '--list', action='store_true', help="List all existing investigations") group.add_argument('-s', '--switch', metavar='NAME', help="Switch to the specified investigation") group.add_argument('-d', '--delete', type=int, metavar='ID', help="delete investigation by id.") try: args = parser.parse_args(args) except: return projects_path = os.path.join(os.getcwd(), 'investigations') if not os.path.exists(projects_path): self.log('info', "The investigations directory does not exist yet") return if args.list: self.log('info', "Current Investigations:") rows = [] items = self.db.get_investigation_list() # Populate the list of search results. count = 1 for item in items: row = [item.id, item.name] rows.append(row) self.log('table', dict(header=['ID', 'Name'], rows=rows)) elif args.switch: if __sessions__.is_set(): __sessions__.close() self.log('info', "Closed opened session") __project__.open(args.switch, self.db) self.log('info', "Switched to investigation {0}".format(bold(args.switch))) # Need to re-initialize the Database to open the new SQLite file. self.db = Database() elif args.delete: if __sessions__.is_set(): __sessions__.close() self.log('info', "Closed opened session") __project__.delete(args.delete, self.db) self.log('info', "Deleted investigation {0}".format(bold(args.delete))) # 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): super(Exif, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return if not HAVE_EXIF: self.log('error', "Missing dependency, install pyexiftool") return try: with exiftool.ExifTool() as et: metadata = et.get_metadata(__sessions__.current.file.path) except OSError: self.log('error', "Exiftool is not installed") return rows = [] for key, value in metadata.items(): rows.append([key, value]) rows = sorted(rows, key=lambda entry: entry[0]) self.log('info', "MetaData:") self.log('table', dict(header=['Key', 'Value'], rows=rows))
def upload(self): if not __sessions__.is_set(): self.log('error', "No session opened") return False categ = self.categories.get(self.args.categ) if self.args.info is not None: info = ' '.join(self.args.info) else: info = None if __sessions__.current.misp_event and self.args.event is None: event = __sessions__.current.misp_event.event_id else: event = None try: out = self.misp.upload_sample(__sessions__.current.file.name, __sessions__.current.file.path, event, self.args.distrib, self.args.ids, categ, info, self.args.analysis, self.args.threat) except Exception as e: self.log('error', e) return result = out.json() if out.status_code == 200: if result.get('errors') is not None: self.log('error', result.get('errors')[0]['error']['value'][0]) else: if event is not None: full_event = self.misp.get_event(event) return __sessions__.new(misp_event=MispEvent(full_event.json())) # TODO: also open a session when upload_sample created a new event # (the response doesn't contain the event ID) # __sessions__.new(misp_event=MispEvent(result)) self.log('success', "File uploaded sucessfully") else: self.log('error', result.get('message'))
def run(self): super(Reports, self).run() if self.args is None: return if not HAVE_REQUESTS and not HAVE_BS4: self.log('error', "Missing dependencies (`pip install requests beautifulsoup4`)") return if not __sessions__.is_set(): self.log('error', "No session opened") return if self.args.malwr: self.malwr() elif self.args.anubis: self.anubis() elif self.args.threat: self.threat() elif self.args.joe: self.joe() elif self.args.meta: self.meta() elif self.args.wildfire: self.wildfire() else: self.log('error', 'At least one of the parameters is required') self.usage()
def get_config(self, family): if not __sessions__.is_set(): self.log('error', "No session opened") return try: module = importlib.import_module('modules.rats.{0}'.format(family)) except ImportError: self.log('error', "There is no module for family {0}".format(bold(family))) return config = module.config(__sessions__.current.file.data) if not config: self.log('error', "No Configuration Detected") return rows = [] for key, value in config.items(): rows.append([key, value]) rows = sorted(rows, key=lambda entry: entry[0]) self.log('info', "Configuration:") self.log('table', dict(header=['Key', 'Value'], rows=rows))
def run(self): super(Reports, self).run() if self.args is None: return if not HAVE_REQUESTS and not HAVE_BS4: self.log( 'error', "Missing dependencies (`pip install requests beautifulsoup4`)") return if not __sessions__.is_set(): self.log('error', "No session opened") return if self.args.malwr: self.malwr() elif self.args.anubis: self.anubis() elif self.args.threat: self.threat() elif self.args.joe: self.joe() elif self.args.meta: self.meta() elif self.args.wildfire: self.wildfire() else: self.log('error', 'At least one of the parameters is required') self.usage()
def run(self): super(Editdistance, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return self.edit()
def keywords(self, data): # Check if $self is in the user input data. if '$self' in data: # Check if there is an open session. if __sessions__.is_set(): # If a session is opened, replace $self with the path to # the file which is currently being analyzed. data = data.replace('$self', __sessions__.current.file.path) else: print("No session opened") return None return data
def __check_session(self): if not __sessions__.is_set(): self.log('error', "No session opened") return False if not self.pe: try: self.pe = pefile.PE(__sessions__.current.file.path) except pefile.PEFormatError as e: self.log('error', "Unable to parse PE file: {0}".format(e)) return False return True
def cmd_export(self, *args): parser = argparse.ArgumentParser(prog='export', description="Export the current session to file or zip") parser.add_argument('-z', '--zip', action='store_true', help="Export session in a zip archive") parser.add_argument('value', help="path or archive name") try: args = parser.parse_args(args) except: return # This command requires a session to be opened. if not __sessions__.is_set(): self.log('error', "No session opened") parser.print_usage() return # Check for valid export path. if args.value is None: parser.print_usage() return # TODO: having for one a folder and for the other a full # target path can be confusing. We should perhaps standardize this. # Abort if the specified path already exists. if os.path.isfile(args.value): self.log('error', "File at path \"{0}\" already exists, abort".format(args.value)) return # If the argument chosed so, archive the file when exporting it. # TODO: perhaps add an option to use a password for the archive # and default it to "infected". if args.zip: try: with ZipFile(args.value, 'w') as export_zip: export_zip.write(__sessions__.current.file.path, arcname=__sessions__.current.file.name) except IOError as e: self.log('error', "Unable to export file: {0}".format(e)) else: self.log('info', "File archived and exported to {0}".format(args.value)) # Otherwise just dump it to the given directory. else: # XXX: Export file with the original file name. store_path = os.path.join(args.value, __sessions__.current.file.name) try: shutil.copyfile(__sessions__.current.file.path, store_path) except IOError as e: self.log('error', "Unable to export file: {0}".format(e)) else: self.log('info', "File exported to {0}".format(store_path))
def run(self): super(Cuckoo, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return if not HAVE_REQUESTS: self.log( 'error', "Missing dependency, install requests (`pip install requests`)" ) return host = self.args.host port = self.args.port url = 'http://{0}:{1}/tasks/create/file'.format(host, port) files = dict(file=open(__sessions__.current.file.path, 'rb')) try: response = requests.post(url, files=files) except requests.ConnectionError: self.log('error', "Unable to connect to Cuckoo API at '{0}'.".format(url)) return except Exception as e: self.log('error', "Failed performing request at '{0}': {1}".format(url, e)) return try: parsed_response = response.json() except Exception as e: try: parsed_response = response.json except Exception as e: self.log('error', "Failed parsing the response: {0}".format(e)) self.log('error', "Data:\n{}".format(response.content)) return if 'task_id' in parsed_response: self.log('info', "Task ID: {0}".format(parsed_response['task_id'])) else: self.log( 'error', "Failed to parse the task id from the returned JSON ('{0}'): {1}" .format(parsed_response, e))
def cmd_tags(self, *args): parser = argparse.ArgumentParser(prog='tags', description="Modify tags of the opened file") parser.add_argument('-a', '--add', metavar='TAG', help="Add tags to the opened file (comma separated)") parser.add_argument('-d', '--delete', metavar='TAG', help="Delete a tag from the opened file") try: args = parser.parse_args(args) except: return # This command requires a session to be opened. if not __sessions__.is_set(): self.log('error', "No session opened") parser.print_usage() return # If no arguments are specified, there's not much to do. # However, it could make sense to also retrieve a list of existing # tags from this command, and not just from the "find" command alone. if args.add is None and args.delete is None: parser.print_usage() return # TODO: handle situation where addition or deletion of a tag fail. db = Database() if not db.find(key='sha256', value=__sessions__.current.file.sha256): self.log('error', "The opened file is not stored in the database. " "If you want to add it use the `store` command.") return if args.add: # Add specified tags to the database's entry belonging to # the opened file. db.add_tags(__sessions__.current.file.sha256, args.add) self.log('info', "Tags added to the currently opened file") # We refresh the opened session to update the attributes. # Namely, the list of tags returned by the 'info' command # needs to be re-generated, or it wouldn't show the new tags # until the existing session is closed a new one is opened. self.log('info', "Refreshing session to update attributes...") __sessions__.new(__sessions__.current.file.path) if args.delete: # Delete the tag from the database. db.delete_tag(args.delete, __sessions__.current.file.sha256) # Refresh the session so that the attributes of the file are # updated. self.log('info', "Refreshing session to update attributes...") __sessions__.new(__sessions__.current.file.path)
def run(self): super(Image, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return if self.args.ghiro: self.ghiro() else: self.log('error', 'At least one of the parameters is required') self.usage()
def __check_session(self): if not __sessions__.is_set(): self.log('error', "No session opened") return False if not self.elf: try: fd = open(__sessions__.current.file.path, 'rb') self.elf = ELFFile(fd) except: self.log('error', "Unable to parse ELF file") return False return True
def run(self): super(SWF, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return arg_dump = self.args.dump if arg_dump is None: arg_dump = tempfile.gettempdir() self.decompress(arg_dump)
def show(self): if not __sessions__.is_set(): self.log('error', "No session opened") return False if not __sessions__.current.misp_event: self.log('error', "Not attached to a MISP event") return False current_event = copy.deepcopy(__sessions__.current.misp_event.event) header = ['type', 'value', 'comment'] rows = [] for a in current_event['Event']['Attribute']: rows.append([a['type'], a['value'], a['comment']]) self.log('table', dict(header=header, rows=rows))
def auto(self): if not HAVE_YARA: self.log('error', "Missing dependency, install yara (see http://plusvic.github.io/yara/)") return if not __sessions__.is_set(): self.log('error', "No session opened") return rules = yara.compile(os.path.join(CIRTKIT_ROOT, 'data/yara/rats.yara')) for match in rules.match(__sessions__.current.file.path): if 'family' in match.meta: self.log('info', "Automatically detected supported RAT {0}".format(match.rule)) self.get_config(match.meta['family']) return self.log('info', "No known RAT detected")
def run(self): def read_manifest(manifest): rows = [] lines = manifest.split('\r\n') for line in lines: if len(line) > 1: item, value = line.split(':') rows.append([item, value]) self.log('info', "Manifest File:") self.log('table', dict(header=['Item', 'Value'], rows=rows)) super(Jar, self).run() if self.args is None: return arg_dump = self.args.dump if not __sessions__.is_set(): self.log('error', "No session opened") return if not zipfile.is_zipfile(__sessions__.current.file.path): self.log('error', "Doesn't Appear to be a valid jar archive") return with zipfile.ZipFile(__sessions__.current.file.path, 'r') as archive: jar_tree = [] for name in archive.namelist(): item_data = archive.read(name) if name == 'META-INF/MANIFEST.MF': read_manifest(item_data) item_md5 = hashlib.md5(item_data).hexdigest() jar_tree.append([name, item_md5]) self.log('info', "Jar Tree:") self.log('table', dict(header=['Java File', 'MD5'], rows=jar_tree)) if arg_dump: archive.extractall(arg_dump) self.log('info', "Archive content extracted to {0}".format(arg_dump))
def run(self): super(Cuckoo, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return if not HAVE_REQUESTS: self.log('error', "Missing dependency, install requests (`pip install requests`)") return host = self.args.host port = self.args.port url = 'http://{0}:{1}/tasks/create/file'.format(host, port) files = dict(file=open(__sessions__.current.file.path, 'rb')) try: response = requests.post(url, files=files) except requests.ConnectionError: self.log('error', "Unable to connect to Cuckoo API at '{0}'.".format(url)) return except Exception as e: self.log('error', "Failed performing request at '{0}': {1}".format(url, e)) return try: parsed_response = response.json() except Exception as e: try: parsed_response = response.json except Exception as e: self.log('error', "Failed parsing the response: {0}".format(e)) self.log('error', "Data:\n{}".format(response.content)) return if 'task_id' in parsed_response: self.log('info', "Task ID: {0}".format(parsed_response['task_id'])) else: self.log('error', "Failed to parse the task id from the returned JSON ('{0}'): {1}".format(parsed_response, e))
def cmd_info(self, *args): if __sessions__.is_set(): self.log('table', dict( header=['Key', 'Value'], rows=[ ['Name', __sessions__.current.file.name], ['Tags', __sessions__.current.file.tags], ['Path', __sessions__.current.file.path], ['Size', __sessions__.current.file.size], ['Type', __sessions__.current.file.type], ['Mime', __sessions__.current.file.mime], ['MD5', __sessions__.current.file.md5], ['SHA1', __sessions__.current.file.sha1], ['SHA256', __sessions__.current.file.sha256], ['SHA512', __sessions__.current.file.sha512], ['SSdeep', __sessions__.current.file.ssdeep], ['CRC32', __sessions__.current.file.crc32] ] ))
def run(self): super(PDF, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return False if 'PDF' not in __sessions__.current.file.type: self.log('error', "The opened file doesn't appear to be a PDF document") return if self.args.subname == 'id': self.pdf_id() elif self.args.subname == 'streams': self.streams() else: self.log('error', 'At least one of the parameters is required') self.usage()
def cmd_delete(self, *args): if __sessions__.is_set(): while True: choice = input("Are you sure you want to delete this binary? Can't be reverted! [y/n] ") if choice == 'y': break elif choice == 'n': return rows = self.db.find('sha256', __sessions__.current.file.sha256) if rows: malware_id = rows[0].id if self.db.delete_file(malware_id): self.log("success", "File deleted") else: self.log('error', "Unable to delete file") os.remove(__sessions__.current.file.path) __sessions__.close() else: self.log('error', "No session opened")
def run(self): super(Radare, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return if self.args.webserver: self.server = "-c=H" filetype = __sessions__.current.file.type if 'x86-64' in filetype: self.is_64b = True arch = '64' if self.is_64b else '32' if 'DLL' in filetype: self.ext = '.dll' to_print = [arch, 'bit DLL (Windows)'] if "native" in filetype: to_print.append('perhaps a driver (.sys)') self.log('info', ' '.join(to_print)) elif 'PE32' in filetype: self.ext = '.exe' self.log('info', ' '.join([arch, 'bit executable (Windows)'])) elif 'shared object' in filetype: self.ext = '.so' self.log('info', ' '.join([arch, 'bit shared object (linux)'])) elif 'ELF' in filetype: self.ext = '' self.log('info', ' '.join([arch, 'bit executable (linux)'])) else: self.log('error', "Unknown binary") try: self.open_radare(__sessions__.current.file.path) except: self.log('error', "Unable to start Radare2")
def download(self): ok = False data = None if self.args.event: ok, data = self.misp.download_samples(event_id=self.args.event) elif self.args.hash: ok, data = self.misp.download_samples(sample_hash=self.args.hash) else: # Download from current MISP event if possible if not __sessions__.is_set(): self.log('error', "No session opened") return False if not __sessions__.current.misp_event: self.log('error', "Not connected to a MISP event.") return False ok, data = self.misp.download_samples( event_id=__sessions__.current.misp_event.event_id) if not ok: self.log('error', data) return to_print = [] for d in data: eid, filename, payload = d path = os.path.join(tempfile.gettempdir(), filename) with open(path, 'w') as f: f.write(payload.getvalue()) to_print.append((eid, path)) if len(to_print) == 1: self.log( 'success', 'The sample has been downloaded from Event {}'.format( to_print[0][0])) event = self.misp.get_event(to_print[0][0]) return __sessions__.new(to_print[0][1], MispEvent(event.json())) else: self.log('success', 'The following files have been downloaded:') for p in to_print: self.log('success', '\tEventID: {} - {}'.format(*p))
def auto(self): if not HAVE_YARA: self.log( 'error', "Missing dependency, install yara (see http://plusvic.github.io/yara/)" ) return if not __sessions__.is_set(): self.log('error', "No session opened") return rules = yara.compile(os.path.join(CIRTKIT_ROOT, 'data/yara/rats.yara')) for match in rules.match(__sessions__.current.file.path): if 'family' in match.meta: self.log( 'info', "Automatically detected supported RAT {0}".format( match.rule)) self.get_config(match.meta['family']) return self.log('info', "No known RAT detected")
def upload(self): if not __sessions__.is_set(): self.log('error', "No session opened") return False categ = self.categories.get(self.args.categ) if self.args.info is not None: info = ' '.join(self.args.info) else: info = None if __sessions__.current.misp_event and self.args.event is None: event = __sessions__.current.misp_event.event_id else: event = None try: out = self.misp.upload_sample(__sessions__.current.file.name, __sessions__.current.file.path, event, self.args.distrib, self.args.ids, categ, info, self.args.analysis, self.args.threat) except Exception as e: self.log('error', e) return result = out.json() if out.status_code == 200: if result.get('errors') is not None: self.log('error', result.get('errors')[0]['error']['value'][0]) else: if event is not None: full_event = self.misp.get_event(event) return __sessions__.new( misp_event=MispEvent(full_event.json())) # TODO: also open a session when upload_sample created a new event # (the response doesn't contain the event ID) # __sessions__.new(misp_event=MispEvent(result)) self.log('success', "File uploaded sucessfully") else: self.log('error', result.get('message'))
def download(self): ok = False data = None if self.args.event: ok, data = self.misp.download_samples(event_id=self.args.event) elif self.args.hash: ok, data = self.misp.download_samples(sample_hash=self.args.hash) else: # Download from current MISP event if possible if not __sessions__.is_set(): self.log('error', "No session opened") return False if not __sessions__.current.misp_event: self.log('error', "Not connected to a MISP event.") return False ok, data = self.misp.download_samples(event_id=__sessions__.current.misp_event.event_id) if not ok: self.log('error', data) return to_print = [] for d in data: eid, filename, payload = d path = os.path.join(tempfile.gettempdir(), filename) with open(path, 'w') as f: f.write(payload.getvalue()) to_print.append((eid, path)) if len(to_print) == 1: self.log('success', 'The sample has been downloaded from Event {}'.format(to_print[0][0])) event = self.misp.get_event(to_print[0][0]) return __sessions__.new(to_print[0][1], MispEvent(event.json())) else: self.log('success', 'The following files have been downloaded:') for p in to_print: self.log('success', '\tEventID: {} - {}'.format(*p))
def run(self): super(Strings, self).run() if self.args is None: return arg_all = self.args.all arg_hosts = self.args.hosts if not __sessions__.is_set(): self.log('error', "No session opened") return if os.path.exists(__sessions__.current.file.path): regexp = '[\x20\x30-\x39\x41-\x5a\x61-\x7a\-\.:]{4,}' strings = re.findall(regexp, __sessions__.current.file.data) if arg_all: for entry in strings: self.log('', entry) elif arg_hosts: self.extract_hosts(strings) else: self.log('error', 'At least one of the parameters is required') self.usage()
def run(self): super(Macho, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return if not HAVE_MACHO: self.log('error', "Missing dependency") return # List general info def macho_headers(m): self.log('info', "Headers: ") magic="magic : 0x%x %s" % (m.header.magic, "- " + m.header.display_magic()) self.log('item', magic) cputype="cputype : 0x%x %s" % (m.header.cputype, "- " + m.header.display_cputype()) self.log('item', cputype) cpu_subtype="cpusubtype : 0x%s" % (m.header.cpusubtype) self.log('item', cpu_subtype) filetype="filetype : 0x%x %s" % (m.header.filetype, "- " + m.header.display_filetype()) self.log('item', filetype) ncmds="ncmds : %d" % (m.header.ncmds) self.log('item', ncmds) sizeofcmds="sizeofcmds : %d bytes" % (m.header.sizeofcmds) self.log('item', sizeofcmds) flags="flags : 0x%x %s" % (m.header.flags, "- " + ", ".join(m.header.display_flags())) self.log('item',flags) if m.header.is_64(): reserved="reserved : 0x%x" % (m.header.reserved) self.log('item',reserved) #self.log('item', "filetype: 0x{0}".format(m.header.display_filetype())) #self.log('item', "ncmds: 0x{0}".format(m.header.ncmds)) #print all load commands #TODO replace display method def macho_load_commands(m): load_commands=" Load Commands (%d)" % len(m.commands) self.log('info', load_commands) for lc in m.commands: lc.display("\t") #print all segments #TODO replace display method def macho_segments(m): segments=" Segments (%d)" % len(m.segments) self.log('info', segments) for segment in m.segments: segment.display(before="\t") try: m = MachO(__sessions__.current.file.path) except Exception as e: self.log('error', "No Mach0 file, {0}".format(e)) return if self.args is None: return elif self.args.all: macho_headers(m) macho_segments(m) macho_load_commands(m) elif self.args.headers: macho_headers(m) elif self.args.segments: macho_segments(m) elif self.args.load_commands: macho_load_commands(m)
def run(self): super(Shellcode, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return collection = [ { 'description': 'FS:[30h] shellcode', 'patterns': [ b'\x64\xa1\x30\x00|\x64\x8b\x0d\x30|\x64\x8b\x0d\x30|\x64\x8b\x15\x30|\x64\x8b\x35\x30|\x64\x8b\x3d\x30|\x6a\x30.\x64\x8b|\x33..\xb3\x64\x8b', '64a13000|648b0d30|648b0d30|648b1530|648b3530|648b3d30|6a30..648b|33....b3648b' ] }, { 'description': 'FS:[00h] shellcode', 'patterns': [ b'\x64\x8b\x1d|\x64\xa1\x00|\x64\x8b\x0d|\x64\x8b\x15|\x64\x8b\x35|\x64\x8b\x3d', '648b1d00|64a10000|648b0d00|648b1500|648b3500|648b3d00' ] }, { 'description': 'API hashing', 'patterns': [ b'\x74.\xc1.\x0d\x03|\x74.\xc1.\x07\x03', '74..c1..0d03|74..c1..0703' ] }, { 'description': 'PUSH DWORD[]/CALL[]', 'patterns': [ b'\x00\xff\x75\x00\xff\x55', '00ff7500ff55' ] }, { 'description': 'FLDZ/FSTENV [esp-12]', 'patterns': [ b'\x00\xd9\x00\xee\x00\xd9\x74\x24\x00\xf4\x00\x00', '00d900ee00d9742400f40000' ] }, { 'description': 'CALL next/POP', 'patterns': [ b'\x00\xe8\x00\x00\x00\x00(\x58|\x59|\x5a|\x5b|\x5e|\x5f|\x5d)\x00\x00', '00e800000000(58|59|5a|5b|5e|5f|5d)0000' ] }, { 'description': 'Function prolog', 'patterns': [ b'\x55\x8b\x00\xec\x83\x00\xc4|\x55\x8b\x0ec\x81\x00\xec|\x55\x8b\x00\xec\x8b|\x55\x8b\x00\xec\xe8|\x55\x8b\x00\xec\xe9', '558b00ec8300c4|558b0ec8100ec|558b00ec8b|558b00ece8|558b00ece9' ] }, ] self.log('info', "Searching for known shellcode patterns...") for entry in collection: for pattern in entry['patterns']: match = re.search(pattern, __sessions__.current.file.data) if match: offset = match.start() self.log('info', "{0} pattern matched at offset {1}".format(entry['description'], offset)) self.log('', cyan(hexdump(__sessions__.current.file.data[offset:], maxlines=15)))
def run(self): super(Debup, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return if not HAVE_OLE: self.log( 'error', "Missing dependency, install olefile (`pip install olefile`)") return # Check for valid OLE if not olefile.isOleFile(__sessions__.current.file.path): self.log('error', "Not a valid BUP File") return # Extract all the contents from the bup file. ole = olefile.OleFileIO(__sessions__.current.file.path) # We know that BUPS are xor'd with 6A which is dec 106 for the decoder # This is the stored file. data = self.xordata(ole.openstream('File_0').read(), 106) # Get the details page data2 = self.xordata(ole.openstream('Details').read(), 106) # Close the OLE ole.close() # Process the details file rows = [] lines = data2.split('\n') for line in lines: if line.startswith('OriginalName'): fullpath = line.split('=')[1] pathsplit = fullpath.split('\\') filename = str(pathsplit[-1][:-1]) try: k, v = line.split('=') rows.append([k, v[:-1]]) # Strip the \r from v except: pass # If we opted to switch session then do that if data and self.args.session: try: tempName = os.path.join('/tmp', filename) with open(tempName, 'w') as temp: temp.write(data) self.log('info', "Switching Session to Embedded File") __sessions__.new(tempName) return except: self.log('error', "Unble to Switch Session") # Else jsut print the date else: self.log('info', "BUP Details:") self.log('table', dict(header=['Description', 'Value'], rows=rows))
def run(self): super(Debup, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return if not HAVE_OLE: self.log('error', "Missing dependency, install olefile (`pip install olefile`)") return # Check for valid OLE if not olefile.isOleFile(__sessions__.current.file.path): self.log('error', "Not a valid BUP File") return # Extract all the contents from the bup file. ole = olefile.OleFileIO(__sessions__.current.file.path) # We know that BUPS are xor'd with 6A which is dec 106 for the decoder # This is the stored file. data = self.xordata(ole.openstream('File_0').read(), 106) # Get the details page data2 = self.xordata(ole.openstream('Details').read(), 106) # Close the OLE ole.close() # Process the details file rows = [] lines = data2.split('\n') for line in lines: if line.startswith('OriginalName'): fullpath = line.split('=')[1] pathsplit = fullpath.split('\\') filename = str(pathsplit[-1][:-1]) try: k, v = line.split('=') rows.append([k, v[:-1]]) # Strip the \r from v except: pass # If we opted to switch session then do that if data and self.args.session: try: tempName = os.path.join('/tmp', filename) with open(tempName, 'w') as temp: temp.write(data) self.log('info', "Switching Session to Embedded File") __sessions__.new(tempName) return except: self.log('error', "Unble to Switch Session") # Else jsut print the date else: self.log('info', "BUP Details:") self.log('table', dict(header=['Description', 'Value'], rows=rows))
def run(self): super(Office, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return if not HAVE_OLE: self.log( 'error', "Missing dependency, install OleFileIO (`pip install olefile`)" ) return file_data = __sessions__.current.file.data if file_data.startswith('<?xml'): OLD_XML = file_data else: OLD_XML = False if file_data.startswith( 'MIME-Version:') and 'application/x-mso' in file_data: MHT_FILE = file_data else: MHT_FILE = False # Tests to check for valid Office structures. OLE_FILE = olefile.isOleFile(__sessions__.current.file.path) XML_FILE = zipfile.is_zipfile(__sessions__.current.file.path) if OLE_FILE: ole = olefile.OleFileIO(__sessions__.current.file.path) elif XML_FILE: zip_xml = zipfile.ZipFile(__sessions__.current.file.path, 'r') elif OLD_XML: pass elif MHT_FILE: pass else: self.log('error', "Not a valid office document") return if self.args.export is not None: if OLE_FILE: self.export(ole, self.args.export) elif XML_FILE: self.xml_export(zip_xml, self.args.export) elif self.args.meta: if OLE_FILE: self.metadata(ole) elif XML_FILE: self.xmlmeta(zip_xml) elif self.args.streams: if OLE_FILE: self.metatimes(ole) elif XML_FILE: self.xmlstruct(zip_xml) elif self.args.oleid: if OLE_FILE: self.oleid(ole) else: self.log('error', "Not an OLE file") elif self.args.vba or self.args.code: self.parse_vba(self.args.code) else: self.log('error', 'At least one of the parameters is required') self.usage()
def run(self): super(HTMLParse, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return try: html_data = open(__sessions__.current.file.path).read() self.soup = BeautifulSoup(html_data) except Exception as e: self.log('error', "Something went wrong: {0}".format(e)) return # Set dump path, none if not set. arg_dump = self.args.dump if self.args.script: scripts, script_content = self.parse_scripts() if arg_dump: self.log('info', "Dumping Output to {0}".format(arg_dump)) for s in script_content: self.dump_output(s, arg_dump, 'Scripts') else: self.log('info', "Scripts:") self.log('table', dict(header=['Type', 'Source', 'Entropy'], rows=scripts)) elif self.args.links: links = self.parse_hrefs() self.log('info', "Links") self.log('info', "Target \t Text") for link in links: self.log('item', "{0}\t {1}".format(link[0], self.string_clean(link[1]))) # iFrames elif self.args.iframe: frames, frame_content = self.parse_iframes() if arg_dump: self.log('info', "Dumping Output to {0}".format(arg_dump)) for f in frame_content: self.dump_output(f, arg_dump, 'iframe') else: self.log('info', "IFrames") self.log('table', dict(header=['Source', 'Size', 'Entropy'], rows=frames)) # Images elif self.args.images: images = self.parse_images() if arg_dump: self.log('info', "Dumping Images to {0}".format(arg_dump)) self.log('error', "Not Implemented Yet") # this will need an extra http request to download the images else: self.log('info', "Images") self.log('table', dict(header=['Source', 'Alt', ], rows=images)) # Embedded elif self.args.embed: java, flash = self.parse_embedded() if arg_dump: self.log('info', "Dumping Embedded Items to {0}".format(arg_dump)) self.log('error', "Not Implemented Yet") # this will need an extra http request to download the images else: if len(java) > 0: self.log('info', "Embedded Java Objects") self.log('table', dict(header=['Archive', 'Code', ], rows=java)) print "" if len(flash) > 0: self.log('info', "Embedded Flash Objects") self.log('table', dict(header=['Swf Src'], rows=flash)) else: self.log('error', 'At least one of the parameters is required') self.usage()
def run(self): super(HTMLParse, self).run() if self.args is None: return if not __sessions__.is_set(): self.log('error', "No session opened") return try: html_data = open(__sessions__.current.file.path).read() self.soup = BeautifulSoup(html_data) except Exception as e: self.log('error', "Something went wrong: {0}".format(e)) return # Set dump path, none if not set. arg_dump = self.args.dump if self.args.script: scripts, script_content = self.parse_scripts() if arg_dump: self.log('info', "Dumping Output to {0}".format(arg_dump)) for s in script_content: self.dump_output(s, arg_dump, 'Scripts') else: self.log('info', "Scripts:") self.log( 'table', dict(header=['Type', 'Source', 'Entropy'], rows=scripts)) elif self.args.links: links = self.parse_hrefs() self.log('info', "Links") self.log('info', "Target \t Text") for link in links: self.log( 'item', "{0}\t {1}".format(link[0], self.string_clean(link[1]))) # iFrames elif self.args.iframe: frames, frame_content = self.parse_iframes() if arg_dump: self.log('info', "Dumping Output to {0}".format(arg_dump)) for f in frame_content: self.dump_output(f, arg_dump, 'iframe') else: self.log('info', "IFrames") self.log( 'table', dict(header=['Source', 'Size', 'Entropy'], rows=frames)) # Images elif self.args.images: images = self.parse_images() if arg_dump: self.log('info', "Dumping Images to {0}".format(arg_dump)) self.log('error', "Not Implemented Yet") # this will need an extra http request to download the images else: self.log('info', "Images") self.log('table', dict(header=[ 'Source', 'Alt', ], rows=images)) # Embedded elif self.args.embed: java, flash = self.parse_embedded() if arg_dump: self.log('info', "Dumping Embedded Items to {0}".format(arg_dump)) self.log('error', "Not Implemented Yet") # this will need an extra http request to download the images else: if len(java) > 0: self.log('info', "Embedded Java Objects") self.log('table', dict(header=[ 'Archive', 'Code', ], rows=java)) print "" if len(flash) > 0: self.log('info', "Embedded Flash Objects") self.log('table', dict(header=['Swf Src'], rows=flash)) else: self.log('error', 'At least one of the parameters is required') self.usage()
def run(self, *args): def string_clean(value): if value: return re.sub("[\n\t\r]", "", value) return "" def parse_ole_msg(ole): stream_dirs = ole.listdir() for stream in stream_dirs: # get stream that contains the email header if stream[0].startswith("__substg1.0_007D"): email_header = ole.openstream(stream).read() if stream[0].endswith("001F"): # Unicode probably needs something better than just stripping \x00 email_header = email_header.replace("\x00", "") # If it came from outlook we may need to trim some lines try: email_header = email_header.split("Version 2.0\x0d\x0a", 1)[1] except: pass # Leaving us an RFC compliant email to parse msg = email.message_from_string(email_header) return msg def parse_ole_attachments(ole): # Hard part now, each part of the attachment is in a seperate stream # need to get a unique stream id for each att # its in the streamname as an 8 digit number. for i in range(20): # arbitrary count of emails. i dont expecet this many stream_number = str(i).zfill(8) stream_name = "__attach_version1.0_#" + stream_number # Unicode try: att_filename = ole.openstream(stream_name + "/__substg1.0_3704001F").read() att_mime = ole.openstream(stream_name + "/__substg1.0_370E001F").read() att_data = ole.openstream(stream_name + "/__substg1.0_37010102").read() att_size = len(att_data) att_md5 = hashlib.md5(att_data).hexdigest() print i, att_size, att_md5, att_filename, att_mime except: pass # ASCII try: att_filename = ole.openstream(stream_name + "/__substg1.0_3704001E").read() att_mime = ole.openstream(stream_name + "/__substg1.0_370E001E").read() att_data = ole.openstream(stream_name + "/__substg1.0_37010102").read() att_size = len(att_data) att_md5 = hashlib.md5(att_data).hexdigest() print i, att_size, att_md5, att_filename, att_mime except: pass def att_session(att_id, msg, ole_flag): att_count = 0 if ole_flag: ole = msg # Hard part now, each part of the attachment is in a seperate stream # need to get a unique stream id for each att # its in the streamname as an 8 digit number. for i in range(20): # arbitrary count of emails. i dont expecet this many stream_number = str(i).zfill(8) stream_name = "__attach_version1.0_#" + stream_number # Unicode try: att_filename = ole.openstream(stream_name + "/__substg1.0_3704001F").read() att_filename = att_filename.replace("\x00", "") att_data = ole.openstream(stream_name + "/__substg1.0_37010102").read() except: pass # ASCII try: att_filename = ole.openstream(stream_name + "/__substg1.0_3704001E").read() att_data = ole.openstream(stream_name + "/__substg1.0_37010102").read() except: pass if i == att_id: self.log("info", "Switching session to {0}".format(att_filename)) tmp_path = os.path.join(tempfile.gettempdir(), att_filename) with open(tmp_path, "w") as tmp: tmp.write(att_data) __sessions__.new(tmp_path) return else: for part in msg.walk(): if part.get_content_type() == "message/rfc822": rfc822 = True else: rfc822 = False if part.get_content_maintype() == "multipart" or not part.get("Content-Disposition") and not rfc822: continue att_count += 1 if att_count == att_id: if rfc822: data = part.as_string() m = re.match("Content-Type: message/rfc822\r?\n\r?\n(.*)", data, flags=re.S) if not m: self.log("error", "Could not extract RFC822 formatted message") return data = m.group(1) att_size = len(data) filename = "rfc822msg_{0}.eml".format(att_size) else: data = part.get_payload(decode=True) filename = part.get_filename() self.log("info", "Switching session to {0}".format(filename)) if data: tmp_path = os.path.join(tempfile.gettempdir(), filename) with open(tmp_path, "w") as tmp: tmp.write(data) __sessions__.new(tmp_path) return def email_envelope(msg): # Envelope self.log("info", "Email envelope:") rows = [ ["Subject", msg.get("Subject")], ["To", msg.get("To")], ["From", msg.get("From")], ["Cc", msg.get("Cc")], ["Bcc", msg.get("Bcc")], ["Date", msg.get("Date")], ] self.log("table", dict(header=["Key", "Value"], rows=rows)) return def email_header(msg): # Headers rows = [] for x in msg.keys(): # Adding Received to ignore list. this has to be handeled separately if there are more then one line if x not in ["Subject", "From", "To", "Date", "Cc", "Bcc", "DKIM-Signature", "Received"]: rows.append([x, string_clean(msg.get(x))]) for x in msg.get_all("Received"): rows.append(["Received", string_clean(x)]) self.log("info", "Email headers:") rows = sorted(rows, key=lambda entry: entry[0]) self.log("table", dict(header=["Key", "Value"], rows=rows)) return def email_trace(msg, verbose): rows = [] if verbose: fields = ["from", "by", "with", "id", "for", "timestamp"] else: fields = ["from", "by", "timestamp"] for x in msg.get_all("Received"): x = string_clean(x) cre = re.compile( """ (?: from \s+ (?P<from>.*?) (?=by|with|id|ID|for|;|$) )? (?: by \s+ (?P<by>.*?) (?=with|id|ID|for|;|$) )? (?: with \s+ (?P<with>.*?) (?=id|ID|for|;|$) )? (?: (id|ID) \s+ (?P<id>.*?) (?=for|;|$) )? (?: for \s+ (?P<for>.*?) (?=;|$) )? (?: \s* ; \s* (?P<timestamp>.*) )? """, flags=re.X | re.I, ) m = cre.search(x) if not m: self.log("error", "Received header regex didn't match") return t = [] for groupname in fields: t.append(string_clean(m.group(groupname))) rows.insert(0, t) self.log("info", "Email path trace:") self.log("table", dict(header=fields, rows=rows)) return def email_spoofcheck(msg, dnsenabled): self.log("info", "Email spoof check:") # test 1: check if From address is the same as Sender, Reply-To, and Return-Path rows = [ ["Sender", string_clean(msg.get("Sender"))], ["From", string_clean(msg.get("From"))], ["Reply-To", string_clean(msg.get("Reply-To"))], ["Return-Path", string_clean(msg.get("Return-Path"))], ] self.log("table", dict(header=["Key", "Value"], rows=rows)) addr = { "Sender": email.utils.parseaddr(string_clean(msg.get("Sender")))[1], "From": email.utils.parseaddr(string_clean(msg.get("From")))[1], "Reply-To": email.utils.parseaddr(string_clean(msg.get("Reply-To")))[1], "Return-Path": email.utils.parseaddr(string_clean(msg.get("Return-Path")))[1], } if addr["From"] == "": self.log("error", "No From address!") return elif addr["Sender"] and (addr["From"] != addr["Sender"]): self.log("warning", "Email FAILED: From address different than Sender") elif addr["Reply-To"] and (addr["From"] != addr["Reply-To"]): self.log("warning", "Email FAILED: From address different than Reply-To") elif addr["Return-Path"] and (addr["From"] != addr["Return-Path"]): self.log("warning", "Email FAILED: From address different than Return-Path") else: self.log("success", "Email PASSED: From address the same as Sender, Reply-To, and Return-Path") # test 2: check to see if first Received: by domain matches sender MX domain if not dnsenabled: self.log("info", "Unable to run Received by / sender check without dnspython available") else: r = msg.get_all("Received")[-1] m = re.search("by\s+(\S*?)(?:\s+\(.*?\))?\s+with", r) if not m: self.log("error", "Received header regex didn't match") return byname = m.group(1) # this can be either a name or an IP m = re.search("(\w+\.\w+|\d+\.\d+\.\d+\.\d+)$", byname) if not m: self.log("error", "Could not find domain or IP in Received by field") return bydomain = m.group(1) domains = [["Received by", bydomain]] # if it's an IP, do the reverse lookup m = re.search("\.\d+$", bydomain) if m: bydomain = str(dns.reversename.from_address(bydomain)).strip(".") domains.append(["Received by reverse lookup", bydomain]) # if the email has a Sender header, use that if addr["Sender"] != "": m = re.search("(\w+\.\w+)$", addr["Sender"]) if not m: self.log("error", "Sender header regex didn't match") return fromdomain = m.group(1) domains.append(["Sender", fromdomain]) # otherwise, use the From header else: m = re.search("(\w+\.\w+)$", addr["From"]) if not m: self.log("error", "From header regex didn't match") return fromdomain = m.group(1) domains.append(["From", fromdomain]) bymatch = False try: mx = dns.resolver.query(fromdomain, "MX") if mx: for rdata in mx: m = re.search("(\w+\.\w+).$", str(rdata.exchange)) if not m: self.log("error", "MX domain regex didn't match") continue domains.append(["MX for " + fromdomain, m.group(1)]) if bydomain == m.group(1): bymatch = True self.log("table", dict(header=["Key", "Value"], rows=domains)) except: domains.append(["MX for " + fromdomain, "not registered in DNS"]) self.log("table", dict(header=["Key", "Value"], rows=domains)) if bymatch: self.log("success", "Email PASSED: Received by domain found in Sender/From MX domains") else: self.log("warning", "Email FAILED: Could not match Received by domain to Sender/From MX") # test 3: look at SPF records rspf = [] results = set() allspf = msg.get_all("Received-SPF") if not allspf: return for spf in allspf: # self.log('info', string_clean(spf)) m = re.search("\s*(\w+)\s+\((.*?):\s*(.*?)\)\s+(.*);", string_clean(spf)) if not m: self.log("error", "Received-SPF regex didn't match") return rspf.append([m.group(2), m.group(1), m.group(3), m.group(4)]) results = results | {m.group(1)} self.log("table", dict(header=["Domain", "Action", "Info", "Additional"], rows=rspf)) if results & {"fail", "softfail"}: self.log("warning", "Email FAILED: Found fail or softfail SPF results") elif results & {"none", "neutral"}: self.log("warning", "Email NEUTRAL: Found none or neutral SPF results") elif results & {"permerror", "temperror"}: self.log("warning", "Email NEUTRAL: Found error condition") elif results & {"pass"}: self.log("success", "Email PASSED: Found SPF pass result") return def email_attachments(msg, ole_flag): # Attachments att_count = 0 rows = [] links = [] if ole_flag: ole = msg # Hard part now, each part of the attachment is in a seperate stream # need to get a unique stream id for each att # its in the streamname as an 8 digit number. for i in range(20): # arbitrary count of emails. i dont expecet this many stream_number = str(i).zfill(8) stream_name = "__attach_version1.0_#" + stream_number # Unicode try: att_filename = ole.openstream(stream_name + "/__substg1.0_3704001F").read() att_mime = ole.openstream(stream_name + "/__substg1.0_370E001F").read() att_data = ole.openstream(stream_name + "/__substg1.0_37010102").read() att_size = len(att_data) att_md5 = hashlib.md5(att_data).hexdigest() rows.append([i, att_filename, att_mime, att_size, att_md5]) att_count += 1 except: pass # ASCII try: att_filename = ole.openstream(stream_name + "/__substg1.0_3704001E").read() att_mime = ole.openstream(stream_name + "/__substg1.0_370E001E").read() att_data = ole.openstream(stream_name + "/__substg1.0_37010102").read() att_size = len(att_data) att_md5 = hashlib.md5(att_data).hexdigest() rows.append([i, att_filename, att_mime, att_size, att_md5]) att_count += 1 except: pass else: # Walk through email string. for part in msg.walk(): content_type = part.get_content_type() if content_type == "multipart": continue if content_type in ("text/plain", "text/html"): part_content = part.get_payload(decode=True) for link in re.findall(r'(https?://[^"<>\s]+)', part_content): if link not in links: links.append(link) if content_type == "message/rfc822": part_content = part.as_string() m = re.match("Content-Type: message/rfc822\r?\n\r?\n(.*)", part_content, flags=re.S) if not m: self.log("error", "Could not extract RFC822 formatted message") return part_content = m.group(1) att_size = len(part_content) att_file_name = "rfc822msg_{0}.eml".format(att_size) att_md5 = hashlib.md5(part_content).hexdigest() att_count += 1 rows.append([att_count, att_file_name, content_type, att_size, att_md5]) continue if not part.get("Content-Disposition"): # These are not attachments. continue att_file_name = part.get_filename() att_size = len(part_content) if not att_file_name: continue att_data = part.get_payload(decode=True) att_md5 = hashlib.md5(att_data).hexdigest() att_count += 1 rows.append([att_count, att_file_name, part.get_content_type(), att_size, att_md5]) self.log("info", "Email attachments (total: {0}):".format(att_count)) if att_count > 0: self.log("table", dict(header=["ID", "FileName", "Content Type", "File Size", "MD5"], rows=rows)) self.log("info", "Email links:") for link in links: self.log("item", link) return # Start Here if not __sessions__.is_set(): self.log("error", "No session opened") return super(EmailParse, self).run(*args) if self.args is None: return # see if we can load the dns library for MX lookup spoof detecton try: import dns.resolver import dns.reversename dnsenabled = True except ImportError: dnsenabled = False # Try to open as an ole msg, if not treat as email string try: ole = olefile.OleFileIO(__sessions__.current.file.path) ole_flag = True except: ole_flag = False email_handle = open(__sessions__.current.file.path) msg = email.message_from_file(email_handle) email_handle.close() if self.args.open is not None: if ole_flag: msg = ole att_session(self.args.open, msg, ole_flag) elif self.args.envelope: if ole_flag: msg = parse_ole_msg(ole) email_envelope(msg) elif self.args.attach: if ole_flag: msg = ole email_attachments(msg, ole_flag) elif self.args.header: if ole_flag: msg = parse_ole_msg(ole) email_header(msg) elif self.args.trace: if ole_flag: msg = parse_ole_msg(ole) email_trace(msg, False) elif self.args.traceall: if ole_flag: msg = parse_ole_msg(ole) email_trace(msg, True) elif self.args.spoofcheck: if ole_flag: msg = parse_ole_msg(ole) email_spoofcheck(msg, dnsenabled) elif self.args.all: if ole_flag: msg = parse_ole_msg(ole) email_envelope(msg) email_header(msg) email_trace(msg, True) email_spoofcheck(msg, dnsenabled) if ole_flag: msg = ole email_attachments(msg, ole_flag) else: self.log("error", "At least one of the parameters is required") self.usage()
def pehash(self): if not HAVE_PEHASH: self.log( 'error', "PEhash is missing. Please copy PEhash to the modules directory of Viper" ) return current_pehash = None if __sessions__.is_set(): current_pehash = calculate_pehash(__sessions__.current.file.path) self.log('info', "PEhash: {0}".format(bold(current_pehash))) if self.args.all or self.args.cluster or self.args.scan: db = Database() samples = db.find(key='all') rows = [] for sample in samples: sample_path = get_sample_path(sample.sha256) pe_hash = calculate_pehash(sample_path) if pe_hash: rows.append((sample.name, sample.md5, pe_hash)) if self.args.all: self.log('info', "PEhash for all files:") header = ['Name', 'MD5', 'PEhash'] self.log('table', dict(header=header, rows=rows)) elif self.args.cluster: self.log('info', "Clustering files by PEhash...") cluster = {} for sample_name, sample_md5, pe_hash in rows: cluster.setdefault(pe_hash, []).append([sample_name, sample_md5]) for item in cluster.items(): if len(item[1]) > 1: self.log('info', "PEhash cluster {0}:".format(bold(item[0]))) self.log('table', dict(header=['Name', 'MD5'], rows=item[1])) elif self.args.scan: if __sessions__.is_set() and current_pehash: self.log('info', "Finding matching samples...") matches = [] for row in rows: if row[1] == __sessions__.current.file.md5: continue if row[2] == current_pehash: matches.append([row[0], row[1]]) if matches: self.log('table', dict(header=['Name', 'MD5'], rows=matches)) else: self.log('info', "No matches found")
def run(self): super(Fuzzy, self).run() if not HAVE_PYDEEP: self.log( 'error', "Missing dependency, install pydeep (`pip install pydeep`)") return arg_verbose = False arg_cluster = False if self.args: if self.args.verbose: arg_verbose = self.args.verbose if self.args.cluster: arg_cluster = self.args.cluster db = Database() samples = db.find(key='all') # Check if we're operating in cluster mode, otherwise we run on the # currently opened file. if arg_cluster: self.log('info', "Generating clusters, this might take a while...") clusters = dict() for sample in samples: if not sample.ssdeep: continue if arg_verbose: self.log( 'info', "Testing file {0} with ssdeep {1}".format( sample.md5, sample.ssdeep)) clustered = False for cluster_name, cluster_members in clusters.items(): # Check if sample is already in the cluster. if sample.md5 in cluster_members: continue if arg_verbose: self.log( 'info', "Testing {0} in cluser {1}".format( sample.md5, cluster_name)) for member in cluster_members: if sample.md5 == member[0]: continue member_hash = member[0] member_name = member[1] member_ssdeep = db.find( key='md5', value=member_hash)[0].ssdeep if pydeep.compare(sample.ssdeep, member_ssdeep) > 40: if arg_verbose: self.log( 'info', "Found home for {0} in cluster {1}". format(sample.md5, cluster_name)) clusters[cluster_name].append( [sample.md5, sample.name]) clustered = True break if not clustered: cluster_id = len(clusters) + 1 clusters[cluster_id] = [ [sample.md5, sample.name], ] ordered_clusters = collections.OrderedDict( sorted(clusters.items())) self.log( 'info', "Following are the identified clusters with more than one member" ) for cluster_name, cluster_members in ordered_clusters.items(): # We include in the results only clusters with more than just # one member. if len(cluster_members) <= 1: continue self.log('info', "Ssdeep cluster {0}".format(bold(cluster_name))) self.log( 'table', dict(header=['MD5', 'Name'], rows=cluster_members)) # We're running against the already opened file. else: if not __sessions__.is_set(): self.log('error', "No session opened") return if not __sessions__.current.file.ssdeep: self.log('error', "No ssdeep hash available for opened file") return matches = [] for sample in samples: if sample.sha256 == __sessions__.current.file.sha256: continue if not sample.ssdeep: continue score = pydeep.compare(__sessions__.current.file.ssdeep, sample.ssdeep) if score > 40: matches.append( ['{0}%'.format(score), sample.name, sample.sha256]) if arg_verbose: self.log( 'info', "Match {0}%: {2} [{1}]".format( score, sample.name, sample.sha256)) self.log( 'info', "{0} relevant matches found".format(bold(len(matches)))) if len(matches) > 0: self.log( 'table', dict(header=['Score', 'Name', 'SHA256'], rows=matches))
def run(self): super(Fuzzy, self).run() if not HAVE_PYDEEP: self.log('error', "Missing dependency, install pydeep (`pip install pydeep`)") return arg_verbose = False arg_cluster = False if self.args: if self.args.verbose: arg_verbose = self.args.verbose if self.args.cluster: arg_cluster = self.args.cluster db = Database() samples = db.find(key='all') # Check if we're operating in cluster mode, otherwise we run on the # currently opened file. if arg_cluster: self.log('info', "Generating clusters, this might take a while...") clusters = dict() for sample in samples: if not sample.ssdeep: continue if arg_verbose: self.log('info', "Testing file {0} with ssdeep {1}".format( sample.md5, sample.ssdeep)) clustered = False for cluster_name, cluster_members in clusters.items(): # Check if sample is already in the cluster. if sample.md5 in cluster_members: continue if arg_verbose: self.log('info', "Testing {0} in cluser {1}".format( sample.md5, cluster_name)) for member in cluster_members: if sample.md5 == member[0]: continue member_hash = member[0] member_name = member[1] member_ssdeep = db.find(key='md5', value=member_hash)[0].ssdeep if pydeep.compare(sample.ssdeep, member_ssdeep) > 40: if arg_verbose: self.log('info', "Found home for {0} in cluster {1}".format( sample.md5, cluster_name)) clusters[cluster_name].append([sample.md5, sample.name]) clustered = True break if not clustered: cluster_id = len(clusters) + 1 clusters[cluster_id] = [[sample.md5, sample.name],] ordered_clusters = collections.OrderedDict(sorted(clusters.items())) self.log('info', "Following are the identified clusters with more than one member") for cluster_name, cluster_members in ordered_clusters.items(): # We include in the results only clusters with more than just # one member. if len(cluster_members) <= 1: continue self.log('info', "Ssdeep cluster {0}".format(bold(cluster_name))) self.log('table', dict(header=['MD5', 'Name'], rows=cluster_members)) # We're running against the already opened file. else: if not __sessions__.is_set(): self.log('error', "No session opened") return if not __sessions__.current.file.ssdeep: self.log('error', "No ssdeep hash available for opened file") return matches = [] for sample in samples: if sample.sha256 == __sessions__.current.file.sha256: continue if not sample.ssdeep: continue score = pydeep.compare(__sessions__.current.file.ssdeep, sample.ssdeep) if score > 40: matches.append(['{0}%'.format(score), sample.name, sample.sha256]) if arg_verbose: self.log('info', "Match {0}%: {2} [{1}]".format(score, sample.name, sample.sha256)) self.log('info', "{0} relevant matches found".format(bold(len(matches)))) if len(matches) > 0: self.log('table', dict(header=['Score', 'Name', 'SHA256'], rows=matches))
def add(self): if not __sessions__.is_set(): self.log('error', "No session opened") return False if not __sessions__.current.misp_event: self.log('error', "Not attached to a MISP event") return False current_event = copy.deepcopy(__sessions__.current.misp_event.event) if self.args.add == 'hashes': if self.args.filename is None and self.args.md5 is None and self.args.sha1 is None and self.args.sha256 is None: if not __sessions__.current.file: self.log('error', "Not attached to a file, please set the hashes manually.") return False event = self.misp.add_hashes(current_event, filename=__sessions__.current.file.name, md5=__sessions__.current.file.md5, sha1=__sessions__.current.file.sha1, sha256=__sessions__.current.file.sha256, comment=__sessions__.current.file.tags) else: event = self.misp.add_hashes(current_event, filename=self.args.filename, md5=self.args.md5, sha1=self.args.sha1, sha256=self.args.sha256) self._check_add(event) elif self.args.add == 'regkey': if len(self.args.regkey) == 2: reg, val = self.args.regkey else: reg = self.args.regkey[0] val = None event = self.misp.add_regkey(current_event, reg, val) self._check_add(event) elif self.args.add == 'pipe': event = self.misp.add_pipe(current_event, self.args.pipe) self._check_add(event) elif self.args.add == 'mutex': event = self.misp.add_mutex(current_event, self.args.mutex) self._check_add(event) elif self.args.add == 'ipdst': event = self.misp.add_ipdst(current_event, self.args.ipdst) self._check_add(event) elif self.args.add == 'hostname': event = self.misp.add_hostname(current_event, self.args.hostname) self._check_add(event) elif self.args.add == 'domain': event = self.misp.add_domain(current_event, self.args.domain) self._check_add(event) elif self.args.add == 'url': event = self.misp.add_url(current_event, self.args.full_url) self._check_add(event) elif self.args.add == 'ua': event = self.misp.add_useragent(current_event, self.args.ua) self._check_add(event) elif self.args.add == 'pattern_file': event = self.misp.add_pattern(current_event, self.args.pfile, True, False) self._check_add(event) elif self.args.add == 'pattern_mem': event = self.misp.add_pattern(current_event, self.args.pmem, False, True) self._check_add(event) elif self.args.add == 'pattern_traffic': event = self.misp.add_traffic_pattern(current_event, self.args.ptraffic) self._check_add(event)