1#![no_std]
2extern crate alloc;
3use alloc::borrow::Cow;
4use alloc::vec::Vec;
5use core::error::Error;
6use core::ffi::c_void;
7use core::fmt::Display;
8use core::mem::size_of;
9use core::result::Result;
10#[cfg(target_pointer_width = "64")]
11pub mod elf64;
12#[cfg(target_pointer_width = "64")]
13use elf64 as elf;
14#[cfg(target_pointer_width = "32")]
15pub mod elf32;
16#[cfg(target_pointer_width = "32")]
17use elf32 as elf;
18
19#[derive(Debug)]
21pub enum DynamicError {
22 TypeCast(elf::DynTypeError),
23 DependentSection(DynamicSectionType, DynamicSectionType),
24 RequiredSection(DynamicSectionType),
25 ProgramHeader,
26}
27
28impl Display for DynamicError {
29 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
30 match self {
31 Self::TypeCast(e) => write!(f, "Unknown type witnessed: {e}"),
32 Self::DependentSection(dependent, depended) => write!(
33 f,
34 "Given the prescence of `{dependent:#?}`, expected prescence of `{depended:#?}`"
35 ),
36 Self::RequiredSection(required) => write!(
37 f,
38 "Failed to parse, required section missing `{required:#?}`"
39 ),
40 Self::ProgramHeader => write!(f, "No dynamic program header available"),
41 }
42 }
43}
44
45impl From<elf::DynTypeError> for DynamicError {
46 fn from(value: elf::DynTypeError) -> Self {
47 Self::TypeCast(value)
48 }
49}
50
51impl Error for DynamicError {}
52
53#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
55#[allow(non_camel_case_types)]
56pub enum DynamicSectionType {
57 DT_NULL,
58 DT_PLTRELSZ,
59 DT_PLTGOT,
60 DT_PLTREL,
61
62 DT_STRTAB,
63 DT_SYMTAB,
64 DT_SYMENT,
65
66 DT_RELA,
67 DT_RELASZ,
68 DT_RELAENT,
69
70 DT_REL,
71 DT_RELSZ,
72 DT_RELENT,
73
74 DT_STRSZ,
75 DT_JMPREL,
76}
77
78pub struct DynamicRelocations<'a> {
80 inner: &'a [elf::DynRel],
81}
82
83impl DynamicRelocations<'_> {
84 pub fn read_at(&self, index: usize) -> Option<&elf::DynRel> {
86 self.inner.get(index)
87 }
88
89 pub fn entries(&self) -> &[elf::DynRel] {
91 self.inner
92 }
93}
94
95pub struct DynamicAddendRelocations<'a> {
97 inner: &'a [elf::DynRela],
98}
99
100impl DynamicAddendRelocations<'_> {
101 pub fn read_at(&self, index: usize) -> Option<&elf::DynRela> {
103 self.inner.get(index)
104 }
105
106 pub fn entries(&self) -> &[elf::DynRela] {
108 self.inner
109 }
110}
111
112pub struct DynamicSymbols<'a> {
114 inner: &'a elf::DynSym,
115}
116
117impl DynamicSymbols<'_> {
118 fn get(&self, index: usize) -> Option<&elf::DynSym> {
120 unsafe { (self.inner as *const elf::DynSym).add(index).as_ref() }
121 }
122
123 pub fn resolve_name<'b>(
125 &'b self,
126 index: usize,
127 string_table: &'b StringTable<'b>,
128 ) -> Option<Cow<'b, str>> {
129 let entry = self.get(index)?;
130 string_table.read_at(entry.st_name as usize)
131 }
132}
133
134pub struct DynamicSection<'a> {
136 inner: &'a elf::DynEntry,
137}
138
139#[derive(Debug)]
140pub struct StringTable<'a> {
144 raw: &'a [libc::c_char],
145}
146
147impl<'a> StringTable<'a> {
148 pub fn read_at(&'a self, carrot: usize) -> Option<Cow<'a, str>> {
150 match carrot >= self.raw.len() {
151 true => None,
152 false => unsafe {
153 Some(core::ffi::CStr::from_ptr(&self.raw[carrot]).to_string_lossy())
154 },
155 }
156 }
157
158 pub fn total_size(&self) -> usize {
161 self.raw.len()
162 }
163}
164
165impl DynamicSection<'_> {
166 fn find_section(&self, tag: DynamicSectionType) -> Option<&elf::DynEntry> {
168 let mut current = Some(self.inner);
169 while let Some(inner) = current {
170 match DynamicSectionType::try_from(inner.d_tag) {
171 Ok(DynamicSectionType::DT_NULL) => return None,
172 Ok(this_tag) if this_tag == tag => return Some(inner),
173 Ok(_) => {
174 }
176 Err(_err) => {
177 }
179 }
180
181 current = unsafe { (inner as *const elf::DynEntry).offset(1).as_ref() };
182 }
183
184 None
185 }
186}
187
188pub struct ProgramHeader<'a> {
191 inner: &'a elf::ProgramHeader,
192}
193
194impl ProgramHeader<'_> {
195 pub fn header_type(&self) -> elf::Word {
197 self.inner.p_type
198 }
199
200 pub fn virtual_addr(&self) -> usize {
202 self.inner.p_vaddr as usize
203 }
204
205 pub fn memory_size(&self) -> usize {
207 self.inner.p_memsz as usize
208 }
209
210 pub fn file_size(&self) -> usize {
212 self.inner.p_filesz as usize
213 }
214
215 pub fn program_addr(&self) -> usize {
217 self.inner.p_paddr as usize
218 }
219
220 pub fn offset(&self) -> usize {
222 self.inner.p_offset as usize
223 }
224}
225
226pub enum RelocationTable<'a> {
228 WithAddend(DynamicAddendRelocations<'a>),
229 WithoutAddend(DynamicRelocations<'a>),
230}
231
232pub struct DynamicLibrary<'a> {
235 library: LoadedLibrary<'a>,
236 dyn_section: DynamicSection<'a>,
237 dyn_string_table: StringTable<'a>,
238
239 dyn_symbols: Option<DynamicSymbols<'a>>,
240 dyn_relocs: Option<DynamicRelocations<'a>>,
241 dyn_addend_relocs: Option<DynamicAddendRelocations<'a>>,
242 dyn_plt: Option<RelocationTable<'a>>,
243}
244
245fn extract_dyn_symbols<'a, 'b>(
247 lib: &'a LoadedLibrary<'a>,
248 dynamic_section: &'a DynamicSection<'a>,
249) -> Result<Option<DynamicSymbols<'b>>, DynamicError> {
250 let Some(dyn_symbol_table) = dynamic_section.find_section(DynamicSectionType::DT_SYMTAB) else {
252 return Ok(None);
253 };
254
255 let table_size = dynamic_section
258 .find_section(DynamicSectionType::DT_SYMENT)
259 .ok_or(DynamicError::DependentSection(
260 DynamicSectionType::DT_SYMTAB,
261 DynamicSectionType::DT_SYMENT,
262 ))?
263 .d_val_ptr as usize;
264 assert_eq!(table_size, size_of::<elf::DynSym>());
265
266 let dyn_sym_ptr = match dyn_symbol_table.d_val_ptr as usize <= lib.addr() {
269 false => dyn_symbol_table.d_val_ptr as usize,
270 true => dyn_symbol_table.d_val_ptr as usize + lib.addr(),
271 } as *const elf::DynSym;
272
273 Ok(Some(DynamicSymbols {
274 inner: unsafe { dyn_sym_ptr.as_ref().unwrap() },
275 }))
276}
277
278fn extract_dyn_section<'a, 'b>(
280 lib: &'a LoadedLibrary<'a>,
281) -> Result<DynamicSection<'b>, DynamicError> {
282 let dynamic_header = lib
283 .program_headers()
284 .find(|p_h| p_h.header_type() == 0x02)
285 .ok_or(DynamicError::ProgramHeader)?;
286
287 let dynamic_sections = lib.addr() + dynamic_header.virtual_addr();
288 let dynamic_sections = dynamic_sections as *const elf::DynEntry;
289 Ok(DynamicSection {
290 inner: unsafe { dynamic_sections.as_ref().unwrap() },
291 })
292}
293
294fn extract_dyn_string_table<'a, 'b>(
296 lib: &'a LoadedLibrary<'a>,
297 dynamic_section: &'a DynamicSection<'a>,
298) -> Result<StringTable<'b>, DynamicError> {
299 let str_table_entry = dynamic_section
300 .find_section(DynamicSectionType::DT_STRTAB)
301 .ok_or(DynamicError::RequiredSection(DynamicSectionType::DT_STRTAB))?;
302 let table_size = dynamic_section
303 .find_section(DynamicSectionType::DT_STRSZ)
304 .ok_or(DynamicError::DependentSection(
305 DynamicSectionType::DT_STRTAB,
306 DynamicSectionType::DT_STRSZ,
307 ))?
308 .d_val_ptr as usize;
309
310 let str_table_ptr = match str_table_entry.d_val_ptr as usize <= lib.addr() {
313 false => str_table_entry.d_val_ptr as usize,
314 true => str_table_entry.d_val_ptr as usize + lib.addr(),
315 } as *const libc::c_char;
316
317 Ok(StringTable {
318 raw: unsafe { core::slice::from_raw_parts(str_table_ptr, table_size) },
319 })
320}
321
322fn extract_dyn_relocs<'a, 'b>(
324 lib: &'a LoadedLibrary<'a>,
325 dynamic_section: &'a DynamicSection<'a>,
326) -> Result<Option<DynamicRelocations<'b>>, DynamicError> {
327 let Some(dyn_rel_entry) = dynamic_section.find_section(DynamicSectionType::DT_REL) else {
328 return Ok(None);
329 };
330
331 let total_size = dynamic_section
332 .find_section(DynamicSectionType::DT_RELSZ)
333 .ok_or(DynamicError::DependentSection(
334 DynamicSectionType::DT_REL,
335 DynamicSectionType::DT_RELSZ,
336 ))?
337 .d_val_ptr as usize;
338 let entry_size = dynamic_section
339 .find_section(DynamicSectionType::DT_RELENT)
340 .ok_or(DynamicError::DependentSection(
341 DynamicSectionType::DT_REL,
342 DynamicSectionType::DT_RELENT,
343 ))?
344 .d_val_ptr as usize;
345
346 assert_eq!(entry_size, size_of::<elf::DynRel>());
347
348 let entry_count = total_size / entry_size;
349 let dyn_rel_entry = match dyn_rel_entry.d_val_ptr as usize <= lib.addr() {
352 false => dyn_rel_entry.d_val_ptr as usize,
353 true => dyn_rel_entry.d_val_ptr as usize + lib.addr(),
354 } as *const elf::DynRel;
355
356 Ok(Some(DynamicRelocations {
357 inner: unsafe { core::slice::from_raw_parts(dyn_rel_entry, entry_count) },
358 }))
359}
360
361fn extract_dyn_addend_relocs<'a, 'b>(
363 lib: &'a LoadedLibrary<'a>,
364 dynamic_section: &'a DynamicSection<'a>,
365) -> Result<Option<DynamicAddendRelocations<'b>>, DynamicError> {
366 let Some(dyn_rel_entry) = dynamic_section.find_section(DynamicSectionType::DT_RELA) else {
367 return Ok(None);
368 };
369
370 let total_size = dynamic_section
371 .find_section(DynamicSectionType::DT_RELASZ)
372 .ok_or(DynamicError::DependentSection(
373 DynamicSectionType::DT_RELA,
374 DynamicSectionType::DT_RELASZ,
375 ))?
376 .d_val_ptr as usize;
377 let entry_size = dynamic_section
378 .find_section(DynamicSectionType::DT_RELAENT)
379 .ok_or(DynamicError::DependentSection(
380 DynamicSectionType::DT_RELA,
381 DynamicSectionType::DT_RELAENT,
382 ))?
383 .d_val_ptr as usize;
384
385 assert_eq!(entry_size, size_of::<elf::DynRela>());
386
387 let entry_count = total_size / entry_size;
388 let dyn_rel_entry = match dyn_rel_entry.d_val_ptr as usize <= lib.addr() {
391 false => dyn_rel_entry.d_val_ptr as usize,
392 true => dyn_rel_entry.d_val_ptr as usize + lib.addr(),
393 } as *const elf::DynRela;
394
395 Ok(Some(DynamicAddendRelocations {
396 inner: unsafe { core::slice::from_raw_parts(dyn_rel_entry, entry_count) },
397 }))
398}
399
400fn extract_dyn_plt<'a, 'b>(
402 lib: &'a LoadedLibrary<'a>,
403 dynamic_section: &'a DynamicSection<'a>,
404) -> Result<Option<RelocationTable<'b>>, DynamicError> {
405 let Some(dyn_type) = dynamic_section.find_section(DynamicSectionType::DT_PLTREL) else {
408 return Ok(None);
409 };
410
411 let relocation_type = DynamicSectionType::try_from(dyn_type.d_val_ptr)?;
412
413 let dyn_plt_entry = dynamic_section
414 .find_section(DynamicSectionType::DT_JMPREL)
415 .ok_or(DynamicError::DependentSection(
416 DynamicSectionType::DT_PLTREL,
417 DynamicSectionType::DT_JMPREL,
418 ))?;
419 let total_size = dynamic_section
420 .find_section(DynamicSectionType::DT_PLTRELSZ)
421 .ok_or(DynamicError::DependentSection(
422 DynamicSectionType::DT_PLTREL,
423 DynamicSectionType::DT_PLTRELSZ,
424 ))?
425 .d_val_ptr as usize;
426
427 let entry_addr = match dyn_plt_entry.d_val_ptr as usize <= lib.addr() {
428 false => dyn_plt_entry.d_val_ptr as usize,
429 true => dyn_plt_entry.d_val_ptr as usize + lib.addr(),
430 };
431
432 Ok(match relocation_type {
433 DynamicSectionType::DT_REL => {
434 let entry_count = total_size / size_of::<elf::DynRel>();
435 Some(RelocationTable::WithoutAddend(DynamicRelocations {
436 inner: unsafe {
437 core::slice::from_raw_parts(entry_addr as *const elf::DynRel, entry_count)
438 },
439 }))
440 }
441 DynamicSectionType::DT_RELA => {
442 let entry_count = total_size / size_of::<elf::DynRela>();
443 Some(RelocationTable::WithAddend(DynamicAddendRelocations {
444 inner: unsafe {
445 core::slice::from_raw_parts(entry_addr as *const elf::DynRela, entry_count)
446 },
447 }))
448 }
449 _ => None,
450 })
451}
452
453impl<'a> DynamicLibrary<'a> {
454 pub fn initialize(lib: LoadedLibrary<'a>) -> Result<Self, DynamicError> {
458 let dyn_section = extract_dyn_section(&lib)?;
459 let dyn_string_table = extract_dyn_string_table(&lib, &dyn_section)?;
460 let dyn_symbols = extract_dyn_symbols(&lib, &dyn_section)?;
461 let dyn_relocs = extract_dyn_relocs(&lib, &dyn_section)?;
462 let dyn_addend_relocs = extract_dyn_addend_relocs(&lib, &dyn_section)?;
463 let dyn_plt = extract_dyn_plt(&lib, &dyn_section)?;
464
465 Ok(Self {
466 library: lib,
467 dyn_section,
468 dyn_string_table,
469 dyn_symbols,
470 dyn_relocs,
471 dyn_addend_relocs,
472 dyn_plt,
473 })
474 }
475
476 #[cfg(target_pointer_width = "32")]
480 pub fn try_find_function(&self, symbol_name: &str) -> Option<&'_ elf32::DynRel> {
481 let string_table = self.string_table();
482 let dyn_symbols = self.symbols()?;
483 if let Some(dyn_relas) = self.relocs() {
484 let dyn_relas = dyn_relas.entries().iter();
485 if let Some(symbol) = dyn_relas
486 .flat_map(|e| {
487 dyn_symbols
488 .resolve_name(e.symbol_index() as usize, string_table)
489 .map(|s| (e, s))
490 })
491 .filter(|(_, s)| s.eq(symbol_name))
492 .next()
493 .map(|(target_function, _)| target_function)
494 {
495 return Some(symbol);
496 }
497 }
498
499 if let Some(dyn_relas) = self.plt_rel() {
500 let dyn_relas = dyn_relas.entries().iter();
501 if let Some(symbol) = dyn_relas
502 .flat_map(|e| {
503 dyn_symbols
504 .resolve_name(e.symbol_index() as usize, string_table)
505 .map(|s| (e, s))
506 })
507 .filter(|(_, s)| s.eq(symbol_name))
508 .next()
509 .map(|(target_function, _)| target_function)
510 {
511 return Some(symbol);
512 }
513 }
514 None
515 }
516
517 #[cfg(target_pointer_width = "64")]
521 pub fn try_find_function(&self, symbol_name: &str) -> Option<&'_ elf64::DynRela> {
522 let string_table = self.string_table();
523 let symbols = self.symbols()?;
524 if let Some(dyn_relas) = self.addend_relocs() {
525 let dyn_relas = dyn_relas.entries().iter();
526 if let Some(symbol) = dyn_relas
527 .flat_map(|e| {
528 symbols
529 .resolve_name(e.symbol_index() as usize, string_table)
530 .map(|s| (e, s))
531 })
532 .find(|(_, s)| s.eq(symbol_name))
533 .map(|(target_function, _)| target_function)
534 {
535 return Some(symbol);
536 }
537 }
538
539 if let Some(dyn_relas) = self.plt_rela() {
540 let dyn_relas = dyn_relas.entries().iter();
541 if let Some(symbol) = dyn_relas
542 .flat_map(|e| {
543 symbols
544 .resolve_name(e.symbol_index() as usize, string_table)
545 .map(|s| (e, s))
546 })
547 .find(|(_, s)| s.eq(symbol_name))
548 .map(|(target_function, _)| target_function)
549 {
550 return Some(symbol);
551 }
552 }
553 None
554 }
555 pub fn plt_rel(&self) -> Option<&DynamicRelocations<'_>> {
558 match self.plt() {
559 Some(RelocationTable::WithoutAddend(relocs)) => Some(relocs),
560 _ => None,
561 }
562 }
563
564 pub fn plt_rela(&self) -> Option<&DynamicAddendRelocations<'_>> {
567 match self.plt() {
568 Some(RelocationTable::WithAddend(relocs)) => Some(relocs),
569 _ => None,
570 }
571 }
572 pub fn plt(&self) -> Option<&RelocationTable<'_>> {
575 self.dyn_plt.as_ref()
576 }
577
578 pub fn relocs(&self) -> Option<&DynamicRelocations<'_>> {
580 self.dyn_relocs.as_ref()
581 }
582
583 pub fn addend_relocs(&self) -> Option<&DynamicAddendRelocations<'_>> {
585 self.dyn_addend_relocs.as_ref()
586 }
587
588 pub fn symbols(&self) -> Option<&DynamicSymbols<'_>> {
590 self.dyn_symbols.as_ref()
591 }
592
593 pub fn dyn_section(&self) -> &DynamicSection<'_> {
595 &self.dyn_section
596 }
597
598 pub fn library(&self) -> &LoadedLibrary<'_> {
601 &self.library
602 }
603
604 pub fn base_addr(&self) -> usize {
607 self.library.addr
608 }
609
610 pub fn string_table(&self) -> &StringTable<'_> {
612 &self.dyn_string_table
613 }
614}
615
616pub struct LoadedLibrary<'a> {
618 addr: usize,
619 name: Cow<'a, str>,
620 program_headers: &'a [elf::ProgramHeader],
621}
622
623impl<'a> LoadedLibrary<'a> {
624 pub fn name(&self) -> &str {
627 &self.name
628 }
629
630 pub fn addr(&self) -> usize {
632 self.addr
633 }
634
635 pub fn program_headers(&self) -> impl Iterator<Item = ProgramHeader<'_>> {
637 self.program_headers
638 .iter()
639 .map(|header| ProgramHeader { inner: header })
640 }
641
642 pub fn interpreter_header(&self) -> Option<ProgramHeader<'_>> {
644 self.program_headers().find(|p_h| p_h.header_type() == 0x03)
645 }
646
647 pub fn load_headers(&self) -> impl Iterator<Item = ProgramHeader<'_>> {
649 self.program_headers()
650 .filter(|p_h| p_h.header_type() == 0x01)
651 }
652}
653
654#[derive(Debug)]
655pub struct PatchError {
656 addr: usize,
657 page_size: usize,
658 prot: i32,
659}
660
661impl Display for PatchError {
662 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
663 write!(
664 f,
665 "Error while patching {:X}, page_size = {:X}, protection flags: {}",
666 self.addr, self.page_size, self.prot,
667 )
668 }
669}
670
671impl Error for PatchError {}
672
673pub fn patch(entry_addr: usize, func: usize) -> Result<usize, PatchError> {
678 let page_size = unsafe { libc::sysconf(libc::_SC_PAGE_SIZE) as usize };
679 let page_aligned_addr = ((entry_addr as usize / page_size) * page_size) as *mut c_void;
680
681 unsafe {
682 let prot_res = libc::mprotect(
684 page_aligned_addr,
685 page_size,
686 libc::PROT_WRITE | libc::PROT_READ,
687 );
688 if prot_res != 0 {
689 return Err(PatchError {
690 addr: page_aligned_addr as usize,
691 page_size,
692 prot: libc::PROT_WRITE | libc::PROT_EXEC,
693 });
694 }
695
696 let previous_address = core::ptr::replace(entry_addr as *mut _, func as *mut c_void);
698
699 let prot_res = libc::mprotect(page_aligned_addr, page_size, libc::PROT_READ);
701 if prot_res != 0 {
702 return Err(PatchError {
703 addr: page_aligned_addr as usize,
704 page_size,
705 prot: libc::PROT_READ,
706 });
707 }
708
709 Ok(previous_address as usize)
710 }
711}
712
713pub fn collect_modules<'a>() -> Vec<LoadedLibrary<'a>> {
715 let mut ret = Vec::new();
716
717 extern "C" fn push_object(objs: &mut Vec<LoadedLibrary>, dl_info: &libc::dl_phdr_info) {
719 let name = unsafe { core::ffi::CStr::from_ptr(dl_info.dlpi_name) }.to_string_lossy();
720 if dl_info.dlpi_phnum == 0 {
725 return;
726 }
727
728 let program_headers =
729 unsafe { core::slice::from_raw_parts(dl_info.dlpi_phdr, dl_info.dlpi_phnum as usize) };
730 objs.push(LoadedLibrary {
731 addr: dl_info.dlpi_addr as usize,
732 name,
733 program_headers,
734 });
735 }
736
737 unsafe extern "C" fn collect_objs(
739 info: *mut libc::dl_phdr_info,
740 _sz: usize,
741 data: *mut libc::c_void,
742 ) -> libc::c_int {
743 if let Some(info) = unsafe { info.as_ref() } {
744 push_object(&mut *(data as *mut Vec<LoadedLibrary>), info); };
746
747 0
748 }
749
750 let ret_void_p = &mut ret as *mut Vec<LoadedLibrary> as *mut libc::c_void;
751 unsafe { libc::dl_iterate_phdr(Some(collect_objs), ret_void_p) };
752
753 ret
754}