Пример #1
0
 def add_family_detections(self, file_info, cape_names):
     for cape_name in cape_names:
         if cape_name != "UPX" and cape_name:
             if processing_conf.detections.yara:
                 add_family_detection(self.results, cape_name, "Yara",
                                      file_info["sha256"])
         if file_info.get("pid"):
             self.detect2pid(str(file_info["pid"]), cape_name)
Пример #2
0
    def run(self):
        """Run analysis.
        @return: structured results.
        """
        self.key = "procmemory"
        results = []
        do_strings = self.options.get("strings", False)
        nulltermonly = self.options.get("nullterminated_only", True)
        minchars = str(self.options.get("minchars", 5)).encode()

        if os.path.exists(self.pmemory_path):
            for dmp in os.listdir(self.pmemory_path):
                # if we're re-processing this task, this means if zips are enabled, we won't do any reprocessing on the
                # process dumps (only matters for now for Yara)
                if not dmp.endswith(".dmp"):
                    continue

                dmp_path = os.path.join(self.pmemory_path, dmp)
                if os.path.getsize(dmp_path) == 0:
                    continue

                dmp_file = File(dmp_path)
                process_name = ""
                process_path = ""
                process_id = int(
                    os.path.splitext(os.path.basename(dmp_path))[0])
                for process in self.results.get("behavior",
                                                {}).get("processes", []):
                    if process_id == process["process_id"]:
                        process_name = process["process_name"]
                        process_path = process["module_path"]

                procdump = ProcDump(dmp_path, pretty=True)

                proc = dict(
                    path=dmp_path,
                    sha256=dmp_file.get_sha256(),
                    pid=process_id,
                    name=process_name,
                    proc_path=process_path,
                    yara=dmp_file.get_yara(category="memory"),
                    cape_yara=dmp_file.get_yara(category="CAPE"),
                    address_space=procdump.pretty_print(),
                )

                for hit in proc["cape_yara"]:
                    hit["memblocks"] = {}
                    for item in hit["addresses"]:
                        memblock = self.get_yara_memblock(
                            proc["address_space"], hit["addresses"][item])
                        if memblock:
                            hit["memblocks"][item] = memblock

                # if self.options.get("extract_pe", False)
                extracted_pes = self.get_procmemory_pe(proc)

                endlimit = b"" if HAVE_RE2 else b"8192"
                if do_strings:
                    if nulltermonly:
                        apat = b"([\x20-\x7e]{" + minchars + b"," + endlimit + b"})\x00"
                        upat = b"((?:[\x20-\x7e][\x00]){" + minchars + b"," + endlimit + b"})\x00\x00"
                    else:
                        apat = b"[\x20-\x7e]{" + minchars + b"," + endlimit + b"}"
                        upat = b"(?:[\x20-\x7e][\x00]){" + minchars + b"," + endlimit + b"}"

                    matchdict = procdump.search(apat, all=True)
                    strings = matchdict["matches"]
                    matchdict = procdump.search(upat, all=True)
                    ustrings = matchdict["matches"]
                    for ws in ustrings:
                        strings.append(ws.decode("utf-16le").encode())

                    proc["strings_path"] = f"{dmp_path}.strings"
                    proc["extracted_pe"] = extracted_pes
                    with open(proc["strings_path"], "wb") as f:
                        f.write(b"\n".join(strings))
                procdump.close()
                results.append(proc)

                if processing_conf.detections.yara:
                    cape_name = cape_name_from_yara(proc, process_id,
                                                    self.results)
                    if cape_name:
                        add_family_detection(self.results, cape_name, "Yara",
                                             proc["sha256"])
        return results
