mcpField
P
performing-yara-rule-development-for-detection

performing-yara-rule-development-for-detection

github.com/mukul975セキュリティ35.2k58 回インストール8 週間前に更新
リポジトリ

performing-yara-rule-development-for-detection について

マルウェア研究者やセキュリティアナリスト向けのYARAルール開発スキルです。特徴の選定、ルールのテスト、性能調整を扱い、マルウェアファミリーの検出を支援します。

主な機能

  1. 01特徴的な文字列やバイト列の抽出
  2. 02YARAマッチングルールの作成
  3. 03検出結果と誤検知のテスト
  4. 04スキャン性能の調整

活用シーン

  1. 01マルウェアファミリー向けルールの作成
  2. 02サンプル群を使ったルールの検証
  3. 03大量ファイルのスキャン時間の短縮

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill performing-yara-rule-development-for-detection

インストール後、Claude Code で/performing-yara-rule-development-for-detectionを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

LICENSE
assets/
template.md
references/
api-reference.md
standards.md
workflows.md
scripts/
agent.py
process.py

詳細

検知のための YARA ルール開発

概要

YARA は、マルウェア研究者がテキストまたはバイナリのパターンに基づいてマルウェアを特定・分類するための、パターンマッチングの万能ツールです。効果的な YARA ルールは、固有の文字列パターン、バイト列、PE ヘッダーの特性、インポートテーブルの分析、条件ロジックを組み合わせ、誤検知を回避しながらマルウェアファミリーを検知します。最新の YARA-X(Rust で書き直され、2025 年 6 月から安定版)は、パフォーマンスを向上させ、新しいモジュールを導入しています。ルールでは、パッカーのシグネチャではなく、ハードコードされたスタック文字列、C2 URL、ミューテックス名、暗号化定数、固有のコードシーケンスなど、展開済みのマルウェアの痕跡を対象にする必要があります。

使用する場面

  • 検知用の YARA ルール開発を伴うセキュリティ評価を実施する場合
  • 関連するセキュリティイベントのインシデント対応手順に従う場合
  • 定期的なセキュリティテストや監査を実施する場合
  • 実地テストを通じてセキュリティ制御を検証する場合

前提条件

  • yara-python ライブラリを含む Python 3.9 以降
  • YARA 4.5 以降または YARA-X 0.10 以降
  • PE 分析ツール(pefile、pestudio)
  • 固有のバイトパターンを特定するための 16 進エディター
  • マルウェアサンプルへのアクセス(VirusTotal、MalwareBazaar)
  • PE ファイル形式、文字列、インポートテーブルに関する知識

主な概念

ルールの構造

すべての YARA ルールは、3 つのセクションで構成されます。meta(任意の説明用メタデータ)、strings(パターン定義)、condition(マッチングロジック)です。文字列の種類には、テキスト文字列(ASCII/wide/nocase)、ワイルドカードやジャンプを含む 16 進パターン、正規表現があります。条件では、ブール演算子を使って文字列のマッチとファイルのプロパティを組み合わせます。

文字列の選定戦略

効果的なルールでは、マルウェアファミリーに固有で、再コンパイル後も維持されるパターンを対象にします。コンパイラーによって一貫して埋め込まれるため、ハードコードされたスタック文字列は優れた候補です。C2 ドメインのパターン、独自の暗号化ルーチン、固有のエラーメッセージ、特定の API 呼び出しシーケンスも、安定した検知の手掛かりになります。コンパイラーが生成する定型コードや、一般的なライブラリの文字列は避けてください。

パフォーマンスの最適化

YARA は条件を短絡評価します。識別力が最も高く、評価コストが最も低い条件を先に配置してください。filesize の上限を設定して、無関係なファイルをすばやく除外します。正規表現の使用を最小限に抑え、代わりに 16 進パターンを使用してください。private ルールは、単独のマッチを生成せずに複雑な検知ロジックを構築するための部品として使用します。

ワークフロー

ステップ 1:サンプルを分析して固有のパターンを特定する
#!/usr/bin/env python3
"""Extract candidate strings and byte patterns for YARA rule creation."""
import pefile
import re
import sys
from collections import Counter


