Beispiel #1
0
    def analyze_sample(self, sample_path, workdir, outdir, start_command,
                       timeout):
        analysis_info = dict()

        dns_server = self.config.config['drakrun'].get('dns_server', '8.8.8.8')
        drakmon_log_fp = os.path.join(outdir, "drakmon.log")

        with self.run_vm() as vm, \
             graceful_exit(start_dnsmasq(self.instance_id, dns_server)), \
             graceful_exit(start_tcpdump_collector(self.instance_id, outdir)), \
             open(drakmon_log_fp, "wb") as drakmon_log:

            analysis_info[
                'snapshot_version'] = vm.backend.get_vm0_snapshot_time()

            kernel_profile = os.path.join(PROFILE_DIR, "kernel.json")

            self.log.info("Copying sample to VM...")
            injector = Injector(self.vm_name, self.runtime_info,
                                kernel_profile)
            result = injector.write_file(
                sample_path,
                f"%USERPROFILE%\\Desktop\\{os.path.basename(sample_path)}")
            injected_fn = json.loads(result.stdout)['ProcessName']

            if "%f" not in start_command:
                self.log.warning("No file name in start command")
            cur_start_command = start_command.replace("%f", injected_fn)

            # don't include our internal maintanance commands
            analysis_info['start_command'] = cur_start_command
            self.log.info("Using command: %s", cur_start_command)

            if self.net_enable:
                self.log.info("Setting up network...")
                injector.create_process("cmd /C ipconfig /renew >nul",
                                        wait=True,
                                        timeout=120)

            drakvuf_cmd = self.build_drakvuf_cmdline(
                timeout=timeout,
                cwd=subprocess.list2cmdline([ntpath.dirname(injected_fn)]),
                full_cmd=cur_start_command,
                dump_dir=os.path.join(outdir, "dumps"),
                ipt_dir=os.path.join(outdir, "ipt"),
                workdir=workdir,
            )

            try:
                subprocess.run(drakvuf_cmd,
                               stdout=drakmon_log,
                               check=True,
                               timeout=timeout + 60)
            except subprocess.TimeoutExpired:
                self.log.exception("DRAKVUF timeout expired")

        return analysis_info
Beispiel #2
0
class DrakmonShell:
    def __init__(self, vm_id: int, dns: str):

        self.cleanup(vm_id)

        install_info = InstallInfo.load()
        backend = get_storage_backend(install_info)

        generate_vm_conf(install_info, vm_id)
        self.vm = VirtualMachine(backend, vm_id)
        self._dns = dns

        with open(Path(PROFILE_DIR) / "runtime.json", "r") as f:
            self.runtime_info = RuntimeInfo.load(f)
        self.desktop = WinPath(r"%USERPROFILE%") / "Desktop"

        self.kernel_profile = Path(PROFILE_DIR) / "kernel.json"
        self.injector = Injector(
            self.vm.vm_name,
            self.runtime_info,
            self.kernel_profile,
        )
        setup_vm_network(vm_id, True, find_default_interface(), dns)

    def cleanup(self, vm_id: int):

        logging.info(f"Ensuring that drakrun@{vm_id} service is stopped...")
        try:
            subprocess.run(
                ["systemctl", "stop", f"drakrun@{vm_id}"],
                stderr=subprocess.STDOUT,
                check=True,
            )
        except subprocess.CalledProcessError:
            raise Exception(f"drakrun@{vm_id} not stopped")

    def drakvuf(self, plugins, timeout=60):
        d = tempfile.TemporaryDirectory(prefix="drakvuf_")
        workdir = Path(d.name)

        log = open(workdir / "drakmon.log", "wb")

        cmd = ["drakvuf"]
        cmd.extend([
            "-o",
            "json",
            "F",
            "-j",
            "5",
            "-t",
            str(timeout),
            "-i",
            str(self.runtime_info.inject_pid),
            "-k",
            str(self.runtime_info.vmi_offsets.kpgd),
            "-r",
            self.kernel_profile,
            "-d",
            self.vm.vm_name,
            "--dll-hooks-list",
            Path(ETC_DIR) / "hooks.txt",
        ])

        if "memdump" in plugins:
            dumps = workdir / "dumps"
            dumps.mkdir()
            cmd.extend(["--memdump-dir", dumps])

        if "ipt" in plugins:
            ipt = workdir / "ipt"
            ipt.mkdir()
            cmd.extend(["--ipt-dir", ipt])

        for chk in (["-a", plugin] for plugin in plugins):
            cmd.extend(chk)

        subprocess.run(cmd, stdout=log)

        return d

    def help(self):
        usage = dedent("""\
        Available commands:
        - copy(file_path)   # copy file onto vm desktop
        - mount(iso_path)   # mount iso, useful for installing software, e.g. office
        - drakvuf(plugins)  # start drakvuf with provided set of plugins
        - run(cmd)          # run command inside vm
        - exit()            # exit playground
        """)
        print(usage)

    def copy(self, local):
        local = Path(local)
        self.injector.write_file(local, self.desktop / local.name)

    def mount(self, local_iso_path, drive=FIRST_CDROM_DRIVE):
        local_iso_path = Path(local_iso_path)
        insert_cd(self.vm.vm_name, drive, local_iso_path)

    def run(self, cmd):
        self.injector.create_process(cmd)

    def __enter__(self):
        self.vm.restore()
        return self

    def __exit__(self, exc_type, exc_value, exc_traceback):
        self.vm.destroy()
        delete_vm_network(self.vm.vm_id, True, find_default_interface(),
                          self._dns)
