Compare commits

...
6 Commits
17 changed files with 857 additions and 53 deletions

No files matched your search

+95 -13
View File
@@ -34,6 +34,8 @@ package command
*/
import (
"os"
"github.com/bishopfox/sliver/client/command/backdoor"
"github.com/bishopfox/sliver/client/command/environment"
"github.com/bishopfox/sliver/client/command/exec"
@@ -51,6 +53,7 @@ import (
"github.com/bishopfox/sliver/client/command/portfwd"
"github.com/bishopfox/sliver/client/command/privilege"
"github.com/bishopfox/sliver/client/command/processes"
"github.com/bishopfox/sliver/client/command/reaction"
"github.com/bishopfox/sliver/client/command/registry"
"github.com/bishopfox/sliver/client/command/screenshot"
"github.com/bishopfox/sliver/client/command/sessions"
@@ -71,6 +74,13 @@ const (
// BindCommands - Bind commands to a App
func BindCommands(con *console.SliverConsoleClient) {
n, err := reaction.LoadReactions()
if err != nil && !os.IsNotExist(err) {
con.PrintErrorf("Failed to load reactions: %s\n", err)
} else if n > 0 {
con.PrintInfof("Loaded %d reaction(s) from disk\n", n)
}
con.App.SetPrintHelp(help.HelpCmd(con)) // Responsible for display long-form help templates, etc.
// [ Update ] --------------------------------------------------------------
@@ -1529,10 +1539,10 @@ func BindCommands(con *console.SliverConsoleClient) {
// [ Environment ] ---------------------------------------------
con.App.AddCommand(&grumble.Command{
Name: consts.GetEnvStr,
envCmd := &grumble.Command{
Name: consts.EnvStr,
Help: "List environment variables",
LongHelp: help.GetHelpFor([]string{consts.GetEnvStr}),
LongHelp: help.GetHelpFor([]string{consts.EnvStr}),
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -1546,12 +1556,11 @@ func BindCommands(con *console.SliverConsoleClient) {
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
con.App.AddCommand(&grumble.Command{
Name: consts.SetEnvStr,
}
envCmd.AddCommand(&grumble.Command{
Name: consts.SetStr,
Help: "Set environment variables",
LongHelp: help.GetHelpFor([]string{consts.SetEnvStr}),
LongHelp: help.GetHelpFor([]string{consts.EnvStr, consts.SetStr}),
Flags: func(f *grumble.Flags) {
f.Int("t", "timeout", defaultTimeout, "command timeout in seconds")
},
@@ -1567,11 +1576,10 @@ func BindCommands(con *console.SliverConsoleClient) {
},
HelpGroup: consts.GenericHelpGroup,
})
con.App.AddCommand(&grumble.Command{
Name: consts.UnsetEnvStr,
envCmd.AddCommand(&grumble.Command{
Name: consts.UnsetStr,
Help: "Clear environment variables",
LongHelp: help.GetHelpFor([]string{consts.UnsetEnvStr}),
LongHelp: help.GetHelpFor([]string{consts.EnvStr, consts.UnsetStr}),
Args: func(a *grumble.Args) {
a.String("name", "environment variable name")
},
@@ -1586,6 +1594,9 @@ func BindCommands(con *console.SliverConsoleClient) {
},
HelpGroup: consts.GenericHelpGroup,
})
con.App.AddCommand(envCmd)
// [ Licenses ] ---------------------------------------------
con.App.AddCommand(&grumble.Command{
Name: consts.LicensesStr,
@@ -1611,7 +1622,6 @@ func BindCommands(con *console.SliverConsoleClient) {
},
HelpGroup: consts.SliverWinHelpGroup,
}
registryCmd.AddCommand(&grumble.Command{
Name: consts.RegistryReadStr,
Help: "Read values from the Windows registry",
@@ -1846,6 +1856,7 @@ func BindCommands(con *console.SliverConsoleClient) {
con.App.AddCommand(wgSocksCmd)
// [ Portfwd ] --------------------------------------------------------------
portfwdCmd := &grumble.Command{
Name: consts.PortfwdStr,
Help: "In-band TCP port forwarding",
@@ -1897,6 +1908,7 @@ func BindCommands(con *console.SliverConsoleClient) {
con.App.AddCommand(portfwdCmd)
// [ Monitor ] --------------------------------------------------------------
monitorCmd := &grumble.Command{
Name: consts.MonitorStr,
Help: "Monitor threat intel platforms for Sliver implants",
@@ -2054,4 +2066,74 @@ func BindCommands(con *console.SliverConsoleClient) {
HelpGroup: consts.GenericHelpGroup,
})
con.App.AddCommand(lootCmd)
// [ Reactions ] -----------------------------------------------------------------
reactionCmd := &grumble.Command{
Name: consts.ReactionStr,
Help: "Manage automatic reactions to events",
LongHelp: help.GetHelpFor([]string{consts.ReactionStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
reaction.ReactionCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
}
reactionCmd.AddCommand(&grumble.Command{
Name: consts.SetStr,
Help: "Set a reaction to an event",
LongHelp: help.GetHelpFor([]string{consts.ReactionStr, consts.SetStr}),
Flags: func(f *grumble.Flags) {
f.String("e", "event", "", "specify the event type to react to")
},
Run: func(ctx *grumble.Context) error {
con.Println()
reaction.ReactionSetCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
reactionCmd.AddCommand(&grumble.Command{
Name: consts.UnsetStr,
Help: "Unset an existing reaction",
LongHelp: help.GetHelpFor([]string{consts.ReactionStr, consts.UnsetStr}),
Flags: func(f *grumble.Flags) {
f.Int("i", "id", 0, "the id of the reaction to remove")
},
Run: func(ctx *grumble.Context) error {
con.Println()
reaction.ReactionUnsetCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
reactionCmd.AddCommand(&grumble.Command{
Name: consts.SaveStr,
Help: "Save current reactions to disk",
LongHelp: help.GetHelpFor([]string{consts.ReactionStr, consts.SaveStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
reaction.ReactionSaveCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
reactionCmd.AddCommand(&grumble.Command{
Name: consts.ReloadStr,
Help: "Reload reactions from disk, replaces the running configuration",
LongHelp: help.GetHelpFor([]string{consts.ReactionStr, consts.ReloadStr}),
Run: func(ctx *grumble.Context) error {
con.Println()
reaction.ReactionReloadCmd(ctx, con)
con.Println()
return nil
},
HelpGroup: consts.GenericHelpGroup,
})
con.App.AddCommand(reactionCmd)
}
+1 -1
View File
@@ -32,7 +32,7 @@ func MigrateCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
}
pid := ctx.Args.Uint("pid")
config := con.GetActiveSliverConfig()
config := con.GetActiveSessionConfig()
ctrl := make(chan bool)
con.SpinUntil(fmt.Sprintf("Migrating into %d ...", pid), ctrl)
migrate, err := con.Rpc.Migrate(context.Background(), &clientpb.MigrateReq{
+8 -6
View File
@@ -19,9 +19,9 @@ package filesystem
*/
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"text/tabwriter"
@@ -48,16 +48,17 @@ func LsCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
if err != nil {
con.PrintWarnf("%s\n", err)
} else {
PrintLs(con.App.Stdout(), ls)
PrintLs(ls, con)
}
}
// PrintLs - Display an sliverpb.Ls object
func PrintLs(stdout io.Writer, ls *sliverpb.Ls) {
fmt.Fprintf(stdout, "%s\n", ls.Path)
fmt.Fprintf(stdout, "%s\n", strings.Repeat("=", len(ls.Path)))
func PrintLs(ls *sliverpb.Ls, con *console.SliverConsoleClient) {
con.Printf("%s\n", ls.Path)
con.Printf("%s\n", strings.Repeat("=", len(ls.Path)))
table := tabwriter.NewWriter(stdout, 0, 2, 2, ' ', 0)
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
for _, fileInfo := range ls.Files {
if fileInfo.IsDir {
fmt.Fprintf(table, "%s\t<dir>\t\n", fileInfo.Name)
@@ -66,4 +67,5 @@ func PrintLs(stdout io.Writer, ls *sliverpb.Ls) {
}
}
table.Flush()
con.Printf("%s\n", outputBuf.String())
}
+79 -15
View File
@@ -24,12 +24,17 @@ package help
import (
"bytes"
"fmt"
"strings"
"text/template"
consts "github.com/bishopfox/sliver/client/constants"
)
const (
sep = "."
)
var (
// NOTE: For sub-commands use a "." hierarchy, for example root.sub for "sub" help
@@ -71,25 +76,30 @@ var (
consts.BackdoorStr: backdoorHelp,
consts.SpawnDllStr: spawnDllHelp,
consts.WebsitesStr: websitesHelp,
consts.ScreenshotStr: screenshotHelp,
consts.MakeTokenStr: makeTokenHelp,
consts.GetEnvStr: getEnvHelp,
consts.SetEnvStr: setEnvHelp,
consts.RegistryWriteStr: regWriteHelp,
consts.RegistryReadStr: regReadHelp,
consts.RegistryCreateKeyStr: regCreateKeyHelp,
consts.PivotsListStr: pivotListHelp,
consts.WgPortFwdStr: wgPortFwdHelp,
consts.WgSocksStr: wgSocksHelp,
consts.SSHStr: sshHelp,
consts.WebsitesStr: websitesHelp,
consts.ScreenshotStr: screenshotHelp,
consts.MakeTokenStr: makeTokenHelp,
consts.EnvStr: getEnvHelp,
consts.EnvStr + sep + consts.SetStr: setEnvHelp,
consts.RegistryWriteStr: regWriteHelp,
consts.RegistryReadStr: regReadHelp,
consts.RegistryCreateKeyStr: regCreateKeyHelp,
consts.PivotsListStr: pivotListHelp,
consts.WgPortFwdStr: wgPortFwdHelp,
consts.WgSocksStr: wgSocksHelp,
consts.SSHStr: sshHelp,
// Loot
consts.LootStr: lootHelp,
// Profiles
consts.ProfilesStr + "." + consts.NewStr: newProfileHelp,
consts.ProfilesStr + "." + consts.GenerateStr: generateProfileHelp,
consts.ProfilesStr + sep + consts.NewStr: newProfileHelp,
consts.ProfilesStr + sep + consts.GenerateStr: generateProfileHelp,
// Reactions
consts.ReactionStr: reactionHelp,
consts.ReactionStr + sep + consts.SetStr: reactionSetHelp,
consts.ReactionStr + sep + consts.UnsetStr: reactionUnsetHelp,
}
jobsHelp = `[[.Bold]]Command:[[.Normal]] jobs <options>
@@ -544,6 +554,60 @@ loot --filter creds
# Display the contents of a piece of loot:
loot fetch
`
reactionHelp = fmt.Sprintf(`[[.Bold]]Command:[[.Normal]] reaction
[[.Bold]]About:[[.Normal]] Automate commands in reaction to event(s).
[[.Bold]]Reactable Events:[[.Normal]]
% 20s Triggered when a new session is opened to a target
% 20s Triggered on changes to session metadata
% 20s Triggered when a session is closed (for any reason)
% 20s Triggered when a canary is burned or created
% 20s Triggered when implants are discovered on threat intel platforms
% 20s Triggered when a new piece of loot is added to the server
% 20s Triggered when a piece of loot is removed from the server
`,
consts.SessionOpenedEvent,
consts.SessionUpdateEvent,
consts.SessionClosedEvent,
consts.CanaryEvent,
consts.WatchtowerEvent,
consts.LootAddedEvent,
consts.LootRemovedEvent,
)
reactionSetHelp = fmt.Sprintf(`[[.Bold]]Command:[[.Normal]] reaction set
[[.Bold]]About:[[.Normal]] Set automated commands in reaction to event(s).
[[.Bold]]Examples:[[.Normal]]
# The command uses interactive menus to build a reaction. Simply run:
reaction set
[[.Bold]]Reactable Events:[[.Normal]]
% 20s Triggered when a new session is opened to a target
% 20s Triggered on changes to session metadata
% 20s Triggered when a session is closed (for any reason)
% 20s Triggered when a canary is burned or created
% 20s Triggered when implants are discovered on threat intel platforms
% 20s Triggered when a new piece of loot is added to the server
% 20s Triggered when a piece of loot is removed from the server
`,
consts.SessionOpenedEvent,
consts.SessionUpdateEvent,
consts.SessionClosedEvent,
consts.CanaryEvent,
consts.WatchtowerEvent,
consts.LootAddedEvent,
consts.LootRemovedEvent,
)
reactionUnsetHelp = `[[.Bold]]Command:[[.Normal]] reaction unset
[[.Bold]]About:[[.Normal]] Unset/remove automated commands in reaction to event(s).
[[.Bold]]Examples:[[.Normal]]
# Remove a reaction
reaction unset --id 1
`
)
@@ -568,7 +632,7 @@ const (
// GetHelpFor - Get help string for a command
func GetHelpFor(cmdName []string) string {
if 0 < len(cmdName) {
if helpTmpl, ok := cmdHelp[strings.Join(cmdName, ".")]; ok {
if helpTmpl, ok := cmdHelp[strings.Join(cmdName, sep)]; ok {
return FormatHelpTmpl(helpTmpl)
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ func GetSystemCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
return
}
process := ctx.Flags.String("process")
config := con.GetActiveSliverConfig()
config := con.GetActiveSessionConfig()
ctrl := make(chan bool)
con.SpinUntil("Attempting to create a new sliver session as 'NT AUTHORITY\\SYSTEM'...", ctrl)
+18
View File
@@ -1,5 +1,23 @@
package processes
/*
Sliver Implant Framework
Copyright (C) 2021 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"
+18
View File
@@ -1,5 +1,23 @@
package processes
/*
Sliver Implant Framework
Copyright (C) 2021 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"
+5
View File
@@ -0,0 +1,5 @@
Reaction
=========
Commands to set a reaction, or unset a reaction. Reactions allow the operator to automate commands in response to event(s).
+80
View File
@@ -0,0 +1,80 @@
package reaction
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import (
"encoding/json"
"io/ioutil"
"path"
"github.com/bishopfox/sliver/client/assets"
"github.com/bishopfox/sliver/client/core"
)
const (
ReactionFileName = "reactions.json"
)
// GetReactionFilePath - Get the
func GetReactionFilePath() string {
return path.Join(assets.GetRootAppDir(), ReactionFileName)
}
// SaveReactions - Save the reactions to the reaction file
func SaveReactions(reactions []core.Reaction) error {
reactionFilePath := GetReactionFilePath()
data, err := json.MarshalIndent(reactions, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(reactionFilePath, data, 0600)
}
// LoadReactions - Save the reactions to the reaction file
func LoadReactions() (int, error) {
reactionFilePath := GetReactionFilePath()
data, err := ioutil.ReadFile(reactionFilePath)
if err != nil {
return 0, err
}
reactions := []core.Reaction{}
err = json.Unmarshal(data, &reactions)
if err != nil {
return 0, err
}
for _, oldReaction := range core.Reactions.All() {
core.Reactions.Remove(oldReaction.ID)
}
for _, reaction := range reactions {
if !isReactable(reaction) {
continue
}
core.Reactions.Add(reaction)
}
return len(reactions), nil
}
func isReactable(reaction core.Reaction) bool {
for _, eventType := range core.ReactableEvents {
if reaction.EventType == eventType {
return true
}
}
return false
}
+90
View File
@@ -0,0 +1,90 @@
package reaction
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"bytes"
"fmt"
"strings"
"text/tabwriter"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/core"
"github.com/desertbit/grumble"
)
// ReactionCmd - Manage reactions to events
func ReactionCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
totalReactions := 0
for _, eventType := range core.ReactableEvents {
reactions := core.Reactions.On(eventType)
if 0 < len(reactions) {
displayReactionsTable(eventType, reactions, con)
}
totalReactions += len(reactions)
}
if totalReactions == 0 {
con.PrintInfof("No reactions set\n")
}
}
func displayReactionsTable(eventType string, reactions []core.Reaction, con *console.SliverConsoleClient) {
// Title
con.Printf("%s%s%s\n", console.Bold, EventTypeToTitle(eventType), console.Normal)
// Table
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
fmt.Fprintf(table, "ID\tCommands\t\n")
fmt.Fprintf(table, "%s\t%s\t\n",
strings.Repeat("=", len("ID")),
strings.Repeat("=", len("Commands")),
)
for _, react := range reactions {
fmt.Fprintf(table, "%d\t%s\t\n",
react.ID, strings.Join(react.Commands, ","),
)
}
table.Flush()
con.Printf("%s", outputBuf.String())
}
// EventTypeToTitle - Convert an eventType to a more human friendly string
func EventTypeToTitle(eventType string) string {
switch eventType {
case consts.SessionOpenedEvent:
return "Session Opened"
case consts.SessionClosedEvent:
return "Session Closed"
case consts.SessionUpdateEvent:
return "Session Updated"
case consts.CanaryEvent:
return "Canary Trigger"
case consts.WatchtowerEvent:
return "Watchtower Trigger"
default:
return eventType
}
}
+48
View File
@@ -0,0 +1,48 @@
package reaction
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/desertbit/grumble"
)
// ReactionSaveCmd - Manage reactions to events
func ReactionReloadCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
if _, err := os.Stat(GetReactionFilePath()); os.IsNotExist(err) {
con.PrintWarnf("Missing reaction file %s\n", GetReactionFilePath())
return
}
confirm := false
prompt := &survey.Confirm{Message: "Reload reactions from disk?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
n, err := LoadReactions()
if err != nil {
con.PrintErrorf("Failed to load reactions: %s\n", err)
}
con.Println()
con.PrintInfof("Reloaded %d reactions from disk\n", n)
}
+46
View File
@@ -0,0 +1,46 @@
package reaction
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"os"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/desertbit/grumble"
)
// ReactionSaveCmd - Manage reactions to events
func ReactionSaveCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
if _, err := os.Stat(GetReactionFilePath()); !os.IsNotExist(err) {
confirm := false
prompt := &survey.Confirm{Message: "Overwrite reactions on disk?"}
survey.AskOne(prompt, &confirm)
if !confirm {
return
}
}
err := SaveReactions(core.Reactions.All())
if err != nil {
con.PrintErrorf("%s\n", err)
} else {
con.PrintInfof("Saved reactions to disk\n")
}
}
+106
View File
@@ -0,0 +1,106 @@
package reaction
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"errors"
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/desertbit/grumble"
)
var (
// ErrNonReactableEvent - Event does not exist or is not supported by reactions
ErrNonReactableEvent = errors.New("non-reactable event type")
)
// ReactionSetCmd - Set a reaction upon an event
func ReactionSetCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
eventType, err := getEventType(ctx, con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
con.Println()
con.PrintInfof("Setting reaction to: %s\n", EventTypeToTitle(eventType))
con.Println()
rawCommands, err := userCommands()
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
commands := []string{}
for _, rawCommand := range strings.Split(rawCommands, "\n") {
if rawCommand != "" {
commands = append(commands, rawCommand)
}
}
reaction := core.Reactions.Add(core.Reaction{
EventType: eventType,
Commands: commands,
})
con.Println()
con.PrintInfof("Set reaction to %s (id: %d)\n", eventType, reaction.ID)
}
func getEventType(ctx *grumble.Context, con *console.SliverConsoleClient) (string, error) {
rawEventType := ctx.Flags.String("event")
if rawEventType == "" {
return selectEventType(con)
} else {
for _, eventType := range core.ReactableEvents {
if eventType == rawEventType {
return eventType, nil
}
}
return "", ErrNonReactableEvent
}
}
func selectEventType(con *console.SliverConsoleClient) (string, error) {
prompt := &survey.Select{
Message: "Select an event:",
Options: core.ReactableEvents,
}
selection := ""
err := survey.AskOne(prompt, &selection)
if err != nil {
return "", err
}
for _, eventType := range core.ReactableEvents {
if eventType == selection {
return eventType, nil
}
}
return "", ErrNonReactableEvent
}
func userCommands() (string, error) {
text := ""
prompt := &survey.Multiline{
Message: "Enter commands: ",
}
err := survey.AskOne(prompt, &text)
return text, err
}
+84
View File
@@ -0,0 +1,84 @@
package reaction
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"bytes"
"errors"
"fmt"
"strings"
"text/tabwriter"
"github.com/AlecAivazis/survey/v2"
"github.com/bishopfox/sliver/client/console"
"github.com/bishopfox/sliver/client/core"
"github.com/desertbit/grumble"
)
// ReactionUnsetCmd - Unset a reaction upon an event
func ReactionUnsetCmd(ctx *grumble.Context, con *console.SliverConsoleClient) {
reactionID := ctx.Flags.Int("id")
if reactionID == 0 {
reaction, err := selectReaction(con)
if err != nil {
con.PrintErrorf("%s\n", err)
return
}
reactionID = reaction.ID
}
success := core.Reactions.Remove(reactionID)
if success {
con.Println()
con.PrintInfof("Successfully removed reaction with id %d", reactionID)
} else {
con.PrintWarnf("No reaction found with id %d", reactionID)
}
con.Println()
}
func selectReaction(con *console.SliverConsoleClient) (*core.Reaction, error) {
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
allReactions := core.Reactions.All()
for _, react := range allReactions {
fmt.Fprintf(table, "%d\t%s\t%s\t\n",
react.ID, react.EventType, strings.Join(react.Commands, ", "),
)
}
table.Flush()
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove blank line at the end
// Prompt user with options
prompt := &survey.Select{
Message: "Select a reaction:",
Options: options,
}
selection := ""
err := survey.AskOne(prompt, &selection)
if err != nil {
return nil, err
}
for index, option := range options {
if option == selection {
return &allReactions[index], nil
}
}
return nil, errors.New("reaction not found")
}
+47 -9
View File
@@ -41,6 +41,7 @@ import (
"time"
"github.com/desertbit/go-shlex"
"github.com/desertbit/grumble"
"github.com/fatih/color"
)
@@ -203,11 +204,47 @@ func (con *SliverConsoleClient) EventLoop() {
con.Println()
}
con.triggerReactions(event)
con.Printf(Clearln + con.GetPrompt())
bufio.NewWriter(con.App.Stdout()).Flush()
}
}
func (con *SliverConsoleClient) triggerReactions(event *clientpb.Event) {
reactions := core.Reactions.On(event.EventType)
if len(reactions) == 0 {
return
}
// We need some special handling for SessionOpenedEvent to
// set the new session as the active session
currentActiveSession := con.ActiveSession.Get()
defer con.ActiveSession.Set(currentActiveSession)
con.ActiveSession.Set(nil)
if event.EventType == consts.SessionOpenedEvent {
if event.Session == nil || event.Session.OS == "" {
return // Half-open session, do not execute any command
}
con.ActiveSession.Set(event.Session)
}
for _, reaction := range reactions {
for _, line := range reaction.Commands {
con.PrintInfof("Execute reaction: '%s'\n", line)
args, err := shlex.Split(line, true)
if err != nil {
con.PrintErrorf("Reaction command has invalid args: %s\n", err)
continue
}
err = con.App.RunCommand(args)
if err != nil {
con.PrintErrorf("Reaction command error: %s\n", err)
}
}
}
}
func (con *SliverConsoleClient) GetPrompt() string {
prompt := Underline + "sliver" + Normal
if con.IsServer {
@@ -233,16 +270,16 @@ func (con *SliverConsoleClient) PrintLogo() {
insecureRand.Seed(time.Now().Unix())
logo := asciiLogos[insecureRand.Intn(len(asciiLogos))]
fmt.Println(logo)
fmt.Println("All hackers gain " + abilities[insecureRand.Intn(len(abilities))])
fmt.Printf(Info+"Server v%s - %s%s\n", serverSemVer, serverVer.Commit, dirty)
con.Println(logo)
con.Println("All hackers gain " + abilities[insecureRand.Intn(len(abilities))])
con.Printf(Info+"Server v%s - %s%s\n", serverSemVer, serverVer.Commit, dirty)
if version.GitCommit != serverVer.Commit {
fmt.Printf(Info+"Client %s\n", version.FullVersion())
con.Printf(Info+"Client %s\n", version.FullVersion())
}
fmt.Println(Info + "Welcome to the sliver shell, please type 'help' for options")
fmt.Println()
con.Println(Info + "Welcome to the sliver shell, please type 'help' for options")
con.Println()
if serverVer.Major != int32(version.SemanticVersion()[0]) {
fmt.Printf(Warn + "Warning: Client and server may be running incompatible versions.\n")
con.Printf(Warn + "Warning: Client and server may be running incompatible versions.\n")
}
con.CheckLastUpdate()
}
@@ -259,7 +296,7 @@ func (con *SliverConsoleClient) CheckLastUpdate() {
day := 24 * time.Hour
if compiledAt.Add(30 * day).Before(now) {
if lastUpdate == nil || lastUpdate.Add(30*day).Before(now) {
fmt.Printf(Info + "Check for updates with the 'update' command\n\n")
con.Printf(Info + "Check for updates with the 'update' command\n\n")
}
}
}
@@ -312,7 +349,8 @@ func (con *SliverConsoleClient) GetSessionsByName(name string) []*clientpb.Sessi
return matched
}
func (con *SliverConsoleClient) GetActiveSliverConfig() *clientpb.ImplantConfig {
// GetActiveSessionConfig - Get the active sessions's config
func (con *SliverConsoleClient) GetActiveSessionConfig() *clientpb.ImplantConfig {
session := con.ActiveSession.Get()
if session == nil {
return nil
+13 -8
View File
@@ -105,11 +105,16 @@ const (
ProfilesStr = "profiles"
// Generic
NewStr = "new"
AddStr = "add"
StartStr = "start"
StopStr = "stop"
// RmStr - Already defined below for "rm" command
// NewStr - "new"
NewStr = "new"
AddStr = "add"
StartStr = "start"
StopStr = "stop"
SetStr = "set"
UnsetStr = "unset"
SaveStr = "save"
ReloadStr = "reload"
LootStr = "loot"
LootLocalStr = "local"
@@ -181,9 +186,7 @@ const (
PsExecStr = "psexec"
BackdoorStr = "backdoor"
MakeTokenStr = "make-token"
GetEnvStr = "getenv"
SetEnvStr = "setenv"
UnsetEnvStr = "unsetenv"
EnvStr = "env"
RegistryStr = "registry"
RegistryReadStr = "read"
RegistryWriteStr = "write"
@@ -199,6 +202,8 @@ const (
PortfwdStr = "portfwd"
ReactionStr = "reaction"
LicensesStr = "licenses"
)
+118
View File
@@ -0,0 +1,118 @@
package core
/*
Sliver Implant Framework
Copyright (C) 2021 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 (
"sync"
consts "github.com/bishopfox/sliver/client/constants"
)
var (
// Reactions - Manages/tracks reactions
Reactions = &reactions{
reactionMap: map[string][]Reaction{},
mutex: &sync.RWMutex{},
}
// ReactableEvents - A list of reactionable events
ReactableEvents = []string{
consts.SessionOpenedEvent,
consts.SessionUpdateEvent,
consts.SessionClosedEvent,
consts.CanaryEvent,
consts.WatchtowerEvent,
consts.LootAddedEvent,
consts.LootRemovedEvent,
// Not sure if we want to add these or not:
// consts.JobStartedEvent,
// consts.JobStoppedEvent,
// consts.BuildEvent,
// consts.BuildCompletedEvent,
// consts.ProfileEvent,
// consts.WebsiteEvent,
}
)
type reactions struct {
reactionMap map[string][]Reaction
mutex *sync.RWMutex
reactionID int
}
// Add - Add a reaction
func (r *reactions) Add(reaction Reaction) Reaction {
r.mutex.Lock()
defer r.mutex.Unlock()
r.reactionID++
reaction.ID = r.reactionID
if eventActions, ok := r.reactionMap[reaction.EventType]; ok {
r.reactionMap[reaction.EventType] = append(eventActions, reaction)
} else {
r.reactionMap[reaction.EventType] = []Reaction{reaction}
}
return reaction
}
// Remove - Remove a reaction, yes we're using linear search but this isn't exactly
// a performance critical piece of code and the map/slice is going to be very small
func (r *reactions) Remove(reactionID int) bool {
r.mutex.Lock()
defer r.mutex.Unlock()
for eventType, eventReactions := range r.reactionMap {
for index, reaction := range eventReactions {
if reaction.ID == reactionID {
r.reactionMap[eventType] = append(eventReactions[:index], eventReactions[index+1:]...)
return true
}
}
}
return false
}
// On - Get all reactions of a specific type
func (r *reactions) On(eventType string) []Reaction {
r.mutex.RLock()
defer r.mutex.RUnlock()
if reactions, ok := r.reactionMap[eventType]; ok {
tmp := make([]Reaction, len(reactions))
copy(tmp, reactions)
return tmp
}
return []Reaction{}
}
// All - Get all reactions (returns a flat list with all event types)
func (r *reactions) All() []Reaction {
r.mutex.RLock()
defer r.mutex.RUnlock()
reactions := []Reaction{}
for _, eventReactions := range r.reactionMap {
reactions = append(reactions, eventReactions...)
}
return reactions
}
// Reaction - Metadata about a portfwd listener
type Reaction struct {
ID int `json:"-"`
EventType string `json:"event_type"`
Commands []string `json:"commands"`
}