feat: search for symbols in exported but also elf
this allows finding non-exported symbols on non-stripped elfs. requires being able to read the executable that created this process. also added example code in C to target with this tool
This commit is contained in:
parent
28778ab2e1
commit
fab29c5423
3 changed files with 130 additions and 43 deletions
|
@ -21,4 +21,4 @@ retour = "0.1" # plain detour doesn't work on latest nightly? idk
|
|||
elf = "0.7.2"
|
||||
nix = "0.26.2"
|
||||
proc-maps = "0.3.0"
|
||||
dlopen = "0.1.8"
|
||||
libloading = "0.7.4"
|
||||
|
|
34
examples/victim.c
Normal file
34
examples/victim.c
Normal file
|
@ -0,0 +1,34 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int load_secret();
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int secret;
|
||||
Dl_info info;
|
||||
if (argc > 1 && dladdr(dlopen, &info)) {
|
||||
printf("> dlopen addr: %p\n", info.dli_saddr);
|
||||
}
|
||||
puts("> working...");
|
||||
srand(42);
|
||||
while (1) {
|
||||
printf("> generating secret ");
|
||||
fflush(stdout);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
usleep(200 * 1000);
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
}
|
||||
secret = load_secret();
|
||||
printf(" saved!\n");
|
||||
}
|
||||
}
|
||||
|
||||
int load_secret() {
|
||||
return rand();
|
||||
}
|
129
src/lib.rs
129
src/lib.rs
|
@ -1,58 +1,111 @@
|
|||
use std::{error::Error, ffi::c_int};
|
||||
use std::{error::Error, ffi::c_int, path::{Path, PathBuf}};
|
||||
|
||||
use dlopen::symbor::Library;
|
||||
use nix::libc::{socklen_t, sockaddr};
|
||||
use retour::static_detour;
|
||||
use elf::{ElfBytes, endian::AnyEndian, ParseError};
|
||||
use libloading::os::unix::{Library, Symbol};
|
||||
use proc_maps::get_process_maps;
|
||||
use retour::{static_detour, Function};
|
||||
|
||||
static_detour! {
|
||||
static SOCKET_HOOK : unsafe extern "C" fn(i32, i32, i32) -> i32;
|
||||
static CONNECT_HOOK : unsafe extern "C" fn(c_int, *const sockaddr, socklen_t) -> c_int;
|
||||
static LOAD_EXT_HOOK : unsafe extern "C" fn(c_int) -> c_int;
|
||||
static HOOK : unsafe extern "C" fn() -> c_int;
|
||||
}
|
||||
|
||||
// extern "C" {
|
||||
// fn load_ext() -> ();
|
||||
// }
|
||||
#[ctor::ctor] // our entrypoint is the library constructor, invoked by dlopen
|
||||
fn constructor() {
|
||||
std::thread::spawn(|| {
|
||||
eprint!(" -[infected]- ");
|
||||
|
||||
if let Err(e) = add_hooks() {
|
||||
eprintln!("[!] Could not add hooks : {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn add_hooks() -> Result<(), Box<dyn Error>> {
|
||||
let exec = Library::open_self()?;
|
||||
|
||||
let load_ext_sym = unsafe { exec.symbol::<unsafe extern "C" fn(c_int) -> c_int>("load_ext") };
|
||||
let ptr = find_symbol("load_secret")?;
|
||||
|
||||
unsafe {
|
||||
SOCKET_HOOK.initialize(nix::libc::socket, |dom, tp, proto| {
|
||||
eprintln!("caught socket({}, {}, {}) call", dom, tp, proto);
|
||||
SOCKET_HOOK.call(dom, tp, proto)
|
||||
HOOK.initialize(ptr, || {
|
||||
let secret = HOOK.call();
|
||||
eprint!(" ( ͡° ͜ʖ ͡°) its {} ", secret);
|
||||
secret
|
||||
})?;
|
||||
SOCKET_HOOK.enable()?;
|
||||
|
||||
CONNECT_HOOK.initialize(nix::libc::connect, |fd, info, len| {
|
||||
eprintln!("caught connect({}, ??, {}) call", fd, len);
|
||||
CONNECT_HOOK.call(fd, info, len)
|
||||
})?;
|
||||
CONNECT_HOOK.enable()?;
|
||||
|
||||
match load_ext_sym {
|
||||
Ok(sym) => {
|
||||
LOAD_EXT_HOOK.initialize(*sym, |x| { eprintln!("intercepted load_ext!"); x })?;
|
||||
LOAD_EXT_HOOK.enable()?;
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("[!] skipping load_ext hook : {}", e);
|
||||
},
|
||||
}
|
||||
HOOK.enable()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_symbol<T : Function>(name: &str) -> Result<T, Box<dyn Error>> {
|
||||
// try to find it among exported symbols
|
||||
let this = Library::this(); // TODO don't reopen it every time
|
||||
let sym : Result<Symbol<T>, libloading::Error> = unsafe { this.get(name.as_bytes()) };
|
||||
if let Ok(s) = sym {
|
||||
return Ok(*s);
|
||||
}
|
||||
|
||||
// try to read it from executable's elf
|
||||
if let Some(exec) = find_argv0() {
|
||||
let (base, path) = map_addr_path(&exec)?;
|
||||
let offset = offset_in_elf(&path, &name)?;
|
||||
let addr : *const () = (base + offset) as *const ();
|
||||
return Ok(unsafe { Function::from_ptr(addr) } );
|
||||
}
|
||||
|
||||
#[ctor::ctor]
|
||||
fn constructor() {
|
||||
println!("Infected!");
|
||||
Err(Box::new(not_found("could not find symbol in executable ELF, possibly stripped?")))
|
||||
}
|
||||
|
||||
if let Err(e) = add_hooks() {
|
||||
eprintln!("[!] Could not add hooks : {}", e);
|
||||
fn offset_in_elf(path: &Path, symbol: &str) -> Result<usize, ParseError> {
|
||||
let exec_data = std::fs::read(path)?;
|
||||
let headers = ElfBytes::<AnyEndian>::minimal_parse(&exec_data)?;
|
||||
let common = headers.find_common_data()?;
|
||||
|
||||
// first try with hash table
|
||||
if let Some(hash_table) = common.sysv_hash {
|
||||
if let Some(dynsyms) = common.dynsyms {
|
||||
if let Some(strtab) = common.dynsyms_strs {
|
||||
if let Some((_id, sym)) = hash_table.find(symbol.as_bytes(), &dynsyms, &strtab)? {
|
||||
return Ok(sym.st_value as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fall back to iterating symbols table
|
||||
if let Some(symtab) = common.symtab {
|
||||
if let Some(strs) = common.symtab_strs {
|
||||
for sym in symtab {
|
||||
let name = strs.get(sym.st_name as usize)?;
|
||||
if name == symbol {
|
||||
return Ok(sym.st_value as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(not_found("idk where to search :(").into())
|
||||
}
|
||||
|
||||
fn map_addr_path(name: &str) -> std::io::Result<(usize, PathBuf)> {
|
||||
let proc_maps = get_process_maps(std::process::id() as i32)?;
|
||||
|
||||
for map in proc_maps {
|
||||
// println!("map > 0x{:08X} {} [{:x}] - {} [{}]", map.start(), map.flags, map.offset, map.inode, _path_to_str(map.filename()));
|
||||
if map.is_exec() {
|
||||
if let Some(path) = map.filename() {
|
||||
if path.ends_with(name) {
|
||||
return Ok((map.start() - map.offset, map.filename().unwrap().to_path_buf()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(not_found("no process is mapped from a path ending with given name"))
|
||||
}
|
||||
|
||||
fn find_argv0() -> Option<String> { // could be a relative path, just get last member
|
||||
Some(std::env::args().next()?.split("/").last()?.into()) // TODO separator for windows?
|
||||
}
|
||||
|
||||
fn not_found(txt: &str) -> std::io::Error {
|
||||
std::io::Error::new(std::io::ErrorKind::NotFound, txt)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue