def _check_snapshot(self, vm_uuid, snapshot_uuid): """Check snapshot existence and that the snapshot is of the specified vm uuid. @param vm_uuid: vm uuid @param snapshot_uuid: snapshot uuid """ try: snapshot_ref = self._get_vm_ref(snapshot_uuid) snapshot = self._get_vm_record(snapshot_ref) except: raise CuckooMachineError("Snapshot not found: %s" % snapshot_uuid) if not snapshot["is_a_snapshot"]: raise CuckooMachineError("Invalid snapshot: %s" % snapshot_uuid) try: parent = self._get_vm_record(snapshot["snapshot_of"]) except: raise CuckooMachineError("Invalid snapshot: %s" % snapshot_uuid) parent_uuid = parent["uuid"] if parent_uuid != vm_uuid: raise CuckooMachineError("Snapshot does not belong to specified " "vm: %s" % snapshot_uuid)
def _check_snapshot(self, vmx_path, snapshot): """Checks snapshot existance. @param vmx_path: path to vmx file @param snapshot: snapshot name @raise CuckooMachineError: if snapshot not found """ try: p = subprocess.Popen( [self.options.vmware.path, "listSnapshots", vmx_path], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) output, _ = p.communicate() except OSError as e: raise CuckooMachineError( f"Unable to get snapshot list for {vmx_path}: {e}") else: if output: return snapshot in output else: raise CuckooMachineError( f"Unable to get snapshot list for {vmx_path}, no output from `vmrun listSnapshots`" )
def _delete_snapshot(self, vm, name): """Remove named snapshot of virtual machine""" snapshot = self._get_snapshot_by_name(vm, name) if snapshot: log.info("Removing snapshot {0} for machine {1}".format( name, vm.summary.config.name)) task = snapshot.RemoveSnapshot_Task(removeChildren=True) try: self._wait_task(task) except CuckooMachineError as e: log.error("RemoveSnapshot: {0}".format(e)) else: raise CuckooMachineError( "Snapshot {0} for machine {1} not found".format( name, vm.summary.config.name))
def dump_memory(self, label, path): """Take a memory dump of a machine. @param path: path to where to store the memory dump @raise CuckooMachineError: if error taking the memory dump """ name = "cuckoo_memdump_{0}".format(random.randint(100000, 999999)) with SmartConnection(**self.connect_opts) as conn: vm = self._get_virtual_machine_by_label(conn, label) if vm: self._create_snapshot(vm, name) self._download_snapshot(conn, vm, name, path) self._delete_snapshot(vm, name) else: raise CuckooMachineError( "Machine {0} not found on host".format(label))
def start(self, label): """Start a physical machine. @param label: physical machine name. @param task: task object. @raise CuckooMachineError: if unable to start. """ # Check to ensure a given machine is running log.debug("Checking if machine %s is running", label) status = self._status(label) if status == self.RUNNING: log.debug("Machine already running: %s", label) elif status == self.STOPPED: self._wait_status(label, self.RUNNING) else: raise CuckooMachineError(f"Error occurred while starting: {label} (STATUS={status})")
def stop(self, label, force=True): """Stops a virtual machine. Kill them all. @param label: virtual machine name. @param force: poweroff instead of ACPI shutdown @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: if force: self.vms[label].destroy() # Machete's way! else: # Destroy the vm if it takes more than 30 seconds to shutdown # The shutdown() api is blocking, that's why we need a thread # waiting for the timeout timer = threading.Timer(30, self.stop, {label}) timer.start() self.vms[label].shutdown() timer.cancel() 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 _check_snapshot(self, host, snapshot): """Checks snapshot existance. @param host: file path @param snapshot: snapshot name @raise CuckooMachineError: if snapshot not found """ try: p = subprocess.Popen( [self.options.vmware.path, "listSnapshots", host], stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, error = p.communicate() if output: if snapshot in output: return True else: return False else: raise CuckooMachineError("Unable to get snapshot list for %s. " "No output from " "`vmrun listSnapshots`" % host) except OSError as e: raise CuckooMachineError("Unable to get snapshot list for %s. " "Reason: %s" % (host, e))
def stop(self, label=None): """Stop a virtual machine. @param label: vm uuid """ ref = self._get_vm_ref(label) vm = self._get_vm_record(ref) if self._is_halted(vm): log.warning("Trying to stop an already stopped machine: %s", label) else: try: self.session.xenapi.VM.hard_shutdown(ref) except XenAPI.Failure as e: raise CuckooMachineError("Error shutting down virtual machine:" " %s: %s" % (label, e.details[0]))
def start(self, vmx_path): """Start a virtual machine. @param vmx_path: path to vmx file. @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 ], universal_newlines=True, 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 rollback(self, label, vm, node): """Roll back a VM's status to a statically configured or the most recent snapshot. @param label: VM label for lookup in Proxmox and additional parameter retrieval. @raise CuckooMachineError: if snapshot cannot be found, reverting the machine to the snapshot cannot be triggered or times out or fails for another reason.""" snapshot = self.find_snapshot(label, vm) if not snapshot: raise CuckooMachineError("No snapshot found - check config") try: log.debug("%s: Reverting to snapshot %s", label, snapshot) taskid = vm.snapshot(snapshot).rollback.post() except ResourceException as e: raise CuckooMachineError(f"Couldn't trigger rollback to snapshot {snapshot}: {e}") task = self.wait_for_task(taskid, label, vm, node) if not task: raise CuckooMachineError(f"Timeout expired while rolling back to snapshot {snapshot}") if task["exitstatus"] != "OK": raise CuckooMachineError(f"Rollback to snapshot {snapshot} failed: {task['exitstatus']}")
def dump_memory(self, vmx_path, path): """Take a memory dump of the machine.""" if not os.path.exists(vmx_path): raise CuckooMachineError( f"Can't find .vmx file {vmx_path}. Ensure to configure a fully qualified path in vmware.conf (key = vmx_path)" ) try: subprocess.call( [self.options.vmware.path, "snapshot", vmx_path, "memdump"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) except OSError as e: raise CuckooMachineError(f"vmrun failed to take a memory dump of the machine with label {vmx_path}: {e}") vmwarepath, _ = os.path.split(vmx_path) latestvmem = max(glob.iglob(os.path.join(vmwarepath, "*.vmem")), key=os.path.getctime) # We need to move the snapshot to the current analysis directory as # vmware doesn't support an option for the destination path :-/ shutil.move(latestvmem, path) # Old snapshot can be deleted, as it isn't needed any longer. try: subprocess.call( [self.options.vmware.path, "deleteSnapshot", vmx_path, "memdump"], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) except OSError as e: raise CuckooMachineError(f"vmrun failed to delete the temporary snapshot in {vmx_path}: {e}") log.info("Successfully generated memory dump for virtual machine with label %s", vmx_path)
def _initialize_check(self): """Runs all checks when a machine manager is initialized. @raise CuckooMachineError: if libvirt version is not supported. """ # Version checks. if not self._version_check(): raise CuckooMachineError("Libvirt version is not supported, " "please get an updated version") # Preload VMs self.vms = self._fetch_machines() # Base checks. Also attempts to shutdown any machines which are # currently still active. super(LibVirtMachinery, self)._initialize_check()
def stop(self, label): """Stops a virtual machine. @param label: virtual machine name. @raise CuckooMachineError: if unable to stop. """ log.debug("Stopping vm %s" % label) if self._status(label) in [self.POWEROFF, self.ABORTED]: raise CuckooMachineError("Trying to stop an already stopped " "vm %s" % label) try: proc = subprocess.Popen( [self.options.virtualbox.path, "controlvm", label, "poweroff"], 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 < int(self.options_globals.timeouts.vm_state): time.sleep(1) stop_me += 1 else: log.debug("Stopping vm %s timeouted. Killing" % label) proc.terminate() if proc.returncode != 0 and \ stop_me < int(self.options_globals.timeouts.vm_state): log.debug("VBoxManage exited with error " "powering off the machine") 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 _lookup(self, label): """Search for a virtual machine. @param conn: libvirt connection handle. @param label: virtual machine name. @raise CuckooMachineError: if virtual machine is not found. """ conn = self._connect() try: vm = conn.lookupByName(label) except libvirt.libvirtError: raise CuckooMachineError("Cannot find machine " "{0}".format(label)) finally: self._disconnect(conn) return vm
def dump_memory(self, label, path): """Takes a memory dump. @param path: path to where to store the memory dump. """ log.debug("Dumping memory for machine %s", label) conn = self._connect() try: self.vms[label].coreDump(path, flags=libvirt.VIR_DUMP_MEMORY_ONLY) except libvirt.libvirtError as e: raise CuckooMachineError( "Error dumping memory virtual machine {0}: {1}".format( label, e)) finally: self._disconnect(conn)
def isTaskigDone(self, hostID): """This function checks if there are any running tasks for host ID in fog @param hostID: ID of the host to look for tasks @return: Returns true if there is an active task and false if there are none """ try: searchURL = "http://" + self.options.fog.hostname + "/fog/task/active" r = requests.get(searchURL, headers=headers) tasks = r.json()['tasks'] flag = True for task in tasks: if (task['host']['id']) == hostID: flag = False return flag except: raise CuckooMachineError("Error while checking for fog task state for hostID " + str(hostID) + ": " + sys.exc_info()[0])
def dump_memory(self, label, path): """Takes a memory dump. @param path: path to where to store the memory dump. """ try: subprocess.call( [self.options.virtualbox.path, "debugvm", label, "dumpvmcore", "--filename", path], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, ) log.info("Successfully generated memory dump for virtual machine with label %s to path %s", label, path) except OSError as e: raise CuckooMachineError(f"VBoxManage failed to take a memory dump of the machine with label {label}: {e}")
def stop(self, label): """Stops a virtual machine. Kill them all. @param label: virtual machine name. @raise CuckooMachineError: if unable to stop virtual machine. """ conn = self._connect() vm = self._lookup(conn, label) # Force virtual machine shutdown. try: vm.destroy() # Machete's way! except libvirt.libvirtError: raise CuckooMachineError("Error stopping virtual machine %s" % label) finally: self._disconnect(conn)
def start(self, label): """Start a virtual machine. @param label: virtual machine name. @raise CuckooMachineError: if unable to start. """ try: subprocess.Popen([ self.options.virtualbox.path, "startvm", label, "--type", self.options.virtualbox.mode ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError as e: raise CuckooMachineError( "VBoxManage failed starting the machine in %s mode: %s" % (mode.upper(), e))
def fog_init(self): """Initiate by indexing FOG regarding all available machines.""" self.fog_machines = {} if not HAVE_FOG or self.options.fog.hostname == "none": return # TODO Handle exceptions such as not being able to connect. r = self.fog_query("node=task&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:]: host_info = row.find_all("td")[0] host_info_text = host_info.getText(",") aux = host_info_text.split(",") macaddr = aux[1] hostname = aux[0] host_download = row.find_all("td")[2] host_links = [] for l in host_download.children: if type(l) == bs4.element.Tag: host_links.append(l.attrs["href"][1:]) next(host_download.children) self.fog_machines[hostname] = ( macaddr, host_links[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 start(self, label): """Start a physical machine. @param label: physical machine name. @raise CuckooMachineError: if unable to start. """ # Check to ensure a given machine is running log.debug("Checking if machine %r is running.", label) status = self._status(label) if status == self.RUNNING: log.debug("Machine already running: %s.", label) elif status == self.STOPPED: self._wait_status(label, self.RUNNING) else: raise CuckooMachineError("Error occurred while starting: " "%s (STATUS=%s)" % (label, status))
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) status = None try: proc = subprocess.Popen( [ self.options.virtualbox.path, "showvminfo", label, "--machinereadable" ], universal_newlines=True, 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) status = self.ERROR except OSError as e: log.warning("VBoxManage failed to check status for machine %s: %s", label, e) status = self.ERROR if not status: for line in output.split("\n"): state = re.match(r"VMState=\"(\w+)\"", line, re.M | re.I) if state: status = state.group(1) log.debug("Machine %s status %s", label, status) status = status.lower() # Report back status. if status: self.set_status(label, status) return status else: raise CuckooMachineError(f"Unable to get status for {label}")
def dump_memory(self, label, path): """Takes a memory dump. @param path: path to where to store the memory dump. """ log.debug("Dumping memory for machine %s", label) conn = self._connect() try: # Resolve permission issue as libvirt creates the file as # root/root in mode 0600, preventing us from reading it. This # supposedly still doesn't allow us to remove it, though.. open(path, "wb").close() self.vms[label].coreDump(path, flags=libvirt.VIR_DUMP_MEMORY_ONLY) except libvirt.libvirtError as e: raise CuckooMachineError("Error dumping memory virtual machine " "{0}: {1}".format(label, e)) finally: self._disconnect(conn)
def dump_memory(self, label, path): """Takes a memory dump. @param path: path to where to store the memory dump. """ try: subprocess.call([ self.options.virtualbox.path, "debugvm", label, "dumpguestcore", "--filename", path ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) log.info( "Successfully generated memory dump for virtual machine with label %s to path %s" % (label, path)) except OSError as e: raise CuckooMachineError( "VBoxManage failed to take a memory dump of the machine with label %s: %s" % (label, e))
def isTaskigDone(self, hostID): """This function checks if there are any running tasks for host ID in fog @param hostID: ID of the host to look for tasks @return: Returns true if there is an active task and false if there are none """ try: searchURL = f"http://{self.options.fog.hostname}/fog/task/active" r = requests.get(searchURL, headers=headers) tasks = r.json()["tasks"] flag = True for task in tasks: if (task["host"]["id"]) == hostID: flag = False return flag except Exception: raise CuckooMachineError( f"Error while checking for fog task state for hostID {hostID}: {sys.exc_info()[0]}" )
def dump_memory(self, label, path): """Takes a memory dump. @param path: path to where to store the memory dump. """ log.debug("Dumping memory for machine %s", label) conn = self._connect() try: # create the memory dump file ourselves first so it doesn't end up root/root 0600 # it'll still be owned by root, so we can't delete it, but at least we can read it fd = open(path, "w") fd.close() self.vms[label].coreDump(path, flags=libvirt.VIR_DUMP_MEMORY_ONLY) except libvirt.libvirtError as e: raise CuckooMachineError("Error dumping memory virtual machine " "{0}: {1}".format(label, e)) finally: self._disconnect(conn)
def fog_init(self): """Initiate by indexing FOG regarding all available machines.""" self.fog_machines = {} if not HAVE_FOG or self.options.fog.hostname == "none": return # TODO Handle exceptions such as not being able to connect. r = self.fog_query("node=task&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.") # Pull out the FOG version from the header and raise error if it is not 1.3.4 v = b.find("div", {"id": "version"}) runningVer = re.match(r"Running Version\s+(([0-9]+\.)+[0-9]+)", v.text).group(1) if runningVer != "1.3.4": # This may be better suited to go in lib.cuckoo.common.constants raise CuckooCriticalError( "The current version of FOG was detected as %s. " "The only supported version is 1.3.4." % runningVer) # 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:]: hostinfo, imagename, actions = row.find_all("td") self.fog_machines[hostinfo.find("a").text] = ( hostinfo.find("small").text, actions.find(title="Deploy").parent.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 _status(self, label): """Gets current status of a vm. @param label: virtual machine name. @return: status string. """ # For physical machines, the agent can either be contacted or not. # However, there is some information to be garnered from potential # exceptions. log.debug("Getting status for machine: %s." % label) machine = self._get_machine(label) guest = GuestManager(machine.id, machine.ip, machine.platform) if not guest: raise CuckooMachineError('Unable to get status for machine: %s.' % label) else: try: status = guest.server.get_status() except xmlrpclib.Fault as e: # Contacted Agent, but it threw an error log.debug("Agent error: %s (%s) (Error: %s)." % (machine.id, machine.ip, e)) return self.ERROR except socket.error as e: # Could not contact agent log.debug("Agent unresponsive: %s (%s) (Error: %s)." % (machine.id, machine.ip, e)) return self.STOPPED except Exception as e: # TODO: Handle this better log.debug("Received unknown exception: %s." % e) return self.ERROR # If the agent responded successfully, the machine is running if status: return self.RUNNING return self.ERROR
def _get_snapshot(self, label): """Get current snapshot for virtual machine @param label: virtual machine name @return None or current snapshot @raise CuckooMachineError: if cannot find current snapshot or when there are too many snapshots available """ # Checks for current snapshots. conn = self._connect() try: vm = self.vms[label] snap = vm.hasCurrentSnapshot(flags=0) except libvirt.libvirtError: self._disconnect(conn) raise CuckooMachineError("Unable to get current snapshot for " "virtual machine {0}".format(label)) finally: self._disconnect(conn) if snap: return vm.snapshotCurrent(flags=0) # If no current snapshot, get the last one. conn = self._connect() try: snaps = vm[label].snapshotListNames(flags=0) def get_create(sn): xml_desc = sn.getXMLDesc(flags=0) return ET.fromstring(xml_desc).findtext("./creationTime") return max( get_create(vm.snapshotLookupByName(name, flags=0)) for name in snaps) except libvirt.libvirtError: return None except ValueError: return None finally: self._disconnect(conn)
def _initialize_check(self): """Check for configuration file and vmware setup. @raise CuckooMachineError: if configuration is missing or wrong. """ if not self.options.vmwareserver.path: raise CuckooMachineError("VMware vmrun path missing, " "please add it to vmwareserver.conf") # if not os.path.exists(self.options.vmwareserver.path): # raise CuckooMachineError("VMware vmrun not found in " # "specified path %s" % # self.options.vmwareserver.path) # Consistency checks. # for machine in self.machines(): # vmx_path = machine.label # snapshot = self._snapshot_from_vmx(vmx_path) # self._check_vmx(vmx_path) self._check_snapshot(vmx_path, snapshot) # Base checks. super(VMwareServer, self)._initialize_check()