1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//! MBR boot-code (bootloader) fingerprints.
//!
//! Single source of truth identifying the bootloader occupying an MBR's 446-byte
//! boot-code area, for forensic tools (e.g. `mbr-forensic`). Each signature is a
//! set of `(offset, expected-bytes)` constraints; **all** must match.
//!
//! Signatures are evaluated in table order, so more specific entries
//! (Windows 7+ before Vista — they share the same stub and differ only in the
//! offset of the `"BOOTMGR"` string) come first.
//!
//! Sources:
//! - Windows Vista/7+ MBR — entry stub `33 C0 8E D0 BC 00 7C`
//! (`xor ax,ax; mov ss,ax; mov sp,7C00h`) plus the `"BOOTMGR"` string; the
//! string offset is 418 on Windows 7+ and 424 on Vista. Ref: Geoff Chappell,
//! "The MBR" / "BOOTMGR"; Microsoft boot process docs.
//! - GRUB 2 `boot.img` — begins `EB 63 90` (`jmp .+0x65; nop`):
//! GRUB source `grub-core/boot/i386/pc/boot.S`.
//! - GRUB Legacy `stage1` — begins `EB 48 90`: GRUB Legacy `stage1/stage1.S`.
//! - Syslinux MBR — `"SYSLINUX"` at offset 3: Syslinux `mbr/mbr.S`.
/// A bootloader fingerprint: a `name` and the `(offset, bytes)` constraints that
/// must all hold within the boot-code area.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct BootCodeSignature {
/// Bootloader name.
pub name: &'static str,
/// `(offset, expected-bytes)` constraints; all must match.
pub patterns: &'static [(usize, &'static [u8])],
}
/// Windows entry stub shared by Vista and 7+ (`xor ax,ax; mov ss,ax; mov sp,7C00h`).
const WIN_STUB: &[u8] = &[0x33, 0xC0, 0x8E, 0xD0, 0xBC, 0x00, 0x7C];
/// Known MBR boot-code signatures, in evaluation order.
pub const BOOT_CODE_SIGNATURES: &[BootCodeSignature] = &[
BootCodeSignature { name: "Windows 7+", patterns: &[(0, WIN_STUB), (418, b"BOOTMGR")] },
BootCodeSignature { name: "Windows Vista", patterns: &[(0, WIN_STUB), (424, b"BOOTMGR")] },
BootCodeSignature { name: "Syslinux", patterns: &[(3, b"SYSLINUX")] },
BootCodeSignature { name: "GRUB Legacy", patterns: &[(0, &[0xEB, 0x48, 0x90])] },
BootCodeSignature { name: "GRUB 2", patterns: &[(0, &[0xEB, 0x63, 0x90])] },
];
/// Identify the bootloader from a boot-code area, returning the first matching
/// signature's name. Returns `None` when nothing matches.
#[must_use]
pub fn identify_loader(code: &[u8]) -> Option<&'static str> {
BOOT_CODE_SIGNATURES
.iter()
.find(|sig| matches_all(code, sig.patterns))
.map(|sig| sig.name)
}
/// `true` when every `(offset, bytes)` constraint holds within `code`.
fn matches_all(code: &[u8], patterns: &[(usize, &[u8])]) -> bool {
patterns.iter().all(|(offset, pattern)| {
offset
.checked_add(pattern.len())
.is_some_and(|end| code.len() >= end && &code[*offset..end] == *pattern)
})
}
#[cfg(test)]
mod tests {
use super::*;
fn boot_with(stub: &[u8], at: usize, s: &[u8]) -> Vec<u8> {
let mut b = vec![0u8; 446];
b[..stub.len()].copy_from_slice(stub);
b[at..at + s.len()].copy_from_slice(s);
b
}
#[test]
fn identifies_windows7_by_bootmgr_offset_418() {
assert_eq!(identify_loader(&boot_with(WIN_STUB, 418, b"BOOTMGR")), Some("Windows 7+"));
}
#[test]
fn identifies_windows_vista_by_bootmgr_offset_424() {
assert_eq!(identify_loader(&boot_with(WIN_STUB, 424, b"BOOTMGR")), Some("Windows Vista"));
}
#[test]
fn identifies_grub2_and_syslinux() {
let mut grub = vec![0u8; 446];
grub[0..3].copy_from_slice(&[0xEB, 0x63, 0x90]);
assert_eq!(identify_loader(&grub), Some("GRUB 2"));
let mut sys = vec![0u8; 446];
sys[3..11].copy_from_slice(b"SYSLINUX");
assert_eq!(identify_loader(&sys), Some("Syslinux"));
}
#[test]
fn unknown_and_short_are_none() {
assert_eq!(identify_loader(&[0u8; 446]), None);
assert_eq!(identify_loader(&[0u8; 4]), None); // too short, no panic
}
}