From 4c8ce5c64807f9ee407ff1548f49aee18dad4b5a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sat, 27 Aug 2022 20:23:52 +0300 Subject: [PATCH] proto --- Cargo.toml | 5 + lib/Cargo.toml | 18 ++++ lib/examples/patchsomedll.rs | 58 +++++++++++ lib/src/lib.rs | 187 +++++++++++++++++++++++++++++++++++ macro/Cargo.toml | 17 ++++ macro/src/lib.rs | 91 +++++++++++++++++ 6 files changed, 376 insertions(+) create mode 100644 Cargo.toml create mode 100644 lib/Cargo.toml create mode 100644 lib/examples/patchsomedll.rs create mode 100644 lib/src/lib.rs create mode 100644 macro/Cargo.toml create mode 100644 macro/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..48bf131 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "macro", + "lib" +] diff --git a/lib/Cargo.toml b/lib/Cargo.toml new file mode 100644 index 0000000..b29c512 --- /dev/null +++ b/lib/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "binnpatch" +version = "0.1.0" +edition = "2021" +authors = ["b4tman "] +readme = "../README.md" +repository = "https://gitea.b4tman.ru/b4tman/binnpatch" +license = "MIT" + + +[lib] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +binnpatch_macro = { path = "../macro" } +data-encoding = "^2.3" +memmap = "^0.7" diff --git a/lib/examples/patchsomedll.rs b/lib/examples/patchsomedll.rs new file mode 100644 index 0000000..dda3e25 --- /dev/null +++ b/lib/examples/patchsomedll.rs @@ -0,0 +1,58 @@ +extern crate binnpatch; +extern crate data_encoding; +extern crate memmap; + + +extern crate binnpatch_macro; + +use data_encoding::HEXUPPER; +use memmap::{Mmap, MmapMut}; +use std::fs; +use std::fs::{File, OpenOptions}; +use std::ops::{Deref, DerefMut}; + +use binnpatch::{BytesPattern, FindPattern, ApplyPatch}; +use binnpatch_macro::binnvec; + +fn main() { + let src_file = "Some.dll"; + let dst_file = "Some.dll.patched"; + let src_pattern = + "40 3E 1D ?? ?? 12 1C 7C 48 ?? 73 6F 02 22 ?? 61 19 4E 13 60 48 45 19 27 5B"; + let replacement = binnvec!(06 5A 18 74 2D 62 12 6A 13 4A 2B 0E 6F 0F 36 7A 28 0A 37 67 0A 4B 01 73 0x14); + + let pattern = BytesPattern::from(src_pattern); + + let file = File::open(src_file).expect("src open"); + + let src_map = unsafe { Mmap::map(&file) }.expect("src map"); + let data_src = src_map.deref(); + + let offset = data_src.find_pattern_first(&pattern); + if 0 == offset { + panic!("src pattern not found"); + } + println!("found at {:#010X}", offset); + + if replacement.len() > data_src.len() - offset { + panic!("replace overflow"); + } + + drop(src_map); + drop(file); + + fs::copy(src_file, dst_file).expect("file copy"); + + let file = OpenOptions::new() + .read(true) + .write(true) + .open(dst_file) + .expect("dst open"); + let mut dst_map = unsafe { MmapMut::map_mut(&file) }.expect("dst map"); + let mut data_dst = dst_map.deref_mut(); + + data_dst.apply_patch(offset, &replacement); + drop(dst_map); + + println!("done"); +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs new file mode 100644 index 0000000..0b77b95 --- /dev/null +++ b/lib/src/lib.rs @@ -0,0 +1,187 @@ +extern crate data_encoding; + +use data_encoding::HEXUPPER; +use std::ops::Deref; + +pub struct BytesPattern { + bytes: Vec, + mask: Vec, + pi: Vec, +} + +impl Deref for BytesPattern { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + &self.bytes + } +} + +impl BytesPattern { + fn len(&self) -> usize { + self.deref().len() + } + fn match_at(&self, i: usize, byte: u8) -> bool { + byte == self[i] || self.mask[i] + } + fn prefix_function(&mut self) { + let mut pi = vec![0; self.len()]; + let mut j = 0; + for i in 1..self.len() { + while j > 0 && !self.match_at(j, self[i]) { + j = pi[j - 1]; + } + if self.match_at(j, self[i]) { + j += 1; + } + pi[i] = j; + } + self.pi = pi; + } + fn reset_offset_from(&self, current_offset: usize) -> usize { + let mut new_offset: usize = 0; + if 0 < current_offset && current_offset <= self.len() { + new_offset = self.pi[current_offset - 1]; + } + new_offset + } +} + +impl From<&str> for BytesPattern { + fn from(str_val: &str) -> Self { + let mut elements: Vec<&str> = str_val.split(' ').collect(); + let mask: Vec = elements.iter().map(|item| *item == "??").collect(); + + for (i, &item) in mask.iter().enumerate() { + if item { + elements[i] = "00"; + } + } + + let mut s = Self { + bytes: HEXUPPER + .decode(elements.join("").as_bytes()) + .expect("decode pattern"), + mask, + pi: Vec::::new(), + }; + s.prefix_function(); + s + } +} + +pub trait FindPattern { + fn find_pattern_first(&self, pattern: &BytesPattern) -> usize; +} + +pub trait ApplyPatch { + fn apply_patch(&mut self, offset: usize, patch: &[u8]); +} + +impl FindPattern for &[u8] { + fn find_pattern_first(&self, pattern: &BytesPattern) -> usize { + let mut offset_result = 0; + let mut offset_pattern = 0; + let data_lenght = self.len(); + let pattern_lenght = pattern.len(); + + for (i, &item) in self.iter().enumerate() { + if data_lenght - i < pattern_lenght - offset_pattern { + break; + } + + if pattern.match_at(offset_pattern, item) { + offset_pattern += 1; + if offset_pattern == pattern_lenght { + offset_result = i + 1 - pattern_lenght; + break; + } + } else { + offset_pattern = pattern.reset_offset_from(offset_pattern); + } + } + offset_result + } +} + +impl ApplyPatch for &mut [u8] { + fn apply_patch(&mut self, offset: usize, patch: &[u8]) { + for (i, &item) in patch.iter().enumerate() { + self[offset + i] = item; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bytes_from_pattern() { + let src_pattern = "E8 4D F8 FF 83 C4 85 C0 5F 75 32 8B 54 24 48 50 50"; + let pattern = BytesPattern::from(src_pattern); + let bytes: Vec = vec![ + 0xE8, 0x4D, 0xF8, 0xFF, 0x83, 0xC4, 0x85, 0xC0, 0x5F, 0x75, 0x32, 0x8B, 0x54, 0x24, + 0x48, 0x50, 0x50, + ]; + assert_eq!(bytes, pattern.bytes); + } + #[test] + fn mask_from_pattern() { + let src_pattern = "E8 ?? ?? FF ?? C4 ?? ?? 5F 75 32 ?? 54 24 48 50 50"; + let pattern = BytesPattern::from(src_pattern); + let mask: Vec = vec![ + false, true, true, false, true, false, true, true, false, false, false, true, false, + false, false, false, false, + ]; + assert_eq!(mask, pattern.mask); + } + #[test] + fn find_pattern_first() { + let src_data: Vec = vec![ + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + ]; + let src_pattern = "EE FF 00 11 22"; + let pattern = BytesPattern::from(src_pattern); + let offset = src_data.as_slice().find_pattern_first(&pattern); + assert_eq!(offset, 4); + } + #[test] + fn find_pattern_first_loop() { + let src_data: Vec = vec![ + 0xAA, 0xBB, 0xCC, 0xAA, 0xBB, 0xEE, 0xAA, 0xBB, 0xCC, 0xAA, 0xBB, 0xCC, 0xAA, 0xBB, + 0xDD, 0xAA, 0xBB, 0xCC, + ]; + let src_pattern = "AA BB CC AA BB DD"; + let pattern = BytesPattern::from(src_pattern); + let offset = src_data.as_slice().find_pattern_first(&pattern); + assert_eq!(offset, 9); + } + #[test] + fn prefix_function() { + let src_pattern = "AA BB BB AA AA BB BB AA BB"; + let expected_pi: Vec = vec![0, 0, 0, 1, 1, 2, 3, 4, 2]; + let pattern = BytesPattern::from(src_pattern); + assert_eq!(pattern.pi, expected_pi); + } + #[test] + fn apply_patch() { + let src_data: Vec = vec![ + 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + ]; + let expected_data: Vec = vec![ + 0xAA, 0xBB, 0xCC, 0xDD, 0x99, 0x88, 0x77, 0x66, 0x55, 0x33, 0x44, 0x55, 0x66, + ]; + let src_pattern = "EE FF 00 11 22"; + let replacement = "99 88 77 66 55"; + let pattern = BytesPattern::from(src_pattern); + let offset = src_data.as_slice().find_pattern_first(&pattern); + + let replacement = replacement.replace(" ", ""); + let replacement = HEXUPPER + .decode(replacement.as_bytes()) + .expect("decode replacement"); + let mut dst_data = src_data.clone(); + dst_data.as_mut_slice().apply_patch(offset, &replacement); + assert_eq!(dst_data, expected_data); + } +} diff --git a/macro/Cargo.toml b/macro/Cargo.toml new file mode 100644 index 0000000..fcfeb5d --- /dev/null +++ b/macro/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "binnpatch_macro" +version = "0.1.0" +edition = "2021" +authors = ["b4tman "] +readme = "../README.md" +repository = "https://gitea.b4tman.ru/b4tman/binnpatch" +license = "MIT" + + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + diff --git a/macro/src/lib.rs b/macro/src/lib.rs new file mode 100644 index 0000000..c96ac3f --- /dev/null +++ b/macro/src/lib.rs @@ -0,0 +1,91 @@ +extern crate proc_macro; +use proc_macro::{TokenStream, TokenTree}; + +use proc_macro::{Punct, Literal, Spacing, Span, Ident, Group, Delimiter}; + +fn parse_str(s: &str) -> TokenStream { + s.parse().unwrap() +} + +#[proc_macro] +pub fn binnvec(_item: TokenStream) -> TokenStream { + println!("- - - - -"); + println!("{}", _item.to_string()); + println!("- - - - -"); + println!("{:?}", _item); + println!("- - - - -"); + + let mut new_tokens: Vec = vec![]; + let comma: Punct = Punct::new(',', Spacing::Alone); + let zero = Literal::u8_suffixed(0); + let mut prev_separated: bool = true; + let mut prev_is_mask: bool = false; + + for token in _item { + match token { + TokenTree::Punct(ref x) if x.as_char() == ',' => { + new_tokens.push(token.into()); + prev_separated = true; + continue; + } + TokenTree::Punct(ref x) if x.as_char() == '?' => { + if prev_is_mask { + if !prev_separated { + new_tokens.push(comma.clone().into()); + } + let mut val = zero.clone(); + val.set_span(x.span()); + new_tokens.push(zero.clone().into()); + prev_is_mask = false; + prev_separated = false; + } else if !prev_is_mask { + prev_is_mask = true; + } + continue; + } + TokenTree::Ident(ref x) if x.to_string().len() == 2 => { + if !prev_separated { + new_tokens.push(comma.clone().into()); + } + let lit_str = format!("0x{}u8", x.to_string()); + let mut val:Literal = lit_str.as_str().parse().expect("parse literal from ident"); + val.set_span(x.span()); + new_tokens.push(val.into()); + } + TokenTree::Literal(ref x) => { + if !prev_separated { + new_tokens.push(comma.clone().into()); + } + + let in_str = x.to_string(); + if in_str.starts_with("0x") { + new_tokens.push(token.into()); + } else { + let lit_str = format!("0x{}u8", in_str); + let mut val:Literal = lit_str.as_str().parse().expect("parse literal"); + val.set_span(x.span()); + new_tokens.push(val.into()); + } + } + _ => { + + } + } + prev_separated = false; + } + + let mut ts_new = TokenStream::new(); + ts_new.extend(new_tokens); + + let group = Group::new(Delimiter::Bracket, ts_new); + let mut result = TokenStream::new(); + let vec_ident = Ident::new("vec", Span::call_site()); + let macro_marc = Punct::new('!', Spacing::Joint); + result.extend([TokenTree::from(vec_ident), TokenTree::from(macro_marc)]); + result.extend([TokenTree::from(group)]); + + println!("result: \"{}\"", result.to_string()); + + result +} +