1
mirror of https://github.com/rapid7/metasploit-payloads synced 2025-01-08 14:36:22 +01:00

Rework powershell_shell to work with "streaming"

This commit changes the channel functionality within the powershell extension so that commands do execute behind the scenes and stream the results to the UI in the current channel.

This comes with the caveat that users are patient. I haven't yet made sure that running separate commands while long running ones are running will not cause problems. We'll have to see.
This commit is contained in:
OJ 2018-05-07 21:09:04 +10:00
parent f44877ae29
commit 90265c5a0f
No known key found for this signature in database
GPG Key ID: D5DC61FB93260597
6 changed files with 3454 additions and 3088 deletions

View File

@ -20,6 +20,7 @@ typedef struct _InteractiveShell
HANDLE wait_handle;
_bstr_t output;
wchar_t* session_id;
LOCK* buffer_lock;
} InteractiveShell;
#define SAFE_RELEASE(x) if((x) != NULL) { (x)->Release(); x = NULL; }
@ -41,6 +42,9 @@ static _AssemblyPtr gClrPowershellAssembly = NULL;
static _TypePtr gClrPowershellType = NULL;
static LIST* gLoadedAssemblies = NULL;
DWORD channelise_session(wchar_t* sessionId, Channel* channel, LPVOID context);
DWORD unchannelise_session(wchar_t* sessionId);
DWORD load_assembly(BYTE* assemblyData, DWORD assemblySize)
{
dprintf("[PSH] loading assembly of size %u", assemblySize);
@ -123,7 +127,7 @@ DWORD remove_session(wchar_t* sessionId)
// Invoke the method from the Type interface.
hr = gClrPowershellType->InvokeMember_3(
bstrStaticMethodName,
static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),
static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_NonPublic),
NULL,
vtEmpty,
psaStaticMethodArgs,
@ -186,7 +190,7 @@ DWORD invoke_ps_command(wchar_t* sessionId, wchar_t* command, _bstr_t& output)
// Invoke the method from the Type interface.
hr = gClrPowershellType->InvokeMember_3(
bstrStaticMethodName,
static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),
static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_NonPublic),
NULL,
vtEmpty,
psaStaticMethodArgs,
@ -451,9 +455,13 @@ DWORD powershell_channel_interact_notify(Remote *remote, LPVOID entryContext, LP
if (shell->output.length() > 1 && shell->wait_handle != NULL)
{
lock_acquire(shell->buffer_lock);
dprintf("[PSH SHELL] received notification to write");
DWORD result = channel_write(channel, remote, NULL, 0, (PUCHAR)(char*)shell->output, byteCount, NULL);
shell->output = "";
ResetEvent(shell->wait_handle);
lock_release(shell->buffer_lock);
dprintf("[PSH SHELL] write completed");
}
return ERROR_SUCCESS;
@ -466,7 +474,11 @@ DWORD powershell_channel_interact_destroy(HANDLE waitable, LPVOID entryContext,
if (shell->wait_handle)
{
HANDLE h = shell->wait_handle;
lock_acquire(shell->buffer_lock);
unchannelise_session(shell->session_id);
shell->wait_handle = NULL;
lock_release(shell->buffer_lock);
lock_destroy(shell->buffer_lock);
CloseHandle(h);
}
return ERROR_SUCCESS;
@ -482,10 +494,13 @@ DWORD powershell_channel_interact(Channel *channel, Packet *request, LPVOID cont
{
dprintf("[PSH SHELL] beginning interaction");
shell->wait_handle = CreateEventA(NULL, FALSE, FALSE, NULL);
shell->buffer_lock = lock_create();
result = scheduler_insert_waitable(shell->wait_handle, channel, context,
powershell_channel_interact_notify, powershell_channel_interact_destroy);
channelise_session(shell->session_id, channel, context);
SetEvent(shell->wait_handle);
}
}
@ -511,12 +526,28 @@ DWORD powershell_channel_write(Channel* channel, Packet* request, LPVOID context
DWORD result = invoke_ps_command(shell->session_id, codeMarshall, output);
if (result == ERROR_SUCCESS && shell->wait_handle)
{
shell->output += output + "PS > ";
lock_acquire(shell->buffer_lock);
shell->output += output;
SetEvent(shell->wait_handle);
lock_release(shell->buffer_lock);
}
return result;
}
void powershell_channel_streamwrite(Channel* channel, LPVOID context, char* message)
{
InteractiveShell* shell = (InteractiveShell*)context;
if (shell->wait_handle)
{
lock_acquire(shell->buffer_lock);
vdprintf("[PSH SHELL] received message: %s", message);
shell->output += message;
SetEvent(shell->wait_handle);
lock_release(shell->buffer_lock);
}
}
DWORD powershell_channel_close(Channel* channel, Packet* request, LPVOID context)
{
dprintf("[PSH SHELL] closing channel");
@ -538,6 +569,151 @@ DWORD powershell_channel_close(Channel* channel, Packet* request, LPVOID context
return ERROR_SUCCESS;
}
DWORD channelise_session(wchar_t* sessionId, Channel* channel, LPVOID context)
{
if (sessionId == NULL)
{
sessionId = L"Default";
}
HRESULT hr;
bstr_t bstrStaticMethodName(L"Channelise");
SAFEARRAY *psaStaticMethodArgs = NULL;
variant_t vtEmpty;
variant_t vtSessionArg(sessionId == NULL ? L"Default" : sessionId);
variant_t vtWriterArg((__int64)powershell_channel_streamwrite);
variant_t vtChannelArg((__int64)channel);
variant_t vtContextArg((__int64)context);
LONG index = 0;
if (gClrPowershellType == NULL)
{
return ERROR_INVALID_HANDLE;
}
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 4);
do
{
hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtSessionArg);
if (FAILED(hr))
{
dprintf("[PSH] failed to prepare session argument: 0x%x", hr);
break;
}
index++;
hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtWriterArg);
if (FAILED(hr))
{
dprintf("[PSH] failed to prepare command argument: 0x%x", hr);
break;
}
index++;
hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtChannelArg);
if (FAILED(hr))
{
dprintf("[PSH] failed to prepare command argument: 0x%x", hr);
break;
}
index++;
hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtContextArg);
if (FAILED(hr))
{
dprintf("[PSH] failed to prepare command argument: 0x%x", hr);
break;
}
// Invoke the method from the Type interface.
hr = gClrPowershellType->InvokeMember_3(
bstrStaticMethodName,
static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_NonPublic),
NULL,
vtEmpty,
psaStaticMethodArgs,
NULL);
if (FAILED(hr))
{
dprintf("[PSH] failed to invoke powershell function %s 0x%x", (char*)bstrStaticMethodName, hr);
break;
}
} while (0);
if (psaStaticMethodArgs != NULL)
{
SafeArrayDestroy(psaStaticMethodArgs);
}
if (SUCCEEDED(hr))
{
return ERROR_SUCCESS;
}
return (DWORD)hr;
}
DWORD unchannelise_session(wchar_t* sessionId)
{
if (sessionId == NULL)
{
sessionId = L"Default";
}
HRESULT hr;
bstr_t bstrStaticMethodName(L"Unchannelise");
SAFEARRAY *psaStaticMethodArgs = NULL;
variant_t vtSessionArg(sessionId);
variant_t vtEmpty;
LONG index = 0;
if (gClrPowershellType == NULL)
{
return ERROR_INVALID_HANDLE;
}
dprintf("[PSH] Attempting to Unchannelise %S", sessionId);
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
do
{
hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtSessionArg);
if (FAILED(hr))
{
dprintf("[PSH] failed to prepare session argument: 0x%x", hr);
break;
}
// Invoke the method from the Type interface.
hr = gClrPowershellType->InvokeMember_3(
bstrStaticMethodName,
static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_NonPublic),
NULL,
vtEmpty,
psaStaticMethodArgs,
NULL);
if (FAILED(hr))
{
dprintf("[PSH] failed to invoke powershell function %s 0x%x", (char*)bstrStaticMethodName, hr);
break;
}
} while (0);
if (psaStaticMethodArgs != NULL)
{
SafeArrayDestroy(psaStaticMethodArgs);
}
if (SUCCEEDED(hr))
{
return ERROR_SUCCESS;
}
return (DWORD)hr;
}
/*!
* @brief Start an interactive powershell session.
* @param remote Pointer to the \c Remote making the request.

View File

@ -6,7 +6,7 @@
#ifndef _METERPRETER_SOURCE_EXTENSION_POWERSHELL_RUNNER_H
#define _METERPRETER_SOURCE_EXTENSION_POWERSHELL_RUNNER_H
#define PSHRUNNER_DLL_LEN 47616
#define PSHRUNNER_DLL_LEN 48640
extern unsigned char PowerShellRunnerDll[PSHRUNNER_DLL_LEN];

View File

@ -2,7 +2,9 @@
using System.Collections.Generic;
using System.Management.Automation.Host;
using System.Management.Automation.Runspaces;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace MSF.Powershell
{
@ -22,7 +24,21 @@ namespace MSF.Powershell
_runners = new Dictionary<string, Runner>();
}
public static string Execute(string id, string ps)
internal static void Channelise(string id, Int64 channelWriter, Int64 channel, Int64 context)
{
var runner = Get(id);
System.Diagnostics.Debug.Write(string.Format("[PSH RUNNER] Channelising {0} with 0x{1:X} - 0x{2:X}", id, channelWriter, context));
runner._host.UserInterface.Channelise(channelWriter, channel, context);
}
internal static void Unchannelise(string id)
{
var runner = Get(id);
System.Diagnostics.Debug.Write(string.Format("[PSH RUNNER] Unchannelising {0}", id));
runner._host.UserInterface.Unchannelise();
}
internal static string Execute(string id, string ps)
{
System.Diagnostics.Debug.Write(string.Format("[PSH RUNNER] Executing command on session {0}", id));
if (!_runners.ContainsKey(id))
@ -33,7 +49,7 @@ namespace MSF.Powershell
return runner.Execute(ps);
}
public static Runner Get(string id)
internal static Runner Get(string id)
{
if (!_runners.ContainsKey(id))
{
@ -42,7 +58,7 @@ namespace MSF.Powershell
return _runners[id];
}
public static void Remove(string id)
internal static void Remove(string id)
{
if (_runners.ContainsKey(id))
{
@ -51,7 +67,7 @@ namespace MSF.Powershell
}
}
public Runner(string id)
internal Runner(string id)
{
_id = id;
_state = InitialSessionState.CreateDefault();
@ -69,21 +85,52 @@ namespace MSF.Powershell
}
}
public string Execute(string ps)
private string InvokePipline(string ps)
{
ps = "IEX ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(\"" + Convert.ToBase64String(Encoding.UTF8.GetBytes(ps), Base64FormattingOptions.None) + "\")))";
System.Diagnostics.Debug.Write(string.Format("[PSH RUNNER] Executing PS: {0}", ps));
System.Diagnostics.Debug.Write(string.Format("[PSH RUNNER] Executing PS directly: {0}", ps));
using (Pipeline pipeline = _runspace.CreatePipeline())
{
pipeline.Commands.AddScript(ps);
pipeline.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
pipeline.Commands.Add("out-default");
pipeline.Invoke();
}
return _host.GetAndFlushOutput();
}
private void ThreadInvokePipeline(object psObj)
{
// Sneak a prompt string in at the end.
var ps = psObj.ToString();
ps = "IEX ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(\"" + Convert.ToBase64String(Encoding.UTF8.GetBytes(ps), Base64FormattingOptions.None) + "\")))";
System.Diagnostics.Debug.Write(string.Format("[PSH RUNNER] Executing PS on thread: {0}", ps));
using (Pipeline pipeline = _runspace.CreatePipeline())
{
pipeline.Commands.AddScript(ps);
pipeline.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
pipeline.Commands.Add("out-default");
pipeline.Invoke();
_host.GetAndFlushOutput();
_host.UserInterface.WriteRaw("PS > ");
}
}
internal string Execute(string ps)
{
if (_host.UserInterface.IsChannelised)
{
var t = new Thread(new ParameterizedThreadStart(ThreadInvokePipeline));
t.Start(ps);
return string.Empty;
}
return InvokePipline(ps);
}
public void Dispose()
{
if (_runspace != null)
@ -98,6 +145,11 @@ namespace MSF.Powershell
private Guid _hostId;
private CustomPSHostUserInterface _ui = null;
public CustomPSHostUserInterface UserInterface
{
get { return _ui; }
}
public CustomPSHost()
{
_hostId = Guid.NewGuid();
@ -167,17 +219,40 @@ namespace MSF.Powershell
private StringBuilder _buffer;
private CustomPSHostRawUserInterface _rawUI;
private delegate void WriteChannel(Int64 channel, Int64 context, byte[] buffer);
private WriteChannel _chanWriter = null;
private Int64 _channel = 0;
private Int64 _context = 0;
public CustomPSHostUserInterface()
{
_buffer = new StringBuilder();
_rawUI = new CustomPSHostRawUserInterface();
}
public bool IsChannelised
{
get { return _chanWriter != null; }
}
public override string ToString()
{
return _buffer.ToString();
}
public void Channelise(Int64 channelWriter, Int64 channel, Int64 context)
{
_chanWriter = (WriteChannel)Marshal.GetDelegateForFunctionPointer(new IntPtr(channelWriter), typeof(WriteChannel));
_channel = channel;
_context = context;
}
public void Unchannelise()
{
_chanWriter = null;
_context = 0;
}
public void Clear()
{
_buffer.Remove(0, _buffer.Length);
@ -218,41 +293,44 @@ namespace MSF.Powershell
return new System.Security.SecureString();
}
public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string message)
{
_buffer.Append(value.TrimEnd());
WriteTarget(message.TrimEnd());
}
public override void Write(string value)
public override void Write(string message)
{
_buffer.Append(value.TrimEnd());
WriteTarget(message.TrimEnd());
}
public void WriteRaw(string message)
{
WriteTarget(message);
}
public override void WriteDebugLine(string message)
{
_buffer.Append("DEBUG: ");
_buffer.AppendLine(message.TrimEnd());
WriteTarget(string.Format("DEBUG: {0}\n", message.TrimEnd()));
}
public override void WriteErrorLine(string value)
public override void WriteErrorLine(string message)
{
_buffer.Append("ERROR: ");
_buffer.AppendLine(value.TrimEnd());
WriteTarget(string.Format("ERROR: {0}\n", message.TrimEnd()));
}
public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string message)
{
_buffer.AppendLine(value.TrimEnd());
WriteTarget(string.Format("{0}\n", message.TrimEnd()));
}
public override void WriteLine(string value)
public override void WriteLine(string message)
{
_buffer.AppendLine(value.TrimEnd());
WriteTarget(string.Format("{0}\n", message.TrimEnd()));
}
public override void WriteLine()
{
_buffer.AppendLine();
WriteTarget("\n");
}
public override void WriteProgress(long sourceId, System.Management.Automation.ProgressRecord record)
@ -261,14 +339,27 @@ namespace MSF.Powershell
public override void WriteVerboseLine(string message)
{
_buffer.Append("VERBOSE: ");
_buffer.AppendLine(message.TrimEnd());
WriteTarget(string.Format("VERBOSE: {0}\n", message.TrimEnd()));
}
public override void WriteWarningLine(string message)
{
_buffer.Append("WARNING: ");
_buffer.AppendLine(message.TrimEnd());
WriteTarget(string.Format("WARNING: {0}\n", message.TrimEnd()));
}
private void WriteTarget(string message)
{
if (IsChannelised)
{
var bytes = System.Text.Encoding.ASCII.GetBytes(message);
//System.Diagnostics.Debug.WriteLine("[PSH BINDING] Writing to channel: " + message);
_chanWriter(_channel, _context, bytes);
}
else
{
//System.Diagnostics.Debug.WriteLine("[PSH BINDING] Writing to buffer: " + message);
_buffer.Append(message);
}
}
}

View File

@ -47,6 +47,9 @@ function Invoke-DcSyncAll {
.SYNOPSIS
Use the DCSync functionality to DCSync every user that's present in the DC.
It is recommended that this script be invoked inside channelised shell
(via powershell_shell) because it can take a while in large domains.
.PARAMETER Domain
Specifies the domain name to contact for extracting all the users from.
@ -104,6 +107,9 @@ function Invoke-DcSyncHashDump {
basically attempting to be a hashdump function that works remotely and doesn't
require the need to be on the DC, or to extract NTDS.dit.
It is recommended that this script be invoked inside channelised shell
(via powershell_shell) because it can take a while in large domains.
.PARAMETER Domain
Specifies the domain name that is the target of the hash dumping.

View File

@ -5,19 +5,23 @@ function Generate-MetasploitPowershell {
Powershell extension. These files contain the body of the MSF.Powershell.Runner
class in .NET that allow for the extension to interact with the interpreter.
.PARAMETER Build
Specifies which build to use when generating the source. Values are:
Release (Default)
Debug
.PARAMETER BuildDir
Specifies the 'build' folder the powershelll project. By default, the current
folder is used, however if this is invoked outside of the specified folder then
the location of the folder containing this script has to be specified.
.PARAMETER Debug
Indicates that the debug build should be used instead of the release build (for testing).
.INPUTS
None.
.OPUTPUTS
Writes some content to screen to inform the user of success or failure.
.EXAMPLE
PS C:\metasploit-payloads\powershell\build\> Generate-MetasploitPowershell -Build Debug
PS C:\> Generate-MetasploitPowershell -Build Release -BuildDir C:\metasploit-payloads\powershell\build\
PS C:\metasploit-payloads\powershell\build\> Generate-MetasploitPowershell -Debug
PS C:\> Generate-MetasploitPowershell -BuildDir C:\metasploit-payloads\powershell\build\
#>
param(
@ -25,11 +29,15 @@ function Generate-MetasploitPowershell {
[String]
$BuildDir = $(Get-Location),
[ValidatePattern('^([Rr]elease|[Dd]ebug)$')]
[String]
$Build = 'Release'
[Switch]
$Debug
)
$Build = 'Release'
If ($Debug) {
$Build = 'Debug'
}
$SourceAssembly = [System.IO.Path]::Combine($BuildDir, '..', 'MSF.Powershell', 'bin', $Build, 'MSF.Powershell.dll')
Write-Host [+] Building source using binary at $SourceAssembly ...
If (-not (Test-Path -LiteralPath $SourceAssembly)) {