performing-malware-ioc-extraction について
マルウェア分析者とインシデント対応担当者向けの IOC 抽出ガイドです。静的・動的解析、YARA スキャン、結果の STIX 2.1 指標化を扱います。
主な機能
- 01PE メタデータとハッシュの抽出
- 02文字列からネットワーク IOC を特定
- 03YARA によるサンプルのスキャン
- 04STIX 2.1 指標の生成
活用シーン
- 01不審なマルウェアサンプルの解析
- 02ブロック用ネットワーク指標の準備
- 03解析結果の STIX データでの共有
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill performing-malware-ioc-extractionインストール後、Claude Code で/performing-malware-ioc-extractionを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEassets/template.mdreferences/api-reference.mdstandards.mdworkflows.mdscripts/agent.pyprocess.py詳細
マルウェア IOC の抽出
概要
マルウェア IOC の抽出とは、悪意のあるソフトウェアを分析し、ファイルハッシュ、ネットワーク指標(C2 ドメイン、IP アドレス、URL)、レジストリの変更、ミューテックス名、埋め込み文字列、動作に関する痕跡など、対処に役立つ侵害指標を特定するプロセスです。このスキルでは、PE 解析と文字列抽出による静的解析、サンドボックスでの実行による動的解析、YARA などのツールを使用した IOC の自動抽出、共有用の STIX 2.1 指標としての結果のフォーマットを扱います。
使用する場面
- マルウェア IOC の抽出を伴うセキュリティ評価を実施する場合
- 関連するセキュリティインシデントに対するインシデント対応手順に従う場合
- 定期的なセキュリティテストや監査活動を実施する場合
- 実地テストを通じてセキュリティ制御を検証する場合
前提条件
pefile、yara-python、oletools、stix2ライブラリを含む Python 3.9+- マルウェア解析サンドボックス(Cuckoo、CAPE、Any.Run、Joe Sandbox)へのアクセス
- エンリッチメント用の VirusTotal API キー
- 隔離された解析環境(VM またはコンテナ)
- PE ファイル形式と一般的なパッキング手法に関する理解
- YARA ルール構文に関する知識
主な概念
静的解析 IOC
- ファイルハッシュ:サンプルおよびドロップされたファイルの MD5、SHA-1、SHA-256
- インポートハッシュ(imphash):インポート関数テーブルのハッシュ。マルウェアファミリーの分類に使用されます
- Rich ヘッダーハッシュ:コンパイラーのフィンガープリントに使用する PE Rich ヘッダーのハッシュ
- 文字列:埋め込み URL、IP アドレス、ドメイン名、レジストリパス、ミューテックス名
- PE メタデータ:コンパイル日時、セクション名、リソース、デジタル署名
- 埋め込みアーティファクト:PDB パス、バージョン情報、証明書の詳細
動的解析 IOC
- ネットワークアクティビティ:DNS クエリ、HTTP リクエスト、TCP/UDP 接続、SSL 証明書
- ファイルシステム:作成、変更、削除されたファイルとディレクトリ
- レジストリ:作成または変更されたレジストリキーと値
- プロセス:起動されたプロセス、コードを注入されたプロセス、サービスの作成
- 動作:API 呼び出し、ミューテックスの作成、スケジュールされたタスク、永続化の仕組み
YARA ルール
YARA は、マルウェアの特定と分類に使用するパターンマッチングツールです。ルールは、文字列(テキスト、16 進数、正規表現)と、マッチングロジックを定義する条件で構成されます。ルールを使って、マルウェアファミリー、パッカー、エクスプロイトキット、特定のキャンペーン用ツールを検出できます。
ワークフロー
ステップ 1:静的解析 - PE の解析とハッシュの生成
import pefile
import hashlib
import os
def analyze_pe(filepath):
"""Extract IOCs from a PE file through static analysis."""
iocs = {"hashes": {}, "pe_info": {}, "strings": [], "imports": []}
# Calculate file hashes
with open(filepath, "rb") as f:
data = f.read()
iocs["hashes"]["md5"] = hashlib.md5(data).hexdigest()
iocs["hashes"]["sha1"] = hashlib.sha1(data).hexdigest()
iocs["hashes"]["sha256"] = hashlib.sha256(data).hexdigest()
iocs["hashes"]["file_size"] = len(data)
# Parse PE headers
try:
pe = pefile.PE(filepath)
iocs["hashes"]["imphash"] = pe.get_imphash()
iocs["pe_info"]["compilation_time"] = str(pe.FILE_HEADER.TimeDateStamp)
iocs["pe_info"]["machine_type"] = hex(pe.FILE_HEADER.Machine)
iocs["pe_info"]["subsystem"] = pe.OPTIONAL_HEADER.Subsystem
# Extract sections
iocs["pe_info"]["sections"] = []
for section in pe.sections:
iocs["pe_info"]["sections"].append({
"name": section.Name.decode("utf-8", errors="ignore").strip("\x00"),
"virtual_size": section.Misc_VirtualSize,
"raw_size": section.SizeOfRawData,
"entropy": section.get_entropy(),
"md5": section.get_hash_md5(),
})
# Extract imports
if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"):
for entry in pe.DIRECTORY_ENTRY_IMPORT:
dll_name = entry.dll.decode("utf-8", errors="ignore")
functions = [
imp.name.decode("utf-8", errors="ignore")
for imp in entry.imports
if imp.name
]
iocs["imports"].append({"dll": dll_name, "functions": functions})
# Check for suspicious characteristics
iocs["pe_info"]["is_dll"] = pe.is_dll()
iocs["pe_info"]["is_driver"] = pe.is_driver()
iocs["pe_info"]["is_exe"] = pe.is_exe()
# Version info
if hasattr(pe, "VS_VERSIONINFO"):
for entry in pe.FileInfo:
for st in entry:
for item in st.entries.items():
key = item[0].decode("utf-8", errors="ignore")
val = item[1].decode("utf-8", errors="ignore")
iocs["pe_info"][f"version_{key}"] = val
pe.close()
except pefile.PEFormatError as e:
iocs["pe_info"]["error"] = str(e)
return iocs
ステップ 2:文字列の抽出と IOC パターンのマッチング
import re
def extract_ioc_strings(filepath):
"""Extract IOC-relevant strings from binary file."""
patterns = {
"ipv4": re.compile(
r"\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}"
r"(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b"
),
"domain": re.compile(
r"\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+"
r"(?:com|net|org|io|ru|cn|tk|xyz|top|info|biz|cc|ws|pw)\b"
),
"url": re.compile(
r"https?://[^\s\"'<>]{5,200}"
),
"email": re.compile(
r"\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b"
),
"registry": re.compile(
r"(?:HKEY_[A-Z_]+|HKLM|HKCU|HKU|HKCR|HKCC)"
r"\\[\\a-zA-Z0-9_ .{}-]+"
),
"filepath_windows": re.compile(
r"[A-Z]:\\(?:[^\\/:*?\"<>|\r\n]+\\)*[^\\/:*?\"<>|\r\n]+"
),
"mutex": re.compile(
r"(?:Global\\|Local\\)[a-zA-Z0-9_\-{}.]{4,}"
),
"useragent": re.compile(
r"Mozilla/[45]\.0[^\"']{10,200}"
),
"bitcoin": re.compile(
r"\b[13][a-km-zA-HJ-NP-Z1-9]{25,34}\b"
),
"pdb_path": re.compile(
r"[A-Z]:\\[^\"]{5,200}\.pdb"
),
}
with open(filepath, "rb") as f:
data = f.read()
# Extract ASCII strings (min length 4)
ascii_strings = re.findall(rb"[\x20-\x7e]{4,}", data)
# Extract Unicode strings
unicode_strings = re.findall(
rb"(?:[\x20-\x7e]\x00){4,}", data
)
all_strings = [s.decode("ascii", errors="ignore") for s in ascii_strings]
all_strings += [
s.decode("utf-16-le", errors="ignore") for s in unicode_strings
]
extracted = {category: set() for category in patterns}
for string in all_strings:
for category, pattern in patterns.items():
matches = pattern.findall(string)
for match in matches:
extracted[category].add(match)
# Convert sets to sorted lists
return {k: sorted(v) for k, v in extracted.items() if v}
ステップ 3:YARA ルールによるスキャン
import yara
def scan_with_yara(filepath, rules_path):
"""Scan file with YARA rules for malware classification."""
rules = yara.compile(filepath=rules_path)
matches = rules.match(filepath)
results = []
for match in matches:
result = {
"rule": match.rule,
"namespace": match.namespace,
"tags": match.tags,
"meta": match.meta,
"strings": [],
}
for offset, identifier, data in match.strings:
result["strings"].append({
"offset": hex(offset),
"identifier": identifier,
"data": data.hex() if len(data) < 100 else data[:100].hex() + "...",
})
results.append(result)
return results
# Example YARA rule for common malware indicators
SAMPLE_YARA_RULE = """
rule Suspicious_Network_Indicators {
meta:
description = "Detects suspicious network-related strings"
author = "CTI Analyst"
severity = "medium"
strings:
$ua1 = "Mozilla/5.0" ascii
$cmd1 = "cmd.exe /c" ascii nocase
$ps1 = "powershell" ascii nocase
$wget = "wget" ascii nocase
$curl = "curl" ascii nocase
$b64 = "base64" ascii nocase
$reg1 = "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run" ascii nocase
condition:
uint16(0) == 0x5A4D and
(2 of ($ua1, $cmd1, $ps1, $wget, $curl, $b64)) or $reg1
}
rule Packed_Binary {
meta:
description = "Detects potentially packed binary"
author = "CTI Analyst"
condition:
uint16(0) == 0x5A4D and
for any section in pe.sections : (
section.entropy >= 7.0
)
}
"""
ステップ 4:STIX 2.1 指標の生成
from stix2 import (
Bundle, Indicator, Malware, Relationship,
File as STIXFile, DomainName, IPv4Address,
ObservedData,
)
from datetime import datetime
def create_stix_bundle(pe_iocs, string_iocs, yara_results, sample_name):
"""Create STIX 2.1 bundle from extracted IOCs."""
objects = []
# Create Malware SDO
malware = Malware(
name=sample_name,
is_family=False,
malware_types=["unknown"],
description=f"Malware sample analyzed: {pe_iocs['hashes']['sha256']}",
allow_custom=True,
)
objects.append(malware)
# File hash indicator
sha256 = pe_iocs["hashes"]["sha256"]
hash_indicator = Indicator(
name=f"Malware hash: {sha256[:16]}...",
pattern=f"[file:hashes.'SHA-256' = '{sha256}']",
pattern_type="stix",
valid_from=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
indicator_types=["malicious-activity"],
allow_custom=True,
)
objects.append(hash_indicator)
objects.append(Relationship(
relationship_type="indicates",
source_ref=hash_indicator.id,
target_ref=malware.id,
))
# Network indicators from strings
for ip in string_iocs.get("ipv4", []):
if not ip.startswith(("10.", "172.", "192.168.", "127.")):
ip_indicator = Indicator(
name=f"C2 IP: {ip}",
pattern=f"[ipv4-addr:value = '{ip}']",
pattern_type="stix",
valid_from=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
indicator_types=["malicious-activity"],
allow_custom=True,
)
objects.append(ip_indicator)
objects.append(Relationship(
relationship_type="indicates",
source_ref=ip_indicator.id,
target_ref=malware.id,
))
for domain in string_iocs.get("domain", []):
domain_indicator = Indicator(
name=f"C2 Domain: {domain}",
pattern=f"[domain-name:value = '{domain}']",
pattern_type="stix",
valid_from=datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
indicator_types=["malicious-activity"],
allow_custom=True,
)
objects.append(domain_indicator)
objects.append(Relationship(
relationship_type="indicates",
source_ref=domain_indicator.id,
target_ref=malware.id,
))
bundle = Bundle(objects=objects, allow_custom=True)
return bundle
検証基準
- PE ファイルが正常に解析され、ハッシュ、インポート情報、セクション分析が得られている
- 文字列抽出によってネットワーク IOC(IP、ドメイン、URL)が特定されている
- YARA ルールが既知のマルウェアの特徴に一致している
- STIX 2.1 バンドルに有効な Indicator オブジェクトと Malware オブジェクトが含まれている
- プライベート IP アドレス範囲と無害な文字列が IOC 出力から除外されている
- IOC がブロックや検知ルールの作成に活用できる
参考資料
コメント
Markdown 対応