Пример #3
0
    def run(self):
        """Run Suricata.
        @return: hash with alerts
        """
        self.key = "suricata"
        # General
        SURICATA_CONF = self.options.get("conf")
        SURICATA_EVE_LOG = self.options.get("evelog")
        SURICATA_ALERT_LOG = self.options.get("alertlog")
        SURICATA_TLS_LOG = self.options.get("tlslog")
        SURICATA_HTTP_LOG = self.options.get("httplog")
        SURICATA_SSH_LOG = self.options.get("sshlog")
        SURICATA_DNS_LOG = self.options.get("dnslog")
        SURICATA_FILE_LOG = self.options.get("fileslog")
        SURICATA_FILES_DIR = self.options.get("filesdir")
        SURICATA_RUNMODE = self.options.get("runmode")
        SURICATA_FILE_BUFFER = self.options.get("buffer", 8192)
        Z7_PATH = self.options.get("7zbin")
        FILES_ZIP_PASS = self.options.get("zippass")

        # Socket
        SURICATA_SOCKET_PATH = self.options.get("socket_file")

        # Command Line
        SURICATA_BIN = self.options.get("bin")

        suricata = {
            "alerts": [],
            "tls": [],
            "perf": [],
            "files": [],
            "http": [],
            "dns": [],
            "ssh": [],
            "fileinfo": [],
            "eve_log_full_path": None,
            "alert_log_full_path": None,
            "tls_log_full_path": None,
            "http_log_full_path": None,
            "file_log_full_path": None,
            "ssh_log_full_path": None,
            "dns_log_full_path": None,
        }

        tls_items = ("fingerprint", "issuerdn", "version", "subject", "sni",
                     "ja3", "ja3s", "serial", "notbefore", "notafter")

        SURICATA_ALERT_LOG_FULL_PATH = f"{self.logs_path}/{SURICATA_ALERT_LOG}"
        SURICATA_TLS_LOG_FULL_PATH = f"{self.logs_path}/{SURICATA_TLS_LOG}"
        SURICATA_HTTP_LOG_FULL_PATH = f"{self.logs_path}/{SURICATA_HTTP_LOG}"
        SURICATA_SSH_LOG_FULL_PATH = f"{self.logs_path}/{SURICATA_SSH_LOG}"
        SURICATA_DNS_LOG_FULL_PATH = f"{self.logs_path}/{SURICATA_DNS_LOG}"
        SURICATA_EVE_LOG_FULL_PATH = f"{self.logs_path}/{SURICATA_EVE_LOG}"
        SURICATA_FILE_LOG_FULL_PATH = f"{self.logs_path}/{SURICATA_FILE_LOG}"
        SURICATA_FILES_DIR_FULL_PATH = f"{self.logs_path}/{SURICATA_FILES_DIR}"

        separate_log_paths = (
            ("alert_log_full_path", SURICATA_ALERT_LOG_FULL_PATH),
            ("tls_log_full_path", SURICATA_TLS_LOG_FULL_PATH),
            ("http_log_full_path", SURICATA_HTTP_LOG_FULL_PATH),
            ("ssh_log_full_path", SURICATA_SSH_LOG_FULL_PATH),
            ("dns_log_full_path", SURICATA_DNS_LOG_FULL_PATH),
        )

        # handle reprocessing
        all_log_paths = [x[1] for x in separate_log_paths] + [
            SURICATA_EVE_LOG_FULL_PATH, SURICATA_FILE_LOG_FULL_PATH
        ]
        for log_path in all_log_paths:
            if os.path.exists(log_path):
                try:
                    os.unlink(log_path)
                except Exception:
                    pass
        if os.path.isdir(SURICATA_FILES_DIR_FULL_PATH):
            try:
                shutil.rmtree(SURICATA_FILES_DIR_FULL_PATH, ignore_errors=True)
            except Exception:
                pass

        if not os.path.exists(SURICATA_CONF):
            log.warning("Unable to Run Suricata: Conf File %s does not exist",
                        SURICATA_CONF)
            return suricata
        if not os.path.exists(self.pcap_path):
            log.warning("Unable to Run Suricata: Pcap file %s does not exist",
                        self.pcap_path)
            return suricata

        # Add to this if you wish to ignore any SIDs for the suricata alert logs
        # Useful for ignoring SIDs without disabling them. Ex: surpress an alert for
        # a SID which is a dependent of another. (Bad TCP data for HTTP(S) alert)
        sid_blacklist = (
            # SURICATA FRAG IPv6 Fragmentation overlap
            2200074,
            # ET INFO InetSim Response from External Source Possible SinkHole
            2017363,
            # SURICATA UDPv4 invalid checksum
            2200075,
            # ET POLICY SSLv3 outbound connection from client vulnerable to POODLE attack
            2019416,
        )

        if SURICATA_RUNMODE == "socket":
            try:
                # from suricatasc import SuricataSC
                from lib.cuckoo.common.suricatasc import SuricataSC
            except Exception as e:
                log.warning("Failed to import suricatasc lib: %s", e)
                return suricata

            loopcnt = 0
            maxloops = 24
            loopsleep = 5

            args = {
                "filename": self.pcap_path,
                "output-dir": self.logs_path,
            }

            suris = SuricataSC(SURICATA_SOCKET_PATH)
            try:
                suris.connect()
                suris.send_command("pcap-file", args)
            except Exception as e:
                log.warning(
                    "Failed to connect to socket and send command %s: %s",
                    SURICATA_SOCKET_PATH, e)
                return suricata
            while loopcnt < maxloops:
                try:
                    pcap_flist = suris.send_command("pcap-file-list")
                    current_pcap = suris.send_command("pcap-current")
                    log.debug("pcapfile list: %s current pcap: %s", pcap_flist,
                              current_pcap)

                    if self.pcap_path not in pcap_flist["message"][
                            "files"] and current_pcap[
                                "message"] != self.pcap_path:
                        log.debug(
                            "Pcap not in list and not current pcap lets assume it's processed"
                        )
                        break
                    else:
                        loopcnt += 1
                        time.sleep(loopsleep)
                except Exception as e:
                    log.warning(
                        "Failed to get pcap status breaking out of loop: %s",
                        e)
                    break

            if loopcnt == maxloops:
                log.warning(
                    "Loop timeout of %d sec occurred waiting for file %s to finish processing",
                    maxloops * loopsleep, current_pcap)
                return suricata
        elif SURICATA_RUNMODE == "cli":
            if not os.path.exists(SURICATA_BIN):
                log.warning(
                    "Unable to Run Suricata: Bin File %s does not exist",
                    SURICATA_CONF)
                return suricata["alerts"]
            cmd = f"{SURICATA_BIN} -c {SURICATA_CONF} -k none -l {self.logs_path} -r {self.pcap_path}"
            ret, _, stderr = self.cmd_wrapper(cmd)
            if ret != 0:
                log.warning(
                    "Suricata returned a Exit Value Other than Zero: %s",
                    stderr)
                return suricata

        else:
            log.warning("Unknown Suricata Runmode")
            return suricata

        datalist = []
        if os.path.exists(SURICATA_EVE_LOG_FULL_PATH):
            suricata["eve_log_full_path"] = SURICATA_EVE_LOG_FULL_PATH
            with open(SURICATA_EVE_LOG_FULL_PATH, "rb") as eve_log:
                datalist.append(eve_log.read())
        else:
            for path in separate_log_paths:
                if os.path.exists(path[1]):
                    suricata[path[0]] = path[1]
                    with open(path[1], "rb") as the_log:
                        datalist.append(the_log.read())

        if not datalist:
            log.warning("Suricata: Failed to find usable Suricata log file")

        parsed_files = []
        for data in datalist:
            for line in data.splitlines():
                try:
                    parsed = json.loads(line)
                except Exception:
                    log.warning("Suricata: Failed to parse line %s as json",
                                line)
                    continue

                if "event_type" in parsed:
                    if (parsed["event_type"] == "alert"
                            and parsed["alert"]["signature_id"]
                            not in sid_blacklist and not parsed["alert"]
                        ["signature"].startswith("SURICATA STREAM")):
                        alog = {
                            "gid": parsed["alert"]["gid"] or "None",
                            "rev": parsed["alert"]["rev"] or "None",
                            "severity": parsed["alert"]["severity"] or "None",
                            "sid": parsed["alert"]["signature_id"],
                        }
                        try:
                            alog["srcport"] = parsed["src_port"]
                        except Exception:
                            alog["srcport"] = "None"
                        alog["srcip"] = parsed["src_ip"]
                        try:
                            alog["dstport"] = parsed["dest_port"]
                        except Exception:
                            alog["dstport"] = "None"
                        alog["dstip"] = parsed["dest_ip"]
                        alog["protocol"] = parsed["proto"]
                        alog["timestamp"] = parsed["timestamp"].replace(
                            "T", " ")
                        alog[
                            "category"] = parsed["alert"]["category"] or "None"
                        alog["signature"] = parsed["alert"]["signature"]
                        suricata["alerts"].append(alog)

                    elif parsed["event_type"] == "http":
                        hlog = {
                            "srcport": parsed["src_port"],
                            "srcip": parsed["src_ip"],
                            "dstport": parsed["dest_port"],
                            "dstip": parsed["dest_ip"],
                            "timestamp": parsed["timestamp"].replace("T", " "),
                        }
                        keyword = ("uri", "length", "hostname", "status",
                                   "http_method", "contenttype", "ua",
                                   "referrer")
                        keyword_suri = (
                            "url",
                            "length",
                            "hostname",
                            "status",
                            "http_method",
                            "http_content_type",
                            "http_user_agent",
                            "http_refer",
                        )
                        for key, key_s in zip(keyword, keyword_suri):
                            try:
                                hlog[key] = parsed["http"].get(key_s, "None")
                            except Exception:
                                hlog[key] = "None"
                        suricata["http"].append(hlog)

                    elif parsed["event_type"] == "tls":
                        tlog = {
                            "srcport": parsed["src_port"],
                            "srcip": parsed["src_ip"],
                            "dstport": parsed["dest_port"],
                            "dstip": parsed["dest_ip"],
                            "timestamp": parsed["timestamp"].replace("T", " "),
                        }
                        for key in tls_items:
                            if key in parsed["tls"]:
                                tlog[key] = parsed["tls"][key]
                        suricata["tls"].append(tlog)

                    elif parsed["event_type"] == "ssh":
                        suricata["ssh"].append(parsed)
                    elif parsed["event_type"] == "dns":
                        suricata["dns"].append(parsed)
                    elif parsed["event_type"] == "fileinfo":
                        flog = {
                            "http_host":
                            parsed.get("http", {}).get("hostname", ""),
                            "http_uri":
                            parsed.get("http", {}).get("url", ""),
                            "http_referer":
                            parsed.get("http", {}).get("referer", ""),
                            "http_user_agent":
                            parsed.get("http", {}).get("http_user_agent", ""),
                            "protocol":
                            parsed.get("proto", ""),
                            "magic":
                            parsed.get("fileinfo", {}).get("magic", ""),
                            "size":
                            parsed.get("fileinfo", {}).get("size", ""),
                            "stored":
                            parsed.get("fileinfo", {}).get("stored", ""),
                            "sha256":
                            parsed.get("fileinfo", {}).get("sha256", ""),
                            "md5":
                            parsed.get("fileinfo", {}).get("md5", ""),
                            "filename":
                            parsed.get("fileinfo", {}).get("filename", ""),
                            "file_info": {},
                        }
                        if "/" in flog["filename"]:
                            flog["filename"] = flog["filename"].rsplit("/",
                                                                       1)[-1]
                        parsed_files.append(flog)

        if parsed_files:
            for sfile in parsed_files:
                if sfile.get("stored", False):
                    filename = sfile["sha256"]
                    src_file = f"{SURICATA_FILES_DIR_FULL_PATH}/{filename[0:2]}/{filename}"
                    dst_file = f"{SURICATA_FILES_DIR_FULL_PATH}/{filename}"
                    if os.path.exists(src_file):
                        try:
                            shutil.move(src_file, dst_file)
                        except OSError as e:
                            log.warning("Unable to move suricata file: %s", e)
                            break
                        file_info, pefile_object = File(
                            file_path=dst_file).get_all()
                        if pefile_object:
                            self.results.setdefault("pefiles", {})
                            self.results["pefiles"].setdefault(
                                file_info["sha256"], pefile_object)
                        try:
                            with open(file_info["path"], "r") as drop_open:
                                filedata = drop_open.read(
                                    SURICATA_FILE_BUFFER + 1)
                            file_info[
                                "data"] = convert_to_printable_and_truncate(
                                    filedata, SURICATA_FILE_BUFFER)
                        except UnicodeDecodeError:
                            pass
                        if file_info:
                            sfile["file_info"] = file_info
                    suricata["files"].append(sfile)

            if HAVE_ORJSON:
                with open(SURICATA_FILE_LOG_FULL_PATH, "wb") as drop_log:
                    drop_log.write(
                        orjson.dumps(suricata["files"],
                                     option=orjson.OPT_INDENT_2,
                                     default=self.json_default)
                    )  # orjson.OPT_SORT_KEYS |
            else:
                with open(SURICATA_FILE_LOG_FULL_PATH, "w") as drop_log:
                    json.dump(suricata["files"], drop_log, indent=4)

            # Cleanup file subdirectories left behind by messy Suricata
            for d in (dirpath for dirpath, dirnames, filenames in os.walk(
                    SURICATA_FILES_DIR_FULL_PATH)
                      if len(dirnames) == 0 == len(filenames)):
                try:
                    shutil.rmtree(d)
                except OSError as e:
                    log.warning(
                        "Unable to delete suricata file subdirectories: %s", e)

        if SURICATA_FILES_DIR_FULL_PATH and os.path.exists(
                SURICATA_FILES_DIR_FULL_PATH) and Z7_PATH and os.path.exists(
                    Z7_PATH):
            # /usr/bin/7z a -pinfected -y files.zip files-json.log files
            cmdstr = f"cd {self.logs_path} && {Z7_PATH} a -p{FILES_ZIP_PASS} -y files.zip {SURICATA_FILE_LOG} {SURICATA_FILES_DIR}"
            ret, _, stderr = self.cmd_wrapper(cmdstr)
            if ret > 1:
                log.warning(
                    "Suricata: Failed to create %s/files.zip - Error %d",
                    self.logs_path, ret)

        suricata["alerts"] = self.sort_by_timestamp(suricata["alerts"])
        suricata["http"] = self.sort_by_timestamp(suricata["http"])
        suricata["tls"] = self.sort_by_timestamp(suricata["tls"])

        if processing_cfg.detections.suricata:
            for alert in suricata.get("alerts", []):
                if alert.get("signature", "").startswith(et_categories):
                    family = get_suricata_family(alert["signature"])
                    if family:
                        add_family_detection(self.results, family, "Suricata",
                                             alert["signature"])

        return suricata
