introduce concept of context in various format, to communicate information found in detection phase to extractor

- this does bring inconsistency to the detectors of formats, but  I will work on fixing that. all detectors should be using Result<> anyway.
- also add some static values to mtk_pkg , mtk_bdp
- these improvements will be applied to other formats in future as well
This commit is contained in:
theubusu
2026-02-02 01:54:35 +01:00
parent 13482802ba
commit cf46b29e3d
7 changed files with 172 additions and 129 deletions
+22 -16
View File
@@ -3,9 +3,14 @@ use std::path::{Path};
use std::fs::{self, OpenOptions}; use std::fs::{self, OpenOptions};
use std::io::{Seek, SeekFrom, Read, Write}; use std::io::{Seek, SeekFrom, Read, Write};
use binrw::{BinRead, BinReaderExt}; use binrw::{BinRead, BinReaderExt};
use std::sync::OnceLock;
use crate::utils::common; use crate::utils::common;
pub struct MtkBdpContext {
pitit_offset: u64,
}
static PITIT_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50];
static PITIT_END_MARKER: u32 = 0x69_54_49_50; //PITi - end marker of PITIT
#[derive(BinRead)] #[derive(BinRead)]
struct PITITPITEntry { struct PITITPITEntry {
@@ -32,6 +37,8 @@ struct PITHeader {
entry_count: u32, entry_count: u32,
} }
static PIT_MAGIC: [u8; 8] = [0xDC, 0xEA, 0x30, 0x85, 0xDC, 0xEA, 0x30, 0x85];
#[derive(BinRead)] #[derive(BinRead)]
struct PITEntry { struct PITEntry {
#[br(count = 16)] name_bytes: Vec<u8>, #[br(count = 16)] name_bytes: Vec<u8>,
@@ -56,36 +63,35 @@ struct BITEntry {
_unknown: u32, _unknown: u32,
} }
static PITIT_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50]; static BIT_MAGIC: [u8; 20] = [0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85];
static PITIT_OFFSET: OnceLock<usize> = OnceLock::new(); static BIT_END_MARKER: u32 = 0x85_30_EF_EF; //EF EF 30 85 - end marker of BIT
fn find_bytes(data: &[u8], pattern: &[u8]) -> Option<usize> { fn find_bytes(data: &[u8], pattern: &[u8]) -> Option<usize> {
data.windows(pattern.len()).position(|window| window == pattern) data.windows(pattern.len()).position(|window| window == pattern)
} }
pub fn is_mtk_bdp_file(mut file: &File) -> bool { pub fn is_mtk_bdp_file(mut file: &File) -> Result<Option<MtkBdpContext>, Box<dyn std::error::Error>> {
let file_size = file.metadata().expect("REASON").len(); let file_size = file.metadata()?.len();
let mut data = Vec::new(); let mut data = Vec::new();
// I wish there was a better way // I wish there was a better way
let start_offset = file_size.saturating_sub(file_size / 20); let start_offset = file_size.saturating_sub(file_size / 20);
let _ = file.seek(SeekFrom::Start(start_offset)); let _ = file.seek(SeekFrom::Start(start_offset));
file.read_to_end(&mut data).expect("Failed to read from file."); file.read_to_end(&mut data)?;
if let Some(pos) = find_bytes(&data, &PITIT_MAGIC) { if let Some(pos) = find_bytes(&data, &PITIT_MAGIC) {
PITIT_OFFSET.set(start_offset as usize + pos).unwrap(); Ok(Some(MtkBdpContext {pitit_offset: start_offset + pos as u64}))
true
} else { } else {
false Ok(None)
} }
} }
pub fn extract_mtk_bdp(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> { pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpContext) -> Result<(), Box<dyn std::error::Error>> {
let offset = PITIT_OFFSET.get().unwrap(); let offset = context.pitit_offset;
println!("\nReading PITIT at: {}", offset); println!("\nReading PITIT at: {}", offset);
file.seek(SeekFrom::Start(*offset as u64 + 8))?; file.seek(SeekFrom::Start(offset + 8))?;
let pitit_check = common::read_exact(&mut file, 8)?; let pitit_check = common::read_exact(&mut file, 8)?;
//{UPG_INFO}the upg bin is %d version(old is 0,new is 1)!\n //{UPG_INFO}the upg bin is %d version(old is 0,new is 1)!\n
@@ -95,7 +101,7 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str) -> Result<(), Box<d
let mut bit_offset: u64 = 0; let mut bit_offset: u64 = 0;
loop { loop {
let pitit_pit_entry: PITITPITEntry = file.read_le()?; let pitit_pit_entry: PITITPITEntry = file.read_le()?;
if pitit_pit_entry.nand_size == 0x69544950 {break}; //PITi - end marker of PITIT if pitit_pit_entry.nand_size == PITIT_END_MARKER {break};
if pitit_ver == 1 { if pitit_ver == 1 {
//old PITIT does not have BIT entry, because BIT appears directly after PITIT //old PITIT does not have BIT entry, because BIT appears directly after PITIT
let pitit_bit_entry: PITITBITEntry = file.read_le()?; let pitit_bit_entry: PITITBITEntry = file.read_le()?;
@@ -117,7 +123,7 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str) -> Result<(), Box<d
file.seek(SeekFrom::Start(pit_offset))?; file.seek(SeekFrom::Start(pit_offset))?;
let mut pit_entries: Vec<PITEntry> = Vec::new(); let mut pit_entries: Vec<PITEntry> = Vec::new();
let pit_header: PITHeader = file.read_le()?; let pit_header: PITHeader = file.read_le()?;
if pit_header.pit_magic != b"\xDC\xEA\x30\x85\xDC\xEA\x30\x85" { if pit_header.pit_magic != PIT_MAGIC {
println!("Invalid PIT Magic!"); println!("Invalid PIT Magic!");
return Ok(()); return Ok(());
} }
@@ -135,7 +141,7 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str) -> Result<(), Box<d
file.seek(SeekFrom::Start(bit_offset))?; file.seek(SeekFrom::Start(bit_offset))?;
let mut bit_entries: Vec<BITEntry> = Vec::new(); let mut bit_entries: Vec<BITEntry> = Vec::new();
let bit_magic = common::read_exact(&mut file, 20)?; let bit_magic = common::read_exact(&mut file, 20)?;
if bit_magic != b"\xCD\xAB\x30\x85\xCD\xAB\x30\x85\xCD\xAB\x30\x85\xCD\xAB\x30\x85\xCD\xAB\x30\x85" { if bit_magic != BIT_MAGIC {
println!("Invalid BIT Magic!"); println!("Invalid BIT Magic!");
return Ok(()); return Ok(());
} }
@@ -143,7 +149,7 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str) -> Result<(), Box<d
let mut bit_i = 0; let mut bit_i = 0;
loop { loop {
let bit_entry: BITEntry = file.read_le()?; let bit_entry: BITEntry = file.read_le()?;
if bit_entry.partition_id == 0x8530EFEF {break}; //EF EF 30 85 - end marker of BIT if bit_entry.partition_id == BIT_END_MARKER {break};
println!("{}. ID: {:02x}, Offset: {}, Size: {}, Offset in part: {}", println!("{}. ID: {:02x}, Offset: {}, Size: {}, Offset in part: {}",
bit_i + 1, bit_entry.partition_id, bit_entry.offset, bit_entry.size, bit_entry.offset_in_target_part); bit_i + 1, bit_entry.partition_id, bit_entry.offset, bit_entry.size, bit_entry.offset_in_target_part);
bit_entries.push(bit_entry); bit_entries.push(bit_entry);
+42 -21
View File
@@ -1,6 +1,6 @@
use std::path::Path; use std::path::Path;
use std::fs::{self, File, OpenOptions}; use std::fs::{self, File, OpenOptions};
use std::io::{Write, Cursor, Seek}; use std::io::{Write, Cursor, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt}; use binrw::{BinRead, BinReaderExt};
use crate::utils::common; use crate::utils::common;
@@ -8,6 +8,11 @@ use crate::utils::aes::{decrypt_aes128_cbc_nopad};
use crate::utils::lzhs::{decompress_lzhs_fs_file2file}; use crate::utils::lzhs::{decompress_lzhs_fs_file2file};
use crate::keys; use crate::keys;
pub struct MtkPkgContext {
is_philips_variant: bool,
decrypted_header: Vec<u8>,
}
#[derive(BinRead)] #[derive(BinRead)]
struct Header { struct Header {
#[br(count = 4)] vendor_magic_bytes: Vec<u8>, #[br(count = 4)] vendor_magic_bytes: Vec<u8>,
@@ -52,6 +57,17 @@ impl PartEntry {
} }
} }
pub static MTK_HEADER_MAGIC: &[u8; 8] = b"#DH@FiRm";
pub static MTK_RESERVED_MAGIC: &[u8; 16] = b"reserved mtk inc";
pub static MTK_META_MAGIC: &[u8; 4] = b"iMtK";
pub static MTK_META_PAD_MAGIC: &[u8; 4] = b"iPAd";
pub static CRYPTED_HEADER_SIZE: usize = 0x30;
static HEADER_SIZE: usize = 0x90;
static PHILIPS_EXTRA_HEADER_SIZE: usize = 0x80;
static _PHILIPS_FOOTER_SIGNATURE_SIZE: usize = 0x100;
static HEADER_KEY: [u8; 16] = [ static HEADER_KEY: [u8; 16] = [
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
@@ -59,34 +75,39 @@ static HEADER_KEY: [u8; 16] = [
static HEADER_IV: [u8; 16] = [0x00; 16]; static HEADER_IV: [u8; 16] = [0x00; 16];
pub fn is_mtk_pkg_file(file: &File) -> bool { pub fn is_mtk_pkg_file(file: &File) -> Result<Option<MtkPkgContext>, Box<dyn std::error::Error>> {
let mut encrypted_header = common::read_file(&file, 0, 144).expect("Failed to read from file."); let mut encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?;
let mut header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV).expect("Decryption error!"); let mut header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
if &header[4..12] == b"#DH@FiRm" { if &header[4..12] == MTK_HEADER_MAGIC {
true Ok(Some(MtkPkgContext { is_philips_variant: false, decrypted_header: header}))
} else { } else {
// try for philips which has additional 128 bytes at beginning // try for philips which has additional 128 bytes at beginning
encrypted_header = common::read_file(&file, 128, 144).expect("Failed to read from file."); encrypted_header = common::read_file(&file, PHILIPS_EXTRA_HEADER_SIZE as u64, HEADER_SIZE)?;
header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV).expect("Decryption error!"); header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
if &header[4..12] == b"#DH@FiRm" { if &header[4..12] == MTK_HEADER_MAGIC {
true Ok(Some(MtkPkgContext { is_philips_variant: true, decrypted_header: header }))
} else { } else {
false Ok(None)
} }
} }
} }
pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> { pub fn extract_mtk_pkg(mut file: &File, output_folder: &str, context: MtkPkgContext) -> Result<(), Box<dyn std::error::Error>> {
let file_size = file.metadata()?.len(); let file_size = file.metadata()?.len();
let encrypted_header = common::read_exact(&mut file, 144)?; let header = context.decrypted_header;
let header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
let mut hdr_reader = Cursor::new(header); let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?; let hdr: Header = hdr_reader.read_le()?;
println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" , println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" ,
hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name()); hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name());
if context.is_philips_variant {
file.seek(SeekFrom::Start(HEADER_SIZE as u64 + PHILIPS_EXTRA_HEADER_SIZE as u64))?;
} else {
file.seek(SeekFrom::Start(HEADER_SIZE as u64))?;
}
let mut part_n = 0; let mut part_n = 0;
while file.stream_position()? < file_size as u64 { while file.stream_position()? < file_size as u64 {
part_n += 1; part_n += 1;
@@ -96,7 +117,7 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box<d
println!("\n#{} - {}, Size: {}{} {}", println!("\n#{} - {}, Size: {}{} {}",
part_n, part_entry.name(), part_entry.size, if part_entry.is_compressed() {" [COMPRESSED]"} else {""}, if part_entry.is_encrypted() {"[ENCRYPTED]"} else {""} ); part_n, part_entry.name(), part_entry.size, if part_entry.is_compressed() {" [COMPRESSED]"} else {""}, if part_entry.is_encrypted() {"[ENCRYPTED]"} else {""} );
let data = common::read_exact(&mut file, part_entry.size as usize + 48)?; let data = common::read_exact(&mut file, part_entry.size as usize + CRYPTED_HEADER_SIZE)?;
if part_entry.size == 0 { if part_entry.size == 0 {
println!("- Empty entry, skipping!"); println!("- Empty entry, skipping!");
@@ -108,7 +129,7 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box<d
let mut matching_key: Option<[u8; 16]> = None; let mut matching_key: Option<[u8; 16]> = None;
let mut matching_iv: Option<[u8; 16]> = None; let mut matching_iv: Option<[u8; 16]> = None;
let crypted_header = &data[..48]; let crypted_header = &data[..CRYPTED_HEADER_SIZE];
// try decrypting with vendor magic repeated 4 times (works for most) // try decrypting with vendor magic repeated 4 times (works for most)
let mut key = [0u8; 16]; let mut key = [0u8; 16];
@@ -116,7 +137,7 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box<d
key[i * 4..(i + 1) * 4].copy_from_slice(&hdr.vendor_magic_bytes); key[i * 4..(i + 1) * 4].copy_from_slice(&hdr.vendor_magic_bytes);
} }
let try_decrypt = decrypt_aes128_cbc_nopad(&crypted_header, &key, &HEADER_IV)?; let try_decrypt = decrypt_aes128_cbc_nopad(&crypted_header, &key, &HEADER_IV)?;
if try_decrypt.starts_with(b"reserved mtk inc") { if try_decrypt.starts_with(MTK_RESERVED_MAGIC) {
println!("- Decrypting with 4xVendor magic..."); println!("- Decrypting with 4xVendor magic...");
matching_key = Some(key); matching_key = Some(key);
matching_iv = Some(HEADER_IV); matching_iv = Some(HEADER_IV);
@@ -128,7 +149,7 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box<d
let iv_array: [u8; 16] = hex::decode(iv_hex)?.as_slice().try_into()?; let iv_array: [u8; 16] = hex::decode(iv_hex)?.as_slice().try_into()?;
let try_decrypt = decrypt_aes128_cbc_nopad(&crypted_header, &key_array, &iv_array)?; let try_decrypt = decrypt_aes128_cbc_nopad(&crypted_header, &key_array, &iv_array)?;
if try_decrypt.starts_with(b"reserved mtk inc") { if try_decrypt.starts_with(MTK_RESERVED_MAGIC) {
println!("- Decrypting with key {}...", name); println!("- Decrypting with key {}...", name);
matching_key = Some(key_array); matching_key = Some(key_array);
matching_iv = Some(iv_array); matching_iv = Some(iv_array);
@@ -155,9 +176,9 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box<d
} }
//strip iMtK thing and get version //strip iMtK thing and get version
let extra_header_len = if &out_data[48..52] == b"iMtK" { let extra_header_len = if &out_data[48..52] == MTK_META_MAGIC {
let imtk_len = u32::from_le_bytes(out_data[52..56].try_into().unwrap()); let imtk_len = u32::from_le_bytes(out_data[52..56].try_into().unwrap());
if &out_data[56..60] != b"iPAd" { if &out_data[56..60] != MTK_META_PAD_MAGIC {
let version_len = u32::from_le_bytes(out_data[56..60].try_into().unwrap()); let version_len = u32::from_le_bytes(out_data[56..60].try_into().unwrap());
let version = common::string_from_bytes(&out_data[60..60 + version_len as usize]); let version = common::string_from_bytes(&out_data[60..60 + version_len as usize]);
println!("- Version: {}", version); println!("- Version: {}", version);
@@ -171,7 +192,7 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box<d
let output_path = Path::new(&output_folder).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"}); let output_path = Path::new(&output_folder).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"});
fs::create_dir_all(&output_folder)?; fs::create_dir_all(&output_folder)?;
let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?; let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?;
out_file.write_all(&out_data[48 + extra_header_len as usize..])?; out_file.write_all(&out_data[CRYPTED_HEADER_SIZE + extra_header_len as usize..])?;
if part_entry.is_compressed() { if part_entry.is_compressed() {
let lzhs_out_path = Path::new(&output_folder).join(part_entry.name() + ".bin"); let lzhs_out_path = Path::new(&output_folder).join(part_entry.name() + ".bin");
+36 -35
View File
@@ -1,6 +1,6 @@
use std::path::Path; use std::path::Path;
use std::fs::{self, File, OpenOptions}; use std::fs::{self, File, OpenOptions};
use std::io::{Write, Cursor, Seek}; use std::io::{Write, Cursor, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt}; use binrw::{BinRead, BinReaderExt};
use crate::utils::common; use crate::utils::common;
@@ -8,6 +8,15 @@ use crate::utils::aes::{decrypt_aes128_cbc_nopad};
use crate::utils::lzhs::{decompress_lzhs_fs_file2file}; use crate::utils::lzhs::{decompress_lzhs_fs_file2file};
use crate::keys; use crate::keys;
use crate::formats::mtk_pkg::{MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
pub struct MtkPkgNewContext {
matching_key_name: String,
matching_key_key: [u8; 16],
matching_key_iv: [u8; 16],
decrypted_header: Vec<u8>,
}
#[derive(BinRead)] #[derive(BinRead)]
struct Header { struct Header {
#[br(count = 4)] vendor_magic_bytes: Vec<u8>, #[br(count = 4)] vendor_magic_bytes: Vec<u8>,
@@ -53,45 +62,36 @@ impl PartEntry {
} }
} }
pub fn is_mtk_pkg_new_file(file: &File) -> bool { static HEADER_SIZE: usize = 0x170;
let encrypted_header = common::read_file(&file, 0, 112).expect("Failed to read from file.");
for (key_hex, iv_hex, _name) in keys::MTK_PKG_CUST {
let key_array: [u8; 16] = hex::decode(key_hex).expect("Error").as_slice().try_into().expect("Error");
let iv_array: [u8; 16] = hex::decode(iv_hex).expect("Error").as_slice().try_into().expect("Error");
let try_decrypt = decrypt_aes128_cbc_nopad(&encrypted_header, &key_array, &iv_array).expect("Failed to decrypt.");
if &try_decrypt[4..12] == b"#DH@FiRm" { pub fn is_mtk_pkg_new_file(file: &File) -> Result<Option<MtkPkgNewContext>, Box<dyn std::error::Error>> {
return true; let encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?;
for (key_hex, iv_hex, name) in keys::MTK_PKG_CUST {
let key_array: [u8; 16] = hex::decode(key_hex)?.as_slice().try_into()?;
let iv_array: [u8; 16] = hex::decode(iv_hex)?.as_slice().try_into()?;
let try_decrypt = decrypt_aes128_cbc_nopad(&encrypted_header, &key_array, &iv_array)?;
if &try_decrypt[4..12] == MTK_HEADER_MAGIC {
return Ok(Some(MtkPkgNewContext {
matching_key_name: name.to_string(),
matching_key_key: key_array,
matching_key_iv: iv_array,
decrypted_header: try_decrypt
}));
} }
} }
false Ok(None)
} }
pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> { pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str, context: MtkPkgNewContext) -> Result<(), Box<dyn std::error::Error>> {
let file_size = file.metadata()?.len(); let file_size = file.metadata()?.len();
let encrypted_header = common::read_exact(&mut file, 368)?;
let mut header = Vec::new(); //the key was founf, and header was decrypted at detection stage so we can reuse
let mut matching_key: Option<[u8; 16]> = None; println!("Using key {}", context.matching_key_name);
let mut matching_iv: Option<[u8; 16]> = None; let key_array = context.matching_key_key;
//find key, the header and data key will be the same let iv_array = context.matching_key_iv;
for (key_hex, iv_hex, name) in keys::MTK_PKG_CUST { let header = context.decrypted_header;
let key_array = hex::decode(key_hex)?.as_slice().try_into()?;
let iv_array = hex::decode(iv_hex)?.as_slice().try_into()?;
header = decrypt_aes128_cbc_nopad(&encrypted_header, &key_array, &iv_array)?;
if &header[4..12] == b"#DH@FiRm" {
println!("Using key {}", name);
matching_key = Some(key_array);
matching_iv = Some(iv_array);
break
}
}
if matching_key.is_none() && matching_iv.is_none() {
println!("Failed to find key!");
return Ok(())
}
let mut hdr_reader = Cursor::new(header); let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?; let hdr: Header = hdr_reader.read_le()?;
@@ -99,6 +99,8 @@ pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str) -> Result<(), B
println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" , println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" ,
hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name()); hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name());
file.seek(SeekFrom::Start(HEADER_SIZE as u64))?;
let mut part_n = 0; let mut part_n = 0;
while file.stream_position()? < file_size as u64 { while file.stream_position()? < file_size as u64 {
part_n += 1; part_n += 1;
@@ -120,7 +122,6 @@ pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str) -> Result<(), B
let mut out_data; let mut out_data;
if part_entry.is_encrypted() { if part_entry.is_encrypted() {
println!("- Decrypting..."); println!("- Decrypting...");
let (key_array, iv_array) = (matching_key.unwrap(), matching_iv.unwrap());
//data aligned to 16 bytes is AES encrypted. the remaining unaligned data is XORed with the key //data aligned to 16 bytes is AES encrypted. the remaining unaligned data is XORed with the key
let align_len = data.len() & !15; let align_len = data.len() & !15;
let (aes_enc, xor_tail) = data.split_at(align_len); let (aes_enc, xor_tail) = data.split_at(align_len);
@@ -133,9 +134,9 @@ pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str) -> Result<(), B
} }
//strip iMtK thing and get version //strip iMtK thing and get version
let extra_header_len = if &out_data[48..52] == b"iMtK" { let extra_header_len = if &out_data[48..52] == MTK_META_MAGIC {
let imtk_len = u32::from_le_bytes(out_data[52..56].try_into().unwrap()); let imtk_len = u32::from_le_bytes(out_data[52..56].try_into().unwrap());
if &out_data[56..60] != b"iPAd" { if &out_data[56..60] != MTK_META_PAD_MAGIC {
let version_len = u32::from_le_bytes(out_data[56..60].try_into().unwrap()); let version_len = u32::from_le_bytes(out_data[56..60].try_into().unwrap());
let version = common::string_from_bytes(&out_data[60..60 + version_len as usize]); let version = common::string_from_bytes(&out_data[60..60 + version_len as usize]);
println!("- Version: {}", version); println!("- Version: {}", version);
+11 -7
View File
@@ -12,6 +12,8 @@ static KEY: u32 = 0x94102909; //09 29 10 94
static HEADER_XOR_MASK: u32 = 0x04BE7C75; //75 7C BE 04 static HEADER_XOR_MASK: u32 = 0x04BE7C75; //75 7C BE 04
static CONTENT_XOR_MASK: u32 = 0x04BE7C72; //72 7C BE 04 static CONTENT_XOR_MASK: u32 = 0x04BE7C72; //72 7C BE 04
use crate::formats::mtk_pkg::{MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
#[derive(BinRead)] #[derive(BinRead)]
struct Header { struct Header {
#[br(count = 4)] vendor_magic_bytes: Vec<u8>, #[br(count = 4)] vendor_magic_bytes: Vec<u8>,
@@ -53,16 +55,18 @@ impl PartEntry {
} }
} }
static HEADER_SIZE: usize = 0x98;
pub fn is_mtk_pkg_old_file(mut file: &File) -> bool { pub fn is_mtk_pkg_old_file(mut file: &File) -> bool {
let encrypted_header = common::read_file(&file, 0, 152).expect("Failed to read from file."); let encrypted_header = common::read_file(&file, 0, HEADER_SIZE).expect("Failed to read from file.");
let header = decrypt(&encrypted_header, KEY, Some(HEADER_XOR_MASK)); let header = decrypt(&encrypted_header, KEY, Some(HEADER_XOR_MASK));
if &header[4..12] == b"#DH@FiRm" { if &header[4..12] == MTK_HEADER_MAGIC {
true true
} else if &header[68..76] == b"#DH@FiRm" { } else if &header[68..76] == MTK_HEADER_MAGIC {
//check for 64 byte additional header used in some Sony and Philips firmwares and skip it //check for 64 byte additional header used in some Sony and Philips firmwares and skip it
file.seek(std::io::SeekFrom::Start(64)).expect("Failed to seek"); file.seek(std::io::SeekFrom::Start(64)).expect("Failed to seek");
true true
} else if &header[132..140] == b"#DH@FiRm" { } else if &header[132..140] == MTK_HEADER_MAGIC {
//check for 128 byte additional header used in some Philips firmwares and skip it //check for 128 byte additional header used in some Philips firmwares and skip it
file.seek(std::io::SeekFrom::Start(128)).expect("Failed to seek"); file.seek(std::io::SeekFrom::Start(128)).expect("Failed to seek");
true true
@@ -73,7 +77,7 @@ pub fn is_mtk_pkg_old_file(mut file: &File) -> bool {
pub fn extract_mtk_pkg_old(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> { pub fn extract_mtk_pkg_old(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
let file_size = file.metadata()?.len(); let file_size = file.metadata()?.len();
let encrypted_header = common::read_exact(&mut file, 152)?; let encrypted_header = common::read_exact(&mut file, HEADER_SIZE)?;
let header = decrypt(&encrypted_header, KEY, Some(HEADER_XOR_MASK)); let header = decrypt(&encrypted_header, KEY, Some(HEADER_XOR_MASK));
let mut hdr_reader = Cursor::new(header); let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?; let hdr: Header = hdr_reader.read_le()?;
@@ -101,9 +105,9 @@ pub fn extract_mtk_pkg_old(mut file: &File, output_folder: &str) -> Result<(), B
} }
//strip iMtK thing and get version //strip iMtK thing and get version
let extra_header_len = if &out_data[0..4] == b"iMtK" { let extra_header_len = if &out_data[0..4] == MTK_META_MAGIC {
let imtk_len = u32::from_le_bytes(out_data[4..8].try_into().unwrap()); let imtk_len = u32::from_le_bytes(out_data[4..8].try_into().unwrap());
if &out_data[8..12] != b"iPAd" { if &out_data[8..12] != MTK_META_PAD_MAGIC {
let version_len = u32::from_le_bytes(out_data[8..12].try_into().unwrap()); let version_len = u32::from_le_bytes(out_data[8..12].try_into().unwrap());
let version = common::string_from_bytes(&out_data[12..12 + version_len as usize]); let version = common::string_from_bytes(&out_data[12..12 + version_len as usize]);
println!("- Version: {}", version); println!("- Version: {}", version);
+51 -39
View File
@@ -11,6 +11,14 @@ use crate::utils::aes::{decrypt_aes128_cbc_nopad};
use crate::utils::compression::{decompress_gzip}; use crate::utils::compression::{decompress_gzip};
use crate::utils::lzss::{decompress_lzss}; use crate::utils::lzss::{decompress_lzss};
pub struct PanaDvdContext {
matching_key: [u8; 8],
base_hdr_size: u32,
is_aes: bool,
aes_key: Option<[u8; 16]>,
aes_iv: Option<[u8; 16]>
}
#[derive(BinRead)] #[derive(BinRead)]
struct AesHeaderFileEntry { struct AesHeaderFileEntry {
offset: u32, offset: u32,
@@ -104,6 +112,8 @@ impl MainEntryHeader {
} }
} }
static MAX_HEADER_SIZE: usize = 0x2000;
pub fn find_key<'a>(key_array: &'a [&'a str], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result<Option<[u8; 8]>, Box<dyn std::error::Error>> { pub fn find_key<'a>(key_array: &'a [&'a str], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result<Option<[u8; 8]>, Box<dyn std::error::Error>> {
for key_hex in key_array { for key_hex in key_array {
let key_bytes = hex::decode(key_hex)?; let key_bytes = hex::decode(key_hex)?;
@@ -134,75 +144,77 @@ pub fn find_aes_key_pair<'a>(key_array: &'a [(&'a str, &'a str, &'a str)], data:
Ok(None) Ok(None)
} }
pub fn is_pana_dvd_file(file: &File) -> bool { pub fn is_pana_dvd_file(file: &File) -> Result<Option<PanaDvdContext>, Box<dyn std::error::Error>> {
let header = common::read_file(&file, 0, 64).expect("Failed to read from file."); let header = common::read_file(&file, 0, 64)?;
if header.starts_with(b"PANASONIC\x00\x00\x00") { if let Some(matching_key) = find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 0)? {
true Ok(Some(PanaDvdContext {
} else if find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 0).expect("Failed to decrypt header.").is_some() { matching_key: matching_key,
true base_hdr_size: 0,
} else if find_aes_key_pair(&keys::PANA_DVD_AESPAIR, &header, b"PANASONIC", 32).expect("Failed to decrypt header.").is_some() { is_aes: false,
true aes_key: None,
aes_iv: None,
}))
} else if header.starts_with(b"PANASONIC\x00\x00\x00") && let Some(matching_key) = find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 48)? {
Ok(Some(PanaDvdContext {
matching_key: matching_key,
base_hdr_size: 48,
is_aes: false,
aes_key: None,
aes_iv: None,
}))
} else if let Some((aes_key, aes_iv, matching_key)) = find_aes_key_pair(&keys::PANA_DVD_AESPAIR, &header, b"PANASONIC", 32)? {
Ok(Some(PanaDvdContext {
matching_key: matching_key,
base_hdr_size: 48,
is_aes: true,
aes_key: Some(aes_key),
aes_iv: Some(aes_iv),
}))
} else { } else {
false Ok(None)
} }
} }
pub fn extract_pana_dvd(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> { pub fn extract_pana_dvd(mut file: &File, output_folder: &str, context: PanaDvdContext) -> Result<(), Box<dyn std::error::Error>> {
let mut data = Vec::new(); // we need to load the entire file into memory so we can swap it with AES decrypted data if its AES encrypted let mut data = Vec::new(); // we need to load the entire file into memory so we can swap it with AES decrypted data if its AES encrypted
file.read_to_end(&mut data)?; file.read_to_end(&mut data)?;
let mut file_reader = Cursor::new(data); let mut file_reader = Cursor::new(data);
let enc_header_s = common::read_exact(&mut file_reader, 64)?;
let matching_key; let matching_key = context.matching_key;
let mut file_entries: Vec<FileEntry> = Vec::new(); let mut file_entries: Vec<FileEntry> = Vec::new();
// (custom enc) find the key, knowing that the first entry is always "PROG" (offset 0) if context.is_aes {
if let Some(key_array) = find_key(&keys::PANA_DVD_KEYONLY, &enc_header_s, b"PROG", 0)? { let (aes_key, aes_iv) = (context.aes_key.unwrap(), context.aes_iv.unwrap());
println!("Found valid key: {}\n", hex::encode_upper(key_array)); println!("Using key: {} + AES key: {}, IV: {}", hex::encode_upper(matching_key), hex::encode_upper(aes_key), hex::encode_upper(aes_iv));
matching_key = Some(key_array);
file_entries.push(FileEntry { offset: 0, base_offset: 0 })
// (custom enc) find the key, knowing that the first entry is always "PROG" (offset 48)
} else if let Some(key_array) = find_key(&keys::PANA_DVD_KEYONLY, &enc_header_s, b"PROG", 48)? {
println!("Found valid key: {}\n", hex::encode_upper(key_array));
matching_key = Some(key_array);
file_entries.push(FileEntry { offset: 0, base_offset: 48 })
// (aes) find the key using PANASONIC magic
} else if let Some((aes_key, aes_iv, key_array)) = find_aes_key_pair(&keys::PANA_DVD_AESPAIR, &enc_header_s, b"PANASONIC", 32)? {
println!("Found AES key pair: aes={} iv={} cust={}", hex::encode_upper(aes_key), hex::encode_upper(aes_iv), hex::encode_upper(key_array));
matching_key = Some(key_array);
println!("Decrypting AES...\n"); println!("Decrypting AES...\n");
let aes_decrypted = decrypt_aes128_cbc_nopad(&file_reader.get_ref(), &aes_key, &aes_iv)?; let aes_decrypted = decrypt_aes128_cbc_nopad(&file_reader.get_ref(), &aes_key, &aes_iv)?;
file_reader = Cursor::new(aes_decrypted); //set the file reader to use AES decrypted stream file_reader = Cursor::new(aes_decrypted); //set the file reader to use AES decrypted stream
//read file entries in extra header //read file entries in extra header
let file_table = common::read_exact(&mut file_reader, 48)?; let file_table = common::read_exact(&mut file_reader, 48)?;
let mut file_table_reader = Cursor::new(decrypt_data(&file_table, &key_array)); let mut file_table_reader = Cursor::new(decrypt_data(&file_table, &matching_key));
for _i in 0..4 { for _i in 0..4 {
let file_entry: AesHeaderFileEntry = file_table_reader.read_le()?; let file_entry: AesHeaderFileEntry = file_table_reader.read_le()?;
if file_entry.size == 0 && file_entry.offset == 0 { if file_entry.size == 0 && file_entry.offset == 0 {
break break
} }
file_entries.push(FileEntry { offset: file_entry.offset, base_offset: 48 }); file_entries.push(FileEntry { offset: file_entry.offset, base_offset: context.base_hdr_size });
} }
} else { } else {
println!("\nNo valid key found!"); println!("Using key: {}", hex::encode_upper(matching_key));
return Ok(()); file_entries.push(FileEntry { offset: 0, base_offset: context.base_hdr_size });
} }
let matching_key_array = matching_key.unwrap();
if file_entries.len() == 1 { if file_entries.len() == 1 {
//only one file, standard extraction //only one file, standard extraction
println!("File contains no extra sub-files..."); println!("File contains no extra sub-files...\n");
extract_file(&mut file_reader, file_entries[0].offset as u64, file_entries[0].base_offset as u64, matching_key_array, output_folder)?; extract_file(&mut file_reader, file_entries[0].offset as u64, file_entries[0].base_offset as u64, matching_key, output_folder)?;
} else { } else {
println!("File contains {} sub-files...", file_entries.len()); println!("File contains {} sub-files...", file_entries.len());
for (i, file_entry ) in file_entries.iter().enumerate() { for (i, file_entry ) in file_entries.iter().enumerate() {
println!("\nExtracting file {}/{} - Offset: {}, base: {}", i + 1, file_entries.len(), file_entry.offset, file_entry.base_offset); println!("\nExtracting file {}/{} - Offset: {}, base: {}", i + 1, file_entries.len(), file_entry.offset, file_entry.base_offset);
extract_file(&mut file_reader, file_entry.offset as u64, file_entry.base_offset as u64, matching_key_array, &format!("{}/file_{}", output_folder, i + 1))?; extract_file(&mut file_reader, file_entry.offset as u64, file_entry.base_offset as u64, matching_key, &format!("{}/file_{}", output_folder, i + 1))?;
} }
} }
@@ -214,7 +226,7 @@ pub fn extract_pana_dvd(mut file: &File, output_folder: &str) -> Result<(), Box<
fn extract_file(file_reader: &mut Cursor<Vec<u8>>, offset: u64, base_offset: u64, key: [u8; 8], output_folder: &str) -> Result<(), Box<dyn std::error::Error>> { fn extract_file(file_reader: &mut Cursor<Vec<u8>>, offset: u64, base_offset: u64, key: [u8; 8], output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
file_reader.seek(SeekFrom::Start(offset + base_offset))?; file_reader.seek(SeekFrom::Start(offset + base_offset))?;
let enc_header = common::read_exact(file_reader, 8192)?; let enc_header = common::read_exact(file_reader, MAX_HEADER_SIZE)?;
let mut hdr_reader = Cursor::new(decrypt_data(&enc_header, &key)); let mut hdr_reader = Cursor::new(decrypt_data(&enc_header, &key));
let mut modules: Vec<ModuleEntry> = Vec::new(); let mut modules: Vec<ModuleEntry> = Vec::new();
+2 -2
View File
@@ -111,10 +111,10 @@ pub fn extract_sony_bdp(mut file: &File, output_folder: &str) -> Result<(), Box<
if last_file_path.is_some() { if last_file_path.is_some() {
println!("\nChecking if it's also MTK BDP..."); println!("\nChecking if it's also MTK BDP...");
let last_file = File::open(last_file_path.unwrap())?; let last_file = File::open(last_file_path.unwrap())?;
if formats::mtk_bdp::is_mtk_bdp_file(&last_file) { if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&last_file)? {
println!("- MTK BDP file detected!\n"); println!("- MTK BDP file detected!\n");
let mtk_extraction_path = format!("{}/{}", &output_folder, i - 1); let mtk_extraction_path = format!("{}/{}", &output_folder, i - 1);
formats::mtk_bdp::extract_mtk_bdp(&last_file, &mtk_extraction_path)?; formats::mtk_bdp::extract_mtk_bdp(&last_file, &mtk_extraction_path, result)?;
} else { } else {
println!("- Not an MTK BDP file."); println!("- Not an MTK BDP file.");
} }
+8 -9
View File
@@ -21,7 +21,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Input target: {}", target_path); println!("Input target: {}", target_path);
let path = PathBuf::from(target_path); let path = PathBuf::from(target_path);
let output_path = if args.output_folder.is_some() { let output_path = if args.output_folder.is_some() {
args.output_folder.unwrap() args.output_folder.unwrap()
} else { } else {
@@ -120,9 +119,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Amlogic image file detected!"); println!("Amlogic image file detected!");
formats::amlogic::extract_amlogic(&file, &output_path)?; formats::amlogic::extract_amlogic(&file, &output_path)?;
} }
else if formats::pana_dvd::is_pana_dvd_file(&file) { else if let Some(result) = formats::pana_dvd::is_pana_dvd_file(&file)? {
println!("PANA_DVD file detected!"); println!("PANA_DVD file detected!");
formats::pana_dvd::extract_pana_dvd(&file, &output_path)?; formats::pana_dvd::extract_pana_dvd(&file, &output_path, result)?;
} }
else if formats::pup::is_pup_file(&file) { else if formats::pup::is_pup_file(&file) {
println!("PUP file detected!"); println!("PUP file detected!");
@@ -144,21 +143,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Roku file detected!"); println!("Roku file detected!");
formats::roku::extract_roku(&file, &output_path)?; formats::roku::extract_roku(&file, &output_path)?;
} }
else if formats::mtk_pkg::is_mtk_pkg_file(&file) { else if let Some(result) = formats::mtk_pkg::is_mtk_pkg_file(&file)? {
println!("MTK PKG file detected!"); println!("MTK PKG file detected!");
formats::mtk_pkg::extract_mtk_pkg(&file, &output_path)?; formats::mtk_pkg::extract_mtk_pkg(&file, &output_path, result)?;
} }
else if formats::mtk_pkg_old::is_mtk_pkg_old_file(&file) { else if formats::mtk_pkg_old::is_mtk_pkg_old_file(&file) {
println!("MTK PKG (Old) file detected!"); println!("MTK PKG (Old) file detected!");
formats::mtk_pkg_old::extract_mtk_pkg_old(&file, &output_path)?; formats::mtk_pkg_old::extract_mtk_pkg_old(&file, &output_path)?;
} }
else if formats::mtk_pkg_new::is_mtk_pkg_new_file(&file) { else if let Some(result) = formats::mtk_pkg_new::is_mtk_pkg_new_file(&file)? {
println!("MTK PKG (New) file detected!"); println!("MTK PKG (New) file detected!");
formats::mtk_pkg_new::extract_mtk_pkg_new(&file, &output_path)?; formats::mtk_pkg_new::extract_mtk_pkg_new(&file, &output_path, result)?;
} }
else if formats::mtk_bdp::is_mtk_bdp_file(&file) { else if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&file)? {
println!("MTK BDP file detected!"); println!("MTK BDP file detected!");
formats::mtk_bdp::extract_mtk_bdp(&file, &output_path)?; formats::mtk_bdp::extract_mtk_bdp(&file, &output_path, result)?;
} }
else { else {
println!("Input format not recognized!"); println!("Input format not recognized!");