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