From ac7a1658c3c6cd958e7769bb47426e9e7183f6ea Mon Sep 17 00:00:00 2001 From: SpookyDervish Date: Fri, 3 Apr 2026 21:29:33 +1100 Subject: [PATCH] added hash based scanning, yara scanning, and heuristics scanning --- .gitignore | 2 ++ src/console.py | 4 ++++ src/detections/__init__.py | 3 +++ src/detections/detection.py | 6 +++++ src/detections/hash.py | 23 ++++++++++++++++++ src/detections/heuristics.py | 45 ++++++++++++++++++++++++++++++++++++ src/detections/yara.py | 13 +++++++++++ src/main.py | 5 ++++ src/scanner.py | 36 +++++++++++++++++++++++++++++ src/yara_rules.yara | 12 ++++++++++ 10 files changed, 149 insertions(+) create mode 100644 .gitignore create mode 100644 src/console.py create mode 100644 src/detections/__init__.py create mode 100644 src/detections/detection.py create mode 100644 src/detections/hash.py create mode 100644 src/detections/heuristics.py create mode 100644 src/detections/yara.py create mode 100644 src/main.py create mode 100644 src/scanner.py create mode 100644 src/yara_rules.yara diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..01d7f95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +venv \ No newline at end of file diff --git a/src/console.py b/src/console.py new file mode 100644 index 0000000..d411dcd --- /dev/null +++ b/src/console.py @@ -0,0 +1,4 @@ +from rich.console import Console + + +console = Console() \ No newline at end of file diff --git a/src/detections/__init__.py b/src/detections/__init__.py new file mode 100644 index 0000000..726e324 --- /dev/null +++ b/src/detections/__init__.py @@ -0,0 +1,3 @@ +from .hash import Hash +from .yara import Yara +from .heuristics import Heuristics \ No newline at end of file diff --git a/src/detections/detection.py b/src/detections/detection.py new file mode 100644 index 0000000..3efc469 --- /dev/null +++ b/src/detections/detection.py @@ -0,0 +1,6 @@ +class Detection: + def __init__(self): + pass + + def run(self, contents: bytes, file) -> bool: + pass \ No newline at end of file diff --git a/src/detections/hash.py b/src/detections/hash.py new file mode 100644 index 0000000..0b787d8 --- /dev/null +++ b/src/detections/hash.py @@ -0,0 +1,23 @@ +from .detection import Detection + +import hashlib + + +hashes = { + "test virus": { + "md5": "e74af7e29983d1c1d1b6fa11c9e6ea9a", + "sha1": "e66c4bc68cc58313baf227843df85911f23ae202", + "sha256": "fcd2db21ad4eb03b77405235381b87ddd2d29150cfc273db520b3f852702c8e4" + } +} + + +class Hash(Detection): + def run(self, content: bytes, _) -> bool: + md5, sha1, sha256 = hashlib.md5(content).hexdigest(), hashlib.sha1(content).hexdigest(), hashlib.sha256(content).hexdigest() + + for virus_name, virus_hashes in hashes.items(): + if virus_hashes["md5"] == md5 or virus_hashes["sha1"] == sha1 or virus_hashes["sha256"] == sha256: + return True + + return False \ No newline at end of file diff --git a/src/detections/heuristics.py b/src/detections/heuristics.py new file mode 100644 index 0000000..2524965 --- /dev/null +++ b/src/detections/heuristics.py @@ -0,0 +1,45 @@ +from .detection import Detection + +from elftools.elf.elffile import ELFFile + + +class Heuristics(Detection): + THRESHOLD = 3 + + def run(self, contents: bytes, file): + elf = ELFFile(file) + + scary = { + "Process spawning": { + "exec": 2, + "execv": 2, + "execve": 2, + "execvp": 2, + "system": 2, + "popen": 2, + "fork": 2, + "vfork": 2, + "clone": 2, + }, + + "Tracing": { + "ptrace": 1 + }, + + "Networking": { + "connect": 1, + "socket": 1, + } + } + + score = 0 + dynsym = elf.get_section_by_name(".dynsym") + if dynsym: + for sym in dynsym.iter_symbols(): + for reason, scary_names in scary.items(): + if sym.name in scary_names.keys(): + score += scary_names[sym.name] + + print(score) + + return score >= self.THRESHOLD \ No newline at end of file diff --git a/src/detections/yara.py b/src/detections/yara.py new file mode 100644 index 0000000..d248a39 --- /dev/null +++ b/src/detections/yara.py @@ -0,0 +1,13 @@ +from .detection import Detection + +import yara +import os + + +class Yara(Detection): + def run(self, contents: bytes, _) -> bool: + rules = yara.compile(os.path.realpath(os.path.join(__file__, "../../yara_rules.yara"))) + + matches = rules.match(data=contents) + + return len(matches) > 0 \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..36c5ea4 --- /dev/null +++ b/src/main.py @@ -0,0 +1,5 @@ +from scanner import Scanner + + +scanner = Scanner() +scanner.scan_file("a.out") \ No newline at end of file diff --git a/src/scanner.py b/src/scanner.py new file mode 100644 index 0000000..da192d3 --- /dev/null +++ b/src/scanner.py @@ -0,0 +1,36 @@ +from detections import * +from console import console + +from rich.table import Table +from time import time + + +class Scanner: + def __init__(self): + self.detections = { + "Hash Detection": Hash(), + "Yara Pattern Matching": Yara(), + "Heuristics": Heuristics() + } + + def scan_file(self, file_path: str): + with console.status(f"Scanning {file_path}...") as status: + f = open(file_path, "rb") + contents = f.read() + + + table = Table("Scan", "Match") + + for name, detection in self.detections.items(): + start_time = time() + console.print(f"[d]Running {name}..", end='\r') + + match = detection.run(contents, f) + table.add_row(name, "[bold orange1]⚠️ Match" if match else "[bold green]✅ Clean") + + console.print(f"Running {name}.. [d]({round(time()-start_time, 3)}s)", highlight=False) + + f.close() + + print() + console.print(table) \ No newline at end of file diff --git a/src/yara_rules.yara b/src/yara_rules.yara new file mode 100644 index 0000000..80fb6f1 --- /dev/null +++ b/src/yara_rules.yara @@ -0,0 +1,12 @@ +rule test_virus { + meta: + author = "Azure" + description = "A test virus that isn't actually dangerous but in theory is really naughty." + + strings: + $a = "virus" + + condition: + any of them + +}