Commit 9afc59abd38d23a56d8d6f3ec12a2ac84374a3fb

Authored by decalage2
1 parent ce3c92cf

rtfobj: detect executable filenames in OLE Package

Showing 1 changed file with 24 additions and 9 deletions
oletools/rtfobj.py
@@ -60,6 +60,7 @@ http://www.decalage.info/python/oletools @@ -60,6 +60,7 @@ http://www.decalage.info/python/oletools
60 # 2016-07-30 PL: - new API with class RtfObject 60 # 2016-07-30 PL: - new API with class RtfObject
61 # - backward-compatible API rtf_iter_objects (fixed issue #70) 61 # - backward-compatible API rtf_iter_objects (fixed issue #70)
62 # 2016-07-31 PL: - table output with tablestream 62 # 2016-07-31 PL: - table output with tablestream
  63 +# 2016-08-01 PL: - detect executable filenames in OLE Package
63 64
64 __version__ = '0.50' 65 __version__ = '0.50'
65 66
@@ -72,6 +73,7 @@ __version__ = '0.50' @@ -72,6 +73,7 @@ __version__ = '0.50'
72 # === IMPORTS ================================================================= 73 # === IMPORTS =================================================================
73 74
74 import re, os, sys, binascii, logging, optparse 75 import re, os, sys, binascii, logging, optparse
  76 +import os.path
75 77
76 from thirdparty.xglob import xglob 78 from thirdparty.xglob import xglob
77 from oleobj import OleObject, OleNativeStream 79 from oleobj import OleObject, OleNativeStream
@@ -219,6 +221,10 @@ re_delims_bin_decimal = re.compile(DELIMITERS_ZeroOrMore + BACKSLASH_BIN @@ -219,6 +221,10 @@ re_delims_bin_decimal = re.compile(DELIMITERS_ZeroOrMore + BACKSLASH_BIN
219 + DECIMAL_GROUP + DELIMITER) 221 + DECIMAL_GROUP + DELIMITER)
220 re_delim_hexblock = re.compile(DELIMITER + PATTERN) 222 re_delim_hexblock = re.compile(DELIMITER + PATTERN)
221 223
  224 +# TODO: use a frozenset instead of a regex?
  225 +re_executable_extensions = re.compile(
  226 + r"(?i)\.(EXE|COM|PIF|GADGET|MSI|MSP|MSC|VBS|VBE|VB|JSE|JS|WSF|WSC|WSH|WS|BAT|CMD|DLL|SCR|HTA|CPL|CLASS|JAR|PS1XML|PS1|PS2XML|PS2|PSC1|PSC2|SCF|LNK|INF|REG)\b")
  227 +
222 # Destination Control Words, according to MS RTF Specifications v1.9.1: 228 # Destination Control Words, according to MS RTF Specifications v1.9.1:
223 DESTINATION_CONTROL_WORDS = frozenset(( 229 DESTINATION_CONTROL_WORDS = frozenset((
224 b"aftncn", b"aftnsep", b"aftnsepc", b"annotation", b"atnauthor", b"atndate", b"atnicn", b"atnid", b"atnparent", b"atnref", 230 b"aftncn", b"aftnsep", b"aftnsepc", b"annotation", b"atnauthor", b"atndate", b"atnicn", b"atnid", b"atnparent", b"atnref",
@@ -604,12 +610,6 @@ def sanitize_filename(filename, replacement='_', max_length=200): @@ -604,12 +610,6 @@ def sanitize_filename(filename, replacement='_', max_length=200):
604 610
605 611
606 def process_file(container, filename, data, output_dir=None): 612 def process_file(container, filename, data, output_dir=None):
607 - tstream = tablestream.TableStream(  
608 - column_width=(3, 10, 31, 31),  
609 - header_row=('id', 'index', 'OLE Object', 'OLE Package'),  
610 - style=tablestream.TableStyleSlim  
611 - )  
612 -  
613 if output_dir: 613 if output_dir:
614 if not os.path.isdir(output_dir): 614 if not os.path.isdir(output_dir):
615 log.info('creating output directory %s' % output_dir) 615 log.info('creating output directory %s' % output_dir)
@@ -625,8 +625,13 @@ def process_file(container, filename, data, output_dir=None): @@ -625,8 +625,13 @@ def process_file(container, filename, data, output_dir=None):
625 # TODO: option to extract objects to files (false by default) 625 # TODO: option to extract objects to files (false by default)
626 if data is None: 626 if data is None:
627 data = open(filename, 'rb').read() 627 data = open(filename, 'rb').read()
628 - # print('='*79)  
629 - # print('File: %r - %d bytes' % (filename, len(data))) 628 + print('='*79)
  629 + print('File: %r - size: %d bytes' % (filename, len(data)))
  630 + tstream = tablestream.TableStream(
  631 + column_width=(3, 10, 31, 31),
  632 + header_row=('id', 'index', 'OLE Object', 'OLE Package'),
  633 + style=tablestream.TableStyleSlim
  634 + )
630 rtfp = RtfObjParser(data) 635 rtfp = RtfObjParser(data)
631 rtfp.parse() 636 rtfp.parse()
632 for rtfobj in rtfp.objects: 637 for rtfobj in rtfp.objects:
@@ -636,6 +641,7 @@ def process_file(container, filename, data, output_dir=None): @@ -636,6 +641,7 @@ def process_file(container, filename, data, output_dir=None):
636 # fname = '%s_object_%08X.raw' % (fname_prefix, rtfobj.start) 641 # fname = '%s_object_%08X.raw' % (fname_prefix, rtfobj.start)
637 # print('saving object to file %s' % fname) 642 # print('saving object to file %s' % fname)
638 # open(fname, 'wb').write(rtfobj.rawdata) 643 # open(fname, 'wb').write(rtfobj.rawdata)
  644 + pkg_color = None
639 if rtfobj.is_ole: 645 if rtfobj.is_ole:
640 ole_column = 'format_id: %d\n' % rtfobj.format_id 646 ole_column = 'format_id: %d\n' % rtfobj.format_id
641 ole_column += 'class name: %r\n' % rtfobj.class_name 647 ole_column += 'class name: %r\n' % rtfobj.class_name
@@ -670,9 +676,17 @@ def process_file(container, filename, data, output_dir=None): @@ -670,9 +676,17 @@ def process_file(container, filename, data, output_dir=None):
670 # fname = '%s_object_%08X.noname' % (fname_prefix, rtfobj.start) 676 # fname = '%s_object_%08X.noname' % (fname_prefix, rtfobj.start)
671 # print('saving to file %s' % fname) 677 # print('saving to file %s' % fname)
672 # open(fname, 'wb').write(rtfobj.olepkgdata) 678 # open(fname, 'wb').write(rtfobj.olepkgdata)
  679 + pkg_color = 'yellow'
  680 + # check if the file extension is executable:
  681 + _, ext = os.path.splitext(rtfobj.filename)
  682 + log.debug('File extension: %r' % ext)
  683 + if re_executable_extensions.match(ext):
  684 + pkg_color = 'red'
  685 + pkg_column += '\nEXECUTABLE FILE'
673 else: 686 else:
674 pkg_column = 'Not an OLE Package' 687 pkg_column = 'Not an OLE Package'
675 else: 688 else:
  689 + pkg_column = ''
676 ole_column = 'Not a well-formed OLE object' 690 ole_column = 'Not a well-formed OLE object'
677 tstream.write_row(( 691 tstream.write_row((
678 rtfp.objects.index(rtfobj), 692 rtfp.objects.index(rtfobj),
@@ -680,7 +694,8 @@ def process_file(container, filename, data, output_dir=None): @@ -680,7 +694,8 @@ def process_file(container, filename, data, output_dir=None):
680 '%08Xh' % rtfobj.start, 694 '%08Xh' % rtfobj.start,
681 ole_column, 695 ole_column,
682 pkg_column 696 pkg_column
683 - )) 697 + ), colors=(None, None, None, pkg_color)
  698 + )
684 699
685 700
686 #=== MAIN ================================================================= 701 #=== MAIN =================================================================