Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apfs support #721

Merged
merged 6 commits into from
Oct 28, 2024
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
9 changes: 9 additions & 0 deletions dependencies/src.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,12 @@ make
cp srec2bin /usr/local/bin/
cd /tmp
rm -rf /tmp/srec

# Install latest version of 7z (static) for APFS support
mkdir /tmp/7z
cd /tmp/7z
wget https://www.7-zip.org/a/7z2408-linux-x64.tar.xz
tar -xf 7z2408-linux-x64.tar.xz
cp 7zzs /usr/local/bin/
cd /tmp
rm -rf /tmp/7z
4 changes: 2 additions & 2 deletions src/binwalk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,8 @@ impl Binwalk {
signature_result.name, FILE_START_OFFSET
);

// Only update the next_valid_offset if confidence is at least medium
if signature_result.confidence >= signatures::common::CONFIDENCE_MEDIUM {
// Only update the next_valid_offset if confidence is high; these are, after all, short signatures
if signature_result.confidence >= signatures::common::CONFIDENCE_HIGH {
next_valid_offset = signature_result.offset + signature_result.size;
}

Expand Down
1 change: 1 addition & 0 deletions src/extractors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
//! ```

pub mod androidsparse;
pub mod apfs;
pub mod arcadyan;
pub mod autel;
pub mod bzip2;
Expand Down
18 changes: 18 additions & 0 deletions src/extractors/apfs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::extractors;

/// Describes how to run the 7zzs utility to extract APFS images
pub fn apfs_extractor() -> extractors::common::Extractor {
extractors::common::Extractor {
utility: extractors::common::ExtractorType::External("7zzs".to_string()),
extension: "img".to_string(),
arguments: vec![
"x".to_string(), // Perform extraction
"-y".to_string(), // Assume Yes to all questions
"-o.".to_string(), // Output to current working directory
extractors::common::SOURCE_FILE_PLACEHOLDER.to_string(),
],
// If there is trailing data after the compressed data, extraction will happen but exit code will be 2
exit_codes: vec![0, 2],
..Default::default()
}
}
11 changes: 11 additions & 0 deletions src/magic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,17 @@ pub fn patterns() -> Vec<signatures::common::Signature> {
description: signatures::ntfs::DESCRIPTION.to_string(),
extractor: Some(extractors::tsk::tsk_extractor()),
},
// APFS
signatures::common::Signature {
name: "apfs".to_string(),
short: false,
magic_offset: 0,
always_display: false,
magic: signatures::apfs::apfs_magic(),
parser: signatures::apfs::apfs_parser,
description: signatures::apfs::DESCRIPTION.to_string(),
extractor: Some(extractors::apfs::apfs_extractor()),
},
];

binary_signatures
Expand Down
1 change: 1 addition & 0 deletions src/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
//! ```
pub mod aes;
pub mod androidsparse;
pub mod apfs;
pub mod arcadyan;
pub mod autel;
pub mod binhdr;
Expand Down
56 changes: 56 additions & 0 deletions src/signatures/apfs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use crate::signatures::common::{SignatureError, SignatureResult, CONFIDENCE_MEDIUM};
use crate::structures::apfs::{parse_apfs_header, MAGIC_OFFSET};

/// Human readable description
pub const DESCRIPTION: &str = "APple File System";

/// APFS magic bytes
pub fn apfs_magic() -> Vec<Vec<u8>> {
vec![b"NXSB".to_vec()]
}

/// Validates the APFS header
pub fn apfs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {
const MBR_BLOCK_SIZE: usize = 512;

// Successful return value
let mut result = SignatureResult {
description: DESCRIPTION.to_string(),
confidence: CONFIDENCE_MEDIUM,
..Default::default()
};

if offset >= MAGIC_OFFSET {
result.offset = offset - MAGIC_OFFSET;
let available_data = file_data.len() - result.offset;

if let Ok(apfs_header) = parse_apfs_header(&file_data[result.offset..]) {
let mut truncated_message = "".to_string();
result.size = apfs_header.block_count * apfs_header.block_size;

// It is observed that an APFS contained in an EFIGPT with a protective MBR includes the MBR block in its size.
// If the APFS image is pulled out of the EFIGPT, the reported size will be 512 bytes too long, but otherwise valid.
if result.size > available_data {
let truncated_size = result.size - available_data;

// If the calculated size is 512 bytes short, adjust the reported APFS size accordingly
if truncated_size == MBR_BLOCK_SIZE {
result.size -= truncated_size;
truncated_message = format!(" (truncated by {} bytes)", truncated_size);
}
}

result.description = format!(
"{}, block size: {} bytes, block count: {}, total size: {} bytes{}",
result.description,
apfs_header.block_size,
apfs_header.block_count,
result.size,
truncated_message
);
return Ok(result);
}
}

Err(SignatureError)
}
1 change: 1 addition & 0 deletions src/structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
//! ```

