Compare commits

..
35 changed files with 2744 additions and 1238 deletions

No files matched your search

+1 -1
View File
@@ -115,7 +115,7 @@ func Setup(force bool, echo bool) {
if force || localVer == "" || localVer != ver.GitCommit {
if echo {
fmt.Printf(`
Sliver Copyright (C) 2025 Bishop Fox
Sliver Copyright (C) 2026 Bishop Fox
This program comes with ABSOLUTELY NO WARRANTY; for details type 'licenses'.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'licenses' for details.`)
+74 -35
View File
@@ -47,41 +47,44 @@ var (
consts.GenerateStr: generateHelp,
consts.StageListenerStr: stageListenerHelp,
consts.MsfStr: msfHelp,
consts.MsfInjectStr: msfInjectHelp,
consts.PsStr: psHelp,
consts.PingStr: pingHelp,
consts.KillStr: killHelp,
consts.LsStr: lsHelp,
consts.CdStr: cdHelp,
consts.PwdStr: pwdHelp,
consts.CatStr: catHelp,
consts.EditStr: editHelp,
consts.HexEditStr: hexEditHelp,
consts.DownloadStr: downloadHelp,
consts.GrepStr: grepHelp,
consts.HeadStr: headHelp,
consts.TailStr: tailHelp,
consts.UploadStr: uploadHelp,
consts.MkdirStr: mkdirHelp,
consts.RmStr: rmHelp,
consts.ChtimesStr: chtimesHelp,
consts.ProcdumpStr: procdumpHelp,
consts.ElevateStr: elevateHelp,
consts.RunAsStr: runAsHelp,
consts.ImpersonateStr: impersonateHelp,
consts.RevToSelfStr: revToSelfHelp,
consts.ExecuteStr: executeHelp,
consts.ExecuteAssemblyStr: executeAssemblyHelp,
consts.ExecuteShellcodeStr: executeShellcodeHelp,
consts.MigrateStr: migrateHelp,
consts.SideloadStr: sideloadHelp,
consts.TerminateStr: terminateHelp,
consts.AliasesStr: loadAliasHelp,
consts.PsExecStr: psExecHelp,
consts.BackdoorStr: backdoorHelp,
consts.SpawnDllStr: spawnDllHelp,
consts.MountStr: mountHelp,
consts.MsfStr: msfHelp,
consts.MsfInjectStr: msfInjectHelp,
consts.PsStr: psHelp,
consts.PingStr: pingHelp,
consts.KillStr: killHelp,
consts.LsStr: lsHelp,
consts.CdStr: cdHelp,
consts.PwdStr: pwdHelp,
consts.CatStr: catHelp,
consts.EditStr: editHelp,
consts.HexEditStr: hexEditHelp,
consts.DownloadStr: downloadHelp,
consts.GrepStr: grepHelp,
consts.HeadStr: headHelp,
consts.TailStr: tailHelp,
consts.UploadStr: uploadHelp,
consts.MkdirStr: mkdirHelp,
consts.RmStr: rmHelp,
consts.ChtimesStr: chtimesHelp,
consts.ProcdumpStr: procdumpHelp,
consts.ElevateStr: elevateHelp,
consts.RunAsStr: runAsHelp,
consts.ImpersonateStr: impersonateHelp,
consts.RevToSelfStr: revToSelfHelp,
consts.ExecuteStr: executeHelp,
consts.ExecuteAssemblyStr: executeAssemblyHelp,
consts.ExecuteShellcodeStr: executeShellcodeHelp,
consts.MigrateStr: migrateHelp,
consts.SideloadStr: sideloadHelp,
consts.TerminateStr: terminateHelp,
consts.AliasesStr: loadAliasHelp,
consts.PsExecStr: psExecHelp,
consts.BackdoorStr: backdoorHelp,
consts.SpawnDllStr: spawnDllHelp,
consts.MountStr: mountHelp,
consts.ShikataGaNai: shikataGaNaiHelp,
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
consts.WebsitesStr: websitesHelp,
consts.ScreenshotStr: screenshotHelp,
@@ -418,6 +421,42 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`
Shellcode files should be binary encoded, you can generate Sliver shellcode files with the generate command:
generate --format shellcode
`
shikataGaNaiHelp = `[[.Bold]]Command:[[.Normal]] shikata-ga-nai [local path to raw shellcode]
[[.Bold]]About:[[.Normal]] Encodes shellcode with Shikata Ga Nai. This uses the same backend implementation as
[[.Bold]]shellcode-encoders encode[[.Normal]] with the shikata_ga_nai encoder, but exposes SGN-specific options
like architecture, iterations, and bad characters.
[[.Bold]][[.Underline]]++ Examples ++[[.Normal]]
Encode x64 shellcode with 3 iterations:
shikata-ga-nai -a amd64 -i 3 /tmp/payload.bin
Avoid bad chars and write to a file:
shikata-ga-nai -b 000a0d -s /tmp/payload.sgn /tmp/payload.bin
[[.Bold]]Related:[[.Normal]] shellcode-encoders encode --encoder shikata_ga_nai ...`
shellcodeEncodersHelp = `[[.Bold]]Command:[[.Normal]] shellcode-encoders
[[.Bold]]About:[[.Normal]] Lists the shellcode encoders supported by the server along with architectures and descriptions.
[[.Bold]][[.Underline]]++ Examples ++[[.Normal]]
List encoders and supported architectures:
shellcode-encoders
`
shellcodeEncodersEncodeHelp = `[[.Bold]]Command:[[.Normal]] shellcode-encoders encode [local path to raw shellcode]...
[[.Bold]]About:[[.Normal]] Encodes one or more shellcode files using a server-side encoder. Encoder support depends on
architecture; use [[.Bold]]shellcode-encoders[[.Normal]] to list valid combinations.
[[.Bold]][[.Underline]]++ Examples ++[[.Normal]]
Encode a single file with shikata_ga_nai:
shellcode-encoders encode --encoder shikata_ga_nai --arch amd64 /tmp/payload.bin
Encode multiple files and write outputs to a directory:
shellcode-encoders encode --encoder xor --arch amd64 -o /tmp/out payload1.bin payload2.bin
Avoid bad characters with SGN:
shellcode-encoders encode --encoder shikata_ga_nai --arch 386 -b 000a0d -o /tmp/encoded.bin /tmp/payload.bin
`
migrateHelp = `[[.Bold]]Command:[[.Normal]] migrate <flags>
[[.Bold]]About:[[.Normal]] (Windows Only) Migrates into the process designated by <flags>.`
+3 -1
View File
@@ -46,6 +46,7 @@ import (
"github.com/bishopfox/sliver/client/command/reaction"
"github.com/bishopfox/sliver/client/command/sessions"
"github.com/bishopfox/sliver/client/command/settings"
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
sgn "github.com/bishopfox/sliver/client/command/shikata-ga-nai"
"github.com/bishopfox/sliver/client/command/socks"
"github.com/bishopfox/sliver/client/command/taskmany"
@@ -103,7 +104,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
crack.Commands,
certificates.Commands,
clean.Command,
aka.ServerCommands,
aka.ServerCommands,
)
// C2 Network
@@ -119,6 +120,7 @@ func ServerCommands(con *client.SliverClient, serverCmds func() []*cobra.Command
// Payloads
bind(consts.PayloadsHelpGroup,
sgn.Commands,
shellcodeencoders.Commands,
generate.Commands,
builders.Commands,
)
@@ -0,0 +1,55 @@
package shellcodeencoders
import (
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Commands returns the shellcode-encoders command and its subcommands.
func Commands(con *console.SliverClient) []*cobra.Command {
shellcodeEncodersCmd := &cobra.Command{
Use: consts.ShellcodeEncodersStr,
Short: "List supported shellcode encoders",
Long: help.GetHelpFor([]string{consts.ShellcodeEncodersStr}),
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
ShellcodeEncodersCmd(cmd, con, args)
},
GroupID: consts.PayloadsHelpGroup,
}
flags.Bind("shellcode-encoders", false, shellcodeEncodersCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
shellcodeEncodersEncodeCmd := &cobra.Command{
Use: "encode <shellcode file>...",
Short: "Encode shellcode using server encoders",
Long: help.GetHelpFor([]string{consts.ShellcodeEncodersStr, "encode"}),
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ShellcodeEncodersEncodeCmd(cmd, con, args)
},
}
flags.Bind("shellcode-encoders encode", false, shellcodeEncodersEncodeCmd, func(f *pflag.FlagSet) {
f.StringP("encoder", "e", "", "encoder to use (shikata_ga_nai, xor, xor_dynamic)")
f.StringP("arch", "a", "amd64", "architecture of the shellcode")
f.IntP("iterations", "i", 1, "number of encoding iterations")
f.StringP("bad-chars", "b", "", "hex encoded bad characters to avoid (e.g. 0001)")
f.StringP("output", "o", "", "output file (or directory when encoding multiple files)")
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
})
flags.BindFlagCompletions(shellcodeEncodersEncodeCmd, func(comp *carapace.ActionMap) {
(*comp)["encoder"] = ShellcodeEncoderNameCompleter(con)
(*comp)["arch"] = ShellcodeEncoderArchCompleter(con)
(*comp)["output"] = carapace.ActionFiles().Tag("output file/directory")
})
carapace.Gen(shellcodeEncodersEncodeCmd).PositionalCompletion(carapace.ActionFiles().Tag("shellcode file"))
shellcodeEncodersCmd.AddCommand(shellcodeEncodersEncodeCmd)
return []*cobra.Command{shellcodeEncodersCmd}
}
+166
View File
@@ -0,0 +1,166 @@
package shellcodeencoders
import (
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
)
// ShellcodeEncodersEncodeCmd - Encode shellcode files with server encoders.
func ShellcodeEncodersEncodeCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
encoderFlag, _ := cmd.Flags().GetString("encoder")
if strings.TrimSpace(encoderFlag) == "" {
con.PrintErrorf("Encoder is required (use --encoder). See %s for options.\n", consts.ShellcodeEncodersStr)
return
}
encoderName := normalizeShellcodeEncoderName(encoderFlag)
encoderEnum, ok := shellcodeEncoderEnum(encoderName)
if !ok {
con.PrintErrorf("Unknown encoder %q (see %s for supported encoders)\n", encoderFlag, consts.ShellcodeEncodersStr)
return
}
archFlag, _ := cmd.Flags().GetString("arch")
arch := normalizeShellcodeArch(archFlag)
if arch == "" {
arch = "amd64"
}
iterations, _ := cmd.Flags().GetInt("iterations")
if iterations <= 0 {
iterations = 1
}
badCharsRaw, _ := cmd.Flags().GetString("bad-chars")
badChars, err := decodeHexBytes("bad-chars", badCharsRaw)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
output, _ := cmd.Flags().GetString("output")
outputInfo, err := outputInfoForArgs(output, len(args))
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
grpcCtx, cancel := con.GrpcContext(cmd)
defer cancel()
hadError := false
for _, path := range args {
data, err := os.ReadFile(path)
if err != nil {
con.PrintErrorf("Failed to read %s: %s\n", path, err)
hadError = true
continue
}
resp, err := con.Rpc.ShellcodeEncoder(grpcCtx, &clientpb.ShellcodeEncodeReq{
Encoder: encoderEnum,
Architecture: arch,
Iterations: uint32(iterations),
BadChars: badChars,
Data: data,
Request: con.ActiveTarget.Request(cmd),
})
if err != nil {
con.PrintErrorf("Failed to encode %s: %s\n", path, err)
hadError = true
continue
}
if resp.GetResponse() != nil && resp.GetResponse().GetErr() != "" {
con.PrintErrorf("Failed to encode %s: %s\n", path, resp.GetResponse().GetErr())
hadError = true
continue
}
outputPath, err := resolveOutputPath(path, output, outputInfo, encoderName, len(args) > 1)
if err != nil {
con.PrintErrorf("Failed to write %s: %s\n", path, err)
hadError = true
continue
}
if err := os.WriteFile(outputPath, resp.Data, 0o644); err != nil {
con.PrintErrorf("Failed to write %s: %s\n", outputPath, err)
hadError = true
continue
}
con.PrintInfof("Shellcode written to %s (%d bytes)\n", outputPath, len(resp.Data))
}
if hadError {
con.PrintWarnf("One or more files failed to encode\n")
}
}
func decodeHexBytes(label string, raw string) ([]byte, error) {
trimmed := strings.TrimSpace(raw)
trimmed = strings.TrimPrefix(trimmed, "0x")
trimmed = strings.TrimPrefix(trimmed, "0X")
trimmed = strings.ReplaceAll(trimmed, " ", "")
if trimmed == "" {
return nil, nil
}
data, err := hex.DecodeString(trimmed)
if err != nil {
return nil, fmt.Errorf("%s must be hex encoded: %w", label, err)
}
return data, nil
}
func outputInfoForArgs(output string, argCount int) (os.FileInfo, error) {
if output == "" {
return nil, nil
}
info, err := os.Stat(output)
if err != nil {
if os.IsNotExist(err) {
if argCount > 1 {
if mkErr := os.MkdirAll(output, 0o755); mkErr != nil {
return nil, fmt.Errorf("failed to create output directory %s: %w", output, mkErr)
}
info, err = os.Stat(output)
if err != nil {
return nil, fmt.Errorf("failed to stat output directory %s: %w", output, err)
}
return info, nil
}
return nil, nil
}
return nil, err
}
if argCount > 1 && !info.IsDir() {
return nil, fmt.Errorf("output must be a directory when encoding multiple files")
}
return info, nil
}
func resolveOutputPath(inputPath string, output string, outputInfo os.FileInfo, encoderName string, multiple bool) (string, error) {
suffix := "." + strings.ReplaceAll(encoderName, "_", "-")
base := filepath.Base(inputPath)
if output == "" {
return filepath.Join(filepath.Dir(inputPath), base+suffix), nil
}
if multiple {
if outputInfo == nil || !outputInfo.IsDir() {
return "", fmt.Errorf("output must be a directory when encoding multiple files")
}
return filepath.Join(output, base+suffix), nil
}
if outputInfo != nil && outputInfo.IsDir() {
return filepath.Join(output, base+suffix), nil
}
return output, nil
}
@@ -0,0 +1,101 @@
package shellcodeencoders
import (
"fmt"
"sort"
"strings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/rsteube/carapace"
)
var shellcodeEncoderNameToEnum = map[string]clientpb.ShellcodeEncoder{
"shikata_ga_nai": clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
"xor": clientpb.ShellcodeEncoder_XOR,
"xor_dynamic": clientpb.ShellcodeEncoder_XOR_DYNAMIC,
}
func normalizeShellcodeEncoderName(name string) string {
normalized := strings.ToLower(strings.TrimSpace(name))
normalized = strings.ReplaceAll(normalized, "-", "_")
return normalized
}
func normalizeShellcodeArch(arch string) string {
normalized := strings.ToLower(strings.TrimSpace(arch))
switch normalized {
case "amd64", "x64", "x86_64":
return "amd64"
case "386", "x86", "i386":
return "386"
default:
return normalized
}
}
func shellcodeEncoderEnum(name string) (clientpb.ShellcodeEncoder, bool) {
normalized := normalizeShellcodeEncoderName(name)
encoder, ok := shellcodeEncoderNameToEnum[normalized]
return encoder, ok
}
func fetchShellcodeEncoderMap(con *console.SliverClient) (*clientpb.ShellcodeEncoderMap, error) {
grpcCtx, cancel := con.GrpcContext(nil)
defer cancel()
return con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
}
// ShellcodeEncoderNameCompleter returns available encoder names with supported arches.
func ShellcodeEncoderNameCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
encoderMap, err := fetchShellcodeEncoderMap(con)
if err != nil {
return carapace.ActionMessage("failed to fetch shellcode encoders: %s", err.Error())
}
nameArchs := map[string][]string{}
for arch, archMap := range encoderMap.GetEncoders() {
if archMap == nil {
continue
}
for name := range archMap.GetEncoders() {
nameArchs[name] = append(nameArchs[name], arch)
}
}
names := make([]string, 0, len(nameArchs))
for name := range nameArchs {
names = append(names, name)
}
sort.Strings(names)
results := []string{}
for _, name := range names {
arches := nameArchs[name]
sort.Strings(arches)
desc := fmt.Sprintf("(%s)", strings.Join(arches, ", "))
results = append(results, name, desc)
}
return carapace.ActionValuesDescribed(results...).Tag("shellcode encoders")
})
}
// ShellcodeEncoderArchCompleter returns available architectures.
func ShellcodeEncoderArchCompleter(con *console.SliverClient) carapace.Action {
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
encoderMap, err := fetchShellcodeEncoderMap(con)
if err != nil {
return carapace.ActionMessage("failed to fetch shellcode encoders: %s", err.Error())
}
arches := make([]string, 0, len(encoderMap.GetEncoders()))
for arch := range encoderMap.GetEncoders() {
arches = append(arches, arch)
}
sort.Strings(arches)
return carapace.ActionValues(arches...).Tag("shellcode architectures")
})
}
@@ -0,0 +1,65 @@
package shellcodeencoders
import (
"sort"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
// ShellcodeEncodersCmd - Display supported shellcode encoders and architectures.
func ShellcodeEncodersCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
grpcCtx, cancel := con.GrpcContext(cmd)
defer cancel()
encoderMap, err := con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
if err != nil {
con.PrintErrorf("%s", err)
return
}
DisplayShellcodeEncoders(encoderMap, con)
}
// DisplayShellcodeEncoders - Display shellcode encoder map from server.
func DisplayShellcodeEncoders(encoderMap *clientpb.ShellcodeEncoderMap, con *console.SliverClient) {
if encoderMap == nil || len(encoderMap.GetEncoders()) == 0 {
con.PrintInfof("No shellcode encoders available\n")
return
}
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{"Arch", "Encoder", "Description"})
tw.SortBy([]table.SortBy{
{Name: "Arch", Mode: table.Asc},
{Name: "Encoder", Mode: table.Asc},
})
arches := make([]string, 0, len(encoderMap.GetEncoders()))
for arch := range encoderMap.GetEncoders() {
arches = append(arches, arch)
}
sort.Strings(arches)
for _, arch := range arches {
archMap := encoderMap.GetEncoders()[arch]
if archMap == nil {
continue
}
names := make([]string, 0, len(archMap.GetEncoders()))
for name := range archMap.GetEncoders() {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
desc := archMap.GetDescriptions()[name]
tw.AppendRow(table.Row{arch, name, desc})
}
}
con.Println(tw.Render())
}
+2 -1
View File
@@ -299,7 +299,8 @@ const (
GetPrivsStr = "getprivs"
ConnectStr = "connect"
ShikataGaNai = "shikata-ga-nai"
ShikataGaNai = "shikata-ga-nai"
ShellcodeEncodersStr = "shellcode-encoders"
Cursed = "cursed"
CursedChrome = "chrome"
+2 -2
View File
@@ -146,8 +146,8 @@ else
exit 1
fi
# server / sgn
if go test -tags=server,$TAGS ./server/sgn ; then
# server / encoders / shellcode / sgn
if go test -tags=server,$TAGS ./server/encoders/shellcode/sgn ; then
:
else
cat ~/.sliver/logs/sliver.log
File diff suppressed because it is too large. Load diff
+11 -1
View File
@@ -690,6 +690,8 @@ message Backdoor { commonpb.Response Response = 9; }
enum ShellcodeEncoder {
NONE = 0;
SHIKATA_GA_NAI = 1;
XOR = 2;
XOR_DYNAMIC = 3;
}
message ShellcodeEncodeReq {
@@ -697,6 +699,7 @@ message ShellcodeEncodeReq {
string Architecture = 2;
uint32 Iterations = 3;
bytes BadChars = 4;
reserved 5;
bytes Data = 8;
commonpb.Request Request = 9;
@@ -708,7 +711,14 @@ message ShellcodeEncode {
commonpb.Response Response = 9;
}
message ShellcodeEncoderMap { map<string, ShellcodeEncoder> Encoders = 1; }
message ShellcodeEncoderArchMap {
map<string, ShellcodeEncoder> Encoders = 1;
map<string, string> Descriptions = 2;
}
message ShellcodeEncoderMap {
map<string, ShellcodeEncoderArchMap> Encoders = 1;
}
// [ Builder ] ----------------------------------------
+1 -1
View File
@@ -31,7 +31,7 @@ Server daemon responsible for managing operators, implants, and infrastructure.
- `msf/` – Metasploit integration utilities. Implements payload translation and RPC bridging with Metasploit.
- `netstack/` – gVisor-based userland network stack for server transports. Provides packet buffers, endpoint management, and adapters.
- `rpc/` – RPC server implementations and wiring. Boots gRPC servers, registers services, and manages middleware.
- `sgn/` – SGN (Sliver Guard Node) coordination and helpers. Implements SGN enrollment, messaging, and policy logic.
- `encoders/shellcode/sgn/` – SGN (Shikata Ga Nai) shellcode encoder helpers and configuration wiring.
- `transport/` – Server-side transports and listener orchestration. Coordinates C2 listener lifecycles and connection routing.
- `watchtower/` – Watchtower monitoring and background jobs. Runs scheduled tasks, telemetry collection, and alerting hooks.
- `website/` – Static website assets and server-side handling. Serves operator web artifacts and supporting handlers.
+1 -1
View File
@@ -120,7 +120,7 @@ func Setup(force bool, echo bool) {
setupLog.Infof("Version mismatch %v != %v", localVer, ver.GitCommit)
if echo {
fmt.Printf(`
Sliver Copyright (C) 2025 Bishop Fox
Sliver Copyright (C) 2026 Bishop Fox
This program comes with ABSOLUTELY NO WARRANTY; for details type 'licenses'.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'licenses' for details.`)
-5
View File
@@ -23,7 +23,6 @@ import (
"crypto/rand"
insecureRand "math/rand"
"testing"
"time"
implantEncoders "github.com/bishopfox/sliver/implant/sliver/encoders"
util "github.com/bishopfox/sliver/util/encoders"
@@ -33,10 +32,6 @@ const (
sampleSizeMax = 8192
)
func init() {
insecureRand.Seed(time.Now().Unix())
}
func randomDataRandomSize(maxSize int) []byte {
buf := make([]byte, insecureRand.Intn(maxSize))
rand.Read(buf)
+47
View File
@@ -0,0 +1,47 @@
package amd64
import (
"crypto/rand"
"fmt"
)
const xorDynamicKeySize = 8
// XorKeyGen returns a random key suitable for the XOR encoder.
func XorKeyGen() ([]byte, error) {
return randomBytes(xorKeySize)
}
// XorDynamicKeyGen returns a random key suitable for the XOR dynamic encoder.
func XorDynamicKeyGen() ([]byte, error) {
return randomBytesFiltered(xorDynamicKeySize, xorDynamicBadchars)
}
func randomBytes(length int) ([]byte, error) {
if length <= 0 {
return nil, fmt.Errorf("invalid random length %d", length)
}
buf := make([]byte, length)
if _, err := rand.Read(buf); err != nil {
return nil, err
}
return buf, nil
}
func randomBytesFiltered(length int, badchars map[byte]bool) ([]byte, error) {
if length <= 0 {
return nil, fmt.Errorf("invalid random length %d", length)
}
buf := make([]byte, length)
for i := range buf {
for {
if _, err := rand.Read(buf[i : i+1]); err != nil {
return nil, err
}
if !badchars[buf[i]] {
break
}
}
}
return buf, nil
}
Binary file not shown.
Binary file not shown.
+133
View File
@@ -0,0 +1,133 @@
package amd64
/*
Sliver Implant Framework
Copyright (C) 2026 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/>.
------------------------------------------------------------------------
Based on the Metasploit x64/xor encoder by [email protected]
*/
import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"github.com/moloch--/go-keystone"
)
const (
xorKeySize = 8
xorBlockSize = 8
decoderPayloadSize = 0x27
)
// Xor encodes an amd64 payload using the Metasploit x64/xor scheme.
func Xor(data []byte, key []byte) ([]byte, error) {
if len(data) == 0 {
return nil, fmt.Errorf("xor encoder: empty payload")
}
if len(key) != xorKeySize {
return nil, fmt.Errorf("xor encoder: invalid key length %d", len(key))
}
blockCount := (len(data) + xorBlockSize - 1) / xorBlockSize
if blockCount <= 0 {
return nil, fmt.Errorf("xor encoder: invalid block count %d", blockCount)
}
paddedLen := blockCount * xorBlockSize
encoded := make([]byte, paddedLen)
copy(encoded, data)
for i := 0; i < paddedLen; i += xorBlockSize {
for j := 0; j < xorKeySize; j++ {
encoded[i+j] ^= key[j]
}
}
stub, err := buildDecoderStub(blockCount, key)
if err != nil {
return nil, err
}
return append(stub, encoded...), nil
}
func buildDecoderStub(blockCount int, key []byte) ([]byte, error) {
engine, err := keystone.NewEngine(keystone.ARCH_X86, keystone.MODE_64)
if err != nil {
return nil, err
}
defer func() { _ = engine.Close() }()
err = engine.Option(keystone.OPT_SYNTAX, keystone.OPT_SYNTAX_INTEL)
if err != nil {
return nil, err
}
src := strings.Join([]string{
".code64",
"xor rcx, rcx",
"sub rcx, 0x81",
"lea rax, [rip - 0x11]",
"mov rbx, 0x1122334455667788",
"decode:",
"xor qword ptr [rax + 0x27], rbx",
"sub rax, 0x80",
"loop decode",
}, "\n")
inst, err := engine.Assemble(src, 0)
if err != nil {
return nil, err
}
if len(inst) != decoderPayloadSize {
return nil, fmt.Errorf("xor encoder: unexpected decoder stub length %d", len(inst))
}
blockValue := uint32(int32(-blockCount))
if err := patchImm32(inst, []byte{0x48, 0x81, 0xE9}, blockValue); err != nil {
return nil, err
}
keyOffset := bytes.Index(inst, []byte{0x48, 0xBB})
if keyOffset == -1 || keyOffset+10 > len(inst) {
return nil, fmt.Errorf("xor encoder: failed to locate key offset")
}
copy(inst[keyOffset+2:keyOffset+10], key)
if err := patchImm32(inst, []byte{0x48, 0x2D}, 0xFFFFFFF8); err != nil {
return nil, err
}
return inst, nil
}
func patchImm32(stub []byte, opcode []byte, value uint32) error {
offset := bytes.Index(stub, opcode)
if offset == -1 {
return fmt.Errorf("xor encoder: opcode %x not found", opcode)
}
offset += len(opcode)
if offset+4 > len(stub) {
return fmt.Errorf("xor encoder: opcode %x out of range", opcode)
}
binary.LittleEndian.PutUint32(stub[offset:offset+4], value)
return nil
}
@@ -0,0 +1,283 @@
package amd64
/*
Sliver Implant Framework
Copyright (C) 2026 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/>.
------------------------------------------------------------------------
Based on the Metasploit x64/xor_dynamic encoder by lupman, phra
*/
import (
"bytes"
"fmt"
"strings"
"github.com/moloch--/go-keystone"
)
const (
xorDynamicKeyPlaceholder = 0x41
xorDynamicPayloadPlaceholder = 0x42
xorDynamicStubSize = 46
)
var xorDynamicBadchars = map[byte]bool{
0x00: true,
0x0a: true,
0x0d: true,
}
// XorDynamic encodes an amd64 payload using the Metasploit x64/xor_dynamic scheme.
// If key includes a trailing key terminator + payload terminator (3 bytes) that satisfy the
// encoder constraints, those are used verbatim.
func XorDynamic(data []byte, key []byte) ([]byte, error) {
if len(data) == 0 {
return nil, fmt.Errorf("xor_dynamic encoder: empty payload")
}
if len(key) == 0 {
return nil, fmt.Errorf("xor_dynamic encoder: empty key")
}
coreKey := key
var keyTerm byte
var payloadTerm []byte
encoded := xorDynamicEncode(data, key)
explicit := false
if candidateKey, candidateKeyTerm, candidatePayloadTerm, ok := splitExplicitTerms(data, key); ok {
coreKey = candidateKey
keyTerm = candidateKeyTerm
payloadTerm = candidatePayloadTerm
encoded = xorDynamicEncode(data, coreKey)
explicit = true
}
if containsBadchars(coreKey, xorDynamicBadchars) {
return nil, fmt.Errorf("xor_dynamic encoder: key contains badchars")
}
if !explicit {
var err error
keyTerm, err = selectKeyTerm(coreKey)
if err != nil {
return nil, err
}
payloadTerm, err = selectPayloadTerm(encoded)
if err != nil {
return nil, err
}
} else {
if bytes.IndexByte(coreKey, keyTerm) != -1 {
return nil, fmt.Errorf("xor_dynamic encoder: key contains key terminator")
}
if bytes.Contains(encoded, payloadTerm) {
return nil, fmt.Errorf("xor_dynamic encoder: encoded payload contains payload terminator")
}
}
stub, err := buildXorDynamicStub(keyTerm, payloadTerm)
if err != nil {
return nil, err
}
final := make([]byte, 0, len(stub)+len(coreKey)+1+len(encoded)+len(payloadTerm))
final = append(final, stub...)
final = append(final, coreKey...)
final = append(final, keyTerm)
final = append(final, encoded...)
final = append(final, payloadTerm...)
if containsBadchars(final, xorDynamicBadchars) {
return nil, fmt.Errorf("xor_dynamic encoder: badchars present in output")
}
return final, nil
}
func xorDynamicEncode(data []byte, key []byte) []byte {
encoded := make([]byte, len(data))
for i := range data {
encoded[i] = data[i] ^ key[i%len(key)]
}
return encoded
}
func splitExplicitTerms(data []byte, key []byte) ([]byte, byte, []byte, bool) {
if len(key) < 4 {
return nil, 0, nil, false
}
candidateKey := key[:len(key)-3]
if len(candidateKey) == 0 {
return nil, 0, nil, false
}
keyTerm := key[len(key)-3]
payloadTerm := key[len(key)-2:]
if xorDynamicBadchars[keyTerm] || xorDynamicBadchars[payloadTerm[0]] || xorDynamicBadchars[payloadTerm[1]] {
return nil, 0, nil, false
}
if bytes.IndexByte(candidateKey, keyTerm) != -1 {
return nil, 0, nil, false
}
if containsBadchars(candidateKey, xorDynamicBadchars) {
return nil, 0, nil, false
}
encoded := xorDynamicEncode(data, candidateKey)
if bytes.Contains(encoded, payloadTerm) {
return nil, 0, nil, false
}
return candidateKey, keyTerm, append([]byte{}, payloadTerm...), true
}
func selectKeyTerm(key []byte) (byte, error) {
for _, b := range allowedDynamicChars() {
if bytes.IndexByte(key, b) == -1 {
return b, nil
}
}
return 0, fmt.Errorf("xor_dynamic encoder: key terminator not found")
}
func selectPayloadTerm(encoded []byte) ([]byte, error) {
allowed := allowedDynamicChars()
for _, first := range allowed {
for _, second := range allowed {
term := []byte{first, second}
if !bytes.Contains(encoded, term) {
return term, nil
}
}
}
return nil, fmt.Errorf("xor_dynamic encoder: payload terminator not found")
}
func allowedDynamicChars() []byte {
allowed := make([]byte, 0, 255)
for i := 1; i <= 255; i++ {
b := byte(i)
if xorDynamicBadchars[b] {
continue
}
allowed = append(allowed, b)
}
return allowed
}
func buildXorDynamicStub(keyTerm byte, payloadTerm []byte) ([]byte, error) {
if len(payloadTerm) != 2 {
return nil, fmt.Errorf("xor_dynamic encoder: payload terminator must be 2 bytes")
}
engine, err := keystone.NewEngine(keystone.ARCH_X86, keystone.MODE_64)
if err != nil {
return nil, err
}
defer func() { _ = engine.Close() }()
if err := engine.Option(keystone.OPT_SYNTAX, keystone.OPT_SYNTAX_INTEL); err != nil {
return nil, err
}
src := strings.Join([]string{
".code64",
"jmp _call",
"_ret:",
"pop rbx",
"push rbx",
"pop rdi",
"mov al, 0x41",
"cld",
"_lp1:",
"scasb",
"jne _lp1",
"push rdi",
"pop rcx",
"_lp2:",
"push rbx",
"pop rsi",
"_lp3:",
"mov al, byte ptr [rsi]",
"xor byte ptr [rdi], al",
"inc rdi",
"inc rsi",
"cmp word ptr [rdi], 0x4242",
"je _jmp",
"cmp byte ptr [rsi], 0x41",
"jne _lp3",
"jmp _lp2",
"_jmp:",
"jmp rcx",
"_call:",
"call _ret",
}, "\n")
inst, err := engine.Assemble(src, 0)
if err != nil {
return nil, err
}
if len(inst) != xorDynamicStubSize {
return nil, fmt.Errorf("xor_dynamic encoder: unexpected stub length %d", len(inst))
}
keyReplaced := 0
for i := range inst {
if inst[i] == xorDynamicKeyPlaceholder {
inst[i] = keyTerm
keyReplaced++
}
}
if keyReplaced == 0 {
return nil, fmt.Errorf("xor_dynamic encoder: key placeholder not found")
}
payloadReplaced := 0
for i := 0; i+1 < len(inst); i++ {
if inst[i] == xorDynamicPayloadPlaceholder && inst[i+1] == xorDynamicPayloadPlaceholder {
inst[i] = payloadTerm[0]
inst[i+1] = payloadTerm[1]
payloadReplaced++
i++
}
}
if payloadReplaced == 0 {
return nil, fmt.Errorf("xor_dynamic encoder: payload placeholder not found")
}
if containsBadchars(inst, xorDynamicBadchars) {
return nil, fmt.Errorf("xor_dynamic encoder: badchars present in stub")
}
return inst, nil
}
func containsBadchars(data []byte, badchars map[byte]bool) bool {
for _, b := range data {
if badchars[b] {
return true
}
}
return false
}
@@ -0,0 +1,126 @@
package amd64
import (
"bytes"
"fmt"
"io/fs"
"os/exec"
"path/filepath"
"testing"
)
func TestXorDynamicEncoderMatchesMSFVenom(t *testing.T) {
if _, err := exec.LookPath("msfvenom"); err != nil {
t.Skipf("msfvenom not available: %v", err)
}
if !keystoneAvailable() {
t.Skip("keystone assembler not available")
}
fixtures, err := fs.Glob(xorFixtures, "testdata/*.bin")
if err != nil {
t.Fatalf("failed to list fixtures: %v", err)
}
if len(fixtures) == 0 {
t.Fatal("no xor fixtures found")
}
home := t.TempDir()
primeMSFVenom(t, home)
for _, name := range fixtures {
name := name
t.Run(filepath.Base(name), func(t *testing.T) {
payload, err := xorFixtures.ReadFile(name)
if err != nil {
t.Fatalf("failed to read fixture %s: %v", name, err)
}
if len(payload) == 0 {
t.Fatalf("fixture %s is empty", name)
}
platform, err := platformFromFixture(name)
if err != nil {
t.Fatalf("fixture %s: %v", name, err)
}
msfEncoded := msfvenomEncode(t, home, payload, platform, "x64/xor_dynamic")
key, keyTerm, payloadTerm, err := extractXorDynamicParams(msfEncoded)
if err != nil {
t.Fatalf("fixture %s: %v", name, err)
}
keyWithTerms := make([]byte, 0, len(key)+3)
keyWithTerms = append(keyWithTerms, key...)
keyWithTerms = append(keyWithTerms, keyTerm)
keyWithTerms = append(keyWithTerms, payloadTerm...)
encoded, err := XorDynamic(payload, keyWithTerms)
if err != nil {
t.Fatalf("fixture %s: XorDynamic failed: %v", name, err)
}
if !bytes.Equal(encoded, msfEncoded) {
diff := firstDiff(encoded, msfEncoded)
t.Fatalf("fixture %s: output mismatch at byte %d (got=%d, msf=%d)", name, diff, len(encoded), len(msfEncoded))
}
})
}
}
func extractXorDynamicParams(encoded []byte) ([]byte, byte, []byte, error) {
if len(encoded) < xorDynamicStubSize+3 {
return nil, 0, nil, fmt.Errorf("xor_dynamic output too short")
}
stub := encoded[:xorDynamicStubSize]
keyTerm, payloadTerm, err := extractXorDynamicTerms(stub)
if err != nil {
return nil, 0, nil, err
}
rest := encoded[xorDynamicStubSize:]
keyEnd := bytes.IndexByte(rest, keyTerm)
if keyEnd == -1 {
return nil, 0, nil, fmt.Errorf("xor_dynamic: key terminator not found in payload")
}
key := rest[:keyEnd]
if len(key) == 0 {
return nil, 0, nil, fmt.Errorf("xor_dynamic: empty key in payload")
}
if len(rest) < keyEnd+1+2 {
return nil, 0, nil, fmt.Errorf("xor_dynamic: payload missing terminator")
}
if len(rest) < 2 || !bytes.HasSuffix(rest, payloadTerm) {
return nil, 0, nil, fmt.Errorf("xor_dynamic: payload terminator mismatch")
}
return key, keyTerm, payloadTerm, nil
}
func extractXorDynamicTerms(stub []byte) (byte, []byte, error) {
movIdx := bytes.Index(stub, []byte{0xB0})
if movIdx == -1 || movIdx+1 >= len(stub) {
return 0, nil, fmt.Errorf("xor_dynamic: mov al not found in stub")
}
keyTerm := stub[movIdx+1]
cmpIdx := bytes.Index(stub, []byte{0x80, 0x3E})
if cmpIdx == -1 || cmpIdx+2 >= len(stub) {
return 0, nil, fmt.Errorf("xor_dynamic: cmp byte not found in stub")
}
if stub[cmpIdx+2] != keyTerm {
return 0, nil, fmt.Errorf("xor_dynamic: stub key terminator mismatch")
}
termIdx := bytes.Index(stub, []byte{0x66, 0x81, 0x3F})
if termIdx == -1 || termIdx+4 >= len(stub) {
return 0, nil, fmt.Errorf("xor_dynamic: cmp word not found in stub")
}
payloadTerm := []byte{stub[termIdx+3], stub[termIdx+4]}
return keyTerm, payloadTerm, nil
}
+172
View File
@@ -0,0 +1,172 @@
package amd64
import (
"bytes"
"embed"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/moloch--/go-keystone"
)
//go:embed testdata/*.bin
var xorFixtures embed.FS
func TestXorEncoderMatchesMSFVenom(t *testing.T) {
if _, err := exec.LookPath("msfvenom"); err != nil {
t.Skipf("msfvenom not available: %v", err)
}
if !keystoneAvailable() {
t.Skip("keystone assembler not available")
}
fixtures, err := fs.Glob(xorFixtures, "testdata/*.bin")
if err != nil {
t.Fatalf("failed to list fixtures: %v", err)
}
if len(fixtures) == 0 {
t.Fatal("no xor fixtures found")
}
home := t.TempDir()
primeMSFVenom(t, home)
for _, name := range fixtures {
name := name
t.Run(filepath.Base(name), func(t *testing.T) {
payload, err := xorFixtures.ReadFile(name)
if err != nil {
t.Fatalf("failed to read fixture %s: %v", name, err)
}
if len(payload) == 0 {
t.Fatalf("fixture %s is empty", name)
}
platform, err := platformFromFixture(name)
if err != nil {
t.Fatalf("fixture %s: %v", name, err)
}
msfEncoded := msfvenomEncode(t, home, payload, platform, "x64/xor")
key, err := extractMSFKey(msfEncoded)
if err != nil {
t.Fatalf("fixture %s: %v", name, err)
}
encoded, err := Xor(payload, key)
if err != nil {
t.Fatalf("fixture %s: Xor failed: %v", name, err)
}
if !bytes.Equal(encoded, msfEncoded) {
diff := firstDiff(encoded, msfEncoded)
t.Fatalf("fixture %s: output mismatch at byte %d (got=%d, msf=%d)", name, diff, len(encoded), len(msfEncoded))
}
})
}
}
func keystoneAvailable() bool {
engine, err := keystone.NewEngine(keystone.ARCH_X86, keystone.MODE_64)
if err != nil {
return false
}
_ = engine.Close()
return true
}
func msfvenomEncode(t *testing.T, home string, payload []byte, platform string, encoder string) []byte {
t.Helper()
outFile, err := os.CreateTemp(home, "msf-xor-*.bin")
if err != nil {
t.Fatalf("msfvenom temp file: %v", err)
}
outPath := outFile.Name()
_ = outFile.Close()
defer func() { _ = os.Remove(outPath) }()
cmd := exec.Command("msfvenom",
"-p", "-",
"-a", "x64",
"--platform", platform,
"-e", encoder,
"-i", "1",
"-f", "raw",
"-o", outPath,
)
cmd.Env = append(os.Environ(), "HOME="+home)
cmd.Stdin = bytes.NewReader(payload)
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
t.Fatalf("msfvenom failed: %v (stderr=%s)", err, strings.TrimSpace(stderr.String()))
}
encoded, err := os.ReadFile(outPath)
if err != nil {
t.Fatalf("msfvenom read output: %v", err)
}
if len(encoded) == 0 {
t.Fatalf("msfvenom produced empty output (stderr=%s)", strings.TrimSpace(stderr.String()))
}
return encoded
}
func primeMSFVenom(t *testing.T, home string) {
t.Helper()
cmd := exec.Command("msfvenom", "-l", "encoders")
cmd.Env = append(os.Environ(), "HOME="+home)
cmd.Stdin = bytes.NewReader(nil)
var stderr bytes.Buffer
var stdout bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
t.Fatalf("msfvenom init failed: %v (stderr=%s)", err, strings.TrimSpace(stderr.String()))
}
}
func extractMSFKey(encoded []byte) ([]byte, error) {
keyOffset := bytes.Index(encoded, []byte{0x48, 0xBB})
if keyOffset == -1 || keyOffset+10 > len(encoded) {
return nil, fmt.Errorf("failed to locate xor key in msf output")
}
key := make([]byte, xorKeySize)
copy(key, encoded[keyOffset+2:keyOffset+10])
return key, nil
}
func platformFromFixture(name string) (string, error) {
base := filepath.Base(name)
switch {
case strings.HasPrefix(base, "linux-"):
return "linux", nil
case strings.HasPrefix(base, "windows-"):
return "windows", nil
default:
return "", fmt.Errorf("unknown platform for fixture %s", base)
}
}
func firstDiff(a, b []byte) int {
min := len(a)
if len(b) < min {
min = len(b)
}
for i := 0; i < min; i++ {
if a[i] != b[i] {
return i
}
}
return min
}
@@ -1,4 +1,4 @@
# server/sgn
# server/encoders/shellcode/sgn
## Overview
@@ -27,7 +27,7 @@ These options mirror the upstream CLI flags so server-side tasks can reuse the s
Shellcode fixtures used by the unit tests live under `testdata/` with a `.bin` extension. They are produced via `msfvenom` using a dedicated Go generator:
```bash
go generate ./server/sgn
go generate ./server/encoders/shellcode/sgn
```
The generator invokes `msfvenom` three times (reverse TCP/HTTP stagers and an exec payload) and writes raw shellcode into the `testdata` directory. Ensure the Metasploit framework is installed and `msfvenom` is on `$PATH` before running the generation step.
@@ -40,7 +40,7 @@ The log subsystem expects a writable Sliver root directory. Point it to a tempor
export SLIVER_ROOT_DIR=$(pwd)/.tmp-sliver
export GOCACHE=$(pwd)/.tmp-gocache
mkdir -p "$SLIVER_ROOT_DIR" "$GOCACHE"
go test ./server/sgn
go test ./server/encoders/shellcode/sgn
```
The test suite focuses on option wiring and constraint helpers rather than the full stochastic encoding pipeline.
File renamed without changes.
File renamed without changes.
@@ -37,8 +37,6 @@ var (
// SGNConfig - Configuration for sgn
type SGNConfig struct {
AppDir string
Architecture string // Binary architecture (32/64) (default 32)
Asci bool // Generates a full ASCI printable payload (takes very long time to bruteforce)
BadChars []byte // Don't use specified bad characters given in hex format (\x00\x01\x02...)
File renamed without changes.
+99
View File
@@ -0,0 +1,99 @@
package shellcode
import (
"github.com/bishopfox/sliver/server/encoders/shellcode/amd64"
"github.com/bishopfox/sliver/server/encoders/shellcode/sgn"
)
type ShellcodeEncoderArgs struct {
Iterations int
BadChars []byte
}
type ShellcodeEncoder interface {
Description() string
Encode(data []byte, args ShellcodeEncoderArgs) ([]byte, error)
}
type XorEncoder struct{}
func (e *XorEncoder) Encode(data []byte, args ShellcodeEncoderArgs) ([]byte, error) {
iterations := args.Iterations
if iterations <= 0 {
iterations = 1
}
for i := 0; i < iterations; i++ {
key, err := amd64.XorKeyGen()
if err != nil {
return nil, err
}
encoded, err := amd64.Xor(data, key)
if err != nil {
return nil, err
}
data = encoded
}
return data, nil
}
func (e *XorEncoder) Description() string {
return "Basic XOR encoder for AMD64"
}
type XorDynamicEncoder struct{}
func (e *XorDynamicEncoder) Encode(data []byte, args ShellcodeEncoderArgs) ([]byte, error) {
iterations := args.Iterations
if iterations <= 0 {
iterations = 1
}
for i := 0; i < iterations; i++ {
key, err := amd64.XorDynamicKeyGen()
if err != nil {
return nil, err
}
encoded, err := amd64.XorDynamic(data, key)
if err != nil {
return nil, err
}
data = encoded
}
return data, nil
}
func (e *XorDynamicEncoder) Description() string {
return "An x64 XOR encoder with dynamic key size for AMD64"
}
type ShikataGaNaiEncoderAmd64 struct{}
func (e *ShikataGaNaiEncoderAmd64) Encode(data []byte, args ShellcodeEncoderArgs) ([]byte, error) {
return sgn.EncodeShellcode(data, "amd64", args.Iterations, args.BadChars)
}
func (e *ShikataGaNaiEncoderAmd64) Description() string {
return "Shikata Ga Nai encoder for AMD64"
}
type ShikataGaNaiEncoder386 struct{}
func (e *ShikataGaNaiEncoder386) Encode(data []byte, args ShellcodeEncoderArgs) ([]byte, error) {
return sgn.EncodeShellcode(data, "386", args.Iterations, args.BadChars)
}
func (e *ShikataGaNaiEncoder386) Description() string {
return "Shikata Ga Nai encoder for 386"
}
var (
ShellcodeEncoders = map[string]map[string]ShellcodeEncoder{
"amd64": {
"xor": &XorEncoder{},
"xor_dynamic": &XorDynamicEncoder{},
"shikata_ga_nai": &ShikataGaNaiEncoderAmd64{},
},
"386": {
"shikata_ga_nai": &ShikataGaNaiEncoder386{},
},
}
)
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/configs"
"github.com/bishopfox/sliver/server/sgn"
"github.com/bishopfox/sliver/server/encoders/shellcode/sgn"
)
var (
+128 -18
View File
@@ -21,33 +21,84 @@ package rpc
import (
"context"
"fmt"
"sort"
"strings"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/server/sgn"
"github.com/bishopfox/sliver/server/encoders/shellcode"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var shellcodeEncoderNames = map[clientpb.ShellcodeEncoder]string{
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
clientpb.ShellcodeEncoder_XOR: "xor",
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
}
var shellcodeEncoderEnums = map[string]clientpb.ShellcodeEncoder{
"shikata_ga_nai": clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
"xor": clientpb.ShellcodeEncoder_XOR,
"xor_dynamic": clientpb.ShellcodeEncoder_XOR_DYNAMIC,
}
// ShellcodeEncode - Encode a piece shellcode
func (rpc *Server) ShellcodeEncoder(ctx context.Context, req *clientpb.ShellcodeEncodeReq) (*clientpb.ShellcodeEncode, error) {
resp := &clientpb.ShellcodeEncode{Response: &commonpb.Response{}}
var err error
switch req.Encoder {
case clientpb.ShellcodeEncoder_SHIKATA_GA_NAI:
rpcLog.Infof("[rpc] Shellcode encoder request for: SHIKATA_GA_NAI")
rpcLog.Infof("[rpc] Encoding shellcode (%d bytes) for architecture %s with %d iterations and badchars: %v", len(req.Data), req.Architecture, req.Iterations, req.BadChars)
resp.Data, err = sgn.EncodeShellcode(req.Data, req.Architecture, int(req.Iterations), req.BadChars)
if err != nil {
rpcLog.Errorf("[rpc] Failed to encode shellcode: %v", err)
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to encode shellcode (%s)", err))
}
default:
resp.Response.Err = "Unknown encoder"
if req == nil {
return nil, status.Error(codes.InvalidArgument, "missing request")
}
if req.Encoder == clientpb.ShellcodeEncoder_NONE {
resp.Data = req.Data
return resp, nil
}
encoderName, ok := shellcodeEncoderNames[req.Encoder]
if !ok {
resp.Response.Err = "Unknown encoder"
return resp, nil
}
arch := normalizeShellcodeArch(req.Architecture)
if arch == "" {
arch = defaultShellcodeArch(encoderName)
}
if arch == "" {
resp.Response.Err = "Unknown architecture"
return resp, nil
}
encoderArchs := shellcode.ShellcodeEncoders[arch]
if encoderArchs == nil {
resp.Response.Err = fmt.Sprintf("Unknown architecture: %s", arch)
return resp, nil
}
encoder := encoderArchs[encoderName]
if encoder == nil {
resp.Response.Err = fmt.Sprintf("Encoder %s not supported for architecture %s", encoderName, arch)
return resp, nil
}
iterations := int(req.Iterations)
if iterations <= 0 {
iterations = 1
}
rpcLog.Infof("[rpc] Shellcode encoder request for: %s (%s)", encoderName, arch)
rpcLog.Infof("[rpc] Encoding shellcode (%d bytes) for architecture %s with %d iterations and badchars: %v", len(req.Data), arch, iterations, req.BadChars)
encoded, err := encoder.Encode(req.Data, shellcode.ShellcodeEncoderArgs{
Iterations: iterations,
BadChars: req.BadChars,
})
if err != nil {
rpcLog.Errorf("[rpc] Failed to encode shellcode: %v", err)
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to encode shellcode (%s)", err))
}
resp.Data = encoded
rpcLog.Infof("[rpc] Successfully encoded shellcode (%d bytes)", len(resp.Data))
return resp, nil
@@ -56,10 +107,69 @@ func (rpc *Server) ShellcodeEncoder(ctx context.Context, req *clientpb.Shellcode
// ShellcodeEncoderMap - Get a map of support shellcode encoders <human readable/enum>
func (rpc *Server) ShellcodeEncoderMap(ctx context.Context, _ *commonpb.Empty) (*clientpb.ShellcodeEncoderMap, error) {
resp := &clientpb.ShellcodeEncoderMap{
Encoders: map[string]clientpb.ShellcodeEncoder{
// Human Readable: enum value
"shikata-ga-nai": clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
},
Encoders: map[string]*clientpb.ShellcodeEncoderArchMap{},
}
arches := make([]string, 0, len(shellcode.ShellcodeEncoders))
for arch := range shellcode.ShellcodeEncoders {
arches = append(arches, arch)
}
sort.Strings(arches)
for _, arch := range arches {
encoderMap := shellcode.ShellcodeEncoders[arch]
if encoderMap == nil {
continue
}
resp.Encoders[arch] = &clientpb.ShellcodeEncoderArchMap{
Encoders: map[string]clientpb.ShellcodeEncoder{},
Descriptions: map[string]string{},
}
names := make([]string, 0, len(encoderMap))
for name := range encoderMap {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
if enumVal, ok := shellcodeEncoderEnums[name]; ok {
resp.Encoders[arch].Encoders[name] = enumVal
}
resp.Encoders[arch].Descriptions[name] = encoderMap[name].Description()
}
}
return resp, nil
}
func normalizeShellcodeArch(arch string) string {
normalized := strings.ToLower(strings.TrimSpace(arch))
switch normalized {
case "amd64", "x64", "x86_64":
return "amd64"
case "386", "x86", "i386":
return "386"
default:
return normalized
}
}
func defaultShellcodeArch(encoderName string) string {
var candidates []string
for arch, encoderMap := range shellcode.ShellcodeEncoders {
if _, ok := encoderMap[encoderName]; ok {
candidates = append(candidates, arch)
}
}
if len(candidates) == 1 {
return candidates[0]
}
for _, arch := range candidates {
if arch == "amd64" {
return arch
}
}
if len(candidates) > 0 {
sort.Strings(candidates)
return candidates[0]
}
return ""
}
+1 -1
View File
@@ -38,7 +38,7 @@ import (
"github.com/bishopfox/sliver/server/db/models"
"github.com/bishopfox/sliver/server/generate"
"github.com/bishopfox/sliver/server/log"
"github.com/bishopfox/sliver/server/sgn"
"github.com/bishopfox/sliver/server/encoders/shellcode/sgn"
"github.com/bishopfox/sliver/util"
"google.golang.org/grpc/codes"