Chapter 2: Establishing a Listening Post
Building a basic HTTP listening post, REST API & database.
building-c2-implants-in-cpp-master.zip
230KB
Binary
Book Source Code

Using the Source Code

This is the chapter where we'll begin writing all our code. You can download the source files for this primer using the link at the top of the page labeled "Book Source Code". Inside, you'll find the final project code and incremental versions in various "chapter" folders. You can follow along by writing the code as you go, or just jump in at various points by using the chapter folder projects.
If you notice something that could be improved and want to submit a pull request or just want to browse the source code on GitHub, the repository can be found here: https://github.com/shogunlab/building-c2-implants-in-cpp

Introduction

Our listening post known as Skytree is going to be built with Flask, a REST API plugin called flask_restful and we'll use a MongoDB database for storage. At a high level, it will need to be capable of serving tasks to an implant, storing a record of tasks that were sent and receiving the results of those tasks. The reason why I chose Flask to build the REST API is because I'm comfortable with programming in Python and it's quick to get started with. Additionally, I think that the source code is pretty easy to read and understand if you're just starting out. I decided to use MongoDB for storage because I'm familiar with it and wanted to use something that would easily ingest JSON results from the implant. I don't have any strong technical reasons for choosing MongoDB, so feel free to modify the source code to use an SQL database if you'd prefer that instead.
Let's take our first step and write out the starting code for our HTTP listening post. Download the source code for this book and unzip it. We're going to be installing a number of Python packages, so I'd recommend using a tool such as virtualenv to have a clean environment for installation. Navigate to the directory called "chapter_2-1". Go to the "Skytree" folder in a terminal window and run the pip install -r requirements.txt command to ensure you have the Python library prerequisites for the project installed. For the database, you'll need to install the MongoDB Community Server. You can read a detailed guide on installing it here if you run into any issues. Then, open the "Skytree" folder in your preferred code editor and find the file called listening_post.py. You'll see the following contents:
listening_post.py
1
import json
2
import resources
3
4
from flask import Flask
5
from flask_restful import Api
6
from database.db import initialize_db
7
8
# Initialize our Flask app
9
app = Flask(__name__)
10
11
# Configure our database on localhost
12
app.config['MONGODB_SETTINGS'] = {
13
'host': 'mongodb://localhost/skytree'
14
}
15
16
# Initialize our database
17
initialize_db(app)
18
19
# Initialize our API
20
api = Api(app)
21
22
# Define the routes for each of our resources
23
api.add_resource(resources.Tasks, '/tasks', endpoint='tasks')
24
25
# Start the Flask app in debug mode
26
if __name__ == '__main__':
27
app.run(debug=True)
28
Copied!

Database & Models Starting File

Let's go over each of the major code blocks in the above file. We start by initializing the Flask app and the database:
1
import json
2
import resources
3
4
from flask import Flask
5
from flask_restful import Api
6
from database.db import initialize_db
7
8
# Initialize our Flask app
9
app = Flask(__name__)
10
11
# Configure our database on localhost
12
app.config['MONGODB_SETTINGS'] = {
13
'host': 'mongodb://localhost/skytree'
14
}
15
16
# Initialize our database
17
initialize_db(app)
Copied!
Go to the "database" folder and you'll see two files:
  • db.py
  • models.py
Open db.py and you'll see the following:
db.py
1
from flask_mongoengine import MongoEngine
2
3
# Initialize MongoEngine and our database
4
db = MongoEngine()
5
6
def initialize_db(app):
7
db.init_app(app)
Copied!
The above code will initialize the database and it takes our Flask app as input. Open up the models.py file and you'll see where we define our models:
models.py
1
from database.db import db
2
3
# Define Task object in database
4
class Task(db.DynamicDocument):
5
task_id = db.StringField(required=True)
Copied!
The above code tells the database about each field we're storing and the kind of data to expect. For simplicity, we're using a "dynamic document" so that we don't need to specify every field. In the task model, we're requiring that an ID be provided to ensure we can keep track of each task and map results back. Each time we add a new resource for the REST API, we'll want to put a corresponding model specification in this file.

