initial add for mythic 2.2 updates

This commit is contained in:
Cody Thomas
2021-03-21 21:19:18 -07:00
commit 3c6d42a9ec
22 changed files with 543 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
__pycache__/
View File
+4
View File
@@ -0,0 +1,4 @@
From itsafeaturemythic/python38_sanic_c2profile:0.0.3
RUN pip uninstall -y mythic_c2_container
RUN pip install mythic_c2_container==0.0.15
RUN apt-get update && apt-get install apt-file -y && apt-file update && apt-get install vim -y
View File
+17
View File
@@ -0,0 +1,17 @@
{
"instances": [
{
"ServerHeaders": {
"Server": "NetDNA-cache/2.2",
"Cache-Control": "max-age=0, no-cache",
"Pragma": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/javascript; charset=utf-8"
},
"port": 80,
"key_path": "",
"cert_path": "",
"debug": false
}
]
}
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Page Not Found!</title>
</head>
<body>
</body>
</html>
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
from sanic import Sanic
from sanic.response import html, redirect, text, raw
from sanic.exceptions import NotFound
import sys
import asyncio
import ssl
from pathlib import Path
import requests
import json
import os
config = {}
async def print_flush(message):
print(message)
sys.stdout.flush()
def server_error_handler(request, exception):
if request is None:
print("Invalid HTTP Method - Likely HTTPS trying to talk to HTTP")
sys.stdout.flush()
return html("Error: Failed to process request", status=500, headers={})
return html("Error: Requested URL {} not found".format(request.url), status=404, headers=config[request.app.name]['headers'])
async def agent_message(request, **kwargs):
global config
try:
if config[request.app.name]['debug']:
await print_flush("agent_message request from: {} with {} and {}".format(request.url, request.cookies, request.headers))
await print_flush(" and URI: {}".format(request.query_string))
if config[request.app.name]['debug']:
await print_flush("Forwarding along to: {}".format(config['mythic_address']))
if request.method == "POST":
# manipulate the request if needed
#await MythicCallbackRPC().add_event_message(message="got a POST message")
response = requests.post(config['mythic_address'], data=request.body, verify=False, cookies=request.cookies, headers={"Mythic": "http", **request.headers})
else:
# manipulate the request if needed
#await MythicCallbackRPC().add_event_message(message="got a GET message")
#msg = await MythicCallbackRPC().encrypt_bytes(with_uuid=True, data="my message".encode(), uuid="eaf10700-cb30-402d-b101-8e35d67cdb41")
#await MythicCallbackRPC().add_event_message(message=msg.response)
response = requests.get(config['mythic_address'] + "?{}".format(request.query_string), verify=False, data=request.body, cookies=request.cookies, headers={"Mythic": "http", **request.headers})
return raw(response.content, headers=config[request.app.name]['headers'], status=response.status_code)
except Exception as e:
if request is None:
await print_flush("Invalid HTTP Method - Likely HTTPS trying to talk to HTTP")
return server_error_handler(request, e)
if config[request.app.name]['debug']:
await print_flush("error in agent_message: {}".format(str(e)))
return server_error_handler(request, e)
if __name__ == "__main__":
sys.path.append("/Mythic/mythic")
from mythic_c2_container.C2ProfileBase import *
from mythic_c2_container.MythicCallbackRPC import *
config_file = open("config.json", 'rb')
main_config = json.loads(config_file.read().decode('utf-8'))
print("Opening config and starting instances...")
sys.stdout.flush()
# basic mapping of the general endpoints to the real endpoints
try:
config['mythic_address'] = os.environ['MYTHIC_ADDRESS']
except Exception as e:
print("failed to find MYTHIC_ADDRESS environment variable")
sys.stdout.flush()
sys.exit(1)
# now look at the specific instances to start
for inst in main_config['instances']:
config[str(inst['port'])] = {'debug': inst['debug'],
'headers': inst['ServerHeaders']}
if inst['debug']:
print("Debugging statements are enabled. This gives more context, but might be a performance hit")
else:
print("Debugging statements are disabled")
sys.stdout.flush()
# now to create an app instance to handle responses
app = Sanic(str(inst['port']))
app.config['REQUEST_MAX_SIZE'] = 1000000000
app.config['REQUEST_TIMEOUT'] = 600
app.config['RESPONSE_TIMEOUT'] = 600
app.add_route(agent_message, "/<uri:path>", methods=['GET','POST'])
app.add_route(agent_message, "/", methods=['GET','POST'])
app.error_handler.add(Exception, server_error_handler)
keyfile = Path(inst['key_path'])
certfile = Path(inst['cert_path'])
if keyfile.is_file() and certfile.is_file():
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(inst['cert_path'], keyfile=inst['key_path'])
if inst['debug']:
server = app.create_server(host="0.0.0.0", port=inst['port'], ssl=context, debug=False, return_asyncio_server=True, access_log=True)
else:
server = app.create_server(host="0.0.0.0", port=inst['port'], ssl=context, debug=False, return_asyncio_server=True, access_log=False)
if inst['debug']:
print("using SSL for port {}".format(inst['port']))
sys.stdout.flush()
else:
if inst['debug']:
print("not using SSL for port {}".format(inst['port']))
sys.stdout.flush()
if inst['debug']:
server = app.create_server(host="0.0.0.0", port=inst['port'], debug=False, return_asyncio_server=True, access_log=True)
else:
server = app.create_server(host="0.0.0.0", port=inst['port'], debug=False, return_asyncio_server=True, access_log=False)
task = asyncio.ensure_future(server)
try:
loop = asyncio.get_event_loop()
def callback(fut):
try:
fetch_count = fut.result()
except:
print("port already in use")
sys.stdout.flush()
sys.exit()
task.add_done_callback(callback)
loop.run_forever()
except:
sys.exit()
loop.stop()
View File
@@ -0,0 +1,27 @@
from mythic_c2_container.C2ProfileBase import *
import sys
# request is a dictionary: {"action": func_name, "message": "the input", "task_id": task id num}
# must return an RPCResponse() object and set .status to an instance of RPCStatus and response to str of message
async def test(request):
response = RPCResponse()
response.status = RPCStatus.Success
response.response = "hello"
#resp = await MythicCallbackRPC.MythicCallbackRPC().add_event_message(message="got a POST message")
return response
# The opsec function is called when a payload is created as a check to see if the parameters supplied are good
# The input for "request" is a dictionary of:
# {
# "action": "opsec",
# "parameters": {
# "param_name": "param_value",
# "param_name2: "param_value2",
# }
# }
# This function should return one of two things:
# For success: {"status": "success", "message": "your success message here" }
# For error: {"status": "error", "error": "your error message here" }
async def opsec(request):
return {"status": "success", "message": "No OPSEC Check"}
@@ -0,0 +1,132 @@
from mythic_c2_container.C2ProfileBase import *
class HTTP(C2Profile):
name = "http"
description = "Uses HTTP(S) connections with a simple query parameter or basic POST messages. For more configuration options use dynamicHTTP."
author = "@its_a_feature_"
is_p2p = False
is_server_routed = False
parameters = [
C2ProfileParameter(
name="callback_port",
description="Callback Port",
default_value="80",
verifier_regex="^[0-9]+$",
required=False,
),
C2ProfileParameter(
name="killdate",
description="Kill Date",
parameter_type=ParameterType.Date,
default_value=365,
required=False,
),
C2ProfileParameter(
name="encrypted_exchange_check",
description="Perform Key Exchange",
choices=["T", "F"],
required=False,
parameter_type=ParameterType.ChooseOne,
),
C2ProfileParameter(
name="callback_jitter",
description="Callback Jitter in percent",
default_value="23",
verifier_regex="^[0-9]+$",
required=False,
),
C2ProfileParameter(
name="headers",
description="HTTP Headers",
required=False,
parameter_type=ParameterType.Dictionary,
default_value=[
{
"name": "User-Agent",
"max": 1,
"default_value": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
"default_show": True,
},
{
"name": "Host",
"max": 1,
"default_value": "",
"default_show": False,
},
{
"name": "*",
"max": -1,
"default_value": "",
"default_show": False
}
]
),
C2ProfileParameter(
name="AESPSK",
description="Crypto type",
default_value="aes256_hmac",
parameter_type=ParameterType.ChooseOne,
choices=["aes256_hmac", "none"],
required=False,
crypto_type=True
),
C2ProfileParameter(
name="callback_host",
description="Callback Host",
default_value="https://domain.com",
verifier_regex="^(http|https):\/\/[a-zA-Z0-9]+",
),
C2ProfileParameter(
name="get_uri",
description="GET request URI",
default_value="index",
required=False,
),
C2ProfileParameter(
name="post_uri",
description="POST request URI",
default_value="data",
required=False,
),
C2ProfileParameter(
name="query_path_name",
description="Name of the query parameter",
default_value="q",
required=False,
verifier_regex="^[^\/]",
),
C2ProfileParameter(
name="proxy_host",
description="Proxy Host",
default_value="",
required=False,
verifier_regex="^$|^(http|https):\/\/[a-zA-Z0-9]+",
),
C2ProfileParameter(
name="proxy_port",
description="Proxy Port",
default_value="",
verifier_regex="^$|^[0-9]+$",
required=False,
),
C2ProfileParameter(
name="proxy_user",
description="Proxy Username",
default_value="",
required=False,
),
C2ProfileParameter(
name="proxy_pass",
description="Proxy Password",
default_value="",
required=False,
),
C2ProfileParameter(
name="callback_interval",
description="Callback Interval in seconds",
default_value="10",
verifier_regex="^[0-9]+$",
required=False,
),
]
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
cd /Mythic/mythic
export PYTHONPATH=/Mythic:/Mythic/mythic
python3.8 mythic_service.py
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env python3
from mythic_c2_container import mythic_service
mythic_service.start_service_and_heartbeat()
+8
View File
@@ -0,0 +1,8 @@
{
"username": "mythic_user",
"password": "mythic_password",
"virtual_host": "mythic_vhost",
"host": "127.0.0.1",
"name": "hostname",
"container_files_path": "/Mythic/"
}
View File
+81
View File
@@ -0,0 +1,81 @@
# Mythic External Agent
This repo defines the folder structure for an external Mythic agent that can be remotely "installed" into a Mythic instance. This process allows users to create their own Mythic agents and host them on their own GitHub repositories while also allowing an easy process to install agents.
## How to use this git project
This project is a template for what _your_ git-based repository should look like. Simply copy/fork this project and update it with your agent's information. Then, you can use the corresponding install script from within the Mythic repo to install this agent. The Mythic install script for 3rd party agents should work for any git-based repository (GitHub, GitLab, Bitbucket, etc).
## How to install an agent in this format within Mythic
When it's time for you to test out your install or for another user to install your agent, it's pretty simple. Within Mythic is a `install_agent_from_github.sh` script (https://github.com/its-a-feature/Mythic/blob/master/install_agent_from_github.sh). You can run this in one of two ways:
* `sudo ./install_agent_from_github.sh https://github.com/user/repo` to install the main branch
* `sudo ./install_agent_from_github.sh https://github.com/user/repo branchname` to install a specific branch of that repo
Now, you might be wondering _when_ should you or a user do this to properly add your agent to their Mythic instance. There's no wrong answer here, just depends on your preference. The three options are:
* Mythic is already up and going, then you can run the install script and just direct that agent's containers to start (i.e. `sudo ./start_payload_types.sh agentName` and if that agent has its own special C2 containers, you'll need to start them too via `sudo ./start_c2_profiles.sh c2profileName`).
* Mythic is already up and going, but you want to minimize your steps, you can just install the agent and run `sudo ./start_mythic.sh`. That script will first _stop_ all of your containers, then start everything back up again. This will also bring in the new agent you just installed.
* Mythic isn't running, you can install the script and just run `sudo ./start_mythic.sh`.
## Folder Structure
### Payload_Type
This folder should contain exactly what you have in your `Payload_Types` folder for Mythic. Specifically, it should contain one folder with the same name as your agent.
Example: https://github.com/its-a-feature/Mythic/tree/master/Payload_Types
### C2_Profiles
This folder should contain exactly what you have in your `C2_Profiles` folder if you added a new C2 Profile for your agent. Specifically, it should containe a folder with the name of the C2 Profile for every C2 Profile you added. If you have no additional C2 Profiles, leave this folder empty.
Example: https://github.com/its-a-feature/Mythic/tree/master/C2_Profiles
### documentation-c2
This folder contains all of the contents for the documentation-docker container that petains to your new C2 profiles (if any). If you have no extra documentation, then leave this folder empty.
Example: https://github.com/its-a-feature/Mythic/tree/master/documentation-docker/content/C2%20Profiles
### documentation-payload
This folder contains all of the contents for the documentation-docker container that pertains to your new payload type. If you have no documentation (you reall should though), then leave this folder empty.
Example: https://github.com/its-a-feature/Mythic/tree/master/documentation-docker/content/Agents
### documentation-wrapper
If you are creating any "wrapper" style payload types, the documentation for those go in a slightly different location with a slightly different format. Wrapper payload types don't have any C2 or commands associated with them; instead, they take in another agent and "wrap" it with a more generic mechanism (service executable, office macro, obfuscation, etc). While the wrapper payload type code goes into the same Payload_Type area, the documentation is broken out. Copy that documentation here similar to:
https://github.com/its-a-feature/Mythic/tree/master/documentation-docker/content/Wrappers
### agent_icons
This folder contains the svg icons that should be used for the agent. This svg icon is used on the Payload Types page, on the active callbacks page, and when viewing the graph/tree modes of your callbacks.
## Config.json
This file configures which components to ignore on the install process.
### exclude_payload_type
Setting this value to `true` means that after cloning down the remote repository, the installer program will _NOT_ copy the contents of the `Payload_Type` folder into the local `Payload_Types` folder. This is helpful for when an agent's Payload Type contents needs to be installed on a specific computer/VM and _not_ used as part of a Docker deployment.
### exclude_c2_profiles
Setting this value to `true` means that after cloning down the remote repository, the installer program wil _NOT_ copy the contents of the `C2_Profiles` folder into the local `C2_Profiles` folder. This is helpful for when an agent's C2 Profiles might need to run on a different Computer/VM and _not_ used as part of a Docker deployment locally to Mythic.
### exclude_documentation_payload
Setting this value to `true` will prevent Mythic from copying the contents into the documentation-docker's agent folders.
### exclude_documentation_c2
Setting this value to `true` will prevent Mythic from copying the contents into the documentation-docker's c2 folders.
### exclude_agent_icons
Setting this value to `true` will prevent Mythic from copying the contents into the mythic-docker/app/static folder with the other icons.
View File
+7
View File
@@ -0,0 +1,7 @@
{
"exclude_payload_type": false,
"exclude_c2_profiles": false,
"exclude_documentation_payload": false,
"exclude_documentation_c2": false,
"exclude_agent_icons": false
}
View File
+121
View File
@@ -0,0 +1,121 @@
+++
title = "HTTP"
chapter = false
weight = 5
+++
## Overview
This C2 profile consists of HTTP requests from an agent to the C2 profile container, where messages are then forwarded to Mythic's API. The C2 Profile container acts as a proxy between agents and the Mythic server itself.
The Profile is not proxy aware by default - this is a component left as an exercise for the individual agents.
### C2 Workflow
{{<mermaid>}}
sequenceDiagram
participant M as Mythic
participant H as HTTP Container
participant A as Agent
A ->>+ H: GET/POST for tasking
H ->>+ M: forward request to Mythic
M -->>- H: reply with tasking
H -->>- A: reply with tasking
{{< /mermaid >}}
Legend:
- Solid line is a new connection
- Dotted line is a message within that connection
## Configuration Options
The profile reads a `config.json` file for a set of instances of `Sanic` webservers to stand up (`80` by default) and redirects the content.
```JSON
{
"instances": [
{
"ServerHeaders": {
"Server": "NetDNA-cache/2.2",
"Cache-Control": "max-age=0, no-cache",
"Pragma": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/javascript; charset=utf-8"
},
"port": 80,
"key_path": "",
"cert_path": "",
"debug": true
}
]
}
```
You can specify the headers that the profile will set on Server responses. If there's an error, the server will return a `404` message based on the `fake.html` file contents in `C2_Profiles/HTTP/c2_code`.
If you want to use SSL within this container specifically, then you can put your key and cert in the `C2_Profiles/HTTP/c2_code` folder and update the `key_path` and `cert_path` variables to have the `names` of those files.
You should get a notification when the server starts with information about the configuration:
```
Messages will disappear when this dialog is closed.
Received Message:
Started with pid: 16...
Output: Opening config and starting instances...
Debugging statements are enabled. This gives more context, but might be a performance hit
not using SSL for port 80
[2020-07-29 21:48:26 +0000] [16] [INFO] Goin' Fast @ http://0.0.0.0:80
```
A note about debugging:
- With `debug` set to `true`, you'll be able to `view stdout/stderr` from within the UI for the container, but it's not recommended to always have this on (especially if you start using something like SOCKS). There can be a lot of traffic and a lot of debugging information captured here which can be both a performance and memory bottleneck depending on your environment and operational timelines.
- It's recommended to have it on initially to help troubleshoot payload connectivity and configuration issues, but then to set it to `false` for actual operations
### Profile Options
#### Base64 of a 32-byte AES Key
Base64 value of the AES pre-shared key to use for communication with the agent. This will be auto-populated with a random key per payload, but you can also replace this with the base64 of any 32 bytes you want. If you don't want to use encryption here, blank out this value.
#### User Agent
This is the user-agent string the agent will use when making HTTP requests.
#### Callback Host
The URL for the redirector or Mythic server. This must include the protocol to use (e.g. `http://` or `https://`)
#### Callback Interval
A number to indicate how many seconds the agent should wait in between tasking requests.
#### Callback Jitter
Percentage of jitter effect for callback interval.
#### Callback Port
Number to specify the port number to callback to. This is split out since you don't _have_ to connect to the normal port (i.e. you could connect to http on port 8080).
#### Host header
Value for the `Host` header in web requests. This is used with _Domain Fronting_.
#### Kill Date
Date for the agent to automatically exit, typically the after an assessment is finished.
#### Perform Key Exchange
T or F for if you want to perform a key exchange with the Mythic Server. When this is true, the agent uses the key specified by the base64 32Byte key to send an initial message to the Mythic server with a newly generated RSA public key. If this is set to `F`, then the agent tries to just use the base64 of the key as a static AES key for encryption. If that key is also blanked out, then the requests will all be in plaintext.
#### Get Request URI
The URI to use when performing a get request such as `index`. A leading `/` is used automatically to separate this value from the end of the callback host value.
#### Name of the endpoint before the query string
This is the query parameter name used in Get requests. For example, if this is `q` and the get request URI is `index`, then a request might look like `http://domain.com/index?q=someback64here`.
#### Proxy Host
If you need to manually specify a proxy endpoint, do that here. This follows the same format as the callback host.
#### Proxy Password
If you need to authenticate to the proxy endpoint, specify the password here.
#### Proxy Username
If you need to authenticate to the proxy endpoint, specify the username here.
#### Proxy Port
If you need to manually specify a proxy endpoint, this is where you specify the associated port number.
## OPSEC
This profile doesn't do any randomization of network components outside of allowing operators to specify internals/jitter. Every GET request for tasking will be the same. This is important to take into consideration for profiling/beaconing analytics.
## Development
All of the code for the server is Python3 using `Sanic` and located in `C2_Profiles/HTTP/c2_code/server`. It loops through the `instances` in the `config.json` file and stands up those individual web servers.
View File
View File