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

Gallopsled / pwntools / 5190673800

pending completion
5190673800

Pull #2205

github-actions

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

3936 of 6604 branches covered (59.6%)

12074 of 16876 relevant lines covered (71.55%)

0.72 hits per line

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

67.21
/pwnlib/tubes/process.py
1
# -*- coding: utf-8 -*-
2
from __future__ import absolute_import
1✔
3
from __future__ import division
1✔
4

5
import ctypes
1✔
6
import errno
1✔
7
import logging
1✔
8
import os
1✔
9
import platform
1✔
10
import select
1✔
11
import signal
1✔
12
import six
1✔
13
import stat
1✔
14
import subprocess
1✔
15
import sys
1✔
16
import time
1✔
17

18
if sys.platform != 'win32':
1!
19
    import fcntl
1✔
20
    import pty
1✔
21
    import resource
1✔
22
    import tty
1✔
23

24
from pwnlib import qemu
1✔
25
from pwnlib.context import context
1✔
26
from pwnlib.log import getLogger
1✔
27
from pwnlib.timeout import Timeout
1✔
28
from pwnlib.tubes.tube import tube
1✔
29
from pwnlib.util.hashes import sha256file
1✔
30
from pwnlib.util.misc import parse_ldd_output
1✔
31
from pwnlib.util.misc import which
1✔
32
from pwnlib.util.misc import normalize_argv_env
1✔
33
from pwnlib.util.packing import _need_bytes
1✔
34

35
log = getLogger(__name__)
1✔
36

37
class PTY(object): pass
1✔
38
PTY=PTY()
1✔
39
STDOUT = subprocess.STDOUT
1✔
40
PIPE = subprocess.PIPE
1✔
41

42
signal_names = {-v:k for k,v in signal.__dict__.items() if k.startswith('SIG')}
1✔
43

