#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct BootkitMarker {
pub name: &'static str,
pub needle: &'static [u8],
}
pub const BOOTKIT_MARKERS: &[BootkitMarker] = &[
BootkitMarker {
name: "Stoned",
needle: b"Your PC is now Stoned!",
},
BootkitMarker {
name: "Stoned",
needle: b"LEGALISE MARIJUANA",
},
];
#[must_use]
pub fn scan(boot_code: &[u8]) -> Vec<&'static str> {
let mut hits: Vec<&'static str> = Vec::new();
for m in BOOTKIT_MARKERS {
if contains(boot_code, m.needle) && !hits.contains(&m.name) {
hits.push(m.name);
}
}
hits
}
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
!needle.is_empty()
&& needle.len() <= haystack.len()
&& haystack.windows(needle.len()).any(|w| w == needle)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct StashSector {
pub lba: u64,
pub family: &'static str,
pub note: &'static str,
}
pub const ORIGINAL_MBR_STASH_SECTORS: &[StashSector] = &[
StashSector {
lba: 60,
family: "Mebroot/Sinowal",
note: "kernel patcher",
},
StashSector {
lba: 61,
family: "Mebroot/Sinowal",
note: "payload loader",
},
StashSector {
lba: 62,
family: "Mebroot/Sinowal",
note: "original MBR",
},
StashSector {
lba: 56,
family: "Petya (Red)",
note: "original MBR, XOR 0x37",
},
StashSector {
lba: 55,
family: "Petya (Red)",
note: "verification sector (repeating 0x37)",
},
StashSector {
lba: 34,
family: "NotPetya",
note: "original sector-0 backup",
},
];
pub const TRACK0_GAP: core::ops::RangeInclusive<u64> = 1..=62;
pub const PACKED_BOOT_CODE_ENTROPY_SUSPECT: f64 = 7.0;
pub const PACKED_BOOT_CODE_ENTROPY_STRONG: f64 = 7.5;
pub const EXPECTED_BOOT_INTERRUPT_VECTORS: &[u8] = &[0x10, 0x13, 0x18, 0x1a];
pub fn stash_sectors_at(lba: u64) -> impl Iterator<Item = &'static StashSector> {
ORIGINAL_MBR_STASH_SECTORS
.iter()
.filter(move |s| s.lba == lba)
}
#[cfg(test)]
mod tests {
#[test]
fn stash_sectors_at_finds_documented_lba() {
let first = ORIGINAL_MBR_STASH_SECTORS
.first()
.expect("stash table must be non-empty");
let hits: Vec<_> = stash_sectors_at(first.lba).collect();
assert!(!hits.is_empty());
assert!(hits.iter().all(|s| s.lba == first.lba));
assert_eq!(stash_sectors_at(u64::MAX).count(), 0);
}
use super::*;
#[test]
fn detects_stoned_marker() {
let mut boot = vec![0u8; 446];
boot[0x100..0x100 + 22].copy_from_slice(b"Your PC is now Stoned!");
assert_eq!(scan(&boot), vec!["Stoned"]);
}
#[test]
fn dedups_repeated_family() {
let mut boot = vec![0u8; 446];
boot[0x10..0x10 + 22].copy_from_slice(b"Your PC is now Stoned!");
boot[0x80..0x80 + 18].copy_from_slice(b"LEGALISE MARIJUANA");
assert_eq!(scan(&boot), vec!["Stoned"]);
}
#[test]
fn clean_boot_code_finds_nothing() {
assert!(scan(&[0u8; 446]).is_empty());
}
#[test]
fn table_is_non_empty() {
assert!(!BOOTKIT_MARKERS.is_empty());
}
}