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

Gallopsled / pwntools / 11996306831

24 Nov 2024 12:54PM UTC coverage: 73.582% (-0.1%) from 73.708%
11996306831

Pull #2496

github

web-flow
Merge aa3582943 into 6f0793eeb
Pull Request #2496: add_ko_file_search_support

3796 of 6420 branches covered (59.13%)

2 of 40 new or added lines in 1 file covered. (5.0%)

63 existing lines in 1 file now uncovered.

13311 of 18090 relevant lines covered (73.58%)

0.74 hits per line

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

79.61
/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, PAGESIZE
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:
1✔
355
            log.warn("Could not populate PLT: %s", e)
1✔
356

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

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

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

368
    def close(self):
1✔
369
        """close() -> None
370

371
        Close the ELF file and release all resources associated with it.
372
        """
373
        super(ELF, self).close()
×
374
        self.file.close()
×
375

376
    @staticmethod
1✔
377
    @LocalContext
1✔
378
    def from_assembly(assembly, *a, **kw):
1✔
379
        """from_assembly(assembly) -> ELF
380

381
        Given an assembly listing, return a fully loaded ELF object
382
        which contains that assembly at its entry point.
383

384
        Arguments:
385

386
            assembly(str): Assembly language listing
387
            vma(int): Address of the entry point and the module's base address.
388

389
        Example:
390

391
            >>> e = ELF.from_assembly('nop; foo: int 0x80', vma = 0x400000)
392
            >>> e.symbols['foo'] = 0x400001
393
            >>> e.disasm(e.entry, 1)
394
            '  400000:       90                      nop'
395
            >>> e.disasm(e.symbols['foo'], 2)
396
            '  400001:       cd 80                   int    0x80'
397
        """
398
        return ELF(make_elf_from_assembly(assembly, *a, **kw))
1✔
399

400
    @staticmethod
1✔
401
    @LocalContext
1✔
402
    def from_bytes(bytes, *a, **kw):
1✔
403
        r"""from_bytes(bytes) -> ELF
404

405
        Given a sequence of bytes, return a fully loaded ELF object
406
        which contains those bytes at its entry point.
407

408
        Arguments:
409

410
            bytes(str): Shellcode byte string
411
            vma(int): Desired base address for the ELF.
412

413
        Example:
414

415
            >>> e = ELF.from_bytes(b'\x90\xcd\x80', vma=0xc000)
416
            >>> print(e.disasm(e.entry, 3))
417
                c000:       90                      nop
418
                c001:       cd 80                   int    0x80
419
        """
420
        return ELF(make_elf(bytes, extract=False, *a, **kw))
1✔
421

422
    def process(self, argv=[], *a, **kw):
1✔
423
        """process(argv=[], *a, **kw) -> process
424

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

428
        Arguments:
429
            argv(list): List of arguments to the binary
430
            *args: Extra arguments to :class:`.process`
431
            **kwargs: Extra arguments to :class:`.process`
432

433
        Returns:
434
            :class:`.process`
435
        """
436

437
        p = process
1✔
438
        if context.os == 'android':
1!
439
            p = adb.process
×
440
        return p([self.path] + argv, *a, **kw)
1✔
441

442
    def debug(self, argv=[], *a, **kw):
1✔
443
        """debug(argv=[], *a, **kw) -> tube
444

445
        Debug the ELF with :func:`.gdb.debug`.
446

447
        Arguments:
448
            argv(list): List of arguments to the binary
449
            *args: Extra arguments to :func:`.gdb.debug`
450
            **kwargs: Extra arguments to :func:`.gdb.debug`
451

452
        Returns:
453
            :class:`.tube`: See :func:`.gdb.debug`
454
        """
455
        import pwnlib.gdb
×
456
        return pwnlib.gdb.debug([self.path] + argv, *a, **kw)
×
457

458
    def _describe(self, *a, **kw):
1✔
459
        log.info_once(
1✔
460
            '%s\n%-12s%s-%s-%s\n%s',
461
            repr(self.path),
462
            'Arch:',
463
            self.arch,
464
            self.bits,
465
            self.endian,
466
            self.checksec(*a, **kw)
467
        )
468

469
    def get_machine_arch(self):
1✔
470
        return {
1✔
471
            ('EM_X86_64', 64): 'amd64',
472
            ('EM_X86_64', 32): 'amd64', # x32 ABI
473
            ('EM_386', 32): 'i386',
474
            ('EM_486', 32): 'i386',
475
            ('EM_ARM', 32): 'arm',
476
            ('EM_AARCH64', 64): 'aarch64',
477
            ('EM_MIPS', 32): 'mips',
478
            ('EM_MIPS', 64): 'mips64',
479
            ('EM_PPC', 32): 'powerpc',
480
            ('EM_PPC64', 64): 'powerpc64',
481
            ('EM_SPARC32PLUS', 32): 'sparc',
482
            ('EM_SPARCV9', 64): 'sparc64',
483
            ('EM_IA_64', 64): 'ia64',
484
            ('EM_RISCV', 32): 'riscv32',
485
            ('EM_RISCV', 64): 'riscv64',
486
        }.get((self['e_machine'], self.bits), self['e_machine'])
487

488
    @property
1✔
489
    def entry(self):
1✔
490
        """:class:`int`: Address of the entry point for the ELF"""
491
        return self.address + (self.header.e_entry - self.load_addr)
1✔
492
    entrypoint = entry
1✔
493
    start      = entry
1✔
494

495
    @property
1✔
496
    def elftype(self):
1✔
497
        """:class:`str`: ELF type (``EXEC``, ``DYN``, etc)"""
498
        return describe_e_type(self.header.e_type).split()[0]
1✔
499

500
    def iter_segments(self):
1✔
501
        # Yield and cache all the segments in the file
502
        if self._segments is None:
1✔
503
            self._segments = [self.get_segment(i) for i in range(self.num_segments())]
1✔
504

505
        return iter(self._segments)
1✔
506

507
    @property
1✔
508
    def segments(self):
1✔
509
        """
510
        :class:`list`: A list of :class:`elftools.elf.segments.Segment` objects
511
            for the segments in the ELF.
512
        """
513
        return list(self.iter_segments())
1✔
514

515
    def iter_segments_by_type(self, t):
1✔
516
        """
517
        Yields:
518
            Segments matching the specified type.
519
        """
520
        for seg in self.iter_segments():
1✔
521
            if t == seg.header.p_type or t in str(seg.header.p_type):
1✔
522
                yield seg
1✔
523

524
    def iter_notes(self):
1✔
525
        """ 
526
        Yields:
527
            All the notes in the PT_NOTE segments.  Each result is a dictionary-
528
            like object with ``n_name``, ``n_type``, and ``n_desc`` fields, amongst
529
            others.
530
        """
531
        for seg in self.iter_segments_by_type('PT_NOTE'):
1✔
532
            for note in seg.iter_notes():
1✔
533
                yield note
1✔
534

535
    def iter_properties(self):
1✔
536
        """
537
        Yields:
538
            All the GNU properties in the PT_NOTE segments.  Each result is a dictionary-
539
            like object with ``pr_type``, ``pr_datasz``, and ``pr_data`` fields.
540
        """
541
        for note in self.iter_notes():
1✔
542
            if note.n_type != 'NT_GNU_PROPERTY_TYPE_0':
1✔
543
                continue
1✔
544
            for prop in note.n_desc:
1!
545
                yield prop
1✔
546
                
547
    def get_segment_for_address(self, address, size=1):
1✔
548
        """get_segment_for_address(address, size=1) -> Segment
549

550
        Given a virtual address described by a ``PT_LOAD`` segment, return the
551
        first segment which describes the virtual address.  An optional ``size``
552
        may be provided to ensure the entire range falls into the same segment.
553

554
        Arguments:
555
            address(int): Virtual address to find
556
            size(int): Number of bytes which must be available after ``address``
557
                in **both** the file-backed data for the segment, and the memory
558
                region which is reserved for the data.
559

560
        Returns:
561
            Either returns a :class:`.segments.Segment` object, or ``None``.
562
        """
563
        for seg in self.iter_segments_by_type("PT_LOAD"):
1!
564
            mem_start = seg.header.p_vaddr
1✔
565
            mem_stop  = seg.header.p_memsz + mem_start
1✔
566

567
            if not (mem_start <= address <= address+size < mem_stop):
1✔
568
                continue
1✔
569

570
            offset = self.vaddr_to_offset(address)
1✔
571

572
            file_start = seg.header.p_offset
1✔
573
            file_stop  = seg.header.p_filesz + file_start
1✔
574

575
            if not (file_start <= offset <= offset+size < file_stop):
1!
576
                continue
×
577

578
            return seg
1✔
579

580
        return None
×
581

582
    def iter_sections(self):
1✔
583
        # Yield and cache all the sections in the file
584
        if self._sections is None:
1✔
585
            self._sections = [self.get_section(i) for i in range(self.num_sections())]
1✔
586

587
        return iter(self._sections)
1✔
588

589
    @property
1✔
590
    def sections(self):
1✔
591
        """
592
        :class:`list`: A list of :class:`elftools.elf.sections.Section` objects
593
            for the segments in the ELF.
594
        """
595
        return list(self.iter_sections())
1✔
596

597
    @property
1✔
598
    def dwarf(self):
1✔
599
        """DWARF info for the elf"""
600
        return self.get_dwarf_info()
×
601

602
    @property
1✔
603
    def sym(self):
1✔
604
        """:class:`dotdict`: Alias for :attr:`.ELF.symbols`"""
605
        return self.symbols
1✔
606

607
    @property
1✔
608
    def address(self):
1✔
609
        """:class:`int`: Address of the lowest segment loaded in the ELF.
610

611
        When updated, the addresses of the following fields are also updated:
612

613
        - :attr:`~.ELF.symbols`
614
        - :attr:`~.ELF.got`
615
        - :attr:`~.ELF.plt`
616
        - :attr:`~.ELF.functions`
617

618
        However, the following fields are **NOT** updated:
619

620
        - :attr:`~.ELF.segments`
621
        - :attr:`~.ELF.sections`
622

623
        Example:
624

625
            >>> bash = ELF('/bin/bash')
626
            >>> read = bash.symbols['read']
627
            >>> text = bash.get_section_by_name('.text').header.sh_addr
628
            >>> bash.address += 0x1000
629
            >>> read + 0x1000 == bash.symbols['read']
630
            True
631
            >>> text == bash.get_section_by_name('.text').header.sh_addr
632
            True
633
        """
634
        return self._address
1✔
635

636
    @address.setter
1✔
637
    def address(self, new):
1✔
638
        delta     = new-self._address
1✔
639
        update    = lambda x: x+delta
1✔
640

641
        self.symbols = dotdict({k:update(v) for k,v in self.symbols.items()})
1✔
642
        self.plt     = dotdict({k:update(v) for k,v in self.plt.items()})
