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

Gallopsled / pwntools / 12797806857

15 Jan 2025 10:04PM UTC coverage: 71.089% (-2.6%) from 73.646%
12797806857

push

github

peace-maker
Drop Python 2.7 support / Require Python 3.10

Only test on Python 3 and bump minimal required python version to 3.10.

3628 of 6402 branches covered (56.67%)

12848 of 18073 relevant lines covered (71.09%)

0.71 hits per line

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

81.44
/pwnlib/context/__init__.py
1
# -*- coding: utf-8 -*-
2
"""
3
Implements context management so that nested/scoped contexts and threaded
4
contexts work properly and as expected.
5
"""
6
from __future__ import absolute_import
1✔
7
from __future__ import division
1✔
8

9
import atexit
1✔
10
import collections
1✔
11
import errno
1✔
12
import functools
1✔
13
import logging
1✔
14
import os
1✔
15
import os.path
1✔
16
import platform
1✔
17
import shutil
1✔
18
import six
1✔
19
import socket
1✔
20
import string
1✔
21
import sys
1✔
22
import tempfile
1✔
23
import threading
1✔
24
import time
1✔
25

26
import socks
1✔
27

28
from pwnlib.config import register_config
1✔
29
from pwnlib.device import Device
1✔
30
from pwnlib.timeout import Timeout
1✔
31

32
try:
1✔
33
    from collections.abc import Iterable
1✔
34
except ImportError:
×
35
    from collections import Iterable
×
36

37
__all__ = ['context', 'ContextType', 'Thread']
1✔
38

39
_original_socket = socket.socket
1✔
40

41
class _devnull(object):
1✔
42
    name = None
1✔
43
    def write(self, *a, **kw): pass
1!
44
    def read(self, *a, **kw):  return ''
1!
45
    def flush(self, *a, **kw): pass
1✔
46
    def close(self, *a, **kw): pass
1✔
47

48
class _defaultdict(dict):
1✔
49
    """
50
    Dictionary which loads missing keys from another dictionary.
51

52
    This is neccesary because the ``default_factory`` method of
53
    :class:`collections.defaultdict` does not provide the key.
54

55
    Examples:
56

57
        >>> a = {'foo': 'bar'}
58
        >>> b = pwnlib.context._defaultdict(a)
59
        >>> b['foo']
60
        'bar'
61
        >>> 'foo' in b
62
        False
63
        >>> b['foo'] = 'baz'
64
        >>> b['foo']
65
        'baz'
66
        >>> del b['foo']
67
        >>> b['foo']
68
        'bar'
69

70
        >>> a = {'foo': 'bar'}
71
        >>> b = pwnlib.context._defaultdict(a)
72
        >>> b['baz'] #doctest: +ELLIPSIS
73
        Traceback (most recent call last):
74
        ...
75
        KeyError: 'baz'
76
    """
77
    def __init__(self, default=None):
1✔
78
        super(_defaultdict, self).__init__()
1✔
79
        if default is None:
1!
80
            default = {}
×
81

82
        self.default = default
1✔
83

84

85
    def __missing__(self, key):
1✔
86
        return self.default[key]
1✔
87

88
class _DictStack(object):
1✔
89
    """
90
    Manages a dictionary-like object, permitting saving and restoring from
91
    a stack of states via :func:`push` and :func:`pop`.
92

93
    The underlying object used as ``default`` must implement ``copy``, ``clear``,
94
    and ``update``.
95

96
    Examples:
97

98
        >>> t = pwnlib.context._DictStack(default={})
99
        >>> t['key'] = 'value'
100
        >>> t
101
        {'key': 'value'}
102
        >>> t.push()
103
        >>> t
104
        {'key': 'value'}
105
        >>> t['key'] = 'value2'
106
        >>> t
107
        {'key': 'value2'}
108
        >>> t.pop()
109
        >>> t
110
        {'key': 'value'}
111
    """
112
    def __init__(self, default):
1✔
113
        self._current = _defaultdict(default)
1✔
114
        self.__stack  = []
1✔
115

116
    def push(self):
1✔
117
        self.__stack.append(self._current.copy())
1✔
118

119
    def pop(self):
1✔
120
        self._current.clear()
1✔
121
        self._current.update(self.__stack.pop())
1✔
122

123
    def copy(self):
1✔
124
        return self._current.copy()
1✔
125

126
    # Pass-through container emulation routines
127
    def __len__(self):              return self._current.__len__()
1!
128
    def __delitem__(self, k):       return self._current.__delitem__(k)
1✔
129
    def __getitem__(self, k):       return self._current.__getitem__(k)
1✔
130
    def __setitem__(self, k, v):    return self._current.__setitem__(k, v)
1✔
131
    def __contains__(self, k):      return self._current.__contains__(k)
1✔
132
    def __iter__(self):             return self._current.__iter__()
1!
133
    def __repr__(self):             return self._current.__repr__()
1!
134
    def __eq__(self, other):        return self._current.__eq__(other)
1!
135

136
    # Required for keyword expansion operator ** to work
137
    def keys(self):                 return self._current.keys()
1!
138
    def values(self):               return self._current.values()
1!
139
    def items(self):                return self._current.items()
1!
140

141

142
class _Tls_DictStack(threading.local, _DictStack):
1✔
143
    """
144
    Per-thread implementation of :class:`_DictStack`.
145

146
    Examples:
147

148
        >>> t = pwnlib.context._Tls_DictStack({})
149
        >>> t['key'] = 'value'
150
        >>> print(t)
151
        {'key': 'value'}
152
        >>> def p(): print(t)
153
        >>> thread = threading.Thread(target=p)
154
        >>> _ = (thread.start(), thread.join())
155
        {}
156
    """
157
    pass
1✔
158

159

160
def _validator(validator):
1✔
161
    """
162
    Validator that is tightly coupled to the implementation
163
    of the classes here.
164

165
    This expects that the object has a ._tls property which
166
    is of type _DictStack.
167
    """
168

169
    name = validator.__name__
1✔
170
    doc  = validator.__doc__
1✔
171

172
    def fget(self):
1✔
173
        return self._tls[name]
1✔
174

175
    def fset(self, val):
1✔
176
        self._tls[name] = validator(self, val)
1✔
177

178
    def fdel(self):
1✔
179
        self._tls._current.pop(name,None)
×
180

181
    return property(fget, fset, fdel, doc)
1✔
182

183
class Thread(threading.Thread):
1✔
184
    """
185
    Instantiates a context-aware thread, which inherit its context when it is
186
    instantiated. The class can be accessed both on the context module as
187
    `pwnlib.context.Thread` and on the context singleton object inside the
188
    context module as `pwnlib.context.context.Thread`.
189

190
    Threads created by using the native :class`threading`.Thread` will have a
191
    clean (default) context.
192

193
    Regardless of the mechanism used to create any thread, the context
194
    is de-coupled from the parent thread, so changes do not cascade
195
    to child or parent.
196

197
    Saves a copy of the context when instantiated (at ``__init__``)
198
    and updates the new thread's context before passing control
199
    to the user code via ``run`` or ``target=``.
200

201
    Examples:
202

203
        >>> context.clear()
204
        >>> context.update(arch='arm')
205
        >>> def p():
206
        ...     print(context.arch)
207
        ...     context.arch = 'mips'
208
        ...     print(context.arch)
209
        >>> # Note that a normal Thread starts with a clean context
210
        >>> # (i386 is the default architecture)
211
        >>> t = threading.Thread(target=p)
212
        >>> _=(t.start(), t.join())
213
        i386
214
        mips
215
        >>> # Note that the main Thread's context is unchanged
216
        >>> print(context.arch)
217
        arm
218
        >>> # Note that a context-aware Thread receives a copy of the context
219
        >>> t = pwnlib.context.Thread(target=p)
220
        >>> _=(t.start(), t.join())
221
        arm
222
        mips
223
        >>> # Again, the main thread is unchanged
224
        >>> print(context.arch)
225
        arm
226

227
    Implementation Details:
228

229
        This class implemented by hooking the private function
230
        :func:`threading.Thread._Thread_bootstrap`, which is called before
231
        passing control to :func:`threading.Thread.run`.
232

233
        This could be done by overriding ``run`` itself, but we would have to
234
        ensure that all uses of the class would only ever use the keyword
235
        ``target=`` for ``__init__``, or that all subclasses invoke
236
        ``super(Subclass.self).set_up_context()`` or similar.
237
    """
