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 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 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 run(analyzer_name, job_id, filepath, filename, md5, additional_config_params): logger.info("started analyzer {} job_id {}" "".format(analyzer_name, job_id)) report = general.get_basic_report_template(analyzer_name) try: results = {} # olevba olevba_results = {} try: vbaparser = VBA_Parser(filepath) olevba_results[ 'macro_found'] = True if vbaparser.detect_vba_macros( ) else False if olevba_results['macro_found']: macro_data = [] for (v_filename, stream_path, vba_filename, vba_code) in vbaparser.extract_macros(): extracted_macro = { "filename": v_filename, "ole_stream": stream_path, "vba_filename": vba_filename, "vba_code": vba_code } macro_data.append(extracted_macro) olevba_results['macro_data'] = macro_data # example output ''' {'description': 'Runs when the Word document is opened', 'keyword': 'AutoOpen', 'type': 'AutoExec'}, {'description': 'May run an executable file or a system command', 'keyword': 'Shell', 'type': 'Suspicious'}, {'description': 'May run an executable file or a system command', 'keyword': 'WScript.Shell', 'type': 'Suspicious'}, {'description': 'May run an executable file or a system command', 'keyword': 'Run', 'type': 'Suspicious'}, {'description': 'May run PowerShell commands', 'keyword': 'powershell', 'type': 'Suspicious'}, {'description': '9BA55BE5', 'keyword': 'xxx', 'type': 'Hex String'}, ''' analyzer_results = vbaparser.analyze_macros( show_decoded_strings=True) # it gives None if it does not find anything if analyzer_results: analyze_macro_results = [] for kw_type, keyword, description in analyzer_results: if kw_type != 'Hex String': analyze_macro_result = { "type": kw_type, "keyword": keyword, "description": description } analyze_macro_results.append(analyze_macro_result) olevba_results['analyze_macro'] = analyze_macro_results olevba_results['reveal'] = vbaparser.reveal() vbaparser.close() except Exception as e: traceback.print_exc() error_message = "job_id {} vba parser failed. Error: {}".format( job_id, e) logger.exception(error_message) report['errors'].append(error_message) results['olevba'] = olevba_results # mraptor macro_raptor = mraptor.MacroRaptor(olevba_results.get('reveal', '')) if macro_raptor: macro_raptor.scan() results[ 'mraptor'] = "suspicious" if macro_raptor.suspicious else 'ok' # pprint.pprint(results) report['report'] = results except AnalyzerRunException as e: error_message = "job_id:{} analyzer:{} md5:{} filename: {} Analyzer Error {}" \ "".format(job_id, analyzer_name, md5, filename, e) logger.error(error_message) report['errors'].append(error_message) report['success'] = False except Exception as e: traceback.print_exc() error_message = "job_id:{} analyzer:{} md5:{} filename: {} Unexpected Error {}" \ "".format(job_id, analyzer_name, md5, filename, e) logger.exception(error_message) report['errors'].append(str(e)) report['success'] = False else: report['success'] = True general.set_report_and_cleanup(job_id, report, logger) logger.info("ended analyzer {} job_id {}" "".format(analyzer_name, job_id)) return report
def run(self): results = {} # olevba try: self.vbaparser = VBA_Parser(self.filepath) self.manage_encrypted_doc() if self.experimental: self.experimental_analysis() # go on with the normal oletools execution self.olevba_results["macro_found"] = self.vbaparser.detect_vba_macros() if self.olevba_results["macro_found"]: vba_code_all_modules = "" macro_data = [] for ( v_filename, stream_path, vba_filename, vba_code, ) in self.vbaparser.extract_macros(): extracted_macro = { "filename": v_filename, "ole_stream": stream_path, "vba_filename": vba_filename, "vba_code": vba_code, } macro_data.append(extracted_macro) vba_code_all_modules += vba_code + "\n" self.olevba_results["macro_data"] = macro_data # example output # # {'description': 'Runs when the Word document is opened', # 'keyword': 'AutoOpen', # 'type': 'AutoExec'}, # {'description': 'May run an executable file or a system command', # 'keyword': 'Shell', # 'type': 'Suspicious'}, # {'description': 'May run an executable file or a system command', # 'keyword': 'WScript.Shell', # 'type': 'Suspicious'}, # {'description': 'May run an executable file or a system command', # 'keyword': 'Run', # 'type': 'Suspicious'}, # {'description': 'May run PowerShell commands', # 'keyword': 'powershell', # 'type': 'Suspicious'}, # {'description': '9BA55BE5', 'keyword': 'xxx', 'type': 'Hex String'}, # mraptor macro_raptor = mraptor.MacroRaptor(vba_code_all_modules) if macro_raptor: macro_raptor.scan() results["mraptor"] = ( "suspicious" if macro_raptor.suspicious else "ok" ) # analyze macros analyzer_results = self.vbaparser.analyze_macros() # it gives None if it does not find anything if analyzer_results: analyze_macro_results = [] for kw_type, keyword, description in analyzer_results: if kw_type != "Hex String": analyze_macro_result = { "type": kw_type, "keyword": keyword, "description": description, } analyze_macro_results.append(analyze_macro_result) self.olevba_results["analyze_macro"] = analyze_macro_results except CannotDecryptException as e: logger.info(e) except Exception as e: error_message = f"job_id {self.job_id} vba parser failed. Error: {e}" logger.exception(error_message) self.report["errors"].append(error_message) finally: if self.vbaparser: self.vbaparser.close() results["olevba"] = self.olevba_results return results
def run(self): results = {} # olevba olevba_results = {} try: vbaparser = VBA_Parser(self.filepath) olevba_results["macro_found"] = ( True if vbaparser.detect_vba_macros() else False ) if olevba_results["macro_found"]: macro_data = [] for ( v_filename, stream_path, vba_filename, vba_code, ) in vbaparser.extract_macros(): extracted_macro = { "filename": v_filename, "ole_stream": stream_path, "vba_filename": vba_filename, "vba_code": vba_code, } macro_data.append(extracted_macro) olevba_results["macro_data"] = macro_data # example output """ {'description': 'Runs when the Word document is opened', 'keyword': 'AutoOpen', 'type': 'AutoExec'}, {'description': 'May run an executable file or a system command', 'keyword': 'Shell', 'type': 'Suspicious'}, {'description': 'May run an executable file or a system command', 'keyword': 'WScript.Shell', 'type': 'Suspicious'}, {'description': 'May run an executable file or a system command', 'keyword': 'Run', 'type': 'Suspicious'}, {'description': 'May run PowerShell commands', 'keyword': 'powershell', 'type': 'Suspicious'}, {'description': '9BA55BE5', 'keyword': 'xxx', 'type': 'Hex String'}, """ analyzer_results = vbaparser.analyze_macros(show_decoded_strings=True) # it gives None if it does not find anything if analyzer_results: analyze_macro_results = [] for kw_type, keyword, description in analyzer_results: if kw_type != "Hex String": analyze_macro_result = { "type": kw_type, "keyword": keyword, "description": description, } analyze_macro_results.append(analyze_macro_result) olevba_results["analyze_macro"] = analyze_macro_results olevba_results["reveal"] = vbaparser.reveal() vbaparser.close() except Exception as e: traceback.print_exc() error_message = f"job_id {self.job_id} vba parser failed. Error: {e}" logger.exception(error_message) self.report["errors"].append(error_message) results["olevba"] = olevba_results # mraptor macro_raptor = mraptor.MacroRaptor(olevba_results.get("reveal", None)) if macro_raptor: macro_raptor.scan() results["mraptor"] = "suspicious" if macro_raptor.suspicious else "ok" return results
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