Пример #4
0
def vt_lookup(category: str, target: str, results: dict = {}, on_demand: bool = False):
    if not processing_conf.virustotal.enabled or processing_conf.virustotal.get("on_demand", False) and not on_demand:
        return {}
    if category not in ("file", "url"):
        return {"error": True, "msg": "VT category isn't supported"}

    if category == "file":
        if not do_file_lookup:
            return {"error": True, "msg": "VT File lookup disabled in processing.conf"}
        if not os.path.exists(target) and len(target) != 64:
            return {"error": True, "msg": "File doesn't exist"}

        sha256 = target if len(target) == 64 else File(target).get_sha256()
        url = VIRUSTOTAL_FILE_URL.format(id=sha256)

    elif category == "url":
        if not do_url_lookup:
            return {"error": True, "msg": "VT URL lookup disabled in processing.conf"}
        if urlscrub:
            urlscrub_compiled_re = None
            try:
                urlscrub_compiled_re = re.compile(urlscrub)
            except Exception as e:
                log.error(f"Failed to compile urlscrub regex: {e}")
                return {}
            try:
                target = re.sub(urlscrub_compiled_re, "", target)
            except Exception as e:
                return {"error": True, "msg": f"Failed to scrub url: {e}"}

        # normalize the URL the way VT appears to
        if not target.lower().startswith(("http://", "https://")):
            target = f"http://{target}"
        slashsplit = target.split("/")
        slashsplit[0] = slashsplit[0].lower()
        slashsplit[2] = slashsplit[2].lower()
        if len(slashsplit) == 3:
            slashsplit.append("")
        target = "/".join(slashsplit)

        sha256 = hashlib.sha256(target.encode()).hexdigest()
        url = VIRUSTOTAL_URL_URL.format(id=target)

    try:
        r = requests.get(url, headers=headers, verify=True, timeout=timeout)
        if not r.ok:
            return {"error": True, "msg": f"Unable to complete connection to VirusTotal. Status code: {r.status_code}"}
        vt_response = r.json()
        engines = vt_response.get("data", {}).get("attributes", {}).get("last_analysis_results", {})
        if not engines:
            return {}
        virustotal = {
            "names": vt_response.get("data", {}).get("attributes", {}).get("names"),
            "scan_id": vt_response.get("data", {}).get("id"),
            "md5": vt_response.get("data", {}).get("attributes", {}).get("md5"),
            "sha1": vt_response.get("data", {}).get("attributes", {}).get("sha1"),
            "sha256": vt_response.get("data", {}).get("attributes", {}).get("sha256"),
            "tlsh": vt_response.get("data", {}).get("attributes", {}).get("tlsh"),
            "positive": vt_response.get("data", {}).get("attributes", {}).get("last_analysis_stats", {}).get("malicious"),
            "total": len(engines.keys()),
            "permalink": vt_response.get("data", {}).get("links", {}).get("self"),
        }
        if remove_empty:
            virustotal["scans"] = {engine.replace(".", "_"): block for engine, block in engines.items() if block["result"]}
        else:
            virustotal["scans"] = {engine.replace(".", "_"): block for engine, block in engines.items()}

        virustotal["resource"] = sha256
        virustotal["results"] = []
        detectnames = []
        for engine, block in engines.items():
            virustotal["results"] += [{"vendor": engine.replace(".", "_"), "sig": block["result"]}]
            if block["result"] and "Trojan.Heur." not in block["result"]:
                # weight Microsoft's detection, they seem to be more accurate than the rest
                if engine == "Microsoft":
                    detectnames.append(block["result"])
                detectnames.append(block["result"])

        virustotal["detection"] = get_vt_consensus(detectnames)
        if virustotal.get("detection", False) and results:
            add_family_detection(results, virustotal["detection"], "VirusTotal", virustotal["sha256"])
        if virustotal.get("positives", False) and virustotal.get("total", False):
            virustotal["summary"] = f"{virustotal['positives']}/{virustotal['total']}"

        return virustotal
    except requests.exceptions.RequestException as e:
        return {
            "error": True,
            "msg": f"Unable to complete connection to VirusTotal: {e}",
        }

    return {}
