1
2
3
4
5 import os
6 import time
7 import shutil
8 import logging
9 import Queue
10 from threading import Thread, Lock
11
12 from lib.cuckoo.common.config import Config
13 from lib.cuckoo.common.constants import CUCKOO_ROOT
14 from lib.cuckoo.common.exceptions import CuckooMachineError, CuckooGuestError
15 from lib.cuckoo.common.exceptions import CuckooOperationalError
16 from lib.cuckoo.common.exceptions import CuckooCriticalError
17 from lib.cuckoo.common.objects import File
18 from lib.cuckoo.common.utils import create_folder
19 from lib.cuckoo.core.database import Database, TASK_COMPLETED, TASK_REPORTED
20 from lib.cuckoo.core.guest import GuestManager
21 from lib.cuckoo.core.plugins import list_plugins, RunAuxiliary, RunProcessing
22 from lib.cuckoo.core.plugins import RunSignatures, RunReporting
23 from lib.cuckoo.core.resultserver import Resultserver
24
25 log = logging.getLogger(__name__)
26
27 machinery = None
28 machine_lock = Lock()
29
30 total_analysis_count = 0
31 active_analysis_count = 0
32
33
35 """Exception thrown when a machine turns dead.
36
37 When this exception has been thrown, the analysis task will start again,
38 and will try to use another machine, when available.
39 """
40 pass
41
42
44 """Analysis Manager.
45
46 This class handles the full analysis process for a given task. It takes
47 care of selecting the analysis machine, preparing the configuration and
48 interacting with the guest agent and analyzer components to launch and
49 complete the analysis and store, process and report its results.
50 """
51
53 """@param task: task object containing the details for the analysis."""
54 Thread.__init__(self)
55 Thread.daemon = True
56
57 self.task = task
58 self.errors = error_queue
59 self.cfg = Config()
60 self.storage = ""
61 self.binary = ""
62 self.machine = None
63
65 """Initialize analysis storage folder."""
66 self.storage = os.path.join(CUCKOO_ROOT,
67 "storage",
68 "analyses",
69 str(self.task.id))
70
71
72
73 if os.path.exists(self.storage):
74 log.error("Analysis results folder already exists at path \"%s\","
75 " analysis aborted", self.storage)
76 return False
77
78
79
80 try:
81 create_folder(folder=self.storage)
82 except CuckooOperationalError:
83 log.error("Unable to create analysis folder %s", self.storage)
84 return False
85
86 return True
87
98
100 """Store a copy of the file being analyzed."""
101 if not os.path.exists(self.task.target):
102 log.error("The file to analyze does not exist at path \"%s\", "
103 "analysis aborted", self.task.target)
104 return False
105
106 sha256 = File(self.task.target).get_sha256()
107 self.binary = os.path.join(CUCKOO_ROOT, "storage", "binaries", sha256)
108
109 if os.path.exists(self.binary):
110 log.info("File already exists at \"%s\"", self.binary)
111 else:
112
113
114 try:
115 shutil.copy(self.task.target, self.binary)
116 except (IOError, shutil.Error) as e:
117 log.error("Unable to store file from \"%s\" to \"%s\", "
118 "analysis aborted", self.task.target, self.binary)
119 return False
120
121 try:
122 new_binary_path = os.path.join(self.storage, "binary")
123
124 if hasattr(os, "symlink"):
125 os.symlink(self.binary, new_binary_path)
126 else:
127 shutil.copy(self.binary, new_binary_path)
128 except (AttributeError, OSError) as e:
129 log.error("Unable to create symlink/copy from \"%s\" to "
130 "\"%s\": %s", self.binary, self.storage, e)
131
132 return True
133
170
197
199 """Start analysis."""
200 succeeded = False
201 dead_machine = False
202
203 log.info("Starting analysis of %s \"%s\" (task=%d)",
204 self.task.category.upper(), self.task.target, self.task.id)
205
206
207 if not self.init_storage():
208 return False
209
210 if self.task.category == "file":
211
212
213 if not self.check_file():
214 return False
215
216
217 if not self.store_file():
218 return False
219
220
221 try:
222 self.acquire_machine()
223 except CuckooOperationalError as e:
224 log.error("Cannot acquire machine: {0}".format(e))
225 return False
226
227
228 options = self.build_options()
229
230
231 try:
232 Resultserver().add_task(self.task, self.machine)
233 except Exception as e:
234 machinery.release(self.machine.label)
235 self.errors.put(e)
236
237 aux = RunAuxiliary(task=self.task, machine=self.machine)
238 aux.start()
239
240 try:
241
242 guest_log = Database().guest_start(self.task.id,
243 self.machine.name,
244 self.machine.label,
245 machinery.__class__.__name__)
246
247 machinery.start(self.machine.label)
248 except CuckooMachineError as e:
249 log.error(str(e), extra={"task_id": self.task.id})
250 dead_machine = True
251 else:
252 try:
253
254 guest = GuestManager(self.machine.name, self.machine.ip, self.machine.platform)
255
256 guest.start_analysis(options)
257 except CuckooGuestError as e:
258 log.error(str(e), extra={"task_id": self.task.id})
259 else:
260
261 try:
262 guest.wait_for_completion()
263 succeeded = True
264 except CuckooGuestError as e:
265 log.error(str(e), extra={"task_id": self.task.id})
266 succeeded = False
267
268 finally:
269
270 aux.stop()
271
272
273 if self.cfg.cuckoo.memory_dump or self.task.memory:
274 try:
275 machinery.dump_memory(self.machine.label,
276 os.path.join(self.storage, "memory.dmp"))
277 except NotImplementedError:
278 log.error("The memory dump functionality is not available "
279 "for the current machine manager")
280 except CuckooMachineError as e:
281 log.error(e)
282
283 try:
284
285 machinery.stop(self.machine.label)
286 except CuckooMachineError as e:
287 log.warning("Unable to stop machine %s: %s",
288 self.machine.label, e)
289
290
291
292
293 Database().guest_stop(guest_log)
294
295
296
297 Resultserver().del_task(self.task, self.machine)
298
299 if dead_machine:
300
301
302
303 Database().guest_remove(guest_log)
304
305
306
307 shutil.rmtree(self.storage)
308
309
310
311
312 raise CuckooDeadMachine()
313
314 try:
315
316
317 machinery.release(self.machine.label)
318 except CuckooMachineError as e:
319 log.error("Unable to release machine %s, reason %s. "
320 "You might need to restore it manually",
321 self.machine.label, e)
322
323 return succeeded
324
326 """Process the analysis results and generate the enabled reports."""
327 results = RunProcessing(task_id=self.task.id).run()
328 RunSignatures(results=results).run()
329 RunReporting(task_id=self.task.id, results=results).run()
330
331
332
333 if self.task.category == "file" and self.cfg.cuckoo.delete_original:
334 if not os.path.exists(self.task.target):
335 log.warning("Original file does not exist anymore: \"%s\": "
336 "File not found", self.task.target)
337 else:
338 try:
339 os.remove(self.task.target)
340 except OSError as e:
341 log.error("Unable to delete original file at path "
342 "\"%s\": %s", self.task.target, e)
343
344
345
346 if self.task.category == "file" and self.cfg.cuckoo.delete_bin_copy:
347 if not os.path.exists(self.binary):
348 log.warning("Copy of the original file does not exist anymore: \"%s\": File not found", self.binary)
349 else:
350 try:
351 os.remove(self.binary)
352 except OSError as e:
353 log.error("Unable to delete the copy of the original file at path \"%s\": %s", self.binary, e)
354
355 log.info("Task #%d: reports generation completed (path=%s)",
356 self.task.id, self.storage)
357
358 return True
359
387
389 """Tasks Scheduler.
390
391 This class is responsible for the main execution loop of the tool. It
392 prepares the analysis machines and keep waiting and loading for new
393 analysis tasks.
394 Whenever a new task is available, it launches AnalysisManager which will
395 take care of running the full analysis process and operating with the
396 assigned analysis machine.
397 """
398
403
442
448
450 """Start scheduler."""
451 global total_analysis_count
452 self.initialize()
453
454 log.info("Waiting for analysis tasks...")
455
456
457 errors = Queue.Queue()
458
459 maxcount = self.cfg.cuckoo.max_analysis_count
460
461
462 while self.running:
463 time.sleep(1)
464
465
466
467
468 if self.cfg.cuckoo.freespace:
469
470
471 dir_path = os.path.join(CUCKOO_ROOT, "storage", "analyses")
472
473
474 if hasattr(os, "statvfs"):
475 dir_stats = os.statvfs(dir_path)
476
477
478 space_available = dir_stats.f_bavail * dir_stats.f_frsize
479 space_available /= 1024 * 1024
480
481 if space_available < self.cfg.cuckoo.freespace:
482 log.error("Not enough free diskspace! (Only %d MB!)",
483 space_available)
484 continue
485
486
487
488 if machinery.availables() == 0:
489 continue
490
491
492
493 if maxcount and total_analysis_count >= maxcount:
494 if active_analysis_count <= 0:
495 self.stop()
496 else:
497
498 task = self.db.fetch()
499
500 if task:
501 log.debug("Processing task #%s", task.id)
502 total_analysis_count += 1
503
504
505 analysis = AnalysisManager(task, errors)
506
507 analysis.start()
508
509
510 try:
511 error = errors.get(block=False)
512 except Queue.Empty:
513 pass
514 else:
515 raise error
516