Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
892b2bd
feat: scaffold extender support infrastructure
TGJLS Jul 10, 2026
5a05c2b
feat: add extenders table and CRUD to db.py
TGJLS Jul 10, 2026
e0e0621
feat: add .axs parser and field classifier
TGJLS Jul 10, 2026
61123cc
feat: add profile_manager for atomic profile.yaml management
TGJLS Jul 10, 2026
e73a186
feat: add /v1/extenders REST API
TGJLS Jul 10, 2026
b1dcdec
feat: resolve active extenders in runner before fallback to config pr…
TGJLS Jul 10, 2026
742ad68
feat: add CI/CD jobs for Kharon and Extension-Kit extenders
TGJLS Jul 10, 2026
7fd5254
fix: resolve CI failures for extender support
TGJLS Jul 10, 2026
9205722
fix: resolve all round-2 CI failures for extender support
TGJLS Jul 10, 2026
eb85516
fix: fix Kharon build path and skip Extension-Kit restart
TGJLS Jul 10, 2026
671d37e
fix: increase timeouts for Windows boot and adaptixc2 plugin load
TGJLS Jul 10, 2026
bca0bbe
fix: pin axc2 to adaptixc2's version when building Kharon plugins
TGJLS Jul 10, 2026
64dd09a
fix: hardcode axc2 version pin instead of runtime detection
TGJLS Jul 10, 2026
ab21a6f
feat: add Kharon extender CI support with sleep mask disabled
TGJLS Jul 11, 2026
f0b8095
fix: provide CI-safe defaults for Kharon evasive/optional agent fields
TGJLS Jul 11, 2026
d9952c6
fix: install python3 in adaptixc2 container before running install-kh…
TGJLS Jul 11, 2026
ae33056
fix: use actual Go version from binary in go.work instead of hardcode…
TGJLS Jul 11, 2026
96d05fe
fix: build listener plugin with combined go.work to fix axc2 version …
TGJLS Jul 11, 2026
6825c3a
fix: diagnose axc2 ABI mismatch + pin to binary version + minimal go.…
TGJLS Jul 11, 2026
e6b1217
fix: rebuild adaptixserver from source to guarantee plugin ABI compat…
TGJLS Jul 11, 2026
9f721fd
fix: pin ALL server dep versions to prevent plugin ABI mismatch
TGJLS Jul 12, 2026
9a06e2e
fix: restore profile.yaml without Kharon extender entries
TGJLS Jul 12, 2026
f67017b
fix: remove GOFLAGS=-mod=mod to avoid combining with container go env
TGJLS Jul 12, 2026
22d6f7e
fix: rebuild adaptixserver from source to fix Kharon plugin ABI mismatch
TGJLS Jul 12, 2026
a2aa5d7
fix: use static IP in Kharon malleable profile for agent callback
TGJLS Jul 12, 2026
259e327
fix: code review cleanups — debug output, silent failures, ABI deps
TGJLS Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/cicd/extension-kit-tasks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
tasks:
- cmdline: "shell whoami"
expected: "ci_runner"

- cmdline: "shell echo extension_kit_ok"
expected: "extension_kit_ok"

- cmdline: "xyzzy frobnicate"
expected: "will never succeed"
allowed_to_fail: true
18 changes: 18 additions & 0 deletions .github/cicd/install-extension-kit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Install Extension-Kit BOF collection inside adaptixc2.
# The repo is cloned to /app/extenders/extension-kit.
set -euo pipefail

EXT_KIT_DIR=/app/userextenders/extension-kit

echo "Extension-Kit: checking for pre-built BOF files..."

if [[ -f "${EXT_KIT_DIR}/setup.sh" ]]; then
bash "${EXT_KIT_DIR}/setup.sh"
elif [[ -f "${EXT_KIT_DIR}/install.sh" ]]; then
bash "${EXT_KIT_DIR}/install.sh"
else
echo "No setup script found — BOF files assumed pre-compiled."
fi

echo "Extension-Kit install complete."
224 changes: 224 additions & 0 deletions .github/cicd/install-kharon.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#!/usr/bin/env bash
# Build and install Kharon extender inside the adaptixc2 container.
# The repo is cloned to /app/userextenders/kharon by Testing-Kit.
set -euo pipefail

KHARON_DIR=/app/userextenders/kharon

