1
2
3
4
5
6 import os
7 import pkgutil
8 import importlib
9 import inspect
10 import logging
11 from collections import defaultdict
12 from distutils.version import StrictVersion
13
14 from lib.cuckoo.common.abstracts import Auxiliary, Machinery, LibVirtMachinery, Processing
15 from lib.cuckoo.common.abstracts import Report, Signature
16 from lib.cuckoo.common.config import Config
17 from lib.cuckoo.common.constants import CUCKOO_ROOT, CUCKOO_VERSION
18 from lib.cuckoo.common.exceptions import CuckooCriticalError
19 from lib.cuckoo.common.exceptions import CuckooOperationalError
20 from lib.cuckoo.common.exceptions import CuckooProcessingError
21 from lib.cuckoo.common.exceptions import CuckooReportError
22 from lib.cuckoo.common.exceptions import CuckooDependencyError
23 from lib.cuckoo.common.exceptions import CuckooDisableModule
24
25 log = logging.getLogger(__name__)
26
27 _modules = defaultdict(list)
28
31 """Import plugins of type `class` located at `dirpath` into the
32 `namespace` that starts with `module_prefix`. If `dirpath` represents a
33 filepath then it is converted into its containing directory. The
34 `attributes` dictionary allows one to set extra fields for all imported
35 plugins."""
36 if os.path.isfile(dirpath):
37 dirpath = os.path.dirname(dirpath)
38
39 for fname in os.listdir(dirpath):
40 if fname.endswith(".py") and not fname.startswith("__init__"):
41 module_name, _ = os.path.splitext(fname)
42 importlib.import_module("%s.%s" % (module_prefix, module_name))
43
44 plugins = []
45 for subclass in class_.__subclasses__():
46
47
48
49 if module_prefix != ".".join(subclass.__module__.split(".")[:-1]):
50 continue
51
52 namespace[subclass.__name__] = subclass
53 for key, value in attributes.items():
54 setattr(subclass, key, value)
55 plugins.append(subclass)
56 return plugins
57
59 try:
60 module = __import__(name, globals(), locals(), ["dummy"], -1)
61 except ImportError as e:
62 raise CuckooCriticalError("Unable to import plugin "
63 "\"{0}\": {1}".format(name, e))
64 else:
65 load_plugins(module)
66
71
73 for name, value in inspect.getmembers(module):
74 if inspect.isclass(value):
75 if issubclass(value, Auxiliary) and value is not Auxiliary:
76 register_plugin("auxiliary", value)
77 elif issubclass(value, Machinery) and value is not Machinery and value is not LibVirtMachinery:
78 register_plugin("machinery", value)
79 elif issubclass(value, Processing) and value is not Processing:
80 register_plugin("processing", value)
81 elif issubclass(value, Report) and value is not Report:
82 register_plugin("reporting", value)
83 elif issubclass(value, Signature) and value is not Signature:
84 register_plugin("signatures", value)
85
90
96
98 """Auxiliary modules manager."""
99
100 - def __init__(self, task, machine, guest_manager):
107
109 for module in list_plugins(group="auxiliary"):
110 try:
111 current = module()
112 except:
113 log.exception("Failed to load the auxiliary module "
114 "\"{0}\":".format(module))
115 return
116
117 module_name = inspect.getmodule(current).__name__
118 if "." in module_name:
119 module_name = module_name.rsplit(".", 1)[1]
120
121 try:
122 options = self.cfg.get(module_name)
123 except CuckooOperationalError:
124 log.debug("Auxiliary module %s not found in "
125 "configuration file", module_name)
126 continue
127
128 if not options.enabled:
129 continue
130
131 current.set_task(self.task)
132 current.set_machine(self.machine)
133 current.set_guest_manager(self.guest_manager)
134 current.set_options(options)
135
136 try:
137 current.start()
138 except NotImplementedError:
139 pass
140 except CuckooDisableModule:
141 continue
142 except Exception as e:
143 log.warning("Unable to start auxiliary module %s: %s",
144 module_name, e)
145 else:
146 log.debug("Started auxiliary module: %s",
147 current.__class__.__name__)
148 self.enabled.append(current)
149
150 - def callback(self, name, *args, **kwargs):
151 def default(*args, **kwargs):
152 pass
153
154 enabled = []
155 for module in self.enabled:
156 try:
157 getattr(module, "cb_%s" % name, default)(*args, **kwargs)
158 except NotImplementedError:
159 pass
160 except CuckooDisableModule:
161 continue
162 except Exception as e:
163 log.warning(
164 "Error performing callback %r on auxiliary module %r: %s",
165 name, module.__class__.__name__, e
166 )
167
168 enabled.append(module)
169 self.enabled = enabled
170
172 for module in self.enabled:
173 try:
174 module.stop()
175 except NotImplementedError:
176 pass
177 except Exception as e:
178 log.warning("Unable to stop auxiliary module: %s", e)
179 else:
180 log.debug("Stopped auxiliary module: %s",
181 module.__class__.__name__)
182
184 """Analysis Results Processing Engine.
185
186 This class handles the loading and execution of the processing modules.
187 It executes the enabled ones sequentially and generates a dictionary which
188 is then passed over the reporting engine.
189 """
190
192 """@param task: task dictionary of the analysis to process."""
193 self.task = task
194 self.analysis_path = os.path.join(CUCKOO_ROOT, "storage", "analyses", str(task["id"]))
195 self.baseline_path = os.path.join(CUCKOO_ROOT, "storage", "baseline")
196 self.cfg = Config("processing")
197
198 - def process(self, module, results):
199 """Run a processing module.
200 @param module: processing module to run.
201 @param results: results dict.
202 @return: results generated by module.
203 """
204
205 try:
206 current = module()
207 except:
208 log.exception("Failed to load the processing module "
209 "\"{0}\":".format(module))
210 return None, None
211
212
213 module_name = inspect.getmodule(current).__name__
214 if "." in module_name:
215 module_name = module_name.rsplit(".", 1)[1]
216
217 try:
218 options = self.cfg.get(module_name)
219 except CuckooOperationalError:
220 log.debug("Processing module %s not found in configuration file",
221 module_name)
222 return None, None
223
224
225 if not options.enabled:
226 return None, None
227
228
229 current.set_baseline(self.baseline_path)
230
231 current.set_path(self.analysis_path)
232
233 current.set_task(self.task)
234
235 current.set_options(options)
236
237 current.set_results(results)
238
239 try:
240
241
242 data = current.run()
243
244 log.debug("Executed processing module \"%s\" on analysis at "
245 "\"%s\"", current.__class__.__name__, self.analysis_path)
246
247
248 return current.key, data
249 except CuckooDependencyError as e:
250 log.warning("The processing module \"%s\" has missing dependencies: %s", current.__class__.__name__, e)
251 except CuckooProcessingError as e:
252 log.warning("The processing module \"%s\" returned the following "
253 "error: %s", current.__class__.__name__, e)
254 except:
255 log.exception("Failed to run the processing module \"%s\" for task #%d:",
256 current.__class__.__name__, self.task["id"])
257
258 return None, None
259
261 """Run all processing modules and all signatures.
262 @return: processing results.
263 """
264
265
266
267
268
269
270 results = {
271 "_temp": {},
272 }
273
274
275
276
277 processing_list = list_plugins(group="processing")
278
279
280 if processing_list:
281 processing_list.sort(key=lambda module: module.order)
282
283
284 for module in processing_list:
285 key, result = self.process(module, results)
286
287
288 if key and result:
289 results[key] = result
290 else:
291 log.info("No processing modules loaded")
292
293 results.pop("_temp", None)
294
295
296 return results
297
299 """Run Signatures."""
300
318
320 """Should the given signature be enabled for this analysis?"""
321 if not signature.enabled:
322 return False
323
324 if not self.check_signature_version(signature):
325 return False
326
327
328 if not signature.platform:
329 return True
330
331 task_platform = self.results.get("info", {}).get("platform")
332
333
334
335
336 if not task_platform and signature.platform == "windows":
337 return True
338
339 return task_platform == signature.platform
340
342 """Check signature version.
343 @param current: signature class/instance to check.
344 @return: check result.
345 """
346
347 if signature.minimum:
348 try:
349
350
351 if StrictVersion(self.version) < StrictVersion(signature.minimum):
352 log.debug("You are running an older incompatible version "
353 "of Cuckoo, the signature \"%s\" requires "
354 "minimum version %s.",
355 signature.name, signature.minimum)
356 return False
357
358 if StrictVersion("1.2") > StrictVersion(signature.minimum):
359 log.warn("Cuckoo signature style has been redesigned in "
360 "cuckoo 1.2. This signature is not "
361 "compatible: %s.", signature.name)
362 return False
363
364 if StrictVersion("2.0") > StrictVersion(signature.minimum):
365 log.warn("Cuckoo version 2.0 features a lot of changes that "
366 "render old signatures ineffective as they are not "
367 "backwards-compatible. Please upgrade this "
368 "signature: %s.", signature.name)
369 return False
370
371 if hasattr(signature, "run"):
372 log.warn("This signatures features one or more deprecated "
373 "functions which indicates that it is very likely "
374 "an old-style signature. Please upgrade this "
375 "signature: %s.", signature.name)
376 return False
377
378 except ValueError:
379 log.debug("Wrong minor version number in signature %s",
380 signature.name)
381 return False
382
383
384 if signature.maximum:
385 try:
386
387
388 if StrictVersion(self.version) > StrictVersion(signature.maximum):
389 log.debug("You are running a newer incompatible version "
390 "of Cuckoo, the signature \"%s\" requires "
391 "maximum version %s.",
392 signature.name, signature.maximum)
393 return False
394 except ValueError:
395 log.debug("Wrong major version number in signature %s",
396 signature.name)
397 return False
398
399 return True
400
402 """Wrapper to call into 3rd party signatures. This wrapper yields the
403 event to the signature and handles matched signatures recursively."""
404 try:
405 if handler(*args, **kwargs):
406 signature.matched = True
407 for sig in self.signatures:
408 self.call_signature(sig, sig.on_signature, signature)
409 except NotImplementedError:
410 return False
411 except:
412 log.exception("Failed to run '%s' of the %s signature",
413 handler.__name__, signature.name)
414 return True
415
417 """Initialize a list of signatures for which we should trigger its
418 on_call method for this particular API name and category."""
419 self.api_sigs[apiname] = []
420
421 for sig in self.signatures:
422 if sig.filter_apinames and apiname not in sig.filter_apinames:
423 continue
424
425 if sig.filter_categories and category not in sig.filter_categories:
426 continue
427
428 self.api_sigs[apiname].append(sig)
429
431 """Yield calls of interest to each interested signature."""
432 for idx, call in enumerate(proc.get("calls", [])):
433
434
435 if call["api"] not in self.api_sigs:
436 self.init_api_sigs(call["api"], call.get("category"))
437
438
439
440 for sig in reversed(self.api_sigs[call["api"]]):
441 sig.cid, sig.call = idx, call
442 if self.call_signature(sig, sig.on_call, call, proc) is False:
443 self.api_sigs[call["api"]].remove(sig)
444
480
482 """Reporting Engine.
483
484 This class handles the loading and execution of the enabled reporting
485 modules. It receives the analysis results dictionary from the Processing
486 Engine and pass it over to the reporting modules before executing them.
487 """
488
490 """@param analysis_path: analysis folder path."""
491 self.task = task
492 self.results = results
493 self.analysis_path = os.path.join(CUCKOO_ROOT, "storage", "analyses", str(task["id"]))
494 self.cfg = Config("reporting")
495
497 """Run a single reporting module.
498 @param module: reporting module.
499 @param results: results results from analysis.
500 """
501
502 try:
503 current = module()
504 except:
505 log.exception("Failed to load the reporting module \"{0}\":".format(module))
506 return
507
508
509 module_name = inspect.getmodule(current).__name__
510 if "." in module_name:
511 module_name = module_name.rsplit(".", 1)[1]
512
513 try:
514 options = self.cfg.get(module_name)
515 except CuckooOperationalError:
516 log.debug("Reporting module %s not found in configuration file", module_name)
517 return
518
519
520 if not options.enabled:
521 return
522
523
524 current.set_path(self.analysis_path)
525
526 current.set_task(self.task)
527
528 current.set_options(options)
529
530 current.cfg = Config(cfg=current.conf_path)
531
532 try:
533 current.run(self.results)
534 log.debug("Executed reporting module \"%s\"", current.__class__.__name__)
535 except CuckooDependencyError as e:
536 log.warning("The reporting module \"%s\" has missing dependencies: %s", current.__class__.__name__, e)
537 except CuckooReportError as e:
538 log.warning("The reporting module \"%s\" returned the following error: %s", current.__class__.__name__, e)
539 except:
540 log.exception("Failed to run the reporting module \"%s\":", current.__class__.__name__)
541
543 """Generates all reports.
544 @raise CuckooReportError: if a report module fails.
545 """
546
547
548
549
550 reporting_list = list_plugins(group="reporting")
551
552
553 if reporting_list:
554 reporting_list.sort(key=lambda module: module.order)
555
556
557 for module in reporting_list:
558 self.process(module)
559 else:
560 log.info("No reporting modules loaded")
561