can scan for function calls now

This commit is contained in:
2026-04-04 15:42:57 +11:00
parent ac7a1658c3
commit a54bc2b107
8 changed files with 291 additions and 58 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 = []
scary = {
"Process spawning": {
"exec": 2,
"execv": 2,
"execve": 2,
"execvp": 2,
"system": 2,
"popen": 2,
"fork": 2,
"vfork": 2,
"clone": 2,
},
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""
"Tracing": {
"ptrace": 1
},
return strings
"Networking": {
"connect": 1,
"socket": 1,
}
}
def scan_for_scary_strings(self, string_list: list[str]):
scores = {}
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]
scores[reason] = 0
print(score)
for sym in string_list:
if sym in scary_names.keys():
scores[reason] += 1
return score >= self.THRESHOLD
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")
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
return False

View File

@@ -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)

11
src/hashes.json Normal file
View File

@@ -0,0 +1,11 @@
{
"Linux.Gafgyt": {
"md5": "7c0c01dbf6b557b4b154d84254554ff3",
"sha1": "6cd2581525bfc1f484bd67c1b38a84eb52ad8751",
"sha256": "ff4816dd923e0c7d2806c9928ed29396133cc1f81ed40a47c8e748c366811448"
},
"Linux.Bew": {
"md5": "27d857e12b9be5d43f935b8cc86eaabf",
"sha256": "80c4d1a1ef433ac44c4fe72e6ca42395261fbca36eff243b07438263a1b1cf06"
}
}

View File

@@ -2,4 +2,4 @@ from scanner import Scanner
scanner = Scanner()
scanner.scan_file("a.out")
scanner.scan_file("virus")

View File

@@ -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()

View File

@@ -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:
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
}