Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 62 additions & 36 deletions src/fpt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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<FPTEntry>,
}

impl<'a> FPT {
pub fn parse(data: &'a [u8]) -> Option<Result<Self, FptError<'a>>> {
let m = &data[..4];
pub const FPT_SIZE: usize = size_of::<FPT>();

// The FPT magic is either at the start or at a 16 bytes offset.
fn determine_offset(data: &[u8]) -> Option<usize> {
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<CodePartitionDirectory>,
pub gen2dirs: Vec<Gen2Directory>,
pub fit: Result<Fit, FitError>,
impl<'a> FPT {
pub fn parse(data: &'a [u8]) -> Option<Result<Self, FptError<'a>>> {
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<Self, FptError<'a>>, 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)]
Expand Down Expand Up @@ -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(_))));
Expand All @@ -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);
}
151 changes: 26 additions & 125 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<IFD, IfdError>,
pub me: Option<Result<ME, String>>,
pub fit: Result<Fit, FitError>,
}

pub fn parse(data: &[u8], debug: bool) -> Result<ME_FW, String> {
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::<dir::gen2::Directory>::new();
let mut gen3dirs = Vec::<dir::gen3::CodePartitionDirectory>::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::<fpt::FPT>() <= 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())
}
48 changes: 27 additions & 21 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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);
}
},
}
Expand Down
Loading