def stop(self, label): """Stops a virtual machine. @param label: virtual machine name. @raise CuckooMachineError: if unable to stop. """ vmid = self.vm_label_id[ label ]['vmid'] log.debug("Stopping vm %s id %s" % ( label, vmid ) ) status = self._status(label) if status == self.SAVED: return if status == self.POWEROFF: raise CuckooMachineError( "Trying to stop an already stopped VM: %s" % label ) try: self.nodes.post('%s/qemu/%s/status/stop' % ( self.nodename, vmid ) ) except OSError as e: raise CuckooMachineError( "Proxmox failed powering off the machine: %s" % e ) self._wait_status(label, self.POWEROFF, self.SAVED, self.FINISH)
def start(self, vmx_path, task): """Start a virtual machine. @param vmx_path: path to vmx file. @param task: task object. @raise CuckooMachineError: if unable to start. """ snapshot = self._snapshot_from_vmx(vmx_path) # Preventive check if self._is_running(vmx_path): raise CuckooMachineError("Machine %s is already running" % vmx_path) self._revert(vmx_path, snapshot) time.sleep(3) log.debug("Starting vm %s" % vmx_path) try: p = subprocess.Popen([ self.options.vmware.path, "start", vmx_path, self.options.vmware.mode ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) if self.options.vmware.mode.lower() == "gui": output, _ = p.communicate() if output: raise CuckooMachineError("Unable to start machine " "%s: %s" % (vmx_path, output)) except OSError as e: mode = self.options.vmware.mode.upper() raise CuckooMachineError("Unable to start machine %s in %s " "mode: %s" % (vmx_path, mode, e))
def _initialize_check(self): """Check XenServer configuration, initialize a Xen API connection, and verify machine validity. """ self._sessions = {} if not HAVE_XENAPI: raise CuckooDependencyError("Unable to import XenAPI") if not self.options.xenserver.user: raise CuckooMachineError("XenServer username missing, please add " "it to xenserver.conf.") if not self.options.xenserver.password: raise CuckooMachineError("XenServer password missing, please add " "it to xenserver.conf") if not self.options.xenserver.url: raise CuckooMachineError("XenServer url missing, please add it to " "xenserver.conf") self._make_xenapi_session() for machine in self.machines(): uuid = machine.label (ref, vm) = self._check_vm(uuid) if machine.snapshot: self._check_snapshot(uuid, machine.snapshot) else: self._check_disks_reset(vm) super(XenServer, self)._initialize_check()
def stop(self, label): """Stops a virtual machine. Kill them all. @param label: virtual machine name. @raise CuckooMachineError: if unable to stop virtual machine. """ log.debug("Stopping machine %s", label) if self._status(label) == self.POWEROFF: raise CuckooMachineError("Trying to stop an already stopped " "machine {0}".format(label)) # Force virtual machine shutdown. conn = self._connect() try: if not self.vms[label].isActive(): log.debug( "Trying to stop an already stopped machine %s. " "Skip", label) else: self.vms[label].destroy() # Machete's way! except libvirt.libvirtError as e: raise CuckooMachineError("Error stopping virtual machine " "{0}: {1}".format(label, e)) finally: self._disconnect(conn) # Check state. self._wait_status(label, self.POWEROFF)
def start(self, label, task): """Start a virtual machine. @param label: virtual machine name. @param task: task object. @raise CuckooMachineError: if unable to start. """ vmid = self.vm_label_id[ label ]['vmid'] log.debug("Starting vm %s id %s", label, vmid) if self._status(label) == self.RUNNING: raise CuckooMachineError( "Trying to start an already started VM: %s" % label ) machine = self.db.view_machine_by_label(label) self.restore(label, machine) # Proxmox can revert to saved paused state but the state is in the qemu status not the vm status. self._wait_status(label, self.SAVED, self.FINISH, self.RUNNING) if not self._status(label) == self.RUNNING: try: self.nodes.post('%s/qemu/%s/status/start' % ( self.nodename, vmid ) ) except OSError as e: raise CuckooMachineError( "Proxmox failed to start the machine: %s" % e ) self._wait_status(label, self.RUNNING) if "nictrace" in machine.options: self.dump_pcap(label, task)
def stop(self, label): """Stop a virtual machine. @param label: virtual machine name. @raise CuckooMachineError: if unable to stop. """ log.debug("Stopping vm %s" % label) status = self._status(label) # The VM has already been restored, don't shut it down again. This # appears to be a VirtualBox-specific state though, hence we handle # it here rather than in Machinery._initialize_check(). if status == self.SAVED: return if status == self.POWEROFF or status == self.ABORTED: raise CuckooMachineError( "Trying to stop an already stopped VM: %s" % label ) vm_state_timeout = config("cuckoo:timeouts:vm_state") try: args = [ self.options.virtualbox.path, "controlvm", label, "poweroff" ] with _power_lock: proc = Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) # Sometimes VBoxManage stucks when stopping vm so we needed # to add a timeout and kill it after that. stop_me = 0 while proc.poll() is None: if stop_me < vm_state_timeout: time.sleep(1) stop_me += 1 else: log.debug("Stopping vm %s timeouted. Killing" % label) proc.terminate() if proc.returncode != 0 and stop_me < vm_state_timeout: _, err = proc.communicate() log.debug( "VBoxManage exited with error powering off the " "machine: %s", err ) raise OSError(err) except OSError as e: raise CuckooMachineError( "VBoxManage failed powering off the machine: %s" % e ) self._wait_status(label, self.POWEROFF, self.ABORTED, self.SAVED)
def start(self, label, task): """Starts a virtual machine. @param label: virtual machine name. @param task: task object. @raise CuckooMachineError: if unable to start virtual machine. """ log.debug("Starting machine %s", label) if self._status(label) != self.POWEROFF: msg = "Trying to start a virtual machine that has not " \ "been turned off {0}".format(label) raise CuckooMachineError(msg) conn = self._connect() vm_info = self.db.view_machine_by_label(label) snapshot_list = self.vms[label].snapshotListNames(flags=0) # If a snapshot is configured try to use it. if vm_info.snapshot and vm_info.snapshot in snapshot_list: # Revert to desired snapshot, if it exists. log.debug("Using snapshot {0} for virtual machine " "{1}".format(vm_info.snapshot, label)) try: vm = self.vms[label] snapshot = vm.snapshotLookupByName(vm_info.snapshot, flags=0) self.vms[label].revertToSnapshot(snapshot, flags=0) #modify by xuyuu self.vms[label].create() except libvirt.libvirtError: msg = "Unable to restore snapshot {0} on " \ "virtual machine {1}".format(vm_info.snapshot, label) raise CuckooMachineError(msg) finally: self._disconnect(conn) elif self._get_snapshot(label): snapshot = self._get_snapshot(label) log.debug("Using snapshot {0} for virtual machine " "{1}".format(snapshot.getName(), label)) try: self.vms[label].revertToSnapshot(snapshot, flags=0) #modify by xuyuu self.vms[label].create() except libvirt.libvirtError: raise CuckooMachineError("Unable to restore snapshot on " "virtual machine {0}".format(label)) finally: self._disconnect(conn) else: self._disconnect(conn) raise CuckooMachineError("No snapshot found for virtual machine " "{0}".format(label)) # Check state. self._wait_status(label, self.RUNNING)
def start(self, label, task, revert=True): """Start a virtual machine. @param label: virtual machine name. @param task: task object. @param revert: Revert machine to snapshot @raise CuckooMachineError: if unable to start. """ log.debug("Starting vm %s", label) if self._status(label) == self.RUNNING: raise CuckooMachineError( "Trying to start an already started VM: %s" % label) machine = self.db.view_machine_by_label(label) if revert: self.restore(label, machine) self._wait_status(label, self.SAVED) else: self.compact_hd(label) if self.remote_control: self.enable_vrde(label) try: args = [ self.options.virtualbox.path, "startvm", label, "--type", self.options.virtualbox.mode ] with _power_lock: _, err = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True).communicate() if err: raise OSError(err) except OSError as e: if self.options.virtualbox.mode == "gui": raise CuckooMachineError( "VBoxManage failed starting the machine in gui mode! " "In case you're on a headless server, you should probably " "try using 'headless' mode. Error: %s" % e) else: raise CuckooMachineError( "VBoxManage failed starting the machine in headless mode. " "Are you sure your machine is still functioning correctly " "when trying to use it manually? Error: %s" % e) self._wait_status(label, self.RUNNING) # Handle network dumping through the internal VirtualBox functionality. if "nictrace" in machine.options: self.dump_pcap(label, task)
def _check_vmx(self, vmx_path): """Check whether a vmx file exists and is valid. @param vmx_path: path to vmx file @raise CuckooMachineError: if file not found or not ending with .vmx """ if not vmx_path.endswith(".vmx"): raise CuckooMachineError("Wrong configuration: vm path not " "ending with .vmx: %s)" % vmx_path) if not os.path.exists(vmx_path): raise CuckooMachineError("Vm file %s not found" % vmx_path)
def dump_memory(self, label, path): """Take a memory dump. @param path: path to where to store the memory dump. """ try: args = [self.options.virtualbox.path, "-v"] proc = Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) output, err = proc.communicate() if proc.returncode != 0: # It's quite common for virtualbox crap utility to exit with: # VBoxManage: error: Details: code E_ACCESSDENIED (0x80070005) # So we just log to debug this. log.debug( "VBoxManage returns error checking status for " "machine %s: %s", label, err ) except OSError as e: raise CuckooMachineError( "VBoxManage failed to return its version: %s" % e ) # VirtualBox version 4, 5 and 6 if output.startswith(("5", "6")): dumpcmd = "dumpvmcore" else: dumpcmd = "dumpguestcore" try: args = [ self.options.virtualbox.path, "debugvm", label, dumpcmd, "--filename", path ] Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ).wait() log.info( "Successfully generated memory dump for virtual machine " "with label %s to path %s", label, path.encode("utf8") ) except OSError as e: raise CuckooMachineError( "VBoxManage failed to take a memory dump of the machine " "with label %s: %s" % (label, e) )
def _connect(self): """Connects to libvirt subsystem. @raise CuckooMachineError: when unable to connect to libvirt. """ # Check if a connection string is available. if not self.dsn: raise CuckooMachineError("You must provide a proper " "connection string") try: return libvirt.open(self.dsn) except libvirt.libvirtError: raise CuckooMachineError("Cannot connect to libvirt")
def _initialize_check(self): """Runs all checks when a machine manager is initialized. @raise CuckooMachineError: if configuration is invalid """ if not self.options.esx.dsn: raise CuckooMachineError("ESX(i) DSN is missing, please add it to the config file") if not self.options.esx.username: raise CuckooMachineError("ESX(i) username is missing, please add it to the config file") if not self.options.esx.password: raise CuckooMachineError("ESX(i) password is missing, please add it to the config file") self.dsn = self.options.esx.dsn self.global_conn = self._global_connect() super(ESX, self)._initialize_check()
def _revert_snapshot(self, vm, name): """Revert virtual machine to named snapshot""" snapshot = self._get_snapshot_by_name(vm, name) if snapshot: log.info("Reverting machine %s to snapshot %s", vm.summary.config.name, name) task = snapshot.RevertToSnapshot_Task() try: self._wait_task(task) except CuckooMachineError as e: raise CuckooMachineError("RevertToSnapshot: %s" % e) else: raise CuckooMachineError("Snapshot %s for machine %s not found" % (name, vm.summary.config.name))
def _wait_task(self, task): """Wait for a task to complete with timeout""" limit = datetime.timedelta(seconds=config("cuckoo:timeouts:vm_state")) start = datetime.datetime.utcnow() while True: if task.info.state == "error": raise CuckooMachineError("Task error") if task.info.state == "success": break if datetime.datetime.utcnow() - start > limit: raise CuckooMachineError("Task timed out") time.sleep(1)
def _initialize_check(self): """Ensures that credentials have been entered into the config file. @raise CuckooCriticalError: if no credentials were provided or if one or more physical machines are offline. """ # TODO This should be moved to a per-machine thing. if not self.options.physical.user or not self.options.physical.password: raise CuckooCriticalError( "Physical machine credentials are missing, please add it to " "the Physical machinery configuration file." ) self.fog_init() for machine in self.machines(): status = self._status(machine.label) if status == self.STOPPED: # Send a Wake On Lan message (if we're using FOG). self.wake_on_lan(machine.label) elif status == self.ERROR: raise CuckooMachineError( "Unknown error occurred trying to obtain the status of " "physical machine %s. Please turn it on and check the " "Cuckoo Agent." % machine.label )
def _list(self): """Lists virtual machines installed. @return: virtual machine names list. """ try: args = [self.options.virtualbox.path, "list", "vms"] output, _ = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True).communicate() except OSError as e: raise CuckooMachineError( "VBoxManage error listing installed machines: %s" % e) machines = [] for line in output.split("\n"): if '"' not in line: continue label = line.split('"')[1] if label == "<inaccessible>": log.warning( "Found an inaccessible virtual machine, please check " "its state.") continue machines.append(label) return machines
def fog_init(self): """Initiate by indexing FOG regarding all available machines.""" self.fog_machines = {} if self.options.fog.hostname == "none": return # TODO Handle exceptions such as not being able to connect. r = self.fog_query("node=tasks&sub=listhosts") # Parse the HTML. b = bs4.BeautifulSoup(r.content, "html.parser") if not b.find_all("table"): raise CuckooCriticalError( "The supplied FOG username and/or password do not allow us " "to login into FOG, please configure the correct credentials.") # Mapping for physical machine hostnames to their mac address and uri # for "downloading" a safe image onto the host. Great piece of FOG API # usage here. for row in b.find_all("table")[0].find_all("tr")[1:]: hostname, macaddr, download, upload, advanced = row.find_all("td") self.fog_machines[hostname.text] = ( macaddr.text, next(download.children).attrs["href"][1:], ) # Check whether all our machines are available on FOG. for machine in self.machines(): if machine.label not in self.fog_machines: raise CuckooMachineError( "The physical machine %s has not been defined in FOG, " "please investigate and configure the configuration " "correctly." % machine.label)
def enable_vrde(self, label): try: proc = self._set_flag(label, "vrde", "on") if proc.returncode != 0: log.error("VBoxManage returned non-zero value while enabling " "remote control: %d" % proc.returncode) return False proc = self._set_flag(label, "vrdemulticon", "on") if proc.returncode != 0: log.error("VBoxManage returned non-zero value while enabling " "remote control multicon: %d" % proc.returncode) return False self._set_vrde_ports(label, self.options.virtualbox.controlports) log.info( "Successfully enabled remote control for virtual machine " "with label %s on port(s): %s", label, self.vminfo(label, "vrdeports") ) except OSError as e: raise CuckooMachineError( "VBoxManage failed to enable remote control: %s" % e )
def _check_disks_reset(self, vm): """Check whether each attached disk is set to reset on boot. @param vm: vm record """ for ref in vm["VBDs"]: try: vbd = self.session.xenapi.VBD.get_record(ref) except: log.warning("Invalid VBD for vm %s: %s", vm["uuid"], ref) continue if vbd["type"] == "Disk": vdi_ref = vbd["VDI"] try: vdi = self.session.xenapi.VDI.get_record(vdi_ref) except: log.warning("Invalid VDI for vm %s: %s", vm["uuid"], vdi_ref) continue if vdi["on_boot"] != "reset" and vdi["read_only"] is False: raise CuckooMachineError( "Vm %s contains invalid VDI %s: disk is not reset on " "boot. Please set the on-boot parameter to 'reset'." % (vm["uuid"], vdi["uuid"]))
def stop(self, label): """Stops a physical machine. @param label: physical machine name. @raise CuckooMachineError: if unable to stop. """ # Since we are 'stopping' a physical machine, it must # actually be rebooted to kick off the re-imaging process. creds = "%s%%%s" % ( self.options.physical.user, self.options.physical.password ) if self._status(label) == self.RUNNING: log.debug("Rebooting machine: %s.", label) machine = self._get_machine(label) args = [ "net", "rpc", "shutdown", "-I", machine.ip, "-U", creds, "-r", "-f", "--timeout=5" ] output = subprocess.check_output(args) if "Shutdown of remote machine succeeded" not in output: raise CuckooMachineError("Unable to initiate RPC request") else: log.debug("Reboot success: %s." % label) # Deploy a clean image through FOG, assuming we're using FOG. self.fog_queue_task(label) # Hold here until we are certain the physical guest is rebooting while self._status(label) == self.RUNNING: time.sleep(1) continue
def stop(self, label): """Stops a virtual machine. @param label: virtual machine label. @raise CuckooMachineError: if unable to stop. """ log.debug("Stopping vm %s" % label) vm_info = self.db.view_machine_by_label(label) if self._status(vm_info.name) == self.STOPPED: raise CuckooMachineError( "Trying to stop an already stopped vm %s" % label) proc = self.state.get(vm_info.name, None) proc.kill() stop_me = 0 while proc.poll() is None: if stop_me < config("cuckoo:timeouts:vm_state"): time.sleep(1) stop_me += 1 else: log.debug("Stopping vm %s timeouted. Killing" % label) proc.terminate() time.sleep(1) # if proc.returncode != 0 and stop_me < config("cuckoo:timeouts:vm_state"): # log.debug("QEMU exited with error powering off the machine") self.state[vm_info.name] = None
def _wait_status(self, label, *states): """Waits for a vm status. @param label: virtual machine name. @param state: virtual machine status, accepts multiple states as list. @raise CuckooMachineError: if default waiting timeout expire. """ # This block was originally suggested by Loic Jaquemet. waitme = 0 try: current = self._status(label) except NameError: return while current not in states: log.debug( "Waiting %i cuckooseconds for machine %s to switch " "to status %s", waitme, label, states) if waitme > config("cuckoo:timeouts:vm_state"): raise CuckooMachineError( "Timeout hit while for machine %s to change status" % label) time.sleep(1) waitme += 1 current = self._status(label)
def _initialize_check(self): """Init KVM configuration to open libvirt dsn connection.""" self._sessions = {} if not self.options.kvm.dsn: raise CuckooMachineError( "KVM(i) DSN is missing, please add it to the config file") self.dsn = self.options.kvm.dsn super(KVM, self)._initialize_check()
def _disconnect(self, conn): """Disconnects to libvirt subsystem. @raise CuckooMachineError: if cannot disconnect from libvirt. """ try: conn.close() except libvirt.libvirtError: raise CuckooMachineError("Cannot disconnect from libvirt")
def start(self, label, task): """Start a virtual machine. @param label: virtual machine name. @param task: task object. @raise CuckooMachineError: if unable to start. """ log.debug("Obtaining vm %s", label) vbox = self._connect() machine = self._get_machine(vbox, label) if machine.state() == machine.RUNNING: log.debug("Turning off machine") machine.poweroff() log.debug("Restoring machine") machine_conf = self.db.view_machine_by_label(label) try: if machine_conf.snapshot: machine.restore(machine_conf.snapshot) else: """Restore to a current snapshot""" machine.restore() except remotevbox.exceptions.MachineSnapshotNX as e: raise CuckooMachineError( "No current snapshot or a specified snapshot " "not found: %s" % e) log.debug("Enable network tracing") try: machine.enable_net_trace( self.options.virtualbox_websrv.remote_storage + '/analyses/' + str(task.id) + '/dump.pcap') except remotevbox.exceptions.MachineEnableNetTraceError as e: CuckooMachineError("Can't enable net trace: %s" % e) except remotevbox.exceptions.MachineSetTraceFileError as e: CuckooMachineError("Can't enable net trace: %s" % e) log.debug("Start vm %s" % label) try: machine.launch() except remotevbox.exceptions.MachineLaunchError as e: CuckooMachineError("Can't start virtual machine: %s" % e) vbox.disconnect()
def _status(self, label): """Gets current status of a vm. @param label: virtual machine name. @return: status string. """ log.debug("Getting status for %s", label) # Stetes mapping of python-libvirt. # virDomainState # VIR_DOMAIN_NOSTATE = 0 # VIR_DOMAIN_RUNNING = 1 # VIR_DOMAIN_BLOCKED = 2 # VIR_DOMAIN_PAUSED = 3 # VIR_DOMAIN_SHUTDOWN = 4 # VIR_DOMAIN_SHUTOFF = 5 # VIR_DOMAIN_CRASHED = 6 # VIR_DOMAIN_PMSUSPENDED = 7 conn = self._connect() try: state = self.vms[label].state(flags=0) except libvirt.libvirtError as e: raise CuckooMachineError("Error getting status for virtual " "machine {0}: {1}".format(label, e)) finally: self._disconnect(conn) if state: if state[0] == 1: status = self.RUNNING elif state[0] == 3: status = self.PAUSED elif state[0] == 4 or state[0] == 5: status = self.POWEROFF else: status = self.ERROR # Report back status. if status: self.set_status(label, status) return status else: raise CuckooMachineError("Unable to get status for " "{0}".format(label))
def _make_xenapi_session(self, tid=None): tid = tid or threading.current_thread().ident try: sess = XenAPI.Session(self.options.xenserver.url) except: raise CuckooMachineError("Could not connect to XenServer: invalid " "or incorrect url, please ensure the url " "is correct in xenserver.conf") try: sess.xenapi.login_with_password(self.options.xenserver.user, self.options.xenserver.password) except: raise CuckooMachineError("Could not connect to XenServer: " "incorrect credentials, please ensure " "the user and password are correct in " "xenserver.conf") self._sessions[tid] = sess return sess
def stop(self, vmx_path): """Stop a virtual machine. @param vmx_path: path to vmx file @raise CuckooMachineError: if unable to stop. """ log.debug("Stopping vm %s" % vmx_path) if self._is_running(vmx_path): try: if subprocess.call( [self.options.vmware.path, "stop", vmx_path, "hard"], stdout=subprocess.PIPE, stderr=subprocess.PIPE): raise CuckooMachineError("Error shutting down " "machine %s" % vmx_path) except OSError as e: raise CuckooMachineError("Error shutting down machine " "%s: %s" % (vmx_path, e)) else: log.warning("Trying to stop an already stopped machine: %s", vmx_path)
def _revert(self, vmx_path, snapshot): """Revert machine to snapshot. @param vmx_path: path to vmx file @param snapshot: snapshot name @raise CuckooMachineError: if unable to revert """ log.debug("Revert snapshot for vm %s" % vmx_path) try: if subprocess.call([ self.options.vmware.path, "revertToSnapshot", vmx_path, snapshot ], stdout=subprocess.PIPE, stderr=subprocess.PIPE): raise CuckooMachineError("Unable to revert snapshot for " "machine %s: vmrun exited with " "error" % vmx_path) except OSError as e: raise CuckooMachineError("Unable to revert snapshot for " "machine %s: %s" % (vmx_path, e))
def _is_running(self, vmx_path): """Check if virtual machine is running. @param vmx_path: path to vmx file @return: running status """ try: p = subprocess.Popen([self.options.vmware.path, "list"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, error = p.communicate() except OSError as e: raise CuckooMachineError("Unable to check running status for %s. " "Reason: %s" % (vmx_path, e)) else: if output: return vmx_path.lower() in output.lower() else: raise CuckooMachineError("Unable to check running status " "for %s. No output from " "`vmrun list`" % vmx_path)