hancitor/hancitor.py (78 lines of code) (raw):

import collections import hashlib import logging import math import malduck from malduck.extractor import Extractor from malduck.pe import PE 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.0" MAX_STRING_SIZE = 2048 class Hancitor(Extractor): """ Hancitor C2s and Campaign ID Extractor """ family: str = "hancitor" yara_rules: tuple = ("hancitor",) @staticmethod def estimate_shannon_entropy(data: bytes) -> float: m = len(data) bases = collections.Counter([tmp_base for tmp_base in data]) shannon_entropy_value: float = 0.0 for base in bases: n_i = bases[base] p_i = n_i / float(m) entropy_i = p_i * (math.log(p_i, 2)) shannon_entropy_value += entropy_i return shannon_entropy_value * -1 @staticmethod def string_from_offset(data: bytes, offset: int) -> bytes: string = data[offset : offset + MAX_STRING_SIZE].split(b"\0")[0] return string @Extractor.rule def hancitor(self, p: ProcessMemory, match: YaraRuleMatch) -> Config | bool: _info: Config = get_rule_metadata(match) return _info def parse_config(self, raw_config_blob: bytes) -> dict: conf = {} split_conf = raw_config_blob.split(b"\x00") cleaned_conf = [x for x in split_conf if x] log.info(cleaned_conf) conf["family"] = self.family _urls = cleaned_conf[1].split(b"|") conf[self.family] = { "id": cleaned_conf[0].decode("utf-8"), } conf["urls"] = [x.decode("utf-8") for x in _urls if x] return conf @Extractor.final def ref_c2(self, p: ProcessMemory) -> dict | None: pe_rep = PE(data=p) raw_rc4_key = None crypted_data = None for section in pe_rep.sections: if b".data" in section.Name: section_data = section.get_data() raw_rc4_key = section_data[16:24] crypted_data = section_data[24 : 24 + 8192] if raw_rc4_key is None or crypted_data is None: log.error("unable to find .data section") return log.info("key: %s", malduck.enhex(raw_rc4_key).decode("utf-8")) flags = 0x280011 key_length = int((flags >> 16) / 8) raw_hash = hashlib.sha1(raw_rc4_key).digest()[:key_length] log.info( "len of encrypted data: %s, decrypting with %s", len(crypted_data), malduck.enhex(raw_hash).decode("utf-8"), ) decrypted = malduck.rc4(raw_hash, crypted_data) entropy = self.estimate_shannon_entropy(decrypted) log.info("decrypted data entropy: %s", entropy) if entropy < 1: conf = self.parse_config(decrypted) if raw_rc4_key: conf["rc4_key"] = malduck.enhex(raw_rc4_key).decode("utf-8") return conf