Dynamic HTTP/s Payload Stager
A dynamic HTTP/S stager that lets one shellcode loader be reused for different encrypted payloads - no rebuilds.

Introduction
When testing C2 frameworks I kept rebuilding shellcode loaders to update decryption variables (e.g., keys), which was slow and inconvenient.
To fix this I built a dynamic HTTP/S stager that lets a single loader be reused across different encrypted payloads without rebuilding.
Demo
To use the Dynamic HTTP/s Stager, you only need to configure three parameters:
- The number of variables
- The URL to the hosted file
- The delimiter used in the Python script
You can view all the code in GitHub.
Here’s a video demo showing the Payload stager in action:
How it works
A small Python tool packages the decryption parameters (key, IV, etc.) into a single Base64-encoded file you host. Each line uses the format <name><delimiter><hex>
(two hex characters = one byte).
At runtime, the stager fetches the file, splits each line on the delimiter, decodes the Base64 content, converts hex pairs into bytes, and loads them into memory.
This allows the same loader to decrypt different payloads on demand - no rebuilds or redeployments required.
File Format and Python Script
The text file containing these variables must adhere to a specific format to ensure compatibility with the stager:
# Format
<variable name>-<hexadecimal code>
To simplify converting variables generated by WafflesCrypt into the required format, I created a Python script that handles this conversion. By default, the delimiter is set to -, but this can be customized as needed.
You can view the Python script here: ConvertToFormat.py.
After generating the file, host it on a web server. For quick hosting, use the built-in Python HTTP server with the command:
python -m http.server 8080
Alternatively, you can use my enhanced version of the http.server module, httpserver-plus, which offers additional features like HTTPS support and customizable route handling for redirection.
Enhancing the Stager’s Flexibility
The current HTTP Stager relies on a specific URL. If this URL or domain is blocked by defensive measures, the payload will cease to function.
To mitigate this, I thought of two potential solutions:
- Generating Dynamic, Single-Use URLs
- Storing Multiple Domains in Arrays and Obfuscating with IPv4Fuscation
These solutions are also effective for C2 callbacks when designing implants.
Generating Dynamic, Single-Use URLs
Creating dynamic, single-use URLs makes it more difficult for defenders to detect, block, or reuse URLs.
Here are some techniques to generate unique, one-time URLs:
- Time-Based URL Generation
- Use the current timestamp and a secret key to generate a URL valid only for a short period.
- The URL could look like: http://example.com/file/1686584376/ABcdEF12…
- Token-Based URL with Expiry
- Generate a unique token for each request, encoding a random key, session ID, and expiration time.
- The URL could look like: http://example.com/file?token=YXNkZmFzZGZhc2RmYXM…
- Mathematical Pattern Generation
- Utilize a mathematical pattern to create a predictable but hard-to-reverse sequence.
- Server-Side Session Tracking
- Use a server-side session or database to manage URL validity, mapping each unique URL to an expiration time and client ID.
- Hash Chaining or Rolling Hashes
- Employ a rolling hash or hash chaining to generate evolving, unique URLs for each session.
Storing Multiple Domains in Arrays and Obfuscating with IPv4Fuscation
Instead of hardcoding a single domain or IP address, you can store multiple potential domains or IP addresses in an array:
const char* domains[] = {
"http://example1.com",
"http://example2.net",
"http://example3.org",
};
Additionally, you can use IPv4/IPv6 obfuscation methods, as demonstrated in my Waffles Crypt post, to make these addresses harder to detect during static analysis.
For example, converting http://example1.com
to hexadecimal yields: 68 74 74 70 3A 2F 2F 65 78 61 6D 70 6C 65 31 2E 63 6F 6D.
This hexadecimal representation can then be converted to an IP address:
const char* domains[] = {
"104.116.116.112",
"58.47.47.101",
"120.97.109.112",
"108.101.49.46",
"0.99.111.109"
};
Conclusion
This HTTP Stager streamlines the process of managing encrypted payloads by eliminating the need to constantly regenerate shellcode loaders.
While this method can significantly reduce time and effort, it’s important to note that it remains a proof of concept (PoC). From an operational security (OPSEC) standpoint, leaving decryption variables in plaintext isn’t ideal.
A better approach might involve using generic variable names, like a, b, c, and d, since the actual names are irrelevant to the decryption process itself and are only placeholders for the values in arrays.
I hope this post has sparked new ideas or provided valuable insights. I’d love to hear your thoughts, suggestions, or any improvements you might have! Feel free to reach out to me directly.
Happy hacking!