Skip to content
All posts

· 7 min read

Sigma and YARA Rules with AI, From Draft to Tested Detection

Draft Sigma rules from attack descriptions and YARA rules from extracted strings with Odin v1, then tune false positives, convert to SIEM queries and test.

Writing detection rules is mostly translation work: an attack description or a pile of extracted strings goes in, a precise rule comes out. A language model is good at that translation, as long as it does not refuse to discuss the attacker behavior you are trying to detect. This guide shows how to use Odin v1 to draft Sigma and YARA rules, tune them, convert them to SIEM queries and test them before production.

Where a model helps, and where it does not

Odin v1 speeds up the slow but not hard parts: turning a threat report paragraph into a first Sigma rule, picking candidate strings out of a long strings dump, suggesting filters for noisy hits, and explaining why a rule did or did not fire.

It does not replace testing. A model has never seen your environment or your log pipeline. Treat every generated rule as a draft: validate the syntax with real tools, run it against benign data, then against a known-bad sample in a lab.

Sigma rules from an attack description

Start with a short, concrete behavior description. Vague input ("detect malicious PowerShell") gets vague rules. A good prompt names the log source, the behavior and the noise you already know about:

Write a Sigma rule for Windows process creation logs (Sysmon Event ID 1 or Security 4688 with command line auditing). Detect powershell.exe or pwsh.exe started with -EncodedCommand or any of its short forms (-e, -ec, -en, -enc). Match on Image and OriginalFileName so renamed binaries are caught. Our SCCM client (C:\Windows\CCM\CcmExec.exe) launches encoded commands legitimately, so filter it. Include ATT&CK tags, false positives and a level.

Expect a complete YAML rule plus notes on tradeoffs: -e with surrounding spaces avoids matching -ExecutionPolicy, PowerShell accepts any unambiguous parameter prefix, and a parent-based filter can be abused by an attacker who controls that parent. After review, the rule looks like this:

yaml
title: PowerShell Started With an Encoded Commandid: 3f6b2a9e-5c1d-4e8a-9b27-0d4c8e1f7a63status: experimentaldescription: Detects powershell.exe or pwsh.exe launched with the -EncodedCommand parameter or one of its short forms, often used to hide script content.references:    - https://attack.mitre.org/techniques/T1059/001/author: InterOdin teamdate: 2026-10-20tags:    - attack.execution    - attack.t1059.001    - attack.t1027logsource:    category: process_creation    product: windowsdetection:    selection_img:        - Image|endswith:              - '\powershell.exe'              - '\pwsh.exe'        - OriginalFileName:              - 'PowerShell.EXE'              - 'pwsh.dll'    selection_cli:        CommandLine|contains:            - ' -e '            - ' -ec '            - ' -en '            - ' -enc '            - ' -enco'    filter_main_sccm:        ParentImage: 'C:\Windows\CCM\CcmExec.exe'    condition: all of selection_* and not 1 of filter_main_*falsepositives:    - Configuration management agents and admin scripts that pass encoded commandslevel: medium

The same approach works for living-off-the-land binaries (LOLBins). Describe one abuse pattern per rule, for example "certutil.exe with urlcache or verifyctl fetching a file over HTTP":

yaml
title: Certutil Used to Download a Remote Fileid: 8c41d0e7-2b6f-4a59-a3d8-61e9f2b07c14status: experimentaldescription: Detects certutil.exe called with urlcache or verifyctl and a URL on the command line, a common living-off-the-land download technique.references:    - https://attack.mitre.org/techniques/T1105/author: InterOdin teamdate: 2026-10-20tags:    - attack.command-and-control    - attack.t1105logsource:    category: process_creation    product: windowsdetection:    selection_img:        - Image|endswith: '\certutil.exe'        - OriginalFileName: 'CertUtil.exe'    selection_flags:        CommandLine|contains:            - 'urlcache'            - 'verifyctl'    selection_url:        CommandLine|contains:            - 'http://'            - 'https://'    condition: all of selection_*falsepositives:    - Rare administrative use to fetch certificates or revocation listslevel: high

Generate a fresh UUID for every rule yourself; never reuse an id from an example.

YARA rules from extracted strings

For YARA, the input is the output of your static analysis: strings or FLOSS output, PE metadata, notable byte sequences from a disassembler. Odin v1 accepts text only, so paste these as text. Its 1,000,000-token context window means you can include strings from several family members at once, which is exactly what you want: a good family rule is built from strings that are shared across samples and rare in clean software.

Below are FLOSS string dumps from four samples of the same loader family, collected in our malware lab. Pick strings that appear in at least three samples and are unlikely in benign software. Avoid API names, compiler artifacts and common library strings. Write a YARA rule for PE files under 2 MB and explain why each string was chosen.

Expect a rule, the reason for each chosen string, and the strings it rejected (such as kernel32.dll or a stock browser user agent). For the fictional family from our malware triage walkthrough, the result looks like this:

