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

Gallopsled / pwntools / 5190673800

pending completion
5190673800

Pull #2205

github-actions

web-flow
Merge 38675c90e into c72886a9b
Pull Request #2205: Fix stable Python 2 installation from a built wheel

3936 of 6604 branches covered (59.6%)

12074 of 16876 relevant lines covered (71.55%)

0.72 hits per line

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

78.6
/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 E_FLAGS
1✔
53
from elftools.elf.constants import P_FLAGS
1✔
54
from elftools.elf.constants import SHN_INDICES
1✔
55
from elftools.elf.descriptions import describe_e_type
1✔
56
from elftools.elf.elffile import ELFFile
1✔
57
from elftools.elf.gnuversions import GNUVerDefSection
1✔
58
from elftools.elf.relocation import RelocationSection
1✔
59
from elftools.elf.sections import SymbolTableSection
1✔
60
from elftools.elf.segments import InterpSegment
1✔
61

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

68
import intervaltree
1✔
69

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

89
log = getLogger(__name__)
1✔
90

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

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

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

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

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

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

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

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

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

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

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

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

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

146
    Supports recursive instantiation for keys which contain dots.
147

148
    Example:
149

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

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

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

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

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

181
    Example:
182

183
        .. code-block:: python
184

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

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

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

213

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

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

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

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

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

230
        #: :class:`str`: Architecture of the file (e.g. ``'i386'``, ``'arm'``).
231
        #:
232
        #: See: :attr:`.ContextType.arch`
233
        self.arch = self.get_machine_arch()
1✔
234
        if isinstance(self.arch, (bytes, six.text_type)):
1!
235
            self.arch = self.arch.lower()
1✔
236

237
        #: :class:`dotdict` of ``name`` to ``address`` for all symbols in the ELF
238
        self.symbols = dotdict()
1✔
239

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

243
        #: :class:`dotdict` of ``name`` to ``address`` for all Procedure Linkate Table (PLT) entries
244
        self.plt = dotdict()
1✔
245

246
        #: :class:`dotdict` of ``name`` to :class:`.Function` for each function in the ELF
247
        self.functions = dotdict()
1✔
248

249
        #: :class:`dict`: Linux kernel configuration, if this is a Linux kernel image
250
        self.config = {}
1✔
251

252
        #: :class:`tuple`: Linux kernel version, if this is a Linux kernel image
253
        self.version = (0,)
1✔
254

255
        #: :class:`str`: Linux kernel build commit, if this is a Linux kernel image
256
        self.build = ''
1✔
257

258
        #: :class:`str`: Endianness of the file (e.g. ``'big'``, ``'little'``)
259
        self.endian = {
1✔
260
            'ELFDATANONE': 'little',
261
            'ELFDATA2LSB': 'little',
262
            'ELFDATA2MSB': 'big'
263
        }[self['e_ident']['EI_DATA']]
264

265
        #: :class:`int`: Bit-ness of the file
266
        self.bits = self.elfclass
1✔
267

268
        #: :class:`int`: Pointer width, in bytes
269
        self.bytes = self.bits // 8
1✔
270

271
        if self.arch == 'mips':
1✔
272
            mask = lambda a, b: a & b == b
1✔
273
            flags = self.header['e_flags']
1✔
274

275
            if mask(flags, E_FLAGS.EF_MIPS_ARCH_32) \
1!
276
            or mask(flags, E_FLAGS.EF_MIPS_ARCH_32R2):
277
                pass
×
278
            elif mask(flags, E_FLAGS.EF_MIPS_ARCH_64) \
1✔
279
            or mask(flags, E_FLAGS.EF_MIPS_ARCH_64R2):
280
                self.arch = 'mips64'
1✔
281
                self.bits = 64
1✔
282

283
        self._sections = None
1✔
284
        self._segments = None
1✔
285

286
        #: IntervalTree which maps all of the loaded memory segments
287
        self.memory = intervaltree.IntervalTree()
1✔
288
        self._populate_memory()
1✔
289

290
        # Is this a native binary? Should we be checking QEMU?
291
        try:
1✔
292
            with context.local(arch=self.arch):
1✔
293
                #: Whether this ELF should be able to run natively
294
                self.native = context.native
1✔
295
        except AttributeError:
×
296
            # The architecture may not be supported in pwntools
297
            self.native = False
×
298

299
        self._address = 0
1✔
300
        if self.elftype != 'DYN':
1✔
301
            for seg in self.iter_segments_by_type('PT_LOAD'):
1✔
302
                addr = seg.header.p_vaddr
1✔
303
                if addr == 0:
1!
304
                    continue
×
305
                if addr < self._address or self._address == 0:
1✔
306
                    self._address = addr
1✔
307

308
        self.load_addr = self._address
1✔
309

310
        # Try to figure out if we have a kernel configuration embedded
311
        IKCFG_ST=b'IKCFG_ST'
1✔
312

313
        for start in self.search(IKCFG_ST):
1!
314
            start += len(IKCFG_ST)
×
315
            stop = next(self.search(b'IKCFG_ED'))
×
316

317
            fileobj = BytesIO(self.read(start, stop-start))
×
318

319
            # Python gzip throws an exception if there is non-Gzip data
320
            # after the Gzip stream.
321
            #
322
            # Catch the exception, and just deal with it.
323
            with gzip.GzipFile(fileobj=fileobj) as gz:
×
324
                config = gz.read()
×
325

326
            if config:
×
327
                self.config = parse_kconfig(config.decode())
×
328

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

332
        #: ``True`` if the ELF is an executable
333
        self.executable = bool(self.elftype == 'EXEC')
1✔
334

335
        for seg in self.iter_segments_by_type('PT_INTERP'):
1✔
336
            self.executable = True
1✔
337

338
            #: ``True`` if the ELF is statically linked
339
            self.statically_linked = False
1✔
340

341
            #: Path to the linker for the ELF
342
            self.linker = self.read(seg.header.p_vaddr, seg.header.p_memsz)
1✔
343
            self.linker = self.linker.rstrip(b'\x00')
1✔
344

345
        #: Operating system of the ELF
346
        self.os = 'linux'
1✔
347

348
        if self.linker and self.linker.startswith(b'/system/bin/linker'):
1!
349
            self.os = 'android'
×
350

351
        #: ``True`` if the ELF is a shared library
352
        self.library = not self.executable and self.elftype == 'DYN'
1✔
353

354
        try:
1✔
355
            self._populate_symbols()
1✔
356
        except Exception as e:
×
357
            log.warn("Could not populate symbols: %s", e)
×
358

359
        try:
1✔
360
            self._populate_got()
1✔
361
        except Exception as e:
×
362
            log.warn("Could not populate GOT: %s", e)
×
363

364
        try:
1✔
365
            self._populate_plt()
1✔
366
        except Exception as e:
×
367
            log.warn("Could not populate PLT: %s", e)
×
368

369
        self._populate_synthetic_symbols()
1✔
370
        self._populate_functions()
1✔
371
        self._populate_kernel_version()
1✔
372

373
        if checksec:
1✔
374
            self._describe()
1✔
375

376
        self._libs = None
1✔
377
        self._maps = None
1✔
378

379
    @staticmethod
1✔
380
    @LocalContext
1✔
381
    def from_assembly(assembly, *a, **kw):
1✔
382
        """from_assembly(assembly) -> ELF
383

384
        Given an assembly listing, return a fully loaded ELF object
385
        which contains that assembly at its entry point.
386

387
        Arguments:
388

389
            assembly(str): Assembly language listing
390
            vma(int): Address of the entry point and the module's base address.
391

392
        Example:
393

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

403
    @staticmethod
1✔
404
    @LocalContext
1✔
405
    def from_bytes(bytes, *a, **kw):
1✔
406
        r"""from_bytes(bytes) -> ELF
407

408
        Given a sequence of bytes, return a fully loaded ELF object
409
        which contains those bytes at its entry point.
410

411
        Arguments:
412

413
            bytes(str): Shellcode byte string
414
            vma(int): Desired base address for the ELF.
415

416
        Example:
417

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

425
    def process(self, argv=[], *a, **kw):
1✔
426
        """process(argv=[], *a, **kw) -> process
427

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

431
        Arguments:
432
            argv(list): List of arguments to the binary
433
            *args: Extra arguments to :class:`.process`
434
            **kwargs: Extra arguments to :class:`.process`
435

436
        Returns:
437
            :class:`.process`
438
        """
439

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

445
    def debug(self, argv=[], *a, **kw):
