Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefer Python3 for the remotely-executed Python script #1746

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ The table below shows which release corresponds to each branch, and what date th
## 4.6.0 (`dev`)

- [#1739][1739] Add/fix shellcraft.linux.kill() / shellcraft.linux.killparent()
- [#1746][1746] Prefer Python3 over Python2 for spawning remote processes over SSH
- [#1776][1776] mips: do not use $t0 temporary variable in dupio

[1739]: https://github.com/Gallopsled/pwntools/pull/1739
[1746]: https://github.com/Gallopsled/pwntools/pull/1746
[1776]: https://github.com/Gallopsled/pwntools/pull/1776

## 4.5.0 (`beta`)
Expand Down
28 changes: 16 additions & 12 deletions pwnlib/tubes/ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,7 +864,7 @@ def process(self, argv=None, executable=None, tty=True, cwd=None, env=None, time
>>> print(s.process('false', preexec_fn=uses_globals).recvall().strip().decode()) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
NameError: ... name 'bar' is not defined
NameError: ...name 'bar' is not defined

>>> s.process('echo hello', shell=True).recvall()
b'hello\n'
Expand Down Expand Up @@ -932,7 +932,7 @@ def process(self, argv=None, executable=None, tty=True, cwd=None, env=None, time
# Validate, since failures on the remote side will suck.
if not isinstance(executable, (six.text_type, six.binary_type, bytearray)):
self.error("executable / argv[0] must be a string: %r" % executable)
executable = packing._decode(executable)
executable = bytearray(packing._encode(executable))

# Allow passing in sys.stdin/stdout/stderr objects
handles = {sys.stdin: 0, sys.stdout:1, sys.stderr:2}
Expand All @@ -954,7 +954,7 @@ def func(): pass

func_src = inspect.getsource(func).strip()
setuid = True if setuid is None else bool(setuid)

script = r"""
#!/usr/bin/env python
import os, sys, ctypes, resource, platform, stat
Expand All @@ -963,25 +963,27 @@ def func(): pass
integer_types = int, long
except NameError:
integer_types = int,
exe = %(executable)r
exe = bytes(%(executable)r)
argv = [bytes(a) for a in %(argv)r]
env = %(env)r

os.chdir(%(cwd)r)

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

if env is not None:
env = OrderedDict((bytes(k), bytes(v)) for k,v in env)
os.environ.clear()
getattr(os, 'environb', os.environ).update(env)
environ.update(env)
else:
env = os.environ

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

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

if os.path.sep not in exe and not is_exe(exe):
if os.path.sep.encode() not in exe and not is_exe(exe):
for path in PATH:
test_path = os.path.join(path, exe)
if is_exe(test_path):
Expand All @@ -990,7 +992,7 @@ def is_exe(path):

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

if not %(setuid)r:
Expand Down Expand Up @@ -1027,7 +1029,7 @@ def is_exe(path):
sys.stdout.write(str(os.getgid()) + "\n")
sys.stdout.write(str(suid) + "\n")
sys.stdout.write(str(sgid) + "\n")
sys.stdout.write(os.path.realpath(exe) + '\x00')
getattr(sys.stdout, 'buffer', sys.stdout).write(os.path.realpath(exe) + b'\x00')
sys.stdout.flush()

for fd, newfd in {0: %(stdin)r, 1: %(stdout)r, 2:%(stderr)r}.items():
Expand Down Expand Up @@ -1098,7 +1100,7 @@ def is_exe(path):

with self.progress(msg) as h:

script = 'echo PWNTOOLS; for py in python2.7 python2 python; do test -x "$(which $py 2>&1)" && echo $py && exec $py -c %s check; done; echo 2' % sh_string(script)
script = 'echo PWNTOOLS; for py in python3 python2.7 python2 python; do test -x "$(which $py 2>&1)" && echo $py && exec $py -c %s check; done; echo 2' % sh_string(script)
with context.quiet:
python = ssh_process(self, script, tty=True, raw=True, level=self.level, timeout=timeout)

Expand All @@ -1108,8 +1110,10 @@ def is_exe(path):
result = safeeval.const(python.recvline()) # Status flag from the Python script
except (EOFError, ValueError):
h.failure("Process creation failed")
self.warn_once('Could not find a Python interpreter on %s\n' % self.host \
+ "Use ssh.run() instead of ssh.process()")
self.warn_once('Could not find a Python interpreter on %s\n' % self.host
+ "Use ssh.run() instead of ssh.process()\n"
"The original error message:\n"
+ python.recvall().decode())
return None

# If an error occurred, try to grab as much output
Expand Down