• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Gallopsled / pwntools / 7250413177

18 Dec 2023 03:44PM UTC coverage: 71.866% (-2.7%) from 74.55%
7250413177

Pull #2297

github

web-flow
Merge fbc1d8e0b into c7649c95e
Pull Request #2297: add "retguard" property and display it with checksec

4328 of 7244 branches covered (0.0%)

5 of 6 new or added lines in 1 file covered. (83.33%)

464 existing lines in 9 files now uncovered.

12381 of 17228 relevant lines covered (71.87%)

0.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.48
/pwnlib/elf/elf.py
1
"""Exposes functionality for manipulating ELF files
2

3

4
Stop hard-coding things!  Look them up at runtime with :mod:`pwnlib.elf`.
5

6
Example Usage
7
-------------
8

9
.. code-block:: python
10

11
    >>> e = ELF('/bin/cat')
12
    >>> print(hex(e.address)) #doctest: +SKIP
13
    0x400000
14
    >>> print(hex(e.symbols['write'])) #doctest: +SKIP
15
    0x401680
16
    >>> print(hex(e.got['write'])) #doctest: +SKIP
17
    0x60b070
18
    >>> print(hex(e.plt['write'])) #doctest: +SKIP
19
    0x401680
20

21
You can even patch and save the files.
22

23
.. code-block:: python
24

25
    >>> e = ELF('/bin/cat')
26
    >>> e.read(e.address+1, 3)
27
    b'ELF'
28
    >>> e.asm(e.address, 'ret')
29
    >>> e.save('/tmp/quiet-cat')
30
    >>> disasm(open('/tmp/quiet-cat','rb').read(1))
31
    '   0:   c3                      ret'
32

33
Module Members
34
--------------
35
"""
36
from __future__ import absolute_import
1✔
37
from __future__ import division
1✔
38

39
import collections
1✔
40
import gzip
1✔
41
import mmap
1✔
42
import os
1✔
43
import re
1✔
44
import six
1✔
45
import subprocess
1✔
46
import tempfile
1✔
47

48
from six import BytesIO
1✔
49

50
from collections import namedtuple, defaultdict
1✔
51

52
from elftools.elf.constants import P_FLAGS
1✔
53
from elftools.elf.constants import SHN_INDICES
1✔
54
from elftools.elf.descriptions import describe_e_type
1✔
55
from elftools.elf.elffile import ELFFile
1✔
56
from elftools.elf.enums import ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS
1✔
57
from elftools.elf.gnuversions import GNUVerDefSection
1✔
58
from elftools.elf.relocation import RelocationSection, RelrRelocationSection
1✔
59
from elftools.elf.sections import SymbolTableSection
1✔
60
from elftools.elf.segments import InterpSegment
1✔
61

62
# See https://github.com/Gallopsled/pwntools/issues/1189
63
try:
1✔
64
    from elftools.elf.enums import ENUM_P_TYPE
1✔
65
except ImportError:
1✔
66
    from elftools.elf.enums import ENUM_P_TYPE_BASE as ENUM_P_TYPE
1✔
67

68
import intervaltree
1✔
69

70
from pwnlib import adb
1✔
71
from pwnlib import qemu
1✔
72
from pwnlib.asm import *
1✔
73
from pwnlib.context import LocalContext
1✔
74
from pwnlib.context import context
1✔
75
from pwnlib.elf.config import kernel_configuration
1✔
76
from pwnlib.elf.config import parse_kconfig
1✔
77
from pwnlib.elf.datatypes import constants
1✔
78
from pwnlib.elf.maps import CAT_PROC_MAPS_EXIT
1✔
79
from pwnlib.elf.plt import emulate_plt_instructions
1✔
80
from pwnlib.log import getLogger
1✔
81
from pwnlib.term import text
1✔
82
from pwnlib.tubes.process import process
1✔
83
from pwnlib.util import misc
1✔
84
from pwnlib.util import packing
1✔
85
from pwnlib.util.fiddling import unhex
1✔
86
from pwnlib.util.misc import align, align_down, which
1✔
87
from pwnlib.util.sh_string import sh_string
1✔
88

89
log = getLogger(__name__)
1✔
90

91
__all__ = ['load', 'ELF']
1✔
92

93
def _iter_symbols(sec):
1✔
94
    # Cache result of iter_symbols.
95
    if not hasattr(sec, '_symbols'):
1✔
96
        sec._symbols = list(sec.iter_symbols())
1✔
97
    return iter(sec._symbols)
1✔
98

99
class Function(object):
1✔
100
    """Encapsulates information about a function in an :class:`.ELF` binary.
101

102
    Arguments:
103
        name(str): Name of the function
104
        address(int): Address of the function
105
        size(int): Size of the function, in bytes
106
        elf(ELF): Encapsulating ELF object
107
    """
108
    def __init__(self, name, address, size, elf=None):
1✔
109
        #: Name of the function
110
        self.name = name
1✔
111

112
        #: Address of the function in the encapsulating ELF
113
        self.address = address
1✔
114

115
        #: Size of the function, in bytes
116
        self.size = size
1✔
117

118
        #: Encapsulating ELF object
119
        self.elf = elf
1✔
120

121
    def __repr__(self):
1✔
122
        return '%s(name=%r, address=%#x, size=%#x, elf=%r)' % (
×
123
            self.__class__.__name__,
124
            self.name,
125
            self.address,
126
            self.size,
127
            self.elf
128
            )
129

130
    def __flat__(self):
1✔
131
        return packing.pack(self.address)
×
132

133
    def disasm(self):
1✔
134
        return self.elf.disasm(self.address, self.size)
1✔
135

136
def load(*args, **kwargs):
1✔
137
    """Compatibility wrapper for pwntools v1"""
138
    return ELF(*args, **kwargs)
×
139

140
class dotdict(dict):
1✔
141
    """Wrapper to allow dotted access to dictionary elements.
142

143
    Is a real :class:`dict` object, but also serves up keys as attributes
144
    when reading attributes.
145

146
    Supports recursive instantiation for keys which contain dots.
147

148
    Example:
149

150
        >>> x = pwnlib.elf.elf.dotdict()
151
        >>> isinstance(x, dict)
152
        True
153
        >>> x['foo'] = 3
154
        >>> x.foo
155
        3
156
        >>> x['bar.baz'] = 4
157
        >>> x.bar.baz
158
        4
159
    """
160
    def __missing__(self, name):
1✔
161
        if isinstance(name, (bytes, bytearray)):
×
162
            name = packing._decode(name)
×
163
            return self[name]
×
164
        raise KeyError(name)
×
165

166
    def __getattr__(self, name):
1✔
167
        if name in self:
1✔
168
            return self[name]
1✔
169

170
        name_dot = name + '.'
1✔
171
        name_len = len(name_dot)
1✔
172
        subkeys = {k[name_len:]: self[k] for k in self if k.startswith(name_dot)}
1✔
173

174
        if subkeys:
1!
175
            return dotdict(subkeys)
1✔
176
        raise AttributeError(name)
×
177

178
class ELF(ELFFile):
1✔
179
    """Encapsulates information about an ELF file.
180

181
    Example:
182

183
        .. code-block:: python
184

185
           >>> bash = ELF(which('bash'))
186
           >>> hex(bash.symbols['read'])
187
           0x41dac0
188
           >>> hex(bash.plt['read'])
189
           0x41dac0
190
           >>> u32(bash.read(bash.got['read'], 4))
191
           0x41dac6
192
           >>> print(bash.disasm(bash.plt.read, 16))
193
           0:   ff 25 1a 18 2d 00       jmp    QWORD PTR [rip+0x2d181a]        # 0x2d1820
194
           6:   68 59 00 00 00          push   0x59
195
           b:   e9 50 fa ff ff          jmp    0xfffffffffffffa60
196
    """
197

198
    # These class-level intitializers are only for ReadTheDocs
199
    bits = 32
1✔
200
    bytes = 4
1✔
201
    path = '/path/to/the/file'
1✔
202
    symbols = {}
1✔
203
    got = {}
1✔
204
    plt = {}
1✔
205
    functions = {}
1✔
206
    endian = 'little'
1✔
207
    address = 0x400000
1✔
208
    linker = None
1✔
209

210
    # Whether to fill gaps in memory with zeroed pages
211
    _fill_gaps = True
1✔
212

213

214
    def __init__(self, path, checksec=True):
1✔
215
        # elftools uses the backing file for all reads and writes
216
        # in order to permit writing without being able to write to disk,
217
        # mmap() the file.
218

219
        #: :class:`file`: Open handle to the ELF file on disk
220
        self.file = open(path,'rb')
1✔
221

222
        #: :class:`mmap.mmap`: Memory-mapped copy of the ELF file on disk
223
        self.mmap = mmap.mmap(self.file.fileno(), 0, access=mmap.ACCESS_COPY)
1✔
224

225
        super(ELF,self).__init__(self.mmap)
1✔
226

227
        #: :class:`str`: Path to the file
228
        self.path = packing._need_text(os.path.abspath(path))
1✔
229

230
        #: :class:`dotdict` of ``name`` to ``address`` for all symbols in the ELF
231
        self.symbols = dotdict()
1✔
232

233
        #: :class:`dotdict` of ``name`` to ``address`` for all Global Offset Table (GOT) entries
234
        self.got = dotdict()
1✔
235

236
        #: :class:`dotdict` of ``name`` to ``address`` for all Procedure Linkage Table (PLT) entries
237
        self.plt = dotdict()
1✔
238

239
        #: :class:`dotdict` of ``name`` to :class:`.Function` for each function in the ELF
240
        self.functions = dotdict()
1✔
241

242
        #: :class:`dict`: Linux kernel configuration, if this is a Linux kernel image
243
        self.config = {}
1✔
244

245
        #: :class:`tuple`: Linux kernel version, if this is a Linux kernel image
246
        self.version = (0,)
1✔
247

248
        #: :class:`str`: Linux kernel build commit, if this is a Linux kernel image
249
        self.build = ''
1✔
250

251
        #: :class:`str`: Endianness of the file (e.g. ``'big'``, ``'little'``)
252
        self.endian = {
1✔
253
            'ELFDATANONE': 'little',
254
            'ELFDATA2LSB': 'little',
255
            'ELFDATA2MSB': 'big'
256
        }[self['e_ident']['EI_DATA']]
257

258
        #: :class:`int`: Bit-ness of the file
259
        self.bits = self.elfclass
1✔
260

261
        #: :class:`int`: Pointer width, in bytes
262
        self.bytes = self.bits // 8
1✔
263

264
        #: :class:`str`: Architecture of the file (e.g. ``'i386'``, ``'arm'``).
265
        #:
266
        #: See: :attr:`.ContextType.arch`
267
        self.arch = self.get_machine_arch()
1✔
268
        if isinstance(self.arch, (bytes, six.text_type)):
1!
269
            self.arch = self.arch.lower()
1✔
270

271
        self._sections = None
1✔
272
        self._segments = None
1✔
273

274
        #: IntervalTree which maps all of the loaded memory segments
275
        self.memory = intervaltree.IntervalTree()
1✔
276
        self._populate_memory()
1✔
277

278
        # Is this a native binary? Should we be checking QEMU?
279
        try:
1✔
280
            with context.local(arch=self.arch):
1✔
281
                #: Whether this ELF should be able to run natively
282
                self.native = context.native
1✔
283
        except AttributeError:
×
284
            # The architecture may not be supported in pwntools
285
            self.native = False
×
286

287
        self._address = 0
1✔
288
        if self.elftype != 'DYN':
1✔
289
            for seg in self.iter_segments_by_type('PT_LOAD'):
1✔
290
                addr = seg.header.p_vaddr
1✔
291
                if addr == 0:
1!
292
                    continue
×
293
                if addr < self._address or self._address == 0:
1✔
294
                    self._address = addr
1✔
295

296
        self.load_addr = self._address
1✔
297

298
        # Try to figure out if we have a kernel configuration embedded
299
        IKCFG_ST=b'IKCFG_ST'
1✔
300

301
        for start in self.search(IKCFG_ST):
1!
302
            start += len(IKCFG_ST)
×
303
            stop = next(self.search(b'IKCFG_ED'))
×
304

305
            fileobj = BytesIO(self.read(start, stop-start))
×
306

307
            # Python gzip throws an exception if there is non-Gzip data
308
            # after the Gzip stream.
309
            #
310
            # Catch the exception, and just deal with it.
311
            with gzip.GzipFile(fileobj=fileobj) as gz:
×
312
                config = gz.read()
×
313

314
            if config:
×
315
                self.config = parse_kconfig(config.decode())
×
316

317
        #: ``True`` if the ELF is a statically linked executable
318
        self.statically_linked = bool(self.elftype == 'EXEC' and self.load_addr)
1✔
319

320
        #: ``True`` if the ELF is an executable
321
        self.executable = bool(self.elftype == 'EXEC')
1✔
322

323
        for seg in self.iter_segments_by_type('PT_INTERP'):
1✔
324
            self.executable = True
1✔
325

326
            #: ``True`` if the ELF is statically linked
327
            self.statically_linked = False
1✔
328

329
            #: Path to the linker for the ELF
330
            self.linker = self.read(seg.header.p_vaddr, seg.header.p_memsz)
1✔
331
            self.linker = self.linker.rstrip(b'\x00')
1✔
332

333
        #: Operating system of the ELF
334
        self.os = 'linux'
1✔
335

336
        if self.linker and self.linker.startswith(b'/system/bin/linker'):
1!
337
            self.os = 'android'
×
338

339
        #: ``True`` if the ELF is a shared library
340
        self.library = not self.executable and self.elftype == 'DYN'
1✔
341

342
        try:
1✔
343
            self._populate_symbols()
1✔
344
        except Exception as e:
×
345
            log.warn("Could not populate symbols: %s", e)
×
346

347
        try:
1✔
348
            self._populate_got()
1✔
349
        except Exception as e:
×
350
            log.warn("Could not populate GOT: %s", e)
×
351

352
        try:
1✔
353
            self._populate_plt()
1✔
354
        except Exception as e:
×
355
            log.warn("Could not populate PLT: %s", e)
×
356

357
        self._populate_synthetic_symbols()
1✔
358
        self._populate_functions()
1✔
359
        self._populate_kernel_version()
1✔
360

361
        if checksec:
1✔
362
            self._describe()
1✔
363

364
        self._libs = None
1✔
365
        self._maps = None
1✔
366

367
    @staticmethod
1✔
368
    @LocalContext
1✔
369
    def from_assembly(assembly, *a, **kw):
1✔
370
        """from_assembly(assembly) -> ELF
371

372
        Given an assembly listing, return a fully loaded ELF object
373
        which contains that assembly at its entry point.
374

375
        Arguments:
376

377
            assembly(str): Assembly language listing
378
            vma(int): Address of the entry point and the module's base address.
379

380
        Example:
381

382
            >>> e = ELF.from_assembly('nop; foo: int 0x80', vma = 0x400000)
383
            >>> e.symbols['foo'] = 0x400001
384
            >>> e.disasm(e.entry, 1)
385
            '  400000:       90                      nop'
386
            >>> e.disasm(e.symbols['foo'], 2)
387
            '  400001:       cd 80                   int    0x80'
388
        """
389
        return ELF(make_elf_from_assembly(assembly, *a, **kw))
1✔
390

391
    @staticmethod
1✔
392
    @LocalContext
1✔
393
    def from_bytes(bytes, *a, **kw):
1✔
394
        r"""from_bytes(bytes) -> ELF
395

396
        Given a sequence of bytes, return a fully loaded ELF object
397
        which contains those bytes at its entry point.
398

399
        Arguments:
400

401
            bytes(str): Shellcode byte string
402
            vma(int): Desired base address for the ELF.
403

404
        Example:
405

406
            >>> e = ELF.from_bytes(b'\x90\xcd\x80', vma=0xc000)
407
            >>> print(e.disasm(e.entry, 3))
408
                c000:       90                      nop
409
                c001:       cd 80                   int    0x80
410
        """
411
        return ELF(make_elf(bytes, extract=False, *a, **kw))
1✔
412

413
    def process(self, argv=[], *a, **kw):
1✔
414
        """process(argv=[], *a, **kw) -> process
415

416
        Execute the binary with :class:`.process`.  Note that ``argv``
417
        is a list of arguments, and should not include ``argv[0]``.
418

419
        Arguments:
420
            argv(list): List of arguments to the binary
421
            *args: Extra arguments to :class:`.process`
422
            **kwargs: Extra arguments to :class:`.process`
423

424
        Returns:
425
            :class:`.process`
426
        """
427

428
        p = process
1✔
429
        if context.os == 'android':
1!
430
            p = adb.process
×
431
        return p([self.path] + argv, *a, **kw)
1✔
432

433
    def debug(self, argv=[], *a, **kw):
1✔
434
        """debug(argv=[], *a, **kw) -> tube
435

436
        Debug the ELF with :func:`.gdb.debug`.
437

438
        Arguments:
439
            argv(list): List of arguments to the binary
440
            *args: Extra arguments to :func:`.gdb.debug`
441
            **kwargs: Extra arguments to :func:`.gdb.debug`
442

443
        Returns:
444
            :class:`.tube`: See :func:`.gdb.debug`
445
        """
446
        import pwnlib.gdb
×
447
        return pwnlib.gdb.debug([self.path] + argv, *a, **kw)
×
448

449
    def _describe(self, *a, **kw):
1✔
450
        log.info_once(
1✔
451
            '%s\n%-10s%s-%s-%s\n%s',
452
            repr(self.path),
453
            'Arch:',
454
            self.arch,
455
            self.bits,
456
            self.endian,
457
            self.checksec(*a, **kw)
458
        )
459

460
    def get_machine_arch(self):
1✔
461
        return {
1✔
462
            ('EM_X86_64', 64): 'amd64',
463
            ('EM_X86_64', 32): 'amd64', # x32 ABI
464
            ('EM_386', 32): 'i386',
465
            ('EM_486', 32): 'i386',
466
            ('EM_ARM', 32): 'arm',
467
            ('EM_AARCH64', 64): 'aarch64',
468
            ('EM_MIPS', 32): 'mips',
469
            ('EM_MIPS', 64): 'mips64',
470
            ('EM_PPC', 32): 'powerpc',
471
            ('EM_PPC64', 64): 'powerpc64',
472
            ('EM_SPARC32PLUS', 32): 'sparc',
473
            ('EM_SPARCV9', 64): 'sparc64',
474
            ('EM_IA_64', 64): 'ia64',
475
            ('EM_RISCV', 32): 'riscv32',
476
            ('EM_RISCV', 64): 'riscv64',
477
        }.get((self['e_machine'], self.bits), self['e_machine'])
478

479
    @property
1✔
480
    def entry(self):
1✔
481
        """:class:`int`: Address of the entry point for the ELF"""
482
        return self.address + (self.header.e_entry - self.load_addr)
1✔
483
    entrypoint = entry
1✔
484
    start      = entry
1✔
485

486
    @property
1✔
487
    def elftype(self):
1✔
488
        """:class:`str`: ELF type (``EXEC``, ``DYN``, etc)"""
489
        return describe_e_type(self.header.e_type).split()[0]
1✔
490

491
    def iter_segments(self):
1✔
492
        # Yield and cache all the segments in the file
493
        if self._segments is None:
1✔
494
            self._segments = [self.get_segment(i) for i in range(self.num_segments())]
1✔
495

496
        return iter(self._segments)
1✔
497

498
    @property
1✔
499
    def segments(self):
1✔
500
        """
501
        :class:`list`: A list of :class:`elftools.elf.segments.Segment` objects
502
            for the segments in the ELF.
503
        """
504
        return list(self.iter_segments())
1✔
505

506
    def iter_segments_by_type(self, t):
1✔
507
        """
508
        Yields:
509
            Segments matching the specified type.
510
        """
511
        for seg in self.iter_segments():
1✔
512
            if t == seg.header.p_type or t in str(seg.header.p_type):
1✔
513
                yield seg
1✔
514

515
    def iter_notes(self):
1✔
516
        """ 
517
        Yields:
518
            All the notes in the PT_NOTE segments.  Each result is a dictionary-
519
            like object with ``n_name``, ``n_type``, and ``n_desc`` fields, amongst
520
            others.
521
        """
522
        for seg in self.iter_segments_by_type('PT_NOTE'):
1✔
523
            for note in seg.iter_notes():
1✔
524
                yield note
1✔
525

526
    def iter_properties(self):
1✔
527
        """
528
        Yields:
529
            All the GNU properties in the PT_NOTE segments.  Each result is a dictionary-
530
            like object with ``pr_type``, ``pr_datasz``, and ``pr_data`` fields.
531
        """
532
        for note in self.iter_notes():
1✔
533
            if note.n_type != 'NT_GNU_PROPERTY_TYPE_0':
1✔
534
                continue
1✔
535
            for prop in note.n_desc:
1!
536
                yield prop
1✔
537
                
538
    def get_segment_for_address(self, address, size=1):
1✔
539
        """get_segment_for_address(address, size=1) -> Segment
540

541
        Given a virtual address described by a ``PT_LOAD`` segment, return the
542
        first segment which describes the virtual address.  An optional ``size``
543
        may be provided to ensure the entire range falls into the same segment.
544

545
        Arguments:
546
            address(int): Virtual address to find
547
            size(int): Number of bytes which must be available after ``address``
548
                in **both** the file-backed data for the segment, and the memory
549
                region which is reserved for the data.
550

551
        Returns:
552
            Either returns a :class:`.segments.Segment` object, or ``None``.
553
        """
554
        for seg in self.iter_segments_by_type("PT_LOAD"):
1!
555
            mem_start = seg.header.p_vaddr
1✔
556
            mem_stop  = seg.header.p_memsz + mem_start
1✔
557

558
            if not (mem_start <= address <= address+size < mem_stop):
1✔
559
                continue
1✔
560

561
            offset = self.vaddr_to_offset(address)
1✔
562

563
            file_start = seg.header.p_offset
1✔
564
            file_stop  = seg.header.p_filesz + file_start
1✔
565

566
            if not (file_start <= offset <= offset+size < file_stop):
1!
567
                continue
×
568

569
            return seg
1✔
570

571
        return None
×
572

573
    def iter_sections(self):
1✔
574
        # Yield and cache all the sections in the file
575
        if self._sections is None:
1✔
576
            self._sections = [self.get_section(i) for i in range(self.num_sections())]
1✔
577

578
        return iter(self._sections)
1✔
579

580
    @property
1✔
581
    def sections(self):
1✔
582
        """
583
        :class:`list`: A list of :class:`elftools.elf.sections.Section` objects
584
            for the segments in the ELF.
585
        """
586
        return list(self.iter_sections())
1✔
587

588
    @property
1✔
589
    def dwarf(self):
1✔
590
        """DWARF info for the elf"""
591
        return self.get_dwarf_info()
×
592

593
    @property
1✔
594
    def sym(self):
1✔
595
        """:class:`dotdict`: Alias for :attr:`.ELF.symbols`"""
596
        return self.symbols
1✔
597

598
    @property
1✔
599
    def address(self):
1✔
600
        """:class:`int`: Address of the lowest segment loaded in the ELF.
601

602
        When updated, the addresses of the following fields are also updated:
603

604
        - :attr:`~.ELF.symbols`
605
        - :attr:`~.ELF.got`
606
        - :attr:`~.ELF.plt`
607
        - :attr:`~.ELF.functions`
608

609
        However, the following fields are **NOT** updated:
610

611
        - :attr:`~.ELF.segments`
612
        - :attr:`~.ELF.sections`
613

614
        Example:
615

616
            >>> bash = ELF('/bin/bash')
617
            >>> read = bash.symbols['read']
618
            >>> text = bash.get_section_by_name('.text').header.sh_addr
619
            >>> bash.address += 0x1000
620
            >>> read + 0x1000 == bash.symbols['read']
621
            True
622
            >>> text == bash.get_section_by_name('.text').header.sh_addr
623
            True
624
        """
625
        return self._address
1✔
626

627
    @address.setter
1✔
628
    def address(self, new):
1✔
629
        delta     = new-self._address
1✔
630
        update    = lambda x: x+delta
1✔
631

632
        self.symbols = dotdict({k:update(v) for k,v in self.symbols.items()})
1✔
633
        self.plt     = dotdict({k:update(v) for k,v in self.plt.items()})
1✔
634
        self.got     = dotdict({k:update(v) for k,v in self.got.items()})
1✔
635
        for f in self.functions.values():
1✔
636
            f.address += delta
1✔
637

638
        # Update our view of memory
639
        memory = intervaltree.IntervalTree()
1✔
640

641
        for begin, end, data in self.memory:
1✔
642
            memory.addi(update(begin),
1✔
643
                        update(end),
644
                        data)
645

646
        self.memory = memory
1✔
647

648
        self._address = update(self.address)
1✔
649

650
    def section(self, name):
1✔
651
        """section(name) -> bytes
652

653
        Gets data for the named section
654

655
        Arguments:
656
            name(str): Name of the section
657

658
        Returns:
659
            :class:`str`: String containing the bytes for that section
660
        """
661
        return self.get_section_by_name(name).data()
×
662

663
    @property
1✔
664
    def rwx_segments(self):
1✔
665
        """:class:`list`: List of all segments which are writeable and executable.
666

667
        See:
668
            :attr:`.ELF.segments`
669
        """
670
        if not self.nx:
1✔
671
            return self.writable_segments
1✔
672

673
        wx = P_FLAGS.PF_X | P_FLAGS.PF_W
1✔
674
        return [s for s in self.segments if s.header.p_flags & wx == wx]
1✔
675

676
    @property
1✔
677
    def executable_segments(self):
1✔
678
        """:class:`list`: List of all segments which are executable.
679

680
        See:
681
            :attr:`.ELF.segments`
682
        """
683
        if not self.nx:
1!
684
            return list(self.segments)
1✔
685

686
        return [s for s in self.segments if s.header.p_flags & P_FLAGS.PF_X]
×
687

688
    @property
1✔
689
    def writable_segments(self):
1✔
690
        """:class:`list`: List of all segments which are writeable.
691

692
        See:
693
            :attr:`.ELF.segments`
694
        """
695
        return [s for s in self.segments if s.header.p_flags & P_FLAGS.PF_W]
1✔
696

697
    @property
1✔
698
    def non_writable_segments(self):
1✔
699
        """:class:`list`: List of all segments which are NOT writeable.
700

701
        See:
702
            :attr:`.ELF.segments`
703
        """
704
        return [s for s in self.segments if not s.header.p_flags & P_FLAGS.PF_W]
×
705

706
    @property
1✔
707
    def libs(self):
1✔
708
        """Dictionary of {path: address} for every library loaded for this ELF."""
709
        if self._libs is None:
1✔
710
            self._populate_libraries()
1✔
711
        return self._libs
1✔
712

713
    @property
1✔
714
    def maps(self):
1✔
715
        """Dictionary of {name: address} for every mapping in this ELF's address space."""
716
        if self._maps is None:
×
717
            self._populate_libraries()
×
718
        return self._maps
×
719

720
    @property
1✔
721
    def libc(self):
1✔
722
        """:class:`.ELF`: If this :class:`.ELF` imports any libraries which contain ``'libc[.-]``,
723
        and we can determine the appropriate path to it on the local
724
        system, returns a new :class:`.ELF` object pertaining to that library.
725

726
        If not found, the value will be :const:`None`.
727
        """
728
        for lib in self.libs:
1!
729
            if '/libc.' in lib or '/libc-' in lib:
1✔
730
                return ELF(lib)
1✔
731

732
    def _populate_libraries(self):
1✔
733
        """
734
        >>> from os.path import exists
735
        >>> bash = ELF(which('bash'))
736
        >>> all(map(exists, bash.libs.keys()))
737
        True
738
        >>> any(map(lambda x: 'libc' in x, bash.libs.keys()))
739
        True
740
        """
741
        # Patch some shellcode into the ELF and run it.
742
        maps = self._patch_elf_and_read_maps()
1✔
743

744
        self._maps = maps
1✔
745
        self._libs = {}
1✔
746

747
        for lib, address in maps.items():
1✔
748

749
            # Filter out [stack] and such from the library listings
750
            if lib.startswith('['):
1✔
751
                continue
1✔
752

753
            # Any existing files we can just use
754
            if os.path.exists(lib):
1!
755
                self._libs[lib] = address
1✔
756

757
            # Try etc/qemu-binfmt, as per Ubuntu
758
            if not self.native:
1!
759
                ld_prefix = qemu.ld_prefix()
×
760

761
                qemu_lib = os.path.join(ld_prefix, lib)
×
762
                qemu_lib = os.path.realpath(qemu_lib)
×
763

764
                if os.path.exists(qemu_lib):
×
765
                    self._libs[qemu_lib] = address
×
766

767
    def _patch_elf_and_read_maps(self):
1✔
768
        r"""patch_elf_and_read_maps(self) -> dict
769

770
        Read ``/proc/self/maps`` as if the ELF were executing.
771

772
        This is done by replacing the code at the entry point with shellcode which
773
        dumps ``/proc/self/maps`` and exits, and **actually executing the binary**.
774

775
        Returns:
776
            A ``dict`` mapping file paths to the lowest address they appear at.
777
            Does not do any translation for e.g. QEMU emulation, the raw results
778
            are returned.
779

780
            If there is not enough space to inject the shellcode in the segment
781
            which contains the entry point, returns ``{}``.
782

783
        Doctests:
784

785
            These tests are just to ensure that our shellcode is correct.
786

787
            >>> for arch in CAT_PROC_MAPS_EXIT:
788
            ...   context.clear()
789
            ...   with context.local(arch=arch):
790
            ...     sc = shellcraft.cat2("/proc/self/maps")
791
            ...     sc += shellcraft.exit()
792
            ...     sc = asm(sc)
793
            ...     sc = enhex(sc)
794
            ...     assert sc == CAT_PROC_MAPS_EXIT[arch], (arch, sc)
795
        """
796

797
        # Get our shellcode
798
        sc = CAT_PROC_MAPS_EXIT.get(self.arch, None)
1✔
799

800
        if sc is None:
1!
801
            log.error("Cannot patch /proc/self/maps shellcode into %r binary", self.arch)
×
802

803
        sc = unhex(sc)
1✔
804

805
        # Ensure there is enough room in the segment where the entry point resides
806
        # in order to inject our shellcode.
807
        seg = self.get_segment_for_address(self.entry, len(sc))
1✔
808
        if not seg:
1!
809
            log.warn_once("Could not inject code to determine memory mapping for %r: Not enough space", self)
×
810
            return {}
×
811

812
        # Create our temporary file
813
        # NOTE: We cannot use "with NamedTemporaryFile() as foo", because we cannot
814
        # execute the file while the handle is open.
815
        fd, path = tempfile.mkstemp()
1✔
816

817
        # Close the file descriptor so that it may be executed
818
        os.close(fd)
1✔
819

820
        # Save off a copy of the ELF
821
        self.save(path)
1✔
822

823
        # Load a new copy of the ELF at the temporary file location
824
        old = self.read(self.entry, len(sc))
1✔
825
        try:
1✔
826
            self.write(self.entry, sc)
1✔
827
            self.save(path)
1✔
828
        finally:
829
            # Restore the original contents
830
            self.write(self.entry, old)
1✔
831

832
        # Make the file executable
833
        os.chmod(path, 0o755)
1✔
834

835
        # Run a copy of it, get the maps
836
        try:
1✔
837
            with context.silent:
1✔
838
                io = process(path)
1✔
839
                data = packing._decode(io.recvall(timeout=2))
1✔
840
        except Exception:
×
841
            log.warn_once("Injected /proc/self/maps code did not execute correctly")
×
842
            return {}
×
843

844
        # Swap in the original ELF name
845
        data = data.replace(path, self.path)
1✔
846

847
        # All we care about in the data is the load address of each file-backed mapping,
848
        # or each kernel-supplied mapping.
849
        #
850
        # For quick reference, the data looks like this:
851
        # 7fcb025f2000-7fcb025f3000 r--p 00025000 fe:01 3025685  /lib/x86_64-linux-gnu/ld-2.23.so
852
        # 7fcb025f3000-7fcb025f4000 rw-p 00026000 fe:01 3025685  /lib/x86_64-linux-gnu/ld-2.23.so
853
        # 7fcb025f4000-7fcb025f5000 rw-p 00000000 00:00 0
854
        # 7ffe39cd4000-7ffe39cf6000 rw-p 00000000 00:00 0        [stack]
855
        # 7ffe39d05000-7ffe39d07000 r--p 00000000 00:00 0        [vvar]
856
        result = {}
1✔
857
        for line in data.splitlines():
1✔
858
            if '/' in line:
1✔
859
                index = line.index('/')
1✔
860
            elif '[' in line:
1✔
861
                index = line.index('[')
1✔
862
            else:
863
                continue
1✔
864

865
            address, _ = line.split('-', 1)
1✔
866

867
            address = int(address, 0x10)
1✔
868
            name = line[index:]
1✔
869

870
            result.setdefault(name, address)
1✔
871

872
        # Remove the temporary file, best-effort
873
        os.unlink(path)
1✔
874

875
        return result
1✔
876

877
    def _populate_functions(self):
1✔
878
        """Builds a dict of 'functions' (i.e. symbols of type 'STT_FUNC')
879
        by function name that map to a tuple consisting of the func address and size
880
        in bytes.
881
        """
882
        for sec in self.sections:
1✔
883
            if not isinstance(sec, SymbolTableSection):
1✔
884
                continue
1✔
885

886
            for sym in _iter_symbols(sec):
1✔
887
                # Avoid duplicates
888
                if sym.name in self.functions:
1✔
889
                    continue
1✔
890
                if sym.entry.st_info['type'] == 'STT_FUNC' and sym.entry.st_size != 0:
1✔
891
                    name = sym.name
1✔
892
                    if name not in self.symbols:
1!
893
                        continue
×
894
                    addr = self.symbols[name]
1✔
895
                    size = sym.entry.st_size
1✔
896
                    self.functions[name] = Function(name, addr, size, self)
1✔
897

898
    def _populate_symbols(self):
1✔
899
        """
900
        >>> bash = ELF(which('bash'))
901
        >>> bash.symbols['_start'] == bash.entry
902
        True
903
        """
904

905
        # Populate all of the "normal" symbols from the symbol tables
906
        for section in self.sections:
1✔
907
            if not isinstance(section, SymbolTableSection):
1✔
908
                continue
1✔
909

910
            for symbol in _iter_symbols(section):
1✔
911
                value = symbol.entry.st_value
1✔
912
                if not value:
1✔
913
                    continue
1✔
914
                self.symbols[symbol.name] = value
1✔
915

916
    def _populate_synthetic_symbols(self):
1✔
917
        """Adds symbols from the GOT and PLT to the symbols dictionary.
918

919
        Does not overwrite any existing symbols, and prefers PLT symbols.
920

921
        Synthetic plt.xxx and got.xxx symbols are added for each PLT and
922
        GOT entry, respectively.
923

924
        Example:bash.
925

926
            >>> bash = ELF(which('bash'))
927
            >>> bash.symbols.wcscmp == bash.plt.wcscmp
928
            True
929
            >>> bash.symbols.wcscmp == bash.symbols.plt.wcscmp
930
            True
931
            >>> bash.symbols.stdin  == bash.got.stdin
932
            True
933
            >>> bash.symbols.stdin  == bash.symbols.got.stdin
934
            True
935
        """
936
        for symbol, address in self.plt.items():
1✔
937
            self.symbols.setdefault(symbol, address)
1✔
938
            self.symbols['plt.' + symbol] = address
1✔
939

940
        for symbol, address in self.got.items():
1✔
941
            self.symbols.setdefault(symbol, address)
1✔
942
            self.symbols['got.' + symbol] = address
1✔
943

944
    def _populate_got(self):
1✔
945
        """Loads the symbols for all relocations.
946

947
            >>> libc = ELF(which('bash')).libc
948
            >>> assert 'strchrnul' in libc.got
949
            >>> assert 'memcpy' in libc.got
950
            >>> assert libc.got.strchrnul != libc.got.memcpy
951
        """
952
        # Statically linked implies no relocations, since there is no linker
953
        # Could always be self-relocating like Android's linker *shrug*
954
        if self.statically_linked:
1✔
955
            return
1✔
956

957
        revsymbols = defaultdict(list)
1✔
958
        for name, addr in self.symbols.items():
1✔
959
            revsymbols[addr].append(name)
1✔
960

961
        for section in self.sections:
1✔
962
            # We are only interested in relocations
963
            if not isinstance(section, (RelocationSection, RelrRelocationSection)):
1✔
964
                continue
1✔
965

966
            # Only get relocations which link to another section (for symbols)
967
            if section.header.sh_link == SHN_INDICES.SHN_UNDEF:
1✔
968
                continue
1✔
969

970
            symbols = self.get_section(section.header.sh_link)
1✔
971

972
            for rel in section.iter_relocations():
1✔
973
                sym_idx  = rel.entry.r_info_sym
1✔
974

975
                if not sym_idx and rel.is_RELA():
1✔
976
                    # TODO: actually resolve relocations
977
                    relocated = rel.entry.r_addend  # sufficient for now
1✔
978

979
                    symnames = revsymbols[relocated]
1✔
980
                    for symname in symnames:
1✔
981
                        self.got[symname] = rel.entry.r_offset
1✔
982
                    continue
1✔
983

984
                symbol = symbols.get_symbol(sym_idx)
1✔
985

986
                if symbol and symbol.name:
1✔
987
                    self.got[symbol.name] = rel.entry.r_offset
1✔
988

989
        if self.arch == 'mips':
1✔
990
            try:
1✔
991
                self._populate_mips_got()
1✔
992
            except Exception as e:
×
993
                log.warn("Could not populate MIPS GOT: %s", e)