1✔
446
        """debug(argv=[], *a, **kw) -> tube
447

448
        Debug the ELF with :func:`.gdb.debug`.
449

450
        Arguments:
451
            argv(list): List of arguments to the binary
452
            *args: Extra arguments to :func:`.gdb.debug`
453
            **kwargs: Extra arguments to :func:`.gdb.debug`
454

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

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

472
    def get_machine_arch(self):
1✔
473
        return {
1✔
474
            'EM_X86_64': 'amd64',
475
            'EM_386' :'i386',
476
            'EM_486': 'i386',
477
            'EM_ARM': 'arm',
478
            'EM_AARCH64': 'aarch64',
479
            'EM_MIPS': 'mips',
480
            'EM_PPC': 'powerpc',
481
            'EM_PPC64': 'powerpc64',
482
            'EM_SPARC32PLUS': 'sparc',
483
            'EM_SPARCV9': 'sparc64',
484
            'EM_IA_64': 'ia64'
485
        }.get(self['e_machine'], self['e_machine'])
486

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

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

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

504
        return iter(self._segments)
1✔
505

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

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

523
    def get_segment_for_address(self, address, size=1):
1✔
524
        """get_segment_for_address(address, size=1) -> Segment
525

526
        Given a virtual address described by a ``PT_LOAD`` segment, return the
527
        first segment which describes the virtual address.  An optional ``size``
528
        may be provided to ensure the entire range falls into the same segment.
529

530
        Arguments:
531
            address(int): Virtual address to find
532
            size(int): Number of bytes which must be available after ``address``
533
                in **both** the file-backed data for the segment, and the memory
534
                region which is reserved for the data.
535

536
        Returns:
537
            Either returns a :class:`.segments.Segment` object, or ``None``.
538
        """
539
        for seg in self.iter_segments_by_type("PT_LOAD"):
1!
540
            mem_start = seg.header.p_vaddr
1✔
541
            mem_stop  = seg.header.p_memsz + mem_start
1✔
542

543
            if not (mem_start <= address <= address+size < mem_stop):
1!
544
                continue
×
545

546
            offset = self.vaddr_to_offset(address)
1✔
547

548
            file_start = seg.header.p_offset
1✔
549
            file_stop  = seg.header.p_filesz + file_start
1✔
550

551
            if not (file_start <= offset <= offset+size < file_stop):
1!
552
                continue
×
553

554
            return seg
1✔
555

556
        return None
×
557

558
    def iter_sections(self):
1✔
559
        # Yield and cache all the sections in the file
560
        if self._sections is None:
1✔
561
            self._sections = [self.get_section(i) for i in range(self.num_sections())]
1✔
562

563
        return iter(self._sections)
1✔
564

565
    @property
1✔
566
    def sections(self):
1✔
567
        """
568
        :class:`list`: A list of :class:`elftools.elf.sections.Section` objects
569
            for the segments in the ELF.
570
        """
571
        return list(self.iter_sections())
1✔
572

573
    @property
1✔
574
    def dwarf(self):
1✔
575
        """DWARF info for the elf"""
576
        return self.get_dwarf_info()
×
577

578
    @property
1✔
579
    def sym(self):
1✔
580
        """:class:`dotdict`: Alias for :attr:`.ELF.symbols`"""
581
        return self.symbols
1✔
582

583
    @property
1✔
584
    def address(self):
1✔
585
        """:class:`int`: Address of the lowest segment loaded in the ELF.
586

587
        When updated, the addresses of the following fields are also updated:
588

589
        - :attr:`~.ELF.symbols`
590
        - :attr:`~.ELF.got`
591
        - :attr:`~.ELF.plt`
592
        - :attr:`~.ELF.functions`
593

594
        However, the following fields are **NOT** updated:
595

596
        - :attr:`~.ELF.segments`
597
        - :attr:`~.ELF.sections`
598

599
        Example:
600

601
            >>> bash = ELF('/bin/bash')
602
            >>> read = bash.symbols['read']
603
            >>> text = bash.get_section_by_name('.text').header.sh_addr
604
            >>> bash.address += 0x1000
605
            >>> read + 0x1000 == bash.symbols['read']
606
            True
607
            >>> text == bash.get_section_by_name('.text').header.sh_addr
608
            True
609
        """
610
        return self._address
1✔
611

612
    @address.setter
1✔
613
    def address(self, new):
1✔
614
        delta     = new-self._address
1✔
615
        update    = lambda x: x+delta
1✔
616

617
        self.symbols = dotdict({k:update(v) for k,v in self.symbols.items()})
1✔
618
        self.plt     = dotdict({k:update(v) for k,v in self.plt.items()})
1✔
619
        self.got     = dotdict({k:update(v) for k,v in self.got.items()})
1✔
620
        for f in self.functions.values():
1✔
621
            f.address += delta
1✔
622

623
        # Update our view of memory
624
        memory = intervaltree.IntervalTree()
1✔
625

626
        for begin, end, data in self.memory:
1✔
627
            memory.addi(update(begin),
1✔
628
                        update(end),
629
                        data)
630

631
        self.memory = memory
1✔
632

633
        self._address = update(self.address)
1✔
634

635
    def section(self, name):
1✔
636
        """section(name) -> bytes
637

638
        Gets data for the named section
639

640
        Arguments:
641
            name(str): Name of the section
642

643
        Returns:
644
            :class:`str`: String containing the bytes for that section
645
        """
646
        return self.get_section_by_name(name).data()
×
647

648
    @property
1✔
649
    def rwx_segments(self):
1✔
650
        """:class:`list`: List of all segments which are writeable and executable.
651

652
        See:
653
            :attr:`.ELF.segments`
654
        """
655
        if not self.nx:
1✔
656
            return self.writable_segments
1✔
657

658
        wx = P_FLAGS.PF_X | P_FLAGS.PF_W
1✔
659
        return [s for s in self.segments if s.header.p_flags & wx == wx]
1✔
660

661
    @property
1✔
662
    def executable_segments(self):
1✔
663
        """:class:`list`: List of all segments which are executable.
664

665
        See:
666
            :attr:`.ELF.segments`
667
        """
668
        if not self.nx:
1!
669
            return list(self.segments)
1✔
670

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

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

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

682
    @property
1✔
683
    def non_writable_segments(self):
1✔
684
        """:class:`list`: List of all segments which are NOT writeable.
685

686
        See:
687
            :attr:`.ELF.segments`
688
        """
689
        return [s for s in self.segments if not s.header.p_flags & P_FLAGS.PF_W]
×
690

691
    @property
1✔
692
    def libs(self):
1✔
693
        """Dictionary of {path: address} for every library loaded for this ELF."""
694
        if self._libs is None:
1✔
695
            self._populate_libraries()
1✔
696
        return self._libs
1✔
697

698
    @property
1✔
699
    def maps(self):
1✔
700
        """Dictionary of {name: address} for every mapping in this ELF's address space."""
701
        if self._maps is None:
×
702
            self._populate_libraries()
×
703
        return self._maps
×
704

705
    @property
1✔
706
    def libc(self):
1✔
707
        """:class:`.ELF`: If this :class:`.ELF` imports any libraries which contain ``'libc[.-]``,
708
        and we can determine the appropriate path to it on the local
709
        system, returns a new :class:`.ELF` object pertaining to that library.
710

711
        If not found, the value will be :const:`None`.
712
        """
713
        for lib in self.libs:
1!
714
            if '/libc.' in lib or '/libc-' in lib:
1✔
715
                return ELF(lib)
1✔
716

717
    def _populate_libraries(self):
1✔
718
        """
719
        >>> from os.path import exists
720
        >>> bash = ELF(which('bash'))
721
        >>> all(map(exists, bash.libs.keys()))
722
        True
723
        >>> any(map(lambda x: 'libc' in x, bash.libs.keys()))
724
        True
725
        """
726
        # Patch some shellcode into the ELF and run it.
727
        maps = self._patch_elf_and_read_maps()
1✔
728

729
        self._maps = maps
1✔
730
        self._libs = {}
1✔
731

732
        for lib, address in maps.items():
1✔
733

734
            # Filter out [stack] and such from the library listings
735
            if lib.startswith('['):
1✔
736
                continue
1✔
737

738
            # Any existing files we can just use
739
            if os.path.exists(lib):
1!
740
                self._libs[lib] = address
1✔
741

742
            # Try etc/qemu-binfmt, as per Ubuntu
743
            if not self.native:
1!
744
                ld_prefix = qemu.ld_prefix()
×
745

746
                qemu_lib = os.path.join(ld_prefix, lib)
×
747
                qemu_lib = os.path.realpath(qemu_lib)
×
748

749
                if os.path.exists(qemu_lib):
×
750
                    self._libs[qemu_lib] = address
×
751

752
    def _patch_elf_and_read_maps(self):
