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

Gallopsled / pwntools / 6995022811

26 Nov 2023 10:33AM UTC coverage: 71.199% (-3.3%) from 74.499%
6995022811

Pull #2233

github

web-flow
Merge 080a79802 into d10c70fb3
Pull Request #2233: Bugfix gdb.debug: exe parameter now respected

4330 of 7306 branches covered (0.0%)

49 of 68 new or added lines in 3 files covered. (72.06%)

571 existing lines in 9 files now uncovered.

12336 of 17326 relevant lines covered (71.2%)

0.71 hits per line

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

66.67
/pwnlib/util/misc.py
1
from __future__ import division
1✔
2

3
import base64
1✔
4
import errno
1✔
5
import os
1✔
6
import re
1✔
7
import signal
1✔
8
import six
1✔
9
import socket
1✔
10
import stat
1✔
11
import string
1✔
12
import subprocess
1✔
13
import sys
1✔
14
import tempfile
1✔
15
import inspect
1✔
16
import types
1✔
17

18
from pwnlib import atexit
1✔
19
from pwnlib.context import context
1✔
20
from pwnlib.log import getLogger
1✔
21
from pwnlib.util import fiddling
1✔
22
from pwnlib.util import lists
1✔
23
from pwnlib.util import packing
1✔
24

25
log = getLogger(__name__)
1✔
26

27
def align(alignment, x):
1✔
28
    """align(alignment, x) -> int
29

30
    Rounds `x` up to nearest multiple of the `alignment`.
31

32
    Example:
33
      >>> [align(5, n) for n in range(15)]
34
      [0, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 15, 15, 15, 15]
35
    """
36
    return x + -x % alignment
1✔
37

38

39
def align_down(alignment, x):
1✔
40
    """align_down(alignment, x) -> int
41

42
    Rounds `x` down to nearest multiple of the `alignment`.
43

44
    Example:
45
        >>> [align_down(5, n) for n in range(15)]
46
        [0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10]
47
    """
48
    return x - x % alignment
1✔
49

50

51
def binary_ip(host):
1✔
52
    """binary_ip(host) -> str
53

54
    Resolve host and return IP as four byte string.
55

56
    Example:
57
        >>> binary_ip("127.0.0.1")
58
        b'\\x7f\\x00\\x00\\x01'
59
    """
60
    return socket.inet_aton(socket.gethostbyname(host))
1✔
61

62

63
def size(n, abbrev = 'B', si = False):
1✔
64
    """size(n, abbrev = 'B', si = False) -> str
65

66
    Convert the length of a bytestream to human readable form.
67

68
    Arguments:
69
      n(int,iterable): The length to convert to human readable form,
70
        or an object which can have ``len()`` called on it.
71
      abbrev(str): String appended to the size, defaults to ``'B'``.
72

73
    Example:
74
        >>> size(451)
75
        '451B'
76
        >>> size(1000)
77
        '1000B'
78
        >>> size(1024)
79
        '1.00KB'
80
        >>> size(1024, ' bytes')
81
        '1.00K bytes'
82
        >>> size(1024, si = True)
83
        '1.02KB'
84
        >>> [size(1024 ** n) for n in range(7)]
85
        ['1B', '1.00KB', '1.00MB', '1.00GB', '1.00TB', '1.00PB', '1024.00PB']
86
        >>> size([])
87
        '0B'
88
        >>> size([1,2,3])
89
        '3B'
90
    """
91
    if hasattr(n, '__len__'):
1✔
92
        n = len(n)
1✔
93

94
    base = 1000.0 if si else 1024.0
1✔
95
    if n < base:
1✔
96
        return '%d%s' % (n, abbrev)
1✔
97

98
    for suffix in ['K', 'M', 'G', 'T']:
1✔
99
        n /= base
1✔
100
        if n < base:
1✔
101
            return '%.02f%s%s' % (n, suffix, abbrev)
1✔
102

103
    return '%.02fP%s' % (n / base, abbrev)
1✔
104

105
KB = 1000
1✔
106
MB = 1000 * KB
1✔
107
GB = 1000 * MB
1✔
108

109
KiB = 1024
1✔
110
MiB = 1024 * KiB
1✔
111
GiB = 1024 * MiB
1✔
112

113
def read(path, count=-1, skip=0):
1✔
114
    r"""read(path, count=-1, skip=0) -> str
115

116
    Open file, return content.
117

118
    Examples:
119
        >>> read('/proc/self/exe')[:4]
120
        b'\x7fELF'
121
    """
122
    path = os.path.expanduser(os.path.expandvars(path))
