use goblin::{
container::{Container, Ctx},
elf::{dynamic::{Dynamic, DT_RELA, DT_LOOS}, sym::Symtab, Elf, ProgramHeader, RelocSection},
strtab::Strtab,
};
use libloading::Library;
use nix::sys::mman::{mprotect, ProtFlags};
use regex::Regex;
use scroll::Endian;
use std::{ffi::c_void, fs::File, io::{BufRead, BufReader}};
pub struct GotHookLibrary<'a> {
path: &'a str,
start: usize,
end: usize,
file_in_memory: Box<&'a [u8]>,
elf: Elf<'a>,
}
impl<'a> GotHookLibrary<'a> {
pub fn new(path: &'a str, load: bool) -> Self {
println!("Path is {:?}", path);
let library = if load {
Some(unsafe { Library::new(path) })
} else {
None
};
println!("library: {:?}", library);
let (start, end) = mapping_for_library(path);
let file_in_memory = unsafe { std::slice::from_raw_parts(start as *const u8, end - start) };
let mut elf = Elf::lazy_parse(
Elf::parse_header(file_in_memory).expect("Failed to parse elf"),
)
.expect("Failed to parse elf lazily");
let ctx = Ctx {
le: Endian::Little,
container: Container::Big,
};
elf.program_headers = ProgramHeader::parse(
&file_in_memory,
elf.header.e_phoff as usize,
elf.header.e_phnum as usize,
ctx,
)
.expect("parse program headers");
for mut program_header in &mut elf.program_headers {
program_header.p_offset = program_header.p_vaddr;
}
elf.dynamic =
Dynamic::parse(&file_in_memory, &elf.program_headers, ctx)
.expect("parse dynamic section");
let info = &elf.dynamic.as_ref().unwrap().info;
let chain_count = unsafe {
if let Some(offset) = info.hash {
std::slice::from_raw_parts((start + offset as usize + 4) as *mut u32, 1)[0]
} else {
(info.pltrelsz / info.syment) as u32
}
};
elf.dynsyms = Symtab::parse(
&file_in_memory,
info.symtab,
chain_count as usize,
ctx,
)
.expect("parse dynsyms");
elf.dynstrtab =
Strtab::parse(&file_in_memory, info.strtab, info.strsz, b'\x00')
.expect("parse dynstrtab");
elf.pltrelocs = RelocSection::parse(
&file_in_memory,
info.jmprel,
info.pltrelsz,
info.pltrel == DT_RELA,
ctx,
)
.expect("parse pltrel");
Self {
path,
start,
end,
file_in_memory: Box::new(file_in_memory),
elf,
}
}
pub fn start(&self) -> usize {
self.start
}
pub fn end(&self) -> usize {
self.end
}
pub fn path(&self) -> &str {
self.path
}
pub unsafe fn hook_function(&self, name: &str, newfunc: *const c_void) -> bool {
let mut symindex: isize = -1;
for (i, symbol) in self.elf.dynsyms.iter().enumerate() {
if name == self.elf.dynstrtab.get(symbol.st_name).unwrap().unwrap() {
symindex = i as isize;
break;
}
}
if symindex == -1 {
println!("failed to find function {:?}", name);
return false;
}
let mut offset: isize = -1;
for reloc in self.elf.pltrelocs.iter() {
if reloc.r_sym == symindex as usize {
offset = reloc.r_offset as isize;
break;
}
}
if offset == -1 {
println!("failed to find relocation for {:?}", name);
return false;
}
let address = self.start + offset as usize;
let value = std::ptr::read(address as *const *const c_void);
println!(
"found {:?} at address {:x}, with value {:x}, replacing...",
name, address, value as usize
);
mprotect(
((address / 0x1000) * 0x1000) as *mut c_void,
0x1000,
ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
)
.expect("Failed to mprotect to read/write");
std::ptr::replace(address as *mut *const c_void, newfunc);
mprotect(
((address / 0x1000) * 0x1000) as *mut c_void,
0x1000,
ProtFlags::PROT_READ,
)
.expect("Failed to mprotect back to read-only");
let value = std::ptr::read(address as *const *const c_void);
println!(
"verified value set to {:x}, expected {:x}",
value as usize, newfunc as usize
);
value == newfunc
}
}
fn walk_self_maps(visitor: &mut dyn FnMut(usize, usize, String, String) -> bool) {
let re = Regex::new(r"^(?P<start>[0-9a-f]{8,16})-(?P<end>[0-9a-f]{8,16}) (?P<perm>[-rwxp]{4}) (?P<offset>[0-9a-f]{8}) [0-9a-f]+:[0-9a-f]+ [0-9]+\s+(?P<path>.*)$")
.unwrap();
let mapsfile = File::open("/proc/self/maps").expect("Unable to open /proc/self/maps");
for line in BufReader::new(mapsfile).lines() {
let line = line.unwrap();
if let Some(caps) = re.captures(&line) {
if visitor(
usize::from_str_radix(caps.name("start").unwrap().as_str(), 16).unwrap(),
usize::from_str_radix(caps.name("end").unwrap().as_str(), 16).unwrap(),
caps.name("perm").unwrap().as_str().to_string(),
caps.name("path").unwrap().as_str().to_string(),
) {
break;
};
}
}
}
fn mapping_for_library(libpath: &str) -> (usize, usize) {
let mut libstart = 0;
let mut libend = 0;
walk_self_maps(&mut |start, end, _permissions, path| {
if libpath == path {
if libstart == 0 {
libstart = start;
}
libend = end;
}
false
});
(libstart, libend)
}