1✔
753
        r"""patch_elf_and_read_maps(self) -> dict
754

755
        Read ``/proc/self/maps`` as if the ELF were executing.
756

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

760
        Returns:
761
            A ``dict`` mapping file paths to the lowest address they appear at.
762
            Does not do any translation for e.g. QEMU emulation, the raw results
763
            are returned.
764

765
            If there is not enough space to inject the shellcode in the segment
766
            which contains the entry point, returns ``{}``.
767

768
        Doctests:
769

770
            These tests are just to ensure that our shellcode is correct.
771

772
            >>> for arch in CAT_PROC_MAPS_EXIT:
773
            ...   context.clear()
774
            ...   with context.local(arch=arch):
775
            ...     sc = shellcraft.cat2("/proc/self/maps")
776
            ...     sc += shellcraft.exit()
777
            ...     sc = asm(sc)
778
            ...     sc = enhex(sc)
779
            ...     assert sc == CAT_PROC_MAPS_EXIT[arch], (arch, sc)
780
        """
781

782
        # Get our shellcode
783
        sc = CAT_PROC_MAPS_EXIT.get(self.arch, None)
1✔
784

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

788
        sc = unhex(sc)
1✔
789

790
        # Ensure there is enough room in the segment where the entry point resides
791
        # in order to inject our shellcode.
792
        seg = self.get_segment_for_address(self.entry, len(sc))
1✔
793
        if not seg:
1!
794
            log.warn_once("Could not inject code to determine memory mapping for %r: Not enough space", self)
×
795
            return {}
×
796

797
        # Create our temporary file
798
        # NOTE: We cannot use "with NamedTemporaryFile() as foo", because we cannot
799
        # execute the file while the handle is open.
800
        fd, path = tempfile.mkstemp()
1✔
801

802
        # Close the file descriptor so that it may be executed
803
        os.close(fd)
1✔
804

805
        # Save off a copy of the ELF
806
        self.save(path)
1✔
807

808
        # Load a new copy of the ELF at the temporary file location
809
        old = self.read(self.entry, len(sc))
1✔
810
        try:
1✔
811
            self.write(self.entry, sc)
1✔
812
            self.save(path)
1✔
813
        finally:
814
            # Restore the original contents
815
            self.write(self.entry, old)
1✔
816

817
        # Make the file executable
818
        os.chmod(path, 0o755)
1✔
819

820
        # Run a copy of it, get the maps
821
        try:
1✔
822
            with context.silent:
1✔
823
                io = process(path)
1✔
824
                data = packing._decode(io.recvall(timeout=2))
1✔
825
        except Exception:
×
826
            log.warn_once("Injected /proc/self/maps code did not execute correctly")
×
827
            return {}
×
828

829
        # Swap in the original ELF name
830
        data = data.replace(path, self.path)
1✔
831

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

850
            address, _ = line.split('-', 1)
1✔
851

852
            address = int(address, 0x10)
1✔
853
            name = line[index:]
1✔
854

855
            result.setdefault(name, address)
1✔
856

857
        # Remove the temporary file, best-effort
858
        os.unlink(path)
1✔
859

860
        return result
1✔
861

862
    def _populate_functions(self):
1✔
863
        """Builds a dict of 'functions' (i.e. symbols of type 'STT_FUNC')
864
        by function name that map to a tuple consisting of the func address and size
865
        in bytes.
866
        """
867
        for sec in self.sections:
1✔
868
            if not isinstance(sec, SymbolTableSection):
1✔
869
                continue
1✔
870

871
            for sym in _iter_symbols(sec):
1✔
872
                # Avoid duplicates
873
                if sym.name in self.functions:
1✔
874
                    continue
1✔
875
                if sym.entry.st_info['type'] == 'STT_FUNC' and sym.entry.st_size != 0:
1✔
876
                    name = sym.name
1✔
877
                    if name not in self.symbols:
1!
878
                        continue
×
879
                    addr = self.symbols[name]
1✔
880
                    size = sym.entry.st_size
1✔
881
                    self.functions[name] = Function(name, addr, size, self)
1✔
882

883
    def _populate_symbols(self):
1✔
884
        """
885
        >>> bash = ELF(which('bash'))
886
        >>> bash.symbols['_start'] == bash.entry
887
        True
888
        """
889

890
        # Populate all of the "normal" symbols from the symbol tables
891
        for section in self.sections:
1✔
892
            if not isinstance(section, SymbolTableSection):
1✔
893
                continue
1✔
894

895
            for symbol in _iter_symbols(section):
1✔
896
                value = symbol.entry.st_value
1✔
897
                if not value:
1✔
898
                    continue
1✔
899
                self.symbols[symbol.name] = value
1✔
900

901
    def _populate_synthetic_symbols(self):
1✔
902
        """Adds symbols from the GOT and PLT to the symbols dictionary.
903

904
        Does not overwrite any existing symbols, and prefers PLT symbols.
905

906
        Synthetic plt.xxx and got.xxx symbols are added for each PLT and
907
        GOT entry, respectively.
908

909
        Example:bash.
910

911
            >>> bash = ELF(which('bash'))
912
            >>> bash.symbols.wcscmp == bash.plt.wcscmp
913
            True
914
            >>> bash.symbols.wcscmp == bash.symbols.plt.wcscmp
915
            True
916
            >>> bash.symbols.stdin  == bash.got.stdin
917
            True
918
            >>> bash.symbols.stdin  == bash.symbols.got.stdin
919
            True
920
        """
921
        for symbol, address in self.plt.items():
1✔
922
            self.symbols.setdefault(symbol, address)
1✔
923
            self.symbols['plt.' + symbol] = address
1✔
924

925
        for symbol, address in self.got.items():
1✔
926
            self.symbols.setdefault(symbol, address)
1✔
927
            self.symbols['got.' + symbol] = address
1✔
928

929
    def _populate_got(self):
1✔
930
        """Loads the symbols for all relocations"""
931
        # Statically linked implies no relocations, since there is no linker
932
        # Could always be self-relocating like Android's linker *shrug*
933
        if self.statically_linked:
1✔
934
            return
1✔
935

936
        for section in self.sections:
1✔
937
            # We are only interested in relocations
938
            if not isinstance(section, RelocationSection):
1✔
939
                continue
1✔
940

941
            # Only get relocations which link to another section (for symbols)
942
            if section.header.sh_link == SHN_INDICES.SHN_UNDEF:
1!
943
                continue
×
944

945
            symbols = self.get_section(section.header.sh_link)
1✔
946

947
            for rel in section.iter_relocations():
1✔
948
                sym_idx  = rel.entry.r_info_sym
1✔
949

950
                if not sym_idx:
1✔
951
                    continue
1✔
952

953
                symbol = symbols.get_symbol(sym_idx)
1✔
954

955
                if symbol and symbol.name:
1!
956
                    self.got[symbol.name] = rel.entry.r_offset
1✔
957

958
        if self.arch == 'mips':
1✔
959
            try:
1✔
960
                self._populate_mips_got()
1✔
961
            except Exception as e:
×
962
                log.warn("Could not populate MIPS GOT: %s", e)
×
963

964
        if not self.got:
1✔
965
            log.warn("Did not find any GOT entries")
1✔
966

967
    def _populate_mips_got(self):
1✔
968
        self._mips_got = {}
1✔
969
        strings = self.get_section(self.header.e_shstrndx)
1✔
970

971
        ELF_MIPS_GNU_GOT1_MASK = 0x80000000
1✔
972

973
        if self.bits == 64:
1!
974
            ELF_MIPS_GNU_GOT1_MASK <<= 32
×
975

976
        # Beginning of the GOT
977
        got = self.dynamic_value_by_tag('DT_PLTGOT') or 0
1✔
978

979
        # Find the beginning of the GOT pointers
980
        got1_mask = (self.unpack(got) & ELF_MIPS_GNU_GOT1_MASK)
1✔
981
        i = 2 if got1_mask else 1
1✔
982
        self._mips_skip = i
1✔
983

984
        # We don't care about local GOT entries, skip them
985
        local_gotno = self.dynamic_value_by_tag('DT_MIPS_LOCAL_GOTNO')
1✔
986
        got += local_gotno * context.bytes
1✔
987

988
        # Iterate over the dynamic symbol table
989
        dynsym = self.get_section_by_name('.dynsym')
1✔
990
        symbol_iter = _iter_symbols(dynsym)
1✔
991

992
        # 'gotsym' is the index of the first GOT symbol
993
        gotsym = self.dynamic_value_by_tag('DT_MIPS_GOTSYM')
1✔
994
        for i in range(gotsym):
1✔
995
            next(symbol_iter)
1✔
996

997
        # 'symtabno' is the total number of symbols
998
        symtabno = self.dynamic_value_by_tag('DT_MIPS_SYMTABNO')
1✔
999

1000
        for i in range(symtabno - gotsym):
1✔
1001
            symbol = next(symbol_iter)
1✔
1002
            self._mips_got[i + gotsym] = got
1✔
1003
            self.got[symbol.name] = got
1✔
1004
            got += self.bytes