1✔
123
    with open(path, 'rb') as fd:
1✔
124
        if skip:
1!
125
            fd.seek(skip)
×
126
        return fd.read(count)
1✔
127

128

129
def write(path, data = b'', create_dir = False, mode = 'w'):
1✔
130
    """Create new file or truncate existing to zero length and write data."""
131
    path = os.path.expanduser(os.path.expandvars(path))
1✔
132
    if create_dir:
1!
133
        path = os.path.realpath(path)
×
134
        mkdir_p(os.path.dirname(path))
×
135
    if mode == 'w' and isinstance(data, bytes): mode += 'b'
1✔
136
    with open(path, mode) as f:
1✔
137
        f.write(data)
1✔
138

139
def which(name, all = False, path=None):
1✔
140
    """which(name, flags = os.X_OK, all = False) -> str or str set
141

142
    Works as the system command ``which``; searches $PATH for ``name`` and
143
    returns a full path if found.
144

145
    If `all` is :const:`True` the set of all found locations is returned, else
146
    the first occurrence or :const:`None` is returned.
147

148
    Arguments:
149
      `name` (str): The file to search for.
150
      `all` (bool):  Whether to return all locations where `name` was found.
151

152
    Returns:
153
      If `all` is :const:`True` the set of all locations where `name` was found,
154
      else the first location or :const:`None` if not found.
155

156
    Example:
157

158
        >>> which('sh') # doctest: +ELLIPSIS
159
        '.../bin/sh'
160
    """
161
    # If name is a path, do not attempt to resolve it.
162
    if os.path.sep in name:
1✔
163
        return name
1✔
164

165
    isroot = os.getuid() == 0
1✔
166
    out = set()
1✔
167
    try:
1✔
168
        path = path or os.environ['PATH']
1✔
169
    except KeyError:
×
170
        log.exception('Environment variable $PATH is not set')
×
171
    for p in path.split(os.pathsep):
1✔
172
        p = os.path.join(p, name)
1✔
173
        if os.access(p, os.X_OK):
1✔
174
            st = os.stat(p)
1✔
175
            if not stat.S_ISREG(st.st_mode):
1!
176
                continue
×
177
            # work around this issue: https://bugs.python.org/issue9311
178
            if isroot and not \
1!
179
              st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
180
                continue
×
181
            if all:
1!
182
                out.add(p)
×
183
            else:
184
                return p
1✔
185
    if all:
1!
186
        return out
×
187
    else:
188
        return None
1✔
189

190

191
def normalize_argv_env(argv, env, log, level=2):
1✔
192
    #
193
    # Validate argv
194
    #
195
    # - Must be a list/tuple of strings
196
    # - Each string must not contain '\x00'
197
    #
198
    argv = argv or []
1✔
199
    if isinstance(argv, (six.text_type, six.binary_type)):
1✔
200
        argv = [argv]
1✔
201

202
    if not isinstance(argv, (list, tuple)):
1!
203
        log.error('argv must be a list or tuple: %r' % argv)
×
204

205
    if not all(isinstance(arg, (six.text_type, bytes, bytearray)) for arg in argv):
1!
206
        log.error("argv must be strings or bytes: %r" % argv)
×
207

208
    # Create a duplicate so we can modify it
209
    argv = list(argv)
1✔
210

211
    for i, oarg in enumerate(argv):
1✔
212
        arg = packing._need_bytes(oarg, level, 0x80)  # ASCII text is okay
1✔
213
        if b'\x00' in arg[:-1]:
1!
214
            log.error('Inappropriate nulls in argv[%i]: %r' % (i, oarg))
×
215
        argv[i] = bytearray(arg.rstrip(b'\x00'))
1✔
216

217
    #
218
    # Validate environment
219
    #
220
    # - Must be a dictionary of {string:string}
221
    # - No strings may contain '\x00'
222
    #
223

224
    # Create a duplicate so we can modify it safely
225
    env2 = []
1✔
226
    if hasattr(env, 'items'):
1✔
227
        env_items = env.items()
1✔
228
    else:
229
        env_items = env
1✔
230
    if env:
1✔
231
        for k,v in env_items:
1✔
232
            if not isinstance(k, (bytes, six.text_type)):
1!
233
                log.error('Environment keys must be strings: %r' % k)
×
234
            # Check if = is in the key, Required check since we sometimes call ctypes.execve directly
235
            # https://github.com/python/cpython/blob/025995feadaeebeef5d808f2564f0fd65b704ea5/Modules/posixmodule.c#L6476
236
            if b'=' in packing._encode(k):
1!
237
                log.error('Environment keys may not contain "=": %r' % (k))
