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

Gallopsled / pwntools / 5500924378

pending completion
5500924378

push

github-actions

peace-maker
Update CHANGELOG

3968 of 6659 branches covered (59.59%)

12136 of 16977 relevant lines covered (71.48%)

0.71 hits per line

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

78.74
/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
1✔
51

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

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

67
import intervaltree
1✔
68

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

88
log = getLogger(__name__)
1✔
89

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

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

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

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

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

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

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

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

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

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

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

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

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

145
    Supports recursive instantiation for keys which contain dots.
146

147
    Example:
148

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

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

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

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

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

180
    Example:
181

182
        .. code-block:: python
183

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

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

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

212

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

295
        self.load_addr = self._address
1✔
296

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

374
        Arguments:
375

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

379
        Example:
380

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

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

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

398
        Arguments:
399

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

403
        Example:
404

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

494
        return iter(self._segments)
1✔
495

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

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

513
    def get_segment_for_address(self, address, size=1):
1✔
514
        """get_segment_for_address(address, size=1) -> Segment
515

516
        Given a virtual address described by a ``PT_LOAD`` segment, return the
517
        first segment which describes the virtual address.  An optional ``size``
518
        may be provided to ensure the entire range falls into the same segment.
519

520
        Arguments:
521
            address(int): Virtual address to find
522
            size(int): Number of bytes which must be available after ``address``
523
                in **both** the file-backed data for the segment, and the memory
524
                region which is reserved for the data.
525

526
        Returns:
527
            Either returns a :class:`.segments.Segment` object, or ``None``.
528
        """
529
        for seg in self.iter_segments_by_type("PT_LOAD"):
1!
530
            mem_start = seg.header.p_vaddr
1✔
531
            mem_stop  = seg.header.p_memsz + mem_start
1✔
532

533
            if not (mem_start <= address <= address+size < mem_stop):
1!
534
                continue
×
535

536
            offset = self.vaddr_to_offset(address)
1✔
537

538
            file_start = seg.header.p_offset
1✔
539
            file_stop  = seg.header.p_filesz + file_start
1✔
540

541
            if not (file_start <= offset <= offset+size < file_stop):
1!
542
                continue
×
543

544
            return seg
1✔
545

546
        return None
×
547

548
    def iter_sections(self):
1✔
549
        # Yield and cache all the sections in the file
550
        if self._sections is None:
1✔
551
            self._sections = [self.get_section(i) for i in range(self.num_sections())]
1✔
552

553
        return iter(self._sections)
1✔
554

555
    @property
1✔
556
    def sections(self):
1✔
557
        """
558
        :class:`list`: A list of :class:`elftools.elf.sections.Section` objects
559
            for the segments in the ELF.
560
        """
561
        return list(self.iter_sections())
1✔
562

563
    @property
1✔
564
    def dwarf(self):
1✔
565
        """DWARF info for the elf"""
566
        return self.get_dwarf_info()
×
567

568
    @property
1✔
569
    def sym(self):
1✔
570
        """:class:`dotdict`: Alias for :attr:`.ELF.symbols`"""
571
        return self.symbols
1✔
572

573
    @property
1✔
574
    def address(self):
1✔
575
        """:class:`int`: Address of the lowest segment loaded in the ELF.
576

577
        When updated, the addresses of the following fields are also updated:
578

579
        - :attr:`~.ELF.symbols`
580
        - :attr:`~.ELF.got`
581
        - :attr:`~.ELF.plt`
582
        - :attr:`~.ELF.functions`
583

584
        However, the following fields are **NOT** updated:
585

586
        - :attr:`~.ELF.segments`
587
        - :attr:`~.ELF.sections`
588

589
        Example:
590

591
            >>> bash = ELF('/bin/bash')
592
            >>> read = bash.symbols['read']
593
            >>> text = bash.get_section_by_name('.text').header.sh_addr
594
            >>> bash.address += 0x1000
595
            >>> read + 0x1000 == bash.symbols['read']
596
            True
597
            >>> text == bash.get_section_by_name('.text').header.sh_addr
598
            True
599
        """
600
        return self._address
1✔
601

602
    @address.setter
1✔
603
    def address(self, new):
1✔
604
        delta     = new-self._address
1✔
605
        update    = lambda x: x+delta
1✔
606

607
        self.symbols = dotdict({k:update(v) for k,v in self.symbols.items()})
1✔
608
        self.plt     = dotdict({k:update(v) for k,v in self.plt.items()})
1✔
609
        self.got     = dotdict({k:update(v) for k,v in self.got.items()})
1✔
610
        for f in self.functions.values():
1✔
611
            f.address += delta
1✔
612

613
        # Update our view of memory
614
        memory = intervaltree.IntervalTree()
1✔
615

616
        for begin, end, data in self.memory:
1✔
617
            memory.addi(update(begin),
1✔
618
                        update(end),
619
                        data)
620

621
        self.memory = memory
1✔
622

623
        self._address = update(self.address)
1✔
624

625
    def section(self, name):
1✔
626
        """section(name) -> bytes
627

628
        Gets data for the named section
629

630
        Arguments:
631
            name(str): Name of the section
632

633
        Returns:
634
            :class:`str`: String containing the bytes for that section
635
        """
636
        return self.get_section_by_name(name).data()
×
637

638
    @property
1✔
639
    def rwx_segments(self):
1✔
640
        """:class:`list`: List of all segments which are writeable and executable.
641

642
        See:
643
            :attr:`.ELF.segments`
644
        """
645
        if not self.nx:
1✔
646
            return self.writable_segments
1✔
647

648
        wx = P_FLAGS.PF_X | P_FLAGS.PF_W
1✔
649
        return [s for s in self.segments if s.header.p_flags & wx == wx]
1✔
650

651
    @property
1✔
652
    def executable_segments(self):
1✔
653
        """:class:`list`: List of all segments which are executable.
654

655
        See:
656
            :attr:`.ELF.segments`
657
        """
658
        if not self.nx:
1!
659
            return list(self.segments)
1✔
660

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

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

667
        See:
668
            :attr:`.ELF.segments`
669
        """
670
        return [s for s in self.segments if s.header.p_flags & P_FLAGS.PF_W]
1✔
671

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

676
        See:
677
            :attr:`.ELF.segments`
678
        """
679
        return [s for s in self.segments if not s.header.p_flags & P_FLAGS.PF_W]
×
680

681
    @property
1✔
682
    def libs(self):
1✔
683
        """Dictionary of {path: address} for every library loaded for this ELF."""
684
        if self._libs is None:
1✔
685
            self._populate_libraries()
1✔
686
        return self._libs
1✔
687

688
    @property
1✔
689
    def maps(self):
1✔
690
        """Dictionary of {name: address} for every mapping in this ELF's address space."""
691
        if self._maps is None:
×
692
            self._populate_libraries()
×
693
        return self._maps
×
694

695
    @property
1✔
696
    def libc(self):
1✔
697
        """:class:`.ELF`: If this :class:`.ELF` imports any libraries which contain ``'libc[.-]``,
698
        and we can determine the appropriate path to it on the local
699
        system, returns a new :class:`.ELF` object pertaining to that library.
700

701
        If not found, the value will be :const:`None`.
702
        """
703
        for lib in self.libs:
1!
704
            if '/libc.' in lib or '/libc-' in lib:
1✔
705
                return ELF(lib)
1✔
706

707
    def _populate_libraries(self):
1✔
708
        """
709
        >>> from os.path import exists
710
        >>> bash = ELF(which('bash'))
711
        >>> all(map(exists, bash.libs.keys()))
712
        True
713
        >>> any(map(lambda x: 'libc' in x, bash.libs.keys()))
714
        True
715
        """
716
        # Patch some shellcode into the ELF and run it.
717
        maps = self._patch_elf_and_read_maps()
1✔
718

719
        self._maps = maps
1✔
720
        self._libs = {}
1✔
721

722
        for lib, address in maps.items():
1✔
723

724
            # Filter out [stack] and such from the library listings
725
            if lib.startswith('['):
1✔
726
                continue
1✔
727

728
            # Any existing files we can just use
729
            if os.path.exists(lib):
1!
730
                self._libs[lib] = address
1✔
731

732
            # Try etc/qemu-binfmt, as per Ubuntu
733
            if not self.native:
1!
734
                ld_prefix = qemu.ld_prefix()
×
735

736
                qemu_lib = os.path.join(ld_prefix, lib)
×
737
                qemu_lib = os.path.realpath(qemu_lib)
×
738

739
                if os.path.exists(qemu_lib):
×
740
                    self._libs[qemu_lib] = address
×
741

742
    def _patch_elf_and_read_maps(self):
