Commit e9dca546b053f7c7a8be921255a7bc0361e22425

Authored by decalage2
1 parent 7d06413c

mraptor3: replaced by a redirection to mraptor + deprecation warning (issue #106)

Showing 1 changed file with 6 additions and 336 deletions
oletools/mraptor3.py
1 1 #!/usr/bin/env python
2   -"""
3   -mraptor.py - MacroRaptor
4 2  
5   -MacroRaptor is a script to parse OLE and OpenXML files such as MS Office
6   -documents (e.g. Word, Excel), to detect malicious macros.
  3 +# mraptor3 is a stub that redirects to mraptor.py, for backwards compatibility
7 4  
8   -Supported formats:
9   -- Word 97-2003 (.doc, .dot), Word 2007+ (.docm, .dotm)
10   -- Excel 97-2003 (.xls), Excel 2007+ (.xlsm, .xlsb)
11   -- PowerPoint 97-2003 (.ppt), PowerPoint 2007+ (.pptm, .ppsm)
12   -- Word/PowerPoint 2007+ XML (aka Flat OPC)
13   -- Word 2003 XML (.xml)
14   -- Word/Excel Single File Web Page / MHTML (.mht)
15   -- Publisher (.pub)
  5 +import sys, os, warnings
16 6  
17   -Author: Philippe Lagadec - http://www.decalage.info
18   -License: BSD, see source code or documentation
19   -
20   -MacroRaptor is part of the python-oletools package:
21   -http://www.decalage.info/python/oletools
22   -"""
23   -
24   -# === LICENSE ==================================================================
25   -
26   -# MacroRaptor is copyright (c) 2016-2018 Philippe Lagadec (http://www.decalage.info)
27   -# All rights reserved.
28   -#
29   -# Redistribution and use in source and binary forms, with or without modification,
30   -# are permitted provided that the following conditions are met:
31   -#
32   -# * Redistributions of source code must retain the above copyright notice, this
33   -# list of conditions and the following disclaimer.
34   -# * Redistributions in binary form must reproduce the above copyright notice,
35   -# this list of conditions and the following disclaimer in the documentation
36   -# and/or other materials provided with the distribution.
37   -#
38   -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
39   -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
40   -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
41   -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
42   -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
43   -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
44   -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
45   -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46   -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
47   -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48   -
49   -#------------------------------------------------------------------------------
50   -# CHANGELOG:
51   -# 2016-02-23 v0.01 PL: - first version
52   -# 2016-02-29 v0.02 PL: - added Workbook_Activate, FileSaveAs
53   -# 2016-03-04 v0.03 PL: - returns an exit code based on the overall result
54   -# 2016-03-08 v0.04 PL: - collapse long lines before analysis
55   -# 2016-07-19 v0.50 SL: - converted to Python 3
56   -# 2016-08-26 PL: - changed imports for Python 3
57   -# 2017-04-26 v0.51 PL: - fixed absolute imports (issue #141)
58   -# 2017-06-29 PL: - synced with mraptor.py 0.51
59   -# 2018-05-25 v0.53 PL: - added Word/PowerPoint 2007+ XML (aka Flat OPC) issue #283
60   -
61   -__version__ = '0.53'
62   -
63   -#------------------------------------------------------------------------------
64   -# TODO:
65   -
66   -
67   -#--- IMPORTS ------------------------------------------------------------------
68   -
69   -import sys, os, logging, optparse, re
  7 +warnings.warn('mraptor3 is deprecated, mraptor should be used instead.', DeprecationWarning)
70 8  
71 9 # IMPORTANT: it should be possible to run oletools directly as scripts
72 10 # in any directory without installing them with pip or setup.py.
... ... @@ -74,280 +12,12 @@ import sys, os, logging, optparse, re
74 12 # And to enable Python 2+3 compatibility, we need to use absolute imports,
75 13 # so we add the oletools parent folder to sys.path (absolute+normalized path):
76 14 _thismodule_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__)))
77   -# print('_thismodule_dir = %r' % _thismodule_dir)
78 15 _parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..'))
79   -# print('_parent_dir = %r' % _thirdparty_dir)
80   -if not _parent_dir in sys.path:
  16 +if _parent_dir not in sys.path:
