WIP pfl upg extractor

This commit is contained in:
theubusu
2025-09-27 00:31:18 +02:00
parent 81fd148cb8
commit 6e8fd5f0cc
7 changed files with 441 additions and 29 deletions
+12 -1
View File
@@ -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<Vec<u8>, Box<dyn std::error::Error>> {
file.seek(SeekFrom::Start(offset))?;
@@ -9,4 +9,15 @@ pub fn read_file(mut file: &File, offset: u64, size: usize) -> Result<Vec<u8>, B
// reset seek (!
file.seek(SeekFrom::Start(offset))?;
Ok(buffer)
}
pub fn read_exact<R: Read>(reader: &mut R, size: usize) -> io::Result<Vec<u8>> {
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()
}
+2 -1
View File
@@ -1,3 +1,4 @@
pub mod mstar;
pub mod samsung_old;
pub mod tpv_timg;
pub mod tpv_timg;
pub mod pfl_upg;
+121
View File
@@ -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<Vec<u8>, Box<dyn std::error::Error>>{
let mut cipher = Decryptor::<Aes256>::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<dyn std::error::Error>> {
//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(())
}
+14 -24
View File
@@ -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<Vec<u8>, Box<dyn std::error
Ok(decompressed)
}
fn read_exact<R: Read>(reader: &mut R, size: usize) -> io::Result<Vec<u8>> {
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<dyn std::error::Error>> {
//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);
+7 -2
View File
@@ -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<dyn std::error::Error>> {
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!();