Malpedia を用いたマルウェアファミリー関連分析
概要
Malpedia は、Fraunhofer FKIE が維持する協調型プラットフォームで、マルウェアファミリーの別名、YARA ルール、脅威アクターとの関連、参照レポートをカタログ化しています。2,600 以上のマルウェアファミリーが文書化されており、マルウェアの系統の理解、亜種の進化追跡、特定の脅威グループへのマルウェアの関連付けのための決定的なリソースとして機能します。このスキルでは、Malpedia API へのクエリ、マルウェアファミリー関係のマッピング、検出のための YARA ルールの抽出、および攻撃者が使用するマルウェアエコシステムに関するインテリジェンスの構築について説明します。
使用すべき場面
- マルウェアファミリーの関連分析が必要なセキュリティインシデントを調査する場合
- このドメインの検出ルールや脅威ハンティングクエリを構築する場合
- SOC アナリストがこの分析タイプの構造化された手順を必要とする場合
- 関連する攻撃手法に対するセキュリティ監視のカバレッジを検証する場合
前提条件
- Python 3.9+(
requests、yara-python、stix2ライブラリ) - Malpedia API キー(https://malpedia.caad.fkie.fraunhofer.de/ で登録)
- マルウェアの分類と命名規則に関する理解
- 検出のための YARA ルール構文への習熟
- 検証用のマルウェアサンプルへのアクセス(オプション)
主要な概念
Malpedia データモデル
Malpedia はマルウェアをファミリー(例:win.cobalt_strike)に整理し、各ファミリーには別名(ベンダー固有の名前、「Beacon」、「CobaltStrike」など)、YARA ルール(コミュニティおよびベンダーが提供)、アクター関連(そのファミリーを使用する脅威グループ)、参照レポート(そのファミリーを文書化した CTI レポート)、サンプルハッシュ(各亜種を代表するサンプル)が含まれます。
マルウェアファミリーの命名
Malpedia は プラットフォーム.ファミリー名 の形式(例:win.emotet、elf.mirai、apk.flubot)を使用します。プラットフォームには win(Windows)、elf(Linux)、apk(Android)、osx(macOS)、py(Python)が含まれます。この標準化された命名規則により、異なるベンダーが同じマルウェアに異なる名前を割り当てる「多名称問題」が解決されます。
ファミリー間の関係
マルウェアファミリーには、親子関係(コードの再利用、フォーク)、ローダー-ペイロード関係(Emotet が TrickBot をロードし、TrickBot が Ryuk をロードする)、共通の作成者関係(同じ脅威アクターが複数のツールを開発)、インフラ共有関係(共通の C2 フレームワーク)などがあります。
ワークフロー
ステップ1: Malpedia API へのマルウェアファミリーのクエリ
import requests
import json
from collections import defaultdict
class MalpediaClient:
BASE_URL = "https://malpedia.caad.fkie.fraunhofer.de/api"
def __init__(self, api_key):
self.headers = {"Authorization": f"apitoken {api_key}"}
def get_family_list(self):
"""すべてのマルウェアファミリーのリストを取得します。"""
resp = requests.get(f"{self.BASE_URL}/list/families",
headers=self.headers, timeout=30)
if resp.status_code == 200:
families = resp.json()
print(f"[+] Malpedia: {len(families)} のマルウェアファミリー")
return families
return {}
def get_family_info(self, family_name):
"""マルウェアファミリーの詳細情報を取得します。"""
resp = requests.get(f"{self.BASE_URL}/get/family/{family_name}",
headers=self.headers, timeout=30)
if resp.status_code == 200:
info = resp.json()
print(f"[+] ファミリー: {family_name}")
print(f" 別名: {info.get('alt_names', [])}")
print(f" アクター: {[a.get('value', '') for a in info.get('attribution', [])]}")
print(f" URL参照数: {len(info.get('urls', []))}")
return info
print(f"[-] ファミリーが見つかりません: {family_name}")
return None
def get_family_yara(self, family_name):
"""マルウェアファミリーのYARAルールを取得します。"""
resp = requests.get(f"{self.BASE_URL}/get/yara/{family_name}",
headers=self.headers, timeout=30)
if resp.status_code == 200:
rules = resp.json()
rule_count = sum(len(v) for v in rules.values()) if isinstance(rules, dict) else 0
print(f"[+] {family_name} のYARAルール: {rule_count} ルール")
return rules
return {}
def get_actor_families(self, actor_name):
"""脅威アクターに関連するマルウェアファミリーを取得します。"""
resp = requests.get(f"{self.BASE_URL}/get/actor/{actor_name}",
headers=self.headers, timeout=30)
if resp.status_code == 200:
data = resp.json()
families = data.get("families", {})
print(f"[+] {actor_name}: {len(families)} のマルウェアファミリー")
return data
return {}
def search_families(self, keyword):
"""キーワードでファミリーを検索します。"""
all_families = self.get_family_list()
matches = {
name: info for name, info in all_families.items()
if keyword.lower() in name.lower()
or keyword.lower() in str(info.get("alt_names", [])).lower()
}
print(f"[+] 検索 '{keyword}': {len(matches)} 件の一致")
return matches
client = MalpediaClient("YOUR_MALPEDIA_API_KEY")
families = client.get_family_list()
emotet_info = client.get_family_info("win.emotet")
ステップ2: マルウェアファミリー関係のマッピング
class MalwareFamilyMapper:
def __init__(self, malpedia_client):
self.client = malpedia_client
self.relationship_graph = defaultdict(list)
def map_actor_ecosystem(self, actor_name):
"""脅威アクターが使用するマルウェアエコシステムをマッピングします。"""
actor_data = self.client.get_actor_families(actor_name)
families = actor_data.get("families", {})
ecosystem = {
"actor": actor_name,
"families": [],
"family_count": len(families),
}
for family_name in families:
info = self.client.get_family_info(family_name)
if info:
ecosystem["families"].append({
"name": family_name,
"aliases": info.get("alt_names", []),
"description": info.get("description", "")[:200],
"shared_actors": [
a.get("value", "")
for a in info.get("attribution", [])
],
"reference_count": len(info.get("urls", [])),
})
print(f"\n=== {actor_name} マルウェアエコシステム ===")
for fam in ecosystem["families"]:
shared = [a for a in fam["shared_actors"] if a != actor_name]
print(f" {fam['name']}")
print(f" 別名: {fam['aliases'][:5]}")
if shared:
print(f" 他の使用者: {shared}")
return ecosystem
def find_shared_tooling(self, actor_names):
"""脅威アクター間で共有されているマルウェアファミリーを見つけます。"""
actor_families = {}
for actor in actor_names:
data = self.client.get_actor_families(actor)
actor_families[actor] = set(data.get("families", {}).keys())
# 重複を見つける
shared = {}
for i, actor1 in enumerate(actor_names):
for actor2 in actor_names[i+1:]:
common = actor_families[actor1] & actor_families[actor2]
if common:
shared[f"{actor1} <-> {actor2}"] = sorted(common)
print(f"\n=== 共有ツール分析 ===")
for pair, families in shared.items():
print(f" {pair}: {len(families)} の共有ファミリー")
for f in families[:5]:
print(f" - {f}")
return shared
def build_loader_payload_chain(self, family_name):
"""ファミリーのローダー・ペイロード配信チェーンを構築します。"""
info = self.client.get_family_info(family_name)
if not info:
return {}
chain = {
"family": family_name,
"description": info.get("description", ""),
"known_loaders": [],
"known_payloads": [],
}
# 既知の一般的な配信チェーン
known_chains = {
"win.emotet": {"loaders": ["email/macro"], "payloads": ["win.trickbot", "win.qakbot", "win.cobalt_strike"]},
"win.trickbot": {"loaders": ["win.emotet"], "payloads": ["win.ryuk", "win.conti", "win.cobalt_strike"]},
"win.qakbot": {"loaders": ["email/macro", "win.emotet"], "payloads": ["win.cobalt_strike", "win.blackbasta"]},
"win.cobalt_strike": {"loaders": ["win.emotet", "win.trickbot", "win.qakbot"], "payloads": ["ransomware"]},
}
if family_name in known_chains:
chain["known_loaders"] = known_chains[family_name]["loaders"]
chain["known_payloads"] = known_chains[family_name]["payloads"]
return chain
mapper = MalwareFamilyMapper(client)
ecosystem = mapper.map_actor_ecosystem("Wizard Spider")
shared = mapper.find_shared_tooling(["Wizard Spider", "FIN7", "Lazarus Group"])
chain = mapper.build_loader_payload_chain("win.emotet")
ステップ3: YARA ルールの抽出とコンパイル
def compile_yara_ruleset(client, family_names, output_file="malware_yara_rules.yar"):
"""複数のマルウェアファミリーのYARAルールをコンパイルします。"""
all_rules = []
for family in family_names:
yara_data = client.get_family_yara(family)
if isinstance(yara_data, dict):
for source, rules in yara_data.items():
if isinstance(rules, list):
for rule in rules:
all_rules.append(f"// Source: {source} - Family: {family}\n{rule}")
elif isinstance(rules, str):
all_rules.append(f"// Source: {source} - Family: {family}\n{rules}")
with open(output_file, "w") as f:
f.write(f"// Malpedia YARA Rules - {len(all_rules)} ルール\n")
f.write(f"// ファミリー: {', '.join(family_names)}\n\n")
for rule in all_rules:
f.write(rule + "\n\n")
print(f"[+] {len(all_rules)} 個のYARAルールを {output_file} にコンパイルしました")
return all_rules
compile_yara_ruleset(client, ["win.emotet", "win.trickbot", "win.cobalt_strike"])
検証基準
- Malpedia API へのクエリが成功し、マルウェアファミリーが取得できること
- 別名、アクター、参照情報を含むファミリー情報が取得できること
- アクターとファミリーの関係が正しくマッピングされること
- アクター間の共有ツールが特定されること
- 検出用の YARA ルールが抽出・コンパイルされること
- ローダー・ペイロードチェーンが脅威インテリジェンスとして文書化されること