Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions pwncat/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def run(self, manager: "pwncat.manager.Manager", args: "argparse.Namespace"):
import pkgutil
import termios
import argparse
import importlib.util
from io import TextIOWrapper
from enum import Enum, auto
from typing import Dict, List, Type, Callable, Iterable
Expand Down Expand Up @@ -432,11 +433,14 @@ def __init__(self, manager: "pwncat.manager.Manager"):
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
if module_name == "base":
continue
self.commands.append(
loader.find_module(module_name)
.load_module(module_name)
.Command(manager)
)

spec = importlib.util.find_spec(f"{__name__}.{module_name}")
if spec is None:
raise ImportError(f"Could not find spec for module {module_name}")

module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
self.commands.append(module.Command(manager))

self.prompt: PromptSession = None
self.toolbar: PromptSession = None
Expand Down
35 changes: 21 additions & 14 deletions pwncat/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import tempfile
import threading
import contextlib
import importlib.util
from io import TextIOWrapper
from enum import Enum, auto
from typing import Dict, List, Tuple, Union, Callable, Optional, Generator
Expand Down Expand Up @@ -922,31 +923,37 @@ def create_db_session(self):
return self.db.open()

def load_modules(self, *paths):
"""Dynamically load modules from the specified paths
"""
Dynamically load modules from the specified paths.

If a module has the same name as an already loaded module, it will
take it's place in the module list. This includes built-in modules.
take its place in the module list. This includes built-in modules.
"""

for loader, module_name, _ in pkgutil.walk_packages(
paths, prefix="pwncat.modules."
):
for loader, module_name, is_pkg in pkgutil.walk_packages(paths, prefix="pwncat.modules."):
# Locate the module spec
spec = importlib.util.find_spec(module_name)
if spec is None:
continue

# Why is this check *not* part of pkgutil??????? D:<
if module_name not in sys.modules:
module = loader.find_module(module_name).load_module(module_name)
# Always check `sys.modules` under `spec.name`.
# If it's already loaded, reuse that module; if not, load anew.
if spec.name in sys.modules:
module = sys.modules[spec.name]
else:
module = sys.modules[module_name]
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)

if getattr(module, "Module", None) is None:
# We only care about modules that actually define `Module`
if not hasattr(module, "Module"):
continue

# Create an instance of this module
module_name = module_name.split("pwncat.modules.")[1]
self.modules[module_name] = module.Module()
short_name = module_name.split("pwncat.modules.", 1)[1]
self.modules[short_name] = module.Module()
self.modules[short_name].name = short_name

# Store it's name so we know it later
setattr(self.modules[module_name], "name", module_name)

def log(self, *args, **kwargs):
"""Output a log entry"""
Expand Down
7 changes: 4 additions & 3 deletions pwncat/modules/linux/enumerate/escalate/append_passwd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import crypt

from passlib.hash import sha512_crypt

import pwncat
from pwncat.util import console
Expand Down Expand Up @@ -31,7 +32,7 @@ def escalate(self, session: "pwncat.manager.Session"):
shell = session.platform.getenv("SHELL")

# Hash the backdoor password
backdoor_hash = crypt.crypt(backdoor_pass, crypt.METHOD_SHA512)
backdoor_hash = sha512_crypt.using(salt_size=16).hash(backdoor_pass)

if not any(line.startswith(f"{backdoor_user}:") for line in passwd_contents):

Expand Down Expand Up @@ -85,4 +86,4 @@ def enumerate(self, session):
if ability.uid != 0:
continue

yield AppendPasswd(self.name, ability)
yield AppendPasswd(self.name, ability)
2 changes: 1 addition & 1 deletion pwncat/modules/linux/enumerate/file/suid.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@ def enumerate(self, session: "pwncat.manager.Session"):
)
)
finally:
proc.wait()
proc.wait()
10 changes: 7 additions & 3 deletions pwncat/modules/linux/enumerate/software/sudo/cve_2019_14287.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
from packaging import version
from packaging.version import parse, InvalidVersion

import pwncat
from pwncat.facts import build_gtfo_ability
Expand All @@ -26,7 +26,11 @@ def enumerate(self, session: "pwncat.manager.Session"):
return

# This vulnerability was patched in 1.8.28
if version.parse(sudo_info.version) >= version.parse("1.8.28"):
try:
parsed_version = parse(sudo_info.version)
if parsed_version >= parse("1.8.28"):
return
except InvalidVersion:
return

# Grab the current user/group
Expand Down Expand Up @@ -74,4 +78,4 @@ def enumerate(self, session: "pwncat.manager.Session"):
source_uid=current_user.id,
user="\\#-1",
spec=command,
)
)
5 changes: 3 additions & 2 deletions pwncat/modules/linux/implant/passwd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import crypt

from passlib.hash import sha512_crypt

import pwncat
from pwncat.facts import Implant, ImplantType
Expand Down Expand Up @@ -93,7 +94,7 @@ def install(

# Hash the password
yield Status("hashing password")
backdoor_hash = crypt.crypt(backdoor_pass, crypt.METHOD_SHA512)
backdoor_hash = sha512_crypt.using(salt_size=16).hash(backdoor_pass)

# Store the new line we are adding
new_line = f"""{backdoor_user}:{backdoor_hash}:0:0::/root:{shell}\n"""
Expand Down
11 changes: 5 additions & 6 deletions pwncat/platform/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,12 +515,11 @@ def __init__(
target = self

class RemotePath(base_path, Path):

_target = target
_stat = None

def __init__(self, *args):
base_path.__init__(*args)
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls, *args, **kwargs)
obj._target = target
obj._stat = None
return obj

self.Path = RemotePath
""" A concrete Path object for this platform conforming to pathlib.Path """
Expand Down
12 changes: 5 additions & 7 deletions pwncat/platform/linux.py
Original file line number Diff line number Diff line change
Expand Up @@ -1635,14 +1635,12 @@ def context_changed(self):

# Update self.shell just in case the user changed shells
try:
# Get the PID of the running shell
pid = self.getenv("$")
# Grab the path to the executable representing the shell
self.shell = self.Path("/proc", pid, "exe").readlink()
except (FileNotFoundError, PermissionError, OSError):
# Fall back to SHELL even though it's not really trustworthy
pid = self.getenv("$").strip()
proc_exe_path = f"/proc/{pid}/exe"
self.shell = self.readlink(proc_exe_path)
except (FileNotFoundError, PermissionError, OSError, AttributeError):
self.shell = self.getenv("SHELL")
if self.shell is None or self.shell == "":
if not self.shell:
self.shell = "/bin/sh"

# Refresh the currently tracked user and group IDs
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ addopts = "-v"

[tool.poetry]
name = "pwncat-cs"
version = "0.5.4"
version = "0.5.5"
description = "Reverse and bind shell automation framework"
authors = ["Caleb Stewart <caleb.stewart94@gmail.com>", "John Hammond"]
readme = "README.md"
Expand All @@ -32,7 +32,8 @@ pwncat-cs = "pwncat.__main__:main"
[tool.poetry.dependencies]
python = "^3.9"
netifaces = "^0.11.0"
packaging = "^20.9"
packaging = ">=23.0"
passlib = "^1.7.4"
prompt-toolkit = "^3.0.19"
pycryptodome = "^3.10.1"
requests = "^2.25.1"
Expand Down