238
    def __init__(self, *args, **kwargs):
1✔
239
        super(Thread, self).__init__(*args, **kwargs)
1✔
240
        self.old = context.copy()
1✔
241

242
    def __bootstrap(self):
1✔
243
        """
244
        Implementation Details:
245
            This only works because the class is named ``Thread``.
246
            If its name is changed, we have to implement this hook
247
            differently.
248
        """
249
        context.update(**self.old)
×
250
        sup = super(Thread, self)
×
251
        bootstrap = getattr(sup, '_bootstrap', None)
×
252
        if bootstrap is None:
×
253
            sup.__bootstrap()
×
254
        else:
255
            bootstrap()
×
256
    _bootstrap = __bootstrap
1✔
257

258
def _longest(d):
1✔
259
    """
260
    Returns an OrderedDict with the contents of the input dictionary ``d``
261
    sorted by the length of the keys, in descending order.
262

263
    This is useful for performing substring matching via ``str.startswith``,
264
    as it ensures the most complete match will be found.
265

266
    >>> data = {'a': 1, 'bb': 2, 'ccc': 3}
267
    >>> pwnlib.context._longest(data) == data
268
    True
269
    >>> for i in pwnlib.context._longest(data):
270
    ...     print(i)
271
    ccc
272
    bb
273
    a
274
    """
275
    return collections.OrderedDict((k,d[k]) for k in sorted(d, key=len, reverse=True))
1✔
276

277
class ContextType(object):
1✔
278
    r"""
279
    Class for specifying information about the target machine.
280
    Intended for use as a pseudo-singleton through the global
281
    variable :data:`.context`, available via
282
    ``from pwn import *`` as ``context``.
283

284
    The context is usually specified at the top of the Python file for clarity. ::
285

286
        #!/usr/bin/env python
287
        context.update(arch='i386', os='linux')
288

289
    Currently supported properties and their defaults are listed below.
290
    The defaults are inherited from :data:`pwnlib.context.ContextType.defaults`.
291

292
    Additionally, the context is thread-aware when using
293
    :class:`pwnlib.context.Thread` instead of :class:`threading.Thread`
294
    (all internal ``pwntools`` threads use the former).
295

296
    The context is also scope-aware by using the ``with`` keyword.
297

298
    Examples:
299

300
        >>> context.clear()
301
        >>> context.update(os='linux') # doctest: +ELLIPSIS
302
        >>> context.os == 'linux'
303
        True
304
        >>> context.arch = 'arm'
305
        >>> vars(context) == {'arch': 'arm', 'bits': 32, 'endian': 'little', 'os': 'linux', 'newline': b'\n'}
306
        True
307
        >>> context.endian
308
        'little'
309
        >>> context.bits
310
        32
311
        >>> def nop():
312
        ...   print(enhex(pwnlib.asm.asm('nop')))
313
        >>> nop()
314
        00f020e3
315
        >>> with context.local(arch = 'i386'):
316
        ...   nop()
317
        90
318
        >>> from pwnlib.context import Thread as PwnThread
319
        >>> from threading      import Thread as NormalThread
320
        >>> with context.local(arch = 'mips'):
321
        ...     pwnthread = PwnThread(target=nop)
322
        ...     thread    = NormalThread(target=nop)
323
        >>> # Normal thread uses the default value for arch, 'i386'
324
        >>> _=(thread.start(), thread.join())
325
        90
326
        >>> # Pwnthread uses the correct context from creation-time
327
        >>> _=(pwnthread.start(), pwnthread.join())
328
        00000000
329
        >>> nop()
330
        00f020e3
331
    """
332

333
    #
334
    # Use of 'slots' is a heavy-handed way to prevent accidents
335
    # like 'context.architecture=' instead of 'context.arch='.
336
    #
337
    # Setting any properties on a ContextType object will throw an
338
    # exception.
339
    #
340
    __slots__ = '_tls',
1✔
341

342
    #: Default values for :class:`pwnlib.context.ContextType`
343
    defaults = {
1✔
344
        'adb_host': 'localhost',
345
        'adb_port': 5037,
346
        'arch': 'i386',
347
        'aslr': True,
348
        'binary': None,
349
        'bits': 32,
350
        'buffer_size': 4096,
351
        'cache_dir_base': os.environ.get(
352
            'XDG_CACHE_HOME',
353
            os.path.join(os.path.expanduser('~'), '.cache')
354
        ),
355
        'cyclic_alphabet': string.ascii_lowercase.encode(),
356
        'cyclic_size': 4,
357
        'delete_corefiles': False,
358
        'device': os.getenv('ANDROID_SERIAL', None) or None,
359
        'encoding': 'auto',
360
        'endian': 'little',
361
        'gdbinit': "",
362
        'kernel': None,
363
        'local_libcdb': "/var/lib/libc-database",
364
        'log_level': logging.INFO,
365
        'log_file': _devnull(),
366
        'log_console': sys.stdout,
367
        'randomize': False,
368
        'rename_corefiles': True,
369
        'newline': b'\n',
370
        'throw_eof_on_incomplete_line': None,
371
        'noptrace': False,
372
        'os': 'linux',
373
        'proxy': None,
374
        'ssh_session': None,
375
        'signed': False,
376
        'terminal': tuple(),
377
        'timeout': Timeout.maximum,
378
    }
379

380
    unix_like    = {'newline': b'\n'}
1✔
381
    windows_like = {'newline': b'\r\n'}
1✔
382

383
    #: Keys are valid values for :meth:`pwnlib.context.ContextType.os`
384
    oses = _longest({
1✔
385
        'linux':     unix_like,
386
        'freebsd':   unix_like,
387
        'windows':   windows_like,
388
        'cgc':       unix_like,
389
        'android':   unix_like,
390
        'baremetal': unix_like,
391
        'darwin':    unix_like,
392
    })
393

394
    big_32    = {'endian': 'big', 'bits': 32}
1✔
395
    big_64    = {'endian': 'big', 'bits': 64}
1✔
396
    little_8  = {'endian': 'little', 'bits': 8}
1✔
397
    little_16 = {'endian': 'little', 'bits': 16}
1✔
398
    little_32 = {'endian': 'little', 'bits': 32}
1✔
399
    little_64 = {'endian': 'little', 'bits': 64}
1✔
400

401
    #: Keys are valid values for :meth:`pwnlib.context.ContextType.arch`.
402
    #
403
    #: Values are defaults which are set when
404
    #: :attr:`pwnlib.context.ContextType.arch` is set
405
    architectures = _longest({
1✔
406
        'aarch64':   little_64,
407
        'alpha':     little_64,
408
        'avr':       little_8,
409
        'amd64':     little_64,
410
        'arm':       little_32,
411
        'cris':      little_32,
412
        'i386':      little_32,
413
        'ia64':      big_64,
414
        'm68k':      big_32,
415
        'mips':      little_32,
416
        'mips64':    little_64,
417
        'msp430':    little_16,
418
        'powerpc':   big_32,
419
        'powerpc64': big_64,
420
        'riscv32':   little_32,
421
        'riscv64':   little_64,
422
        's390':      big_32,
423
        'sparc':     big_32,
424
        'sparc64':   big_64,
425
        'thumb':     little_32,
426
        'vax':       little_32,
427
        'none':      {},
428
    })
