Compare commits

...
6 Commits
Author SHA1 Message Date
moloch-- d95da61556 Improve go linker flag handling 2026-09-04 06:57:44 -07:00
moloch-- 5d5661947c Update Garble v1.27.1 checksums 2026-09-03 17:13:50 -07:00
moloch-- aad8ca5d4e Merge branch 'master' into obf-cfo
# Conflicts:
#	server/db/models/beacon.go
#	server/db/models/implant.go
#	server/gogo/go.go
#	server/handlers/beacons.go
2026-09-03 16:53:58 -07:00
Ronan Kervella 55b0f2bce3 Merge branch 'master' into obf-cfo 2026-08-25 10:14:11 -07:00
moloch-- 73a567550d Fix control-flow CI validation
Handle newly introduced lint findings, make Windows default builds produce executable names, and verify pinned Garble extraction before the native control-flow probe.
2026-08-18 13:13:35 -07:00
moloch-- 9be2cd8b34 Add versioned Garble control-flow obfuscation
Carry the balanced-v1 policy through clients, profiles, builders, and persisted build snapshots. Verify pinned Garble assets, fail closed on unsupported runtimes, and cover local/external build lifecycle and native control-flow behavior.
2026-08-18 09:39:51 -07:00
67 changed files with 5158 additions and 605 deletions

No files matched your search