1✔
1005

1006
    def _populate_plt(self):
1✔
1007
        """Loads the PLT symbols
1008

1009
        >>> path = pwnlib.data.elf.path
1010
        >>> for test in glob(os.path.join(path, 'test-*')):
1011
        ...     test = ELF(test)
1012
        ...     assert '__stack_chk_fail' in test.got, test
1013
        ...     if test.arch != 'ppc':
1014
        ...         assert '__stack_chk_fail' in test.plt, test
1015
        """
1016
        if self.statically_linked:
1✔
1017
            log.debug("%r is statically linked, skipping GOT/PLT symbols" % self.path)
1✔
1018
            return
1✔
1019

1020
        if not self.got:
1✔
1021
            log.debug("%r doesn't have any GOT symbols, skipping PLT" % self.path)
1✔
1022
            return
1✔
1023

1024
        # This element holds an address associated with the procedure linkage table
1025
        # and/or the global offset table.
1026
        #
1027
        # Zach's note: This corresponds to the ".got.plt" section, in a PIE non-RELRO binary.
1028
        #              This corresponds to the ".got" section, in a PIE full-RELRO binary.
1029
        #              In particular, this is where EBX points when it points into the GOT.
1030
        dt_pltgot = self.dynamic_value_by_tag('DT_PLTGOT') or 0
1✔
1031

1032
        # There are three PLTs we may need to search
1033
        plt = self.get_section_by_name('.plt')          # <-- Functions only
1✔
1034
        plt_got = self.get_section_by_name('.plt.got')  # <-- Functions used as data
1✔
1035
        plt_sec = self.get_section_by_name('.plt.sec')
1✔
1036
        plt_mips = self.get_section_by_name('.MIPS.stubs')
1✔
1037

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

1042
        with context.local(arch=self.arch, bits=self.bits, endian=self.endian):
1✔
1043
            for section in (plt, plt_got, plt_sec, plt_mips):
1✔
1044
                if not section:
1✔
1045
                    continue
1✔
1046

1047
                res = emulate_plt_instructions(self,
1✔
1048
                                                dt_pltgot,
1049
                                                section.header.sh_addr,
1050
                                                section.data(),
1051
                                                inv_symbols)
1052

1053
                for address, target in sorted(res.items()):
1✔
1054
                    self.plt[inv_symbols[target]] = address
1✔
1055

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

1059
    def _populate_kernel_version(self):
1✔
1060
        if 'linux_banner' not in self.symbols:
1!
1061
            return
1✔
1062

1063
        banner = self.string(self.symbols.linux_banner)
×
1064
        
1065
        # convert banner into a utf-8 string since re.search does not accept bytes anymore
1066
        banner = banner.decode('utf-8')
×
1067
        
1068
        # 'Linux version 3.18.31-gd0846ecc
1069
        regex = r'Linux version (\S+)'
×
1070
        match = re.search(regex, banner)
×
1071

1072
        if match:
×
1073
            version = match.group(1)
×
1074

1075
            if '-' in version:
×
1076
                version, self.build = version.split('-', 1)
×
1077

1078
            self.version = list(map(int, version.rstrip('+').split('.')))
×
1079

1080
        self.config['version'] = self.version
×
1081

1082
    @property
1✔
1083
    def libc_start_main_return(self):
1✔
1084
        """:class:`int`: Address of the return address into __libc_start_main from main.
1085

1086
        >>> bash = ELF(which('bash'))
1087
        >>> libc = bash.libc
1088
        >>> libc.libc_start_main_return > 0
1089
        True
1090

1091
        Try to find the return address from main into __libc_start_main.
1092
        The heuristic to find the call to the function pointer of main is
1093
        to list all calls inside __libc_start_main, find the call to exit
1094
        after the call to main and select the previous call.
1095
        """
1096
        if '__libc_start_main' not in self.functions:
1!
1097
            return 0
×
1098

1099
        if 'exit' not in self.symbols:
1!
1100
            return 0
×
1101

1102
        # If there's no delay slot, execution continues on the next instruction after a call.
1103
        call_return_offset = 1
1✔
1104
        if self.arch in ['arm', 'thumb']:
1!
1105
            call_instructions = set(['blx', 'bl'])
×
1106
        elif self.arch == 'aarch64':
1!
1107
            call_instructions = set(['blr', 'bl'])
×
1108
        elif self.arch in ['mips', 'mips64']:
1!
1109
            call_instructions = set(['bal', 'jalr'])
×
1110
            # Account for the delay slot.
1111
            call_return_offset = 2
×
1112
        elif self.arch in ['i386', 'amd64', 'ia64']:
1!
1113
            call_instructions = set(['call'])
1✔
1114
        else:
1115
            log.error('Unsupported architecture %s in ELF.libc_start_main_return', self.arch)
×
1116
            return 0
×
1117
        
1118
        lines = self.functions['__libc_start_main'].disasm().split('\n')
1✔
1119
        exit_addr = hex(self.symbols['exit'])
1✔
1120
        calls = [(index, line) for index, line in enumerate(lines) if set(line.split()) & call_instructions]
1✔
1121

1122
        def find_ret_main_addr(lines, calls):
1✔
1123
            exit_calls = [index for index, line in enumerate(calls) if exit_addr in line[1]]
1✔
1124
            if len(exit_calls) != 1:
1✔
1125
                return 0
1✔
1126

1127
            call_to_main = calls[exit_calls[0] - 1]
1✔
1128
            return_from_main = lines[call_to_main[0] + call_return_offset].lstrip()
1✔
1129
            return_from_main = int(return_from_main[ : return_from_main.index(':') ], 16)
1✔
1130
            return return_from_main
1✔
1131
        
1132
        # Starting with glibc-2.34 calling `main` is split out into `__libc_start_call_main`
1133
        ret_addr = find_ret_main_addr(lines, calls)
1✔
1134
        # Pre glibc-2.34 case - `main` is called directly
1135
        if ret_addr:
1!
1136
            return ret_addr
×
1137

1138
        # `__libc_start_main` -> `__libc_start_call_main` -> `main`
1139
        # Find a direct call which calls `exit` once. That's probably `__libc_start_call_main`.
1140
        direct_call_pattern = re.compile(r'['+r'|'.join(call_instructions)+r']\s+(0x[0-9a-zA-Z]+)')
1✔
1141
        for line in calls:
1!
1142
            match = direct_call_pattern.search(line[1])
1✔
1143
            if not match:
1✔
1144
                continue
1✔
1145
            
1146
            target_addr = int(match.group(1), 0)
1✔
1147
            # `__libc_start_call_main` is usually smaller than `__libc_start_main`, so
1148
            # we might disassemble a bit too much, but it's a good dynamic estimate.
1149
            callee_lines = self.disasm(target_addr, self.functions['__libc_start_main'].size).split('\n')
1✔
1150
            callee_calls = [(index, line) for index, line in enumerate(callee_lines) if set(line.split()) & call_instructions]
1✔
1151
            ret_addr = find_ret_main_addr(callee_lines, callee_calls)
1✔
1152
            if ret_addr:
1✔
1153
                return ret_addr
1✔
1154
        return 0
×
1155

1156
    def search(self, needle, writable = False, executable = False):
1✔
1157
        """search(needle, writable = False, executable = False) -> generator
1158

1159
        Search the ELF's virtual address space for the specified string.
1160

1161
        Notes:
1162
            Does not search empty space between segments, or uninitialized
1163
            data.  This will only return data that actually exists in the
1164
            ELF file.  Searching for a long string of NULL bytes probably
1165
            won't work.
1166

1167
        Arguments:
1168
            needle(bytes): String to search for.
1169
            writable(bool): Search only writable sections.
1170
            executable(bool): Search only executable sections.
1171

1172
        Yields:
1173
            An iterator for each virtual address that matches.
1174

1175
        Examples:
1176

1177
            An ELF header starts with the bytes ``\\x7fELF``, so we
1178
            sould be able to find it easily.
1179

1180
            >>> bash = ELF('/bin/bash')
1181
            >>> bash.address + 1 == next(bash.search(b'ELF'))
1182
            True
1183

1184
            We can also search for string the binary.
1185

1186
            >>> len(list(bash.search(b'GNU bash'))) > 0
1187
            True
1188

1189
            It is also possible to search for instructions in executable sections.
1190

1191
            >>> binary = ELF.from_assembly('nop; mov eax, 0; jmp esp; ret')
1192
            >>> jmp_addr = next(binary.search(asm('jmp esp'), executable = True))
1193
            >>> binary.read(jmp_addr, 2) == asm('jmp esp')
1194
            True
1195
        """
1196
        load_address_fixup = (self.address - self.load_addr)
1✔
1197

1198
        if writable:
1!
1199
            segments = self.writable_segments
×
1200
        elif executable:
1✔
1201
            segments = self.executable_segments
1✔
1202
        else:
1203
            segments = self.segments
1✔
1204

1205
        for seg in segments:
1✔
1206
            addr   = seg.header.p_vaddr
1✔
1207
            memsz  = seg.header.p_memsz
1✔
1208
            zeroed = memsz - seg.header.p_filesz
1✔
1209
            offset = seg.header.p_offset
1✔
1210
            data   = self.mmap[offset:offset+memsz]
1✔
1211
            data   += b'\x00' * zeroed
1✔
1212
            offset = 0
1✔
1213
            while True:
1214
                offset = data.find(needle, offset)
1✔
1215
                if offset == -1:
1✔
1216
                    break
1✔
1217
                yield (addr + offset + load_address_fixup)
1✔
1218
                offset += 1
1✔
1219

1220
    def offset_to_vaddr(self, offset):
1✔
1221
        """offset_to_vaddr(offset) -> int
1222

1223
        Translates the specified offset to a virtual address.
1224

1225
        Arguments:
1226
            offset(int): Offset to translate
1227

1228
        Returns:
1229
            `int`: Virtual address which corresponds to the file offset, or
1230
            :const:`None`.
1231

1232
        Examples:
1233

1234
            This example shows that regardless of changes to the virtual
1235
            address layout by modifying :attr:`.ELF.address`, the offset
1236
            for any given address doesn't change.
1237

1238
            >>> bash = ELF('/bin/bash')
1239
            >>> bash.address == bash.offset_to_vaddr(0)
1240
            True
1241
            >>> bash.address += 0x123456
1242
            >>> bash.address == bash.offset_to_vaddr(0)
1243
            True
1244
        """
1245
        load_address_fixup = (self.address - self.load_addr)
1✔
1246

1247
        for segment in self.segments:
1!
1248
            begin = segment.header.p_offset
1✔
1249
            size  = segment.header.p_filesz
1✔
1250
            end   = begin + size
1✔
1251
            if begin <= offset and offset <= end:
1✔
1252
                delta = offset - begin
1✔
1253
                return segment.header.p_vaddr + delta + load_address_fixup
1✔
1254
        return None
×
1255

1256
    def _populate_memory(self):
1✔
1257
        load_segments = list(filter(lambda s: s.header.p_type == 'PT_LOAD', self.iter_segments()))
1✔
1258

1259
        # Map all of the segments
1260
        for i, segment in enumerate(load_segments):
1✔
1261
            start = segment.header.p_vaddr
1✔
1262
            stop_data = start + segment.header.p_filesz
1✔
1263
            stop_mem  = start + segment.header.p_memsz
1✔
1264

1265
            # Chop any existing segments which cover the range described by
1266
            # [vaddr, vaddr+filesz].
1267
            #
1268
            # This has the effect of removing any issues we may encounter
1269
            # with "overlapping" segments, by giving precedence to whichever
1270
            # DT_LOAD segment is **last** to load data into the region.
1271
            self.memory.chop(start, stop_data)
1✔
1272

1273
            # Fill the start of the segment's first page
1274
            page_start = align_down(0x1000, start)
1✔
1275
            if page_start < start and not self.memory[page_start]:
1!
1276
                self.memory.addi(page_start, start, None)
×
1277

1278
            # Add the new segment
1279
            if start != stop_data:
1✔
1280
                self.memory.addi(start, stop_data, segment)
1✔
1281

1282
            if stop_data != stop_mem:
1✔
1283
                self.memory.addi(stop_data, stop_mem, b'\x00')
1✔
1284

1285
            page_end = align(0x1000, stop_mem)
1✔
1286

1287
            # Check for holes which we can fill
1288
            if self._fill_gaps and i+1 < len(load_segments):
1✔
1289
                next_start = load_segments[i+1].header.p_vaddr
1✔
1290
                page_next = align_down(0x1000, next_start)
1✔
1291

1292
                if stop_mem < next_start:
1✔
1293
                    if page_end < page_next:
1✔
1294
                        if stop_mem < page_end:
1!
1295
                            self.memory.addi(stop_mem, page_end, None)
1✔
1296
                        if page_next < next_start:
1✔
1297
                            self.memory.addi(page_next, next_start, None)
1✔
1298
                    else:
1299
                        self.memory.addi(stop_mem, next_start, None)
1✔
1300
            else:
1301
                if stop_mem < page_end:
1✔
1302
                    self.memory.addi(stop_mem, page_end, None)
1✔
1303

1304
    def vaddr_to_offset(self, address):
1✔
1305
        """vaddr_to_offset(address) -> int
1306

1307
        Translates the specified virtual address to a file offset
1308

1309
        Arguments:
1310
            address(int): Virtual address to translate
1311

1312
        Returns:
1313
            int: Offset within the ELF file which corresponds to the address,
1314
            or :const:`None`.
1315

1316
        Examples:
1317
            >>> bash = ELF(which('bash'))
1318
            >>> bash.vaddr_to_offset(bash.address)
1319
            0
1320
            >>> bash.address += 0x123456
1321
            >>> bash.vaddr_to_offset(bash.address)
1322
            0
1323
            >>> bash.vaddr_to_offset(0) is None
1324
            True
1325
        """
1326

1327
        for interval in self.memory[address]:
1✔
1328
            segment = interval.data
1✔
1329

1330
            # Convert the address back to how it was when the segment was loaded
1331
            address = (address - self.address) + self.load_addr
1✔
1332

1333
            # Figure out the offset into the segment
1334
            offset = address - segment.header.p_vaddr
1✔
1335

1336
            # Add the segment-base offset to the offset-within-the-segment
1337
            return segment.header.p_offset + offset
1✔
1338

1339
    def read(self, address, count):
1✔
1340
        r"""read(address, count) -> bytes
1341

1342
        Read data from the specified virtual address
1343

1344
        Arguments:
1345
            address(int): Virtual address to read
1346
            count(int): Number of bytes to read
1347

1348
        Returns:
1349
            A :class:`bytes` object, or :const:`None`.
1350

1351
        Examples:
1352
            The simplest example is just to read the ELF header.
1353

1354
            >>> bash = ELF(which('bash'))
1355
            >>> bash.read(bash.address, 4)
1356
            b'\x7fELF'
1357

1358
            ELF segments do not have to contain all of the data on-disk
1359
            that gets loaded into memory.
1360

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

1363
            >>> assembly = '''
1364
            ... .section .A,"awx"
1365
            ... .global A
1366
            ... A: nop
1367
            ... .section .B,"awx"
1368
            ... .global B
1369
            ... B: int3
1370
            ... '''
1371
            >>> e = ELF.from_assembly(assembly, vma=False)
1372

1373
            By default, these come right after eachother in memory.
1374

1375
            >>> e.read(e.symbols.A, 2)
1376
            b'\x90\xcc'
1377
            >>> e.symbols.B - e.symbols.A
1378
            1
1379

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

1382
            >>> objcopy = pwnlib.asm._objcopy()
1383
            >>> objcopy += [
1384
            ...     '--change-section-vma', '.B+5',
1385
            ...     '--change-section-lma', '.B+5',
1386
            ...     e.path
1387
            ... ]
1388
            >>> subprocess.check_call(objcopy)
1389
            0
1390

1391
            Now let's re-load the ELF, and check again
1392

1393
            >>> e = ELF(e.path)
1394
            >>> e.symbols.B - e.symbols.A
1395
            6
1396
            >>> e.read(e.symbols.A, 2)
1397
            b'\x90\x00'
1398
            >>> e.read(e.symbols.A, 7)
1399
            b'\x90\x00\x00\x00\x00\x00\xcc'
1400
            >>> e.read(e.symbols.A, 10)
1401
            b'\x90\x00\x00\x00\x00\x00\xcc\x00\x00\x00'
1402

1403
            Everything is relative to the user-selected base address, so moving
1404
            things around keeps everything working.
1405

1406
            >>> e.address += 0x1000
1407
            >>> e.read(e.symbols.A, 10)
1408
            b'\x90\x00\x00\x00\x00\x00\xcc\x00\x00\x00'
1409
        """
1410
        retval = []
1✔
1411

1412
        if count == 0:
1!
1413
            return b''
×
1414

1415
        start = address
1✔
1416
        stop = address + count
1✔
1417

1418
        overlap = self.memory.overlap(start, stop)
1✔
1419

1420
        # Create a new view of memory, for just what we need
1421
        memory = intervaltree.IntervalTree(overlap)
1✔
1422
        memory.chop(-1<<64, start)
1✔
1423
        memory.chop(stop, 1<<64)
1✔
1424

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

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

1431
        # We have a view of memory which lets us get everything we need
1432
        for begin, end, data in sorted(memory):
1✔
1433
            length = end-begin
1✔
1434

