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 _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 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 _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 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 _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 stop(self, label): """Stops 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" ] 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: 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 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 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 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 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 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 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
def stop_safe(self, label): """Stop a machine safely by sending a shutdown signal, allowing the operating system to shut down. @param label: machine name.""" log.debug("Stopping vm %s safely", label) status = self._status(label) if status == self.SAVED: return if status == self.POWEROFF or status == self.ABORTED: raise CuckooMachineError( "Trying to stop an already stopped VM: %s" % label) command = [ self.options.virtualbox.path, "controlvm", label, "acpipowerbutton" ] state_timeout = config("cuckoo:timeouts:safe_shutdown") try: proc = Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) except OSError as e: raise CuckooMachineError( "VBoxManage failed powering off the machine: %s" % e) stopcount = 0 while self._status(label) != self.POWEROFF: log.debug("Waiting for vm %s to shut down safely", label) if stopcount > state_timeout: break stopcount += 1 time.sleep(1) if self._status(label) != self.POWEROFF: log.warning( "Safe shutdown of vm %s timed out. Using hard shutdown", label) self.stop()
def start(self): mitmdump = self.options.get("mitmdump", "/usr/local/bin/mitmdump") port_base = int(self.options.get("port_base", 50000)) script = cwd(self.options.get("script", "stuff/mitm.py")) certificate = self.options.get("certificate", "bin/cert.p12") if not os.path.exists(mitmdump): log.error("Mitmdump does not exist at path \"%s\", man in the " "middle interception aborted.", mitmdump) return if not os.path.exists(script): log.error("Mitmdump script file does not exist at path \"%s\", " "man in the middle interception aborted.", script) return cert_path = cwd("analyzer", "windows", certificate) if not os.path.exists(cert_path): log.error("Mitmdump root certificate not found at path \"%s\" " "(real path \"%s\"), man in the middle interception " "aborted.", certificate, cert_path) return PORT_LOCK.acquire() for port in xrange(port_base, port_base + 512): if port not in PORTS: self.port = port break PORTS.append(self.port) PORT_LOCK.release() args = [ mitmdump, "-q", "-s", '"{}" {}'.format( script, self.task.options.get("mitm", "") ).strip(), "-p", "%d" % self.port, "-w", cwd("dump.mitm", analysis=self.task.id), ] self.proc = Popen( args, close_fds=True, stdout=open(cwd("mitm.log", analysis=self.task.id), "wb"), stderr=open(cwd("mitm.err", analysis=self.task.id), "wb") ) if "cert" in self.task.options: log.warning("A root certificate has been provided for this task, " "however, this is overridden by the mitm auxiliary " "module.") self.task.options["cert"] = certificate if "proxy" in self.task.options: log.warning("A proxy has been provided for this task, however, " "this is overridden by the mitm auxiliary module.") # We are using the resultserver IP address as address for the host # where our mitmdump instance is running. TODO Is this correct? self.task.options["proxy"] = ( "%s:%d" % (self.machine.resultserver_ip, port) ) log.info("Started mitm interception with PID %d (ip=%s, port=%d).", self.proc.pid, self.machine.resultserver_ip, self.port)
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
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)
def start(self): # Have to explicitly enable Replay. if not self.task.options.get("replay"): return if self.task.options.get("route"): log.error( "A network route must not be specified when performing a " "Replay analysis." ) return # TODO We have to do version checking on mitmdump. mitmdump = self.options["mitmdump"] port_base = self.options["port_base"] certificate = self.options["certificate"] cert_path = cwd("analyzer", "windows", certificate) if not os.path.exists(cert_path): log.error("Mitmdump root certificate not found at path \"%s\" " "(real path \"%s\"), man in the middle interception " "aborted.", certificate, cert_path) return mitmpath = self.task.options["replay"] if not mitmpath.endswith((".pcap", ".mitm")): log.error( "Invalid filename (should end with .pcap or .mitm): %s. " "Can't proceed with replay analysis.", mitmpath ) return # We support both .mitm and .pcap files. if mitmpath.endswith(".pcap"): tlsmaster = self.task.options.get("replay.tls") mitmpath = self.pcap2mitm(mitmpath, tlsmaster) if not os.path.getsize(mitmpath): log.error( "Empty .mitm file (potentially after conversion from .pcap), " "do you have the mitmproxy version 0.18.2 installed (in the " "same environment as Cuckoo)?" ) log.info("Aborting Replay capabilities.") return PORT_LOCK.acquire() for port in xrange(port_base, port_base + 512): if port not in PORTS: self.port = port break PORTS.append(self.port) PORT_LOCK.release() # TODO Better access to self.machine and its related fields. machinery = config("cuckoo:cuckoo:machinery") rooter( "inetsim_enable", self.machine.ip, config("cuckoo:resultserver:ip"), config("%s:%s:interface" % (machinery, machinery)), str(config("cuckoo:resultserver:port")), "80:%d 443:%d" % (self.port, self.port) ) args = [ mitmdump, "-S", mitmpath, "--set", "server_replay_ignore_content", "--set", "server_replay_ignore_host", # With the port redirection provided by our InetSim support, # server_replay_ignore_port is strictly speaking irrelevant. # "--set", "server_replay_ignore_port", "--server-replay-kill-extra", "--mode", "transparent", "-k", "-q", "-p", "%d" % self.port, ] self.proc = Popen(args, close_fds=True) if "cert" in self.task.options: log.warning("A root certificate has been provided for this task, " "however, this is overridden by the mitm auxiliary " "module.") self.task.options["cert"] = certificate log.info( "Started PCAP replay PID %d (ip=%s, port=%d).", self.proc.pid, self.machine.resultserver_ip, self.port )
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
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 MITM(Auxiliary): def __init__(self): Auxiliary.__init__(self) self.proc = None def start(self): mitmdump = self.options.get("mitmdump", "/usr/local/bin/mitmdump") port_base = int(self.options.get("port_base", 50000)) script = cwd(self.options.get("script", "stuff/mitm.py")) certificate = self.options.get("certificate", "bin/cert.p12") if not os.path.exists(mitmdump): log.error( "Mitmdump does not exist at path \"%s\", man in the " "middle interception aborted.", mitmdump) return if not os.path.exists(script): log.error( "Mitmdump script file does not exist at path \"%s\", " "man in the middle interception aborted.", script) return cert_path = cwd("analyzer", "windows", certificate) if not os.path.exists(cert_path): log.error( "Mitmdump root certificate not found at path \"%s\" " "(real path \"%s\"), man in the middle interception " "aborted.", certificate, cert_path) return PORT_LOCK.acquire() for port in xrange(port_base, port_base + 512): if port not in PORTS: self.port = port break PORTS.append(self.port) PORT_LOCK.release() args = [ mitmdump, "-q", "-s", '"{}" {}'.format(script, self.task.options.get("mitm", "")).strip(), "-p", "%d" % self.port, "-w", cwd("dump.mitm", analysis=self.task.id), ] self.proc = Popen(args, close_fds=True, stdout=open(cwd("mitm.log", analysis=self.task.id), "wb"), stderr=open(cwd("mitm.err", analysis=self.task.id), "wb")) if "cert" in self.task.options: log.warning("A root certificate has been provided for this task, " "however, this is overridden by the mitm auxiliary " "module.") self.task.options["cert"] = certificate if "proxy" in self.task.options: log.warning("A proxy has been provided for this task, however, " "this is overridden by the mitm auxiliary module.") # We are using the resultserver IP address as address for the host # where our mitmdump instance is running. TODO Is this correct? self.task.options["proxy"] = ("%s:%d" % (self.machine.resultserver_ip, port)) log.info("Started mitm interception with PID %d (ip=%s, port=%d).", self.proc.pid, self.machine.resultserver_ip, self.port) def stop(self): if self.proc and not self.proc.poll(): try: self.proc.terminate() PORTS.remove(self.port) except: try: if not self.proc.poll(): log.debug("Killing mitmdump") self.proc.kill() PORTS.remove(self.port) except OSError as e: log.debug("Error killing mitmdump: %s. Continue", e) except Exception as e: log.exception("Unable to stop mitmdump with pid %d: %s", self.proc.pid, e)
class MITM(Auxiliary): def __init__(self): Auxiliary.__init__(self) self.proc = None def start(self): port_base = self.options["port_base"] certificate = self.options["certificate"] mitmdump = self.options["mitmdump"] if not os.path.exists(mitmdump): log.error("Mitmdump does not exist at path \"%s\", man in the " "middle interception aborted.", mitmdump) return script = cwd(self.options["script"]) if not os.path.exists(script): log.error("Mitmdump script file does not exist at path \"%s\", " "man in the middle interception aborted.", script) return cert_path = cwd("analyzer", "windows", certificate) if not os.path.exists(cert_path): log.error("Mitmdump root certificate not found at path \"%s\" " "(real path \"%s\"), man in the middle interception " "aborted.", certificate, cert_path) return PORT_LOCK.acquire() for port in xrange(port_base, port_base + 512): if port not in PORTS: self.port = port break PORTS.append(self.port) PORT_LOCK.release() args = [ mitmdump, "-q", "-s", '"{}" {}'.format( script, self.task.options.get("mitm", "") ).strip(), "-p", "%d" % self.port, "-w", cwd("dump.mitm", analysis=self.task.id), ] self.proc = Popen( args, close_fds=True, stdout=open(cwd("mitm.log", analysis=self.task.id), "wb"), stderr=open(cwd("mitm.err", analysis=self.task.id), "wb") ) if "cert" in self.task.options: log.warning("A root certificate has been provided for this task, " "however, this is overridden by the mitm auxiliary " "module.") self.task.options["cert"] = certificate if "proxy" in self.task.options: log.warning("A proxy has been provided for this task, however, " "this is overridden by the mitm auxiliary module.") # We are using the resultserver IP address as address for the host # where our mitmdump instance is running. TODO Is this correct? self.task.options["proxy"] = ( "%s:%d" % (self.machine.resultserver_ip, port) ) log.info("Started mitm interception with PID %d (ip=%s, port=%d).", self.proc.pid, self.machine.resultserver_ip, self.port) def stop(self): if self.proc and not self.proc.poll(): try: self.proc.terminate() PORTS.remove(self.port) except: try: if not self.proc.poll(): log.debug("Killing mitmdump") self.proc.kill() PORTS.remove(self.port) except OSError as e: log.debug("Error killing mitmdump: %s. Continue", e) except Exception as e: log.exception("Unable to stop mitmdump with pid %d: %s", self.proc.pid, e)
def start(self): mitmdump = self.options.get("mitmdump", "/usr/local/bin/mitmdump") port_base = int(self.options.get("port_base", 50000)) script = cwd(self.options.get("script", "stuff/mitm.py")) certificate = self.options.get("certificate", "bin/cert.p12") outpath = cwd("storage", "analyses", "%d" % self.task.id, "dump.mitm") if not os.path.exists(mitmdump): log.error( "Mitmdump does not exist at path \"%s\", man in the " "middle interception aborted.", mitmdump) return if not os.path.exists(script): log.error( "Mitmdump script file does not exist at path \"%s\", " "man in the middle interception aborted.", script) return cert_path = cwd("analyzer", "windows", certificate) if not os.path.exists(cert_path): log.error( "Mitmdump root certificate not found at path \"%s\" " "(real path \"%s\"), man in the middle interception " "aborted.", certificate, cert_path) return PORT_LOCK.acquire() for port in xrange(port_base, port_base + 512): if port not in PORTS: self.port = port break PORTS.append(self.port) PORT_LOCK.release() args = [ mitmdump, "-q", "-s", "\"%s\" %s" % (script, self.task.options.get("mitm", "")), "-p", "%d" % self.port, "-w", outpath ] mitmlog = cwd("storage", "analyses", "%d" % self.task.id, "mitm.log") mitmerr = cwd("storage", "analyses", "%d" % self.task.id, "mitm.err") self.proc = Popen(args, close_fds=True, stdout=open(mitmlog, "wb"), stderr=open(mitmerr, "wb")) if "cert" in self.task.options: log.warning("A root certificate has been provided for this task, " "however, this is overridden by the mitm auxiliary " "module.") self.task.options["cert"] = certificate if "proxy" in self.task.options: log.warning("A proxy has been provided for this task, however, " "this is overridden by the mitm auxiliary module.") # We are using the resultserver IP address as address for the host # where our mitmdump instance is running. TODO Is this correct? self.task.options["proxy"] = \ "%s:%d" % (self.machine.resultserver_ip, port) log.info("Started mitm interception with PID %d (ip=%s, port=%d).", self.proc.pid, self.machine.resultserver_ip, self.port)
def start(self): port_base = self.options["port_base"] certificate = self.options["certificate"] mitmdump = self.options["mitmdump"] if not os.path.exists(mitmdump): log.error("Mitmdump does not exist at path \"%s\", man in the " "middle interception aborted.", mitmdump) return script = cwd(self.options["script"]) if not os.path.exists(script): log.error("Mitmdump script file does not exist at path \"%s\", " "man in the middle interception aborted.", script) return cert_path = cwd("analyzer", self.machine.platform, certificate) if not os.path.exists(cert_path): log.error("Mitmdump root certificate not found at path \"%s\" " "(real path \"%s\"), man in the middle interception " "aborted.", certificate, cert_path) return PORT_LOCK.acquire() for port in xrange(port_base, port_base + 512): if port not in PORTS: self.port = port break PORTS.append(self.port) PORT_LOCK.release() args = [ mitmdump, "-q", "-s", '"{}" {}'.format( script, self.task.options.get("mitm", "") ).strip(), "-p", "%d" % self.port, "-w", cwd("dump.mitm", analysis=self.task.id), ] self.proc = Popen( args, close_fds=True, stdout=open(cwd("mitm.log", analysis=self.task.id), "wb"), stderr=open(cwd("mitm.err", analysis=self.task.id), "wb") ) if "cert" in self.task.options: log.warning("A root certificate has been provided for this task, " "however, this is overridden by the mitm auxiliary " "module.") self.task.options["cert"] = certificate if "proxy" in self.task.options: log.warning("A proxy has been provided for this task, however, " "this is overridden by the mitm auxiliary module.") # We are using the resultserver IP address as address for the host # where our mitmdump instance is running. TODO Is this correct? self.task.options["proxy"] = ( "%s:%d" % (self.machine.resultserver_ip, port) ) log.info("Started mitm interception with PID %d (ip=%s, port=%d).", self.proc.pid, self.machine.resultserver_ip, self.port)