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

Gallopsled / pwntools / 11532290288

26 Oct 2024 01:42PM UTC coverage: 73.702% (-0.04%) from 73.738%
11532290288

push

github

peace-maker
Fix gzip compression on Python 2

3793 of 6394 branches covered (59.32%)

7 of 7 new or added lines in 1 file covered. (100.0%)

385 existing lines in 13 files now uncovered.

13304 of 18051 relevant lines covered (73.7%)

0.74 hits per line

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

60.34
/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 time
1✔
17
import types
1✔
18

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

27
log = getLogger(__name__)
1✔
28

29
def align(alignment, x):
1✔
30
    """align(alignment, x) -> int
31

32
    Rounds `x` up to nearest multiple of the `alignment`.
33

34
    Example:
35

36
      >>> [align(5, n) for n in range(15)]
37
      [0, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 15, 15, 15, 15]
38
    """
39
    return x + -x % alignment
1✔
40

41

42
def align_down(alignment, x):
1✔
43
    """align_down(alignment, x) -> int
44

45
    Rounds `x` down to nearest multiple of the `alignment`.
46

47
    Example:
48

49
        >>> [align_down(5, n) for n in range(15)]
50
        [0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10]
51
    """
52
    return x - x % alignment
1✔
53

54

55
def binary_ip(host):
1✔
56
    """binary_ip(host) -> str
57

58
    Resolve host and return IP as four byte string.
59

60
    Example:
61

62
        >>> binary_ip("127.0.0.1")
63
        b'\\x7f\\x00\\x00\\x01'
64
    """
65
    return socket.inet_aton(socket.gethostbyname(host))
1✔
66

67

68
def size(n, abbrev = 'B', si = False):
1✔
69
    """size(n, abbrev = 'B', si = False) -> str
70

71
    Convert the length of a bytestream to human readable form.
72

73
    Arguments:
74
      n(int,iterable): The length to convert to human readable form,
75
        or an object which can have ``len()`` called on it.
76
      abbrev(str): String appended to the size, defaults to ``'B'``.
77

78
    Example:
79

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

100
    base = 1000.0 if si else 1024.0
1✔
101
    if n < base:
1✔
102
        return '%d%s' % (n, abbrev)
1✔
103

104
    for suffix in ['K', 'M', 'G', 'T']:
1✔
105
        n /= base
1✔
106
        if n < base:
1✔
107
            return '%.02f%s%s' % (n, suffix, abbrev)
1✔
108

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

111
KB = 1000
1✔
112
MB = 1000 * KB
1✔
113
GB = 1000 * MB
1✔
114

115
KiB = 1024
1✔
116
MiB = 1024 * KiB
1✔
117
GiB = 1024 * MiB
1✔
118

119
def read(path, count=-1, skip=0):
1✔
120
    r"""read(path, count=-1, skip=0) -> str
121

122
    Open file, return content.
123

124
    Examples:
125

126
        >>> read('/proc/self/exe')[:4]
127
        b'\x7fELF'
128
    """
129
    path = os.path.expanduser(os.path.expandvars(path))
1✔
130
    with open(path, 'rb') as fd:
1✔
131
        if skip:
1!
132
            fd.seek(skip)
×
133
        return fd.read(count)
1✔
134

135

136
def write(path, data = b'', create_dir = False, mode = 'w'):
1✔
137
    """Create new file or truncate existing to zero length and write data."""
138
    path = os.path.expanduser(os.path.expandvars(path))
1✔
139
    if create_dir:
1!
140
        path = os.path.realpath(path)
×
141
        mkdir_p(os.path.dirname(path))
×
142
    if mode == 'w' and isinstance(data, bytes): mode += 'b'
1✔
143
    with open(path, mode) as f:
1✔
144
        f.write(data)
1✔
145

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

149
    Works as the system command ``which``; searches $PATH for ``name`` and
150
    returns a full path if found.
151
    Tries all of the file extensions in $PATHEXT on Windows too.
152

153
    If `all` is :const:`True` the set of all found locations is returned, else
154
    the first occurrence or :const:`None` is returned.
155

156
    Arguments:
157
      `name` (str): The file to search for.
158
      `all` (bool):  Whether to return all locations where `name` was found.
159

160
    Returns:
161
      If `all` is :const:`True` the set of all locations where `name` was found,
162
      else the first location or :const:`None` if not found.
163

164
    Example:
165

166
        >>> which('sh') # doctest: +ELLIPSIS
167
        '.../bin/sh'
168
    """
169
    # If name is a path, do not attempt to resolve it.
170
    if os.path.sep in name:
1✔
171
        return name
1✔
172

173
    if sys.platform == 'win32':
1!
174
        pathexts = os.environ.get('PATHEXT', '').split(os.pathsep)
×
175
        isroot = False
×
176
    else:
177
        pathexts = []
1✔
178
        isroot = os.getuid() == 0
1✔
179
    pathexts = [''] + pathexts
1✔
180
    out = set()
1✔
181
    try:
1✔
182
        path = path or os.environ['PATH']
1✔
183
    except KeyError:
×
184
        log.exception('Environment variable $PATH is not set')
×
185
    for path_part in path.split(os.pathsep):
1✔
186
        for ext in pathexts:
1✔
187
            nameext = name + ext
1✔
188
            p = os.path.join(path_part, nameext)
1✔
189
            if os.access(p, os.X_OK):
1✔
190
                st = os.stat(p)
1✔
191
                if not stat.S_ISREG(st.st_mode):
1!
192
                    continue
×
193
                # work around this issue: https://bugs.python.org/issue9311
194
                if isroot and not \
1!
195
                st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
196
                    continue
×
197
                if all:
1!
198
                    out.add(p)
×
199
                    break
×
200
                else:
201
                    return p
1✔
202
    if all:
1!
203
        return out
×
204
    else:
205
        return None
1✔
206

207

208
def normalize_argv_env(argv, env, log, level=2):
1✔
209
    #
210
    # Validate argv
211
    #
212
    # - Must be a list/tuple of strings
213
    # - Each string must not contain '\x00'
214
    #
215
    argv = argv or []
1✔
216
    if isinstance(argv, (six.text_type, six.binary_type)):
1✔
217
        argv = [argv]
1✔
218

219
    if not isinstance(argv, (list, tuple)):
1!
220
        log.error('argv must be a list or tuple: %r' % argv)
×
221

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

225
    # Create a duplicate so we can modify it
226
    argv = list(argv)
1✔
227

228
    for i, oarg in enumerate(argv):
1✔
229
        arg = packing._need_bytes(oarg, level, 0x80)  # ASCII text is okay
1✔
230
        if b'\x00' in arg[:-1]:
1!
231
            log.error('Inappropriate nulls in argv[%i]: %r' % (i, oarg))
×
232
        argv[i] = bytearray(arg.rstrip(b'\x00'))
1✔
233

234
    #
235
    # Validate environment
236
    #
237
    # - Must be a dictionary of {string:string}
238
    # - No strings may contain '\x00'
239
    #
240

241
    # Create a duplicate so we can modify it safely
242
    env2 = []
1✔
243
    if hasattr(env, 'items'):
1✔
244
        env_items = env.items()
1✔
245
    else:
246
        env_items = env
1✔
247
    if env:
1✔
248
        for k,v in env_items:
1✔
249
            if not isinstance(k, (bytes, six.text_type)):
1!
250
                log.error('Environment keys must be strings: %r' % k)
×
251
            # Check if = is in the key, Required check since we sometimes call ctypes.execve directly
252
            # https://github.com/python/cpython/blob/025995feadaeebeef5d808f2564f0fd65b704ea5/Modules/posixmodule.c#L6476
253
            if b'=' in packing._encode(k):
1!
254
                log.error('Environment keys may not contain "=": %r' % (k))
×
255
            if not isinstance(v, (bytes, six.text_type)):
1!
256
                log.error('Environment values must be strings: %r=%r' % (k,v))
×
257
            k = packing._need_bytes(k, level, 0x80)  # ASCII text is okay
1✔
258
            v = packing._need_bytes(v, level, 0x80)  # ASCII text is okay
1✔
259
            if b'\x00' in k[:-1]:
1!
260
                log.error('Inappropriate nulls in env key: %r' % (k))
×
261
            if b'\x00' in v[:-1]:
1!
262
                log.error('Inappropriate nulls in env value: %r=%r' % (k, v))
×
263
            env2.append((bytearray(k.rstrip(b'\x00')), bytearray(v.rstrip(b'\x00'))))
1✔
264

265
    return argv, env2 or env
1✔
266

267

