Skip to content

Commit

Permalink
Lookup using $PATHEXT file extensions in which on Windows (#2328)
Browse files Browse the repository at this point in the history
* Lookup process executable using PATHEXT file extensions

Windows uses some file extensions to determine which file to run when given
a non-existing file.

This allows to run `calc` instead of `calc.exe`.

* Try PATHEXT in `which` directly

* Update CHANGELOG
  • Loading branch information
peace-maker committed Jan 17, 2024
1 parent 7b78334 commit d427844
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 17 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -81,6 +81,7 @@ The table below shows which release corresponds to each branch, and what date th
- [#2279][2279] Make `pwn template` always set context.binary
- [#2310][2310] Add support to start a process on Windows
- [#2334][2334] Speed up disasm commandline tool with colored output
- [#2328][2328] Lookup using $PATHEXT file extensions in `which` on Windows

[2242]: https://github.com/Gallopsled/pwntools/pull/2242
[2277]: https://github.com/Gallopsled/pwntools/pull/2277
Expand All @@ -93,6 +94,7 @@ The table below shows which release corresponds to each branch, and what date th
[2279]: https://github.com/Gallopsled/pwntools/pull/2279
[2310]: https://github.com/Gallopsled/pwntools/pull/2310
[2334]: https://github.com/Gallopsled/pwntools/pull/2334
[2328]: https://github.com/Gallopsled/pwntools/pull/2328

## 4.12.0 (`beta`)

Expand Down
8 changes: 6 additions & 2 deletions pwnlib/tubes/process.py
Expand Up @@ -33,7 +33,7 @@
from pwnlib.util.misc import parse_ldd_output
from pwnlib.util.misc import which
from pwnlib.util.misc import normalize_argv_env
from pwnlib.util.packing import _need_bytes
from pwnlib.util.packing import _decode

log = getLogger(__name__)

Expand Down Expand Up @@ -558,7 +558,11 @@ def _validate(self, cwd, executable, argv, env):

argv, env = normalize_argv_env(argv, env, self, 4)
if env:
env = {bytes(k): bytes(v) for k, v in env}
if sys.platform == 'win32':
# Windows requires that all environment variables be strings
env = {_decode(k): _decode(v) for k, v in env}
else:
env = {bytes(k): bytes(v) for k, v in env}
if argv:
argv = list(map(bytes, argv))

Expand Down
40 changes: 25 additions & 15 deletions pwnlib/util/misc.py
Expand Up @@ -139,6 +139,7 @@ def which(name, all = False, path=None):
Works as the system command ``which``; searches $PATH for ``name`` and
returns a full path if found.
Tries all of the file extensions in $PATHEXT on Windows too.
If `all` is :const:`True` the set of all found locations is returned, else
the first occurrence or :const:`None` is returned.
Expand All @@ -160,26 +161,35 @@ def which(name, all = False, path=None):
if os.path.sep in name:
return name

isroot = False if sys.platform == 'win32' else (os.getuid() == 0)
if sys.platform == 'win32':
pathexts = os.environ.get('PATHEXT', '').split(os.pathsep)
isroot = False
else:
pathexts = []
isroot = os.getuid() == 0
pathexts = [''] + pathexts
out = set()
try:
path = path or os.environ['PATH']
except KeyError:
log.exception('Environment variable $PATH is not set')
for p in path.split(os.pathsep):
p = os.path.join(p, name)
if os.access(p, os.X_OK):
st = os.stat(p)
if not stat.S_ISREG(st.st_mode):
continue
# work around this issue: https://bugs.python.org/issue9311
if isroot and not \
st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
continue
if all:
out.add(p)
else:
return p
for path_part in path.split(os.pathsep):
for ext in pathexts:
nameext = name + ext
p = os.path.join(path_part, nameext)
if os.access(p, os.X_OK):
st = os.stat(p)
if not stat.S_ISREG(st.st_mode):
continue
# work around this issue: https://bugs.python.org/issue9311
if isroot and not \
st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
continue
if all:
out.add(p)
break
else:
return p
if all:
return out
else:
Expand Down

0 comments on commit d427844

Please sign in to comment.