def unroute_network(self): """Disable any enabled network routing.""" cfg = self.routing_cfg if self.interface: rooter( "forward_disable", self.machine.interface, self.interface, self.machine.ip ) if self.rt_table: rooter( "srcroute_disable", self.rt_table, self.machine.ip ) if self.route != "none": rooter( "drop_disable", self.machine.ip, config("cuckoo:resultserver:ip"), str(config("cuckoo:resultserver:port")) ) if self.route == "inetsim": rooter("inetsim_disable", self.machine.ip, cfg.inetsim.server, str(cfg.resultserver.port)) if self.route == "tor": rooter( "tor_disable", self.machine.ip, str(config("cuckoo:resultserver:ip")), str(config("routing:tor:dnsport")), str(config("routing:tor:proxyport")) )
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)
def test_rooter_client(p, q, r): set_cwd(tempfile.mkdtemp()) cuckoo_create() s = p.socket.return_value s.recv.return_value = json.dumps({ "exception": None, "output": "thisisoutput", }) assert rooter( "command", "arg1", "arg2", arg3="foo", arg4="bar" ) == "thisisoutput" s.bind.assert_called_once() s.connect.assert_called_once_with("/tmp/cuckoo-rooter") s.send.assert_called_once_with(json.dumps({ "command": "command", "args": ( "arg1", "arg2", ), "kwargs": { "arg3": "foo", "arg4": "bar", } })) q.acquire.assert_called_once() q.release.assert_called_once()
def unroute_network(self): """Disable any enabled network routing.""" if self.interface: rooter( "forward_disable", self.machine.interface, self.interface, self.machine.ip ) if self.rt_table: rooter( "srcroute_disable", self.rt_table, self.machine.ip ) if self.route == "drop" or self.route == "internet": rooter( "drop_disable", self.machine.ip, config("cuckoo:resultserver:ip"), str(config("cuckoo:resultserver:port")) ) if self.route == "inetsim": machinery = config("cuckoo:cuckoo:machinery") rooter( "inetsim_disable", self.machine.ip, config("routing:inetsim:server"), config("%s:%s:interface" % (machinery, machinery)), str(config("cuckoo:resultserver:port")), config("routing:inetsim:ports") or "" ) if self.route == "tor": rooter( "tor_disable", self.machine.ip, str(config("cuckoo:resultserver:ip")), str(config("routing:tor:dnsport")), str(config("routing:tor:proxyport")) )
def init_rooter(): """If required, check if the rooter is running and if we can connect to it. The default configuration doesn't require the rooter to be ran.""" required = ( config("routing:routing:route") != "none" or config("routing:routing:internet") != "none" or config("routing:routing:drop") or config("routing:inetsim:enabled") or config("routing:tor:enabled") or config("routing:vpn:enabled") ) if not required: return s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) try: s.connect(config("cuckoo:cuckoo:rooter")) except socket.error as e: if e.strerror == "No such file or directory": raise CuckooStartupError( "The rooter is required but it is either not running or it " "has been configured to a different Unix socket path. Please " "refer to the documentation on working with the rooter." ) if e.strerror == "Connection refused": raise CuckooStartupError( "The rooter is required but we can't connect to it as the " "rooter is not actually running. Please refer to the " "documentation on working with the rooter." ) if e.strerror == "Permission denied": raise CuckooStartupError( "The rooter is required but we can't connect to it due to " "incorrect permissions. Did you assign it the correct group? " "Please refer to the documentation on working with the " "rooter." ) raise CuckooStartupError("Unknown rooter error: %s" % e) # Do not forward any packets unless we have explicitly stated so. rooter("forward_drop") # Enable stateful connection tracking (but only once). rooter("state_disable") rooter("state_enable")
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 )
default="/tmp/cuckoo-vpncheck", help="Unix socket path of this client") parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose logging") args = parser.parse_args() if os.path.exists(args.client): os.unlink(args.client) init_rooter() init_routing() error = 0 for vpn, status in rooter("vpn_status").items(): if vpn not in vpns: print "Not a configured VPN", vpn continue if not rooter("nic_available", vpns[vpn].interface): print >> sys.stderr, "VPN is no longer available", vpn error = 1 continue ipaddr = get_ip_address(vpns[vpn].interface) rooter("forward_enable", vpns[vpn].interface, vpns[vpn].interface, ipaddr) rooter("srcroute_enable", vpns[vpn].rt_table, ipaddr)
def vpn_status(): status = rooter("vpn_status") if status is None: return json_error(500, "Rooter not available") return jsonify({"vpns": status})
def route_network(self): """Enable network routing if desired.""" # Determine the desired routing strategy (none, internet, VPN). self.route = self.task.options.get("route", config("routing:routing:route")) if self.route == "none" or self.route == "drop": self.interface = None self.rt_table = None elif self.route == "inetsim": pass elif self.route == "tor": pass elif self.route == "internet": if config("routing:routing:internet") == "none": log.warning( "Internet network routing has been specified, but not " "configured, ignoring routing for this analysis", extra={ "action": "network.route", "status": "error", "route": self.route, }) self.route = "none" self.task.options["route"] = "none" self.interface = None self.rt_table = None else: self.interface = config("routing:routing:internet") self.rt_table = config("routing:routing:rt_table") elif self.route in config("routing:vpn:vpns"): self.interface = config("routing:%s:interface" % self.route) self.rt_table = config("routing:%s:rt_table" % self.route) else: log.warning( "Unknown network routing destination specified, ignoring " "routing for this analysis: %r", self.route, extra={ "action": "network.route", "status": "error", "route": self.route, }) self.route = "none" self.task.options["route"] = "none" self.interface = None self.rt_table = None # Check if the network interface is still available. If a VPN dies for # some reason, its tunX interface will no longer be available. if self.interface and not rooter("nic_available", self.interface): log.error( "The network interface '%s' configured for this analysis is " "not available at the moment, switching to route=none mode.", self.interface, extra={ "action": "network.route", "status": "error", "route": self.route, }) self.route = "none" self.task.options["route"] = "none" self.interface = None self.rt_table = None # For now this doesn't work yet in combination with tor routing. if self.route == "drop" or self.route == "internet": rooter("drop_enable", self.machine.ip, config("cuckoo:resultserver:ip"), str(config("cuckoo:resultserver:port"))) if self.route == "inetsim": machinery = config("cuckoo:cuckoo:machinery") rooter("inetsim_enable", self.machine.ip, config("routing:inetsim:server"), config("%s:%s:interface" % (machinery, machinery)), str(config("cuckoo:resultserver:port")), config("routing:inetsim:ports") or "") if self.route == "tor": rooter("tor_enable", self.machine.ip, str(config("cuckoo:resultserver:ip")), str(config("routing:tor:dnsport")), str(config("routing:tor:proxyport"))) if self.interface: rooter("forward_enable", self.machine.interface, self.interface, self.machine.ip) if self.rt_table: rooter("srcroute_enable", self.rt_table, self.machine.ip) # Propagate the taken route to the database. self.db.set_route(self.task.id, self.route)
def initialize(self): """Initialize the machine manager.""" global machinery, machine_lock machinery_name = self.cfg.cuckoo.machinery max_vmstartup_count = self.cfg.cuckoo.max_vmstartup_count if max_vmstartup_count: machine_lock = threading.Semaphore(max_vmstartup_count) else: machine_lock = threading.Lock() log.info("Using \"%s\" as machine manager", machinery_name, extra={ "action": "init.machinery", "status": "success", "machinery": machinery_name, }) # Initialize the machine manager. machinery = cuckoo.machinery.plugins[machinery_name]() # Provide a dictionary with the configuration options to the # machine manager instance. machinery.set_options(Config(machinery_name)) # Initialize the machine manager. try: machinery.initialize(machinery_name) except CuckooMachineError as e: raise CuckooCriticalError("Error initializing machines: %s" % e) # At this point all the available machines should have been identified # and added to the list. If none were found, Cuckoo aborts the # execution. TODO In the future we'll probably want get rid of this. if not machinery.machines(): raise CuckooCriticalError("No machines available.") log.info("Loaded %s machine/s", len(machinery.machines()), extra={ "action": "init.machines", "status": "success", "count": len(machinery.machines()), }) if len(machinery.machines()) > 1 and self.db.engine.name == "sqlite": log.warning("As you've configured Cuckoo to execute parallel " "analyses, we recommend you to switch to a MySQL or " "a PostgreSQL database as SQLite might cause some " "issues.") if len(machinery.machines()) > 4 and self.cfg.cuckoo.process_results: log.warning("When running many virtual machines it is recommended " "to process the results in separate 'cuckoo process' " "instances to increase throughput and stability. " "Please read the documentation about the " "`Processing Utility`.") # Drop all existing packet forwarding rules for each VM. Just in case # Cuckoo was terminated for some reason and various forwarding rules # have thus not been dropped yet. for machine in machinery.machines(): if not machine.interface: log.info( "Unable to determine the network interface for VM " "with name %s, Cuckoo will not be able to give it " "full internet access or route it through a VPN! " "Please define a default network interface for the " "machinery or define a network interface for each " "VM.", machine.name) continue # Drop forwarding rule to each VPN. if config("routing:vpn:enabled"): for vpn in config("routing:vpn:vpns"): rooter("forward_disable", machine.interface, config("routing:%s:interface" % vpn), machine.ip) # Drop forwarding rule to the internet / dirty line. if config("routing:routing:internet") != "none": rooter("forward_disable", machine.interface, config("routing:routing:internet"), machine.ip)
def initialize(self): """Initialize the machine manager.""" global machinery, machine_lock machinery_name = self.cfg.cuckoo.machinery max_vmstartup_count = self.cfg.cuckoo.max_vmstartup_count if max_vmstartup_count: machine_lock = threading.Semaphore(max_vmstartup_count) else: machine_lock = threading.Lock() log.info("Using \"%s\" as machine manager", machinery_name, extra={ "action": "init.machinery", "status": "success", "machinery": machinery_name, }) # Initialize the machine manager. machinery = cuckoo.machinery.plugins[machinery_name]() # Provide a dictionary with the configuration options to the # machine manager instance. machinery.set_options(Config(machinery_name)) # Initialize the machine manager. try: machinery.initialize(machinery_name) except CuckooMachineError as e: raise CuckooCriticalError("Error initializing machines: %s" % e) # At this point all the available machines should have been identified # and added to the list. If none were found, Cuckoo aborts the # execution. TODO In the future we'll probably want get rid of this. if not machinery.machines(): raise CuckooCriticalError("No machines available.") log.info("Loaded %s machine/s", len(machinery.machines()), extra={ "action": "init.machines", "status": "success", "count": len(machinery.machines()), }) if len(machinery.machines()) > 1 and self.db.engine.name == "sqlite": log.warning("As you've configured Cuckoo to execute parallel " "analyses, we recommend you to switch to a MySQL or " "a PostgreSQL database as SQLite might cause some " "issues.") if len(machinery.machines()) > 4 and self.cfg.cuckoo.process_results: log.warning("When running many virtual machines it is recommended " "to process the results in separate 'cuckoo process' " "instances to increase throughput and stability. " "Please read the documentation about the " "`Processing Utility`.") # Drop all existing packet forwarding rules for each VM. Just in case # Cuckoo was terminated for some reason and various forwarding rules # have thus not been dropped yet. for machine in machinery.machines(): if not machine.interface: log.info("Unable to determine the network interface for VM " "with name %s, Cuckoo will not be able to give it " "full internet access or route it through a VPN! " "Please define a default network interface for the " "machinery or define a network interface for each " "VM.", machine.name) continue # Drop forwarding rule to each VPN. if config("routing:vpn:enabled"): for vpn in config("routing:vpn:vpns"): rooter( "forward_disable", machine.interface, config("routing:%s:interface" % vpn), machine.ip ) # Drop forwarding rule to the internet / dirty line. if config("routing:routing:internet") != "none": rooter( "forward_disable", machine.interface, config("routing:routing:internet"), machine.ip )
def init_routing(): """Initialize and check whether the routing information is correct.""" interfaces = set() # Check if all configured VPNs exist and are up and enable NAT on # each VPN interface. if config("routing:vpn:enabled"): for name in config("routing:vpn:vpns"): entry = config2("routing", name) if not rooter("nic_available", entry.interface): raise CuckooStartupError( "The network interface that has been configured for " "VPN %s is not available." % entry.name ) if not rooter("rt_available", entry.rt_table): raise CuckooStartupError( "The routing table that has been configured for " "VPN %s is not available." % entry.name ) interfaces.add((entry.rt_table, entry.interface)) standard_routes = "none", "drop", "internet", "inetsim", "tor" # Check whether the default VPN exists if specified. if config("routing:routing:route") not in standard_routes: if config("routing:routing:route") not in config("routing:vpn:vpns"): raise CuckooStartupError( "The default routing target (%s) has not been configured in " "routing.conf, is it supposed to be a VPN?" % config("routing:routing:route") ) if not config("routing:vpn:enabled"): raise CuckooStartupError( "The default route configured is a VPN, but VPNs have " "not been enabled in routing.conf." ) # Check whether the dirty line exists if it has been defined. if config("routing:routing:internet") != "none": if not rooter("nic_available", config("routing:routing:internet")): raise CuckooStartupError( "The network interface that has been configured as dirty " "line is not available." ) if not rooter("rt_available", config("routing:routing:rt_table")): raise CuckooStartupError( "The routing table that has been configured for dirty " "line interface is not available." ) interfaces.add(( config("routing:routing:rt_table"), config("routing:routing:internet") )) for rt_table, interface in interfaces: # Disable & enable NAT on this network interface. Disable it just # in case we still had the same rule from a previous run. rooter("disable_nat", interface) rooter("enable_nat", interface) # Populate routing table with entries from main routing table. if config("routing:routing:auto_rt"): rooter("flush_rttable", rt_table) rooter("init_rttable", rt_table, interface)
def route_network(self): """Enable network routing if desired.""" # Determine the desired routing strategy (none, internet, VPN). self.route = self.task.options.get( "route", config("routing:routing:route") ) if self.route == "none" or self.route == "drop": self.interface = None self.rt_table = None elif self.route == "inetsim": pass elif self.route == "tor": pass elif self.route == "internet": if config("routing:routing:internet") == "none": log.warning( "Internet network routing has been specified, but not " "configured, ignoring routing for this analysis", extra={ "action": "network.route", "status": "error", "route": self.route, } ) self.route = "none" self.task.options["route"] = "none" self.interface = None self.rt_table = None else: self.interface = config("routing:routing:internet") self.rt_table = config("routing:routing:rt_table") elif self.route in config("routing:vpn:vpns"): self.interface = config("routing:%s:interface" % self.route) self.rt_table = config("routing:%s:rt_table" % self.route) else: log.warning( "Unknown network routing destination specified, ignoring " "routing for this analysis: %r", self.route, extra={ "action": "network.route", "status": "error", "route": self.route, } ) self.route = "none" self.task.options["route"] = "none" self.interface = None self.rt_table = None # Check if the network interface is still available. If a VPN dies for # some reason, its tunX interface will no longer be available. if self.interface and not rooter("nic_available", self.interface): log.error( "The network interface '%s' configured for this analysis is " "not available at the moment, switching to route=none mode.", self.interface, extra={ "action": "network.route", "status": "error", "route": self.route, } ) self.route = "none" self.task.options["route"] = "none" self.interface = None self.rt_table = None # For now this doesn't work yet in combination with tor routing. if self.route == "drop" or self.route == "internet": rooter( "drop_enable", self.machine.ip, config("cuckoo:resultserver:ip"), str(config("cuckoo:resultserver:port")) ) if self.route == "inetsim": machinery = config("cuckoo:cuckoo:machinery") rooter( "inetsim_enable", self.machine.ip, config("routing:inetsim:server"), config("%s:%s:interface" % (machinery, machinery)), str(config("cuckoo:resultserver:port")), config("routing:inetsim:ports") or "" ) if self.route == "tor": rooter( "tor_enable", self.machine.ip, str(config("cuckoo:resultserver:ip")), str(config("routing:tor:dnsport")), str(config("routing:tor:proxyport")) ) if self.interface: rooter( "forward_enable", self.machine.interface, self.interface, self.machine.ip ) if self.rt_table: rooter( "srcroute_enable", self.rt_table, self.machine.ip ) # Propagate the taken route to the database. self.db.set_route(self.task.id, self.route)
def vpn_status(request): status = rooter("vpn_status") if status is None: return json_fatal_response("Rooter not available") return JsonResponse({"status": True, "vpns": status})
def init_routing(): """Initialize and check whether the routing information is correct.""" interfaces = set() # Check if all configured VPNs exist and are up and enable NAT on # each VPN interface. if config("routing:vpn:enabled"): for name in config("routing:vpn:vpns"): entry = config2("routing", name) if not rooter("nic_available", entry.interface): raise CuckooStartupError( "The network interface that has been configured for " "VPN %s is not available." % entry.name) if not rooter("rt_available", entry.rt_table): raise CuckooStartupError( "The routing table that has been configured for " "VPN %s is not available." % entry.name) interfaces.add((entry.rt_table, entry.interface)) VPNManager.init() standard_routes = "none", "drop", "internet", "inetsim", "tor" # Check whether the default VPN exists if specified. if config("routing:routing:route") not in standard_routes: if config("routing:routing:route") not in config("routing:vpn:vpns"): raise CuckooStartupError( "The default routing target (%s) has not been configured in " "routing.conf, is it supposed to be a VPN?" % config("routing:routing:route")) if not config("routing:vpn:enabled"): raise CuckooStartupError( "The default route configured is a VPN, but VPNs have " "not been enabled in routing.conf.") # Check whether the dirty line exists if it has been defined. if config("routing:routing:internet") != "none": if not rooter("nic_available", config("routing:routing:internet")): raise CuckooStartupError( "The network interface that has been configured as dirty " "line is not available.") if not rooter("rt_available", config("routing:routing:rt_table")): raise CuckooStartupError( "The routing table that has been configured for dirty " "line interface is not available.") interfaces.add((config("routing:routing:rt_table"), config("routing:routing:internet"))) for rt_table, interface in interfaces: # Disable & enable NAT on this network interface. Disable it just # in case we still had the same rule from a previous run. rooter("disable_nat", interface) rooter("enable_nat", interface) # Populate routing table with entries from main routing table. if config("routing:routing:auto_rt"): rooter("flush_rttable", rt_table) rooter("init_rttable", rt_table, interface)