1✔
643
        self.got     = dotdict({k:update(v) for k,v in self.got.items()})
1✔
644
        for f in self.functions.values():
1✔
645
            f.address += delta
1✔
646

647
        # Update our view of memory
648
        memory = intervaltree.IntervalTree()
1✔
649

650
        for begin, end, data in self.memory:
1✔
651
            memory.addi(update(begin),
1✔
652
                        update(end),
653
                        data)
654

655
        self.memory = memory
1✔
656

657
        self._address = update(self.address)
1✔
658

659
    def section(self, name):
1✔
660
        """section(name) -> bytes
661

662
        Gets data for the named section
663

664
        Arguments:
665
            name(str): Name of the section
666

667
        Returns:
668
            :class:`str`: String containing the bytes for that section
669
        """
670
        return self.get_section_by_name(name).data()
×
671

672
    @property
1✔
673
    def rwx_segments(self):
1✔
674
        """:class:`list`: List of all segments which are writeable and executable.
675

676
        See:
677
            :attr:`.ELF.segments`
678
        """
679
        if not self.nx:
1✔
680
            return self.writable_segments
1✔
681

682
        wx = P_FLAGS.PF_X | P_FLAGS.PF_W
1✔
683
        return [s for s in self.segments if s.header.p_flags & wx == wx]
1✔
684

685
    @property
1✔
686
    def executable_segments(self):
1✔
687
        """:class:`list`: List of all segments which are executable.
688

689
        See:
690
            :attr:`.ELF.segments`
691
        """
692
        if not self.nx:
1!
693
            return list(self.segments)
1✔
694

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

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

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

706
    @property
1✔
707
    def non_writable_segments(self):
1✔
708
        """:class:`list`: List of all segments which are NOT writeable.
709

710
        See:
711
            :attr:`.ELF.segments`
712
        """
713
        return [s for s in self.segments if not s.header.p_flags & P_FLAGS.PF_W]
×
714

715
    @property
1✔
716
    def libs(self):
1✔
717
        """Dictionary of ``{path: address}`` for every library loaded for this ELF."""
718
        if self._libs is None:
1✔
719
            self._populate_libraries()
1✔
720
        return self._libs
1✔
721

722
    @property
1✔
723
    def maps(self):
1✔
724
        """Dictionary of ``{name: address}`` for every mapping in this ELF's address space."""
725
        if self._maps is None:
×
726
            self._populate_libraries()
×
727
        return self._maps
×
728

729
    @property
1✔
730
    def libc(self):
1✔
731
        """:class:`.ELF`: If this :class:`.ELF` imports any libraries which contain ``'libc[.-]``,
732
        and we can determine the appropriate path to it on the local
733
        system, returns a new :class:`.ELF` object pertaining to that library.
734
        Prints the `checksec` output of the library if it was printed for the original ELF too.
735

736
        If not found, the value will be :const:`None`.
737
        """
738
        for lib in self.libs:
1!
739
            if '/libc.' in lib or '/libc-' in lib:
1✔
740
                return ELF(lib, self._print_checksec)
1✔
741

742
    def _populate_libraries(self):
1✔
743
        """
744
        >>> from os.path import exists
745
        >>> bash = ELF(which('bash'))
746
        >>> all(map(exists, bash.libs.keys()))
747
        True
748
        >>> any(map(lambda x: 'libc' in x, bash.libs.keys()))
749
        True
750
        """
751
        # Patch some shellcode into the ELF and run it.
752
        maps = self._patch_elf_and_read_maps()
1✔
753

754
        self._maps = maps
1✔
755
        self._libs = {}
1✔
756

757
        for lib, address in maps.items():
1✔
758

759
            # Filter out [stack] and such from the library listings
760
            if lib.startswith('['):
1✔
761
                continue
1✔
762

763
            # Any existing files we can just use
764
            if os.path.exists(lib):
1!
765
                self._libs[lib] = address
1✔
766

767
            # Try etc/qemu-binfmt, as per Ubuntu
768
            if not self.native:
1!
769
                ld_prefix = qemu.ld_prefix()
×
770

771
                qemu_lib = os.path.join(ld_prefix, lib)
×
772
                qemu_lib = os.path.realpath(qemu_lib)
×
773

774
                if os.path.exists(qemu_lib):
×
775
                    self._libs[qemu_lib] = address
×
776

777
    def _patch_elf_and_read_maps(self):
1✔
778
        r"""patch_elf_and_read_maps(self) -> dict
779

780
        Read ``/proc/self/maps`` as if the ELF were executing.
781

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

785
        Returns:
786
            A ``dict`` mapping file paths to the lowest address they appear at.
787
            Does not do any translation for e.g. QEMU emulation, the raw results
788
            are returned.
789

790
            If there is not enough space to inject the shellcode in the segment
791
            which contains the entry point, returns ``{}``.
792

793
        Doctests:
794

795
            These tests are just to ensure that our shellcode is correct.
796

797
            >>> for arch in CAT_PROC_MAPS_EXIT:
798
            ...   context.clear()
799
            ...   with context.local(arch=arch):
800
            ...     sc = shellcraft.cat2("/proc/self/maps")
801
            ...     sc += shellcraft.exit()
802
            ...     sc = asm(sc)
803
            ...     sc = enhex(sc)
804
            ...     assert sc == CAT_PROC_MAPS_EXIT[arch], (arch, sc)
805
        """
806

807
        # Get our shellcode
808
        sc = CAT_PROC_MAPS_EXIT.get(self.arch, None)
1✔
809

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

813
        sc = unhex(sc)
1✔
814

815
        # Ensure there is enough room in the segment where the entry point resides
816
        # in order to inject our shellcode.
817
        seg = self.get_segment_for_address(self.entry, len(sc))
1✔
818
        if not seg:
1!
819
            log.warn_once("Could not inject code to determine memory mapping for %r: Not enough space", self)
×
820
            return {}
×
821

822
        # Create our temporary file
823
        # NOTE: We cannot use "with NamedTemporaryFile() as foo", because we cannot
824
        # execute the file while the handle is open.
825
        fd, path = tempfile.mkstemp()
1✔
826

827
        # Close the file descriptor so that it may be executed
828
        os.close(fd)
1✔
829

830
        # Save off a copy of the ELF
831
        self.save(path)
1✔
832

833
        # Load a new copy of the ELF at the temporary file location
834
        old = self.read(self.entry, len(sc))
1✔
835
        try:
1✔
836
            self.write(self.entry, sc)
1✔
837
            self.save(path)
1✔
838
        finally:
839
            # Restore the original contents
840
            self.write(self.entry, old)
1✔
841

842
        # Make the file executable
843
        os.chmod(path, 0o755)
1✔
844

845
        # Run a copy of it, get the maps
846
        try:
1✔
847
            with context.silent:
1✔
848
                io = process(path)
1✔
849
                data = packing._decode(io.recvall(timeout=2))
1✔
850
        except Exception:
×
851
            log.warn_once("Injected /proc/self/maps code did not execute correctly")
×
852
            return {}
×
853

854
        # Swap in the original ELF name
855
        data = data.replace(path, self.path)
1✔
856

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

875
            address, _ = line.split('-', 1)
1✔
876

877
            address = int(address, 0x10)
1✔
878
            name = line[index:]
1✔
879

880
            result.setdefault(name, address)
1✔
881

882
        # Remove the temporary file, best-effort
883
        os.unlink(path)
1✔
884

885
        return result
1✔
886

887
    def _populate_functions(self):
1✔
888
        """Builds a dict of 'functions' (i.e. symbols of type 'STT_FUNC')
889
        by function name that map to a tuple consisting of the func address and size
890
        in bytes.
891
        """
892
        for sec in self.sections:
1✔
893
            if not isinstance(sec, SymbolTableSection):
1✔
894
                continue
1✔
895

896
            for sym in _iter_symbols(sec):
1✔
897
                # Avoid duplicates
898
                if sym.name in self.functions:
1✔
899
                    continue
1✔
900
                if sym.entry.st_info['type'] == 'STT_FUNC' and sym.entry.st_size != 0:
1✔
901
                    name = sym.name
1✔
902
                    if name not in self.symbols:
1!
903
                        continue
×
904
                    addr = self.symbols[name]
1✔
905
                    size = sym.entry.st_size
1✔
906
                    self.functions[name] = Function(name, addr, size, self)
1✔
907

908
    def _populate_symbols(self):
1✔
909
        """
910
        >>> bash = ELF(which('bash'))
911
        >>> bash.symbols['_start'] == bash.entry
912
        True
913
        """
914

915
        # Populate all of the "normal" symbols from the symbol tables
916
        for section in self.sections:
1✔
917
            if not isinstance(section, SymbolTableSection):
1✔
918
                continue
1✔
919

920
            for symbol in _iter_symbols(section):
1✔
921
                value = symbol.entry.st_value
1✔
922
                if not value:
1✔
923
                    continue
1✔
924
                self.symbols[symbol.name] = value
1✔
925

926
    def _populate_synthetic_symbols(self):
1✔
927
        """Adds symbols from the GOT and PLT to the symbols dictionary.
928

929
        Does not overwrite any existing symbols, and prefers PLT symbols.
930

931
        Synthetic plt.xxx and got.xxx symbols are added for each PLT and
932
        GOT entry, respectively.
933

934
        Example:bash.
935

936
            >>> bash = ELF(which('bash'))
937
            >>> bash.symbols.wcscmp == bash.plt.wcscmp
938
            True
939
            >>> bash.symbols.wcscmp == bash.symbols.plt.wcscmp
940
            True
941
            >>> bash.symbols.stdin  == bash.got.stdin
942
            True
943
            >>> bash.symbols.stdin  == bash.symbols.got.stdin
944
            True
945
        """
946
        for symbol, address in self.plt.items():
1✔
947
            self.symbols.setdefault(symbol, address)
1✔
948
            self.symbols['plt.' + symbol] = address
1✔
949

950
        for symbol, address in self.got.items():
1✔
951
            self.symbols.setdefault(symbol, address)
1✔
952
            self.symbols['got.' + symbol] = address
1✔
953

954
    def _populate_got(self):
1✔
955
        """Loads the symbols for all relocations.
956

957
            >>> libc = ELF(which('bash')).libc
958
            >>> assert 'strchrnul' in libc.got
959
            >>> assert 'memcpy' in libc.got
960
            >>> assert libc.got.strchrnul != libc.got.memcpy
961
        """
962
        # Statically linked implies no relocations, since there is no linker
963
        # Could always be self-relocating like Android's linker *shrug*
964
        if self.statically_linked:
1✔
965
            return
1✔
966

967
        revsymbols = defaultdict(list)
1✔
968
        for name, addr in self.symbols.items():
1✔
969
            revsymbols[addr].append(name)
1✔
970

971
        for section in self.sections:
1✔
972
            # We are only interested in relocations
973
            if not isinstance(section, (RelocationSection, RelrRelocationSection)):