1435
            if data in (None, b'\x00'):
1✔
1436
                retval.append(b'\x00' * length)
1✔
1437
                continue
1✔
1438

1439
            # Offset within VMA range
1440
            begin -= self.address
1✔
1441

1442
            # Adjust to original VMA range
1443
            begin += self.load_addr
1✔
1444

1445
            # Adjust to offset within segment VMA
1446
            offset = begin - data.header.p_vaddr
1✔
1447

1448
            # Adjust in-segment offset to in-file offset
1449
            offset += data.header.p_offset
1✔
1450

1451
            retval.append(self.mmap[offset:offset+length])
1✔
1452

1453
        return b''.join(retval)
1✔
1454

1455
    def write(self, address, data):
1✔
1456
        """Writes data to the specified virtual address
1457

1458
        Arguments:
1459
            address(int): Virtual address to write
1460
            data(str): Bytes to write
1461

1462
        Note:
1463
            This routine does not check the bounds on the write to ensure
1464
            that it stays in the same segment.
1465

1466
        Examples:
1467
          >>> bash = ELF(which('bash'))
1468
          >>> bash.read(bash.address+1, 3)
1469
          b'ELF'
1470
          >>> bash.write(bash.address, b"HELO")
1471
          >>> bash.read(bash.address, 4)
1472
          b'HELO'
1473
        """
1474
        offset = self.vaddr_to_offset(address)
1✔
1475

1476
        if offset is not None:
1!
1477
            length = len(data)
1✔
1478
            self.mmap[offset:offset+length] = data
1✔
1479

1480
        return None
1✔
1481

1482
    def save(self, path=None):
1✔
1483
        """Save the ELF to a file
1484

1485
        >>> bash = ELF(which('bash'))
1486
        >>> bash.save('/tmp/bash_copy')
1487
        >>> copy = open('/tmp/bash_copy', 'rb')
1488
        >>> bash = open(which('bash'), 'rb')
1489
        >>> bash.read() == copy.read()
1490
        True
1491
        """
1492
        if path is None:
1✔
1493
            path = self.path
1✔
1494
        misc.write(path, self.data)
1✔
1495

1496
    def get_data(self):
1✔
1497
        """get_data() -> bytes
1498

1499
        Retrieve the raw data from the ELF file.
1500

1501
        >>> bash = ELF(which('bash'))
1502
        >>> fd   = open(which('bash'), 'rb')
1503
        >>> bash.get_data() == fd.read()
1504
        True
1505
        """
1506
        return self.mmap[:]
1✔
1507

1508
    @property
1✔
1509
    def data(self):
1✔
1510
        """:class:`bytes`: Raw data of the ELF file.
1511

1512
        See:
1513
            :meth:`get_data`
1514
        """
1515
        return self.mmap[:]
1✔
1516

1517
    def disasm(self, address, n_bytes):
1✔
1518
        """disasm(address, n_bytes) -> str
1519

1520
        Returns a string of disassembled instructions at
1521
        the specified virtual memory address"""
1522
        arch = self.arch
1✔
1523
        if self.arch == 'arm' and address & 1:
1!
1524
            arch = 'thumb'
×
1525
            address -= 1
×
1526

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

1529
    def asm(self, address, assembly):
1✔
1530
        """asm(address, assembly)
1531

1532
        Assembles the specified instructions and inserts them
1533
        into the ELF at the specified address.
1534

1535
        This modifies the ELF in-place.
1536
        The resulting binary can be saved with :meth:`.ELF.save`
1537
        """
1538
        binary = asm(assembly, vma=address, arch=self.arch, endian=self.endian, bits=self.bits)
1✔
1539
        self.write(address, binary)
1✔
1540

1541
    def bss(self, offset=0):
1✔
1542
        """bss(offset=0) -> int
1543

1544
        Returns:
1545
            Address of the ``.bss`` section, plus the specified offset.
1546
        """
1547
        orig_bss = self.get_section_by_name('.bss').header.sh_addr
×
1548
        curr_bss = orig_bss - self.load_addr + self.address
×
1549
        return curr_bss + offset
×
1550

1551
    def __repr__(self):
1✔
1552
        return "%s(%r)" % (self.__class__.__name__, self.path)
1✔
1553

1554
    def dynamic_by_tag(self, tag):
1✔
1555
        """dynamic_by_tag(tag) -> tag
1556

1557
        Arguments:
1558
            tag(str): Named ``DT_XXX`` tag (e.g. ``'DT_STRTAB'``).
1559

1560
        Returns:
1561
            :class:`elftools.elf.dynamic.DynamicTag`
1562
        """
1563
        dt      = None
1✔
1564
        dynamic = self.get_section_by_name('.dynamic')
1✔
1565

1566
        if not dynamic:
1✔
1567
            return None
1✔
1568

1569
        try:
1✔
1570
            dt = next(t for t in dynamic.iter_tags() if tag == t.entry.d_tag)
1✔
1571
        except StopIteration:
1✔
1572
            pass
1✔
1573

1574
        return dt
1✔
1575

1576
    def dynamic_value_by_tag(self, tag):
1✔
1577
        """dynamic_value_by_tag(tag) -> int
1578

1579
        Retrieve the value from a dynamic tag a la ``DT_XXX``.
1580

1581
        If the tag is missing, returns ``None``.
1582
        """
1583
        tag = self.dynamic_by_tag(tag)
1✔
1584

1585
        if tag:
1✔
1586
            return tag.entry.d_val
1✔
1587

1588
    def dynamic_string(self, offset):
1✔
1589
        """dynamic_string(offset) -> bytes
1590

1591
        Fetches an enumerated string from the ``DT_STRTAB`` table.
1592

1593
        Arguments:
1594
            offset(int): String index
1595

1596
        Returns:
1597
            :class:`str`: String from the table as raw bytes.
1598
        """
1599
        dt_strtab = self.dynamic_by_tag('DT_STRTAB')
×
1600

1601
        if not dt_strtab:
×
1602
            return None
×
1603

1604
        address   = dt_strtab.entry.d_ptr + offset
×
1605
        string    = b''
×
1606
        while b'\x00' not in string:
×
1607
            string  += self.read(address, 1)
×
1608
            address += 1
×
1609
        return string.rstrip(b'\x00')
×
1610

1611

1612

1613
    @property
1✔
1614
    def relro(self):
1✔
1615
        """:class:`bool`: Whether the current binary uses RELRO protections.
1616

1617
        This requires both presence of the dynamic tag ``DT_BIND_NOW``, and
1618
        a ``GNU_RELRO`` program header.
1619

1620
        The `ELF Specification`_ describes how the linker should resolve
1621
        symbols immediately, as soon as a binary is loaded.  This can be
1622
        emulated with the ``LD_BIND_NOW=1`` environment variable.
1623

1624
            ``DT_BIND_NOW``
1625

1626
            If present in a shared object or executable, this entry instructs
1627
            the dynamic linker to process all relocations for the object
1628
            containing this entry before transferring control to the program.
1629
            The presence of this entry takes precedence over a directive to use
1630
            lazy binding for this object when specified through the environment
1631
            or via ``dlopen(BA_LIB)``.
1632

1633
            (`page 81`_)
1634

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

1639
        Finally, a new-ish extension which doesn't seem to have a canonical
1640
        source of documentation is DF_BIND_NOW_, which has supposedly superceded
1641
        ``DT_BIND_NOW``.
1642

1643
            ``DF_BIND_NOW``
1644

1645
            If set in a shared object or executable, this flag instructs the
1646
            dynamic linker to process all relocations for the object containing
1647
            this entry before transferring control to the program. The presence
1648
            of this entry takes precedence over a directive to use lazy binding
1649
            for this object when specified through the environment or via
1650
            ``dlopen(BA_LIB)``.
1651

1652
        .. _ELF Specification: https://refspecs.linuxbase.org/elf/elf.pdf
1653
        .. _page 81: https://refspecs.linuxbase.org/elf/elf.pdf#page=81
1654
        .. _DT_BIND_NOW: https://refspecs.linuxbase.org/elf/elf.pdf#page=81
1655
        .. _PT_GNU_RELRO: https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic.html#PROGHEADER
1656
        .. _DF_BIND_NOW: https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html#df_bind_now
1657

1658
        >>> path = pwnlib.data.elf.relro.path
1659
        >>> for test in glob(os.path.join(path, 'test-*')):
1660
        ...     e = ELF(test)
1661
        ...     expected = os.path.basename(test).split('-')[2]
1662
        ...     actual = str(e.relro).lower()
1663
        ...     assert actual == expected
1664
        """
1665
        if not any('GNU_RELRO' in str(s.header.p_type) for s in self.segments):
1✔
1666
            return None
1✔
1667

1668
        if self.dynamic_by_tag('DT_BIND_NOW'):