REST API & Resources

To facilitate testing the REST API we're building, we'll use a tool called Postman. You do not need an account to use the tool, just select the "skip" option when you first run the application. I find that this tool is useful for experimenting with APIs and easily interacting with them. I've included a Postman Collection file for reference called "Skytree_REST_API.postman_collection.json" in the root directory of the book source code files. You can import this collection and use it to follow along with the API requests referred to in this chapter. Alternatively, I've included PowerShell snippets to make API requests in-case you prefer not to use Postman.
Now, let's go back to the listening_post.py file. In the next block we set up the REST API and specify the resources that map to each of the API endpoints:
1
# Initialize our API
2
api = Api(app)
3
4
# Define the routes for each of our resources
5
api.add_resource(resources.Tasks, '/tasks', endpoint='tasks')
Copied!
The "/tasks" endpoint will be responsible for handling creation of tasks and displaying existing tasks. We'll go into more detail about this resource shortly, but we're just defining the route here.
Lastly, in the final block for our listening_post.py file, we start the Flask app in debug mode:
1
# Start the Flask app in debug mode
2
if __name__ == '__main__':
3
app.run(debug=True)
Copied!
To validate that everything is working as expected with our initial code, try running the command python listening_post.py from inside the "Skytree" folder and then in a browser, visit the following address http://127.0.0.1:5000/tasks. You should see a short message that says "GET success!".
Let's add in the behavior for our "tasks" resource next. Open up the file called resources.py and you'll see the following contents:
resources.py
1
import uuid
2
import json
3
4
from flask import request, Response
5
from flask_restful import Resource
6
from database.db import initialize_db
7
from database.models import Task
8
9
10
class Tasks(Resource):
11
# ListTasks
12
def get(self):
13
# Add behavior for GET here
14
return "GET success!", 200
15
16
# AddTasks
17
def post(self):
18
# Add behavior for POST here
19
return "POST success!", 200
Copied!

Tasks API

