Skip to content

Commit

Permalink
Implemented User enumeration
Browse files Browse the repository at this point in the history
  • Loading branch information
calebstewart committed May 2, 2021
1 parent 81e0005 commit 148c0ba
Show file tree
Hide file tree
Showing 60 changed files with 560 additions and 309 deletions.
3 changes: 1 addition & 2 deletions pwncat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@
from .config import Config
from .commands import parser
from .util import console
from .db import get_session
from .tamper import TamperManager

tamper: TamperManager = TamperManager()


def interactive(platform):
""" Run the interactive pwncat shell with the given initialized victim.
"""Run the interactive pwncat shell with the given initialized victim.
This function handles the pwncat and remote prompts and does not return
until explicitly exited by the user.
Expand Down
15 changes: 15 additions & 0 deletions pwncat/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,21 @@ def _type(command: "CommandDefinition", name: str):
return _type


def get_module_choices(command):
"""Yields a list of module choices to be used which command argument
choices to select a valid module for the current target"""

if command.manager.target is None:
return

yield from [
module.name.removeprefix("agnostic.").removeprefix(
command.manager.target.platform.name + "."
)
for module in command.manager.target.find_module("*")
]


class Parameter:
"""Generic parameter definition for commands.
Expand Down
1 change: 0 additions & 1 deletion pwncat/commands/connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
)

from pwncat.modules import PersistError
from pwncat.db import get_session


class Command(CommandDefinition):
Expand Down
25 changes: 15 additions & 10 deletions pwncat/commands/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@
from rich import box

import pwncat
from pwncat.commands.base import CommandDefinition, Complete, Parameter
from pwncat.commands.base import (
CommandDefinition,
Complete,
Parameter,
get_module_choices,
)
from pwncat.util import console


class Command(CommandDefinition):
""" View info about a module """

def get_module_choices(self):
if self.manager.target is None:
return

yield from [module.name for module in self.manager.target.find_module("*")]

PROG = "info"
ARGS = {
"module": Parameter(
Expand All @@ -37,15 +36,21 @@ def run(self, manager: "pwncat.manager.Manager", args):

if args.module:
try:
module = list(manager.target.find_module(args.module, exact=True))[0]
except IndexError:
module = next(manager.target.find_module(args.module, exact=True))
module_name = args.module
except StopIteration:
console.log(f"[red]error[/red]: {args.module}: no such module")
return
else:
module = manager.config.module
module_name = module.name.removeprefix("agnostic.")
if self.manager.target is not None:
module_name = module_name.removeprefix(
self.manager.target.platform.name + "."
)

console.print(
f"[bold underline]Module [cyan]{module.name}[/cyan][/bold underline]"
f"[bold underline]Module [cyan]{module_name}[/cyan][/bold underline]"
)
console.print(
textwrap.indent(textwrap.dedent(module.__doc__.strip("\n")), " ") + "\n"
Expand Down
13 changes: 6 additions & 7 deletions pwncat/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
import pwncat
import pwncat.modules
from pwncat.util import console
from pwncat.commands.base import CommandDefinition, Complete, Parameter
from pwncat.commands.base import (
CommandDefinition,
Complete,
Parameter,
get_module_choices,
)


class Command(CommandDefinition):
Expand All @@ -22,12 +27,6 @@ class Command(CommandDefinition):
arguments, you can use the `info` command.
"""

def get_module_choices(self):
if self.manager.target is None:
return

yield from [module.name for module in self.manager.target.find_module("*")]

