diff --git a/src/formats.rs b/src/formats.rs index e6950be..42693bf 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -6,4 +6,5 @@ pub mod epk1; pub mod novatek; pub mod msd10; pub mod msd11; -pub mod sddl_sec; \ No newline at end of file +pub mod sddl_sec; +pub mod epk2; \ No newline at end of file diff --git a/src/formats/epk2.rs b/src/formats/epk2.rs new file mode 100644 index 0000000..8cf9c9e --- /dev/null +++ b/src/formats/epk2.rs @@ -0,0 +1,156 @@ +use std::fs::{self, File, OpenOptions}; +use std::path::{Path}; +use std::io::{Write, Seek, SeekFrom}; + +use aes::Aes128; +use ecb::{Decryptor, cipher::{BlockDecryptMut, KeyInit, generic_array::GenericArray}}; + +use crate::common; + +pub fn is_epk2_file(file: &File) -> bool { + let header = common::read_file(&file, 128, 4).expect("Failed to read from file."); + if header == b"epak" { + true + } else { + false + } +} + +struct Pak { + offset: u32, + size: u32, + name: String, +} + +type Aes128EcbDec = Decryptor; + +fn decrypt_aes128_ecb(key: &[u8], ciphertext: &[u8]) -> Result, Box> { + let key_array: [u8; 16] = key.try_into()?; + let mut decryptor = Aes128EcbDec::new(&key_array.into()); + let mut buffer = ciphertext.to_vec(); + + for chunk in buffer.chunks_exact_mut(16) { + let block: &mut [u8; 16] = chunk.try_into()?; + decryptor.decrypt_block_mut(GenericArray::from_mut_slice(block)); + } + + Ok(buffer) +} + +pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box> { + let _epak = common::read_exact(&mut file, 4)?; // epak magic + + let file_size_bytes = common::read_exact(&mut file, 4)?; + let file_size = u32::from_le_bytes(file_size_bytes.try_into().unwrap()); + + let pak_count_bytes = common::read_exact(&mut file, 4)?; + let pak_count = u32::from_le_bytes(pak_count_bytes.try_into().unwrap()); + + let _epk2 = common::read_exact(&mut file, 4)?; // EPK2 magic + + let version = common::read_exact(&mut file, 4)?; + + let ota_id_bytes = common::read_exact(&mut file, 32)?; + let ota_id = common::string_from_bytes(&ota_id_bytes); + + println!("\nEPK info:\nFile size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}\n", + file_size, pak_count, ota_id, version[3], version[2], version[1]); + + let mut paks: Vec = Vec::new(); + + for i in 0..pak_count { + let offset_bytes = common::read_exact(&mut file, 4)?; + let offset = u32::from_le_bytes(offset_bytes.try_into().unwrap()) + 128; //add 128 bytes of initial signature + + let size_bytes = common::read_exact(&mut file, 4)?; + let size = u32::from_le_bytes(size_bytes.try_into().unwrap()); + + let name_bytes = common::read_exact(&mut file, 4)?; + let name = common::string_from_bytes(&name_bytes); + + let _version = common::read_exact(&mut file, 4)?; + + let segment_size_bytes = common::read_exact(&mut file, 4)?; + let segment_size = u32::from_le_bytes(segment_size_bytes.try_into().unwrap()); + + println!("Pak {}: {}, offset: {}, size: {}, segment size: {}", i + 1, name, offset, size, segment_size); + + paks.push(Pak { offset, size, name }); + } + + // Saturn7/BCM3556 + let key = "2F2E2D2C2B2A29281716151413121110"; + let key_bytes = hex::decode(key)?; + + let mut signature_count = 0; + + for (pak_n, pak) in paks.iter().enumerate() { + + let actual_offset = pak.offset + (128 * signature_count); + + file.seek(SeekFrom::Start(actual_offset as u64))?; + + let _signature = common::read_exact(&mut file, 128)?; + signature_count += 1; + + let encrypted_header = common::read_exact(&mut file, 128)?; + let header = decrypt_aes128_ecb(&key_bytes, &encrypted_header)?; + + let segment_count = u32::from_le_bytes(header[84..88].try_into().unwrap()); + let mut segment_size = u32::from_le_bytes(header[88..92].try_into().unwrap()); + + println!("\nPak {}/{} - {}, Size: {}, Segments: {}", pak_n + 1, paks.len(), pak.name, pak.size, segment_count); + + for i in 0..segment_count { + // for first segment we already read the header so skip doing that for it + if i > 0 { + let _signature = common::read_exact(&mut file, 128)?; + signature_count += 1; + + let encrypted_header = common::read_exact(&mut file, 128)?; + let header = decrypt_aes128_ecb(&key_bytes, &encrypted_header)?; + segment_size = u32::from_le_bytes(header[88..92].try_into().unwrap()); + } + + let actual_segment_size = + // check if this is the last segment and not the last PAK + if i == segment_count - 1 && pak_n < paks.len() - 1 { + // calculate distance to next PAK + let next_pak_offset = &paks[pak_n + 1].offset + (128 * signature_count); + let current_pos = file.stream_position()?; + let distance = next_pak_offset - current_pos as u32; + + // if distance less than segment size, use the distance as actual size + if distance < segment_size { + distance + } else { + segment_size + } + + } else { + segment_size + }; + + let segment_data = common::read_exact(&mut file, actual_segment_size as usize)?; + let out_data = decrypt_aes128_ecb(&key_bytes, &segment_data)?; + + println!("- Segment {}/{}, size: {}", i + 1, segment_count, actual_segment_size); + + let output_path = Path::new(&output_folder).join(pak.name.clone() + ".bin"); + + fs::create_dir_all(&output_folder)?; + let mut out_file = OpenOptions::new() + .append(true) + .create(true) + .open(output_path)?; + + out_file.write_all(&out_data)?; + + println!("-- Saved to file!"); + } + } + + println!("\nExtraction finished!"); + + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 4a6e7ed..d71e1a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,8 @@ mod formats; use clap::Parser; use std::path::{PathBuf}; -use std::fs::{File}; +use std::io::{self}; +use std::fs::{self, File}; #[derive(Parser, Debug)] struct Args { @@ -21,6 +22,18 @@ fn main() -> Result<(), Box> { let output_path = args.output_folder; println!("Output folder: {}", output_path); + let output_folder_path = PathBuf::from(&output_path); + if output_folder_path.exists() { + if output_folder_path.is_dir() { + let is_empty = fs::read_dir(&output_folder_path)?.next().is_none(); + if !is_empty { + println!("\nWarning: Output folder exists and is NOT empty! Files may be overwritten!"); + println!("Press Enter if you want to continue..."); + io::stdin().read_line(&mut String::new())?; + } + } + } + let path = PathBuf::from(target_path); if path.is_dir() { if formats::samsung_old::is_samsung_old_dir(&path) { @@ -37,28 +50,40 @@ fn main() -> Result<(), Box> { if formats::sddl_sec::is_sddl_sec_file(&file) { println!("SDDL.SEC file detected!"); formats::sddl_sec::extract_sddl_sec(&file, &output_path)?; - } else if formats::msd10::is_msd10_file(&file) { + } + else if formats::msd10::is_msd10_file(&file) { println!("MSD10 file detected!"); formats::msd10::extract_msd10(&file, &output_path)?; - } else if formats::msd11::is_msd11_file(&file) { + } + else if formats::msd11::is_msd11_file(&file) { println!("MSD11 file detected!"); formats::msd11::extract_msd11(&file, &output_path)?; - } else if formats::tpv_timg::is_tpv_timg_file(&file) { + } + else if formats::tpv_timg::is_tpv_timg_file(&file) { println!("TPV TIMG file detected!"); formats::tpv_timg::extract_tpv_timg(&file, &output_path)?; - } else if formats::novatek::is_novatek_file(&file) { + } + else if formats::novatek::is_novatek_file(&file) { println!("Novatek file detected!"); formats::novatek::extract_novatek(&file, &output_path)?; - } else if formats::epk1::is_epk1_file(&file) { + } + else if formats::epk1::is_epk1_file(&file) { println!("EPK1 file detected!"); formats::epk1::extract_epk1(&file, &output_path)?; - } else if formats::pfl_upg::is_pfl_upg_file(&file) { + } + else if formats::epk2::is_epk2_file(&file) { + println!("EPK2 file detected!"); + formats::epk2::extract_epk2(&file, &output_path)?; + } + else if formats::pfl_upg::is_pfl_upg_file(&file) { println!("PFL UPG file detected!"); formats::pfl_upg::extract_pfl_upg(&file, &output_path)?; - } else if formats::mstar::is_mstar_file(&file) { + } + else if formats::mstar::is_mstar_file(&file) { println!("Mstar upgrade file detected!"); formats::mstar::extract_mstar(&file, &output_path)?; - } else { + } + else { println!("Input format not recognized!"); } }