We'll define the behavior for GET requests first. Let's get all the Task objects that we have in the database, convert them to JSON format and put them in a variable. Then, we'll return that in the GET response:
1
# ListTasks
2
def get(self):
3
# Get all the task objects and return them to the user
4
tasks = Task.objects().to_json()
5
return Response(tasks, mimetype="application/json", status=200)
Copied!
For the POST, let's get the JSON payload from the request body first and find out how many Task objects are in the request. Next, we'll load it into a JSON object and then for each Task object, we'll add a UUID for tracking and save it to the database. Finally, we store everything that comes after "task_type" and "task_id" in a "task_options" array so we can store it in a TaskHistory object later on. We return a response that includes the Task objects that were added to the database.
1
# AddTasks
2
def post(self):
3
# Parse out the JSON body we want to add to the database
4
body = request.get_json()
5
json_obj = json.loads(json.dumps(body))
6
# Get the number of Task objects in the request
7
obj_num = len(body)
8
# For each Task object, add it to the database
9
for i in range(len(body)):
10
# Add a task UUID to each task object for tracking
11
json_obj[i]['task_id'] = str(uuid.uuid4())
12
# Save Task object to database
13
Task(**json_obj[i]).save()
14
# Load the options provided for the task into an array for tracking in history
15
task_options = []
16
for key in json_obj[i].keys():
17
# Anything that comes after task_type and task_id is treated as an option
18
if (key != "task_type" and key != "task_id"):
19
task_options.append(key + ": " + json_obj[i][key])
20
# Return the last Task objects that were added
21
return Response(Task.objects.skip(Task.objects.count() - obj_num).to_json(),
22
mimetype="application/json",
23
status=200)
Copied!
Once you're done adding the code for POST, your resources.py file should look like this:
resources.py
1
import uuid
2
import json
3
4
from flask import request, Response
5
from flask_restful import Resource
6
from database.db import initialize_db
7
from database.models import Task
8
9
10
class Tasks(Resource):
11
# ListTasks
12
def get(self):
13
# Get all the task objects and return them to the user
14
tasks = Task.objects().to_json()
15
return Response(tasks, mimetype="application/json", status=200)
16
17
# AddTasks
18
def post(self):
19
# Parse out the JSON body we want to add to the database
20
body = request.get_json()
21
json_obj = json.loads(json.dumps(body))
22
# Get the number of Task objects in the request
23
obj_num = len(body)
24
# For each Task object, add it to the database
25
for i in range(len(body)):
26
# Add a task UUID to each task object for tracking
27
json_obj[i]['task_id'] = str(uuid.uuid4())
28
# Save Task object to database
29
Task(**json_obj[i]).save()
30
# Load the options provided for the task into an array for tracking in history
31
task_options = []
32
for key in json_obj[i].keys():
33
# Anything that comes after task_type and task_id is treated as an option
34
if (key != "task_type" and key != "task_id"):
35
task_options.append(key + ": " + json_obj[i][key])
36
# Return the last Task objects that were added
37
return Response(Task.objects.skip(Task.objects.count() - obj_num).to_json(),
38
mimetype="application/json",
39
status=200)
40
Copied!
Let's test out our AddTasks API. Start the listening post with the following:
1
python listening_post.py
Copied!
Once the listening post is running, make the following POST request with the following format (we're starting out with a simple "ping" task):
1
POST /tasks HTTP/1.1
2
Host: localhost:5000
3
Content-Type: application/json
4
5
[
6
{
7
"task_type":"ping"
8
}
9
]
Copied!
You can also make the above POST request with the following PowerShell command lines:
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]"
5
6
$response = Invoke-RestMethod 'http://localhost:5000/tasks' -Method 'POST' -Headers $headers -Body $body
7
$response | ConvertTo-Json
Copied!
You should get back a response that looks something like this:
1
[
2
{
3
"_id": {
4
"$oid": "5f37310f6adea94a3b8bdc3c"
5
},
6
"task_id": "26fdb35d-1d86-428c-90da-d2460a332c28",
7
"task_type": "ping"
8
}
9
]
Copied!
Now, let's test out the ListTasks API by visiting the endpoint (http://127.0.0.1:5000/tasks) in a browser. You should get back a response that looks something like this:
1
[
2
{
3
"_id": {
4
"$oid": "5f37310f6adea94a3b8bdc3c"
5
},
6
"task_id": "26fdb35d-1d86-428c-90da-d2460a332c28",
7
"task_type": "ping"
8
}
9
]
Copied!
Feel free to play around with the ListTasks and AddTasks APIs. You can add multiple ping tasks and you'll see that each one gets added to the database, then listed in a JSON response when you call ListTasks:
1
[
2
{
3
"_id": {
4
"$oid": "5f37310f6adea94a3b8bdc3c"
5
},
6
"task_id": "26fdb35d-1d86-428c-90da-d2460a332c28",
7
"task_type": "ping"
8
},
9
{
10
"_id": {
11
"$oid": "5f3731c46adea94a3b8bdc3d"
12
},
13
"task_id": "aa66f366-e699-44ae-a75b-2be72b62da2d",
14
"task_type": "ping"
15
},
16
{
17
"_id": {
18
"$oid": "5f3731c76adea94a3b8bdc3e"
19
},
20
"task_id": "fe9b1fa8-9ff4-4b41-9df2-012084e09a60",
21
"task_type": "ping"
22
}
23
]
Copied!

Results API