×
238
            if not isinstance(v, (bytes, six.text_type)):
1!
239
                log.error('Environment values must be strings: %r=%r' % (k,v))
×
240
            k = packing._need_bytes(k, level, 0x80)  # ASCII text is okay
1✔
241
            v = packing._need_bytes(v, level, 0x80)  # ASCII text is okay
1✔
242
            if b'\x00' in k[:-1]:
1!
243
                log.error('Inappropriate nulls in env key: %r' % (k))
×
244
            if b'\x00' in v[:-1]:
1!
245
                log.error('Inappropriate nulls in env value: %r=%r' % (k, v))
×
246
            env2.append((bytearray(k.rstrip(b'\x00')), bytearray(v.rstrip(b'\x00'))))
1✔
247

248
    return argv, env2 or env
1✔
249

250

251
def run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, preexec_fn=None):
1✔
252
    """run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, preexec_fn=None) -> int
253

254
    Run a command in a new terminal.
255

256
    When ``terminal`` is not set:
257
        - If ``context.terminal`` is set it will be used.
258
          If it is an iterable then ``context.terminal[1:]`` are default arguments.
259
        - If a ``pwntools-terminal`` command exists in ``$PATH``, it is used
260
        - If tmux is detected (by the presence of the ``$TMUX`` environment
261
          variable), a new pane will be opened.
262
        - If GNU Screen is detected (by the presence of the ``$STY`` environment
263
          variable), a new screen will be opened.
264
        - If ``$TERM_PROGRAM`` is set, that is used.
265
        - If X11 is detected (by the presence of the ``$DISPLAY`` environment
266
          variable), ``x-terminal-emulator`` is used.
267
        - If KDE Konsole is detected (by the presence of the ``$KONSOLE_VERSION``
268
          environment variable), a terminal will be split.
269
        - If WSL (Windows Subsystem for Linux) is detected (by the presence of
270
          a ``wsl.exe`` binary in the ``$PATH`` and ``/proc/sys/kernel/osrelease``
271
          containing ``Microsoft``), a new ``cmd.exe`` window will be opened.
272

273
    If `kill_at_exit` is :const:`True`, try to close the command/terminal when the
274
    current process exits. This may not work for all terminal types.
275

276
    Arguments:
277
        command (str): The command to run.
278
        terminal (str): Which terminal to use.
279
        args (list): Arguments to pass to the terminal
280
        kill_at_exit (bool): Whether to close the command/terminal on process exit.
281
        preexec_fn (callable): Callable to invoke before exec().
282

283
    Note:
284
        The command is opened with ``/dev/null`` for stdin, stdout, stderr.
285

286
    Returns:
287
      PID of the new terminal process
288
    """
289
    if not terminal:
1!
290
        if context.terminal:
1!
291
            terminal = context.terminal[0]
1✔
292
            args     = context.terminal[1:]
1✔
293
        elif which('pwntools-terminal'):
×
294
            terminal = 'pwntools-terminal'
×
295
            args     = []
×
296
        elif 'TMUX' in os.environ and which('tmux'):
×
297
            terminal = 'tmux'
×
298
            args     = ['splitw']
×
299
        elif 'STY' in os.environ and which('screen'):
×
300
            terminal = 'screen'
×
301
            args     = ['-t','pwntools-gdb','bash','-c']
×
302
        elif 'TERM_PROGRAM' in os.environ:
×
303
            terminal = os.environ['TERM_PROGRAM']
×
304
            args     = []
×
305
        elif 'DISPLAY' in os.environ and which('x-terminal-emulator'):
×
306
            terminal = 'x-terminal-emulator'
×
307
            args     = ['-e']
×
308
        elif 'KONSOLE_VERSION' in os.environ and which('qdbus'):
×
309
            qdbus = which('qdbus')
×
310
            window_id = os.environ['WINDOWID']
×
311
            konsole_dbus_service = os.environ['KONSOLE_DBUS_SERVICE']
×
312

313
            with subprocess.Popen((qdbus, konsole_dbus_service), stdout=subprocess.PIPE) as proc:
×
314
                lines = proc.communicate()[0].decode().split('\n')
×
315

316
            # Iterate over all MainWindows
317
            for line in lines:
×
318
                parts = line.split('/')
×
319
                if len(parts) == 3 and parts[2].startswith('MainWindow_'):
×
320
                    name = parts[2]
×
321
                    with subprocess.Popen((qdbus, konsole_dbus_service, '/konsole/' + name,
×
322
                                           'org.kde.KMainWindow.winId'), stdout=subprocess.PIPE) as proc:
