Commit 9afc59abd38d23a56d8d6f3ec12a2ac84374a3fb
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 | 60 | # 2016-07-30 PL: - new API with class RtfObject |
| 61 | 61 | # - backward-compatible API rtf_iter_objects (fixed issue #70) |
| 62 | 62 | # 2016-07-31 PL: - table output with tablestream |
| 63 | +# 2016-08-01 PL: - detect executable filenames in OLE Package | |
| 63 | 64 | |
| 64 | 65 | __version__ = '0.50' |
| 65 | 66 | |
| ... | ... | @@ -72,6 +73,7 @@ __version__ = '0.50' |
| 72 | 73 | # === IMPORTS ================================================================= |
| 73 | 74 | |
| 74 | 75 | import re, os, sys, binascii, logging, optparse |
| 76 | +import os.path | |
| 75 | 77 | |
| 76 | 78 | from thirdparty.xglob import xglob |
| 77 | 79 | from oleobj import OleObject, OleNativeStream |
| ... | ... | @@ -219,6 +221,10 @@ re_delims_bin_decimal = re.compile(DELIMITERS_ZeroOrMore + BACKSLASH_BIN |
| 219 | 221 | + DECIMAL_GROUP + DELIMITER) |
| 220 | 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 | 228 | # Destination Control Words, according to MS RTF Specifications v1.9.1: |
| 223 | 229 | DESTINATION_CONTROL_WORDS = frozenset(( |
| 224 | 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 | 610 | |
| 605 | 611 | |
| 606 | 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 | 613 | if output_dir: |
| 614 | 614 | if not os.path.isdir(output_dir): |
| 615 | 615 | log.info('creating output directory %s' % output_dir) |
| ... | ... | @@ -625,8 +625,13 @@ def process_file(container, filename, data, output_dir=None): |
| 625 | 625 | # TODO: option to extract objects to files (false by default) |
| 626 | 626 | if data is None: |
| 627 | 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 | 635 | rtfp = RtfObjParser(data) |
| 631 | 636 | rtfp.parse() |
| 632 | 637 | for rtfobj in rtfp.objects: |
| ... | ... | @@ -636,6 +641,7 @@ def process_file(container, filename, data, output_dir=None): |
| 636 | 641 | # fname = '%s_object_%08X.raw' % (fname_prefix, rtfobj.start) |
| 637 | 642 | # print('saving object to file %s' % fname) |
| 638 | 643 | # open(fname, 'wb').write(rtfobj.rawdata) |
| 644 | + pkg_color = None | |
| 639 | 645 | if rtfobj.is_ole: |
| 640 | 646 | ole_column = 'format_id: %d\n' % rtfobj.format_id |
| 641 | 647 | ole_column += 'class name: %r\n' % rtfobj.class_name |
| ... | ... | @@ -670,9 +676,17 @@ def process_file(container, filename, data, output_dir=None): |
| 670 | 676 | # fname = '%s_object_%08X.noname' % (fname_prefix, rtfobj.start) |
| 671 | 677 | # print('saving to file %s' % fname) |
| 672 | 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 | 686 | else: |
| 674 | 687 | pkg_column = 'Not an OLE Package' |
| 675 | 688 | else: |
| 689 | + pkg_column = '' | |
| 676 | 690 | ole_column = 'Not a well-formed OLE object' |
| 677 | 691 | tstream.write_row(( |
| 678 | 692 | rtfp.objects.index(rtfobj), |
| ... | ... | @@ -680,7 +694,8 @@ def process_file(container, filename, data, output_dir=None): |
| 680 | 694 | '%08Xh' % rtfobj.start, |
| 681 | 695 | ole_column, |
| 682 | 696 | pkg_column |
| 683 | - )) | |
| 697 | + ), colors=(None, None, None, pkg_color) | |
| 698 | + ) | |
| 684 | 699 | |
| 685 | 700 | |
| 686 | 701 | #=== MAIN ================================================================= | ... | ... |