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"
|
elf = "0.7.2"
|
||||||
nix = "0.26.2"
|
nix = "0.26.2"
|
||||||
proc-maps = "0.3.0"
|
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();
|
||||||
|
}
|
137
src/lib.rs
137
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 elf::{ElfBytes, endian::AnyEndian, ParseError};
|
||||||
use nix::libc::{socklen_t, sockaddr};
|
use libloading::os::unix::{Library, Symbol};
|
||||||
use retour::static_detour;
|
use proc_maps::get_process_maps;
|
||||||
|
use retour::{static_detour, Function};
|
||||||
|
|
||||||
static_detour! {
|
static_detour! {
|
||||||
static SOCKET_HOOK : unsafe extern "C" fn(i32, i32, i32) -> i32;
|
static HOOK : unsafe extern "C" fn() -> c_int;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// extern "C" {
|
#[ctor::ctor] // our entrypoint is the library constructor, invoked by dlopen
|
||||||
// fn load_ext() -> ();
|
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>> {
|
fn add_hooks() -> Result<(), Box<dyn Error>> {
|
||||||
let exec = Library::open_self()?;
|
let ptr = find_symbol("load_secret")?;
|
||||||
|
|
||||||
let load_ext_sym = unsafe { exec.symbol::<unsafe extern "C" fn(c_int) -> c_int>("load_ext") };
|
unsafe {
|
||||||
|
HOOK.initialize(ptr, || {
|
||||||
unsafe {
|
let secret = HOOK.call();
|
||||||
SOCKET_HOOK.initialize(nix::libc::socket, |dom, tp, proto| {
|
eprint!(" ( ͡° ͜ʖ ͡°) its {} ", secret);
|
||||||
eprintln!("caught socket({}, {}, {}) call", dom, tp, proto);
|
secret
|
||||||
SOCKET_HOOK.call(dom, tp, proto)
|
|
||||||
})?;
|
})?;
|
||||||
SOCKET_HOOK.enable()?;
|
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);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_symbol<T : Function>(name: &str) -> Result<T, Box<dyn Error>> {
|
||||||
|
// try to find it among exported symbols
|
||||||
#[ctor::ctor]
|
let this = Library::this(); // TODO don't reopen it every time
|
||||||
fn constructor() {
|
let sym : Result<Symbol<T>, libloading::Error> = unsafe { this.get(name.as_bytes()) };
|
||||||
println!("Infected!");
|
if let Ok(s) = sym {
|
||||||
|
return Ok(*s);
|
||||||
if let Err(e) = add_hooks() {
|
|
||||||
eprintln!("[!] Could not add hooks : {}", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) } );
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Box::new(not_found("could not find symbol in executable ELF, possibly stripped?")))
|
||||||
|
}
|
||||||
|
|
||||||
|
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