323
                        target_window_id = proc.communicate()[0].decode().strip()
×
324
                        if target_window_id == window_id:
×
325
                            break
×
326
            else:
327
                log.error('MainWindow not found')
×
328

329
            # Split
330
            subprocess.run((qdbus, konsole_dbus_service, '/konsole/' + name,
×
331
                            'org.kde.KMainWindow.activateAction', 'split-view-left-right'), stdout=subprocess.DEVNULL)
332

333
            # Find new session
334
            with subprocess.Popen((qdbus, konsole_dbus_service, os.environ['KONSOLE_DBUS_WINDOW'],
×
335
                                   'org.kde.konsole.Window.sessionList'), stdout=subprocess.PIPE) as proc:
336
                session_list = map(int, proc.communicate()[0].decode().split())
×
337
            last_konsole_session = max(session_list)
×
338

339
            terminal = 'qdbus'
×
340
            args = [konsole_dbus_service, '/Sessions/{}'.format(last_konsole_session),
×
341
                    'org.kde.konsole.Session.runCommand']
342

343
        else:
344
            is_wsl = False
×
345
            if os.path.exists('/proc/sys/kernel/osrelease'):
×
346
                with open('/proc/sys/kernel/osrelease', 'rb') as f:
×
347
                    is_wsl = b'icrosoft' in f.read()
×
348
            if is_wsl and which('cmd.exe') and which('wsl.exe') and which('bash.exe'):
×
349
                terminal    = 'cmd.exe'
×
350
                args        = ['/c', 'start']
×
351
                distro_name = os.getenv('WSL_DISTRO_NAME')
×
352

353
                # Split pane in Windows Terminal
354
                if 'WT_SESSION' in os.environ and which('wt.exe'):
×
355
                    args.extend(['wt.exe', '-w', '0', 'split-pane', '-d', '.'])
×
356

357
                if distro_name:
×
358
                    args.extend(['wsl.exe', '-d', distro_name, 'bash', '-c'])
×
359
                else:
360
                    args.extend(['bash.exe', '-c'])
×
361
                
362

363
    if not terminal:
1!
364
        log.error('Could not find a terminal binary to use. Set context.terminal to your terminal.')
×
365
    elif not which(terminal):
1!
366
        log.error('Could not find terminal binary %r. Set context.terminal to your terminal.' % terminal)
×
367

368
    if isinstance(args, tuple):
1!
369
        args = list(args)
×
370

371
    # When not specifying context.terminal explicitly, we used to set these flags above.
372
    # However, if specifying terminal=['tmux', 'splitw', '-h'], we would be lacking these flags.
373
    # Instead, set them here and hope for the best.
374
    if terminal == 'tmux':
1!
375
        args += ['-F' '#{pane_pid}', '-P']
×
376

377
    argv = [which(terminal)] + args
1✔
378

379
    if isinstance(command, six.string_types):
1!
380
        if ';' in command:
×
381
            log.error("Cannot use commands with semicolon.  Create a script and invoke that directly.")
×
382
        argv += [command]
×
383
    elif isinstance(command, (list, tuple)):
1!
384
        # Dump the full command line to a temporary file so we can be sure that
385
        # it is parsed correctly, and we do not need to account for shell expansion
386
        script = '''
1✔
387
#!{executable!s}
388
import os
389
os.execve({argv0!r}, {argv!r}, os.environ)
390
'''
391
        script = script.format(executable='/bin/env ' * (' ' in sys.executable) + sys.executable,
1✔
392
                               argv=command,
393
                               argv0=which(command[0]))
394
        script = script.lstrip()
1✔
395

396
        log.debug("Created script for new terminal:\n%s" % script)
1✔
397

398
        with tempfile.NamedTemporaryFile(delete=False, mode='wt+') as tmp:
1✔
399
          tmp.write(script)
1✔
400
          tmp.flush()
1✔
401
          os.chmod(tmp.name, 0o700)
1✔
402
          argv += [tmp.name]
1✔
403

404

405
    log.debug("Launching a new terminal: %r" % argv)
1✔
406

407
    stdin = stdout = stderr = open(os.devnull, 'r+b')
1✔
408
    if terminal == 'tmux':
1!
409
        stdout = subprocess.PIPE
×
410

411
    p = subprocess.Popen(argv, stdin=stdin, stdout=stdout, stderr=stderr, preexec_fn=preexec_fn)
1✔
412

413
    if terminal == 'tmux':
1!
414
        out, _ = p.communicate()
×
415
        pid = int(out)
×
416
    elif terminal == 'qdbus':
