def test_popen(): """Ensures that Popen is working properly.""" with mock.patch("subprocess.Popen") as p: p.return_value = None Popen(["foo", "bar"]) p.assert_called_once_with(["foo", "bar"]) with mock.patch("subprocess.Popen") as p: p.return_value = None Popen( ["foo", "bar"], close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) if is_windows(): p.assert_called_once_with( ["foo", "bar"], stdout=subprocess.PIPE, stderr=subprocess.PIPE ) else: p.assert_called_once_with( ["foo", "bar"], close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) # Test that the method actually works. p = Popen("echo 123", stdout=subprocess.PIPE, shell=True) out, err = p.communicate() assert out.strip() == "123" and not err # The following would normally throw an exception on Windows. p = Popen("echo 1234", close_fds=True, stdout=subprocess.PIPE, shell=True) out, err = p.communicate() assert out.strip() == "1234" and not err
def restore(self, label, machine): """Restore a VM to its snapshot.""" args = [self.options.virtualbox.path, "snapshot", label] if machine.snapshot: log.debug("Restoring virtual machine %s to %s", label, machine.snapshot) args.extend(["restore", machine.snapshot]) else: log.debug("Restoring virtual machine %s to its current snapshot", label) args.append("restorecurrent") try: p = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) _, err = p.communicate() if p.returncode: raise OSError("error code %d: %s" % (p.returncode, err)) except OSError as e: raise CuckooMachineSnapshotError( "VBoxManage failed trying to restore the snapshot of " "machine '%s' (this most likely means there is no snapshot, " "please refer to our documentation for more information on " "how to setup a snapshot for your VM): %s" % (label, e))
def restore(self, label, machine): """Restore a VM to its snapshot.""" args = [ self.options.virtualbox.path, "snapshot", label ] if machine.snapshot: log.debug( "Restoring virtual machine %s to %s", label, machine.snapshot ) args.extend(["restore", machine.snapshot]) else: log.debug( "Restoring virtual machine %s to its current snapshot", label ) args.append("restorecurrent") try: p = Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) _, err = p.communicate() if p.returncode: raise OSError("error code %d: %s" % (p.returncode, err)) except OSError as e: raise CuckooMachineSnapshotError( "VBoxManage failed trying to restore the snapshot of " "machine '%s' (this most likely means there is no snapshot, " "please refer to our documentation for more information on " "how to setup a snapshot for your VM): %s" % (label, e) )
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 _set_flag(self, label, key, val): args = [ self.options.virtualbox.path, "modifyvm", label, "--%s" % key, val ] proc = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) _, _ = proc.communicate() return proc
def _set_flag(self, label, key, val): args = [ self.options.virtualbox.path, "modifyvm", label, "--%s" % key, val ] proc = Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) _, _ = proc.communicate() return proc
def dump_memory(self, label, path): """Takes 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 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 vminfo(self, label, field): """Returns False if invoking vboxmanage fails. Otherwise the VM information value, if any.""" try: args = [ self.options.virtualbox.path, "showvminfo", label, "--machinereadable" ] proc = Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) output, err = proc.communicate() if proc.returncode != 0: if "VBOX_E_OBJECT_NOT_FOUND" in err: raise CuckooMissingMachineError( "The virtual machine '%s' doesn't exist! Please " "create one or more Cuckoo analysis VMs and properly " "fill out the Cuckoo configuration!" % label ) # 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 ) return False except OSError as e: log.warning( "VBoxManage failed to check status for machine %s: %s", label, e ) return False for line in output.split("\n"): if not line.startswith("%s=" % field): continue if line.count('"') == 2: return line.split('"')[1].lower() else: return line.split("=", 1)[1]
def vminfo(self, label, field): """Return False if invoking vboxmanage fails. Otherwise return the VM information value, if any.""" try: args = [ self.options.virtualbox.path, "showvminfo", label, "--machinereadable" ] proc = Popen( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) output, err = proc.communicate() if proc.returncode != 0: if "VBOX_E_OBJECT_NOT_FOUND" in err: raise CuckooMissingMachineError( "The virtual machine '%s' doesn't exist! Please " "create one or more Cuckoo analysis VMs and properly " "fill out the Cuckoo configuration!" % label ) # 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 ) return False except OSError as e: log.warning( "VBoxManage failed to check status for machine %s: %s", label, e ) return False for line in output.split("\n"): if not line.startswith("%s=" % field): continue if line.count('"') == 2: return line.split('"')[1].lower() else: return line.split("=", 1)[1]
def version(): """Get the version for the installed Virtualbox""" try: proc = Popen( [config("virtualbox:virtualbox:path"), "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) output, err = proc.communicate() except OSError: return None output = output.strip() version = "" for c in output: if not c.isdigit() and c != ".": break version += c # A 3 digit version number is expected. If it has none or fewer, return # None because we are unsure what we have. if len(version.split(".", 2)) < 3: return None return version
class Sniffer(Auxiliary): def __init__(self): Auxiliary.__init__(self) self.proc = None def start(self): if not self.machine.interface: log.error("Network interface not defined, network capture aborted") return False # Handle special pcap dumping options. if "nictrace" in self.machine.options: return True tcpdump = self.options["tcpdump"] bpf = self.options["bpf"] or "" file_path = cwd("storage", "analyses", "%s" % self.task.id, "dump.pcap") if not os.path.exists(tcpdump): log.error( "Tcpdump does not exist at path \"%s\", network " "capture aborted", tcpdump) return False # TODO: this isn't working. need to fix. # mode = os.stat(tcpdump)[stat.ST_MODE] # if (mode & stat.S_ISUID) == 0: # log.error("Tcpdump is not accessible from this user, " # "network capture aborted") # return pargs = [ tcpdump, "-U", "-q", "-s", "0", "-n", "-i", self.machine.interface, ] # Trying to save pcap with the same user which cuckoo is running. user = getuser() if user: pargs.extend(["-Z", user]) pargs.extend(["-w", file_path]) pargs.extend(["host", self.machine.ip]) if self.task.options.get("sniffer.debug") != "1": # Do not capture Agent traffic. pargs.extend([ "and", "not", "(", "dst", "host", self.machine.ip, "and", "dst", "port", "%s" % CUCKOO_GUEST_PORT, ")", "and", "not", "(", "src", "host", self.machine.ip, "and", "src", "port", "%s" % CUCKOO_GUEST_PORT, ")", ]) # Do not capture ResultServer traffic. pargs.extend([ "and", "not", "(", "dst", "host", self.machine.resultserver_ip, "and", "dst", "port", "%s" % self.machine.resultserver_port, ")", "and", "not", "(", "src", "host", self.machine.resultserver_ip, "and", "src", "port", "%s" % self.machine.resultserver_port, ")", ]) if bpf: pargs.extend(["and", "(", bpf, ")"]) try: self.proc = Popen(pargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) except (OSError, ValueError): log.exception( "Failed to start sniffer (interface=%s, host=%s, pcap=%s)", self.machine.interface, self.machine.ip, file_path, ) return False log.info( "Started sniffer with PID %d (interface=%s, host=%s, pcap=%s)", self.proc.pid, self.machine.interface, self.machine.ip, file_path, ) return True def _check_output(self, out, err): if out: raise CuckooOperationalError( "Potential error while running tcpdump, did not expect " "standard output, got: %r." % out) err_whitelist_start = ("tcpdump: listening on ", ) err_whitelist_ends = ( "packets captured", "packets received by filter", "packets dropped by kernel", "dropped privs to root", ) for line in err.split("\n"): if not line or line.startswith(err_whitelist_start): continue if line.endswith(err_whitelist_ends): continue raise CuckooOperationalError( "Potential error while running tcpdump, did not expect " "the following standard error output: %r." % line) def stop(self): """Stop sniffing. @return: operation status. """ # The tcpdump process was never started in the first place. if not self.proc: return # The tcpdump process has already quit, generally speaking this # indicates an error such as "permission denied". if self.proc.poll(): out, err = self.proc.communicate() raise CuckooOperationalError( "Error running tcpdump to sniff the network traffic during " "the analysis; stdout = %r and stderr = %r. Did you enable " "the extra capabilities to allow running tcpdump as non-root " "user and disable AppArmor properly (the latter only applies " "to Ubuntu-based distributions with AppArmor)?" % (out, err)) try: self.proc.terminate() except: try: if not self.proc.poll(): log.debug("Killing sniffer") self.proc.kill() except OSError as e: log.debug("Error killing sniffer: %s. Continue", e) except Exception as e: log.exception("Unable to stop the sniffer with pid %d: %s", self.proc.pid, e) # Ensure expected output was received from tcpdump. out, err = self.proc.communicate() self._check_output(out, err)
class Sniffer(Auxiliary): def __init__(self): Auxiliary.__init__(self) self.proc = None def start(self): if not self.machine.interface: log.error("Network interface not defined, network capture aborted") return False # Handle special pcap dumping options. if "nictrace" in self.machine.options: return True tcpdump = self.options["tcpdump"] bpf = self.options["bpf"] or "" file_path = cwd("storage", "analyses", "%s" % self.task.id, "dump.pcap") if not os.path.exists(tcpdump): log.error("Tcpdump does not exist at path \"%s\", network " "capture aborted", tcpdump) return False # TODO: this isn't working. need to fix. # mode = os.stat(tcpdump)[stat.ST_MODE] # if (mode & stat.S_ISUID) == 0: # log.error("Tcpdump is not accessible from this user, " # "network capture aborted") # return pargs = [ tcpdump, "-U", "-q", "-s", "0", "-n", "-i", self.machine.interface, ] # Trying to save pcap with the same user which cuckoo is running. user = getuser() if user: pargs.extend(["-Z", user]) pargs.extend(["-w", file_path]) pargs.extend(["host", self.machine.ip]) if self.task.options.get("sniffer.debug") != "1": # Do not capture Agent traffic. pargs.extend([ "and", "not", "(", "dst", "host", self.machine.ip, "and", "dst", "port", "%s" % CUCKOO_GUEST_PORT, ")", "and", "not", "(", "src", "host", self.machine.ip, "and", "src", "port", "%s" % CUCKOO_GUEST_PORT, ")", ]) # Do not capture ResultServer traffic. pargs.extend([ "and", "not", "(", "dst", "host", self.machine.resultserver_ip, "and", "dst", "port", "%s" % self.machine.resultserver_port, ")", "and", "not", "(", "src", "host", self.machine.resultserver_ip, "and", "src", "port", "%s" % self.machine.resultserver_port, ")", ]) if bpf: pargs.extend(["and", "(", bpf, ")"]) try: self.proc = Popen( pargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) except (OSError, ValueError): log.exception( "Failed to start sniffer (interface=%s, host=%s, pcap=%s)", self.machine.interface, self.machine.ip, file_path, ) return False log.info( "Started sniffer with PID %d (interface=%s, host=%s, pcap=%s)", self.proc.pid, self.machine.interface, self.machine.ip, file_path, ) return True def _check_output(self, out, err): if out: raise CuckooOperationalError( "Potential error while running tcpdump, did not expect " "standard output, got: %r." % out ) err_whitelist_start = ( "tcpdump: listening on ", ) err_whitelist_ends = ( "packets captured", "packets received by filter", "packets dropped by kernel", "dropped privs to root", ) for line in err.split("\n"): if not line or line.startswith(err_whitelist_start): continue if line.endswith(err_whitelist_ends): continue raise CuckooOperationalError( "Potential error while running tcpdump, did not expect " "the following standard error output: %r." % line ) def stop(self): """Stop sniffing. @return: operation status. """ # The tcpdump process was never started in the first place. if not self.proc: return # The tcpdump process has already quit, generally speaking this # indicates an error such as "permission denied". if self.proc.poll(): out, err = self.proc.communicate() raise CuckooOperationalError( "Error running tcpdump to sniff the network traffic during " "the analysis; stdout = %r and stderr = %r. Did you enable " "the extra capabilities to allow running tcpdump as non-root " "user and disable AppArmor properly (the latter only applies " "to Ubuntu-based distributions with AppArmor)?" % (out, err) ) try: self.proc.terminate() except: try: if not self.proc.poll(): log.debug("Killing sniffer") self.proc.kill() except OSError as e: log.debug("Error killing sniffer: %s. Continue", e) except Exception as e: log.exception("Unable to stop the sniffer with pid %d: %s", self.proc.pid, e) # Ensure expected output was received from tcpdump. out, err = self.proc.communicate() self._check_output(out, err)