×
994

995
        if not self.got:
1✔
996
            log.warn("Did not find any GOT entries")
1✔
997

998
    def _populate_mips_got(self):
1✔
999
        self._mips_got = {}
1✔
1000
        strings = self.get_section(self.header.e_shstrndx)
1✔
1001

1002
        ELF_MIPS_GNU_GOT1_MASK = 0x80000000
1✔
1003

1004
        if self.bits == 64:
1!
1005
            ELF_MIPS_GNU_GOT1_MASK <<= 32
×
1006

1007
        # Beginning of the GOT
1008
        got = self.dynamic_value_by_tag('DT_PLTGOT') or 0
1✔
1009

1010
        # Find the beginning of the GOT pointers
1011
        got1_mask = (self.unpack(got) & ELF_MIPS_GNU_GOT1_MASK)
1✔
1012
        i = 2 if got1_mask else 1
1✔
1013
        self._mips_skip = i
1✔
1014

1015
        # We don't care about local GOT entries, skip them
1016
        local_gotno = self.dynamic_value_by_tag('DT_MIPS_LOCAL_GOTNO')
1✔
1017
        got += local_gotno * context.bytes
1✔
1018

1019
        # Iterate over the dynamic symbol table
1020
        dynsym = self.get_section_by_name('.dynsym')
1✔
1021
        symbol_iter = _iter_symbols(dynsym)
1✔
1022

1023
        # 'gotsym' is the index of the first GOT symbol
1024
        gotsym = self.dynamic_value_by_tag('DT_MIPS_GOTSYM')
1✔
1025
        for i in range(gotsym):
1✔
1026
            next(symbol_iter)
1✔
1027

1028
        # 'symtabno' is the total number of symbols
1029
        symtabno = self.dynamic_value_by_tag('DT_MIPS_SYMTABNO')
1✔
1030

1031
        for i in range(symtabno - gotsym):
1✔
1032
            symbol = next(symbol_iter)
1✔
1033
            self._mips_got[i + gotsym] = got
1✔
1034
            self.got[symbol.name] = got
1✔
1035
            got += self.bytes
1✔
1036

1037
    def _populate_plt(self):
1✔
1038
        """Loads the PLT symbols
1039

1040
        >>> path = pwnlib.data.elf.path
1041
        >>> for test in glob(os.path.join(path, 'test-*')):
1042
        ...     test = ELF(test)
1043
        ...     assert '__stack_chk_fail' in test.got, test
1044
        ...     if test.arch != 'ppc':
1045
        ...         assert '__stack_chk_fail' in test.plt, test
1046
        """
1047
        if self.statically_linked:
1✔
1048
            log.debug("%r is statically linked, skipping GOT/PLT symbols" % self.path)
1✔
1049
            return
1✔
1050

1051
        if not self.got:
1✔
1052
            log.debug("%r doesn't have any GOT symbols, skipping PLT" % self.path)
1✔
1053
            return
1✔
1054

1055
        # This element holds an address associated with the procedure linkage table
1056
        # and/or the global offset table.
1057
        #
1058
        # Zach's note: This corresponds to the ".got.plt" section, in a PIE non-RELRO binary.
1059
        #              This corresponds to the ".got" section, in a PIE full-RELRO binary.
1060
        #              In particular, this is where EBX points when it points into the GOT.
1061
        dt_pltgot = self.dynamic_value_by_tag('DT_PLTGOT') or 0
1✔
1062

1063
        # There are three PLTs we may need to search
1064
        plt = self.get_section_by_name('.plt')          # <-- Functions only
1✔
1065
        plt_got = self.get_section_by_name('.plt.got')  # <-- Functions used as data
1✔
1066
        plt_sec = self.get_section_by_name('.plt.sec')
1✔
1067
        plt_mips = self.get_section_by_name('.MIPS.stubs')
1✔
1068

1069
        # Invert the GOT symbols we already have, so we can look up by address
1070
        inv_symbols = {v:k for k,v in self.got.items()}
1✔
1071
        inv_symbols.update({v:k for k,v in self.symbols.items()})
1✔
1072

1073
        with context.local(arch=self.arch, bits=self.bits, endian=self.endian):
1✔
1074
            for section in (plt, plt_got, plt_sec, plt_mips):
1✔
1075
                if not section:
1✔
1076
                    continue
1✔
1077

1078
                res = emulate_plt_instructions(self,
1✔
1079
                                                dt_pltgot,
1080
                                                section.header.sh_addr,
1081
                                                section.data(),
1082
                                                inv_symbols)
1083

1084
                for address, target in sorted(res.items()):
1✔
1085
                    self.plt[inv_symbols[target]] = address
1✔
1086

1087
        # for a,n in sorted({v:k for k,v in self.plt.items()}.items()):
1088
            # log.debug('PLT %#x %s', a, n)
1089

1090
    def _populate_kernel_version(self):
1✔
1091
        if 'linux_banner' not in self.symbols:
1!
1092
            return
1✔
1093

1094
        banner = self.string(self.symbols.linux_banner)
×
1095

1096
        # convert banner into a utf-8 string since re.search does not accept bytes anymore
1097
        banner = banner.decode('utf-8')
×
1098

1099
        # 'Linux version 3.18.31-gd0846ecc
1100
        regex = r'Linux version (\S+)'
×
1101
        match = re.search(regex, banner)
×
1102

1103
        if match:
×
1104
            version = match.group(1)
×
1105

1106
            if '-' in version:
×
1107
                version, self.build = version.split('-', 1)
×
1108

1109
            self.version = list(map(int, version.rstrip('+').split('.')))
×
1110

1111
        self.config['version'] = self.version
×
1112

1113
    @property
1✔
1114
    def libc_start_main_return(self):
1✔
1115
        """:class:`int`: Address of the return address into __libc_start_main from main.
1116

1117
        >>> bash = ELF(which('bash'))
1118
        >>> libc = bash.libc
1119
        >>> libc.libc_start_main_return > 0
1120
        True
1121

1122
        Try to find the return address from main into __libc_start_main.
1123
        The heuristic to find the call to the function pointer of main is
1124
        to list all calls inside __libc_start_main, find the call to exit
1125
        after the call to main and select the previous call.
1126
        """
1127
        if '__libc_start_main' not in self.functions:
1!
1128
            return 0
×
1129

1130
        if 'exit' not in self.symbols:
1!
1131
            return 0
×
1132

1133
        # If there's no delay slot, execution continues on the next instruction after a call.
1134
        call_return_offset = 1
1✔
1135
        if self.arch in ['arm', 'thumb']:
1!
1136
            call_instructions = set(['blx', 'bl'])
×
1137
        elif self.arch == 'aarch64':
1!
1138
            call_instructions = set(['blr', 'bl'])
×
1139
        elif self.arch in ['mips', 'mips64']:
1!
1140
            call_instructions = set(['bal', 'jalr'])
×
1141
            # Account for the delay slot.
1142
            call_return_offset = 2
×
1143
        elif self.arch in ['i386', 'amd64', 'ia64']:
1!
1144
            call_instructions = set(['call'])
1✔
1145
        else:
1146
            log.error('Unsupported architecture %s in ELF.libc_start_main_return', self.arch)
×
1147
            return 0
×
1148

1149
        lines = self.functions['__libc_start_main'].disasm().split('\n')
1✔
1150
        exit_addr = hex(self.symbols['exit'])
1✔
1151
        calls = [(index, line) for index, line in enumerate(lines) if set(line.split()) & call_instructions]
1✔
1152

1153
        def find_ret_main_addr(lines, calls):
1✔
1154
            exit_calls = [index for index, line in enumerate(calls) if exit_addr in line[1]]
1✔
1155
            if len(exit_calls) != 1:
1✔
1156
                return 0
1✔
1157

1158
            call_to_main = calls[exit_calls[0] - 1]
1✔
1159
            return_from_main = lines[call_to_main[0] + call_return_offset].lstrip()
1✔
1160
            return_from_main = int(return_from_main[ : return_from_main.index(':') ], 16)
1✔
1161
            return return_from_main
1✔
1162

1163
        # Starting with glibc-2.34 calling `main` is split out into `__libc_start_call_main`
1164
        ret_addr = find_ret_main_addr(lines, calls)
1✔
1165
        # Pre glibc-2.34 case - `main` is called directly
1166
        if ret_addr:
1!
1167
            return ret_addr
×
1168

1169
        # `__libc_start_main` -> `__libc_start_call_main` -> `main`
1170
        # Find a direct call which calls `exit` once. That's probably `__libc_start_call_main`.
1171
        direct_call_pattern = re.compile(r'['+r'|'.join(call_instructions)+r']\s+(0x[0-9a-zA-Z]+)')
1✔
1172
        for line in calls:
1!
1173
            match = direct_call_pattern.search(line[1])
1✔
1174
            if not match:
1✔
1175
                continue
1✔
1176

1177
            target_addr = int(match.group(1), 0)
1✔
1178
            # `__libc_start_call_main` is usually smaller than `__libc_start_main`, so
1179
            # we might disassemble a bit too much, but it's a good dynamic estimate.
1180
            callee_lines = self.disasm(target_addr, self.functions['__libc_start_main'].size).split('\n')
1✔
1181
            callee_calls = [(index, line) for index, line in enumerate(callee_lines) if set(line.split()) & call_instructions]
1✔
1182
            ret_addr = find_ret_main_addr(callee_lines, callee_calls)
1✔
1183
            if ret_addr:
1✔
1184
                return ret_addr
1✔
1185
        return 0
×
1186

1187
    def search(self, needle, writable = False, executable = False):
1✔
1188
        """search(needle, writable = False, executable = False) -> generator
1189

1190
        Search the ELF's virtual address space for the specified string.
1191

1192
        Notes:
1193
            Does not search empty space between segments, or uninitialized
1194
            data.  This will only return data that actually exists in the
1195
            ELF file.  Searching for a long string of NULL bytes probably
1196
            won't work.
1197

1198
        Arguments:
1199
            needle(bytes): String to search for.
1200
            writable(bool): Search only writable sections.
1201
            executable(bool): Search only executable sections.
1202

1203
        Yields:
1204
            An iterator for each virtual address that matches.
1205

1206
        Examples:
1207

1208
            An ELF header starts with the bytes ``\\x7fELF``, so we
1209
            sould be able to find it easily.
1210

1211
            >>> bash = ELF('/bin/bash')
1212
            >>> bash.address + 1 == next(bash.search(b'ELF'))
1213
            True
1214

1215
            We can also search for string the binary.
1216

1217
            >>> len(list(bash.search(b'GNU bash'))) > 0
1218
            True
1219

1220
            It is also possible to search for instructions in executable sections.
1221

1222
            >>> binary = ELF.from_assembly('nop; mov eax, 0; jmp esp; ret')
1223
            >>> jmp_addr = next(binary.search(asm('jmp esp'), executable = True))
1224
            >>> binary.read(jmp_addr, 2) == asm('jmp esp')
1225
            True
1226
        """
1227
        load_address_fixup = (self.address - self.load_addr)
1✔
1228

1229
        if writable:
1!
1230
            segments = self.writable_segments
×
1231
        elif executable:
1✔
1232
            segments = self.executable_segments
1✔
1233
        else:
1234
            segments = self.segments
1✔
1235
        needle = packing._need_bytes(needle)
1✔
1236
        for seg in segments:
1✔
1237
            addr   = seg.header.p_vaddr
1✔
1238
            memsz  = seg.header.p_memsz
1✔
1239
            filesz = seg.header.p_filesz
1✔
1240
            zeroed = memsz - filesz
1✔
1241
            offset = seg.header.p_offset
1✔
1242
            data   = self.mmap[offset:offset+filesz]
1✔
1243
            data   += b'\x00' * zeroed
1✔
1244
            offset = 0
1✔
1245
            while True:
1✔
1246
                offset = data.find(needle, offset)
1✔
1247
                if offset == -1:
1✔
1248
                    break
1✔
1249
                yield (addr + offset + load_address_fixup)
1✔
1250
                offset += 1
1✔
1251

1252
    def offset_to_vaddr(self, offset):
1✔
1253
        """offset_to_vaddr(offset) -> int
1254

1255
        Translates the specified offset to a virtual address.
1256

1257
        Arguments:
1258
            offset(int): Offset to translate
1259

1260
        Returns:
1261
            `int`: Virtual address which corresponds to the file offset, or
1262
            :const:`None`.
1263

1264
        Examples:
1265

1266
            This example shows that regardless of changes to the virtual
1267
            address layout by modifying :attr:`.ELF.address`, the offset
1268
            for any given address doesn't change.
1269

1270
            >>> bash = ELF('/bin/bash')
1271
            >>> bash.address == bash.offset_to_vaddr(0)
1272
            True
1273
            >>> bash.address += 0x123456
1274
            >>> bash.address == bash.offset_to_vaddr(0)
1275
            True
1276
        """
