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