yara
rule SyncHost_Loader_Strings{    meta:        description = "Fictional loader family used as an example on the InterOdin blog"        author = "InterOdin team"        date = "2026-10-20"        reference = "Synthetic strings, not a real malware family"
    strings:        $pdb   = "C:\\build\\synchost\\Release\\loader.pdb" ascii        $mutex = "Global\\SyncHostUpdateMtx" ascii wide        $ua    = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) SyncHost/2.1" ascii        $task  = "OneDrive Sync Helper" ascii wide        $path  = "/a/upd.dat" ascii        $cfg   = { 53 48 43 46 47 01 00 ?? ?? 00 00 }
    condition:        uint16(0) == 0x5A4D and        filesize < 2MB and        ( $pdb or $cfg or 3 of ($mutex, $ua, $task, $path) )}

One high-confidence string (the PDB path or the config header) is enough on its own, while weaker strings must appear together. Ask the model to justify each string; if it cannot, drop the string.

Tuning false positives

A rule that fires 400 times a day gets disabled. Tuning is where a model saves the most time: the work is reading many noisy hits and spotting patterns. Export hits aggregated by parent image, user and command line, redact hostnames and usernames you do not need, and ask:

These are 7 days of hits for the encoded PowerShell rule, grouped by ParentImage and CommandLine prefix. Which groups look like legitimate software? Propose Sigma filters that are as narrow as possible, and tell me what an attacker would need to control to abuse each filter.

Good tuning habits to enforce in the prompt and in review:

  • Filter on full paths plus parent process, not on a substring of the command line that an attacker can copy.
  • Use the Sigma convention of filter_main_* for filters that apply everywhere and filter_optional_* for environment-specific ones.
  • Lower the level instead of adding a filter when you cannot make the filter narrow.
  • For YARA, scan a large goodware set (a clean Windows install, your software repository) and remove any string that hits it.

Converting Sigma to a SIEM query

Sigma rules are converted with sigma-cli, which uses pySigma backends and processing pipelines to map generic fields to your log schema:

bash
pip install sigma-clisigma plugin install splunksigma plugin install sysmonsigma check rules/sigma convert -t splunk -p sysmon rules/powershell_encoded_command.yml

For the encoded PowerShell rule, the Splunk output is:

text
EventID=1 Image IN ("*\\powershell.exe", "*\\pwsh.exe") OR OriginalFileName IN ("PowerShell.EXE", "pwsh.dll") CommandLine IN ("* -e *", "* -ec *", "* -en *", "* -enc *", "* -enco*") NOT ParentImage="C:\\Windows\\CCM\\CcmExec.exe"

For Elastic, install the elasticsearch plugin and use -t lucene -p ecs_windows. Prefer the tool over asking the model to hand-convert: backends handle escaping and field mapping consistently.

Testing against benign logs and lab samples

Every rule needs two tests before deployment.

A negative test. Run the rule over a known-clean baseline, such as a week of process creation logs from lab machines and a few representative workstations. Tools like Chainsaw and Zircolite run Sigma rules directly against EVTX exports, or you can run the converted query in your SIEM over a quiet time window. Any hit is a false positive to explain or filter.

A positive test. Trigger the behavior in an isolated lab VM and confirm the rule fires. For the encoded PowerShell rule, a harmless command is enough:

powershell
$cmd = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes("Write-Output 'sigma test'"))powershell.exe -NoProfile -EncodedCommand $cmd

For YARA, the equivalent is:

bash
yara -s -r synchost.yar ./family_samples/   # should match every known family memberyara -r synchost.yar ./goodware/            # should print nothing

Keep real samples inside the analysis VM, never on a workstation.

Automating the draft and check loop

The script below sends a behavior description to Odin v1, extracts the YAML, parses it with pySigma (which raises an error on invalid rules) and prints the Splunk query. Install the dependencies with pip install openai sigma-cli pysigma-backend-splunk pysigma-pipeline-sysmon.

python
import osimport reimport sys
from openai import OpenAIfrom sigma.backends.splunk import SplunkBackendfrom sigma.collection import SigmaCollectionfrom sigma.pipelines.sysmon import sysmon_pipeline
client = OpenAI(base_url="https://api.interodin.com/v1", api_key=os.environ["INTERODIN_API_KEY"])
SYSTEM = (    "You are a detection engineer. Write exactly one Sigma rule in YAML for the behavior "    "described. Use logsource category process_creation and product windows, use Sigma "    "modifiers, list realistic false positives, and return only the YAML.")

def draft_rule(description: str) -> str:    resp = client.chat.completions.create(        model="odin-v1",        messages=[{"role": "system", "content": SYSTEM}, {"role": "user", "content": description}],    )    text = resp.choices[0].message.content    fenced = re.search(r"`{3}(?:ya?ml)?\n(.*?)`{3}", text, re.DOTALL)    return fenced.group(1) if fenced else text

def to_splunk(rule_yaml: str) -> str:    rules = SigmaCollection.from_yaml(rule_yaml)    return SplunkBackend(sysmon_pipeline()).convert(rules)[0]

if __name__ == "__main__":    rule = draft_rule(sys.stdin.read())    print(rule)    print("--- Splunk ---")    print(to_splunk(rule))

Run it as python draft_sigma.py < behavior.txt. If parsing fails, paste the error back to the model and ask for a fix. For a harness that runs YARA rules against sample and goodware folders, see building security tooling with Claude Code on Odin. The OpenAI SDK guide covers client setup.

Get started

Create an API key in the console, follow the quickstart, and point the script above at a behavior from your latest threat report. Keep sample handling inside your lab and your work within the Acceptable Use Policy.