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::io::{Seek, SeekFrom, Read, Write};
use binrw::{BinRead, BinReaderExt};
use std::sync::OnceLock;
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)]
struct PITITPITEntry {
@@ -32,6 +37,8 @@ struct PITHeader {
entry_count: u32,
}
static PIT_MAGIC: [u8; 8] = [0xDC, 0xEA, 0x30, 0x85, 0xDC, 0xEA, 0x30, 0x85];
#[derive(BinRead)]
struct PITEntry {
#[br(count = 16)] name_bytes: Vec<u8>,
@@ -56,36 +63,35 @@ struct BITEntry {
_unknown: u32,
}
static PITIT_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50];
static PITIT_OFFSET: OnceLock<usize> = OnceLock::new();
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 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> {
data.windows(pattern.len()).position(|window| window == pattern)
}
pub fn is_mtk_bdp_file(mut file: &File) -> bool {
let file_size = file.metadata().expect("REASON").len();
pub fn is_mtk_bdp_file(mut file: &File) -> Result<Option<MtkBdpContext>, Box<dyn std::error::Error>> {
let file_size = file.metadata()?.len();
let mut data = Vec::new();
// I wish there was a better way
let start_offset = file_size.saturating_sub(file_size / 20);
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) {
PITIT_OFFSET.set(start_offset as usize + pos).unwrap();
true
Ok(Some(MtkBdpContext {pitit_offset: start_offset + pos as u64}))
} else {
false
Ok(None)
}
}
pub fn extract_mtk_bdp(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
let offset = PITIT_OFFSET.get().unwrap();
pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpContext) -> Result<(), Box<dyn std::error::Error>> {
let offset = context.pitit_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)?;
//{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;
loop {
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 {
//old PITIT does not have BIT entry, because BIT appears directly after PITIT
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))?;
let mut pit_entries: Vec<PITEntry> = Vec::new();
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!");
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))?;
let mut bit_entries: Vec<BITEntry> = Vec::new();
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!");
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;
loop {
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: {}",
bit_i + 1, bit_entry.partition_id, bit_entry.offset, bit_entry.size, bit_entry.offset_in_target_part);
bit_entries.push(bit_entry);
+42 -21
View File
@@ -1,6 +1,6 @@
use std::path::Path;
use std::fs::{self, File, OpenOptions};
use std::io::{Write, Cursor, Seek};
use std::io::{Write, Cursor, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
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::keys;
pub struct MtkPkgContext {
is_philips_variant: bool,
decrypted_header: Vec<u8>,
}
#[derive(BinRead)]
struct Header {
#[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] = [
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];
pub fn is_mtk_pkg_file(file: &File) -> bool {
let mut encrypted_header = common::read_file(&file, 0, 144).expect("Failed to read from file.");
let mut header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV).expect("Decryption error!");
if &header[4..12] == b"#DH@FiRm" {
true
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, HEADER_SIZE)?;
let mut header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
if &header[4..12] == MTK_HEADER_MAGIC {
Ok(Some(MtkPkgContext { is_philips_variant: false, decrypted_header: header}))
} else {
// try for philips which has additional 128 bytes at beginning
encrypted_header = common::read_file(&file, 128, 144).expect("Failed to read from file.");
header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV).expect("Decryption error!");
if &header[4..12] == b"#DH@FiRm" {
true
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)?;
if &header[4..12] == MTK_HEADER_MAGIC {
Ok(Some(MtkPkgContext { is_philips_variant: true, decrypted_header: header }))
} 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 encrypted_header = common::read_exact(&mut file, 144)?;
let header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
let header = context.decrypted_header;
let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?;
println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct 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;
while file.stream_position()? < file_size as u64 {
part_n += 1;
@@ -96,7 +117,7 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box<d
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 {""} );
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 {
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_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)
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);
}
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...");
matching_key = Some(key);
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 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);
matching_key = Some(key_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
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());
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 = common::string_from_bytes(&out_data[60..60 + version_len as usize]);
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"});
fs::create_dir_all(&output_folder)?;
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() {
let lzhs_out_path = Path::new(&output_folder).join(part_entry.name() + ".bin");
+38 -37
View File
@@ -1,6 +1,6 @@
use std::path::Path;
use std::fs::{self, File, OpenOptions};
use std::io::{Write, Cursor, Seek};
use std::io::{Write, Cursor, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
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::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)]
struct Header {
#[br(count = 4)] vendor_magic_bytes: Vec<u8>,
@@ -53,52 +62,45 @@ impl PartEntry {
}
}
pub fn is_mtk_pkg_new_file(file: &File) -> bool {
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.");
static HEADER_SIZE: usize = 0x170;
if &try_decrypt[4..12] == b"#DH@FiRm" {
return true;
}
}
false
}
pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
let file_size = file.metadata()?.len();
let encrypted_header = common::read_exact(&mut file, 368)?;
let mut header = Vec::new();
let mut matching_key: Option<[u8; 16]> = None;
let mut matching_iv: Option<[u8; 16]> = None;
//find key, the header and data key will be the same
pub fn is_mtk_pkg_new_file(file: &File) -> Result<Option<MtkPkgNewContext>, Box<dyn std::error::Error>> {
let encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?;
for (key_hex, iv_hex, name) in keys::MTK_PKG_CUST {
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)?;
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 &header[4..12] == b"#DH@FiRm" {
println!("Using key {}", name);
matching_key = Some(key_array);
matching_iv = Some(iv_array);
break
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
}));
}
}
if matching_key.is_none() && matching_iv.is_none() {
println!("Failed to find key!");
return Ok(())
Ok(None)
}
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();
//the key was founf, and header was decrypted at detection stage so we can reuse
println!("Using key {}", context.matching_key_name);
let key_array = context.matching_key_key;
let iv_array = context.matching_key_iv;
let header = context.decrypted_header;
let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?;
println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct 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;
while file.stream_position()? < file_size as u64 {
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;
if part_entry.is_encrypted() {
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
let align_len = data.len() & !15;
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
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());
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 = common::string_from_bytes(&out_data[60..60 + version_len as usize]);
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 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)]
struct Header {
#[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 {
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));
if &header[4..12] == b"#DH@FiRm" {
if &header[4..12] == MTK_HEADER_MAGIC {
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
file.seek(std::io::SeekFrom::Start(64)).expect("Failed to seek");
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
file.seek(std::io::SeekFrom::Start(128)).expect("Failed to seek");
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>> {
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 mut hdr_reader = Cursor::new(header);
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
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());
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 = common::string_from_bytes(&out_data[12..12 + version_len as usize]);
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::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)]
struct AesHeaderFileEntry {
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>> {
for key_hex in key_array {
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)
}
pub fn is_pana_dvd_file(file: &File) -> bool {
let header = common::read_file(&file, 0, 64).expect("Failed to read from file.");
if header.starts_with(b"PANASONIC\x00\x00\x00") {
true
} else if find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 0).expect("Failed to decrypt header.").is_some() {
true
} else if find_aes_key_pair(&keys::PANA_DVD_AESPAIR, &header, b"PANASONIC", 32).expect("Failed to decrypt header.").is_some() {
true
pub fn is_pana_dvd_file(file: &File) -> Result<Option<PanaDvdContext>, Box<dyn std::error::Error>> {
let header = common::read_file(&file, 0, 64)?;
if let Some(matching_key) = find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 0)? {
Ok(Some(PanaDvdContext {
matching_key: matching_key,
base_hdr_size: 0,
is_aes: false,
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 {
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
file.read_to_end(&mut 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();
// (custom enc) find the key, knowing that the first entry is always "PROG" (offset 0)
if let Some(key_array) = find_key(&keys::PANA_DVD_KEYONLY, &enc_header_s, b"PROG", 0)? {
println!("Found valid key: {}\n", hex::encode_upper(key_array));
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);
if context.is_aes {
let (aes_key, aes_iv) = (context.aes_key.unwrap(), context.aes_iv.unwrap());
println!("Using key: {} + AES key: {}, IV: {}", hex::encode_upper(matching_key), hex::encode_upper(aes_key), hex::encode_upper(aes_iv));
println!("Decrypting AES...\n");
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
//read file entries in extra header
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 {
let file_entry: AesHeaderFileEntry = file_table_reader.read_le()?;
if file_entry.size == 0 && file_entry.offset == 0 {
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 {
println!("\nNo valid key found!");
return Ok(());
println!("Using key: {}", hex::encode_upper(matching_key));
file_entries.push(FileEntry { offset: 0, base_offset: context.base_hdr_size });
}
let matching_key_array = matching_key.unwrap();
if file_entries.len() == 1 {
//only one file, standard extraction
println!("File contains no extra sub-files...");
extract_file(&mut file_reader, file_entries[0].offset as u64, file_entries[0].base_offset as u64, matching_key_array, output_folder)?;
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, output_folder)?;
} else {
println!("File contains {} sub-files...", file_entries.len());
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);
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>> {
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 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() {
println!("\nChecking if it's also MTK BDP...");
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");
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 {
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);
let path = PathBuf::from(target_path);
let output_path = if args.output_folder.is_some() {
args.output_folder.unwrap()
} else {
@@ -120,9 +119,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Amlogic image file detected!");
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!");
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) {
println!("PUP file detected!");
@@ -144,21 +143,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Roku file detected!");
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!");
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) {
println!("MTK PKG (Old) file detected!");
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!");
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!");
formats::mtk_bdp::extract_mtk_bdp(&file, &output_path)?;
formats::mtk_bdp::extract_mtk_bdp(&file, &output_path, result)?;
}
else {
println!("Input format not recognized!");