tugger_binary_analysis/
elf.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use {
6    crate::UndefinedSymbol,
7    byteorder::ReadBytesExt,
8    std::{ffi::CStr, os::raw::c_char},
9};
10
11#[repr(C)]
12#[derive(Debug, Clone)]
13struct Elf64_Verdef {
14    vd_version: u16,
15    vd_flags: u16,
16    vd_ndx: u16,
17    vd_cnt: u16,
18    vd_hash: u32,
19    vd_aux: u32,
20    vd_next: u32,
21}
22
23#[repr(C)]
24#[derive(Debug, Clone)]
25struct Elf64_Verneed {
26    vn_version: u16,
27    vn_cnt: u16,
28    vn_file: u32,
29    vn_aux: u32,
30    vn_next: u32,
31}
32
33#[repr(C)]
34#[derive(Debug, Clone)]
35struct Elf64_Vernaux {
36    vna_hash: u32,
37    vna_flags: u16,
38    vna_other: u16,
39    vna_name: u32,
40    vna_next: u32,
41}
42
43fn resolve_verneed(
44    verneed_entries: &[(Elf64_Verneed, Vec<Elf64_Vernaux>)],
45    names_data: &[u8],
46    versym: u16,
47) -> (Option<String>, Option<String>) {
48    // versym corresponds to value in Elf64_Vernaux.vna_other.
49    for (verneed, vernauxes) in verneed_entries {
50        for vernaux in vernauxes {
51            if vernaux.vna_other != versym {
52                continue;
53            }
54
55            let filename_ptr = unsafe { names_data.as_ptr().add(verneed.vn_file as usize) };
56            let filename = unsafe { CStr::from_ptr(filename_ptr as *const c_char) };
57
58            let depend_ptr = unsafe { names_data.as_ptr().add(vernaux.vna_name as usize) };
59            let depend = unsafe { CStr::from_ptr(depend_ptr as *const c_char) };
60
61            return (
62                Some(filename.to_string_lossy().into_owned()),
63                Some(depend.to_string_lossy().into_owned()),
64            );
65        }
66    }
67
68    (None, None)
69}
70
71/// Find undefined dynamic symbols in an ELF binary.
72///
73/// Will also resolve the filename and symbol version, if available.
74#[allow(clippy::cast_ptr_alignment)]
75pub fn find_undefined_elf_symbols(buffer: &[u8], elf: &goblin::elf::Elf) -> Vec<UndefinedSymbol> {
76    let mut verneed_entries: Vec<(Elf64_Verneed, Vec<Elf64_Vernaux>)> = Vec::new();
77    let mut versym: Vec<u16> = Vec::new();
78    let mut verneed_names_section: u32 = 0;
79
80    for section_header in &elf.section_headers {
81        match section_header.sh_type {
82            goblin::elf::section_header::SHT_GNU_VERSYM => {
83                let data: &[u8] = &buffer[section_header
84                    .file_range()
85                    .expect("SHT_GNU_VERSYM missing file range")];
86
87                let mut reader = std::io::Cursor::new(data);
88
89                while let Ok(value) = reader.read_u16::<byteorder::NativeEndian>() {
90                    versym.push(value);
91                }
92            }
93            goblin::elf::section_header::SHT_GNU_VERNEED => {
94                verneed_names_section = section_header.sh_link;
95
96                let data: &[u8] = &buffer[section_header
97                    .file_range()
98                    .expect("SHT_GNU_VERNEED missing file range")];
99
100                let mut ptr = data.as_ptr();
101
102                for _ in 0..elf.dynamic.as_ref().unwrap().info.verneednum {
103                    let record: Elf64_Verneed = unsafe { std::ptr::read(ptr as *const _) };
104
105                    // Stash pointer to next Verneed record.
106                    let next_record = unsafe { ptr.add(record.vn_next as usize) };
107
108                    let mut vernaux: Vec<Elf64_Vernaux> = Vec::new();
109
110                    ptr = unsafe { ptr.add(record.vn_aux as usize) };
111
112                    for _ in 0..record.vn_cnt {
113                        let aux: Elf64_Vernaux = unsafe { std::ptr::read(ptr as *const _) };
114                        vernaux.push(aux.clone());
115                        ptr = unsafe { ptr.add(aux.vna_next as usize) };
116                    }
117
118                    verneed_entries.push((record.clone(), vernaux));
119
120                    ptr = next_record;
121                }
122            }
123            _ => {}
124        }
125    }
126
127    let dynstrtab = &elf.dynstrtab;
128    let verneed_names_data: &[u8] = &buffer[elf.section_headers[verneed_names_section as usize]
129        .file_range()
130        .expect("verneed names section missing file range")];
131
132    let mut res: Vec<UndefinedSymbol> = Vec::new();
133
134    let mut versym_iter = versym.iter();
135
136    for sym in elf.dynsyms.iter() {
137        let versym = *versym_iter.next().unwrap();
138
139        if sym.is_import() {
140            let name = dynstrtab.get_at(sym.st_name).unwrap();
141
142            res.push(if versym > 1 {
143                let (filename, version) =
144                    resolve_verneed(&verneed_entries, verneed_names_data, versym);
145
146                UndefinedSymbol {
147                    symbol: String::from(name),
148                    filename,
149                    version,
150                }
151            } else {
152                UndefinedSymbol {
153                    symbol: String::from(name),
154                    filename: None,
155                    version: None,
156                }
157            });
158        }
159    }
160
161    res
162}