From 704e27059dd576c5fb9b0d6e8711921012f72866 Mon Sep 17 00:00:00 2001 From: Pari Malam Date: Fri, 5 Jun 2026 21:40:54 +0800 Subject: [PATCH] + cleanup --- src/formats/epk/mod.rs | 9 +++--- src/formats/msd/mod.rs | 59 +++++++++++++++------------------- src/formats/novatek_raw/mod.rs | 48 +++++++++++++++++++-------- src/keys.rs | 4 +-- 4 files changed, 66 insertions(+), 54 deletions(-) diff --git a/src/formats/epk/mod.rs b/src/formats/epk/mod.rs index ef58a43..ff174bb 100644 --- a/src/formats/epk/mod.rs +++ b/src/formats/epk/mod.rs @@ -9,6 +9,7 @@ use log::info; pub struct EpkContext { epk_version: u8, + versions: Vec, } pub fn is_epk_file(app_ctx: &AppContext) -> Result>, Box> { @@ -16,7 +17,7 @@ pub fn is_epk_file(app_ctx: &AppContext) -> Result>, Box) -> Result<(), Box().map_err(|_| "Invalid context type")?; - let versions = common::read_file(&file, 1712, 36)?; - - let platform_version = common::string_from_bytes(&versions[4..20]); - let sdk_version = common::string_from_bytes(&versions[20..36]); + let platform_version = common::string_from_bytes(&ctx.versions[4..20]); + let sdk_version = common::string_from_bytes(&ctx.versions[20..36]); info!("Platform version: {}\nSDK version: {}", platform_version, sdk_version); file.seek(std::io::SeekFrom::Start(0))?; diff --git a/src/formats/msd/mod.rs b/src/formats/msd/mod.rs index 855b553..f6161df 100644 --- a/src/formats/msd/mod.rs +++ b/src/formats/msd/mod.rs @@ -6,47 +6,40 @@ pub mod msd_ouith_parser_tizen_1_9; // COMMON MSD FUNCTIONS use crate::utils::aes::decrypt_aes128_cbc_pcks7; -/// Decrypt "Salted__" format data using OpenSSL-compatible key derivation -/// (MD5 of passphrase+salt for key, MD5 of key+passphrase+salt for IV). -pub fn decrypt_aes_salted_old(encrypted_data: &[u8], passphrase_bytes: &Vec) -> Result, Box> { +pub fn decrypt_aes_salted(encrypted_data: &[u8], passphrase_bytes: &Vec, tizen: bool) -> Result, Box> { if encrypted_data.len() < 16 || encrypted_data[0..8].to_vec() != b"Salted__" { return Err("Invalid encrypted data!".into()); } let salt = &encrypted_data[8..16]; - // key = MD5(passphrase + salt) - let mut key_input = Vec::new(); - key_input.extend_from_slice(&passphrase_bytes); - key_input.extend_from_slice(&salt); - let key_md5 = md5::compute(&key_input); - - // iv = MD5(key + passphrase + salt) - let mut iv_input = Vec::new(); - iv_input.extend_from_slice(&key_md5.0); - iv_input.extend_from_slice(&passphrase_bytes); - iv_input.extend_from_slice(&salt); - let iv_md5 = md5::compute(&iv_input); - - let key: [u8; 16] = key_md5.0; - let iv: [u8; 16] = iv_md5.0; - decrypt_aes128_cbc_pcks7(&encrypted_data[16..], &key, &iv) + if tizen { + let iv = md5::compute(&salt); + let key: [u8; 16] = passphrase_bytes.as_slice().try_into() + .map_err(|_| "Passphrase must be 16 bytes for AES-128")?; + let iv: [u8; 16] = iv.0; + decrypt_aes128_cbc_pcks7(&encrypted_data[16..], &key, &iv) + } else { + let mut key_input = Vec::new(); + key_input.extend_from_slice(&passphrase_bytes); + key_input.extend_from_slice(&salt); + let key_md5 = md5::compute(&key_input); + let mut iv_input = Vec::new(); + iv_input.extend_from_slice(&key_md5.0); + iv_input.extend_from_slice(&passphrase_bytes); + iv_input.extend_from_slice(&salt); + let iv_md5 = md5::compute(&iv_input); + let key: [u8; 16] = key_md5.0; + let iv: [u8; 16] = iv_md5.0; + decrypt_aes128_cbc_pcks7(&encrypted_data[16..], &key, &iv) + } +} + +pub fn decrypt_aes_salted_old(encrypted_data: &[u8], passphrase_bytes: &Vec) -> Result, Box> { + decrypt_aes_salted(encrypted_data, passphrase_bytes, false) } -/// Decrypt "Salted__" format data using Tizen-style key derivation -/// (passphrase used directly as key, MD5 of salt for IV). pub fn decrypt_aes_salted_tizen(encrypted_data: &[u8], passphrase_bytes: &Vec) -> Result, Box> { - if encrypted_data.len() < 16 || encrypted_data[0..8].to_vec() != b"Salted__" { - return Err("Invalid encrypted data!".into()); - } - let salt = &encrypted_data[8..16]; - - // iv = MD5(salt) - let iv = md5::compute(&salt); - let key: [u8; 16] = passphrase_bytes.as_slice().try_into() - .map_err(|_| "Passphrase must be 16 bytes for AES-128")?; - let iv: [u8; 16] = iv.0; - - decrypt_aes128_cbc_pcks7(&encrypted_data[16..], &key, &iv) + decrypt_aes_salted(encrypted_data, passphrase_bytes, true) } /// Decrypt data using Tizen-style AES-128-CBC with passphrase as key and diff --git a/src/formats/novatek_raw/mod.rs b/src/formats/novatek_raw/mod.rs index 70f56fb..9b1ab7e 100644 --- a/src/formats/novatek_raw/mod.rs +++ b/src/formats/novatek_raw/mod.rs @@ -1,14 +1,16 @@ use std::any::Any; -use crate::AppContext; - -use std::path::Path; use std::fs::{self, OpenOptions}; use std::io::Write; +use std::path::Path; -use crate::utils::common; +use crate::AppContext; use crate::utils::aes::decrypt_aes_ecb_auto; +use crate::utils::common; use crate::keys; -use log::info; +use log::{info, warn}; + +// shredder format detectordecryptor +// Detects files starting with bsl magic and attempts XOR/AES-ECB decryption const NVT_PARTITIONS: [(&str, u32, u32); 16] = [ ("spi_boot", 0x0000020, 0x00020000), @@ -29,15 +31,23 @@ const NVT_PARTITIONS: [(&str, u32, u32); 16] = [ ("nand_xboot", 0x0000000, 0x0000000), ]; +const SHREDDER_MAGIC: &[u8] = b"bsl"; + pub fn is_novatek_raw_file(app_ctx: &AppContext) -> Result>, Box> { let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; let file_size = file.metadata()?.len(); - if file_size < 0x10000 { + if file_size < SHREDDER_MAGIC.len() as u64 { return Ok(None); } + let magic = common::read_file(&file, 0, SHREDDER_MAGIC.len())?; + if magic == SHREDDER_MAGIC { + info!("- Detected shredder format"); + return Ok(Some(Box::new(()))); + } + if app_ctx.has_option("nvt_raw:key") { return Ok(Some(Box::new(()))); } @@ -46,29 +56,35 @@ pub fn is_novatek_raw_file(app_ctx: &AppContext) -> Result>, } pub fn extract_novatek_raw(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { - let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let file = app_ctx.file().ok_or("Extractor expected file")?; let key_hex = app_ctx.options.iter() .find(|o| o.starts_with("nvt_raw:key=")) - .map(|o| o.strip_prefix("nvt_raw:key=").unwrap()) - .or(keys::NOVATEK_RAW.first().map(|(_, k)| *k)); + .map(|o| o.strip_prefix("nvt_raw:key=").unwrap()); let key_hex = match key_hex { Some(k) => k, - None => return Err("nvt_raw:key= option required for decryption".into()), + None => keys::NOVATEK_RAW.first().map(|(_, k)| *k).ok_or("nvt_raw:key= option required for decryption")?, }; let key = hex::decode(key_hex).map_err(|e| format!("Invalid hex key: {}", e))?; let file_size = file.metadata()?.len() as usize; - let enc_data = common::read_file(&mut file, 0, file_size)?; + let enc_data = common::read_file(&file, 0, file_size)?; info!("- File size: {} bytes", file_size); info!("- Decrypting with AES-{}...", if key.len() == 16 { "128" } else { "256" }); - let dec_data = decrypt_aes_ecb_auto(&key, &enc_data)?; - - info!("- Decryption complete"); + let dec_data = match decrypt_aes_ecb_auto(&key, &enc_data) { + Ok(d) => { + info!("- Decryption complete"); + d + } + Err(_) => { + warn!("- AES-ECB failed, trying XOR..."); + xor_decrypt(&key, &enc_data) + } + }; fs::create_dir_all(&app_ctx.output_dir)?; @@ -89,4 +105,8 @@ pub fn extract_novatek_raw(app_ctx: &AppContext, _ctx: Box) -> Result<( } Ok(()) +} + +fn xor_decrypt(key: &[u8], data: &[u8]) -> Vec { + data.iter().enumerate().map(|(i, b)| b ^ key[i % key.len()]).collect() } \ No newline at end of file diff --git a/src/keys.rs b/src/keys.rs index 5b23d40..43d4e16 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -171,10 +171,10 @@ pub static EPK: &[(&str, &str)] = &[ //Vestel keys pub static VESTEL: &[(&str, &str)] = &[ - ("VESTEL-MB180/MB130/MB181/MB230/MB211/MB281", "12345678123456781234567812345678"), + ("VESTEL-MB180/MB130/MB181/MB230/MB281", "12345678123456781234567812345678"), ]; //Novatek/MStar custom keys pub static NOVATEK_RAW: &[(&str, &str)] = &[ - ("MB211", "A9A05FC6F986254382DF2C93DEDF7B98"), + ("MB211_shredder", "A9A05FC6F986254382DF2C93DEDF7B98"), ]; \ No newline at end of file