Пример #5
0
    def process_file(self, file_path, append_file, metadata=None):
        """Process file.
        @return: file_info
        """

        if metadata is None:
            metadata = {}
        cape_name = ""
        type_string = ""

        if not os.path.exists(file_path):
            return

        file_info, pefile_object = File(file_path,
                                        metadata.get("metadata",
                                                     "")).get_all()
        cape_names = set()

        if pefile_object:
            self.results.setdefault("pefiles",
                                    {}).setdefault(file_info["sha256"],
                                                   pefile_object)

        if file_info.get("clamav") and processing_conf.detections.clamav:
            clamav_detection = get_clamav_consensus(file_info["clamav"])
            if clamav_detection:
                add_family_detection(self.results, clamav_detection, "ClamAV",
                                     file_info["sha256"])

        # should we use dropped path here?
        static_file_info(
            file_info,
            file_path,
            str(self.task["id"]),
            self.task.get("package", ""),
            self.task.get("options", ""),
            self.self_extracted,
            self.results,
        )

        # Get the file data
        with open(file_info["path"], "rb") as file_open:
            file_data = file_open.read()

        if metadata.get("pids", False):
            file_info["pid"] = metadata["pids"][0] if len(
                metadata["pids"]) == 1 else ",".join(metadata["pids"])

        metastrings = metadata.get("metadata", "").split(";?")
        if len(metastrings) > 2:
            file_info["process_path"] = metastrings[1]
            file_info["process_name"] = metastrings[1].rsplit("\\", 1)[-1]
        if len(metastrings) > 3:
            file_info["module_path"] = metastrings[2]

        file_info["cape_type_code"] = 0
        file_info["cape_type"] = ""
        if metastrings and metastrings[0] and metastrings[0].isdigit():
            file_info["cape_type_code"] = int(metastrings[0])

            if file_info["cape_type_code"] == TYPE_STRING:
                if len(metastrings) > 4:
                    type_string = metastrings[3]

            elif file_info["cape_type_code"] == COMPRESSION:
                file_info["cape_type"] = "Decompressed PE Image"

            elif file_info["cape_type_code"] in inject_map:
                file_info["cape_type"] = inject_map[
                    file_info["cape_type_code"]]
                if len(metastrings) > 4:
                    file_info["target_path"] = metastrings[3]
                    file_info["target_process"] = metastrings[3].rsplit(
                        "\\", 1)[-1]
                    file_info["target_pid"] = metastrings[4]

            elif file_info["cape_type_code"] in unpack_map:
                file_info["cape_type"] = unpack_map[
                    file_info["cape_type_code"]]
                if len(metastrings) > 4:
                    file_info["virtual_address"] = metastrings[3]

            type_strings = file_info["type"].split()

            if type_strings[0] in ("PE32+", "PE32"):
                file_info["cape_type"] += pe_map[type_strings[0]]
                if type_strings[2] == ("(DLL)"):
                    file_info["cape_type"] += "DLL"
                else:
                    file_info["cape_type"] += "executable"

            if file_info["cape_type_code"] in code_mapping:
                file_info["cape_type"] = code_mapping[
                    file_info["cape_type_code"]]
                type_strings = file_info["type"].split()
                if type_strings[0] in ("PE32+", "PE32"):
                    file_info["cape_type"] += pe_map[type_strings[0]]
                    if type_strings[2] == ("(DLL)"):
                        file_info["cape_type"] += "DLL"
                    else:
                        file_info["cape_type"] += "executable"
                if file_info["cape_type_code"] in name_mapping:
                    cape_name = name_mapping[file_info["cape_type_code"]]
                append_file = True

            # PlugX
            elif file_info["cape_type_code"] == PLUGX_CONFIG:
                file_info["cape_type"] = "PlugX Config"
                if plugx_parser:
                    plugx_config = plugx_parser.parse_config(
                        file_data, len(file_data))
                    if plugx_config:
                        cape_name = "PlugX"
                        self.update_cape_configs(cape_name, plugx_config)
                        cape_names.add(cape_name)
                    else:
                        log.error(
                            "CAPE: PlugX config parsing failure - size many not be handled"
                        )
                    append_file = False

            # Attempt to decrypt script dump
            elif file_info["cape_type_code"] == SCRIPT_DUMP:
                data = file_data.decode("utf-16").replace("\x00", "")
                cape_name = "ScriptDump"
                malwareconfig_loaded = False
                try:
                    malwareconfig_parsers = os.path.join(
                        CUCKOO_ROOT, "modules", "processing", "parsers",
                        "CAPE")
                    file, pathname, description = imp.find_module(
                        cape_name, [malwareconfig_parsers])
                    module = imp.load_module(cape_name, file, pathname,
                                             description)
                    malwareconfig_loaded = True
                    log.debug("CAPE: Imported parser %s", cape_name)
                except ImportError:
                    log.debug("CAPE: parser: No module named %s", cape_name)
                if malwareconfig_loaded:
                    try:
                        script_data = module.config(self, data)
                        if script_data and "more_eggs" in script_data["type"]:
                            bindata = script_data["data"]
                            sha256 = hashlib.sha256(bindata).hexdigest()
                            filepath = os.path.join(self.CAPE_path, sha256)
                            if "text" in script_data["datatype"]:
                                file_info["cape_type"] = "MoreEggsJS"
                            elif "binary" in script_data["datatype"]:
                                file_info["cape_type"] = "MoreEggsBin"
                            with open(filepath, "w") as cfile:
                                cfile.write(bindata)
                                self.script_dump_files.append(filepath)
                        else:
                            file_info["cape_type"] = "Script Dump"
                            log.info(
                                "CAPE: Script Dump does not contain known encrypted payload"
                            )
                    except Exception as e:
                        log.error(
                            "CAPE: malwareconfig parsing error with %s: %s",
                            cape_name, e)
                append_file = True

            # More_Eggs
            elif file_info["cape_type_code"] == MOREEGGSJS_PAYLOAD:
                file_info["cape_type"] = "More Eggs JS Payload"
                cape_name = "MoreEggs"
                append_file = True

        # Process CAPE Yara hits

        # Prefilter extracted data + beauty is better than oneliner:
        all_files = []
        for extracted_file in file_info.get("extracted_files", []):
            yara_hits = extracted_file["cape_yara"]
            if not yara_hits:
                continue
            if extracted_file.get("data", b""):
                extracted_file_data = make_bytes(extracted_file["data"])
            else:
                with open(extracted_file["path"], "rb") as fil:
                    extracted_file_data = fil.read()
            for yara in yara_hits:
                all_files.append((
                    f"[{extracted_file.get('sha256', '')}]{file_info['path']}",
                    extracted_file_data,
                    yara,
                ))

        for yara in file_info["cape_yara"]:
            all_files.append((file_info["path"], file_data, yara))

        executed_config_parsers = collections.defaultdict(set)
        for tmp_path, tmp_data, hit in all_files:
            # Check for a payload or config hit
            try:
                if File.yara_hit_provides_detection(hit):
                    file_info["cape_type"] = hit["meta"]["cape_type"]
                    cape_name = File.get_cape_name_from_yara_hit(hit)
                    cape_names.add(cape_name)
            except Exception as e:
                print(f"Cape type error: {e}")
            type_strings = file_info["type"].split()
            if "-bit" not in file_info["cape_type"]:
                if type_strings[0] in ("PE32+", "PE32"):
                    file_info["cape_type"] += pe_map[type_strings[0]]
                    file_info["cape_type"] += "DLL" if type_strings[2] == (
                        "(DLL)") else "executable"

            if cape_name and cape_name not in executed_config_parsers[tmp_path]:
                tmp_config = static_config_parsers(cape_name, tmp_path,
                                                   tmp_data)
                self.update_cape_configs(cape_name, tmp_config)
                executed_config_parsers[tmp_path].add(cape_name)

        if type_string:
            log.info("CAPE: type_string: %s", type_string)
            tmp_cape_name = File.get_cape_name_from_cape_type(type_string)
            if tmp_cape_name and tmp_cape_name not in executed_config_parsers:
                tmp_config = static_config_parsers(tmp_cape_name,
                                                   file_info["path"],
                                                   file_data)
                if tmp_config:
                    cape_name = tmp_cape_name
                    cape_names.add(cape_name)
                    log.info("CAPE: config returned for: %s", cape_name)
                    self.update_cape_configs(cape_name, tmp_config)

        self.add_family_detections(file_info, cape_names)

        # Remove duplicate payloads from web ui
        for cape_file in self.cape["payloads"] or []:
            if file_info["size"] == cape_file["size"]:
                if HAVE_PYDEEP:
                    ssdeep_grade = pydeep.compare(file_info["ssdeep"].encode(),
                                                  cape_file["ssdeep"].encode())
                    if ssdeep_grade >= ssdeep_threshold:
                        log.debug(
                            "CAPE duplicate output file skipped: ssdeep grade %d, threshold %d",
                            ssdeep_grade, ssdeep_threshold)
                        append_file = False
                if file_info.get("entrypoint") and file_info.get(
                        "ep_bytes") and cape_file.get("entrypoint"):
                    if (file_info["entrypoint"] == cape_file["entrypoint"]
                            and file_info["cape_type_code"]
                            == cape_file["cape_type_code"] and
                            file_info["ep_bytes"] == cape_file["ep_bytes"]):
                        log.debug(
                            "CAPE duplicate output file skipped: matching entrypoint"
                        )
                        append_file = False

        if append_file:
            if HAVE_FLARE_CAPA:
                pretime = timeit.default_timer()
                capa_details = flare_capa_details(file_path, "cape")
                if capa_details:
                    file_info["flare_capa"] = capa_details
                self.add_statistic_tmp("flare_capa", "time", pretime=pretime)
            self.cape["payloads"].append(file_info)
