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

Gallopsled / pwntools / 11996306614

24 Nov 2024 12:54PM UTC coverage: 73.582% (+0.1%) from 73.442%
11996306614

push

github

web-flow
Merge branch 'dev' into add_ko_file_search_support

3796 of 6420 branches covered (59.13%)

62 of 65 new or added lines in 3 files covered. (95.38%)

3 existing lines in 2 files now uncovered.

13311 of 18090 relevant lines covered (73.58%)

0.74 hits per line

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

75.66
/pwnlib/elf/corefile.py
1
# -*- coding: utf-8 -*-
2
"""Read information from Core Dumps.
3

4
Core dumps are extremely useful when writing exploits, even outside of
5
the normal act of debugging things.
6

7
Using Corefiles to Automate Exploitation
8
----------------------------------------
9

10
For example, if you have a trivial buffer overflow and don't want to
11
open up a debugger or calculate offsets, you can use a generated core
12
dump to extract the relevant information.
13

14
.. code-block:: c
15

16
    #include <string.h>
17
    #include <stdlib.h>
18
    #include <unistd.h>
19
    void win() {
20
        system("sh");
21
    }
22
    int main(int argc, char** argv) {
23
        char buffer[64];
24
        strcpy(buffer, argv[1]);
25
    }
26

27
.. code-block:: shell
28

29
    $ gcc crash.c -m32 -o crash -fno-stack-protector
30

31
.. code-block:: python
32

33
    from pwn import *
34

35
    # Generate a cyclic pattern so that we can auto-find the offset
36
    payload = cyclic(128)
37

38
    # Run the process once so that it crashes
39
    process(['./crash', payload]).wait()
40

41
    # Get the core dump
42
    core = Coredump('./core')
43

44
    # Our cyclic pattern should have been used as the crashing address
45
    assert pack(core.eip) in payload
46

47
    # Cool! Now let's just replace that value with the address of 'win'
48
    crash = ELF('./crash')
49
    payload = fit({
50
        cyclic_find(core.eip): crash.symbols.win
51
    })
52

53
    # Get a shell!
54
    io = process(['./crash', payload])
55
    io.sendline(b'id')
56
    print(io.recvline())
57
    # uid=1000(user) gid=1000(user) groups=1000(user)
58

59
Module Members
60
----------------------------------------
61

62
"""
63
from __future__ import absolute_import
1✔
64
from __future__ import division
1✔
65

66
import collections
1✔
67
import ctypes
1✔
68
import glob
1✔
69
import gzip
1✔
70
import re
1✔
71
import os
1✔
72
import socket
1✔
73
import subprocess
1✔
74
import tempfile
1✔
75

76
from io import BytesIO, StringIO
1✔
77

78
import elftools
1✔
79
from elftools.common.utils import roundup
1✔
80
from elftools.common.utils import struct_parse
1✔
81
from elftools.construct import CString
1✔
82

83
from pwnlib import atexit
1✔
84
from pwnlib.context import context
1✔
85
from pwnlib.elf.datatypes import *
1✔
86
from pwnlib.elf.elf import ELF
1✔
87
from pwnlib.log import getLogger
1✔
88
from pwnlib.tubes.process import process
1✔
89
from pwnlib.tubes.ssh import ssh_channel
1✔
90
from pwnlib.tubes.tube import tube
1✔
91
from pwnlib.util.fiddling import b64d
1✔
92
from pwnlib.util.fiddling import enhex
1✔
93
from pwnlib.util.fiddling import unhex
1✔
94
from pwnlib.util.misc import read
1✔
95
from pwnlib.util.misc import write
1✔
96
from pwnlib.util.packing import pack
1✔
97
from pwnlib.util.packing import unpack_many
1✔
98

99
log = getLogger(__name__)
1✔
100

101
prstatus_types = {
1✔
102
    'i386': elf_prstatus_i386,
103
    'amd64': elf_prstatus_amd64,
104
    'arm': elf_prstatus_arm,
105
    'aarch64': elf_prstatus_aarch64
106
}
107

108
siginfo_types = {
1✔
109
    32: elf_siginfo_32,
110
    64: elf_siginfo_64
111
}
112

113

114
class Mapping(object):
1✔
115
    """Encapsulates information about a memory mapping in a :class:`Corefile`.
116
    """
117
    def __init__(self, core, name, start, stop, flags, page_offset):
1✔
118
        self._core=core
1✔
119

120
        #: :class:`str`: Name of the mapping, e.g. ``'/bin/bash'`` or ``'[vdso]'``.
121
        self.name = name or ''
1✔
122

123
        #: :class:`int`: First mapped byte in the mapping
124
        self.start = start
1✔
125

126
        #: :class:`int`: First byte after the end of hte mapping
127
        self.stop = stop
1✔
128

129
        #: :class:`int`: Size of the mapping, in bytes
130
        self.size = stop-start
1✔
131

132
        #: :class:`int`: Offset in pages in the mapped file
133
        self.page_offset = page_offset or 0
1✔
134

135
        #: :class:`int`: Mapping flags, using e.g. ``PROT_READ`` and so on.
136
        self.flags = flags
1✔
137

138
    @property
1✔
139
    def path(self):
1✔
140
        """:class:`str`: Alias for :attr:`.Mapping.name`"""
141
        return self.name
×
142

143
    @property
1✔
144
    def address(self):
1✔
145
        """:class:`int`: Alias for :data:`Mapping.start`."""
146
        return self.start
1✔
147

148
    @property
1✔
149
    def permstr(self):
1✔
150
        """:class:`str`: Human-readable memory permission string, e.g. ``r-xp``."""
151
        flags = self.flags
×
152
        return ''.join(['r' if flags & 4 else '-',
×
153
                        'w' if flags & 2 else '-',
154
                        'x' if flags & 1 else '-',
155
                        'p'])
156
    def __str__(self):
1✔
157
        return '%x-%x %s %x %s' % (self.start,self.stop,self.permstr,self.size,self.name)
×
158

159
    def __repr__(self):
1✔
160
        return '%s(%r, start=%#x, stop=%#x, size=%#x, flags=%#x, page_offset=%#x)' \
1✔
161
            % (self.__class__.__name__,
162
               self.name,
163
               self.start,
164
               self.stop,
165
               self.size,
166
               self.flags,
167
               self.page_offset)
168

169
    def __int__(self):
1✔
170
        return self.start
×
171

172
    @property
1✔
173
    def data(self):
1✔
174
        """:class:`str`: Memory of the mapping."""
175
        return self._core.read(self.start, self.size)
1✔
176

177
    def __getitem__(self, item):
1✔
178
        if isinstance(item, slice):
1✔
179
            start = int(item.start or self.start)
1✔
180
            stop  = int(item.stop or self.stop)
1✔
181

182
            # Negative slices...
183
            if start < 0:
1!
184
                start += self.stop
×
185
            if stop < 0:
1!
186
                stop += self.stop
×
187

188
            if not (self.start <= start <= stop <= self.stop):
1!
189
                log.error("Byte range [%#x:%#x] not within range [%#x:%#x]",
×
190
                          start, stop, self.start, self.stop)
191

192
            data = self._core.read(start, stop-start)
1✔
193

194
            if item.step == 1:
1!
195
                return data
×
196
            return data[::item.step]
1✔
197

198
        return self._core.read(item, 1)
1✔
199

200
    def __contains__(self, item):
1✔
201
        if isinstance(item, Mapping):
1!
202
            return (self.start <= item.start) and (item.stop <= self.stop)
×
203
        return self.start <= item < self.stop
1✔
204

205
    def find(self, sub, start=None, end=None):
1✔
206
        """Similar to str.find() but works on our address space"""
207
        if start is None:
1!
208
            start = self.start
1✔
209
        if end is None:
1!
210
            end = self.stop
×
211

212
        result = self.data.find(sub, start-self.address, end-self.address)
1✔
213

214
        if result == -1:
1!
215
            return result
×
216

217
        return result + self.address
1✔
218

219
    def rfind(self, sub, start=None, end=None):
1✔
220
        """Similar to str.rfind() but works on our address space"""
221
        if start is None:
1!
222
            start = self.start
