From 00aed7acdbf31737116180f516d8df5c52fde0ee Mon Sep 17 00:00:00 2001 From: gfelber <34159565+gfelber@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:06:31 +0200 Subject: [PATCH 1/5] 1. stage of upstreaming [vagd](https://github.com/gfelber/vagd) to pwntools + implemented base class pwnvirt (previously Pwngd) + implemented ssh client sshvirt (previously Shgd) --- docs/source/globals.rst | 3 + docs/source/index.rst | 2 + docs/source/virtualization.rst | 28 ++ docs/source/virtualization/sshvirt.rst | 11 + pwn/toplevel.py | 1 + pwnlib/tubes/ssh.py | 3 + pwnlib/virtualization/__init__.py | 3 + pwnlib/virtualization/pwnvirt.py | 368 +++++++++++++++++++++++++ pwnlib/virtualization/sshvirt.py | 143 ++++++++++ 9 files changed, 562 insertions(+) create mode 100644 docs/source/virtualization.rst create mode 100644 docs/source/virtualization/sshvirt.rst create mode 100644 pwnlib/virtualization/__init__.py create mode 100644 pwnlib/virtualization/pwnvirt.py create mode 100644 pwnlib/virtualization/sshvirt.py diff --git a/docs/source/globals.rst b/docs/source/globals.rst index bca88e855..fd4d37dcc 100644 --- a/docs/source/globals.rst +++ b/docs/source/globals.rst @@ -54,6 +54,9 @@ This is a quick list of most of the objects and routines imported, in rough orde - ``ROP`` - :mod:`pwnlib.rop` - Automatically generate ROP chains using a DSL to describe what you want to do, rather than raw addresses +- ``Virtualization`` + - :mod:`pwnlib.virtualization` + - Automatically virtualize our exploit in different environments - ``gdb.debug`` and ``gdb.attach`` - :mod:`pwnlib.gdb` - Launch a binary under GDB and pop up a new terminal to interact with it. Automates setting breakpoints and makes iteration on exploits MUCH faster. diff --git a/docs/source/index.rst b/docs/source/index.rst index 051ece0af..43a180f8e 100755 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -77,6 +77,8 @@ Each of the ``pwntools`` modules is documented here. update useragents util/* + virtualization + virtualization/* windbg .. toctree:: diff --git a/docs/source/virtualization.rst b/docs/source/virtualization.rst new file mode 100644 index 000000000..07027f2a9 --- /dev/null +++ b/docs/source/virtualization.rst @@ -0,0 +1,28 @@ +.. testsetup:: * + + from pwn import * + +:mod:`pwnlib.virtualization` --- Virtualizing your exploits +============================================= + +.. automodule:: pwnlib.tubes + + +Types of Tubes +------------------- + +.. toctree:: + :maxdepth: 3 + :glob: + + virtualization/* + + +:mod:`pwnlib.virtualization.pwnvirt` --- Base class +------------------------------------------------- + + +.. automodule:: pwnlib.virtualization.pwnvirt + + .. autoclass:: pwnlib.virtualization.pwnvirt.pwnvirt() + :members: diff --git a/docs/source/virtualization/sshvirt.rst b/docs/source/virtualization/sshvirt.rst new file mode 100644 index 000000000..8fe8804e2 --- /dev/null +++ b/docs/source/virtualization/sshvirt.rst @@ -0,0 +1,11 @@ +.. testsetup:: * + + from pwn import * + context.arch = 'amd64' + context.terminal = [os.path.join(os.path.dirname(pwnlib.__file__), 'gdb_faketerminal.py')] + +:mod:`pwnlib.virtualization.sshvirt` --- Working with Sshvirt +====================================== + +.. automodule:: pwnlib.virtualization.sshvirt + :members: diff --git a/pwn/toplevel.py b/pwn/toplevel.py index 92b01ad1d..d5658f944 100644 --- a/pwn/toplevel.py +++ b/pwn/toplevel.py @@ -70,6 +70,7 @@ from pwnlib.util.sh_string import sh_string, sh_prepare, sh_command_with from pwnlib.util.splash import * from pwnlib.util.web import * +from pwnlib.virtualization.sshvirt import sshvirt # Promote these modules, so that "from pwn import *" will let you access them diff --git a/pwnlib/tubes/ssh.py b/pwnlib/tubes/ssh.py index 5463df518..29942ece1 100644 --- a/pwnlib/tubes/ssh.py +++ b/pwnlib/tubes/ssh.py @@ -597,6 +597,9 @@ def __init__(self, user=None, host=None, port=22, password=None, key=None, NOTE: The proxy_command and proxy_sock arguments is only available if a fairly new version of paramiko is used. + Note: alternativly use :meth:`.virtulization.sshvirt`. + + Example proxying: .. doctest:: diff --git a/pwnlib/virtualization/__init__.py b/pwnlib/virtualization/__init__.py new file mode 100644 index 000000000..91767ab4a --- /dev/null +++ b/pwnlib/virtualization/__init__.py @@ -0,0 +1,3 @@ +from __future__ import absolute_import + +__all__ = ['sshvirt'] \ No newline at end of file diff --git a/pwnlib/virtualization/pwnvirt.py b/pwnlib/virtualization/pwnvirt.py new file mode 100644 index 000000000..d1ef676fc --- /dev/null +++ b/pwnlib/virtualization/pwnvirt.py @@ -0,0 +1,368 @@ +import os +from abc import ABC, abstractmethod +from shutil import which +from typing import Union, Iterable, List + +import pwnlib.args +import pwnlib.filesystem +import pwnlib.gdb +import pwnlib.tubes + +log = pwnlib.log.getLogger(__name__) + + +class pwnvirt(ABC): + """ + start binary inside virtualized environment and return pwnlib.tubes.process.process using Pwnvirt.start() + + Arguments: + + binary(str): + binary for virtualization debugging + files(list): + other files or directories that need to be uploaded to VM + packages(list): + packages to install on vm + symbols(bool): + additionally install libc6 debug symbols + tmp(bool): + if a temporary directory should be created for files + gdb_port(int): + specify static gdbserver port + fast(bool): + mounts libs locally for faster symbol extraction (experimental) + """ + LOCAL_DIR = './.pwntools/' + HOME_DIR = os.path.expanduser('~/share/pwntools/') + SYSROOT = LOCAL_DIR + 'sysroot/' + LOCKFILE = LOCAL_DIR + 'vagd.lock' + SYSROOT_LIB = SYSROOT + 'lib/' + SYSROOT_LIB_DEBUG = SYSROOT + 'lib/debug' + KEYFILE = HOME_DIR + 'keyfile' + PUBKEYFILE = KEYFILE + '.pub' + DEFAULT_PORT = 2222 + STATIC_GDBSRV_PORT = 42069 + + is_new: bool = False + _path: str + _gdb_port: int + _binary: str + _ssh: pwnlib.tubes.ssh.ssh + _fast: bool + + def __init__(self, + binary: str, + libs=False, + files: Union[str, list[str]] = None, + packages: List[str] = None, + symbols=True, + tmp: bool = False, + gdb_port: int = 0, + fast: bool = False): + + self._path = binary + self._gdb_port = gdb_port + self._binary = './' + os.path.basename(binary) + + pwnlib.context.context.ssh_session = self._ssh + + if tmp: + self._ssh.set_working_directory() + + if self._sync(self._path): + self.system('chmod +x ' + self._binary) + + if self.is_new and libs: + if not (os.path.exists(pwnvirt.LIBS_DIRECTORY)): + os.makedirs(pwnvirt.LIBS_DIRECTORY) + + self.libs(pwnvirt.LIBS_DIRECTORY) + + if self.is_new and packages is not None: + if symbols: + packages.append(pwnvirt.LIBC6_DEBUG) + try: + elf = pwnlib.elf.ELF(binary) + if elf.arch == 'i386': + packages.append(pwnvirt.LIBC6_I386) + except: + log.warn("failed to get architecture from binary") + self._install_packages(packages) + + self._fast = fast + + if self._fast: + self._mount_lib() + + # Copy files to remote + if isinstance(files, str): + self._sync(files) + elif hasattr(files, '__iter__'): + for file in files: + self._sync(file) + + @abstractmethod + def _vm_setup(self) -> None: + """ + setup virtualized machine + """ + pass + + @abstractmethod + def _ssh_setup(self) -> None: + """ + setup ssh connection + """ + pass + + def _sync(self, file: str) -> bool: + """ + upload file on remote if it doesn't exist + Arguments: + + file(str): + file to upload + + Returns: + if the file was uploaded + """ + sshpath = pwnlib.filesystem.SSHPath(file) + if not sshpath.exists(): + self.put(file) + return True + return False + + _SSHFS_TEMPLATE = \ + 'sshfs -p {port} -o StrictHostKeyChecking=no,ro,IdentityFile={keyfile} {user}@{host}:{remote_dir} {local_dir}' + + def _mount(self, remote_dir: str, local_dir: str) -> None: + """ + mount remote dir on locally using sshfs + + Arguments: + + remote_dir(str): + directory on remote to mount + local_dir(str): + local mount point + """ + if not which('sshfs'): + log.error('sshfs isn\'t installed') + cmd = pwnvirt._SSHFS_TEMPLATE.format(port=self._ssh.port, + keyfile=self._ssh.keyfile, + user=self._ssh.user, + host=self._ssh.host, + remote_dir=remote_dir, + local_dir=local_dir) + log.info(cmd) + os.system(cmd) + + def _lock(self, typ: str) -> None: + """ + create lock file vor current virtualization type + + Arguments: + + typ(str): + the type of virtualization + """ + if not os.path.exists(pwnvirt.LOCAL_DIR): + os.makedirs(pwnvirt.LOCAL_DIR) + + with open(pwnvirt.LOCKFILE, 'w') as lfile: + lfile.write(typ) + + def _mount_lib(self, remote_lib: str = '/usr/lib') -> None: + """ + mount the lib directory of remote + + Arguments: + + remote_lib(str): + the lib directory to mount locally + """ + if not (os.path.exists(pwnvirt.SYSROOT) and os.path.exists(pwnvirt.SYSROOT_LIB)): + os.makedirs(pwnvirt.SYSROOT_LIB) + if not os.path.ismount(pwnvirt.SYSROOT_LIB): + log.info('mounting libs in sysroot') + self._mount(remote_lib, pwnvirt.SYSROOT_LIB) + + def system(self, cmd: str) -> pwnlib.tubes.ssh.ssh_channel: + """ + executes command on vm, interface to :class: pwnlib.tubes.ssh.ssh.system + + Arguments: + + cmd(str): + command to execute on virtualized environment + + Returns: + + :class:`pwnlib.tubes.ssh.ssh_channel.SSHChannel` + """ + return self._ssh.system(cmd) + + DEFAULT_PACKAGES = ['gdbserver', 'python3', 'sudo'] + LIBC6_DEBUG = 'libc6-dbg' + LIBC6_I386 = 'libc6-i386' + + def _install_packages(self, packages: Iterable) -> None: + """ + install packages on remote machine + + Arguments: + + packages(list): + packages to install on remote machine + """ + self.system("sudo apt update").recvall() + packages_str = " ".join(packages) + self.system(f"sudo NEEDRESTART_MODE=a apt install -y {packages_str}").recvall() + + def put(self, file: str, remote: str = None) -> None: + """ + upload file or dir on vm + + Arguments: + + file(str): + file to upload + remote(str): + remote location of file, working directory if not specified + """ + if os.path.isdir(file): + self._ssh.upload_dir(file, remote=remote) + else: + self._ssh.upload(file, remote=remote) + + def pull(self, file: str, local: str = None) -> None: + """ + download file or dir on vm + + Arguments: + + file(str): + remote location of file, working directory if not specified + local(str): + local location of file, current directory if not specified + """ + sshpath = pwnlib.filesystem.SSHPath(os.path.basename(file)) + if sshpath.is_dir(): + self._ssh.download_dir(file, local=local) + else: + self._ssh.download_file(file, local=local) + + LIBS_DIRECTORY = "libs" + + def close(self): + """ + closing vm + """ + self._ssh.close() + + def libs(self, directory=None) -> None: + """ + Downloads the libraries referred to by a file. + This is done by running ldd on the remote server, parsing the output and downloading the relevant files. + + Arguments: + + directory(str): + Output directory + """ + for lib in self._ssh._libs_remote(self._binary).keys(): + self.pull(lib, directory + '/' + os.path.basename(lib)) + + def debug(self, argv: list[str] = None, ssh=None, gdb_args=None, gdbscript='', sysroot=None, + **kwargs) -> pwnlib.tubes.process.process: + """ + run binary in vm with gdb (pwnlib feature set) + + Arguments: + + argv(list): + comandline arguments for binary + ssh(None): + ignored self._ssh is used instead + gdb_args(list): + gdb args to forward to gdb + gdbscript(str): + GDB script for GDB + sysroot(str): + sysroot dir + \**kwargs: + passthrough arguments to pwnlib.gdb.debug + + Returns: + :class:`pwnlib.tubes.process.process` + """ + + if argv is None: + argv = list() + + if gdb_args is None: + gdb_args = list() + + if self._fast: + if sysroot is not None: + log.warn('fast enabled but sysroot set, sysroot is ignored') + sysroot = pwnvirt.SYSROOT_LIB + + if sysroot is not None: + gdbscript = f"set debug-file-directory {pwnvirt.SYSROOT_LIB_DEBUG}\n" + gdbscript + + gdb_args += ["-ex", f"file -readnow {self._path}"] + + return pwnlib.gdb.debug([self._binary] + argv, ssh=self._ssh, gdb_args=gdb_args, port=self._gdb_port, + gdbscript=gdbscript, sysroot=sysroot, **kwargs) + + def process(self, argv: list[str] = None, **kwargs) -> pwnlib.tubes.process.process: + """ + run binary in vm as process + + Arguments: + + argv(list): + commandline arguments for binary + \**kwargs: + passthrough arguments to pwnlib.ssh.ssh.process + + Returns: + :class:`pwnlib.tubes.process.process` + """ + if argv is None: + argv = list() + return self._ssh.process([self._binary] + argv, **kwargs) + + def start(self, + argv: list[str] = None, + gdbscript: str = '', + api: bool = None, + sysroot: str = None, + gdb_args: list = None, + **kwargs) -> pwnlib.tubes.process.process: + """ + start binary on remote and return pwnlib.tubes.process.process + + Arguments: + argv(list): + commandline arguments for binary + gdbscript(str): + GDB script for GDB + api(bool): + if GDB API should be enabled + sysroot(str): + sysroot dir + gdb_args(list): + extra gdb args + \**kwargs: + passthrough arguments + + Returns: + :class:`pwnlib.tubes.process.process` + """ + if pwnlib.args.args.GDB: + return self.debug(argv=argv, gdbscript=gdbscript, gdb_args=gdb_args, sysroot=sysroot, + api=api, **kwargs) + else: + return self.process(argv=argv, **kwargs) diff --git a/pwnlib/virtualization/sshvirt.py b/pwnlib/virtualization/sshvirt.py new file mode 100644 index 000000000..a73a95733 --- /dev/null +++ b/pwnlib/virtualization/sshvirt.py @@ -0,0 +1,143 @@ +import time +import pwnlib.tubes.ssh +from pwnlib.virtualization.pwnvirt import pwnvirt + +log = pwnlib.log.getLogger(__name__) + + +class sshvirt(pwnvirt): + r""" + ssh virtualization interface for pwntools + + Arguments: + binary(str): + binary to execute + user(str): + ssh user + host(str): + ssh hostname + port(int): + ssh port + keyfile(str): + ssh keyfile + password(str): + ssh password + ignore_config(bool): + If :const:`True`, disable usage of ~/.ssh/config and ~/.ssh/authorized_keys + \**kwargs: + Passthrough arguments to :class: Pwnvirt + + :meth:`.ssh.process`. + Examples: + + >>> with open('test', 'w') as f: + ... _ = f.write('#!/bin/echo') + >>> vm = sshvirt('./test', user='travis', host='example.pwnme', password='demopass') + >>> vm.system('ls ./test').recvall(timeout=1) + b'./test\n' + + >>> io = vm.process() + >>> io.recvall(timeout=1) + b'./test\n' + + >>> io.close() + + >>> io = vm.debug(api=True) + >>> bp = io.gdb.Breakpoint('write', temporary=True) + >>> io.gdb.continue_and_wait() + >>> count = io.gdb.parse_and_eval('$rdx') + >>> long = io.gdb.lookup_type('long') + >>> int(count.cast(long)) + 7 + >>> io.gdb.continue_nowait() + >>> io.recvline(timeout=1) + b'./test\n' + >>> io.close() + >>> vm.close() + """ + + DEFAULT_HOST = 'localhost' + DEFAULT_PORT = 22 + DEFAULT_USER = 'root' + + _user: str + _host: str + _port: int + _keyfile: str + _password: str + _ssh: pwnlib.tubes.ssh.ssh + + def __init__(self, + binary: str, + user: str = DEFAULT_USER, + host: str = DEFAULT_HOST, + port: int = DEFAULT_PORT, + keyfile: str = None, + password: str = None, + ignore_config: bool = False, + **kwargs): + self._user = user + self._host = host + self._port = port + self._keyfile = keyfile + self._password = password + self._ignore_config = ignore_config + + self._ssh_setup() + + super().__init__(binary=binary, **kwargs) + + def bind(self, port: int) -> int: + """ + bind port from ssh connection locally + :param port: + :return: + """ + + remote = self._ssh.connect_remote('127.0.0.1', port) + listener = pwnlib.tubes.listen.listen(0) + port = listener.lport + + # Disable showing GDB traffic when debugging verbosity is increased + remote.level = 'error' + listener.level = 'error' + + # Hook them up + remote.connect_both(listener) + + return port + + def _vm_setup(self) -> None: + """ + pass + """ + pass + + _TRIES = 3 # three times the charm + + def _ssh_setup(self) -> None: + """ + setup ssh connection + """ + progress = log.progress("connecting to ssh") + for i in range(sshvirt._TRIES): + try: + self._ssh = pwnlib.tubes.ssh.ssh( + user=self._user, + host=self._host, + port=self._port, + password=self._password, + keyfile=self._keyfile, + ignore_config=self._ignore_config + ) + progress.success("Done") + break + except: + if i + 1 >= sshvirt._TRIES: + progress.failure('Failed') + log.error("Failed to connect to ssh") + else: + progress.status('Trying again') + # shorter pause for first two tries + time.sleep(1 if i == 0 else 10) + From 8ab82a7bae952d1fa210f99830f23310c8899417 Mon Sep 17 00:00:00 2001 From: gfelber <34159565+gfelber@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:17:47 +0200 Subject: [PATCH 2/5] updated changelog removed attribute typing (python 2 compatibility) --- CHANGELOG.md | 2 ++ pwnlib/virtualization/pwnvirt.py | 14 ++++++++------ pwnlib/virtualization/sshvirt.py | 12 ++++++------ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05d6575a3..9fcd100b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ The table below shows which release corresponds to each branch, and what date th - [#2405][2405] Add "none" ssh authentication method - [#2427][2427] Document behaviour of remote()'s sni argument as string. - [#2382][2382] added optional port, gdb_args and gdbserver_args parameters to gdb.debug() +- [#2434][2434] upstreaming [vagd](https://github.com/gfelber/vagd) functionality #2434 [2360]: https://github.com/Gallopsled/pwntools/pull/2360 [2356]: https://github.com/Gallopsled/pwntools/pull/2356 @@ -105,6 +106,7 @@ The table below shows which release corresponds to each branch, and what date th [2405]: https://github.com/Gallopsled/pwntools/pull/2405 [2427]: https://github.com/Gallopsled/pwntools/pull/2405 [2382]: https://github.com/Gallopsled/pwntools/pull/2382 +[2434]: https://github.com/Gallopsled/pwntools/pull/2434 ## 4.13.0 (`beta`) diff --git a/pwnlib/virtualization/pwnvirt.py b/pwnlib/virtualization/pwnvirt.py index d1ef676fc..0e62dbdd3 100644 --- a/pwnlib/virtualization/pwnvirt.py +++ b/pwnlib/virtualization/pwnvirt.py @@ -43,12 +43,14 @@ class pwnvirt(ABC): DEFAULT_PORT = 2222 STATIC_GDBSRV_PORT = 42069 - is_new: bool = False - _path: str - _gdb_port: int - _binary: str - _ssh: pwnlib.tubes.ssh.ssh - _fast: bool + #: if the pwnvirt was newly created (``bool``) + is_new = False + + _path = None + _gdb_port = None + _binary = None + _ssh = None + _fast = False def __init__(self, binary: str, diff --git a/pwnlib/virtualization/sshvirt.py b/pwnlib/virtualization/sshvirt.py index a73a95733..2d30c2007 100644 --- a/pwnlib/virtualization/sshvirt.py +++ b/pwnlib/virtualization/sshvirt.py @@ -60,12 +60,12 @@ class sshvirt(pwnvirt): DEFAULT_PORT = 22 DEFAULT_USER = 'root' - _user: str - _host: str - _port: int - _keyfile: str - _password: str - _ssh: pwnlib.tubes.ssh.ssh + _user = None + _host = None + _port = 0 + _keyfile = None + _password = None + _ssh = None def __init__(self, binary: str, From 08fe9d48a03b659641f26588823a67f04cf76b7b Mon Sep 17 00:00:00 2001 From: gfelber <34159565+gfelber@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:33:42 +0200 Subject: [PATCH 3/5] more python 2 compatibility fixes --- pwnlib/virtualization/pwnvirt.py | 65 +++++++++++++++----------------- pwnlib/virtualization/sshvirt.py | 21 +++++------ 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/pwnlib/virtualization/pwnvirt.py b/pwnlib/virtualization/pwnvirt.py index 0e62dbdd3..ce3b64730 100644 --- a/pwnlib/virtualization/pwnvirt.py +++ b/pwnlib/virtualization/pwnvirt.py @@ -1,7 +1,5 @@ import os -from abc import ABC, abstractmethod from shutil import which -from typing import Union, Iterable, List import pwnlib.args import pwnlib.filesystem @@ -10,8 +8,8 @@ log = pwnlib.log.getLogger(__name__) - -class pwnvirt(ABC): +# abstract class +class pwnvirt(): """ start binary inside virtualized environment and return pwnlib.tubes.process.process using Pwnvirt.start() @@ -53,14 +51,14 @@ class pwnvirt(ABC): _fast = False def __init__(self, - binary: str, + binary, libs=False, - files: Union[str, list[str]] = None, - packages: List[str] = None, + files=None, + packages=None, symbols=True, - tmp: bool = False, - gdb_port: int = 0, - fast: bool = False): + tmp=False, + gdb_port=0, + fast=False): self._path = binary self._gdb_port = gdb_port @@ -103,21 +101,19 @@ def __init__(self, for file in files: self._sync(file) - @abstractmethod - def _vm_setup(self) -> None: + def _vm_setup(self): """ setup virtualized machine """ pass - @abstractmethod - def _ssh_setup(self) -> None: + def _ssh_setup(self): """ setup ssh connection """ pass - def _sync(self, file: str) -> bool: + def _sync(self, file): """ upload file on remote if it doesn't exist Arguments: @@ -137,7 +133,7 @@ def _sync(self, file: str) -> bool: _SSHFS_TEMPLATE = \ 'sshfs -p {port} -o StrictHostKeyChecking=no,ro,IdentityFile={keyfile} {user}@{host}:{remote_dir} {local_dir}' - def _mount(self, remote_dir: str, local_dir: str) -> None: + def _mount(self, remote_dir, local_dir): """ mount remote dir on locally using sshfs @@ -159,7 +155,7 @@ def _mount(self, remote_dir: str, local_dir: str) -> None: log.info(cmd) os.system(cmd) - def _lock(self, typ: str) -> None: + def _lock(self, typ): """ create lock file vor current virtualization type @@ -174,7 +170,7 @@ def _lock(self, typ: str) -> None: with open(pwnvirt.LOCKFILE, 'w') as lfile: lfile.write(typ) - def _mount_lib(self, remote_lib: str = '/usr/lib') -> None: + def _mount_lib(self, remote_lib='/usr/lib'): """ mount the lib directory of remote @@ -189,7 +185,7 @@ def _mount_lib(self, remote_lib: str = '/usr/lib') -> None: log.info('mounting libs in sysroot') self._mount(remote_lib, pwnvirt.SYSROOT_LIB) - def system(self, cmd: str) -> pwnlib.tubes.ssh.ssh_channel: + def system(self, cmd): """ executes command on vm, interface to :class: pwnlib.tubes.ssh.ssh.system @@ -208,7 +204,7 @@ def system(self, cmd: str) -> pwnlib.tubes.ssh.ssh_channel: LIBC6_DEBUG = 'libc6-dbg' LIBC6_I386 = 'libc6-i386' - def _install_packages(self, packages: Iterable) -> None: + def _install_packages(self, packages): """ install packages on remote machine @@ -219,9 +215,9 @@ def _install_packages(self, packages: Iterable) -> None: """ self.system("sudo apt update").recvall() packages_str = " ".join(packages) - self.system(f"sudo NEEDRESTART_MODE=a apt install -y {packages_str}").recvall() + self.system("sudo NEEDRESTART_MODE=a apt install -y {}".format(packages_str)).recvall() - def put(self, file: str, remote: str = None) -> None: + def put(self, file, remote=None): """ upload file or dir on vm @@ -237,7 +233,7 @@ def put(self, file: str, remote: str = None) -> None: else: self._ssh.upload(file, remote=remote) - def pull(self, file: str, local: str = None) -> None: + def pull(self, file, local=None): """ download file or dir on vm @@ -262,7 +258,7 @@ def close(self): """ self._ssh.close() - def libs(self, directory=None) -> None: + def libs(self, directory=None): """ Downloads the libraries referred to by a file. This is done by running ldd on the remote server, parsing the output and downloading the relevant files. @@ -275,8 +271,7 @@ def libs(self, directory=None) -> None: for lib in self._ssh._libs_remote(self._binary).keys(): self.pull(lib, directory + '/' + os.path.basename(lib)) - def debug(self, argv: list[str] = None, ssh=None, gdb_args=None, gdbscript='', sysroot=None, - **kwargs) -> pwnlib.tubes.process.process: + def debug(self, argv=None, ssh=None, gdb_args=None, gdbscript='', sysroot=None, **kwargs): """ run binary in vm with gdb (pwnlib feature set) @@ -311,14 +306,14 @@ def debug(self, argv: list[str] = None, ssh=None, gdb_args=None, gdbscript='', s sysroot = pwnvirt.SYSROOT_LIB if sysroot is not None: - gdbscript = f"set debug-file-directory {pwnvirt.SYSROOT_LIB_DEBUG}\n" + gdbscript + gdbscript = "set debug-file-directory {}\n".format(pwnvirt.SYSROOT_LIB_DEBUG) + gdbscript - gdb_args += ["-ex", f"file -readnow {self._path}"] + gdb_args += ["-ex", "file -readnow {}".format(self._path)] return pwnlib.gdb.debug([self._binary] + argv, ssh=self._ssh, gdb_args=gdb_args, port=self._gdb_port, gdbscript=gdbscript, sysroot=sysroot, **kwargs) - def process(self, argv: list[str] = None, **kwargs) -> pwnlib.tubes.process.process: + def process(self, argv=None, **kwargs): """ run binary in vm as process @@ -337,12 +332,12 @@ def process(self, argv: list[str] = None, **kwargs) -> pwnlib.tubes.process.proc return self._ssh.process([self._binary] + argv, **kwargs) def start(self, - argv: list[str] = None, - gdbscript: str = '', - api: bool = None, - sysroot: str = None, - gdb_args: list = None, - **kwargs) -> pwnlib.tubes.process.process: + argv=None, + gdbscript='', + api=None, + sysroot=None, + gdb_args=None, + **kwargs): """ start binary on remote and return pwnlib.tubes.process.process diff --git a/pwnlib/virtualization/sshvirt.py b/pwnlib/virtualization/sshvirt.py index 2d30c2007..e3c6bf586 100644 --- a/pwnlib/virtualization/sshvirt.py +++ b/pwnlib/virtualization/sshvirt.py @@ -68,13 +68,13 @@ class sshvirt(pwnvirt): _ssh = None def __init__(self, - binary: str, - user: str = DEFAULT_USER, - host: str = DEFAULT_HOST, - port: int = DEFAULT_PORT, - keyfile: str = None, - password: str = None, - ignore_config: bool = False, + binary, + user=DEFAULT_USER, + host=DEFAULT_HOST, + port=DEFAULT_PORT, + keyfile=None, + password=None, + ignore_config=False, **kwargs): self._user = user self._host = host @@ -87,7 +87,7 @@ def __init__(self, super().__init__(binary=binary, **kwargs) - def bind(self, port: int) -> int: + def bind(self, port): """ bind port from ssh connection locally :param port: @@ -107,7 +107,7 @@ def bind(self, port: int) -> int: return port - def _vm_setup(self) -> None: + def _vm_setup(self): """ pass """ @@ -115,7 +115,7 @@ def _vm_setup(self) -> None: _TRIES = 3 # three times the charm - def _ssh_setup(self) -> None: + def _ssh_setup(self): """ setup ssh connection """ @@ -140,4 +140,3 @@ def _ssh_setup(self) -> None: progress.status('Trying again') # shorter pause for first two tries time.sleep(1 if i == 0 else 10) - From 822189e2a1414a5e9a388a0908ba57165e33ce67 Mon Sep 17 00:00:00 2001 From: gfelber <34159565+gfelber@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:38:35 +0200 Subject: [PATCH 4/5] now using pwntools pwnlib.utils.misc.which --- pwnlib/virtualization/pwnvirt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnlib/virtualization/pwnvirt.py b/pwnlib/virtualization/pwnvirt.py index ce3b64730..5349de444 100644 --- a/pwnlib/virtualization/pwnvirt.py +++ b/pwnlib/virtualization/pwnvirt.py @@ -1,10 +1,10 @@ import os -from shutil import which import pwnlib.args import pwnlib.filesystem import pwnlib.gdb import pwnlib.tubes +import pwnlib.util.misc as misc log = pwnlib.log.getLogger(__name__) @@ -144,7 +144,7 @@ def _mount(self, remote_dir, local_dir): local_dir(str): local mount point """ - if not which('sshfs'): + if not misc.which('sshfs'): log.error('sshfs isn\'t installed') cmd = pwnvirt._SSHFS_TEMPLATE.format(port=self._ssh.port, keyfile=self._ssh.keyfile, From 7665af322b1918ab5a98855298b0ff962ed7fc87 Mon Sep 17 00:00:00 2001 From: gfelber <34159565+gfelber@users.noreply.github.com> Date: Sat, 3 Aug 2024 19:03:50 +0200 Subject: [PATCH 5/5] fixed python2 doctests --- docs/source/virtualization.rst | 8 ++++---- docs/source/virtualization/sshvirt.rst | 2 +- pwnlib/virtualization/pwnvirt.py | 2 +- pwnlib/virtualization/sshvirt.py | 18 ++++++++++++++++-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/source/virtualization.rst b/docs/source/virtualization.rst index 07027f2a9..cfbedf66a 100644 --- a/docs/source/virtualization.rst +++ b/docs/source/virtualization.rst @@ -3,13 +3,13 @@ from pwn import * :mod:`pwnlib.virtualization` --- Virtualizing your exploits -============================================= +============================================================= .. automodule:: pwnlib.tubes -Types of Tubes -------------------- +Types of Virtualization +---------------------------- .. toctree:: :maxdepth: 3 @@ -19,7 +19,7 @@ Types of Tubes :mod:`pwnlib.virtualization.pwnvirt` --- Base class -------------------------------------------------- +----------------------------------------------------- .. automodule:: pwnlib.virtualization.pwnvirt diff --git a/docs/source/virtualization/sshvirt.rst b/docs/source/virtualization/sshvirt.rst index 8fe8804e2..0b0024c71 100644 --- a/docs/source/virtualization/sshvirt.rst +++ b/docs/source/virtualization/sshvirt.rst @@ -5,7 +5,7 @@ context.terminal = [os.path.join(os.path.dirname(pwnlib.__file__), 'gdb_faketerminal.py')] :mod:`pwnlib.virtualization.sshvirt` --- Working with Sshvirt -====================================== +=============================================================== .. automodule:: pwnlib.virtualization.sshvirt :members: diff --git a/pwnlib/virtualization/pwnvirt.py b/pwnlib/virtualization/pwnvirt.py index 5349de444..ec54f00a9 100644 --- a/pwnlib/virtualization/pwnvirt.py +++ b/pwnlib/virtualization/pwnvirt.py @@ -9,7 +9,7 @@ log = pwnlib.log.getLogger(__name__) # abstract class -class pwnvirt(): +class pwnvirt(object): """ start binary inside virtualized environment and return pwnlib.tubes.process.process using Pwnvirt.start() diff --git a/pwnlib/virtualization/sshvirt.py b/pwnlib/virtualization/sshvirt.py index e3c6bf586..925abb117 100644 --- a/pwnlib/virtualization/sshvirt.py +++ b/pwnlib/virtualization/sshvirt.py @@ -28,7 +28,7 @@ class sshvirt(pwnvirt): Passthrough arguments to :class: Pwnvirt :meth:`.ssh.process`. - Examples: + Running as process (or using start()): >>> with open('test', 'w') as f: ... _ = f.write('#!/bin/echo') @@ -42,6 +42,18 @@ class sshvirt(pwnvirt): >>> io.close() + Running with gdb (or using start() and args.GDB): + + >>> io = vm.debug(gdbscript='continue') + >>> io.recvline(timeout=5) + b'./test\n' + >>> io.close() + + Running with gdb and api: + + .. doctest:: + :skipif: is_python2 + >>> io = vm.debug(api=True) >>> bp = io.gdb.Breakpoint('write', temporary=True) >>> io.gdb.continue_and_wait() @@ -53,6 +65,8 @@ class sshvirt(pwnvirt): >>> io.recvline(timeout=1) b'./test\n' >>> io.close() + + Closing vm (optional): >>> vm.close() """ @@ -85,7 +99,7 @@ def __init__(self, self._ssh_setup() - super().__init__(binary=binary, **kwargs) + super(sshvirt, self).__init__(binary=binary, **kwargs) def bind(self, port): """