429

430
    #: Valid values for :attr:`endian`
431
    endiannesses = _longest({
1✔
432
        'be':     'big',
433
        'eb':     'big',
434
        'big':    'big',
435
        'le':     'little',
436
        'el':     'little',
437
        'little': 'little'
438
    })
439

440
    #: Valid string values for :attr:`signed`
441
    signednesses = {
1✔
442
        'unsigned': False,
443
        'no':       False,
444
        'yes':      True,
445
        'signed':   True
446
    }
447

448
    valid_signed = sorted(signednesses)
1✔
449

450
    def __init__(self, **kwargs):
1✔
451
        """
452
        Initialize the ContextType structure.
453

454
        All keyword arguments are passed to :func:`update`.
455
        """
456
        self._tls = _Tls_DictStack(_defaultdict(self.defaults))
1✔
457
        self.update(**kwargs)
1✔
458

459

460
    def copy(self):
1✔
461
        r"""copy() -> dict
462
        Returns a copy of the current context as a dictionary.
463

464
        Examples:
465

466
            >>> context.clear()
467
            >>> context.os   = 'linux'
468
            >>> vars(context) == {'os': 'linux', 'newline': b'\n'}
469
            True
470
        """
471
        return self._tls.copy()
1✔
472

473

474
    @property
1✔
475
    def __dict__(self):
1✔
476
        return self.copy()
1✔
477

478
    def update(self, *args, **kwargs):
1✔
479
        """
480
        Convenience function, which is shorthand for setting multiple
481
        variables at once.
482

483
        It is a simple shorthand such that::
484

485
            context.update(os = 'linux', arch = 'arm', ...)
486

487
        is equivalent to::
488

489
            context.os   = 'linux'
490
            context.arch = 'arm'
491
            ...
492

493
        The following syntax is also valid::
494

495
            context.update({'os': 'linux', 'arch': 'arm'})
496

497
        Arguments:
498
          kwargs: Variables to be assigned in the environment.
499

500
        Examples:
501

502
            >>> context.clear()
503
            >>> context.update(arch = 'i386', os = 'linux')
504
            >>> context.arch, context.os
505
            ('i386', 'linux')
506
        """
507
        for arg in args:
1!
508
            self.update(**arg)
×
509

510
        for k,v in kwargs.items():
1✔
511
            setattr(self,k,v)
1✔
512

513
    def __repr__(self):
1✔
514
        v = sorted("%s = %r" % (k,v) for k,v in self._tls._current.items())
1✔
515
        return '%s(%s)' % (self.__class__.__name__, ', '.join(v))
1✔
516

517
    def local(self, function=None, **kwargs):
1✔
518
        """local(**kwargs) -> context manager
519

520
        Create a context manager for use with the ``with`` statement.
521

522
        For more information, see the example below or PEP 343.
523

524
        Arguments:
525
          kwargs: Variables to be assigned in the new environment.
526

527
        Returns:
528
          ContextType manager for managing the old and new environment.
529

530
        Examples:
531

532
            >>> context.clear()
533
            >>> context.timeout = 1
534
            >>> context.timeout == 1
535
            True
536
            >>> print(context.timeout)
537
            1.0
538
            >>> with context.local(timeout = 2):
539
            ...     print(context.timeout)
540
            ...     context.timeout = 3
541
            ...     print(context.timeout)
542
            2.0
543
            3.0
544
            >>> print(context.timeout)
545
            1.0
546
        """
547
        class LocalContext(object):
1✔
548
            def __enter__(a):
1✔
549
                self._tls.push()
1✔
550
                self.update(**{k:v for k,v in kwargs.items() if v is not None})
1✔
551
                return self
1✔
552

553
            def __exit__(a, *b, **c):
1✔
554
                self._tls.pop()
1✔
555

556
            def __call__(self, function, *a, **kw):
1✔
557
                @functools.wraps(function)
1✔
558
                def inner(*a, **kw):
1✔
559
                    with self:
1✔
560
                        return function(*a, **kw)
1✔
561
                return inner
1✔
562

563
        return LocalContext()
1✔
564

565
    @property
1✔
566
    def silent(self, function=None):
1✔
567
        """Disable all non-error logging within the enclosed scope.
568
        """
569
        return self.local(function, log_level='error')
1✔
570

571
    @property
1✔
572
    def quiet(self, function=None):
1✔
573
        """Disables all non-error logging within the enclosed scope,
574
        *unless* the debugging level is set to 'debug' or lower.
575

576
        Example:
577

578
            Let's assume the normal situation, where log_level is INFO.
579

580
            >>> context.clear(log_level='info')
581

582
            Note that only the log levels below ERROR do not print anything.
583

584
            >>> with context.quiet:
585
            ...     log.debug("DEBUG")
586
            ...     log.info("INFO")
587
            ...     log.warn("WARN")
588

589
            Next let's try with the debugging level set to 'debug' before we
590
            enter the context handler:
591

592
            >>> with context.local(log_level='debug'):
593
            ...     with context.quiet:
594
            ...         log.debug("DEBUG")
595
            ...         log.info("INFO")
596
            ...         log.warn("WARN")
597
            [...] DEBUG
598
            [...] INFO
599
            [...] WARN
600
        """
601
        level = 'error'
1✔
602
        if context.log_level <= logging.DEBUG:
1✔
603
            level = None
1✔
604
        return self.local(function, log_level=level)
1✔
605

606
    def quietfunc(self, function):
1✔
607
        """Similar to :attr:`quiet`, but wraps a whole function.
608

609
        Example:
610

611
            Let's set up two functions, which are the same but one is
612
            wrapped with :attr:`quietfunc`.
613

614
            >>> def loud(): log.info("Loud")
615
            >>> @context.quietfunc
616
            ... def quiet(): log.info("Quiet")
617

618
            If we set the logging level to 'info', the loud function
619
            prints its contents.
620

621
            >>> with context.local(log_level='info'): loud()
622
            [*] Loud
623

624
            However, the quiet function does not, since :attr:`quietfunc`
625
            silences all output unless the log level is DEBUG.
626

627
            >>> with context.local(log_level='info'): quiet()
628

629
            Now let's try again with debugging enabled.
630

631
            >>> with context.local(log_level='debug'): quiet()
632
            [*] Quiet
633
        """
634
        @functools.wraps(function)
1✔
635
        def wrapper(*a, **kw):
1✔
636
            level = 'error'
1✔
637
            if context.log_level <= logging.DEBUG:
1✔
638
                level = None
1✔
639
            with self.local(function, log_level=level):
1✔
640
                return function(*a, **kw)
1✔
641
        return wrapper
1✔
642

643

644
    @property
1✔
645
    def verbose(self):
1✔
646
        """Enable all logging within the enclosed scope.
647

648
        This is the opposite of :attr:`.quiet` and functionally equivalent to:
649

650
        .. code-block:: python
651

652
            with context.local(log_level='debug'):
653
                ...
654

655
        Example:
656

657
            Note that the function does not emit any information by default
658

659
            >>> context.clear()
660
            >>> def func(): log.debug("Hello")
661
            >>> func()
662

663
            But if we put it inside a :attr:`.verbose` context manager, the
664
            information is printed.
665

666
            >>> with context.verbose: func()
667
            [...] Hello
668

669
        """
670
        return self.local(log_level='debug')
1✔
671

672
    def clear(self, *a, **kw):
1✔
673
        """
674
        Clears the contents of the context.
675
        All values are set to their defaults.
676

677
        Arguments:
678

679
            a: Arguments passed to ``update``
680
            kw: Arguments passed to ``update``
681

682
        Examples:
683

684
            >>> # Default value
685
            >>> context.clear()
686
            >>> context.arch == 'i386'
687
            True
688
            >>> context.arch = 'arm'
689
            >>> context.arch == 'i386'
690
            False
691
            >>> context.clear()
692
            >>> context.arch == 'i386'
693
            True
694
        """
695
        self._tls._current.clear()
1✔
696

697
        if a or kw:
1✔
698
            self.update(*a, **kw)
1✔
699

700
    @property
1✔
701
    def native(self):
1✔
702
        if context.os in ('android', 'baremetal', 'cgc'):
1!
703
            return False
×
704

705
        arch = context.arch
1✔
706
        with context.local(arch = platform.machine()):
1✔
707
            platform_arch = context.arch
1✔
708

709
            if arch in ('i386', 'amd64') and platform_arch in ('i386', 'amd64'):
1✔
710
                return True
1✔
711

712
            return arch == platform_arch
1✔
713

714
    @_validator
1✔
715
    def arch(self, arch):
1✔
716
        """
717
        Target binary architecture.
718

719
        Allowed values are listed in :attr:`pwnlib.context.ContextType.architectures`.
720

721
        Side Effects:
722

723
            If an architecture is specified which also implies additional
724
            attributes (e.g. 'amd64' implies 64-bit words, 'powerpc' implies
725
            big-endian), these attributes will be set on the context if a
726
            user has not already set a value.
727

728
            The following properties may be modified.
729

730
            - :attr:`bits`
731
            - :attr:`endian`
732

733
        Raises:
734
            AttributeError: An invalid architecture was specified
735

736
        Examples:
737

738
            >>> context.clear()
739
            >>> context.arch == 'i386' # Default architecture
740
            True
741

742
            >>> context.arch = 'mips'
743
            >>> context.arch == 'mips'
744
            True
745

746
            >>> context.arch = 'doge' #doctest: +ELLIPSIS
747
            Traceback (most recent call last):
748
             ...
749
            AttributeError: arch must be one of ['aarch64', ..., 'thumb']
750

751
            >>> context.arch = 'ppc'
752
            >>> context.arch == 'powerpc' # Aliased architecture
753
            True
754

755
            >>> context.clear()
756
            >>> context.bits == 32 # Default value
757
            True
758
            >>> context.arch = 'amd64'
759
            >>> context.bits == 64 # New value
760
            True
761

762
            Note that expressly setting :attr:`bits` means that we use
763
            that value instead of the default
764

765
            >>> context.clear()
766
            >>> context.bits = 32
767
            >>> context.arch = 'amd64'
768
            >>> context.bits == 32
769
            True
770

771
            Setting the architecture can override the defaults for
772
            both :attr:`endian` and :attr:`bits`
773

774
            >>> context.clear()
775
            >>> context.arch = 'powerpc64'
776
            >>> vars(context) == {'arch': 'powerpc64', 'bits': 64, 'endian': 'big'}
777
            True
778
        """
779
        # Lowercase
780
        arch = arch.lower()
1✔
781

782
        # Attempt to perform convenience and legacy compatibility transformations.
783
        # We have to make sure that x86_64 appears before x86 for this to work correctly.
784
        transform = [('ppc64', 'powerpc64'),
1✔
785
                     ('ppc', 'powerpc'),
786
                     ('x86-64', 'amd64'),
787
                     ('x86_64', 'amd64'),
788
                     ('x86', 'i386'),
789
                     ('i686', 'i386'),
790
                     ('armv7l', 'arm'),
791
                     ('armeabi', 'arm'),
792
                     ('arm64', 'aarch64'),
793
                     ('rv32', 'riscv32'),
794
                     ('rv64', 'riscv64')]
795
        for k, v in transform:
1✔
796
            if arch.startswith(k):
1✔
797
                arch = v
1✔
798
                break
1✔
799

800
        try:
1✔
801
            defaults = self.architectures[arch]
1✔
802
        except KeyError:
1✔
803
            raise AttributeError('AttributeError: arch (%r) must be one of %r' % (arch, sorted(self.architectures)))
1✔
804

805
        for k,v in defaults.items():
1✔
806
            if k not in self._tls:
1✔
807
                self._tls[k] = v
1✔
808

809
        return arch
1✔
810

811
    @_validator
1✔
812
    def aslr(self, aslr):
1✔
813
        """
814
        ASLR settings for new processes.
815

816
        If :const:`False`, attempt to disable ASLR in all processes which are
817
        created via ``personality`` (``setarch -R``) and ``setrlimit``
818
        (``ulimit -s unlimited``).
819

820
        The ``setarch`` changes are lost if a ``setuid`` binary is executed.
821
        """
822
        return bool(aslr)
1✔
823

824
    @_validator
1✔
825
    def kernel(self, arch):
1✔
826
        """
827
        Target machine's kernel architecture.
828

829
        Usually, this is the same as ``arch``, except when
830
        running a 32-bit binary on a 64-bit kernel (e.g. i386-on-amd64).
831

832
        Even then, this doesn't matter much -- only when the the segment
833
        registers need to be known
834
        """
835
        with self.local(arch=arch):
1✔
836
            return self.arch
1✔
837

838
    @_validator
1✔
839
    def bits(self, bits):
1✔
840
        """
841
        Target machine word size, in bits (i.e. the size of general purpose registers).
842

843
        The default value is ``32``, but changes according to :attr:`arch`.
844

845
        Examples:
846

847
            >>> context.clear()
848
            >>> context.bits == 32
849
            True
850
            >>> context.bits = 64
851
            >>> context.bits == 64
852
            True
853
            >>> context.bits = -1 #doctest: +ELLIPSIS
854
            Traceback (most recent call last):
855
            ...
856
            AttributeError: bits must be > 0 (-1)
857
        """
858
        bits = int(bits)
1✔
859

860
        if bits <= 0:
1✔
861
            raise AttributeError("bits must be > 0 (%r)" % bits)
1✔
862

863
        return bits
1✔
864

865
    @_validator
1✔
866
    def binary(self, binary):
1✔
867
        """
868
        Infer target architecture, bit-with, and endianness from a binary file.
869
        Data type is a :class:`pwnlib.elf.ELF` object.
870

871
        Examples:
872

873
            >>> context.clear()
874
            >>> context.arch, context.bits
875
            ('i386', 32)
876
            >>> context.binary = '/bin/bash'
877
            >>> context.arch, context.bits
878
            ('amd64', 64)
879
            >>> context.binary
880
            ELF('/bin/bash')
881

882
        """
883
        # Cyclic imports... sorry Idolf.
884
        from pwnlib.elf     import ELF
1✔
885

886
        if not isinstance(binary, ELF):
1✔
887
            binary = ELF(binary)
1✔
888

889
        self.arch   = binary.arch
1✔
890
        self.bits   = binary.bits
1✔
891
        self.endian = binary.endian
1✔
892
        self.os     = binary.os
1✔
893

894
        return binary
1✔
895

896
    @property
1✔
897
    def bytes(self):
1✔
898
        """
899
        Target machine word size, in bytes (i.e. the size of general purpose registers).
900

901
        This is a convenience wrapper around ``bits // 8``.
902

903
        Examples:
904

905
            >>> context.bytes = 1
906
            >>> context.bits == 8
907
            True
908

909
            >>> context.bytes = 0 #doctest: +ELLIPSIS
910
            Traceback (most recent call last):
911
            ...
912
            AttributeError: bits must be > 0 (0)
913
        """
914
        return self.bits // 8
1✔
915
    @bytes.setter
1✔
916
    def bytes(self, value):
1✔
917
        self.bits = value*8
1✔
918

919
    @_validator
1✔
920
    def encoding(self, charset):
1✔
921
        if charset == 'auto':
×
922
            return charset
×
923

