1
2
3
4
5 import os
6 import json
7 import pkgutil
8 import inspect
9 import logging
10 from collections import defaultdict
11 from distutils.version import StrictVersion
12
13 from lib.cuckoo.common.abstracts import Auxiliary, Machinery, Processing
14 from lib.cuckoo.common.abstracts import Report, Signature
15 from lib.cuckoo.common.config import Config
16 from lib.cuckoo.common.constants import CUCKOO_ROOT, CUCKOO_VERSION
17 from lib.cuckoo.common.exceptions import CuckooCriticalError
18 from lib.cuckoo.common.exceptions import CuckooOperationalError
19 from lib.cuckoo.common.exceptions import CuckooProcessingError
20 from lib.cuckoo.common.exceptions import CuckooReportError
21 from lib.cuckoo.common.exceptions import CuckooDependencyError
22 from lib.cuckoo.core.database import Database
23
24 log = logging.getLogger(__name__)
25
26 _modules = defaultdict(dict)
27
29 try:
30 module = __import__(name, globals(), locals(), ["dummy"], -1)
31 except ImportError as e:
32 raise CuckooCriticalError("Unable to import plugin "
33 "\"{0}\": {1}".format(name, e))
34 else:
35 load_plugins(module)
36
44
46 for name, value in inspect.getmembers(module):
47 if inspect.isclass(value):
48 if issubclass(value, Auxiliary) and value is not Auxiliary:
49 register_plugin("auxiliary", value)
50 elif issubclass(value, Machinery) and value is not Machinery:
51 register_plugin("machinery", value)
52 elif issubclass(value, Processing) and value is not Processing:
53 register_plugin("processing", value)
54 elif issubclass(value, Report) and value is not Report:
55 register_plugin("reporting", value)
56 elif issubclass(value, Signature) and value is not Signature:
57 register_plugin("signatures", value)
58
63
69
71 """Auxiliary modules manager."""
72
78
80 auxiliary_list = list_plugins(group="auxiliary")
81 if auxiliary_list:
82 for module in auxiliary_list:
83 try:
84 current = module()
85 except:
86 log.exception("Failed to load the auxiliary module "
87 "\"{0}\":".format(module))
88 return
89
90 module_name = inspect.getmodule(current).__name__
91 if "." in module_name:
92 module_name = module_name.rsplit(".", 1)[1]
93
94 try:
95 options = self.cfg.get(module_name)
96 except CuckooOperationalError:
97 log.debug("Auxiliary module %s not found in "
98 "configuration file", module_name)
99 continue
100
101 if not options.enabled:
102 continue
103
104 current.set_task(self.task)
105 current.set_machine(self.machine)
106 current.set_options(options)
107
108 try:
109 current.start()
110 except NotImplementedError:
111 pass
112 except Exception as e:
113 log.warning("Unable to start auxiliary module %s: %s",
114 module_name, e)
115 else:
116 log.debug("Started auxiliary module: %s",
117 current.__class__.__name__)
118 self.enabled.append(current)
119
121 for module in self.enabled:
122 try:
123 module.stop()
124 except NotImplementedError:
125 pass
126 except Exception as e:
127 log.warning("Unable to stop auxiliary module: %s", e)
128 else:
129 log.debug("Stopped auxiliary module: %s",
130 module.__class__.__name__)
131
133 """Analysis Results Processing Engine.
134
135 This class handles the loading and execution of the processing modules.
136 It executes the enabled ones sequentially and generates a dictionary which
137 is then passed over the reporting engine.
138 """
139
145
147 """Run a processing module.
148 @param module: processing module to run.
149 @param results: results dict.
150 @return: results generated by module.
151 """
152
153 try:
154 current = module()
155 except:
156 log.exception("Failed to load the processing module "
157 "\"{0}\":".format(module))
158 return
159
160
161 module_name = inspect.getmodule(current).__name__
162 if "." in module_name:
163 module_name = module_name.rsplit(".", 1)[1]
164
165 try:
166 options = self.cfg.get(module_name)
167 except CuckooOperationalError:
168 log.debug("Processing module %s not found in configuration file",
169 module_name)
170 return None
171
172
173 if not options.enabled:
174 return None
175
176
177 current.set_path(self.analysis_path)
178
179 current.set_task(self.task)
180
181 current.set_options(options)
182
183 try:
184
185
186 data = current.run()
187
188 log.debug("Executed processing module \"%s\" on analysis at "
189 "\"%s\"", current.__class__.__name__, self.analysis_path)
190
191
192
193 return {current.key: data}
194 except CuckooDependencyError as e:
195 log.warning("The processing module \"%s\" has missing dependencies: %s", current.__class__.__name__, e)
196 except CuckooProcessingError as e:
197 log.warning("The processing module \"%s\" returned the following "
198 "error: %s", current.__class__.__name__, e)
199 except:
200 log.exception("Failed to run the processing module \"%s\":",
201 current.__class__.__name__)
202
203 return None
204
206 """Run all processing modules and all signatures.
207 @return: processing results.
208 """
209
210
211
212
213
214
215 results = {}
216
217
218
219
220 processing_list = list_plugins(group="processing")
221
222
223 if processing_list:
224 processing_list.sort(key=lambda module: module.order)
225
226
227 for module in processing_list:
228 result = self.process(module)
229
230
231 if result:
232 results.update(result)
233 else:
234 log.info("No processing modules loaded")
235
236
237 return results
238
240 """Run Signatures."""
241
243 self.results = results
244
246 """Loads overlay data from a json file.
247 See example in data/signature_overlay.json
248 """
249 filename = os.path.join(CUCKOO_ROOT, "data", "signature_overlay.json")
250
251 try:
252 with open(filename) as fh:
253 odata = json.load(fh)
254 return odata
255 except IOError:
256 pass
257
258 return {}
259
261 """Applies the overlay attributes to the signature object."""
262 if signature.name in overlay:
263 attrs = overlay[signature.name]
264 for attr, value in attrs.items():
265 setattr(signature, attr, value)
266
268 """Check signature version.
269 @param current: signature class/instance to check.
270 @return: check result.
271 """
272
273
274
275 version = CUCKOO_VERSION.split("-")[0]
276
277
278
279 if current.minimum:
280 try:
281
282
283 if StrictVersion(version) < StrictVersion(current.minimum.split("-")[0]):
284 log.debug("You are running an older incompatible version "
285 "of Cuckoo, the signature \"%s\" requires "
286 "minimum version %s",
287 current.name, current.minimum)
288 return None
289 except ValueError:
290 log.debug("Wrong minor version number in signature %s",
291 current.name)
292 return None
293
294
295
296 if current.maximum:
297 try:
298
299
300 if StrictVersion(version) > StrictVersion(current.maximum.split("-")[0]):
301 log.debug("You are running a newer incompatible version "
302 "of Cuckoo, the signature \"%s\" requires "
303 "maximum version %s",
304 current.name, current.maximum)
305 return None
306 except ValueError:
307 log.debug("Wrong major version number in signature %s",
308 current.name)
309 return None
310
311 return True
312
314 """Run a signature.
315 @param signature: signature to run.
316 @return: matched signature.
317 """
318
319 if not self.results:
320 return
321
322
323 try:
324 current = signature(self.results)
325 except:
326 log.exception("Failed to load signature "
327 "\"{0}\":".format(signature))
328 return
329
330 log.debug("Running signature \"%s\"", current.name)
331
332
333 if not current.enabled:
334 return None
335
336 if not self._check_signature_version(current):
337 return None
338
339 try:
340
341
342 if current.run():
343 log.debug("Analysis matched signature \"%s\"", current.name)
344
345 return current.as_result()
346 except NotImplementedError:
347 return None
348 except:
349 log.exception("Failed to run signature \"%s\":", current.name)
350
351 return None
352
354 """Run evented signatures."""
355
356 matched = []
357
358 complete_list = list_plugins(group="signatures")
359 evented_list = [sig(self.results)
360 for sig in complete_list
361 if sig.enabled and sig.evented and
362 self._check_signature_version(sig)]
363
364 overlay = self._load_overlay()
365 log.debug("Applying signature overlays for signatures: %s", ", ".join(overlay.keys()))
366 for signature in complete_list + evented_list:
367 self._apply_overlay(signature, overlay)
368
369 if evented_list:
370 log.debug("Running %u evented signatures", len(evented_list))
371 for sig in evented_list:
372 if sig == evented_list[-1]:
373 log.debug("\t `-- %s", sig.name)
374 else:
375 log.debug("\t |-- %s", sig.name)
376
377
378 for proc in self.results["behavior"]["processes"]:
379 for call in proc["calls"]:
380
381 for sig in evented_list:
382
383 if sig.filter_processnames and not proc["process_name"] in sig.filter_processnames:
384 continue
385 if sig.filter_apinames and not call["api"] in sig.filter_apinames:
386 continue
387 if sig.filter_categories and not call["category"] in sig.filter_categories:
388 continue
389
390 result = None
391 try:
392 result = sig.on_call(call, proc)
393 except NotImplementedError:
394 result = False
395 except:
396 log.exception("Failed to run signature \"%s\":", sig.name)
397 result = False
398
399
400
401 if result is None:
402 continue
403
404
405 if result is True:
406 log.debug("Analysis matched signature \"%s\"", sig.name)
407 matched.append(sig.as_result())
408 if sig in complete_list:
409 complete_list.remove(sig)
410
411
412 evented_list.remove(sig)
413 del sig
414
415
416 for sig in evented_list:
417 try:
418 result = sig.on_complete()
419 except NotImplementedError:
420 continue
421 except:
422 log.exception("Failed run on_complete() method for signature \"%s\":", sig.name)
423 continue
424 else:
425 if result is True:
426 log.debug("Analysis matched signature \"%s\"", sig.name)
427 matched.append(sig.as_result())
428 if sig in complete_list:
429 complete_list.remove(sig)
430
431
432 self.results["signatures"] = matched
433
434
435 if complete_list:
436 complete_list.sort(key=lambda sig: sig.order)
437 log.debug("Running non-evented signatures")
438
439 for signature in complete_list:
440 match = self.process(signature)
441
442 if match:
443 matched.append(match)
444
445
446 if "behavior" in self.results:
447 for process in self.results["behavior"]["processes"]:
448 process["calls"].reset()
449
450
451 matched.sort(key=lambda key: key["severity"])
452
454 """Reporting Engine.
455
456 This class handles the loading and execution of the enabled reporting
457 modules. It receives the analysis results dictionary from the Processing
458 Engine and pass it over to the reporting modules before executing them.
459 """
460
467
469 """Run a single reporting module.
470 @param module: reporting module.
471 @param results: results results from analysis.
472 """
473
474 try:
475 current = module()
476 except:
477 log.exception("Failed to load the reporting module \"{0}\":".format(module))
478 return
479
480
481 module_name = inspect.getmodule(current).__name__
482 if "." in module_name:
483 module_name = module_name.rsplit(".", 1)[1]
484
485 try:
486 options = self.cfg.get(module_name)
487 except CuckooOperationalError:
488 log.debug("Reporting module %s not found in configuration file", module_name)
489 return
490
491
492 if not options.enabled:
493 return
494
495
496 current.set_path(self.analysis_path)
497
498 current.set_task(self.task)
499
500 current.set_options(options)
501
502 current.cfg = Config(cfg=current.conf_path)
503
504 try:
505 current.run(self.results)
506 log.debug("Executed reporting module \"%s\"", current.__class__.__name__)
507 except CuckooDependencyError as e:
508 log.warning("The reporting module \"%s\" has missing dependencies: %s", current.__class__.__name__, e)
509 except CuckooReportError as e:
510 log.warning("The reporting module \"%s\" returned the following error: %s", current.__class__.__name__, e)
511 except:
512 log.exception("Failed to run the reporting module \"%s\":", current.__class__.__name__)
513
515 """Generates all reports.
516 @raise CuckooReportError: if a report module fails.
517 """
518
519
520
521
522 reporting_list = list_plugins(group="reporting")
523
524
525 if reporting_list:
526 reporting_list.sort(key=lambda module: module.order)
527
528
529 for module in reporting_list:
530 self.process(module)
531 else:
532 log.info("No reporting modules loaded")
533