Consistency cleanup + add MSD11 and SDDL.SEC extractors

This commit is contained in:
theubusu
2025-10-07 16:00:32 +02:00
parent bf364f9f36
commit 22c8658173
11 changed files with 478 additions and 85 deletions
+3 -1
View File
@@ -4,4 +4,6 @@ pub mod tpv_timg;
pub mod pfl_upg;
pub mod epk1;
pub mod novatek;
pub mod msd10;
pub mod msd10;
pub mod msd11;
pub mod sddl_sec;
+9 -11
View File
@@ -7,9 +7,7 @@ use crate::common;
pub fn is_epk1_file(file: &File) -> bool {
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
let header_string = String::from_utf8_lossy(&header);
if header_string == "epak"{
if header == b"epak" {
true
} else {
false
@@ -28,15 +26,15 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
let init_pak_count = u32::from_le_bytes(init_pak_count_bytes.try_into().unwrap());
if init_pak_count > 256 {
println!("Big endian EPK1 detected.");
println!("\nBig endian EPK1 detected.");
epk1_type = "be";
} else if init_pak_count < 21 {
println!("Little endian EPK1 detected.");
println!("\nLittle endian EPK1 detected.");
epk1_type = "le";
} else {
//println!("EPK1(new) detected.");
//println!("\nEPK1(new) detected.");
//epk1_type = "new";
println!("Not supported!");
println!("\nNot supported!");
return Ok(());
}
@@ -71,7 +69,7 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
let version = common::read_exact(&mut file, 4)?;
println!("EPK info:\nFile size: {}\nPak count: {}\nVersion: {:02x?}.{:02x?}.{:02x?}\n",
println!("EPK info:\nFile size: {}\nPak count: {}\nVersion: {:02x?}.{:02x?}.{:02x?}",
file_size, pak_count, version[1], version[2], version[3]);
} else if epk1_type == "le" {
@@ -104,7 +102,7 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
let ota_id_bytes = common::read_exact(&mut file, 32)?;
let ota_id = common::string_from_bytes(&ota_id_bytes);
println!("EPK info:\nFile size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}\n",
println!("EPK info:\nFile size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}",
file_size, pak_count, ota_id, version[2], version[1], version[0]);
}
@@ -125,7 +123,7 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
let data = common::read_file(&file, pak.offset as u64, pak.size as usize)?;
println!("- Pak {}: {}, Offset: {}, Size: {}, Platform: {}", i + 1, pak_name, pak.offset, pak.size, pak_platform);
println!("\nPak {}: {}, Offset: {}, Size: {}, Platform: {}", i + 1, pak_name, pak.offset, pak.size, pak_platform);
let output_path = Path::new(&output_folder).join(pak_name + ".bin");
@@ -137,7 +135,7 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
out_file.write_all(&data[128..pak_actual_size as usize + 128])?;
println!("-- Saved file!");
println!("- Saved file!");
}
println!("\nExtraction finished!");
+14 -10
View File
@@ -11,7 +11,6 @@ use crate::common;
pub fn is_msd10_file(file: &File) -> bool {
let header = common::read_file(&file, 0, 6).expect("Failed to read from file.");
if header == b"MSDU10" {
true
} else {
@@ -28,14 +27,15 @@ struct Section {
//fw name, type, key
static KEYS: &[(&str, &str, &str)] = &[
("T-NT14M", "old", "95d01e0bae861a05695bc8a6edb2ea835a09accd"),
("T-HKM", "tizen", "1ac8989ff57db5e75ea67b033050871c"),
("T-NT14M", "old", "95d01e0bae861a05695bc8a6edb2ea835a09accd"),
("T-HKM", "tizen", "1ac8989ff57db5e75ea67b033050871c"),
("T-HKP", "tizen", "cce8a3ef92f3e94895999e928f4dd6c3"),
];
fn decrypt_aes_salted_old(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut data = encrypted_data.to_vec();
assert!(String::from_utf8(data[0..8].to_vec())? == "Salted__", "invalid encrypted data!");
assert!(data[0..8].to_vec() == b"Salted__", "invalid encrypted data!");
let salt = &data[8..16];
//key = md5 of (passphrase + salt)
@@ -61,7 +61,7 @@ fn decrypt_aes_salted_old(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>) ->
fn decrypt_aes_salted_tizen(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut data = encrypted_data.to_vec();
assert!(String::from_utf8(data[0..8].to_vec())? == "Salted__", "invalid encrypted data!");
assert!(data[0..8].to_vec() == b"Salted__", "invalid encrypted data!");
let salt = &data[8..16];
//iv = md5 of salt
@@ -93,7 +93,7 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
let section_count_bytes = common::read_exact(&mut file, 4)?;
let section_count = u32::from_le_bytes(section_count_bytes.try_into().unwrap());
println!("Number of sections: {}", section_count);
println!("\nNumber of sections: {}", section_count);
let mut sections: Vec<Section> = Vec::new();
@@ -158,7 +158,7 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
if let Some(p) = passphrase {
println!("Passphrase: {}", p);
passphrase_bytes = hex::decode(p)?;
println!("Firmware type: {}\n", firmware_type);
println!("Firmware type: {}", firmware_type);
} else {
println!("Sorry, this firmware is not supported!");
std::process::exit(1);
@@ -191,11 +191,13 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
toc_reader.seek(SeekFrom::Current(13))?; //unknown
println!("- Extracting section {}: {}...", sections[i as usize].index, name);
println!("\nSection {}: {}", sections[i as usize].index, name);
let offset = sections[i as usize].offset;
let size = sections[i as usize].size;
let encrypted_data = common::read_file(&file, offset as u64, size as usize)?;
println!("- Decrypting...");
let decrypted_data = decrypt_aes_tizen(&encrypted_data, &passphrase_bytes, &salt)?;
let output_path = Path::new(&output_folder).join(name);
@@ -242,17 +244,19 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
toc_reader.seek(SeekFrom::Current((segment_length - name_length as u32 - 31).into()))?;
println!("- Extracting section {}: {}...", sections[i as usize].index, name);
println!("\nSection {}: {}", sections[i as usize].index, name);
let offset = sections[i as usize].offset;
let size = sections[i as usize].size;
if i != 0 && name == sections[i as usize - 1].name { //second section with the same name is some sort of signature
println!("-- Skipping signature file..");
println!("- Skipping signature file...");
continue;
}
let encrypted_data = common::read_file(&file, offset as u64 + 136, size as usize - 136)?;
println!("- Decrypting...");
let out_data = decrypt_aes_salted_old(&encrypted_data, &passphrase_bytes)?;
let output_path = Path::new(&output_folder).join(name);
+194
View File
@@ -0,0 +1,194 @@
use std::fs::{self, File, OpenOptions};
use std::path::{Path};
use std::io::{Cursor, Write, Seek, SeekFrom};
use aes::Aes128;
use cbc::{Decryptor, cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}};
type Aes128CbcDec = Decryptor<Aes128>;
use crate::common;
pub fn is_msd11_file(file: &File) -> bool {
let header = common::read_file(&file, 0, 6).expect("Failed to read from file.");
if header == b"MSDU11" {
true
} else {
false
}
}
struct Section {
index: u32,
offset: u64,
size: u64,
name: String,
}
static KEYS: &[(&str, &str)] = &[
("T-JZM", "9b1d077c0d137d406c79ddacb6b159fe"), //2015
("T-HKMFK", "c7097975e8ab994beb5eaae57e0ba77c"), //2016
("T-KTM2L", "46b04f5e794ca4377a20951c9ea00427"), //2018
("T-KTM2", "29110e0ce940b3a9b67d3e158f3f1342"), //2018
("T-KTM", "d0d49d5f36f5c0da50062fbf32168f5b"), //2017
("T-KTSU", "19e1ba41163f03735e692d9daa2cbb47"), //2018
("T-KTSD", "39332605ff47a0aea999b10ce9087389"), //2018
("T-NKL", "5bab1098dab48792619ebd63650d929f"), //2020
];
fn decrypt_aes_salted_tizen(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut data = encrypted_data.to_vec();
assert!(data[0..8].to_vec() == b"Salted__", "invalid encrypted data!");
let salt = &data[8..16];
//iv = md5 of salt
let iv = md5::compute(&salt);
let decryptor = Aes128CbcDec::new((&(**passphrase_bytes)).into(), (&iv.0).into());
let decrypted = decryptor.decrypt_padded_mut::<Pkcs7>(&mut data[16..])
.map_err(|e| format!("Decryption error!!: {:?}", e))?;
Ok(decrypted.to_vec())
}
fn decrypt_aes_tizen(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>, salt: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut data = encrypted_data.to_vec();
//iv = md5 of salt
let iv = md5::compute(&salt);
let decryptor = Aes128CbcDec::new((&(**passphrase_bytes)).into(), (&iv.0).into());
let decrypted = decryptor.decrypt_padded_mut::<Pkcs7>(&mut data)
.map_err(|e| format!("Decryption error!!: {:?}", e))?;
Ok(decrypted.to_vec())
}
pub fn extract_msd11(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
let _magic = common::read_exact(&mut file, 6)?; //MSDU11 magic
let _ = common::read_exact(&mut file, 12)?;
let section_count_bytes = common::read_exact(&mut file, 4)?;
let section_count = u32::from_le_bytes(section_count_bytes.try_into().unwrap());
println!("\nNumber of sections: {}", section_count);
let mut sections: Vec<Section> = Vec::new();
//parse sections
for _i in 0..section_count {
let index_bytes = common::read_exact(&mut file, 4)?;
let index = u32::from_le_bytes(index_bytes.try_into().unwrap());
let offset_bytes = common::read_exact(&mut file, 8)?;
let offset = u64::from_le_bytes(offset_bytes.try_into().unwrap());
let size_bytes = common::read_exact(&mut file, 8)?;
let size = u64::from_le_bytes(size_bytes.try_into().unwrap());
println!("Section {}: offset: {}, size: {}", index, offset, size);
sections.push(Section { index, offset, size , name: "".to_string() });
}
let header_count_bytes = common::read_exact(&mut file, 4)?;
let header_count = u32::from_le_bytes(header_count_bytes.try_into().unwrap());
println!("\nNumber of headers: {}", header_count);
let mut headers: Vec<Section> = Vec::new();
//parse headers
for i in 0..header_count {
let offset_bytes = common::read_exact(&mut file, 8)?;
let offset = u64::from_le_bytes(offset_bytes.try_into().unwrap());
let size_bytes = common::read_exact(&mut file, 4)?;
let size = u32::from_le_bytes(size_bytes.try_into().unwrap());
let name_length_byte = common::read_exact(&mut file, 1)?;
let name_length = u8::from_le_bytes(name_length_byte.try_into().unwrap());
let name_bytes = common::read_exact(&mut file, name_length as usize)?;
let name = String::from_utf8(name_bytes)?;
println!("Header {}: {}, offset: {}, size: {}", i + 1, name, offset, size);
headers.push(Section { index: 0, offset, size: size as u64 , name });
}
//use first header
let firmware_name = &headers[0].name;
println!("\nFirmware name: {}", firmware_name);
let mut passphrase: Option<&str> = None;
let passphrase_bytes;
//find passphrase
for (prefix, value) in KEYS {
if firmware_name.starts_with(prefix) {
passphrase = Some(value);
break;
}
}
if let Some(p) = passphrase {
println!("Passphrase: {}", p);
passphrase_bytes = hex::decode(p)?;
} else {
println!("Sorry, this firmware is not supported!");
std::process::exit(1);
}
let toc_offset = headers[0].offset + 8;
let toc_size = headers[0].size - 8;
let toc_data = common::read_file(&file, toc_offset as u64, toc_size as usize)?;
//parse TOC
let toc = decrypt_aes_salted_tizen(&toc_data, &passphrase_bytes)?;
let mut toc_reader = Cursor::new(toc);
toc_reader.seek(SeekFrom::Current(262))?; // probably signature
toc_reader.seek(SeekFrom::Current(50))?; // Tizen Software Upgrade Tree Binary Format ver. 1.9
for i in 0..section_count {
toc_reader.seek(SeekFrom::Current(74))?; //unknown
let name_length_byte = common::read_exact(&mut toc_reader, 1)?;
let name_length = u8::from_le_bytes(name_length_byte.try_into().unwrap());
let name_bytes = common::read_exact(&mut toc_reader, name_length as usize)?;
let name = String::from_utf8(name_bytes)?;
toc_reader.seek(SeekFrom::Current(39))?; //unknown
let salt = common::read_exact(&mut toc_reader, 8)?;
toc_reader.seek(SeekFrom::Current(267))?; //unknown
println!("\nSection {}: {}", sections[i as usize].index, name);
let offset = sections[i as usize].offset;
let size = sections[i as usize].size;
let encrypted_data = common::read_file(&file, offset as u64, size as usize)?;
println!("- Decrypting...");
let decrypted_data = decrypt_aes_tizen(&encrypted_data, &passphrase_bytes, &salt)?;
let output_path = Path::new(&output_folder).join(name);
fs::create_dir_all(&output_folder)?;
let mut out_file = OpenOptions::new()
.write(true)
.create(true)
.open(output_path)?;
out_file.write_all(&decrypted_data)?;
println!("-- Saved file!");
}
println!("\nExtraction finished!");
Ok(())
}
+9 -7
View File
@@ -132,28 +132,28 @@ pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box<dyn std
j += 1;
}
println!("- Part: Offset: {}, Size: {} --> {}", offset, size, partname);
println!("\nPart: Offset: {}, Size: {} --> {}", offset, size, partname);
if partname == "unknown"{
println!("-- Unknown destination, skipping!");
println!("- Unknown destination, skipping!");
} else {
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
let out_data;
if compression == "lzma" {
println!("-- Decompressing LZMA...");
println!("- Decompressing LZMA...");
out_data = decompress_lzma(&data)?;
} else if compression == "double_lzma" {
println!("-- Decompressing LZMA (Pass 1)...");
println!("- Decompressing LZMA (Pass 1)...");
let pass_1 = decompress_lzma(&data)?;
println!("-- Decompressing LZMA (Pass 2)...");
println!("- Decompressing LZMA (Pass 2)...");
out_data = decompress_lzma(&pass_1)?;
} else if compression == "lz4" {
println!("-- Decompressing lz4, expected size: {}", lz4_expect_size);
println!("- Decompressing lz4, expected size: {}", lz4_expect_size);
out_data = decompress_lz4(&data, lz4_expect_size.try_into().unwrap())?;
} else if compression == "lzo" {
// nothing in rust to parse lzo file with header
println!("-- lzo compression is not supported yet!!...");
println!("- lzo compression is not supported yet!");
out_data = data;
}else {
out_data = data;
@@ -168,6 +168,8 @@ pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box<dyn std
.open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved file!");
}
}
}
+4 -6
View File
@@ -7,9 +7,7 @@ use crate::common;
pub fn is_novatek_file(file: &File) -> bool {
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
let header_string = String::from_utf8_lossy(&header);
if header_string == "NFWB"{
if header == b"NFWB" {
true
} else {
false
@@ -27,7 +25,7 @@ pub fn extract_novatek(mut file: &File, output_folder: &str) -> Result<(), Box<d
let _first_part_offset = common::read_exact(&mut file, 4)?;
println!("Part count: {}", part_count);
println!("\nPart count: {}", part_count);
let _ = common::read_exact(&mut file, 116)?;
@@ -47,7 +45,7 @@ pub fn extract_novatek(mut file: &File, output_folder: &str) -> Result<(), Box<d
let data = common::read_file(&file, offset as u64, size as usize)?;
println!("- Part {}: index: {}, size: {}, offset: {}", i + 1, index, size, offset);
println!("\nPart {}: index: {}, size: {}, offset: {}", i + 1, index, size, offset);
let output_path = Path::new(&output_folder).join(format!("part_{}.bin", i + 1));
@@ -59,7 +57,7 @@ pub fn extract_novatek(mut file: &File, output_folder: &str) -> Result<(), Box<d
out_file.write_all(&data)?;
println!("-- Saved file!");
println!("- Saved file!");
file.seek(std::io::SeekFrom::Start(current_pos))?;
+6 -12
View File
@@ -12,9 +12,7 @@ use crate::common;
pub fn is_pfl_upg_file(file: &File) -> bool {
let header = common::read_file(&file, 0, 8).expect("Failed to read from file.");
let header_string = String::from_utf8_lossy(&header);
if header_string == "2SWU3TXV"{
if header == b"2SWU3TXV" {
true
} else {
false
@@ -88,7 +86,7 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<d
let version_bytes = common::read_exact(&mut file, header_size as usize - 704)?;
let version = common::string_from_bytes(&version_bytes);
println!("Version: {}", version);
println!("\nVersion: {}", version);
println!("Description: \n{}", description);
println!("Data size: {}", data_size);
@@ -130,8 +128,7 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<d
let decrypted = decrypted_int.to_bytes_le();
let aes_key = &decrypted[20..52];
println!("AES key: {}", hex::encode(aes_key));
println!();
println!("AES key: {}\n", hex::encode(aes_key));
let encrypted_data = common::read_exact(&mut file, data_size as usize)?;
println!("Decrypting data...");
@@ -141,8 +138,6 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<d
decrypted_data = common::read_exact(&mut file, data_size as usize)?;
}
println!();
let mut data_reader = Cursor::new(decrypted_data);
while (data_reader.position() as usize) < data_reader.get_ref().len() {
@@ -157,7 +152,7 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<d
let _header_size = u32::from_le_bytes(file_header[68..72].try_into().unwrap());
println!("- File: {}, Size: {}", file_name, real_size);
println!("\nFile: {}, Size: {}", file_name, real_size);
let data = common::read_exact(&mut data_reader, stored_size as usize)?;
@@ -175,11 +170,10 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<d
out_file.write_all(&data[..real_size as usize])?;
println!("-- Saved file!");
println!("- Saved file!");
}
println!();
println!("Extraction finished!");
println!("\nExtraction finished!");
Ok(())
}
+5 -4
View File
@@ -85,7 +85,7 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo
let file = File::open(&path)?;
let filename = path.file_name().unwrap().to_str().unwrap();
let file_size = file.metadata()?.len();
println!("- File: {}", filename);
println!("\nFile: {}", filename);
let data = common::read_file(&file, 0, file_size.try_into().unwrap())?;
let salt = &data[8..16];
@@ -126,7 +126,7 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo
//println!("IV: {:02x?}", iv_md5);
let end = file_size - 260;
println!("-- Decrypting file...");
println!("- Decrypting file...");
let decrypted_data = decrypt_aes(&data[16..end.try_into().unwrap()], &key_md5, &iv_md5)?;
println!("-- DeXORing file...");
@@ -142,13 +142,14 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo
.open(output_path)?;
out_file.write_all(&out_data)?;
println!("--- Saved file!");
}
}
}
}
println!();
println!("Extraction finished!");
println!("\nExtraction finished!");
Ok(())
}
+214
View File
@@ -0,0 +1,214 @@
use std::path::{Path, PathBuf};
use std::fs::{self, File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use aes::Aes128;
use flate2::read::ZlibDecoder;
use cbc::{Decryptor, cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}};
type Aes128CbcDec = Decryptor<Aes128>;
use crate::common;
pub fn is_sddl_sec_file(file: &File) -> bool {
let header = common::read_file(&file, 0, 8).expect("Failed to read from file.");
if header == b"\x12\xB8\x02\x8C\x6F\xC6\xBD\x15" {
true
} else {
false
}
}
fn decrypt(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut data = encrypted_data.to_vec();
let decryptor = Aes128CbcDec::new(key.into(), iv.into());
let decrypted = decryptor.decrypt_padded_mut::<Pkcs7>(&mut data)
.map_err(|e| format!("!!Decryption error!!: {:?}", e))?;
Ok(decrypted.to_vec())
}
//ported from original from https://nese.team/posts/justctf/
fn decipher(s: &[u8], len_: usize) -> Vec<u8> {
let mut v3: u32 = 904;
let mut out = s.to_vec();
let mut cnt = 0;
if len_ > 0 {
let true_len = if len_ >= 0x80 {
128
} else {
len_
};
if true_len > 0 {
let mut i = 0;
let mut j: u8 = 0;
while i < s.len() {
let iter_ = s[i];
i += 1;
j = j.wrapping_add(1);
let v11 = (iter_ as u32).wrapping_add(38400) & 0xffffffff;
let v12 = iter_ ^ ((v3 & 0xff00) >> 8) as u8;
v3 = v3.wrapping_add(v11).wrapping_add(163) & 0xffffffff;
if j == 0 {
v3 = 904;
}
if cnt < out.len() {
out[cnt] = v12;
cnt += 1;
}
}
}
}
out
}
fn decompress_zlib(data: &[u8]) -> io::Result<Vec<u8>> {
let mut decoder = ZlibDecoder::new(data);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed)?;
Ok(decompressed)
}
pub fn extract_sddl_sec(file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
let file_size = file.metadata()?.len();
let key = [
0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB,
0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C,
];
let iv = [
0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54,
0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66,
];
let mut offset = 32;
while offset < file_size {
let header = common::read_file(&file, offset, 32)?;
let decrypted_header: Vec<u8>;
match decrypt(&header, &key, &iv) {
Ok(v) => decrypted_header = v,
Err(_) => {
// SDDL files can have a footer(signature?) of 0x80 OR 0x100 lenght in later ones, and there is no good way to detect it before entering the while loop and the footer has no common header.
// so we can assume if a file fails to decode at negative offsets 0x80 or 0x100, that is the footer and it can be skipped.
if offset == file_size - 128 {
break
} else if offset == file_size - 256{
break
} else {
println!("!!Decryption error!! This file is invalid or not compatible!");
std::process::exit(0)
}
},
}
let decrypted_string = String::from_utf8_lossy(&decrypted_header);
let filename = decrypted_string.split("\0").next().unwrap();
let size_str = &decrypted_string[decrypted_string.len() - 12..];
let size: u64 = size_str.parse().unwrap();
println!("\nFile: {}, Size: {}", filename, size);
offset += 32;
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
let decrypted_data = decrypt(&data, &key, &iv)?;
if decrypted_data.starts_with(&[0x11, 0x22, 0x33, 0x44]) && filename != "SDIT.FDI"{ // header of obfuscated file, SDIT.FDI also has this header but seems to work differently so its skipped
println!("- Version: {}.{}{}{}", decrypted_data[24], decrypted_data[25], decrypted_data[26], decrypted_data[27]);
println!("- Deciphering...");
let deciphered_data = decipher(&decrypted_data[48..], decrypted_data[48..].len());
let control_byte = decrypted_data[34];
let out_data: Vec<u8>;
if control_byte == 3 {
println!("-- Decompressing...");
out_data = decompress_zlib(&deciphered_data)?;
} else {
println!("-- Uncompressed...");
out_data = deciphered_data;
}
let first_byte;
if out_data[1] & 0xF0 == 0xD0 {
first_byte = out_data[1] & 0x0F;
} else {
first_byte = out_data[1];
}
let dest_offset = u32::from_be_bytes([first_byte, out_data[2], out_data[3], out_data[4]]);
let source_offset = u32::from_be_bytes([0x00, out_data[6], out_data[7], out_data[8]]);
let path: PathBuf;
let msg: &str;
if filename.starts_with("PEAKS.F") {
if source_offset == 270 { //unique for 2014-2018 files
let embedded_file_name_string = String::from_utf8_lossy(&out_data[14..270]);
let embedded_file_name = embedded_file_name_string.split("\0").next().unwrap();
println!("--- Embedded file: {}", embedded_file_name);
let folder_path = Path::new(&output_folder).join("PEAKS");
fs::create_dir_all(&folder_path)?;
path = Path::new(&folder_path).join(embedded_file_name);
msg = "to PEAKS";
} else {
path = Path::new(&output_folder).join("PEAKS.bin");
msg = "to PEAKS.bin";
}
} else {
path = Path::new(&output_folder).join(filename);
msg = "file";
}
fs::create_dir_all(&output_folder)?;
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)?;
file.seek(SeekFrom::Start(dest_offset as u64))?;
file.write_all(&out_data[source_offset as usize..])?;
println!("--- Saved {}!", msg);
} else {
let out_data = decrypted_data;
if filename.ends_with(".TXT") {
println!("{}", String::from_utf8_lossy(&out_data));
} else {
let path = Path::new(&output_folder).join(filename);
fs::create_dir_all(&output_folder)?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.open(path)?;
file.write_all(&out_data)?;
println!("-- Saved file!");
}
}
offset += size;
}
println!("\nExtraction finished!");
Ok(())
}
+11 -24
View File
@@ -9,9 +9,7 @@ use crate::common;
pub fn is_tpv_timg_file(file: &File) -> bool {
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
let header_string = String::from_utf8_lossy(&header);
if header_string == "TIMG"{
if header == b"TIMG" {
true
} else {
false
@@ -27,8 +25,7 @@ fn decompress_gzip(compressed_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error
pub fn extract_tpv_timg(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
//TIMG header
let _timg = common::read_exact(&mut file, 288)?;
let _timg = common::read_exact(&mut file, 288)?; //TIMG magic + header
loop {
//PIMG
@@ -39,48 +36,39 @@ pub fn extract_tpv_timg(mut file: &File, output_folder: &str) -> Result<(), Box<
assert!(&pimg == b"PIMG", "Invalid PIMG section!");
}
//4 bytes 00
let _ = common::read_exact(&mut file, 4)?;
let _ = common::read_exact(&mut file, 4)?; //4 bytes 00
//4 bytes size
let size_bytes = common::read_exact(&mut file, 4)?;
let size = u32::from_le_bytes(size_bytes.try_into().unwrap());
//4 bytes nothing
let _ = common::read_exact(&mut file, 4)?;
let _ = common::read_exact(&mut file, 4)?; //4 bytes 00
//16 bytes checksum? or maybe signature
let _checksum = common::read_exact(&mut file, 16)?;
let _checksum = common::read_exact(&mut file, 16)?; //16 bytes checksum? or maybe signature
//16 bytes name
let name_bytes = common::read_exact(&mut file, 16)?;
let name = common::string_from_bytes(&name_bytes);
//64 bytes destination device
let dev_bytes = common::read_exact(&mut file, 64)?;
let dev = common::string_from_bytes(&dev_bytes);
//16 bytes compression type
let comp_bytes = common::read_exact(&mut file, 16)?;
let comp_type = common::string_from_bytes(&comp_bytes);
let _ = common::read_exact(&mut file, 1032)?; //1032 bytes maybe comment? skip this
//1032 bytes maybe comment? skip this
let _ = common::read_exact(&mut file, 1032)?;
//actual data
let data = common::read_exact(&mut file, size as usize)?;
println!("- PIMG: Name: {} Size: {} Dest: {} Compression: {}", name, size, dev, comp_type);
println!("\nPIMG: Name: {}, Size: {}, Dest: {}, Compression: {}", name, size, dev, comp_type);
let out_data;
if comp_type == "gzip" {
println!("-- Decompressing gzip...");
println!("- Decompressing gzip...");
out_data = decompress_gzip(&data)?;
} else if comp_type == "none" {
out_data = data;
} else {
println!("-- Warning: unsupported compression type!");
println!("- Warning: unsupported compression type!");
out_data = data;
}
@@ -97,8 +85,7 @@ pub fn extract_tpv_timg(mut file: &File, output_folder: &str) -> Result<(), Box<
println!("-- Saved file!");
}
println!();
println!("Extraction finished!");
println!("\nExtraction finished!");
Ok(())
}
+9 -10
View File
@@ -12,7 +12,7 @@ struct Args {
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("unixtract Firmware extractor v0.0");
println!("unixtract Firmware extractor");
let args = Args::parse();
let target_path = args.input_target;
@@ -24,8 +24,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = PathBuf::from(target_path);
if path.is_dir() {
if formats::samsung_old::is_samsung_old_dir(&path) {
println!("Samsung old firmware dir detected!");
println!();
println!("Samsung old firmware dir detected!\n");
formats::samsung_old::extract_samsung_old(&path, &output_path)?
} else {
println!("Input format not recognized!");
@@ -35,29 +34,29 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!();
if formats::msd10::is_msd10_file(&file) {
if formats::sddl_sec::is_sddl_sec_file(&file) {
println!("SDDL.SEC file detected!");
formats::sddl_sec::extract_sddl_sec(&file, &output_path)?;
} else if formats::msd10::is_msd10_file(&file) {
println!("MSD10 file detected!");
println!();
formats::msd10::extract_msd10(&file, &output_path)?;
} else if formats::msd11::is_msd11_file(&file) {
println!("MSD11 file detected!");
formats::msd11::extract_msd11(&file, &output_path)?;
} else if formats::tpv_timg::is_tpv_timg_file(&file) {
println!("TPV TIMG file detected!");
println!();
formats::tpv_timg::extract_tpv_timg(&file, &output_path)?;
} else if formats::novatek::is_novatek_file(&file) {
println!("Novatek file detected!");
println!();
formats::novatek::extract_novatek(&file, &output_path)?;
} else if formats::epk1::is_epk1_file(&file) {
println!("EPK1 file detected!");
println!();
formats::epk1::extract_epk1(&file, &output_path)?;
} else if formats::pfl_upg::is_pfl_upg_file(&file) {
println!("PFL UPG file detected!");
println!();
formats::pfl_upg::extract_pfl_upg(&file, &output_path)?;
} else if formats::mstar::is_mstar_file(&file) {
println!("Mstar upgrade file detected!");
println!();
formats::mstar::extract_mstar(&file, &output_path)?;
} else {
println!("Input format not recognized!");