공급망 악성코드 아티팩트 분석은(는) 무엇을 하나요?
개요
공급망 공격은 합법적인 소프트웨어 배포 채널을 손상시켜 신뢰할 수 있는 업데이트 메커니즘을 통해 악성코드를 전달합니다. 대표적인 예로는 SolarWinds SUNBURST(2020년, 18,000개 이상의 고객에 영향), 3CX SmoothOperator(2023년, Trading Technologies에서 시작된 연쇄 공급망 공격), 그리고 수많은 npm/PyPI 패키지 중독 캠페인이 있습니다. 분석은 트로이 목마화된 바이너리를 정식 버전과 비교하고, 빌드 아티팩트에서 삽입된 코드를 식별하며, 코드 서명 이상을 검사하고, 초기 침해부터 페이로드 전달까지의 감염 체인을 추적하는 과정을 포함합니다. 2025년 기준으로 공급망 공격은 전체 침해 사고의 30%를 차지하며, 이는 이전 대비 100% 증가한 수치입니다.
사용 시기
- 공급망 악성코드 아티팩트 분석이 필요한 보안 사고를 조사할 때
- 이 도메인에 대한 탐지 규칙 또는 위협 헌팅 쿼리를 구축할 때
- SOC 분석가가 이 분석 유형에 대한 구조화된 절차가 필요할 때
- 관련 공격 기술에 대한 보안 모니터링 범위를 검증할 때
전제 조건
- Python 3.9+ (
pefile,ssdeep,hashlib포함) - 바이너리 비교 도구 (BinDiff, Diaphora)
- 코드 서명 검증 도구 (sigcheck, codesign)
- 소프트웨어 구성 분석 (SCA) 도구
- 비교를 위한 정식 소프트웨어 버전에 대한 접근
- 패키지 저장소 모니터링 (npm, PyPI, NuGet)
워크플로
1단계: 이진 비교 분석
#!/usr/bin/env python3
"""트로이 목마화된 바이너리와 정식 버전을 비교합니다."""
import hashlib
import pefile
import sys
import json
def compare_pe_files(legitimate_path, suspect_path):
"""정식 버전과 의심 버전 간의 PE 파일 구조를 비교합니다."""
legit_pe = pefile.PE(legitimate_path)
suspect_pe = pefile.PE(suspect_path)
report = {"differences": [], "suspicious_sections": [], "import_changes": []}
# 섹션 비교
legit_sections = {s.Name.rstrip(b'\x00').decode(): {
"size": s.SizeOfRawData,
"entropy": s.get_entropy(),
"characteristics": s.Characteristics,
} for s in legit_pe.sections}
suspect_sections = {s.Name.rstrip(b'\x00').decode(): {
"size": s.SizeOfRawData,
"entropy": s.get_entropy(),
"characteristics": s.Characteristics,
} for s in suspect_pe.sections}
# 새롭거나 변경된 섹션 찾기
for name, props in suspect_sections.items():
if name not in legit_sections:
report["suspicious_sections"].append({
"name": name, "reason": "정식 버전에 없는 새 섹션",
"size": props["size"], "entropy": round(props["entropy"], 2),
})
elif abs(props["size"] - legit_sections[name]["size"]) > 1024:
report["suspicious_sections"].append({
"name": name, "reason": "섹션 크기가 크게 변경됨",
"legit_size": legit_sections[name]["size"],
"suspect_size": props["size"],
})
# 임포트 비교
legit_imports = set()
if hasattr(legit_pe, 'DIRECTORY_ENTRY_IMPORT'):
for entry in legit_pe.DIRECTORY_ENTRY_IMPORT:
for imp in entry.imports:
if imp.name:
legit_imports.add(f"{entry.dll.decode()}!{imp.name.decode()}")
suspect_imports = set()
if hasattr(suspect_pe, 'DIRECTORY_ENTRY_IMPORT'):
for entry in suspect_pe.DIRECTORY_ENTRY_IMPORT:
for imp in entry.imports:
if imp.name:
suspect_imports.add(f"{entry.dll.decode()}!{imp.name.decode()}")
new_imports = suspect_imports - legit_imports
if new_imports:
report["import_changes"] = list(new_imports)
# 코드 서명 확인
report["legit_signed"] = bool(legit_pe.OPTIONAL_HEADER.DATA_DIRECTORY[4].Size)
report["suspect_signed"] = bool(suspect_pe.OPTIONAL_HEADER.DATA_DIRECTORY[4].Size)
return report
def hash_file(filepath):
"""파일의 여러 해시를 계산합니다."""
hashes = {}
with open(filepath, 'rb') as f:
data = f.read()
for algo in ['md5', 'sha1', 'sha256']:
h = hashlib.new(algo)
h.update(data)
hashes[algo] = h.hexdigest()
return hashes
if __name__ == "__main__":
if len(sys.argv) < 3:
print(f"사용법: {sys.argv[0]} <정식_바이너리> <의심_바이너리>")
sys.exit(1)
report = compare_pe_files(sys.argv[1], sys.argv[2])
print(json.dumps(report, indent=2))
검증 기준
- 이진 비교를 통해 트로이 목마화된 구성 요소 식별
- 삽입된 코드를 분리하여 별도 분석
- 코드 서명 이상 문서화
- 빌드 아티팩트로부터 감염 타임라인 재구성
- 영향을 받은 시스템 전반에 걸친 다운스트림 영향 범위 평가
- 탐지 및 차단을 위한 IOC 추출