use crate::{error::Error, overlay::offsettable::SetupLdrFamily, util::read::u32_le_at};
#[allow(dead_code)]
pub(crate) const OFFSET_TABLE_RESOURCE_ID: u32 = 11111;
const LEGACY_LOCATOR_FILE_OFFSET: usize = 0x30;
const LEGACY_LOCATOR_MAGIC: u32 = 0x6f6e_6e49;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum LocatorMode {
SignatureScan,
LegacyFileOffset,
}
stable_name_enum!(LocatorMode, {
Self::SignatureScan => "signature_scan",
Self::LegacyFileOffset => "legacy_file_offset",
});
#[derive(Clone, Copy, Debug)]
pub struct OffsetTableLocation {
pub start: usize,
pub len: usize,
pub mode: LocatorMode,
}
pub(crate) fn locate(input: &[u8]) -> Result<OffsetTableLocation, Error> {
if input.len() < 64
|| input.first().copied() != Some(b'M')
|| input.get(1).copied() != Some(b'Z')
{
return Err(Error::NotPe);
}
if let Some(loc) = try_legacy_file_offset(input)? {
return Ok(loc);
}
if let Some(loc) = try_signature_scan(input) {
return Ok(loc);
}
Err(Error::NotInnoSetup)
}
fn try_signature_scan(input: &[u8]) -> Option<OffsetTableLocation> {
const FAMILIES: &[SetupLdrFamily] = &[
SetupLdrFamily::V1_2_10,
SetupLdrFamily::V4_0_0,
SetupLdrFamily::V4_0_3,
SetupLdrFamily::V4_0_10,
SetupLdrFamily::V4_1_6,
SetupLdrFamily::V5_1_5,
SetupLdrFamily::V5_1_5Alt,
];
let mut best: Option<usize> = None;
for family in FAMILIES {
let signature = family.signature();
if let Some(offset) = find_subslice(input, signature) {
best = Some(match best {
Some(prev) => prev.min(offset),
None => offset,
});
}
}
let start = best?;
let len = input.len().saturating_sub(start);
Some(OffsetTableLocation {
start,
len,
mode: LocatorMode::SignatureScan,
})
}
fn find_subslice(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if needle.is_empty() || needle.len() > haystack.len() {
return None;
}
let last_start = haystack.len().checked_sub(needle.len())?;
let mut i = 0usize;
while i <= last_start {
let window = haystack.get(i..i.checked_add(needle.len())?)?;
if window == needle {
return Some(i);
}
i = i.checked_add(1)?;
}
None
}
fn try_legacy_file_offset(input: &[u8]) -> Result<Option<OffsetTableLocation>, Error> {
let probe_end = LEGACY_LOCATOR_FILE_OFFSET
.checked_add(12)
.ok_or(Error::Overflow {
what: "legacy locator probe end",
})?;
if input.len() < probe_end {
return Ok(None);
}
let magic = u32_le_at(input, LEGACY_LOCATOR_FILE_OFFSET, "legacy locator magic")?;
if magic != LEGACY_LOCATOR_MAGIC {
return Ok(None);
}
let pointer_offset = LEGACY_LOCATOR_FILE_OFFSET
.checked_add(4)
.ok_or(Error::Overflow {
what: "legacy locator pointer offset",
})?;
let pointer = u32_le_at(input, pointer_offset, "legacy locator pointer")?;
let not_pointer_offset = LEGACY_LOCATOR_FILE_OFFSET
.checked_add(8)
.ok_or(Error::Overflow {
what: "legacy locator not-pointer offset",
})?;
let not_pointer = u32_le_at(input, not_pointer_offset, "legacy locator pointer check")?;
if pointer != !not_pointer {
return Ok(None);
}
let start = pointer as usize;
if start >= input.len() {
return Err(Error::Truncated {
what: "legacy offset table pointer",
});
}
let len = input.len().saturating_sub(start);
Ok(Some(OffsetTableLocation {
start,
len,
mode: LocatorMode::LegacyFileOffset,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_non_pe() {
let err = locate(&[0u8; 128]).unwrap_err();
assert!(matches!(err, Error::NotPe));
}
#[test]
fn finds_signature_in_synthetic_buffer() {
let mut buf = vec![0u8; 4096];
buf[0] = b'M';
buf[1] = b'Z';
let sig = SetupLdrFamily::V5_1_5.signature();
buf[1024..1024 + sig.len()].copy_from_slice(sig);
let loc = locate(&buf).unwrap();
assert_eq!(loc.mode, LocatorMode::SignatureScan);
assert_eq!(loc.start, 1024);
}
#[test]
fn unknown_resource_id_constant_is_documented() {
assert_eq!(OFFSET_TABLE_RESOURCE_ID, 11111);
}
}