1!
417
        with subprocess.Popen((qdbus, konsole_dbus_service, '/Sessions/{}'.format(last_konsole_session),
×
418
                               'org.kde.konsole.Session.processId'), stdout=subprocess.PIPE) as proc:
419
            pid = int(proc.communicate()[0].decode())
×
420
    else:
421
        pid = p.pid
1✔
422

423
    if kill_at_exit:
1!
424
        def kill():
1✔
425
            try:
×
426
                if terminal == 'qdbus':
×
427
                    os.kill(pid, signal.SIGHUP)
×
428
                else:
429
                    os.kill(pid, signal.SIGTERM)
×
430
            except OSError:
×
431
                pass
×
432

433
        atexit.register(kill)
1✔
434

435
    return pid
1✔
436

437
def parse_ldd_output(output):
1✔
438
    """Parses the output from a run of 'ldd' on a binary.
439
    Returns a dictionary of {path: address} for
440
    each library required by the specified binary.
441

442
    Arguments:
443
      output(str): The output to parse
444

445
    Example:
446
        >>> sorted(parse_ldd_output('''
447
        ...     linux-vdso.so.1 =>  (0x00007fffbf5fe000)
448
        ...     libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007fe28117f000)
449
        ...     libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fe280f7b000)
450
        ...     libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe280bb4000)
451
        ...     /lib64/ld-linux-x86-64.so.2 (0x00007fe2813dd000)
452
        ... ''').keys())
453
        ['/lib/x86_64-linux-gnu/libc.so.6', '/lib/x86_64-linux-gnu/libdl.so.2', '/lib/x86_64-linux-gnu/libtinfo.so.5', '/lib64/ld-linux-x86-64.so.2']
454
    """
455
    expr_linux   = re.compile(r'\s(?P<lib>\S?/\S+)\s+\((?P<addr>0x.+)\)')
1✔
456
    expr_openbsd = re.compile(r'^\s+(?P<addr>[0-9a-f]+)\s+[0-9a-f]+\s+\S+\s+[01]\s+[0-9]+\s+[0-9]+\s+(?P<lib>\S+)$')
1✔
457
    libs = {}
1✔
458

459
    for s in output.split('\n'):
1✔
460
        match = expr_linux.search(s) or expr_openbsd.search(s)
1✔
461
        if not match:
1✔
462
            continue
1✔
463
        lib, addr = match.group('lib'), match.group('addr')
1✔
464
        libs[lib] = int(addr, 16)
1✔
465

466
    return libs
1✔
467

468
def mkdir_p(path):
1✔
469
    """Emulates the behavior of ``mkdir -p``."""
470

471
    try:
1✔
472
        os.makedirs(path)
1✔
473
    except OSError as exc:
1✔
474
        if exc.errno == errno.EEXIST and os.path.isdir(path):
1!
475
            pass
1✔
476
        else:
477
            raise
×
478

479
def dealarm_shell(tube):
1✔
480
    """Given a tube which is a shell, dealarm it.
481
    """
482
    tube.clean()
×
483

484
    tube.sendline('which python || echo')
×
485
    if tube.recvline().startswith('/'):
×
486
        tube.sendline('''exec python -c "import signal, os; signal.alarm(0); os.execl('$SHELL','')"''')
×
487
        return tube
×
488

489
    tube.sendline('which perl || echo')
×
490
    if tube.recvline().startswith('/'):
×
491
        tube.sendline('''exec perl -e "alarm 0; exec '${SHELL:-/bin/sh}'"''')
×
492
        return tube
×
493

494
    return None
×
495