1✔
743
        r"""patch_elf_and_read_maps(self) -> dict
744

745
        Read ``/proc/self/maps`` as if the ELF were executing.
746

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

750
        Returns:
751
            A ``dict`` mapping file paths to the lowest address they appear at.
752
            Does not do any translation for e.g. QEMU emulation, the raw results
753
            are returned.
754

755
            If there is not enough space to inject the shellcode in the segment
756
            which contains the entry point, returns ``{}``.
757

758
        Doctests:
759

760
            These tests are just to ensure that our shellcode is correct.
761

762
            >>> for arch in CAT_PROC_MAPS_EXIT:
763
            ...   context.clear()
764
            ...   with context.local(arch=arch):
765
            ...     sc = shellcraft.cat2("/proc/self/maps")
766
            ...     sc += shellcraft.exit()
767
            ...     sc = asm(sc)
768
            ...     sc = enhex(sc)
769
            ...     assert sc == CAT_PROC_MAPS_EXIT[arch], (arch, sc)
770
        """
771

772
        # Get our shellcode
773
        sc = CAT_PROC_MAPS_EXIT.get(self.arch, None)
1✔
774

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

778
        sc = unhex(sc)
1✔
779

780
        # Ensure there is enough room in the segment where the entry point resides
781
        # in order to inject our shellcode.
782
        seg = self.get_segment_for_address(self.entry, len(sc))
1✔
783
        if not seg:
1!
784
            log.warn_once("Could not inject code to determine memory mapping for %r: Not enough space", self)
×
785
            return {}
×
786

787
        # Create our temporary file
788
        # NOTE: We cannot use "with NamedTemporaryFile() as foo", because we cannot
789
        # execute the file while the handle is open.
790
        fd, path = tempfile.mkstemp()
1✔
791

792
        # Close the file descriptor so that it may be executed
793
        os.close(fd)
1✔
794

795
        # Save off a copy of the ELF
796
        self.save(path)
1✔
797

798
        # Load a new copy of the ELF at the temporary file location
799
        old = self.read(self.entry, len(sc))
1✔
800
        try:
1✔
801
            self.write(self.entry, sc)
1✔
802
            self.save(path)
1✔
803
        finally:
804
            # Restore the original contents
805
            self.write(self.entry, old)
1✔
806

807
        # Make the file executable
808
        os.chmod(path, 0o755)
1✔
809

810
        # Run a copy of it, get the maps
811
        try:
1✔
812
            with context.silent:
1✔
813
                io = process(path)
1✔
814
                data = packing._decode(io.recvall(timeout=2))
1✔
815
        except Exception:
×
816
            log.warn_once("Injected /proc/self/maps code did not execute correctly")
×
817
            return {}
×
818

819
        # Swap in the original ELF name
820
        data = data.replace(path, self.path)
1✔
821

822
        # All we care about in the data is the load address of each file-backed mapping,
823
        # or each kernel-supplied mapping.
824
        #
825
        # For quick reference, the data looks like this:
826
        # 7fcb025f2000-7fcb025f3000 r--p 00025000 fe:01 3025685  /lib/x86_64-linux-gnu/ld-2.23.so
827
        # 7fcb025f3000-7fcb025f4000 rw-p 00026000 fe:01 3025685  /lib/x86_64-linux-gnu/ld-2.23.so
828
        # 7fcb025f4000-7fcb025f5000 rw-p 00000000 00:00 0
829
        # 7ffe39cd4000-7ffe39cf6000 rw-p 00000000 00:00 0        [stack]
830
        # 7ffe39d05000-7ffe39d07000 r--p 00000000 00:00 0        [vvar]
831
        result = {}
1✔
832
        for line in data.splitlines():
1✔
833
            if '/' in line:
1✔
834
                index = line.index('/')
1✔
835
            elif '[' in line:
1!
836
                index = line.index('[')
1✔
837
            else:
838
                continue
×
839

840
            address, _ = line.split('-', 1)
1✔
841

842
            address = int(address, 0x10)
1✔
843
            name = line[index:]
1✔
844

845
            result.setdefault(name, address)
1✔
846

847
        # Remove the temporary file, best-effort
848
        os.unlink(path)
1✔
849

850
        return result
1✔
851

852
    def _populate_functions(self):
1✔
853
        """Builds a dict of 'functions' (i.e. symbols of type 'STT_FUNC')
854
        by function name that map to a tuple consisting of the func address and size
855
        in bytes.
856
        """
857
        for sec in self.sections:
1✔
858
            if not isinstance(sec, SymbolTableSection):
1✔
859
                continue
1✔
860

861
            for sym in _iter_symbols(sec):
1✔
862
                # Avoid duplicates
863
                if sym.name in self.functions:
1✔
864
                    continue
1✔
865
                if sym.entry.st_info['type'] == 'STT_FUNC' and sym.entry.st_size != 0:
1✔
866
                    name = sym.name
1✔
867
                    if name not in self.symbols:
1!
868
                        continue
×
869
                    addr = self.symbols[name]
1✔
870
                    size = sym.entry.st_size
1✔
871
                    self.functions[name] = Function(name, addr, size, self)
1✔
872

873
    def _populate_symbols(self):
1✔
874
        """
875
        >>> bash = ELF(which('bash'))
876
        >>> bash.symbols['_start'] == bash.entry
877
        True
878
        """
879

880
        # Populate all of the "normal" symbols from the symbol tables
881
        for section in self.sections:
1✔
882
            if not isinstance(section, SymbolTableSection):
1✔
883
                continue
1✔
884

885
            for symbol in _iter_symbols(section):
1✔
886
                value = symbol.entry.st_value
1✔
887
                if not value:
1✔
888
                    continue
1✔
889
                self.symbols[symbol.name] = value
1✔
890

891
    def _populate_synthetic_symbols(self):
1✔
892
        """Adds symbols from the GOT and PLT to the symbols dictionary.
893

894
        Does not overwrite any existing symbols, and prefers PLT symbols.
895

896
        Synthetic plt.xxx and got.xxx symbols are added for each PLT and
897
        GOT entry, respectively.
898

899
        Example:bash.
900

901
            >>> bash = ELF(which('bash'))
902
            >>> bash.symbols.wcscmp == bash.plt.wcscmp
903
            True
904
            >>> bash.symbols.wcscmp == bash.symbols.plt.wcscmp
905
            True
906
            >>> bash.symbols.stdin  == bash.got.stdin
907
            True
908
            >>> bash.symbols.stdin  == bash.symbols.got.stdin
909
            True
910
        """
911
        for symbol, address in self.plt.items():
1✔
912
            self.symbols.setdefault(symbol, address)
1✔
913
            self.symbols['plt.' + symbol] = address
1✔
914

915
        for symbol, address in self.got.items():
1✔
916
            self.symbols.setdefault(symbol, address)
1✔
917
            self.symbols['got.' + symbol] = address
1✔
918

919
    def _populate_got(self):
1✔
920
        """Loads the symbols for all relocations"""
921
        # Statically linked implies no relocations, since there is no linker
922
        # Could always be self-relocating like Android's linker *shrug*
923
        if self.statically_linked:
1✔
924
            return
1✔
925

926
        for section in self.sections:
1✔
927
            # We are only interested in relocations
928
            if not isinstance(section, RelocationSection):
1✔
929
                continue
1✔
930

931
            # Only get relocations which link to another section (for symbols)
932
            if section.header.sh_link == SHN_INDICES.SHN_UNDEF:
1!
933
                continue
×
934

935
            symbols = self.get_section(section.header.sh_link)
1✔
936

937
            for rel in section.iter_relocations():
1✔
938
                sym_idx  = rel.entry.r_info_sym
1✔
939

940
                if not sym_idx:
1✔
941
                    continue
1✔
942

943
                symbol = symbols.get_symbol(sym_idx)
1✔
944

945
                if symbol and symbol.name:
1!
946
                    self.got[symbol.name] = rel.entry.r_offset
1✔
947

948
        if self.arch == 'mips':
1✔
949
            try:
1✔
950
                self._populate_mips_got()
1✔
951
            except Exception as e:
×
952
                log.warn("Could not populate MIPS GOT: %s", e)
×
953

954
        if not self.got:
1✔
955
            log.warn("Did not find any GOT entries")
1✔
956

957
    def _populate_mips_got(self):
1✔
958
        self._mips_got = {}
1✔
959
        strings = self.get_section(self.header.e_shstrndx)
1✔
960

961
        ELF_MIPS_GNU_GOT1_MASK = 0x80000000
1✔
962

963
        if self.bits == 64:
1!
964
            ELF_MIPS_GNU_GOT1_MASK <<= 32
×
965

966
        # Beginning of the GOT
967
        got = self.dynamic_value_by_tag('DT_PLTGOT') or 0
