Serverless C2 transport plugin for AdaptixC2 v1.2 using AWS Lambda + DynamoDB as the relay infrastructure. Agent traffic appears as outbound HTTPS to AWS endpoints, requiring no inbound ports or public IPs on the C2 server.
Kharon Agent (target)
|
| HTTPS GET/POST (outbound only, randomly alternated)
v
AWS Lambda Function URL (stateless relay)
|
| Store inbound / Poll outbound (up to 8s)
v
DynamoDB (inbound + outbound tables)
^
| Poll every 5 seconds
|
Listener Plugin (inside AdaptixC2)
|
| TsAgent API + TsExtenderData (key persistence)
v
AdaptixC2 Teamserver + UI
Data flow:
- Kharon agent sends encrypted check-in via HTTPS to Lambda Function URL
- Lambda stores raw data in DynamoDB
inboundtable - Lambda polls
outboundtable for up to 8 seconds (bridges the async gap for registration) - If a response exists, Lambda returns it and marks it delivered
- AdaptixC2 listener plugin polls DynamoDB
inboundtable for unprocessed records - Listener parses Kharon wire protocol (LokyCrypt encrypted), registers/updates agent
- When operator issues commands, listener encrypts them in Kharon format, stores in
outbound - On next check-in, Lambda returns the response to the agent
| Component | Path | Purpose |
|---|---|---|
| Kharon agent | agent/ |
Bundled Kharon implant (C++ source) |
| Terraform | deploy/aws/ |
Lambda + DynamoDB + IAM + KMS infrastructure |
| Lambda relay | deploy/aws/lambda/ |
Stateless HTTP-to-DynamoDB proxy with outbound polling |
| Listener plugin | listener/ |
AdaptixC2 plugin, DynamoDB polling, Kharon protocol bridge |
| Kharon configs | kharon/ |
AXS command registration and config for agent/listener extenders |
| Patches | patches/ |
AdaptixC2 source patches for BeaconServerless support |
| Profiles | profiles/ |
Malleable HTTP profile for Lambda URL |
| Scripts | scripts/ |
Install, build, deploy, uninstall |
- AWS account with credentials configured (
aws configure) - Terraform >= 1.5.0
- Go >= 1.23 (listener plugin requires matching GOEXPERIMENT with server binary)
- AdaptixC2 v1.2 installed
- clang++ with mingw targets:
apt install clang lld - NASM assembler:
apt install nasm - objcopy:
apt install binutils-mingw-w64-x86-64
In the AWS Console:
- Go to IAM > Users > Create user
- Name it
adaptix-deployer - Select Attach policies directly
- Attach these AWS managed policies:
AmazonDynamoDBFullAccessAWSLambda_FullAccessIAMFullAccessCloudWatchLogsFullAccess
- Add an inline policy for KMS:
- Click Add permissions > Create inline policy > JSON
- Paste:
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"kms:*","Resource":"*"}]} - Name it
KMSFullAccess
- Create the user, then go to Security credentials > Create access key
- Choose Command Line Interface (CLI)
- Save the Access Key ID and Secret Access Key
aws configure
# AWS Access Key ID: <paste access key>
# AWS Secret Access Key: <paste secret key>
# Default region: us-east-1
# Default output format: jsonVerify:
aws sts get-caller-identity# Build the Lambda relay binary (cross-compiled for Amazon Linux)
./scripts/build_relay.sh
# Deploy Lambda + DynamoDB + IAM with Terraform
./scripts/deploy_infra.sh
# Note the Lambda Function URL from the output, e.g.:
# lambda_function_url = "https://xxxxx.lambda-url.us-east-1.on.aws"./scripts/install.sh /path/to/AdaptixC2This script will:
- Back up your
dist/directory (certs, database, profile) - Apply source patches for BeaconServerless connector support
- Detect
GOEXPERIMENTflags from the server binary - Build the listener plugin (
.so) with matching flags - Rebuild AdaptixC2 server and extenders
- Restore your backed-up files (certs, database, profile)
- Install the serverless listener plugin + Kharon extender configs
- Create/update the systemd service
- Restart AdaptixC2
- Log into the AdaptixC2 web UI
- Go to Listeners and create a new
BeaconServerlesslistener - Set Lambda URL to the Function URL from Step 1
- Set Relay API Key (must match
relay_api_keyin your terraform.tfvars) - Optionally adjust: AWS Region, Poll Interval (default 5s), TTL Hours (default 24)
- Upload the malleable profile from
profiles/lambda_default.json - Start the listener
- In the GUI, generate a Kharon agent selecting the
BeaconServerlesslistener - Choose format (Exe, Dll, Svc) and architecture (x64)
- Execute the payload on the target
- The agent should check in within seconds
region = "us-east-1"
function_name = "adaptix-relay"
relay_api_key = "your-secret-key-here"
tags = {
Project = "adaptix-serverless"
}| Field | Description | Default |
|---|---|---|
| AWS Region | Region where infra is deployed | us-east-1 |
| Lambda URL | Function URL from Terraform output | (required) |
| Relay API Key | Must match Terraform's relay_api_key | (optional) |
| Inbound Table | DynamoDB table for agent check-ins | adaptix-inbound |
| Outbound Table | DynamoDB table for server responses | adaptix-outbound |
| Poll Interval | How often to check DynamoDB (seconds) | 5 |
| TTL Hours | DynamoDB record expiry | 24 |
The Kharon agent uses a custom binary protocol over HTTP:
- New agent (registration):
[36-byte UUID][encrypted_checkin_data][16-byte LokyCrypt key] - Connected agent (GetTask):
[36-byte UUID][encrypted_payload](no trailing key) - !Connected agent (key stomping): When payload is small, the 16-byte key at
TotalLen-16overlaps into the UUID area. For a 44-byte packet, key starts at offset 28, stomping UUID bytes 28-35 and all encrypted data.
The listener detects all three formats and handles them correctly.
The async nature of Lambda + DynamoDB means the listener hasn't processed the inbound record when Lambda first checks for a response. The Lambda polls the outbound table for up to 8 seconds after storing an inbound record, giving the listener time to process and queue the response. This is critical for registration (the Checkin response must arrive during the Checkin phase, not a later GetTask).
Agent encryption keys are persisted via TsExtenderDataSave/TsExtenderDataLoad (SQLite-backed). This survives service restarts. On restart, the listener recovers keys from the persistent store when it encounters a known agent ID.
Kharon randomly alternates between GET and POST requests (HTTP_METHOD_USE_BOTH). GET requests put data in the id query parameter (base64-encoded per the profile). POST requests put raw binary in the HTTP body. The Lambda handles both transparently.
- All agent traffic is outbound HTTPS to
*.lambda-url.*.on.awsendpoints - No inbound ports required on the C2 server
- DynamoDB records auto-expire via TTL
- CloudWatch logs are KMS-encrypted with 3-day retention
- Lambda relay authenticates via API key header
- Consider using a custom domain with CloudFront for domain fronting
- The Lambda poll loop adds 0-8 seconds latency to responses
# Remove plugin from AdaptixC2
./scripts/uninstall.sh /path/to/AdaptixC2
# Destroy AWS infrastructure
cd deploy/aws && terraform destroyadaptix-serverless-c2/
agent/
src_beacon/ # Kharon implant (C++ source)
src_loader/ # Loader wrappers (EXE, DLL, SVC)
deploy/
aws/
main.tf # Lambda + DynamoDB + IAM + KMS
variables.tf # Terraform variables
outputs.tf # Output Lambda URL and table ARNs
lambda/
main.go # Lambda relay handler
listener/
pl_main.go # AdaptixC2 plugin entry (Teamserver interface)
pl_transport.go # DynamoDB polling, Kharon protocol, LokyCrypt
ax_config.axs # Listener creation UI
config.yaml # Plugin metadata
kharon/
agent_kharon/ # AXS commands + config for Kharon agent extender
listener_kharon_http/ # AXS + config for Kharon HTTP listener extender
patches/ # AdaptixC2 source patches
profiles/
lambda_default.json # Malleable profile for Lambda URL
scripts/
install.sh # Full install into AdaptixC2
uninstall.sh # Reverse patches + cleanup
build_relay.sh # Cross-compile Lambda binary
build_agent.sh # Build Kharon agent shellcode
build_objects.sh # Build beacon objects
deploy_infra.sh # Terraform init + apply
