-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #721 from ReFirmLabs/apfs_support
Apfs support
- Loading branch information
Showing
9 changed files
with
186 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -140,6 +140,7 @@ | |
//! ``` | ||
|
||
pub mod androidsparse; | ||
pub mod apfs; | ||
pub mod arcadyan; | ||
pub mod autel; | ||
pub mod bzip2; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -97,6 +97,7 @@ | |
//! ``` | ||
|
||
pub mod androidsparse; | ||
pub mod apfs; | ||
pub mod autel; | ||
pub mod binhdr; | ||
pub mod cab; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |