def arclist(self, filename, fileformat): import oletools.thirdparty.olefile.olefile as olefile import oletools.olevba as vba file_scan_list = [] # 검사 대상의 압축 엔진 ID 및 임의의 문자열이 저장될 리스트 if 'ff_ole' in fileformat: # format 함수에 의해 분석된 OLE 파일 포맷이 있을 경우 # OLE Stream 목록 추출 o = olefile.OleFileIO(filename) for path in o.listdir(): name = '/'.join(path) if o.get_type(name) == olefile.STGTY_STREAM: # 파일인 경우 file_scan_list.append(['arc_ole', name]) o.close() # 매크로 목록 추출 v = vba.VBA_Parser(filename) if v.detect_vba_macros(): # 매크로가 존재할 경우 macros = v.extract_all_macros() # 매크로 추출 for macro in macros(): name = macro[1] print name + '\n' + macro[3] + '\n' file_scan_list.append( ['arc_vba', name.encode('ascii', 'ignore')]) v.close() elif 'ff_zip' in fileformat: # OOXML(Open Office XML) 파일 포맷일 경우 # 매크로 목록 추출 v = vba.VBA_Parser(filename) if v.detect_vba_macros(): # 매크로가 존재할 경우 macros = v.extract_all_macros() # 매크로 추출 for macro in macros(): name = macro[0] + '/' + macro[1] print name + '\n' + macro[3] + '\n' file_scan_list.append( ['arc_vba', name.encode('ascii', 'ignore')]) v.close() return file_scan_list
def getZipFiles(self, attachment, filename): ''' Checks a zip for parsable files and extracts all macros ''' log.debug( "[%d] Found attachment with archive extension - file name: %s" % (self.id, filename)) vba_code_all_modules = '' file_object = StringIO.StringIO(attachment) files_in_zip = self.zipwalk(file_object, 0, []) for zip_name, zip_data in files_in_zip: # checks if it is a file zip_mem_data = StringIO.StringIO(zip_data) name, ext = os.path.splitext(zip_name.filename) # send to the VBA_Parser # fallback with extensions - maybe removed in future releases if olefile.isOleFile(zip_mem_data) or ext in EXTENSIONS: log.info( "[%d] File in zip detected! Name: %s - check for VBA" % (self.id, zip_name.filename)) vba_parser = olevba.VBA_Parser(filename=zip_name.filename, data=zip_data) for (subfilename, stream_path, vba_filename, vba_code) in vba_parser.extract_all_macros(): vba_code_all_modules += vba_code + '\n' return vba_code_all_modules
def check_macros(self): """ Check whether this file contains macros (VBA and XLM/Excel 4). :returns: :py:class:`Indicator` """ vba_indicator = Indicator(_id='vba', value='No', _type=str, name='VBA Macros', description='This file does not contain VBA macros.', risk=RISK.NONE) try: vba_parser = olevba.VBA_Parser(filename=self.filename, data=self.data) if vba_parser.detect_vba_macros(): vba_indicator.value = 'Yes' vba_indicator.risk = RISK.MEDIUM vba_indicator.description = 'This file contains VBA macros. No suspicious keyword was found. Use olevba and mraptor for more info.' # check code with mraptor vba_code = vba_parser.get_vba_code_all_modules() m = mraptor.MacroRaptor(vba_code) m.scan() if m.suspicious: vba_indicator.value = 'Yes, suspicious' vba_indicator.risk = RISK.HIGH vba_indicator.description = 'This file contains VBA macros. Suspicious keywords were found. Use olevba and mraptor for more info.' except Exception as e: vba_indicator.risk = RISK.ERROR vba_indicator.value = 'Error' vba_indicator.description = 'Error while checking VBA macros: %s' % str(e) self.indicators.append(vba_indicator) return vba_indicator
def unarc(self, arc_engine_id, arc_name, fname_in_arc): import oletools.thirdparty.olefile.olefile as olefile import oletools.olevba as vba data = None if arc_engine_id == 'arc_ole': # 압축 해제 엔진 ID가 arc_ole 일 경우 o = olefile.OleFileIO(arc_name) fp = o.openstream(fname_in_arc) # OLE 파일 내부 파일 열기 data = fp.read() # 데이터 추출 o.close() return data elif arc_engine_id == 'arc_vba': # 압축 해제 엔진 ID가 arc_vba 일 경우 v = vba.VBA_Parser(arc_name) if v.detect_vba_macros(): # 매크로가 존재할 경우 macros = v.extract_all_macros() # 매크로 추출 for macro in macros(): if v.type == 'OLE': name = macro[1] elif v.type == 'OpenXML': name = macro[0] + '/' + macro[1] else: name = 'UNKNOWN' if name == fname_in_arc: data = macro[3] # VBA Code break v.close() return data return None
def get_macros(data_stream): """ :param data_stream: binary data stream :return: macro [{'vba_filename': 'ThisDocument.cls', 'subfilename': 'vba', 'ole_stream': 'VBA/ThisDocument', 'code': 'this_is_code_space'}, ...] """ vba2 = olevba.VBA_Parser(filename="vba", data=data_stream) macros = [] try: if vba2.detect_vba_macros(): for (subfilename, stream_path, vba_filename, vba_code) in vba2.extract_all_macros(): curr_macro = {} if vba_code is None: continue vba_code_filtered = filter_vba(vba_code) curr_macro['vba_filename'] = vba_filename curr_macro['subfilename'] = subfilename curr_macro['ole_stream'] = stream_path curr_macro['code'] = vba_code_filtered.strip() if curr_macro['code'] != '': macros.append(curr_macro) except TypeError as te: logging.exception("get_macros: {te}".format(te=te)) return macros
def _run(self, scanObject, result, depth, args): moduleResult = [] try: vbaparser = olevba.VBA_Parser( scanObject.objectHash, data=scanObject.buffer) #load ole into olevba if vbaparser.detect_vba_macros(): #VBA Macro Found # Loop to parse VBA Macro for (filename, stream_path, vba_filename, vba_code ) in vbaparser.extract_macros(): # macro extraction macrofilesdict = {} macrofilesdict.update({ 'Type': vbaparser.type, 'VBA_project': vbaparser.vba_projects, 'OLE_stream': stream_path, 'VBA_filename': vba_filename }) scanObject.addMetadata(self.module_name, "Parsed_Macros_Metadata", macrofilesdict) explodevbafilename = 'e_vba_%s_%s' % ( scanObject.objectHash, vba_filename ) # Exploded file name contains source hash moduleResult.append( ModuleObject(buffer=vba_code, externalVars=ExternalVars( filename=explodevbafilename))) # Loop to parse VBA Forms combinedstring = "" formfilesdlist = set() for (filename, stream_path, form_string) in vbaparser.extract_form_strings(): formfilesdlist.add( stream_path ) #set because stream_path could be the same over and over again combinedstring += " %s" % form_string #combining all found forms text into a single variable if combinedstring: #form text found scanObject.addMetadata(self.module_name, "VBA_Forms_Found_Streams", formfilesdlist) explodeformsfilename = 'e_vba_%s_combined_forms.txt' % ( scanObject.objectHash) moduleResult.append( ModuleObject(buffer=combinedstring, externalVars=ExternalVars( filename=explodeformsfilename))) vbaparser.close() except olevba.OlevbaBaseException as e: # exceptions from olevba import will raise olevbaerror = 'e_vba:err:%s' % e #scanObject.addFlag(olevbaerror) log_module("MSG", self.module_name, 0, scanObject, result, olevbaerror) except (QuitScanException, GlobalScanTimeoutError, GlobalModuleTimeoutError): raise return moduleResult
def check_mraptor(self): ''' Check the attachments of a message using mraptor. If an attachment is identified as suspicious, it is replaced by a simple text file. :return: Milter.ACCEPT or Milter.DISCARD if processing error ''' msg = email.message_from_string(self.message.getvalue()) result = Milter.ACCEPT try: for part in msg.walk(): # for name, value in part.items(): # logging.debug(' - %s: %r' % (name, value)) content_type = part.get_content_type() logging.debug('[%d] Content-type: %r' % (self.id, content_type)) # TODO: handle any content-type, but check the file magic? if not content_type.startswith('multipart'): filename = part.get_filename(None) logging.debug('[%d] Analyzing attachment %r' % (self.id, filename)) attachment = part.get_payload(decode=True) attachment_lowercase = attachment.lower() # check if this is a supported file type (if not, just skip it) # TODO: use is_zipfile instead of 'PK' # TODO: this function should be provided by olevba if attachment.startswith(olevba.olefile.MAGIC) \ or attachment.startswith('PK') \ or 'http://schemas.microsoft.com/office/word/2003/wordml' in attachment \ or ('mime' in attachment_lowercase and 'version' in attachment_lowercase and 'multipart' in attachment_lowercase): vba_parser = olevba.VBA_Parser(filename='message', data=attachment) vba_code_all_modules = '' for (subfilename, stream_path, vba_filename, vba_code) in vba_parser.extract_all_macros(): vba_code_all_modules += vba_code + '\n' m = mraptor.MacroRaptor(vba_code_all_modules) m.scan() if m.suspicious: logging.warning('[%d] The attachment %r contains a suspicious macro: replace it with a text file' % (self.id, filename)) part.set_payload('This attachment has been removed because it contains a suspicious macro.') part.set_type('text/plain') # TODO: handle case when CTE is absent part.replace_header('Content-Transfer-Encoding', '7bit') # for name, value in part.items(): # logging.debug(' - %s: %r' % (name, value)) # TODO: archive filtered e-mail to a file else: logging.debug('The attachment %r is clean.' % filename) except Exception: logging.exception('[%d] Error while processing the message' % self.id) # TODO: depending on error, decide to forward the e-mail as-is or not result = Milter.DISCARD # TODO: only do this if the body has actually changed body = str(msg) self.message = io.BytesIO(body) self.replacebody(body) logging.info('[%d] Message relayed' % self.id) return result
def each(self, target): self.results = { 'macros': u'', 'analysis': { 'AutoExec': [], 'Suspicious': [], 'IOC': [], 'Hex String': [], 'Base64 String': [], 'Dridex string': [], 'VBA string': [], 'Form String': [] } } vba = olevba.VBA_Parser(target) # code is inspired by 'reveal' method in olevba analysis = vba.analyze_macros(show_decoded_strings=True) # extract all macros code for (_, _, _, vba_code) in vba.extract_all_macros(): self.results['macros'] += vba_code.decode('utf-8', errors='replace') + '\n' # extract all form strings for (_, _, form_string) in vba.extract_form_strings(): self.results['analysis']['Form String'].append( form_string.decode('utf-8', errors='replace')) # extract all analysis if analysis: analysis = sorted( analysis, key=lambda type_decoded_encoded: len(type_decoded_encoded[2]), reverse=True) for kw_type, keyword, description in analysis: # and replace obfuscated strings if kw_type in [ 'VBA string', 'Dridex string', 'Base64 String', 'Hex String' ]: if olevba.is_printable(keyword): keyword = keyword.replace('"', '""') self.results['macros'] = self.results[ 'macros'].replace(description, '"%s"' % keyword) self.results['analysis'][kw_type].append( (keyword.decode('utf-8', errors='replace'), description.decode('utf-8', errors='replace'))) else: self.results['analysis'][kw_type].append( (keyword, description)) return len(self.results['macros']) > 0
def scan(self, data, file, options, expire_at): analyze_macros = options.get('analyze_macros', True) self.event['total'] = {'files': 0, 'extracted': 0} try: vba = olevba.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 olevba.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 extract_macro(self): vba = olevba.VBA_Parser(self.sample) macro_code = "" if vba.detect_vba_macros(): for (filename, stream_path, vba_filename, vba_code) in vba.extract_macros(): macro_code += olevba.filter_vba(vba_code) self.results["analysis"] = vba.analyze_macros() self.results["code"] = macro_code vba.close() return self.results vba.close() return False
def make_analysis(self): """ Function that make the file analysis :return: """ logging.debug('\n\n%s' % self.separator) ole_parser = olevba.VBA_Parser(self.file_path) if not ole_parser.detect_vba_macros(): return logging.info('No VBA Macros were found in this file') logging.critical('VBA Macros found') # Extracts the macro into analysis path for filename, stream_path, vba_filename, vba_code in ole_parser.extract_macros(): logging.debug(self.separator) logging.info('OLE stream : %s' % stream_path) logging.info('VBA filename : %s' % vba_filename) tp = os.path.join(self.analysis_path, vba_filename) with open(tp, 'w') as f: f.write(vba_code) logging.warning('\nSaved in: "%s"\n' % tp) # Analyze all macros logging.debug(self.separator) logging.critical('Keywords: \n') for kw_type, keyword, description in ole_parser.analyze_macros(): logging.warning('Type: %s' % kw_type) logging.info('Keyword: %s\nDescription: %s\n' % (keyword, description)) logging.debug(self.separator) logging.critical('Analysis: \n') logging.warning('VBA obfuscated strings: %d' % ole_parser.nb_vbastrings) logging.warning('IOCs: %d' % ole_parser.nb_iocs) logging.warning('AutoExec keywords: %d' % ole_parser.nb_autoexec) logging.warning('Suspicious keywords: %d' % ole_parser.nb_suspicious) logging.warning('Hex obfuscated strings: %d' % ole_parser.nb_hexstrings) logging.warning('Base64 obfuscated strings: %d' % ole_parser.nb_base64strings) logging.warning('Dridex obfuscated strings: %d' % ole_parser.nb_dridexstrings)
def analyze(self, afile): '''Analyze OLE files and extract metadata about the file into the FileAnalysis object. Args: afile (FileAnalysis): The file to be analyzed. Returns: None ''' if afile.mime_type in self.analyzed_mimes: # Parse the metadata for the ole file and add all ole metadata # attributes to the FileAnalysis object. This should add a ton # of contectual information to the file. try: ole = olefile.OleFileIO(afile.path) process_metadata = True except IOError: afile.errors = afile.errors + [ 'doc plugin: unsupported filetype' ] output = 'None' afile.plugin_output[self.__NAME__] = output process_metadata = False # There are OLE files out there with LOTS of embedded objects. # This should prevent plugin crashes for those cases. except RuntimeError: afile.errors = afile.errors + [ 'doc plugin: max recursion reached' ] output = 'None' process_metadata = False afile.suspicious = True process_metadata = False if process_metadata: meta = ole.get_metadata() # These loops iterate through the meta for attributes and then # set attributes with the same name in the FileAnalysis object for prop in meta.SUMMARY_ATTRIBS: value = getattr(meta, prop) setattr(afile, prop, value) for prop in meta.DOCSUM_ATTRIBS: value = getattr(meta, prop) setattr(afile, prop, value) # Thumbnails are binary streams and muck up the output so they # are removed. This is a temporary work-around... the doc # analyzer will be rewritten to accomidate things like this if hasattr(afile, 'thumbnail'): afile.has_thumbnail = True del afile.thumbnail # Explicitly call close to ensure that the ole object gets closed ole.close() # Parse the file again, this time looking for VBA scripts. try: parser = olevba.VBA_Parser(afile.path) except TypeError: afile.errors = afile.errors + [ 'doc plugin: unsupported filetype' ] output = 'None' afile.plugin_output[self.__NAME__] = output return results = parser.analyze_macros() contains_macro = parser.detect_vba_macros() if contains_macro and self.alert_on_macro: afile.alert = True if contains_macro and self.suspicious_on_macro: afile.suspicious = True output = '' if results is not None: for result in results: output = output + '[%s] keyword: %s description: %s' % result else: output = 'None' if contains_macro: afile.vba = parser.reveal() afile.plugin_output[self.__NAME__] = output # The parser requires an explicit close parser.close()
def checkforVBA(self, msg): ''' Checks if it contains a vba macro and checks if user is whitelisted or file already parsed ''' # Accept all messages with no attachment result = Milter.ACCEPT try: for part in msg.walk(): # for name, value in part.items(): # log.debug(' - %s: %r' % (name, value)) content_type = part.get_content_type() log.debug('[%d] Content-Type: %r' % (self.id, content_type)) # TODO: handle any content-type, but check the file magic? if not content_type.startswith('multipart'): filename = part.get_filename(None) attachment = part.get_payload(decode=True) if attachment is None: return Milter.CONTINUE log.debug('[%d] Analyzing attachment: %r' % (self.id, filename)) attachment_lowercase = attachment.lower() attachment_fileobj = StringIO.StringIO(attachment) # check if file was already parsed if self.fileHasAlreadyBeenParsed(attachment): return Milter.REJECT # check if this is a supported file type (if not, just skip it) # TODO: this function should be provided by olevba if olefile.isOleFile(attachment_fileobj) or is_zipfile(attachment_fileobj) or 'http://schemas.microsoft.com/office/word/2003/wordml' in attachment \ or ('mime' in attachment_lowercase and 'version' in attachment_lowercase \ and 'multipart' in attachment_lowercase): vba_code_all_modules = '' # check if the attachment is a zip if not olefile.isOleFile(attachment_fileobj): extn = (os.path.splitext(filename)[1]).lower() # skip non archives if is_zipfile(attachment_fileobj) and not ( ".docx" in extn or ".xlsx" in extn or ".pptx" in extn): # extract all file in zip and add try: zipvba = self.getZipFiles( attachment, filename) vba_code_all_modules += zipvba + '\n' except ToManyZipException: log.warning( "[%d] Attachment %s is reached the max. nested zip count! ZipBomb?: REJECT" % (self.id, filename)) # rewrite the reject message self.setreply( '550', '5.7.2', "The message contains a suspicious archive and was rejected!" ) return Milter.REJECT # check the rest of the message vba_parser = olevba.VBA_Parser(filename='message', data=attachment) for (subfilename, stream_path, vba_filename, vba_code) in vba_parser.extract_all_macros(): vba_code_all_modules += vba_code + '\n' # run the mraptor m = mraptor.MacroRaptor(vba_code_all_modules) m.scan() if m.suspicious: # Add MD5 to the database self.addHashtoDB(attachment) # Replace the attachment or reject it if REJECT_MESSAGE: log.warning( '[%d] The attachment %r contains a suspicious macro: REJECT' % (self.id, filename)) result = Milter.REJECT else: log.warning( '[%d] The attachment %r contains a suspicious macro: replace it with a text file' % (self.id, filename)) part.set_payload( 'This attachment has been removed because it contains a suspicious macro.' ) part.set_type('text/plain') part.replace_header( 'Content-Transfer-Encoding', '7bit') else: log.debug('[%d] The attachment %r is clean.' % (self.id, filename)) except Exception: log.error('[%d] Error while processing the message' % self.id) exc_type, exc_value, exc_traceback = sys.exc_info() lines = traceback.format_exception(exc_type, exc_value, exc_traceback) exep = ''.join('!! ' + line for line in lines) log.debug("[%d] Exeption code: [%s]" % (self.id, exep)) if REJECT_MESSAGE is False: body = str(msg) self.message = io.BytesIO(body) self.replacebody(body) log.info('[%d] Message relayed' % self.id) return result
for container, filename, data in xglob.iter_files(args, recursive=True, zip_password=None, zip_fname=None): # ignore directory names stored in zip files: if container and filename.endswith('/'): continue full_name = '%s in %s' % (filename, container) if container else filename print(full_name) if isinstance(data, Exception): print("Error occured") else: filetype = '???' try: vba_parser = olevba.VBA_Parser(filename=filename, data=data, container=container) filetype = TYPE2TAG[vba_parser.type] except Exception as e: print(e) 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 + '\n' except Exception as e: print(e) continue mraptor = MacroRaptor(vba_code_all_modules)
import re import io import sys import pefile import olefile import hashlib import zipfile import tempfile from subprocess import Popen, PIPE from oletools import olevba from base64 import b64decode B64_RX ='(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?' vba = olevba.VBA_Parser(sys.argv[1]) path = ['Macros','UserForm1','o'] ole = vba.ole_file if not ole: zipf = zipfile.ZipFile(sys.argv[1]) data=zipf.read('word/vbaProject.bin') vba = olevba.VBA_Parser(io.BytesIO(data)) path = ['UserForm1','o'] ole = vba.ole_file for _, _, _, t in vba.extract_macros(): x = t.find('Lib "') if x !=-1: print('LIB: '+t[x+5:x+200].split('"')[0]) x = t.find('"S-') if x != -1: print('KEY: '+t[x+1:x+200].split('"')[0])
def check_macros(self): """ Check whether this file contains macros (VBA and XLM/Excel 4). :returns: :py:class:`Indicator` """ vba_indicator = Indicator( _id='vba', value='No', _type=str, name='VBA Macros', description='This file does not contain VBA macros.', risk=RISK.NONE, hide_if_false=False) self.indicators.append(vba_indicator) xlm_indicator = Indicator( _id='xlm', value='No', _type=str, name='XLM Macros', description='This file does not contain Excel 4/XLM macros.', risk=RISK.NONE, hide_if_false=False) self.indicators.append(xlm_indicator) if self.ftg.filetype == ftguess.FTYPE.RTF: # For RTF we don't call olevba otherwise it triggers an error vba_indicator.description = 'RTF files cannot contain VBA macros' xlm_indicator.description = 'RTF files cannot contain XLM macros' return vba_indicator, xlm_indicator vba_parser = None # flag in case olevba fails try: vba_parser = olevba.VBA_Parser(filename=self.filename, data=self.data) if vba_parser.detect_vba_macros(): vba_indicator.value = 'Yes' vba_indicator.risk = RISK.MEDIUM vba_indicator.description = 'This file contains VBA macros. No suspicious keyword was found. Use olevba and mraptor for more info.' # check code with mraptor vba_code = vba_parser.get_vba_code_all_modules() m = mraptor.MacroRaptor(vba_code) m.scan() if m.suspicious: vba_indicator.value = 'Yes, suspicious' vba_indicator.risk = RISK.HIGH vba_indicator.description = 'This file contains VBA macros. Suspicious keywords were found. Use olevba and mraptor for more info.' except Exception as e: vba_indicator.risk = RISK.ERROR vba_indicator.value = 'Error' vba_indicator.description = 'Error while checking VBA macros: %s' % str( e) # Check XLM macros only for Excel file types: if self.ftg.is_excel(): # TODO: for now XLM detection only works for files on disk... So we need to reload VBA_Parser from the filename # To be improved once XLMMacroDeobfuscator can work on files in memory if self.file_on_disk: try: vba_parser = olevba.VBA_Parser(filename=self.filename) if vba_parser.detect_xlm_macros(): xlm_indicator.value = 'Yes' xlm_indicator.risk = RISK.MEDIUM xlm_indicator.description = 'This file contains XLM macros. Use olevba to analyse them.' except Exception as e: xlm_indicator.risk = RISK.ERROR xlm_indicator.value = 'Error' xlm_indicator.description = 'Error while checking XLM macros: %s' % str( e) else: xlm_indicator.risk = RISK.UNKNOWN xlm_indicator.value = 'Unknown' xlm_indicator.description = 'For now, XLM macros can only be detected for files on disk, not in memory' return vba_indicator, xlm_indicator
def main(): """ Main function, called when olevba is run from the command line """ DEFAULT_LOG_LEVEL = "warning" # Default log level usage = 'usage: mraptor [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) log_helper.enable_logging(level=options.loglevel) # enable logging in the modules: olevba.enable_logging() 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: vba_code_all_modules = vba_parser.get_vba_code_all_modules() 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 log_helper.end_logging() print('') print('Flags: A=AutoExec, W=Write, X=Execute') print('Exit code: %d - %s' % (exitcode, global_result.name)) sys.exit(exitcode)
def _handle_mraptor_scan(self, param): self.save_progress("In action handler for: {0}".format( self.get_action_identifier())) action_result = self.add_action_result(ActionResult(dict(param))) summary = action_result.update_summary({}) vault_id = param['vault_id'] try: success, message, info = vault.vault_info( vault_id=vault_id, container_id=self.get_container_id(), trace=True) if phantom.is_fail(success): return action_result.set_status(phantom.APP_ERROR, message) info = list(info) except Exception as e: error_msg = self._get_error_message_from_exception(e) return action_result.set_status(phantom.APP_ERROR, error_msg) # phantom vault file path vault_path = info[0].get("path") if not vault_path: return action_result.set_status(phantom.APP_ERROR, OLETOOLS_ERR_UNABLE_TO_FETCH_FILE) try: oid = oletools.oleid.OleID(vault_path) indicators = oid.check() result = {"oleid": {}, "mraptor": {}} for i in indicators: result["oleid"][i.id] = { "id": i.id, "name": i.name, "value": str(i.value) } summary["ftype"] = result["oleid"].get("ftype", {}).get("value") vba_parser = olevba.VBA_Parser(filename=vault_path) if vba_parser.detect_vba_macros(): vba_code_all_modules = '' vba_code_all_modules = vba_parser.get_vba_code_all_modules() mraptor = MacroRaptor(vba_code_all_modules) mraptor.scan() result["mraptor"] = mraptor.__dict__ summary['suspicious'] = mraptor.suspicious except Exception as e: error_msg = self._get_error_message_from_exception(e) return action_result.set_status(phantom.APP_ERROR, error_msg) action_result.add_data(result) # Add a dictionary that is made up of the most important values from data into the summary if not summary.get('suspicious'): summary["suspicious"] = False # Return success, no need to set the message, only the status # BaseConnector will create a textual message based off of the summary dictionary return action_result.set_status(phantom.APP_SUCCESS)