From 4d044d1ecfa3b66326d940357ad960f39928b5df Mon Sep 17 00:00:00 2001 From: ecrou-exact Date: Thu, 20 Nov 2025 19:59:19 +0100 Subject: [PATCH 1/5] Refactor YARA validator: unify validation logic into handler(), add auto-imports, external variable handling --- .../expansion/yara_syntax_validator.py | 95 +++++++++++++++++-- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/misp_modules/modules/expansion/yara_syntax_validator.py b/misp_modules/modules/expansion/yara_syntax_validator.py index cf02dd1e..0994863a 100644 --- a/misp_modules/modules/expansion/yara_syntax_validator.py +++ b/misp_modules/modules/expansion/yara_syntax_validator.py @@ -1,6 +1,6 @@ import json - import yara +import re misperrors = {"error": "Error"} mispattributes = {"input": ["yara"], "output": ["text"]} @@ -22,23 +22,98 @@ } moduleconfig = [] +# List of most uses Modules in yara + +YARA_MODULES = {"pe", "math", "cuckoo", "magic", "hash", "dotnet", "elf", "macho"} + +# YARA rules can reference internal modules such as `pe`, `math`, `cuckoo`, +# `elf`, etc. Normally, a rule must explicitly import these modules using: +# import "pe" +# import "elf" +# +# Even if the YARA rule itself is perfectly valid, it will fail to compile +# if the correct modules are NOT imported. For example, references such as: +# pe.entry_point +# or elf.sections +# will raise "undefined identifier" errors unless the appropriate imports +# are present. + +def insert_import_module(rule_text, module_name): + lines = rule_text.strip().splitlines() + if not any(line.strip().startswith(f'import "{module_name}"') for line in lines): + return f'import "{module_name}"\n' + rule_text + return rule_text + + +# ------------------------------- +# HANDLER NOW DOES VALIDATION LOGIC +# ------------------------------- + +# ------------------------------------------------------------------------- +# To make the validator more user-friendly, we automatically insert missing +# imports when YARA reports an undefined identifier matching a known module. +# +# Additionally, YARA rules sometimes use *external variables*, for example: +# rule test { condition: filename == "test.exe" } +# +# If these variables are not provided, YARA will also fail, even though the +# rule is syntactically valid. To prevent unnecessary failures, the validator +# automatically assigns dummy values to any missing external variables. +# +# This ensures: +# - A clean, user-friendly validation process +# - Correct detection of real syntax errors +# - No false negatives caused by missing imports or missing externals +# ------------------------------------------------------------------------- def handler(q=False): if q is False: return False + request = json.loads(q) - if not request.get("yara"): + rule_content = request.get("yara") + + if not rule_content: misperrors["error"] = "Yara rule missing" return misperrors - try: - yara.compile(source=request.get("yara")) - summary = "Syntax valid" - except Exception as e: - summary = "Syntax error: " + str(e) + externals = {} + attempts = 0 + max_attempts = 10 # to prevent from infinite loop + current_rule_text = rule_content - r = {"results": [{"types": mispattributes["output"], "values": summary}]} - return r + while attempts < max_attempts: + try: + yara.compile(source=current_rule_text, externals=externals) # some times the compilator needs externals variables + summary = "Syntax valid" + break + except yara.SyntaxError as e: + error_msg = str(e) + + # try to catch modules or externals variables errors to auto correct it + match_id = re.search(r'undefined identifier "(\w+)"', error_msg) + if match_id: + var_name = match_id.group(1) + + # Auto-import YARA modules + if var_name in YARA_MODULES: + current_rule_text = insert_import_module(current_rule_text, var_name) + else: + # Treat as external variable + 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) + + attempts += 1 + continue + + # Other syntax errors + summary = "Syntax error: " + error_msg + break + + else: + # Max attempts exceeded + summary = "Syntax error: Max validation attempts exceeded" + + return {"results": [{"types": mispattributes["output"], "values": summary}]} def introspection(): @@ -48,3 +123,5 @@ def introspection(): def version(): moduleinfo["config"] = moduleconfig return moduleinfo + + From 917ecbf0f161c8ca8442390e1fcc26e05076bc73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?GEFFE=20Th=C3=A9o?= <100565171+ecrou-exact@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:09:54 +0100 Subject: [PATCH 2/5] Refactor YARA syntax validation handler --- .../modules/expansion/yara_syntax_validator.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/misp_modules/modules/expansion/yara_syntax_validator.py b/misp_modules/modules/expansion/yara_syntax_validator.py index 0994863a..e793171d 100644 --- a/misp_modules/modules/expansion/yara_syntax_validator.py +++ b/misp_modules/modules/expansion/yara_syntax_validator.py @@ -44,7 +44,6 @@ def insert_import_module(rule_text, module_name): return f'import "{module_name}"\n' + rule_text return rule_text - # ------------------------------- # HANDLER NOW DOES VALIDATION LOGIC # ------------------------------- @@ -69,19 +68,15 @@ def insert_import_module(rule_text, module_name): def handler(q=False): if q is False: return False - request = json.loads(q) rule_content = request.get("yara") - if not rule_content: misperrors["error"] = "Yara rule missing" return misperrors - externals = {} attempts = 0 max_attempts = 10 # to prevent from infinite loop current_rule_text = rule_content - while attempts < max_attempts: try: yara.compile(source=current_rule_text, externals=externals) # some times the compilator needs externals variables @@ -89,39 +84,29 @@ def handler(q=False): break except yara.SyntaxError as e: error_msg = str(e) - # try to catch modules or externals variables errors to auto correct it match_id = re.search(r'undefined identifier "(\w+)"', error_msg) if match_id: var_name = match_id.group(1) - # Auto-import YARA modules if var_name in YARA_MODULES: current_rule_text = insert_import_module(current_rule_text, var_name) else: # Treat as external variable 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) - attempts += 1 continue - # Other syntax errors summary = "Syntax error: " + error_msg break - else: # Max attempts exceeded summary = "Syntax error: Max validation attempts exceeded" - return {"results": [{"types": mispattributes["output"], "values": summary}]} - def introspection(): return mispattributes - def version(): moduleinfo["config"] = moduleconfig return moduleinfo - - From 0044a0341ff8b92db15264bdf3a4acdb0fceef9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?GEFFE=20Th=C3=A9o?= <100565171+ecrou-exact@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:22:39 +0100 Subject: [PATCH 3/5] Fix comments and improve readability in yara_syntax_validator --- misp_modules/modules/expansion/yara_syntax_validator.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/misp_modules/modules/expansion/yara_syntax_validator.py b/misp_modules/modules/expansion/yara_syntax_validator.py index e793171d..2f5b092c 100644 --- a/misp_modules/modules/expansion/yara_syntax_validator.py +++ b/misp_modules/modules/expansion/yara_syntax_validator.py @@ -79,12 +79,12 @@ def handler(q=False): current_rule_text = rule_content while attempts < max_attempts: try: - yara.compile(source=current_rule_text, externals=externals) # some times the compilator needs externals variables + yara.compile(source=current_rule_text, externals=externals) # some times the compilator needs externals variables summary = "Syntax valid" break except yara.SyntaxError as e: error_msg = str(e) - # try to catch modules or externals variables errors to auto correct it + # try to catch modules or externals variables errors to auto correct it match_id = re.search(r'undefined identifier "(\w+)"', error_msg) if match_id: var_name = match_id.group(1) @@ -93,7 +93,7 @@ def handler(q=False): current_rule_text = insert_import_module(current_rule_text, var_name) else: # Treat as external variable - 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) + 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) attempts += 1 continue # Other syntax errors @@ -104,9 +104,11 @@ def handler(q=False): summary = "Syntax error: Max validation attempts exceeded" return {"results": [{"types": mispattributes["output"], "values": summary}]} + def introspection(): return mispattributes + def version(): moduleinfo["config"] = moduleconfig return moduleinfo From 0a178b90f98565ba85837dff33c4044db197b070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?GEFFE=20Th=C3=A9o?= <100565171+ecrou-exact@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:44:36 +0100 Subject: [PATCH 4/5] Update version and author in YARA syntax validator --- misp_modules/modules/expansion/yara_syntax_validator.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/misp_modules/modules/expansion/yara_syntax_validator.py b/misp_modules/modules/expansion/yara_syntax_validator.py index 2f5b092c..b4eb32fe 100644 --- a/misp_modules/modules/expansion/yara_syntax_validator.py +++ b/misp_modules/modules/expansion/yara_syntax_validator.py @@ -5,8 +5,8 @@ misperrors = {"error": "Error"} mispattributes = {"input": ["yara"], "output": ["text"]} moduleinfo = { - "version": "0.1", - "author": "Dennis Rand", + "version": "0.2", + "author": "Dennis Rand and Theo Geffe", "description": "An expansion hover module to perform a syntax check on if yara rules are valid or not.", "module-type": ["hover"], "name": "YARA Syntax Validator", @@ -15,6 +15,7 @@ "features": ( "This modules simply takes a YARA rule as input, and checks its syntax. It returns then a confirmation if the" " syntax is valid, otherwise the syntax error is displayed." + " This modules can compile YARA rule even if there is externals variables or miss modules" ), "references": ["http://virustotal.github.io/yara/"], "input": "YARA rule attribute.", @@ -38,6 +39,7 @@ # will raise "undefined identifier" errors unless the appropriate imports # are present. + def insert_import_module(rule_text, module_name): lines = rule_text.strip().splitlines() if not any(line.strip().startswith(f'import "{module_name}"') for line in lines): @@ -65,6 +67,7 @@ def insert_import_module(rule_text, module_name): # - No false negatives caused by missing imports or missing externals # ------------------------------------------------------------------------- + def handler(q=False): if q is False: return False @@ -104,11 +107,9 @@ def handler(q=False): summary = "Syntax error: Max validation attempts exceeded" return {"results": [{"types": mispattributes["output"], "values": summary}]} - def introspection(): return mispattributes - def version(): moduleinfo["config"] = moduleconfig return moduleinfo From c705736fb3d1f2f2d5737acca374626bfc8ec39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?GEFFE=20Th=C3=A9o?= <100565171+ecrou-exact@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:47:02 +0100 Subject: [PATCH 5/5] Fix return statement indentation in version function --- misp_modules/modules/expansion/yara_syntax_validator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/misp_modules/modules/expansion/yara_syntax_validator.py b/misp_modules/modules/expansion/yara_syntax_validator.py index b4eb32fe..52763014 100644 --- a/misp_modules/modules/expansion/yara_syntax_validator.py +++ b/misp_modules/modules/expansion/yara_syntax_validator.py @@ -107,9 +107,11 @@ def handler(q=False): summary = "Syntax error: Max validation attempts exceeded" return {"results": [{"types": mispattributes["output"], "values": summary}]} + def introspection(): return mispattributes + def version(): moduleinfo["config"] = moduleconfig return moduleinfo