mirror of
https://github.com/calebstewart/pwncat.git
synced 2024-11-23 17:15:38 +01:00
Migrated some enumeration code into the new module framework
This commit is contained in:
parent
9a855c409f
commit
1706213920
46
pwncat/modules/enumerate/arch.py
Normal file
46
pwncat/modules/enumerate/arch.py
Normal file
@ -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)
|
43
pwncat/modules/enumerate/aslr.py
Normal file
43
pwncat/modules/enumerate/aslr.py
Normal file
@ -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
|
51
pwncat/modules/enumerate/container.py
Normal file
51
pwncat/modules/enumerate/container.py
Normal file
@ -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
|
75
pwncat/modules/enumerate/distro.py
Normal file
75
pwncat/modules/enumerate/distro.py
Normal file
@ -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)
|
38
pwncat/modules/enumerate/hostname.py
Normal file
38
pwncat/modules/enumerate/hostname.py
Normal file
@ -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
|
50
pwncat/modules/enumerate/hosts.py
Normal file
50
pwncat/modules/enumerate/hosts.py
Normal file
@ -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
|
77
pwncat/modules/enumerate/init.py
Normal file
77
pwncat/modules/enumerate/init.py
Normal file
@ -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)
|
65
pwncat/modules/enumerate/kernel.py
Normal file
65
pwncat/modules/enumerate/kernel.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user