1277
        load_address_fixup = (self.address - self.load_addr)
1✔
1278

1279
        for segment in self.segments:
1!
1280
            begin = segment.header.p_offset
1✔
1281
            size  = segment.header.p_filesz
1✔
1282
            end   = begin + size
1✔
1283
            if begin <= offset and offset <= end:
1✔
1284
                delta = offset - begin
1✔
1285
                return segment.header.p_vaddr + delta + load_address_fixup
1✔
1286
        return None
×
1287

1288
    def _populate_memory(self):
1✔
1289
        load_segments = list(filter(lambda s: s.header.p_type == 'PT_LOAD', self.iter_segments()))
1✔
1290

1291
        # Map all of the segments
1292
        for i, segment in enumerate(load_segments):
1✔
1293
            start = segment.header.p_vaddr
1✔
1294
            stop_data = start + segment.header.p_filesz
1✔
1295
            stop_mem  = start + segment.header.p_memsz
1✔
1296

1297
            # Chop any existing segments which cover the range described by
1298
            # [vaddr, vaddr+filesz].
1299
            #
1300
            # This has the effect of removing any issues we may encounter
1301
            # with "overlapping" segments, by giving precedence to whichever
1302
            # DT_LOAD segment is **last** to load data into the region.
1303
            self.memory.chop(start, stop_data)
1✔
1304

1305
            # Fill the start of the segment's first page
1306
            page_start = align_down(0x1000, start)
1✔
1307
            if page_start < start and not self.memory[page_start]:
1!
1308
                self.memory.addi(page_start, start, None)
×
1309

1310
            # Add the new segment
1311
            if start != stop_data:
1✔
1312
                self.memory.addi(start, stop_data, segment)
1✔
1313

1314
            if stop_data != stop_mem:
1✔
1315
                self.memory.addi(stop_data, stop_mem, b'\x00')
1✔
1316

1317
            page_end = align(0x1000, stop_mem)
1✔
1318

1319
            # Check for holes which we can fill
1320
            if self._fill_gaps and i+1 < len(load_segments):
1✔
1321
                next_start = load_segments[i+1].header.p_vaddr
1✔
1322
                page_next = align_down(0x1000, next_start)
1✔
1323

1324
                if stop_mem < next_start:
1✔
1325
                    if page_end < page_next:
1✔
1326
                        if stop_mem < page_end:
1!
1327
                            self.memory.addi(stop_mem, page_end, None)
1✔
1328
                        if page_next < next_start:
1✔
1329
                            self.memory.addi(page_next, next_start, None)
1✔
1330
                    else:
1331
                        self.memory.addi(stop_mem, next_start, None)
1✔
1332
            else:
1333
                if stop_mem < page_end:
1✔
1334
                    self.memory.addi(stop_mem, page_end, None)
1✔
1335

1336
    def vaddr_to_offset(self, address):
1✔
1337
        """vaddr_to_offset(address) -> int
1338

1339
        Translates the specified virtual address to a file offset
1340

1341
        Arguments:
1342
            address(int): Virtual address to translate
1343

1344
        Returns:
1345
            int: Offset within the ELF file which corresponds to the address,
1346
            or :const:`None`.
1347

1348
        Examples:
1349
            >>> bash = ELF(which('bash'))
1350
            >>> bash.vaddr_to_offset(bash.address)
1351
            0
1352
            >>> bash.address += 0x123456
1353
            >>> bash.vaddr_to_offset(bash.address)
1354
            0
1355
            >>> bash.vaddr_to_offset(0) is None
1356
            True
1357
        """
1358

1359
        for interval in self.memory[address]:
1✔
1360
            segment = interval.data
1✔
1361

1362
            # Convert the address back to how it was when the segment was loaded
1363
            address = (address - self.address) + self.load_addr
1✔
1364

1365
            # Figure out the offset into the segment
1366
            offset = address - segment.header.p_vaddr
1✔
1367

1368
            # Add the segment-base offset to the offset-within-the-segment
1369
            return segment.header.p_offset + offset
1✔
1370

1371
    def read(self, address, count):
1✔
1372
        r"""read(address, count) -> bytes
1373

1374
        Read data from the specified virtual address
1375

1376
        Arguments:
1377
            address(int): Virtual address to read
1378
            count(int): Number of bytes to read
1379

1380
        Returns:
1381
            A :class:`bytes` object, or :const:`None`.
1382

1383
        Examples:
1384
            The simplest example is just to read the ELF header.
1385

1386
            >>> bash = ELF(which('bash'))
1387
            >>> bash.read(bash.address, 4)
1388
            b'\x7fELF'
1389

1390
            ELF segments do not have to contain all of the data on-disk
1391
            that gets loaded into memory.
1392

1393
            First, let's create an ELF file has some code in two sections.
1394

1395
            >>> assembly = '''
1396
            ... .section .A,"awx"
1397
            ... .global A
1398
            ... A: nop
1399
            ... .section .B,"awx"
1400
            ... .global B
1401
            ... B: int3
1402
            ... '''
1403
            >>> e = ELF.from_assembly(assembly, vma=False)
1404

1405
            By default, these come right after eachother in memory.
1406

1407
            >>> e.read(e.symbols.A, 2)
1408
            b'\x90\xcc'
1409
            >>> e.symbols.B - e.symbols.A
1410
            1
1411

1412
            Let's move the sections so that B is a little bit further away.
1413

1414
            >>> objcopy = pwnlib.asm._objcopy()
1415
            >>> objcopy += [
1416
            ...     '--change-section-vma', '.B+5',
1417
            ...     '--change-section-lma', '.B+5',
1418
            ...     e.path
1419
            ... ]
1420
            >>> subprocess.check_call(objcopy)
1421
            0
1422

1423
            Now let's re-load the ELF, and check again
1424

1425
            >>> e = ELF(e.path)
1426
            >>> e.symbols.B - e.symbols.A
1427
            6
1428
            >>> e.read(e.symbols.A, 2)
1429
            b'\x90\x00'
1430
            >>> e.read(e.symbols.A, 7)
1431
            b'\x90\x00\x00\x00\x00\x00\xcc'
1432
            >>> e.read(e.symbols.A, 10)
1433
            b'\x90\x00\x00\x00\x00\x00\xcc\x00\x00\x00'
1434

1435
            Everything is relative to the user-selected base address, so moving
1436
            things around keeps everything working.
1437

1438
            >>> e.address += 0x1000
1439
            >>> e.read(e.symbols.A, 10)
1440
            b'\x90\x00\x00\x00\x00\x00\xcc\x00\x00\x00'
1441
        """
1442
        retval = []
1✔
1443

1444
        if count == 0:
1!
1445
            return b''
×
1446

1447
        start = address
1✔
1448
        stop = address + count
1✔
1449

1450
        overlap = self.memory.overlap(start, stop)
1✔
1451

1452
        # Create a new view of memory, for just what we need
1453
        memory = intervaltree.IntervalTree(overlap)
1✔
1454
        memory.chop(-1<<64, start)
1✔
1455
        memory.chop(stop, 1<<64)
1✔
1456

1457
        if memory.begin() != start:
1!
1458
            log.error("Address %#x is not contained in %s" % (start, self))
×
1459

1460
        if memory.end() != stop:
1!
1461
            log.error("Address %#x is not contained in %s" % (stop, self))
×
1462

1463
        # We have a view of memory which lets us get everything we need
1464
        for begin, end, data in sorted(memory):
1✔
1465
            length = end-begin
1✔
1466

1467
            if data in (None, b'\x00'):
1✔
1468
                retval.append(b'\x00' * length)
1✔
1469
                continue
1✔
1470

1471
            # Offset within VMA range
1472
            begin -= self.address
1✔
1473

1474
            # Adjust to original VMA range
1475
            begin += self.load_addr
1✔
1476

1477
            # Adjust to offset within segment VMA
1478
            offset = begin - data.header.p_vaddr
1✔
1479

1480
            # Adjust in-segment offset to in-file offset
1481
            offset += data.header.p_offset
1✔
1482

1483
            retval.append(self.mmap[offset:offset+length])
1✔
1484

1485
        return b''.join(retval)
1✔
1486

1487
    def write(self, address, data):
1✔
1488
        """Writes data to the specified virtual address
1489

1490
        Arguments:
1491
            address(int): Virtual address to write
1492
            data(str): Bytes to write
1493

1494
        Note:
1495
            This routine does not check the bounds on the write to ensure
1496
            that it stays in the same segment.
1497

1498
        Examples:
1499
          >>> bash = ELF(which('bash'))
1500
          >>> bash.read(bash.address+1, 3)
1501
          b'ELF'
1502
          >>> bash.write(bash.address, b"HELO")
1503
          >>> bash.read(bash.address, 4)
1504
          b'HELO'
1505
        """
1506
        offset = self.vaddr_to_offset(address)
1✔
1507

1508
        if offset is not None:
1!
1509
            length = len(data)
1✔
1510
            self.mmap[offset:offset+length] = data
1✔
1511

1512
        return None
1✔
1513

1514
    def save(self, path=None):
1✔
1515
        """Save the ELF to a file
1516

1517
        >>> bash = ELF(which('bash'))
1518
        >>> bash.save('/tmp/bash_copy')
1519
        >>> copy = open('/tmp/bash_copy', 'rb')
1520
        >>> bash = open(which('bash'), 'rb')
1521
        >>> bash.read() == copy.read()
1522
        True
1523
        """
1524
        if path is None:
1✔
1525
            path = self.path
1✔
1526
        misc.write(path, self.data)
1✔
1527

1528
    def get_data(self):
1✔
1529
        """get_data() -> bytes
1530

1531
        Retrieve the raw data from the ELF file.
1532

1533
        >>> bash = ELF(which('bash'))
1534
        >>> fd   = open(which('bash'), 'rb')
1535
        >>> bash.get_data() == fd.read()
1536
        True
1537
        """
1538
        return self.mmap[:]
1✔
1539

1540
    @property
1✔
1541
    def data(self):
1✔
1542
        """:class:`bytes`: Raw data of the ELF file.
1543

1544
        See:
1545
            :meth:`get_data`
1546
        """
1547
        return self.mmap[:]
1✔
1548

1549
    def disasm(self, address, n_bytes):
1✔
1550
        """disasm(address, n_bytes) -> str
1551

1552
        Returns a string of disassembled instructions at
1553
        the specified virtual memory address"""
1554
        arch = self.arch
1✔
1555
        if self.arch == 'arm' and address & 1:
1!
1556
            arch = 'thumb'
×
1557
            address -= 1
×
1558

1559
        return disasm(self.read(address, n_bytes), vma=address, arch=arch, endian=self.endian)
1✔
1560

1561
    def asm(self, address, assembly):
1✔
1562
        """asm(address, assembly)
1563

1564
        Assembles the specified instructions and inserts them
1565
        into the ELF at the specified address.
1566

1567
        This modifies the ELF in-place.
1568
        The resulting binary can be saved with :meth:`.ELF.save`
1569
        """
1570
        binary = asm(assembly, vma=address, arch=self.arch, endian=self.endian, bits=self.bits)
1✔
1571
        self.write(address, binary)
1✔
1572

1573
    def bss(self, offset=0):
1✔
1574
        """bss(offset=0) -> int
1575

1576
        Returns:
1577
            Address of the ``.bss`` section, plus the specified offset.
1578
        """
1579
        orig_bss = self.get_section_by_name('.bss').header.sh_addr
×
1580
        curr_bss = orig_bss - self.load_addr + self.address
×
1581
        return curr_bss + offset
×
1582

1583
    def __repr__(self):
1✔
1584
        return "%s(%r)" % (self.__class__.__name__, self.path)
1✔
1585

1586
    def dynamic_by_tag(self, tag):
1✔
1587
        """dynamic_by_tag(tag) -> tag
1588

1589
        Arguments:
1590
            tag(str): Named ``DT_XXX`` tag (e.g. ``'DT_STRTAB'``).
1591

1592
        Returns:
1593
            :class:`elftools.elf.dynamic.DynamicTag`
1594
        """
1595
        dt      = None
1✔
1596
        dynamic = self.get_section_by_name('.dynamic')
1✔
1597

1598
        if not dynamic:
1✔
1599
            return None
1✔
1600

1601
        try:
1✔
1602
            dt = next(t for t in dynamic.iter_tags() if tag == t.entry.d_tag)
1✔
1603
        except StopIteration:
1✔
1604
            pass
1✔
1605

1606
        return dt
1✔
1607

1608
    def dynamic_value_by_tag(self, tag):