1✔
968

969
        # Find the beginning of the GOT pointers
970
        got1_mask = (self.unpack(got) & ELF_MIPS_GNU_GOT1_MASK)
1✔
971
        i = 2 if got1_mask else 1
1✔
972
        self._mips_skip = i
1✔
973

974
        # We don't care about local GOT entries, skip them
975
        local_gotno = self.dynamic_value_by_tag('DT_MIPS_LOCAL_GOTNO')
1✔
976
        got += local_gotno * context.bytes
1✔
977

978
        # Iterate over the dynamic symbol table
979
        dynsym = self.get_section_by_name('.dynsym')
1✔
980
        symbol_iter = _iter_symbols(dynsym)
1✔
981

982
        # 'gotsym' is the index of the first GOT symbol
983
        gotsym = self.dynamic_value_by_tag('DT_MIPS_GOTSYM')
1✔
984
        for i in range(gotsym):
1✔
985
            next(symbol_iter)
1✔
986

987
        # 'symtabno' is the total number of symbols
988
        symtabno = self.dynamic_value_by_tag('DT_MIPS_SYMTABNO')
1✔
989

990
        for i in range(symtabno - gotsym):
1✔
991
            symbol = next(symbol_iter)
1✔
992
            self._mips_got[i + gotsym] = got
1✔
993
            self.got[symbol.name] = got
1✔
994
            got += self.bytes
1✔
995

996
    def _populate_plt(self):
1✔
997
        """Loads the PLT symbols
998

999
        >>> path = pwnlib.data.elf.path
1000
        >>> for test in glob(os.path.join(path, 'test-*')):
1001
        ...     test = ELF(test)
1002
        ...     assert '__stack_chk_fail' in test.got, test
1003
        ...     if test.arch != 'ppc':
1004
        ...         assert '__stack_chk_fail' in test.plt, test
1005
        """
1006
        if self.statically_linked:
1✔
1007
            log.debug("%r is statically linked, skipping GOT/PLT symbols" % self.path)
1✔
1008
            return
1✔
1009

1010
        if not self.got:
1✔
1011
            log.debug("%r doesn't have any GOT symbols, skipping PLT" % self.path)
1✔
1012
            return
1✔
1013

1014
        # This element holds an address associated with the procedure linkage table
1015
        # and/or the global offset table.
1016
        #
1017
        # Zach's note: This corresponds to the ".got.plt" section, in a PIE non-RELRO binary.
1018
        #              This corresponds to the ".got" section, in a PIE full-RELRO binary.
1019
        #              In particular, this is where EBX points when it points into the GOT.
1020
        dt_pltgot = self.dynamic_value_by_tag('DT_PLTGOT') or 0
1✔
1021

1022
        # There are three PLTs we may need to search
1023
        plt = self.get_section_by_name('.plt')          # <-- Functions only
1✔
1024
        plt_got = self.get_section_by_name('.plt.got')  # <-- Functions used as data
1✔
1025
        plt_sec = self.get_section_by_name('.plt.sec')
1✔
1026
        plt_mips = self.get_section_by_name('.MIPS.stubs')
1✔
1027

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

1032
        with context.local(arch=self.arch, bits=self.bits, endian=self.endian):
1✔
1033
            for section in (plt, plt_got, plt_sec, plt_mips):
1✔
1034
                if not section:
1✔
1035
                    continue
1✔
1036

1037
                res = emulate_plt_instructions(self,
1✔
1038
                                                dt_pltgot,
1039
                                                section.header.sh_addr,
1040
                                                section.data(),
1041
                                                inv_symbols)
1042

1043
                for address, target in sorted(res.items()):
1✔
1044
                    self.plt[inv_symbols[target]] = address
1✔
1045

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

1049
    def _populate_kernel_version(self):
1✔
1050
        if 'linux_banner' not in self.symbols:
1!
1051
            return
1✔
1052

1053
        banner = self.string(self.symbols.linux_banner)
×
1054

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

1058
        # 'Linux version 3.18.31-gd0846ecc
1059
        regex = r'Linux version (\S+)'
×
1060
        match = re.search(regex, banner)
×
1061

1062
        if match:
×
1063
            version = match.group(1)
×
1064

1065
            if '-' in version:
×
1066
                version, self.build = version.split('-', 1)
×
1067

1068
            self.version = list(map(int, version.rstrip('+').split('.')))
×
1069

1070
        self.config['version'] = self.version
×
1071

1072
    @property
1✔
1073
    def libc_start_main_return(self):
1✔
1074
        """:class:`int`: Address of the return address into __libc_start_main from main.
1075

1076
        >>> bash = ELF(which('bash'))
1077
        >>> libc = bash.libc
1078
        >>> libc.libc_start_main_return > 0
1079
        True
1080

1081
        Try to find the return address from main into __libc_start_main.
1082
        The heuristic to find the call to the function pointer of main is
1083
        to list all calls inside __libc_start_main, find the call to exit
1084
        after the call to main and select the previous call.
1085
        """
1086
        if '__libc_start_main' not in self.functions:
1!
1087
            return 0
×
1088

1089
        if 'exit' not in self.symbols:
1!
1090
            return 0
×
1091

1092
        # If there's no delay slot, execution continues on the next instruction after a call.
1093
        call_return_offset = 1
1✔
1094
        if self.arch in ['arm', 'thumb']:
1!
1095
            call_instructions = set(['blx', 'bl'])
×
1096
        elif self.arch == 'aarch64':
1!
1097
            call_instructions = set(['blr', 'bl'])
×
1098
        elif self.arch in ['mips', 'mips64']:
1!
1099
            call_instructions = set(['bal', 'jalr'])
×
1100
            # Account for the delay slot.
1101
            call_return_offset = 2
×
1102
        elif self.arch in ['i386', 'amd64', 'ia64']:
1!
1103
            call_instructions = set(['call'])
1✔
1104
        else:
1105
            log.error('Unsupported architecture %s in ELF.libc_start_main_return', self.arch)
×
1106
            return 0
×
1107

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

1112
        def find_ret_main_addr(lines, calls):
1✔
1113
            exit_calls = [index for index, line in enumerate(calls) if exit_addr in line[1]]
1✔
1114
            if len(exit_calls) != 1:
1✔
1115
                return 0
1✔
1116

1117
            call_to_main = calls[exit_calls[0] - 1]
1✔
1118
            return_from_main = lines[call_to_main[0] + call_return_offset].lstrip()
1✔
1119
            return_from_main = int(return_from_main[ : return_from_main.index(':') ], 16)
1✔
1120
            return return_from_main
1✔
1121

1122
        # Starting with glibc-2.34 calling `main` is split out into `__libc_start_call_main`
1123
        ret_addr = find_ret_main_addr(lines, calls)
1✔
1124
        # Pre glibc-2.34 case - `main` is called directly
1125
        if ret_addr:
1!
1126
            return ret_addr
×
1127

1128
        # `__libc_start_main` -> `__libc_start_call_main` -> `main`
1129
        # Find a direct call which calls `exit` once. That's probably `__libc_start_call_main`.
1130
        direct_call_pattern = re.compile(r'['+r'|'.join(call_instructions)+r']\s+(0x[0-9a-zA-Z]+)')
1✔
1131
        for line in calls:
1!
1132
            match = direct_call_pattern.search(line[1])
1✔
1133
            if not match:
1✔
1134
                continue
1✔
1135

1136
            target_addr = int(match.group(1), 0)
1✔
1137
            # `__libc_start_call_main` is usually smaller than `__libc_start_main`, so
1138
            # we might disassemble a bit too much, but it's a good dynamic estimate.
1139
            callee_lines = self.disasm(target_addr, self.functions['__libc_start_main'].size).split('\n')
1✔
1140
            callee_calls = [(index, line) for index, line in enumerate(callee_lines) if set(line.split()) & call_instructions]
1✔
1141
            ret_addr = find_ret_main_addr(callee_lines, callee_calls)
1✔
1142
            if ret_addr:
1✔
1143
                return ret_addr
1✔
1144
        return 0
×
1145

1146
    def search(self, needle, writable = False, executable = False):
1✔
1147
        """search(needle, writable = False, executable = False) -> generator
1148

1149
        Search the ELF's virtual address space for the specified string.
1150

1151
        Notes:
1152
            Does not search empty space between segments, or uninitialized
1153
            data.  This will only return data that actually exists in the
1154
            ELF file.  Searching for a long string of NULL bytes probably
1155
            won't work.
1156

1157
        Arguments:
1158
            needle(bytes): String to search for.
1159
            writable(bool): Search only writable sections.
1160
            executable(bool): Search only executable sections.
1161

1162
        Yields:
1163
            An iterator for each virtual address that matches.
1164

1165
        Examples:
1166

1167
            An ELF header starts with the bytes ``\\x7fELF``, so we
1168
            sould be able to find it easily.
1169

1170
            >>> bash = ELF('/bin/bash')
1171
            >>> bash.address + 1 == next(bash.search(b'ELF'))
1172
            True
1173

1174
            We can also search for string the binary.
1175

1176
            >>> len(list(bash.search(b'GNU bash'))) > 0
1177
            True
1178

1179
            It is also possible to search for instructions in executable sections.
1180

1181
            >>> binary = ELF.from_assembly('nop; mov eax, 0; jmp esp; ret')
1182
            >>> jmp_addr = next(binary.search(asm('jmp esp'), executable = True))
1183
            >>> binary.read(jmp_addr, 2) == asm('jmp esp')
1184
            True
1185
        """
1186
        load_address_fixup = (self.address - self.load_addr)
1✔
1187

1188
        if writable:
1!
1189
            segments = self.writable_segments
×
1190
        elif executable:
1✔
1191
            segments = self.executable_segments
1✔
1192
        else:
1193
            segments = self.segments
1✔
1194
        needle = packing._need_bytes(needle)
1✔
1195
        for seg in segments:
1✔
1196
            addr   = seg.header.p_vaddr
1✔
1197
            memsz  = seg.header.p_memsz
1✔
1198
            zeroed = memsz - seg.header.p_filesz
1✔
1199
            offset = seg.header.p_offset
1✔
1200
            data   = self.mmap[offset:offset+memsz]
1✔
1201
            data   += b'\x00' * zeroed
1✔
1202
            offset = 0
1✔
1203
            while True:
1204
                offset = data.find(needle, offset)
1✔
1205
                if offset == -1:
1✔
1206
                    break
1✔
1207
                yield (addr + offset + load_address_fixup)
1✔
1208
                offset += 1
1✔
1209

1210
    def offset_to_vaddr(self, offset):
1✔
1211
        """offset_to_vaddr(offset) -> int
1212

1213
        Translates the specified offset to a virtual address.
1214

1215
        Arguments:
1216
            offset(int): Offset to translate
1217

1218
        Returns:
1219
            `int`: Virtual address which corresponds to the file offset, or
1220
            :const:`None`.
1221

1222
        Examples:
1223

1224
            This example shows that regardless of changes to the virtual
1225
            address layout by modifying :attr:`.ELF.address`, the offset
1226
            for any given address doesn't change.
1227

1228
            >>> bash = ELF('/bin/bash')
1229
            >>> bash.address == bash.offset_to_vaddr(0)
1230
            True
1231
            >>> bash.address += 0x123456
1232
            >>> bash.address == bash.offset_to_vaddr(0)
1233
            True
1234
        """
1235
        load_address_fixup = (self.address - self.load_addr)
1✔
1236

1237
        for segment in self.segments:
1!
1238
            begin = segment.header.p_offset
1✔
1239
            size  = segment.header.p_filesz
1✔
1240
            end   = begin + size
1✔
1241
            if begin <= offset and offset <= end:
1✔
1242
                delta = offset - begin
1✔
1243
                return segment.header.p_vaddr + delta + load_address_fixup
1✔
1244
        return None
×
1245

1246
    def _populate_memory(self):
1✔
1247
        load_segments = list(filter(lambda s: s.header.p_type == 'PT_LOAD', self.iter_segments()))
1✔
1248

1249
        # Map all of the segments
1250
        for i, segment in enumerate(load_segments):
1✔
1251
            start = segment.header.p_vaddr
1✔
1252
            stop_data = start + segment.header.p_filesz
1✔
1253
            stop_mem  = start + segment.header.p_memsz
1✔
1254

1255
            # Chop any existing segments which cover the range described by
1256
            # [vaddr, vaddr+filesz].
1257
            #
1258
            # This has the effect of removing any issues we may encounter
1259
            # with "overlapping" segments, by giving precedence to whichever
1260
            # DT_LOAD segment is **last** to load data into the region.
1261
            self.memory.chop(start, stop_data)
1✔
1262

1263
            # Fill the start of the segment's first page
1264
            page_start = align_down(0x1000, start)
1✔
1265
            if page_start < start and not self.memory[page_start]:
1!
1266
                self.memory.addi(page_start, start, None)
×
1267

1268
            # Add the new segment
1269
            if start != stop_data:
1✔
1270
                self.memory.addi(start, stop_data, segment)
1✔
1271

1272
            if stop_data != stop_mem:
1✔
1273
                self.memory.addi(stop_data, stop_mem, b'\x00')
1✔
1274

1275
            page_end = align(0x1000, stop_mem)
1✔
1276

1277
            # Check for holes which we can fill
1278
            if self._fill_gaps and i+1 < len(load_segments):
1✔
1279
                next_start = load_segments[i+1].header.p_vaddr
1✔
1280
                page_next = align_down(0x1000, next_start)
1✔
1281

1282
                if stop_mem < next_start:
1✔
1283
                    if page_end < page_next:
1✔
1284
                        if stop_mem < page_end:
1!
1285
                            self.memory.addi(stop_mem, page_end, None)
1✔
1286
                        if page_next < next_start:
1✔
1287
                            self.memory.addi(page_next, next_start, None)
1✔
1288
                    else:
1289
                        self.memory.addi(stop_mem, next_start, None)
1✔
1290
            else:
1291
                if stop_mem < page_end:
1✔
1292
                    self.memory.addi(stop_mem, page_end, None)
1✔
1293

1294
    def vaddr_to_offset(self, address):
1✔
1295
        """vaddr_to_offset(address) -> int
1296

1297
        Translates the specified virtual address to a file offset
1298

1299
        Arguments:
1300
            address(int): Virtual address to translate
1301

1302
        Returns:
1303
            int: Offset within the ELF file which corresponds to the address,
1304
            or :const:`None`.
1305

1306
        Examples:
1307
            >>> bash = ELF(which('bash'))
1308
            >>> bash.vaddr_to_offset(bash.address)
1309
            0
1310
            >>> bash.address += 0x123456
1311
            >>> bash.vaddr_to_offset(bash.address)
1312
            0
1313
            >>> bash.vaddr_to_offset(0) is None
1314
            True
1315
        """
1316

1317
        for interval in self.memory[address]:
1✔
1318
            segment = interval.data
1✔
1319

1320
            # Convert the address back to how it was when the segment was loaded
1321
            address = (address - self.address) + self.load_addr
1✔
1322

1323
            # Figure out the offset into the segment
1324
            offset = address - segment.header.p_vaddr
1✔
1325

1326
            # Add the segment-base offset to the offset-within-the-segment
1327
            return segment.header.p_offset + offset
1✔
1328

1329
    def read(self, address, count):
1✔
1330
        r"""read(address, count) -> bytes
1331

1332
        Read data from the specified virtual address
1333

1334
        Arguments:
1335
            address(int): Virtual address to read
1336
            count(int): Number of bytes to read
1337

1338
        Returns:
1339
            A :class:`bytes` object, or :const:`None`.
1340

1341
        Examples:
1342
            The simplest example is just to read the ELF header.
1343

1344
            >>> bash = ELF(which('bash'))
1345
            >>> bash.read(bash.address, 4)
1346
            b'\x7fELF'
1347

1348
            ELF segments do not have to contain all of the data on-disk
1349
            that gets loaded into memory.
1350

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

1353
            >>> assembly = '''
1354
            ... .section .A,"awx"
1355
            ... .global A
1356
            ... A: nop
1357
            ... .section .B,"awx"
1358
            ... .global B
1359
            ... B: int3
1360
            ... '''
1361
            >>> e = ELF.from_assembly(assembly, vma=False)
1362

1363
            By default, these come right after eachother in memory.
1364

1365
            >>> e.read(e.symbols.A, 2)
1366
            b'\x90\xcc'
1367
            >>> e.symbols.B - e.symbols.A
1368
            1
1369

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

1372
            >>> objcopy = pwnlib.asm._objcopy()
1373
            >>> objcopy += [
1374
            ...     '--change-section-vma', '.B+5',
1375
            ...     '--change-section-lma', '.B+5',
1376
            ...     e.path
1377
            ... ]
1378
            >>> subprocess.check_call(objcopy)
1379
            0
1380

1381
            Now let's re-load the ELF, and check again
1382

1383
            >>> e = ELF(e.path)
1384
            >>> e.symbols.B - e.symbols.A
1385
            6
1386
            >>> e.read(e.symbols.A, 2)
1387
            b'\x90\x00'
1388
            >>> e.read(e.symbols.A, 7)
1389
            b'\x90\x00\x00\x00\x00\x00\xcc'
1390
            >>> e.read(e.symbols.A, 10)
1391
            b'\x90\x00\x00\x00\x00\x00\xcc\x00\x00\x00'
1392

1393
            Everything is relative to the user-selected base address, so moving
1394
            things around keeps everything working.
1395

1396
            >>> e.address += 0x1000
1397
            >>> e.read(e.symbols.A, 10)
1398
            b'\x90\x00\x00\x00\x00\x00\xcc\x00\x00\x00'
1399
        """
