Compare commits

...
Author SHA1 Message Date
moloch-- b7566a3b09 Update client grpc to modern apis 2026-02-06 17:31:08 -08:00
moloch-- b889e2eb6f Fix merge 2026-02-06 16:30:53 -08:00
moloch-- 4466ace279 Merge master 2026-02-06 16:29:39 -08:00
Joe 2b65089b27 Merge pull request #2164 from BishopFox/fix/dns-dos
Fix/dns dos
2026-02-06 15:41:17 -07:00
moloch-- 87d5fd53a8 Add e2e dns c2 tests 2026-02-06 13:54:07 -08:00
moloch-- cf79a2d284 Merge patch for oversized msg size checks 2026-02-06 12:16:03 -08:00
moloch-- 6d0276ff67 Add more tests for dns gc 2026-02-06 11:56:55 -08:00
moloch-- 9b001ab88e Remove totp from dns c2, implement pending msg gc 2026-02-06 11:40:00 -08:00
Joe 503218f3f4 Merge pull request #2163 from BishopFox/feature/exec-bg
Feature/exec bg
2026-02-06 10:44:20 -07:00
moloch-- 25d242230b Rengerate pb 2026-01-30 15:10:02 -08:00
moloch-- b4345226c1 Merge branch 'feature/shellcode-encoders' into crack/basic 2026-01-30 15:09:21 -08:00
moloch-- c9f4970e4b Add crack db models 2026-01-29 19:35:39 -08:00
moloch-- e72fa3dbd0 Switch crack.yaml to snake case 2026-01-29 16:30:51 -08:00
moloch-- 2e93ce5684 Merge branch 'master' into crack/basic 2026-01-29 16:16:43 -08:00
moloch-- 828838cddf Re-add new gprc server config fields 2026-01-29 05:52:41 -08:00
moloch-- 16c1dc21fd Merge master 2026-01-29 05:47:18 -08:00
moloch-- 33c656d082 Implement first part of crack cmd 2026-01-24 18:28:07 -08:00
moloch-- 99a44e340f Improve crack benchmark output 2026-01-24 11:50:20 -08:00
moloch-- de5fa3458b Allow keep alives 2026-01-23 17:05:53 -08:00
40 changed files with 3929 additions and 1702 deletions

No files matched your search