PROG = "run"
ARGS = {
"--raw,-r": Parameter(
Expand Down
15 changes: 8 additions & 7 deletions pwncat/commands/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@
class Command(CommandDefinition):
""" View info about a module """

def get_module_choices(self):
if self.manager.target is None:
return

yield from [module.name for module in self.manager.target.find_module("*")]

PROG = "search"
ARGS = {
"module": Parameter(
Expand All @@ -42,8 +36,15 @@ def run(self, manager: "pwncat.manager.Manager", args):
# the easiest way to do that, so we use a large size for
# width.
description = module.__doc__ if module.__doc__ is not None else ""
module_name = module.name.removeprefix("agnostic.")

if self.manager.target is not None:
module_name = module_name.removeprefix(
self.manager.target.platform.name + "."
)

table.add_row(
f"[cyan]{module.name}[/cyan]",
f"[cyan]{module_name}[/cyan]",
textwrap.shorten(
description.replace("\n", " "), width=200, placeholder="..."
),
Expand Down
1 change: 0 additions & 1 deletion pwncat/commands/set.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import pwncat
from pwncat.commands.base import CommandDefinition, Complete, Parameter
from pwncat.util import console, State
from pwncat.db import get_session, reset_engine


class Command(CommandDefinition):
Expand Down
10 changes: 6 additions & 4 deletions pwncat/commands/use.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
#!/usr/bin/env python3

import pwncat
from pwncat.commands.base import CommandDefinition, Complete, Parameter
from pwncat.commands.base import (
CommandDefinition,
Complete,
Parameter,
get_module_choices,
)
from pwncat.util import console


class Command(CommandDefinition):
""" Set the currently used module in the config handler """

def get_module_choices(self):
yield from [module.name for module in self.manager.target.find_module("*")]

PROG = "use"
ARGS = {
"module": Parameter(
Expand Down
4 changes: 1 addition & 3 deletions pwncat/db/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
#!/usr/bin/env python3

import pwncat
from pwncat.db.base import Base
from pwncat.db.binary import Binary
from pwncat.db.history import History
from pwncat.db.host import Host
from pwncat.db.persist import Persistence
from pwncat.db.suid import SUID
from pwncat.db.tamper import Tamper
from pwncat.db.user import User, Group, SecondaryGroupAssociation
from pwncat.db.user import User, Group
from pwncat.db.fact import Fact
39 changes: 26 additions & 13 deletions pwncat/db/fact.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
#!/usr/bin/env python3
from typing import Optional

import persistent
from typing import Optional
from persistent.list import PersistentList

from pwncat.modules import Result


class Fact(Result, persistent.Persistent):
"""Abstract enumerated facts about an enumerated target. Individual
enumeration modules will create subclasses containing the data for
the fact. A generic fact is guaranteed to have a list of types, a
module source, a __repr__ implementation, a __str__ implementation.
class Fact(persistent.Persistent):
"""Store enumerated facts. The pwncat.enumerate.Fact objects are pickled and
stored in the "data" column. The enumerator is arbitrary, but allows for
organizations based on the source enumerator."""
By default, a category property is defined which is the first type
in the list of types. This can be overloaded if needed, and is used
when formatted and displaying enumeration results.
def __init__(self, arg_type, source):
Lastly, if the description property is not None, it indicates that
the fact has a "long form" description as opposed to a single-line
content. This only effects the way reports are generated.
"""

def __init__(self, types, source):
super().__init__()

if not isinstance(types, PersistentList):
types = PersistentList(types)

# The type of fact (e.g.., "system.user")
self.type: Optional[str] = arg_type
self.types: PersistentList = types
# The original procedure that found this fact
self.source: Optional[str] = source

# The original SQLAlchemy-style code held a property, "data",
# which was a pickle object. We will re-implement that as a subclass
# but that may need to include the class properties used previously.
self.source: str = source

@property
def category(self) -> str:
return f"{self.type}"
return f"{self.types[0]} facts"
63 changes: 38 additions & 25 deletions pwncat/db/user.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,51 @@
#!/usr/bin/env python3

import persistent
import persistent.list
from typing import Optional

from persistent.list import PersistentList

from pwncat.db.fact import Fact

class Group(persistent.Persistent):
"""
Stores a record of changes on the target (i.e., things that have been
tampered with)
"""

def __init__(self, name, members):
class Group(Fact):
"""Basic representation of a user group on the target system. Individual
platform enumeration modules may subclass this to implement other user
properties as needed for their platform."""

self.name: Optional[str] = name
self.members: persistent.list.PersistentList = persistent.list.PersistentList()
def __init__(self, source: str, name: str, gid, members):
super().__init__(["group"], source)

self.name: str = name
self.id = gid
self.members: PersistentList = PersistentList(members)

def __repr__(self):
return f"""Group(gid={self.id}, name={repr(self.name)}), members={repr(",".join(m.name for m in self.members))})"""
return f"""Group(gid={self.id}, name={repr(self.name)}), members={repr(",".join(m for m in self.members))})"""


class User(Fact):
"""Basic representation of a user on the target system. Individual platform
enumeration modules may subclass this to implement other user properties as
needed for their platform."""

class User(persistent.Persistent):
def __init__(self, name, gid, fullname, homedir, password, hash, shell, groups):
def __init__(
self,
source: str,
name,
uid,
password: Optional[str] = None,
hash: Optional[str] = None,
):
super().__init__(["user"], source)

self.name: Optional[str] = name
self.gid: Optional[int] = gid
self.fullname: Optional[str] = fullname
self.homedir: Optional[str] = homedir
self.password: Optional[str] = password
self.hash: Optional[str] = hash
self.shell: Optional[str] = shell
self.groups: persistent.list.PersistentList = persistent.list.PersistentList(
groups
)
self.name: str = name
self.id = uid
self.password: Optional[str] = None
self.hash: Optional[str] = None

def __repr__(self):
return f"""User(uid={self.id}, gid={self.gid}, name={repr(self.name)})"""
if self.password is None and self.hash is None:
return f"""User(uid={self.id}, name={repr(self.name)})"""
elif self.password is not None:
return f"""User(uid={repr(self.id)}, name={repr(self.name)}, password={repr(self.password)})"""
else:
return f"""User(uid={repr(self.id)}, name={repr(self.name)}, hash={repr(self.hash)})"""
Loading

0 comments on commit 148c0ba

Please sign in to comment.