Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 60 additions & 34 deletions C2_Profiles/websocket/websocket/mythic/websocket.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from mythic_container.C2ProfileBase import *
import pathlib

version = "0.1.1"
version = "0.1.2"


class Websocket(C2Profile):
name = "websocket"
Expand All @@ -12,78 +13,103 @@ class Websocket(C2Profile):
server_folder_path = pathlib.Path(".") / "websocket" / "c2_code"
parameters = [
C2ProfileParameter(
name="callback_host",
description="Callback Host",
default_value="ws://127.0.0.1",
verifier_regex="^(ws|wss)://[a-zA-Z0-9]+",
name="callback_hosts",
description="Callback hosts (including optional port)",
parameter_type=ParameterType.Array,
default_value=["ws://127.0.0.1", "ws://127.0.1.1:8081"],
verifier_regex=f"^(ws|wss)://[a-zA-Z0-9.]+(:[0-9]{2, 5})?",
required=True,
),
C2ProfileParameter(
name="domain_rotation_method",
description="Domain rotation method",
parameter_type=ParameterType.ChooseOne,
default_value="fail-over",
choices=["fail-over", "random"],
required=False,
),
C2ProfileParameter(
name="domain_rotation_delay",
description="Domain rotation delay",
parameter_type=ParameterType.Number,
default_value=10,
required=False,
),
C2ProfileParameter(
name="domain_rotation_failure_threshold",
description="Domain rotation failure threshold",
parameter_type=ParameterType.Number,
default_value=-1,
required=False,
),
C2ProfileParameter(
name="USER_AGENT",
description="User Agent",
default_value="Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
name="headers",
description="Custom headers",
parameter_type=ParameterType.Dictionary,
dictionary_choices=[
DictionaryChoice(name="user-agent",
default_value="Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
default_show=True,
),
DictionaryChoice(name="host",
default_value="",
default_show=False,
),
],
required=False,
),
C2ProfileParameter(
name="AESPSK",
description="Crypto type",
default_value="aes256_hmac",
parameter_type=ParameterType.ChooseOne,
default_value="aes256_hmac",
choices=["aes256_hmac", "none"],
required=False,
crypto_type=True
crypto_type=True,
),
C2ProfileParameter(
name="callback_interval",
description="Callback Interval in seconds",
default_value="10",
verifier_regex="^[0-9]+$",
parameter_type=ParameterType.Number,
default_value=10,
required=False,
),
C2ProfileParameter(
name="encrypted_exchange_check",
description="Perform Key Exchange",
choices=["T", "F"],
parameter_type=ParameterType.ChooseOne,
required=False,
),
C2ProfileParameter(
name="domain_front",
description="Host header value for domain fronting",
default_value="",
parameter_type=ParameterType.Boolean,
default_value=True,
required=False,
),
C2ProfileParameter(
name="ENDPOINT_REPLACE",
name="endpoint",
description="Websockets Endpoint",
default_value="socket",
parameter_type=ParameterType.String,
default_value="/socket",
verifier_regex=f"^/[a-zA-Z0-9]",
required=False,
),
C2ProfileParameter(
name="callback_jitter",
description="Callback Jitter in percent",
default_value="37",
verifier_regex="^[0-9]+$",
required=False,
),
C2ProfileParameter(
name="callback_port",
description="Callback Port",
default_value="8081",
verifier_regex="^[0-9]+$",
parameter_type=ParameterType.Number,
default_value=37,
required=False,
),
C2ProfileParameter(
name="tasking_type",
description="'Poll' for tasking at an interval or have Mythic 'Push' new tasking as it arrives",
default_value="Poll",
choices=["Poll", "Push"],
parameter_type=ParameterType.ChooseOne,
default_value="Push",
choices=["Poll", "Push"],
required=False
),
C2ProfileParameter(
name="killdate",
description="Killdate for when the C2 Profile should stop working and exit the agent",
default_value=365,
parameter_type=ParameterType.Date,
default_value=365,
required=False
),
]

Expand Down
71 changes: 52 additions & 19 deletions documentation-c2/websocket/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ weight = 5
+++

## Overview
The websockets protocol enables two-way communication between a client and remote host over a single connection. To establish a websockets connection, the client and the server complete a simple handshake followed by chunked messages (framing), layered over TCP. For more information, please review the RFC located here: https://tools.ietf.org/html/rfc6455. The 'Config.json' file is what configures the 'server' file within the docker container. Be sure to update this to match the port your server is listening on as well as updating it to match the configuration of your agent. The source code for the websockets server is based on @xorrior's code here: https://github.com/xorrior/poseidonC2.

