can scan for function calls now
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
11
src/hashes.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"Linux.Gafgyt": {
|
||||
"md5": "7c0c01dbf6b557b4b154d84254554ff3",
|
||||
"sha1": "6cd2581525bfc1f484bd67c1b38a84eb52ad8751",
|
||||
"sha256": "ff4816dd923e0c7d2806c9928ed29396133cc1f81ed40a47c8e748c366811448"
|
||||
},
|
||||
"Linux.Bew": {
|
||||
"md5": "27d857e12b9be5d43f935b8cc86eaabf",
|
||||
"sha256": "80c4d1a1ef433ac44c4fe72e6ca42395261fbca36eff243b07438263a1b1cf06"
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,4 @@ from scanner import Scanner
|
||||
|
||||
|
||||
scanner = Scanner()
|
||||
scanner.scan_file("a.out")
|
||||
scanner.scan_file("virus")
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user