44
class process(tube):
1✔
45
    r"""
46
    Spawns a new process, and wraps it with a tube for communication.
47

48
    Arguments:
49

50
        argv(list):
51
            List of arguments to pass to the spawned process.
52
        shell(bool):
53
            Set to `True` to interpret `argv` as a string
54
            to pass to the shell for interpretation instead of as argv.
55
        executable(str):
56
            Path to the binary to execute.  If :const:`None`, uses ``argv[0]``.
57
            Cannot be used with ``shell``.
58
        cwd(str):
59
            Working directory.  Uses the current working directory by default.
60
        env(dict):
61
            Environment variables.  By default, inherits from Python's environment.
62
        stdin(int):
63
            File object or file descriptor number to use for ``stdin``.
64
            By default, a pipe is used.  A pty can be used instead by setting
65
            this to ``PTY``.  This will cause programs to behave in an
66
            interactive manner (e.g.., ``python`` will show a ``>>>`` prompt).
67
            If the application reads from ``/dev/tty`` directly, use a pty.
68
        stdout(int):
69
            File object or file descriptor number to use for ``stdout``.
70
            By default, a pty is used so that any stdout buffering by libc
71
            routines is disabled.
72
            May also be ``PIPE`` to use a normal pipe.
73
        stderr(int):
74
            File object or file descriptor number to use for ``stderr``.
75
            By default, ``STDOUT`` is used.
76
            May also be ``PIPE`` to use a separate pipe,
77
            although the :class:`pwnlib.tubes.tube.tube` wrapper will not be able to read this data.
78
        close_fds(bool):
79
            Close all open file descriptors except stdin, stdout, stderr.
80
            By default, :const:`True` is used.
81
        preexec_fn(callable):
82
            Callable to invoke immediately before calling ``execve``.
83
        raw(bool):
84
            Set the created pty to raw mode (i.e. disable echo and control
85
            characters).  :const:`True` by default.  If no pty is created, this
86
            has no effect.
87
        aslr(bool):
88
            If set to :const:`False`, disable ASLR via ``personality`` (``setarch -R``)
89
            and ``setrlimit`` (``ulimit -s unlimited``).
90

91
            This disables ASLR for the target process.  However, the ``setarch``
92
            changes are lost if a ``setuid`` binary is executed.
93

94
            The default value is inherited from ``context.aslr``.
95
            See ``setuid`` below for additional options and information.
96
        setuid(bool):
97
            Used to control `setuid` status of the target binary, and the
98
            corresponding actions taken.
99

100
            By default, this value is :const:`None`, so no assumptions are made.
101

102
            If :const:`True`, treat the target binary as ``setuid``.
103
            This modifies the mechanisms used to disable ASLR on the process if
104
            ``aslr=False``.
105
            This is useful for debugging locally, when the exploit is a
106
            ``setuid`` binary.
107

108
            If :const:`False`, prevent ``setuid`` bits from taking effect on the
109
            target binary.  This is only supported on Linux, with kernels v3.5
110
            or greater.
111
        where(str):
112
            Where the process is running, used for logging purposes.
113
        display(list):
114
            List of arguments to display, instead of the main executable name.
115
        alarm(int):
116
            Set a SIGALRM alarm timeout on the process.
117

118
    Examples:
119

120
        >>> p = process('python')
121
        >>> p.sendline(b"print('Hello world')")
122
        >>> p.sendline(b"print('Wow, such data')")
123
        >>> b'' == p.recv(timeout=0.01)
124
        True
125
        >>> p.shutdown('send')
126
        >>> p.proc.stdin.closed
127
        True
128
        >>> p.connected('send')
129
        False
130
        >>> p.recvline()
131
        b'Hello world\n'
132
        >>> p.recvuntil(b',')
133
        b'Wow,'
134
        >>> p.recvregex(b'.*data')
135
        b' such data'
136
        >>> p.recv()
137
        b'\n'
138
        >>> p.recv() # doctest: +ELLIPSIS
139
        Traceback (most recent call last):
140
        ...
141
        EOFError
142

143
        >>> p = process('cat')
144
        >>> d = open('/dev/urandom', 'rb').read(4096)
145
        >>> p.recv(timeout=0.1)
146
        b''
147
        >>> p.write(d)
148
        >>> p.recvrepeat(0.1) == d
149
        True
150
        >>> p.recv(timeout=0.1)
151
        b''
152
        >>> p.shutdown('send')
153
        >>> p.wait_for_close()
154
        >>> p.poll()
155
        0
156

157
        >>> p = process('cat /dev/zero | head -c8', shell=True, stderr=open('/dev/null', 'w+b'))
158
        >>> p.recv()
159
        b'\x00\x00\x00\x00\x00\x00\x00\x00'
160

161
        >>> p = process(['python','-c','import os; print(os.read(2,1024).decode())'],
162
        ...             preexec_fn = lambda: os.dup2(0,2))
163
        >>> p.sendline(b'hello')
164
        >>> p.recvline()
165
        b'hello\n'
166

167
        >>> stack_smashing = ['python','-c','open("/dev/tty","wb").write(b"stack smashing detected")']
168
        >>> process(stack_smashing).recvall()
169
        b'stack smashing detected'
170

171
        >>> process(stack_smashing, stdout=PIPE).recvall()
172
        b''
173

174
        >>> getpass = ['python','-c','import getpass; print(getpass.getpass("XXX"))']
175
        >>> p = process(getpass, stdin=PTY)
176
        >>> p.recv()
177
        b'XXX'
178
        >>> p.sendline(b'hunter2')
179
        >>> p.recvall()
180
        b'\nhunter2\n'
181

182
        >>> process('echo hello 1>&2', shell=True).recvall()
183
        b'hello\n'
184

185
        >>> process('echo hello 1>&2', shell=True, stderr=PIPE).recvall()
186
        b''
187

188
        >>> a = process(['cat', '/proc/self/maps']).recvall()
189
        >>> b = process(['cat', '/proc/self/maps'], aslr=False).recvall()
190
        >>> with context.local(aslr=False):
191
        ...    c = process(['cat', '/proc/self/maps']).recvall()
192
        >>> a == b
193
        False
194
        >>> b == c
195
        True
196

197
        >>> process(['sh','-c','ulimit -s'], aslr=0).recvline()
198
        b'unlimited\n'
199

200
        >>> io = process(['sh','-c','sleep 10; exit 7'], alarm=2)
201
        >>> io.poll(block=True) == -signal.SIGALRM
202
        True
203

204
        >>> binary = ELF.from_assembly('nop', arch='mips')
205
        >>> p = process(binary.path)
206
        >>> binary_dir, binary_name = os.path.split(binary.path)
207
        >>> p = process('./{}'.format(binary_name), cwd=binary_dir)
208
        >>> p = process(binary.path, cwd=binary_dir)
209
        >>> p = process('./{}'.format(binary_name), cwd=os.path.relpath(binary_dir))
210
        >>> p = process(binary.path, cwd=os.path.relpath(binary_dir))
211
    """
212

213
    STDOUT = STDOUT
1✔
214
    PIPE = PIPE
1✔
215
    PTY = PTY
1✔
216

217
    #: Have we seen the process stop?  If so, this is a unix timestamp.
218
    _stop_noticed = 0
1✔
219

220
    proc = None
1✔
221

222
    def __init__(self, argv = None,
1!
223
                 shell = False,
224
                 executable = None,
225
                 cwd = None,
226
                 env = None,
227
                 stdin  = PIPE,
228
                 stdout = PTY,
229
                 stderr = STDOUT,
230
                 close_fds = True,
231
                 preexec_fn = lambda: None,
232
                 raw = True,
233
                 aslr = None,
234
                 setuid = None,
235
                 where = 'local',
236
                 display = None,
237
                 alarm = None,
238
                 *args,
239
                 **kwargs
240
                 ):
241
        super(process, self).__init__(*args,**kwargs)
1✔
242

243
        # Permit using context.binary
244
        if argv is None:
1✔
245
            if context.binary:
1!
246
                argv = [context.binary.path]
1✔
247
            else:
248
                raise TypeError('Must provide argv or set context.binary')
×
249

250

251
        #: :class:`subprocess.Popen` object that backs this process
252
        self.proc = None
1✔
253

254
        # We need to keep a copy of the un-_validated environment for printing
255
        original_env = env
1✔
256

257
        if shell:
1✔
258
            executable_val, argv_val, env_val = executable, argv, env
1✔
259
        else:
260
            executable_val, argv_val, env_val = self._validate(cwd, executable, argv, env)
1✔
261

262
        # Avoid the need to have to deal with the STDOUT magic value.
263
        if stderr is STDOUT:
1✔
264
            stderr = stdout
1✔
265

266
        # Determine which descriptors will be attached to a new PTY
267
        handles = (stdin, stdout, stderr)
1✔
268

269
        #: Which file descriptor is the controlling TTY
270
        self.pty          = handles.index(PTY) if PTY in handles else None
1✔
271

272
        #: Whether the controlling TTY is set to raw mode
273
        self.raw          = raw
1✔
274

275
        #: Whether ASLR should be left on
276
        self.aslr         = aslr if aslr is not None else context.aslr
1✔
277

278
        #: Whether setuid is permitted
279
        self._setuid      = setuid if setuid is None else bool(setuid)
1✔
280

281
        # Create the PTY if necessary
282
        stdin, stdout, stderr, master, slave = self._handles(*handles)
1✔
283

284
        #: Arguments passed on argv
285
        self.argv = argv_val
1✔
286

287
        #: Full path to the executable
288
        self.executable = executable_val
1✔
289

290
        #: Environment passed on envp
291
        self.env = os.environ if env is None else env_val
1✔
292

293
        if self.executable is None:
1✔
294
            if shell:
1!
295
                self.executable = '/bin/sh'
1✔
296
            else:
297
                self.executable = which(self.argv[0], path=self.env.get('PATH'))
×
298

299
        self._cwd = os.path.realpath(cwd or os.path.curdir)
1✔
300

301
        #: Alarm timeout of the process
302
        self.alarm        = alarm
1✔
303

304
        self.preexec_fn = preexec_fn
1✔
305
        self.display    = display or self.program
1✔
306
        self._qemu      = False
1✔
307
        self._corefile  = None
1✔
308

309
        message = "Starting %s process %r" % (where, self.display)
1✔
310

311
        if self.isEnabledFor(logging.DEBUG):
1!
312
            if argv != [self.executable]: message += ' argv=%r ' % self.argv
×
313
            if original_env not in (os.environ, None):  message += ' env=%r ' % self.env
×
314

315
        with self.progress(message) as p:
1✔
316

317
            if not self.aslr:
1✔
318
                self.warn_once("ASLR is disabled!")
1✔
319

320
            # In the event the binary is a foreign architecture,
321
            # and binfmt is not installed (e.g. when running on
322
            # Travis CI), re-try with qemu-XXX if we get an
323
            # 'Exec format error'.
324
            prefixes = [([], self.executable)]
1✔
325
            exception = None
1✔
326

327
            for prefix, executable in prefixes:
1!
328
                try:
1✔
329
                    args = self.argv
1✔
330
                    if prefix:
1!
331
                        args = prefix + args
×
332
                    self.proc = subprocess.Popen(args = args,
1✔
333
                                                 shell = shell,
334
                                                 executable = executable,
335
                                                 cwd = cwd,
336
                                                 env = self.env,
337
                                                 stdin = stdin,
338
                                                 stdout = stdout,
339
                                                 stderr = stderr,
340
                                                 close_fds = close_fds,
341
                                                 preexec_fn = self.__preexec_fn)
342
                    break
1✔
343
                except OSError as exception:
×
344
                    if exception.errno != errno.ENOEXEC:
×
345
                        raise
×
346
                    prefixes.append(self.__on_enoexec(exception))
×
347

348
            p.success('pid %i' % self.pid)
1✔
349

350
        if self.pty is not None:
1✔
351
            if stdin is slave:
1✔
352
                self.proc.stdin = os.fdopen(os.dup(master), 'r+b', 0)
1✔
353
            if stdout is slave:
1!
354
                self.proc.stdout = os.fdopen(os.dup(master), 'r+b', 0)
1✔
355
            if stderr is slave:
1✔
356
                self.proc.stderr = os.fdopen(os.dup(master), 'r+b', 0)
1✔
357

358
            os.close(master)
1✔
359
            os.close(slave)
1✔
360

361
        # Set in non-blocking mode so that a call to call recv(1000) will
362
        # return as soon as a the first byte is available
363
        if self.proc.stdout:
1!
364
            fd = self.proc.stdout.fileno()
1✔
365
            fl = fcntl.fcntl(fd, fcntl.F_GETFL)
1✔
366
            fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
1✔
367

368
        # Save off information about whether the binary is setuid / setgid
369
        self.suid = self.uid = os.getuid()
1✔
370
        self.sgid = self.gid = os.getgid()
1✔
371
        st = os.stat(self.executable)
1✔
372
        if self._setuid:
1!
373
            if (st.st_mode & stat.S_ISUID):
×
374
                self.suid = st.st_uid
×
375
            if (st.st_mode & stat.S_ISGID):
×
376
                self.sgid = st.st_gid
×
377

378
    def __preexec_fn(self):
1✔
379
        """
380
        Routine executed in the child process before invoking execve().
381

382
        Handles setting the controlling TTY as well as invoking the user-
383
        supplied preexec_fn.
384
        """
385
        if self.pty is not None:
×
386
            self.__pty_make_controlling_tty(self.pty)
×
387

388
        if not self.aslr:
×
389
            try:
×
390
                if context.os == 'linux' and self._setuid is not True:
×
391
                    ADDR_NO_RANDOMIZE = 0x0040000
×
392
                    ctypes.CDLL('libc.so.6').personality(ADDR_NO_RANDOMIZE)
×
393

394
                resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))
×
395
            except Exception:
×
396
                self.exception("Could not disable ASLR")
×
397

398
        # Assume that the user would prefer to have core dumps.
399
        try:
×
400
            resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
×
401
        except Exception:
×
402
            pass
×
403

404
        # Given that we want a core file, assume that we want the whole thing.
405
        try:
×
406
            with open('/proc/self/coredump_filter', 'w') as f:
×
407
                f.write('0xff')
×
408
        except Exception:
×
409
            pass
×
410

411
        if self._setuid is False:
×
412
            try:
×
413
                PR_SET_NO_NEW_PRIVS = 38
×
414
                ctypes.CDLL('libc.so.6').prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
×
415
            except Exception:
×
416
                pass
×
417

418
        # Avoid issues with attaching to processes when yama-ptrace is set
419
        try:
×
420
            PR_SET_PTRACER = 0x59616d61
×
421
            PR_SET_PTRACER_ANY = -1
×
422
            ctypes.CDLL('libc.so.6').prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0)
×
423
        except Exception:
×
424
            pass
×
425

426

427
        if self.alarm is not None:
×
428
            signal.alarm(self.alarm)
×
429

430
        self.preexec_fn()
×
431

432
    def __on_enoexec(self, exception):
1✔
433
        """We received an 'exec format' error (ENOEXEC)
434

435
        This implies that the user tried to execute e.g.
436
        an ARM binary on a non-ARM system, and does not have
437
        binfmt helpers installed for QEMU.
438
        """
439
        # Get the ELF binary for the target executable
440
        with context.quiet:
×
441
            # XXX: Cyclic imports :(
442
            from pwnlib.elf import ELF
×
443
            binary = ELF(self.executable)
×
444

445
        # If we're on macOS, this will never work.  Bail now.
446
        # if platform.mac_ver()[0]:
447
            # self.error("Cannot run ELF binaries on macOS")
448

449
        # Determine what architecture the binary is, and find the
450
        # appropriate qemu binary to run it.
451
        qemu_path = qemu.user_path(arch=binary.arch)
×
452

453
        if not qemu_path:
×
454
            raise exception
×
455

456
        qemu_path = which(qemu_path)
×
457
        if qemu_path:
×
458
            self._qemu = qemu_path
×
459

460
            args = [qemu_path]
×
461
            if self.argv:
×
462
                args += ['-0', self.argv[0]]
×
463
            args += ['--']
×
464

465
            return [args, qemu_path]
×
466

467
        # If we get here, we couldn't run the binary directly, and
468
        # we don't have a qemu which can run it.
469
        self.exception(exception)
×
470

471
    @property
1✔
472
    def program(self):
1✔
473
        """Alias for ``executable``, for backward compatibility.
474

475
        Example:
476

477
            >>> p = process('/bin/true')
478
            >>> p.executable == '/bin/true'
479
            True
480
            >>> p.executable == p.program
481
            True
482

483
        """
484
        return self.executable
1✔
485

486
    @property
1✔
487
    def cwd(self):
1✔
488
        """Directory that the process is working in.
489

490
        Example:
491

492
            >>> p = process('sh')
493
            >>> p.sendline(b'cd /tmp; echo AAA')
494
            >>> _ = p.recvuntil(b'AAA')
495
            >>> p.cwd == '/tmp'
496
            True
497
            >>> p.sendline(b'cd /proc; echo BBB;')
498
            >>> _ = p.recvuntil(b'BBB')
499
            >>> p.cwd
500
            '/proc'
501
        """
502
        try:
1✔
503
            self._cwd = os.readlink('/proc/%i/cwd' % self.pid)
1✔
504
        except Exception:
1✔
505
            pass
1✔
506

507
        return self._cwd
1✔
508

509

510
    def _validate(self, cwd, executable, argv, env):
1✔
511
        """
512
        Perform extended validation on the executable path, argv, and envp.
513

514
        Mostly to make Python happy, but also to prevent common pitfalls.
515
        """
516

517
        orig_cwd = cwd
1✔
518
        cwd = cwd or os.path.curdir
1✔
519

520
        argv, env = normalize_argv_env(argv, env, self, 4)
1✔
521
        if env:
1✔
522
            env = {bytes(k): bytes(v) for k, v in env}
1✔
523
        if argv:
1!
524
            argv = list(map(bytes, argv))
1✔
525

526
        #
527
        # Validate executable
528
        #
529
        # - Must be an absolute or relative path to the target executable
530
        # - If not, attempt to resolve the name in $PATH
531
        #
532
        if not executable:
1!
533
            if not argv:
1!
534
                self.error("Must specify argv or executable")
×
535
            executable = argv[0]
1✔
536

537
        if not isinstance(executable, str):
1!
538
            executable = executable.decode('utf-8')
1✔
539

540
        path = env and env.get(b'PATH')
1✔
541
        if path:
1✔
542
            path = path.decode()
1✔
543
        else:
544
            path = os.environ.get('PATH')
1✔
545
        # Do not change absolute paths to binaries
546
        if executable.startswith(os.path.sep):
1✔
547
            pass
1✔
548

549
        # If there's no path component, it's in $PATH or relative to the
550
        # target directory.
551
        #
552
        # For example, 'sh'
553
        elif os.path.sep not in executable and which(executable, path=path):
1✔
554
            executable = which(executable, path=path)
1✔
555

556
        # Either there is a path component, or the binary is not in $PATH
557
        # For example, 'foo/bar' or 'bar' with cwd=='foo'
558
        elif os.path.sep not in executable:
1!
559
            tmp = executable
×
560
            executable = os.path.join(cwd, executable)
×
561
            self.warn_once("Could not find executable %r in $PATH, using %r instead" % (tmp, executable))
×
562

563
        # There is a path component and user specified a working directory,
564
        # it must be relative to that directory. For example, 'bar/baz' with
565
        # cwd='foo' or './baz' with cwd='foo/bar'
566
        elif orig_cwd:
1!
567
            executable = os.path.join(orig_cwd, executable)
1✔
568

569
        if not os.path.exists(executable):
1!
570
            self.error("%r does not exist"  % executable)
×
571
        if not os.path.isfile(executable):
1!
572
            self.error("%r is not a file" % executable)
×
573
        if not os.access(executable, os.X_OK):
1!
574
            self.error("%r is not marked as executable (+x)" % executable)
×
575

576
        return executable, argv, env
1✔
577

578
    def _handles(self, stdin, stdout, stderr):
1✔
579
        master = slave = None
1✔
580

581
        if self.pty is not None:
1✔
582
            # Normally we could just use PIPE and be happy.
583
            # Unfortunately, this results in undesired behavior when
584
            # printf() and similar functions buffer data instead of
585
            # sending it directly.
586
            #
587
            # By opening a PTY for STDOUT, the libc routines will not
588
            # buffer any data on STDOUT.
589
            master, slave = pty.openpty()
1✔
590

591
            if self.raw:
1!
592
                # By giving the child process a controlling TTY,
593
                # the OS will attempt to interpret terminal control codes
594
                # like backspace and Ctrl+C.
595
                #
596
                # If we don't want this, we set it to raw mode.
597
                tty.setraw(master)
1✔
598
                tty.setraw(slave)
1✔
599

600
            if stdin is PTY:
1✔
601
                stdin = slave
1✔
602
            if stdout is PTY:
1!
603
                stdout = slave
1✔
604
            if stderr is PTY:
1✔
605
                stderr = slave
1✔
606

607
        return stdin, stdout, stderr, master, slave
1✔
608

609
    def __getattr__(self, attr):
1✔
610
        """Permit pass-through access to the underlying process object for
611
        fields like ``pid`` and ``stdin``.
612
        """
613
        if not attr.startswith('_') and hasattr(self.proc, attr):
1!
614
            return getattr(self.proc, attr)
1✔
615
        raise AttributeError("'process' object has no attribute '%s'" % attr)
×
616

617
    def kill(self):
1✔
618
        """kill()
619

620
        Kills the process.
621
        """
622
        self.close()
1✔
623

624
    def poll(self, block = False):
1✔
625
        """poll(block = False) -> int
626

627
        Arguments:
628
            block(bool): Wait for the process to exit
629

630
        Poll the exit code of the process. Will return None, if the
631
        process has not yet finished and the exit code otherwise.
632
        """
633

634
        # In order to facilitate retrieving core files, force an update
635
        # to the current working directory
636
        _ = self.cwd
1✔
637

638
        if block:
1✔
639
            self.wait_for_close()
1✔
640

641
        self.proc.poll()
1✔
642
        returncode = self.proc.returncode
1✔
643

644
        if returncode is not None and not self._stop_noticed:
1✔
645
            self._stop_noticed = time.time()
1✔
646
            signame = ''
1✔
647
            if returncode < 0:
1✔
648
                signame = ' (%s)' % (signal_names.get(returncode, 'SIG???'))
1✔
649

650
            self.info("Process %r stopped with exit code %d%s (pid %i)" % (self.display,
1✔
651
                                                                  returncode,
652
                                                                  signame,
653
                                                                  self.pid))
654
        return returncode
1✔
655

656
    def communicate(self, stdin = None):
1✔
657
        """communicate(stdin = None) -> str
658

659
        Calls :meth:`subprocess.Popen.communicate` method on the process.
660
        """
661

662
        return self.proc.communicate(stdin)
×
663

664
    # Implementation of the methods required for tube
665
    def recv_raw(self, numb):
1✔
666
        # This is a slight hack. We try to notice if the process is
667
        # dead, so we can write a message.
668
        self.poll()
1✔
669

670
        if not self.connected_raw('recv'):
1!
671
            raise EOFError
×
672

673
        if not self.can_recv_raw(self.timeout):
1✔
674
            return ''
1✔
675

676
        # This will only be reached if we either have data,
677
        # or we have reached an EOF. In either case, it
678
        # should be safe to read without expecting it to block.
679
        data = ''
1✔
680

681
        try:
1✔
682
            data = self.proc.stdout.read(numb)
1✔
683
        except IOError:
1✔
684
            pass
1✔
685

686
        if not data:
1✔
687
            self.shutdown("recv")
1✔
688
            raise EOFError
1✔
689

690
        return data
1✔
691

692
    def send_raw(self, data):
1✔
693
        # This is a slight hack. We try to notice if the process is
694
        # dead, so we can write a message.
695
        self.poll()
1✔
696

697
        if not self.connected_raw('send'):
1!
698
            raise EOFError
×
699

700
        try:
1✔
701
            self.proc.stdin.write(data)
1✔
702
            self.proc.stdin.flush()
1✔
703
        except IOError:
×
704
            raise EOFError
×
705

706
    def settimeout_raw(self, timeout):
1✔
707
        pass
1✔
708

709
    def can_recv_raw(self, timeout):
1✔
710
        if not self.connected_raw('recv'):
1!
711
            return False
×
712

713
        try:
1✔
714
            if timeout is None:
1✔
715
                return select.select([self.proc.stdout], [], []) == ([self.proc.stdout], [], [])
1✔
716

717
            return select.select([self.proc.stdout], [], [], timeout) == ([self.proc.stdout], [], [])
1✔
718
        except ValueError:
×
719
            # Not sure why this isn't caught when testing self.proc.stdout.closed,
720
            # but it's not.
721
            #
722
            #   File "/home/user/pwntools/pwnlib/tubes/process.py", line 112, in can_recv_raw
723
            #     return select.select([self.proc.stdout], [], [], timeout) == ([self.proc.stdout], [], [])
724
            # ValueError: I/O operation on closed file
725
            raise EOFError
×
726
        except select.error as v:
×
727
            if v.args[0] == errno.EINTR:
×
728
                return False
×
729

730
    def connected_raw(self, direction):
1✔
731
        if direction == 'any':
1✔
732
            return self.poll() is None
1✔
733
        elif direction == 'send':
1✔
734
            return self.proc.stdin and not self.proc.stdin.closed
1✔
735
        elif direction == 'recv':
1!
736
            return self.proc.stdout and not self.proc.stdout.closed
1✔
737

738
    def close(self):
1✔
739
        if self.proc is None:
1!
740
            return
×
741

742
        # First check if we are already dead
743
        self.poll()
1✔
744

745
        # close file descriptors
746
        for fd in [self.proc.stdin, self.proc.stdout, self.proc.stderr]:
1✔
747
            if fd is not None:
1!
748
                try:
1✔
749
                    fd.close()
1✔
750
                except IOError as e:
×
751
                    if e.errno != errno.EPIPE:
×
752
                        raise
×
753

754
        if not self._stop_noticed:
1✔
755
            try:
1✔
756
                self.proc.kill()
1✔
757
                self.proc.wait()
1✔
758
                self._stop_noticed = time.time()
1✔
759
                self.info('Stopped process %r (pid %i)' % (self.program, self.pid))
1✔
760
            except OSError:
×
761
                pass
×
762

763

764
    def fileno(self):
1✔
765
        if not self.connected():
×
766
            self.error("A stopped process does not have a file number")
×
767

768
        return self.proc.stdout.fileno()
×
769

770
    def shutdown_raw(self, direction):
1✔
771
        if direction == "send":
1✔
772
            self.proc.stdin.close()
1✔
773

774
        if direction == "recv":
1✔
775
            self.proc.stdout.close()
1✔
776

777
        if all(fp is None or fp.closed for fp in [self.proc.stdin, self.proc.stdout]):
1✔
778
            self.close()
1✔
779

780
    def __pty_make_controlling_tty(self, tty_fd):
1✔
781
        '''This makes the pseudo-terminal the controlling tty. This should be
782
        more portable than the pty.fork() function. Specifically, this should
783
        work on Solaris. '''
784

785
        child_name = os.ttyname(tty_fd)
×
786

787
        # Disconnect from controlling tty. Harmless if not already connected.
788
        try:
×
789
            fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
×
790
            if fd >= 0:
×
791
                os.close(fd)
×
792
        # which exception, shouldnt' we catch explicitly .. ?
793
        except OSError:
×
794
            # Already disconnected. This happens if running inside cron.
795
            pass
×
796

797
        os.setsid()
×
798

799
        # Verify we are disconnected from controlling tty
800
        # by attempting to open it again.
801
        try:
×
802
            fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
×
803
            if fd >= 0:
×
804
                os.close(fd)
×
805
                raise Exception('Failed to disconnect from '
×
806
                    'controlling tty. It is still possible to open /dev/tty.')
807
        # which exception, shouldnt' we catch explicitly .. ?
808
        except OSError:
×
809
            # Good! We are disconnected from a controlling tty.
810
            pass
×
811

812
        # Verify we can open child pty.
813
        fd = os.open(child_name, os.O_RDWR)
×
814
        if fd < 0:
×
815
            raise Exception("Could not open child pty, " + child_name)
×
816
        else:
817
            os.close(fd)
×
818

819
        # Verify we now have a controlling tty.
820
        fd = os.open("/dev/tty", os.O_WRONLY)
×
821
        if fd < 0:
×
822
            raise Exception("Could not open controlling tty, /dev/tty")
×
823
        else:
824
            os.close(fd)
×
825

826
    def libs(self):
1✔
827
        """libs() -> dict
828

829
        Return a dictionary mapping the path of each shared library loaded
830
        by the process to the address it is loaded at in the process' address
831
        space.
832
        """
833
        try:
1✔
834
            maps_raw = open('/proc/%d/maps' % self.pid).read()
1✔
835
        except IOError:
×
836
            maps_raw = None
×
837

838
        if not maps_raw:
1!
839
            import pwnlib.elf.elf
×
840

841
            with context.quiet:
×
842
                return pwnlib.elf.elf.ELF(self.executable).maps
×
843

844
        # Enumerate all of the libraries actually loaded right now.
845
        maps = {}
1✔
846
        for line in maps_raw.splitlines():
1✔
847
            if '/' not in line: continue
1✔
848
            path = line[line.index('/'):]
1✔
849
            path = os.path.realpath(path)
1✔
850
            if path not in maps:
1✔
851
                maps[path]=0
1✔
852

853
        for lib in maps:
1✔
854
            path = os.path.realpath(lib)
1✔
855
            for line in maps_raw.splitlines():
1!
856
                if line.endswith(path):
1✔
857
                    address = line.split('-')[0]
1✔
858
                    maps[lib] = int(address, 16)
1✔
859
                    break
1✔
860

861
        return maps
1✔
862

863
    @property
1✔
864
    def libc(self):
1✔
865
        """libc() -> ELF
866

867
        Returns an ELF for the libc for the current process.
868
        If possible, it is adjusted to the correct address
869
        automatically.
870

871
        Example:
872

873
        >>> p = process("/bin/cat")
874
        >>> libc = p.libc
875
        >>> libc # doctest: +SKIP
876
        ELF('/lib64/libc-...so')
877
        >>> p.close()
878
        """
879
        from pwnlib.elf import ELF
1✔
880

881
        for lib, address in self.libs().items():
1✔
882
            if 'libc.so' in lib or 'libc-' in lib:
1!
883
                e = ELF(lib)
×
884
                e.address = address
×
885
                return e
×
886

887
    @property
1✔
888
    def elf(self):
1✔
889
        """elf() -> pwnlib.elf.elf.ELF
890

891
        Returns an ELF file for the executable that launched the process.
892
        """
893
        import pwnlib.elf.elf
×
894
        return pwnlib.elf.elf.ELF(self.executable)
×
895

896
    @property
1✔
897
    def corefile(self):
1✔
898
        """corefile() -> pwnlib.elf.elf.Core
899

900
        Returns a corefile for the process.
901

902
        If the process is alive, attempts to create a coredump with GDB.
903

904
        If the process is dead, attempts to locate the coredump created
905
        by the kernel.
906
        """
907
        # If the process is still alive, try using GDB
908
        import pwnlib.elf.corefile
1✔
909
        import pwnlib.gdb
1✔
910

911
        try:
1✔
912
            if self.poll() is None:
1✔
913
                corefile = pwnlib.gdb.corefile(self)
1✔
914
                if corefile is None:
1!
915
                    self.error("Could not create corefile with GDB for %s", self.executable)
×
916
                return corefile
1✔
917

918
            # Handle race condition against the kernel or QEMU to write the corefile
919
            # by waiting up to 5 seconds for it to be written.
920
            t = Timeout()
1✔
921
            finder = None
1✔
922
            with t.countdown(5):
1✔
923
                while t.timeout and (finder is None or not finder.core_path):
1✔
924
                    finder = pwnlib.elf.corefile.CorefileFinder(self)
1✔
925
                    time.sleep(0.5)
1✔
926

927
            if not finder.core_path:
1!
928
                self.error("Could not find core file for pid %i" % self.pid)
×
929

930
            core_hash = sha256file(finder.core_path)
1✔
931

932
            if self._corefile and self._corefile._hash == core_hash:
1✔
933
                return self._corefile
1✔
934

935
            self._corefile = pwnlib.elf.corefile.Corefile(finder.core_path)
1✔
936
        except AttributeError as e:
×
937
            raise RuntimeError(e) # AttributeError would route through __getattr__, losing original message
×
938
        self._corefile._hash = core_hash
1✔
939

940
        return self._corefile
1✔
941

942
    def leak(self, address, count=1):
1✔
943
        r"""Leaks memory within the process at the specified address.
944

945
        Arguments:
946
            address(int): Address to leak memory at
947
            count(int): Number of bytes to leak at that address.
948

949
        Example:
950

951
            >>> e = ELF(which('bash-static'))
952
            >>> p = process(e.path)
953

954
            In order to make sure there's not a race condition against
955
            the process getting set up...
956

957
            >>> p.sendline(b'echo hello')
958
            >>> p.recvuntil(b'hello')
959
            b'hello'
960

961
            Now we can leak some data!
962

963
            >>> p.leak(e.address, 4)
964
            b'\x7fELF'
965
        """
966
        # If it's running under qemu-user, don't leak anything.
967
        if 'qemu-' in os.path.realpath('/proc/%i/exe' % self.pid):
1!
968
            self.error("Cannot use leaker on binaries under QEMU.")
×
969

970
        with open('/proc/%i/mem' % self.pid, 'rb') as mem:
1✔
971
            mem.seek(address)
1✔
972
            return mem.read(count) or None
1✔
973

974
    readmem = leak
1✔
975

976
    def writemem(self, address, data):
1✔
977
        r"""Writes memory within the process at the specified address.
978

979
        Arguments:
980
            address(int): Address to write memory
981
            data(bytes): Data to write to the address
982

983
        Example:
984
        
985
            Let's write data to  the beginning of the mapped memory of the  ELF.
986

987
            >>> context.clear(arch='i386')
988
            >>> address = 0x100000
989
            >>> data = cyclic(32)
990
            >>> assembly = shellcraft.nop() * len(data)
991

992
            Wait for one byte of input, then write the data to stdout
993

994
            >>> assembly += shellcraft.write(1, address, 1)
995
            >>> assembly += shellcraft.read(0, 'esp', 1)
996
            >>> assembly += shellcraft.write(1, address, 32)
997
            >>> assembly += shellcraft.exit()
998
            >>> asm(assembly)[32:]
999
            b'j\x01[\xb9\xff\xff\xef\xff\xf7\xd1\x89\xdaj\x04X\xcd\x801\xdb\x89\xe1j\x01Zj\x03X\xcd\x80j\x01[\xb9\xff\xff\xef\xff\xf7\xd1j Zj\x04X\xcd\x801\xdbj\x01X\xcd\x80'
1000

1001
            Assemble the binary and test it
1002

1003
            >>> elf = ELF.from_assembly(assembly, vma=address)
1004
            >>> io = elf.process()
1005
            >>> _ = io.recvuntil(b'\x90')
1006
            >>> _ = io.writemem(address, data)
1007
            >>> io.send(b'X')
1008
            >>> io.recvall()
1009
            b'aaaabaaacaaadaaaeaaafaaagaaahaaa'
1010
        """
1011

1012
        if 'qemu-' in os.path.realpath('/proc/%i/exe' % self.pid):
1!
1013
            self.error("Cannot use leaker on binaries under QEMU.")
×
1014

1015
        with open('/proc/%i/mem' % self.pid, 'wb') as mem:
1✔
1016
            mem.seek(address)
1✔
1017
            return mem.write(data)
1✔
1018

1019

1020
    @property
1✔
1021
    def stdin(self):
1✔
1022
        """Shorthand for ``self.proc.stdin``
1023

1024
        See: :obj:`.process.proc`
1025
        """
1026
        return self.proc.stdin
1✔
1027
    @property
1✔
1028
    def stdout(self):
1✔
1029
        """Shorthand for ``self.proc.stdout``
1030

1031
        See: :obj:`.process.proc`
1032
        """
1033
        return self.proc.stdout
1✔
1034
    @property
1✔
1035
    def stderr(self):
1✔
1036
        """Shorthand for ``self.proc.stderr``
1037

1038
        See: :obj:`.process.proc`
1039
        """
1040
        return self.proc.stderr
×
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