Beispiel #3
0
    def analyze_sample(self, sample_path, workdir, outdir, start_command,
                       timeout):
        analysis_info = dict()

        dns_server = self.config.config["drakrun"].get("dns_server", "8.8.8.8")
        drakmon_log_fp = os.path.join(outdir, "drakmon.log")

        with self.run_vm() as vm, graceful_exit(
                start_dnsmasq(self.instance_id, dns_server)), graceful_exit(
                    start_tcpdump_collector(self.instance_id, outdir)), open(
                        drakmon_log_fp, "wb") as drakmon_log:

            analysis_info[
                "snapshot_version"] = vm.backend.get_vm0_snapshot_time()

            kernel_profile = os.path.join(PROFILE_DIR, "kernel.json")

            self.log.info("Copying sample to VM...")
            injector = Injector(self.vm_name, self.runtime_info,
                                kernel_profile)
            result = injector.write_file(
                sample_path,
                f"%USERPROFILE%\\Desktop\\{os.path.basename(sample_path)}")

            try:
                injected_fn = json.loads(result.stdout)["ProcessName"]
            except ValueError as e:
                self.log.error(
                    "JSON decode error occurred when tried to parse injector's logs."
                )
                self.log.error(f"Raw log line: {result.stdout}")
                raise e

            # don't include our internal maintanance commands
            start_command = start_command.replace("%f", injected_fn)
            analysis_info["start_command"] = start_command
            self.log.info("Using command: %s", start_command)

            if self.net_enable:
                self.log.info("Setting up network...")
                injector.create_process("cmd /C ipconfig /renew >nul",
                                        wait=True,
                                        timeout=120)

            task_quality = self.current_task.headers.get("quality", "high")
            requested_plugins = self.current_task.payload.get(
                "plugins", self.active_plugins["_all_"])
            analysis_info["plugins"] = self.get_plugin_list(
                task_quality, requested_plugins)

            drakvuf_cmd = self.build_drakvuf_cmdline(
                timeout=timeout,
                cwd=subprocess.list2cmdline([ntpath.dirname(injected_fn)]),
                full_cmd=start_command,
                dump_dir=os.path.join(outdir, "dumps"),
                ipt_dir=os.path.join(outdir, "ipt"),
                workdir=workdir,
                enabled_plugins=analysis_info["plugins"],
            )

            try:
                subprocess.run(drakvuf_cmd,
                               stdout=drakmon_log,
                               check=True,
                               timeout=timeout + 60)
            except subprocess.CalledProcessError as e:
                # see DRAKVUF src/exitcodes.h for more details
                INJECTION_UNSUCCESSFUL = 4

                if e.returncode == INJECTION_UNSUCCESSFUL:
                    self.log_startup_failure(drakmon_log_fp)
                raise e
            except subprocess.TimeoutExpired as e:
                self.log.exception("DRAKVUF timeout expired")
                raise e

        return analysis_info