924
        if (  b'aA'.decode(charset) != 'aA'
×
925
            or 'aA'.encode(charset) != b'aA'):
926
            raise ValueError('Strange encoding!')
×
927

928
        return charset
×
929

930
    @_validator
1✔
931
    def endian(self, endianness):
1✔
932
        """
933
        Endianness of the target machine.
934

935
        The default value is ``'little'``, but changes according to :attr:`arch`.
936

937
        Raises:
938
            AttributeError: An invalid endianness was provided
939

940
        Examples:
941

942
            >>> context.clear()
943
            >>> context.endian == 'little'
944
            True
945

946
            >>> context.endian = 'big'
947
            >>> context.endian
948
            'big'
949

950
            >>> context.endian = 'be'
951
            >>> context.endian == 'big'
952
            True
953

954
            >>> context.endian = 'foobar' #doctest: +ELLIPSIS
955
            Traceback (most recent call last):
956
             ...
957
            AttributeError: endian must be one of ['be', 'big', 'eb', 'el', 'le', 'little']
958
        """
959
        endian = endianness.lower()
1✔
960

961
        if endian not in self.endiannesses:
1✔
962
            raise AttributeError("endian must be one of %r" % sorted(self.endiannesses))
1✔
963

964
        return self.endiannesses[endian]
1✔
965

966

967
    @_validator
1✔
968
    def log_level(self, value):
1✔
969
        """
970
        Sets the verbosity of ``pwntools`` logging mechanism.
971

972
        More specifically it controls the filtering of messages that happens
973
        inside the handler for logging to the screen. So if you want e.g. log
974
        all messages to a file, then this attribute makes no difference to you.
975

976
        Valid values are specified by the standard Python ``logging`` module.
977

978
        Default value is set to ``INFO``.
979

980
        Examples:
981

982
            >>> context.log_level = 'error'
983
            >>> context.log_level == logging.ERROR
984
            True
985
            >>> context.log_level = 10
986
            >>> context.log_level = 'foobar' #doctest: +ELLIPSIS
987
            Traceback (most recent call last):
988
            ...
989
            AttributeError: log_level must be an integer or one of ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
990
        """
991
        # If it can be converted into an int, success
992
        try:                    return int(value)
1✔
993
        except ValueError:  pass
1✔
994

995
        # If it is defined in the logging module, success
996
        try:                    return getattr(logging, value.upper())
1✔
997
        except AttributeError:  pass
1✔
998

999
        # Otherwise, fail
1000
        try:
1✔
1001
            level_names = logging._levelToName.values()
1✔
1002
        except AttributeError:
×
1003
            level_names = filter(lambda x: isinstance(x,str), logging._levelNames)
×
1004
        permitted = sorted(level_names)
1✔
1005
        raise AttributeError('log_level must be an integer or one of %r' % permitted)
1✔
1006

1007
    @_validator
1✔
1008
    def log_file(self, value):
1✔
1009
        r"""
1010
        Sets the target file for all logging output.
1011

1012
        Works in a similar fashion to :attr:`log_level`.
1013

1014
        Examples:
1015

1016

1017
            >>> foo_txt = tempfile.mktemp()
1018
            >>> bar_txt = tempfile.mktemp()
1019
            >>> context.log_file = foo_txt
1020
            >>> log.debug('Hello!')
1021
            >>> with context.local(log_level='ERROR'): #doctest: +ELLIPSIS
1022
            ...     log.info('Hello again!')
1023
            >>> with context.local(log_file=bar_txt):
1024
            ...     log.debug('Hello from bar!')
1025
            >>> log.info('Hello from foo!')
1026
            >>> open(foo_txt).readlines()[-3] #doctest: +ELLIPSIS
1027
            '...:DEBUG:...:Hello!\n'
1028
            >>> open(foo_txt).readlines()[-2] #doctest: +ELLIPSIS
1029
            '...:INFO:...:Hello again!\n'
1030
            >>> open(foo_txt).readlines()[-1] #doctest: +ELLIPSIS
1031
            '...:INFO:...:Hello from foo!\n'
1032
            >>> open(bar_txt).readlines()[-1] #doctest: +ELLIPSIS
1033
            '...:DEBUG:...:Hello from bar!\n'
1034
        """
1035
        if isinstance(value, (bytes, six.text_type)):
1!
1036
            # check if mode was specified as "[value],[mode]"
1037
            from pwnlib.util.packing import _need_text
1✔
1038
            value = _need_text(value)
1✔
1039
            if ',' not in value:
1!
1040
                value += ',a'
1✔
1041
            filename, mode = value.rsplit(',', 1)
1✔
1042
            value = open(filename, mode)
1✔
1043

1044
        elif not hasattr(value, "fileno"):
×
1045
            raise AttributeError('log_file must be a file')
×
1046

1047
        # Is this the same file we already have open?
1048
        # If so, don't re-print the banner.
1049
        if self.log_file and not isinstance(self.log_file, _devnull):
1✔
1050
            a = os.fstat(value.fileno()).st_ino
1✔
1051
            b = os.fstat(self.log_file.fileno()).st_ino
1✔
1052

1053
            if a == b:
1!
1054
                return self.log_file
×
1055

1056
        iso_8601 = '%Y-%m-%dT%H:%M:%S'
1✔
1057
        lines = [
1✔
1058
            '=' * 78,
1059
            ' Started at %s ' % time.strftime(iso_8601),
1060
            ' sys.argv = [',
1061
            ]
1062
        for arg in sys.argv:
1✔
1063
            lines.append('   %r,' % arg)
1✔
1064
        lines.append(' ]')
1✔
1065
        lines.append('=' * 78)
1✔
1066
        for line in lines:
1✔
1067
            value.write('=%-78s=\n' % line)
1✔
1068
        value.flush()
1✔
1069
        return value
1✔
1070

1071
    @_validator
1✔
1072
    def log_console(self, stream):
1✔
1073
        """
1074
        Sets the default logging console target.
1075

1076
        Examples:
1077

1078
            >>> context.log_level = 'warn'
1079
            >>> log.warn("Hello")
1080
            [!] Hello
1081
            >>> context.log_console=open('/dev/null', 'w')
1082
            >>> log.warn("Hello")
1083
            >>> context.clear()
1084
        """
1085
        if isinstance(stream, str):
1!
1086
            stream = open(stream, 'wt')
×
1087
        return stream
1✔
1088

1089
    @_validator
1✔
1090
    def local_libcdb(self, path):
1✔
1091
        """ 
1092
        Sets path to local libc-database, get more information for libc-database:
1093
        https://github.com/niklasb/libc-database
1094

1095
        Works in :attr:`pwnlib.libcdb` when searching by local database provider.
1096

1097
        The default value is ``/var/lib/libc-database``.
1098

1099
        Sets `context.local_libcdb` to empty string or `None` will turn off local libc-database integration.
1100

1101
        Examples:
1102

1103
            >>> context.local_libcdb = pwnlib.data.elf.path
1104
            >>> context.local_libcdb = 'foobar'
1105
            Traceback (most recent call last):
1106
            ...
1107
            AttributeError: 'foobar' does not exist, please download libc-database first
1108
        """
1109

1110
        if not os.path.isdir(path):
1✔
1111
            raise AttributeError("'%s' does not exist, please download libc-database first" % path)
1✔
1112

1113
        return path
1✔
1114

1115
    @property
1✔
1116
    def mask(self):
1✔
1117
        return (1 << self.bits) - 1
1✔
1118

1119
    @_validator
1✔
1120
    def os(self, os):
