objpoke/
elf.rs

1mod reloc;
2mod symtab;
3
4use crate::elf::{
5    reloc::{rel_size, ElfRelocationUpdate},
6    symtab::{sym_size, ElfSymbolTableUpdate},
7};
8use anyhow::{anyhow, bail, Result};
9use goblin::{
10    container::{Container, Ctx},
11    elf::{
12        section_header::{
13            section_header32, section_header64, SHT_GNU_HASH, SHT_GNU_VERSYM, SHT_HASH, SHT_NULL, SHT_RELA,
14            SHT_SYMTAB_SHNDX,
15        },
16        Elf, Header, SectionHeaders, Sym,
17    },
18    elf32::section_header::SHT_GROUP,
19    strtab::Strtab,
20};
21use regex::Regex;
22use scroll::{ctx::IntoCtx, Pread, Pwrite};
23use std::collections::HashMap;
24
25const GRP_COMDAT: u32 = 1; // Per https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter7-26.html
26
27pub fn localize_elf_symbols(data: Vec<u8>, keep_regexes: &[Regex]) -> Result<Vec<u8>> {
28    let elf = Elf::parse(&data)?;
29    let container = elf.header.container()?;
30    let endianness = elf.header.endianness()?;
31    let ctx = Ctx::new(container, endianness);
32    let section_headers = elf.section_headers.clone();
33
34    let new_symtabs = symtab::localize_elf_symbols(&elf, ctx, &data, keep_regexes)?;
35    let new_relocs = reloc::process_elf_relocations(&elf, ctx, &data, &new_symtabs);
36
37    patch_new_elf_symbols(elf.header, section_headers, ctx, data, new_symtabs, new_relocs)
38}
39
40fn patch_new_elf_symbols(
41    elf_header: Header,
42    section_headers: SectionHeaders,
43    ctx: Ctx,
44    mut data: Vec<u8>,
45    mut new_symtabs: HashMap<usize, ElfSymbolTableUpdate>,
46    mut new_relocs: HashMap<usize, ElfRelocationUpdate>,
47) -> Result<Vec<u8>> {
48    let shoff = elf_header.e_shoff as usize;
49    let sym_size = sym_size(&ctx);
50    let header_size = match ctx.container {
51        Container::Little => section_header32::SIZEOF_SHDR,
52        Container::Big => section_header64::SIZEOF_SHDR,
53    };
54    let mut shndx_to_symtab_map = HashMap::<usize, HashMap<usize, usize>>::new();
55
56    for (sh_idx, header) in section_headers.iter().enumerate() {
57        if header.sh_type == SHT_SYMTAB_SHNDX {
58            if header.sh_info != 0 {
59                bail!("SYMTAB_SHNDX ELF section has invalid non-zero sh_info");
60            }
61            match new_symtabs.get_mut(&(header.sh_link as usize)) {
62                None => bail!("SYMTAB_SHNDX ELF section references invalid symtab in sh_link"),
63                Some(symtab) => shndx_to_symtab_map.insert(sh_idx, std::mem::take(&mut symtab.sym_idx_map)),
64            };
65        }
66    }
67
68    for (sh_idx, mut header) in section_headers.into_iter().enumerate() {
69        // We don't implement hash sections, but they're just an optimization. Discard them.
70        if header.sh_type == SHT_HASH || header.sh_type == SHT_GNU_HASH {
71            header.sh_type = SHT_NULL;
72        } else if header.sh_type == SHT_SYMTAB_SHNDX {
73            // SYMTAB_SHNDX entries match the symtab entry order 1:1, so we just apply the same swaps
74            let symtab_idx_map = shndx_to_symtab_map.get(&sh_idx).unwrap();
75            let shndx_range = header.file_range().expect("Symtab SHNDX without file range");
76            let shndx_data = &mut data[shndx_range.start..shndx_range.end];
77            for (old_idx, new_idx) in symtab_idx_map {
78                if old_idx < new_idx {
79                    let old_entry: u32 = shndx_data.pread_with(old_idx * 4, ctx.le).unwrap();
80                    let new_entry: u32 = shndx_data.pread_with(new_idx * 4, ctx.le).unwrap();
81                    shndx_data.pwrite_with(new_entry, old_idx * 4, ctx.le)?;
82                    shndx_data.pwrite_with(old_entry, new_idx * 4, ctx.le)?;
83                }
84            }
85        } else if header.sh_type == SHT_GNU_VERSYM {
86            // We could handle those by reordering the entries, but it's not always enough
87            // If we're trying to localize a versioned symbol, we'd need to modify other sections
88            return Err(anyhow!("Cannot handle GNU_VERSYM ELF sections"));
89        } else if let Some(new_symtab) = new_symtabs.remove(&sh_idx) {
90            for (sym_idx, sym) in new_symtab.syms.into_iter().enumerate() {
91                let offset = new_symtab.header.sh_offset as usize + sym_idx * sym_size;
92                sym.into_ctx(&mut data[offset..], ctx);
93            }
94            header = new_symtab.header;
95        } else if let Some(new_rel) = new_relocs.remove(&sh_idx) {
96            let is_rela = new_rel.header.sh_type == SHT_RELA;
97            let rel_size = rel_size(ctx, &new_rel.header);
98            for (rel_idx, rel) in new_rel.rels.into_iter().enumerate() {
99                let offset = new_rel.header.sh_offset as usize + rel_idx * rel_size;
100                rel.into_ctx(&mut data[offset..], (is_rela, ctx));
101            }
102        }
103
104        let offset = shoff + sh_idx * header_size;
105        header.into_ctx(&mut data[offset..], ctx);
106    }
107
108    Ok(data)
109}
110
111pub fn demote_comdat_groups(mut data: Vec<u8>, keep_regexes: &[Regex]) -> Result<Vec<u8>> {
112    let elf = Elf::parse(&data)?;
113    let container = elf.header.container()?;
114    let endianness = elf.header.endianness()?;
115    let section_headers = elf.section_headers;
116    let ctx = Ctx::new(container, endianness);
117
118    'next_section: for header in section_headers.iter() {
119        // "The sh_flags member of the section header contains the value zero."
120        if header.sh_type != SHT_GROUP || header.sh_flags != 0 {
121            continue;
122        }
123
124        let group_range = header.file_range().expect("Section header without file range");
125        let group_data = &data[group_range.start..group_range.end];
126        if group_data.len() < 4 {
127            continue; // Not Supposed To Happen, but can't be too careful with wild ELFs...
128        }
129        let group_flags: u32 = group_data.pread_with(0, endianness).unwrap();
130        if group_flags & GRP_COMDAT == 0 {
131            continue;
132        }
133        if group_flags != GRP_COMDAT {
134            // It's probably safe to unset just GRP_COMDAT, but I can't rule out that someone
135            // will create a new flag that _only_ makes sense alongside GRP_COMDAT,
136            // so out of an abundance of caution let's reject unknown group flags
137            return Err(anyhow!(
138                "COMDAT section group also contains unknown flags ({}), refusing to continue",
139                group_flags
140            ));
141        }
142
143        // "The section header of the SHT_GROUP section specifies the identifying symbol entry.
144        //  The sh_link member contains the section header index of the symbol table section that contains the entry.
145        //  The sh_info member contains the symbol table index of the identifying entry"
146        let symtab_idx = header.sh_link as usize;
147        let sym_idx = header.sh_info as usize;
148
149        let symtab = match section_headers.get(symtab_idx) {
150            Some(symtab) => symtab,
151            None => {
152                return Err(anyhow!(
153                    "Section group references invalid symbol table index: {}",
154                    symtab_idx
155                ));
156            },
157        };
158        let symtab_range = symtab.file_range().expect("Symtab section without file range");
159        let symtab_data = &data[symtab_range.start..symtab_range.end];
160        let sym_size = sym_size(&ctx);
161        let name_sym: Sym = symtab_data.pread_with(sym_idx * sym_size, ctx).unwrap();
162        let strtab_idx = symtab.sh_link as usize;
163
164        let strtab = if strtab_idx >= section_headers.len() {
165            return Err(anyhow!(
166                "Section group symbol references invalid string table index: {}",
167                strtab_idx
168            ));
169        } else {
170            let shdr = &section_headers[strtab_idx];
171            shdr.check_size(data.len())?;
172            Strtab::parse(&data, shdr.sh_offset as usize, shdr.sh_size as usize, 0x0)
173        }?;
174
175        if let Some(name) = strtab.get_at(name_sym.st_name) {
176            for regex in keep_regexes {
177                if regex.is_match(name) {
178                    continue 'next_section;
179                }
180            }
181        } else {
182            continue 'next_section;
183        }
184
185        let demoted_flags = group_flags & !GRP_COMDAT;
186        data.pwrite_with(demoted_flags, group_range.start, endianness)?;
187    }
188
189    Ok(data)
190}