You can find the complete contents of the project so far in the folder called "chapter_2-2". We'll move on now to adding our results APIs, ListResults and AddResults. First, write the following code to define our Result object in the database:
models.py
1
from database.db import db
2
3
# Define Task object in database
4
class Task(db.DynamicDocument):
5
task_id = db.StringField(required=True)
6
7
# Define Result object in database
8
class Result(db.DynamicDocument):
9
result_id = db.StringField(required=True)
Copied!
Next, we'll edit our resources.py file to import the Result database object:
resources.py
1
from database.models import Task, Result
Copied!
Now, we can start adding the logic for the Result APIs with the following boilerplate:
1
class Results(Resource):
2
# ListResults
3
def get(self):
4
# Add behavior for GET here
5
return "GET success!", 200
6
7
# AddResults
8
def post(self):
9
# Add behavior for POST here
10
return "POST success!", 200
11
Copied!
The ListResults API can be built by adding the following code to return results as a JSON response to the user:
1
# ListResults
2
def get(self):
3
# Get all the result objects and return them to the user
4
results = Result.objects().to_json()
5
return Response(results, mimetype="application.json", status=200)
Copied!
You'll note that the above code is very similar to the ListTasks API. We'll start building the AddResults API by handling the POST request. We first check if the results returned are empty and if they're populated, we parse out the JSON in the request body. We save each Result object to the database and get the list of Task objects waiting to be served to the implant. We delete the Task objects we're serving to the implant so that tasks are never executed twice. Then, we send the Task objects to the implant in the POST request response:
1
# AddResults
2
def post(self):
3
# Check if results from the implant are populated
4
if str(request.get_json()) != '{}':
5
# Parse out the result JSON that we want to add to the database
6
body = request.get_json()
7
print("Received implant response: {}".format(body))
8
json_obj = json.loads(json.dumps(body))
9
# Add a result UUID to each result object for tracking
10
json_obj['result_id'] = str(uuid.uuid4())
11
Result(**json_obj).save()
12
# Serve latest tasks to implant
13
tasks = Task.objects().to_json()
14
# Clear tasks so they don't execute twice
15
Task.objects().delete()
16
return Response(tasks, mimetype="application/json", status=200)
Copied!
We add an "else" check to handle cases where no results are returned and we will simply return the Task objects waiting, then delete them:
1
else:
2
# Serve latest tasks to implant
3
tasks = Task.objects().to_json()
4
# Clear tasks so they don't execute twice
5
Task.objects().delete()
6
return Response(tasks, mimetype="application/json", status=200)
Copied!
When you're all done, your resources.py file should look like this:
1
import uuid
2
import json
3
4
from flask import request, Response
5
from flask_restful import Resource
6
from database.db import initialize_db
7
from database.models import Task, Result
8
9
10
class Tasks(Resource):
11
# ListTasks
12
def get(self):
13
# Get all the task objects and return them to the user
14
tasks = Task.objects().to_json()
15
return Response(tasks, mimetype="application/json", status=200)
16
17
# AddTasks
18
def post(self):
19
# Parse out the JSON body we want to add to the database
20
body = request.get_json()
21
json_obj = json.loads(json.dumps(body))
22
# Get the number of Task objects in the request
23
obj_num = len(body)
24
# For each Task object, add it to the database
25
for i in range(len(body)):
26
# Add a task UUID to each task object for tracking
27
json_obj[i]['task_id'] = str(uuid.uuid4())
28
# Save Task object to database
29
Task(**json_obj[i]).save()
30
# Load the options provided for the task into an array for tracking in history
31
task_options = []
32
for key in json_obj[i].keys():
33
# Anything that comes after task_type and task_id is treated as an option
34
if (key != "task_type" and key != "task_id"):
35
task_options.append(key + ": " + json_obj[i][key])
36
# Return the last Task objects that were added
37
return Response(Task.objects.skip(Task.objects.count() - obj_num).to_json(),
38
mimetype="application/json",
39
status=200)
40
41
class Results(Resource):
42
# ListResults
43
def get(self):
44
# Get all the result objects and return them to the user
45
results = Result.objects().to_json()
46
return Response(results, mimetype="application.json", status=200)
47
48
# AddResults
49
def post(self):
50
# Check if results from the implant are populated
51
if str(request.get_json()) != '{}':
52
# Parse out the result JSON that we want to add to the database
53
body = request.get_json()
54
print("Received implant response: {}".format(body))
55
json_obj = json.loads(json.dumps(body))
56
# Add a result UUID to each result object for tracking
57
json_obj['result_id'] = str(uuid.uuid4())
58
Result(**json_obj).save()
59
# Serve latest tasks to implant
60
tasks = Task.objects().to_json()
61
# Clear tasks so they don't execute twice
62
Task.objects().delete()
63
return Response(tasks, mimetype="application/json", status=200)
64
else:
65
# Serve latest tasks to implant
66
tasks = Task.objects().to_json()
67
# Clear tasks so they don't execute twice
68
Task.objects().delete()
69
return Response(tasks, mimetype="application/json", status=200)
Copied!
The last piece we need to complete the Results APIs are to open up the "listening_post.py" file and add the following code, which associates the Results resource with the "/results" endpoint:
1
# Define the routes for each of our resources
2
api.add_resource(resources.Tasks, '/tasks', endpoint='tasks')
3
api.add_resource(resources.Results, '/results')
Copied!
You can test the AddResults API by sending a POST request with the following mock implant result:
1
POST /results HTTP/1.1
2
Host: localhost:5000
3
Content-Type: application/json
4
5
{
6
"c839c32a-9338-491b-9d57-30a4bfc4a2e8": {
7
"contents": "PONG!",
8
"success": "true"
9
}
10
}
Copied!
The above POST request can be made with the following PowerShell command lines:
1
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
2
$headers.Add("Content-Type", "application/json")
3
4
$body = "{
5
`n `"c839c32a-9338-491b-9d57-30a4bfc4a2e8`": {
6
`n `"contents`": `"PONG!`",
7
`n `"success`": `"true`"
8
`n }
9
`n}"
10
11
$response = Invoke-RestMethod 'http://localhost:5000/results' -Method 'POST' -Headers $headers -Body $body
12
$response | ConvertTo-Json
Copied!
You'll get a response with an empty array if you haven't added any new tasks or you'll get back some tasks if you did add some before calling the AddResults API:
1
[]
Copied!
1
[
2
{
3
"_id": {
4
"$oid": "5f37540c87f8d76f8e578cff"
5
},
6
"task_id": "3bfd8e20-00f2-479d-b42f-f8af337b8aae",
7
"task_type": "ping"
8
}
9
]
Copied!
Navigate to the http://localhost:5000/results endpoint and you'll see the mock results from the Ping task stored:
1
[
2
{
3
"_id": {
4
"$oid": "5f9ee7b276f94422b607e08e"
5
},
6
"result_id": "13b47ed9-35f2-4e36-ae3e-2a683eaca0fc",
7
"c839c32a-9338-491b-9d57-30a4bfc4a2e8": {
8
"contents": "PONG!",
9
"success": "true"
10
}
11
}
12
]
Copied!