1✔
1121
        r"""
1122
        Operating system of the target machine.
1123

1124
        The default value is ``linux``.
1125

1126
        Allowed values are listed in :attr:`pwnlib.context.ContextType.oses`.
1127

1128
        Side Effects:
1129

1130
            If an os is specified some attributes will be set on the context
1131
            if a user has not already set a value.
1132

1133
            The following property may be modified:
1134

1135
            - :attr:`newline`
1136

1137
        Raises:
1138
            AttributeError: An invalid os was specified
1139

1140
        Examples:
1141

1142
            >>> context.clear()
1143
            >>> context.os == 'linux' # Default os
1144
            True
1145

1146
            >>> context.os = 'freebsd'
1147
            >>> context.os == 'freebsd'
1148
            True
1149

1150
            >>> context.os = 'foobar' #doctest: +ELLIPSIS
1151
            Traceback (most recent call last):
1152
            ...
1153
            AttributeError: os must be one of ['android', 'baremetal', 'cgc', 'freebsd', 'linux', 'windows']
1154

1155
            >>> context.clear()
1156
            >>> context.newline == b'\n' # Default value
1157
            True
1158
            >>> context.os = 'windows'
1159
            >>> context.newline == b'\r\n' # New value
1160
            True
1161

1162
            Note that expressly setting :attr:`newline` means that we use
1163
            that value instead of the default
1164

1165
            >>> context.clear()
1166
            >>> context.newline = b'\n'
1167
            >>> context.os = 'windows'
1168
            >>> context.newline == b'\n'
1169
            True
1170

1171
            Setting the os can override the default for :attr:`newline`
1172

1173
            >>> context.clear()
1174
            >>> context.os = 'windows'
1175
            >>> vars(context) == {'os': 'windows', 'newline': b'\r\n'}
1176
            True
1177
        """
1178
        os = os.lower()
1✔
1179

1180
        try:
1✔
1181
            defaults = self.oses[os]
1✔
1182
        except KeyError:
1✔
1183
            raise AttributeError("os must be one of %r" % sorted(self.oses))
1✔
1184

1185
        for k,v in defaults.items():
1✔
1186
            if k not in self._tls:
1✔
1187
                self._tls[k] = v
1✔
1188

1189
        return os
1✔
1190

1191
    @_validator
1✔
1192
    def randomize(self, r):
1✔
1193
        """
1194
        Global flag that lots of things should be randomized.
1195
        """
1196
        return bool(r)
×
1197

1198
    @_validator
1✔
1199
    def signed(self, signed):
1✔
1200
        """
1201
        Signed-ness for packing operation when it's not explicitly set.
1202

1203
        Can be set to any non-string truthy value, or the specific string
1204
        values ``'signed'`` or ``'unsigned'`` which are converted into
1205
        :const:`True` and :const:`False` correspondingly.
1206

1207
        Examples:
1208

1209
            >>> context.signed
1210
            False
1211
            >>> context.signed = 1
1212
            >>> context.signed
1213
            True
1214
            >>> context.signed = 'signed'
1215
            >>> context.signed
1216
            True
1217
            >>> context.signed = 'unsigned'
1218
            >>> context.signed
1219
            False
1220
            >>> context.signed = 'foobar' #doctest: +ELLIPSIS
1221
            Traceback (most recent call last):
1222
            ...
1223
            AttributeError: signed must be one of ['no', 'signed', 'unsigned', 'yes'] or a non-string truthy value
1224
        """
1225
        try:             signed = self.signednesses[signed]
1✔
1226
        except KeyError: pass
1✔
1227

1228
        if isinstance(signed, str):
1✔
1229
            raise AttributeError('signed must be one of %r or a non-string truthy value' % sorted(self.signednesses))
1✔
1230

1231
        return bool(signed)
1✔
1232

1233
    @_validator
1✔
1234
    def timeout(self, value=Timeout.default):
1✔
1235
        """
1236
        Default amount of time to wait for a blocking operation before it times out,
1237
        specified in seconds.
1238

1239
        The default value is to have an infinite timeout.
1240

1241
        See :class:`pwnlib.timeout.Timeout` for additional information on
1242
        valid values.
1243
        """
1244
        return Timeout(value).timeout
1✔
1245

1246
    @_validator
1✔
1247
    def terminal(self, value):
1✔
1248
        """
1249
        Default terminal used by :meth:`pwnlib.util.misc.run_in_new_terminal`.
1250
        Can be a string or an iterable of strings.  In the latter case the first
1251
        entry is the terminal and the rest are default arguments.
1252
        """
1253
        if isinstance(value, (bytes, six.text_type)):
1!
1254
            return [value]
×
1255
        return value
1✔
1256

1257
    @property
1✔
1258
    def abi(self):
1✔
1259
        return self._abi
×
1260

1261
    @_validator
1✔
1262
    def proxy(self, proxy):
1✔
1263
        """
1264
        Default proxy for all socket connections.
1265

1266
        Accepts either a string (hostname or IP address) for a SOCKS5 proxy on
1267
        the default port, **or** a ``tuple`` passed to ``socks.set_default_proxy``,
1268
        e.g. ``(socks.SOCKS4, 'localhost', 1234)``.
1269

1270
        >>> context.proxy = 'localhost' #doctest: +ELLIPSIS
1271
        >>> r=remote('google.com', 80)
1272
        Traceback (most recent call last):
1273
        ...
1274
        ProxyConnectionError: Error connecting to SOCKS5 proxy localhost:1080: [Errno 111] Connection refused
1275

1276
        >>> context.proxy = None
1277
        >>> r=remote('google.com', 80, level='error')
1278
        """
1279

1280
        if not proxy:
1✔
1281
            socket.socket = _original_socket
1✔
1282
            return None
1✔
1283

1284
        if isinstance(proxy, str):
1!
1285
            proxy = (socks.SOCKS5, proxy)
1✔
1286

1287
        if not isinstance(proxy, Iterable):
1!
1288
            raise AttributeError('proxy must be a string hostname, or tuple of arguments for socks.set_default_proxy')
×
1289

1290
        socks.set_default_proxy(*proxy)
1✔
1291
        socket.socket = socks.socksocket
1✔
1292

1293
        return proxy
1✔
1294

1295
    @_validator
1✔
1296
    def noptrace(self, value):
1✔
1297
        """Disable all actions which rely on ptrace.
1298

1299
        This is useful for switching between local exploitation with a debugger,
1300
        and remote exploitation (without a debugger).
1301

1302
        This option can be set with the ``NOPTRACE`` command-line argument.
1303
        """
1304
        return bool(value)
×
1305

1306

1307
    @_validator
1✔
1308
    def adb_host(self, value):
1✔
1309
        """Sets the target host which is used for ADB.
1310

1311
        This is useful for Android exploitation.
1312

1313
        The default value is inherited from ANDROID_ADB_SERVER_HOST, or set
1314
        to the default 'localhost'.
1315
        """
1316
        return str(value)
×
1317

1318

1319
    @_validator
1✔
1320
    def adb_port(self, value):
1✔
1321
        """Sets the target port which is used for ADB.
1322

1323
        This is useful for Android exploitation.
1324

1325
        The default value is inherited from ANDROID_ADB_SERVER_PORT, or set
1326
        to the default 5037.
1327
        """
1328
        return int(value)
×
1329

1330
    @_validator
1✔
1331
    def device(self, device):
1✔
1332
        """Sets the device being operated on.
1333
        """
1334
        if isinstance(device, (bytes, six.text_type)):
×
1335
            device = Device(device)
×
1336
        if isinstance(device, Device):
×
1337
            self.arch = device.arch or self.arch
×
1338
            self.bits = device.bits or self.bits
×
1339
            self.endian = device.endian or self.endian
×
1340
            self.os = device.os or self.os
×
1341
        elif device is not None:
×
1342
            raise AttributeError("device must be either a Device object or a serial number as a string")
×
1343

1344
        return device
×
1345

1346
    @property
1✔
1347
    def adb(self):
1✔
1348
        """Returns an argument array for connecting to adb.
1349

1350
        Unless ``$ADB_PATH`` is set, uses the default ``adb`` binary in ``$PATH``.
1351
        """
