icedid/peloader/icedid_peloader.py (122 lines of code) (raw):

import collections import logging import math import malduck from malduck.extractor import Extractor from malduck.procmem import ProcessMemory from malduck.yara import YaraRuleMatch from ...utils import Config, get_rule_metadata log = logging.getLogger(__name__) __author__ = "myrtus0x0" __version__ = "1.0.1" class IcedIDPELoader(Extractor): """ IcedID PELoader Config Extractor """ family = "icedid" yara_rules = ("icedid_peloader",) key = None @staticmethod def entropy(data: bytes) -> float: e = 0 counter = collections.Counter(data) length = len(data) for count in counter.values(): # count is always > 0 p_x = count / length e += -p_x * math.log2(p_x) return e @staticmethod def strip_non_ascii(byte_str: bytes) -> str: res = "" for x in byte_str: if 32 < x < 128: res += chr(x) return res def decrypt(self, encrypted_config: bytes) -> bytes: data = encrypted_config[:-16] size = len(data) log.info("size of data: %d", len(data)) key = encrypted_config[-16:] log.info("encryption key: %s", malduck.enhex(key).decode("utf-8")) self.key = malduck.enhex(key).decode("utf-8") return self.internal_decrypt(data, size, key) def fix_key(self, key: bytes, x: int, y: int) -> bytes: tempVal = key[y : y + 4] tempVal = int.from_bytes(tempVal, byteorder="little") rotVal = (tempVal & 7) & 0xFF tempVal = key[x : x + 4] tempVal = int.from_bytes(tempVal, byteorder="little") tempVal = malduck.bits.ror(tempVal, rotVal, 32) tempVal += 1 tempValX = tempVal.to_bytes(4, byteorder="little") rotVal = (tempVal & 7) & 0xFF tempVal = key[y : y + 4] tempVal = int.from_bytes(tempVal, byteorder="little") tempVal = malduck.bits.ror(tempVal, rotVal, 32) tempVal += 1 tempValY = tempVal.to_bytes(4, byteorder="little") tempKey = key[:x] + tempValX + key[x + 4 :] tempKey = tempKey[:y] + tempValY + tempKey[y + 4 :] return tempKey def internal_decrypt(self, data: bytes, size: int, key: bytes) -> bytes: outList = [] if size > 400: log.info("size of data: %d", size) for i in range(size): x = i & 3 y = (i + 1) & 3 c = key[y * 4] + key[x * 4] c = (c ^ data[i]) & 0xFF outList.append(c.to_bytes(1, byteorder="little")) key = self.fix_key(key, x * 4, y * 4) return b"".join(outList) def parse_config(self, raw_config_blob: bytes) -> dict: conf = {} config_values = raw_config_blob.split(b"\x00") cleaned_values = [x for x in config_values if x != b""] project_id = cleaned_values[0][0:4] loader_version = cleaned_values[0][4:8] cleaned_values = cleaned_values[1:-1] for val in cleaned_values: ascii_str = self.strip_non_ascii(val) if "/" in ascii_str: conf["uri"] = ascii_str else: if "domains" not in conf: conf["domains"] = [] conf["domains"].append(ascii_str) conf["family"] = self.family conf[self.family] = { "loader_version": malduck.enhex(loader_version).decode("utf-8"), "project_id": malduck.enhex(project_id).decode("utf-8"), } return conf def find_encrypted_config(self, file_data: bytes) -> bytes | None: window = 0x25C for i in range(len(file_data) - window): buf = file_data[i : i + window] entropy_val = self.entropy(buf) if ( entropy_val > 7.5 and file_data[i - 1] == 0x00 and file_data[i + window] == 0x00 ): return buf return None @Extractor.rule def icedid_peloader(self, p: ProcessMemory, match: YaraRuleMatch) -> Config | bool: _info: Config = get_rule_metadata(match) return _info @Extractor.final def ref_c2(self, p: ProcessMemory) -> dict | None: file_contents = p.readp(0, p.length) encrypted_config = self.find_encrypted_config(file_contents) if encrypted_config is None: log.error("unable to find encrypted buffer") return log.info("len of encrypted data: %s", len(encrypted_config)) decrypted = self.decrypt(encrypted_config) entropy = self.entropy(decrypted) log.info("decrypted data entropy: %s", entropy) if entropy < 2: conf = self.parse_config(decrypted) if self.key: conf["key"] = self.key return conf