Commit c203e02dc9bab6fb7affc6012d80e27a024abdd3
1 parent
efe28a7b
mraptor: now returns an exit code based on the overall result
Showing
1 changed file
with
63 additions
and
26 deletions
oletools/mraptor.py
| @@ -48,8 +48,9 @@ http://www.decalage.info/python/oletools | @@ -48,8 +48,9 @@ http://www.decalage.info/python/oletools | ||
| 48 | # CHANGELOG: | 48 | # CHANGELOG: |
| 49 | # 2016-02-23 v0.01 PL: - first version | 49 | # 2016-02-23 v0.01 PL: - first version |
| 50 | # 2016-02-29 v0.02 PL: - added Workbook_Activate, FileSaveAs | 50 | # 2016-02-29 v0.02 PL: - added Workbook_Activate, FileSaveAs |
| 51 | +# 2016-03-04 v0.03 PL: - returns an exit code based on the overall result | ||
| 51 | 52 | ||
| 52 | -__version__ = '0.01' | 53 | +__version__ = '0.03' |
| 53 | 54 | ||
| 54 | #------------------------------------------------------------------------------ | 55 | #------------------------------------------------------------------------------ |
| 55 | # TODO: | 56 | # TODO: |
| @@ -113,6 +114,36 @@ TYPE2TAG = { | @@ -113,6 +114,36 @@ TYPE2TAG = { | ||
| 113 | 114 | ||
| 114 | # === CLASSES ================================================================= | 115 | # === CLASSES ================================================================= |
| 115 | 116 | ||
| 117 | +class Result_NoMacro(object): | ||
| 118 | + exit_code = 0 | ||
| 119 | + color = 'green' | ||
| 120 | + name = 'No Macro' | ||
| 121 | + | ||
| 122 | + | ||
| 123 | +class Result_NotMSOffice(object): | ||
| 124 | + exit_code = 1 | ||
| 125 | + color = 'green' | ||
| 126 | + name = 'Not MS Office' | ||
| 127 | + | ||
| 128 | + | ||
| 129 | +class Result_MacroOK(object): | ||
| 130 | + exit_code = 2 | ||
| 131 | + color = 'cyan' | ||
| 132 | + name = 'Macro OK' | ||
| 133 | + | ||
| 134 | + | ||
| 135 | +class Result_Error(object): | ||
| 136 | + exit_code = 10 | ||
| 137 | + color = 'yellow' | ||
| 138 | + name = 'ERROR' | ||
| 139 | + | ||
| 140 | + | ||
| 141 | +class Result_Suspicious(object): | ||
| 142 | + exit_code = 20 | ||
| 143 | + color = 'red' | ||
| 144 | + name = 'SUSPICIOUS' | ||
| 145 | + | ||
| 146 | + | ||
| 116 | class MacroRaptor(object): | 147 | class MacroRaptor(object): |
| 117 | """ | 148 | """ |
| 118 | class to scan VBA macro code to detect if it is malicious | 149 | class to scan VBA macro code to detect if it is malicious |
| @@ -202,6 +233,9 @@ def main(): | @@ -202,6 +233,9 @@ def main(): | ||
| 202 | if len(args) == 0: | 233 | if len(args) == 0: |
| 203 | print __doc__ | 234 | print __doc__ |
| 204 | parser.print_help() | 235 | parser.print_help() |
| 236 | + print '\nAn exit code is returned based on the analysis result:' | ||
| 237 | + for result in (Result_NoMacro, Result_NotMSOffice, Result_MacroOK, Result_Error, Result_Suspicious): | ||
| 238 | + print ' - %d: %s' % (result.exit_code, result.name) | ||
| 205 | sys.exit() | 239 | sys.exit() |
| 206 | 240 | ||
| 207 | # print banner with version | 241 | # print banner with version |
| @@ -216,6 +250,8 @@ def main(): | @@ -216,6 +250,8 @@ def main(): | ||
| 216 | header_row=['Result', 'Flags', 'Type', 'File'], | 250 | header_row=['Result', 'Flags', 'Type', 'File'], |
| 217 | column_width=[10, 5, 4, 56]) | 251 | column_width=[10, 5, 4, 56]) |
| 218 | 252 | ||
| 253 | + exitcode = -1 | ||
| 254 | + global_result = None | ||
| 219 | # TODO: handle errors in xglob, to continue processing the next files | 255 | # TODO: handle errors in xglob, to continue processing the next files |
| 220 | for container, filename, data in xglob.iter_files(args, recursive=options.recursive, | 256 | for container, filename, data in xglob.iter_files(args, recursive=options.recursive, |
| 221 | zip_password=options.zip_password, zip_fname=options.zip_fname): | 257 | zip_password=options.zip_password, zip_fname=options.zip_fname): |
| @@ -231,24 +267,24 @@ def main(): | @@ -231,24 +267,24 @@ def main(): | ||
| 231 | # log.exception('Error when opening file %r' % full_name) | 267 | # log.exception('Error when opening file %r' % full_name) |
| 232 | # continue | 268 | # continue |
| 233 | if isinstance(data, Exception): | 269 | if isinstance(data, Exception): |
| 234 | - result = '* ERROR *' | ||
| 235 | - result_color = 'yellow' | ||
| 236 | - t.write_row([result, '', '', full_name], | ||
| 237 | - colors=[result_color, None, None, None]) | 270 | + result = Result_Error |
| 271 | + t.write_row([result.name, '', '', full_name], | ||
| 272 | + colors=[result.color, None, None, None]) | ||
| 238 | t.write_row(['', '', '', str(data)], | 273 | t.write_row(['', '', '', str(data)], |
| 239 | - colors=[None, None, None, result_color]) | 274 | + colors=[None, None, None, result.color]) |
| 240 | else: | 275 | else: |
| 276 | + filetype = '???' | ||
| 241 | try: | 277 | try: |
| 242 | vba_parser = olevba.VBA_Parser(filename=filename, data=data, container=container) | 278 | vba_parser = olevba.VBA_Parser(filename=filename, data=data, container=container) |
| 243 | filetype = TYPE2TAG[vba_parser.type] | 279 | filetype = TYPE2TAG[vba_parser.type] |
| 244 | except Exception as e: | 280 | except Exception as e: |
| 245 | # log.error('Error when parsing VBA macros from file %r' % full_name) | 281 | # log.error('Error when parsing VBA macros from file %r' % full_name) |
| 246 | - result = '* ERROR *' | ||
| 247 | - result_color = 'yellow' | ||
| 248 | - t.write_row([result, '', TYPE2TAG[vba_parser.type], full_name], | ||
| 249 | - colors=[result_color, None, None, None]) | 282 | + # TODO: distinguish actual errors from non-MSOffice files |
| 283 | + result = Result_Error | ||
| 284 | + t.write_row([result.name, '', filetype, full_name], | ||
| 285 | + colors=[result.color, None, None, None]) | ||
| 250 | t.write_row(['', '', '', str(e)], | 286 | t.write_row(['', '', '', str(e)], |
| 251 | - colors=[None, None, None, result_color]) | 287 | + colors=[None, None, None, result.color]) |
| 252 | continue | 288 | continue |
| 253 | if vba_parser.detect_vba_macros(): | 289 | if vba_parser.detect_vba_macros(): |
| 254 | vba_code_all_modules = '' | 290 | vba_code_all_modules = '' |
| @@ -257,33 +293,34 @@ def main(): | @@ -257,33 +293,34 @@ def main(): | ||
| 257 | vba_code_all_modules += vba_code + '\n' | 293 | vba_code_all_modules += vba_code + '\n' |
| 258 | except Exception as e: | 294 | except Exception as e: |
| 259 | # log.error('Error when parsing VBA macros from file %r' % full_name) | 295 | # log.error('Error when parsing VBA macros from file %r' % full_name) |
| 260 | - result = '* ERROR *' | ||
| 261 | - result_color = 'yellow' | ||
| 262 | - t.write_row([result, '', TYPE2TAG[vba_parser.type], full_name], | ||
| 263 | - colors=[result_color, None, None, None]) | 296 | + result = Result_Error |
| 297 | + t.write_row([result.name, '', TYPE2TAG[vba_parser.type], full_name], | ||
| 298 | + colors=[result.color, None, None, None]) | ||
| 264 | t.write_row(['', '', '', str(e)], | 299 | t.write_row(['', '', '', str(e)], |
| 265 | - colors=[None, None, None, result_color]) | 300 | + colors=[None, None, None, result.color]) |
| 266 | continue | 301 | continue |
| 267 | mraptor = MacroRaptor(vba_code_all_modules) | 302 | mraptor = MacroRaptor(vba_code_all_modules) |
| 268 | mraptor.scan() | 303 | mraptor.scan() |
| 269 | if mraptor.suspicious: | 304 | if mraptor.suspicious: |
| 270 | - result = 'SUSPICIOUS' | ||
| 271 | - result_color = 'red' | 305 | + result = Result_Suspicious |
| 272 | else: | 306 | else: |
| 273 | - result = 'Macro OK' | ||
| 274 | - result_color = 'cyan' | ||
| 275 | - t.write_row([result, mraptor.get_flags(), filetype, full_name], | ||
| 276 | - colors=[result_color, None, None, None]) | 307 | + result = Result_MacroOK |
| 308 | + t.write_row([result.name, mraptor.get_flags(), filetype, full_name], | ||
| 309 | + colors=[result.color, None, None, None]) | ||
| 277 | if mraptor.matches and options.show_matches: | 310 | if mraptor.matches and options.show_matches: |
| 278 | t.write_row(['', '', '', 'Matches: %r' % mraptor.matches]) | 311 | t.write_row(['', '', '', 'Matches: %r' % mraptor.matches]) |
| 279 | else: | 312 | else: |
| 280 | - result = 'No Macro' | ||
| 281 | - result_color = 'green' | ||
| 282 | - t.write_row([result, '', filetype, full_name], | ||
| 283 | - colors=[result_color, None, None, None]) | 313 | + result = Result_NoMacro |
| 314 | + t.write_row([result.name, '', filetype, full_name], | ||
| 315 | + colors=[result.color, None, None, None]) | ||
| 316 | + if result.exit_code > exitcode: | ||
| 317 | + global_result = result | ||
| 318 | + exitcode = result.exit_code | ||
| 284 | 319 | ||
| 285 | print '' | 320 | print '' |
| 286 | print 'Flags: A=AutoExec, W=Write, X=Execute' | 321 | print 'Flags: A=AutoExec, W=Write, X=Execute' |
| 322 | + print 'Exit code: %d - %s' % (exitcode, global_result.name) | ||
| 323 | + sys.exit(exitcode) | ||
| 287 | 324 | ||
| 288 | if __name__ == '__main__': | 325 | if __name__ == '__main__': |
| 289 | main() | 326 | main() |