Repository: Gallopsled/pwntools

Issue:
Process PATH inconsistency
Consider the follwing two files exists: `/tmp/bin/ls /usr/bin/ls`.

I tried to run:
```python
from pwn import *
print(process(['ls', '--version'], env={'PATH': '/tmp/bin'}).recvall())
```
But the the version printed was of `/usr/bin/ls`, and `env['PATH']` was ignored.
I also tried running the same with `shell=True`:
```python
print(process('ls --version', shell=True, env={'PATH': '/tmp/bin'}).recvall())
```
And this time the version was of `/tmp/bin/ls` as expected.

When running the same using subprocess the output was allways the version of `/tmp/bin/ls`.
```python
import subprocess
subprocess.Popen(['ls', '--version'], env={'PATH': '/tmp/bin'})
subprocess.Popen('ls --version', env={'PATH': '/tmp/bin'}, shell=True)
```

Why?
I think this happens, because when running process with `shell=False` pwntools attemps to resolve the path using `which(executable)`.
Which totally ignores `env` and so finds `/usr/bin/ls`.
Then `subprocess.Popen` gets `/usr/bin/ls` as the executable which makes it not search it's location in the path.

But, when running with `shell=True`, self.executable is `'/bin/sh'` which tells `Popen` to let the shell resolve the correct path.
Then, it reads `env['PATH']` and find the correct version at `/tmp/`.


Discussion:

Resolving patch:
diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py
--- a/pwnlib/tubes/process.py
+++ b/pwnlib/tubes/process.py
@@ -278,14 +278,14 @@ def __init__(self, argv = None,
         #: Full path to the executable
         self.executable = executable_val
 
+        #: Environment passed on envp
+        self.env = os.environ if env is None else env_val
+
         if self.executable is None:
             if shell:
                 self.executable = '/bin/sh'
             else:
-                self.executable = which(self.argv[0])
-
-        #: Environment passed on envp
-        self.env = os.environ if env is None else env_val
+                self.executable = which(self.argv[0], path=self.env.get('PATH'))
 
         self._cwd = os.path.realpath(cwd or os.path.curdir)
 
@@ -550,6 +550,9 @@ def _validate(self, cwd, executable, argv, env):
         if not isinstance(executable, str):
             executable = executable.decode('utf-8')
 
+        env = os.environ if env is None else env
+
+        path = env.get('PATH')
         # Do not change absolute paths to binaries
         if executable.startswith(os.path.sep):
             pass
@@ -558,8 +561,8 @@ def _validate(self, cwd, executable, argv, env):
         # target directory.
         #
         # For example, 'sh'
-        elif os.path.sep not in executable and which(executable):
-            executable = which(executable)
+        elif os.path.sep not in executable and which(executable, path=path):
+            executable = which(executable, path=path)
 
         # Either there is a path component, or the binary is not in $PATH
         # For example, 'foo/bar' or 'bar' with cwd=='foo'
@@ -583,8 +586,6 @@ def _validate(self, cwd, executable, argv, env):
         #
 
         # Create a duplicate so we can modify it safely
-        env = os.environ if env is None else env
-
         env2 = {}
         for k,v in env.items():
             if not isinstance(k, (bytes, six.text_type)):
diff --git a/pwnlib/util/misc.py b/pwnlib/util/misc.py
--- a/pwnlib/util/misc.py
+++ b/pwnlib/util/misc.py
@@ -131,7 +131,7 @@ def write(path, data = b'', create_dir = False, mode = 'w'):
     with open(path, mode) as f:
         f.write(data)
 
-def which(name, all = False):
+def which(name, all = False, path=None):
     """which(name, flags = os.X_OK, all = False) -> str or str set
 
     Works as the system command ``which``; searches $PATH for ``name`` and
@@ -159,7 +159,7 @@ def which(name, all = False):
     isroot = os.getuid() == 0
     out = set()
     try:
-        path = os.environ['PATH']
+        path = path or os.environ['PATH']
     except KeyError:
         log.exception('Environment variable $PATH is not set')
     for p in path.split(os.pathsep):


Test patch: