+ cleanup

This commit is contained in:
Pari Malam
2026-06-05 21:40:54 +08:00
parent c1f5b7961d
commit 704e27059d
4 changed files with 66 additions and 54 deletions
+4 -5
View File
@@ -9,6 +9,7 @@ use log::info;
pub struct EpkContext { pub struct EpkContext {
epk_version: u8, epk_version: u8,
versions: Vec<u8>,
} }
pub fn is_epk_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> { pub fn is_epk_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
@@ -16,7 +17,7 @@ pub fn is_epk_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn
let versions = common::read_file(&file, 1712, 36)?; let versions = common::read_file(&file, 1712, 36)?;
if let Some(epk_version) = check_epk_version(&versions) { if let Some(epk_version) = check_epk_version(&versions) {
Ok(Some(Box::new(EpkContext {epk_version}))) Ok(Some(Box::new(EpkContext {epk_version, versions})))
} else { } else {
Ok(None) Ok(None)
} }
@@ -58,10 +59,8 @@ pub fn extract_epk(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
let mut file = app_ctx.file().ok_or("Extractor expected file")?; let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let ctx = ctx.downcast::<EpkContext>().map_err(|_| "Invalid context type")?; let ctx = ctx.downcast::<EpkContext>().map_err(|_| "Invalid context type")?;
let versions = common::read_file(&file, 1712, 36)?; let platform_version = common::string_from_bytes(&ctx.versions[4..20]);
let sdk_version = common::string_from_bytes(&ctx.versions[20..36]);
let platform_version = common::string_from_bytes(&versions[4..20]);
let sdk_version = common::string_from_bytes(&versions[20..36]);
info!("Platform version: {}\nSDK version: {}", platform_version, sdk_version); info!("Platform version: {}\nSDK version: {}", platform_version, sdk_version);
file.seek(std::io::SeekFrom::Start(0))?; file.seek(std::io::SeekFrom::Start(0))?;
+26 -33
View File
@@ -6,47 +6,40 @@ pub mod msd_ouith_parser_tizen_1_9;
// COMMON MSD FUNCTIONS // COMMON MSD FUNCTIONS
use crate::utils::aes::decrypt_aes128_cbc_pcks7; use crate::utils::aes::decrypt_aes128_cbc_pcks7;
/// Decrypt "Salted__" format data using OpenSSL-compatible key derivation pub fn decrypt_aes_salted(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>, tizen: bool) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
/// (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<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
if encrypted_data.len() < 16 || encrypted_data[0..8].to_vec() != b"Salted__" { if encrypted_data.len() < 16 || encrypted_data[0..8].to_vec() != b"Salted__" {
return Err("Invalid encrypted data!".into()); return Err("Invalid encrypted data!".into());
} }
let salt = &encrypted_data[8..16]; let salt = &encrypted_data[8..16];
// key = MD5(passphrase + salt) if tizen {
let mut key_input = Vec::new(); let iv = md5::compute(&salt);
key_input.extend_from_slice(&passphrase_bytes); let key: [u8; 16] = passphrase_bytes.as_slice().try_into()
key_input.extend_from_slice(&salt); .map_err(|_| "Passphrase must be 16 bytes for AES-128")?;
let key_md5 = md5::compute(&key_input); let iv: [u8; 16] = iv.0;
decrypt_aes128_cbc_pcks7(&encrypted_data[16..], &key, &iv)
// iv = MD5(key + passphrase + salt) } else {
let mut iv_input = Vec::new(); let mut key_input = Vec::new();
iv_input.extend_from_slice(&key_md5.0); key_input.extend_from_slice(&passphrase_bytes);
iv_input.extend_from_slice(&passphrase_bytes); key_input.extend_from_slice(&salt);
iv_input.extend_from_slice(&salt); let key_md5 = md5::compute(&key_input);
let iv_md5 = md5::compute(&iv_input); let mut iv_input = Vec::new();
iv_input.extend_from_slice(&key_md5.0);
let key: [u8; 16] = key_md5.0; iv_input.extend_from_slice(&passphrase_bytes);
let iv: [u8; 16] = iv_md5.0; iv_input.extend_from_slice(&salt);
decrypt_aes128_cbc_pcks7(&encrypted_data[16..], &key, &iv) 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<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
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<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> { pub fn decrypt_aes_salted_tizen(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
if encrypted_data.len() < 16 || encrypted_data[0..8].to_vec() != b"Salted__" { decrypt_aes_salted(encrypted_data, passphrase_bytes, true)
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 data using Tizen-style AES-128-CBC with passphrase as key and /// Decrypt data using Tizen-style AES-128-CBC with passphrase as key and
+34 -14
View File
@@ -1,14 +1,16 @@
use std::any::Any; use std::any::Any;
use crate::AppContext;
use std::path::Path;
use std::fs::{self, OpenOptions}; use std::fs::{self, OpenOptions};
use std::io::Write; 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::aes::decrypt_aes_ecb_auto;
use crate::utils::common;
use crate::keys; 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] = [ const NVT_PARTITIONS: [(&str, u32, u32); 16] = [
("spi_boot", 0x0000020, 0x00020000), ("spi_boot", 0x0000020, 0x00020000),
@@ -29,15 +31,23 @@ const NVT_PARTITIONS: [(&str, u32, u32); 16] = [
("nand_xboot", 0x0000000, 0x0000000), ("nand_xboot", 0x0000000, 0x0000000),
]; ];
const SHREDDER_MAGIC: &[u8] = b"bsl";
pub fn is_novatek_raw_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> { pub fn is_novatek_raw_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 = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
let file_size = file.metadata()?.len(); let file_size = file.metadata()?.len();
if file_size < 0x10000 { if file_size < SHREDDER_MAGIC.len() as u64 {
return Ok(None); 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") { if app_ctx.has_option("nvt_raw:key") {
return Ok(Some(Box::new(()))); return Ok(Some(Box::new(())));
} }
@@ -46,29 +56,35 @@ pub fn is_novatek_raw_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>,
} }
pub fn extract_novatek_raw(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> { pub fn extract_novatek_raw(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 file = app_ctx.file().ok_or("Extractor expected file")?;
let key_hex = app_ctx.options.iter() let key_hex = app_ctx.options.iter()
.find(|o| o.starts_with("nvt_raw:key=")) .find(|o| o.starts_with("nvt_raw:key="))
.map(|o| o.strip_prefix("nvt_raw:key=").unwrap()) .map(|o| o.strip_prefix("nvt_raw:key=").unwrap());
.or(keys::NOVATEK_RAW.first().map(|(_, k)| *k));
let key_hex = match key_hex { let key_hex = match key_hex {
Some(k) => k, Some(k) => k,
None => return Err("nvt_raw:key=<hex_key> option required for decryption".into()), None => keys::NOVATEK_RAW.first().map(|(_, k)| *k).ok_or("nvt_raw:key=<hex_key> option required for decryption")?,
}; };
let key = hex::decode(key_hex).map_err(|e| format!("Invalid hex key: {}", e))?; let key = hex::decode(key_hex).map_err(|e| format!("Invalid hex key: {}", e))?;
let file_size = file.metadata()?.len() as usize; 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!("- File size: {} bytes", file_size);
info!("- Decrypting with AES-{}...", if key.len() == 16 { "128" } else { "256" }); info!("- Decrypting with AES-{}...", if key.len() == 16 { "128" } else { "256" });
let dec_data = decrypt_aes_ecb_auto(&key, &enc_data)?; let dec_data = match decrypt_aes_ecb_auto(&key, &enc_data) {
Ok(d) => {
info!("- Decryption complete"); info!("- Decryption complete");
d
}
Err(_) => {
warn!("- AES-ECB failed, trying XOR...");
xor_decrypt(&key, &enc_data)
}
};
fs::create_dir_all(&app_ctx.output_dir)?; fs::create_dir_all(&app_ctx.output_dir)?;
@@ -90,3 +106,7 @@ pub fn extract_novatek_raw(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
Ok(()) Ok(())
} }
fn xor_decrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
data.iter().enumerate().map(|(i, b)| b ^ key[i % key.len()]).collect()
}
+2 -2
View File
@@ -171,10 +171,10 @@ pub static EPK: &[(&str, &str)] = &[
//Vestel keys //Vestel keys
pub static VESTEL: &[(&str, &str)] = &[ pub static VESTEL: &[(&str, &str)] = &[
("VESTEL-MB180/MB130/MB181/MB230/MB211/MB281", "12345678123456781234567812345678"), ("VESTEL-MB180/MB130/MB181/MB230/MB281", "12345678123456781234567812345678"),
]; ];
//Novatek/MStar custom keys //Novatek/MStar custom keys
pub static NOVATEK_RAW: &[(&str, &str)] = &[ pub static NOVATEK_RAW: &[(&str, &str)] = &[
("MB211", "A9A05FC6F986254382DF2C93DEDF7B98"), ("MB211_shredder", "A9A05FC6F986254382DF2C93DEDF7B98"),
]; ];