1352
        ADB_PATH = os.environ.get('ADB_PATH', 'adb')
×
1353

1354
        command = [ADB_PATH]
×
1355

1356
        if self.adb_host != self.defaults['adb_host']:
×
1357
            command += ['-H', self.adb_host]
×
1358

1359
        if self.adb_port != self.defaults['adb_port']:
×
1360
            command += ['-P', str(self.adb_port)]
×
1361

1362
        if self.device:
×
1363
            command += ['-s', str(self.device)]
×
1364

1365
        return command
×
1366

1367
    @_validator
1✔
1368
    def buffer_size(self, size):
1✔
1369
        """Internal buffer size to use for :class:`pwnlib.tubes.tube.tube` objects.
1370

1371
        This is not the maximum size of the buffer, but this is the amount of data
1372
        which is passed to each raw ``read`` syscall (or equivalent).
1373
        """
1374
        return int(size)
1✔
1375

1376
    @_validator
1✔
1377
    def cache_dir_base(self, new_base):
1✔
1378
        """Base directory to use for caching content.
1379

1380
        Changing this to a different value will clear the :attr:`cache_dir` path
1381
        stored in TLS since a new path will need to be generated to respect the
1382
        new :attr:`cache_dir_base` value.
1383
        """
1384

1385
        if new_base != self.cache_dir_base:
×
1386
            del self._tls["cache_dir"]
×
1387
        if os.access(new_base, os.F_OK) and not os.access(new_base, os.W_OK):
×
1388
            raise OSError(errno.EPERM, "Cache base dir is not writable")
×
1389
        return new_base
×
1390

1391
    @property
1✔
1392
    def cache_dir(self):
1✔
1393
        """Directory used for caching data.
1394

1395
        Note:
1396
            May be either a path string, or :const:`None`.
1397
            Set to :const:`None` to disable caching.
1398
            Set to :const:`True` to generate the default cache directory path
1399
            based on :attr:`cache_dir_base` again.
1400

1401
        Example:
1402

1403
            >>> cache_dir = context.cache_dir
1404
            >>> cache_dir is not None
1405
            True
1406
            >>> os.chmod(cache_dir, 0o000)
1407
            >>> context.cache_dir = True
1408
            >>> context.cache_dir is None
1409
            True
1410
            >>> os.chmod(cache_dir, 0o755)
1411
            >>> cache_dir == context.cache_dir
1412
            True
1413
            >>> context.cache_dir = None
1414
            >>> context.cache_dir is None
1415
            True
1416
            >>> context.cache_dir = True
1417
            >>> context.cache_dir is not None
1418
            True
1419
        """
1420
        try:
1✔
1421
            # If the TLS already has a cache directory path, we return it
1422
            # without any futher checks since it must have been valid when it
1423
            # was set and if that has changed, hiding the TOCTOU here would be
1424
            # potentially confusing
1425
            return self._tls["cache_dir"]
1✔
1426
        except KeyError:
1✔
1427
            pass
1✔
1428

1429
        # Attempt to create a Python version specific cache dir and its parents
1430
        cache_dirname = '.pwntools-cache-%d.%d' % sys.version_info[:2]
1✔
1431
        cache_dirpath = os.path.join(self.cache_dir_base, cache_dirname)
1✔
1432
        try:
1✔
1433
            os.makedirs(cache_dirpath)
1✔
1434
        except OSError as exc:
1✔
1435
            # If we failed for any reason other than the cache directory
1436
            # already existing then we'll fall back to a temporary directory
1437
            # object which doesn't respect the `cache_dir_base`
1438
            if exc.errno != errno.EEXIST:
1!
1439
                try:
×
1440
                    cache_dirpath = tempfile.mkdtemp(prefix=".pwntools-tmp")
×
1441
                except IOError:
×
1442
                    # This implies no good candidates for temporary files so we
1443
                    # have to return `None`
1444
                    return None
×
1445
                else:
1446
                    # Ensure the temporary cache dir is cleaned up on exit. A
1447
                    # `TemporaryDirectory` would do this better upon garbage
1448
                    # collection but this is necessary for Python 2 support.
1449
                    atexit.register(shutil.rmtree, cache_dirpath)
×
1450
        # By this time we have a cache directory which exists but we don't know
1451
        # if it is actually writable. Some wargames e.g. pwnable.kr have
1452
        # created dummy directories which cannot be modified by the user
1453
        # account (owned by root).
1454
        if os.access(cache_dirpath, os.W_OK):
1✔
1455
            # Stash this in TLS for later reuse
1456
            self._tls["cache_dir"] = cache_dirpath
1✔
1457
            return cache_dirpath
1✔
1458
        else:
1459
            return None
1✔
1460

1461
    @cache_dir.setter
1✔
1462
    def cache_dir(self, v):
1✔
1463
        if v is True:
1✔
1464
            del self._tls["cache_dir"]
1✔
1465
        elif v is None or os.access(v, os.W_OK):
1!
1466
            # Stash this in TLS for later reuse
1467
            self._tls["cache_dir"] = v
1✔
1468

1469
    @_validator
1✔
1470
    def delete_corefiles(self, v):
1✔
1471
        """Whether pwntools automatically deletes corefiles after exiting.
1472
        This only affects corefiles accessed via :attr:`.process.corefile`.
1473

1474
        Default value is ``False``.
1475
        """
1476
        return bool(v)
×
1477

1478
    @_validator
1✔
1479
    def rename_corefiles(self, v):
1✔
1480
        """Whether pwntools automatically renames corefiles.
1481

1482
        This is useful for two things:
1483

1484
        - Prevent corefiles from being overwritten, if ``kernel.core_pattern``
1485
          is something simple like ``"core"``.
1486
        - Ensure corefiles are generated, if ``kernel.core_pattern`` uses ``apport``,
1487
          which refuses to overwrite any existing files.
1488

1489
        This only affects corefiles accessed via :attr:`.process.corefile`.
1490

1491
        Default value is ``True``.
1492
        """
1493
        return bool(v)
×
1494

1495
    @_validator
1✔
1496
    def newline(self, v):
1✔
1497
        """Line ending used for Tubes by default.
1498

1499
        This configures the newline emitted by e.g. ``sendline`` or that is used
1500
        as a delimiter for e.g. ``recvline``.
1501
        """
1502
        # circular imports
1503
        from pwnlib.util.packing import _need_bytes
1✔
1504
        return _need_bytes(v)
1✔
1505
    
1506
    @_validator
1✔
1507
    def throw_eof_on_incomplete_line(self, v):
1✔
1508
        """Whether to raise an :class:`EOFError` if an EOF is received before a newline in ``tube.recvline``.
1509

1510
        Controls if an :class:`EOFError` is treated as newline in ``tube.recvline`` and similar functions
1511
        and whether a warning should be logged about it.
1512

1513
        Possible values are:
1514

1515
        - ``True``: Raise an :class:`EOFError` if an EOF is received before a newline.
1516
        - ``False``: Return the data received so far if an EOF is received
1517
          before a newline without logging a warning.
1518
        - ``None``: Return the data received so far if an EOF is received
1519
          before a newline and log a warning.
1520

1521
        Default value is ``None``.
1522
        """
1523
        return v if v is None else bool(v)
×
1524

1525

1526
    @_validator
1✔
1527
    def gdbinit(self, value):
1✔
1528
        """Path to the gdbinit that is used when running GDB locally.
1529

1530
        This is useful if you want pwntools-launched GDB to include some additional modules,
1531
        like PEDA but you do not want to have GDB include them by default.
1532

1533
        The setting will only apply when GDB is launched locally since remote hosts may not have
1534
        the necessary requirements for the gdbinit.
1535

1536
        If set to an empty string, GDB will use the default `~/.gdbinit`.
1537

1538
        Default value is ``""``.
1539
        """