1✔
1609
        """dynamic_value_by_tag(tag) -> int
1610

1611
        Retrieve the value from a dynamic tag a la ``DT_XXX``.
1612

1613
        If the tag is missing, returns ``None``.
1614
        """
1615
        tag = self.dynamic_by_tag(tag)
1✔
1616

1617
        if tag:
1✔
1618
            return tag.entry.d_val
1✔
1619

1620
    def dynamic_string(self, offset):
1✔
1621
        """dynamic_string(offset) -> bytes
1622

1623
        Fetches an enumerated string from the ``DT_STRTAB`` table.
1624

1625
        Arguments:
1626
            offset(int): String index
1627

1628
        Returns:
1629
            :class:`str`: String from the table as raw bytes.
1630
        """
1631
        dt_strtab = self.dynamic_by_tag('DT_STRTAB')
1✔
1632

1633
        if not dt_strtab:
1!
1634
            return None
×
1635

1636
        address   = dt_strtab.entry.d_ptr + offset
1✔
1637
        string    = b''
1✔
1638
        while b'\x00' not in string:
1✔
1639
            string  += self.read(address, 1)
1✔
1640
            address += 1
1✔
1641
        return string.rstrip(b'\x00')
1✔
1642

1643

1644

1645
    @property
1✔
1646
    def relro(self):
1✔
1647
        """:class:`bool`: Whether the current binary uses RELRO protections.
1648

1649
        This requires both presence of the dynamic tag ``DT_BIND_NOW``, and
1650
        a ``GNU_RELRO`` program header.
1651

1652
        The `ELF Specification`_ describes how the linker should resolve
1653
        symbols immediately, as soon as a binary is loaded.  This can be
1654
        emulated with the ``LD_BIND_NOW=1`` environment variable.
1655

1656
            ``DT_BIND_NOW``
1657

1658
            If present in a shared object or executable, this entry instructs
1659
            the dynamic linker to process all relocations for the object
1660
            containing this entry before transferring control to the program.
1661
            The presence of this entry takes precedence over a directive to use
1662
            lazy binding for this object when specified through the environment
1663
            or via ``dlopen(BA_LIB)``.
1664

1665
            (`page 81`_)
1666

1667
        Separately, an extension to the GNU linker allows a binary to specify
1668
        a PT_GNU_RELRO_ program header, which describes the *region of memory
1669
        which is to be made read-only after relocations are complete.*
1670

1671
        Finally, a new-ish extension which doesn't seem to have a canonical
1672
        source of documentation is DF_BIND_NOW_, which has supposedly superceded
1673
        ``DT_BIND_NOW``.
1674

1675
            ``DF_BIND_NOW``
1676

1677
            If set in a shared object or executable, this flag instructs the
1678
            dynamic linker to process all relocations for the object containing
1679
            this entry before transferring control to the program. The presence
1680
            of this entry takes precedence over a directive to use lazy binding
1681
            for this object when specified through the environment or via
1682
            ``dlopen(BA_LIB)``.
1683

1684
        .. _ELF Specification: https://refspecs.linuxbase.org/elf/elf.pdf
1685
        .. _page 81: https://refspecs.linuxbase.org/elf/elf.pdf#page=81
1686
        .. _DT_BIND_NOW: https://refspecs.linuxbase.org/elf/elf.pdf#page=81
1687
        .. _PT_GNU_RELRO: https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic.html#PROGHEADER
1688
        .. _DF_BIND_NOW: https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html#df_bind_now
1689

1690
        >>> path = pwnlib.data.elf.relro.path
1691
        >>> for test in glob(os.path.join(path, 'test-*')):
1692
        ...     e = ELF(test)
1693
        ...     expected = os.path.basename(test).split('-')[2]
1694
        ...     actual = str(e.relro).lower()
1695
        ...     assert actual == expected
1696
        """
1697
        if not any('GNU_RELRO' in str(s.header.p_type) for s in self.segments):
1✔
1698
            return None
1✔
1699

1700
        if self.dynamic_by_tag('DT_BIND_NOW'):
1✔
1701
            return "Full"
1✔
1702

1703
        flags = self.dynamic_value_by_tag('DT_FLAGS')
1✔
1704
        if flags and flags & constants.DF_BIND_NOW:
1✔
1705
            return "Full"
1✔
1706

1707
        flags_1 = self.dynamic_value_by_tag('DT_FLAGS_1')
1✔
1708
        if flags_1 and flags_1 & constants.DF_1_NOW:
1!
1709
            return "Full"
×
1710

1711
        return "Partial"
1✔
1712

1713
    @property
1✔
1714
    def nx(self):
1✔
1715
        """:class:`bool`: Whether the current binary uses NX protections.
1716

1717
        Specifically, we are checking for ``READ_IMPLIES_EXEC`` being set
1718
        by the kernel, as a result of honoring ``PT_GNU_STACK`` in the kernel.
1719

1720
        ``READ_IMPLIES_EXEC`` is set, according to a set of architecture specific
1721
        rules, that depend on the CPU features, and the presence of ``PT_GNU_STACK``.
1722

1723
        Unfortunately, :class:`ELF` is not context-aware, so it's not always possible
1724
        to determine whether the process of a binary that's missing ``PT_GNU_STACK``
1725
        will have NX or not.
1726
        
1727
        The rules are as follows:
1728

1729
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1730
            | ELF arch  | linux        | GNU_STACK                 | other                                          | NX       |
1731
            +===========+==============+===========================+================================================+==========+
1732
            | i386      | < 5.8        | non-exec                  |                                                | enabled  |
1733
            |           | [#x86_5.7]_  +---------------------------+------------------------------------------------+----------+
1734
            |           |              | exec / missing            |                                                | disabled |
1735
            |           +--------------+---------------------------+------------------------------------------------+----------+
1736
            |           | >= 5.8       | exec / non-exec           |                                                | enabled  |
1737
            |           | [#x86_5.8]_  +---------------------------+------------------------------------------------+----------+
1738
            |           |              | missing                   |                                                | disabled |
1739
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1740
            | amd64     | < 5.8        | non-exec                  |                                                | enabled  |
1741
            |           | [#x86_5.7]_  +---------------------------+------------------------------------------------+----------+
1742
            |           |              | exec / missing            |                                                | disabled |
1743
            |           +--------------+---------------------------+------------------------------------------------+----------+
1744
            |           | >= 5.8       | exec / non-exec / missing |                                                | enabled  |
1745
            |           | [#x86_5.8]_  |                           |                                                |          |
1746
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1747
            | arm       | < 5.8        | non-exec*                 |                                                | enabled  |
1748
            |           | [#arm_5.7]_  +---------------------------+------------------------------------------------+----------+
1749
            |           |              | exec / missing            |                                                | disabled |
1750
            |           +--------------+---------------------------+------------------------------------------------+----------+
1751
            |           | >= 5.8       | exec / non-exec*          |                                                | enabled  |
1752
            |           | [#arm_5.8]_  +---------------------------+------------------------------------------------+----------+
1753
            |           |              | missing                   |                                                | disabled |
1754
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1755
            | mips      | < 5.18       | non-exec*                 |                                                | enabled  |
1756
            |           | [#mips_5.17]_+---------------------------+------------------------------------------------+----------+
1757
            |           |              | exec / missing            |                                                | disabled |
1758
            |           +--------------+---------------------------+------------------------------------------------+----------+
1759
            |           | >= 5.18      | exec / non-exec*          |                                                | enabled  |
1760
            |           | [#mips_5.18]_+---------------------------+------------------------------------------------+----------+
1761
            |           |              | missing                   |                                                | disabled |
1762
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1763
            | powerpc   | [#powerpc]_  | non-exec / exec           |                                                | enabled  |
1764
            |           |              +---------------------------+------------------------------------------------+----------+
1765
            |           |              | missing                   |                                                | disabled |
1766
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1767
            | powerpc64 | [#powerpc]_  | exec / non-exec / missing |                                                | enabled  |
1768
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1769
            | ia64      | [#ia64]_     | non-exec                  |                                                | enabled  |
1770
            |           |              +---------------------------+------------------------------------------------+----------+
1771
            |           |              | exec / missing            | e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK == 0 | enabled  |
1772
            |           |              +                           +------------------------------------------------+----------+
1773
            |           |              |                           | e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK != 0 | disabled |
1774
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1775
            | the rest  | [#the_rest]_ | exec / non-exec / missing |                                                | enabled  |
1776
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1777

1778
            \\* Hardware limitations are ignored.
1779

1780
        If ``READ_IMPLIES_EXEC`` is set, then `all readable pages are executable`__.
1781
            .. __: https://github.com/torvalds/linux/blob/v6.3/fs/binfmt_elf.c#L1008-L1009
1782
            .. code-block:: c
1783

1784
                if (elf_read_implies_exec(loc->elf_ex, executable_stack))
1785
                    current->personality |= READ_IMPLIES_EXEC;
1786

1787
        .. [#x86_5.7]
1788
            `source <https://github.com/torvalds/linux/blob/v5.7/arch/x86/include/asm/elf.h#L285-L286>`__
1789

1790
            .. code-block:: c
1791

1792
                #define elf_read_implies_exec(ex, executable_stack)        \\
1793
                    (executable_stack != EXSTACK_DISABLE_X)
1794

1795
        .. [#x86_5.8]
1796
            `source <https://github.com/torvalds/linux/blob/v5.8/arch/x86/include/asm/elf.h#L305-L306>`__
1797

1798
            .. code-block:: c
1799

1800
                #define elf_read_implies_exec(ex, executable_stack)        \\
1801
                    (mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT)
1802

1803
            `mmap_is_ia32()`__:
1804
                .. __: https://github.com/torvalds/linux/blob/v5.8/arch/x86/include/asm/elf.h#L318-L321
1805
                .. code-block:: c
1806

1807
                    /*
1808
                     * True on X86_32 or when emulating IA32 on X86_64
1809
                     */
1810
                    static inline int mmap_is_ia32(void)
1811

1812
        .. [#arm_5.7]
1813
            `source <https://github.com/torvalds/linux/blob/v5.7/arch/arm/kernel/elf.c#L85-L92>`__
1814

1815
            .. code-block:: c
1816

1817
                int arm_elf_read_implies_exec(int executable_stack)
1818
                {
1819
                    if (executable_stack != EXSTACK_DISABLE_X)
1820
                        return 1;
1821
                    if (cpu_architecture() < CPU_ARCH_ARMv6)
1822
                        return 1;
1823
                    return 0;
1824
                }
1825

1826
        .. [#arm_5.8]
1827
            `source <https://github.com/torvalds/linux/blob/v5.8/arch/arm/kernel/elf.c#L104-L111>`__
1828

1829
            .. code-block:: c
1830

1831
                int arm_elf_read_implies_exec(int executable_stack)
1832
                {
1833
                    if (executable_stack == EXSTACK_DEFAULT)
1834
                        return 1;
1835
                    if (cpu_architecture() < CPU_ARCH_ARMv6)
1836
                        return 1;
1837
                    return 0;
1838
                }
1839

1840
        .. [#mips_5.17]
1841
            `source <https://github.com/torvalds/linux/blob/v5.17/arch/mips/kernel/elf.c#L329-L342>`__
1842

1843
            .. code-block:: c
1844

1845
                int mips_elf_read_implies_exec(void *elf_ex, int exstack)
1846
                {
1847
                    if (exstack != EXSTACK_DISABLE_X) {
1848
                        /* The binary doesn't request a non-executable stack */
1849
                        return 1;
1850
                    }
1851
                    if (!cpu_has_rixi) {
1852
                        /* The CPU doesn't support non-executable memory */
1853
                        return 1;
1854
                    }
1855
                    return 0;
1856
                }
1857

1858
        .. [#mips_5.18]
1859
            `source <https://github.com/torvalds/linux/blob/v5.18/arch/mips/kernel/elf.c#L329-L336>`__
1860

1861
            .. code-block:: c
1862

1863
                int mips_elf_read_implies_exec(void *elf_ex, int exstack)
1864
                {
1865
                    /*
1866
                     * Set READ_IMPLIES_EXEC only on non-NX systems that
1867
                     * do not request a specific state via PT_GNU_STACK.
1868
                     */
1869
                    return (!cpu_has_rixi && exstack == EXSTACK_DEFAULT);
1870
                }
1871

1872
        .. [#powerpc]
1873
            `source <https://github.com/torvalds/linux/blob/v6.3/arch/powerpc/include/asm/elf.h#L82-L108>`__
1874

1875
            .. code-block:: c
1876

1877
                #ifdef __powerpc64__
1878
                /* stripped */
1879
                # define elf_read_implies_exec(ex, exec_stk) (is_32bit_task() ? \\
1880
                        (exec_stk == EXSTACK_DEFAULT) : 0)
1881
                #else 
1882
                # define elf_read_implies_exec(ex, exec_stk) (exec_stk == EXSTACK_DEFAULT)
1883
                #endif /* __powerpc64__ */
1884

1885
        .. [#ia64]
1886
            `source <https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L203-L204>`__
1887

1888
            .. code-block:: c
1889

1890
                #define elf_read_implies_exec(ex, executable_stack)                                        \\
1891
                    ((executable_stack!=EXSTACK_DISABLE_X) && ((ex).e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK) != 0)
1892

1893
            EF_IA_64_LINUX_EXECUTABLE_STACK__:
1894
                .. __: https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L33
1895

1896
                .. code-block:: c
1897

1898
                    #define EF_IA_64_LINUX_EXECUTABLE_STACK        0x1        /* is stack (& heap) executable by default? */
1899

1900
        .. [#the_rest]
1901
            `source <https://github.com/torvalds/linux/blob/v6.3/include/linux/elf.h#L13>`__
1902

1903
            .. code-block:: c
1904

1905
                # define elf_read_implies_exec(ex, have_pt_gnu_stack)        0
1906
        """
