Skip to content

Commit

Permalink
Merge pull request #721 from ReFirmLabs/apfs_support
Browse files Browse the repository at this point in the history
Apfs support
  • Loading branch information
devttys0 authored Oct 28, 2024
2 parents 5ef95bf + b624a5e commit 437db68
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 2 deletions.
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)
}

0 comments on commit 437db68

Please sign in to comment.