From 87aac861fa4e9a10e5aed706940e1889ba35b7f8 Mon Sep 17 00:00:00 2001 From: Daniel Maslowski Date: Mon, 17 Nov 2025 22:48:03 +0100 Subject: [PATCH 1/4] README: add references for development and caveats Signed-off-by: Daniel Maslowski --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index d616c52..6f82825 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,18 @@ To run the CLI via `cargo` directly, remember to add arguments after an extra `--`; i.e., to print the general help, invoke `cargo run --relase -- -h`, or, for a subcommand, e.g. `cargo run --relase -- me clean -h`. +For inspiration of how to explore and add new features, take a look at the notes +on the [analysis process](docs/analysis.md) and [extending](docs/extend.md). + +### Caveats + +Be careful: Always check claimed offsets and sizes in binary data structures to +be both reasonable and within the bounds of the given slices. Sizes can be +direct values, such as "this here points to that 8KB other thing", or counts, +which may lead to overallocation. Say a thing that typically has about a dozen +entries uses a `u32` to represent its count, which may end up being huge. As a +defensive measure, implement a maximum, say 100, to avoid running out of memory. + ## TODOs - [ ] sync up; has another patch that From b8a4a4b00f70f9cc1007cbf929e5c6ea0438578c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A7=80=EC=A7=80=20=E1=9A=A0=D7=93=20=28Jiji=20Freya=20D?= =?UTF-8?q?aniel=29=20Maslowski?= Date: Sun, 28 Jun 2026 10:19:26 +0200 Subject: [PATCH 2/4] elegantly map analysis result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 지지 ᚠד (Jiji Freya Daniel) Maslowski --- src/main.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3dcf144..59daa66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -175,19 +175,13 @@ fn main() -> Result<(), io::Error> { show::show(&fw, verbose); println!(); - let me_res = match fw.me { - Some(r) => r, - None => { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "no ME firmware recognized", - )); - } - }; - let me = match me_res { - Ok(r) => r, - Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)), - }; + let me = fw + .me + .ok_or(io::Error::new( + io::ErrorKind::NotFound, + "no ME firmware recognized", + ))? + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; if check { let fpt = &me.fpt_area.fpt; let cs = fpt.header_checksum(); From 6a23925a03eb162ace598f8fe37bfd5236985182 Mon Sep 17 00:00:00 2001 From: Daniel Maslowski Date: Mon, 17 Nov 2025 00:25:29 +0100 Subject: [PATCH 3/4] me: add feature to extract files for directory Signed-off-by: Daniel Maslowski --- src/me.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/me.rs b/src/me.rs index 40b8e92..187c657 100644 --- a/src/me.rs +++ b/src/me.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize}; use crate::EMPTY; +use crate::dir::gen2::Module; use crate::dir::{ gen2::Directory as Gen2Directory, gen3::{CPD_MAGIC_BYTES, CodePartitionDirectory}, @@ -50,6 +51,12 @@ pub struct FPTArea { pub original_size: usize, } +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct File { + pub name: String, + pub data: Vec, +} + impl FPTArea { /// Clear out fully removable partitions and adjust FPT pub fn clean(&mut self, options: &ClearOptions) { @@ -67,6 +74,67 @@ impl FPTArea { } } + pub fn files_for_dir(&self, part_name: &String) -> Vec { + match &self.partitions { + Partitions::Gen2(parts) => { + let dir = parts.iter().find(|p| p.entry().name() == *part_name); + match dir { + Some(Gen2Partition::Dir(d)) => { + let mut res = vec![]; + for m in &d.dir.modules { + match m { + Module::Huffman(Ok((e, _))) => { + let o = e.offset as usize; + let s = e.size as usize; + let data = d.data[o..o + s].to_vec(); + let name = format!("{}.huff", e.name()); + res.push(File { name, data }); + } + Module::Uncompressed(e) => { + let o = e.offset as usize; + let s = e.size as usize; + let data = d.data[o..o + s].to_vec(); + let name = format!("{}.bin", e.name()); + res.push(File { name, data }); + } + Module::Lzma(Ok(e)) => { + let o = e.offset as usize; + let s = e.size as usize; + let data = d.data[o..o + s].to_vec(); + let name = format!("{}.lzma", e.name()); + res.push(File { name, data }); + } + _ => {} + } + } + res + } + _ => vec![], + } + } + Partitions::Gen3(parts) => { + let dir = parts.iter().find(|p| p.entry().name() == *part_name); + match dir { + Some(Gen3Partition::Dir(d)) => { + let mut res = vec![]; + for e in &d.cpd.entries { + let f = e.flags_and_offset; + let o = f.offset() as usize; + let s = e.size as usize; + let data = d.data[o..o + s].to_vec(); + // NOTE: already includes extension + let name = e.name(); + res.push(File { name, data }); + } + res + } + _ => vec![], + } + } + _ => vec![], + } + } + pub fn check_dir_sigs(&self) -> Vec<(String, Result<(), String>)> { match &self.partitions { Partitions::Gen2(parts) => { From b36b0e800dc17402daf079bb4e5cd5d583ac25a2 Mon Sep 17 00:00:00 2001 From: Daniel Maslowski Date: Mon, 17 Nov 2025 00:25:43 +0100 Subject: [PATCH 4/4] add `me extract` command Signed-off-by: Daniel Maslowski --- src/main.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 59daa66..99961f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,8 @@ //! So we can only provide meaningful information by looking at a full firmware //! image in its entirety. This tool brings together all publicly known details. -use std::fs; use std::io::{self, Write}; +use std::{fs, path}; use clap::{Parser, Subcommand}; use log::{debug, error, info, warn}; @@ -80,6 +80,15 @@ enum MeCommand { /// File to read file_name: String, }, + /// Extract directory partition + #[clap(verbatim_doc_comment)] + Extract { + /// Partition to extract + #[clap(long, short)] + part_name: Option, + /// File to read + file_name: String, + }, } #[derive(Subcommand)] @@ -257,6 +266,70 @@ fn main() -> Result<(), io::Error> { let fw = Firmware::parse(&data, debug); show::show(&fw, verbose); } + MeCommand::Extract { + part_name, + file_name, + } => { + use intel_fw::part::gen2::Gen2Partition; + use intel_fw::part::gen3::Gen3Partition; + use intel_fw::part::partitions::Partitions; + + let data = fs::read(&file_name)?; + info!("Extracting {}", file_name); + + let fw = Firmware::parse(&data, debug); + let me = fw + .me + .ok_or(io::Error::new( + io::ErrorKind::NotFound, + "no ME firmware recognized", + ))? + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + let extract_dir = |dir_name: &String| -> Result<(), std::io::Error> { + let target_dir = path::Path::new("extracted").join(dir_name); + fs::create_dir_all(&target_dir)?; + let mods = &me.fpt_area.files_for_dir(dir_name); + for m in mods { + let f = target_dir.join(&m.name); + info!(" Extracting file {f:?}"); + let mut f = fs::File::create(f)?; + f.write_all(&m.data)?; + } + Ok(()) + }; + + // TODO: For partitions not holding a directory, extract the + // whole partition. + if let Some(p) = part_name { + extract_dir(&p)?; + } else { + match &me.fpt_area.partitions { + Partitions::Gen2(parts) => { + info!("ME Gen 2 recognized"); + warn!("NOTE: Huffman modules will not be decompressed yet."); + for p in parts { + if let Gen2Partition::Dir(d) = p { + let pname = &d.dir.name; + info!(" Extracting partition {pname}"); + extract_dir(pname)?; + } + } + } + Partitions::Gen3(parts) => { + info!("ME Gen 3 recognized"); + for p in parts { + if let Gen3Partition::Dir(d) = p { + let pname = &d.cpd.name; + info!(" Extracting partition {pname}"); + extract_dir(pname)?; + } + } + } + _ => error!("Unknown/unrecognized partitioning scheme"), + } + } + } }, }