Beispiel #4
0
    def process(self):
        sample = self.current_task.get_resource("sample")
        self.log.info("hostname: {}".format(socket.gethostname()))
        sha256sum = hashlib.sha256(sample.content).hexdigest()
        magic_output = magic.from_buffer(sample.content)
        self.log.info("running sample sha256: {}".format(sha256sum))

        timeout = self.current_task.payload.get(
            'timeout') or self.default_timeout
        hard_time_limit = 60 * 20
        if timeout > hard_time_limit:
            self.log.error(
                "Tried to run the analysis for more than hard limit of %d seconds",
                hard_time_limit)
            return

        self.log.info(f"analysis UID: {self.analysis_uid}")
        self.rs.set(f"drakvnc:{self.analysis_uid}", self.instance_id,
                    ex=3600)  # 1h

        workdir = f"/tmp/drakrun/{self.vm_name}"

        extension = self.current_task.headers.get("extension", "exe").lower()
        if '(DLL)' in magic_output:
            extension = 'dll'
        self.log.info("Running file as %s", extension)

        file_name = self.current_task.payload.get("file_name",
                                                  "malwar") + f".{extension}"
        # Alphanumeric, dot, underscore, dash
        if not re.match(r"^[a-zA-Z0-9\._\-]+$", file_name):
            self.log.error("Filename contains invalid characters")
            return
        self.log.info("Using file name %s", file_name)

        # Save sample to disk here as some branches of _get_start_command require file path.
        try:
            shutil.rmtree(workdir)
        except Exception as e:
            print(e)
        os.makedirs(workdir, exist_ok=True)
        with open(os.path.join(workdir, file_name), 'wb') as f:
            f.write(sample.content)

        start_command = self.current_task.payload.get(
            "start_command",
            self._get_start_command(extension, sample,
                                    os.path.join(workdir, file_name)))
        if not start_command:
            self.log.error(
                "Unable to run malware sample, could not generate any suitable command to run it."
            )
            return

        outdir = os.path.join(workdir, 'output')
        os.mkdir(outdir)
        os.mkdir(os.path.join(outdir, 'dumps'))
        os.mkdir(os.path.join(outdir, 'ipt'))

        metadata = {
            "sample_sha256": sha256sum,
            "magic_output": magic_output,
            "time_started": int(time.time())
        }

        with open(os.path.join(outdir, 'sample_sha256.txt'), 'w') as f:
            f.write(hashlib.sha256(sample.content).hexdigest())

        watcher_tcpdump = None
        watcher_dnsmasq = None

        for _ in range(3):
            try:
                self.log.info("Running VM {}".format(self.instance_id))
                watcher_dnsmasq = start_dnsmasq(
                    self.instance_id,
                    self.config.config['drakrun'].get('dns_server', '8.8.8.8'))

                snapshot_version = self.run_vm()
                metadata['snapshot_version'] = snapshot_version

                watcher_tcpdump = start_tcpdump_collector(
                    self.instance_id, outdir)

                self.log.info("running monitor {}".format(self.instance_id))

                hooks_list = os.path.join(ETC_DIR, "hooks.txt")
                kernel_profile = os.path.join(PROFILE_DIR, "kernel.json")
                dump_dir = os.path.join(outdir, "dumps")
                ipt_dir = os.path.join(outdir, "ipt")
                drakmon_log_fp = os.path.join(outdir, "drakmon.log")

                self.log.info("Copying sample to VM...")
                injector = Injector(self.vm_name, self.runtime_info,
                                    kernel_profile)
                result = injector.write_file(
                    os.path.join(workdir, file_name),
                    f"%USERPROFILE%\\Desktop\\{file_name}")

                injected_fn = json.loads(result.stdout)['ProcessName']
                net_enable = int(self.config.config['drakrun'].get(
                    'net_enable', '0'))

                if "%f" not in start_command:
                    self.log.warning("No file name in start command")

                cwd = subprocess.list2cmdline([ntpath.dirname(injected_fn)])
                cur_start_command = start_command.replace("%f", injected_fn)

                # don't include our internal maintanance commands
                metadata['start_command'] = cur_start_command

                if net_enable:
                    self.log.info("Setting up network...")
                    injector.create_process("cmd /C ipconfig /renew >nul",
                                            wait=True,
                                            timeout=120)

                full_cmd = cur_start_command
                self.log.info("Using command: %s", full_cmd)

                task_quality = self.current_task.headers.get("quality", "high")

                drakvuf_cmd = ["drakvuf"] + self.generate_plugin_cmdline(task_quality) + \
                              ["-o", "json"
                               "-j", "5",
                               "-t", str(timeout),
                               "-i", str(self.runtime_info.inject_pid),
                               "-k", hex(self.runtime_info.vmi_offsets.kpgd),
                               "-d", self.vm_name,
                               "--dll-hooks-list", hooks_list,
                               "--memdump-dir", dump_dir,
                               "-r", kernel_profile,
                               "-e", full_cmd,
                               "-c", cwd]

                if self.config.config['drakrun'].getboolean('enable_ipt',
                                                            fallback=False):
                    drakvuf_cmd.extend(["--ipt-dir", ipt_dir])

                drakvuf_cmd.extend(self.get_profile_list())

                syscall_filter = self.config.config['drakrun'].get(
                    'syscall_filter', None)
                if syscall_filter:
                    drakvuf_cmd.extend(["-S", syscall_filter])

                with open(drakmon_log_fp, "wb") as drakmon_log:
                    drakvuf = subprocess.Popen(drakvuf_cmd, stdout=drakmon_log)

                    try:
                        exit_code = drakvuf.wait(timeout + 60)
                    except subprocess.TimeoutExpired as e:
                        logging.error(
                            "BUG: Monitor command doesn\'t terminate automatically after timeout expires."
                        )
                        logging.error("Trying to terminate DRAKVUF...")
                        drakvuf.terminate()
                        drakvuf.wait(10)
                        logging.error(
                            "BUG: Monitor command also doesn\'t terminate after sending SIGTERM."
                        )
                        drakvuf.kill()
                        drakvuf.wait()
                        logging.error("Monitor command was forcefully killed.")
                        raise e

                    if exit_code != 0:
                        raise subprocess.CalledProcessError(
                            exit_code, drakvuf_cmd)
                break
            except subprocess.CalledProcessError:
                self.log.info("Something went wrong with the VM {}".format(
                    self.instance_id),
                              exc_info=True)
            finally:
                try:
                    subprocess.run(["xl", "destroy", self.vm_name],
                                   cwd=workdir,
                                   check=True)
                except subprocess.CalledProcessError:
                    self.log.info("Failed to destroy VM {}".format(
                        self.instance_id),
                                  exc_info=True)

                if watcher_dnsmasq:
                    watcher_dnsmasq.terminate()
        else:
            self.log.info(
                "Failed to analyze sample after 3 retries, giving up.")
            return

        self.log.info("waiting for tcpdump to exit")

        if watcher_tcpdump:
            try:
                watcher_tcpdump.wait(timeout=60)
            except subprocess.TimeoutExpired:
                self.log.exception("tcpdump doesn't exit cleanly after 60s")

        self.crop_dumps(os.path.join(outdir, 'dumps'),
                        os.path.join(outdir, 'dumps.zip'))

        if self.config.config['drakrun'].getboolean('enable_ipt',
                                                    fallback=False):
            self.compress_ipt(os.path.join(outdir, 'ipt'),
                              os.path.join(outdir, 'ipt.zip'))

        self.log.info("uploading artifacts")

        metadata['time_finished'] = int(time.time())

        with open(os.path.join(outdir, 'metadata.json'), 'w') as f:
            f.write(json.dumps(metadata))

        payload = {"analysis_uid": self.analysis_uid}
        payload.update(metadata)

        headers = dict(self.headers)
        headers["quality"] = self.current_task.headers.get("quality", "high")

        t = Task(headers, payload=payload)

        for resource in self.upload_artifacts(self.analysis_uid, workdir):
            t.add_payload(resource.name, resource)

        t.add_payload('sample', sample)
        self.send_task(t)
