mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
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:
+22
-16
@@ -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
@@ -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");
|
||||
|
||||
+36
-35
@@ -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,45 +62,36 @@ 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;
|
||||
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: [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 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
|
||||
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)?;
|
||||
|
||||
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(())
|
||||
}
|
||||
//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()?;
|
||||
@@ -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: {}" ,
|
||||
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);
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
|
||||
@@ -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
@@ -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!");
|
||||
|
||||
Reference in New Issue
Block a user