Creating a New Connector
There are several levels of integrating a C2's output into Nemesis. The easiest start and the best bang for the buck is the processing of file downloads.
Regardless of the connector actions, it will need to somehow save the following configuration values:
Description | Example |
---|---|
Address of the Nemesis endpoint | https:// |
Nemesis basic-auth connection credentials | nemesis:Qwerty12345 |
Project name | PROJECT-X |
Expiration days (or date) | 100 (or 01/01/2024) |
Download Processing
File processing is the one flow that differs from other structured data ingestion. First, the file bytes need to be uploaded to Nemesis, and second, a metadata message needs to be posted to kick off processing.
Step 1 - File Upload
For a file to be processed, the raw file bytes first need to be posted to the correct API route for storage in the data lake. This is accomplished by POSTing the file bytes to the https://<NEMESIS_URL>/api/file
which returns a simple JSON response with an object_id
field containing a UUID that references the uploaded file. For example, to do this in Python (as shown in mythic-connector), you would run something like this:
basic = HTTPBasicAuth(NEMESIS_USERNAME, NEMESIS_PASSWORD)
r = requests.request("POST", f"{NEMESIS_URL}/file", auth=basic, data=file_bytes, headers={"Content-Type": "application/octet-stream"})
json_result = r.json()
nemesis_file_id = json_result["object_id"]
curl
command:
curl -H "Content-Type: application/octet-stream" -v --user 'nemesis:Qwerty12345' -k --data-binary @/etc/issue https://192.168.230.42:8080/api/file
The nemesis_file_id
is used in the file_data
message in Step 2 below. This UUID is the unique reference for the file in Nemesis.
Step 2 - File Data Message
After the file is uploaded to Nemesis, a file_data ODR message needs to be posted with file metadata information. The example from the mythic-connector is:
metadata = {}
metadata["agent_id"] = file_meta["task"]["callback"]["agent_callback_id"]
metadata["agent_type"] = "mythic"
metadata["automated"] = True
metadata["data_type"] = "file_data"
# add EXPIRATION_DAYS to the timestamp
metadata["expiration"] = convert_timestamp(file_meta["timestamp"], EXPIRATION_DAYS)
metadata["project"] = file_meta["task"]["callback"]["operation"]["name"]
metadata["timestamp"] = convert_timestamp(file_meta["timestamp"])
file_data = {}
file_data["path"] = base64.b64decode(file_meta["full_remote_path_text"]).decode("utf-8").replace("\\", "/")
file_data["size"] = file_size
file_data["object_id"] = nemesis_file_id
# post to the Nemesis data API (`data`` needs to be an array of dictionaries!)
data = {"metadata": metadata, "data": [file_data]}
basic = HTTPBasicAuth(NEMESIS_USERNAME, NEMESIS_PASSWORD)
r = requests.request("POST", f"{NEMESIS_URL}/data", auth=basic, data=data, headers={"Content-Type": "application/octet-stream"})
Note that timestamps need to be in ISO 8601 UTC form, e.g., 2023-08-01T22:51:35
Other Structured Data
For other types of structured data, only a single message needs to be posted to the http://<NEMESIS_URL>/api/data
API route, e.g. Step 2 in the downloading processing example. The metadata["data_type"]
field should be one of the types defined in the ODR. The appropriate ODR document will also define the fields and structure needed for the datatype.
Note that the "data" section of the message is an array of dictionaries, i.e., multiple instances of a datatype can be posted in a single message. For example, multiple process messages can exist in the single post.
As an example, see the handle_process()
function in the mythic-connector.
Example of many of the structured datatypes can be found in the ./sample_files/structured/
folder. Example of using these to submit process data:
curl -H "Content-Type: application/octet-stream" -k -v --user 'nemesis:Qwerty12345' --data-binary @./sample_files/structured/process_data.json https://192.168.230.42:8080/api/data