1✔
974
                continue
1✔
975

976
            # Only get relocations which link to another section (for symbols)
977
            if section.header.sh_link == SHN_INDICES.SHN_UNDEF:
1✔
978
                continue
1✔
979

980
            symbols = self.get_section(section.header.sh_link)
1✔
981

982
            for rel in section.iter_relocations():
1✔
983
                sym_idx  = rel.entry.r_info_sym
1✔
984

985
                if not sym_idx and rel.is_RELA():
1✔
986
                    # TODO: actually resolve relocations
987
                    relocated = rel.entry.r_addend  # sufficient for now
1✔
988

989
                    symnames = revsymbols[relocated]
1✔
990
                    for symname in symnames:
1✔
991
                        self.got[symname] = rel.entry.r_offset
1✔
992
                    continue
1✔
993

994
                symbol = symbols.get_symbol(sym_idx)
1✔
995

996
                if symbol and symbol.name:
1✔
997
                    self.got[symbol.name] = rel.entry.r_offset
1✔
998

999
        if self.arch == 'mips':
1✔
1000
            try:
1✔
1001
                self._populate_mips_got()
1✔
1002
            except Exception as e:
×
1003
                log.warn("Could not populate MIPS GOT: %s", e)
×
1004

1005
        if not self.got:
1✔
1006
            log.warn("Did not find any GOT entries")
1✔
1007

1008
    def _populate_mips_got(self):
1✔
1009
        self._mips_got = {}
1✔
1010
        strings = self.get_section(self.header.e_shstrndx)
1✔
1011

1012
        ELF_MIPS_GNU_GOT1_MASK = 0x80000000
1✔
1013

1014
        if self.bits == 64:
1!
1015
            ELF_MIPS_GNU_GOT1_MASK <<= 32
×
1016

1017
        # Beginning of the GOT
1018
        got = self.dynamic_value_by_tag('DT_PLTGOT') or 0
1✔
1019

1020
        # Find the beginning of the GOT pointers
1021
        got1_mask = (self.unpack(got) & ELF_MIPS_GNU_GOT1_MASK)
1✔
1022
        i = 2 if got1_mask else 1
1✔
1023
        self._mips_skip = i
1✔
1024

1025
        # We don't care about local GOT entries, skip them
1026
        local_gotno = self.dynamic_value_by_tag('DT_MIPS_LOCAL_GOTNO')
1✔
1027
        got += local_gotno * context.bytes
1✔
1028

1029
        # Iterate over the dynamic symbol table
1030
        dynsym = self.get_section_by_name('.dynsym')
1✔
1031
        symbol_iter = _iter_symbols(dynsym)
1✔
1032

1033
        # 'gotsym' is the index of the first GOT symbol
1034
        gotsym = self.dynamic_value_by_tag('DT_MIPS_GOTSYM')
1✔
1035
        for i in range(gotsym):
1✔
1036
            next(symbol_iter)
1✔
1037

1038
        # 'symtabno' is the total number of symbols
1039
        symtabno = self.dynamic_value_by_tag('DT_MIPS_SYMTABNO')
1✔
1040

1041
        for i in range(symtabno - gotsym):
1✔
1042
            symbol = next(symbol_iter)
1✔
1043
            self._mips_got[i + gotsym] = got
1✔
1044
            self.got[symbol.name] = got
1✔
1045
            got += self.bytes
1✔
1046

1047
    def _populate_plt(self):
1✔
1048
        """Loads the PLT symbols
1049

1050
        >>> path = pwnlib.data.elf.path
1051
        >>> for test in glob(os.path.join(path, 'test-*')):
1052
        ...     test = ELF(test)
1053
        ...     assert '__stack_chk_fail' in test.got, test
1054
        ...     if test.arch != 'ppc':
1055
        ...         assert '__stack_chk_fail' in test.plt, test
1056
        """
1057
        if self.statically_linked:
1✔
1058
            log.debug("%r is statically linked, skipping GOT/PLT symbols" % self.path)
1✔
1059
            return
1✔
1060

1061
        if not self.got:
1✔
1062
            log.debug("%r doesn't have any GOT symbols, skipping PLT" % self.path)
1✔
1063
            return
1✔
1064

1065
        # This element holds an address associated with the procedure linkage table
1066
        # and/or the global offset table.
1067
        #
1068
        # Zach's note: This corresponds to the ".got.plt" section, in a PIE non-RELRO binary.
1069
        #              This corresponds to the ".got" section, in a PIE full-RELRO binary.
1070
        #              In particular, this is where EBX points when it points into the GOT.
1071
        dt_pltgot = self.dynamic_value_by_tag('DT_PLTGOT') or 0
1✔
1072

1073
        # There are three PLTs we may need to search
1074
        plt = self.get_section_by_name('.plt')          # <-- Functions only
1✔
1075
        plt_got = self.get_section_by_name('.plt.got')  # <-- Functions used as data
1✔
1076
        plt_sec = self.get_section_by_name('.plt.sec')
1✔
1077
        plt_mips = self.get_section_by_name('.MIPS.stubs')
1✔
1078

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

1083
        with context.local(arch=self.arch, bits=self.bits, endian=self.endian):
1✔
1084
            for section in (plt, plt_got, plt_sec, plt_mips):
1✔
1085
                if not section:
1✔
1086
                    continue
1✔
1087

1088
                res = emulate_plt_instructions(self,
1✔
1089
                                                dt_pltgot,
1090
                                                section.header.sh_addr,
1091
                                                section.data(),
1092
                                                inv_symbols)
1093

1094
                for address, target in sorted(res.items()):
1✔
1095
                    self.plt[inv_symbols[target]] = address
1✔
1096

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

1100
    def _populate_kernel_version(self):
1✔
1101
        if 'linux_banner' not in self.symbols:
1!
1102
            return
1✔
1103

1104
        banner = self.string(self.symbols.linux_banner)
×
1105

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

1109
        # 'Linux version 3.18.31-gd0846ecc
1110
        regex = r'Linux version (\S+)'
×
1111
        match = re.search(regex, banner)
×
1112

1113
        if match:
×
1114
            version = match.group(1)
×
1115

1116
            if '-' in version:
×
1117
                version, self.build = version.split('-', 1)
×
1118

1119
            self.version = list(map(int, version.rstrip('+').split('.')))
×
1120

1121
        self.config['version'] = self.version
×
1122

1123
    @property
1✔
1124
    def libc_start_main_return(self):
1✔
1125
        """:class:`int`: Address of the return address into __libc_start_main from main.
1126

1127
        >>> bash = ELF(which('bash'))
1128
        >>> libc = bash.libc
1129
        >>> libc.libc_start_main_return > 0
1130
        True
1131

1132
        Try to find the return address from main into __libc_start_main.
1133
        The heuristic to find the call to the function pointer of main is
1134
        to list all calls inside __libc_start_main, find the call to exit
1135
        after the call to main and select the previous call.
1136
        """
1137
        if '__libc_start_main' not in self.functions:
1!
1138
            return 0
×
1139

1140
        if 'exit' not in self.symbols:
1!
1141
            return 0
×
1142

1143
        # If there's no delay slot, execution continues on the next instruction after a call.
1144
        call_return_offset = 1
1✔
1145
        if self.arch in ['arm', 'thumb']:
1!
1146
            call_instructions = set(['blx', 'bl'])
×
1147
        elif self.arch == 'aarch64':
1!
1148
            call_instructions = set(['blr', 'bl'])
×
1149
        elif self.arch in ['mips', 'mips64']:
1!
1150
            call_instructions = set(['bal', 'jalr'])
×
1151
            # Account for the delay slot.
1152
            call_return_offset = 2
×
1153
        elif self.arch in ['i386', 'amd64', 'ia64']:
1!
1154
            call_instructions = set(['call'])
1✔
1155
        else:
1156
            log.error('Unsupported architecture %s in ELF.libc_start_main_return', self.arch)
×
1157
            return 0
×
1158

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

1163
        def find_ret_main_addr(lines, calls):
1✔
1164
            exit_calls = [index for index, line in enumerate(calls) if exit_addr in line[1]]
1✔
1165
            if len(exit_calls) != 1:
1✔
1166
                return 0
1✔
1167

1168
            call_to_main = calls[exit_calls[0] - 1]
1✔
1169
            return_from_main = lines[call_to_main[0] + call_return_offset].lstrip()
1✔
1170
            return_from_main = int(return_from_main[ : return_from_main.index(':') ], 16)
1✔
1171
            return return_from_main
1✔
1172

1173
        # Starting with glibc-2.34 calling `main` is split out into `__libc_start_call_main`
1174
        ret_addr = find_ret_main_addr(lines, calls)
1✔
1175
        # Pre glibc-2.34 case - `main` is called directly
1176
        if ret_addr:
1✔
1177
            return ret_addr
1✔
1178

1179
        # `__libc_start_main` -> `__libc_start_call_main` -> `main`
1180
        # Find a direct call which calls `exit` once. That's probably `__libc_start_call_main`.
1181
        direct_call_pattern = re.compile(r'['+r'|'.join(call_instructions)+r']\s+(0x[0-9a-zA-Z]+)')
1✔
1182
        for line in calls:
1!
1183
            match = direct_call_pattern.search(line[1])
1✔
1184
            if not match:
1✔
1185
                continue
1✔
1186

1187
            target_addr = int(match.group(1), 0)
1✔
1188
            # `__libc_start_call_main` is usually smaller than `__libc_start_main`, so
1189
            # we might disassemble a bit too much, but it's a good dynamic estimate.
1190
            callee_lines = self.disasm(target_addr, self.functions['__libc_start_main'].size).split('\n')
1✔
1191
            callee_calls = [(index, line) for index, line in enumerate(callee_lines) if set(line.split()) & call_instructions]
1✔
1192
            ret_addr = find_ret_main_addr(callee_lines, callee_calls)
1✔
1193
            if ret_addr:
1✔
1194
                return ret_addr
1✔
1195
        return 0
×
1196

1197
    def search(self, needle, writable = False, executable = False):
1✔
1198
        """search(needle, writable = False, executable = False) -> generator
1199

1200
        Search the ELF's virtual address space for the specified string.
1201

1202
        Notes:
1203
            Does not search empty space between segments, or uninitialized
1204
            data.  This will only return data that actually exists in the
1205
            ELF file.  Searching for a long string of NULL bytes probably
1206
            won't work.
1207

1208
        Arguments:
1209
            needle(bytes): String to search for.
1210
            writable(bool): Search only writable sections.
1211
            executable(bool): Search only executable sections.
1212

1213
        Yields:
1214
            An iterator for each virtual address that matches.
1215

1216
        Examples:
1217

1218
            An ELF header starts with the bytes ``\\x7fELF``, so we
1219
            sould be able to find it easily.
1220

1221
            >>> bash = ELF('/bin/bash')
1222
            >>> bash.address + 1 == next(bash.search(b'ELF'))
1223
            True
1224

1225
            We can also search for string the binary.
1226

1227
            >>> len(list(bash.search(b'GNU bash'))) > 0
1228
            True
1229

1230
            It is also possible to search for instructions in executable sections.
1231

1232
            >>> binary = ELF.from_assembly('nop; mov eax, 0; jmp esp; ret')
1233
            >>> jmp_addr = next(binary.search(asm('jmp esp'), executable = True))
1234
            >>> binary.read(jmp_addr, 2) == asm('jmp esp')
1235
            True
1236
        """