1✔
1669
            return "Full"
1✔
1670

1671
        flags = self.dynamic_value_by_tag('DT_FLAGS')
1✔
1672
        if flags and flags & constants.DF_BIND_NOW:
1✔
1673
            return "Full"
1✔
1674

1675
        flags_1 = self.dynamic_value_by_tag('DT_FLAGS_1')
1✔
1676
        if flags_1 and flags_1 & constants.DF_1_NOW:
1!
1677
            return "Full"
×
1678

1679
        return "Partial"
1✔
1680

1681
    @property
1✔
1682
    def nx(self):
1✔
1683
        """:class:`bool`: Whether the current binary uses NX protections.
1684

1685
        Specifically, we are checking for ``READ_IMPLIES_EXEC`` being set
1686
        by the kernel, as a result of honoring ``PT_GNU_STACK`` in the kernel.
1687

1688
        The **Linux kernel** directly honors ``PT_GNU_STACK`` to `mark the
1689
        stack as executable.`__
1690

1691
        .. __: https://github.com/torvalds/linux/blob/v4.9/fs/binfmt_elf.c#L784-L789
1692

1693
        .. code-block:: c
1694

1695
            case PT_GNU_STACK:
1696
                if (elf_ppnt->p_flags & PF_X)
1697
                    executable_stack = EXSTACK_ENABLE_X;
1698
                else
1699
                    executable_stack = EXSTACK_DISABLE_X;
1700
                break;
1701

1702
        Additionally, it then sets ``read_implies_exec``, so that `all readable pages
1703
        are executable`__.
1704

1705
        .. __: https://github.com/torvalds/linux/blob/v4.9/fs/binfmt_elf.c#L849-L850
1706

1707
        .. code-block:: c
1708

1709
            if (elf_read_implies_exec(loc->elf_ex, executable_stack))
1710
                current->personality |= READ_IMPLIES_EXEC;
1711
        """
1712
        if not self.executable:
1✔
1713
            return True
1✔
1714

1715
        for seg in self.iter_segments_by_type('GNU_STACK'):
1✔
1716
            return not bool(seg.header.p_flags & P_FLAGS.PF_X)
1✔
1717

1718
        # If you NULL out the PT_GNU_STACK section via ELF.disable_nx(),
1719
        # everything is executable.
1720
        return False
1✔
1721

1722
    @property
1✔
1723
    def execstack(self):
1✔
1724
        """:class:`bool`: Whether the current binary uses an executable stack.
1725

1726
        This is based on the presence of a program header PT_GNU_STACK_
1727
        being present, and its setting.
1728

1729
            ``PT_GNU_STACK``
1730

1731
            The p_flags member specifies the permissions on the segment
1732
            containing the stack and is used to indicate whether the stack
1733
            should be executable. The absense of this header indicates
1734
            that the stack will be executable.
1735

1736
        In particular, if the header is missing the stack is executable.
1737
        If the header is present, it may **explicitly** mark that the stack is
1738
        executable.
1739

1740
        This is only somewhat accurate.  When using the GNU Linker, it usees
1741
        DEFAULT_STACK_PERMS_ to decide whether a lack of ``PT_GNU_STACK``
1742
        should mark the stack as executable:
1743

1744
        .. code-block:: c
1745

1746
            /* On most platforms presume that PT_GNU_STACK is absent and the stack is
1747
             * executable.  Other platforms default to a nonexecutable stack and don't
1748
             * need PT_GNU_STACK to do so.  */
1749
            uint_fast16_t stack_flags = DEFAULT_STACK_PERMS;
1750

1751
        By searching the source for ``DEFAULT_STACK_PERMS``, we can see which
1752
        architectures have which settings.
1753

1754
        ::
1755

1756
            $ git grep '#define DEFAULT_STACK_PERMS' | grep -v PF_X
1757
            sysdeps/aarch64/stackinfo.h:31:#define DEFAULT_STACK_PERMS (PF_R|PF_W)
1758
            sysdeps/nios2/stackinfo.h:31:#define DEFAULT_STACK_PERMS (PF_R|PF_W)
1759
            sysdeps/tile/stackinfo.h:31:#define DEFAULT_STACK_PERMS (PF_R|PF_W)
1760

1761
        .. _PT_GNU_STACK: https://refspecs.linuxbase.org/LSB_3.0.0/LSB-PDA/LSB-PDA/progheader.html
1762
        .. _DEFAULT_STACK_PERMS: https://github.com/bminor/glibc/blob/glibc-2.25/elf/dl-load.c#L1036-L1038
1763
        """
1764
        # Dynamic objects do not have the ability to change the executable state of the stack.
1765
        if not self.executable:
1✔
1766
            return False
1✔
1767

1768
        # If NX is completely off for the process, the stack is executable.
1769
        if not self.nx:
1✔
1770
            return True
1✔
1771

1772
        # If the ``PT_GNU_STACK`` program header is missing, then use the
1773
        # default rules.  Only AArch64 gets a non-executable stack by default.
1774
        for _ in self.iter_segments_by_type('GNU_STACK'):
1!
1775
            break
1✔
1776
        else:
1777
            return self.arch != 'aarch64'
×
1778

1779
        return False
1✔
1780

1781
    @property
1✔
1782
    def canary(self):
1✔
1783
        """:class:`bool`: Whether the current binary uses stack canaries."""
1784

1785
        # Sometimes there is no function for __stack_chk_fail,
1786
        # but there is an entry in the GOT
1787
        return '__stack_chk_fail' in (set(self.symbols) | set(self.got))
1✔
1788

1789
    @property
1✔
1790
    def packed(self):
1✔
1791
        """:class:`bool`: Whether the current binary is packed with UPX."""
1792
        return b'UPX!' in self.get_data()[:0xFF]
1✔
1793

1794
    @property
1✔
1795
    def pie(self):
1✔
1796
        """:class:`bool`: Whether the current binary is position-independent."""
1797
        return self.elftype == 'DYN'
1✔
1798
    aslr=pie
1✔
1799

1800
    @property
1✔
1801
    def rpath(self):
1✔
1802
        """:class:`bool`: Whether the current binary has an ``RPATH``."""
1803
        dt_rpath = self.dynamic_by_tag('DT_RPATH')
1✔
1804

1805
        if not dt_rpath:
1!
1806
            return None
1✔
1807

1808
        return self.dynamic_string(dt_rpath.entry.d_ptr)
×
1809

1810
    @property
1✔
1811
    def runpath(self):
1✔
1812
        """:class:`bool`: Whether the current binary has a ``RUNPATH``."""
1813
        dt_runpath = self.dynamic_by_tag('DT_RUNPATH')
1✔
1814

1815
        if not dt_runpath:
1!
1816
            return None
1✔
1817

1818
        return self.dynamic_string(dt_runpath.entry.d_ptr)
×
1819

1820
    def checksec(self, banner=True, color=True):
1✔
1821
        """checksec(banner=True, color=True)
1822

1823
        Prints out information in the binary, similar to ``checksec.sh``.
1824

1825
        Arguments:
1826
            banner(bool): Whether to print the path to the ELF binary.
1827
            color(bool): Whether to use colored output.
1828
        """
1829
        red    = text.red if color else str
1✔
1830
        green  = text.green if color else str
1✔
1831
        yellow = text.yellow if color else str
1✔
1832

1833
        res = []
1✔
1834

1835
        # Kernel version?
1836
        if self.version and self.version != (0,):
1!
1837
            res.append('Version:'.ljust(10) + '.'.join(map(str, self.version)))
×
1838
        if self.build:
1!
1839
            res.append('Build:'.ljust(10) + self.build)
×
1840

1841
        res.extend([
1✔
1842
            "RELRO:".ljust(10) + {
1843
                'Full':    green("Full RELRO"),
1844
                'Partial': yellow("Partial RELRO"),
1845
                None:      red("No RELRO")
1846
            }[self.relro],
1847
            "Stack:".ljust(10) + {
1848
                True:  green("Canary found"),
1849
                False: red("No canary found")
1850
            }[self.canary],
1851
            "NX:".ljust(10) + {
1852
                True:  green("NX enabled"),
1853
                False: red("NX disabled"),
1854
            }[self.nx],
1855
            "PIE:".ljust(10) + {
1856
                True: green("PIE enabled"),
1857
                False: red("No PIE (%#x)" % self.address)
1858
            }[self.pie],
1859
        ])
1860

1861
        # Execstack may be a thing, even with NX enabled, because of glibc
1862
        if self.execstack and self.nx:
1!
1863
            res.append("Stack:".ljust(10) + red("Executable"))
×
1864

1865
        # Are there any RWX areas in the binary?
1866
        #
1867
        # This will occur if NX is disabled and *any* area is
1868
        # RW, or can expressly occur.
