Пример #1
0
    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))
Пример #2
0
    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
Пример #3
0
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
Пример #4
0
    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
Пример #5
0
    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)
            )
Пример #6
0
 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
Пример #7
0
 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
Пример #8
0
    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)
Пример #9
0
    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)
            )
Пример #10
0
    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)
Пример #11
0
    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)
            )
Пример #12
0
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
Пример #13
0
    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]
Пример #14
0
    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]
Пример #15
0
    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)
Пример #16
0
    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
Пример #17
0
    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()
Пример #18
0
    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)
Пример #19
0
    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
Пример #20
0
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)
Пример #21
0
    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
        )
Пример #22
0
    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
Пример #23
0
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)
Пример #24
0
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)
Пример #25
0
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)
Пример #26
0
    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)
Пример #27
0
    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)