1use std::borrow::Cow;
2use thiserror::Error;
3
4#[cfg(target_pointer_width = "64")]
5pub mod elf64;
6#[cfg(target_pointer_width = "64")]
7use elf64 as elf;
8
9#[cfg(target_pointer_width = "32")]
10pub mod elf32;
11#[cfg(target_pointer_width = "32")]
12use elf32 as elf;
13
14#[derive(Debug, Error)]
16pub enum DynamicError {
17 #[error("Unknown type witnessed: `{0}`")]
19 TypeCast(#[from] elf::DynTypeError),
20
21 #[error("Given the prescence of `{0:#?}`, expected prescence of `{1:#?}`")]
23 DependentSection(DynamicSectionType, DynamicSectionType),
24
25 #[error("Failed to parse, required section missing `{0:#?}`")]
26 RequiredSection(DynamicSectionType),
27
28 #[error("No dynamic program header available")]
29 ProgramHeader,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
34#[allow(non_camel_case_types)]
35pub enum DynamicSectionType {
36 DT_NULL,
37 DT_PLTRELSZ,
38 DT_PLTGOT,
39 DT_PLTREL,
40
41 DT_STRTAB,
42 DT_SYMTAB,
43 DT_SYMENT,
44
45 DT_RELA,
46 DT_RELASZ,
47 DT_RELAENT,
48
49 DT_REL,
50 DT_RELSZ,
51 DT_RELENT,
52
53 DT_STRSZ,
54 DT_JMPREL,
55}
56
57pub struct DynamicRelocations<'a> {
59 inner: &'a [elf::DynRel],
60}
61
62impl DynamicRelocations<'_> {
63 pub fn read_at(&self, index: usize) -> Option<&elf::DynRel> {
65 self.inner.get(index)
66 }
67
68 pub fn entries(&self) -> &[elf::DynRel] {
70 self.inner
71 }
72}
73
74pub struct DynamicAddendRelocations<'a> {
76 inner: &'a [elf::DynRela],
77}
78
79impl DynamicAddendRelocations<'_> {
80 pub fn read_at(&self, index: usize) -> Option<&elf::DynRela> {
82 self.inner.get(index)
83 }
84
85 pub fn entries(&self) -> &[elf::DynRela] {
87 self.inner
88 }
89}
90
91pub struct DynamicSymbols<'a> {
93 inner: &'a elf::DynSym,
94}
95
96impl DynamicSymbols<'_> {
97 fn get(&self, index: usize) -> Option<&elf::DynSym> {
99 unsafe { (self.inner as *const elf::DynSym).add(index).as_ref() }
100 }
101
102 pub fn resolve_name<'b>(
104 &'b self,
105 index: usize,
106 string_table: &'b StringTable<'b>,
107 ) -> Option<Cow<str>> {
108 let entry = self.get(index)?;
109 string_table.read_at(entry.st_name as usize)
110 }
111}
112
113pub struct DynamicSection<'a> {
115 inner: &'a elf::DynEntry,
116}
117
118#[derive(Debug)]
119pub struct StringTable<'a> {
123 raw: &'a [libc::c_char],
124}
125
126impl<'a> StringTable<'a> {
127 pub fn read_at(&self, carrot: usize) -> Option<Cow<str>> {
129 match carrot >= self.raw.len() {
130 true => None,
131 false => unsafe { Some(std::ffi::CStr::from_ptr(&self.raw[carrot]).to_string_lossy()) },
132 }
133 }
134
135 pub fn total_size(&self) -> usize {
138 self.raw.len()
139 }
140}
141
142impl DynamicSection<'_> {
143 fn find_section(&self, tag: DynamicSectionType) -> Option<&elf::DynEntry> {
145 let mut current = Some(self.inner);
146 while let Some(inner) = current {
147 match DynamicSectionType::try_from(inner.d_tag) {
148 Ok(DynamicSectionType::DT_NULL) => return None,
149 Ok(this_tag) if this_tag == tag => return Some(inner),
150 Ok(_) => {
151 }
153 Err(_err) => {
154 }
156 }
157
158 current = unsafe { (inner as *const elf::DynEntry).offset(1).as_ref() };
159 }
160
161 None
162 }
163}
164
165pub struct ProgramHeader<'a> {
168 inner: &'a elf::ProgramHeader,
169}
170
171impl ProgramHeader<'_> {
172 pub fn header_type(&self) -> elf::Word {
174 self.inner.p_type
175 }
176
177 pub fn virtual_addr(&self) -> usize {
179 self.inner.p_vaddr as usize
180 }
181
182 pub fn memory_size(&self) -> usize {
184 self.inner.p_memsz as usize
185 }
186
187 pub fn file_size(&self) -> usize {
189 self.inner.p_filesz as usize
190 }
191
192 pub fn program_addr(&self) -> usize {
194 self.inner.p_paddr as usize
195 }
196
197 pub fn offset(&self) -> usize {
199 self.inner.p_offset as usize
200 }
201}
202
203pub enum RelocationTable<'a> {
205 WithAddend(DynamicAddendRelocations<'a>),
206 WithoutAddend(DynamicRelocations<'a>),
207}
208
209pub struct DynamicLibrary<'a> {
212 library: LoadedLibrary<'a>,
213 dyn_section: DynamicSection<'a>,
214 dyn_string_table: StringTable<'a>,
215
216 dyn_symbols: Option<DynamicSymbols<'a>>,
217 dyn_relocs: Option<DynamicRelocations<'a>>,
218 dyn_addend_relocs: Option<DynamicAddendRelocations<'a>>,
219 dyn_plt: Option<RelocationTable<'a>>,
220}
221
222fn extract_dyn_symbols<'a, 'b>(
224 lib: &'a LoadedLibrary<'a>,
225 dynamic_section: &'a DynamicSection<'a>,
226) -> std::result::Result<Option<DynamicSymbols<'b>>, DynamicError> {
227 let Some(dyn_symbol_table) = dynamic_section.find_section(DynamicSectionType::DT_SYMTAB) else {
229 return Ok(None);
230 };
231
232 let table_size = dynamic_section
235 .find_section(DynamicSectionType::DT_SYMENT)
236 .ok_or(DynamicError::DependentSection(
237 DynamicSectionType::DT_SYMTAB,
238 DynamicSectionType::DT_SYMENT,
239 ))?
240 .d_val_ptr as usize;
241 assert_eq!(table_size, std::mem::size_of::<elf::DynSym>());
242
243 let dyn_sym_ptr = match dyn_symbol_table.d_val_ptr as usize <= lib.addr() {
246 false => dyn_symbol_table.d_val_ptr as usize,
247 true => dyn_symbol_table.d_val_ptr as usize + lib.addr(),
248 } as *const elf::DynSym;
249
250 Ok(Some(DynamicSymbols {
251 inner: unsafe { dyn_sym_ptr.as_ref().unwrap() },
252 }))
253}
254
255fn extract_dyn_section<'a, 'b>(
257 lib: &'a LoadedLibrary<'a>,
258) -> std::result::Result<DynamicSection<'b>, DynamicError> {
259 let dynamic_header = lib
260 .program_headers()
261 .find(|p_h| p_h.header_type() == 0x02)
262 .ok_or(DynamicError::ProgramHeader)?;
263
264 let dynamic_sections = lib.addr() + dynamic_header.virtual_addr();
265 let dynamic_sections = dynamic_sections as *const elf::DynEntry;
266 Ok(DynamicSection {
267 inner: unsafe { dynamic_sections.as_ref().unwrap() },
268 })
269}
270
271fn extract_dyn_string_table<'a, 'b>(
273 lib: &'a LoadedLibrary<'a>,
274 dynamic_section: &'a DynamicSection<'a>,
275) -> std::result::Result<StringTable<'b>, DynamicError> {
276 let str_table_entry = dynamic_section
277 .find_section(DynamicSectionType::DT_STRTAB)
278 .ok_or(DynamicError::RequiredSection(DynamicSectionType::DT_STRTAB))?;
279 let table_size = dynamic_section
280 .find_section(DynamicSectionType::DT_STRSZ)
281 .ok_or(DynamicError::DependentSection(
282 DynamicSectionType::DT_STRTAB,
283 DynamicSectionType::DT_STRSZ,
284 ))?
285 .d_val_ptr as usize;
286
287 let str_table_ptr = match str_table_entry.d_val_ptr as usize <= lib.addr() {
290 false => str_table_entry.d_val_ptr as usize,
291 true => str_table_entry.d_val_ptr as usize + lib.addr(),
292 } as *const libc::c_char;
293
294 Ok(StringTable {
295 raw: unsafe { std::slice::from_raw_parts(str_table_ptr, table_size) },
296 })
297}
298
299fn extract_dyn_relocs<'a, 'b>(
301 lib: &'a LoadedLibrary<'a>,
302 dynamic_section: &'a DynamicSection<'a>,
303) -> std::result::Result<Option<DynamicRelocations<'b>>, DynamicError> {
304 let Some(dyn_rel_entry) = dynamic_section.find_section(DynamicSectionType::DT_REL) else {
305 return Ok(None);
306 };
307
308 let total_size = dynamic_section
309 .find_section(DynamicSectionType::DT_RELSZ)
310 .ok_or(DynamicError::DependentSection(
311 DynamicSectionType::DT_REL,
312 DynamicSectionType::DT_RELSZ,
313 ))?
314 .d_val_ptr as usize;
315 let entry_size = dynamic_section
316 .find_section(DynamicSectionType::DT_RELENT)
317 .ok_or(DynamicError::DependentSection(
318 DynamicSectionType::DT_REL,
319 DynamicSectionType::DT_RELENT,
320 ))?
321 .d_val_ptr as usize;
322
323 assert_eq!(entry_size, std::mem::size_of::<elf::DynRel>());
324
325 let entry_count = total_size / entry_size;
326 let dyn_rel_entry = match dyn_rel_entry.d_val_ptr as usize <= lib.addr() {
329 false => dyn_rel_entry.d_val_ptr as usize,
330 true => dyn_rel_entry.d_val_ptr as usize + lib.addr(),
331 } as *const elf::DynRel;
332
333 Ok(Some(DynamicRelocations {
334 inner: unsafe { std::slice::from_raw_parts(dyn_rel_entry, entry_count) },
335 }))
336}
337
338fn extract_dyn_addend_relocs<'a, 'b>(
340 lib: &'a LoadedLibrary<'a>,
341 dynamic_section: &'a DynamicSection<'a>,
342) -> std::result::Result<Option<DynamicAddendRelocations<'b>>, DynamicError> {
343 let Some(dyn_rel_entry) = dynamic_section.find_section(DynamicSectionType::DT_RELA) else {
344 return Ok(None);
345 };
346
347 let total_size = dynamic_section
348 .find_section(DynamicSectionType::DT_RELASZ)
349 .ok_or(DynamicError::DependentSection(
350 DynamicSectionType::DT_RELA,
351 DynamicSectionType::DT_RELASZ,
352 ))?
353 .d_val_ptr as usize;
354 let entry_size = dynamic_section
355 .find_section(DynamicSectionType::DT_RELAENT)
356 .ok_or(DynamicError::DependentSection(
357 DynamicSectionType::DT_RELA,
358 DynamicSectionType::DT_RELAENT,
359 ))?
360 .d_val_ptr as usize;
361
362 assert_eq!(entry_size, std::mem::size_of::<elf::DynRela>());
363
364 let entry_count = total_size / entry_size;
365 let dyn_rel_entry = match dyn_rel_entry.d_val_ptr as usize <= lib.addr() {
368 false => dyn_rel_entry.d_val_ptr as usize,
369 true => dyn_rel_entry.d_val_ptr as usize + lib.addr(),
370 } as *const elf::DynRela;
371
372 Ok(Some(DynamicAddendRelocations {
373 inner: unsafe { std::slice::from_raw_parts(dyn_rel_entry, entry_count) },
374 }))
375}
376
377fn extract_dyn_plt<'a, 'b>(
379 lib: &'a LoadedLibrary<'a>,
380 dynamic_section: &'a DynamicSection<'a>,
381) -> std::result::Result<Option<RelocationTable<'b>>, DynamicError> {
382 let Some(dyn_type) = dynamic_section.find_section(DynamicSectionType::DT_PLTREL) else {
385 return Ok(None);
386 };
387
388 let relocation_type = DynamicSectionType::try_from(dyn_type.d_val_ptr)?;
389
390 let dyn_plt_entry = dynamic_section
391 .find_section(DynamicSectionType::DT_JMPREL)
392 .ok_or(DynamicError::DependentSection(
393 DynamicSectionType::DT_PLTREL,
394 DynamicSectionType::DT_JMPREL,
395 ))?;
396 let total_size = dynamic_section
397 .find_section(DynamicSectionType::DT_PLTRELSZ)
398 .ok_or(DynamicError::DependentSection(
399 DynamicSectionType::DT_PLTREL,
400 DynamicSectionType::DT_PLTRELSZ,
401 ))?
402 .d_val_ptr as usize;
403
404 let entry_addr = match dyn_plt_entry.d_val_ptr as usize <= lib.addr() {
405 false => dyn_plt_entry.d_val_ptr as usize,
406 true => dyn_plt_entry.d_val_ptr as usize + lib.addr(),
407 };
408
409 Ok(match relocation_type {
410 DynamicSectionType::DT_REL => {
411 let entry_count = total_size / std::mem::size_of::<elf::DynRel>();
412 Some(RelocationTable::WithoutAddend(DynamicRelocations {
413 inner: unsafe {
414 std::slice::from_raw_parts(entry_addr as *const elf::DynRel, entry_count)
415 },
416 }))
417 }
418 DynamicSectionType::DT_RELA => {
419 let entry_count = total_size / std::mem::size_of::<elf::DynRela>();
420 Some(RelocationTable::WithAddend(DynamicAddendRelocations {
421 inner: unsafe {
422 std::slice::from_raw_parts(entry_addr as *const elf::DynRela, entry_count)
423 },
424 }))
425 }
426 _ => None,
427 })
428}
429
430impl<'a> DynamicLibrary<'a> {
431 pub fn initialize(lib: LoadedLibrary<'a>) -> std::result::Result<Self, DynamicError> {
435 let dyn_section = extract_dyn_section(&lib)?;
436 let dyn_string_table = extract_dyn_string_table(&lib, &dyn_section)?;
437 let dyn_symbols = extract_dyn_symbols(&lib, &dyn_section)?;
438 let dyn_relocs = extract_dyn_relocs(&lib, &dyn_section)?;
439 let dyn_addend_relocs = extract_dyn_addend_relocs(&lib, &dyn_section)?;
440 let dyn_plt = extract_dyn_plt(&lib, &dyn_section)?;
441
442 Ok(Self {
443 library: lib,
444 dyn_section,
445 dyn_string_table,
446 dyn_symbols,
447 dyn_relocs,
448 dyn_addend_relocs,
449 dyn_plt,
450 })
451 }
452
453 #[cfg(target_pointer_width = "32")]
457 pub fn try_find_function(&self, symbol_name: &str) -> Option<&'_ elf32::DynRel> {
458 let string_table = self.string_table();
459 let dyn_symbols = self.symbols()?;
460 if let Some(dyn_relas) = self.relocs() {
461 let dyn_relas = dyn_relas.entries().iter();
462 if let Some(symbol) = dyn_relas
463 .flat_map(|e| {
464 dyn_symbols
465 .resolve_name(e.symbol_index() as usize, string_table)
466 .map(|s| (e, s))
467 })
468 .filter(|(_, s)| s.eq(symbol_name))
469 .next()
470 .map(|(target_function, _)| target_function)
471 {
472 return Some(symbol);
473 }
474 }
475
476 if let Some(dyn_relas) = self.plt_rel() {
477 let dyn_relas = dyn_relas.entries().iter();
478 if let Some(symbol) = dyn_relas
479 .flat_map(|e| {
480 dyn_symbols
481 .resolve_name(e.symbol_index() as usize, string_table)
482 .map(|s| (e, s))
483 })
484 .filter(|(_, s)| s.eq(symbol_name))
485 .next()
486 .map(|(target_function, _)| target_function)
487 {
488 return Some(symbol);
489 }
490 }
491 None
492 }
493
494 #[cfg(target_pointer_width = "64")]
498 pub fn try_find_function(&self, symbol_name: &str) -> Option<&'_ elf64::DynRela> {
499 let string_table = self.string_table();
500 let symbols = self.symbols()?;
501 if let Some(dyn_relas) = self.addend_relocs() {
502 let dyn_relas = dyn_relas.entries().iter();
503 if let Some(symbol) = dyn_relas
504 .flat_map(|e| {
505 symbols
506 .resolve_name(e.symbol_index() as usize, string_table)
507 .map(|s| (e, s))
508 })
509 .find(|(_, s)| s.eq(symbol_name))
510 .map(|(target_function, _)| target_function)
511 {
512 return Some(symbol);
513 }
514 }
515
516 if let Some(dyn_relas) = self.plt_rela() {
517 let dyn_relas = dyn_relas.entries().iter();
518 if let Some(symbol) = dyn_relas
519 .flat_map(|e| {
520 symbols
521 .resolve_name(e.symbol_index() as usize, string_table)
522 .map(|s| (e, s))
523 })
524 .find(|(_, s)| s.eq(symbol_name))
525 .map(|(target_function, _)| target_function)
526 {
527 return Some(symbol);
528 }
529 }
530 None
531 }
532 pub fn plt_rel(&self) -> Option<&DynamicRelocations<'_>> {
535 match self.plt() {
536 Some(RelocationTable::WithoutAddend(relocs)) => Some(relocs),
537 _ => None,
538 }
539 }
540
541 pub fn plt_rela(&self) -> Option<&DynamicAddendRelocations<'_>> {
544 match self.plt() {
545 Some(RelocationTable::WithAddend(relocs)) => Some(relocs),
546 _ => None,
547 }
548 }
549 pub fn plt(&self) -> Option<&RelocationTable<'_>> {
552 self.dyn_plt.as_ref()
553 }
554
555 pub fn relocs(&self) -> Option<&DynamicRelocations<'_>> {
557 self.dyn_relocs.as_ref()
558 }
559
560 pub fn addend_relocs(&self) -> Option<&DynamicAddendRelocations<'_>> {
562 self.dyn_addend_relocs.as_ref()
563 }
564
565 pub fn symbols(&self) -> Option<&DynamicSymbols<'_>> {
567 self.dyn_symbols.as_ref()
568 }
569
570 pub fn dyn_section(&self) -> &DynamicSection<'_> {
572 &self.dyn_section
573 }
574
575 pub fn library(&self) -> &LoadedLibrary<'_> {
578 &self.library
579 }
580
581 pub fn string_table(&self) -> &StringTable<'_> {
583 &self.dyn_string_table
584 }
585}
586
587pub struct LoadedLibrary<'a> {
589 addr: usize,
590 name: Cow<'a, str>,
591 program_headers: &'a [elf::ProgramHeader],
592}
593
594impl LoadedLibrary<'_> {
595 pub fn name(&self) -> &str {
598 &self.name
599 }
600
601 pub fn addr(&self) -> usize {
603 self.addr
604 }
605
606 pub fn program_headers(&self) -> impl Iterator<Item = ProgramHeader<'_>> {
608 self.program_headers
609 .iter()
610 .map(|header| ProgramHeader { inner: header })
611 }
612
613 pub fn interpreter_header(&self) -> Option<ProgramHeader<'_>> {
615 self.program_headers().find(|p_h| p_h.header_type() == 0x03)
616 }
617
618 pub fn load_headers(&self) -> impl Iterator<Item = ProgramHeader<'_>> {
620 self.program_headers()
621 .filter(|p_h| p_h.header_type() == 0x01)
622 }
623}
624
625pub fn collect_modules<'a>() -> Vec<LoadedLibrary<'a>> {
627 let mut ret = Vec::new();
628
629 extern "C" fn push_object(objs: &mut Vec<LoadedLibrary>, dl_info: &libc::dl_phdr_info) {
631 let name = unsafe { std::ffi::CStr::from_ptr(dl_info.dlpi_name) }.to_string_lossy();
632 println!("{} {}", dl_info.dlpi_addr, dl_info.dlpi_phnum);
636
637 if dl_info.dlpi_phnum == 0 {
638 return;
639 }
640
641 let program_headers =
642 unsafe { std::slice::from_raw_parts(dl_info.dlpi_phdr, dl_info.dlpi_phnum as usize) };
643 objs.push(LoadedLibrary {
644 addr: dl_info.dlpi_addr as usize,
645 name,
646 program_headers,
647 });
648 }
649
650 unsafe extern "C" fn collect_objs(
652 info: *mut libc::dl_phdr_info,
653 _sz: usize,
654 data: *mut libc::c_void,
655 ) -> libc::c_int {
656 if let Some(info) = unsafe { info.as_ref() } {
657 push_object(&mut *(data as *mut Vec<LoadedLibrary>), info); };
659
660 0
661 }
662
663 let ret_void_p = &mut ret as *mut Vec<LoadedLibrary> as *mut libc::c_void;
664 unsafe { libc::dl_iterate_phdr(Some(collect_objs), ret_void_p) };
665
666 ret
667}