diff --git a/src/extractors.rs b/src/extractors.rs index ee4161613..f9663ccd5 100644 --- a/src/extractors.rs +++ b/src/extractors.rs @@ -141,6 +141,7 @@ pub mod androidsparse; pub mod arcadyan; +pub mod autel; pub mod bzip2; pub mod cab; pub mod common; diff --git a/src/extractors/autel.rs b/src/extractors/autel.rs new file mode 100644 index 000000000..f18e34f67 --- /dev/null +++ b/src/extractors/autel.rs @@ -0,0 +1,340 @@ +use crate::extractors::common::{Chroot, ExtractionResult, Extractor, ExtractorType}; +use crate::structures::autel::parse_autel_header; + +const BLOCK_SIZE: usize = 256; + +/// Defines the internal extractor function for deobfuscating Autel firmware +pub fn autel_extractor() -> Extractor { + Extractor { + utility: ExtractorType::Internal(autel_deobfuscate), + ..Default::default() + } +} + +/// Internal extractor for obfuscated Autel firmware +/// https://gist.github.com/sector7-nl/3fc815cd2497817ad461bfbd393294cb +pub fn autel_deobfuscate( + file_data: &[u8], + offset: usize, + output_directory: Option<&String>, +) -> ExtractionResult { + const OUTPUT_FILE_NAME: &str = "autel.decoded"; + + let mut result = ExtractionResult { + ..Default::default() + }; + + // Parse and validate the header + if let Ok(autel_header) = parse_autel_header(&file_data[offset..]) { + // Get the start and end offsets of the actual encoded data + let data_start = offset + autel_header.header_size; + let data_end = data_start + autel_header.data_size; + + // Get the encoded data + if let Some(autel_data) = file_data.get(data_start..data_end) { + // Interate through each block of the encoded data + let mut block_iter = autel_data.chunks(BLOCK_SIZE); + + loop { + match block_iter.next() { + None => { + // EOF + result.size = Some(autel_header.data_size); + result.success = true; + break; + } + Some(block_bytes) => { + // Decode the data block + let decoded_block = decode_autel_block(block_bytes); + + // Write to file, if requested + if output_directory.is_some() { + let chroot = Chroot::new(output_directory); + if !chroot.append_to_file(OUTPUT_FILE_NAME, &decoded_block) { + break; + } + } + } + } + } + } + } + + result +} + +/// Block decoder for autel encoded firmware. +/// block_data *must* be 256 bytes in size, or less. +fn decode_autel_block(block_data: &[u8]) -> Vec { + // Lookup table for encoding/decoding bytes + let encoding_table: Vec<(usize, usize)> = vec![ + (54, 147), + (96, 129), + (59, 193), + (191, 0), + (45, 130), + (96, 144), + (27, 129), + (152, 0), + (44, 180), + (118, 141), + (115, 129), + (210, 0), + (13, 164), + (27, 133), + (20, 192), + (139, 0), + (28, 166), + (17, 133), + (19, 193), + (224, 0), + (20, 161), + (145, 0), + (14, 193), + (12, 132), + (18, 161), + (17, 140), + (29, 192), + (246, 0), + (115, 178), + (28, 132), + (155, 0), + (12, 132), + (31, 165), + (20, 136), + (27, 193), + (142, 0), + (96, 164), + (18, 133), + (145, 0), + (23, 132), + (13, 165), + (13, 148), + (23, 193), + (19, 132), + (27, 178), + (83, 137), + (146, 0), + (145, 0), + (18, 166), + (96, 148), + (13, 193), + (159, 0), + (96, 166), + (20, 129), + (20, 193), + (27, 132), + (9, 160), + (96, 148), + (13, 192), + (159, 0), + (96, 180), + (142, 0), + (31, 193), + (155, 0), + (7, 166), + (224, 0), + (20, 192), + (27, 132), + (28, 160), + (17, 149), + (19, 193), + (96, 132), + (76, 164), + (208, 0), + (80, 192), + (78, 132), + (96, 160), + (27, 144), + (24, 193), + (140, 0), + (96, 178), + (17, 141), + (12, 193), + (224, 0), + (14, 161), + (17, 141), + (151, 0), + (14, 132), + (16, 165), + (96, 137), + (13, 193), + (155, 0), + (20, 161), + (29, 141), + (23, 192), + (24, 132), + (27, 178), + (10, 133), + (96, 192), + (140, 0), + (14, 180), + (17, 133), + (16, 192), + (144, 0), + (11, 163), + (13, 141), + (96, 192), + (17, 132), + (12, 178), + (96, 141), + (28, 192), + (27, 132), + (27, 130), + (18, 141), + (96, 193), + (31, 132), + (96, 181), + (13, 140), + (23, 193), + (224, 0), + (27, 166), + (142, 0), + (27, 192), + (24, 132), + (12, 183), + (96, 133), + (84, 192), + (14, 132), + (27, 178), + (10, 140), + (155, 0), + (9, 132), + (17, 160), + (56, 133), + (96, 192), + (82, 132), + (13, 160), + (27, 137), + (20, 193), + (139, 0), + (28, 161), + (145, 0), + (19, 192), + (118, 132), + (115, 165), + (20, 132), + (145, 0), + (14, 132), + (12, 167), + (146, 0), + (17, 193), + (29, 132), + (96, 176), + (28, 144), + (27, 193), + (140, 0), + (31, 180), + (148, 0), + (27, 192), + (14, 132), + (83, 160), + (18, 137), + (17, 193), + (23, 132), + (13, 165), + (13, 145), + (151, 0), + (147, 0), + (27, 178), + (96, 137), + (19, 193), + (159, 0), + (14, 160), + (25, 148), + (17, 193), + (142, 0), + (16, 180), + (27, 136), + (14, 193), + (224, 0), + (17, 178), + (12, 144), + (224, 0), + (28, 132), + (27, 160), + (13, 141), + (11, 193), + (96, 132), + (27, 165), + (30, 140), + (224, 0), + (146, 0), + (31, 165), + (29, 129), + (96, 192), + (140, 0), + (31, 161), + (24, 145), + (140, 0), + (96, 132), + (27, 165), + (29, 140), + (31, 192), + (154, 0), + (14, 161), + (27, 145), + (140, 0), + (18, 132), + (23, 167), + (96, 140), + (21, 129), + (14, 132), + (17, 165), + (9, 137), + (12, 193), + (155, 0), + (18, 161), + (96, 141), + (27, 192), + (148, 0), + (29, 178), + (23, 133), + (24, 192), + (155, 0), + (10, 180), + (96, 133), + (28, 192), + (14, 132), + (31, 130), + (28, 129), + (18, 193), + (31, 132), + (12, 180), + (13, 144), + (96, 193), + (31, 132), + (96, 160), + (13, 141), + (27, 193), + (18, 132), + (23, 181), + (26, 140), + (27, 193), + (156, 0), + (96, 166), + (79, 141), + (211, 0), + (76, 132), + (77, 160), + (75, 133), + (206, 0), + (182, 0), + (96, 129), + (59, 133), + (191, 0), + (173, 0), + ]; + + assert!(block_data.len() <= BLOCK_SIZE); + + let mut decoded_block: Vec = vec![]; + + for (i, byte) in block_data.iter().enumerate() { + let encoding = encoding_table[i]; + + decoded_block.push(((((*byte as usize) + encoding.0) ^ encoding.1) % 256) as u8); + } + + decoded_block +} diff --git a/src/magic.rs b/src/magic.rs index 1742f68b6..7e6113925 100644 --- a/src/magic.rs +++ b/src/magic.rs @@ -888,6 +888,17 @@ pub fn patterns() -> Vec { description: signatures::binhdr::DESCRIPTION.to_string(), extractor: None, }, + // Autel obfuscated firmware + signatures::common::Signature { + name: "autel".to_string(), + short: false, + magic_offset: 0, + always_display: false, + magic: signatures::autel::autel_magic(), + parser: signatures::autel::autel_parser, + description: signatures::autel::DESCRIPTION.to_string(), + extractor: Some(extractors::autel::autel_extractor()), + }, ]; binary_signatures diff --git a/src/signatures.rs b/src/signatures.rs index 3c5b09adb..113711ead 100644 --- a/src/signatures.rs +++ b/src/signatures.rs @@ -108,6 +108,7 @@ pub mod aes; pub mod androidsparse; pub mod arcadyan; +pub mod autel; pub mod binhdr; pub mod bzip2; pub mod cab; diff --git a/src/signatures/autel.rs b/src/signatures/autel.rs new file mode 100644 index 000000000..e1c012cd0 --- /dev/null +++ b/src/signatures/autel.rs @@ -0,0 +1,32 @@ +use crate::signatures::common::{SignatureError, SignatureResult, CONFIDENCE_MEDIUM}; +use crate::structures::autel::parse_autel_header; + +/// Human readable description +pub const DESCRIPTION: &str = "Autel obfuscated firmware"; + +/// Autel magic bytes +pub fn autel_magic() -> Vec> { + vec![b"ECC0101\x00".to_vec()] +} + +/// Validates the Autel header +pub fn autel_parser(file_data: &[u8], offset: usize) -> Result { + // Successful return value + let mut result = SignatureResult { + offset, + description: DESCRIPTION.to_string(), + confidence: CONFIDENCE_MEDIUM, + ..Default::default() + }; + + if let Ok(autel_header) = parse_autel_header(&file_data[offset..]) { + result.size = autel_header.header_size + autel_header.data_size; + result.description = format!( + "{}, header size: {} bytes, data size: {}, total size: {}", + result.description, autel_header.header_size, autel_header.data_size, result.size + ); + return Ok(result); + } + + Err(SignatureError) +} diff --git a/src/structures.rs b/src/structures.rs index 8f264d3d6..8455b32af 100644 --- a/src/structures.rs +++ b/src/structures.rs @@ -97,6 +97,7 @@ //! ``` pub mod androidsparse; +pub mod autel; pub mod binhdr; pub mod cab; pub mod chk; diff --git a/src/structures/autel.rs b/src/structures/autel.rs new file mode 100644 index 000000000..acd97a484 --- /dev/null +++ b/src/structures/autel.rs @@ -0,0 +1,47 @@ +use crate::common::get_cstring; +use crate::structures::common::{self, StructureError}; + +/// Struct to store Autel ECC header info +#[derive(Debug, Default, Clone)] +pub struct AutelECCHeader { + pub data_size: usize, + pub header_size: usize, +} + +/// Parses an Autel header +pub fn parse_autel_header(autel_data: &[u8]) -> Result { + const EXPECTED_HEADER_SIZE: usize = 0x20; + const COPYRIGHT_SIZE: usize = 16; + const EXPECTED_COPYRIGHT_STRING: &str = "Copyright Autel"; + + let autel_ecc_structure = vec![ + ("magic", "u64"), + ("data_size", "u32"), + ("header_size", "u32"), + // Followed by 16-byte copyright string + ]; + + // Parse the header + if let Ok(autel_header) = common::parse(autel_data, &autel_ecc_structure, "little") { + // Sanity check the reported header size + if autel_header["header_size"] == EXPECTED_HEADER_SIZE { + let copyright_start = common::size(&autel_ecc_structure); + let copyright_end = copyright_start + COPYRIGHT_SIZE; + + // Get the copyright string contained in the header + if let Some(copyright_bytes) = autel_data.get(copyright_start..copyright_end) { + let copyright_string = get_cstring(copyright_bytes); + + // Sanity check the copyright string value + if copyright_string == EXPECTED_COPYRIGHT_STRING { + return Ok(AutelECCHeader { + data_size: autel_header["data_size"], + header_size: autel_header["header_size"], + }); + } + } + } + } + + Err(StructureError) +} diff --git a/src/structures/dlob.rs b/src/structures/dlob.rs index 6d0ce9aeb..d8069491f 100644 --- a/src/structures/dlob.rs +++ b/src/structures/dlob.rs @@ -1,6 +1,7 @@ use crate::structures::common::{self, StructureError}; /// Struct to store DLOB header info +#[derive(Debug, Default, Clone)] pub struct DlobHeader { pub data_size: usize, pub header_size: usize,