mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87d5fd53a8 | ||
|
|
cf79a2d284 | ||
|
|
6d0276ff67 | ||
|
|
9b001ab88e | ||
|
|
503218f3f4 | ||
|
|
495dce0987 | ||
|
|
a7ff35c229 | ||
|
|
162fc4fb8a | ||
|
|
636ea0a9b2 | ||
|
|
84ed9bc4e7 | ||
|
|
b985d8ff9f | ||
|
|
5a43705e1d | ||
|
|
76af248850 | ||
|
|
628757250e |
No files matched your search
@@ -27,6 +27,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("", false, executeCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("token", "T", false, "execute command with current token (Windows only)")
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.Bool("background", false, "start the process in the background and track it")
|
||||
f.BoolP("save", "s", false, "save output to a file")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
@@ -45,6 +46,20 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
carapace.Gen(executeCmd).PositionalCompletion(carapace.ActionValues().Usage("command to execute (required)"))
|
||||
carapace.Gen(executeCmd).PositionalAnyCompletion(carapace.ActionValues().Usage("arguments to the command (optional)"))
|
||||
|
||||
executeChildrenCmd := &cobra.Command{
|
||||
Use: consts.ExecuteChildrenStr,
|
||||
Short: "List tracked background execute child processes",
|
||||
Long: help.GetHelpFor([]string{consts.ExecuteStr, consts.ExecuteChildrenStr}),
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ExecuteChildrenCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
flags.Bind("", false, executeChildrenCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
executeCmd.AddCommand(executeChildrenCmd)
|
||||
|
||||
executeAssemblyCmd := &cobra.Command{
|
||||
Use: consts.ExecuteAssemblyStr,
|
||||
Short: "Loads and executes a .NET assembly in a child process (Windows Only)",
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package exec
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2019 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ExecuteChildrenCmd - List tracked background execute child processes on the implant.
|
||||
func ExecuteChildrenCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil("Querying execute children ...", ctrl)
|
||||
execChildren, err := con.Rpc.ExecuteChildren(context.Background(), &sliverpb.ExecuteChildrenReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
})
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if execChildren.Response != nil && execChildren.Response.Async {
|
||||
con.AddBeaconCallback(execChildren.Response.TaskID, func(task *clientpb.BeaconTask) {
|
||||
err = proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode response %s\n", err)
|
||||
return
|
||||
}
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
})
|
||||
con.PrintAsyncResponse(execChildren.Response)
|
||||
return
|
||||
}
|
||||
|
||||
PrintExecuteChildren(execChildren, con)
|
||||
}
|
||||
|
||||
// PrintExecuteChildren - Render execute children output.
|
||||
func PrintExecuteChildren(execChildren *sliverpb.ExecuteChildren, con *console.SliverClient) {
|
||||
if execChildren == nil || len(execChildren.Children) == 0 {
|
||||
con.PrintInfof("No tracked background execute children\n")
|
||||
return
|
||||
}
|
||||
|
||||
children := append([]*sliverpb.ExecuteChild(nil), execChildren.Children...)
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"pid", "state", "exit", "started", "command", "stdout", "stderr"})
|
||||
|
||||
for _, child := range children {
|
||||
state := "running"
|
||||
exitCode := "-"
|
||||
if child.Exited {
|
||||
state = "exited"
|
||||
exitCode = fmt.Sprintf("%d", child.ExitCode)
|
||||
}
|
||||
|
||||
started := "-"
|
||||
if child.StartTime != 0 {
|
||||
started = time.Unix(child.StartTime, 0).Local().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
command := strings.TrimSpace(fmt.Sprintf("%s %s", child.Path, strings.Join(child.Args, " ")))
|
||||
tw.AppendRow(table.Row{child.Pid, state, exitCode, started, command, child.Stdout, child.Stderr})
|
||||
}
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -44,6 +44,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
token, _ := cmd.Flags().GetBool("token")
|
||||
hidden, _ := cmd.Flags().GetBool("hidden")
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background, _ := cmd.Flags().GetBool("background")
|
||||
stdout, _ := cmd.Flags().GetString("stdout")
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
saveLoot, _ := cmd.Flags().GetBool("loot")
|
||||
@@ -58,10 +59,15 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
}
|
||||
hostName := getHostname(session, beacon)
|
||||
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := output || saveLoot || saveOutput
|
||||
if background && (saveLoot || saveOutput) {
|
||||
con.PrintErrorf("The loot and save options are not supported with --background\n")
|
||||
return
|
||||
}
|
||||
|
||||
if output && beacon != nil {
|
||||
// If the user wants to loot or save the output, we have to capture it regardless of if they specified -o
|
||||
captureOutput := !background && (output || saveLoot || saveOutput)
|
||||
|
||||
if captureOutput && beacon != nil {
|
||||
con.PrintWarnf("Using --output in beacon mode, if the command blocks the task will never complete\n\n")
|
||||
}
|
||||
|
||||
@@ -84,6 +90,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
UseToken: token,
|
||||
@@ -96,6 +103,7 @@ func ExecuteCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
Path: cmdPath,
|
||||
Args: args,
|
||||
Output: captureOutput,
|
||||
Background: background,
|
||||
Stderr: stderr,
|
||||
Stdout: stdout,
|
||||
EnvInheritance: envInheritance,
|
||||
@@ -154,11 +162,23 @@ func PrintExecute(exec *sliverpb.Execute, cmd *cobra.Command, con *console.Slive
|
||||
stderr, _ := cmd.Flags().GetString("stderr")
|
||||
|
||||
output, _ := cmd.Flags().GetBool("output")
|
||||
background := false
|
||||
if cmd.Flags().Lookup("background") != nil {
|
||||
background, _ = cmd.Flags().GetBool("background")
|
||||
}
|
||||
if background {
|
||||
// Background execution never returns inline output.
|
||||
output = false
|
||||
}
|
||||
if !output {
|
||||
if exec.Status == 0 {
|
||||
con.PrintInfof("Command executed successfully\n")
|
||||
if exec.Pid != 0 {
|
||||
if background {
|
||||
con.PrintInfof("Started background process (pid: %d)\n", exec.Pid)
|
||||
return
|
||||
}
|
||||
con.PrintInfof("Started process (pid: %d)\n", exec.Pid)
|
||||
} else {
|
||||
con.PrintErrorf("Exit code %d\n", exec.Status)
|
||||
con.PrintInfof("Process started\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -47,44 +47,45 @@ var (
|
||||
consts.GenerateStr: generateHelp,
|
||||
consts.StageListenerStr: stageListenerHelp,
|
||||
|
||||
consts.MsfStr: msfHelp,
|
||||
consts.MsfInjectStr: msfInjectHelp,
|
||||
consts.PsStr: psHelp,
|
||||
consts.PingStr: pingHelp,
|
||||
consts.KillStr: killHelp,
|
||||
consts.LsStr: lsHelp,
|
||||
consts.CdStr: cdHelp,
|
||||
consts.PwdStr: pwdHelp,
|
||||
consts.CatStr: catHelp,
|
||||
consts.EditStr: editHelp,
|
||||
consts.HexEditStr: hexEditHelp,
|
||||
consts.DownloadStr: downloadHelp,
|
||||
consts.GrepStr: grepHelp,
|
||||
consts.HeadStr: headHelp,
|
||||
consts.TailStr: tailHelp,
|
||||
consts.UploadStr: uploadHelp,
|
||||
consts.MkdirStr: mkdirHelp,
|
||||
consts.RmStr: rmHelp,
|
||||
consts.ChtimesStr: chtimesHelp,
|
||||
consts.ProcdumpStr: procdumpHelp,
|
||||
consts.ElevateStr: elevateHelp,
|
||||
consts.RunAsStr: runAsHelp,
|
||||
consts.ImpersonateStr: impersonateHelp,
|
||||
consts.RevToSelfStr: revToSelfHelp,
|
||||
consts.ExecuteStr: executeHelp,
|
||||
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
||||
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
||||
consts.MigrateStr: migrateHelp,
|
||||
consts.SideloadStr: sideloadHelp,
|
||||
consts.TerminateStr: terminateHelp,
|
||||
consts.AliasesStr: loadAliasHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
consts.MountStr: mountHelp,
|
||||
consts.ShikataGaNai: shikataGaNaiHelp,
|
||||
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
|
||||
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
|
||||
consts.MsfStr: msfHelp,
|
||||
consts.MsfInjectStr: msfInjectHelp,
|
||||
consts.PsStr: psHelp,
|
||||
consts.PingStr: pingHelp,
|
||||
consts.KillStr: killHelp,
|
||||
consts.LsStr: lsHelp,
|
||||
consts.CdStr: cdHelp,
|
||||
consts.PwdStr: pwdHelp,
|
||||
consts.CatStr: catHelp,
|
||||
consts.EditStr: editHelp,
|
||||
consts.HexEditStr: hexEditHelp,
|
||||
consts.DownloadStr: downloadHelp,
|
||||
consts.GrepStr: grepHelp,
|
||||
consts.HeadStr: headHelp,
|
||||
consts.TailStr: tailHelp,
|
||||
consts.UploadStr: uploadHelp,
|
||||
consts.MkdirStr: mkdirHelp,
|
||||
consts.RmStr: rmHelp,
|
||||
consts.ChtimesStr: chtimesHelp,
|
||||
consts.ProcdumpStr: procdumpHelp,
|
||||
consts.ElevateStr: elevateHelp,
|
||||
consts.RunAsStr: runAsHelp,
|
||||
consts.ImpersonateStr: impersonateHelp,
|
||||
consts.RevToSelfStr: revToSelfHelp,
|
||||
consts.ExecuteStr: executeHelp,
|
||||
consts.ExecuteStr + sep + consts.ExecuteChildrenStr: executeChildrenHelp,
|
||||
consts.ExecuteAssemblyStr: executeAssemblyHelp,
|
||||
consts.ExecuteShellcodeStr: executeShellcodeHelp,
|
||||
consts.MigrateStr: migrateHelp,
|
||||
consts.SideloadStr: sideloadHelp,
|
||||
consts.TerminateStr: terminateHelp,
|
||||
consts.AliasesStr: loadAliasHelp,
|
||||
consts.PsExecStr: psExecHelp,
|
||||
consts.BackdoorStr: backdoorHelp,
|
||||
consts.SpawnDllStr: spawnDllHelp,
|
||||
consts.MountStr: mountHelp,
|
||||
consts.ShikataGaNai: shikataGaNaiHelp,
|
||||
consts.ShellcodeEncodersStr: shellcodeEncodersHelp,
|
||||
consts.ShellcodeEncodersStr + sep + "encode": shellcodeEncodersEncodeHelp,
|
||||
|
||||
consts.WebsitesStr: websitesHelp,
|
||||
consts.ScreenshotStr: screenshotHelp,
|
||||
@@ -135,6 +136,7 @@ var (
|
||||
|
||||
[[.Bold]]About:[[.Normal]] Execute a program on the remote system with optional arguments, note the subprocess
|
||||
arguments are be separated from the sliver 'execute' arguments with '--'
|
||||
Use [[.Bold]]--background[[.Normal]] to start the process without waiting for output (use [[.Bold]]--stdout[[.Normal]]/[[.Bold]]--stderr[[.Normal]] to redirect).
|
||||
|
||||
[[.Bold]]Example:[[.Normal]]
|
||||
execute /bin/bash --env FOO=1 -- -c env
|
||||
@@ -145,6 +147,11 @@ FOO=1
|
||||
PWD=/Users/moloch/git/sliver
|
||||
SHLVL=1
|
||||
_=/usr/bin/env
|
||||
`
|
||||
|
||||
executeChildrenHelp = `[[.Bold]]Command:[[.Normal]] execute children
|
||||
|
||||
[[.Bold]]About:[[.Normal]] List tracked background child processes started with [[.Bold]]execute --background[[.Normal]].
|
||||
`
|
||||
|
||||
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
|
||||
|
||||
@@ -82,7 +82,6 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("no-canaries", "c", false, "disable dns canary detection")
|
||||
f.StringP("lhost", "L", "", "interface to bind server to")
|
||||
f.Uint32P("lport", "l", generate.DefaultDNSLPort, "udp listen port")
|
||||
f.BoolP("disable-otp", "D", false, "disable otp authentication")
|
||||
})
|
||||
|
||||
// HTTP
|
||||
|
||||
@@ -40,15 +40,13 @@ func DNSListenerCmd(cmd *cobra.Command, con *console.SliverClient, args []string
|
||||
lhost, _ := cmd.Flags().GetString("lhost")
|
||||
lport, _ := cmd.Flags().GetUint32("lport")
|
||||
canaries, _ := cmd.Flags().GetBool("no-canaries")
|
||||
enforceOTP, _ := cmd.Flags().GetBool("disable-otp")
|
||||
|
||||
con.PrintInfof("Starting DNS listener with parent domain(s) %v ...\n", domains)
|
||||
dns, err := con.Rpc.StartDNSListener(context.Background(), &clientpb.DNSListenerReq{
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
EnforceOTP: !enforceOTP,
|
||||
Domains: domains,
|
||||
Host: lhost,
|
||||
Port: lport,
|
||||
Canaries: !canaries,
|
||||
})
|
||||
con.Println()
|
||||
if err != nil {
|
||||
|
||||
@@ -285,11 +285,12 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
}
|
||||
|
||||
f := pflag.NewFlagSet(constants.ExecuteStr, pflag.ContinueOnError)
|
||||
f.BoolP("output", "o", true, "capture command output")
|
||||
f.BoolP("output", "o", execReq.Output, "capture command output")
|
||||
f.Bool("background", execReq.Background, "start the process in the background and track it")
|
||||
f.BoolP("loot", "X", false, "save output as loot")
|
||||
f.BoolP("ignore-stderr", "S", false, "don't print STDERR output")
|
||||
f.StringP("stdout", "O", "", "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", "", "remote path to redirect STDERR to")
|
||||
f.StringP("stdout", "O", execReq.Stdout, "remote path to redirect STDOUT to")
|
||||
f.StringP("stderr", "E", execReq.Stderr, "remote path to redirect STDERR to")
|
||||
|
||||
execCmd := &cobra.Command{Use: constants.ExecuteStr}
|
||||
execCmd.Flags().AddFlagSet(f)
|
||||
@@ -297,6 +298,15 @@ func renderTaskResponse(task *clientpb.BeaconTask, con *console.SliverClient) {
|
||||
|
||||
exec.PrintExecute(execResult, execCmd, con)
|
||||
|
||||
case sliverpb.MsgExecuteChildrenReq:
|
||||
execChildren := &sliverpb.ExecuteChildren{}
|
||||
err := proto.Unmarshal(task.Response, execChildren)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Failed to decode task response: %s\n", err)
|
||||
return
|
||||
}
|
||||
exec.PrintExecuteChildren(execChildren, con)
|
||||
|
||||
case sliverpb.MsgSideloadReq:
|
||||
sideload := &sliverpb.Sideload{}
|
||||
err := proto.Unmarshal(task.Response, sideload)
|
||||
|
||||
@@ -208,8 +208,9 @@ const (
|
||||
GetGIDStr = "getgid"
|
||||
WhoamiStr = "whoami"
|
||||
|
||||
ShellStr = "shell"
|
||||
ExecuteStr = "execute"
|
||||
ShellStr = "shell"
|
||||
ExecuteStr = "execute"
|
||||
ExecuteChildrenStr = "children"
|
||||
|
||||
LsStr = "ls"
|
||||
MvStr = "mv"
|
||||
|
||||
@@ -40,7 +40,8 @@ func ArmoryInstallForm(options []ArmoryInstallOption) (*ArmoryInstallFormResult,
|
||||
Title("Select packages or bundles to install").
|
||||
Description("Use space to select and enter to install.").
|
||||
Options(selectOptions...).
|
||||
Height(listHeight(len(selectOptions))).
|
||||
// huh.MultiSelect Height includes title/description, so add 2 lines for them.
|
||||
Height(listHeight(len(selectOptions)) + 2).
|
||||
Value(&result.SelectedNames)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field))
|
||||
|
||||
@@ -39,7 +39,8 @@ func ArmoryUpdateForm(options []ArmoryUpdateOption) (*ArmoryUpdateFormResult, er
|
||||
Title("Select updates to apply").
|
||||
Description("Use space to select and enter to apply.").
|
||||
Options(selectOptions...).
|
||||
Height(listHeight(len(selectOptions))).
|
||||
// huh.MultiSelect Height includes title/description, so add 2 lines for them.
|
||||
Height(listHeight(len(selectOptions)) + 2).
|
||||
Value(&result.SelectedIDs)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field))
|
||||
|
||||
@@ -2,13 +2,14 @@ package forms
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const defaultSelectHeight = 10
|
||||
const defaultSelectHeight = 5
|
||||
|
||||
func getTerminalWidth() int {
|
||||
// Try to get actual terminal width
|
||||
@@ -93,7 +94,8 @@ func MultiSelect(title string, options []string, value *[]string) error {
|
||||
field := huh.NewMultiSelect[string]().
|
||||
Title(title).
|
||||
Options(makeStringOptions(options)...).
|
||||
Height(listHeight(len(options))).
|
||||
// huh.Select/MultiSelect Height includes title/description, so add 1 for the title line.
|
||||
Height(listHeight(len(options)) + 1).
|
||||
Value(value)
|
||||
|
||||
form := huh.NewForm(huh.NewGroup(field)).WithWidth(getTerminalWidth())
|
||||
@@ -115,7 +117,8 @@ func selectPrompt(title string, options []string, value *string, required bool)
|
||||
field := huh.NewSelect[string]().
|
||||
Title(title).
|
||||
Options(makeStringOptions(options)...).
|
||||
Height(listHeight(len(options))).
|
||||
// huh.Select Height includes title/description, so add 1 for the title line.
|
||||
Height(listHeight(len(options)) + 1).
|
||||
Value(value)
|
||||
|
||||
if required {
|
||||
@@ -147,10 +150,8 @@ func ensureSelectedValue(options []string, value *string) {
|
||||
*value = options[0]
|
||||
return
|
||||
}
|
||||
for _, option := range options {
|
||||
if option == *value {
|
||||
return
|
||||
}
|
||||
if slices.Contains(options, *value) {
|
||||
return
|
||||
}
|
||||
*value = options[0]
|
||||
}
|
||||
|
||||
@@ -202,6 +202,15 @@ else
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# server / c2 / e2e (dns)
|
||||
if SLIVER_ROOT_DIR="$SLIVER_ROOT_DIR_E2E" go test -tags=server,$TAGS,sliver_e2e ./server/c2 -run 'TestDNS_' -count=1 ; then
|
||||
:
|
||||
else
|
||||
cat "$SLIVER_ROOT_DIR_E2E/logs/sliver.log" 2>/dev/null || true
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
exit 1
|
||||
fi
|
||||
rm -rf "$SLIVER_ROOT_DIR_E2E"
|
||||
|
||||
# server / configs
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
"log"
|
||||
// {{end}}
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
maxTrackedExecuteChildren = 256
|
||||
)
|
||||
|
||||
type executeChildEntry struct {
|
||||
PID int32
|
||||
Path string
|
||||
Args []string
|
||||
Stdout string
|
||||
Stderr string
|
||||
StartTime int64
|
||||
|
||||
Exited bool
|
||||
ExitCode int32
|
||||
ExitTime int64
|
||||
Error string
|
||||
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
var (
|
||||
executeChildrenMu sync.RWMutex
|
||||
trackedExecuteChildren = map[int32]*executeChildEntry{}
|
||||
)
|
||||
|
||||
func trimTrackedExecuteChildrenLocked() {
|
||||
if len(trackedExecuteChildren) <= maxTrackedExecuteChildren {
|
||||
return
|
||||
}
|
||||
|
||||
// Prefer trimming completed entries first, oldest exit time first.
|
||||
exited := make([]*executeChildEntry, 0, len(trackedExecuteChildren))
|
||||
for _, entry := range trackedExecuteChildren {
|
||||
if entry.Exited {
|
||||
exited = append(exited, entry)
|
||||
}
|
||||
}
|
||||
sort.Slice(exited, func(i, j int) bool {
|
||||
return exited[i].ExitTime < exited[j].ExitTime
|
||||
})
|
||||
|
||||
for len(trackedExecuteChildren) > maxTrackedExecuteChildren && 0 < len(exited) {
|
||||
delete(trackedExecuteChildren, exited[0].PID)
|
||||
exited = exited[1:]
|
||||
}
|
||||
}
|
||||
|
||||
func startExecuteChild(cmd *exec.Cmd, track bool, path string, args []string, stdoutPath string, stderrPath string) (int32, error) {
|
||||
var (
|
||||
stdoutFile *os.File
|
||||
stderrFile *os.File
|
||||
err error
|
||||
)
|
||||
|
||||
startTime := time.Now().Unix()
|
||||
|
||||
if stdoutPath != "" {
|
||||
stdoutFile, err = os.Create(stdoutPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
cmd.Stdout = stdoutFile
|
||||
}
|
||||
if stderrPath != "" {
|
||||
stderrFile, err = os.Create(stderrPath)
|
||||
if err != nil {
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
cmd.Stderr = stderrFile
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
if stderrFile != nil {
|
||||
stderrFile.Close()
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var pid int32
|
||||
if cmd.Process != nil {
|
||||
pid = int32(cmd.Process.Pid)
|
||||
}
|
||||
|
||||
if track && pid != 0 {
|
||||
entry := &executeChildEntry{
|
||||
PID: pid,
|
||||
Path: path,
|
||||
Args: append([]string(nil), args...),
|
||||
Stdout: stdoutPath,
|
||||
Stderr: stderrPath,
|
||||
StartTime: startTime,
|
||||
cmd: cmd,
|
||||
}
|
||||
executeChildrenMu.Lock()
|
||||
trackedExecuteChildren[pid] = entry
|
||||
trimTrackedExecuteChildrenLocked()
|
||||
executeChildrenMu.Unlock()
|
||||
}
|
||||
|
||||
go func(pid int32, cmd *exec.Cmd, stdoutFile *os.File, stderrFile *os.File, track bool) {
|
||||
waitErr := cmd.Wait()
|
||||
|
||||
exitCode := int32(0)
|
||||
waitErrStr := ""
|
||||
if waitErr != nil {
|
||||
if exitErr, ok := waitErr.(*exec.ExitError); ok {
|
||||
exitCode = int32(exitErr.ExitCode())
|
||||
} else {
|
||||
exitCode = -1
|
||||
waitErrStr = waitErr.Error()
|
||||
}
|
||||
}
|
||||
|
||||
if track && pid != 0 {
|
||||
executeChildrenMu.Lock()
|
||||
entry := trackedExecuteChildren[pid]
|
||||
if entry != nil {
|
||||
entry.Exited = true
|
||||
entry.ExitCode = exitCode
|
||||
entry.ExitTime = time.Now().Unix()
|
||||
entry.Error = waitErrStr
|
||||
entry.cmd = nil
|
||||
}
|
||||
executeChildrenMu.Unlock()
|
||||
}
|
||||
|
||||
if stdoutFile != nil {
|
||||
stdoutFile.Close()
|
||||
}
|
||||
if stderrFile != nil {
|
||||
stderrFile.Close()
|
||||
}
|
||||
}(pid, cmd, stdoutFile, stderrFile, track)
|
||||
|
||||
return pid, nil
|
||||
}
|
||||
|
||||
func snapshotExecuteChildren() []*sliverpb.ExecuteChild {
|
||||
executeChildrenMu.RLock()
|
||||
children := make([]*sliverpb.ExecuteChild, 0, len(trackedExecuteChildren))
|
||||
for _, entry := range trackedExecuteChildren {
|
||||
children = append(children, &sliverpb.ExecuteChild{
|
||||
Pid: entry.PID,
|
||||
Path: entry.Path,
|
||||
Args: append([]string(nil), entry.Args...),
|
||||
StartTime: entry.StartTime,
|
||||
Exited: entry.Exited,
|
||||
ExitCode: entry.ExitCode,
|
||||
ExitTime: entry.ExitTime,
|
||||
Stdout: entry.Stdout,
|
||||
Stderr: entry.Stderr,
|
||||
Error: entry.Error,
|
||||
})
|
||||
}
|
||||
executeChildrenMu.RUnlock()
|
||||
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].StartTime < children[j].StartTime
|
||||
})
|
||||
return children
|
||||
}
|
||||
|
||||
func executeChildrenHandler(data []byte, resp RPCResponse) {
|
||||
req := &sliverpb.ExecuteChildrenReq{}
|
||||
err := proto.Unmarshal(data, req)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("error decoding message: %v", err)
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
|
||||
execChildren := &sliverpb.ExecuteChildren{
|
||||
Children: snapshotExecuteChildren(),
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
data, err = proto.Marshal(execChildren)
|
||||
resp(data, err)
|
||||
}
|
||||
@@ -1181,7 +1181,7 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
if execReq.Output && !execReq.Background {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
@@ -1242,19 +1242,14 @@ func executeHandler(data []byte, resp RPCResponse) {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||
if startErr != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
Err: fmt.Sprintf("%s", startErr),
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
}()
|
||||
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
if pid != 0 {
|
||||
execResp.Pid = uint32(pid)
|
||||
}
|
||||
}
|
||||
data, err = proto.Marshal(execResp)
|
||||
|
||||
@@ -33,25 +33,26 @@ import (
|
||||
|
||||
var (
|
||||
darwinHandlers = map[uint32]RPCHandler{
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgChtimesReq: chtimesHandler,
|
||||
pb.MsgGrepReq: grepHandler,
|
||||
pb.MsgPsReq: psHandler,
|
||||
pb.MsgTerminateReq: terminateHandler,
|
||||
pb.MsgPing: pingHandler,
|
||||
pb.MsgLsReq: dirListHandler,
|
||||
pb.MsgDownloadReq: downloadHandler,
|
||||
pb.MsgUploadReq: uploadHandler,
|
||||
pb.MsgCdReq: cdHandler,
|
||||
pb.MsgPwdReq: pwdHandler,
|
||||
pb.MsgRmReq: rmHandler,
|
||||
pb.MsgMkdirReq: mkdirHandler,
|
||||
pb.MsgMvReq: mvHandler,
|
||||
pb.MsgCpReq: cpHandler,
|
||||
pb.MsgIfconfigReq: ifconfigHandler,
|
||||
pb.MsgExecuteReq: executeHandler,
|
||||
pb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
pb.MsgEnvReq: getEnvHandler,
|
||||
pb.MsgSetEnvReq: setEnvHandler,
|
||||
pb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
pb.MsgChtimesReq: chtimesHandler,
|
||||
pb.MsgGrepReq: grepHandler,
|
||||
|
||||
pb.MsgScreenshotReq: screenshotHandler,
|
||||
pb.MsgNetstatReq: netstatHandler,
|
||||
|
||||
@@ -34,23 +34,24 @@ import (
|
||||
|
||||
var (
|
||||
genericHandlers = map[uint32]RPCHandler{
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Wasm Extensions - Note that execution can be done via a tunnel handler
|
||||
sliverpb.MsgRegisterWasmExtensionReq: registerWasmExtensionHandler,
|
||||
|
||||
@@ -45,24 +45,25 @@ import (
|
||||
|
||||
var (
|
||||
linuxHandlers = map[uint32]RPCHandler{
|
||||
sliverpb.MsgPsReq: psHandler,
|
||||
sliverpb.MsgTerminateReq: terminateHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
sliverpb.MsgPsReq: psHandler,
|
||||
sliverpb.MsgTerminateReq: terminateHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgTaskReq: taskHandler,
|
||||
sliverpb.MsgIfconfigReq: ifconfigLinuxHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgEnvReq: getEnvHandler,
|
||||
sliverpb.MsgSetEnvReq: setEnvHandler,
|
||||
sliverpb.MsgUnsetEnvReq: unsetEnvHandler,
|
||||
|
||||
sliverpb.MsgScreenshotReq: screenshotHandler,
|
||||
|
||||
|
||||
@@ -96,21 +96,22 @@ var (
|
||||
sliverpb.MsgMountReq: mountHandler,
|
||||
|
||||
// Generic
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
sliverpb.MsgPing: pingHandler,
|
||||
sliverpb.MsgLsReq: dirListHandler,
|
||||
sliverpb.MsgDownloadReq: downloadHandler,
|
||||
sliverpb.MsgUploadReq: uploadHandler,
|
||||
sliverpb.MsgCdReq: cdHandler,
|
||||
sliverpb.MsgPwdReq: pwdHandler,
|
||||
sliverpb.MsgRmReq: rmHandler,
|
||||
sliverpb.MsgMvReq: mvHandler,
|
||||
sliverpb.MsgCpReq: cpHandler,
|
||||
sliverpb.MsgMkdirReq: mkdirHandler,
|
||||
sliverpb.MsgExecuteReq: executeHandler,
|
||||
sliverpb.MsgExecuteChildrenReq: executeChildrenHandler,
|
||||
sliverpb.MsgReconfigureReq: reconfigureHandler,
|
||||
sliverpb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
sliverpb.MsgChtimesReq: chtimesHandler,
|
||||
sliverpb.MsgGrepReq: grepHandler,
|
||||
|
||||
// Extensions
|
||||
sliverpb.MsgRegisterExtensionReq: registerExtensionHandler,
|
||||
@@ -386,7 +387,7 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
if execReq.Output {
|
||||
if execReq.Output && !execReq.Background {
|
||||
stdOutBuff := new(bytes.Buffer)
|
||||
stdErrBuff := new(bytes.Buffer)
|
||||
stdErr = stdErrBuff
|
||||
@@ -443,13 +444,19 @@ func executeWindowsHandler(data []byte, resp RPCResponse) {
|
||||
}
|
||||
execResp.Stderr = stdErrBuff.Bytes()
|
||||
execResp.Stdout = stdOutBuff.Bytes()
|
||||
if cmd.Process != nil {
|
||||
execResp.Pid = uint32(cmd.Process.Pid)
|
||||
}
|
||||
} else {
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
pid, startErr := startExecuteChild(cmd, execReq.Background, exePath, execReq.Args, execReq.Stdout, execReq.Stderr)
|
||||
if startErr != nil {
|
||||
execResp.Response = &commonpb.Response{
|
||||
Err: fmt.Sprintf("%s", err),
|
||||
Err: fmt.Sprintf("%s", startErr),
|
||||
}
|
||||
}
|
||||
if pid != 0 {
|
||||
execResp.Pid = uint32(pid)
|
||||
}
|
||||
}
|
||||
data, err = proto.Marshal(execResp)
|
||||
resp(data, err)
|
||||
|
||||
@@ -37,6 +37,8 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
|
||||
// {{end}}
|
||||
@@ -46,7 +48,6 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.IncludeWG}}
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
@@ -208,6 +209,9 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
return errors.New("[mtls] connect returned nil conn")
|
||||
}
|
||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
@@ -217,21 +221,40 @@ func mtlsBeacon(uri *url.URL) *Beacon {
|
||||
_ = conn.Close()
|
||||
return err
|
||||
}
|
||||
if muxSession == nil {
|
||||
_ = conn.Close()
|
||||
return errors.New("[mtls] failed to create yamux session (nil)")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Recv: func() (*pb.Envelope, error) {
|
||||
if muxSession == nil {
|
||||
return nil, errors.New("[mtls] recv called with nil yamux session")
|
||||
}
|
||||
stream, err := muxSession.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return nil, errors.New("[mtls] accept returned nil stream")
|
||||
}
|
||||
defer stream.Close()
|
||||
return mtls.ReadEnvelope(stream)
|
||||
},
|
||||
Send: func(envelope *pb.Envelope) error {
|
||||
if envelope == nil {
|
||||
return errors.New("[mtls] nil envelope")
|
||||
}
|
||||
if muxSession == nil {
|
||||
return errors.New("[mtls] send called with nil yamux session")
|
||||
}
|
||||
stream, err := muxSession.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return errors.New("[mtls] open returned nil stream")
|
||||
}
|
||||
defer stream.Close()
|
||||
return mtls.WriteEnvelope(stream, envelope)
|
||||
},
|
||||
|
||||
@@ -46,6 +46,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"hash/crc32"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -278,24 +279,23 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
}
|
||||
s.resolvers = []DNSResolver{}
|
||||
for _, server := range s.resolvConf.Servers {
|
||||
address, port := splitResolverAddress(server, s.resolvConf.Port)
|
||||
s.resolvers = append(s.resolvers,
|
||||
NewGenericResolver(server, s.resolvConf.Port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
NewGenericResolver(address, port, s.retryWait, s.retryCount, s.queryTimeout, s.parent),
|
||||
)
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] found resolvers: %v", s.resolvConf.Servers)
|
||||
// {{end}}
|
||||
|
||||
err = s.getDNSSessionID() // Get a 'dns session id'
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.fingerprintResolvers() // Fingerprint the resolvers
|
||||
if len(s.resolvers) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no working resolvers!")
|
||||
// {{end}}
|
||||
return errNoResolvers
|
||||
// Generate a random session ID locally. The server will only allocate state
|
||||
// after a successful INIT, which avoids an unauthenticated session bootstrap.
|
||||
if s.dnsSessionID == 0 {
|
||||
sid, err := s.randomDNSSessionID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.dnsSessionID = sid
|
||||
}
|
||||
|
||||
// Key agreement with server
|
||||
@@ -308,39 +308,40 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
resolver, _ := s.randomResolver()
|
||||
initMsg := &dnspb.DNSMessage{
|
||||
ID: s.nextMsgID(),
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
Size: uint32(len(initData)),
|
||||
}
|
||||
respData, err := s.sendInit(resolver, s.base32, initMsg, initData)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg send failure %v", err)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
|
||||
if len(respData) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no data received in message")
|
||||
// {{end}}
|
||||
return ErrEmptyResponse
|
||||
var respData []byte
|
||||
var initErr error
|
||||
for _, resolver := range s.resolvers {
|
||||
respData, initErr = s.sendInit(resolver, s.base32, initMsg, initData)
|
||||
if initErr != nil {
|
||||
continue
|
||||
}
|
||||
if len(respData) < 1 {
|
||||
initErr = ErrEmptyResponse
|
||||
continue
|
||||
}
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
initErr = err
|
||||
continue
|
||||
}
|
||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
||||
initErr = errInvalidDNSSessionID
|
||||
continue
|
||||
}
|
||||
initErr = nil
|
||||
break
|
||||
}
|
||||
|
||||
data, err := s.cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
if initErr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg decryption failure %v", err)
|
||||
log.Printf("[dns] init msg send failure %v", initErr)
|
||||
// {{end}}
|
||||
return err
|
||||
}
|
||||
if binary.LittleEndian.Uint32(data)&sessionIDBitMask != s.dnsSessionID {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] init msg dns session id mismatch")
|
||||
// {{end}}
|
||||
return err
|
||||
return initErr
|
||||
}
|
||||
|
||||
// Good to go!
|
||||
@@ -348,6 +349,16 @@ func (s *SliverDNSClient) SessionInit() error {
|
||||
log.Printf("[dns] key exchange was successful!")
|
||||
// {{end}}
|
||||
|
||||
// Now that we have a server-side session, benchmark/fingerprint resolvers and
|
||||
// drop ones that error.
|
||||
s.fingerprintResolvers()
|
||||
if len(s.resolvers) < 1 {
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] no working resolvers!")
|
||||
// {{end}}
|
||||
return errNoResolvers
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] starting worker(s) ...")
|
||||
// {{end}}
|
||||
@@ -778,40 +789,19 @@ func (s *SliverDNSClient) SplitBuffer(msg *dnspb.DNSMessage, encoder encoders.En
|
||||
return subdata, nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) getDNSSessionID() error {
|
||||
otpMsg, err := s.otpMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
otpDomain, err := s.joinSubdataToParent(otpMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] Fetching dns session id via '%s' ...", otpDomain)
|
||||
// {{end}}
|
||||
|
||||
var a []byte
|
||||
for _, resolver := range s.resolvers {
|
||||
a, _, err = resolver.A(otpDomain)
|
||||
if err == nil {
|
||||
break
|
||||
func (s *SliverDNSClient) randomDNSSessionID() (uint32, error) {
|
||||
// sessionID is 24-bit and must be non-zero.
|
||||
randBuf := make([]byte, 4)
|
||||
for {
|
||||
_, err := rand.Read(randBuf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
sid := binary.LittleEndian.Uint32(randBuf) & sessionIDBitMask
|
||||
if sid != 0 {
|
||||
return sid, nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err // All resolvers failed
|
||||
}
|
||||
if len(a) < 1 {
|
||||
return errInvalidDNSSessionID
|
||||
}
|
||||
s.dnsSessionID = binary.LittleEndian.Uint32(a) & sessionIDBitMask
|
||||
if s.dnsSessionID == 0 {
|
||||
return errInvalidDNSSessionID
|
||||
}
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[dns] dns session id: %d", s.dnsSessionID)
|
||||
// {{end}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) loadResolvConf() error {
|
||||
@@ -847,6 +837,24 @@ func (s *SliverDNSClient) loadResolvConf() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func splitResolverAddress(server string, defaultPort string) (string, string) {
|
||||
server = strings.TrimSpace(server)
|
||||
if server == "" {
|
||||
return "", defaultPort
|
||||
}
|
||||
|
||||
// Allow tests/operators to specify an explicit port, e.g. "127.0.0.1:5353"
|
||||
// or "[::1]:5353". Raw IPv6 addresses in resolv.conf typically do not include
|
||||
// a port and will fail SplitHostPort, in which case we fall back to defaultPort.
|
||||
if host, port, err := net.SplitHostPort(server); err == nil {
|
||||
return host, port
|
||||
}
|
||||
if strings.HasPrefix(server, "[") && strings.HasSuffix(server, "]") {
|
||||
return strings.TrimSuffix(strings.TrimPrefix(server, "["), "]"), defaultPort
|
||||
}
|
||||
return server, defaultPort
|
||||
}
|
||||
|
||||
// Joins subdata to the parent domain, you must have already done the math to
|
||||
// ensure the subdata can fit in the domain
|
||||
func (s *SliverDNSClient) joinSubdataToParent(subdata string) (string, error) {
|
||||
@@ -893,19 +901,6 @@ func (s *SliverDNSClient) clearMsg(msgId uint32) (string, error) {
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
func (s *SliverDNSClient) otpMsg() (string, error) {
|
||||
otpMsg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_TOTP,
|
||||
ID: uint32(0), // Take advantage of the variable length encoding
|
||||
}
|
||||
data, err := proto.Marshal(otpMsg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
msg, _ := s.base32.Encode(data)
|
||||
return string(msg), nil
|
||||
}
|
||||
|
||||
// fingerprintResolver - Fingerprints resolve to validate base32 transport.
|
||||
func (s *SliverDNSClient) fingerprintResolvers() {
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
@@ -230,28 +230,17 @@ func clientSplitBuffer(t *testing.T, client *SliverDNSClient, encoder encoders.E
|
||||
}
|
||||
}
|
||||
|
||||
func TestOTPMsgRoundTrip(t *testing.T) {
|
||||
func TestRandomDNSSessionID(t *testing.T) {
|
||||
client := NewDNSClient(parent1, opts)
|
||||
encoded, err := client.otpMsg()
|
||||
sid, err := client.randomDNSSessionID()
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected otpMsg error: %s", err)
|
||||
t.Fatalf("Unexpected randomDNSSessionID error: %s", err)
|
||||
}
|
||||
if !isBase32String(encoded) {
|
||||
t.Fatalf("otpMsg contains non-base32 characters: %s", encoded)
|
||||
if sid == 0 {
|
||||
t.Fatal("expected non-zero dns session id")
|
||||
}
|
||||
raw, err := encoders.Base32{}.Decode([]byte(encoded))
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected otpMsg decode error: %s", err)
|
||||
}
|
||||
msg := &dnspb.DNSMessage{}
|
||||
if err := proto.Unmarshal(raw, msg); err != nil {
|
||||
t.Fatalf("Unexpected otpMsg unmarshal error: %s", err)
|
||||
}
|
||||
if msg.Type != dnspb.DNSMessageType_TOTP {
|
||||
t.Fatalf("Unexpected otpMsg type: %v", msg.Type)
|
||||
}
|
||||
if msg.ID != 0 {
|
||||
t.Fatalf("Unexpected otpMsg id: %d", msg.ID)
|
||||
if (sid & ^uint32(sessionIDBitMask)) != 0 {
|
||||
t.Fatalf("expected 24-bit session id, got: 0x%08x", sid)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -44,7 +45,7 @@ func NewGenericResolver(address string, port string, retryWait time.Duration, re
|
||||
retries = 1
|
||||
}
|
||||
return &GenericResolver{
|
||||
address: address + ":" + port,
|
||||
address: net.JoinHostPort(address, port),
|
||||
retries: retries,
|
||||
retryWait: retryWait,
|
||||
resolver: &dns.Client{
|
||||
|
||||
@@ -21,7 +21,6 @@ package mtls
|
||||
// {{if .Config.IncludeMTLS}}
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
@@ -30,6 +29,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -90,43 +90,77 @@ func mtlsEnvelopeSigningKey() (ed25519.PrivateKey, uint64, error) {
|
||||
return envelopeSigningPriv, envelopeSigningKeyID, envelopeSigningErr
|
||||
}
|
||||
|
||||
func isNilInterface(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func writeAll(w io.Writer, p []byte) error {
|
||||
for len(p) > 0 {
|
||||
n, err := w.Write(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n <= 0 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteEnvelope - Writes a message to the TLS socket using length prefix framing
|
||||
// which is a fancy way of saying we write the length of the message then the message
|
||||
// e.g. [uint32 length|message] so the receiver can delimit messages properly
|
||||
func WriteEnvelope(w io.Writer, envelope *pb.Envelope) error {
|
||||
if envelope == nil {
|
||||
return errors.New("[mtls] nil envelope")
|
||||
}
|
||||
if isNilInterface(w) {
|
||||
return errors.New("[mtls] nil writer")
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(envelope)
|
||||
if err != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Envelope marshaling error: ", err)
|
||||
// {{end}}
|
||||
return err
|
||||
return fmt.Errorf("[mtls] marshal envelope: %w", err)
|
||||
}
|
||||
|
||||
signingKey, keyID, err := mtlsEnvelopeSigningKey()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("[mtls] envelope signing key: %w", err)
|
||||
}
|
||||
rawSigBuf := make([]byte, cryptography.RawSigSize)
|
||||
binary.LittleEndian.PutUint16(rawSigBuf[:2], cryptography.EdDSA)
|
||||
binary.LittleEndian.PutUint64(rawSigBuf[2:10], keyID)
|
||||
copy(rawSigBuf[10:], ed25519.Sign(signingKey, data))
|
||||
if _, werr := w.Write(rawSigBuf); werr != nil {
|
||||
return werr
|
||||
if werr := writeAll(w, rawSigBuf); werr != nil {
|
||||
return fmt.Errorf("[mtls] write raw signature: %w", werr)
|
||||
}
|
||||
|
||||
dataLengthBuf := new(bytes.Buffer)
|
||||
binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(data)))
|
||||
if _, werr := w.Write(dataLengthBuf.Bytes()); werr != nil {
|
||||
var dataLengthBuf [4]byte
|
||||
binary.LittleEndian.PutUint32(dataLengthBuf[:], uint32(len(data)))
|
||||
if werr := writeAll(w, dataLengthBuf[:]); werr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Error writing data length: ", werr)
|
||||
// {{end}}
|
||||
return werr
|
||||
return fmt.Errorf("[mtls] write data length: %w", werr)
|
||||
}
|
||||
if _, werr := w.Write(data); werr != nil {
|
||||
if werr := writeAll(w, data); werr != nil {
|
||||
// {{if .Config.Debug}}
|
||||
log.Print("Error writing data: ", werr)
|
||||
// {{end}}
|
||||
return werr
|
||||
return fmt.Errorf("[mtls] write data: %w", werr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package mtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"testing"
|
||||
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
)
|
||||
|
||||
func TestWriteEnvelope_NilEnvelope(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := WriteEnvelope(&buf, nil); err == nil {
|
||||
t.Fatalf("expected error for nil envelope")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteEnvelope_NilWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("WriteEnvelope panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := WriteEnvelope(nil, &pb.Envelope{}); err == nil {
|
||||
t.Fatalf("expected error for nil writer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteEnvelope_NilTLSConnWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("WriteEnvelope panicked: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var conn *tls.Conn
|
||||
if err := WriteEnvelope(conn, &pb.Envelope{}); err == nil {
|
||||
t.Fatalf("expected error for nil *tls.Conn writer")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package transports
|
||||
|
||||
import "reflect"
|
||||
|
||||
// isNilInterface returns true if v is nil, or if v is an interface value holding a nil pointer.
|
||||
func isNilInterface(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if or .Config.IncludeMTLS .Config.IncludeWG}}
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
@@ -45,8 +47,6 @@ import (
|
||||
// {{end}}
|
||||
|
||||
// {{if .Config.IncludeWG}}
|
||||
"errors"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/wireguard"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
|
||||
@@ -257,6 +257,13 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn == nil {
|
||||
errMsg := ""
|
||||
// {{if .Config.Debug}}
|
||||
errMsg = "mtls connection returned nil conn"
|
||||
// {{end}}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil {
|
||||
conn.Close()
|
||||
return err
|
||||
@@ -266,6 +273,14 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
if muxSession == nil {
|
||||
conn.Close()
|
||||
errMsg := ""
|
||||
// {{if .Config.Debug}}
|
||||
errMsg = "[mtls] failed to create yamux session (nil)"
|
||||
// {{end}}
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
connection.IsOpen = true
|
||||
|
||||
go func() {
|
||||
@@ -292,6 +307,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
if err := mtls.WriteEnvelope(stream, envelope); err != nil {
|
||||
connection.Cleanup()
|
||||
@@ -315,6 +334,10 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
connection.Cleanup()
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
if err := mtls.WritePing(stream); err != nil {
|
||||
connection.Cleanup()
|
||||
@@ -346,6 +369,9 @@ func mtlsConnect(uri *url.URL) (*Connection, error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if isNilInterface(stream) {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case streamSem <- struct{}{}:
|
||||
|
||||
+12
-19
@@ -24,7 +24,6 @@ type DNSMessageType int32
|
||||
|
||||
const (
|
||||
DNSMessageType_NOP DNSMessageType = 0 // aka FINGERPRINT
|
||||
DNSMessageType_TOTP DNSMessageType = 1
|
||||
DNSMessageType_INIT DNSMessageType = 2
|
||||
DNSMessageType_POLL DNSMessageType = 3
|
||||
DNSMessageType_CLOSE DNSMessageType = 4
|
||||
@@ -38,7 +37,6 @@ const (
|
||||
var (
|
||||
DNSMessageType_name = map[int32]string{
|
||||
0: "NOP",
|
||||
1: "TOTP",
|
||||
2: "INIT",
|
||||
3: "POLL",
|
||||
4: "CLOSE",
|
||||
@@ -49,7 +47,6 @@ var (
|
||||
}
|
||||
DNSMessageType_value = map[string]int32{
|
||||
"NOP": 0,
|
||||
"TOTP": 1,
|
||||
"INIT": 2,
|
||||
"POLL": 3,
|
||||
"CLOSE": 4,
|
||||
@@ -89,10 +86,7 @@ func (DNSMessageType) EnumDescriptor() ([]byte, []int) {
|
||||
|
||||
//
|
||||
//NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
//depending on the DNSMessageType as noted below:
|
||||
//
|
||||
//[Type TOTP]: ID field is used for the TOTP code
|
||||
//
|
||||
//depending on the DNSMessageType.
|
||||
type DNSMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -194,19 +188,18 @@ var file_dnspb_dns_proto_rawDesc = []byte{
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x53, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x53, 0x69, 0x7a, 0x65,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,
|
||||
0x44, 0x61, 0x74, 0x61, 0x2a, 0x87, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x44, 0x61, 0x74, 0x61, 0x2a, 0x83, 0x01, 0x0a, 0x0e, 0x44, 0x4e, 0x53, 0x4d, 0x65, 0x73, 0x73,
|
||||
0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x4e, 0x4f, 0x50, 0x10, 0x00,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x54, 0x4f, 0x54, 0x50, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e,
|
||||
0x49, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x4c, 0x4c, 0x10, 0x03, 0x12, 0x09,
|
||||
0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x41, 0x4e,
|
||||
0x49, 0x46, 0x45, 0x53, 0x54, 0x10, 0x06, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x41, 0x54, 0x41, 0x5f,
|
||||
0x54, 0x4f, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54, 0x10, 0x07, 0x12, 0x15, 0x0a, 0x11,
|
||||
0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e,
|
||||
0x54, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x45, 0x41, 0x52, 0x10, 0x09, 0x42, 0x2c,
|
||||
0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73,
|
||||
0x68, 0x6f, 0x70, 0x66, 0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f,
|
||||
0x4c, 0x4c, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x04, 0x12,
|
||||
0x0c, 0x0a, 0x08, 0x4d, 0x41, 0x4e, 0x49, 0x46, 0x45, 0x53, 0x54, 0x10, 0x06, 0x12, 0x13, 0x0a,
|
||||
0x0f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x54, 0x4f, 0x5f, 0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54,
|
||||
0x10, 0x07, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f,
|
||||
0x49, 0x4d, 0x50, 0x4c, 0x41, 0x4e, 0x54, 0x10, 0x08, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x4c, 0x45,
|
||||
0x41, 0x52, 0x10, 0x09, 0x22, 0x04, 0x08, 0x01, 0x10, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x69, 0x73, 0x68, 0x6f, 0x70, 0x66,
|
||||
0x6f, 0x78, 0x2f, 0x73, 0x6c, 0x69, 0x76, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2f, 0x64, 0x6e, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -4,7 +4,7 @@ option go_package = "github.com/bishopfox/sliver/protobuf/dnspb";
|
||||
|
||||
enum DNSMessageType {
|
||||
NOP = 0; // aka FINGERPRINT
|
||||
TOTP = 1;
|
||||
reserved 1; // formerly TOTP
|
||||
INIT = 2;
|
||||
POLL = 3;
|
||||
CLOSE = 4;
|
||||
@@ -17,10 +17,7 @@ enum DNSMessageType {
|
||||
|
||||
/*
|
||||
NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
depending on the DNSMessageType as noted below:
|
||||
|
||||
[Type TOTP]: ID field is used for the TOTP code
|
||||
|
||||
depending on the DNSMessageType.
|
||||
*/
|
||||
message DNSMessage {
|
||||
DNSMessageType Type = 1;
|
||||
@@ -29,4 +26,4 @@ message DNSMessage {
|
||||
uint32 Stop = 4; // Bytes stop at
|
||||
uint32 Size = 5; // Total size
|
||||
bytes Data = 6; // Actual data
|
||||
}
|
||||
}
|
||||
+638
-629
File diff suppressed because it is too large.
Load diff
@@ -195,6 +195,7 @@ service SliverRPC {
|
||||
rpc Migrate(clientpb.MigrateReq) returns (sliverpb.Migrate);
|
||||
rpc Execute(sliverpb.ExecuteReq) returns (sliverpb.Execute);
|
||||
rpc ExecuteWindows(sliverpb.ExecuteWindowsReq) returns (sliverpb.Execute);
|
||||
rpc ExecuteChildren(sliverpb.ExecuteChildrenReq) returns (sliverpb.ExecuteChildren);
|
||||
rpc Sideload(sliverpb.SideloadReq) returns (sliverpb.Sideload);
|
||||
rpc SpawnDll(sliverpb.InvokeSpawnDllReq) returns (sliverpb.SpawnDll);
|
||||
rpc Screenshot(sliverpb.ScreenshotReq) returns (sliverpb.Screenshot);
|
||||
|
||||
@@ -169,6 +169,7 @@ type SliverRPCClient interface {
|
||||
Migrate(ctx context.Context, in *clientpb.MigrateReq, opts ...grpc.CallOption) (*sliverpb.Migrate, error)
|
||||
Execute(ctx context.Context, in *sliverpb.ExecuteReq, opts ...grpc.CallOption) (*sliverpb.Execute, error)
|
||||
ExecuteWindows(ctx context.Context, in *sliverpb.ExecuteWindowsReq, opts ...grpc.CallOption) (*sliverpb.Execute, error)
|
||||
ExecuteChildren(ctx context.Context, in *sliverpb.ExecuteChildrenReq, opts ...grpc.CallOption) (*sliverpb.ExecuteChildren, error)
|
||||
Sideload(ctx context.Context, in *sliverpb.SideloadReq, opts ...grpc.CallOption) (*sliverpb.Sideload, error)
|
||||
SpawnDll(ctx context.Context, in *sliverpb.InvokeSpawnDllReq, opts ...grpc.CallOption) (*sliverpb.SpawnDll, error)
|
||||
Screenshot(ctx context.Context, in *sliverpb.ScreenshotReq, opts ...grpc.CallOption) (*sliverpb.Screenshot, error)
|
||||
@@ -1458,6 +1459,15 @@ func (c *sliverRPCClient) ExecuteWindows(ctx context.Context, in *sliverpb.Execu
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) ExecuteChildren(ctx context.Context, in *sliverpb.ExecuteChildrenReq, opts ...grpc.CallOption) (*sliverpb.ExecuteChildren, error) {
|
||||
out := new(sliverpb.ExecuteChildren)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/ExecuteChildren", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) Sideload(ctx context.Context, in *sliverpb.SideloadReq, opts ...grpc.CallOption) (*sliverpb.Sideload, error) {
|
||||
out := new(sliverpb.Sideload)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/Sideload", in, out, opts...)
|
||||
@@ -2181,6 +2191,7 @@ type SliverRPCServer interface {
|
||||
Migrate(context.Context, *clientpb.MigrateReq) (*sliverpb.Migrate, error)
|
||||
Execute(context.Context, *sliverpb.ExecuteReq) (*sliverpb.Execute, error)
|
||||
ExecuteWindows(context.Context, *sliverpb.ExecuteWindowsReq) (*sliverpb.Execute, error)
|
||||
ExecuteChildren(context.Context, *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error)
|
||||
Sideload(context.Context, *sliverpb.SideloadReq) (*sliverpb.Sideload, error)
|
||||
SpawnDll(context.Context, *sliverpb.InvokeSpawnDllReq) (*sliverpb.SpawnDll, error)
|
||||
Screenshot(context.Context, *sliverpb.ScreenshotReq) (*sliverpb.Screenshot, error)
|
||||
@@ -2634,6 +2645,9 @@ func (UnimplementedSliverRPCServer) Execute(context.Context, *sliverpb.ExecuteRe
|
||||
func (UnimplementedSliverRPCServer) ExecuteWindows(context.Context, *sliverpb.ExecuteWindowsReq) (*sliverpb.Execute, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteWindows not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) ExecuteChildren(context.Context, *sliverpb.ExecuteChildrenReq) (*sliverpb.ExecuteChildren, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ExecuteChildren not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) Sideload(context.Context, *sliverpb.SideloadReq) (*sliverpb.Sideload, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Sideload not implemented")
|
||||
}
|
||||
@@ -5115,6 +5129,24 @@ func _SliverRPC_ExecuteWindows_Handler(srv interface{}, ctx context.Context, dec
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_ExecuteChildren_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.ExecuteChildrenReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).ExecuteChildren(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/ExecuteChildren",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).ExecuteChildren(ctx, req.(*sliverpb.ExecuteChildrenReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_Sideload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(sliverpb.SideloadReq)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -6645,6 +6677,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "ExecuteWindows",
|
||||
Handler: _SliverRPC_ExecuteWindows_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ExecuteChildren",
|
||||
Handler: _SliverRPC_ExecuteChildren_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Sideload",
|
||||
Handler: _SliverRPC_Sideload_Handler,
|
||||
|
||||
@@ -355,6 +355,9 @@ const (
|
||||
|
||||
// MsgShellResizeReq - Resize a shell PTY
|
||||
MsgShellResizeReq
|
||||
|
||||
// MsgExecuteChildrenReq - List tracked background execute child processes
|
||||
MsgExecuteChildrenReq
|
||||
)
|
||||
|
||||
// Constants to replace enums
|
||||
@@ -460,6 +463,8 @@ func MsgNumber(request proto.Message) uint32 {
|
||||
return MsgIfconfig
|
||||
case *ExecuteReq:
|
||||
return MsgExecuteReq
|
||||
case *ExecuteChildrenReq:
|
||||
return MsgExecuteChildrenReq
|
||||
case *TerminateReq:
|
||||
return MsgTerminateReq
|
||||
case *Terminate:
|
||||
|
||||
+1955
-1645
File diff suppressed because it is too large.
Load diff
@@ -517,6 +517,7 @@ message ExecuteReq {
|
||||
string Stderr = 5;
|
||||
bool EnvInheritance = 6;
|
||||
map<string, string> Env = 7;
|
||||
bool Background = 8;
|
||||
|
||||
uint32 PPid = 10;
|
||||
|
||||
@@ -531,6 +532,7 @@ message ExecuteWindowsReq {
|
||||
string Stderr = 5;
|
||||
bool UseToken = 6;
|
||||
bool HideWindow = 7;
|
||||
bool Background = 8;
|
||||
uint32 PPid = 10;
|
||||
|
||||
commonpb.Request Request = 9;
|
||||
@@ -545,6 +547,31 @@ message Execute {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message ExecuteChildrenReq {
|
||||
commonpb.Request Request = 9;
|
||||
}
|
||||
|
||||
message ExecuteChild {
|
||||
int32 Pid = 1;
|
||||
string Path = 2;
|
||||
repeated string Args = 3;
|
||||
|
||||
int64 StartTime = 4;
|
||||
bool Exited = 5;
|
||||
int32 ExitCode = 6;
|
||||
int64 ExitTime = 7;
|
||||
|
||||
string Stdout = 8;
|
||||
string Stderr = 11;
|
||||
string Error = 12;
|
||||
}
|
||||
|
||||
message ExecuteChildren {
|
||||
repeated ExecuteChild Children = 1;
|
||||
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
message SideloadReq {
|
||||
bytes Data = 1;
|
||||
string ProcessName = 2;
|
||||
|
||||
+185
-66
@@ -21,14 +21,12 @@ package c2
|
||||
|
||||
We've put a little effort to making the server at least not super easily fingerprintable,
|
||||
though I'm guessing it's also still not super hard to do. The server must receive a valid
|
||||
TOTP code before we start returning any non-error records. All requests must be formatted
|
||||
as valid protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a
|
||||
8 bit "message ID." The server only responds to non-TOTP queries with valid dns session IDs
|
||||
16,777,216 can probably be bruteforced but it'll at least be slow.
|
||||
protobuf and contain a 24-bit "dns session ID" (16777216 possible values), and a 8 bit
|
||||
"message ID." 16,777,216 can probably be bruteforced but it'll at least be slow.
|
||||
|
||||
DNS command and control outline:
|
||||
1. Implant sends TOTP encoded message to DNS server, server checks validity
|
||||
2. DNS server responds with the "DNS Session ID" which is just some random value
|
||||
1. Implant generates a random DNS Session ID and sends an INIT (Age key exchange)
|
||||
2. DNS server validates INIT and allocates session state
|
||||
3. Requests with valid DNS session IDs enable the server to respond with CRC32 responses
|
||||
4. Implant establishes encrypted session
|
||||
|
||||
@@ -44,6 +42,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
@@ -66,6 +65,17 @@ const (
|
||||
messageIDBitMask = 0xff000000 // Bitwise mask to get the message ID
|
||||
|
||||
defaultMaxTXTLength = 254
|
||||
|
||||
// Upper bound on unauthenticated init reassembly state. INIT is split across multiple
|
||||
// DNS queries, so we cap the number of pending init messages to avoid unbounded memory use.
|
||||
defaultMaxPendingDNSInits = 1024
|
||||
|
||||
// Pending INIT messages are expected to complete quickly. If they don't, expire them.
|
||||
defaultPendingDNSInitTTL = 2 * time.Minute
|
||||
defaultPendingDNSInitGCInterval = 30 * time.Second
|
||||
|
||||
// INIT carries only key-exchange material and should remain small.
|
||||
defaultMaxDNSInitSize = 16 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -78,7 +88,7 @@ var (
|
||||
)
|
||||
|
||||
// StartDNSListener - Start a DNS listener
|
||||
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool, enforceOTP bool) *SliverDNSServer {
|
||||
func StartDNSListener(bindIface string, lport uint16, domains []string, canaries bool) *SliverDNSServer {
|
||||
// StartPivotListener()
|
||||
server := &SliverDNSServer{
|
||||
server: &dns.Server{Addr: fmt.Sprintf("%s:%d", bindIface, lport), Net: "udp"},
|
||||
@@ -86,8 +96,8 @@ func StartDNSListener(bindIface string, lport uint16, domains []string, canaries
|
||||
messages: &sync.Map{}, // In progress message streams
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
EnforceOTP: enforceOTP,
|
||||
}
|
||||
server.startInitGC()
|
||||
dnsLog.Infof("Starting DNS listener for %v (canaries: %v) ...", domains, canaries)
|
||||
dns.HandleFunc(".", func(writer dns.ResponseWriter, req *dns.Msg) {
|
||||
started := time.Now()
|
||||
@@ -103,6 +113,9 @@ type DNSSession struct {
|
||||
ImplantConn *core.ImplantConnection
|
||||
CipherCtx *cryptography.CipherContext
|
||||
|
||||
Created time.Time
|
||||
lastSeen atomic.Int64 // unix nanos
|
||||
|
||||
dnsIdMsgIdMap map[uint32]uint32
|
||||
outgoingMsgIDs []uint32
|
||||
outgoingBuffers map[uint32][]byte
|
||||
@@ -113,6 +126,10 @@ type DNSSession struct {
|
||||
msgCount uint32
|
||||
}
|
||||
|
||||
func (s *DNSSession) touch(now time.Time) {
|
||||
s.lastSeen.Store(now.UnixNano())
|
||||
}
|
||||
|
||||
func (s *DNSSession) msgID(id uint32) uint32 {
|
||||
return uint32(id<<24) | uint32(s.ID)
|
||||
}
|
||||
@@ -256,6 +273,13 @@ type PendingEnvelope struct {
|
||||
complete bool
|
||||
}
|
||||
|
||||
// pendingInit tracks the reassembly state for an INIT message (which may arrive
|
||||
// split across multiple DNS queries).
|
||||
type pendingInit struct {
|
||||
Created time.Time
|
||||
Envelope *PendingEnvelope
|
||||
}
|
||||
|
||||
// Reassemble - Reassemble a completed message
|
||||
func (p *PendingEnvelope) Reassemble() ([]byte, error) {
|
||||
// There's some weird race here with a nil pointer
|
||||
@@ -316,11 +340,23 @@ type SliverDNSServer struct {
|
||||
messages *sync.Map
|
||||
TTL uint32
|
||||
MaxTXTLength int
|
||||
EnforceOTP bool
|
||||
|
||||
pendingInits atomic.Int64
|
||||
gcStopOnce sync.Once
|
||||
gcStop chan struct{}
|
||||
gcDone chan struct{}
|
||||
}
|
||||
|
||||
// Shutdown - Shutdown the DNS server
|
||||
func (s *SliverDNSServer) Shutdown() error {
|
||||
s.gcStopOnce.Do(func() {
|
||||
if s.gcStop != nil {
|
||||
close(s.gcStop)
|
||||
if s.gcDone != nil {
|
||||
<-s.gcDone
|
||||
}
|
||||
}
|
||||
})
|
||||
return s.server.Shutdown()
|
||||
}
|
||||
|
||||
@@ -329,6 +365,56 @@ func (s *SliverDNSServer) ListenAndServe() error {
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) startInitGC() {
|
||||
// Tests construct SliverDNSServer directly; only start GC for real listeners.
|
||||
if s.gcStop != nil {
|
||||
return
|
||||
}
|
||||
s.gcStop = make(chan struct{})
|
||||
s.gcDone = make(chan struct{})
|
||||
go func() {
|
||||
ticker := time.NewTicker(defaultPendingDNSInitGCInterval)
|
||||
defer ticker.Stop()
|
||||
defer close(s.gcDone)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.gcPendingInits()
|
||||
case <-s.gcStop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) gcPendingInits() {
|
||||
now := time.Now()
|
||||
var deleted int64
|
||||
s.messages.Range(func(key, value any) bool {
|
||||
p, ok := value.(*pendingInit)
|
||||
if !ok || p == nil {
|
||||
if _, loaded := s.messages.LoadAndDelete(key); loaded {
|
||||
deleted++
|
||||
}
|
||||
return true
|
||||
}
|
||||
created := p.Created
|
||||
if created.IsZero() {
|
||||
created = now
|
||||
}
|
||||
if now.Sub(created) > defaultPendingDNSInitTTL {
|
||||
if _, loaded := s.messages.LoadAndDelete(key); loaded {
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if deleted > 0 {
|
||||
s.pendingInits.Add(-deleted)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// DNS Handler
|
||||
// ---------------------------
|
||||
@@ -354,6 +440,9 @@ func (s *SliverDNSServer) HandleDNSRequest(domains []string, canaries bool, writ
|
||||
resp = s.handleCanary(req)
|
||||
}
|
||||
if resp != nil {
|
||||
// These responses often have near-max-length QNAMEs. Enable compression to
|
||||
// keep UDP responses under the minimum DNS message size limits.
|
||||
resp.Compress = true
|
||||
writer.WriteMsg(resp)
|
||||
} else {
|
||||
dnsLog.Infof("Invalid query, no DNS response")
|
||||
@@ -384,24 +473,26 @@ func (s *SliverDNSServer) handleC2(domain string, req *dns.Msg) *dns.Msg {
|
||||
return s.nameErrorResp(req)
|
||||
}
|
||||
|
||||
// TOTP Handler can be called without dns session ID
|
||||
if msg.Type == dnspb.DNSMessageType_TOTP {
|
||||
return s.handleHello(domain, msg, req)
|
||||
// INIT can be called without an existing server-side session. The server only
|
||||
// stores session state after validating the key exchange.
|
||||
if msg.Type == dnspb.DNSMessageType_INIT {
|
||||
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
||||
}
|
||||
|
||||
// All other handlers require a valid dns session ID
|
||||
_, ok := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
sessionValue, ok := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
if !ok {
|
||||
dnsLog.Warnf("[dns] session not found for id %v (%v)", msg.ID, msg.ID&sessionIDBitMask)
|
||||
return s.nameErrorResp(req)
|
||||
}
|
||||
if session, ok := sessionValue.(*DNSSession); ok && session != nil {
|
||||
session.touch(time.Now())
|
||||
}
|
||||
|
||||
// Msg Type -> Handler
|
||||
switch msg.Type {
|
||||
case dnspb.DNSMessageType_NOP:
|
||||
return s.handleNOP(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_INIT:
|
||||
return s.handleDNSSessionInit(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_POLL:
|
||||
return s.handlePoll(domain, msg, checksum, req)
|
||||
case dnspb.DNSMessageType_DATA_FROM_IMPLANT:
|
||||
@@ -456,81 +547,86 @@ func (s *SliverDNSServer) accumulateInitData(msg *dnspb.DNSMessage) ([]byte, boo
|
||||
if msg.Size == 0 {
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
if msg.Size > defaultMaxDNSInitSize {
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
pendingValue, loaded := s.messages.Load(msg.ID)
|
||||
if !loaded {
|
||||
pendingValue = &PendingEnvelope{
|
||||
Size: msg.Size,
|
||||
received: uint32(0),
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
// Enforce a hard cap on pending INIT messages to prevent unbounded allocation.
|
||||
next := s.pendingInits.Add(1)
|
||||
if int64(defaultMaxPendingDNSInits) > 0 && next > int64(defaultMaxPendingDNSInits) {
|
||||
s.pendingInits.Add(-1)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
|
||||
pendingValue = &pendingInit{
|
||||
Created: time.Now(),
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: msg.Size,
|
||||
received: uint32(0),
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
}
|
||||
actual, loaded := s.messages.LoadOrStore(msg.ID, pendingValue)
|
||||
if loaded {
|
||||
// Another goroutine won the race. Roll back our pending counter.
|
||||
s.pendingInits.Add(-1)
|
||||
pendingValue = actual
|
||||
}
|
||||
actual, _ := s.messages.LoadOrStore(msg.ID, pendingValue)
|
||||
pendingValue = actual
|
||||
}
|
||||
pending := pendingValue.(*PendingEnvelope)
|
||||
p, ok := pendingValue.(*pendingInit)
|
||||
if !ok || p == nil || p.Envelope == nil {
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
pending := p.Envelope
|
||||
if pending.Size != msg.Size && msg.Size != 0 {
|
||||
s.messages.Delete(msg.ID)
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
if !pending.Insert(msg) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
if pending.received > pending.Size {
|
||||
s.deletePendingInit(msg.ID)
|
||||
return nil, false, ErrInvalidMsg
|
||||
}
|
||||
|
||||
data, err := pending.Reassemble()
|
||||
s.messages.Delete(msg.ID)
|
||||
s.deletePendingInit(msg.ID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return data, true, nil
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// DNS Message Handlers
|
||||
// ---------------------------
|
||||
func (s *SliverDNSServer) handleHello(domain string, msg *dnspb.DNSMessage, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[dns] totp request: %v", msg)
|
||||
|
||||
dnsSessionID := dnsSessionID()
|
||||
dnsLog.Debugf("[dns] Assigned new dns session id = %d", dnsSessionID&sessionIDBitMask)
|
||||
s.sessions.Store(dnsSessionID&sessionIDBitMask, &DNSSession{
|
||||
ID: dnsSessionID & sessionIDBitMask,
|
||||
dnsIdMsgIdMap: map[uint32]uint32{},
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
incomingEnvelopes: map[uint32]*PendingEnvelope{},
|
||||
incomingMutex: &sync.Mutex{},
|
||||
msgCount: uint32(0),
|
||||
})
|
||||
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(req)
|
||||
resp.Authoritative = true
|
||||
respBuf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(respBuf, dnsSessionID)
|
||||
for _, q := range req.Question {
|
||||
switch q.Qtype {
|
||||
case dns.TypeA:
|
||||
a := &dns.A{
|
||||
Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.TTL},
|
||||
A: respBuf,
|
||||
}
|
||||
resp.Answer = append(resp.Answer, a)
|
||||
}
|
||||
func (s *SliverDNSServer) deletePendingInit(id uint32) {
|
||||
if _, loaded := s.messages.LoadAndDelete(id); loaded {
|
||||
s.pendingInits.Add(-1)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMessage, checksum uint32, req *dns.Msg) *dns.Msg {
|
||||
dnsLog.Debugf("[session init] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
sessionID := msg.ID & sessionIDBitMask
|
||||
dnsLog.Debugf("[session init] with dns session id %d", sessionID)
|
||||
if sessionID == 0 {
|
||||
dnsLog.Warnf("[session init] invalid dns session id")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
if msg.Size <= 32 {
|
||||
dnsLog.Warnf("[session init] invalid msg data length")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
if dnsSession.CipherCtx != nil {
|
||||
dnsLog.Warnf("[session init] session is already initialized")
|
||||
if existing, ok := s.sessions.Load(sessionID); ok {
|
||||
// Defensive: INIT should only be called once per session ID.
|
||||
if dnsSession, ok := existing.(*DNSSession); ok && dnsSession != nil && dnsSession.CipherCtx != nil {
|
||||
dnsLog.Warnf("[session init] session is already initialized")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
dnsLog.Warnf("[session init] session already exists")
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
@@ -564,7 +660,27 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
dnsSession := &DNSSession{
|
||||
ID: sessionID,
|
||||
Created: now,
|
||||
dnsIdMsgIdMap: map[uint32]uint32{},
|
||||
outgoingMsgIDs: []uint32{},
|
||||
outgoingBuffers: map[uint32][]byte{},
|
||||
outgoingMutex: &sync.RWMutex{},
|
||||
incomingEnvelopes: map[uint32]*PendingEnvelope{},
|
||||
incomingMutex: &sync.Mutex{},
|
||||
msgCount: uint32(0),
|
||||
}
|
||||
dnsSession.touch(now)
|
||||
dnsSession.ImplantConn = core.NewImplantConnection("dns", "n/a")
|
||||
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
|
||||
|
||||
if _, loaded := s.sessions.LoadOrStore(sessionID, dnsSession); loaded {
|
||||
dnsLog.Warnf("[session init] session id collision for %d", sessionID)
|
||||
return s.refusedErrorResp(req)
|
||||
}
|
||||
|
||||
go func() {
|
||||
dnsLog.Debugf("[dns] starting implant conn send loop")
|
||||
for envelope := range dnsSession.ImplantConn.Send {
|
||||
@@ -572,7 +688,6 @@ func (s *SliverDNSServer) handleDNSSessionInit(domain string, msg *dnspb.DNSMess
|
||||
}
|
||||
dnsLog.Debugf("[dns] closing implant conn send loop")
|
||||
}()
|
||||
dnsSession.CipherCtx = cryptography.NewCipherContext(sessionKey)
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, dnsSession.ID)
|
||||
respData, err := dnsSession.CipherCtx.Encrypt(buf)
|
||||
@@ -628,6 +743,7 @@ func (s *SliverDNSServer) handlePoll(domain string, msg *dnspb.DNSMessage, check
|
||||
dnsLog.Debugf("[poll] with dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
|
||||
msgID, msgLen, err := dnsSession.PopOutgoingMsgID(msg)
|
||||
if err != nil {
|
||||
@@ -709,6 +825,7 @@ func (s *SliverDNSServer) handleDataFromImplant(domain string, msg *dnspb.DNSMes
|
||||
dnsLog.Debugf("[from implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
dnsLog.Debugf("[from implant] msg id: %d, size: %d", msg.ID, msg.Size)
|
||||
pending := dnsSession.IncomingPendingEnvelope(msg.ID, msg.Size)
|
||||
complete := pending.Insert(msg)
|
||||
@@ -739,6 +856,7 @@ func (s *SliverDNSServer) handleDataToImplant(domain string, msg *dnspb.DNSMessa
|
||||
dnsLog.Debugf("[to implant] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
|
||||
data, err := dnsSession.OutgoingRead(msg.ID, msg.Start, msg.Stop)
|
||||
if err != nil {
|
||||
@@ -798,6 +916,7 @@ func (s *SliverDNSServer) handleClear(domain string, msg *dnspb.DNSMessage, chec
|
||||
dnsLog.Debugf("[clear] dns session id %d", msg.ID&sessionIDBitMask)
|
||||
loadSession, _ := s.sessions.Load(msg.ID & sessionIDBitMask)
|
||||
dnsSession := loadSession.(*DNSSession)
|
||||
dnsSession.touch(time.Now())
|
||||
dnsSession.ClearOutgoingEnvelope(msg.ID)
|
||||
|
||||
respBuf := make([]byte, 4)
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
//go:build server && go_sqlite && sliver_e2e
|
||||
|
||||
package c2_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
implantCrypto "github.com/bishopfox/sliver/implant/sliver/cryptography"
|
||||
implantHandlers "github.com/bishopfox/sliver/implant/sliver/handlers"
|
||||
"github.com/bishopfox/sliver/implant/sliver/transports/dnsclient"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
serverCrypto "github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/transport"
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const dnsE2EEchoMsgType = uint32(0x7fffffff)
|
||||
|
||||
func TestDNS_EndToEndPingRPC(t *testing.T) {
|
||||
// NOTE: If you run this test in a restricted environment where writes to
|
||||
// `~/.sliver` are blocked, set `SLIVER_ROOT_DIR` to a writable temp dir.
|
||||
|
||||
t.Cleanup(func() {
|
||||
for _, session := range core.Sessions.All() {
|
||||
core.Sessions.Remove(session.ID)
|
||||
}
|
||||
})
|
||||
|
||||
setupDNSKeyExImplantBuild(t)
|
||||
|
||||
grpcServer, grpcListener, err := transport.LocalListener()
|
||||
if err != nil {
|
||||
t.Fatalf("start local grpc listener: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
grpcServer.Stop()
|
||||
_ = grpcListener.Close()
|
||||
})
|
||||
|
||||
rpcConn, err := dialBufConn(context.Background(), grpcListener)
|
||||
if err != nil {
|
||||
t.Fatalf("dial grpc/bufconn: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = rpcConn.Close() })
|
||||
rpcClient := rpcpb.NewSliverRPCClient(rpcConn)
|
||||
|
||||
dnsParent := "e2e.example.com."
|
||||
dnsPort := freeUDPPort(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
dnsListener, err := rpcClient.StartDNSListener(ctx, &clientpb.DNSListenerReq{
|
||||
Domains: []string{dnsParent},
|
||||
Canaries: false,
|
||||
Host: "127.0.0.1",
|
||||
Port: dnsPort,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("StartDNSListener: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if job := core.Jobs.Get(int(dnsListener.JobID)); job != nil {
|
||||
job.JobCtrl <- true
|
||||
}
|
||||
_ = db.DeleteListener(dnsListener.JobID)
|
||||
})
|
||||
|
||||
// Give the UDP listener a moment to bind before the implant starts sending queries.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
stopImplant := startTestDNSImplant(t, dnsParent, dnsPort)
|
||||
t.Cleanup(stopImplant)
|
||||
|
||||
sessionID := waitForSessionID(t, 10*time.Second)
|
||||
|
||||
pingReq := &sliverpb.Ping{
|
||||
Nonce: 31337,
|
||||
Request: &commonpb.Request{
|
||||
SessionID: sessionID,
|
||||
Timeout: int64(5 * time.Second),
|
||||
},
|
||||
}
|
||||
pingResp, err := rpcClient.Ping(ctx, pingReq)
|
||||
if err != nil {
|
||||
t.Fatalf("Ping: %v", err)
|
||||
}
|
||||
if pingResp.Nonce != pingReq.Nonce {
|
||||
t.Fatalf("ping nonce mismatch: %d != %d", pingResp.Nonce, pingReq.Nonce)
|
||||
}
|
||||
|
||||
// Extra validation: send a larger request/response directly via the session to
|
||||
// force message fragmentation and reassembly over the DNS C2 transport.
|
||||
session := core.Sessions.Get(sessionID)
|
||||
if session == nil {
|
||||
t.Fatalf("session not found: %s", sessionID)
|
||||
}
|
||||
large := make([]byte, 8192)
|
||||
if _, err := rand.Read(large); err != nil {
|
||||
t.Fatalf("rand.Read: %v", err)
|
||||
}
|
||||
resp, err := session.Request(dnsE2EEchoMsgType, 20*time.Second, large)
|
||||
if err != nil {
|
||||
t.Fatalf("session.Request(echo): %v", err)
|
||||
}
|
||||
if !bytesEqual(resp, large) {
|
||||
t.Fatalf("echo response mismatch: %d != %d bytes", len(resp), len(large))
|
||||
}
|
||||
}
|
||||
|
||||
func bytesEqual(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func freeUDPPort(t *testing.T) uint32 {
|
||||
t.Helper()
|
||||
conn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen udp: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
_, portStr, err := net.SplitHostPort(conn.LocalAddr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("split udp addr: %v", err)
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
t.Fatalf("parse udp port: %v", err)
|
||||
}
|
||||
if port <= 0 || 65535 < port {
|
||||
t.Fatalf("unexpected udp port: %d", port)
|
||||
}
|
||||
return uint32(port)
|
||||
}
|
||||
|
||||
func setupDNSKeyExImplantBuild(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
certs.SetupCAs()
|
||||
serverAgeKeyPair := serverCrypto.AgeServerKeyPair()
|
||||
peerAgeKeyPair, err := serverCrypto.RandomAgeKeyPair()
|
||||
if err != nil {
|
||||
t.Fatalf("random age key pair: %v", err)
|
||||
}
|
||||
|
||||
implantCrypto.SetSecrets(
|
||||
peerAgeKeyPair.Public,
|
||||
peerAgeKeyPair.Private,
|
||||
"",
|
||||
serverAgeKeyPair.Public,
|
||||
serverCrypto.MinisignServerPublicKey(),
|
||||
)
|
||||
|
||||
digest := sha256.Sum256([]byte(peerAgeKeyPair.Public))
|
||||
publicKeyDigest := hex.EncodeToString(digest[:])
|
||||
|
||||
implantBuild := &models.ImplantBuild{
|
||||
Name: "test-" + publicKeyDigest,
|
||||
PeerPublicKey: peerAgeKeyPair.Public,
|
||||
PeerPublicKeyDigest: publicKeyDigest,
|
||||
PeerPrivateKey: peerAgeKeyPair.Private,
|
||||
AgeServerPublicKey: serverAgeKeyPair.Public,
|
||||
}
|
||||
|
||||
if err := db.Session().Create(implantBuild).Error; err != nil {
|
||||
t.Fatalf("create implant build: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = db.Session().Delete(implantBuild).Error
|
||||
})
|
||||
}
|
||||
|
||||
func startTestDNSImplant(t *testing.T, dnsParent string, dnsPort uint32) func() {
|
||||
t.Helper()
|
||||
|
||||
opts := &dnsclient.DNSOptions{
|
||||
QueryTimeout: 250 * time.Millisecond,
|
||||
RetryWait: 50 * time.Millisecond,
|
||||
RetryCount: 10,
|
||||
MaxErrors: 10,
|
||||
WorkersPerResolver: 1,
|
||||
NoTXT: false,
|
||||
ForceResolvers: fmt.Sprintf("127.0.0.1:%d", dnsPort),
|
||||
}
|
||||
|
||||
// Starting the UDP listener is asynchronous relative to the gRPC call that
|
||||
// created it. Retry session init for a short time to avoid flakes.
|
||||
var (
|
||||
client *dnsclient.SliverDNSClient
|
||||
err error
|
||||
)
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for {
|
||||
client, err = dnsclient.DNSStartSession(dnsParent, opts)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("DNSStartSession: %v", err)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
register := &sliverpb.Register{
|
||||
Name: "e2e-dns",
|
||||
Hostname: "localhost",
|
||||
Uuid: uuid.NewString(),
|
||||
Username: "unit-test",
|
||||
Os: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
Pid: int32(os.Getpid()),
|
||||
Filename: "sliver-e2e",
|
||||
ActiveC2: "dns://e2e",
|
||||
Version: "e2e",
|
||||
ReconnectInterval: 0,
|
||||
ProxyURL: "",
|
||||
Locale: "en_US",
|
||||
}
|
||||
regData, err := proto.Marshal(register)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal register: %v", err)
|
||||
}
|
||||
if err := client.WriteEnvelope(&sliverpb.Envelope{Type: sliverpb.MsgRegister, Data: regData}); err != nil {
|
||||
t.Fatalf("send register: %v", err)
|
||||
}
|
||||
|
||||
handlers := implantHandlers.GetSystemHandlers()
|
||||
abort := make(chan struct{})
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
for {
|
||||
select {
|
||||
case <-abort:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
envelope, err := client.ReadEnvelope()
|
||||
switch err {
|
||||
case nil:
|
||||
if envelope == nil {
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
case dnsclient.ErrTimeout:
|
||||
continue
|
||||
case dnsclient.ErrClosed:
|
||||
return
|
||||
default:
|
||||
// Treat transient resolver errors as retryable in this test loop.
|
||||
continue
|
||||
}
|
||||
|
||||
if envelope.ID == 0 {
|
||||
continue
|
||||
}
|
||||
if envelope.Type == dnsE2EEchoMsgType {
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, Data: envelope.Data})
|
||||
continue
|
||||
}
|
||||
|
||||
handler, ok := handlers[envelope.Type]
|
||||
if !ok {
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, UnknownMessageType: true})
|
||||
continue
|
||||
}
|
||||
|
||||
handler(envelope.Data, func(data []byte, err error) {
|
||||
_ = err
|
||||
_ = client.WriteEnvelope(&sliverpb.Envelope{ID: envelope.ID, Data: data})
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
close(abort)
|
||||
_ = client.CloseSession()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
}
|
||||
}
|
||||
+289
-19
@@ -232,31 +232,55 @@ func reassemble(t *testing.T, parent string, size int, encoder encoders.Encoder)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSHelloRoundTrip(t *testing.T) {
|
||||
func TestDNSSessionInitRoundTrip(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
resolver := &testDNSResolver{server: server, parent: example1}
|
||||
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_TOTP,
|
||||
ID: 0,
|
||||
sessionID := uint32(0x123456)
|
||||
if _, ok := server.sessions.Load(sessionID); ok {
|
||||
t.Fatalf("expected session %d to not exist before init", sessionID)
|
||||
}
|
||||
domain, err := encodeMessageDomain(example1, msg)
|
||||
|
||||
key := implantCrypto.RandomSymmetricKey()
|
||||
cipherCtx := implantCrypto.NewCipherContext(key)
|
||||
initData, err := implantCrypto.AgeKeyExToServer(key[:])
|
||||
if err != nil {
|
||||
t.Fatalf("encodeMessageDomain failed: %s", err)
|
||||
t.Fatalf("AgeKeyExToServer failed: %s", err)
|
||||
}
|
||||
data, _, err := resolver.A(domain)
|
||||
initMsg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01000000 | sessionID,
|
||||
Size: uint32(len(initData)),
|
||||
}
|
||||
client := dnsclient.NewDNSClient(example1, opts)
|
||||
domains, err := client.SplitBuffer(initMsg, implantEncoders.Base32Encoder{}, initData)
|
||||
if err != nil {
|
||||
t.Fatalf("resolver.A failed: %s", err)
|
||||
t.Fatalf("SplitBuffer failed: %s", err)
|
||||
}
|
||||
if len(data) != 4 {
|
||||
t.Fatalf("expected 4 bytes in response, got %d", len(data))
|
||||
|
||||
var respData []byte
|
||||
for _, domain := range domains {
|
||||
data, _, err := resolver.TXT(domain)
|
||||
if err != nil {
|
||||
t.Fatalf("resolver.TXT failed: %s", err)
|
||||
}
|
||||
if 0 < len(data) {
|
||||
respData = data
|
||||
}
|
||||
}
|
||||
sessionID := binary.LittleEndian.Uint32(data) & sessionIDBitMask
|
||||
if sessionID == 0 {
|
||||
t.Fatal("expected non-zero session id")
|
||||
if len(respData) < 1 {
|
||||
t.Fatal("expected non-empty init response")
|
||||
}
|
||||
plaintext, err := cipherCtx.Decrypt(respData)
|
||||
if err != nil {
|
||||
t.Fatalf("init response decrypt failed: %s", err)
|
||||
}
|
||||
got := binary.LittleEndian.Uint32(plaintext) & sessionIDBitMask
|
||||
if got != sessionID {
|
||||
t.Fatalf("expected session id %d, got %d", sessionID, got)
|
||||
}
|
||||
if _, ok := server.sessions.Load(sessionID); !ok {
|
||||
t.Fatalf("expected session %d to be stored", sessionID)
|
||||
t.Fatalf("expected session %d to be stored after init", sessionID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +445,8 @@ func TestDNSDataToImplantRejectsInvalidRead(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIsC2Domain(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
isC2, domain := listener.isC2SubDomain(c2Domains, "asdf.1.example.com.")
|
||||
if !isC2 {
|
||||
t.Fatal("IsC2Domain expected true, got false")
|
||||
@@ -444,7 +469,8 @@ func TestIsC2Domain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataBase32(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
payload := make([]byte, 64)
|
||||
rand.Read(payload)
|
||||
original := &dnspb.DNSMessage{
|
||||
@@ -476,7 +502,8 @@ func TestDecodeSubdataBase32(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataRejectsInvalid(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
_, _, err := listener.decodeSubdata("invalid")
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
@@ -484,7 +511,8 @@ func TestDecodeSubdataRejectsInvalid(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecodeSubdataRejectsInvalidProto(t *testing.T) {
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false, true)
|
||||
listener := StartDNSListener("", uint16(9999), c2Domains, false)
|
||||
t.Cleanup(func() { _ = listener.Shutdown() })
|
||||
encoded, err := implantEncoders.Base32Encoder{}.Encode([]byte{0x00})
|
||||
if err != nil {
|
||||
t.Fatalf("encode failed: %s", err)
|
||||
@@ -570,6 +598,199 @@ func TestAccumulateInitDataReassembles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataCapsPendingInits(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
// Only send a single fragment per INIT so each call allocates one pending init.
|
||||
for i := 0; i < defaultMaxPendingDNSInits+10; i++ {
|
||||
id := uint32(i + 1) // avoid zero
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2048,
|
||||
Start: 0,
|
||||
Data: []byte{0xAA},
|
||||
}
|
||||
_, complete, err := server.accumulateInitData(msg)
|
||||
if i < defaultMaxPendingDNSInits {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for pending init %d: %v", i, err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatalf("unexpected completion for pending init %d", i)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); !ok {
|
||||
t.Fatalf("expected pending init %d to be stored", id)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg after cap, got %v", err)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected pending init %d to not be stored after cap", id)
|
||||
}
|
||||
}
|
||||
|
||||
if got := server.pendingInits.Load(); got != int64(defaultMaxPendingDNSInits) {
|
||||
t.Fatalf("expected pendingInits=%d, got %d", defaultMaxPendingDNSInits, got)
|
||||
}
|
||||
|
||||
var count int64
|
||||
server.messages.Range(func(key, value any) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
if count != int64(defaultMaxPendingDNSInits) {
|
||||
t.Fatalf("expected %d pending init entries, got %d", defaultMaxPendingDNSInits, count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataCompletionCleansUpPendingInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
client := dnsclient.NewDNSClient(example1, opts)
|
||||
|
||||
testData := make([]byte, 2048)
|
||||
rand.Read(testData)
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01020304,
|
||||
Size: uint32(len(testData)),
|
||||
}
|
||||
domains, err := client.SplitBuffer(msg, implantEncoders.Base32Encoder{}, testData)
|
||||
if err != nil {
|
||||
t.Fatalf("SplitBuffer failed: %s", err)
|
||||
}
|
||||
|
||||
subMsgs := make([]*dnspb.DNSMessage, 0, len(domains))
|
||||
for _, domain := range domains {
|
||||
subdata := strings.TrimSuffix(domain, example1)
|
||||
subdata = strings.ReplaceAll(subdata, ".", "")
|
||||
raw, err := implantEncoders.Base32Encoder{}.Decode([]byte(subdata))
|
||||
if err != nil {
|
||||
t.Fatalf("decode subdata failed: %s", err)
|
||||
}
|
||||
subMsg := &dnspb.DNSMessage{}
|
||||
if err := proto.Unmarshal(raw, subMsg); err != nil {
|
||||
t.Fatalf("unmarshal submsg failed: %s", err)
|
||||
}
|
||||
subMsgs = append(subMsgs, subMsg)
|
||||
}
|
||||
|
||||
for _, subMsg := range subMsgs {
|
||||
_, _, err := server.accumulateInitData(subMsg)
|
||||
if err != nil {
|
||||
t.Fatalf("accumulateInitData failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if got := server.pendingInits.Load(); got != 0 {
|
||||
t.Fatalf("expected pendingInits=0 after completion, got %d", got)
|
||||
}
|
||||
if _, ok := server.messages.Load(msg.ID); ok {
|
||||
t.Fatalf("expected pending init %d to be deleted after completion", msg.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataSizeMismatchCleansUpPendingInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
id := uint32(0x01020304)
|
||||
|
||||
// First fragment creates pending state.
|
||||
_, complete, err := server.accumulateInitData(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2048,
|
||||
Start: 0,
|
||||
Data: []byte{0xAA},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating pending init: %v", err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatal("unexpected completion creating pending init")
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != 1 {
|
||||
t.Fatalf("expected pendingInits=1, got %d", got)
|
||||
}
|
||||
|
||||
// Mismatched Size should reject and delete pending state.
|
||||
_, _, err = server.accumulateInitData(&dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: id,
|
||||
Size: 2049,
|
||||
Start: 1,
|
||||
Data: []byte{0xBB},
|
||||
})
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg for mismatched size, got %v", err)
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != 0 {
|
||||
t.Fatalf("expected pendingInits=0 after cleanup, got %d", got)
|
||||
}
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected pending init %d to be deleted after size mismatch", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGCPendingInitsExpiresOldEntries(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
now := time.Now()
|
||||
expiredAt := now.Add(-defaultPendingDNSInitTTL - time.Second)
|
||||
freshAt := now.Add(-defaultPendingDNSInitTTL / 2)
|
||||
|
||||
expiredIDs := make([]uint32, 0, 10)
|
||||
freshIDs := make([]uint32, 0, 5)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
id := uint32(0x90000000 + i)
|
||||
expiredIDs = append(expiredIDs, id)
|
||||
server.messages.Store(id, &pendingInit{
|
||||
Created: expiredAt,
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: 1,
|
||||
received: 0,
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
})
|
||||
server.pendingInits.Add(1)
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
id := uint32(0xA0000000 + i)
|
||||
freshIDs = append(freshIDs, id)
|
||||
server.messages.Store(id, &pendingInit{
|
||||
Created: freshAt,
|
||||
Envelope: &PendingEnvelope{
|
||||
Size: 1,
|
||||
received: 0,
|
||||
messages: map[uint32][]byte{},
|
||||
mutex: &sync.Mutex{},
|
||||
complete: false,
|
||||
},
|
||||
})
|
||||
server.pendingInits.Add(1)
|
||||
}
|
||||
|
||||
server.gcPendingInits()
|
||||
|
||||
for _, id := range expiredIDs {
|
||||
if _, ok := server.messages.Load(id); ok {
|
||||
t.Fatalf("expected expired pending init %d to be deleted", id)
|
||||
}
|
||||
}
|
||||
for _, id := range freshIDs {
|
||||
if _, ok := server.messages.Load(id); !ok {
|
||||
t.Fatalf("expected fresh pending init %d to remain", id)
|
||||
}
|
||||
}
|
||||
if got := server.pendingInits.Load(); got != int64(len(freshIDs)) {
|
||||
t.Fatalf("expected pendingInits=%d, got %d", len(freshIDs), got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitToChunksPadding(t *testing.T) {
|
||||
data := []byte{1, 2, 3, 4, 5, 6}
|
||||
chunks := splitToChunks(data, 4)
|
||||
@@ -658,7 +879,6 @@ func newTestDNSServer() *SliverDNSServer {
|
||||
messages: &sync.Map{},
|
||||
TTL: 0,
|
||||
MaxTXTLength: defaultMaxTXTLength,
|
||||
EnforceOTP: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,3 +1080,53 @@ func randomBytes(size int) []byte {
|
||||
rand.Read(buf)
|
||||
return buf
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataRejectsOversizedInit(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
msg := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x01020304,
|
||||
Size: defaultMaxDNSInitSize + 1,
|
||||
Data: []byte{0x01},
|
||||
}
|
||||
|
||||
_, _, err := server.accumulateInitData(msg)
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccumulateInitDataRejectsReceivedOverrun(t *testing.T) {
|
||||
server := newTestDNSServer()
|
||||
|
||||
// Declared size is 8 bytes, but two chunks deliver 10 bytes total.
|
||||
first := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x0a0b0c0d,
|
||||
Size: 8,
|
||||
Start: 0,
|
||||
Stop: 5,
|
||||
Data: []byte("12345"),
|
||||
}
|
||||
second := &dnspb.DNSMessage{
|
||||
Type: dnspb.DNSMessageType_INIT,
|
||||
ID: 0x0a0b0c0d,
|
||||
Size: 8,
|
||||
Start: 5,
|
||||
Stop: 10,
|
||||
Data: []byte("67890"),
|
||||
}
|
||||
|
||||
_, complete, err := server.accumulateInitData(first)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error on first chunk: %v", err)
|
||||
}
|
||||
if complete {
|
||||
t.Fatal("expected incomplete state after first chunk")
|
||||
}
|
||||
|
||||
_, _, err = server.accumulateInitData(second)
|
||||
if err != ErrInvalidMsg {
|
||||
t.Fatalf("expected ErrInvalidMsg, got %v", err)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -128,7 +128,7 @@ func StartWGListenerJob(wgListener *clientpb.WGListenerReq) (*core.Job, error) {
|
||||
|
||||
// StartDNSListenerJob - Start a DNS listener as a job
|
||||
func StartDNSListenerJob(dnsListener *clientpb.DNSListenerReq) (*core.Job, error) {
|
||||
server := StartDNSListener(dnsListener.Host, uint16(dnsListener.Port), dnsListener.Domains, dnsListener.Canaries, dnsListener.EnforceOTP)
|
||||
server := StartDNSListener(dnsListener.Host, uint16(dnsListener.Port), dnsListener.Domains, dnsListener.Canaries)
|
||||
description := fmt.Sprintf("%s (canaries %v)", strings.Join(dnsListener.Domains, " "), dnsListener.Canaries)
|
||||
job := &core.Job{
|
||||
ID: core.NextJobID(),
|
||||
|
||||
@@ -446,6 +446,7 @@ func renderSliverGoCode(name string, build *clientpb.ImplantBuild, config *clien
|
||||
|
||||
buildLog.Debugf("Generating new sliver binary '%s'", name)
|
||||
pbC2Implant = models.RandomizeImplantConfig(pbC2Implant, config.GOOS, config.GOARCH)
|
||||
|
||||
sliversDir := GetSliversDir() // ~/.sliver/slivers
|
||||
projectGoPathDir := filepath.Join(sliversDir, config.GOOS, config.GOARCH, filepath.Base(name))
|
||||
if _, err := os.Stat(projectGoPathDir); os.IsNotExist(err) {
|
||||
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
Reference in new issue
Block a user