1907
        if not self.executable:
1✔
1908
            return True
1✔
1909
        
1910
        exec_bit = None
1✔
1911
        for seg in self.iter_segments_by_type('GNU_STACK'):
1✔
1912
            exec_bit = bool(seg.header.p_flags & P_FLAGS.PF_X)
1✔
1913

1914
        non_exec = exec_bit is False
1✔
1915
        missing = exec_bit is None
1✔
1916
        EF_IA_64_LINUX_EXECUTABLE_STACK = 1
1✔
1917

1918
        if self.arch in ['i386', 'arm', 'aarch64', 'mips', 'mips64']:
1✔
1919
            if non_exec:
1✔
1920
                return True
1✔
1921
            elif missing:
1✔
1922
                return False
1✔
1923
            return None
1✔
1924
        elif self.arch == 'amd64':
1✔
1925
            return True if non_exec else None
1✔
1926
        elif self.arch == 'powerpc':
1✔
1927
            return not missing
1✔
1928
        elif self.arch == 'powerpc64':
1✔
1929
            return True
1✔
1930
        elif self.arch == 'ia64':
1!
1931
            if non_exec:
×
1932
                return True
×
1933
            return not bool(self['e_flags'] & EF_IA_64_LINUX_EXECUTABLE_STACK)
×
1934

1935
        return True
1✔
1936

1937
    @property
1✔
1938
    def execstack(self):
1✔
1939
        """:class:`bool`: Whether dynamically loading the current binary will make the stack executable.
1940

1941
        This is based on the presence of a program header ``PT_GNU_STACK``,
1942
        its setting, and the default stack permissions for the architecture.
1943

1944
        If ``PT_GNU_STACK`` is persent, the stack permissions are `set according to it`__:
1945

1946
        .. __: https://github.com/bminor/glibc/blob/glibc-2.37/elf/dl-load.c#L1218-L1220
1947

1948
        .. code-block:: c
1949

1950
            case PT_GNU_STACK:
1951
              stack_flags = ph->p_flags;
1952
              break;
1953

1954
        Else, the stack permissions are set according to the architecture defaults
1955
        as `defined by`__ ``DEFAULT_STACK_PERMS``:
1956

1957
        .. __: https://github.com/bminor/glibc/blob/glibc-2.37/elf/dl-load.c#L1093-L1096
1958

1959
        .. code-block:: c
1960

1961
            /* On most platforms presume that PT_GNU_STACK is absent and the stack is
1962
             * executable.  Other platforms default to a nonexecutable stack and don't
1963
             * need PT_GNU_STACK to do so.  */
1964
            uint_fast16_t stack_flags = DEFAULT_STACK_PERMS;
1965

1966
        By searching the source for ``DEFAULT_STACK_PERMS``, we can see which
1967
        architectures have which settings.
1968

1969
        ::
1970

1971
            $ git grep '#define DEFAULT_STACK_PERMS' | grep -v PF_X
1972
            sysdeps/aarch64/stackinfo.h:    #define DEFAULT_STACK_PERMS (PF_R|PF_W)
1973
            sysdeps/arc/stackinfo.h:        #define DEFAULT_STACK_PERMS (PF_R|PF_W)
1974
            sysdeps/csky/stackinfo.h:       #define DEFAULT_STACK_PERMS (PF_R|PF_W)
1975
            sysdeps/ia64/stackinfo.h:       #define DEFAULT_STACK_PERMS (PF_R|PF_W)
1976
            sysdeps/loongarch/stackinfo.h:  #define DEFAULT_STACK_PERMS (PF_R | PF_W)
1977
            sysdeps/nios2/stackinfo.h:      #define DEFAULT_STACK_PERMS (PF_R|PF_W)
1978
            sysdeps/riscv/stackinfo.h:      #define DEFAULT_STACK_PERMS (PF_R | PF_W)
1979
        """
1980
        if not self.executable:
1✔
1981
            return False
1✔
1982

1983
        # If the ``PT_GNU_STACK`` program header is preset, use it's premissions.
1984
        for seg in self.iter_segments_by_type('GNU_STACK'):
1✔
1985
            return bool(seg.header.p_flags & P_FLAGS.PF_X)
1✔
1986
        
1987
        # If the ``PT_GNU_STACK`` program header is missing, then use the
1988
        # default rules. Out of the supported architectures, only AArch64,
1989
        # IA-64, and RISC-V get a non-executable stack by default.
1990
        return self.arch not in ['aarch64', 'ia64', 'riscv32', 'riscv64']
1✔
1991

1992
    @property
1✔
1993
    def canary(self):
1✔
1994
        """:class:`bool`: Whether the current binary uses stack canaries."""
1995

1996
        # Sometimes there is no function for __stack_chk_fail,
1997
        # but there is an entry in the GOT
1998
        return '__stack_chk_fail' in (set(self.symbols) | set(self.got))
1✔
1999

2000
    @property
1✔
2001
    def packed(self):
1✔
2002
        """:class:`bool`: Whether the current binary is packed with UPX."""
2003
        return b'UPX!' in self.get_data()[:0xFF]
1✔
2004

2005
    @property
1✔
2006
    def pie(self):
1✔
2007
        """:class:`bool`: Whether the current binary is position-independent."""
2008
        return self.elftype == 'DYN'
1✔
2009
    aslr=pie
1✔
2010

2011
    @property
1✔
2012
    def retguard(self):
1✔
2013
        """:class:`bool`: Whether the current binary was compiled with retguard."""
2014
        s_randomdata = self.get_section_by_name('.openbsd.randomdata')
1✔
2015
        if s_randomdata and len(s_randomdata.data()) >= 48:
1!
NEW
2016
            return True
×
2017

2018
        return False
1✔
2019

2020
    @property
1✔
2021
    def rpath(self):
1✔
2022
        """:class:`bool`: Whether the current binary has an ``RPATH``."""
2023
        dt_rpath = self.dynamic_by_tag('DT_RPATH')
1✔
2024

2025
        if not dt_rpath:
1!
2026
            return None
1✔
2027

2028
        return self.dynamic_string(dt_rpath.entry.d_ptr)
×
2029

2030
    @property
1✔
2031
    def runpath(self):
1✔
2032
        """:class:`bool`: Whether the current binary has a ``RUNPATH``."""
2033
        dt_runpath = self.dynamic_by_tag('DT_RUNPATH')
1✔
2034

2035
        if not dt_runpath:
1✔
2036
            return None
1✔
2037

2038
        return self.dynamic_string(dt_runpath.entry.d_ptr)
1✔
2039

2040
    def checksec(self, banner=True, color=True):
1✔
2041
        """checksec(banner=True, color=True)
2042

2043
        Prints out information in the binary, similar to ``checksec.sh``.
2044

2045
        Arguments:
2046
            banner(bool): Whether to print the path to the ELF binary.
2047
            color(bool): Whether to use colored output.
2048
        """
2049
        red    = text.red if color else str
1✔
2050
        green  = text.green if color else str
1✔
2051
        yellow = text.yellow if color else str
1✔
2052

2053
        res = []
1✔
2054

2055
        # Kernel version?
2056
        if self.version and self.version != (0,):
1!
2057
            res.append('Version:'.ljust(10) + '.'.join(map(str, self.version)))
×
2058
        if self.build:
1!
2059
            res.append('Build:'.ljust(10) + self.build)
×
2060

2061
        res.extend([
1✔
2062
            "RELRO:".ljust(10) + {
2063
                'Full':    green("Full RELRO"),
2064
                'Partial': yellow("Partial RELRO"),
2065
                None:      red("No RELRO")
2066
            }[self.relro],
2067
            "Stack:".ljust(10) + {
2068
                True:  green("Canary found"),
2069
                False: red("No canary found")
2070
            }[self.canary],
2071
            "NX:".ljust(10) + {
2072
                True:  green("NX enabled"),
2073
                False: red("NX disabled"),
2074
                None: yellow("NX unknown - GNU_STACK missing"),
2075
            }[self.nx],
2076
            "PIE:".ljust(10) + {
2077
                True: green("PIE enabled"),
2078
                False: red("No PIE (%#x)" % self.address)
2079
            }[self.pie],
2080
            "Retguard:".ljust(10) + {
2081
                True: green("Retguard found"),
2082
                False: red("No retguard found"),
2083
            }[self.retguard],
2084
        ])
2085

2086
        # Execstack may be a thing, even with NX enabled, because of glibc
2087
        if self.execstack and self.nx is not False:
1✔
2088
            res.append("Stack:".ljust(10) + red("Executable"))
1✔
2089

2090
        # Are there any RWX areas in the binary?
2091
        #
2092
        # This will occur if NX is disabled and *any* area is
2093
        # RW, or can expressly occur.
2094
        if self.rwx_segments or (not self.nx and self.writable_segments):
1✔
2095
            res += [ "RWX:".ljust(10) + red("Has RWX segments") ]
1✔
2096

2097
        if self.rpath:
1!
2098
            res += [ "RPATH:".ljust(10) + red(repr(self.rpath)) ]
×
2099

2100
        if self.runpath:
1!
2101
            res += [ "RUNPATH:".ljust(10) + red(repr(self.runpath)) ]
×
2102

2103
        if self.packed:
1!
2104
            res.append('Packer:'.ljust(10) + red("Packed with UPX"))
×
2105

2106
        if self.fortify:
1✔
2107
            res.append("FORTIFY:".ljust(10) + green("Enabled"))
1✔
2108

2109
        if self.asan:
1!
2110
            res.append("ASAN:".ljust(10) + green("Enabled"))
×
2111

2112
        if self.msan:
1!
2113
            res.append("MSAN:".ljust(10) + green("Enabled"))
×
2114

2115
        if self.ubsan:
1!
2116
            res.append("UBSAN:".ljust(10) + green("Enabled"))
×
2117
        
2118
        if self.shadowstack:
1✔
2119
            res.append("SHSTK:".ljust(10) + green("Enabled"))
1✔
2120
        
2121
        if self.ibt:
1✔
2122
            res.append("IBT:".ljust(10) + green("Enabled"))
1✔
2123

2124
        # Check for Linux configuration, it must contain more than
2125
        # just the version.
2126
        if len(self.config) > 1:
1!
2127
            config_opts = collections.defaultdict(list)
×
2128
            for checker in kernel_configuration:
×
2129
                result, message = checker(self.config)
×
2130

2131
                if not result:
×
2132
                    config_opts[checker.title].append((checker.name, message))
×
2133

2134

2135
            for title, values in config_opts.items():
×
2136
                res.append(title + ':')
×
2137
                for name, message in sorted(values):
×
2138
                    line = '{} = {}'.format(name, red(str(self.config.get(name, None))))
×
2139
                    if message:
×
2140
                        line += ' ({})'.format(message)
×
2141
                    res.append('    ' + line)
×
2142

2143
            # res.extend(sorted(config_opts))
2144

2145
        return '\n'.join(res)
1✔
2146

2147
    @property
1✔
2148
    def buildid(self):
1✔
2149
        """:class:`bytes`: GNU Build ID embedded into the binary"""
2150
        section = self.get_section_by_name('.note.gnu.build-id')
1✔
2151
        if section:
1!
2152
            return section.data()[16:]
1✔
2153
        return None
×
2154

2155
    @property
1✔
2156
    def fortify(self):
1✔
2157
        """:class:`bool`: Whether the current binary was built with
2158
        Fortify Source (``-DFORTIFY``)."""
2159
        if any(s.endswith('_chk') for s in self.plt):
1✔
2160
            return True
1✔
2161
        return False
1✔
2162

2163
    @property
1✔
2164
    def asan(self):
1✔
2165
        """:class:`bool`: Whether the current binary was built with
2166
        Address Sanitizer (``ASAN``)."""
