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)) + } + }) } }