サプライチェーン・マルウェアアーティファクトの分析
概要
サプライチェーン攻撃は、正規のソフトウェア配信チャネルを侵害し、信頼された更新メカニズムを通じてマルウェアを配信します。代表的な例として、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"Usage: {sys.argv[0]} <legitimate_binary> <suspect_binary>")
sys.exit(1)
report = compare_pe_files(sys.argv[1], sys.argv[2])
print(json.dumps(report, indent=2))
検証基準
- バイナリ差分によりトロイの木馬化されたコンポーネントが特定されている
- 注入されたコードが分離され、個別に分析されている
- コード署名の異常が文書化されている
- ビルドアーティファクトから感染のタイムラインが再構築されている
- 影響を受けたシステム全体でのダウンストリーム影響範囲が評価されている
- 検出およびブロックのためにIoCが抽出されている