diff --git a/Cargo.lock b/Cargo.lock index 969ade3..229dfb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ab_glyph" @@ -242,7 +242,7 @@ dependencies = [ "objc-foundation", "objc_id", "parking_lot 0.12.1", - "thiserror", + "thiserror 1.0.56", "winapi", "x11rb", ] @@ -404,7 +404,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -439,7 +439,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -612,7 +612,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -637,7 +637,7 @@ dependencies = [ "log", "nix 0.25.1", "slotmap", - "thiserror", + "thiserror 1.0.56", "vec_map", ] @@ -756,7 +756,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -1060,6 +1060,15 @@ dependencies = [ "parking_lot_core 0.9.9", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + [[package]] name = "deranged" version = "0.3.10" @@ -1149,7 +1158,8 @@ dependencies = [ "indexmap 2.1.0", "itertools 0.12.0", "parking_lot 0.12.1", - "patternsleuth", + "patternsleuth_image", + "patternsleuth_resolvers", "regex", "retour", "simple-log", @@ -1195,7 +1205,7 @@ dependencies = [ "percent-encoding", "raw-window-handle", "static_assertions", - "thiserror", + "thiserror 1.0.56", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1275,6 +1285,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "enumflags2" version = "0.7.8" @@ -1293,7 +1312,7 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -1551,7 +1570,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -1976,7 +1995,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.56", "walkdir", "windows-sys 0.45.0", ] @@ -2174,6 +2193,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -2192,6 +2220,41 @@ dependencies = [ "autocfg", ] +[[package]] +name = "minidump" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a30545aa9eded3d58064b370aa0efc43be2e5a3fdb14e46fe7d7f1043280090" +dependencies = [ + "debugid", + "encoding_rs", + "memmap2 0.9.5", + "minidump-common", + "num-traits", + "procfs-core", + "range-map", + "scroll 0.12.0", + "thiserror 2.0.11", + "time", + "tracing", + "uuid", +] + +[[package]] +name = "minidump-common" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dad3a7b1bd550c5f6f181b6b0fee6794f81aef6809e71467a6170ac2c0107b7" +dependencies = [ + "bitflags 2.4.1", + "debugid", + "num-derive", + "num-traits", + "range-map", + "scroll 0.12.0", + "smart-default", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -2244,7 +2307,7 @@ dependencies = [ "ndk-sys", "num_enum 0.5.11", "raw-window-handle", - "thiserror", + "thiserror 1.0.56", ] [[package]] @@ -2321,6 +2384,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -2390,7 +2464,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -2599,59 +2673,64 @@ dependencies = [ ] [[package]] -name = "patternsleuth" +name = "patternsleuth_cli" version = "0.1.0" dependencies = [ "anyhow", - "futures", - "futures-scopes", - "gimli", + "clap", + "colored", + "crossbeam", + "globset", "iced-x86", - "inventory", + "indicatif", "itertools 0.12.0", - "libc", - "memchr", "msvc-demangler", - "object", + "patricia_tree", + "patternsleuth_image", + "patternsleuth_resolvers", "patternsleuth_scanner", - "pdb", + "prettytable-rs", "rayon", + "regex", + "rusqlite", "serde", - "strum", + "serde_json", + "time", "tracing", - "typetag", - "windows 0.52.0", + "tracing-subscriber", ] [[package]] -name = "patternsleuth_cli" +name = "patternsleuth_image" version = "0.1.0" dependencies = [ "anyhow", - "clap", - "colored", - "crossbeam", - "globset", - "iced-x86", - "indicatif", + "gimli", "itertools 0.12.0", "libc", - "memchr", + "minidump", "msvc-demangler", "object", - "patricia_tree", - "patternsleuth", + "patternsleuth_scanner", "pdb", - "prettytable-rs", - "rayon", - "regex", - "rusqlite", "serde", - "serde_json", - "strum", - "time", + "windows 0.52.0", +] + +[[package]] +name = "patternsleuth_resolvers" +version = "0.1.0" +dependencies = [ + "futures", + "futures-scopes", + "iced-x86", + "inventory", + "itertools 0.12.0", + "patternsleuth_image", + "patternsleuth_scanner", + "serde", "tracing", - "tracing-subscriber", + "typetag", ] [[package]] @@ -2673,7 +2752,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82040a392923abe6279c00ab4aff62d5250d1c8555dc780e4b02783a7aa74863" dependencies = [ "fallible-iterator 0.2.0", - "scroll", + "scroll 0.11.0", "uuid", ] @@ -2700,7 +2779,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -2847,13 +2926,23 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] +[[package]] +name = "procfs-core" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" +dependencies = [ + "bitflags 2.4.1", + "hex", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -2899,6 +2988,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "range-map" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95f" +dependencies = [ + "num-traits", +] + [[package]] name = "raw-window-handle" version = "0.5.2" @@ -2966,7 +3064,7 @@ checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall 0.2.16", - "thiserror", + "thiserror 1.0.56", ] [[package]] @@ -3137,6 +3235,26 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +[[package]] +name = "scroll" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "sctk-adwaita" version = "0.5.4" @@ -3145,7 +3263,7 @@ checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" dependencies = [ "ab_glyph", "log", - "memmap2", + "memmap2 0.5.10", "smithay-client-toolkit", "tiny-skia", ] @@ -3177,7 +3295,7 @@ checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -3199,7 +3317,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -3293,6 +3411,17 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "smart-default" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "smithay-client-toolkit" version = "0.16.1" @@ -3304,7 +3433,7 @@ dependencies = [ "dlib", "lazy_static", "log", - "memmap2", + "memmap2 0.5.10", "nix 0.24.3", "pkg-config", "wayland-client", @@ -3362,28 +3491,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.48", -] - [[package]] name = "syn" version = "1.0.109" @@ -3397,9 +3504,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -3436,7 +3543,16 @@ version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.56", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -3447,7 +3563,18 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] @@ -3573,10 +3700,11 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3584,20 +3712,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -3616,9 +3744,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -3675,7 +3803,7 @@ checksum = "ac73887f47b9312552aa90ef477927ff014d63d1920ca8037c6c1951eab64bb1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] @@ -3740,9 +3868,9 @@ checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -3805,7 +3933,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", "wasm-bindgen-shared", ] @@ -3839,7 +3967,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4468,7 +4596,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.98", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0dab389..d1b4a1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [workspace] resolver = "2" members = [ - "patternsleuth", "patternsleuth_cli", "patternsleuth_scanner", + "patternsleuth_image", + "patternsleuth_resolvers", "examples/*", ] default-members = ["patternsleuth_*"] @@ -13,7 +14,7 @@ repository = "https://github.com/trumank/patternsleuth" authors = [ "trumank" ] license = "MIT OR Apache-2.0" version = "0.1.0" -edition = "2021" +edition = "2024" [profile.release] incremental = true diff --git a/examples/dll_hook/Cargo.toml b/examples/dll_hook/Cargo.toml index 72a55a5..e23ec6f 100644 --- a/examples/dll_hook/Cargo.toml +++ b/examples/dll_hook/Cargo.toml @@ -19,7 +19,8 @@ egui-winit = { version = "0.24.0", default-features = false } indexmap = "2.1.0" itertools.workspace = true parking_lot = "0.12.1" -patternsleuth = { path = "../../patternsleuth", features = ["process-internal", "image-pe"] } +patternsleuth_image = { path = "../../patternsleuth_image", features = ["process-internal", "image-pe"] } +patternsleuth_resolvers = { version = "0.1.0", path = "../../patternsleuth_resolvers", features = ["image-pe"] } regex.workspace = true retour = { git = "https://github.com/Hpmason/retour-rs", features = ["static-detour"] } simple-log = "1.6.0" diff --git a/examples/dll_hook/src/gui.rs b/examples/dll_hook/src/gui.rs index 799529e..b7d5b05 100644 --- a/examples/dll_hook/src/gui.rs +++ b/examples/dll_hook/src/gui.rs @@ -1,4 +1,4 @@ -use std::sync::{mpsc::Receiver, Arc, OnceLock, RwLock}; +use std::sync::{Arc, OnceLock, RwLock, mpsc::Receiver}; use eframe::egui; diff --git a/examples/dll_hook/src/hooks.rs b/examples/dll_hook/src/hooks.rs index 383de95..6dae416 100644 --- a/examples/dll_hook/src/hooks.rs +++ b/examples/dll_hook/src/hooks.rs @@ -76,6 +76,7 @@ pub unsafe fn initialize() -> Result<()> { //info!("tick time={:0.5}", delta_seconds); + #[allow(static_mut_refs)] GUOBJECT_LOCK.take(); HookUGameEngineTick.call(game_engine, delta_seconds, idle_mode); GUOBJECT_LOCK = Some(globals().guobject_array()); diff --git a/examples/dll_hook/src/lib.rs b/examples/dll_hook/src/lib.rs index fdebbdc..204844e 100644 --- a/examples/dll_hook/src/lib.rs +++ b/examples/dll_hook/src/lib.rs @@ -1,18 +1,17 @@ -#![feature(backtrace_frames)] - mod app; mod gui; mod hooks; mod object_cache; mod ue; +use std::mem::MaybeUninit; use std::path::PathBuf; -use anyhow::{anyhow, Context, Result}; -use patternsleuth::resolvers::impl_try_collector; -use patternsleuth::resolvers::unreal::blueprint_library::UFunctionBind; -use patternsleuth::resolvers::unreal::UObjectBaseUtilityGetPathName; -use patternsleuth::resolvers::unreal::{ +use anyhow::{Context, Result, anyhow}; +use patternsleuth_resolvers::unreal::UObjectBaseUtilityGetPathName; +use patternsleuth_resolvers::unreal::blueprint_library::UFunctionBind; +use patternsleuth_resolvers::unreal::{ + KismetSystemLibrary, fname::FNameToString, game_loop::{FEngineLoopInit, UGameEngineTick}, gmalloc::GMalloc, @@ -20,9 +19,9 @@ use patternsleuth::resolvers::unreal::{ FUObjectArrayAllocateUObjectIndex, FUObjectArrayFreeUObjectIndex, GUObjectArray, }, kismet::{FFrameStep, FFrameStepExplicitProperty, FFrameStepViaExec}, - KismetSystemLibrary, }; -use simple_log::{error, info, LogConfigBuilder}; +use patternsleuth_resolvers::{impl_try_collector, resolve}; +use simple_log::{LogConfigBuilder, error, info}; use windows::Win32::{ Foundation::HMODULE, System::{ @@ -31,36 +30,7 @@ use windows::Win32::{ }, }; -// x3daudio1_7.dll -#[no_mangle] -#[allow(non_snake_case, unused_variables)] -extern "system" fn X3DAudioCalculate() {} -#[no_mangle] -#[allow(non_snake_case, unused_variables)] -extern "system" fn X3DAudioInitialize() {} - -// d3d9.dll -#[no_mangle] -#[allow(non_snake_case, unused_variables)] -extern "system" fn D3DPERF_EndEvent() {} -#[no_mangle] -#[allow(non_snake_case, unused_variables)] -extern "system" fn D3DPERF_BeginEvent() {} - -// d3d11.dll -#[no_mangle] -#[allow(non_snake_case, unused_variables)] -extern "system" fn D3D11CreateDevice() {} - -// dxgi.dll -#[no_mangle] -#[allow(non_snake_case, unused_variables)] -extern "system" fn CreateDXGIFactory() {} -#[no_mangle] -#[allow(non_snake_case, unused_variables)] -extern "system" fn CreateDXGIFactory1() {} - -#[no_mangle] +#[unsafe(no_mangle)] #[allow(non_snake_case, unused_variables)] extern "system" fn DllMain(dll_module: HMODULE, call_reason: u32, _: *mut ()) -> bool { unsafe { @@ -106,10 +76,8 @@ pub struct FFrameKismetExecutionMessage(usize); mod resolvers { use super::*; - use patternsleuth::{ - resolvers::{futures::future::join_all, *}, - scanner::Pattern, - }; + use patternsleuth_image::scanner::Pattern; + use patternsleuth_resolvers::{futures::future::join_all, *}; impl_resolver_singleton!(collect, FFrameKismetExecutionMessage); impl_resolver_singleton!(PEImage, FFrameKismetExecutionMessage, |ctx| async { @@ -140,7 +108,7 @@ impl_try_collector! { } } -static mut GLOBALS: Option = None; +static mut GLOBALS: MaybeUninit = MaybeUninit::uninit(); pub struct Globals { resolution: DllHookResolution, @@ -173,7 +141,10 @@ impl Globals { } pub fn globals() -> &'static Globals { - unsafe { GLOBALS.as_ref().unwrap() } + #[allow(static_mut_refs)] + unsafe { + GLOBALS.assume_init_ref() + } } #[macro_export] @@ -195,26 +166,29 @@ fn dump_backtrace() { } unsafe fn patch(bin_dir: PathBuf) -> Result<()> { - let exe = patternsleuth::process::internal::read_image()?; + unsafe { + let exe = patternsleuth_image::process::internal::read_image()?; - info!("starting scan"); - let resolution = exe.resolve(DllHookResolution::resolver())?; - info!("finished scan"); + info!("starting scan"); + let resolution = resolve(&exe, DllHookResolution::resolver())?; + info!("finished scan"); - info!("results: {:?}", resolution); + info!("results: {:?}", resolution); - let guobject_array: &'static ue::FUObjectArray = - &*(resolution.guobject_array.0 as *const ue::FUObjectArray); + let guobject_array: &'static ue::FUObjectArray = + &*(resolution.guobject_array.0 as *const ue::FUObjectArray); - GLOBALS = Some(Globals { - guobject_array: guobject_array.into(), - resolution, - main_thread_id: std::thread::current().id(), - }); + #[allow(static_mut_refs)] + GLOBALS.write(Globals { + guobject_array: guobject_array.into(), + resolution, + main_thread_id: std::thread::current().id(), + }); - hooks::initialize()?; + hooks::initialize()?; - info!("initialized"); + info!("initialized"); - app::run(bin_dir) + app::run(bin_dir) + } } diff --git a/examples/dll_hook/src/ue.rs b/examples/dll_hook/src/ue.rs index 3789384..c7fc89a 100644 --- a/examples/dll_hook/src/ue.rs +++ b/examples/dll_hook/src/ue.rs @@ -6,7 +6,7 @@ use std::{ }; use windows::Win32::System::Threading::{ - EnterCriticalSection, LeaveCriticalSection, CRITICAL_SECTION, + CRITICAL_SECTION, EnterCriticalSection, LeaveCriticalSection, }; use crate::globals; diff --git a/patternsleuth/src/resolvers/unreal/ftext.rs b/patternsleuth/src/resolvers/unreal/ftext.rs deleted file mode 100644 index b73e183..0000000 --- a/patternsleuth/src/resolvers/unreal/ftext.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::fmt::Debug; - -use futures::future::join_all; - -use patternsleuth_scanner::Pattern; - -use crate::{ - resolvers::{impl_resolver_singleton, try_ensure_one}, - MemoryTrait, -}; - -/// private: __cdecl FText::FText(class FString &&) -#[derive(Debug, PartialEq)] -#[cfg_attr( - feature = "serde-resolvers", - derive(serde::Serialize, serde::Deserialize) -)] -pub struct FTextFString(pub usize); -impl_resolver_singleton!(all, FTextFString, |ctx| async { - #[derive(Debug)] - enum Directness { - Direct, - Indirect, - } - let patterns = [ - (Directness::Indirect, "40 53 48 83 ec ?? 48 8b d9 e8 | ?? ?? ?? ?? 83 4b ?? 12 48 8b c3 48 83 ?? ?? 5b c3"), - (Directness::Indirect, "eb 12 48 8d ?? 24 ?? e8 | ?? ?? ?? ?? ?? 02 00 00 00 48 8b 10 48 89 17"), - (Directness::Indirect, "eb 12 48 8d ?? 24 ?? e8 | ?? ?? ?? ?? ?? 02 00 00 00 48 8b 10 89"), - (Directness::Direct, "48 89 5C 24 10 48 89 6C 24 18 56 57 41 54 41 56 41 57 48 83 EC 40 45 33 E4 48 8B F1 41 8B DC 4C 8B F2 89 5C 24 70 41 8D 4C 24 70 E8 ?? ?? ?? FF 48 8B F8 48 85 C0 0F 84 ?? 00 00 00 49 63 5E 08 ?? 8B ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 8B ?? EB 2E 45 33 C0 48 8D 4C 24 20 8B D3 E8"), - (Directness::Direct, "48 89 5C 24 10 48 89 6C 24 18 57 48 83 EC 50 33 ED 48 8D 05 ?? ?? ?? 03 48 8B F9 48 89 6C 24 38 48 8B DA 48 89 6C 24 48 48 89 44 24 30 8D 4D 60 48 89 44 24 40 E8 ?? ?? ?? FF 4C 8B C0 48 85 C0 74 65 0F 10 44 24 30 C7 40 08 01 00 00 00 0F 10 4C 24 40 C7 40 0C 01 00 00 00 48 8D 05 ?? ?? ?? 03 49 89 00"), - (Directness::Direct, "48 89 5C 24 10 48 89 6C 24 18 56 57 41 54 41 56 41 57 48 83 EC 50 45 33 E4 48 8B F9 41 8B DC 4C 8B F2 89 9C 24 80 00 00 00 41 8D 4C 24 70 E8 ?? ?? ?? ?? 48 8B F0 48 85 C0 0F 84 98 00 00 00 49 63 5E 08 41 8B EC 4D 8B 3E 4C 89 64 24 20 89 5C 24 28 85 DB 75 05 45 8B FC EB 2E 45 33 C0 48 8D 4C 24 20 8B"), - (Directness::Direct, "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 54 41 56 41 57 48 83 EC 40 4C 8B F1 48 8B F2"), - (Directness::Direct, "48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 54 41 56 41 57 48 83 EC 40 45 33 E4 48 8B F1"), - (Directness::Direct, "40 53 56 48 83 EC 48 33 DB 48 89 6C 24 68 48 8B F1 48 89 7C 24 70 4C 89 74 24 78 4C 8B F2 89 5C 24 60 8D 4B 70 E8 ?? ?? ?? FF 48 8B F8 48 85 C0 0F 84 9E 00 00 00 49 63 5E 08 33 ED 4C 89 7C 24 40 4D 8B 3E 48 89 6C 24 20 89 5C 24 28 85 DB 75 05 45 33 FF EB 2E 45 33 C0 48 8D 4C 24 20 8B D3 E8"), - (Directness::Direct, "41 57 41 56 41 54 56 57 55 53 48 83 EC 40 48 89 D7 48 89 CE 48 8B 0D"), - // FText::AsCultureInvariant on Linux - (Directness::Indirect, "48 85 c9 74 13 f0 83 41 08 01 eb 0c 48 89 df e8 | ?? ?? ?? ?? 48 8d 43 10"), - // FText::FText signature - (Directness::Direct, "41 57 41 56 53 48 83 ec 20 49 89 f7 49 89 fe 0f 57 c0 0f 29 44 24 10 0f 29 04 24 48 8d 5c 24 10 48 89 e7 e8 ?? ?? ?? ?? 48 89 df e8 ?? ?? ?? ?? bf 60 00 00 00 e8"), - // UE 5.4 - (Directness::Direct, "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 33 F6 48 8B D9 48 89 44 24"), - ]; - - let res = join_all( - patterns - .iter() - .map(|(tag, p)| ctx.scan_tagged(tag, Pattern::new(p).unwrap())), - ) - .await; - - let mem = &ctx.image().memory; - - Ok(FTextFString(try_ensure_one(res.iter().flat_map( - |(directness, _, a)| match directness { - Directness::Direct => itertools::Either::Right(a.iter().map(|a| Ok(*a))), - Directness::Indirect => itertools::Either::Left(a.iter().map(|a| Ok(mem.rip4(*a)?))), - }, - ))?)) -}); diff --git a/patternsleuth_cli/Cargo.toml b/patternsleuth_cli/Cargo.toml index 9a66e29..8d045e0 100644 --- a/patternsleuth_cli/Cargo.toml +++ b/patternsleuth_cli/Cargo.toml @@ -11,26 +11,23 @@ name = "patternsleuth" path = "src/main.rs" [dependencies] -patternsleuth = { path = "../patternsleuth", features = ["process-external", "symbols", "serde-resolvers", "image-pe", "image-elf"] } +patternsleuth_image = { path = "../patternsleuth_image", features = ["process-external", "symbols"] } +patternsleuth_resolvers = { path = "../patternsleuth_resolvers", features = ["serde-resolvers", "image-pe", "image-elf"] } +patternsleuth_scanner = { path = "../patternsleuth_scanner" } anyhow = { workspace = true } clap = { workspace = true } colored = { workspace = true } globset = { workspace = true } iced-x86 = { workspace = true } itertools = { workspace = true } -memchr = { workspace = true } msvc-demangler = { workspace = true } -object = { workspace = true } -pdb = { workspace = true } prettytable-rs = { workspace = true } rayon.workspace = true regex = { workspace = true } -strum = { workspace = true } indicatif = { version = "0.17.7", features = ["rayon"] } rusqlite = { version = "0.30.0", features = ["bundled"] } crossbeam = "0.8.4" patricia_tree = "0.8.0" -libc = "0.2.152" serde = { workspace = true, features = ["rc"] } serde_json = "1.0.111" time = { version = "0.3.31", features = ["formatting", "macros", "local-offset"] } diff --git a/patternsleuth_cli/build.rs b/patternsleuth_cli/build.rs index 0fc47ce..9a8675a 100644 --- a/patternsleuth_cli/build.rs +++ b/patternsleuth_cli/build.rs @@ -14,8 +14,7 @@ fn main() { .args(["diff-index", "--quiet", "HEAD"]) .status() .ok() - .map(|status| !status.success()) - .unwrap_or(false) + .is_some_and(|status| !status.success()) { println!("cargo:rustc-env=GIT_DIRTY=true"); } diff --git a/patternsleuth_cli/src/db.rs b/patternsleuth_cli/src/db.rs index d8859f3..f58dae7 100644 --- a/patternsleuth_cli/src/db.rs +++ b/patternsleuth_cli/src/db.rs @@ -6,13 +6,14 @@ use std::{ use anyhow::Result; use itertools::Itertools; -use patternsleuth::{image::Image, scanner::Pattern, PatternConfig}; +use patternsleuth_image::{PatternConfig, image::Image, scanner::Pattern}; +use patternsleuth_resolvers::resolve_many; use prettytable::{Cell, Row, Table}; use rayon::prelude::*; use rusqlite::{Connection, OptionalExtension}; use crate::{ - disassemble, get_games, CommandAutoGen, CommandBuildIndex, CommandViewSymbol, GameFileEntry, + CommandAutoGen, CommandBuildIndex, CommandViewSymbol, GameFileEntry, disassemble, get_games, }; fn generate_patterns_for_symbol(symbol: &str) -> Result> { @@ -263,7 +264,7 @@ pub(crate) fn view(command: CommandViewSymbol) -> Result<()> { functions.push(Function { game: function.path, address: function.start, - data: img.memory[function.start..function.end].to_vec(), + data: img.memory.range(function.start..function.end)?.to_vec(), }); } @@ -297,15 +298,15 @@ pub(crate) fn view(command: CommandViewSymbol) -> Result<()> { games.insert(name.to_string()); - let resolution = exe.resolve_many(&resolvers); + let resolution = resolve_many(&exe, &resolvers); println!("{resolution:#x?}"); for res in resolution.into_iter().flatten() { let start = res.get().unwrap(); - let bounds = patternsleuth::disassemble::function_range(&exe, start)?; + let bounds = patternsleuth_resolvers::disassemble::function_range(&exe, start)?; functions.push(Function { game: exe_path.to_string_lossy().to_string(), address: start, - data: exe.memory[bounds].to_vec(), + data: exe.memory.range(bounds)?.to_vec(), }); } } @@ -648,7 +649,7 @@ pub(crate) fn build(command: CommandBuildIndex) -> Result<()> { |function| -> Result<()> { let range = function; - let bytes = &exe.memory[range.clone()]; + let bytes = exe.memory.range(range.clone())?; tx.send(Insert::Function(( exe_path.to_string_lossy().to_string(), diff --git a/patternsleuth_cli/src/disassemble.rs b/patternsleuth_cli/src/disassemble.rs index 860481b..df734b7 100644 --- a/patternsleuth_cli/src/disassemble.rs +++ b/patternsleuth_cli/src/disassemble.rs @@ -4,7 +4,7 @@ use colored::{ColoredString, Colorize}; use iced_x86::{ Decoder, DecoderOptions, Formatter, FormatterOutput, FormatterTextKind, IntelFormatter, OpKind, }; -use patternsleuth::{image::Image, scanner::Pattern, MemoryTrait}; +use patternsleuth_image::{image::Image, scanner::Pattern}; #[derive(Default)] struct Output { diff --git a/patternsleuth_cli/src/main.rs b/patternsleuth_cli/src/main.rs index 7ca5d4b..d4c9c15 100644 --- a/patternsleuth_cli/src/main.rs +++ b/patternsleuth_cli/src/main.rs @@ -7,20 +7,21 @@ use std::fs; use std::path::{Path, PathBuf}; use std::str::FromStr; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; +use clap::Parser; use clap::builder::{ IntoResettable, PossibleValue, PossibleValuesParser, TypedValueParser, ValueParser, }; -use clap::Parser; use indicatif::ProgressBar; use itertools::Itertools; use patricia_tree::StringPatriciaMap; -use patternsleuth::image::Image; -use patternsleuth::resolvers::{resolvers, NamedResolver}; +use patternsleuth_image::image::Image; +use patternsleuth_image::{PatternConfig, Resolution}; +use patternsleuth_resolvers::{NamedResolver, resolve_many, resolvers}; -use patternsleuth::scanner::Xref; -use patternsleuth::symbols::Symbol; -use patternsleuth::{scanner::Pattern, PatternConfig, Resolution}; +use patternsleuth_image::symbols::Symbol; +use patternsleuth_scanner::Pattern; +use patternsleuth_scanner::Xref; #[derive(Parser)] enum Commands { @@ -213,7 +214,7 @@ fn find_ext, E: AsRef>(dir: P, ext: &[E]) -> Result Result<()> { - use tracing_subscriber::{fmt, fmt::format::FmtSpan, EnvFilter}; + use tracing_subscriber::{EnvFilter, fmt, fmt::format::FmtSpan}; fmt() .compact() @@ -291,7 +292,7 @@ fn scan(command: CommandScan) -> Result<()> { use colored::Colorize; use indicatif::ProgressIterator; use itertools::join; - use prettytable::{format, row, Cell, Row, Table}; + use prettytable::{Cell, Row, Table, format, row}; enum Output { Stdout, @@ -329,7 +330,7 @@ fn scan(command: CommandScan) -> Result<()> { #[allow(unused_assignments)] let mut bin_data = None; - let (name, exe) = match game { + let (name, img) = match game { GameEntry::File(GameFileEntry { name, exe_path }) => { output.println(format!("{:?} {:?}", name, exe_path.display())); @@ -357,14 +358,14 @@ fn scan(command: CommandScan) -> Result<()> { ( Cow::Owned(format!("PID={pid}")), - patternsleuth::process::external::read_image_from_pid(*pid)?, + patternsleuth_image::process::external::read_image_from_pid(*pid)?, ) } }; games.insert(name.to_string()); - let scan = exe.scan(&patterns)?; + let scan = img.scan(&patterns)?; // group results by Sig let folded_scans = scan @@ -393,7 +394,7 @@ fn scan(command: CommandScan) -> Result<()> { "{}\n{}", m.0.name, disassemble::disassemble( - &exe, + &img, m.1.address, m.0.scan.scan_type.get_pattern() ) @@ -402,7 +403,7 @@ fn scan(command: CommandScan) -> Result<()> { } cells.push(Cell::new(&table.to_string())); } else if command.disassemble_merged { - cells.push(Cell::new({ + cells.push(Cell::new(&{ let cells = sig_scans .iter() .fold( @@ -416,7 +417,7 @@ fn scan(command: CommandScan) -> Result<()> { // sort by pattern name, then match address .sorted_by_key(|&data| data.0) .map(|(m, counts)| { - let dis = disassemble::disassemble(&exe, m.address, None); + let dis = disassemble::disassemble(&img, m.address, None); let mut lines = vec![]; for (name, count) in counts.iter().sorted_by_key(|e| e.0) { @@ -439,10 +440,10 @@ fn scan(command: CommandScan) -> Result<()> { table.add_row(Row::new(cells)); - &table.to_string() + table.to_string() })); } else { - cells.push(Cell::new({ + cells.push(Cell::new(&{ let mut lines = sig_scans .iter() // group and count matches by (pattern name, address) @@ -468,7 +469,7 @@ fn scan(command: CommandScan) -> Result<()> { format!("{:016x} {:?}{}", m.1.address, m.0, count) .normal() .to_string(), - exe.symbols + img.symbols .as_ref() .and_then(|symbols| symbols.get(&m.1.address)), ) @@ -484,7 +485,7 @@ fn scan(command: CommandScan) -> Result<()> { )); } } - &join(lines.iter().map(|(line, _)| line), "\n").to_string() + join(lines.iter().map(|(line, _)| line), "\n").to_string() })); } } else { @@ -501,7 +502,7 @@ fn scan(command: CommandScan) -> Result<()> { }; let resolution = tracing::info_span!("scan", game = game_name) - .in_scope(|| exe.resolve_many(&dyn_resolvers)); + .in_scope(|| resolve_many(&img, &dyn_resolvers)); for (resolver, resolution) in resolvers.iter().zip(&resolution) { table.add_row(Row::new( @@ -694,7 +695,7 @@ fn report(command: CommandReport) -> Result<()> { } }; - let resolution = exe.resolve_many(&resolvers); + let resolution = resolve_many(&exe, &resolvers); let map = command .resolver @@ -734,7 +735,7 @@ fn report(command: CommandReport) -> Result<()> { } fn diff_report(command: CommandDiffReport) -> Result<()> { use colored::Colorize; - use patternsleuth::resolvers::{Resolution, ResolveError}; + use patternsleuth_resolvers::{Resolution, ResolveError}; use prettytable::{Cell, Row, Table}; type Report = BTreeMap, ResolveError>>>; @@ -981,7 +982,7 @@ fn get_games(filter: impl AsRef<[String]>) -> Result> { return Ok(None); } - let Some(exe_path) = find_ext(entry.path(), &["exe", "elf"]) + let Some(exe_path) = find_ext(entry.path(), &["exe", "elf", "dmp"]) .transpose() .transpose()? else { diff --git a/patternsleuth/Cargo.toml b/patternsleuth_image/Cargo.toml similarity index 77% rename from patternsleuth/Cargo.toml rename to patternsleuth_image/Cargo.toml index ec225b5..828fb93 100644 --- a/patternsleuth/Cargo.toml +++ b/patternsleuth_image/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "patternsleuth" +name = "patternsleuth_image" repository.workspace = true authors.workspace = true license.workspace = true @@ -9,21 +9,13 @@ edition.workspace = true [dependencies] patternsleuth_scanner = { path = "../patternsleuth_scanner" } anyhow = { workspace = true } -memchr = { workspace = true } object = { workspace = true } -rayon = { workspace = true } -strum = { workspace = true } pdb = { workspace = true, optional = true } msvc-demangler = { workspace = true, optional = true} -iced-x86.workspace = true -futures = "0.3.30" -futures-scopes = "0.2.0" -inventory = "0.3.14" itertools.workspace = true serde = { workspace = true, optional = true, features = ["derive"] } -typetag = { version = "0.2.15", optional = true } gimli = { version = "0.28.1", optional = true } -tracing = "0.1.40" +minidump = "0.24.1" [target.'cfg(target_os = "linux")'.dependencies] libc = { version = "0.2.152", optional = true } @@ -39,7 +31,7 @@ windows = { workspace = true, optional = true, features = [ [features] default = [] -serde-resolvers = ["dep:serde", "dep:typetag"] +serde-resolvers = ["dep:serde"] symbols = ["dep:pdb", "dep:msvc-demangler"] process-external = ["image-pe", "dep:libc", "dep:windows"] process-internal = ["dep:libc", "dep:windows"] diff --git a/patternsleuth/src/image/elf.rs b/patternsleuth_image/src/image/elf.rs similarity index 94% rename from patternsleuth/src/image/elf.rs rename to patternsleuth_image/src/image/elf.rs index 92e45f7..aa22daf 100644 --- a/patternsleuth/src/image/elf.rs +++ b/patternsleuth_image/src/image/elf.rs @@ -7,10 +7,10 @@ use gimli::{BaseAddresses, CieOrFde, EhFrame, EhFrameHdr, NativeEndian, UnwindSe #[cfg(feature = "symbols")] use crate::uesym; -use anyhow::{bail, Context, Error, Result}; +use anyhow::{Context, Error, Result, bail}; use object::{ - elf::ProgramHeader64, read::elf::ElfFile64, read::elf::ProgramHeader, Endianness, File, Object, - ObjectSection, SectionKind, + Endianness, File, Object, ObjectSection, SectionKind, elf::ProgramHeader64, + read::elf::ElfFile64, read::elf::ProgramHeader, }; pub struct ElfImage { @@ -79,7 +79,7 @@ impl ElfImage { // read_inner impl ElfImage { /// Read and parse ELF object, using data from memory - pub fn read_inner_memory<'data, P: AsRef>( + fn read_inner_memory<'data, P: AsRef>( base_address: usize, #[allow(unused_variables)] exe_path: Option

, linked: bool, @@ -133,7 +133,7 @@ impl ElfImage { .iter() .find(|s| s.name == ".text") .context("Cannot find .text section")? - .address(); + .address; let ehframe_hdr_start = base_address + p.p_vaddr as usize; let bases = BaseAddresses::default() .set_eh_frame_hdr(ehframe_hdr_start as _) @@ -233,7 +233,7 @@ impl ElfImage { }; Ok(Image { base_address, - memory, + memory: Box::new(memory), #[cfg(feature = "symbols")] symbols, imports: HashMap::default(), @@ -252,17 +252,17 @@ impl ElfImage { ) -> Result, anyhow::Error> { let base_address = base_addr.unwrap_or(object.relative_address_base() as usize); let linked = base_addr.is_some(); - let calc_kind = |flag: u32| { - if flag & object::elf::PF_X == object::elf::PF_X { - SectionKind::Text - } else if flag & object::elf::PF_W == object::elf::PF_W { - SectionKind::Data - } else if flag & object::elf::PF_R == object::elf::PF_R { - SectionKind::ReadOnlyData - } else { - SectionKind::Unknown - } - }; + //let calc_kind = |flag: u32| { + // if flag & object::elf::PF_X == object::elf::PF_X { + // SectionKind::Text + // } else if flag & object::elf::PF_W == object::elf::PF_W { + // SectionKind::Data + // } else if flag & object::elf::PF_R == object::elf::PF_R { + // SectionKind::ReadOnlyData + // } else { + // SectionKind::Unknown + // } + //}; // the elf may not contains section table if it's in memory, use phdr instead. if let File::Elf64(object) = object { @@ -317,13 +317,13 @@ impl ElfImage { NamedMemorySection::new( section_name, base_address + segment.p_vaddr as usize, - calc_kind(segment.p_flags), + crate::SectionFlags::default(), &object.data()[offset_range], ) }) .collect::>(); - let memory = Memory { sections }; + let memory = Memory::new_sections(sections); Self::read_inner_memory(base_address, exe_path, linked, memory, object) } else { diff --git a/patternsleuth/src/image/macros.rs b/patternsleuth_image/src/image/macros.rs similarity index 93% rename from patternsleuth/src/image/macros.rs rename to patternsleuth_image/src/image/macros.rs index 8151761..35779b4 100644 --- a/patternsleuth/src/image/macros.rs +++ b/patternsleuth_image/src/image/macros.rs @@ -9,6 +9,7 @@ macro_rules! image_type_dispatch { } ) => { image_type_dispatch!(@define_imagetype $enum_name_it $enum_tt); + #[allow(unused)] impl<'data> Image<'data> { $( pub fn $fnname_it(&self, $($arg: $arg_ty),*) -> $ret { @@ -32,6 +33,8 @@ macro_rules! image_type_dispatch { #[cfg(feature = $img_feature)] $enum_name_it::$img_ident(inner) => inner.$fnname_it($self, $args_tt), )* + #[allow(unreachable_patterns)] + _ => unreachable!(), } }; @@ -41,6 +44,8 @@ macro_rules! image_type_dispatch { #[cfg(feature = $img_feature)] $enum_name_it::$img_ident(inner) => inner.$fnname_it($self), )* + #[allow(unreachable_patterns)] + _ => unreachable!(), } }; diff --git a/patternsleuth/src/image/mod.rs b/patternsleuth_image/src/image/mod.rs similarity index 50% rename from patternsleuth/src/image/mod.rs rename to patternsleuth_image/src/image/mod.rs index 62c9971..3b35974 100644 --- a/patternsleuth/src/image/mod.rs +++ b/patternsleuth_image/src/image/mod.rs @@ -8,14 +8,12 @@ use crate::*; use anyhow::Error; #[cfg(feature = "image-elf")] use elf::ElfImage; +use itertools::Itertools; #[cfg(feature = "image-pe")] use pe::PEImage; use macros::*; -#[cfg(not(any(feature = "image-pe", feature = "image-elf")))] -compile_error!("requires at least one of image-pe or image-elf features"); - image_type_dispatch! { @enum ImageType as _image_type_reflection { PEImage(PEImage, "image-pe"), @@ -32,10 +30,11 @@ image_type_dispatch! { } pub use _image_type_reflection as image_type_reflection; +use pe::PEImageBuilder; pub struct Image<'data> { pub base_address: usize, - pub memory: Memory<'data>, + pub memory: Box + 'data>, #[cfg(feature = "symbols")] pub symbols: Option>, pub imports: HashMap>, @@ -44,41 +43,121 @@ pub struct Image<'data> { // Type-independent impl<'data> Image<'data> { + #[allow(unused)] pub fn read>( base_addr: Option, data: &'data [u8], exe_path: Option

, cache_functions: bool, ) -> Result> { - let object = object::File::parse(data)?; + let object = match object::File::parse(data) { + Err(e) if e.to_string() == "Unknown file magic" => { + fn merge_adjacent_slices<'a, T>(a: &'a [T], b: &'a [T]) -> &'a [T] { + assert_eq!( + unsafe { a.as_ptr().add(a.len()) }, + b.as_ptr(), + "Slices are not adjacent in memory" + ); + + unsafe { std::slice::from_raw_parts(a.as_ptr(), a.len() + b.len()) } + } + + let dump = minidump::Minidump::read(data)?; + + let mem = dump.get_memory().unwrap(); + let modules = dump.get_stream::()?; + let main = modules.main_module().unwrap(); + + //let mut bytes: Option<&'data [u8]> = None; + let base_address = main.raw.base_of_image; + let size = main.raw.size_of_image; + let main_range = base_address..(base_address + size as u64); + + // verify sorted by address + for (a, b) in mem.by_addr().tuple_windows() { + assert!(a.base_address() < b.base_address()); + } + + let mut sections = vec![]; + let mut main_bytes = vec![0; size as usize]; + + let mut chunk: Option<(&[u8], u64)> = None; + + for mem in mem.by_addr() { + let bytes = unsafe { std::mem::transmute::<&[u8], &'data [u8]>(mem.bytes()) }; + if main_range.contains(&mem.base_address()) { + //println!("XX {:X} {:X}", mem.base_address(), mem.size()); + let start = mem.base_address() - base_address; + main_bytes[start as usize..(start + mem.size()) as usize] + .copy_from_slice(bytes); + } + + if let Some((slice, address)) = chunk { + // check if continuous with existing slice + if address + slice.len() as u64 == mem.base_address() { + // extend existing slice + chunk = Some((merge_adjacent_slices(slice, bytes), address)); + } else { + // add section + sections.push(NamedMemorySection::new( + "".to_string(), + address as usize, + SectionFlags::default(), + slice, + )); + // reset slice + chunk = Some((bytes, mem.base_address())); + } + } else { + // set slice + chunk = Some((bytes, mem.base_address())); + } + + //println!("{inc} {:X} {:X}", mem.base_address(), mem.size()); + } + // add any remaining section + if let Some((slice, address)) = chunk { + sections.push(NamedMemorySection::new( + "".to_string(), + address as usize, + SectionFlags::default(), + slice, + )); + } + + let object = object::File::parse(main_bytes.as_slice())?; + + match object { + object::File::Pe64(_) => { + return PEImageBuilder::new() + .object(object)? + .memory(Box::new(Memory::new_sections(sections))) + .exe_path(exe_path.as_ref().map(AsRef::as_ref)) + .build(); + } + _ => unreachable!(), + } + } + Err(e) => return Err(e.into()), + Ok(o) => o, + }; + match object { #[cfg(feature = "image-elf")] object::File::Elf64(_) => { ElfImage::read_inner(base_addr, exe_path, cache_functions, object) } #[cfg(feature = "image-pe")] - object::File::Pe64(_) => { - PEImage::read_inner(base_addr, exe_path, cache_functions, object) - } + object::File::Pe64(_) => PEImageBuilder::new() + .memory_from_object(object)? + .exe_path(exe_path.as_ref().map(AsRef::as_ref)) + .build(), _ => Err(Error::msg("Unsupported file format")), } } pub fn builder() -> ImageBuilder { Default::default() } - pub fn resolve( - &self, - resolver: &'static resolvers::ResolverFactory, - ) -> resolvers::Result { - resolvers::resolve(self, resolver) - } - - pub fn resolve_many( - &self, - resolvers: &[fn() -> &'static resolvers::DynResolverFactory], - ) -> Vec>> { - resolvers::resolve_many(self, resolvers) - } pub fn scan<'patterns, S>( &self, @@ -108,29 +187,15 @@ impl<'data> Image<'data> { .iter() .filter_map(|scan| { scan.scan - .section - .map(|s| s == section.kind()) - .unwrap_or(true) - .then(|| { - scan.scan - .scan_type - .get_pattern() - .map(|pattern| (scan, pattern)) - }) - .flatten() + .scan_type + .get_pattern() + .map(|pattern| (scan, pattern)) }) .unzip(); let (xref_scans, xrefs): (Vec<_>, Vec<_>) = scan_queue .iter() - .filter_map(|scan| { - scan.scan - .section - .map(|s| s == section.kind()) - .unwrap_or(true) - .then(|| scan.scan.scan_type.get_xref().map(|xref| (scan, xref))) - .flatten() - }) + .filter_map(|scan| scan.scan.scan_type.get_xref().map(|xref| (scan, xref))) .unzip(); let scan_results = scanner::scan_pattern(&patterns, base_address, data) diff --git a/patternsleuth/src/image/pe.rs b/patternsleuth_image/src/image/pe.rs similarity index 75% rename from patternsleuth/src/image/pe.rs rename to patternsleuth_image/src/image/pe.rs index cb9e53c..916d53e 100644 --- a/patternsleuth/src/image/pe.rs +++ b/patternsleuth_image/src/image/pe.rs @@ -1,13 +1,13 @@ use std::collections::{HashMap, HashSet}; use std::ops::Range; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use itertools::Itertools; use super::{Image, ImageType}; #[cfg(feature = "symbols")] use crate::symbols; -use crate::{Memory, MemoryAccessError, MemoryTrait, RuntimeFunction}; +use crate::{Memory, MemoryAccessError, RuntimeFunction, SectionedMemoryTrait}; use object::Object; pub struct PEImage { @@ -65,9 +65,9 @@ impl PEImage { let section = image.memory.get_section_containing(unwind_addr)?; - let has_chain_info = section.section.index(unwind_addr)? >> 3 == 0x4; + let has_chain_info = section.index(unwind_addr)? >> 3 == 0x4; if has_chain_info { - let unwind_code_count = section.section.index(unwind_addr + 2)?; + let unwind_code_count = section.index(unwind_addr + 2)?; unwind_addr += 4 + 2 * unwind_code_count as usize; if unwind_addr % 4 != 0 { @@ -169,7 +169,7 @@ impl Image<'_> { #[allow(irrefutable_let_patterns)] if let ImageType::PEImage(ref mut pe) = self.image_type { for i in pe.exception_directory_range.clone().step_by(12) { - let f = RuntimeFunction::read(&self.memory, self.base_address, i)?; + let f = RuntimeFunction::read(self.memory.as_ref(), self.base_address, i)?; pe.exception_children_cache.insert(f.range.start, vec![]); let Ok(section) = self.memory.get_section_containing(f.unwind) else { @@ -179,9 +179,9 @@ impl Image<'_> { }; let mut unwind = f.unwind; - let has_chain_info = section.section.index(unwind)? >> 3 == 0x4; + let has_chain_info = section.index(unwind)? >> 3 == 0x4; if has_chain_info { - let unwind_code_count = section.section.index(unwind + 2)?; + let unwind_code_count = section.index(unwind + 2)?; unwind += 4 + 2 * unwind_code_count as usize; if unwind % 4 != 0 { @@ -218,25 +218,15 @@ impl Image<'_> { } } -impl PEImage { - /// Read and parse ELF object, using data from memory - pub fn read_inner_memory<'data, P: AsRef>( - base_address: usize, - #[allow(unused_variables)] exe_path: Option

, - cache_functions: bool, - memory: Memory<'data>, - object: object::File<'_>, - ) -> Result, anyhow::Error> { - #[cfg(feature = "symbols")] - let symbols = if let Some(exe_path) = exe_path { - let pdb_path = exe_path.as_ref().with_extension("pdb"); - pdb_path - .exists() - .then(|| symbols::dump_pdb_symbols(pdb_path, base_address)) - .transpose()? - } else { - None - }; +/// Helper struct to extract info from object::File without hitting lifetime issues with memory +struct PEImagePartial { + base_address: usize, + imports: HashMap>, + exception_directory_range: Range, +} +impl PEImagePartial { + fn new(object: object::File<'_>) -> Result { + let base_address = object.relative_address_base() as usize; let get_ex_dir = || -> Result> { Ok(match object { @@ -255,9 +245,9 @@ impl PEImage { let get_imports = || -> Result<_> { Ok(match object { object::File::Pe64(ref inner) => { + use object::LittleEndian as LE; use object::pe::ImageNtHeaders64; use object::read::pe::ImageThunkData; - use object::LittleEndian as LE; let mut imports: HashMap> = Default::default(); @@ -287,15 +277,41 @@ impl PEImage { _ => bail!("not a PE file"), }) }; + Ok(Self { + base_address, + imports: get_imports().unwrap_or_default(), + exception_directory_range: get_ex_dir().unwrap_or_default(), + }) + } +} + +impl PEImage { + fn read_inner_memory<'data, P: AsRef>( + base_address: usize, + #[allow(unused_variables)] exe_path: Option

, + cache_functions: bool, + memory: Box + 'data>, + partial: PEImagePartial, + ) -> Result, anyhow::Error> { + #[cfg(feature = "symbols")] + let symbols = if let Some(exe_path) = exe_path { + let pdb_path = exe_path.as_ref().with_extension("pdb"); + pdb_path + .exists() + .then(|| symbols::dump_pdb_symbols(pdb_path, base_address)) + .transpose()? + } else { + None + }; let mut new = Image { base_address, memory, #[cfg(feature = "symbols")] symbols, - imports: get_imports().unwrap_or_default(), + imports: partial.imports, image_type: ImageType::PEImage(PEImage { - exception_directory_range: get_ex_dir().unwrap_or_default(), + exception_directory_range: partial.exception_directory_range, exception_children_cache: Default::default(), }), }; @@ -305,15 +321,79 @@ impl PEImage { } Ok(new) } +} - pub fn read_inner>( - base_addr: Option, - exe_path: Option

, - cache_functions: bool, - object: object::File<'_>, - ) -> Result, anyhow::Error> { - let base_address = base_addr.unwrap_or(object.relative_address_base() as usize); - let memory = Memory::new(&object)?; - Self::read_inner_memory(base_address, exe_path, cache_functions, memory, object) +pub struct PEImageBuilder<'data, 'path> { + partial: Option, + memory: Option + 'data>>, + exe_path: Option<&'path std::path::Path>, + cache_functions: bool, +} +impl Default for PEImageBuilder<'_, '_> { + fn default() -> Self { + Self::new() + } +} +impl<'data, 'path> PEImageBuilder<'data, 'path> { + pub fn new() -> Self { + Self { + partial: None, + memory: None, + exe_path: None, + cache_functions: false, + } + } + pub fn cache_functions(self, cache_functions: bool) -> Self { + Self { + cache_functions, + ..self + } + } + pub fn exe_path(self, exe_path: Option<&'path std::path::Path>) -> Self { + Self { exe_path, ..self } + } + pub fn memory(self, memory: Box + 'data>) -> Self { + Self { + memory: Some(memory), + ..self + } + } + pub fn object(self, object: object::File<'_>) -> Result { + let partial = PEImagePartial::new(object)?; + Ok(Self { + partial: Some(partial), + ..self + }) + } + pub fn object_from_memory( + self, + memory: Box + 'data>, + base_address: usize, + ) -> Result { + let object = object::File::parse(memory.range_from(base_address..)?)?; + let partial = PEImagePartial::new(object)?; + Ok(Self { + partial: Some(partial), + memory: Some(memory), + ..self + }) + } + pub fn memory_from_object(self, object: object::File<'data>) -> Result { + let memory = Box::new(Memory::new(&object)?); + let partial = PEImagePartial::new(object)?; + Ok(Self { + partial: Some(partial), + memory: Some(memory), + ..self + }) + } + pub fn build(self) -> Result> { + PEImage::read_inner_memory( + self.partial.as_ref().unwrap().base_address, + self.exe_path, + self.cache_functions, + self.memory.unwrap(), + self.partial.unwrap(), + ) } } diff --git a/patternsleuth/src/lib.rs b/patternsleuth_image/src/lib.rs similarity index 56% rename from patternsleuth/src/lib.rs rename to patternsleuth_image/src/lib.rs index 2b9ec17..b8175a3 100644 --- a/patternsleuth/src/lib.rs +++ b/patternsleuth_image/src/lib.rs @@ -1,6 +1,5 @@ pub mod image; pub mod process; -pub mod resolvers; #[cfg(feature = "symbols")] pub mod symbols; #[cfg(feature = "symbols")] @@ -10,27 +9,20 @@ pub mod scanner { pub use patternsleuth_scanner::*; } +use itertools::Itertools; use scanner::{Pattern, Xref}; use std::{ borrow::Cow, collections::HashMap, - ops::{Index, Range, RangeFrom, RangeTo}, + ops::{Range, RangeFrom, RangeTo}, path::Path, }; -use anyhow::{bail, Context, Result}; -use object::{File, Object, ObjectSection}; +use anyhow::{Context, Result, bail}; +use object::{File, Object, ObjectSection, ReadRef}; use image::Image; -pub struct ResolveContext<'data, 'pattern> { - pub exe: &'data Image<'data>, - pub memory: &'data Memory<'data>, - pub section: String, - pub match_address: usize, - pub scan: &'pattern Scan, -} - #[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct Resolution { pub address: usize, @@ -134,7 +126,7 @@ pub struct RuntimeFunction { } impl RuntimeFunction { pub fn read<'data>( - memory: &impl MemoryTrait<'data>, + memory: &(impl MemoryTrait<'data> + ?Sized), base_address: usize, address: usize, ) -> Result { @@ -154,6 +146,30 @@ impl RuntimeFunction { } } +pub trait SectionedMemoryTrait<'data>: MemoryTrait<'data> + Send + Sync { + fn sections(&self) -> Box> + '_>; + fn get_section_containing( + &self, + address: usize, + ) -> Result<&dyn SectionTrait<'data>, MemoryAccessError> { + self.sections() + .find(|section| { + address >= section.address() && address < section.address() + section.data().len() + }) + .ok_or(MemoryAccessError::MemoryOutOfBoundsError) + } + fn read(&self, address: usize, buf: &mut [u8]) -> Result<(), MemoryAccessError> { + buf.copy_from_slice( + self.get_section_containing(address)? + .range(address..address + buf.len())?, + ); + Ok(()) + } +} +pub trait SectionTrait<'a>: MemoryBlockTrait<'a> + MemoryTrait<'a> + Send + Sync { + fn name(&self) -> &str; +} + #[derive(Debug, Clone, PartialEq)] #[cfg_attr( feature = "serde-resolvers", @@ -283,6 +299,15 @@ pub trait MemoryTrait<'data> { Ok(String::from_utf16(data)?) } + + fn captures( + &self, + pattern: &Pattern, + address: usize, + ) -> Result>>, MemoryAccessError> { + // TODO bounds check data passed to captures + Ok(pattern.captures(self.range_from(address..)?, address, 0)) + } } impl<'data, T: MemoryBlockTrait<'data>> MemoryTrait<'data> for T { @@ -319,39 +344,30 @@ impl<'data> MemoryTrait<'data> for Memory<'data> { } } -pub struct MemorySection<'data> { - address: usize, - data: Cow<'data, [u8]>, -} -impl<'data> MemoryBlockTrait<'data> for MemorySection<'data> { - fn address(&self) -> usize { - self.address - } - fn data(&self) -> &[u8] { - &self.data - } +#[derive(Default, Debug, PartialEq)] +pub struct SectionFlags { + kind: Option, } pub struct NamedMemorySection<'data> { name: String, - kind: object::SectionKind, - section: MemorySection<'data>, + flags: SectionFlags, + address: usize, + data: Cow<'data, [u8]>, } impl<'data> NamedMemorySection<'data> { - fn new>>( + fn new>>( name: String, address: usize, - kind: object::SectionKind, - data: T, + flags: SectionFlags, + data: D, ) -> Self { Self { name, - kind, - section: MemorySection { - address, - data: data.into(), - }, + flags, + address, + data: data.into(), } } } @@ -359,82 +375,116 @@ impl NamedMemorySection<'_> { pub fn name(&self) -> &str { &self.name } - pub fn kind(&self) -> object::SectionKind { - self.kind - } - pub fn address(&self) -> usize { - self.section.address() - } - pub fn data(&self) -> &[u8] { - self.section.data() - } pub fn len(&self) -> usize { - self.section.data.len() + self.data.len() } pub fn is_empty(&self) -> bool { - self.section.data.is_empty() + self.data.is_empty() } } impl<'data> MemoryBlockTrait<'data> for NamedMemorySection<'data> { fn address(&self) -> usize { - self.section.address() + self.address } fn data(&self) -> &[u8] { - self.section.data() + &self.data } } -pub struct Memory<'data> { +struct Memory<'data> { sections: Vec>, + section_addresses: Vec, +} +impl<'data> SectionedMemoryTrait<'data> for Memory<'data> { + fn sections(&self) -> Box> + '_> { + Box::new( + self.sections + .iter() + .map(|s| -> &dyn SectionTrait<'data> { s }), + ) + } + fn get_section_containing( + &self, + address: usize, + ) -> Result<&dyn SectionTrait<'data>, MemoryAccessError> { + // use specialized version which is much faster + self.get_section_containing(address) + .map(|s| -> &dyn SectionTrait<'data> { s }) + } +} +impl<'data> SectionTrait<'data> for NamedMemorySection<'data> { + fn name(&self) -> &str { + &self.name + } } impl<'data> Memory<'data> { + pub fn new_sections(mut sections: Vec>) -> Self { + sections.sort_by_key(|s| s.address); + // verify non-overlapping sections (dumps sometimes contain them so is disable ¯\_(ツ)_/¯) + //for (a, b) in sections.iter().tuple_windows() { + // assert!( + // a.address + a.data.len() < b.address, + // "sections have overlapping memory" + // ); + //} + Self { + section_addresses: sections.iter().map(|s| s.address).collect(), + sections, + } + } pub fn new(object: &File<'data>) -> Result { - Ok(Self { - sections: object + Ok(Self::new_sections( + object .sections() .map(|s| { Ok(NamedMemorySection::new( s.name()?.to_string(), s.address() as usize, - s.kind(), + SectionFlags { + kind: Some(s.kind()), + }, s.data()?, )) }) .collect::>>()?, - }) + )) } pub fn new_external_data(sections: Vec<(object::Section<'_, '_>, Vec)>) -> Result { - Ok(Self { - sections: sections + Ok(Self::new_sections( + sections .into_iter() .map(|(s, d)| { Ok(NamedMemorySection::new( s.name()?.to_string(), s.address() as usize, - s.kind(), + SectionFlags { + kind: Some(s.kind()), + }, d, )) }) .collect::>>()?, - }) + )) } pub fn new_internal_data( sections: Vec<(object::Section<'_, '_>, &'data [u8])>, ) -> Result { - Ok(Self { - sections: sections + Ok(Self::new_sections( + sections .into_iter() .map(|(s, d)| { Ok(NamedMemorySection::new( s.name()?.to_string(), s.address() as usize, - s.kind(), + SectionFlags { + kind: Some(s.kind()), + }, d, )) }) .collect::>>()?, - }) + )) } pub fn sections(&self) -> &[NamedMemorySection] { &self.sections @@ -443,61 +493,21 @@ impl<'data> Memory<'data> { &self, address: usize, ) -> Result<&NamedMemorySection<'data>, MemoryAccessError> { - self.sections - .iter() - .find(|section| { - address >= section.section.address - && address < section.section.address + section.section.data.len() - }) - .ok_or(MemoryAccessError::MemoryOutOfBoundsError) - } - pub fn find(&self, kind: object::SectionKind, filter: F) -> Option - where - F: Fn(usize, &[u8]) -> bool, - { - self.sections.iter().find_map(|section| { - if section.kind == kind { - section - .section - .data - .windows(4) - .enumerate() - .find_map(|(i, slice)| { - filter(section.section.address + i, slice) - .then_some(section.section.address + i) - }) - } else { - None - } - }) - } -} -impl Index for Memory<'_> { - type Output = u8; - fn index(&self, index: usize) -> &Self::Output { - self.sections - .iter() - .find_map(|section| section.section.data.get(index - section.section.address)) - .unwrap() - } -} -impl Index> for Memory<'_> { - type Output = [u8]; - fn index(&self, index: Range) -> &Self::Output { - self.sections - .iter() - .find_map(|section| { - if index.start >= section.section.address - && index.end <= section.section.address + section.section.data.len() - { - let relative_range = - index.start - section.section.address..index.end - section.section.address; - Some(§ion.section.data[relative_range]) - } else { - None + match self.section_addresses.binary_search(&address) { + Ok(idx) => return Ok(&self.sections[idx]), + // Not an exact match, so we need the insertion point - 1 + Err(idx) if idx > 0 => { + let prev_idx = idx - 1; + // Check if address is within the range + let section = &self.sections[prev_idx]; + if address < section.address + section.data.len() { + return Ok(section); } - }) - .unwrap() + } + _ => {} + } + + Err(MemoryAccessError::MemoryOutOfBoundsError) } } @@ -519,205 +529,3 @@ impl Addressable for patternsleuth_scanner::Capture<'_> { u32::from_le_bytes(self.data.try_into().unwrap()) } } - -pub trait Matchable<'data> { - fn captures( - &'data self, - pattern: &Pattern, - address: usize, - ) -> Result>>, MemoryAccessError>; -} - -impl<'data> Matchable<'data> for Memory<'data> { - fn captures( - &'data self, - pattern: &Pattern, - address: usize, - ) -> Result>>, MemoryAccessError> { - let s = self.get_section_containing(address)?; - // TODO bounds check data passed to captures - Ok(pattern.captures(s.data(), s.address(), address - s.address())) - } -} - -pub mod disassemble { - use std::{collections::HashSet, ops::Range}; - - use iced_x86::{Decoder, DecoderOptions, FlowControl, Formatter, Instruction, NasmFormatter}; - - use crate::{Image, MemoryAccessError, MemoryTrait}; - - pub fn function_range( - exe: &Image<'_>, - address: usize, - ) -> Result, MemoryAccessError> { - let min = address; - let mut max = min; - disassemble(exe, address, |inst| { - let cur = inst.ip() as usize; - if Some(address) != exe.get_root_function(cur)?.map(|f| f.range.start) { - return Ok(Control::Break); - } - max = max.max(cur + inst.len()); - Ok(Control::Continue) - })?; - Ok(min..max) - } - - pub fn disassemble_single<'mem, 'img: 'mem>( - exe: &'img Image<'mem>, - address: usize, - ) -> Result, MemoryAccessError> { - Ok(Decoder::with_ip( - 64, - exe.memory.range_from(address..)?, - address as u64, - DecoderOptions::NONE, - ) - .iter() - .next()) - } - - pub enum Control { - Continue, - Break, - Exit, - } - - pub fn disassemble<'mem, 'img: 'mem, F>( - exe: &'img Image<'mem>, - address: usize, - mut visitor: F, - ) -> Result<(), MemoryAccessError> - where - F: FnMut(&Instruction) -> Result, - { - struct Ctx<'mem, 'img: 'mem> { - exe: &'img Image<'mem>, - queue: Vec, - visited: HashSet, - address: usize, - block: &'mem [u8], - decoder: Decoder<'mem>, - instruction: Instruction, - } - - let block = exe.memory.range_from(address..)?; - let mut ctx = Ctx { - exe, - queue: Default::default(), - visited: Default::default(), - address, - block, - decoder: Decoder::with_ip(64, block, address as u64, DecoderOptions::NONE), - instruction: Default::default(), - }; - - impl Ctx<'_, '_> { - #[allow(unused)] - fn print(&self) { - let mut formatter = NasmFormatter::new(); - //formatter.options_mut().set_digit_separator("`"); - //formatter.options_mut().set_first_operand_char_index(10); - - let mut output = String::new(); - formatter.format(&self.instruction, &mut output); - - // Eg. "00007FFAC46ACDB2 488DAC2400FFFFFF lea rbp,[rsp-100h]" - print!("{:016X} ", self.instruction.ip()); - let start_index = self.instruction.ip() as usize - self.address; - let instr_bytes = &self.block[start_index..start_index + self.instruction.len()]; - for b in instr_bytes.iter() { - print!("{:02X}", b); - } - if instr_bytes.len() < 0x10 { - for _ in 0..0x10 - instr_bytes.len() { - print!(" "); - } - } - println!(" {}", output); - } - fn start(&mut self, address: usize) -> Result<(), MemoryAccessError> { - //println!("starting at {address:x}"); - self.address = address; - self.block = self.exe.memory.range_from(self.address..)?; - self.decoder = - Decoder::with_ip(64, self.block, self.address as u64, DecoderOptions::NONE); - Ok(()) - } - /// Returns true if pop was successful - fn pop(&mut self) -> Result { - Ok(if let Some(next) = self.queue.pop() { - self.start(next)?; - true - } else { - false - }) - } - } - - while ctx.decoder.can_decode() { - ctx.decoder.decode_out(&mut ctx.instruction); - - let addr = ctx.instruction.ip() as usize; - - if ctx.visited.contains(&addr) { - if ctx.pop()? { - continue; - } else { - break; - } - } else { - ctx.visited.insert(addr); - match visitor(&ctx.instruction)? { - Control::Continue => {} - Control::Break => { - if ctx.pop()? { - continue; - } else { - break; - } - } - Control::Exit => { - break; - } - } - } - - /* - if !matches!(ctx.instruction.flow_control(), FlowControl::Next) { - //println!(); - } - ctx.print(); - if !matches!(ctx.instruction.flow_control(), FlowControl::Next) { - //println!(); - } - */ - - match ctx.instruction.flow_control() { - FlowControl::Next => {} - FlowControl::UnconditionalBranch => { - // TODO figure out how to handle tail calls - ctx.start(ctx.instruction.near_branch_target() as usize)?; - } - //FlowControl::IndirectBranch => todo!(), - FlowControl::ConditionalBranch => { - ctx.queue - .push(ctx.instruction.near_branch_target() as usize); - } - FlowControl::Return => { - if !ctx.pop()? { - break; - } - } - //FlowControl::Call => todo!(), - //FlowControl::IndirectCall => todo!(), - //FlowControl::Interrupt => todo!(), - //FlowControl::XbeginXabortXend => todo!(), - //FlowControl::Exception => todo!(), - _ => {} - } - } - Ok(()) - } -} diff --git a/patternsleuth/src/process/external.rs b/patternsleuth_image/src/process/external.rs similarity index 88% rename from patternsleuth/src/process/external.rs rename to patternsleuth_image/src/process/external.rs index 98281cd..483bdd1 100644 --- a/patternsleuth/src/process/external.rs +++ b/patternsleuth_image/src/process/external.rs @@ -5,10 +5,10 @@ pub use linux::*; mod linux { use std::ops::Range; - use anyhow::{bail, Context, Result}; + use anyhow::{Context, Result, bail}; use object::{Object, ObjectSection}; - use crate::{image, Image, Memory}; + use crate::{Image, Memory, image::pe::PEImageBuilder}; fn read_process_mem(pid: i32, address: usize, buffer: &mut [u8]) -> Result { unsafe { @@ -42,7 +42,14 @@ mod linux { .with_context(|| format!("could not read process maps (PID={pid})"))?; for line in maps.lines() { let mut split = line.splitn(6, |c: char| c.is_whitespace()); - if let [Some(range), Some(_permissions), Some(_offset), Some(_device), Some(_inode), Some(path)] = [ + if let [ + Some(range), + Some(_permissions), + Some(_offset), + Some(_device), + Some(_inode), + Some(path), + ] = [ split.next(), split.next(), split.next(), @@ -83,13 +90,10 @@ mod linux { let memory = Memory::new_external_data(sections)?; - image::pe::PEImage::read_inner_memory::( - object.relative_address_base() as usize, - None, - false, - memory, - object, - ) + PEImageBuilder::new() + .object(object)? + .memory(Box::new(memory)) + .build() } } @@ -112,10 +116,10 @@ pub use windows::*; #[cfg(windows)] mod windows { - use anyhow::{bail, Result}; + use anyhow::{Result, bail}; use object::{Object, ObjectSection}; - use crate::image::pe::PEImage; + use crate::image::pe::PEImageBuilder; use crate::{Image, Memory}; use windows::Win32::Foundation::HMODULE; @@ -128,7 +132,7 @@ mod windows { }; pub fn read_image_from_pid<'data>(pid: i32) -> Result> { - let (memory, base) = unsafe { + let memory = unsafe { let process = OpenProcess( PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, @@ -165,7 +169,7 @@ mod windows { None, )?; - (mem, info.lpBaseOfDll as usize) + mem }; let object = object::File::parse(memory.as_slice())?; @@ -182,6 +186,9 @@ mod windows { let memory = Memory::new_external_data(sections)?; - PEImage::read_inner_memory::(base, None, false, memory, object) + PEImageBuilder::new() + .object(object)? + .memory(Box::new(memory)) + .build() } } diff --git a/patternsleuth/src/process/internal.rs b/patternsleuth_image/src/process/internal.rs similarity index 94% rename from patternsleuth/src/process/internal.rs rename to patternsleuth_image/src/process/internal.rs index c948633..d7b3436 100644 --- a/patternsleuth/src/process/internal.rs +++ b/patternsleuth_image/src/process/internal.rs @@ -8,7 +8,7 @@ mod linux { use anyhow::Result; use crate::Image; - use libc::{dl_iterate_phdr, Elf64_Addr, Elf64_Phdr, Elf64_Sxword, Elf64_Xword, PT_LOAD}; + use libc::{Elf64_Addr, Elf64_Phdr, Elf64_Sxword, Elf64_Xword, PT_LOAD, dl_iterate_phdr}; #[repr(C)] #[derive(Debug)] @@ -46,7 +46,9 @@ mod linux { if name.is_empty() { // find the main //eprintln!("Base addr from iter = {:08x}", (*info).dlpi_addr); - *image = *info; + unsafe { + *image = *info; + } } 0 } @@ -122,7 +124,7 @@ mod windows { Threading::GetCurrentProcess, }; - use crate::image::pe::PEImage; + use crate::image::pe::PEImageBuilder; use crate::{Image, Memory}; pub fn read_image<'data>() -> Result> { @@ -160,6 +162,9 @@ mod windows { let memory = Memory::new_internal_data(sections)?; - PEImage::read_inner_memory::(image_base_address, None, false, memory, object) + PEImageBuilder::new() + .object(object)? + .memory(Box::new(memory)) + .build() } } diff --git a/patternsleuth/src/process/mod.rs b/patternsleuth_image/src/process/mod.rs similarity index 100% rename from patternsleuth/src/process/mod.rs rename to patternsleuth_image/src/process/mod.rs diff --git a/patternsleuth/src/symbols.rs b/patternsleuth_image/src/symbols.rs similarity index 100% rename from patternsleuth/src/symbols.rs rename to patternsleuth_image/src/symbols.rs diff --git a/patternsleuth/src/uesym.rs b/patternsleuth_image/src/uesym.rs similarity index 96% rename from patternsleuth/src/uesym.rs rename to patternsleuth_image/src/uesym.rs index 3028ede..f2c05fc 100644 --- a/patternsleuth/src/uesym.rs +++ b/patternsleuth_image/src/uesym.rs @@ -1,5 +1,5 @@ -use anyhow::{anyhow, Result}; -use object::{from_bytes, slice_from_bytes, Pod}; +use anyhow::{Result, anyhow}; +use object::{Pod, from_bytes, slice_from_bytes}; use std::{collections::HashMap, path::Path}; use crate::symbols::Symbol; diff --git a/patternsleuth_resolvers/Cargo.toml b/patternsleuth_resolvers/Cargo.toml new file mode 100644 index 0000000..90cda0f --- /dev/null +++ b/patternsleuth_resolvers/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "patternsleuth_resolvers" +repository.workspace = true +authors.workspace = true +license.workspace = true +version.workspace = true +edition.workspace = true + +[dependencies] +patternsleuth_scanner = { path = "../patternsleuth_scanner" } +patternsleuth_image = { path = "../patternsleuth_image" } +iced-x86.workspace = true +futures = "0.3.30" +futures-scopes = "0.2.0" +inventory = "0.3.14" +itertools.workspace = true +serde = { workspace = true, optional = true, features = ["derive"] } +typetag = { version = "0.2.15", optional = true } +tracing = "0.1.40" + +[features] +default = [] +serde-resolvers = ["dep:serde", "dep:typetag", "patternsleuth_image/serde-resolvers"] +image-pe = ["patternsleuth_image/image-pe"] +image-elf = ["patternsleuth_image/image-elf"] diff --git a/patternsleuth_resolvers/src/disassemble.rs b/patternsleuth_resolvers/src/disassemble.rs new file mode 100644 index 0000000..2d1bbb2 --- /dev/null +++ b/patternsleuth_resolvers/src/disassemble.rs @@ -0,0 +1,172 @@ +use std::{collections::HashSet, ops::Range}; + +use iced_x86::{Decoder, DecoderOptions, FlowControl, Formatter, Instruction, NasmFormatter}; + +use crate::{Image, MemoryAccessError}; + +pub fn function_range(exe: &Image, address: usize) -> Result, MemoryAccessError> { + let min = address; + let mut max = min; + disassemble(exe, address, |inst| { + let cur = inst.ip() as usize; + if Some(address) != exe.get_root_function(cur)?.map(|f| f.range.start) { + return Ok(Control::Break); + } + max = max.max(cur + inst.len()); + Ok(Control::Continue) + })?; + Ok(min..max) +} + +pub fn disassemble_single( + exe: &Image, + address: usize, +) -> Result, MemoryAccessError> { + Ok(Decoder::with_ip( + 64, + exe.memory.range_from(address..)?, + address as u64, + DecoderOptions::NONE, + ) + .iter() + .next()) +} + +pub enum Control { + Continue, + Break, + Exit, +} + +pub fn disassemble(exe: &Image, address: usize, mut visitor: F) -> Result<(), MemoryAccessError> +where + F: FnMut(&Instruction) -> Result, +{ + struct Ctx<'data, 'img> { + exe: &'img Image<'data>, + queue: Vec, + visited: HashSet, + address: usize, + block: &'img [u8], // these lifetimes are NOT 'data because of oddities with how Cow<'data> is handled in Image + decoder: Decoder<'img>, + instruction: Instruction, + } + + let block = exe.memory.range_from(address..)?; + let mut ctx = Ctx { + exe, + queue: Default::default(), + visited: Default::default(), + address, + block, + decoder: Decoder::with_ip(64, block, address as u64, DecoderOptions::NONE), + instruction: Default::default(), + }; + + impl Ctx<'_, '_> { + #[allow(unused)] + fn print(&self) { + let mut formatter = NasmFormatter::new(); + //formatter.options_mut().set_digit_separator("`"); + //formatter.options_mut().set_first_operand_char_index(10); + + let mut output = String::new(); + formatter.format(&self.instruction, &mut output); + + // Eg. "00007FFAC46ACDB2 488DAC2400FFFFFF lea rbp,[rsp-100h]" + print!("{:016X} ", self.instruction.ip()); + let start_index = self.instruction.ip() as usize - self.address; + let instr_bytes = &self.block[start_index..start_index + self.instruction.len()]; + for b in instr_bytes.iter() { + print!("{:02X}", b); + } + if instr_bytes.len() < 0x10 { + for _ in 0..0x10 - instr_bytes.len() { + print!(" "); + } + } + println!(" {}", output); + } + fn start(&mut self, address: usize) -> Result<(), MemoryAccessError> { + //println!("starting at {address:x}"); + self.address = address; + self.block = self.exe.memory.range_from(self.address..)?; + self.decoder = + Decoder::with_ip(64, self.block, self.address as u64, DecoderOptions::NONE); + Ok(()) + } + /// Returns true if pop was successful + fn pop(&mut self) -> Result { + Ok(if let Some(next) = self.queue.pop() { + self.start(next)?; + true + } else { + false + }) + } + } + + while ctx.decoder.can_decode() { + ctx.decoder.decode_out(&mut ctx.instruction); + + let addr = ctx.instruction.ip() as usize; + + if ctx.visited.contains(&addr) { + if ctx.pop()? { + continue; + } else { + break; + } + } else { + ctx.visited.insert(addr); + match visitor(&ctx.instruction)? { + Control::Continue => {} + Control::Break => { + if ctx.pop()? { + continue; + } else { + break; + } + } + Control::Exit => { + break; + } + } + } + + /* + if !matches!(ctx.instruction.flow_control(), FlowControl::Next) { + //println!(); + } + ctx.print(); + if !matches!(ctx.instruction.flow_control(), FlowControl::Next) { + //println!(); + } + */ + + match ctx.instruction.flow_control() { + FlowControl::Next => {} + FlowControl::UnconditionalBranch => { + // TODO figure out how to handle tail calls + ctx.start(ctx.instruction.near_branch_target() as usize)?; + } + //FlowControl::IndirectBranch => todo!(), + FlowControl::ConditionalBranch => { + ctx.queue + .push(ctx.instruction.near_branch_target() as usize); + } + FlowControl::Return => { + if !ctx.pop()? { + break; + } + } + //FlowControl::Call => todo!(), + //FlowControl::IndirectCall => todo!(), + //FlowControl::Interrupt => todo!(), + //FlowControl::XbeginXabortXend => todo!(), + //FlowControl::Exception => todo!(), + _ => {} + } + } + Ok(()) +} diff --git a/patternsleuth/src/resolvers/mod.rs b/patternsleuth_resolvers/src/lib.rs similarity index 76% rename from patternsleuth/src/resolvers/mod.rs rename to patternsleuth_resolvers/src/lib.rs index a4d9a62..4aa994c 100644 --- a/patternsleuth/src/resolvers/mod.rs +++ b/patternsleuth_resolvers/src/lib.rs @@ -1,14 +1,18 @@ +pub mod disassemble; pub mod unreal; -use crate::{Image, MemoryAccessError}; +use _image::Image; +use patternsleuth_image::MemoryAccessError; +pub use patternsleuth_image::image as _image; + use futures::{ channel::oneshot, executor::LocalPool, - future::{join_all, BoxFuture}, + future::{BoxFuture, join_all}, }; use futures_scopes::{ - relay::{new_relay_scope, RelayScopeLocalSpawning}, ScopedSpawnExt, SpawnScope, + relay::{RelayScopeLocalSpawning, new_relay_scope}, }; use patternsleuth_scanner::Pattern; use std::{ @@ -43,15 +47,12 @@ pub fn try_ensure_one( } } match unique.len() { - 0 => Err(ResolveError::Msg("expected at least one value".into())), + 0 => Err(ResolveError::new_msg("expected at least one value")), 1 => Ok(unique.swap_remove(0)), - len => Err(ResolveError::Msg( - format!( - "found {}{len} unique values {unique:X?}", - if reached_max { ">=" } else { "" } - ) - .into(), - )), + len => Err(ResolveError::new_msg(format!( + "found {}{len} unique values {unique:X?}", + if reached_max { ">=" } else { "" } + ))), } } @@ -61,15 +62,37 @@ pub type Result = std::result::Result; feature = "serde-resolvers", derive(serde::Serialize, serde::Deserialize) )] -pub enum ResolveError { +pub struct ResolveError { + context: Vec, + r#type: ResolveErrorType, +} +impl ResolveError { + fn new_msg(msg: impl Into>) -> Self { + Self { + context: vec![], + r#type: ResolveErrorType::Msg(msg.into()), + } + } +} +#[derive(Debug, Clone, PartialEq)] +#[cfg_attr( + feature = "serde-resolvers", + derive(serde::Serialize, serde::Deserialize) +)] +pub enum ResolveErrorType { Msg(Cow<'static, str>), MemoryAccessOutOfBounds(MemoryAccessError), } impl std::fmt::Display for ResolveError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - ResolveError::Msg(msg) => write!(f, "{msg}"), - ResolveError::MemoryAccessOutOfBounds(err) => err.fmt(f), + match &self.r#type { + ResolveErrorType::Msg(msg) => { + for ctx in self.context.iter().rev() { + write!(f, "{ctx}: ")?; + } + write!(f, "{msg}") + } + ResolveErrorType::MemoryAccessOutOfBounds(err) => err.fmt(f), } } } @@ -77,14 +100,17 @@ impl Error for ResolveError {} impl From for ResolveError { fn from(value: MemoryAccessError) -> Self { - Self::MemoryAccessOutOfBounds(value) + Self { + context: vec![], + r#type: ResolveErrorType::MemoryAccessOutOfBounds(value), + } } } #[macro_export] macro_rules! _bail_out { ($msg:expr) => { - return Err($crate::resolvers::ResolveError::Msg($msg.into())); + return Err($crate::ResolveError::new_msg($msg)); }; } pub use _bail_out as bail_out; @@ -99,7 +125,7 @@ impl Context for Option { fn context(self, msg: &'static str) -> Result { match self { Some(value) => Ok(value), - None => Err(ResolveError::Msg(msg.into())), + None => Err(ResolveError::new_msg(msg)), } } } @@ -168,11 +194,13 @@ impl PartialEq for dyn Resolution { } pub struct DynResolverFactory { - pub factory: for<'ctx> fn(&'ctx AsyncContext<'_>) -> DynResolver<'ctx>, + pub name: &'static str, + pub factory: for<'ctx> fn(&'ctx AsyncContext<'_, '_>) -> DynResolver<'ctx>, } pub struct ResolverFactory { - pub factory: for<'ctx> fn(&'ctx AsyncContext<'_>) -> Resolver<'ctx, T>, + pub name: &'static str, + pub factory: for<'ctx> fn(&'ctx AsyncContext<'_, '_>) -> Resolver<'ctx, T>, } pub use ::futures; @@ -189,8 +217,8 @@ macro_rules! _matcharm_generator { let img = &$ctx.image().image_type; let mut res = None; $( - $crate::resolvers::cfg_image::$img_ident! { - if matches!(img, $crate::image::$enum_name_it::$img_ident(_)) { + $crate::cfg_image::$img_ident! { + if matches!(img, $crate::_image::$enum_name_it::$img_ident(_)) { res = Some($name::$img_ident($ctx).await); } } @@ -222,7 +250,7 @@ macro_rules! _impl_resolver { (all, $name:ident, |$ctx:ident| async $x:block ) => { $crate::_impl_resolver_inner!($name, |$ctx| async $x); - impl $crate::resolvers::Singleton for $name { + impl $crate::Singleton for $name { fn get(&self) -> Option { None } @@ -230,30 +258,30 @@ macro_rules! _impl_resolver { }; ($arch:ident, $name:ident, |$ctx:ident| async $x:block ) => { - $crate::resolvers::cfg_image::$arch! { - impl $name where $name: $crate::resolvers::PleaseAddCollectForMe { + $crate::cfg_image::$arch! { + impl $name where $name: $crate::PleaseAddCollectForMe { #[allow(non_snake_case)] - pub async fn $arch($ctx: &$crate::resolvers::AsyncContext<'_>) -> $crate::resolvers::Result<$name> $x + pub async fn $arch($ctx: &$crate::AsyncContext<'_, '_>) -> $crate::Result<$name> $x } } }; (collect, $name:ident) => { $crate::_impl_resolver_inner!($name, |ctx| async { - $crate::image::image_type_reflection!(all, impl_resolver; generate; {ctx, $name}) + $crate::_image::image_type_reflection!(all, impl_resolver; generate; {ctx, $name}) }); - impl $crate::resolvers::Singleton for $name { + impl $crate::Singleton for $name { fn get(&self) -> Option { None } } - impl $crate::resolvers::PleaseAddCollectForMe for $name {} + impl $crate::PleaseAddCollectForMe for $name {} }; (generate, $enum_name_it:ident { $( $img_ident:ident( $img_ty:ty, $img_feature:literal )),* $(,)? }, {$ctx:ident, $name:ident}) => { - $crate::resolvers::matcharm_generator!( + $crate::matcharm_generator!( $enum_name_it { $( $img_ident( $img_ty, $img_feature )),* }, { $ctx, $name } ) @@ -270,7 +298,7 @@ macro_rules! _impl_resolver_singleton { $x }); - impl $crate::resolvers::Singleton for $name { + impl $crate::Singleton for $name { fn get(&self) -> Option { Some(self.0) } @@ -278,10 +306,10 @@ macro_rules! _impl_resolver_singleton { }; ($arch:ident, $name:ident, |$ctx:ident| async $x:block ) => { - $crate::resolvers::cfg_image::$arch! { - impl $name where $name: $crate::resolvers::PleaseAddCollectForMe { + $crate::cfg_image::$arch! { + impl $name where $name: $crate::PleaseAddCollectForMe { #[allow(non_snake_case)] - async fn $arch($ctx: &$crate::resolvers::AsyncContext<'_>) -> $crate::resolvers::Result<$name> $x + async fn $arch($ctx: &$crate::AsyncContext<'_, '_>) -> $crate::Result<$name> $x } } }; @@ -291,20 +319,20 @@ macro_rules! _impl_resolver_singleton { if let Some(a) = std::env::var(concat!("PATTERNSLEUTH_RES_", stringify!($name))).ok().and_then(|s| (s.strip_prefix("0x").map(|s| usize::from_str_radix(s, 16).ok()).unwrap_or_else(|| s.parse().ok()))) { return Ok($name(a)); } - $crate::image::image_type_reflection!(all, impl_resolver_singleton; generate; {ctx, $name}) + $crate::_image::image_type_reflection!(all, impl_resolver_singleton; generate; {ctx, $name}) }); - impl $crate::resolvers::Singleton for $name { + impl $crate::Singleton for $name { fn get(&self) -> Option { Some(self.0) } } - impl $crate::resolvers::PleaseAddCollectForMe for $name {} + impl $crate::PleaseAddCollectForMe for $name {} }; (generate, $enum_name_it:ident { $( $img_ident:ident( $img_ty:ty, $img_feature:literal )),* $(,)? }, {$ctx:ident, $name:ident}) => { - $crate::resolvers::matcharm_generator!( + $crate::matcharm_generator!( $enum_name_it { $( $img_ident( $img_ty, $img_feature )),* }, { $ctx, $name } ) @@ -313,30 +341,32 @@ macro_rules! _impl_resolver_singleton { #[macro_export] macro_rules! _impl_resolver_inner { ( $name:ident, |$ctx:ident| async $x:block ) => { - $crate::resolvers::inventory::submit! { - $crate::resolvers::NamedResolver { name: stringify!($name), getter: $name::dyn_resolver } + $crate::inventory::submit! { + $crate::NamedResolver { name: stringify!($name), getter: $name::dyn_resolver } } - #[cfg_attr(feature = "serde-resolvers", $crate::resolvers::typetag::serde)] - impl $crate::resolvers::Resolution for $name {} + #[cfg_attr(feature = "serde-resolvers", $crate::typetag::serde)] + impl $crate::Resolution for $name {} impl $name { - pub fn resolver() -> &'static $crate::resolvers::ResolverFactory<$name> { - static GLOBAL: ::std::sync::OnceLock<&$crate::resolvers::ResolverFactory<$name>> = ::std::sync::OnceLock::new(); + pub fn resolver() -> &'static $crate::ResolverFactory<$name> { + static GLOBAL: ::std::sync::OnceLock<&$crate::ResolverFactory<$name>> = ::std::sync::OnceLock::new(); - GLOBAL.get_or_init(|| &$crate::resolvers::ResolverFactory { - factory: |$ctx: &$crate::resolvers::AsyncContext| -> $crate::resolvers::futures::future::BoxFuture<$crate::resolvers::Result<$name>> { + GLOBAL.get_or_init(|| &$crate::ResolverFactory { + name: stringify!($name), + factory: |$ctx: &$crate::AsyncContext| -> $crate::futures::future::BoxFuture<$crate::Result<$name>> { Box::pin(async $x) }, }) } - pub fn dyn_resolver() -> &'static $crate::resolvers::DynResolverFactory { - static GLOBAL: ::std::sync::OnceLock<&$crate::resolvers::DynResolverFactory> = ::std::sync::OnceLock::new(); + pub fn dyn_resolver() -> &'static $crate::DynResolverFactory { + static GLOBAL: ::std::sync::OnceLock<&$crate::DynResolverFactory> = ::std::sync::OnceLock::new(); - GLOBAL.get_or_init(|| &$crate::resolvers::DynResolverFactory { - factory: |$ctx: &$crate::resolvers::AsyncContext| -> $crate::resolvers::futures::future::BoxFuture<$crate::resolvers::Result<::std::sync::Arc>> { + GLOBAL.get_or_init(|| &$crate::DynResolverFactory { + name: stringify!($name), + factory: |$ctx: &$crate::AsyncContext| -> $crate::futures::future::BoxFuture<$crate::Result<::std::sync::Arc>> { Box::pin(async { - $ctx.resolve(Self::resolver()).await.map(|ok| -> ::std::sync::Arc { ok }) + $ctx.resolve(Self::resolver()).await.map(|ok| -> ::std::sync::Arc { ok }) }) }, }) @@ -368,7 +398,7 @@ macro_rules! _impl_try_collector { #[allow(non_snake_case)] let ( $( $member_name, )* - ) = $crate::resolvers::futures::try_join!( + ) = $crate::futures::try_join!( $( ctx.resolve($resolver::resolver()), )* )?; Ok($struct_name { @@ -394,14 +424,14 @@ macro_rules! _impl_collector { $struct_vis struct $struct_name { $( $(#[$inner $($args)*])* - $member_vis $member_name: $crate::resolvers::Result<::std::sync::Arc<$resolver>>, + $member_vis $member_name: $crate::Result<::std::sync::Arc<$resolver>>, )* } $crate::_impl_resolver!(all, $struct_name, |ctx| async { #[allow(non_snake_case)] let ( $( $member_name, )* - ) = $crate::resolvers::futures::join!( + ) = $crate::futures::join!( $( ctx.resolve($resolver::resolver()), )* ); Ok($struct_name { @@ -440,18 +470,18 @@ struct AsyncContextInnerWrite { queue: Vec<(Pattern, oneshot::Sender)>, } -struct AsyncContextInnerRead<'data> { +struct AsyncContextInnerRead<'img, 'data> { write: Mutex, - image: &'data Image<'data>, + image: &'img Image<'data>, } #[derive(Clone)] -pub struct AsyncContext<'data> { - read: Arc>, +pub struct AsyncContext<'img, 'data> { + read: Arc>, } -impl<'data> AsyncContext<'data> { - fn new(image: &'data Image<'data>) -> Self { +impl<'img, 'data> AsyncContext<'img, 'data> { + fn new(image: &'img Image<'data>) -> Self { Self { read: Arc::new(AsyncContextInnerRead { write: Default::default(), @@ -459,7 +489,7 @@ impl<'data> AsyncContext<'data> { }), } } - pub fn image(&self) -> &Image<'_> { + pub fn image(&self) -> &'img Image<'data> { self.read.image } pub async fn scan(&self, pattern: Pattern) -> Vec { @@ -516,8 +546,16 @@ impl<'data> AsyncContext<'data> { } // compute the resolver value + let name = resolver.name; let resolver = (resolver.factory)(self); - let res = resolver.await.map(Arc::new); + let res = match resolver.await { + Err(mut e) => { + e.context.push(name.into()); + Err(e) + } + res => res, + }; + let res = res.map(Arc::new); let cache: Result> = match res.as_ref() { Ok(ok) => Ok(ok.clone()), @@ -538,9 +576,9 @@ impl<'data> AsyncContext<'data> { } #[tracing::instrument(level = "debug", skip_all, fields(stages))] -pub fn eval(image: &Image<'_>, f: F) -> T +pub fn eval<'img, 'data, F, T: Send + Sync>(image: &'img Image<'data>, f: F) -> T where - F: for<'ctx> FnOnce(&'ctx AsyncContext<'_>) -> BoxFuture<'ctx, T> + Send + Sync, + F: for<'ctx> FnOnce(&'ctx AsyncContext<'_, '_>) -> BoxFuture<'ctx, T> + Send + Sync, { { tracing::debug!("starting eval"); @@ -590,7 +628,7 @@ where let span = tracing::debug_span!( "section", section = section.name(), - kind = format!("{:?}", section.kind()), + //kind = format!("{:?}", section.kind()), results = tracing::field::Empty ) .entered(); diff --git a/patternsleuth/src/resolvers/unreal/aes.rs b/patternsleuth_resolvers/src/unreal/aes.rs similarity index 84% rename from patternsleuth/src/resolvers/unreal/aes.rs rename to patternsleuth_resolvers/src/unreal/aes.rs index bdae28c..e98a05d 100644 --- a/patternsleuth/src/resolvers/unreal/aes.rs +++ b/patternsleuth_resolvers/src/unreal/aes.rs @@ -4,10 +4,7 @@ use futures::future::join_all; use patternsleuth_scanner::Pattern; -use crate::{ - resolvers::{impl_resolver, Result}, - MemoryTrait, -}; +use crate::{Result, impl_resolver}; #[derive(PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr( @@ -52,9 +49,18 @@ impl_resolver!(PEImage, AESKeys, |ctx| async { C, } let patterns = [ - (KeyType::A, "C7 45 D0 ?? ?? ?? ?? C7 45 D4 ?? ?? ?? ?? ?? ?? ?? ?? C7 45 D8 ?? ?? ?? ?? C7 45 DC ?? ?? ?? ?? C7 45 E0 ?? ?? ?? ?? C7 45 E4 ?? ?? ?? ?? C7 45 E8 ?? ?? ?? ?? C7 45 EC ?? ?? ?? ??"), - (KeyType::B, "C7 01 ?? ?? ?? ?? C7 41 04 ?? ?? ?? ?? C7 41 08 ?? ?? ?? ?? C7 41 0C ?? ?? ?? ?? C7 41 10 ?? ?? ?? ?? C7 41 14 ?? ?? ?? ?? C7 41 18 ?? ?? ?? ?? C7 41 1C ?? ?? ?? ?? C3"), - (KeyType::C, "C7 45 D0 ?? ?? ?? ?? C7 45 D4 ?? ?? ?? ?? C7 45 D8 ?? ?? ?? ?? C7 45 DC ?? ?? ?? ?? 0F 10 45 D0 C7 45 E0 ?? ?? ?? ?? C7 45 E4 ?? ?? ?? ?? C7 45 E8 ?? ?? ?? ?? C7 45 EC ?? ?? ?? ??"), + ( + KeyType::A, + "C7 45 D0 ?? ?? ?? ?? C7 45 D4 ?? ?? ?? ?? ?? ?? ?? ?? C7 45 D8 ?? ?? ?? ?? C7 45 DC ?? ?? ?? ?? C7 45 E0 ?? ?? ?? ?? C7 45 E4 ?? ?? ?? ?? C7 45 E8 ?? ?? ?? ?? C7 45 EC ?? ?? ?? ??", + ), + ( + KeyType::B, + "C7 01 ?? ?? ?? ?? C7 41 04 ?? ?? ?? ?? C7 41 08 ?? ?? ?? ?? C7 41 0C ?? ?? ?? ?? C7 41 10 ?? ?? ?? ?? C7 41 14 ?? ?? ?? ?? C7 41 18 ?? ?? ?? ?? C7 41 1C ?? ?? ?? ?? C3", + ), + ( + KeyType::C, + "C7 45 D0 ?? ?? ?? ?? C7 45 D4 ?? ?? ?? ?? C7 45 D8 ?? ?? ?? ?? C7 45 DC ?? ?? ?? ?? 0F 10 45 D0 C7 45 E0 ?? ?? ?? ?? C7 45 E4 ?? ?? ?? ?? C7 45 E8 ?? ?? ?? ?? C7 45 EC ?? ?? ?? ??", + ), ]; let res = join_all( diff --git a/patternsleuth/src/resolvers/unreal/blueprint_library.rs b/patternsleuth_resolvers/src/unreal/blueprint_library.rs similarity index 97% rename from patternsleuth/src/resolvers/unreal/blueprint_library.rs rename to patternsleuth_resolvers/src/unreal/blueprint_library.rs index 654136e..61b305a 100644 --- a/patternsleuth/src/resolvers/unreal/blueprint_library.rs +++ b/patternsleuth_resolvers/src/unreal/blueprint_library.rs @@ -2,12 +2,10 @@ use std::fmt::Debug; use futures::{future::join_all, join}; +use patternsleuth_image::Addressable as _; use patternsleuth_scanner::Pattern; -use crate::{ - resolvers::{ensure_one, impl_resolver, impl_resolver_singleton, Context}, - Addressable, Matchable, -}; +use crate::{Context, ensure_one, impl_resolver, impl_resolver_singleton}; #[derive(Debug, PartialEq)] #[cfg_attr( @@ -181,7 +179,7 @@ impl_resolver!(all, BlueprintLibraryInit, |ctx| async { pub struct UFunctionBind(pub usize); impl_resolver_singleton!(collect, UFunctionBind); impl_resolver_singleton!(PEImage, UFunctionBind, |ctx| async { - use crate::resolvers::unreal::util; + use crate::unreal::util; let string = async { let strings = ctx diff --git a/patternsleuth/src/resolvers/unreal/engine_version.rs b/patternsleuth_resolvers/src/unreal/engine_version.rs similarity index 86% rename from patternsleuth/src/resolvers/unreal/engine_version.rs rename to patternsleuth_resolvers/src/unreal/engine_version.rs index 0a40f05..c282c47 100644 --- a/patternsleuth/src/resolvers/unreal/engine_version.rs +++ b/patternsleuth_resolvers/src/unreal/engine_version.rs @@ -3,12 +3,10 @@ use std::fmt::{Debug, Display}; use futures::future::join_all; use itertools::Itertools; +use patternsleuth_image::Addressable as _; use patternsleuth_scanner::Pattern; -use crate::{ - resolvers::{bail_out, impl_resolver, try_ensure_one}, - MemoryTrait, -}; +use crate::{bail_out, impl_resolver, try_ensure_one}; #[derive(PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr( @@ -32,15 +30,19 @@ impl Debug for EngineVersion { impl_resolver!(all, EngineVersion, |ctx| async { let patterns = [ - "C7 03 | 04 00 ?? 00 66 89 4B 04 48 3B F8 74 ?? 48", + "C7 47 20 | 04 00 ?? 00 66 89 6F 24", + "C7 4? 20 | 04 00 ?? ?? 66 4? 89 ?? 24", + "C7 ?? 24 20 | 04 00 ?? ?? 48 8D 45 F0", "C7 05 ?? ?? ?? ?? | 04 00 ?? 00 66 89 ?? ?? ?? ?? ?? C7 05", "C7 05 ?? ?? ?? ?? | 04 00 ?? 00 66 89 ?? ?? ?? ?? ?? 89", "41 C7 ?? | 04 00 ?? 00 ?? ?? 00 00 00 66 41 89", "41 C7 ?? | 04 00 18 00 66 41 89 ?? 04", "41 C7 04 24 | 04 00 ?? 00 66 ?? 89 ?? 24", "41 C7 04 24 | 04 00 ?? 00 B9 ?? 00 00 00", + "41 C7 44 24 20 | 04 00 ?? 00 66 ?? 89 ?? 24", + "C7 05 ?? ?? ?? ?? | 04 00 ?? 00 89 3D ?? ?? ?? ?? 85 FF", "C7 05 ?? ?? ?? ?? | 04 00 ?? 00 89 05 ?? ?? ?? ?? E8", - "C7 05 ?? ?? ?? ?? | 04 00 ?? 00 66 89 ?? ?? ?? ?? ?? 89 05", + "C7 05 ?? ?? ?? ?? | 04 00 ?? 00 66 89 ?? ?? ?? ?? ??", "C7 46 20 | 04 00 ?? 00 66 44 89 76 24 44 89 76 28 48 39 C7", "C7 03 | 04 00 ?? 00 66 44 89 63 04 C7 43 08 C1 5C 08 80 E8", "C7 47 20 | 04 00 ?? 00 66 89 6F 24 C7 47 28 ?? ?? ?? ?? 49", @@ -49,7 +51,14 @@ impl_resolver!(all, EngineVersion, |ctx| async { "C7 06 | 05 00 ?? ?? 48 8B 5C 24 20 4C 8D 76 10 33 ED", "11 76 30 c7 46 20 | 04 00 ?? 00", // maybe better go from BuildSettings::GetBranchName -> FGlobalEngineVersions::FGlobalEngineVersions - "0f 57 c0 0f 11 43 10 c7 03 | 05 ?? ?? ?? 66 c7 43 04 ?? ??", // <- last one is patch + "0F 57 C0 0F 11 43 10 C7 03 | 05 ?? ?? ?? 66 C7 43 04 ?? ??", // <- last one is patch + "48 89 2? 48 89 6? 08 C7 0? | 05 00 ?? ?? 66", + "49 89 2? 49 89 6? 08 C7 0? | 05 00 ?? ?? 66", + "C7 46 20 | 05 00 ?? ?? 66 89 ?? 24", + "C7 43 20 | 05 00 ?? ?? 48 3B F0", + "C7 46 20 | 05 00 ?? ?? 48 8D 44 24 20", + "C7 4? 20 | 05 00 ?? ?? 66 44 89 ?? 24", + "C7 ?? 24 20 | 05 00 ?? ?? 48 8D 45 F0", ]; let res = join_all(patterns.iter().map(|p| ctx.scan(Pattern::new(p).unwrap()))).await; @@ -86,7 +95,7 @@ pub struct EngineVersionStrings { impl_resolver!(collect, EngineVersionStrings); // "++UE5+Release-{}.{}" impl_resolver!(ElfImage, EngineVersionStrings, |ctx| async { - use crate::resolvers::{ensure_one, unreal::util}; + use crate::{ensure_one, unreal::util}; let pattern_name = util::utf16_pattern("++UE5+Release-"); let name_scan = ctx.scan(pattern_name).await; @@ -137,7 +146,6 @@ impl_resolver!(ElfImage, EngineVersionStrings, |ctx| async { }); impl_resolver!(PEImage, EngineVersionStrings, |ctx| async { - use crate::{Addressable, Matchable, MemoryTrait}; use std::collections::HashSet; let patterns = [ @@ -163,6 +171,7 @@ impl_resolver!(PEImage, EngineVersionStrings, |ctx| async { for (_, pattern, addresses) in res { for a in addresses { let caps = mem.captures(&pattern, a)?.unwrap(); + let date = caps[1].rip(); if mem .range(date..date + 6) diff --git a/patternsleuth/src/resolvers/unreal/fname.rs b/patternsleuth_resolvers/src/unreal/fname.rs similarity index 94% rename from patternsleuth/src/resolvers/unreal/fname.rs rename to patternsleuth_resolvers/src/unreal/fname.rs index 3b372b2..7f3248b 100644 --- a/patternsleuth/src/resolvers/unreal/fname.rs +++ b/patternsleuth_resolvers/src/unreal/fname.rs @@ -4,10 +4,7 @@ use futures::future::join_all; use patternsleuth_scanner::Pattern; -use crate::{ - resolvers::{ensure_one, impl_resolver_singleton, try_ensure_one, unreal::util, Result}, - MemoryTrait, -}; +use crate::{Result, ensure_one, impl_resolver_singleton, try_ensure_one, unreal::util}; /// public: __cdecl FName::FName(wchar_t const *, enum EFindName) #[derive(Debug, PartialEq)] @@ -29,7 +26,7 @@ FEngineLoop::LoadPreInitModules: RenderCore */ impl_resolver_singleton!(ElfImage, FNameCtorWchar, |ctx| async { - use crate::resolvers::ResolveError; + use crate::ResolveError; use std::collections::HashSet; let strings = [ @@ -105,7 +102,7 @@ impl_resolver_singleton!(ElfImage, FNameCtorWchar, |ctx| async { } } // how to scan code from X? - let result = result.ok_or(ResolveError::Msg("cannot find address".into()))?; + let result = result.ok_or(ResolveError::new_msg("cannot find address"))?; /* Post check if util::root_functions(ctx, &[result]).unwrap()[0] == result { @@ -116,7 +113,7 @@ impl_resolver_singleton!(ElfImage, FNameCtorWchar, |ctx| async { }); impl_resolver_singleton!(PEImage, FNameCtorWchar, |ctx| async { - use crate::{resolvers::Context, MemoryTrait}; + use crate::Context; use futures::join; use iced_x86::{Code, Decoder, DecoderOptions}; @@ -219,7 +216,7 @@ impl_resolver_singleton!(ElfImage, FNameToString, |ctx| async { }); impl_resolver_singleton!(PEImage, FNameToString, |ctx| async { - use crate::{resolvers::Context, MemoryTrait}; + use crate::Context; use futures::join; use iced_x86::{Code, Decoder, DecoderOptions}; @@ -354,9 +351,8 @@ impl_resolver_singleton!(all, FNameToStringFString, |ctx| async { pub struct FNamePool(pub usize); impl_resolver_singleton!(all, FNamePool, |ctx| async { let patterns = [ - "74 ?? 4C 8D 05 | ?? ?? ?? ?? EB ?? 48 8D 0D", - "48 8d 0d | ?? ?? ?? ?? e8 ?? ?? ?? ?? 48 8b d0 c6 05 dc ?? ?? ?? ?? 48 8b 44 24 30 48 c1 e8 20 03 c0 48 03 44 da 10 48 83 c4 20 5b c3", - "48 8d 2d | ?? ?? ?? ?? ?? ?? ?? ?? 48 bf cd cc cc cc cc cc cc", + "74 09 4C 8D 05 | ?? ?? ?? ?? EB ?? 48 8D 0D ?? ?? ?? ?? E8", + "48 83 EC 20 C1 EA 03 48 8d 2d | ?? ?? ?? ?? ?? CA ?? ?? 48 bf cd cc cc cc cc cc cc", ]; let res = join_all(patterns.iter().map(|p| ctx.scan(Pattern::new(p).unwrap()))).await; diff --git a/patternsleuth_resolvers/src/unreal/ftext.rs b/patternsleuth_resolvers/src/unreal/ftext.rs new file mode 100644 index 0000000..812b6f8 --- /dev/null +++ b/patternsleuth_resolvers/src/unreal/ftext.rs @@ -0,0 +1,95 @@ +use std::fmt::Debug; + +use futures::future::join_all; + +use patternsleuth_scanner::Pattern; + +use crate::{impl_resolver_singleton, try_ensure_one}; + +/// private: __cdecl FText::FText(class FString &&) +#[derive(Debug, PartialEq)] +#[cfg_attr( + feature = "serde-resolvers", + derive(serde::Serialize, serde::Deserialize) +)] +pub struct FTextFString(pub usize); +impl_resolver_singleton!(all, FTextFString, |ctx| async { + #[derive(Debug)] + enum Directness { + Direct, + Indirect, + } + let patterns = [ + ( + Directness::Indirect, + "40 53 48 83 ec ?? 48 8b d9 e8 | ?? ?? ?? ?? 83 4b ?? 12 48 8b c3 48 83 ?? ?? 5b c3", + ), + ( + Directness::Indirect, + "eb 12 48 8d ?? 24 ?? e8 | ?? ?? ?? ?? ?? 02 00 00 00 48 8b 10 48 89 17", + ), + ( + Directness::Indirect, + "eb 12 48 8d ?? 24 ?? e8 | ?? ?? ?? ?? ?? 02 00 00 00 48 8b 10 89", + ), + ( + Directness::Direct, + "48 89 5C 24 10 48 89 6C 24 18 56 57 41 54 41 56 41 57 48 83 EC 40 45 33 E4 48 8B F1 41 8B DC 4C 8B F2 89 5C 24 70 41 8D 4C 24 70 E8 ?? ?? ?? FF 48 8B F8 48 85 C0 0F 84 ?? 00 00 00 49 63 5E 08 ?? 8B ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 8B ?? EB 2E 45 33 C0 48 8D 4C 24 20 8B D3 E8", + ), + ( + Directness::Direct, + "48 89 5C 24 10 48 89 6C 24 18 57 48 83 EC 50 33 ED 48 8D 05 ?? ?? ?? 03 48 8B F9 48 89 6C 24 38 48 8B DA 48 89 6C 24 48 48 89 44 24 30 8D 4D 60 48 89 44 24 40 E8 ?? ?? ?? FF 4C 8B C0 48 85 C0 74 65 0F 10 44 24 30 C7 40 08 01 00 00 00 0F 10 4C 24 40 C7 40 0C 01 00 00 00 48 8D 05 ?? ?? ?? 03 49 89 00", + ), + ( + Directness::Direct, + "48 89 5C 24 10 48 89 6C 24 18 56 57 41 54 41 56 41 57 48 83 EC 50 45 33 E4 48 8B F9 41 8B DC 4C 8B F2 89 9C 24 80 00 00 00 41 8D 4C 24 70 E8 ?? ?? ?? ?? 48 8B F0 48 85 C0 0F 84 98 00 00 00 49 63 5E 08 41 8B EC 4D 8B 3E 4C 89 64 24 20 89 5C 24 28 85 DB 75 05 45 8B FC EB 2E 45 33 C0 48 8D 4C 24 20 8B", + ), + ( + Directness::Direct, + "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 54 41 56 41 57 48 83 EC 40 4C 8B F1 48 8B F2", + ), + ( + Directness::Direct, + "48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 54 41 56 41 57 48 83 EC 40 45 33 E4 48 8B F1", + ), + ( + Directness::Direct, + "40 53 56 48 83 EC 48 33 DB 48 89 6C 24 68 48 8B F1 48 89 7C 24 70 4C 89 74 24 78 4C 8B F2 89 5C 24 60 8D 4B 70 E8 ?? ?? ?? FF 48 8B F8 48 85 C0 0F 84 9E 00 00 00 49 63 5E 08 33 ED 4C 89 7C 24 40 4D 8B 3E 48 89 6C 24 20 89 5C 24 28 85 DB 75 05 45 33 FF EB 2E 45 33 C0 48 8D 4C 24 20 8B D3 E8", + ), + ( + Directness::Direct, + "41 57 41 56 41 54 56 57 55 53 48 83 EC 40 48 89 D7 48 89 CE 48 8B 0D", + ), + // FText::AsCultureInvariant on Linux + ( + Directness::Indirect, + "48 85 c9 74 13 f0 83 41 08 01 eb 0c 48 89 df e8 | ?? ?? ?? ?? 48 8d 43 10", + ), + // FText::FText signature + ( + Directness::Direct, + "41 57 41 56 53 48 83 ec 20 49 89 f7 49 89 fe 0f 57 c0 0f 29 44 24 10 0f 29 04 24 48 8d 5c 24 10 48 89 e7 e8 ?? ?? ?? ?? 48 89 df e8 ?? ?? ?? ?? bf 60 00 00 00 e8", + ), + // UE 5.4 + ( + Directness::Direct, + "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 33 F6 48 8B D9 48 89 44 24", + ), + ]; + + let res = join_all( + patterns + .iter() + .map(|(tag, p)| ctx.scan_tagged(tag, Pattern::new(p).unwrap())), + ) + .await; + + let mem = &ctx.image().memory; + + Ok(FTextFString(try_ensure_one(res.iter().flat_map( + |(directness, _, a)| match directness { + Directness::Direct => itertools::Either::Right(a.iter().map(|a| Ok(*a))), + Directness::Indirect => itertools::Either::Left(a.iter().map(|a| Ok(mem.rip4(*a)?))), + }, + ))?)) +}); diff --git a/patternsleuth/src/resolvers/unreal/fuobject_hash_tables.rs b/patternsleuth_resolvers/src/unreal/fuobject_hash_tables.rs similarity index 95% rename from patternsleuth/src/resolvers/unreal/fuobject_hash_tables.rs rename to patternsleuth_resolvers/src/unreal/fuobject_hash_tables.rs index 87e9c27..f6a927e 100644 --- a/patternsleuth/src/resolvers/unreal/fuobject_hash_tables.rs +++ b/patternsleuth_resolvers/src/unreal/fuobject_hash_tables.rs @@ -4,10 +4,7 @@ use futures::future::join_all; use patternsleuth_scanner::Pattern; -use crate::{ - resolvers::{impl_resolver_singleton, try_ensure_one, Result}, - MemoryTrait, -}; +use crate::{Result, impl_resolver_singleton, try_ensure_one}; /// public: static class FUObjectHashTables & __cdecl FUObjectHashTables::Get(void) #[derive(Debug, PartialEq)] diff --git a/patternsleuth/src/resolvers/unreal/game_loop.rs b/patternsleuth_resolvers/src/unreal/game_loop.rs similarity index 97% rename from patternsleuth/src/resolvers/unreal/game_loop.rs rename to patternsleuth_resolvers/src/unreal/game_loop.rs index 9a10083..2f0e516 100644 --- a/patternsleuth/src/resolvers/unreal/game_loop.rs +++ b/patternsleuth_resolvers/src/unreal/game_loop.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use futures::future::join_all; -use crate::resolvers::{ensure_one, impl_resolver_singleton, unreal::util}; +use crate::{ensure_one, impl_resolver_singleton, unreal::util}; #[derive(Debug, PartialEq)] #[cfg_attr( @@ -52,7 +52,7 @@ pub struct UGameEngineTick(pub usize); impl_resolver_singleton!(collect, UGameEngineTick); impl_resolver_singleton!(PEImage, UGameEngineTick, |ctx| async { - use crate::resolvers::Result; + use crate::Result; use patternsleuth_scanner::Pattern; let strings = ctx diff --git a/patternsleuth/src/resolvers/unreal/gengine.rs b/patternsleuth_resolvers/src/unreal/gengine.rs similarity index 87% rename from patternsleuth/src/resolvers/unreal/gengine.rs rename to patternsleuth_resolvers/src/unreal/gengine.rs index a328d61..4e3b228 100644 --- a/patternsleuth/src/resolvers/unreal/gengine.rs +++ b/patternsleuth_resolvers/src/unreal/gengine.rs @@ -2,11 +2,11 @@ use std::fmt::Debug; use iced_x86::Register; use itertools::Itertools as _; +use patternsleuth_image::image::Image; use crate::{ - disassemble::{disassemble, Control}, - image::Image, - resolvers::{impl_resolver_singleton, try_ensure_one, unreal::util, Result}, + disassemble::{Control, disassemble}, + {Result, impl_resolver_singleton, try_ensure_one, unreal::util}, }; #[derive(Debug, PartialEq)] @@ -20,7 +20,7 @@ impl_resolver_singleton!(PEImage, GEngine, |ctx| async { let strings = ctx.scan(util::utf16_pattern("rhi.DumpMemory\0")).await; let refs = util::scan_xrefs(ctx, &strings).await; - fn for_each(img: &Image<'_>, addr: usize) -> Result> { + fn for_each(img: &Image, addr: usize) -> Result> { let Some(root) = img.get_root_function(addr)? else { return Ok(None); }; diff --git a/patternsleuth/src/resolvers/unreal/gmalloc.rs b/patternsleuth_resolvers/src/unreal/gmalloc.rs similarity index 98% rename from patternsleuth/src/resolvers/unreal/gmalloc.rs rename to patternsleuth_resolvers/src/unreal/gmalloc.rs index 78f9c1d..e2bf6f7 100644 --- a/patternsleuth/src/resolvers/unreal/gmalloc.rs +++ b/patternsleuth_resolvers/src/unreal/gmalloc.rs @@ -6,9 +6,8 @@ use itertools::Itertools; use patternsleuth_scanner::Pattern; use crate::{ - disassemble::{disassemble, Control}, - resolvers::{impl_resolver_singleton, try_ensure_one, unreal::util, Result}, - MemoryTrait, + disassemble::{Control, disassemble}, + {Result, impl_resolver_singleton, try_ensure_one, unreal::util}, }; #[derive(Debug, PartialEq)] @@ -37,7 +36,7 @@ impl_resolver_singleton!(all, GMallocPatterns, |ctx| async { "48 ?? ?? f0 ?? 0f b1 ?? | ?? ?? ?? ?? 74 ?? ?? 85 ?? 74 ?? ?? 8b", // Purgatory "eb 03 ?? 8b ?? 48 8b ?? f0 ?? 0f b1 ?? | ?? ?? ?? ?? 74 ?? ?? 85 ?? 74 ?? ?? 8b", // Purg_notX "e8 ?? ?? ?? ?? 48 8b ?? f0 ?? 0f b1 ?? | ?? ?? ?? ?? 74 ?? ?? 85 ?? 74 ?? ?? 8b", // Purg_withX - "48 85 C9 74 2E 53 48 83 EC 20 48 8B D9 48 8B ?? | ?? ?? ?? ?? 48 85 C9", // A + "48 85 C9 74 2E 53 48 83 EC 20 48 8B D9 48 8B ?? | ?? ?? ?? ?? 48 85 C9", // A "75 ?? E8 ?? ?? ?? ?? 48 8b 0d | ?? ?? ?? ?? 48 8b ?? 48 ?? ?? ff 50 ?? 48 83 c4 ?? ?? c3", // bnew1 "48 85 C9 74 ?? 4C 8B 05 | ?? ?? ?? ?? 4D 85 C0 0F 84", // altshort "48 ?? ?? ?? ?? ?? ?? e8 ?? ?? ?? ?? 48 8b 0d | ?? ?? ?? ?? 48 8b 01 ff 50 ?? 84 c0 75 ?? b9 38 00 00 00", // gcreatemallocshort diff --git a/patternsleuth/src/resolvers/unreal/guobject_array.rs b/patternsleuth_resolvers/src/unreal/guobject_array.rs similarity index 96% rename from patternsleuth/src/resolvers/unreal/guobject_array.rs rename to patternsleuth_resolvers/src/unreal/guobject_array.rs index 406a704..5cb1d31 100644 --- a/patternsleuth/src/resolvers/unreal/guobject_array.rs +++ b/patternsleuth_resolvers/src/unreal/guobject_array.rs @@ -4,10 +4,7 @@ use futures::{future::join_all, try_join}; use patternsleuth_scanner::Pattern; -use crate::{ - resolvers::{ensure_one, impl_resolver_singleton, try_ensure_one, unreal::util, Result}, - MemoryTrait, -}; +use crate::{Result, ensure_one, impl_resolver_singleton, try_ensure_one, unreal::util}; #[derive(Debug, PartialEq)] #[cfg_attr( @@ -22,6 +19,8 @@ impl_resolver_singleton!(all, GUObjectArray, |ctx| async { "75 ?? 48 ?? ?? 48 8D 0D | ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 33 C9 4C 89 74 24", "45 84 c0 48 c7 41 10 00 00 00 00 b8 ff ff ff ff 4c 8d 1d | ?? ?? ?? ?? 89 41 08 4c 8b d1 4c 89 19 0f 45 05 ?? ?? ?? ?? ff c0 89 41 08 3b 05", "81 ce 00 00 00 02 83 e0 fb 89 47 08 48 8d 0d | ?? ?? ?? ?? 48 89 fa 45 31 c0 e8 ?? ?? ?? ??", + "8B 05 ?? ?? ?? ?? 2B 05 ?? ?? ?? ?? 2B 05 | ?? ?? ?? ??", + "E8 ?? ?? ?? ?? 8B 05 ?? ?? ?? ?? 8B 0D ?? ?? ?? ?? 03 0D | ?? ?? ?? ?? 29 C8 87 05", ]; // mov imm32 pattern for linux let patterns1 = [ diff --git a/patternsleuth/src/resolvers/unreal/kismet.rs b/patternsleuth_resolvers/src/unreal/kismet.rs similarity index 87% rename from patternsleuth/src/resolvers/unreal/kismet.rs rename to patternsleuth_resolvers/src/unreal/kismet.rs index 4738db4..7086272 100644 --- a/patternsleuth/src/resolvers/unreal/kismet.rs +++ b/patternsleuth_resolvers/src/unreal/kismet.rs @@ -2,14 +2,10 @@ use std::fmt::Debug; use futures::future::join_all; use iced_x86::{Decoder, DecoderOptions, Instruction}; +use patternsleuth_image::Addressable as _; use patternsleuth_scanner::Pattern; -use crate::{ - resolvers::{ - bail_out, ensure_one, impl_resolver, impl_resolver_singleton, try_ensure_one, Result, - }, - Addressable, Matchable, MemoryTrait, -}; +use crate::{Result, bail_out, ensure_one, impl_resolver, impl_resolver_singleton, try_ensure_one}; /// public: void __cdecl UObject::SkipFunction(struct FFrame &, void *const, class UFunction *) #[derive(Debug, PartialEq)] @@ -21,9 +17,8 @@ pub struct UObjectSkipFunction(pub usize); impl_resolver_singleton!(all, UObjectSkipFunction, |ctx| async { let patterns = [ "40 55 41 54 41 55 41 56 41 57 48 83 EC 30 48 8D 6C 24 20 48 89 5D 40 48 89 75 48 48 89 7D 50 48 8B 05 ?? ?? ?? ?? 48 33 C5 48 89 45 00 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 4D 8B ?? ?? 8B ?? 85 ?? 75 05 41 8B FC EB ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 48 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 48 ?? E0", - // linux - "55 01001??? 89 e5 01000??? 57 01000??? 56 01000??? 55 01000??? 54 53 50 01001??? 89 cf 01001??? 89 d6 01001??? 89 f3 01001??? 63 51 58 01001??? 85 d2 74 ?? 01001??? 63 67 5c 01001??? 83 fc 10 7f ?? 01001??? 89 e4 01001??? 8d 42 1e 01001??? 83 e0 f0 01001??? 29 c4 01001??? 89 e4 eb ??" + "55 01001??? 89 e5 01000??? 57 01000??? 56 01000??? 55 01000??? 54 53 50 01001??? 89 cf 01001??? 89 d6 01001??? 89 f3 01001??? 63 51 58 01001??? 85 d2 74 ?? 01001??? 63 67 5c 01001??? 83 fc 10 7f ?? 01001??? 89 e4 01001??? 8d 42 1e 01001??? 83 e0 f0 01001??? 29 c4 01001??? 89 e4 eb ??", ]; let res = join_all(patterns.iter().map(|p| ctx.scan(Pattern::new(p).unwrap()))).await; @@ -116,10 +111,10 @@ impl_resolver_singleton!(all, FFrameStep, |ctx| async { pub struct FFrameStepExplicitProperty(pub usize); impl_resolver_singleton!(all, FFrameStepExplicitProperty, |ctx| async { let patterns = [ - "41 8B 40 40 4D 8B C8 4C 8B D1 48 0F BA E0 08 73 ?? 48 8B ?? ?? ?? ?? 00 ?? ?? ?? ?? ?? ?? ?? 00 48 8B 40 10 4C 39 08 75 F7 48 8B 48 08 49 89 4A 38 ?? ?? ?? 40 ?? ?? ?? ?? ?? 4C ?? 41 ?? 49", - "48 89 5C 24 ?? 48 89 ?? 24 ?? 57 48 83 EC 20 41 8B 40 40 49 8B D8 48 8B ?? 48 8B F9 48 0F BA E0 08 73 ?? 48 8B ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 48 8B 40 10 48 39 18 75 F7 48 8B ?? 08 48 89 ?? 38 48", - // linux - "41 57 41 56 53 48 89 d3 49 89 ff f6 42 41 01 75 ?? 01001??? 89 f6 01001??? 8b 47 28 01001??? 63 4b 4c 01001??? 01 c1 01001??? 89 4f 38 01001??? 89 47 40 01001??? 8b 03 01001??? 89 df ff 90 88 00 00 00 84 c0 74 ?? 01001??? 8b 43 08 b9 00 00 00 24 23 48 10 01001??? 8b 7f 40 81 f9 00 00 00 04", + "41 8B 40 40 4D 8B C8 4C 8B D1 48 0F BA E0 08 73 ?? 48 8B ?? ?? ?? ?? 00 ?? ?? ?? ?? ?? ?? ?? 00 48 8B 40 10 4C 39 08 75 F7 48 8B 48 08 49 89 4A 38 ?? ?? ?? 40 ?? ?? ?? ?? ?? 4C ?? 41 ?? 49", + "48 89 5C 24 ?? 48 89 ?? 24 ?? 57 48 83 EC 20 41 8B 40 40 49 8B D8 48 8B ?? 48 8B F9 48 0F BA E0 08 73 ?? 48 8B ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 48 8B 40 10 48 39 18 75 F7 48 8B ?? 08 48 89 ?? 38 48", + // linux + "41 57 41 56 53 48 89 d3 49 89 ff f6 42 41 01 75 ?? 01001??? 89 f6 01001??? 8b 47 28 01001??? 63 4b 4c 01001??? 01 c1 01001??? 89 4f 38 01001??? 89 47 40 01001??? 8b 03 01001??? 89 df ff 90 88 00 00 00 84 c0 74 ?? 01001??? 8b 43 08 b9 00 00 00 24 23 48 10 01001??? 8b 7f 40 81 f9 00 00 00 04", ]; let res = join_all(patterns.iter().map(|p| ctx.scan(Pattern::new(p).unwrap()))).await; @@ -145,7 +140,7 @@ impl_resolver!(all, FFrameStepViaExec, |ctx| async { "48 89 5C 24 08 48 89 74 24 10 57 48 83 EC ?? 33 FF 33 C0 49 8B F0 48 8B DA 48 8B CA 48 89 7C 24 20 48 89 7C 24 28 48 39 42 20 74 10 48 8B 52 18 4C 8D 44 24 20 E8 [ ?? ?? ?? ?? ] EB 1C 4C 8B 82 80 00 00 00 49 8B 40 ?? 48 89 82 80 00 00 00 48 8D 54 24 20 E8 [ ?? ?? ?? ?? ] 48 8B 43 20 48 8D 4C 24 20 48 85 C0", "48 89 5C 24 08 48 89 74 24 10 57 48 83 EC ?? 33 FF 49 8B F0 48 8B DA 48 89 7C 24 20 48 ?? ?? ?? ?? ?? ?? ?? 48 39 7A 20 74 10 48 8B 52 18 4C 8D 44 24 20 E8 [ ?? ?? ?? ?? ] EB 1C 4C 8B 82 80 00 00 00 49 8B 40 ?? 48 89 82 80 00 00 00 48 8D 54 24 20 E8 [ ?? ?? ?? ?? ] 48 8B 43 20 48 8D 4C 24 20 48 85 C0 40 0F", "48 89 5C 24 08 48 89 74 24 10 57 48 83 EC 30 33 ?? 49 8B F0 48 89 ?? 24 20 48 8B ?? 48 89 ?? 24 28 E8 [ ?? ?? ?? ?? ] 48 8B ?? 48 39 ?? 20 74 10 48 8B ?? 18 4C 8D 44 24 20 E8 ?? ?? ?? ?? EB 1C 4C 8B ?? ?? 00 00 00 48 8D 54 24 20 49 8B 40 20 48 89 ?? ?? 00 00 00 E8 [ ?? ?? ?? ?? ] 48 8B ?? 20 48", - "41 56 53 01001??? 83 ec 18 01001??? 89 d6 01001??? 89 f3 0f 57 c0 0f 29 04 ??100??? e8 ?? ?? ?? ?? 01001??? 83 7b 20 00 74 ?? 01001??? 8b 73 18 01001??? 89 e2 01001??? 89 df e8 [ ?? ?? ?? ?? ] eb ?? 01001??? 8b 93 88 00 00 00 01001??? 8b 42 20 01001??? 89 83 88 00 00 00 01001??? 89 e6 01001??? 89 df e8 [ ?? ?? ?? ?? ]" + "41 56 53 01001??? 83 ec 18 01001??? 89 d6 01001??? 89 f3 0f 57 c0 0f 29 04 ??100??? e8 ?? ?? ?? ?? 01001??? 83 7b 20 00 74 ?? 01001??? 8b 73 18 01001??? 89 e2 01001??? 89 df e8 [ ?? ?? ?? ?? ] eb ?? 01001??? 8b 93 88 00 00 00 01001??? 8b 42 20 01001??? 89 83 88 00 00 00 01001??? 89 e6 01001??? 89 df e8 [ ?? ?? ?? ?? ]", ]; let res = join_all( diff --git a/patternsleuth/src/resolvers/unreal/mod.rs b/patternsleuth_resolvers/src/unreal/mod.rs similarity index 95% rename from patternsleuth/src/resolvers/unreal/mod.rs rename to patternsleuth_resolvers/src/unreal/mod.rs index d5c5cbb..c836931 100644 --- a/patternsleuth/src/resolvers/unreal/mod.rs +++ b/patternsleuth_resolvers/src/unreal/mod.rs @@ -22,19 +22,18 @@ use std::{ use futures::future::join_all; use iced_x86::FlowControl; use itertools::Itertools; +use patternsleuth_image::Addressable as _; use patternsleuth_scanner::Pattern; use crate::{ - disassemble::{disassemble, Control}, - resolvers::{ - bail_out, ensure_one, impl_resolver, impl_resolver_singleton, try_ensure_one, Result, - }, - Addressable, Image, Matchable, MemoryTrait, + Image, + disassemble::{Control, disassemble}, + {Result, bail_out, ensure_one, impl_resolver, impl_resolver_singleton, try_ensure_one}, }; #[allow(unused)] pub mod util { - use crate::resolvers::AsyncContext; + use crate::AsyncContext; use super::*; @@ -55,7 +54,7 @@ pub mod util { Pattern::from_bytes(utf16(string)).unwrap() } pub async fn scan_xrefs( - ctx: &AsyncContext<'_>, + ctx: &AsyncContext<'_, '_>, addresses: impl IntoIterator + Copy, ) -> Vec { let refs_indirect = join_all( @@ -96,7 +95,7 @@ pub mod util { } pub async fn scan_xcalls( - ctx: &AsyncContext<'_>, + ctx: &AsyncContext<'_, '_>, addresses: impl IntoIterator + Copy, ) -> Vec { let refs_indirect = join_all( @@ -124,7 +123,7 @@ pub mod util { refs.into_iter().flatten().collect() } - pub fn root_functions<'a, I>(ctx: &AsyncContext<'_>, addresses: I) -> Result> + pub fn root_functions<'a, I>(ctx: &AsyncContext<'_, '_>, addresses: I) -> Result> where I: IntoIterator + Copy, { @@ -137,7 +136,7 @@ pub mod util { .collect()) } - pub fn find_calls(img: &Image<'_>, f: usize) -> Result> { + pub fn find_calls(img: &Image, f: usize) -> Result> { let mut calls = vec![]; disassemble(img, f, |inst| { @@ -172,8 +171,8 @@ pub mod util { Ok(calls) } - pub fn find_path( - img: &Image<'_>, + pub fn find_path<'a>( + img: &'a Image<'a>, f: usize, depth: usize, searched: &mut HashSet, diff --git a/patternsleuth/src/resolvers/unreal/pak.rs b/patternsleuth_resolvers/src/unreal/pak.rs similarity index 93% rename from patternsleuth/src/resolvers/unreal/pak.rs rename to patternsleuth_resolvers/src/unreal/pak.rs index b58feba..351a6e1 100644 --- a/patternsleuth/src/resolvers/unreal/pak.rs +++ b/patternsleuth_resolvers/src/unreal/pak.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use futures::future::join_all; -use crate::resolvers::{ensure_one, impl_resolver_singleton, unreal::util}; +use crate::{ensure_one, impl_resolver_singleton, unreal::util}; #[derive(Debug, PartialEq)] #[cfg_attr( diff --git a/patternsleuth/src/resolvers/unreal/save_game.rs b/patternsleuth_resolvers/src/unreal/save_game.rs similarity index 99% rename from patternsleuth/src/resolvers/unreal/save_game.rs rename to patternsleuth_resolvers/src/unreal/save_game.rs index 8a6bdf7..1754b46 100644 --- a/patternsleuth/src/resolvers/unreal/save_game.rs +++ b/patternsleuth_resolvers/src/unreal/save_game.rs @@ -4,7 +4,7 @@ use futures::future::join_all; use patternsleuth_scanner::Pattern; -use crate::resolvers::{ensure_one, impl_resolver_singleton}; +use crate::{ensure_one, impl_resolver_singleton}; /// public: static bool __cdecl UGameplayStatics::SaveGameToMemory(class USaveGame *, class TArray > &) #[derive(Debug, PartialEq)] @@ -92,7 +92,7 @@ impl_resolver_singleton!(all, UGameplayStaticsLoadGameFromSlot, |ctx| async { "48 89 5C 24 08 55 56 57 48 8D AC 24 ?? FF FF FF 48 81 EC ?? 01 00 00 48 8B D9 ?? ?? ?? ?? ?? ?? ?? ?? ?? 8B F2 48 85 C9 75 26 8D 4F 08 E8 ?? ?? ?? FF 48 8B C8 48 85 C0 74 0C 48 8D 05 ?? ?? ?? ?? 48 89 01 EB 03 48 8B CF 48 89 0D ?? ?? ?? ?? 48 8B 01 FF 50 40 48 8B C8 48 85 C0 0F 84 ?? ?? 00 00 8B 43", "48 89 5C 24 18 55 56 57 48 8D AC 24 ?? FF FF FF 48 81 EC ?? ?? 00 00 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? 00 00 00 48 8B D9 33 FF 48 8B 0D ?? ?? ?? ?? 8B F2 48 85 C9 75 26 8D 4F 08 E8 ?? ?? ?? ?? 48 8B C8 48 85 C0 74 0C 48 8D 05 ?? ?? ?? ?? 48 89 01 EB 03 48 8B CF 48 89 0D ?? ?? ?? ?? 48 8B 01", // linux - "55 53 48 83 ec 18 89 f5 01001??? 89 fb 0f 57 c0 0f 29 04 ??100100 01001??? 8b 3d ?? ?? ?? ?? 01001??? 85 ff 74 ?? 01001??? 8b 07 ff 50 48 01001??? 85 c0 75 ?? eb ?? bf 08 00 00 00 e8 ?? ?? ?? ?? 01001??? 89 c7 01001??? c7 00 ?? ?? ?? ?? 01001??? 89 05 ?? ?? ?? ?? 01001??? 8b 07 ff 01010??? ?? 01001??? 85 c0 74 ??" + "55 53 48 83 ec 18 89 f5 01001??? 89 fb 0f 57 c0 0f 29 04 ??100100 01001??? 8b 3d ?? ?? ?? ?? 01001??? 85 ff 74 ?? 01001??? 8b 07 ff 50 48 01001??? 85 c0 75 ?? eb ?? bf 08 00 00 00 e8 ?? ?? ?? ?? 01001??? 89 c7 01001??? c7 00 ?? ?? ?? ?? 01001??? 89 05 ?? ?? ?? ?? 01001??? 8b 07 ff 01010??? ?? 01001??? 85 c0 74 ??", ]; let res = join_all(patterns.iter().map(|p| ctx.scan(Pattern::new(p).unwrap()))).await; diff --git a/patternsleuth/src/resolvers/unreal/static_construct_object.rs b/patternsleuth_resolvers/src/unreal/static_construct_object.rs similarity index 90% rename from patternsleuth/src/resolvers/unreal/static_construct_object.rs rename to patternsleuth_resolvers/src/unreal/static_construct_object.rs index 6b9fca3..f94baf3 100644 --- a/patternsleuth/src/resolvers/unreal/static_construct_object.rs +++ b/patternsleuth_resolvers/src/unreal/static_construct_object.rs @@ -4,9 +4,8 @@ use futures::{future::join_all, join}; use patternsleuth_scanner::Pattern; use crate::{ - disassemble::{disassemble, Control}, - resolvers::{ensure_one, impl_resolver_singleton, try_ensure_one, unreal::util, Result}, - MemoryTrait, + disassemble::{Control, disassemble}, + {Result, ensure_one, impl_resolver_singleton, try_ensure_one, unreal::util}, }; /// class UObject * __cdecl StaticConstructObject_Internal(struct FStaticConstructObjectParameters const &) @@ -41,18 +40,18 @@ impl_resolver_singleton!(all, StaticConstructObjectInternalPatterns, |ctx| async "E8 | ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? C0 E9 ?? 32 88 ?? ?? ?? ?? 80 E1 01 30 88 ?? ?? ?? ?? 48", "E8 | ?? ?? ?? ?? 48 8B D8 48 39 75 30 74 15", /* - 03f4df3f c6 44 24 MOV byte ptr [RSP + local_88 ],0x0 - 30 00 - 03f4df44 0f 57 c0 XORPS XMM0 ,XMM0 - 03f4df47 0f 11 44 MOVUPS xmmword ptr [RSP + local_80[0] ],XMM0 - 24 38 - 03f4df4c 4c 89 ff MOV RDI ,R15 - 03f4df4f e8 2c b6 CALL StaticConstructObject_Internal undefined StaticConstructObject_ - 02 03 - 03f4df54 48 89 c3 MOV RBX ,RAX - - */ - "c6 44 24 30 00 0f 57 c0 0f 11 44 24 38 4c 89 ff e8 | ?? ?? ?? ?? 48 89" + 03f4df3f c6 44 24 MOV byte ptr [RSP + local_88 ],0x0 + 30 00 + 03f4df44 0f 57 c0 XORPS XMM0 ,XMM0 + 03f4df47 0f 11 44 MOVUPS xmmword ptr [RSP + local_80[0] ],XMM0 + 24 38 + 03f4df4c 4c 89 ff MOV RDI ,R15 + 03f4df4f e8 2c b6 CALL StaticConstructObject_Internal undefined StaticConstructObject_ + 02 03 + 03f4df54 48 89 c3 MOV RBX ,RAX + + */ + "c6 44 24 30 00 0f 57 c0 0f 11 44 24 38 4c 89 ff e8 | ?? ?? ?? ?? 48 89", ]; let res = join_all(patterns.iter().map(|p| ctx.scan(Pattern::new(p).unwrap()))).await; @@ -124,9 +123,9 @@ impl_resolver_singleton!(PEImage, StaticConstructObjectInternalString, |ctx| asy use itertools::Itertools; use crate::{ + Image, disassemble::disassemble_single, - resolvers::{bail_out, Context, Result}, - Image, MemoryTrait, + {Context, Result, bail_out}, }; let strings = join_all( @@ -176,7 +175,7 @@ impl_resolver_singleton!(PEImage, StaticConstructObjectInternalString, |ctx| asy .into_iter() .flatten(); - fn check_is_new_object(img: &Image<'_>, f: usize) -> Result { + fn check_is_new_object(img: &Image, f: usize) -> Result { let cmp = "NewObject with empty name can't" .encode_utf16() .flat_map(u16::to_le_bytes) @@ -199,8 +198,7 @@ impl_resolver_singleton!(PEImage, StaticConstructObjectInternalString, |ctx| asy if img .memory .range(ptr..ptr + cmp.len()) - .map(|data| data == cmp) - .unwrap_or(false) + .is_ok_and(|data| data == cmp) { is = true; return Ok(Control::Exit); @@ -233,7 +231,7 @@ impl_resolver_singleton!(PEImage, StaticConstructObjectInternalString, |ctx| asy Ok(false) } - fn check_is_static_construct(img: &Image<'_>, f: usize) -> Result { + fn check_is_static_construct(img: &Image, f: usize) -> Result { let mut is = false; disassemble(img, f, |inst| { let cur = inst.ip() as usize; diff --git a/patternsleuth/src/resolvers/unreal/static_find_object.rs b/patternsleuth_resolvers/src/unreal/static_find_object.rs similarity index 52% rename from patternsleuth/src/resolvers/unreal/static_find_object.rs rename to patternsleuth_resolvers/src/unreal/static_find_object.rs index d0eea5c..4a05a54 100644 --- a/patternsleuth/src/resolvers/unreal/static_find_object.rs +++ b/patternsleuth_resolvers/src/unreal/static_find_object.rs @@ -1,9 +1,9 @@ -use crate::resolvers::{ensure_one, impl_resolver_singleton, unreal::util}; +use crate::{ensure_one, impl_resolver_singleton, unreal::util}; /// ``` -/// class UObject * __cdecl StaticFindObjectFast(class UClass *, class UObject *, class FName, bool, bool, enum EObjectFlags, enum EInternalObjectFlags) -/// class UObject * __cdecl StaticFindObjectFast(class UClass *, class UObject *, class FName, bool, enum EObjectFlags, enum EInternalObjectFlags) -/// class UObject * __cdecl StaticFindObjectFast(class UClass *, class UObject *, class FName, bool, bool, enum EObjectFlags) +/// //class UObject * __cdecl StaticFindObjectFast(class UClass *, class UObject *, class FName, bool, bool, enum EObjectFlags, enum EInternalObjectFlags) +/// //class UObject * __cdecl StaticFindObjectFast(class UClass *, class UObject *, class FName, bool, enum EObjectFlags, enum EInternalObjectFlags) +/// //class UObject * __cdecl StaticFindObjectFast(class UClass *, class UObject *, class FName, bool, bool, enum EObjectFlags) /// ``` #[derive(Debug, PartialEq)] #[cfg_attr( diff --git a/patternsleuth_scanner/benches/scan.rs b/patternsleuth_scanner/benches/scan.rs index ce4ebc7..efef510 100644 --- a/patternsleuth_scanner/benches/scan.rs +++ b/patternsleuth_scanner/benches/scan.rs @@ -1,4 +1,4 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use patternsleuth_scanner::*; fn gig(c: &mut Criterion) { @@ -9,7 +9,7 @@ fn gig(c: &mut Criterion) { let needle = b"\xf9\x82\xdb\xdb\x2d\x32\x6f\x15\x11\x44\x54\xf4\xc8\xaa\xd1\x72\x53\x96\xa5\x7b\x22\x24\x94\x7f\xec\x28\xc7\xe0\x5e\xd4\xae\x39"; - data.extend((0..size - needle.len()).map(|_| rng.gen::())); + data.extend((0..size - needle.len()).map(|_| rng.r#gen::())); data.extend(needle); let pattern = Pattern::new("f9 82 db db 2d ?? 6f 15 ?? 44 54 f4 c8 aa d1 72 53 ?? a5 7b 22 24 94 7f ec 28 ?? e0 5e d4 ae 39").unwrap(); diff --git a/patternsleuth_scanner/src/lib.rs b/patternsleuth_scanner/src/lib.rs index bb63fee..1cae147 100644 --- a/patternsleuth_scanner/src/lib.rs +++ b/patternsleuth_scanner/src/lib.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Context, Error, Result}; +use anyhow::{Context, Error, Result, bail}; #[derive(Clone, Eq, PartialEq)] pub struct PatternSimple { @@ -35,8 +35,8 @@ fn fmt_byte(f: &mut std::fmt::Formatter<'_>, sig: u8, mask: u8) -> std::fmt::Res write!(f, "{:X}?", sig >> 4)?; } else { for bit in (0..8).rev() { - if mask >> bit & 1 == 1 { - write!(f, "{}", sig >> bit & 1)?; + if (mask >> bit) & 1 == 1 { + write!(f, "{}", (sig >> bit) & 1)?; } else { write!(f, "?")?; } @@ -234,8 +234,7 @@ impl Pattern { .checked_add_signed(i32::from_le_bytes( data[index + offset..index + offset + 4].try_into().unwrap(), ) as isize) - .map(|x| x == xref.0) - .unwrap_or(false) + .is_some_and(|x| x == xref.0) }) } pub fn captures<'data>( @@ -877,7 +876,7 @@ mod test { let patterns = [&Pattern::new("01 02").unwrap()]; // obtuse generator to test every combination of chunk boundaries - let data: Vec<_> = std::iter::repeat([1, 2, 3]).take(32).flatten().collect(); + let data: Vec<_> = std::iter::repeat_n([1, 2, 3], 32).flatten().collect(); let matches: Vec<_> = (0..3) .map(|offset| { (0..len / 3)