496
def register_sizes(regs, in_sizes):
1✔
497
    """Create dictionaries over register sizes and relations
498

499
    Given a list of lists of overlapping register names (e.g. ['eax','ax','al','ah']) and a list of input sizes,
500
    it returns the following:
501

502
    * all_regs    : list of all valid registers
503
    * sizes[reg]  : the size of reg in bits
504
    * bigger[reg] : list of overlapping registers bigger than reg
505
    * smaller[reg]: list of overlapping registers smaller than reg
506

507
    Used in i386/AMD64 shellcode, e.g. the mov-shellcode.
508

509
    Example:
510
        >>> regs = [['eax', 'ax', 'al', 'ah'],['ebx', 'bx', 'bl', 'bh'],
511
        ... ['ecx', 'cx', 'cl', 'ch'],
512
        ... ['edx', 'dx', 'dl', 'dh'],
513
        ... ['edi', 'di'],
514
        ... ['esi', 'si'],
515
        ... ['ebp', 'bp'],
516
        ... ['esp', 'sp'],
517
        ... ]
518
        >>> all_regs, sizes, bigger, smaller = register_sizes(regs, [32, 16, 8, 8])
519
        >>> all_regs
520
        ['eax', 'ax', 'al', 'ah', 'ebx', 'bx', 'bl', 'bh', 'ecx', 'cx', 'cl', 'ch', 'edx', 'dx', 'dl', 'dh', 'edi', 'di', 'esi', 'si', 'ebp', 'bp', 'esp', 'sp']
521
        >>> pprint(sizes)
522
        {'ah': 8,
523
         'al': 8,
524
         'ax': 16,
525
         'bh': 8,
526
         'bl': 8,
527
         'bp': 16,
528
         'bx': 16,
529
         'ch': 8,
530
         'cl': 8,
531
         'cx': 16,
532
         'dh': 8,
533
         'di': 16,
534
         'dl': 8,
535
         'dx': 16,
536
         'eax': 32,
537
         'ebp': 32,
538
         'ebx': 32,
539
         'ecx': 32,
540
         'edi': 32,
541
         'edx': 32,
542
         'esi': 32,
543
         'esp': 32,
544
         'si': 16,
545
         'sp': 16}
546
        >>> pprint(bigger)
547
        {'ah': ['eax', 'ax', 'ah'],
548
         'al': ['eax', 'ax', 'al'],
549
         'ax': ['eax', 'ax'],
550
         'bh': ['ebx', 'bx', 'bh'],
551
         'bl': ['ebx', 'bx', 'bl'],
552
         'bp': ['ebp', 'bp'],
553
         'bx': ['ebx', 'bx'],
554
         'ch': ['ecx', 'cx', 'ch'],
555
         'cl': ['ecx', 'cx', 'cl'],
556
         'cx': ['ecx', 'cx'],
557
         'dh': ['edx', 'dx', 'dh'],
558
         'di': ['edi', 'di'],
559
         'dl': ['edx', 'dx', 'dl'],
560
         'dx': ['edx', 'dx'],
561
         'eax': ['eax'],
562
         'ebp': ['ebp'],
563
         'ebx': ['ebx'],
564
         'ecx': ['ecx'],
565
         'edi': ['edi'],
566
         'edx': ['edx'],
567
         'esi': ['esi'],
568
         'esp': ['esp'],
569
         'si': ['esi', 'si'],
570
         'sp': ['esp', 'sp']}
571
        >>> pprint(smaller)
572
        {'ah': [],
573
         'al': [],
574
         'ax': ['al', 'ah'],
575
         'bh': [],
576
         'bl': [],
577
         'bp': [],
578
         'bx': ['bl', 'bh'],
579
         'ch': [],
580
         'cl': [],
581
         'cx': ['cl', 'ch'],
582
         'dh': [],
583
         'di': [],
584
         'dl': [],
585
         'dx': ['dl', 'dh'],
586
         'eax': ['ax', 'al', 'ah'],
587
         'ebp': ['bp'],
588
         'ebx': ['bx', 'bl', 'bh'],
589
         'ecx': ['cx', 'cl', 'ch'],
590
         'edi': ['di'],
591
         'edx': ['dx', 'dl', 'dh'],
592
         'esi': ['si'],
593
         'esp': ['sp'],
594
         'si': [],
595
         'sp': []}
596
    """
597
    sizes = {}
1✔
598
    bigger = {}
1✔
599
    smaller = {}
1✔
600

601
    for l in regs:
1✔
602
        for r, s in zip(l, in_sizes):
1✔
603
            sizes[r] = s
1✔
604

605
        for r in l:
1✔
606
            bigger[r] = [r_ for r_ in l if sizes[r_] > sizes[r] or r == r_]
1✔
607
            smaller[r] = [r_ for r_ in l if sizes[r_] < sizes[r]]
1✔
608

609
    return lists.concat(regs), sizes, bigger, smaller
1✔
610

611

612
def python_2_bytes_compatible(klass):
1✔
613
    """
614
    A class decorator that defines __str__ methods under Python 2.
615
    Under Python 3 it does nothing.
616
    """
617
    if six.PY2:
1✔
618
        if '__str__' not in klass.__dict__:
1!
619
            klass.__str__ = klass.__bytes__
1✔
620
    return klass
1✔
621

622

623

624
def create_execve_script(argv=None, executable=None, cwd=None, env=None,
1✔
625
        stdin=0, stdout=1, stderr=2, preexec_fn=None, preexec_args=(), aslr=None, setuid=None,
626
        shell=False):
