1
0
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:
Caleb Stewart 2020-05-28 00:09:53 -04:00
parent da591f9a22
commit 8461de7182
6 changed files with 207 additions and 21 deletions

View File

@ -1,10 +1,11 @@
#!/usr/bin/env python3
import argparse
import os
import textwrap
from typing import List, Dict
import pytablewriter
from colorama import Fore, Style
from pytablewriter import MarkdownTableWriter
import pwncat
from pwncat import util
@ -14,7 +15,6 @@ from pwncat.commands.base import (
Parameter,
StoreConstOnce,
Group,
StoreForAction,
)
@ -144,13 +144,102 @@ class Command(CommandDefinition):
report_data: Dict[str, Dict[str, List[pwncat.db.Fact]]] = {}
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")
for fact in pwncat.victim.enumerate.iter(
typ, filter=lambda f: provider is None or f.source == provider
):
util.progress(f"enumerating report_data: {fact.data}")
if fact.type == "system.hostname":
hostname = str(fact.data)
if fact.type in ignore_types:
continue
if fact.type not in report_data:
report_data[fact.type] = {}
if fact.source not in report_data[fact.type]:
@ -162,7 +251,43 @@ class Command(CommandDefinition):
try:
with open(report_path, "w") as filp:
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():
if typ in ordered_types or typ in noisy_types:
continue
self.render_section(filp, typ, sources)
# Output the noisy types
for typ in noisy_types:
if typ not in report_data:
continue
self.render_section(filp, typ, report_data[typ])
util.success(f"enumeration report written to {report_path}")
except OSError:
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():
@ -175,13 +300,8 @@ class Command(CommandDefinition):
filp.write("\n")
for section in sections:
filp.write(
f"### {util.strip_ansi_escape(str(section.data))}\n\n"
)
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}")
except OSError:
self.parser.error(f"{report_path}: failed to open output file")
def show_facts(self, typ: str, provider: str, long: bool):
""" Display known facts matching the criteria """

View File

@ -114,7 +114,9 @@ class Enumerate:
continue
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):
self.add_fact(enumerator.provides, None, dummy_name)
@ -126,6 +128,25 @@ class Enumerate:
"""
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:
"""
Register a fact with the fact database. This does not have to come from

View 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)

View File

@ -9,7 +9,7 @@ from pwncat import util
import pwncat
name = "pwncat.enumerate.system"
provides = "system.version.distro"
provides = "system.distro"
per_user = False

View File

@ -8,7 +8,7 @@ import pwncat
from pwncat.enumerate import FactData
name = "pwncat.enumerate.capabilities"
provides = "system.packages"
provides = "system.package"
per_user = True
always_run = False

View File

@ -3,6 +3,8 @@ from io import RawIOBase
import socket
import time
import pwncat
class RemoteBinaryPipe(RawIOBase):
""" Encapsulate a piped interaction with a remote process. The remote PTY