Commit 209688eb7ca9580c3e8264865193f2a595d9972f
1 parent
31ce789a
rtfobj: added option -d to set the output directory (contribution by Thomas Jarosch)
Showing
1 changed file
with
22 additions
and
8 deletions
oletools/rtfobj.py
| @@ -48,8 +48,10 @@ http://www.decalage.info/python/oletools | @@ -48,8 +48,10 @@ http://www.decalage.info/python/oletools | ||
| 48 | # - extract files from OLE Package objects | 48 | # - extract files from OLE Package objects |
| 49 | # 2016-04-01 v0.04 PL: - fixed logging output to use stdout instead of stderr | 49 | # 2016-04-01 v0.04 PL: - fixed logging output to use stdout instead of stderr |
| 50 | # 2016-04-07 v0.45 PL: - improved parsing to handle some malware tricks | 50 | # 2016-04-07 v0.45 PL: - improved parsing to handle some malware tricks |
| 51 | +# 2016-05-06 v0.47 TJ: - added option -d to set the output directory | ||
| 52 | +# (contribution by Thomas Jarosch) | ||
| 51 | 53 | ||
| 52 | -__version__ = '0.45' | 54 | +__version__ = '0.47' |
| 53 | 55 | ||
| 54 | #------------------------------------------------------------------------------ | 56 | #------------------------------------------------------------------------------ |
| 55 | # TODO: | 57 | # TODO: |
| @@ -60,7 +62,7 @@ __version__ = '0.45' | @@ -60,7 +62,7 @@ __version__ = '0.45' | ||
| 60 | 62 | ||
| 61 | #=== IMPORTS ================================================================= | 63 | #=== IMPORTS ================================================================= |
| 62 | 64 | ||
| 63 | -import re, sys, string, binascii, logging, optparse | 65 | +import re, os, sys, string, binascii, logging, optparse |
| 64 | 66 | ||
| 65 | from thirdparty.xglob import xglob | 67 | from thirdparty.xglob import xglob |
| 66 | from oleobj import OleObject, OleNativeStream | 68 | from oleobj import OleObject, OleNativeStream |
| @@ -280,7 +282,16 @@ def rtf_iter_objects (data, min_size=32): | @@ -280,7 +282,16 @@ def rtf_iter_objects (data, min_size=32): | ||
| 280 | match = re_hexblock.search(data, pos=current) | 282 | match = re_hexblock.search(data, pos=current) |
| 281 | 283 | ||
| 282 | 284 | ||
| 283 | -def process_file(container, filename, data): | 285 | +def process_file(container, filename, data, output_dir=None): |
| 286 | + if output_dir: | ||
| 287 | + if not os.path.isdir(output_dir): | ||
| 288 | + log.info('creating output directory %s' % output_dir) | ||
| 289 | + os.mkdir(output_dir) | ||
| 290 | + | ||
| 291 | + fname_prefix = os.path.join(output_dir, os.path.basename(filename)) | ||
| 292 | + else: | ||
| 293 | + fname_prefix = filename | ||
| 294 | + | ||
| 284 | # TODO: option to extract objects to files (false by default) | 295 | # TODO: option to extract objects to files (false by default) |
| 285 | if data is None: | 296 | if data is None: |
| 286 | data = open(filename, 'rb').read() | 297 | data = open(filename, 'rb').read() |
| @@ -288,7 +299,7 @@ def process_file(container, filename, data): | @@ -288,7 +299,7 @@ def process_file(container, filename, data): | ||
| 288 | print 'File: %r - %d bytes' % (filename, len(data)) | 299 | print 'File: %r - %d bytes' % (filename, len(data)) |
| 289 | for index, orig_len, objdata in rtf_iter_objects(data): | 300 | for index, orig_len, objdata in rtf_iter_objects(data): |
| 290 | print 'found object size %d at index %08X - end %08X' % (len(objdata), index, index+orig_len) | 301 | print 'found object size %d at index %08X - end %08X' % (len(objdata), index, index+orig_len) |
| 291 | - fname = '%s_object_%08X.raw' % (filename, index) | 302 | + fname = '%s_object_%08X.raw' % (fname_prefix, index) |
| 292 | print 'saving object to file %s' % fname | 303 | print 'saving object to file %s' % fname |
| 293 | open(fname, 'wb').write(objdata) | 304 | open(fname, 'wb').write(objdata) |
| 294 | # TODO: check if all hex data is extracted properly | 305 | # TODO: check if all hex data is extracted properly |
| @@ -308,7 +319,8 @@ def process_file(container, filename, data): | @@ -308,7 +319,8 @@ def process_file(container, filename, data): | ||
| 308 | ext = 'package' | 319 | ext = 'package' |
| 309 | else: | 320 | else: |
| 310 | ext = 'bin' | 321 | ext = 'bin' |
| 311 | - fname = '%s_object_%08X.%s' % (filename, index, ext) | 322 | + |
| 323 | + fname = '%s_object_%08X.%s' % (fname_prefix, index, ext) | ||
| 312 | print 'saving to file %s' % fname | 324 | print 'saving to file %s' % fname |
| 313 | open(fname, 'wb').write(obj.data) | 325 | open(fname, 'wb').write(obj.data) |
| 314 | if obj.class_name.lower() == 'package': | 326 | if obj.class_name.lower() == 'package': |
| @@ -318,9 +330,9 @@ def process_file(container, filename, data): | @@ -318,9 +330,9 @@ def process_file(container, filename, data): | ||
| 318 | print 'Source path = %r' % opkg.src_path | 330 | print 'Source path = %r' % opkg.src_path |
| 319 | print 'Temp path = %r' % opkg.temp_path | 331 | print 'Temp path = %r' % opkg.temp_path |
| 320 | if opkg.filename: | 332 | if opkg.filename: |
| 321 | - fname = '%s_%s' % (filename, opkg.filename) | 333 | + fname = '%s_%s' % (fname_prefix, opkg.filename) |
| 322 | else: | 334 | else: |
| 323 | - fname = '%s_object_%08X.noname' % (filename, index) | 335 | + fname = '%s_object_%08X.noname' % (fname_prefix, index) |
| 324 | print 'saving to file %s' % fname | 336 | print 'saving to file %s' % fname |
| 325 | open(fname, 'wb').write(opkg.data) | 337 | open(fname, 'wb').write(opkg.data) |
| 326 | except: | 338 | except: |
| @@ -354,6 +366,8 @@ if __name__ == '__main__': | @@ -354,6 +366,8 @@ if __name__ == '__main__': | ||
| 354 | # help='export results to a CSV file') | 366 | # help='export results to a CSV file') |
| 355 | parser.add_option("-r", action="store_true", dest="recursive", | 367 | parser.add_option("-r", action="store_true", dest="recursive", |
| 356 | help='find files recursively in subdirectories.') | 368 | help='find files recursively in subdirectories.') |
| 369 | + parser.add_option("-d", type="str", dest="output_dir", | ||
| 370 | + help='use specified directory to output files.', default=None) | ||
| 357 | parser.add_option("-z", "--zip", dest='zip_password', type='str', default=None, | 371 | parser.add_option("-z", "--zip", dest='zip_password', type='str', default=None, |
| 358 | help='if the file is a zip archive, open first file from it, using the provided password (requires Python 2.6+)') | 372 | help='if the file is a zip archive, open first file from it, using the provided password (requires Python 2.6+)') |
| 359 | parser.add_option("-f", "--zipfname", dest='zip_fname', type='str', default='*', | 373 | parser.add_option("-f", "--zipfname", dest='zip_fname', type='str', default='*', |
| @@ -384,7 +398,7 @@ if __name__ == '__main__': | @@ -384,7 +398,7 @@ if __name__ == '__main__': | ||
| 384 | # ignore directory names stored in zip files: | 398 | # ignore directory names stored in zip files: |
| 385 | if container and filename.endswith('/'): | 399 | if container and filename.endswith('/'): |
| 386 | continue | 400 | continue |
| 387 | - process_file(container, filename, data) | 401 | + process_file(container, filename, data, options.output_dir) |
| 388 | 402 | ||
| 389 | 403 | ||
| 390 | 404 |