From 5d1e0ca908119a204fecb633b1cd233dbf21072b Mon Sep 17 00:00:00 2001 From: jujuforce Date: Wed, 16 Jul 2025 15:49:11 +0000 Subject: [PATCH] feat: exfat content extraction (#1) Co-authored-by: jujuforce Co-authored-by: beerpsi Reviewed-on: https://gitea.tendokyu.moe/beerpsi/fsdecrypt/pulls/1 Co-authored-by: jujuforce Co-committed-by: jujuforce --- Cargo.lock | 241 +++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/main.rs | 63 ++++++++++++-- 3 files changed, 297 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4824116..6942941 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aes" @@ -19,6 +19,21 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "bytemuck", +] + [[package]] name = "block-padding" version = "0.3.3" @@ -28,6 +43,26 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cbc" version = "0.1.2" @@ -43,6 +78,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "checked_num" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb5a345c406bf67f62bd14f284c9cb041ce5107725c22e665a163c871a4fb7b" +dependencies = [ + "num-traits", +] + [[package]] name = "cipher" version = "0.4.4" @@ -94,12 +138,130 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "endify" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "412c0c5048548a61a2ed94233a7305d2dfa4d1d3acc69039da77efa84e51df21" +dependencies = [ + "endify-derive", +] + +[[package]] +name = "endify-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a204d4aa704d0a74ee3bebccc65f24bca0cdef2588f08eaafda467cd4d3c8a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enumeric" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2f9bd649bd8c884aa518a798861fd111699dfc7d7dcc193bc2b658c8b9188d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "exfat-fs" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbdfa8ecf36ab2341032974d3644a56f1e40c9da3b643118492cf97f357f3c5" +dependencies = [ + "bitflags", + "bytemuck", + "checked_num", + "derive_builder", + "endify", + "enumeric", + "thiserror", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fsdecrypt" version = "0.1.1" @@ -108,6 +270,7 @@ dependencies = [ "anyhow", "cbc", "crc32fast", + "exfat-fs", "hex-literal", "indicatif", ] @@ -128,6 +291,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indicatif" version = "0.17.8" @@ -172,6 +341,15 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -184,12 +362,73 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + [[package]] name = "unicode-width" version = "0.1.13" diff --git a/Cargo.toml b/Cargo.toml index fd5fd16..f8966b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ cbc = "0.1.2" crc32fast = "1.4.2" hex-literal = "0.4.1" indicatif = "0.17.8" +exfat-fs = "0.1.3" diff --git a/src/main.rs b/src/main.rs index 09bf4ff..62381a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ use std::{ - fs::File, + fs::{create_dir_all, File}, io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write}, path::Path, }; +use exfat_fs::dir::{entry::fs::FsElement, Root}; + use aes::{ cipher::{block_padding::NoPadding, BlockDecryptMut, InnerIvInit, KeyInit, KeyIvInit}, Aes128Dec, @@ -21,6 +23,43 @@ mod crypto; const PAGE_SIZE: u64 = 4096; +fn extract_exfat_contents(exfat_path: &Path) -> Result<()> { + println!("Extracting contents of {}", exfat_path.display()); + + let file = File::open(exfat_path)?; + let mut root = Root::open(file)?; + + // Create output directory with same name as exfat file (without extension) + let output_dir = exfat_path.with_extension(""); + + create_dir_all(&output_dir)?; + extract_fs_elements(root.items(), &output_dir)?; + + Ok(()) +} + +fn extract_fs_elements(elements: &mut [FsElement], output_dir: &Path) -> Result<()> { + for element in elements { + match element { + FsElement::F(ref mut file) => { + let dest_path = output_dir.join(file.name()); + let mut dest = File::create(dest_path)?; + + std::io::copy(file, &mut dest)?; + } + FsElement::D(directory) => { + let dest_path = output_dir.join(directory.name()); + create_dir_all(&dest_path)?; + + let mut children = directory.open()?; + extract_fs_elements(&mut children, &dest_path)?; + } + } + } + + Ok(()) +} + fn main() -> Result<()> { let args = std::env::args().collect::>(); @@ -83,11 +122,7 @@ fn main() -> Result<()> { let data_offset = bootid.header_block_count * bootid.block_size; let key = keys.key; - let iv = if bootid.use_custom_iv { - None - } else { - keys.iv - }; + let iv = if bootid.use_custom_iv { None } else { keys.iv }; let iv = match iv { Some(iv) => iv, None => { @@ -160,7 +195,7 @@ fn main() -> Result<()> { .template("{prefix} [{bar:20!.bright.yellow/dim.white}] {bytes:>8} [{elapsed}<{eta}, {bytes_per_sec}]")? ); - pb.set_prefix(output_filename); + pb.set_prefix(output_filename.clone()); reader.seek(SeekFrom::Start(data_offset))?; for _ in 0..output_size / PAGE_SIZE { @@ -184,6 +219,20 @@ fn main() -> Result<()> { writer.flush()?; pb.finish(); + // Extract exfat contents if this is an exfat file + if bootid.container_type == ContainerType::OPTION + && output_path.extension().unwrap_or_default() == "exfat" + { + if let Err(e) = extract_exfat_contents(&output_path) { + println!("WARNING: Failed to extract exfat contents: {e:#?}"); + } else { + println!("Extracted exfat contents: {:?}", output_path); + println!("Deleting exfat file: {:?}", output_path); + + std::fs::remove_file(output_path)?; + } + } + page.clear(); page_iv.fill(0); bootid_bytes.fill(0);