mirror of
https://github.com/mandiant/capa.git
synced 2025-12-05 20:40:05 -08:00
cli: link to rule names to capa rules website (#2338)
* web: rules: redirect from various rule names to canonical rule URL closes #2319 Update index.html Co-authored-by: Moritz <mr-tz@users.noreply.github.com> * cli: link to rule names to capa rules website * just: make `just lint` run all steps, not fail on first error --------- Co-authored-by: Moritz <mr-tz@users.noreply.github.com>
This commit is contained in:
@@ -235,7 +235,7 @@ const contextMenuItems = computed(() => [
|
||||
label: "View rule in capa-rules",
|
||||
icon: "pi pi-external-link",
|
||||
target: "_blank",
|
||||
url: createCapaRulesUrl(selectedNode.value, props.data.meta.version)
|
||||
url: createCapaRulesUrl(selectedNode.value)
|
||||
},
|
||||
{
|
||||
label: "Lookup rule in VirusTotal",
|
||||
|
||||
@@ -62,9 +62,8 @@ export function createATTACKHref(attack) {
|
||||
*/
|
||||
export function createCapaRulesUrl(node, tag) {
|
||||
if (!node || !node.data || !tag) return null;
|
||||
const namespace = node.data.namespace || "lib";
|
||||
const ruleName = node.data.name.toLowerCase().replace(/\s+/g, "-");
|
||||
return `https://github.com/mandiant/capa-rules/blob/v${tag}/${namespace}/${ruleName}.yml`;
|
||||
return `https://mandiant.github.io/capa/rules/${ruleName}/`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -200,14 +200,14 @@
|
||||
<!-- TODO(williballenthin): add date -->
|
||||
<li>
|
||||
added:
|
||||
<a href="./rules/overwrite-dll-text-section-to-remove-hooks.html">
|
||||
<a href="./rules/overwrite DLL .text section to remove hooks/">
|
||||
overwrite DLL .text section to remove hooks
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
added:
|
||||
<a href="./rules/attach-bpf-to-socket-on-linux.html">
|
||||
<a href="./rules/attach BPF to socket on Linux/">
|
||||
attach BPF to socket on Linux
|
||||
</a>
|
||||
</li>
|
||||
|
||||
1
web/rules/.gitignore
vendored
1
web/rules/.gitignore
vendored
@@ -10,3 +10,4 @@ file_modification_dates.txt
|
||||
public/*.html
|
||||
public/pagefind/
|
||||
public/index.html
|
||||
public/
|
||||
|
||||
@@ -259,7 +259,6 @@ def generate_html(categories_data, color_map):
|
||||
for card in cards_data:
|
||||
first_word = get_first_word(card["namespace"])
|
||||
rectangle_color = color_map[first_word]
|
||||
file_name = card["filename"].rpartition(".yml")[0]
|
||||
|
||||
card_html = f"""
|
||||
<div class="card-wrapper">
|
||||
@@ -267,7 +266,7 @@ def generate_html(categories_data, color_map):
|
||||
<div class="thin-rectangle" style="background-color: {rectangle_color};"></div>
|
||||
<div class="card-body">
|
||||
<div class="namespace">{card['namespace']}</div>
|
||||
<div class="rule-name"><a href="./{file_name}.html">{card['name']}</a></div>
|
||||
<div class="rule-name"><a href="./{card['name']}/">{card['name']}</a></div>
|
||||
<div class="authors">{', '.join(card['authors'])}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ See the License for the specific language governing permissions and limitations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import urllib.parse
|
||||
from glob import glob
|
||||
from pathlib import Path
|
||||
@@ -20,6 +21,9 @@ from pygments.formatters import HtmlFormatter
|
||||
|
||||
import capa.rules
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
input_directory = Path(sys.argv[1])
|
||||
txt_file_path = Path(sys.argv[2])
|
||||
output_directory = Path(sys.argv[3])
|
||||
@@ -29,13 +33,13 @@ assert txt_file_path.exists(), "file-modification txt file must exist"
|
||||
assert output_directory.exists(), "output directory must exist"
|
||||
|
||||
|
||||
def convert_yaml_to_html(timestamps, yaml_file: Path, output_dir: Path):
|
||||
rule_content = yaml_file.read_text(encoding="utf-8")
|
||||
def render_rule(timestamps, path: Path) -> str:
|
||||
rule_content = path.read_text(encoding="utf-8")
|
||||
rule = capa.rules.Rule.from_yaml(rule_content, use_ruamel=True)
|
||||
|
||||
filename = os.path.basename(yaml_file).rpartition(".yml")[0]
|
||||
filename = path.with_suffix("").name
|
||||
namespace = rule.meta.get("namespace", "")
|
||||
timestamp = timestamps[yaml_file.as_posix()]
|
||||
timestamp = timestamps[path.as_posix()]
|
||||
|
||||
rendered_rule = pygments.highlight(
|
||||
rule_content,
|
||||
@@ -53,7 +57,7 @@ def convert_yaml_to_html(timestamps, yaml_file: Path, output_dir: Path):
|
||||
vt_fragment = urllib.parse.quote(urllib.parse.quote(vt_query))
|
||||
vt_link = f"https://www.virustotal.com/gui/search/{vt_fragment}/files"
|
||||
ns_query = f'"namespace: {namespace} "'
|
||||
ns_link = f"./?{urllib.parse.urlencode({'q': ns_query})}"
|
||||
ns_link = f"../?{urllib.parse.urlencode({'q': ns_query})}"
|
||||
|
||||
html_content = f"""
|
||||
<!DOCTYPE html>
|
||||
@@ -62,12 +66,12 @@ def convert_yaml_to_html(timestamps, yaml_file: Path, output_dir: Path):
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{rule.name}</title>
|
||||
<link rel="icon" href="./img/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="./css/bootstrap-5.3.3.min.css">
|
||||
<script src="./js/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="./js/bootstrap-5.3.3.bundle.min.js"></script>
|
||||
<script defer src="https://cloud.umami.is/script.js" data-website-id="0bb8ff9e-fbcc-4ee2-9f9f-b337a2e8cc7f"></script>
|
||||
<link rel="stylesheet" type="text/css" href="./css/style.css">
|
||||
<link rel="icon" href="../img/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="../css/bootstrap-5.3.3.min.css">
|
||||
<script src="../js/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="../js/bootstrap-5.3.3.bundle.min.js"></script>
|
||||
<script src="https://cloud.umami.is/script.js" defer data-website-id="0bb8ff9e-fbcc-4ee2-9f9f-b337a2e8cc7f"></script>
|
||||
<link rel="stylesheet" type="text/css" href="../css/style.css">
|
||||
<style>
|
||||
.rule-content .highlight pre {{
|
||||
overflow: visible;
|
||||
@@ -81,7 +85,7 @@ def convert_yaml_to_html(timestamps, yaml_file: Path, output_dir: Path):
|
||||
box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.05),inset 0 -1px 0 rgba(0,0,0,0.15);"
|
||||
>
|
||||
<a href="/capa/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto">
|
||||
<img src="./img/logo.png" height=48 />
|
||||
<img src="../img/logo.png" height=48 />
|
||||
</a>
|
||||
|
||||
<ul class="nav nav-pills">
|
||||
@@ -115,9 +119,7 @@ def convert_yaml_to_html(timestamps, yaml_file: Path, output_dir: Path):
|
||||
</html>
|
||||
"""
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
output_file_path = output_dir / (filename + ".html")
|
||||
output_file_path.write_text(html_content, encoding="utf-8")
|
||||
return html_content
|
||||
|
||||
|
||||
yaml_files = glob(os.path.join(input_directory, "**/*.yml"), recursive=True)
|
||||
@@ -129,8 +131,41 @@ for line in txt_file_path.read_text(encoding="utf-8").splitlines():
|
||||
if line.startswith("==="):
|
||||
continue
|
||||
|
||||
path, _, timestamp = line.partition(" ")
|
||||
timestamps[path] = timestamp
|
||||
filepath, _, timestamp = line.partition(" ")
|
||||
timestamps[filepath] = timestamp
|
||||
|
||||
|
||||
for yaml_file in yaml_files:
|
||||
convert_yaml_to_html(timestamps, Path(yaml_file), output_directory)
|
||||
path = Path(yaml_file)
|
||||
rule_content = path.read_text(encoding="utf-8")
|
||||
html_content = render_rule(timestamps, path)
|
||||
rule = capa.rules.Rule.from_yaml(path.read_text(encoding="utf-8"), use_ruamel=True)
|
||||
|
||||
# like: rules/create file/index.html
|
||||
#
|
||||
# which looks like the URL fragments:
|
||||
#
|
||||
# rules/create%20file/index.html
|
||||
# rules/create%20file/
|
||||
# rules/create file/
|
||||
html_path = output_directory / rule.name / "index.html"
|
||||
html_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
html_path.write_text(html_content, encoding="utf-8")
|
||||
logger.info("wrote: %s", html_path)
|
||||
|
||||
# like: create-file
|
||||
rule_id = path.with_suffix("").name
|
||||
# like: rules/create-file/index.html
|
||||
#
|
||||
# which looks like the URL fragments:
|
||||
#
|
||||
# rules/create-file/index.html
|
||||
# rules/create-file/
|
||||
#
|
||||
# and redirects, via meta refresh, to the canonical path above.
|
||||
# since we don't control the GH Pages web server, we can't use HTTP redirects.
|
||||
id_path = output_directory / rule_id / "index.html"
|
||||
id_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
redirect = f"""<html><head><meta http-equiv="refresh" content="0; url=../{rule.name}/"></head></html>"""
|
||||
id_path.write_text(redirect, encoding="utf-8")
|
||||
logger.info("wrote: %s", id_path)
|
||||
|
||||
Reference in New Issue
Block a user