+22 -14
View File
@@ -35,6 +35,7 @@ ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
PB_COMPILERS = protoc protoc-gen-go protoc-gen-go-grpc
ASSET_SOURCES = $(filter-out %_test.go,$(wildcard util/assets/*.go util/cmd/assets/*.go))
.PHONY: lint
lint:
@@ -170,8 +171,8 @@ pb:
protoc -I protobuf/ protobuf/dnspb/dns.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/rpcpb/services.proto --go_out=paths=source_relative:protobuf/ --go-grpc_out=protobuf/ --go-grpc_opt=paths=source_relative
.PHONY: debug
debug: clean
.PHONY: debug
debug: clean .downloaded_assets validate-go-version
$(ENV) CGO_ENABLED=$(CGO_ENABLED) $(GO) build -mod=vendor $(TAGS),server $(LDFLAGS_DEBUG) -o sliver-server$(ARTIFACT_SUFFIX) ./server
$(ENV) CGO_ENABLED=0 $(GO) build -mod=vendor $(TAGS),client $(LDFLAGS_DEBUG) -o sliver-client$(ARTIFACT_SUFFIX) ./client
@@ -199,9 +200,9 @@ clean-all: clean
clean:
rm -f sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe
.downloaded_assets:
$(ENV) $(GO) run -mod=vendor ./util/cmd/assets
touch ./.downloaded_assets
.downloaded_assets: $(ASSET_SOURCES)
$(ENV) $(GO) run -mod=vendor ./util/cmd/assets
touch ./.downloaded_assets
#
@@ -221,6 +222,13 @@ LDFLAGS = -ldflags "-s -w -X $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLI
LDFLAGS_DEBUG = -ldflags "-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) -X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
COMMA := ,
DEFAULT_EXECUTABLE_EXT := .exe
ifneq ($(strip $(GOOS)),)
ifneq ($(strip $(GOOS)),windows)
DEFAULT_EXECUTABLE_EXT :=
endif
endif
ifeq ($(MAKECMDGOALS), linux)
LDFLAGS = -ldflags "-s -w -extldflags '-static' -X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) -X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
@@ -235,10 +243,10 @@ $(call windows_exec,$(ENV) GOOS=$(1) GOARCH=$(2) CGO_ENABLED=$(3),"$(GO)" build
endef
.PHONY: default
default: clean validate-go-version
$(call windows_exec,$(ENV) GOOS= GOARCH=,"$(MAKE)" GOOS= GOARCH= .downloaded_assets)
$(call windows_go_build,$(GOOS),$(GOARCH),$(CGO_ENABLED),-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server$(ARTIFACT_SUFFIX))
$(call windows_go_build,$(GOOS),$(GOARCH),0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client$(ARTIFACT_SUFFIX))
default: clean validate-go-version
$(call windows_exec,$(ENV) GOOS= GOARCH=,"$(MAKE)" GOOS= GOARCH= .downloaded_assets)
$(call windows_go_build,$(GOOS),$(GOARCH),$(CGO_ENABLED),-mod=vendor -trimpath,server,$(LDFLAGS),sliver-server$(ARTIFACT_SUFFIX)$(DEFAULT_EXECUTABLE_EXT))
$(call windows_go_build,$(GOOS),$(GOARCH),0,-mod=vendor -trimpath,client,$(LDFLAGS),sliver-client$(ARTIFACT_SUFFIX)$(DEFAULT_EXECUTABLE_EXT))
.PHONY: client
client: clean .downloaded_assets validate-go-version
@@ -299,8 +307,8 @@ pb: validate-pb-compilers
protoc -I protobuf/ protobuf/dnspb/dns.proto --go_out=paths=source_relative:protobuf/
protoc -I protobuf/ protobuf/rpcpb/services.proto --go_out=paths=source_relative:protobuf/ --go-grpc_out=protobuf/ --go-grpc_opt=paths=source_relative
.PHONY: debug
debug: clean
.PHONY: debug
debug: clean .downloaded_assets validate-go-version
$(call windows_go_build,$(GOOS),$(GOARCH),$(CGO_ENABLED),-mod=vendor,server,$(LDFLAGS_DEBUG),sliver-server$(ARTIFACT_SUFFIX))
$(call windows_go_build,$(GOOS),$(GOARCH),0,-mod=vendor,client,$(LDFLAGS_DEBUG),sliver-client$(ARTIFACT_SUFFIX))
@@ -324,8 +332,8 @@ clean-all: clean
clean:
-del /Q /F sliver-client sliver-client_* sliver-server sliver-server_* sliver-*.exe 2>NUL
.downloaded_assets:
$(call windows_exec,$(ENV),"$(GO)" run -mod=vendor ./util/cmd/assets)
@type NUL > .downloaded_assets
.downloaded_assets: $(ASSET_SOURCES)
$(call windows_exec,$(ENV),"$(GO)" run -mod=vendor ./util/cmd/assets)
@type NUL > .downloaded_assets
endif
+2 -1
View File
@@ -49,7 +49,7 @@ func PrintBuilders(externalBuilders []*clientpb.Builder, con *console.SliverClie
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Name", "Operator", "Templates", "Platform", "Compiler Targets",
"Name", "Operator", "Templates", "Platform", "Capabilities", "Compiler Targets",
})
for _, builder := range externalBuilders {
@@ -63,6 +63,7 @@ func PrintBuilders(externalBuilders []*clientpb.Builder, con *console.SliverClie
builder.OperatorName,
strings.Join(builder.Templates, ", "),
fmt.Sprintf("%s/%s", builder.GOOS, builder.GOARCH),
strings.Join(builder.Capabilities, ", "),
strings.Join(targets, "\n"),
}
tw.AppendRow(table.Row(row))
+28
View File
@@ -59,6 +59,19 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
config = con.GetActiveBeaconConfig()
implantName = beacon.Name
}
if config == nil {
con.PrintErrorf("Failed to derive active implant config.\n")
return
}
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("Failed to retrieve originating implant build %q: %v\n", implantName, err)
return
}
if !applyMigrateObfuscationPolicy(config, implantName, builds) {
con.PrintWarnf("Originating implant build %q was not found; continuing with reconstructed legacy obfuscation settings.\n", implantName)
}
targetArch := config.GetGOARCH()
if session != nil && session.Arch != "" {
@@ -134,6 +147,21 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
}
}
func applyMigrateObfuscationPolicy(config *clientpb.ImplantConfig, implantName string, builds *clientpb.ImplantBuilds) bool {
if config == nil || builds == nil {
return false
}
originatingConfig, ok := builds.GetConfigs()[implantName]
if !ok || originatingConfig == nil {
return false
}
config.Debug = originatingConfig.Debug
config.ObfuscateSymbols = originatingConfig.ObfuscateSymbols
config.ControlFlow = originatingConfig.ControlFlow
return true
}
func normalizeShellcodeEncoderName(name string) string {
normalized := strings.ToLower(strings.TrimSpace(name))
normalized = strings.ReplaceAll(normalized, "-", "_")
@@ -0,0 +1,53 @@
package exec
import (
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
func TestApplyMigrateObfuscationPolicy(t *testing.T) {
config := &clientpb.ImplantConfig{
Debug: true,
ObfuscateSymbols: false,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED,
}
builds := &clientpb.ImplantBuilds{Configs: map[string]*clientpb.ImplantConfig{
"source-build": {
Debug: false,
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
},
}}
if !applyMigrateObfuscationPolicy(config, "source-build", builds) {
t.Fatal("applyMigrateObfuscationPolicy() = false, want true")
}
if config.Debug {
t.Fatal("Debug = true, want originating build value false")
}
if !config.ObfuscateSymbols {
t.Fatal("ObfuscateSymbols = false, want originating build value true")
}
if config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1 {
t.Fatalf("ControlFlow = %s, want %s", config.ControlFlow, clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1)
}
}
func TestApplyMigrateObfuscationPolicyMissingBuildPreservesLegacyConfig(t *testing.T) {
config := &clientpb.ImplantConfig{
Debug: true,
ObfuscateSymbols: false,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED,
}
if applyMigrateObfuscationPolicy(
config,
"missing-build",
&clientpb.ImplantBuilds{Configs: map[string]*clientpb.ImplantConfig{}},
) {
t.Fatal("applyMigrateObfuscationPolicy() = true for missing build, want false")
}
if !config.Debug || config.ObfuscateSymbols || config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
t.Fatalf("missing build changed legacy config: %+v", config)
}
}
+5 -1
View File
@@ -90,7 +90,11 @@ func PsExecCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintErrorf("No profile found for name %s\n", profile)
return
}
serviceBinary, _ = generate.GetSliverBinary(implantProfile, con)
serviceBinary, err = generate.GetSliverBinary(implantProfile, con)
if err != nil {
con.PrintErrorf("Failed to generate service binary: %s\n", err)
return
}
} else {
// use a custom exe instead of generating a new Sliver
fileBytes, err := os.ReadFile(customExe)
+1
View File
@@ -326,6 +326,7 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
f.StringP("debug-file", "O", "", "path to debug output")
f.BoolP("evasion", "e", false, "enable evasion features (e.g. overwrite user space hooks)")
f.BoolP("skip-symbols", "l", false, "skip symbol obfuscation")
f.String("control-flow", "off", "control-flow obfuscation policy (off, balanced-v1)")
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
f.StringP("exports", "v", "StartW,VoidFunc,DllInstall,DllRegisterServer,DllUnregisterServer", "comma separated list of exports to include in the binary")
f.StringP("canary", "c", "", "canary domain(s)")
@@ -0,0 +1,87 @@
package generate
import (
"testing"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
func TestParseControlFlowPolicy(t *testing.T) {
tests := []struct {
name string
value string
want clientpb.ControlFlowPolicy
wantErr bool
}{
{name: "off", value: "off", want: clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED},
{name: "balanced", value: "balanced-v1", want: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1},
{name: "case and whitespace", value: " BALANCED-V1 ", want: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1},
{name: "unknown", value: "aggressive", wantErr: true},
{name: "empty", value: "", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseControlFlowPolicy(tt.value)
if tt.wantErr {
if err == nil {
t.Fatal("expected an error")
}
return
}
if err != nil {
t.Fatalf("parseControlFlowPolicy() error = %v", err)
}
if got != tt.want {
t.Fatalf("parseControlFlowPolicy() = %v, want %v", got, tt.want)
}
})
}
}
func TestValidateControlFlowFlags(t *testing.T) {
disabled := clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED
balanced := clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1
if err := validateControlFlowFlags(disabled, true, true); err != nil {
t.Fatalf("disabled control flow should not constrain other flags: %v", err)
}
if err := validateControlFlowFlags(balanced, false, false); err != nil {
t.Fatalf("balanced control flow with symbol obfuscation should be valid: %v", err)
}
if err := validateControlFlowFlags(balanced, true, false); err == nil {
t.Fatal("expected --debug conflict")
}
if err := validateControlFlowFlags(balanced, false, true); err == nil {
t.Fatal("expected --skip-symbols conflict")
}
}
func TestApplyGenerateBeaconFormSetsControlFlow(t *testing.T) {
generateCmd := commandByUse(Commands(&console.SliverClient{}), consts.GenerateStr)
if generateCmd == nil {
t.Fatalf("missing %q command", consts.GenerateStr)
}
beaconCmd := commandByUse(generateCmd.Commands(), consts.BeaconStr)
if beaconCmd == nil {
t.Fatalf("missing %q subcommand", consts.BeaconStr)
}
err := applyGenerateBeaconForm(beaconCmd, &forms.GenerateBeaconFormResult{
OS: "darwin",
Arch: "arm64",
Format: "exe",
C2Type: "mtls",
C2Value: "127.0.0.1:31337",
ControlFlow: "balanced-v1",
})
if err != nil {
t.Fatalf("applyGenerateBeaconForm() error = %v", err)
}
if got, err := beaconCmd.Flags().GetString("control-flow"); err != nil || got != "balanced-v1" {
t.Fatalf("control-flow flag = %q, %v; want balanced-v1", got, err)
}
}
+7 -2
View File
@@ -57,9 +57,11 @@ func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []str
save, _ = os.Getwd()
}
if external, _ := cmd.Flags().GetBool("external-builder"); !external {
compile(name, config, spoofMetadata, save, con)
_, _ = compile(name, config, spoofMetadata, save, con)
} else {
externalBuild(name, config, spoofMetadata, save, con)
if _, err := externalBuild(name, config, spoofMetadata, save, con); err != nil {
con.PrintErrorf("%s\n", err)
}
}
}
@@ -113,6 +115,9 @@ func applyGenerateBeaconForm(cmd *cobra.Command, result *forms.GenerateBeaconFor
if err := cmd.Flags().Set("format", result.Format); err != nil {
return err
}
if err := cmd.Flags().Set("control-flow", result.ControlFlow); err != nil {
return err
}
name := strings.TrimSpace(result.Name)
if name != "" {
if err := cmd.Flags().Set("name", name); err != nil {
+129 -10
View File
@@ -74,7 +74,9 @@ const (
)
const (
crossCompilerInfoURL = "https://github.com/BishopFox/sliver/wiki/Cross-Compiling-Implants"
crossCompilerInfoURL = "https://github.com/BishopFox/sliver/wiki/Cross-Compiling-Implants"
controlFlowCapability = "garble.control-flow/balanced-v1"
localControlFlowCapability = "garble.control-flow.local/balanced-v1"
)
var (
@@ -90,9 +92,14 @@ var (
"windows/arm64": true,
}
// ErrNoExternalBuilder indicates that no external builders are registered.
ErrNoExternalBuilder = errors.New("no external builders are available")
ErrNoValidBuilders = errors.New("no valid external builders for target")
ErrNoSelection = errors.New("no selection")
// ErrNoValidBuilders indicates that no external builder supports the target.
ErrNoValidBuilders = errors.New("no valid external builders for target")
// ErrNoControlFlowBuilder indicates that no target-compatible builder supports control-flow obfuscation.
ErrNoControlFlowBuilder = errors.New("no target-compatible external builder supports control-flow obfuscation")
// ErrNoSelection indicates that the user did not select an external builder.
ErrNoSelection = errors.New("no selection")
)
const (
@@ -100,6 +107,38 @@ const (
imageFileDLLCharacteristic = 0x2000
)
func parseControlFlowPolicy(value string) (clientpb.ControlFlowPolicy, error) {
switch strings.ToLower(strings.TrimSpace(value)) {
case "off":
return clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED, nil
case "balanced-v1":
return clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1, nil
default:
return clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED, fmt.Errorf("invalid control-flow policy %q (expected off or balanced-v1)", value)
}
}
func validateControlFlowFlags(policy clientpb.ControlFlowPolicy, debug, skipSymbols bool) error {
if policy == clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
return nil
}
if debug || skipSymbols {
return errors.New("--control-flow=balanced-v1 requires symbol obfuscation and cannot be combined with --debug or --skip-symbols")
}
return nil
}
func controlFlowPolicyName(policy clientpb.ControlFlowPolicy) string {
switch policy {
case clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED:
return "disabled"
case clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1:
return "balanced-v1"
default:
return fmt.Sprintf("unknown (%d)", policy)
}
}
// GenerateCmd - The main command used to generate implant binaries
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateForm(cmd, con, args) {
@@ -172,6 +211,9 @@ func applyGenerateForm(cmd *cobra.Command, result *forms.GenerateFormResult) err
if err := cmd.Flags().Set("format", result.Format); err != nil {
return err
}
if err := cmd.Flags().Set("control-flow", result.ControlFlow); err != nil {
return err
}
name := strings.TrimSpace(result.Name)
if name != "" {
if err := cmd.Flags().Set("name", name); err != nil {
@@ -329,12 +371,18 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
}
c2s = append(c2s, tcpPivotC2...)
var symbolObfuscation bool
if debug, _ := cmd.Flags().GetBool("debug"); debug {
symbolObfuscation = false
} else {
symbolObfuscation, _ = cmd.Flags().GetBool("skip-symbols")
symbolObfuscation = !symbolObfuscation
debug, _ := cmd.Flags().GetBool("debug")
skipSymbols, _ := cmd.Flags().GetBool("skip-symbols")
symbolObfuscation := !debug && !skipSymbols
controlFlowValue, _ := cmd.Flags().GetString("control-flow")
controlFlow, err := parseControlFlowPolicy(controlFlowValue)
if err != nil {
con.PrintErrorf("%s\n", err)
return "", nil
}
if err := validateControlFlowFlags(controlFlow, debug, skipSymbols); err != nil {
con.PrintErrorf("%s\n", err)
return "", nil
}
if len(mtlsC2) == 0 && len(wgC2) == 0 && len(httpC2) == 0 && len(dnsC2) == 0 && len(namedPipeC2) == 0 && len(tcpPivotC2) == 0 {
@@ -353,7 +401,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
}
}
debug, _ := cmd.Flags().GetBool("debug")
evasion, _ := cmd.Flags().GetBool("evasion")
templateName, _ := cmd.Flags().GetString("template")
@@ -516,6 +563,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
SGNEnabled: sgnEnabled,
ShellcodeEncoder: shellcodeEncoder,
ObfuscateSymbols: symbolObfuscation,
ControlFlow: controlFlow,
C2: c2s,
CanaryDomains: canaryDomains,
TemplateName: templateName,
@@ -1643,6 +1691,10 @@ func outputSavePath(save string, defaultName string, config *clientpb.ImplantCon
}
func externalBuild(name string, config *clientpb.ImplantConfig, spoofMetadata *clientpb.SpoofMetadataConfig, save string, con *console.SliverClient) (*commonpb.File, error) {
if err := ensureServerSupportsControlFlow(config, con); err != nil {
return nil, err
}
potentialBuilders, err := findExternalBuilders(config, con)
if err != nil {
return nil, err
@@ -1670,6 +1722,9 @@ func externalBuild(name string, config *clientpb.ImplantConfig, spoofMetadata *c
} else if !config.Debug {
con.PrintErrorf("Symbol obfuscation is %s\n", console.StyleBold.Render("disabled"))
}
if config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
con.PrintInfof("Control-flow obfuscation policy: %s\n", console.StyleBold.Render(controlFlowPolicyName(config.ControlFlow)))
}
start := time.Now()
listenerID, listener := con.CreateEventListener()
@@ -1690,6 +1745,14 @@ func externalBuild(name string, config *clientpb.ImplantConfig, spoofMetadata *c
con.PrintErrorf("%s\n", err)
return nil, err
}
if config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED &&
(externalImplantConfig == nil || externalImplantConfig.Config == nil || externalImplantConfig.Config.ControlFlow != config.ControlFlow) {
return nil, fmt.Errorf(
"server returned control-flow policy %s for requested policy %s",
controlFlowPolicyName(externalImplantConfig.GetConfig().GetControlFlow()),
controlFlowPolicyName(config.ControlFlow),
)
}
con.Printf("done\n")
msgF := "Waiting for external builder to acknowledge build (template: %s) ... %s"
@@ -1818,6 +1881,11 @@ func externalBuild(name string, config *clientpb.ImplantConfig, spoofMetadata *c
}
func compile(name string, config *clientpb.ImplantConfig, spoofMetadata *clientpb.SpoofMetadataConfig, save string, con *console.SliverClient) (*commonpb.File, error) {
if err := ensureServerCanBuildControlFlow(config, con); err != nil {
con.PrintErrorf("%s\n", err)
return nil, err
}
if config.IsBeacon {
interval := time.Duration(config.BeaconInterval)
con.PrintInfof("Generating new %s/%s beacon implant binary (%v)\n", config.GOOS, config.GOARCH, interval)
@@ -1829,6 +1897,9 @@ func compile(name string, config *clientpb.ImplantConfig, spoofMetadata *clientp
} else if !config.Debug {
con.PrintErrorf("Symbol obfuscation is %s\n", console.StyleBold.Render("disabled"))
}
if config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
con.PrintInfof("Control-flow obfuscation policy: %s\n", console.StyleBold.Render(controlFlowPolicyName(config.ControlFlow)))
}
start := time.Now()
ctrl := make(chan bool)
@@ -2009,9 +2080,14 @@ func findExternalBuilders(config *clientpb.ImplantConfig, con *console.SliverCli
}
validBuilders := []*clientpb.Builder{}
targetCompatibleBuilder := false
for _, builder := range builders.Builders {
for _, target := range builder.Targets {
if target.GOOS == config.GOOS && target.GOARCH == config.GOARCH && config.Format == target.Format {
targetCompatibleBuilder = true
if config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED && !hasCapability(builder.Capabilities, controlFlowCapability) {
break
}
validBuilders = append(validBuilders, builder)
break
}
@@ -2019,12 +2095,55 @@ func findExternalBuilders(config *clientpb.ImplantConfig, con *console.SliverCli
}
if len(validBuilders) < 1 {
if targetCompatibleBuilder && config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
return []*clientpb.Builder{}, ErrNoControlFlowBuilder
}
return []*clientpb.Builder{}, ErrNoValidBuilders
}
return validBuilders, nil
}
func ensureServerSupportsControlFlow(config *clientpb.ImplantConfig, con *console.SliverClient) error {
if config == nil || config.ControlFlow == clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
return nil
}
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
if err != nil {
return fmt.Errorf("failed to query server control-flow capability: %w", err)
}
if !hasCapability(compiler.Capabilities, controlFlowCapability) {
return fmt.Errorf("server does not support %s", controlFlowCapability)
}
return nil
}
func ensureServerCanBuildControlFlow(config *clientpb.ImplantConfig, con *console.SliverClient) error {
if config == nil || config.ControlFlow == clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
return nil
}
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
if err != nil {
return fmt.Errorf("failed to query server control-flow capability: %w", err)
}
if !hasCapability(compiler.Capabilities, controlFlowCapability) {
return fmt.Errorf("server does not support %s", controlFlowCapability)
}
if !hasCapability(compiler.Capabilities, localControlFlowCapability) {
return fmt.Errorf("server local compiler does not provide %s", localControlFlowCapability)
}
return nil
}
func hasCapability(capabilities []string, required string) bool {
for _, capability := range capabilities {
if capability == required {
return true
}
}
return false
}
func selectExternalBuilder(builders []*clientpb.Builder, _ *console.SliverClient) (*clientpb.Builder, error) {
choices := []string{}
for _, builder := range builders {
+27 -2
View File
@@ -58,7 +58,7 @@ func ProfilesNewCmd(cmd *cobra.Command, con *console.SliverClient, args []string
Name: name,
Config: config,
}
resp, err := con.Rpc.SaveImplantProfile(context.Background(), profile)
resp, err := saveImplantProfileWithControlFlowCheck(con, profile)
if err != nil {
con.PrintErrorf("%s\n", err)
} else {
@@ -106,7 +106,7 @@ func ProfilesNewBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []
Name: name,
Config: config,
}
resp, err := con.Rpc.SaveImplantProfile(context.Background(), profile)
resp, err := saveImplantProfileWithControlFlowCheck(con, profile)
if err != nil {
con.PrintErrorf("%s\n", err)
} else {
@@ -114,6 +114,25 @@ func ProfilesNewBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []
}
}
func saveImplantProfileWithControlFlowCheck(con *console.SliverClient, profile *clientpb.ImplantProfile) (*clientpb.ImplantProfile, error) {
if profile == nil || profile.Config == nil {
return nil, errors.New("implant profile config cannot be nil")
}
if err := ensureServerSupportsControlFlow(profile.Config, con); err != nil {
return nil, err
}
resp, err := con.Rpc.SaveImplantProfile(context.Background(), profile)
if err != nil {
return nil, err
}
if profile.Config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED &&
(resp == nil || resp.Config == nil || resp.Config.ControlFlow != profile.Config.ControlFlow) {
return nil, errors.New("server did not preserve the requested control-flow policy")
}
return resp, nil
}
func shouldRunGenerateProfilesNewForm(cmd *cobra.Command, con *console.SliverClient, args []string) bool {
if con == nil || con.IsCLI {
return false
@@ -134,6 +153,9 @@ func applyGenerateProfilesNewForm(cmd *cobra.Command, result *forms.GenerateProf
if err := cmd.Flags().Set("format", result.Format); err != nil {
return "", err
}
if err := cmd.Flags().Set("control-flow", result.ControlFlow); err != nil {
return "", err
}
profileName := strings.TrimSpace(result.ProfileName)
if profileName == "" {
@@ -200,6 +222,9 @@ func applyGenerateProfilesNewBeaconForm(cmd *cobra.Command, result *forms.Genera
if err := cmd.Flags().Set("format", result.Format); err != nil {
return "", err
}
if err := cmd.Flags().Set("control-flow", result.ControlFlow); err != nil {
return "", err
}
profileName := strings.TrimSpace(result.ProfileName)
if profileName == "" {
+7
View File
@@ -60,6 +60,7 @@ func PrintProfiles(profiles []*clientpb.ImplantProfile, con *console.SliverClien
"Debug",
"Format",
"Obfuscation",
"Control Flow",
"Limitations",
"C2 Profile",
// "Nonce",
@@ -91,6 +92,7 @@ func PrintProfiles(profiles []*clientpb.ImplantProfile, con *console.SliverClien
fmt.Sprintf("%v", config.Debug),
fmt.Sprintf("%v", config.Format),
obfuscation,
controlFlowPolicyName(config.ControlFlow),
getLimitsString(config),
config.HTTPC2ConfigName,
// profile.ImplantID,
@@ -164,6 +166,7 @@ func populateProfileProperties(config *clientpb.ImplantConfig) map[string]string
} else {
properties["obsymbols"] = "disabled"
}
properties["obcontrolflow"] = controlFlowPolicyName(config.ControlFlow)
properties["shellcodeencoder"] = "n/a"
if config.Format == clientpb.OutputFormat_SHELLCODE {
@@ -323,6 +326,10 @@ func PrintProfileInfo(name string, con *console.SliverClient) {
"Obfuscation of symbols is",
properties["obsymbols"],
})
tw.AppendRow(table.Row{
"Control-flow obfuscation is",
properties["obcontrolflow"],
})
tw.AppendRow(table.Row{
"Shellcode encoder is",
properties["shellcodeencoder"],
+24
View File
@@ -28,6 +28,7 @@ import (
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
)
@@ -53,6 +54,14 @@ func GetSystemCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
con.PrintErrorf("Failed to derive active session config.\n")
return
}
builds, err := con.Rpc.ImplantBuilds(context.Background(), &commonpb.Empty{})
if err != nil {
con.PrintErrorf("Failed to retrieve originating implant build %q: %v\n", session.Name, err)
return
}
if !applyGetSystemObfuscationPolicy(config, session.Name, builds) {
con.PrintWarnf("Originating implant build %q was not found; continuing with reconstructed legacy obfuscation settings.\n", session.Name)
}
/* If the HTTP C2 Config name is not defined, then put in the default value
This will have no effect on implants that do not use HTTP C2
@@ -94,6 +103,21 @@ func GetSystemCmd(cmd *cobra.Command, con *console.SliverClient, args []string)
}
}
func applyGetSystemObfuscationPolicy(config *clientpb.ImplantConfig, implantName string, builds *clientpb.ImplantBuilds) bool {
if config == nil || builds == nil {
return false
}
originatingConfig, ok := builds.GetConfigs()[implantName]
if !ok || originatingConfig == nil {
return false
}
config.Debug = originatingConfig.Debug
config.ObfuscateSymbols = originatingConfig.ObfuscateSymbols
config.ControlFlow = originatingConfig.ControlFlow
return true
}
// PrintGetSystem - Print the results of get system
func PrintGetSystem(getsystemResp *sliverpb.GetSystem, con *console.SliverClient) {
if getsystemResp.Response != nil && getsystemResp.Response.GetErr() != "" {
@@ -0,0 +1,35 @@
package privilege
import (
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
func TestApplyGetSystemObfuscationPolicy(t *testing.T) {
config := &clientpb.ImplantConfig{Debug: true}
builds := &clientpb.ImplantBuilds{Configs: map[string]*clientpb.ImplantConfig{
"source-build": {
Debug: false,
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
},
}}
if !applyGetSystemObfuscationPolicy(config, "source-build", builds) {
t.Fatal("applyGetSystemObfuscationPolicy() = false, want true")
}
if config.Debug || !config.ObfuscateSymbols || config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1 {
t.Fatalf("obfuscation policy = %+v, want originating build values", config)
}
}
func TestApplyGetSystemObfuscationPolicyMissingBuildPreservesLegacyConfig(t *testing.T) {
config := &clientpb.ImplantConfig{Debug: true}
if applyGetSystemObfuscationPolicy(config, "missing-build", &clientpb.ImplantBuilds{}) {
t.Fatal("applyGetSystemObfuscationPolicy() = true for missing build, want false")
}
if !config.Debug || config.ObfuscateSymbols || config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
t.Fatalf("missing build changed legacy config: %+v", config)
}
}
+20 -11
View File
@@ -15,22 +15,24 @@ var ErrUserAborted = huh.ErrUserAborted
// GenerateFormResult captures the inputs needed to drive the generate command.
type GenerateFormResult struct {
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
Save string
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
ControlFlow string
Save string
}
// GenerateForm prompts for core generate flags and returns the collected values.
func GenerateForm(compiler *clientpb.Compiler) (*GenerateFormResult, error) {
result := &GenerateFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
ControlFlow: "off",
}
commonPlatformsOnly := true
lastCommonPlatformsOnly := commonPlatformsOnly
@@ -78,6 +80,13 @@ func GenerateForm(compiler *clientpb.Compiler) (*GenerateFormResult, error) {
}, targetBindings).
Height(3).
Value(&result.Format),
huh.NewSelect[string]().
Title("Control-flow obfuscation").
Options(
huh.NewOption("Off", "off"),
huh.NewOption("Balanced (experimental)", "balanced-v1"),
).
Value(&result.ControlFlow),
),
huh.NewGroup(
huh.NewInput().
+26 -16
View File
@@ -13,27 +13,29 @@ import (
// GenerateBeaconFormResult captures the inputs needed to drive the generate beacon command.
type GenerateBeaconFormResult struct {
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
Save string
Days string
Hours string
Minutes string
Seconds string
Jitter string
OS string
Arch string
Format string
Name string
C2Type string
C2Value string
ControlFlow string
Save string
Days string
Hours string
Minutes string
Seconds string
Jitter string
}
// GenerateBeaconForm prompts for generate beacon flags and returns the collected values.
func GenerateBeaconForm(compiler *clientpb.Compiler) (*GenerateBeaconFormResult, error) {
result := &GenerateBeaconFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
ControlFlow: "off",
}
commonPlatformsOnly := true
lastCommonPlatformsOnly := commonPlatformsOnly
@@ -81,6 +83,14 @@ func GenerateBeaconForm(compiler *clientpb.Compiler) (*GenerateBeaconFormResult,
}, targetBindings).
Height(3).
Value(&result.Format),
huh.NewSelect[string]().
Title("Control-flow obfuscation").
Description("Experimental; balanced-v1 requires symbol obfuscation").
Options(
huh.NewOption("Off", "off"),
huh.NewOption("Balanced (experimental)", "balanced-v1"),
).
Value(&result.ControlFlow),
),
huh.NewGroup(
huh.NewInput().
+13 -4
View File
@@ -18,15 +18,17 @@ type GenerateProfilesNewFormResult struct {
Name string
C2Type string
C2Value string
ControlFlow string
}
// GenerateProfilesNewForm prompts for profiles new flags and returns the collected values.
func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
result := &GenerateProfilesNewFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
ControlFlow: "off",
}
form := huh.NewForm(
@@ -93,6 +95,13 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
}, &result.OS).
Height(3).
Value(&result.Format),
huh.NewSelect[string]().
Title("Control-flow obfuscation").
Options(
huh.NewOption("Off", "off"),
huh.NewOption("Balanced (experimental)", "balanced-v1"),
).
Value(&result.ControlFlow),
),
huh.NewGroup(
huh.NewInput().
+13 -4
View File
@@ -18,6 +18,7 @@ type GenerateProfilesNewBeaconFormResult struct {
Name string
C2Type string
C2Value string
ControlFlow string
Days string
Hours string
Minutes string
@@ -28,10 +29,11 @@ type GenerateProfilesNewBeaconFormResult struct {
// GenerateProfilesNewBeaconForm prompts for profiles new beacon flags and returns the collected values.
func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, error) {
result := &GenerateProfilesNewBeaconFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
ControlFlow: "off",
}
form := huh.NewForm(
@@ -98,6 +100,13 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
}, &result.OS).
Height(3).
Value(&result.Format),
huh.NewSelect[string]().
Title("Control-flow obfuscation").
Options(
huh.NewOption("Off", "off"),
huh.NewOption("Balanced (experimental)", "balanced-v1"),
).
Value(&result.ControlFlow),
),
huh.NewGroup(
huh.NewInput().
@@ -42,6 +42,8 @@ Some commands such as `shell` and `portfwd` only work over interactive sessions.
Generating implants is done using the `generate` command, you must specify at least one C2 endpoint using `--mtls`, `--wg`, `--http`, or `--dns`. Note that when an implant attempts to connect to an endpoint specified using `--http` it will try both HTTPS and then HTTP (if HTTPS fails). We recommend using mTLS (`--mtls`) or WireGuard (`--wg`) whenever possible. You can also specify an output directory with `--save`, by default the implant will be saved to the current working directory.
Control-flow obfuscation is experimental and opt-in. Add `--control-flow=balanced-v1` to a session, beacon, or profile generation command to enable Sliver's versioned balanced policy. It requires symbol obfuscation and cannot be combined with `--debug` or `--skip-symbols`. The server admits one local control-flow build at a time because Garble's SSA transformation is memory intensive; external builds require a builder that advertises the matching capability. The default is `--control-flow=off`.
#### Session Mode
```asciinema
+31 -9
View File
@@ -139,32 +139,54 @@ function Get-TestDirectories {
}
function Get-ServerBinaryPath {
$candidates = @(
(Join-Path $PWD.Path "sliver-server.exe"),
(Join-Path $PWD.Path "sliver-server")
)
$serverName = "sliver-server"
if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
[System.Runtime.InteropServices.OSPlatform]::Windows
)) {
$serverName = "sliver-server.exe"
}
foreach ($candidate in $candidates) {
if (Test-Path -LiteralPath $candidate) {
return $candidate
}
$candidate = Join-Path $PWD.Path $serverName
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
return $candidate
}
return $null
}
function Get-PinnedGarbleAssetPath {
$garbleName = "garble"
if ([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
[System.Runtime.InteropServices.OSPlatform]::Windows
)) {
$garbleName = "garble.exe"
}
$goBin = Join-Path (Join-Path $env:SLIVER_ROOT_DIR "go") "bin"
return (Join-Path $goBin $garbleName)
}
function Assert-PinnedGarbleAssetUnpacked {
$garblePath = Get-PinnedGarbleAssetPath
if (-not (Test-Path -LiteralPath $garblePath -PathType Leaf)) {
throw "Server asset unpack did not produce pinned Garble at $garblePath"
}
}
function Invoke-UnpackServerAssets {
$serverBinary = Get-ServerBinaryPath
if ($serverBinary) {
try {
Invoke-ExternalCommand -FilePath $serverBinary -Arguments @("unpack", "--force")
Assert-PinnedGarbleAssetUnpacked
return
} catch {
Write-Host "sliver-server unpack failed, falling back to go run ./server unpack --force"
Write-Host "sliver-server unpack failed ($($_.Exception.Message)), falling back to go run ./server unpack --force"
}
}
Invoke-ExternalCommand -FilePath "go" -Arguments @("run", "-tags=server,go_sqlite", "./server", "unpack", "--force")
Assert-PinnedGarbleAssetUnpacked
}
function Stop-TestProcesses {
+1
View File
@@ -84,6 +84,7 @@ func pingHandler(data []byte, resp RPCResponse) {
resp(data, err)
}
//garble:controlflow block_splits=2 junk_jumps=2 flatten_passes=1 flatten_hardening=xor trash_blocks=0
func determineDirPathFilter(targetPath string) (string, string) {
// The filter
filter := ""
+2
View File
@@ -757,6 +757,8 @@ func wrapEnvelope(msgType uint32, message protoreflect.ProtoMessage) *sliverpb.E
}
// registerSliver - Creates a registration protobuf message
//
//garble:controlflow block_splits=2 junk_jumps=2 flatten_passes=1 flatten_hardening=xor trash_blocks=0
func registerSliver() *sliverpb.Register {
hostname, err := os.Hostname()
if err != nil {
+3 -1
View File
@@ -91,7 +91,7 @@ func C2Generator(abort <-chan struct{}, temporaryC2 ...string) <-chan *url.URL {
next = c2Servers[c2Counter%uint(len(c2Servers))]()
}
// check if reconfig used to set a new C2-URI
// check if reconfig used to set a new C2-URI
if dynamic := GetC2URI(); dynamic != "" {
next = dynamic
}
@@ -127,6 +127,8 @@ func C2Generator(abort <-chan struct{}, temporaryC2 ...string) <-chan *url.URL {
}
// randomCCDomain - Random selection within a protocol
//
//garble:controlflow block_splits=2 junk_jumps=2 flatten_passes=1 flatten_hardening=xor trash_blocks=0
func randomCCDomain(ccServers []func() string, next string) string {
uri, err := url.Parse(next)
if err != nil {
+425 -345
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc v7.34.1
// protoc v7.35.1
// source: clientpb/client.proto
package clientpb
@@ -1331,6 +1331,54 @@ func (AIConversationEventType) EnumDescriptor() ([]byte, []int) {
return file_clientpb_client_proto_rawDescGZIP(), []int{17}
}
// ControlFlowPolicy selects a server-defined Garble control-flow obfuscation
// policy. Policy parameters are versioned so existing profiles remain stable.
type ControlFlowPolicy int32
const (
ControlFlowPolicy_CONTROL_FLOW_DISABLED ControlFlowPolicy = 0
ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1 ControlFlowPolicy = 1
)
// Enum value maps for ControlFlowPolicy.
var (
ControlFlowPolicy_name = map[int32]string{
0: "CONTROL_FLOW_DISABLED",
1: "CONTROL_FLOW_BALANCED_V1",
}
ControlFlowPolicy_value = map[string]int32{
"CONTROL_FLOW_DISABLED": 0,
"CONTROL_FLOW_BALANCED_V1": 1,
}
)
func (x ControlFlowPolicy) Enum() *ControlFlowPolicy {
p := new(ControlFlowPolicy)
*p = x
return p
}
func (x ControlFlowPolicy) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ControlFlowPolicy) Descriptor() protoreflect.EnumDescriptor {
return file_clientpb_client_proto_enumTypes[18].Descriptor()
}
func (ControlFlowPolicy) Type() protoreflect.EnumType {
return &file_clientpb_client_proto_enumTypes[18]
}
func (x ControlFlowPolicy) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ControlFlowPolicy.Descriptor instead.
func (ControlFlowPolicy) EnumDescriptor() ([]byte, []int) {
return file_clientpb_client_proto_rawDescGZIP(), []int{18}
}
type Version struct {
state protoimpl.MessageState `protogen:"open.v1"`
Major int32 `protobuf:"varint,1,opt,name=Major,proto3" json:"Major,omitempty"`
@@ -2478,30 +2526,31 @@ type ImplantConfig struct {
MaxConnectionErrors uint32 `protobuf:"varint,41,opt,name=MaxConnectionErrors,proto3" json:"MaxConnectionErrors,omitempty"`
PollTimeout int64 `protobuf:"varint,42,opt,name=PollTimeout,proto3" json:"PollTimeout,omitempty"`
// c2
C2 []*ImplantC2 `protobuf:"bytes,50,rep,name=C2,proto3" json:"C2,omitempty"`
CanaryDomains []string `protobuf:"bytes,51,rep,name=CanaryDomains,proto3" json:"CanaryDomains,omitempty"`
ConnectionStrategy string `protobuf:"bytes,52,opt,name=ConnectionStrategy,proto3" json:"ConnectionStrategy,omitempty"`
LimitDomainJoined bool `protobuf:"varint,60,opt,name=LimitDomainJoined,proto3" json:"LimitDomainJoined,omitempty"`
LimitDatetime string `protobuf:"bytes,61,opt,name=LimitDatetime,proto3" json:"LimitDatetime,omitempty"`
LimitHostname string `protobuf:"bytes,62,opt,name=LimitHostname,proto3" json:"LimitHostname,omitempty"`
LimitUsername string `protobuf:"bytes,63,opt,name=LimitUsername,proto3" json:"LimitUsername,omitempty"`
LimitFileExists string `protobuf:"bytes,64,opt,name=LimitFileExists,proto3" json:"LimitFileExists,omitempty"`
LimitLocale string `protobuf:"bytes,65,opt,name=LimitLocale,proto3" json:"LimitLocale,omitempty"`
Format OutputFormat `protobuf:"varint,100,opt,name=Format,proto3,enum=clientpb.OutputFormat" json:"Format,omitempty"`
IsSharedLib bool `protobuf:"varint,101,opt,name=IsSharedLib,proto3" json:"IsSharedLib,omitempty"`
IsService bool `protobuf:"varint,103,opt,name=IsService,proto3" json:"IsService,omitempty"`
IsShellcode bool `protobuf:"varint,104,opt,name=IsShellcode,proto3" json:"IsShellcode,omitempty"`
RunAtLoad bool `protobuf:"varint,105,opt,name=RunAtLoad,proto3" json:"RunAtLoad,omitempty"`
DebugFile string `protobuf:"bytes,106,opt,name=DebugFile,proto3" json:"DebugFile,omitempty"`
Exports []string `protobuf:"bytes,107,rep,name=exports,proto3" json:"exports,omitempty"`
ShellcodeConfig *ShellcodeConfig `protobuf:"bytes,108,opt,name=ShellcodeConfig,proto3" json:"ShellcodeConfig,omitempty"`
ShellcodeEncoder ShellcodeEncoder `protobuf:"varint,109,opt,name=ShellcodeEncoder,proto3,enum=clientpb.ShellcodeEncoder" json:"ShellcodeEncoder,omitempty"`
HTTPC2ConfigName string `protobuf:"bytes,150,opt,name=HTTPC2ConfigName,proto3" json:"HTTPC2ConfigName,omitempty"`
NetGoEnabled bool `protobuf:"varint,151,opt,name=NetGoEnabled,proto3" json:"NetGoEnabled,omitempty"`
TrafficEncodersEnabled bool `protobuf:"varint,152,opt,name=TrafficEncodersEnabled,proto3" json:"TrafficEncodersEnabled,omitempty"`
TrafficEncoders []string `protobuf:"bytes,153,rep,name=TrafficEncoders,proto3" json:"TrafficEncoders,omitempty"`
Extension string `protobuf:"bytes,154,opt,name=Extension,proto3" json:"Extension,omitempty"`
Assets []*commonpb.File `protobuf:"bytes,200,rep,name=Assets,proto3" json:"Assets,omitempty"`
C2 []*ImplantC2 `protobuf:"bytes,50,rep,name=C2,proto3" json:"C2,omitempty"`
CanaryDomains []string `protobuf:"bytes,51,rep,name=CanaryDomains,proto3" json:"CanaryDomains,omitempty"`
ConnectionStrategy string `protobuf:"bytes,52,opt,name=ConnectionStrategy,proto3" json:"ConnectionStrategy,omitempty"`
LimitDomainJoined bool `protobuf:"varint,60,opt,name=LimitDomainJoined,proto3" json:"LimitDomainJoined,omitempty"`
LimitDatetime string `protobuf:"bytes,61,opt,name=LimitDatetime,proto3" json:"LimitDatetime,omitempty"`
LimitHostname string `protobuf:"bytes,62,opt,name=LimitHostname,proto3" json:"LimitHostname,omitempty"`
LimitUsername string `protobuf:"bytes,63,opt,name=LimitUsername,proto3" json:"LimitUsername,omitempty"`
LimitFileExists string `protobuf:"bytes,64,opt,name=LimitFileExists,proto3" json:"LimitFileExists,omitempty"`
LimitLocale string `protobuf:"bytes,65,opt,name=LimitLocale,proto3" json:"LimitLocale,omitempty"`
Format OutputFormat `protobuf:"varint,100,opt,name=Format,proto3,enum=clientpb.OutputFormat" json:"Format,omitempty"`
IsSharedLib bool `protobuf:"varint,101,opt,name=IsSharedLib,proto3" json:"IsSharedLib,omitempty"`
IsService bool `protobuf:"varint,103,opt,name=IsService,proto3" json:"IsService,omitempty"`
IsShellcode bool `protobuf:"varint,104,opt,name=IsShellcode,proto3" json:"IsShellcode,omitempty"`
RunAtLoad bool `protobuf:"varint,105,opt,name=RunAtLoad,proto3" json:"RunAtLoad,omitempty"`
DebugFile string `protobuf:"bytes,106,opt,name=DebugFile,proto3" json:"DebugFile,omitempty"`
Exports []string `protobuf:"bytes,107,rep,name=exports,proto3" json:"exports,omitempty"`
ShellcodeConfig *ShellcodeConfig `protobuf:"bytes,108,opt,name=ShellcodeConfig,proto3" json:"ShellcodeConfig,omitempty"`
ShellcodeEncoder ShellcodeEncoder `protobuf:"varint,109,opt,name=ShellcodeEncoder,proto3,enum=clientpb.ShellcodeEncoder" json:"ShellcodeEncoder,omitempty"`
HTTPC2ConfigName string `protobuf:"bytes,150,opt,name=HTTPC2ConfigName,proto3" json:"HTTPC2ConfigName,omitempty"`
NetGoEnabled bool `protobuf:"varint,151,opt,name=NetGoEnabled,proto3" json:"NetGoEnabled,omitempty"`
TrafficEncodersEnabled bool `protobuf:"varint,152,opt,name=TrafficEncodersEnabled,proto3" json:"TrafficEncodersEnabled,omitempty"`
TrafficEncoders []string `protobuf:"bytes,153,rep,name=TrafficEncoders,proto3" json:"TrafficEncoders,omitempty"`
Extension string `protobuf:"bytes,154,opt,name=Extension,proto3" json:"Extension,omitempty"`
ControlFlow ControlFlowPolicy `protobuf:"varint,156,opt,name=ControlFlow,proto3,enum=clientpb.ControlFlowPolicy" json:"ControlFlow,omitempty"`
Assets []*commonpb.File `protobuf:"bytes,200,rep,name=Assets,proto3" json:"Assets,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -2879,6 +2928,13 @@ func (x *ImplantConfig) GetExtension() string {
return ""
}
func (x *ImplantConfig) GetControlFlow() ControlFlowPolicy {
if x != nil {
return x.ControlFlow
}
return ControlFlowPolicy_CONTROL_FLOW_DISABLED
}
func (x *ImplantConfig) GetAssets() []*commonpb.File {
if x != nil {
return x.Assets
@@ -4233,6 +4289,7 @@ type Compiler struct {
Targets []*CompilerTarget `protobuf:"bytes,3,rep,name=Targets,proto3" json:"Targets,omitempty"`
CrossCompilers []*CrossCompiler `protobuf:"bytes,4,rep,name=CrossCompilers,proto3" json:"CrossCompilers,omitempty"`
UnsupportedTargets []*CompilerTarget `protobuf:"bytes,5,rep,name=UnsupportedTargets,proto3" json:"UnsupportedTargets,omitempty"`
Capabilities []string `protobuf:"bytes,6,rep,name=Capabilities,proto3" json:"Capabilities,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -4302,6 +4359,13 @@ func (x *Compiler) GetUnsupportedTargets() []*CompilerTarget {
return nil
}
func (x *Compiler) GetCapabilities() []string {
if x != nil {
return x.Capabilities
}
return nil
}
type DeleteReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"`
@@ -8477,6 +8541,7 @@ type Builder struct {
Templates []string `protobuf:"bytes,5,rep,name=Templates,proto3" json:"Templates,omitempty"`
Targets []*CompilerTarget `protobuf:"bytes,6,rep,name=Targets,proto3" json:"Targets,omitempty"`
CrossCompilers []*CrossCompiler `protobuf:"bytes,7,rep,name=CrossCompilers,proto3" json:"CrossCompilers,omitempty"`
Capabilities []string `protobuf:"bytes,8,rep,name=Capabilities,proto3" json:"Capabilities,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -8560,6 +8625,13 @@ func (x *Builder) GetCrossCompilers() []*CrossCompiler {
return nil
}
func (x *Builder) GetCapabilities() []string {
if x != nil {
return x.Capabilities
}
return nil
}
// [ HTTP C2 ] ----------------------------------------
type HTTPC2Configs struct {
state protoimpl.MessageState `protogen:"open.v1"`
@@ -13069,7 +13141,7 @@ const file_clientpb_client_proto_rawDesc = "" +
"\aHeaders\x18\x06 \x01(\rR\aHeaders\x12\x16\n" +
"\x06Thread\x18\a \x01(\bR\x06Thread\x12\x18\n" +
"\aUnicode\x18\b \x01(\bR\aUnicode\x12\x10\n" +
"\x03OEP\x18\t \x01(\rR\x03OEP\"\x8e\x0f\n" +
"\x03OEP\x18\t \x01(\rR\x03OEP\"\xce\x0f\n" +
"\rImplantConfig\x12\x0e\n" +
"\x02ID\x18\x01 \x01(\tR\x02ID\x12<\n" +
"\rImplantBuilds\x18\x02 \x03(\v2\x16.clientpb.ImplantBuildR\rImplantBuilds\x12*\n" +
@@ -13126,7 +13198,8 @@ const file_clientpb_client_proto_rawDesc = "" +
"\fNetGoEnabled\x18\x97\x01 \x01(\bR\fNetGoEnabled\x127\n" +
"\x16TrafficEncodersEnabled\x18\x98\x01 \x01(\bR\x16TrafficEncodersEnabled\x12)\n" +
"\x0fTrafficEncoders\x18\x99\x01 \x03(\tR\x0fTrafficEncoders\x12\x1d\n" +
"\tExtension\x18\x9a\x01 \x01(\tR\tExtension\x12'\n" +
"\tExtension\x18\x9a\x01 \x01(\tR\tExtension\x12>\n" +
"\vControlFlow\x18\x9c\x01 \x01(\x0e2\x1b.clientpb.ControlFlowPolicyR\vControlFlow\x12'\n" +
"\x06Assets\x18\xc8\x01 \x03(\v2\x0e.commonpb.FileR\x06AssetsJ\x06\b\x9b\x01\x10\x9c\x01R\tSpoofData\";\n" +
"\x11SpoofMetadataFile\x12\x12\n" +
"\x04Name\x18\x01 \x01(\tR\x04Name\x12\x12\n" +
@@ -13255,13 +13328,14 @@ const file_clientpb_client_proto_rawDesc = "" +
"TargetGOOS\x12\"\n" +
"\fTargetGOARCH\x18\x02 \x01(\tR\fTargetGOARCH\x12\x16\n" +
"\x06CCPath\x18\x03 \x01(\tR\x06CCPath\x12\x18\n" +
"\aCXXPath\x18\x04 \x01(\tR\aCXXPath\"\xf5\x01\n" +
"\aCXXPath\x18\x04 \x01(\tR\aCXXPath\"\x99\x02\n" +
"\bCompiler\x12\x12\n" +
"\x04GOOS\x18\x01 \x01(\tR\x04GOOS\x12\x16\n" +
"\x06GOARCH\x18\x02 \x01(\tR\x06GOARCH\x122\n" +
"\aTargets\x18\x03 \x03(\v2\x18.clientpb.CompilerTargetR\aTargets\x12?\n" +
"\x0eCrossCompilers\x18\x04 \x03(\v2\x17.clientpb.CrossCompilerR\x0eCrossCompilers\x12H\n" +
"\x12UnsupportedTargets\x18\x05 \x03(\v2\x18.clientpb.CompilerTargetR\x12UnsupportedTargets\"\x1f\n" +
"\x12UnsupportedTargets\x18\x05 \x03(\v2\x18.clientpb.CompilerTargetR\x12UnsupportedTargets\x12\"\n" +
"\fCapabilities\x18\x06 \x03(\tR\fCapabilities\"\x1f\n" +
"\tDeleteReq\x12\x12\n" +
"\x04Name\x18\x01 \x01(\tR\x04Name\"\xd7\x01\n" +
"\tDNSCanary\x12\x0e\n" +
@@ -13583,7 +13657,7 @@ const file_clientpb_client_proto_rawDesc = "" +
"\vBuilderName\x18\x02 \x01(\tR\vBuilderName\x12\x12\n" +
"\x04Name\x18\x03 \x01(\tR\x04Name\"9\n" +
"\bBuilders\x12-\n" +
"\bBuilders\x18\x01 \x03(\v2\x11.clientpb.BuilderR\bBuilders\"\x80\x02\n" +
"\bBuilders\x18\x01 \x03(\v2\x11.clientpb.BuilderR\bBuilders\"\xa4\x02\n" +
"\aBuilder\x12\x12\n" +
"\x04Name\x18\x01 \x01(\tR\x04Name\x12\"\n" +
"\fOperatorName\x18\x02 \x01(\tR\fOperatorName\x12\x12\n" +
@@ -13591,7 +13665,8 @@ const file_clientpb_client_proto_rawDesc = "" +
"\x06GOARCH\x18\x04 \x01(\tR\x06GOARCH\x12\x1c\n" +
"\tTemplates\x18\x05 \x03(\tR\tTemplates\x122\n" +
"\aTargets\x18\x06 \x03(\v2\x18.clientpb.CompilerTargetR\aTargets\x12?\n" +
"\x0eCrossCompilers\x18\a \x03(\v2\x17.clientpb.CrossCompilerR\x0eCrossCompilers\"A\n" +
"\x0eCrossCompilers\x18\a \x03(\v2\x17.clientpb.CrossCompilerR\x0eCrossCompilers\x12\"\n" +
"\fCapabilities\x18\b \x03(\tR\fCapabilities\"A\n" +
"\rHTTPC2Configs\x120\n" +
"\aconfigs\x18\x01 \x03(\v2\x16.clientpb.HTTPC2ConfigR\aconfigs\"\"\n" +
"\fC2ProfileReq\x12\x12\n" +
@@ -14281,7 +14356,10 @@ const file_clientpb_client_proto_rawDesc = "" +
"&AI_CONVERSATION_EVENT_TYPE_TURN_FAILED\x10\x05\x12.\n" +
"*AI_CONVERSATION_EVENT_TYPE_MESSAGE_STARTED\x10\x06\x12.\n" +
"*AI_CONVERSATION_EVENT_TYPE_MESSAGE_UPDATED\x10\a\x120\n" +
",AI_CONVERSATION_EVENT_TYPE_MESSAGE_COMPLETED\x10\bB/Z-github.com/bishopfox/sliver/protobuf/clientpbb\x06proto3"
",AI_CONVERSATION_EVENT_TYPE_MESSAGE_COMPLETED\x10\b*L\n" +
"\x11ControlFlowPolicy\x12\x19\n" +
"\x15CONTROL_FLOW_DISABLED\x10\x00\x12\x1c\n" +
"\x18CONTROL_FLOW_BALANCED_V1\x10\x01B/Z-github.com/bishopfox/sliver/protobuf/clientpbb\x06proto3"
var (
file_clientpb_client_proto_rawDescOnce sync.Once
@@ -14295,7 +14373,7 @@ func file_clientpb_client_proto_rawDescGZIP() []byte {
return file_clientpb_client_proto_rawDescData
}
var file_clientpb_client_proto_enumTypes = make([]protoimpl.EnumInfo, 18)
var file_clientpb_client_proto_enumTypes = make([]protoimpl.EnumInfo, 19)
var file_clientpb_client_proto_msgTypes = make([]protoimpl.MessageInfo, 158)
var file_clientpb_client_proto_goTypes = []any{
(OutputFormat)(0), // 0: clientpb.OutputFormat
@@ -14316,321 +14394,323 @@ var file_clientpb_client_proto_goTypes = []any{
(AIConversationMessageVisibility)(0), // 15: clientpb.AIConversationMessageVisibility
(AIConversationMessageState)(0), // 16: clientpb.AIConversationMessageState
(AIConversationEventType)(0), // 17: clientpb.AIConversationEventType
(*Version)(nil), // 18: clientpb.Version
(*ClientLogData)(nil), // 19: clientpb.ClientLogData
(*Session)(nil), // 20: clientpb.Session
(*Beacon)(nil), // 21: clientpb.Beacon
(*Beacons)(nil), // 22: clientpb.Beacons
(*BeaconTask)(nil), // 23: clientpb.BeaconTask
(*BeaconTasks)(nil), // 24: clientpb.BeaconTasks
(*BeaconIntegrity)(nil), // 25: clientpb.BeaconIntegrity
(*ImplantC2)(nil), // 26: clientpb.ImplantC2
(*ShellcodeConfig)(nil), // 27: clientpb.ShellcodeConfig
(*ImplantConfig)(nil), // 28: clientpb.ImplantConfig
(*SpoofMetadataFile)(nil), // 29: clientpb.SpoofMetadataFile
(*IMAGE_RESOURCE_DIRECTORY)(nil), // 30: clientpb.IMAGE_RESOURCE_DIRECTORY
(*IMAGE_RESOURCE_DIRECTORY_ENTRY)(nil), // 31: clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY
(*IMAGE_RESOURCE_DATA_ENTRY)(nil), // 32: clientpb.IMAGE_RESOURCE_DATA_ENTRY
(*IMAGE_EXPORT_DIRECTORY)(nil), // 33: clientpb.IMAGE_EXPORT_DIRECTORY
(*PESpoofMetadataConfig)(nil), // 34: clientpb.PESpoofMetadataConfig
(*SpoofMetadataConfig)(nil), // 35: clientpb.SpoofMetadataConfig
(*TrafficEncoder)(nil), // 36: clientpb.TrafficEncoder
(*TrafficEncoderMap)(nil), // 37: clientpb.TrafficEncoderMap
(*TrafficEncoderTest)(nil), // 38: clientpb.TrafficEncoderTest
(*TrafficEncoderTests)(nil), // 39: clientpb.TrafficEncoderTests
(*ExternalImplantConfig)(nil), // 40: clientpb.ExternalImplantConfig
(*ExternalImplantBinary)(nil), // 41: clientpb.ExternalImplantBinary
(*ImplantBuilds)(nil), // 42: clientpb.ImplantBuilds
(*ImplantStageReq)(nil), // 43: clientpb.ImplantStageReq
(*ImplantBuild)(nil), // 44: clientpb.ImplantBuild
(*CompilerTarget)(nil), // 45: clientpb.CompilerTarget
(*CrossCompiler)(nil), // 46: clientpb.CrossCompiler
(*Compiler)(nil), // 47: clientpb.Compiler
(*DeleteReq)(nil), // 48: clientpb.DeleteReq
(*DNSCanary)(nil), // 49: clientpb.DNSCanary
(*Canaries)(nil), // 50: clientpb.Canaries
(*UniqueWGIP)(nil), // 51: clientpb.UniqueWGIP
(*ImplantProfile)(nil), // 52: clientpb.ImplantProfile
(*ImplantProfiles)(nil), // 53: clientpb.ImplantProfiles
(*RegenerateReq)(nil), // 54: clientpb.RegenerateReq
(*Job)(nil), // 55: clientpb.Job
(*Jobs)(nil), // 56: clientpb.Jobs
(*KillJobReq)(nil), // 57: clientpb.KillJobReq
(*RestartJobReq)(nil), // 58: clientpb.RestartJobReq
(*KillJob)(nil), // 59: clientpb.KillJob
(*ListenerJob)(nil), // 60: clientpb.ListenerJob
(*MultiplayerListenerReq)(nil), // 61: clientpb.MultiplayerListenerReq
(*MTLSListenerReq)(nil), // 62: clientpb.MTLSListenerReq
(*WGListenerReq)(nil), // 63: clientpb.WGListenerReq
(*DNSListenerReq)(nil), // 64: clientpb.DNSListenerReq
(*HTTPListenerReq)(nil), // 65: clientpb.HTTPListenerReq
(*NamedPipesReq)(nil), // 66: clientpb.NamedPipesReq
(*NamedPipes)(nil), // 67: clientpb.NamedPipes
(*TCPPivotReq)(nil), // 68: clientpb.TCPPivotReq
(*TCPPivot)(nil), // 69: clientpb.TCPPivot
(*Sessions)(nil), // 70: clientpb.Sessions
(*RenameReq)(nil), // 71: clientpb.RenameReq
(*GenerateReq)(nil), // 72: clientpb.GenerateReq
(*GenerateStageReq)(nil), // 73: clientpb.GenerateStageReq
(*Generate)(nil), // 74: clientpb.Generate
(*GenerateSpoofMetadataReq)(nil), // 75: clientpb.GenerateSpoofMetadataReq
(*MSFReq)(nil), // 76: clientpb.MSFReq
(*MSFRemoteReq)(nil), // 77: clientpb.MSFRemoteReq
(*StagerListenerReq)(nil), // 78: clientpb.StagerListenerReq
(*StagerListener)(nil), // 79: clientpb.StagerListener
(*ShellcodeRDIReq)(nil), // 80: clientpb.ShellcodeRDIReq
(*ShellcodeRDI)(nil), // 81: clientpb.ShellcodeRDI
(*GetSystemReq)(nil), // 82: clientpb.GetSystemReq
(*MigrateReq)(nil), // 83: clientpb.MigrateReq
(*CreateTunnelReq)(nil), // 84: clientpb.CreateTunnelReq
(*CreateTunnel)(nil), // 85: clientpb.CreateTunnel
(*CloseTunnelReq)(nil), // 86: clientpb.CloseTunnelReq
(*PivotGraphEntry)(nil), // 87: clientpb.PivotGraphEntry
(*PivotGraph)(nil), // 88: clientpb.PivotGraph
(*Client)(nil), // 89: clientpb.Client
(*Event)(nil), // 90: clientpb.Event
(*Operators)(nil), // 91: clientpb.Operators
(*Operator)(nil), // 92: clientpb.Operator
(*WebContent)(nil), // 93: clientpb.WebContent
(*WebsiteAddContent)(nil), // 94: clientpb.WebsiteAddContent
(*WebsiteRemoveContent)(nil), // 95: clientpb.WebsiteRemoveContent
(*Website)(nil), // 96: clientpb.Website
(*Websites)(nil), // 97: clientpb.Websites
(*WGClientConfig)(nil), // 98: clientpb.WGClientConfig
(*Loot)(nil), // 99: clientpb.Loot
(*AllLoot)(nil), // 100: clientpb.AllLoot
(*IOC)(nil), // 101: clientpb.IOC
(*ExtensionData)(nil), // 102: clientpb.ExtensionData
(*Host)(nil), // 103: clientpb.Host
(*AllHosts)(nil), // 104: clientpb.AllHosts
(*DllHijackReq)(nil), // 105: clientpb.DllHijackReq
(*DllHijack)(nil), // 106: clientpb.DllHijack
(*BackdoorReq)(nil), // 107: clientpb.BackdoorReq
(*Backdoor)(nil), // 108: clientpb.Backdoor
(*ShellcodeEncodeReq)(nil), // 109: clientpb.ShellcodeEncodeReq
(*ShellcodeEncode)(nil), // 110: clientpb.ShellcodeEncode
(*ShellcodeEncoderArchMap)(nil), // 111: clientpb.ShellcodeEncoderArchMap
(*ShellcodeEncoderMap)(nil), // 112: clientpb.ShellcodeEncoderMap
(*ExternalGenerateReq)(nil), // 113: clientpb.ExternalGenerateReq
(*Builders)(nil), // 114: clientpb.Builders
(*Builder)(nil), // 115: clientpb.Builder
(*HTTPC2Configs)(nil), // 116: clientpb.HTTPC2Configs
(*C2ProfileReq)(nil), // 117: clientpb.C2ProfileReq
(*HTTPC2ConfigReq)(nil), // 118: clientpb.HTTPC2ConfigReq
(*HTTPC2Config)(nil), // 119: clientpb.HTTPC2Config
(*HTTPC2ServerConfig)(nil), // 120: clientpb.HTTPC2ServerConfig
(*HTTPC2ImplantConfig)(nil), // 121: clientpb.HTTPC2ImplantConfig
(*HTTPC2Cookie)(nil), // 122: clientpb.HTTPC2Cookie
(*HTTPC2Header)(nil), // 123: clientpb.HTTPC2Header
(*HTTPC2URLParameter)(nil), // 124: clientpb.HTTPC2URLParameter
(*HTTPC2PathSegment)(nil), // 125: clientpb.HTTPC2PathSegment
(*Credential)(nil), // 126: clientpb.Credential
(*Credentials)(nil), // 127: clientpb.Credentials
(*Crackstations)(nil), // 128: clientpb.Crackstations
(*CrackstationStatus)(nil), // 129: clientpb.CrackstationStatus
(*CrackSyncStatus)(nil), // 130: clientpb.CrackSyncStatus
(*CrackBenchmark)(nil), // 131: clientpb.CrackBenchmark
(*CrackTask)(nil), // 132: clientpb.CrackTask
(*Crackstation)(nil), // 133: clientpb.Crackstation
(*CUDABackendInfo)(nil), // 134: clientpb.CUDABackendInfo
(*OpenCLBackendInfo)(nil), // 135: clientpb.OpenCLBackendInfo
(*MetalBackendInfo)(nil), // 136: clientpb.MetalBackendInfo
(*CrackCommand)(nil), // 137: clientpb.CrackCommand
(*CrackJob)(nil), // 138: clientpb.CrackJob
(*CrackResponse)(nil), // 139: clientpb.CrackResponse
(*CrackConfig)(nil), // 140: clientpb.CrackConfig
(*CrackFiles)(nil), // 141: clientpb.CrackFiles
(*CrackFile)(nil), // 142: clientpb.CrackFile
(*CrackFileChunk)(nil), // 143: clientpb.CrackFileChunk
(*AIProviderConfigs)(nil), // 144: clientpb.AIProviderConfigs
(*AIProviderConfig)(nil), // 145: clientpb.AIProviderConfig
(*AIConfigSummary)(nil), // 146: clientpb.AIConfigSummary
(*AIConversationReq)(nil), // 147: clientpb.AIConversationReq
(*AIConversations)(nil), // 148: clientpb.AIConversations
(*AIContextWindowUsage)(nil), // 149: clientpb.AIContextWindowUsage
(*AIConversation)(nil), // 150: clientpb.AIConversation
(*AIConversationMessages)(nil), // 151: clientpb.AIConversationMessages
(*AIConversationMessage)(nil), // 152: clientpb.AIConversationMessage
(*AIConversationEvent)(nil), // 153: clientpb.AIConversationEvent
(*MonitoringProviders)(nil), // 154: clientpb.MonitoringProviders
(*MonitoringProvider)(nil), // 155: clientpb.MonitoringProvider
(*ResourceID)(nil), // 156: clientpb.ResourceID
(*CertificatesReq)(nil), // 157: clientpb.CertificatesReq
(*CertificateData)(nil), // 158: clientpb.CertificateData
(*CertificateInfo)(nil), // 159: clientpb.CertificateInfo
(*CertificateAuthorityData)(nil), // 160: clientpb.CertificateAuthorityData
(*CertificateAuthorityInfo)(nil), // 161: clientpb.CertificateAuthorityInfo
nil, // 162: clientpb.TrafficEncoderMap.EncodersEntry
nil, // 163: clientpb.ExternalImplantConfig.EncodersEntry
nil, // 164: clientpb.ImplantBuilds.ConfigsEntry
nil, // 165: clientpb.ImplantBuilds.ResourceIDsEntry
nil, // 166: clientpb.ImplantBuilds.StagedEntry
nil, // 167: clientpb.WebsiteAddContent.ContentsEntry
nil, // 168: clientpb.Website.ContentsEntry
nil, // 169: clientpb.Host.ExtensionDataEntry
nil, // 170: clientpb.ShellcodeEncoderArchMap.EncodersEntry
nil, // 171: clientpb.ShellcodeEncoderArchMap.DescriptionsEntry
nil, // 172: clientpb.ShellcodeEncoderMap.EncodersEntry
nil, // 173: clientpb.CrackSyncStatus.ProgressEntry
nil, // 174: clientpb.CrackBenchmark.BenchmarksEntry
nil, // 175: clientpb.Crackstation.BenchmarksEntry
(*commonpb.File)(nil), // 176: commonpb.File
(*commonpb.Request)(nil), // 177: commonpb.Request
(*commonpb.Response)(nil), // 178: commonpb.Response
(ControlFlowPolicy)(0), // 18: clientpb.ControlFlowPolicy
(*Version)(nil), // 19: clientpb.Version
(*ClientLogData)(nil), // 20: clientpb.ClientLogData
(*Session)(nil), // 21: clientpb.Session
(*Beacon)(nil), // 22: clientpb.Beacon
(*Beacons)(nil), // 23: clientpb.Beacons
(*BeaconTask)(nil), // 24: clientpb.BeaconTask
(*BeaconTasks)(nil), // 25: clientpb.BeaconTasks
(*BeaconIntegrity)(nil), // 26: clientpb.BeaconIntegrity
(*ImplantC2)(nil), // 27: clientpb.ImplantC2
(*ShellcodeConfig)(nil), // 28: clientpb.ShellcodeConfig
(*ImplantConfig)(nil), // 29: clientpb.ImplantConfig
(*SpoofMetadataFile)(nil), // 30: clientpb.SpoofMetadataFile
(*IMAGE_RESOURCE_DIRECTORY)(nil), // 31: clientpb.IMAGE_RESOURCE_DIRECTORY
(*IMAGE_RESOURCE_DIRECTORY_ENTRY)(nil), // 32: clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY
(*IMAGE_RESOURCE_DATA_ENTRY)(nil), // 33: clientpb.IMAGE_RESOURCE_DATA_ENTRY
(*IMAGE_EXPORT_DIRECTORY)(nil), // 34: clientpb.IMAGE_EXPORT_DIRECTORY
(*PESpoofMetadataConfig)(nil), // 35: clientpb.PESpoofMetadataConfig
(*SpoofMetadataConfig)(nil), // 36: clientpb.SpoofMetadataConfig
(*TrafficEncoder)(nil), // 37: clientpb.TrafficEncoder
(*TrafficEncoderMap)(nil), // 38: clientpb.TrafficEncoderMap
(*TrafficEncoderTest)(nil), // 39: clientpb.TrafficEncoderTest
(*TrafficEncoderTests)(nil), // 40: clientpb.TrafficEncoderTests
(*ExternalImplantConfig)(nil), // 41: clientpb.ExternalImplantConfig
(*ExternalImplantBinary)(nil), // 42: clientpb.ExternalImplantBinary
(*ImplantBuilds)(nil), // 43: clientpb.ImplantBuilds
(*ImplantStageReq)(nil), // 44: clientpb.ImplantStageReq
(*ImplantBuild)(nil), // 45: clientpb.ImplantBuild
(*CompilerTarget)(nil), // 46: clientpb.CompilerTarget
(*CrossCompiler)(nil), // 47: clientpb.CrossCompiler
(*Compiler)(nil), // 48: clientpb.Compiler
(*DeleteReq)(nil), // 49: clientpb.DeleteReq
(*DNSCanary)(nil), // 50: clientpb.DNSCanary
(*Canaries)(nil), // 51: clientpb.Canaries
(*UniqueWGIP)(nil), // 52: clientpb.UniqueWGIP
(*ImplantProfile)(nil), // 53: clientpb.ImplantProfile
(*ImplantProfiles)(nil), // 54: clientpb.ImplantProfiles
(*RegenerateReq)(nil), // 55: clientpb.RegenerateReq
(*Job)(nil), // 56: clientpb.Job
(*Jobs)(nil), // 57: clientpb.Jobs
(*KillJobReq)(nil), // 58: clientpb.KillJobReq
(*RestartJobReq)(nil), // 59: clientpb.RestartJobReq
(*KillJob)(nil), // 60: clientpb.KillJob
(*ListenerJob)(nil), // 61: clientpb.ListenerJob
(*MultiplayerListenerReq)(nil), // 62: clientpb.MultiplayerListenerReq
(*MTLSListenerReq)(nil), // 63: clientpb.MTLSListenerReq
(*WGListenerReq)(nil), // 64: clientpb.WGListenerReq
(*DNSListenerReq)(nil), // 65: clientpb.DNSListenerReq
(*HTTPListenerReq)(nil), // 66: clientpb.HTTPListenerReq
(*NamedPipesReq)(nil), // 67: clientpb.NamedPipesReq
(*NamedPipes)(nil), // 68: clientpb.NamedPipes
(*TCPPivotReq)(nil), // 69: clientpb.TCPPivotReq
(*TCPPivot)(nil), // 70: clientpb.TCPPivot
(*Sessions)(nil), // 71: clientpb.Sessions
(*RenameReq)(nil), // 72: clientpb.RenameReq
(*GenerateReq)(nil), // 73: clientpb.GenerateReq
(*GenerateStageReq)(nil), // 74: clientpb.GenerateStageReq
(*Generate)(nil), // 75: clientpb.Generate
(*GenerateSpoofMetadataReq)(nil), // 76: clientpb.GenerateSpoofMetadataReq
(*MSFReq)(nil), // 77: clientpb.MSFReq
(*MSFRemoteReq)(nil), // 78: clientpb.MSFRemoteReq
(*StagerListenerReq)(nil), // 79: clientpb.StagerListenerReq
(*StagerListener)(nil), // 80: clientpb.StagerListener
(*ShellcodeRDIReq)(nil), // 81: clientpb.ShellcodeRDIReq
(*ShellcodeRDI)(nil), // 82: clientpb.ShellcodeRDI
(*GetSystemReq)(nil), // 83: clientpb.GetSystemReq
(*MigrateReq)(nil), // 84: clientpb.MigrateReq
(*CreateTunnelReq)(nil), // 85: clientpb.CreateTunnelReq
(*CreateTunnel)(nil), // 86: clientpb.CreateTunnel
(*CloseTunnelReq)(nil), // 87: clientpb.CloseTunnelReq
(*PivotGraphEntry)(nil), // 88: clientpb.PivotGraphEntry
(*PivotGraph)(nil), // 89: clientpb.PivotGraph
(*Client)(nil), // 90: clientpb.Client
(*Event)(nil), // 91: clientpb.Event
(*Operators)(nil), // 92: clientpb.Operators
(*Operator)(nil), // 93: clientpb.Operator
(*WebContent)(nil), // 94: clientpb.WebContent
(*WebsiteAddContent)(nil), // 95: clientpb.WebsiteAddContent
(*WebsiteRemoveContent)(nil), // 96: clientpb.WebsiteRemoveContent
(*Website)(nil), // 97: clientpb.Website
(*Websites)(nil), // 98: clientpb.Websites
(*WGClientConfig)(nil), // 99: clientpb.WGClientConfig
(*Loot)(nil), // 100: clientpb.Loot
(*AllLoot)(nil), // 101: clientpb.AllLoot
(*IOC)(nil), // 102: clientpb.IOC
(*ExtensionData)(nil), // 103: clientpb.ExtensionData
(*Host)(nil), // 104: clientpb.Host
(*AllHosts)(nil), // 105: clientpb.AllHosts
(*DllHijackReq)(nil), // 106: clientpb.DllHijackReq
(*DllHijack)(nil), // 107: clientpb.DllHijack
(*BackdoorReq)(nil), // 108: clientpb.BackdoorReq
(*Backdoor)(nil), // 109: clientpb.Backdoor
(*ShellcodeEncodeReq)(nil), // 110: clientpb.ShellcodeEncodeReq
(*ShellcodeEncode)(nil), // 111: clientpb.ShellcodeEncode
(*ShellcodeEncoderArchMap)(nil), // 112: clientpb.ShellcodeEncoderArchMap
(*ShellcodeEncoderMap)(nil), // 113: clientpb.ShellcodeEncoderMap
(*ExternalGenerateReq)(nil), // 114: clientpb.ExternalGenerateReq
(*Builders)(nil), // 115: clientpb.Builders
(*Builder)(nil), // 116: clientpb.Builder
(*HTTPC2Configs)(nil), // 117: clientpb.HTTPC2Configs
(*C2ProfileReq)(nil), // 118: clientpb.C2ProfileReq
(*HTTPC2ConfigReq)(nil), // 119: clientpb.HTTPC2ConfigReq
(*HTTPC2Config)(nil), // 120: clientpb.HTTPC2Config
(*HTTPC2ServerConfig)(nil), // 121: clientpb.HTTPC2ServerConfig
(*HTTPC2ImplantConfig)(nil), // 122: clientpb.HTTPC2ImplantConfig
(*HTTPC2Cookie)(nil), // 123: clientpb.HTTPC2Cookie
(*HTTPC2Header)(nil), // 124: clientpb.HTTPC2Header
(*HTTPC2URLParameter)(nil), // 125: clientpb.HTTPC2URLParameter
(*HTTPC2PathSegment)(nil), // 126: clientpb.HTTPC2PathSegment
(*Credential)(nil), // 127: clientpb.Credential
(*Credentials)(nil), // 128: clientpb.Credentials
(*Crackstations)(nil), // 129: clientpb.Crackstations
(*CrackstationStatus)(nil), // 130: clientpb.CrackstationStatus
(*CrackSyncStatus)(nil), // 131: clientpb.CrackSyncStatus
(*CrackBenchmark)(nil), // 132: clientpb.CrackBenchmark
(*CrackTask)(nil), // 133: clientpb.CrackTask
(*Crackstation)(nil), // 134: clientpb.Crackstation
(*CUDABackendInfo)(nil), // 135: clientpb.CUDABackendInfo
(*OpenCLBackendInfo)(nil), // 136: clientpb.OpenCLBackendInfo
(*MetalBackendInfo)(nil), // 137: clientpb.MetalBackendInfo
(*CrackCommand)(nil), // 138: clientpb.CrackCommand
(*CrackJob)(nil), // 139: clientpb.CrackJob
(*CrackResponse)(nil), // 140: clientpb.CrackResponse
(*CrackConfig)(nil), // 141: clientpb.CrackConfig
(*CrackFiles)(nil), // 142: clientpb.CrackFiles
(*CrackFile)(nil), // 143: clientpb.CrackFile
(*CrackFileChunk)(nil), // 144: clientpb.CrackFileChunk
(*AIProviderConfigs)(nil), // 145: clientpb.AIProviderConfigs
(*AIProviderConfig)(nil), // 146: clientpb.AIProviderConfig
(*AIConfigSummary)(nil), // 147: clientpb.AIConfigSummary
(*AIConversationReq)(nil), // 148: clientpb.AIConversationReq
(*AIConversations)(nil), // 149: clientpb.AIConversations
(*AIContextWindowUsage)(nil), // 150: clientpb.AIContextWindowUsage
(*AIConversation)(nil), // 151: clientpb.AIConversation
(*AIConversationMessages)(nil), // 152: clientpb.AIConversationMessages
(*AIConversationMessage)(nil), // 153: clientpb.AIConversationMessage
(*AIConversationEvent)(nil), // 154: clientpb.AIConversationEvent
(*MonitoringProviders)(nil), // 155: clientpb.MonitoringProviders
(*MonitoringProvider)(nil), // 156: clientpb.MonitoringProvider
(*ResourceID)(nil), // 157: clientpb.ResourceID
(*CertificatesReq)(nil), // 158: clientpb.CertificatesReq
(*CertificateData)(nil), // 159: clientpb.CertificateData
(*CertificateInfo)(nil), // 160: clientpb.CertificateInfo
(*CertificateAuthorityData)(nil), // 161: clientpb.CertificateAuthorityData
(*CertificateAuthorityInfo)(nil), // 162: clientpb.CertificateAuthorityInfo
nil, // 163: clientpb.TrafficEncoderMap.EncodersEntry
nil, // 164: clientpb.ExternalImplantConfig.EncodersEntry
nil, // 165: clientpb.ImplantBuilds.ConfigsEntry
nil, // 166: clientpb.ImplantBuilds.ResourceIDsEntry
nil, // 167: clientpb.ImplantBuilds.StagedEntry
nil, // 168: clientpb.WebsiteAddContent.ContentsEntry
nil, // 169: clientpb.Website.ContentsEntry
nil, // 170: clientpb.Host.ExtensionDataEntry
nil, // 171: clientpb.ShellcodeEncoderArchMap.EncodersEntry
nil, // 172: clientpb.ShellcodeEncoderArchMap.DescriptionsEntry
nil, // 173: clientpb.ShellcodeEncoderMap.EncodersEntry
nil, // 174: clientpb.CrackSyncStatus.ProgressEntry
nil, // 175: clientpb.CrackBenchmark.BenchmarksEntry
nil, // 176: clientpb.Crackstation.BenchmarksEntry
(*commonpb.File)(nil), // 177: commonpb.File
(*commonpb.Request)(nil), // 178: commonpb.Request
(*commonpb.Response)(nil), // 179: commonpb.Response
}
var file_clientpb_client_proto_depIdxs = []int32{
21, // 0: clientpb.Beacons.Beacons:type_name -> clientpb.Beacon
23, // 1: clientpb.BeaconTasks.Tasks:type_name -> clientpb.BeaconTask
44, // 2: clientpb.ImplantConfig.ImplantBuilds:type_name -> clientpb.ImplantBuild
26, // 3: clientpb.ImplantConfig.C2:type_name -> clientpb.ImplantC2
22, // 0: clientpb.Beacons.Beacons:type_name -> clientpb.Beacon
24, // 1: clientpb.BeaconTasks.Tasks:type_name -> clientpb.BeaconTask
45, // 2: clientpb.ImplantConfig.ImplantBuilds:type_name -> clientpb.ImplantBuild
27, // 3: clientpb.ImplantConfig.C2:type_name -> clientpb.ImplantC2
0, // 4: clientpb.ImplantConfig.Format:type_name -> clientpb.OutputFormat
27, // 5: clientpb.ImplantConfig.ShellcodeConfig:type_name -> clientpb.ShellcodeConfig
28, // 5: clientpb.ImplantConfig.ShellcodeConfig:type_name -> clientpb.ShellcodeConfig
3, // 6: clientpb.ImplantConfig.ShellcodeEncoder:type_name -> clientpb.ShellcodeEncoder
176, // 7: clientpb.ImplantConfig.Assets:type_name -> commonpb.File
29, // 8: clientpb.PESpoofMetadataConfig.Source:type_name -> clientpb.SpoofMetadataFile
29, // 9: clientpb.PESpoofMetadataConfig.Icon:type_name -> clientpb.SpoofMetadataFile
30, // 10: clientpb.PESpoofMetadataConfig.ResourceDirectory:type_name -> clientpb.IMAGE_RESOURCE_DIRECTORY
31, // 11: clientpb.PESpoofMetadataConfig.ResourceDirectoryEntries:type_name -> clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY
32, // 12: clientpb.PESpoofMetadataConfig.ResourceDataEntries:type_name -> clientpb.IMAGE_RESOURCE_DATA_ENTRY
33, // 13: clientpb.PESpoofMetadataConfig.ExportDirectory:type_name -> clientpb.IMAGE_EXPORT_DIRECTORY
34, // 14: clientpb.SpoofMetadataConfig.PE:type_name -> clientpb.PESpoofMetadataConfig
176, // 15: clientpb.TrafficEncoder.Wasm:type_name -> commonpb.File
162, // 16: clientpb.TrafficEncoderMap.Encoders:type_name -> clientpb.TrafficEncoderMap.EncodersEntry
36, // 17: clientpb.TrafficEncoderTests.Encoder:type_name -> clientpb.TrafficEncoder
38, // 18: clientpb.TrafficEncoderTests.Tests:type_name -> clientpb.TrafficEncoderTest
28, // 19: clientpb.ExternalImplantConfig.Config:type_name -> clientpb.ImplantConfig
44, // 20: clientpb.ExternalImplantConfig.Build:type_name -> clientpb.ImplantBuild
119, // 21: clientpb.ExternalImplantConfig.HTTPC2:type_name -> clientpb.HTTPC2Config
163, // 22: clientpb.ExternalImplantConfig.encoders:type_name -> clientpb.ExternalImplantConfig.EncodersEntry
176, // 23: clientpb.ExternalImplantBinary.File:type_name -> commonpb.File
164, // 24: clientpb.ImplantBuilds.Configs:type_name -> clientpb.ImplantBuilds.ConfigsEntry
165, // 25: clientpb.ImplantBuilds.ResourceIDs:type_name -> clientpb.ImplantBuilds.ResourceIDsEntry
166, // 26: clientpb.ImplantBuilds.staged:type_name -> clientpb.ImplantBuilds.StagedEntry
0, // 27: clientpb.CompilerTarget.Format:type_name -> clientpb.OutputFormat
45, // 28: clientpb.Compiler.Targets:type_name -> clientpb.CompilerTarget
46, // 29: clientpb.Compiler.CrossCompilers:type_name -> clientpb.CrossCompiler
45, // 30: clientpb.Compiler.UnsupportedTargets:type_name -> clientpb.CompilerTarget
49, // 31: clientpb.Canaries.Canaries:type_name -> clientpb.DNSCanary
28, // 32: clientpb.ImplantProfile.Config:type_name -> clientpb.ImplantConfig
52, // 33: clientpb.ImplantProfiles.Profiles:type_name -> clientpb.ImplantProfile
55, // 34: clientpb.Jobs.Active:type_name -> clientpb.Job
62, // 35: clientpb.ListenerJob.MTLSConf:type_name -> clientpb.MTLSListenerReq
63, // 36: clientpb.ListenerJob.WGConf:type_name -> clientpb.WGListenerReq
64, // 37: clientpb.ListenerJob.DNSConf:type_name -> clientpb.DNSListenerReq
65, // 38: clientpb.ListenerJob.HTTPConf:type_name -> clientpb.HTTPListenerReq
61, // 39: clientpb.ListenerJob.MultiConf:type_name -> clientpb.MultiplayerListenerReq
78, // 40: clientpb.ListenerJob.TCPConf:type_name -> clientpb.StagerListenerReq
177, // 41: clientpb.NamedPipesReq.Request:type_name -> commonpb.Request
178, // 42: clientpb.NamedPipes.Response:type_name -> commonpb.Response
177, // 43: clientpb.TCPPivotReq.Request:type_name -> commonpb.Request
178, // 44: clientpb.TCPPivot.Response:type_name -> commonpb.Response
20, // 45: clientpb.Sessions.Sessions:type_name -> clientpb.Session
28, // 46: clientpb.GenerateReq.Config:type_name -> clientpb.ImplantConfig
176, // 47: clientpb.Generate.File:type_name -> commonpb.File
35, // 48: clientpb.GenerateSpoofMetadataReq.SpoofMetadata:type_name -> clientpb.SpoofMetadataConfig
177, // 49: clientpb.MSFReq.Request:type_name -> commonpb.Request
177, // 50: clientpb.MSFRemoteReq.Request:type_name -> commonpb.Request
1, // 51: clientpb.StagerListenerReq.Protocol:type_name -> clientpb.StageProtocol
28, // 52: clientpb.GetSystemReq.Config:type_name -> clientpb.ImplantConfig
177, // 53: clientpb.GetSystemReq.Request:type_name -> commonpb.Request
28, // 54: clientpb.MigrateReq.Config:type_name -> clientpb.ImplantConfig
3, // 55: clientpb.MigrateReq.Encoder:type_name -> clientpb.ShellcodeEncoder
177, // 56: clientpb.MigrateReq.Request:type_name -> commonpb.Request
177, // 57: clientpb.CreateTunnelReq.Request:type_name -> commonpb.Request
177, // 58: clientpb.CloseTunnelReq.Request:type_name -> commonpb.Request
20, // 59: clientpb.PivotGraphEntry.Session:type_name -> clientpb.Session
87, // 60: clientpb.PivotGraphEntry.Children:type_name -> clientpb.PivotGraphEntry
87, // 61: clientpb.PivotGraph.Children:type_name -> clientpb.PivotGraphEntry
92, // 62: clientpb.Client.Operator:type_name -> clientpb.Operator
20, // 63: clientpb.Event.Session:type_name -> clientpb.Session
55, // 64: clientpb.Event.Job:type_name -> clientpb.Job
89, // 65: clientpb.Event.Client:type_name -> clientpb.Client
92, // 66: clientpb.Operators.Operators:type_name -> clientpb.Operator
167, // 67: clientpb.WebsiteAddContent.Contents:type_name -> clientpb.WebsiteAddContent.ContentsEntry
168, // 68: clientpb.Website.Contents:type_name -> clientpb.Website.ContentsEntry
96, // 69: clientpb.Websites.Websites:type_name -> clientpb.Website
2, // 70: clientpb.Loot.FileType:type_name -> clientpb.FileType
176, // 71: clientpb.Loot.File:type_name -> commonpb.File
99, // 72: clientpb.AllLoot.Loot:type_name -> clientpb.Loot
101, // 73: clientpb.Host.IOCs:type_name -> clientpb.IOC
169, // 74: clientpb.Host.ExtensionData:type_name -> clientpb.Host.ExtensionDataEntry
103, // 75: clientpb.AllHosts.Hosts:type_name -> clientpb.Host
177, // 76: clientpb.DllHijackReq.Request:type_name -> commonpb.Request
178, // 77: clientpb.DllHijack.Response:type_name -> commonpb.Response
177, // 78: clientpb.BackdoorReq.Request:type_name -> commonpb.Request
178, // 79: clientpb.Backdoor.Response:type_name -> commonpb.Response
3, // 80: clientpb.ShellcodeEncodeReq.Encoder:type_name -> clientpb.ShellcodeEncoder
177, // 81: clientpb.ShellcodeEncodeReq.Request:type_name -> commonpb.Request
178, // 82: clientpb.ShellcodeEncode.Response:type_name -> commonpb.Response
170, // 83: clientpb.ShellcodeEncoderArchMap.Encoders:type_name -> clientpb.ShellcodeEncoderArchMap.EncodersEntry
171, // 84: clientpb.ShellcodeEncoderArchMap.Descriptions:type_name -> clientpb.ShellcodeEncoderArchMap.DescriptionsEntry
172, // 85: clientpb.ShellcodeEncoderMap.Encoders:type_name -> clientpb.ShellcodeEncoderMap.EncodersEntry
28, // 86: clientpb.ExternalGenerateReq.Config:type_name -> clientpb.ImplantConfig
115, // 87: clientpb.Builders.Builders:type_name -> clientpb.Builder
45, // 88: clientpb.Builder.Targets:type_name -> clientpb.CompilerTarget
46, // 89: clientpb.Builder.CrossCompilers:type_name -> clientpb.CrossCompiler
119, // 90: clientpb.HTTPC2Configs.configs:type_name -> clientpb.HTTPC2Config
119, // 91: clientpb.HTTPC2ConfigReq.C2Config:type_name -> clientpb.HTTPC2Config
120, // 92: clientpb.HTTPC2Config.ServerConfig:type_name -> clientpb.HTTPC2ServerConfig
121, // 93: clientpb.HTTPC2Config.ImplantConfig:type_name -> clientpb.HTTPC2ImplantConfig
123, // 94: clientpb.HTTPC2ServerConfig.Headers:type_name -> clientpb.HTTPC2Header
122, // 95: clientpb.HTTPC2ServerConfig.Cookies:type_name -> clientpb.HTTPC2Cookie
124, // 96: clientpb.HTTPC2ImplantConfig.ExtraURLParameters:type_name -> clientpb.HTTPC2URLParameter
123, // 97: clientpb.HTTPC2ImplantConfig.Headers:type_name -> clientpb.HTTPC2Header
125, // 98: clientpb.HTTPC2ImplantConfig.PathSegments:type_name -> clientpb.HTTPC2PathSegment
5, // 99: clientpb.Credential.HashType:type_name -> clientpb.HashType
126, // 100: clientpb.Credentials.Credentials:type_name -> clientpb.Credential
133, // 101: clientpb.Crackstations.Crackstations:type_name -> clientpb.Crackstation
6, // 102: clientpb.CrackstationStatus.State:type_name -> clientpb.States
130, // 103: clientpb.CrackstationStatus.Syncing:type_name -> clientpb.CrackSyncStatus
173, // 104: clientpb.CrackSyncStatus.Progress:type_name -> clientpb.CrackSyncStatus.ProgressEntry
174, // 105: clientpb.CrackBenchmark.Benchmarks:type_name -> clientpb.CrackBenchmark.BenchmarksEntry
137, // 106: clientpb.CrackTask.Command:type_name -> clientpb.CrackCommand
175, // 107: clientpb.Crackstation.Benchmarks:type_name -> clientpb.Crackstation.BenchmarksEntry
134, // 108: clientpb.Crackstation.CUDA:type_name -> clientpb.CUDABackendInfo
136, // 109: clientpb.Crackstation.Metal:type_name -> clientpb.MetalBackendInfo
135, // 110: clientpb.Crackstation.OpenCL:type_name -> clientpb.OpenCLBackendInfo
8, // 111: clientpb.CrackCommand.AttackMode:type_name -> clientpb.CrackAttackMode
5, // 112: clientpb.CrackCommand.HashType:type_name -> clientpb.HashType
10, // 113: clientpb.CrackCommand.OutfileFormat:type_name -> clientpb.CrackOutfileFormat
9, // 114: clientpb.CrackCommand.EncodingFrom:type_name -> clientpb.CrackEncoding
9, // 115: clientpb.CrackCommand.EncodingTo:type_name -> clientpb.CrackEncoding
11, // 116: clientpb.CrackCommand.WorkloadProfile:type_name -> clientpb.CrackWorkloadProfile
7, // 117: clientpb.CrackJob.Status:type_name -> clientpb.CrackJobStatus
137, // 118: clientpb.CrackJob.Command:type_name -> clientpb.CrackCommand
138, // 119: clientpb.CrackResponse.Job:type_name -> clientpb.CrackJob
142, // 120: clientpb.CrackFiles.Files:type_name -> clientpb.CrackFile
12, // 121: clientpb.CrackFile.Type:type_name -> clientpb.CrackFileType
143, // 122: clientpb.CrackFile.Chunks:type_name -> clientpb.CrackFileChunk
145, // 123: clientpb.AIProviderConfigs.Providers:type_name -> clientpb.AIProviderConfig
146, // 124: clientpb.AIProviderConfigs.Config:type_name -> clientpb.AIConfigSummary
150, // 125: clientpb.AIConversations.Conversations:type_name -> clientpb.AIConversation
152, // 126: clientpb.AIConversation.Messages:type_name -> clientpb.AIConversationMessage
13, // 127: clientpb.AIConversation.TurnState:type_name -> clientpb.AIConversationTurnState
149, // 128: clientpb.AIConversation.ContextWindowUsage:type_name -> clientpb.AIContextWindowUsage
152, // 129: clientpb.AIConversationMessages.Messages:type_name -> clientpb.AIConversationMessage
14, // 130: clientpb.AIConversationMessage.Kind:type_name -> clientpb.AIConversationMessageKind
15, // 131: clientpb.AIConversationMessage.Visibility:type_name -> clientpb.AIConversationMessageVisibility
16, // 132: clientpb.AIConversationMessage.State:type_name -> clientpb.AIConversationMessageState
17, // 133: clientpb.AIConversationEvent.EventType:type_name -> clientpb.AIConversationEventType
150, // 134: clientpb.AIConversationEvent.Conversation:type_name -> clientpb.AIConversation
152, // 135: clientpb.AIConversationEvent.Message:type_name -> clientpb.AIConversationMessage
155, // 136: clientpb.MonitoringProviders.providers:type_name -> clientpb.MonitoringProvider
158, // 137: clientpb.CertificateInfo.info:type_name -> clientpb.CertificateData
160, // 138: clientpb.CertificateAuthorityInfo.info:type_name -> clientpb.CertificateAuthorityData
36, // 139: clientpb.TrafficEncoderMap.EncodersEntry.value:type_name -> clientpb.TrafficEncoder
28, // 140: clientpb.ImplantBuilds.ConfigsEntry.value:type_name -> clientpb.ImplantConfig
156, // 141: clientpb.ImplantBuilds.ResourceIDsEntry.value:type_name -> clientpb.ResourceID
93, // 142: clientpb.WebsiteAddContent.ContentsEntry.value:type_name -> clientpb.WebContent
93, // 143: clientpb.Website.ContentsEntry.value:type_name -> clientpb.WebContent
102, // 144: clientpb.Host.ExtensionDataEntry.value:type_name -> clientpb.ExtensionData
3, // 145: clientpb.ShellcodeEncoderArchMap.EncodersEntry.value:type_name -> clientpb.ShellcodeEncoder
111, // 146: clientpb.ShellcodeEncoderMap.EncodersEntry.value:type_name -> clientpb.ShellcodeEncoderArchMap
147, // [147:147] is the sub-list for method output_type
147, // [147:147] is the sub-list for method input_type
147, // [147:147] is the sub-list for extension type_name
147, // [147:147] is the sub-list for extension extendee
0, // [0:147] is the sub-list for field type_name
18, // 7: clientpb.ImplantConfig.ControlFlow:type_name -> clientpb.ControlFlowPolicy
177, // 8: clientpb.ImplantConfig.Assets:type_name -> commonpb.File
30, // 9: clientpb.PESpoofMetadataConfig.Source:type_name -> clientpb.SpoofMetadataFile
30, // 10: clientpb.PESpoofMetadataConfig.Icon:type_name -> clientpb.SpoofMetadataFile
31, // 11: clientpb.PESpoofMetadataConfig.ResourceDirectory:type_name -> clientpb.IMAGE_RESOURCE_DIRECTORY
32, // 12: clientpb.PESpoofMetadataConfig.ResourceDirectoryEntries:type_name -> clientpb.IMAGE_RESOURCE_DIRECTORY_ENTRY
33, // 13: clientpb.PESpoofMetadataConfig.ResourceDataEntries:type_name -> clientpb.IMAGE_RESOURCE_DATA_ENTRY
34, // 14: clientpb.PESpoofMetadataConfig.ExportDirectory:type_name -> clientpb.IMAGE_EXPORT_DIRECTORY
35, // 15: clientpb.SpoofMetadataConfig.PE:type_name -> clientpb.PESpoofMetadataConfig
177, // 16: clientpb.TrafficEncoder.Wasm:type_name -> commonpb.File
163, // 17: clientpb.TrafficEncoderMap.Encoders:type_name -> clientpb.TrafficEncoderMap.EncodersEntry
37, // 18: clientpb.TrafficEncoderTests.Encoder:type_name -> clientpb.TrafficEncoder
39, // 19: clientpb.TrafficEncoderTests.Tests:type_name -> clientpb.TrafficEncoderTest
29, // 20: clientpb.ExternalImplantConfig.Config:type_name -> clientpb.ImplantConfig
45, // 21: clientpb.ExternalImplantConfig.Build:type_name -> clientpb.ImplantBuild
120, // 22: clientpb.ExternalImplantConfig.HTTPC2:type_name -> clientpb.HTTPC2Config
164, // 23: clientpb.ExternalImplantConfig.encoders:type_name -> clientpb.ExternalImplantConfig.EncodersEntry
177, // 24: clientpb.ExternalImplantBinary.File:type_name -> commonpb.File
165, // 25: clientpb.ImplantBuilds.Configs:type_name -> clientpb.ImplantBuilds.ConfigsEntry
166, // 26: clientpb.ImplantBuilds.ResourceIDs:type_name -> clientpb.ImplantBuilds.ResourceIDsEntry
167, // 27: clientpb.ImplantBuilds.staged:type_name -> clientpb.ImplantBuilds.StagedEntry
0, // 28: clientpb.CompilerTarget.Format:type_name -> clientpb.OutputFormat
46, // 29: clientpb.Compiler.Targets:type_name -> clientpb.CompilerTarget
47, // 30: clientpb.Compiler.CrossCompilers:type_name -> clientpb.CrossCompiler
46, // 31: clientpb.Compiler.UnsupportedTargets:type_name -> clientpb.CompilerTarget
50, // 32: clientpb.Canaries.Canaries:type_name -> clientpb.DNSCanary
29, // 33: clientpb.ImplantProfile.Config:type_name -> clientpb.ImplantConfig
53, // 34: clientpb.ImplantProfiles.Profiles:type_name -> clientpb.ImplantProfile
56, // 35: clientpb.Jobs.Active:type_name -> clientpb.Job
63, // 36: clientpb.ListenerJob.MTLSConf:type_name -> clientpb.MTLSListenerReq
64, // 37: clientpb.ListenerJob.WGConf:type_name -> clientpb.WGListenerReq
65, // 38: clientpb.ListenerJob.DNSConf:type_name -> clientpb.DNSListenerReq
66, // 39: clientpb.ListenerJob.HTTPConf:type_name -> clientpb.HTTPListenerReq
62, // 40: clientpb.ListenerJob.MultiConf:type_name -> clientpb.MultiplayerListenerReq
79, // 41: clientpb.ListenerJob.TCPConf:type_name -> clientpb.StagerListenerReq
178, // 42: clientpb.NamedPipesReq.Request:type_name -> commonpb.Request
179, // 43: clientpb.NamedPipes.Response:type_name -> commonpb.Response
178, // 44: clientpb.TCPPivotReq.Request:type_name -> commonpb.Request
179, // 45: clientpb.TCPPivot.Response:type_name -> commonpb.Response
21, // 46: clientpb.Sessions.Sessions:type_name -> clientpb.Session
29, // 47: clientpb.GenerateReq.Config:type_name -> clientpb.ImplantConfig
177, // 48: clientpb.Generate.File:type_name -> commonpb.File
36, // 49: clientpb.GenerateSpoofMetadataReq.SpoofMetadata:type_name -> clientpb.SpoofMetadataConfig
178, // 50: clientpb.MSFReq.Request:type_name -> commonpb.Request
178, // 51: clientpb.MSFRemoteReq.Request:type_name -> commonpb.Request
1, // 52: clientpb.StagerListenerReq.Protocol:type_name -> clientpb.StageProtocol
29, // 53: clientpb.GetSystemReq.Config:type_name -> clientpb.ImplantConfig
178, // 54: clientpb.GetSystemReq.Request:type_name -> commonpb.Request
29, // 55: clientpb.MigrateReq.Config:type_name -> clientpb.ImplantConfig
3, // 56: clientpb.MigrateReq.Encoder:type_name -> clientpb.ShellcodeEncoder
178, // 57: clientpb.MigrateReq.Request:type_name -> commonpb.Request
178, // 58: clientpb.CreateTunnelReq.Request:type_name -> commonpb.Request
178, // 59: clientpb.CloseTunnelReq.Request:type_name -> commonpb.Request
21, // 60: clientpb.PivotGraphEntry.Session:type_name -> clientpb.Session
88, // 61: clientpb.PivotGraphEntry.Children:type_name -> clientpb.PivotGraphEntry
88, // 62: clientpb.PivotGraph.Children:type_name -> clientpb.PivotGraphEntry
93, // 63: clientpb.Client.Operator:type_name -> clientpb.Operator
21, // 64: clientpb.Event.Session:type_name -> clientpb.Session
56, // 65: clientpb.Event.Job:type_name -> clientpb.Job
90, // 66: clientpb.Event.Client:type_name -> clientpb.Client
93, // 67: clientpb.Operators.Operators:type_name -> clientpb.Operator
168, // 68: clientpb.WebsiteAddContent.Contents:type_name -> clientpb.WebsiteAddContent.ContentsEntry
169, // 69: clientpb.Website.Contents:type_name -> clientpb.Website.ContentsEntry
97, // 70: clientpb.Websites.Websites:type_name -> clientpb.Website
2, // 71: clientpb.Loot.FileType:type_name -> clientpb.FileType
177, // 72: clientpb.Loot.File:type_name -> commonpb.File
100, // 73: clientpb.AllLoot.Loot:type_name -> clientpb.Loot
102, // 74: clientpb.Host.IOCs:type_name -> clientpb.IOC
170, // 75: clientpb.Host.ExtensionData:type_name -> clientpb.Host.ExtensionDataEntry
104, // 76: clientpb.AllHosts.Hosts:type_name -> clientpb.Host
178, // 77: clientpb.DllHijackReq.Request:type_name -> commonpb.Request
179, // 78: clientpb.DllHijack.Response:type_name -> commonpb.Response
178, // 79: clientpb.BackdoorReq.Request:type_name -> commonpb.Request
179, // 80: clientpb.Backdoor.Response:type_name -> commonpb.Response
3, // 81: clientpb.ShellcodeEncodeReq.Encoder:type_name -> clientpb.ShellcodeEncoder
178, // 82: clientpb.ShellcodeEncodeReq.Request:type_name -> commonpb.Request
179, // 83: clientpb.ShellcodeEncode.Response:type_name -> commonpb.Response
171, // 84: clientpb.ShellcodeEncoderArchMap.Encoders:type_name -> clientpb.ShellcodeEncoderArchMap.EncodersEntry
172, // 85: clientpb.ShellcodeEncoderArchMap.Descriptions:type_name -> clientpb.ShellcodeEncoderArchMap.DescriptionsEntry
173, // 86: clientpb.ShellcodeEncoderMap.Encoders:type_name -> clientpb.ShellcodeEncoderMap.EncodersEntry
29, // 87: clientpb.ExternalGenerateReq.Config:type_name -> clientpb.ImplantConfig
116, // 88: clientpb.Builders.Builders:type_name -> clientpb.Builder
46, // 89: clientpb.Builder.Targets:type_name -> clientpb.CompilerTarget
47, // 90: clientpb.Builder.CrossCompilers:type_name -> clientpb.CrossCompiler
120, // 91: clientpb.HTTPC2Configs.configs:type_name -> clientpb.HTTPC2Config
120, // 92: clientpb.HTTPC2ConfigReq.C2Config:type_name -> clientpb.HTTPC2Config
121, // 93: clientpb.HTTPC2Config.ServerConfig:type_name -> clientpb.HTTPC2ServerConfig
122, // 94: clientpb.HTTPC2Config.ImplantConfig:type_name -> clientpb.HTTPC2ImplantConfig
124, // 95: clientpb.HTTPC2ServerConfig.Headers:type_name -> clientpb.HTTPC2Header
123, // 96: clientpb.HTTPC2ServerConfig.Cookies:type_name -> clientpb.HTTPC2Cookie
125, // 97: clientpb.HTTPC2ImplantConfig.ExtraURLParameters:type_name -> clientpb.HTTPC2URLParameter
124, // 98: clientpb.HTTPC2ImplantConfig.Headers:type_name -> clientpb.HTTPC2Header
126, // 99: clientpb.HTTPC2ImplantConfig.PathSegments:type_name -> clientpb.HTTPC2PathSegment
5, // 100: clientpb.Credential.HashType:type_name -> clientpb.HashType
127, // 101: clientpb.Credentials.Credentials:type_name -> clientpb.Credential
134, // 102: clientpb.Crackstations.Crackstations:type_name -> clientpb.Crackstation
6, // 103: clientpb.CrackstationStatus.State:type_name -> clientpb.States
131, // 104: clientpb.CrackstationStatus.Syncing:type_name -> clientpb.CrackSyncStatus
174, // 105: clientpb.CrackSyncStatus.Progress:type_name -> clientpb.CrackSyncStatus.ProgressEntry
175, // 106: clientpb.CrackBenchmark.Benchmarks:type_name -> clientpb.CrackBenchmark.BenchmarksEntry
138, // 107: clientpb.CrackTask.Command:type_name -> clientpb.CrackCommand
176, // 108: clientpb.Crackstation.Benchmarks:type_name -> clientpb.Crackstation.BenchmarksEntry
135, // 109: clientpb.Crackstation.CUDA:type_name -> clientpb.CUDABackendInfo
137, // 110: clientpb.Crackstation.Metal:type_name -> clientpb.MetalBackendInfo
136, // 111: clientpb.Crackstation.OpenCL:type_name -> clientpb.OpenCLBackendInfo
8, // 112: clientpb.CrackCommand.AttackMode:type_name -> clientpb.CrackAttackMode
5, // 113: clientpb.CrackCommand.HashType:type_name -> clientpb.HashType
10, // 114: clientpb.CrackCommand.OutfileFormat:type_name -> clientpb.CrackOutfileFormat
9, // 115: clientpb.CrackCommand.EncodingFrom:type_name -> clientpb.CrackEncoding
9, // 116: clientpb.CrackCommand.EncodingTo:type_name -> clientpb.CrackEncoding
11, // 117: clientpb.CrackCommand.WorkloadProfile:type_name -> clientpb.CrackWorkloadProfile
7, // 118: clientpb.CrackJob.Status:type_name -> clientpb.CrackJobStatus
138, // 119: clientpb.CrackJob.Command:type_name -> clientpb.CrackCommand
139, // 120: clientpb.CrackResponse.Job:type_name -> clientpb.CrackJob
143, // 121: clientpb.CrackFiles.Files:type_name -> clientpb.CrackFile
12, // 122: clientpb.CrackFile.Type:type_name -> clientpb.CrackFileType
144, // 123: clientpb.CrackFile.Chunks:type_name -> clientpb.CrackFileChunk
146, // 124: clientpb.AIProviderConfigs.Providers:type_name -> clientpb.AIProviderConfig
147, // 125: clientpb.AIProviderConfigs.Config:type_name -> clientpb.AIConfigSummary
151, // 126: clientpb.AIConversations.Conversations:type_name -> clientpb.AIConversation
153, // 127: clientpb.AIConversation.Messages:type_name -> clientpb.AIConversationMessage
13, // 128: clientpb.AIConversation.TurnState:type_name -> clientpb.AIConversationTurnState
150, // 129: clientpb.AIConversation.ContextWindowUsage:type_name -> clientpb.AIContextWindowUsage
153, // 130: clientpb.AIConversationMessages.Messages:type_name -> clientpb.AIConversationMessage
14, // 131: clientpb.AIConversationMessage.Kind:type_name -> clientpb.AIConversationMessageKind
15, // 132: clientpb.AIConversationMessage.Visibility:type_name -> clientpb.AIConversationMessageVisibility
16, // 133: clientpb.AIConversationMessage.State:type_name -> clientpb.AIConversationMessageState
17, // 134: clientpb.AIConversationEvent.EventType:type_name -> clientpb.AIConversationEventType
151, // 135: clientpb.AIConversationEvent.Conversation:type_name -> clientpb.AIConversation
153, // 136: clientpb.AIConversationEvent.Message:type_name -> clientpb.AIConversationMessage
156, // 137: clientpb.MonitoringProviders.providers:type_name -> clientpb.MonitoringProvider
159, // 138: clientpb.CertificateInfo.info:type_name -> clientpb.CertificateData
161, // 139: clientpb.CertificateAuthorityInfo.info:type_name -> clientpb.CertificateAuthorityData
37, // 140: clientpb.TrafficEncoderMap.EncodersEntry.value:type_name -> clientpb.TrafficEncoder
29, // 141: clientpb.ImplantBuilds.ConfigsEntry.value:type_name -> clientpb.ImplantConfig
157, // 142: clientpb.ImplantBuilds.ResourceIDsEntry.value:type_name -> clientpb.ResourceID
94, // 143: clientpb.WebsiteAddContent.ContentsEntry.value:type_name -> clientpb.WebContent
94, // 144: clientpb.Website.ContentsEntry.value:type_name -> clientpb.WebContent
103, // 145: clientpb.Host.ExtensionDataEntry.value:type_name -> clientpb.ExtensionData
3, // 146: clientpb.ShellcodeEncoderArchMap.EncodersEntry.value:type_name -> clientpb.ShellcodeEncoder
112, // 147: clientpb.ShellcodeEncoderMap.EncodersEntry.value:type_name -> clientpb.ShellcodeEncoderArchMap
148, // [148:148] is the sub-list for method output_type
148, // [148:148] is the sub-list for method input_type
148, // [148:148] is the sub-list for extension type_name
148, // [148:148] is the sub-list for extension extendee
0, // [0:148] is the sub-list for field type_name
}
func init() { file_clientpb_client_proto_init() }
@@ -14644,7 +14724,7 @@ func file_clientpb_client_proto_init() {
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_clientpb_client_proto_rawDesc), len(file_clientpb_client_proto_rawDesc)),
NumEnums: 18,
NumEnums: 19,
NumMessages: 158,
NumExtensions: 0,
NumServices: 0,
+10
View File
@@ -220,6 +220,7 @@ message ImplantConfig {
string Extension = 154;
reserved 155;
reserved "SpoofData";
ControlFlowPolicy ControlFlow = 156;
repeated commonpb.File Assets = 200;
}
@@ -387,6 +388,7 @@ message Compiler {
repeated CompilerTarget Targets = 3;
repeated CrossCompiler CrossCompilers = 4;
repeated CompilerTarget UnsupportedTargets = 5;
repeated string Capabilities = 6;
}
message DeleteReq { string Name = 1; }
@@ -844,6 +846,7 @@ message Builder {
repeated string Templates = 5;
repeated CompilerTarget Targets = 6;
repeated CrossCompiler CrossCompilers = 7;
repeated string Capabilities = 8;
}
@@ -1476,6 +1479,13 @@ enum AIConversationEventType {
AI_CONVERSATION_EVENT_TYPE_MESSAGE_COMPLETED = 8;
}
// ControlFlowPolicy selects a server-defined Garble control-flow obfuscation
// policy. Policy parameters are versioned so existing profiles remain stable.
enum ControlFlowPolicy {
CONTROL_FLOW_DISABLED = 0;
CONTROL_FLOW_BALANCED_V1 = 1;
}
message AIContextWindowUsage {
int64 InputTokens = 1;
int64 OutputTokens = 2;
+36 -8
View File
@@ -24,10 +24,12 @@ import (
"os/user"
"path"
"path/filepath"
"runtime"
"strings"
"github.com/bishopfox/sliver/server/log"
ver "github.com/bishopfox/sliver/server/version"
utilAssets "github.com/bishopfox/sliver/util/assets"
)
const (
@@ -105,19 +107,26 @@ func assetVersion() string {
return strings.TrimSpace(string(data))
}
func saveAssetVersion(appDir string) {
func expectedAssetVersion() string {
garbleDigest, ok := utilAssets.ExpectedGarbleSHA256(runtime.GOOS, runtime.GOARCH)
if !ok {
return ver.GitCommit
}
return ver.GitCommit + ":garble-" + garbleDigest
}
func saveAssetVersion(appDir, version string) error {
versionFilePath := filepath.Join(appDir, versionFileName)
fVer, _ := os.Create(versionFilePath)
defer fVer.Close()
fVer.Write([]byte(ver.GitCommit))
return os.WriteFile(versionFilePath, []byte(version), 0600)
}
// Setup - Extract or create local assets
func Setup(force bool, echo bool) {
appDir := GetRootAppDir()
localVer := assetVersion()
if force || localVer == "" || localVer != ver.GitCommit {
setupLog.Infof("Version mismatch %v != %v", localVer, ver.GitCommit)
expectedVer := expectedAssetVersion()
if force || localVer == "" || localVer != expectedVer {
setupLog.Infof("Version mismatch %v != %v", localVer, expectedVer)
if echo {
fmt.Printf(`
Sliver Copyright (C) 2026 Bishop Fox
@@ -126,13 +135,32 @@ This is free software, and you are welcome to redistribute it
under certain conditions; type 'licenses' for details.`)
fmt.Printf("\n\nUnpacking assets ...\n")
}
setupGo(appDir)
setupSucceeded := true
if err := setupGo(appDir); err != nil {
setupLog.Errorf("Failed to setup Go: %s", err)
setupSucceeded = false
}
err := setupZig(appDir)
if err != nil {
setupLog.Errorf("Failed to setup Zig: %s", err)
}
setupCodenames(appDir)
saveAssetVersion(appDir)
if setupSucceeded {
garbleName := "garble"
if runtime.GOOS == "windows" {
garbleName += ".exe"
}
garblePath := filepath.Join(appDir, GoDirName, "bin", garbleName)
if err := utilAssets.VerifyGarbleBinary(garblePath, runtime.GOOS, runtime.GOARCH); err != nil {
setupLog.Errorf("Failed to verify extracted Garble: %s", err)
setupSucceeded = false
}
}
if setupSucceeded {
if err := saveAssetVersion(appDir, expectedVer); err != nil {
setupLog.Errorf("Failed to save asset version: %s", err)
}
}
unpackDefaultTrafficEncoders(force)
}
setupLog.Infof("Initialized english encoder with %d words", len(English()))
+25
View File
@@ -0,0 +1,25 @@
package assets
import (
"runtime"
"strings"
"testing"
ver "github.com/bishopfox/sliver/server/version"
utilAssets "github.com/bishopfox/sliver/util/assets"
)
func TestExpectedAssetVersionIncludesGarbleManifest(t *testing.T) {
digest, ok := utilAssets.ExpectedGarbleSHA256(runtime.GOOS, runtime.GOARCH)
if !ok {
t.Skipf("no pinned Garble artifact for %s/%s", runtime.GOOS, runtime.GOARCH)
}
version := expectedAssetVersion()
if !strings.HasPrefix(version, ver.GitCommit+":garble-") {
t.Fatalf("expectedAssetVersion() = %q, want Git commit and Garble marker", version)
}
if !strings.HasSuffix(version, digest) {
t.Fatalf("expectedAssetVersion() = %q, want pinned digest %q", version, digest)
}
}
+21
View File
@@ -169,6 +169,17 @@ func (b *Builder) handleBuildEvent(event *clientpb.Event) {
})
return
}
if err := generate.ValidateControlFlowConfig(extConfig.Config); err != nil {
builderLog.Warnf("Reject event, invalid control-flow config: %s", err)
b.reportBuildFailure(implantBuildID, err)
return
}
if generate.ControlFlowEnabled(extConfig.Config) && !generate.HasControlFlowCapability(b.externalBuilder.Capabilities) {
err := fmt.Errorf("builder does not support %s", generate.ControlFlowCapability)
builderLog.Warnf("Reject event: %s", err)
b.reportBuildFailure(implantBuildID, err)
return
}
extModel := models.ImplantConfigFromProtobuf(extConfig.Config)
@@ -272,6 +283,16 @@ func (b *Builder) handleBuildEvent(event *clientpb.Event) {
builderLog.Infof("All done, built and saved %s", fileName)
}
func (b *Builder) reportBuildFailure(implantBuildID string, buildErr error) {
_, err := b.rpc.BuilderTrigger(context.Background(), &clientpb.Event{
EventType: consts.ExternalBuildFailedEvent,
Data: []byte(fmt.Sprintf("%s:%s", implantBuildID, buildErr.Error())),
})
if err != nil {
builderLog.Errorf("Failed to report build failure for %s: %s", implantBuildID, err)
}
}
func isSupportedTarget(targets []*clientpb.CompilerTarget, config *clientpb.ImplantConfig) bool {
for _, target := range targets {
if target.GOOS == config.GOOS && target.GOARCH == config.GOARCH {
+9 -1
View File
@@ -266,7 +266,15 @@ func createBuilder(cmd *cobra.Command, configPath string, mutex *sync.Mutex) (*b
}
func parseBuilderConfigFlags(cmd *cobra.Command) *clientpb.Builder {
externalBuilder := &clientpb.Builder{GOOS: runtime.GOOS, GOARCH: runtime.GOARCH}
externalBuilder := &clientpb.Builder{
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
}
if err := generate.ControlFlowRuntimeError(); err != nil {
builderLog.Warnf("Not advertising %s: %s", generate.ControlFlowCapability, err)
} else {
externalBuilder.Capabilities = []string{generate.ControlFlowCapability}
}
externalBuilder.CrossCompilers = generate.GetCrossCompilers()
builderLog.Infof("Found %d cross-compilers", len(externalBuilder.CrossCompilers))
+114 -10
View File
@@ -118,16 +118,19 @@ func ImplantBuilds() (*clientpb.ImplantBuilds, error) {
Staged: map[string]bool{},
}
for _, dbBuild := range builds {
resource, err := ResourceIDByName(dbBuild.Name)
if errors.Is(err, ErrRecordNotFound) {
continue
}
if err != nil {
return nil, err
}
config, err := ImplantConfigByID(dbBuild.ImplantConfigID.String())
if err != nil {
return nil, err
}
pbBuilds.Configs[dbBuild.Name] = config
resource, err := ResourceIDByName(dbBuild.Name)
if err != nil {
return nil, err
}
pbBuilds.ResourceIDs[dbBuild.Name] = resource
pbBuilds.Staged[dbBuild.Name] = dbBuild.Stage
@@ -138,20 +141,47 @@ func ImplantBuilds() (*clientpb.ImplantBuilds, error) {
// SaveImplantBuild
func SaveImplantBuild(ib *clientpb.ImplantBuild) (*clientpb.ImplantBuild, error) {
return saveImplantBuild(ib, nil)
}
// SaveImplantBuildWithSourceProfile saves an implant build while retaining the
// profile it was derived from. The build's ImplantConfigID still references an
// immutable per-build config snapshot.
func SaveImplantBuildWithSourceProfile(ib *clientpb.ImplantBuild, sourceProfileID string) (*clientpb.ImplantBuild, error) {
profileID, err := models.ParseUUID(sourceProfileID)
if err != nil || profileID == models.NilUUID() {
return nil, fmt.Errorf("invalid source implant profile ID %q", sourceProfileID)
}
return saveImplantBuild(ib, &profileID)
}
func saveImplantBuild(ib *clientpb.ImplantBuild, sourceProfileID *models.UUID) (*clientpb.ImplantBuild, error) {
if ib == nil {
return nil, errors.New("implant build cannot be nil")
}
dbSession := Session()
var implantBuild *models.ImplantBuild
if ib.ID != "" {
_, err := ImplantBuildByID(ib.ID)
if err != nil {
existingBuild := &models.ImplantBuild{}
buildID := models.ParseUUIDOrNil(ib.ID)
if buildID == models.NilUUID() {
return nil, ErrRecordNotFound
}
if err := dbSession.Where(&models.ImplantBuild{ID: buildID}).First(existingBuild).Error; err != nil {
return nil, err
}
implantBuild = models.ImplantBuildFromProtobuf(ib)
err = dbSession.Save(implantBuild).Error
implantBuild.SourceImplantProfileID = existingBuild.SourceImplantProfileID
if sourceProfileID != nil {
implantBuild.SourceImplantProfileID = sourceProfileID
}
err := dbSession.Save(implantBuild).Error
if err != nil {
return nil, err
}
} else {
implantBuild = models.ImplantBuildFromProtobuf(ib)
implantBuild.SourceImplantProfileID = sourceProfileID
err := dbSession.Create(&implantBuild).Error
if err != nil {
return nil, err
@@ -185,6 +215,70 @@ func SaveImplantConfig(ic *clientpb.ImplantConfig) (*clientpb.ImplantConfig, err
return implantConfig.ToProtobuf(), nil
}
// CreateImplantConfigWithID creates a new implant config using a caller-chosen
// UUID. This allows generated source to embed the final persistent config ID
// before compilation without leaving an orphan record when compilation fails.
func CreateImplantConfigWithID(ic *clientpb.ImplantConfig) (*clientpb.ImplantConfig, error) {
if ic == nil {
return nil, errors.New("implant config cannot be nil")
}
configID, err := models.ParseUUID(ic.ID)
if err != nil || configID == models.NilUUID() {
return nil, fmt.Errorf("invalid implant config ID %q", ic.ID)
}
implantConfig := models.ImplantConfigFromProtobuf(ic)
implantConfig.ID = configID
if err := Session().Create(implantConfig).Error; err != nil {
return nil, err
}
return implantConfig.ToProtobuf(), nil
}
// DeleteFailedImplantConfigSnapshot rolls back a newly-created, detached
// config only when the immediately subsequent build save failed before any
// ImplantBuild row could reference it. It must not be used for normal build or
// profile deletion because active implants retain the config ID as origin
// policy metadata.
func DeleteFailedImplantConfigSnapshot(id string) error {
configID := models.ParseUUIDOrNil(id)
if configID == models.NilUUID() {
return fmt.Errorf("invalid failed implant config snapshot ID %q", id)
}
return Session().Transaction(func(tx *gorm.DB) error {
config := &models.ImplantConfig{}
if err := tx.Where(&models.ImplantConfig{ID: configID}).First(config).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return err
}
if config.ImplantProfileID != nil {
return fmt.Errorf("refusing to delete profile implant config %q after build save failure", id)
}
var buildCount int64
if err := tx.Model(&models.ImplantBuild{}).Where("implant_config_id = ?", configID).Count(&buildCount).Error; err != nil {
return err
}
if buildCount != 0 {
return nil
}
for _, relation := range []interface{}{
&models.ImplantC2{},
&models.EncoderAsset{},
&models.CanaryDomain{},
} {
if err := tx.Where("implant_config_id = ?", configID).Delete(relation).Error; err != nil {
return err
}
}
return tx.Where("id = ? AND implant_profile_id IS NULL", configID).Delete(&models.ImplantConfig{}).Error
})
}
// ImplantBuildByName - Fetch implant build by name
func ImplantBuildByName(name string) (*clientpb.ImplantBuild, error) {
if len(name) < 1 {
@@ -693,10 +787,20 @@ func ProfileByName(name string) (*clientpb.ImplantProfile, error) {
return nil, ErrRecordNotFound
}
dbProfile := &models.ImplantProfile{}
err := Session().Preload("ImplantConfig").Where(&models.ImplantProfile{Name: name}).Find(dbProfile).Error
err := Session().Preload("ImplantConfig").Where(&models.ImplantProfile{Name: name}).First(dbProfile).Error
if err != nil {
return nil, err
}
dbBuilds := []*models.ImplantBuild{}
err = Session().Where(&models.ImplantBuild{ImplantConfigID: dbProfile.ImplantConfig.ID}).Find(&dbBuilds).Error
err = Session().Where(
"implant_config_id = ? OR source_implant_profile_id = ?",
dbProfile.ImplantConfig.ID,
dbProfile.ID,
).Find(&dbBuilds).Error
if err != nil {
return nil, err
}
builds := []*clientpb.ImplantBuild{}
for _, build := range dbBuilds {
builds = append(builds, build.ToProtobuf())
+2 -1
View File
@@ -55,7 +55,8 @@ type Beacon struct {
Integrity string
Capabilities uint64
ImplantBuildID UUID `gorm:"type:uuid;"`
ImplantBuildID UUID `gorm:"type:uuid;"`
ImplantConfigID UUID `gorm:"type:uuid;"`
Interval int64
Jitter int64
+8 -2
View File
@@ -52,7 +52,8 @@ type ImplantBuild struct {
// Resource ID referencing build
ImplantID uint64
ImplantConfigID UUID
ImplantConfigID UUID
SourceImplantProfileID *UUID
// ECC
PeerPublicKey string
@@ -159,6 +160,7 @@ type ImplantConfig struct {
DebugFile string
Evasion bool
ObfuscateSymbols bool
ControlFlow clientpb.ControlFlowPolicy
ReconnectInterval int64
PollTimeout int64
MaxConnectionErrors uint32
@@ -220,7 +222,9 @@ type ImplantConfig struct {
// BeforeCreate - GORM hook
func (ic *ImplantConfig) BeforeCreate(tx *gorm.DB) (err error) {
ic.ID = NewUUID()
if ic.ID == NilUUID() {
ic.ID = NewUUID()
}
ic.CreatedAt = time.Now()
return nil
}
@@ -257,6 +261,7 @@ func (ic *ImplantConfig) ToProtobuf() *clientpb.ImplantConfig {
DebugFile: ic.DebugFile,
Evasion: ic.Evasion,
ObfuscateSymbols: ic.ObfuscateSymbols,
ControlFlow: ic.ControlFlow,
TemplateName: ic.TemplateName,
SGNEnabled: ic.SGNEnabled,
ShellcodeEncoder: clientpb.ShellcodeEncoder(ic.ShellcodeEncoder),
@@ -461,6 +466,7 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) *ImplantConfig
cfg.Debug = pbConfig.Debug
cfg.Evasion = pbConfig.Evasion
cfg.ObfuscateSymbols = pbConfig.ObfuscateSymbols
cfg.ControlFlow = pbConfig.ControlFlow
cfg.TemplateName = pbConfig.TemplateName
if cfg.TemplateName == "" {
cfg.TemplateName = defaultTemplateName
@@ -0,0 +1,39 @@
package models
import (
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
func TestImplantConfigControlFlowRoundTrip(t *testing.T) {
policies := []clientpb.ControlFlowPolicy{
clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED,
clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
}
for _, policy := range policies {
t.Run(policy.String(), func(t *testing.T) {
model := ImplantConfigFromProtobuf(&clientpb.ImplantConfig{ControlFlow: policy})
if model.ControlFlow != policy {
t.Fatalf("model ControlFlow = %v, want %v", model.ControlFlow, policy)
}
got := model.ToProtobuf().ControlFlow
if got != policy {
t.Fatalf("protobuf ControlFlow = %v, want %v", got, policy)
}
})
}
}
func TestImplantConfigBeforeCreatePreservesAssignedID(t *testing.T) {
assignedID := NewUUID()
config := &ImplantConfig{ID: assignedID}
if err := config.BeforeCreate(nil); err != nil {
t.Fatalf("BeforeCreate() error = %v", err)
}
if config.ID != assignedID {
t.Fatalf("BeforeCreate() ID = %s, want assigned ID %s", config.ID, assignedID)
}
}
+75 -34
View File
@@ -198,14 +198,29 @@ func appendToLDFlags(ldflags []string, extra string) []string {
return []string{joined}
}
const nonDebugImplantLDFlags = "-s -w -buildid="
func implantLDFlags(config *clientpb.ImplantConfig) []string {
if config.Debug {
return nil
}
ldflags := []string{nonDebugImplantLDFlags}
if config.GOOS == WINDOWS {
ldflags = appendToLDFlags(ldflags, "-H=windowsgui")
}
return ldflags
}
const linuxShellcodeBuildIDLDFlag = "-extldflags=-Wl,--build-id"
func applyLinuxShellcodeBuildID(ldflags []string) []string {
// Garble removes Go's build ID. With Go 1.27, a native GNU c-shared link
// then has neither PT_NOTE nor PT_PHDR, leaving Malasada one program header
// short when it adds its entry stub, interpreter, and PT_PHDR. Ask the
// external linker for its own build-id note so Malasada has a PT_NOTE to
// repurpose without changing implant source or Malasada's conversion logic.
// Release builds remove Go's build ID, and Garble does the same. With Go
// 1.27, a native GNU c-shared link then has neither PT_NOTE nor PT_PHDR,
// leaving Malasada one program header short when it adds its entry stub,
// interpreter, and PT_PHDR. Ask the external linker for its own build-id note
// so Malasada has a PT_NOTE to repurpose without changing implant source or
// Malasada's conversion logic.
return appendToLDFlags(ldflags, linuxShellcodeBuildIDLDFlag)
}
@@ -374,8 +389,9 @@ func linuxShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.
HTTPPROXY: getGoHttpProxy(),
HTTPSPROXY: getGoHttpsProxy(),
Obfuscation: config.ObfuscateSymbols,
GOGARBLE: goGarble(config),
Obfuscation: config.ObfuscateSymbols,
ObfuscationControlFlow: controlFlowObfuscationEnabled(config),
GOGARBLE: goGarble(config),
}
sanitizeZigForBuild(goConfig, "c-shared")
buildLog.Infof(" CC: %s", goConfig.CC)
@@ -399,7 +415,7 @@ func linuxShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.
if config.NetGoEnabled {
tags = append(tags, "netgo")
}
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
ldflags := implantLDFlags(config)
ldflags = applyLinuxShellcodeBuildID(ldflags)
ldflags, wantStaticSO := applyZigStaticLinking(goConfig, "c-shared", ldflags)
gcFlags := ""
@@ -464,8 +480,9 @@ func darwinShellcode(name string, build *clientpb.ImplantBuild, config *clientpb
HTTPPROXY: getGoHttpProxy(),
HTTPSPROXY: getGoHttpsProxy(),
Obfuscation: config.ObfuscateSymbols,
GOGARBLE: goGarble(config),
Obfuscation: config.ObfuscateSymbols,
ObfuscationControlFlow: controlFlowObfuscationEnabled(config),
GOGARBLE: goGarble(config),
}
config.IsSharedLib = true
@@ -486,7 +503,7 @@ func darwinShellcode(name string, build *clientpb.ImplantBuild, config *clientpb
if config.NetGoEnabled {
tags = append(tags, "netgo")
}
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
ldflags := implantLDFlags(config)
// Keep those for potential later use
gcFlags := ""
asmFlags := ""
@@ -541,8 +558,9 @@ func windowsShellcode(name string, build *clientpb.ImplantBuild, config *clientp
HTTPPROXY: getGoHttpProxy(),
HTTPSPROXY: getGoHttpsProxy(),
Obfuscation: config.ObfuscateSymbols,
GOGARBLE: goGarble(config),
Obfuscation: config.ObfuscateSymbols,
ObfuscationControlFlow: controlFlowObfuscationEnabled(config),
GOGARBLE: goGarble(config),
}
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
if err != nil {
@@ -561,10 +579,7 @@ func windowsShellcode(name string, build *clientpb.ImplantBuild, config *clientp
if config.NetGoEnabled {
tags = append(tags, "netgo")
}
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
if !config.Debug && goConfig.GOOS == WINDOWS {
ldflags[0] += " -H=windowsgui"
}
ldflags := implantLDFlags(config)
// Keep those for potential later use
gcFlags := ""
asmFlags := ""
@@ -614,8 +629,9 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
HTTPPROXY: getGoHttpProxy(),
HTTPSPROXY: getGoHttpsProxy(),
Obfuscation: config.ObfuscateSymbols,
GOGARBLE: goGarble(config),
Obfuscation: config.ObfuscateSymbols,
ObfuscationControlFlow: controlFlowObfuscationEnabled(config),
GOGARBLE: goGarble(config),
}
sanitizeZigForBuild(goConfig, "c-shared")
buildLog.Infof(" CC: %s", goConfig.CC)
@@ -641,11 +657,8 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
if config.NetGoEnabled {
tags = append(tags, "netgo")
}
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
ldflags := implantLDFlags(config)
ldflags, wantStaticSO := applyZigStaticLinking(goConfig, "c-shared", ldflags)
if !config.Debug && goConfig.GOOS == WINDOWS {
ldflags[0] += " -H=windowsgui"
}
// Keep those for potential later use
gcFlags := ""
@@ -722,8 +735,9 @@ func SliverArchive(name string, build *clientpb.ImplantBuild, config *clientpb.I
HTTPPROXY: getGoHttpProxy(),
HTTPSPROXY: getGoHttpsProxy(),
Obfuscation: config.ObfuscateSymbols,
GOGARBLE: goGarble(config),
Obfuscation: config.ObfuscateSymbols,
ObfuscationControlFlow: controlFlowObfuscationEnabled(config),
GOGARBLE: goGarble(config),
}
sanitizeZigForBuild(goConfig, "c-archive")
buildLog.Infof(" CC: %s", goConfig.CC)
@@ -745,7 +759,7 @@ func SliverArchive(name string, build *clientpb.ImplantBuild, config *clientpb.I
if config.NetGoEnabled {
tags = append(tags, "netgo")
}
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
ldflags := implantLDFlags(config)
// Keep those for potential later use
gcFlags := ""
@@ -784,8 +798,9 @@ func SliverExecutable(name string, build *clientpb.ImplantBuild, config *clientp
HTTPPROXY: getGoHttpProxy(),
HTTPSPROXY: getGoHttpsProxy(),
Obfuscation: config.ObfuscateSymbols,
GOGARBLE: goGarble(config),
Obfuscation: config.ObfuscateSymbols,
ObfuscationControlFlow: controlFlowObfuscationEnabled(config),
GOGARBLE: goGarble(config),
}
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
@@ -801,15 +816,11 @@ func SliverExecutable(name string, build *clientpb.ImplantBuild, config *clientp
if config.NetGoEnabled {
tags = append(tags, "netgo")
}
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
if !config.Debug && goConfig.GOOS == WINDOWS {
ldflags[0] += " -H=windowsgui"
}
ldflags := implantLDFlags(config)
gcFlags := ""
asmFlags := ""
if config.Debug {
gcFlags = "all=-N -l"
ldflags = []string{}
}
_, err = gogo.GoBuild(*goConfig, pkgPath, dest, "", tags, ldflags, gcFlags, asmFlags)
if err != nil {
@@ -821,6 +832,12 @@ func SliverExecutable(name string, build *clientpb.ImplantBuild, config *clientp
// This function is a little too long, we should probably refactor it as some point
func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, goConfig *gogo.GoConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
controlFlowPolicy, err := controlFlowPolicyForConfig(config)
if err != nil {
return "", err
}
controlFlowFunctionsRendered := map[string]struct{}{}
target := fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH)
if _, ok := gogo.ValidCompilerTargets(*goConfig)[target]; !ok {
return "", fmt.Errorf("invalid compiler target: %s", target)
@@ -846,7 +863,7 @@ func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clien
// srcDir - ~/.sliver/slivers/<os>/<arch>/<name>/src
srcDir := filepath.Join(projectGoPathDir, "src")
assets.SetupGoPath(srcDir, config.IncludeDNS) // Extract GOPATH dependency files
err := util.ChmodR(srcDir, 0600, 0700) // Ensures src code files are writable
err = util.ChmodR(srcDir, 0600, 0700) // Ensures src code files are writable
if err != nil {
buildLog.Errorf("fs perms: %v", err)
return "", err
@@ -953,6 +970,18 @@ func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clien
buildLog.Errorf("Template execution error %s", err)
return err
}
if controlFlowPolicy != nil && path.Ext(f.Name()) == ".go" {
functionIDs, err := controlFlowFunctionsInSource(controlFlowPolicy, fsPath, buf.Bytes())
if err != nil {
return err
}
for _, functionID := range functionIDs {
if _, duplicate := controlFlowFunctionsRendered[functionID]; duplicate {
return fmt.Errorf("control-flow directive for %s rendered more than once", functionID)
}
controlFlowFunctionsRendered[functionID] = struct{}{}
}
}
// Render canaries
if len(config.CanaryDomains) > 0 {
@@ -979,6 +1008,14 @@ func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clien
if err != nil {
return "", err
}
if controlFlowPolicy != nil && len(controlFlowFunctionsRendered) != len(controlFlowPolicy.functions) {
return "", fmt.Errorf(
"control-flow policy %s rendered %d allowlisted functions, expected %d",
config.ControlFlow,
len(controlFlowFunctionsRendered),
len(controlFlowPolicy.functions),
)
}
// Render encoder assets
renderNativeEncoderAssets(build, config, sliverPkgDir)
@@ -1142,6 +1179,10 @@ func renderImplantEnglish() []string {
// GenerateConfig - Generate the keys/etc for the implant
func GenerateConfig(name string, implantConfig *clientpb.ImplantConfig) (*clientpb.ImplantBuild, error) {
if err := ValidateControlFlowConfig(implantConfig); err != nil {
return nil, err
}
var err error
// Cert PEM encoded certificates
+39
View File
@@ -22,6 +22,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
@@ -225,6 +226,44 @@ func TestSymbolObfuscation(t *testing.T) {
symbolObfuscation(t, "freebsd", "amd64")
}
func TestControlFlowObfuscation(t *testing.T) {
target := runtime.GOOS + "/" + runtime.GOARCH
if _, supported := SupportedCompilerTargets[target]; !supported {
t.Skipf("control-flow generator regression is unsupported on %s", target)
}
name := fmt.Sprintf("control_flow_test%d", nonce)
nonce++
config := &clientpb.ImplantConfig{
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
TemplateName: SliverTemplateName,
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
Format: clientpb.OutputFormat_EXECUTABLE,
IncludeMTLS: true,
ConnectionStrategy: "s",
C2: []*clientpb.ImplantC2{
{URL: "mtls://127.0.0.1:31337"},
},
}
build, err := GenerateConfig(name, config)
if err != nil {
t.Fatalf("GenerateConfig() error = %v", err)
}
httpC2Config := configs.GenerateDefaultHTTPC2Config()
binPath, err := SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
if err != nil {
t.Fatalf("build control-flow Sliver for %s: %v", target, err)
}
defer cleanupGeneratedArtifacts(t, binPath)
if stat, err := os.Stat(binPath); err != nil {
t.Fatalf("stat generated control-flow Sliver: %v", err)
} else if stat.Size() == 0 {
t.Fatal("generated control-flow Sliver is empty")
}
}
func TestTrafficEncoders(t *testing.T) {
// Supported platforms
trafficEncodersExecutable(t, "windows", "amd64")
+46
View File
@@ -0,0 +1,46 @@
package generate
import (
"errors"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/gofrs/uuid"
"google.golang.org/protobuf/proto"
)
// NewImplantConfigSnapshot creates the immutable configuration that a build
// embeds and references. The source profile ID is returned separately so build
// listing and profile deletion can retain their existing relationship without
// making the snapshot itself part of the mutable profile.
func NewImplantConfigSnapshot(config *clientpb.ImplantConfig) (*clientpb.ImplantConfig, string, error) {
if config == nil {
return nil, "", errors.New("implant config cannot be nil")
}
snapshot, ok := proto.Clone(config).(*clientpb.ImplantConfig)
if !ok {
return nil, "", errors.New("failed to clone implant config")
}
configID, err := uuid.NewV4()
if err != nil {
return nil, "", err
}
sourceProfileID := snapshot.ImplantProfileID
if sourceProfileID != "" {
profileID, err := uuid.FromString(sourceProfileID)
if err != nil || profileID == uuid.Nil {
return nil, "", errors.New("invalid source implant profile ID")
}
sourceProfileID = profileID.String()
}
snapshot.ID = configID.String()
snapshot.ImplantProfileID = ""
snapshot.ImplantBuilds = nil
for _, c2 := range snapshot.C2 {
if c2 != nil {
c2.ID = ""
}
}
return snapshot, sourceProfileID, nil
}
@@ -0,0 +1,231 @@
//go:build server
package generate
import (
"errors"
"fmt"
"testing"
"time"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
"github.com/gofrs/uuid"
"google.golang.org/protobuf/proto"
)
type storedBuildSnapshotFixture struct {
profileName string
buildName string
profile *clientpb.ImplantProfile
snapshot *clientpb.ImplantConfig
build *clientpb.ImplantBuild
snapshotID string
buildID string
}
func newStoredBuildSnapshotFixture(t *testing.T) *storedBuildSnapshotFixture {
t.Helper()
suffix := time.Now().UnixNano()
profileName := fmt.Sprintf("snapshot-profile-%d", suffix)
buildName := fmt.Sprintf("snapshot-build-%d", suffix)
profile, err := SaveImplantProfile(&clientpb.ImplantProfile{
Name: profileName,
Config: &clientpb.ImplantConfig{
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
TemplateName: SliverTemplateName,
},
})
if err != nil {
t.Fatalf("SaveImplantProfile() error = %v", err)
}
fixture := &storedBuildSnapshotFixture{
profileName: profileName,
buildName: buildName,
profile: profile,
}
t.Cleanup(fixture.cleanup)
snapshot, sourceProfileID, err := NewImplantConfigSnapshot(profile.Config)
if err != nil {
t.Fatalf("NewImplantConfigSnapshot() error = %v", err)
}
fixture.snapshot = snapshot
fixture.snapshotID = snapshot.ID
if _, err := db.CreateImplantConfigWithID(snapshot); err != nil {
t.Fatalf("CreateImplantConfigWithID() error = %v", err)
}
build, err := db.SaveImplantBuildWithSourceProfile(&clientpb.ImplantBuild{
Name: buildName,
ImplantConfigID: snapshot.ID,
}, sourceProfileID)
if err != nil {
t.Fatalf("SaveImplantBuildWithSourceProfile() error = %v", err)
}
fixture.build = build
fixture.buildID = build.ID
return fixture
}
func (fixture *storedBuildSnapshotFixture) cleanup() {
if fixture.buildID != "" {
db.Session().Where("id = ?", uuid.FromStringOrNil(fixture.buildID)).Delete(&models.ImplantBuild{})
}
if fixture.snapshotID != "" {
configID := uuid.FromStringOrNil(fixture.snapshotID)
db.Session().Where("implant_config_id = ?", configID).Delete(&models.ImplantC2{})
db.Session().Where("implant_config_id = ?", configID).Delete(&models.EncoderAsset{})
db.Session().Where("implant_config_id = ?", configID).Delete(&models.CanaryDomain{})
db.Session().Where("id = ?", configID).Delete(&models.ImplantConfig{})
}
_ = db.DeleteProfile(fixture.profileName)
}
func (fixture *storedBuildSnapshotFixture) mutateProfilePolicy(t *testing.T) {
t.Helper()
updatedProfile := proto.Clone(fixture.profile).(*clientpb.ImplantProfile)
updatedProfile.Config.ControlFlow = clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED
if _, err := SaveImplantProfile(updatedProfile); err != nil {
t.Fatalf("update profile policy: %v", err)
}
}
func (fixture *storedBuildSnapshotFixture) assertPersistedSnapshot(t *testing.T) *clientpb.ImplantBuild {
t.Helper()
persistedBuild, err := db.ImplantBuildByName(fixture.buildName)
if err != nil {
t.Fatalf("ImplantBuildByName() error = %v", err)
}
persistedConfig, err := db.ImplantConfigByID(persistedBuild.ImplantConfigID)
if err != nil {
t.Fatalf("ImplantConfigByID() error = %v", err)
}
if persistedConfig.ID != fixture.snapshot.ID {
t.Fatalf("build config ID = %q, want snapshot %q", persistedConfig.ID, fixture.snapshot.ID)
}
if persistedConfig.ImplantProfileID != "" {
t.Fatalf("build snapshot profile ID = %q, want detached config", persistedConfig.ImplantProfileID)
}
if persistedConfig.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1 {
t.Fatalf("stored build policy = %v after profile mutation, want balanced-v1", persistedConfig.ControlFlow)
}
return persistedBuild
}
func (fixture *storedBuildSnapshotFixture) assertBuildUpdatePreservesSourceProfile(t *testing.T, persistedBuild *clientpb.ImplantBuild) {
t.Helper()
if _, err := db.SaveImplantBuild(persistedBuild); err != nil {
t.Fatalf("SaveImplantBuild() update error = %v", err)
}
storedBuild := &models.ImplantBuild{}
if err := db.Session().Where("id = ?", uuid.FromStringOrNil(fixture.build.ID)).First(storedBuild).Error; err != nil {
t.Fatalf("load build model: %v", err)
}
if storedBuild.SourceImplantProfileID == nil || storedBuild.SourceImplantProfileID.String() != fixture.profile.ID {
t.Fatalf("source profile ID after build update = %v, want %q", storedBuild.SourceImplantProfileID, fixture.profile.ID)
}
}
func (fixture *storedBuildSnapshotFixture) assertProfileListsBuild(t *testing.T) {
t.Helper()
listedProfile, err := db.ProfileByName(fixture.profileName)
if err != nil {
t.Fatalf("ProfileByName() error = %v", err)
}
if len(listedProfile.Config.ImplantBuilds) != 1 || listedProfile.Config.ImplantBuilds[0].Name != fixture.buildName {
t.Fatalf("profile builds = %v, want detached snapshot build %q", listedProfile.Config.ImplantBuilds, fixture.buildName)
}
}
func (fixture *storedBuildSnapshotFixture) deleteBuildAndProfileRetainsAuditSnapshot(t *testing.T) {
t.Helper()
if err := db.Session().Where("id = ?", uuid.FromStringOrNil(fixture.build.ID)).Delete(&models.ImplantBuild{}).Error; err != nil {
t.Fatalf("delete build record: %v", err)
}
fixture.buildID = ""
if err := db.DeleteProfile(fixture.profileName); err != nil {
t.Fatalf("DeleteProfile() error = %v", err)
}
auditConfig, err := db.ImplantConfigByID(fixture.snapshot.ID)
if err != nil {
t.Fatalf("load origin-policy audit config after build/profile deletion: %v", err)
}
if auditConfig.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1 {
t.Fatalf("retained audit policy = %v, want balanced-v1", auditConfig.ControlFlow)
}
}
func TestProfileMutationDoesNotChangeStoredBuildSnapshotPolicy(t *testing.T) {
fixture := newStoredBuildSnapshotFixture(t)
fixture.mutateProfilePolicy(t)
persistedBuild := fixture.assertPersistedSnapshot(t)
fixture.assertBuildUpdatePreservesSourceProfile(t, persistedBuild)
fixture.assertProfileListsBuild(t)
fixture.deleteBuildAndProfileRetainsAuditSnapshot(t)
}
func TestDeleteFailedImplantConfigSnapshotOnlyDeletesUnreferencedSnapshot(t *testing.T) {
newSnapshot := func(t *testing.T) *clientpb.ImplantConfig {
t.Helper()
config, err := db.CreateImplantConfigWithID(&clientpb.ImplantConfig{
ID: uuid.Must(uuid.NewV4()).String(),
C2: []*clientpb.ImplantC2{{URL: "mtls://127.0.0.1:8888"}},
})
if err != nil {
t.Fatalf("CreateImplantConfigWithID() error = %v", err)
}
return config
}
deleteConfig := func(configID string) {
id := uuid.FromStringOrNil(configID)
db.Session().Where("implant_config_id = ?", id).Delete(&models.ImplantC2{})
db.Session().Where("id = ?", id).Delete(&models.ImplantConfig{})
}
unreferenced := newSnapshot(t)
t.Cleanup(func() { deleteConfig(unreferenced.ID) })
if err := db.DeleteFailedImplantConfigSnapshot(unreferenced.ID); err != nil {
t.Fatalf("DeleteFailedImplantConfigSnapshot() error = %v", err)
}
if _, err := db.ImplantConfigByID(unreferenced.ID); !errors.Is(err, db.ErrRecordNotFound) {
t.Fatalf("unreferenced snapshot lookup error = %v, want record not found", err)
}
var c2Count int64
if err := db.Session().Model(&models.ImplantC2{}).Where("implant_config_id = ?", uuid.FromStringOrNil(unreferenced.ID)).Count(&c2Count).Error; err != nil {
t.Fatalf("count rolled-back C2 rows: %v", err)
}
if c2Count != 0 {
t.Fatalf("rolled-back snapshot C2 rows = %d, want 0", c2Count)
}
referenced := newSnapshot(t)
buildName := fmt.Sprintf("snapshot-rollback-guard-%d", time.Now().UnixNano())
build, err := db.SaveImplantBuild(&clientpb.ImplantBuild{
Name: buildName,
ImplantConfigID: referenced.ID,
})
if err != nil {
t.Fatalf("SaveImplantBuild() error = %v", err)
}
t.Cleanup(func() {
db.Session().Where("id = ?", uuid.FromStringOrNil(build.ID)).Delete(&models.ImplantBuild{})
deleteConfig(referenced.ID)
})
if err := db.DeleteFailedImplantConfigSnapshot(referenced.ID); err != nil {
t.Fatalf("guard referenced snapshot: %v", err)
}
if _, err := db.ImplantConfigByID(referenced.ID); err != nil {
t.Fatalf("referenced snapshot was deleted: %v", err)
}
}
+84
View File
@@ -0,0 +1,84 @@
package generate
import (
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/gofrs/uuid"
)
func TestNewImplantConfigSnapshotDetachesAndPreservesPolicy(t *testing.T) {
profileID := uuid.Must(uuid.NewV4()).String()
configID := uuid.Must(uuid.NewV4()).String()
c2ID := uuid.Must(uuid.NewV4()).String()
source := &clientpb.ImplantConfig{
ID: configID,
ImplantProfileID: profileID,
ImplantBuilds: []*clientpb.ImplantBuild{{Name: "old-build"}},
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
C2: []*clientpb.ImplantC2{
{ID: c2ID, URL: "mtls://127.0.0.1:8888"},
nil,
},
}
snapshot, sourceProfileID, err := NewImplantConfigSnapshot(source)
if err != nil {
t.Fatalf("NewImplantConfigSnapshot() error = %v", err)
}
assertDetachedImplantConfigSnapshot(t, snapshot, sourceProfileID, profileID, configID)
source.ControlFlow = clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED
source.C2[0].URL = "mtls://127.0.0.1:9999"
if snapshot.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1 {
t.Fatal("mutating the source profile changed the build snapshot policy")
}
if snapshot.C2[0].URL != "mtls://127.0.0.1:8888" {
t.Fatal("mutating the source profile changed the build snapshot C2")
}
if source.ID != configID || source.ImplantProfileID != profileID || source.C2[0].ID != c2ID {
t.Fatal("snapshot creation mutated the source profile config")
}
second, _, err := NewImplantConfigSnapshot(source)
if err != nil {
t.Fatalf("second NewImplantConfigSnapshot() error = %v", err)
}
if second.ID == snapshot.ID {
t.Fatalf("two build snapshots reused config ID %q", snapshot.ID)
}
}
func assertDetachedImplantConfigSnapshot(t *testing.T, snapshot *clientpb.ImplantConfig, sourceProfileID, profileID, configID string) {
t.Helper()
if sourceProfileID != profileID {
t.Fatalf("source profile ID = %q, want %q", sourceProfileID, profileID)
}
if snapshot.ID == "" || snapshot.ID == configID {
t.Fatalf("snapshot ID = %q, want a fresh UUID", snapshot.ID)
}
if uuid.FromStringOrNil(snapshot.ID) == uuid.Nil {
t.Fatalf("snapshot ID = %q, want a valid UUID", snapshot.ID)
}
if snapshot.ImplantProfileID != "" {
t.Fatalf("snapshot profile ID = %q, want detached config", snapshot.ImplantProfileID)
}
if snapshot.ImplantBuilds != nil {
t.Fatalf("snapshot builds = %v, want nil", snapshot.ImplantBuilds)
}
if snapshot.C2[0].ID != "" {
t.Fatalf("snapshot C2 ID = %q, want fresh persistence ID", snapshot.C2[0].ID)
}
if snapshot.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1 {
t.Fatalf("snapshot control-flow policy = %v, want balanced-v1", snapshot.ControlFlow)
}
}
func TestNewImplantConfigSnapshotRejectsInvalidSourceProfileID(t *testing.T) {
_, _, err := NewImplantConfigSnapshot(&clientpb.ImplantConfig{ImplantProfileID: "not-a-uuid"})
if err == nil {
t.Fatal("NewImplantConfigSnapshot() accepted an invalid source profile ID")
}
}
+253
View File
@@ -0,0 +1,253 @@
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 <https://www.gnu.org/licenses/>.
*/
import (
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/bishopfox/sliver/protobuf/clientpb"
serverAssets "github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/gogo"
utilAssets "github.com/bishopfox/sliver/util/assets"
)
const (
// ControlFlowCapability is the protocol/policy contract understood by
// coordinators and capable external builders.
ControlFlowCapability = "garble.control-flow/balanced-v1"
// LocalControlFlowCapability is advertised by a coordinator only when its
// pinned local Garble runtime is present and verified.
LocalControlFlowCapability = "garble.control-flow.local/balanced-v1"
controlFlowDirectivePrefix = "//garble:controlflow"
balancedV1Directive = controlFlowDirectivePrefix + " block_splits=2 junk_jumps=2 flatten_passes=1 flatten_hardening=xor trash_blocks=0"
)
type controlFlowPolicy struct {
directive string
functions map[string]controlFlowFunction
}
type controlFlowFunction struct {
sourcePath string
name string
}
var (
// ErrControlFlowBuildBusy is returned instead of allowing an unbounded
// queue of memory-intensive SSA builds to accumulate on the server.
ErrControlFlowBuildBusy = errors.New("another control-flow build is already running")
// ErrControlFlowUnavailable indicates that the extracted compiler asset is
// absent or does not match Sliver's pinned control-flow-capable artifact.
ErrControlFlowUnavailable = errors.New("control-flow obfuscation is unavailable")
controlFlowBuildSlot = make(chan struct{}, 1)
)
var balancedV1ControlFlowPolicy = &controlFlowPolicy{
directive: balancedV1Directive,
functions: map[string]controlFlowFunction{
"handlers.determineDirPathFilter": {
sourcePath: "sliver/handlers/handlers.go",
name: "determineDirPathFilter",
},
"runner.registerSliver": {
sourcePath: "sliver/runner/runner.go",
name: "registerSliver",
},
"transports.randomCCDomain": {
sourcePath: "sliver/transports/transports.go",
name: "randomCCDomain",
},
},
}
// ValidateControlFlowConfig validates the public control-flow policy before
// any source is rendered or cryptographic build material is created.
func ValidateControlFlowConfig(config *clientpb.ImplantConfig) error {
_, err := controlFlowPolicyForConfig(config)
return err
}
func controlFlowPolicyForConfig(config *clientpb.ImplantConfig) (*controlFlowPolicy, error) {
if config == nil {
return nil, fmt.Errorf("implant config cannot be nil")
}
switch config.ControlFlow {
case clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED:
return nil, nil
case clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1:
if !config.ObfuscateSymbols {
return nil, fmt.Errorf("control-flow obfuscation requires symbol obfuscation")
}
if config.Debug {
return nil, fmt.Errorf("control-flow obfuscation is not supported for debug builds")
}
if templateName := strings.TrimSpace(config.TemplateName); templateName != "" && templateName != SliverTemplateName {
return nil, fmt.Errorf("control-flow obfuscation is not supported by implant template %q", templateName)
}
return balancedV1ControlFlowPolicy, nil
default:
return nil, fmt.Errorf("unsupported control-flow obfuscation policy: %d", config.ControlFlow)
}
}
func controlFlowDirective(config *clientpb.ImplantConfig, functionID string) (string, bool, error) {
policy, err := controlFlowPolicyForConfig(config)
if err != nil {
return "", false, err
}
if policy == nil {
return "", false, nil
}
if _, ok := policy.functions[functionID]; !ok {
return "", false, fmt.Errorf("function %q is not part of control-flow policy %s", functionID, config.ControlFlow)
}
return policy.directive, true, nil
}
func controlFlowObfuscationEnabled(config *clientpb.ImplantConfig) bool {
return config != nil && config.ControlFlow == clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1
}
// ControlFlowEnabled reports whether an implant config requests a supported
// non-disabled control-flow policy.
func ControlFlowEnabled(config *clientpb.ImplantConfig) bool {
return controlFlowObfuscationEnabled(config)
}
// HasControlFlowCapability reports whether a server or external builder
// advertises support for Sliver's current control-flow policy contract.
func HasControlFlowCapability(capabilities []string) bool {
for _, capability := range capabilities {
if capability == ControlFlowCapability {
return true
}
}
return false
}
// HasLocalControlFlowCapability reports whether a coordinator can currently
// execute the balanced-v1 policy with its local Garble runtime.
func HasLocalControlFlowCapability(capabilities []string) bool {
for _, capability := range capabilities {
if capability == LocalControlFlowCapability {
return true
}
}
return false
}
// ControlFlowRuntimeError verifies that the host Garble executable is exactly
// the artifact pinned by Sliver's asset manifest. Builder capability
// advertisement and local build admission fail closed when extraction is
// stale or incomplete; the coordinator's protocol capability remains
// independent so healthy external builders can still be used.
func ControlFlowRuntimeError() error {
garbleName := "garble"
if runtime.GOOS == "windows" {
garbleName += ".exe"
}
garblePath := filepath.Join(gogo.GetGoRootDir(serverAssets.GetRootAppDir()), "bin", garbleName)
if err := utilAssets.VerifyGarbleBinary(garblePath, runtime.GOOS, runtime.GOARCH); err != nil {
return fmt.Errorf("%w: %v", ErrControlFlowUnavailable, err)
}
return nil
}
// AcquireControlFlowBuildSlot admits at most one local control-flow build. It
// fails immediately under contention so queued RPCs cannot retain generated
// key material or outlive a canceled client request while waiting for Garble.
func AcquireControlFlowBuildSlot(config *clientpb.ImplantConfig) (func(), error) {
policy, err := controlFlowPolicyForConfig(config)
if err != nil {
return nil, err
}
if policy == nil {
return func() {}, nil
}
if err := ControlFlowRuntimeError(); err != nil {
return nil, err
}
select {
case controlFlowBuildSlot <- struct{}{}:
var releaseOnce sync.Once
return func() {
releaseOnce.Do(func() { <-controlFlowBuildSlot })
}, nil
default:
return nil, ErrControlFlowBuildBusy
}
}
func controlFlowFunctionsInSource(policy *controlFlowPolicy, sourcePath string, source []byte) ([]string, error) {
file, err := parser.ParseFile(token.NewFileSet(), sourcePath, source, parser.ParseComments)
if err != nil {
return nil, fmt.Errorf("parse rendered control-flow source %q: %w", sourcePath, err)
}
functionIDs := []string{}
for _, declaration := range file.Decls {
function, ok := declaration.(*ast.FuncDecl)
if !ok || function.Doc == nil {
continue
}
directiveCount := 0
for _, comment := range function.Doc.List {
if !strings.HasPrefix(comment.Text, controlFlowDirectivePrefix) {
continue
}
if comment.Text != policy.directive {
return nil, fmt.Errorf("control-flow directive on %s in %s does not match the selected policy", function.Name.Name, sourcePath)
}
directiveCount++
}
if directiveCount == 0 {
continue
}
if directiveCount != 1 {
return nil, fmt.Errorf("function %s in %s has %d control-flow directives, expected exactly one", function.Name.Name, sourcePath, directiveCount)
}
if function.Recv != nil {
return nil, fmt.Errorf("unexpected control-flow directive on method %s in %s", function.Name.Name, sourcePath)
}
functionID := ""
for candidateID, candidate := range policy.functions {
if candidate.sourcePath == sourcePath && candidate.name == function.Name.Name {
functionID = candidateID
break
}
}
if functionID == "" {
return nil, fmt.Errorf("unexpected control-flow directive on %s in %s", function.Name.Name, sourcePath)
}
functionIDs = append(functionIDs, functionID)
}
return functionIDs, nil
}
+244
View File
@@ -0,0 +1,244 @@
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 <https://www.gnu.org/licenses/>.
*/
import (
"errors"
"strings"
"testing"
"github.com/bishopfox/sliver/implant"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
func TestValidateControlFlowConfig(t *testing.T) {
tests := []struct {
name string
config *clientpb.ImplantConfig
wantErr string
}{
{
name: "disabled is backward compatible",
config: &clientpb.ImplantConfig{},
},
{
name: "balanced v1",
config: &clientpb.ImplantConfig{
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
TemplateName: SliverTemplateName,
},
},
{
name: "requires symbol obfuscation",
config: &clientpb.ImplantConfig{
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
},
wantErr: "requires symbol obfuscation",
},
{
name: "rejects debug builds",
config: &clientpb.ImplantConfig{
Debug: true,
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
},
wantErr: "not supported for debug builds",
},
{
name: "rejects unsupported template",
config: &clientpb.ImplantConfig{
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
TemplateName: "custom",
},
wantErr: "not supported by implant template",
},
{
name: "rejects unknown policy",
config: &clientpb.ImplantConfig{
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy(99),
},
wantErr: "unsupported control-flow obfuscation policy",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := ValidateControlFlowConfig(test.config)
if test.wantErr == "" {
if err != nil {
t.Fatalf("ValidateControlFlowConfig() error = %v", err)
}
return
}
if err == nil || !strings.Contains(err.Error(), test.wantErr) {
t.Fatalf("ValidateControlFlowConfig() error = %v, want substring %q", err, test.wantErr)
}
})
}
}
func TestBalancedV1ControlFlowPolicy(t *testing.T) {
config := &clientpb.ImplantConfig{
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
}
if len(balancedV1ControlFlowPolicy.functions) != 3 {
t.Fatalf("balanced-v1 function count = %d, want 3", len(balancedV1ControlFlowPolicy.functions))
}
for functionID := range balancedV1ControlFlowPolicy.functions {
directive, enabled, err := controlFlowDirective(config, functionID)
if err != nil {
t.Fatalf("controlFlowDirective(%q) error = %v", functionID, err)
}
if !enabled {
t.Fatalf("controlFlowDirective(%q) unexpectedly disabled", functionID)
}
if directive != balancedV1Directive {
t.Fatalf("controlFlowDirective(%q) = %q, want %q", functionID, directive, balancedV1Directive)
}
}
if _, _, err := controlFlowDirective(config, "runner.notAllowlisted"); err == nil {
t.Fatal("controlFlowDirective accepted a non-allowlisted function")
}
}
func TestBalancedV1SourceAnnotations(t *testing.T) {
found := map[string]struct{}{}
visitedPaths := map[string]struct{}{}
for _, expected := range balancedV1ControlFlowPolicy.functions {
if _, visited := visitedPaths[expected.sourcePath]; visited {
continue
}
visitedPaths[expected.sourcePath] = struct{}{}
source, err := implant.FS.ReadFile(expected.sourcePath)
if err != nil {
t.Fatalf("read %s: %v", expected.sourcePath, err)
}
functionIDs, err := controlFlowFunctionsInSource(balancedV1ControlFlowPolicy, expected.sourcePath, source)
if err != nil {
t.Fatalf("validate %s: %v", expected.sourcePath, err)
}
for _, functionID := range functionIDs {
if _, duplicate := found[functionID]; duplicate {
t.Fatalf("control-flow directive for %s appears more than once", functionID)
}
found[functionID] = struct{}{}
}
}
if len(found) != len(balancedV1ControlFlowPolicy.functions) {
t.Fatalf("found %d balanced-v1 source annotations, want %d", len(found), len(balancedV1ControlFlowPolicy.functions))
}
}
func TestControlFlowSourceRejectsUnapprovedDirective(t *testing.T) {
source := []byte(`package handlers
//garble:controlflow flatten_passes=4
func determineDirPathFilter() {}
`)
_, err := controlFlowFunctionsInSource(
balancedV1ControlFlowPolicy,
"sliver/handlers/handlers.go",
source,
)
if err == nil || !strings.Contains(err.Error(), "does not match the selected policy") {
t.Fatalf("controlFlowFunctionsInSource() error = %v, want policy mismatch", err)
}
}
func TestControlFlowSourceRejectsAllowlistedNameOnMethod(t *testing.T) {
source := []byte(`package handlers
type receiver struct{}
//garble:controlflow block_splits=2 junk_jumps=2 flatten_passes=1 flatten_hardening=xor trash_blocks=0
func (receiver) determineDirPathFilter() {}
`)
_, err := controlFlowFunctionsInSource(
balancedV1ControlFlowPolicy,
"sliver/handlers/handlers.go",
source,
)
if err == nil || !strings.Contains(err.Error(), "on method") {
t.Fatalf("controlFlowFunctionsInSource() error = %v, want method rejection", err)
}
}
func TestControlFlowSourceScansEveryDirectiveComment(t *testing.T) {
source := []byte(`package handlers
//garble:controlflow block_splits=2 junk_jumps=2 flatten_passes=1 flatten_hardening=xor trash_blocks=0
//garble:controlflow max
func determineDirPathFilter() {}
`)
_, err := controlFlowFunctionsInSource(
balancedV1ControlFlowPolicy,
"sliver/handlers/handlers.go",
source,
)
if err == nil || !strings.Contains(err.Error(), "does not match the selected policy") {
t.Fatalf("controlFlowFunctionsInSource() error = %v, want second directive rejection", err)
}
}
func TestControlFlowCapabilities(t *testing.T) {
if !HasControlFlowCapability([]string{"unrelated", ControlFlowCapability}) {
t.Fatal("HasControlFlowCapability did not find advertised capability")
}
if HasControlFlowCapability([]string{"unrelated"}) {
t.Fatal("HasControlFlowCapability accepted an unrelated capability")
}
}
func TestControlFlowBuildAdmission(t *testing.T) {
disabledRelease, err := AcquireControlFlowBuildSlot(&clientpb.ImplantConfig{})
if err != nil {
t.Fatalf("AcquireControlFlowBuildSlot(disabled) error = %v", err)
}
disabledRelease()
config := &clientpb.ImplantConfig{
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
TemplateName: SliverTemplateName,
}
firstRelease, err := AcquireControlFlowBuildSlot(config)
if err != nil {
t.Fatalf("first AcquireControlFlowBuildSlot() error = %v", err)
}
defer firstRelease()
if _, err := AcquireControlFlowBuildSlot(config); !errors.Is(err, ErrControlFlowBuildBusy) {
t.Fatalf("contended AcquireControlFlowBuildSlot() error = %v, want ErrControlFlowBuildBusy", err)
}
firstRelease()
secondRelease, err := AcquireControlFlowBuildSlot(config)
if err != nil {
t.Fatalf("AcquireControlFlowBuildSlot() after release error = %v", err)
}
secondRelease()
}
+17 -2
View File
@@ -19,6 +19,8 @@ package generate
*/
import (
"fmt"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
@@ -26,6 +28,11 @@ import (
// SliverExternal - Generates the cryptographic keys for the implant but compiles no code
func SliverExternal(name string, config *clientpb.ImplantConfig) (*clientpb.ExternalImplantConfig, error) {
config, sourceProfileID, err := NewImplantConfigSnapshot(config)
if err != nil {
return nil, err
}
config.IncludeMTLS = config.IncludeMTLS || models.IsC2Enabled([]string{"mtls"}, config.C2)
config.IncludeWG = config.IncludeWG || models.IsC2Enabled([]string{"wg"}, config.C2)
config.IncludeHTTP = config.IncludeHTTP || models.IsC2Enabled([]string{"http", "https"}, config.C2)
@@ -55,14 +62,22 @@ func SliverExternal(name string, config *clientpb.ImplantConfig) (*clientpb.Exte
if err != nil {
return nil, err
}
config, err = db.SaveImplantConfig(config)
config, err = db.CreateImplantConfigWithID(config)
if err != nil {
return nil, err
}
build.ImplantConfigID = config.ID
implantBuild, err := db.SaveImplantBuild(build)
var implantBuild *clientpb.ImplantBuild
if sourceProfileID == "" {
implantBuild, err = db.SaveImplantBuild(build)
} else {
implantBuild, err = db.SaveImplantBuildWithSourceProfile(build, sourceProfileID)
}
if err != nil {
if cleanupErr := db.DeleteFailedImplantConfigSnapshot(config.ID); cleanupErr != nil {
return nil, fmt.Errorf("save external implant build: %w (failed to roll back config snapshot: %v)", err, cleanupErr)
}
return nil, err
}
+125 -2
View File
@@ -38,6 +38,7 @@ import (
"github.com/bishopfox/sliver/server/log"
"github.com/bishopfox/sliver/server/watchtower"
"google.golang.org/protobuf/proto"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
@@ -46,6 +47,9 @@ var (
// ErrImplantBuildFileNotFound - More descriptive 'key not found' error
ErrImplantBuildFileNotFound = errors.New("implant build file not found")
// ErrImplantBuildNotPending protects completed or partially completed
// artifacts from pending external-build cleanup.
ErrImplantBuildNotPending = errors.New("implant build is not pending")
)
func getBuildsDir() (string, error) {
@@ -95,6 +99,17 @@ func ImplantConfigSave(config *clientpb.ImplantConfig) (*clientpb.ImplantConfig,
// ImplantBuildSave - Saves a binary file into the database
func ImplantBuildSave(build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, fPath string) error {
return implantBuildSave(build, config, fPath, "")
}
// ImplantBuildSaveWithSourceProfile stores a build against its immutable
// config snapshot while retaining its source profile for profile listing and
// deletion behavior.
func ImplantBuildSaveWithSourceProfile(build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, fPath string, sourceProfileID string) error {
return implantBuildSave(build, config, fPath, sourceProfileID)
}
func implantBuildSave(build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, fPath string, sourceProfileID string) error {
rootAppDir, _ := filepath.Abs(assets.GetRootAppDir())
fPath, _ = filepath.Abs(fPath)
if !strings.HasPrefix(fPath, rootAppDir) {
@@ -132,7 +147,12 @@ func ImplantBuildSave(build *clientpb.ImplantBuild, config *clientpb.ImplantConf
}
build.ImplantConfigID = config.ID
implantBuild, err := db.SaveImplantBuild(build)
var implantBuild *clientpb.ImplantBuild
if sourceProfileID == "" {
implantBuild, err = db.SaveImplantBuild(build)
} else {
implantBuild, err = db.SaveImplantBuildWithSourceProfile(build, sourceProfileID)
}
if err != nil {
return err
}
@@ -184,6 +204,16 @@ func overwriteImplantBuild(dst *clientpb.ImplantBuild, src *clientpb.ImplantBuil
}
func SaveStage(build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, stage2 []byte, stageType string) error {
return saveStage(build, config, stage2, stageType, "")
}
// SaveStageWithSourceProfile stores a staged build against its immutable
// config snapshot while retaining its source profile association.
func SaveStageWithSourceProfile(build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, stage2 []byte, stageType string, sourceProfileID string) error {
return saveStage(build, config, stage2, stageType, sourceProfileID)
}
func saveStage(build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, stage2 []byte, stageType string, sourceProfileID string) error {
md5Hash, sha1Hash, sha256Hash := computeHashes(stage2)
buildsDir, err := getBuildsDir()
if err != nil {
@@ -211,7 +241,12 @@ func SaveStage(build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, sta
}
build.ImplantConfigID = config.ID
implantBuild, err := db.SaveImplantBuild(build)
var implantBuild *clientpb.ImplantBuild
if sourceProfileID == "" {
implantBuild, err = db.SaveImplantBuild(build)
} else {
implantBuild, err = db.SaveImplantBuildWithSourceProfile(build, sourceProfileID)
}
if err != nil {
return err
}
@@ -256,3 +291,91 @@ func ImplantFileDelete(build *clientpb.ImplantBuild) error {
}
return os.Remove(buildFilePath)
}
// DeletePendingImplantBuild removes an external build that never produced an
// artifact. Completion state is checked again inside the transaction so a
// completed build cannot be mistaken for a pending one. Once the build is
// removed, its immutable config snapshot and associations are removed only if
// the snapshot is detached from a profile and no other build references it.
func DeletePendingImplantBuild(buildID string) error {
id := models.ParseUUIDOrNil(buildID)
if id == models.NilUUID() {
return db.ErrRecordNotFound
}
buildsDir, err := getBuildsDir()
if err != nil {
return err
}
return db.Session().Transaction(func(tx *gorm.DB) error {
build, err := loadPendingImplantBuild(tx, id, buildsDir)
if err != nil {
return err
}
configID := build.ImplantConfigID
if err := tx.Delete(build).Error; err != nil {
return err
}
return deleteUnreferencedImplantConfig(tx, configID)
})
}
func loadPendingImplantBuild(tx *gorm.DB, id models.UUID, buildsDir string) (*models.ImplantBuild, error) {
build := &models.ImplantBuild{}
if err := tx.Where(&models.ImplantBuild{ID: id}).First(build).Error; err != nil {
return nil, err
}
if build.ImplantID != 0 || build.MD5 != "" || build.SHA1 != "" || build.SHA256 != "" {
return nil, ErrImplantBuildNotPending
}
var resourceCount int64
if err := tx.Model(&models.ResourceID{}).Where("name = ?", build.Name).Count(&resourceCount).Error; err != nil {
return nil, err
}
if resourceCount != 0 {
return nil, ErrImplantBuildNotPending
}
buildFilePath := filepath.Join(buildsDir, build.ID.String())
if _, err := os.Stat(buildFilePath); err == nil {
return nil, ErrImplantBuildNotPending
} else if !os.IsNotExist(err) {
return nil, err
}
return build, nil
}
func deleteUnreferencedImplantConfig(tx *gorm.DB, configID models.UUID) error {
var remainingBuilds int64
if err := tx.Model(&models.ImplantBuild{}).Where("implant_config_id = ?", configID).Count(&remainingBuilds).Error; err != nil {
return err
}
if remainingBuilds != 0 || configID == models.NilUUID() {
return nil
}
config := &models.ImplantConfig{}
if err := tx.Where(&models.ImplantConfig{ID: configID}).First(config).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil
}
return err
}
if config.ImplantProfileID != nil {
return nil
}
if err := tx.Where(&models.ImplantC2{ImplantConfigID: configID}).Delete(&models.ImplantC2{}).Error; err != nil {
return err
}
if err := tx.Where(&models.EncoderAsset{ImplantConfigID: configID}).Delete(&models.EncoderAsset{}).Error; err != nil {
return err
}
if err := tx.Where(&models.CanaryDomain{ImplantConfigID: configID}).Delete(&models.CanaryDomain{}).Error; err != nil {
return err
}
return tx.Delete(config).Error
}
+58 -7
View File
@@ -1,12 +1,60 @@
package generate
import (
"reflect"
"strings"
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/gogo"
)
func TestImplantLDFlags(t *testing.T) {
tests := []struct {
name string
config *clientpb.ImplantConfig
want []string
}{
{
name: "non-debug linux",
config: &clientpb.ImplantConfig{GOOS: LINUX},
want: []string{"-s -w -buildid="},
},
{
name: "non-debug windows",
config: &clientpb.ImplantConfig{GOOS: WINDOWS},
want: []string{"-s -w -buildid= -H=windowsgui"},
},
{
name: "debug linux",
config: &clientpb.ImplantConfig{GOOS: LINUX, Debug: true},
},
{
name: "debug windows",
config: &clientpb.ImplantConfig{GOOS: WINDOWS, Debug: true},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := implantLDFlags(test.config); !reflect.DeepEqual(got, test.want) {
t.Fatalf("implantLDFlags() = %q, want %q", got, test.want)
}
})
}
}
func TestImplantLDFlagsReturnsIndependentSlices(t *testing.T) {
config := &clientpb.ImplantConfig{GOOS: LINUX}
first := implantLDFlags(config)
first[0] = "modified"
want := []string{nonDebugImplantLDFlags}
if second := implantLDFlags(config); !reflect.DeepEqual(second, want) {
t.Fatalf("implantLDFlags() reused mutable state: got %q, want %q", second, want)
}
}
func TestIsZigCC(t *testing.T) {
tests := []struct {
cc string
@@ -32,24 +80,24 @@ func TestApplyZigStaticLinking(t *testing.T) {
CGO: "1",
CC: "/tmp/zig cc -target x86_64-linux-musl",
}
ldflags, enabled := applyZigStaticLinking(cfg, "c-shared", []string{""})
ldflags, enabled := applyZigStaticLinking(cfg, "c-shared", []string{nonDebugImplantLDFlags})
if enabled {
t.Fatalf("did not expect -static flags for c-shared (zig emits an ar archive instead of a .so)")
}
if len(ldflags) != 1 {
t.Fatalf("expected a single -ldflags string, got %d (%v)", len(ldflags), ldflags)
}
if ldflags[0] != "" {
if ldflags[0] != nonDebugImplantLDFlags {
t.Fatalf("expected ldflags unchanged, got %q", ldflags[0])
}
}
func TestApplyLinuxShellcodeBuildID(t *testing.T) {
ldflags := applyLinuxShellcodeBuildID([]string{"-s -w"})
ldflags := applyLinuxShellcodeBuildID([]string{nonDebugImplantLDFlags})
if len(ldflags) != 1 {
t.Fatalf("expected a single -ldflags string, got %d (%v)", len(ldflags), ldflags)
}
want := "-s -w " + linuxShellcodeBuildIDLDFlag
want := nonDebugImplantLDFlags + " " + linuxShellcodeBuildIDLDFlag
if ldflags[0] != want {
t.Fatalf("applyLinuxShellcodeBuildID() = %q, want %q", ldflags[0], want)
}
@@ -61,7 +109,7 @@ func TestApplyZigStaticLinking_Executable(t *testing.T) {
CGO: "1",
CC: "/tmp/zig cc -target x86_64-linux-musl",
}
ldflags, enabled := applyZigStaticLinking(cfg, "", []string{""})
ldflags, enabled := applyZigStaticLinking(cfg, "", []string{nonDebugImplantLDFlags})
if !enabled {
t.Fatalf("expected static linking to be enabled for linux zig cc executable build")
}
@@ -74,6 +122,9 @@ func TestApplyZigStaticLinking_Executable(t *testing.T) {
if !strings.Contains(ldflags[0], "-extldflags=-static") {
t.Fatalf("expected -extldflags=-static in ldflags, got %q", ldflags[0])
}
if !strings.Contains(ldflags[0], nonDebugImplantLDFlags) {
t.Fatalf("expected production linker flags in ldflags, got %q", ldflags[0])
}
}
func TestApplyZigStaticLinking_NotLinux(t *testing.T) {
@@ -82,11 +133,11 @@ func TestApplyZigStaticLinking_NotLinux(t *testing.T) {
CGO: "1",
CC: "zig cc -target x86_64-windows-gnu",
}
ldflags, enabled := applyZigStaticLinking(cfg, "c-shared", []string{""})
ldflags, enabled := applyZigStaticLinking(cfg, "c-shared", []string{nonDebugImplantLDFlags})
if enabled {
t.Fatalf("did not expect static linking to be enabled on %s", cfg.GOOS)
}
if len(ldflags) != 1 || ldflags[0] != "" {
if len(ldflags) != 1 || ldflags[0] != nonDebugImplantLDFlags {
t.Fatalf("expected ldflags unchanged, got %v", ldflags)
}
}
+7
View File
@@ -28,6 +28,13 @@ import (
// SaveImplantProfile - Save a sliver profile to disk
func SaveImplantProfile(pbProfile *clientpb.ImplantProfile) (*clientpb.ImplantProfile, error) {
if pbProfile == nil || pbProfile.Config == nil {
return nil, errors.New("implant profile config cannot be nil")
}
if err := ValidateControlFlowConfig(pbProfile.Config); err != nil {
return nil, err
}
dbProfile, err := db.ImplantProfileByName(pbProfile.Name)
if err != nil && !errors.Is(err, db.ErrRecordNotFound) {
return nil, err
+155
View File
@@ -0,0 +1,155 @@
// Package gogo tests Sliver's Go and Garble compiler integration.
package gogo
import (
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"github.com/bishopfox/sliver/server/assets"
utilAssets "github.com/bishopfox/sliver/util/assets"
)
func TestPinnedGarbleControlFlow(t *testing.T) {
garblePath := pinnedGarblePath(t)
moduleDir := controlFlowProbeModule(t)
transformedRuns := make([]map[string]string, 0, 2)
for run := 1; run <= 2; run++ {
runID := fmt.Sprintf("run-%d", run)
transformedRuns = append(transformedRuns, runControlFlowProbe(t, moduleDir, garblePath, runID))
}
if !reflect.DeepEqual(transformedRuns[0], transformedRuns[1]) {
t.Fatal("fixed Garble seed produced different xor-hardened control-flow output across independent clean caches")
}
}
func pinnedGarblePath(t *testing.T) string {
t.Helper()
if _, ok := utilAssets.ExpectedGarbleSHA256(runtime.GOOS, runtime.GOARCH); !ok {
t.Skipf("no pinned Garble asset for %s/%s", runtime.GOOS, runtime.GOARCH)
}
garblePath := goToolPath(GetGoRootDir(assets.GetRootAppDir()), "garble")
if _, err := os.Stat(garblePath); err != nil {
t.Fatalf("pinned Garble asset is unavailable at %s: %v", garblePath, err)
}
if err := utilAssets.VerifyGarbleBinary(garblePath, runtime.GOOS, runtime.GOARCH); err != nil {
t.Fatalf("verify pinned Garble asset at %s: %v", garblePath, err)
}
return garblePath
}
func controlFlowProbeModule(t *testing.T) string {
t.Helper()
moduleDir := t.TempDir()
if err := os.WriteFile(filepath.Join(moduleDir, "go.mod"), []byte("module example.com/sliver-control-flow-probe\n\ngo 1.26.0\n"), 0o600); err != nil {
t.Fatalf("write probe go.mod: %v", err)
}
probeSource := `package main
import "fmt"
//garble:controlflow block_splits=2 junk_jumps=2 flatten_passes=1 flatten_hardening=xor trash_blocks=0
func classify(value int) string {
if value%2 == 0 {
return "even"
}
return "odd"
}
func main() {
fmt.Print(classify(42))
}
`
if err := os.WriteFile(filepath.Join(moduleDir, "main.go"), []byte(probeSource), 0o600); err != nil {
t.Fatalf("write probe source: %v", err)
}
return moduleDir
}
func runControlFlowProbe(t *testing.T, moduleDir string, garblePath string, runID string) map[string]string {
t.Helper()
debugDir := filepath.Join(moduleDir, "debug-"+runID)
outputPath := filepath.Join(moduleDir, "probe-"+runID)
if runtime.GOOS == "windows" {
outputPath += ".exe"
}
config := GoConfig{
ProjectDir: moduleDir,
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
GOROOT: GetGoRootDir(assets.GetRootAppDir()),
GOCACHE: filepath.Join(moduleDir, "go-cache-"+runID),
GOMODCACHE: filepath.Join(moduleDir, "go-mod-cache-"+runID),
GOPROXY: "off",
CGO: "0",
Obfuscation: true,
ObfuscationControlFlow: true,
GOGARBLE: "*",
}
cmd := exec.Command(
garblePath,
"-seed=AAAAAAAAAAA",
"-literals",
"-tiny",
"-debugdir="+debugDir,
"build",
"-o", outputPath,
".",
)
cmd.Dir = moduleDir
cmd.Env = garbleCommandEnv(config)
if output, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("build control-flow probe %s with pinned Garble: %v\n%s", runID, err, output)
}
output, err := exec.Command(outputPath).CombinedOutput()
if err != nil {
t.Fatalf("run control-flow probe %s: %v\n%s", runID, err, output)
}
if string(output) != "even" {
t.Fatalf("control-flow probe %s output = %q, want %q", runID, output, "even")
}
return transformedControlFlowSources(t, debugDir, runID)
}
func transformedControlFlowSources(t *testing.T, debugDir string, runID string) map[string]string {
t.Helper()
transformed := map[string]string{}
err := filepath.WalkDir(debugDir, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() || entry.Name() != "GARBLE_controlflow.go" {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
if !strings.Contains(string(data), "goto ") {
return nil
}
relativePath, err := filepath.Rel(debugDir, path)
if err != nil {
return err
}
transformed[relativePath] = string(data)
return nil
})
if err != nil {
t.Fatalf("inspect Garble debug output for %s: %v", runID, err)
}
if len(transformed) == 0 {
t.Fatalf("Garble debug output for %s did not contain a transformed GARBLE_controlflow.go", runID)
}
return transformed
}
+162 -28
View File
@@ -20,24 +20,34 @@ package gogo
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/log"
utilAssets "github.com/bishopfox/sliver/util/assets"
)
const (
goDirName = "go"
darwinNoCGOCheckLinknameLDFlag = "-checklinkname=0"
goDirName = "go"
garbleExperimentalControlFlowEnv = "GARBLE_EXPERIMENTAL_CONTROLFLOW"
garbleDebugDirEnv = "SLIVER_GARBLE_DEBUG_DIR"
garbleRandomSeedPrefix = "-seed chosen at random: "
commandErrorOutputLimit = 8 * 1024
darwinNoCGOCheckLinknameLDFlag = "-checklinkname=0"
)
var (
gogoLog = log.NamedLogger("gogo", "compiler")
// ErrGarbleAssetVerification indicates that a control-flow build did not
// use the exact host Garble artifact pinned by Sliver.
ErrGarbleAssetVerification = errors.New("garble asset verification failed")
gogoLog = log.NamedLogger("gogo", "compiler")
)
// GoConfig - Env variables for Go compiler
@@ -56,8 +66,9 @@ type GoConfig struct {
HTTPPROXY string
HTTPSPROXY string
Obfuscation bool
GOGARBLE string
Obfuscation bool
ObfuscationControlFlow bool
GOGARBLE string
}
// GetGoRootDir - Get the path to GOROOT
@@ -110,6 +121,42 @@ func goToolPath(goRoot string, toolName string) string {
return filepath.Join(goRoot, "bin", goToolExecutableName(toolName, runtime.GOOS))
}
func envKeyEqual(first, second string) bool {
if runtime.GOOS == "windows" {
return strings.EqualFold(first, second)
}
return first == second
}
func setCommandEnv(env []string, name, value string) []string {
updated := make([]string, 0, len(env)+1)
for _, envVar := range env {
key, _, _ := strings.Cut(envVar, "=")
if envKeyEqual(key, name) {
continue
}
updated = append(updated, envVar)
}
return append(updated, fmt.Sprintf("%s=%s", name, value))
}
func setGoDebugSetting(settings, name, value string) string {
updated := make([]string, 0, strings.Count(settings, ",")+1)
for _, setting := range strings.Split(settings, ",") {
setting = strings.TrimSpace(setting)
if setting == "" {
continue
}
key, _, _ := strings.Cut(setting, "=")
if key == name {
continue
}
updated = append(updated, setting)
}
updated = append(updated, name+"="+value)
return strings.Join(updated, ",")
}
func buildCommandEnv(config GoConfig, extra map[string]string) []string {
goBinDir := ""
if strings.TrimSpace(config.GOROOT) != "" {
@@ -117,45 +164,125 @@ func buildCommandEnv(config GoConfig, extra map[string]string) []string {
}
env := append([]string{}, os.Environ()...)
overrides := []string{
fmt.Sprintf("CC=%s", config.CC),
fmt.Sprintf("CGO_ENABLED=%s", config.CGO),
overrides := []struct {
name string
value string
}{
{name: "CC", value: config.CC},
{name: "CGO_ENABLED", value: config.CGO},
// GOROOT explicitly selects the compiler used for implant builds. Do not
// let a surrounding host module make that compiler switch toolchains.
"GOTOOLCHAIN=local",
fmt.Sprintf("GOOS=%s", config.GOOS),
fmt.Sprintf("GOARCH=%s", config.GOARCH),
fmt.Sprintf("GOROOT=%s", config.GOROOT),
fmt.Sprintf("GOPATH=%s", config.ProjectDir),
fmt.Sprintf("GOCACHE=%s", config.GOCACHE),
fmt.Sprintf("GOMODCACHE=%s", config.GOMODCACHE),
fmt.Sprintf("GOPROXY=%s", config.GOPROXY),
fmt.Sprintf("HTTP_PROXY=%s", config.HTTPPROXY),
fmt.Sprintf("HTTPS_PROXY=%s", config.HTTPSPROXY),
fmt.Sprintf("PATH=%s", joinPathList(goBinDir, assets.GetZigDir(), os.Getenv("PATH"))),
fmt.Sprintf("HOME=%s", getHomeDir()),
}
for name, value := range extra {
overrides = append(overrides, fmt.Sprintf("%s=%s", name, value))
{name: "GOTOOLCHAIN", value: "local"},
{name: "GOOS", value: config.GOOS},
{name: "GOARCH", value: config.GOARCH},
{name: "GOROOT", value: config.GOROOT},
{name: "GOPATH", value: config.ProjectDir},
{name: "GOCACHE", value: config.GOCACHE},
{name: "GOMODCACHE", value: config.GOMODCACHE},
{name: "GOPROXY", value: config.GOPROXY},
{name: "HTTP_PROXY", value: config.HTTPPROXY},
{name: "HTTPS_PROXY", value: config.HTTPSPROXY},
{name: "PATH", value: joinPathList(goBinDir, assets.GetZigDir(), os.Getenv("PATH"))},
{name: "HOME", value: getHomeDir()},
}
return append(env, overrides...)
extraNames := make([]string, 0, len(extra))
for name := range extra {
extraNames = append(extraNames, name)
}
sort.Strings(extraNames)
for _, name := range extraNames {
overrides = append(overrides, struct {
name string
value string
}{name: name, value: extra[name]})
}
for _, override := range overrides {
env = setCommandEnv(env, override.name, override.value)
}
return env
}
func garbleCommandEnv(config GoConfig) []string {
controlFlow := "0"
if config.ObfuscationControlFlow {
controlFlow = "1"
}
return buildCommandEnv(config, map[string]string{
garbleExperimentalControlFlowEnv: controlFlow,
"GOGARBLE": config.GOGARBLE,
"GODEBUG": setGoDebugSetting(os.Getenv("GODEBUG"), "randautoseed", "0"),
})
}
func validateObfuscationConfig(config GoConfig) error {
if config.ObfuscationControlFlow && !config.Obfuscation {
return fmt.Errorf("control-flow obfuscation requires symbol obfuscation")
}
return nil
}
func garbleRandomSeed(stderr string) string {
for _, line := range strings.Split(stderr, "\n") {
if seed, ok := strings.CutPrefix(strings.TrimSpace(line), garbleRandomSeedPrefix); ok {
return strings.TrimSpace(seed)
}
}
return ""
}
func sanitizeCommandErrorOutput(output string) string {
output = strings.Map(func(character rune) rune {
switch character {
case '\n', '\r', '\t':
return character
default:
if character < 0x20 || character == 0x7f {
return -1
}
return character
}
}, strings.TrimSpace(output))
if len(output) <= commandErrorOutputLimit {
return output
}
headLength := commandErrorOutputLimit / 4
tailLength := commandErrorOutputLimit - headLength
return output[:headLength] + "\n... output truncated ...\n" + output[len(output)-tailLength:]
}
func garbleCommandError(err error, stderr string) error {
details := sanitizeCommandErrorOutput(stderr)
if details == "" {
return fmt.Errorf("garble command failed: %w", err)
}
return fmt.Errorf("garble command failed: %w: %s", err, details)
}
// GarbleCmd - Execute a go command
func GarbleCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
if err := validateObfuscationConfig(config); err != nil {
return nil, err
}
target := fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH)
if _, ok := ValidCompilerTargets(config)[target]; !ok {
return nil, fmt.Errorf("%s", fmt.Sprintf("Invalid compiler target: %s", target))
}
garbleBinPath := goToolPath(config.GOROOT, "garble")
if config.ObfuscationControlFlow {
if err := utilAssets.VerifyGarbleBinary(garbleBinPath, runtime.GOOS, runtime.GOARCH); err != nil {
return nil, fmt.Errorf("%w: %v", ErrGarbleAssetVerification, err)
}
}
garbleFlags := []string{"-seed=random", "-literals", "-tiny"}
if debugDir := strings.TrimSpace(os.Getenv(garbleDebugDirEnv)); debugDir != "" {
garbleFlags = append(garbleFlags, "-debugdir="+debugDir)
}
command = append(garbleFlags, command...)
cmd := exec.Command(garbleBinPath, command...)
cmd.Dir = cwd
cmd.Env = buildCommandEnv(config, map[string]string{
"GOGARBLE": config.GOGARBLE,
})
cmd.Env = garbleCommandEnv(config)
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
@@ -166,6 +293,9 @@ func GarbleCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
}
gogoLog.Infof("garble cmd: '%v'", cmd)
err := cmd.Run()
if seed := garbleRandomSeed(stderr.String()); seed != "" {
gogoLog.Infof("garble random seed: %s", seed)
}
if err != nil {
gogoLog.Debugf("--- env ---\n")
for _, envVar := range cmd.Env {
@@ -174,9 +304,10 @@ func GarbleCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
gogoLog.Errorf("--- stdout ---\n%s\n", stdout.String())
gogoLog.Errorf("--- stderr ---\n%s\n", stderr.String())
gogoLog.Error(err)
return stdout.Bytes(), garbleCommandError(err, stderr.String())
}
return stdout.Bytes(), err
return stdout.Bytes(), nil
}
// GoCmd - Execute a go command
@@ -207,6 +338,9 @@ func GoCmd(config GoConfig, cwd string, command []string) ([]byte, error) {
// GoBuild - Execute a go build command, returns stdout/error
func GoBuild(config GoConfig, src string, dest string, buildmode string, tags []string, ldflags []string, gcflags, asmflags string) ([]byte, error) {
if err := validateObfuscationConfig(config); err != nil {
return nil, err
}
target := fmt.Sprintf("%s/%s", config.GOOS, config.GOARCH)
if _, ok := ValidCompilerTargets(config)[target]; !ok {
return nil, fmt.Errorf("%s", fmt.Sprintf("Invalid compiler target: %s", target))
+114 -2
View File
@@ -19,11 +19,23 @@ package gogo
*/
import (
"os"
"runtime"
"strings"
"testing"
)
func commandEnvValues(env []string, name string) []string {
values := []string{}
for _, envVar := range env {
key, value, found := strings.Cut(envVar, "=")
if found && envKeyEqual(key, name) {
values = append(values, value)
}
}
return values
}
func TestGoToolExecutableName(t *testing.T) {
tests := []struct {
name string
@@ -75,8 +87,8 @@ func TestWithDarwinNoCGOLinknameCompatibility(t *testing.T) {
{
name: "darwin arm64 preserves existing flags",
config: GoConfig{GOOS: "darwin", GOARCH: "arm64", CGO: "0"},
ldflags: []string{"-s -w"},
want: []string{"-s -w " + darwinNoCGOCheckLinknameLDFlag},
ldflags: []string{"-s -w -buildid="},
want: []string{"-s -w -buildid= " + darwinNoCGOCheckLinknameLDFlag},
},
{
name: "coalesces multiple linker flag arguments",
@@ -148,3 +160,103 @@ func TestGoVersionUsesHostToolchain(t *testing.T) {
t.Fatalf("unexpected go version output: %q", string(output))
}
}
func TestGarbleCommandEnvOverridesAmbientControlFlow(t *testing.T) {
tests := []struct {
name string
ambient string
controlFlow bool
want string
}{
{
name: "enable overrides ambient disabled",
ambient: "0",
controlFlow: true,
want: "1",
},
{
name: "disable overrides ambient enabled",
ambient: "1",
controlFlow: false,
want: "0",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Setenv(garbleExperimentalControlFlowEnv, test.ambient)
config := GoConfig{
Obfuscation: true,
ObfuscationControlFlow: test.controlFlow,
}
values := commandEnvValues(garbleCommandEnv(config), garbleExperimentalControlFlowEnv)
if len(values) != 1 {
t.Fatalf("%s occurs %d times in Garble environment, want exactly once: %q", garbleExperimentalControlFlowEnv, len(values), values)
}
if values[0] != test.want {
t.Fatalf("%s = %q, want %q", garbleExperimentalControlFlowEnv, values[0], test.want)
}
if ambient := os.Getenv(garbleExperimentalControlFlowEnv); ambient != test.ambient {
t.Fatalf("building Garble environment changed process %s to %q, want ambient value %q", garbleExperimentalControlFlowEnv, ambient, test.ambient)
}
})
}
}
func TestGarbleCommandEnvMakesGlobalRandDeterministic(t *testing.T) {
ambient := "asyncpreemptoff=1,randautoseed=1,panicnil=1,randautoseed=2"
t.Setenv("GODEBUG", ambient)
values := commandEnvValues(garbleCommandEnv(GoConfig{}), "GODEBUG")
if len(values) != 1 {
t.Fatalf("GODEBUG occurs %d times in Garble environment, want exactly once: %q", len(values), values)
}
want := "asyncpreemptoff=1,panicnil=1,randautoseed=0"
if values[0] != want {
t.Fatalf("GODEBUG = %q, want %q", values[0], want)
}
if got := os.Getenv("GODEBUG"); got != ambient {
t.Fatalf("building Garble environment changed process GODEBUG to %q, want %q", got, ambient)
}
}
func TestGoBuildRejectsControlFlowWithoutObfuscation(t *testing.T) {
config := GoConfig{ObfuscationControlFlow: true}
_, err := GoBuild(config, "", "", "", nil, nil, "", "")
if err == nil {
t.Fatal("GoBuild accepted control-flow obfuscation without symbol obfuscation")
}
if !strings.Contains(err.Error(), "control-flow obfuscation requires symbol obfuscation") {
t.Fatalf("GoBuild returned unexpected error: %v", err)
}
}
func TestGarbleRandomSeed(t *testing.T) {
stderr := "-seed chosen at random: AAAAAAAAAAA\nsome other output\n"
if seed := garbleRandomSeed(stderr); seed != "AAAAAAAAAAA" {
t.Fatalf("garbleRandomSeed() = %q, want %q", seed, "AAAAAAAAAAA")
}
if seed := garbleRandomSeed("no seed here"); seed != "" {
t.Fatalf("garbleRandomSeed() = %q, want empty", seed)
}
}
func TestGarbleCommandErrorIsBoundedAndSanitized(t *testing.T) {
stderr := garbleRandomSeedPrefix + "AAAAAAAAAAA\n" + strings.Repeat("x", commandErrorOutputLimit*2) + "\x00failure detail"
err := garbleCommandError(os.ErrInvalid, stderr)
if err == nil {
t.Fatal("garbleCommandError() returned nil")
}
message := err.Error()
if len(message) > commandErrorOutputLimit+512 {
t.Fatalf("garbleCommandError() returned %d bytes, expected bounded output", len(message))
}
if strings.ContainsRune(message, '\x00') {
t.Fatal("garbleCommandError() retained a NUL byte")
}
if !strings.Contains(message, "AAAAAAAAAAA") || !strings.Contains(message, "failure detail") {
t.Fatalf("garbleCommandError() lost seed or tail detail: %q", message)
}
}
+1 -1
View File
@@ -80,7 +80,7 @@ func beaconRegisterHandler(implantConn *core.ImplantConnection, data []byte) *sl
beacon.ReconnectInterval = beaconReg.Register.ReconnectInterval
beacon.ActiveC2 = beaconReg.Register.ActiveC2
beacon.ProxyURL = beaconReg.Register.ProxyURL
// beacon.ConfigID = models.ParseUUIDOrNil(beaconReg.Register.ConfigID)
beacon.ImplantConfigID = models.ParseUUIDOrNil(beaconReg.Register.ConfigID)
beacon.Locale = beaconReg.Register.Locale
beacon.Capabilities = beaconReg.Register.Capabilities
+20
View File
@@ -24,9 +24,12 @@ import (
"fmt"
"os"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
"github.com/bishopfox/sliver/server/generate"
"github.com/bishopfox/sliver/server/gogo"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@@ -81,6 +84,12 @@ func rpcError(err error) error {
return status.Error(codes.AlreadyExists, err.Error())
case errors.Is(err, core.ErrDuplicateExternalBuilderName):
return status.Error(codes.AlreadyExists, err.Error())
case errors.Is(err, generate.ErrControlFlowBuildBusy):
return status.Error(codes.ResourceExhausted, err.Error())
case errors.Is(err, generate.ErrControlFlowUnavailable):
return status.Error(codes.FailedPrecondition, err.Error())
case errors.Is(err, gogo.ErrGarbleAssetVerification):
return status.Error(codes.FailedPrecondition, err.Error())
case errors.Is(err, db.ErrRecordNotFound):
return status.Error(codes.NotFound, err.Error())
case os.IsNotExist(err):
@@ -91,3 +100,14 @@ func rpcError(err error) error {
return status.Error(codes.Internal, err.Error())
}
}
func acquireControlFlowBuild(config *clientpb.ImplantConfig) (func(), error) {
if err := generate.ValidateControlFlowConfig(config); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
release, err := generate.AcquireControlFlowBuildSlot(config)
if err != nil {
return nil, rpcError(err)
}
return release, nil
}
+9
View File
@@ -98,10 +98,18 @@ func (rpc *Server) Backdoor(ctx context.Context, req *clientpb.BackdoorReq) (*cl
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("no profile found for name %s", req.ProfileName))
}
if p.Config == nil {
return nil, status.Error(codes.InvalidArgument, "profile is missing an implant config")
}
if p.Config.Format != clientpb.OutputFormat_SHELLCODE {
return nil, status.Error(codes.InvalidArgument, "please select a profile targeting a shellcode format")
}
releaseControlFlowBuild, err := acquireControlFlowBuild(p.Config)
if err != nil {
return nil, err
}
defer releaseControlFlowBuild()
build, err := generate.GenerateConfig(name, p.Config)
if err != nil {
return nil, rpcError(err)
@@ -117,6 +125,7 @@ func (rpc *Server) Backdoor(ctx context.Context, req *clientpb.BackdoorReq) (*cl
if err != nil {
return nil, rpcError(err)
}
releaseControlFlowBuild()
shellcode, err := os.ReadFile(fPath)
if err != nil {
return nil, rpcError(err)
+132 -25
View File
@@ -59,10 +59,14 @@ var (
// Generate - Generate a new implant
func (rpc *Server) Generate(ctx context.Context, req *clientpb.GenerateReq) (*clientpb.Generate, error) {
var (
err error
config *clientpb.ImplantConfig
name string
err error
config *clientpb.ImplantConfig
name string
sourceProfileID string
)
if req == nil || req.Config == nil {
return nil, status.Error(codes.InvalidArgument, "missing implant config")
}
if req.Name == "" {
name, err = codenames.GetCodename()
@@ -76,14 +80,25 @@ func (rpc *Server) Generate(ctx context.Context, req *clientpb.GenerateReq) (*cl
}
if req.Config.ID != "" {
// if this is a profile reuse existing configuration
// If this is a profile, load its configuration as the source for an
// immutable per-build snapshot. Do not attach the build to the mutable
// profile config row.
config, err = db.ImplantConfigByID(req.Config.ID)
if err != nil {
return nil, rpcError(err)
}
} else {
// configure c2 channels to enable
if strings.TrimSpace(req.Config.ImplantProfileID) != "" {
return nil, status.Error(codes.InvalidArgument, "implant profile ID requires a persisted implant config ID")
}
config = req.Config
}
config, sourceProfileID, err = generate.NewImplantConfigSnapshot(config)
if err != nil {
return nil, rpcError(err)
}
if req.Config.ID == "" {
// configure c2 channels to enable
config.IncludeMTLS = config.IncludeMTLS || models.IsC2Enabled([]string{"mtls"}, config.C2)
config.IncludeWG = config.IncludeWG || models.IsC2Enabled([]string{"wg"}, config.C2)
config.IncludeHTTP = config.IncludeHTTP || models.IsC2Enabled([]string{"http", "https"}, config.C2)
@@ -91,10 +106,14 @@ func (rpc *Server) Generate(ctx context.Context, req *clientpb.GenerateReq) (*cl
config.IncludeNamePipe = config.IncludeNamePipe || models.IsC2Enabled([]string{"namedpipe"}, config.C2)
config.IncludeTCP = config.IncludeTCP || models.IsC2Enabled([]string{"tcppivot"}, config.C2)
}
if len(config.Exports) == 0 {
config.Exports = []string{"StartW"}
}
releaseControlFlowBuild, err := acquireControlFlowBuild(config)
if err != nil {
return nil, err
}
defer releaseControlFlowBuild()
// generate config
build, err := generate.GenerateConfig(name, config)
@@ -103,13 +122,13 @@ func (rpc *Server) Generate(ctx context.Context, req *clientpb.GenerateReq) (*cl
}
// retrieve http c2 implant config
httpC2Config, err := db.LoadHTTPC2ConfigByName(req.Config.HTTPC2ConfigName)
httpC2Config, err := db.LoadHTTPC2ConfigByName(config.HTTPC2ConfigName)
if err != nil {
return nil, rpcError(err)
}
var fPath string
switch req.Config.Format {
switch config.Format {
case clientpb.OutputFormat_SERVICE:
fallthrough
case clientpb.OutputFormat_EXECUTABLE:
@@ -121,7 +140,7 @@ func (rpc *Server) Generate(ctx context.Context, req *clientpb.GenerateReq) (*cl
case clientpb.OutputFormat_SHELLCODE:
fPath, err = generate.SliverShellcode(name, build, config, httpC2Config.ImplantConfig)
default:
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid output format: %s", req.Config.Format))
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid output format: %s", config.Format))
}
if err != nil {
return nil, rpcError(err)
@@ -132,10 +151,21 @@ func (rpc *Server) Generate(ctx context.Context, req *clientpb.GenerateReq) (*cl
if err != nil {
return nil, rpcError(err)
}
config, err = db.CreateImplantConfigWithID(config)
if err != nil {
return nil, rpcError(err)
}
err = generate.ImplantBuildSave(build, config, fPath)
if sourceProfileID == "" {
err = generate.ImplantBuildSave(build, config, fPath)
} else {
err = generate.ImplantBuildSaveWithSourceProfile(build, config, fPath, sourceProfileID)
}
if err != nil {
rpcLog.Errorf("Failed to save external build: %s", err)
if cleanupErr := db.DeleteFailedImplantConfigSnapshot(config.ID); cleanupErr != nil {
rpcLog.Errorf("Failed to roll back config snapshot %s after build save failure: %s", config.ID, cleanupErr)
}
return nil, rpcError(err)
}
@@ -487,6 +517,12 @@ func (rpc *Server) ImplantProfiles(ctx context.Context, _ *commonpb.Empty) (*cli
// SaveImplantProfile - Save a new profile
func (rpc *Server) SaveImplantProfile(ctx context.Context, profile *clientpb.ImplantProfile) (*clientpb.ImplantProfile, error) {
if profile == nil || profile.Config == nil {
return nil, status.Error(codes.InvalidArgument, "missing implant profile config")
}
if err := generate.ValidateControlFlowConfig(profile.Config); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
profile.Name = filepath.Base(profile.Name)
if 0 < len(profile.Name) && profile.Name != "." {
rpcLog.Infof("Saving new profile with name %#v", profile.Name)
@@ -533,12 +569,27 @@ func (rpc *Server) DeleteImplantBuild(ctx context.Context, req *clientpb.DeleteR
// Remove Implant build given the build name
func RemoveBuildByName(name string) error {
resourceID, err := db.ResourceIDByName(name)
build, err := db.ImplantBuildByName(name)
if err != nil {
return err
}
build, err := db.ImplantBuildByName(name)
resourceID, err := db.ResourceIDByName(name)
if errors.Is(err, db.ErrRecordNotFound) {
err = generate.DeletePendingImplantBuild(build.ID)
if errors.Is(err, generate.ErrImplantBuildNotPending) {
return fmt.Errorf("implant build %q has completion state but no resource ID", name)
}
if err != nil {
return err
}
core.RemoveExternalBuildAssignment(build.ID)
core.EventBroker.Publish(core.Event{
EventType: consts.BuildEvent,
Data: []byte(build.Name),
})
return nil
}
if err != nil {
return err
}
@@ -581,6 +632,12 @@ func (rpc *Server) GetCompiler(ctx context.Context, _ *commonpb.Empty) (*clientp
Targets: generate.GetCompilerTargets(),
UnsupportedTargets: generate.GetUnsupportedTargets(),
CrossCompilers: generate.GetCrossCompilers(),
Capabilities: []string{generate.ControlFlowCapability},
}
if err := generate.ControlFlowRuntimeError(); err != nil {
rcpGenLog.Warnf("Local builds using %s are unavailable: %s", generate.ControlFlowCapability, err)
} else {
compiler.Capabilities = append(compiler.Capabilities, generate.LocalControlFlowCapability)
}
rcpGenLog.Debugf("GetCompiler = %v", compiler)
return compiler, nil
@@ -616,6 +673,16 @@ func (rpc *Server) GenerateExternal(ctx context.Context, req *clientpb.ExternalG
if config == nil {
return nil, status.Error(codes.InvalidArgument, "invalid implant config")
}
config, err = resolveExternalBuildConfig(config)
if err != nil {
return nil, err
}
if err := generate.ValidateControlFlowConfig(config); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
if generate.ControlFlowEnabled(config) && !generate.HasControlFlowCapability(builder.Capabilities) {
return nil, status.Errorf(codes.FailedPrecondition, "builder %q does not support %s", req.BuilderName, generate.ControlFlowCapability)
}
externalConfig, err := generate.SliverExternal(name, config)
if err != nil {
return nil, rpcError(err)
@@ -630,6 +697,24 @@ func (rpc *Server) GenerateExternal(ctx context.Context, req *clientpb.ExternalG
return externalConfig, err
}
func resolveExternalBuildConfig(config *clientpb.ImplantConfig) (*clientpb.ImplantConfig, error) {
if config == nil {
return nil, status.Error(codes.InvalidArgument, "invalid implant config")
}
if config.ID == "" {
if strings.TrimSpace(config.ImplantProfileID) != "" {
return nil, status.Error(codes.InvalidArgument, "implant profile ID requires a persisted implant config ID")
}
return config, nil
}
persistedConfig, err := db.ImplantConfigByID(config.ID)
if err != nil {
return nil, rpcError(err)
}
return persistedConfig, nil
}
// GenerateExternalSaveBuild - Allows an external builder to save the build to the server
func (rpc *Server) GenerateExternalSaveBuild(ctx context.Context, req *clientpb.ExternalImplantBinary) (*commonpb.Empty, error) {
if req.GetFile() == nil {
@@ -805,11 +890,16 @@ func (rpc *Server) BuilderTrigger(ctx context.Context, req *clientpb.Event) (*co
if err = rpc.authorizeBuilderOperatorForExternalBuild(ctx, buildID); err != nil {
return nil, err
}
cleanupErr := generate.DeletePendingImplantBuild(buildID)
core.RemoveExternalBuildAssignment(buildID)
core.EventBroker.Publish(core.Event{
EventType: req.EventType,
Data: req.Data,
})
if cleanupErr != nil && !errors.Is(cleanupErr, generate.ErrImplantBuildNotPending) {
rpcLog.Errorf("Failed to clean up pending external build %s: %s", buildID, cleanupErr)
return nil, rpcError(cleanupErr)
}
case consts.AcknowledgeBuildEvent:
buildID := strings.TrimSpace(string(req.Data))
if buildID == "" {
@@ -1092,37 +1182,46 @@ func (rpc *Server) GenerateStage(ctx context.Context, req *clientpb.GenerateStag
} else {
name = req.Name
}
// retrieve http c2 implant config
httpC2Config, err := db.LoadHTTPC2ConfigByName(profile.Config.HTTPC2ConfigName)
config, sourceProfileID, err := generate.NewImplantConfigSnapshot(profile.Config)
if err != nil {
return nil, rpcError(err)
}
if len(profile.Config.Exports) == 0 {
profile.Config.Exports = []string{"StartW"}
// retrieve http c2 implant config
httpC2Config, err := db.LoadHTTPC2ConfigByName(config.HTTPC2ConfigName)
if err != nil {
return nil, rpcError(err)
}
if len(config.Exports) == 0 {
config.Exports = []string{"StartW"}
}
releaseControlFlowBuild, err := acquireControlFlowBuild(config)
if err != nil {
return nil, err
}
defer releaseControlFlowBuild()
// generate config
build, err := generate.GenerateConfig(name, profile.Config)
build, err := generate.GenerateConfig(name, config)
if err != nil {
return nil, rpcError(err)
}
var fPath string
switch profile.Config.Format {
switch config.Format {
case clientpb.OutputFormat_SERVICE:
fallthrough
case clientpb.OutputFormat_EXECUTABLE:
fPath, err = generate.SliverExecutable(name, build, profile.Config, httpC2Config.ImplantConfig)
fPath, err = generate.SliverExecutable(name, build, config, httpC2Config.ImplantConfig)
case clientpb.OutputFormat_SHARED_LIB:
fPath, err = generate.SliverSharedLibrary(name, build, profile.Config, httpC2Config.ImplantConfig)
fPath, err = generate.SliverSharedLibrary(name, build, config, httpC2Config.ImplantConfig)
case clientpb.OutputFormat_GO_ARCHIVE:
fPath, err = generate.SliverArchive(name, build, profile.Config, httpC2Config.ImplantConfig)
fPath, err = generate.SliverArchive(name, build, config, httpC2Config.ImplantConfig)
case clientpb.OutputFormat_SHELLCODE:
fPath, err = generate.SliverShellcode(name, build, profile.Config, httpC2Config.ImplantConfig)
fPath, err = generate.SliverShellcode(name, build, config, httpC2Config.ImplantConfig)
default:
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid output format: %s", profile.Config.Format))
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid output format: %s", config.Format))
}
if err != nil {
return nil, rpcError(err)
@@ -1170,9 +1269,17 @@ func (rpc *Server) GenerateStage(ctx context.Context, req *clientpb.GenerateStag
}
}
err = generate.SaveStage(build, profile.Config, stage2, stageType)
config, err = db.CreateImplantConfigWithID(config)
if err != nil {
return nil, rpcError(err)
}
err = generate.SaveStageWithSourceProfile(build, config, stage2, stageType, sourceProfileID)
if err != nil {
rpcLog.Errorf("Failed to save external build: %s", err)
if cleanupErr := db.DeleteFailedImplantConfigSnapshot(config.ID); cleanupErr != nil {
rpcLog.Errorf("Failed to roll back config snapshot %s after stage save failure: %s", config.ID, cleanupErr)
}
return nil, rpcError(err)
}
@@ -0,0 +1,105 @@
package rpc
import (
"context"
"strings"
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/generate"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestGenerateExternalRejectsBuilderWithoutControlFlowCapability(t *testing.T) {
builderName := "control-flow-incompatible-builder"
if err := core.AddBuilder(&clientpb.Builder{
Name: builderName,
OperatorName: "control-flow-test-operator",
}); err != nil {
t.Fatalf("register test builder: %v", err)
}
t.Cleanup(func() {
core.RemoveBuilder(builderName)
})
rpc := &Server{}
_, err := rpc.GenerateExternal(context.Background(), &clientpb.ExternalGenerateReq{
Name: "control-flow-capability-test",
BuilderName: builderName,
Config: &clientpb.ImplantConfig{
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
TemplateName: "sliver",
},
})
if status.Code(err) != codes.FailedPrecondition {
t.Fatalf("GenerateExternal() error = %v, code = %s; want %s", err, status.Code(err), codes.FailedPrecondition)
}
if !strings.Contains(err.Error(), "does not support garble.control-flow/balanced-v1") {
t.Fatalf("GenerateExternal() error = %v, want control-flow capability detail", err)
}
}
func TestGenerateRejectsInvalidControlFlowConfigAsInvalidArgument(t *testing.T) {
_, err := (&Server{}).Generate(context.Background(), &clientpb.GenerateReq{
Name: "control-flow-invalid-config",
Config: &clientpb.ImplantConfig{
Debug: true,
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
},
})
if status.Code(err) != codes.InvalidArgument {
t.Fatalf("Generate() error = %v, code = %s; want %s", err, status.Code(err), codes.InvalidArgument)
}
}
func TestSaveImplantProfileRejectsInvalidControlFlowConfigAsInvalidArgument(t *testing.T) {
_, err := (&Server{}).SaveImplantProfile(context.Background(), &clientpb.ImplantProfile{
Name: "control-flow-invalid-profile",
Config: &clientpb.ImplantConfig{
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy(99),
},
})
if status.Code(err) != codes.InvalidArgument {
t.Fatalf("SaveImplantProfile() error = %v, code = %s; want %s", err, status.Code(err), codes.InvalidArgument)
}
}
func TestGenerateRejectsConcurrentControlFlowBuild(t *testing.T) {
config := &clientpb.ImplantConfig{
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
TemplateName: "sliver",
}
release, err := generate.AcquireControlFlowBuildSlot(config)
if err != nil {
t.Fatalf("AcquireControlFlowBuildSlot() error = %v", err)
}
defer release()
_, err = (&Server{}).Generate(context.Background(), &clientpb.GenerateReq{
Name: "control-flow-contended-build",
Config: config,
})
if status.Code(err) != codes.ResourceExhausted {
t.Fatalf("Generate() error = %v, code = %s; want %s", err, status.Code(err), codes.ResourceExhausted)
}
}
func TestGetCompilerAdvertisesProtocolAndVerifiedLocalControlFlowCapabilities(t *testing.T) {
compiler, err := (&Server{}).GetCompiler(context.Background(), &commonpb.Empty{})
if err != nil {
t.Fatalf("GetCompiler() error = %v", err)
}
if !generate.HasControlFlowCapability(compiler.Capabilities) {
t.Fatalf("GetCompiler() capabilities = %q, want %q", compiler.Capabilities, generate.ControlFlowCapability)
}
if !generate.HasLocalControlFlowCapability(compiler.Capabilities) {
t.Fatalf("GetCompiler() capabilities = %q, want %q", compiler.Capabilities, generate.LocalControlFlowCapability)
}
}
@@ -0,0 +1,233 @@
package rpc
import (
"context"
"fmt"
"testing"
"time"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/server/core"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
"github.com/bishopfox/sliver/server/generate"
"github.com/gofrs/uuid"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestExternalBuildFailedDeletesPendingBuildAndDetachedSnapshot(t *testing.T) {
fixture := newPendingExternalBuildFixture(t, "external-failed")
builds, err := db.ImplantBuilds()
if err != nil {
t.Fatalf("ImplantBuilds() with a pending build: %v", err)
}
if builds.Configs[fixture.name] != nil {
t.Fatalf("ImplantBuilds() exposed pending build %q as completed", fixture.name)
}
if builds.ResourceIDs[fixture.name] != nil {
t.Fatalf("pending build %q unexpectedly has a resource ID", fixture.name)
}
const operatorName = "external-failed-operator"
core.TrackExternalBuildAssignment(fixture.buildID, "external-failed-builder", operatorName)
t.Cleanup(func() { core.RemoveExternalBuildAssignment(fixture.buildID) })
_, err = (&Server{}).BuilderTrigger(contextWithCommonName(operatorName), &clientpb.Event{
EventType: consts.ExternalBuildFailedEvent,
Data: []byte(fixture.buildID + ":compiler failed"),
})
if err != nil {
t.Fatalf("BuilderTrigger(ExternalBuildFailedEvent) error = %v", err)
}
if assignment := core.GetExternalBuildAssignment(fixture.buildID); assignment != nil {
t.Fatalf("external build assignment still exists after failure: %+v", assignment)
}
assertExternalBuildFixtureCounts(t, fixture, 0, 0, 0)
}
func TestExternalBuildFailedProtectsCompletedBuild(t *testing.T) {
fixture := newPendingExternalBuildFixture(t, "external-completed")
resourceID := uint64(time.Now().UnixNano())
if err := db.SaveResourceID(&clientpb.ResourceID{Name: fixture.name, Value: resourceID}); err != nil {
t.Fatalf("SaveResourceID() error = %v", err)
}
build, err := db.ImplantBuildByName(fixture.name)
if err != nil {
t.Fatalf("ImplantBuildByName() error = %v", err)
}
build.ImplantID = resourceID
build.MD5 = "completed-md5"
build.SHA1 = "completed-sha1"
build.SHA256 = "completed-sha256"
if _, err := db.SaveImplantBuild(build); err != nil {
t.Fatalf("SaveImplantBuild() completed update error = %v", err)
}
const operatorName = "external-completed-operator"
core.TrackExternalBuildAssignment(fixture.buildID, "external-completed-builder", operatorName)
t.Cleanup(func() { core.RemoveExternalBuildAssignment(fixture.buildID) })
_, err = (&Server{}).BuilderTrigger(contextWithCommonName(operatorName), &clientpb.Event{
EventType: consts.ExternalBuildFailedEvent,
Data: []byte(fixture.buildID + ":late failure"),
})
if err != nil {
t.Fatalf("BuilderTrigger(ExternalBuildFailedEvent) error = %v", err)
}
if assignment := core.GetExternalBuildAssignment(fixture.buildID); assignment != nil {
t.Fatalf("external build assignment still exists after terminal failure: %+v", assignment)
}
assertExternalBuildFixtureCounts(t, fixture, 1, 1, 1)
}
func TestRemoveBuildByNameDeletesPendingBuildAndDetachedSnapshot(t *testing.T) {
fixture := newPendingExternalBuildFixture(t, "external-remove-pending")
core.TrackExternalBuildAssignment(fixture.buildID, "external-remove-builder", "external-remove-operator")
t.Cleanup(func() { core.RemoveExternalBuildAssignment(fixture.buildID) })
if err := RemoveBuildByName(fixture.name); err != nil {
t.Fatalf("RemoveBuildByName() pending build error = %v", err)
}
if assignment := core.GetExternalBuildAssignment(fixture.buildID); assignment != nil {
t.Fatalf("external build assignment still exists after removal: %+v", assignment)
}
assertExternalBuildFixtureCounts(t, fixture, 0, 0, 0)
}
func TestGenerateExternalRejectsUnpersistedProfileAssociation(t *testing.T) {
builderName := fmt.Sprintf("external-profile-trust-builder-%d", time.Now().UnixNano())
if err := core.AddBuilder(&clientpb.Builder{Name: builderName, OperatorName: "external-profile-trust-operator"}); err != nil {
t.Fatalf("AddBuilder() error = %v", err)
}
t.Cleanup(func() { core.RemoveBuilder(builderName) })
_, err := (&Server{}).GenerateExternal(context.Background(), &clientpb.ExternalGenerateReq{
Name: fmt.Sprintf("external-profile-trust-%d", time.Now().UnixNano()),
BuilderName: builderName,
Config: &clientpb.ImplantConfig{
ImplantProfileID: uuid.Must(uuid.NewV4()).String(),
},
})
if status.Code(err) != codes.InvalidArgument {
t.Fatalf("GenerateExternal() error = %v, code = %s; want %s", err, status.Code(err), codes.InvalidArgument)
}
}
func TestResolveExternalBuildConfigUsesPersistedProfileAssociation(t *testing.T) {
profileName := fmt.Sprintf("external-profile-source-%d", time.Now().UnixNano())
profile, err := generate.SaveImplantProfile(&clientpb.ImplantProfile{
Name: profileName,
Config: &clientpb.ImplantConfig{
GOOS: "linux",
GOARCH: "amd64",
TemplateName: generate.SliverTemplateName,
},
})
if err != nil {
t.Fatalf("SaveImplantProfile() error = %v", err)
}
t.Cleanup(func() { _ = db.DeleteProfile(profileName) })
resolved, err := resolveExternalBuildConfig(&clientpb.ImplantConfig{
ID: profile.Config.ID,
ImplantProfileID: uuid.Must(uuid.NewV4()).String(),
GOOS: "forged",
})
if err != nil {
t.Fatalf("resolveExternalBuildConfig() error = %v", err)
}
if resolved.GOOS != "linux" {
t.Fatalf("resolved GOOS = %q, want persisted value %q", resolved.GOOS, "linux")
}
if resolved.ImplantProfileID != profile.ID {
t.Fatalf("resolved profile ID = %q, want persisted association %q", resolved.ImplantProfileID, profile.ID)
}
}
type pendingExternalBuildFixture struct {
name string
buildID string
configID uuid.UUID
}
func newPendingExternalBuildFixture(t *testing.T, prefix string) pendingExternalBuildFixture {
t.Helper()
configID := uuid.Must(uuid.NewV4())
name := fmt.Sprintf("%s-%d", prefix, time.Now().UnixNano())
config, err := db.CreateImplantConfigWithID(&clientpb.ImplantConfig{
ID: configID.String(),
GOOS: "linux",
GOARCH: "amd64",
TemplateName: generate.SliverTemplateName,
C2: []*clientpb.ImplantC2{{URL: "mtls://127.0.0.1:8888"}},
CanaryDomains: []string{"pending.example"},
Assets: []*commonpb.File{{Name: "pending-asset"}},
})
if err != nil {
t.Fatalf("CreateImplantConfigWithID() error = %v", err)
}
build, err := db.SaveImplantBuild(&clientpb.ImplantBuild{
Name: name,
ImplantConfigID: config.ID,
})
if err != nil {
t.Fatalf("SaveImplantBuild() pending build error = %v", err)
}
fixture := pendingExternalBuildFixture{name: name, buildID: build.ID, configID: configID}
t.Cleanup(func() { cleanupExternalBuildFixture(fixture) })
return fixture
}
func cleanupExternalBuildFixture(fixture pendingExternalBuildFixture) {
buildID := uuid.FromStringOrNil(fixture.buildID)
db.Session().Where("id = ?", buildID).Delete(&models.ImplantBuild{})
db.Session().Where("name = ?", fixture.name).Delete(&models.ResourceID{})
db.Session().Where("implant_config_id = ?", fixture.configID).Delete(&models.ImplantC2{})
db.Session().Where("implant_config_id = ?", fixture.configID).Delete(&models.EncoderAsset{})
db.Session().Where("implant_config_id = ?", fixture.configID).Delete(&models.CanaryDomain{})
db.Session().Where("id = ?", fixture.configID).Delete(&models.ImplantConfig{})
}
func assertExternalBuildFixtureCounts(t *testing.T, fixture pendingExternalBuildFixture, wantBuilds int64, wantConfigs int64, wantResources int64) {
t.Helper()
var buildCount int64
if err := db.Session().Model(&models.ImplantBuild{}).Where("id = ?", uuid.FromStringOrNil(fixture.buildID)).Count(&buildCount).Error; err != nil {
t.Fatalf("count implant builds: %v", err)
}
var configCount int64
if err := db.Session().Model(&models.ImplantConfig{}).Where("id = ?", fixture.configID).Count(&configCount).Error; err != nil {
t.Fatalf("count implant configs: %v", err)
}
var resourceCount int64
if err := db.Session().Model(&models.ResourceID{}).Where("name = ?", fixture.name).Count(&resourceCount).Error; err != nil {
t.Fatalf("count resource IDs: %v", err)
}
if buildCount != wantBuilds || configCount != wantConfigs || resourceCount != wantResources {
t.Fatalf(
"fixture counts = builds:%d configs:%d resources:%d, want builds:%d configs:%d resources:%d",
buildCount, configCount, resourceCount, wantBuilds, wantConfigs, wantResources,
)
}
if wantConfigs == 0 {
for name, model := range map[string]interface{}{
"C2": &models.ImplantC2{},
"assets": &models.EncoderAsset{},
"canaries": &models.CanaryDomain{},
} {
var count int64
if err := db.Session().Model(model).Where("implant_config_id = ?", fixture.configID).Count(&count).Error; err != nil {
t.Fatalf("count %s associations: %v", name, err)
}
if count != 0 {
t.Fatalf("%s association count = %d, want 0", name, count)
}
}
}
}
+108
View File
@@ -8,6 +8,7 @@ import (
"net"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
@@ -20,9 +21,12 @@ import (
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/configs"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
generatePkg "github.com/bishopfox/sliver/server/generate"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"google.golang.org/grpc/test/bufconn"
)
@@ -72,6 +76,23 @@ func TestGenerateSpoofMetadataAppliesPETimestampOverBufnet(t *testing.T) {
if len(origData) == 0 {
t.Fatal("Generate() returned empty file data")
}
persistedBuild, err := db.ImplantBuildByName(buildName)
if err != nil {
t.Fatalf("load generated implant build: %v", err)
}
if persistedBuild.ImplantConfigID == "" {
t.Fatal("generated implant build has an empty config ID")
}
persistedConfig, err := db.ImplantConfigByID(persistedBuild.ImplantConfigID)
if err != nil {
t.Fatalf("load generated implant config: %v", err)
}
if persistedConfig.ID != persistedBuild.ImplantConfigID {
t.Fatalf("persisted config ID = %q, build config ID = %q", persistedConfig.ID, persistedBuild.ImplantConfigID)
}
if !bytes.Contains(origData, []byte(persistedConfig.ID)) {
t.Fatalf("generated artifact does not contain persisted config ID %q", persistedConfig.ID)
}
origTimestamp, err := peFileTimestamp(origData)
if err != nil {
t.Fatalf("parse generated PE timestamp: %v", err)
@@ -131,6 +152,93 @@ func TestGenerateSpoofMetadataAppliesPETimestampOverBufnet(t *testing.T) {
}
}
func TestGenerateCompileFailureDoesNotPersistPreselectedConfig(t *testing.T) {
setupGenerateSpoofTest(t)
var configCountBefore int64
if err := db.Session().Model(&models.ImplantConfig{}).Count(&configCountBefore).Error; err != nil {
t.Fatalf("count implant configs before Generate(): %v", err)
}
buildName := fmt.Sprintf("rpc-config-id-failure-%d", time.Now().UnixNano())
config := &clientpb.ImplantConfig{
GOOS: "unsupported",
GOARCH: "amd64",
Format: clientpb.OutputFormat_EXECUTABLE,
Debug: true,
ObfuscateSymbols: false,
C2: []*clientpb.ImplantC2{
{URL: "http://127.0.0.1"},
},
HTTPC2ConfigName: consts.DefaultC2Profile,
}
_, err := (&Server{}).Generate(context.Background(), &clientpb.GenerateReq{
Name: buildName,
Config: config,
})
if err == nil {
t.Fatal("Generate() unexpectedly succeeded for an unsupported compiler target")
}
if !strings.Contains(err.Error(), "invalid compiler target") {
t.Fatalf("Generate() error = %v, want invalid compiler target", err)
}
var configCountAfter int64
if err := db.Session().Model(&models.ImplantConfig{}).Count(&configCountAfter).Error; err != nil {
t.Fatalf("count implant configs after Generate(): %v", err)
}
if configCountAfter != configCountBefore {
t.Fatalf("implant config count after failed Generate() = %d, want %d", configCountAfter, configCountBefore)
}
}
func TestGenerateUsesPersistedProfileBuildSettings(t *testing.T) {
setupGenerateSpoofTest(t)
profileName := fmt.Sprintf("rpc-profile-authority-%d", time.Now().UnixNano())
profile, err := generatePkg.SaveImplantProfile(&clientpb.ImplantProfile{
Name: profileName,
Config: &clientpb.ImplantConfig{
GOOS: "linux",
GOARCH: "amd64",
Format: clientpb.OutputFormat(99),
TemplateName: generatePkg.SliverTemplateName,
HTTPC2ConfigName: consts.DefaultC2Profile,
ObfuscateSymbols: true,
ConnectionStrategy: "s",
C2: []*clientpb.ImplantC2{
{URL: "http://127.0.0.1"},
},
},
})
if err != nil {
t.Fatalf("SaveImplantProfile() error = %v", err)
}
t.Cleanup(func() { _ = db.DeleteProfile(profileName) })
_, err = (&Server{}).Generate(context.Background(), &clientpb.GenerateReq{
Name: fmt.Sprintf("rpc-profile-authority-build-%d", time.Now().UnixNano()),
Config: &clientpb.ImplantConfig{
ID: profile.Config.ID,
Format: clientpb.OutputFormat_EXECUTABLE,
HTTPC2ConfigName: "does-not-exist",
},
})
if status.Code(err) != codes.InvalidArgument || !strings.Contains(err.Error(), "invalid output format") {
t.Fatalf("Generate() error = %v, code = %s; want persisted profile's invalid output format", err, status.Code(err))
}
}
func TestGenerateRejectsUnpersistedProfileAssociation(t *testing.T) {
_, err := (&Server{}).Generate(context.Background(), &clientpb.GenerateReq{
Name: fmt.Sprintf("rpc-profile-trust-%d", time.Now().UnixNano()),
Config: &clientpb.ImplantConfig{
ImplantProfileID: "502f6ad5-1ff1-49cc-bbb5-8a6ea4533661",
},
})
if status.Code(err) != codes.InvalidArgument {
t.Fatalf("Generate() error = %v, code = %s; want %s", err, status.Code(err), codes.InvalidArgument)
}
}
func setupGenerateSpoofTest(t *testing.T) {
t.Helper()
spoofGenerateSetupOnce.Do(func() {
+9
View File
@@ -106,6 +106,9 @@ func (rpc *Server) HijackDLL(ctx context.Context, req *clientpb.DllHijackReq) (*
))
}
if p.Config == nil {
return nil, status.Error(codes.InvalidArgument, "profile is missing an implant config")
}
if p.Config.Format != clientpb.OutputFormat_SHARED_LIB {
return nil, status.Error(codes.InvalidArgument,
"please select a profile targeting a shared library format",
@@ -123,6 +126,11 @@ func (rpc *Server) HijackDLL(ctx context.Context, req *clientpb.DllHijackReq) (*
} else {
name = req.Name
}
releaseControlFlowBuild, err := acquireControlFlowBuild(config)
if err != nil {
return nil, err
}
defer releaseControlFlowBuild()
build, err := generate.GenerateConfig(name, config)
if err != nil {
return nil, rpcError(err)
@@ -143,6 +151,7 @@ func (rpc *Server) HijackDLL(ctx context.Context, req *clientpb.DllHijackReq) (*
if err != nil {
return nil, rpcError(err)
}
releaseControlFlowBuild()
} else {
if len(req.TargetDLL) == 0 {
return nil, status.Error(codes.InvalidArgument, "missing target DLL")
+36 -8
View File
@@ -86,11 +86,25 @@ func (rpc *Server) GetSystem(ctx context.Context, req *clientpb.GetSystemReq) (*
if req == nil || req.Request == nil {
return nil, ErrMissingRequestField
}
if req.Config == nil {
return nil, status.Error(codes.InvalidArgument, "missing implant config")
}
session := core.Sessions.Get(req.Request.SessionID)
if session == nil {
return nil, ErrInvalidSessionID
}
if session.ConfigID != "" {
originatingConfig, err := db.ImplantConfigByID(session.ConfigID)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "cannot verify originating implant policy for session config %q: %v", session.ConfigID, err)
} else if originatingConfig.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
return nil, status.Error(codes.FailedPrecondition, "getsystem does not support control-flow-obfuscated helper builds")
}
}
if req.Config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
return nil, status.Error(codes.FailedPrecondition, "getsystem does not support control-flow-obfuscated helper builds")
}
// retrieve http c2 implant config
httpC2Config, err := db.LoadHTTPC2ConfigByName(req.Config.HTTPC2ConfigName)
@@ -109,15 +123,10 @@ func (rpc *Server) GetSystem(ctx context.Context, req *clientpb.GetSystemReq) (*
name = req.Name
}
shellcode, _, err = getSliverShellcode(name)
shellcode, _, _, err = getSliverShellcode(name)
if err != nil {
req.Config.Format = clientpb.OutputFormat_SHELLCODE
req.Config.ObfuscateSymbols = false
req.Config.IsShellcode = true
req.Config.IsSharedLib = false
req.Config.TemplateName = "sliver"
if len(req.Config.Exports) == 0 {
req.Config.Exports = []string{"StartW"}
if err := prepareGetSystemFallbackConfig(req.Config); err != nil {
return nil, err
}
build, err := generate.GenerateConfig(name, req.Config)
if err != nil {
@@ -151,6 +160,25 @@ func (rpc *Server) GetSystem(ctx context.Context, req *clientpb.GetSystemReq) (*
return getSystem, nil
}
func prepareGetSystemFallbackConfig(config *clientpb.ImplantConfig) error {
if config == nil {
return status.Error(codes.InvalidArgument, "missing implant config")
}
if config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED {
return status.Error(codes.FailedPrecondition, "getsystem does not support control-flow-obfuscated helper builds")
}
config.Format = clientpb.OutputFormat_SHELLCODE
config.ObfuscateSymbols = false
config.IsShellcode = true
config.IsSharedLib = false
config.TemplateName = "sliver"
if len(config.Exports) == 0 {
config.Exports = []string{"StartW"}
}
return nil
}
// MakeToken - Creates a new logon session to impersonate a user based on its credentials.
func (rpc *Server) MakeToken(ctx context.Context, req *sliverpb.MakeTokenReq) (*sliverpb.MakeToken, error) {
resp := &sliverpb.MakeToken{Response: &commonpb.Response{}}
+69
View File
@@ -0,0 +1,69 @@
package rpc
import (
"context"
"strings"
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestGetSystemRejectsMissingImplantConfig(t *testing.T) {
_, err := (&Server{}).GetSystem(context.Background(), &clientpb.GetSystemReq{
Request: &commonpb.Request{SessionID: "unused"},
})
if status.Code(err) != codes.InvalidArgument {
t.Fatalf("GetSystem() error = %v, code = %s; want %s", err, status.Code(err), codes.InvalidArgument)
}
if !strings.Contains(err.Error(), "missing implant config") {
t.Fatalf("GetSystem() error = %v, want missing implant config detail", err)
}
}
func TestPrepareGetSystemFallbackConfigRejectsControlFlowWithoutDowngrade(t *testing.T) {
config := &clientpb.ImplantConfig{
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
Format: clientpb.OutputFormat_EXECUTABLE,
IsSharedLib: true,
TemplateName: "custom",
}
err := prepareGetSystemFallbackConfig(config)
if status.Code(err) != codes.FailedPrecondition {
t.Fatalf("prepareGetSystemFallbackConfig() error = %v, code = %s; want %s", err, status.Code(err), codes.FailedPrecondition)
}
if !strings.Contains(err.Error(), "does not support control-flow-obfuscated helper builds") {
t.Fatalf("prepareGetSystemFallbackConfig() error = %v, want control-flow detail", err)
}
if config.ControlFlow != clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1 || !config.ObfuscateSymbols {
t.Fatalf("control-flow policy was downgraded: %+v", config)
}
if config.Format != clientpb.OutputFormat_EXECUTABLE || !config.IsSharedLib || config.TemplateName != "custom" {
t.Fatalf("config was mutated before fallback rejection: %+v", config)
}
}
func TestPrepareGetSystemFallbackConfigPreservesDisabledBehavior(t *testing.T) {
config := &clientpb.ImplantConfig{
ObfuscateSymbols: true,
Format: clientpb.OutputFormat_EXECUTABLE,
IsSharedLib: true,
}
if err := prepareGetSystemFallbackConfig(config); err != nil {
t.Fatalf("prepareGetSystemFallbackConfig() error = %v", err)
}
if config.Format != clientpb.OutputFormat_SHELLCODE || config.ObfuscateSymbols || !config.IsShellcode || config.IsSharedLib {
t.Fatalf("fallback config = %+v, want legacy shellcode settings", config)
}
if config.TemplateName != "sliver" {
t.Fatalf("TemplateName = %q, want sliver", config.TemplateName)
}
if len(config.Exports) != 1 || config.Exports[0] != "StartW" {
t.Fatalf("Exports = %q, want [StartW]", config.Exports)
}
}
+66 -13
View File
@@ -40,6 +40,7 @@ import (
"github.com/bishopfox/sliver/server/generate"
"github.com/bishopfox/sliver/server/log"
"github.com/bishopfox/sliver/util"
"github.com/gofrs/uuid"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -88,10 +89,42 @@ func (rpc *Server) Migrate(ctx context.Context, req *clientpb.MigrateReq) (*sliv
}
}
var originatingConfig *clientpb.ImplantConfig
if session != nil && session.ConfigID != "" {
originatingConfig, err = db.ImplantConfigByID(session.ConfigID)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "cannot verify originating implant policy for session config %q: %v", session.ConfigID, err)
}
} else if dbBeacon != nil && dbBeacon.ImplantConfigID != models.NilUUID() {
originatingConfig, err = db.ImplantConfigByID(dbBeacon.ImplantConfigID.String())
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "cannot verify originating implant policy for beacon config %q: %v", dbBeacon.ImplantConfigID, err)
}
} else if req.Config != nil {
originatingConfig = req.Config
}
name := filepath.Base(req.Name)
sc, arch, err := getSliverShellcode(name)
sc, arch, cachedConfig, err := getSliverShellcode(name)
if err == nil && !migrateCachedPolicyCompatible(originatingConfig, cachedConfig) {
return nil, status.Errorf(
codes.FailedPrecondition,
"cached build %q control-flow policy %s does not match originating policy %s",
name,
cachedConfig.ControlFlow,
originatingConfig.ControlFlow,
)
}
if err != nil {
if req.Config == nil {
return nil, status.Error(codes.InvalidArgument, "missing implant config")
}
config := req.Config
if originatingConfig != nil {
config.Debug = originatingConfig.Debug
config.ObfuscateSymbols = originatingConfig.ObfuscateSymbols
config.ControlFlow = originatingConfig.ControlFlow
}
if req.Name == "" {
name, err = codenames.GetCodename()
if err != nil {
@@ -108,6 +141,16 @@ func (rpc *Server) Migrate(ctx context.Context, req *clientpb.MigrateReq) (*sliv
config.IsSharedLib = false
config.TemplateName = "sliver"
config.ObfuscateSymbols = true
configID, err := uuid.NewV4()
if err != nil {
return nil, rpcError(err)
}
config.ID = configID.String()
releaseControlFlowBuild, err := acquireControlFlowBuild(config)
if err != nil {
return nil, err
}
defer releaseControlFlowBuild()
build, err := generate.GenerateConfig(name, config)
if err != nil {
return nil, rpcError(err)
@@ -123,10 +166,13 @@ func (rpc *Server) Migrate(ctx context.Context, req *clientpb.MigrateReq) (*sliv
if err != nil {
return nil, rpcError(err)
}
sc, _ = os.ReadFile(shellcodePath)
sc, err = os.ReadFile(shellcodePath)
if err != nil {
return nil, rpcError(err)
}
releaseControlFlowBuild()
// Save the implant config in the database so that the server recognizes it when it tries to connect
config.ID = ""
savedConfig, err := db.SaveImplantConfig(config)
savedConfig, err := db.CreateImplantConfigWithID(config)
if err != nil {
return nil, rpcError(err)
}
@@ -383,17 +429,24 @@ func getOS(session *core.Session, beacon *clientpb.Beacon) string {
return ""
}
func migrateCachedPolicyCompatible(originatingConfig, cachedConfig *clientpb.ImplantConfig) bool {
if originatingConfig == nil {
return true
}
return cachedConfig != nil && cachedConfig.ControlFlow == originatingConfig.ControlFlow
}
// Utility functions
func getSliverShellcode(name string) ([]byte, string, error) {
func getSliverShellcode(name string) ([]byte, string, *clientpb.ImplantConfig, error) {
var data []byte
build, err := db.ImplantBuildByName(name)
if err != nil {
return nil, "", err
return nil, "", nil, err
}
config, err := db.ImplantConfigByID(build.ImplantConfigID)
if err != nil {
return nil, "", err
return nil, "", nil, err
}
switch config.Format {
@@ -401,7 +454,7 @@ func getSliverShellcode(name string) ([]byte, string, error) {
case clientpb.OutputFormat_SHELLCODE:
fileData, err := generate.ImplantFileFromBuild(build)
if err != nil {
return []byte{}, "", err
return []byte{}, "", config, err
}
data = fileData
@@ -410,23 +463,23 @@ func getSliverShellcode(name string) ([]byte, string, error) {
fileData, err := generate.ImplantFileFromBuild(build)
rpcLog.Debugf("Found implant. Len: %d\n", len(fileData))
if err != nil {
return []byte{}, "", err
return []byte{}, "", config, err
}
data, err = generate.DonutShellcodeFromPE(fileData, config.GOARCH, false, "", "", "", false, false, false, config.ShellcodeConfig)
if err != nil {
rpcLog.Errorf("DonutShellcodeFromPE error: %v\n", err)
return []byte{}, "", err
return []byte{}, "", config, err
}
case clientpb.OutputFormat_SHARED_LIB:
// retrieve DLL from db
fileData, err := generate.ImplantFileFromBuild(build)
if err != nil {
return []byte{}, "", err
return []byte{}, "", config, err
}
data, err = generate.ShellcodeRDIFromBytes(fileData, "StartW", "")
if err != nil {
return []byte{}, "", err
return []byte{}, "", config, err
}
case clientpb.OutputFormat_SERVICE:
@@ -435,7 +488,7 @@ func getSliverShellcode(name string) ([]byte, string, error) {
err = fmt.Errorf("no existing shellcode found")
}
return data, config.GOARCH, err
return data, config.GOARCH, config, err
}
// ExportDirectory - stores the Export data
+28
View File
@@ -0,0 +1,28 @@
package rpc
import (
"testing"
"github.com/bishopfox/sliver/protobuf/clientpb"
)
func TestMigrateCachedPolicyCompatible(t *testing.T) {
disabled := &clientpb.ImplantConfig{ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_DISABLED}
balanced := &clientpb.ImplantConfig{ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1}
if !migrateCachedPolicyCompatible(nil, disabled) {
t.Fatal("unknown originating policy should preserve legacy cache behavior")
}
if !migrateCachedPolicyCompatible(balanced, balanced) {
t.Fatal("matching control-flow policies should permit cache reuse")
}
if migrateCachedPolicyCompatible(balanced, disabled) {
t.Fatal("balanced originating policy accepted a disabled cached build")
}
if migrateCachedPolicyCompatible(disabled, balanced) {
t.Fatal("disabled originating policy accepted a balanced cached build")
}
if migrateCachedPolicyCompatible(balanced, nil) {
t.Fatal("known originating policy accepted a cache entry without config metadata")
}
}
+52
View File
@@ -0,0 +1,52 @@
# Control-flow obfuscation integration harness
This opt-in harness builds and runs native Darwin/arm64 session and beacon
executables through the public multiplayer RPC API. It verifies:
- the server advertises `garble.control-flow/balanced-v1`;
- both builds use symbol obfuscation and `CONTROL_FLOW_BALANCED_V1`;
- the three balanced-v1 directives are attached to the expected rendered
functions;
- each build is captured with Garble's debug output and produces one parsed,
flattened `GARBLE_controlflow.go` file for each of the three selected
packages;
- the session produces a matching `SessionOpened` event and answers a
synchronous Ping with the requested nonce;
- both implants use randomized C2 selection, exercising the transformed
`randomCCDomain` before their single mTLS endpoint is selected;
- the session successfully lists a harness-owned temporary proof file through
a wildcard path, exercising the transformed `determineDirPathFilter`; and
- the beacon produces a matching `BeaconRegistered` event and completes a
nonempty asynchronous Ping task with the requested nonce, exercising the
transformed `registerSliver` during registration.
The server, operator files, generated source, compiler cache, logs, and
artifacts live under a unique temporary directory. Multiplayer and implant
mTLS listeners bind dynamic loopback-only ports. Every long-lived child is put
in its own process group, and cleanup signals only those owned groups.
## Run with a prebuilt server
Build the current branch using the repository's normal command, then pass the
result to the harness:
```sh
make
go run -tags=sliver_controlflow_e2e ./test/controlflow \
--server /absolute/path/to/sliver-server
```
## Let the harness build the server
If the embedded assets are already populated (run `make` once if needed), omit
`--server`. The harness performs an isolated `go build` of the current checkout
and does not write its server binary or Go build cache into the repository:
```sh
go run -tags=sliver_controlflow_e2e ./test/controlflow
```
Use `--keep-work-dir` to retain successful artifacts and logs. Failed runs are
always preserved and print their work-directory path. The harness refuses to
run on a host other than native Darwin/arm64 because callback verification
executes the generated binaries directly.
+217
View File
@@ -0,0 +1,217 @@
//go:build sliver_controlflow_e2e
package main
import (
"context"
"errors"
"fmt"
"net"
"time"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"google.golang.org/protobuf/proto"
)
type eventPump struct {
events chan *clientpb.Event
errors chan error
}
func startEventPump(stream rpcpb.SliverRPC_EventsClient) *eventPump {
pump := &eventPump{
events: make(chan *clientpb.Event, 256),
errors: make(chan error, 1),
}
go func() {
defer close(pump.events)
for {
event, err := stream.Recv()
if err != nil {
pump.errors <- err
return
}
select {
case pump.events <- event:
case <-stream.Context().Done():
pump.errors <- stream.Context().Err()
return
}
}
}()
return pump
}
func waitForListenerReady(ctx context.Context, pump *eventPump, jobID uint32, address string) error {
ticker := time.NewTicker(listenerPollInterval)
defer ticker.Stop()
started := false
for {
if started {
connection, err := net.DialTimeout("tcp", address, listenerDialTimeout)
if err == nil {
_ = connection.Close()
return nil
}
}
select {
case <-ctx.Done():
return ctx.Err()
case event, ok := <-pump.events:
if !ok {
return eventStreamError(pump, "event stream ended while starting implant mTLS listener")
}
if event.Job == nil || event.Job.ID != jobID {
continue
}
switch event.EventType {
case consts.JobStartedEvent:
started = true
case consts.JobStoppedEvent:
if event.Err != "" {
return fmt.Errorf("implant mTLS listener job %d stopped: %s", jobID, event.Err)
}
return fmt.Errorf("implant mTLS listener job %d stopped before accepting connections", jobID)
}
case <-ticker.C:
}
}
}
func waitForMatchingSession(
ctx context.Context,
pump *eventPump,
process *managedProcess,
listenerJobID uint32,
implantName string,
) (*clientpb.Event, error) {
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-process.done:
for {
select {
case event, ok := <-pump.events:
if !ok {
return nil, eventStreamError(pump, "event stream ended while waiting for SessionOpened")
}
matched, err := matchingSession(event, listenerJobID, implantName)
if matched || err != nil {
return event, err
}
default:
return nil, process.failure("generated session exited before SessionOpened")
}
}
case event, ok := <-pump.events:
if !ok {
return nil, eventStreamError(pump, "event stream ended while waiting for SessionOpened")
}
matched, err := matchingSession(event, listenerJobID, implantName)
if matched || err != nil {
return event, err
}
}
}
}
func matchingSession(event *clientpb.Event, listenerJobID uint32, implantName string) (bool, error) {
if err := listenerStoppedError(event, listenerJobID); err != nil {
return false, err
}
if event.EventType != consts.SessionOpenedEvent || event.Session == nil || event.Session.Name != implantName {
return false, nil
}
if event.Session.OS != targetOS || event.Session.Arch != targetArch {
return true, fmt.Errorf("session target = %s/%s, want %s/%s", event.Session.OS, event.Session.Arch, targetOS, targetArch)
}
if event.Session.ID == "" {
return true, errors.New("matching SessionOpened event has an empty ID")
}
return true, nil
}
func waitForMatchingBeacon(
ctx context.Context,
pump *eventPump,
process *managedProcess,
listenerJobID uint32,
implantName string,
) (*clientpb.Beacon, error) {
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-process.done:
for {
select {
case event, ok := <-pump.events:
if !ok {
return nil, eventStreamError(pump, "event stream ended while waiting for BeaconRegistered")
}
beacon, matched, err := matchingBeacon(event, listenerJobID, implantName)
if matched || err != nil {
return beacon, err
}
default:
return nil, process.failure("generated beacon exited before BeaconRegistered")
}
}
case event, ok := <-pump.events:
if !ok {
return nil, eventStreamError(pump, "event stream ended while waiting for BeaconRegistered")
}
beacon, matched, err := matchingBeacon(event, listenerJobID, implantName)
if matched || err != nil {
return beacon, err
}
}
}
}
func matchingBeacon(event *clientpb.Event, listenerJobID uint32, implantName string) (*clientpb.Beacon, bool, error) {
if err := listenerStoppedError(event, listenerJobID); err != nil {
return nil, false, err
}
if event.EventType != consts.BeaconRegisteredEvent {
return nil, false, nil
}
beacon := &clientpb.Beacon{}
if err := proto.Unmarshal(event.Data, beacon); err != nil {
return nil, false, fmt.Errorf("decode BeaconRegistered event: %w", err)
}
if beacon.Name != implantName {
return nil, false, nil
}
if beacon.OS != targetOS || beacon.Arch != targetArch {
return beacon, true, fmt.Errorf("beacon target = %s/%s, want %s/%s", beacon.OS, beacon.Arch, targetOS, targetArch)
}
if beacon.ID == "" {
return beacon, true, errors.New("matching BeaconRegistered event has an empty ID")
}
return beacon, true, nil
}
func listenerStoppedError(event *clientpb.Event, jobID uint32) error {
if event.EventType != consts.JobStoppedEvent || event.Job == nil || event.Job.ID != jobID {
return nil
}
if event.Err != "" {
return fmt.Errorf("implant mTLS listener job %d stopped: %s", jobID, event.Err)
}
return fmt.Errorf("implant mTLS listener job %d stopped before implant verification completed", jobID)
}
func eventStreamError(pump *eventPump, message string) error {
select {
case err := <-pump.errors:
if err != nil {
return fmt.Errorf("%s: %w", message, err)
}
default:
}
return errors.New(message)
}
+791
View File
@@ -0,0 +1,791 @@
//go:build sliver_controlflow_e2e
package main
import (
"context"
"errors"
"flag"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
clientassets "github.com/bishopfox/sliver/client/assets"
consts "github.com/bishopfox/sliver/client/constants"
clienttransport "github.com/bishopfox/sliver/client/transport"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"google.golang.org/protobuf/proto"
)
const (
operatorName = "controlflowe2e"
targetOS = "darwin"
targetArch = "arm64"
controlFlowCapability = "garble.control-flow/balanced-v1"
processLogTailBytes = 1024 * 1024
listenerPollInterval = 250 * time.Millisecond
listenerDialTimeout = 500 * time.Millisecond
cleanupGraceTimeout = 2 * time.Second
cleanupProcessTimeout = 10 * time.Second
sessionPingNonce = int32(0x534553)
beaconPingNonce = int32(0x424543)
)
type options struct {
repoPath string
serverPath string
timeout time.Duration
startupTimeout time.Duration
generateTimeout time.Duration
callbackTimeout time.Duration
keepWorkDir bool
}
type testLayout struct {
workDir string
serverRoot string
clientRoot string
homeDir string
tempDir string
logDir string
artifactDir string
garbleDebugDir string
}
type generatedArtifact struct {
name string
path string
data []byte
}
func main() {
opts := options{}
flag.StringVar(&opts.repoPath, "repo", ".", "path to the Sliver repository")
flag.StringVar(&opts.serverPath, "server", "", "prebuilt Sliver server executable; omitted builds the current checkout into the isolated work directory")
flag.DurationVar(&opts.timeout, "timeout", 90*time.Minute, "overall integration timeout")
flag.DurationVar(&opts.startupTimeout, "startup-timeout", 10*time.Minute, "Sliver daemon startup timeout")
flag.DurationVar(&opts.generateTimeout, "generate-timeout", 40*time.Minute, "timeout for each Garble implant build")
flag.DurationVar(&opts.callbackTimeout, "callback-timeout", 5*time.Minute, "timeout for each implant callback and Ping verification")
flag.BoolVar(&opts.keepWorkDir, "keep-work-dir", false, "preserve the isolated work directory after success (failed runs are always preserved)")
flag.Parse()
if err := run(opts); err != nil {
fmt.Fprintf(os.Stderr, "control-flow integration failed: %v\n", err)
os.Exit(1)
}
}
func run(opts options) (retErr error) {
if err := validateOptions(&opts); err != nil {
return err
}
layout, err := newTestLayout()
if err != nil {
return err
}
fmt.Printf("Control-flow integration work directory: %s\n", layout.workDir)
defer func() {
if opts.keepWorkDir || retErr != nil {
fmt.Printf("Preserved integration work directory: %s\n", layout.workDir)
return
}
if err := os.RemoveAll(layout.workDir); err != nil {
fmt.Fprintf(os.Stderr, "warning: remove integration work directory: %v\n", err)
}
}()
isolation := map[string]string{
"HOME": layout.homeDir,
"SLIVER_CLIENT_ROOT_DIR": layout.clientRoot,
"SLIVER_GARBLE_DEBUG_DIR": layout.garbleDebugDir,
"SLIVER_ROOT_DIR": layout.serverRoot,
"TMPDIR": layout.tempDir,
"USERPROFILE": layout.homeDir,
}
restoreEnvironment, err := setProcessEnvironment(isolation)
if err != nil {
return fmt.Errorf("set isolated process environment: %w", err)
}
defer restoreEnvironment()
isolatedEnv := environmentWith(os.Environ(), isolation)
ctx, cancel := context.WithTimeout(context.Background(), opts.timeout)
defer cancel()
serverPath := opts.serverPath
if serverPath == "" {
serverPath, err = buildServer(ctx, opts.repoPath, layout, isolatedEnv)
if err != nil {
return err
}
fmt.Printf("Built Sliver server from current checkout: %s\n", serverPath)
} else {
fmt.Printf("Using prebuilt Sliver server: %s\n", serverPath)
}
multiplayerPort, implantPort, err := twoUnusedTCPPorts()
if err != nil {
return fmt.Errorf("select loopback ports: %w", err)
}
serverLog := filepath.Join(layout.logDir, "sliver-server.log")
serverProcess, err := startProcess(
serverPath,
[]string{
"daemon",
"--lhost", "127.0.0.1",
"--lport", fmt.Sprintf("%d", multiplayerPort),
"--force",
},
opts.repoPath,
isolatedEnv,
serverLog,
)
if err != nil {
return fmt.Errorf("start Sliver daemon: %w", err)
}
defer serverProcess.stop()
defer func() {
if retErr != nil {
retErr = fmt.Errorf("%w\nSliver server log:\n%s", retErr, readLogTail(serverLog))
}
}()
fmt.Printf("Started Sliver daemon on 127.0.0.1:%d\n", multiplayerPort)
startupCtx, startupCancel := context.WithTimeout(ctx, opts.startupTimeout)
err = waitForTCP(startupCtx, fmt.Sprintf("127.0.0.1:%d", multiplayerPort), serverProcess)
startupCancel()
if err != nil {
return fmt.Errorf("wait for Sliver daemon: %w", err)
}
profilePath := filepath.Join(layout.clientRoot, operatorName+".cfg")
profileCtx, profileCancel := context.WithTimeout(ctx, 2*time.Minute)
profileOutput, profileErr := runOwnedCommand(
profileCtx,
opts.repoPath,
isolatedEnv,
serverPath,
"operator",
"--name", operatorName,
"--lhost", "127.0.0.1",
"--lport", fmt.Sprintf("%d", multiplayerPort),
"--permissions", "all",
"--save", profilePath,
)
profileCancel()
if profileErr != nil {
return fmt.Errorf("generate operator profile: %w\n%s", profileErr, profileOutput)
}
if _, err := os.Stat(profilePath); err != nil {
return fmt.Errorf("operator command did not create %q: %w\n%s", profilePath, err, profileOutput)
}
clientConfig, err := clientassets.ReadConfig(profilePath)
if err != nil {
return fmt.Errorf("read operator profile: %w", err)
}
if clientConfig.Operator != operatorName || clientConfig.LHost != "127.0.0.1" || clientConfig.LPort != multiplayerPort {
return fmt.Errorf(
"operator profile mismatch: got operator=%q endpoint=%s:%d",
clientConfig.Operator,
clientConfig.LHost,
clientConfig.LPort,
)
}
clienttransport.SetMultiplayerConnectMode(clienttransport.MultiplayerConnectDirect)
rpc, conn, err := clienttransport.MTLSConnect(clientConfig)
if err != nil {
return fmt.Errorf("connect to multiplayer gRPC: %w", err)
}
defer clienttransport.CloseGRPCConnection(conn)
version, err := rpc.GetVersion(ctx, &commonpb.Empty{})
if err != nil {
return fmt.Errorf("query Sliver server version: %w", err)
}
fmt.Printf("Connected to Sliver server %s/%s over multiplayer mTLS\n", version.OS, version.Arch)
if err := verifyCompilerCapability(ctx, rpc); err != nil {
return err
}
eventsCtx, eventsCancel := context.WithCancel(ctx)
defer eventsCancel()
eventStream, err := rpc.Events(eventsCtx, &commonpb.Empty{})
if err != nil {
return fmt.Errorf("subscribe to Sliver events: %w", err)
}
pump := startEventPump(eventStream)
listener, err := rpc.StartMTLSListener(ctx, &clientpb.MTLSListenerReq{
Host: "127.0.0.1",
Port: uint32(implantPort),
})
if err != nil {
return fmt.Errorf("start implant mTLS listener: %w", err)
}
defer func() {
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cleanupCancel()
_, _ = rpc.KillJob(cleanupCtx, &clientpb.KillJobReq{ID: listener.JobID})
}()
listenerCtx, listenerCancel := context.WithTimeout(ctx, 30*time.Second)
err = waitForListenerReady(listenerCtx, pump, listener.JobID, fmt.Sprintf("127.0.0.1:%d", implantPort))
listenerCancel()
if err != nil {
return fmt.Errorf("wait for implant mTLS listener: %w", err)
}
fmt.Printf("Started implant mTLS listener on 127.0.0.1:%d (job %d)\n", implantPort, listener.JobID)
sessionArtifact, err := generateArtifact(ctx, opts.generateTimeout, rpc, layout, implantPort, "cfoe2esession", false)
if err != nil {
return fmt.Errorf("generate control-flow session: %w", err)
}
fmt.Printf("Generated control-flow session %s (%d bytes)\n", sessionArtifact.path, len(sessionArtifact.data))
sessionLog := filepath.Join(layout.logDir, "session.log")
sessionProcess, err := startProcess(sessionArtifact.path, nil, layout.artifactDir, isolatedEnv, sessionLog)
if err != nil {
return fmt.Errorf("start generated session: %w", err)
}
defer sessionProcess.stop()
sessionCtx, sessionCancel := context.WithTimeout(ctx, opts.callbackTimeout)
sessionEvent, err := waitForMatchingSession(
sessionCtx,
pump,
sessionProcess,
listener.JobID,
sessionArtifact.name,
)
if err == nil {
err = verifySessionPing(sessionCtx, rpc, sessionEvent.Session.ID)
}
if err == nil {
err = verifySessionFilteredLs(sessionCtx, rpc, sessionEvent.Session.ID, layout.tempDir)
}
sessionCancel()
if err != nil {
return fmt.Errorf("verify generated session callback: %w\nsession log:\n%s", err, readLogTail(sessionLog))
}
fmt.Printf("Verified SessionOpened %s and synchronous Ping nonce %d\n", sessionEvent.Session.ID, sessionPingNonce)
sessionProcess.stop()
beaconArtifact, err := generateArtifact(ctx, opts.generateTimeout, rpc, layout, implantPort, "cfoe2ebeacon", true)
if err != nil {
return fmt.Errorf("generate control-flow beacon: %w", err)
}
fmt.Printf("Generated control-flow beacon %s (%d bytes)\n", beaconArtifact.path, len(beaconArtifact.data))
beaconLog := filepath.Join(layout.logDir, "beacon.log")
beaconProcess, err := startProcess(beaconArtifact.path, nil, layout.artifactDir, isolatedEnv, beaconLog)
if err != nil {
return fmt.Errorf("start generated beacon: %w", err)
}
defer beaconProcess.stop()
beaconCtx, beaconCancel := context.WithTimeout(ctx, opts.callbackTimeout)
beacon, err := waitForMatchingBeacon(
beaconCtx,
pump,
beaconProcess,
listener.JobID,
beaconArtifact.name,
)
if err == nil {
err = verifyBeaconPing(beaconCtx, rpc, beaconProcess, beacon.ID)
}
beaconCancel()
if err != nil {
return fmt.Errorf("verify generated beacon callback: %w\nbeacon log:\n%s", err, readLogTail(beaconLog))
}
fmt.Printf("Verified BeaconRegistered %s and completed asynchronous Ping nonce %d\n", beacon.ID, beaconPingNonce)
beaconProcess.stop()
fmt.Println("PASS: Darwin/arm64 control-flow session and beacon both connected and answered Ping")
return nil
}
func validateOptions(opts *options) error {
if runtime.GOOS != targetOS || runtime.GOARCH != targetArch {
return fmt.Errorf("control-flow callback harness must run natively on %s/%s, current host is %s/%s", targetOS, targetArch, runtime.GOOS, runtime.GOARCH)
}
if opts.timeout <= 0 || opts.startupTimeout <= 0 || opts.generateTimeout <= 0 || opts.callbackTimeout <= 0 {
return errors.New("timeouts must be positive")
}
repoPath, err := filepath.Abs(opts.repoPath)
if err != nil {
return fmt.Errorf("resolve repository path: %w", err)
}
if _, err := os.Stat(filepath.Join(repoPath, "go.mod")); err != nil {
return fmt.Errorf("repository does not contain go.mod: %w", err)
}
opts.repoPath = repoPath
if opts.serverPath == "" {
return validateEmbeddedAssetInputs(repoPath)
}
serverPath, err := filepath.Abs(opts.serverPath)
if err != nil {
return fmt.Errorf("resolve server executable: %w", err)
}
stat, err := os.Stat(serverPath)
if err != nil {
return fmt.Errorf("stat server executable: %w", err)
}
if stat.IsDir() {
return fmt.Errorf("server executable %q is a directory", serverPath)
}
if stat.Mode()&0o111 == 0 {
return fmt.Errorf("server executable %q is not executable", serverPath)
}
opts.serverPath = serverPath
return nil
}
func validateEmbeddedAssetInputs(repoPath string) error {
required := []string{
filepath.Join("server", "assets", "fs", "src.zip"),
filepath.Join("server", "assets", "fs", targetOS, targetArch, "go.zip"),
filepath.Join("server", "assets", "fs", targetOS, targetArch, "garble"),
}
for _, relativePath := range required {
if _, err := os.Stat(filepath.Join(repoPath, relativePath)); err != nil {
return fmt.Errorf("server build asset %q is unavailable: %w; run make once to populate embedded assets or pass --server", relativePath, err)
}
}
return nil
}
func newTestLayout() (*testLayout, error) {
workDir, err := os.MkdirTemp("", "sliver-controlflow-e2e-")
if err != nil {
return nil, fmt.Errorf("create integration work directory: %w", err)
}
layout := &testLayout{
workDir: workDir,
serverRoot: filepath.Join(workDir, "server-root"),
clientRoot: filepath.Join(workDir, "client-root"),
homeDir: filepath.Join(workDir, "home"),
tempDir: filepath.Join(workDir, "tmp"),
logDir: filepath.Join(workDir, "logs"),
artifactDir: filepath.Join(workDir, "artifacts"),
garbleDebugDir: filepath.Join(workDir, "garble-debug"),
}
for _, dir := range []string{
layout.serverRoot,
layout.clientRoot,
layout.homeDir,
layout.tempDir,
layout.logDir,
layout.artifactDir,
} {
if err := os.MkdirAll(dir, 0o700); err != nil {
_ = os.RemoveAll(workDir)
return nil, fmt.Errorf("create isolated directory %q: %w", dir, err)
}
}
return layout, nil
}
func buildServer(ctx context.Context, repoPath string, layout *testLayout, isolatedEnv []string) (string, error) {
binDir := filepath.Join(layout.workDir, "bin")
goCache := filepath.Join(layout.workDir, "server-build-cache")
goPath := filepath.Join(layout.workDir, "server-build-gopath")
goModCache := filepath.Join(layout.workDir, "server-build-modcache")
for _, dir := range []string{binDir, goCache, goPath, goModCache} {
if err := os.MkdirAll(dir, 0o700); err != nil {
return "", fmt.Errorf("create server build directory: %w", err)
}
}
serverPath := filepath.Join(binDir, "sliver-server")
buildEnv := environmentWith(isolatedEnv, map[string]string{
"CGO_ENABLED": "0",
"GOARCH": targetArch,
"GOCACHE": goCache,
"GOENV": "off",
"GOFLAGS": "",
"GOMODCACHE": goModCache,
"GOOS": targetOS,
"GOPATH": goPath,
"GOWORK": "off",
})
output, err := runOwnedCommand(
ctx,
repoPath,
buildEnv,
"go",
"build",
"-buildvcs=false",
"-mod=vendor",
"-trimpath",
"-tags=go_sqlite,server",
"-o", serverPath,
"./server",
)
if err != nil {
return "", fmt.Errorf("build Sliver server: %w\n%s", err, output)
}
return serverPath, nil
}
func verifyCompilerCapability(ctx context.Context, rpc rpcpb.SliverRPCClient) error {
compiler, err := rpc.GetCompiler(ctx, &commonpb.Empty{})
if err != nil {
return fmt.Errorf("query server compiler: %w", err)
}
hasCapability := false
for _, capability := range compiler.Capabilities {
if capability == controlFlowCapability {
hasCapability = true
break
}
}
if !hasCapability {
return fmt.Errorf("server does not advertise required capability %q", controlFlowCapability)
}
hasTarget := false
for _, target := range compiler.Targets {
if target.GOOS == targetOS && target.GOARCH == targetArch && target.Format == clientpb.OutputFormat_EXECUTABLE {
hasTarget = true
break
}
}
if !hasTarget {
return fmt.Errorf("server compiler does not advertise %s/%s executable support", targetOS, targetArch)
}
fmt.Printf("Server advertises %s and %s/%s executable support\n", controlFlowCapability, targetOS, targetArch)
return nil
}
func generateArtifact(
ctx context.Context,
timeout time.Duration,
rpc rpcpb.SliverRPCClient,
layout *testLayout,
implantPort int,
name string,
isBeacon bool,
) (*generatedArtifact, error) {
config := &clientpb.ImplantConfig{
GOOS: targetOS,
GOARCH: targetArch,
TemplateName: "sliver",
Debug: false,
ObfuscateSymbols: true,
ControlFlow: clientpb.ControlFlowPolicy_CONTROL_FLOW_BALANCED_V1,
IsBeacon: isBeacon,
BeaconInterval: int64(2 * time.Second),
BeaconJitter: 0,
Format: clientpb.OutputFormat_EXECUTABLE,
C2: []*clientpb.ImplantC2{{URL: fmt.Sprintf("mtls://127.0.0.1:%d", implantPort)}},
IncludeMTLS: true,
HTTPC2ConfigName: consts.DefaultC2Profile,
ConnectionStrategy: "rd",
ReconnectInterval: int64(time.Second),
PollTimeout: int64(time.Second),
MaxConnectionErrors: 10,
NetGoEnabled: true,
}
buildCtx, buildCancel := context.WithTimeout(ctx, timeout)
generated, err := rpc.Generate(buildCtx, &clientpb.GenerateReq{Name: name, Config: config})
buildCancel()
if err != nil {
return nil, err
}
if generated.File == nil || generated.File.Name == "" || len(generated.File.Data) == 0 {
return nil, errors.New("Sliver returned an empty executable")
}
if generated.ImplantName != name {
return nil, fmt.Errorf("generated implant name = %q, want %q", generated.ImplantName, name)
}
if err := verifyRenderedControlFlow(layout.serverRoot, generated.ImplantName); err != nil {
return nil, err
}
transformedFiles, err := verifyGarbleControlFlowDebugDir(layout.garbleDebugDir)
if err != nil {
return nil, fmt.Errorf("inspect direct Garble transformation output for %s: %w", name, err)
}
fmt.Printf("Verified three exact rendered directives and %d transformed %s files for %s\n", transformedFiles, controlFlowGeneratedFile, name)
artifactPath := filepath.Join(layout.artifactDir, filepath.Base(generated.File.Name))
if err := os.WriteFile(artifactPath, generated.File.Data, 0o700); err != nil {
return nil, fmt.Errorf("save generated executable: %w", err)
}
return &generatedArtifact{
name: generated.ImplantName,
path: artifactPath,
data: generated.File.Data,
}, nil
}
func verifySessionPing(ctx context.Context, rpc rpcpb.SliverRPCClient, sessionID string) error {
if sessionID == "" {
return errors.New("SessionOpened event has an empty session ID")
}
response, err := rpc.Ping(ctx, &sliverpb.Ping{
Nonce: sessionPingNonce,
Request: &commonpb.Request{
SessionID: sessionID,
Timeout: int64(30 * time.Second),
},
})
if err != nil {
return fmt.Errorf("synchronous session Ping: %w", err)
}
if response.Nonce != sessionPingNonce {
return fmt.Errorf("session Ping nonce = %d, want %d", response.Nonce, sessionPingNonce)
}
return nil
}
func verifySessionFilteredLs(ctx context.Context, rpc rpcpb.SliverRPCClient, sessionID string, tempDir string) error {
const proofFileName = "controlflow-filter-proof.txt"
proofPath := filepath.Join(tempDir, proofFileName)
if err := os.WriteFile(proofPath, []byte("control-flow directory filter proof\n"), 0o600); err != nil {
return fmt.Errorf("create filtered Ls proof file: %w", err)
}
wildcardPath := filepath.Join(tempDir, "controlflow-filter-*")
listing, err := rpc.Ls(ctx, &sliverpb.LsReq{
Path: wildcardPath,
Request: &commonpb.Request{
SessionID: sessionID,
Timeout: int64(30 * time.Second),
},
})
if err != nil {
return fmt.Errorf("filtered session Ls(%q): %w", wildcardPath, err)
}
if listing.Response != nil && listing.Response.Err != "" {
return fmt.Errorf("filtered session Ls(%q) returned implant error: %s", wildcardPath, listing.Response.Err)
}
if !listing.Exists {
return fmt.Errorf("filtered session Ls(%q) reported that its directory does not exist", wildcardPath)
}
foundProof := false
for _, file := range listing.Files {
if file.Name == proofFileName {
foundProof = true
break
}
}
if !foundProof {
return fmt.Errorf("filtered session Ls(%q) did not return proof file %q", wildcardPath, proofFileName)
}
fmt.Printf(
"Verified transformed determineDirPathFilter via Ls(%q): matched %s in %s (%d result(s))\n",
wildcardPath,
proofFileName,
listing.Path,
len(listing.Files),
)
return nil
}
func verifyBeaconPing(ctx context.Context, rpc rpcpb.SliverRPCClient, process *managedProcess, beaconID string) error {
if beaconID == "" {
return errors.New("BeaconRegistered event has an empty beacon ID")
}
response, err := rpc.Ping(ctx, &sliverpb.Ping{
Nonce: beaconPingNonce,
Request: &commonpb.Request{
Async: true,
BeaconID: beaconID,
Timeout: int64(30 * time.Second),
},
})
if err != nil {
return fmt.Errorf("queue asynchronous beacon Ping: %w", err)
}
if response.Response == nil || response.Response.TaskID == "" {
return fmt.Errorf("asynchronous beacon Ping returned no task ID: %#v", response.Response)
}
task, err := waitForCompletedBeaconTask(ctx, rpc, process, response.Response.TaskID)
if err != nil {
return err
}
if len(task.Response) == 0 {
return fmt.Errorf("completed beacon Ping task %s has an empty response", task.ID)
}
pong := &sliverpb.Ping{}
if err := proto.Unmarshal(task.Response, pong); err != nil {
return fmt.Errorf("unmarshal completed beacon Ping response: %w", err)
}
if pong.Nonce != beaconPingNonce {
return fmt.Errorf("beacon Ping nonce = %d, want %d", pong.Nonce, beaconPingNonce)
}
return nil
}
func waitForCompletedBeaconTask(
ctx context.Context,
rpc rpcpb.SliverRPCClient,
process *managedProcess,
taskID string,
) (*clientpb.BeaconTask, error) {
ticker := time.NewTicker(250 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil, fmt.Errorf("wait for beacon Ping task %s: %w", taskID, ctx.Err())
case <-process.done:
return nil, process.failure("beacon exited before its Ping task completed")
case <-ticker.C:
pollCtx, pollCancel := context.WithTimeout(ctx, 3*time.Second)
task, err := rpc.GetBeaconTaskContent(pollCtx, &clientpb.BeaconTask{ID: taskID})
pollCancel()
if err != nil {
continue
}
switch task.State {
case "completed":
return task, nil
case "failed", "canceled":
return nil, fmt.Errorf("beacon Ping task %s entered terminal state %q", taskID, task.State)
}
}
}
}
func twoUnusedTCPPorts() (int, int, error) {
first, err := unusedTCPPort()
if err != nil {
return 0, 0, err
}
for attempts := 0; attempts < 8; attempts++ {
second, err := unusedTCPPort()
if err != nil {
return 0, 0, err
}
if first != second {
return first, second, nil
}
}
return 0, 0, errors.New("could not select two distinct TCP ports")
}
func unusedTCPPort() (int, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return 0, err
}
defer listener.Close()
return listener.Addr().(*net.TCPAddr).Port, nil
}
func waitForTCP(ctx context.Context, address string, process *managedProcess) error {
ticker := time.NewTicker(listenerPollInterval)
defer ticker.Stop()
for {
connection, err := net.DialTimeout("tcp", address, listenerDialTimeout)
if err == nil {
_ = connection.Close()
return nil
}
select {
case <-ctx.Done():
return ctx.Err()
case <-process.done:
return process.failure("Sliver daemon exited before accepting multiplayer connections")
case <-ticker.C:
}
}
}
func setProcessEnvironment(values map[string]string) (func(), error) {
type previousValue struct {
value string
set bool
}
previous := make(map[string]previousValue, len(values))
for name, value := range values {
oldValue, set := os.LookupEnv(name)
previous[name] = previousValue{value: oldValue, set: set}
if err := os.Setenv(name, value); err != nil {
for restoreName, restoreValue := range previous {
if restoreValue.set {
_ = os.Setenv(restoreName, restoreValue.value)
} else {
_ = os.Unsetenv(restoreName)
}
}
return nil, err
}
}
return func() {
for name, value := range previous {
if value.set {
_ = os.Setenv(name, value.value)
} else {
_ = os.Unsetenv(name)
}
}
}, nil
}
func environmentWith(environment []string, values map[string]string) []string {
updated := append([]string{}, environment...)
for name, value := range values {
updated = environmentValue(updated, name, value)
}
return updated
}
func environmentValue(environment []string, name string, value string) []string {
prefix := name + "="
updated := make([]string, 0, len(environment)+1)
for _, entry := range environment {
if !strings.HasPrefix(entry, prefix) {
updated = append(updated, entry)
}
}
return append(updated, prefix+value)
}
func runOwnedCommand(ctx context.Context, dir string, env []string, path string, args ...string) (string, error) {
cmd := exec.Command(path, args...)
cmd.Dir = dir
cmd.Env = env
var output strings.Builder
cmd.Stdout = &output
cmd.Stderr = &output
prepareCommand(cmd)
if err := cmd.Start(); err != nil {
return output.String(), err
}
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
select {
case err := <-done:
return strings.TrimSpace(output.String()), err
case <-ctx.Done():
terminateProcessTree(cmd)
select {
case <-done:
case <-time.After(cleanupGraceTimeout):
killProcessTree(cmd)
select {
case <-done:
case <-time.After(cleanupProcessTimeout):
}
}
return strings.TrimSpace(output.String()), ctx.Err()
}
}
+86
View File
@@ -0,0 +1,86 @@
//go:build sliver_controlflow_e2e
package main
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
"time"
)
type managedProcess struct {
cmd *exec.Cmd
done chan struct{}
err error
logPath string
}
func startProcess(path string, args []string, dir string, env []string, logPath string) (*managedProcess, error) {
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
if err != nil {
return nil, err
}
cmd := exec.Command(path, args...)
cmd.Dir = dir
cmd.Env = env
cmd.Stdout = logFile
cmd.Stderr = logFile
prepareCommand(cmd)
if err := cmd.Start(); err != nil {
_ = logFile.Close()
return nil, err
}
process := &managedProcess{
cmd: cmd,
done: make(chan struct{}),
logPath: logPath,
}
go func() {
process.err = cmd.Wait()
_ = logFile.Close()
close(process.done)
}()
return process, nil
}
func (process *managedProcess) stop() {
select {
case <-process.done:
return
default:
}
terminateProcessTree(process.cmd)
select {
case <-process.done:
return
case <-time.After(cleanupGraceTimeout):
}
killProcessTree(process.cmd)
select {
case <-process.done:
case <-time.After(cleanupProcessTimeout):
fmt.Fprintf(os.Stderr, "timed out cleaning up owned process group for %s (pid %d)\n", process.cmd.Path, process.cmd.Process.Pid)
}
}
func (process *managedProcess) failure(message string) error {
processErr := process.err
if processErr == nil {
processErr = errors.New("process exited")
}
return fmt.Errorf("%s: %w\n%s", message, processErr, readLogTail(process.logPath))
}
func readLogTail(path string) string {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Sprintf("(could not read %s: %v)", path, err)
}
if len(data) > processLogTailBytes {
data = data[len(data)-processLogTailBytes:]
}
return strings.TrimSpace(string(data))
}
+20
View File
@@ -0,0 +1,20 @@
//go:build sliver_controlflow_e2e && (darwin || linux)
package main
import (
"os/exec"
"syscall"
)
func prepareCommand(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
func terminateProcessTree(cmd *exec.Cmd) {
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGTERM)
}
func killProcessTree(cmd *exec.Cmd) {
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
+18
View File
@@ -0,0 +1,18 @@
//go:build sliver_controlflow_e2e && windows
package main
import (
"os/exec"
"strconv"
)
func prepareCommand(_ *exec.Cmd) {}
func terminateProcessTree(cmd *exec.Cmd) {
_ = exec.Command("taskkill.exe", "/PID", strconv.Itoa(cmd.Process.Pid), "/T").Run()
}
func killProcessTree(cmd *exec.Cmd) {
_ = exec.Command("taskkill.exe", "/PID", strconv.Itoa(cmd.Process.Pid), "/T", "/F").Run()
}
+145
View File
@@ -0,0 +1,145 @@
//go:build sliver_controlflow_e2e
package main
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/fs"
"os"
"path/filepath"
"strings"
)
const (
balancedV1Directive = "//garble:controlflow block_splits=2 junk_jumps=2 flatten_passes=1 flatten_hardening=xor trash_blocks=0"
controlFlowGeneratedFile = "GARBLE_controlflow.go"
)
var expectedControlFlowFunctions = map[string]string{
"determineDirPathFilter": filepath.Join("handlers", "handlers.go"),
"registerSliver": filepath.Join("runner", "runner.go"),
"randomCCDomain": filepath.Join("transports", "transports.go"),
}
func verifyRenderedControlFlow(serverRoot string, implantName string) error {
sourceRoot := filepath.Join(serverRoot, "slivers", targetOS, targetArch, filepath.Base(implantName), "src")
found := map[string]string{}
directiveCount := 0
err := filepath.WalkDir(sourceRoot, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() && path != sourceRoot {
switch entry.Name() {
case "vendor", "protobuf":
return filepath.SkipDir
}
}
if entry.IsDir() || filepath.Ext(entry.Name()) != ".go" {
return nil
}
relativePath, err := filepath.Rel(sourceRoot, path)
if err != nil {
return err
}
file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ParseComments)
if err != nil {
return fmt.Errorf("parse rendered source %q: %w", path, err)
}
for _, declaration := range file.Decls {
function, ok := declaration.(*ast.FuncDecl)
if !ok || function.Doc == nil {
continue
}
for _, comment := range function.Doc.List {
if comment.Text != balancedV1Directive {
continue
}
directiveCount++
expectedSuffix, expected := expectedControlFlowFunctions[function.Name.Name]
if !expected {
return fmt.Errorf("unexpected exact control-flow directive on %s in %s", function.Name.Name, relativePath)
}
if !pathHasSuffix(relativePath, expectedSuffix) {
return fmt.Errorf("control-flow function %s rendered in %s, want suffix %s", function.Name.Name, relativePath, expectedSuffix)
}
if previousPath, duplicate := found[function.Name.Name]; duplicate {
return fmt.Errorf("control-flow directive for %s appears in both %s and %s", function.Name.Name, previousPath, relativePath)
}
found[function.Name.Name] = relativePath
}
}
return nil
})
if err != nil {
return fmt.Errorf("inspect rendered source for %s: %w", implantName, err)
}
if directiveCount != len(expectedControlFlowFunctions) {
return fmt.Errorf("rendered source has %d exact control-flow directives, want %d", directiveCount, len(expectedControlFlowFunctions))
}
for function, suffix := range expectedControlFlowFunctions {
if found[function] == "" {
return fmt.Errorf("rendered source is missing control-flow directive for %s in *%s", function, suffix)
}
}
return nil
}
func verifyGarbleControlFlowDebugDir(debugDir string) (int, error) {
garbledRoot := filepath.Join(debugDir, "garbled")
foundPackages := map[string]string{}
transformedFiles := 0
err := filepath.WalkDir(garbledRoot, func(path string, entry fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if entry.IsDir() || entry.Name() != controlFlowGeneratedFile {
return nil
}
transformedFiles++
packageName := filepath.Base(filepath.Dir(path))
if _, expected := map[string]struct{}{"handlers": {}, "runner": {}, "transports": {}}[packageName]; !expected {
return fmt.Errorf("unexpected transformed control-flow package %q at %s", packageName, path)
}
if previousPath, duplicate := foundPackages[packageName]; duplicate {
return fmt.Errorf("multiple %s files for package %s: %s and %s", controlFlowGeneratedFile, packageName, previousPath, path)
}
source, err := os.ReadFile(path)
if err != nil {
return err
}
if !bytes.Contains(source, []byte("goto ")) {
return fmt.Errorf("transformed source %s contains no flattened control-flow jumps", path)
}
if _, err := parser.ParseFile(token.NewFileSet(), path, source, parser.SkipObjectResolution); err != nil {
return fmt.Errorf("parse transformed source %s: %w", path, err)
}
foundPackages[packageName] = path
return nil
})
if err != nil {
return 0, err
}
if transformedFiles != len(expectedControlFlowFunctions) {
return 0, fmt.Errorf("Garble debug output has %d %s files, want %d", transformedFiles, controlFlowGeneratedFile, len(expectedControlFlowFunctions))
}
for _, packageName := range []string{"handlers", "runner", "transports"} {
if foundPackages[packageName] == "" {
return 0, fmt.Errorf("Garble debug output is missing transformed package %s", packageName)
}
}
return transformedFiles, nil
}
func pathHasSuffix(path string, suffix string) bool {
path = filepath.ToSlash(filepath.Clean(path))
suffix = filepath.ToSlash(filepath.Clean(suffix))
if path == suffix {
return true
}
return strings.HasSuffix(path, "/"+suffix)
}
+111 -41
View File
@@ -1,8 +1,13 @@
package assets
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
type garblePlatform struct {
@@ -10,51 +15,13 @@ type garblePlatform struct {
arch string
filename string
url string
sha256 string
}
func (r *runner) buildGarbleAssets() error {
r.logger.Section("Garble")
platforms := []garblePlatform{
{
os: "linux",
arch: "amd64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_linux-amd64", garbleVersion),
},
{
os: "linux",
arch: "arm64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_linux-arm64", garbleVersion),
},
{
os: "windows",
arch: "amd64",
filename: "garble.exe",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_windows-amd64.exe", garbleVersion),
},
{
os: "windows",
arch: "arm64",
filename: "garble.exe",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_windows-arm64.exe", garbleVersion),
},
{
os: "darwin",
arch: "amd64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_darwin-amd64", garbleVersion),
},
{
os: "darwin",
arch: "arm64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_darwin-arm64", garbleVersion),
},
}
for _, platform := range platforms {
for _, platform := range garblePlatforms() {
r.garbleIndex++
r.logger.Logf("Fetch garble %s/%s (%d/%d)", platform.os, platform.arch, r.garbleIndex, garbleTotal)
outputDir := filepath.Join(r.outputDir, platform.os, platform.arch)
@@ -62,10 +29,113 @@ func (r *runner) buildGarbleAssets() error {
return err
}
destPath := filepath.Join(outputDir, platform.filename)
if err := r.downloadFile(platform.url, destPath); err != nil {
downloadPath, err := r.downloadToTemp(platform.url, r.workDir)
if err != nil {
return err
}
if err := verifyGarbleSHA256(downloadPath, platform.sha256); err != nil {
_ = os.Remove(downloadPath)
return fmt.Errorf("verify garble %s/%s: %w", platform.os, platform.arch, err)
}
if err := moveFile(downloadPath, destPath); err != nil {
_ = os.Remove(downloadPath)
return fmt.Errorf("install garble %s/%s: %w", platform.os, platform.arch, err)
}
}
return nil
}
func garblePlatforms() []garblePlatform {
return []garblePlatform{
{
os: "linux",
arch: "amd64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_linux-amd64", garbleVersion),
sha256: "ee10c850ec4b4078155e7fb6a28c6dc50ac1c991bbb8029829c40d41c8c57b64",
},
{
os: "linux",
arch: "arm64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_linux-arm64", garbleVersion),
sha256: "5503dc9fcb6db3b797f0c10e20078de50df16b1cd8bf00668701589a601ae821",
},
{
os: "windows",
arch: "amd64",
filename: "garble.exe",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_windows-amd64.exe", garbleVersion),
sha256: "c7b979d9d61ecf5aa7de811a8e97a90364eaf03763778a9e1ee3c5a9473366be",
},
{
os: "windows",
arch: "arm64",
filename: "garble.exe",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_windows-arm64.exe", garbleVersion),
sha256: "f1de3caed55ff9422b940108a85ea61a1e868ec3bca01ec51fc5a4163ce2b44d",
},
{
os: "darwin",
arch: "amd64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_darwin-amd64", garbleVersion),
sha256: "62c46c30c4983b176db5b46a22256d61fce3b9da647cecb7e7d479bfe47f17b8",
},
{
os: "darwin",
arch: "arm64",
filename: "garble",
url: fmt.Sprintf("https://github.com/moloch--/garble/releases/download/v%s/garble_darwin-arm64", garbleVersion),
sha256: "63626c1833f94732fb43a2d00dc2236450ecedfe0e51497cfc5a310818c32f05",
},
}
}
// ExpectedGarbleSHA256 returns the pinned digest for the Garble artifact used
// by a host platform. The server uses the same manifest at runtime so it does
// not advertise control-flow support for a stale or partially extracted tool.
func ExpectedGarbleSHA256(goos, goarch string) (string, bool) {
for _, platform := range garblePlatforms() {
if platform.os == goos && platform.arch == goarch {
return platform.sha256, true
}
}
return "", false
}
// VerifyGarbleBinary verifies an extracted Garble binary against the artifact
// manifest used when server assets are assembled.
func VerifyGarbleBinary(path, goos, goarch string) error {
expected, ok := ExpectedGarbleSHA256(goos, goarch)
if !ok {
return fmt.Errorf("unsupported garble host platform %s/%s", goos, goarch)
}
if err := verifyGarbleSHA256(path, expected); err != nil {
return fmt.Errorf("verify garble binary for %s/%s: %w", goos, goarch, err)
}
return nil
}
func verifyGarbleSHA256(path, expected string) (resultErr error) {
file, err := os.Open(path)
if err != nil {
return fmt.Errorf("open artifact: %w", err)
}
defer func() {
if err := file.Close(); err != nil && resultErr == nil {
resultErr = fmt.Errorf("close artifact: %w", err)
}
}()
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return fmt.Errorf("hash artifact: %w", err)
}
actual := hex.EncodeToString(hash.Sum(nil))
if !strings.EqualFold(actual, strings.TrimSpace(expected)) {
return fmt.Errorf("sha256 mismatch: got %s, want %s", actual, expected)
}
return nil
}
+65
View File
@@ -0,0 +1,65 @@
package assets
import (
"crypto/sha256"
"encoding/hex"
"os"
"path/filepath"
"strings"
"testing"
)
func TestVerifyGarbleSHA256(t *testing.T) {
artifact := filepath.Join(t.TempDir(), "garble")
data := []byte("garble-control-flow")
if err := os.WriteFile(artifact, data, 0o600); err != nil {
t.Fatalf("write test artifact: %v", err)
}
digest := sha256.Sum256(data)
expected := hex.EncodeToString(digest[:])
if err := verifyGarbleSHA256(artifact, strings.ToUpper(expected)); err != nil {
t.Fatalf("verifyGarbleSHA256() error = %v", err)
}
if err := verifyGarbleSHA256(artifact, strings.Repeat("0", sha256.Size*2)); err == nil {
t.Fatal("verifyGarbleSHA256() accepted an incorrect digest")
}
}
func TestGarblePlatformManifest(t *testing.T) {
platforms := garblePlatforms()
if len(platforms) != garbleTotal {
t.Fatalf("garble platform count = %d, want %d", len(platforms), garbleTotal)
}
seen := map[string]struct{}{}
for _, platform := range platforms {
key := platform.os + "/" + platform.arch
if _, duplicate := seen[key]; duplicate {
t.Fatalf("duplicate Garble platform %s", key)
}
seen[key] = struct{}{}
digest, err := hex.DecodeString(platform.sha256)
if err != nil || len(digest) != sha256.Size {
t.Fatalf("Garble platform %s has invalid sha256 %q", key, platform.sha256)
}
if !strings.Contains(platform.url, "/v"+garbleVersion+"/") {
t.Fatalf("Garble platform %s URL %q does not use pinned version %s", key, platform.url, garbleVersion)
}
if platform.os == "windows" && platform.filename != "garble.exe" {
t.Fatalf("Garble platform %s filename = %q, want garble.exe", key, platform.filename)
}
if platform.os != "windows" && platform.filename != "garble" {
t.Fatalf("Garble platform %s filename = %q, want garble", key, platform.filename)
}
expected, ok := ExpectedGarbleSHA256(platform.os, platform.arch)
if !ok || expected != platform.sha256 {
t.Fatalf("ExpectedGarbleSHA256(%q, %q) = %q, %t; want %q, true", platform.os, platform.arch, expected, ok, platform.sha256)
}
}
if _, ok := ExpectedGarbleSHA256("plan9", "mips"); ok {
t.Fatal("ExpectedGarbleSHA256() accepted an unsupported platform")
}
}