def process(self, task: Task) -> None:  # type: ignore
        mwdb = self.mwdb()
        object_type = task.headers["type"]
        mwdb_object: Optional[MWDBObject]

        if object_type == "sample":
            mwdb_object = self.process_sample(task, mwdb)
        else:
            mwdb_object = self.process_config(task, mwdb)

        if not mwdb_object:
            return

        # Add payload tags
        if task.has_payload("tags"):
            for tag in task.get_payload("tags"):
                if tag not in mwdb_object.tags:
                    self.log.info("[%s %s] Adding tag %s", object_type,
                                  mwdb_object.id, tag)
                    mwdb_object.add_tag(tag)

        # Add payload attributes
        if task.has_payload("attributes"):
            for key, values in task.get_payload("attributes").items():
                for value in values:
                    if value not in mwdb_object.metakeys.get(key, []):
                        self.log.info(
                            "[%s %s] Adding metakey %s: %s",
                            object_type,
                            mwdb_object.id,
                            key,
                            value,
                        )
                        mwdb_object.add_metakey(key, value)

        # Add payload comments
        comments = task.get_payload("comments") or task.get_payload(
            "additional_info")
        if comments:
            for comment in comments:
                self.log.info(
                    "[%s %s] Adding comment: %s",
                    object_type,
                    mwdb_object.id,
                    repr(comment),
                )
                mwdb_object.add_comment(comment)
    def process_config(self, task: Task, mwdb: MWDB) -> MWDBConfig:
        """
        Processing of Config task

        Clarification:
            sample -> parent -> config
            sample is original sample
            parent is parent of the config
            config is config

        :param mwdb: MWDB instance
        :return: MWDBConfig object
        """
        config_data = task.get_payload("config")
        family = (task.headers["family"] or config_data.get("family")
                  or config_data.get("type", "unknown"))

        if task.has_payload("sample"):
            sample = self._upload_file(task, mwdb, task.get_payload("sample"))
            if sample:
                self.log.info("[sample %s] Adding tag ripped:%s", sample.id,
                              family)
                sample.add_tag("ripped:" + family)
            else:
                self.log.warning("Couldn't upload original sample")
        else:
            sample = None

        if task.has_payload("parent"):
            parent = self._upload_file(task,
                                       mwdb,
                                       task.get_payload("parent"),
                                       parent=sample)
            if parent:
                self.log.info("[sample %s] Adding tag %s", parent.id, family)
                parent.add_tag(family)
            else:
                self.log.warning("Couldn't upload parent sample")
        else:
            parent = None

        config = self._upload_config(task,
                                     mwdb,
                                     family,
                                     config_data,
                                     parent=parent)
        return config
    def process_sample(self, task: Task, mwdb: MWDB) -> Optional[MWDBFile]:
        """
        Processing of Sample task

        :param mwdb: MWDB instance
        :return: MWDBFile object or None
        """
        if task.has_payload("parent"):
            parent = self._upload_file(task, mwdb, task.get_payload("parent"))
        else:
            parent = None

        if task.has_payload("sample"):
            sample = self._upload_file(task,
                                       mwdb,
                                       task.get_payload("sample"),
                                       parent=parent)
        else:
            sample = None

        return sample
Beispiel #4
0
    def process(self, task: Task):
        # Gather basic facts
        sample = task.get_resource("sample")
        magic_output = magic.from_buffer(sample.content)
        sha256sum = hashlib.sha256(sample.content).hexdigest()

        self.log.info(f"Running on: {socket.gethostname()}")
        self.log.info(f"Sample SHA256: {sha256sum}")
        self.log.info(f"Analysis UID: {self.analysis_uid}")

        # Timeout sanity check
        timeout = 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.update_vnc_info()

        # Get sample extension. If none set, fall back to exe/dll
        extension = task.headers.get("extension", "exe").lower()
        if '(DLL)' in magic_output:
            extension = 'dll'
        self.log.info("Running file as %s", extension)

        # Prepare sample file name
        file_name = 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)

        # workdir - configs, sample, etc.
        # outdir - analysis artifacts
        workdir, outdir = self._prepare_workdir()

        sample_path = os.path.join(workdir, file_name)
        sample.download_to_file(sample_path)

        # Try to come up with a start command for this file
        # or use the one provided by the sender
        cmd = self._get_start_command(extension, sample, sample_path)

        start_command = task.payload.get("start_command", cmd)
        if not start_command:
            self.log.error(
                "Unable to run malware sample. Could not generate any suitable"
                " command to run it.")
            return
        self.log.info("Start command: %s", start_command)

        # If task contains 'custom_hooks' override local defaults
        with open(os.path.join(workdir, "hooks.txt"), "wb") as hooks:
            if task.has_payload("custom_hooks"):
                custom_hooks = task.get_resource("custom_hooks")
                assert custom_hooks.content is not None
                hooks.write(custom_hooks.content)
            else:
                with open(os.path.join(ETC_DIR, "hooks.txt"),
                          "rb") as default_hooks:
                    hooks.write(default_hooks.read())

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

        max_attempts = 3
        for i in range(max_attempts):
            try:
                self.log.info(
                    f"Trying to analyze sample (attempt {i + 1}/{max_attempts})"
                )
                info = self.analyze_sample(sample_path, workdir, outdir,
                                           start_command, timeout)
                metadata.update(info)
                break
            except Exception:
                self.log.exception("Analysis attempt failed. Retrying...")
        else:
            self.log.error(f"Giving up after {max_attempts} failures...")
            return

        self.log.info("Analysis done. Collecting artifacts...")

        # Make sure dumps have a reasonable size
        self.crop_dumps(os.path.join(outdir, 'dumps'),
                        os.path.join(outdir, 'dumps.zip'))

        # Compress IPT traces, they're quite large however they compress well
        self.compress_ipt(os.path.join(outdir, 'ipt'),
                          os.path.join(outdir, 'ipt.zip'))

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

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

        quality = task.headers.get("quality", "high")
        self.send_analysis(sample, outdir, metadata, quality)