use crate::{
error::Error,
util::{checksum::crc32, read::Reader},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SetupLdrFamily {
V1_2_10,
V4_0_0,
V4_0_3,
V4_0_10,
V4_1_6,
V5_1_5,
V5_1_5Alt,
}
stable_name_enum!(SetupLdrFamily, {
Self::V1_2_10 => "v1_2_10",
Self::V4_0_0 => "v4_0_0",
Self::V4_0_3 => "v4_0_3",
Self::V4_0_10 => "v4_0_10",
Self::V4_1_6 => "v4_1_6",
Self::V5_1_5 => "v5_1_5",
Self::V5_1_5Alt => "v5_1_5_alt",
});
impl SetupLdrFamily {
pub fn signature(self) -> &'static [u8; 12] {
match self {
Self::V1_2_10 => &[
b'r', b'D', b'l', b'P', b't', b'S', b'0', b'2', 0x87, b'e', b'V', b'x',
],
Self::V4_0_0 => &[
b'r', b'D', b'l', b'P', b't', b'S', b'0', b'4', 0x87, b'e', b'V', b'x',
],
Self::V4_0_3 => &[
b'r', b'D', b'l', b'P', b't', b'S', b'0', b'5', 0x87, b'e', b'V', b'x',
],
Self::V4_0_10 => &[
b'r', b'D', b'l', b'P', b't', b'S', b'0', b'6', 0x87, b'e', b'V', b'x',
],
Self::V4_1_6 => &[
b'r', b'D', b'l', b'P', b't', b'S', b'0', b'7', 0x87, b'e', b'V', b'x',
],
Self::V5_1_5 => &[
b'r', b'D', b'l', b'P', b't', b'S', 0xCD, 0xE6, 0xD7, b'{', 0x0B, b'*',
],
Self::V5_1_5Alt => &[
b'n', b'S', b'5', b'W', b'7', b'd', b'T', 0x83, 0xAA, 0x1B, 0x0F, b'j',
],
}
}
pub fn from_bytes(magic: &[u8; 12]) -> Option<Self> {
[
Self::V1_2_10,
Self::V4_0_0,
Self::V4_0_3,
Self::V4_0_10,
Self::V4_1_6,
Self::V5_1_5,
Self::V5_1_5Alt,
]
.into_iter()
.find(|f| magic == f.signature())
}
pub fn min_version(self) -> (u8, u8, u8) {
match self {
Self::V1_2_10 => (1, 2, 10),
Self::V4_0_0 => (4, 0, 0),
Self::V4_0_3 => (4, 0, 3),
Self::V4_0_10 => (4, 0, 10),
Self::V4_1_6 => (4, 1, 6),
Self::V5_1_5 | Self::V5_1_5Alt => (5, 1, 5),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum OffsetTableGeneration {
V0,
V1,
V2,
}
#[derive(Clone, Copy, Debug)]
pub struct OffsetTableSource {
pub start: usize,
pub len: usize,
pub family: SetupLdrFamily,
pub generation: OffsetTableGeneration,
}
#[derive(Clone, Debug)]
pub struct OffsetTable {
pub source: OffsetTableSource,
pub version_id: u32,
pub offset_exe: u64,
pub offset_setup0: u64,
pub offset_setup1: u64,
pub uncompressed_size_exe: u32,
pub crc_exe: u32,
pub total_size: u64,
pub message_offset: u32,
pub exe_compressed_size: u32,
}
impl OffsetTable {
pub fn parse(input: &[u8], start: usize, len: usize) -> Result<Self, Error> {
let end = start.checked_add(len).ok_or(Error::Overflow {
what: "offset table end",
})?;
let region = input.get(start..end).ok_or(Error::Truncated {
what: "offset table region",
})?;
let mut reader = Reader::new(region);
let magic_bytes = reader.array::<12>("offset table magic")?;
let family = SetupLdrFamily::from_bytes(&magic_bytes)
.ok_or(Error::UnknownSetupLdrMagic { magic: magic_bytes })?;
match family {
SetupLdrFamily::V5_1_5 | SetupLdrFamily::V5_1_5Alt => {
Self::parse_modern(region, start, &mut reader, family)
}
_ => Self::parse_legacy(region, start, &mut reader, family),
}
}
fn parse_modern(
region: &[u8],
start: usize,
reader: &mut Reader<'_>,
family: SetupLdrFamily,
) -> Result<Self, Error> {
let version_id = reader.u32_le("offset table Version")?;
match version_id {
2 => Self::parse_v2(region, start, reader, family),
1 => Self::parse_v1(region, start, reader, family),
_ => {
let pos_after = reader.pos();
let _ = pos_after; Self::parse_legacy(region, start, &mut Reader::at(region, 12)?, family)
}
}
}
fn parse_v2(
region: &[u8],
start: usize,
reader: &mut Reader<'_>,
family: SetupLdrFamily,
) -> Result<Self, Error> {
let total_size = reader.u64_le("TotalSize")?;
let offset_exe = reader.u64_le("OffsetEXE")?;
let uncompressed_size_exe = reader.u32_le("UncompressedSizeEXE")?;
let crc_exe = reader.u32_le("CRCEXE")?;
let offset_setup0 = reader.u64_le("Offset0")?;
let offset_setup1 = reader.u64_le("Offset1")?;
let _reserved = reader.u32_le("ReservedPadding")?;
let table_crc = reader.u32_le("TableCRC")?;
let bytes_read = reader.pos();
let crc_input_end = bytes_read.checked_sub(4).ok_or(Error::Overflow {
what: "v2 CRC range",
})?;
let crc_input = region.get(..crc_input_end).ok_or(Error::Truncated {
what: "v2 CRC range",
})?;
let actual = crc32(crc_input);
if actual != table_crc {
return Err(Error::BadChecksum {
what: "SetupLdrOffsetTable v2",
expected: table_crc,
actual,
});
}
Ok(Self {
source: OffsetTableSource {
start,
len: bytes_read,
family,
generation: OffsetTableGeneration::V2,
},
version_id: 2,
offset_exe,
offset_setup0,
offset_setup1,
uncompressed_size_exe,
crc_exe,
total_size,
message_offset: 0,
exe_compressed_size: 0,
})
}
fn parse_v1(
region: &[u8],
start: usize,
reader: &mut Reader<'_>,
family: SetupLdrFamily,
) -> Result<Self, Error> {
let _total_size_lo = reader.u32_le("v1 placeholder/total")?;
let offset_exe = u64::from(reader.u32_le("v1 OffsetEXE")?);
let uncompressed_size_exe = reader.u32_le("v1 UncompressedSizeEXE")?;
let crc_exe = reader.u32_le("v1 CRCEXE")?;
let offset_setup0 = u64::from(reader.u32_le("v1 Offset0")?);
let offset_setup1 = u64::from(reader.u32_le("v1 Offset1")?);
let table_crc = reader.u32_le("v1 TableCRC")?;
let bytes_read = reader.pos();
let crc_input_end = bytes_read.checked_sub(4).ok_or(Error::Overflow {
what: "v1 CRC range",
})?;
let crc_input = region.get(..crc_input_end).ok_or(Error::Truncated {
what: "v1 CRC range",
})?;
let actual = crc32(crc_input);
if actual != table_crc {
return Err(Error::BadChecksum {
what: "SetupLdrOffsetTable v1",
expected: table_crc,
actual,
});
}
Ok(Self {
source: OffsetTableSource {
start,
len: bytes_read,
family,
generation: OffsetTableGeneration::V1,
},
version_id: 1,
offset_exe,
offset_setup0,
offset_setup1,
uncompressed_size_exe,
crc_exe,
total_size: 0,
message_offset: 0,
exe_compressed_size: 0,
})
}
fn parse_legacy(
region: &[u8],
start: usize,
reader: &mut Reader<'_>,
family: SetupLdrFamily,
) -> Result<Self, Error> {
let (min_a, min_b, min_c) = family.min_version();
let _ = reader.u32_le("legacy placeholder")?;
let offset_exe = u64::from(reader.u32_le("legacy OffsetEXE")?);
let exe_compressed_size = if (min_a, min_b, min_c) >= (4, 1, 6) {
0
} else {
reader.u32_le("legacy ExeCompressedSize")?
};
let uncompressed_size_exe = reader.u32_le("legacy UncompressedSizeEXE")?;
let crc_exe = reader.u32_le("legacy CRCEXE/Adler32")?;
let message_offset = if (min_a, min_b, min_c) >= (4, 0, 0) {
0
} else {
reader.u32_le("legacy MessageOffset")?
};
let offset_setup0 = u64::from(reader.u32_le("legacy Offset0")?);
let offset_setup1 = u64::from(reader.u32_le("legacy Offset1")?);
if (min_a, min_b, min_c) >= (4, 0, 10) {
let bytes_read_before_crc = reader.pos();
let table_crc = reader.u32_le("legacy TableCRC")?;
let crc_input = region
.get(..bytes_read_before_crc)
.ok_or(Error::Truncated {
what: "legacy CRC range",
})?;
let actual = crc32(crc_input);
if actual != table_crc {
return Err(Error::BadChecksum {
what: "SetupLdrOffsetTable legacy",
expected: table_crc,
actual,
});
}
}
Ok(Self {
source: OffsetTableSource {
start,
len: reader.pos(),
family,
generation: OffsetTableGeneration::V0,
},
version_id: 0,
offset_exe,
offset_setup0,
offset_setup1,
uncompressed_size_exe,
crc_exe,
total_size: 0,
message_offset,
exe_compressed_size,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn family_signatures_round_trip() {
for f in [
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,
] {
assert_eq!(SetupLdrFamily::from_bytes(f.signature()), Some(f));
}
}
#[test]
fn unknown_magic_rejected() {
let m = [0u8; 12];
assert_eq!(SetupLdrFamily::from_bytes(&m), None);
}
}