268
def run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, preexec_fn=None):
1✔
269
    """run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, preexec_fn=None) -> int
270

271
    Run a command in a new terminal.
272

273
    When ``terminal`` is not set:
274
        - If ``context.terminal`` is set it will be used.
275
          If it is an iterable then ``context.terminal[1:]`` are default arguments.
276
        - If a ``pwntools-terminal`` command exists in ``$PATH``, it is used
277
        - If tmux is detected (by the presence of the ``$TMUX`` environment
278
          variable), a new pane will be opened.
279
        - If GNU Screen is detected (by the presence of the ``$STY`` environment
280
          variable), a new screen will be opened.
281
        - If ``$TERM_PROGRAM`` is set, that is used.
282
        - If X11 is detected (by the presence of the ``$DISPLAY`` environment
283
          variable), ``x-terminal-emulator`` is used.
284
        - If KDE Konsole is detected (by the presence of the ``$KONSOLE_VERSION``
285
          environment variable), a terminal will be split.
286
        - If WSL (Windows Subsystem for Linux) is detected (by the presence of
287
          a ``wsl.exe`` binary in the ``$PATH`` and ``/proc/sys/kernel/osrelease``
288
          containing ``Microsoft``), a new ``cmd.exe`` window will be opened.
289

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

293
    Arguments:
294
        command (str): The command to run.
295
        terminal (str): Which terminal to use.
296
        args (list): Arguments to pass to the terminal
297
        kill_at_exit (bool): Whether to close the command/terminal on process exit.
298
        preexec_fn (callable): Callable to invoke before exec().
299

300
    Note:
301
        The command is opened with ``/dev/null`` for stdin, stdout, stderr.
302

303
    Returns:
304
      PID of the new terminal process
305
    """
306
    if not terminal:
1!
307
        if context.terminal:
1!
308
            terminal = context.terminal[0]
1✔
309
            args     = context.terminal[1:]
1✔
310
        elif which('pwntools-terminal'):
×
311
            terminal = 'pwntools-terminal'
×
312
            args     = []
×
313
        elif 'TMUX' in os.environ and which('tmux'):
×
314
            terminal = 'tmux'
×
315
            args     = ['splitw']
×
316
        elif 'STY' in os.environ and which('screen'):
×
317
            terminal = 'screen'
×
318
            args     = ['-t','pwntools-gdb','bash','-c']
×
319
        elif 'TERM_PROGRAM' in os.environ and os.environ['TERM_PROGRAM'] == "iTerm.app" and which('osascript'):
×
320
            # if we're on a mac, and using iTerm
321
            terminal = "osascript"
×
322
            args     = []
×
323
        elif 'TERM_PROGRAM' in os.environ and which(os.environ['TERM_PROGRAM']):
×
324
            terminal = os.environ['TERM_PROGRAM']
×
325
            args     = []
×
326
        elif 'DISPLAY' in os.environ and which('x-terminal-emulator'):
×
327
            terminal = 'x-terminal-emulator'
×
328
            args     = ['-e']
×
329
        elif 'KONSOLE_VERSION' in os.environ and which('qdbus'):
×
330
            qdbus = which('qdbus')
×
331
            window_id = os.environ['WINDOWID']
×
332
            konsole_dbus_service = os.environ['KONSOLE_DBUS_SERVICE']
×
333

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

337
            # Iterate over all MainWindows
338
            for line in lines:
×
339
                parts = line.split('/')
×
340
                if len(parts) == 3 and parts[2].startswith('MainWindow_'):
×
341
                    name = parts[2]
×
342
                    with subprocess.Popen((qdbus, konsole_dbus_service, '/konsole/' + name,
×
343
                                           'org.kde.KMainWindow.winId'), stdout=subprocess.PIPE) as proc:
344
                        target_window_id = proc.communicate()[0].decode().strip()
×
345
                        if target_window_id == window_id:
×
346
                            break
×
347
            else:
348
                log.error('MainWindow not found')
×
349

350
            # Split
351
            subprocess.run((qdbus, konsole_dbus_service, '/konsole/' + name,
×
352
                            'org.kde.KMainWindow.activateAction', 'split-view-left-right'), stdout=subprocess.DEVNULL)
353

354
            # Find new session
355
            with subprocess.Popen((qdbus, konsole_dbus_service, os.environ['KONSOLE_DBUS_WINDOW'],
×
356
                                   'org.kde.konsole.Window.sessionList'), stdout=subprocess.PIPE) as proc:
357
                session_list = map(int, proc.communicate()[0].decode().split())
×
358
            last_konsole_session = max(session_list)
×
359

360
            terminal = 'qdbus'
×
361
            args = [konsole_dbus_service, '/Sessions/{}'.format(last_konsole_session),
×
362
                    'org.kde.konsole.Session.runCommand']
363

364
        else:
365
            is_wsl = False
×
366
            if os.path.exists('/proc/sys/kernel/osrelease'):
×
367
                with open('/proc/sys/kernel/osrelease', 'rb') as f:
×
368
                    is_wsl = b'icrosoft' in f.read()
×
369
            if is_wsl and which('cmd.exe') and which('wsl.exe') and which('bash.exe'):
×
370
                terminal    = 'cmd.exe'
×
371
                args        = ['/c', 'start']
×
372
                distro_name = os.getenv('WSL_DISTRO_NAME')
×
UNCOV
373
                current_dir = os.getcwd()
×
374

375
                # Split pane in Windows Terminal
376
                if 'WT_SESSION' in os.environ and which('wt.exe'):
×
UNCOV
377
                    args.extend(['wt.exe', '-w', '0', 'split-pane'])
×
378
                if distro_name:
×
379
                    args.extend(['wsl.exe', '-d', distro_name, '--cd', current_dir, 'bash', '-c'])
×
380
                else:
381
                    args.extend(['bash.exe', '-c'])
×
382

383
    if not terminal:
1!
384
        log.error('Could not find a terminal binary to use. Set context.terminal to your terminal.')
×
385
    elif not which(terminal):
1!
386
        log.error('Could not find terminal binary %r. Set context.terminal to your terminal.' % terminal)
×
387

388
    if isinstance(args, tuple):
1!
389
        args = list(args)
×
390

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

397
    argv = [which(terminal)] + args
1✔
398

399
    if isinstance(command, six.string_types):
1!
400
        if ';' in command:
×
401
            log.error("Cannot use commands with semicolon.  Create a script and invoke that directly.")
×
402
        argv += [command]
×
403
    elif isinstance(command, (list, tuple)):
1!
404
        # Dump the full command line to a temporary file so we can be sure that
405
        # it is parsed correctly, and we do not need to account for shell expansion
406
        script = '''
1✔
407
#!{executable!s}
408
import os
409
os.execve({argv0!r}, {argv!r}, os.environ)
410
'''
411
        script = script.format(executable='/bin/env ' * (' ' in sys.executable) + sys.executable,
1✔
412
                               argv=command,
413
                               argv0=which(command[0]))
414
        script = script.lstrip()
1✔
415

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

418
        with tempfile.NamedTemporaryFile(delete=False, mode='wt+') as tmp:
1✔
419
          tmp.write(script)
1✔
420
          tmp.flush()
1✔
421
          os.chmod(tmp.name, 0o700)
1✔
422
          argv += [tmp.name]
1✔
423

424

425
    # if we're on a Mac and use iTerm, we use `osascript` to split the current window
426
    # `command` was sanitized on the previous step. It is now either a string, or was written to a tmp file
427
    # we run the command, which is now `argv[-1]`
428
    if terminal == 'osascript':
1!
429
        osa_script = """
×
430
tell application "iTerm"
431
    tell current session of current window
432
        set newSession to (split horizontally with default profile)
433
    end tell
434
    tell newSession
435
        write text "{}"
436
    end tell
437
end tell
438
""".format(argv[-1])
439
        with tempfile.NamedTemporaryFile(delete=False, mode='wt+') as tmp:
×
440
            tmp.write(osa_script.lstrip())
×
441
            tmp.flush()
×
442
            os.chmod(tmp.name, 0o700)
×
443
            argv = [which(terminal), tmp.name]
×
444
    # cmd.exe does not support WSL UNC paths as working directory
445
    # so it gets reset to %WINDIR% before starting wsl again.
446
    # Set the working directory correctly in WSL.
447
    elif terminal == 'cmd.exe':
1!
448
        argv[-1] = "cd '{}' && {}".format(os.getcwd(), argv[-1])
×
449

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

452
    stdin = stdout = stderr = open(os.devnull, 'r+b')
1✔
453
    if terminal == 'tmux' or terminal == 'kitty':
1!
454
        stdout = subprocess.PIPE
×
455

456
    p = subprocess.Popen(argv, stdin=stdin, stdout=stdout, stderr=stderr, preexec_fn=preexec_fn)
1✔
457

458
    kittyid = None
1✔
459

460
    if terminal == 'tmux':
1!
461
        out, _ = p.communicate()
×
462
        try:
×
463
            pid = int(out)
×
464
        except ValueError:
×
465
            pid = None
×
466
        if pid is None:
×
467
            log.error("Could not parse PID from tmux output (%r). Start tmux first.", out)
×
468
    elif terminal == 'qdbus':
1!
469
        with subprocess.Popen((qdbus, konsole_dbus_service, '/Sessions/{}'.format(last_konsole_session),
×
470
                               'org.kde.konsole.Session.processId'), stdout=subprocess.PIPE) as proc:
471
            pid = int(proc.communicate()[0].decode())
×
472
    elif terminal == 'kitty':
1!
473
        pid = p.pid
×
474
        
475
        out, _ = p.communicate()
×
476
        try:
×
477
            kittyid = int(out)
×
478
        except ValueError:
×
479
            kittyid = None
×
480
        if kittyid is None:
×
481
            log.error("Could not parse kitty window ID from output (%r)", out)
×
482
    elif terminal == 'cmd.exe':
1!
483
        # p.pid is cmd.exe's pid instead of the WSL process we want to start eventually.
484
        # I don't know how to trace the execution through Windows and back into the WSL2 VM.
485
        # Do a best guess by waiting for a new process matching the command to be run.
486
        # Otherwise it's better to return nothing instead of a know wrong pid.
487
        from pwnlib.util.proc import pid_by_name
×
488
        pid = None
×
489
        ran_program = command.split(' ')[0] if isinstance(command, six.string_types) else command[0]
×
490
        t = Timeout()
×
491
        with t.countdown(timeout=5):
×
492
            while t.timeout:
×
493
                new_pid = pid_by_name(ran_program)
×
494
                if new_pid and new_pid[0] > p.pid:
×
495
                    pid = new_pid[0]
×
496
                    break
×
497
                time.sleep(0.01)
×
498
    else:
499
        pid = p.pid
1✔
500

501
    if kill_at_exit and pid:
1!
502
        def kill():
1✔
503
            try:
×
504
                if terminal == 'qdbus':
×
505
                    os.kill(pid, signal.SIGHUP)
×
506
                elif terminal == 'kitty':
×
507
                    subprocess.Popen(["kitten", "@", "close-window", "--match", "id:{}".format(kittyid)], stderr=stderr)
×
508
                else:
509
                    os.kill(pid, signal.SIGTERM)
×
510
            except OSError:
×
511
                pass
×
512

513
        atexit.register(kill)
1✔
514

515
    return pid
1✔
516

517
def parse_ldd_output(output):
1✔
518
    """Parses the output from a run of 'ldd' on a binary.
519
    Returns a dictionary of {path: address} for
520
    each library required by the specified binary.
521

522
    Arguments:
523
      output(str): The output to parse
524

525
    Example:
526

527
        >>> sorted(parse_ldd_output('''
528
        ...     linux-vdso.so.1 =>  (0x00007fffbf5fe000)
529
        ...     libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007fe28117f000)
530
        ...     libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fe280f7b000)
531
        ...     libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe280bb4000)
532
        ...     /lib64/ld-linux-x86-64.so.2 (0x00007fe2813dd000)
533
        ... ''').keys())
534
        ['/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']
535
    """
536
    expr_linux   = re.compile(r'\s(?P<lib>\S?/\S+)\s+\((?P<addr>0x.+)\)')
1✔
537
    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✔
538
    libs = {}
1✔
539

540
    for s in output.split('\n'):
1✔
541
        match = expr_linux.search(s) or expr_openbsd.search(s)
1✔
542
        if not match:
1✔
543
            continue
1✔
544
        lib, addr = match.group('lib'), match.group('addr')
1✔
545
        libs[lib] = int(addr, 16)
1✔
546

547
    return libs
1✔
548

549
def mkdir_p(path):
1✔
550
    """Emulates the behavior of ``mkdir -p``."""
551

552
    try:
1✔
553
        os.makedirs(path)
1✔
554
    except OSError as exc:
1✔
555
        if exc.errno == errno.EEXIST and os.path.isdir(path):
1!
556
            pass
1✔
557
        else:
558
            raise
×
559

560
def dealarm_shell(tube):
1✔
561
    """Given a tube which is a shell, dealarm it.
562
    """
563
    tube.clean()
×
564

565
    tube.sendline('which python || echo')
×
566
    if tube.recvline().startswith('/'):
×
567
        tube.sendline('''exec python -c "import signal, os; signal.alarm(0); os.execl('$SHELL','')"''')
×
568
        return tube
×
569

570
    tube.sendline('which perl || echo')
×
571
    if tube.recvline().startswith('/'):
×
572
        tube.sendline('''exec perl -e "alarm 0; exec '${SHELL:-/bin/sh}'"''')
×
573
        return tube
×
574

575
    return None
×
576

577
def register_sizes(regs, in_sizes):
1✔
578
    """Create dictionaries over register sizes and relations
579

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

583
    * all_regs    : list of all valid registers
584
    * sizes[reg]  : the size of reg in bits
585
    * bigger[reg] : list of overlapping registers bigger than reg
586
    * smaller[reg]: list of overlapping registers smaller than reg
587

588
    Used in i386/AMD64 shellcode, e.g. the mov-shellcode.
589

590
    Example:
591

592
        >>> regs = [['eax', 'ax', 'al', 'ah'],['ebx', 'bx', 'bl', 'bh'],
593
        ... ['ecx', 'cx', 'cl', 'ch'],
594
        ... ['edx', 'dx', 'dl', 'dh'],
595
        ... ['edi', 'di'],
596
        ... ['esi', 'si'],
597
        ... ['ebp', 'bp'],
598
        ... ['esp', 'sp'],
599
        ... ]
600
        >>> all_regs, sizes, bigger, smaller = register_sizes(regs, [32, 16, 8, 8])
601
        >>> all_regs
602
        ['eax', 'ax', 'al', 'ah', 'ebx', 'bx', 'bl', 'bh', 'ecx', 'cx', 'cl', 'ch', 'edx', 'dx', 'dl', 'dh', 'edi', 'di', 'esi', 'si', 'ebp', 'bp', 'esp', 'sp']
603
        >>> pprint(sizes)
604
        {'ah': 8,
605
         'al': 8,
606
         'ax': 16,
607
         'bh': 8,
608
         'bl': 8,
609
         'bp': 16,
610
         'bx': 16,
611
         'ch': 8,
612
         'cl': 8,
613
         'cx': 16,
614
         'dh': 8,
615
         'di': 16,
616
         'dl': 8,
617
         'dx': 16,
618
         'eax': 32,
619
         'ebp': 32,
620
         'ebx': 32,
621
         'ecx': 32,
622
         'edi': 32,
623
         'edx': 32,
624
         'esi': 32,
625
         'esp': 32,
626
         'si': 16,
627
         'sp': 16}
628
        >>> pprint(bigger)
629
        {'ah': ['eax', 'ax', 'ah'],
630
         'al': ['eax', 'ax', 'al'],
631
         'ax': ['eax', 'ax'],
632
         'bh': ['ebx', 'bx', 'bh'],
633
         'bl': ['ebx', 'bx', 'bl'],
634
         'bp': ['ebp', 'bp'],
635
         'bx': ['ebx', 'bx'],
636
         'ch': ['ecx', 'cx', 'ch'],
637
         'cl': ['ecx', 'cx', 'cl'],
638
         'cx': ['ecx', 'cx'],
639
         'dh': ['edx', 'dx', 'dh'],
640
         'di': ['edi', 'di'],
641
         'dl': ['edx', 'dx', 'dl'],
642
         'dx': ['edx', 'dx'],
643
         'eax': ['eax'],
644
         'ebp': ['ebp'],
645
         'ebx': ['ebx'],
646
         'ecx': ['ecx'],
647
         'edi': ['edi'],
648
         'edx': ['edx'],
649
         'esi': ['esi'],
650
         'esp': ['esp'],
651
         'si': ['esi', 'si'],
652
         'sp': ['esp', 'sp']}
653
        >>> pprint(smaller)
654
        {'ah': [],
655
         'al': [],
656
         'ax': ['al', 'ah'],
657
         'bh': [],
658
         'bl': [],
659
         'bp': [],
660
         'bx': ['bl', 'bh'],
661
         'ch': [],
662
         'cl': [],
663
         'cx': ['cl', 'ch'],
664
         'dh': [],
665
         'di': [],
666
         'dl': [],
667
         'dx': ['dl', 'dh'],
668
         'eax': ['ax', 'al', 'ah'],
669
         'ebp': ['bp'],
670
         'ebx': ['bx', 'bl', 'bh'],
671
         'ecx': ['cx', 'cl', 'ch'],
672
         'edi': ['di'],
673
         'edx': ['dx', 'dl', 'dh'],
674
         'esi': ['si'],
675
         'esp': ['sp'],
676
         'si': [],
677
         'sp': []}
678
    """
679
    sizes = {}
1✔
680
    bigger = {}
1✔
681
    smaller = {}
1✔
682

683
    for l in regs:
1✔
684
        for r, s in zip(l, in_sizes):
1✔
685
            sizes[r] = s
1✔
686

687
        for r in l:
1✔
688
            bigger[r] = [r_ for r_ in l if sizes[r_] > sizes[r] or r == r_]
1✔
689
            smaller[r] = [r_ for r_ in l if sizes[r_] < sizes[r]]
1✔
690

691
    return lists.concat(regs), sizes, bigger, smaller
1✔
692

693

694
def python_2_bytes_compatible(klass):
1✔
695
    """
696
    A class decorator that defines __str__ methods under Python 2.
697
    Under Python 3 it does nothing.
698
    """
699
    if six.PY2:
1✔
700
        if '__str__' not in klass.__dict__:
1!
701
            klass.__str__ = klass.__bytes__
1✔
702
    return klass
1✔
703

704
def _create_execve_script(argv=None, executable=None, cwd=None, env=None, ignore_environ=None,
1✔
705
        stdin=0, stdout=1, stderr=2, preexec_fn=None, preexec_args=(), aslr=None, setuid=None,
706
        shell=False, log=log):
707
    """
708
    Creates a python wrapper script that triggers the syscall `execve` directly.
709

710
    Arguments:
711
        argv(list):
712
            List of arguments to pass into the process
713
        executable(str):
714
            Path to the executable to run.
715
            If :const:`None`, ``argv[0]`` is used.
716
        cwd(str):
717
            Working directory.  If :const:`None`, uses the working directory specified
718
            on :attr:`cwd` or set via :meth:`set_working_directory`.
719
        env(dict):
720
            Environment variables to add to the environment.
721
        ignore_environ(bool):
722
            Ignore default environment.  By default use default environment iff env not specified.
723
        stdin(int, str):
724
            If an integer, replace stdin with the numbered file descriptor.
725
            If a string, a open a file with the specified path and replace
726
            stdin with its file descriptor.  May also be one of ``sys.stdin``,
727
            ``sys.stdout``, ``sys.stderr``.  If :const:`None`, the file descriptor is closed.
728
        stdout(int, str):
729
            See ``stdin``.
730
        stderr(int, str):
731
            See ``stdin``.
732
        preexec_fn(callable):
733
            Function which is executed on the remote side before execve().
734
            This **MUST** be a self-contained function -- it must perform
735
            all of its own imports, and cannot refer to variables outside
736
            its scope.
737
        preexec_args(object):
738
            Argument passed to ``preexec_fn``.
739
            This **MUST** only consist of native Python objects.
740
        aslr(bool):
741
            See :class:`pwnlib.tubes.process.process` for more information.
742
        setuid(bool):
743
            See :class:`pwnlib.tubes.process.process` for more information.
744
        shell(bool):
745
            Pass the command-line arguments to the shell.
746

747
    Returns:
748
        A string containing the python script.
749
    """
750
    if not argv and not executable:
1!
751
        log.error("Must specify argv or executable")
×
752

753
    aslr      = aslr if aslr is not None else context.aslr
1✔
754

755
    if ignore_environ is None:
1!
756
        ignore_environ = env is not None  # compat
1✔
757

758
    argv, env = normalize_argv_env(argv, env, log)
1✔
759

760
    if shell:
1✔
761
        if len(argv) != 1:
1!
762
            log.error('Cannot provide more than 1 argument if shell=True')
×
763
        argv = [bytearray(b'/bin/sh'), bytearray(b'-c')] + argv
1✔
764

765
    executable = executable or argv[0]
1✔
766
    cwd        = cwd or '.'
1✔
767

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

773
    # Allow passing in sys.stdin/stdout/stderr objects
774
    handles = {sys.stdin: 0, sys.stdout:1, sys.stderr:2}
1✔
775
    stdin  = handles.get(stdin, stdin)
1✔
776
    stdout = handles.get(stdout, stdout)
1✔
777
    stderr = handles.get(stderr, stderr)
1✔
778

779
    # Allow the user to provide a self-contained function to run
780
    def func(): pass
1!
781
    func      = preexec_fn or func
1✔
782
    func_args = preexec_args
1✔
783

784
    if not isinstance(func, types.FunctionType):
1✔
785
        log.error("preexec_fn must be a function")
1✔
786

787
    func_name = func.__name__
1✔
788
    if func_name == (lambda: 0).__name__:
1✔
789
        log.error("preexec_fn cannot be a lambda")
1✔
790

791
    func_src  = inspect.getsource(func).strip()
1✔
792
    setuid = True if setuid is None else bool(setuid)
1✔
793

794

795
    script = r"""
1✔
796
#!/usr/bin/env python
797
import os, sys, ctypes, resource, platform, stat
798
from collections import OrderedDict
799
try:
800
    integer_types = int, long
801
except NameError:
802
    integer_types = int,
803
exe   = bytes(%(executable)r)
804
argv  = [bytes(a) for a in %(argv)r]
805
env   = %(env)r
806

807
os.chdir(%(cwd)r)
808

809
if %(ignore_environ)r:
810
    os.environ.clear()
811

812
environ = getattr(os, 'environb', os.environ)
813

814
if env is not None:
815
    env = OrderedDict((bytes(k), bytes(v)) for k,v in env)
816
    environ.update(env)
817
else:
818
    env = environ
819

820
def is_exe(path):
821
    return os.path.isfile(path) and os.access(path, os.X_OK)
822

823
PATH = environ.get(b'PATH',b'').split(os.pathsep.encode())
824

825
if os.path.sep.encode() not in exe and not is_exe(exe):
826
    for path in PATH:
827
        test_path = os.path.join(path, exe)
828
        if is_exe(test_path):
829
            exe = test_path
830
            break
831

832
if not is_exe(exe):
833
    sys.stderr.write('3\n')
834
    sys.stderr.write("{!r} is not executable or does not exist in $PATH: {!r}".format(exe,PATH))
835
    sys.exit(-1)
836

837
if not %(setuid)r:
838
    PR_SET_NO_NEW_PRIVS = 38
839
    result = ctypes.CDLL('libc.so.6').prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
840

841
    if result != 0:
842
        sys.stdout.write('3\n')
843
        sys.stdout.write("Could not disable setuid: prctl(PR_SET_NO_NEW_PRIVS) failed")
844
        sys.exit(-1)
845

846
try:
847
    PR_SET_PTRACER = 0x59616d61
848
    PR_SET_PTRACER_ANY = -1
849
    ctypes.CDLL('libc.so.6').prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0)
850
except Exception:
851
    pass
852

853
# Determine what UID the process will execute as
854
# This is used for locating apport core dumps
855
suid = os.getuid()
856
sgid = os.getgid()
857
st = os.stat(exe)
858
if %(setuid)r:
859
    if (st.st_mode & stat.S_ISUID):
860
        suid = st.st_uid
861
    if (st.st_mode & stat.S_ISGID):
862
        sgid = st.st_gid
863

864
if sys.argv[-1] == 'check':
865
    sys.stdout.write("1\n")
866
    sys.stdout.write(str(os.getpid()) + "\n")
867
    sys.stdout.write(str(os.getuid()) + "\n")
868
    sys.stdout.write(str(os.getgid()) + "\n")
869
    sys.stdout.write(str(suid) + "\n")
870
    sys.stdout.write(str(sgid) + "\n")
871
    getattr(sys.stdout, 'buffer', sys.stdout).write(os.path.realpath(exe) + b'\x00')
872
    sys.stdout.flush()
873

874
for fd, newfd in {0: %(stdin)r, 1: %(stdout)r, 2:%(stderr)r}.items():
875
    if newfd is None:
876
        os.close(fd)
877
    elif isinstance(newfd, (str, bytes)):
878
        newfd = os.open(newfd, os.O_RDONLY if fd == 0 else (os.O_RDWR|os.O_CREAT))
879
        os.dup2(newfd, fd)
880
        os.close(newfd)
881
    elif isinstance(newfd, integer_types) and newfd != fd:
882
        os.dup2(fd, newfd)
883

884
if not %(aslr)r:
885
    if platform.system().lower() == 'linux' and %(setuid)r is not True:
886
        ADDR_NO_RANDOMIZE = 0x0040000
887
        ctypes.CDLL('libc.so.6').personality(ADDR_NO_RANDOMIZE)
888

889
    resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))
890

891
# Attempt to dump ALL core file regions
892
try:
893
    with open('/proc/self/coredump_filter', 'w') as core_filter:
894
        core_filter.write('0x3f\n')
895
except Exception:
896
    pass
897

898
# Assume that the user would prefer to have core dumps.
899
try:
900
    resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
901
except Exception:
902
    pass
903

904
%(func_src)s
905
%(func_name)s(*%(func_args)r)
906

907
""" % locals()  
908

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

912
    # os.execve does not allow us to pass empty argv[0]
913
    # Therefore we use ctypes to call execve directly
914
    else:
915
        script += r"""
1✔
916
# Transform envp from dict to list
917
env_list = [key + b"=" + value for key, value in env.items()]
918

919
# ctypes helper to convert a python list to a NULL-terminated C array
920
def to_carray(py_list):
921
    py_list += [None] # NULL-terminated
922
    return (ctypes.c_char_p * len(py_list))(*py_list)
923

924
c_argv = to_carray(argv)
925
c_env = to_carray(env_list)
926

927
# Call execve
928
libc = ctypes.CDLL('libc.so.6')
929
libc.execve(exe, c_argv, c_env)
930

931
# We should never get here, since we sanitized argv and env,
932
# but just in case, indicate that something went wrong.
933
libc.perror(b"execve")
934
raise OSError("execve failed")
935
""" % locals()
936
    script = script.strip()
1✔
937

938
    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