mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
update sddl_sec to sddl_dec 7.0 version and add Sdboot as sep. format
This commit is contained in:
Generated
+10
@@ -311,6 +311,15 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "des"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -913,6 +922,7 @@ dependencies = [
|
||||
"bzip2",
|
||||
"cbc",
|
||||
"clap",
|
||||
"des",
|
||||
"ecb",
|
||||
"flate2",
|
||||
"hex",
|
||||
|
||||
+2
-1
@@ -22,4 +22,5 @@ minilzo-rs = "0.6.1"
|
||||
prost = "0.14.1"
|
||||
prost-types = "0.14.1"
|
||||
bzip2 = "0.6.1"
|
||||
liblzma = "0.4.5"
|
||||
liblzma = "0.4.5"
|
||||
des = "0.8.1"
|
||||
@@ -26,6 +26,7 @@ pub mod pana_dvd;
|
||||
pub mod android_ota_payload;
|
||||
pub mod bdl;
|
||||
pub mod amlogic;
|
||||
pub mod sdboot;
|
||||
|
||||
pub mod pup;
|
||||
|
||||
@@ -72,6 +73,11 @@ pub fn get_registry() -> Vec<Format> {
|
||||
detector_func: crate::formats::sddl_sec::is_sddl_sec_file,
|
||||
extractor_func: crate::formats::sddl_sec::extract_sddl_sec,
|
||||
},
|
||||
Format {
|
||||
name: "sdboot",
|
||||
detector_func: crate::formats::sdboot::is_sdboot_file,
|
||||
extractor_func: crate::formats::sdboot::extract_sdboot,
|
||||
},
|
||||
Format {
|
||||
name: "novatek",
|
||||
detector_func: crate::formats::novatek::is_novatek_file,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
use crate::utils::common::{string_from_bytes};
|
||||
use binrw::BinRead;
|
||||
use crate::formats::sddl_sec::include::AesKeyEntry;
|
||||
|
||||
//These keys seem to be the same for most models
|
||||
pub const KEYS: [AesKeyEntry; 2] = [
|
||||
//from tbl_sdboot;0, Decrypted with AES
|
||||
AesKeyEntry {
|
||||
key: [0x2e, 0x2a, 0x33, 0x62, 0x33, 0xe5, 0x5a, 0xba, 0xf5, 0xff, 0xec, 0x54, 0xf8, 0xab, 0x71, 0x25 ],
|
||||
iv: [0x2c, 0xa4, 0xb4, 0x7a, 0xff, 0xcb, 0x1a, 0xe8, 0xe1, 0xea, 0x2d, 0x9e, 0xf5, 0x12, 0x62, 0x9a]
|
||||
},
|
||||
|
||||
//from tbl_sdboot;1, Decrypted with AES
|
||||
AesKeyEntry {
|
||||
key: [0x24, 0x5e, 0x8d, 0xe8, 0xf4, 0x99, 0xb0, 0xf9, 0x6e, 0xc1, 0x55, 0xb6, 0x08, 0xe2, 0x42, 0xf3],
|
||||
iv: [0x3e, 0x8f, 0x29, 0xd4, 0xba, 0xe7, 0x76, 0xa5, 0x18, 0xa7, 0xb6, 0x3c, 0x42, 0xca, 0x1b, 0x43]
|
||||
}
|
||||
];
|
||||
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct SdbootSecHeader {
|
||||
num_files_str_bytes: [u8; 4],
|
||||
key_id_str_bytes: [u8; 4],
|
||||
_unused: [u8; 24],
|
||||
}
|
||||
|
||||
impl SdbootSecHeader {
|
||||
pub fn num_files(&self) -> u32 {
|
||||
let string = string_from_bytes(&self.num_files_str_bytes);
|
||||
string.parse().unwrap()
|
||||
}
|
||||
pub fn key_id(&self) -> u16 {
|
||||
let string = string_from_bytes(&self.key_id_str_bytes);
|
||||
string.parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FileEntry {
|
||||
pub name: String,
|
||||
pub size: usize,
|
||||
pub offset: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct SdbootEntryHeader {
|
||||
file_name_bytes: [u8; 0x34],
|
||||
file_size_str_bytes: [u8; 0xc],
|
||||
}
|
||||
impl SdbootEntryHeader {
|
||||
pub fn name(&self) -> String {
|
||||
string_from_bytes(&self.file_name_bytes)
|
||||
}
|
||||
pub fn file_size(&self) -> usize {
|
||||
let string = string_from_bytes(&self.file_size_str_bytes);
|
||||
string.parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct EntrySubHeader {
|
||||
size_str_bytes: [u8; 0xc],
|
||||
_unused: [u8; 20],
|
||||
}
|
||||
impl EntrySubHeader {
|
||||
pub fn size(&self) -> usize {
|
||||
let string = string_from_bytes(&self.size_str_bytes);
|
||||
string.parse().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct InfoListHeader {
|
||||
pub part_count: u16,
|
||||
_unk1: u32,
|
||||
_unk2: u32,
|
||||
_unk3: u32,
|
||||
_unk4: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct InfoListEntry {
|
||||
_crc: u32,
|
||||
pub out_size: u32,
|
||||
_stored_size: u32,
|
||||
pub compressed_flag: u8,
|
||||
pub ciphered_flag: u8,
|
||||
_unused: u16,
|
||||
}
|
||||
impl InfoListEntry {
|
||||
pub fn is_compressed(&self) -> bool {
|
||||
self.compressed_flag == 0x01
|
||||
}
|
||||
pub fn is_ciphered(&self) -> bool {
|
||||
self.ciphered_flag == 0x01
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
mod include;
|
||||
|
||||
use std::any::Any;
|
||||
use crate::AppContext;
|
||||
use std::path::Path;
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{Cursor, Seek, SeekFrom, Write};
|
||||
use std::collections::HashSet;
|
||||
use binrw::BinReaderExt;
|
||||
|
||||
use crate::utils::common;
|
||||
use crate::formats::sddl_sec::include::*;
|
||||
use crate::utils::compression::decompress_zlib;
|
||||
use include::*;
|
||||
|
||||
pub fn is_sdboot_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 header = common::read_file(&file, 0, 32).expect("Failed to read from file.");
|
||||
let deciph_header = decipher(&header);
|
||||
|
||||
let chk = deciph_header.iter().all(|&b| {
|
||||
matches!(b,
|
||||
b'0'..=b'9'
|
||||
)
|
||||
});
|
||||
|
||||
if chk {
|
||||
Ok(Some(Box::new(())))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_file(mut in_file: &File, search_file_name: &str, file_list: &Vec<FileEntry>, key: &KeyEntry) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let file_idx = file_list.iter().position(|entry| entry.name == search_file_name)
|
||||
.ok_or_else(|| format!("Requested file {} was not found!", search_file_name))?;
|
||||
let entry = &file_list[file_idx];
|
||||
|
||||
in_file.seek(SeekFrom::Start(entry.offset))?;
|
||||
let enc_data = common::read_exact(&mut in_file, entry.size)?;
|
||||
let dec_data = KeyEntry::decrypt(key, &enc_data)?;
|
||||
let mut data_rdr = Cursor::new(dec_data);
|
||||
|
||||
let sub_hdr: EntrySubHeader = data_rdr.read_be()?;
|
||||
let data = common::read_exact(&mut data_rdr, sub_hdr.size() as usize)?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn extract_sdboot(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 mut secfile_hdr_reader = Cursor::new(decipher(&common::read_exact(&mut file, 32)?));
|
||||
let secfile_header: SdbootSecHeader = secfile_hdr_reader.read_be()?;
|
||||
|
||||
let key_id = secfile_header.key_id();
|
||||
if key_id != 0 && key_id != 1 {
|
||||
return Err(format!("Invalid sdboot key_id! got {} but must be 0 or 1", key_id).into());
|
||||
}
|
||||
let key: KeyEntry = KeyEntry::AES(KEYS[key_id as usize]);
|
||||
|
||||
println!("File info -\nKey ID: {}\nFile count: {}", secfile_header.key_id(), secfile_header.num_files());
|
||||
|
||||
//create file list
|
||||
let mut file_list: Vec<FileEntry> = Vec::new();
|
||||
for _i in 0..secfile_header.num_files() {
|
||||
let mut entry_header_reader = Cursor::new(KeyEntry::decrypt(&key, &common::read_exact(&mut file, 64)?)?);
|
||||
let entry_header: SdbootEntryHeader = entry_header_reader.read_be()?;
|
||||
|
||||
let offset = file.stream_position()?;
|
||||
//println!("File: {} - Offset: {}, Size: {}", entry_header.name(), offset, entry_header.file_size());
|
||||
|
||||
file_list.push( FileEntry { name: entry_header.name(), size: entry_header.file_size(), offset });
|
||||
file.seek(SeekFrom::Current(entry_header.file_size() as i64))?;
|
||||
}
|
||||
|
||||
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||
let mut processed_image_files: HashSet<String> = HashSet::new(); //so processed image files are not extracted later
|
||||
|
||||
//check for "IMGFILE.TXT" , if it exists, we will extract NAND/NOR images. (old route?)
|
||||
if let Ok(imgfile_data) = get_file(&file, "IMGFILE.TXT", &mut file_list, &key) {
|
||||
//example- nand: nandall.img
|
||||
let imgfile_text = common::string_from_bytes(&imgfile_data);
|
||||
let mut s = imgfile_text.trim_end().split(": ");
|
||||
let target = s.next().unwrap(); //"nand"
|
||||
let target_filename = s.next().unwrap(); //"nandall.img"
|
||||
|
||||
println!("\nSaving {} to {}...", target, target_filename);
|
||||
let output_path = Path::new(&app_ctx.output_dir).join(&target_filename);
|
||||
let mut out_file = OpenOptions::new().write(true).create(true).truncate(true).open(&output_path)?;
|
||||
|
||||
//get info file
|
||||
let infofile_name = format!("{}.inf", target_filename.split(".").next().unwrap()); //"nandall.inf"
|
||||
let infofile = get_file(&file, &infofile_name, &file_list, &key)?;
|
||||
let mut infofile_reader = Cursor::new(infofile);
|
||||
let info_header: InfoListHeader = infofile_reader.read_le()?;
|
||||
for i in 0..info_header.part_count {
|
||||
let part_entry: InfoListEntry = infofile_reader.read_le()?;
|
||||
println!("- ({}/{}) Size: {}, Compressed?: {}", i+1, info_header.part_count, part_entry.out_size, part_entry.is_compressed());
|
||||
|
||||
let part_file_name = format!("{}{:02x}", target_filename, i); //not sure what happens if it goes over 255
|
||||
let mut part_data = get_file(&file, &part_file_name, &file_list, &key)?;
|
||||
|
||||
if part_entry.is_ciphered() {
|
||||
println!("-- Deciphering...");
|
||||
part_data = decipher(&part_data);
|
||||
}
|
||||
|
||||
if part_entry.is_compressed() {
|
||||
println!("-- Decompressing...");
|
||||
part_data = decompress_zlib(&part_data)?;
|
||||
}
|
||||
|
||||
out_file.write_all(&part_data)?;
|
||||
processed_image_files.insert(part_file_name);
|
||||
}
|
||||
|
||||
println!("--- Saved file!");
|
||||
}
|
||||
|
||||
//extract the rest of the files
|
||||
for entry in file_list.iter() {
|
||||
if processed_image_files.contains(&entry.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("\nFile: {} - Size: {}", entry.name, entry.size);
|
||||
|
||||
let file_data = get_file(&file, &entry.name, &file_list, &key)?;
|
||||
let output_path = Path::new(&app_ctx.output_dir).join(&entry.name);
|
||||
let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?;
|
||||
out_file.write_all(&file_data)?;
|
||||
println!("- Saved file!");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
//sddl_dec 6.0 https://github.com/theubusu/sddl_dec
|
||||
use crate::utils::common;
|
||||
use binrw::BinRead;
|
||||
//base: sddl_dec 7.0 https://github.com/theubusu/sddl_dec
|
||||
use binrw::{BinRead};
|
||||
use aes::{cipher::block_padding::NoPadding};
|
||||
use cbc::{Decryptor, cipher::{BlockDecryptMut, KeyIvInit}};
|
||||
use des::{TdesEde3};
|
||||
|
||||
use crate::utils::common::{string_from_bytes};
|
||||
use crate::utils::aes::{decrypt_aes128_cbc_nopad, decrypt_aes128_cbc_pcks7};
|
||||
|
||||
pub fn decipher(s: &[u8]) -> Vec<u8> {
|
||||
let len_ = s.len();
|
||||
@@ -43,6 +48,84 @@ pub fn decipher(s: &[u8]) -> Vec<u8> {
|
||||
out
|
||||
}
|
||||
|
||||
pub fn decrypt_3des(encrypted_data: &[u8], key_entry: &DesKeyEntry) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let mut data = encrypted_data.to_vec();
|
||||
let decryptor = Decryptor::<TdesEde3>::new_from_slices(&key_entry.key, &key_entry.iv).unwrap();
|
||||
|
||||
let out_data = decryptor.decrypt_padded_mut::<NoPadding>(&mut data)
|
||||
.map_err(|e| format!("!!Decryption error!!: {:?}", e))?;
|
||||
|
||||
Ok(out_data.to_vec())
|
||||
}
|
||||
pub enum KeyEntry {
|
||||
DES(DesKeyEntry),
|
||||
AES(AesKeyEntry),
|
||||
AESPcks7(AesKeyEntry)
|
||||
}
|
||||
impl KeyEntry {
|
||||
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
match self {
|
||||
KeyEntry::DES(k) => decrypt_3des(data, &k),
|
||||
KeyEntry::AES(k) => decrypt_aes128_cbc_nopad(data, &k.key, &k.iv),
|
||||
KeyEntry::AESPcks7(k) => decrypt_aes128_cbc_pcks7(data, &k.key, &k.iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct AesKeyEntry {
|
||||
pub key: [u8; 16],
|
||||
pub iv: [u8; 16],
|
||||
}
|
||||
|
||||
pub struct DesKeyEntry {
|
||||
pub key: [u8; 24],
|
||||
pub iv: [u8; 8],
|
||||
}
|
||||
|
||||
//new type (2011+) always has this key, it is decipher()'ed from /usr/local/customer_dl/crypto_key
|
||||
pub const NEW_KEY: AesKeyEntry = AesKeyEntry {
|
||||
key: [0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB, 0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C],
|
||||
iv: [0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54, 0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66],
|
||||
};
|
||||
|
||||
//for old (pre-2011), can have multiple keys either AES or 3DES
|
||||
//these keys are stored in key tables with id 0 and 1 (this is what key_id in secfile hdr should decide, but it seems to be always 0), and are decrypted using the master DES key:
|
||||
//c2a421f6adeb44be b0fda68c234bb3c5 e98ec68c326fd395 07c8d75ef1b1b142
|
||||
//There are 3 known key tables - tbl2009 (From 2009 dlget), tbl2010 (From 2010 dlget) and tbl_sdboot (From bootloader)
|
||||
//the decrypted key is then used as AES or DES key for the contents
|
||||
pub const OLD_KEYS_AES: [AesKeyEntry; 2] = [
|
||||
//from tbl2009;0, Decrypted with DES
|
||||
//2009/ some 2010
|
||||
AesKeyEntry {
|
||||
key: [0xe5, 0x80, 0x3d, 0x1c, 0x23, 0x51, 0x16, 0xa4, 0xd0, 0xbc, 0x94, 0xad, 0x08, 0x92, 0xab, 0x29],
|
||||
iv: [0x26, 0x70, 0xe0, 0xf8, 0x0e, 0x2f, 0x8c, 0xef, 0xf8, 0x3e, 0xd9, 0x94, 0x8a, 0xf8, 0x34, 0xfd],
|
||||
},
|
||||
|
||||
//from tbl_sdboot;0, Decrypted with DES
|
||||
//2009/2010 Japan
|
||||
AesKeyEntry {
|
||||
key: [0xa2, 0x76, 0xd3, 0x75, 0x75, 0x02, 0x4a, 0xec, 0x52, 0x38, 0x3d, 0x97, 0x20, 0x8c, 0xc1, 0x7a],
|
||||
iv: [0x16, 0xa8, 0xf1, 0xef, 0xec, 0x26, 0x6e, 0x26, 0xd3, 0x79, 0x51, 0xa8, 0x1a, 0xdc, 0xf4, 0x0b],
|
||||
}
|
||||
];
|
||||
|
||||
pub const OLD_KEYS_DES: [DesKeyEntry; 2] = [
|
||||
//from tbl2010;0, Decrypted with DES
|
||||
//2010
|
||||
DesKeyEntry {
|
||||
key: [0x46, 0xd0, 0x26, 0x61, 0x0b, 0xc2, 0x9d, 0x32, 0x57, 0x16, 0x16, 0x92, 0x3b, 0xae, 0xc4, 0xc8, 0x0e, 0x91, 0xf2, 0xe9, 0x8a, 0xef, 0x02, 0x62],
|
||||
iv: [0x64, 0x3d, 0x0e, 0xa7, 0x3b, 0xa1, 0x19, 0x02],
|
||||
},
|
||||
|
||||
//from tbl_sdboot;0, Decrypted with DES
|
||||
//before 2009
|
||||
DesKeyEntry {
|
||||
key: [0xa2, 0x76, 0xd3, 0x75, 0x75, 0x02, 0x4a, 0xec, 0x52, 0x38, 0x3d, 0x97, 0x20, 0x8c, 0xc1, 0x7a, 0x16, 0xa8, 0xf1, 0xef, 0xec, 0x26, 0x6e, 0x26],
|
||||
iv: [0xd3, 0x79, 0x51, 0xa8, 0x1a, 0xdc, 0xf4, 0x0b]
|
||||
},
|
||||
];
|
||||
|
||||
// -- STRUCTURES --
|
||||
// -- SECFILE --
|
||||
|
||||
@@ -58,15 +141,15 @@ pub struct SecHeader {
|
||||
}
|
||||
impl SecHeader {
|
||||
pub fn key_id(&self) -> u32 {
|
||||
let string = common::string_from_bytes(&self.key_id_str_bytes);
|
||||
let string = string_from_bytes(&self.key_id_str_bytes);
|
||||
string.parse().unwrap()
|
||||
}
|
||||
pub fn grp_num(&self) -> u32 {
|
||||
let string = common::string_from_bytes(&self.grp_num_str_bytes);
|
||||
let string = string_from_bytes(&self.grp_num_str_bytes);
|
||||
string.parse().unwrap()
|
||||
}
|
||||
pub fn prg_num(&self) -> u32 {
|
||||
let string = common::string_from_bytes(&self.prg_num_str_bytes);
|
||||
let string = string_from_bytes(&self.prg_num_str_bytes);
|
||||
string.parse().unwrap()
|
||||
}
|
||||
}
|
||||
@@ -80,10 +163,10 @@ pub struct FileHeader {
|
||||
}
|
||||
impl FileHeader {
|
||||
pub fn name(&self) -> String {
|
||||
common::string_from_bytes(&self.name_str_bytes)
|
||||
string_from_bytes(&self.name_str_bytes)
|
||||
}
|
||||
pub fn size(&self) -> u64 {
|
||||
let string = common::string_from_bytes(&self.size_str_bytes);
|
||||
let string = string_from_bytes(&self.size_str_bytes);
|
||||
string.parse().unwrap()
|
||||
}
|
||||
}
|
||||
@@ -91,7 +174,7 @@ impl FileHeader {
|
||||
// -- MODULE --
|
||||
#[derive(Debug, BinRead)]
|
||||
pub struct ModuleComHeader { //"com_header"
|
||||
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
||||
pub _download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
||||
_outer_maker_id: u8,
|
||||
_outer_model_id: u8,
|
||||
_inner_maker_id: u8,
|
||||
@@ -188,25 +271,14 @@ pub struct TdiTgtInf {
|
||||
pub target_id: u8,
|
||||
_num_of_compatible_target: u8,
|
||||
pub num_of_txx: u16, //"TXX" refers to the ".FXX" segment files of each module. I assume F is an encrypted version of T, the same happens with SDIT; "TDI" -> "FDI"
|
||||
_unknown: [u8; 8],
|
||||
_module_path: [u8; 8],
|
||||
module_name_bytes: [u8; 8],
|
||||
}
|
||||
impl TdiTgtInf {
|
||||
pub fn module_name(&self) -> String {
|
||||
common::string_from_bytes(&self.module_name_bytes)
|
||||
string_from_bytes(&self.module_name_bytes)
|
||||
}
|
||||
pub fn version_string(&self) -> String {
|
||||
format!("{}.{}{}{}", self.new_version[0], self.new_version[1], self.new_version[2], self.new_version[3])
|
||||
}
|
||||
}
|
||||
|
||||
// -- dec key --
|
||||
pub static DEC_KEY: [u8; 16] = [
|
||||
0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB,
|
||||
0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C,
|
||||
];
|
||||
|
||||
pub static DEC_IV: [u8; 16] = [
|
||||
0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54,
|
||||
0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66,
|
||||
];
|
||||
}
|
||||
+97
-20
@@ -1,5 +1,5 @@
|
||||
//sddl_dec 6.0 https://github.com/theubusu/sddl_dec
|
||||
mod include;
|
||||
//base: sddl_dec 7.0 https://github.com/theubusu/sddl_dec
|
||||
pub mod include;
|
||||
mod util;
|
||||
use std::any::Any;
|
||||
use crate::AppContext;
|
||||
@@ -10,7 +10,8 @@ use std::io::{Cursor, Seek, SeekFrom, Write};
|
||||
use binrw::BinReaderExt;
|
||||
|
||||
use crate::utils::common;
|
||||
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
|
||||
use crate::utils::common::{string_from_bytes, read_exact};
|
||||
use crate::utils::aes::{decrypt_aes128_cbc_nopad, decrypt_aes128_cbc_pcks7};
|
||||
use crate::utils::compression::{decompress_zlib};
|
||||
use include::*;
|
||||
use util::split_peaks_file;
|
||||
@@ -26,10 +27,42 @@ pub fn is_sddl_sec_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Bo
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sec_file(mut file: &File) -> Result<(FileHeader, Vec<u8>), Box<dyn std::error::Error>> {
|
||||
let mut hdr_reader = Cursor::new(decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, 32)?, &DEC_KEY, &DEC_IV)?);
|
||||
fn get_sec_file(mut file: &File, key_entry: &KeyEntry) -> Result<(FileHeader, Vec<u8>), Box<dyn std::error::Error>> {
|
||||
//new type check because only new is Pcks7.. i know
|
||||
let new_type = match key_entry {
|
||||
KeyEntry::AESPcks7(_) => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
let dec_header = KeyEntry::decrypt(key_entry, &read_exact(&mut file, 32)?)?;
|
||||
let mut hdr_reader = Cursor::new(dec_header);
|
||||
let file_header: FileHeader = hdr_reader.read_be()?;
|
||||
let file_data = decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, file_header.size() as usize)?, &DEC_KEY, &DEC_IV)?;
|
||||
|
||||
let enc_size= if new_type {
|
||||
file_header.size() as usize
|
||||
} else {
|
||||
//extra ciphered data before encrypted data, prefixed by size, like "0021XXXXXX PEAKS.T00/12900002"
|
||||
//this counts into file size but not decrypt size
|
||||
let extra_size: usize = string_from_bytes(&read_exact(&mut file, 4)?).parse().unwrap();
|
||||
let _extra_data = read_exact(&mut file, extra_size)?;
|
||||
|
||||
file_header.size() as usize - (extra_size + 4)
|
||||
};
|
||||
|
||||
let dec_data = KeyEntry::decrypt(key_entry, &read_exact(&mut file, enc_size)?)?;
|
||||
let file_data = if new_type {
|
||||
dec_data
|
||||
} else {
|
||||
let mut data_rdr = Cursor::new(dec_data);
|
||||
|
||||
//extra info in enc data, like "0021XXXXXX PEAKS.T00/12900002000000571800"
|
||||
//part before size looks to be a duplicate of previous extra data, probably for signing purpose, size used for unpad
|
||||
let extra_size: usize = string_from_bytes(&read_exact(&mut data_rdr, 4)?).parse().unwrap();
|
||||
let _extra_data = read_exact(&mut data_rdr, extra_size + 4)?;
|
||||
|
||||
let data_size: usize = string_from_bytes(&read_exact(&mut data_rdr, 12)?).parse().unwrap();
|
||||
read_exact(&mut data_rdr, data_size)?
|
||||
};
|
||||
|
||||
Ok((file_header, file_data))
|
||||
}
|
||||
@@ -70,13 +103,57 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
|
||||
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||
let save_extra = app_ctx.has_option("sddl_sec:save_extra");
|
||||
|
||||
let mut secfile_hdr_reader = Cursor::new(decipher(&common::read_exact(&mut file, 32)?));
|
||||
let mut secfile_hdr_reader = Cursor::new(decipher(&read_exact(&mut file, 32)?));
|
||||
let secfile_header: SecHeader = secfile_hdr_reader.read_be()?;
|
||||
|
||||
println!("File info -\nKey ID: {}\nGroup count: {}\nModule file count: {}\n", secfile_header.key_id(), secfile_header.grp_num(), secfile_header.prg_num());
|
||||
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||
println!("File info -\nKey ID: {}\nGroup count: {}\nModule file count: {}", secfile_header.key_id(), secfile_header.grp_num(), secfile_header.prg_num());
|
||||
|
||||
let (tdi_file, tdi_data) = get_sec_file(&file)?;
|
||||
//by knowing that the first file is always SDIT.FDI, find key(and mode)
|
||||
let try_hdr = read_exact(&mut file, 0x20)?;
|
||||
|
||||
let mut key: Option<KeyEntry> = None;
|
||||
|
||||
//for new, key will always be the same
|
||||
if let Ok(dec) = decrypt_aes128_cbc_pcks7(&try_hdr, &NEW_KEY.key, &NEW_KEY.iv) {
|
||||
if dec.starts_with(TDI_FILENAME.as_bytes()) {
|
||||
println!("- New type detected\n");
|
||||
key = Some(KeyEntry::AESPcks7(NEW_KEY));
|
||||
}
|
||||
}
|
||||
//new did not match, try all old AES keys
|
||||
if key.is_none() {
|
||||
for key_entry in OLD_KEYS_AES {
|
||||
let dec = decrypt_aes128_cbc_nopad(&try_hdr, &key_entry.key, &key_entry.iv)?;
|
||||
if dec.starts_with(TDI_FILENAME.as_bytes()) {
|
||||
println!("- Old type detected with AES key={}, iv={}\n", hex::encode(key_entry.key) ,hex::encode(key_entry.iv));
|
||||
key = Some(KeyEntry::AES(key_entry));
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
//...old DES keys
|
||||
if key.is_none() {
|
||||
for key_entry in OLD_KEYS_DES {
|
||||
let dec = decrypt_3des(&try_hdr, &key_entry)?;
|
||||
if dec.starts_with(TDI_FILENAME.as_bytes()) {
|
||||
println!("- Old type detected with DES key={}, iv={}\n", hex::encode(key_entry.key) ,hex::encode(key_entry.iv));
|
||||
key = Some(KeyEntry::DES(key_entry));
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
//nothing matched, quit
|
||||
if key.is_none() {
|
||||
return Err("No matching key found!".into());
|
||||
}
|
||||
|
||||
// -- key search end
|
||||
|
||||
let key = key.unwrap();
|
||||
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||
file.seek(SeekFrom::Start(0x20))?;
|
||||
|
||||
let (tdi_file, tdi_data) = get_sec_file(&file, &key)?;
|
||||
println!("[TDI] Name: {}, Size: {}", tdi_file.name(), tdi_file.size());
|
||||
if save_extra { //Save SDIT
|
||||
let mut out_file = OpenOptions::new().write(true).create(true).open(Path::new(&app_ctx.output_dir).join(tdi_file.name()))?;
|
||||
@@ -90,7 +167,7 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
|
||||
|
||||
//get info files, each info file belongs to its respecitve group in the TDI
|
||||
for i in 0..secfile_header.grp_num() {
|
||||
let (info_file, info_data) = get_sec_file(&file)?;
|
||||
let (info_file, info_data) = get_sec_file(&file, &key)?;
|
||||
println!("\n[INFO] ID: {}, Name: {}, Size: {}", i, info_file.name(), info_file.size());
|
||||
if !info_file.name().ends_with(INFO_FILE_EXTENSION) {
|
||||
return Err(format!("Info file {} does not have the expected extension {}!", info_file.name(), INFO_FILE_EXTENSION).into());
|
||||
@@ -111,20 +188,20 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
|
||||
let mut final_out_path: Option<PathBuf> = None;
|
||||
|
||||
for i in 0..module.num_of_txx {
|
||||
let (module_file, module_data) = get_sec_file(&file)?;
|
||||
let (module_file, module_data) = get_sec_file(&file, &key)?;
|
||||
if !module_file.name().starts_with(&module.module_name()) {
|
||||
return Err(format!("Module file {} does not start with the module's name: {}!", module_file.name(), module.module_name()).into());
|
||||
}
|
||||
println!(" Segment #{}/{} - Name: {}, Size: {}", i+1, module.num_of_txx, module_file.name(), module_file.size());
|
||||
|
||||
let mut module_reader = Cursor::new(module_data);
|
||||
let com_header: ModuleComHeader = module_reader.read_be()?;
|
||||
if com_header.download_id != DOWNLOAD_ID {
|
||||
return Err("Invalid module com_header!".into());
|
||||
}
|
||||
let _com_header: ModuleComHeader = module_reader.read_be()?;
|
||||
//if com_header.download_id != DOWNLOAD_ID { it seems this can differ in some files
|
||||
// return Err("Invalid module com_header!".into());
|
||||
//}
|
||||
|
||||
let module_header: ModuleHeader = module_reader.read_be()?;
|
||||
let mut module_data = common::read_exact(&mut module_reader, module_header.cmp_size as usize)?;
|
||||
let mut module_data = read_exact(&mut module_reader, module_header.cmp_size as usize)?;
|
||||
if module_header.is_ciphered() {
|
||||
println!(" - Deciphering...");
|
||||
module_data = decipher(&module_data);
|
||||
@@ -140,7 +217,7 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
|
||||
|
||||
let output_path: PathBuf;
|
||||
if content_header.has_subfile() {
|
||||
let sub_filename_bytes = common::read_exact(&mut content_reader, 0x100)?;
|
||||
let sub_filename_bytes = read_exact(&mut content_reader, 0x100)?;
|
||||
let sub_filename = common::string_from_bytes(&sub_filename_bytes);
|
||||
println!(" --> {}", sub_filename);
|
||||
|
||||
@@ -153,8 +230,8 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
|
||||
}
|
||||
final_out_path = Some(output_path.clone());
|
||||
|
||||
let data = common::read_exact(&mut content_reader, content_header.size as usize)?;
|
||||
let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(&output_path)?;
|
||||
let data = read_exact(&mut content_reader, content_header.size as usize)?;
|
||||
let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?;
|
||||
out_file.seek(SeekFrom::Start(content_header.dest_offset() as u64))?;
|
||||
out_file.write_all(&data)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user