diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 400d577..4910b91 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,8 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + - package-ecosystem: cargo + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 diff --git a/Cargo.lock b/Cargo.lock index e165fd6..902da49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,19 +4,19 @@ version = 3 [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "plt-rs" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "libc", @@ -25,27 +25,27 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "syn" -version = "2.0.52" +version = "2.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "837a7e8026c6ce912ff01cefbe8cafc2f8010ac49682e2a3d9decc3bce1ecaaf" dependencies = [ "proc-macro2", "quote", @@ -54,18 +54,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -74,6 +74,6 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml index e911170..0c89bb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "plt-rs" -version = "0.2.0" +version = "0.3.0" edition = "2021" authors = ["ohchase"] license = "MIT" @@ -9,7 +9,7 @@ documentation = "https://docs.rs/plt-rs" readme = "README.md" repository = "https://github.com/ohchase/plt-rs/" homepage = "https://github.com/ohchase/plt-rs/" -keywords = ["plt", "elf", "bionic", "linker", "hooking"] +keywords = ["plt", "elf", "linker", "hook", "symbols"] exclude = ["/examples"] [lib] @@ -18,8 +18,8 @@ crate-type = ["lib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libc = "0.2.149" -thiserror = "1.0.49" +libc = "0.2" +thiserror = "1.0" [dev-dependencies] anyhow = "1.0.75" diff --git a/Cross.toml b/Cross.toml index f7229f8..32cb1da 100644 --- a/Cross.toml +++ b/Cross.toml @@ -4,4 +4,4 @@ image = "ghcr.io/cross-rs/aarch64-linux-android:main" [target.i686-linux-android] image = "ghcr.io/cross-rs/i686-linux-android:main" [target.x86_64-linux-android] -image = "ghcr.io/cross-rs/x86_64-linux-android:main" \ No newline at end of file +image = "ghcr.io/cross-rs/x86_64-linux-android:main" diff --git a/README.md b/README.md index 8f74122..bb3e335 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PLT-RS +# Plt-rs ## Change Notes ### 0.1.0 initial release @@ -6,9 +6,14 @@ - removed hooking functionality - reduced linux/android bloat - documented and generally made more ergonomic - -## Inspirations / Sources utilized -Projects I referenced while working on this. +### 0.3.0 usability +- promote finding function in dynamic library functionality to public +- added tests +- don't use patch version in dependencies +- ci/cd dependency updates + +## Inspired +Projects I referenced and was heavily inspired by while working on this. - [Plthook by Kubo] https://github.com/kubo/plthook - [Bhook by bytedance] https://github.com/bytedance/bhook @@ -27,7 +32,9 @@ the library has to change how it crawls the dynamially loaded objects. ## Why Video game modding, reverse engineering, etc - Can hook networking calls: recv / send -- Rendering calls: eglSwapBuffers / video game overlays +- Rendering calls: eglSwapBuffers / video game mods and overlays +- Application hardening and monitoring +- Defensive and Offensive usages ## Supports and tests against many targets - ![i686-unknown-linux-gnu](https://github.com/ohchase/plt-rs/actions/workflows/i686-unknown-linux-gnu.yml/badge.svg) @@ -156,3 +163,8 @@ successfully identified libc getpid offset: 0x7E460 page start for function is 0x000061019c41b000 new pid is: 999 ``` + +## References / Inspirations +Projects I referenced and was heavily inspired by while working on this. +- [Plthook by Kubo] https://github.com/kubo/plthook +- [Bhook by bytedance] https://github.com/bytedance/bhook diff --git a/src/lib.rs b/src/lib.rs index 25e0a41..fe7a0ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -488,7 +488,7 @@ impl<'a> DynamicLibrary<'a> { return Some(symbol); } } - return None; + None } /// Finding target function differs on 32 bit and 64 bit. @@ -505,9 +505,7 @@ impl<'a> DynamicLibrary<'a> { symbols .resolve_name(e.symbol_index() as usize, string_table) .map(|s| (e, s)) - }) - .filter(|(_, s)| s.eq(symbol_name)) - .next() + }).find(|(_, s)| s.eq(symbol_name)) .map(|(target_function, _)| target_function) { return Some(symbol); @@ -521,15 +519,13 @@ impl<'a> DynamicLibrary<'a> { symbols .resolve_name(e.symbol_index() as usize, string_table) .map(|s| (e, s)) - }) - .filter(|(_, s)| s.eq(symbol_name)) - .next() + }).find(|(_, s)| s.eq(symbol_name)) .map(|(target_function, _)| target_function) { return Some(symbol); } } - return None; + None } /// Access the plt as a dynamic relocation table if possible /// can fail if the plt is not available or the plt is with addend diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index baa1672..c6db566 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,5 +1,134 @@ +use libc::c_void; +use plt_rs::{collect_modules, DynamicLibrary, RelocationTable}; + +/// Make sure we can load all the modules we load ourselves +/// A simple sanity check, we are not checking the modules contents in any meaningful way. +/// But this works great to catch issues, because realistically we should never run into a issue parsing libraries. #[test] -fn can_load_own_link_map() {} +fn can_load_own_link_map() { + let entries = collect_modules(); + + for entry in entries.into_iter() { + if let Ok(dynamic_lib) = DynamicLibrary::initialize(entry) { + let dynamic_symbols = dynamic_lib.symbols().expect("symbols..."); + let string_table = dynamic_lib.string_table(); + if let Some(dyn_relas) = dynamic_lib.addend_relocs() { + dyn_relas + .entries() + .iter() + .flat_map(|e| { + dynamic_symbols.resolve_name(e.symbol_index() as usize, string_table) + }) + .filter(|s| !s.is_empty()) + .for_each(|s| println!("\t{}", s)); + } + + if let Some(dyn_relocs) = dynamic_lib.relocs() { + dyn_relocs + .entries() + .iter() + .flat_map(|e| { + dynamic_symbols.resolve_name(e.symbol_index() as usize, string_table) + }) + .filter(|s| !s.is_empty()) + .for_each(|s| println!("\t{}", s)); + } + + if let Some(plt) = dynamic_lib.plt() { + match plt { + RelocationTable::WithAddend(rel) => { + rel.entries() + .iter() + .flat_map(|e| { + dynamic_symbols + .resolve_name(e.symbol_index() as usize, string_table) + }) + .filter(|s| !s.is_empty()) + .for_each(|s| println!("\t{}", s)); + } + RelocationTable::WithoutAddend(rel) => { + rel.entries() + .iter() + .flat_map(|e| { + dynamic_symbols + .resolve_name(e.symbol_index() as usize, string_table) + }) + .filter(|s| !s.is_empty()) + .for_each(|s| println!("\t{}", s)); + } + } + } + } + } +} +unsafe fn getpid() -> u32 { + 999 +} + +/// Finding executable target differs on unix and android +#[cfg(target_os = "linux")] +fn find_executable<'a>() -> Option> { + let loaded_modules = collect_modules(); + loaded_modules.into_iter().next() +} + +/// Finding executable target differs on unix and android +#[cfg(target_os = "android")] +fn find_executable<'a>() -> Option> { + let executable = std::env::current_exe().expect("current exe"); + let file_stem = executable.file_stem()?; + let file_stem = file_stem.to_str()?; + let loaded_modules = collect_modules(); + loaded_modules + .into_iter() + .filter(|lib| lib.name().contains(file_stem)) + .next() +} #[test] -fn can_hook_getpid() {} +fn can_hook_getpid() { + let my_pid = unsafe { libc::getpid() }; + println!("application pid is {my_pid}"); + + let executable_entry = find_executable().expect("can find executable"); + println!("successfully identified executable"); + + let dyn_lib = DynamicLibrary::initialize(executable_entry).expect("can load"); + println!("successfully initialied dynamic library for instrumentation"); + + let target_function = dyn_lib + .try_find_function("getpid") + .expect("executable should link getpid"); + println!( + "successfully identified libc getpid offset: {:#X?}", + target_function.r_offset + ); + + let base_addr = dyn_lib.library().addr(); + let plt_fn_ptr = (base_addr + target_function.r_offset as usize) as *mut *mut libc::c_void; + let page_size = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as usize }; + let plt_page = ((plt_fn_ptr as usize / page_size) * page_size) as *mut libc::c_void; + println!("page start for function is {plt_page:#X?}"); + + let _stored_address = unsafe { + // Set the memory page to read, write + let prot_res = libc::mprotect(plt_page, page_size, libc::PROT_WRITE | libc::PROT_READ); + if prot_res != 0 { + panic!("failed to set prot res"); + } + + // Replace the function address + let previous_address = std::ptr::replace(plt_fn_ptr, getpid as *mut _); + + // Set the memory page protection back to read only + let prot_res = libc::mprotect(plt_page, page_size, libc::PROT_READ); + if prot_res != 0 { + panic!("failed to set prot res"); + } + + previous_address as *const c_void + }; + + let get_pid = unsafe { libc::getpid() }; + assert_eq!(get_pid, 999) +}