mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 10:54:14 +01:00
Added windows local user and group enumeration
Also added markdown table generator/jinja filter for report generation.
This is currentl the best I can do since commonmark (and therefore rich)
doesn't support tables at the moment. 😭
This commit is contained in:
parent
830fe7b211
commit
a15577892d
@ -5,13 +5,14 @@ The report was generated on {{ datetime }}.
|
||||
|
||||
## Common System Information
|
||||
|
||||
| Platform | {{ platform.name }} |
|
||||
|--------------|---------------------|
|
||||
| Architecture | {{ session.run("enumerate", types=["system.arch"]) | first_or_none | title_or_unknown }} |
|
||||
| Hostname | {{ session.run("enumerate", types=["system.hostname"]) | first_or_none | title_or_unknown }} |
|
||||
| ASLR | {{ session.run("enumerate", types=["system.aslr"]) | first_or_none | title_or_unknown }} |
|
||||
| Container | {{ session.run("enumerate", types=["system.container"]) | first_or_none | title_or_unknown }} |
|
||||
| Distribution | {{ session.run("enumerate", types=["system.distro"]) | first_or_none | title_or_unknown }} |
|
||||
{{ [
|
||||
["**Platform**", platform.name],
|
||||
["**Architecture**", session.run("enumerate", types=["system.arch"]) | first_or_none | title_or_unknown ],
|
||||
["**Hostname**", session.run("enumerate", types=["system.hostname"]) | first_or_none | title_or_unknown],
|
||||
["**ASLR**", session.run("enumerate", types=["system.aslr"]) | first_or_none | title_or_unknown],
|
||||
["**Container**", session.run("enumerate", types=["system.container"]) | first_or_none | title_or_unknown],
|
||||
["**Distribution**", session.run("enumerate", types=["system.distro"]) | first_or_none | title_or_unknown],
|
||||
] | table(headers=False) }}
|
||||
|
||||
{% if session.run("enumerate", types=["implant.*"]) %}
|
||||
## Installed Implants
|
||||
|
@ -0,0 +1,8 @@
|
||||
{% extends "generic.md" %}
|
||||
|
||||
{% block platform %}
|
||||
## Windows Specific Info!
|
||||
|
||||
{{ [["Hello", "World"], ["Goodbye", "World"]] | table(headers=True) }}
|
||||
|
||||
{% endblock %}
|
62
pwncat/facts/windows.py
Normal file
62
pwncat/facts/windows.py
Normal file
@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
from pwncat.facts import User, Group
|
||||
|
||||
|
||||
class WindowsUser(User):
|
||||
""" Windows-specific user """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source: str,
|
||||
name: str,
|
||||
uid: str,
|
||||
account_expires: Optional[datetime],
|
||||
description: str,
|
||||
enabled: bool,
|
||||
full_name: str,
|
||||
password_changeable_date: Optional[datetime],
|
||||
password_expires: Optional[datetime],
|
||||
user_may_change_password: bool,
|
||||
password_required: bool,
|
||||
password_last_set: Optional[datetime],
|
||||
last_logon: Optional[datetime],
|
||||
principal_source: str,
|
||||
password: Optional[str] = None,
|
||||
hash: Optional[str] = None,
|
||||
):
|
||||
super().__init__(
|
||||
source=source, name=name, uid=uid, password=password, hash=hash
|
||||
)
|
||||
|
||||
self.account_expires: Optional[datetime] = account_expires
|
||||
self.user_description: str = description
|
||||
self.enabled: bool = enabled
|
||||
self.full_name: str = full_name
|
||||
self.password_changeable_date: Optional[datetime] = password_changeable_date
|
||||
self.password_expires: Optional[datetime] = password_expires
|
||||
self.user_may_change_password: bool = user_may_change_password
|
||||
self.password_required: bool = password_required
|
||||
self.password_last_set: Optional[datetime] = password_last_set
|
||||
self.last_logon: Optional[datetime] = last_logon
|
||||
self.principal_source: str = principal_source
|
||||
|
||||
|
||||
class WindowsGroup(Group):
|
||||
""" Windows-specific group """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source: str,
|
||||
name: str,
|
||||
gid: str,
|
||||
description: str,
|
||||
principal_source: str,
|
||||
members: List[str],
|
||||
):
|
||||
super().__init__(source=source, name=name, gid=gid, members=members)
|
||||
|
||||
self.group_description: str = description
|
||||
self.principal_source: str = principal_source
|
@ -1,8 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import datetime
|
||||
import textwrap
|
||||
from typing import List
|
||||
|
||||
import jinja2
|
||||
import rich.box
|
||||
from rich.table import Table
|
||||
from pwncat.util import console, strip_markup
|
||||
from rich.markdown import Markdown
|
||||
from pwncat.modules import Bool, Argument, BaseModule, ModuleFailed
|
||||
@ -38,6 +42,37 @@ class Module(BaseModule):
|
||||
),
|
||||
}
|
||||
|
||||
def generate_markdown_table(self, data: List[List], headers: bool = False):
|
||||
""" Generate a markdown table from the given data and headers """
|
||||
|
||||
# Get column widths
|
||||
widths = [
|
||||
max(len(data[r][c]) for r in range(len(data))) for c in range(len(data[0]))
|
||||
]
|
||||
|
||||
rows = []
|
||||
for r in range(len(data)):
|
||||
rows.append(
|
||||
"|"
|
||||
+ "|".join(
|
||||
[
|
||||
" " + data[r][c] + " " * (widths[c] - len(data[r][c]) + 1)
|
||||
for c in range(len(data[r]))
|
||||
]
|
||||
)
|
||||
+ "|"
|
||||
)
|
||||
|
||||
if headers:
|
||||
rows.insert(
|
||||
1,
|
||||
"|"
|
||||
+ "|".join([" " + "-" * widths[c] + " " for c in range(len(data[r]))])
|
||||
+ "|",
|
||||
)
|
||||
|
||||
return " \n".join(rows)
|
||||
|
||||
def run(self, session: "pwncat.manager.Session", output, template, fmt, custom):
|
||||
""" Perform enumeration and optionally write report """
|
||||
|
||||
@ -74,6 +109,7 @@ class Module(BaseModule):
|
||||
else "unknown"
|
||||
)
|
||||
env.filters["remove_rich"] = lambda thing: strip_markup(str(thing))
|
||||
env.filters["table"] = self.generate_markdown_table
|
||||
|
||||
try:
|
||||
template = env.get_template(f"{template}.{fmt}")
|
||||
|
0
pwncat/modules/windows/enumerate/__init__.py
Normal file
0
pwncat/modules/windows/enumerate/__init__.py
Normal file
43
pwncat/modules/windows/enumerate/user/__init__.py
Normal file
43
pwncat/modules/windows/enumerate/user/__init__.py
Normal file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pwncat.modules import Status, ModuleFailed
|
||||
from pwncat.facts.windows import WindowsUser
|
||||
from pwncat.platform.windows import Windows, PowershellError
|
||||
from pwncat.modules.enumerate import Schedule, EnumerateModule
|
||||
|
||||
|
||||
class Module(EnumerateModule):
|
||||
""" Enumerate users from a windows target """
|
||||
|
||||
PROVIDES = ["user"]
|
||||
PLATFORM = [Windows]
|
||||
SCHEDULE = Schedule.ONCE
|
||||
|
||||
def enumerate(self, session: "pwncat.manager.Session"):
|
||||
|
||||
try:
|
||||
users = session.platform.powershell("Get-LocalUser")
|
||||
if not users:
|
||||
raise ModuleFailed("no users returned from Get-Localuser")
|
||||
except PowershellError as exc:
|
||||
raise ModuleFailed(str(exc)) from exc
|
||||
|
||||
users = users[0]
|
||||
|
||||
for user in users:
|
||||
yield WindowsUser(
|
||||
source=self.name,
|
||||
name=user["Name"],
|
||||
uid=user["SID"],
|
||||
account_expires=None,
|
||||
description=user["Description"],
|
||||
enabled=user["Enabled"],
|
||||
full_name=user["FullName"],
|
||||
password_changeable_date=None,
|
||||
password_expires=None,
|
||||
user_may_change_password=user["UserMayChangePassword"],
|
||||
password_required=user["PasswordRequired"],
|
||||
password_last_set=None,
|
||||
last_logon=None,
|
||||
principal_source=user["PrincipalSource"],
|
||||
)
|
47
pwncat/modules/windows/enumerate/user/group.py
Normal file
47
pwncat/modules/windows/enumerate/user/group.py
Normal file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from pwncat.modules import Status, ModuleFailed
|
||||
from pwncat.facts.windows import WindowsGroup
|
||||
from pwncat.platform.windows import Windows, PowershellError
|
||||
from pwncat.modules.enumerate import Schedule, EnumerateModule
|
||||
|
||||
|
||||
class Module(EnumerateModule):
|
||||
"""Enumerate groups from a windows target"""
|
||||
|
||||
PROVIDES = ["group"]
|
||||
PLATFORM = [Windows]
|
||||
SCHEDULE = Schedule.ONCE
|
||||
|
||||
def enumerate(self, session: "pwncat.manager.Session"):
|
||||
""" Yield WindowsGroup objects """
|
||||
|
||||
try:
|
||||
groups = session.platform.powershell("Get-LocalGroup")
|
||||
if not groups:
|
||||
raise ModuleFailed("no groups returned from Get-LocalGroup")
|
||||
except PowershellError as exc:
|
||||
raise ModuleFailed(str(exc)) from exc
|
||||
|
||||
for group in groups[0]:
|
||||
try:
|
||||
members = session.platform.powershell(
|
||||
f"Get-LocalGroupMember {group['Name']}"
|
||||
)
|
||||
if members:
|
||||
members = (
|
||||
[m["SID"] for m in members[0]]
|
||||
if isinstance(members[0], list)
|
||||
else [members[0]["SID"]["Value"]]
|
||||
)
|
||||
except PowershellError as exc:
|
||||
members = []
|
||||
|
||||
yield WindowsGroup(
|
||||
source=self.name,
|
||||
name=group["Name"],
|
||||
gid=group["SID"],
|
||||
description=group["Description"],
|
||||
principal_source=group["PrincipalSource"],
|
||||
members=members,
|
||||
)
|
83
pwncat/modules/windows/enumerate/user/privs.py
Normal file
83
pwncat/modules/windows/enumerate/user/privs.py
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
import dataclasses
|
||||
from enum import IntFlag
|
||||
|
||||
from rich.table import Table
|
||||
from pwncat.facts import Fact
|
||||
from pwncat.platform.windows import Windows
|
||||
from pwncat.modules.enumerate import Schedule, EnumerateModule
|
||||
|
||||
|
||||
class LuidAttributes(IntFlag):
|
||||
|
||||
DISABLED = 0x00
|
||||
SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x01
|
||||
SE_PRIVILEGE_ENABLED = 0x02
|
||||
SE_PRIVILEGE_REMOVE = 0x04
|
||||
SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000
|
||||
|
||||
|
||||
class TokenPrivilegeData(Fact):
|
||||
def __init__(
|
||||
self,
|
||||
source: str,
|
||||
privilege: str,
|
||||
attributes: LuidAttributes,
|
||||
token_handle: int,
|
||||
pid: int,
|
||||
):
|
||||
super().__init__(source=source, types=["user.privs"])
|
||||
|
||||
self.privilege: str = privilege
|
||||
self.attributes: LuidAttributes = attributes
|
||||
# self.token_handle: int = token_handle
|
||||
# self.pid: int = pid
|
||||
|
||||
def title(self, session: "pwncat.manager.Session"):
|
||||
|
||||
if self.attributes == LuidAttributes.DISABLED:
|
||||
color = "red"
|
||||
else:
|
||||
color = "green"
|
||||
|
||||
attrs = str(self.attributes)
|
||||
attrs = attrs.replace("LuidAttributes.", "")
|
||||
attrs = attrs.replace("|", "[/cyan]|[cyan]")
|
||||
attrs = "[cyan]" + attrs + "[/cyan]"
|
||||
|
||||
return f"[{color}]{self.privilege}[/{color}] -> {attrs}"
|
||||
|
||||
|
||||
class Module(EnumerateModule):
|
||||
"""Enumerate user privileges using PowerView's Get-ProcessTokenPrivilege"""
|
||||
|
||||
PROVIDES = ["user.privs"]
|
||||
PLATFORM = [Windows]
|
||||
SCHEDULE = Schedule.ALWAYS
|
||||
|
||||
def enumerate(self, session: "pwncat.manager.Session"):
|
||||
|
||||
# Ensure that powerview is loaded
|
||||
session.run(
|
||||
"manage.powershell.import",
|
||||
path="PowerShellMafia/PowerSploit/Privesc/PowerUp.ps1",
|
||||
)
|
||||
|
||||
# Grab our current token privileges
|
||||
results = session.platform.powershell("Get-ProcessTokenPrivilege")
|
||||
if len(results) == 0:
|
||||
session.log("[red]error[/red]: Get-ProcessTokenPrivilege failed")
|
||||
return
|
||||
|
||||
# They end up in an array in an array
|
||||
privs = results[0]
|
||||
|
||||
# Create our enumeration data types
|
||||
for priv in privs:
|
||||
yield TokenPrivilegeData(
|
||||
source=self.name,
|
||||
privilege=priv["Privilege"],
|
||||
attributes=LuidAttributes(priv["Attributes"]),
|
||||
token_handle=priv["TokenHandle"],
|
||||
pid=priv["ProcessId"],
|
||||
)
|
@ -4,7 +4,6 @@ from io import IOBase, BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
from pwncat.modules import Bool, Argument, BaseModule, ModuleFailed
|
||||
from pwncat.platform.windows import Windows
|
||||
|
||||
@ -71,14 +70,7 @@ class Module(BaseModule):
|
||||
name, filp = self.resolve_psmodule(session, path)
|
||||
|
||||
if name in session.platform.psmodules and not force:
|
||||
session.log(
|
||||
f"[yellow]warning[/yellow]: {name}: skipping previously loaded module"
|
||||
)
|
||||
return
|
||||
elif name in session.platform.psmodules and force:
|
||||
session.log(f"[yellow]warning[/yellow]: {name}: reloading module")
|
||||
else:
|
||||
session.log(f"executing powershell module: {name}")
|
||||
|
||||
session.platform.powershell(filp)
|
||||
|
||||
|
@ -527,8 +527,13 @@ class Platform(ABC):
|
||||
return str(self.channel)
|
||||
|
||||
@abstractmethod
|
||||
def getuid(self):
|
||||
"""Get the current user ID"""
|
||||
def refresh_uid(self) -> Union[int, str]:
|
||||
""" Refresh the cached UID of the current session. """
|
||||
|
||||
@abstractmethod
|
||||
def getuid(self) -> Union[int, str]:
|
||||
"""Get the current user ID. This should not query the target, but should
|
||||
return the current cached UID as found with `refresh_uid`."""
|
||||
|
||||
@abstractmethod
|
||||
def getenv(self, name: str) -> str:
|
||||
|
@ -834,7 +834,7 @@ function prompt {
|
||||
)
|
||||
self.user_info = self.powershell(
|
||||
"([System.DirectoryServices.AccountManagement.UserPrincipal]::Current).SID.Value"
|
||||
)
|
||||
)[0]
|
||||
|
||||
def getuid(self):
|
||||
|
||||
|
27
test.py
27
test.py
@ -1,24 +1,23 @@
|
||||
#!./env/bin/python
|
||||
import json
|
||||
import stat
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
import pwncat.manager
|
||||
import pwncat.platform.windows
|
||||
import time
|
||||
import stat
|
||||
import json
|
||||
|
||||
# Create a manager
|
||||
manager = pwncat.manager.Manager("data/pwncatrc")
|
||||
with pwncat.manager.Manager("data/pwncatrc") as manager:
|
||||
|
||||
# Tell the manager to create verbose sessions that
|
||||
# log all commands executed on the remote host
|
||||
# manager.config.set("verbose", True, glob=True)
|
||||
# Tell the manager to create verbose sessions that
|
||||
# log all commands executed on the remote host
|
||||
# manager.config.set("verbose", True, glob=True)
|
||||
|
||||
# Establish a session
|
||||
# session = manager.create_session("windows", host="192.168.56.10", port=4444)
|
||||
# session = manager.create_session("windows", host="192.168.122.11", port=4444)
|
||||
session = manager.create_session("linux", host="pwncat-ubuntu", port=4444)
|
||||
# session = manager.create_session("windows", host="0.0.0.0", port=4444)
|
||||
# Establish a session
|
||||
# session = manager.create_session("windows", host="192.168.56.10", port=4444)
|
||||
session = manager.create_session("windows", host="192.168.122.11", port=4444)
|
||||
# session = manager.create_session("linux", host="pwncat-ubuntu", port=4444)
|
||||
# session = manager.create_session("windows", host="0.0.0.0", port=4444)
|
||||
|
||||
while True:
|
||||
session.platform.getuid()
|
||||
manager.print(session.platform.powershell("Get-LocalGroupMember Administrators"))
|
||||
|
Loading…
Reference in New Issue
Block a user