Skip to content

Commit

Permalink
Merge pull request #823 from ReFirmLabs/partial_zip_support
Browse files Browse the repository at this point in the history
Partial zip support
  • Loading branch information
devttys0 authored Feb 2, 2025
2 parents a9e3b3d + 66b8cb6 commit 99e08c4
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 14 deletions.
57 changes: 47 additions & 10 deletions src/signatures/zip.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::common::is_offset_safe;
use crate::signatures::common::{SignatureError, SignatureResult, CONFIDENCE_HIGH};
use crate::structures::zip::{parse_eocd_header, parse_zip_header};
use aho_corasick::AhoCorasick;
Expand All @@ -23,16 +24,52 @@ pub fn zip_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, Si
// Parse the ZIP file header
if let Ok(zip_file_header) = parse_zip_header(&file_data[offset..]) {
// Locate the end-of-central-directory header, which must come after the zip local file entries
if let Ok(zip_info) = find_zip_eof(file_data, offset) {
result.size = zip_info.eof - offset;
result.description = format!(
"{}, version: {}.{}, file count: {}, total size: {} bytes",
result.description,
zip_file_header.version_major,
zip_file_header.version_minor,
zip_info.file_count,
result.size
);
match find_zip_eof(file_data, offset) {
Ok(zip_info) => {
result.size = zip_info.eof - offset;
result.description = format!(
"{}, version: {}.{}, file count: {}, total size: {} bytes",
result.description,
zip_file_header.version_major,
zip_file_header.version_minor,
zip_info.file_count,
result.size
);
}
// If the ZIP file is corrupted and no EOCD header exists, attempt to parse all the individual ZIP file headers
Err(_) => {
let available_data = file_data.len() - offset;
let mut previous_file_header_offset = None;
let mut next_file_header_offset = offset + zip_file_header.total_size;

while is_offset_safe(
available_data,
next_file_header_offset,
previous_file_header_offset,
) {
match parse_zip_header(&file_data[next_file_header_offset..]) {
Ok(zip_header) => {
previous_file_header_offset = Some(next_file_header_offset);
next_file_header_offset += zip_header.total_size;
}
Err(_) => {
result.size = next_file_header_offset - offset;
result.description = format!(
"{}, version: {}.{}, missing end-of-central-directory header, total size: {} bytes",
result.description,
zip_file_header.version_major,
zip_file_header.version_minor,
result.size
);
break;
}
}
}
}
}

// Only return success if the identified ZIP file is larger than the first ZIP file entry
if result.size > zip_file_header.total_size {
return Ok(result);
}
}
Expand Down
23 changes: 19 additions & 4 deletions src/structures/zip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use crate::structures::common::{self, StructureError};

#[derive(Debug, Default, Clone)]
pub struct ZipFileHeader {
pub data_size: usize,
pub header_size: usize,
pub total_size: usize,
pub version_major: usize,
pub version_minor: usize,
}
Expand Down Expand Up @@ -53,17 +56,29 @@ pub fn parse_zip_header(zip_data: &[u8]) -> Result<ZipFileHeader, StructureError
COMPRESSION_ENCRYPTED,
];

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

// Parse the ZIP local file structure
if let Ok(zip_local_file_header) = common::parse(zip_data, &zip_local_file_structure, "little")
{
// Unused/reserved flag bits should be 0
if (zip_local_file_header["flags"] & UNUSED_FLAGS_MASK) == 0 {
// Specified compression method should be one of the defined ZIP compression methods
if allowed_compression_methods.contains(&zip_local_file_header["compression"]) {
return Ok(ZipFileHeader {
version_major: zip_local_file_header["version"] / 10,
version_minor: zip_local_file_header["version"] % 10,
});
result.version_major = zip_local_file_header["version"] / 10;
result.version_minor = zip_local_file_header["version"] % 10;
result.header_size = common::size(&zip_local_file_structure)
+ zip_local_file_header["file_name_len"]
+ zip_local_file_header["extra_field_len"];
result.data_size = if zip_local_file_header["compressed_size"] > 0 {
zip_local_file_header["compressed_size"]
} else {
zip_local_file_header["uncompressed_size"]
};
result.total_size = result.header_size + result.data_size;
return Ok(result);
}
}
}
Expand Down
Binary file added tests/inputs/zip.bin
Binary file not shown.
Binary file added tests/inputs/zip_truncated.bin
Binary file not shown.
8 changes: 8 additions & 0 deletions tests/zip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mod common;

#[test]
fn integration_test_valid_zip() {
const SIGNATURE_TYPE: &str = "zip";
const INPUT_FILE_NAME: &str = "zip.bin";
common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);
}
8 changes: 8 additions & 0 deletions tests/zip_truncated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mod common;

#[test]
fn integration_test_truncated_zip() {
const SIGNATURE_TYPE: &str = "zip";
const INPUT_FILE_NAME: &str = "zip_truncated.bin";
common::integration_test(SIGNATURE_TYPE, INPUT_FILE_NAME);
}

0 comments on commit 99e08c4

Please sign in to comment.