def olevba_streams(self, args, file, opts): output = [] try: vbaparser = olevba3.VBA_Parser(file.file_path) except Exception: raise error.CommandWarning('file ' + str(file.file_path) + ' is not a valid ole file') try: vbaparser.detect_vba_macros() except: vbaparser.close() raise error.CommandWarning('no macro was detected on this file') try: macros = vbaparser.extract_all_macros() except: raise error.CommandWarning('vbaparser.extract_all_macros() failed to extract macros') i = 1 for m in macros: # pylint: disable=invalid-name try: output += [{ 'stream': str(i), 'stream_path': str(m[1]), 'vba_filename': str(m[2]), 'code': str(m[3].decode('utf-8')) }] except: output += [{ 'stream': str(i), 'stream_path': str(m[1]), 'vba_filename': str(m[2]), 'code': str(m[3]) }] i += 1 return output
def scan(self, data, file, options, expire_at): analyze_macros = options.get('analyze_macros', True) self.event['total'] = {'files': 0, 'extracted': 0} try: vba = olevba3.VBA_Parser(filename=file.name, data=data) if vba.detect_vba_macros(): extract_macros = list(vba.extract_macros()) self.event['total']['files'] = len(extract_macros) for (filename, stream_path, vba_filename, vba_code) in extract_macros: extract_file = strelka.File( name=f'{vba_filename}', source=self.name, ) for c in strelka.chunk_string(vba_code): self.upload_to_coordinator( extract_file.pointer, c, expire_at, ) self.files.append(extract_file) self.event['total']['extracted'] += 1 if analyze_macros: self.event.setdefault('auto_exec', []) self.event.setdefault('base64', []) self.event.setdefault('dridex', []) self.event.setdefault('hex', []) self.event.setdefault('ioc', []) self.event.setdefault('suspicious', []) macros = vba.analyze_macros() for (macro_type, keyword, description) in macros: if macro_type == 'AutoExec': self.event['auto_exec'].append(keyword) elif macro_type == 'Base64 String': self.event['base64'].append(keyword) elif macro_type == 'Dridex String': self.event['dridex'].append(keyword) elif macro_type == 'Hex String': self.event['hex'].append(keyword) elif macro_type == 'IOC': self.event['ioc'].append(keyword) elif macro_type == 'Suspicious': self.event['suspicious'].append(keyword) except olevba3.FileOpenError: self.flags.append('file_open_error') finally: # TODO referenced before potential assignment as vba is opened in a try / catch block vba.close()
def scan(self, file_object, options): analyze_macros = options.get("analyze_macros", True) self.metadata["total"] = {"files": 0, "extracted": 0} try: vba_parser = olevba3.VBA_Parser(filename=file_object.filename, data=file_object.data) if vba_parser.detect_vba_macros(): extract_macros = list(vba_parser.extract_macros()) self.metadata["total"]["files"] = len(extract_macros) for (filename, stream_path, vba_filename, vba_code) in extract_macros: child_filename = f"{self.scanner_name}::{vba_filename}" child_fo = objects.StrelkaFile( data=vba_code, filename=child_filename, depth=file_object.depth + 1, parent_uid=file_object.uid, root_uid=file_object.root_uid, parent_hash=file_object.hash, root_hash=file_object.root_hash, source=self.scanner_name) self.children.append(child_fo) self.metadata["total"]["extracted"] += 1 if analyze_macros: self.metadata.setdefault("autoExec", []) self.metadata.setdefault("base64", []) self.metadata.setdefault("dridex", []) self.metadata.setdefault("hex", []) self.metadata.setdefault("ioc", []) self.metadata.setdefault("suspicious", []) macros = vba_parser.analyze_macros() for (type, keyword, description) in macros: if type == "AutoExec": self.metadata["autoExec"].append(keyword) elif type == "Base64 String": self.metadata["base64"].append(keyword) elif type == "Dridex String": self.metadata["dridex"].append(keyword) elif type == "Hex String": self.metadata["hex"].append(keyword) elif type == "IOC": self.metadata["ioc"].append(keyword) elif type == "Suspicious": self.metadata["suspicious"].append(keyword) vba_parser.close() except olevba3.FileOpenError: file_object.flags.append(f"{self.scanner_name}::file_open_error")
def mraptor(self, args, file, opts): # Monkeypatch 1 - This is to force the script argument to the appropriate file locaiton for analysis def temp_args(_a, _b, _c, _d): return [file.file_path] # Deploy Monkeypatch 1 import optparse get_args = optparse.OptionParser._get_args optparse.OptionParser._get_args = temp_args # Monkeypatch - This rediverts stdout to a stream that can be collected later for results sys.stdout = io.StringIO() result = [] try: vbaparser = olevba3.VBA_Parser(file.file_path) except Exception: raise error.CommandWarning('file ' + str(file.file_path) + ' is not a valid ole file') filetype = olevba3.TYPE2TAG[vbaparser.type] if not vbaparser.detect_vba_macros(): vbaparser.close() raise error.CommandWarning('file does not have macros') try: vba_code_all_modules = '' for (subfilename, stream_path, vba_filename, vba_code) in vbaparser.extract_all_macros(): vba_code_all_modules += vba_code + '\n' m = mraptor3.MacroRaptor(vba_code_all_modules) m.scan() if m.suspicious: result += [{ 'Result': 'SUSPICIOUS', 'Flags': str(m.get_flags()), 'Match_on': str(m.matches) }] else: result += [{ 'Result': 'Macro seems fine' }] except Exception: # Revert patched functions to originals optparse.OptionParser._get_args = get_args sys.stdout = sys.__stdout__ raise error.CommandWarning('failed to parse macros') # Revert patched function to originals optparse.OptionParser._get_args = get_args sys.stdout = sys.__stdout__ reload(oledir) return result
def html_vba_dump(args, office_filename, outfile): vba_parser = vba3.VBA_Parser(office_filename) for (filename, stream_path, vba_filename, vba_code) in vba_parser.extract_macros(): # The stream_path should be the module name with a "VBA/" prefix; e.g., "VBA/Exercises" stream_path = html.escape(stream_path) if args.modules is None or stream_path[stream_path.find("/") + 1:] in args.modules: if args.sort: # If the user specified the sort option, the summary tag will only # show Lastname, Firstname, show the full file name in the output. print("""<h3 style="margin-left: 20px">{}: {}</h3>""".format(html.escape(office_filename), html.escape(stream_path)), file=outfile) # Pre-format and pretty-print the code. print("""<pre style="margin-left: 20px" class="prettyprint lang-vb">""", file=outfile) print(vba_code.decode("utf-8").replace("\r", ""), file=outfile) print("""</pre>""", file=outfile) # Show progress if the user has asked for it. if args.verbose: print("""{}: {}""".format(html.escape(office_filename), html.escape(stream_path)))
def olevba_keywords(self, args, file, opts): try: vbaparser = olevba3.VBA_Parser(file.file_path) except Exception: raise error.CommandWarning('file ' + str(file.file_path) + ' is not a valid ole file') output = [] if not vbaparser.detect_vba_macros(): vbaparser.close() return output results = vbaparser.analyze_macros() for kw_type, keyword, description in results: output += [{ 'type': kw_type, 'keyword': str(str(keyword).encode('utf-8'))[2:-1], 'description': description }] vbaparser.close() return output
def scan(self, payload: Payload, request_meta: RequestMeta) -> WorkerResponse: results = {} filename = payload.payload_meta.extra_data.get('filename', payload.payload_id) vba_parser = olevba.VBA_Parser(filename=filename, data=payload.content) if vba_parser.detect_vba_macros(): vba_modules = [ vba_code[3].decode('utf-8', 'replace') for vba_code in vba_parser.extract_all_macros() ] vba_modules = '\n'.join(vba_modules) mraptor = MacroRaptor(vba_modules) mraptor.scan() flags = [ self.FLAGS[flag] for flag in mraptor.get_flags() if flag in self.FLAGS ] results = { 'suspicous': mraptor.suspicious, 'flags': flags, 'filetype': vba_parser.type, 'matches': mraptor.matches, } return WorkerResponse(results)
def main(): """ Main function, called when olevba is run from the command line """ global log DEFAULT_LOG_LEVEL = "warning" # Default log level LOG_LEVELS = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, 'critical': logging.CRITICAL } usage = 'usage: %prog [options] <filename> [filename2 ...]' parser = optparse.OptionParser(usage=usage) parser.add_option("-r", action="store_true", dest="recursive", help='find files recursively in subdirectories.') parser.add_option( "-z", "--zip", dest='zip_password', type='str', default=None, help= 'if the file is a zip archive, open all files from it, using the provided password (requires Python 2.6+)' ) parser.add_option( "-f", "--zipfname", dest='zip_fname', type='str', default='*', help= 'if the file is a zip archive, file(s) to be opened within the zip. Wildcards * and ? are supported. (default:*)' ) parser.add_option( '-l', '--loglevel', dest="loglevel", action="store", default=DEFAULT_LOG_LEVEL, help= "logging level debug/info/warning/error/critical (default=%default)") parser.add_option("-m", '--matches', action="store_true", dest="show_matches", help='Show matched strings.') # TODO: add logfile option (options, args) = parser.parse_args() # Print help if no arguments are passed if len(args) == 0: print('MacroRaptor %s - http://decalage.info/python/oletools' % __version__) print('This is work in progress, please report issues at %s' % URL_ISSUES) print(__doc__) parser.print_help() print('\nAn exit code is returned based on the analysis result:') for result in (Result_NoMacro, Result_NotMSOffice, Result_MacroOK, Result_Error, Result_Suspicious): print(' - %d: %s' % (result.exit_code, result.name)) sys.exit() # print banner with version print('MacroRaptor %s - http://decalage.info/python/oletools' % __version__) print('This is work in progress, please report issues at %s' % URL_ISSUES) logging.basicConfig(level=LOG_LEVELS[options.loglevel], format='%(levelname)-8s %(message)s') # enable logging in the modules: log.setLevel(logging.NOTSET) t = tablestream.TableStream(style=tablestream.TableStyleSlim, header_row=['Result', 'Flags', 'Type', 'File'], column_width=[10, 5, 4, 56]) exitcode = -1 global_result = None # TODO: handle errors in xglob, to continue processing the next files for container, filename, data in xglob.iter_files( args, recursive=options.recursive, zip_password=options.zip_password, zip_fname=options.zip_fname): # ignore directory names stored in zip files: if container and filename.endswith('/'): continue full_name = '%s in %s' % (filename, container) if container else filename # try: # # Open the file # if data is None: # data = open(filename, 'rb').read() # except: # log.exception('Error when opening file %r' % full_name) # continue if isinstance(data, Exception): result = Result_Error t.write_row([result.name, '', '', full_name], colors=[result.color, None, None, None]) t.write_row(['', '', '', str(data)], colors=[None, None, None, result.color]) else: filetype = '???' try: vba_parser = olevba.VBA_Parser(filename=filename, data=data, container=container) filetype = TYPE2TAG[vba_parser.type] except Exception as e: # log.error('Error when parsing VBA macros from file %r' % full_name) # TODO: distinguish actual errors from non-MSOffice files result = Result_Error t.write_row([result.name, '', filetype, full_name], colors=[result.color, None, None, None]) t.write_row(['', '', '', str(e)], colors=[None, None, None, result.color]) continue if vba_parser.detect_vba_macros(): vba_code_all_modules = '' try: for (subfilename, stream_path, vba_filename, vba_code) in vba_parser.extract_all_macros(): vba_code_all_modules += vba_code.decode( 'utf-8', 'replace') + '\n' except Exception as e: # log.error('Error when parsing VBA macros from file %r' % full_name) result = Result_Error t.write_row([ result.name, '', TYPE2TAG[vba_parser.type], full_name ], colors=[result.color, None, None, None]) t.write_row(['', '', '', str(e)], colors=[None, None, None, result.color]) continue mraptor = MacroRaptor(vba_code_all_modules) mraptor.scan() if mraptor.suspicious: result = Result_Suspicious else: result = Result_MacroOK t.write_row( [result.name, mraptor.get_flags(), filetype, full_name], colors=[result.color, None, None, None]) if mraptor.matches and options.show_matches: t.write_row(['', '', '', 'Matches: %r' % mraptor.matches]) else: result = Result_NoMacro t.write_row([result.name, '', filetype, full_name], colors=[result.color, None, None, None]) if result.exit_code > exitcode: global_result = result exitcode = result.exit_code print('') print('Flags: A=AutoExec, W=Write, X=Execute') print('Exit code: %d - %s' % (exitcode, global_result.name)) sys.exit(exitcode)