+4
View File
@@ -24,6 +24,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
}
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
bindCrackFlags(f)
})
crackStationsCmd := &cobra.Command{
@@ -34,6 +35,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
CrackStationsCmd(cmd, con, args)
},
}
flags.Bind("", false, crackStationsCmd, func(f *pflag.FlagSet) {
f.Bool("show-benchmarks", false, "show benchmark rates for crackstations")
})
crackCmd.AddCommand(crackStationsCmd)
wordlistsCmd := &cobra.Command{
+326
View File
@@ -0,0 +1,326 @@
package crack
import (
"fmt"
"strconv"
"strings"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func shouldRunCrack(cmd *cobra.Command, args []string) bool {
if len(args) > 0 {
return true
}
hasFlag := false
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
if flag.Changed && flag.Name != "timeout" {
hasFlag = true
}
})
return hasFlag
}
func buildCrackCommand(cmd *cobra.Command, args []string) (*clientpb.CrackCommand, error) {
flags := cmd.Flags()
req := &clientpb.CrackCommand{}
if flags.Changed("attack-mode") {
raw, _ := flags.GetString("attack-mode")
attackMode, err := parseCrackAttackMode(raw)
if err != nil {
return nil, err
}
req.AttackMode = attackMode
}
if flags.Changed("hash-type") {
raw, _ := flags.GetString("hash-type")
hashType, err := parseHashType(raw)
if err != nil {
return nil, err
}
req.HashType = hashType
}
hashes, _ := flags.GetStringSlice("hash")
if len(args) > 0 {
hashes = append(hashes, args...)
}
req.Hashes = hashes
req.Quiet, _ = flags.GetBool("quiet")
req.HexCharset, _ = flags.GetBool("hex-charset")
req.HexSalt, _ = flags.GetBool("hex-salt")
req.HexWordlist, _ = flags.GetBool("hex-wordlist")
req.Force, _ = flags.GetBool("force")
req.DeprecatedCheckDisable, _ = flags.GetBool("deprecated-check-disable")
req.Status, _ = flags.GetBool("status")
req.StatusJSON, _ = flags.GetBool("status-json")
req.StatusTimer, _ = flags.GetUint32("status-timer")
req.StdinTimeoutAbort, _ = flags.GetUint32("stdin-timeout-abort")
req.MachineReadable, _ = flags.GetBool("machine-readable")
req.KeepGuessing, _ = flags.GetBool("keep-guessing")
req.SelfTestDisable, _ = flags.GetBool("self-test-disable")
req.Loopback, _ = flags.GetBool("loopback")
if raw, _ := flags.GetString("markov-hcstat2"); raw != "" {
req.MarkovHcstat2 = []byte(raw)
}
req.MarkovDisable, _ = flags.GetBool("markov-disable")
req.MarkovClassic, _ = flags.GetBool("markov-classic")
req.MarkovInverse, _ = flags.GetBool("markov-inverse")
req.MarkovThreshold, _ = flags.GetUint32("markov-threshold")
req.Runtime, _ = flags.GetUint32("runtime")
req.Session, _ = flags.GetString("session")
req.Restore, _ = flags.GetBool("restore")
req.RestoreDisable, _ = flags.GetBool("restore-disable")
if raw, _ := flags.GetString("restore-file"); raw != "" {
req.RestoreFile = []byte(raw)
}
if formats, _ := flags.GetStringSlice("outfile-format"); len(formats) > 0 {
for _, raw := range formats {
format, err := parseCrackOutfileFormat(raw)
if err != nil {
return nil, err
}
req.OutfileFormat = append(req.OutfileFormat, format)
}
}
req.OutfileAutohexDisable, _ = flags.GetBool("outfile-autohex-disable")
req.OutfileCheckTimer, _ = flags.GetUint32("outfile-check-timer")
req.WordlistAutohexDisable, _ = flags.GetBool("wordlist-autohex-disable")
req.Separator, _ = flags.GetString("separator")
req.Stdout, _ = flags.GetBool("stdout")
req.Show, _ = flags.GetBool("show")
req.Left, _ = flags.GetBool("left")
req.Username, _ = flags.GetBool("username")
req.Remove, _ = flags.GetBool("remove")
req.RemoveTimer, _ = flags.GetUint32("remove-timer")
req.PotfileDisable, _ = flags.GetBool("potfile-disable")
if raw, _ := flags.GetString("potfile"); raw != "" {
req.Potfile = []byte(raw)
}
if flags.Changed("encoding-from") {
raw, _ := flags.GetString("encoding-from")
enc, err := parseCrackEncoding(raw)
if err != nil {
return nil, err
}
req.EncodingFrom = enc
}
if flags.Changed("encoding-to") {
raw, _ := flags.GetString("encoding-to")
enc, err := parseCrackEncoding(raw)
if err != nil {
return nil, err
}
req.EncodingTo = enc
}
req.DebugMode, _ = flags.GetUint32("debug-mode")
req.LogfileDisable, _ = flags.GetBool("logfile-disable")
req.HccapxMessagePair, _ = flags.GetUint32("hccapx-message-pair")
req.NonceErrorCorrections, _ = flags.GetUint32("nonce-error-corrections")
if raw, _ := flags.GetString("keyboard-layout-mapping"); raw != "" {
req.KeyboardLayoutMapping = []byte(raw)
}
req.Benchmark, _ = flags.GetBool("benchmark")
req.BenchmarkAll, _ = flags.GetBool("benchmark-all")
req.SpeedOnly, _ = flags.GetBool("speed-only")
req.ProgressOnly, _ = flags.GetBool("progress-only")
req.SegmentSize, _ = flags.GetUint32("segment-size")
req.BitmapMin, _ = flags.GetUint32("bitmap-min")
req.BitmapMax, _ = flags.GetUint32("bitmap-max")
req.CPUAffinity = uintSliceToUint32(flags, "cpu-affinity")
req.HookThreads, _ = flags.GetUint32("hook-threads")
req.HashInfo, _ = flags.GetBool("hash-info")
req.BackendIgnoreCUDA, _ = flags.GetBool("backend-ignore-cuda")
req.BackendIgnoreHip, _ = flags.GetBool("backend-ignore-hip")
req.BackendIgnoreMetal, _ = flags.GetBool("backend-ignore-metal")
req.BackendIgnoreOpenCL, _ = flags.GetBool("backend-ignore-opencl")
req.BackendInfo, _ = flags.GetBool("backend-info")
req.BackendDevices = uintSliceToUint32(flags, "backend-devices")
req.OpenCLDeviceTypes = uintSliceToUint32(flags, "opencl-device-types")
req.OptimizedKernelEnable, _ = flags.GetBool("optimized-kernel-enable")
req.MultiplyAccelDisabled, _ = flags.GetBool("multiply-accel-disabled")
if flags.Changed("workload-profile") {
raw, _ := flags.GetString("workload-profile")
profile, err := parseCrackWorkloadProfile(raw)
if err != nil {
return nil, err
}
req.WorkloadProfile = profile
}
req.KernelAccel, _ = flags.GetUint32("kernel-accel")
req.KernelLoops, _ = flags.GetUint32("kernel-loops")
req.KernelThreads, _ = flags.GetUint32("kernel-threads")
req.BackendVectorWidth, _ = flags.GetUint32("backend-vector-width")
req.SpinDamp, _ = flags.GetUint32("spin-damp")
req.HwmonDisable, _ = flags.GetBool("hwmon-disable")
req.HwmonTempAbort, _ = flags.GetUint32("hwmon-temp-abort")
req.ScryptTMTO, _ = flags.GetUint32("scrypt-tmto")
req.Skip, _ = flags.GetUint64("skip")
req.Limit, _ = flags.GetUint64("limit")
req.Keyspace, _ = flags.GetBool("keyspace")
if raw, _ := flags.GetString("rules-file"); raw != "" {
req.RulesFile = []byte(raw)
}
req.GenerateRules, _ = flags.GetUint32("generate-rules")
req.GenerateRulesFunMin, _ = flags.GetUint32("generate-rules-fun-min")
req.GenerateRulesFunMax, _ = flags.GetUint32("generate-rules-fun-max")
req.GenerateRulesFuncSel, _ = flags.GetString("generate-rules-func-sel")
req.GenerateRulesSeed, _ = flags.GetInt32("generate-rules-seed")
req.CustomCharset1, _ = flags.GetString("custom-charset1")
req.CustomCharset2, _ = flags.GetString("custom-charset2")
req.CustomCharset3, _ = flags.GetString("custom-charset3")
req.CustomCharset4, _ = flags.GetString("custom-charset4")
req.Identify, _ = flags.GetString("identify")
req.Increment, _ = flags.GetBool("increment")
req.IncrementMin, _ = flags.GetUint32("increment-min")
req.IncrementMax, _ = flags.GetUint32("increment-max")
req.SlowCandidates, _ = flags.GetBool("slow-candidates")
req.BrainServer, _ = flags.GetBool("brain-server")
req.BrainServerTimer, _ = flags.GetUint32("brain-server-timer")
req.BrainClient, _ = flags.GetBool("brain-client")
req.BrainClientFeatures, _ = flags.GetString("brain-client-features")
req.BrainHost, _ = flags.GetString("brain-host")
req.BrainPort, _ = flags.GetUint32("brain-port")
req.BrainPassword, _ = flags.GetString("brain-password")
req.BrainSession, _ = flags.GetString("brain-session")
req.BrainSessionWhitelist, _ = flags.GetString("brain-session-whitelist")
return req, nil
}
func uintSliceToUint32(flags *pflag.FlagSet, name string) []uint32 {
values, _ := flags.GetUintSlice(name)
if len(values) == 0 {
return nil
}
out := make([]uint32, len(values))
for i, value := range values {
out[i] = uint32(value)
}
return out
}
func parseCrackAttackMode(raw string) (clientpb.CrackAttackMode, error) {
if raw == "" {
return clientpb.CrackAttackMode_STRAIGHT, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackAttackMode(numeric), nil
}
normalized := strings.ToLower(strings.TrimSpace(raw))
normalized = strings.NewReplacer("-", "_", " ", "_").Replace(normalized)
switch normalized {
case "straight":
return clientpb.CrackAttackMode_STRAIGHT, nil
case "combination":
return clientpb.CrackAttackMode_COMBINATION, nil
case "bruteforce", "brute_force", "brute-force":
return clientpb.CrackAttackMode_BRUTEFORCE, nil
case "hybrid_wordlist_mask":
return clientpb.CrackAttackMode_HYBRID_WORDLIST_MASK, nil
case "hybrid_mask_wordlist":
return clientpb.CrackAttackMode_HYBRID_MASK_WORDLIST, nil
case "association":
return clientpb.CrackAttackMode_ASSOCIATION, nil
case "no_attack", "none":
return clientpb.CrackAttackMode_NO_ATTACK, nil
default:
return clientpb.CrackAttackMode_STRAIGHT, fmt.Errorf("unknown attack mode: %s", raw)
}
}
func parseHashType(raw string) (clientpb.HashType, error) {
if raw == "" {
return clientpb.HashType_MD5, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.HashType(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.HashType_value[key]; ok {
return clientpb.HashType(value), nil
}
return clientpb.HashType_MD5, fmt.Errorf("unknown hash type: %s", raw)
}
func parseCrackEncoding(raw string) (clientpb.CrackEncoding, error) {
if raw == "" {
return clientpb.CrackEncoding_INVALID_ENCODING, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackEncoding(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.CrackEncoding_value[key]; ok {
return clientpb.CrackEncoding(value), nil
}
return clientpb.CrackEncoding_INVALID_ENCODING, fmt.Errorf("unknown encoding: %s", raw)
}
func parseCrackOutfileFormat(raw string) (clientpb.CrackOutfileFormat, error) {
if raw == "" {
return clientpb.CrackOutfileFormat_INVALID_FORMAT, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackOutfileFormat(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.CrackOutfileFormat_value[key]; ok {
return clientpb.CrackOutfileFormat(value), nil
}
return clientpb.CrackOutfileFormat_INVALID_FORMAT, fmt.Errorf("unknown outfile format: %s", raw)
}
func parseCrackWorkloadProfile(raw string) (clientpb.CrackWorkloadProfile, error) {
if raw == "" {
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, nil
}
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
return clientpb.CrackWorkloadProfile(numeric), nil
}
key := normalizeEnumKey(raw)
if value, ok := clientpb.CrackWorkloadProfile_value[key]; ok {
return clientpb.CrackWorkloadProfile(value), nil
}
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, fmt.Errorf("unknown workload profile: %s", raw)
}
func normalizeEnumKey(value string) string {
if value == "" {
return ""
}
var builder strings.Builder
builder.Grow(len(value))
underscore := false
for i := 0; i < len(value); i++ {
ch := value[i]
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
if ch >= 'a' && ch <= 'z' {
ch -= 'a' - 'A'
}
builder.WriteByte(ch)
underscore = false
continue
}
if !underscore {
builder.WriteByte('_')
underscore = true
}
}
return strings.Trim(builder.String(), "_")
}
+108
View File
@@ -0,0 +1,108 @@
package crack
import "github.com/spf13/pflag"
func bindCrackFlags(f *pflag.FlagSet) {
f.String("attack-mode", "", "crack attack mode (straight, combination, bruteforce, hybrid-wordlist-mask, hybrid-mask-wordlist, association, no-attack, or numeric)")
f.String("hash-type", "", "hashcat hash type (number or enum name)")
f.StringSlice("hash", nil, "hashes to crack (repeatable)")
f.Bool("quiet", false, "quiet mode")
f.Bool("hex-charset", false, "treat charset as hex")
f.Bool("hex-salt", false, "treat salt as hex")
f.Bool("hex-wordlist", false, "treat wordlist as hex")
f.Bool("force", false, "ignore warnings")
f.Bool("deprecated-check-disable", false, "disable deprecated check")
f.Bool("status", false, "enable status")
f.Bool("status-json", false, "emit status in JSON")
f.Uint32("status-timer", 0, "status timer interval (seconds)")
f.Uint32("stdin-timeout-abort", 0, "abort after stdin timeout (seconds)")
f.Bool("machine-readable", false, "machine readable output")
f.Bool("keep-guessing", false, "keep guessing")
f.Bool("self-test-disable", false, "disable self-test")
f.Bool("loopback", false, "use loopback mode")
f.String("markov-hcstat2", "", "markov hcstat2 filename or identifier")
f.Bool("markov-disable", false, "disable markov mode")
f.Bool("markov-classic", false, "use classic markov")
f.Bool("markov-inverse", false, "use inverse markov")
f.Uint32("markov-threshold", 0, "markov threshold")
f.Uint32("runtime", 0, "runtime in seconds")
f.String("session", "", "session name")
f.Bool("restore", false, "restore session")
f.Bool("restore-disable", false, "disable restore")
f.String("restore-file", "", "restore file path or identifier")
f.StringSlice("outfile-format", nil, "outfile formats (hash-salt, plain, hex-plain, crack-pos, timestamp-absolute, timestamp-relative, or numeric)")
f.Bool("outfile-autohex-disable", false, "disable outfile autohex")
f.Uint32("outfile-check-timer", 0, "outfile check timer interval (seconds)")
f.Bool("wordlist-autohex-disable", false, "disable wordlist autohex")
f.String("separator", "", "separator character")
f.Bool("stdout", false, "stdout output")
f.Bool("show", false, "show cracked hashes")
f.Bool("left", false, "show uncracked hashes")
f.Bool("username", false, "enable username handling")
f.Bool("remove", false, "remove cracked hashes")
f.Uint32("remove-timer", 0, "remove timer interval (seconds)")
f.Bool("potfile-disable", false, "disable potfile")
f.String("potfile", "", "potfile path or identifier")
f.String("encoding-from", "", "input encoding (iso-8859-15, utf-32le, or numeric)")
f.String("encoding-to", "", "output encoding (iso-8859-15, utf-32le, or numeric)")
f.Uint32("debug-mode", 0, "debug mode mask")
f.Bool("logfile-disable", false, "disable logfile")
f.Uint32("hccapx-message-pair", 0, "hccapx message pair")
f.Uint32("nonce-error-corrections", 0, "nonce error corrections")
f.String("keyboard-layout-mapping", "", "keyboard layout mapping file or identifier")
f.Bool("benchmark", false, "run benchmark")
f.Bool("benchmark-all", false, "run full benchmark")
f.Bool("speed-only", false, "show speed only")
f.Bool("progress-only", false, "show progress only")
f.Uint32("segment-size", 0, "segment size")
f.Uint32("bitmap-min", 0, "bitmap min")
f.Uint32("bitmap-max", 0, "bitmap max")
f.UintSlice("cpu-affinity", nil, "CPU affinity list")
f.Uint32("hook-threads", 0, "hook threads")
f.Bool("hash-info", false, "show hash information")
f.Bool("backend-ignore-cuda", false, "ignore CUDA backend")
f.Bool("backend-ignore-hip", false, "ignore HIP backend")
f.Bool("backend-ignore-metal", false, "ignore Metal backend")
f.Bool("backend-ignore-opencl", false, "ignore OpenCL backend")
f.Bool("backend-info", false, "show backend info")
f.UintSlice("backend-devices", nil, "backend device IDs")
f.UintSlice("opencl-device-types", nil, "OpenCL device types")
f.Bool("optimized-kernel-enable", false, "enable optimized kernels")
f.Bool("multiply-accel-disabled", false, "disable multiply accel")
f.String("workload-profile", "", "workload profile (low, default, high, nightmare, or numeric)")
f.Uint32("kernel-accel", 0, "kernel acceleration")
f.Uint32("kernel-loops", 0, "kernel loops")
f.Uint32("kernel-threads", 0, "kernel threads")
f.Uint32("backend-vector-width", 0, "backend vector width")
f.Uint32("spin-damp", 0, "spin damp")
f.Bool("hwmon-disable", false, "disable hardware monitoring")
f.Uint32("hwmon-temp-abort", 0, "hardware monitor temperature abort")
f.Uint32("scrypt-tmto", 0, "scrypt tmto")
f.Uint64("skip", 0, "skip count")
f.Uint64("limit", 0, "limit count")
f.Bool("keyspace", false, "show keyspace")
f.String("rules-file", "", "rules file path or identifier")
f.Uint32("generate-rules", 0, "generate rules")
f.Uint32("generate-rules-fun-min", 0, "generate rules function min")
f.Uint32("generate-rules-fun-max", 0, "generate rules function max")
f.String("generate-rules-func-sel", "", "generate rules function selection")
f.Int32("generate-rules-seed", 0, "generate rules seed")
f.String("custom-charset1", "", "custom charset 1")
f.String("custom-charset2", "", "custom charset 2")
f.String("custom-charset3", "", "custom charset 3")
f.String("custom-charset4", "", "custom charset 4")
f.String("identify", "", "identify hash")
f.Bool("increment", false, "incremental mode")
f.Uint32("increment-min", 0, "increment min")
f.Uint32("increment-max", 0, "increment max")
f.Bool("slow-candidates", false, "slow candidates")
f.Bool("brain-server", false, "brain server mode")
f.Uint32("brain-server-timer", 0, "brain server timer")
f.Bool("brain-client", false, "brain client mode")
f.String("brain-client-features", "", "brain client features")
f.String("brain-host", "", "brain host")
f.Uint32("brain-port", 0, "brain port")
f.String("brain-password", "", "brain password")
f.String("brain-session", "", "brain session")
f.String("brain-session-whitelist", "", "brain session whitelist")
}
+52 -9
View File
@@ -22,6 +22,7 @@ import (
"context"
"fmt"
"sort"
"time"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@@ -34,6 +35,37 @@ import (
// CrackCmd - GPU password cracking interface
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunCrack(cmd, args) {
crackCmd, err := buildCrackCommand(cmd, args)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
timeoutSeconds, _ := cmd.Flags().GetInt64("timeout")
ctx := context.Background()
if timeoutSeconds > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
}
resp, err := con.Rpc.Crack(ctx, crackCmd)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
if resp == nil || resp.Job == nil {
con.PrintInfof("Crack request submitted\n")
return
}
con.PrintInfof("Crack job %s created (status: %s)\n", resp.Job.ID, resp.Job.Status.String())
if resp.Job.Err != "" {
con.PrintErrorf("Crack job error: %s\n", resp.Job.Err)
}
return
}
if !AreCrackersOnline(con) {
PrintNoCrackstations(con)
} else {
@@ -64,10 +96,11 @@ func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
con.PrintErrorf("%s\n", err)
return
}
showBenchmarks, _ := cmd.Flags().GetBool("show-benchmarks")
if len(crackers.Crackstations) == 0 {
PrintNoCrackstations(con)
} else {
PrintCrackers(crackers.Crackstations, con)
PrintCrackers(crackers.Crackstations, con, showBenchmarks)
}
}
@@ -83,12 +116,12 @@ func AreCrackersOnline(con *console.SliverClient) bool {
return len(crackers.Crackstations) > 0
}
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient) {
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient, showBenchmarks bool) {
sort.Slice(crackers, func(i, j int) bool {
return crackers[i].Name < crackers[j].Name
})
for index, cracker := range crackers {
printCracker(cracker, index, con)
printCracker(cracker, index, con, showBenchmarks)
if index < len(crackers)-1 {
con.Println()
con.Println()
@@ -96,7 +129,7 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient)
}
}
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient) {
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
@@ -131,8 +164,10 @@ func printCracker(cracker *clientpb.Crackstation, index int, con *console.Sliver
}
}
con.Printf("%s\n", tw.Render())
con.Println()
printBenchmarks(cracker, con)
if showBenchmarks {
con.Println()
printBenchmarks(cracker, con)
}
}
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
@@ -140,9 +175,17 @@ func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient)
tw.SetStyle(settings.GetTableStyle(con))
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
tw.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
for hashType, speed := range cracker.Benchmarks {
tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
if len(cracker.Benchmarks) == 0 {
tw.AppendRow(table.Row{"No benchmarks reported", "-"})
} else {
for hashType, speed := range cracker.Benchmarks {
name, ok := hashcatHashTypeName(hashType)
if !ok {
name = clientpb.HashType(hashType).String()
}
tw.AppendRow(table.Row{name, humanizeHashRate(speed)})
}
}
con.Printf("%s\n", tw.Render())
}
+419
View File
@@ -0,0 +1,419 @@
package crack
import "fmt"
// Hashcat mode names sourced from client/credentials/EXAMPLES.md.
var hashcatHashTypes = map[int32]string{
0: "MD5",
10: "md5($pass.$salt)",
11: "Joomla < 2.5.18",
12: "PostgreSQL",
20: "md5($salt.$pass)",
21: "osCommerce, xt:Commerce",
22: "Juniper NetScreen/SSG (ScreenOS)",
23: "Skype",
24: "SolarWinds Serv-U",
30: "md5(utf16le($pass).$salt)",
40: "md5($salt.utf16le($pass))",
50: "HMAC-MD5 (key = $pass)",
60: "HMAC-MD5 (key = $salt)",
70: "md5(utf16le($pass))",
100: "SHA1",
101: "nsldap, SHA-1(Base64), Netscape LDAP SHA",
110: "sha1($pass.$salt)",
111: "nsldaps, SSHA-1(Base64), Netscape LDAP SSHA",
112: "Oracle S: Type (Oracle 11+)",
120: "sha1($salt.$pass)",
121: "SMF (Simple Machines Forum) > v1.1",
122: "macOS v10.4, macOS v10.5, macOS v10.6",
123: "EPi",
124: "Django (SHA-1)",
125: "ArubaOS",
130: "sha1(utf16le($pass).$salt)",
131: "MSSQL (2000)",
132: "MSSQL (2005)",
133: "PeopleSoft",
140: "sha1($salt.utf16le($pass))",
141: "Episerver 6.x < .NET 4",
150: "HMAC-SHA1 (key = $pass)",
160: "HMAC-SHA1 (key = $salt)",
170: "sha1(utf16le($pass))",
190: "sha1(LinkedIn)",
200: "MySQL323",
300: "MySQL4.1/MySQL5",
400: "phpass, phpBB3 (MD5)",
500: "md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)",
501: "Juniper IVE",
600: "BLAKE2b-512",
900: "MD4",
1000: "NTLM",
1100: "Domain Cached Credentials (DCC), MS Cache",
1300: "SHA2-224",
1400: "SHA2-256",
1410: "sha256($pass.$salt)",
1411: "SSHA-256(Base64), LDAP {SSHA256}",
1420: "sha256($salt.$pass)",
1421: "hMailServer",
1430: "sha256(utf16le($pass).$salt)",
1431: "base64(sha256(unicode($pass)))",
1440: "sha256($salt.utf16le($pass))",
1441: "Episerver 6.x >= .NET 4",
1450: "HMAC-SHA256 (key = $pass)",
1460: "HMAC-SHA256 (key = $salt)",
1470: "sha256(utf16le($pass))",
1500: "descrypt, DES (Unix), Traditional DES",
1600: "Apache $apr1$ MD5, md5apr1, MD5 (APR)",
1700: "SHA2-512",
1710: "sha512($pass.$salt)",
1711: "SSHA-512(Base64), LDAP {SSHA512}",
1720: "sha512($salt.$pass)",
1722: "macOS v10.7",
1730: "sha512(utf16le($pass).$salt)",
1731: "MSSQL (2012, 2014)",
1740: "sha512($salt.utf16le($pass))",
1750: "HMAC-SHA512 (key = $pass)",
1760: "HMAC-SHA512 (key = $salt)",
1770: "sha512(utf16le($pass))",
1800: "sha512crypt $6$, SHA512 (Unix)",
2000: "STDOUT",
2100: "Domain Cached Credentials 2 (DCC2), MS Cache 2",
2400: "Cisco-PIX MD5",
2410: "Cisco-ASA MD5",
2500: "WPA-EAPOL-PBKDF2",
2501: "WPA-EAPOL-PMK",
2600: "md5(md5($pass))",
2611: "vBulletin < v3.8.5",
2612: "PHPS",
2711: "vBulletin >= v3.8.5",
2811: "MyBB 1.2+, IPB2+ (Invision Power Board)",
3000: "LM",
3100: "Oracle H: Type (Oracle 7+)",
3200: "bcrypt $2\\*$, Blowfish (Unix)",
3300: "MD5(Sun)",
3500: "md5(md5(md5($pass)))",
3610: "md5(md5($salt).$pass )",
3710: "md5($salt.md5($pass))",
3711: "MediaWiki B type",
3720: "md5($pass.md5($salt))",
3721: "WebEdition CMS",
3800: "md5($salt.$pass.$salt)",
3910: "md5(md5($pass).md5($salt))",
4010: "md5($salt.md5($salt.$pass))",
4110: "md5($salt.md5($pass.$salt))",
4210: "md5($username.0.$pass)",
4300: "md5(strtoupper(md5($pass)))",
4400: "md5(sha1($pass))",
4500: "sha1(sha1($pass))",
4510: "sha1(sha1($pass).$salt)",
4520: "sha1($salt.sha1($pass))",
4521: "Redmine",
4522: "PunBB",
4600: "sha1(sha1(sha1($pass)))",
4700: "sha1(md5($pass))",
4710: "sha1(md5($pass).$salt)",
4711: "Huawei sha1(md5($pass).$salt)",
4800: "iSCSI CHAP authentication, MD5(CHAP)",
4900: "sha1($salt.$pass.$salt)",
5000: "sha1(sha1($salt.$pass.$salt))",
5100: "Half MD5",
5200: "Password Safe v3",
5300: "IKE-PSK MD5",
5400: "IKE-PSK SHA1",
5500: "NetNTLMv1 / NetNTLMv1+ESS",
5600: "NetNTLMv2",
5700: "Cisco-IOS type 4 (SHA256)",
5800: "Samsung Android Password/PIN",
6000: "RIPEMD-160",
6100: "Whirlpool",
6211: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish",
6212: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
6213: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
6221: "TrueCrypt 5.0+ SHA512 + AES / TrueCrypt 5.0+ SHA512 + Serpent / TrueCrypt 5.0+ SHA512 + Twofish",
6222: "TrueCrypt 5.0+ SHA512 + AES-Twofish / TrueCrypt 5.0+ SHA512 + Serpent-AES / TrueCrypt 5.0+ SHA512 + Twofish-Serpent",
6223: "TrueCrypt 5.0+ SHA512 + AES-Twofish-Serpent / TrueCrypt 5.0+ SHA512 + Serpent-Twofish-AES",
6231: "TrueCrypt 5.0+ Whirlpool + AES / TrueCrypt 5.0+ Whirlpool + Serpent / TrueCrypt 5.0+ Whirlpool + Twofish",
6232: "TrueCrypt 5.0+ Whirlpool + AES-Twofish / TrueCrypt 5.0+ Whirlpool + Serpent-AES / TrueCrypt 5.0+ Whirlpool + Twofish-Serpent",
6233: "TrueCrypt 5.0+ Whirlpool + AES-Twofish-Serpent / TrueCrypt 5.0+ Whirlpool + Serpent-Twofish-AES",
6241: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish + boot",
6242: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent + boot",
6243: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES + boot",
6300: "AIX {smd5}",
6400: "AIX {ssha256}",
6500: "AIX {ssha512}",
6600: "1Password, agilekeychain",
6700: "AIX {ssha1}",
6800: "LastPass + LastPass sniffed",
6900: "GOST R 34.11-94",
7000: "FortiGate (FortiOS)",
7100: "macOS v10.8+ (PBKDF2-SHA512)",
7200: "GRUB 2",
7300: "IPMI2 RAKP HMAC-SHA1",
7400: "sha256crypt $5$, SHA256 (Unix)",
7401: "MySQL $A$ (sha256crypt)",
7500: "Kerberos 5, etype 23, AS-REQ Pre-Auth",
7700: "SAP CODVN B (BCODE)",
7701: "SAP CODVN B (BCODE) from RFC_READ_TABLE",
7800: "SAP CODVN F/G (PASSCODE)",
7801: "SAP CODVN F/G (PASSCODE) from RFC_READ_TABLE",
7900: "Drupal7",
8000: "Sybase ASE",
8100: "Citrix NetScaler (SHA1)",
8200: "1Password, cloudkeychain",
8300: "DNSSEC (NSEC3)",
8400: "WBB3 (Woltlab Burning Board)",
8500: "RACF",
8600: "Lotus Notes/Domino 5",
8700: "Lotus Notes/Domino 6",
8800: "Android FDE <= 4.3",
8900: "scrypt",
9000: "Password Safe v2",
9100: "Lotus Notes/Domino 8",
9200: "Cisco-IOS $8$ (PBKDF2-SHA256)",
9300: "Cisco-IOS $9$ (scrypt)",
9400: "MS Office 2007",
9500: "MS Office 2010",
9600: "MS Office 2013",
9700: "MS Office <= 2003 MD5 + RC4, oldoffice$0, oldoffice$1",
9710: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #1",
9720: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #2",
9800: "MS Office <= 2003 SHA1 + RC4, oldoffice$3, oldoffice$4",
9810: "MS Office <= 2003 $3, SHA1 + RC4, collider #1",
9820: "MS Office <= 2003 $3, SHA1 + RC4, collider #2",
9900: "Radmin2",
10000: "Django (PBKDF2-SHA256)",
10100: "SipHash",
10200: "CRAM-MD5",
10300: "SAP CODVN H (PWDSALTEDHASH) iSSHA-1",
10400: "PDF 1.1 - 1.3 (Acrobat 2 - 4)",
10410: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #1",
10420: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #2",
10500: "PDF 1.4 - 1.6 (Acrobat 5 - 8)",
10600: "PDF 1.7 Level 3 (Acrobat 9)",
10700: "PDF 1.7 Level 8 (Acrobat 10 - 11)",
10800: "SHA2-384",
10810: "sha384($pass.$salt)",
10820: "sha384($salt.$pass)",
10830: "sha384(utf16le($pass).$salt)",
10840: "sha384($salt.utf16le($pass))",
10870: "sha384(utf16le($pass))",
10900: "PBKDF2-HMAC-SHA256",
10901: "RedHat 389-DS LDAP (PBKDF2-HMAC-SHA256)",
11000: "PrestaShop",
11100: "PostgreSQL CRAM (MD5)",
11200: "MySQL CRAM (SHA1)",
11300: "Bitcoin/Litecoin wallet.dat",
11400: "SIP digest authentication (MD5)",
11500: "CRC32",
11600: "7-Zip",
11700: "GOST R 34.11-2012 (Streebog) 256-bit, big-endian",
11750: "HMAC-Streebog-256 (key = $pass), big-endian",
11760: "HMAC-Streebog-256 (key = $salt), big-endian",
11800: "GOST R 34.11-2012 (Streebog) 512-bit, big-endian",
11850: "HMAC-Streebog-512 (key = $pass), big-endian",
11860: "HMAC-Streebog-512 (key = $salt), big-endian",
11900: "PBKDF2-HMAC-MD5",
12000: "PBKDF2-HMAC-SHA1",
12001: "Atlassian (PBKDF2-HMAC-SHA1)",
12100: "PBKDF2-HMAC-SHA512",
12200: "eCryptfs",
12300: "Oracle T: Type (Oracle 12+)",
12400: "BSDi Crypt, Extended DES",
12500: "RAR3-hp",
12600: "ColdFusion 10+",
12700: "Blockchain, My Wallet",
12800: "MS-AzureSync PBKDF2-HMAC-SHA256",
12900: "Android FDE (Samsung DEK)",
13000: "RAR5",
13100: "Kerberos 5, etype 23, TGS-REP",
13200: "AxCrypt 1",
13300: "AxCrypt 1 in-memory SHA1",
13400: "KeePass 1 AES / without keyfile / KeePass 2 AES / without keyfile / KeePass 1 Twofish / with keyfile / Keepass 2 AES / with keyfile",
13500: "PeopleSoft PS_TOKEN",
13600: "WinZip",
13711: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish",
13712: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES-Twofish / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
13713: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
13721: "VeraCrypt PBKDF2-HMAC-SHA512 + AES / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish",
13722: "VeraCrypt PBKDF2-HMAC-SHA512 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish-Serpent",
13723: "VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-Twofish-AES",
13731: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish",
13732: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES-Twofish / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish-Serpent",
13733: "VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-Twofish-AES",
13741: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES",
13742: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish",
13743: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish-Serpent",
13751: "VeraCrypt PBKDF2-HMAC-SHA256 + AES / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish",
13752: "VeraCrypt PBKDF2-HMAC-SHA256 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish-Serpent",
13753: "VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-Twofish-AES",
13761: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + PIM + AES",
13762: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-AES",
13763: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-Twofish-AES",
13771: "VeraCrypt Streebog-512 + XTS 512 bit",
13772: "VeraCrypt Streebog-512 + XTS 1024 bit",
13773: "VeraCrypt Streebog-512 + XTS 1536 bit",
13800: "Windows Phone 8+ PIN/password",
13900: "OpenCart",
14000: "DES (PT = $salt, key = $pass)",
14100: "3DES (PT = $salt, key = $pass)",
14400: "sha1(CX)",
14500: "Linux Kernel Crypto API (2.4)",
14600: "LUKS",
14700: "iTunes backup < 10.0",
14800: "iTunes backup >= 10.0",
14900: "Skip32 (PT = $salt, key = $pass)",
15000: "FileZilla Server >= 0.9.55",
15100: "Juniper/NetBSD sha1crypt",
15200: "Blockchain, My Wallet, V2",
15300: "DPAPI masterkey file v1 + local context",
15400: "ChaCha20",
15500: "JKS Java Key Store Private Keys (SHA1)",
15600: "Ethereum Wallet, PBKDF2-HMAC-SHA256",
15700: "Ethereum Wallet, SCRYPT",
15900: "DPAPI masterkey file v2 + Active Directory domain context",
16000: "Tripcode",
16100: "TACACS+",
16200: "Apple Secure Notes",
16300: "Ethereum Pre-Sale Wallet, PBKDF2-HMAC-SHA256",
16400: "CRAM-MD5 Dovecot",
16500: "JWT (JSON Web Token)",
16600: "Electrum Wallet (Salt-Type 1-3)",
16700: "FileVault 2",
16800: "WPA-PMKID-PBKDF2",
16801: "WPA-PMKID-PMK",
16900: "Ansible Vault",
17200: "PKZIP (Compressed)",
17210: "PKZIP (Uncompressed)",
17220: "PKZIP (Compressed Multi-File)",
17225: "PKZIP (Mixed Multi-File)",
17230: "PKZIP (Mixed Multi-File Checksum-Only)",
17300: "SHA3-224",
17400: "SHA3-256",
17500: "SHA3-384",
17600: "SHA3-512",
17700: "Keccak-224",
17800: "Keccak-256",
17900: "Keccak-384",
18000: "Keccak-512",
18100: "TOTP (HMAC-SHA1)",
18200: "Kerberos 5, etype 23, AS-REP",
18300: "Apple File System (APFS)",
18400: "Open Document Format (ODF) 1.2 (SHA-256, AES)",
18500: "sha1(md5(md5($pass)))",
18600: "Open Document Format (ODF) 1.1 (SHA-1, Blowfish)",
18700: "Java Object hashCode()",
18800: "Blockchain, My Wallet, Second Password (SHA256)",
18900: "Android Backup",
19000: "QNX /etc/shadow (MD5)",
19100: "QNX /etc/shadow (SHA256)",
19200: "QNX /etc/shadow (SHA512)",
19300: "sha1($salt1.$pass.$salt2)",
19500: "Ruby on Rails Restful-Authentication",
19600: "Kerberos 5, etype 17, TGS-REP (AES128-CTS-HMAC-SHA1-96)",
19700: "Kerberos 5, etype 18, TGS-REP (AES256-CTS-HMAC-SHA1-96)",
19800: "Kerberos 5, etype 17, Pre-Auth",
19900: "Kerberos 5, etype 18, Pre-Auth",
20011: "DiskCryptor SHA512 + XTS 512 bit (AES) / DiskCryptor SHA512 + XTS 512 bit (Twofish) / DiskCryptor SHA512 + XTS 512 bit (Serpent)",
20012: "DiskCryptor SHA512 + XTS 1024 bit (AES-Twofish) / DiskCryptor SHA512 + XTS 1024 bit (Twofish-Serpent) / DiskCryptor SHA512 + XTS 1024 bit (Serpent-AES)",
20013: "DiskCryptor SHA512 + XTS 1536 bit (AES-Twofish-Serpent)",
20200: "Python passlib pbkdf2-sha512",
20300: "Python passlib pbkdf2-sha256",
20400: "Python passlib pbkdf2-sha1",
20500: "PKZIP Master Key",
20510: "PKZIP Master Key (6 byte optimization)",
20600: "Oracle Transportation Management (SHA256)",
20710: "sha256(sha256($pass).$salt)",
20711: "AuthMe sha256",
20720: "sha256($salt.sha256($pass))",
20800: "sha256(md5($pass))",
20900: "md5(sha1($pass).md5($pass).sha1($pass))",
21000: "BitShares v0.x - sha512(sha512_bin(pass))",
21100: "sha1(md5($pass.$salt))",
21200: "md5(sha1($salt).md5($pass))",
21300: "md5($salt.sha1($salt.$pass))",
21400: "sha256(sha256_bin($pass))",
21500: "SolarWinds Orion",
21501: "SolarWinds Orion v2",
21600: "Web2py pbkdf2-sha512",
21700: "Electrum Wallet (Salt-Type 4)",
21800: "Electrum Wallet (Salt-Type 5)",
22000: "WPA-PBKDF2-PMKID+EAPOL",
22001: "WPA-PMK-PMKID+EAPOL",
22100: "BitLocker",
22200: "Citrix NetScaler (SHA512)",
22300: "sha256($salt.$pass.$salt)",
22301: "Telegram Mobile App Passcode (SHA256)",
22400: "AES Crypt (SHA256)",
22500: "MultiBit Classic .key (MD5)",
22600: "Telegram Desktop < v2.1.14 (PBKDF2-HMAC-SHA1)",
22700: "MultiBit HD (scrypt)",
22911: "RSA/DSA/EC/OpenSSH Private Keys ($0$)",
22921: "RSA/DSA/EC/OpenSSH Private Keys ($6$)",
22931: "RSA/DSA/EC/OpenSSH Private Keys ($1, $3$)",
22941: "RSA/DSA/EC/OpenSSH Private Keys ($4$)",
22951: "RSA/DSA/EC/OpenSSH Private Keys ($5$)",
23001: "SecureZIP AES-128",
23002: "SecureZIP AES-192",
23003: "SecureZIP AES-256",
23100: "Apple Keychain",
23200: "XMPP SCRAM PBKDF2-SHA1",
23300: "Apple iWork",
23400: "Bitwarden",
23500: "AxCrypt 2 AES-128",
23600: "AxCrypt 2 AES-256",
23700: "RAR3-p (Uncompressed)",
23800: "RAR3-p (Compressed)",
23900: "BestCrypt v3 Volume Encryption",
24100: "MongoDB ServerKey SCRAM-SHA-1",
24200: "MongoDB ServerKey SCRAM-SHA-256",
24300: "sha1($salt.sha1($pass.$salt))",
24410: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA1 + 3DES/AES)",
24420: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA256 + 3DES/AES)",
24500: "Telegram Desktop >= v2.1.14 (PBKDF2-HMAC-SHA512)",
24600: "SQLCipher",
24700: "Stuffit5",
24800: "Umbraco HMAC-SHA1",
24900: "Dahua Authentication MD5",
25300: "MS Office 2016 - SheetProtection",
25400: "PDF 1.4 - 1.6 (Acrobat 5 - 8) - edit password",
25500: "Stargazer Stellar Wallet XLM",
25600: "bcrypt(md5($pass)) / bcryptmd5",
25700: "MurmurHash",
25800: "bcrypt(sha1($pass)) / bcryptsha1",
25900: "KNX IP Secure - Device Authentication Code",
26000: "Mozilla key3.db",
26100: "Mozilla key4.db",
26200: "OpenEdge Progress Encode",
26300: "FortiGate256 (FortiOS256)",
26401: "AES-128-ECB NOKDF (PT = $salt, key = $pass)",
26402: "AES-192-ECB NOKDF (PT = $salt, key = $pass)",
26403: "AES-256-ECB NOKDF (PT = $salt, key = $pass)",
26500: "iPhone passcode (UID key + System Keybag)",
26600: "MetaMask Wallet",
99999: "Plaintext",
}
func hashcatHashTypeName(mode int32) (string, bool) {
name, ok := hashcatHashTypes[mode]
return name, ok
}
func humanizeHashRate(rate uint64) string {
const unit = 1000
if rate < unit {
return fmt.Sprintf("%d H/s", rate)
}
value := float64(rate)
exp := 0
for value >= unit && exp < 5 {
value /= unit
exp++
}
suffixes := []string{"kH/s", "MH/s", "GH/s", "TH/s", "PH/s"}
if exp == 0 {
return fmt.Sprintf("%d H/s", rate)
}
if exp > len(suffixes) {
exp = len(suffixes)
}
return fmt.Sprintf("%.2f %s", value, suffixes[exp-1])
}
-1
View File
@@ -82,7 +82,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
f.BoolP("no-canaries", "c", false, "disable dns canary detection")
f.StringP("lhost", "L", "", "interface to bind server to")
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
f.BoolP("disable-otp", "D", false, "disable otp authentication")
})
// HTTP
+4 -6
View File
@@ -40,15 +40,13 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string
lhost, _ := cmd.Flags().GetString("lhost")
lport, _ := cmd.Flags().GetUint32("lport")
canaries, _ := cmd.Flags().GetBool("no-canaries")
enforceOTP, _ := cmd.Flags().GetBool("disable-otp")
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
dns, err := con.Rpc.StartDNSListener(context.Background(), &clientpb.DNSListenerReq{
Domains: domains,
Host: lhost,
Port: lport,
Canaries: !canaries,
EnforceOTP: !enforceOTP,
Domains: domains,
Host: lhost,
Port: lport,
Canaries: !canaries,
})
con.Println()
if err != nil {
+30
View File
@@ -0,0 +1,30 @@
package transport
import (
"sync"
"google.golang.org/grpc/keepalive"
)
var (
kaMu sync.RWMutex
kaParams *keepalive.ClientParameters
)
// SetKeepaliveParams configures gRPC keepalive pings for future MTLSConnect dials.
// Behavior is unchanged unless this is called.
func SetKeepaliveParams(p keepalive.ClientParameters) {
kaMu.Lock()
kaParams = &p
kaMu.Unlock()
}
func getKeepaliveParams() (keepalive.ClientParameters, bool) {
kaMu.RLock()
p := kaParams
kaMu.RUnlock()
if p == nil {
return keepalive.ClientParameters{}, false
}
return *p, true
}
+28 -6
View File
@@ -28,6 +28,7 @@ import (
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials"
"github.com/bishopfox/sliver/client/assets"
@@ -71,15 +72,36 @@ func MTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.Clie
options := []grpc.DialOption{
grpc.WithTransportCredentials(transportCreds),
grpc.WithPerRPCCredentials(callCreds),
grpc.WithBlock(),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(ClientMaxReceiveMessageSize)),
}
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
connection, err := grpc.DialContext(ctx, fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
if kp, ok := getKeepaliveParams(); ok {
options = append(options, grpc.WithKeepaliveParams(kp))
}
connection, err := grpc.NewClient(fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
if err != nil {
return nil, nil, err
}
// Preserve the legacy grpc.DialContext + grpc.WithBlock behavior: block
// until the channel becomes READY or the timeout expires.
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
for {
state := connection.GetState()
if state == connectivity.Idle {
connection.Connect()
}
if state == connectivity.Ready {
break
}
if !connection.WaitForStateChange(ctx, state) {
_ = connection.Close()
return nil, nil, ctx.Err()
}
}
return rpcpb.NewSliverRPCClient(connection), connection, nil
}
@@ -120,7 +142,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
cert, err := x509.ParseCertificate(rawCerts[0]) // We should only get one cert
if err != nil {
log.Printf("Failed to parse certificate: " + err.Error())
log.Printf("Failed to parse certificate: %v", err)
return err
}
@@ -134,7 +156,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
panic("no root certificate")
}
if _, err := cert.Verify(options); err != nil {
log.Printf("Failed to verify certificate: " + err.Error())
log.Printf("Failed to verify certificate: %v", err)
return err
}
@@ -15,6 +15,12 @@ The Sliver server has a configuration file located in the `configs` sub-director
"level": 4,
"grpc_unary_payloads": false,
"grpc_stream_payloads": false
},
"grpc": {
"keepalive": {
"min_time_seconds": 30,
"permit_without_stream": true
}
}
}
```
@@ -29,6 +35,10 @@ The Sliver server has a configuration file located in the `configs` sub-director
- `level` - The `logrus` logging level (1 - 5), by default this is set to `4`, which is the `INFO` level; increasing this value increases the verbosity of the logs e.g. `DEBUG` is `5` and `SILENT` is `1`
- `grpc_unary_payloads` - Log gRPC unary payloads
- `grpc_stream_payloads` - Log gRPC streaming payloads
- `grpc` - An object containing options for the server gRPC stack
- `keepalive` - gRPC keepalive enforcement configuration
- `min_time_seconds` - Minimum amount of time (in seconds) between client pings before the server sends a GOAWAY with `too_many_pings`
- `permit_without_stream` - Allow client pings when there are no active streams
## Database Configuration
@@ -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
}
}
}
```
+9
View File
@@ -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
@@ -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{
File diff suppressed because it is too large. Load diff
+15
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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 (
+3 -6
View File
@@ -4,7 +4,7 @@ option go_package = "github.com/bishopfox/sliver/protobuf/dnspb";
enum DNSMessageType {
NOP = 0; // aka FINGERPRINT
TOTP = 1;
reserved 1; // formerly TOTP
INIT = 2;
POLL = 3;
CLOSE = 4;
@@ -17,10 +17,7 @@ enum DNSMessageType {
/*
NOTE: DNS is very space sensitive so certain fields are re-purposed
depending on the DNSMessageType as noted below:
[Type TOTP]: ID field is used for the TOTP code
depending on the DNSMessageType.
*/
message DNSMessage {
DNSMessageType Type = 1;
@@ -29,4 +26,4 @@ message DNSMessage {
uint32 Stop = 4; // Bytes stop at
uint32 Size = 5; // Total size
bytes Data = 6; // Actual data
}
}
File diff suppressed because it is too large. Load diff
+1
View File
@@ -112,6 +112,7 @@ service SliverRPC {
returns (clientpb.CertificateAuthorityInfo);
// *** Crackstation ***
rpc Crack(clientpb.CrackCommand) returns (clientpb.CrackResponse);
rpc CrackstationRegister(clientpb.Crackstation)
returns (stream clientpb.Event);
rpc CrackstationTrigger(clientpb.Event) returns (commonpb.Empty);
+36
View File
@@ -98,6 +98,7 @@ type SliverRPCClient interface {
GetCertificateInfo(ctx context.Context, in *clientpb.CertificatesReq, opts ...grpc.CallOption) (*clientpb.CertificateInfo, error)
GetCertificateAuthorityInfo(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.CertificateAuthorityInfo, error)
// *** Crackstation ***
Crack(ctx context.Context, in *clientpb.CrackCommand, opts ...grpc.CallOption) (*clientpb.CrackResponse, error)
CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error)
CrackstationTrigger(ctx context.Context, in *clientpb.Event, opts ...grpc.CallOption) (*commonpb.Empty, error)
CrackstationBenchmark(ctx context.Context, in *clientpb.CrackBenchmark, opts ...grpc.CallOption) (*commonpb.Empty, error)
@@ -824,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 {
@@ -2120,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)
@@ -2441,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")
}
@@ -3902,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 {
@@ -6409,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,
+1 -1
View File
@@ -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: sliverpb/sliver.proto
package sliverpb
+185 -66
View File
@@ -21,14 +21,12 @@ package c2
We've put a little effort to making the server at least not super easily fingerprintable,
though I'm guessing it's also still not super hard to do. The server must receive a valid
TOTP code before we start returning any non-error records. All requests must be formatted
as valid protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a
8 bit "message ID." The server only responds to non-TOTP queries with valid dns session IDs
16,777,216 can probably be bruteforced but it'll at least be slow.
protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a 8 bit
"message ID." 16,777,216 can probably be bruteforced but it'll at least be slow.
DNS command and control outline:
1. Implant sends TOTP encoded message to DNS server, server checks validity
2. DNS server responds with the "DNS Session ID" which is just some random value
1. Implant generates a random DNS Session ID and sends an INIT (Age key exchange)
2. DNS server validates INIT and allocates session state
3. Requests with valid DNS session IDs enable the server to respond with CRC32 responses
4. Implant establishes encrypted session
@@ -44,6 +42,7 @@ import (
"sort"
"strings"
"sync"
"sync/atomic"
"time"
consts "github.com/bishopfox/sliver/client/constants"
@@ -66,6 +65,17 @@ const (
messageIDBitMask = 0xff000000 // Bitwise mask to get the message ID
defaultMaxTXTLength = 254
// Upper bound on unauthenticated init reassembly state. INIT is split across multiple
// DNS queries, so we cap the number of pending init messages to avoid unbounded memory use.
defaultMaxPendingDNSInits = 1024
// Pending INIT messages are expected to complete quickly. If they don't, expire them.
defaultPendingDNSInitTTL = 2 * time.Minute
defaultPendingDNSInitGCInterval = 30 * time.Second
// INIT carries only key-exchange material and should remain small.
defaultMaxDNSInitSize = 16 * 1024
)
var (
@@ -78,7 +88,7 @@ var (
)
// StartDNSListener - Start a DNS listener
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool, enforceOTP bool) *SliverDNSServer {
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool) *SliverDNSServer {
// StartPivotListener()
server := &SliverDNSServer{
server: &dns.Server{Addr: fmt.Sprintf("%s:%d", bindIface, lport), Net: "udp"},
@@ -86,8 +96,8 @@ func StartDNSListener(bindIface string, lport uint16, domains []string, canaries
messages: &sync.Map{}, // In progress message streams
TTL: 0,
MaxTXTLength: defaultMaxTXTLength,
EnforceOTP: enforceOTP,
}
server.startInitGC()
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
started := time.Now()
@@ -103,6 +113,9 @@ type DNSSession struct {
ImplantConn *core.ImplantConnection
CipherCtx *cryptography.CipherContext
Created time.Time
lastSeen atomic.Int64 // unix nanos
dnsIdMsgIdMap map[uint32]uint32
outgoingMsgIDs []uint32
outgoingBuffers map[uint32][]byte
@@ -113,6 +126,10 @@ type DNSSession struct {
msgCount uint32
}
func (s *DNSSession) touch(now time.Time) {
s.lastSeen.Store(now.UnixNano())
}
func (s *DNSSession) msgID(id uint32) uint32 {
return uint32(id<<24) | uint32(s.ID)
}
@@ -256,6 +273,13 @@ type PendingEnvelope struct {
complete bool
}
// pendingInit tracks the reassembly state for an INIT message (which may arrive
// split across multiple DNS queries).
type pendingInit struct {
Created time.Time
Envelope *PendingEnvelope
}
// Reassemble - Reassemble a completed message
func (p *PendingEnvelope) Reassemble() ([]byte, error) {
// There's some weird race here with a nil pointer
@@ -316,11 +340,23 @@ type SliverDNSServer struct {
messages *sync.Map
TTL uint32
MaxTXTLength int
EnforceOTP bool
pendingInits atomic.Int64
gcStopOnce sync.Once
gcStop chan struct{}
gcDone chan struct{}
}
// Shutdown - Shutdown the DNS server
func (s *SliverDNSServer) Shutdown() error {
s.gcStopOnce.Do(func() {
if s.gcStop != nil {
close(s.gcStop)
if s.gcDone != nil {
<-s.gcDone
}
}
})
return s.server.Shutdown()
}
@@ -329,6 +365,56 @@ func (s *SliverDNSServer) ListenAndServe() error {
return s.server.ListenAndServe()
}
func (s *SliverDNSServer) startInitGC() {
// Tests construct SliverDNSServer directly; only start GC for real listeners.
if s.gcStop != nil {
return
}
s.gcStop = make(chan struct{})
s.gcDone = make(chan struct{})
go func() {
ticker := time.NewTicker(defaultPendingDNSInitGCInterval)
defer ticker.Stop()
defer close(s.gcDone)
for {
select {
case <-ticker.C:
s.gcPendingInits()
case <-s.gcStop:
return
}
}
}()
}
func (s *SliverDNSServer) gcPendingInits() {
now := time.Now()
var deleted int64
s.messages.Range(func(key, value any) bool {
p, ok := value.(*pendingInit)
if !ok || p == nil {
if _, loaded := s.messages.LoadAndDelete(key); loaded {
deleted++
}
return true
}
created := p.Created
if created.IsZero() {
created = now
}
if now.Sub(created) > defaultPendingDNSInitTTL {
if _, loaded := s.messages.LoadAndDelete(key); loaded {
deleted++
}
}
return true
})
if deleted > 0 {
s.pendingInits.Add(-deleted)
}
}
// ---------------------------
// DNS Handler
// ---------------------------
@@ -354,6 +440,9 @@ func (s *SliverDNSServer) HandleDNSRequest(domains []string, canaries bool, writ
resp = s.handleCanary(req)
}
if resp != nil {
// These responses often have near-max-length QNAMEs. Enable compression to
// keep UDP responses under the minimum DNS message size limits.
resp.Compress = true
writer.WriteMsg(resp)
} else {
dnsLog.Infof("Invalid query, no DNS response")
@@ -384,24 +473,26 @@ func (s *SliverDNSServer) handleC2(domain string, req *dns.Msg) *dns.Msg {
return s.nameErrorResp(req)
}
// TOTP Handler can be called without dns session ID
if msg.Type == dnspb.DNSMessageType_TOTP {
return s.handleHello(domain, msg, req)
// INIT can be called without an existing server-side session. The server only
// stores session state after validating the key exchange.
if msg.Type == dnspb.DNSMessageType_INIT {
return s.handleDNSSessionInit(domain, msg, checksum, req)
}
// All other handlers require a valid dns session ID
_, ok := s.sessions.Load(msg.ID & sessionIDBitMask)
sessionValue, ok := s.sessions.Load(msg.ID & sessionIDBitMask)
if !ok {
dnsLog.Warnf("[dns] session not found for id %v (%v)", msg.ID, msg.ID&sessionIDBitMask)
return s.nameErrorResp(req)
}
if session, ok := sessionValue.(*DNSSession); ok && session != nil {
session.touch(time.Now())
}
// Msg Type -> Handler
switch msg.Type {
case dnspb.DNSMessageType_NOP:
return s.handleNOP(domain, msg, checksum, req)
case dnspb.DNSMessageType_INIT:
return s.handleDNSSessionInit(domain, msg, checksum, req)
case dnspb.DNSMessageType_POLL:
return s.handlePoll(domain, msg, checksum, req)
case dnspb.DNSMessageType_DATA_FROM_IMPLANT:
@@ -456,81 +547,86 @@ func (s *SliverDNSServer) accumulateInitData(msg *dnspb.DNSMessage) ([]byte, boo
if msg.Size == 0 {
return nil, false, ErrInvalidMsg
}
if msg.Size > defaultMaxDNSInitSize {
return nil, false, ErrInvalidMsg
}
pendingValue, loaded := s.messages.Load(msg.ID)
if !loaded {
pendingValue = &PendingEnvelope{
Size: msg.Size,
received: uint32(0),
messages: map[uint32][]byte{},
mutex: &sync.Mutex{},
complete: false,
// Enforce a hard cap on pending INIT messages to prevent unbounded allocation.
next := s.pendingInits.Add(1)
if int64(defaultMaxPendingDNSInits) > 0 && next > int64(defaultMaxPendingDNSInits) {
s.pendingInits.Add(-1)
return nil, false, ErrInvalidMsg
}
pendingValue = &pendingInit{
Created: time.Now(),
Envelope: &PendingEnvelope{
Size: msg.Size,
received: uint32(0),
messages: map[uint32][]byte{},
mutex: &sync.Mutex{},
complete: false,
},
}
actual, loaded := s.messages.LoadOrStore(msg.ID, pendingValue)
if loaded {
// Another goroutine won the race. Roll back our pending counter.
s.pendingInits.Add(-1)
pendingValue = actual
}
actual, _ := s.messages.LoadOrStore(msg.ID, pendingValue)
pendingValue = actual
}
pending := pendingValue.(*PendingEnvelope)
p, ok := pendingValue.(*pendingInit)
if !ok || p == nil || p.Envelope == nil {
s.deletePendingInit(msg.ID)
return nil, false, ErrInvalidMsg
}
pending := p.Envelope
if pending.Size != msg.Size && msg.Size != 0 {
s.messages.Delete(msg.ID)
s.deletePendingInit(msg.ID)
return nil, false, ErrInvalidMsg
}
if !pending.Insert(msg) {
return nil, false, nil
}
if pending.received > pending.Size {
s.deletePendingInit(msg.ID)
return nil, false, ErrInvalidMsg
}
data, err := pending.Reassemble()
s.messages.Delete(msg.ID)
s.deletePendingInit(msg.ID)
if err != nil {
return nil, false, err
}
return data, true, nil
}
// ---------------------------
// DNS Message Handlers
// ---------------------------
func (s *SliverDNSServer) handleHello(domain string, msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
dnsLog.Debugf("[dns] totp request: %v", msg)
dnsSessionID := dnsSessionID()
dnsLog.Debugf("[dns] Assigned new dns session id = %d", dnsSessionID&sessionIDBitMask)
s.sessions.Store(dnsSessionID&sessionIDBitMask, &DNSSession{
ID: dnsSessionID & sessionIDBitMask,
dnsIdMsgIdMap: map[uint32]uint32{},
outgoingMsgIDs: []uint32{},
outgoingBuffers: map[uint32][]byte{},
outgoingMutex: &sync.RWMutex{},
incomingEnvelopes: map[uint32]*PendingEnvelope{},
incomingMutex: &sync.Mutex{},
msgCount: uint32(0),
})
resp := new(dns.Msg)
resp.SetReply(req)
resp.Authoritative = true
respBuf := make([]byte, 4)
binary.LittleEndian.PutUint32(respBuf, dnsSessionID)
for _, q := range req.Question {
switch q.Qtype {
case dns.TypeA:
a := &dns.A{
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.TTL},
A: respBuf,
}
resp.Answer = append(resp.Answer, a)
}
func (s *SliverDNSServer) deletePendingInit(id uint32) {
if _, loaded := s.messages.LoadAndDelete(id); loaded {
s.pendingInits.Add(-1)
}
return resp
}
func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
dnsLog.Debugf("[session init] with dns session id %d", msg.ID&sessionIDBitMask)
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
dnsSession := loadSession.(*DNSSession)
sessionID := msg.ID & sessionIDBitMask
dnsLog.Debugf("[session init] with dns session id %d", sessionID)
if sessionID == 0 {
dnsLog.Warnf("[session init] invalid dns session id")
return s.refusedErrorResp(req)
}
if msg.Size <= 32 {
dnsLog.Warnf("[session init] invalid msg data length")
return s.refusedErrorResp(req)
}
if dnsSession.CipherCtx != nil {
dnsLog.Warnf("[session init] session is already initialized")
if existing, ok := s.sessions.Load(sessionID); ok {
// Defensive: INIT should only be called once per session ID.
if dnsSession, ok := existing.(*DNSSession); ok && dnsSession != nil && dnsSession.CipherCtx != nil {
dnsLog.Warnf("[session init] session is already initialized")
return s.refusedErrorResp(req)
}
dnsLog.Warnf("[session init] session already exists")
return s.refusedErrorResp(req)
}
@@ -564,7 +660,27 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
return s.refusedErrorResp(req)
}
now := time.Now()
dnsSession := &DNSSession{
ID: sessionID,
Created: now,
dnsIdMsgIdMap: map[uint32]uint32{},
outgoingMsgIDs: []uint32{},
outgoingBuffers: map[uint32][]byte{},
outgoingMutex: &sync.RWMutex{},
incomingEnvelopes: map[uint32]*PendingEnvelope{},
incomingMutex: &sync.Mutex{},
msgCount: uint32(0),
}
dnsSession.touch(now)
dnsSession.ImplantConn = core.NewImplantConnection("dns", "n/a")
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
if _, loaded := s.sessions.LoadOrStore(sessionID, dnsSession); loaded {
dnsLog.Warnf("[session init] session id collision for %d", sessionID)
return s.refusedErrorResp(req)
}
go func() {
dnsLog.Debugf("[dns] starting implant conn send loop")
for envelope := range dnsSession.ImplantConn.Send {
@@ -572,7 +688,6 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
}
dnsLog.Debugf("[dns] closing implant conn send loop")
}()
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, dnsSession.ID)
respData, err := dnsSession.CipherCtx.Encrypt(buf)
@@ -628,6 +743,7 @@ func (s *SliverDNSServer) handlePoll(domain string, msg *dnspb.DNSMessage, check
dnsLog.Debugf("[poll] with dns session id %d", msg.ID&sessionIDBitMask)
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
dnsSession := loadSession.(*DNSSession)
dnsSession.touch(time.Now())
msgID, msgLen, err := dnsSession.PopOutgoingMsgID(msg)
if err != nil {
@@ -709,6 +825,7 @@ func (s *SliverDNSServer) handleDataFromImplant(domain string, msg *dnspb.DNSMes
dnsLog.Debugf("[from implant] dns session id %d", msg.ID&sessionIDBitMask)
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
dnsSession := loadSession.(*DNSSession)
dnsSession.touch(time.Now())
dnsLog.Debugf("[from implant] msg id: %d, size: %d", msg.ID, msg.Size)
pending := dnsSession.IncomingPendingEnvelope(msg.ID, msg.Size)
complete := pending.Insert(msg)
@@ -739,6 +856,7 @@ func (s *SliverDNSServer) handleDataToImplant(domain string, msg *dnspb.DNSMessa
dnsLog.Debugf("[to implant] dns session id %d", msg.ID&sessionIDBitMask)
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
dnsSession := loadSession.(*DNSSession)
dnsSession.touch(time.Now())
data, err := dnsSession.OutgoingRead(msg.ID, msg.Start, msg.Stop)
if err != nil {
@@ -798,6 +916,7 @@ func (s *SliverDNSServer) handleClear(domain string, msg *dnspb.DNSMessage, chec
dnsLog.Debugf("[clear] dns session id %d", msg.ID&sessionIDBitMask)
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
dnsSession := loadSession.(*DNSSession)
dnsSession.touch(time.Now())
dnsSession.ClearOutgoingEnvelope(msg.ID)
respBuf := make([]byte, 4)
+309
View File
@@ -0,0 +1,309 @@
//go:build server && go_sqlite && sliver_e2e
package c2_test
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"net"
"os"
"runtime"
"strconv"
"testing"
"time"
implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
implantHandlers "github.com/bishopfox/sliver/implant/sliver/handlers"
"github.com/bishopfox/sliver/implant/sliver/transports/dnsclient"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/bishopfox/sliver/protobuf/rpcpb"
"github.com/bishopfox/sliver/protobuf/sliverpb"
"github.com/bishopfox/sliver/server/certs"
"github.com/bishopfox/sliver/server/core"
serverCrypto "github.com/bishopfox/sliver/server/cryptography"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
"github.com/bishopfox/sliver/server/transport"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
)
const dnsE2EEchoMsgType = uint32(0x7fffffff)
func TestDNS_EndToEndPingRPC(t *testing.T) {
// NOTE: If you run this test in a restricted environment where writes to
// `~/.sliver` are blocked, set `SLIVER_ROOT_DIR` to a writable temp dir.
t.Cleanup(func() {
for _, session := range core.Sessions.All() {
core.Sessions.Remove(session.ID)
}
})
setupDNSKeyExImplantBuild(t)
grpcServer, grpcListener, err := transport.LocalListener()
if err != nil {
t.Fatalf("start local grpc listener: %v", err)
}
t.Cleanup(func() {
grpcServer.Stop()
_ = grpcListener.Close()
})
rpcConn, err := dialBufConn(context.Background(), grpcListener)
if err != nil {
t.Fatalf("dial grpc/bufconn: %v", err)
}
t.Cleanup(func() { _ = rpcConn.Close() })
rpcClient := rpcpb.NewSliverRPCClient(rpcConn)
dnsParent := "e2e.example.com."
dnsPort := freeUDPPort(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
dnsListener, err := rpcClient.StartDNSListener(ctx, &clientpb.DNSListenerReq{
Domains: []string{dnsParent},
Canaries: false,
Host: "127.0.0.1",
Port: dnsPort,
})
if err != nil {
t.Fatalf("StartDNSListener: %v", err)
}
t.Cleanup(func() {
if job := core.Jobs.Get(int(dnsListener.JobID)); job != nil {
job.JobCtrl <- true
}
_ = db.DeleteListener(dnsListener.JobID)
})
// Give the UDP listener a moment to bind before the implant starts sending queries.
time.Sleep(50 * time.Millisecond)
stopImplant := startTestDNSImplant(t, dnsParent, dnsPort)
t.Cleanup(stopImplant)
sessionID := waitForSessionID(t, 10*time.Second)
pingReq := &sliverpb.Ping{
Nonce: 31337,
Request: &commonpb.Request{
SessionID: sessionID,
Timeout: int64(5 * time.Second),
},
}
pingResp, err := rpcClient.Ping(ctx, pingReq)
if err != nil {
t.Fatalf("Ping: %v", err)
}
if pingResp.Nonce != pingReq.Nonce {
t.Fatalf("ping nonce mismatch: %d != %d", pingResp.Nonce, pingReq.Nonce)
}
// Extra validation: send a larger request/response directly via the session to
// force message fragmentation and reassembly over the DNS C2 transport.
session := core.Sessions.Get(sessionID)
if session == nil {
t.Fatalf("session not found: %s", sessionID)
}
large := make([]byte, 8192)
if _, err := rand.Read(large); err != nil {
t.Fatalf("rand.Read: %v", err)
}
resp, err := session.Request(dnsE2EEchoMsgType, 20*time.Second, large)
if err != nil {
t.Fatalf("session.Request(echo): %v", err)
}
if !bytesEqual(resp, large) {
t.Fatalf("echo response mismatch: %d != %d bytes", len(resp), len(large))
}
}
func bytesEqual(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func freeUDPPort(t *testing.T) uint32 {
t.Helper()
conn, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen udp: %v", err)
}
defer conn.Close()
_, portStr, err := net.SplitHostPort(conn.LocalAddr().String())
if err != nil {
t.Fatalf("split udp addr: %v", err)
}
port, err := strconv.Atoi(portStr)
if err != nil {
t.Fatalf("parse udp port: %v", err)
}
if port <= 0 || 65535 < port {
t.Fatalf("unexpected udp port: %d", port)
}
return uint32(port)
}
func setupDNSKeyExImplantBuild(t *testing.T) {
t.Helper()
certs.SetupCAs()
serverAgeKeyPair := serverCrypto.AgeServerKeyPair()
peerAgeKeyPair, err := serverCrypto.RandomAgeKeyPair()
if err != nil {
t.Fatalf("random age key pair: %v", err)
}
implantCrypto.SetSecrets(
peerAgeKeyPair.Public,
peerAgeKeyPair.Private,
"",
serverAgeKeyPair.Public,
serverCrypto.MinisignServerPublicKey(),
)
digest := sha256.Sum256([]byte(peerAgeKeyPair.Public))
publicKeyDigest := hex.EncodeToString(digest[:])
implantBuild := &models.ImplantBuild{
Name: "test-" + publicKeyDigest,
PeerPublicKey: peerAgeKeyPair.Public,
PeerPublicKeyDigest: publicKeyDigest,
PeerPrivateKey: peerAgeKeyPair.Private,
AgeServerPublicKey: serverAgeKeyPair.Public,
}
if err := db.Session().Create(implantBuild).Error; err != nil {
t.Fatalf("create implant build: %v", err)
}
t.Cleanup(func() {
_ = db.Session().Delete(implantBuild).Error
})
}
func startTestDNSImplant(t *testing.T, dnsParent string, dnsPort uint32) func() {
t.Helper()
opts := &dnsclient.DNSOptions{
QueryTimeout: 250 * time.Millisecond,
RetryWait: 50 * time.Millisecond,
RetryCount: 10,
MaxErrors: 10,
WorkersPerResolver: 1,
NoTXT: false,
ForceResolvers: fmt.Sprintf("127.0.0.1:%d", dnsPort),
}
// Starting the UDP listener is asynchronous relative to the gRPC call that
// created it. Retry session init for a short time to avoid flakes.
var (
client *dnsclient.SliverDNSClient
err error
)
deadline := time.Now().Add(5 * time.Second)
for {
client, err = dnsclient.DNSStartSession(dnsParent, opts)
if err == nil {
break
}
if time.Now().After(deadline) {
t.Fatalf("DNSStartSession: %v", err)
}
time.Sleep(50 * time.Millisecond)
}
register := &sliverpb.Register{
Name: "e2e-dns",
Hostname: "localhost",
Uuid: uuid.NewString(),
Username: "unit-test",
Os: runtime.GOOS,
Arch: runtime.GOARCH,
Pid: int32(os.Getpid()),
Filename: "sliver-e2e",
ActiveC2: "dns://e2e",
Version: "e2e",
ReconnectInterval: 0,
ProxyURL: "",
Locale: "en_US",
}
regData, err := proto.Marshal(register)
if err != nil {
t.Fatalf("marshal register: %v", err)
}
if err := client.WriteEnvelope(&sliverpb.Envelope{Type: sliverpb.MsgRegister, Data: regData}); err != nil {
t.Fatalf("send register: %v", err)
}
handlers := implantHandlers.GetSystemHandlers()
abort := make(chan struct{})
done := make(chan struct{})
go func() {
defer close(done)
for {
select {
case <-abort:
return
default:
}
envelope, err := client.ReadEnvelope()
switch err {
case nil:
if envelope == nil {
time.Sleep(25 * time.Millisecond)
continue
}
case dnsclient.ErrTimeout:
continue
case dnsclient.ErrClosed:
return
default:
// Treat transient resolver errors as retryable in this test loop.
continue
}
if envelope.ID == 0 {
continue
}
if envelope.Type == dnsE2EEchoMsgType {
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, Data: envelope.Data})
continue
}
handler, ok := handlers[envelope.Type]
if !ok {
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, UnknownMessageType: true})
continue
}
handler(envelope.Data, func(data []byte, err error) {
_ = err
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, Data: data})
})
}
}()
return func() {
close(abort)
_ = client.CloseSession()
select {
case <-done:
case <-time.After(5 * time.Second):
}
}
}
+289 -19
View File
@@ -232,31 +232,55 @@ func reassemble(t *testing.T, parent string, size int, encoder encoders.Encoder)
}
}
func TestDNSHelloRoundTrip(t *testing.T) {
func TestDNSSessionInitRoundTrip(t *testing.T) {
server := newTestDNSServer()
resolver := &testDNSResolver{server: server, parent: example1}
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_TOTP,
ID: 0,
sessionID := uint32(0x123456)
if _, ok := server.sessions.Load(sessionID); ok {
t.Fatalf("expected session %d to not exist before init", sessionID)
}
domain, err := encodeMessageDomain(example1, msg)
key := implantCrypto.RandomSymmetricKey()
cipherCtx := implantCrypto.NewCipherContext(key)
initData, err := implantCrypto.AgeKeyExToServer(key[:])
if err != nil {
t.Fatalf("encodeMessageDomain failed: %s", err)
t.Fatalf("AgeKeyExToServer failed: %s", err)
}
data, _, err := resolver.A(domain)
initMsg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_INIT,
ID: 0x01000000 | sessionID,
Size: uint32(len(initData)),
}
client := dnsclient.NewDNSClient(example1, opts)
domains, err := client.SplitBuffer(initMsg, implantEncoders.Base32Encoder{}, initData)
if err != nil {
t.Fatalf("resolver.A failed: %s", err)
t.Fatalf("SplitBuffer failed: %s", err)
}
if len(data) != 4 {
t.Fatalf("expected 4 bytes in response, got %d", len(data))
var respData []byte
for _, domain := range domains {
data, _, err := resolver.TXT(domain)
if err != nil {
t.Fatalf("resolver.TXT failed: %s", err)
}
if 0 < len(data) {
respData = data
}
}
sessionID := binary.LittleEndian.Uint32(data) & sessionIDBitMask
if sessionID == 0 {
t.Fatal("expected non-zero session id")
if len(respData) < 1 {
t.Fatal("expected non-empty init response")
}
plaintext, err := cipherCtx.Decrypt(respData)
if err != nil {
t.Fatalf("init response decrypt failed: %s", err)
}
got := binary.LittleEndian.Uint32(plaintext) & sessionIDBitMask
if got != sessionID {
t.Fatalf("expected session id %d, got %d", sessionID, got)
}
if _, ok := server.sessions.Load(sessionID); !ok {
t.Fatalf("expected session %d to be stored", sessionID)
t.Fatalf("expected session %d to be stored after init", sessionID)
}
}
@@ -421,7 +445,8 @@ func TestDNSDataToImplantRejectsInvalidRead(t *testing.T) {
}
func TestIsC2Domain(t *testing.T) {
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
listener := StartDNSListener("", uint16(9999), c2Domains, false)
t.Cleanup(func() { _ = listener.Shutdown() })
isC2, domain := listener.isC2SubDomain(c2Domains, "asdf.1.example.com.")
if !isC2 {
t.Fatal("IsC2Domain expected true, got false")
@@ -444,7 +469,8 @@ func TestIsC2Domain(t *testing.T) {
}
func TestDecodeSubdataBase32(t *testing.T) {
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
listener := StartDNSListener("", uint16(9999), c2Domains, false)
t.Cleanup(func() { _ = listener.Shutdown() })
payload := make([]byte, 64)
rand.Read(payload)
original := &dnspb.DNSMessage{
@@ -476,7 +502,8 @@ func TestDecodeSubdataBase32(t *testing.T) {
}
func TestDecodeSubdataRejectsInvalid(t *testing.T) {
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
listener := StartDNSListener("", uint16(9999), c2Domains, false)
t.Cleanup(func() { _ = listener.Shutdown() })
_, _, err := listener.decodeSubdata("invalid")
if err != ErrInvalidMsg {
t.Fatalf("expected ErrInvalidMsg, got %v", err)
@@ -484,7 +511,8 @@ func TestDecodeSubdataRejectsInvalid(t *testing.T) {
}
func TestDecodeSubdataRejectsInvalidProto(t *testing.T) {
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
listener := StartDNSListener("", uint16(9999), c2Domains, false)
t.Cleanup(func() { _ = listener.Shutdown() })
encoded, err := implantEncoders.Base32Encoder{}.Encode([]byte{0x00})
if err != nil {
t.Fatalf("encode failed: %s", err)
@@ -570,6 +598,199 @@ func TestAccumulateInitDataReassembles(t *testing.T) {
}
}
func TestAccumulateInitDataCapsPendingInits(t *testing.T) {
server := newTestDNSServer()
// Only send a single fragment per INIT so each call allocates one pending init.
for i := 0; i < defaultMaxPendingDNSInits+10; i++ {
id := uint32(i + 1) // avoid zero
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_INIT,
ID: id,
Size: 2048,
Start: 0,
Data: []byte{0xAA},
}
_, complete, err := server.accumulateInitData(msg)
if i < defaultMaxPendingDNSInits {
if err != nil {
t.Fatalf("unexpected error for pending init %d: %v", i, err)
}
if complete {
t.Fatalf("unexpected completion for pending init %d", i)
}
if _, ok := server.messages.Load(id); !ok {
t.Fatalf("expected pending init %d to be stored", id)
}
continue
}
if err != ErrInvalidMsg {
t.Fatalf("expected ErrInvalidMsg after cap, got %v", err)
}
if _, ok := server.messages.Load(id); ok {
t.Fatalf("expected pending init %d to not be stored after cap", id)
}
}
if got := server.pendingInits.Load(); got != int64(defaultMaxPendingDNSInits) {
t.Fatalf("expected pendingInits=%d, got %d", defaultMaxPendingDNSInits, got)
}
var count int64
server.messages.Range(func(key, value any) bool {
count++
return true
})
if count != int64(defaultMaxPendingDNSInits) {
t.Fatalf("expected %d pending init entries, got %d", defaultMaxPendingDNSInits, count)
}
}
func TestAccumulateInitDataCompletionCleansUpPendingInit(t *testing.T) {
server := newTestDNSServer()
client := dnsclient.NewDNSClient(example1, opts)
testData := make([]byte, 2048)
rand.Read(testData)
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_INIT,
ID: 0x01020304,
Size: uint32(len(testData)),
}
domains, err := client.SplitBuffer(msg, implantEncoders.Base32Encoder{}, testData)
if err != nil {
t.Fatalf("SplitBuffer failed: %s", err)
}
subMsgs := make([]*dnspb.DNSMessage, 0, len(domains))
for _, domain := range domains {
subdata := strings.TrimSuffix(domain, example1)
subdata = strings.ReplaceAll(subdata, ".", "")
raw, err := implantEncoders.Base32Encoder{}.Decode([]byte(subdata))
if err != nil {
t.Fatalf("decode subdata failed: %s", err)
}
subMsg := &dnspb.DNSMessage{}
if err := proto.Unmarshal(raw, subMsg); err != nil {
t.Fatalf("unmarshal submsg failed: %s", err)
}
subMsgs = append(subMsgs, subMsg)
}
for _, subMsg := range subMsgs {
_, _, err := server.accumulateInitData(subMsg)
if err != nil {
t.Fatalf("accumulateInitData failed: %v", err)
}
}
if got := server.pendingInits.Load(); got != 0 {
t.Fatalf("expected pendingInits=0 after completion, got %d", got)
}
if _, ok := server.messages.Load(msg.ID); ok {
t.Fatalf("expected pending init %d to be deleted after completion", msg.ID)
}
}
func TestAccumulateInitDataSizeMismatchCleansUpPendingInit(t *testing.T) {
server := newTestDNSServer()
id := uint32(0x01020304)
// First fragment creates pending state.
_, complete, err := server.accumulateInitData(&dnspb.DNSMessage{
Type: dnspb.DNSMessageType_INIT,
ID: id,
Size: 2048,
Start: 0,
Data: []byte{0xAA},
})
if err != nil {
t.Fatalf("unexpected error creating pending init: %v", err)
}
if complete {
t.Fatal("unexpected completion creating pending init")
}
if got := server.pendingInits.Load(); got != 1 {
t.Fatalf("expected pendingInits=1, got %d", got)
}
// Mismatched Size should reject and delete pending state.
_, _, err = server.accumulateInitData(&dnspb.DNSMessage{
Type: dnspb.DNSMessageType_INIT,
ID: id,
Size: 2049,
Start: 1,
Data: []byte{0xBB},
})
if err != ErrInvalidMsg {
t.Fatalf("expected ErrInvalidMsg for mismatched size, got %v", err)
}
if got := server.pendingInits.Load(); got != 0 {
t.Fatalf("expected pendingInits=0 after cleanup, got %d", got)
}
if _, ok := server.messages.Load(id); ok {
t.Fatalf("expected pending init %d to be deleted after size mismatch", id)
}
}
func TestGCPendingInitsExpiresOldEntries(t *testing.T) {
server := newTestDNSServer()
now := time.Now()
expiredAt := now.Add(-defaultPendingDNSInitTTL - time.Second)
freshAt := now.Add(-defaultPendingDNSInitTTL / 2)
expiredIDs := make([]uint32, 0, 10)
freshIDs := make([]uint32, 0, 5)
for i := 0; i < 10; i++ {
id := uint32(0x90000000 + i)
expiredIDs = append(expiredIDs, id)
server.messages.Store(id, &pendingInit{
Created: expiredAt,
Envelope: &PendingEnvelope{
Size: 1,
received: 0,
messages: map[uint32][]byte{},
mutex: &sync.Mutex{},
complete: false,
},
})
server.pendingInits.Add(1)
}
for i := 0; i < 5; i++ {
id := uint32(0xA0000000 + i)
freshIDs = append(freshIDs, id)
server.messages.Store(id, &pendingInit{
Created: freshAt,
Envelope: &PendingEnvelope{
Size: 1,
received: 0,
messages: map[uint32][]byte{},
mutex: &sync.Mutex{},
complete: false,
},
})
server.pendingInits.Add(1)
}
server.gcPendingInits()
for _, id := range expiredIDs {
if _, ok := server.messages.Load(id); ok {
t.Fatalf("expected expired pending init %d to be deleted", id)
}
}
for _, id := range freshIDs {
if _, ok := server.messages.Load(id); !ok {
t.Fatalf("expected fresh pending init %d to remain", id)
}
}
if got := server.pendingInits.Load(); got != int64(len(freshIDs)) {
t.Fatalf("expected pendingInits=%d, got %d", len(freshIDs), got)
}
}
func TestSplitToChunksPadding(t *testing.T) {
data := []byte{1, 2, 3, 4, 5, 6}
chunks := splitToChunks(data, 4)
@@ -658,7 +879,6 @@ func newTestDNSServer() *SliverDNSServer {
messages: &sync.Map{},
TTL: 0,
MaxTXTLength: defaultMaxTXTLength,
EnforceOTP: false,
}
}
@@ -860,3 +1080,53 @@ func randomBytes(size int) []byte {
rand.Read(buf)
return buf
}
func TestAccumulateInitDataRejectsOversizedInit(t *testing.T) {
server := newTestDNSServer()
msg := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_INIT,
ID: 0x01020304,
Size: defaultMaxDNSInitSize + 1,
Data: []byte{0x01},
}
_, _, err := server.accumulateInitData(msg)
if err != ErrInvalidMsg {
t.Fatalf("expected ErrInvalidMsg, got %v", err)
}
}
func TestAccumulateInitDataRejectsReceivedOverrun(t *testing.T) {
server := newTestDNSServer()
// Declared size is 8 bytes, but two chunks deliver 10 bytes total.
first := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_INIT,
ID: 0x0a0b0c0d,
Size: 8,
Start: 0,
Stop: 5,
Data: []byte("12345"),
}
second := &dnspb.DNSMessage{
Type: dnspb.DNSMessageType_INIT,
ID: 0x0a0b0c0d,
Size: 8,
Start: 5,
Stop: 10,
Data: []byte("67890"),
}
_, complete, err := server.accumulateInitData(first)
if err != nil {
t.Fatalf("unexpected error on first chunk: %v", err)
}
if complete {
t.Fatal("expected incomplete state after first chunk")
}
_, _, err = server.accumulateInitData(second)
if err != ErrInvalidMsg {
t.Fatalf("expected ErrInvalidMsg, got %v", err)
}
}
+1 -1
View File
@@ -128,7 +128,7 @@ func StartWGListenerJob(wgListener *clientpb.WGListenerReq) (*core.Job, error) {
// StartDNSListenerJob - Start a DNS listener as a job
func StartDNSListenerJob(dnsListener *clientpb.DNSListenerReq) (*core.Job, error) {
server := StartDNSListener(dnsListener.Host, uint16(dnsListener.Port), dnsListener.Domains, dnsListener.Canaries, dnsListener.EnforceOTP)
server := StartDNSListener(dnsListener.Host, uint16(dnsListener.Port), dnsListener.Domains, dnsListener.Canaries)
description := fmt.Sprintf("%s (canaries %v)", strings.Join(dnsListener.Domains, " "), dnsListener.Canaries)
job := &core.Job{
ID: core.NextJobID(),
+44 -4
View File
@@ -52,10 +52,50 @@ func getCrackLegacyConfigPath() string {
}
type crackConfigYAML struct {
AutoFire bool `yaml:"AutoFire"`
MaxFileSize int64 `yaml:"MaxFileSize"`
ChunkSize int64 `yaml:"ChunkSize"`
MaxDiskUsage int64 `yaml:"MaxDiskUsage"`
AutoFire bool `yaml:"auto_fire"`
MaxFileSize int64 `yaml:"max_file_size"`
ChunkSize int64 `yaml:"chunk_size"`
MaxDiskUsage int64 `yaml:"max_disk_usage"`
}
type crackConfigYAMLCompat struct {
AutoFire *bool `yaml:"auto_fire"`
MaxFileSize *int64 `yaml:"max_file_size"`
ChunkSize *int64 `yaml:"chunk_size"`
MaxDiskUsage *int64 `yaml:"max_disk_usage"`
AutoFireLegacy *bool `yaml:"AutoFire"`
MaxFileSizeLegacy *int64 `yaml:"MaxFileSize"`
ChunkSizeLegacy *int64 `yaml:"ChunkSize"`
MaxDiskUsageLegacy *int64 `yaml:"MaxDiskUsage"`
}
func (c *crackConfigYAML) UnmarshalYAML(node *yaml.Node) error {
var raw crackConfigYAMLCompat
if err := node.Decode(&raw); err != nil {
return err
}
if raw.AutoFire != nil {
c.AutoFire = *raw.AutoFire
} else if raw.AutoFireLegacy != nil {
c.AutoFire = *raw.AutoFireLegacy
}
if raw.MaxFileSize != nil {
c.MaxFileSize = *raw.MaxFileSize
} else if raw.MaxFileSizeLegacy != nil {
c.MaxFileSize = *raw.MaxFileSizeLegacy
}
if raw.ChunkSize != nil {
c.ChunkSize = *raw.ChunkSize
} else if raw.ChunkSizeLegacy != nil {
c.ChunkSize = *raw.ChunkSizeLegacy
}
if raw.MaxDiskUsage != nil {
c.MaxDiskUsage = *raw.MaxDiskUsage
} else if raw.MaxDiskUsageLegacy != nil {
c.MaxDiskUsage = *raw.MaxDiskUsageLegacy
}
return nil
}
func crackConfigFromYAML(data []byte) (*clientpb.CrackConfig, error) {
+35
View File
@@ -17,6 +17,41 @@ func TestCrackConfigParsesYAML(t *testing.T) {
t.Fatalf("failed to create config dir: %v", err)
}
data := []byte(`auto_fire: false
max_file_size: 2048
chunk_size: 1024
max_disk_usage: 4096
`)
if err := os.WriteFile(configPath, data, 0600); err != nil {
t.Fatalf("failed to write yaml config: %v", err)
}
config, err := LoadCrackConfig()
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if config.AutoFire {
t.Fatalf("expected AutoFire false")
}
if config.MaxFileSize != 2048 {
t.Fatalf("expected MaxFileSize %d, got %d", 2048, config.MaxFileSize)
}
if config.ChunkSize != 1024 {
t.Fatalf("expected ChunkSize %d, got %d", 1024, config.ChunkSize)
}
if config.MaxDiskUsage != 4096 {
t.Fatalf("expected MaxDiskUsage %d, got %d", 4096, config.MaxDiskUsage)
}
}
func TestCrackConfigParsesLegacyYAML(t *testing.T) {
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
configPath := getCrackConfigPath()
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
t.Fatalf("failed to create config dir: %v", err)
}
data := []byte(`AutoFire: false
MaxFileSize: 2048
ChunkSize: 1024
+36 -2
View File
@@ -30,8 +30,9 @@ import (
)
const (
serverConfigFileName = "server.yaml"
serverLegacyConfigFileName = "server.json"
serverConfigFileName = "server.yaml"
serverLegacyConfigFileName = "server.json"
defaultGRPCKeepaliveMinTimeSeconds int64 = 30
)
var (
@@ -59,6 +60,17 @@ type LogConfig struct {
TLSKeyLogger bool `json:"tls_key_logger" yaml:"tls_key_logger"`
}
// GRPCKeepaliveConfig - gRPC keepalive enforcement settings
type GRPCKeepaliveConfig struct {
MinTimeSeconds int64 `json:"min_time_seconds" yaml:"min_time_seconds"`
PermitWithoutStream *bool `json:"permit_without_stream" yaml:"permit_without_stream"`
}
// GRPCConfig - gRPC server settings
type GRPCConfig struct {
Keepalive *GRPCKeepaliveConfig `json:"keepalive" yaml:"keepalive"`
}
// DaemonConfig - Configure daemon mode
type DaemonConfig struct {
Host string `json:"host" yaml:"host"`
@@ -182,6 +194,7 @@ type ServerConfig struct {
DaemonMode bool `json:"daemon_mode" yaml:"daemon_mode"`
DaemonConfig *DaemonConfig `json:"daemon" yaml:"daemon"`
Logs *LogConfig `json:"logs" yaml:"logs"`
GRPC *GRPCConfig `json:"grpc" yaml:"grpc"`
Watchtower *WatchTowerConfig `json:"watch_tower" yaml:"watch_tower"`
GoProxy string `json:"go_proxy" yaml:"go_proxy"`
HTTPDefaults *HttpDefaultConfig `json:"http_default" yaml:"http_default"`
@@ -257,6 +270,20 @@ func GetServerConfig() *ServerConfig {
}
log.RootLogger.SetLevel(log.LevelFrom(config.Logs.Level))
if config.GRPC == nil {
config.GRPC = &GRPCConfig{}
}
if config.GRPC.Keepalive == nil {
config.GRPC.Keepalive = &GRPCKeepaliveConfig{}
}
if config.GRPC.Keepalive.MinTimeSeconds <= 0 {
config.GRPC.Keepalive.MinTimeSeconds = defaultGRPCKeepaliveMinTimeSeconds
}
if config.GRPC.Keepalive.PermitWithoutStream == nil {
defaultPermit := true
config.GRPC.Keepalive.PermitWithoutStream = &defaultPermit
}
err := config.Save() // This updates the config with any missing fields
if err != nil {
serverConfigLog.Errorf("Failed to save default config %s", err)
@@ -271,6 +298,7 @@ func GetServerConfig() *ServerConfig {
}
func getDefaultServerConfig() *ServerConfig {
defaultPermitWithoutStream := true
return &ServerConfig{
DaemonMode: false,
DaemonConfig: &DaemonConfig{
@@ -282,6 +310,12 @@ func getDefaultServerConfig() *ServerConfig {
GRPCUnaryPayloads: false,
GRPCStreamPayloads: false,
},
GRPC: &GRPCConfig{
Keepalive: &GRPCKeepaliveConfig{
MinTimeSeconds: defaultGRPCKeepaliveMinTimeSeconds,
PermitWithoutStream: &defaultPermitWithoutStream,
},
},
HTTPDefaults: &HttpDefaultConfig{
Headers: []HttpDefaultHeader{
{
+68 -4
View File
@@ -138,15 +138,20 @@ func (c *CrackFileChunk) ToProtobuf() *clientpb.CrackFileChunk {
// crack job contains the parent command, whose keyspace may get broken
// up into multiple crack tasks and distributed to multiple crackstations
type CrackJob struct {
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
CreatedAt time.Time `gorm:"->;<-:create;"`
CompletedAt time.Time
Tasks []CrackTask
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
CreatedAt time.Time `gorm:"->;<-:create;"`
CompletedAt time.Time
Err string
ResultFileID string
Tasks []CrackTask
Command CrackCommand // Parent command
}
func (c *CrackJob) Status() clientpb.CrackJobStatus {
if c.Err != "" {
return clientpb.CrackJobStatus_FAILED
}
if c.CompletedAt.IsZero() {
return clientpb.CrackJobStatus_IN_PROGRESS
}
@@ -158,9 +163,67 @@ func (c *CrackJob) Status() clientpb.CrackJobStatus {
return clientpb.CrackJobStatus_COMPLETED
}
// BeforeCreate - GORM hook
func (c *CrackJob) BeforeCreate(tx *gorm.DB) (err error) {
if c.ID == uuid.Nil {
c.ID, err = uuid.NewV4()
if err != nil {
return err
}
}
if c.CreatedAt.IsZero() {
c.CreatedAt = time.Now()
}
return nil
}
func (c *CrackJob) ToProtobuf() *clientpb.CrackJob {
job := &clientpb.CrackJob{
ID: c.ID.String(),
CreatedAt: c.CreatedAt.UTC().Format(time.RFC3339),
Status: c.Status(),
Err: c.Err,
Command: c.Command.ToProtobuf(),
ResultFileID: c.ResultFileID,
}
if !c.CompletedAt.IsZero() {
job.CompletedAt = c.CompletedAt.UTC().Format(time.RFC3339)
}
return job
}
func (CrackJob) FromProtobuf(c *clientpb.CrackJob) *CrackJob {
job := &CrackJob{}
if c == nil {
return job
}
jobID := uuid.FromStringOrNil(c.ID)
if jobID != uuid.Nil {
job.ID = jobID
}
if c.CreatedAt != "" {
if createdAt, err := time.Parse(time.RFC3339, c.CreatedAt); err == nil {
job.CreatedAt = createdAt
}
}
if c.CompletedAt != "" {
if completedAt, err := time.Parse(time.RFC3339, c.CompletedAt); err == nil {
job.CompletedAt = completedAt
}
}
job.Err = c.Err
job.ResultFileID = c.ResultFileID
if c.Command != nil {
job.Command = *CrackCommand{}.FromProtobuf(c.Command)
job.Command.CrackJobID = job.ID
}
return job
}
// CrackTask - An individual chunk of a job sent to a specific crackstation
type CrackTask struct {
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
CrackJobID uuid.UUID `gorm:"type:uuid;"`
CrackstationID uuid.UUID `gorm:"type:uuid;"`
CreatedAt time.Time `gorm:"->;<-:create;"`
StartedAt time.Time
@@ -201,6 +264,7 @@ type CrackCommand struct {
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
CreatedAt time.Time `gorm:"->;<-:create;"`
CrackTaskID uuid.UUID `gorm:"type:uuid;"`
CrackJobID uuid.UUID `gorm:"type:uuid;"`
// FLAGS
AttackMode int32
+1
View File
@@ -63,6 +63,7 @@ func newDBClient() *gorm.DB {
&models.DNSCanary{},
&models.Crackstation{},
&models.Benchmark{},
&models.CrackJob{},
&models.CrackTask{},
&models.CrackCommand{},
&models.CrackFile{},
+1
View File
@@ -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) {
+76
View File
@@ -0,0 +1,76 @@
package rpc
/*
Sliver Implant Framework
Copyright (C) 2022 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"context"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/server/db"
"github.com/bishopfox/sliver/server/db/models"
"github.com/bishopfox/sliver/server/log"
"github.com/gofrs/uuid"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"gorm.io/gorm"
)
var (
crackCommandRpcLog = log.NamedLogger("rpc", "crack")
)
func (rpc *Server) Crack(ctx context.Context, req *clientpb.CrackCommand) (*clientpb.CrackResponse, error) {
if req == nil {
crackCommandRpcLog.Warn("Received empty crack command")
return &clientpb.CrackResponse{}, nil
}
crackCommandRpcLog.Infof(
"Crack request: attack=%s hashType=%s hashes=%d status=%t",
req.AttackMode.String(),
req.HashType.String(),
len(req.Hashes),
req.Status,
)
jobID, err := uuid.NewV4()
if err != nil {
crackCommandRpcLog.Errorf("Failed to generate crack job ID: %s", err)
return nil, status.Error(codes.Internal, "failed to generate crack job id")
}
job := &models.CrackJob{
ID: jobID,
}
command := models.CrackCommand{}.FromProtobuf(req)
command.CrackJobID = jobID
if err := db.Session().Transaction(func(tx *gorm.DB) error {
if err := tx.Create(job).Error; err != nil {
return err
}
return tx.Create(command).Error
}); err != nil {
crackCommandRpcLog.Errorf("Failed to save crack job: %s", err)
return nil, status.Error(codes.Internal, "failed to save crack job")
}
job.Command = *command
return &clientpb.CrackResponse{Job: job.ToProtobuf()}, nil
}
+31 -4
View File
@@ -40,6 +40,7 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"gorm.io/gorm"
)
var (
@@ -112,16 +113,37 @@ func (rpc *Server) CrackstationBenchmark(ctx context.Context, req *clientpb.Crac
crackRpcLog.Errorf("Failed to get crackstation by host UUID: %s", err)
return nil, status.Errorf(codes.NotFound, "Failed to find crackstation by host UUID")
}
crackstation.Benchmarks = []models.Benchmark{}
benchmarks := make([]models.Benchmark, 0, len(req.Benchmarks))
for hashType, speed := range req.Benchmarks {
crackstation.Benchmarks = append(crackstation.Benchmarks, models.Benchmark{HashType: hashType, PerSecondRate: speed})
benchmarks = append(benchmarks, models.Benchmark{
CrackstationID: crackstation.ID,
HashType: hashType,
PerSecondRate: speed,
})
}
dbSession := db.Session()
err = dbSession.Save(&crackstation).Error
err = dbSession.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("crackstation_id = ?", crackstation.ID).Delete(&models.Benchmark{}).Error; err != nil {
return err
}
if len(benchmarks) == 0 {
return nil
}
return tx.Create(&benchmarks).Error
})
if err != nil {
crackRpcLog.Errorf("Failed to save crackstation benchmarks: %s", err)
return nil, status.Errorf(codes.Internal, "Failed to save crackstation benchmarks")
}
crackStation := core.GetCrackstation(req.HostUUID)
if crackStation != nil {
crackStation.Station.Benchmarks = map[int32]uint64{}
for hashType, speed := range req.Benchmarks {
crackStation.Station.Benchmarks[hashType] = speed
}
}
return &commonpb.Empty{}, nil
}
@@ -161,7 +183,12 @@ func (rpc *Server) CrackstationRegister(req *clientpb.Crackstation, stream rpcpb
if len(dbCrackstation.Benchmarks) == 0 {
crackRpcLog.Infof("No benchmark information for '%s', requesting benchmark...", req.Name)
err = stream.Send(&clientpb.Event{EventType: consts.CrackBenchmark, Data: []byte{}})
taskID, err := uuid.NewV4()
if err != nil {
crackRpcLog.Errorf("Failed to generate benchmark task ID: %s", err)
return status.Error(codes.Internal, "failed to create benchmark task")
}
err = stream.Send(&clientpb.Event{EventType: consts.CrackBenchmark, Data: taskID.Bytes()})
if err != nil {
crackRpcLog.Errorf("Failed to send benchmark task to crackstation: %s", err)
return status.Error(codes.Internal, "failed to send benchmark task")
+50
View File
@@ -0,0 +1,50 @@
package transport
/*
Sliver Implant Framework
Copyright (C) 2019 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
)
func grpcKeepaliveOptions() []grpc.ServerOption {
if serverConfig == nil || serverConfig.GRPC == nil || serverConfig.GRPC.Keepalive == nil {
return nil
}
minTimeSeconds := serverConfig.GRPC.Keepalive.MinTimeSeconds
if minTimeSeconds <= 0 {
return nil
}
permitWithoutStream := true
if serverConfig.GRPC.Keepalive.PermitWithoutStream != nil {
permitWithoutStream = *serverConfig.GRPC.Keepalive.PermitWithoutStream
}
policy := keepalive.EnforcementPolicy{
MinTime: time.Duration(minTimeSeconds) * time.Second,
PermitWithoutStream: permitWithoutStream,
}
return []grpc.ServerOption{
grpc.KeepaliveEnforcementPolicy(policy),
}
}
+1
View File
@@ -43,6 +43,7 @@ func LocalListener() (*grpc.Server, *bufconn.Listener, error) {
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
grpc.MaxSendMsgSize(ServerMaxMessageSize),
}
options = append(options, grpcKeepaliveOptions()...)
options = append(options, initMiddleware(false)...)
grpcServer := grpc.NewServer(options...)
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
+1
View File
@@ -64,6 +64,7 @@ func StartMtlsClientListener(host string, port uint16) (*grpc.Server, net.Listen
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
grpc.MaxSendMsgSize(ServerMaxMessageSize),
}
options = append(options, grpcKeepaliveOptions()...)
options = append(options, initMiddleware(true)...)
grpcServer := grpc.NewServer(options...)
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
+1
View File
@@ -81,6 +81,7 @@ func StartTsNetClientListener(hostname string, port uint16) (*grpc.Server, net.L
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
grpc.MaxSendMsgSize(ServerMaxMessageSize),
}
options = append(options, grpcKeepaliveOptions()...)
options = append(options, initMiddleware(true)...)
grpcServer := grpc.NewServer(options...)
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())