NEED_PKGS=()
command -v make &>/dev/null || NEED_PKGS+=(make)
command -v python3 &>/dev/null || NEED_PKGS+=(python3)
command -v git &>/dev/null || NEED_PKGS+=(git)
command -v nasm &>/dev/null || NEED_PKGS+=(nasm)
command -v clang &>/dev/null || NEED_PKGS+=(clang llvm)
if [ ${#NEED_PKGS[@]} -gt 0 ]; then
apt-get update -qq
apt-get install -y -qq "${NEED_PKGS[@]}"
fi

# --- Go toolchain ---
# Go plugins require the EXACT same Go toolchain version AND GOEXPERIMENT flags
# as the main adaptixserver binary. Read both from the binary.
BINARY_GO=$(go version -m /app/adaptixserver 2>/dev/null | awk 'NR==1{print $2}') || BINARY_GO=""
CURRENT_GO=$(go version 2>/dev/null | awk '{print $3}') || CURRENT_GO=""
# Only set GOEXPERIMENT when the binary actually carries X: flags; otherwise
# passing the whole version line as GOEXPERIMENT would abort every go build.
BINARY_GOEXP=$(go version -m /app/adaptixserver 2>/dev/null | awk 'NR==1 && /X:/{sub(/^.*X:/,""); print}') || BINARY_GOEXP=""

if [ -n "$BINARY_GO" ] && [ "$BINARY_GO" != "$CURRENT_GO" ]; then
echo "Toolchain mismatch: adaptixserver=${BINARY_GO}, container=${CURRENT_GO}"
echo "Installing ${BINARY_GO} at /usr/local/go..."
wget -qO- "https://dl.google.com/go/${BINARY_GO}.linux-amd64.tar.gz" | \
tar -xz -C /usr/local/ --overwrite
echo "Now using: $(go version)"
fi

echo "Using Go: $(go version)"
[ -n "$BINARY_GOEXP" ] && echo "GOEXPERIMENT: ${BINARY_GOEXP}"

# --- Read original server deps (before any rebuild) ---
# Extract dep versions from the ORIGINAL binary so we can pin identical
# versions when rebuilding adaptixserver AND when building Kharon plugins.
SERVER_BUILD_INFO=$(go version -m /app/adaptixserver 2>/dev/null) || SERVER_BUILD_INFO=""

SERVER_DEPS_FILE=/tmp/server_deps.txt
echo "$SERVER_BUILD_INFO" | awk '/^\tdep\t/{print $2"@"$3}' > "$SERVER_DEPS_FILE"
echo "Found $(wc -l < "$SERVER_DEPS_FILE" | tr -d ' ') dep modules in server binary"

# --- Rebuild adaptixserver from source ---
# Go plugin ABI compatibility requires every shared package (axc2, x/sys, …)
# to have the SAME package build ID. Build IDs are:
# hash(source_content + dep_build_ids + compiler_binary_hash)
# Even with an identical version string (go1.25.11), a freshly-downloaded
# tarball may have a different compiler binary than the one used inside the
# Docker image, causing ALL package build IDs to diverge.
#
# Fix: rebuild /app/adaptixserver with OUR go binary using the ORIGINAL
# dep versions. Then build Kharon plugins with the same binary.
# Both server and plugins share the same compiler hash → ABI is compatible.
AXC2_CLONE=/tmp/adaptixc2-src
if [ ! -d "$AXC2_CLONE" ]; then
echo "Cloning TGJLS/AdaptixC2 for server rebuild..."
git clone --depth=1 https://github.com/TGJLS/AdaptixC2 "$AXC2_CLONE"
else
echo "Using existing TGJLS/AdaptixC2 clone at ${AXC2_CLONE}"
fi

echo "Rebuilding /app/adaptixserver from ${AXC2_CLONE}/AdaptixServer ..."
(
cd "${AXC2_CLONE}/AdaptixServer"
while IFS= read -r dep; do
go mod edit -require "$dep" 2>/dev/null || true
done < "$SERVER_DEPS_FILE"
GONOSUMDB='*' go mod download 2>&1 || true
GOEXPERIMENT="${BINARY_GOEXP}" CGO_ENABLED=1 \
go build -ldflags="-s -w" -o /app/adaptixserver .
echo "Rebuilt: $(go version -m /app/adaptixserver 2>/dev/null | head -1)"
)

# Extract the exact axc2 version. Fall back to v1.2.0 if not found.
BINARY_AXC2=$(echo "$SERVER_BUILD_INFO" | \
awk '/github\.com\/Adaptix-Framework\/axc2/{print $3}') || BINARY_AXC2=""
if [[ "${BINARY_AXC2}" =~ ^v[0-9] ]]; then
AXC2_VERSION="${BINARY_AXC2}"
else
AXC2_VERSION=v1.2.0
fi
echo "Pinning axc2 to ${AXC2_VERSION}"

# --- Build combined go.work ---
# Include ONLY Kharon modules so AdaptixC2 HEAD doesn't bump deps via MVS.
COMBINED_WORK=/tmp/combined.work
GO_WORK_VER="${BINARY_GO#go}"
[ -z "$GO_WORK_VER" ] && GO_WORK_VER="1.25"
{
printf 'go %s\n\nuse (\n' "${GO_WORK_VER}"
printf ' %s\n' "${KHARON_DIR}/listener_kharon_http"
printf ' %s\n' "${KHARON_DIR}/agent_kharon"
printf ')\n'
} > "$COMBINED_WORK"

# Pin ALL server dep versions into a module's go.mod so MVS selects identical
# versions for every shared package.
pin_server_deps() {
local dir="$1"
echo "Pinning server dep versions in $(basename "$dir")..."
(cd "$dir" && while IFS= read -r dep; do
go mod edit -require "$dep" 2>/dev/null || true
done < "$SERVER_DEPS_FILE")
echo "Downloading pinned modules to update go.sum..."
(cd "$dir" && GOWORK="${COMBINED_WORK}" GONOSUMDB='*' go mod download 2>&1 || true)
}

# --- Build Kharon listener ---
echo "Building Kharon listener..."
cd "${KHARON_DIR}/listener_kharon_http"
go get "github.com/Adaptix-Framework/axc2@${AXC2_VERSION}"
pin_server_deps "${KHARON_DIR}/listener_kharon_http"
GOWORK="${COMBINED_WORK}" GOEXPERIMENT="${BINARY_GOEXP}" GONOSUMDB='*' make all

# --- Patch pl_agent.go: add mask_sleep="none" -> KH_SLEEP_MASK=0 ---
# Without this, the default sleep mask mode (3) uses obfuscation techniques
# that prevent the agent from beaconing in a plain QEMU VM environment.
AGENT_GO="${KHARON_DIR}/agent_kharon/src_server/pl_agent.go"
python3 -c "
import sys
path = sys.argv[1]
with open(path) as f:
content = f.read()
old = ''' case \"pooling\":
makeVars = append(makeVars, \"KH_SLEEP_MASK=2\")
default:
makeVars = append(makeVars, \"KH_SLEEP_MASK=3\")'''
new = ''' case \"pooling\":
makeVars = append(makeVars, \"KH_SLEEP_MASK=2\")
case \"none\":
makeVars = append(makeVars, \"KH_SLEEP_MASK=0\")
default:
makeVars = append(makeVars, \"KH_SLEEP_MASK=3\")'''
if old in content:
content = content.replace(old, new)
with open(path, 'w') as f:
f.write(content)
print('Patched pl_agent.go: added KH_SLEEP_MASK=0 for mask_sleep=none')
elif 'case \"none\":' in content:
print('pl_agent.go already patched')
else:
print('ERROR: patch target not found in pl_agent.go — upstream may have changed the switch structure', file=sys.stderr)
sys.exit(1)
" "$AGENT_GO"

# --- Build Kharon agent plugin ---
echo "Building Kharon agent plugin..."
cd "${KHARON_DIR}/agent_kharon"
go get "github.com/Adaptix-Framework/axc2@${AXC2_VERSION}"
pin_server_deps "${KHARON_DIR}/agent_kharon"
rm -f dist/agent_kharon.so
cd "${KHARON_DIR}/agent_kharon/src_server"
GOWORK="${COMBINED_WORK}" GOEXPERIMENT="${BINARY_GOEXP}" GONOSUMDB='*' \
go build -buildmode=plugin -o "../dist/agent_kharon.so" .
echo "Built: $(ls -sh ../dist/agent_kharon.so)"

# --- Build src_beacon BOF prerequisites ---
echo "Building src_beacon prerequisites (nasm, LLVM object files)..."
cd "${KHARON_DIR}/agent_kharon/src_beacon"
make prebuild-x64

# --- Build src_core BOF modules ---
# Patch win32.h for types missing from older MinGW SDK headers.
WIN32_H="${KHARON_DIR}/agent_kharon/src_core/include/win32.h"
python3 -c "
import sys
path = sys.argv[1]
with open(path) as f:
content = f.read()
stub = '''
// Types missing from older MinGW SDK
#ifndef _PROCESS_MITIGATION_USER_POINTER_AUTH_POLICY_DEFINED
#define _PROCESS_MITIGATION_USER_POINTER_AUTH_POLICY_DEFINED
typedef struct { DWORD EnablePointerAuthKernel : 1; DWORD Spare : 31; } PROCESS_MITIGATION_USER_POINTER_AUTH_POLICY;
#endif
#ifndef _PROCESS_MITIGATION_SEHOP_POLICY_DEFINED
#define _PROCESS_MITIGATION_SEHOP_POLICY_DEFINED
typedef struct { DWORD EnableSehop : 1; DWORD Spare : 31; } PROCESS_MITIGATION_SEHOP_POLICY;
#endif
'''
marker = 'typedef struct _PROCESS_MITIGATION_POLICY_INFORMATION'
if stub.strip() not in content:
content = content.replace(marker, stub + marker)
with open(path, 'w') as f:
f.write(content)
print('Patched win32.h: added missing MinGW type stubs')
else:
print('win32.h already patched')
" "$WIN32_H"

echo "Building src_core BOF modules..."
cd "${KHARON_DIR}/agent_kharon/src_core"
make all

# --- Set up /dist/extenders/agent_kharon symlinks ---
# adaptixserver looks for src_beacon, src_loader, src_core under
# /dist/extenders/agent_kharon/ at agent-generate time.
DIST_KH=/dist/extenders/agent_kharon
mkdir -p "$DIST_KH"

for dir in src_beacon src_loader src_core; do
target="${KHARON_DIR}/agent_kharon/${dir}"
link="${DIST_KH}/${dir}"
if [ ! -L "$link" ]; then
ln -sf "$target" "$link"
fi
done

# --- cstdint shim for clang 14 MinGW Exe format compilation ---
CSTDINT="${KHARON_DIR}/agent_kharon/src_loader/Include/cstdint"
if [ ! -f "$CSTDINT" ]; then
cat > "$CSTDINT" <<'SHIM'
#pragma once
#include <stdint.h>
#include <stddef.h>
SHIM
fi

echo "Kharon build complete."
35 changes: 35 additions & 0 deletions .github/cicd/kharon-malleable-profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"callbacks": [
{
"hosts": ["172.28.0.10:8080"],
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"server_error": {
"http_status": 404,
"response": "Not Found",
"headers": {"Content-Type": "text/plain"}
},
"get": {
"server_headers": {"Content-Type": "application/octet-stream"},
"client_headers": {"Accept": "*/*"},
"empty_response": "",
"uri": {
"/api/v1/status": {
"server_output": {"mask": false, "format": "base64"},
"client_output": {"mask": false, "format": "base64"}
}
}
},
"post": {
"server_headers": {"Content-Type": "application/octet-stream"},
"client_headers": {"Content-Type": "application/octet-stream"},
"empty_response": "",
"uri": {
"/api/v1/check": {
"server_output": {"mask": false, "format": "base64"},
"client_output": {"mask": false, "format": "base64"}
}
}
}
}
]
}
17 changes: 17 additions & 0 deletions .github/cicd/kharon-tasks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
tasks:
- cmdline: "token getuid"
expected: "ci_runner"

- cmdline: "process create --command \"hostname\" --pipe"
expected_regex: "(?i)win|desktop|server"

- cmdline: "fs ls C:\\"
expected: "Windows"
not_expected: "File Not Found"

- cmdline: "process create --command \"cmd.exe /c echo kharon_test_ok\" --pipe"
expected: "kharon_test_ok"

- cmdline: "xyzzy frobnicate"
expected: "will never succeed"
allowed_to_fail: true
File renamed without changes.
Loading
Loading