mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
+ cleanup
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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"),
|
||||
];
|
||||
Reference in New Issue
Block a user