Commit 9a505c809823a462e0335415c368b1826d3b2246
1 parent
5dfb7b56
olevba: triage now uses VBA_Scanner results, shows Base64 and Dridex strings, ex…
…ception handling in detect_base64_strings
Showing
1 changed file
with
44 additions
and
14 deletions
oletools/olevba.py
| @@ -114,8 +114,11 @@ https://github.com/unixfreak0037/officeparser | @@ -114,8 +114,11 @@ https://github.com/unixfreak0037/officeparser | ||
| 114 | # 2015-02-01 v0.22 PL: - fixed issue #4: regex for URL, e-mail and exe filename | 114 | # 2015-02-01 v0.22 PL: - fixed issue #4: regex for URL, e-mail and exe filename |
| 115 | # - added Base64 obfuscation decoding (contribution from | 115 | # - added Base64 obfuscation decoding (contribution from |
| 116 | # @JamesHabben) | 116 | # @JamesHabben) |
| 117 | +# 2015-02-03 v0.23 PL: - triage now uses VBA_Scanner results, shows Base64 and | ||
| 118 | +# Dridex strings | ||
| 119 | +# - exception handling in detect_base64_strings | ||
| 117 | 120 | ||
| 118 | -__version__ = '0.22' | 121 | +__version__ = '0.23' |
| 119 | 122 | ||
| 120 | #------------------------------------------------------------------------------ | 123 | #------------------------------------------------------------------------------ |
| 121 | # TODO: | 124 | # TODO: |
| @@ -940,14 +943,19 @@ def detect_base64_strings(vba_code): | @@ -940,14 +943,19 @@ def detect_base64_strings(vba_code): | ||
| 940 | :param vba_code: str, VBA source code | 943 | :param vba_code: str, VBA source code |
| 941 | :return: list of str tuples (encoded string, decoded string) | 944 | :return: list of str tuples (encoded string, decoded string) |
| 942 | """ | 945 | """ |
| 946 | + #TODO: avoid matching simple hex strings as base64? | ||
| 943 | results = [] | 947 | results = [] |
| 944 | found = set() | 948 | found = set() |
| 945 | for match in re_base64_string.finditer(vba_code): | 949 | for match in re_base64_string.finditer(vba_code): |
| 946 | value = match.group() | 950 | value = match.group() |
| 947 | if value not in found: | 951 | if value not in found: |
| 948 | - decoded = base64.b64decode(value) | ||
| 949 | - results.append((value, decoded)) | ||
| 950 | - found.add(value) | 952 | + try: |
| 953 | + decoded = base64.b64decode(value) | ||
| 954 | + results.append((value, decoded)) | ||
| 955 | + found.add(value) | ||
| 956 | + except: | ||
| 957 | + # if an exception occurs, it is likely not a base64-encoded string | ||
| 958 | + pass | ||
| 951 | return results | 959 | return results |
| 952 | 960 | ||
| 953 | 961 | ||
| @@ -1076,6 +1084,20 @@ class VBA_Scanner (object): | @@ -1076,6 +1084,20 @@ class VBA_Scanner (object): | ||
| 1076 | results.append(('Dridex string', repr(decoded), encoded)) | 1084 | results.append(('Dridex string', repr(decoded), encoded)) |
| 1077 | return results | 1085 | return results |
| 1078 | 1086 | ||
| 1087 | + def scan_summary(self): | ||
| 1088 | + """ | ||
| 1089 | + Analyze the provided VBA code to detect suspicious keywords, | ||
| 1090 | + auto-executable macros, IOC patterns, obfuscation patterns | ||
| 1091 | + such as hex-encoded strings. | ||
| 1092 | + | ||
| 1093 | + :return: tuple with the number of items found for each category: | ||
| 1094 | + (autoexec, suspicious, IOCs, hex, base64, dridex) | ||
| 1095 | + """ | ||
| 1096 | + self.scan() | ||
| 1097 | + return (len(self.autoexec_keywords), len(self.suspicious_keywords), | ||
| 1098 | + len(self.iocs), len(self.hex_strings), len(self.base64_strings), | ||
| 1099 | + len(self.dridex_strings)) | ||
| 1100 | + | ||
| 1079 | 1101 | ||
| 1080 | 1102 | ||
| 1081 | def scan_vba(vba_code, include_hex_strings): | 1103 | def scan_vba(vba_code, include_hex_strings): |
| @@ -1397,6 +1419,8 @@ def process_file_triage (container, filename, data): | @@ -1397,6 +1419,8 @@ def process_file_triage (container, filename, data): | ||
| 1397 | nb_suspicious = 0 | 1419 | nb_suspicious = 0 |
| 1398 | nb_iocs = 0 | 1420 | nb_iocs = 0 |
| 1399 | nb_hexstrings = 0 | 1421 | nb_hexstrings = 0 |
| 1422 | + nb_base64strings = 0 | ||
| 1423 | + nb_dridexstrings = 0 | ||
| 1400 | # ftype = 'Other' | 1424 | # ftype = 'Other' |
| 1401 | message = '' | 1425 | message = '' |
| 1402 | try: | 1426 | try: |
| @@ -1406,22 +1430,28 @@ def process_file_triage (container, filename, data): | @@ -1406,22 +1430,28 @@ def process_file_triage (container, filename, data): | ||
| 1406 | for (subfilename, stream_path, vba_filename, vba_code) in vba.extract_macros(): | 1430 | for (subfilename, stream_path, vba_filename, vba_code) in vba.extract_macros(): |
| 1407 | nb_macros += 1 | 1431 | nb_macros += 1 |
| 1408 | if vba_code.strip() != '': | 1432 | if vba_code.strip() != '': |
| 1409 | - #TODO: same changes as scan_vba, or modify scan_vba to return these counts | ||
| 1410 | - nb_autoexec += len(detect_autoexec(vba_code)) | ||
| 1411 | - nb_suspicious += len(detect_suspicious(vba_code)) | ||
| 1412 | - nb_iocs += len(detect_patterns(vba_code)) | ||
| 1413 | - nb_hexstrings += len(detect_hex_strings(vba_code)) | 1433 | + scanner = VBA_Scanner(vba_code) |
| 1434 | + autoexec, suspicious, iocs, hexstrings, base64strings, dridex = scanner.scan_summary() | ||
| 1435 | + nb_autoexec += autoexec | ||
| 1436 | + nb_suspicious += suspicious | ||
| 1437 | + nb_iocs += iocs | ||
| 1438 | + nb_hexstrings += hexstrings | ||
| 1439 | + nb_base64strings += base64strings | ||
| 1440 | + nb_dridexstrings += dridex | ||
| 1414 | if vba.type == TYPE_OLE: | 1441 | if vba.type == TYPE_OLE: |
| 1415 | - flags = 'O' | 1442 | + flags = 'OLE:' |
| 1416 | else: | 1443 | else: |
| 1417 | - flags = 'X' | ||
| 1418 | - macros = autoexec = suspicious = iocs = hexstrings = '-' | 1444 | + flags = 'OpX:' |
| 1445 | + macros = autoexec = suspicious = iocs = hexstrings = base64strings = dridex = '-' | ||
| 1419 | if nb_macros: macros = 'M' | 1446 | if nb_macros: macros = 'M' |
| 1420 | if nb_autoexec: autoexec = 'A' | 1447 | if nb_autoexec: autoexec = 'A' |
| 1421 | if nb_suspicious: suspicious = 'S' | 1448 | if nb_suspicious: suspicious = 'S' |
| 1422 | if nb_iocs: iocs = 'I' | 1449 | if nb_iocs: iocs = 'I' |
| 1423 | if nb_hexstrings: hexstrings = 'H' | 1450 | if nb_hexstrings: hexstrings = 'H' |
| 1424 | - flags += '%s%s%s%s%s' % (macros, autoexec, suspicious, iocs, hexstrings) | 1451 | + if nb_base64strings: base64strings = 'B' |
| 1452 | + if nb_dridexstrings: dridex = 'D' | ||
| 1453 | + flags += '%s%s%s%s%s%s%s' % (macros, autoexec, suspicious, iocs, hexstrings, | ||
| 1454 | + base64strings, dridex) | ||
| 1425 | 1455 | ||
| 1426 | # macros = autoexec = suspicious = iocs = hexstrings = 'no' | 1456 | # macros = autoexec = suspicious = iocs = hexstrings = 'no' |
| 1427 | # if nb_macros: macros = 'YES:%d' % nb_macros | 1457 | # if nb_macros: macros = 'YES:%d' % nb_macros |
| @@ -1535,7 +1565,7 @@ def main(): | @@ -1535,7 +1565,7 @@ def main(): | ||
| 1535 | process_file_triage(container, filename, data) | 1565 | process_file_triage(container, filename, data) |
| 1536 | count += 1 | 1566 | count += 1 |
| 1537 | if not options.detailed_mode or options.triage_mode: | 1567 | if not options.detailed_mode or options.triage_mode: |
| 1538 | - print '\n(Flags: O=OLE, X=OpenXML, M=Macros, A=Auto-executable, S=Suspicious keywords, I=IOCs, H=Hex-encoded strings, ?=Unknown)\n' | 1568 | + print '\n(Flags: OpX=OpenXML, M=Macros, A=Auto-executable, S=Suspicious keywords, I=IOCs, H=Hex strings, B=Base64 strings, D=Dridex strings, ?=Unknown)\n' |
| 1539 | 1569 | ||
| 1540 | if count == 1 and not options.triage_mode and not options.detailed_mode: | 1570 | if count == 1 and not options.triage_mode and not options.detailed_mode: |
| 1541 | # if options -t and -d were not specified and it's a single file, print details: | 1571 | # if options -t and -d were not specified and it's a single file, print details: |