1✔
223
        if end is None:
1!
224
            end = self.stop
×
225

226
        result = self.data.rfind(sub, start-self.address, end-self.address)
1✔
227

228
        if result == -1:
1!
229
            return result
×
230

231
        return result + self.address
1✔
232

233
class Corefile(ELF):
1✔
234
    r"""Enhances the information available about a corefile (which is an extension
235
    of the ELF format) by permitting extraction of information about the mapped
236
    data segments, and register state.
237

238
    Registers can be accessed directly, e.g. via ``core_obj.eax`` and enumerated
239
    via :data:`Corefile.registers`.
240

241
    Memory can be accessed directly via :meth:`.read` or :meth:`.write`, and also
242
    via :meth:`.pack` or :meth:`.unpack` or even :meth:`.string`.
243

244
    Arguments:
245
        core: Path to the core file.  Alternately, may be a :class:`.process` instance,
246
              and the core file will be located automatically.
247

248
    ::
249

250
        >>> c = Corefile('./core')
251
        >>> hex(c.eax)
252
        '0xfff5f2e0'
253
        >>> c.registers
254
        {'eax': 4294308576,
255
         'ebp': 1633771891,
256
         'ebx': 4151132160,
257
         'ecx': 4294311760,
258
         'edi': 0,
259
         'edx': 4294308700,
260
         'eflags': 66050,
261
         'eip': 1633771892,
262
         'esi': 0,
263
         'esp': 4294308656,
264
         'orig_eax': 4294967295,
265
         'xcs': 35,
266
         'xds': 43,
267
         'xes': 43,
268
         'xfs': 0,
269
         'xgs': 99,
270
         'xss': 43}
271

272
    Mappings can be iterated in order via :attr:`Corefile.mappings`.
273

274
    ::
275

276
        >>> Corefile('./core').mappings
277
        [Mapping('/home/user/pwntools/crash', start=0x8048000, stop=0x8049000, size=0x1000, flags=0x5, page_offset=0x0),
278
         Mapping('/home/user/pwntools/crash', start=0x8049000, stop=0x804a000, size=0x1000, flags=0x4, page_offset=0x1),
279
         Mapping('/home/user/pwntools/crash', start=0x804a000, stop=0x804b000, size=0x1000, flags=0x6, page_offset=0x2),
280
         Mapping(None, start=0xf7528000, stop=0xf7529000, size=0x1000, flags=0x6, page_offset=0x0),
281
         Mapping('/lib/i386-linux-gnu/libc-2.19.so', start=0xf7529000, stop=0xf76d1000, size=0x1a8000, flags=0x5, page_offset=0x0),
282
         Mapping('/lib/i386-linux-gnu/libc-2.19.so', start=0xf76d1000, stop=0xf76d2000, size=0x1000, flags=0x0, page_offset=0x1a8),
283
         Mapping('/lib/i386-linux-gnu/libc-2.19.so', start=0xf76d2000, stop=0xf76d4000, size=0x2000, flags=0x4, page_offset=0x1a9),
284
         Mapping('/lib/i386-linux-gnu/libc-2.19.so', start=0xf76d4000, stop=0xf76d5000, size=0x1000, flags=0x6, page_offset=0x1aa),
285
         Mapping(None, start=0xf76d5000, stop=0xf76d8000, size=0x3000, flags=0x6, page_offset=0x0),
286
         Mapping(None, start=0xf76ef000, stop=0xf76f1000, size=0x2000, flags=0x6, page_offset=0x0),
287
         Mapping('[vdso]', start=0xf76f1000, stop=0xf76f2000, size=0x1000, flags=0x5, page_offset=0x0),
288
         Mapping('/lib/i386-linux-gnu/ld-2.19.so', start=0xf76f2000, stop=0xf7712000, size=0x20000, flags=0x5, page_offset=0x0),
289
         Mapping('/lib/i386-linux-gnu/ld-2.19.so', start=0xf7712000, stop=0xf7713000, size=0x1000, flags=0x4, page_offset=0x20),
290
         Mapping('/lib/i386-linux-gnu/ld-2.19.so', start=0xf7713000, stop=0xf7714000, size=0x1000, flags=0x6, page_offset=0x21),
291
         Mapping('[stack]', start=0xfff3e000, stop=0xfff61000, size=0x23000, flags=0x6, page_offset=0x0)]
292

293
    Examples:
294

295
        Let's build an example binary which should eat ``R0=0xdeadbeef``
296
        and ``PC=0xcafebabe``.
297

298
        If we run the binary and then wait for it to exit, we can get its
299
        core file.
300

301
        >>> context.clear(arch='arm')
302
        >>> shellcode = shellcraft.mov('r0', 0xdeadbeef)
303
        >>> shellcode += shellcraft.mov('r1', 0xcafebabe)
304
        >>> shellcode += 'bx r1'
305
        >>> address = 0x41410000
306
        >>> elf = ELF.from_assembly(shellcode, vma=address)
307
        >>> io = elf.process(env={'HELLO': 'WORLD'})
308
        >>> io.poll(block=True)
309
        -11
310

311
        You can specify a full path a la ``Corefile('/path/to/core')``,
312
        but you can also just access the :attr:`.process.corefile` attribute.
313

314
        There's a lot of behind-the-scenes logic to locate the corefile for
315
        a given process, but it's all handled transparently by Pwntools.
316

317
        >>> core = io.corefile
318

319
        The core file has a :attr:`exe` property, which is a :class:`.Mapping`
320
        object.  Each mapping can be accessed with virtual addresses via subscript, or
321
        contents can be examined via the :attr:`.Mapping.data` attribute.
322

323
        >>> core.exe # doctest: +ELLIPSIS
324
        Mapping('/.../step3', start=..., stop=..., size=0x1000, flags=0x..., page_offset=...)
325
        >>> hex(core.exe.address)
326
        '0x41410000'
327

328
        The core file also has registers which can be accessed direclty.
329
        Pseudo-registers :attr:`pc` and :attr:`sp` are available on all architectures,
330
        to make writing architecture-agnostic code more simple.
331
        If this were an amd64 corefile, we could access e.g. ``core.rax``.
332

333
        >>> core.pc == 0xcafebabe
334
        True
335
        >>> core.r0 == 0xdeadbeef
336
        True
337
        >>> core.sp == core.r13
338
        True
339

340
        We may not always know which signal caused the core dump, or what address
341
        caused a segmentation fault.  Instead of accessing registers directly, we
342
        can also extract this information from the core dump via :attr:`fault_addr`
343
        and :attr:`signal`.
344

345
        On QEMU-generated core dumps, this information is unavailable, so we
346
        substitute the value of PC.  In our example, that's correct anyway.
347

348
        >>> core.fault_addr == 0xcafebabe
349
        True
350
        >>> core.signal
351
        11
352

353
        Core files can also be generated from running processes.
354
        This requires GDB to be installed, and can only be done with native processes.
355
        Getting a "complete" corefile requires GDB 7.11 or better.
356

357
        >>> elf = ELF(which('bash-static'))
358
        >>> context.clear(binary=elf)
359
        >>> env = dict(os.environ)
360
        >>> env['HELLO'] = 'WORLD'
361
        >>> io = process(elf.path, env=env)
362
        >>> io.sendline(b'echo hello')
363
        >>> io.recvline()
364
        b'hello\n'
365

366
        The process is still running, but accessing its :attr:`.process.corefile` property
367
        automatically invokes GDB to attach and dump a corefile.
368

369
        >>> core = io.corefile
370
        >>> io.close()
371

372
        The corefile can be inspected and read from, and even exposes various mappings
373

374
        >>> core.exe # doctest: +ELLIPSIS
375
        Mapping('.../bin/bash-static', start=..., stop=..., size=..., flags=..., page_offset=...)
376
        >>> core.exe.data[0:4]
377
        b'\x7fELF'
378

379
        It also supports all of the features of :class:`ELF`, so you can :meth:`.read`
380
        or :meth:`.write` or even the helpers like :meth:`.pack` or :meth:`.unpack`.
381

382
        Don't forget to call :meth:`.ELF.save` to save the changes to disk.
383

384
        >>> core.read(elf.address, 4)
385
        b'\x7fELF'
386
        >>> core.pack(core.sp, 0xdeadbeef)
387
        >>> core.save()
388

389
        Let's re-load it as a new :attr:`Corefile` object and have a look!
390

391
        >>> core2 = Corefile(core.path)
392
        >>> hex(core2.unpack(core2.sp))
393
        '0xdeadbeef'
394

395
        Various other mappings are available by name, for the first segment of:
396

397
        * :attr:`.exe` the executable
398
        * :attr:`.libc` the loaded libc, if any
399
        * :attr:`.stack` the stack mapping
400
        * :attr:`.vvar`
401
        * :attr:`.vdso`
402
        * :attr:`.vsyscall`
403

404
        On Linux, 32-bit Intel binaries should have a VDSO section via :attr:`vdso`.  
405
        Since our ELF is statically linked, there is no libc which gets mapped.
406

407
        >>> core.vdso.data[:4]
408
        b'\x7fELF'
409
        >>> core.libc
410

411
        But if we dump a corefile from a dynamically-linked binary, the :attr:`.libc`
412
        will be loaded.
413

414
        >>> process('bash').corefile.libc # doctest: +ELLIPSIS
415
        Mapping('.../libc...so...', start=0x..., stop=0x..., size=0x..., flags=..., page_offset=...)
416

417
        The corefile also contains a :attr:`.stack` property, which gives
418
        us direct access to the stack contents.  On Linux, the very top of the stack
419
        should contain two pointer-widths of NULL bytes, preceded by the NULL-
420
        terminated path to the executable (as passed via the first arg to ``execve``).
421

422
        >>> core.stack # doctest: +ELLIPSIS
423
        Mapping('[stack]', start=0x..., stop=0x..., size=0x..., flags=0x6, page_offset=0x0)
424

425
        When creating a process, the kernel puts the absolute path of the binary and some
426
        padding bytes at the end of the stack.  We can look at those by looking at 
427
        ``core.stack.data``.
428

429
        >>> size = len('/bin/bash-static') + 8
430
        >>> core.stack.data[-size:]
431
        b'bin/bash-static\x00\x00\x00\x00\x00\x00\x00\x00\x00'
432

433
        We can also directly access the environment variables and arguments, via
434
        :attr:`.argc`, :attr:`.argv`, and :attr:`.env`.
435

436
        >>> 'HELLO' in core.env
437
        True
438
        >>> core.string(core.env['HELLO'])
439
        b'WORLD'
440
        >>> core.getenv('HELLO')
441
        b'WORLD'
442
        >>> core.argc
443
        1
444
        >>> core.argv[0] in core.stack
445
        True
446
        >>> core.string(core.argv[0]) # doctest: +ELLIPSIS
447
        b'.../bin/bash-static'
448

449
        Corefiles can also be pulled from remote machines via SSH!
450

451
        >>> s = ssh(user='travis', host='example.pwnme', password='demopass')
452
        >>> _ = s.set_working_directory()
453
        >>> elf = ELF.from_assembly(shellcraft.trap())
454
        >>> path = s.upload(elf.path)
455
        >>> _ =s.chmod('+x', path)
456
        >>> io = s.process(path)
457
        >>> io.wait(1)
458
        -1
459
        >>> io.corefile.signal == signal.SIGTRAP # doctest: +SKIP
460
        True
461

462
        Make sure fault_addr synthesis works for amd64 on ret.
463

464
        >>> context.clear(arch='amd64')
465
        >>> elf = ELF.from_assembly('push 1234; ret')
466
        >>> io = elf.process()
467
        >>> io.wait(1)
468
        >>> io.corefile.fault_addr
469
        1234
470

471
        Corefile.getenv() works correctly, even if the environment variable's
472
        value contains embedded '='. Corefile is able to find the stack, even
473
        if the stack pointer doesn't point at the stack.
474

475
        >>> elf = ELF.from_assembly(shellcraft.crash())
476
        >>> io = elf.process(env={'FOO': 'BAR=BAZ'})
477
        >>> io.wait(1)
478
        >>> core = io.corefile
479
        >>> core.getenv('FOO')
480
        b'BAR=BAZ'
481
        >>> core.sp == 0
482
        True
483
        >>> core.sp in core.stack
484
        False
485

486
        Corefile gracefully handles the stack being filled with garbage, including
487
        argc / argv / envp being overwritten.
488

489
        >>> context.clear(arch='i386')
490
        >>> assembly = '''
491
        ... LOOP:
492
        ...   mov dword ptr [esp], 0x41414141
493
        ...   pop eax
494
        ...   jmp LOOP
495
        ... '''
496
        >>> elf = ELF.from_assembly(assembly)
497
        >>> io = elf.process()
498
        >>> io.wait(2)
499
        >>> core = io.corefile
500
        >>> core.argc, core.argv, core.env
501
        (0, [], {})
502
        >>> core.stack.data.endswith(b'AAAA')
503
        True
504
        >>> core.fault_addr == core.sp
505
        True
506
    """
