update sddl_sec to sddl_dec 7.0 version and add Sdboot as sep. format

This commit is contained in:
theubusu
2026-04-08 16:51:45 +02:00
parent 73709c87cd
commit a234eb6e49
7 changed files with 443 additions and 44 deletions
+96
View 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
}
}
+137
View File
@@ -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(())
}