def process(self, cmd, delim=True) -> Tuple[str, str]: """ Run a command in the context of the remote host and return the output. This is run synchrounously. :param cmd: The command to run. Either a string or an argv list. :param has_pty: Whether a pty was spawned """ if isinstance(cmd, list): cmd = shlex.join(cmd) sdelim = util.random_string(10) edelim = util.random_string(10) if delim: command = f" echo; echo {sdelim}; {cmd}; echo {edelim}" else: command = f" {cmd}" # Send the command to the remote host self.client.send(command.encode("utf-8") + b"\n") if delim: # Receive until we get our starting delimeter on a line by itself while not self.recvuntil(b"\n").startswith(sdelim.encode("utf-8")): pass return sdelim, edelim
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 test_platform_dir_io(session): """Test creating a directory and interacting with the contents""" # Create a path object representing the new remote directory path = session.platform.Path(random_string()) # Create the directory path.mkdir() # We construct a new path object to avoid cached stat results assert session.platform.Path(str(path)).is_dir() # Create a file (path / "test.txt").touch() assert "test.txt" in [item.name for item in path.iterdir()]
def tempfile(self, mode: str, length: int = None): """ Create a temporary file on the remote system and return an open file handle to it. This uses `mktemp` on the remote system to create the file and then opens it with `PtyHandler.open`. """ # Reading a new temporary file doesn't make sense if "w" not in mode: raise ValueError("expected write mode for temporary files") mktemp = self.which("mktemp") if mktemp is None: path = "/tmp/tmp" + util.random_string(8) else: path = self.run(mktemp).strip().decode("utf-8") return self.open(path, mode, length=length)
def subprocess( self, cmd, mode="rb", data: bytes = None, exit_cmd: str = None, no_job=False, name: str = None, ) -> RemoteBinaryPipe: """ Create an asynchronous child on the remote end and return a file-like object which can communicate with it's standard output. The remote terminal is placed in raw mode with no-echo first, and the command is run on a separate background shell w/ no standard input. The output of the command can be retrieved through the returned file-like object. You **must** either call `close()` of the pipe, or read until eof, or the PTY will not be restored to a normal state. If `close()` is called prior to EOF, the remote process will be killed, and any remaining output will be flushed prior to resetting the terminal. """ if isinstance(cmd, list): cmd = shlex.join(cmd) for c in mode: if c not in "rwb": raise ValueError("mode must only contain 'r', 'w' and 'b'") sdelim = util.random_string(10) # "_PWNCAT_STARTDELIM_" edelim = util.random_string(10) # "_PWNCAT_ENDDELIM_" # List of ";" separated commands that will be run commands: List[str] = [] # Clear the prompt, or it will get displayed in our output due to the # background task commands.append(" export PS1=") # Needed to disable job control messages in bash commands.append("set +m") # This is gross, but it allows us to recieve stderr and stdout, while # ignoring the job control start message. if "w" not in mode and not no_job: commands.append( f"{{ echo; echo {sdelim}; {cmd} && echo {edelim} || echo {edelim} & }} 2>/dev/null" ) else: # This is dangerous. We are in raw mode, and if the process never # ends and doesn't provide a way to exit, then we are stuck. commands.append(f"echo; echo {sdelim}; {cmd}; echo {edelim}") # Re-enable normal job control in bash commands.append("set -m") # Join them all into one command command = ";".join(commands).encode("utf-8") # Enter raw mode w/ no echo on the remote terminal # DANGER if "b" in mode: self.raw(echo=False) self.client.sendall(command + b"\n") while not self.recvuntil(b"\n").startswith(sdelim.encode("utf-8")): continue # Send the data if requested if callable(data): data() elif data is not None: self.client.sendall(data) pipe = RemoteBinaryPipe(self, mode, edelim.encode("utf-8"), True, exit_cmd) pipe.name = name if "w" in mode: wrapped = io.BufferedRWPair(pipe, pipe) wrapped.name = pipe.name pipe = wrapped else: pipe = io.BufferedReader(pipe) return pipe