def extract_strings(filepath, min_length=6):
    """Extract ASCII and wide strings from binary."""
    with open(filepath, 'rb') as f:
        data = f.read()

    # ASCII strings
    ascii_strings = re.findall(
        rb'[\x20-\x7e]{' + str(min_length).encode() + rb',}', data
    )

    # Wide (UTF-16LE) strings
    wide_strings = re.findall(
        rb'(?:[\x20-\x7e]\x00){' + str(min_length).encode() + rb',}', data
    )

    return {
        'ascii': [s.decode('ascii') for s in ascii_strings],
        'wide': [s.decode('utf-16-le') for s in wide_strings],
    }


def analyze_pe_imports(filepath):
    """Extract import table for API-based detection."""
    try:
        pe = pefile.PE(filepath)
    except pefile.PEFormatError:
        return []

    imports = []
    if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
        for entry in pe.DIRECTORY_ENTRY_IMPORT:
            dll_name = entry.dll.decode('utf-8', errors='replace')
            for imp in entry.imports:
                if imp.name:
                    func_name = imp.name.decode('utf-8', errors='replace')
                    imports.append(f"{dll_name}!{func_name}")
    return imports


def find_unique_byte_patterns(filepath, pattern_length=16):
    """Find unique byte sequences suitable for YARA hex patterns."""
    with open(filepath, 'rb') as f:
        data = f.read()

    try:
        pe = pefile.PE(filepath)
        # Focus on code section
        for section in pe.sections:
            if section.Characteristics & 0x20000000:  # IMAGE_SCN_MEM_EXECUTE
                code_start = section.PointerToRawData
                code_end = code_start + section.SizeOfRawData
                code_data = data[code_start:code_end]
                break
        else:
            code_data = data
    except Exception:
        code_data = data

    # Find byte patterns that appear exactly once
    patterns = []
    for i in range(0, len(code_data) - pattern_length, 4):
        pattern = code_data[i:i+pattern_length]
        if pattern.count(b'\x00') < pattern_length // 3:  # Skip null-heavy
            hex_pattern = ' '.join(f'{b:02X}' for b in pattern)
            patterns.append(hex_pattern)

    # Count frequency and return unique ones
    freq = Counter(patterns)
    unique = [p for p, count in freq.items() if count == 1]

    return unique[:20]  # Top 20 candidates


def suggest_rule_strings(filepath):
    """Suggest strings and patterns for YARA rule."""
    print(f"[+] Analyzing: {filepath}")

    # Extract strings
    strings = extract_strings(filepath)

    # Filter for suspicious/unique strings
    suspicious_keywords = [
        'http', 'https', 'cmd', 'powershell', 'mutex', 'pipe',
        'password', 'credential', 'inject', 'hook', 'debug',
        'sandbox', 'virtual', 'vmware', 'vbox',
    ]

    print("\n[+] Suspicious ASCII strings:")
    for s in strings['ascii']:
        if any(kw in s.lower() for kw in suspicious_keywords):
            print(f"  $ = \"{s}\" ascii")

    print("\n[+] Suspicious wide strings:")
    for s in strings['wide']:
        if any(kw in s.lower() for kw in suspicious_keywords):
            print(f"  $ = \"{s}\" wide")

    # Import analysis
    imports = analyze_pe_imports(filepath)
    suspicious_apis = [
        'VirtualAlloc', 'VirtualProtect', 'WriteProcessMemory',
        'CreateRemoteThread', 'NtUnmapViewOfSection', 'RtlMoveMemory',
        'OpenProcess', 'CreateToolhelp32Snapshot',
        'InternetOpenA', 'HttpSendRequestA',
        'CryptEncrypt', 'CryptDecrypt',
    ]

    print("\n[+] Suspicious imports:")
    for imp in imports:
        func = imp.split('!')[-1]
        if func in suspicious_apis:
            print(f"  {imp}")

    # Byte patterns
    print("\n[+] Candidate hex patterns:")
    patterns = find_unique_byte_patterns(filepath)
    for p in patterns[:5]:
        print(f"  $hex = {{ {p} }}")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <sample_path>")
        sys.exit(1)
    suggest_rule_strings(sys.argv[1])