Beispiel #5
0
class DrakmonShell:
    def __init__(self, vm_id: int, dns: str):

        self.cleanup(vm_id)

        install_info = InstallInfo.load()
        backend = get_storage_backend(install_info)

        generate_vm_conf(install_info, vm_id)
        self.vm = VirtualMachine(backend, vm_id)
        self._dns = dns

        with open(Path(PROFILE_DIR) / "runtime.json", 'r') as f:
            self.runtime_info = RuntimeInfo.load(f)
        self.desktop = WinPath(r"%USERPROFILE%") / "Desktop"

        self.kernel_profile = Path(PROFILE_DIR) / "kernel.json"
        self.injector = Injector(
            self.vm.vm_name,
            self.runtime_info,
            self.kernel_profile,
        )
        setup_vm_network(vm_id, True, find_default_interface(), dns)

    def cleanup(self, vm_id: int):

        logging.info(f"Ensuring that drakrun@{vm_id} service is stopped...")
        try:
            subprocess.run(['systemctl', 'stop', f'drakrun@{vm_id}'],
                           stderr=subprocess.STDOUT,
                           check=True)
        except subprocess.CalledProcessError:
            raise Exception(f"drakrun@{vm_id} not stopped")

    def drakvuf(self, plugins, timeout=60):
        d = tempfile.TemporaryDirectory(prefix="drakvuf_")
        workdir = Path(d.name)

        log = open(workdir / "drakmon.log", "wb")

        cmd = ["drakvuf"]
        cmd.extend([
            "-o",
            "json",
            "F",
            "-j",
            "5",
            "-t",
            str(timeout),
            "-i",
            str(self.runtime_info.inject_pid),
            "-k",
            str(self.runtime_info.vmi_offsets.kpgd),
            "-r",
            self.kernel_profile,
            "-d",
            self.vm.vm_name,
            "--dll-hooks-list",
            Path(ETC_DIR) / "hooks.txt",
        ])

        if "memdump" in plugins:
            dumps = workdir / "dumps"
            dumps.mkdir()
            cmd.extend(["--memdump-dir", dumps])

        if "ipt" in plugins:
            ipt = workdir / "ipt"
            ipt.mkdir()
            cmd.extend(["--ipt-dir", ipt])

        for chk in (["-a", plugin] for plugin in plugins):
            cmd.extend(chk)

        subprocess.run(cmd, stdout=log)

        return d

    def copy(self, local):
        local = Path(local)
        self.injector.write_file(local, self.desktop / local.name)

    def run(self, cmd):
        self.injector.create_process(cmd)

    def __enter__(self):
        self.vm.restore()
        return self

    def __exit__(self, exc_type, exc_value, exc_traceback):
        self.vm.destroy()
        delete_vm_network(self.vm.vm_id, True, find_default_interface(),
                          self._dns)