diff --git a/src/fpt.rs b/src/fpt.rs index 5dd0060..7f1e43c 100644 --- a/src/fpt.rs +++ b/src/fpt.rs @@ -22,12 +22,7 @@ use serde::{Deserialize, Serialize}; use zerocopy::{AlignmentError, ConvertError, FromBytes, IntoBytes, Ref, SizeError}; use zerocopy_derive::{FromBytes, Immutable, IntoBytes}; -use crate::{ - dir::gen2::Directory as Gen2Directory, - dir::gen3::CodePartitionDirectory, - fit::{Fit, FitError}, - ver::Version, -}; +use crate::ver::Version; const FPT_MAGIC: &str = "$FPT"; @@ -127,44 +122,65 @@ impl Display for FPTEntry { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct FPT { + pub offset: usize, pub header: FPTHeader, pub entries: Vec, } -impl<'a> FPT { - pub fn parse(data: &'a [u8]) -> Option>> { - let m = &data[..4]; +pub const FPT_SIZE: usize = size_of::(); + +// The FPT magic is either at the start or at a 16 bytes offset. +fn determine_offset(data: &[u8]) -> Option { + let m = &data[..4]; + if m.eq(FPT_MAGIC.as_bytes()) { + return Some(0); + } else { + let m = &data[16..20]; if m.eq(FPT_MAGIC.as_bytes()) { - let header = match FPTHeader::read_from_prefix(data) { - Ok((h, _)) => h, - Err(e) => return Some(Err(FptError::HeaderParseError(e))), - }; - // NOTE: Skip $FPT (header) itself - let slice = &data[FPT_HEADER_SIZE..]; - let count = header.entries as usize; - let entries = match Ref::<_, [FPTEntry]>::from_prefix_with_elems(slice, count) { - Ok((r, _)) => r, - Err(e) => return Some(Err(FptError::EntryParseError(e))), - }; - - Some(Ok(Self { - header, - entries: entries.to_vec(), - })) + return Some(16); } else { - None + return None; } } } -#[allow(non_camel_case_types)] -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ME_FW { - pub base: usize, - pub fpt: FPT, - pub gen3dirs: Vec, - pub gen2dirs: Vec, - pub fit: Result, +impl<'a> FPT { + pub fn parse(data: &'a [u8]) -> Option>> { + let Some(offset) = determine_offset(data) else { + return None; + }; + let d = &data[offset..]; + let header = match FPTHeader::read_from_prefix(d) { + Ok((h, _)) => h, + Err(e) => return Some(Err(FptError::HeaderParseError(e))), + }; + // NOTE: Skip $FPT (header) itself + let slice = &d[FPT_HEADER_SIZE..]; + let count = header.entries as usize; + let entries = match Ref::<_, [FPTEntry]>::from_prefix_with_elems(slice, count) { + Ok((r, _)) => r, + Err(e) => return Some(Err(FptError::EntryParseError(e))), + }; + + Some(Ok(Self { + offset, + header, + entries: entries.to_vec(), + })) + } + + // Find an FPT in a given slice, and if the magic is detected, get the + // parse result and the offset. + pub fn scan(data: &'a [u8]) -> Option<(Result>, usize)> { + let mut o = 0; + while o + 16 + FPT_SIZE <= data.len() { + if let Some(fpt) = Self::parse(data) { + return Some((fpt, o)); + } + o += 16; + } + None + } } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -218,7 +234,7 @@ static FPT_CLEANED: &[u8] = include_bytes!("../tests/me11_cleaned.fpt"); #[test] fn parse_size_error() { - let parsed = FPT::parse(&DATA[16..70]); + let parsed = FPT::parse(&DATA[..70]); assert!(parsed.is_some()); let fpt_res = parsed.unwrap(); assert!(matches!(fpt_res, Err(FptError::EntryParseError(_)))); @@ -234,9 +250,19 @@ fn parse_okay_fpt() { assert_eq!(fpt.header.entries as usize, fpt.entries.len()); } +#[test] +fn parse_okay_fpt_with_offset() { + let parsed = FPT::parse(&DATA); + assert!(parsed.is_some()); + let fpt_res = parsed.unwrap(); + assert!(fpt_res.is_ok()); + let fpt = fpt_res.unwrap(); + assert_eq!(fpt.header.entries as usize, fpt.entries.len()); +} + #[test] fn checksum() { - let parsed = FPT::parse(&DATA[16..12 * 32 + 16]); + let parsed = FPT::parse(&DATA); let fpt = parsed.unwrap().unwrap(); assert_eq!(fpt.header.checksum(), fpt.header.checksum); } diff --git a/src/lib.rs b/src/lib.rs index 8b14c8b..5c7e87a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,142 +1,43 @@ #![doc = include_str!("../README.md")] -use core::mem; -use log::{error, warn}; +use log::{info, warn}; +use serde::{Deserialize, Serialize}; pub mod dir; pub mod fit; pub mod fpt; pub mod ifd; +pub mod me; pub mod meta; pub mod ver; -pub use fpt::ME_FW; -use fpt::{AFSP, DLMP, EFFS, FTPR, FTUP, MDMV, MFS, NFTP}; +use fit::{Fit, FitError}; +use ifd::{IFD, IfdError}; +use me::ME; -fn dump48(data: &[u8]) { - println!("Here are the first 48 bytes:"); - let b = &data[0..0x10]; - println!("{b:02x?}"); - let b = &data[0x10..0x20]; - println!("{b:02x?}"); - let b = &data[0x20..0x30]; - println!("{b:02x?}"); +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Firmware { + pub ifd: Result, + pub me: Option>, + pub fit: Result, } -pub fn parse(data: &[u8], debug: bool) -> Result { - let ifd = ifd::IFD::parse(&data); - match ifd { - Ok(ifd) => println!("{ifd}"), - Err(e) => warn!("Not a full image: {e:?}"), - } - - let fit = fit::Fit::new(data); - - let mut gen2dirs = Vec::::new(); - let mut gen3dirs = Vec::::new(); - - // Scan for all CPDs (there may be some not listed in FPT) - if debug { - let mut o = 0; - while o < data.len() { - let buf = &data[o..o + 4]; - if buf.eq(dir::gen3::CPD_MAGIC_BYTES) { - let Ok(cpd) = dir::gen3::CodePartitionDirectory::new(data[o..].to_vec(), o) else { - continue; - }; - gen3dirs.push(cpd); +impl Firmware { + pub fn parse(data: &[u8], debug: bool) -> Self { + let ifd = IFD::parse(&data); + let me = match &ifd { + Ok(ifd) => { + let me_region = ifd.regions.flreg2.range(); + let (b, l) = me_region; + info!("ME region start @ {b:08x}"); + ME::parse(&data[b..l], b, debug) } - o += 16; - } - println!("Found {} CPDs doing a full scan", gen3dirs.len()); - } - - let mut base = 0; - while base + 16 + mem::size_of::() <= data.len() { - // first 16 bytes are potentially other stuff - let o = base + 16; - if let Some(r) = fpt::FPT::parse(&data[o..]) { - let fpt = match r { - Ok(r) => r, - Err(e) => { - error!("Cannot parse ME FPT entries @ {o:08x}: {e:?}"); - continue; - } - }; - // realign base; what does this indicate? - if base % 0x1000 != 0 { - base = o; - if debug { - println!("Realigned FPT base to {o:08x}"); - } - } - - for e in &fpt.entries { - let name = match std::str::from_utf8(&e.name) { - // some names are shorter than 4 bytes and padded with 0x0 - Ok(n) => n.trim_end_matches('\0').to_string(), - Err(_) => format!("{:02x?}", &e.name), - }; - let n = u32::from_be_bytes(e.name); - let o = base + (e.offset & 0x003f_ffff) as usize; - let s = e.size as usize; - match n { - MDMV | DLMP | FTPR | NFTP => { - if o + 4 < data.len() { - let buf = &data[o..o + 4]; - if buf.eq(dir::gen3::CPD_MAGIC_BYTES) { - if let Ok(cpd) = dir::gen3::CodePartitionDirectory::new( - data[o..o + s].to_vec(), - o, - ) { - gen3dirs.push(cpd); - } - } else if let Ok(dir) = dir::gen2::Directory::new(&data[o..], o) { - gen2dirs.push(dir); - } else if debug { - println!("{name} @ {o:08x} has no CPD signature"); - dump48(&data[o..]); - } - } - } - MFS | AFSP | EFFS => { - // TODO: parse MFS - } - _ => { - if !debug { - continue; - } - // We may encounter unknown CPDs. - if n != FTUP && o + 4 < data.len() { - let buf = &data[o..o + 4]; - if let Ok(sig) = std::str::from_utf8(buf) { - if sig == dir::gen3::CPD_MAGIC { - println!("Unknown $CPD in {name} @ 0x{o:08x} (0x{s:08x})."); - continue; - } - } - } - if let Ok(m) = dir::man::Manifest::new(&data[o..]) { - println!("Manifest found in {name}: {m}"); - continue; - } - println!("Cannot (yet) parse {name} @ 0x{o:08x} (0x{s:08x}), skipping..."); - if debug { - dump48(&data[o..]); - } - } - } + Err(e) => { + warn!("Not a full image: {e:?}"); + ME::parse(data, 0, debug) } - - return Ok(ME_FW { - base, - fpt, - gen3dirs, - gen2dirs, - fit, - }); - } - base += 16; + }; + let fit = Fit::new(data); + Self { ifd, me, fit } } - Err("No $FPT :(".to_string()) } diff --git a/src/main.rs b/src/main.rs index 2ed6909..42ff795 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,12 @@ use std::fs; use clap::{Parser, Subcommand, ValueEnum}; -use log::{debug, error, info, trace, warn}; +use log::{debug, info}; mod clean; mod show; -use intel_fw::parse; +use intel_fw::Firmware; #[derive(Clone, Copy, Debug, ValueEnum)] enum Partition { @@ -55,12 +55,24 @@ enum MeCommand { /// File to read file_name: String, }, - /// Display the (CS)ME high-level structures + /// Display the (CS)ME high-level structures (full image or ME region) #[clap(verbatim_doc_comment)] Show { /// File to read file_name: String, }, + /// Scan for (CS)ME data structures (useful for update images) + #[clap(verbatim_doc_comment)] + Scan { + /// File to read + file_name: String, + }, + /// Check for consistency (full image or ME region) + #[clap(verbatim_doc_comment)] + Check { + /// File to read + file_name: String, + }, } #[derive(Subcommand)] @@ -146,27 +158,21 @@ fn main() { } info!("Reading {file_name}..."); let data = fs::read(file_name).unwrap(); - match parse(&data, debug) { - Ok(fpt) => { - show::show(&fpt, verbose); - println!(); - todo!("clean"); - } - Err(e) => { - error!("Could not parse ME FPT: {e}"); - } - } + let fw = Firmware::parse(&data, debug); + show::show(&fw, verbose); + println!(); + todo!("clean"); + } + MeCommand::Scan { file_name } => { + todo!("scan {file_name}") + } + MeCommand::Check { file_name } => { + todo!("check {file_name}") } MeCommand::Show { file_name } => { let data = fs::read(file_name).unwrap(); - match parse(&data, debug) { - Ok(fpt) => { - show::show(&fpt, verbose); - } - Err(e) => { - error!("Could not parse ME FPT: {e}"); - } - } + let fw = Firmware::parse(&data, debug); + show::show(&fw, verbose); } }, } diff --git a/src/me.rs b/src/me.rs new file mode 100644 index 0000000..ce2a263 --- /dev/null +++ b/src/me.rs @@ -0,0 +1,146 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + dir::{ + gen2::Directory as Gen2Directory, + gen3::{CPD_MAGIC_BYTES, CodePartitionDirectory}, + man::Manifest, + }, + fpt::{AFSP, DLMP, EFFS, FPT, FTPR, FTUP, MDMV, MFS, NFTP}, +}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum Generation { + Gen1, + Gen2, + Gen3, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum Directories { + Gen2(Vec), + Gen3(Vec), +} + +#[allow(non_camel_case_types)] +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ME { + pub base: usize, + pub fpt: FPT, + pub dirs: Directories, +} + +fn dump48(data: &[u8]) { + println!("Here are the first 48 bytes:"); + let b = &data[0..0x10]; + println!("{b:02x?}"); + let b = &data[0x10..0x20]; + println!("{b:02x?}"); + let b = &data[0x20..0x30]; + println!("{b:02x?}"); +} + +impl ME { + pub fn parse(data: &[u8], base: usize, debug: bool) -> Option> { + if let Some(r) = FPT::parse(data) { + let fpt = match r { + Ok(r) => r, + Err(e) => { + return Some(Err(format!("Cannot parse ME FPT @ {base:08x}: {e:?}"))); + } + }; + + let mut gen2dirs = Vec::::new(); + let mut gen3dirs = Vec::::new(); + // NOTE: We can only implicitly decide whether the given image is for + // the 2nd or 3rd ME generation by looking at the directories themselves. + for e in &fpt.entries { + let name = match std::str::from_utf8(&e.name) { + // some names are shorter than 4 bytes and padded with 0x0 + Ok(n) => n.trim_end_matches('\0').to_string(), + Err(_) => format!("{:02x?}", &e.name), + }; + let n = u32::from_be_bytes(e.name); + let o = (e.offset & 0x003f_ffff) as usize; + let s = e.size as usize; + match n { + MDMV | DLMP | FTPR | NFTP => { + if o + 4 < data.len() { + let buf = &data[o..o + 4]; + if buf.eq(CPD_MAGIC_BYTES) { + if let Ok(cpd) = + CodePartitionDirectory::new(data[o..o + s].to_vec(), o) + { + gen3dirs.push(cpd); + } + } else if let Ok(dir) = Gen2Directory::new(&data[o..], o) { + gen2dirs.push(dir); + } else if debug { + println!("{name} @ {o:08x} has no CPD signature"); + dump48(&data[o..]); + } + } + } + MFS | AFSP | EFFS => { + // TODO: parse MFS + } + _ => { + if !debug { + continue; + } + // We may encounter unknown CPDs. + if n != FTUP && o + 4 < data.len() { + let buf = &data[o..o + 4]; + if buf == CPD_MAGIC_BYTES { + println!("Unknown $CPD in {name} @ 0x{o:08x} (0x{s:08x})."); + continue; + } + } + if let Ok(m) = Manifest::new(&data[o..]) { + println!("Manifest found in {name}: {m}"); + continue; + } + println!("Cannot (yet) parse {name} @ 0x{o:08x} (0x{s:08x}), skipping..."); + if debug { + dump48(&data[o..]); + } + } + } + } + + let dirs = { + if gen3dirs.len() > 0 { + Directories::Gen3(gen3dirs) + } else if gen2dirs.len() > 0 { + Directories::Gen2(gen2dirs) + } else { + return None; + } + }; + + Some(Ok(Self { base, fpt, dirs })) + } else { + None + } + } + + // Scan for all CPDs (there may be some not listed in FPT) + pub fn cpd_scan(data: &[u8]) -> Vec { + let mut gen3dirs = Vec::::new(); + let mut o = 0; + while o < data.len() { + let buf = &data[o..o + 4]; + if buf.eq(CPD_MAGIC_BYTES) { + let Ok(cpd) = CodePartitionDirectory::new(data[o..].to_vec(), o) else { + continue; + }; + gen3dirs.push(cpd); + } + o += 16; + } + if false { + println!("Found {} CPDs doing a full scan", gen3dirs.len()); + } + gen3dirs + } +} diff --git a/src/show.rs b/src/show.rs index 1691552..fd34251 100644 --- a/src/show.rs +++ b/src/show.rs @@ -1,6 +1,12 @@ -use intel_fw::dir::{gen2::Directory as Gen2Dir, gen3::CodePartitionDirectory}; -use intel_fw::fit::{Fit, FitError}; -use intel_fw::fpt::{FPT, ME_FW}; +use log::{error, warn}; + +use intel_fw::{ + Firmware, + dir::{gen2::Directory as Gen2Dir, gen3::CodePartitionDirectory}, + fit::Fit, + fpt::FPT, + me::{Directories, ME}, +}; fn print_gen2_dirs(dirs: &Vec) { println!("Gen 2 directories:"); @@ -36,8 +42,14 @@ fn print_gen3_dirs(dirs: &Vec) { } } -fn print_fpt(fpt: &FPT) { - let FPT { header, entries } = fpt; +fn print_me(me: &ME) { + println!("FPT at 0x{:08x}:", me.base); + let FPT { + offset, + header, + entries, + } = &me.fpt; + println!("Offset: {offset:08x}"); println!("{header}"); println!("Entries:"); println!(" name offset end size type notes"); @@ -46,43 +58,40 @@ fn print_fpt(fpt: &FPT) { for e in entries { println!("- {e}"); } + match &me.dirs { + Directories::Gen2(dirs) => print_gen2_dirs(dirs), + Directories::Gen3(dirs) => print_gen3_dirs(dirs), + } } -fn print_fit(fit: &Result) { - match fit { - Ok(fit) => { - println!("FIT @ {:08x}, {}", fit.offset, fit.header); - for e in &fit.entries { - println!(" {e}"); - } - } - Err(e) => { - log::error!("Could not parse FIT: {e:?}"); - } +fn print_fit(fit: &Fit) { + println!("FIT @ {:08x}, {}", fit.offset, fit.header); + for e in &fit.entries { + println!(" {e}"); } } -pub fn show(me_fw: &ME_FW, verbose: bool) { +pub fn show(fw: &Firmware, verbose: bool) { if verbose { - println!("{me_fw:#02x?}"); + println!("{fw:#02x?}"); } println!(); - let ME_FW { - base, - fpt, - gen3dirs, - gen2dirs, - fit, - } = me_fw; - println!("FPT at 0x{base:08x}:"); - print_fpt(fpt); - println!(); - print_fit(fit); - println!(); - if !gen2dirs.is_empty() { - print_gen2_dirs(&gen2dirs); + match &fw.ifd { + Ok(ifd) => println!("{ifd}"), + Err(e) => warn!("Could not parse IFD: {e:?}"), } - if !gen3dirs.is_empty() { - print_gen3_dirs(&gen3dirs); + if let Some(me_res) = &fw.me { + match me_res { + Ok(me) => print_me(&me), + Err(e) => error!("ME firmware could not be parsed: {e:?}"), + } + } else { + error!("No ME firmware found"); } + println!(); + match &fw.fit { + Ok(fit) => print_fit(&fit), + Err(e) => warn!("Could not parse FIT: {e:?}"), + } + println!(); }