507

508
    _fill_gaps = False
1✔
509

510
    def __init__(self, *a, **kw):
1✔
511
        #: The NT_PRSTATUS object.
512
        self.prstatus = None
1✔
513

514
        #: The NT_PRPSINFO object
515
        self.prpsinfo = None
1✔
516

517
        #: The NT_SIGINFO object
518
        self.siginfo = None
1✔
519

520
        #: :class:`list`: A list of :class:`.Mapping` objects for each loaded memory region
521
        self.mappings = []
1✔
522

523
        #: :class:`int`: A :class:`Mapping` corresponding to the stack
524
        self.stack    = None
1✔
525

526
        """
1✔
527
        Environment variables read from the stack.
528
        Keys are the environment variable name, values are the memory 
529
        address of the variable.
530
        
531
        Use :meth:`.getenv` or :meth:`.string` to retrieve the textual value.
532
        
533
        Note: If ``FOO=BAR`` is in the environment, ``self.env['FOO']`` is the address of the string ``"BAR\x00"``.
534
        """
535
        self.env = {}
1✔
536

537
        #: :class:`int`: Pointer to envp on the stack
538
        self.envp_address = 0
1✔
539

540
        #: :class:`list`: List of addresses of arguments on the stack.
541
        self.argv = []
1✔
542

543
        #: :class:`int`: Pointer to argv on the stack
544
        self.argv_address = 0
1✔
545

546
        #: :class:`int`: Number of arguments passed
547
        self.argc = 0
1✔
548

549
        #: :class:`int`: Pointer to argc on the stack
550
        self.argc_address = 0
1✔
551

552
        # Pointer to the executable filename on the stack
553
        self.at_execfn = 0
1✔
554

555
        # Pointer to the entry point
556
        self.at_entry = 0
1✔
557

558
        try:
1✔
559
            super(Corefile, self).__init__(*a, **kw)
1✔
560
        except IOError:
×
561
            log.warning("No corefile.  Have you set /proc/sys/kernel/core_pattern?")
×
562
            raise
×
563

564
        self.load_addr = 0
1✔
565
        self._address  = 0
1✔
566

567
        if self.elftype != 'CORE':
1!
568
            log.error("%s is not a valid corefile" % self.file.name)
×
569

570
        if self.arch not in prstatus_types:
1!
571
            log.warn_once("%s does not use a supported corefile architecture, registers are unavailable" % self.file.name)
×
572

573
        prstatus_type = prstatus_types.get(self.arch)
1✔
574
        siginfo_type = siginfo_types.get(self.bits)
1✔
575

576
        with log.waitfor("Parsing corefile...") as w:
1✔
577
            self._load_mappings()
1✔
578

579
            for segment in self.segments:
1✔
580
                if not isinstance(segment, elftools.elf.segments.NoteSegment):
1✔
581
                    continue
1✔
582

583

584
                for note in segment.iter_notes():
1✔
585
                    # Try to find NT_PRSTATUS.
586
                    if note.n_type == 'NT_PRSTATUS':
1✔
587
                        self.NT_PRSTATUS = note
1✔
588
                        self.prstatus = prstatus_type.from_buffer_copy(note.n_desc)
1✔
589