1237
        load_address_fixup = (self.address - self.load_addr)
1✔
1238

1239
        if writable:
1!
1240
            segments = self.writable_segments
×
1241
        elif executable:
1✔
1242
            segments = self.executable_segments
1✔
1243
        else:
1244
            segments = self.segments
1✔
1245
        needle = packing._need_bytes(needle)
1✔
1246
        for seg in segments:
1✔
1247
            addr   = seg.header.p_vaddr
1✔
1248
            memsz  = seg.header.p_memsz
1✔
1249
            filesz = seg.header.p_filesz
1✔
1250
            zeroed = memsz - filesz
1✔
1251
            offset = seg.header.p_offset
1✔
1252
            data   = self.mmap[offset:offset+filesz]
1✔
1253
            data   += b'\x00' * zeroed
1✔
1254
            offset = 0
1✔
1255
            while True:
1✔
1256
                offset = data.find(needle, offset)
1✔
1257
                if offset == -1:
1✔
1258
                    break
1✔
1259
                yield (addr + offset + load_address_fixup)
1✔
1260
                offset += 1
1✔
1261
        if not segments:
1!
NEW
1262
            if writable:
×
NEW
1263
                ko_check_segments = [".data"]
×
NEW
1264
            elif executable:
×
NEW
1265
                ko_check_segments = [".text"]
×
1266
            else:
NEW
1267
                ko_check_segments = [".text",".note",".rodata",".data"]
×
NEW
1268
            for section in super().iter_sections():
×
NEW
1269
                if section.name not in ko_check_segments and \
×
1270
                       not any(section.name.startswith(ko_check_segment) for ko_check_segment in ko_check_segments):
NEW
1271
                    continue
×
NEW
1272
                filesz = section['sh_size']
×
NEW
1273
                offset = section['sh_offset']
×
NEW
1274
                data = self.mmap[offset:offset + filesz]
×
NEW
1275
                data += b'\x00'
×
NEW
1276
                offset = 0
×
NEW
1277
                while True:
×
NEW
1278
                    offset = data.find(needle, offset)
×
NEW
1279
                    if offset == -1:
×
NEW
1280
                        break
×
1281
                    # ko_file: header->.note->.text->.rodata->.data
1282
                    # after insmod: text page(executable page), note and rodate page(read only page), data page(writable page)
NEW
1283
                    if section.name == ".text":
×
NEW
1284
                        addr = 0
×
NEW
1285
                    elif section.name.startswith(".note") :
×
NEW
1286
                        text_filesz=self.get_section_by_name(".text")['sh_size']
