Skip to content

Commit

Permalink
initial code push
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Feb 6, 2024
1 parent cc4d698 commit e3d084d
Show file tree
Hide file tree
Showing 51 changed files with 2,646 additions and 1 deletion.
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/arachne
file: Payload_Type/arachne/.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/arachne
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.arachne
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: "['C2_Profiles/basic_webhook/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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea/
.DS_Store
__pycache__/
rabbitmq_config.json
Empty file added C2_Profiles/.keep
Empty file.
29 changes: 29 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BSD 3-Clause License

Copyright (c) 2022, Dwight Hohnstein
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 the copyright holder 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.
22 changes: 22 additions & 0 deletions Payload_Type/arachne/.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"]
8 changes: 8 additions & 0 deletions Payload_Type/arachne/.docker/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
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.8
requests
bs4
22 changes: 22 additions & 0 deletions Payload_Type/arachne/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"]
Empty file.
100 changes: 100 additions & 0 deletions Payload_Type/arachne/arachne/WebshellRPC/WebshellRPC.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import aiohttp
import asyncio
import base64
from bs4 import BeautifulSoup
from mythic_container.MythicCommandBase import *
from mythic_container.MythicGoRPC.send_mythic_rpc_callback_edge_search import *


async def GetRequest(uuid: str, message: bytes, taskData: PTTaskMessageAllData) -> bytes:
edges_query = await SendMythicRPCCallbackEdgeSearch(MythicRPCCallbackEdgeSearchMessage(
AgentCallbackUUID=taskData.Callback.AgentCallbackID,
SearchActiveEdgesOnly=True
))
if not edges_query.Success:
logger.debug("Failed to query edges: %s", edges_query.Error)
elif len(edges_query.Results) > 0:
logger.debug(edges_query.Results)
return b''
param_name = None
cookie_name = None
user_agent = None
target = None
for name, value in taskData.C2Profiles[0].Parameters.items():
if name == "query_param":
param_name = value
elif name == "cookie_name":
cookie_name = value
elif name == "user_agent":
user_agent = value
elif name == "url":
target = value
encoded_uuid = base64.b64encode(uuid.encode('UTF8'))
final_message = taskData.Callback.AgentCallbackID.encode() + message
final_message = base64.b64encode(final_message)
try:
async with aiohttp.ClientSession(headers={'User-Agent': user_agent},
cookies={cookie_name: encoded_uuid.decode('UTF8')}) as session:
async with session.get(target, ssl=False, params={param_name: final_message.decode('UTF8')}, ) as resp:
responseData = await resp.text()
#logger.debug(f"WebShell response data: {responseData}")
if resp.status == 200:
if len(responseData) > 0:
response = BeautifulSoup(responseData, 'html.parser')
base64_data = response.find("span", id="task_response")
if base64_data:
return base64_data.text.encode()
else:
raise Exception(f"Failed to find task_response in agent response:\n{response}\n{responseData}")
raise Exception(f"No response data back from agent\n")
else:
logger.error(f"[-] Failed to send WebShell message: {resp}\n")
raise Exception(f"[-] Failed to send WebShell message: {resp}\n{responseData}")
except Exception as e:
logger.exception(f"[-] Failed to connect for WebShell: {e}\n")
raise Exception(f"[-] Failed to connect for WebShell: {e}\n")


async def PostRequest(uuid: str, message: bytes, taskData: PTTaskMessageAllData):
edges_query = await SendMythicRPCCallbackEdgeSearch(MythicRPCCallbackEdgeSearchMessage(
AgentCallbackUUID=taskData.Callback.AgentCallbackID,
SearchActiveEdgesOnly=True
))
if not edges_query.Success:
logger.debug("Failed to query edges: %s", edges_query.Error)
elif len(edges_query.Results) > 0:
return b''
cookie_name = None
user_agent = None
target = None
for name, value in taskData.C2Profiles[0].Parameters.items():
if name == "cookie_name":
cookie_name = value
elif name == "user_agent":
user_agent = value
elif name == "url":
target = value
encoded_uuid = base64.b64encode(uuid.encode('UTF8'))
final_message = taskData.Callback.AgentCallbackID.encode() + message
final_message = base64.b64encode(final_message)
try:
async with aiohttp.ClientSession(headers={'User-Agent': user_agent},
cookies={cookie_name: encoded_uuid.decode('UTF8')}) as session:
async with session.post(target, ssl=False, data=final_message, ) as resp:
responseData = await resp.text()
#logger.debug(f"WebShell response data: {responseData}")
if resp.status == 200:
if len(responseData) > 0:
response = BeautifulSoup(responseData, 'html.parser')
base64_data = response.find("span", id="task_response")
if base64_data:
return base64_data.text.encode()
else:
raise Exception(f"Failed to find task_response in agent response:\n{response}\n{responseData}")
raise Exception(f"No response data back from agent\n")
else:
logger.error(f"[-] Failed to send WebShell message: {resp}\n")
raise Exception(f"[-] Failed to send WebShell message: {resp}\n{responseData}")
except Exception as e:
logger.exception(f"[-] Failed to connect for WebShell: {e}\n")
raise Exception(f"[-] Failed to connect for WebShell: {e}\n")
Empty file.
20 changes: 20 additions & 0 deletions Payload_Type/arachne/arachne/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import glob
import os.path
from pathlib import Path
from importlib import import_module, invalidate_caches
import sys
# Get file paths of all modules.

currentPath = Path(__file__)
searchPath = currentPath.parent / "agent_functions" / "*.py"
modules = glob.glob(f"{searchPath}")
invalidate_caches()
for x in modules:
if not x.endswith("__init__.py") and x[-3:] == ".py":
module = import_module(f"{__name__}.agent_functions." + Path(x).stem)
for el in dir(module):
if "__" not in el:
globals()[el] = getattr(module, el)


sys.path.append(os.path.abspath(currentPath.name))
Loading

0 comments on commit e3d084d

Please sign in to comment.