Skip to main content

plt_rs/
lib.rs

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/// Errors related to dynamic libraries
20#[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/// Section type enumeration
54#[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
78/// Container of Dynamic Relocations
79pub struct DynamicRelocations<'a> {
80    inner: &'a [elf::DynRel],
81}
82
83impl DynamicRelocations<'_> {
84    /// Extract string from table starting at carrot position
85    pub fn read_at(&self, index: usize) -> Option<&elf::DynRel> {
86        self.inner.get(index)
87    }
88
89    /// Dynamic relocations internal slice
90    pub fn entries(&self) -> &[elf::DynRel] {
91        self.inner
92    }
93}
94
95/// Container of Dynamic Addend Relocations
96pub struct DynamicAddendRelocations<'a> {
97    inner: &'a [elf::DynRela],
98}
99
100impl DynamicAddendRelocations<'_> {
101    /// Extract string from table starting at carrot position
102    pub fn read_at(&self, index: usize) -> Option<&elf::DynRela> {
103        self.inner.get(index)
104    }
105
106    /// Dynamic addend relocations internal slice
107    pub fn entries(&self) -> &[elf::DynRela] {
108        self.inner
109    }
110}
111
112/// Container of Dynamic Symbols
113pub struct DynamicSymbols<'a> {
114    inner: &'a elf::DynSym,
115}
116
117impl DynamicSymbols<'_> {
118    /// gets the dynamic symbol at this index
119    fn get(&self, index: usize) -> Option<&elf::DynSym> {
120        unsafe { (self.inner as *const elf::DynSym).add(index).as_ref() }
121    }
122
123    /// resolves the name of the dynamic symbol at `index`
124    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
134/// Container of Dynamic Entries
135pub struct DynamicSection<'a> {
136    inner: &'a elf::DynEntry,
137}
138
139#[derive(Debug)]
140/// A view of the Library's String table
141/// The inner `raw` reference refers to a continguous array of zero terminated strings.
142/// The string table view is needed to arbitrarily access into the data and pull out the null terminated strings.
143pub struct StringTable<'a> {
144    raw: &'a [libc::c_char],
145}
146
147impl<'a> StringTable<'a> {
148    /// Extract string from table starting at carrot position
149    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    /// total size of the string table in memory.
159    /// This does not reflect how many strings
160    pub fn total_size(&self) -> usize {
161        self.raw.len()
162    }
163}
164
165impl DynamicSection<'_> {
166    /// Iterate dynamic section's DynEntry link list attempting to find section with target section type
167    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                    // nothing to do.
175                }
176                Err(_err) => {
177                    // continue for now...;
178                }
179            }
180
181            current = unsafe { (inner as *const elf::DynEntry).offset(1).as_ref() };
182        }
183
184        None
185    }
186}
187
188/// An Elf Program Header
189/// Primary examples are PT_LOAD and PT_DYNAMIC
190pub struct ProgramHeader<'a> {
191    inner: &'a elf::ProgramHeader,
192}
193
194impl ProgramHeader<'_> {
195    /// Access the program headers type
196    pub fn header_type(&self) -> elf::Word {
197        self.inner.p_type
198    }
199
200    /// Access the program headers virtual address
201    pub fn virtual_addr(&self) -> usize {
202        self.inner.p_vaddr as usize
203    }
204
205    /// Total size in memory
206    pub fn memory_size(&self) -> usize {
207        self.inner.p_memsz as usize
208    }
209
210    /// File size
211    pub fn file_size(&self) -> usize {
212        self.inner.p_filesz as usize
213    }
214
215    /// Program headers absolute address
216    pub fn program_addr(&self) -> usize {
217        self.inner.p_paddr as usize
218    }
219
220    /// Program headers offset
221    pub fn offset(&self) -> usize {
222        self.inner.p_offset as usize
223    }
224}
225
226/// A dynamic libraries plt maybe be addend entries or non addend entries
227pub enum RelocationTable<'a> {
228    WithAddend(DynamicAddendRelocations<'a>),
229    WithoutAddend(DynamicRelocations<'a>),
230}
231
232/// Dynamic Library Entry
233/// An 'upgraded' LibraryEntry with the dynamic section resolved.
234pub 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
245/// Access the libraries dynamic symbols through the library's dynamic section
246fn extract_dyn_symbols<'a, 'b>(
247    lib: &'a LoadedLibrary<'a>,
248    dynamic_section: &'a DynamicSection<'a>,
249) -> Result<Option<DynamicSymbols<'b>>, DynamicError> {
250    // No actual requirement for dynamic symbols.
251    let Some(dyn_symbol_table) = dynamic_section.find_section(DynamicSectionType::DT_SYMTAB) else {
252        return Ok(None);
253    };
254
255    // We use explicit Elf Dynamic entry structs.
256    // The SYMENT size doesn't seem relevant anymore, so we can assert it the same size as the dyn entry to combat any egregious misusages
257    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    // We don't have enough information to tell if this elf represents an Object Mapped or Shared Library / Executable mapped entry
267    // For object mapped the ptr's are relative. So we have to rebase by the virtual address from dl_info
268    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
278/// Access the libraries dynamic section by dereferencing the PT_DYN program header's virtual address value
279fn 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
294/// Access the libraries string table through the library's dynamic section
295fn 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    // We don't have enough information to tell if this elf represents an Object Mapped or Shared Library / Executable mapped entry
311    // For object mapped the ptr's are relative. So we have to rebase by the virtual address from dl_info
312    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
322/// Access the libaries dynamic relocations through the dynamic program header
323fn 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    // We don't have enough information to tell if this elf represents an Object Mapped or Shared Library / Executable mapped entry
350    // For object mapped the ptr's are relative. So we have to rebase by the virtual address from dl_info
351    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
361/// Access the libaries dynamic addend relocations through the dynamic program header
362fn 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    // We don't have enough information to tell if this elf represents an Object Mapped or Shared Library / Executable mapped entry
389    // For object mapped the ptr's are relative. So we have to rebase by the virtual address from dl_info
390    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
400/// Access the libraries plt relocations
401fn extract_dyn_plt<'a, 'b>(
402    lib: &'a LoadedLibrary<'a>,
403    dynamic_section: &'a DynamicSection<'a>,
404) -> Result<Option<RelocationTable<'b>>, DynamicError> {
405    // decipher if its rel or rela relocation entries
406    // if this isn't present we can't have a plt
407    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    /// Try to consume a LoadedLibrary and create a resolved Dynamic view
455    /// The Dynamic Library will take ownership of the load library as well as store
456    /// all relevant dynamic sections for easy access and symbol resolution
457    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    /// Finding target function differs on 32 bit and 64 bit.
477    /// On 32 bit we want to check the relocations table only, opposed to the addend relocations table.
478    /// Additionally, we will fall back to the plt given it is an addendless relocation table.
479    #[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    /// Finding target function differs on 32 bit and 64 bit.
518    /// On 64 bit we want to check the addended relocations table only, opposed to the addendless relocations table.
519    /// Additionally, we will fall back to the plt given it is an addended relocation table.
520    #[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    /// Access the plt as a dynamic relocation table if possible
556    /// can fail if the plt is not available or the plt is with addend
557    pub fn plt_rel(&self) -> Option<&DynamicRelocations<'_>> {
558        match self.plt() {
559            Some(RelocationTable::WithoutAddend(relocs)) => Some(relocs),
560            _ => None,
561        }
562    }
563
564    /// Access the plt as a dynamic addend relocation table if possible
565    /// can fail if the plt is not available or the plt is without addend
566    pub fn plt_rela(&self) -> Option<&DynamicAddendRelocations<'_>> {
567        match self.plt() {
568            Some(RelocationTable::WithAddend(relocs)) => Some(relocs),
569            _ => None,
570        }
571    }
572    /// Access the dynamic libraries plt if available
573    /// Can be either a DynamicRelocations or DynamicAddendRelocations
574    pub fn plt(&self) -> Option<&RelocationTable<'_>> {
575        self.dyn_plt.as_ref()
576    }
577
578    /// Access the dynamic libraries relocations if available
579    pub fn relocs(&self) -> Option<&DynamicRelocations<'_>> {
580        self.dyn_relocs.as_ref()
581    }
582
583    /// Access the dynamic libraries addend relocations if available
584    pub fn addend_relocs(&self) -> Option<&DynamicAddendRelocations<'_>> {
585        self.dyn_addend_relocs.as_ref()
586    }
587
588    /// Access the dynamic libraries symbol table if available
589    pub fn symbols(&self) -> Option<&DynamicSymbols<'_>> {
590        self.dyn_symbols.as_ref()
591    }
592
593    /// Access the dynamic libraries dynamic section
594    pub fn dyn_section(&self) -> &DynamicSection<'_> {
595        &self.dyn_section
596    }
597
598    /// Access the dynamic libraries backing general loaded library structure
599    /// capable of providing the name and base address of the in memory
600    pub fn library(&self) -> &LoadedLibrary<'_> {
601        &self.library
602    }
603
604    /// Accesses the Dynamic modules base address.
605    /// Convenience function that reads base addr from backing LoadedLibrary
606    pub fn base_addr(&self) -> usize {
607        self.library.addr
608    }
609
610    /// Access the dynamic string table
611    pub fn string_table(&self) -> &StringTable<'_> {
612        &self.dyn_string_table
613    }
614}
615
616/// A library loaded in the process
617pub struct LoadedLibrary<'a> {
618    addr: usize,
619    name: Cow<'a, str>,
620    program_headers: &'a [elf::ProgramHeader],
621}
622
623impl<'a> LoadedLibrary<'a> {
624    /// Access the libraries string name
625    /// This is more the libraries `path` than the name per say
626    pub fn name(&self) -> &str {
627        &self.name
628    }
629
630    /// Access the libraries virtual address
631    pub fn addr(&self) -> usize {
632        self.addr
633    }
634
635    /// Iterate the libraries program headers
636    pub fn program_headers(&self) -> impl Iterator<Item = ProgramHeader<'_>> {
637        self.program_headers
638            .iter()
639            .map(|header| ProgramHeader { inner: header })
640    }
641
642    /// Access the libraries PT_INTERP program headers
643    pub fn interpreter_header(&self) -> Option<ProgramHeader<'_>> {
644        self.program_headers().find(|p_h| p_h.header_type() == 0x03)
645    }
646
647    /// Access the libraries PT_LOAD program headers
648    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
673/// Attempts to patch plt entry at entry_addr.
674/// Sets page protections containing entry_addr to PROT_WRITE | PROT_EXEC before replacing the pointer.
675/// After writing out reverts the page to solely PROT_READ.
676/// Returns the previous value contained in the entry_addr prior to patching.
677pub 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        // Set the memory page to read, write
683        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        // Replace the function address
697        let previous_address = core::ptr::replace(entry_addr as *mut _, func as *mut c_void);
698
699        // Set the memory page protection back to read only
700        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
713/// Returns a `Vec` of objects loaded into the current address space.
714pub fn collect_modules<'a>() -> Vec<LoadedLibrary<'a>> {
715    let mut ret = Vec::new();
716
717    // Pushes an `Object` into the result vector on the behalf of C.
718    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        // We have to copy sthe `dl_phdr_info` struct out, as the same memory buffer is used for
721        // each entry during the iteration process. Otherwise we could have used a vector of
722        // pointers.
723
724        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    // Callback for `dl_iterate_phdr(3)`.
738    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); // Get Rust to push the object.
745        };
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}