1540
        return str(value)
×
1541

1542
    @_validator
1✔
1543
    def cyclic_alphabet(self, alphabet):
1✔
1544
        """Cyclic alphabet.
1545

1546
        Default value is `string.ascii_lowercase`.
1547
        """
1548

1549
        # Do not allow multiple occurrences
1550
        if len(set(alphabet)) != len(alphabet):
1!
1551
            raise AttributeError("cyclic alphabet cannot contain duplicates")
×
1552

1553
        return alphabet.encode()
1✔
1554

1555
    @_validator
1✔
1556
    def cyclic_size(self, size):
1✔
1557
        """Cyclic pattern size.
1558

1559
        Default value is `4`.
1560
        """
1561
        size = int(size)
×
1562

1563
        if size > self.bytes:
×
1564
            raise AttributeError("cyclic pattern size cannot be larger than word size")
×
1565

1566
        return size
×
1567

1568
    @_validator
1✔
1569
    def ssh_session(self, shell):
1✔
1570
        from pwnlib.tubes.ssh import ssh
1✔
1571

1572
        if not isinstance(shell, ssh):
1!
1573
            raise AttributeError("context.ssh_session must be an ssh tube")
×
1574

1575
        return shell
1✔
1576

1577
    #*************************************************************************
1578
    #                               ALIASES
1579
    #*************************************************************************
1580
    #
1581
    # These fields are aliases for fields defined above, either for
1582
    # convenience or compatibility.
1583
    #
1584
    #*************************************************************************
1585

1586
    def __call__(self, **kwargs):
1✔
1587
        """
1588
        Alias for :meth:`pwnlib.context.ContextType.update`
1589
        """
1590
        return self.update(**kwargs)
1✔
1591

1592
    def reset_local(self):
1✔
1593
        """
1594
        Deprecated.  Use :meth:`clear`.
1595
        """
1596
        self.clear()
1✔
1597

1598
    @property
1✔
1599
    def endianness(self):
1✔
1600
        """
1601
        Legacy alias for :attr:`endian`.
1602

1603
        Examples:
1604

1605
            >>> context.endian == context.endianness
1606
            True
1607
        """
1608
        return self.endian
1✔
1609
    @endianness.setter
1✔
1610
    def endianness(self, value):
1✔
1611
        self.endian = value
1✔
1612

1613

1614
    @property
1✔
1615
    def sign(self):
1✔
1616
        """
1617
        Alias for :attr:`signed`
1618
        """
1619
        return self.signed
1✔
1620

1621
    @sign.setter
1✔
1622
    def sign(self, value):
1✔
1623
        self.signed = value
1✔
1624

1625
    @property
1✔
1626
    def signedness(self):
1✔
1627
        """
1628
        Alias for :attr:`signed`
1629
        """
1630
        return self.signed
×
1631

1632
    @signedness.setter
1✔
1633
    def signedness(self, value):
1✔
1634
        self.signed = value
×
1635

1636

1637
    @property
1✔
1638
    def word_size(self):
1✔
1639
        """
1640
        Alias for :attr:`bits`
1641
        """
1642
        return self.bits
1✔
1643

1644
    @word_size.setter
1✔
1645
    def word_size(self, value):
1✔
1646
        self.bits = value
1✔
1647

1648
    Thread = Thread
1✔
1649

1650

1651
#: Global :class:`.ContextType` object, used to store commonly-used pwntools settings.
1652
#:
1653
#: In most cases, the context is used to infer default variables values.
1654
#: For example, :func:`.asm` can take an ``arch`` parameter as a
1655
#: keyword argument.
1656
#:
1657
#: If it is not supplied, the ``arch`` specified by ``context`` is used instead.
1658
#:
1659
#: Consider it a shorthand to passing ``os=`` and ``arch=`` to every single
1660
#: function call.
1661
context = ContextType()
1✔
1662

1663
# Inherit default ADB values
1664
if 'ANDROID_ADB_SERVER_HOST' in os.environ:
1!
1665
    context.adb_host = os.environ.get('ANDROID_ADB_SERVER_HOST')
×
1666

1667
if 'ANDROID_ADB_SERVER_PORT' in os.environ:
1!
1668
    context.adb_port = int(os.getenv('ANDROID_ADB_SERVER_PORT'))
×
1669

1670
def LocalContext(function):
1✔
1671
    """
1672
    Wraps the specified function on a context.local() block, using kwargs.
1673

1674
    Example:
1675

1676
        >>> context.clear()
1677
        >>> @LocalContext
1678
        ... def printArch():
1679
        ...     print(context.arch)
1680
        >>> printArch()
1681
        i386
1682
        >>> printArch(arch='arm')
1683
        arm
1684
    """
1685
    @functools.wraps(function)
1✔
1686
    def setter(*a, **kw):
1✔
1687
        with context.local(**{k:kw.pop(k) for k,v in tuple(kw.items()) if isinstance(getattr(ContextType, k, None), property)}):
1✔
1688
            arch = context.arch
1✔
1689
            bits = context.bits
1✔
1690
            endian = context.endian
1✔
1691

1692
            # Prevent the user from doing silly things with invalid
1693
            # architecture / bits / endianness combinations.
1694
            if (arch == 'i386' and bits != 32) \
1!
1695
              or (arch == 'amd64' and bits != 64):
1696
                raise AttributeError("Invalid arch/bits combination: %s/%s" % (arch, bits))
×
1697

1698
            if arch in ('i386', 'amd64') and endian == 'big':
1!
1699
                raise AttributeError("Invalid arch/endianness combination: %s/%s" % (arch, endian))
×
1700

1701
            return function(*a, **kw)
1✔
1702
    return setter
1✔
1703

1704
def LocalNoarchContext(function):
1✔
1705
    """
1706
    Same as LocalContext, but resets arch to :const:`'none'` by default
1707

1708
    Example:
1709

1710
        >>> @LocalNoarchContext
1711
        ... def printArch():
1712
        ...     print(context.arch)
1713
        >>> printArch()
1714
        none
1715
    """
1716
    @functools.wraps(function)
1✔
1717
    def setter(*a, **kw):
1✔
1718
        kw.setdefault('arch', 'none')
1✔
1719
        with context.local(**{k:kw.pop(k) for k,v in tuple(kw.items()) if isinstance(getattr(ContextType, k, None), property)}):
1✔
1720
            return function(*a, **kw)
1✔
1721
    return setter
1✔
1722

1723
# Read configuration options from the context section
1724
def update_context_defaults(section):
1✔
1725
    # Circular imports FTW!
1726
    from pwnlib.util import safeeval
×
1727
    from pwnlib.log import getLogger
×
1728
    log = getLogger(__name__)
×
1729
    for key, value in section.items():
×
1730
        if key not in ContextType.defaults:
×
1731
            log.warn("Unknown configuration option %r in section %r" % (key, 'context'))
×
1732
            continue
×
1733

1734
        default = ContextType.defaults[key]
×
1735

1736
        if isinstance(default, six.string_types + six.integer_types + (tuple, list, dict)):
×
1737
            value = safeeval.expr(value)
×
1738
        else:
1739
            log.warn("Unsupported configuration option %r in section %r" % (key, 'context'))
×
1740

1741
        # Attempt to set the value, to see if it is value:
1742
        try:
×
1743
            with context.local(**{key: value}):
×
1744
                value = getattr(context, key)
×
1745
        except (ValueError, AttributeError) as e:
×
1746
            log.warn("Could not set context.%s=%s via pwn.conf (%s)", key, section[key], e)
×
1747
            continue
×
1748

1749
        ContextType.defaults[key] = value
×
1750

1751
register_config('context', update_context_defaults)
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

© 2026 Coveralls, Inc