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 #!/usr/bin/env python 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 # IMPORTANT: it should be possible to run oletools directly as scripts 9 # IMPORTANT: it should be possible to run oletools directly as scripts
72 # in any directory without installing them with pip or setup.py. 10 # in any directory without installing them with pip or setup.py.
@@ -74,280 +12,12 @@ import sys, os, logging, optparse, re @@ -74,280 +12,12 @@ import sys, os, logging, optparse, re
74 # And to enable Python 2+3 compatibility, we need to use absolute imports, 12 # And to enable Python 2+3 compatibility, we need to use absolute imports,
75 # so we add the oletools parent folder to sys.path (absolute+normalized path): 13 # so we add the oletools parent folder to sys.path (absolute+normalized path):
76 _thismodule_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) 14 _thismodule_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__)))
77 -# print('_thismodule_dir = %r' % _thismodule_dir)  
78 _parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..')) 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 sys.path.insert(0, _parent_dir) 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 if __name__ == '__main__': 22 if __name__ == '__main__':
351 main() 23 main()
352 -  
353 -# Soundtrack: "Dark Child" by Marlon Williams