Getting Started
To get started first download the latest Sliver server release you’ll need v1.5 or later to use SliverPy.
SliverPy connects to the Sliver server using “multiplayer mode” which can be enabled in the server console or using the Sliver server’s command line interface. In order to connect to the server you’ll need to first generate an operator configuration file. Clients connect to the Sliver server using mutual TLS (mTLS) and these operator configuration files contain the per-user TLS certificates (and other metadata) needed to make the connection to the server. These configuration files contain the user’s private key and should be treated as if they were a credential.
In the interactive console, the new-operator
command is used to generate an operator configuration file. You’ll need to
subsequently enable multiplayer mode using the multiplayer
command to start the multiplayer server listener. See the
--help
for each of these commands for more details:
$ ./sliver-server
sliver > new-operator --name zer0cool --lhost localhost --save ./zer0cool.cfg
[*] Generating new client certificate, please wait ...
[*] Saved new client config to: /Users/zer0cool/zer0cool.cfg
sliver > multiplayer
[*] Multiplayer mode enabled!
Alternatively, the command line interface can be used to generate operator configuration files and start the multiplayer listener
without entering into the interactive console. See each subcommand’s --help
for more details:
$ ./sliver-server operator --name zer0cool --lhost localhost --save ./zer0cool.cfg
$ ./sliver-server daemon
Now with the server running in the background you can connect to Sliver remotely (or locally) using the .cfg
with SliverPy!
Connect Example
SliverPy is implemented using asyncio
, if you’re unfamiliar with Python’s asyncio
you may want to go read up on it before continuing.
I recommend starting with this presentation by Raymond Hettinger if you’re completely unfamiliar with Python threads/asyncio.
The main class is SliverClient
, which when paired with a configuration file, allows you to interact with the Sliver server, sessions, and beacons:
#!/usr/bin/env python3
import os
import asyncio
from sliver import SliverClientConfig, SliverClient
CONFIG_PATH = os.path.join('path', 'to', 'default.cfg')
async def main():
''' Async client connect example '''
config = SliverClientConfig.parse_config_file(CONFIG_PATH)
client = SliverClient(config)
await client.connect()
sessions = await client.sessions()
print('Sessions: %r' % sessions)
if __name__ == '__main__':
asyncio.run(main())
Protobuf / gRPC
Under the hood SliverPy is communicating with the Sliver server using Protobuf and gRPC. While most of the details of these libraries are abstracted for you, it may be useful to familiarize yourself with the library conventions as SliverPy operates largely on Protobuf objects which do not follow Python language conventions.
There are three modules of Protobuf objects:
sliver.commonpb_pb2
Contains common Protobuf objects that represent things like files and processes.sliver.client_pb2
Contains objects that are specifically passed between the client and server, but not to the implant.sliver.sliver_pb2
Contains objects that are passed to the client, server, and implant.
NOTE: Protobuf objects use CapitolCase
whereas the SliverPy classes/etc. use snake_case
.
These modules contain generated code and are not easy to read. However, the source Protobuf definitions are in the Sliver server repository to find the exact definitions that SliverPy is using see the git submodule in the SliverPy repository.
Interactive Sessions
To interact with a Sliver session we need to create an InteractiveSession
object, the easiest way to do this is using the SliverClient
’s
.interact_session()
method, which takes a session ID and returns an InteractiveSession
for that ID:
#!/usr/bin/env python3
import os
import asyncio
from sliver import SliverClientConfig, SliverClient
# Construct path to operator config file
CONFIG_PATH = os.path.join('path', 'to', 'operator.cfg')
async def main():
''' Session interact example '''
config = SliverClientConfig.parse_config_file(CONFIG_PATH)
client = SliverClient(config)
await client.connect()
sessions = await client.sessions() # <-- List Protobuf Session objects
if not len(sessions):
print('No sessions!')
return
session = await client.interact_session(sessions[0].ID) # <-- Create InteractiveSession object
ls = await session.ls() # <-- Returns an Ls Protobuf object
print('Listing directory contents of: %s' % ls.Path)
for fi in ls.Files:
print('FileName: %s (dir: %s, size: %d)' % (fi.Name, fi.IsDir, fi.Size))
if __name__ == '__main__':
asyncio.run(main())
NOTE: There are two “session” related objects the Protobuf client_pb2.Session
object, which contains metadata about the sessions such as
the session ID, the active C2 protocol, etc. and the InteractiveSession
class, which is used to interact with the session (i.e., execute commands, etc).
Interactive Beacons
To interact with a Sliver beacon we need to create an InteractiveBeacon
object, the easiest way to do this is using the SliverClient
’s
.interact_beacon()
method, which takes a beacon ID and returns an InteractiveBeacon
for that ID:
#!/usr/bin/env python3
import os
import asyncio
from sliver import SliverClientConfig, SliverClient
# Construct path to operator config file
CONFIG_PATH = os.path.join('path', 'to', 'operator.cfg')
async def main():
''' Session interact example '''
config = SliverClientConfig.parse_config_file(CONFIG_PATH)
client = SliverClient(config)
await client.connect()
beacons = await client.beacons() # <-- List Protobuf Session objects
if not len(beacons):
print('No beacons!')
return
beacon = await client.interact_beacon(beacons[0].ID) # <-- Create InteractiveSession object
ls_task = await beacon.ls() # <-- Creates a beacon task Future
print('Created beacon task: %s' % ls_task)
print('Waiting for beacon task to complete ...')
ls = await ls_task
# Beacon Task has completed (Future was resolved)
print('Listing directory contents of: %s' % ls.Path)
for fi in ls.Files:
print('FileName: %s (dir: %s, size: %d)' % (fi.Name, fi.IsDir, fi.Size))
if __name__ == '__main__':
asyncio.run(main())
NOTE: The main difference between interacting with a session vs. a beacon, is that a beacon’s command will return a Future
object that eventually resolves to the task result.
Realtime Events
SliverPy also supports realtime events, which are pushed from the server to the client whenever an event occurs. For example, some of the more common events you’ll likely be interested in are when a new session is created or when a job starts/stops.
The SliverClient
implements these real time events using asyncio
.
Events are identified by an “event type,” which is just a string set by the producer of the event. This loose form
allows events to be very dynamic, however this also means there is no central authority for every event type. I
recommend always filtering on expected event types. The data included in an event also depends on whatever produced
the event, so you should always check that an attribute exists before accessing that attribute (with the exception of
event.EventType
which must exist).
Here is a non exhaustive list of event types:
Event Type |
Description |
||
|
An existing session was lost |
||
|
An existing session was renamed / updated |
||
|
A job was started on the server |
||
|
A job stopped (due to error or user action) |
||
|
A new client connected to the server |
||
|
A client disconnected from the server |
||
|
A canary was burned / triggered / etc. |
||
|
A modification was made to implant builds |
||
|
An implant build completed (in success or failure) |
||
|
A modification was made to implant profiles |
||
|
A modification was made to website(s) |
||
|
A new beacon connected to the server |
||
|
A beacon task completed |
Automatically Interact With New Sessions
The SliverClient
’s .on()
method returns an async generator, which can be iterated over. .on()
accepts a string or a list of strings to filter events.
Additionally, .events()
can be used to obtain a generator that will yield all events.
Here is an example of using .on()
to automatically interact with new sessions when they connect:
#!/usr/bin/env python3
import os
import asyncio
from sliver import SliverClientConfig, AsyncSliverClient, client_pb2
CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".sliver-client", "configs")
CONFIG_PATH = os.path.join(CONFIG_DIR, "default.cfg")
async def main():
''' Client connect example '''
config = SliverClientConfig.parse_config_file(CONFIG_PATH)
client = AsyncSliverClient(config)
await client.connect()
async for event in client.on('session-connected'):
print('Automatically interacting with session %s' % event.Session.ID)
interact = await client.interact(event.Session.ID)
exec_result = await interact.execute('whoami', [], True)
print('Exec %r' % exec_result)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
SliverPy should integrate well with any framework that supports asyncio
, but doing so is left
as an exercise for the reader.