diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 34291a1..0000000 --- a/TODO.md +++ /dev/null @@ -1,14 +0,0 @@ -# Formats -- Mstar upgrade bin - 80% complete - need to handle securewrite and LZO compression -- Samsung Old fw dir - Complete -- TPV(Philips) 2023/TitanOS TIMG - Complete -- PFL UPG(Philips) - Complete -- LG Epk1 - 66% complete, epk1(new) not supported, couldnt find one -- Novatek (NFWB) - Complete -- Samsung MSD10(Legacy and Tizen 2015) - Complete -- Samsung MSD11(Tizen 2016+) - Complete -- Panasonic SDDL.SEC(2011+) - Complete -- LG Epk2 (Crypted and plain header)- 100% complete (unless unencrypted versions exist >) -- LG epk3 - Probably fine??? but needs more testing (and more stolen keys) - -Todo: use STRUCTS on everything and move them to seperate files probably \ No newline at end of file diff --git a/src/formats.rs b/src/formats.rs index 1496cc2..331ed6e 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -12,4 +12,7 @@ pub mod msd11; pub mod epk; pub mod epk1; pub mod epk2; -pub mod epk3; \ No newline at end of file +pub mod epk3; + +pub mod mtk_pkg; +pub mod mtk_upgrade_loader; \ No newline at end of file diff --git a/src/formats/mtk_pkg.rs b/src/formats/mtk_pkg.rs new file mode 100644 index 0000000..3b3af28 --- /dev/null +++ b/src/formats/mtk_pkg.rs @@ -0,0 +1,157 @@ +use std::path::Path; +use std::fs::{self, File, OpenOptions}; +use std::io::{Write, Cursor, Seek}; + +use aes::Aes128; +use cbc::{Decryptor, cipher::{block_padding::NoPadding, BlockDecryptMut, KeyIvInit}}; +use binrw::{BinRead, BinReaderExt}; + +use crate::common; + +#[derive(BinRead)] +struct Header { + #[br(count = 4)] vendor_magic_bytes: Vec, + #[br(count = 8)] _mtk_magic: Vec, //#DH@FiRm + #[br(count = 60)] version_bytes: Vec, + file_size: u32, + _platform: u32, + #[br(count = 32)] product_name_bytes: Vec, + #[br(count = 32)] _unknown: Vec, +} +impl Header { + fn vendor_magic(&self) -> String { + common::string_from_bytes(&self.vendor_magic_bytes) + } + fn version(&self) -> String { + common::string_from_bytes(&self.version_bytes) + } + fn product_name(&self) -> String { + common::string_from_bytes(&self.product_name_bytes) + } +} + +#[derive(BinRead)] +struct PartEntry { + #[br(count = 4)] name_bytes: Vec, + flags: u32, + size: u32, +} +impl PartEntry { + fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } +} + +static HEADER_KEY: [u8; 16] = [ + 0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94, + 0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94, +]; + +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_aes_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV).expect("Decryption error!"); + if &header[4..12] == b"#DH@FiRm" { + true + } 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_aes_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV).expect("Decryption error!"); + if &header[4..12] == b"#DH@FiRm" { + true + } else { + false + + } + } +} + +type Aes128CbcDec = Decryptor; +fn decrypt_aes_nopad(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result, Box> { + let mut data = encrypted_data.to_vec(); + let decryptor = Aes128CbcDec::new(key.into(), iv.into()); + + let decrypted = decryptor + .decrypt_padded_mut::(&mut data) + .map_err(|e| format!("UnpadError: {:?}", e))?; + + Ok(decrypted.to_vec()) +} + +pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box> { + let encrypted_header = common::read_exact(&mut file, 144)?; + let header = decrypt_aes_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?; + let mut hdr_reader = Cursor::new(header); + let hdr: Header = hdr_reader.read_le()?; + + println!("\nFile info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" , + hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name()); + + let mut part_n = 0; + while file.stream_position()? < hdr.file_size as u64 { + part_n += 1; + let part_entry: PartEntry = file.read_le()?; + let is_encrypted = if (part_entry.flags & 1 << 0) == 1 << 0 {true} else {false}; + + println!("\nPart {} - {}{}, Size: {}", part_n, part_entry.name(), if is_encrypted {" (Encrypted)"} else {""} ,part_entry.size); + + let data = common::read_exact(&mut file, part_entry.size as usize + 48)?; + + let out_data; + if is_encrypted { + // try decrypting with vendor magic repeated 4 times (works for sony and hisense) + let mut key = [0u8; 16]; + for i in 0..4 { + key[i * 4..(i + 1) * 4].copy_from_slice(&hdr.vendor_magic_bytes); + } + + let crypted_header = &data[..48]; + let try_decrypt = decrypt_aes_nopad(&crypted_header, &key, &HEADER_IV)?; + + if try_decrypt.starts_with(b"reserved mtk inc") { + println!("- Decrypting with 4xVendor magic..."); + out_data = decrypt_aes_nopad(&data[..data.len() & !15], &key, &HEADER_IV)?; + } else { + println!("- Failed to decrypt data!"); + continue + }; + } else { + out_data = data; + } + + //strip iMtK thing + let extra_header_len = if &out_data[48..52] == b"iMtK" { + let imtk_len = u32::from_le_bytes(out_data[56..60].try_into().unwrap()); + let imtk_content = common::string_from_bytes(&out_data[60..60 + imtk_len as usize]); + println!("iMtK Info: {}", imtk_content); + + if &out_data[60 + imtk_len as usize..(60 + imtk_len as usize) + 4] == b"iPAd" { + //println!("iPAd found!"); + 64 + } else { + imtk_len + 12 + } + } else { + 0 + }; + + println!("Extra header size: {}", extra_header_len); + + let output_path = Path::new(&output_folder).join(part_entry.name() + ".bin"); + + fs::create_dir_all(&output_folder)?; + let mut out_file = OpenOptions::new() + .write(true) + .create(true) + .open(output_path)?; + + out_file.write_all(&out_data[48 + extra_header_len as usize..])?; + + println!("-- Saved file!"); + } + + println!("\nExtraction finished!"); + + Ok(()) +} \ No newline at end of file diff --git a/src/formats/mtk_upgrade_loader.rs b/src/formats/mtk_upgrade_loader.rs new file mode 100644 index 0000000..286f3c3 --- /dev/null +++ b/src/formats/mtk_upgrade_loader.rs @@ -0,0 +1,78 @@ +use std::path::Path; +use std::fs::{self, File, OpenOptions}; +use std::io::{Write, Seek}; +use binrw::{BinRead, BinReaderExt}; + +use crate::common; + +pub fn is_mtk_upgrade_loader_file(file: &File) -> bool { + let header = common::read_file(&file, 152, 4).expect("Failed to read from file."); + if header == b"cfig" { //cfig is always(?) the first partition in upgrade_loader + true + } else { + false + } +} + +//This format is similar to mtk_pkg, but has different header size and key. It also doesnt have the crypted headers + +#[derive(BinRead)] +struct PartEntry { + #[br(count = 4)] name_bytes: Vec, + flags: u32, + size: u32, +} +impl PartEntry { + fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } +} + +pub fn extract_mtk_upgrade_loader(mut file: &File, output_folder: &str) -> Result<(), Box> { + let file_size = file.metadata()?.len(); + + let mut part_n = 0; + while file.stream_position()? < file_size as u64 { + part_n += 1; + let part_entry: PartEntry = file.read_le()?; + let is_encrypted = if (part_entry.flags & 1 << 0) == 1 << 0 {true} else {false}; + + println!("\nPart {} - {}{}, Size: {}", part_n, part_entry.name(), if is_encrypted {" (Encrypted)"} else {""} ,part_entry.size); + + let data = common::read_exact(&mut file, part_entry.size as usize)?; + + //strip iMtK thing + let extra_header_len = if &data[0..4] == b"iMtK" { + let imtk_len = u32::from_le_bytes(data[8..12].try_into().unwrap()); + let imtk_content = common::string_from_bytes(&data[12..12 + imtk_len as usize]); + println!("iMtK Info: {}", imtk_content); + + if &data[12 + imtk_len as usize..(12 + imtk_len as usize) + 4] == b"iPAd" { + //println!("iPAd found!"); + 64 + } else { + imtk_len + 12 + } + } else { + 0 + }; + + println!("Extra header size: {}", extra_header_len); + + let output_path = Path::new(&output_folder).join(part_entry.name() + ".bin"); + + fs::create_dir_all(&output_folder)?; + let mut out_file = OpenOptions::new() + .write(true) + .create(true) + .open(output_path)?; + + out_file.write_all(&data[extra_header_len as usize..])?; + + println!("-- Saved file!"); + } + + println!("\nExtraction finished!"); + + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 6e713bb..8e4abc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,6 +90,14 @@ fn main() -> Result<(), Box> { println!("Mstar upgrade file detected!"); formats::mstar::extract_mstar(&file, &output_path)?; } + else if formats::mtk_pkg::is_mtk_pkg_file(&file) { + println!("MTK Pkg file detected!"); + formats::mtk_pkg::extract_mtk_pkg(&file, &output_path)?; + } + else if formats::mtk_upgrade_loader::is_mtk_upgrade_loader_file(&file) { + println!("MTK upgrade_loader file detected!"); + formats::mtk_upgrade_loader::extract_mtk_upgrade_loader(&file, &output_path)?; + } else { println!("Input format not recognized!"); }