멀웨어 네트워크 은닉 채널 분석은(는) 무엇을 하나요?
개요
멀웨어는 은닉 채널을 사용하여 정상적인 네트워크 트래픽처럼 위장한 C2 통신과 데이터 유출을 수행합니다. DNS 터널링은 DNS 쿼리와 응답에 데이터를 인코딩하며(iodine, dnscat2, FrameworkPOS와 같은 멀웨어 계열에서 사용), ICMP 터널링은 에코 요청/응답 페이로드에 데이터를 숨깁니다(icmpsh, ptunnel). HTTP 은닉 채널은 헤더, 쿠키 또는 스테가노그래피 이미지에 C2 데이터를 포함시킵니다. 프로토콜 남용은 허용된 프로토콜을 이용해 방화벽을 우회합니다. DNS 터널링 탐지는 최신 ML 기반 접근 방식으로 99% 이상의 재현율을 달성하지만, 저처리량 유출은 여전히 어려운 과제입니다. Palo Alto Unit42는 2024년까지 TrkCdn, SecShow, Savvy Seahorse라는 세 가지 주요 DNS 터널링 캠페인을 추적하여 해당 기법의 지속적인 확산을 보여주었습니다.
사용 시기
- 멀웨어의 네트워크 은닉 채널 분석이 필요한 보안 사고를 조사할 때
- 이 도메인에 대한 탐지 규칙 또는 위협 헌팅 쿼리를 구축할 때
- SOC 분석가가 이 분석 유형에 대한 구조화된 절차를 필요로 할 때
- 관련 공격 기법에 대한 보안 모니터링 적용 범위를 검증할 때
사전 요구 사항
- Python 3.9+ 및
scapy,dpkt,dnslib - PCAP 분석을 위한 Wireshark/tshark
- 네트워크 모니터링을 위한 Zeek (구 Bro)
- DNS 쿼리 로깅 인프라
- 패킷 수준의 DNS, ICMP, HTTP 프로토콜 이해
워크플로우
1단계: DNS 터널링 탐지
#!/usr/bin/env python3
"""네트워크 트래픽에서 DNS 터널링 및 은닉 채널을 탐지합니다."""
import sys
import json
import math
from collections import Counter, defaultdict
try:
from scapy.all import rdpcap, DNS, DNSQR, DNSRR, IP, ICMP
except ImportError:
print("pip install scapy")
sys.exit(1)
def entropy(data):
if not data:
return 0
freq = Counter(data)
length = len(data)
return -sum((c/length) * math.log2(c/length) for c in freq.values())
def analyze_dns_tunneling(pcap_path):
"""PCAP에서 DNS 터널링 지표를 탐지합니다."""
packets = rdpcap(pcap_path)
domain_stats = defaultdict(lambda: {
"queries": 0, "total_qname_len": 0, "subdomain_lengths": [],
"query_types": Counter(), "unique_subdomains": set(),
})
for pkt in packets:
if pkt.haslayer(DNS) and pkt.haslayer(DNSQR):
qname = pkt[DNSQR].qname.decode('utf-8', errors='replace').rstrip('.')
qtype = pkt[DNSQR].qtype
parts = qname.split('.')
if len(parts) >= 3:
base_domain = '.'.join(parts[-2:])
subdomain = '.'.join(parts[:-2])
stats = domain_stats[base_domain]
stats["queries"] += 1
stats["total_qname_len"] += len(qname)
stats["subdomain_lengths"].append(len(subdomain))
stats["query_types"][qtype] += 1
stats["unique_subdomains"].add(subdomain)
# 터널링 지표에 따라 도메인 점수 부여
suspicious = []
for domain, stats in domain_stats.items():
if stats["queries"] < 5:
continue
avg_subdomain_len = (sum(stats["subdomain_lengths"]) /
len(stats["subdomain_lengths"]))
unique_ratio = len(stats["unique_subdomains"]) / stats["queries"]
# 서브도메인 엔트로피 계산
all_subdomains = ''.join(stats["unique_subdomains"])
sub_entropy = entropy(all_subdomains)
score = 0
reasons = []
if avg_subdomain_len > 30:
score += 30
reasons.append(f"긴 서브도메인 (평균 {avg_subdomain_len:.0f}자)")
if unique_ratio > 0.9:
score += 25
reasons.append(f"높은 고유성 ({unique_ratio:.2%})")
if sub_entropy > 4.0:
score += 25
reasons.append(f"높은 엔트로피 ({sub_entropy:.2f})")
if stats["query_types"].get(16, 0) > 10: # TXT 레코드
score += 20
reasons.append(f"많은 TXT 쿼리 ({stats['query_types'][16]})")
if score >= 50:
suspicious.append({
"domain": domain,
"score": score,
"queries": stats["queries"],
"avg_subdomain_length": round(avg_subdomain_len, 1),
"unique_subdomains": len(stats["unique_subdomains"]),
"subdomain_entropy": round(sub_entropy, 2),
"reasons": reasons,
})
return sorted(suspicious, key=lambda x: -x["score"])
def analyze_icmp_tunneling(pcap_path):
"""PCAP에서 ICMP 터널링을 탐지합니다."""
packets = rdpcap(pcap_path)
icmp_stats = defaultdict(lambda: {"count": 0, "payload_sizes": [], "payloads": []})
for pkt in packets:
if pkt.haslayer(ICMP) and pkt.haslayer(IP):
src = pkt[IP].src
dst = pkt[IP].dst
key = f"{src}->{dst}"
payload = bytes(pkt[ICMP].payload)
icmp_stats[key]["count"] += 1
icmp_stats[key]["payload_sizes"].append(len(payload))
if len(payload) > 64:
icmp_stats[key]["payloads"].append(payload[:100])
suspicious = []
for flow, stats in icmp_stats.items():
if stats["count"] < 5:
continue
avg_size = sum(stats["payload_sizes"]) / len(stats["payload_sizes"])
if avg_size > 64 or stats["count"] > 100:
suspicious.append({
"flow": flow,
"packets": stats["count"],
"avg_payload_size": round(avg_size, 1),
"reason": "크거나 빈번한 ICMP 페이로드는 터널링을 암시합니다.",
})
return suspicious
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"사용법: {sys.argv[0]} <pcap_file>")
sys.exit(1)
print("[+] DNS 터널링 분석")
dns_results = analyze_dns_tunneling(sys.argv[1])
for r in dns_results:
print(f" {r['domain']} (점수: {r['score']})")
for reason in r['reasons']:
print(f" - {reason}")
print("\n[+] ICMP 터널링 분석")
icmp_results = analyze_icmp_tunneling(sys.argv[1])
for r in icmp_results:
print(f" {r['flow']}: {r['reason']}")
검증 기준
- DNS 터널링은 엔트로피, 서브도메인 길이 및 쿼리 볼륨 분석을 통해 탐지
- ICMP 은닉 채널은 페이로드 크기 이상값을 통해 식별
- 터널링 도메인은 합법적인 CDN/클라우드 트래픽과 구분
- 캡처된 트래픽에서 데이터 유출량 추정
- C2 통신 패턴 및 비콘 간격 추출