Пример #6
0
    def run(self, test_signature: str = False):
        """Run evented signatures.
        test_signature: signature name, Ex: cape_detected_threat, to test unique signature
        """

        # This will contain all the matched signatures.
        matched = []
        stats = {}

        if test_signature:
            self.evented_list = next(
                (sig
                 for sig in self.evented_list if sig.name == test_signature),
                [])
            self.non_evented_list = next((sig for sig in self.non_evented_list
                                          if sig.name == test_signature), [])

        if self.evented_list and "behavior" in self.results:
            log.debug("Running %d evented signatures", len(self.evented_list))
            for sig in self.evented_list:
                stats[sig.name] = 0
                if sig == self.evented_list[-1]:
                    log.debug("\t `-- %s", sig.name)
                else:
                    log.debug("\t |-- %s", sig.name)

            # Iterate calls and tell interested signatures about them.
            for proc in self.results["behavior"]["processes"]:
                process_name = proc["process_name"]
                process_id = proc["process_id"]
                calls = proc.get("calls", [])
                for idx, call in enumerate(calls):
                    api = call.get("api")
                    sigs = self.api_sigs.get(api)
                    if sigs is None:
                        # Build interested signatures
                        cat = call.get("category")
                        sigs = self.call_always.union(
                            self.call_for_api.get(api, set()),
                            self.call_for_cat.get(cat, set()),
                            self.call_for_processname.get(process_name, set()),
                        )
                    for sig in sigs:
                        # Setting signature attributes per call
                        sig.cid = idx
                        sig.call = call
                        sig.pid = process_id

                        if sig.matched:
                            continue
                        try:
                            pretime = timeit.default_timer()
                            result = sig.on_call(call, proc)
                            timediff = timeit.default_timer() - pretime
                            stats[sig.name] += timediff
                        except NotImplementedError:
                            result = False
                        except Exception as e:
                            log.exception("Failed to run signature %s: %s",
                                          sig.name, e)
                            result = False

                        if result:
                            sig.matched = True

            # Call the stop method on all remaining instances.
            for sig in self.evented_list:
                if sig.matched:
                    continue
                try:
                    pretime = timeit.default_timer()
                    result = sig.on_complete()
                    timediff = timeit.default_timer() - pretime
                    stats[sig.name] += timediff
                except NotImplementedError:
                    continue
                except Exception as e:
                    log.exception(
                        'Failed run on_complete() method for signature "%s": %s',
                        sig.name, e)
                    continue
                else:
                    if result and not sig.matched:
                        if hasattr(sig, "ttps"):
                            [
                                self.ttps.append({
                                    "ttp": ttp,
                                    "signature": sig.name
                                }) for ttp in sig.ttps
                            ]

        # Link this into the results already at this point, so non-evented signatures can use it
        self.results["signatures"] = matched

        # Add in statistics for evented signatures that took at least some time
        for key, value in stats.items():
            if value:
                self.results["statistics"]["signatures"].append({
                    "name":
                    key,
                    "time":
                    round(timediff, 3)
                })
        # Compat loop for old-style (non evented) signatures.
        if self.non_evented_list:
            if hasattr(self.non_evented_list, "sort"):
                self.non_evented_list.sort(key=lambda sig: sig.order)
            else:
                # for testing single signature with process.py
                self.non_evented_list = [self.non_evented_list]
            log.debug("Running non-evented signatures")

            for signature in self.non_evented_list:
                if (not signature.filter_analysistypes
                        or self.results.get("target", {}).get("category")
                        in signature.filter_analysistypes):
                    match = self.process(signature)
                    # If the signature is matched, add it to the list.
                    if match and not signature.matched:
                        if hasattr(signature, "ttps"):
                            [
                                self.ttps.append({
                                    "ttp": ttp,
                                    "signature": signature.name
                                }) for ttp in signature.ttps
                            ]
                        signature.matched = True

        for signature in self.signatures:
            if not signature.matched:
                continue
            log.debug('Analysis matched signature "%s"', signature.name)
            signature.matched = True
            matched.append(signature.as_result())

        # Sort the matched signatures by their severity level.
        matched.sort(key=lambda key: key["severity"])

        # Tweak later as needed
        malscore = 0.0
        for match in matched:
            if match["severity"] == 1:
                malscore += match["weight"] * 0.5 * (match["confidence"] /
                                                     100.0)
            else:
                malscore += match["weight"] * (match["severity"] - 1) * (
                    match["confidence"] / 100.0)
        if malscore > 10.0:
            malscore = 10.0
        if malscore < 0.0:
            malscore = 0.0

        self.results["malscore"] = malscore
        self.results["ttps"] = self.ttps

        # Make a best effort detection of malware family name (can be updated later by re-processing the analysis)
        if (self.results.get("malfamily_tag", "") != "Yara"
                and self.cfg_processing.detections.enabled
                and self.cfg_processing.detections.behavior):
            for match in matched:
                if match.get("families"):
                    add_family_detection(self.results, match["families"][0],
                                         "Behavior", "")
                    break