Skip to content

Commit

Permalink
Prefer Python3 for the remotely-executed Python script (#1746)
Browse files Browse the repository at this point in the history
* Prefer Python3 for the remotely-executed Python script

This should be done as part of our general transition to Python3,
but should also be done because some systems with e.g. Python2.6
do not support OrderedDict, but DO have a Python3 version that works.

* Make the script actually work in py2 and py3

* Fix & update CHANGELOG.md

This actually is a behavior change, and can be a breaking change for some,
so must be stated prominently in the changelog.

Co-authored-by: Arusekk <arek_koz@o2.pl>
  • Loading branch information
heapcrash and Arusekk authored Apr 28, 2021
1 parent f3b08ed commit cb24f37
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 12 deletions.
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

0 comments on commit cb24f37

Please sign in to comment.