From 17062139206b7e2c60bb1b4fe62716605dbfbed3 Mon Sep 17 00:00:00 2001 From: John Hammond Date: Mon, 31 Aug 2020 22:05:49 -0400 Subject: [PATCH] Migrated some enumeration code into the new module framework --- pwncat/modules/enumerate/arch.py | 46 ++++++++++++++++ pwncat/modules/enumerate/aslr.py | 43 +++++++++++++++ pwncat/modules/enumerate/container.py | 51 ++++++++++++++++++ pwncat/modules/enumerate/distro.py | 75 ++++++++++++++++++++++++++ pwncat/modules/enumerate/hostname.py | 38 +++++++++++++ pwncat/modules/enumerate/hosts.py | 50 +++++++++++++++++ pwncat/modules/enumerate/init.py | 77 +++++++++++++++++++++++++++ pwncat/modules/enumerate/kernel.py | 65 ++++++++++++++++++++++ 8 files changed, 445 insertions(+) create mode 100644 pwncat/modules/enumerate/arch.py create mode 100644 pwncat/modules/enumerate/aslr.py create mode 100644 pwncat/modules/enumerate/container.py create mode 100644 pwncat/modules/enumerate/distro.py create mode 100644 pwncat/modules/enumerate/hostname.py create mode 100644 pwncat/modules/enumerate/hosts.py create mode 100644 pwncat/modules/enumerate/init.py create mode 100644 pwncat/modules/enumerate/kernel.py diff --git a/pwncat/modules/enumerate/arch.py b/pwncat/modules/enumerate/arch.py new file mode 100644 index 0000000..7fdefc4 --- /dev/null +++ b/pwncat/modules/enumerate/arch.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +from typing import List +import dataclasses + +import pwncat +from pwncat import util +from pwncat.modules.enumerate import EnumerateModule, Schedule + + +@dataclasses.dataclass +class ArchData: + """ + 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 + """ The determined architecture. """ + + def __str__(self): + return f"Running on a [cyan]{self.arch}[/cyan] processor" + + +class Module(EnumerateModule): + """ + Enumerate kernel/OS version information + :return: + """ + + PROVIDES = ["arch"] + + def enumerate(self): + """ + Enumerate kernel/OS version information + :return: + """ + + try: + result = pwncat.victim.env(["uname", "-m"]).decode("utf-8").strip() + except FileNotFoundError: + return + + yield "arch", ArchData(result) \ No newline at end of file diff --git a/pwncat/modules/enumerate/aslr.py b/pwncat/modules/enumerate/aslr.py new file mode 100644 index 0000000..6256f85 --- /dev/null +++ b/pwncat/modules/enumerate/aslr.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +from typing import List +import dataclasses + +import pwncat +from pwncat import util +from pwncat.modules.enumerate import EnumerateModule, Schedule + + +@dataclasses.dataclass +class ASLRStateData: + + state: int + """ the value of /proc/sys/kernel/randomize_va_space """ + + def __str__(self): + if self.state == 0: + return f"ASLR is [green]disabled[/green]" + return f"ASLR is [red]enabled[/red]" + + +class Module(EnumerateModule): + """ + Determine whether or not ASLR is enabled or disabled. + :return: + """ + + PROVIDES = ["aslr"] + + def enumerate(self): + + try: + with pwncat.victim.open("/proc/sys/kernel/randomize_va_space", "r") as filp: + value = filp.read() + try: + value = int(value) + except ValueError: + value = None + + if value is not None: + yield "aslr", ASLRStateData(value) + except (FileNotFoundError, PermissionError): + pass \ No newline at end of file diff --git a/pwncat/modules/enumerate/container.py b/pwncat/modules/enumerate/container.py new file mode 100644 index 0000000..ecaca3f --- /dev/null +++ b/pwncat/modules/enumerate/container.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +from typing import List +import dataclasses + +import pwncat +from pwncat import util +from pwncat.modules.enumerate import EnumerateModule, Schedule + + +@dataclasses.dataclass +class ContainerData: + + type: str + """ what type of container? either docker or lxd """ + + def __str__(self): + return f"Running in a [yellow]{self.type}[/yellow] container" + + +class Module(EnumerateModule): + """ + Check if this system is inside a container + :return: + """ + + PROVIDES = ["container"] + + def enumerate(self): + + try: + with pwncat.victim.open("/proc/self/cgroup", "r") as filp: + if "docker" in filp.read().lower(): + yield "container", ContainerData("docker") + return + except (FileNotFoundError, PermissionError): + pass + + with pwncat.victim.subprocess( + f'find / -maxdepth 3 -name "*dockerenv*" -exec ls -la {{}} \\; 2>/dev/null', "r" + ) as pipe: + if pipe.read().strip() != b"": + yield "container", ContainerData("docker") + return + + try: + with pwncat.victim.open("/proc/1/environ", "r") as filp: + if "container=lxc" in filp.read().lower(): + yield "container", ContainerData("lxc") + return + except (FileNotFoundError, PermissionError): + pass \ No newline at end of file diff --git a/pwncat/modules/enumerate/distro.py b/pwncat/modules/enumerate/distro.py new file mode 100644 index 0000000..106386f --- /dev/null +++ b/pwncat/modules/enumerate/distro.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +from typing import List +import dataclasses + +import pwncat +from pwncat import util +from pwncat.modules.enumerate import EnumerateModule, Schedule + + +@dataclasses.dataclass +class DistroVersionData: + """ + 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 + """ + + name: str + ident: str + build_id: str + version: str + + def __str__(self): + return ( + f"Running [blue]{self.name}[/blue] ([cyan]{self.ident}[/cyan]), " + f"Version [red]{self.version}[/red], " + f"Build ID [green]{self.build_id}[/green]." + ) + +class Module(EnumerateModule): + """ + Enumerate kernel/OS version information + :return: + """ + + PROVIDES = ["distro"] + + def enumerate(self): + + build_id = None + pretty_name = None + ident = None + version = None + + try: + with pwncat.victim.open("/etc/os-release", "r") as filp: + for line in filp: + line = line.strip() + if line.startswith("PRETTY_NAME="): + pretty_name = line.split("=")[1].strip('"') + elif line.startswith("BUILD_ID="): + build_id = line.split("=")[1].strip('"') + elif line.startswith("ID="): + ident = line.split("=")[1].strip('"') + elif line.startswith("VERSION_ID="): + version = line.split("=")[1].strip('"') + except (PermissionError, FileNotFoundError): + pass + + if version is None: + try: + with pwncat.victim.open("/etc/lsb-release", "r") as filp: + for line in filp: + if line.startswith("LSB_VERSION="): + version = line.split("=")[1].strip('"') + break + except (PermissionError, FileNotFoundError): + pass + + if pretty_name is None and build_id is None and ident is None and version is None: + return + + yield "distro", DistroVersionData(pretty_name, ident, build_id, version) \ No newline at end of file diff --git a/pwncat/modules/enumerate/hostname.py b/pwncat/modules/enumerate/hostname.py new file mode 100644 index 0000000..fd17a64 --- /dev/null +++ b/pwncat/modules/enumerate/hostname.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +from typing import List +import dataclasses + +import pwncat +from pwncat import util +from pwncat.modules.enumerate import EnumerateModule, Schedule + + +class Module(EnumerateModule): + """ + Enumerate system hostname facts + :return: A generator of hostname facts + """ + + PROVIDES = ["hostname"] + + def enumerate(self): + + try: + hostname = pwncat.victim.env(["hostname", "-f"]).decode("utf-8").strip() + yield "hostname", hostname + return + except FileNotFoundError: + pass + + try: + hostname = pwncat.victim.env(["hostnamectl"]).decode("utf-8").strip() + hostname = hostname.replace("\r\n", "\n").split("\n") + for name in hostname: + if "static hostname" in name.lower(): + hostname = name.split(": ")[1] + yield "hostname", hostname + return + except (FileNotFoundError, IndexError): + pass + + return \ No newline at end of file diff --git a/pwncat/modules/enumerate/hosts.py b/pwncat/modules/enumerate/hosts.py new file mode 100644 index 0000000..422f9b0 --- /dev/null +++ b/pwncat/modules/enumerate/hosts.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +from typing import List +import dataclasses +import re + +import pwncat +from pwncat import util +from pwncat.modules.enumerate import EnumerateModule, Schedule + +@dataclasses.dataclass +class HostData: + + address: str + hostnames: List[str] + + def __str__(self): + joined_hostnames = ", ".join(self.hostnames) + return f"[cyan]{self.address}[/cyan] -> [blue]{joined_hostnames}[/blue]" + +class Module(EnumerateModule): + """ + Enumerate hosts identified in /etc/hosts which are not localhost + :return: + """ + + PROVIDES = ["hosts"] + + def enumerate(self): + + try: + with pwncat.victim.open("/etc/hosts", "r") as filp: + for line in filp: + # Remove comments + line = re.sub(r"#.*$", "", line).strip() + line = line.replace("\t", " ") + # We don't care about localhost or localdomain entries + if ( + line.endswith("localhost") + or line.endswith(".localdomain") + or line.endswith("localhost6") + or line.endswith(".localdomain") + or line.endswith("localhost4") + or line.endswith("localdomain4") + or line == "" + ): + continue + address, *hostnames = [e for e in line.split(" ") if e != ""] + yield "hosts", HostData(address, hostnames) + except (PermissionError, FileNotFoundError): + pass diff --git a/pwncat/modules/enumerate/init.py b/pwncat/modules/enumerate/init.py new file mode 100644 index 0000000..51e5440 --- /dev/null +++ b/pwncat/modules/enumerate/init.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +from typing import List +import dataclasses + +import pwncat +from pwncat import util +from pwncat.modules.enumerate import EnumerateModule, Schedule + +@dataclasses.dataclass +class InitSystemData: + + init: util.Init + version: str + + def __str__(self): + return f"Running [blue]{self.init}[/blue]" + + @property + def description(self): + return self.version + +class Module(EnumerateModule): + """ + Enumerate system init service + :return: + """ + + PROVIDES = ["init"] + + def enumerate(self): + + init = util.Init.UNKNOWN + version = None + + # Try to get the command name of the running init process + try: + with pwncat.victim.open("/proc/1/comm", "r") as filp: + comm = filp.read().strip() + if comm is not None: + if "systemd" in comm.lower(): + init = util.Init.SYSTEMD + elif "sysv" in comm.lower(): + init = util.Init.SYSV + elif "upstart" in comm.lower(): + init = util.Init.UPSTART + except (PermissionError, FileNotFoundError): + comm = None + + # Try to get the command name of the running init process + try: + with pwncat.victim.open("/proc/1/cmdline", "r") as filp: + comm = filp.read().strip().split("\x00")[0] + except (PermissionError, FileNotFoundError): + comm = None + + if comm is not None: + if "systemd" in comm.lower(): + init = util.Init.SYSTEMD + elif "sysv" in comm.lower(): + init = util.Init.SYSV + elif "upstart" in comm.lower(): + init = util.Init.UPSTART + + with pwncat.victim.subprocess(f"{comm} --version", "r") as filp: + version = filp.read().decode("utf-8").strip() + if "systemd" in version.lower(): + init = util.Init.SYSTEMD + elif "sysv" in version.lower(): + init = util.Init.SYSV + elif "upstart" in version.lower(): + init = util.Init.UPSTART + + # No need to provide an empty version string. They apparently don't support "--version" + if version == "": + version = None + + yield "init", InitSystemData(init, version) diff --git a/pwncat/modules/enumerate/kernel.py b/pwncat/modules/enumerate/kernel.py new file mode 100644 index 0000000..eaad77b --- /dev/null +++ b/pwncat/modules/enumerate/kernel.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +from typing import List +import dataclasses + +import pwncat +from pwncat import util +from pwncat.modules.enumerate import EnumerateModule, Schedule + +@dataclasses.dataclass +class KernelVersionData: + """ + 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 + """ + + major: int + minor: int + patch: int + abi: str + + def __str__(self): + return ( + f"Running Linux Kernel [red]{self.major}[/red]." + f"[green]{self.minor}[/green]." + f"[blue]{self.patch}[/blue]-[cyan]{self.abi}[/cyan]" + ) + +class Module(EnumerateModule): + """ + Enumerate kernel/OS version information + :return: + """ + PROVIDES = ["kernel"] + + def enumerate(self): + + # Try to find kernel version number + try: + kernel = pwncat.victim.env(["uname", "-r"]).strip().decode("utf-8") + if kernel == "": + raise FileNotFoundError + except FileNotFoundError: + try: + with pwncat.victim.open("/proc/version", "r") as filp: + kernel = filp.read() + except (PermissionError, FileNotFoundError): + kernel = None + + # Parse the kernel version number + if kernel is not None: + kernel = kernel.strip() + # We got the full "uname -a" style output + if kernel.lower().startswith("linux"): + kernel = kernel.split(" ")[2] + + # Split out the sections + w, x, *y_and_z = kernel.split(".") + y_and_z = ".".join(y_and_z).split("-") + y = y_and_z[0] + z = "-".join(y_and_z[1:]) + + yield "kernel", KernelVersionData(int(w), int(x), int(y), z)