diff --git a/Cargo.lock b/Cargo.lock index 3d375d0..7edd40e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,6 +10,27 @@ dependencies = [ "offset-finder", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -66,6 +87,16 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "offset-finder" version = "0.1.0" @@ -241,6 +272,14 @@ dependencies = [ "syn", ] +[[package]] +name = "tlzma" +version = "0.1.0" +dependencies = [ + "lzma-rs", + "thiserror 2.0.9", +] + [[package]] name = "unicode-ident" version = "1.0.14" diff --git a/Cargo.toml b/Cargo.toml index 7e85566..586e92f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,8 @@ members = [ "cursor", "offset-finder", "pe-utils", - "restorer" + "restorer", + "tlzma" ] [workspace.package] @@ -15,6 +16,7 @@ edition = "2021" [workspace.dependencies] goblin = "0.9.2" log = "0.4.22" +lzma-rs = { version = "0.3.0", features = ["raw_decoder"] } patternscanner = "0.5.0" serde = { version = "1.0.217", features = ["derive"] } thiserror = "2.0.9" diff --git a/tlzma/Cargo.toml b/tlzma/Cargo.toml new file mode 100644 index 0000000..ea65057 --- /dev/null +++ b/tlzma/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "tlzma" +version.workspace = true +edition.workspace = true + +[dependencies] +lzma-rs.workspace = true +thiserror.workspace = true \ No newline at end of file diff --git a/tlzma/src/lib.rs b/tlzma/src/lib.rs new file mode 100644 index 0000000..b726cfa --- /dev/null +++ b/tlzma/src/lib.rs @@ -0,0 +1,97 @@ +use std::io::Cursor; +use lzma_rs::decompress::raw::{LzmaDecoder, LzmaParams, LzmaProperties}; + +const LZMA_PROP_MAX: u32 = 9 * 5 * 5; +const LZMA_DIC_MIN: u32 = 1 << 12; + +const PROPS_XK: u8 = 0x9d ^ 0xf3; +const PROPS_AK: u8 = 0x65 ^ 0xa2; +const USIZE_XK: u32 = 0x218d5a0a ^ 0xab8832cb; +const USIZE_AK: u32 = 0x5e98c630 ^ 0x66e44294; +const CSIZE_XK: u32 = 0x6dfc36ff ^ 0xfc4c53bf; +const CSIZE_AK: u32 = 0xc68dd929 ^ 0x78bdb6e9; + +const GOOD_PROPS: u32 = 0xcd386d0f ^ 0x996f3a58; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Lzma Error: {0}")] + Lzma(#[from] lzma_rs::error::Error), + #[error("TryFromSlice Error: {0}")] + TryFromSlice(#[from] std::array::TryFromSliceError), + #[error("LZMA header invalid properties: {0} must be < 225")] + PropsTooBig(u32), +} + +#[repr(packed)] +#[derive(Debug)] +pub struct TlzmaHeader { + pub props: [u8; 5], + pub _1: [u8; 3], + pub uncompressed_size: u32, + pub compressed_size: u32, +} + +impl TlzmaHeader { + pub fn tlzma_test_header(data: &[u8]) -> Result { + let tlzma: TlzmaHeader = unsafe { std::ptr::read(data.as_ptr() as *const _) }; + let good_props = u32::from_le_bytes(tlzma.props[1..].try_into()?); + Ok(GOOD_PROPS == good_props) + } + + pub fn parse_header(data: &[u8]) -> Self { + let mut tlzma: TlzmaHeader = unsafe { std::ptr::read(data.as_ptr() as *const _) }; + for i in 0..tlzma.props.len() { + tlzma.props[i] = (tlzma.props[i] ^ PROPS_XK).wrapping_add(PROPS_AK); + } + tlzma.uncompressed_size = (tlzma.uncompressed_size ^ USIZE_XK).wrapping_add(USIZE_AK); + tlzma.compressed_size = (tlzma.compressed_size ^ CSIZE_XK).wrapping_add(CSIZE_AK); + tlzma + } + + pub fn get_next_chunk_offset(&self, offset: usize, data: &[u8]) -> Result { + let tmp = offset + size_of::() + self.compressed_size as usize; + let input = &data[tmp..tmp + 16]; + match Self::tlzma_test_header(input)? { + true => Ok(tmp), + false => Ok(offset + self.uncompressed_size as usize) + } + } + + pub fn lzma_decode(&self, input: &[u8], offset: usize) -> Result<(Vec, u32), Error> { + let params = self.get_params_from_props()?; + let mut decoder = LzmaDecoder::new(params, None)?; + let start = offset + size_of::(); + let end = start + self.compressed_size as usize; + let mut data = Cursor::new(&input[start..end]); + let mut output = Vec::with_capacity(self.uncompressed_size as usize); + decoder.decompress(&mut data, &mut output)?; + + Ok((output, self.uncompressed_size)) + } + + fn get_params_from_props(&self) -> Result { + let mut pb = self.props[0] as u32; + if pb >= LZMA_PROP_MAX { + return Err(Error::PropsTooBig(pb)); + } + + let mut dic = u32::from_le_bytes(self.props[1..].try_into()?); + if dic < LZMA_DIC_MIN { + dic = LZMA_DIC_MIN; + } + + let lc: u32 = pb % 9; + pb /= 9; + let lp: u32 = pb % 5; + pb /= 5; + + Ok( + LzmaParams::new( + LzmaProperties { lc, lp, pb }, + dic, + Some(self.uncompressed_size as u64), + ) + ) + } +} \ No newline at end of file