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 test_init_star_multiple(self): cuckoo_create(cfg={ "virtualbox": { "virtualbox": { "machines": [ "cuckoo2", "cuckoo3", ], }, "cuckoo2": { "ip": "192.168.56.102", }, "cuckoo3": { "ip": "192.168.56.103", }, "notexistingvm": { "ip": "1.2.3.4", }, }, }) assert config("virtualbox:virtualbox:machines") == [ "cuckoo2", "cuckoo3" ] assert config("virtualbox:cuckoo2:ip") == "192.168.56.102" assert config("virtualbox:cuckoo3:ip") == "192.168.56.103" assert config("virtualbox:notexistingvm:ip") is None
def moloch(request, **kwargs): if not config("reporting:moloch:enabled"): return view_error(request, "Moloch is not enabled!") query = [] for key, value in kwargs.items(): if value and value != "None": query.append(moloch_mapper[key] % value) if ":" in request.get_host(): hostname = request.get_host().split(":")[0] else: hostname = request.get_host() if config("reporting:moloch:insecure"): url = "http://" else: url = "https://" url += "%s:8005/?%s" % ( config("reporting:moloch:host") or hostname, urllib.urlencode({ "date": "-1", "expression": " && ".join(query), }), ) return redirect(url)
def test_dump_pcap(self): class task(object): id = 1234 with mock.patch("subprocess.call") as p: p.side_effect = 0, 0 self.m.dump_pcap("label", task()) p.assert_has_calls([ mock.call([ config("virtualbox:virtualbox:path"), "controlvm", "label", "nictracefile1", cwd("storage", "analyses", "1234", "dump.pcap") ]), mock.call([ config("virtualbox:virtualbox:path"), "controlvm", "label", "nictrace1", "on" ]) ]) with mock.patch("subprocess.call") as p: p.side_effect = 1, self.m.dump_pcap("label", task()) p.assert_called_once() with mock.patch("subprocess.call") as p: p.side_effect = 0, 1 self.m.dump_pcap("label", task()) assert len(p.call_args_list) == 2
def test_config_wrapper(self): assert config("virtualbox:7:label") == "7" assert config("virtualbox:7:ip") == "192.168.58.10" assert config("virtualbox:7:resultserver_port") == 2042 assert config("cuckoo:notasection:hello") is None assert config("cuckoo:cuckoo:notafield") is None
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_dump_memory_vbox5(self): p1 = mock.MagicMock() p1.communicate.return_value = "5.0.28r111378", "" p1.returncode = 0 p2 = mock.MagicMock() p2.wait.return_value = None with mock.patch("cuckoo.machinery.virtualbox.Popen") as p: p.side_effect = p1, p2 self.m.dump_memory("label", "memory.dmp") p.assert_has_calls([ mock.call( [config("virtualbox:virtualbox:path"), "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ), mock.call( [ config("virtualbox:virtualbox:path"), "debugvm", "label", "dumpvmcore", "--filename", "memory.dmp" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ), ])
def test_machine_add(self): cuckoo_machine( "cuckoo2", "add", "1.2.3.4", "windows", None, None, None, None, None ) assert config("virtualbox:virtualbox:machines") == [ "cuckoo1", "cuckoo2", ] assert config("virtualbox:cuckoo2:ip") == "1.2.3.4" assert config("virtualbox:cuckoo2:platform") == "windows"
def test_machine_delete(self): cuckoo_machine( "cuckoo1", "delete", None, None, None, None, None, None, None ) assert config("virtualbox:virtualbox:machines") == [] # TODO This might require a little tweak. with pytest.raises(CuckooConfigurationError) as e: config("virtualbox:cuckoo1:label", strict=True) e.match("No such configuration value exists")
def test_invalid_section(): set_cwd(tempfile.mkdtemp()) Folders.create(cwd(), "conf") Files.create(cwd("conf"), "cuckoo.conf", "[invalid_section]\nfoo = bar") with pytest.raises(CuckooConfigurationError) as e: Config("cuckoo", strict=True) e.match("Config section.*not found") Files.create(cwd("conf"), "cuckoo.conf", "[cuckoo]\ninvalid = entry") with pytest.raises(CuckooConfigurationError) as e: config("cuckoo:invalid:entry", strict=True) e.match("No such configuration value exists")
def __init__(self, message=None, email=None, name=None, company=None, automated=False): self.automated = automated self.message = message self.contact = { "name": name or config("cuckoo:feedback:name"), "company": company or config("cuckoo:feedback:company"), "email": email or config("cuckoo:feedback:email"), } self.errors = [] self.traceback = None self.export = [] self.info = {} self.report = None
def check_specific_config(filename): sections = Config.configuration[filename] for section, entries in sections.items(): if section == "*" or section == "__star__": continue # If an enabled field is present, check it beforehand. if config("%s:%s:enabled" % (filename, section)) is False: continue for key, value in entries.items(): config( "%s:%s:%s" % (filename, section, key), check=True, strict=True )
def tasks_create_submit(): files = [] for f in request.files.getlist("file") + request.files.getlist("files"): files.append({ # The pseudo-file "f" has a read() method so passing it along to # the Submit Manager as-is should be fine. "name": f.filename, "data": f, }) if files: submit_type = "files" elif request.form.get("strings"): submit_type = "strings" strings = request.form["strings"].split("\n") else: return json_error(500, "No files or strings have been given!") # Default options. options = { "procmemdump": "yes", } options.update(parse_options(request.form.get("options", ""))) submit_id = sm.pre( submit_type, files or strings, sm.translate_options_to(options) ) if not submit_id: return json_error(500, "Error creating Submit entry") files, errors, options = sm.get_files(submit_id, astree=True) options["full-memory-dump"] = parse_bool( request.form.get("memory", config("cuckoo:cuckoo:memory_dump")) ) options["enforce-timeout"] = parse_bool( request.form.get("enforce_timeout", 0) ) def selected(files, arcname=None): ret = [] for entry in files: if entry.get("selected"): entry["arcname"] = arcname ret.append(entry) ret += selected(entry["children"], arcname or entry["filename"]) return ret task_ids = sm.submit(submit_id, { "global": { "timeout": request.form.get("timeout", ""), "priority": request.form.get("priority", 1), "tags": request.form.get("tags", None), "custom": request.form.get("custom", ""), "owner": request.form.get("owner", ""), "clock": request.form.get("clock", None), "options": options, }, "file_selection": selected(files), }) return jsonify(submit_id=submit_id, task_ids=task_ids, errors=errors)
def post(self, *args, **kwargs): if "headers" not in kwargs: kwargs["headers"] = {} kwargs["headers"]["Authorization"] = ( "Bearer %s" % config("cuckoo:cuckoo:api_token") ) return self.app.post(*args, **kwargs)
def check_authentication(): token = config("cuckoo:cuckoo:api_token") if token: expect = "Bearer " + token auth = request.headers.get("Authorization") if not constant_time_compare(auth, expect): abort(401)
def test_start_startvm_oserror(self): class machine_no_snapshot(object): snapshot = None options = [] self.m._status = mock.MagicMock(return_value=self.m.POWEROFF) self.m.db.view_machine_by_label.return_value = machine_no_snapshot() self.m._wait_status = mock.MagicMock(return_value=None) p1 = mock.MagicMock() p1.communicate.return_value = "", "" p1.returncode = 0 p2 = mock.MagicMock() p2.communicate.return_value = "", "error starting" with pytest.raises(CuckooMachineError) as e: with mock.patch("cuckoo.machinery.virtualbox.Popen") as p: p.side_effect = p1, p2 self.m.start("label", None) e.match("failed starting the machine") p.assert_any_call( [ config("virtualbox:virtualbox:path"), "snapshot", "label", "restorecurrent" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True )
def test_start_with_snapshot(self): class machine_with_snapshot(object): snapshot = "snapshot" options = [] self.m._status = mock.MagicMock(return_value=self.m.POWEROFF) self.m.db.view_machine_by_label.return_value = machine_with_snapshot() self.m._wait_status = mock.MagicMock(return_value=None) p1 = mock.MagicMock() p1.communicate.return_value = "", "" p1.returncode = 0 p2 = mock.MagicMock() p2.communicate.return_value = "", "" with mock.patch("cuckoo.machinery.virtualbox.Popen") as p: p.side_effect = p1, p2 self.m.start("label", None) p.assert_any_call( [ config("virtualbox:virtualbox:path"), "snapshot", "label", "restore", "snapshot" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True )
def test_stop_failure(self): self.m._status = mock.MagicMock(return_value=self.m.RUNNING) self.m._wait_status = mock.MagicMock(return_value=None) def poll(): return True if p["Popen"].return_value.terminate.call_count else None with mock.patch.multiple( "cuckoo.machinery.virtualbox", time=mock.DEFAULT, Popen=mock.DEFAULT ) as p: p["time"].sleep.return_value = None p["Popen"].return_value.poll.side_effect = poll p["Popen"].return_value.terminate.return_value = None p["Popen"].return_value.returncode = 0 self.m.stop("label") p["Popen"].assert_called_once_with( [ config("virtualbox:virtualbox:path"), "controlvm", "label", "poweroff" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True ) p["Popen"].return_value.terminate.assert_called_once()
def test_list_split(): set_cwd(tempfile.mkdtemp()) cuckoo_create() assert config("virtualbox:cuckoo1:options") == [] set_cwd(tempfile.mkdtemp()) cuckoo_create(cfg={ "virtualbox": { "cuckoo1": { "options": ["noagent", "nictrace"], }, }, }) assert config("virtualbox:cuckoo1:options") == [ "noagent", "nictrace", ]
def stop(self, label): """Stops a virtual machine. @param label: virtual machine label. @raise CuckooMachineError: if unable to stop. """ log.debug("Stopping vm %s" % label) vm_info = self.db.view_machine_by_label(label) if self._status(vm_info.name) == self.STOPPED: raise CuckooMachineError("Trying to stop an already stopped vm %s" % label) proc = self.state.get(vm_info.name, None) proc.kill() stop_me = 0 while proc.poll() is None: if stop_me < config("cuckoo:timeouts:vm_state"): time.sleep(1) stop_me += 1 else: log.debug("Stopping vm %s timeouted. Killing" % label) proc.terminate() time.sleep(1) # if proc.returncode != 0 and stop_me < config("cuckoo:timeouts:vm_state"): # log.debug("QEMU exited with error powering off the machine") self.state[vm_info.name] = None
def test_import_confirm(self, p): set_cwd(tempfile.mkdtemp()) p.return_value = True dirpath = init_legacy_analyses() os.makedirs(os.path.join(dirpath, "lib", "cuckoo", "common")) open(os.path.join( dirpath, "lib", "cuckoo", "common", "constants.py" ), "wb").write(constants_11_py) shutil.copytree( "tests/files/conf/110_plain", os.path.join(dirpath, "conf") ) filepath = os.path.join(dirpath, "conf", "cuckoo.conf") buf = open(filepath, "rb").read() open(filepath, "wb").write(buf.replace( "connection =", "connection = %s" % self.URI )) try: main.main( ("--cwd", cwd(), "import", dirpath), standalone_mode=False ) except CuckooOperationalError as e: assert "SQL database dump as the command" in e.message assert not is_linux() return db = Database() db.connect() assert db.engine.name == self.ENGINE assert open(cwd("logs", "a.txt", analysis=1), "rb").read() == "a" assert config("cuckoo:database:connection") == self.URI assert db.count_tasks() == 2
def test_import_noconfirm(self, p): set_cwd(tempfile.mkdtemp()) p.side_effect = True, False dirpath = init_legacy_analyses() os.makedirs(os.path.join(dirpath, "lib", "cuckoo", "common")) open(os.path.join( dirpath, "lib", "cuckoo", "common", "constants.py" ), "wb").write(constants_11_py) shutil.copytree( "tests/files/conf/110_plain", os.path.join(dirpath, "conf") ) filepath = os.path.join(dirpath, "conf", "cuckoo.conf") buf = open(filepath, "rb").read() open(filepath, "wb").write(buf.replace( "connection =", "connection = %s" % self.URI )) main.main( ("--cwd", cwd(), "import", dirpath), standalone_mode=False ) db = Database() db.connect() assert db.engine.name == self.ENGINE assert open(cwd("logs", "a.txt", analysis=1), "rb").read() == "a" assert config("cuckoo:database:connection") == self.URI assert db.count_tasks() == 2
def test_status_vboxmanage_success(self): vmstate = ( 'biossystemtimeoffset=0\n' 'rtcuseutc="off"\n' 'hwvirtex="on"\n' 'nestedpaging="on"\n' 'largepages="off"\n' 'VMState="poweroff"\n' 'vtxvpid="on"\n' 'vtxux="on"\n' ) with mock.patch("cuckoo.machinery.virtualbox.Popen") as p: p.return_value.communicate.return_value = vmstate, "" p.return_value.returncode = 0 assert self.m._status("label") == "poweroff" p.assert_called_once() p.call_args_list[0] = ( config("virtualbox:virtualbox:path"), "showvminfo", "label", "--machinereadable" ) self.m.db.set_machine_status.assert_called_once_with( "label", "poweroff" ) with mock.patch("cuckoo.machinery.virtualbox.Popen") as p: p.return_value.communicate.return_value = vmstate, "" p.return_value.returncode = 0 assert self.m.vminfo("label", "VMState") == "poweroff" assert self.m.vminfo("label", "biossystemtimeoffset") == "0" assert self.m.vminfo("label", "notanoption") is None
def test_missing_snapshot(self): class machine_no_snapshot(object): snapshot = None options = [] self.m._status = mock.MagicMock(return_value=self.m.POWEROFF) self.m.db.view_machine_by_label.return_value = machine_no_snapshot() p1 = mock.MagicMock() p1.wait.return_value = 0 p2 = mock.MagicMock() p2.communicate.return_value = "", "" with pytest.raises(CuckooMachineSnapshotError) as e: with mock.patch("cuckoo.machinery.virtualbox.Popen") as p: p.return_value.communicate.return_value = "", "error!" self.m.start("label", None) e.match("failed trying to restore") p.assert_called_once_with( [ config("virtualbox:virtualbox:path"), "snapshot", "label", "restorecurrent" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True )
def test_incomplete_envvar(): set_cwd(tempfile.mkdtemp()) cuckoo_create(cfg={ "cuckoo": { "database": { "connection": "%(", }, }, }) # Clear cache. for key in _cache.keys(): del _cache[key] with pytest.raises(CuckooConfigurationError) as e: config("cuckoo:database:connection") e.match("One of the fields")
def process_request(self, request): if request.path.startswith(("/secret/", "/static/")): return # If no web_secret has been initialized, ignore this functionality. if not config("cuckoo:cuckoo:web_secret"): return if not request.session.get("auth"): return redirect("/secret/")
def cuckoo_api(hostname, port, debug): if not config("cuckoo:cuckoo:api_token"): log.warning( "It is strongly recommended to enable API authentication to " "protect against unauthorized access and CSRF attacks." ) log.warning("Please check the API documentation for more information.") app.run(host=hostname, port=port, debug=debug)
def __init__(self, *args, **kwargs): self.analysistasks = {} self.analysishandlers = {} self.ip = config("cuckoo:resultserver:ip") self.port = config("cuckoo:resultserver:port") while True: try: server_addr = self.ip, self.port SocketServer.ThreadingTCPServer.__init__( self, server_addr, ResultHandler, *args, **kwargs ) except Exception as e: if e.errno == errno.EADDRINUSE: if config("cuckoo:resultserver:force_port"): raise CuckooCriticalError( "Cannot bind ResultServer on port %d, " "bailing." % self.port ) else: log.warning("Cannot bind ResultServer on port %s, " "trying another port.", self.port) self.port += 1 elif e.errno == errno.EADDRNOTAVAIL: raise CuckooCriticalError( "Unable to bind ResultServer on %s:%s %s. This " "usually happens when you start Cuckoo without " "bringing up the virtual interface associated with " "the ResultServer IP address. Please refer to " "http://docs.cuckoosandbox.org/en/latest/faq/#troubles-problem" " for more information." % (self.ip, self.port, e) ) else: raise CuckooCriticalError( "Unable to bind ResultServer on %s:%s: %s" % (self.ip, self.port, e) ) else: log.debug( "ResultServer running on %s:%s.", self.ip, self.port ) self.servethread = threading.Thread(target=self.serve_forever) self.servethread.setDaemon(True) self.servethread.start() break
def secret(request): if request.method == "GET": return render_template(request, "secret.html") if request.POST.get("secret") == config("cuckoo:cuckoo:web_secret"): request.session["auth"] = True return redirect("/") return render_template(request, "secret.html", fail=True)
def test_init_star_existing(self): cuckoo_create(cfg={ "virtualbox": { "cuckoo1": { "ip": "192.168.56.102", }, }, }) assert config("virtualbox:cuckoo1:ip") == "192.168.56.102"
def test_cuckoo_init_kv_conf(self): filepath = Files.temp_put("cuckoo.cuckoo.version_check = no") # Create a new CWD as Files.temp_put() indexes - or tries to - the # original cuckoo.conf (even though it doesn't exist yet). set_cwd(tempfile.mkdtemp()) with pytest.raises(SystemExit): main.main(("--cwd", cwd(), "init", "--conf", filepath), standalone_mode=False) assert config("cuckoo:cuckoo:version_check") is False
def rooter(command, *args, **kwargs): if not os.path.exists(config("cuckoo:cuckoo:rooter")): log.critical( "Unable to passthrough root command (%s) as the rooter " "unix socket doesn't exist.", command ) return lock.acquire() s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) if os.path.exists(unixpath): os.remove(unixpath) s.bind(unixpath) try: s.connect(config("cuckoo:cuckoo:rooter")) except socket.error as e: log.critical( "Unable to passthrough root command as we're unable to " "connect to the rooter unix socket: %s.", e ) lock.release() return s.send(json.dumps({ "command": command, "args": args, "kwargs": kwargs, })) ret = json.loads(s.recv(0x10000)) lock.release() if ret["exception"]: log.warning("Rooter returned error: %s", ret["exception"]) return ret["output"]
def init(self): self.enabled = config("reporting:elasticsearch:enabled") self.hosts = config("reporting:elasticsearch:hosts") self.timeout = config("reporting:elasticsearch:timeout") self.calls = config("reporting:elasticsearch:calls") self.index = config("reporting:elasticsearch:index") self.index_time_pattern = config( "reporting:elasticsearch:index_time_pattern") self.cuckoo_node = config("reporting:elasticsearch:cuckoo_node") return self.enabled
def process(target, copy_path, task): results = RunProcessing(task=task).run() RunSignatures(results=results).run() RunReporting(task=task, results=results).run() if config("cuckoo:cuckoo:delete_original"): try: if target and os.path.exists(target): os.remove(target) except OSError as e: log.error("Unable to delete original file at path \"%s\": %s", target, e) if config("cuckoo:cuckoo:delete_bin_copy"): try: if copy_path and os.path.exists(copy_path): os.remove(copy_path) except OSError as e: log.error( "Unable to delete the copy of the original file at " "path \"%s\": %s", copy_path, e)
def __init__(self, *args, **kwargs): self.analysistasks = {} self.analysishandlers = {} self.ip = config("cuckoo:resultserver:ip") self.port = config("cuckoo:resultserver:port") while True: try: server_addr = self.ip, self.port SocketServer.ThreadingTCPServer.__init__( self, server_addr, ResultHandler, *args, **kwargs) except Exception as e: if e.errno == errno.EADDRINUSE: if config("cuckoo:resultserver:force_port"): raise CuckooCriticalError( "Cannot bind ResultServer on port %d, " "bailing." % self.port) else: log.warning( "Cannot bind ResultServer on port %s, " "trying another port.", self.port) self.port += 1 elif e.errno == errno.EADDRNOTAVAIL: raise CuckooCriticalError( "Unable to bind ResultServer on %s:%s %s. This " "usually happens when you start Cuckoo without " "bringing up the virtual interface associated with " "the ResultServer IP address. Please refer to " "http://docs.cuckoosandbox.org/en/latest/faq/#troubles-problem" " for more information." % (self.ip, self.port, e)) else: raise CuckooCriticalError( "Unable to bind ResultServer on %s:%s: %s" % (self.ip, self.port, e)) else: log.debug("ResultServer running on %s:%s.", self.ip, self.port) self.servethread = threading.Thread(target=self.serve_forever) self.servethread.setDaemon(True) self.servethread.start() break
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 start_analysis(self, options, monitor): """Start analysis. @param options: options. @return: operation status. """ # TODO Deal with unicode URLs, should probably try URL encoding. # Unicode files are being taken care of. self.timeout = options["timeout"] + config("cuckoo:timeouts:critical") url = "http://{0}:{1}".format(self.ip, CUCKOO_GUEST_PORT) self.server = TimeoutServer(url, allow_none=True, timeout=self.timeout) try: # Wait for the agent to respond. This is done to check the # availability of the agent and verify that it's ready to receive # data. self.wait(CUCKOO_GUEST_INIT) # Invoke the upload of the analyzer to the guest. self.upload_analyzer(monitor) # Give the analysis options to the guest, so it can generate the # analysis.conf inside the guest. try: self.server.add_config(options) except: raise CuckooGuestError( "%s: unable to upload config to analysis machine" % self.id) # If the target of the analysis is a file, upload it to the guest. if self.target.is_file: file_data = self.target.helper.read() data = xmlrpclib.Binary(file_data) try: self.server.add_malware(data, options["file_name"]) except Exception as e: raise CuckooGuestError( "#%s: unable to upload malware to analysis " "machine: %s" % (self.id, e)) # Launch the analyzer. pid = self.server.execute() log.debug("%s: analyzer started with PID %d", self.id, pid) # If something goes wrong when establishing the connection, raise an # exception and abort the analysis. except (socket.timeout, socket.error): raise CuckooGuestError( "%s: guest communication timeout, check networking or try " "to increase timeout" % self.id)
def _allocate_new_machine(self): """ allocating/creating new EC2 instance(autoscale option) """ # read configuration file machinery_options = self.options.get("aws") autoscale_options = self.options.get("autoscale") # If configured, use specific network interface for this # machine, else use the default value. interface = autoscale_options["interface"] if autoscale_options.get( "interface") else machinery_options.get("interface") resultserver_ip = autoscale_options[ "resultserver_ip"] if autoscale_options.get( "resultserver_ip") else config("cuckoo:resultserver:ip") if autoscale_options.get("resultserver_port"): resultserver_port = autoscale_options["resultserver_port"] else: # The ResultServer port might have been dynamically changed, # get it from the ResultServer singleton. Also avoid import # recursion issues by importing ResultServer here. from cuckoo.core.resultserver import ResultServer resultserver_port = ResultServer().port log.info("All machines are busy, allocating new machine") self.dynamic_machines_sequence += 1 self.dynamic_machines_count += 1 new_machine_name = "cuckoo_autoscale_%03d" % self.dynamic_machines_sequence instance = self._create_instance(tags=[{ "Key": "Name", "Value": new_machine_name }, { "Key": self.AUTOSCALE_CUCKOO, "Value": "True" }]) if instance is None: return False self.ec2_machines[instance.id] = instance # sets "new_machine" object in configuration object to avoid raising an exception setattr(self.options, new_machine_name, {}) # add machine to DB self.db.add_machine(name=new_machine_name, label=instance.id, ip=instance.private_ip_address, platform=autoscale_options["platform"], options=autoscale_options["options"], tags=autoscale_options["tags"], interface=interface, snapshot=None, resultserver_ip=resultserver_ip, resultserver_port=resultserver_port) return True
def __init__(self, message=None, email=None, name=None, company=None, automated=False, task_id=None, include_files=False): self.automated = automated self.message = message self.contact = { "name": name or config("cuckoo:feedback:name"), "company": company or config("cuckoo:feedback:company"), "email": email or config("cuckoo:feedback:email"), } self.errors = [] self.traceback = None self.export = [] self.info = {} self.report = None self.task_id = task_id self.include_files = include_files
def __init__(self, memfile, osprofile, number, file_path): self.mask_pid = [] self.taint_pid = set() self.memfile = memfile self.osprofile = osprofile self.number = number self.file_path = file_path for pid in config("memory:mask:pid_generic"): if pid and pid.isdigit(): self.mask_pid.append(int(pid)) self.vol = VolatilityAPI(self.memfile, self.osprofile, self.file_path)
def _do_connect(task): if not task.guest: return JsonResponse( { "status": "failed", "message": "task is not assigned to a machine yet", }, status=500) machine = db.view_machine_by_label(task.guest.label) rcparams = machine.rcparams protocol = rcparams.get("protocol") host = rcparams.get("host") port = rcparams.get("port") guacd_host = config("cuckoo:remotecontrol:guacd_host") guacd_port = config("cuckoo:remotecontrol:guacd_port") try: guac = GuacamoleClient(guacd_host, guacd_port, debug=False) guac.handshake(protocol=protocol, hostname=host, port=port) except (socket.error, GuacamoleError) as e: log.error("Failed to connect to guacd on %s:%d -> %s", guacd_host, guacd_port, e) return JsonResponse( { "status": "failed", "message": "connection failed", }, status=500) cache_key = str(uuid.uuid4()) with sockets_lock: sockets[cache_key] = guac response = HttpResponse(content=cache_key) response["Cache-Control"] = "no-cache" return response
def test_start_no_snapshot(self): class machine_no_snapshot(object): snapshot = None options = {} self.m._status = mock.MagicMock(return_value=self.m.POWEROFF) self.m.db.view_machine_by_label.return_value = machine_no_snapshot() self.m._wait_status = mock.MagicMock(return_value=None) p1 = mock.MagicMock() p1.communicate.return_value = "", "" p1.returncode = 0 p2 = mock.MagicMock() p2.communicate.return_value = "", "" with mock.patch("cuckoo.machinery.virtualbox.Popen") as p: p.side_effect = p1, p2 self.m.start("label", None) p.assert_has_calls([ mock.call([ config("virtualbox:virtualbox:path"), "snapshot", "label", "restorecurrent" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True), mock.call([ config("virtualbox:virtualbox:path"), "startvm", "label", "--type", "headless", ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) ])
def init(self): self.enabled = config("reporting:mongodb:enabled") self.hostname = config("reporting:mongodb:host") self.port = config("reporting:mongodb:port") self.database = config("reporting:mongodb:db") self.username = config("reporting:mongodb:username") self.password = config("reporting:mongodb:password") return self.enabled
def clean_tasks(dt): ses = db.db.Session() tasks = [] try: tasks = ses.query(DbTask.id).filter(DbTask.owner == "cuckoo.massurl", DbTask.started_on <= dt).all() if not tasks: return log.debug("Deleting %s tasks", len(tasks)) tasks = set(task.id for task in tasks) db.db.engine.execute(URLGroupTask.__table__.delete().where( URLGroupTask.task_id.in_(tasks))) ses.commit() finally: ses.close() # Move PCAPS if enabled if config("massurl:retention:keep_pcap"): pcaps = cwd("storage", "files", "pcap") if not os.path.exists(pcaps): os.makedirs(pcaps) for task_id in tasks: dump_path = cwd("dump.pcap", analysis=task_id) if not os.path.isfile(dump_path): continue new_path = os.path.join(pcaps, "%s.pcap" % task_id) if os.path.isfile(new_path): continue shutil.move(dump_path, new_path) for task_id in tasks: task_path = cwd(analysis=task_id) if os.path.exists(task_path): shutil.rmtree(task_path) # delete tasks ses = db.db.Session() try: # Remove relations with tasks that will be removed db.db.engine.execute(tasks_tags.delete().where( tasks_tags.c.task_id.in_(tasks))) db.db.engine.execute(Error.__table__.delete().where( Error.task_id.in_(tasks))) db.db.engine.execute(DbTask.__table__.delete().where( DbTask.id.in_(tasks))) ses.commit() finally: ses.close()
def read_config(): global THRESHOLD, DAEMON, ARCHIVE, BINARIES_FOLDER, REPORTS_FOLDER if config("cuckoo:purge:threshold"): THRESHOLD = config("cuckoo:purge:threshold") DAEMON = config("cuckoo:purge:daemon") ARCHIVE = config("cuckoo:purge:archive") BINARIES_FOLDER = config("cuckoo:purge:binaries_folder") REPORTS_FOLDER = config("cuckoo:purge:reports_folder")
def test_default_config(): """Test the default configuration.""" dirpath = tempfile.mkdtemp() with pytest.raises(SystemExit): main.main( ("--cwd", dirpath, "--nolog", "init"), standalone_mode=False ) assert config("cuckoo:cuckoo:version_check") is True assert config("cuckoo:cuckoo:tmppath") is None assert config("cuckoo:resultserver:ip") == "192.168.56.1" assert config("cuckoo:processing:analysis_size_limit") == 128*1024*1024 assert config("cuckoo:timeouts:critical") == 60 assert config("auxiliary:mitm:mitmdump") == "/usr/local/bin/mitmdump" with pytest.raises(RuntimeError) as e: config("nope") e.match("Invalid configuration entry") with pytest.raises(RuntimeError) as e: config("nope:nope") e.match("Invalid configuration entry") assert check_configs() os.remove(os.path.join(dirpath, "conf", "cuckoo.conf")) with pytest.raises(CuckooStartupError) as e: check_configs() e.match("Config file does not exist") Files.create( (dirpath, "conf"), "cuckoo.conf", "[cuckoo]\nversion_check = on" ) assert check_configs()
def __init__(self): ip = config("cuckoo:resultserver:ip") port = config("cuckoo:resultserver:port") pool_size = config('cuckoo:resultserver:pool_size') sock = gevent.socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: sock.bind((ip, port)) except (OSError, socket.error) as e: if e.errno == errno.EADDRINUSE: raise CuckooCriticalError( "Cannot bind ResultServer on port %d " "because it was in use, bailing." % port) elif e.errno == errno.EADDRNOTAVAIL: raise CuckooCriticalError( "Unable to bind ResultServer on %s:%s %s. This " "usually happens when you start Cuckoo without " "bringing up the virtual interface associated with " "the ResultServer IP address. Please refer to " "https://cuckoo.sh/docs/faq/#troubles-problem " "for more information." % (ip, port, e)) else: raise CuckooCriticalError( "Unable to bind ResultServer on %s:%s: %s" % (ip, port, e)) # We allow user to specify port 0 to get a random port, report it back # here _, self.port = sock.getsockname() sock.listen(128) self.thread = threading.Thread(target=self.create_server, args=(sock, pool_size)) self.thread.daemon = True self.thread.start()
def run(self): results = {} # Include any results provided by the mitm script. results["mitm"] = [] if os.path.exists(self.mitmout_path): for line in open(self.mitmout_path, "rb"): try: results["mitm"].append(json.loads(line)) except: results["mitm"].append(line) if not os.path.exists(self.pcap_path): log.warning("The PCAP file does not exist at path \"%s\".", self.pcap_path) return results if not os.path.getsize(self.pcap_path): log.error("The PCAP file at path \"%s\" is empty." % self.pcap_path) return results # PCAP file hash. results["pcap_sha256"] = File(self.pcap_path).get_sha256() sorted_path = self.pcap_path.replace("dump.", "dump_sorted.") if config("cuckoo:processing:sort_pcap"): sort_pcap(self.pcap_path, sorted_path) # Sorted PCAP file hash. if os.path.exists(sorted_path): results["sorted_pcap_sha256"] = File(sorted_path).get_sha256() pcap_path = sorted_path else: pcap_path = self.pcap_path else: pcap_path = self.pcap_path results.update(Pcap(pcap_path, self.options).run()) if os.path.exists(pcap_path): try: p2 = Pcap2(pcap_path, self.get_tlsmaster(), self.network_path) results.update(p2.run()) except: log.exception("Error running httpreplay-based PCAP analysis") return results
def run(self): """Run analysis. @return: volatility results dict. """ self.key = "memory" if not HAVE_VOLATILITY: log.error( "Cannot run volatility module: the volatility library " "is not available. Please install it according to their " "documentation." ) return if not self.memory_path or not os.path.exists(self.memory_path): log.error( "VM memory dump not found: to create VM memory dumps you " "have to enable memory_dump in cuckoo.conf!" ) return if not os.path.getsize(self.memory_path): log.error( "VM memory dump empty: to properly create VM memory dumps " "you have to enable memory_dump in cuckoo.conf!" ) return osprofile = ( self.machine.get("osprofile") or config("memory:basic:guest_profile") ) analyse_type = self.task["custom"] if analyse_type is not None: if "offline" in analyse_type: try: return VolatilityManager(self.memory_path, osprofile, self.task["id"], self.task["target"]).run() except CuckooOperationalError as e: log.error( "Error running Volatility on machine '%s': %s", (self.machine.get("name") or "unknown VM name"), e ) elif "online" in analyse_type: return else: return
def _wait_task(self, task): """Wait for a task to complete with timeout""" limit = datetime.timedelta(seconds=config("cuckoo:timeouts:vm_state")) start = datetime.datetime.utcnow() while True: if task.info.state == "error": raise CuckooMachineError("Task error") if task.info.state == "success": break if datetime.datetime.utcnow() - start > limit: raise CuckooMachineError("Task timed out") time.sleep(1)
def test_compact_hd(self): self.m.vminfo = mock.MagicMock(return_value="\"30d29d87-e54d\"") c1 = mock.MagicMock() c1.returncode = 0 c1.return_value = "", "" with mock.patch("subprocess.check_output") as co: co.side_effect = c1 self.m.compact_hd("label") co.assert_called_once_with([ config("virtualbox:virtualbox:path"), "modifyhd", "30d29d87-e54d", "--compact" ], stderr=subprocess.PIPE)
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 test_stop_success(self): self.m._emulator_labels = {"cuckoo": "emulator-1337"} with mock.patch("cuckoo.machinery.avd.subprocess.Popen") as p: proc = mock.MagicMock() proc.communicate.return_value = "", "" proc.returncode = 0 p.return_value = proc self.m.stop("cuckoo") p.assert_called_once_with([ "sudo", config("avd:avd:adb_path"), "-s", "emulator-1337", "emu", "kill" ], stdout=subprocess.PIPE, stderr=subprocess.PIPE) assert self.m._emulator_labels == {}
def init_tasks(): """Check tasks and reschedule uncompleted ones.""" db = Database() log.debug("Checking for locked tasks..") for task in db.list_tasks(status=TASK_RUNNING): if config("cuckoo:cuckoo:reschedule"): task_id = db.reschedule(task.id) log.info("Rescheduled task with ID %s and target %s: task #%s", task.id, task.target, task_id) else: db.set_status(task.id, TASK_FAILED_ANALYSIS) log.info("Updated running task ID %s status to failed_analysis", task.id) log.debug("Checking for pending service tasks..") for task in db.list_tasks(status=TASK_PENDING, category="service"): db.set_status(task.id, TASK_FAILED_ANALYSIS)
def tunnel(request, task_id): task = db.view_task(int(task_id)) if not task: return HttpResponse(status=404) if not config("cuckoo:remotecontrol:enabled"): return JsonResponse( { "status": "failed", "msg": "remote control is not enabled", }, status=500) if task.options.get("remotecontrol") != "yes": return JsonResponse( { "status": "failed", "msg": "this task does not have remote control", }, status=500) if task.status != "running": return JsonResponse( { "status": task.status, "msg": "this task is not running", }, status=500) qs = request.META["QUERY_STRING"] if qs == "connect": return ControlApi._do_connect(task) try: cmd, conn, = qs.split(":")[:2] except ValueError: return HttpResponse(status=400) if cmd == "read": return ControlApi._do_read(conn) elif cmd == "write": return ControlApi._do_write(request, conn) return HttpResponse(status=400)
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 != "none": 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")) ) 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 mask_filter(self, old): """Filter out masked stuff. Keep tainted stuff.""" new = {} for akey in old.keys(): new[akey] = {"config": old[akey]["config"], "data": []} do_filter = config("memory:%s:filter" % akey) new[akey]["config"]["filter"] = do_filter for item in old[akey]["data"]: # TODO: need to improve this logic. if not do_filter: new[akey]["data"].append(item) elif "process_id" in item and \ item["process_id"] in self.mask_pid and \ item["process_id"] not in self.taint_pid: pass else: new[akey]["data"].append(item) return new
def enabled(self, plugin_name, profiles): # Some plugins can only run in certain profiles (i.e., only in # Windows XP/Vista/7, or only in x86 or x64). osprofile = self.osprofile.lower() for profile in profiles: if osprofile.startswith(profile) or osprofile.endswith(profile): break else: if profiles: return False if not config("memory:%s:enabled" % plugin_name): log.debug("Skipping '%s' volatility module", plugin_name) return False if plugin_name not in self.vol.plugins: return False return True
def __init__(self, task_id, error_queue): """@param task: task object containing the details for the analysis.""" threading.Thread.__init__(self) self.errors = error_queue self.cfg = Config() self.storage = "" self.binary = "" self.storage_binary = "" self.machine = None self.db = Database() self.task = self.db.view_task(task_id) self.guest_manager = None self.route = None self.interface = None self.rt_table = None self.unrouted_network = False self.stopped_aux = False self.rs_port = config("cuckoo:resultserver:port")
def _initialize(self, module_name): """Read configuration. @param module_name: module name. """ machinery = self.options.get(module_name) for vmname in machinery["machines"]: options = self.options.get(vmname) # If configured, use specific network interface for this # machine, else use the default value. if options.get("interface"): interface = options["interface"] else: interface = machinery.get("interface") if options.get("resultserver_ip"): ip = options["resultserver_ip"] else: ip = config("cuckoo:resultserver:ip") if options.get("resultserver_port"): port = options["resultserver_port"] else: # The ResultServer port might have been dynamically changed, # get it from the ResultServer singleton. Also avoid import # recursion issues by importing ResultServer here. from cuckoo.core.resultserver import ResultServer port = ResultServer().port self.db.add_machine( name=vmname, label=options[self.LABEL], ip=options.ip, platform=options.platform, options=options.get("options", ""), tags=options.tags, interface=interface, snapshot=options.snapshot, resultserver_ip=ip, resultserver_port=port )