#![no_std]
extern crate alloc;
use alloc::borrow::Cow;
use alloc::vec::Vec;
use core::error::Error;
use core::ffi::c_void;
use core::fmt::Display;
use core::mem::size_of;
use core::result::Result;
#[cfg(target_pointer_width = "64")]
pub mod elf64;
#[cfg(target_pointer_width = "64")]
use elf64 as elf;
#[cfg(target_pointer_width = "32")]
pub mod elf32;
#[cfg(target_pointer_width = "32")]
use elf32 as elf;
#[derive(Debug)]
pub enum DynamicError {
TypeCast(elf::DynTypeError),
DependentSection(DynamicSectionType, DynamicSectionType),
RequiredSection(DynamicSectionType),
ProgramHeader,
}
impl Display for DynamicError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::TypeCast(e) => write!(f, "Unknown type witnessed: {e}"),
Self::DependentSection(dependent, depended) => write!(
f,
"Given the prescence of `{dependent:#?}`, expected prescence of `{depended:#?}`"
),
Self::RequiredSection(required) => write!(
f,
"Failed to parse, required section missing `{required:#?}`"
),
Self::ProgramHeader => write!(f, "No dynamic program header available"),
}
}
}
impl From<elf::DynTypeError> for DynamicError {
fn from(value: elf::DynTypeError) -> Self {
Self::TypeCast(value)
}
}
impl Error for DynamicError {}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[allow(non_camel_case_types)]
pub enum DynamicSectionType {
DT_NULL,
DT_PLTRELSZ,
DT_PLTGOT,
DT_PLTREL,
DT_STRTAB,
DT_SYMTAB,
DT_SYMENT,
DT_RELA,
DT_RELASZ,
DT_RELAENT,
DT_REL,
DT_RELSZ,
DT_RELENT,
DT_STRSZ,
DT_JMPREL,
}
pub struct DynamicRelocations<'a> {
inner: &'a [elf::DynRel],
}
impl DynamicRelocations<'_> {
pub fn read_at(&self, index: usize) -> Option<&elf::DynRel> {
self.inner.get(index)
}
pub fn entries(&self) -> &[elf::DynRel] {
self.inner
}
}
pub struct DynamicAddendRelocations<'a> {
inner: &'a [elf::DynRela],
}
impl DynamicAddendRelocations<'_> {
pub fn read_at(&self, index: usize) -> Option<&elf::DynRela> {
self.inner.get(index)
}
pub fn entries(&self) -> &[elf::DynRela] {
self.inner
}
}
pub struct DynamicSymbols<'a> {
inner: &'a elf::DynSym,
}
impl DynamicSymbols<'_> {
fn get(&self, index: usize) -> Option<&elf::DynSym> {
unsafe { (self.inner as *const elf::DynSym).add(index).as_ref() }
}
pub fn resolve_name<'b>(
&'b self,
index: usize,
string_table: &'b StringTable<'b>,
) -> Option<Cow<'b, str>> {
let entry = self.get(index)?;
string_table.read_at(entry.st_name as usize)
}
}
pub struct DynamicSection<'a> {
inner: &'a elf::DynEntry,
}
#[derive(Debug)]
pub struct StringTable<'a> {
raw: &'a [libc::c_char],
}
impl<'a> StringTable<'a> {
pub fn read_at(&'a self, carrot: usize) -> Option<Cow<'a, str>> {
match carrot >= self.raw.len() {
true => None,
false => unsafe {
Some(core::ffi::CStr::from_ptr(&self.raw[carrot]).to_string_lossy())
},
}
}
pub fn total_size(&self) -> usize {
self.raw.len()
}
}
impl DynamicSection<'_> {
fn find_section(&self, tag: DynamicSectionType) -> Option<&elf::DynEntry> {
let mut current = Some(self.inner);
while let Some(inner) = current {
match DynamicSectionType::try_from(inner.d_tag) {
Ok(DynamicSectionType::DT_NULL) => return None,
Ok(this_tag) if this_tag == tag => return Some(inner),
Ok(_) => {
}
Err(_err) => {
}
}
current = unsafe { (inner as *const elf::DynEntry).offset(1).as_ref() };
}
None
}
}
pub struct ProgramHeader<'a> {
inner: &'a elf::ProgramHeader,
}
impl ProgramHeader<'_> {
pub fn header_type(&self) -> elf::Word {
self.inner.p_type
}
pub fn virtual_addr(&self) -> usize {
self.inner.p_vaddr as usize
}
pub fn memory_size(&self) -> usize {
self.inner.p_memsz as usize
}
pub fn file_size(&self) -> usize {
self.inner.p_filesz as usize
}
pub fn program_addr(&self) -> usize {
self.inner.p_paddr as usize
}
pub fn offset(&self) -> usize {
self.inner.p_offset as usize
}
}
pub enum RelocationTable<'a> {
WithAddend(DynamicAddendRelocations<'a>),
WithoutAddend(DynamicRelocations<'a>),
}
pub struct DynamicLibrary<'a> {
library: LoadedLibrary<'a>,
dyn_section: DynamicSection<'a>,
dyn_string_table: StringTable<'a>,
dyn_symbols: Option<DynamicSymbols<'a>>,
dyn_relocs: Option<DynamicRelocations<'a>>,
dyn_addend_relocs: Option<DynamicAddendRelocations<'a>>,
dyn_plt: Option<RelocationTable<'a>>,
}
fn extract_dyn_symbols<'a, 'b>(
lib: &'a LoadedLibrary<'a>,
dynamic_section: &'a DynamicSection<'a>,
) -> Result<Option<DynamicSymbols<'b>>, DynamicError> {
let Some(dyn_symbol_table) = dynamic_section.find_section(DynamicSectionType::DT_SYMTAB) else {
return Ok(None);
};
let table_size = dynamic_section
.find_section(DynamicSectionType::DT_SYMENT)
.ok_or(DynamicError::DependentSection(
DynamicSectionType::DT_SYMTAB,
DynamicSectionType::DT_SYMENT,
))?
.d_val_ptr as usize;
assert_eq!(table_size, size_of::<elf::DynSym>());
let dyn_sym_ptr = match dyn_symbol_table.d_val_ptr as usize <= lib.addr() {
false => dyn_symbol_table.d_val_ptr as usize,
true => dyn_symbol_table.d_val_ptr as usize + lib.addr(),
} as *const elf::DynSym;
Ok(Some(DynamicSymbols {
inner: unsafe { dyn_sym_ptr.as_ref().unwrap() },
}))
}
fn extract_dyn_section<'a, 'b>(
lib: &'a LoadedLibrary<'a>,
) -> Result<DynamicSection<'b>, DynamicError> {
let dynamic_header = lib
.program_headers()
.find(|p_h| p_h.header_type() == 0x02)
.ok_or(DynamicError::ProgramHeader)?;
let dynamic_sections = lib.addr() + dynamic_header.virtual_addr();
let dynamic_sections = dynamic_sections as *const elf::DynEntry;
Ok(DynamicSection {
inner: unsafe { dynamic_sections.as_ref().unwrap() },
})
}
fn extract_dyn_string_table<'a, 'b>(
lib: &'a LoadedLibrary<'a>,
dynamic_section: &'a DynamicSection<'a>,
) -> Result<StringTable<'b>, DynamicError> {
let str_table_entry = dynamic_section
.find_section(DynamicSectionType::DT_STRTAB)
.ok_or(DynamicError::RequiredSection(DynamicSectionType::DT_STRTAB))?;
let table_size = dynamic_section
.find_section(DynamicSectionType::DT_STRSZ)
.ok_or(DynamicError::DependentSection(
DynamicSectionType::DT_STRTAB,
DynamicSectionType::DT_STRSZ,
))?
.d_val_ptr as usize;
let str_table_ptr = match str_table_entry.d_val_ptr as usize <= lib.addr() {
false => str_table_entry.d_val_ptr as usize,
true => str_table_entry.d_val_ptr as usize + lib.addr(),
} as *const libc::c_char;
Ok(StringTable {
raw: unsafe { core::slice::from_raw_parts(str_table_ptr, table_size) },
})
}
fn extract_dyn_relocs<'a, 'b>(
lib: &'a LoadedLibrary<'a>,
dynamic_section: &'a DynamicSection<'a>,
) -> Result<Option<DynamicRelocations<'b>>, DynamicError> {
let Some(dyn_rel_entry) = dynamic_section.find_section(DynamicSectionType::DT_REL) else {
return Ok(None);
};
let total_size = dynamic_section
.find_section(DynamicSectionType::DT_RELSZ)
.ok_or(DynamicError::DependentSection(
DynamicSectionType::DT_REL,
DynamicSectionType::DT_RELSZ,
))?
.d_val_ptr as usize;
let entry_size = dynamic_section
.find_section(DynamicSectionType::DT_RELENT)
.ok_or(DynamicError::DependentSection(
DynamicSectionType::DT_REL,
DynamicSectionType::DT_RELENT,
))?
.d_val_ptr as usize;
assert_eq!(entry_size, size_of::<elf::DynRel>());
let entry_count = total_size / entry_size;
let dyn_rel_entry = match dyn_rel_entry.d_val_ptr as usize <= lib.addr() {
false => dyn_rel_entry.d_val_ptr as usize,
true => dyn_rel_entry.d_val_ptr as usize + lib.addr(),
} as *const elf::DynRel;
Ok(Some(DynamicRelocations {
inner: unsafe { core::slice::from_raw_parts(dyn_rel_entry, entry_count) },
}))
}
fn extract_dyn_addend_relocs<'a, 'b>(
lib: &'a LoadedLibrary<'a>,
dynamic_section: &'a DynamicSection<'a>,
) -> Result<Option<DynamicAddendRelocations<'b>>, DynamicError> {
let Some(dyn_rel_entry) = dynamic_section.find_section(DynamicSectionType::DT_RELA) else {
return Ok(None);
};
let total_size = dynamic_section
.find_section(DynamicSectionType::DT_RELASZ)
.ok_or(DynamicError::DependentSection(
DynamicSectionType::DT_RELA,
DynamicSectionType::DT_RELASZ,
))?
.d_val_ptr as usize;
let entry_size = dynamic_section
.find_section(DynamicSectionType::DT_RELAENT)
.ok_or(DynamicError::DependentSection(
DynamicSectionType::DT_RELA,
DynamicSectionType::DT_RELAENT,
))?
.d_val_ptr as usize;
assert_eq!(entry_size, size_of::<elf::DynRela>());
let entry_count = total_size / entry_size;
let dyn_rel_entry = match dyn_rel_entry.d_val_ptr as usize <= lib.addr() {
false => dyn_rel_entry.d_val_ptr as usize,
true => dyn_rel_entry.d_val_ptr as usize + lib.addr(),
} as *const elf::DynRela;
Ok(Some(DynamicAddendRelocations {
inner: unsafe { core::slice::from_raw_parts(dyn_rel_entry, entry_count) },
}))
}
fn extract_dyn_plt<'a, 'b>(
lib: &'a LoadedLibrary<'a>,
dynamic_section: &'a DynamicSection<'a>,
) -> Result<Option<RelocationTable<'b>>, DynamicError> {
let Some(dyn_type) = dynamic_section.find_section(DynamicSectionType::DT_PLTREL) else {
return Ok(None);
};
let relocation_type = DynamicSectionType::try_from(dyn_type.d_val_ptr)?;
let dyn_plt_entry = dynamic_section
.find_section(DynamicSectionType::DT_JMPREL)
.ok_or(DynamicError::DependentSection(
DynamicSectionType::DT_PLTREL,
DynamicSectionType::DT_JMPREL,
))?;
let total_size = dynamic_section
.find_section(DynamicSectionType::DT_PLTRELSZ)
.ok_or(DynamicError::DependentSection(
DynamicSectionType::DT_PLTREL,
DynamicSectionType::DT_PLTRELSZ,
))?
.d_val_ptr as usize;
let entry_addr = match dyn_plt_entry.d_val_ptr as usize <= lib.addr() {
false => dyn_plt_entry.d_val_ptr as usize,
true => dyn_plt_entry.d_val_ptr as usize + lib.addr(),
};
Ok(match relocation_type {
DynamicSectionType::DT_REL => {
let entry_count = total_size / size_of::<elf::DynRel>();
Some(RelocationTable::WithoutAddend(DynamicRelocations {
inner: unsafe {
core::slice::from_raw_parts(entry_addr as *const elf::DynRel, entry_count)
},
}))
}
DynamicSectionType::DT_RELA => {
let entry_count = total_size / size_of::<elf::DynRela>();
Some(RelocationTable::WithAddend(DynamicAddendRelocations {
inner: unsafe {
core::slice::from_raw_parts(entry_addr as *const elf::DynRela, entry_count)
},
}))
}
_ => None,
})
}
impl<'a> DynamicLibrary<'a> {
pub fn initialize(lib: LoadedLibrary<'a>) -> Result<Self, DynamicError> {
let dyn_section = extract_dyn_section(&lib)?;
let dyn_string_table = extract_dyn_string_table(&lib, &dyn_section)?;
let dyn_symbols = extract_dyn_symbols(&lib, &dyn_section)?;
let dyn_relocs = extract_dyn_relocs(&lib, &dyn_section)?;
let dyn_addend_relocs = extract_dyn_addend_relocs(&lib, &dyn_section)?;
let dyn_plt = extract_dyn_plt(&lib, &dyn_section)?;
Ok(Self {
library: lib,
dyn_section,
dyn_string_table,
dyn_symbols,
dyn_relocs,
dyn_addend_relocs,
dyn_plt,
})
}
#[cfg(target_pointer_width = "32")]
pub fn try_find_function(&self, symbol_name: &str) -> Option<&'_ elf32::DynRel> {
let string_table = self.string_table();
let dyn_symbols = self.symbols()?;
if let Some(dyn_relas) = self.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(symbol_name))
.next()
.map(|(target_function, _)| target_function)
{
return Some(symbol);
}
}
if let Some(dyn_relas) = self.plt_rel() {
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(symbol_name))
.next()
.map(|(target_function, _)| target_function)
{
return Some(symbol);
}
}
None
}
#[cfg(target_pointer_width = "64")]
pub fn try_find_function(&self, symbol_name: &str) -> Option<&'_ elf64::DynRela> {
let string_table = self.string_table();
let symbols = self.symbols()?;
if let Some(dyn_relas) = self.addend_relocs() {
let dyn_relas = dyn_relas.entries().iter();
if let Some(symbol) = dyn_relas
.flat_map(|e| {
symbols
.resolve_name(e.symbol_index() as usize, string_table)
.map(|s| (e, s))
})
.find(|(_, s)| s.eq(symbol_name))
.map(|(target_function, _)| target_function)
{
return Some(symbol);
}
}
if let Some(dyn_relas) = self.plt_rela() {
let dyn_relas = dyn_relas.entries().iter();
if let Some(symbol) = dyn_relas
.flat_map(|e| {
symbols
.resolve_name(e.symbol_index() as usize, string_table)
.map(|s| (e, s))
})
.find(|(_, s)| s.eq(symbol_name))
.map(|(target_function, _)| target_function)
{
return Some(symbol);
}
}
None
}
pub fn plt_rel(&self) -> Option<&DynamicRelocations<'_>> {
match self.plt() {
Some(RelocationTable::WithoutAddend(relocs)) => Some(relocs),
_ => None,
}
}
pub fn plt_rela(&self) -> Option<&DynamicAddendRelocations<'_>> {
match self.plt() {
Some(RelocationTable::WithAddend(relocs)) => Some(relocs),
_ => None,
}
}
pub fn plt(&self) -> Option<&RelocationTable<'_>> {
self.dyn_plt.as_ref()
}
pub fn relocs(&self) -> Option<&DynamicRelocations<'_>> {
self.dyn_relocs.as_ref()
}
pub fn addend_relocs(&self) -> Option<&DynamicAddendRelocations<'_>> {
self.dyn_addend_relocs.as_ref()
}
pub fn symbols(&self) -> Option<&DynamicSymbols<'_>> {
self.dyn_symbols.as_ref()
}
pub fn dyn_section(&self) -> &DynamicSection<'_> {
&self.dyn_section
}
pub fn library(&self) -> &LoadedLibrary<'_> {
&self.library
}
pub fn base_addr(&self) -> usize {
self.library.addr
}
pub fn string_table(&self) -> &StringTable<'_> {
&self.dyn_string_table
}
}
pub struct LoadedLibrary<'a> {
addr: usize,
name: Cow<'a, str>,
program_headers: &'a [elf::ProgramHeader],
}
impl<'a> LoadedLibrary<'a> {
pub fn name(&self) -> &str {
&self.name
}
pub fn addr(&self) -> usize {
self.addr
}
pub fn program_headers(&self) -> impl Iterator<Item = ProgramHeader<'_>> {
self.program_headers
.iter()
.map(|header| ProgramHeader { inner: header })
}
pub fn interpreter_header(&self) -> Option<ProgramHeader<'_>> {
self.program_headers().find(|p_h| p_h.header_type() == 0x03)
}
pub fn load_headers(&self) -> impl Iterator<Item = ProgramHeader<'_>> {
self.program_headers()
.filter(|p_h| p_h.header_type() == 0x01)
}
}
#[derive(Debug)]
pub struct PatchError {
addr: usize,
page_size: usize,
prot: i32,
}
impl Display for PatchError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Error while patching {:X}, page_size = {:X}, protection flags: {}",
self.addr, self.page_size, self.prot,
)
}
}
impl Error for PatchError {}
pub fn patch(entry_addr: usize, func: usize) -> Result<usize, PatchError> {
let page_size = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as usize };
let page_aligned_addr = ((entry_addr as usize / page_size) * page_size) as *mut c_void;
unsafe {
let prot_res = libc::mprotect(
page_aligned_addr,
page_size,
libc::PROT_WRITE | libc::PROT_READ,
);
if prot_res != 0 {
return Err(PatchError {
addr: page_aligned_addr as usize,
page_size,
prot: libc::PROT_WRITE | libc::PROT_EXEC,
});
}
let previous_address = core::ptr::replace(entry_addr as *mut _, func as *mut c_void);
let prot_res = libc::mprotect(page_aligned_addr, page_size, libc::PROT_READ);
if prot_res != 0 {
return Err(PatchError {
addr: page_aligned_addr as usize,
page_size,
prot: libc::PROT_READ,
});
}
Ok(previous_address as usize)
}
}
pub fn collect_modules<'a>() -> Vec<LoadedLibrary<'a>> {
let mut ret = Vec::new();
extern "C" fn push_object(objs: &mut Vec<LoadedLibrary>, dl_info: &libc::dl_phdr_info) {
let name = unsafe { core::ffi::CStr::from_ptr(dl_info.dlpi_name) }.to_string_lossy();
if dl_info.dlpi_phnum == 0 {
return;
}
let program_headers =
unsafe { core::slice::from_raw_parts(dl_info.dlpi_phdr, dl_info.dlpi_phnum as usize) };
objs.push(LoadedLibrary {
addr: dl_info.dlpi_addr as usize,
name,
program_headers,
});
}
unsafe extern "C" fn collect_objs(
info: *mut libc::dl_phdr_info,
_sz: usize,
data: *mut libc::c_void,
) -> libc::c_int {
if let Some(info) = unsafe { info.as_ref() } {
push_object(&mut *(data as *mut Vec<LoadedLibrary>), info); };
0
}
let ret_void_p = &mut ret as *mut Vec<LoadedLibrary> as *mut libc::c_void;
unsafe { libc::dl_iterate_phdr(Some(collect_objs), ret_void_p) };
ret
}