1#!/usr/bin/env python3 2 3from __future__ import print_function 4 5import argparse 6import collections 7import struct 8import sys 9 10 11Elf_Hdr = collections.namedtuple( 12 'Elf_Hdr', 13 'ei_class ei_data ei_version ei_osabi e_type e_machine e_version ' 14 'e_entry e_phoff e_shoff e_flags e_ehsize e_phentsize e_phnum ' 15 'e_shentsize e_shnum e_shstridx') 16 17 18Elf_Shdr = collections.namedtuple( 19 'Elf_Shdr', 20 'sh_name sh_type sh_flags sh_addr sh_offset sh_size sh_link sh_info ' 21 'sh_addralign sh_entsize') 22 23 24Elf_Dyn = collections.namedtuple('Elf_Dyn', 'd_tag d_val') 25 26 27class Elf_Sym(collections.namedtuple( 28 'ELF_Sym', 'st_name st_value st_size st_info st_other st_shndx')): 29 30 STB_LOCAL = 0 31 STB_GLOBAL = 1 32 STB_WEAK = 2 33 34 SHN_UNDEF = 0 35 36 @property 37 def st_bind(self): 38 return (self.st_info >> 4) 39 40 @property 41 def is_local(self): 42 return self.st_bind == Elf_Sym.STB_LOCAL 43 44 @property 45 def is_global(self): 46 return self.st_bind == Elf_Sym.STB_GLOBAL 47 48 @property 49 def is_weak(self): 50 return self.st_bind == Elf_Sym.STB_WEAK 51 52 @property 53 def is_undef(self): 54 return self.st_shndx == Elf_Sym.SHN_UNDEF 55 56 57class ELFError(ValueError): 58 pass 59 60 61# ELF file format constants. 62ELF_MAGIC = b'\x7fELF' 63 64EI_CLASS = 4 65EI_DATA = 5 66 67ELFCLASS32 = 1 68ELFCLASS64 = 2 69ELFDATA2LSB = 1 70ELFDATA2MSB = 2 71 72DT_NULL = 0 73DT_NEEDED = 1 74 75 76if sys.version_info >= (3, 0): 77 def _extract_buf_byte(buf, offset): 78 return buf[offset] 79else: 80 def _extract_buf_byte(buf, offset): 81 return ord(buf[offset]) 82 83 84def _extract_zero_terminated_buf_slice(buf, offset): 85 """Extract a zero-terminated buffer slice from the given offset""" 86 end = offset 87 try: 88 while _extract_buf_byte(buf, end) != 0: 89 end += 1 90 except IndexError: 91 pass 92 return buf[offset:end] 93 94 95if sys.version_info >= (3, 0): 96 def _extract_zero_terminated_str(buf, offset): 97 buf_slice = _extract_zero_terminated_buf_slice(buf, offset) 98 return buf_slice.decode('utf-8') 99else: 100 def _extract_zero_terminated_str(buf, offset): 101 return _extract_zero_terminated_buf_slice(buf, offset) 102 103 104def _replace_dt_needed_buf_internal(buf, dt_needed_name): 105 # Check ELF ident. 106 if len(buf) < 8: 107 raise ELFError('bad ident') 108 109 if buf[0:4] != ELF_MAGIC: 110 raise ELFError('bad magic') 111 112 ei_class = _extract_buf_byte(buf, EI_CLASS) 113 if ei_class not in (ELFCLASS32, ELFCLASS64): 114 raise ELFError('unknown word size') 115 is_32bit = ei_class == ELFCLASS32 116 117 ei_data = _extract_buf_byte(buf, EI_DATA) 118 if ei_data not in (ELFDATA2LSB, ELFDATA2MSB): 119 raise ELFError('unknown endianness') 120 121 # ELF structure definitions. 122 endian_fmt = '<' if ei_data == ELFDATA2LSB else '>' 123 124 if is_32bit: 125 elf_hdr_fmt = endian_fmt + '4x4B8xHHLLLLLHHHHHH' 126 elf_shdr_fmt = endian_fmt + 'LLLLLLLLLL' 127 elf_dyn_fmt = endian_fmt + 'lL' 128 elf_sym_fmt = endian_fmt + 'LLLBBH' 129 else: 130 elf_hdr_fmt = endian_fmt + '4x4B8xHHLQQQLHHHHHH' 131 elf_shdr_fmt = endian_fmt + 'LLQQQQLLQQ' 132 elf_dyn_fmt = endian_fmt + 'QQ' 133 elf_sym_fmt = endian_fmt + 'LBBHQQ' 134 135 def parse_struct(cls, fmt, offset, error_msg): 136 try: 137 return cls._make(struct.unpack_from(fmt, buf, offset)) 138 except struct.error: 139 raise ELFError(error_msg) 140 141 def parse_elf_hdr(offset): 142 return parse_struct(Elf_Hdr, elf_hdr_fmt, offset, 'bad elf header') 143 144 def parse_elf_shdr(offset): 145 return parse_struct(Elf_Shdr, elf_shdr_fmt, offset, 146 'bad section header') 147 148 def parse_elf_dyn(offset): 149 return parse_struct(Elf_Dyn, elf_dyn_fmt, offset, 'bad .dynamic entry') 150 151 def extract_str(offset): 152 return _extract_zero_terminated_str(buf, offset) 153 154 # Parse ELF header. 155 header = parse_elf_hdr(0) 156 157 # Check section header size. 158 if header.e_shentsize == 0: 159 raise ELFError('no section header') 160 161 # Find .shstrtab section. 162 shstrtab_shdr_off = \ 163 header.e_shoff + header.e_shstridx * header.e_shentsize 164 shstrtab_shdr = parse_elf_shdr(shstrtab_shdr_off) 165 shstrtab_off = shstrtab_shdr.sh_offset 166 167 # Parse ELF section header. 168 sections = dict() 169 header_end = header.e_shoff + header.e_shnum * header.e_shentsize 170 for shdr_off in range(header.e_shoff, header_end, header.e_shentsize): 171 shdr = parse_elf_shdr(shdr_off) 172 name = extract_str(shstrtab_off + shdr.sh_name) 173 sections[name] = shdr 174 175 # Find .dynamic and .dynstr section header. 176 dynamic_shdr = sections.get('.dynamic') 177 if not dynamic_shdr: 178 raise ELFError('no .dynamic section') 179 180 dynstr_shdr = sections.get('.dynstr') 181 if not dynstr_shdr: 182 raise ELFError('no .dynstr section') 183 184 dynamic_off = dynamic_shdr.sh_offset 185 dynstr_off = dynstr_shdr.sh_offset 186 187 # Find DT_NULL entry. 188 ent_size = dynamic_shdr.sh_entsize 189 assert struct.calcsize(elf_dyn_fmt) == ent_size 190 191 if dynamic_shdr.sh_size < ent_size: 192 raise ELFError('.dynamic section is empty') 193 194 dynamic_end = dynamic_off + dynamic_shdr.sh_size 195 dt_null_off = dynamic_end - ent_size 196 if parse_elf_dyn(dt_null_off).d_tag != DT_NULL: 197 raise ELFError('.dynamic section is not ended with DT_NULL') 198 dt_null_ent = buf[dt_null_off:dt_null_off + ent_size] 199 200 # Build result buffer which replaces matching DT_NEEDED entries. 201 res = buf[0:dynamic_off] 202 for ent_off in range(dynamic_off, dynamic_end, ent_size): 203 ent = parse_elf_dyn(ent_off) 204 if ent.d_tag != DT_NEEDED or \ 205 extract_str(dynstr_off + ent.d_val) != dt_needed_name: 206 res += buf[ent_off:ent_off + ent_size] 207 for ent_off in range(len(res), dynamic_end, ent_size): 208 res += dt_null_ent 209 res += buf[dynamic_end:] 210 return res 211 212 213def replace_dt_needed_buf(buf, dt_needed_name): 214 try: 215 return _replace_dt_needed_buf_internal(buf, dt_needed_name) 216 except IndexError: 217 raise ELFError('bad offset') 218 219 220def replace_dt_needed(input_path, output_path, dt_needed_name): 221 with open(input_path, 'rb') as f: 222 buf = f.read() 223 224 buf = replace_dt_needed_buf(buf, dt_needed_name) 225 226 with open(output_path, 'wb') as f: 227 f.write(buf) 228 229 230def main(): 231 parser = argparse.ArgumentParser(description='Remove DT_NEEDED entries') 232 parser.add_argument('input', help='input ELF file') 233 parser.add_argument('--output', '-o', required=True, help='output ELF file') 234 parser.add_argument('--name', required=True, help='name') 235 args = parser.parse_args() 236 237 try: 238 replace_dt_needed(args.input, args.output, args.name) 239 return 0 240 except (OSError, ELFError) as e: 241 print('error:', e, file=sys.stderr) 242 243 return 1 244 245if __name__ == '__main__': 246 sys.exit(main()) 247