use alloc::vec;
use alloc::vec::Vec;
use wdk::println;
use wdk_sys::{
ntddk::MmIsAddressValid,
PVOID,
};
#[derive(Debug, Clone)]
pub struct SignatureMatch {
pub address: usize,
pub rule_name: [u8; 64],
pub matched_bytes: Vec<u8>,
pub context: Vec<u8>,
pub pid: Option<usize>,
pub confidence: u8,
}
#[derive(Debug, Clone, Copy)]
pub enum ScanTarget {
Process(usize), KernelPool,
Range { start: usize, end: usize },
Driver([u8; 64]), }
#[derive(Debug, Clone)]
pub struct Signature {
pub id: u32,
pub name: [u8; 64],
pub pattern: Vec<PatternByte>,
pub severity: u8,
pub category: SignatureCategory,
pub description: [u8; 256],
}
#[derive(Debug, Clone, Copy)]
pub enum PatternByte {
Exact(u8),
Any,
HighNibble(u8),
LowNibble(u8),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SignatureCategory {
Malware,
Ransomware,
Shellcode,
CredentialTheft,
Rootkit,
Backdoor,
Exploit,
Cryptominer,
Suspicious,
}
pub struct ScannerConfig {
pub max_region_size: usize,
pub skip_protections: u32,
pub context_size: usize,
pub max_matches: usize,
pub timeout_ms: u32,
}
impl Default for ScannerConfig {
fn default() -> Self {
Self {
max_region_size: 100 * 1024 * 1024, skip_protections: 0,
context_size: 32,
max_matches: 1000,
timeout_ms: 60000,
}
}
}
pub struct MemoryScanner {
signatures: Vec<Signature>,
config: ScannerConfig,
stats: ScanStatistics,
}
#[derive(Debug, Default)]
pub struct ScanStatistics {
pub bytes_scanned: u64,
pub regions_scanned: u32,
pub matches_found: u32,
pub duration_us: u64,
pub errors: u32,
}
impl MemoryScanner {
pub fn new() -> Self {
Self {
signatures: Vec::new(),
config: ScannerConfig::default(),
stats: ScanStatistics::default(),
}
}
pub fn with_config(config: ScannerConfig) -> Self {
Self {
signatures: Vec::new(),
config,
stats: ScanStatistics::default(),
}
}
pub fn load_builtin_signatures(&mut self) {
self.add_signature(Signature {
id: 1,
name: *b"Shellcode_x64_Stub\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
pattern: vec![
PatternByte::Exact(0x48),
PatternByte::Exact(0x83),
PatternByte::Exact(0xEC),
PatternByte::Exact(0x28),
],
severity: 8,
category: SignatureCategory::Shellcode,
description: [0u8; 256],
});
self.add_signature(Signature {
id: 2,
name: *b"Metasploit_MeterpreterLoader\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
pattern: vec![
PatternByte::Exact(0x4D),
PatternByte::Exact(0x5A),
PatternByte::Any,
PatternByte::Any,
],
severity: 9,
category: SignatureCategory::Backdoor,
description: [0u8; 256],
});
self.add_signature(Signature {
id: 3,
name: *b"CobaltStrike_Beacon\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
pattern: vec![
PatternByte::Any,
PatternByte::Any,
PatternByte::Any,
PatternByte::Any,
],
severity: 10,
category: SignatureCategory::Backdoor,
description: [0u8; 256],
});
self.add_signature(Signature {
id: 4,
name: *b"Mimikatz_String\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
pattern: string_to_pattern(b"sekurlsa"),
severity: 10,
category: SignatureCategory::CredentialTheft,
description: [0u8; 256],
});
println!(
"[Leviathan] Loaded {} built-in signatures",
self.signatures.len()
);
}
pub fn add_signature(&mut self, sig: Signature) {
self.signatures.push(sig);
}
pub fn parse_pattern(pattern_str: &str) -> Vec<PatternByte> {
let mut pattern = Vec::new();
for part in pattern_str.split_whitespace() {
if part == "??" {
pattern.push(PatternByte::Any);
} else if part.starts_with('?') {
if let Ok(nibble) = u8::from_str_radix(&part[1..], 16) {
pattern.push(PatternByte::LowNibble(nibble));
}
} else if part.ends_with('?') {
if let Ok(nibble) = u8::from_str_radix(&part[..1], 16) {
pattern.push(PatternByte::HighNibble(nibble << 4));
}
} else if let Ok(byte) = u8::from_str_radix(part, 16) {
pattern.push(PatternByte::Exact(byte));
}
}
pattern
}
pub unsafe fn scan_process(&mut self, pid: usize) -> Vec<SignatureMatch> {
let matches = Vec::new();
println!("[Leviathan] Scanning process {}", pid);
self.stats.regions_scanned += 1;
matches
}
pub unsafe fn scan_region(
&mut self,
start: usize,
size: usize,
) -> Vec<SignatureMatch> {
let mut matches = Vec::new();
if unsafe { MmIsAddressValid(start as PVOID) } == 0 {
return matches;
}
let scan_size = core::cmp::min(size, self.config.max_region_size);
for sig in &self.signatures {
if let Some(m) = unsafe { self.scan_for_signature(start, scan_size, sig) } {
matches.push(m);
if matches.len() >= self.config.max_matches {
break;
}
}
}
self.stats.bytes_scanned += scan_size as u64;
matches
}
unsafe fn scan_for_signature(
&self,
start: usize,
size: usize,
sig: &Signature,
) -> Option<SignatureMatch> {
let pattern_len = sig.pattern.len();
if pattern_len == 0 || size < pattern_len {
return None;
}
for offset in 0..(size - pattern_len) {
let addr = start + offset;
if unsafe { self.pattern_matches(addr, &sig.pattern) } {
let mut matched_bytes = Vec::with_capacity(pattern_len);
for i in 0..pattern_len {
let byte = unsafe { *((addr + i) as *const u8) };
matched_bytes.push(byte);
}
return Some(SignatureMatch {
address: addr,
rule_name: sig.name,
matched_bytes,
context: Vec::new(), pid: None,
confidence: 100,
});
}
}
None
}
unsafe fn pattern_matches(&self, addr: usize, pattern: &[PatternByte]) -> bool {
for (i, pat_byte) in pattern.iter().enumerate() {
let mem_addr = (addr + i) as *const u8;
if unsafe { MmIsAddressValid(mem_addr as PVOID) } == 0 {
return false;
}
let byte = unsafe { *mem_addr };
match pat_byte {
PatternByte::Exact(expected) => {
if byte != *expected {
return false;
}
}
PatternByte::Any => {
}
PatternByte::HighNibble(nibble) => {
if (byte & 0xF0) != *nibble {
return false;
}
}
PatternByte::LowNibble(nibble) => {
if (byte & 0x0F) != *nibble {
return false;
}
}
}
}
true
}
pub fn calculate_entropy(data: &[u8]) -> f64 {
if data.is_empty() {
return 0.0;
}
let mut freq = [0u64; 256];
for &byte in data {
freq[byte as usize] += 1;
}
let len = data.len() as f64;
let mut entropy = 0.0;
for &count in &freq {
if count > 0 {
let p = count as f64 / len;
entropy -= p * libm::log2(p);
}
}
entropy
}
pub fn find_pe_headers(data: &[u8]) -> Vec<usize> {
let mut headers = Vec::new();
for i in 0..data.len().saturating_sub(2) {
if data[i] == 0x4D && data[i + 1] == 0x5A {
if i + 0x3C < data.len() {
let pe_offset = u32::from_le_bytes([
data[i + 0x3C],
data.get(i + 0x3D).copied().unwrap_or(0),
data.get(i + 0x3E).copied().unwrap_or(0),
data.get(i + 0x3F).copied().unwrap_or(0),
]) as usize;
if i + pe_offset + 4 < data.len() {
let pe_sig = &data[i + pe_offset..i + pe_offset + 4];
if pe_sig == [0x50, 0x45, 0x00, 0x00] {
headers.push(i);
}
}
}
}
}
headers
}
pub fn detect_shellcode_patterns(data: &[u8]) -> Vec<(usize, &'static str)> {
let mut findings = Vec::new();
let patterns: &[(&[u8], &str)] = &[
(&[0x0F, 0x05], "syscall"),
(&[0x0F, 0x34], "sysenter"),
(&[0xCD, 0x80], "int80"),
(&[0xCD, 0x2E], "int2e"),
(&[0xE8], "call_relative"),
(&[0x47, 0x65, 0x74, 0x50, 0x72, 0x6F, 0x63], "GetProcAddress"),
(&[0x4C, 0x6F, 0x61, 0x64, 0x4C, 0x69, 0x62], "LoadLibrary"),
(&[0x56, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6C, 0x41], "VirtualAlloc"),
];
for (pattern, name) in patterns {
for i in 0..data.len().saturating_sub(pattern.len()) {
if &data[i..i + pattern.len()] == *pattern {
findings.push((i, *name));
}
}
}
findings
}
pub fn get_statistics(&self) -> &ScanStatistics {
&self.stats
}
pub fn reset_statistics(&mut self) {
self.stats = ScanStatistics::default();
}
}
fn string_to_pattern(s: &[u8]) -> Vec<PatternByte> {
s.iter().map(|&b| PatternByte::Exact(b)).collect()
}
#[derive(Debug, Clone)]
pub struct MemoryRegion {
pub base: usize,
pub size: usize,
pub protection: u32,
pub state: u32,
pub region_type: u32,
}
pub struct VadWalker {
process: usize,
}
impl VadWalker {
pub fn new(process: usize) -> Self {
Self { process }
}
pub unsafe fn enumerate_regions(&self) -> Vec<MemoryRegion> {
let regions = Vec::new();
regions
}
}
#[derive(Debug)]
pub struct ScanSummary {
pub total_matches: usize,
pub by_category: [(SignatureCategory, usize); 9],
pub max_severity: u8,
pub affected_pids: Vec<usize>,
}