590
                    # Try to find NT_PRPSINFO
591
                    if note.n_type == 'NT_PRPSINFO':
1✔
592
                        self.NT_PRPSINFO = note
1✔
593
                        self.prpsinfo = note.n_desc
1✔
594

595
                    # Try to find NT_SIGINFO so we can see the fault
596
                    if note.n_type == 'NT_SIGINFO':
1✔
597
                        self.NT_SIGINFO = note
1✔
598
                        self.siginfo = siginfo_type.from_buffer_copy(note.n_desc)
1✔
599

600
                    # Try to find the list of mapped files
601
                    if note.n_type == 'NT_FILE':
1✔
602
                        with context.local(bytes=self.bytes):
1✔
603
                            self._parse_nt_file(note)
1✔
604

605
                    # Try to find the auxiliary vector, which will tell us
606
                    # where the top of the stack is.
607
                    if note.n_type == 'NT_AUXV':
1✔
608
                        self.NT_AUXV = note
1✔
609
                        with context.local(bytes=self.bytes):
1✔
610
                            self._parse_auxv(note)
1✔
611

612
            if not self.stack and self.mappings:
1!
613
                self.stack = self.mappings[-1].stop
×
614

615
            if self.stack and self.mappings:
1!
616
                for mapping in self.mappings:
1!
617
                    if self.stack in mapping or self.stack == mapping.stop:
1✔
618
                        mapping.name = '[stack]'
1✔
619
                        self.stack   = mapping
1✔
620
                        break
1✔
621
                else:
622
                    log.warn('Could not find the stack!')
×
623
                    self.stack = None
×
624

625
            with context.local(bytes=self.bytes):
1✔
626
                try:
1✔
627
                    self._parse_stack()
1✔
UNCOV
628
                except ValueError:
×
629
                    # If there are no environment variables, we die by running
630
                    # off the end of the stack.
UNCOV
631
                    pass
×
632

633
            # Corefiles generated by QEMU do not have a name for the 
634
            # main module mapping.
635
            # Fetching self.exe will cause this to be auto-populated,
636
            # and is a no-op in other cases.
637
            self.exe
1✔
638

639
            # Print out the nice display for the user
640
            self._describe_core()
1✔
641

642
    def _parse_nt_file(self, note):
1✔
643
        starts = []
1✔
644
        addresses = {}
1✔
645

646
        for vma, filename in zip(note.n_desc.Elf_Nt_File_Entry, note.n_desc.filename):
1✔
647
            if not isinstance(filename, str):
1✔
648
                filename = filename.decode('utf-8', 'surrogateescape')
1✔
649
            for mapping in self.mappings:
1✔
650
                if mapping.start == vma.vm_start:
1✔
651
                    mapping.name = filename
1✔
652
                    mapping.page_offset = vma.page_offset
1✔
653

654
        self.mappings = sorted(self.mappings, key=lambda m: m.start)
1✔
655

656
        vvar = vdso = vsyscall = False
1✔
657
        for mapping in reversed(self.mappings):
1✔
658
            if mapping.name:
1✔
659
                continue
1✔
660

661
            if not vsyscall and mapping.start == 0xffffffffff600000:
1✔
662
                mapping.name = '[vsyscall]'
1✔
663
                vsyscall = True
1✔
664
                continue
1✔
665

666
            if mapping.start == self.at_sysinfo_ehdr \
1✔
667
            or (not vdso and mapping.size in [0x1000, 0x2000]
668
                and mapping.flags == 5
669
                and self.read(mapping.start, 4) == b'\x7fELF'):
670
                mapping.name = '[vdso]'
1✔
671
                vdso = True
1✔
672
                continue
1✔
673

674
            if not vvar and mapping.size == 0x2000 and mapping.flags == 4:
1!
675
                mapping.name = '[vvar]'
×
676
                vvar = True
×
677
                continue
×
678

679
    @property
1✔
680
    def vvar(self):
1✔
681
        """:class:`Mapping`: Mapping for the vvar section"""
682
        for m in self.mappings:
×
683
            if m.name == '[vvar]':
×
684
                return m
×
685

686
    @property
1✔
687
    def vdso(self):
1✔
688
        """:class:`Mapping`: Mapping for the vdso section"""
689
        for m in self.mappings:
1!
690
            if m.name == '[vdso]':
1✔
691
                return m
1✔
692

693
    @property
1✔
694
    def vsyscall(self):
1✔
695
        """:class:`Mapping`: Mapping for the vsyscall section"""
696
        for m in self.mappings:
×
697
            if m.name == '[vsyscall]':
×
698
                return m
×
699

700
    @property
1✔
701
    def libc(self):
1✔
702
        """:class:`Mapping`: First mapping for ``libc.so``"""
703
        expr = r'^libc\b.*so(?:\.6)?$'
1✔
704

705
        for m in self.mappings:
1✔
706
            if not m.name:
1✔
707
                continue
1✔
708

709
            basename = os.path.basename(m.name)
1✔
710

711
            if re.match(expr, basename):
1✔
712
                return m
1✔
713

714
    @property
1✔
715
    def exe(self):
1✔
716
        """:class:`Mapping`: First mapping for the executable file."""
717

718
        # Finding the executable mapping requires knowing the entry point
719
        # from the auxv
720
        if not self.at_entry:
1!
721
            return None
×
722

723
        # The entry point may not be in the first segment of a given file,
724
        # but we want to find the first segment of the file -- not the segment that 
725
        # contains the entrypoint.
726
        first_segment_for_name = {}
1✔
727

728
        for m in self.mappings:
1✔
729
            first_segment_for_name.setdefault(m.name, m)
1✔
730

731
        # Find which segment conains the entry point
732
        for m in self.mappings:
1!
733
            if m.start <= self.at_entry < m.stop:
1✔
734

735
                if not m.name and self.at_execfn:
1✔
736
                    m.name = self.string(self.at_execfn)
1✔
737
                    if not isinstance(m.name, str):
1✔
738
                        m.name = m.name.decode('utf-8')
1✔
739

740
                return first_segment_for_name.get(m.name, m)
1✔
741

742
    @property
1✔
743
    def pid(self):
1✔
744
        """:class:`int`: PID of the process which created the core dump."""
745
        if self.prstatus:
1!
746
            return int(self.prstatus.pr_pid)
1✔
747

748
    @property
1✔
749
    def ppid(self):
1✔
750
        """:class:`int`: Parent PID of the process which created the core dump."""
751
        if self.prstatus:
×
752
            return int(self.prstatus.pr_ppid)
×
753

754
    @property
1✔
755
    def signal(self):
1✔
756
        """:class:`int`: Signal which caused the core to be dumped.
757

758
        Example:
759

760
            >>> elf = ELF.from_assembly(shellcraft.trap())
761
            >>> io = elf.process()
762
            >>> io.wait(1)
763
            >>> io.corefile.signal == signal.SIGTRAP
764
            True
765

766
            >>> elf = ELF.from_assembly(shellcraft.crash())
767
            >>> io = elf.process()
768
            >>> io.wait(1)
769
            >>> io.corefile.signal == signal.SIGSEGV
770
            True
771
        """
772
        if self.siginfo:
1✔
773
            return int(self.siginfo.si_signo)
1✔
774
        if self.prstatus:
1!
775
            return int(self.prstatus.pr_cursig)
1✔
776

777
    @property
1✔
778
    def fault_addr(self):
1✔
779
        """:class:`int`: Address which generated the fault, for the signals
780
            SIGILL, SIGFPE, SIGSEGV, SIGBUS.  This is only available in native
781
            core dumps created by the kernel.  If the information is unavailable,
782
            this returns the address of the instruction pointer.
783

784

785
        Example:
786

787
            >>> elf = ELF.from_assembly('mov eax, 0xdeadbeef; jmp eax', arch='i386')
788
            >>> io = elf.process()
789
            >>> io.wait(1)
790
            >>> io.corefile.fault_addr == io.corefile.eax == 0xdeadbeef
791
            True
792
        """
793
        if not self.siginfo:
1✔
794
            return getattr(self, 'pc', 0)
1✔
795

796
        fault_addr = int(self.siginfo.sigfault_addr)
