From 6e8fd5f0ccb32de48d394a513cfb8a57a8c4dccd Mon Sep 17 00:00:00 2001 From: theubusu <80545678+theubusu@users.noreply.github.com> Date: Sat, 27 Sep 2025 00:31:18 +0200 Subject: [PATCH] WIP pfl upg extractor --- Cargo.lock | 280 +++++++++++++++++++++++++++++ Cargo.toml | 6 +- src/common.rs | 13 +- src/{formats/mod.rs => formats.rs} | 3 +- src/formats/pfl_upg.rs | 121 +++++++++++++ src/formats/tpv_timg.rs | 38 ++-- src/main.rs | 9 +- 7 files changed, 441 insertions(+), 29 deletions(-) rename src/{formats/mod.rs => formats.rs} (51%) create mode 100644 src/formats/pfl_upg.rs diff --git a/Cargo.lock b/Cargo.lock index fdf16c5..6ee2840 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + [[package]] name = "block-buffer" version = "0.10.4" @@ -174,6 +186,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -217,6 +235,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "digest" version = "0.10.7" @@ -224,9 +253,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", ] +[[package]] +name = "ecb" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" +dependencies = [ + "cipher", +] + [[package]] name = "find-msvc-tools" version = "0.1.1" @@ -253,12 +292,29 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "inout" version = "0.1.4" @@ -275,12 +331,27 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "lz4" version = "1.28.1" @@ -325,12 +396,98 @@ dependencies = [ "adler2", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "once_cell_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -349,6 +506,55 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "sha1" version = "0.10.6" @@ -366,12 +572,50 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.106" @@ -400,12 +644,16 @@ name = "unixtract" version = "0.1.0" dependencies = [ "aes", + "block-padding", "cbc", "clap", + "ecb", "flate2", + "hex", "lz4", "lzma-rs", "md5", + "rsa", "sha1", ] @@ -421,6 +669,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "windows-link" version = "0.1.3" @@ -500,3 +754,29 @@ name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 51bd7a9..6cd5bb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,8 @@ aes = "0.8.4" cbc = "0.1.2" md5 = "0.7" sha1 = "0.10" -flate2 = "1.0" \ No newline at end of file +flate2 = "1.0" +rsa = { version = "0.9", features = ["hazmat"] } +hex = "0.4" +ecb = "0.1" +block-padding = "0.3" \ No newline at end of file diff --git a/src/common.rs b/src/common.rs index 2ff5fc7..2a00d67 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,5 +1,5 @@ use std::fs::{File}; -use std::io::{Read, Seek, SeekFrom}; +use std::io::{self, Read, Seek, SeekFrom}; pub fn read_file(mut file: &File, offset: u64, size: usize) -> Result, Box> { file.seek(SeekFrom::Start(offset))?; @@ -9,4 +9,15 @@ pub fn read_file(mut file: &File, offset: u64, size: usize) -> Result, B // reset seek (! file.seek(SeekFrom::Start(offset))?; Ok(buffer) +} + +pub fn read_exact(reader: &mut R, size: usize) -> io::Result> { + let mut buf = vec![0u8; size]; + reader.read_exact(&mut buf)?; + Ok(buf) +} + +pub fn string_from_bytes(buf: &[u8]) -> String { + let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len()); + String::from_utf8_lossy(&buf[..end]).to_string() } \ No newline at end of file diff --git a/src/formats/mod.rs b/src/formats.rs similarity index 51% rename from src/formats/mod.rs rename to src/formats.rs index 990ad56..541440c 100644 --- a/src/formats/mod.rs +++ b/src/formats.rs @@ -1,3 +1,4 @@ pub mod mstar; pub mod samsung_old; -pub mod tpv_timg; \ No newline at end of file +pub mod tpv_timg; +pub mod pfl_upg; \ No newline at end of file diff --git a/src/formats/pfl_upg.rs b/src/formats/pfl_upg.rs new file mode 100644 index 0000000..4daf096 --- /dev/null +++ b/src/formats/pfl_upg.rs @@ -0,0 +1,121 @@ +use std::fs::{File}; +use rsa::{RsaPublicKey, BigUint}; +use hex::decode; +use std::io::Cursor; + +use aes::Aes256; +use ecb::cipher::{BlockDecryptMut, KeyInit}; +use ecb::Decryptor; +use block_padding::NoPadding; + +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"{ + true + } else { + false + } +} + +fn decrypt_aes256_ecb_no_padding(key: &[u8], ciphertext: &[u8]) -> Result, Box>{ + + let mut cipher = Decryptor::::new_from_slice(key)?; + let mut buf = ciphertext.to_vec(); + + // decrypt in place + for block in buf.chunks_mut(16) { + cipher.decrypt_block_mut(block.into()); + } + + Ok(buf) +} + +pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box> { + //2SWU3TXV magic + let _ = common::read_exact(&mut file, 8)?; + + //header size + let _ = common::read_exact(&mut file, 4)?; + + //data size + let data_size_bytes = common::read_exact(&mut file, 4)?; + let data_size = u32::from_le_bytes(data_size_bytes.try_into().unwrap()); + println!("Data size: {}", data_size); + + //crc32 + let _ = common::read_exact(&mut file, 4)?; + + //mask?? + let _ = common::read_exact(&mut file, 4)?; + + //data size decompressed + let _ = common::read_exact(&mut file, 4)?; + + //padding2? + let _ = common::read_exact(&mut file, 4)?; + + //description + let description_bytes = common::read_exact(&mut file, 512)?; + let description = common::string_from_bytes(&description_bytes); + println!("Description: \n{}", description); + + //signature + let signature = common::read_exact(&mut file, 128)?; + + //unknown + let _ = common::read_exact(&mut file, 32)?; + + //version string + let version_bytes = common::read_exact(&mut file, 28)?; + let version = common::string_from_bytes(&version_bytes); + println!("Version: {}", version); + + //get aes key + //QF1EU + let n_hex = "ACD684155C7CCCB04372A8808514489FA9EE75D305987D1337420241FDBE0AE1F7CDFBB931C9D56C91D36F2CE79D222695B484FF42BCA12CE362C7C9ABBDEEC8E5D6107FADCF2D4DA5DF0693E13ACE54A18AEB21C051F6B62C075A1791985547C1CFF4FB5B6EA7E0A9405A1B2BB71EB89A9B209E0F62BF9794D673179C0E60F1"; + let e_hex = "010001"; + + let n = BigUint::from_bytes_be(&decode(n_hex)?); + let e = BigUint::from_bytes_be(&decode(e_hex)?); + let pubkey = RsaPublicKey::new(n, e)?; + + let signature_int = BigUint::from_bytes_le(&signature); + + let decrypted_int = rsa::hazmat::rsa_encrypt(&pubkey, &signature_int)?; + let decrypted = decrypted_int.to_bytes_le(); + + let aes_key = &decrypted[20..52]; + println!("AES key: {}", hex::encode(aes_key)); + //end get aes key + + let encrypted_data = common::read_exact(&mut file, data_size as usize)?; + println!("Decrypting data..."); + let decrypted_data = decrypt_aes256_ecb_no_padding(aes_key, &encrypted_data)?; + + let mut data_reader = Cursor::new(decrypted_data); + + while (data_reader.position() as usize) < data_reader.get_ref().len() { + //file header + let file_header = common::read_exact(&mut data_reader, 76)?; + + let file_name = common::string_from_bytes(&file_header[0..60]); + println!("File: {}", file_name); + + let real_size = u32::from_le_bytes(file_header[60..64].try_into().unwrap()); + println!("- Real size: {}", real_size); + + let stored_size = u32::from_le_bytes(file_header[64..68].try_into().unwrap()); + println!("- Stored size: {}", stored_size); + + let header_size = u32::from_le_bytes(file_header[68..72].try_into().unwrap()); + println!("- Header size: {}", header_size); + + let _data = common::read_exact(&mut data_reader, stored_size as usize)?; + } + + Ok(()) +} \ No newline at end of file diff --git a/src/formats/tpv_timg.rs b/src/formats/tpv_timg.rs index 4b8b54d..d3b2edd 100644 --- a/src/formats/tpv_timg.rs +++ b/src/formats/tpv_timg.rs @@ -1,6 +1,6 @@ use std::str; use std::path::{Path}; -use std::io::{self, Read, Write}; +use std::io::{Read, Write}; use std::fs::{self, File, OpenOptions}; use flate2::read::GzDecoder; @@ -25,20 +25,10 @@ fn decompress_gzip(compressed_data: &[u8]) -> Result, Box(reader: &mut R, size: usize) -> io::Result> { - let mut buf = vec![0u8; size]; - reader.read_exact(&mut buf)?; - Ok(buf) -} - -fn string_from_bytes(buf: &[u8]) -> String { - let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len()); - String::from_utf8_lossy(&buf[..end]).to_string() -} pub fn extract_tpv_timg(mut file: &File, output_folder: &str) -> Result<(), Box> { //TIMG header - let _timg = read_exact(&mut file, 288)?; + let _timg = common::read_exact(&mut file, 288)?; loop { //PIMG @@ -50,35 +40,35 @@ pub fn extract_tpv_timg(mut file: &File, output_folder: &str) -> Result<(), Box< } //4 bytes 00 - let _ = read_exact(&mut file, 4)?; + let _ = common::read_exact(&mut file, 4)?; //4 bytes size - let size_bytes = read_exact(&mut file, 4)?; + let size_bytes = common::read_exact(&mut file, 4)?; let size = u32::from_le_bytes(size_bytes.try_into().unwrap()); //4 bytes nothing - let _ = read_exact(&mut file, 4)?; + let _ = common::read_exact(&mut file, 4)?; //16 bytes checksum? or maybe signature - let _checksum = read_exact(&mut file, 16)?; + let _checksum = common::read_exact(&mut file, 16)?; //16 bytes name - let name_bytes = read_exact(&mut file, 16)?; - let name = string_from_bytes(&name_bytes); + let name_bytes = common::read_exact(&mut file, 16)?; + let name = common::string_from_bytes(&name_bytes); //64 bytes destination device - let dev_bytes = read_exact(&mut file, 64)?; - let dev = string_from_bytes(&dev_bytes); + let dev_bytes = common::read_exact(&mut file, 64)?; + let dev = common::string_from_bytes(&dev_bytes); //16 bytes compression type - let comp_bytes = read_exact(&mut file, 16)?; - let comp_type = string_from_bytes(&comp_bytes); + let comp_bytes = common::read_exact(&mut file, 16)?; + let comp_type = common::string_from_bytes(&comp_bytes); //1032 bytes maybe comment? skip this - let _ = read_exact(&mut file, 1032)?; + let _ = common::read_exact(&mut file, 1032)?; //actual data - let data = read_exact(&mut file, size as usize)?; + let data = common::read_exact(&mut file, size as usize)?; println!("PIMG - Name: {} Size: {} Dest: {} Compression: {}", name, size, dev, comp_type); diff --git a/src/main.rs b/src/main.rs index c77ab43..e3c0f8c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ +mod common; +mod formats; + use clap::Parser; use std::path::{PathBuf}; use std::fs::{File}; -mod formats; -mod common; #[derive(Parser, Debug)] struct Args { @@ -38,6 +39,10 @@ fn main() -> Result<(), Box> { println!("TPV TIMG file detected!"); println!(); formats::tpv_timg::extract_tpv_timg(&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!();