+ 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 {
epk_version: u8,
versions: Vec<u8>,
}
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)?;
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 {
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 ctx = ctx.downcast::<EpkContext>().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))?;
+26 -33
View File
@@ -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<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
pub fn decrypt_aes_salted(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>, tizen: bool) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
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<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>> {
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
+34 -14
View File
@@ -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<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 < 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<Option<Box<dyn Any>>,
}
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()
.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=<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 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<dyn Any>) -> Result<(
}
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
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"),
];