Task History API

You'll find the project code we've created so far in the folder called "chapter-2-3". The final API we will be adding is ListHistory, this will return all the tasks that were successfully served to the implant and their associated results (when they are received from the implant). Again, let's start with defining the TaskHistory object in our models.py file:
models.py
1
from database.db import db
2
3
# Define Task object in database
4
class Task(db.DynamicDocument):
5
task_id = db.StringField(required=True)
6
7
# Define Result object in database
8
class Result(db.DynamicDocument):
9
result_id = db.StringField(required=True)
10
11
# Define TaskHistory object in database
12
class TaskHistory(db.DynamicDocument):
13
task_object = db.StringField()
Copied!
Add "TaskHistory" as an import at the top of the resources.py file:
resources.py
1
from database.models import Task, Result, TaskHistory
Copied!
Next, we'll modify our AddTasks API to copy tasks that are sent to the implant into our TaskHistory collection:
1
# Load the options provided for the task into an array for tracking in history
2
task_options = []
3
for key in json_obj[i].keys():
4
# Anything that comes after task_type and task_id is treated as an option
5
if (key != "task_type" and key != "task_id"):
6
task_options.append(key + ": " + json_obj[i][key])
7
# Add to task history
8
TaskHistory(
9
task_id=json_obj[i]['task_id'],
10
task_type=json_obj[i]['task_type'],
11
task_object=json.dumps(json_obj),
12
task_options=task_options,
13
task_results=""
14
).save()
Copied!
Now, add some scaffolding for our ListHistory API in the resources.py file:
1
class History(Resource):
2
# ListHistory
3
def get(self):
4
# Add behavior for GET here
5
return "GET success!", 200
Copied!
The first thing we'll do in this API is get all the TaskHistory objects and keep them in a variable to return later and store any results we have in a collection so we can match them with tasks:
1
# ListHistory
2
def get(self):
3
# Get all the task history objects so we can return them to the user
4
task_history = TaskHistory.objects().to_json()
5
# Update any served tasks with results from implant
6
# Get all the result objects and return them to the user
7
results = Result.objects().to_json()
8
json_obj = json.loads(results)
Copied!
Next, we format each result to be more friendly to us for consumption and display them with a task_id that has a matching task_results parameters:
1
# Format each result from the implant to be more friendly for consumption/display
2
result_obj_collection = []
3
for i in range(len(json_obj)):
4
for field in json_obj[i]:
5
result_obj = {
6
"task_id": field,
7
"task_results": json_obj[i][field]
8
}
9
result_obj_collection.append(result_obj)
Copied!
Finally, we search for any results with a task ID that matches tasks that we served previously and insert them into the corresponding TaskHistory object, then we return the TaskHistory objects to the user:
1
# For each result in the collection, check for a corresponding task ID and if
2
# there's a match, update it with the results. This is hacky and there's probably
3
# a more elegant solution to update tasks with their results when they come in...
4
for result in result_obj_collection:
5
if TaskHistory.objects(task_id=result["task_id"]):
6
TaskHistory.objects(task_id=result["task_id"]).update_one(
7
set__task_results=result["task_results"])
8
return Response(task_history, mimetype="application/json", status=200)
Copied!
There's probably a more elegant and simple way to do the above, but for now this works for our purposes.
The full resources.py file should look like the following:
resources.py
1
import uuid
2
import json
3
4
from flask import request, Response
5
from flask_restful import Resource
6
from database.db import initialize_db
7
from database.models import Task, Result, TaskHistory
8
9
10
class Tasks(Resource):
11
# ListTasks
12
def get(self):
13
# Get all the task objects and return them to the user
14
tasks = Task.objects().to_json()
15
return Response(tasks, mimetype="application/json", status=200)
16
17
# AddTasks
18
def post(self):
19
# Parse out the JSON body we want to add to the database
20
body = request.get_json()
21
json_obj = json.loads(json.dumps(body))
22
# Get the number of Task objects in the request
23
obj_num = len(body)
24
# For each Task object, add it to the database
25
for i in range(obj_num):
26
# Add a task UUID to each task object for tracking
27
json_obj[i]['task_id'] = str(uuid.uuid4())
28
# Save Task object to database
29
Task(**json_obj[i]).save()
30
# Load the options provided for the task into an array for tracking in history
31
task_options = []
32
for key in json_obj[i].keys():
33
# Anything that comes after task_type and task_id is treated as an option
34
if (key != "task_type" and key != "task_id"):
35
task_options.append(key + ": " + json_obj[i][key])
36
# Add to task history
37
TaskHistory(
38
task_id=json_obj[i]['task_id'],
39
task_type=json_obj[i]['task_type'],
40
task_object=json.dumps(json_obj),
41
task_options=task_options,
42
task_results=""
43
).save()
44
# Return the last Task objects that were added
45
return Response(Task.objects.skip(Task.objects.count() - obj_num).to_json(),
46
mimetype="application/json",
47
status=200)
48
49
50
class Results(Resource):
51
# ListResults
52
def get(self):
53
# Get all the result objects and return them to the user
54
results = Result.objects().to_json()
55
return Response(results, mimetype="application.json", status=200)
56
57
# AddResults
58
def post(self):
59
# Check if results from the implant are populated
60
if str(request.get_json()) != '{}':
61
# Parse out the result JSON that we want to add to the database
62
body = request.get_json()
63
print("Received implant response: {}".format(body))
64
json_obj = json.loads(json.dumps(body))
65
# Add a result UUID to each result object for tracking
66
json_obj['result_id'] = str(uuid.uuid4())
67
Result(**json_obj).save()
68
# Serve latest tasks to implant
69
tasks = Task.objects().to_json()
70
# Clear tasks so they don't execute twice
71
Task.objects().delete()
72
return Response(tasks, mimetype="application/json", status=200)
73
else:
74
# Serve latest tasks to implant
75
tasks = Task.objects().to_json()
76
# Clear tasks so they don't execute twice
77
Task.objects().delete()
78
return Response(tasks, mimetype="application/json", status=200)
79
80
81
class History(Resource):
82
# ListHistory
83
def get(self):
84
# Get all the task history objects so we can return them to the user
85
task_history = TaskHistory.objects().to_json()
86
# Update any served tasks with results from implant
87
# Get all the result objects and return them to the user
88
results = Result.objects().to_json()
89
json_obj = json.loads(results)
90
# Format each result from the implant to be more friendly for consumption/display
91
result_obj_collection = []
92
for i in range(len(json_obj)):
93
for field in json_obj[i]:
94
result_obj = {
95
"task_id": field,
96
"task_results": json_obj[i][field]
97
}
98
result_obj_collection.append(result_obj)
99
# For each result in the collection, check for a corresponding task ID and if
100
# there's a match, update it with the results. This is hacky and there's probably
101
# a more elegant solution to update tasks with their results when they come in...
102
for result in result_obj_collection:
103
if TaskHistory.objects(task_id=result["task_id"]):
104
TaskHistory.objects(task_id=result["task_id"]).update_one(
105
set__task_results=result["task_results"])
106
return Response(task_history, mimetype="application/json", status=200)
107
Copied!
The last thing to add in order to have a fully functional ListHistory API is to add the following in the listening_post.py file:
listening_post.py
1
# Define the routes for each of our resources
2
api.add_resource(resources.Tasks, '/tasks', endpoint='tasks')
3
api.add_resource(resources.Results, '/results')
4
api.add_resource(resources.History, '/history')
Copied!
That's it! You'll find the complete Skytree listening post project in the folder "chapter_2-4". You can get a list of the task history by visiting the ListHistory endpoint (http://127.0.0.1:5000/history). It'll be empty if you haven't made any AddTask requests. If you make an AddTask request now with a ping task, then call ListHistory, you should get back a response that looks like this:
1
[
2
{
3
"_id": {
4
"$oid": "5f3760aa50954f9c61397b8e"
5
},
6
"task_object": "[{\"task_type\": \"ping\", \"task_id\": \"59906de7-8739-4738-a69d-864f9a37cb3b\"}]",
7
"task_id": "59906de7-8739-4738-a69d-864f9a37cb3b",
8
"task_type": "ping",
9
"task_options": [],
10
"task_results": ""
11
}
12
]
Copied!
You'll see that the TaskHistory object consists of the original JSON task object that was sent to the implant and the task options supplied. When the implant returns a result, the task_results field will be updated with the result contents.

Conclusion

Congrats on a job well done! If you've reached this point, you now have a working HTTP listening post that our implant can talk to! We can use this to send new tasks to our implant and get back results of the tasks we send. We also have the ability to associate specific tasks with results and display a history of the tasks that operators have sent.
It's worth restating that this is a very basic listening post, but it covers the core elements that a command and control framework should offer. While you're starting out, it's helpful to keep things simple and work on more advanced techniques/features when you're confident in the basics. Some examples of what could be built in the future include:
  • Authentication/authorization controls
  • User management APIs
  • Listening post initial setup/install script
In the next chapter, we'll get out feet wet with some C++ code and deploy our implant to complete the next major component of our C2 project.

Further Reading & Next Steps

To learn more about building C2 listening posts, see the following resources:
Last modified 9mo ago