Skip to content

Commit

Permalink
Merge pull request #2 from ohchase/version-0.2.0
Browse files Browse the repository at this point in the history
Version 0.2.0
  • Loading branch information
ohchase committed Mar 11, 2024
2 parents 620e2e0 + 5dc98cd commit 1a81908
Show file tree
Hide file tree
Showing 19 changed files with 1,150 additions and 1,869 deletions.
436 changes: 16 additions & 420 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
[package]
name = "plt-rs"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
authors = ["ohchase"]
license = "MIT"
description = "Library for iterating and hooking linux and android applications PLT (Procedure Linkage Table) at runtime"
description = "Library for inspecting, analyzing, and instrumenting linux and android applications runtime symbol linkage"
documentation = "https://docs.rs/plt-rs"
readme = "README.md"
repository = "https://github.com/ohchase/plt-rs/"
homepage = "https://github.com/ohchase/plt-rs/"
keywords = ["hacking", "linux", "android", "reversing", "plt", "elf", "bionic", "linkmap"]
exclude = ["/examples", "/templates"]
exclude = ["/examples"]

[lib]
crate-type = ["lib"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
libc = "0.2.137"
thiserror = "1.0.40"
proc-maps = "0.3.0"
libc = "0.2.149"
thiserror = "1.0.49"

[dev-dependencies]
anyhow = "1.0.75"
145 changes: 112 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# PLT-RS
Get in losers we are hooking the procedural linkage tables

## Derivatives
## Change Notes
### 0.1.0 initial release
### 0.2.0 total revamp
- removed hooking functionality
- reduced linux/android bloat
- documented and generally made more ergonomic

## Inspirations / Sources utilized
Projects I referenced while working on this.
- [Plthook by Kubo] https://github.com/kubo/plthook
- [Bhook by bytedance] https://github.com/bytedance/bhook
Expand All @@ -23,7 +29,7 @@ Video game modding, reverse engineering, etc
- Can hook networking calls: recv / send
- Rendering calls: eglSwapBuffers / video game overlays

## Look at all these ~~chickens~~ targets
## 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)
- ![x86_64-unknown-linux-gnu](https://github.com/ohchase/plt-rs/actions/workflows/x86_64-unknown-linux-gnu.yml/badge.svg)
- ![aarch64-unknown-linux-gnu](https://github.com/ohchase/plt-rs/actions/workflows/aarch64-unknown-linux-gnu.yml/badge.svg)
Expand All @@ -35,45 +41,118 @@ Video game modding, reverse engineering, etc
- ![armv7-linux-androideabi](https://github.com/ohchase/plt-rs/actions/workflows/armv7-linux-androideabi.yml/badge.svg)

## Show me da code
Here we are hooking our own executables usages of libc puts
Here we are hooking our own executables usages of libc getpid.
Refer to `examples/hook_getpid.rs` for the full example, supporting android and 32 bit.
A good chunk of the code is for the actual pointer replacement to hook the function!

```rust
#[inline(never)]
unsafe fn puts_hooked(_input: *const libc::c_char) -> libc::c_int {
let c_str: &std::ffi::CStr = unsafe { std::ffi::CStr::from_ptr(_input) };
let str_slice: &str = c_str.to_str().unwrap();
println!("Puts C was hooked. Intercepted: {:#?}", str_slice);
0
}

fn get_mut_map<'a>() -> plt::MutableLinkMap<'a> {
use plt::LinkMapBacked;
let link_map =
plt::LinkMapView::from_address(main as *mut libc::c_void as usize).expect("open link map");
plt::MutableLinkMap::from_view(link_map)
/// our own get pid function
unsafe fn getpid() -> u32 {
999
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut mutable_link_map = get_mut_map();
let _previous_function = mutable_link_map
.hook::<unsafe fn(*const libc::c_char) -> libc::c_int>("puts", puts_hooked as *const _)?
.unwrap();
unsafe { libc::puts(String::from("Hello\0").as_ptr() as *const _) };
Ok(())
/// Finding target function differs on 32 bit and 64 bit.
/// On 64 bit we want to check the addended relocations table only, opposed to the addendless relocations table.
/// Additionally, we will fall back to the plt given it is an addended relocation table.
#[cfg(target_pointer_width = "64")]
fn try_find_function<'a>(
dyn_lib: &'a DynamicLibrary,
dyn_symbols: &'a DynamicSymbols,
) -> Option<&'a plt_rs::elf64::DynRela> {
let string_table = dyn_lib.string_table();
if let Some(dyn_relas) = dyn_lib.addend_relocs() {
let dyn_relas = dyn_relas.entries().iter();
if let Some(symbol) = dyn_relas
.flat_map(|e| {
dyn_symbols
.resolve_name(e.symbol_index() as usize, string_table)
.map(|s| (e, s))
})
.filter(|(_, s)| s.eq("getpid"))
.next()
.map(|(target_function, _)| target_function)
{
return Some(symbol);
}
}

if let Some(dyn_relas) = dyn_lib.plt_rela() {
let dyn_relas = dyn_relas.entries().iter();
if let Some(symbol) = dyn_relas
.flat_map(|e| {
dyn_symbols
.resolve_name(e.symbol_index() as usize, string_table)
.map(|s| (e, s))
})
.filter(|(_, s)| s.eq("getpid"))
.next()
.map(|(target_function, _)| target_function)
{
return Some(symbol);
}
}
return None;
}
```

Below you can see it works on both debug and release build.
fn main() -> Result<()> {
let my_pid = unsafe { libc::getpid() };
println!("application pid is {my_pid}");

let executable_entry = find_executable().ok_or(anyhow!("unable to find target executable"))?;
println!("successfully identified executable");

let dyn_lib = DynamicLibrary::initialize(executable_entry)?;
println!("successfully initialied dynamic library for instrumentation");

let dyn_symbols = dyn_lib
.symbols()
.ok_or(anyhow!("dynamic lib should have symbols"))?;
let target_function =
try_find_function(&dyn_lib, &dyn_symbols).ok_or(anyhow!("unable to find getpid symbol"))?;
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 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 c_void;
println!("page start for function is {plt_page:#X?}");

```shell
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
Running `target/debug/examples/libc_puts`
Puts C was hooked. Intercepted: "Hello"
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 {
println!("protection res: {prot_res}");
return Err(anyhow!("mprotect to rw"));
}

// 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 {
return Err(anyhow!("mprotect to r"));
}

previous_address as *const c_void
};

let get_pid = unsafe { libc::getpid() };
println!("new pid is: {get_pid}");

Ok(())
}
```

```shell
Finished release [optimized] target(s) in 8.09s
Running `target/release/examples/libc_puts`
Puts C was hooked. Intercepted: "Hello"
```terminal
application pid is 127765
successfully identified executable
successfully initialied dynamic library for instrumentation
successfully identified libc getpid offset: 0x7E460
page start for function is 0x000061019c41b000
new pid is: 999
```
72 changes: 72 additions & 0 deletions examples/dump_plt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use anyhow::Result;
use plt_rs::{collect_modules, DynamicLibrary, RelocationTable};

fn main() -> Result<()> {
let entries = collect_modules();

for entry in entries.into_iter() {
println!("[{:?}] Addr: {:#X?}", entry.name(), entry.addr());
if let Ok(dynamic_lib) = DynamicLibrary::initialize(entry) {
println!(
"Dynamic String Table Length: {}",
dynamic_lib.string_table().total_size()
);

let dynamic_symbols = dynamic_lib.symbols().expect("symbols...");
let string_table = dynamic_lib.string_table();

println!("dynamic addend relocations:");
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));
}

println!("dynamic relocations:");
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));
}

println!("plt:");
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));
}
}
}
}
println!();
}

Ok(())
}
Loading

0 comments on commit 1a81908

Please sign in to comment.