msd10: implement OUITH parser for old fws, fill in key for X14HK

This commit is contained in:
theubusu
2026-01-20 14:27:20 +01:00
parent fd21afcf3d
commit 76702c9e1f
5 changed files with 251 additions and 51 deletions
+31 -41
View File
@@ -6,6 +6,7 @@ use binrw::{BinRead, BinReaderExt};
use crate::utils::common; use crate::utils::common;
use crate::keys; use crate::keys;
use crate::formats::msd::{decrypt_aes_salted_old, decrypt_aes_salted_tizen, decrypt_aes_tizen}; use crate::formats::msd::{decrypt_aes_salted_old, decrypt_aes_salted_tizen, decrypt_aes_tizen};
use crate::utils::msd_ouith_parser_old::{parse_ouith_blob};
#[derive(BinRead)] #[derive(BinRead)]
struct FileHeader { struct FileHeader {
@@ -37,7 +38,6 @@ struct Section {
index: u32, index: u32,
offset: u32, offset: u32,
size: u32, size: u32,
name: String,
} }
#[derive(BinRead)] #[derive(BinRead)]
@@ -55,21 +55,6 @@ impl TizenTocEntry {
} }
} }
#[derive(BinRead)]
struct OldTocEntry {
#[br(count = 4)] _magic: Vec<u8>,
segment_length: u32,
segment_size: u32,
#[br(count = 26)] _unk: Vec<u8>,
name_lenght: u8,
#[br(count = name_lenght)] name_bytes: Vec<u8>,
}
impl OldTocEntry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
pub fn is_msd10_file(file: &File) -> bool { pub fn is_msd10_file(file: &File) -> bool {
let header = common::read_file(&file, 0, 6).expect("Failed to read from file."); let header = common::read_file(&file, 0, 6).expect("Failed to read from file.");
if header == b"MSDU10" { if header == b"MSDU10" {
@@ -84,20 +69,17 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
println!("\nNumber of sections: {}", header.section_count); println!("\nNumber of sections: {}", header.section_count);
let mut sections: Vec<Section> = Vec::new(); let mut sections: Vec<Section> = Vec::new();
for _i in 0..header.section_count { for _i in 0..header.section_count {
let section: SectionEntry = file.read_le()?; let section: SectionEntry = file.read_le()?;
println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size); println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
sections.push(Section {index: section.index, offset: section.offset, size: section.size, name: "".to_owned()}); sections.push(Section {index: section.index, offset: section.offset, size: section.size});
} }
let _0 = common::read_exact(&mut file, 4)?; //0000 let _zero_padding = common::read_exact(&mut file, 4)?;
let header_count_bytes = common::read_exact(&mut file, 4)?; let header_count: u32 = file.read_le()?;
let header_count = u32::from_le_bytes(header_count_bytes.try_into().unwrap());
println!("\nNumber of headers: {}", header_count); println!("\nNumber of headers: {}", header_count);
let mut headers: Vec<HeaderEntry> = Vec::new(); let mut headers: Vec<HeaderEntry> = Vec::new();
for i in 0..header_count { for i in 0..header_count {
let header: HeaderEntry = file.read_le()?; let header: HeaderEntry = file.read_le()?;
println!("Header {}: {}, offset: {}, size: {}", i + 1, header.name(), header.offset, header.size); println!("Header {}: {}, offset: {}, size: {}", i + 1, header.name(), header.offset, header.size);
@@ -168,33 +150,41 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
} else if firmware_type == "old" { } else if firmware_type == "old" {
let toc = decrypt_aes_salted_old(&toc_data, &passphrase_bytes)?; let toc = decrypt_aes_salted_old(&toc_data, &passphrase_bytes)?;
let mut toc_reader = Cursor::new(toc); let (items, info) = parse_ouith_blob(&toc)?;
toc_reader.seek(SeekFrom::Current(124))?; //probably signature + first magic if let Some(info) = info {
println!("\nImage info:\n{} {}.{} {}/{}/20{}",
info.name(), info.major_ver, info.minor_ver, info.date_day, info.date_month, info.date_year);
}
for i in 0..header.section_count { for (i, item) in items.iter().enumerate() {
let entry: OldTocEntry = toc_reader.read_be()?; let type_str = if item.item_type == 0x0A {"Partition"} else if item.item_type == 0x0B {"File"} else if item.item_type == 0x11 {"CMAC Data"} else {"Unknown"};
println!("\n({}/{}) - {}, Type: {}, Size: {}",
item.item_id, items.len(), item.name, type_str, item.all_size);
toc_reader.seek(SeekFrom::Current((entry.segment_length - entry.name_lenght as u32 - 31).into()))?; assert!(sections[i as usize].index == item.item_id, "Item ID in TOC does not match ID from header!");
assert!(entry.segment_size == sections[i as usize].size, "size in TOC does not match size from header!"); if item.item_type == 0x11 { //Skip CMAC DATA
sections[i as usize].name = entry.name().clone(); println!("- Skipping CMAC Data...");
let offset = sections[i as usize].offset; continue
let size = sections[i as usize].size;
println!("\n({}/{}) - {}, Size: {}", sections[i as usize].index, sections.len(), entry.name(), size);
if i != 0 && entry.name() == sections[i as usize - 1].name { //second section with the same name is some sort of signature
println!("- Skipping signature file...");
continue;
} }
let encrypted_data = common::read_file(&file, offset as u64 + 136, size as usize - 136)?; let offset = sections[i as usize].offset;
file.seek(SeekFrom::Start(offset as u64))?;
println!("- Decrypting..."); //skip heading metadata thing
let out_data = decrypt_aes_salted_old(&encrypted_data, &passphrase_bytes)?; file.seek(SeekFrom::Current(item.heading_size as i64))?;
let stored_data = common::read_exact(&mut file, item.data_size as usize)?;
let out_data;
if item.aes_encryption {
println!("- Decrypting...");
out_data = decrypt_aes_salted_old(&stored_data, &passphrase_bytes)?;
} else {
out_data = stored_data;
}
let output_path = Path::new(&output_folder).join(entry.name()); let output_path = Path::new(&output_folder).join(item.name.clone());
fs::create_dir_all(&output_folder)?; fs::create_dir_all(&output_folder)?;
let mut out_file = OpenOptions::new() let mut out_file = OpenOptions::new()
.write(true) .write(true)
+1 -4
View File
@@ -69,19 +69,16 @@ pub fn extract_msd11(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
println!("\nNumber of sections: {}", header.section_count); println!("\nNumber of sections: {}", header.section_count);
let mut sections: Vec<Section> = Vec::new(); let mut sections: Vec<Section> = Vec::new();
for _i in 0..header.section_count { for _i in 0..header.section_count {
let section: SectionEntry = file.read_le()?; let section: SectionEntry = file.read_le()?;
println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size); println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
sections.push(Section {index: section.index, offset: section.offset, size: section.size}); sections.push(Section {index: section.index, offset: section.offset, size: section.size});
} }
let header_count_bytes = common::read_exact(&mut file, 4)?; let header_count: u32 = file.read_le()?;
let header_count = u32::from_le_bytes(header_count_bytes.try_into().unwrap());
println!("\nNumber of headers: {}", header_count); println!("\nNumber of headers: {}", header_count);
let mut headers: Vec<HeaderEntry> = Vec::new(); let mut headers: Vec<HeaderEntry> = Vec::new();
for i in 0..header_count { for i in 0..header_count {
let header: HeaderEntry = file.read_le()?; let header: HeaderEntry = file.read_le()?;
println!("Header {}: {}, offset: {}, size: {}", i + 1, header.name(), header.offset, header.size); println!("Header {}: {}, offset: {}, size: {}", i + 1, header.name(), header.offset, header.size);
+6 -5
View File
@@ -16,11 +16,12 @@ pub static SAMSUNG: &[(&str, &str)] = &[
//MSD10 keys //MSD10 keys
//fw prefix, type, key //fw prefix, type, key
pub static MSD10: &[(&str, &str, &str)] = &[ pub static MSD10: &[(&str, &str, &str)] = &[
("T-ECP", "old", "3ef6067262cf0c678598bff22169d1f1ea57c284"), //for T-ECPDEUT ("T-ECP", "old", "3ef6067262cf0c678598bff22169d1f1ea57c284"), //EchoP - 2012 Samsung
("T-MST12", "old", "010287362008be691dc8c9f6d2c5ca5aa210ecb8"), //X12 - 2013 Mstar ("T-MST12", "old", "010287362008be691dc8c9f6d2c5ca5aa210ecb8"), //X12 - 2013 Mstar
("T-MST14", "old", "d145f1d55aee3ef520d194e1275b429c580baacb"), //X14H - 2014 Mstar ("T-MST14", "old", "d145f1d55aee3ef520d194e1275b429c580baacb"), //X14 - 2014 Mstar
("T-MS14J", "old", "d145f1d55aee3ef520d194e1275b429c580baacb"), //X14J - 2014 Mstar (For 2015 models) ("T-MS14J", "old", "d145f1d55aee3ef520d194e1275b429c580baacb"), //X14J - 2014 Mstar (For 2015 models)
("T-NT14M", "old", "95d01e0bae861a05695bc8a6edb2ea835a09accd"), //NT14M - 2014 Novatek (low-end) ("T-M14HK", "old", "d145f1d55aee3ef520d194e1275b429c580baacb"), //X14HK - 2014 Mstar (For 2016 models)
("T-NT14M", "old", "95d01e0bae861a05695bc8a6edb2ea835a09accd"), //NT14M - 2014 Novatek (low-end)
("T-N14MJ", "old", "95d01e0bae861a05695bc8a6edb2ea835a09accd"), //NT14M_J - 2014 Novatek (low-end) (for 2015 models) ("T-N14MJ", "old", "95d01e0bae861a05695bc8a6edb2ea835a09accd"), //NT14M_J - 2014 Novatek (low-end) (for 2015 models)
//github.com/bugficks/msddecrypt //github.com/bugficks/msddecrypt
+2 -1
View File
@@ -8,4 +8,5 @@ pub mod compression;
pub mod android_ota_update_metadata; pub mod android_ota_update_metadata;
pub mod mtk_crypto; pub mod mtk_crypto;
pub mod lzhs; pub mod lzhs;
pub mod huffman_tables; pub mod huffman_tables;
pub mod msd_ouith_parser_old;
+211
View File
@@ -0,0 +1,211 @@
//MAIN CODE: https://github.com/theubusu/msd_OUITH_parser
use std::io::{Seek, SeekFrom, Cursor};
use binrw::{BinRead, BinReaderExt};
use crate::utils::common;
#[derive(BinRead)]
struct ChunkHeader {
size: u32,
value: u32,
}
#[derive(BinRead)]
struct DescriptorHeader {
tag: u16,
size: u32,
}
#[derive(BinRead)]
//OUSWFileVersionDesc, OUPartitionVersionDesc, OUCMACDataDesc
struct CommonDestinationInfo {
_name_len: u8,
#[br(count = _name_len)] name_bytes: Vec<u8>,
_version: u16,
}
impl CommonDestinationInfo {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
struct OUAESEncryptionDesc {
_mode: u8,
_key_size: u32,
_salt_size: u32,
}
#[derive(BinRead)]
struct OURSAValidationDesc {
_mode: u8,
_unknown: u32,
_signature_size: u32,
}
#[derive(BinRead)]
struct OUSecureHashValidationDesc {
_mode: u8,
_hash_size: u16,
#[br(count = _hash_size)] hash: Vec<u8>,
}
#[derive(BinRead)]
struct OUGroupDesc {
_group_id: u32,
_field_2: u8,
_field_3: u8,
}
#[derive(BinRead)]
pub struct OUSWImageVersionExDesc {
pub _name_len: u8,
#[br(count = _name_len)] pub name_bytes: Vec<u8>,
pub major_ver: u16,
pub minor_ver: u16,
pub date_year: u8,
pub date_month: u8,
pub date_day: u8,
}
impl OUSWImageVersionExDesc {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
//this is a struct that will communicate all values important when parsing the MSD file.
pub struct MSDItem {
pub item_id: u32,
pub item_type: u16, //File, Partition, CMACData
pub all_size: u32, //same size as in msd header
pub name: String,
pub heading_size: u32, //has the signature and salt
pub data_size: u32,
pub aes_encryption: bool,
//pub crc32_hash: Option<u32>,
//pub secure_hash: Option<Vec<u8>>,
}
pub fn parse_ouith_blob(blob: &[u8]) -> Result<(Vec<MSDItem>, Option<OUSWImageVersionExDesc>), Box<dyn std::error::Error>> {
let mut reader = Cursor::new(blob);
let mut items: Vec<MSDItem> = Vec::new();
let mut info: Option<OUSWImageVersionExDesc> = None;
let _signature = common::read_exact(&mut reader, 128)?; //signature included at the beginning of blob in MSD file
let mut _chunk_n = 0;
while reader.stream_position()? < blob.len() as u64 {
_chunk_n += 1;
let chunk: ChunkHeader = reader.read_be()?;
let chunk_end = reader.stream_position()? + chunk.size as u64;
//parse top level descriptor. it can only be ID 1(OUUpgradeItemDesc) or 2(OUGroupDesc)
let top_descriptor: DescriptorHeader = reader.read_be()?;
if top_descriptor.tag == 0x01 {
let item_id: u32 = reader.read_be()?;
//REQUIRED are items in order: OUDestinationDesc(0x03), OUDataProcessingDesc(0x07), OUGroupInfoDesc(0x13). OPTIONAL items: OUDependenciesDesc(0x04), OUDataPostProcessingDesc(0x08)
//In MSD files, no others seem to be used than the required ones. We will ignore all data after required descriptors.
let destination_descriptor: DescriptorHeader = reader.read_be()?;
if destination_descriptor.tag != 0x03 {return Err(format!("Unexpected descriptor type in OUUpgradeItemDesc, Expected: 0x03, Got: 0x{:02x}!", destination_descriptor.tag).into())}
let _out_size: u32 = reader.read_be()?;
//OUDestinationDesc needs one of OUSWFileVersionDesc(0x0B), OUPartitionVersionDesc(0x0A), OUCMACDataDesc(0x11). Their structure is the same. so we can store the type
let type_descriptor: DescriptorHeader = reader.read_be()?;
if ![0x0B, 0xA, 0x11].contains(&type_descriptor.tag) {return Err(format!("Unexpected descriptor type in OUDestinationDesc, Expected: one of 0x0B, 0x0A, 0x11, Got: 0x{:02x}!", type_descriptor.tag).into())}
let destination_info: CommonDestinationInfo = reader.read_be()?;
//OUDataProcessingDesc can have OUXOREncryptionDesc(0x0D), OUAESEncryptionDesc(0x0E), OUCompressionDesc(0x0F), OUSecureHashValidationDesc(0x18), OURSAValidationDesc(0x10), OUDataCopyDesc(0x16), OUKeepCurrentDataDesc(0x1E), OUCRC32ValidationDesc(0x12)
let data_processing_descriptor: DescriptorHeader = reader.read_be()?;
if data_processing_descriptor.tag != 0x07 {return Err(format!("Unexpected descriptor type in OUUpgradeItemDesc, Expected: 0x07, Got: 0x{:02x}!", data_processing_descriptor.tag).into())}
let heading_size: u32 = reader.read_be()?;
let data_size: u32 = reader.read_be()?;
let mut aes_encryption = false;
let mut _crc32_hash: Option<u32> = None;
let mut _secure_hash: Option<Vec<u8>> = None;
let epos = reader.stream_position()? + (data_processing_descriptor.size - 8) as u64;
while reader.stream_position()? < epos {
let descriptor: DescriptorHeader = reader.read_be()?;
if ![0x0D, 0x0E, 0x0F, 0x18, 0x10, 0x16, 0x1E, 0x12].contains(&descriptor.tag) {return Err(format!("Unexpected descriptor type in OUDataProcessingDesc, Expected: one of 0x0D, 0x0E, 0x0F, 0x18, 0x10, 0x16, 0x1E, 0x12, Got: 0x{:02x}!", descriptor.tag).into())}
if descriptor.tag == 0x0E {
//OUAESEncryptionDesc
let _aes_encryption_desc: OUAESEncryptionDesc = reader.read_be()?;
aes_encryption = true;
}
else if descriptor.tag == 0x10 {
//OURSAValidationDesc
let _rsa_validation_desc: OURSAValidationDesc = reader.read_be()?;
}
else if descriptor.tag == 0x12 {
//OUCRC32ValidationDesc
let crc32: u32 = reader.read_be()?;
_crc32_hash = Some(crc32);
}
else if descriptor.tag == 0x18 {
//OUSecureHashValidationDesc
let secure_hash_validation_desc: OUSecureHashValidationDesc = reader.read_be()?;
_secure_hash = Some(secure_hash_validation_desc.hash);
}
else {
//type not implemented, ignore the data
let _descriptor_data = common::read_exact(&mut reader, descriptor.size as usize);
}
}
//OUGroupInfoDesc
let group_info_descriptor: DescriptorHeader = reader.read_be()?;
if group_info_descriptor.tag != 0x13 {return Err(format!("Unexpected descriptor type in OUUpgradeItemDesc, Expected: 0x13, Got: 0x{:02x}!", group_info_descriptor.tag).into())}
let _group_id: u32 = reader.read_be()?;
//create the msd item with all infos
let msd_item = MSDItem {
item_id: item_id,
item_type: type_descriptor.tag,
all_size: chunk.value,
name: destination_info.name(),
heading_size: heading_size,
data_size: data_size,
aes_encryption: aes_encryption,
//crc32_hash: crc32_hash,
//secure_hash: secure_hash,
};
items.push(msd_item);
//go directly to the end of the chunk to skip optional descriptors just in case
reader.seek(SeekFrom::Start(chunk_end))?;
}
else if top_descriptor.tag == 0x02 {
let _group_desc: OUGroupDesc = reader.read_be()?;
//OUGroupDesc REQUIRES one of: OUSWImageVersionDesc(0x09), OUSWImageVersionExDesc(0x19), OUOptionalDataVersionDesc(0x14), OUFirmwareVersionDesc(0x15). OPTIONALLY it can also have OUDependenciesDesc
//MSD files seem to exclusively use OUSWImageVersionExDesc
let version_descriptor: DescriptorHeader = reader.read_be()?;
if ![0x09, 0x19, 0x14, 0x15].contains(&version_descriptor.tag) {return Err(format!("Unexpected descriptor type in OUGroupDesc, Expected: one of 0x09, 0x19, 0x14, 0x15, Got: 0x{:02x}!", version_descriptor.tag).into())}
if version_descriptor.tag == 0x19 {
let sw_image_version_ex_desc: OUSWImageVersionExDesc = reader.read_be()?;
info = Some(sw_image_version_ex_desc);
}
else {
//type not implemented, ignore the data
let _descriptor_data = common::read_exact(&mut reader, version_descriptor.size as usize);
}
//go directly to the end of the chunk to skip optional descriptors just in case
reader.seek(SeekFrom::Start(chunk_end))?;
}
else {
return Err(format!("Unexpected top level descriptor type 0x{:02x}!", top_descriptor.tag).into());
}
}
Ok((items, info)) //finally, it will return a list of MSD items and info about image if it was collected.
}