Cobalt Strike 비콘 설정 분석은(는) 무엇을 하나요?
개요
Cobalt Strike는 위협 행위자들이 사후 침해 작업에 널리 악용하는 상용 적대적 시뮬레이션 도구입니다. 비콘 페이로드에는 C2 서버 주소, 통신 프로토콜, 슬립 간격, 지터 값, Malleable C2 프로필 설정, 워터마크 식별자 및 암호화 키를 나타내는 포함된 구성 데이터가 들어 있습니다. PE 파일, 셸코드 또는 메모리 덤프에서 이 구성을 추출하는 것은 침해 사고 대응자가 공격자 인프라를 매핑하고 캠페인을 귀속시키는 데 중요합니다. 비콘 구성은 단일 바이트(버전 3의 경우 0x69, 버전 4의 경우 0x2e)를 사용하여 XOR 인코딩되며 .data 섹션 내에 TLV(Type-Length-Value) 형식으로 저장됩니다.
사용 시기
- Cobalt Strike 비콘 구성을 분석해야 하는 보안 사고를 조사할 때
- 이 도메인에 대한 탐지 규칙 또는 위협 헌팅 쿼리를 구축할 때
- SOC 분석가가 이 분석 유형에 대한 체계적인 절차가 필요할 때
- 관련 공격 기술에 대한 보안 모니터링 범위를 검증할 때
전제 조건
- Python 3.9+ 및
dissect.cobaltstrike,pefile,yara-python - SentinelOne CobaltStrikeParser (
parse_beacon_config.py) - 16진수 편집기(010 Editor, HxD) 수동 검사용
- PE 파일 형식 및 XOR 인코딩 이해
- 메모리 덤프 획득 도구(Volatility3, WinDbg)
- C2 트래픽 상관 분석을 위한 네트워크 분석 도구(Wireshark)
주요 개념
비콘 구성 구조
Cobalt Strike 비콘은 PE의 .data 섹션 내에 TLV 항목의 blob으로 구성을 저장합니다. Stageless 비콘은 4바이트 키로 전체 비콘 코드를 XOR합니다. 구성 blob 자체는 단일 바이트 XOR 키를 사용합니다. 각 TLV 항목에는 2바이트 유형 식별자(예: BeaconType의 경우 0x0001, C2Server의 경우 0x0008), 2바이트 길이 및 가변 길이 데이터가 포함됩니다.
Malleable C2 프로필
비콘 구성은 HTTP 요청/응답 변환을 규정하는 Malleable C2 프로필을 인코딩합니다. 여기에는 URI 경로, 헤더, 메타데이터 인코딩(Base64, NetBIOS) 및 데이터 변환이 포함됩니다. 이러한 설정을 분석하면 비콘이 정상 웹 트래픽과 혼합되도록 트래픽을 위장하는 방법이 드러납니다.
워터마크 및 라이선스 식별
각 Cobalt Strike 라이선스는 생성된 비콘에 고유한 워터마크(4바이트 정수)를 포함합니다. 워터마크를 추출하면 여러 비콘을 동일한 운영자 또는 크랙된 라이선스에 연결할 수 있습니다. 위협 인텔리전스 제공업체가 관리하는 알려진 워터마크 데이터베이스는 워터마크를 특정 위협 행위자 또는 유출된 라이선스 키에 매핑합니다.
워크플로우
1단계: CobaltStrikeParser로 구성 추출
#!/usr/bin/env python3
"""PE 또는 메모리 덤프에서 Cobalt Strike 비콘 구성을 추출합니다."""
import sys
import json
# SentinelOne의 CobaltStrikeParser 사용
# pip install dissect.cobaltstrike
from dissect.cobaltstrike.beacon import BeaconConfig
def extract_beacon_config(filepath):
"""파일에서 비콘 구성을 파싱합니다."""
configs = list(BeaconConfig.from_path(filepath))
if not configs:
print(f"[-] {filepath}에서 비콘 구성을 찾을 수 없습니다")
return None
for i, config in enumerate(configs):
print(f"\n[+] 비콘 구성 #{i+1}")
print(f"{'='*60}")
settings = config.as_dict()
# 침해 사고 대응에 중요한 필드
critical_fields = [
"SETTING_C2_REQUEST",
"SETTING_C2_RECOVER",
"SETTING_PUBKEY",
"SETTING_DOMAINS",
"SETTING_BEACONTYPE",
"SETTING_PORT",
"SETTING_SLEEPTIME",
"SETTING_JITTER",
"SETTING_MAXGET",
"SETTING_SPAWNTO_X86",
"SETTING_SPAWNTO_X64",
"SETTING_PIPENAME",
"SETTING_WATERMARK",
"SETTING_C2_VERB_GET",
"SETTING_C2_VERB_POST",
"SETTING_USERAGENT",
"SETTING_PROTOCOL",
]
for field in critical_fields:
value = settings.get(field, "N/A")
print(f" {field}: {value}")
return settings
return None
def extract_c2_indicators(config):
"""비콘 구성에서 실행 가능한 C2 지표를 추출합니다."""
indicators = {
"c2_domains": [],
"c2_ips": [],
"c2_urls": [],
"user_agent": "",
"named_pipes": [],
"spawn_processes": [],
"watermark": "",
}
if not config:
return indicators
# C2 도메인 추출
domains = config.get("SETTING_DOMAINS", "")
if domains:
for domain in str(domains).split(","):
domain = domain.strip().rstrip("/")
if domain:
indicators["c2_domains"].append(domain)
# 사용자 에이전트 추출
indicators["user_agent"] = str(config.get("SETTING_USERAGENT", ""))
# 명명된 파이프 추출
pipe = config.get("SETTING_PIPENAME", "")
if pipe:
indicators["named_pipes"].append(str(pipe))
# Spawn-to 프로세스 추출
for arch in ["SETTING_SPAWNTO_X86", "SETTING_SPAWNTO_X64"]:
proc = config.get(arch, "")
if proc:
indicators["spawn_processes"].append(str(proc))
# 워터마크 추출
indicators["watermark"] = str(config.get("SETTING_WATERMARK", ""))
return indicators
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"사용법: {sys.argv[0]} <비콘_파일_또는_덤프>")
sys.exit(1)
config = extract_beacon_config(sys.argv[1])
if config:
indicators = extract_c2_indicators(config)
print(f"\n[+] 추출된 C2 지표:")
print(json.dumps(indicators, indent=2))
2단계: 비콘 구성 수동 XOR 복호화
import struct
def find_and_decrypt_config(data):
"""비콘 구성을 수동으로 찾아 복호화합니다."""
# Cobalt Strike 4.x는 XOR 키로 0x2e를 사용
xor_keys = [0x2e, 0x69] # v4, v3
for xor_key in xor_keys:
# XOR 후 구성 매직 바이트 검색
# 구성은 XOR 키로 XOR된 0x0001(BeaconType)로 시작
magic = bytes([0x00 ^ xor_key, 0x01 ^ xor_key,
0x00 ^ xor_key, 0x02 ^ xor_key])
offset = data.find(magic)
if offset == -1:
continue
print(f"[+] 오프셋 0x{offset:x}에서 구성 발견 (XOR 키: 0x{xor_key:02x})")
# 구성 blob 복호화(보통 4096바이트)
config_size = 4096
encrypted = data[offset:offset + config_size]
decrypted = bytes([b ^ xor_key for b in encrypted])
# TLV 항목 파싱
entries = parse_tlv(decrypted)
return entries
return None
def parse_tlv(data):
"""TLV 구성 항목을 파싱합니다."""
entries = {}
offset = 0
# TLV 필드 유형 매핑
field_names = {
0x0001: "BeaconType",
0x0002: "Port",
0x0003: "SleepTime",
0x0004: "MaxGetSize",
0x0005: "Jitter",
0x0006: "MaxDNS",
0x0007: "Deprecated_PublicKey",
0x0008: "C2Server",
0x0009: "UserAgent",
0x000a: "PostURI",
0x000b: "Malleable_C2_Instructions",
0x000c: "Deprecated_HttpGet_Metadata",
0x000d: "SpawnTo_x86",
0x000e: "SpawnTo_x64",
0x000f: "CryptoScheme",
0x001a: "Watermark",
0x001d: "C2_HostHeader",
0x0024: "PipeName",
0x0025: "Year",
0x0026: "Month",
0x0027: "Day",
0x0036: "ProxyHostname",
}
while offset + 6 <= len(data):
entry_type = struct.unpack(">H", data[offset:offset+2])[0]
entry_len_type = struct.unpack(">H", data[offset+2:offset+4])[0]
entry_len = struct.unpack(">H", data[offset+4:offset+6])[0]
if entry_type == 0:
break
value_start = offset + 6
value_end = value_start + entry_len
value_data = data[value_start:value_end]
field_name = field_names.get(entry_type, f"Unknown_0x{entry_type:04x}")
if entry_len_type == 1: # Short
value = struct.unpack(">H", value_data[:2])[0]
elif entry_len_type == 2: # Int
value = struct.unpack(">I", value_data[:4])[0]
elif entry_len_type == 3: # String/Blob
value = value_data.rstrip(b'\x00').decode('utf-8', errors='replace')
else:
value = value_data.hex()
entries[field_name] = value
print(f" {field_name}: {value}")
offset = value_end
return entries
3단계: 비콘 탐지를 위한 YARA 규칙
import yara
cobalt_strike_rule = """
rule CobaltStrike_Beacon_Config {
meta:
description = "Cobalt Strike 비콘 구성 탐지"
author = "악성코드 분석 팀"
date = "2025-01-01"
strings:
// CS 4.x의 XOR된 구성 마커 (키 0x2e)
$config_v4 = { 2e 2f 2e 2c }
// CS 3.x의 XOR된 구성 마커 (키 0x69)
$config_v3 = { 69 68 69 6b }
// 일반적인 비콘 문자열
$str_pipe = "\\\\.\\pipe\\" ascii wide
$str_beacon = "beacon" ascii nocase
$str_sleeptime = "sleeptime" ascii nocase
// 반사형 로더 패턴
$reflective = { 4D 5A 41 52 55 48 89 E5 }
condition:
($config_v4 or $config_v3) or
(2 of ($str_*) and $reflective)
}
"""
def scan_for_beacons(filepath):
"""YARA 규칙을 사용하여 Cobalt Strike 비콘 파일을 스캔합니다."""
rules = yara.compile(source=cobalt_strike_rule)
matches = rules.match(filepath)
for match in matches:
print(f"[+] YARA 일치: {match.rule}")
for string_match in match.strings:
offset = string_match.instances[0].offset
print(f" 문자열: {string_match.identifier} (오프셋 0x{offset:x})")
return matches
4단계: 네트워크 트래픽 상관 분석
from dissect.cobaltstrike.c2 import HttpC2Config
def analyze_c2_profile(beacon_config):
"""비콘 구성에서 Malleable C2 프로필을 분석합니다."""
print("\n[+] Malleable C2 프로필 분석")
print("=" * 60)
# HTTP GET 구성
get_verb = beacon_config.get("SETTING_C2_VERB_GET", "GET")
get_uri = beacon_config.get("SETTING_C2_REQUEST", "")
print(f"\n HTTP GET 요청:")
print(f" 동사: {get_verb}")
print(f" URI: {get_uri}")
# HTTP POST 구성
post_verb = beacon_config.get("SETTING_C2_VERB_POST", "POST")
post_uri = beacon_config.get("SETTING_C2_POSTREQ", "")
print(f"\n HTTP POST 요청:")
print(f" 동사: {post_verb}")
print(f" URI: {post_uri}")
# 사용자 에이전트
ua = beacon_config.get("SETTING_USERAGENT", "")
print(f"\n User-Agent: {ua}")
# 호스트 헤더
host = beacon_config.get("SETTING_C2_HOSTHEADER", "")
print(f" Host 헤더: {host}")
# 트래픽 패턴을 위한 슬립 및 지터
sleep_ms = beacon_config.get("SETTING_SLEEPTIME", 60000)
jitter = beacon_config.get("SETTING_JITTER", 0)
print(f"\n 슬립 시간: {sleep_ms}ms")
print(f" 지터: {jitter}%")
# Suricata/Snort 시그니처 생성
print(f"\n[+] 제안된 네트워크 시그니처:")
if ua:
print(f' alert http any any -> any any (msg:"CS 비콘 UA"; '
f'content:"{ua}"; http_user_agent; sid:1000001; rev:1;)')
if get_uri:
print(f' alert http any any -> any any (msg:"CS 비콘 URI"; '
f'content:"{get_uri}"; http_uri; sid:1000002; rev:1;)')
검증 기준
- PE 파일 또는 메모리 덤프에서 비콘 구성이 성공적으로 추출됨
- C2 서버 도메인/IP가 포트 및 프로토콜과 함께 올바르게 식별됨
- Malleable C2 프로필 매개변수가 복호화되어 HTTP 변환을 보여줌
- 귀속 상관 분석을 위해 워터마크 값이 추출됨
- 슬립 시간 및 지터 값이 관찰된 네트워크 비콘 간격과 일치함
- YARA 규칙이 패킹 및 언패킹 샘플 모두에서 비콘을 탐지함
- 추출된 C2 프로필에서 네트워크 시그니처가 생성됨