From a54bc2b107d449aecd73d80b0789938ea06d1222 Mon Sep 17 00:00:00 2001 From: SpookyDervish Date: Sat, 4 Apr 2026 15:42:57 +1100 Subject: [PATCH] can scan for function calls now --- src/detections/detection.py | 2 +- src/detections/hash.py | 23 ++--- src/detections/heuristics.py | 138 ++++++++++++++++++++++-------- src/detections/yara.py | 2 +- src/hashes.json | 11 +++ src/main.py | 2 +- src/scanner.py | 10 +-- src/yara_rules.yara | 161 +++++++++++++++++++++++++++++++++-- 8 files changed, 291 insertions(+), 58 deletions(-) create mode 100644 src/hashes.json diff --git a/src/detections/detection.py b/src/detections/detection.py index 3efc469..093cc28 100644 --- a/src/detections/detection.py +++ b/src/detections/detection.py @@ -2,5 +2,5 @@ class Detection: def __init__(self): pass - def run(self, contents: bytes, file) -> bool: + def run(self, contents: bytes, file, path: str) -> bool: pass \ No newline at end of file diff --git a/src/detections/hash.py b/src/detections/hash.py index 0b787d8..0a68656 100644 --- a/src/detections/hash.py +++ b/src/detections/hash.py @@ -1,23 +1,26 @@ from .detection import Detection +from console import console +import json import hashlib - - -hashes = { - "test virus": { - "md5": "e74af7e29983d1c1d1b6fa11c9e6ea9a", - "sha1": "e66c4bc68cc58313baf227843df85911f23ae202", - "sha256": "fcd2db21ad4eb03b77405235381b87ddd2d29150cfc273db520b3f852702c8e4" - } -} +import os class Hash(Detection): - def run(self, content: bytes, _) -> bool: + def run(self, content: bytes, _, __) -> bool: + + console.print("[d]Getting hashes...") + with open(os.path.realpath(os.path.join(__file__, "../../hashes.json")), "r") as f: + hashes = json.load(f) + + console.print("[d]Generating hashes...") md5, sha1, sha256 = hashlib.md5(content).hexdigest(), hashlib.sha1(content).hexdigest(), hashlib.sha256(content).hexdigest() + console.print(f"[d] - MD5: {md5}\n - SHA1: {sha1}\n - SHA256: {sha256}[/]") + for virus_name, virus_hashes in hashes.items(): if virus_hashes["md5"] == md5 or virus_hashes["sha1"] == sha1 or virus_hashes["sha256"] == sha256: + console.print(f"[bold orange1]⚠️ HASH MATCH: {virus_name} DETECTED") return True return False \ No newline at end of file diff --git a/src/detections/heuristics.py b/src/detections/heuristics.py index 2524965..1cfb7be 100644 --- a/src/detections/heuristics.py +++ b/src/detections/heuristics.py @@ -1,45 +1,115 @@ from .detection import Detection from elftools.elf.elffile import ELFFile +from elftools.common.exceptions import ELFError +from elftools.elf.elffile import Section + +from console import console +import r2pipe + +import re + + +scary = { + "Reverse Shell": {"socket": 1, "connect": 1, "execve": 1, "dup2": 1}, + "Process Injection": {"mmap": 1, "mprotect": 1, "ptrace": 1, "malloc": 10}, + "Dynamic Resolution": {"dlopen": 1, "dlsym": 1, "socket": 1, "connect": 1}, +} class Heuristics(Detection): - THRESHOLD = 3 + THRESHOLD = 4 - def run(self, contents: bytes, file): - elf = ELFFile(file) + def scan_for_strings(self, section: Section): + data = section.data() + strings = [] + + current = b"" + for b in data: + if 32 <= b <= 126: # printable ASCII + current += bytes([b]) + else: + if len(current) >= 4: + strings.append(current.decode(errors="ignore")) + current = b"" + + return strings + + def scan_for_scary_strings(self, string_list: list[str]): + scores = {} - scary = { - "Process spawning": { - "exec": 2, - "execv": 2, - "execve": 2, - "execvp": 2, - "system": 2, - "popen": 2, - "fork": 2, - "vfork": 2, - "clone": 2, - }, + for reason, scary_names in scary.items(): + scores[reason] = 0 - "Tracing": { - "ptrace": 1 - }, - - "Networking": { - "connect": 1, - "socket": 1, - } - } + for sym in string_list: + if sym in scary_names.keys(): + scores[reason] += 1 + + if self.is_ip(sym): + scores["Reverse Shell"] += 1 + + return scores + + def detect_function_calls(self, scores: dict, path: str): + r2 = r2pipe.open(path) + r2.cmd("aaa") + functions = r2.cmdj("aflj") - 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] + for func_name in [f["name"] for f in functions]: + for reason, scary_names in scary.items(): + for key in scary_names.keys(): + if func_name in key: + scores[reason] += 1 + + return scores + + def sum_scores(self, a_dict: dict, b_dict: dict): + return {k: a_dict.get(k, 0) + b_dict.get(k, 0) for k in a_dict.keys() | b_dict.keys()} + + def is_ip(self, ip_port: str): + try: + if ':' in ip_port: + ip, port = ip_port.split(':') + if not (0 <= int(port) <= 65535): + return False + else: + ip = ip_port + parts = ip.split('.') + return all(0 <= int(p) <= 255 for p in parts) + except ValueError: + return False + + def subtract_score(self, scores: dict[str, int], score_name: str, amount: int): + if score_name == "all": + for key, value in scores.items(): + scores[key] -= amount + else: + scores[score_name] -= amount + + def run(self, contents: bytes, file, path: str): + try: + elf = ELFFile(file) + except ELFError: + return False + + + scores = {} + + rodata = elf.get_section_by_name(".rodata") + if rodata: + scores = self.sum_scores(scores, self.scan_for_scary_strings(self.scan_for_strings(rodata))) + + plt = elf.get_section_by_name(".plt") + if plt: + scores = self.sum_scores(scores, self.scan_for_scary_strings(self.scan_for_strings(plt))) + + if b"glibc" in contents: + self.subtract_score(scores, "all", 1) + + scores = self.detect_function_calls(scores, path) + + for virus_type, score in scores.items(): + if score > self.THRESHOLD: + return True - print(score) - - return score >= self.THRESHOLD \ No newline at end of file + return False \ No newline at end of file diff --git a/src/detections/yara.py b/src/detections/yara.py index d248a39..a039ed8 100644 --- a/src/detections/yara.py +++ b/src/detections/yara.py @@ -5,7 +5,7 @@ import os class Yara(Detection): - def run(self, contents: bytes, _) -> bool: + def run(self, contents: bytes, _, __) -> bool: rules = yara.compile(os.path.realpath(os.path.join(__file__, "../../yara_rules.yara"))) matches = rules.match(data=contents) diff --git a/src/hashes.json b/src/hashes.json new file mode 100644 index 0000000..00904b0 --- /dev/null +++ b/src/hashes.json @@ -0,0 +1,11 @@ +{ + "Linux.Gafgyt": { + "md5": "7c0c01dbf6b557b4b154d84254554ff3", + "sha1": "6cd2581525bfc1f484bd67c1b38a84eb52ad8751", + "sha256": "ff4816dd923e0c7d2806c9928ed29396133cc1f81ed40a47c8e748c366811448" + }, + "Linux.Bew": { + "md5": "27d857e12b9be5d43f935b8cc86eaabf", + "sha256": "80c4d1a1ef433ac44c4fe72e6ca42395261fbca36eff243b07438263a1b1cf06" + } +} \ No newline at end of file diff --git a/src/main.py b/src/main.py index 36c5ea4..a4f0bc5 100644 --- a/src/main.py +++ b/src/main.py @@ -2,4 +2,4 @@ from scanner import Scanner scanner = Scanner() -scanner.scan_file("a.out") \ No newline at end of file +scanner.scan_file("virus") \ No newline at end of file diff --git a/src/scanner.py b/src/scanner.py index da192d3..9160bd8 100644 --- a/src/scanner.py +++ b/src/scanner.py @@ -4,6 +4,8 @@ from console import console from rich.table import Table from time import time +import os + class Scanner: def __init__(self): @@ -22,13 +24,11 @@ class Scanner: table = Table("Scan", "Match") for name, detection in self.detections.items(): - start_time = time() - console.print(f"[d]Running {name}..", end='\r') + console.print(f"Running {name}..") - match = detection.run(contents, f) + match = detection.run(contents, f, os.path.realpath(file_path)) 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) + print() f.close() diff --git a/src/yara_rules.yara b/src/yara_rules.yara index 80fb6f1..d4a9785 100644 --- a/src/yara_rules.yara +++ b/src/yara_rules.yara @@ -1,12 +1,161 @@ -rule test_virus { + +rule wannacry_ransomware +{ meta: author = "Azure" - description = "A test virus that isn't actually dangerous but in theory is really naughty." + description = "This is a rule that tests against strings in WannaCry" strings: - $a = "virus" + $b = "C:\\%s\\qeriuwjhrf" + $c = "WNcry@2017" + $d = "msg/m_bulgarian.wnry" + $e = "WanaCryptor" - condition: - any of them - + condition: + 3 of them } + +rule mimikatz_strings +{ + meta: + author = "Azure" + description = "Detects Mimikatz usage" + + strings: + $m1 = "sekurlsa::logonpasswords" + $m2 = "mimikatz" + $m3 = "lsadump::sam" + $m4 = "kerberos::tickets" + $m5 = "privilege::debug" + + condition: + 2 of them +} + +rule generic_ransomware_extensions +{ + meta: + author = "Azure" + description = "Detects ransomware file extensions" + + strings: + $r1 = ".locked" + $r2 = ".encrypted" + $r3 = ".crypt" + $r4 = "restore_files" + $r5 = "readme.txt" + + condition: + 3 of them +} + +rule lokibot_stealer +{ + meta: + author = "Azure" + description = "Detects LokiBot stealer behavior" + + strings: + $l1 = "pass.txt" + $l2 = "wallet.dat" + $l3 = "cookies.sqlite" + $l4 = "logins.json" + $l5 = "FileZilla" + + condition: + 3 of them +} + +rule vmdetect +{ + meta: + author = "nex" + description = "Possibly employs anti-virtualization techniques" + + strings: + // Binary tricks + $vmware = {56 4D 58 68} + $virtualpc = {0F 3F 07 0B} + $ssexy = {66 0F 70 ?? ?? 66 0F DB ?? ?? ?? ?? ?? 66 0F DB ?? ?? ?? ?? ?? 66 0F EF} + $vmcheckdll = {45 C7 00 01} + $redpill = {0F 01 0D 00 00 00 00 C3} + + // Random strings + $vmware1 = "VMXh" + $vmware2 = "Ven_VMware_" nocase + $vmware3 = "Prod_VMware_Virtual_" nocase + $vmware4 = "hgfs.sys" nocase + $vmware5 = "mhgfs.sys" nocase + $vmware6 = "prleth.sys" nocase + $vmware7 = "prlfs.sys" nocase + $vmware8 = "prlmouse.sys" nocase + $vmware9 = "prlvideo.sys" nocase + $vmware10 = "prl_pv32.sys" nocase + $vmware11 = "vpc-s3.sys" nocase + $vmware12 = "vmsrvc.sys" nocase + $vmware13 = "vmx86.sys" nocase + $vmware14 = "vmnet.sys" nocase + $vmware15 = "vmicheartbeat" nocase + $vmware16 = "vmicvss" nocase + $vmware17 = "vmicshutdown" nocase + $vmware18 = "vmicexchange" nocase + $vmware19 = "vmdebug" nocase + $vmware20 = "vmmouse" nocase + $vmware21 = "vmtools" nocase + $vmware22 = "VMMEMCTL" nocase + $vmware23 = "vmx86" nocase + $vmware24 = "vmware" nocase + $virtualpc1 = "vpcbus" nocase + $virtualpc2 = "vpc-s3" nocase + $virtualpc3 = "vpcuhub" nocase + $virtualpc4 = "msvmmouf" nocase + $xen1 = "xenevtchn" nocase + $xen2 = "xennet" nocase + $xen3 = "xennet6" nocase + $xen4 = "xensvc" nocase + $xen5 = "xenvdb" nocase + $xen6 = "XenVMM" nocase + $virtualbox1 = "VBoxHook.dll" nocase + $virtualbox2 = "VBoxService" nocase + $virtualbox3 = "VBoxTray" nocase + $virtualbox4 = "VBoxMouse" nocase + $virtualbox5 = "VBoxGuest" nocase + $virtualbox6 = "VBoxSF" nocase + $virtualbox7 = "VBoxGuestAdditions" nocase + $virtualbox8 = "VBOX HARDDISK" nocase + + // MAC addresses + $vmware_mac_1a = "00-05-69" + $vmware_mac_1b = "00:05:69" + $vmware_mac_1c = "000569" + $vmware_mac_2a = "00-50-56" + $vmware_mac_2b = "00:50:56" + $vmware_mac_2c = "005056" + $vmware_mac_3a = "00-0C-29" nocase + $vmware_mac_3b = "00:0C:29" nocase + $vmware_mac_3c = "000C29" nocase + $vmware_mac_4a = "00-1C-14" nocase + $vmware_mac_4b = "00:1C:14" nocase + $vmware_mac_4c = "001C14" nocase + $virtualbox_mac_1a = "08-00-27" + $virtualbox_mac_1b = "08:00:27" + $virtualbox_mac_1c = "080027" + + condition: + any of them +} + +rule linux_bew +{ + meta: + description = "Linux.Bew Backdoor" + author = "Joan Soriano / @w0lfvan" + + strings: + $a = "src/secp256k1.c" + $b = "hfir.u230.org" + $c = "tempfile-x11session" + + condition: + all of them +} \ No newline at end of file