1400
        retval = []
1✔
1401

1402
        if count == 0:
1!
1403
            return b''
×
1404

1405
        start = address
1✔
1406
        stop = address + count
1✔
1407

1408
        overlap = self.memory.overlap(start, stop)
1✔
1409

1410
        # Create a new view of memory, for just what we need
1411
        memory = intervaltree.IntervalTree(overlap)
1✔
1412
        memory.chop(-1<<64, start)
1✔
1413
        memory.chop(stop, 1<<64)
1✔
1414

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

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

1421
        # We have a view of memory which lets us get everything we need
1422
        for begin, end, data in sorted(memory):
1✔
1423
            length = end-begin
1✔
1424

1425
            if data in (None, b'\x00'):
1✔
1426
                retval.append(b'\x00' * length)
1✔
1427
                continue
1✔
1428

1429
            # Offset within VMA range
1430
            begin -= self.address
1✔
1431

1432
            # Adjust to original VMA range
1433
            begin += self.load_addr
1✔
1434

1435
            # Adjust to offset within segment VMA
1436
            offset = begin - data.header.p_vaddr
1✔
1437

1438
            # Adjust in-segment offset to in-file offset
1439
            offset += data.header.p_offset
1✔
1440

1441
            retval.append(self.mmap[offset:offset+length])
1✔
1442

1443
        return b''.join(retval)
1✔
1444

1445
    def write(self, address, data):
1✔
1446
        """Writes data to the specified virtual address
1447

1448
        Arguments:
1449
            address(int): Virtual address to write
1450
            data(str): Bytes to write
1451

1452
        Note:
1453
            This routine does not check the bounds on the write to ensure
1454
            that it stays in the same segment.
1455

1456
        Examples:
1457
          >>> bash = ELF(which('bash'))
1458
          >>> bash.read(bash.address+1, 3)
1459
          b'ELF'
1460
          >>> bash.write(bash.address, b"HELO")
1461
          >>> bash.read(bash.address, 4)
1462
          b'HELO'
1463
        """
1464
        offset = self.vaddr_to_offset(address)
1✔
1465

1466
        if offset is not None:
1!
1467
            length = len(data)
1✔
1468
            self.mmap[offset:offset+length] = data
1✔
1469

1470
        return None
1✔
1471

1472
    def save(self, path=None):
1✔
1473
        """Save the ELF to a file
1474

1475
        >>> bash = ELF(which('bash'))
1476
        >>> bash.save('/tmp/bash_copy')
1477
        >>> copy = open('/tmp/bash_copy', 'rb')
1478
        >>> bash = open(which('bash'), 'rb')
1479
        >>> bash.read() == copy.read()
1480
        True
1481
        """
1482
        if path is None:
1✔
1483
            path = self.path
1✔
1484
        misc.write(path, self.data)
1✔
1485

1486
    def get_data(self):
1✔
1487
        """get_data() -> bytes
1488

1489
        Retrieve the raw data from the ELF file.
1490

1491
        >>> bash = ELF(which('bash'))
1492
        >>> fd   = open(which('bash'), 'rb')
1493
        >>> bash.get_data() == fd.read()
1494
        True
1495
        """
1496
        return self.mmap[:]
1✔
1497

1498
    @property
1✔
1499
    def data(self):
1✔
1500
        """:class:`bytes`: Raw data of the ELF file.
1501

1502
        See:
1503
            :meth:`get_data`
1504
        """
1505
        return self.mmap[:]
1✔
1506

1507
    def disasm(self, address, n_bytes):
1✔
1508
        """disasm(address, n_bytes) -> str
1509

1510
        Returns a string of disassembled instructions at
1511
        the specified virtual memory address"""
1512
        arch = self.arch
1✔
1513
        if self.arch == 'arm' and address & 1:
1!
1514
            arch = 'thumb'
×
1515
            address -= 1
×
1516

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

1519
    def asm(self, address, assembly):
1✔
1520
        """asm(address, assembly)
1521

1522
        Assembles the specified instructions and inserts them
1523
        into the ELF at the specified address.
1524

1525
        This modifies the ELF in-place.
1526
        The resulting binary can be saved with :meth:`.ELF.save`
1527
        """
1528
        binary = asm(assembly, vma=address, arch=self.arch, endian=self.endian, bits=self.bits)
1✔
1529
        self.write(address, binary)
1✔
1530

1531
    def bss(self, offset=0):
1✔
1532
        """bss(offset=0) -> int
1533

1534
        Returns:
1535
            Address of the ``.bss`` section, plus the specified offset.
1536
        """
1537
        orig_bss = self.get_section_by_name('.bss').header.sh_addr
×
1538
        curr_bss = orig_bss - self.load_addr + self.address
×
1539
        return curr_bss + offset
×
1540

1541
    def __repr__(self):
1✔
1542
        return "%s(%r)" % (self.__class__.__name__, self.path)
1✔
1543

1544
    def dynamic_by_tag(self, tag):
1✔
1545
        """dynamic_by_tag(tag) -> tag
1546

1547
        Arguments:
1548
            tag(str): Named ``DT_XXX`` tag (e.g. ``'DT_STRTAB'``).
1549

1550
        Returns:
1551
            :class:`elftools.elf.dynamic.DynamicTag`
1552
        """
1553
        dt      = None
1✔
1554
        dynamic = self.get_section_by_name('.dynamic')
1✔
1555

1556
        if not dynamic:
1✔
1557
            return None
1✔
1558

1559
        try:
1✔
1560
            dt = next(t for t in dynamic.iter_tags() if tag == t.entry.d_tag)
1✔
1561
        except StopIteration:
1✔
1562
            pass
1✔
1563

1564
        return dt
1✔
1565

1566
    def dynamic_value_by_tag(self, tag):
1✔
1567
        """dynamic_value_by_tag(tag) -> int
1568

1569
        Retrieve the value from a dynamic tag a la ``DT_XXX``.
1570

1571
        If the tag is missing, returns ``None``.
1572
        """
1573
        tag = self.dynamic_by_tag(tag)
1✔
1574

1575
        if tag:
1✔
1576
            return tag.entry.d_val
1✔
1577

1578
    def dynamic_string(self, offset):
1✔
1579
        """dynamic_string(offset) -> bytes
1580

1581
        Fetches an enumerated string from the ``DT_STRTAB`` table.
1582

1583
        Arguments:
1584
            offset(int): String index
1585

1586
        Returns:
1587
            :class:`str`: String from the table as raw bytes.
1588
        """
1589
        dt_strtab = self.dynamic_by_tag('DT_STRTAB')
×
1590

1591
        if not dt_strtab:
×
1592
            return None
×
1593

1594
        address   = dt_strtab.entry.d_ptr + offset
×
1595
        string    = b''
×
1596
        while b'\x00' not in string:
×
1597
            string  += self.read(address, 1)
×
1598
            address += 1
×
1599
        return string.rstrip(b'\x00')
×
1600

1601

1602

1603
    @property
1✔
1604
    def relro(self):
1✔
1605
        """:class:`bool`: Whether the current binary uses RELRO protections.
1606

1607
        This requires both presence of the dynamic tag ``DT_BIND_NOW``, and
1608
        a ``GNU_RELRO`` program header.
1609

1610
        The `ELF Specification`_ describes how the linker should resolve
1611
        symbols immediately, as soon as a binary is loaded.  This can be
1612
        emulated with the ``LD_BIND_NOW=1`` environment variable.
1613

1614
            ``DT_BIND_NOW``
1615

1616
            If present in a shared object or executable, this entry instructs
1617
            the dynamic linker to process all relocations for the object
1618
            containing this entry before transferring control to the program.
1619
            The presence of this entry takes precedence over a directive to use
1620
            lazy binding for this object when specified through the environment
1621
            or via ``dlopen(BA_LIB)``.
1622

1623
            (`page 81`_)
1624

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

1629
        Finally, a new-ish extension which doesn't seem to have a canonical
1630
        source of documentation is DF_BIND_NOW_, which has supposedly superceded
1631
        ``DT_BIND_NOW``.
1632

1633
            ``DF_BIND_NOW``
1634

1635
            If set in a shared object or executable, this flag instructs the
1636
            dynamic linker to process all relocations for the object containing
1637
            this entry before transferring control to the program. The presence
1638
            of this entry takes precedence over a directive to use lazy binding
1639
            for this object when specified through the environment or via
1640
            ``dlopen(BA_LIB)``.
1641

1642
        .. _ELF Specification: https://refspecs.linuxbase.org/elf/elf.pdf
1643
        .. _page 81: https://refspecs.linuxbase.org/elf/elf.pdf#page=81
1644
        .. _DT_BIND_NOW: https://refspecs.linuxbase.org/elf/elf.pdf#page=81
1645
        .. _PT_GNU_RELRO: https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic.html#PROGHEADER
1646
        .. _DF_BIND_NOW: https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html#df_bind_now
1647

1648
        >>> path = pwnlib.data.elf.relro.path
1649
        >>> for test in glob(os.path.join(path, 'test-*')):
1650
        ...     e = ELF(test)
1651
        ...     expected = os.path.basename(test).split('-')[2]
1652
        ...     actual = str(e.relro).lower()
1653
        ...     assert actual == expected
1654
        """