×
NEW
1287
                        addr = (text_filesz//PAGESIZE + 1)*PAGESIZE + section['sh_offset'] - self.header['e_ehsize']
×
NEW
1288
                    elif section.name.startswith(".rodata"):
×
NEW
1289
                        text_filesz=self.get_section_by_name(".text")['sh_size']
×
NEW
1290
                        text_offset=self.get_section_by_name(".text")['sh_offset']
×
NEW
1291
                        addr = (text_filesz//PAGESIZE + 1)*PAGESIZE + text_offset - self.header['e_ehsize']
×
NEW
1292
                    elif section.name == ".data" :
×
NEW
1293
                        text_filesz=self.get_section_by_name(".text")['sh_size']
×
NEW
1294
                        rodata_filesz=0
×
NEW
1295
                        note_filez=0
×
NEW
1296
                        for section in super().iter_sections():
×
NEW
1297
                            if section.name.startswith(".rodata"):
×
NEW
1298
                                rodata_filesz += section['sh_size']
×
NEW
1299
                            elif section.name.startswith(".node"):
×
NEW
1300
                                note_filesz += section['sh_size']
×
NEW
1301
                        addr = (text_filesz//PAGESIZE + 1 + (note_filez+rodata_filesz)//PAGESIZE + 1)*PAGESIZE
×
NEW
1302
                    yield (addr + offset + load_address_fixup)
×
NEW
1303
                    offset += 1
×
1304
    def offset_to_vaddr(self, offset):
1✔
1305
        """offset_to_vaddr(offset) -> int
1306

1307
        Translates the specified offset to a virtual address.
1308

1309
        Arguments:
1310
            offset(int): Offset to translate
1311

1312
        Returns:
1313
            `int`: Virtual address which corresponds to the file offset, or
1314
            :const:`None`.
1315

1316
        Examples:
1317

1318
            This example shows that regardless of changes to the virtual
1319
            address layout by modifying :attr:`.ELF.address`, the offset
1320
            for any given address doesn't change.
1321

1322
            >>> bash = ELF('/bin/bash')
1323
            >>> bash.address == bash.offset_to_vaddr(0)
1324
            True
1325
            >>> bash.address += 0x123456
1326
            >>> bash.address == bash.offset_to_vaddr(0)
1327
            True
1328
        """
1329
        load_address_fixup = (self.address - self.load_addr)
1✔
1330

1331
        for segment in self.segments:
1!
1332
            begin = segment.header.p_offset
1✔
1333
            size  = segment.header.p_filesz
1✔
1334
            end   = begin + size
1✔
1335
            if begin <= offset and offset <= end:
1✔
1336
                delta = offset - begin
1✔
1337
                return segment.header.p_vaddr + delta + load_address_fixup
1✔
UNCOV
1338
        return None
×
1339

1340
    def _populate_memory(self):
1✔
1341
        load_segments = list(filter(lambda s: s.header.p_type == 'PT_LOAD', self.iter_segments()))
1✔
1342

1343
        # Map all of the segments
1344
        for i, segment in enumerate(load_segments):
1✔
1345
            start = segment.header.p_vaddr
1✔
1346
            stop_data = start + segment.header.p_filesz
1✔
1347
            stop_mem  = start + segment.header.p_memsz
1✔
1348

1349
            # Chop any existing segments which cover the range described by
1350
            # [vaddr, vaddr+filesz].
1351
            #
1352
            # This has the effect of removing any issues we may encounter
1353
            # with "overlapping" segments, by giving precedence to whichever
1354
            # DT_LOAD segment is **last** to load data into the region.
1355
            self.memory.chop(start, stop_data)
1✔
1356

1357
            # Fill the start of the segment's first page
1358
            page_start = align_down(0x1000, start)
1✔
1359
            if page_start < start and not self.memory[page_start]:
1!
UNCOV
1360
                self.memory.addi(page_start, start, None)
×
1361

1362
            # Add the new segment
1363
            if start != stop_data:
1✔
1364
                self.memory.addi(start, stop_data, segment)
1✔
1365

1366
            if stop_data != stop_mem:
1✔
1367
                self.memory.addi(stop_data, stop_mem, b'\x00')
1✔
1368

1369
            page_end = align(0x1000, stop_mem)
1✔
1370

1371
            # Check for holes which we can fill
1372
            if self._fill_gaps and i+1 < len(load_segments):
1✔
1373
                next_start = load_segments[i+1].header.p_vaddr
1✔
1374
                page_next = align_down(0x1000, next_start)
1✔
1375

1376
                if stop_mem < next_start:
1✔
1377
                    if page_end < page_next:
1✔
1378
                        if stop_mem < page_end:
1!
1379
                            self.memory.addi(stop_mem, page_end, None)
1✔
1380
                        if page_next < next_start:
1✔
1381
                            self.memory.addi(page_next, next_start, None)
1✔
1382
                    else:
1383
                        self.memory.addi(stop_mem, next_start, None)
1✔
1384
            else:
1385
                if stop_mem < page_end:
1✔
1386
                    self.memory.addi(stop_mem, page_end, None)
1✔
1387

1388
    def vaddr_to_offset(self, address):
1✔
1389
        """vaddr_to_offset(address) -> int
1390

1391
        Translates the specified virtual address to a file offset
1392

1393
        Arguments:
1394
            address(int): Virtual address to translate
1395

1396
        Returns:
1397
            int: Offset within the ELF file which corresponds to the address,
1398
            or :const:`None`.
1399

1400
        Examples:
1401

1402
            >>> bash = ELF(which('bash'))
1403
            >>> bash.vaddr_to_offset(bash.address)
1404
            0
1405
            >>> bash.address += 0x123456
1406
            >>> bash.vaddr_to_offset(bash.address)
1407
            0
1408
            >>> bash.vaddr_to_offset(0) is None
1409
            True
1410
        """
1411

1412
        for interval in self.memory[address]:
1✔
1413
            segment = interval.data
1✔
1414

1415
            # Convert the address back to how it was when the segment was loaded
1416
            address = (address - self.address) + self.load_addr
1✔
1417

1418
            # Figure out the offset into the segment
1419
            offset = address - segment.header.p_vaddr
1✔
1420

1421
            # Add the segment-base offset to the offset-within-the-segment
1422
            return segment.header.p_offset + offset
1✔
1423

1424
    def read(self, address, count):
1✔
1425
        r"""read(address, count) -> bytes
1426

1427
        Read data from the specified virtual address
1428

1429
        Arguments:
1430
            address(int): Virtual address to read
1431
            count(int): Number of bytes to read
1432

1433
        Returns:
1434
            A :class:`bytes` object, or :const:`None`.
1435

1436
        Examples:
1437
            The simplest example is just to read the ELF header.
1438

1439
            >>> bash = ELF(which('bash'))
1440
            >>> bash.read(bash.address, 4)
1441
            b'\x7fELF'
1442

1443
            ELF segments do not have to contain all of the data on-disk
1444
            that gets loaded into memory.
1445

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

1448
            >>> assembly = '''
1449
            ... .section .A,"awx"
1450
            ... .global A
1451
            ... A: nop
1452
            ... .section .B,"awx"
1453
            ... .global B
1454
            ... B: int3
1455
            ... '''
1456
            >>> e = ELF.from_assembly(assembly, vma=False)
1457

1458
            By default, these come right after eachother in memory.
1459

1460
            >>> e.read(e.symbols.A, 2)
1461
            b'\x90\xcc'
1462
            >>> e.symbols.B - e.symbols.A
1463
            1
1464

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

1467
            >>> objcopy = pwnlib.asm._objcopy()
1468
            >>> objcopy += [
1469
            ...     '--change-section-vma', '.B+5',
1470
            ...     '--change-section-lma', '.B+5',
1471
            ...     e.path
1472
            ... ]
1473
            >>> subprocess.check_call(objcopy)
1474
            0
1475

1476
            Now let's re-load the ELF, and check again
1477

1478
            >>> e = ELF(e.path)
1479
            >>> e.symbols.B - e.symbols.A
1480
            6
1481
            >>> e.read(e.symbols.A, 2)
1482
            b'\x90\x00'
1483
            >>> e.read(e.symbols.A, 7)
1484
            b'\x90\x00\x00\x00\x00\x00\xcc'
1485
            >>> e.read(e.symbols.A, 10)
1486
            b'\x90\x00\x00\x00\x00\x00\xcc\x00\x00\x00'
1487

1488
            Everything is relative to the user-selected base address, so moving
1489
            things around keeps everything working.
1490

1491
            >>> e.address += 0x1000
1492
            >>> e.read(e.symbols.A, 10)
1493
            b'\x90\x00\x00\x00\x00\x00\xcc\x00\x00\x00'
1494
        """
1495
        retval = []
1✔
1496

1497
        if count == 0:
1!
UNCOV
1498
            return b''
×
1499

1500
        start = address
1✔
1501
        stop = address + count
1✔
1502

1503
        overlap = self.memory.overlap(start, stop)
1✔
1504

1505
        # Create a new view of memory, for just what we need
1506
        memory = intervaltree.IntervalTree(overlap)
1✔
1507
        memory.chop(-1<<64, start)
1✔
1508
        memory.chop(stop, 1<<64)
1✔
1509

1510
        if memory.begin() != start:
1!
UNCOV
1511
            log.error("Address %#x is not contained in %s" % (start, self))
×
1512

1513
        if memory.end() != stop:
1!
UNCOV
1514
            log.error("Address %#x is not contained in %s" % (stop, self))
×
1515

1516
        # We have a view of memory which lets us get everything we need
1517
        for begin, end, data in sorted(memory):
1✔
1518
            length = end-begin
1✔
1519

1520
            if data in (None, b'\x00'):
1✔
1521
                retval.append(b'\x00' * length)
1✔
1522
                continue
1✔
1523

1524
            # Offset within VMA range
1525
            begin -= self.address
1✔
1526

1527
            # Adjust to original VMA range
1528
            begin += self.load_addr
1✔
1529

1530
            # Adjust to offset within segment VMA
1531
            offset = begin - data.header.p_vaddr
1✔
1532

1533
            # Adjust in-segment offset to in-file offset
1534
            offset += data.header.p_offset
1✔
1535

1536
            retval.append(self.mmap[offset:offset+length])
1✔
1537

1538
        return b''.join(retval)
1✔
1539

1540
    def write(self, address, data):
1✔
1541
        """Writes data to the specified virtual address
1542

1543
        Arguments:
1544
            address(int): Virtual address to write
1545
            data(str): Bytes to write
1546

1547
        Note:
1548
            This routine does not check the bounds on the write to ensure
1549
            that it stays in the same segment.
1550

1551
        Examples:
1552

1553
          >>> bash = ELF(which('bash'))
1554
          >>> bash.read(bash.address+1, 3)
1555
          b'ELF'
1556
          >>> bash.write(bash.address, b"HELO")
1557
          >>> bash.read(bash.address, 4)
1558
          b'HELO'
1559
        """
1560
        offset = self.vaddr_to_offset(address)
1✔
1561

1562
        if offset is not None:
1!
1563
            length = len(data)
1✔
1564
            self.mmap[offset:offset+length] = data
1✔
1565

1566
        return None
1✔
1567

1568
    def save(self, path=None):
1✔
1569
        """Save the ELF to a file
1570

1571
        >>> bash = ELF(which('bash'))
1572
        >>> bash.save('/tmp/bash_copy')
1573
        >>> copy = open('/tmp/bash_copy', 'rb')
1574
        >>> bash = open(which('bash'), 'rb')
1575
        >>> bash.read() == copy.read()
1576
        True
1577
        """
1578
        if path is None:
1✔
1579
            path = self.path
1✔
1580
        misc.write(path, self.data)
1✔
1581

1582
    def get_data(self):
1✔
1583
        """get_data() -> bytes
1584

1585
        Retrieve the raw data from the ELF file.
1586

1587
        >>> bash = ELF(which('bash'))
1588
        >>> fd   = open(which('bash'), 'rb')
1589
        >>> bash.get_data() == fd.read()
1590
        True
1591
        """
1592
        return self.mmap[:]
1✔
1593

1594
    @property
1✔
1595
    def data(self):
1✔
1596
        """:class:`bytes`: Raw data of the ELF file.
1597

1598
        See:
1599
            :meth:`get_data`
1600
        """
1601
        return self.mmap[:]
1✔
1602

1603
    def disasm(self, address, n_bytes):
1✔
1604
        """disasm(address, n_bytes) -> str
1605

1606
        Returns a string of disassembled instructions at
1607
        the specified virtual memory address"""
1608
        arch = self.arch
1✔
1609
        if self.arch == 'arm' and address & 1:
1!
UNCOV
1610
            arch = 'thumb'
×
UNCOV
1611
            address -= 1
×
1612

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

1615
    def asm(self, address, assembly):
1✔
1616
        """asm(address, assembly)
1617

1618
        Assembles the specified instructions and inserts them
1619
        into the ELF at the specified address.
1620

1621
        This modifies the ELF in-place.
1622
        The resulting binary can be saved with :meth:`.ELF.save`
1623
        """
1624
        binary = asm(assembly, vma=address, arch=self.arch, endian=self.endian, bits=self.bits)
1✔
1625
        self.write(address, binary)
1✔
1626

1627
    def bss(self, offset=0):
1✔
1628
        """bss(offset=0) -> int
1629

1630
        Returns:
1631
            Address of the ``.bss`` section, plus the specified offset.
1632
        """
UNCOV
1633
        orig_bss = self.get_section_by_name('.bss').header.sh_addr
×
UNCOV
1634
        curr_bss = orig_bss - self.load_addr + self.address
×
UNCOV
1635
        return curr_bss + offset
×
1636

1637
    def __repr__(self):
1✔
1638
        return "%s(%r)" % (self.__class__.__name__, self.path)
1✔
1639

1640
    def dynamic_by_tag(self, tag):
1✔
1641
        """dynamic_by_tag(tag) -> tag
1642

1643
        Arguments:
1644
            tag(str): Named ``DT_XXX`` tag (e.g. ``'DT_STRTAB'``).
1645

1646
        Returns:
1647
            :class:`elftools.elf.dynamic.DynamicTag`
1648
        """
1649
        dt      = None
1✔
1650
        dynamic = self.get_section_by_name('.dynamic')
1✔
1651

1652
        if not dynamic:
1✔
1653
            return None
1✔
1654

1655
        try:
1✔
1656
            dt = next(t for t in dynamic.iter_tags() if tag == t.entry.d_tag)
1✔
1657
        except StopIteration:
1✔
1658
            pass
1✔
1659

1660
        return dt
1✔
1661

1662
    def dynamic_value_by_tag(self, tag):
1✔
1663
        """dynamic_value_by_tag(tag) -> int
1664

1665
        Retrieve the value from a dynamic tag a la ``DT_XXX``.
1666

1667
        If the tag is missing, returns ``None``.
1668
        """
1669
        tag = self.dynamic_by_tag(tag)
1✔
1670

1671
        if tag:
1✔
1672
            return tag.entry.d_val
1✔
1673

1674
    def dynamic_string(self, offset):
1✔
1675
        """dynamic_string(offset) -> bytes
1676

1677
        Fetches an enumerated string from the ``DT_STRTAB`` table.
1678

1679
        Arguments:
1680
            offset(int): String index
1681

1682
        Returns:
1683
            :class:`str`: String from the table as raw bytes.
1684
        """
1685
        dt_strtab = self.dynamic_by_tag('DT_STRTAB')
1✔
1686

1687
        if not dt_strtab:
1!
UNCOV
1688
            return None
×
1689

1690
        address   = dt_strtab.entry.d_ptr + offset
1✔
1691
        string    = b''
1✔
1692
        while b'\x00' not in string:
1✔
1693
            string  += self.read(address, 1)
1✔
1694
            address += 1
1✔
1695
        return string.rstrip(b'\x00')
1✔
1696

1697

1698

1699
    @property
1✔
1700
    def relro(self):
1✔
1701
        """:class:`bool`: Whether the current binary uses RELRO protections.
1702

1703
        This requires both presence of the dynamic tag ``DT_BIND_NOW``, and
1704
        a ``GNU_RELRO`` program header.
1705

1706
        The `ELF Specification`_ describes how the linker should resolve
1707
        symbols immediately, as soon as a binary is loaded.  This can be
1708
        emulated with the ``LD_BIND_NOW=1`` environment variable.
1709

1710
            ``DT_BIND_NOW``
1711

1712
            If present in a shared object or executable, this entry instructs
1713
            the dynamic linker to process all relocations for the object
1714
            containing this entry before transferring control to the program.
1715
            The presence of this entry takes precedence over a directive to use
1716
            lazy binding for this object when specified through the environment
1717
            or via ``dlopen(BA_LIB)``.
1718

1719
            (`page 81`_)
1720

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

1725
        Finally, a new-ish extension which doesn't seem to have a canonical
1726
        source of documentation is DF_BIND_NOW_, which has supposedly superceded
1727
        ``DT_BIND_NOW``.
1728

1729
            ``DF_BIND_NOW``
1730

1731
            If set in a shared object or executable, this flag instructs the
1732
            dynamic linker to process all relocations for the object containing
1733
            this entry before transferring control to the program. The presence
1734
            of this entry takes precedence over a directive to use lazy binding
1735
            for this object when specified through the environment or via
1736
            ``dlopen(BA_LIB)``.
1737

1738
        .. _ELF Specification: https://refspecs.linuxbase.org/elf/elf.pdf
1739
        .. _page 81: https://refspecs.linuxbase.org/elf/elf.pdf#page=81
1740
        .. _DT_BIND_NOW: https://refspecs.linuxbase.org/elf/elf.pdf#page=81
1741
        .. _PT_GNU_RELRO: https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic.html#PROGHEADER
1742
        .. _DF_BIND_NOW: https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html#df_bind_now
1743

1744
        >>> path = pwnlib.data.elf.relro.path
1745
        >>> for test in glob(os.path.join(path, 'test-*')):
1746
        ...     e = ELF(test)
1747
        ...     expected = os.path.basename(test).split('-')[2]
1748
        ...     actual = str(e.relro).lower()
1749
        ...     assert actual == expected
1750
        """
1751
        if not any('GNU_RELRO' in str(s.header.p_type) for s in self.segments):
1✔
1752
            return None
1✔
1753

1754
        if self.dynamic_by_tag('DT_BIND_NOW'):
1✔
1755
            return "Full"
1✔
1756

1757
        flags = self.dynamic_value_by_tag('DT_FLAGS')
1✔
1758
        if flags and flags & constants.DF_BIND_NOW:
1✔
1759
            return "Full"
1✔
1760

1761
        flags_1 = self.dynamic_value_by_tag('DT_FLAGS_1')
1✔
1762
        if flags_1 and flags_1 & constants.DF_1_NOW:
1!
UNCOV
1763
            return "Full"
×
1764

1765
        return "Partial"
1✔
1766

1767
    @property
1✔
1768
    def nx(self):
1✔
1769
        """:class:`bool`: Whether the current binary uses NX protections.
1770

1771
        Specifically, we are checking for ``READ_IMPLIES_EXEC`` being set
1772
        by the kernel, as a result of honoring ``PT_GNU_STACK`` in the kernel.
1773

1774
        ``READ_IMPLIES_EXEC`` is set, according to a set of architecture specific
1775
        rules, that depend on the CPU features, and the presence of ``PT_GNU_STACK``.
1776

1777
        Unfortunately, :class:`ELF` is not context-aware, so it's not always possible
1778
        to determine whether the process of a binary that's missing ``PT_GNU_STACK``
1779
        will have NX or not.
1780
        
1781
        The rules are as follows:
1782

1783
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1784
            | ELF arch  | linux        | GNU_STACK                 | other                                          | NX       |
1785
            +===========+==============+===========================+================================================+==========+
1786
            | i386      | < 5.8        | non-exec                  |                                                | enabled  |
1787
            |           | [#x86_5.7]_  +---------------------------+------------------------------------------------+----------+
1788
            |           |              | exec / missing            |                                                | disabled |
1789
            |           +--------------+---------------------------+------------------------------------------------+----------+
1790
            |           | >= 5.8       | exec / non-exec           |                                                | enabled  |
1791
            |           | [#x86_5.8]_  +---------------------------+------------------------------------------------+----------+
1792
            |           |              | missing                   |                                                | disabled |
1793
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1794
            | amd64     | < 5.8        | non-exec                  |                                                | enabled  |
1795
            |           | [#x86_5.7]_  +---------------------------+------------------------------------------------+----------+
1796
            |           |              | exec / missing            |                                                | disabled |
1797
            |           +--------------+---------------------------+------------------------------------------------+----------+
1798
            |           | >= 5.8       | exec / non-exec / missing |                                                | enabled  |
1799
            |           | [#x86_5.8]_  |                           |                                                |          |
1800
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1801
            | arm       | < 5.8        | non-exec*                 |                                                | enabled  |
1802
            |           | [#arm_5.7]_  +---------------------------+------------------------------------------------+----------+
1803
            |           |              | exec / missing            |                                                | disabled |
1804
            |           +--------------+---------------------------+------------------------------------------------+----------+
1805
            |           | >= 5.8       | exec / non-exec*          |                                                | enabled  |
1806
            |           | [#arm_5.8]_  +---------------------------+------------------------------------------------+----------+
1807
            |           |              | missing                   |                                                | disabled |
1808
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1809
            | mips      | < 5.18       | non-exec*                 |                                                | enabled  |
1810
            |           | [#mips_5.17]_+---------------------------+------------------------------------------------+----------+
1811
            |           |              | exec / missing            |                                                | disabled |
1812
            |           +--------------+---------------------------+------------------------------------------------+----------+
1813
            |           | >= 5.18      | exec / non-exec*          |                                                | enabled  |
1814
            |           | [#mips_5.18]_+---------------------------+------------------------------------------------+----------+
1815
            |           |              | missing                   |                                                | disabled |
1816
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1817
            | powerpc   | [#powerpc]_  | non-exec / exec           |                                                | enabled  |
1818
            |           |              +---------------------------+------------------------------------------------+----------+
1819
            |           |              | missing                   |                                                | disabled |
1820
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1821
            | powerpc64 | [#powerpc]_  | exec / non-exec / missing |                                                | enabled  |
1822
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1823
            | ia64      | [#ia64]_     | non-exec                  |                                                | enabled  |
1824
            |           |              +---------------------------+------------------------------------------------+----------+
1825
            |           |              | exec / missing            | e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK == 0 | enabled  |
1826
            |           |              +                           +------------------------------------------------+----------+
1827
            |           |              |                           | e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK != 0 | disabled |
1828
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1829
            | the rest  | [#the_rest]_ | exec / non-exec / missing |                                                | enabled  |
1830
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1831

1832
            \\* Hardware limitations are ignored.
1833

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

1838
                if (elf_read_implies_exec(loc->elf_ex, executable_stack))
1839
                    current->personality |= READ_IMPLIES_EXEC;
1840

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

1844
            .. code-block:: c
1845

1846
                #define elf_read_implies_exec(ex, executable_stack)        \\
1847
                    (executable_stack != EXSTACK_DISABLE_X)
1848

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

1852
            .. code-block:: c
1853

1854
                #define elf_read_implies_exec(ex, executable_stack)        \\
1855
                    (mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT)
1856

1857
            `mmap_is_ia32()`__:
1858
                .. __: https://github.com/torvalds/linux/blob/v5.8/arch/x86/include/asm/elf.h#L318-L321
1859
                .. code-block:: c
1860

1861
                    /*
1862
                     * True on X86_32 or when emulating IA32 on X86_64
1863
                     */
1864
                    static inline int mmap_is_ia32(void)
1865

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

1869
            .. code-block:: c
1870

1871
                int arm_elf_read_implies_exec(int executable_stack)
1872
                {
1873
                    if (executable_stack != EXSTACK_DISABLE_X)
1874
                        return 1;
1875
                    if (cpu_architecture() < CPU_ARCH_ARMv6)
1876
                        return 1;
1877
                    return 0;
1878
                }
1879

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

1883
            .. code-block:: c
1884

1885
                int arm_elf_read_implies_exec(int executable_stack)
1886
                {
1887
                    if (executable_stack == EXSTACK_DEFAULT)
1888
                        return 1;
1889
                    if (cpu_architecture() < CPU_ARCH_ARMv6)
1890
                        return 1;
1891
                    return 0;
1892
                }
1893

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

1897
            .. code-block:: c
1898

1899
                int mips_elf_read_implies_exec(void *elf_ex, int exstack)
1900
                {
1901
                    if (exstack != EXSTACK_DISABLE_X) {
1902
                        /* The binary doesn't request a non-executable stack */
1903
                        return 1;
1904
                    }
1905
                    if (!cpu_has_rixi) {
1906
                        /* The CPU doesn't support non-executable memory */
1907
                        return 1;
1908
                    }
1909
                    return 0;
1910
                }
1911

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

1915
            .. code-block:: c
1916

1917
                int mips_elf_read_implies_exec(void *elf_ex, int exstack)
1918
                {
1919
                    /*
1920
                     * Set READ_IMPLIES_EXEC only on non-NX systems that
1921
                     * do not request a specific state via PT_GNU_STACK.
1922
                     */
1923
                    return (!cpu_has_rixi && exstack == EXSTACK_DEFAULT);
1924
                }
1925

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

1929
            .. code-block:: c
1930

1931
                #ifdef __powerpc64__
1932
                /* stripped */
1933
                # define elf_read_implies_exec(ex, exec_stk) (is_32bit_task() ? \\
1934
                        (exec_stk == EXSTACK_DEFAULT) : 0)
1935
                #else 
1936
                # define elf_read_implies_exec(ex, exec_stk) (exec_stk == EXSTACK_DEFAULT)
1937
                #endif /* __powerpc64__ */
1938

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

1942
            .. code-block:: c
1943

1944
                #define elf_read_implies_exec(ex, executable_stack)                                        \\
1945
                    ((executable_stack!=EXSTACK_DISABLE_X) && ((ex).e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK) != 0)
1946

1947
            EF_IA_64_LINUX_EXECUTABLE_STACK__:
1948
                .. __: https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L33
1949

1950
                .. code-block:: c
1951

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

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

1957
            .. code-block:: c
1958

1959
                # define elf_read_implies_exec(ex, have_pt_gnu_stack)        0
1960
        """
1961
        if not self.executable:
1✔
1962
            return True
1✔
1963
        
1964
        exec_bit = None
1✔
1965
        for seg in self.iter_segments_by_type('GNU_STACK'):
1✔
1966
            exec_bit = bool(seg.header.p_flags & P_FLAGS.PF_X)
1✔
1967

1968
        non_exec = exec_bit is False
1✔
1969
        missing = exec_bit is None
1✔
1970
        EF_IA_64_LINUX_EXECUTABLE_STACK = 1
1✔
1971

1972
        if self.arch in ['i386', 'arm', 'aarch64', 'mips', 'mips64']:
1✔
1973
            if non_exec:
1✔
1974
                return True
1✔
1975
            elif missing:
1✔
1976
                return False
1✔
1977
            return None
1✔
1978
        elif self.arch == 'amd64':
1✔
1979
            return True if non_exec else None
1✔
1980
        elif self.arch == 'powerpc':
1✔
1981
            return not missing
1✔
1982
        elif self.arch == 'powerpc64':
1✔
1983
            return True
1✔
1984
        elif self.arch == 'ia64':
1!
UNCOV
1985
            if non_exec:
×
UNCOV
1986
                return True
×
UNCOV
1987
            return not bool(self['e_flags'] & EF_IA_64_LINUX_EXECUTABLE_STACK)
×
1988

1989
        return True
1✔
1990

1991
    @property
1✔
1992
    def execstack(self):
1✔
1993
        """:class:`bool`: Whether dynamically loading the current binary will make the stack executable.
1994

1995
        This is based on the presence of a program header ``PT_GNU_STACK``,
1996
        its setting, and the default stack permissions for the architecture.
1997

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

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

2002
        .. code-block:: c
2003

2004
            case PT_GNU_STACK:
2005
              stack_flags = ph->p_flags;
2006
              break;
2007

2008
        Else, the stack permissions are set according to the architecture defaults
2009
        as `defined by`__ ``DEFAULT_STACK_PERMS``:
2010

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

2013
        .. code-block:: c
2014

2015
            /* On most platforms presume that PT_GNU_STACK is absent and the stack is
2016
             * executable.  Other platforms default to a nonexecutable stack and don't
2017
             * need PT_GNU_STACK to do so.  */
2018
            uint_fast16_t stack_flags = DEFAULT_STACK_PERMS;
2019

2020
        By searching the source for ``DEFAULT_STACK_PERMS``, we can see which
2021
        architectures have which settings.
2022

2023
        ::
2024

2025
            $ git grep '#define DEFAULT_STACK_PERMS' | grep -v PF_X
2026
            sysdeps/aarch64/stackinfo.h:    #define DEFAULT_STACK_PERMS (PF_R|PF_W)
2027
            sysdeps/arc/stackinfo.h:        #define DEFAULT_STACK_PERMS (PF_R|PF_W)
2028
            sysdeps/csky/stackinfo.h:       #define DEFAULT_STACK_PERMS (PF_R|PF_W)
2029
            sysdeps/ia64/stackinfo.h:       #define DEFAULT_STACK_PERMS (PF_R|PF_W)
2030
            sysdeps/loongarch/stackinfo.h:  #define DEFAULT_STACK_PERMS (PF_R | PF_W)
2031
            sysdeps/nios2/stackinfo.h:      #define DEFAULT_STACK_PERMS (PF_R|PF_W)
2032
            sysdeps/riscv/stackinfo.h:      #define DEFAULT_STACK_PERMS (PF_R | PF_W)
2033
        """
2034
        if not self.executable:
1✔
2035
            return False
1✔
2036

2037
        # If the ``PT_GNU_STACK`` program header is preset, use it's premissions.
2038
        for seg in self.iter_segments_by_type('GNU_STACK'):
1✔
2039
            return bool(seg.header.p_flags & P_FLAGS.PF_X)
1✔
2040
        
2041
        # If the ``PT_GNU_STACK`` program header is missing, then use the
2042
        # default rules. Out of the supported architectures, only AArch64,
2043
        # IA-64, and RISC-V get a non-executable stack by default.
2044
        return self.arch not in ['aarch64', 'ia64', 'riscv32', 'riscv64']
1✔
2045

2046
    @property
1✔
2047
    def canary(self):
1✔
2048
        """:class:`bool`: Whether the current binary uses stack canaries."""
2049

2050
        # Sometimes there is no function for __stack_chk_fail,
2051
        # but there is an entry in the GOT
2052
        return '__stack_chk_fail' in (set(self.symbols) | set(self.got))
1✔
2053

2054
    @property
1✔
2055
    def packed(self):
1✔
2056
        """:class:`bool`: Whether the current binary is packed with UPX."""
2057
        return b'UPX!' in self.get_data()[:0xFF]
1✔
2058

2059
    @property
1✔
2060
    def stripped(self):
1✔
2061
        """:class:`bool`: Whether the current binary has been stripped of symbols"""
2062
        return not any(section['sh_type'] == 'SHT_SYMTAB' for section in self.iter_sections())
1✔
2063

2064
    @property
1✔
2065
    def debuginfo(self):
1✔
2066
        """:class:`bool`: Whether the current binary has debug information"""
2067
        return self.get_section_by_name('.debug_info') is not None
1✔
2068

2069
    @property
1✔
2070
    def pie(self):
1✔
2071
        """:class:`bool`: Whether the current binary is position-independent."""
2072
        return self.elftype == 'DYN'
1✔
2073
    aslr=pie
1✔
2074

2075
    @property
1✔
2076
    def rpath(self):
1✔
2077
        """:class:`bool`: Whether the current binary has an ``RPATH``."""
2078
        dt_rpath = self.dynamic_by_tag('DT_RPATH')
1✔
2079

2080
        if not dt_rpath:
1!
2081
            return None
1✔
2082

UNCOV
2083
        return self.dynamic_string(dt_rpath.entry.d_ptr)
×
2084

2085
    @property
1✔
2086
    def runpath(self):
1✔
2087
        """:class:`bool`: Whether the current binary has a ``RUNPATH``."""
2088
        dt_runpath = self.dynamic_by_tag('DT_RUNPATH')
1✔
2089

2090
        if not dt_runpath:
1✔
2091
            return None
1✔
2092

2093
        return self.dynamic_string(dt_runpath.entry.d_ptr)
1✔
2094

2095
    def checksec(self, banner=True, color=True):
1✔
2096
        """checksec(banner=True, color=True)
2097

2098
        Prints out information in the binary, similar to ``checksec.sh``.
2099

2100
        Arguments:
2101
            banner(bool): Whether to print the path to the ELF binary.
2102
            color(bool): Whether to use colored output.
2103
        """
2104
        red    = text.red if color else str
1✔
2105
        green  = text.green if color else str
1✔
2106
        yellow = text.yellow if color else str
1✔
2107

2108
        res = []
1✔
2109

2110
        # Kernel version?
2111
        if self.version and self.version != (0,):
1!
UNCOV
2112
            res.append('Version:'.ljust(12) + '.'.join(map(str, self.version)))
×
2113
        if self.build:
1!
UNCOV
2114
            res.append('Build:'.ljust(12) + self.build)
×
2115

2116
        res.extend([
1✔
2117
            "RELRO:".ljust(12) + {
2118
                'Full':    green("Full RELRO"),
2119
                'Partial': yellow("Partial RELRO"),
2120
                None:      red("No RELRO")
2121
            }[self.relro],
2122
            "Stack:".ljust(12) + {
2123
                True:  green("Canary found"),
2124
                False: red("No canary found")
2125
            }[self.canary],
2126
            "NX:".ljust(12) + {
2127
                True:  green("NX enabled"),
2128
                False: red("NX disabled"),
2129
                None: yellow("NX unknown - GNU_STACK missing"),
2130
            }[self.nx],
2131
            "PIE:".ljust(12) + {
2132
                True: green("PIE enabled"),
2133
                False: red("No PIE (%#x)" % self.address)
2134
            }[self.pie],
2135
        ])
2136

2137
        # Execstack may be a thing, even with NX enabled, because of glibc
2138
        if self.execstack and self.nx is not False:
1✔
2139
            res.append("Stack:".ljust(12) + red("Executable"))
1✔
2140

2141
        # Are there any RWX areas in the binary?
2142
        #
2143
        # This will occur if NX is disabled and *any* area is
2144
        # RW, or can expressly occur.
2145
        if self.rwx_segments or (not self.nx and self.writable_segments):
1✔
2146
            res += [ "RWX:".ljust(12) + red("Has RWX segments") ]
1✔
2147

2148
        if self.rpath:
1!
UNCOV
2149
            res += [ "RPATH:".ljust(12) + red(repr(self.rpath)) ]
×
2150

2151
        if self.runpath:
1!
UNCOV
2152
            res += [ "RUNPATH:".ljust(12) + red(repr(self.runpath)) ]
×
2153

2154
        if self.packed:
1!
UNCOV
2155
            res.append('Packer:'.ljust(12) + red("Packed with UPX"))
×
2156

2157
        if self.fortify:
1✔
2158
            res.append("FORTIFY:".ljust(12) + green("Enabled"))
1✔
2159

2160
        if self.asan:
1!
UNCOV
2161
            res.append("ASAN:".ljust(12) + green("Enabled"))
×
2162

2163
        if self.msan:
1!
UNCOV
2164
            res.append("MSAN:".ljust(12) + green("Enabled"))
×
2165

2166
        if self.ubsan:
1!
UNCOV
2167
            res.append("UBSAN:".ljust(12) + green("Enabled"))
×
2168
        
2169
        if self.shadowstack:
1✔
2170
            res.append("SHSTK:".ljust(12) + green("Enabled"))
1✔
2171
        
2172
        if self.ibt:
1✔
2173
            res.append("IBT:".ljust(12) + green("Enabled"))
1✔
2174
        
2175
        if not self.stripped:
1✔
2176
            res.append("Stripped:".ljust(12) + red("No"))
1✔
2177
        
2178
        if self.debuginfo:
1✔
2179
            res.append("Debuginfo:".ljust(12) + red("Yes"))
1✔
2180

2181
        # Check for Linux configuration, it must contain more than
2182
        # just the version.
2183
        if len(self.config) > 1:
1!
UNCOV
2184
            config_opts = collections.defaultdict(list)
×
UNCOV
2185
            for checker in kernel_configuration:
×
UNCOV
2186
                result, message = checker(self.config)
×
2187

UNCOV
2188
                if not result:
×
UNCOV
2189
                    config_opts[checker.title].append((checker.name, message))
×
2190

2191

UNCOV
2192
            for title, values in config_opts.items():
×
UNCOV
2193
                res.append(title + ':')
×
2194
                for name, message in sorted(values):
×
2195
                    line = '{} = {}'.format(name, red(str(self.config.get(name, None))))
×
2196
                    if message:
×
UNCOV
2197
                        line += ' ({})'.format(message)
×
2198
                    res.append('    ' + line)
×
2199

2200
            # res.extend(sorted(config_opts))
2201

2202
        return '\n'.join(res)
1✔
2203

2204
    @property
1✔
2205
    def buildid(self):
1✔
2206
        """:class:`bytes`: GNU Build ID embedded into the binary"""
2207
        section = self.get_section_by_name('.note.gnu.build-id')
1✔
2208
        if section:
1!
2209
            return section.data()[16:]
1✔
UNCOV
2210
        return None
×
2211

2212
    @property
1✔
2213
    def fortify(self):
1✔
2214
        """:class:`bool`: Whether the current binary was built with
2215
        Fortify Source (``-DFORTIFY``)."""
2216
        if any(s.endswith('_chk') for s in self.plt):
1✔
2217
            return True
1✔
2218
        return False
1✔
2219

2220
    @property
1✔
2221
    def asan(self):
1✔
2222
        """:class:`bool`: Whether the current binary was built with
2223
        Address Sanitizer (``ASAN``)."""
2224
        return any(s.startswith('__asan_') for s in self.symbols)
1✔
2225

2226
    @property
1✔
2227
    def msan(self):
1✔
2228
        """:class:`bool`: Whether the current binary was built with
2229
        Memory Sanitizer (``MSAN``)."""
2230
        return any(s.startswith('__msan_') for s in self.symbols)
1✔
2231

2232
    @property
1✔
2233
    def ubsan(self):
1✔
2234
        """:class:`bool`: Whether the current binary was built with
2235
        Undefined Behavior Sanitizer (``UBSAN``)."""
2236
        return any(s.startswith('__ubsan_') for s in self.symbols)
1✔
2237
    
2238
    @property
1✔
2239
    def shadowstack(self):
1✔
2240
        """:class:`bool`: Whether the current binary was built with        
2241
        Shadow Stack (``SHSTK``)"""
2242
        if self.arch not in ['i386', 'amd64']:
1✔
2243
            return False
1✔
2244
        for prop in self.iter_properties():
1✔
2245
            if prop.pr_type != 'GNU_PROPERTY_X86_FEATURE_1_AND':
1!
UNCOV
2246
                continue
×
2247
            return prop.pr_data & ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS['GNU_PROPERTY_X86_FEATURE_1_SHSTK'] > 0
1✔
2248
        return False
1✔
2249

2250
    @property
1✔
2251
    def ibt(self):
1✔
2252
        """:class:`bool`: Whether the current binary was built with
2253
        Indirect Branch Tracking (``IBT``)"""
2254
        if self.arch not in ['i386', 'amd64']:
1✔
2255
            return False
1✔
2256
        for prop in self.iter_properties():
1✔
2257
            if prop.pr_type != 'GNU_PROPERTY_X86_FEATURE_1_AND':
1!
UNCOV
2258
                continue
×
2259
            return prop.pr_data & ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS['GNU_PROPERTY_X86_FEATURE_1_IBT'] > 0
1✔
2260
        return False
1✔
2261

2262

2263
    def _update_args(self, kw):
1✔
2264
        kw.setdefault('arch', self.arch)
1✔
2265
        kw.setdefault('bits', self.bits)
1✔
2266
        kw.setdefault('endian', self.endian)
1✔
2267

2268
    def p64(self,  address, data, *a, **kw):
1✔
2269
        """Writes a 64-bit integer ``data`` to the specified ``address``"""
UNCOV
2270
        self._update_args(kw)
×
UNCOV
2271
        return self.write(address, packing.p64(data, *a, **kw))
×
2272

2273
    def p32(self,  address, data, *a, **kw):
1✔
2274
        """Writes a 32-bit integer ``data`` to the specified ``address``"""
UNCOV
2275
        self._update_args(kw)
×
UNCOV
2276
        return self.write(address, packing.p32(data, *a, **kw))
×
2277

2278
    def p16(self,  address, data, *a, **kw):
1✔
2279
        """Writes a 16-bit integer ``data`` to the specified ``address``"""
2280
        self._update_args(kw)
×
2281
        return self.write(address, packing.p16(data, *a, **kw))
×
2282

2283
    def p8(self,   address, data, *a, **kw):
1✔
2284
        """Writes a 8-bit integer ``data`` to the specified ``address``"""
2285
        self._update_args(kw)
×
2286
        return self.write(address, packing.p8(data, *a, **kw))
×
2287

2288
    def pack(self, address, data, *a, **kw):
1✔
2289
        """Writes a packed integer ``data`` to the specified ``address``"""
2290
        self._update_args(kw)
1✔
2291
        return self.write(address, packing.pack(data, *a, **kw))
1✔
2292

2293
    def u64(self,    address, *a, **kw):
1✔
2294
        """Unpacks an integer from the specified ``address``."""
2295
        self._update_args(kw)
×
2296
        return packing.u64(self.read(address, 8), *a, **kw)
×
2297

2298
    def u32(self,    address, *a, **kw):
1✔
2299
        """Unpacks an integer from the specified ``address``."""
UNCOV
2300
        self._update_args(kw)
×
UNCOV
2301
        return packing.u32(self.read(address, 4), *a, **kw)
×
2302

2303
    def u16(self,    address, *a, **kw):
1✔
2304
        """Unpacks an integer from the specified ``address``."""
2305
        self._update_args(kw)
×
2306
        return packing.u16(self.read(address, 2), *a, **kw)
×
2307

2308
    def u8(self,     address, *a, **kw):
1✔
2309
        """Unpacks an integer from the specified ``address``."""
2310
        self._update_args(kw)
×
2311
        return packing.u8(self.read(address, 1), *a, **kw)
×
2312

2313
    def unpack(self, address, *a, **kw):
1✔
2314
        """Unpacks an integer from the specified ``address``."""
2315
        self._update_args(kw)
1✔
2316
        return packing.unpack(self.read(address, self.bytes), *a, **kw)
1✔
2317

2318
    def string(self, address):
1✔
2319
        """string(address) -> str
2320

2321
        Reads a null-terminated string from the specified ``address``
2322

2323
        Returns:
2324
            A ``str`` with the string contents (NUL terminator is omitted),
2325
            or an empty string if no NUL terminator could be found.
2326
        """
2327
        data = b''
1✔
2328
        while True:
1✔
2329
            read_size = 0x1000
1✔
2330
            partial_page = address & 0xfff
1✔
2331

2332
            if partial_page:
1✔
2333
                read_size -= partial_page
1✔
2334

2335
            c = self.read(address, read_size)
1✔
2336

2337
            if not c:
1!
UNCOV
2338
                return b''
×
2339

2340
            data += c
1✔
2341

2342
            if b'\x00' in c:
1✔
2343
                return data[:data.index(b'\x00')]
1✔
2344

2345
            address += len(c)
1✔
2346

2347
    def flat(self, address, *a, **kw):
1✔
2348
        """Writes a full array of values to the specified address.
2349

2350
        See: :func:`.packing.flat`
2351
        """
UNCOV
2352
        return self.write(address, packing.flat(*a,**kw))
×
2353

2354
    def fit(self, address, *a, **kw):
1✔
2355
        """Writes fitted data into the specified address.
2356

2357
        See: :func:`.packing.fit`
2358
        """
UNCOV
2359
        return self.write(address, packing.fit(*a, **kw))
×
2360

2361
    def parse_kconfig(self, data):
1✔
2362
        self.config.update(parse_kconfig(data))
×
2363

2364
    def disable_nx(self):
1✔
2365
        """Disables NX for the ELF.
2366

2367
        Zeroes out the ``PT_GNU_STACK`` program header ``p_type`` field.
2368
        """
2369
        PT_GNU_STACK = packing.p32(ENUM_P_TYPE['PT_GNU_STACK'])
×
2370

UNCOV
2371
        if not self.executable:
×
2372
            log.error("Can only make stack executable with executables")
×
2373

UNCOV
2374
        for i, segment in enumerate(self.iter_segments()):
×
UNCOV
2375
            if not segment.header.p_type:
×
UNCOV
2376
                continue
×
UNCOV
2377
            if 'GNU_STACK' not in segment.header.p_type:
×
UNCOV
2378
                continue
×
2379

UNCOV
2380
            phoff = self.header.e_phoff
×
2381
            phentsize = self.header.e_phentsize
×
2382
            offset = phoff + phentsize * i
×
2383

2384
            if self.mmap[offset:offset+4] == PT_GNU_STACK:
×
2385
                self.mmap[offset:offset+4] = b'\x00' * 4
×
2386
                self.save()
×
2387
                # Invalidate the cached segments, ``PT_GNU_STACK`` was removed.
2388
                self._segments = None
×
UNCOV
2389
                return
×
2390

2391
        log.error("Could not find PT_GNU_STACK, stack should already be executable")
×
2392
    
2393
    @staticmethod
1✔
2394
    def set_runpath(exepath, runpath):
1✔
2395
        r"""set_runpath(str, str) -> ELF
2396

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

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

2403
        Arguments:
2404
            exepath(str): Path to the binary to patch.
2405
            runpath(str): Path containing the needed libraries.
2406

2407
        Returns:
2408
            A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
2409

2410
        Example:
2411

2412
            >>> tmpdir = tempfile.mkdtemp()
2413
            >>> ls_path = os.path.join(tmpdir, 'ls')
2414
            >>> _ = shutil.copy(which('ls'), ls_path)
2415
            >>> e = ELF.set_runpath(ls_path, './libs')
2416
            >>> e.runpath == b'./libs'
2417
            True
2418
        """
2419
        if not which('patchelf'):
1!
UNCOV
2420
            log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
×
UNCOV
2421
            return None
×
2422
        try:
1✔
2423
            subprocess.check_output(['patchelf', '--set-rpath', runpath, exepath], stderr=subprocess.STDOUT)
1✔
UNCOV
2424
        except subprocess.CalledProcessError as e:
×
UNCOV
2425
            log.failure('Patching RUNPATH failed (%d): %r', e.returncode, e.stdout)
×
2426
        return ELF(exepath, checksec=False)
1✔
2427

2428
    @staticmethod
1✔
2429
    def set_interpreter(exepath, interpreter_path):
1✔
2430
        r"""set_interpreter(str, str) -> ELF
2431

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

2434
        When running the binary, the new interpreter will be used to load the ELF.
2435

2436
        Arguments:
2437
            exepath(str): Path to the binary to patch.
2438
            interpreter_path(str): Path to the ld.so dynamic loader.
2439

2440
        Returns:
2441
            A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
2442

2443
        Example:
2444

2445
            >>> tmpdir = tempfile.mkdtemp()
2446
            >>> ls_path = os.path.join(tmpdir, 'ls')
2447
            >>> _ = shutil.copy(which('ls'), ls_path)
2448
            >>> e = ELF.set_interpreter(ls_path, '/tmp/correct_ld.so')
2449
            >>> e.linker == b'/tmp/correct_ld.so'
2450
            True
2451
        """
2452
        # patch the interpreter
2453
        if not which('patchelf'):
1!
UNCOV
2454
            log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
×
UNCOV
2455
            return None
×
2456
        try:
1✔
2457
            subprocess.check_output(['patchelf', '--set-interpreter', interpreter_path, exepath], stderr=subprocess.STDOUT)
1✔
UNCOV
2458
        except subprocess.CalledProcessError as e:
×
UNCOV
2459
            log.failure('Patching interpreter failed (%d): %r', e.returncode, e.stdout)
×
2460
        return ELF(exepath, checksec=False)
1✔
2461

2462
    @staticmethod
1✔
2463
    def patch_custom_libraries(exe_path, custom_library_path, create_copy=True, suffix='_remotelibc'):
1✔
2464
        r"""patch_custom_libraries(str, str, bool, str) -> ELF
2465

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

2469
        Arguments:
2470
            exe_path(str): Path to the binary to patch.
2471
            custom_library_path(str): Path to a folder containing the libraries.
2472
            create_copy(bool): Create a copy of the binary and apply the patches to the copy.
2473
            suffix(str): Suffix to append to the filename when creating the copy to patch.
2474

2475
        Returns:
2476
            A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
2477

2478
        Example:
2479

2480
            >>> tmpdir = tempfile.mkdtemp()
2481
            >>> linker_path = os.path.join(tmpdir, 'ld-mock.so')
2482
            >>> write(linker_path, b'loader')
2483
            >>> ls_path = os.path.join(tmpdir, 'ls')
2484
            >>> _ = shutil.copy(which('ls'), ls_path)
2485
            >>> e = ELF.patch_custom_libraries(ls_path, tmpdir)
2486
            >>> e.runpath.decode() == tmpdir
2487
            True
2488
            >>> e.linker.decode() == linker_path
2489
            True
2490
        """
2491
        if not which('patchelf'):
1!
UNCOV
2492
            log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
×
UNCOV
2493
            return None
×
2494
        
2495
        # Create a copy of the ELF to patch instead of the original file.
2496
        if create_copy:
1!
2497
            import shutil
1✔
2498
            patched_path = exe_path + suffix
1✔
2499
            shutil.copy2(exe_path, patched_path)
1✔
2500
            exe_path = patched_path
1✔
2501

2502
        # Set interpreter in ELF to the one in the library path.
2503
        interpreter_name = [filename for filename in os.listdir(custom_library_path) if filename.startswith('ld-')]
1✔
2504
        if interpreter_name:
1!
2505
            interpreter_path = os.path.realpath(os.path.join(custom_library_path, interpreter_name[0]))
1✔
2506
            ELF.set_interpreter(exe_path, interpreter_path)
1✔
2507
        else:
UNCOV
2508
            log.warn("Couldn't find ld.so in library path. Interpreter not set.")
×
2509

2510
        # Set RUNPATH to library path in order to find other libraries.
2511
        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