Sliver#
InfraGuard parses Sliver’s HTTPS C2 profile JSON to generate all valid URI combinations that an implant might use. Sliver constructs request URIs procedurally from path/file/extension segments, so the parser expands these into a full URI set for the profile filter.
Sliver supports proxy-aware C2 over both HTTP and HTTPS. Since Sliver does not rely on the SSL/TLS layer for security, these protocols are considered synonymous – an implant generated with --http may attempt to connect over both HTTP and HTTPS.
Config#
domains:
assets.example.com:
upstream: "${SLIVER_UPSTREAM}"
profile_path: "examples/sliver.json"
profile_type: "sliver"
drop_action:
type: "tarpit"
target: ""See config/examples/c2-sliver.yaml.
Profile JSON Format#
The C2 profile lives at ~/.sliver/configs/http-c2.json on the Sliver teamserver. It has two top-level objects: implant_config (controls how the implant generates requests) and server_config (controls how the server responds).
Changes to the C2 profile may break compatibility with existing implants. Make profile changes before generating any implants.
implant_config#
The implant procedurally generates each HTTP request. URIs are constructed as /{path}/{file}{ext}, where each segment is randomly selected from the configured lists.
URI Generation Fields#
The profile uses three flat lists shared across all communication stages:
| Field | Description |
|---|---|
paths | Array of path segments (e.g. js, assets, scripts) |
files | Array of file name stems (e.g. jquery.min, bootstrap, app) |
extensions | Array of file extensions (e.g. ["js", "", "php"]). Empty string means no extension. |
Each implant is embedded with a randomly generated subset of the server’s profile (controlled by min_files/max_files and min_paths/max_paths), so two implants generated from the same server may produce different URL patterns.
Legacy per-stage keys#
Older Sliver versions used stage-specific path/file/extension keys. The parser still supports these and prefers them when present, falling back to the generic paths/files/extensions arrays when a stage-specific key is missing.
| Stage | Paths key | Files key | Extension key | Default Ext | HTTP Method |
|---|---|---|---|---|---|
| stager | stager_paths | stager_files | stager_file_ext | .woff | GET |
| poll | poll_paths | poll_files | poll_file_ext | .js | GET |
| start_session | session_paths | session_files | start_session_file_ext | .html | POST |
| session | session_paths | session_files | session_file_ext | .php | POST |
| close | close_paths | close_files | close_file_ext | .png | GET |
If your profile uses the flat format (the common case now), the parser uses paths for all stage path lookups, files for all stage file lookups, and extensions as the extension list for every stage.
Other Fields#
| Field | Description |
|---|---|
user_agent | The implant’s User-Agent. Empty string (default) randomly generates a platform-appropriate UA. |
chrome_base_version | Chrome version number for the generated UA, plus a random value up to 3 (default: 100) |
macos_version | macOS version string used in the generated UA (e.g. 10_15_7) |
min_files / max_files | Min/max number of file names randomly selected per implant build (default: 2/4) |
min_paths / max_paths | Min/max number of path segments randomly selected per implant build (default: 2/4) |
min_path_length / max_path_length | Min/max character length of individual path segments (default: 2/4) |
url_parameters | Array of {name, value, probability} objects added as URL query parameters. Probability 1-100. |
headers | Array of {name, value, probability} objects added as request headers |
Nonce#
Every request includes a nonce query parameter that looks like a standard cache-buster but actually encodes which data encoder was used. The nonce behavior is configured by:
| Field | Description |
|---|---|
nonce_query_args | Alphabet used to generate the nonce parameter name (default: lowercase a-z) |
nonce_query_length | Length of the nonce parameter name (default: 1, so the param name is a single letter like ?a=, ?b=, etc.) |
nonce_mode | How the nonce is transmitted. UrlParam puts it in the query string. |
The server computes nonce % EncoderModulus to determine the encoder ID. Requests without a valid nonce are ignored from a C2 standpoint (though the server may still respond with static website content).
Supported encoders:
| Encoder | Description |
|---|---|
| Base64 | Custom alphabet (not interoperable with standard Base64) |
| Hex | Standard hex encoding |
| Gzip | Standard gzip |
| English | Encodes data as English ASCII text |
| PNG | Encodes data into valid PNG image files |
| Gzip+English | Combined |
| Base64+Gzip | Combined |
The server always responds using the same encoder as the request.
server_config#
| Field | Description |
|---|---|
random_version_headers | If true, the server returns random Apache/Nginx and PHP version numbers (default: false) |
headers | Array of {name, value, probability, method} response headers. probability 1-100 (any other value treated as 100). method optionally restricts the header to GET or POST responses only. |
cookies | Array of cookie name strings (e.g. JSESSIONID, PHPSESSID, SID) |
Example Profile#
{
"implant_config": {
"user_agent": "",
"chrome_base_version": 106,
"macos_version": "10_15_7",
"nonce_query_args": "abcdefghijklmnopqrstuvwxyz",
"nonce_query_length": 1,
"nonce_mode": "UrlParam",
"url_parameters": null,
"headers": null,
"max_files": 4,
"min_files": 2,
"max_paths": 4,
"min_paths": 2,
"max_path_length": 4,
"min_path_length": 2,
"extensions": ["js", "", "php"],
"files": [
"bootstrap", "bootstrap.min", "jquery.min", "jquery",
"route", "app", "app.min", "array", "backbone", "script",
"email", "main", "index", "utils"
],
"paths": [
"js", "umd", "assets", "bundle", "bundles", "scripts",
"script", "javascripts", "javascript", "jscript"
]
},
"server_config": {
"random_version_headers": false,
"headers": [
{
"name": "Cache-Control",
"value": "no-store, no-cache, must-revalidate",
"probability": 100,
"method": "GET"
}
],
"cookies": [
"JSESSIONID", "rememberMe", "authToken", "PHPSESSID",
"SID", "SSID", "APISID"
]
}
}The example above is trimmed for readability. Real profiles typically have much larger
files,paths, andcookiesarrays to increase randomization.
How InfraGuard Parses It#
The SliverParser handles both the flat format and the legacy per-stage format. For each communication stage, it checks for the stage-specific key first (e.g. poll_paths), then falls back to the generic paths array. Same logic for files and extensions.
URI generation works by computing the cross product of paths, files, and extensions:
- GET URIs: poll + close + stager combinations
- POST URIs: session + start_session combinations
Each set is capped at 100 URIs to prevent combinatorial explosion with large path/file lists.
Extensions without a leading dot get one prepended automatically. An empty string in the extensions array produces URIs with no extension (e.g. /{path}/{file} instead of /{path}/{file}.js).
The parser maps Sliver’s cookie list to the client message config – the first cookie in the server’s cookie list (e.g. JSESSIONID) is used as the data transport cookie name for GET requests. POST requests carry data in the body.
Server response headers with probability >= 50 are included in the profile’s expected response headers.
Generating the Implant#
sliver > generate --http example.com --os macMultiple domains can be specified as fallbacks. If the implant can’t connect to the first domain, it tries each subsequent one based on --reconnect (default 60 seconds) until --max-errors (default 1000) is reached:
sliver > generate --http example.com,fallback.comURL Prefixes#
A path prefix can be prepended to all generated URLs by appending it to the domain:
sliver > generate --http example.com/foo/barThis prepends /foo/bar to every request URI. Different domains can have different prefixes. Useful when routing through InfraGuard based on path.
Proxy Support#
The implant auto-detects system proxy settings on Windows, macOS, and Linux using a modified go-get-proxied library. Connection attempts are made in this order:
- HTTPS over system proxy
- HTTP over system proxy
- HTTPS direct connect
- HTTP direct connect
Proxy settings are read from environment variables (HTTPS_PROXY, HTTP_PROXY, ALL_PROXY), and on Windows from Internet Options and WinHTTP settings. NO_PROXY is respected on all platforms.
For environments requiring NTLM/Kerberos proxy authentication (Windows only), the implant can be built with the wininet HTTP library, though this is less stable and more susceptible to EDR hooking.
Listeners#
Start an HTTP listener on the Sliver teamserver:
sliver > http
sliver > http --domain example.comThe --domain flag restricts the listener to a specific domain. Static website content can be served alongside C2 traffic using the --website flag to make the server look legitimate:
sliver > websites --website fake-blog --web-path / --content ./index.html add
sliver > http --website fake-blog --domain example.comC2 messages are intercepted before website content is checked, so website content can be mapped to any URL including those used for C2.
For HTTPS, the listener generates a random self-signed certificate by default. Let’s Encrypt automatic certificates are supported with --lets-encrypt, or you can provide your own:
sliver > https --domain example.com --cert ./cert.pem --key ./key.pemRecommended Pipeline Settings#
pipeline:
filter_mode: "scoring"
block_score_threshold: 0.7
enable_profile_filter: true
enable_replay_filter: true
replay_persist: true
enable_sandbox_filter: true
enable_ja3_filter: trueSliver randomizes the User-Agent per implant build (when user_agent is empty), so UA-based filtering won’t be reliable. The bot filter still catches known scanner User-Agents, and the JA3 filter catches non-browser TLS stacks.