Skip to content

Commit

Permalink
initial SO-CON 2024 release
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Mar 11, 2024
1 parent 0c4f6d4 commit b3b94d2
Show file tree
Hide file tree
Showing 33 changed files with 1,575 additions and 0 deletions.
139 changes: 139 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Pulled from Thanatos (https://github.com/MythicAgents/thanatos/blob/rewrite/.github/workflows/image.yml) - MEhrn00

# Name for the Github actions workflow
name: Build and push container images

on:
# Only run workflow when there is a new release published in Github
#release:
# types: [published]
push:
branches:
- 'master'
- 'main'
tags:
- "v*.*.*"

# Variables holding configuration settings
env:
# Container registry the built container image will be pushed to
REGISTRY: ghcr.io

# Set the container image name to the Github repository name. (MythicAgents/apfell)
AGENT_IMAGE_NAME: ${{ github.repository }}

# Description label for the package in Github
IMAGE_DESCRIPTION: ${{ github.repository }} container for use with Mythic

# Source URL for the package in Github. This links the Github repository packages list
# to this container image
IMAGE_SOURCE: ${{ github.server_url }}/${{ github.repository }}

# License for the container image
IMAGE_LICENSE: BSD-3-Clause

# Set the container image version to the Github release tag
VERSION: ${{ github.ref_name }}
#VERSION: ${{ github.event.head_commit.message }}

RELEASE_BRANCH: main

jobs:
# Builds the base container image and pushes it to the container registry
agent_build:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- name: Checkout the repository
uses: actions/checkout@v4 # ref: https://github.com/marketplace/actions/checkout
- name: Log in to the container registry
uses: docker/login-action@v3 # ref: https://github.com/marketplace/actions/docker-login
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
platforms: 'arm64,arm'
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
# the following are unique to this job
- name: Lowercase the server container image name
run: echo "AGENT_IMAGE_NAME=${AGENT_IMAGE_NAME,,}" >> ${GITHUB_ENV}
- name: Build and push the server container image
uses: docker/build-push-action@v5 # ref: https://github.com/marketplace/actions/build-and-push-docker-images
with:
context: Payload_Type/nemesis
file: Payload_Type/nemesis/.docker/Dockerfile
tags: |
${{ env.REGISTRY }}/${{ env.AGENT_IMAGE_NAME }}:${{ env.VERSION }}
${{ env.REGISTRY }}/${{ env.AGENT_IMAGE_NAME }}:latest
push: ${{ github.ref_type == 'tag' }}
# These container metadata labels allow configuring the package in Github
# packages. The source will link the package to this Github repository
labels: |
org.opencontainers.image.source=${{ env.IMAGE_SOURCE }}
org.opencontainers.image.description=${{ env.IMAGE_DESCRIPTION }}
org.opencontainers.image.licenses=${{ env.IMAGE_LICENSE }}
platforms: linux/amd64,linux/arm64

update_files:
runs-on: ubuntu-latest
needs:
- agent_build
permissions:
contents: write
packages: write

steps:
# Pull in the repository code
- name: Checkout the repository
uses: actions/checkout@v4 # ref: https://github.com/marketplace/actions/checkout

# update names to lowercase
- name: Lowercase the container image name
run: echo "AGENT_IMAGE_NAME=${AGENT_IMAGE_NAME,,}" >> ${GITHUB_ENV}

# The Dockerfile which Mythic uses to pull in the base container image needs to be
# updated to reference the newly built container image
- name: Fix the server Dockerfile reference to reference the new release tag
working-directory: Payload_Type/nemesis
run: |
sed -i "s|^FROM ghcr\.io.*$|FROM ${REGISTRY}/${AGENT_IMAGE_NAME}:${VERSION}|" Dockerfile
- name: Update package.json version
uses: jossef/[email protected]
with:
file: config.json
field: remote_images.nemesis
value: ${{env.REGISTRY}}/${{env.AGENT_IMAGE_NAME}}:${{env.VERSION}}

# Push the changes to the Dockerfile
- name: Push the updated base Dockerfile image reference changes
if: ${{ github.ref_type == 'tag' }}
uses: EndBug/add-and-commit@v9 # ref: https://github.com/marketplace/actions/add-commit
with:
# Only add the Dockerfile changes. Nothing else should have been modified
add: "['Payload_Type/nemesis/Dockerfile', 'config.json']"
# Use the Github actions bot for the commit author
default_author: github_actions
committer_email: github-actions[bot]@users.noreply.github.com

# Set the commit message
message: "Bump Dockerfile tag to match release '${{ env.VERSION }}'"

# Overwrite the current git tag with the new changes
tag: '${{ env.VERSION }} --force'

# Push the new changes with the tag overwriting the current one
tag_push: '--force'

# Push the commits to the branch marked as the release branch
push: origin HEAD:${{ env.RELEASE_BRANCH }} --set-upstream

# Have the workflow fail in case there are pathspec issues
pathspec_error_handling: exitImmediately
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
__pycache__/
*.py[cod]
*$py.class
.idea/
.DS_Store
rabbitmq_config.json

# C extensions
*.so
Expand Down
Empty file added C2_Profiles/.keep
Empty file.
28 changes: 28 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

Copyright (c) 2024, its-a-feature
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of nemesis, mythic, nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Empty file added Payload_Type/__init__.py
Empty file.
22 changes: 22 additions & 0 deletions Payload_Type/nemesis/.docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM python:3.11-slim-bookworm as builder

COPY [".docker/requirements.txt", "requirements.txt"]
RUN apt-get -y update && \
apt-get -y upgrade && \
apt-get install --no-install-recommends \
software-properties-common apt-utils make build-essential libssl-dev zlib1g-dev libbz2-dev \
xz-utils tk-dev libffi-dev liblzma-dev libsqlite3-dev protobuf-compiler \
binutils-aarch64-linux-gnu libc-dev-arm64-cross -y
RUN python3 -m pip wheel --wheel-dir /wheels -r requirements.txt

FROM python:3.11-slim-bookworm

COPY --from=builder /wheels /wheels

RUN pip install --no-cache /wheels/*

WORKDIR /Mythic/

COPY [".", "."]

CMD ["python3", "main.py"]
7 changes: 7 additions & 0 deletions Payload_Type/nemesis/.docker/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
aio-pika==9.0.4
dynaconf==3.1.11
ujson==5.7.0
aiohttp==3.8.3
psutil==5.9.4
mythic-container==0.4.13
requests
22 changes: 22 additions & 0 deletions Payload_Type/nemesis/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM python:3.11-slim-bookworm as builder

COPY [".docker/requirements.txt", "requirements.txt"]
RUN apt-get -y update && \
apt-get -y upgrade && \
apt-get install --no-install-recommends \
software-properties-common apt-utils make build-essential libssl-dev zlib1g-dev libbz2-dev \
xz-utils tk-dev libffi-dev liblzma-dev libsqlite3-dev protobuf-compiler \
binutils-aarch64-linux-gnu libc-dev-arm64-cross -y
RUN python3 -m pip wheel --wheel-dir /wheels -r requirements.txt

FROM python:3.11-slim-bookworm

COPY --from=builder /wheels /wheels

RUN pip install --no-cache /wheels/*

WORKDIR /Mythic/

COPY [".", "."]

CMD ["python3", "main.py"]
6 changes: 6 additions & 0 deletions Payload_Type/nemesis/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import mythic_container
import asyncio
# import the nemesis agent
import nemesis

mythic_container.mythic_service.start_and_run_forever()
142 changes: 142 additions & 0 deletions Payload_Type/nemesis/nemesis/NemesisRequests/NemesisAPI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from mythic_container.MythicCommandBase import *
from nemesis.NemesisRequests.NemesisAPIClasses import *
from mythic_container.MythicRPC import *
from gql import gql
from datetime import datetime, timedelta

NEMESIS_USERNAME = "NEMESIS_USERNAME"
NEMESIS_PASSWORD = "NEMESIS_PASSWORD"


def check_valid_values(username, password, url) -> bool:
if username == "" or username is None:
logger.error("missing username")
return False
if password == "" or password is None:
logger.error("missing password")
return False
if url == "" or url is None:
logger.error("missing url")
return False
return True


def convert_timestamp(timestamp, days_to_add=0):
"""
Strips off the microseconds from a timestamp and reformats to our unified format.
**Parameters**
``timestamp``
The timestamp string to reformat.
**Returns**
A reformatted timestamp string.
"""

dt = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ")

if days_to_add != 0:
dt = dt + timedelta(days=days_to_add)

return dt.strftime("%Y-%m-%dT%H:%M:%S.000Z")


async def query_graphql(taskData: PTTaskMessageAllData, query: gql, uri: str = '/hasura/v1/graphql',
variable_values: dict = None) -> (int, dict):
username = None
password = None
url = None
for buildParam in taskData.BuildParameters:
if buildParam.Name == "URL":
url = buildParam.Value
if NEMESIS_USERNAME in taskData.Secrets:
username = taskData.Secrets[NEMESIS_USERNAME]
if NEMESIS_PASSWORD in taskData.Secrets:
password = taskData.Secrets[NEMESIS_PASSWORD]
if not check_valid_values(username, password, url):
return 500, f"Missing {NEMESIS_USERNAME} or {NEMESIS_PASSWORD} in User settings or missing Nemesis URL"
try:
credentials = Credentials(username=username, password=password)
client = NemesisClient(url=url.rstrip("/") + uri, credentials=credentials)
response = await client.graphql_query(query=query, variable_values=variable_values)
logger.info(f"Nemesis Query: {uri}")
if response is not None:
return 200, response
else:
return 500, client.last_error
except Exception as e:
logger.exception(f"[-] Failed to query Nemesis: \n{e}\n")
raise Exception(f"[-] Failed to query Nemesis: \n{e}\n")


async def post_data_api(taskData: PTTaskMessageAllData, data: dict) -> (int, dict):
username = None
password = None
url = None
for buildParam in taskData.BuildParameters:
if buildParam.Name == "URL":
url = buildParam.Value
if NEMESIS_USERNAME in taskData.Secrets:
username = taskData.Secrets[NEMESIS_USERNAME]
if NEMESIS_PASSWORD in taskData.Secrets:
password = taskData.Secrets[NEMESIS_PASSWORD]
if not check_valid_values(username, password, url):
return 500, f"Missing {NEMESIS_USERNAME} or {NEMESIS_PASSWORD} in User settings or missing Nemesis URL"
try:
credentials = Credentials(username=username, password=password)
client = NemesisClient(url=url.rstrip("/"), credentials=credentials, graphql=False)
response = await client.nemesis_post_data(data=data)
if response is not None:
return 200, response
else:
return 500, client.last_error
except Exception as e:
logger.exception(f"[-] Failed to post data to Nemesis: \n{e}\n")
raise Exception(f"[-] Failed to post data to Nemesis: \n{e}\n")


async def post_file_api(taskData: PTTaskMessageAllData, file_bytes: bytes) -> (int, str):
username = None
password = None
url = None
for buildParam in taskData.BuildParameters:
if buildParam.Name == "URL":
url = buildParam.Value
if NEMESIS_USERNAME in taskData.Secrets:
username = taskData.Secrets[NEMESIS_USERNAME]
if NEMESIS_PASSWORD in taskData.Secrets:
password = taskData.Secrets[NEMESIS_PASSWORD]
if not check_valid_values(username, password, url):
return 500, f"Missing {NEMESIS_USERNAME} or {NEMESIS_PASSWORD} in User settings or missing Nemesis URL"
try:
credentials = Credentials(username=username, password=password)
client = NemesisClient(url=url.rstrip("/"), credentials=credentials, graphql=False)
response = await client.nemesis_post_file(file_bytes=file_bytes)
if response is not None:
return 200, response
else:
return 500, client.last_error
except Exception as e:
logger.exception(f"[-] Failed to post file to Nemesis: \n{e}\n")
raise Exception(f"[-] Failed to post file to Nemesis: \n{e}\n")


async def process_standard_response(response_code: int, response_data: any,
taskData: PTTaskMessageAllData, response: PTTaskCreateTaskingMessageResponse) -> \
PTTaskCreateTaskingMessageResponse:
if response_code == 200:
await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=taskData.Task.ID,
Response=json.dumps(response_data).encode("UTF8"),
))
response.Success = True
else:
await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=taskData.Task.ID,
Response=f"{response_data}".encode("UTF8"),
))
response.TaskStatus = "Error: Nemesis Query Error"
response.Success = False
return response
Loading

0 comments on commit b3b94d2

Please sign in to comment.