Files
2026-02-07 15:46:17 -08:00

242 lines
6.6 KiB
Go

package taskmany
/*
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
Copyright (C) 2023 ActualTrash
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"
"context"
"fmt"
"sort"
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/client/forms"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/protobuf/commonpb"
)
func Command(con *console.SliverClient) []*cobra.Command {
taskmanyCmd := &cobra.Command{
Use: consts.TaskmanyStr,
Short: "Task many beacons or sessions",
Long: help.GetHelpFor([]string{consts.TaskmanyStr}),
GroupID: consts.SliverHelpGroup,
Run: func(cmd *cobra.Command, args []string) {
TaskmanyCmd(cmd, con, args)
},
}
// Add the relevant beacon commands as a subcommand to taskmany
// taskmanyCmds := map[string]bool{
// consts.ExecuteStr: true,
// consts.LsStr: true,
// consts.CdStr: true,
// consts.MkdirStr: true,
// consts.RmStr: true,
// consts.UploadStr: true,
// consts.DownloadStr: true,
// consts.InteractiveStr: true,
// consts.ChmodStr: true,
// consts.ChownStr: true,
// consts.ChtimesStr: true,
// consts.PwdStr: true,
// consts.CatStr: true,
// consts.MvStr: true,
// consts.PingStr: true,
// consts.NetstatStr: true,
// consts.PsStr: true,
// consts.IfconfigStr: true,
// }
// for _, c := range SliverCommands(con)().Commands() {
// _, ok := taskmanyCmds[c.Use]
// if ok {
// taskmanyCmd.AddCommand(WrapCommand(c, con))
// }
// }
return []*cobra.Command{taskmanyCmd}
}
// TaskmanyCmd - Task many beacons / sessions
func TaskmanyCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
con.PrintErrorf("Must specify subcommand. See taskmany --help for supported subcommands.\n")
}
// Helper function to wrap grumble commands with taskmany logic
func WrapCommand(c *cobra.Command, con *console.SliverClient) *cobra.Command {
wc := &cobra.Command{
Use: c.Use,
Short: c.Short,
Long: c.Long,
Args: c.Args,
Run: wrapFunctionWithTaskmany(con, c.Run),
}
wc.Flags().AddFlagSet(c.Flags())
wc.PersistentFlags().AddFlagSet(c.PersistentFlags())
return wc
}
// Wrap a function to run it for each beacon / session
func wrapFunctionWithTaskmany(con *console.SliverClient, f func(cmd *cobra.Command, args []string)) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
defer con.Println()
sessions, beacons, err := SelectMultipleBeaconsAndSessions(con)
if err != nil {
con.Println()
con.PrintErrorf("%s\n", err)
return
}
con.Println()
// Save current active beacon or session
origSession, origBeacon := con.ActiveTarget.Get()
nB := 0
nBSkipped := 0
for _, b := range beacons {
if !b.IsDead {
con.ActiveTarget.Set(nil, b)
f(cmd, args)
nB += 1
} else {
nBSkipped += 1
}
}
nS := 0
nSSkipped := 0
for _, s := range sessions {
if !s.IsDead {
con.ActiveTarget.Set(s, nil)
f(cmd, args)
nS += 1
} else {
nSSkipped += 1
}
}
// Restore active session / beacon
con.ActiveTarget.Set(origSession, origBeacon)
con.PrintInfof("Tasked %d sessions and %d beacons >:D\n", nS, nB)
if nBSkipped > 0 || nSSkipped > 0 {
con.PrintWarnf("Skipped %d dead sessions and %d dead beacons\n", nSSkipped, nBSkipped)
}
}
}
func SelectMultipleBeaconsAndSessions(con *console.SliverClient) ([]*clientpb.Session, []*clientpb.Beacon, error) {
// Get and sort sessions
sessionsObj, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, nil, err
}
sessions := sessionsObj.Sessions
sort.Slice(sessions, func(i, j int) bool {
return sessions[i].ID < sessions[j].ID
})
// Get and sort beacons
beaconsObj, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, nil, err
}
beacons := beaconsObj.Beacons
sort.Slice(beacons, func(i, j int) bool {
return beacons[i].ID < beacons[j].ID
})
if len(beacons) == 0 && len(sessions) == 0 {
return nil, nil, fmt.Errorf("no sessions or beacons 🙁")
}
// Render selection table
outputBuf := bytes.NewBufferString("")
table := tabwriter.NewWriter(outputBuf, 0, 2, 2, ' ', 0)
sessionOptionMap := map[string]*clientpb.Session{}
for _, session := range sessions {
option := fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\t%s",
"SESSION",
strings.Split(session.ID, "-")[0],
session.Name,
session.RemoteAddress,
session.Hostname,
session.Username,
fmt.Sprintf("%s/%s", session.OS, session.Arch),
)
fmt.Fprintln(table, option)
o := strings.ReplaceAll(option, "\t", "")
sessionOptionMap[o] = session
}
beaconOptionMap := map[string]*clientpb.Beacon{}
for _, beacon := range beacons {
option := fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\t%s",
"BEACON",
strings.Split(beacon.ID, "-")[0],
beacon.Name,
beacon.RemoteAddress,
beacon.Hostname,
beacon.Username,
fmt.Sprintf("%s/%s", beacon.OS, beacon.Arch),
)
fmt.Fprintln(table, option)
o := strings.ReplaceAll(option, "\t", "")
beaconOptionMap[o] = beacon
}
table.Flush()
options := strings.Split(outputBuf.String(), "\n")
options = options[:len(options)-1] // Remove the last empty option
selected := []string{}
_ = forms.MultiSelect("Select sessions and beacons:", options, &selected)
if len(selected) == 0 {
return nil, nil, fmt.Errorf("no sessions or beacons selected 🤔")
}
selectedSessions := []*clientpb.Session{}
selectedBeacons := []*clientpb.Beacon{}
for _, s := range selected {
s = strings.ReplaceAll(s, " ", "")
s = strings.ReplaceAll(s, "\t", "")
session, ok := sessionOptionMap[s]
if ok {
selectedSessions = append(selectedSessions, session)
}
beacon, ok := beaconOptionMap[s]
if ok {
selectedBeacons = append(selectedBeacons, beacon)
}
}
return selectedSessions, selectedBeacons, nil
}