From 92affac092b8d80701c2bf46fee7a7fc9898ebd5 Mon Sep 17 00:00:00 2001 From: theubusu <80545678+theubusu@users.noreply.github.com> Date: Thu, 30 Oct 2025 22:04:59 +0100 Subject: [PATCH] Added update.roku extractor, moved some common AES functions. --- Cargo.lock | 89 ++++++++++++++++++++ Cargo.toml | 2 +- src/formats.rs | 1 + src/formats/invincible_image.rs | 17 +--- src/formats/mtk_pkg.rs | 26 ++---- src/formats/roku.rs | 139 ++++++++++++++++++++++++++++++++ src/formats/ruf.rs | 15 +--- src/formats/samsung_old.rs | 16 +--- src/formats/sddl_sec.rs | 19 +---- src/main.rs | 7 +- src/utils.rs | 1 + src/utils/aes.rs | 24 ++++++ 12 files changed, 276 insertions(+), 80 deletions(-) create mode 100644 src/formats/roku.rs create mode 100644 src/utils.rs create mode 100644 src/utils/aes.rs diff --git a/Cargo.lock b/Cargo.lock index 284b756..dccc676 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,6 +111,12 @@ dependencies = [ "syn", ] +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + [[package]] name = "block-buffer" version = "0.10.4" @@ -308,6 +314,28 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "filetime" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys", +] + [[package]] name = "find-msvc-tools" version = "0.1.1" @@ -394,6 +422,23 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "lz4" version = "1.28.1" @@ -583,6 +628,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "rsa" version = "0.9.8" @@ -603,6 +657,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "sha1" version = "0.10.6" @@ -675,6 +742,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "typenum" version = "1.18.0" @@ -703,6 +781,7 @@ dependencies = [ "md5", "rsa", "sha1", + "tar", ] [[package]] @@ -803,6 +882,16 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "zerocopy" version = "0.8.27" diff --git a/Cargo.toml b/Cargo.toml index 00a0390..b49622a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,5 +15,5 @@ flate2 = "1.0" rsa = { version = "0.9", features = ["hazmat"] } hex = "0.4" ecb = "0.1" - +tar = "0.4.44" binrw = "0.15" \ No newline at end of file diff --git a/src/formats.rs b/src/formats.rs index 9975307..99115ec 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -7,6 +7,7 @@ pub mod novatek; pub mod ruf; pub mod invincible_image; pub mod slp; +pub mod roku; pub mod pup; diff --git a/src/formats/invincible_image.rs b/src/formats/invincible_image.rs index 84cab27..1d80ed5 100644 --- a/src/formats/invincible_image.rs +++ b/src/formats/invincible_image.rs @@ -2,10 +2,8 @@ use std::path::{Path}; use std::fs::{self, File, OpenOptions}; use std::io::{Write, Read, Seek, SeekFrom, Cursor}; use binrw::{BinRead, BinReaderExt}; -use aes::Aes128; -use cbc::{Decryptor, cipher::{block_padding::NoPadding, BlockDecryptMut, KeyIvInit}}; -type Aes128CbcDec = Decryptor; +use crate::utils::aes::{decrypt_aes128_cbc_nopad}; use crate::common; #[derive(BinRead)] @@ -67,17 +65,6 @@ pub fn is_invincible_image_file(file: &File) -> bool { } } -fn decrypt_aes_nopad(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result, Box> { - let mut data = encrypted_data.to_vec(); - let decryptor = Aes128CbcDec::new(key.into(), iv.into()); - - let decrypted = decryptor - .decrypt_padded_mut::(&mut data) - .map_err(|e| format!("UnpadError: {:?}", e))?; - - Ok(decrypted.to_vec()) -} - pub fn extract_invincible_image(mut file: &File, output_folder: &str) -> Result<(), Box> { let header: Header = file.read_le()?; @@ -116,7 +103,7 @@ pub fn extract_invincible_image(mut file: &File, output_folder: &str) -> Result< let iv = b"\xe3\x9f\x36\x39\x56\x9a\x6b\x8d\x3f\x2e\xc9\x44\xd9\xbc\xec\x43"; println!("\nDecrypting data..."); - let decrypted_data = decrypt_aes_nopad(&encrypted_data, &key, &iv)?; + let decrypted_data = decrypt_aes128_cbc_nopad(&encrypted_data, &key, &iv)?; let mut data_reader = Cursor::new(decrypted_data); diff --git a/src/formats/mtk_pkg.rs b/src/formats/mtk_pkg.rs index 7e144ca..e8463d5 100644 --- a/src/formats/mtk_pkg.rs +++ b/src/formats/mtk_pkg.rs @@ -1,12 +1,10 @@ use std::path::Path; use std::fs::{self, File, OpenOptions}; use std::io::{Write, Cursor, Seek}; - -use aes::Aes128; -use cbc::{Decryptor, cipher::{block_padding::NoPadding, BlockDecryptMut, KeyIvInit}}; use binrw::{BinRead, BinReaderExt}; use crate::common; +use crate::utils::aes::{decrypt_aes128_cbc_nopad}; #[derive(BinRead)] struct Header { @@ -51,13 +49,13 @@ static HEADER_IV: [u8; 16] = [0x00; 16]; pub fn is_mtk_pkg_file(file: &File) -> bool { let mut encrypted_header = common::read_file(&file, 0, 144).expect("Failed to read from file."); - let mut header = decrypt_aes_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV).expect("Decryption error!"); + let mut header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV).expect("Decryption error!"); if &header[4..12] == b"#DH@FiRm" { true } else { // try for philips which has additional 128 bytes at beginning encrypted_header = common::read_file(&file, 128, 144).expect("Failed to read from file."); - header = decrypt_aes_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV).expect("Decryption error!"); + header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV).expect("Decryption error!"); if &header[4..12] == b"#DH@FiRm" { true } else { @@ -67,21 +65,9 @@ pub fn is_mtk_pkg_file(file: &File) -> bool { } } -type Aes128CbcDec = Decryptor; -fn decrypt_aes_nopad(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result, Box> { - let mut data = encrypted_data.to_vec(); - let decryptor = Aes128CbcDec::new(key.into(), iv.into()); - - let decrypted = decryptor - .decrypt_padded_mut::(&mut data) - .map_err(|e| format!("UnpadError: {:?}", e))?; - - Ok(decrypted.to_vec()) -} - pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box> { let encrypted_header = common::read_exact(&mut file, 144)?; - let header = decrypt_aes_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?; + let header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?; let mut hdr_reader = Cursor::new(header); let hdr: Header = hdr_reader.read_le()?; @@ -107,11 +93,11 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str) -> Result<(), Box, + #[br(count = 8)] _magic_bytes: Vec, //imgARMcC + #[br(count = 4)] _target_bytes: Vec, + #[br(count = 4)] _platform_id: Vec, + image_type: u32, + size1: u32, + _size2: u32, + data_start_offset: u32, + _data_link_address: u32, + _data_entry_point_offset: u32, + flags: u32, + _timestamp: u32, + #[br(count = 4)] _build_host: Vec, + #[br(count = 4)] _unk: Vec, + #[br(count = 192)] _rest_of_header: Vec, +} +impl ImageHeader { + fn is_encrypted(&self) -> bool { + self.flags == 0x80 + } + fn type_string(&self) -> &str { + if self.image_type == 0xa { + return "initfs" + } else if self.image_type == 0x18 { + return "uImage" + } else if self.image_type == 0x3 { + return "loader" + } else if self.image_type == 0xd { + return "app_cramfs" + } else if self.image_type == 0x6 { + return "Customization Package" + } else if self.image_type == 0xe { + return "firmware_blob" + } else { + return "unknown" + } + } +} + +pub fn is_roku_file(file: &File) -> bool { + let header = common::read_file(&file, 0, 32).expect("Failed to read from file."); + let try_decrypt_header = decrypt_aes128_cbc_nopad(&header, &FILE_KEY, &FILE_IV).expect("Decryption error!"); + + if try_decrypt_header.starts_with(b"manifest\x00\x00\x00\x00\x00\x00\x00\x00") { + true + } else { + false + } +} + +pub fn extract_roku(mut file: &File, output_folder: &str) -> Result<(), Box> { + let mut encrypted_data = Vec::new(); + file.read_to_end(&mut encrypted_data)?; + + println!("\nDecrypting...\n"); + let tar_data = decrypt_aes128_cbc_pcks7(&encrypted_data, &FILE_KEY, &FILE_IV)?; + let tar_reader = Cursor::new(tar_data); + let mut tar_archive = Archive::new(tar_reader); + + for entry_result in tar_archive.entries_with_seek()? { + let mut entry = entry_result?; + let path = entry.path()?.to_path_buf(); + + if path == std::path::Path::new("manifest") { + let size = entry.header().size()? as usize; + let mut contents = Vec::new(); + entry.read_to_end(&mut contents)?; + + let text = String::from_utf8_lossy(&contents[..size - 256]); //dont display signature + println!("Manifest file:\n{}", text); + } else { + let mut contents = Vec::new(); + entry.read_to_end(&mut contents)?; //entry cant seek + + if contents.starts_with(b"\x00\x00\x00\x00\x00\x00\x00\x00imgARMcC") { + println!("\nImage file: {:?}:", path); + let size = entry.header().size()? as usize; + let mut image_reader = Cursor::new(contents); + let mut i = 1; + + while image_reader.stream_position()? < size as u64 { + let image: ImageHeader = image_reader.read_le()?; + println!("\nImage {} - Type: {:x}({}), Size: {}, Flags: {:x}{}, Data offset: {}", + i ,image.image_type, image.type_string(), image.size1, image.flags, if image.is_encrypted(){"(Encrypted)"}else{" "}, image.data_start_offset); + + let data = + if image.data_start_offset == 0 { + common::read_exact(&mut image_reader, image.size1 as usize - 256)? + } else { + let _extra_data = common::read_exact(&mut image_reader, image.data_start_offset as usize - 256)?; + common::read_exact(&mut image_reader, image.size1 as usize - image.data_start_offset as usize)? + }; + + let folder_path = Path::new(&output_folder).join(&path); + let output_path = Path::new(&folder_path).join(format!("{}_{}.bin", i, image.type_string())); + + fs::create_dir_all(&folder_path)?; + let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; + out_file.write_all(&data)?; + + println!("- Saved file!"); + + i += 1; + } + + } else { + println!("\nOther/Unknown file: {:?}", path); + let output_path = Path::new(&output_folder).join(&path); + + fs::create_dir_all(&output_folder)?; + let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; + out_file.write_all(&contents)?; + + println!("- Saved file!"); + } + } + } + + println!("\nExtraction finished!"); + Ok(()) +} \ No newline at end of file diff --git a/src/formats/ruf.rs b/src/formats/ruf.rs index 2199ad6..b57dc35 100644 --- a/src/formats/ruf.rs +++ b/src/formats/ruf.rs @@ -2,12 +2,10 @@ use std::path::{Path}; use std::fs::{self, File, OpenOptions}; use binrw::{BinRead, BinReaderExt}; use std::io::{Write, Seek, SeekFrom, Cursor}; -use aes::Aes128; -use cbc::{Decryptor, cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}}; -type Aes128CbcDec = Decryptor; use crate::common; use crate::keys; +use crate::utils::aes::{decrypt_aes128_cbc_pcks7}; #[derive(BinRead)] struct RufHeader { @@ -80,15 +78,6 @@ pub fn is_ruf_file(file: &File) -> bool { } } -fn decrypt(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result, Box> { - let mut data = encrypted_data.to_vec(); - let decryptor = Aes128CbcDec::new(key.into(), iv.into()); - let decrypted = decryptor.decrypt_padded_mut::(&mut data) - .map_err(|e| format!("!!Decryption error!!: {:?}", e))?; - - Ok(decrypted.to_vec()) -} - pub fn extract_ruf(mut file: &File, output_folder: &str) -> Result<(), Box> { let header: RufHeader = file.read_be()?; if header.is_dual_ruf() { @@ -157,7 +146,7 @@ fn actually_extract_ruf(mut file: &File, output_folder: &str, start_offset: u64) file.seek(SeekFrom::Start(start_offset + 2048))?; let encrypted_data = common::read_exact(&mut file, header.data_size as usize)?; println!("Decrypting data..."); - let decrypted_data = decrypt(&encrypted_data, &key_bytes, &iv_bytes)?; + let decrypted_data = decrypt_aes128_cbc_pcks7(&encrypted_data, &key_bytes, &iv_bytes)?; let mut data_reader = Cursor::new(decrypted_data); diff --git a/src/formats/samsung_old.rs b/src/formats/samsung_old.rs index 65a8ec2..861d449 100644 --- a/src/formats/samsung_old.rs +++ b/src/formats/samsung_old.rs @@ -2,15 +2,12 @@ use std::fs; use std::path::{Path, PathBuf}; use std::fs::{File, OpenOptions}; use std::io::{Write}; -use aes::Aes128; use hex::decode; -use cbc::{Decryptor, cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}}; use sha1::{Digest, Sha1}; -type Aes128CbcDec = Decryptor; - use crate::common; use crate::keys; +use crate::utils::aes::{decrypt_aes128_cbc_pcks7}; use md5; @@ -22,15 +19,6 @@ pub fn is_samsung_old_dir(path: &PathBuf) -> bool { } } -fn decrypt_aes(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result, Box> { - let mut data = encrypted_data.to_vec(); - let decryptor = Aes128CbcDec::new(key.into(), iv.into()); - let decrypted = decryptor.decrypt_padded_mut::(&mut data) - .map_err(|e| format!("!!Decryption error!!: {:?}", e))?; - - Ok(decrypted.to_vec()) -} - fn decrypt_xor(data: &[u8], key: &str) -> Vec { let key_bytes = key.as_bytes(); data.iter() @@ -120,7 +108,7 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo //println!("IV: {:02x?}", iv_md5); let end = file_size - 260; println!("- Decrypting file..."); - let decrypted_data = decrypt_aes(&data[16..end.try_into().unwrap()], &key_md5, &iv_md5)?; + let decrypted_data = decrypt_aes128_cbc_pcks7(&data[16..end.try_into().unwrap()], &key_md5, &iv_md5)?; println!("-- DeXORing file..."); let xor_key = fw_info.split_whitespace().next().unwrap(); diff --git a/src/formats/sddl_sec.rs b/src/formats/sddl_sec.rs index 84fe2b5..d984c51 100644 --- a/src/formats/sddl_sec.rs +++ b/src/formats/sddl_sec.rs @@ -1,14 +1,10 @@ use std::path::{Path, PathBuf}; use std::fs::{self, File, OpenOptions}; use std::io::{self, Read, Seek, SeekFrom, Write}; - -use aes::Aes128; use flate2::read::ZlibDecoder; -use cbc::{Decryptor, cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}}; - -type Aes128CbcDec = Decryptor; use crate::common; +use crate::utils::aes::{decrypt_aes128_cbc_pcks7}; pub fn is_sddl_sec_file(file: &File) -> bool { let header = common::read_file(&file, 0, 8).expect("Failed to read from file."); @@ -19,15 +15,6 @@ pub fn is_sddl_sec_file(file: &File) -> bool { } } -fn decrypt(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result, Box> { - let mut data = encrypted_data.to_vec(); - let decryptor = Aes128CbcDec::new(key.into(), iv.into()); - let decrypted = decryptor.decrypt_padded_mut::(&mut data) - .map_err(|e| format!("!!Decryption error!!: {:?}", e))?; - - Ok(decrypted.to_vec()) -} - //ported from original from https://nese.team/posts/justctf/ fn decipher(s: &[u8], len_: usize) -> Vec { let mut v3: u32 = 904; @@ -97,7 +84,7 @@ pub fn extract_sddl_sec(file: &File, output_folder: &str) -> Result<(), Box; - match decrypt(&header, &key, &iv) { + match decrypt_aes128_cbc_pcks7(&header, &key, &iv) { Ok(v) => decrypted_header = v, Err(_) => { // SDDL files can have a footer(signature?) of 0x80 OR 0x100 lenght in later ones, and there is no good way to detect it before entering the while loop and the footer has no common header. @@ -124,7 +111,7 @@ pub fn extract_sddl_sec(file: &File, output_folder: &str) -> Result<(), Box Result<(), Box> { else if formats::pup::is_pup_file(&file) { println!("PUP file detected!"); formats::pup::extract_pup(&file, &output_path)?; - } + } + else if formats::roku::is_roku_file(&file) { + println!("Roku file detected!"); + formats::roku::extract_roku(&file, &output_path)?; + } else if formats::mtk_pkg::is_mtk_pkg_file(&file) { println!("MTK Pkg file detected!"); formats::mtk_pkg::extract_mtk_pkg(&file, &output_path)?; diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..a8cfff6 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1 @@ +pub mod aes; \ No newline at end of file diff --git a/src/utils/aes.rs b/src/utils/aes.rs new file mode 100644 index 0000000..10b0065 --- /dev/null +++ b/src/utils/aes.rs @@ -0,0 +1,24 @@ +use aes::Aes128; +use cbc::{Decryptor, cipher::{block_padding::Pkcs7, block_padding::NoPadding, BlockDecryptMut, KeyIvInit}}; + +type Aes128CbcDec = Decryptor; + +pub fn decrypt_aes128_cbc_pcks7(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result, Box> { + let mut data = encrypted_data.to_vec(); + let decryptor = Aes128CbcDec::new(key.into(), iv.into()); + let decrypted = decryptor.decrypt_padded_mut::(&mut data) + .map_err(|e| format!("!!Decryption error!!: {:?}", e))?; + + Ok(decrypted.to_vec()) +} + +pub fn decrypt_aes128_cbc_nopad(encrypted_data: &[u8], key: &[u8; 16], iv: &[u8; 16]) -> Result, Box> { + let mut data = encrypted_data.to_vec(); + let decryptor = Aes128CbcDec::new(key.into(), iv.into()); + + let decrypted = decryptor + .decrypt_padded_mut::(&mut data) + .map_err(|e| format!("UnpadError: {:?}", e))?; + + Ok(decrypted.to_vec()) +} \ No newline at end of file