1869
        if self.rwx_segments or (not self.nx and self.writable_segments):
1✔
1870
            res += [ "RWX:".ljust(10) + red("Has RWX segments") ]
1✔
1871

1872
        if self.rpath:
1!
1873
            res += [ "RPATH:".ljust(10) + red(repr(self.rpath)) ]
×
1874

1875
        if self.runpath:
1!
1876
            res += [ "RUNPATH:".ljust(10) + red(repr(self.runpath)) ]
×
1877

1878
        if self.packed:
1!
1879
            res.append('Packer:'.ljust(10) + red("Packed with UPX"))
×
1880

1881
        if self.fortify:
1✔
1882
            res.append("FORTIFY:".ljust(10) + green("Enabled"))
1✔
1883

1884
        if self.asan:
1!
1885
            res.append("ASAN:".ljust(10) + green("Enabled"))
×
1886

1887
        if self.msan:
1!
1888
            res.append("MSAN:".ljust(10) + green("Enabled"))
×
1889

1890
        if self.ubsan:
1!
1891
            res.append("UBSAN:".ljust(10) + green("Enabled"))
×
1892

1893
        # Check for Linux configuration, it must contain more than
1894
        # just the version.
1895
        if len(self.config) > 1:
1!
1896
            config_opts = collections.defaultdict(list)
×
1897
            for checker in kernel_configuration:
×
1898
                result, message = checker(self.config)
×
1899

1900
                if not result:
×
1901
                    config_opts[checker.title].append((checker.name, message))
×
1902

1903

1904
            for title, values in config_opts.items():
×
1905
                res.append(title + ':')
×
1906
                for name, message in sorted(values):
×
1907
                    line = '{} = {}'.format(name, red(str(self.config.get(name, None))))
×
1908
                    if message:
×
1909
                        line += ' ({})'.format(message)
×
1910
                    res.append('    ' + line)
×
1911

1912
            # res.extend(sorted(config_opts))
1913

1914
        return '\n'.join(res)
1✔
1915

1916
    @property
1✔
1917
    def buildid(self):
1✔
1918
        """:class:`bytes`: GNU Build ID embedded into the binary"""
1919
        section = self.get_section_by_name('.note.gnu.build-id')
1✔
1920
        if section:
1!
1921
            return section.data()[16:]
1✔
1922
        return None
×
1923

1924
    @property
1✔
1925
    def fortify(self):
1✔
1926
        """:class:`bool`: Whether the current binary was built with
1927
        Fortify Source (``-DFORTIFY``)."""
1928
        if any(s.endswith('_chk') for s in self.plt):
1✔
1929
            return True
1✔
1930
        return False
1✔
1931

1932
    @property
1✔
1933
    def asan(self):
1✔
1934
        """:class:`bool`: Whether the current binary was built with
1935
        Address Sanitizer (``ASAN``)."""
1936
        return any(s.startswith('__asan_') for s in self.symbols)
1✔
1937

1938
    @property
1✔
1939
    def msan(self):
1✔
1940
        """:class:`bool`: Whether the current binary was built with
1941
        Memory Sanitizer (``MSAN``)."""
1942
        return any(s.startswith('__msan_') for s in self.symbols)
1✔
1943

1944
    @property
1✔
1945
    def ubsan(self):
1✔
1946
        """:class:`bool`: Whether the current binary was built with
1947
        Undefined Behavior Sanitizer (``UBSAN``)."""
1948
        return any(s.startswith('__ubsan_') for s in self.symbols)
1✔
1949

1950
    def _update_args(self, kw):
1✔
1951
        kw.setdefault('arch', self.arch)
1✔
1952
        kw.setdefault('bits', self.bits)
1✔
1953
        kw.setdefault('endian', self.endian)
1✔
1954

1955
    def p64(self,  address, data, *a, **kw):
1✔
1956
        """Writes a 64-bit integer ``data`` to the specified ``address``"""
1957
        self._update_args(kw)
×
1958
        return self.write(address, packing.p64(data, *a, **kw))
×
1959

1960
    def p32(self,  address, data, *a, **kw):
1✔
1961
        """Writes a 32-bit integer ``data`` to the specified ``address``"""
1962
        self._update_args(kw)
×
1963
        return self.write(address, packing.p32(data, *a, **kw))
×
1964

1965
    def p16(self,  address, data, *a, **kw):
1✔
1966
        """Writes a 16-bit integer ``data`` to the specified ``address``"""
1967
        self._update_args(kw)
×
1968
        return self.write(address, packing.p16(data, *a, **kw))
×
1969

1970
    def p8(self,   address, data, *a, **kw):
1✔
1971
        """Writes a 8-bit integer ``data`` to the specified ``address``"""
1972
        self._update_args(kw)
×
1973
        return self.write(address, packing.p8(data, *a, **kw))
×
1974

1975
    def pack(self, address, data, *a, **kw):
1✔
1976
        """Writes a packed integer ``data`` to the specified ``address``"""
1977
        self._update_args(kw)
1✔
1978
        return self.write(address, packing.pack(data, *a, **kw))
1✔
1979

1980
    def u64(self,    address, *a, **kw):
1✔
1981
        """Unpacks an integer from the specified ``address``."""
1982
        self._update_args(kw)
×
1983
        return packing.u64(self.read(address, 8), *a, **kw)
×
1984

1985
    def u32(self,    address, *a, **kw):
1✔
1986
        """Unpacks an integer from the specified ``address``."""
1987
        self._update_args(kw)
×
1988
        return packing.u32(self.read(address, 4), *a, **kw)
×
1989

1990
    def u16(self,    address, *a, **kw):
1✔
1991
        """Unpacks an integer from the specified ``address``."""
1992
        self._update_args(kw)
×
1993
        return packing.u16(self.read(address, 2), *a, **kw)
×
1994

1995
    def u8(self,     address, *a, **kw):
1✔
1996
        """Unpacks an integer from the specified ``address``."""
1997
        self._update_args(kw)
×
1998
        return packing.u8(self.read(address, 1), *a, **kw)
×
1999

2000
    def unpack(self, address, *a, **kw):
1✔
2001
        """Unpacks an integer from the specified ``address``."""
2002
        self._update_args(kw)
1✔
2003
        return packing.unpack(self.read(address, self.bytes), *a, **kw)
1✔
2004

2005
    def string(self, address):
1✔
2006
        """string(address) -> str
2007

2008
        Reads a null-terminated string from the specified ``address``
2009

2010
        Returns:
2011
            A ``str`` with the string contents (NUL terminator is omitted),
2012
            or an empty string if no NUL terminator could be found.
2013
        """
2014
        data = b''
1✔
2015
        while True:
2016
            read_size = 0x1000
1✔
2017
            partial_page = address & 0xfff
1✔
2018

2019
            if partial_page:
1✔
2020
                read_size -= partial_page
1✔
2021

2022
            c = self.read(address, read_size)
1✔
2023

2024
            if not c:
1!
2025
                return b''
×
2026

2027
            data += c
1✔
2028

2029
            if b'\x00' in c:
1✔
2030
                return data[:data.index(b'\x00')]
1✔
2031

2032
            address += len(c)
1✔
2033

2034
    def flat(self, address, *a, **kw):
1✔
2035
        """Writes a full array of values to the specified address.
2036

2037
        See: :func:`.packing.flat`
2038
        """
2039
        return self.write(address, packing.flat(*a,**kw))
×
2040

2041
    def fit(self, address, *a, **kw):
1✔
2042
        """Writes fitted data into the specified address.
2043

2044
        See: :func:`.packing.fit`
2045
        """
2046
        return self.write(address, packing.fit(*a, **kw))
×
2047

2048
    def parse_kconfig(self, data):
1✔
2049
        self.config.update(parse_kconfig(data))
×
2050

2051
    def disable_nx(self):
1✔
2052
        """Disables NX for the ELF.
2053

2054
        Zeroes out the ``PT_GNU_STACK`` program header ``p_type`` field.
2055
        """
2056
        PT_GNU_STACK = packing.p32(ENUM_P_TYPE['PT_GNU_STACK'])
×
2057

2058
        if not self.executable:
×
2059
            log.error("Can only make stack executable with executables")
×
2060

2061
        for i, segment in enumerate(self.iter_segments()):
×
2062
            if not segment.header.p_type:
×
2063
                continue
×
2064
            if 'GNU_STACK' not in segment.header.p_type:
×
2065
                continue
×
2066

2067
            phoff = self.header.e_phoff
×
2068
            phentsize = self.header.e_phentsize
×
2069
            offset = phoff + phentsize * i
×
2070

2071
            if self.mmap[offset:offset+4] == PT_GNU_STACK:
×
2072
                self.mmap[offset:offset+4] = b'\x00' * 4
×
2073
                self.save()
×
2074
                return
×
2075

2076
        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