├── .gitignore
├── LICENSE
├── README.md
├── additional-files
├── WinSigmaRuleAnalyzer-Thumbnail.jpg
└── WinSigmaRuleAnalyzer.pdf
├── main.py
└── mapping data
├── channel.json
└── eventIds.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 |
162 |
163 | venv/
164 | sigma/
165 | output/
166 | *.csv
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 rowham
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sigma Windows Rule Analyzer
2 |
3 | This repository provides a simple script to parse [#Sigma](https://github.com/SigmaHQ/sigma) rules and map the event IDs to their corresponding channels or providers. The script downloads Sigma rules from sigma rule repository and produces a CSV file with the mapped event IDs, their corresponding channels or providers, and the rule type (category or service).
4 |
5 |
6 |
7 |
8 |
9 |
10 | ## Overview
11 |
12 | The script performs the following tasks:
13 |
14 | 1. **Clone Sigma Repository:** If the Sigma repository doesn't exist locally, it is cloned from [https://github.com/Neo23x0/sigma.git](https://github.com/Neo23x0/sigma.git).
15 |
16 | 2. **Read Mapping Files:** Event IDs and channel mappings are loaded from `mapping data/eventIds.json` and `mapping data/channel.json`, respectively.
17 |
18 | 3. **Parse Sigma Rules:** Sigma rules are parsed to extract relevant information such as type, category, service, event IDs, and detection level.
19 |
20 | 4. **Process Sigma Directory:** All Sigma rule files in the specified directory (`sigma/rules/windows/`) are processed to generate statistics.
21 |
22 | 5. **Write Results to CSV:** The results are written to a CSV file in the `output` directory, including information such as rule type, service or category, channel/provider, event IDs, and rule counts based on severity levels.
23 |
24 |
25 | ## Usage
26 |
27 | The script requires only Python and Git to be installed. Just clone the [WinSigmaRuleAnalyzer](https://github.com/rowham/WinSigmaRuleAnalyzer.git) repository and run the main.py file.
28 |
29 | ```
30 | git clone https://github.com/rowham/WinSigmaRuleAnalyzer.git
31 | python3 main.py
32 | ```
33 |
34 | ## Output
35 |
36 | The script will produce a CSV file named `sigma_results_.csv` in the `output` directory. The CSV file will have the following columns:
37 |
38 | - `Type`: The type of the rule (category or service)
39 | - `Service or Category`: The service or category of the rule
40 | - Sigma rules contain either a service field OR a category. [Refer to the official Sigma Rule documentation (Rule-Creation-Guide#log-source)](https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-Guide#log-source).
41 | - `Channel / Provider`: The corresponding channel or provider for the event IDs
42 | - This column has been enriched by the script, aiding in the identification of entries related to Sysmon or Powershell.
43 | - `Event IDs`: A comma-separated list of event IDs
44 | - This column contains either EventIDs used in the rules or is enriched by the script. Though not perfect, it provides an overview. [Check the sigma log source configuration](https://github.com/SigmaHQ/sigma/tree/master/documentation/logsource-guides/windows).
45 | - `Level`: This column displays the number of rules per 'Service or Category' at each level.
46 | In theory, higher level triggers more critical alert.
47 | For more information about levels in Sigma rules, [refer to the documentation (Rule-Creation-Guide#level)](https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-Guide#level).
48 | - `Informational`: The number of informational rules
49 | - `Low`: The number of low-priority rules
50 | - `Medium`: The number of medium-priority rules
51 | - `High`: The number of high-priority rules
52 | - `Critical`: The number of critical rules
53 | - `Total Rules in Service or Category`: The total number of rules in the service or category
54 | - This column shows the quantity of rules per 'Service or Category'.
55 | It would be beneficial to understand the number of Sigma rules that can be implemented for a given log source or a win channel.
56 |
57 | The output CSV file will contain the mapped event IDs, their corresponding channels or providers, and the rule type (category or service) for all the Sigma rules in the `sigma/rules/windows` directory.
58 |
59 | ## Notes
60 |
61 | - The script may output warnings for certain rule files with missing service or category information or unexpected level values.
62 | - Feel free to customize this simple script or contribute to its development.
63 |
64 | ## License
65 | **MIT License**
66 |
67 | Licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
68 |
69 | ## Visualized Report
70 |
71 | A sample visualized report is assembled with the assistance of the CSV file as input.
72 | [[Download the PDF - HQ]](additional-files%2FWinSigmaRuleAnalyzer.pdf)
73 |
--------------------------------------------------------------------------------
/additional-files/WinSigmaRuleAnalyzer-Thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rowham/WinSigmaRuleAnalyzer/a4277557a0fd0a316b55dc4aef8c8f5e66d2ed72/additional-files/WinSigmaRuleAnalyzer-Thumbnail.jpg
--------------------------------------------------------------------------------
/additional-files/WinSigmaRuleAnalyzer.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rowham/WinSigmaRuleAnalyzer/a4277557a0fd0a316b55dc4aef8c8f5e66d2ed72/additional-files/WinSigmaRuleAnalyzer.pdf
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | import os
2 | import csv
3 | from pathlib import Path
4 | import subprocess
5 | import re
6 | import yaml
7 | import json
8 | from datetime import datetime
9 |
10 | # Clone the Sigma repository
11 | repo_url = "https://github.com/Neo23x0/sigma.git"
12 | repo_path = "sigma"
13 | if not os.path.exists(repo_path):
14 | subprocess.run(["git", "clone", repo_url, repo_path])
15 |
16 | # Read eventIds.json and channel.json mapping files
17 | mapping_file_path = "mapping data/eventIds.json"
18 | channel_file_path = "mapping data/channel.json"
19 | with open(mapping_file_path, "r") as mapping_file, open(channel_file_path, "r") as channel_file:
20 | event_ids_mapping = json.load(mapping_file)
21 | channel_mapping = json.load(channel_file)
22 |
23 | # To Parse sigma rules
24 | def parse_sigma_rule(file_path):
25 | with open(file_path, "r", encoding="utf-8") as file:
26 | content = file.read()
27 |
28 | try:
29 | sigma_data = yaml.safe_load(content)
30 | detection = sigma_data.get('detection', {})
31 | except yaml.YAMLError as e:
32 | print(f"Error loading YAML in file {file_path}: {e}")
33 | return None, None, []
34 |
35 | category = None
36 | service = None
37 | category_match = re.search(r'logsource:(.*?category:(.*?)(\n|$))', content, re.DOTALL)
38 | service_match = re.search(r'logsource:(.*?service:(.*?)(\n|$))', content, re.DOTALL)
39 | level_match = re.search(r'level:\s+(\w+)', content, re.IGNORECASE)
40 |
41 | level = level_match.group(1).strip() if level_match else "N/A"
42 |
43 | if category_match:
44 | rule_type = "Category"
45 | category = category_match.group(2).strip()
46 |
47 | elif service_match:
48 | rule_type = "Service"
49 | service = service_match.group(2).strip()
50 | else:
51 | rule_type = "N/A"
52 | category = None
53 | service = None
54 |
55 | event_ids = get_event_ids_from_detection(detection, category)
56 |
57 | return rule_type, category, service, event_ids, level
58 |
59 | def get_event_ids_from_detection(detection, category):
60 | event_ids = set()
61 | if isinstance(detection, dict):
62 | for key, value in detection.items():
63 | if key == "EventID":
64 | if isinstance(value, list):
65 | event_ids.update(map(str, value))
66 | else:
67 | event_ids.add(str(value))
68 | elif isinstance(value, (dict, list)):
69 | event_ids.update(get_event_ids_from_detection(value, category))
70 | elif isinstance(detection, list):
71 | for item in detection:
72 | event_ids.update(get_event_ids_from_detection(item, category))
73 |
74 | mapped_ids = event_ids_mapping.get(category, [])
75 |
76 | if isinstance(mapped_ids, int):
77 | event_ids.add(str(mapped_ids))
78 | else:
79 | event_ids.update(map(str, mapped_ids))
80 |
81 | return event_ids
82 |
83 | # To process all Sigma rule files in a directory
84 | def process_sigma_directory(directory):
85 | results = {}
86 |
87 | for file_path in Path(directory).rglob("*.yml"):
88 | rule_type, category, service, event_ids, level = parse_sigma_rule(file_path)
89 | rule_key = category or service
90 |
91 | if not rule_key:
92 | rule_key = service
93 | print(f"Warning! One file has no service or category: {file_path}")
94 |
95 | if rule_key not in results:
96 | results[rule_key] = {"Type": rule_type, "Event IDs": set(), "File Count": 0}
97 |
98 | results[rule_key]["Event IDs"].update(event_ids)
99 | results[rule_key]["File Count"] += 1
100 |
101 | if level and level.lower() != 'n/a':
102 | level_key = level.capitalize()
103 | results[rule_key][level_key] = results[rule_key].get(level_key, 0) + 1
104 | else:
105 | print(f"Warning! Unexpected level value: {level} in file: {file_path}")
106 |
107 | return results
108 |
109 | # Process Sigma rules in the specified directory
110 | sigma_directory = "sigma/rules/windows/"
111 | results = process_sigma_directory(sigma_directory)
112 |
113 | # Write results to a CSV file
114 | os.makedirs('output', exist_ok=True)
115 | current_time = datetime.now().strftime("%Y%m%d%H%M%S")
116 | save_path = f"./output/sigma_results_{current_time}.csv"
117 |
118 | with open(save_path, "w", newline="") as csvfile:
119 | fieldnames = ["Type", "Service or Category", "Channel / Provider", "Event IDs", "Informational", "Low", "Medium", "High", "Critical", "Total Rules in Service or Category"]
120 | writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
121 | writer.writeheader()
122 |
123 | for rule_key, value in results.items():
124 | # Get "Channel / Provider" based on the "service or Category"
125 | channel_provider = channel_mapping.get(rule_key, "")
126 |
127 | writer.writerow({
128 | "Type": value["Type"],
129 | "Service or Category": rule_key,
130 | "Channel / Provider": channel_provider,
131 | "Event IDs": ", ".join(map(str, value["Event IDs"])),
132 | "Informational": value.get("Informational", 0),
133 | "Low": value.get("Low", 0),
134 | "Medium": value.get("Medium", 0),
135 | "High": value.get("High", 0),
136 | "Critical": value.get("Critical", 0),
137 | "Total Rules in Service or Category": value["File Count"]
138 | })
139 |
140 | print(f"Results written to {save_path}")
141 |
--------------------------------------------------------------------------------
/mapping data/channel.json:
--------------------------------------------------------------------------------
1 | {
2 | "clipboard_capture": "Microsoft-Windows-Sysmon",
3 | "create_remote_thread": "Microsoft-Windows-Sysmon",
4 | "create_stream_hash": "Microsoft-Windows-Sysmon",
5 | "dns_query": "Microsoft-Windows-Sysmon",
6 | "driver_load": "Microsoft-Windows-Sysmon",
7 | "file_access": "Microsoft-Windows-Kernel-File",
8 | "file_change": "Microsoft-Windows-Sysmon",
9 | "file_delete": "Microsoft-Windows-Sysmon",
10 | "file_event": "Microsoft-Windows-Sysmon",
11 | "file_rename": "Microsoft-Windows-Kernel-File",
12 | "image_load": "Microsoft-Windows-Sysmon",
13 | "network_connection": "Microsoft-Windows-Sysmon",
14 | "pipe_created": "Microsoft-Windows-Sysmon",
15 | "process_access": "Microsoft-Windows-Sysmon",
16 | "process_creation": "Microsoft-Windows-Sysmon",
17 | "process_tampering": "Microsoft-Windows-Sysmon",
18 | "process_termination": "Microsoft-Windows-Sysmon",
19 | "ps_classic_provider_start": "Windows PowerShell",
20 | "ps_classic_script": "Windows PowerShell",
21 | "ps_classic_start": "Windows PowerShell",
22 | "ps_module": "Microsoft-Windows-PowerShell - PowerShellCore",
23 | "ps_script": "Microsoft-Windows-PowerShell - PowerShellCore",
24 | "raw_access_thread": "Microsoft-Windows-Sysmon",
25 | "registry_add": "Microsoft-Windows-Sysmon",
26 | "registry_delete": "Microsoft-Windows-Sysmon",
27 | "registry_event": "Microsoft-Windows-Sysmon",
28 | "registry_rename": "Microsoft-Windows-Sysmon",
29 | "registry_set": "Microsoft-Windows-Sysmon",
30 | "sysmon_error": "Microsoft-Windows-Sysmon",
31 | "sysmon_status": "Microsoft-Windows-Sysmon",
32 | "wmi_event": "Microsoft-Windows-Sysmon",
33 | "application": "Application",
34 | "applocker": "Microsoft-Windows-AppLocker/MSI and Script - Microsoft-Windows-AppLocker/EXE and DLL - Microsoft-Windows-AppLocker/Packaged",
35 | "appmodel-runtime": "Microsoft-Windows-AppModel-Runtime/Admin",
36 | "appxdeployment-server": "Microsoft-Windows-AppXDeploymentServer",
37 | "appxpackaging-om": "Microsoft-Windows-AppxPackaging",
38 | "bitlocker": "Microsoft-Windows-BitLocker/BitLocker Management",
39 | "bits-client": "Microsoft-Windows-Bits-Client",
40 | "codeintegrity-operational": "Microsoft-Windows-CodeIntegrity",
41 | "dhcp": "Microsoft-Windows-DHCP-Server",
42 | "diagnosis-scripted": "Microsoft-Windows-Diagnosis-Scripted",
43 | "dns-client": "Microsoft-Windows-DNS Client Events",
44 | "dns-server": "DNS Server",
45 | "dns-server-audit": "Microsoft-Windows-DNS-Server/Audit",
46 | "dns-server-analytic": "Microsoft-Windows-DNS-Server/Analytical",
47 | "driver-framework": "Microsoft-Windows-DriverFrameworks-UserMode",
48 | "firewall-as": "Microsoft-Windows-Windows Firewall With Advanced Security/Firewall",
49 | "ldap_debug": "Microsoft-Windows-LDAP-Client/Debug",
50 | "ldap": "Microsoft-Windows-LDAP-Client/Debug",
51 | "lsa-server": "Microsoft-Windows-LSA",
52 | "microsoft-servicebus-client": "Microsoft-ServiceBus-Client",
53 | "msexchange-management": "MSExchange Management",
54 | "ntlm": "Microsoft-Windows-NTLM",
55 | "openssh": "OpenSSH",
56 | "powershell": "Microsoft-Windows-PowerShell",
57 | "powershell-classic": "Windows PowerShell",
58 | "printservice-admin": "Microsoft-Windows-PrintService/Admin",
59 | "printservice-operational": "Microsoft-Windows-PrintService",
60 | "security": "Security",
61 | "security-mitigations": "Microsoft-Windows-Security-Mitigations/Kernel Mode - Microsoft-Windows-Security-Mitigations/User Mode",
62 | "smbclient-security": "Microsoft-Windows-SmbClient/Security",
63 | "shell-core": "Microsoft-Windows-Shell-Core",
64 | "sysmon": "Microsoft-Windows-Sysmon",
65 | "system": "System",
66 | "taskscheduler": "Microsoft-Windows-TaskScheduler",
67 | "terminalservices-localsessionmanager": "Microsoft-Windows-TerminalServices-LocalSessionManager",
68 | "vhdmp": "Microsoft-Windows-VHDMP",
69 | "windefend": "Microsoft-Windows-Windows Defender",
70 | "wmi": "Microsoft-Windows-WMI-Activity",
71 | "certificateservicesclient-lifecycle-system": "Microsoft-Windows-CertificateServicesClient-Lifecycle-System",
72 | "capi2": "Microsoft-Windows-CAPI2"
73 | }
74 |
--------------------------------------------------------------------------------
/mapping data/eventIds.json:
--------------------------------------------------------------------------------
1 | {
2 | "process_creation": 1,
3 | "file_change": 2,
4 | "network_connection": 3,
5 | "process_termination": 5,
6 | "sysmon_status": [4, 16],
7 | "driver_load": 6,
8 | "image_load": 7,
9 | "create_remote_thread": 8,
10 | "raw_access_thread": 9,
11 | "process_access": 10,
12 | "file_event": 11,
13 | "registry_add": 12,
14 | "registry_delete": 12,
15 | "registry_set": 13,
16 | "registry_rename": 14,
17 | "registry_event": [12, 13, 14],
18 | "create_stream_hash": 15,
19 | "pipe_created": [17, 18],
20 | "wmi_event": [19, 20, 21],
21 | "dns_query": 22,
22 | "file_delete": [23, 26],
23 | "clipboard_capture": 24,
24 | "process_tampering": 25,
25 | "sysmon_error": 255,
26 |
27 | "ps_classic_provider_start": 600,
28 | "ps_classic_script": 800,
29 | "ps_classic_start": 400,
30 | "ps_module": 4103,
31 | "ps_script": 4104
32 | }
33 |
--------------------------------------------------------------------------------