ステップ 2:YARA ルールを作成してテストする
import yara
import os

def create_yara_rule(rule_name, meta, strings, condition):
    """Generate a YARA rule from components."""
    meta_str = "\n".join(f'        {k} = "{v}"' for k, v in meta.items())
    strings_str = "\n".join(f"        {s}" for s in strings)

    rule = f"""rule {rule_name} {{
    meta:
{meta_str}

    strings:
{strings_str}

    condition:
        {condition}
}}"""
    return rule


def test_yara_rule(rule_text, test_dir):
    """Compile and test YARA rule against sample directory."""
    try:
        rules = yara.compile(source=rule_text)
    except yara.SyntaxError as e:
        print(f"[-] YARA syntax error: {e}")
        return None

    results = {"matches": [], "no_match": []}

    for filename in os.listdir(test_dir):
        filepath = os.path.join(test_dir, filename)
        if not os.path.isfile(filepath):
            continue

        matches = rules.match(filepath)
        if matches:
            results["matches"].append({
                "file": filename,
                "rules": [m.rule for m in matches],
            })
        else:
            results["no_match"].append(filename)

    print(f"[+] Matches: {len(results['matches'])}")
    print(f"[-] No match: {len(results['no_match'])}")
    return results


# Example: Create a rule for a hypothetical malware family
example_rule = create_yara_rule(
    rule_name="MalwareFamily_Variant_A",
    meta={
        "description": "Detects MalwareFamily Variant A",
        "author": "Malware Analysis Team",
        "date": "2025-01-01",
        "hash": "abc123...",
        "tlp": "WHITE",
    },
    strings=[
        '$mutex = "Global\\\\UniqueM4lwareMutex" ascii wide',
        '$c2_pattern = /https?:\\/\\/[a-z]{5,10}\\.(xyz|top|buzz)\\/gate\\.php/',
        '$api1 = "VirtualAllocEx" ascii',
        '$api2 = "WriteProcessMemory" ascii',
        '$api3 = "CreateRemoteThread" ascii',
        '$hex_decrypt = { 8B 45 ?? 33 C1 89 45 ?? 83 C1 04 }',
        '$pdb = "C:\\\\Users\\\\" ascii',
    ],
    condition=(
        'uint16(0) == 0x5A4D and filesize < 2MB and '
        '($mutex or $c2_pattern) and '
        '2 of ($api*) and '
        '$hex_decrypt'
    ),
)

print(example_rule)
ステップ 3:パフォーマンスをテストして最適化する
import time

def benchmark_rule(rule_text, scan_directory, iterations=3):
    """Benchmark YARA rule scan performance."""
    rules = yara.compile(source=rule_text)

    files = []
    for root, _, filenames in os.walk(scan_directory):
        for f in filenames:
            files.append(os.path.join(root, f))

    print(f"[+] Benchmarking against {len(files)} files "
          f"({iterations} iterations)")

    times = []
    for i in range(iterations):
        start = time.perf_counter()
        matches = 0
        for filepath in files:
            try:
                result = rules.match(filepath)
                if result:
                    matches += 1
            except Exception:
                pass
        elapsed = time.perf_counter() - start
        times.append(elapsed)
        print(f"  Iteration {i+1}: {elapsed:.3f}s ({matches} matches)")

    avg_time = sum(times) / len(times)
    files_per_sec = len(files) / avg_time
    print(f"\n[+] Average: {avg_time:.3f}s ({files_per_sec:.0f} files/sec)")
    return avg_time

検証基準

  • YARA ルールが構文エラーなしでコンパイルできる
  • 対象のマルウェアファミリーのサンプルを、偽陰性なしで検知できる
  • クリーンなファイルのコーパスをスキャンしたとき、誤検知率が 0.1% 未満である
  • 1 秒あたり 1000 個以上のファイルをスキャンできるパフォーマンスがある
  • マルウェアの軽微な変更(再コンパイル、文字列の変更)後もルールが機能する
  • メタデータにハッシュ、作成者、日付、説明、TLP マーキングが含まれている

参考資料

コメント