1✔
797

798
        # The fault_addr is zero if the crash occurs due to a
799
        # "protection fault", e.g. a dereference of 0x4141414141414141
800
        # because this is technically a kernel address.
801
        #
802
        # A protection fault does not set "fault_addr" in the siginfo.
803
        # (http://elixir.free-electrons.com/linux/v4.14-rc8/source/kernel/signal.c#L1052)
804
        #
805
        # Since a common use for corefiles is to spray the stack with a
806
        # cyclic pattern to find the offset to get control of $PC,
807
        # check for a "ret" instruction ("\xc3").
808
        #
809
        # If we find a RET at $PC, extract the "return address" from the
810
        # top of the stack.
811
        if fault_addr == 0 and self.siginfo.si_code == 0x80:
1✔
812
            try:
1✔
813
                code = self.read(self.pc, 1)
1✔
814
                RET = b'\xc3'
1✔
815
                if code == RET:
1!
816
                    fault_addr = self.unpack(self.sp)
×
817
            except Exception:
×
818
                # Could not read $rsp or $rip
819
                pass
×
820

821
        return fault_addr
1✔
822

823
        # No embedded siginfo structure, so just return the
824
        # current instruction pointer.
825

826
    @property
1✔
827
    def _pc_register(self):
1✔
828
        name = {
1✔
829
            'i386': 'eip',
830
            'amd64': 'rip',
831
        }.get(self.arch, 'pc')
832
        return name
1✔
833

834
    @property
1✔
835
    def pc(self):
1✔
836
        """:class:`int`: The program counter for the Corefile
837

838
        This is a cross-platform way to get e.g. ``core.eip``, ``core.rip``, etc.
839
        """
840
        return self.registers.get(self._pc_register, None)
1✔
841

842
    @property
1✔
843
    def _sp_register(self):
1✔
844
        name = {
1✔
845
            'i386': 'esp',
846
            'amd64': 'rsp',
847
        }.get(self.arch, 'sp')
848
        return name
1✔
849

850
    @property
1✔
851
    def sp(self):
1✔
852
        """:class:`int`: The stack pointer for the Corefile
853

854
        This is a cross-platform way to get e.g. ``core.esp``, ``core.rsp``, etc.
855
        """
856
        return self.registers.get(self._sp_register, None)
1✔
857

858
    def _describe(self):
1✔
859
        pass
1✔
860

861
    def _describe_core(self):
1✔
862
        gnu_triplet = '-'.join(map(str, (self.arch, self.bits, self.endian)))
1✔
863

864
        fields = [
1✔
865
            repr(self.path),
866
            '%-10s %s' % ('Arch:', gnu_triplet),
867
            '%-10s %#x' % ('%s:' % self._pc_register.upper(), self.pc or 0),
868
            '%-10s %#x' % ('%s:' % self._sp_register.upper(), self.sp or 0),
869
        ]
870

871
        if self.exe and self.exe.name:
1!
872
            fields += [
1✔
873
                '%-10s %s' % ('Exe:', '%r (%#x)' % (self.exe.name, self.exe.address))
874
            ]
875

876
        if self.fault_addr:
1✔
877
            fields += [
1✔
878
                '%-10s %#x' % ('Fault:', self.fault_addr)
879
            ]
880

881
        log.info_once('\n'.join(fields))
1✔
882

883
    def _load_mappings(self):
1✔
884
        for s in self.segments:
1✔
885
            if s.header.p_type != 'PT_LOAD':
1✔
886
                continue
1✔
887

888
            mapping = Mapping(self,
1✔
889
                              None,
890
                              s.header.p_vaddr,
891
                              s.header.p_vaddr + s.header.p_memsz,
892
                              s.header.p_flags,
893
                              None)
894
            self.mappings.append(mapping)
1✔
895

896
    def _parse_auxv(self, note):
1✔
897
        t = tube()
1✔
898
        t.unrecv(note.n_desc)
1✔
899

900
        for i in range(0, note.n_descsz, context.bytes * 2):
1✔
901
            key = t.unpack()
1✔
902
            value = t.unpack()
1✔
903

904
            # The AT_EXECFN entry is a pointer to the executable's filename
905
            # at the very top of the stack, followed by a word's with of
906
            # NULL bytes.  For example, on a 64-bit system...
907
            #
908
            # 0x7fffffffefe8  53 3d 31 34  33 00 2f 62  69 6e 2f 62  61 73 68 00  |S=14|3./b|in/b|ash.|
909
            # 0x7fffffffeff8  00 00 00 00  00 00 00 00                            |....|....|    |    |
910

911
            if key == constants.AT_EXECFN:
1✔
912
                self.at_execfn = value
1✔
913
                value = value & ~0xfff
1✔
914
                value += 0x1000
1✔
915
                self.stack = value
1✔
916

917
            if key == constants.AT_ENTRY:
1✔
918
                self.at_entry = value
1✔
919

920
            if key == constants.AT_PHDR:
1✔
921
                self.at_phdr = value
1✔
922

923
            if key == constants.AT_BASE:
1✔
924
                self.at_base = value
1✔
925

926
            if key == constants.AT_SYSINFO_EHDR:
1✔
927
                self.at_sysinfo_ehdr = value
1✔
928

929
    def _parse_stack(self):
1✔
930
        # Get a copy of the stack mapping
931
        stack = self.stack
1✔
932

933
        if not stack:
1!
934
            return
×
935

936
        # If the stack does not end with zeroes, something is very wrong.
937
        if not stack.data.endswith(b'\x00' * context.bytes):
1✔
938
            log.warn_once("End of the stack is corrupted, skipping stack parsing (got: %s)",
1✔
939
                          enhex(self.data[-context.bytes:]))
940
            return
1✔
941

942
        # AT_EXECFN is the start of the filename, e.g. '/bin/sh'
943
        # Immediately preceding is a NULL-terminated environment variable string.
944
        # We want to find the beginning of it
945
        if not self.at_execfn:
1!
946
            address = stack.stop
×
947
            address -= 2*self.bytes
×
948
            address -= 1
×
949
            address = stack.rfind(b'\x00', None, address)
×
950
            address += 1
×
951
            self.at_execfn = address
×
952

953
        address = self.at_execfn-1
1✔
954

955

956
        # Sanity check!
957
        try:
1✔
958
            if stack[address] != b'\x00':
1!
959
                log.warning("Error parsing corefile stack: Could not find end of environment")
×
960
                return
×
961
        except ValueError:
×
962
            log.warning("Error parsing corefile stack: Address out of bounds")
×
963
            return
×
964

965
        # address is currently set to the NULL terminator of the last
966
        # environment variable.
967
        address = stack.rfind(b'\x00', None, address)
1✔
968

969
        # We've found the beginning of the last environment variable.
970
        # We should be able to search up the stack for the envp[] array to
971
        # find a pointer to this address, followed by a NULL.
972
        last_env_addr = address + 1
1✔
973
        p_last_env_addr = stack.find(pack(last_env_addr), None, last_env_addr)
1✔
974
        if p_last_env_addr < 0:
1!
975
            # Something weird is happening.  Just don't touch it.
976
            log.warn_once("Error parsing corefile stack: Found bad environment at %#x", last_env_addr)
×
977
            return
×
978

979
        # Sanity check that we did correctly find the envp NULL terminator.
980
        envp_nullterm = p_last_env_addr+context.bytes
1✔
981
        if self.unpack(envp_nullterm) != 0:
1!
982
            log.warning("Error parsing corefile stack: Could not find end of environment variables")
×
983
            return
×
984

985
        # We've successfully located the end of the envp[] array.
986
        #
987
        # It comes immediately after the argv[] array, which itself
988
        # is NULL-terminated.
989
        #
990
        # Now let's find the end of argv
991
        p_end_of_argv = stack.rfind(pack(0), None, p_last_env_addr)
1✔
992

993
        self.envp_address = p_end_of_argv + self.bytes
1✔
994

995
        # Now we can fill in the environment
996
        env_pointer_data = stack[self.envp_address:p_last_env_addr+self.bytes]
1✔
997
        for pointer in unpack_many(env_pointer_data):
