$npx -y skills add killvxk/cybersecurity-skills-zh --skill analyzing-cobalt-strike-beacon-configuration从 PE 文件和内存转储中提取并分析 Cobalt Strike beacon 配置,以识别 C2 基础设施、Malleable C2 配置文件和攻击者操作惯例。
| 1 | # 分析 Cobalt Strike Beacon 配置 |
| 2 | |
| 3 | ## 概述 |
| 4 | |
| 5 | Cobalt Strike 是一个商业对抗模拟工具,被威胁行为者广泛滥用于后渗透操作。Beacon 载荷包含嵌入式配置数据,揭示 C2 服务器地址、通信协议、休眠间隔、抖动值、Malleable C2 配置文件设置、水印标识符和加密密钥。从 PE 文件、shellcode 或内存转储中提取此配置对于事件响应人员绘制攻击者基础设施和关联攻击活动至关重要。Beacon 配置使用单字节 XOR 编码(版本 3 使用 0x69,版本 4 使用 0x2e),以类型-长度-值(TLV)格式存储在 .data 节中。 |
| 6 | |
| 7 | ## 前置条件 |
| 8 | |
| 9 | - Python 3.9+,安装 `dissect.cobaltstrike`、`pefile`、`yara-python` |
| 10 | - SentinelOne CobaltStrikeParser(`parse_beacon_config.py`) |
| 11 | - 十六进制编辑器(010 Editor、HxD)用于手动检查 |
| 12 | - 了解 PE 文件格式和 XOR 编码 |
| 13 | - 内存转储获取工具(Volatility3、WinDbg) |
| 14 | - 网络分析工具(Wireshark)用于 C2 流量关联 |
| 15 | |
| 16 | ## 核心概念 |
| 17 | |
| 18 | ### Beacon 配置结构 |
| 19 | |
| 20 | Cobalt Strike beacon 将其配置存储为 PE 文件 .data 节中的 TLV(类型-长度-值)条目集合。无分段 beacon 使用 4 字节密钥对整个 beacon 代码进行 XOR 编码。配置集合本身使用单字节 XOR 密钥。每个 TLV 条目包含一个 2 字节的类型标识符(如 0x0001 表示 BeaconType,0x0008 表示 C2Server)、一个 2 字节长度和可变长度数据。 |
| 21 | |
| 22 | ### Malleable C2 配置文件 |
| 23 | |
| 24 | Beacon 配置编码了 Malleable C2 配置文件,该文件规定了 HTTP 请求/响应转换,包括 URI 路径、请求头、元数据编码(Base64、NetBIOS)和数据转换。分析这些设置可揭示 beacon 如何伪装其流量以融入合法的 Web 流量。 |
| 25 | |
| 26 | ### 水印和许可证识别 |
| 27 | |
| 28 | 每个 Cobalt Strike 许可证都会在生成的 beacon 中嵌入唯一水印(4 字节整数)。提取水印可将多个 beacon 关联到同一操作者或破解许可证。威胁情报提供商维护的已知水印数据库可将水印映射到特定威胁行为者或泄露的许可证密钥。 |
| 29 | |
| 30 | ## 实操步骤 |
| 31 | |
| 32 | ### 步骤 1:使用 CobaltStrikeParser 提取配置 |
| 33 | |
| 34 | ```python |
| 35 | #!/usr/bin/env python3 |
| 36 | """从 PE 文件或内存转储中提取 Cobalt Strike beacon 配置。""" |
| 37 | import sys |
| 38 | import json |
| 39 | |
| 40 | # 使用 SentinelOne 的 CobaltStrikeParser |
| 41 | # pip install dissect.cobaltstrike |
| 42 | from dissect.cobaltstrike.beacon import BeaconConfig |
| 43 | |
| 44 | def extract_beacon_config(filepath): |
| 45 | """从文件解析 beacon 配置。""" |
| 46 | configs = list(BeaconConfig.from_path(filepath)) |
| 47 | |
| 48 | if not configs: |
| 49 | print(f"[-] 在 {filepath} 中未找到 beacon 配置") |
| 50 | return None |
| 51 | |
| 52 | for i, config in enumerate(configs): |
| 53 | print(f"\n[+] Beacon 配置 #{i+1}") |
| 54 | print(f"{'='*60}") |
| 55 | |
| 56 | settings = config.as_dict() |
| 57 | |
| 58 | # 事件响应的关键字段 |
| 59 | critical_fields = [ |
| 60 | "SETTING_C2_REQUEST", |
| 61 | "SETTING_C2_RECOVER", |
| 62 | "SETTING_PUBKEY", |
| 63 | "SETTING_DOMAINS", |
| 64 | "SETTING_BEACONTYPE", |
| 65 | "SETTING_PORT", |
| 66 | "SETTING_SLEEPTIME", |
| 67 | "SETTING_JITTER", |
| 68 | "SETTING_MAXGET", |
| 69 | "SETTING_SPAWNTO_X86", |
| 70 | "SETTING_SPAWNTO_X64", |
| 71 | "SETTING_PIPENAME", |
| 72 | "SETTING_WATERMARK", |
| 73 | "SETTING_C2_VERB_GET", |
| 74 | "SETTING_C2_VERB_POST", |
| 75 | "SETTING_USERAGENT", |
| 76 | "SETTING_PROTOCOL", |
| 77 | ] |
| 78 | |
| 79 | for field in critical_fields: |
| 80 | value = settings.get(field, "N/A") |
| 81 | print(f" {field}: {value}") |
| 82 | |
| 83 | return settings |
| 84 | |
| 85 | return None |
| 86 | |
| 87 | |
| 88 | def extract_c2_indicators(config): |
| 89 | """从 beacon 配置中提取可操作的 C2 指标。""" |
| 90 | indicators = { |
| 91 | "c2_domains": [], |
| 92 | "c2_ips": [], |
| 93 | "c2_urls": [], |
| 94 | "user_agent": "", |
| 95 | "named_pipes": [], |
| 96 | "spawn_processes": [], |
| 97 | "watermark": "", |
| 98 | } |
| 99 | |
| 100 | if not config: |
| 101 | return indicators |
| 102 | |
| 103 | # 提取 C2 域名 |
| 104 | domains = config.get("SETTING_DOMAINS", "") |
| 105 | if domains: |
| 106 | for domain in str(domains).split(","): |
| 107 | domain = domain.strip().rstrip("/") |
| 108 | if domain: |
| 109 | indicators["c2_domains"].append(domain) |
| 110 | |
| 111 | # 提取 User Agent |
| 112 | indicators["user_agent"] = str(config.get("SETTING_USERAGENT", "")) |
| 113 | |
| 114 | # 提取命名管道 |
| 115 | pipe = config.get("SETTING_PIPENAME", "") |
| 116 | if pipe: |
| 117 | indicators["named_pipes"].append(str(pipe)) |
| 118 | |
| 119 | # 提取注入目标进程 |
| 120 | for arch in ["SETTING_SPAWNTO_X86", "SETTING_SPAWNTO_X64"]: |
| 121 | proc = config.get(arch, "") |
| 122 | if proc: |
| 123 | indicators["spawn_processes"].append(str(proc)) |
| 124 | |
| 125 | # 提取水印 |
| 126 | indicators["watermark"] = str(config.get("SETTING_WATERMARK", "")) |
| 127 | |
| 128 | return indicators |
| 129 | |
| 130 | |
| 131 | if __name__ == "__main__": |
| 132 | if len(sys.argv) < 2: |
| 133 | print(f"用法:{sys.argv[0]} <beacon_file_or_dump>") |
| 134 | sys.exit(1) |
| 135 | |
| 136 | config = extract_beacon_config(sys.argv[1]) |
| 137 | if config: |
| 138 | indicators = extract_c2_indicators(config) |
| 139 | print(f"\n[+] 提取的 C2 指标:") |
| 140 | print(json.dumps(indicators, indent=2)) |
| 141 | ``` |
| 142 | |
| 143 | ### 步骤 2:手动 XOR 解密 Beacon 配置 |
| 144 | |
| 145 | ```python |
| 146 | import struct |
| 147 | |
| 148 | def find_and_decrypt_config(data): |
| 149 | """手动定位并解密 beacon 配置。""" |
| 150 | # Cobalt Strike 4.x 使用 0x2e 作为 XOR 密钥 |
| 151 | xor_keys = [0x2e, 0x69] # v4, v3 |
| 152 | |
| 153 | for xor_key in xor_keys: |
| 154 | # 搜索 XOR 后的配置魔数字节 |
| 155 | # 配置以 0x0001(BeaconType)XOR 后开始 |
| 156 | magic = bytes([0x00 ^ xor_key, 0x01 ^ xor_key, |
| 157 | 0x00 ^ xor_key, 0x02 ^ xor_key]) |
| 158 | |
| 159 | offset = data.find(magic) |
| 160 | if offset == -1: |
| 161 | continue |
| 162 | |
| 163 | print(f"[+] 在偏移 0x{offset:x} 处找到配置(XOR 密钥:0x{xor_key:02x})") |
| 164 | |
| 165 | # 解密配置集合(通常 4096 字节) |
| 166 | config_size = 4096 |
| 167 | encrypted = data[offset:offset + config_size] |
| 168 | decrypted = bytes([b ^ xor_key for b in encrypted]) |