1655
        if not any('GNU_RELRO' in str(s.header.p_type) for s in self.segments):
1✔
1656
            return None
1✔
1657

1658
        if self.dynamic_by_tag('DT_BIND_NOW'):
1✔
1659
            return "Full"
1✔
1660

1661
        flags = self.dynamic_value_by_tag('DT_FLAGS')
1✔
1662
        if flags and flags & constants.DF_BIND_NOW:
1✔
1663
            return "Full"
1✔
1664

1665
        flags_1 = self.dynamic_value_by_tag('DT_FLAGS_1')
1✔
1666
        if flags_1 and flags_1 & constants.DF_1_NOW:
1!
1667
            return "Full"
×
1668

1669
        return "Partial"
1✔
1670

1671
    @property
1✔
1672
    def nx(self):
1✔
1673
        """:class:`bool`: Whether the current binary uses NX protections.
1674

1675
        Specifically, we are checking for ``READ_IMPLIES_EXEC`` being set
1676
        by the kernel, as a result of honoring ``PT_GNU_STACK`` in the kernel.
1677

1678
        ``READ_IMPLIES_EXEC`` is set, according to a set of architecture specific
1679
        rules, that depend on the CPU features, and the presence of ``PT_GNU_STACK``.
1680

1681
        Unfortunately, :class:`ELF` is not context-aware, so it's not always possible
1682
        to determine whether the process of a binary that's missing ``PT_GNU_STACK``
1683
        will have NX or not.
1684
        
1685
        The rules are as follows:
1686

1687
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1688
            | ELF arch  | linux        | GNU_STACK                 | other                                          | NX       |
1689
            +===========+==============+===========================+================================================+==========+
1690
            | i386      | < 5.8        | non-exec                  |                                                | enabled  |
1691
            |           | [#x86_5.7]_  +---------------------------+------------------------------------------------+----------+
1692
            |           |              | exec / missing            |                                                | disabled |
1693
            |           +--------------+---------------------------+------------------------------------------------+----------+
1694
            |           | >= 5.8       | exec / non-exec           |                                                | enabled  |
1695
            |           | [#x86_5.8]_  +---------------------------+------------------------------------------------+----------+
1696
            |           |              | missing                   |                                                | disabled |
1697
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1698
            | amd64     | < 5.8        | non-exec                  |                                                | enabled  |
1699
            |           | [#x86_5.7]_  +---------------------------+------------------------------------------------+----------+
1700
            |           |              | exec / missing            |                                                | disabled |
1701
            |           +--------------+---------------------------+------------------------------------------------+----------+
1702
            |           | >= 5.8       | exec / non-exec / missing |                                                | enabled  |
1703
            |           | [#x86_5.8]_  |                           |                                                |          |
1704
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1705
            | arm       | < 5.8        | non-exec*                 |                                                | enabled  |
1706
            |           | [#arm_5.7]_  +---------------------------+------------------------------------------------+----------+
1707
            |           |              | exec / missing            |                                                | disabled |
1708
            |           +--------------+---------------------------+------------------------------------------------+----------+
1709
            |           | >= 5.8       | exec / non-exec*          |                                                | enabled  |
1710
            |           | [#arm_5.8]_  +---------------------------+------------------------------------------------+----------+
1711
            |           |              | missing                   |                                                | disabled |
1712
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1713
            | mips      | < 5.18       | non-exec*                 |                                                | enabled  |
1714
            |           | [#mips_5.17]_+---------------------------+------------------------------------------------+----------+
1715
            |           |              | exec / missing            |                                                | disabled |
1716
            |           +--------------+---------------------------+------------------------------------------------+----------+
1717
            |           | >= 5.18      | exec / non-exec*          |                                                | enabled  |
1718
            |           | [#mips_5.18]_+---------------------------+------------------------------------------------+----------+
1719
            |           |              | missing                   |                                                | disabled |
1720
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1721
            | powerpc   | [#powerpc]_  | non-exec / exec           |                                                | enabled  |
1722
            |           |              +---------------------------+------------------------------------------------+----------+
1723
            |           |              | missing                   |                                                | disabled |
1724
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1725
            | powerpc64 | [#powerpc]_  | exec / non-exec / missing |                                                | enabled  |
1726
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1727
            | ia64      | [#ia64]_     | non-exec                  |                                                | enabled  |
1728
            |           |              +---------------------------+------------------------------------------------+----------+
1729
            |           |              | exec / missing            | e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK == 0 | enabled  |
1730
            |           |              +                           +------------------------------------------------+----------+
1731
            |           |              |                           | e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK != 0 | disabled |
1732
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1733
            | the rest  | [#the_rest]_ | exec / non-exec / missing |                                                | enabled  |
1734
            +-----------+--------------+---------------------------+------------------------------------------------+----------+
1735

1736
            \\* Hardware limitations are ignored.
1737

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

1742
                if (elf_read_implies_exec(loc->elf_ex, executable_stack))
1743
                    current->personality |= READ_IMPLIES_EXEC;
1744

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

1748
            .. code-block:: c
1749

1750
                #define elf_read_implies_exec(ex, executable_stack)        \\
1751
                    (executable_stack != EXSTACK_DISABLE_X)
1752

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

1756
            .. code-block:: c
1757

1758
                #define elf_read_implies_exec(ex, executable_stack)        \\
1759
                    (mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT)
1760

1761
            `mmap_is_ia32()`__:
1762
                .. __: https://github.com/torvalds/linux/blob/v5.8/arch/x86/include/asm/elf.h#L318-L321
1763
                .. code-block:: c
1764

1765
                    /*
1766
                     * True on X86_32 or when emulating IA32 on X86_64
1767
                     */
1768
                    static inline int mmap_is_ia32(void)
1769

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

1773
            .. code-block:: c
1774

1775
                int arm_elf_read_implies_exec(int executable_stack)
1776
                {
1777
                    if (executable_stack != EXSTACK_DISABLE_X)
1778
                        return 1;
1779
                    if (cpu_architecture() < CPU_ARCH_ARMv6)
1780
                        return 1;
1781
                    return 0;
1782
                }
1783

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

1787
            .. code-block:: c
1788

1789
                int arm_elf_read_implies_exec(int executable_stack)
1790
                {
1791
                    if (executable_stack == EXSTACK_DEFAULT)
1792
                        return 1;
1793
                    if (cpu_architecture() < CPU_ARCH_ARMv6)
1794
                        return 1;
1795
                    return 0;
1796
                }
1797

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

1801
            .. code-block:: c
1802

1803
                int mips_elf_read_implies_exec(void *elf_ex, int exstack)
1804
                {
1805
                    if (exstack != EXSTACK_DISABLE_X) {
1806
                        /* The binary doesn't request a non-executable stack */
1807
                        return 1;
1808
                    }
1809
                    if (!cpu_has_rixi) {
1810
                        /* The CPU doesn't support non-executable memory */
1811
                        return 1;
1812
                    }
1813
                    return 0;
1814
                }
1815

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

1819
            .. code-block:: c
1820

1821
                int mips_elf_read_implies_exec(void *elf_ex, int exstack)
1822
                {
1823
                    /*
1824
                     * Set READ_IMPLIES_EXEC only on non-NX systems that
1825
                     * do not request a specific state via PT_GNU_STACK.
1826
                     */
1827
                    return (!cpu_has_rixi && exstack == EXSTACK_DEFAULT);
1828
                }
1829

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

1833
            .. code-block:: c
1834

1835
                #ifdef __powerpc64__
1836
                /* stripped */
1837
                # define elf_read_implies_exec(ex, exec_stk) (is_32bit_task() ? \\
1838
                        (exec_stk == EXSTACK_DEFAULT) : 0)
1839
                #else 
1840
                # define elf_read_implies_exec(ex, exec_stk) (exec_stk == EXSTACK_DEFAULT)
1841
                #endif /* __powerpc64__ */
1842

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

1846
            .. code-block:: c
1847

1848
                #define elf_read_implies_exec(ex, executable_stack)                                        \\
1849
                    ((executable_stack!=EXSTACK_DISABLE_X) && ((ex).e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK) != 0)
1850

1851
            EF_IA_64_LINUX_EXECUTABLE_STACK__:
1852
                .. __: https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L33
1853

1854
                .. code-block:: c
1855

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

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

1861
            .. code-block:: c
1862

1863
                # define elf_read_implies_exec(ex, have_pt_gnu_stack)        0
1864
        """
