mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-16 05:04:22 +02:00
Consistency cleanup + add MSD11 and SDDL.SEC extractors
This commit is contained in:
+3
-1
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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!");
|
||||
|
||||
Reference in New Issue
Block a user