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
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)