1865
        if not self.executable:
1✔
1866
            return True
1✔
1867
        
1868
        exec_bit = None
1✔
1869
        for seg in self.iter_segments_by_type('GNU_STACK'):
1✔
1870
            exec_bit = bool(seg.header.p_flags & P_FLAGS.PF_X)
1✔
1871

1872
        non_exec = exec_bit is False
1✔
1873
        missing = exec_bit is None
1✔
1874
        EF_IA_64_LINUX_EXECUTABLE_STACK = 1
1✔
1875

1876
        if self.arch in ['i386', 'arm', 'aarch64', 'mips', 'mips64']:
1✔
1877
            if non_exec:
1✔
1878
                return True
1✔
1879
            elif missing:
1✔
1880
                return False
1✔
1881
            return None
1✔
1882
        elif self.arch == 'amd64':
1✔
1883
            return True if non_exec else None
1✔
1884
        elif self.arch == 'powerpc':
1✔
1885
            return not missing
1✔
1886
        elif self.arch == 'powerpc64':
1✔
1887
            return True
1✔
1888
        elif self.arch == 'ia64':
1!
1889
            if non_exec:
×
1890
                return True
×
1891
            return not bool(self['e_flags'] & EF_IA_64_LINUX_EXECUTABLE_STACK)
×
1892

1893
        return True
1✔
1894

1895
    @property
1✔
1896
    def execstack(self):
1✔
1897
        """:class:`bool`: Whether dynamically loading the current binary will make the stack executable.
1898

1899
        This is based on the presence of a program header ``PT_GNU_STACK``,
1900
        its setting, and the default stack permissions for the architecture.
1901

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

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

1906
        .. code-block:: c
1907

1908
            case PT_GNU_STACK:
1909
              stack_flags = ph->p_flags;
1910
              break;
1911

1912
        Else, the stack permissions are set according to the architecture defaults
1913
        as `defined by`__ ``DEFAULT_STACK_PERMS``:
1914

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

1917
        .. code-block:: c
1918

1919
            /* On most platforms presume that PT_GNU_STACK is absent and the stack is
1920
             * executable.  Other platforms default to a nonexecutable stack and don't
1921
             * need PT_GNU_STACK to do so.  */
1922
            uint_fast16_t stack_flags = DEFAULT_STACK_PERMS;
1923

1924
        By searching the source for ``DEFAULT_STACK_PERMS``, we can see which
1925
        architectures have which settings.
1926

1927
        ::
1928

1929
            $ git grep '#define DEFAULT_STACK_PERMS' | grep -v PF_X
1930
            sysdeps/aarch64/stackinfo.h:    #define DEFAULT_STACK_PERMS (PF_R|PF_W)
1931
            sysdeps/arc/stackinfo.h:        #define DEFAULT_STACK_PERMS (PF_R|PF_W)
1932
            sysdeps/csky/stackinfo.h:       #define DEFAULT_STACK_PERMS (PF_R|PF_W)
1933
            sysdeps/ia64/stackinfo.h:       #define DEFAULT_STACK_PERMS (PF_R|PF_W)
1934
            sysdeps/loongarch/stackinfo.h:  #define DEFAULT_STACK_PERMS (PF_R | PF_W)
1935
            sysdeps/nios2/stackinfo.h:      #define DEFAULT_STACK_PERMS (PF_R|PF_W)
1936
            sysdeps/riscv/stackinfo.h:      #define DEFAULT_STACK_PERMS (PF_R | PF_W)
1937
        """
1938
        if not self.executable:
1✔
1939
            return False
1✔
1940

1941
        # If the ``PT_GNU_STACK`` program header is preset, use it's premissions.
1942
        for seg in self.iter_segments_by_type('GNU_STACK'):
1✔
1943
            return bool(seg.header.p_flags & P_FLAGS.PF_X)
1✔
1944
        
1945
        # If the ``PT_GNU_STACK`` program header is missing, then use the
1946
        # default rules. Out of the supported architectures, only AArch64,
1947
        # IA-64, and RISC-V get a non-executable stack by default.
1948
        return self.arch not in ['aarch64', 'ia64', 'riscv32', 'riscv64']
1✔
1949

1950
    @property
1✔
1951
    def canary(self):
1✔
1952
        """:class:`bool`: Whether the current binary uses stack canaries."""
1953

1954
        # Sometimes there is no function for __stack_chk_fail,
1955
        # but there is an entry in the GOT
1956
        return '__stack_chk_fail' in (set(self.symbols) | set(self.got))
1✔
1957

1958
    @property
1✔
1959
    def packed(self):
1✔
1960
        """:class:`bool`: Whether the current binary is packed with UPX."""
1961
        return b'UPX!' in self.get_data()[:0xFF]
1✔
1962

1963
    @property
1✔
1964
    def pie(self):
1✔
1965
        """:class:`bool`: Whether the current binary is position-independent."""
1966
        return self.elftype == 'DYN'
1✔
1967
    aslr=pie
1✔
1968

1969
    @property
1✔
1970
    def rpath(self):
1✔
1971
        """:class:`bool`: Whether the current binary has an ``RPATH``."""
1972
        dt_rpath = self.dynamic_by_tag('DT_RPATH')
1✔
1973

1974
        if not dt_rpath:
1!
1975
            return None
1✔
1976

1977
        return self.dynamic_string(dt_rpath.entry.d_ptr)
×
1978

1979
    @property
1✔
1980
    def runpath(self):
1✔
1981
        """:class:`bool`: Whether the current binary has a ``RUNPATH``."""
1982
        dt_runpath = self.dynamic_by_tag('DT_RUNPATH')
1✔
1983

1984
        if not dt_runpath:
1!
1985
            return None
1✔
1986

1987
        return self.dynamic_string(dt_runpath.entry.d_ptr)
×
1988

1989
    def checksec(self, banner=True, color=True):
1✔
1990
        """checksec(banner=True, color=True)
1991

1992
        Prints out information in the binary, similar to ``checksec.sh``.
1993

1994
        Arguments:
1995
            banner(bool): Whether to print the path to the ELF binary.
1996
            color(bool): Whether to use colored output.
1997
        """
1998
        red    = text.red if color else str
1✔
1999
        green  = text.green if color else str
1✔
2000
        yellow = text.yellow if color else str
1✔
2001

2002
        res = []
1✔
2003

2004
        # Kernel version?
2005
        if self.version and self.version != (0,):
1!
2006
            res.append('Version:'.ljust(10) + '.'.join(map(str, self.version)))
×
2007
        if self.build:
1!
2008
            res.append('Build:'.ljust(10) + self.build)
×
2009

2010
        res.extend([
1✔
2011
            "RELRO:".ljust(10) + {
2012
                'Full':    green("Full RELRO"),
2013
                'Partial': yellow("Partial RELRO"),
2014
                None:      red("No RELRO")
2015
            }[self.relro],
2016
            "Stack:".ljust(10) + {
2017
                True:  green("Canary found"),
2018
                False: red("No canary found")
2019
            }[self.canary],
2020
            "NX:".ljust(10) + {
2021
                True:  green("NX enabled"),
2022
                False: red("NX disabled"),
2023
                None: yellow("NX unknown - GNU_STACK missing"),
2024
            }[self.nx],
2025
            "PIE:".ljust(10) + {
2026
                True: green("PIE enabled"),
2027
                False: red("No PIE (%#x)" % self.address)
2028
            }[self.pie],
2029
        ])
2030

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

2035
        # Are there any RWX areas in the binary?
2036
        #
2037
        # This will occur if NX is disabled and *any* area is
2038
        # RW, or can expressly occur.
2039
        if self.rwx_segments or (not self.nx and self.writable_segments):
1✔
2040
            res += [ "RWX:".ljust(10) + red("Has RWX segments") ]
1✔
2041

2042
        if self.rpath:
1!
2043
            res += [ "RPATH:".ljust(10) + red(repr(self.rpath)) ]