1✔
998

999
            # If the stack is corrupted, the pointer will be outside of
1000
            # the stack.
1001
            if pointer not in stack:
1!
1002
                continue
×
1003

1004
            try:
1✔
1005
                name_value = self.string(pointer)
1✔
1006
            except Exception:
×
1007
                continue
×
1008

1009
            name, _ = name_value.split(b'=', 1)
1✔
1010

1011
            # "end" points at the byte after the null terminator
1012
            end = pointer + len(name_value) + 1
1✔
1013

1014
            # Do not mark things as environment variables if they point
1015
            # outside of the stack itself, or we had to cross into a different
1016
            # mapping (after the stack) to read it.
1017
            # This may occur when the entire stack is filled with non-NUL bytes,
1018
            # and we NULL-terminate on a read failure in .string().
1019
            if end not in stack:
1!
1020
                continue
×
1021

1022
            if not isinstance(name, str):
1✔
1023
                name = name.decode('utf-8', 'surrogateescape')
1✔
1024
            self.env[name] = pointer + len(name) + len('=')
1✔
1025

1026
        # May as well grab the arguments off the stack as well.
1027
        # argc comes immediately before argv[0] on the stack, but
1028
        # we don't know what argc is.
1029
        #
1030
        # It is unlikely that argc is a valid stack address.
1031
        address = p_end_of_argv - self.bytes
1✔
1032
        while self.unpack(address) in stack:
1✔
1033
            address -= self.bytes
1✔
1034

1035
        # address now points at argc
1036
        self.argc_address = address
1✔
1037
        self.argc = self.unpack(self.argc_address)
1✔
1038

1039
        # we can extract all of the arguments as well
1040
        self.argv_address = self.argc_address + self.bytes
1✔
1041
        self.argv = unpack_many(stack[self.argv_address: p_end_of_argv])
1✔
1042

1043
    @property
1✔
1044
    def maps(self):
1✔
1045
        """:class:`str`: A printable string which is similar to /proc/xx/maps.
1046

1047
        ::
1048

1049
            >>> print(Corefile('./core').maps)
1050
            8048000-8049000 r-xp 1000 /home/user/pwntools/crash
1051
            8049000-804a000 r--p 1000 /home/user/pwntools/crash
1052
            804a000-804b000 rw-p 1000 /home/user/pwntools/crash
1053
            f7528000-f7529000 rw-p 1000 None
1054
            f7529000-f76d1000 r-xp 1a8000 /lib/i386-linux-gnu/libc-2.19.so
1055
            f76d1000-f76d2000 ---p 1000 /lib/i386-linux-gnu/libc-2.19.so
1056
            f76d2000-f76d4000 r--p 2000 /lib/i386-linux-gnu/libc-2.19.so
1057
            f76d4000-f76d5000 rw-p 1000 /lib/i386-linux-gnu/libc-2.19.so
1058
            f76d5000-f76d8000 rw-p 3000 None
1059
            f76ef000-f76f1000 rw-p 2000 None
1060
            f76f1000-f76f2000 r-xp 1000 [vdso]
1061
            f76f2000-f7712000 r-xp 20000 /lib/i386-linux-gnu/ld-2.19.so
1062
            f7712000-f7713000 r--p 1000 /lib/i386-linux-gnu/ld-2.19.so
1063
            f7713000-f7714000 rw-p 1000 /lib/i386-linux-gnu/ld-2.19.so
1064
            fff3e000-fff61000 rw-p 23000 [stack]
1065
        """
1066
        return '\n'.join(map(str, self.mappings))
×
1067

1068
    def getenv(self, name):
1✔
1069
        """getenv(name) -> int
1070

1071
        Read an environment variable off the stack, and return its contents.
1072

1073
        Arguments:
1074
            name(str): Name of the environment variable to read.
1075

1076
        Returns:
1077
            :class:`str`: The contents of the environment variable.
1078

1079
        Example:
1080

1081
            >>> elf = ELF.from_assembly(shellcraft.trap())
1082
            >>> io = elf.process(env={'GREETING': 'Hello!'})
1083
            >>> io.wait(1)
1084
            >>> io.corefile.getenv('GREETING')
1085
            b'Hello!'
1086
        """
1087
        if not isinstance(name, str):
1!
1088
            name = name.decode('utf-8', 'surrogateescape')
×
1089
        if name not in self.env:
1!
1090
            log.error("Environment variable %r not set" % name)
×
1091

1092
        return self.string(self.env[name])
1✔
1093

1094
    @property
1✔
1095
    def registers(self):
1✔
1096
        """:class:`dict`: All available registers in the coredump.
1097

1098
        Example:
1099

1100
            >>> elf = ELF.from_assembly('mov eax, 0xdeadbeef;' + shellcraft.trap(), arch='i386')
1101
            >>> io = elf.process()
1102
            >>> io.wait(1)
1103
            >>> io.corefile.registers['eax'] == 0xdeadbeef
1104
            True
1105
        """
1106
        if not self.prstatus:
1!
1107
            return {}
×
1108

1109
        rv = {}
1✔
1110

1111
        for k in dir(self.prstatus.pr_reg):
1✔
1112
            if k.startswith('_'):
1✔
1113
                continue
1✔
1114

1115
            try:
1✔
1116
                rv[k] = int(getattr(self.prstatus.pr_reg, k))
1✔
1117
            except Exception:
1✔
1118
                pass
1✔
1119

1120
        return rv
1✔
1121

1122
    def debug(self):
1✔
1123
        """Open the corefile under a debugger."""
1124
        import pwnlib.gdb
×
1125
        pwnlib.gdb.attach(self, exe=self.exe.path)
×
1126

1127
    def __getattr__(self, attribute):
1✔
1128
        if attribute.startswith('_') or not self.prstatus:
1!
1129
            raise AttributeError(attribute)
×
1130

1131
        if hasattr(self.prstatus, attribute):
1!
1132
            return getattr(self.prstatus, attribute)
×
1133

1134
        return getattr(self.prstatus.pr_reg, attribute)
1✔
1135

1136
    # Override routines which don't make sense for Corefiles
1137
    def _populate_got(*a): pass
1✔
1138
    def _populate_plt(*a): pass
1✔
1139

1140
class Core(Corefile):
1✔
1141
    """Alias for :class:`.Corefile`"""
1142

1143
class Coredump(Corefile):
1✔
1144
    """Alias for :class:`.Corefile`"""
1145

1146
class CorefileFinder(object):
1✔
1147
    def __init__(self, proc):
1✔
1148
        if proc.poll() is None:
1!
1149
            log.error("Process %i has not exited" % (proc.pid))
×
1150

1151
        self.process = proc
1✔
1152
        self.pid = proc.pid
1✔
1153
        self.uid = proc.suid
1✔
1154
        self.gid = proc.sgid
1✔
1155
        self.exe = proc.executable
1✔
1156
        self.basename = os.path.basename(self.exe)
1✔
1157
        self.cwd = proc.cwd
1✔
1158

1159
        # XXX: Should probably break out all of this logic into
1160
        #      its own class, so that we can support "file ops"
1161
        #      locally, via SSH, and over ADB, in a transparent way.
1162
        if isinstance(proc, process):
1!
1163
            self.read = read
1✔
1164
            self.unlink = os.unlink
1✔
1165
        elif isinstance(proc, ssh_channel):
×
1166
            self.read = proc.parent.read
×
1167
            self.unlink = proc.parent.unlink
×
1168

1169
        self.kernel_core_pattern = self.read('/proc/sys/kernel/core_pattern').strip()
1✔
1170
        self.kernel_core_uses_pid = bool(int(self.read('/proc/sys/kernel/core_uses_pid')))
1✔
1171

1172
        log.debug("core_pattern: %r" % self.kernel_core_pattern)
1✔
1173
        log.debug("core_uses_pid: %r" % self.kernel_core_uses_pid)
1✔
1174

1175
        self.interpreter = self.binfmt_lookup()
1✔
1176

1177
        log.debug("interpreter: %r" % self.interpreter)
1✔
1178

1179
        # If we have already located the corefile, we will
1180
        # have renamed it to 'core.<pid>'
