diff --git a/pwncat/modules/enumerate.py b/pwncat/modules/enumerate.py index 37b1d4f..23b154e 100644 --- a/pwncat/modules/enumerate.py +++ b/pwncat/modules/enumerate.py @@ -162,7 +162,7 @@ class EnumerateModule(BaseModule): # Yield all the know facts which have already been enumerated if cache and types: - yield from ( + cached = [ f for f in target.facts if f.source == self.name @@ -170,9 +170,13 @@ class EnumerateModule(BaseModule): any(fnmatch.fnmatch(item_type, req_type) for req_type in types) for item_type in f.types ) - ) + ] elif cache: - yield from (f for f in target.facts if f.source == self.name) + cached = [f for f in target.facts if f.source == self.name] + else: + cached = [] + + yield from cached # Check if the module is scheduled to run now if (self.name in target.enumerate_state) and ( @@ -194,19 +198,22 @@ class EnumerateModule(BaseModule): # Only add the item if it doesn't exist for f in target.facts: if f == item: - yield Status(item.title(session)) break else: target.facts.append(item) - # Don't yield the actual fact if we didn't ask for this type - if not types or any( - any(fnmatch.fnmatch(item_type, req_type) for req_type in types) - for item_type in item.types - ): - yield item + # Don't yield the actual fact if we didn't ask for this type + if not types or any( + any(fnmatch.fnmatch(item_type, req_type) for req_type in types) + for item_type in item.types + ): + for c in cached: + if item == c: + break else: - yield Status(item.title(session)) + yield item + else: + yield Status(item.title(session)) # Update state for restricted modules if self.SCHEDULE == Schedule.ONCE: diff --git a/pwncat/modules/windows/enumerate/domain/computer.py b/pwncat/modules/windows/enumerate/domain/computer.py new file mode 100644 index 0000000..9509062 --- /dev/null +++ b/pwncat/modules/windows/enumerate/domain/computer.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +from typing import Any, Dict +from collections import namedtuple + +from pwncat.db import Fact +from pwncat.modules import Status, ModuleFailed +from pwncat.platform.windows import Windows, PowershellError +from pwncat.modules.enumerate import Schedule, EnumerateModule + + +class ComputerObject(Fact): + def __init__(self, source: str, data: Dict): + super().__init__(source=source, types=["domain.computer"]) + + self.computer = data + + def __getitem__(self, name: str): + """ Shortcut for getting properties from the `self.domain` property. """ + + return self.computer[name] + + def title(self, session: "pwncat.manager.Session"): + return f"[blue]{self['dnshostname']}[/blue] ([cyan]{self['name']}[/cyan])" + + def is_dc(self): + """ Query if this computer object is a domain controller """ + + uac = self.computer.get("useraccountcontrol") or 0 + + return (uac & 0x2000) > 0 + + def is_rodc(self): + """ Query if this computer object is a read only domain controller """ + + uac = self.computer.get("useraccountcontrol") or 0 + + return (uac & 0x04000000) > 0 + + def description(self, session: "pwncat.manager.Session"): + output = [] + + if self.is_rodc(): + output.append("[red]Read-Only Domain Controller[/red]") + elif self.is_dc(): + output.append("[bold red]Domain Controller[/bold red]") + + output.append(f"Computer SID: [cyan]{self['objectsid']}[/cyan]") + output.append(f"Machine Account: [cyan]{self['samaccountname']}[/cyan]") + output.append( + f"Operating System: [blue]{self['operatingsystem']} {self['operatingsystemversion']}[/blue]" + ) + output.append( + f"Distinguished Name: [magenta]{self['distinguishedname']}[/magenta]" + ) + + return "\n".join(output) + + +class Module(EnumerateModule): + """ Retrieve information on all domain computers """ + + PLATFORM = [Windows] + PROVIDES = ["domain.computer"] + SCHEDULE = Schedule.ONCE + + def enumerate(self, session: "pwncat.manager.Session"): + """ Perform enumeration """ + + # Ensure we have PowerView loaded + yield Status("loading powersploit recon") + session.run("powersploit", group="recon") + + try: + yield Status("requesting domain computers") + computers = session.platform.powershell("Get-DomainComputer")[0] + except (IndexError, PowershellError) as exc: + # Doesn't appear to be a domain joined computer + return + + if isinstance(computers, dict): + yield ComputerObject(self.name, computers) + else: + yield from (ComputerObject(self.name, computer) for computer in computers) diff --git a/pwncat/modules/windows/enumerate/domain/fileserver.py b/pwncat/modules/windows/enumerate/domain/fileserver.py new file mode 100644 index 0000000..cd48e86 --- /dev/null +++ b/pwncat/modules/windows/enumerate/domain/fileserver.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +from typing import Any, Dict +from collections import namedtuple + +from pwncat.db import Fact +from pwncat.modules import Status, ModuleFailed +from pwncat.platform.windows import Windows, PowershellError +from pwncat.modules.enumerate import Schedule, EnumerateModule + + +class Module(EnumerateModule): + """ Retrieve information on all domain computers """ + + PLATFORM = [Windows] + PROVIDES = ["domain.fileserver"] + SCHEDULE = Schedule.ONCE + + def enumerate(self, session: "pwncat.manager.Session"): + """ Perform enumeration """ + + # Ensure we have PowerView loaded + yield Status("loading powersploit recon") + session.run("powersploit", group="recon") + + try: + yield Status("requesting domain file servers") + names = session.platform.powershell("Get-DomainFileServer")[0] + except (IndexError, PowershellError) as exc: + return + + if not isinstance(names, list): + names = [names] + + names = [name.lower() for name in names] + + for computer in session.run("enumerate.domain.computer"): + if computer["name"].lower() in names: + yield computer