use memf_format::PhysicalMemoryProvider;
use memf_symbols::SymbolResolver;
use crate::{Error, Result};
const BANNER_PREFIX: &[u8] = b"Linux version ";
const KERNEL_MAP_BASE: u64 = 0xFFFF_FFFF_8000_0000;
pub fn detect_kaslr_offset(
physical: &dyn PhysicalMemoryProvider,
symbols: &dyn SymbolResolver,
) -> Result<u64> {
let banner_symbol_vaddr =
symbols
.symbol_address("linux_banner")
.ok_or_else(|| Error::MissingKernelSymbol {
name: "linux_banner".into(),
})?;
let banner_phys = scan_for_banner(physical)?;
let actual_virt = banner_phys.wrapping_add(KERNEL_MAP_BASE);
let offset = actual_virt.wrapping_sub(banner_symbol_vaddr);
Ok(offset)
}
fn scan_for_banner(physical: &dyn PhysicalMemoryProvider) -> Result<u64> {
let mut buf = vec![0u8; 4096];
for range in physical.ranges() {
let mut addr = range.start;
while addr < range.end {
let to_read = ((range.end - addr) as usize).min(buf.len());
let n =
physical
.read_phys(addr, &mut buf[..to_read])
.map_err(|e| Error::WalkFailed {
walker: "scan_for_banner",
reason: format!("physical read error: {e}"),
})?;
if n == 0 {
break;
}
if let Some(pos) = find_subsequence(&buf[..n], BANNER_PREFIX) {
return Ok(addr + pos as u64);
}
if n > BANNER_PREFIX.len() {
addr += (n - BANNER_PREFIX.len()) as u64;
} else {
addr += n as u64;
}
}
}
Err(Error::WalkFailed {
walker: "scan_for_banner",
reason: "Linux banner string not found in physical memory".into(),
})
}
fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|w| w == needle)
}
#[must_use]
pub fn adjust_address(original: u64, kaslr_offset: u64) -> u64 {
original.wrapping_add(kaslr_offset)
}
#[cfg(test)]
mod tests {
use super::*;
use memf_format::PhysicalRange;
use memf_symbols::isf::IsfResolver;
use memf_symbols::test_builders::IsfBuilder;
struct BannerPhysMem {
data: Vec<u8>,
ranges: Vec<PhysicalRange>,
}
impl PhysicalMemoryProvider for BannerPhysMem {
fn read_phys(&self, addr: u64, buf: &mut [u8]) -> memf_format::Result<usize> {
let start = addr as usize;
if start >= self.data.len() {
return Ok(0);
}
let available = self.data.len() - start;
let to_read = buf.len().min(available);
buf[..to_read].copy_from_slice(&self.data[start..start + to_read]);
Ok(to_read)
}
fn ranges(&self) -> &[PhysicalRange] {
&self.ranges
}
fn format_name(&self) -> &str {
"Test"
}
}
#[test]
fn detect_no_kaslr() {
let banner_phys: u64 = 0x0200_0000;
let banner_vaddr: u64 = 0xFFFF_FFFF_8200_0000;
let mut data = vec![0u8; (banner_phys as usize) + 4096];
let banner = b"Linux version 5.15.0-generic";
data[banner_phys as usize..banner_phys as usize + banner.len()].copy_from_slice(banner);
let mem = BannerPhysMem {
ranges: vec![PhysicalRange {
start: 0,
end: data.len() as u64,
}],
data,
};
let isf = IsfBuilder::new()
.add_symbol("linux_banner", banner_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let offset = detect_kaslr_offset(&mem, &resolver).unwrap();
assert_eq!(offset, 0);
}
#[test]
fn detect_with_kaslr() {
let kaslr_slide: u64 = 0x0060_0000;
let default_banner_vaddr: u64 = 0xFFFF_FFFF_8200_0000;
let banner_phys: u64 = 0x0260_0000;
let mut data = vec![0u8; (banner_phys as usize) + 4096];
let banner = b"Linux version 6.1.0-kaslr";
data[banner_phys as usize..banner_phys as usize + banner.len()].copy_from_slice(banner);
let mem = BannerPhysMem {
ranges: vec![PhysicalRange {
start: 0,
end: data.len() as u64,
}],
data,
};
let isf = IsfBuilder::new()
.add_symbol("linux_banner", default_banner_vaddr)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let offset = detect_kaslr_offset(&mem, &resolver).unwrap();
assert_eq!(offset, kaslr_slide);
}
#[test]
fn missing_linux_banner_symbol_returns_missing_kernel_symbol() {
let data = vec![0u8; 4096];
let mem = BannerPhysMem {
ranges: vec![PhysicalRange {
start: 0,
end: 4096,
}],
data,
};
let isf = IsfBuilder::new().build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let result = detect_kaslr_offset(&mem, &resolver);
assert!(
matches!(result, Err(crate::Error::MissingKernelSymbol { ref name }) if name == "linux_banner"),
"expected MissingKernelSymbol {{name: \"linux_banner\"}}, got {result:?}"
);
}
#[test]
fn no_banner_found() {
let data = vec![0u8; 4096];
let mem = BannerPhysMem {
ranges: vec![PhysicalRange {
start: 0,
end: 4096,
}],
data,
};
let isf = IsfBuilder::new()
.add_symbol("linux_banner", 0xFFFF_FFFF_8200_0000)
.build_json();
let resolver = IsfResolver::from_value(&isf).unwrap();
let result = detect_kaslr_offset(&mem, &resolver);
assert!(result.is_err());
}
#[test]
fn adjust_address_with_offset() {
let original = 0xFFFF_FFFF_8260_0000u64;
let kaslr = 0x0060_0000u64;
let adjusted = adjust_address(original, kaslr);
assert_eq!(adjusted, 0xFFFF_FFFF_82C0_0000);
}
#[test]
fn find_subsequence_basic() {
let haystack = b"hello world Linux version 5.15";
let needle = b"Linux version ";
assert_eq!(find_subsequence(haystack, needle), Some(12));
}
#[test]
fn find_subsequence_not_found() {
let haystack = b"no banner here";
let needle = b"Linux version ";
assert_eq!(find_subsequence(haystack, needle), None);
}
}