can scan for function calls now
This commit is contained in:
@@ -2,5 +2,5 @@ class Detection:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def run(self, contents: bytes, file) -> bool:
|
def run(self, contents: bytes, file, path: str) -> bool:
|
||||||
pass
|
pass
|
||||||
@@ -1,23 +1,26 @@
|
|||||||
from .detection import Detection
|
from .detection import Detection
|
||||||
|
from console import console
|
||||||
|
|
||||||
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import os
|
||||||
|
|
||||||
hashes = {
|
|
||||||
"test virus": {
|
|
||||||
"md5": "e74af7e29983d1c1d1b6fa11c9e6ea9a",
|
|
||||||
"sha1": "e66c4bc68cc58313baf227843df85911f23ae202",
|
|
||||||
"sha256": "fcd2db21ad4eb03b77405235381b87ddd2d29150cfc273db520b3f852702c8e4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Hash(Detection):
|
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()
|
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():
|
for virus_name, virus_hashes in hashes.items():
|
||||||
if virus_hashes["md5"] == md5 or virus_hashes["sha1"] == sha1 or virus_hashes["sha256"] == sha256:
|
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 True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@@ -1,45 +1,115 @@
|
|||||||
from .detection import Detection
|
from .detection import Detection
|
||||||
|
|
||||||
from elftools.elf.elffile import ELFFile
|
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):
|
class Heuristics(Detection):
|
||||||
THRESHOLD = 3
|
THRESHOLD = 4
|
||||||
|
|
||||||
def run(self, contents: bytes, file):
|
def scan_for_strings(self, section: Section):
|
||||||
elf = ELFFile(file)
|
data = section.data()
|
||||||
|
strings = []
|
||||||
|
|
||||||
scary = {
|
current = b""
|
||||||
"Process spawning": {
|
for b in data:
|
||||||
"exec": 2,
|
if 32 <= b <= 126: # printable ASCII
|
||||||
"execv": 2,
|
current += bytes([b])
|
||||||
"execve": 2,
|
else:
|
||||||
"execvp": 2,
|
if len(current) >= 4:
|
||||||
"system": 2,
|
strings.append(current.decode(errors="ignore"))
|
||||||
"popen": 2,
|
current = b""
|
||||||
"fork": 2,
|
|
||||||
"vfork": 2,
|
|
||||||
"clone": 2,
|
|
||||||
},
|
|
||||||
|
|
||||||
"Tracing": {
|
return strings
|
||||||
"ptrace": 1
|
|
||||||
},
|
|
||||||
|
|
||||||
"Networking": {
|
def scan_for_scary_strings(self, string_list: list[str]):
|
||||||
"connect": 1,
|
scores = {}
|
||||||
"socket": 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
score = 0
|
for reason, scary_names in scary.items():
|
||||||
dynsym = elf.get_section_by_name(".dynsym")
|
scores[reason] = 0
|
||||||
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)
|
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):
|
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")))
|
rules = yara.compile(os.path.realpath(os.path.join(__file__, "../../yara_rules.yara")))
|
||||||
|
|
||||||
matches = rules.match(data=contents)
|
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 = Scanner()
|
||||||
scanner.scan_file("a.out")
|
scanner.scan_file("virus")
|
||||||
@@ -4,6 +4,8 @@ from console import console
|
|||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
class Scanner:
|
class Scanner:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -22,13 +24,11 @@ class Scanner:
|
|||||||
table = Table("Scan", "Match")
|
table = Table("Scan", "Match")
|
||||||
|
|
||||||
for name, detection in self.detections.items():
|
for name, detection in self.detections.items():
|
||||||
start_time = time()
|
console.print(f"Running {name}..")
|
||||||
console.print(f"[d]Running {name}..", end='\r')
|
|
||||||
|
|
||||||
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")
|
table.add_row(name, "[bold orange1]⚠️ Match" if match else "[bold green]✅ Clean")
|
||||||
|
print()
|
||||||
console.print(f"Running {name}.. [d]({round(time()-start_time, 3)}s)", highlight=False)
|
|
||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,161 @@
|
|||||||
rule test_virus {
|
|
||||||
|
rule wannacry_ransomware
|
||||||
|
{
|
||||||
meta:
|
meta:
|
||||||
author = "Azure"
|
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:
|
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:
|
condition:
|
||||||
any of them
|
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