79 lines
3.1 KiB
Python
Executable File
79 lines
3.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Scan a sample for destructive wiper primitives and classify wiper vs ransomware.
|
|
|
|
Detects raw-disk/MBR overwrite primitives, backup/recovery destruction commands, and weighs them
|
|
against ransomware (encryption/extortion) indicators to classify intent. Reads bytes statically;
|
|
executes nothing.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ASCII_RE = re.compile(rb"[\x20-\x7e]{4,}")
|
|
UTF16_RE = re.compile(rb"(?:[\x20-\x7e]\x00){4,}")
|
|
|
|
RAW_DISK = ["\\\\.\\physicaldrive", "\\\\.\\c:", "deviceiocontrol", "ioctl_disk",
|
|
"createfilemapping", "setfilepointer", "\\device\\harddisk"]
|
|
BOOT_TAMPER = ["this program cannot be run", "mbr", "\\\\.\\physicaldrive0", "bootmgr is missing"]
|
|
RECOVERY_DENIAL = ["vssadmin delete shadows", "vssadmin.exe delete", "wbadmin delete",
|
|
"bcdedit /set", "recoveryenabled no", "wevtutil cl", "delete catalog",
|
|
"shadowcopy delete"]
|
|
MASS_DELETE = ["deletefile", "removedirectory", "shfileoperation", "writefile", "f0 fill",
|
|
"overwrite"]
|
|
RANSOM_INDICATORS = ["cryptencrypt", "cryptdecrypt", "bcryptencrypt", "readme.txt", "decrypt",
|
|
".onion", "bitcoin", "ransom", "your files have been encrypted",
|
|
"aes", "rsa public key"]
|
|
|
|
|
|
def _strings(data: bytes) -> str:
|
|
parts = [m.decode("ascii", "ignore") for m in ASCII_RE.findall(data)]
|
|
parts += [m.decode("utf-16-le", "ignore") for m in UTF16_RE.findall(data)]
|
|
return "\n".join(parts).lower()
|
|
|
|
|
|
def scan(path: str) -> dict:
|
|
blob = _strings(Path(path).read_bytes())
|
|
raw = sorted({r for r in RAW_DISK if r in blob})
|
|
boot = sorted({b for b in BOOT_TAMPER if b in blob})
|
|
recovery = sorted({r for r in RECOVERY_DENIAL if r in blob})
|
|
deletion = sorted({d for d in MASS_DELETE if d in blob})
|
|
ransom = sorted({r for r in RANSOM_INDICATORS if r in blob})
|
|
destructive_score = len(raw) * 2 + len(boot) * 2 + len(recovery) + len(deletion)
|
|
ransom_score = len(ransom)
|
|
if destructive_score >= 3 and ransom_score <= 1:
|
|
classification = "likely_wiper"
|
|
elif ransom_score >= 3:
|
|
classification = "likely_ransomware"
|
|
elif destructive_score and ransom_score:
|
|
classification = "destructive_with_ransom_facade"
|
|
else:
|
|
classification = "inconclusive"
|
|
return {
|
|
"raw_disk_primitives": raw,
|
|
"boot_record_tampering": boot,
|
|
"recovery_denial": recovery,
|
|
"mass_deletion": deletion,
|
|
"ransomware_indicators": ransom,
|
|
"destructive_score": destructive_score,
|
|
"ransomware_score": ransom_score,
|
|
"classification": classification,
|
|
}
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="Analyze wiper malware")
|
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
p = sub.add_parser("scan")
|
|
p.add_argument("file")
|
|
args = parser.parse_args(argv)
|
|
print(json.dumps(scan(args.file), indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|