81 17 sys.path.insert(0, _parent_dir)
82 18  
83   -from oletools.thirdparty.xglob import xglob
84   -from oletools.thirdparty.tablestream import tablestream
85   -
86   -# import the python 3 version of olevba
87   -from oletools import olevba3 as olevba
88   -from oletools.olevba3 import TYPE2TAG
89   -
90   -# === LOGGING =================================================================
91   -
92   -# a global logger object used for debugging:
93   -log = olevba.get_logger('mraptor')
94   -
95   -
96   -#--- CONSTANTS ----------------------------------------------------------------
97   -
98   -# URL and message to report issues:
99   -# TODO: make it a common variable for all oletools
100   -URL_ISSUES = 'https://github.com/decalage2/oletools/issues'
101   -MSG_ISSUES = 'Please report this issue on %s' % URL_ISSUES
102   -
103   -# 'AutoExec', 'AutoOpen', 'Auto_Open', 'AutoClose', 'Auto_Close', 'AutoNew', 'AutoExit',
104   -# 'Document_Open', 'DocumentOpen',
105   -# 'Document_Close', 'DocumentBeforeClose', 'Document_BeforeClose',
106   -# 'DocumentChange','Document_New',
107   -# 'NewDocument'
108   -# 'Workbook_Open', 'Workbook_Close',
109   -# *_Painted such as InkPicture1_Painted
110   -# *_GotFocus|LostFocus|MouseHover for other ActiveX objects
111   -# reference: http://www.greyhathacker.net/?p=948
112   -
113   -# TODO: check if line also contains Sub or Function
114   -re_autoexec = re.compile(r'(?i)\b(?:Auto(?:Exec|_?Open|_?Close|Exit|New)' +
115   - r'|Document(?:_?Open|_Close|_?BeforeClose|Change|_New)' +
116   - r'|NewDocument|Workbook(?:_Open|_Activate|_Close)' +
117   - r'|\w+_(?:Painted|Painting|GotFocus|LostFocus|MouseHover' +
118   - r'|Layout|Click|Change|Resize|BeforeNavigate2|BeforeScriptExecute' +
119   - r'|DocumentComplete|DownloadBegin|DownloadComplete|FileDownload' +
120   - r'|NavigateComplete2|NavigateError|ProgressChange|PropertyChange' +
121   - r'|SetSecureLockIcon|StatusTextChange|TitleChange|MouseMove' +
122   - r'|MouseEnter|MouseLeave|))\b')
123   -
124   -# MS-VBAL 5.4.5.1 Open Statement:
125   -RE_OPEN_WRITE = r'(?:\bOpen\b[^\n]+\b(?:Write|Append|Binary|Output|Random)\b)'
126   -
127   -re_write = re.compile(r'(?i)\b(?:FileCopy|CopyFile|Kill|CreateTextFile|'
128   - + r'VirtualAlloc|RtlMoveMemory|URLDownloadToFileA?|AltStartupPath|'
129   - + r'ADODB\.Stream|WriteText|SaveToFile|SaveAs|SaveAsRTF|FileSaveAs|MkDir|RmDir|SaveSetting|SetAttr)\b|' + RE_OPEN_WRITE)
130   -
131   -# MS-VBAL 5.2.3.5 External Procedure Declaration
132   -RE_DECLARE_LIB = r'(?:\bDeclare\b[^\n]+\bLib\b)'
133   -
134   -re_execute = re.compile(r'(?i)\b(?:Shell|CreateObject|GetObject|SendKeys|'
135   - + r'MacScript|FollowHyperlink|CreateThread|ShellExecute)\b|' + RE_DECLARE_LIB)
136   -
137   -
138   -# === CLASSES =================================================================
139   -
140   -class Result_NoMacro(object):
141   - exit_code = 0
142   - color = 'green'
143   - name = 'No Macro'
144   -
145   -
146   -class Result_NotMSOffice(object):
147   - exit_code = 1
148   - color = 'green'
149   - name = 'Not MS Office'
150   -
151   -
152   -class Result_MacroOK(object):
153   - exit_code = 2
154   - color = 'cyan'
155   - name = 'Macro OK'
156   -
157   -
158   -class Result_Error(object):
159   - exit_code = 10
160   - color = 'yellow'
161   - name = 'ERROR'
162   -
163   -
164   -class Result_Suspicious(object):
165   - exit_code = 20
166   - color = 'red'
167   - name = 'SUSPICIOUS'
168   -
169   -
170   -class MacroRaptor(object):
171   - """
172   - class to scan VBA macro code to detect if it is malicious
173   - """
174   - def __init__(self, vba_code):
175   - """
176   - MacroRaptor constructor
177   - :param vba_code: string containing the VBA macro code
178   - """
179   - # collapse long lines first
180   - self.vba_code = olevba.vba_collapse_long_lines(vba_code)
181   - self.autoexec = False
182   - self.write = False
183   - self.execute = False
184   - self.flags = ''
185   - self.suspicious = False
186   - self.autoexec_match = None
187   - self.write_match = None
188   - self.execute_match = None
189   - self.matches = []
190   -
191   - def scan(self):
192   - """
193   - Scan the VBA macro code to detect if it is malicious
194   - :return:
195   - """
196   - m = re_autoexec.search(self.vba_code)
197   - if m is not None:
198   - self.autoexec = True
199   - self.autoexec_match = m.group()
200   - self.matches.append(m.group())
201   - m = re_write.search(self.vba_code)
202   - if m is not None:
203   - self.write = True
204   - self.write_match = m.group()
205   - self.matches.append(m.group())
206   - m = re_execute.search(self.vba_code)
207   - if m is not None:
208   - self.execute = True
209   - self.execute_match = m.group()
210   - self.matches.append(m.group())
211   - if self.autoexec and (self.execute or self.write):
212   - self.suspicious = True
213   -
214   - def get_flags(self):
215   - flags = ''
216   - flags += 'A' if self.autoexec else '-'
217   - flags += 'W' if self.write else '-'
218   - flags += 'X' if self.execute else '-'
219   - return flags
220   -
221   -
222   -# === MAIN ====================================================================
223   -
224   -def main():
225   - """
226   - Main function, called when olevba is run from the command line
227   - """
228   - global log
229   - DEFAULT_LOG_LEVEL = "warning" # Default log level
230   - LOG_LEVELS = {
231   - 'debug': logging.DEBUG,
232   - 'info': logging.INFO,
233   - 'warning': logging.WARNING,
234   - 'error': logging.ERROR,
235   - 'critical': logging.CRITICAL
236   - }
237   -
238   - usage = 'usage: %prog [options] <filename> [filename2 ...]'
239   - parser = optparse.OptionParser(usage=usage)
240   - parser.add_option("-r", action="store_true", dest="recursive",
241   - help='find files recursively in subdirectories.')
242   - parser.add_option("-z", "--zip", dest='zip_password', type='str', default=None,
243   - help='if the file is a zip archive, open all files from it, using the provided password (requires Python 2.6+)')
244   - parser.add_option("-f", "--zipfname", dest='zip_fname', type='str', default='*',
245   - help='if the file is a zip archive, file(s) to be opened within the zip. Wildcards * and ? are supported. (default:*)')
246   - parser.add_option('-l', '--loglevel', dest="loglevel", action="store", default=DEFAULT_LOG_LEVEL,
247   - help="logging level debug/info/warning/error/critical (default=%default)")
248   - parser.add_option("-m", '--matches', action="store_true", dest="show_matches",
249   - help='Show matched strings.')
250   -
251   - # TODO: add logfile option
252   -
253   - (options, args) = parser.parse_args()
254   -
255   - # Print help if no arguments are passed
256   - if len(args) == 0:
257   - print('MacroRaptor %s - http://decalage.info/python/oletools' % __version__)
258   - print('This is work in progress, please report issues at %s' % URL_ISSUES)
259   - print(__doc__)
260   - parser.print_help()
261   - print('\nAn exit code is returned based on the analysis result:')
262   - for result in (Result_NoMacro, Result_NotMSOffice, Result_MacroOK, Result_Error, Result_Suspicious):
263   - print(' - %d: %s' % (result.exit_code, result.name))
264   - sys.exit()
265   -
266   - # print banner with version
267   - print('MacroRaptor %s - http://decalage.info/python/oletools' % __version__)
268   - print('This is work in progress, please report issues at %s' % URL_ISSUES)
269   -
270   - logging.basicConfig(level=LOG_LEVELS[options.loglevel], format='%(levelname)-8s %(message)s')
271   - # enable logging in the modules:
272   - log.setLevel(logging.NOTSET)
273   -
274   - t = tablestream.TableStream(style=tablestream.TableStyleSlim,
275   - header_row=['Result', 'Flags', 'Type', 'File'],
276   - column_width=[10, 5, 4, 56])
277   -
278   - exitcode = -1
279   - global_result = None
280   - # TODO: handle errors in xglob, to continue processing the next files
281   - for container, filename, data in xglob.iter_files(args, recursive=options.recursive,
282   - zip_password=options.zip_password, zip_fname=options.zip_fname):
283   - # ignore directory names stored in zip files:
284   - if container and filename.endswith('/'):
285   - continue
286   - full_name = '%s in %s' % (filename, container) if container else filename
287   - # try:
288   - # # Open the file
289   - # if data is None:
290   - # data = open(filename, 'rb').read()
291   - # except:
292   - # log.exception('Error when opening file %r' % full_name)
293   - # continue
294   - if isinstance(data, Exception):
295   - result = Result_Error
296   - t.write_row([result.name, '', '', full_name],
297   - colors=[result.color, None, None, None])
298   - t.write_row(['', '', '', str(data)],
299   - colors=[None, None, None, result.color])
300   - else:
301   - filetype = '???'
302   - try:
303   - vba_parser = olevba.VBA_Parser(filename=filename, data=data, container=container)
304   - filetype = TYPE2TAG[vba_parser.type]
305   - except Exception as e:
306   - # log.error('Error when parsing VBA macros from file %r' % full_name)
307   - # TODO: distinguish actual errors from non-MSOffice files
308   - result = Result_Error
309   - t.write_row([result.name, '', filetype, full_name],
310   - colors=[result.color, None, None, None])
311   - t.write_row(['', '', '', str(e)],
312   - colors=[None, None, None, result.color])
313   - continue
314   - if vba_parser.detect_vba_macros():
315   - vba_code_all_modules = ''
316   - try:
317   - for (subfilename, stream_path, vba_filename, vba_code) in vba_parser.extract_all_macros():
318   - vba_code_all_modules += vba_code.decode('utf-8','replace') + '\n'
319   - except Exception as e:
320   - # log.error('Error when parsing VBA macros from file %r' % full_name)
321   - result = Result_Error
322   - t.write_row([result.name, '', TYPE2TAG[vba_parser.type], full_name],
323   - colors=[result.color, None, None, None])
324   - t.write_row(['', '', '', str(e)],
325   - colors=[None, None, None, result.color])
326   - continue
327   - mraptor = MacroRaptor(vba_code_all_modules)
328   - mraptor.scan()
329   - if mraptor.suspicious:
330   - result = Result_Suspicious
331   - else:
332   - result = Result_MacroOK
333   - t.write_row([result.name, mraptor.get_flags(), filetype, full_name],
334   - colors=[result.color, None, None, None])
335   - if mraptor.matches and options.show_matches:
336   - t.write_row(['', '', '', 'Matches: %r' % mraptor.matches])
337   - else:
338   - result = Result_NoMacro
339   - t.write_row([result.name, '', filetype, full_name],
340   - colors=[result.color, None, None, None])
341   - if result.exit_code > exitcode:
342   - global_result = result
343   - exitcode = result.exit_code
344   -
345   - print('')
346   - print('Flags: A=AutoExec, W=Write, X=Execute')
347   - print('Exit code: %d - %s' % (exitcode, global_result.name))
348   - sys.exit(exitcode)
  19 +from oletools.mraptor import *
  20 +from oletools.mraptor import __doc__, __version__
349 21  
350 22 if __name__ == '__main__':
351 23 main()
352   -
353   -# Soundtrack: "Dark Child" by Marlon Williams
... ...