mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-27 19:04:15 +01:00
Organized the report from enum better. It's not more readable.
This commit is contained in:
parent
da591f9a22
commit
8461de7182
@ -1,10 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
|
import pytablewriter
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Style
|
||||||
|
from pytablewriter import MarkdownTableWriter
|
||||||
|
|
||||||
import pwncat
|
import pwncat
|
||||||
from pwncat import util
|
from pwncat import util
|
||||||
@ -14,7 +15,6 @@ from pwncat.commands.base import (
|
|||||||
Parameter,
|
Parameter,
|
||||||
StoreConstOnce,
|
StoreConstOnce,
|
||||||
Group,
|
Group,
|
||||||
StoreForAction,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -144,13 +144,102 @@ class Command(CommandDefinition):
|
|||||||
report_data: Dict[str, Dict[str, List[pwncat.db.Fact]]] = {}
|
report_data: Dict[str, Dict[str, List[pwncat.db.Fact]]] = {}
|
||||||
hostname = ""
|
hostname = ""
|
||||||
|
|
||||||
|
system_details = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Grab hostname
|
||||||
|
hostname = pwncat.victim.enumerate.first("system.hostname").data
|
||||||
|
system_details.append(["Hostname", hostname])
|
||||||
|
except ValueError:
|
||||||
|
hostname = "[unknown-hostname]"
|
||||||
|
|
||||||
|
# Not provided by enumerate, but natively known due to our connection
|
||||||
|
system_details.append(["Primary Address", pwncat.victim.host.ip])
|
||||||
|
system_details.append(["Derived Hash", pwncat.victim.host.hash])
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Grab distribution
|
||||||
|
distro = pwncat.victim.enumerate.first("system.distro").data
|
||||||
|
system_details.append(
|
||||||
|
["Distribution", f"{distro.name} ({distro.ident}) {distro.version}"]
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Grab the architecture
|
||||||
|
arch = pwncat.victim.enumerate.first("system.arch").data
|
||||||
|
system_details.append(["Architecture", arch.arch])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Grab kernel version
|
||||||
|
kernel = pwncat.victim.enumerate.first("system.kernel.version").data
|
||||||
|
system_details.append(
|
||||||
|
[
|
||||||
|
"Kernel",
|
||||||
|
f"Linux Kernel {kernel.major}.{kernel.minor}.{kernel.patch}-{kernel.abi}",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Grab init system
|
||||||
|
init = pwncat.victim.enumerate.first("system.init").data
|
||||||
|
system_details.append(["Init", init.init])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Build the table writer for the main section
|
||||||
|
table_writer = MarkdownTableWriter()
|
||||||
|
table_writer.headers = ["Property", "Value"]
|
||||||
|
table_writer.column_styles = [
|
||||||
|
pytablewriter.style.Style(align="right"),
|
||||||
|
pytablewriter.style.Style(align="center"),
|
||||||
|
]
|
||||||
|
table_writer.value_matrix = system_details
|
||||||
|
table_writer.margin = 1
|
||||||
|
|
||||||
|
# Note enumeration data we don't need anymore
|
||||||
|
ignore_types = [
|
||||||
|
"system.hostname",
|
||||||
|
"system.kernel.version",
|
||||||
|
"system.distro",
|
||||||
|
"system.init",
|
||||||
|
"system.arch",
|
||||||
|
]
|
||||||
|
|
||||||
|
# This is the list of known enumeration types that we want to
|
||||||
|
# happen first in this order. Other types will still be output
|
||||||
|
# but will be output in an arbitrary order following this list
|
||||||
|
ordered_types = [
|
||||||
|
# Possible kernel exploits - very important
|
||||||
|
"system.kernel.exploit",
|
||||||
|
# Enumerated user passwords - very important
|
||||||
|
"system.user.password",
|
||||||
|
# Enumerated possible user private keys - very important
|
||||||
|
"system.user.private_key",
|
||||||
|
]
|
||||||
|
|
||||||
|
# These types are very noisy. They are important for full enumeration,
|
||||||
|
# but are better suited for the end of the list. These are output last
|
||||||
|
# no matter what in this order.
|
||||||
|
noisy_types = [
|
||||||
|
# System services. There's normally a lot of these
|
||||||
|
"system.service",
|
||||||
|
# Installed packages. There's *always* a lot of these
|
||||||
|
"system.package",
|
||||||
|
]
|
||||||
|
|
||||||
util.progress("enumerating report_data")
|
util.progress("enumerating report_data")
|
||||||
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 report_data: {fact.data}")
|
util.progress(f"enumerating report_data: {fact.data}")
|
||||||
if fact.type == "system.hostname":
|
if fact.type in ignore_types:
|
||||||
hostname = str(fact.data)
|
continue
|
||||||
if fact.type not in report_data:
|
if fact.type not in report_data:
|
||||||
report_data[fact.type] = {}
|
report_data[fact.type] = {}
|
||||||
if fact.source not in report_data[fact.type]:
|
if fact.source not in report_data[fact.type]:
|
||||||
@ -162,27 +251,58 @@ class Command(CommandDefinition):
|
|||||||
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")
|
||||||
|
|
||||||
|
# Write the system info table
|
||||||
|
table_writer.dump(filp, close_after_write=False)
|
||||||
|
filp.write("\n")
|
||||||
|
|
||||||
|
# output ordered types first
|
||||||
|
for typ in ordered_types:
|
||||||
|
if typ not in report_data:
|
||||||
|
continue
|
||||||
|
self.render_section(filp, typ, report_data[typ])
|
||||||
|
|
||||||
|
# output everything that's not a ordered or noisy type
|
||||||
for typ, sources in report_data.items():
|
for typ, sources in report_data.items():
|
||||||
filp.write(f"## {typ.upper()} Facts\n\n")
|
if typ in ordered_types or typ in noisy_types:
|
||||||
sections = []
|
continue
|
||||||
for source, facts in sources.items():
|
self.render_section(filp, typ, sources)
|
||||||
for fact in facts:
|
|
||||||
if getattr(fact.data, "description", None) is not None:
|
|
||||||
sections.append(fact)
|
|
||||||
continue
|
|
||||||
filp.write(f"- {util.strip_ansi_escape(str(fact.data))}\n")
|
|
||||||
|
|
||||||
filp.write("\n")
|
# Output the noisy types
|
||||||
|
for typ in noisy_types:
|
||||||
|
if typ not in report_data:
|
||||||
|
continue
|
||||||
|
self.render_section(filp, typ, report_data[typ])
|
||||||
|
|
||||||
for section in sections:
|
|
||||||
filp.write(
|
|
||||||
f"### {util.strip_ansi_escape(str(section.data))}\n\n"
|
|
||||||
)
|
|
||||||
filp.write(f"```\n{section.data.description}\n```\n\n")
|
|
||||||
util.success(f"enumeration report written to {report_path}")
|
util.success(f"enumeration report written to {report_path}")
|
||||||
except OSError:
|
except OSError:
|
||||||
self.parser.error(f"{report_path}: failed to open output file")
|
self.parser.error(f"{report_path}: failed to open output file")
|
||||||
|
|
||||||
|
def render_section(self, filp, typ: str, sources: Dict[str, List[pwncat.db.Fact]]):
|
||||||
|
"""
|
||||||
|
Render the given facts all of the given type to the report pointed to by the open file
|
||||||
|
filp.
|
||||||
|
|
||||||
|
:param filp: the open file containing the report
|
||||||
|
:param typ: the type all of these facts provide
|
||||||
|
:param sources: a dictionary of sources->fact list
|
||||||
|
"""
|
||||||
|
|
||||||
|
filp.write(f"## {typ.upper()} Facts\n\n")
|
||||||
|
sections = []
|
||||||
|
for source, facts in sources.items():
|
||||||
|
for fact in facts:
|
||||||
|
if getattr(fact.data, "description", None) is not None:
|
||||||
|
sections.append(fact)
|
||||||
|
continue
|
||||||
|
filp.write(f"- {util.strip_ansi_escape(str(fact.data))}\n")
|
||||||
|
|
||||||
|
filp.write("\n")
|
||||||
|
|
||||||
|
for section in sections:
|
||||||
|
filp.write(f"### {util.strip_ansi_escape(str(section.data))}\n\n")
|
||||||
|
filp.write(f"```\n{section.data.description}\n```\n\n")
|
||||||
|
|
||||||
def show_facts(self, typ: str, provider: str, long: bool):
|
def show_facts(self, typ: str, provider: str, long: bool):
|
||||||
""" Display known facts matching the criteria """
|
""" Display known facts matching the criteria """
|
||||||
|
|
||||||
|
@ -114,7 +114,9 @@ class Enumerate:
|
|||||||
continue
|
continue
|
||||||
yield fact
|
yield fact
|
||||||
|
|
||||||
# Add the dummy value
|
# Add the dummy value. We do this after so that
|
||||||
|
# if the generator was closed, this data will get
|
||||||
|
# re-enumerated for the missing entries.
|
||||||
if not getattr(enumerator, "always_run", False):
|
if not getattr(enumerator, "always_run", False):
|
||||||
self.add_fact(enumerator.provides, None, dummy_name)
|
self.add_fact(enumerator.provides, None, dummy_name)
|
||||||
|
|
||||||
@ -126,6 +128,25 @@ class Enumerate:
|
|||||||
"""
|
"""
|
||||||
yield from self.iter()
|
yield from self.iter()
|
||||||
|
|
||||||
|
def first(self, typ: str) -> pwncat.db.Fact:
|
||||||
|
"""
|
||||||
|
Find and return the first fact matching this type. Raises a ValueError
|
||||||
|
if no fact of the given type exists/could be enumerated.
|
||||||
|
|
||||||
|
:param typ: the fact type
|
||||||
|
:return: the fact
|
||||||
|
:raises: ValueError
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
iter = self.iter(typ)
|
||||||
|
fact = next(iter)
|
||||||
|
iter.close()
|
||||||
|
except StopIteration:
|
||||||
|
raise ValueError(f"{typ}: no facts located")
|
||||||
|
|
||||||
|
return fact
|
||||||
|
|
||||||
def add_fact(self, typ: str, data: Any, source: str) -> pwncat.db.Fact:
|
def add_fact(self, typ: str, data: Any, source: str) -> pwncat.db.Fact:
|
||||||
"""
|
"""
|
||||||
Register a fact with the fact database. This does not have to come from
|
Register a fact with the fact database. This does not have to come from
|
||||||
|
43
pwncat/enumerate/system/arch.py
Normal file
43
pwncat/enumerate/system/arch.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import dataclasses
|
||||||
|
from typing import Generator, List
|
||||||
|
|
||||||
|
from colorama import Fore
|
||||||
|
|
||||||
|
from pwncat.enumerate import FactData
|
||||||
|
from pwncat import util
|
||||||
|
import pwncat
|
||||||
|
|
||||||
|
name = "pwncat.enumerate.system"
|
||||||
|
provides = "system.arch"
|
||||||
|
per_user = False
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class ArchData(FactData):
|
||||||
|
"""
|
||||||
|
Represents a W.X.Y-Z kernel version where W is the major version,
|
||||||
|
X is the minor version, Y is the patch, and Z is the ABI.
|
||||||
|
|
||||||
|
This explanation came from here:
|
||||||
|
https://askubuntu.com/questions/843197/what-are-kernel-version-number-components-w-x-yy-zzz-called
|
||||||
|
"""
|
||||||
|
|
||||||
|
arch: str
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Running on a {Fore.CYAN}{self.arch}{Fore.RESET} processor"
|
||||||
|
|
||||||
|
|
||||||
|
def enumerate() -> Generator[FactData, None, None]:
|
||||||
|
"""
|
||||||
|
Enumerate kernel/OS version information
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = pwncat.victim.env(["uname", "-m"]).decode("utf-8").strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
return
|
||||||
|
|
||||||
|
yield ArchData(result)
|
@ -9,7 +9,7 @@ from pwncat import util
|
|||||||
import pwncat
|
import pwncat
|
||||||
|
|
||||||
name = "pwncat.enumerate.system"
|
name = "pwncat.enumerate.system"
|
||||||
provides = "system.version.distro"
|
provides = "system.distro"
|
||||||
per_user = False
|
per_user = False
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import pwncat
|
|||||||
from pwncat.enumerate import FactData
|
from pwncat.enumerate import FactData
|
||||||
|
|
||||||
name = "pwncat.enumerate.capabilities"
|
name = "pwncat.enumerate.capabilities"
|
||||||
provides = "system.packages"
|
provides = "system.package"
|
||||||
per_user = True
|
per_user = True
|
||||||
always_run = False
|
always_run = False
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ from io import RawIOBase
|
|||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import pwncat
|
||||||
|
|
||||||
|
|
||||||
class RemoteBinaryPipe(RawIOBase):
|
class RemoteBinaryPipe(RawIOBase):
|
||||||
""" Encapsulate a piped interaction with a remote process. The remote PTY
|
""" Encapsulate a piped interaction with a remote process. The remote PTY
|
||||||
|
Loading…
Reference in New Issue
Block a user