diff --git a/Cargo.lock b/Cargo.lock index 097faef..9e56f42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index e0a73b9..758963e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" \ No newline at end of file +liblzma = "0.4.5" +des = "0.8.1" \ No newline at end of file diff --git a/src/formats.rs b/src/formats.rs index 268653a..3120a78 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -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 { 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, diff --git a/src/formats/sdboot/include.rs b/src/formats/sdboot/include.rs new file mode 100644 index 0000000..998f855 --- /dev/null +++ b/src/formats/sdboot/include.rs @@ -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 + } +} \ No newline at end of file diff --git a/src/formats/sdboot/mod.rs b/src/formats/sdboot/mod.rs new file mode 100644 index 0000000..097b540 --- /dev/null +++ b/src/formats/sdboot/mod.rs @@ -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>, Box> { + 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, key: &KeyEntry) -> Result, Box> { + 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) -> Result<(), Box> { + 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 = 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 = 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(()) +} \ No newline at end of file diff --git a/src/formats/sddl_sec/include.rs b/src/formats/sddl_sec/include.rs index 6c29ade..8f5f61c 100644 --- a/src/formats/sddl_sec/include.rs +++ b/src/formats/sddl_sec/include.rs @@ -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 { let len_ = s.len(); @@ -43,6 +48,84 @@ pub fn decipher(s: &[u8]) -> Vec { out } +pub fn decrypt_3des(encrypted_data: &[u8], key_entry: &DesKeyEntry) -> Result, Box> { + let mut data = encrypted_data.to_vec(); + let decryptor = Decryptor::::new_from_slices(&key_entry.key, &key_entry.iv).unwrap(); + + let out_data = decryptor.decrypt_padded_mut::(&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, Box> { + 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, -]; \ No newline at end of file +} \ No newline at end of file diff --git a/src/formats/sddl_sec/mod.rs b/src/formats/sddl_sec/mod.rs index 99eabf0..6658d7b 100644 --- a/src/formats/sddl_sec/mod.rs +++ b/src/formats/sddl_sec/mod.rs @@ -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>, Bo } } -fn get_sec_file(mut file: &File) -> Result<(FileHeader, Vec), Box> { - 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), Box> { + //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) -> 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 = 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) -> 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) -> Result<(), let mut final_out_path: Option = 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) -> 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) -> 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)?;