diff --git a/.golangci.yml b/.golangci.yml
index 2181a1f02..f74a2edd7 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -9,6 +9,7 @@ run:
- osusergo
- netgo
- go_sqlite
+ - sliver_generate
# Raw implant entry points are Go templates; lint their supporting packages.
- sliver_lint
diff --git a/client/command/ai/model_test.go b/client/command/ai/model_test.go
index a05e476f6..7bf19e3a0 100644
--- a/client/command/ai/model_test.go
+++ b/client/command/ai/model_test.go
@@ -156,13 +156,6 @@ func TestMessageBlockLabelIgnoresUnknownOperatorPlaceholders(t *testing.T) {
}
}
-func TestTranscriptSpeakerStyleUsesPrimaryThemeForUserMessages(t *testing.T) {
- styles := transcriptSpeakerStyle("alice", "user")
-
- assertColorEqual(t, styles.border.GetForeground(), clienttheme.Primary())
- assertColorEqual(t, styles.label.GetBackground(), clienttheme.Primary())
-}
-
func TestThinkingLevelOptionFocusedStyleUsesDarkerPrimaryShade(t *testing.T) {
model := newAIModel(nil, aiContext{}, nil)
@@ -544,19 +537,12 @@ func TestRenderHeaderUsesSingleCompactRow(t *testing.T) {
}
}
-func TestTranscriptSpeakerPaletteVariesAcrossUsers(t *testing.T) {
- seen := map[int]struct{}{}
- for _, label := range []string{"alice", "bob", "charlie", "dana", "erin", "frank"} {
- seen[transcriptSpeakerPaletteIndex(label, "user")] = struct{}{}
- }
- if len(seen) < 2 {
- t.Fatalf("expected distinct users to map to more than one transcript color, got %d palette entries", len(seen))
- }
-}
-
func TestTranscriptSpeakerStyleUsesPrimaryBorderForUserMessages(t *testing.T) {
userStyles := transcriptSpeakerStyle("alice", "user")
- userBorder := transcriptSpeakerStyle("alice", "user").border.Render("│")
+ assertColorEqual(t, userStyles.border.GetForeground(), clienttheme.Primary())
+ assertColorEqual(t, userStyles.label.GetBackground(), clienttheme.Primary())
+
+ userBorder := userStyles.border.Render("│")
wantUserBorder := lipgloss.NewStyle().Foreground(clienttheme.Primary()).Render("│")
if userBorder != wantUserBorder {
t.Fatalf("expected user message border to use theme primary color")
diff --git a/client/command/armory/armory_test.go b/client/command/armory/armory_test.go
deleted file mode 100644
index f99e2fb8e..000000000
--- a/client/command/armory/armory_test.go
+++ /dev/null
@@ -1 +0,0 @@
-package armory
diff --git a/client/command/exec/execute-shellcode_test.go b/client/command/exec/execute-shellcode_test.go
index 7dea3cb42..03a2d5d9a 100644
--- a/client/command/exec/execute-shellcode_test.go
+++ b/client/command/exec/execute-shellcode_test.go
@@ -3,6 +3,8 @@ package exec
import (
"testing"
+ "github.com/bishopfox/sliver/client/console"
+ consts "github.com/bishopfox/sliver/client/constants"
"github.com/spf13/cobra"
)
@@ -87,14 +89,11 @@ func TestShouldConvertExecuteShellcodePE(t *testing.T) {
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
+ for _, cmd := range Commands(&console.SliverClient{}) {
+ if cmd.Name() == consts.ExecuteShellcodeStr {
+ return cmd
+ }
+ }
+ t.Fatalf("missing %q command", consts.ExecuteShellcodeStr)
+ return nil
}
diff --git a/client/console/event_listener_test.go b/client/console/event_listener_test.go
index 618f0190d..0786d4bff 100644
--- a/client/console/event_listener_test.go
+++ b/client/console/event_listener_test.go
@@ -3,57 +3,77 @@ package console
import (
"sync"
"testing"
+ "testing/synctest"
"time"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
func TestRemoveEventListenerUnblocksInFlightDispatchAndClosesReceiver(t *testing.T) {
- con := &SliverClient{EventListeners: &sync.Map{}}
- listenerID, events := con.CreateEventListener()
- value, ok := con.EventListeners.Load(listenerID)
- if !ok {
- t.Fatal("created listener is not registered")
- }
- listener := value.(*consoleEventListener)
- for index := 0; index < cap(listener.events); index++ {
- listener.events <- &clientpb.Event{EventType: "buffered"}
- }
-
- dispatchDone := make(chan struct{})
- go func() {
- listener.send(&clientpb.Event{EventType: "in-flight"})
- close(dispatchDone)
- }()
-
- con.RemoveEventListener(listenerID)
- select {
- case <-dispatchDone:
- case <-time.After(time.Second):
- t.Fatal("listener removal did not unblock in-flight dispatch")
- }
- if _, stillRegistered := con.EventListeners.Load(listenerID); stillRegistered {
- t.Fatal("removed listener remains registered")
- }
-
- for event := range events {
- _ = event
- }
-}
-
-func TestConcurrentEventDispatchAndRemovalDoesNotPanic(_ *testing.T) {
- for iteration := 0; iteration < 1_000; iteration++ {
+ synctest.Test(t, func(t *testing.T) {
con := &SliverClient{EventListeners: &sync.Map{}}
listenerID, events := con.CreateEventListener()
+ value, ok := con.EventListeners.Load(listenerID)
+ if !ok {
+ t.Fatal("created listener is not registered")
+ }
+ listener := value.(*consoleEventListener)
+ buffered := &clientpb.Event{EventType: "buffered"}
+ for index := 0; index < cap(listener.events); index++ {
+ listener.events <- buffered
+ }
+
dispatchDone := make(chan struct{})
go func() {
- con.triggerEventListeners(&clientpb.Event{EventType: "race"})
+ event := &clientpb.Event{EventType: "in-flight"}
+ con.triggerEventListeners(event)
close(dispatchDone)
}()
- con.RemoveEventListener(listenerID)
- <-dispatchDone
- for event := range events {
- _ = event
+
+ // The full buffer and Wait establish that send is blocked before removal.
+ synctest.Wait()
+ select {
+ case <-dispatchDone:
+ t.Fatal("dispatch completed before listener removal")
+ default:
}
- }
+
+ removeDone := make(chan struct{})
+ go func() {
+ con.RemoveEventListener(listenerID)
+ // Dispatch and repeated removal after closure must remain harmless.
+ con.triggerEventListeners(&clientpb.Event{EventType: "removed"})
+ listener.send(&clientpb.Event{EventType: "closed"})
+ con.RemoveEventListener(listenerID)
+ close(removeDone)
+ }()
+ for _, done := range []<-chan struct{}{dispatchDone, removeDone} {
+ select {
+ case <-done:
+ case <-time.After(time.Second):
+ t.Fatal("listener removal and in-flight dispatch did not complete")
+ }
+ }
+ if _, stillRegistered := con.EventListeners.Load(listenerID); stillRegistered {
+ t.Fatal("removed listener remains registered")
+ }
+
+ deadline := time.After(time.Second)
+ for received := 0; ; received++ {
+ select {
+ case event, open := <-events:
+ if !open {
+ if received != cap(listener.events) {
+ t.Fatalf("received %d buffered events, want %d", received, cap(listener.events))
+ }
+ return
+ }
+ if event != buffered {
+ t.Fatalf("unexpected event after removal: %v", event)
+ }
+ case <-deadline:
+ t.Fatal("removed listener receiver did not close")
+ }
+ }
+ })
}
diff --git a/client/transport/mtls_test.go b/client/transport/mtls_test.go
index 1af40799c..6bde6355d 100644
--- a/client/transport/mtls_test.go
+++ b/client/transport/mtls_test.go
@@ -16,113 +16,103 @@ func TestMultiplayerConnectModeDefaultsToAuto(t *testing.T) {
}
}
-func TestSelectMultiplayerDialStrategyAutoWithoutWGConfigUsesDirectMTLS(t *testing.T) {
- setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
-
- strategy, err := selectMultiplayerDialStrategy(&assets.ClientConfig{})
- if err != nil {
- t.Fatalf("select dial strategy: %v", err)
- }
- if strategy != multiplayerDialDirect {
- t.Fatalf("expected direct mTLS strategy, got %v", strategy)
- }
-}
-
-func TestSelectMultiplayerDialStrategyAutoLegacyWGConfigUsesDirectMTLS(t *testing.T) {
- setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
-
- strategy, err := selectMultiplayerDialStrategy(completeWGClientConfig(false))
- if err != nil {
- t.Fatalf("select dial strategy: %v", err)
- }
- if strategy != multiplayerDialDirect {
- t.Fatalf("expected direct mTLS strategy, got %v", strategy)
- }
-}
-
-func TestSelectMultiplayerDialStrategyAutoEnabledWGConfigRequiresCompleteConfig(t *testing.T) {
- setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
-
- _, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
- WG: &assets.ClientWGConfig{
- Enabled: true,
- ServerPubKey: "server-pub",
- ClientIP: "100.64.0.2",
+func TestSelectMultiplayerDialStrategy(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ mode MultiplayerConnectMode
+ config *assets.ClientConfig
+ want multiplayerDialStrategy
+ wantErr error
+ wantErrText string
+ }{
+ {
+ name: "auto without WireGuard config",
+ mode: MultiplayerConnectAuto,
+ config: &assets.ClientConfig{},
+ want: multiplayerDialDirect,
},
- })
- if !errors.Is(err, ErrIncompleteWireGuardConfig) {
- t.Fatalf("expected incomplete WG config error, got %v", err)
- }
-}
-
-func TestSelectMultiplayerDialStrategyAutoEnabledWGConfigUsesWireGuard(t *testing.T) {
- setTestMultiplayerConnectMode(t, MultiplayerConnectAuto)
-
- strategy, err := selectMultiplayerDialStrategy(completeWGClientConfig(true))
- if err != nil {
- t.Fatalf("select dial strategy: %v", err)
- }
- if strategy != multiplayerDialWireGuard {
- t.Fatalf("expected WireGuard strategy, got %v", strategy)
- }
-}
-
-func TestSelectMultiplayerDialStrategyEnableWGRejectsMissingWGConfig(t *testing.T) {
- setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
-
- _, err := selectMultiplayerDialStrategy(&assets.ClientConfig{})
- if !errors.Is(err, ErrMissingWireGuardConfig) {
- t.Fatalf("expected missing WG config error, got %v", err)
- }
-}
-
-func TestSelectMultiplayerDialStrategyEnableWGRejectsIncompleteWGConfig(t *testing.T) {
- setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
-
- _, err := selectMultiplayerDialStrategy(&assets.ClientConfig{
- WG: &assets.ClientWGConfig{
- ServerPubKey: "server-pub",
- ClientIP: "100.64.0.2",
+ {
+ name: "auto with legacy WireGuard config",
+ mode: MultiplayerConnectAuto,
+ config: completeWGClientConfig(false),
+ want: multiplayerDialDirect,
},
- })
- if !errors.Is(err, ErrIncompleteWireGuardConfig) {
- t.Fatalf("expected incomplete WG config error, got %v", err)
- }
-}
-
-func TestSelectMultiplayerDialStrategyEnableWGUsesCompleteLegacyConfig(t *testing.T) {
- setTestMultiplayerConnectMode(t, MultiplayerConnectEnableWG)
-
- strategy, err := selectMultiplayerDialStrategy(completeWGClientConfig(false))
- if err != nil {
- t.Fatalf("select dial strategy: %v", err)
- }
- if strategy != multiplayerDialWireGuard {
- t.Fatalf("expected WireGuard strategy, got %v", strategy)
- }
-}
-
-func TestSelectMultiplayerDialStrategyDisableWGOverridesEnabledConfig(t *testing.T) {
- setTestMultiplayerConnectMode(t, MultiplayerConnectDisableWG)
-
- strategy, err := selectMultiplayerDialStrategy(completeWGClientConfig(true))
- if err != nil {
- t.Fatalf("select dial strategy: %v", err)
- }
- if strategy != multiplayerDialDirect {
- t.Fatalf("expected direct strategy, got %v", strategy)
- }
-}
-
-func TestSelectMultiplayerDialStrategyRejectsInvalidMode(t *testing.T) {
- setTestMultiplayerConnectMode(t, MultiplayerConnectMode(255))
-
- strategy, err := selectMultiplayerDialStrategy(completeWGClientConfig(true))
- if strategy != multiplayerDialDirect {
- t.Fatalf("expected invalid mode to fall back to direct strategy, got %v", strategy)
- }
- if err == nil || err.Error() != "invalid multiplayer connect mode 255" {
- t.Fatalf("expected invalid mode error, got %v", err)
+ {
+ name: "auto with incomplete enabled config",
+ mode: MultiplayerConnectAuto,
+ config: &assets.ClientConfig{WG: &assets.ClientWGConfig{
+ Enabled: true, ServerPubKey: "server-pub", ClientIP: "100.64.0.2",
+ }},
+ want: multiplayerDialDirect,
+ wantErr: ErrIncompleteWireGuardConfig,
+ },
+ {
+ name: "auto with enabled WireGuard config",
+ mode: MultiplayerConnectAuto,
+ config: completeWGClientConfig(true),
+ want: multiplayerDialWireGuard,
+ },
+ {
+ name: "enable with missing WireGuard config",
+ mode: MultiplayerConnectEnableWG,
+ config: &assets.ClientConfig{},
+ want: multiplayerDialDirect,
+ wantErr: ErrMissingWireGuardConfig,
+ },
+ {
+ name: "enable with incomplete WireGuard config",
+ mode: MultiplayerConnectEnableWG,
+ config: &assets.ClientConfig{WG: &assets.ClientWGConfig{
+ ServerPubKey: "server-pub", ClientIP: "100.64.0.2",
+ }},
+ want: multiplayerDialDirect,
+ wantErr: ErrIncompleteWireGuardConfig,
+ },
+ {
+ name: "enable with complete legacy config",
+ mode: MultiplayerConnectEnableWG,
+ config: completeWGClientConfig(false),
+ want: multiplayerDialWireGuard,
+ },
+ {
+ name: "disable overrides enabled config",
+ mode: MultiplayerConnectDisableWG,
+ config: completeWGClientConfig(true),
+ want: multiplayerDialDirect,
+ },
+ {
+ name: "invalid mode",
+ mode: MultiplayerConnectMode(255),
+ config: completeWGClientConfig(true),
+ want: multiplayerDialDirect,
+ wantErrText: "invalid multiplayer connect mode 255",
+ },
+ {
+ name: "nil config",
+ mode: MultiplayerConnectAuto,
+ want: multiplayerDialDirect,
+ wantErrText: "client config is required",
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ setTestMultiplayerConnectMode(t, test.mode)
+ strategy, err := selectMultiplayerDialStrategy(test.config)
+ if strategy != test.want {
+ t.Errorf("dial strategy = %v, want %v", strategy, test.want)
+ }
+ switch {
+ case test.wantErr != nil:
+ if !errors.Is(err, test.wantErr) {
+ t.Fatalf("select dial strategy error = %v, want %v", err, test.wantErr)
+ }
+ case test.wantErrText != "":
+ if err == nil || err.Error() != test.wantErrText {
+ t.Fatalf("select dial strategy error = %v, want %q", err, test.wantErrText)
+ }
+ case err != nil:
+ t.Fatalf("select dial strategy: %v", err)
+ }
+ })
}
}
diff --git a/go-tests.ps1 b/go-tests.ps1
index 31b4e2ffd..269f215a9 100644
--- a/go-tests.ps1
+++ b/go-tests.ps1
@@ -280,7 +280,6 @@ try {
$pkg = if ($testDir.StartsWith(".")) { $testDir } else { "./$testDir" }
switch -Wildcard ($pkg) {
"./server/c2" { continue }
- "./server/generate" { continue }
"./client/*" {
$clientTestPkgs += $pkg
continue
@@ -359,6 +358,7 @@ try {
}
}
+ # Includes generation helpers without the sliver_generate compilation tests.
foreach ($pkg in $serverUtilTestPkgs) {
if (Test-SkipPackage -Package $pkg -Tags "server,$tags") {
continue
@@ -421,7 +421,7 @@ try {
Invoke-TestStep -Name "./server/generate" -Action {
Invoke-ExternalCommand -FilePath "go" `
- -Arguments @("test", "-timeout", "6h", "-p", $generateGoP, "-parallel", $generateTestParallel, "-tags=server,$tags", "./server/generate") `
+ -Arguments @("test", "-timeout", "6h", "-p", $generateGoP, "-parallel", $generateTestParallel, "-tags=server,$tags,sliver_generate", "./server/generate") `
-Environment @{
GOMAXPROCS = $generateGoMaxProcs
GOPROXY = "off"
@@ -430,9 +430,9 @@ try {
} else {
Write-Host ""
if ($UnitOnly) {
- Write-Host "Skipping ./server/generate tests (--unit-only)"
+ Write-Host "Skipping ./server/generate compilation tests (--unit-only)"
} else {
- Write-Host "Skipping ./server/generate tests (--skip-generate)"
+ Write-Host "Skipping ./server/generate compilation tests (--skip-generate)"
}
}
} finally {
diff --git a/go-tests.sh b/go-tests.sh
index 64f76f802..1a4a03781 100755
--- a/go-tests.sh
+++ b/go-tests.sh
@@ -34,8 +34,8 @@ usage() {
cat <<'EOF'
Usage: ./go-tests.sh [--skip-generate | --unit-only | --generate-only] [--run REGEX]
- --skip-generate Skip implant generation tests.
- --unit-only Skip implant generation and C2 end-to-end tests.
+ --skip-generate Skip implant compilation tests.
+ --unit-only Skip implant compilation and C2 end-to-end tests.
--generate-only Run only the server/generate package tests.
--run REGEX Select generation tests by Go test name regex; requires --generate-only.
--help Show this help.
@@ -240,7 +240,7 @@ run_generate_tests() {
local generate_go_p="${SLIVER_GENERATE_GO_P:-1}"
local generate_test_parallel="${SLIVER_GENERATE_TEST_PARALLEL:-1}"
local generate_test_cmd=(
- "$HOST_GO" test -timeout 6h -p "$generate_go_p" -parallel "$generate_test_parallel" -tags="server,$TAGS"
+ "$HOST_GO" test -timeout 6h -p "$generate_go_p" -parallel "$generate_test_parallel" -tags="server,$TAGS,sliver_generate"
)
if [ -n "$GENERATE_RUN" ]; then
# Keep test selection out of GOFLAGS, which implant compiler children inherit.
@@ -272,7 +272,7 @@ E2E_SUPPORT_TEST_PKGS=()
for test_dir in "${TEST_DIRS[@]}"; do
pkg="./$test_dir"
case "$pkg" in
- ./server/c2 | ./server/generate)
+ ./server/c2)
# handled separately below
;;
./client/*)
@@ -330,7 +330,7 @@ for pkg in "${IMPLANT_TEST_PKGS[@]}"; do
run_test_cmd "$pkg" "$HOST_GO" test "$pkg" || exit 1
done
-## Server + Util
+## Server + Util, including generation helpers without compilation tests.
for pkg in "${SERVER_UTIL_TEST_PKGS[@]}"; do
if should_skip_package "$pkg" "server,$TAGS"; then
continue
@@ -358,14 +358,14 @@ else
run_test_cmd "./server/c2 (e2e dns)" "$HOST_GO" test -tags="server,$TAGS,sliver_e2e" ./server/c2 -run 'TestDNS_' -count=1 || exit 1
fi
-## Server generate
+## Implant compilation tests opt in with sliver_generate; helper tests run above.
if [ "$SKIP_GENERATE" -eq 0 ]; then
run_generate_tests || exit 1
else
echo
if [ "$UNIT_ONLY" -eq 1 ]; then
- echo "Skipping ./server/generate tests (--unit-only)"
+ echo "Skipping ./server/generate compilation tests (--unit-only)"
else
- echo "Skipping ./server/generate tests (--skip-generate)"
+ echo "Skipping ./server/generate compilation tests (--skip-generate)"
fi
fi
diff --git a/implant/sliver/extension/memfs_test.go b/implant/sliver/extension/memfs_test.go
index 8991e73ff..251b3fc7e 100644
--- a/implant/sliver/extension/memfs_test.go
+++ b/implant/sliver/extension/memfs_test.go
@@ -19,6 +19,8 @@ package extension
*/
import (
+ "bytes"
+ "io"
"io/fs"
"testing"
)
@@ -33,7 +35,11 @@ func TestBasicMemFSOpenFile(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if stat, _ := fi.Stat(); stat.Name() != "memfs/test.1" {
+ stat, err := fi.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if stat.Name() != "memfs/test.1" {
t.Fatalf("expected memfs/test.1, got %s", stat.Name())
}
defer fi.Close()
@@ -58,20 +64,24 @@ func TestMemFSOpenRoot(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if len(entries) != 3 {
- for entry := range entries {
- t.Logf("entry: %s", entries[entry].Name())
+ wantEntries := map[string]bool{"memfs/test.1": false, "memfs/test.2": false, "memfs/a": true}
+ if len(entries) != len(wantEntries) {
+ t.Fatalf("expected %d entries, got %d", len(wantEntries), len(entries))
+ }
+ for _, entry := range entries {
+ wantDir, ok := wantEntries[entry.Name()]
+ if !ok || entry.IsDir() != wantDir {
+ t.Fatalf("unexpected entry %q (directory=%v)", entry.Name(), entry.IsDir())
}
- t.Fatalf("expected 3 entries, got %d", len(entries))
+ delete(wantEntries, entry.Name())
}
di, err := dirNode.Stat()
if err != nil {
t.Fatal(err)
}
- t.Logf("name: %s, is dir: %v", di.Name(), di.IsDir())
- for _, entry := range entries {
- t.Logf("entry: %s", entry.Name())
+ if di.Name() != "memfs" || !di.IsDir() {
+ t.Fatalf("expected memfs root directory, got %q (directory=%v)", di.Name(), di.IsDir())
}
}
@@ -80,7 +90,7 @@ func TestBasicMemFSOpenDir(t *testing.T) {
"test/foo.1": {0x00},
"test/foo": {0x00},
"testing/foo": {0x00},
- "a/b/c/d/e/f/test/a.txt": {0x00},
+ "a/b/c/d/e/f/test/a.txt": {0x31, 0x00, 0x7f},
}}
// Test Open File
@@ -91,7 +101,11 @@ func TestBasicMemFSOpenDir(t *testing.T) {
if f == nil {
t.Fatal("expected file, got nil and no error")
}
- if stat, _ := f.Stat(); stat.Name() != "memfs/test" || !stat.IsDir() {
+ stat, err := f.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if stat.Name() != "memfs/test" || !stat.IsDir() {
t.Fatalf("expected 'memfs/test' dir, got '%s'", stat.Name())
}
defer f.Close()
@@ -100,7 +114,16 @@ func TestBasicMemFSOpenDir(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if stat, _ := dir.Stat(); stat.Name() != "memfs/a/b/c/d/e/f/test" || !stat.IsDir() {
+ defer func() {
+ if err := dir.Close(); err != nil {
+ t.Errorf("close directory: %v", err)
+ }
+ }()
+ stat, err = dir.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if stat.Name() != "memfs/a/b/c/d/e/f/test" || !stat.IsDir() {
t.Fatalf("expected 'memfs/a/b/c/d/e/f/test' dir, got '%s'", stat.Name())
}
@@ -108,19 +131,17 @@ func TestBasicMemFSOpenDir(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- data := make([]byte, 1)
- _, err = f.Read(data)
- if err != nil {
- t.Fatal(err)
- }
- if data[0] != 0x00 {
- t.Fatalf("expected 0x00, got %x", data[0])
- }
+ defer func() {
+ if err := f.Close(); err != nil {
+ t.Errorf("close file: %v", err)
+ }
+ }()
+ assertMemFSFileContents(t, f, []byte{0x31, 0x00, 0x7f})
}
func TestMemFSReadFileData(t *testing.T) {
wasmFS := WasmMemoryFS{memFS: map[string][]byte{
- "test/foo.1": {0x00},
+ "test/foo.1": {0x41, 0x00, 0xff, 0x42},
}}
// Test Open File
@@ -128,19 +149,31 @@ func TestMemFSReadFileData(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if stat, _ := fi.Stat(); stat.Name() != "memfs/test/foo.1" {
+ stat, err := fi.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if stat.Name() != "memfs/test/foo.1" {
t.Fatalf("expected memfs/test/foo.1, got %s", stat.Name())
}
defer fi.Close()
- // Read file
- data := make([]byte, 1)
- _, err = fi.Read(data)
- if err != nil {
- t.Fatal(err)
+ assertMemFSFileContents(t, fi, []byte{0x41, 0x00, 0xff, 0x42})
+}
+
+func assertMemFSFileContents(t *testing.T, file fs.File, want []byte) {
+ t.Helper()
+ // Read in chunks to verify byte counts and advancement of the file offset.
+ for offset := 0; offset < len(want); {
+ end := min(offset+2, len(want))
+ data := make([]byte, end-offset)
+ n, err := file.Read(data)
+ if err != nil || n != len(data) || !bytes.Equal(data, want[offset:end]) {
+ t.Fatalf("Read at %d = (%x, %d, %v), want (%x, %d, nil)", offset, data, n, err, want[offset:end], len(data))
+ }
+ offset = end
}
- if data[0] != 0x00 {
- t.Fatalf("expected 0x00, got %x", data[0])
+ if n, err := file.Read(make([]byte, 1)); n != 0 || err != io.EOF {
+ t.Fatalf("Read after file contents = (%d, %v), want (0, EOF)", n, err)
}
-
}
diff --git a/server/assets/traffic-encoders/traffic-encoder_test.go b/server/assets/traffic-encoders/traffic-encoder_test.go
index 329236e1a..4b77d4b31 100644
--- a/server/assets/traffic-encoders/traffic-encoder_test.go
+++ b/server/assets/traffic-encoders/traffic-encoder_test.go
@@ -20,11 +20,10 @@ package traffic_encoders
import (
"bytes"
- "crypto/rand"
_ "embed"
"encoding/hex"
+ "fmt"
"testing"
- "time"
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders/traffic"
serverEncoders "github.com/bishopfox/sliver/util/encoders/traffic"
@@ -34,102 +33,124 @@ import (
var hexWASM []byte
func TestTrafficEncoderCompatibility_hex(t *testing.T) {
-
- // Hex
-
implantSideHex, err := implantEncoders.CreateTrafficEncoder("hex", hexWASM, func(msg string) {
t.Log(msg)
})
if err != nil {
t.Fatal(err)
}
+ t.Cleanup(func() {
+ if err := implantSideHex.Close(); err != nil {
+ t.Errorf("close implant encoder: %v", err)
+ }
+ })
serverSideHex, err := serverEncoders.CreateTrafficEncoder("hex", hexWASM, func(msg string) {
t.Log(msg)
})
if err != nil {
t.Fatal(err)
}
-
- data := make([]byte, 1024)
- _, err = rand.Read(data)
- if err != nil {
- t.Fatal(err)
- }
- encodedData, err := implantSideHex.Encode(data)
- if err != nil {
- t.Fatal(err)
- }
- decodedData, err := serverSideHex.Decode(encodedData)
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(data, decodedData) {
- t.Fatal("Decoded data does not match original")
- }
-
- data = make([]byte, 1024)
- _, err = rand.Read(data)
- if err != nil {
- t.Fatal(err)
- }
- encodedData, err = serverSideHex.Encode(data)
- if err != nil {
- t.Fatal(err)
- }
- decodedData, err = implantSideHex.Decode(encodedData)
- if err != nil {
- t.Fatal(err)
- }
- if !bytes.Equal(data, decodedData) {
- t.Fatal("Decoded data does not match original")
- }
-}
-
-// Encoder specific tests
-func TestHexPerformance(t *testing.T) {
-
- sizes := []int{1024, 1024 * 1024, 2 * 1024 * 1024, 4 * 1024 * 1024}
-
- // Stock encoder
- for i := 0; i < len(sizes); i++ {
- originalValue := make([]byte, sizes[i])
- rand.Read(originalValue)
- stock := time.Now()
- encodedValue := hex.EncodeToString(originalValue)
- decodedValue, err := hex.DecodeString(encodedValue)
- if err != nil {
- t.Fatal(err)
+ t.Cleanup(func() {
+ if err := serverSideHex.Close(); err != nil {
+ t.Errorf("close server encoder: %v", err)
}
- t.Logf("Stock encoder took %v (%d bytes)", time.Since(stock), sizes[i])
- if !bytes.Equal(originalValue, decodedValue) {
- t.Fatalf("Expected %v but got %v", originalValue, decodedValue)
- }
- }
+ })
- // Traffic encoder
- for i := 0; i < len(sizes); i++ {
- encoder, err := serverEncoders.CreateTrafficEncoder("hex", hexWASM, func(msg string) {
- t.Log(msg)
+ directions := []struct {
+ name string
+ encode func([]byte) ([]byte, error)
+ decode func([]byte) ([]byte, error)
+ }{
+ {"implant_to_server", implantSideHex.Encode, serverSideHex.Decode},
+ {"server_to_implant", serverSideHex.Encode, implantSideHex.Decode},
+ }
+ for _, direction := range directions {
+ t.Run(direction.name, func(t *testing.T) {
+ // Exercise empty and binary inputs, both sides of a 64 KiB WASM
+ // page boundary, and the largest payload from the old timing test.
+ for _, size := range []int{0, 1, 256, 65535, 65536, 65537, 4 * 1024 * 1024} {
+ t.Run(fmt.Sprintf("bytes_%d", size), func(t *testing.T) {
+ payload := hexTestPayload(size)
+ encoded, err := direction.encode(payload)
+ if err != nil {
+ t.Fatalf("encode: %v", err)
+ }
+ if !bytes.Equal(encoded, []byte(hex.EncodeToString(payload))) {
+ t.Fatal("WASM output does not match standard hex encoding")
+ }
+ decoded, err := direction.decode(encoded)
+ if err != nil {
+ t.Fatalf("decode: %v", err)
+ }
+ if !bytes.Equal(payload, decoded) {
+ t.Fatalf("round trip changed the %d-byte payload (decoded %d bytes)", len(payload), len(decoded))
+ }
+ })
+ }
})
- if err != nil {
- t.Fatal(err)
- }
- defer encoder.Close()
- originalValue := make([]byte, sizes[i])
- rand.Read(originalValue)
- start := time.Now()
- encodedValue, err := encoder.Encode(originalValue)
- if err != nil {
- t.Fatal(err)
- }
- // t.Logf("Got encoded value (%d bytes)", len(encodedValue))
- decodedValue, err := encoder.Decode(encodedValue)
- if err != nil {
- t.Fatal(err)
- }
- t.Logf("WASM Hex encoder took %v (%d bytes)", time.Since(start), sizes[i])
- if !bytes.Equal(originalValue, decodedValue) {
- t.Fatalf("Expected %v but got %v", originalValue, decodedValue)
- }
}
}
+
+func BenchmarkHex(b *testing.B) {
+ for _, size := range []int{1024, 1024 * 1024, 2 * 1024 * 1024, 4 * 1024 * 1024} {
+ payload := hexTestPayload(size)
+ b.Run(fmt.Sprintf("bytes_%d", size), func(b *testing.B) {
+ b.Run("standard_round_trip", func(b *testing.B) {
+ b.SetBytes(int64(size))
+ b.ReportAllocs()
+ for b.Loop() {
+ encoded := hex.EncodeToString(payload)
+ decoded, err := hex.DecodeString(encoded)
+ if err != nil {
+ b.Fatal(err)
+ }
+ if !bytes.Equal(payload, decoded) {
+ b.Fatal("decoded data does not match original")
+ }
+ }
+ })
+ b.Run("wasm_fresh_runtime_round_trip", func(b *testing.B) {
+ b.SetBytes(int64(size))
+ b.ReportAllocs()
+ // Include runtime creation and close in each iteration. The WASM
+ // API retains output buffers until the runtime is closed, so an
+ // unbounded loop over one runtime would accumulate guest memory.
+ for b.Loop() {
+ benchmarkHexWASMLifecycle(b, payload)
+ }
+ })
+ })
+ }
+}
+
+func benchmarkHexWASMLifecycle(b *testing.B, payload []byte) {
+ b.Helper()
+ encoder, err := serverEncoders.CreateTrafficEncoder("hex", hexWASM, func(string) {})
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer func() {
+ if err := encoder.Close(); err != nil {
+ b.Errorf("close encoder: %v", err)
+ }
+ }()
+ encoded, err := encoder.Encode(payload)
+ if err != nil {
+ b.Fatal(err)
+ }
+ decoded, err := encoder.Decode(encoded)
+ if err != nil {
+ b.Fatal(err)
+ }
+ if !bytes.Equal(payload, decoded) {
+ b.Fatal("decoded data does not match original")
+ }
+}
+
+func hexTestPayload(size int) []byte {
+ payload := make([]byte, size)
+ for i := range payload {
+ payload[i] = byte(i)
+ }
+ return payload
+}
diff --git a/server/c2/http_test.go b/server/c2/http_test.go
deleted file mode 100644
index 37d7eb906..000000000
--- a/server/c2/http_test.go
+++ /dev/null
@@ -1,173 +0,0 @@
-package c2
-
-/*
- Sliver Implant Framework
- Copyright (C) 2021 Bishop Fox
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-
-// import (
-// "bytes"
-// "fmt"
-// insecureRand "math/rand"
-// "net/http"
-// "net/http/httptest"
-// "net/url"
-// "testing"
-
-// implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
-// implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
-// implantTransports "github.com/bishopfox/sliver/implant/sliver/transports/httpclient"
-// "github.com/bishopfox/sliver/protobuf/sliverpb"
-// "github.com/bishopfox/sliver/server/configs"
-// "github.com/bishopfox/sliver/server/cryptography"
-// "google.golang.org/protobuf/proto"
-// )
-
-// func TestStartSessionHandler(t *testing.T) {
-
-// implantTransports.SetNonceQueryArgChars("abcdedfghijklmnopqrstuvwxyz")
-
-// server, err := StartHTTPListener(&HTTPServerConfig{
-// Addr: "127.0.0.1:8888",
-// Secure: false,
-// EnforceOTP: true,
-// })
-// if err != nil {
-// t.Fatalf("Listener failed to start %s", err)
-// return
-// }
-
-// client := implantTransports.SliverHTTPClient{}
-// baseURL := &url.URL{
-// Scheme: "http",
-// Host: "127.0.0.1:8888",
-// Path: fmt.Sprintf("/test/foo.%s", c2Config.ImplantConfig.StartSessionFileExt),
-// }
-// nonce, encoder := implantEncoders.RandomEncoder(0)
-// testURL := client.NonceQueryArgument(baseURL, nonce)
-// testURL = client.OTPQueryArgument(testURL, implantCrypto.GetOTPCode())
-
-// // Generate key exchange request
-// sKey := cryptography.RandomKey()
-// httpSessionInit := &sliverpb.HTTPSessionInit{Key: sKey[:]}
-// data, _ := proto.Marshal(httpSessionInit)
-// encryptedSessionInit, err := implantCrypto.ECCEncryptToServer(data)
-// if err != nil {
-// t.Fatalf("Failed to encrypt session init %s", err)
-// }
-// payload, _ := encoder.Encode(encryptedSessionInit)
-// body := bytes.NewReader(payload)
-
-// validReq := httptest.NewRequest(http.MethodPost, testURL.String(), body)
-// t.Logf("[http] req request uri: '%v'", validReq.RequestURI)
-// rr := httptest.NewRecorder()
-// server.HTTPServer.Handler.ServeHTTP(rr, validReq)
-// if status := rr.Code; status != http.StatusOK {
-// t.Fatalf("handler returned wrong status code: got %d want %d", status, http.StatusOK)
-// }
-
-// }
-
-// func TestGetOTPFromURL(t *testing.T) {
-
-// implantTransports.SetNonceQueryArgChars("abcdedfghijklmnopqrstuvwxyz")
-
-// client := implantTransports.SliverHTTPClient{}
-// for i := 0; i < 100; i++ {
-// baseURL := &url.URL{
-// Scheme: "http",
-// Host: "127.0.0.1:8888",
-// Path: "/test/foo.txt",
-// }
-// value := fmt.Sprintf("%d", insecureRand.Intn(99999999))
-// testURL := client.OTPQueryArgument(baseURL, value)
-// urlValue, err := getOTPFromURL(testURL)
-// if err != nil {
-// t.Fatal(err)
-// }
-// if urlValue != value {
-// t.Fatalf("Mismatched OTP values %s (%s != %s)", testURL.String(), value, urlValue)
-// }
-// }
-// }
-
-// func TestGetNonceFromURL(t *testing.T) {
-
-// implantTransports.SetNonceQueryArgChars("abcdedfghijklmnopqrstuvwxyz")
-
-// client := implantTransports.SliverHTTPClient{}
-// for i := 0; i < 100; i++ {
-// baseURL := &url.URL{
-// Scheme: "http",
-// Host: "127.0.0.1:8888",
-// Path: "/test/foo.txt",
-// }
-// nonce, encoder := implantEncoders.RandomEncoder(0)
-// testURL := client.NonceQueryArgument(baseURL, nonce)
-// t.Log(testURL.String())
-// urlNonce, err := getNonceFromURL(testURL)
-// if err != nil {
-// t.Errorf("Nonce '%d' triggered error from %#v", nonce, encoder)
-// t.Fatal(err)
-// }
-// if urlNonce != nonce {
-// t.Fatalf("Mismatched encoder nonces %s (%d != %d)", testURL.String(), nonce, urlNonce)
-// }
-// }
-// }
-
-// func TestGetNonceFromURLWithCustomQueryArgs(t *testing.T) {
-// for j := 0; j < 100; j++ {
-
-// queryArgs := randomArgs(1)
-// implantTransports.SetNonceQueryArgChars(queryArgs)
-// t.Logf("Using query args: %s", queryArgs)
-
-// client := implantTransports.SliverHTTPClient{}
-// for i := 0; i < 10; i++ {
-// baseURL := &url.URL{
-// Scheme: "http",
-// Host: "127.0.0.1:8888",
-// Path: "/test/foo.txt",
-// }
-// nonce, encoder := implantEncoders.RandomEncoder(0)
-// testURL := client.NonceQueryArgument(baseURL, nonce)
-// t.Log(testURL.String())
-// urlNonce, err := getNonceFromURL(testURL)
-// if err != nil {
-// t.Errorf("Nonce '%d' triggered error from %#v", nonce, encoder)
-// t.Fatal(err)
-// }
-// if urlNonce != nonce {
-// t.Fatalf("Mismatched encoder nonces %s (%d != %d)", testURL.String(), nonce, urlNonce)
-// }
-// }
-// }
-// }
-
-// func randomArgs(min int) string {
-// var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()")
-// size := insecureRand.Intn(25) + min
-// b := make(map[rune]interface{}, size)
-// for len(b) <= size {
-// b[letters[insecureRand.Intn(len(letters))]] = nil
-// }
-// var keys string
-// for k := range b {
-// keys += string(k)
-// }
-// return keys
-// }
diff --git a/server/core/rtunnels/authorization_test.go b/server/core/rtunnels/authorization_test.go
index feff0058e..5021a6265 100644
--- a/server/core/rtunnels/authorization_test.go
+++ b/server/core/rtunnels/authorization_test.go
@@ -465,16 +465,48 @@ func TestRegistryRevokeSessionIsScopedAndIdempotent(t *testing.T) {
func TestRegistryGeneratesUniqueOpaqueIDs(t *testing.T) {
t.Parallel()
- registry := NewRegistry()
- seen := map[AuthorizationID]struct{}{}
- for index := 0; index < 256; index++ {
- id, err := registry.Begin("session", "127.0.0.1:8080", 0)
- must.NoError(t, err)
- assert.Len(t, id, 43)
- assert.False(t, strings.ContainsAny(id.String(), "+/="))
- _, duplicate := seen[id]
- assert.False(t, duplicate)
- seen[id] = struct{}{}
+ firstBytes := bytes.Repeat([]byte{0xff}, authorizationIDBytes)
+ secondBytes := bytes.Repeat([]byte{0x42}, authorizationIDBytes)
+ for _, test := range []struct {
+ name string
+ random []byte
+ wantError bool
+ wantRemaining int
+ }{
+ {name: "distinct IDs", random: secondBytes},
+ {name: "retry collision", random: bytes.Join([][]byte{firstBytes, secondBytes}, nil)},
+ {
+ name: "exhaust collisions",
+ random: bytes.Join([][]byte{bytes.Repeat(firstBytes, maxAuthorizationIDAttempts), secondBytes}, nil),
+ wantError: true,
+ wantRemaining: authorizationIDBytes,
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ random := bytes.NewReader(bytes.Join([][]byte{firstBytes, test.random}, nil))
+ registry := newRegistry(random)
+ firstID, err := registry.Begin("session", "127.0.0.1:8080", 0)
+ must.NoError(t, err)
+ assert.Len(t, firstID, 43)
+ assert.False(t, strings.ContainsAny(firstID.String(), "+/="))
+
+ secondID, err := registry.Begin("session", "127.0.0.1:8081", 0)
+ if test.wantError {
+ must.ErrorIs(t, err, ErrAuthorizationIDGeneration)
+ assert.Empty(t, secondID)
+ assert.Len(t, registry.List("session"), 1)
+ } else {
+ must.NoError(t, err)
+ assert.NotEqual(t, firstID, secondID)
+ assert.Len(t, secondID, 43)
+ assert.False(t, strings.ContainsAny(secondID.String(), "+/="))
+ assert.Len(t, registry.List("session"), 2)
+ }
+ assert.Equal(t, test.wantRemaining, random.Len())
+ first, ok := registry.Lookup("session", firstID)
+ must.True(t, ok)
+ assert.Equal(t, "127.0.0.1:8080", first.Address)
+ })
}
}
@@ -484,13 +516,6 @@ func (errorReader) Read(_ []byte) (int, error) {
return 0, io.ErrUnexpectedEOF
}
-func TestAuthorizationErrorsAreComparable(t *testing.T) {
- t.Parallel()
-
- _, err := canonicalizeAddress("invalid")
- assert.True(t, errors.Is(err, ErrInvalidForwardAddress))
-}
-
func acquirePlanForTest(registry *Registry, sessionID string, id AuthorizationID) (dialPlan, error) {
plan, _, reservation, err := registry.reserve(sessionID, id, "")
if err == nil {
diff --git a/server/cryptography/cryptography_test.go b/server/cryptography/cryptography_test.go
index ba25d44b5..99f329f21 100644
--- a/server/cryptography/cryptography_test.go
+++ b/server/cryptography/cryptography_test.go
@@ -134,26 +134,27 @@ func TestAgeKeyExTamper(t *testing.T) {
t.Fatal(err)
}
- // Tamper with the ciphertext
- ciphertext := allCiphertext[32:]
- ciphertext[insecureRand.Intn(len(ciphertext))] ^= 0xFF
- _, err = AgeKeyExFromImplant(
- serverAgeKeyPair.Private,
- implantPeerAgeKeyPair.Private,
- ciphertext,
- )
- if err == nil {
- t.Fatal(err)
- }
+ tamperedCiphertext := bytes.Clone(allCiphertext[32:])
+ tamperedCiphertext[len(tamperedCiphertext)-1] ^= 0xFF
- // Leave an invalid header with valid ciphertext
- _, err = AgeKeyExFromImplant(
- serverAgeKeyPair.Private,
- implantPeerAgeKeyPair.Private,
- allCiphertext,
- )
- if err == nil {
- t.Fatal(err)
+ for _, test := range []struct {
+ name string
+ ciphertext []byte
+ }{
+ {name: "tampered ciphertext", ciphertext: tamperedCiphertext},
+ // Retaining the public key hash adds an invalid header to otherwise valid ciphertext.
+ {name: "invalid header", ciphertext: bytes.Clone(allCiphertext)},
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ _, err := AgeKeyExFromImplant(
+ serverAgeKeyPair.Private,
+ implantPeerAgeKeyPair.Private,
+ test.ciphertext,
+ )
+ if err == nil {
+ t.Fatal("expected key exchange decryption to reject invalid ciphertext")
+ }
+ })
}
}
diff --git a/server/db/models/uuid_test.go b/server/db/models/uuid_test.go
index 39c735f57..eb3bbbf25 100644
--- a/server/db/models/uuid_test.go
+++ b/server/db/models/uuid_test.go
@@ -19,13 +19,10 @@ package models
*/
import (
- "database/sql"
- "database/sql/driver"
"encoding/json"
"path/filepath"
"strings"
"testing"
- uuid "uuid"
gosqlite "github.com/glebarez/sqlite"
"gorm.io/gorm"
@@ -89,9 +86,6 @@ func TestUUIDSQLAndJSONEncoding(t *testing.T) {
want := NewUUID()
standard := want.Standard()
- var _ sql.Scanner = (*UUID)(nil)
- var _ driver.Valuer = UUID{}
-
value, err := want.Value()
if err != nil {
t.Fatalf("Value: %v", err)
@@ -188,11 +182,3 @@ func TestUUIDGORMRoundTrip(t *testing.T) {
t.Fatalf("stored UUID = %q, want canonical %q", storedID, want.ID.String())
}
}
-
-func TestUUIDMatchesStandardLibrary(t *testing.T) {
- standard := uuid.NewV4()
- model := UUIDFrom(standard)
- if model.Standard() != standard {
- t.Fatalf("Standard = %q, want %q", model.Standard(), standard)
- }
-}
diff --git a/server/db/wasmsqlite/sqlite_version_test.go b/server/db/wasmsqlite/sqlite_version_test.go
deleted file mode 100644
index 7d8c472d0..000000000
--- a/server/db/wasmsqlite/sqlite_version_test.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package wasmsqlite
-
-import (
- "database/sql"
- "log"
- "testing"
-)
-
-func TestSQLiteVersion(t *testing.T) {
- var version string
-
- db, err := sql.Open(DriverName, ":memory:")
- if err != nil {
- log.Fatal(err)
- }
-
- row := db.QueryRow("select sqlite_version()")
- if row.Scan(&version) != nil {
- log.Fatal(err)
- }
-
- t.Log(version)
-}
diff --git a/server/encoders/encoders_test.go b/server/encoders/encoders_test.go
index e0c95f3ef..b1fc93cd9 100644
--- a/server/encoders/encoders_test.go
+++ b/server/encoders/encoders_test.go
@@ -20,206 +20,65 @@ package encoders
import (
"bytes"
- "crypto/rand"
- insecureRand "math/rand"
+ "fmt"
"testing"
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
util "github.com/bishopfox/sliver/util/encoders"
)
-const (
- sampleSizeMax = 8192
-)
-
-func randomDataRandomSize(maxSize int) []byte {
- buf := make([]byte, insecureRand.Intn(maxSize))
- rand.Read(buf)
- return buf
-}
-
-func TestCompatibilityBase64(t *testing.T) {
- for i := 0; i < 100; i++ {
- sample := randomDataRandomSize(sampleSizeMax)
- output, _ := Base64.Encode(sample)
- data, err := implantEncoders.Base64.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into base64")
- return
- }
- if !bytes.Equal(sample, data) {
- t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
- }
-
- sample2 := randomDataRandomSize(sampleSizeMax)
- output, _ = implantEncoders.Base64.Encode(sample2)
- data, err = Base64.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into base64")
- return
- }
- if !bytes.Equal(sample2, data) {
- t.Errorf("sample2 does not match returned\n%#v != %#v", sample2, data)
- }
- }
-}
-
-func TestCompatibilityBase58(t *testing.T) {
- for i := 0; i < 100; i++ {
- sample := randomDataRandomSize(sampleSizeMax)
- output, _ := Base58.Encode(sample)
- data, err := implantEncoders.Base58.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into base58")
- return
- }
- if !bytes.Equal(sample, data) {
- t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
- }
-
- sample2 := randomDataRandomSize(sampleSizeMax)
- output, _ = Base58.Encode(sample2)
- data, err = implantEncoders.Base58.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into base58")
- return
- }
- if !bytes.Equal(sample2, data) {
- t.Errorf("sample2 does not match returned\n%#v != %#v", sample2, data)
- }
- }
-}
-
-func TestCompatibilityBase32(t *testing.T) {
- for i := 0; i < 100; i++ {
- sample := randomDataRandomSize(sampleSizeMax)
- output, _ := Base32.Encode(sample)
- data, err := implantEncoders.Base32.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into base32")
- return
- }
- if !bytes.Equal(sample, data) {
- t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
- }
-
- sample2 := randomDataRandomSize(sampleSizeMax)
- output, _ = Base32.Encode(sample2)
- data, err = implantEncoders.Base32.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into base32")
- return
- }
- if !bytes.Equal(sample2, data) {
- t.Errorf("sample2 does not match returned\n%#v != %#v", sample2, data)
- }
- }
-}
-
-func TestCompatibilityEnglish(t *testing.T) {
-
+func TestEncoderCompatibility(t *testing.T) {
util.SetEnglishDictionary(getTestEnglishDictionary())
- for i := 0; i < 100; i++ {
- sample := randomDataRandomSize(sampleSizeMax)
- output, _ := English.Encode(sample)
- data, err := implantEncoders.English.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into english")
- return
- }
- if !bytes.Equal(sample, data) {
- t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
- }
-
- sample2 := randomDataRandomSize(sampleSizeMax)
- output, _ = implantEncoders.English.Encode(sample2)
- data, err = English.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into english")
- return
- }
- if !bytes.Equal(sample2, data) {
- t.Errorf("sample2 does not match returned\n%#v != %#v", sample2, data)
- }
+ encoders := []struct {
+ name string
+ server util.Encoder
+ implant util.Encoder
+ }{
+ {"base64", Base64, implantEncoders.Base64},
+ {"base58", Base58, implantEncoders.Base58},
+ {"base32", Base32, implantEncoders.Base32},
+ {"english", English, implantEncoders.English},
+ {"hex", Hex, implantEncoders.Hex},
+ {"gzip", Gzip, implantEncoders.Gzip},
+ {"png", PNG, implantEncoders.PNG},
}
-}
-
-func TestCompatibilityHex(t *testing.T) {
- for i := 0; i < 100; i++ {
- sample := randomDataRandomSize(sampleSizeMax)
- output, _ := Hex.Encode(sample)
- data, err := implantEncoders.Hex.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into hex")
- return
- }
- if !bytes.Equal(sample, data) {
- t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
- }
-
- sample2 := randomDataRandomSize(sampleSizeMax)
- output, _ = Hex.Encode(sample2)
- data, err = implantEncoders.Hex.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into hex")
- return
- }
- if !bytes.Equal(sample2, data) {
- t.Errorf("sample2 does not match returned\n%#v != %#v", sample2, data)
- }
- }
-}
-
-func TestCompatibilityGzip(t *testing.T) {
- for i := 0; i < 100; i++ {
- sample := randomDataRandomSize(sampleSizeMax)
- output, _ := Gzip.Encode(sample)
- data, err := implantEncoders.Gzip.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into gzip")
- return
- }
- if !bytes.Equal(sample, data) {
- t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
- }
-
- sample2 := randomDataRandomSize(sampleSizeMax)
- output, _ = Gzip.Encode(sample2)
- data, err = implantEncoders.Gzip.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into gzip")
- return
- }
- if !bytes.Equal(sample2, data) {
- t.Errorf("sample2 does not match returned\n%#v != %#v", sample2, data)
- }
- }
-}
-
-func TestCompatibilityPNG(t *testing.T) {
- for i := 0; i < 100; i++ {
- sample := randomDataRandomSize(sampleSizeMax)
- output, _ := PNG.Encode(sample)
- data, err := implantEncoders.PNG.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into png")
- return
- }
- if !bytes.Equal(sample, data) {
- t.Errorf("sample does not match returned\n%#v != %#v", sample, data)
- }
-
- sample2 := randomDataRandomSize(sampleSizeMax)
- output, _ = PNG.Encode(sample2)
- data, err = implantEncoders.PNG.Decode(output)
- if err != nil {
- t.Error("Failed to encode/decode sample data into png")
- return
- }
- if !bytes.Equal(sample2, data) {
- t.Errorf("sample2 does not match returned\n%#v != %#v", sample2, data)
- }
+ for _, encoder := range encoders {
+ t.Run(encoder.name, func(t *testing.T) {
+ directions := []struct {
+ name string
+ encoder util.Encoder
+ decoder util.Encoder
+ }{
+ {"server_to_implant", encoder.server, encoder.implant},
+ {"implant_to_server", encoder.implant, encoder.server},
+ }
+ for _, direction := range directions {
+ t.Run(direction.name, func(t *testing.T) {
+ // Cover encoding block boundaries, every byte value, leading and
+ // trailing zero bytes, and the old random corpus's upper bound.
+ for _, size := range []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 256, 257, 8192} {
+ t.Run(fmt.Sprintf("bytes_%d", size), func(t *testing.T) {
+ payload := make([]byte, size)
+ for i := range payload {
+ payload[i] = byte(i)
+ }
+ encoded, err := direction.encoder.Encode(payload)
+ if err != nil {
+ t.Fatalf("encode: %v", err)
+ }
+ decoded, err := direction.decoder.Decode(encoded)
+ if err != nil {
+ t.Fatalf("decode: %v", err)
+ }
+ if !bytes.Equal(payload, decoded) {
+ t.Fatalf("round trip changed the %d-byte payload (decoded %d bytes)", len(payload), len(decoded))
+ }
+ })
+ }
+ })
+ }
+ })
}
}
diff --git a/server/generate/binaries_test.go b/server/generate/binaries_test.go
index 9c7a61e67..bf700248f 100644
--- a/server/generate/binaries_test.go
+++ b/server/generate/binaries_test.go
@@ -1,3 +1,5 @@
+//go:build sliver_generate
+
package generate
/*
diff --git a/server/generate/canaries_test.go b/server/generate/canaries_test.go
index 90ea4d7b8..6d0ddb0be 100644
--- a/server/generate/canaries_test.go
+++ b/server/generate/canaries_test.go
@@ -1,3 +1,5 @@
+//go:build sliver_generate
+
package generate
import (
diff --git a/server/generate/profiles_test.go b/server/generate/profiles_test.go
deleted file mode 100644
index 2026d15fb..000000000
--- a/server/generate/profiles_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package generate
-
-/*
- Sliver Implant Framework
- Copyright (C) 2019 Bishop Fox
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
diff --git a/server/generate/shellcode_test.go b/server/generate/shellcode_test.go
index dca915821..cd3e07999 100644
--- a/server/generate/shellcode_test.go
+++ b/server/generate/shellcode_test.go
@@ -1,3 +1,5 @@
+//go:build sliver_generate
+
package generate
/*
diff --git a/server/loot/loot_test.go b/server/loot/loot_test.go
index 57e99369f..4e7634373 100644
--- a/server/loot/loot_test.go
+++ b/server/loot/loot_test.go
@@ -123,40 +123,6 @@ func TestAllLoot(t *testing.T) {
}
}
-func TestAllLootOf(t *testing.T) {
- lootStore := GetLootStore()
- _, err := lootStore.Add(&clientpb.Loot{
- Name: name1,
- FileType: clientpb.FileType_BINARY,
- File: &commonpb.File{
- Name: name1,
- Data: data1,
- },
- })
- if err != nil {
- t.Fatal(err)
- }
- _, err = lootStore.Add(&clientpb.Loot{
- Name: name2,
- FileType: clientpb.FileType_TEXT,
- File: &commonpb.File{
- Name: name1,
- Data: []byte("hello world"),
- },
- })
- if err != nil {
- t.Fatal(err)
- }
-
- // Cleanup
- for _, loot := range lootStore.All().Loot {
- err = lootStore.Rm(loot.ID)
- if err != nil {
- t.Fatal(err)
- }
- }
-}
-
func TestLootErrors(t *testing.T) {
lootStore := GetLootStore()
loot, err := lootStore.Add(&clientpb.Loot{
diff --git a/server/website/website_test.go b/server/website/website_test.go
index eb7571651..dd0b0d594 100644
--- a/server/website/website_test.go
+++ b/server/website/website_test.go
@@ -48,29 +48,6 @@ func randomData() []byte {
return buf
}
-func TestAddContent(t *testing.T) {
- webContent := clientpb.WebContent{
- Path: "/data1",
- ContentType: contentType1,
- Size: uint64(len(data1)),
- Content: data1,
- }
- err := AddContent(website1, &webContent)
- if err != nil {
- t.Fatal(err)
- }
- webContent2 := clientpb.WebContent{
- Path: "/data2",
- ContentType: contentType2,
- Size: uint64(len(data2)),
- Content: data1,
- }
- err = AddContent(website2, &webContent2)
- if err != nil {
- t.Fatal(err)
- }
-}
-
func TestGetContent(t *testing.T) {
webContent := clientpb.WebContent{
diff --git a/util/encoders/images_test.go b/util/encoders/images_test.go
index 9344b990f..34a4df871 100644
--- a/util/encoders/images_test.go
+++ b/util/encoders/images_test.go
@@ -26,43 +26,45 @@ import (
"testing"
)
-var (
- imageTests = []struct {
- Input []byte
- }{
- {[]byte("abc")}, // byte count on image pixel alignment
- {[]byte("abcde")}, // byte count offset of image pixel alignment
- {[]byte{0x0, 0x01, 0x02, 0x03, 0x04}},
- {[]byte{0x01, 0x02, 0x03, 0x04, 0x0}},
- }
-)
-
func TestPNG(t *testing.T) {
- pngEncoder := new(PNGEncoder)
- for _, test := range imageTests {
- buf, _ := pngEncoder.Encode(test.Input)
- decodeOutput, err := pngEncoder.Decode(buf)
- if err != nil {
- t.Errorf("png decode returned error: %q", err)
- }
- if !bytes.Equal(test.Input, decodeOutput) {
- t.Errorf("png Decode(img) => %q, expected %q", decodeOutput, test.Input)
- }
+ allBytes := make([]byte, 256)
+ for i := range allBytes {
+ allBytes[i] = byte(i)
+ }
+ tests := []struct {
+ name string
+ payload []byte
+ }{
+ {"empty", nil},
+ {"one_zero", []byte{0}},
+ {"one_escape", []byte{1}},
+ {"partial_pixel", []byte{2, 3}},
+ {"pixel_aligned", []byte("abc")},
+ {"pixel_overflow", []byte("abcde")},
+ {"before_square_boundary", bytes.Repeat([]byte{2}, 11)},
+ {"at_square_boundary", bytes.Repeat([]byte{2}, 12)},
+ {"after_square_boundary", bytes.Repeat([]byte{2}, 13)},
+ {"leading_zero", []byte{0, 1, 2, 3, 4}},
+ {"trailing_zero", []byte{1, 2, 3, 4, 0}},
+ {"adjacent_escapes", []byte{0, 1, 0, 1, 1, 0}},
+ {"all_bytes", allBytes},
+ {"one_megabyte", bytes.Repeat(allBytes, 4096)},
}
-}
-
-func TestPNGRandomDataRandomSize(t *testing.T) {
pngEncoder := new(PNGEncoder)
- for i := 0; i < 100; i++ {
- sample := randomDataRandomSize(1024 * 1024)
- buf, _ := pngEncoder.Encode(sample)
- decodeOutput, err := pngEncoder.Decode(buf)
- if err != nil {
- t.Errorf("png decode returned error: %q", err)
- }
- if !bytes.Equal(sample, decodeOutput) {
- t.Errorf("png Decode(img) => %q, expected %q", decodeOutput, sample)
- }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ encoded, err := pngEncoder.Encode(test.payload)
+ if err != nil {
+ t.Fatalf("encode: %v", err)
+ }
+ decoded, err := pngEncoder.Decode(encoded)
+ if err != nil {
+ t.Fatalf("decode: %v", err)
+ }
+ if !bytes.Equal(test.payload, decoded) {
+ t.Fatalf("round trip changed the %d-byte payload (decoded %d bytes)", len(test.payload), len(decoded))
+ }
+ })
}
}