1181
        core_path = 'core.%i' % (proc.pid)
1✔
1182
        self.core_path = None
1✔
1183

1184
        if os.path.isfile(core_path):
1✔
1185
            log.debug("Found core immediately: %r" % core_path)
1✔
1186
            self.core_path = core_path
1✔
1187

1188
        # Try QEMU first, since it's unlikely to be a false-positive unless
1189
        # there is a PID *and* filename collision.
1190
        if not self.core_path:
1✔
1191
            log.debug("Looking for QEMU corefile")
1✔
1192
            self.core_path = self.qemu_corefile()
1✔
1193

1194
        # Check for native coredumps as a last resort
1195
        if not self.core_path:
1✔
1196
            log.debug("Looking for native corefile")
1✔
1197
            self.core_path = self.native_corefile()
1✔
1198

1199
        if not self.core_path:
1!
1200
            return
×
1201

1202
        core_pid = self.load_core_check_pid()
1✔
1203

1204
        # Move the corefile if we're configured that way
1205
        if context.rename_corefiles:
1!
1206
            new_path = 'core.%i' % core_pid
1✔
1207
            if core_pid > 0 and new_path != self.core_path:
1✔
1208
                write(new_path, self.read(self.core_path))
1✔
1209
                try:
1✔
1210
                    self.unlink(self.core_path)
1✔
1211
                except (IOError, OSError):
1✔
1212
                    log.warn("Could not delete %r" % self.core_path)
1✔
1213
                self.core_path = new_path
1✔
1214

1215
        # Check the PID
1216
        if core_pid != self.pid:
1!
1217
            log.warn("Corefile PID does not match! (got %i)" % core_pid)
×
1218

1219
        # Register the corefile for removal only if it's an exact match
1220
        elif context.delete_corefiles:
1!
1221
            atexit.register(lambda: os.unlink(self.core_path))
×
1222

1223

1224
    def load_core_check_pid(self):
1✔
1225
        """Test whether a Corefile matches our process
1226

1227
        Speculatively load a Corefile without informing the user, so that we
1228
        can check if it matches the process we're looking for.
1229

1230
        Arguments:
1231
            path(str): Path to the corefile on disk
1232

1233
        Returns:
1234
            `bool`: ``True`` if the Corefile matches, ``False`` otherwise.
1235
        """
1236

1237
        try:
1✔
1238
            with context.quiet:
1✔
1239
                with tempfile.NamedTemporaryFile() as tmp:
1✔
1240
                    tmp.write(self.read(self.core_path))
1✔
1241
                    tmp.flush()
1✔
1242
                    return Corefile(tmp.name).pid
1✔
1243
        except Exception:
×
1244
            pass
×
1245

1246
        return -1
×
1247

1248
    def apport_corefile(self):
1✔
1249
        """Find the apport crash for the process, and extract the core file.
1250

1251
        Arguments:
1252
            process(process): Process object we're looking for.
1253

1254
        Returns:
1255
            `str`: Raw core file contents
1256
        """
1257
        crash_data = self.apport_read_crash_data()
1✔
1258

1259
        log.debug("Apport Crash Data:\n%s" % crash_data)
1✔
1260

1261
        if crash_data:
1!
1262
            return self.apport_crash_extract_corefile(crash_data)
×
1263

1264
    def apport_crash_extract_corefile(self, crashfile_data):
1✔
1265
        """Extract a corefile from an apport crash file contents.
1266

1267
        Arguments:
1268
            crashfile_data(str): Crash file contents
1269

1270
        Returns:
1271
            `str`: Raw binary data for the core file, or ``None``.
1272
        """
1273
        file = StringIO(crashfile_data)
×
1274

1275
        # Find the pid of the crashfile
1276
        for line in file:
×
1277
            if line.startswith(' Pid:'):
×
1278
                pid = int(line.split()[-1])
×
1279

1280
                if pid == self.pid:
×
1281
                    break
×
1282
        else:
1283
            # Could not find a " Pid:" line
1284
            return
×
1285

1286
        # Find the CoreDump section
1287
        for line in file:
×
1288
            if line.startswith('CoreDump: base64'):
×
1289
                break
×
1290
        else:
1291
            # Could not find the coredump data
1292
            return
×
1293

1294
        # Get all of the base64'd lines
1295
        chunks = []
×
1296
        for line in file:
×
1297
            if not line.startswith(' '):
×
1298
                break
×
1299
            chunks.append(b64d(line))
×
1300

1301
        # Smush everything together, then extract it
1302
        compressed_data = b''.join(chunks)
×
1303
        compressed_file = BytesIO(compressed_data)
×
1304
        gzip_file = gzip.GzipFile(fileobj=compressed_file)
×
1305
        core_data = gzip_file.read()
×
1306

1307
        return core_data
×
1308

1309
    def apport_read_crash_data(self):
1✔
1310
        """Find the apport crash for the process
1311

1312
        Returns:
1313
            `str`: Raw contents of the crash file or ``None``.
1314
        """
1315
        uid = self.uid
1✔
1316
        crash_name = self.exe.replace('/', '_')
1✔
1317

1318
        crash_path = '/var/crash/%s.%i.crash' % (crash_name, uid)
1✔
1319

1320
        try:
1✔
1321
            log.debug("Looking for Apport crash at %r" % crash_path)
1✔
1322
            data = self.read(crash_path)
1✔
1323
        except Exception:
1✔
1324
            return None
1✔
1325

1326
        # Remove the crash file, so that future crashes will be captured
1327
        try:
×
1328
            self.unlink(crash_path)
×
1329
        except Exception:
×
1330
            pass
×
1331

1332
        # Convert bytes-like object to string
1333
        if isinstance(data, bytes):
×
1334
            data = data.decode('utf-8')
×
1335

1336
        return data
×
1337

1338
    def systemd_coredump_corefile(self):
1✔
1339
        """Find the systemd-coredump crash for the process and dump it to a file.
1340

1341
        Arguments:
1342
            process(process): Process object we're looking for.
1343

1344
        Returns:
1345
            `str`: Filename of core file, if coredump was found.
1346
        """
1347
        filename = "core.%s.%i.coredumpctl" % (self.basename, self.pid)
×
1348
        try:
×
1349
            subprocess.check_call(
×
1350
                [
1351
                    "coredumpctl",
1352
                    "dump",
1353
                    "--output=%s" % filename,
1354
                    # Filter coredump by pid
1355
                    str(self.pid),
1356
                ],
1357
                stdout=open(os.devnull, 'w'),
1358
                stderr=subprocess.STDOUT,
1359
                shell=False,
1360
            )
1361
            return filename
×
1362
        except subprocess.CalledProcessError as e:
×
1363
            log.debug("coredumpctl failed with status: %d" % e.returncode)
×
1364

1365
    def native_corefile(self):
1✔
1366
        """Find the corefile for a native crash.
1367

1368
        Arguments:
1369
            process(process): Process whose crash we should find.
1370

1371
        Returns:
1372
            `str`: Filename of core file.
1373
        """
1374
        if self.kernel_core_pattern.startswith(b'|'):
1!
1375
            log.debug("Checking for corefile (piped)")
1✔
1376
            return self.native_corefile_pipe()
1✔
1377

1378
        log.debug("Checking for corefile (pattern)")
×
1379
        return self.native_corefile_pattern()
×
1380

1381
    def native_corefile_pipe(self):
1✔
1382
        """Find the corefile for a piped core_pattern
1383

1384
        Supports apport and systemd-coredump.
1385

1386
        Arguments:
1387
            process(process): Process whose crash we should find.
1388

1389
        Returns:
1390
            `str`: Filename of core file.
1391
        """
1392
        if b'/apport' in self.kernel_core_pattern:
1!
1393
            log.debug("Found apport in core_pattern")
1✔
1394
            apport_core = self.apport_corefile()
1✔
1395

1396
            if apport_core:
1!
1397
                # Write the corefile to the local directory
1398
                filename = 'core.%s.%i.apport' % (self.basename, self.pid)
×
1399
                with open(filename, 'wb+') as f:
×
1400
                    f.write(apport_core)
×
1401
                return filename
×
1402