The websockets protocol enables two-way communication between a client and remote host over a single connection. To establish a websockets connection, the client and the server complete a simple handshake followed by chunked messages (framing), layered over TCP. For more information, please review the [RFC](https://tools.ietf.org/html/rfc6455). The 'config.json' file is what configures the 'server' file within the docker container. Be sure to update this to match the port your server is listening on as well as updating it to match the configuration of your agent. The source code for the websockets server is based on [@xorrior's code](https://github.com/xorrior/poseidonC2).

The code has been slightly modified and included locally within the `C2_Profiles/websocket/c2_code/src` folder. There's also directions in there for if you want to modify and re-compile locally.

### Websockets C2 Workflow

{{<mermaid>}}
sequenceDiagram
participant M as Mythic
Expand All @@ -33,7 +35,7 @@ sequenceDiagram
6. Websocket sends new tasks to the agent

## Configuration Options
The profile reads a `config.json` file and starts a Golang websocket client to handle connections.
The profile reads a `config.json` file and starts a Golang websocket client to handle connections.

```JSON
{
Expand All @@ -46,54 +48,85 @@ The profile reads a `config.json` file and starts a Golang websocket client to h
"debug": true
}
```

- bindaddress -> The bind IP and Port for the websocket server. This port needs to match what you use as the `Callback Port` when creating an agent.
- usessl -> Listen on the specified port and enable SSL. If "key.pem" and "cert.pem" don't exist, the server will generate a self-signed certificate and key file.
- defaultpage -> This value points to an html file that is served to clients that connect to any other URI except the one defined for the `websocketuri` key.
- sslkey -> path to the ssl private key
- sslcert -> path to the ssl certificate
- websocketuri -> Websocket endpoint used for client connections (e.g. wss://myserver/websocketuri)


### 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 static key for the operation, 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.

#### Callback Host
This is the address that the agent reaches out to. Since this is a websocket C2, the address must be a websocket address (i.e. `ws://127.0.0.1` or `wss://127.0.0.1`). For websockets, clients will use http/s for the initial upgrade request and then switch to wss or ws for websockets traffic.
#### Callback Hosts

This is the addresses that the agent reaches out to. Since this is a websocket C2, the addresses must be websocket addresses (i.e. `ws://127.0.0.1` or `wss://127.0.0.1`). For websockets, clients will use http/s for the initial upgrade request and then switch to wss or ws for websockets traffic.

Multiple callback hosts can be specified, and a port optionally included. If connecting to a `ws` address, the default is port `80`, if connecting to a `wss` address, the default is `443`, but any custom one can also be specified (e.g. `ws://127.0.0.1:8081`).

#### Domain Rotation

If multiple callback hosts are specified, then the payload will cycle through a series of multiple hosts in the event that some hosts are unavailable.

##### Method

The method for selecting the new callback host when the outgoing connection fails, this currently supports `fail-over` and `random`.

- `fail-over` moves through each host in sequence.
- `random` selects a random host from the list of available callback hosts.

##### Delay

A delay in seconds for the payload to wait before trying the next callback host This can be useful when bandwidth is limited, particularly when a relatively low failure threshold is set.

##### Failure Threshold

After a defined number of failures, a callback host can be removed from the list of available callbacks to prevent the payload from continually trying to query an unreachable host.

#### Headers

##### User Agent

#### User Agent
This is the User-Agent header set when reaching out to the Callback Host. The default value is `Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko`.

#### Callback Interval in seconds
###### Host

This is the host header value if you want to perform domain fronting through your Callback Host. This is simply the value, not the `Host: ` part as well.

#### Callback Interval

This is the interval in seconds in which the agent reaches out to the Callback Host. The default value is 10 seconds. This affects two components:

1. How frequently the agent reaches out for tasking _within_ an already established websocket connection
2. How frequently the agent will try to re-establish the websocket connection.

#### Perform Key Exchange
This is a `T` or `F` flag for if the agent should perform an encrypted key exchange with the server when checking in for the first time. This provides perfect forward secrecy for communications. If this is set to `F`, then the agent will use the AES key for a static pre-shared key set of encrypted communications.
#### Callback Jitter

#### Host header value for domain fronting
This is the host header value if you want to perform domain fronting through your Callback Host. This is simply the value, not the `Host: ` part as well.
This configures a `+/-` randomized percentage of the callback interval so that check-ins aren't at the exact same interval each time. This must be between 0 to 100.

#### Callback Jitter in percent
This configures a +- randomized percentage of the callback interval so that checkins aren't at the exact same interval each time. This must be between 0 to 100.
#### Perform Key Exchange

#### Callback Port
This is the port to use when connecting to the Callback Host. If connecting to a `ws` address, the default is port 80, if connecting to a `wss` address, the default is 443, but any custom one can also be specified.
This is a boolean flag for if the agent should perform an encrypted key exchange with the server when checking in for the first time. This provides perfect forward secrecy for communications. If this is set to `false`, then the agent will use the AES key for a static pre-shared key set of encrypted communications.

## OPSEC

The Agent uses HTTP/S to perform the initial upgrade request before using the websockets protocol.

## Push vs Poll
When creating an agent to utilize this C2 profile, you can decide if you want to support Poll (agent periodically issues get_tasking requests through the websocket connection) or Push (agent sends a checkin message when connecting, then waits for messages to get pushed to it).
## Tasking Type

When creating an agent to utilize this C2 profile, you can decide if you want to support Poll (agent periodically issues `get_tasking` requests through the websocket connection) or Push (agent sends a `checkin` message when connecting, then waits for messages to get pushed to it).

Push vs Poll is determined by a header, `Accept-Type`, when making the initial connection:
```Go

```go
taskingType, ok := r.Header["Accept-Type"]
if !ok || (len(taskingType) > 0 && taskingType[0] == "Poll") {
go s.managePollClient(conn)
} else {
go s.managePushClient(conn)
}
```
```