mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
Add epk2 extractor (only 1 key for now)
This commit is contained in:
+2
-1
@@ -6,4 +6,5 @@ pub mod epk1;
|
||||
pub mod novatek;
|
||||
pub mod msd10;
|
||||
pub mod msd11;
|
||||
pub mod sddl_sec;
|
||||
pub mod sddl_sec;
|
||||
pub mod epk2;
|
||||
@@ -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<Aes128>;
|
||||
|
||||
fn decrypt_aes128_ecb(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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<Pak> = 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(())
|
||||
}
|
||||
+34
-9
@@ -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<dyn std::error::Error>> {
|
||||
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<dyn std::error::Error>> {
|
||||
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!");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user