mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
mstar: add support for old secure format, add option to keep unknown dest
This commit is contained in:
@@ -101,8 +101,13 @@ Tip: if you have split ROM (.ROM-00 and .ROM-01), extract both into the same fol
|
||||
**Used in:** Many MStar-based TVs (Hisense, Toshiba...)
|
||||
**Notes:** All files should be supported, includes lzop, lz4, lzma, sparse_write support
|
||||
**Options:**
|
||||
`mstar:keep_unknown` - Save data with unknown destination.
|
||||
※ Support `dump_dec_hdrs` option (will save the script)
|
||||
|
||||
## MStar upgrade bin (Secure, old)
|
||||
**Used in:** Older MStar-based TVs with Secure upgrade mode (encrypted+signed)
|
||||
**Notes:** Only default upgrade key is supported. This use the extractor above after decrypting.
|
||||
|
||||
## MediaTek BDP
|
||||
**Used in:** Many MediaTek-based Blu-Ray players (LG, Samsung, Philips, Panasonic...)
|
||||
**Notes:** Some older files may fail to extract
|
||||
|
||||
@@ -8,6 +8,7 @@ pub struct Format {
|
||||
}
|
||||
|
||||
pub mod mstar;
|
||||
pub mod mstar_secure_old;
|
||||
pub mod samsung_old;
|
||||
pub mod nvt_timg;
|
||||
pub mod pfl_upg;
|
||||
@@ -58,6 +59,11 @@ pub fn get_registry() -> Vec<Format> {
|
||||
detector_func: crate::formats::mstar::is_mstar_file,
|
||||
extractor_func: crate::formats::mstar::extract_mstar,
|
||||
},
|
||||
Format {
|
||||
name: "mstar_secure_old",
|
||||
detector_func: crate::formats::mstar_secure_old::is_mstar_secure_old_file,
|
||||
extractor_func: crate::formats::mstar_secure_old::extract_mstar_secure_old,
|
||||
},
|
||||
Format {
|
||||
name: "samsung_old",
|
||||
detector_func: crate::formats::samsung_old::is_samsung_old_dir,
|
||||
|
||||
+40
-34
@@ -126,45 +126,51 @@ pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
|
||||
|
||||
println!("\nPart - Offset: {}, Size: {} --> {}", offset, size, partname);
|
||||
|
||||
if partname == "unknown" {
|
||||
println!("- Unknown destination, skipping!");
|
||||
} else {
|
||||
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
|
||||
let out_data;
|
||||
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", partname));
|
||||
|
||||
if compression == CompressionType::Lzma {
|
||||
println!("- Decompressing LZMA...");
|
||||
out_data = decompress_lzma(&data)?;
|
||||
} else if compression == CompressionType::DoubleLzma {
|
||||
println!("- Decompressing LZMA (Pass 1)...");
|
||||
let pass_1 = decompress_lzma(&data)?;
|
||||
println!("- Decompressing LZMA (Pass 2)...");
|
||||
out_data = decompress_lzma(&pass_1)?;
|
||||
} else if compression == CompressionType::Lz4 {
|
||||
println!("- Decompressing lz4, expected size: {}", lz4_expect_size);
|
||||
out_data = decompress_lz4(&data, lz4_expect_size.try_into().unwrap())?;
|
||||
} else if compression == CompressionType::Lzo {
|
||||
println!("- Decompessing LZO..");
|
||||
unlzop_to_file(&data, output_path)?;
|
||||
println!("-- Saved file!");
|
||||
i += 1;
|
||||
continue
|
||||
} else if compression == CompressionType::Sparse {
|
||||
println!("- Unsparsing...");
|
||||
unsparse_to_file(&data, output_path)?;
|
||||
println!("-- Saved file!");
|
||||
i += 1;
|
||||
continue
|
||||
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
|
||||
let out_data;
|
||||
let output_path = if partname == "unknown" {
|
||||
if app_ctx.has_option("mstar:keep_unknown") {
|
||||
println!("- Warning, unknown destination - saving to _unknown_{}.bin", offset);
|
||||
Path::new(&app_ctx.output_dir).join(format!("_unknown_{}.bin", offset))
|
||||
} else {
|
||||
out_data = data;
|
||||
println!("- Warning, unknown destination - skipping...");
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
Path::new(&app_ctx.output_dir).join(format!("{}.bin", partname))
|
||||
};
|
||||
|
||||
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
||||
out_file.write_all(&out_data)?;
|
||||
if compression == CompressionType::Lzma {
|
||||
println!("- Decompressing LZMA...");
|
||||
out_data = decompress_lzma(&data)?;
|
||||
} else if compression == CompressionType::DoubleLzma {
|
||||
println!("- Decompressing LZMA (Pass 1)...");
|
||||
let pass_1 = decompress_lzma(&data)?;
|
||||
println!("- Decompressing LZMA (Pass 2)...");
|
||||
out_data = decompress_lzma(&pass_1)?;
|
||||
} else if compression == CompressionType::Lz4 {
|
||||
println!("- Decompressing lz4, expected size: {}", lz4_expect_size);
|
||||
out_data = decompress_lz4(&data, lz4_expect_size.try_into().unwrap())?;
|
||||
} else if compression == CompressionType::Lzo {
|
||||
println!("- Decompessing LZO..");
|
||||
unlzop_to_file(&data, output_path)?;
|
||||
println!("-- Saved file!");
|
||||
i += 1;
|
||||
continue
|
||||
} else if compression == CompressionType::Sparse {
|
||||
println!("- Unsparsing...");
|
||||
unsparse_to_file(&data, output_path)?;
|
||||
println!("-- Saved file!");
|
||||
i += 1;
|
||||
continue
|
||||
} else {
|
||||
out_data = data;
|
||||
}
|
||||
|
||||
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
||||
out_file.write_all(&out_data)?;
|
||||
println!("-- Saved file!");
|
||||
}
|
||||
|
||||
i += 1;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
use binrw::BinRead;
|
||||
|
||||
pub static MSTAR_DEFAULT_UPGRADE_KEY: [u8; 16] = [0x00, 0x07, 0xff, 0x41, 0x54, 0x53, 0x4d, 0x92, 0xfc, 0x55, 0xaa, 0x0f, 0xff, 0x01, 0x10, 0xe0];
|
||||
pub static CHUNK_ID: &[u8; 8] = b"MSTAR...";
|
||||
pub static CHUNK_END: &[u8; 8] = b"...mstar";
|
||||
|
||||
#[derive(BinRead)]
|
||||
pub struct ChunkFileFooter {
|
||||
_chunk_id: [u8; 8],
|
||||
pub segment_size: u32,
|
||||
pub file_data_offset: u32,
|
||||
pub file_data_len: u32,
|
||||
_file_hash_offset: u32,
|
||||
_file_hash_len: u32,
|
||||
_file_signature_offset: u32,
|
||||
_file_signature_len: u32,
|
||||
_reserved: [u8; 84],
|
||||
_chunk_end: [u8; 8],
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
mod include;
|
||||
use std::any::Any;
|
||||
use crate::{AppContext, InputTarget};
|
||||
|
||||
use std::path::Path;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{Cursor, Write};
|
||||
use binrw::BinReaderExt;
|
||||
|
||||
use crate::utils::common;
|
||||
use crate::utils::aes::decrypt_aes128_ecb;
|
||||
use crate::formats::mstar::{extract_mstar, is_mstar_file};
|
||||
use include::*;
|
||||
|
||||
pub struct MstarSecureCtx {
|
||||
dec_footer: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn is_mstar_secure_old_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||
let file_size = file.metadata()?.len();
|
||||
if file_size < 128 {
|
||||
return Ok(None);
|
||||
}
|
||||
let enc_footer = common::read_file(&file, file_size - 128, 128)?;
|
||||
let dec_footer = decrypt_aes128_ecb(&MSTAR_DEFAULT_UPGRADE_KEY, &enc_footer)?;
|
||||
|
||||
if &dec_footer[0..8] == CHUNK_ID && &dec_footer[120..128] == CHUNK_END {
|
||||
Ok(Some(Box::new(MstarSecureCtx {dec_footer})))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_mstar_secure_old(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||
let ctx = ctx.downcast::<MstarSecureCtx>().expect("Missing context");
|
||||
|
||||
let hdr: ChunkFileFooter = Cursor::new(ctx.dec_footer).read_le()?;
|
||||
println!("Info -\nSegment size: {}\nFile data offset: {}\nFile data len: {}", hdr.segment_size, hdr.file_data_offset, hdr.file_data_len);
|
||||
|
||||
let enc_data = common::read_file(&mut file, hdr.file_data_offset as u64, hdr.file_data_len as usize)?;
|
||||
|
||||
println!("Decrypting...");
|
||||
let dec_data = decrypt_aes128_ecb(&MSTAR_DEFAULT_UPGRADE_KEY, &enc_data)?;
|
||||
|
||||
let output_path = Path::new(&app_ctx.output_dir).join("_decrypted.bin");
|
||||
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||
let mut out_file = OpenOptions::new().write(true).create(true).open(&output_path)?;
|
||||
out_file.write_all(&dec_data)?;
|
||||
|
||||
println!("- Saved decrypted file!");
|
||||
|
||||
//run standard mstar ext into same directory
|
||||
let r_out_file = File::open(&output_path)?;
|
||||
let in_ctx: AppContext = AppContext {
|
||||
input: InputTarget::File(r_out_file),
|
||||
output_dir: app_ctx.output_dir.clone(),
|
||||
options: app_ctx.options.clone()
|
||||
};
|
||||
|
||||
//do check just in case and extract
|
||||
if let Some(result) = is_mstar_file(&in_ctx)? {
|
||||
extract_mstar(&in_ctx, result)?;
|
||||
} else {
|
||||
return Err("detection failed on decrypted data".into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+16
-1
@@ -1,6 +1,6 @@
|
||||
use aes::Aes128;
|
||||
use cbc::{Decryptor, cipher::{block_padding::Pkcs7, block_padding::NoPadding, BlockDecryptMut, KeyIvInit}};
|
||||
|
||||
use cbc::{Decryptor, cipher::{block_padding::Pkcs7, block_padding::NoPadding, BlockDecryptMut, KeyIvInit}};
|
||||
type Aes128CbcDec = Decryptor<Aes128>;
|
||||
|
||||
pub fn decrypt_aes128_cbc_pcks7(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
@@ -21,4 +21,19 @@ pub fn decrypt_aes128_cbc_nopad(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8;
|
||||
.map_err(|e| format!("UnpadError: {:?}", e))?;
|
||||
|
||||
Ok(decrypted.to_vec())
|
||||
}
|
||||
|
||||
use ecb::{Decryptor as EcbDecryptor, cipher::{KeyInit, generic_array::GenericArray}};
|
||||
type Aes128EcbDec = EcbDecryptor<Aes128>;
|
||||
|
||||
pub fn decrypt_aes128_ecb(key: &[u8; 16], ciphertext: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let mut buffer = ciphertext.to_vec();
|
||||
|
||||
let mut decryptor = Aes128EcbDec::new(key.into());
|
||||
for chunk in buffer.chunks_exact_mut(16) {
|
||||
let block: &mut [u8; 16] = chunk.try_into()?;
|
||||
decryptor.decrypt_block_mut(GenericArray::from_mut_slice(block));
|
||||
}
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
Reference in New Issue
Block a user