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", module_name)
117 self.enabled.append(current)
118
120 for module in self.enabled:
121 try:
122 module.stop()
123 except NotImplementedError:
124 pass
125 except Exception as e:
126 log.warning("Unable to stop auxiliary module: %s", e)
127 else:
128 log.debug("Stopped auxiliary module: %s", module)
129
131 """Analysis Results Processing Engine.
132
133 This class handles the loading and execution of the processing modules.
134 It executes the enabled ones sequentially and generates a dictionary which
135 is then passed over the reporting engine.
136 """
137
143
145 """Run a processing module.
146 @param module: processing module to run.
147 @param results: results dict.
148 @return: results generated by module.
149 """
150
151 try:
152 current = module()
153 except:
154 log.exception("Failed to load the processing module "
155 "\"{0}\":".format(module))
156 return
157
158
159 module_name = inspect.getmodule(current).__name__
160 if "." in module_name:
161 module_name = module_name.rsplit(".", 1)[1]
162
163 try:
164 options = self.cfg.get(module_name)
165 except CuckooOperationalError:
166 log.debug("Processing module %s not found in configuration file",
167 module_name)
168 return None
169
170
171 if not options.enabled:
172 return None
173
174
175 current.set_path(self.analysis_path)
176
177 current.set_task(self.task)
178
179 current.set_options(options)
180
181 try:
182
183
184 data = current.run()
185
186 log.debug("Executed processing module \"%s\" on analysis at "
187 "\"%s\"", current.__class__.__name__, self.analysis_path)
188
189
190
191 return {current.key: data}
192 except CuckooDependencyError as e:
193 log.warning("The processing module \"%s\" has missing dependencies: %s", current.__class__.__name__, e)
194 except CuckooProcessingError as e:
195 log.warning("The processing module \"%s\" returned the following "
196 "error: %s", current.__class__.__name__, e)
197 except:
198 log.exception("Failed to run the processing module \"%s\":",
199 current.__class__.__name__)
200
201 return None
202
204 """Run all processing modules and all signatures.
205 @return: processing results.
206 """
207
208
209
210
211
212
213 results = {}
214
215
216
217
218 processing_list = list_plugins(group="processing")
219
220
221 if processing_list:
222 processing_list.sort(key=lambda module: module.order)
223
224
225 for module in processing_list:
226 result = self.process(module)
227
228
229 if result:
230 results.update(result)
231 else:
232 log.info("No processing modules loaded")
233
234
235 return results
236
238 """Run Signatures."""
239
241 self.results = results
242
244 """Loads overlay data from a json file.
245 See example in data/signature_overlay.json
246 """
247 filename = os.path.join(CUCKOO_ROOT, "data", "signature_overlay.json")
248
249 try:
250 with open(filename) as fh:
251 odata = json.load(fh)
252 return odata
253 except IOError:
254 pass
255
256 return {}
257
259 """Applies the overlay attributes to the signature object."""
260 if signature.name in overlay:
261 attrs = overlay[signature.name]
262 for attr, value in attrs.items():
263 setattr(signature, attr, value)
264
266 """Check signature version.
267 @param current: signature class/instance to check.
268 @return: check result.
269 """
270
271
272
273 version = CUCKOO_VERSION.split("-")[0]
274
275
276
277 if current.minimum:
278 try:
279
280
281 if StrictVersion(version) < StrictVersion(current.minimum.split("-")[0]):
282 log.debug("You are running an older incompatible version "
283 "of Cuckoo, the signature \"%s\" requires "
284 "minimum version %s",
285 current.name, current.minimum)
286 return None
287 except ValueError:
288 log.debug("Wrong minor version number in signature %s",
289 current.name)
290 return None
291
292
293
294 if current.maximum:
295 try:
296
297
298 if StrictVersion(version) > StrictVersion(current.maximum.split("-")[0]):
299 log.debug("You are running a newer incompatible version "
300 "of Cuckoo, the signature \"%s\" requires "
301 "maximum version %s",
302 current.name, current.maximum)
303 return None
304 except ValueError:
305 log.debug("Wrong major version number in signature %s",
306 current.name)
307 return None
308
309 return True
310
312 """Run a signature.
313 @param signature: signature to run.
314 @param signs: signature results dict.
315 @return: matched signature.
316 """
317
318 try:
319 current = signature(self.results)
320 except:
321 log.exception("Failed to load signature "
322 "\"{0}\":".format(signature))
323 return
324
325 log.debug("Running signature \"%s\"", current.name)
326
327
328 if not current.enabled:
329 return None
330
331 if not self._check_signature_version(current):
332 return None
333
334 try:
335
336
337 if current.run():
338 log.debug("Analysis matched signature \"%s\"", current.name)
339
340 return current.as_result()
341 except NotImplementedError:
342 return None
343 except:
344 log.exception("Failed to run signature \"%s\":", current.name)
345
346 return None
347
349
350 matched = []
351
352 complete_list = list_plugins(group="signatures")
353 evented_list = [sig(self.results)
354 for sig in complete_list
355 if sig.enabled and sig.evented and
356 self._check_signature_version(sig)]
357
358 overlay = self._load_overlay()
359 log.debug("Applying signature overlays for signatures: %s", ", ".join(overlay.keys()))
360 for signature in complete_list + evented_list:
361 self._apply_overlay(signature, overlay)
362
363 if evented_list:
364 log.debug("Running %u evented signatures", len(evented_list))
365 for sig in evented_list:
366 if sig == evented_list[-1]:
367 log.debug("\t `-- %s", sig.name)
368 else:
369 log.debug("\t |-- %s", sig.name)
370
371
372 for proc in self.results["behavior"]["processes"]:
373 for call in proc["calls"]:
374
375 for sig in evented_list:
376
377 if sig.filter_processnames and not proc["process_name"] in sig.filter_processnames:
378 continue
379 if sig.filter_apinames and not call["api"] in sig.filter_apinames:
380 continue
381 if sig.filter_categories and not call["category"] in sig.filter_categories:
382 continue
383
384 result = None
385 try:
386 result = sig.on_call(call, proc)
387 except NotImplementedError:
388 result = False
389 except:
390 log.exception("Failed to run signature \"%s\":", sig.name)
391 result = False
392
393
394
395 if result is None:
396 continue
397
398
399 if result is True:
400 log.debug("Analysis matched signature \"%s\"", sig.name)
401 matched.append(sig.as_result())
402 if sig in complete_list:
403 complete_list.remove(sig)
404
405
406 evented_list.remove(sig)
407 del sig
408
409
410 for sig in evented_list:
411 try:
412 result = sig.on_complete()
413 except NotImplementedError:
414 continue
415 except:
416 log.exception("Failed run on_complete() method for signature \"%s\":", sig.name)
417 continue
418 else:
419 if result is True:
420 log.debug("Analysis matched signature \"%s\"", sig.name)
421 matched.append(sig.as_result())
422 if sig in complete_list:
423 complete_list.remove(sig)
424
425
426 self.results["signatures"] = matched
427
428
429 if complete_list:
430 complete_list.sort(key=lambda sig: sig.order)
431 log.debug("Running non-evented signatures")
432
433 for signature in complete_list:
434 match = self.process(signature)
435
436 if match:
437 matched.append(match)
438
439
440 if "behavior" in self.results:
441 for process in self.results["behavior"]["processes"]:
442 process["calls"].reset()
443
444
445 matched.sort(key=lambda key: key["severity"])
446
448 """Reporting Engine.
449
450 This class handles the loading and execution of the enabled reporting
451 modules. It receives the analysis results dictionary from the Processing
452 Engine and pass it over to the reporting modules before executing them.
453 """
454
461
463 """Run a single reporting module.
464 @param module: reporting module.
465 @param results: results results from analysis.
466 """
467
468 try:
469 current = module()
470 except:
471 log.exception("Failed to load the reporting module \"{0}\":".format(module))
472 return
473
474
475 module_name = inspect.getmodule(current).__name__
476 if "." in module_name:
477 module_name = module_name.rsplit(".", 1)[1]
478
479 try:
480 options = self.cfg.get(module_name)
481 except CuckooOperationalError:
482 log.debug("Reporting module %s not found in configuration file", module_name)
483 return
484
485
486 if not options.enabled:
487 return
488
489
490 current.set_path(self.analysis_path)
491
492 current.set_task(self.task)
493
494 current.set_options(options)
495
496 current.cfg = Config(current.conf_path)
497
498 try:
499 current.run(self.results)
500 log.debug("Executed reporting module \"%s\"", current.__class__.__name__)
501 except CuckooDependencyError as e:
502 log.warning("The reporting module \"%s\" has missing dependencies: %s", current.__class__.__name__, e)
503 except CuckooReportError as e:
504 log.warning("The reporting module \"%s\" returned the following error: %s", current.__class__.__name__, e)
505 except:
506 log.exception("Failed to run the reporting module \"%s\":", current.__class__.__name__)
507
509 """Generates all reports.
510 @raise CuckooReportError: if a report module fails.
511 """
512
513
514
515
516 reporting_list = list_plugins(group="reporting")
517
518
519 if reporting_list:
520 reporting_list.sort(key=lambda module: module.order)
521
522
523 for module in reporting_list:
524 self.process(module)
525 else:
526 log.info("No reporting modules loaded")
527