1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use {
    crate::UndefinedSymbol,
    byteorder::ReadBytesExt,
    std::{ffi::CStr, os::raw::c_char},
};

#[repr(C)]
#[derive(Debug, Clone)]
struct Elf64_Verdef {
    vd_version: u16,
    vd_flags: u16,
    vd_ndx: u16,
    vd_cnt: u16,
    vd_hash: u32,
    vd_aux: u32,
    vd_next: u32,
}

#[repr(C)]
#[derive(Debug, Clone)]
struct Elf64_Verneed {
    vn_version: u16,
    vn_cnt: u16,
    vn_file: u32,
    vn_aux: u32,
    vn_next: u32,
}

#[repr(C)]
#[derive(Debug, Clone)]
struct Elf64_Vernaux {
    vna_hash: u32,
    vna_flags: u16,
    vna_other: u16,
    vna_name: u32,
    vna_next: u32,
}

fn resolve_verneed(
    verneed_entries: &[(Elf64_Verneed, Vec<Elf64_Vernaux>)],
    names_data: &[u8],
    versym: u16,
) -> (Option<String>, Option<String>) {
    // versym corresponds to value in Elf64_Vernaux.vna_other.
    for (verneed, vernauxes) in verneed_entries {
        for vernaux in vernauxes {
            if vernaux.vna_other != versym {
                continue;
            }

            let filename_ptr = unsafe { names_data.as_ptr().add(verneed.vn_file as usize) };
            let filename = unsafe { CStr::from_ptr(filename_ptr as *const c_char) };

            let depend_ptr = unsafe { names_data.as_ptr().add(vernaux.vna_name as usize) };
            let depend = unsafe { CStr::from_ptr(depend_ptr as *const c_char) };

            return (
                Some(filename.to_string_lossy().into_owned()),
                Some(depend.to_string_lossy().into_owned()),
            );
        }
    }

    (None, None)
}

/// Find undefined dynamic symbols in an ELF binary.
///
/// Will also resolve the filename and symbol version, if available.
#[allow(clippy::cast_ptr_alignment)]
pub fn find_undefined_elf_symbols(buffer: &[u8], elf: &goblin::elf::Elf) -> Vec<UndefinedSymbol> {
    let mut verneed_entries: Vec<(Elf64_Verneed, Vec<Elf64_Vernaux>)> = Vec::new();
    let mut versym: Vec<u16> = Vec::new();
    let mut verneed_names_section: u32 = 0;

    for section_header in &elf.section_headers {
        match section_header.sh_type {
            goblin::elf::section_header::SHT_GNU_VERSYM => {
                let data: &[u8] = &buffer[section_header
                    .file_range()
                    .expect("SHT_GNU_VERSYM missing file range")];

                let mut reader = std::io::Cursor::new(data);

                while let Ok(value) = reader.read_u16::<byteorder::NativeEndian>() {
                    versym.push(value);
                }
            }
            goblin::elf::section_header::SHT_GNU_VERNEED => {
                verneed_names_section = section_header.sh_link;

                let data: &[u8] = &buffer[section_header
                    .file_range()
                    .expect("SHT_GNU_VERNEED missing file range")];

                let mut ptr = data.as_ptr();

                for _ in 0..elf.dynamic.as_ref().unwrap().info.verneednum {
                    let record: Elf64_Verneed = unsafe { std::ptr::read(ptr as *const _) };

                    // Stash pointer to next Verneed record.
                    let next_record = unsafe { ptr.add(record.vn_next as usize) };

                    let mut vernaux: Vec<Elf64_Vernaux> = Vec::new();

                    ptr = unsafe { ptr.add(record.vn_aux as usize) };

                    for _ in 0..record.vn_cnt {
                        let aux: Elf64_Vernaux = unsafe { std::ptr::read(ptr as *const _) };
                        vernaux.push(aux.clone());
                        ptr = unsafe { ptr.add(aux.vna_next as usize) };
                    }

                    verneed_entries.push((record.clone(), vernaux));

                    ptr = next_record;
                }
            }
            _ => {}
        }
    }

    let dynstrtab = &elf.dynstrtab;
    let verneed_names_data: &[u8] = &buffer[elf.section_headers[verneed_names_section as usize]
        .file_range()
        .expect("verneed names section missing file range")];

    let mut res: Vec<UndefinedSymbol> = Vec::new();

    let mut versym_iter = versym.iter();

    for sym in elf.dynsyms.iter() {
        let versym = *versym_iter.next().unwrap();

        if sym.is_import() {
            let name = dynstrtab.get_at(sym.st_name).unwrap();

            res.push(if versym > 1 {
                let (filename, version) =
                    resolve_verneed(&verneed_entries, verneed_names_data, versym);

                UndefinedSymbol {
                    symbol: String::from(name),
                    filename,
                    version,
                }
            } else {
                UndefinedSymbol {
                    symbol: String::from(name),
                    filename: None,
                    version: None,
                }
            });
        }
    }

    res
}