pub mod androidsparse;
pub mod apfs;
pub mod autel;
pub mod binhdr;
pub mod cab;
Expand Down
87 changes: 87 additions & 0 deletions src/structures/apfs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use crate::structures::common::{self, StructureError};

/// Offset of the APFS magic bytes from the start of the APFS image
pub const MAGIC_OFFSET: usize = 0x20;

/// Struct to store APFS header info
#[derive(Debug, Default, Clone)]
pub struct APFSHeader {
pub block_size: usize,
pub block_count: usize,
}

/// Parses an APFS header
pub fn parse_apfs_header(apfs_data: &[u8]) -> Result<APFSHeader, StructureError> {
const MAX_FS_COUNT: usize = 100;
const FS_COUNT_BLOCK_SIZE: usize = 512;

// Partial APFS header, just to figure out the size of the image and validate some fields
// https://developer.apple.com/support/downloads/Apple-File-System-Reference.pdf
let apfs_structure = vec![
("magic", "u32"),
("block_size", "u32"),
("block_count", "u64"),
("nx_features", "u64"),
("nx_ro_compat_features", "u64"),
("nx_incompat_features", "u64"),
("nx_uuid_p1", "u64"),
("nx_uuid_p2", "u64"),
("nx_next_oid", "u64"),
("nx_next_xid", "u64"),
("nx_xp_desc_blocks", "u32"),
("nx_xp_data_blocks", "u32"),
("nx_xp_desc_base", "u64"),
("nx_xp_data_base", "u64"),
("nx_xp_desc_next", "u32"),
("nx_xp_data_next", "u32"),
("nx_xp_desc_index", "u32"),
("nx_xp_desc_len", "u32"),
("nx_xp_data_index", "u32"),
("nx_xp_data_len", "u32"),
("nx_spaceman_oid", "u64"),
("nx_omap_oid", "u64"),
("nx_reaper_oid", "u64"),
("nx_xp_test_type", "u32"),
("nx_xp_max_file_systems", "u32"),
];

// Expected values of superblock flag fields
let allowed_feature_flags: Vec<usize> = vec![0, 1, 2, 3];
let allowed_incompat_flags: Vec<usize> = vec![0, 1, 2, 3, 0x100, 0x101, 0x102, 0x103];
let allowed_ro_compat_flags: Vec<usize> = vec![0];

let apfs_struct_start = MAGIC_OFFSET;
let apfs_struct_end = apfs_struct_start + common::size(&apfs_structure);

// Parse the header
if let Some(apfs_structure_data) = apfs_data.get(apfs_struct_start..apfs_struct_end) {
if let Ok(apfs_header) = common::parse(apfs_structure_data, &apfs_structure, "little") {
// Simple sanity check on the reported block data
if apfs_header["block_size"] != 0 && apfs_header["block_count"] != 0 {
// Sanity check the feature flags
if allowed_feature_flags.contains(&apfs_header["nx_features"])
&& allowed_ro_compat_flags.contains(&apfs_header["nx_ro_compat_features"])
&& allowed_incompat_flags.contains(&apfs_header["nx_incompat_features"])
{
// The test_type field *must* be NULL
if apfs_header["nx_xp_test_type"] == 0 {
// Calculate the file system count; this is max_file_systems divided by 512, rounded up to nearest whole
let fs_count = ((apfs_header["nx_xp_max_file_systems"] as f32)
/ (FS_COUNT_BLOCK_SIZE as f32))
.ceil() as usize;

// Sanity check the file system count
if fs_count > 0 && fs_count <= MAX_FS_COUNT {
return Ok(APFSHeader {
block_size: apfs_header["block_size"],
block_count: apfs_header["block_count"],
});
}
}
}
}
}
}

Err(StructureError)
}