627
    """
628
    Creates a python wrapper script that triggers the syscall `execve` directly.
629

630
    Arguments:
631
        argv(list):
632
            List of arguments to pass into the process
633
        executable(str):
634
            Path to the executable to run.
635
            If :const:`None`, ``argv[0]`` is used.
636
        cwd(str):
637
            Working directory.  If :const:`None`, uses the working directory specified
638
            on :attr:`cwd` or set via :meth:`set_working_directory`.
639
        env(dict):
640
            Environment variables to set in the child.  If :const:`None`, inherits the
641
            default environment.
642
        timeout(int):
643
            Timeout to set on the `tube` created to interact with the process.
644
        run(bool):
645
            Set to :const:`True` to run the program (default).
646
            If :const:`False`, returns the path to an executable Python script on the
647
            remote server which, when executed, will do it.
648
        stdin(int, str):
649
            If an integer, replace stdin with the numbered file descriptor.
650
            If a string, a open a file with the specified path and replace
651
            stdin with its file descriptor.  May also be one of ``sys.stdin``,
652
            ``sys.stdout``, ``sys.stderr``.  If :const:`None`, the file descriptor is closed.
653
        stdout(int, str):
654
            See ``stdin``.
655
        stderr(int, str):
656
            See ``stdin``.
657
        preexec_fn(callable):
658
            Function which is executed on the remote side before execve().
659
            This **MUST** be a self-contained function -- it must perform
660
            all of its own imports, and cannot refer to variables outside
661
            its scope.
662
        preexec_args(object):
663
            Argument passed to ``preexec_fn``.
664
            This **MUST** only consist of native Python objects.
665
        aslr(bool):
666
            See :class:`pwnlib.tubes.process.process` for more information.
667
        setuid(bool):
668
            See :class:`pwnlib.tubes.process.process` for more information.
669
        shell(bool):
670
            Pass the command-line arguments to the shell.
671

672
    Returns:
673
    """
674
    if not argv and not executable:
1!
NEW
675
        log.error("Must specify argv or executable")
×
676

677
    aslr      = aslr if aslr is not None else context.aslr
1✔
678

679
    argv, env = normalize_argv_env(argv, env, log)
1✔
680

681
    if shell:
1✔
682
        if len(argv) != 1:
1!
NEW
683
            log.error('Cannot provide more than 1 argument if shell=True')
×
684
        argv = [bytearray(b'/bin/sh'), bytearray(b'-c')] + argv
1✔
685

686
    executable = executable or argv[0]
1✔
687
    cwd        = cwd or '.'
1✔
688

689
    # Validate, since failures on the remote side will suck.
690
    if not isinstance(executable, (six.text_type, six.binary_type, bytearray)):
1!
NEW
691
        log.error("executable / argv[0] must be a string: %r" % executable)
×
692
    executable = bytearray(packing._need_bytes(executable, min_wrong=0x80))
1✔
693

694
    # Allow passing in sys.stdin/stdout/stderr objects
695
    handles = {sys.stdin: 0, sys.stdout:1, sys.stderr:2}
1✔
696
    stdin  = handles.get(stdin, stdin)
1✔
697
    stdout = handles.get(stdout, stdout)
1✔
698
    stderr = handles.get(stderr, stderr)
1✔
699

700
    # Allow the user to provide a self-contained function to run
701
    def func(): pass
1!
702
    func      = preexec_fn or func
1✔
703
    func_args = preexec_args
1✔
704

705
    if not isinstance(func, types.FunctionType):
1✔
706
        log.error("preexec_fn must be a function")
1✔
707

708
    func_name = func.__name__
1✔
709
    if func_name == (lambda: 0).__name__:
1!
710
        log.error("preexec_fn cannot be a lambda")
1✔
711

712
    func_src  = inspect.getsource(func).strip()
1✔
713
    setuid = True if setuid is None else bool(setuid)
1✔
714

715