2167
        return any(s.startswith('__asan_') for s in self.symbols)
1✔
2168

2169
    @property
1✔
2170
    def msan(self):
1✔
2171
        """:class:`bool`: Whether the current binary was built with
2172
        Memory Sanitizer (``MSAN``)."""
2173
        return any(s.startswith('__msan_') for s in self.symbols)
1✔
2174

2175
    @property
1✔
2176
    def ubsan(self):
1✔
2177
        """:class:`bool`: Whether the current binary was built with
2178
        Undefined Behavior Sanitizer (``UBSAN``)."""
2179
        return any(s.startswith('__ubsan_') for s in self.symbols)
1✔
2180
    
2181
    @property
1✔
2182
    def shadowstack(self):
1✔
2183
        """:class:`bool`: Whether the current binary was built with        
2184
        Shadow Stack (``SHSTK``)"""
2185
        if self.arch not in ['i386', 'amd64']:
1✔
2186
            return False
1✔
2187
        for prop in self.iter_properties():
1✔
2188
            if prop.pr_type != 'GNU_PROPERTY_X86_FEATURE_1_AND':
1!
2189
                continue
×
2190
            return prop.pr_data & ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS['GNU_PROPERTY_X86_FEATURE_1_SHSTK'] > 0
1✔
2191
        return False
1✔
2192

2193
    @property
1✔
2194
    def ibt(self):
1✔
2195
        """:class:`bool`: Whether the current binary was built with
2196
        Indirect Branch Tracking (``IBT``)"""
2197
        if self.arch not in ['i386', 'amd64']:
1✔
2198
            return False
1✔
2199
        for prop in self.iter_properties():
1✔
2200
            if prop.pr_type != 'GNU_PROPERTY_X86_FEATURE_1_AND':
1!
2201
                continue
×
2202
            return prop.pr_data & ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS['GNU_PROPERTY_X86_FEATURE_1_IBT'] > 0
1✔
2203
        return False
1✔
2204

2205

2206
    def _update_args(self, kw):
1✔
2207
        kw.setdefault('arch', self.arch)
1✔
2208
        kw.setdefault('bits', self.bits)
1✔
2209
        kw.setdefault('endian', self.endian)
1✔
2210

2211
    def p64(self,  address, data, *a, **kw):
1✔
2212
        """Writes a 64-bit integer ``data`` to the specified ``address``"""
2213
        self._update_args(kw)
×
2214
        return self.write(address, packing.p64(data, *a, **kw))
×
2215

2216
    def p32(self,  address, data, *a, **kw):
1✔
2217
        """Writes a 32-bit integer ``data`` to the specified ``address``"""
2218
        self._update_args(kw)
×
2219
        return self.write(address, packing.p32(data, *a, **kw))
×
2220

2221
    def p16(self,  address, data, *a, **kw):
1✔
2222
        """Writes a 16-bit integer ``data`` to the specified ``address``"""
2223
        self._update_args(kw)
×
2224
        return self.write(address, packing.p16(data, *a, **kw))
×
2225

2226
    def p8(self,   address, data, *a, **kw):
1✔
2227
        """Writes a 8-bit integer ``data`` to the specified ``address``"""
2228
        self._update_args(kw)
×
2229
        return self.write(address, packing.p8(data, *a, **kw))
×
2230

2231
    def pack(self, address, data, *a, **kw):
1✔
2232
        """Writes a packed integer ``data`` to the specified ``address``"""
2233
        self._update_args(kw)
1✔
2234
        return self.write(address, packing.pack(data, *a, **kw))
1✔
2235

2236
    def u64(self,    address, *a, **kw):
1✔
2237
        """Unpacks an integer from the specified ``address``."""
2238
        self._update_args(kw)
×
2239
        return packing.u64(self.read(address, 8), *a, **kw)
×
2240

2241
    def u32(self,    address, *a, **kw):
1✔
2242
        """Unpacks an integer from the specified ``address``."""
2243
        self._update_args(kw)
×
2244
        return packing.u32(self.read(address, 4), *a, **kw)
×
2245

2246
    def u16(self,    address, *a, **kw):
1✔
2247
        """Unpacks an integer from the specified ``address``."""
2248
        self._update_args(kw)
×
2249
        return packing.u16(self.read(address, 2), *a, **kw)
×
2250

2251
    def u8(self,     address, *a, **kw):
1✔
2252
        """Unpacks an integer from the specified ``address``."""
2253
        self._update_args(kw)
×
2254
        return packing.u8(self.read(address, 1), *a, **kw)
×
2255

2256
    def unpack(self, address, *a, **kw):
1✔
2257
        """Unpacks an integer from the specified ``address``."""
2258
        self._update_args(kw)
1✔
2259
        return packing.unpack(self.read(address, self.bytes), *a, **kw)
1✔
2260

2261
    def string(self, address):
1✔
2262
        """string(address) -> str
2263

2264
        Reads a null-terminated string from the specified ``address``
2265

2266
        Returns:
2267
            A ``str`` with the string contents (NUL terminator is omitted),
2268
            or an empty string if no NUL terminator could be found.
2269
        """
2270
        data = b''
1✔
2271
        while True:
1✔
2272
            read_size = 0x1000
1✔
2273
            partial_page = address & 0xfff
1✔
2274

2275
            if partial_page:
1✔
2276
                read_size -= partial_page
1✔
2277

2278
            c = self.read(address, read_size)
1✔
2279

2280
            if not c:
1!
2281
                return b''
×
2282

2283
            data += c
1✔
2284

2285
            if b'\x00' in c:
1✔
2286
                return data[:data.index(b'\x00')]
1✔
2287

2288
            address += len(c)
1✔
2289

2290
    def flat(self, address, *a, **kw):
1✔
2291
        """Writes a full array of values to the specified address.
2292

2293
        See: :func:`.packing.flat`
2294
        """
2295
        return self.write(address, packing.flat(*a,**kw))
×
2296

2297
    def fit(self, address, *a, **kw):
1✔
2298
        """Writes fitted data into the specified address.
2299

2300
        See: :func:`.packing.fit`
2301
        """
2302
        return self.write(address, packing.fit(*a, **kw))
×
2303

2304
    def parse_kconfig(self, data):
1✔
2305
        self.config.update(parse_kconfig(data))
×
2306

2307
    def disable_nx(self):
1✔
2308
        """Disables NX for the ELF.
2309

2310
        Zeroes out the ``PT_GNU_STACK`` program header ``p_type`` field.
2311
        """
2312
        PT_GNU_STACK = packing.p32(ENUM_P_TYPE['PT_GNU_STACK'])
×
2313

2314
        if not self.executable:
×
2315
            log.error("Can only make stack executable with executables")
×
2316

2317
        for i, segment in enumerate(self.iter_segments()):
×
2318
            if not segment.header.p_type:
×
2319
                continue
×
2320
            if 'GNU_STACK' not in segment.header.p_type:
×
2321
                continue
×
2322

2323
            phoff = self.header.e_phoff
×
2324
            phentsize = self.header.e_phentsize
×
2325
            offset = phoff + phentsize * i
×
2326

2327
            if self.mmap[offset:offset+4] == PT_GNU_STACK:
×
2328
                self.mmap[offset:offset+4] = b'\x00' * 4
×
2329
                self.save()
×
2330
                # Invalidate the cached segments, ``PT_GNU_STACK`` was removed.
2331
                self._segments = None
×
2332
                return
×
2333

2334
        log.error("Could not find PT_GNU_STACK, stack should already be executable")
×
2335
    
2336
    @staticmethod
1✔
2337
    def set_runpath(exepath, runpath):
1✔
2338
        r"""set_runpath(str, str) -> ELF
2339

2340
        Patches the RUNPATH of the ELF to the given path using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
2341

2342
        The dynamic loader will look for any needed shared libraries in the given path first,
2343
        before trying the system library paths. This is useful to run a binary with a different
2344
        libc binary.
2345

2346
        Arguments:
2347
            exepath(str): Path to the binary to patch.
2348
            runpath(str): Path containing the needed libraries.
2349

2350
        Returns:
2351
            A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
2352

2353
        Example:
2354

2355
            >>> tmpdir = tempfile.mkdtemp()
2356
            >>> ls_path = os.path.join(tmpdir, 'ls')
2357
            >>> _ = shutil.copy(which('ls'), ls_path)
2358
            >>> e = ELF.set_runpath(ls_path, './libs')
2359
            >>> e.runpath == b'./libs'
2360
            True
2361
        """
2362
        if not which('patchelf'):
1!
2363
            log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
×
2364
            return None
×
2365
        try:
1✔
2366
            subprocess.check_output(['patchelf', '--set-rpath', runpath, exepath], stderr=subprocess.STDOUT)
1✔
2367
        except subprocess.CalledProcessError as e:
×
2368
            log.failure('Patching RUNPATH failed (%d): %r', e.returncode, e.stdout)
×
2369
        return ELF(exepath, checksec=False)
1✔
2370

2371
    @staticmethod
1✔
2372
    def set_interpreter(exepath, interpreter_path):
1✔
2373
        r"""set_interpreter(str, str) -> ELF
2374

2375
        Patches the interpreter of the ELF to the given binary using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
2376

2377
        When running the binary, the new interpreter will be used to load the ELF.
2378

2379
        Arguments:
2380
            exepath(str): Path to the binary to patch.
2381
            interpreter_path(str): Path to the ld.so dynamic loader.
2382

2383
        Returns:
2384
            A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
2385

2386
        Example:
2387
            >>> tmpdir = tempfile.mkdtemp()
2388
            >>> ls_path = os.path.join(tmpdir, 'ls')
2389
            >>> _ = shutil.copy(which('ls'), ls_path)
2390
            >>> e = ELF.set_interpreter(ls_path, '/tmp/correct_ld.so')
2391
            >>> e.linker == b'/tmp/correct_ld.so'
2392
            True
2393
        """
2394
        # patch the interpreter
2395
        if not which('patchelf'):
1!
2396
            log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
×
2397
            return None
×
2398
        try:
1✔
2399
            subprocess.check_output(['patchelf', '--set-interpreter', interpreter_path, exepath], stderr=subprocess.STDOUT)
1✔
2400
        except subprocess.CalledProcessError as e:
×
2401
            log.failure('Patching interpreter failed (%d): %r', e.returncode, e.stdout)
×
2402
        return ELF(exepath, checksec=False)
1✔
2403

2404
    @staticmethod
1✔
2405
    def patch_custom_libraries(exe_path, custom_library_path, create_copy=True, suffix='_remotelibc'):
1✔
2406
        r"""patch_custom_libraries(str, str, bool, str) -> ELF
2407

2408
        Looks for the interpreter binary in the given path and patches the binary to use
2409
        it if available. Also patches the RUNPATH to the given path using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
2410

2411
        Arguments:
2412
            exe_path(str): Path to the binary to patch.
2413
            custom_library_path(str): Path to a folder containing the libraries.
2414
            create_copy(bool): Create a copy of the binary and apply the patches to the copy.
2415
            suffix(str): Suffix to append to the filename when creating the copy to patch.
2416

2417
        Returns:
2418
            A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
2419

2420
        Example:
2421

2422
            >>> tmpdir = tempfile.mkdtemp()
2423
            >>> linker_path = os.path.join(tmpdir, 'ld-mock.so')
2424
            >>> write(linker_path, b'loader')
2425
            >>> ls_path = os.path.join(tmpdir, 'ls')
2426
            >>> _ = shutil.copy(which('ls'), ls_path)
2427
            >>> e = ELF.patch_custom_libraries(ls_path, tmpdir)
2428
            >>> e.runpath.decode() == tmpdir
2429
            True
2430
            >>> e.linker.decode() == linker_path
2431
            True
2432
        """
2433
        if not which('patchelf'):
1!
2434
            log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
×
2435
            return None
×
2436
        
2437
        # Create a copy of the ELF to patch instead of the original file.
2438
        if create_copy:
1!
2439
            import shutil
1✔
2440
            patched_path = exe_path + suffix
1✔
2441
            shutil.copy2(exe_path, patched_path)
1✔
2442
            exe_path = patched_path
1✔
2443

2444
        # Set interpreter in ELF to the one in the library path.
2445
        interpreter_name = [filename for filename in os.listdir(custom_library_path) if filename.startswith('ld-')]
1✔
2446
        if interpreter_name:
1!
2447
            interpreter_path = os.path.realpath(os.path.join(custom_library_path, interpreter_name[0]))
1✔
2448
            ELF.set_interpreter(exe_path, interpreter_path)
1✔
2449
        else:
2450
            log.warn("Couldn't find ld.so in library path. Interpreter not set.")
×
2451

2452
        # Set RUNPATH to library path in order to find other libraries.
2453
        return ELF.set_runpath(exe_path, custom_library_path)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc