modules.machinery.virtualbox

Package modules :: Package machinery :: Module virtualbox |
[hide private]
[frames] | no frames]

# Source Code for Module modules.machinery.virtualbox

      1  # Copyright (C) 2010-2013 Claudio Guarnieri.
      2  # Copyright (C) 2014-2016 Cuckoo Foundation.
      3  # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
      4  # See the file 'docs/LICENSE' for copying permission.
      5
      6  import os
      7  import re
      8  import time
      9  import logging
     10  import subprocess
     11  import os.path
     12
     13  from lib.cuckoo.common.abstracts import Machinery
     14  from lib.cuckoo.common.exceptions import CuckooCriticalError
     15  from lib.cuckoo.common.exceptions import CuckooMachineError
     16
     17  log = logging.getLogger(__name__)
     18
     19 -class VirtualBox(Machinery):
     20      """Virtualization layer for VirtualBox."""
     21
     22      # VM states.
     23      SAVED = "saved"
     24      RUNNING = "running"
     25      POWEROFF = "poweroff"
     26      ABORTED = "aborted"
     27      ERROR = "machete"
     28
     29 -    def _initialize_check(self):
     30          """Runs all checks when a machine manager is initialized.
     31          @raise CuckooMachineError: if VBoxManage is not found.
     32          """
     33          # VirtualBox specific checks.
     34          if not self.options.virtualbox.path:
     35              raise CuckooCriticalError("VirtualBox VBoxManage path missing, "
     36                                        "please add it to the config file")
     37          if not os.path.exists(self.options.virtualbox.path):
     38              raise CuckooCriticalError("VirtualBox VBoxManage not found at "
     39                                        "specified path \"%s\"" %
     40                                        self.options.virtualbox.path)
     41
     42          # Base checks.
     43          super(VirtualBox, self)._initialize_check()
     44
     45 -    def start(self, label, task):
     46          """Start a virtual machine.
     47          @param label: virtual machine name.
     48          @param task: task object.
     49          @raise CuckooMachineError: if unable to start.
     50          """
     51          log.debug("Starting vm %s" % label)
     52
     53          if self._status(label) == self.RUNNING:
     54              raise CuckooMachineError("Trying to start an already "
     55                                       "started vm %s" % label)
     56
     57          machine = self.db.view_machine_by_label(label)
     58          virtualbox_args = [self.options.virtualbox.path, "snapshot", label]
     59          if machine.snapshot:
     60              log.debug("Using snapshot {0} for virtual machine "
     61                        "{1}".format(machine.snapshot, label))
     62              virtualbox_args.extend(["restore", machine.snapshot])
     63          else:
     64              log.debug("Using current snapshot for virtual machine "
     65                        "{0}".format(label))
     66              virtualbox_args.extend(["restorecurrent"])
     67
     68          try:
     69              if subprocess.call(virtualbox_args,
     70                                 stdout=subprocess.PIPE,
     71                                 stderr=subprocess.PIPE,
     72                                 close_fds=True):
     73                  raise CuckooMachineError("VBoxManage exited with error "
     74                                           "restoring the machine's snapshot")
     75          except OSError as e:
     76              raise CuckooMachineError("VBoxManage failed restoring the "
     77                                       "machine: %s" % e)
     78
     79          self._wait_status(label, self.SAVED)
     80
     81          try:
     82              proc = subprocess.Popen([self.options.virtualbox.path,
     83                                       "startvm",
     84                                       label,
     85                                       "--type",
     86                                       self.options.virtualbox.mode],
     87                                      stdout=subprocess.PIPE,
     88                                      stderr=subprocess.PIPE,
     89                                      close_fds=True)
     90              output, err = proc.communicate()
     91              if err:
     92                  raise OSError(err)
     93          except OSError as e:
     94              raise CuckooMachineError("VBoxManage failed starting the machine "
     95                                       "in %s mode: %s" %
     96                                       (self.options.virtualbox.mode.upper(), e))
     97
     98          self._wait_status(label, self.RUNNING)
     99
    100          # Handle network dumping through the internal VirtualBox functionality.
    101          if "nictrace" in machine.options:
    102              self.dump_pcap(label, task)
    103
    104 -    def dump_pcap(self, label, task):
    105          """Dump the pcap for this analysis through the VirtualBox integrated
    106          nictrace functionality. This is useful in scenarios where multiple
    107          Virtual Machines are talking with each other in the same subnet (which
    108          you normally don't see when tcpdump'ing on the gatway)."""
    109          try:
    110              args = [
    111                  self.options.virtualbox.path,
    112                  "controlvm", label,
    113                  "nictracefile1", self.pcap_path(task.id),
    114              ]
    115              subprocess.check_call(args)
    116          except subprocess.CalledProcessError as e:
    117              log.critical("Unable to set NIC tracefile (pcap file): %s", e)
    118              return
    119
    120          try:
    121              args = [
    122                  self.options.virtualbox.path,
    123                  "controlvm", label,
    124                  "nictrace1", "on",
    125              ]
    126              subprocess.check_call(args)
    127          except subprocess.CalledProcessError as e:
    128              log.critical("Unable to enable NIC tracing (pcap file): %s", e)
    129              return
    130
    131 -    def stop(self, label):
    132          """Stops a virtual machine.
    133          @param label: virtual machine name.
    134          @raise CuckooMachineError: if unable to stop.
    135          """
    136          log.debug("Stopping vm %s" % label)
    137
    138          if self._status(label) in [self.POWEROFF, self.ABORTED]:
    139              raise CuckooMachineError("Trying to stop an already stopped "
    140                                       "vm %s" % label)
    141
    142          try:
    143              proc = subprocess.Popen([self.options.virtualbox.path,
    144                                       "controlvm", label, "poweroff"],
    145                                      stdout=subprocess.PIPE,
    146                                      stderr=subprocess.PIPE,
    147                                      close_fds=True)
    148              # Sometimes VBoxManage stucks when stopping vm so we needed
    149              # to add a timeout and kill it after that.
    150              stop_me = 0
    151              while proc.poll() is None:
    152                  if stop_me < int(self.options_globals.timeouts.vm_state):
    153                      time.sleep(1)
    154                      stop_me += 1
    155                  else:
    156                      log.debug("Stopping vm %s timeouted. Killing" % label)
    157                      proc.terminate()
    158
    159              if proc.returncode != 0 and \
    160                      stop_me < int(self.options_globals.timeouts.vm_state):
    161                  log.debug("VBoxManage exited with error "
    162                            "powering off the machine")
    163          except OSError as e:
    164              raise CuckooMachineError("VBoxManage failed powering off the "
    165                                       "machine: %s" % e)
    166          self._wait_status(label, [self.POWEROFF, self.ABORTED, self.SAVED])
    167
    168 -    def _list(self):
    169          """Lists virtual machines installed.
    170          @return: virtual machine names list.
    171          """
    172          try:
    173              proc = subprocess.Popen([self.options.virtualbox.path,
    174                                       "list", "vms"],
    175                                      stdout=subprocess.PIPE,
    176                                      stderr=subprocess.PIPE,
    177                                      close_fds=True)
    178              output, _ = proc.communicate()
    179          except OSError as e:
    180              raise CuckooMachineError("VBoxManage error listing "
    181                                       "installed machines: %s" % e)
    182
    183          machines = []
    184          for line in output.split("\n"):
    185              try:
    186                  label = line.split('"')[1]
    187                  if label == "<inaccessible>":
    188                      log.warning("Found an inaccessible virtual machine, "
    189                                  "please check its state.")
    190                  else:
    191                      machines.append(label)
    192              except IndexError:
    193                  continue
    194
    195          return machines
    196
    197 -    def _status(self, label):
    198          """Gets current status of a vm.
    199          @param label: virtual machine name.
    200          @return: status string.
    201          """
    202          log.debug("Getting status for %s" % label)
    203          status = None
    204          try:
    205              proc = subprocess.Popen([self.options.virtualbox.path,
    206                                       "showvminfo",
    207                                       label,
    208                                       "--machinereadable"],
    209                                      stdout=subprocess.PIPE,
    210                                      stderr=subprocess.PIPE,
    211                                      close_fds=True)
    212              output, err = proc.communicate()
    213
    214              if proc.returncode != 0:
    215                  # It's quite common for virtualbox crap utility to exit with:
    216                  # VBoxManage: error: Details: code E_ACCESSDENIED (0x80070005)
    217                  # So we just log to debug this.
    218                  log.debug("VBoxManage returns error checking status for "
    219                            "machine %s: %s", label, err)
    220                  status = self.ERROR
    221          except OSError as e:
    222              log.warning("VBoxManage failed to check status for machine %s: %s",
    223                          label, e)
    224              status = self.ERROR
    225          if not status:
    226              for line in output.split("\n"):
    227                  state = re.match(r'VMState="(\w+)"', line, re.M | re.I)
    228                  if state:
    229                      status = state.group(1)
    230                      log.debug("Machine %s status %s" % (label, status))
    231                      status = status.lower()
    232          # Report back status.
    233          if status:
    234              self.set_status(label, status)
    235              return status
    236          else:
    237              raise CuckooMachineError("Unable to get status for %s" % label)
    238
    239 -    def dump_memory(self, label, path):
    240          """Takes a memory dump.
    241          @param path: path to where to store the memory dump.
    242          """
    243
    244          try:
    245              proc = subprocess.Popen([self.options.virtualbox.path, "-v"],
    246                                      stdout=subprocess.PIPE,
    247                                      stderr=subprocess.PIPE,
    248                                      close_fds=True)
    249              output, err = proc.communicate()
    250
    251              if proc.returncode != 0:
    252                  # It's quite common for virtualbox crap utility to exit with:
    253                  # VBoxManage: error: Details: code E_ACCESSDENIED (0x80070005)
    254                  # So we just log to debug this.
    255                  log.debug("VBoxManage returns error checking status for "
    256                            "machine %s: %s", label, err)
    257          except OSError as e:
    258              raise CuckooMachineError("VBoxManage failed return it's version: %s" % (e))
    259
    260          if output[:1] == str(5):
    261              # VirtualBox version 5.x
    262              dumpcmd = "dumpvmcore"
    263          else:
    264              # VirtualBox version 4.x
    265              dumpcmd = "dumpguestcore"
    266
    267          try:
    268              subprocess.call([self.options.virtualbox.path, "debugvm",
    269                               label, dumpcmd, "--filename", path],
    270                              stdout=subprocess.PIPE,
    271                              stderr=subprocess.PIPE,
    272                              close_fds=True)
    273              log.info("Successfully generated memory dump for virtual machine "
    274                       "with label %s to path %s", label, path)
    275          except OSError as e:
    276              raise CuckooMachineError("VBoxManage failed to take a memory "
    277                                       "dump of the machine with label %s: %s" %
    278                                       (label, e))
    279