Skip to content
Open
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
1 change: 1 addition & 0 deletions src/extractors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ pub mod srec;
pub mod svg;
pub mod swapped;
pub mod tarball;
pub mod tiff;
pub mod trx;
pub mod tsk;
pub mod ubi;
Expand Down
38 changes: 38 additions & 0 deletions src/extractors/tiff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType};
use crate::structures::tiff::get_tiff_size;

/// Defines the internal extractor function for carving out TIFF images
pub fn tiff_extractor() -> Extractor {
Extractor {
do_not_recurse: true,
utility: ExtractorType::Internal(extract_tiff_image),
..Default::default()
}
}

/// Internal extractor for carving TIFF images to disk
pub fn extract_tiff_image(
file_data: &[u8],
offset: usize,
output_directory: Option<&str>,
) -> ExtractionResult {
const OUTFILE_NAME: &str = "image.tiff";

let mut result = ExtractionResult {
..Default::default()
};

// Find the TIFF size
if let Ok(size) = get_tiff_size(&file_data[offset..]) {
result.size = Some(size);
result.success = true;

if output_directory.is_some() {
let chroot = Chroot::new(output_directory);
result.success =
chroot.carve_file(OUTFILE_NAME, file_data, offset, result.size.unwrap());
}
}

result
}
11 changes: 11 additions & 0 deletions src/magic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,17 @@ pub fn patterns() -> Vec<signatures::common::Signature> {
description: signatures::jpeg::DESCRIPTION.to_string(),
extractor: Some(extractors::jpeg::jpeg_extractor()),
},
// TIFF image
signatures::common::Signature {
name: "tiff".to_string(),
short: false,
magic_offset: 0,
always_display: false,
magic: signatures::tiff::tiff_magic(),
parser: signatures::tiff::tiff_parser,
description: signatures::tiff::DESCRIPTION.to_string(),
extractor: Some(extractors::tiff::tiff_extractor()),
},
// arcadyan obfuscated lzma
signatures::common::Signature {
name: "arcadyan".to_string(),
Expand Down
1 change: 1 addition & 0 deletions src/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ pub mod squashfs;
pub mod srec;
pub mod svg;
pub mod tarball;
pub mod tiff;
pub mod tplink;
pub mod trx;
pub mod ubi;
Expand Down
43 changes: 43 additions & 0 deletions src/signatures/tiff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::extractors::tiff::extract_tiff_image;
use crate::signatures::common::{CONFIDENCE_MEDIUM, SignatureError, SignatureResult};

/// Human readable description
pub const DESCRIPTION: &str = "TIFF image";

/// TIFF magic bytes
pub fn tiff_magic() -> Vec<Vec<u8>> {
vec![
// Little-endian TIFF
b"\x49\x49\x2A\x00".to_vec(),
// Big-endian TIFF
b"\x4D\x4D\x00\x2A".to_vec(),
]
}

/// Parse a TIFF image
pub fn tiff_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {
// Successful return value
let mut result = SignatureResult {
offset,
description: DESCRIPTION.to_string(),
confidence: CONFIDENCE_MEDIUM,
..Default::default()
};

// Perform an extraction dry-run
let dry_run = extract_tiff_image(file_data, offset, None);

// If the dry-run was a success, this is probably a valid TIFF file
if dry_run.success {
// Get the total size of the TIFF
if let Some(tiff_size) = dry_run.size {
// Report signature result
result.size = tiff_size;
result.description =
format!("{}, total size: {} bytes", result.description, result.size);
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 @@ -156,6 +156,7 @@ pub mod sevenzip;
pub mod shrs;
pub mod squashfs;
pub mod svg;
pub mod tiff;
pub mod tplink;
pub mod trx;
pub mod ubi;
Expand Down
92 changes: 92 additions & 0 deletions src/structures/tiff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use crate::structures::common::{self, StructureError};

/// TIFF magic bytes
const TIFF_MAGIC_LE: u16 = 0x4949; // "II" (little-endian)
const TIFF_MAGIC_BE: u16 = 0x4D4D; // "MM" (big-endian)
const TIFF_VERSION: u16 = 42;

/// Parse and validate a TIFF file header
pub fn get_tiff_size(tiff_data: &[u8]) -> Result<usize, StructureError> {
let tiff_header_structure = vec![
("identifier", "u16"), // 0x4949 for little-endian, 0x4D4D for big-endian
("version", "u16"), // Should be 42
("ifd_offset", "u32"), // Offset to first Image File Directory (IFD)
];

// Try parsing
if tiff_data.len() >= common::size(&tiff_header_structure) {
let identifier = u16::from_le_bytes(tiff_data[..2].try_into().unwrap());
if identifier != TIFF_MAGIC_LE && identifier != TIFF_MAGIC_BE {
return Err(StructureError);
}
let endian = if identifier == TIFF_MAGIC_LE { "little" } else { "big" };
if let Ok(tiff_header) = common::parse(tiff_data, &tiff_header_structure, endian) {
if tiff_header["version"] as u16 != TIFF_VERSION {
return Err(StructureError);
}
let ifd_offset = tiff_header["ifd_offset"];
if let Some(size) = calculate_tiff_size(tiff_data, ifd_offset, endian) {
return Ok(size);
}
}
}

Err(StructureError)
}

/// Calculate the total size of a TIFF file by parsing IFDs
fn calculate_tiff_size(tiff_data: &[u8], first_ifd_offset: usize, endian: &str) -> Option<usize> {
let mut max_offset = first_ifd_offset;
let mut current_ifd_offset = first_ifd_offset;

// Follow the IFD chain
while current_ifd_offset > 0 && current_ifd_offset < tiff_data.len() {
let ifd_data = &tiff_data.get(current_ifd_offset..)?;

let ifd_count_struct = vec![("num_dir_entries", "u16")];
// Parse number of directory entries
let ifd_count = common::parse(ifd_data, &ifd_count_struct, endian).ok()?;
let num_entries = ifd_count["num_dir_entries"];

let tag_structure = vec![
("tag_id", "u16"),
("data_type", "u16"),
("data_count", "u32"),
("data_offset", "u32"),
];
// Parse each tag entry
for i in 0..num_entries {
let entry_offset = common::size(&ifd_count_struct) + (i * common::size(&tag_structure));
let tag_data = ifd_data.get(entry_offset..entry_offset + common::size(&tag_structure))?;
let tag = common::parse(tag_data, &tag_structure, endian).ok()?;

// Calculate size of the data referenced by this tag
let data_type_size = match tag["data_type"] {
1 | 2 | 6 | 7 => 1, // BYTE, ASCII, SBYTE, UNDEFINED
3 | 8 => 2, // SHORT, SSHORT
4 | 9 | 11 => 4, // LONG, SLONG, FLOAT
5 | 10 | 12 => 8, // RATIONAL, SRATIONAL, DOUBLE
_ => 0,
};
let data_size = data_type_size * tag["data_count"];
// Update max_offset if data is stored out-of-line
if data_size > 4 {
let data_offset = tag["data_offset"];
max_offset = max_offset.max(data_offset + data_size);
}
}
// Calculate offset to next IFD pointer (after all entries)
let next_ifd_offset_pos = common::size(&ifd_count_struct) + (num_entries * common::size(&tag_structure));
// Get next IFD offset
let next_ifd_offset_data = ifd_data.get(next_ifd_offset_pos..)?;
let next_ifd_struct = vec![("next_ifd_offset", "u32")];
let next_ifd = common::parse(next_ifd_offset_data, &next_ifd_struct, endian).ok()?;
let ifd_size = next_ifd_offset_pos + common::size(&next_ifd_struct);

max_offset = max_offset.max(current_ifd_offset + ifd_size);
current_ifd_offset = next_ifd["next_ifd_offset"];
}

// Return the end of the last valid data
Some(max_offset)
}
Binary file added tests/inputs/tiff.bin
Binary file not shown.
17 changes: 17 additions & 0 deletions tests/tiff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
mod common;

#[test]
fn integration_test() {
const SIGNATURE_TYPE: &str = "tiff";
const INPUT_FILE_NAME: &str = "tiff.bin";

let expected_signature_offsets: Vec<usize> = vec![0, 0x143AEF];
let expected_extraction_offsets: Vec<usize> = vec![0, 0x143AEF];

let results = common::run_binwalk(SIGNATURE_TYPE, INPUT_FILE_NAME);
common::assert_results_ok(
results,
expected_signature_offsets,
expected_extraction_offsets,
);
}