Skip to content

Commit

Permalink
Merge e744cc7 into 3bb756d
Browse files Browse the repository at this point in the history
  • Loading branch information
peace-maker committed Jan 2, 2024
2 parents 3bb756d + e744cc7 commit 31c7aca
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 31 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -79,6 +79,7 @@ The table below shows which release corresponds to each branch, and what date th
- [#2309][2309] Detect challenge binary and libc in `pwn template`
- [#2308][2308] Fix WinExec shellcraft to make sure it's 16 byte aligned
- [#2279][2279] Make `pwn template` always set context.binary
- [#2310][2310] Add support to start a process on Windows

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

## 4.12.0 (`beta`)

Expand Down
125 changes: 95 additions & 30 deletions pwnlib/tubes/process.py
Expand Up @@ -6,16 +6,19 @@
import errno
import logging
import os
import platform
import select
import signal
import six
import stat
import subprocess
import sys
import time

if sys.platform != 'win32':
IS_WINDOWS = sys.platform.startswith('win')

if IS_WINDOWS:
import queue
import threading
else:
import fcntl
import pty
import resource
Expand Down Expand Up @@ -116,6 +119,8 @@ class process(tube):
List of arguments to display, instead of the main executable name.
alarm(int):
Set a SIGALRM alarm timeout on the process.
creationflags(int):
Windows only. Flags to pass to ``CreateProcess``.
Examples:
Expand Down Expand Up @@ -228,7 +233,7 @@ def __init__(self, argv = None,
env = None,
ignore_environ = None,
stdin = PIPE,
stdout = PTY,
stdout = PTY if not IS_WINDOWS else PIPE,
stderr = STDOUT,
close_fds = True,
preexec_fn = lambda: None,
Expand All @@ -238,6 +243,7 @@ def __init__(self, argv = None,
where = 'local',
display = None,
alarm = None,
creationflags = 0,
*args,
**kwargs
):
Expand All @@ -250,6 +256,8 @@ def __init__(self, argv = None,
else:
raise TypeError('Must provide argv or set context.binary')

if IS_WINDOWS and PTY in (stdin, stdout, stderr):
raise NotImplementedError("ConPTY isn't implemented yet")

#: :class:`subprocess.Popen` object that backs this process
self.proc = None
Expand All @@ -258,31 +266,47 @@ def __init__(self, argv = None,
original_env = env

if shell:
executable_val, argv_val, env_val = executable or '/bin/sh', argv, env
executable_val, argv_val, env_val = executable, argv, env
if executable is None:
if IS_WINDOWS:
executable_val = os.environ.get('ComSpec', 'cmd.exe')
else:
executable_val = '/bin/sh'
else:
executable_val, argv_val, env_val = self._validate(cwd, executable, argv, env)

# Avoid the need to have to deal with the STDOUT magic value.
if stderr is STDOUT:
stderr = stdout

# Determine which descriptors will be attached to a new PTY
handles = (stdin, stdout, stderr)
if IS_WINDOWS:
self.pty = None
self.raw = False
self.aslr = True
self._setuid = False
self.suid = self.uid = None
self.sgid = self.gid = None
internal_preexec_fn = None
else:
# Determine which descriptors will be attached to a new PTY
handles = (stdin, stdout, stderr)

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

#: Which file descriptor is the controlling TTY
self.pty = handles.index(PTY) if PTY in handles else None
#: Whether the controlling TTY is set to raw mode
self.raw = raw

#: Whether the controlling TTY is set to raw mode
self.raw = raw
#: Whether ASLR should be left on
self.aslr = aslr if aslr is not None else context.aslr

#: Whether ASLR should be left on
self.aslr = aslr if aslr is not None else context.aslr
#: Whether setuid is permitted
self._setuid = setuid if setuid is None else bool(setuid)

#: Whether setuid is permitted
self._setuid = setuid if setuid is None else bool(setuid)
# Create the PTY if necessary
stdin, stdout, stderr, master, slave = self._handles(*handles)

# Create the PTY if necessary
stdin, stdout, stderr, master, slave = self._handles(*handles)
internal_preexec_fn = self.__preexec_fn

#: Arguments passed on argv
self.argv = argv_val
Expand Down Expand Up @@ -341,7 +365,8 @@ def __init__(self, argv = None,
stdout = stdout,
stderr = stderr,
close_fds = close_fds,
preexec_fn = self.__preexec_fn)
preexec_fn = internal_preexec_fn,
creationflags = creationflags)
break
except OSError as exception:
if exception.errno != errno.ENOEXEC:
Expand All @@ -350,6 +375,16 @@ def __init__(self, argv = None,

p.success('pid %i' % self.pid)

if IS_WINDOWS:
self._read_thread = None
self._read_queue = queue.Queue()
if self.proc.stdout:
# Read from stdout in a thread
self._read_thread = threading.Thread(target=_read_in_thread, args=(self._read_queue, self.proc.stdout))
self._read_thread.daemon = True
self._read_thread.start()
return

if self.pty is not None:
if stdin is slave:
self.proc.stdin = os.fdopen(os.dup(master), 'r+b', 0)
Expand Down Expand Up @@ -503,7 +538,8 @@ def cwd(self):
'/proc'
"""
try:
self._cwd = os.readlink('/proc/%i/cwd' % self.pid)
from pwnlib.util.proc import cwd
self._cwd = cwd(self.pid)
except Exception:
pass

Expand Down Expand Up @@ -676,6 +712,17 @@ def recv_raw(self, numb):
if not self.can_recv_raw(self.timeout):
return ''

if IS_WINDOWS:
data = b''
count = 0
while count < numb:
if self._read_queue.empty():
break
last_byte = self._read_queue.get(block=False)
data += last_byte
count += 1
return data

# This will only be reached if we either have data,
# or we have reached an EOF. In either case, it
# should be safe to read without expecting it to block.
Expand Down Expand Up @@ -713,6 +760,12 @@ def can_recv_raw(self, timeout):
if not self.connected_raw('recv'):
return False

if IS_WINDOWS:
with self.countdown(timeout=timeout):
while self.timeout and self._read_queue.empty():
time.sleep(0.01)
return not self._read_queue.empty()

try:
if timeout is None:
return select.select([self.proc.stdout], [], []) == ([self.proc.stdout], [], [])
Expand Down Expand Up @@ -751,7 +804,7 @@ def close(self):
try:
fd.close()
except IOError as e:
if e.errno != errno.EPIPE:
if e.errno != errno.EPIPE and e.errno != errno.EINVAL:
raise

if not self._stop_noticed:
Expand Down Expand Up @@ -833,10 +886,8 @@ def libs(self):
by the process to the address it is loaded at in the process' address
space.
"""
try:
maps_raw = open('/proc/%d/maps' % self.pid).read()
except IOError:
maps_raw = None
from pwnlib.util.proc import memory_maps
maps_raw = memory_maps(self.pid)

if not maps_raw:
import pwnlib.elf.elf
Expand All @@ -846,18 +897,18 @@ def libs(self):

# Enumerate all of the libraries actually loaded right now.
maps = {}
for line in maps_raw.splitlines():
if '/' not in line: continue
path = line[line.index('/'):]
for mapping in maps_raw:
path = mapping.path
if os.sep not in path: continue
path = os.path.realpath(path)
if path not in maps:
maps[path]=0

for lib in maps:
path = os.path.realpath(lib)
for line in maps_raw.splitlines():
if line.endswith(path):
address = line.split('-')[0]
for mapping in maps_raw:
if mapping.path == path:
address = mapping.addr.split('-')[0]
maps[lib] = int(address, 16)
break

Expand Down Expand Up @@ -1041,3 +1092,17 @@ def stderr(self):
See: :obj:`.process.proc`
"""
return self.proc.stderr

# Keep reading the process's output in a separate thread,
# since there's no non-blocking read in python on Windows.
def _read_in_thread(recv_queue, proc_stdout):
try:
while True:
b = proc_stdout.read(1)
if b:
recv_queue.put(b)
else:
break
except:
# Ignore any errors during Python shutdown
pass
2 changes: 1 addition & 1 deletion pwnlib/util/misc.py
Expand Up @@ -160,7 +160,7 @@ def which(name, all = False, path=None):
if os.path.sep in name:
return name

isroot = os.getuid() == 0
isroot = False if sys.platform == 'win32' else (os.getuid() == 0)
out = set()
try:
path = path or os.environ['PATH']
Expand Down
17 changes: 17 additions & 0 deletions pwnlib/util/proc.py
Expand Up @@ -223,6 +223,23 @@ def cmdline(pid):
"""
return psutil.Process(pid).cmdline()

def memory_maps(pid):
"""memory_maps(pid) -> list
Arguments:
pid (int): PID of the process.
Returns:
A list of the memory mappings in the process.
Example:
>>> maps = memory_maps(os.getpid())
>>> [(m.path, m.perms) for m in maps if '[stack]' in m.path]
[('[stack]', 'rw-p')]
"""
return psutil.Process(pid).memory_maps(grouped=False)

def stat(pid):
"""stat(pid) -> str list
Expand Down

0 comments on commit 31c7aca

Please sign in to comment.