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 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)
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 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 Replay(Auxiliary): def __init__(self): Auxiliary.__init__(self) self.proc = None self.port = None def pcap2mitm(self, pcappath, tlsmaster): """Used to translate a .pcap into a .mitm file.""" mitmpath = tempfile.mktemp(suffix=".mitm") with open(mitmpath, "wb") as f: httpreplay.utils.pcap2mitm(pcappath, f, tlsmaster, True) return mitmpath 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 stop(self): machinery = config("cuckoo:cuckoo:machinery") self.port and rooter( "inetsim_disable", 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) ) 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 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): 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) 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)