Skip to content

Commit 6cd134d

Browse files
authored
Merge pull request #748 from MISP/theo_yara
Refactor YARA validator: unify validation logic into handler(), add a…
2 parents 98c9574 + c705736 commit 6cd134d

1 file changed

Lines changed: 80 additions & 13 deletions

File tree

misp_modules/modules/expansion/yara_syntax_validator.py

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import json
2-
32
import yara
3+
import re
44

55
misperrors = {"error": "Error"}
66
mispattributes = {"input": ["yara"], "output": ["text"]}
77
moduleinfo = {
8-
"version": "0.1",
9-
"author": "Dennis Rand",
8+
"version": "0.2",
9+
"author": "Dennis Rand and Theo Geffe",
1010
"description": "An expansion hover module to perform a syntax check on if yara rules are valid or not.",
1111
"module-type": ["hover"],
1212
"name": "YARA Syntax Validator",
@@ -15,30 +15,97 @@
1515
"features": (
1616
"This modules simply takes a YARA rule as input, and checks its syntax. It returns then a confirmation if the"
1717
" syntax is valid, otherwise the syntax error is displayed."
18+
" This modules can compile YARA rule even if there is externals variables or miss modules"
1819
),
1920
"references": ["http://virustotal.github.io/yara/"],
2021
"input": "YARA rule attribute.",
2122
"output": "Text to inform users if their rule is valid.",
2223
}
2324
moduleconfig = []
2425

26+
# List of most uses Modules in yara
27+
28+
YARA_MODULES = {"pe", "math", "cuckoo", "magic", "hash", "dotnet", "elf", "macho"}
29+
30+
# YARA rules can reference internal modules such as `pe`, `math`, `cuckoo`,
31+
# `elf`, etc. Normally, a rule must explicitly import these modules using:
32+
# import "pe"
33+
# import "elf"
34+
#
35+
# Even if the YARA rule itself is perfectly valid, it will fail to compile
36+
# if the correct modules are NOT imported. For example, references such as:
37+
# pe.entry_point
38+
# or elf.sections
39+
# will raise "undefined identifier" errors unless the appropriate imports
40+
# are present.
41+
42+
43+
def insert_import_module(rule_text, module_name):
44+
lines = rule_text.strip().splitlines()
45+
if not any(line.strip().startswith(f'import "{module_name}"') for line in lines):
46+
return f'import "{module_name}"\n' + rule_text
47+
return rule_text
48+
49+
# -------------------------------
50+
# HANDLER NOW DOES VALIDATION LOGIC
51+
# -------------------------------
52+
53+
# -------------------------------------------------------------------------
54+
# To make the validator more user-friendly, we automatically insert missing
55+
# imports when YARA reports an undefined identifier matching a known module.
56+
#
57+
# Additionally, YARA rules sometimes use *external variables*, for example:
58+
# rule test { condition: filename == "test.exe" }
59+
#
60+
# If these variables are not provided, YARA will also fail, even though the
61+
# rule is syntactically valid. To prevent unnecessary failures, the validator
62+
# automatically assigns dummy values to any missing external variables.
63+
#
64+
# This ensures:
65+
# - A clean, user-friendly validation process
66+
# - Correct detection of real syntax errors
67+
# - No false negatives caused by missing imports or missing externals
68+
# -------------------------------------------------------------------------
69+
2570

2671
def handler(q=False):
2772
if q is False:
2873
return False
2974
request = json.loads(q)
30-
if not request.get("yara"):
75+
rule_content = request.get("yara")
76+
if not rule_content:
3177
misperrors["error"] = "Yara rule missing"
3278
return misperrors
33-
34-
try:
35-
yara.compile(source=request.get("yara"))
36-
summary = "Syntax valid"
37-
except Exception as e:
38-
summary = "Syntax error: " + str(e)
39-
40-
r = {"results": [{"types": mispattributes["output"], "values": summary}]}
41-
return r
79+
externals = {}
80+
attempts = 0
81+
max_attempts = 10 # to prevent from infinite loop
82+
current_rule_text = rule_content
83+
while attempts < max_attempts:
84+
try:
85+
yara.compile(source=current_rule_text, externals=externals) # some times the compilator needs externals variables
86+
summary = "Syntax valid"
87+
break
88+
except yara.SyntaxError as e:
89+
error_msg = str(e)
90+
# try to catch modules or externals variables errors to auto correct it
91+
match_id = re.search(r'undefined identifier "(\w+)"', error_msg)
92+
if match_id:
93+
var_name = match_id.group(1)
94+
# Auto-import YARA modules
95+
if var_name in YARA_MODULES:
96+
current_rule_text = insert_import_module(current_rule_text, var_name)
97+
else:
98+
# Treat as external variable
99+
externals[var_name] = "example.txt" # a random value so that the compiler does not make an error (most of the time the external variable are in other configs files)
100+
attempts += 1
101+
continue
102+
# Other syntax errors
103+
summary = "Syntax error: " + error_msg
104+
break
105+
else:
106+
# Max attempts exceeded
107+
summary = "Syntax error: Max validation attempts exceeded"
108+
return {"results": [{"types": mispattributes["output"], "values": summary}]}
42109

43110

44111
def introspection():

0 commit comments

Comments
 (0)