def EXTRACT_VBA_MACRO(s, buff): EXTRACT_MACRO = {} counter = 0 ### TODO: REMOVE THIS WORKAROUND ONCE MODULE AUTHOR FIXES CODE ### ### Reference: http://stackoverflow.com/questions/32261679/strange-issue-using-logging-module-in-python/32264445#32264445 ### Reference: https://bitbucket.org/decalage/oletools/issues/26/use-of-logger ### /dev/null used instead of NullHandler for 2.6 compatibility logging.getLogger('workaround').root.addHandler( logging.FileHandler('/dev/null')) ### vba = VBA_Parser('None', data=buff) if not vba.detect_vba_macros(): return EXTRACT_MACRO for (filename, stream_path, vba_filename, vba_code) in vba.extract_macros(): CHILD_MACRO = OrderedDict([('OLE Stream', stream_path), ('VBA Filename', vba_filename.decode('ascii', 'ignore')), ('Scan', scan_macro(vba_code)), ('Buffer', vba_code)]) EXTRACT_MACRO['Object_%s' % counter] = CHILD_MACRO counter += 1 return EXTRACT_MACRO
def parseOLEDocument(f): """Parse an OLE document for VBA macros""" if not f or not useOLETools: return writeLog('DEBUG: Analyzing with oletools') try: v = VBA_Parser(f) except: writeLog("Not a supported file format: %s" % f) return writeLog('DEBUG: Detected file type: %s' % v.type) if v.detect_vba_macros(): writeLog('DEBUG: VBA Macros found') try: t = open("%s.analysis" % f, 'w') except IOError as e: writeLog("Cannot create analysis file %s.analysis: %s" % (f,e.strerror)) return for kw_type, keyword, description in v.analyze_macros(): t.write("%-12s | %-25s | %s\n" % (kw_type, keyword, description)) t.close() writeLog("DEBUG: Analysis dumped to %s.analysis" % f) else: writeLog('DEBUG: No VBA Macros found') return
def EXTRACT_VBA_MACRO(s, buff): EXTRACT_MACRO = {} counter = 0 ### TODO: REMOVE THIS WORKAROUND ONCE MODULE AUTHOR FIXES CODE ### ### Reference: http://stackoverflow.com/questions/32261679/strange-issue-using-logging-module-in-python/32264445#32264445 ### Reference: https://bitbucket.org/decalage/oletools/issues/26/use-of-logger ### /dev/null used instead of NullHandler for 2.6 compatibility logging.getLogger('workaround').root.addHandler(logging.FileHandler('/dev/null')) ### vba = VBA_Parser('None', data=buff) if not vba.detect_vba_macros(): return EXTRACT_MACRO for (filename, stream_path, vba_filename, vba_code) in vba.extract_macros(): CHILD_MACRO = OrderedDict([('OLE Stream', stream_path), ('VBA Filename', vba_filename.decode('ascii', 'ignore')), ('Scan', scan_macro(vba_code)), ('Buffer', vba_code)]) EXTRACT_MACRO['Object_%s' % counter] = CHILD_MACRO counter += 1 return EXTRACT_MACRO
def MacroHunter(targetFile): answerTable = PrettyTable() answerTable.field_names = [f"{green}Threat Levels{white}", f"{green}Macros{white}", f"{green}Descriptions{white}"] print(f"\n{infoS} Looking for VBA Macros...") try: fileData = open(targetFile, "rb").read() vbaparser = VBA_Parser(targetFile, fileData) macroList = list(vbaparser.analyze_macros()) if vbaparser.contains_macros == True: for fi in range(0, len(macroList)): if macroList[fi][0] == 'Suspicious': if "(use option --deobf to deobfuscate)" in macroList[fi][2]: sanitized = f"{macroList[fi][2]}".replace("(use option --deobf to deobfuscate)", "") answerTable.add_row([f"{yellow}{macroList[fi][0]}{white}", f"{macroList[fi][1]}", f"{sanitized}"]) elif "(option --decode to see all)" in macroList[fi][2]: sanitized = f"{macroList[fi][2]}".replace("(option --decode to see all)", "") answerTable.add_row([f"{yellow}{macroList[fi][0]}{white}", f"{macroList[fi][1]}", f"{sanitized}"]) else: answerTable.add_row([f"{yellow}{macroList[fi][0]}{white}", f"{macroList[fi][1]}", f"{macroList[fi][2]}"]) elif macroList[fi][0] == 'IOC': answerTable.add_row([f"{magenta}{macroList[fi][0]}{white}", f"{macroList[fi][1]}", f"{macroList[fi][2]}"]) elif macroList[fi][0] == 'AutoExec': answerTable.add_row([f"{red}{macroList[fi][0]}{white}", f"{macroList[fi][1]}", f"{macroList[fi][2]}"]) else: answerTable.add_row([f"{macroList[fi][0]}", f"{macroList[fi][1]}", f"{macroList[fi][2]}"]) print(f"{answerTable}\n") else: print(f"{errorS} Not any VBA macros found.") except: print(f"{errorS} An error occured while parsing that file for macro scan.")
def olevba_trig(file): try: vbaparser = VBA_Parser(file) if vbaparser.detect_vba_macros(): for (filename, stream_path, vba_filename, vba_code) in vbaparser.extract_macros(): print('Filename :', filename) print('OLE stream :', stream_path) print('VBA filename:', vba_filename) print('- ' * 39) print(vba_code) print('- ' * 39) results = vbaparser.analyze_macros() for kw_type, keyword, description in results: print('type=%s - keyword=%s - description=%s' % (kw_type, keyword, description)) print('AutoExec keywords: %d' % vbaparser.nb_autoexec) print('Suspicious keywords: %d' % vbaparser.nb_suspicious) print('IOCs: %d' % vbaparser.nb_iocs) print('Hex obfuscated strings: %d' % vbaparser.nb_hexstrings) print('Base64 obfuscated strings: %d' % vbaparser.nb_base64strings) print('Dridex obfuscated strings: %d' % vbaparser.nb_dridexstrings) print('VBA obfuscated strings: %d' % vbaparser.nb_vbastrings) print("\n") except: e = sys.exc_info()[0] print(f'{e} from {file}')
def _get_vba_parser(data): """Get an olevba VBA_Parser object for reading an Office file. This handles regular Office files and HTA files with VBScript script elements. @param data (str) The file contents for which to generate a VBA_Parser. @return (VBA_Parser object) On success, the olevba VBA_Parser object for the given file contents. On error, None. """ # First just try the most common case where olevba can directly get the VBA. vba = None try: vba = VBA_Parser('', data, relaxed=True) except Exception as e: if (log.getEffectiveLevel() == logging.DEBUG): log.debug("Creating VBA_PArser() Failed. Trying as HTA. " + safe_str_convert(e)) # If that did not work see if we can pull HTA wrapped VB from the data. extracted_data = get_vb_contents_from_hta(data) # If this throws an exception it will get passed up. vba = VBA_Parser('', extracted_data, relaxed=True) # Return the vba parser. return vba
def vba2graph_from_vba_object(filepath): """ vba2graph as library Args: filepath (string): path to file """ logger.info("Extracting macros from file") if HAVE_OLETOOLS: try: vba = VBA_Parser(filepath) except Exception as e: return False full_vba_code = "" for (subfilename, stream_path, vba_filename, vba_code) in vba.extract_macros(): full_vba_code += 'VBA MACRO %s \n' % vba_filename full_vba_code += '- ' * 39 + '\n' # Temporary workaround. Change when oletools 0.56 will be released. if isinstance(vba_code, bytes): vba_code = vba_code.decode('utf8', errors='replace') full_vba_code += vba_code vba.close() if full_vba_code: input_vba_content = handle_olevba_input(full_vba_code) return input_vba_content return False
def manage_encrypted_doc(self): self.olevba_results["is_encrypted"] = False # checks if it is an OLE file. That could be encrypted if self.vbaparser.ole_file: # check if the ole file is encrypted is_encrypted = self.vbaparser.detect_is_encrypted() self.olevba_results["is_encrypted"] = is_encrypted # in the case the file is encrypted I try to decrypt it # with the default password and the most common ones if is_encrypted: # by default oletools contains some basic passwords # we just add some more guesses common_pwd_to_check = [] for num in range(10): common_pwd_to_check.append(f"{num}{num}{num}{num}") # https://twitter.com/JohnLaTwC/status/1265377724522131457 filename_without_spaces_and_numbers = sub("[-_\d\s]", "", self.filename) filename_without_extension = sub( "(\..+)", "", filename_without_spaces_and_numbers ) common_pwd_to_check.append(filename_without_extension) self.passwords_to_check.extend(common_pwd_to_check) decrypted_file_name = self.vbaparser.decrypt_file( self.passwords_to_check ) self.olevba_results[ "additional_passwords_tried" ] = self.passwords_to_check if decrypted_file_name: self.vbaparser = VBA_Parser(decrypted_file_name) else: self.olevba_results["cannot_decrypt"] = True raise CannotDecryptException( "cannot decrypt the file with the default password" )
def compile_input_file(input_file: str) -> Program: path = Path(input_file) if path.is_dir(): # A directory must be a project containing VBA source files return Compiler.compile_project(input_file) with open(input_file, "rb") as f: content = f.read() # Try to deserialize an already compiled program try: serializer = Serializer() obj = serializer.deserialize(content) if type(obj) == Program: return obj except SerializationError: pass # Compile an Office document or VBA source files units = [] if magic_from_buffer(content, mime=True) == "text/plain": if not input_file.endswith(".vbs"): input_file += ".vbs" units.append(Unit.from_content(content.decode("utf-8"), input_file)) else: vba_parser = VBA_Parser(input_file, data=content) for _, _, vba_filename, vba_code in vba_parser.extract_all_macros(): units.append(Unit.from_content(vba_code, vba_filename)) return Compiler.compile_units(units)
def parseOLEDocument(f): """Parse an OLE document for VBA macros""" if not f or not useOLETools: return writeLog('DEBUG: Analyzing with oletools') try: v = VBA_Parser(f) except: writeLog("Not a supported file format: %s" % f) return writeLog('DEBUG: Detected file type: %s' % v.type) # Hack: Search for a .js extension fname, fextension = os.path.splitext(f) if v.detect_vba_macros() or fextension == ".js": writeLog('DEBUG: VBA Macros/JScript found') try: t = open("%s.analysis" % f, 'w') except IOError as e: writeLog("Cannot create analysis file %s.analysis: %s" % (f,e.strerror)) return for kw_type, keyword, description in v.analyze_macros(): t.write("%-12s | %-25s | %s\n" % (kw_type, keyword, description)) t.close() writeLog("DEBUG: Analysis dumped to %s.analysis" % f) else: writeLog('DEBUG: No VBA Macros found') return
def parse_vba(self, save_path): save = False vbaparser = VBA_Parser(__sessions__.current.file.path) # Check for Macros if not vbaparser.detect_vba_macros(): self.log('error', "No Macro's Detected") return self.log('info', "Macro's Detected") #try: if True: an_results = {'AutoExec':[], 'Suspicious':[], 'IOC':[], 'Hex String':[], 'Base64 String':[], 'Dridex String':[], 'VBA string':[]} for (filename, stream_path, vba_filename, vba_code) in vbaparser.extract_macros(): self.log('info', "Stream Details") self.log('item', "OLE Stream: {0}".format(string_clean(stream_path))) self.log('item', "VBA Filename: {0}".format(string_clean(vba_filename))) # Analyse the VBA Code vba_scanner = VBA_Scanner(vba_code) analysis = vba_scanner.scan(include_decoded_strings=True) for kw_type, keyword, description in analysis: an_results[kw_type].append([string_clean_hex(keyword), description]) # Save the code to external File if save_path: try: with open(save_path, 'a') as out: out.write(vba_code) save = True except: self.log('error', "Unable to write to {0}".format(save_path)) return # Print all Tables together self.log('info', "AutoRun Macros Found") self.log('table', dict(header=['Method', 'Description'], rows=an_results['AutoExec'])) self.log('info', "Suspicious Keywords Found") self.log('table', dict(header=['KeyWord', 'Description'], rows=an_results['Suspicious'])) self.log('info', "Possible IOC's") self.log('table', dict(header=['IOC', 'Type'], rows=an_results['IOC'])) self.log('info', "Hex Strings") self.log('table', dict(header=['Decoded', 'Raw'], rows=an_results['Hex String'])) self.log('info', "Base64 Strings") self.log('table', dict(header=['Decoded', 'Raw'], rows=an_results['Base64 String'])) self.log('info', "Dridex String") self.log('table', dict(header=['Decoded', 'Raw'], rows=an_results['Dridex String'])) self.log('info', "VBA string") self.log('table', dict(header=['Decoded', 'Raw'], rows=an_results['VBA string'])) if save: self.log('success', "Writing VBA Code to {0}".format(save_path)) #except: #self.log('error', "Unable to Process File") # Close the file vbaparser.close()
def process_file_scanexpr (container, filename, data): """ Process a single file :param container: str, path and filename of container if the file is within a zip archive, None otherwise. :param filename: str, path and filename of file on disk, or within the container. :param data: bytes, content of the file if it is in a container, None if it is a file on disk. """ #TODO: replace print by writing to a provided output file (sys.stdout by default) if container: display_filename = '%s in %s' % (filename, container) else: display_filename = filename print '='*79 print 'FILE:', display_filename all_code = '' try: #TODO: handle olefile errors, when an OLE file is malformed vba = VBA_Parser(filename, data) print 'Type:', vba.type if vba.detect_vba_macros(): #print 'Contains VBA Macros:' for (subfilename, stream_path, vba_filename, vba_code) in vba.extract_macros(): # hide attribute lines: #TODO: option to disable attribute filtering vba_code_filtered = filter_vba(vba_code) print '-'*79 print 'VBA MACRO %s ' % vba_filename print 'in file: %s - OLE stream: %s' % (subfilename, repr(stream_path)) print '- '*39 # detect empty macros: if vba_code_filtered.strip() == '': print '(empty macro)' else: # TODO: option to display code print vba_code_filtered vba_code = vba_collapse_long_lines(vba_code) all_code += '\n' + vba_code print '-'*79 print 'EVALUATED VBA EXPRESSIONS:' t = prettytable.PrettyTable(('Obfuscated expression', 'Evaluated value')) t.align = 'l' t.max_width['Obfuscated expression'] = 36 t.max_width['Evaluated value'] = 36 for expression, expr_eval in scan_expressions(all_code): t.add_row((repr(expression), repr(expr_eval))) print t else: print 'No VBA macros found.' except: #TypeError: #raise #TODO: print more info if debug mode #print sys.exc_value # display the exception with full stack trace for debugging, but do not stop: traceback.print_exc() print ''
def is_file_has_VBA_macros(self): """Check if file has VBA macros.""" file_path = self.file_path vbaparser = VBA_Parser(file_path) print('The file type is "%s"' % (vbaparser.type)) report = vbaparser.detect_vba_macros() vbaparser.close() return report
def test_samples(self): if sys.version_info[0] > 2: # Unfortunately, olevba3 doesn't have extract_form_strings_extended return for sample, expected_result in SAMPLES: full_name = join(DATA_BASE_DIR, 'oleform', sample) parser = VBA_Parser(full_name) variables = list(parser.extract_form_strings_extended()) self.assertEqual(variables, expected_result)
def getVBA(self, myfile, source='filepath'): ''' Given a file, parses out the stream paths, vba code, and vba filenames for each. :param myfile: filename :param source: type of data being passed in. Either "filepath" to indicate we need to read from disk or "filecontents" meaning that the file contents are being passed as a parameter. :return: pandas Series that can be used in concert with the pandas DataFrame apply method ''' if source == 'filepath': filedata = open(myfile, 'rb').read() else: filedata = myfile entry = {} try: vbaparser = VBA_Parser('mmbot', data=filedata) allcode = '' pathnames = '' filenames = '' if vbaparser.detect_vba_macros(): filenameslist = [] pathnameslist = [] vbacodelist = [] for (filename, stream_path, filename_vba, extracted_vba) in vbaparser.extract_macros(): vbacodelist.append(extracted_vba.decode("ascii", "ignore")) #vbacodelist.append(extracted_vba.decode('utf8', 'ignore')) if pathnames is None: pathnameslist.append( stream_path.decode("ascii", "ignore")) filenameslist.append( filename_vba.decode("ascii", "ignore")) else: pathnameslist.append( stream_path.decode("ascii", "ignore")) filenameslist.append( filename_vba.decode("ascii", "ignore")) allcode = "\n\n\n\n".join(vbacodelist) filenames = ", ".join(filenameslist) pathnames = ", ".join(pathnameslist) else: pathnames = 'No VBA Macros found' filenames = 'No VBA Macros found' allcode = 'No VBA Macros found' except Exception as e: pathnames = 'Error:' + str(e) filenames = 'Error:' + str(e) allcode = 'Error:' + str(e) return pd.Series({ 'extracted_vba': allcode, 'stream_path': pathnames, 'filename_vba': filenames })
def get_report(self): """ Return oletools report or create if not already cached. """ if self.sample.oletools_report is not None: return self.sample.oletools_report report = { 'autoexec': [], 'suspicious': [], } file_path = self.sample.file_path try: vbaparser = VBA_Parser(file_path) # VBA_Parser reports macros for office documents report['has_macros'] = vbaparser.detect_vba_macros( ) or vbaparser.detect_xlm_macros() try: report['vba'] = vbaparser.reveal() except TypeError: # office document with no macros pass all_macros = vbaparser.extract_all_macros() if (report['has_macros'] and len(all_macros) == 1 and isinstance(all_macros[0], tuple) and len(all_macros[0]) >= 3 and all_macros[0][2] == file_path): logger.warning( "Buggy oletools version detected, result overridden. May " "lead to false negatives, please update to fixed version") report['has_macros'] = False if vbaparser.detect_vba_macros(): vb_code = vbaparser.extract_all_macros() for (_, _, _, c) in vb_code: autoexec = detect_autoexec(c) if len(autoexec) >= 1: report['autoexec'].append(autoexec[0]) suspicious = detect_suspicious(c) if len(suspicious) >= 1: report['suspicious'].append(suspicious[0]) vbaparser.close() except IOError: raise except (TypeError, FileOpenError): # The given file is not an office document. pass except Exception as error: logger.exception(error) report = OletoolsReport(report) self.sample.register_oletools_report(report) return report
def parse_vba(self, save_path): save = False vba = VBA_Parser(__sessions__.current.file.path) # Check for Macros if not vba.detect_vba_macros(): self.log('error', "No Macro's Detected") return self.log('info', "Macro's Detected") try: run_rows = [] word_rows = [] pattern_rows = [] for (filename, stream_path, vba_filename, vba_code) in vba.extract_macros(): self.log('info', "Stream Details") self.log('item', "OLE Stream: {0}".format(string_clean(stream_path))) self.log('item', "VBA Filename: {0}".format(string_clean(vba_filename))) autoexec_keywords = detect_autoexec(vba_code) if autoexec_keywords: for keyword, description in autoexec_keywords: run_rows.append([keyword, description]) # Match Keyword Types suspicious_keywords = detect_suspicious(vba_code) if suspicious_keywords: for keyword, description in suspicious_keywords: word_rows.append([keyword, description]) # Match IOCs patterns = detect_patterns(vba_code) if patterns: for pattern_type, value in patterns: pattern_rows.append([pattern_type, value]) # Save the code to external File if save_path: try: with open(save_path, 'a') as out: out.write(vba_code) save = True except: self.log('Error', "Unable to write to {0}".format(save_path)) return # Print all Tables together self.log('info', "AutoRun Macros Found") self.log('table', dict(header=['KeyWord', 'Description'], rows=run_rows)) self.log('info', "Suspicious Keywords Found") self.log('table', dict(header=['KeyWord', 'Description'], rows=word_rows)) self.log('info', "Suspicious Patterns Found") self.log('table', dict(header=['Pattern', 'Value'], rows=pattern_rows)) if save: self.log('success', "Writing VBA Code to {0}".format(save_path)) except: self.log('Error', "Unable to Process File") # Close the file vba.close()
def vbaparsing(filename): vbafile = VBA_Parser(filename) results = "" for (filename, stream_path, vba_filename, vba_code) in vbafile.extract_macros(): results = results + vba_code result = vbafile.analyze_macros() for kw_type, keyword, description in result: results = results + 'type=%s - keyword=%s - description=%s' % ( kw_type, keyword, description) return results
def extract_src_codes(file): dic = {} parser = VBA_Parser(file) for (_, sname, _, code) in parser.extract_macros(): sname = sname.split("/")[-1] if sname not in dic: lines = code.split("\n")[1: ] dic[sname] = "\n".join(lines) # remove the first line else: printError(f"Error in src_codes: repeat {sname}") return dic
def vba2graph_from_vba_object(filepath): logging.info("Extracting macros from file") full_vba_code = "" vba_parser = VBA_Parser(filepath) for (subfilename, stream_path, vba_filename, vba_code) in vba_parser.extract_macros(): # workaround till oletools version 0.56 if isinstance(vba_code, bytes): vba_code = vba_code.decode('latin1', errors='replace') full_vba_code += vba_code vba_parser.close() return full_vba_code
def parse(file_path): vba_folder = 'src' vba_parser = VBA_Parser(file_path) for _, _, vba_filename, vba_code in vba_parser.extract_macros(): if not os.path.isdir(vba_folder): os.makedirs(vba_folder) with open(os.path.join(vba_folder, vba_filename), 'w', encoding='utf-8', newline='\n') as file: file.write(vba_code) vba_parser.close()
def POE(logdir, targetfile, logging, debug): if (logging == True): LOG = logger() newlogentry = '' macro_dump_data = '' FI = fileio() try: filedata = open(targetfile.filename, 'rb').read() vbaparser = VBA_Parser(targetfile.filename, data=filedata) if vbaparser.detect_vba_macros(): print '[*] VBA macros found - Extracting...\n' if (logging == True): newlogentry = 'VBA macros found - Extracting...' LOG.WriteLog(logdir, targetfile.filename, newlogentry) for (filename, stream_path, vba_filename, vba_code) in vbaparser.extract_macros(): macro_dump_data += '-' * 79 + '\n' try: macro_dump_data += 'Filename :' + filename.encode("ascii", "replace") + '\n' macro_dump_data += 'OLE stream :' + stream_path.encode("ascii", "replace") + '\n' macro_dump_data += 'VBA filename:' + vba_filename.encode("ascii", "replace") + '\n' except Exception, e: print '[x] Current macro - unable to print Filename, OLE stream or VBA filename due to encoding issue (Unicode?): ', e macro_dump_data += '-' * 79 + '\n' macro_dump_data += vba_code FI.WriteLogFile(logdir + vba_filename, macro_dump_data) try: print '[*] Macro ' + vba_filename.encode("ascii", "replace") + ' extracted to: ' + logdir + vba_filename.encode("ascii", "replace") targetfile.macros.append(logdir + vba_filename.encode("ascii", "replace")) if (logging == True): newlogentry = 'Macro ' + vba_filename.encode("ascii", "replace") + ' extracted to: <a href=\"' + logdir + vba_filename.encode("ascii", "replace") + '\">' + vba_filename.encode("ascii", "replace") + '</a>' LOG.WriteLog(logdir, targetfile.filename, newlogentry) if (debug == True): print '-'*79 print 'Filename :', filename.encode("ascii", "replace") print 'OLE stream :', stream_path.encode("ascii", "replace") print 'VBA filename:', vba_filename.encode("utf-8", "ignore") print '-'*79 except Exception, e: print '[x] Current macro - unable print Filename, OLE stream or VBA filename due to encoding issue: (Unicode?)', e if (debug == True): print vba_code macro_dump_data = '' print 'Macro List' for mlist in targetfile.macros: print mlist
def writeVBA(workbookName): vbaparser = VBA_Parser('./'+workbookName) VBA_path = './src/VBA' if os.path.exists(VBA_path): shutil.rmtree(VBA_path) os.mkdir(VBA_path) for (filename, stream_path, vba_filename, vba_code) in vbaparser.extract_macros(): f = open('./src/VBA/' + vba_filename, "w") f.write(vba_code) f.close() vbaparser.close()
def get_macro(self): """ Get Macros from an Office file and write them to a text file """ try: self.mk_tmp_dir() print "Getting Macros from {}".format(self.file) vb = VBA_Parser(self.file, relaxed=True) if vb.detect_vba_macros(): with open("{}{}macros.txt".format(self.tmp_dir, os.sep), "w") as macro_file: for (subfilename, stream_path, vba_filename, vba_code) in vb.extract_all_macros(): macro_file.write(vba_code) except Exception as e: print "get_macro Exception: {}".format(e)
def check_macros(doc_path, filename) -> dict: try: vbaparser = VBA_Parser(doc_path) if not vbaparser.detect_vba_macros(): return "" logging.warning("VBA macros in \"%s\"" % filename) details = "Macro Results\n" suspicious_count = 0 suspicious_list = [] ioc_list = [] ioc_count = 0 mal_score = 0 logging.disable(level=logging.CRITICAL) results = vbaparser.analyze_macros() logging.disable(level=logging.NOTSET) for kw_type, keyword, description in results: details += "\t - " + kw_type + " - (" + keyword + ") - " + description + "\n" if kw_type.lower() == "suspicious": suspicious_count += 1 suspicious_list.append(keyword) elif kw_type.lower() == "ioc": ioc_count += 1 ioc_list.append(keyword) if suspicious_count != 0: logging.warning("Found %d suspicious items in \"%s\"" % (suspicious_count, filename)) mal_score += suspicious_count if ioc_count != 0: logging.warning("Found %d IOCs: %s, in \"%s\"" % (ioc_count, ioc_list, filename)) mal_score += ioc_count * 2 return {'details': details, 'mal_score': mal_score} except Exception as e: logging.error("Failed to parse macros for: {} - {}".format( filename, e)) return
def get_vba_source_from_excel(filename): vbaparser = VBA_Parser(filename) if vbaparser.detect_vba_macros(): print('VBA Macros found') else: print('No VBA Macros found') for (filename, stream_path, vba_filename, vba_code) in vbaparser.extract_macros(): print('-' * 79) print('Filename :', filename) print('OLE stream :', stream_path) print('VBA filename:', vba_filename) print('- ' * 39) print(vba_code)
def get_all_macros(filename_wchar): """ Open a file and extract the source code of all VBA modules by calling oletools.VBA_Parser.get_vba_code_all_modules() :param filename_wchar: file to be opened (unicode wchar_t string) :return: source code of all VBA modules (unicode wchar_t string) """ # print(wchar_string) filename_str = ffi.string(filename_wchar) print("filename: {}".format(filename_str)) ovba = VBA_Parser(filename_str) # TODO: check if there are macros vba_code_str = ovba.get_vba_code_all_modules() # convert python str to C string return ffi.new("wchar_t[]", vba_code_str)
def process(self, sample, tag): try: hashbot = Hasher(sample) mymagic = magic.Magic(mime=True) sample_info = self.identify_sample(oletools.oleid.OleID(sample)) vba = VBA_Parser(sample) # Extend the results of sample_info with extra data and add to Splunk sample_info['submit_date'] = int(time()) sample_info['filetype'] = mymagic.from_file(sample) sample_info['sample_type'] = 'office' sample_info['id_tag'] = tag sample_info['sha1'] = hashbot.get_sha1() sample_info['md5'] = hashbot.get_md5() indicators = dict() if self.doc_has_macros(vba): macro = self.macro_extraction(vba) data = VBA_Scanner(macro['vba_code']).scan(include_decoded_strings=False) i = 1 for kw_type, keyword, description in data: indicators[str(i)] = {'type':kw_type, 'keyword':keyword, 'description':description } i+=1 sample_info['indicators'] = indicators self.share_data(sample_info) self.store_sample(sample) except Exception, e: print "Error on",sample,e
def doc_parsing(self, filename, filecontent): ''' Function to parse the given data in mail content ''' mil_attach = '' # reset var # send data to vba parser vbaparser = VBA_Parser(filename, data=filecontent) # if a macro is detected if vbaparser.detect_vba_macros(): results = vbaparser.analyze_macros() nr = 1 self.log("VBA Macros found") # generate report for log file for kw_type, keyword, description in results: if kw_type == 'Suspicious': mil_attach += 'Macro Number %i:\n Type: %s\n Keyword: %s\n Description: %s\n' % (nr, kw_type, keyword, description) nr += 1 mil_attach += '\nSummery:\nAutoExec keywords: %d\n' % vbaparser.nb_autoexec mil_attach += 'Suspicious keywords: %d\n' % vbaparser.nb_suspicious mil_attach += 'IOCs: %d\n' % vbaparser.nb_iocs mil_attach += 'Hex obfuscated strings: %d\n' % vbaparser.nb_hexstrings mil_attach += 'Base64 obfuscated strings: %d\n' % vbaparser.nb_base64strings mil_attach += 'Dridex obfuscated strings: %d\n' % vbaparser.nb_dridexstrings mil_attach += 'VBA obfuscated strings: %d' % vbaparser.nb_vbastrings r_level = vbaparser.nb_autoexec + vbaparser.nb_suspicious + vbaparser.nb_iocs + vbaparser.nb_hexstrings + vbaparser.nb_base64strings + vbaparser.nb_dridexstrings + vbaparser.nb_vbastrings # set reject level to global self.level = r_level vbaparser.close() return mil_attach # return the log to caller else: self.log("VBA no Macros found in file") vbaparser.close() return None # nothing found
def get_report(self, sample): """ Return oletools report or create if not already cached. """ if sample.oletools_report != None: return sample.oletools_report report = {} try: vbaparser = VBA_Parser(sample.file_path) # VBA_Parser reports macros for office documents report['has_macros'] = vbaparser.detect_vba_macros( ) or vbaparser.detect_xlm_macros() try: report['vba'] = vbaparser.reveal() except TypeError: # no macros pass vbaparser.close() except IOError: raise except (TypeError, FileOpenError): # The given file is not an office document. pass except Exception as error: logger.exception(error) sample.register_oletools_report(OletoolsReport(report)) return report
def _run(self, scanObject, result, depth, args): moduleResult = [] vbap_buffer = VBA_Parser(scanObject.buffer) try: if vbap_buffer.detect_vba_macros(): vbap_result = vbap_buffer.analyze_macros() for kw_type, keyword, description in vbap_result: kw = '%s - %s' % ( keyword,description ) scanObject.addMetadata(self.module_name,kw_type,kw) except (QuitScanException, GlobalScanTimeoutError, GlobalModuleTimeoutError): raise except: logging.debug("Failed to parse OLEVBA") vbap_buffer.close() return moduleResult
def run(self, obj, config): username = self.current_task.username filename = obj.filename filedata = obj.filedata.read() try: vbaparser = VBA_Parser(filename, data=filedata) except Exception, e: self._error("Cannot parse file: %s" % str(e)) return
def manage_xlm_macros(self): self.olevba_results["xlm_macro"] = False # check if the file contains an XLM macro # and try an experimental parsing # credits to https://twitter.com/gabriele_pippi for the idea if self.vbaparser.detect_xlm_macros(): self.olevba_results["xlm_macro"] = True logger.debug("experimental XLM macro analysis start") parsed_file = b"" try: excel_doc = XLSWrapper2(self.filepath) ae_list = [ "auto_open", "auto_close", "auto_activate", "auto_deactivate", ] self.olevba_results["xlm_macro_autoexec"] = [] for ae in ae_list: auto_exec_labels = excel_doc.get_defined_name( ae, full_match=False) for label in auto_exec_labels: self.olevba_results["xlm_macro_autoexec"].append( label[0]) for i in show_cells(excel_doc): rec_str = "" if len(i) == 5: # rec_str = 'CELL:{:10}, {:20}, {}' # .format(i[0].get_local_address(), i[2], i[4]) if i[2] != "None": rec_str = "{:20}".format(i[2]) if rec_str: parsed_file += rec_str.encode() parsed_file += b"\n" except Exception as e: logger.info( f"experimental XLM macro analysis failed. Exception: {e}") else: logger.debug(f"experimental XLM macro analysis succeded. " f"Binary to analyze: {parsed_file}") if parsed_file: self.vbaparser = VBA_Parser(self.filename, data=parsed_file)
def vba2graph_from_vba_object(filepath): """ vba2graph as library Args: filepath (string): path to file """ if HAVE_OLETOOLS: try: vba = VBA_Parser(filepath) except Exception as e: return False full_vba_code = "" for (subfilename, stream_path, vba_filename, vba_code) in vba.extract_macros(): full_vba_code += 'VBA MACRO %s \n' % vba_filename full_vba_code += '- '*39 + '\n' full_vba_code += vba_code vba.close() if full_vba_code: input_vba_content = handle_olevba_input(full_vba_code) return input_vba_content return False
def MacroHunter(targetFile): answerTable = PrettyTable() answerTable.field_names = [f"{green}Threat Levels{white}", f"{green}Macros{white}", f"{green}Descriptions{white}"] print(f"\n{infoS} Looking for VBA Macros...") fileData = open(targetFile, "rb").read() vbaparser = VBA_Parser(targetFile, data=fileData) if vbaparser.contains_macros == True: macroList = list(vbaparser.analyze_macros()) for fi in range(0, len(macroList)): if macroList[fi][0] == 'Suspicious': answerTable.add_row([f"{yellow}{macroList[fi][0]}{white}", f"{macroList[fi][1]}", f"{macroList[fi][2]}"]) elif macroList[fi][0] == 'IOC': answerTable.add_row([f"{magenta}{macroList[fi][0]}{white}", f"{macroList[fi][1]}", f"{macroList[fi][2]}"]) elif macroList[fi][0] == 'AutoExec': answerTable.add_row([f"{red}{macroList[fi][0]}{white}", f"{macroList[fi][1]}", f"{macroList[fi][2]}"]) else: answerTable.add_row([f"{macroList[fi][0]}", f"{macroList[fi][1]}", f"{macroList[fi][2]}"]) print(f"{answerTable}\n") else: print(f"{errorS} Not any macros found.")
def has_office_macros(office_file): """ Detects macros in Microsoft Office documents. :param office_file: The MS Office document to check for macros. :return: True if macros where found, otherwise False. If VBA_Parser crashes it returns False too. """ file_extension = office_file.split('.')[-1] if file_extension not in ms_office_extensions: return False try: # VBA_Parser reports macros for office documents vbaparser = VBA_Parser(office_file) return vbaparser.detect_vba_macros() except TypeError: # The given file is not an office document. return False except Exception as e: logger.exception(e) return False
def _ole_analysis(full_targ_path): # This function calls a number of tools / scripts to run against document samples containing OLE data and extracts data and/or performs analysis as needed. try: vba_parse_Obj = VBA_Parser(full_targ_path) except AttributeError: return("ERROR_PARSING", "ERROR_PARSING", "ERROR_PARSING", "ERROR_PARSING") macro_analysis_over = [] macro_analysis_info = [] if vba_parse_Obj.detect_vba_macros(): vba_macro = "Present" # Utilizing oletools to perform analysis. # Grabbing info from each macro. MA_CNT = 1 for (file_name, ole_stream, vba_filename, vba_code) in vba_parse_Obj.extract_macros(): macro_analysis_over.append(str(MA_CNT)+':'+str(full_targ_path)) macro_analysis_over.append(str(MA_CNT)+":Filename :"+file_name) macro_analysis_over.append(str(MA_CNT)+":OLE Stream :"+ole_stream) macro_analysis_over.append(str(MA_CNT)+":VBA Filename :"+vba_filename) macro_analysis_over.append(str(MA_CNT)+':'+vba_code) MA_CNT+=1 # Grabbing some overall VBA analysis info. macro_flag_types = [] macro_analysis_res = vba_parse_Obj.analyze_macros() if isinstance(macro_analysis_res, list): for iocType in macro_analysis_res: if str(iocType) not in macro_flag_types: macro_flag_types.append(str(iocType[0])) if len(macro_flag_types) > 0: iocs = ':'.join(list(set(macro_flag_types))) else: iocs = "None" else: vba_macro = "None" iocs = "None" macro_analysis_res = "None" vba_parse_Obj.close() return(vba_macro, macro_analysis_over, str(macro_analysis_res), iocs)
def inspect_vba_data(self, filename, filecontent): ''' Function to parse the given data in mail content ''' vbaparser_report_log = '' # reset var # send data to vba parser vbaparser = VBA_Parser(filename, data=filecontent) # if a macro is detected if not vbaparser.detect_vba_macros(): self.log("VBA no Macros found in file") vbaparser.close() return None # nothing found else: results = vbaparser.analyze_macros() nr = 1 self.log("VBA Macros found") # generate report for log file for kw_type, keyword, description in results: if kw_type == 'Suspicious': vbaparser_report_log += 'Macro Number %i:\n Type: %s\n Keyword: %s\n Description: %s\n' % ( nr, kw_type, keyword, description) nr += 1 vbaparser_report_log += '\nSummery:\nAutoExec keywords: %d\n' % vbaparser.nb_autoexec vbaparser_report_log += 'Suspicious keywords: %d\n' % vbaparser.nb_suspicious vbaparser_report_log += 'IOCs: %d\n' % vbaparser.nb_iocs vbaparser_report_log += 'Hex obfuscated strings: %d\n' % vbaparser.nb_hexstrings vbaparser_report_log += 'Base64 obfuscated strings: %d\n' % vbaparser.nb_base64strings vbaparser_report_log += 'Dridex obfuscated strings: %d\n' % vbaparser.nb_dridexstrings vbaparser_report_log += 'VBA obfuscated strings: %d' % vbaparser.nb_vbastrings # TBD: calculate a better level - add additional values to Base64 and Hex. No human writes such strings into code. r_level = vbaparser.nb_autoexec + vbaparser.nb_suspicious + vbaparser.nb_iocs + vbaparser.nb_hexstrings + vbaparser.nb_base64strings + vbaparser.nb_dridexstrings + vbaparser.nb_vbastrings # set reject level to global #self.level = r_level vbaparser.close() return [r_level, vbaparser_report_log] # return the log to caller
return suspicious # Window to select a file for analysis errmsg = 'Error!' file_win = Tkinter.Tk() D = Tkinter.Button( text = 'Choose a file', fg = 'darkgreen', command = sus_call) D.pack() Tkinter.Button( text = 'Start Analysis', fg = 'darkorange', command = file_win.quit).pack() file_win.mainloop() #vbaparser = VBA_Parser(sys.argv[1]) vbaparser = VBA_Parser(suspicious) #sus_file = sys.argv[1] # sus_file suspicious_file sus_file = suspicious ## Other way to do the same sus_filedata = open(sus_file, 'rb').read() vbaparser = VBA_Parser(sus_file, data=sus_filedata) ## Manual Macro extraction details ##for (sys.argv[1], stream_path, vba_filename, vba_code) in vbaparser.extract_macros(): ## print '-' * 79 ## print 'Filename :', sys.argv[1] ## print 'OLE stream :', stream_path ## print 'VBA filename :', vba_filename ## print '- ' * 39 ## print vba_code
def ProcessFile(path): if not(os.path.isfile(path)): print '{0} not a file!'.format(path) return 2 try: data = {} data['valid'] = True oledata = {} vbaparser = VBA_Parser(path) oledata['has_macros'] = vbaparser.detect_vba_macros() # dump macros content macros = [] for (filename, stream_path, vba_filename, vba_code) in vbaparser.extract_macros(): macro = {} macro['filename'] = filename macro['stream'] = stream_path macro['vba'] = vba_filename macro['content'] = convert_to_printable_null_terminated(vba_code) macros.append(macro) oledata['macros'] = macros # macro analysis macros_warnings = [] results = vbaparser.analyze_macros() for kw_type, keyword, description in results: warning = {} warning['type'] = kw_type warning['keyword'] = keyword warning['description'] = description macros_warnings.append(warning) oledata['macros_warnings'] = macros_warnings # counters counters = {} counters['autoexec'] = vbaparser.nb_autoexec counters['suspicious'] = vbaparser.nb_suspicious counters['iocs'] = vbaparser.nb_iocs counters['hexstrings'] = vbaparser.nb_hexstrings counters['base64strings'] = vbaparser.nb_base64strings counters['dridexstrings'] = vbaparser.nb_dridexstrings counters['vbastrings'] = vbaparser.nb_vbastrings oledata['counters'] = counters # deobfuscation oledata['deobfuscated'] = convert_to_printable_null_terminated(vbaparser.reveal()) # close vbaparser.close() data['data'] = oledata encoded = json.dumps(data) print encoded except Exception as ex: data = {} data['valid'] = False data['error'] = str(ex) print json.dumps(data) return 1 return 0