716
    script = r"""
1✔
717
#!/usr/bin/env python
718
import os, sys, ctypes, resource, platform, stat
719
from collections import OrderedDict
720
try:
721
    integer_types = int, long
722
except NameError:
723
    integer_types = int,
724
exe   = bytes(%(executable)r)
725
argv  = [bytes(a) for a in %(argv)r]
726
env   = %(env)r
727

728
os.chdir(%(cwd)r)
729

730
environ = getattr(os, 'environb', os.environ)
731

732
if env is not None:
733
    env = OrderedDict((bytes(k), bytes(v)) for k,v in env)
734
    os.environ.clear()
735
    environ.update(env)
736
else:
737
    env = environ
738

739
def is_exe(path):
740
    return os.path.isfile(path) and os.access(path, os.X_OK)
741

742
PATH = environ.get(b'PATH',b'').split(os.pathsep.encode())
743

744
if os.path.sep.encode() not in exe and not is_exe(exe):
745
    for path in PATH:
746
        test_path = os.path.join(path, exe)
747
        if is_exe(test_path):
748
            exe = test_path
749
            break
750

751
if not is_exe(exe):
752
    sys.stderr.write('3\n')
753
    sys.stderr.write("{!r} is not executable or does not exist in $PATH: {!r}".format(exe,PATH))
754
    sys.exit(-1)
755

756
if not %(setuid)r:
757
    PR_SET_NO_NEW_PRIVS = 38
758
    result = ctypes.CDLL('libc.so.6').prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
759

760
    if result != 0:
761
        sys.stdout.write('3\n')
762
        sys.stdout.write("Could not disable setuid: prctl(PR_SET_NO_NEW_PRIVS) failed")
763
        sys.exit(-1)
764

765
try:
766
    PR_SET_PTRACER = 0x59616d61
767
    PR_SET_PTRACER_ANY = -1
768
    ctypes.CDLL('libc.so.6').prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0)
769
except Exception:
770
    pass
771

772
# Determine what UID the process will execute as
773
# This is used for locating apport core dumps
774
suid = os.getuid()
775
sgid = os.getgid()
776
st = os.stat(exe)
777
if %(setuid)r:
778
    if (st.st_mode & stat.S_ISUID):
779
        suid = st.st_uid
780
    if (st.st_mode & stat.S_ISGID):
781
        sgid = st.st_gid
782

783
if sys.argv[-1] == 'check':
784
    sys.stdout.write("1\n")
785
    sys.stdout.write(str(os.getpid()) + "\n")
786
    sys.stdout.write(str(os.getuid()) + "\n")
787
    sys.stdout.write(str(os.getgid()) + "\n")
788
    sys.stdout.write(str(suid) + "\n")
789
    sys.stdout.write(str(sgid) + "\n")
790
    getattr(sys.stdout, 'buffer', sys.stdout).write(os.path.realpath(exe) + b'\x00')
791
    sys.stdout.flush()
792

793
for fd, newfd in {0: %(stdin)r, 1: %(stdout)r, 2:%(stderr)r}.items():
794
    if newfd is None:
795
        os.close(fd)
796
    elif isinstance(newfd, (str, bytes)):
797
        newfd = os.open(newfd, os.O_RDONLY if fd == 0 else (os.O_RDWR|os.O_CREAT))
798
        os.dup2(newfd, fd)
799
        os.close(newfd)
800
    elif isinstance(newfd, integer_types) and newfd != fd:
801
        os.dup2(fd, newfd)
802

803
if not %(aslr)r:
804
    if platform.system().lower() == 'linux' and %(setuid)r is not True:
805
        ADDR_NO_RANDOMIZE = 0x0040000
806
        ctypes.CDLL('libc.so.6').personality(ADDR_NO_RANDOMIZE)
807

808
    resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))
809

810
# Attempt to dump ALL core file regions
811
try:
812
    with open('/proc/self/coredump_filter', 'w') as core_filter:
813
        core_filter.write('0x3f\n')
814
except Exception:
815
    pass
816

817
# Assume that the user would prefer to have core dumps.
818
try:
819
    resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
820
except Exception:
821
    pass
822

823
%(func_src)s
824
%(func_name)s(*%(func_args)r)
825

826
""" % locals()  
827

828
    if len(argv) > 0 and len(argv[0]) > 0:
1✔
829
        script += r"os.execve(exe, argv, env) " 
1✔
830

831
    # os.execve does not allow us to pass empty argv[0]
832
    # Therefore we use ctypes to call execve directly
833
    else:
834
        script += r"""
1✔
835
# Transform envp from dict to list
836
env_list = [key + b"=" + value for key, value in env.items()]
837

838
# ctypes helper to convert a python list to a NULL-terminated C array
839
def to_carray(py_list):
840
    py_list += [None] # NULL-terminated
841
    return (ctypes.c_char_p * len(py_list))(*py_list)
842

843
c_argv = to_carray(argv)
844
c_env = to_carray(env_list)
845

846
# Call execve
847
libc = ctypes.CDLL('libc.so.6')
848
libc.execve(exe, c_argv, c_env)
849

850
# We should never get here, since we sanitized argv and env,
851
# but just in case, indicate that something went wrong.
852
libc.perror(b"execve")
853
raise OSError("execve failed")
854
""" % locals()
855
    script = script.strip()
1✔
856

857
    return script
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