×
2044

2045
        if self.runpath:
1!
2046
            res += [ "RUNPATH:".ljust(10) + red(repr(self.runpath)) ]
×
2047

2048
        if self.packed:
1!
2049
            res.append('Packer:'.ljust(10) + red("Packed with UPX"))
×
2050

2051
        if self.fortify:
1✔
2052
            res.append("FORTIFY:".ljust(10) + green("Enabled"))
1✔
2053

2054
        if self.asan:
1!
2055
            res.append("ASAN:".ljust(10) + green("Enabled"))
×
2056

2057
        if self.msan:
1!
2058
            res.append("MSAN:".ljust(10) + green("Enabled"))
×
2059

2060
        if self.ubsan:
1!
2061
            res.append("UBSAN:".ljust(10) + green("Enabled"))
×
2062

2063
        # Check for Linux configuration, it must contain more than
2064
        # just the version.
2065
        if len(self.config) > 1:
1!
2066
            config_opts = collections.defaultdict(list)
×
2067
            for checker in kernel_configuration:
×
2068
                result, message = checker(self.config)
×
2069

2070
                if not result:
×
2071
                    config_opts[checker.title].append((checker.name, message))
×
2072

2073

2074
            for title, values in config_opts.items():
×
2075
                res.append(title + ':')
×
2076
                for name, message in sorted(values):
×
2077
                    line = '{} = {}'.format(name, red(str(self.config.get(name, None))))
×
2078
                    if message:
×
2079
                        line += ' ({})'.format(message)
×
2080
                    res.append('    ' + line)
×
2081

2082
            # res.extend(sorted(config_opts))
2083

2084
        return '\n'.join(res)
1✔
2085

2086
    @property
1✔
2087
    def buildid(self):
1✔
2088
        """:class:`bytes`: GNU Build ID embedded into the binary"""
2089
        section = self.get_section_by_name('.note.gnu.build-id')
1✔
2090
        if section:
1!
2091
            return section.data()[16:]
1✔
2092
        return None
×
2093

2094
    @property
1✔
2095
    def fortify(self):
1✔
2096
        """:class:`bool`: Whether the current binary was built with
2097
        Fortify Source (``-DFORTIFY``)."""
2098
        if any(s.endswith('_chk') for s in self.plt):
1✔
2099
            return True
1✔
2100
        return False
1✔
2101

2102
    @property
1✔
2103
    def asan(self):
1✔
2104
        """:class:`bool`: Whether the current binary was built with
2105
        Address Sanitizer (``ASAN``)."""
2106
        return any(s.startswith('__asan_') for s in self.symbols)
1✔
2107

2108
    @property
1✔
2109
    def msan(self):
1✔
2110
        """:class:`bool`: Whether the current binary was built with
2111
        Memory Sanitizer (``MSAN``)."""
2112
        return any(s.startswith('__msan_') for s in self.symbols)
1✔
2113

2114
    @property
1✔
2115
    def ubsan(self):
1✔
2116
        """:class:`bool`: Whether the current binary was built with
2117
        Undefined Behavior Sanitizer (``UBSAN``)."""
2118
        return any(s.startswith('__ubsan_') for s in self.symbols)
1✔
2119

2120
    def _update_args(self, kw):
1✔
2121
        kw.setdefault('arch', self.arch)
1✔
2122
        kw.setdefault('bits', self.bits)
1✔
2123
        kw.setdefault('endian', self.endian)
1✔
2124

2125
    def p64(self,  address, data, *a, **kw):
1✔
2126
        """Writes a 64-bit integer ``data`` to the specified ``address``"""
2127
        self._update_args(kw)
×
2128
        return self.write(address, packing.p64(data, *a, **kw))
×
2129

2130
    def p32(self,  address, data, *a, **kw):
1✔
2131
        """Writes a 32-bit integer ``data`` to the specified ``address``"""
2132
        self._update_args(kw)
×
2133
        return self.write(address, packing.p32(data, *a, **kw))
×
2134

2135
    def p16(self,  address, data, *a, **kw):
1✔
2136
        """Writes a 16-bit integer ``data`` to the specified ``address``"""
2137
        self._update_args(kw)
×
2138
        return self.write(address, packing.p16(data, *a, **kw))
×
2139

2140
    def p8(self,   address, data, *a, **kw):
1✔
2141
        """Writes a 8-bit integer ``data`` to the specified ``address``"""
2142
        self._update_args(kw)
×
2143
        return self.write(address, packing.p8(data, *a, **kw))
×
2144

2145
    def pack(self, address, data, *a, **kw):
1✔
2146
        """Writes a packed integer ``data`` to the specified ``address``"""
2147
        self._update_args(kw)
1✔
2148
        return self.write(address, packing.pack(data, *a, **kw))
1✔
2149

2150
    def u64(self,    address, *a, **kw):
1✔
2151
        """Unpacks an integer from the specified ``address``."""
2152
        self._update_args(kw)
×
2153
        return packing.u64(self.read(address, 8), *a, **kw)
×
2154

2155
    def u32(self,    address, *a, **kw):
1✔
2156
        """Unpacks an integer from the specified ``address``."""
2157
        self._update_args(kw)
×
2158
        return packing.u32(self.read(address, 4), *a, **kw)
×
2159

2160
    def u16(self,    address, *a, **kw):
1✔
2161
        """Unpacks an integer from the specified ``address``."""
2162
        self._update_args(kw)
×
2163
        return packing.u16(self.read(address, 2), *a, **kw)
×
2164

2165
    def u8(self,     address, *a, **kw):
1✔
2166
        """Unpacks an integer from the specified ``address``."""
2167
        self._update_args(kw)
×
2168
        return packing.u8(self.read(address, 1), *a, **kw)
×
2169

2170
    def unpack(self, address, *a, **kw):
1✔
2171
        """Unpacks an integer from the specified ``address``."""
2172
        self._update_args(kw)
1✔
2173
        return packing.unpack(self.read(address, self.bytes), *a, **kw)
1✔
2174

2175
    def string(self, address):
1✔
2176
        """string(address) -> str
2177

2178
        Reads a null-terminated string from the specified ``address``
2179

2180
        Returns:
2181
            A ``str`` with the string contents (NUL terminator is omitted),
2182
            or an empty string if no NUL terminator could be found.
2183
        """
2184
        data = b''
1✔
2185
        while True:
2186
            read_size = 0x1000
1✔
2187
            partial_page = address & 0xfff
1✔
2188

2189
            if partial_page:
1✔
2190
                read_size -= partial_page
1✔
2191

2192
            c = self.read(address, read_size)
1✔
2193

2194
            if not c:
1!
2195
                return b''
×
2196

2197
            data += c
1✔
2198

2199
            if b'\x00' in c:
1✔
2200
                return data[:data.index(b'\x00')]
1✔
2201

2202
            address += len(c)
1✔
2203

2204
    def flat(self, address, *a, **kw):
1✔
2205
        """Writes a full array of values to the specified address.
2206

2207
        See: :func:`.packing.flat`
2208
        """
2209
        return self.write(address, packing.flat(*a,**kw))
×
2210

2211
    def fit(self, address, *a, **kw):
1✔
2212
        """Writes fitted data into the specified address.
2213

2214
        See: :func:`.packing.fit`
2215
        """
2216
        return self.write(address, packing.fit(*a, **kw))
×
2217

2218
    def parse_kconfig(self, data):
1✔
2219
        self.config.update(parse_kconfig(data))
×
2220

2221
    def disable_nx(self):
1✔
2222
        """Disables NX for the ELF.
2223

2224
        Zeroes out the ``PT_GNU_STACK`` program header ``p_type`` field.
2225
        """
2226
        PT_GNU_STACK = packing.p32(ENUM_P_TYPE['PT_GNU_STACK'])
×
2227

2228
        if not self.executable:
×
2229
            log.error("Can only make stack executable with executables")
×
2230

2231
        for i, segment in enumerate(self.iter_segments()):
×
2232
            if not segment.header.p_type:
×
2233
                continue
×
2234
            if 'GNU_STACK' not in segment.header.p_type:
×
2235
                continue
×
2236

2237
            phoff = self.header.e_phoff
×
2238
            phentsize = self.header.e_phentsize
×
2239
            offset = phoff + phentsize * i
×
2240

2241
            if self.mmap[offset:offset+4] == PT_GNU_STACK:
×
2242
                self.mmap[offset:offset+4] = b'\x00' * 4
×
2243
                self.save()
×
2244
                # Invalidate the cached segments, ``PT_GNU_STACK`` was removed.
2245
                self._segments = None
×
2246
                return
×
2247

2248
        log.error("Could not find PT_GNU_STACK, stack should already be executable")
×
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