1403
            filename = self.apport_coredump()
1✔
1404
            if filename:
1!
1405
                return filename
1✔
1406

1407
            # Pretend core_pattern was just 'core', and see if we come up with anything
1408
            self.kernel_core_pattern = 'core'
×
1409
            return self.native_corefile_pattern()
×
1410
        elif b'systemd-coredump' in self.kernel_core_pattern:
×
1411
            log.debug("Found systemd-coredump in core_pattern")
×
1412
            return self.systemd_coredump_corefile()
×
1413
        else:
1414
            log.warn_once("Unsupported core_pattern: %r", self.kernel_core_pattern)
×
1415
            return None
×
1416

1417
    def native_corefile_pattern(self):
1✔
1418
        """
1419
        %%  a single % character
1420
        %c  core file size soft resource limit of crashing process (since Linux 2.6.24)
1421
        %d  dump mode—same as value returned by prctl(2) PR_GET_DUMPABLE (since Linux 3.7)
1422
        %e  executable filename (without path prefix)
1423
        %E  pathname of executable, with slashes ('/') replaced by exclamation marks ('!') (since Linux 3.0).
1424
        %g  (numeric) real GID of dumped process
1425
        %h  hostname (same as nodename returned by uname(2))
1426
        %i  TID of thread that triggered core dump, as seen in the PID namespace in which the thread resides (since Linux 3.18)
1427
        %I  TID of thread that triggered core dump, as seen in the initial PID namespace (since Linux 3.18)
1428
        %p  PID of dumped process, as seen in the PID namespace in which the process resides
1429
        %P  PID of dumped process, as seen in the initial PID namespace (since Linux 3.12)
1430
        %s  number of signal causing dump
1431
        %t  time of dump, expressed as seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)
1432
        %u  (numeric) real UID of dumped process
1433
        """
1434
        replace = {
×
1435
            '%%': '%',
1436
            '%e': os.path.basename(self.interpreter) or self.basename,
1437
            '%E': self.exe.replace('/', '!'),
1438
            '%g': str(self.gid),
1439
            '%h': socket.gethostname(),
1440
            '%i': str(self.pid),
1441
            '%I': str(self.pid),
1442
            '%p': str(self.pid),
1443
            '%P': str(self.pid),
1444
            '%s': str(-self.process.poll()),
1445
            '%u': str(self.uid)
1446
        }
1447
        replace = dict((re.escape(k), v) for k, v in replace.items())
×
1448
        pattern = re.compile("|".join(replace.keys()))
×
1449
        if not hasattr(self.kernel_core_pattern, 'encode'):
×
1450
            self.kernel_core_pattern = self.kernel_core_pattern.decode('utf-8')
×
1451
        core_pattern = self.kernel_core_pattern
×
1452
        corefile_path = pattern.sub(lambda m: replace[re.escape(m.group(0))], core_pattern)
×
1453

1454
        if self.kernel_core_uses_pid:
×
1455
            corefile_path += '.%i' % self.pid
×
1456

1457
        if os.pathsep not in corefile_path:
×
1458
            corefile_path = os.path.join(self.cwd, corefile_path)
×
1459

1460
        log.debug("Trying corefile_path: %r" % corefile_path)
×
1461

1462
        try:
×
1463
            self.read(corefile_path)
×
1464
            return corefile_path
×
1465
        except Exception as e:
×
1466
            log.debug("No dice: %s" % e)
×
1467

1468
    def qemu_corefile(self):
1✔
1469
        """qemu_corefile() -> str
1470

1471
        Retrieves the path to a QEMU core dump.
1472
        """
1473

1474
        # QEMU doesn't follow anybody else's rules
1475
        # https://github.com/qemu/qemu/blob/stable-2.6/linux-user/elfload.c#L2710-L2744
1476
        #
1477
        #     qemu_<basename-of-target-binary>_<date>-<time>_<pid>.core
1478
        #
1479
        # Note that we don't give any fucks about the date and time, since the PID
1480
        # should be unique enough that we can just glob.
1481
        corefile_name = 'qemu_{basename}_*_{pid}.core'
1✔
1482

1483
        # Format the name
1484
        corefile_name = corefile_name.format(basename=self.basename,
1✔
1485
                                             pid=self.pid)
1486

1487
        # Get the full path
1488
        corefile_path = os.path.join(self.cwd, corefile_name)
1✔
1489

1490
        log.debug("Trying corefile_path: %r" % corefile_path)
1✔
1491

1492
        # Glob all of them, return the *most recent* based on numeric sort order.
1493
        for corefile in sorted(glob.glob(corefile_path), reverse=True):
1✔
1494
            return corefile
1✔
1495

1496
    def apport_coredump(self):
1✔
1497
        """Find new-style apport coredump of executables not belonging
1498
        to a system package
1499
        """
1500
        # Now Ubuntu, which is the most silly distro of all, doesn't follow
1501
        # anybody else's rules either...
1502
        # ...and it uses apport FROM SOME OTHER REPO THAN THE DOCS SAY
1503
        # Hey, thanks for making our lives easier, Canonical :----)
1504
        # Seriously, why is Ubuntu even considered to be the default distro
1505
        # on GH Actions?
1506
        #
1507
        #     core.<_path_to_target_binary>.<uid>.<boot_id>.<pid>.<timestamp>
1508
        #
1509
        # Note that we don't give any fucks about the timestamp, since the PID
1510
        # should be unique enough that we can just glob.
1511

1512
        boot_id = read('/proc/sys/kernel/random/boot_id').strip().decode()
1✔
1513
        path = self.exe.replace('/', '_')
1✔
1514

1515
        # Format the name
1516
        corefile_name = 'core.{path}.{uid}.{boot_id}.{pid}.*'.format(
1✔
1517
            path=path,
1518
            uid=self.uid,
1519
            boot_id=boot_id,
1520
            pid=self.pid,
1521
        )
1522

1523
        # Get the full path
1524
        corefile_path = os.path.join('/var/lib/apport/coredump', corefile_name)
1✔
1525

1526
        log.debug("Trying corefile_path: %r" % corefile_path)
1✔
1527

1528
        # Glob all of them, return the *most recent* based on numeric sort order.
1529
        for corefile in sorted(glob.glob(corefile_path), reverse=True):
1!
1530
            return corefile
1✔
1531

1532
    def binfmt_lookup(self):
1✔
1533
        """Parses /proc/sys/fs/binfmt_misc to find the interpreter for a file"""
1534

1535
        binfmt_misc = '/proc/sys/fs/binfmt_misc'
1✔
1536

1537
        if not isinstance(self.process, process):
1!
1538
            log.debug("Not a process")
×
1539
            return ''
×
1540

1541
        if self.process._qemu:
1!
1542
            return self.process._qemu
×
1543

1544
        if not os.path.isdir(binfmt_misc):
1!
1545
            log.debug("No binfmt_misc dir")
×
1546
            return ''
×
1547

1548
        exe_data = bytearray(self.read(self.exe))
1✔
1549

1550
        for entry in os.listdir(binfmt_misc):
1✔
1551
            keys = {}
1✔
1552

1553
            path = os.path.join(binfmt_misc, entry)
1✔
1554

1555
            try:
1✔
1556
                data = self.read(path).decode()
1✔
1557
            except Exception:
1✔
1558
                continue
1✔
1559

1560
            for line in data.splitlines():
1✔
1561
                try:
1✔
1562
                    k,v = line.split(None)
1✔
1563
                except ValueError:
1✔
1564
                    continue
1✔
1565

1566
                keys[k] = v
1✔
1567

1568
            if 'magic' not in keys:
1✔
1569
                continue
1✔
1570

1571
            magic = bytearray(unhex(keys['magic']))
1✔
1572
            mask  = bytearray(b'\xff' * len(magic))
1✔
1573

1574
            if 'mask' in keys:
1✔
1575
                mask = bytearray(unhex(keys['mask']))
1✔
1576

1577
            for i, mag in enumerate(magic):
1✔
1578
                if exe_data[i] & mask[i] != mag:
1✔
1579
                    break
1✔
1580
            else:
1581
                return keys['interpreter']
1✔
1582

1583
        return ''
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc